/*
 * Base styles.
 *
 * **Zwei Paletten, und das Gerät entscheidet.** Dunkel ist der Normalfall — so
 * hat die Anwendung immer ausgesehen, und am Abend über Stunden ist sie das
 * Richtige. Wer sein Telefon tagsüber auf hell stehen hat, bekommt seither
 * aber eine helle Fassung: `prefers-color-scheme` ist die Ansage des
 * Betriebssystems, und die meint auch diese Seite.
 *
 * Dunkel steht deshalb in `:root` und nicht in einer Abfrage — wer nicht
 * antwortet, bekommt das, was hier immer galt.
 *
 * **Keine Farbe steht zweimal im Blatt.** Wer unten einen Wert hinschreibt,
 * statt eine Marke zu nehmen, baut eine Stelle, die im Hellen falsch ist und
 * die niemand findet. Genau das waren die Schatten (`rgba(0,0,0,.8)` unter
 * einem Porträt ist auf Papier ein Fleck) und die Schrift auf einem gefüllten
 * Knopf, die als `#16151a` dreimal im Blatt stand.
 */

:root {
  /* Die Ansage an den Browser. Rollbalken, Auswahlfelder, Ankreuzfelder und
     die Textmarkierung malt er selbst — ohne diese Zeile bleiben sie hell,
     während die Seite dunkel ist, und der Rollbalken ist auf dem Telefon der
     hellste Streifen im Bild. `dark` zuerst: Hat das Gerät keine Meinung,
     gilt das Dunkel. */
  color-scheme: dark light;

  --bg: #16151a;
  --bg-raised: #1e1d24;
  --bg-sunken: #121116;
  /* Was die Spielleitung erzählt. Eine Spur wärmer als eine Blase, nicht mehr —
     die Erzählung soll sich vom Gesagten abheben, ohne lauter zu werden. */
  --bg-narration: color-mix(in oklab, #1e1d24 93%, #c9a227);
  --border: #322f3b;
  /* **Weniger Kontrast, nicht mehr.** Hier stand `#e6e2dc`, gemessen 14,1 : 1
     gegen den Grund. Auf hellem Papier wäre das vorbildlich; auf dunklem Grund
     ist es das Gegenteil: Helle Schrift auf sehr dunklem Grund blüht aus, die
     Ränder der Buchstaben glimmen ineinander, und bei Astigmatismus verschwimmt
     die Zeile. Jetzt 12,1 : 1 — immer noch weit über jeder Anforderung und
     nach einer Stunde Lesen deutlich ruhiger. */
  --text: #d7d2cb;
  --text-muted: #a29ba8;
  --accent: #c9a227;
  --accent-soft: #3a3120;
  /* Die Schrift auf einer gefüllten Fläche — also auf dem Akzent selbst. */
  --on-accent: #16151a;
  --danger: #d4736a;
  /* Die Verzweiflungswürfel — im Buch orange, hier ebenso: Wer sie von Hand
     einträgt, soll sie nicht mit den Vorratswürfeln verwechseln. */
  --desperation: #e08a3c;
  /* Das Blatt in der Maschine. Eine Spur wärmer als der versenkte Grund —
     dieselbe Bewegung wie bei der Erzählung, nur leiser. */
  --bg-paper: color-mix(in oklab, #121116 94%, #c9a227);
  --shadow-bubble: 0 2px 10px rgb(0 0 0 / 0.45);
  --shadow-portrait: inset 0 0 12px rgb(0 0 0 / 0.8), 0 0 0 1px rgb(0 0 0 / 0.6);
  --backdrop: rgb(0 0 0 / 0.65);
  --radius: 6px;

  /* **Der Stapel ist nachgesehen, nicht geraten.** `Iowan Old Style` gibt es
     nur auf iOS, `Palatino Linotype` nur auf Windows, `Georgia` nicht unter
     Linux und **nicht auf Android**. Übrig blieb dort das nackte `serif`, und
     was das ist, entscheidet das Gerät. Gemessen kommt heraus: iOS Iowan,
     Windows Palatino, Linux P052, Android Noto Serif. Die stehen jetzt beim
     Namen, damit man weiß, was man vor sich hat. */
  --font: "Iowan Old Style", "Palatino Linotype", Palatino, "P052", Georgia,
          "Noto Serif", "Liberation Serif", serif;
  --font-ui: system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SFMono-Regular", Menlo, monospace;

  /* **Die Maschine, in die geschrieben wird.** Nicht `--font-mono`: Das ist
     `ui-monospace` und damit SF Mono oder Cascadia — Schriften zum
     Programmieren, mit geraden Enden und großem Auge. Eine Schreibmaschine hat
     Serifen an den Stämmen und dünne Striche.
     Wieder nachgesehen statt behauptet: `"Courier New"` löst **unter Linux auf
     Liberation Mono** auf, und das ist keine Courier, sondern eine serifenlose
     Mono. Die echte heißt hier `Nimbus Mono PS` und muss deshalb **vor** ihr
     stehen. Android hat überhaupt keine Courier; `serif-monospace` ist der
     Name, unter dem Chrome dort Cutive Mono findet — steht als Versuch drin und
     schadet nichts, wo es ihn nicht gibt. */
  --font-type: "Courier Prime", "Nimbus Mono PS", "American Typewriter", Courier,
               "Courier New", "serif-monospace", "Liberation Mono", monospace;

  /* — Was beim langen Lesen zählt ————————————————————————————————————
   *
   * `--reading-scale` setzt der Schieber im Chat, und zwar auf `<html>`: Die
   * Einstellung gilt damit auch auf den Regelseiten, ohne dass dort ein
   * Bedienelement stünde. Vorbelegt wird sie in `<head>` aus dem
   * Browserspeicher, **vor** dem ersten Malen — sonst springt der ganze
   * Verlauf, sobald Stimulus geladen hat.
   *
   * `--measure` ist die Zeilenbreite. Erzähltext lief über die volle Seite —
   * im Browser nachgemessen **112 Zeichen** je Zeile; angenehm liest sich etwa
   * die Hälfte.
   * Beim Rücksprung an den Zeilenanfang muss das Auge die richtige nächste
   * Zeile treffen, und je länger die Zeile, desto öfter misslingt das. In
   * `rem` und nicht in `ch`: `ch` misst in der Schrift des Elements, und die
   * ist hier auf jedem Gerät eine andere — dann wäre die Zeile auf Android
   * wieder länger als hier. Mit `--reading-scale` multipliziert, damit die
   * Spalte mit der Schrift wächst und nicht die Zeichenzahl. */
  /* Die Farben der Sprecher (`Character#color_slot`): Name, Blasenkante,
     Porträtrahmen. Acht, weil die fünf, sechs Hauptsprecher einer Runde fast
     den ganzen Verlauf ausmachen. Kein Gold darunter — das gehört der
     Spielleitung. Gemessen gegen `--bg-raised`: 6,3 bis 8,4 : 1. */
  --speaker-0: #6cc4d8;
  --speaker-1: #e391a8;
  --speaker-2: #8fc47a;
  --speaker-3: #b7a0e0;
  --speaker-4: #e8906f;
  --speaker-5: #86aeea;
  --speaker-6: #d184cc;
  --speaker-7: #5fbfa9;
  --reading-scale: 1;
  --reading-size: calc(1.02rem * var(--reading-scale));
  --measure: calc(34rem * var(--reading-scale));
}

/* Hell. Nur die Marken werden getauscht, keine einzige Regel weiter unten
   kennt die Palette. Warm gehalten und nicht auf Weiß: Reines Weiß strahlt
   auf einem Telefon fast so unangenehm wie zu harter Kontrast im Dunkeln.
   Gemessen 12,7 : 1 für Text auf Grund, 6,2 : 1 für den Akzent — das Gold von
   oben ist auf Papier unlesbar und muss dunkler werden. */
@media (prefers-color-scheme: light) {
  :root {
    --bg: #f2eee6;
    --bg-raised: #fbf8f2;
    --bg-sunken: #e6e0d3;
    --bg-narration: color-mix(in oklab, #fbf8f2 92%, #6f540a);
    --border: #d5cdbb;
    --text: #2b2822;
    --text-muted: #6a6459;
    --accent: #6f540a;
    --accent-soft: #f0e6ca;
    --on-accent: #fbf8f2;
    --danger: #a0392f;
    --desperation: #a8560f;
    /* Im Hellen ist das Blatt heller als die Karte darum — ein frischer Bogen,
       kein Feld in einem Formular. */
    --bg-paper: #fdfcf7;
    /* Auf Papier wirft nichts so schwarze Schatten. */
    --shadow-bubble: 0 1px 3px rgb(43 40 34 / 0.12);
    --shadow-portrait: 0 0 0 1px rgb(43 40 34 / 0.08);
    --backdrop: rgb(43 40 34 / 0.4);
    /* Dieselben Töne, dunkel genug für Papier: 5,6 bis 6,9 : 1. */
    --speaker-0: #1d6b7a;
    --speaker-1: #a33c5a;
    --speaker-2: #3f6e2b;
    --speaker-3: #61469c;
    --speaker-4: #a34a28;
    --speaker-5: #33589c;
    --speaker-6: #86377f;
    --speaker-7: #256e5d;
  }
}

/* Der Satz für lange Prosa. Steht an einer Stelle, weil er an drei Orten
   gebraucht wird: im Verlauf, in der Vorschau des Prüfschritts und in allem,
   was durch Markdown gelaufen ist.
   `font-size-adjust` ist der Ausgleich für den Stapel oben: Bei gleicher
   `font-size` steht Noto Serif mit einer x-Höhe von 0,536 em im Bild, P052 mit
   0,449 — derselbe Absatz ist auf Android also ein Fünftel größer als hier.
   Die Zahl normiert die x-Höhe, und erst danach heißt „1,02rem" auf jedem
   Gerät dasselbe.
   `hyphens` braucht das `lang="de"` am `<html>`, das dort steht: Ohne
   Silbentrennung reißt ein deutsches Kompositum in eine schmale Blase Löcher,
   die halb so breit sind wie die Spalte. */
.message__body,
.compose__preview,
.prose {
  font-family: var(--font);
  font-size: var(--reading-size);
  font-size-adjust: 0.5;
  line-height: 1.7;
  hyphens: auto;
  text-wrap: pretty;
}

/* Im Verlauf hängt das Maß am ganzen Block: Dort ist auch die Karte darum auf
   dasselbe gedeckelt, und ein Beitrag ist Prosa und sonst nichts. */
.message__body, .compose__preview { max-width: var(--measure); }

/* **Auf einer Regelseite nur der Fließtext.** Hier hing das Maß erst an
   `.prose` selbst — und dann quetschte sich der Werteblock einer Figur auf
   34rem und rollte, obwohl daneben 400 px frei standen. Eine Tabelle ist keine
   Zeile, die das Auge zurückspringen muss; sie will die Breite, die da ist.
   Dasselbe gilt für ein Bild und für einen Block Auszeichnung. */
.prose > :is(p, ul, ol, dl, blockquote, h1, h2, h3, h4, h5, h6) {
  max-width: var(--measure);
}

/* Was nicht gelesen, sondern abgelesen wird: eine Würfelformel, ein Bezeichner,
   ein Stück Auszeichnung. Getrennt wird da nichts, und breit darf es sein. */
:is(.message__body, .compose__preview, .prose) :is(code, pre, kbd, samp),
.message--roll_request .message__body {
  hyphens: none;
  text-wrap: wrap;
}

* { box-sizing: border-box; }

/* Die Regelseite und die Welt haben ein Inhaltsverzeichnis mit Ankern. Ohne
   diese Zeile landet die angesprungene Überschrift genau unter der klebenden
   Leiste — man klickt auf einen Abschnitt und sieht seine Überschrift nicht. */
html { scroll-padding-top: 4rem; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.6;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.page {
  max-width: 62rem;
  margin: 0 auto;
  padding: 2rem 1.25rem 4rem;
}

/* Die Verwaltung arbeitet in Tabellen und Listen — sie bekommt die volle
   Breite. Auf dem Tablet ändert sich nichts: die Raster brechen um, und
   Tabellen rollen in ihrem `.table-wrap`. */
.page--admin { max-width: none; padding-inline: clamp(1.25rem, 3vw, 3rem); }

/* Top bar --------------------------------------------------------------- */

/* **Klebend, weil der Verlauf endlos ist.** Eine Kampagne, die einen Abend
   gelaufen ist, füllt mehrere Bildschirmhöhen, und die Seite steht beim Öffnen
   ganz unten — bis zur Navigation wäre es sonst jedes Mal der ganze Weg nach
   oben.

   `sticky` und nicht `fixed`: Die Leiste bleibt damit im Fluss, die Höhe des
   Dokuments ändert sich nicht. Genau darauf rechnen `chat_controller`
   (`scrollHeight - innerHeight - scrollY`, „hängt der Blick unten?") und
   `history_controller` (Höhendifferenz nach dem Nachladen älterer Beiträge).
   Mit `fixed` bliebe die Rechnung zwar richtig, aber der erste Beitrag läge
   unter der Leiste.

   `z-index` ist nicht Zierde: **jede Blase im Chat ist `position: relative`**
   (`.bubble`), und positionierte Geschwister ohne Stapelwert werden nach
   Dokumentreihenfolge gemalt. Ohne diese Zeile schöbe sich jeder Beitrag beim
   Vorbeiscrollen über die Leiste. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.25rem;
  /* Undurchsichtig — der Verlauf läuft darunter hindurch. */
  background: var(--bg-sunken);
  border-bottom: 1px solid var(--border);
}

.topbar__brand {
  font-family: var(--font);
  font-size: 1.1rem;
  letter-spacing: 0.02em;
  color: var(--text);
}

.topbar__nav {
  display: flex;
  /* Auf dem Telefon bricht die Reihe um, statt die Seite aufzuziehen. */
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.25rem;
  font-size: 0.9rem;
  min-width: 0;
}

/* Die offene Kampagne: ihr Name in der Leiste, ihre Seiten als Menü. */
.topbar__campaign { position: relative; min-width: 0; }

.topbar__campaign > summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--font);
  font-weight: 600;
  color: var(--text);
  /* Ein langer Kampagnenname frisst nicht die Leiste. */
  max-width: min(16rem, 45vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.topbar__campaign > summary::after {
  content: " ▾";
  color: var(--text-muted);
}

.topbar__menu {
  list-style: none;
  position: absolute;
  top: calc(100% + 0.6rem);
  left: 0;
  z-index: 20;
  min-width: 11rem;
  margin: 0;
  padding: 0.35rem 0;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.25);
}

.topbar__menu a {
  display: block;
  padding: 0.45rem 1rem;
  white-space: nowrap;
}

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

/* Typography ------------------------------------------------------------ */

h1, h2, h3 { font-family: var(--font); font-weight: 600; line-height: 1.25; }
h1 { font-size: 1.75rem; margin: 0 0 0.5rem; }
h2 { font-size: 1.3rem; margin: 2rem 0 0.75rem; }
h3 { font-size: 1.05rem; margin: 1.5rem 0 0.5rem; }

.muted { color: var(--text-muted); }
.small { font-size: 0.85rem; }

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

/* Cards and lists ------------------------------------------------------- */

.card {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
}

.card__title { font-family: var(--font); font-size: 1.1rem; margin: 0 0 0.25rem; }

/* Ein archivierter Persona-Abschnitt: da, aber zurückgetreten. */
.card--archived { opacity: 0.6; border-style: dashed; }

/* Bedingung und Ladung eines Auslösers, zweispaltig, umbrechend. */
.trigger { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0.25rem 1rem; margin: 0.5rem 0 0; }
.trigger dt { color: var(--text-muted); font-size: 0.9rem; }
.trigger dd { margin: 0; overflow-wrap: anywhere; }

/* Die Kampagnen stehen nebeneinander, nicht untereinander: Ein 16:9-Band über
   der vollen Blattbreite wäre ein halber Bildschirm für eine Karte. `auto-fill`
   statt fester Spalten — wer zwei Kampagnen hat, soll keine leere dritte Spalte
   sehen, und auf dem Telefon bleibt eine. */
.campaign-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 1fr));
  gap: 1rem;
}

.campaign-grid .card { margin-bottom: 0; }

/* Das Titelbild einer Kampagne — ein **Band** über der Karte: volle Breite,
   16:9, und was nicht hineinpasst, wird abgeschnitten. Anders als beim Porträt
   ist das hier richtig: Ein Band, das mal hoch und mal flach ist, macht aus
   einer Liste eine Treppe. */
.card--cover { display: block; padding: 0; overflow: hidden; }
.card--cover .card__body { padding: 1rem 1.25rem; }

/* Die ganze Karte ist der Link. Die Schrift bleibt Schrift: ein
   unterstrichener Absatz in Akzentfarbe wäre eine Karte, die schreit. */
.card--link { color: inherit; text-decoration: none; }
.card--link:hover { border-color: var(--accent); }
.card--link:hover .card__title { color: var(--accent); }
.card--link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.cover {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  /* Der getönte Grund liegt auch unter einem Bild: Solange die Vorschau einer
     PDF noch nicht gerendert ist, steht hier kein Loch. */
  background: color-mix(in oklab, var(--border) 20%, transparent);
}

/* `object-position: top` — bei einer PDF ist das Bild die erste Seite, und der
   Titel steht oben. Ein mittiger Ausschnitt nähme ihn weg. */
.cover img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center top; }

/* Neben einer Überschrift statt über der Karte — dort ist das Bild Beiwerk. */
.cover--small { width: 132px; border-radius: var(--radius); }

/* Im Formular so breit, dass man den Ausschnitt beurteilen kann. */
.cover--preview { max-width: 380px; border-radius: var(--radius); border: 1px solid var(--border); }

.head-with-cover { display: flex; flex: 0 0 auto; align-items: center; gap: 0.9rem; }
.head-with-cover h1 { margin: 0; }

.empty {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  text-align: center;
  color: var(--text-muted);
}

/* Tables ---------------------------------------------------------------- */

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--border); }
th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); font-weight: 600; }
tbody tr:last-child td { border-bottom: none; }

.table-wrap { overflow-x: auto; background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius); }

/* Lange Listen lesbar: die Zeile unter dem Zeiger hebt sich, jede zweite
   setzt sich leise ab. */
.table-wrap tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--bg-sunken) 45%, transparent); }
.table-wrap tbody tr:hover td { background: var(--bg-sunken); }

/* Die Suchleiste über einer Liste: Feld, Filter, Knopf in einer Reihe, die
   auf dem Tablet umbricht. */
.search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0;
}

.search input[type="search"] { flex: 1 1 14rem; max-width: 26rem; }
.search select { width: auto; }

/* Die Statusseite: Karten mit Ampelrand, das Grüne zusammengefaltet. */
.status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(22rem, 100%), 1fr));
  gap: 1rem;
}

/* Die Karten der Pflege-Inbox. Das Notizfeld füllt die Karte; die
   Antwortknöpfe stehen in einer umbruchfähigen Reihe (flex-wrap!). */
.task-card__form textarea { width: 100%; }
.task-card--decided { border-left: 4px solid var(--accent); }

.status-card { border-left: 4px solid var(--border); margin-bottom: 0; }
.status-card--warn { border-left-color: var(--accent); }
.status-card--critical { border-left-color: var(--danger); }
.status-card__value { font-family: var(--font); font-size: 1.5rem; font-weight: 600; margin: 0 0 0.25rem; }
.status-card--critical .status-card__value { color: var(--danger); }
.status-all-clear { color: var(--text-muted); }

/* Die Ampel in der Kampagnenliste. */
.status-dot { display: inline-block; width: 0.75rem; height: 0.75rem; border-radius: 50%; background: #4f8a4f; }
.status-dot--warn { background: var(--accent); }
.status-dot--critical { background: var(--danger); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.status-fine { margin-top: 1.5rem; }
.status-fine summary { cursor: pointer; }
.status-fine li { margin: 0.4rem 0; }

/* Blättern: Zurück · Stand · Weiter. */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
}

/* Reiter der Verwaltung: zwei Bereiche teilen sich eine Seite, ohne dass
   einer am anderen vorbeigescrollt werden muss. Serverseitig über `?tab=`,
   kein Skript. */
.tabs { display: flex; gap: 0.25rem; margin: 1.25rem 0 1rem; border-bottom: 1px solid var(--border); }
.tabs__tab {
  padding: 0.45rem 1rem; margin-bottom: -1px;
  color: var(--text-muted); text-decoration: none;
  border: 1px solid transparent; border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
}
.tabs__tab:hover { color: inherit; }
.tabs__tab--active {
  color: inherit; background: var(--bg-raised);
  border-color: var(--border);
}

/* Eine Lücke, keine Auszeichnung: gestrichelt und still, damit sie beim
   Überfliegen einer Spalte auffällt, ohne die Zeile an sich zu ziehen. */
.mark {
  display: inline-block;
  padding: 0.05rem 0.5rem;
  border: 1px dashed var(--border);
  border-radius: 999px;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.mark:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }

/* Forms ----------------------------------------------------------------- */

.form { max-width: 34rem; }
.field { margin-bottom: 1rem; }
.field label { display: block; margin-bottom: 0.3rem; font-size: 0.85rem; color: var(--text-muted); }

input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], textarea, select {
  width: 100%;
  padding: 0.55rem 0.7rem;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font: inherit;
}

input:focus, textarea:focus, select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.field--check { display: flex; align-items: center; gap: 0.5rem; }
.field--check label { margin: 0; color: var(--text); }
.field--check input { width: auto; }

.hint { font-size: 0.8rem; color: var(--text-muted); margin-top: 0.25rem; }

/* Buttons --------------------------------------------------------------- */

button, .button, input[type="submit"] {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}

button:hover, .button:hover, input[type="submit"]:hover { background: var(--accent); color: var(--on-accent); text-decoration: none; }

/* **Die Gegenregel, gleich daneben.** `display: inline-block` oben ist eine
   Autorenregel und schlägt die Vorgabe des Browsers für `[hidden]` — ein
   Knopf, der versteckt sein soll, stand also weiter da. Hier ist es der
   Bilder-Knopf, der ohne Bilder nichts aufzumachen hat; dieselbe Falle hatten
   wir schon beim `<dialog>` und bei den Porträts neben dem Eingabefeld.
   Wer eine `display`-Regel auf etwas legt, das später `hidden` sein könnte,
   schreibt die Gegenregel dazu. */
button[hidden], .button[hidden], input[type="submit"][hidden] { display: none; }


.button--quiet { background: transparent; border-color: var(--border); color: var(--text-muted); }
.button--quiet:hover { background: var(--bg-raised); color: var(--text); border-color: var(--text-muted); }
.button--danger { background: transparent; border-color: var(--danger); color: var(--danger); }
.button--danger:hover { background: var(--danger); color: var(--on-accent); }

.linkish {
  padding: 0; background: none; border: none; color: var(--accent);
  font: inherit; font-size: 0.9rem; cursor: pointer;
}
.linkish:hover { background: none; color: var(--accent); text-decoration: underline; }

/* `wrap`, weil fünf Knöpfe nebeneinander auf einem Telefon die ganze Seite
   quer ziehen — dann steht der Verlauf halb außerhalb des Bildschirms. */
.actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 1.5rem; flex-wrap: wrap; }
.actions--inline { margin: 0; }

/* Flash ----------------------------------------------------------------- */

.flash {
  padding: 0.7rem 1rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg-raised);
  margin-bottom: 1.25rem;
}
.flash--alert { border-color: var(--danger); color: var(--danger); }
.flash--notice { border-color: var(--accent); }

/* Errors ---------------------------------------------------------------- */

.errors {
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  color: var(--danger);
}
.errors ul { margin: 0.25rem 0 0; padding-left: 1.2rem; }

/* Chat ------------------------------------------------------------------ */

.chat { display: flex; flex-direction: column; gap: 0.75rem; }

.chat__log {
  display: flex;
  flex-direction: column;
  min-height: 12rem;
}

#messages { display: flex; flex-direction: column; gap: 1.15rem; }

/* Ein Zwiegespräch liest sich als Zwiegespräch, wenn die Sprecher einander
   gegenüberstehen — Portrait links, Text rechts, beim Wechsel gespiegelt.
   Erzähltext läuft über die volle Breite und kippt nichts. */

.message {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.7rem 1rem;
  /* **Die Karte wird mitgedeckelt, nicht nur der Text darin.** Nur den Text zu
     begrenzen ließe eine 62rem breite Karte stehen, in der die rechte Hälfte
     leer ist — das sieht kaputt aus. `+ 2rem` ist das Polster links und
     rechts. Ein Bild ist davon ausgenommen; das ist keine Zeile. */
  max-width: calc(var(--measure) + 2rem);
}
.message--image, .message--dialogue { max-width: none; }

.message--dialogue {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  background: none;
  border: none;
  padding: 0;
}
/* **Die eigenen Beiträge rechts, alle anderen links** — wie in jedem
   Messenger. Bis zum 03.10.2026 kippte die Seite bei jedem Sprecherwechsel und
   sagte damit nichts darüber, wer sprach (Ben 865-mal links, 861-mal rechts).
   `message--mine` setzt der `chat_controller` je Betrachter; das Markup ist
   für alle gleich. */
.message--dialogue.message--mine { flex-direction: row-reverse; }

/* **Wer spricht, sagt die Farbe** (`speaker-N` am Beitrag): Name, Kante der
   Blase zum Gesicht hin, Rahmen des Porträts. */
.speaker-0 { --speaker: var(--speaker-0); }
.speaker-1 { --speaker: var(--speaker-1); }
.speaker-2 { --speaker: var(--speaker-2); }
.speaker-3 { --speaker: var(--speaker-3); }
.speaker-4 { --speaker: var(--speaker-4); }
.speaker-5 { --speaker: var(--speaker-5); }
.speaker-6 { --speaker: var(--speaker-6); }
.speaker-7 { --speaker: var(--speaker-7); }

/* Die Trennkarte am Szenenwechsel: eine stille Zeile, kein Beitrag. Farben
   aus den Variablen, damit beide Erscheinungsbilder sie kennen. */
.scene-break {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  margin: 2.2rem auto 1.4rem;
  text-align: center;
}
.scene-break__title {
  font-family: var(--font);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.scene-break__time { font-size: 0.72rem; color: var(--text-muted); }

/* Die Taktvorgabe am Beitrag — Auskunft, kein Schmuck. */
.message__pacing { font-size: 0.72rem; font-style: italic; color: var(--text-muted); }

/* Die Regie unter einem Spielerbeitrag: an die Spielleitung gerichtet, nicht
   an die Figuren — darum klein, leise und außerhalb des Spieltexts. */
.message__direction { margin: 0.35rem 0 0; font-size: 0.8rem; font-style: italic; color: var(--text-muted);
                      white-space: pre-wrap; overflow-wrap: anywhere; }
.message__direction-label { font-style: normal; font-weight: 600; }

/* Der Knopf unter einer Aufforderung — er macht den Wurf-Dialog auf. Er kommt
   `hidden` an und wird nur für den Besitzer der Figur aufgedeckt
   (chat_controller); die Gegenregel dazu steht oben bei `button[hidden]`, wo
   die `display`-Regel für Knöpfe steht. Hier keine zweite. */
.roll-request__go { margin-top: 0.5rem; }

/* Das Spieltage-Register. */
.days__group { margin-bottom: 1.4rem; }
.days__day { font-size: 0.95rem; margin: 0 0 0.4rem; }
.days__scenes { display: flex; flex-direction: column; gap: 0.3rem; }
.days__scenes .muted { margin-left: 0.5rem; }

.portrait {
  flex: 0 0 auto;
  width: 76px;
  display: grid;
  place-items: center;
  background: var(--bg-sunken);
  border: 2px solid var(--border);
  border-radius: 3px;
  box-shadow: var(--shadow-portrait);
}
/* Die Höhe folgt dem Bild — es wird nur verkleinert, nie beschnitten. */
.portrait img { display: block; width: 100%; height: auto; }
/* Ohne Bild bleibt der Rahmen quadratisch, damit die Zeile ihre Form behält. */
.portrait:not(:has(img)) { aspect-ratio: 1; }
/* **`display: grid` oben schlägt die Vorgabe des Browsers für `[hidden]`.**
   Dieselbe Falle wie beim `<dialog>`: Eine Autorenregel gewinnt gegen die
   UA-Regel, unabhängig von der Spezifität. Neben dem Eingabefeld stehen die
   Porträts **aller** eigenen Figuren übereinander und werden getauscht — ohne
   diese Zeile stünden sie alle gleichzeitig da. */
.portrait[hidden] { display: none; }
.portrait__initial { font-family: var(--font); font-size: 2rem; color: var(--accent); opacity: 0.75; }

/* „Wer ist wer": Porträt links, Text daneben; auf dem Telefon bricht die
   Zeile nicht, das Porträt ist schmal genug. `min-width: 0`, damit ein
   langer Name den Text umbricht statt die Seite zu verbreitern. */
.cast { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.cast__entry { display: flex; gap: 1rem; align-items: flex-start; }
.cast__portrait { width: 80px; }
.cast__text { flex: 1 1 0; min-width: 0; }
.cast__text .card__title { margin-top: 0; }

/* Hineinzoomen im Verlauf: Beim Überfahren erscheint neben dem kleinen
   Porträt die große Fassung — ein zweites Bild im Markup, faul geladen, also
   erst beim ersten Zeigen. `display: none` muss hier die eigene Regel
   `.portrait img { width: 100% }` überstimmen, deshalb die zwei Klassen.
   `pointer-events: none`, damit die große Fassung nicht selbst das
   Überfahren beendet, sobald sie unter dem Zeiger auftaucht. */
.message .portrait { position: relative; }
.message .portrait .portrait__zoom {
  display: none;
  position: absolute; top: 0; left: calc(100% + 0.5rem);
  width: auto; max-width: min(304px, 45vw); max-height: 70vh;
  z-index: 30;
  background: var(--bg-sunken);
  border: 2px solid var(--border); border-radius: 3px;
  box-shadow: var(--shadow-portrait);
  pointer-events: none;
}
.message .portrait:hover .portrait__zoom { display: block; }
/* Auf der rechten Sprecherseite klappt es nach links auf — sonst liefe es
   aus dem Bild. */
.message--mine .portrait .portrait__zoom { left: auto; right: calc(100% + 0.5rem); }

/* **Das Gesicht bleibt stehen**, solange man durch den Beitrag liest: Mehr als
   die Hälfte der Figurenbeiträge ist länger als 800 Zeichen, auf dem Telefon
   anderthalb Bildschirme, und danach wusste man nicht mehr, wessen Text man
   las. Unter Kopfleiste und Szenenkarte — `--chat-top` misst der
   `chat_controller`, beide brechen auf dem Telefon um. Klebend ist auch
   positioniert, die große Fassung beim Überfahren hängt also weiter daran.

   **Aber klebend ist eine eigene Stapelebene.** Das `z-index: 30` der großen
   Fassung gilt danach nur noch innerhalb des Porträts. Sie ist höher als ihr
   Beitrag und ragt in den nächsten hinein, und dessen Porträt und Blase
   (positioniert, später im Dokument) malten darüber — die Vergrößerung stand
   hinter der nächsten Nachricht. Also bekommt das Porträt
   selbst einen Stapelwert: 2 über den Blasen, unter der Szenenkarte (5), an
   der es beim Scrollen vorbeigleitet; beim Überfahren 9, über allem im
   Verlauf und weiter unter der Kopfleiste (10). */
.message--dialogue .portrait {
  position: sticky;
  top: var(--chat-top, 7.5rem);
  z-index: 2;
  border-color: var(--speaker, var(--border));
}
.message--dialogue .portrait:is(:hover, .portrait--zoomed) { z-index: 9; }

.bubble {
  position: relative;
  /* Zwei Deckel, der engere gewinnt. Die 78 % halten die Blase davon ab, den
     Bildschirm zu überspannen; das Maß darin hält die Zeile lesbar, auch wenn
     die Seite breit genug wäre. `+ 1.8rem` ist das Polster der Blase. */
  max-width: min(78%, calc(var(--measure) + 1.8rem));
  padding: 0.55rem 0.9rem 0.7rem;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: var(--shadow-bubble);
}

/* Der kleine Sporn zum Portrait hin — die Blase zeigt, wer spricht. */
.bubble::before {
  content: "";
  position: absolute;
  top: 22px;
  width: 0;
  height: 0;
  border: 8px solid transparent;
}
.message--dialogue:not(.message--mine) .bubble::before { left: -17px; border-right-color: var(--speaker, var(--border)); }
.message--mine .bubble::before { right: -17px; border-left-color: var(--speaker, var(--border)); }
.message--mine .message__head { flex-direction: row-reverse; }
.message--mine .message__time { margin-left: 0; margin-right: auto; }

/* Die Kante zum Gesicht hin in der Farbe der Figur. */
.message--dialogue:not(.message--mine) .bubble { border-left: 4px solid var(--speaker, var(--border)); }
.message--mine .bubble { border-right: 4px solid var(--speaker, var(--border)); }
.message--dialogue .message__author { color: var(--speaker, var(--accent)); font-size: 0.95rem; }

/* Beisammensitzen am Tisch, nicht in der Rolle: leiser und ohne Rahmen. */
.message--dialogue.message--ooc .bubble { background: transparent; border-style: dashed; }

.message__head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
  font-size: 0.78rem;
}
.message__author { font-weight: 600; color: var(--accent); }
.message__kind, .message__time { color: var(--text-muted); }
.message__time { margin-left: auto; }

/* Satz siehe oben, bei `.message__body, .compose__preview, .prose`. */

/* Ein Beitrag geht durch Markdown, auch der eines Spielers. Was in einem
   Fließtext richtig ist, wäre in einer Chatblase zu laut — eine Überschrift
   nimmt hier die Größe ihrer Umgebung an und bleibt nur durch das Gewicht
   erkennbar. Verboten ist sie nicht: Wer `# Achtung` tippt, soll das Zeichen
   nicht verlieren.
   Die Vorschau des Prüfschritts bekommt dieselben Regeln — sie zeigt ja
   dieselbe Ausgabe. */
.chat-md :is(h1, h2, h3, h4, h5, h6) {
  font-size: 1em;
  font-weight: 700;
  margin: 0.4em 0 0.2em;
}
.chat-md > *:first-child { margin-top: 0; }
.chat-md > *:last-child { margin-bottom: 0; }
.chat-md p { margin: 0 0 0.5em; }
.chat-md :is(ul, ol) { margin: 0 0 0.5em; padding-left: 1.25em; }
.chat-md blockquote {
  margin: 0 0 0.5em;
  padding-left: 0.7em;
  border-left: 2px solid var(--border);
  color: var(--text-muted);
}
/* Eine Tabelle in einer Blase sprengt sonst die Spalte — vor allem auf dem
   Telefon. Sie scrollt für sich, die Seite bleibt stehen. */
.chat-md table { display: block; overflow-x: auto; max-width: 100%; }

/* Ein Fach für Einstellungen ---------------------------------------------
 *
 * Das Auf und Zu macht `<details>` selbst — samt Tastatur, Vorleser und
 * Suchen-im-Text. Was hier steht, ist nur das Aussehen.
 */

.settings { margin-top: 0.6rem; }

/* Das Fach „Regie" sieht aus wie das der Einstellungen (dieselben Regeln oben),
   steht aber davor, weil es zu jedem Beitrag gehört. Steht etwas darin, trägt
   der Schalter einen Punkt — auch zugeklappt: Eine vergessene Anweisung ginge
   sonst mit dem nächsten Beitrag mit. */
.direction { margin-top: 0.6rem; }
.direction[open] > .direction__toggle { color: var(--text); }
.direction--set > .direction__toggle::after { content: "●"; color: var(--accent); font-size: 0.6rem; }
.direction__body { display: flex; flex-direction: column; gap: 0.4rem; padding: 0.35rem 0 0.1rem 0.15rem;
                   max-width: var(--measure); }
.direction__row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.8rem; }
.direction__beats { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--text-muted); }
.direction__beats input { width: 4.5rem; }
.direction__recent { max-width: 100%; min-width: 0; }
.direction__text { width: 100%; font-size: 0.9rem; resize: vertical; }

.settings__toggle, .direction__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.6rem 0.25rem 0.45rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.8rem;
  cursor: pointer;
  /* Beides zusammen nimmt das Dreieck weg: `list-style` für alle Browser,
     der WebKit-Pseudoselektor für Safari, das die Regel bis heute ignoriert. */
  list-style: none;
  user-select: none;
}
.settings__toggle::-webkit-details-marker, .direction__toggle::-webkit-details-marker { display: none; }

.settings__toggle:hover, .direction__toggle:hover { color: var(--text); border-color: var(--border); }
.settings[open] > .settings__toggle { color: var(--text); }

/* Die Tastatur muss sehen, wo sie steht. `summary` ist ein Knopf, und ohne
   diese Zeile ist er der einzige auf der Seite ohne sichtbaren Fokus. */
.settings__toggle:focus-visible, .direction__toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Das Rad dreht sich beim Öffnen ein Stück — der einzige Hinweis darauf, dass
   die Zeile etwas tut, seit das Dreieck weg ist. */
.settings__icon { rotate: 0deg; transition: rotate 250ms ease; }
.settings[open] > .settings__toggle .settings__icon { rotate: 30deg; }

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

.settings__body { padding: 0.15rem 0 0.1rem 0.15rem; }
.settings__body > .compose__alerts:first-child { margin-top: 0.35rem; }

/* Ton und Benachrichtigung: klein und leise gesetzt. Das ist eine Einstellung,
   keine Handlung — sie soll auffindbar sein, wenn man sie sucht, und sonst
   nicht im Weg stehen. `wrap`, weil drei Elemente auf einem Telefon sonst die
   Seite quer ziehen. */
.compose__alerts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 1rem;
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  color: var(--text-muted);
}
.compose__alerts label { display: flex; align-items: center; gap: 0.35rem; cursor: pointer; }
.compose__alerts input { width: auto; margin: 0; }
.compose__alerts .hint { margin: 0; flex-basis: 100%; }

/* Der Schieber für die Schriftgröße. Steht neben Ton und Benachrichtigung,
   weil er dasselbe ist: eine Einstellung für dieses Gerät, nicht für die
   Figur. Wer am Rechner liest, will etwas anderes als auf dem Telefon.
   Die beiden A zeigen, wohin es geht, ohne eine Zahl zu nennen — Prozente
   sagen niemandem etwas, ein kleines und ein großes A schon. */
.compose__reading label { flex-basis: 100%; }
.compose__reading input[type="range"] {
  width: auto;
  flex: 1 1 8rem;
  max-width: 14rem;
  margin: 0;
  accent-color: var(--accent);
}
.compose__reading .compose__a--small { font-size: 0.75rem; }
.compose__reading .compose__a--large { font-size: 1.25rem; }
.compose__reading :is(.compose__a--small, .compose__a--large) {
  font-family: var(--font);
  line-height: 1;
}
.compose__dice3d label { flex-basis: 100%; }
.compose__dice3d select { width: auto; margin: 0; }

/* Die Bühne der 3D-Würfel (`dice3d_controller`). Sie hängt am `<html>`,
   nicht im Rumpf, liegt über allem und fängt nichts ab — wer während eines
   Wurfs tippt, tippt in die Seite darunter. Sie nimmt dem Chat keine Breite:
   `fixed`, ohne Platz im Fluss. Sichtbar nur, solange gewürfelt wird; die
   Leinwand braucht aber schon vorher eine Größe, deshalb `opacity` und
   nicht `display`. */
.dice-tray {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.dice-tray--shown { opacity: 1; }
.dice-tray canvas { display: block; }

/* Die Meldung zu einem abgewiesenen Beitrag sitzt im Formular, nicht am
   Seitenkopf — dort wäre sie hinter dem ganzen Verlauf. Kein Abstand nach
   oben: Sie schließt an das Feld an, auf das sie sich bezieht. */
.compose__error { margin: 0.75rem 0 0; }

/* Solange die Vorschau noch den rohen Text zeigt, bleiben die Umbrüche so,
   wie sie getippt wurden. */
.compose__preview.plain { white-space: pre-wrap; }

/* Erzähltext trägt den goldenen Streifen und den wärmeren Grund. Beides
   zusammen, damit man die Erzählung auch dort erkennt, wo der Streifen unter
   dem Porträt fast verschwindet. */
.message--narration {
  border-left: 3px solid var(--accent);
  background: var(--bg-narration);
}

/* **Gold gehört der Spielleitung.** Erzählt eine Figur, trägt sie weder den
   goldenen Streifen noch den goldenen Grund — bis zum 03.10.2026 sah Joey
   damit aus wie die Erzählerin. Ihre Farbe hat sie an Kante und Namen. Die
   Zeile ist im Zwiegespräch nur ein Gerüst über die ganze Breite. */
.message--dialogue.message--narration { border-left: none; background: none; }

/* **Nur an Karten, nie an der Zeile eines Zwiegesprächs.** Die Zeile ist
   dort ein Gerüst über die ganze Breite; ein „am Tisch" mit Figur bekam
   sonst einen gestrichelten Rahmen um die ganze Zeile, ein Wurf einer Figur
   einen abgesenkten Grund quer über den leeren Platz neben der Blase. Die
   Blase hat ihre eigenen Regeln (`.message--dialogue.message--ooc .bubble`). */
.message--ooc:not(.message--dialogue) { background: transparent; border-style: dashed; }
.message--note, .message--roll, .message--system {
  font-size: 0.92rem;
}
.message:is(.message--note, .message--roll, .message--system):not(.message--dialogue),
.message--dialogue.message--roll .bubble {
  background: var(--bg-sunken);
}

.message__picture { margin: 0; }
.message__picture img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}
.message__picture figcaption {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  font-style: italic;
}

/* A picture on its way. Deliberately quiet — the waiting should be visible
   but not louder than the story around it. */
.message__pending {
  margin: 0;
  padding: 1.5rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  text-align: center;
  color: var(--text-muted);
  font-style: italic;
}

.message__explains {
  margin: 0.4rem 0 0;
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* Das Urteil am Beitrag einer Figur, die Claude führt (analysev4 P3). Leise:
   eine Zeile unter dem Text, aufgeklappt zwei Knöpfe und ein Feld für den
   Kommentar. Für alle gleich — nur das eigene Urteil färbt die Zeile.
   `flex-wrap`, sonst zieht die Knopfreihe auf dem Telefon die Seite quer.
   `display` nur am geöffneten Kasten (`[open]`), wie beim Dialog. */
.verdict {
  margin: 0.35rem 0 0;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  color: var(--text-muted);
}
.verdict[hidden] { display: none; }
.verdict__summary {
  display: inline-block;
  cursor: pointer;
  list-style: none;
}
.verdict__summary::-webkit-details-marker { display: none; }
.verdict--good .verdict__summary,
.verdict--bad .verdict__summary { color: var(--accent); }
.verdict__box[open] .verdict__form {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-top: 0.4rem;
}
.verdict__error {
  margin: 0;
  color: var(--danger);
}
.verdict__comment {
  width: 100%;
  min-width: 0;
  font-size: 0.85rem;
}
.verdict__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

/* Die Liste der Urteile in der Persona-Verwaltung. */
.persona-examples__list { list-style: none; padding: 0; margin: 0; }
.persona-examples__item { border-top: 1px solid var(--border); padding: 0.5rem 0; }
.persona-examples__item blockquote { margin: 0.25rem 0; white-space: pre-line; }

/* Eine Fassung, die eine Korrektur ersetzt hat. Gedämpft, nicht versteckt —
   und bewusst ohne display-Regel: was `hidden` sein könnte, bekäme sonst die
   bekannte Gegenregel-Falle. */
.message--superseded { opacity: 0.55; }
.message--superseded .message__body {
  color: var(--text-muted);
  font-size: 0.85em;
}
.message__superseded {
  margin: 0 0 0.3rem;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
}
.message__superseded a { color: inherit; }

/* Eine Korrektur an alter Stelle: die gültige Fassung, darunter die frühere
   zugeklappt. Der Rahmen selbst ist unsichtbar — er trägt nur die `id`, auf
   die ein späteres `replace` zielt. Ohne display-Regel, wie oben. */
.revision__earlier { margin: -0.4rem 0 0.6rem; }
.revision__earlier > summary {
  cursor: pointer;
  font-size: 0.75rem;
  color: var(--text-muted);
  font-style: italic;
  list-style-position: inside;
}
.revision__earlier > .message { margin-top: 0.3rem; }

/* Ein verworfener Zweig: stärker gedämpft als eine ersetzte Fassung, weil er
   nicht korrigiert, sondern zurückgenommen ist. Ebenfalls ohne display-Regel. */
.message--discarded { opacity: 0.4; }
.message--discarded .message__body {
  color: var(--text-muted);
  font-size: 0.85em;
  text-decoration: line-through;
  text-decoration-color: var(--text-muted);
}
.message__discarded {
  margin: 0 0 0.3rem;
  font-size: 0.8rem;
  color: var(--text-muted);
  font-style: italic;
}

/* Die Bitte der Spielleitung, etwas zu werfen. Sie sieht bewusst nicht aus
   wie ein Ergebnis: hier steht noch nichts fest, hier wird etwas verlangt. */
.message--roll_request {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
}
.message--roll_request .message__body {
  font-family: var(--font-mono);
  font-size: 0.95rem;
}

/* Drafts are ephemeral: never stored, never sent to Claude, and they have to
   look unmistakably unfinished. */
.chat__drafts:empty { display: none; }

/* „↓ 3 neue Beiträge" — am unteren Fensterrand, solange oben gelesen wird.
   Gefüllt, damit er sich vom Verlauf abhebt; die Gegenregel für `[hidden]`
   steht bei den Knöpfen und gilt hier mit, weil es ein `button` ist.
   `env(safe-area-inset-bottom)` hält ihn über der Gestenleiste. */
.chat__fresh {
  position: fixed;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 30;
  max-width: calc(100vw - 2rem);
  white-space: nowrap;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 999px;
  box-shadow: var(--shadow-bubble);
}
.chat__fresh:hover { background: var(--accent-soft); color: var(--accent); }

/* Die Minikarte. Ein Rasterfeld ist eine SVG-Einheit; Striche und Schrift
   stehen darum in Bruchteilen eines Feldes, nicht in Pixeln. Ist die Ebene
   breiter als der Kasten, rollt der Rahmen um sie — nie die Seite. Die Farben
   kommen aus den Tokens, damit die Karte mit dem Rest dunkel bleibt. */
.dungeonmap__level + .dungeonmap__level { margin-top: 1rem; }
.dungeonmap__name { margin: 0 0 0.4rem; font-size: 0.85rem; }
.dungeonmap__frame { max-width: 100%; overflow: auto; background: var(--bg-sunken);
                     border: 1px solid var(--border); border-radius: var(--radius); }
.dungeonmap { display: block; max-width: none; height: auto; }
.dungeonmap__room rect { fill: var(--bg-raised); stroke: var(--text-muted); stroke-width: 0.08; }
.dungeonmap__room--here rect { fill: var(--accent-soft); stroke: var(--accent); }
.dungeonmap__label { fill: var(--text-muted); font: 0.5px var(--font-ui); }
.dungeonmap__stairs { fill: var(--text-muted); font: 0.55px var(--font-ui); }
.dungeonmap__marker { fill: var(--accent); }
.dungeonmap__passage { stroke: var(--text-muted); stroke-width: 0.12; }
.dungeonmap__passage--stub { stroke-dasharray: 0.15 0.12; }
.dungeonmap__door { fill: var(--text); }
.dungeonmap__door--secret { fill: var(--bg-sunken); stroke: var(--text); stroke-width: 0.06; }
.dungeonmap__unknown { fill: var(--text-muted); font: bold 0.7px var(--font-ui); }
.dungeonmap__legend { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.6rem; margin-top: 0.6rem; }
.dungeonmap__key { display: inline-block; width: 0.8rem; height: 0.8rem; vertical-align: middle; }
.dungeonmap__key--here { background: var(--accent); border-radius: 50%; }
.dungeonmap__key--door { width: 0.5rem; height: 0.5rem; background: var(--text); }
.dungeonmap__key--stub { border-top: 2px dashed var(--text-muted); height: 0; }

/* Hexkarten. Die Geländefarben sind gedeckte Mitteltöne, die auf heller wie
   dunkler Seite lesbar bleiben; was nur von Weitem gesehen wurde, steht
   blasser da. Jede Geländeart setzt `--terrain` **einmal** — Feld und
   Legendenzeichen lesen dieselbe Variable, damit sie nicht auseinanderlaufen. */
.hexmap__cell--plains { --terrain: #b9b574; }
.hexmap__cell--farmland { --terrain: #c9b26a; }
.hexmap__cell--forest { --terrain: #5f7f4f; }
.hexmap__cell--jungle { --terrain: #3f7a4a; }
.hexmap__cell--hills { --terrain: #a08a5c; }
.hexmap__cell--mountains { --terrain: #8a8178; }
.hexmap__cell--swamp { --terrain: #6b7a5a; }
.hexmap__cell--water { --terrain: #5f86a0; }
.hexmap__cell--desert { --terrain: #d2b98a; }
.hexmap__cell--arctic { --terrain: #d8dde0; }
.hexmap__cell--wasteland { --terrain: #8c7a6a; }
.hexmap__cell { fill: var(--terrain); stroke: var(--bg-sunken); stroke-width: 0.06; }
.hexmap__cell--seen, .hexmap__cell--public { opacity: 0.45; }
.hexmap__path { stroke-linecap: round; fill: none; }
.hexmap__section { --hex-track: #3b2f22; --hex-river: #3f6f99; }
.hexmap__path--path { stroke: var(--hex-track); stroke-width: 0.12; stroke-dasharray: 0.25 0.18; }
.hexmap__path--road { stroke: var(--hex-track); stroke-width: 0.18; }
.hexmap__path--river { stroke: var(--hex-river); stroke-width: 0.22; }
/* Der Umschalter zwischen den Landkarten: eine umbrechende Reihe, die
   gewählte Karte hervorgehoben. */
.mapswitch { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.6rem; }
.mapswitch__option[aria-current="true"] { background: var(--accent-soft); border-color: var(--accent);
                                           color: var(--text); }

/* Das Sichtfenster der Landkarte: höchstens so hoch, dass Kopf und Legende
   noch ins Bild passen, in beide Richtungen verschiebbar. `touch-action`
   lässt den Finger rollen, die Maus zieht über den `pan`-Controller. */
.hexmap__viewport { max-height: min(60dvh, 44rem); overflow: auto; cursor: grab;
                    touch-action: pan-x pan-y; overscroll-behavior: contain; }
.hexmap__viewport.is-panning { cursor: grabbing; user-select: none; }
.hexmap__label { fill: #1b1a1f; font: bold 0.42px var(--font-ui); paint-order: stroke;
                 stroke: rgb(255 255 255 / 0.7); stroke-width: 0.1; }
.hexmap__key { display: inline-block; width: 0.8rem; height: 0.8rem; vertical-align: middle;
               border-radius: 2px; background: var(--terrain, var(--text-muted)); }
.hexmap__key--path, .hexmap__key--road, .hexmap__key--river { height: 0; border-radius: 0; background: none; }
.hexmap__key--path { border-top: 2px dashed var(--hex-track); }
.hexmap__key--road { border-top: 3px solid var(--hex-track); }
.hexmap__key--river { border-top: 3px solid var(--hex-river); }

/* Suchtreffer: Kopfzeile wie eine Beitragskarte, darunter der Auszug. */
.search__hits { display: flex; flex-direction: column; gap: 0.9rem; margin-top: 0.8rem; }
.search__meta { margin: 0; display: flex; gap: 0.6rem; flex-wrap: wrap; }
.search__excerpt { margin: 0.15rem 0 0.25rem; }
.draft {
  margin: 0 0 0.3rem;
  padding: 0.3rem 0.6rem;
  border-left: 2px solid var(--border);
  color: var(--text-muted);
  font-style: italic;
  font-size: 0.9rem;
  white-space: pre-wrap;
  max-width: var(--measure);
  hyphens: auto;
}
.draft__author { font-style: normal; font-weight: 600; }
.draft__author::after { content: " tippt:"; font-weight: 400; }
/* Claudes Zeilen tragen ihren Wortlaut selbst („tippt …", „ist dran …"). */
.draft--status .draft__author::after { content: none; }

.compose {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
}
.compose__label {
  display: block;
  margin-bottom: 0.45rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
}

/* Der Schreibtisch: Blatt, Gesicht, Knöpfe ---------------------------------
 *
 * Ein Raster, weil die drei auf dem Telefon anders liegen als am Rechner. Am
 * Rechner steht das Gesicht **rechts neben dem Blatt** — der Platz ist ohnehin
 * da, seit die Zeile auf 66 Zeichen gedeckelt ist, und blieb sonst leer. Auf
 * dem Telefon gibt es diesen Platz nicht: Dort trägt die Zeile 27 Zeichen, und
 * jedes weggenommene Pixel fehlt beim Schreiben. Also rutscht das Gesicht dort
 * neben die Knopfreihe, wo es keine einzige Zeichenbreite kostet.
 *
 * Verschieben kann das nur eine Anordnung, die umbricht. Zweimal dasselbe ins
 * Markup zu setzen und je eines auszublenden wären zwei Fassungen, die
 * auseinanderlaufen — und zwei Knöpfe mit demselben Ziel im Dokument.
 *
 * **Flex und nicht Grid, und das ist gemessen.** Mit `grid` sitzt das Blatt in
 * einer Spur, deren Breite aus dem Inhalt kommt — und die Breite eines
 * `<textarea>` kommt aus seinem `width`, das hier `100 %` der Spur ist. Der
 * Zirkelschluss endet bei der Eigenbreite aus `cols`, also bei **21 Zeichen**;
 * mit einer festen Breite am Feld immerhin bei 43. Ein Flex-Element nimmt seine
 * Ausgangsbreite dagegen direkt von `width`, und die Rechnung geht auf.
 * `order` und `flex-basis: 100%` machen daraus die zweite Anordnung: Was die
 * volle Breite verlangt, erzwingt den Umbruch davor.
 */
.compose__desk {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.6rem 1rem;
}
/* Die Breite steht am Feld selbst (siehe `.compose__sheet`); schrumpfen darf
   es, wachsen nicht. */
.compose__sheet { flex: 0 1 auto; }
/* **Dem Gesicht seine Spalte freihalten.** Die Blattbreite steht in `ch`, und
   `ch` wächst mit dem Schriftgrößen-Schieber — ab etwa Skala 1,25 war das
   Blatt so breit wie der Schreibtisch, der `flex-wrap` brach um, und das
   Gesicht rutschte unters Eingabefeld. Beim Umbruch zählt die *gewünschte*
   Breite, nicht die geschrumpfte, also muss der Deckel sie selbst begrenzen:
   8rem sind die 7rem des Gesichts plus die Spaltenlücke. Nur wo ein Gesicht
   steht — wer keine Figur führt, behält die volle Breite. Der Medienblock
   unten stellt fürs Telefon 100 % wieder her: Dort hat das Blatt seine
   eigene Zeile mit Absicht. */
.compose__desk:has(.compose__who) > .compose__sheet { max-width: calc(100% - 8rem); }
.compose__who { order: 1; flex: 0 0 auto; }
/* Volle Breite heißt: eigene Zeile, unter Blatt und Gesicht. */
/* Die Spurenleiste (`.compose__trackline`, order 2) steht dazwischen. */
.compose__desk > .compose__row { order: 3; flex: 1 1 100%; }

/* Das Gesicht ist der Knopf. Alles, was die allgemeine Knopfregel mitbringt —
   Rahmen, Grund, Polster, Farbe —, muss hier weg: Ein Bild in einem Kasten in
   einem Kasten. */
.compose__who {
  grid-area: who;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  width: 7rem;
  padding: 0;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.72rem;
  line-height: 1.3;
  text-align: center;
  cursor: pointer;
}
.compose__who:hover { background: none; color: var(--text); text-decoration: none; }
.compose__who:hover .portrait { border-color: var(--accent); }
.compose__who:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.compose__who .portrait { width: 100%; }
.compose__who-name { font-weight: 600; color: var(--text); }
.compose__who-name:empty { display: none; }

/* „— als Du selbst —": Es wird nicht in der Rolle geschrieben, also ist das
   Gesicht daneben keine Auskunft über den Beitrag mehr. Leiser gesetzt statt
   ausgetauscht — der Bogen dahinter gilt weiter. */
.compose__who--self .portrait { opacity: 0.4; }
.compose__who--self .compose__who-name { color: var(--text-muted); font-weight: 400; }

@media (max-width: 40rem) {
  /* Blatt über die ganze Zeile, darunter Gesicht **neben** der Knopfreihe —
     dort kostet es keine einzige Zeichenbreite. */
  .compose__desk { gap: 0.6rem; }
  .compose__desk > .compose__sheet { flex: 1 1 100%; }
  /* Gegenstück zum Deckel oben (gleiche Spezifität, spätere Regel): Hier hat
     das Blatt seine eigene Zeile, das Gesicht steht darunter neben den
     Knöpfen — freizuhalten ist nichts. */
  .compose__desk:has(.compose__who) > .compose__sheet { max-width: 100%; }
  .compose__desk > .compose__who { order: 2; }
  /* **Ausgangsbreite 0, nicht `auto`.** Mit `auto` bringt die Reihe die Breite
     ihres Inhalts mit — zwei Auswahlfelder und einen Knopf —, und danach
     entscheidet der Browser über den Umbruch. Sie landete damit in einer
     eigenen Zeile **unter** dem Gesicht, statt daneben, und gespart war nichts.
     Mit 0 passt sie immer neben das Gesicht und füllt danach den Rest; ihre
     eigenen Knöpfe brechen intern um, dafür hat `.compose__row` sein `wrap`. */
  .compose__desk > .compose__row { order: 3; flex: 1 1 0; min-width: 0; }

  .compose__who { width: 44px; }

  /* Nicht `display: none`: Der Knopf braucht seinen Namen, sonst sagt ein
     Vorleser „Schaltfläche" und sonst nichts. Also aus dem Bild genommen und
     im Baum gelassen. Der Name der Figur steht daneben ohnehin im Auswahlfeld. */
  .compose__who-name, .compose__who-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ── Das Blatt in der Maschine ───────────────────────────────────────────────
 *
 * Hier wird geschrieben, und es soll sich danach anfühlen. Vorher stand hier
 * dasselbe Feld wie in jedem Formular: Systemschrift, 6 px Radius, vier Zeilen
 * hoch. An einem Tisch, der in Absätzen spielt, tippt man 1.500 Zeichen durch
 * ein Guckloch von vier Zeilen — und liest dabei in einer Schrift, die mit dem
 * Ergebnis nichts zu tun hat.
 *
 * **Der Bruch ist Absicht.** Geschrieben wird in der Maschine, gedruckt wird in
 * Serifen: Die Vorschau nebenan behält `--font`, weil sie zeigt, was im Verlauf
 * stehen wird. Manuskript und Satz sind zwei verschiedene Dinge.
 *
 * `font-size-adjust`, weil auch hier vier Geräte vier Schriften rendern: Nimbus
 * Mono PS hat eine x-Höhe von 0,417 em, Noto Sans Mono 0,536 — ein Drittel
 * Unterschied. Ohne den Ausgleich ist die Courier auf dem Rechner ein
 * Fliegendreck und auf Android ein Plakat.
 */
.compose__sheet {
  font-family: var(--font-type);
  /* Wächst mit dem Schieber. Wer die Schrift zum Lesen größer stellt, braucht
     sie beim Schreiben auch größer — das ist dieselbe Person mit denselben
     Augen. Das Blatt fängt es auf, weil es ohnehin mitwächst. */
  font-size: calc(1rem * var(--reading-scale));
  font-size-adjust: 0.47;
  /* **Ein Bogen ist nicht so breit wie der Schreibtisch.** Über die volle Karte
     gelassen trug die Zeile 93 Zeichen — genau der Fehler, der beim Lesen eben
     behoben wurde, nur eine Stelle weiter. Eine Schreibmaschine schreibt zehn
     Zeichen je Zoll, auf A4 sind das rund 65.
     Und hier ist `ch` das richtige Maß und nicht `rem`: In einer
     nichtproportionalen Schrift **ist** `ch` die Zeichenbreite, exakt und auf
     jedem Gerät. Beim Fließtext ging das nicht, weil dort jedes Gerät eine
     andere Schrift rendert. */
  /* **`width` und nicht nur `max-width`, seit das Blatt in einem Raster
     steht.** Ein `<textarea>` ohne bestimmte Breite hat als Eigenbreite die
     seines `cols` — und `width: 100%` in einer `auto`-Spur ist zirkulär, also
     nimmt der Browser eben diese Eigenbreite. Das Blatt fiel damit auf **21
     Zeichen** zusammen, und die Deckelung sah aus, als greife sie zu hart.
     `max-width: 100%` daneben, damit es auf einem schmalen Schirm trotzdem
     hineinpasst. */
  width: calc(66ch + 2.1rem);
  max-width: 100%;
  /* Weit gestellt. Eine Schreibmaschine schreibt anderthalbzeilig, und beim
     Schreiben will man zwischen den Zeilen Platz haben, nicht beim Lesen. */
  line-height: 1.85;
  letter-spacing: 0.01em;
  padding: 0.85rem 1.05rem;
  background: var(--bg-paper);
  /* Ein Bogen hat Ecken. 6 px Radius sind ein Bedienelement. */
  border-radius: 2px;
  /* Der Anschlag steht im Akzent — auf einer dunklen Fläche ist ein weißer
     Strich das Hellste, was es gibt, und er blinkt. */
  caret-color: var(--accent);
  /* Wo der Browser es kann, ein Klotz statt eines Strichs. Kennt er die Regel
     nicht, bleibt es beim Strich; kaputt geht dabei nichts. */
  caret-shape: block;
  /* Die Höhe macht `compose#grow`. Ziehen wäre ein zweiter Weg, der dem ersten
     in die Quere kommt. */
  resize: none;
  overflow-y: auto;
  /* Der Boden ist `rows="4"` am Feld selbst: `height: auto` löst darauf auf,
     und `grow` setzt nie weniger. Ein `min-height` daneben wäre eine zweite
     Zahl für dieselbe Sache — und die kleinere von beiden nie zu sehen.
     Irgendwo ist Schluss, sonst schiebt ein langer Beitrag den eigenen Anfang
     aus dem Bild. `dvh` und nicht `vh`: Auf dem Telefon steht die Tastatur im
     Weg, und `vh` weiß davon nichts. */
  max-height: min(26rem, 55dvh);
}

/* Beim Schreiben verschwindet der Rahmen und das Blatt tritt vor. Kein
   2-px-Kasten um die Fläche, in der man gerade arbeitet. */
.compose__sheet:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Auf dem Telefon kostet die feste Zeichenbreite am meisten: Bei 1rem Courier
   trägt eine Zeile dort **22 Zeichen**, und dann schreibt man in einer Säule.
   Etwas kleiner und mit knapperem Rand werden es 32 — die Grenze, unterhalb
   derer ein deutscher Satz nicht mehr als Satz aussieht. */
@media (max-width: 40rem) {
  .compose__sheet {
    font-size: calc(0.92rem * var(--reading-scale));
    line-height: 1.7;
    padding: 0.7rem 0.75rem;
  }
}
.compose__row { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.6rem; flex-wrap: wrap; }
.compose__as { display: flex; gap: 0.5rem; margin-right: auto; flex-wrap: wrap; }
.compose__select { width: auto; }
.compose__preview {
  padding: 0.7rem 1rem;
  background: var(--bg-sunken);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  white-space: pre-wrap;
  min-height: 3rem;
}

/* Import: was ein Bündel täte -------------------------------------------- */

.action {
  display: inline-block;
  min-width: 6.5em;
  padding: 0.1rem 0.5rem;
  border-radius: 3px;
  border: 1px solid var(--border);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}
.action--created { border-color: var(--accent); color: var(--accent); }
.action--updated { border-color: var(--accent); color: var(--text); }
.action--removed { border-color: var(--danger); color: var(--danger); }
.action--warning { border-color: var(--danger); color: var(--danger); }

.form--inline { display: flex; gap: 1rem; align-items: flex-end; max-width: none; }
.form--inline .field { margin-bottom: 0; }

/* Der obere Rand des Verlaufs ------------------------------------------- */

.chat__older {
  display: grid;
  place-items: center;
  min-height: 2.5rem;
  color: var(--text-muted);
  font-size: 0.85rem;
}
/* Der Hinweis erscheint erst, während wirklich geladen wird — sonst steht über
   jedem Chat dauerhaft „lädt", obwohl nichts läuft. */
.chat__older-label { opacity: 0; transition: opacity 150ms; }
.chat__older--loading .chat__older-label { opacity: 1; }

/* Auf der Staff-Spur wird von Hand nachgeladen — sie springt beim Öffnen nicht
   ans Ende, dort stünde der Merker sofort im Bild. */
.chat__older--manual { min-height: 0; margin-bottom: 0.75rem; }
.chat__older--manual button { font-size: 0.8rem; padding: 0.3rem 0.7rem; }

/* Der untere Merker, wenn das Fenster beim Ungelesenen beginnt: Er steht im
   Bild, sobald man die geladenen Beiträge gelesen hat, und lädt die nächsten.
   `display: grid` braucht die Gegenregel für `[hidden]` (siehe CLAUDE.md). */
.chat__newer { display: grid; place-items: center; min-height: 2.5rem; color: var(--text-muted); font-size: 0.85rem; }
.chat__newer[hidden] { display: none; }
.chat__older--manual.chat__older--loading button { opacity: 0.5; }

.chat__beginning {
  margin: 0 0 0.5rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
  text-align: center;
  font-family: var(--font);
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Bogen, Notizzettel, Stand und Wurf als Overlay -------------------------
   Den Bogen öffnet das Porträt neben dem Eingabefeld (`.compose__who`); eine
   eigene Leiste darunter gab es bis dahin und ist entfallen. Den Notizzettel
   öffnet der Knopf in der Reihe darunter, den Wurf der Knopf unter einer
   Aufforderung im Verlauf.

   **Ein Satz Regeln für alle Kästen.** Sie sollen gleich aussehen, und zwei
   Sätze für dasselbe laufen auseinander. Verschiedene Namen tragen sie
   trotzdem, damit eine Prüfung sie auseinanderhalten kann. */

.sheet, .notes, .statusdialog, .mapdialog, .roll-dialog, .reroll-dialog {
  width: min(46rem, 100vw - 2rem);
  max-height: 85dvh;
  margin: auto;
  padding: 0;
  overflow: hidden;
  flex-direction: column;
  background: var(--bg-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* Nur am offenen Dialog. Eine Autorenregel schlägt die Vorgabe des Browsers,
   also stünde der Bogen bei schlichtem `display: flex` auch im geschlossenen
   Zustand offen in der Seite. */
.sheet[open], .notes[open], .statusdialog[open], .mapdialog[open], .roll-dialog[open], .reroll-dialog[open] { display: flex; }
.sheet::backdrop, .notes::backdrop, .statusdialog::backdrop, .mapdialog::backdrop, .roll-dialog::backdrop, .reroll-dialog::backdrop { background: var(--backdrop); }
/* Die Landkarte darf breiter sein als die anderen Kästen: Sie wird
   verschoben, nicht gelesen, und jede Spalte mehr ist Überblick. Hier und
   nicht bei den Hexregeln oben — sonst schlägt die Breite der Kästen sie. Die
   Telefonregel weiter unten schlägt sie wieder, und das ist gewollt. */
.mapdialog--land { width: min(72rem, 100vw - 2rem); }

.sheet__head {
  display: flex;
  align-items: center;
  gap: 0.5rem 0.6rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--border);
  /* Der Kopf liegt außerhalb des rollenden Bereichs: auf dem Telefon ist der
     Schließen-Knopf sonst nach dem ersten Wischen unerreichbar. */
  flex: none;
  /* **Umbrechen, seit die Knöpfe hier stehen.** Mit „Bogen bearbeiten" neben
     „Schließen" blieben auf 360 px für den Namen achtzig Pixel übrig, und
     „Wolfhart „der Bleiche" Grimm" stand vierzeilig da. Ein Umbruch
     entscheidet sich an der Ausgangsbreite: Der Titel bringt seine
     Inhaltsbreite mit, passt sie nicht neben die Knöpfe, bekommt er die Zeile
     für sich und die Knöpfe die nächste. Am Rechner steht weiter alles in
     einer. */
  flex-wrap: wrap;
}
.sheet__title {
  margin: 0;
  margin-right: auto;
  font-size: 1.05rem;
  /* Damit ein langer Name innerhalb seiner Zeile umbricht, statt sie
     aufzuspannen. */
  min-width: 0;
}
.sheet__body {
  padding: 0.9rem 1rem 1.2rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
/* Die Reiter: Werte ⇄ Prosa. Sie liegen wie der Kopf **außerhalb** des
   rollenden Bereichs (`flex: none`) — ein Bogen ist länger als der Bildschirm,
   und ein Reiter, den man erst hochscrollen muss, ist kein Reiter mehr.

   `border-radius: 0` und `background: none` müssen hier stehen: `.button` gilt
   nicht, aber die Vorgaben des Browsers für `<button>` schon. */
.sheet__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 1.2rem;
  padding: 0 1rem;
  border-bottom: 1px solid var(--border);
  flex: none;
}
.sheet__tab {
  padding: 0.55rem 0.1rem;
  /* Zieht die eigene Unterkante über die Linie der Leiste — sonst stünden zwei
     Striche untereinander. */
  margin-bottom: -1px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--text-muted);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  cursor: pointer;
}
/* **`background: none` muss hier ein zweites Mal stehen.** Eine Zeile weiter
   oben in dieser Datei steht `button:hover { background: var(--accent) }` — für
   *jeden* Knopf der Anwendung. Ein Reiter ist ein `<button>` (er muss einer
   sein: Tastatur, Fokus, `role="tab"`), sieht aber keiner aus, und beim
   Darüberfahren wurde er zum goldenen Klotz. Nachgemessen, nicht vermutet:
   `getComputedStyle` gab `rgb(201, 162, 39)`.
   Dass es reicht, ist Spezifität: `.sheet__tab:hover` sticht `button:hover`. */
.sheet__tab:hover { background: none; color: var(--text); }
.sheet__tab--active { color: var(--text); border-bottom-color: var(--accent); }

/* Die Fächer bekommen **bewusst keine** `display`-Regel: Ein `<div>` ist von
   sich aus ein Block, und damit greift die Browservorgabe `[hidden] { display:
   none }`. Wer hier später `display: flex` einträgt, hebelt sie aus und hat
   Werte und Prosa gleichzeitig im Dialog stehen — genau das ist in diesem
   Projekt beim `<dialog>`, beim Porträt und beim Knopf schon passiert. Dann
   gehört die Gegenregel in dieselbe Zeilengruppe. */

.sheet__panel[hidden] { display: none; }
.sheet__panel > *:first-child { margin-top: 0; }

/* Die Attributtabelle eines KULT-Bogens hat fünf Spalten. Ohne Mindestbreite
   quetscht `table { width: 100% }` sie auf dem Telefon zu einer Wortsäule
   zusammen, statt überzulaufen — also erst breit machen, dann rollen lassen. */
.table-scroll { overflow-x: auto; }
.sheet__body table { min-width: 30rem; }

@media (max-width: 40rem) {
  .sheet, .notes, .statusdialog, .mapdialog, .roll-dialog, .reroll-dialog {
    width: 100vw;
    max-width: none;
    height: 100dvh;
    max-height: none;
    border: none;
    border-radius: 0;
  }
}

/* Der Wurf-Dialog ---------------------------------------------------------
   Rahmen, Kopf und Hintergrund kommen von den Kästen oben; hier steht nur,
   was darin liegt. */

/* Das Formular ist die Spalte im Dialog: der rollende Teil (`.sheet__body`, wie
   bei den anderen Kästen) und darunter die Knopfreihe, die **nicht** mitrollt.
   `min-height: 0`, sonst wächst eine Flex-Spalte mit ihrem Inhalt statt ihn
   rollen zu lassen — und dann rollt der ganze Dialog, Knöpfe inklusive. */
.roll-dialog__form { display: flex; flex-direction: column; min-height: 0; margin: 0; }
.roll-dialog__actions {
  flex: none;
  padding: 0.7rem 1rem;
  border-top: 1px solid var(--border);
}
/* Wer werfen soll — im Kopf, rechts vom Titel. Kein `margin-right: auto`: Das
   hat `.sheet__title` schon, und zwei davon teilen den freien Platz
   untereinander auf, statt ihn dem Titel zu geben. */
.roll-dialog__who { color: var(--text-muted); font-size: 0.85rem; }
.roll-dialog__hint { margin: 0 0 0.9rem; }

.roll-row {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* `min-width: 0`, weil ein `<fieldset>` von sich aus `min-content` als
     Mindestbreite mitbringt: Eine lange Begründung im `<legend>` zöge den
     Kasten sonst über den Dialog hinaus, und die Seite ließe sich auf dem
     Telefon seitlich schieben. Dieselbe Falle wie `1fr` statt
     `minmax(0, 1fr)`, nur an einem Element, das sie eingebaut hat. */
  min-width: 0;
}
.roll-row:last-of-type { margin-bottom: 0; }
.roll-row__reason { padding: 0 0.3rem; font-family: var(--font); font-size: 0.95rem; }
.roll-row__words {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.6rem;
  margin: 0 0 0.7rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.roll-row__words code { font-family: var(--font-mono); color: var(--text); }

/* `wrap`: Ein Wurf mit vier Würfeln passt auf dem Telefon nicht in eine Zeile,
   und ohne Umbruch zieht die Reihe den Dialog quer. */
.roll-row__dice { display: flex; flex-wrap: wrap; gap: 0.5rem 0.8rem; }
.roll-row__die { display: flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; color: var(--text-muted); }
/* Breit genug für zwei Ziffern samt der Pfeilchen, die der Browser selbst
   malt — schmaler, und die Zahl steht hinter ihnen. */
.roll-row__die input { width: 5rem; }
/* Ein Verzweiflungswürfel: Rahmen und Beschriftung in seiner Farbe. */
.roll-row__die--desperation { color: var(--desperation); }
.roll-row__die--desperation input { border-color: var(--desperation); }

/* Der Schadenszähler: Kästchen wie auf dem Bogen. `wrap`, weil Gesundheit
   bei zehn Kästchen auf dem Telefon sonst die Zeile quer zieht. */
.track__boxes { display: inline-flex; flex-wrap: wrap; gap: 2px; }
.track__box {
  display: inline-grid;
  place-items: center;
  width: 0.9rem;
  height: 0.9rem;
  border: 1px solid var(--text-muted);
  font-size: 0.7rem;
  line-height: 1;
}
.track__box--superficial { color: var(--text); }
.track__box--aggravated { color: var(--danger); border-color: var(--danger); }
.track--impaired .track__label, .track--incapacitated .track__label { color: var(--danger); }

/* Was nach einem Vorratswurf noch offen ist — Neuwurf, Wahl. Kommt `hidden`
   an wie der Wurfknopf und wird nur für den Besitzer der Figur aufgedeckt.
   Ein `<div>`, kein Knopf: Die Gegenregel für `hidden` steht deshalb hier. */
.roll-follow-ups { display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; margin-top: 0.5rem; }
.roll-follow-ups[hidden] { display: none; }
.roll-follow-ups__choice { display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; margin: 0; }

/* Der Neuwurf-Dialog: je Vorratswürfel ein Haken, und daneben das Feld für
   den, der selbst wirft. */
.reroll__dice { display: flex; flex-direction: column; gap: 0.5rem; }
.reroll__die { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.8rem; }
.reroll__die input[type="number"] { width: 5rem; }
.reroll__error { color: var(--danger); margin: 0 0 0.7rem; }
.reroll__error:empty { display: none; }

/* Der Notizzettel ---------------------------------------------------------
   Zwei Zustände, einer davon zugeklappt. */

/* **Die Gegenregel, gleich daneben.** Hier steht bewusst kein `display` auf
   `.note__stage` — ein `<div>` und ein `<form>` sind von sich aus Blöcke, und
   die Browservorgabe `[hidden] { display: none }` greift dann. Wer hier später
   ein `display: flex` einträgt, hebelt sie aus und hat beide Zustände
   gleichzeitig im Dialog stehen; das ist diesem Projekt beim `<dialog>` und
   beim Porträt schon zweimal passiert. Also die Regel jetzt schon hinschreiben,
   damit sie beim nächsten Umbau nicht fehlt. */
.note__stage[hidden] { display: none; }

.note__label {
  display: block;
  margin-bottom: 0.45rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-muted);
}

/* Geschrieben wird wie im Beitragsfeld — dieselbe Schrift, dieselbe
   Schriftgröße, die derselbe Mensch mit denselben Augen eingestellt hat.
   Anders als dort ziehbar: Hier misst kein `grow` die Höhe nach, also gehört
   sie dem, der schreibt. */
.note__field {
  width: 100%;
  font-family: var(--font-type);
  font-size: calc(0.95rem * var(--reading-scale));
  font-size-adjust: 0.47;
  line-height: 1.7;
  padding: 0.8rem 1rem;
  background: var(--bg-paper);
  border-radius: 2px;
  caret-color: var(--accent);
  resize: vertical;
  min-height: 10rem;
  max-height: 60dvh;
}

.note__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.8rem;
  /* Ohne das zieht eine Knopfreihe die Seite auf dem Telefon quer. */
  flex-wrap: wrap;
}

/* „Notizen" neben dem Namen der Figur im Kopf des Dialogs: Der Name kommt vom
   Controller, das Wort steht fest. */
.notes__kind {
  margin-left: 0.5ch;
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--text-muted);
}

/* Die Zeilen des Charakterbogens ------------------------------------------
   Gruppe, Bezeichnung, Wert — gelesen und bearbeitet.

   Bewusst **keine Tabelle**: `.sheet__body table` trägt eine Mindestbreite von
   30 rem, damit ein fünfspaltiger KULT-Werteblock überläuft statt sich zu
   einer Wortsäule zu quetschen. Zwei Spalten aus zwei Wörtern brauchen die
   nicht und bekämen auf dem Telefon einen Rollbalken, wo nichts überläuft. */

.lines { margin-bottom: 1.2rem; }
.lines__group + .lines__group { margin-top: 1.5rem; }

/* **Die Überschrift muss aus der Reihe fallen, nicht sich einreihen.** Sie war
   klein, grau und in Versalien — dieselbe Behandlung wie `.note__label`, und
   das war der Fehler: Dort steht eine Marke über einem Feld, hier trennt sie
   zehn Gruppen in einer Liste, deren Zeilen alle eine Trennlinie tragen. Eine
   graue Zeile mehr zwischen lauter grauen Trennlinien ist keine Trennung, und
   auf Wolfharts Bogen mit seinen zehn Gruppen sah man die Abschnitte nicht.

   Also ein eigenes Band statt einer Zeile: Fläche, Akzentkante, kräftige
   Schrift. Das ist der einzige Ort in diesem Bogen, der Fläche trägt — damit
   ist er auch beim Überfliegen der einzige Halt. */
.lines__heading {
  margin: 0 0 0.4rem;
  padding: 0.28rem 0.6rem;
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  border-radius: 2px;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text);
}

.lines__list { margin: 0; }

/* **Flex mit Umbruch, kein Raster.** Hier stand `grid-template-columns:
   minmax(0, 1fr) auto`, und bei „Kritischer Treffer: 20 (ab Stufe 4: 19–20) —
   ×2 direkt auf den Körperwert" schob sich die `auto`-Spur über die
   Bezeichnung: Eine `auto`-Spur will so breit werden wie ihr Inhalt, und das
   `minmax(0, …)` daneben erlaubt der ersten, auf null zu schrumpfen — die
   beiden lagen dann übereinander. Am Telefon nachgemessen, nicht vermutet.

   Flex bricht stattdessen um: Ein Umbruch entscheidet sich an der
   Ausgangsbreite (`flex-basis: auto` = Inhaltsbreite), **bevor** geschrumpft
   wird. Passen Bezeichnung und Wert nebeneinander, stehen sie nebeneinander;
   passen sie nicht, bekommt der Wert die nächste Zeile ganz für sich. */
.lines__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.8rem;
  align-items: baseline;
  padding: 0.18rem 0;
  border-bottom: 1px solid var(--border);
}
.lines__row:last-child { border-bottom: none; }
.lines__row--used { opacity: 0.5; }
.lines__row--used .lines__label { text-decoration: line-through; }

.lines__label { flex: 0 0 auto; color: var(--text-muted); }

/* `flex: 1 1 auto` — der Wert nimmt den Rest der Zeile und steht rechts, wo
   die Zahlen untereinander stehen. Landet er auf einer eigenen Zeile, füllt er
   sie und die Ausrichtung fällt nicht mehr auf. `min-width: 0`, damit ein
   langes Wort umbricht statt die Zeile aufzuspannen. */
.lines__value {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Der Merker „zuletzt von der Spielleitung". Ein Zeichen, kein Kasten: Wer
   ihn nicht sucht, soll ihn nicht lesen müssen. */
.lines__mark {
  margin-left: 0.3ch;
  color: var(--accent);
  cursor: help;
}

.lines-edit { margin: 0 0 1rem; padding: 0; border: none; }
.lines-edit__rows { display: flex; flex-direction: column; gap: 0.4rem; }

/* Ohne `flex-wrap` zöge die Zeile die Seite auf dem Telefon quer. Die
   Ausgangsbreite ist ein Maß und keine Null: Bei `flex: 1 1 0` bräche nie
   etwas um, und drei Felder stünden auf 360 px zu je hundert Pixeln
   nebeneinander. */
.line-row { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.line-row input[type=text] { flex: 1 1 9rem; min-width: 0; }
.line-row__group { flex-basis: 7rem; }
/* Eine Zahl braucht keine neun Zeichen; Stand und Maximum stehen mit dem
   Schrägstrich dazwischen wie in der Anzeige darunter: „12 / 14". */
.line-row__number { flex: 0 1 5rem; min-width: 0; }
.line-row__slash { color: var(--text-muted); }

.line-row__drop {
  display: flex;
  align-items: center;
  gap: 0.35ch;
  flex: 0 0 auto;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Staff track: story left, machinery right. */
.tracks { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
@media (min-width: 60rem) {
  .tracks { grid-template-columns: 3fr 2fr; align-items: start; }
}
.tracks__story, .tracks__technical { display: flex; flex-direction: column; }
.tracks__story h2, .tracks__technical h2 { margin-top: 0; }
.tracks__story > div, .tracks__technical > div { display: flex; flex-direction: column; gap: 0.75rem; }

/* Rendered Markdown ----------------------------------------------------- */

.prose > *:first-child { margin-top: 0; }
.prose > *:last-child { margin-bottom: 0; }
/* Satz siehe oben. Hier nur die Abstände. */
.prose h1, .prose h2, .prose h3, .prose h4 { margin: 1.25rem 0 0.5rem; }
.prose p, .prose ul, .prose ol { margin: 0 0 0.75rem; }
.prose ul, .prose ol { padding-left: 1.3rem; }
.prose li { margin-bottom: 0.2rem; }
.prose code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0.05em 0.3em;
}
.prose pre {
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  overflow-x: auto;
}
.prose pre code { background: none; border: none; padding: 0; }
.prose blockquote {
  margin: 0 0 0.75rem;
  padding-left: 1rem;
  border-left: 3px solid var(--accent);
  color: var(--text-muted);
  font-style: italic;
}
/* Eine Tabelle ist keine Zeile Prosa und darf am Maß vorbei. Sie rollt für
   sich, damit die Seite dabei nicht quer geht — dieselbe Regel wie in einer
   Chatblase. Ohne sie schiebt der Werteblock eines Monsters die ganze Seite
   auf dem Telefon aus dem Bild. */
.prose table { display: block; overflow-x: auto; max-width: 100%; margin-bottom: 0.75rem; }
.prose hr { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; }

/* The state block is maintained by the storyteller, not by the player — it
   has to read as something other than the sheet above it. */
.state {
  background: var(--bg-sunken);
  border-left: 3px solid var(--accent);
}

/* A ruling sits inside the section it bends, visibly apart from it. */
.ruling {
  margin: 1rem 0 0;
  padding: 0.75rem 1rem;
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.ruling__head {
  margin: 0 0 0.4rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
}
.ruling .prose { font-size: calc(var(--reading-size) * 0.93); }

code.token {
  display: inline-block;
  padding: 0.4rem 0.6rem;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  word-break: break-all;
}

ul.plain { list-style: none; padding: 0; margin: 0 0 0.5rem; }
ul.plain li { margin-bottom: 0.3rem; }

.form--wide { max-width: none; }
.form--wide textarea { font-family: var(--font-mono); font-size: 0.9rem; line-height: 1.55; }

/* Auth screens ---------------------------------------------------------- */

.auth {
  max-width: 24rem;
  margin: 4rem auto;
}
.auth h1 { font-size: 1.4rem; text-align: center; }
.auth__links { margin-top: 1.5rem; text-align: center; font-size: 0.85rem; }
.auth__links a { display: block; margin-top: 0.4rem; }

.portrait-preview {
  display: block;
  width: 90px;
  height: auto;
  border: 2px solid var(--border);
  border-radius: 3px;
}

/* Kopfzeile einer Figur: Bild neben Name und Angaben. */
.figure-head { display: flex; align-items: flex-start; gap: 1rem; }

/* Breadcrumb: wo bin ich, und wie komme ich zurück. */
.breadcrumb { margin-bottom: 1.25rem; font-size: 0.85rem; }
.breadcrumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; list-style: none; margin: 0; padding: 0; }
.breadcrumb li { display: flex; align-items: center; gap: 0.5rem; color: var(--text-muted); }
.breadcrumb li + li::before { content: "›"; color: var(--border); }
.breadcrumb a { color: var(--text-muted); }
.breadcrumb a:hover { color: var(--accent); }
.breadcrumb [aria-current="page"] span { color: var(--text); }

/* Abenteuerkarte -------------------------------------------------------- */

/* Der Knotenbaum. Eine Linie links statt Aufzählungspunkten: bei fünfzig
   Knoten über vier Ebenen sagt die Einrückung mehr als jedes Zeichen. */
.tree { list-style: none; margin: 0; padding: 0; }
.tree .tree {
  margin: 0.5rem 0 0.75rem 0.35rem;
  padding-left: 1rem;
  border-left: 1px solid var(--border);
}
.node { margin-bottom: 0.75rem; }
.node__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin: 0; }
.node__head code { font-size: 0.8rem; }
.node p { margin: 0.15rem 0; }

/* Die Marken einer Zeile. `wrap`, sonst zieht die Reihe auf dem Telefon
   die ganze Seite quer. */
.marks { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0.35rem 0 0; }
.marks:empty { display: none; }

/* Besetzung: Bild neben Namen und Marken. */
.cast { list-style: none; margin: 0; padding: 0; }
.cast-member {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--border);
}
.cast-member:last-child { border-bottom: none; }
.cast-member > div { min-width: 0; }
.portrait--small { width: 48px; }
.portrait--small .portrait__initial { font-size: 1.2rem; }

/* Handouts als Bild, nicht als Dateiname. */
.handouts { display: flex; flex-wrap: wrap; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.handouts img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border); border-radius: 3px; }

/* Der Chat auf dem Telefon ------------------------------------------------
 *
 * Am Rechner ist die Zeile „Porträt · Blase" richtig und die Deckelung auf
 * 78 % hält die Blasen davon ab, den ganzen Bildschirm zu überspannen. Auf
 * einem Telefon multiplizieren sich beide zum Gegenteil: 76 px Porträt plus
 * Abstand sind rund ein Viertel von 360 px, und was übrig bleibt, wird noch
 * einmal auf 78 % gestutzt. Übrig blieben vier Wörter pro Zeile — bei einer
 * Runde, die in Absätzen spielt, ist das nicht zu lesen.
 *
 * Das Porträt bleibt. Es ist der schnellste Hinweis darauf, wer spricht, und
 * mit einem echten Bild trägt es die halbe Szene. Es wird nur kleiner.
 */
@media (max-width: 40rem) {
  .message--dialogue { gap: 0.6rem; }

  /* Klein genug, dass Text danebenpasst — groß genug, dass man ein Gesicht
     erkennt. Der leere Streifen unter dem Porträt schrumpft mit. */
  .portrait { width: 48px; }
  .portrait__initial { font-size: 1.4rem; }

  /* Die Deckelung gehört auf den breiten Schirm, nicht hierher: Hier ist der
     Platz neben der Blase ohnehin schon vom Porträt genommen. */
  .bubble { max-width: 100%; }

  /* Ohne `wrap` zieht die Kopfzeile aus Name, Art und Uhrzeit die Nachricht
     quer aus dem Bild — dieselbe Falle wie bei den Knopfreihen. */
  .message__head { flex-wrap: wrap; gap: 0.35rem 0.6rem; }
  .message__time { margin-left: 0; }
}

/* ── Die Galerie ───────────────────────────────────────────────────────────
   Alle Bilder eines Abends nebeneinander. `auto-fill` wie bei den Kampagnen:
   Wer drei Bilder hat, soll keine leere vierte Spalte sehen.

   Die Kachel folgt dem Bild und nicht umgekehrt — `height: auto`, kein
   `object-fit: cover`. Ein hochkantes Handout bleibt hochkant; beschnitten
   wird hier so wenig wie bei einem Porträt. Dass die Reihen dadurch
   unterschiedlich hoch werden, ist der Preis dafür, dass jedes Bild ganz zu
   sehen ist — `masonry` gibt es noch nicht überall, also `align-items:
   start`, damit die Kacheln oben bündig stehen statt in der Mitte zu
   schwimmen. */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
  gap: 1.5rem;
  align-items: start;
}

.gallery__item { margin: 0; }

.gallery__frame { display: block; }

.gallery__frame img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.gallery__caption {
  margin: 0.5rem 0 0.15rem;
  font-style: italic;
}

.gallery__when { margin: 0; }

/* ── Der Lichtkasten ───────────────────────────────────────────────────────
 *
 * Alle Bilder des Abends, durchzuwischen. Aufgemacht über den Knopf **Bilder**
 * neben den Notizen.
 *
 * **Er hing eine Fassung lang als Vorschaubild neben dem Chat.** Das ging
 * nicht: `.page` ist auf 62 rem gedeckelt, und die braucht der Schreibtisch
 * ganz — Blatt (`66ch`), Lücke, Gesicht (7 rem). Eine zweite Spalte ließ dem
 * Chat 728 statt 952 px, das Blatt wurde gequetscht, das Gesicht rutschte
 * darunter, und der Schreibtisch sah am Rechner aus wie auf dem Telefon.
 * Ein Knopf in einer Reihe, die es schon gibt, kostet nichts.
 */

/* Nur am **offenen** Dialog: Eine Autorenregel schlägt die Vorgabe des
   Browsers, also stünde der ganze Lichtkasten bei schlichtem `display: flex`
   dauerhaft unter dem Chat. Dieselbe Falle wie beim Charakterbogen. */
.lightbox {
  width: min(60rem, 100vw - 2rem);
  max-height: 90dvh;
  margin: auto;
  padding: 0;
  overflow: hidden;
  flex-direction: column;
  background: var(--bg-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.lightbox[open] { display: flex; }
.lightbox::backdrop { background: var(--backdrop); }

.lightbox__head,
.lightbox__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 1rem;
  /* Außerhalb des rollenden Bereichs: Sonst ist der Schließen-Knopf auf dem
     Telefon nach dem ersten Wischen unerreichbar. */
  flex: none;
}
.lightbox__head { border-bottom: 1px solid var(--border); }
.lightbox__foot { border-top: 1px solid var(--border); justify-content: space-between; }
.lightbox__count { margin: 0; margin-right: auto; color: var(--text-muted); }

/* Der Schieber. `scroll-snap` macht das Wischen, den Schwung und das
   Einrasten — auf dem Telefon ohne eine Zeile JavaScript. */
.lightbox__track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
}

.lightbox__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  margin: 0;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.lightbox__slide img {
  display: block;
  max-width: 100%;
  /* Das ganze Bild, nicht ein Ausschnitt davon — auch ein hochkantes Handout
     bleibt hochkant. */
  max-height: 65dvh;
  width: auto;
  height: auto;
  border-radius: var(--radius);
}

.lightbox__slide figcaption {
  font-style: italic;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* ── Orientierung im Verlauf ─────────────────────────────────────────────────
 *
 * Ein Tisch, der in Absätzen spielt, schreibt sechshundert bis tausend
 * Beiträge am Tag. Wer zurückkommt, braucht drei Dinge: wo er ist (die
 * Szenenkarte klebt oben), wann (die Zeitmarke steht nur, wo die Uhr
 * umspringt) und wo er aufgehört hat (der Merker „ungelesen").
 */

/* **Die Karte klebt.** `sticky` innerhalb von `#messages`: Jede Karte bleibt
   oben stehen, sobald sie den Rand erreicht, und die nächste schiebt sich
   beim Vorbeiscrollen darüber — darum der undurchsichtige Grund und der
   Stapelwert. `top` ist die Höhe der Kopfleiste (siehe `scroll-padding-top`).
   Die Regel gilt nur im Chat; auf der Leseseite einer Szene steht die Karte
   einmal und muss nicht kleben. */
.chat .scene-break {
  position: sticky;
  top: 3.6rem;
  z-index: 5;
  margin-top: 1.4rem;
  padding: 0.35rem 0.9rem;
  background: var(--bg);
}

/* Die Zeitmarke: eine stille Zeile wie die Karte, nur kleiner. */
.time-mark {
  display: flex;
  justify-content: center;
  margin: 0.6rem auto 0;
  font-size: 0.72rem;
  color: var(--text-muted);
  text-align: center;
}

/* Der Merker „ungelesen": eine Linie mit einem Wort, in der Akzentfarbe —
   das eine Mal, wo im Verlauf etwas hervorsticht, weil es der Grund ist,
   überhaupt hinzusehen. */
.unread-mark {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.4rem 0;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.unread-mark::before, .unread-mark::after {
  content: "";
  flex: 1 1 0;
  border-top: 1px solid var(--accent);
  opacity: 0.6;
}

/* Filter: eine Klasse am Verlauf, und alles andere tritt zurück. Karten,
   Zeitmarken und der Merker bleiben — sie sind die Orientierung. `hidden`
   bekommt hier nichts; ein gefilterter Beitrag ist nicht verschwunden,
   sondern nur nicht im Bild. */
.messages--only-ic .message:not(.message--ic, .message--ooc),
.messages--only-narration .message:not(.message--narration, .message--system, .message--image),
.messages--only-roll .message:not(.message--roll, .message--roll_request) { display: none; }

/* Hineinzoomen per Tippen — dieselbe große Fassung wie beim Überfahren. */
.message .portrait.portrait--zoomed .portrait__zoom { display: block; }

/* Die Kopfleiste auf dem Telefon: drei Zeilen waren ein Viertel des Bildes.
   Kleinere Schrift, engere Abstände, und der Kampagnenname darf breiter
   werden, weil die Links daneben weniger brauchen. */
@media (max-width: 40rem) {
  .topbar { padding: 0.45rem 0.9rem; gap: 0.35rem 0.75rem; }
  .topbar__brand { font-size: 1rem; }
  .topbar__nav { font-size: 0.82rem; gap: 0.25rem 0.85rem; }
  .topbar__campaign > summary { max-width: 60vw; }
  .chat .scene-break { top: 2.9rem; }
}

/* Springen und Filtern teilen sich auf dem Telefon eine Zeile — untereinander
   kosteten sie zwei, und die Höhe gehört dem Verlauf. `min-width: 0`, damit
   ein langer Eintrag die Zeile nicht sprengt. */
@media (max-width: 40rem) {
  .compose__jump, .compose__filter { flex: 1 1 8rem; min-width: 0; }
}

/* **Das Menü „⋯"** (`more_controller`). Auf dem Rechner gibt es kein Menü:
   Hülle und Fach sind `display: contents`, ihre Knöpfe und Auswahlfelder
   stehen als Glieder der Knopfreihe da, wie vorher. Auf dem Telefon bleibt
   der Schalter in der Reihe, das Fach klappt als eigene Zeile darunter auf —
   `order` schiebt es hinter „Prüfen". Kein `hidden` am Fach: Es muss auf dem
   Rechner sichtbar sein, ohne dass jemand es aufmacht. */
.compose__more, .compose__more-panel { display: contents; }
.compose__more-toggle { display: none; }

@media (max-width: 40rem) {
  .compose__more-toggle { display: inline-block; order: 1; font-size: 1.1rem; line-height: 1; padding: 0.45rem 0.8rem; }
  .compose__row > :not(.compose__as):not(.compose__more) { order: 1; }
  .compose__more-panel { display: none; }
  .compose__more--open .compose__more-panel {
    display: flex; flex-wrap: wrap; gap: 0.5rem; order: 2; flex: 1 1 100%;
    padding-top: 0.5rem; border-top: 1px solid var(--border);
  }
  .compose__more--open .compose__more-toggle { color: var(--text); border-color: var(--text-muted); }
}

/* ── Die Zugleiste ──────────────────────────────────────────────────────────
   Über dem Eingabefeld, solange ein Kampf läuft: Runde, die Reihenfolge als
   Namen, der Aktive hervorgehoben, Ausgeschiedene gedämpft. Kommt fertig vom
   Server (`Encounter::Broadcast`); ein leerer `#encounter_bar` kostet nichts. */
.turnbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.8rem;
  margin: 0.8rem 0 0.2rem;
  padding: 0.5rem 0.8rem;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.8rem;
}
.turnbar__head { display: flex; gap: 0.5rem; align-items: baseline; }
.turnbar__title { font-weight: 600; }
.turnbar__round { color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.72rem; }
.turnbar__order { display: flex; flex-wrap: wrap; gap: 0.3rem 0.5rem; margin: 0; padding: 0; list-style: none; }
.turnbar__entry {
  display: inline-flex;
  gap: 0.3rem;
  align-items: baseline;
  padding: 0.1rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
}
.turnbar__entry--active { border-color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.turnbar__entry--out { opacity: 0.45; text-decoration: line-through; }
.turnbar__initiative { color: var(--text-muted); font-size: 0.72rem; }
.turnbar__waiting { color: var(--text-muted); font-style: italic; }

/* „Du bist dran": das Eingabefeld leuchtet, bis jemand anderes dran ist.
   Gesetzt vom `encounter`-Controller, nur beim Besitzer der aktiven Figur. */
.chat--your-turn .compose__sheet { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* ── Die Spuren ─────────────────────────────────────────────────────────────
   Welche Form eine Spur hat, entscheidet `TrackDisplay`: Marken bis 10,
   Segmente bis 40, sonst ein Balken; Zähler ohne Maximum; Kästchen für den
   Schadenszähler; Geld als Börse. Diese Regeln hier sind die ruhige
   Grundfassung, die überall gilt — Stand-Dialog, Figurenseite, Gruppenzeile.
   Die Handschrift der Systeme gilt nur in der Spurenleiste (weiter unten).
   Feste Schrift (`rem`): Werte sind Nachschlagwerk, kein Lesetext. */
.tracks { display: flex; flex-direction: column; gap: 0.2rem; width: 100%; font-size: 0.7rem; line-height: 1.3; }
.tracks:empty { display: none; }
.track { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 0.35rem; align-items: center; text-align: left; }
.track--count, .track--purse { grid-template-columns: auto auto; justify-content: space-between; }
.track--tokens { grid-template-columns: auto minmax(0, 1fr) auto; }
.track__label { color: var(--text-muted); white-space: nowrap; }
.track__value { font-variant-numeric: tabular-nums; white-space: nowrap; }
.track__bar {
  position: relative;
  display: block;
  height: 0.45rem;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.track__fill { display: block; height: 100%; background: var(--accent); }
/* Segmente: dieselbe Füllung, darüber `--n` Trennstriche. Ein Feld je Punkt —
   „noch drei Treffer" sieht man, ohne zu rechnen. */
.track--segments .track__bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(to right, transparent 0 calc(100% / var(--n) - 1px),
                                        var(--bg-raised) calc(100% / var(--n) - 1px) calc(100% / var(--n)));
}
.track--segments .track__bar { border-radius: 2px; }
.track__pips { display: inline-flex; flex-wrap: wrap; gap: 3px; }
.track__pip { width: 0.6rem; height: 0.6rem; border-radius: 50%; border: 1.5px solid var(--text-muted); }
.track__pip.is-on { background: var(--accent); border-color: var(--accent); }
.track__coins { display: inline-flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; }
.track__coin { display: inline-flex; align-items: center; gap: 0.25rem; }
.track__coin::before { content: ""; width: 0.7rem; height: 0.7rem; border-radius: 50%; background: var(--coin, var(--accent));
                       box-shadow: inset 0 0 0 1.5px rgb(0 0 0 / 0.3); }
.track__coin--gold { --coin: #d9a93a; }
.track__coin--silver { --coin: #c3c8cd; }
.track__coin--copper { --coin: #b06a3b; }
.track__coin--platinum { --coin: #e4e8ee; }
.track__coin--note { --coin: #6f9a6a; }
.track__coin--coin { --coin: var(--hand-mark, var(--accent)); }
.track__coin--note::before { border-radius: 2px; width: 0.9rem; height: 0.6rem; }
.track__coin-label { color: var(--text-muted); }
/* Zustände (Dragonbane): Wörter mit Kästchen, angekreuzt in der Farbe der
   Gefahr — Nachteil auf alles, was an dem Attribut hängt. */
.track--conditions { grid-template-columns: auto minmax(0, 1fr); }
.track__conditions { display: inline-flex; flex-wrap: wrap; gap: 0.15rem 0.6rem; }
.track__condition { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--text-muted); }
.track__condition::before { content: ""; width: 0.65rem; height: 0.65rem; border: 1.5px solid currentColor; border-radius: 1px; }
.track__condition.is-on { color: var(--danger); font-weight: 600; }
.track__condition.is-on::before { background: currentColor; }
.track__candle { position: relative; display: flex; align-items: flex-end; width: 0.7rem; height: 1.6rem; }
.track__wax { width: 100%; background: linear-gradient(#f5ecd6, #d8c69e); border-radius: 2px 2px 0 0; position: relative; }
.track__wax::before { content: ""; position: absolute; left: 50%; top: -0.5rem; width: 0.4rem; height: 0.5rem; transform: translateX(-50%);
                      border-radius: 50% 50% 45% 45%; background: #e89a2b; box-shadow: 0 0 0.5rem #f4b44a; }
.track__stamp { grid-column: 1 / -1; justify-self: start; color: var(--danger); border: 1.5px solid currentColor;
                padding: 0 0.3rem; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.06em; transform: rotate(-3deg); }
/* Die Fortschrittsuhr (Blades): ein Kreis in Tortenstücken, gefüllt im
   Uhrzeigersinn. Die Striche zwischen den Stücken sind die Kanten der Pfade
   in der Farbe des Grundes — so bleiben sie auch voll sichtbar. */
.track--clock { grid-template-columns: auto auto auto; justify-content: start; }
.track__clock { width: 1.6rem; height: 1.6rem; display: block; }
.track__slice { fill: transparent; stroke: var(--text-muted); stroke-width: 1.2; stroke-linejoin: round; }
.track__slice.is-on { fill: var(--accent); }
.track--clock.is-full .track__value { color: var(--danger); font-weight: 600; }
/* Die Skala (Fraktionsstatus −3 bis +3): eine Marke je Stufe, die Null als
   Strich, die aktuelle gefüllt — rot unter null, in der Akzentfarbe darüber. */
.track--scale { grid-template-columns: auto auto auto; justify-content: start; }
.track__scale { display: inline-flex; align-items: center; gap: 3px; }
.track__step { width: 0.55rem; height: 0.55rem; border: 1.5px solid var(--text-muted); border-radius: 1px; }
.track__step.is-zero { width: 2px; border-width: 0 0 0 2px; border-radius: 0; height: 0.8rem; }
.track__step.is-on { background: var(--accent); border-color: var(--accent); }
.track--scale.is-negative .track__step.is-on { background: var(--danger); border-color: var(--danger); }
.track__step.is-zero.is-on { border-color: var(--accent); }
/* Knapp: höchstens ein Viertel übrig. */
.track--low .track__fill, .track--low .track__pip.is-on { background: var(--danger); border-color: var(--danger); }
.track--low .track__value { color: var(--danger); }

/* Nach einem Treffer einmal aufblitzen, nach Heilung einmal aufleuchten —
   gesetzt vom `tracks`-Controller, der den letzten Wert kennt. */
@media (prefers-reduced-motion: no-preference) {
  :is(.track, .track__coin, .track__condition).is-hit { animation: track-hit 0.9s ease-out; }
  :is(.track, .track__coin, .track__condition).is-healed { animation: track-healed 0.9s ease-out; }
  @keyframes track-hit { 0%, 30% { background: color-mix(in oklab, var(--danger) 45%, transparent); } 100% { background: transparent; } }
  @keyframes track-healed { 0%, 30% { background: color-mix(in oklab, #5fbf6a 40%, transparent); } 100% { background: transparent; } }
}


/* Die Gruppenzeile: Spuren der Kampagne (Kasse, Vorräte) in einer Reihe über
   der Zugleiste. Leer ohne Höhe — der Rahmen bleibt für den Stream stehen.
   `flex-wrap`, sonst zieht sie auf dem Telefon die Seite quer. */
.partyline { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.2rem 0.6rem; font-size: 0.78rem; }
.partyline[hidden] { display: none; }
.partyline__label { color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem; }
.partyline .tracks { flex-direction: row; flex-wrap: wrap; width: auto; flex: 1 1 0; min-width: 0; gap: 0.2rem 1rem; font-size: 0.78rem; }
.partyline .track { grid-template-columns: auto 5rem auto; }
.partyline .track--count, .partyline .track--purse { grid-template-columns: auto auto; }
/* Marken so breit wie sie sind — in 5 rem brächen zehn Rationen in zwei Reihen. */
.partyline :is(.track--pips, .track--tokens) { grid-template-columns: auto auto auto; }
.partyline .track__pips { flex-wrap: nowrap; }
.state .tracks { font-size: 0.9rem; max-width: 24rem; }

/* ── Die Spurenleiste ───────────────────────────────────────────────────────
   Die Spuren der Figur, als die geschrieben wird, in einer eigenen Zeile des
   Schreibtischs: unter Blatt und Gesicht, über der Knopfreihe; auf dem
   Telefon direkt unter dem Blatt (Reihenfolge im Medienblock des
   Schreibtischs). Jede Spur eine Karte, die Reihe bricht um. */
.compose__desk > .compose__trackline { order: 2; flex: 1 1 100%; min-width: 0; }
.trackline[hidden] { display: none; }
/* Auf dem Telefon direkt unter dem Blatt, vor Gesicht und Knopfreihe, die
   nebeneinander stehen bleiben. **Hier und nicht im Medienblock des
   Schreibtischs:** Die Regel darüber hat dieselbe Spezifität und steht
   später — dort überschrieben, landete die Leiste zwischen Gesicht und
   Knöpfen, und die Knöpfe rutschten unter das Gesicht. */
@media (max-width: 40rem) {
  .compose__desk > .compose__trackline { order: 1; }
}
.trackline .tracks { flex-direction: row; flex-wrap: wrap; gap: 0.5rem; padding: 0.55rem 0.7rem; font-size: 0.8rem;
                     background: var(--bg-raised); border: 1px solid var(--border); border-radius: var(--radius); }
:is(.trackline, .stand__tracks) .track { display: flex; flex-direction: column; align-items: stretch; gap: 0.2rem; flex: 1 1 7.5rem; min-width: 0;
                    padding: 0.2rem 0.3rem; border-radius: 4px; }
:is(.trackline, .stand__tracks) .track__label { font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; }
:is(.trackline, .stand__tracks) .track__value { font-size: 1rem; line-height: 1; }
:is(.trackline, .stand__tracks) .track--bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "label label" "gauge value"; }
:is(.trackline, .stand__tracks) .track--bar .track__label { grid-area: label; }
:is(.trackline, .stand__tracks) .track--bar :is(.track__bar, .track__pips, .track__candle) { grid-area: gauge; align-self: center; }
:is(.trackline, .stand__tracks) .track--bar .track__value { grid-area: value; }
:is(.trackline, .stand__tracks) .track__bar { height: 0.65rem; }
:is(.trackline, .stand__tracks) .track__pip { width: 0.75rem; height: 0.75rem; }
/* Marken, Kerze und Kästchen sind so breit wie ihr Inhalt — die Zahl steht
   direkt daneben, nicht am anderen Ende der Karte. */
:is(.trackline, .stand__tracks) :is(.track--pips, .track--candle, .track--tokens, .track--boxes, .track--clock, .track--scale) {
  display: grid; grid-template-columns: max-content max-content; grid-template-areas: "label label" "gauge value";
  justify-content: start; column-gap: 0.6rem; }
:is(.trackline, .stand__tracks) :is(.track--tokens, .track--boxes, .track--clock, .track--scale) .track__label { grid-area: label; }
:is(.trackline, .stand__tracks) :is(.track--tokens .track__pips, .track__boxes, .track__clock, .track__scale) { grid-area: gauge; align-self: center; }
:is(.trackline, .stand__tracks) :is(.track--tokens, .track--boxes, .track--clock, .track--scale) .track__value { grid-area: value; align-self: center; }
:is(.trackline, .stand__tracks) .track--boxes .track__stamp { grid-column: 1 / -1; }
:is(.trackline, .stand__tracks) .track--boxes { flex-basis: 11rem; }
:is(.trackline, .stand__tracks) .track--purse { flex-basis: 10rem; }
:is(.trackline, .stand__tracks) .track--conditions { flex-basis: 100%; }
.partyline :is(.track--clock, .track--scale) { grid-template-columns: auto auto auto; }
:is(.trackline, .stand__tracks) .track__box { width: 1rem; height: 1rem; font-size: 0.75rem; }

/* Die Schriften der Systeme liegen in der App (app/assets/fonts), nicht bei
   Google: Eine Schrift von fremdem Server verrät jedem Aufruf die Adresse an
   einen Dritten. Alle unter SIL Open Font License bzw. Apache 2.0. */
@font-face { font-family: "Archivo Black"; src: url("/assets/archivo-black-4c78e9ca.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Archivo Narrow"; src: url("/assets/archivo-narrow-500-7ef97512.woff2") format("woff2"); font-weight: 500; font-display: swap; }
@font-face { font-family: "Archivo Narrow"; src: url("/assets/archivo-narrow-700-d156d43c.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "IM Fell English SC"; src: url("/assets/im-fell-english-sc-762c22ff.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "IM Fell English"; src: url("/assets/im-fell-english-10e9ce72.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Pirata One"; src: url("/assets/pirata-one-997ef510.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Special Elite"; src: url("/assets/special-elite-61176593.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "VT323"; src: url("/assets/vt323-660b6f60.woff2") format("woff2"); font-display: swap; }
@font-face { font-family: "Cinzel"; src: url("/assets/cinzel-700-875d55e3.woff2") format("woff2"); font-weight: 700; font-display: swap; }

/* ── Die Handschrift der Systeme ───────────────────────────────────────────
   Drei Zeilen über und unter dem Eingabefeld tragen sie: die Gruppenzeile
   (`.partyline`), die Zugleiste im Kampf (`.turnbar`) und die Spurenleiste
   (`.trackline`). **Die Farben stehen einmal je System als Werte** (`--hand-…`)
   und alle drei lesen daraus — drei Fassungen derselben Palette liefen
   auseinander. Welches System, entscheidet `TrackDisplay.theme_for` aus dem
   Grundregelwerk; ohne eigene Handschrift bleibt die PERSONA-Fassung. */

/* Shadowdark: schwarzes Papier, harte Kontraste, fette Groteske. */
.hand--shadowdark { --hand-bg: #0d0d0d; --hand-ink: #f2efe8; --hand-label: #f2efe8; --hand-muted: #b9b4aa;
                    --hand-mark: #f2efe8; --hand-danger: #e8543a; --hand-border: 2px solid #f2efe8; --hand-radius: 3px;
                    --hand-font: "Archivo Narrow", var(--font-ui); --hand-display: "Archivo Black", var(--font-ui);
                    --hand-label-size: 0.68rem; --hand-weight: 500; }
/* Eiserne Zeit: Pergament, Tinte, Kerben, eine Kerze. */
.hand--eiserne-zeit { --hand-bg: #e9dcc0; --hand-ink: #2b1d10; --hand-label: #5b4128; --hand-muted: #7a5e3f;
                      --hand-mark: #3a2412; --hand-danger: #8e1c12; --hand-border: 1px solid #9c7f55; --hand-radius: var(--radius);
                      --hand-shadow: inset 0 0 2.5rem rgb(120 85 40 / 0.35);
                      --hand-font: "IM Fell English", var(--font); --hand-display: "IM Fell English SC", var(--font);
                      --hand-label-size: 0.9rem; --hand-label-case: none; }
/* Pirate Borg: Schwefelgelb auf Schwarz, Fraktur, leicht schief gedruckt. */
.hand--pirate-borg { --hand-bg: #111; --hand-ink: #ffe600; --hand-label: #ff3b2f; --hand-muted: #ffe600aa;
                     --hand-mark: #ffe600; --hand-danger: #ff3b2f; --hand-border: 3px solid #ffe600; --hand-radius: 0;
                     --hand-font: "Pirata One", var(--font); --hand-display: "Pirata One", var(--font);
                     --hand-label-size: 1rem; --hand-label-case: none; --hand-tilt: rotate(-0.3deg); }
/* MÖRK BORG: die Umkehrung von Pirate Borg — Schwarz auf Giftgelb wie der
   Einband, Magenta für das, was stirbt, Fraktur, schief in die andere Richtung. */
.hand--mork-borg { --hand-bg: #ffe900; --hand-ink: #0a0a0a; --hand-label: #0a0a0a; --hand-muted: #0a0a0aaa;
                   --hand-mark: #0a0a0a; --hand-danger: #e4007c; --hand-border: 3px solid #0a0a0a; --hand-radius: 0;
                   --hand-font: "Pirata One", var(--font); --hand-display: "Pirata One", var(--font);
                   --hand-label-size: 1rem; --hand-label-case: none; --hand-tilt: rotate(0.35deg); }
/* Welt der Dunkelheit: ein Blatt aus der Akte, Schreibmaschine, Rot für Schaden. */
.hand--wod { --hand-bg: #f4f1ea; --hand-ink: #1b1b1b; --hand-label: #1b1b1b; --hand-muted: #5d584e;
             --hand-mark: #1b1b1b; --hand-danger: #b3121b; --hand-border: 1px solid #b9b3a6; --hand-radius: var(--radius);
             --hand-image: repeating-linear-gradient(transparent 0 1.45rem, #d9d3c5 1.45rem 1.5rem);
             --hand-font: "Special Elite", var(--font-mono); --hand-display: "Special Elite", var(--font-mono); }
/* Mothership: Monitor im Frachtraum, Phosphorgrün, Pixelschrift. */
.hand--mothership { --hand-bg: #07120c; --hand-ink: #4dff88; --hand-label: #4dff88; --hand-muted: #2fae5c;
                    --hand-mark: #4dff88; --hand-danger: #ff4b4b; --hand-border: 1px solid #1f4d33; --hand-radius: var(--radius);
                    --hand-shadow: inset 0 0 1.2rem rgb(77 255 136 / 0.12);
                    --hand-font: "VT323", var(--font-mono); --hand-display: "VT323", var(--font-mono); --hand-label-size: 0.95rem; }

/* Nimble: die Seiten der Bücher — warmes Pergament, braune Tinte, graue
   Kästen, fette Serifen-Versalien in den Überschriften (Cinzel für das
   Beaufort des Buchs), schmale Groteske im Text (Archivo Narrow für Avenir
   Next Condensed). Marken als Rauten wie die Komplexitätsleiste der Klassen. */
.hand--nimble { --hand-bg: #efe5cf; --hand-ink: #2b221a; --hand-label: #2b221a; --hand-muted: #6e6152;
                --hand-mark: #3a2e24; --hand-danger: #9e2b1d; --hand-border: 1px solid #b9aa8d; --hand-radius: 2px;
                --hand-shadow: inset 0 0 1.8rem rgb(150 120 70 / 0.22);
                --hand-font: "Archivo Narrow", var(--font-ui); --hand-display: "Cinzel", var(--font);
                --hand-label-size: 0.72rem; --hand-weight: 500; }

/* Dragonbane: die Seiten des Grundbuchs — helles Pergament, Überschriften
   und Kästen in Petrolgrün, die Kastentitel als schmale, gesperrte
   Versalien (Archivo Narrow), die Zahlen fett (Archivo Black). */
.hand--dragonbane { --hand-bg: #eee5cf; --hand-ink: #26302d; --hand-label: #1f6b5c; --hand-muted: #5e6a64;
                    --hand-mark: #1f6b5c; --hand-danger: #9b2c1f; --hand-border: 1.5px solid #1f6b5c; --hand-radius: 3px;
                    --hand-shadow: inset 0 0 1.6rem rgb(120 100 60 / 0.2);
                    --hand-font: var(--font); --hand-display: "Archivo Narrow", var(--font-ui);
                    --hand-label-size: 0.72rem; --hand-weight: 400; }

/* Blades in the Dark: die Bögen des Buchs — Tinte auf Nachtschwarz,
   Knochenweiß, schmale gesperrte Versalien; kaltes Elektroplasma-Türkis für
   die Marken, Blutrot für das, was voll läuft. */
.hand--blades { --hand-bg: #15161a; --hand-ink: #e7e1d3; --hand-label: #a9c4c0; --hand-muted: #8d8a82;
                --hand-mark: #e7e1d3; --hand-danger: #c8412f; --hand-border: 1px solid #3b3e46; --hand-radius: 2px;
                --hand-shadow: inset 0 0 1.4rem rgb(95 184 176 / 0.08);
                --hand-font: "IM Fell English", var(--font); --hand-display: "Archivo Narrow", var(--font-ui);
                --hand-label-size: 0.7rem; --hand-weight: 400; }

/* Der Rahmen — an den drei Zeilen und am Stand-Dialog, nie auf der
   Figurenseite: dort bleibt es ruhig. In der Gruppenzeile trägt die Zeile
   den Rahmen, nicht die Spuren darin, sonst stünde ein Kasten im Kasten;
   im Dialog ebenso (siehe dort). */
:is(.trackline .tracks, .partyline, .turnbar, .statusdialog).hand:not(.hand--persona) {
  background-color: var(--hand-bg); background-image: var(--hand-image, none); color: var(--hand-ink);
  border: var(--hand-border); border-radius: var(--hand-radius); box-shadow: var(--hand-shadow, none);
  font-family: var(--hand-font); font-weight: var(--hand-weight, 400); transform: var(--hand-tilt, none);
}
.partyline.hand:not(.hand--persona) { padding: 0.45rem 0.7rem; margin-top: 0.6rem; }
.hand:not(.hand--persona) :is(.track__label, .partyline__label, .turnbar__round, .turnbar__title) {
  color: var(--hand-label); font-family: var(--hand-display); font-size: var(--hand-label-size, 0.66rem);
  text-transform: var(--hand-label-case, uppercase);
}
.hand:not(.hand--persona) .turnbar__title { color: var(--hand-ink); }
.hand:not(.hand--persona) :is(.track__coin-label, .turnbar__initiative, .turnbar__waiting) { color: var(--hand-muted); }
.hand:not(.hand--persona) .track--low .track__value { color: var(--hand-danger); }

/* Die Zugleiste: wer dran ist, steht in der Farbe der Marken, invertiert. */
.turnbar.hand:not(.hand--persona) .turnbar__entry { border-color: var(--hand-muted); border-radius: var(--hand-radius); }
.turnbar.hand:not(.hand--persona) .turnbar__entry--active { background: var(--hand-mark); color: var(--hand-bg); border-color: var(--hand-mark); }
.turnbar.hand:not(.hand--persona) .turnbar__entry--active .turnbar__initiative { color: var(--hand-bg); }
.turnbar.hand--pirate-borg .turnbar__name { font-size: 1.05rem; }
.turnbar.hand--mothership .turnbar__name { font-size: 1.1rem; }

/* Marken, Balken, Segmente je System — in Spurenleiste und Gruppenzeile. */
:is(.trackline, .partyline, .stand__tracks) .hand:not(.hand--persona) .track__pip { border-color: var(--hand-mark); }
:is(.trackline, .partyline, .stand__tracks) .hand:not(.hand--persona) .track__pip.is-on { background: var(--hand-mark); border-color: var(--hand-mark); }
:is(.trackline, .partyline, .stand__tracks) .hand:not(.hand--persona) .track__fill { background: var(--hand-mark); }
:is(.trackline, .partyline, .stand__tracks) .hand:not(.hand--persona) .track--low :is(.track__fill, .track__pip.is-on) { background: var(--hand-danger); border-color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand:not(.hand--persona) .track--segments .track__bar::after {
  background: repeating-linear-gradient(to right, transparent 0 calc(100% / var(--n) - 1px), var(--hand-bg) calc(100% / var(--n) - 1px) calc(100% / var(--n))); }

:is(.trackline, .stand__tracks) .hand.tracks--shadowdark .track__value { font-family: "Archivo Black", var(--font-ui); font-size: 1.15rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--shadowdark .track__bar { background: transparent; border: 2px solid #f2efe8; border-radius: 0; height: 0.8rem; }
:is(.trackline, .stand__tracks) .hand.tracks--shadowdark .track__pip { width: 0.95rem; height: 0.95rem; }

:is(.trackline, .stand__tracks) .hand.tracks--eiserne-zeit .track__value { font-size: 1.1rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--eiserne-zeit .track__bar { background: transparent; border: 0; border-radius: 0; height: 1.3rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--eiserne-zeit .track__fill { background: repeating-linear-gradient(to right, var(--hand-mark) 0 3px, transparent 3px 7px); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--eiserne-zeit .track--low .track__fill { background: repeating-linear-gradient(to right, var(--hand-danger) 0 3px, transparent 3px 7px); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--eiserne-zeit .track--segments .track__bar::after { display: none; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--eiserne-zeit .track__bar::before { content: ""; position: absolute; inset: 0;
                                                                        background: repeating-linear-gradient(to right, #3a241230 0 3px, transparent 3px 7px); }
:is(.trackline, .stand__tracks) .hand.tracks--eiserne-zeit .track--candle .track__candle { height: 2rem; width: 0.8rem; }
.hand--eiserne-zeit .track__coin-label { font-style: italic; }

:is(.trackline, .stand__tracks) .hand.tracks--pirate-borg .track__value { font-size: 1.6rem; }
:is(.trackline, .stand__tracks) .hand.tracks--pirate-borg .track--bar .track__value { font-size: 1.9rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--pirate-borg .track__bar { background: #2a2a00; border: 0; border-radius: 0; height: 0.5rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--pirate-borg .track__pip { border: 0; width: auto; height: auto; font-size: 1.2rem; line-height: 1; opacity: 0.25; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--pirate-borg .track__pip::before { content: "☠"; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--pirate-borg .track__pip.is-on { background: none; opacity: 1; }

.turnbar.hand--mork-borg .turnbar__name { font-size: 1.05rem; }
:is(.trackline, .stand__tracks) .hand.tracks--mork-borg .track__value { font-size: 1.6rem; }
:is(.trackline, .stand__tracks) .hand.tracks--mork-borg .track--bar .track__value { font-size: 1.9rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mork-borg .track__bar { background: #0a0a0a33; border: 0; border-radius: 0; height: 0.5rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mork-borg .track__pip { border: 0; width: auto; height: auto; font-size: 1.3rem; line-height: 1; opacity: 0.25; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mork-borg .track__pip::before { content: "†"; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mork-borg .track__pip.is-on { background: none; opacity: 1; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mork-borg .track--low .track__pip.is-on { background: none; color: var(--hand-danger); }

:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__box { border: 1.5px solid var(--hand-mark); background: #fff; color: var(--hand-mark); border-radius: 0; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__box--aggravated { color: var(--hand-danger); border-color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__bar { background: #fff; border: 1.5px solid var(--hand-mark); border-radius: 0; }
/* „In Verzweiflung": ein Kästchen hinter dem Wort, wie die Schadenskästchen. */
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__condition::before { border-color: var(--hand-mark); background: #fff; border-radius: 0; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__condition.is-on { color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--wod .track__condition.is-on::before { background: var(--hand-danger); border-color: var(--hand-danger); }
.hand--wod .track__stamp { color: var(--hand-danger); }

:is(.trackline, .stand__tracks) .hand.tracks--mothership .track__value { font-size: 1.4rem; }
:is(.trackline, .stand__tracks) .hand.tracks--nimble .track__value { font-family: "Cinzel", var(--font); font-weight: 700; font-size: 1.15rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--nimble .track__bar { background: #d9d0bd; border: 0; border-radius: 0; height: 0.7rem;
  clip-path: polygon(0.35rem 0, 100% 0, calc(100% - 0.35rem) 100%, 0 100%); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--nimble .track__pip { border-radius: 0; transform: rotate(45deg) scale(0.8); background: #d9d0bd; border-color: #d9d0bd; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--nimble .track__pip.is-on { background: var(--hand-mark); border-color: var(--hand-mark); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mothership .track__bar { background: #123321; border: 0; border-radius: 0; height: 0.8rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--mothership .track__pip { border-radius: 0; border: 0; background: #123321; width: 0.55rem; height: 0.9rem; }

.hand--dragonbane :is(.track__label, .partyline__label, .turnbar__round) { font-weight: 700; letter-spacing: 0.12em; }
:is(.trackline, .stand__tracks) .hand.tracks--dragonbane .track__value { font-family: "Archivo Black", var(--font-ui); font-size: 1.1rem; color: #1f6b5c; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--dragonbane .track__bar { background: #d8d0b8; border: 1px solid #1f6b5c; border-radius: 1px; height: 0.7rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--dragonbane .track__pip { border-radius: 1px; border-width: 1.5px; }
/* Die Zustände wie auf dem Bogen: ein Wort, davor ein Kästchen. */
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--dragonbane .track__condition::before { border-color: #1f6b5c; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--dragonbane .track__condition.is-on { color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--dragonbane .track__condition.is-on::before { background: var(--hand-danger); border-color: var(--hand-danger); }

.hand--blades :is(.track__label, .partyline__label, .turnbar__round) { font-weight: 700; letter-spacing: 0.16em; }
:is(.trackline, .stand__tracks) .hand.tracks--blades .track__value { font-family: "IM Fell English SC", var(--font); font-size: 1.15rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__pip { border-radius: 0; border-width: 1.5px; transform: skewX(-8deg); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__bar { background: transparent; border: 1px solid var(--hand-muted); border-radius: 0; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__slice { stroke: var(--hand-bg); stroke-width: 1.4; fill: #2c2f36; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__slice.is-on { fill: var(--hand-ink); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track--clock.is-full .track__slice.is-on { fill: var(--hand-danger); }
:is(.trackline, .stand__tracks) .hand.tracks--blades .track__clock { width: 2rem; height: 2rem; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__step { border-color: var(--hand-muted); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__step.is-on { background: #5fb8b0; border-color: #5fb8b0; }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track--scale.is-negative .track__step.is-on { background: var(--hand-danger); border-color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__condition::before { border-color: var(--hand-muted); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__condition.is-on { color: var(--hand-danger); }
:is(.trackline, .partyline, .stand__tracks) .hand.tracks--blades .track__condition.is-on::before { background: var(--hand-danger); border-color: var(--hand-danger); }

@media (prefers-reduced-motion: no-preference) {
  .trackline .tracks--eiserne-zeit .track__wax::before { animation: kerze 1.8s ease-in-out infinite alternate; }
  @keyframes kerze { from { transform: translateX(-50%) scale(1); } to { transform: translateX(-50%) scale(0.85, 1.1); } }
}

/* ── Der Stand der Gruppe ─────────────────────────────────────────────────
   Ein Reiter je Figur, davor einer für die Gruppe; das Blatt in der
   Handschrift des Systems. Die Schrift ist fest in `rem` — Werte sind
   Nachschlagwerk, kein Lesetext, und mit dem Schieber wuchsen sie ins
   Riesige. Absichtlich kein `--reading-size`.

   Die Spuren stehen in `.stand__tracks` und bekommen dieselben Karten und
   Marken wie die Spurenleiste (`:is(.trackline, .stand__tracks)`), nur nicht
   deren Rahmen: Den trägt der Dialog, und ein Kasten im Kasten ist einer zu
   viel. **Nicht `.trackline` selbst** — die Prüfungen und der Bogen greifen
   die erste `.trackline` der Seite, und der Dialog steht vor dem
   Schreibtisch. */
.statusdialog {
  --hand-panel: color-mix(in oklab, var(--hand-ink, var(--text)) 5%, transparent);
  --hand-line: var(--hand-border, 1px solid var(--border));
}
.statusdialog.hand--mothership::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(transparent 0 2px, rgb(0 0 0 / 0.25) 2px 3px); }
.statusdialog.hand:not(.hand--persona) .sheet__head { border-bottom: var(--hand-line); }
.statusdialog.hand:not(.hand--persona) .sheet__title { font-family: var(--hand-display); font-weight: 400; font-size: 1.15rem; }
.statusdialog.hand:not(.hand--persona) .sheet__head .button {
  background: transparent; color: var(--hand-muted); border: var(--hand-line); border-radius: var(--hand-radius); }
.statusdialog.hand:not(.hand--persona) .sheet__head .button:hover { color: var(--hand-ink); }

/* Die Reiter liegen außerhalb des rollenden Bereichs und rollen selbst
   seitwärts — auf dem Telefon passen drei Gesichter, nicht sechs. */
.stand__tabs { display: flex; gap: 0.3rem; padding: 0 0.8rem; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x proximity;
               scrollbar-width: thin; border-bottom: var(--hand-line); flex: none; }
/* `background: none` und `:hover` wegen `button:hover` für jeden Knopf, wie
   bei `.sheet__tab`; `display: grid` braucht die Gegenregel für `[hidden]`. */
.stand__tab { position: relative; flex: none; scroll-snap-align: start; display: grid;
              grid-template-columns: 2.2rem auto; grid-template-rows: auto auto; column-gap: 0.5rem; row-gap: 0.25rem;
              align-items: center; padding: 0.45rem 0.7rem 0.5rem 0.4rem; margin-bottom: -1px;
              font-family: inherit; font-size: 0.95rem; text-align: left; color: var(--hand-muted, var(--text-muted));
              background: none; border: 0; border-bottom: 3px solid transparent; border-radius: 0; }
.stand__tab[hidden] { display: none; }
.stand__tab:hover { background: none; color: var(--hand-ink, var(--text)); }
.stand__tab[aria-selected="true"] { color: var(--hand-ink, var(--text)); border-bottom-color: var(--hand-mark, var(--accent)); }
.stand__tab--group { grid-template-columns: auto; padding-left: 0.7rem; }
.stand__tab-name { font-family: var(--hand-display, var(--font)); white-space: nowrap; line-height: 1.1; }
.stand__tab-face.portrait { grid-row: 1 / 3; width: 2.2rem; height: 2.2rem; aspect-ratio: auto; border-width: 1px; box-shadow: none; }
/* Das Bild passt in das Quadrat, beschnitten wird es nicht. */
.stand__tab-face.portrait img { width: auto; height: auto; max-width: 100%; max-height: 100%; }
.stand__tab-face .portrait__initial { font-size: 0.95rem; }
/* Der Rahmen ohne Bild in den Farben des Blatts, nicht in denen der Seite. */
.statusdialog.hand:not(.hand--persona) .portrait { background: var(--hand-panel); border-color: color-mix(in oklab, var(--hand-ink) 35%, transparent); box-shadow: none; }
.statusdialog.hand:not(.hand--persona) .portrait__initial { color: var(--hand-muted); opacity: 1; }
.stand__meter { display: block; height: 0.22rem; min-width: 3rem; overflow: hidden;
                background: color-mix(in oklab, var(--hand-ink, var(--text)) 15%, transparent); }
.stand__meter[hidden] { display: none; }
.stand__meter > span { display: block; height: 100%; width: 0; background: var(--hand-mark, var(--accent)); transition: width 0.4s ease; }
.stand__meter.is-low > span { background: var(--hand-danger, var(--danger)); }
.stand__dot { position: absolute; top: 0.35rem; right: 0.25rem; width: 0.5rem; height: 0.5rem; border-radius: 50%;
              background: var(--hand-danger, var(--danger)); box-shadow: 0 0 0 2px var(--hand-bg, var(--bg-raised)); }
.stand__dot[hidden] { display: none; }

.stand__label { margin: 0 0 0.45rem; font-family: var(--hand-display, var(--font-ui)); font-size: var(--hand-label-size, 0.7rem);
                font-weight: 400; color: var(--hand-label, var(--text-muted)); text-transform: var(--hand-label-case, uppercase);
                letter-spacing: 0.1em; }

/* Das Blatt: Porträt links über zwei Zeilen, daneben Name und Spuren, der
   Zustand darunter über die ganze Breite. */
.stand__sheet { display: grid; grid-template-columns: 9rem minmax(0, 1fr); grid-template-rows: auto 1fr; gap: 0.8rem 1.2rem; align-items: start; }
.stand__face.portrait { grid-row: 1 / 3; width: 100%; }
.stand__face .portrait__initial { font-family: var(--hand-display, var(--font)); font-size: 2.6rem; color: var(--hand-muted, var(--accent)); }
.stand__name { margin: 0; font-family: var(--hand-display, var(--font)); font-size: 1.8rem; font-weight: 400; line-height: 1.05;
               text-wrap: balance; overflow-wrap: anywhere; }
.stand__tracks { min-width: 0; }
.stand__state-box { grid-column: 1 / -1; }
.stand__state { padding: 0.7rem 0.9rem; font-family: var(--hand-font, var(--font)); font-size: 0.9rem; line-height: 1.55; background: var(--hand-panel); border: var(--hand-line);
                border-radius: var(--hand-radius, var(--radius)); }
/* `markdown` legt ein `.prose` um den Text, und `.prose` ist Lesetext:
   Serife, `--reading-size`. Hier nicht — der Stand ist Nachschlagwerk. */
.stand__state .prose { font: inherit; font-size-adjust: none; line-height: inherit; }
.stand__state :is(p, ul, ol) { margin: 0.25rem 0; max-width: 65ch; }
.stand__state > :first-child { margin-top: 0; }
.stand__state > :last-child { margin-bottom: 0; }

.stand__tracks .tracks { flex-direction: row; flex-wrap: wrap; gap: 0.5rem; font-size: 0.8rem; }
.stand__tracks .track { padding: 0.45rem 0.55rem; background: var(--hand-panel); border: var(--hand-line);
                                  border-radius: var(--hand-radius, var(--radius)); }
.stand__tracks .track__value { font-size: 1.2rem; }

/* Der Reiter „Gruppe": Kasse und Vorräte, darunter je Figur eine Zeile.
   Mit Gegenregel: `display: grid` schlägt `[hidden]`, und das Fach stand
   sonst unter jeder Figur mit. */
.stand__group { display: grid; gap: 1.1rem; }
.stand__group[hidden] { display: none; }
.stand__rows { display: grid; gap: 0.35rem; }
.stand__row { display: grid; grid-template-columns: 2.2rem minmax(6rem, 12rem) minmax(0, 1fr) auto; gap: 0.7rem; align-items: center;
              padding: 0.35rem 0.55rem; font-family: inherit; font-size: 0.95rem; text-align: left;
              color: var(--hand-ink, var(--text)); background: var(--hand-panel); border: var(--hand-line); border-radius: var(--hand-radius, var(--radius)); }
.stand__row:hover { background: color-mix(in oklab, var(--hand-ink, var(--text)) 10%, transparent); color: var(--hand-ink, var(--text)); }
.stand__row .stand__tab-face.portrait { grid-row: auto; }
.stand__row-name { font-family: var(--hand-display, var(--font)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stand__row .stand__meter { height: 0.55rem; }
.stand__value { font-family: var(--hand-display, var(--font)); font-variant-numeric: tabular-nums; white-space: nowrap; }
.stand__value.is-low { color: var(--hand-danger, var(--danger)); }

/* Telefon: Der Dialog steht ohnehin bildschirmfüllend (Kästen oben). Das
   Porträt schrumpft und steht neben dem Namen, die Spuren darunter. */
@media (max-width: 40rem) {
  .stand__tabs { padding: 0 0.5rem; }
  .stand__tab { grid-template-columns: 2rem auto; padding-right: 0.55rem; }
  .stand__tab-face.portrait { width: 2rem; height: 2rem; }
  .stand__sheet { grid-template-columns: 5rem minmax(0, 1fr); grid-template-rows: auto; gap: 0.8rem; }
  .stand__face.portrait { grid-row: auto; }
  .stand__face .portrait__initial { font-size: 1.6rem; }
  .stand__name { align-self: center; font-size: 1.4rem; }
  .stand__tracks { grid-column: 1 / -1; }
  .stand__row { grid-template-columns: 2.2rem minmax(0, 1fr) auto; row-gap: 0.3rem; }
  .stand__row .stand__meter { grid-column: 2 / 4; grid-row: 2; }
}

/* Die Handschrift im Dialog: je System ein Merkmal über die Farben hinaus. */
.statusdialog.hand--shadowdark :is(.sheet__title, .stand__name) { text-transform: uppercase; letter-spacing: 0.03em; }
.statusdialog.hand--shadowdark .stand__tab[aria-selected="true"] { background: var(--hand-ink); color: var(--hand-bg); border-bottom-color: var(--hand-ink); }
.statusdialog.hand--shadowdark .stand__tab[aria-selected="true"] .stand__meter > span { background: var(--hand-bg); }
.statusdialog.hand--shadowdark .stand__face.portrait { background: repeating-linear-gradient(135deg, #1a1a1a 0 6px, #0d0d0d 6px 12px); border-color: #f2efe8; }

.statusdialog.hand--mork-borg .stand__name { font-size: 2.6rem; transform: rotate(-2deg); transform-origin: left; }
.statusdialog.hand--mork-borg .stand__tab { transform: skewX(-8deg); }
.statusdialog.hand--mork-borg .stand__tab[aria-selected="true"] { background: #0a0a0a; color: #ffe900; }
.statusdialog.hand--mork-borg .stand__tab[aria-selected="true"] .stand__meter > span { background: #ffe900; }
.statusdialog.hand--mork-borg .stand__state { background: #0a0a0a; color: #ffe900; transform: rotate(0.6deg); }

.statusdialog.hand--pirate-borg .stand__name { font-size: 2.5rem; }
.statusdialog.hand--pirate-borg .stand__tab[aria-selected="true"] { background: #ffe600; color: #111; }
.statusdialog.hand--pirate-borg .stand__tab[aria-selected="true"] .stand__meter > span { background: #111; }

.statusdialog.hand--mothership { font-size: 1.1rem; text-shadow: 0 0 6px rgb(77 255 136 / 0.35); }
.statusdialog.hand--mothership :is(.sheet__title, .stand__name) { text-transform: uppercase; }
.statusdialog.hand--mothership .sheet__title::after { content: "▌"; margin-left: 0.2rem; }
.statusdialog.hand--mothership .stand__name { font-size: 2.3rem; }
.statusdialog.hand--mothership .stand__tab-name::before { content: "[ "; }
.statusdialog.hand--mothership .stand__tab-name::after { content: " ]"; }
.statusdialog.hand--mothership .stand__tab[aria-selected="true"] { background: #4dff88; color: #07120c; text-shadow: none; }
.statusdialog.hand--mothership .stand__tab[aria-selected="true"] .stand__meter > span { background: #07120c; }
.statusdialog.hand--mothership .stand__state { border-style: dashed; }
@media (prefers-reduced-motion: no-preference) {
  .statusdialog.hand--mothership .sheet__title::after { animation: stand-cursor 1.1s steps(1) infinite; }
  @keyframes stand-cursor { 50% { opacity: 0; } }
}

/* Eiserne Zeit: Reiter wie Karteikarten, der Zustand kursiv mit Initiale. */
.statusdialog.hand--eiserne-zeit .stand__tab { border-bottom: 0; border-radius: 8px 8px 0 0; margin-bottom: 0; }
.statusdialog.hand--eiserne-zeit .stand__tab[aria-selected="true"] { background: rgb(255 250 235 / 0.55);
  box-shadow: 0 -1px 0 #9c7f55, 1px 0 0 #9c7f55, -1px 0 0 #9c7f55; }
.statusdialog.hand--eiserne-zeit .stand__state { font-style: italic; font-size: 1rem; }
.statusdialog.hand--eiserne-zeit .stand__state > p:first-child::first-letter {
  font-family: "IM Fell English SC", var(--font); font-style: normal; font-size: 2.3em; float: left; line-height: 0.85;
  margin: 0.1rem 0.35rem 0 0; color: var(--hand-danger); }

/* Dragonbane: Kastentitel weiß auf Petrol, wie im Buch. */
.statusdialog.hand--dragonbane :is(.sheet__title, .stand__tab-name, .stand__row-name) { font-weight: 700; }
.statusdialog.hand--dragonbane .stand__name { font-family: "Cinzel", var(--font); font-weight: 700; color: #1f6b5c; }
.statusdialog.hand--dragonbane .stand__label { display: inline-block; padding: 0.2rem 0.55rem; background: #1f6b5c; color: #eee5cf;
                                               border-radius: 2px; font-weight: 700; letter-spacing: 0.14em; }

/* Welt der Dunkelheit: ein Blatt aus der Akte, mit Stempel. */
.statusdialog.hand--wod .stand__sheet { position: relative; }
.statusdialog.hand--wod .stand__sheet::after { content: "AKTE"; position: absolute; top: -0.3rem; right: 0; padding: 0.1rem 0.5rem;
  font-family: "Special Elite", var(--font-mono); font-size: 1.1rem; letter-spacing: 0.2em; color: #b3121b;
  border: 2px solid #b3121b; transform: rotate(8deg); opacity: 0.75; pointer-events: none; }
.statusdialog.hand--wod .stand__tab[aria-selected="true"] { background: #fbf9f4; }

.statusdialog.hand--nimble :is(.sheet__title, .stand__tab-name, .stand__row-name, .stand__label) { font-weight: 700; }
.statusdialog.hand--nimble .stand__name { font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.statusdialog.hand--nimble .stand__tab[aria-selected="true"] { background: #e4d9c0; }

.statusdialog.hand--persona .stand__tab[aria-selected="true"] { background: color-mix(in oklab, var(--accent) 8%, transparent); }

/* ── Die Fädenseite ────────────────────────────────────────────────────── */
.threads__group { margin-bottom: 1.6rem; }
.threads__heading { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
.threads__card--dormant { opacity: 0.8; }
.threads__card--closed { opacity: 0.65; }

/* Die Spuren der Gruppe auf der Staff-Seite, je mit dem Knopf, der sie
   verdeckt oder einblendet. `flex-wrap`, sonst zieht die Zeile das Telefon quer. */
.staff-tracks { list-style: none; padding: 0; margin: 0 0 1.5rem; display: grid; gap: 0.35rem; }
.staff-tracks__row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.3rem 0.8rem; padding: 0.35rem 0.6rem;
                     border: 1px solid var(--border); border-radius: var(--radius); }
.staff-tracks__row.is-hidden { border-style: dashed; }
.staff-tracks__name { flex: 1 1 12rem; min-width: 0; }
.staff-tracks__state { color: var(--text-muted); font-size: 0.8rem; }
.staff-tracks__toggle { margin: 0; }

