/* ============================================================================
   Gestaltungsgrundlage
   ============================================================================

   Leitbild: FELDFORSCHUNG. Die App ist ein Werkzeug, kein Schaufenster — das
   Tier und das eigene Foto sind der Held, die Oberfläche tritt zurück.
   MVP-SPEC Kap. 7 gibt die Richtung vor: Kamera als Oberfläche, wenige
   Container, klare Typografie, KEINE runden Karten, KEINE dekorativen Pillen.

   Drei Regeln, die das Ergebnis tragen:

   1. KANTEN STATT PILLEN. Radien sind sparsam und klein (2–4 px). Eine Pille
      (999px) ist im ganzen Projekt nur noch dort erlaubt, wo etwas wirklich
      rund IST — ein Statuspunkt, ein Auslöser. Alles andere bekommt Kanten:
      das wirkt wie ein Messgerät und nicht wie ein Baukasten.

   2. TYPOGRAFIE TRÄGT, NICHT DER RAHMEN. Bedeutung entsteht durch Größe,
      Gewicht und Sperrung — nicht durch eine Box drumherum. Deshalb die
      abgestufte Skala unten statt beliebiger rem-Werte.

   3. EINE AKZENTFARBE PRO AUSSAGE. Limette = Fortschritt und Aktion, Gold =
      Ertrag, Rot = Ablehnung. Seltenheitsfarben stehen ausschließlich für
      Seltenheit. Nichts wird eingefärbt, weil es hübsch aussieht.
   ============================================================================ */

:root {
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: #f4f5ef;
  background: #0a0d0b;

  /* Grundfarben. Papier und Tinte, nicht Grau auf Grau.
     WERTE AUS docs/entwurf/kartensheet.html — die Karte ist die Leitgestalt,
     die App richtet sich nach ihr und nicht umgekehrt.

     DIE FLAECHE IST HELLER ALS DER GRUND, nicht dunkler. Bis 05.08.2026 war es
     andersherum (--paper #f2f1e8 als Grund, --paper-2 #e7e6dc als Kachel), und
     genau das liess jede Kachel wie ein LOCH im Papier aussehen statt wie ein
     Blatt, das darauf liegt. Es ist der wirksamste Einzelunterschied zwischen
     der App und dem Kartensheet — und er kostet zwei Zeilen, weil die
     Beziehung an allen 66 Fundstellen dieselbe ist.

     Die Namen bleiben: --paper traegt auch die Schriftfarbe auf dunklem Grund
     (.nav, Knoepfe), und #ece9dd ist dort weiterhin hell genug. */
  --ink: #101411;
  --ink-2: #3d443e;
  /* #6b716a bis 06.08.2026 — und damit 4,12:1 auf Papier und 3,90:1 auf
     Nacht, also auf BEIDEN unter der Grenze. Er ist der meistgenannte
     Einzelbefund des Befundlaufs (33 Nennungen). Der neue Wert kommt aus dem
     Systemblatt und liegt dort bei 5,37:1. */
  --ink-3: #838d82;
  --paper: #ece9dd;      /* Grund — der DUNKLERE der beiden */
  --paper-2: #f7f6ee;    /* Flaeche darauf — der HELLERE */
  --night: #0a0d0b;
  --night-2: #141914;

  /* Akzente. Jede hat genau eine Aufgabe. */
  /* AUS system.css. Hier steht nur noch der Verweis — die Werte und ihre
     Begruendung stehen im Systemblatt, und dort rechnet jedes Paar seinen
     Kontrast selbst nach. Zwei Wahrheitsorte fuer eine Farbe sind genau der
     Weg, auf dem hier fuenf Fast-Duplikate von --ink-3 entstanden sind. */
  --warning: var(--amber);
  --danger: var(--rost);

  --line-light: rgba(255, 255, 255, .18);
  --line-dark: rgba(16, 20, 17, .18);
  --line-hair: rgba(16, 20, 17, .1);

  /* Radien: ZWEI, nicht drei. Werte aus dem Kartensheet.

     `--r-sharp: 0` hiess der erste bis 05.08.2026 und war buchstaeblich
     scharfkantig. Neben der Karte — die 3,3 % Radius traegt — sah jede Kachel
     dadurch wie ein Formularfeld aus. 2 px reichen; rund wird davon nichts,
     aber die Kante hoert auf zu schneiden.

     Umbenannt statt umgewertet: ein Name, der `0` verspricht und 2 px liefert,
     ist genau die stille Falschangabe, die dieses Projekt an anderer Stelle
     schon Tage gekostet hat. */
  /* --r-kante, --r-soft, --r-card: aus system.css */

  /* Abstände in einem 4er-Raster — beliebige Zwischenwerte erzeugen genau die
     leichte Unordnung, die ein Layout billig wirken lässt. */
  /* --s1 .. --s8: aus system.css */

  /* Typografische Skala. */
  /* --t-micro .. --t-display: aus system.css */

  /* Sperrung für Versalien-Etiketten — sie tragen einen großen Teil der
     Anmutung und müssen überall gleich sein. */
  /* --track-label: aus system.css */

  --nav-height: 68px;
}

/* ============================================================================
   DAS NACHT-REGISTER (06.08.2026)

   „Eine Karte leuchtet nur auf dunklem Grund" — docs/entwurf/PLAN-OBERFLAECHE
   Abschnitt 3. Seit Sammlung, Feed und Arena Sammelkarten zeigen statt
   Listenzeilen, standen sie auf hellem Papier und wirkten dort als dunkle
   Bloecke.

   NUR DIE VARIABLEN WERDEN GETAUSCHT, keine Regel je Bauteil. Alle Kacheln,
   Linien und Texte laufen ohnehin ueber `--paper`/`--ink`; eine Ausnahmeliste
   waere dieselbe Regel an 66 Orten. Wer eine neue Kachel baut und die
   Variablen benutzt, bekommt Nacht geschenkt — wer eine Farbe hart
   hineinschreibt, faellt hier auf.

   `--night` bleibt unveraendert: es ist die Farbe der Navigationsleiste und
   der Buehnen, und die sind in BEIDEN Registern dunkel. Ein Register, das
   auch sie umdefiniert, haette keine Leiste mehr.
   ============================================================================ */
/* DIE WERTE STEHEN NICHT MEHR HIER, SONDERN IM SYSTEMBLATT.
   Dieser Block bildet nur noch die ALTEN Namen auf die neuen ab. Der Grund
   ist eine Namenskollision, die still gewinnt: `--ink` heisst in dieser Datei
   seit jeher „dunkle Schrift auf hellem Papier", im Systemblatt waere es das
   Gegenteil. Zwei Dateien duerfen denselben Namen nicht gegensaetzlich
   belegen — die spaeter geladene setzt sich durch, und niemand sieht, welche
   das gerade ist. Deshalb heissen sie dort `--text*`, und die Bruecke steht
   an genau EINER Stelle. */
body[data-register="nacht"] {
  --paper: var(--grund);
  --paper-2: var(--flaeche);
  --ink: var(--text);
  --ink-2: var(--text-2);
  --ink-3: var(--text-3);
  --line-dark: var(--kante-satt);
  --line-hair: var(--kante-haar);
}
/* Der Wechsel ist ein Vorhang, kein Sprung. 240 ms, wie im Plan; bei
   `prefers-reduced-motion` gar keiner. */
body { transition: background-color .24s ease, color .24s ease; }
@media (prefers-reduced-motion: reduce) { body { transition: none; } }

/* Zahlen in Tabellenbreite, überall wo gezählt oder verglichen wird. Ohne das
   springt die Breite beim Hochzählen und wirkt unruhig. */
.num, .stat-strip strong, .profile-stats strong, .large-count { font-variant-numeric: tabular-nums; }

/* Wiederkehrendes Versalien-Etikett. Vorher an einem Dutzend Stellen einzeln
   definiert, mit leicht abweichenden Werten. */
.label-micro {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  font-weight: 850; color: var(--ink-3); text-transform: uppercase;
}

/* EIN GESCHLOSSENER DIALOG IST UNSICHTBAR. Punkt.
   ===============================================================
   Der Browser bringt dafuer `dialog:not([open]) { display: none }` mit — und
   diese Regel ist erschreckend leicht auszuhebeln: es genuegt, irgendwo
   `display: flex` auf einer Dialogklasse zu setzen (0,1,0 schlaegt die
   UA-Regel). Der Dialog rendert danach als ganz normales Element MITTEN IM
   DOKUMENT, obwohl `dialog.open` false ist.

   Genau das ist hier zweimal passiert (`.merge-sheet`, `.battle-stage`): das
   geschlossene Werkstatt-Sheet blieb ueber jeder anderen Ansicht stehen und
   verdeckte sie. Im DOM war alles richtig — `open` false, kein `:modal` —,
   nur gezeichnet wurde es trotzdem. Ein Fehler, den man in der Konsole nicht
   findet, weil dort nichts falsch ist.

   `dialog:not([open])` hat Spezifitaet (0,1,1) und schlaegt damit jede
   Klassenregel (0,1,0). Sie steht bewusst hier oben und nicht unten: sie ist
   eine Zusicherung, kein Nachtrag. */
dialog:not([open]) { display: none; }

* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0a0d0b; }
body { overflow-x: hidden; }
button, input { font: inherit; }
button { -webkit-tap-highlight-color: transparent; }
svg { fill: currentColor; }
[hidden] { display: none !important; }

.app-shell { min-height: 100dvh; position: relative; background: #0a0d0b; }
.view { display: none; min-height: 100dvh; padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom)); }
.view.active { display: block; }
.view-scan { padding-bottom: 0; background: #050706; }

.camera-stage { position: relative; width: 100%; height: 100dvh; min-height: min(620px, 100dvh); overflow: hidden; background: #101512; touch-action: none; }
.camera-stage video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.camera-stage video { opacity: 0; transition: opacity .25s ease; }
.camera-stage.video-live.camera-preview-ready video { opacity: 1; }
.camera-stage.native-camera { background: transparent; }
.camera-stage.native-camera video { display: none; }
/* DURCHSICHTIG NUR, SOLANGE DIE SCAN-ANSICHT SICHTBAR IST.
   Die native Kamera rendert HINTER dem WebView; damit man sie sieht, macht
   sich die App durchsichtig. `native-camera-active` haengt aber am laufenden
   Stream, und der laeuft nach einem Ansichtswechsel bewusst rund 30 s weiter
   (schnelle Rueckkehr ohne Sensorneustart).

   Folge im Feld, gemeldet am 06.08.2026: von der Sammlung aus sah man die
   Kamera durch die ganze App hindurch, bis der Stopp-Zeitgeber ablief. Es sah
   aus, als haenge ein Bild fest.

   Die zweite Klasse trennt die beiden Fragen sauber: `native-camera-active`
   heisst „der Sensor laeuft", `scan-sichtbar` heisst „man schaut darauf". Nur
   beides zusammen macht durchsichtig. */
html.native-camera-active.scan-sichtbar,
html.native-camera-active.scan-sichtbar body,
html.native-camera-active.scan-sichtbar .app-shell,
html.native-camera-active.scan-sichtbar .view-scan { background: transparent; }
.camera-start-panel {
  position: absolute; z-index: 7; left: 50%; top: 50%; width: min(340px, calc(100% - 48px));
  transform: translate(-50%, -50%); display: grid; justify-items: center; gap: 10px;
  padding: 22px 20px; border: 1px solid rgba(255,255,255,.2);
  background: rgba(12,18,14,.88); color: #fff; text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,.34); backdrop-filter: blur(14px);
}
.camera-start-panel svg { width: 34px; fill: currentColor; color: var(--lime); }
.camera-start-panel strong { font-size: 1.05rem; }
.camera-start-panel span { color: rgba(255,255,255,.7); font-size: var(--t-small); line-height: 1.45; }
.camera-start-panel button {
  min-height: 46px; margin-top: 3px; padding: 0 18px; border: 0;
  background: var(--ink); color: var(--paper); font: inherit; font-size: var(--t-small);
  box-shadow: inset 0 -2px 0 var(--lime);
  font-weight: 900; cursor: pointer;
}
.camera-start-panel button:disabled { opacity: .55; cursor: wait; }
.camera-stage.camera-preview-ready .camera-start-panel { display: none; }
/* Abdunklung NUR dort, wo Text steht — nicht ueber dem Tier.
 *
 * Vorher lag hier ein Verlauf mit 58 % Schwarz oben, 72 % unten UND eine
 * Vignette, die ab 38 % Radius bis auf 14 % zulief. Gemessen an 528
 * Feldaufnahmen sind die gespeicherten Bilder voellig normal belichtet
 * (Median-Helligkeit 108,5 von 255, nur 5 % unter 60) — die Beschwerde
 * "Kamera viel zu dunkel" betraf also die VORSCHAU, und die haben wir selbst
 * verdunkelt.
 *
 * Die Vignette ist ersatzlos weg: sie traf genau die Bildmitte, wo das Tier
 * steht und wo der Nutzer beurteilen muss, ob er ueberhaupt scharf ist.
 * Oben und unten bleibt ein Verlauf, aber kuerzer und schwaecher — er muss
 * Kopf- und Fussleiste lesbar halten, mehr nicht.
 *
 * Wer daran dreht: die Grenze ist die Lesbarkeit weisser Schrift ueber
 * hellem Himmel. Weniger als etwa 35 % traegt das nicht mehr.
 */
.camera-shade {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(0,0,0,.42) 0%,
      rgba(0,0,0,.10) 14%,
      rgba(0,0,0,0) 30%,
      rgba(0,0,0,0) 68%,
      rgba(0,0,0,.14) 84%,
      rgba(0,0,0,.50) 100%);
}
.camera-stage::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .13;
  background-image: linear-gradient(rgba(255,255,255,.12) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.12) 1px, transparent 1px);
  background-size: 33.333% 33.333%;
}

.camera-header { position: absolute; z-index: 8; top: 0; left: 0; right: 0; padding: calc(18px + env(safe-area-inset-top)) 18px 14px; display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; }
.scan-top-actions {
  position: absolute; z-index: 12; top: calc(58px + env(safe-area-inset-top));
  left: 16px; right: 16px; display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; pointer-events: none;
}
.scan-top-actions > * { pointer-events: auto; }
.scan-cancel {
  position: static; min-height: 46px; display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 13px; border: 1px solid rgba(255,255,255,.28); border-radius: 15px 5px 15px 5px;
  background: linear-gradient(145deg,rgba(14,21,17,.82),rgba(7,11,9,.65)); color: #fff;
  backdrop-filter: blur(16px) saturate(1.2); -webkit-backdrop-filter: blur(16px) saturate(1.2);
  box-shadow: 0 10px 30px rgba(0,0,0,.24), inset 0 1px rgba(255,255,255,.1);
  font-family: inherit; font-size: var(--t-small); font-weight: 850; cursor: pointer;
  touch-action: manipulation; text-shadow: 0 1px 8px #000;
}
.scan-cancel span { font-size: 1.15rem; line-height: .8; font-weight: 500; }
.scan-queue-button {
  position: static; width: 62px; min-height: 62px; margin-left: auto; display: grid;
  justify-items: center; align-content: start; gap: 5px; padding: 0; border: 0;
  background: transparent; color: #fff;
  font-family: inherit; cursor: pointer; touch-action: manipulation;
  text-shadow: 0 1px 8px #000;
}
.scan-queue-icon {
  position: relative; width: 46px; height: 46px; display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,.3); border-radius: 15px 5px 15px 5px;
  background: linear-gradient(145deg,rgba(14,21,17,.82),rgba(7,11,9,.65));
  backdrop-filter: blur(16px) saturate(1.2); -webkit-backdrop-filter: blur(16px) saturate(1.2);
  box-shadow: 0 10px 30px rgba(0,0,0,.24), inset 0 1px rgba(255,255,255,.1);
}
.scan-queue-icon svg {
  width: 21px; height: 21px; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.scan-queue-icon strong {
  position: absolute; top: -6px; right: -7px; min-width: 20px; height: 20px;
  display: grid; place-items: center; padding: 0 5px; border: 2px solid #101411;
  background: var(--lime); color: var(--ink); border-radius: 10px;
  font-size: .64rem; font-weight: 950; line-height: 1; text-shadow: none;
}
.scan-queue-icon strong[hidden] { display: none; }
.scan-queue-label {
  font-size: .56rem; line-height: 1; font-weight: 900; letter-spacing: .12em;
  text-transform: uppercase;
}
.scan-queue-button.has-result { color: var(--lime); }
.scan-queue-button.has-result .scan-queue-icon { border-color: var(--lime); background: rgba(14,20,14,.78); }
.scan-queue-button.has-pending:not(.has-result) .scan-queue-icon::after {
  content: ""; position: absolute; right: 5px; bottom: 5px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--warning); box-shadow: 0 0 9px rgba(255,208,106,.75);
}
.scan-queue-button:focus-visible .scan-queue-icon { outline: 2px solid var(--lime); outline-offset: 3px; }
.app-shell.locking-scan .scan-queue-button,
.app-shell.scanning .scan-queue-button { visibility: hidden; pointer-events: none; }
.scan-catch-ready {
  position:absolute; z-index:18; left:50%; bottom:calc(88px + env(safe-area-inset-bottom));
  width:min(calc(100% - 72px),370px); min-height:58px; padding:4px;
  display:grid; grid-template-columns:minmax(0,1fr) 38px; align-items:center; gap:2px;
  border:1px solid color-mix(in srgb,var(--catch-ready-accent) 60%,white);
  border-radius:18px 7px 18px 7px; color:#f4f8ef;
  background:linear-gradient(105deg,rgba(7,16,12,.94),rgba(20,35,25,.9));
  box-shadow:0 15px 38px rgba(0,0,0,.34),0 0 24px color-mix(in srgb,var(--catch-ready-accent) 28%,transparent);
  transform:translateX(calc(-50% + var(--scan-catch-swipe-x,0px)));
  animation:scan-catch-arrive .42s cubic-bezier(.18,.85,.25,1); touch-action:pan-y;
}
.scan-catch-ready-open{min-width:0;display:grid;grid-template-columns:46px minmax(0,1fr) auto;align-items:center;gap:9px;padding:2px 7px 2px 2px;border:0;background:transparent;color:inherit;text-align:inherit;cursor:pointer}
.scan-catch-ready[hidden]{display:none}.scan-catch-ready-image{width:46px;height:46px;overflow:hidden;border-radius:14px 5px 14px 5px;background:rgba(255,255,255,.08)}
.scan-catch-ready-image img,.scan-catch-ready-image svg{width:100%;height:100%;object-fit:cover}.scan-catch-ready-image svg{padding:11px;color:var(--catch-ready-accent)}
.scan-catch-ready-copy{min-width:0;display:grid;text-align:left}.scan-catch-ready-copy small{color:var(--catch-ready-accent);font-size:.6rem;font-weight:950;letter-spacing:.13em}.scan-catch-ready-copy strong{overflow:hidden;margin-top:2px;font-size:.86rem;text-overflow:ellipsis;white-space:nowrap}
.scan-catch-ready-action{padding-left:9px;border-left:1px solid rgba(255,255,255,.15);color:#fff;font-size:.65rem;font-weight:900}
.scan-catch-ready-dismiss{width:36px;height:44px;display:grid;place-items:center;padding:0;border:0;border-left:1px solid rgba(255,255,255,.13);border-radius:10px 4px 10px 4px;background:rgba(7,16,12,.64);color:rgba(255,255,255,.9);backdrop-filter:blur(9px);cursor:pointer}
.scan-catch-ready-dismiss svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.scan-catch-ready-open:focus-visible,.scan-catch-ready-dismiss:focus-visible{outline:2px solid var(--catch-ready-accent);outline-offset:-2px}
.scan-catch-ready.is-dragging{animation:none;transition:none;cursor:grabbing}
.scan-catch-ready.is-dismissing{pointer-events:none}
.scan-catch-ready.is-outcome{background:linear-gradient(105deg,rgba(20,17,12,.95),rgba(39,29,21,.91))}
.scan-catch-ready.is-outcome .scan-catch-ready-image{filter:saturate(.65)}
.app-shell.locking-scan .scan-catch-ready,.app-shell.scanning .scan-catch-ready{visibility:hidden;pointer-events:none}
/* Ein nachgeliefertes Ergebnis ist die aktuelle Handlungsaufforderung. Es
   sitzt oberhalb des Werkzeugdocks; die normale Scan-Anweisung tritt fuer
   diesen Moment zurueck, statt mit Karte und Werkzeugen zu kollidieren. */
.camera-stage:has(.scan-catch-ready:not([hidden])) .scan-instruction{opacity:0}
.camera-stage:has(.scan-catch-ready:not([hidden])) .scan-catch-ready{
  bottom:calc(var(--nav-height) + 86px + env(safe-area-inset-bottom));
}
.app-shell.scan-active .camera-stage:has(.scan-catch-ready:not([hidden])) .scan-catch-ready{
  bottom:calc(82px + env(safe-area-inset-bottom));
}
@keyframes scan-catch-arrive{from{opacity:0;transform:translate(calc(-50% + var(--scan-catch-swipe-x,0px)),18px) scale(.96)}to{opacity:1;transform:translate(calc(-50% + var(--scan-catch-swipe-x,0px)),0) scale(1)}}
@media (prefers-reduced-motion:reduce){.scan-catch-ready{animation:none}}
.brand-mark { display: block; font-size: .68rem; letter-spacing: .22em; font-weight: 900; color: var(--lime); margin-bottom: 6px; }
/* Die abgedunkelte Limette ist FUER HELLEN GRUND gemacht — auf Papier trägt
   die volle Farbe zu wenig Kontrast. Im Nacht-Register ist es andersherum:
   dort kommt #4f6335 auf 2,95:1 und ist die einzige Stelle der ganzen
   Ansicht, die unter 3:1 fällt (gemessen 06.08.2026). */
.brand-mark.dark { color: #4f6335; }
body[data-register="nacht"] .brand-mark.dark { color: var(--lime); }
.text-control { padding: 0; border: 0; color: #fff; background: transparent; font-size: .88rem; font-weight: 700; text-shadow: 0 1px 12px rgba(0,0,0,.65); cursor: pointer; }
.header-signals { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
.signal { font-size: .64rem; letter-spacing: .12em; font-weight: 900; color: #fff; text-shadow: 0 1px 10px #000; }
.signal::before { content: ""; display: inline-block; width: 6px; height: 6px; margin-right: 6px; border-radius: 50%; background: var(--mint); box-shadow: 0 0 10px var(--mint); }
.signal.warning::before { background: var(--warning); box-shadow: 0 0 8px var(--warning); }

.camera-toggle {
  position: static; min-width: 52px; min-height: 52px; padding: 5px 7px; border: 0;
  border-radius: 13px 4px 13px 4px; background: transparent; color: white;
  display: grid; justify-items: center; align-content: center; gap: 4px; cursor: pointer;
  font-family: inherit; font-size: .58rem; font-weight: 800; line-height: 1; text-shadow: 0 1px 10px #000;
}
.camera-toggle svg { width: 22px; height: 22px; fill: currentColor; }
.app-shell.locking-scan .camera-toggle,
.app-shell.scanning .camera-toggle { visibility: hidden; pointer-events: none; }

.scan-instruction {
  position: absolute; z-index: 8; left: 50%; bottom: calc(var(--nav-height) + 98px + env(safe-area-inset-bottom));
  width: min(calc(100% - 40px), 430px); padding: 11px 16px 12px; border: 1px solid rgba(255,255,255,.18);
  border-radius: 18px 7px 18px 7px; color: white; text-align: center; pointer-events: none;
  background: linear-gradient(145deg,rgba(11,18,14,.68),rgba(8,12,10,.48));
  backdrop-filter: blur(13px) saturate(1.15); -webkit-backdrop-filter: blur(13px) saturate(1.15);
  box-shadow: 0 14px 36px rgba(0,0,0,.2), inset 0 1px rgba(255,255,255,.08);
  text-shadow: 0 2px 16px rgba(0,0,0,.78); transform: translateX(-50%); transition: opacity .16s ease;
}
/* Vor dem ersten echten Videobild ist die Kamerastartkarte die einzige
   Anweisung. Zwei gleichzeitig sichtbare Starttexte ueberlappten sich auf
   kleineren Handys und gaben widerspruechliche Handlungsaufforderungen. */
.camera-stage:not(.camera-preview-ready) .scan-instruction { opacity: 0; visibility: hidden; }
.scan-instruction span { display: block; }
.scan-instruction > span:first-child { color: var(--lime); font-size: .56rem; font-weight: 900; letter-spacing: .18em; margin-bottom: 5px; }
.scan-instruction strong { display: block; font-size: clamp(1.05rem, 4.6vw, 1.35rem); letter-spacing: -.025em; line-height: 1.08; }
#scanSubline { margin-top: 5px; color: rgba(255,255,255,.78); font-size: .68rem; line-height: 1.3; }

.camera-footer {
  position: absolute; z-index: 9; left: 50%; right: auto;
  bottom: calc(var(--nav-height) + 12px + env(safe-area-inset-bottom)); min-height: 64px;
  width: max-content; max-width: calc(100% - 28px); padding: 5px 7px; display: flex; align-items: center;
  justify-content: center; gap: 3px; border: 1px solid rgba(255,255,255,.22);
  border-radius: 20px 7px 20px 7px; background: linear-gradient(145deg,rgba(11,18,14,.82),rgba(7,11,9,.68));
  backdrop-filter: blur(18px) saturate(1.2); -webkit-backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: 0 16px 42px rgba(0,0,0,.3), inset 0 1px rgba(255,255,255,.1);
  transform: translateX(-50%);
}
.camera-footer #torchButton,.camera-footer #zoomQuickButton,.camera-footer #lensButton {
  position: static; grid-column: auto; grid-row: auto; justify-self: auto; transform: none;
}
/* Im flachen Querformat liegt die Anleitung sonst in derselben Zeile wie
   Kamerawechsel und Verlauf. Die Seiten bleiben dort fuer beide Werkzeuge
   frei; der Tierbereich in der Mitte bleibt weiterhin direkt drueckbar. */
@media (orientation: landscape) and (max-height: 500px) {
  .scan-top-actions { top: calc(50px + env(safe-area-inset-top)); }
  .scan-instruction { bottom: calc(76px + env(safe-area-inset-bottom)); width: min(360px,calc(100% - 200px)); padding-block: 8px; }
  .scan-instruction strong { font-size: 1rem; }
  .camera-footer { right: 12px; left: auto; bottom: calc(10px + env(safe-area-inset-bottom)); transform: none; }
}
.ghost-action { min-width: 52px; min-height: 52px; padding: 5px 7px; border: 0; border-radius: 13px 4px 13px 4px; background: transparent; color: white; display: grid; justify-items: center; align-content: center; gap: 4px; font-size: .58rem; font-weight: 800; cursor: pointer; text-shadow: 0 1px 10px #000; }
.ghost-action:first-child,.ghost-action:last-child { justify-self: auto; }
.ghost-action svg { width: 22px; height: 22px; }
.camera-footer :is(.camera-toggle,.ghost-action):hover { background: rgba(255,255,255,.08); }
.camera-footer :is(.camera-toggle,.ghost-action):focus-visible,.scan-cancel:focus-visible {
  outline: 2px solid var(--lime); outline-offset: 2px; background: rgba(201,255,66,.08);
}
/* Eingeschaltete Taschenlampe: Limette = aktive Funktion (Gestaltungsregel 3). */
.ghost-action.active { color: var(--lime); }
.ghost-action.active svg { fill: var(--lime); filter: drop-shadow(0 0 6px rgba(190, 242, 100, .45)); }

/* EMPFOHLEN, NICHT AKTIV — zwei verschiedene Aussagen, zwei Zustaende.
   Limette heisst in dieser App "laeuft gerade" (Regel 3); "waere jetzt
   sinnvoll" braucht deshalb eine eigene Farbe. Gold ist im Rest der
   Oberflaeche der Hinweis auf eine Gelegenheit.

   Der Puls laeuft langsam und ohne Groessenaenderung: der Knopf sitzt neben
   dem Sucher, und alles, was dort zappelt, zieht den Blick vom Tier weg —
   genau davon soll er ihn nicht abziehen. Wer die Bewegung abstellt, verliert
   nichts ausser der Auffaelligkeit (prefers-reduced-motion unten). */
.ghost-action.zoom-empfohlen { color: var(--gold); animation: zoomPuls 1.8s ease-in-out infinite; }
.ghost-action.zoom-empfohlen svg { fill: var(--gold); filter: drop-shadow(0 0 7px rgba(240, 192, 74, .5)); }
@keyframes zoomPuls { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) {
  .ghost-action.zoom-empfohlen { animation: none; }
}
/* Die GPS-Anzeige ist zugleich der Rueckweg zur Standortfreigabe. */
.signal { cursor: pointer; }

.target-glow { position: absolute; z-index: 7; width: 132px; height: 104px; transform: translate(-50%, -50%); pointer-events: none; filter: drop-shadow(0 0 16px rgba(201,255,66,.75)); }
.target-orbit, .target-core, .target-progress { position: absolute; inset: 0; }
.target-orbit { border: 1px solid rgba(225,255,146,.95); border-radius: 46% 54% 55% 45% / 48% 42% 58% 52%; animation: morph 2.3s ease-in-out infinite alternate, pulse 1.1s ease-in-out infinite; }
.target-orbit::before, .target-orbit::after { content: ""; position: absolute; inset: 8px; border: 1px dashed rgba(201,255,66,.58); border-radius: inherit; }
.target-orbit::after { inset: -11px; border-style: solid; opacity: .34; }
.target-core { border-radius: 46% 54% 55% 45% / 48% 42% 58% 52%; background: radial-gradient(circle, rgba(201,255,66,.06) 35%, rgba(201,255,66,.24) 100%); mix-blend-mode: screen; }
.target-progress { display: grid; place-items: center; font-size: .68rem; font-weight: 950; letter-spacing: .08em; color: white; }
@keyframes morph { to { border-radius: 58% 42% 44% 56% / 39% 60% 40% 61%; transform: rotate(2deg) scale(1.04); } }
@keyframes pulse { 50% { opacity: .62; filter: brightness(1.35); } }

.lock-telemetry { position: absolute; z-index: 8; top: 48%; left: 17px; display: grid; gap: 2px; color: white; text-shadow: 0 1px 12px #000; }
.lock-telemetry span { font-size: .58rem; letter-spacing: .16em; color: var(--lime); font-weight: 900; }
.lock-telemetry strong { font-size: 1.55rem; line-height: 1; font-variant-numeric: tabular-nums; }
.lock-telemetry strong::after { content: "%"; font-size: .55rem; margin-left: 2px; }
.capture-flash { position: absolute; z-index: 30; inset: 0; background: white; opacity: 0; pointer-events: none; }
.capture-flash.flash { animation: flash .32s ease-out; }
@keyframes flash { 0% { opacity: 0; } 15% { opacity: .82; } 100% { opacity: 0; } }

.bottom-nav { position: fixed; z-index: 50; left: 0; right: 0; bottom: 0; height: calc(var(--nav-height) + env(safe-area-inset-bottom)); padding: 8px 8px env(safe-area-inset-bottom); background: rgba(8,11,9,.94); border-top: 1px solid rgba(255,255,255,.11); display: grid; grid-template-columns: repeat(5, 1fr); backdrop-filter: blur(18px); }
.app-shell.scan-active .bottom-nav { transform: translateY(100%); pointer-events: none; }
.app-shell.scan-active .camera-footer { bottom: calc(10px + env(safe-area-inset-bottom)); }
.app-shell.scan-active .scan-instruction { bottom: calc(86px + env(safe-area-inset-bottom)); }
/* Navigationsknopf. Strichzeichnung statt gefuellter Silhouette: gefuellte
   Formen mit unterschiedlichem Flaechenanteil wirken nebeneinander unruhig.
   Der aktive Zustand wird durch eine Marke ueber dem Symbol angezeigt, nicht
   nur durch Farbe — Farbe allein ist bei Sehschwaeche kein Unterschied. */
.nav-item {
  position: relative; border: 0; background: transparent; cursor: pointer;
  display: grid; justify-items: center; align-content: center; gap: 4px;
  color: rgba(255,255,255,.42);
  font-size: var(--t-micro); font-weight: 800; letter-spacing: .04em;
  transition: color .18s ease;
}
.nav-item svg {
  width: 21px; height: 21px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Aktiver Reiter: heller Text, der Lime-Strich darueber traegt die Aussage.
   Vorher waren Wort UND Symbol in Neongruen — die Leiste war damit das
   farbigste Element auf jedem Bildschirm, obwohl sie nur sagt, wo man ist. */
.nav-item.active { color: #e9ecdf; }
.nav-item.active::before {
  content: ''; position: absolute; top: -8px; width: 16px; height: 2px;
  background: var(--lime);
}

.content-view { color: var(--ink); background: var(--paper); padding: calc(28px + env(safe-area-inset-top)) 18px calc(var(--nav-height) + 34px + env(safe-area-inset-bottom)); }
.content-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; max-width: 900px; margin: 0 auto 26px; }
/* Ueberschrift. Deutlich zurueckgenommen: 4,8rem fuellten auf dem Telefon die
   halbe Hoehe, bevor ein einziger Inhalt sichtbar war. Der Titel soll
   einordnen, nicht dominieren. */
.content-header h1 {
  margin: 0; font-size: clamp(1.9rem, 7vw, 2.8rem);
  line-height: .95; letter-spacing: -.045em; font-weight: 850; max-width: 620px;
}
.large-count {
  font-size: 2rem; font-weight: 900; line-height: .85; letter-spacing: -.05em;
  font-variant-numeric: tabular-nums;
}
.large-count span {
  font-size: var(--t-micro); letter-spacing: .06em; margin-left: 3px;
  font-weight: 800; color: var(--ink-3);
}
/* Die Zahl rechts oben braucht ein Etikett.
   ===============================================================
   Am 04.08.2026 im Kontaktbogen aufgefallen: sieben Ansichten zeigen dort
   eine grosse Zahl, und sie bedeutet jedes Mal etwas anderes — Faenge,
   verbleibende Kaempfe, Forschungspunkte, Fortschritt, Stufe, Freunde,
   Tauschverhaeltnis. Sammlung und Kaempfe standen beide auf "12", bei
   voellig verschiedenen Gegenstaenden.

   Das Etikett steht UNTER der Zahl, nicht daneben: laengere Woerter wie
   "HEUTE UEBRIG" haetten die Kopfzeile sonst umgebrochen und die Zahl aus
   der Flucht mit der Ueberschrift geschoben. */
.large-count.with-label { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
/* Die Zahl ist ein <strong>, das Etikett ein <span>. Ohne diese Trennung
   traefe die Etikettenregel darunter auch die Zahl und schruempfte sie auf
   Etikettengroesse — derselbe Sorte Fehler wie `.plain-list > div` gegen
   `.check-row`, nur eine Ebene tiefer. */
.large-count.with-label strong { font-weight: 900; line-height: .85; }
.large-count.with-label > span {
  margin-left: 0; line-height: 1; text-transform: uppercase;
  letter-spacing: var(--track-label);
}
.lead-copy {
  max-width: 680px; margin: calc(var(--s1) * -1) auto var(--s6);
  color: var(--ink-3); font-size: var(--t-body); line-height: 1.6;
}

/* Zahlenzeile. Vorher ein Gitter aus fuenf Kaesten mit 1,3-rem-Zahlen — rund
   110 px hoch, direkt ueber der Sammlung, und keine dieser Zahlen ist etwas,
   das man beim Oeffnen des Tabs liest. Als eine Zeile kosten sie 22 px und
   sagen dasselbe. Der grosse Zaehler im Kopf traegt die eine Zahl, die zaehlt.

   Waagerecht scrollend statt umbrechend: fuenf Werte passen auf 375 px nicht
   in eine Zeile, und ein Umbruch waere genau die zweite Reihe, die hier
   ueberall vermieden wird. */
.stat-line {
  max-width: 900px; margin: 0 auto var(--s4);
  display: flex; gap: var(--s4); align-items: baseline;
  overflow-x: auto; scrollbar-width: none;
  padding-bottom: var(--s3); border-bottom: 1px solid var(--line-hair);
  font-size: var(--t-small);
}
.stat-line::-webkit-scrollbar { display: none; }
.stat-line > span { flex: none; display: inline-flex; gap: 5px; align-items: baseline; white-space: nowrap; }
.stat-line strong { font-weight: 850; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat-line em {
  font-style: normal; font-size: var(--t-micro); color: var(--ink-3);
  letter-spacing: var(--track-label); font-weight: 850; text-transform: uppercase;
}

/* Aufklappblock. Ersetzt Bloecke, die dauerhaft offen standen und dabei den
   eigentlichen Inhalt aus dem Bild geschoben haben. <details> statt eigener
   Zustandslogik: das Auf- und Zuklappen kann der Browser, und ein
   selbstgebauter Umschalter waere ein zweiter Zustand, der mit dem DOM
   auseinanderlaufen kann. */
.fold {
  max-width: 900px; margin: 0 auto var(--s4);
  border: 1px solid var(--line-hair); background: var(--paper);
}
.fold > summary {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3); cursor: pointer; list-style: none;
  font-size: var(--t-small);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary strong { font-weight: 850; letter-spacing: -.01em; }
.fold-icon { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; color: var(--ink-3); }
.fold-value {
  margin-left: auto; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); color: var(--ink-3); text-transform: uppercase;
}
.fold-chevron {
  width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; color: var(--ink-3);
  transform: rotate(90deg); transition: transform .18s ease;
}
.fold[open] > summary .fold-chevron { transform: rotate(-90deg); }
.fold-body { padding: 0 var(--s3) var(--s3); border-top: 1px solid var(--line-hair); }
.fold-note { margin: var(--s3) 0 var(--s2); font-size: var(--t-micro); color: var(--ink-3); letter-spacing: .06em; }
/* Limette = Handlung, ueberall in der App dieselbe Aussage. Hier: es liegt
   etwas zum Abholen bereit. */
/* MARKE: Punkt plus Wort, nie eine Fuellflaeche. Sonst konkurriert jedes
   Etikett farblich mit der Karte daneben. Gilt genauso fuer .species-status.ja. */
.fold-value.bereit {
  color: var(--ink); font-weight: 850;
  display: inline-flex; align-items: center; gap: 5px;
}
.fold-value.bereit::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--lime); flex: none;
}

/* Steuerzeile der Sammlung. Suche und Sortierung nebeneinander; die Suche
   nimmt den Rest, weil sie die haeufigere Handlung ist. */
.collection-controls {
  max-width: 900px; margin: 0 auto var(--s3);
  display: grid; grid-template-columns: 1fr auto; gap: var(--s2);
}
.search-field {
  display: flex; align-items: center; gap: var(--s2); min-width: 0;
  padding: 0 var(--s3); border: 1px solid var(--line-dark); background: var(--paper);
}
.search-field svg {
  width: 15px; height: 15px; flex: none; fill: none; stroke: var(--ink-3);
  stroke-width: 1.8; stroke-linecap: round;
}
.search-field input {
  flex: 1; min-width: 0; border: 0; background: transparent; color: var(--ink);
  padding: 10px 0; font-size: var(--t-small); outline: none;
}
.search-field input::-webkit-search-cancel-button { -webkit-appearance: none; }
.sort-field select {
  border: 1px solid var(--line-dark); background: var(--paper); color: var(--ink);
  padding: 10px var(--s3); font-family: inherit; font-size: var(--t-micro);
  font-weight: 850; letter-spacing: .06em; cursor: pointer;
  border-radius: 0; -webkit-appearance: none; appearance: none;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Filterleiste. Waagerecht scrollend, seit der Katalog sechs Eintraege hat:
   bei 375 px Geraetebreite passte "Insekten" nicht mehr und wurde am rechten
   Rand abgeschnitten. Ohne overflow-x sprengte die Leiste ausserdem den
   Container und machte die ganze SEITE waagerecht schiebbar — sichtbar nur im
   Katalog, weil die Sammlung nur drei Filter hat.
   Die Randmaske zeigt an, dass rechts noch etwas kommt. */
.filter-line {
  max-width: 900px; margin: 0 auto 16px; display: flex; gap: 20px;
  border-bottom: 1px solid var(--line-dark);
  overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
.filter-line::-webkit-scrollbar { display: none; }
/* Der obere Innenabstand bringt die 29 px auf 32 — wie bei `.segment` und aus
   demselben Grund: der Strich unten liegt auf der Linie, `min-height` haette
   ihn verschoben. */
.filter {
  flex: none; scroll-snap-align: start; min-width: 32px;
  border: 0; border-bottom: 3px solid transparent; background: transparent;
  color: #72766f; padding: 4px 0 10px; font-size: .78rem; font-weight: 850;
  cursor: pointer; white-space: nowrap;
}
.filter.active { color: var(--ink); border-bottom-color: var(--ink); }
.collection-new-filter span {
  display: inline-block; min-width: 1.2em; margin-left: 3px;
  color: #4f6b09; font-variant-numeric: tabular-nums;
}

.mission-list, .trade-list { max-width: 900px; margin: 0 auto; }

/* ---- Sammlung als Fotoraster ----------------------------------------------
   Vorher eine flache Zeilenliste: je Exemplar eine Zeile mit 104x78-Briefmarke
   und drei Textspalten. Zwei Dinge stimmten daran nicht.

   ERSTENS ist das Foto die Belohnung. Der Spieler war draussen und hat das
   Tier selbst aufgenommen; in einer Textzeile ist es Beiwerk. Im Raster
   traegt es die Kachel.

   ZWEITENS war die Liste nicht nachvollziehbar: 15 Exemplare ergaben 2916 px
   Bildlauf, ohne Gruppierung und ohne Hinweis darauf, wovon man mehrere hat.
   Genau diese Frage stellt aber das Verschmelzen. Das Raster fasst deshalb
   nach ART zusammen und traegt die Stapelzahl auf der Kachel — der Stapel ist
   der Einstieg in die Werkstatt.

   auto-fill mit minmax: auf 375 px zwei Spalten, auf einem Tablet vier. Keine
   Umbruchstelle von Hand, also auch keine, die vergessen wird. */
.collection-grid {
  max-width: 900px; margin: 0 auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: var(--s3);
}

/* ============================================================================
   SAMMLUNG ALS KARTEN — Reihen je Art (05.08.2026)

   Eine Fotokachel zeigte je Art EIN Bild und verbarg den Rest des Stapels.
   Jetzt liegt jedes Exemplar als eigene Sammelkarte da: runter fuer andere
   Arten, seitwaerts fuer weitere Exemplare derselben.

   Die Karte selbst kommt aus karte.css und wird hier NICHT angefasst — sie
   wird aus dem Kartensheet erzeugt, und eine Regel von hier aus wuerde beim
   naechsten Lauf stillschweigend ueberschrieben aussehen, obwohl sie wirkt.
   ============================================================================ */
/* RASTER, nicht Liste. Bei 390 px Geraetebreite passen zwei Karten
   nebeneinander — und zwar mit 171 px, also KNAPP UEBER der 170-px-Grenze der
   Karte. Das ist kein Zufall, sondern der Grund fuer `minmax(160px, 1fr)`:
   unterhalb davon blendet die Karte ihre Wertbeschriftungen aus, und acht
   nackte Zahlen sagen niemandem, welche welche ist. */
.collection-grid.als-karten {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--s5) var(--s2); align-items: start;
  /* SECHS PIXEL ENTSCHEIDEN HIER ALLES. Bei 375 px Geraetebreite (iPhone SE,
     iPhone 13 mini) blieben mit dem regulaeren Seitenrand von 18 px genau
     164 px je Karte — sechs unter der 170-px-Grenze, ab der die Karte ihre
     Wertkuerzel ausblendet. Acht nackte Zahlen sagen niemandem, welche
     welche ist.

     Das Raster zieht deshalb ueber den Seitenrand der Ansicht hinaus: 375
     minus 2x6 Rand minus 8 Spalte ergibt 175 je Karte. Gemessen, nicht
     geschaetzt — wer den Rand oder die Spalte anfasst, prueft es nach. */
  margin-inline: -12px;
}

/* Eine Art mit MEHREREN Exemplaren bekommt eine eigene Zeile ueber die volle
   Breite und wird darin gewischt. Eine mit einem einzigen steht als Karte im
   Raster — sonst zeigte die Sammlung bei zwoelf Faengen zwoelf Zeilen mit je
   einer Karte und viel Leerraum rechts daneben. */
.art-reihe { grid-column: 1 / -1; display: flex; flex-direction: column; gap: var(--s2); }
.art-kopf {
  display: flex; align-items: baseline; gap: var(--s2);
  padding-bottom: 3px; border-bottom: 1px solid var(--line-hair);
}
.art-kopf b { font-size: var(--t-body); font-weight: 850; letter-spacing: -.01em; }
.art-kopf > span {
  margin-left: auto; font-size: var(--t-micro); font-weight: 800;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.art-neu {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-2);
}
.art-neu::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--lime); flex: none;
}

/* Seitwaerts wischen mit Einrastpunkten. Ohne `scroll-snap` bleibt die Reihe
   zwischen zwei Karten stehen, und beide sind angeschnitten. */
.kartenreihe {
  display: flex; gap: var(--s3); overflow-x: auto;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
  /* Rand fuer den Schlagschatten der Karte — ohne ihn schneidet der
     Ueberlaufrahmen ihn an drei Seiten ab. */
  padding: 4px 2px 10px;
  scrollbar-width: none;
}
.kartenreihe::-webkit-scrollbar { display: none; }
/* ============================================================================
   KARTENBUEHNE — antippen, und die Karte steht gross und geneigt da

   Die Karte traegt ihre Perspektive schon selbst
   (`perspective(1200px) rotateX(--rx) rotateY(--ry)` in karte.css). Die
   Buehne setzt nur die beiden Winkel — aus der Zeigerbewegung am Rechner,
   aus dem Lagesensor am Handy. Ein eigener `transform` HIER wuerde die
   Perspektive der Karte ueberschreiben und sie flachlegen.
   ============================================================================ */
#kartenBuehne {
  position: fixed; inset: 0; z-index: 60;
  display: none; place-items: center;
  padding: max(20px, env(safe-area-inset-top)) 20px 20px;
  background: radial-gradient(120% 60% at 50% 30%, rgba(26,33,27,.94), rgba(6,8,7,.97));
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  touch-action: none;
}
#kartenBuehne[open], #kartenBuehne.offen { display: grid; }
/* SOLANGE EINE KARTE VORN STEHT, RUHT ALLES DAHINTER.

   Das Sammlungsraster bleibt im Dokument und arbeitete weiter: zwoelf Karten
   mit laufender Ruheschaukel, hinter einem Grund mit 94 bis 97 Prozent
   Deckkraft. Gemessen am Drehen einer Karte, 100 Bilder je Lauf: 12,1 ms je
   Bild mit, 6,1 ms ohne — die Bildrate halbiert sich fuer Karten, die niemand
   sieht.

   Pausiert, nicht versteckt: der schwache Schimmer durch den Grund bleibt
   erhalten, nur die Bewegung darin hoert auf.

   SIE SITZT AUF DEM HALTER, nicht mehr auf der Karte — sonst pausiert diese
   Regel ins Leere. Begruendung bei `@keyframes schweben` in `karte.css`:
   eine Animation auf Custom Properties laeuft im Hauptthread und kostete im
   Leerlauf 940 ms je Sekunde.

   DIE `:has()`-BEDINGUNG MUSS HIER MITSTEHEN. Ohne sie verliert diese Regel:
   `.kartenplatz:has(> .karte.lebt)` zaehlt drei Klassen, `body.karte-vorn
   .kartenplatz` nur zwei — und weil dort die KURZFORM `animation:` steht,
   setzt sie `animation-play-state` gleich wieder auf `running` zurueck.
   Nachgemessen bei geoeffneter Buehne: `running` statt `paused`. */
body.karte-vorn .kartenplatz:has(> .karte.lebt),
body.karte-vorn .studio-karte:has(.karte.lebt),
body.karte-vorn .kartenhalt:has(.karte.lebt),
body.karte-vorn .karte { animation-play-state: paused; }
body.karte-vorn #kartenBuehne .karte,
body.karte-vorn .buehne-karte { animation-play-state: running; }

/* WAEHREND SIE SICH DREHT, FAELLT DER WEICHZEICHNER WEG.

   `backdrop-filter: blur(6px)` liegt ueber den vollen 375x812 px, und ein
   Vollbild-Weichzeichner muss bei JEDER Aenderung darueber neu komponiert
   werden — genau das tut eine drehende Karte sechzigmal je Sekunde. Das
   Kartenstudio hat keinen, und aus dem Feld kam „im Kartenstudio ist es VIEL
   besser": es ist der einzige verbliebene Unterschied zwischen beiden.

   NICHT GEMESSEN, und das gehoert dazugesagt: der Rechner loest es nicht mehr
   auf — nach dem Stilllegen des Rasters liegen dort alle Varianten bei
   6,1 ms, und das ist die Bildgrenze, nicht das Ergebnis.

   Im Standbild bleibt er. Er tut dort sichtbar etwas (der Hintergrund kommt
   durch 94 % Deckkraft deutlich durch); waehrend die Karte sich dreht, sieht
   ihn niemand. */
body.karte-dreht #kartenBuehne {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* `#kartenBuehne` zentriert genau EIN Kind: diesen Rahmen. Er nimmt die volle
   Spaltenbreite (335 px bei 375 px Fenster, weil der Hinweistext so breit
   wird), Karte und Fuss sind mit `min(78vw, 300px)` aber nur 293 px — und
   standen darin linksbuendig. Gemessen: 42 px zu weit links, waehrend der
   Hinweis darunter mittig lag. Zentriert wird deshalb HIER, eine Ebene
   tiefer. */
.buehne-inhalt { display: grid; justify-items: center; }
.buehne-karte {
  width: min(78vw, 300px);
  /* Die Karte skaliert beim Oeffnen von der Rasterbreite hoch. Ohne die
     Startgroesse springt sie, statt zu wachsen.

     `schweben` steht DANEBEN, nicht auf der Karte darin: die Ruheschaukel
     laeuft seit dem 06.08.2026 als reine transform-Animation auf dem Halter
     (Begruendung bei `@keyframes schweben` in `karte.css`). Beide vertragen
     sich, weil `buehneAuf` nur `scale` und `opacity` bewegt. */
  animation: buehneAuf .34s cubic-bezier(.2,.8,.3,1) both,
             schweben 6s ease-in-out infinite;
  /* Die Perspektive gehoert HIERHER, nicht an den Dreher: ein Element kann
     seine eigene Perspektive nicht auf sich selbst anwenden. Ohne sie klappt
     die Karte beim Drehen zu einem Strich zusammen, statt sich zu wenden. */
  perspective: 1400px;
}
/* IM RAUM: die Karte wird so weit vergroessert, dass ihr Bildfeld das
   Fenster fuellt. Der Faktor kommt aus `raumMass()` in `app.js` — er haengt
   an der gemessenen Feldgroesse und laesst sich hier nicht ausrechnen.

   `animation: none` ist PFLICHT, nicht Kosmetik: die Ruheschaukel `schweben`
   laeuft seit dem 06.08.2026 als reine transform-Animation auf genau diesem
   Halter, und eine laufende Animation schlaegt jede Regel im Stilblatt —
   auch `!important`. Ohne diese Zeile passiert sichtbar gar nichts. */
.buehne-karte.raumnah {
  animation: none;
  transform: translateY(var(--raumhoch, 0px)) scale(var(--raumskala, 1));
  transition: transform .55s cubic-bezier(.2,.8,.3,1);
}
/* Gemeinsamer V2-/Produktionscontroller. Anders als `.raumnah` zentriert er
   das reale Bildfeld auf beiden Achsen und misst nach Rotation/Resize neu. */
.buehne-karte.vitrinenraum-offen {
  animation: none;
  transform: translate(var(--vitrinenraum-x, 0), var(--vitrinenraum-y, 0))
    scale(var(--vitrinenraum-scale, 1));
  transition: transform .55s cubic-bezier(.2,.8,.3,1);
}
/* Fuss und Hinweis treten zurueck — sie liegen unter dem vergroesserten Raum
   und waeren nur noch angeschnittener Text. Weg sind sie nicht: der Ausstieg
   ist derselbe Tipp, mit dem man hineingekommen ist. */
body.raum-offen .buehne-fuss,
body.raum-offen .buehne-hinweis { opacity: 0; pointer-events: none; }
.buehne-fuss, .buehne-hinweis { transition: opacity .3s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .buehne-karte.raumnah,
  .buehne-karte.vitrinenraum-offen { transition: none; }
}

/* DAS KARTENRASTER HOERT AUF ZU ARBEITEN, SOLANGE DIE BUEHNE VORN IST.
   Es liegt vollstaendig verdeckt darunter — und zeichnete trotzdem weiter:
   jede Karte mit ihrer Veredelung, ihrem Glanz und ihrem Vitrinenraum. Bei
   zwoelf Karten ist das zwoelfmal die Arbeit, die man gerade nicht sieht.

   Am Geraet gemessen (A069P, Drehung im Vitrinenraum, paarweise gegen eine
   Basis davor und danach, Drift 0,8 ms):

       mit Raster im Hintergrund   29,3 ms je Bild   ~34 fps
       ohne                        13,2 ms je Bild   ~75 fps

   16,1 ms — mehr als die HAELFTE der Kosten, und sie entstehen fuer etwas
   Unsichtbares. Zum Vergleich: den kompletten 3D-Raum zu entfernen bringt
   6,9 ms, alle sechs Waende zusammen 1,7 ms. Wer an den Waenden optimiert,
   arbeitet am falschen Ende.

   `content-visibility: hidden` statt `display: none`: es ueberspringt Layout
   und Zeichnen des INHALTS, haelt aber den Kasten selbst — die Scrollposition
   bleibt damit erhalten. Mit `display:none` faellt die Liste zusammen, und
   beim Schliessen der Buehne stuende man wieder ganz oben.

   `karte-vorn` setzt `zeigeKartenbuehne()`; sie faellt beim Schliessen wieder
   weg. */
body.karte-vorn #collectionList { content-visibility: hidden; }
/* Collection V2 zeichnet in einem eigenen Wurzelknoten und lief deshalb an
   der alten Rasterbremse vorbei. Nicht nur die Kartenliste, sondern die ganze
   verdeckte Sammlungswelt wird waehrend der Buehne aus dem Paint genommen.
   So kann sie bei einem Android-Compositor-Aussetzer auch nicht mehr fuer ein
   Bild durch die drehende Karte blitzen. */
body.karte-vorn #collectionV2Root {
  content-visibility: hidden;
  visibility: hidden;
  pointer-events: none;
}
.buehne-karte .kartendreher { width: 100%; }
/* Die Rueckseite fuellt den Dreher. Sie kommt als eigene .karte, und die
   traegt ihre Breite von aussen — hier ist das die volle Flaeche. */
.buehne-karte .kartenrueck > .karte { width: 100%; }
@keyframes buehneAuf {
  from { scale: .55; opacity: 0; }
  to { scale: 1; opacity: 1; }
}
.buehne-fuss {
  margin-top: var(--s5); display: flex; gap: var(--s3);
  width: min(78vw, 300px);
}
.buehne-fuss button {
  flex: 1; padding: 12px; border: 0; border-radius: var(--r-kante);
  font: inherit; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; cursor: pointer;
}
.buehne-fuss .weiter { background: var(--paper); color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--lime); }
.buehne-fuss .zu { background: transparent; color: rgba(244,245,239,.72);
  border: 1px solid rgba(255,255,255,.2); }
.buehne-hinweis {
  margin-top: var(--s3); font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: rgba(244,245,239,.34);
  /* Zweizeilig auf schmalen Geraeten. Linksbuendig sah der Block unter der
     zentrierten Karte aus, als haenge die ganze Buehne schief. */
  text-align: center; max-width: min(90vw, 340px);
}
@media (prefers-reduced-motion: reduce) {
  .buehne-karte { animation: none; }
}

/* ── Herausfordern, Einladung, Tierwahl ─────────────────────────────── */
.pvp-fordern { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--s4); }
.pvp-fordern-marke {
  margin-left: auto; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-2);
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
}
.pvp-fordern-marke::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--lime);
}

/* Die Einladung liegt UEBER der Navigation und blockiert nichts.
   Ein modaler Dialog waere hier der Fehler, den diese Codebase schon einmal
   gemacht hat: er liegt im Top-Layer und faengt jeden Klick ab — von aussen
   sieht das aus, als haenge die App. */
#pvpEinladung {
  position: fixed; z-index: 55;
  left: 12px; right: 12px; bottom: calc(var(--nav-height) + 12px + env(safe-area-inset-bottom));
  max-width: 460px; margin: 0 auto;
  display: flex; flex-direction: column; gap: var(--s3);
  padding: var(--s4); border-radius: var(--r-card);
  background: var(--night); color: #e9ecdf;
  box-shadow: 0 10px 30px rgba(0,0,0,.4), inset 0 -2px 0 var(--lime);
}
#pvpEinladung strong { display: block; font-size: var(--t-body); font-weight: 850; }
#pvpEinladung em { font-size: var(--t-small); color: #8d968c; font-style: normal; }
.pvp-einladung-wege { display: flex; gap: var(--s2); }
.pvp-einladung-wege button {
  flex: 1; padding: 11px; border-radius: var(--r-kante); font: inherit;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label);
  text-transform: uppercase; cursor: pointer;
}
.pvp-einladung-wege .ablehnen {
  background: transparent; border: 1px solid rgba(255,255,255,.2); color: #c3c9bf;
}
.pvp-einladung-wege .annehmen {
  background: #e9ecdf; border: 0; color: var(--night);
  box-shadow: inset 0 -2px 0 var(--lime);
}
/* ABGELEHNT sieht anders aus als „läuft". Beides in derselben grünen Leiste
   hätte die Absage wie eine offene Einladung gelesen — und der Kasten ist
   genau dafür da, die beiden auseinanderzuhalten. */
#pvpEinladung.abgelehnt { box-shadow: 0 10px 30px rgba(0,0,0,.4), inset 0 -2px 0 var(--rost); }
#pvpEinladung.abgelehnt strong { color: var(--rost); }

/* DIE WARNUNG AN DER WAHL, nicht in einer Fussnote darueber. Man waehlt hier
   genau das Tier, das man verlieren kann — und der Moment der Wahl ist der
   letzte, in dem das noch etwas aendert. */
.wahl-warnung {
  margin: 0 0 var(--s3); padding: 10px var(--s3);
  border-left: 3px solid var(--rost);
  background: color-mix(in srgb, var(--rost) 12%, var(--flaeche));
  color: var(--ink); font-size: var(--t-small); line-height: 1.4;
}

/* Tierwahl: die eigenen Karten in voller Ausstattung. Eine Namensliste waere
   kuerzer und liesse genau die Entscheidung nicht zu, um die es geht — man
   soll die acht Werte sehen, bevor man sich festlegt. */
#kampftierWahl {
  position: fixed; inset: 0; z-index: 65; overflow-y: auto;
  padding: max(16px, env(safe-area-inset-top)) 14px calc(16px + env(safe-area-inset-bottom));
  background: linear-gradient(180deg, #12180f, #060807); color: #e9ecdf;
}
.wahl-innen { max-width: 460px; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s4); }
.wahl-innen header { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); padding-bottom: var(--s3); border-bottom: 1px solid rgba(255,255,255,.12); }
.wahl-innen header strong { font-size: var(--t-lead); font-weight: 850; }
.wahl-zu { padding: 8px 12px; border: 1px solid rgba(255,255,255,.2); border-radius: var(--r-kante);
  background: transparent; color: #c3c9bf; font: inherit; font-size: var(--t-micro);
  min-height:44px; font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase; cursor: pointer; }
.wahl-raster { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--s3); align-items: start; }
.pvp-lobby-gegenueber{min-height:72px;padding:9px 0;display:grid;grid-template-columns:52px minmax(0,1fr) auto;align-items:center;gap:11px;border-bottom:1px solid rgba(255,255,255,.12)}
.pvp-lobby-avatar{width:50px;height:50px;display:grid;place-items:center;overflow:hidden;border:1px solid rgba(255,255,255,.18);border-radius:17px 7px 17px 7px;background:rgba(255,255,255,.06)}.pvp-lobby-avatar img{width:100%;height:100%;object-fit:cover}.pvp-lobby-avatar i{font-size:1.25rem;font-style:normal;color:#7e8d82}
.pvp-lobby-gegenueber>span:nth-child(2){min-width:0;display:grid}.pvp-lobby-gegenueber small{color:#8fb3a3;font-size:.62rem;font-weight:900;letter-spacing:.1em}.pvp-lobby-gegenueber strong{margin-top:2px;overflow:hidden;font-size:.92rem;text-overflow:ellipsis;white-space:nowrap}.pvp-lobby-gegenueber em{margin-top:2px;color:#99a59d;font-size:.68rem;font-style:normal;line-height:1.25}.pvp-lobby-gegenueber>b{padding:5px 7px;border:1px solid rgba(255,255,255,.13);border-radius:10px 4px 10px 4px;color:#89958c;font-size:.59rem;letter-spacing:.08em}.pvp-lobby-gegenueber>b.bereit{border-color:rgba(201,255,66,.45);color:var(--lime)}
.wahl-arbeitsflaeche{display:grid;grid-template-columns:minmax(0,210px) minmax(0,1fr);grid-template-areas:'preview list' 'confirm list' 'waiting list';align-items:start;gap:10px 12px}.wahl-vorschau{grid-area:preview;width:100%;min-height:290px;display:grid;place-items:start center}.wahl-vorschau>.karte{width:100%!important;max-width:210px!important}.wahl-liste{grid-area:list;max-height:calc(100dvh - 240px);padding-right:3px;display:grid;gap:6px;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin}
.wahl-tier{width:100%;min-height:60px;padding:5px 7px 5px 5px;display:grid;grid-template-columns:48px minmax(0,1fr) auto;align-items:center;gap:8px;border:1px solid rgba(255,255,255,.09);border-radius:15px 5px 15px 5px;color:#e9ecdf;background:rgba(255,255,255,.035);text-align:left}.wahl-tier[aria-pressed=true]{border-color:rgba(201,255,66,.55);background:rgba(201,255,66,.08);box-shadow:inset 3px 0 var(--lime)}.wahl-tier:disabled{opacity:.68}.wahl-tier-bild{width:48px;height:48px;overflow:hidden;border-radius:12px 4px 12px 4px;background:#1d281f}.wahl-tier-bild img,.wahl-tier-bild svg{width:100%;height:100%;object-fit:cover}.wahl-tier-bild svg{padding:12px;color:#849287}.wahl-tier-copy{min-width:0;display:grid}.wahl-tier-copy strong{overflow:hidden;font-size:.8rem;text-overflow:ellipsis;white-space:nowrap}.wahl-tier-copy small{margin-top:2px;color:#91a097;font-size:.62rem}.wahl-tier-staerken{display:grid;justify-items:end;gap:2px}.wahl-tier-staerken i{color:#99a59d;font-size:.57rem;font-style:normal;white-space:nowrap}.wahl-tier-staerken b{color:#e9ecdf;font-size:.66rem}.wahl-bestaetigen{grid-area:confirm;min-height:46px;padding:0 12px;border:0;border-radius:16px 6px 16px 6px;color:#10170f;background:linear-gradient(135deg,#e7f0d9,var(--lime));font:inherit;font-size:.68rem;font-weight:950;letter-spacing:.04em;text-transform:uppercase}.wahl-bestaetigen:disabled{color:#afbbb1;background:#344038}.pvp-lobby-warten{grid-area:waiting;margin:0;color:#9eaaa2;font-size:.66rem;line-height:1.4}
@media(max-width:560px){#kampftierWahl{padding-inline:10px}.wahl-innen{max-width:430px}.wahl-arbeitsflaeche{grid-template-columns:minmax(0,152px) minmax(0,1fr);gap:8px}.wahl-vorschau{min-height:230px}.wahl-vorschau>.karte{max-width:152px!important}.wahl-liste{max-height:calc(100dvh - 230px)}.wahl-tier{grid-template-columns:42px minmax(0,1fr);min-height:54px}.wahl-tier-bild{width:42px;height:42px}.wahl-tier-staerken{display:none}}
@media(max-width:360px){.wahl-arbeitsflaeche{grid-template-columns:132px minmax(0,1fr)}.wahl-vorschau>.karte{max-width:132px!important}.pvp-lobby-gegenueber{grid-template-columns:44px minmax(0,1fr)}.pvp-lobby-avatar{width:42px;height:42px}.pvp-lobby-gegenueber>b{display:none}}

/* ============================================================================
   DIE PVP-BUEHNE

   Nacht-Register: hier wird gekaempft, nicht gelesen. Die Wertwahl ist der
   Kern, und sie passiert AUF DER EIGENEN KARTE — dort steht jeder Wert neben
   dem Angriff, der ihn erhoeht.

   Die Werte des Gegners bleiben verdeckt (KAMPFSYSTEM.md Abschnitt 4, geaendert
   05.08.2026). Hier stand bis dahin das Gegenteil: „im PVP sind alle Werte
   ohnehin offen". Wer beide Zahlenreihen sieht, rechnet die Runde aus, statt
   sie zu spielen.
   ============================================================================ */
#pvpBuehne {
  position: fixed; inset: 0; z-index: 70; display: grid; align-items: start;
  overflow-y: auto;
  padding: max(16px, env(safe-area-inset-top)) 14px calc(16px + env(safe-area-inset-bottom));
  background: radial-gradient(125% 58% at 50% 0%, #1a231c 0%, #06080700 0%),
              linear-gradient(180deg, #12180f, #060807);
  color: #e9ecdf;
}
.pvp-innen { width: 100%; max-width: 460px; margin: 0 auto; display: flex;
  flex-direction: column; gap: var(--s4); }

.pvp-kopf { display: grid; grid-template-columns: 1fr auto 1fr; gap: var(--s3);
  align-items: start; padding-bottom: var(--s3);
  border-bottom: 1px solid rgba(255,255,255,.12); }
.pvp-seite { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.pvp-seite.rechts { text-align: right; align-items: flex-end; }
.pvp-seite strong { font-size: var(--t-body); font-weight: 850; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.pvp-seite em { font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: #7f887e; font-style: normal; }
/* Drei Marken je Seite: drei gewonnene Runden entscheiden. Eine Zahl waere
   kuerzer und saehe nach Punktestand aus — es sind aber Etappen. */
.pvp-siege { display: flex; gap: 4px; }
.pvp-siege i { width: 16px; height: 3px; background: rgba(255,255,255,.16); border-radius: 2px; }
.pvp-siege i.voll { background: var(--lime); }
.pvp-runde { text-align: center; }
.pvp-runde b { display: block; font-size: var(--t-small); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--lime); }
.pvp-runde span { font-size: var(--t-micro); color: #7f887e; }

/* Die Auslosung. Sie steht nur in Runde eins und nur einmal — wer beginnt,
   war vorher eine stille Setzung, die niemand sah. */
.pvp-losung { display: flex; align-items: baseline; gap: var(--s2);
  font-size: var(--t-small); color: #c3c9bf; }
.pvp-losung span { font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--gold); }

/* ---------------------------------------------------------------------------
   DER TISCH: die eigene Karte ist die BEDIENFLAECHE.

   Hier lagen bis zum 06.08.2026 zwei briefmarkengrosse Karten nebeneinander
   und darunter dieselben acht Werte nochmal als Kachelraster. Die Karte trug
   damit nichts bei. Jetzt steht sie gross und wird angetippt; die Gegnerkarte
   liegt daneben, klein und verdeckt.
--------------------------------------------------------------------------- */
.pvp-tisch {
  display: flex; flex-direction: column; gap: var(--s3); align-items: center;
  /* Bezug fuer den Schlagabtausch. Ohne `position` legt er sich auf die
     Seite statt auf den Tisch — und laege damit ueber der Zugleiste. */
  position: relative;
}

/* DER SCHLAGABTAUSCH — der Moment, in dem die Runde faellt.

   Zwei Zahlen fahren aufeinander zu, die hoehere bleibt und schlaegt an, die
   niedrigere sinkt weg. Alles ueber `transform` und `opacity`: die Buehne
   haelt in diesem Moment zwei vollstaendige Karten mit ihren Folien, und
   jede andere Eigenschaft rechnet der Browser je Bild auf dem Hauptthread neu.

   `pointer-events: none` ist Pflicht — die Einblendung liegt ueber der Karte,
   und eine Sekunde tote Bedienflaeche sieht aus wie ein Haenger. */
/* FEST AUF DEM BILDSCHIRM, nicht auf dem Tisch.
   `.pvp-tisch` ist 495 px hoch und reicht in einer gescrollten Buehne weit
   ueber den sichtbaren Bereich hinaus — die Zahlen landeten dann irgendwo,
   im Nachmessen halb oberhalb des Fensters. Der Moment, auf den es ankommt,
   muss in jeder Scrollstellung im Bild stehen. */
.pvp-schlag {
  position: fixed; inset: 0; z-index: 6; pointer-events: none;
  display: grid; place-content: center; gap: var(--s2); justify-items: center;
  /* 2,9 s statt 1,5: die Enthuellung braucht 1,4 s, bis das Urteil steht.
     Kuerzer blendete die Einblendung aus, waehrend sie noch lief. */
  animation: schlagAus 2.9s ease forwards;
  /* EIN GRUND UNTER DEN ZAHLEN. Darueber liegt eine Holokarte mit Folie und
     Farbverlauf; zwei weisse Ziffern darauf waren beim Nachsehen kaum zu
     lesen. Der Verlauf laeuft nach aussen aus, damit die Karte nicht
     abgedeckt, sondern nur gedaempft wird — man soll sehen, worum gekaempft
     wird. Gemalt wird er EINMAL: die Einblendung wird als Ganzes ueber
     `opacity` ausgeblendet, nicht Bild fuer Bild neu gerechnet. */
  background: radial-gradient(closest-side at 50% 50%,
    rgba(4, 6, 5, .93) 0%, rgba(4, 6, 5, .86) 42%, rgba(4, 6, 5, 0) 100%);
}
.schlag-wert {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-2);
  animation: schlagRein .22s ease both;
}
/* Die Hoehe steht fest, obwohl nur Zahlen darin liegen: die Siegerzahl waechst
   auf 128 % und schoebe das Urteil darunter sonst mitten in der Bewegung weg.
   Ein Layout, das sich waehrend einer Animation verschiebt, ruckelt sichtbar
   und rechnet auf dem Hauptthread nach — beides fuer nichts. */
.schlag-paar {
  display: flex; align-items: center; gap: var(--s4); position: relative;
  min-height: 84px;
}
/* DIE ENTHUELLUNG (06.08.2026). Vorher standen beide Zahlen gleichzeitig da —
   das nahm der Runde genau das weg, was sie spannend macht: die Werte des
   Gegners sind verdeckt, und diese eine Zahl ist das, was man nicht wusste.

   Die Reihenfolge steht in den Verzoegerungen: 0,15 s der Wert, 0,35 s dreht
   der Deckel des GEGNERS weg, 0,85 s faehrt die eigene Zahl ein, 1,15 s der
   Aufprall. EINE Zeitachse, nicht vier Zeitgeber — Zeitgeber neben CSS-Dauern
   laufen beim naechsten Anfassen auseinander. */
.schlag-zahl {
  position: relative; display: grid; justify-items: center; gap: 2px;
  font-size: 3.4rem; font-weight: 900; letter-spacing: -.04em; line-height: 1;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 22px rgba(0, 0, 0, .9);
}
.schlag-summe { font-style: normal; }
/* WAS DIE ZAHL AUSMACHT, steht unter ihr — fuer beide Seiten. Dass der Gegner
   einen Angriff gespielt hat, ist die halbe Erklaerung fuer den Ausgang. */
.schlag-zusatz {
  font-style: normal; font-size: var(--t-micro); font-weight: 800;
  letter-spacing: 0; color: var(--gold); text-shadow: none;
  animation: schlagRein .3s .95s both;
}
.schlag-zahl.gegner .schlag-zusatz { animation-delay: .7s; }
/* Der Deckel liegt UEBER der Zahl und dreht weg. Er ist der Moment. */
.schlag-deckel {
  position: absolute; inset: -6px -14px auto; height: 62px;
  display: grid; place-items: center; border-radius: 14px;
  background: linear-gradient(160deg, #2a3128, #171c16);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
  font-style: normal; font-size: 2rem; color: var(--ink-3);
  transform-origin: 50% 0; backface-visibility: hidden;
  animation: deckelWeg .5s .35s cubic-bezier(.35,.85,.3,1) both;
}
/* Links der Gegner (er wird aufgedeckt), rechts die eigene Zahl. Sie faehrt
   spaeter ein: erst sieht man, wogegen man antritt. */
.schlag-zahl.ich { color: var(--lime); animation: schlagVonRechts .3s .85s cubic-bezier(.2,.9,.3,1.2) both; }
.schlag-zahl.gegner { color: var(--ink); animation: schlagRein .3s .15s both; }
.schlag-zahl.ich.haelt { animation: schlagVonRechts .3s .85s cubic-bezier(.2,.9,.3,1.2) both, schlagPuls .34s 1.15s ease both; }
.schlag-zahl.ich.faellt { animation: schlagVonRechts .3s .85s cubic-bezier(.2,.9,.3,1.2) both, schlagSinkt .5s 1.15s ease both; }
.schlag-zahl.gegner.haelt { animation: schlagRein .3s .15s both, schlagPuls .34s 1.15s ease both; }
.schlag-zahl.gegner.faellt { animation: schlagRein .3s .15s both, schlagSinkt .5s 1.15s ease both; }
.schlag-funke {
  position: absolute; left: 50%; top: 50%; width: 92px; height: 92px;
  margin: -46px 0 0 -46px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255,255,255,.9) 0%, transparent 62%);
  opacity: 0; animation: schlagFunke .4s 1.12s ease both;
}
.schlag-urteil {
  font-size: var(--t-small); font-weight: 800; color: var(--ink-2);
  animation: schlagRein .3s 1.4s both;
}
@keyframes deckelWeg {
  from { transform: rotateX(0); opacity: 1; }
  to { transform: rotateX(-96deg); opacity: 0; }
}
.pvp-schlag.sieg .schlag-urteil { color: var(--lime); }
.pvp-schlag.niederlage .schlag-urteil { color: var(--rost); }

@keyframes schlagVonLinks { from { transform: translateX(-90px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes schlagVonRechts { from { transform: translateX(90px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes schlagPuls { 0% { transform: scale(1); } 45% { transform: scale(1.28); } 100% { transform: scale(1.12); } }
@keyframes schlagSinkt { to { transform: translateY(26px) scale(.7); opacity: .28; } }
@keyframes schlagFunke { 0% { opacity: 0; transform: scale(.2); } 40% { opacity: .95; } 100% { opacity: 0; transform: scale(1.6); } }
@keyframes schlagRein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* Der letzte Abschnitt blendet die ganze Einblendung aus. `pvpSchlagabtausch`
   raeumt sie am Ende GENAU DIESER Animation weg — ein Zeitgeber mit derselben
   Zahl daneben liefe beim naechsten Anfassen auseinander. */
@keyframes schlagAus { 0%, 82% { opacity: 1; } 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) { .pvp-schlag { display: none; } }

/* ===========================================================================
   DER MÜNZWURF — wer beginnt, ist keine Nebensache

   Hier stand eine Zeile Text: „Ausgelost — du beginnst". Sie war richtig und
   wurde nicht gelesen. Der Wählende sucht sich seine Stärke aus, der andere
   bekommt dafür Wachsamkeit; eine Münze, die sich einmal dreht und auf einer
   Seite liegen bleibt, sagt in einer Bewegung beides — dass es ausgelost wurde
   und wie es ausging.

   NUR `transform`: die Bühne hält in diesem Moment zwei vollständige Karten
   mit ihren Folien, und alles andere läuft auf dem Hauptthread.
   =========================================================================== */
.pvp-losung {
  display: flex; align-items: center; gap: var(--s3);
  margin: 0 0 var(--s3); padding: var(--s2) 0;
}
.losung-muenze {
  position: relative; flex: 0 0 38px; width: 38px; height: 38px;
  transform-style: preserve-3d;
  animation: muenzeWurf 1.1s cubic-bezier(.25,.9,.3,1) both;
}
.pvp-losung.gegner .losung-muenze { animation-name: muenzeWurfGegner; }
.muenze-seite {
  position: absolute; inset: 0; display: grid; place-items: center;
  border-radius: 50%; backface-visibility: hidden;
  font-style: normal; font-size: 11px; font-weight: 900;
  letter-spacing: var(--track-label);
}
.muenze-seite.du { background: var(--lime); color: #0c1108; }
.muenze-seite.er { background: var(--flaeche-3); color: var(--ink-2); transform: rotateY(180deg); }
.losung-text { display: grid; gap: 1px; }
.losung-text em {
  font-style: normal; font-size: var(--t-micro); font-weight: 900;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.losung-text strong { font-size: var(--t-small); font-weight: 850; }
/* Zwei volle Umdrehungen und ein halber Rest: die Münze landet auf „DU"
   beziehungsweise auf „ER". Der Wurf selbst ist auf dem Server gefallen —
   diese Drehung zeigt das Ergebnis, sie bestimmt es nicht. */
@keyframes muenzeWurf {
  from { transform: rotateY(0) translateY(-14px); }
  60% { transform: rotateY(540deg) translateY(0); }
  to { transform: rotateY(720deg); }
}
@keyframes muenzeWurfGegner {
  from { transform: rotateY(0) translateY(-14px); }
  60% { transform: rotateY(540deg) translateY(0); }
  to { transform: rotateY(900deg); }
}
@media (prefers-reduced-motion: reduce) {
  .losung-muenze { animation: none; }
  .pvp-losung.gegner .losung-muenze { transform: rotateY(180deg); }
}

/* ===========================================================================
   „HIER MUSS ETWAS GEWÄHLT WERDEN"

   Aus dem Feld: die Karte lag da, und dass ihre acht Zahlen die Bedienfläche
   sind, stand nirgends. Der Ring läuft nur, solange NICHTS gewählt ist — er
   ist eine Aufforderung, kein Dauerzustand. Sobald ein Wert liegt, hört er
   auf; ein Blinken, das nie endet, wird nach zwei Sekunden zu Rauschen.
   =========================================================================== */
.karte.pvp-waehlbar .kwerte .kw { transition: box-shadow .18s ease; }
.karte.pvp-waehlbar:not(:has(.kw.gewaehlt)) .kwerte .kw {
  animation: wertePuls 1.9s ease-in-out infinite;
}
@keyframes wertePuls {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(198, 240, 90, .18); }
  50% { box-shadow: inset 0 0 0 1px rgba(198, 240, 90, .62); }
}
@media (prefers-reduced-motion: reduce) {
  .karte.pvp-waehlbar:not(:has(.kw.gewaehlt)) .kwerte .kw {
    animation: none; box-shadow: inset 0 0 0 1px rgba(198, 240, 90, .4);
  }
}

/* DER SPIELBARE ANGRIFF. Die Bindung war da und funktionierte — sie sah nur
   nach nichts aus, und eine Zeile, die aussieht wie beschreibender Text,
   tippt niemand an. Markiert wird NUR der, der auf den Wert zählt, der gerade
   auf dem Tisch liegt; alle drei zu markieren hieße, zwei davon versprechen
   etwas, das in dieser Runde nichts bewirkt. */
.karte .kang .a.spielbar {
  box-shadow: inset 0 0 0 1px var(--lime, #c6f05a);
  border-radius: 6px;
  animation: angriffPuls 1.9s ease-in-out infinite;
}
@keyframes angriffPuls { 0%, 100% { opacity: .78; } 50% { opacity: 1; } }
.karte .kang .a.gewaehlt { box-shadow: inset 0 0 0 2px var(--lime, #c6f05a); border-radius: 6px; }
@media (prefers-reduced-motion: reduce) { .karte .kang .a.spielbar { animation: none; } }

/* Der namentliche Hinweis in der Zugleiste. „Angriff antippen" ist ein Hinweis
   auf etwas Unbestimmtes; „Alarmruf +6" sagt, worauf man tippen soll und was
   es bringt. */
.pvp-angebot b { color: var(--gold); }

/* DER KRAFTABZUG. Ein Angriff ist die einzige Ausgabe im ganzen Kampf; bleibt
   sie unsichtbar, wirkt der Vorrat wie eine Zierzahl. */
.pvp-kraft { position: relative; }
.pvp-kraft.sinkt { color: var(--gold); }
.kraft-abzug {
  position: absolute; left: 100%; top: -2px; margin-left: 6px;
  font-style: normal; font-weight: 900; font-size: var(--t-micro); color: var(--gold);
  animation: kraftWeg 1.1s ease forwards;
}
@keyframes kraftWeg {
  0% { opacity: 0; transform: translateY(4px); }
  22% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: translateY(-12px); }
}


.pvp-gegnerseite { display: grid; grid-template-columns: 62px 1fr; gap: var(--s3);
  align-items: center; width: 100%; padding: 10px 12px;
  border: 1px solid rgba(255,255,255,.14); border-radius: var(--r-kante);
  background: rgba(255,255,255,.04); cursor: pointer;
  transition: translate .16s ease, border-color .16s ease; }
.pvp-gegnerseite:hover { border-color: rgba(255,255,255,.3); }
.pvp-gegnerseite:active { translate: 0 2px; }
.pvp-gegnerkarte > .karte { width: 100%; }
.pvp-gegnerfakten { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pvp-gegnerfakten strong { font-size: var(--t-body); font-weight: 850;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-gegnerfakten span { font-size: var(--t-micro); color: #8d968c; }
.pvp-gegnerfakten em { font-size: var(--t-micro); font-style: normal; color: var(--gold);
  letter-spacing: var(--track-label); text-transform: uppercase; font-weight: 800; }

/* Die eigene Karte. Breite gedeckelt, damit Karte UND Zugleiste zusammen auf
   ein Handy passen: 268 px Karte sind 374 px hoch, und erst darunter blieb
   die zweite Wertezeile frei von der Leiste. Mit 300 px verdeckte sie vier
   der acht Werte — genau die, auf denen gewaehlt wird. */
.pvp-meine { width: min(100%, 268px); }
.pvp-meine > .karte { width: 100%; }
.pvp-ersatzkarte { aspect-ratio: 63/88; display: grid; place-items: center;
  border: 1px solid rgba(255,255,255,.16); border-radius: var(--r-card);
  color: #8d968c; font-size: var(--t-small); text-align: center; padding: 8px; }

/* --- Die Werteleiste der Karte wird zur Tastatur ---------------------------
   Die Felder sind auf der Karte 21 px hoch; das ist zum Lesen richtig und zum
   Tippen zu wenig. Im Kampf bekommen sie Polster — das Bildfeld gibt die
   Hoehe ab, die Karte behaelt ihr Format. */
.pvp-meine .kbild { min-height: 0; }
.pvp-meine .kwerte .kw { padding: 2.4cqw .8cqw; border-radius: 1.6cqw;
  transition: background .12s ease; }
.pvp-meine.pvp-waehlbar .kwerte .kw[data-wert] { cursor: pointer; }
.pvp-waehlbar .kwerte .kw[data-wert]:hover { background: rgba(255,255,255,.07); }
/* Der gewaehlte Wert. Die Zahl darin ist die SUMME, nicht der Grundwert —
   genau daran ist es im Feld gescheitert: der Angriff versprach acht Punkte,
   und auf der Karte stand weiter die Fuenf. */
.pvp-meine .kwerte .kw.gewaehlt { background: color-mix(in srgb, var(--lime) 22%, transparent);
  box-shadow: inset 0 0 0 .5cqw color-mix(in srgb, var(--lime) 60%, transparent); }
.pvp-meine .kwerte .kw.gewaehlt .l { color: rgba(244,245,239,.8); }
.pvp-meine .kwerte .kw .z.erhoeht { color: var(--lime); }
/* Der geforderte Wert in der Antwortphase: er steht fest, also wird er
   markiert statt angeboten. */
.pvp-meine .kwerte .kw.gefordert:not(.gewaehlt) {
  box-shadow: inset 0 0 0 .5cqw color-mix(in srgb, var(--gold) 55%, transparent); }

/* Die Angriffe auf der Karte sind Knoepfe. Sie tragen ihren Leitwert bereits
   („zaehlt auf Drohen") — antippen waehlt deshalb Angriff UND Wert. */
.pvp-meine .kang .a { border-radius: 1.6cqw; padding: .6cqw .8cqw;
  transition: background .12s ease; }
.pvp-meine.pvp-waehlbar .kang .a[data-angriff]:not(.verbraucht):not(.stumm) { cursor: pointer; }
.pvp-meine .kang .a.gewaehlt { background: color-mix(in srgb, var(--gold) 26%, transparent); }
.pvp-meine .kang .a.verbraucht { opacity: .3; text-decoration: line-through; }
/* Zu teuer oder in dieser Runde ohne Wirkung. Sichtbar bleiben muss er
   trotzdem: die Entscheidung „jetzt oder sparen" laesst sich sonst nicht
   treffen. */
.pvp-meine .kang .a.stumm { opacity: .42; }

/* --- Die Zugleiste --------------------------------------------------------
   Sie ersetzt das Kachelraster. Gewaehlt wird auf der Karte; hier steht die
   Rechnung und die Bestaetigung. Vorher ging der Zug ab, sobald man einen
   Wert antippte — Angriff und Kraft liessen sich danach nicht mehr dazulegen.
--------------------------------------------------------------------------- */
/* DIE LEISTE KLEBT UNTEN. Gemessen ist die Buehne mit Karte 974 px hoch, ein
   Handy hat 812 — der Bestaetigungsknopf lag unter der Falz, und wer die Karte
   ansieht, sieht ihn nicht. Sticky statt fest: beim Scrollen nach oben gibt sie
   den Verlauf frei. */
.pvp-leiste { display: flex; flex-direction: column; gap: var(--s2);
  position: sticky; bottom: calc(-1 * env(safe-area-inset-bottom)); z-index: 2;
  padding: var(--s2) 0 calc(var(--s2) + env(safe-area-inset-bottom));
  background: linear-gradient(180deg, #06080700, #060807 18%); }
.pvp-stand { padding: 11px 13px; border-left: 2px solid rgba(255,255,255,.2);
  background: rgba(255,255,255,.04); display: flex; flex-direction: column; gap: 3px; }
.pvp-stand.dran { border-left-color: var(--lime); }
.pvp-stand strong { font-size: 1.3rem; font-weight: 900; letter-spacing: -.02em; }
.pvp-stand strong.klein { font-size: var(--t-body); font-weight: 850; }
.pvp-stand em { font-size: var(--t-micro); color: #8d968c; font-style: normal; }
/* JEDE ZAHL MIT IHRER HERKUNFT. „Drohen 5 + Alarmruf 6 = 11" statt einer
   nackten Summe — im Feld stand eine 17 auf dem Schirm, und der Weg dorthin
   war nirgends nachlesbar. */
.pvp-rechnung { font-size: var(--t-small); color: #c3c9bf;
  font-variant-numeric: tabular-nums; }
.pvp-rechnung b { font-size: 1.15rem; font-weight: 900; color: var(--lime); }
.pvp-hinweis { font-size: var(--t-small); color: #7f887e; }

/* Nur sichtbar, wenn die Karte nicht geladen ist. Ohne sie waere der Kampf in
   genau diesem Fall unbedienbar. */
.pvp-ersatzwahl { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.pvp-ersatzwahl .pvp-label { grid-column: 1/-1; }
.pvp-ersatzwahl button { padding: 10px; border: 1px solid rgba(255,255,255,.18);
  border-radius: var(--r-kante); background: transparent; color: #c3c9bf; font: inherit;
  font-size: var(--t-micro); cursor: pointer; text-align: left; }
.pvp-ersatzwahl button.an { border-color: var(--lime); color: var(--lime); }
.pvp-ersatzwahl button b { font-size: var(--t-body); float: right; }

/* DER KRAFTVORRAT — eine Auskunft, kein Regler (06.08.2026).
   Hier stand ein Zaehler mit - und +: blanke Kraft an JEDEM Wert. Er hob die
   Entscheidung auf, um die es geht — ein Angriff haengt an genau einem Wert,
   ein Regler an allen. Was bleibt, ist die Zahl: wie viele Angriffe noch
   drin sind. */
.pvp-vorrat { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.pvp-vorrat b { font-size: 1.15rem; font-variant-numeric: tabular-nums; min-width: 1.2em;
  text-align: center; }
.pvp-vorrat em { font-size: var(--t-micro); color: var(--ink-3); font-style: normal;
  margin-left: auto; }

.pvp-senden { padding: 14px; border: 0; border-radius: var(--r-kante);
  background: #e9ecdf; color: var(--night); box-shadow: inset 0 -2px 0 var(--lime);
  font: inherit; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; cursor: pointer; }
.pvp-senden:disabled { opacity: .4; cursor: default; box-shadow: none; }
/* Bestaetigen und Aufgeben in EINER Zeile. Untereinander kosteten sie 100 px
   in einer Leiste, die auf dem Handy ohnehin ueber der Karte liegt. */
.pvp-schluss { display: grid; grid-template-columns: 1fr auto; gap: var(--s2); }
.pvp-schluss .pvp-zu { white-space: nowrap; }

/* --- Die Gegnerkarte gross ------------------------------------------------
   Antippbar, aber ohne Zahlen: „die Möglichkeit hat, auf die Karte vom Gegner
   zu tippen, aber man sieht halt nicht die Stats". Was hier steht, liegt
   ohnehin auf dem Tisch — plus der Verlauf, denn was gefallen ist, ist
   gefallen.
--------------------------------------------------------------------------- */
#pvpLupe { position: fixed; inset: 0; z-index: 80; display: grid; place-items: center;
  overflow-y: auto; padding: 20px 16px; background: rgba(4,6,5,.86);
  backdrop-filter: blur(6px); }
.pvp-lupe-innen { width: 100%; max-width: 320px; display: flex; flex-direction: column;
  gap: var(--s2); color: #e9ecdf; }
.pvp-lupe-karte { width: min(100%, 240px); align-self: center; }
.pvp-lupe-karte > .karte { width: 100%; }
.pvp-lupe-innen h3 { font-size: 1.3rem; font-weight: 900; letter-spacing: -.02em; }
.pvp-lupe-fakten { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2); }
.pvp-lupe-fakten dt { font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3); }
.pvp-lupe-fakten dd { font-size: var(--t-body); font-weight: 850;
  font-variant-numeric: tabular-nums; }
.pvp-lupe-verlauf { display: flex; flex-direction: column; gap: 3px;
  font-size: var(--t-small); list-style: none; }
.pvp-lupe-verlauf b { font-variant-numeric: tabular-nums; }
.pvp-lupe-verlauf em { font-size: var(--t-micro); color: var(--gold); font-style: normal; }
.pvp-lupe-zu { padding: 12px; border: 1px solid rgba(255,255,255,.2); border-radius: var(--r-kante);
  background: transparent; color: #c3c9bf; font: inherit; font-size: var(--t-micro);
  font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase;
  cursor: pointer; }

/* `#6e776f` war eine der fuenf Fast-Kopien von `--ink-3`, die das Systemblatt
   abloesen sollte — sie stand mit 4,27 : 1 knapp unter der Grenze und wurde
   erst sichtbar, als die Kampfbuehne in den Befundlauf kam. Der Tokenwert ist
   gerechnet und geprueft; eine getippte Farbe daneben ist es nie. */
.pvp-label { font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3); }
.pvp-angriffe { display: flex; flex-direction: column; gap: 6px; }
.pvp-angriff { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: 9px 11px; border: 1px solid rgba(255,255,255,.16); border-radius: var(--r-kante);
  background: transparent; color: #c3c9bf; font: inherit; cursor: pointer; text-align: left; }
.pvp-angriff b { font-size: var(--t-small); font-weight: 850; }
.pvp-angriff span { font-size: var(--t-micro); color: #7f887e; }
.pvp-angriff.an { border-color: var(--gold); background: color-mix(in srgb, var(--gold) 12%, transparent); }
.pvp-angriff.an b { color: var(--gold); }

.pvp-letzte { padding: 11px 12px; border-left: 2px solid rgba(255,255,255,.2);
  background: rgba(255,255,255,.04); display: flex; flex-direction: column; gap: 5px; }
.pvp-letzte.sieg { border-left-color: var(--lime); }
.pvp-letzte.niederlage { border-left-color: var(--danger); }
.pvp-gegenueber { display: flex; align-items: baseline; gap: var(--s2);
  font-size: var(--t-small); flex-wrap: wrap; }
.pvp-gegenueber b { font-size: 1.15rem; font-weight: 900; font-variant-numeric: tabular-nums; }
.pvp-gegenueber em { font-size: var(--t-micro); color: #7f887e; font-style: normal; }
.pvp-gegen { color: var(--ink-3); font-size: var(--t-micro); text-transform: uppercase;
  letter-spacing: var(--track-label); font-weight: 800; }

.pvp-ende { text-align: center; padding: var(--s5) 0; }
.pvp-ende strong { display: block; font-size: 1.8rem; font-weight: 900; letter-spacing: -.03em; }
.pvp-ende.sieg strong { color: var(--lime); }
.pvp-ende.niederlage strong { color: var(--danger); }
.pvp-ende span { font-size: var(--t-small); color: #7f887e; }
/* Die Beute ist der Grund, in die Wildnis zu gehen — sie steht deshalb im
   Akzent und nicht in derselben Grauzeile wie die Rundenbilanz. */
.pvp-beute {
  display: block; margin-top: var(--s3); font-style: normal;
  font-size: var(--t-body); font-weight: 800; color: var(--lime);
}
.pvp-beute.leer { color: var(--ink-3); font-weight: 600; font-size: var(--t-small); }
.pvp-grund {
  display: block; margin-top: var(--s2); font-style: normal;
  font-size: var(--t-small); color: var(--ink-2);
}
.pvp-verlust {
  display: block; margin-top: var(--s3); font-style: normal;
  font-size: var(--t-body); font-weight: 800; color: var(--rost);
}
/* Der Einsatz steht IM Einladungskasten, nicht darunter. Rot heisst: hier
   geht etwas weg. Der Freundschaftskampf bekommt dieselbe Zeile in Grau —
   die Abwesenheit eines Hinweises wird nicht gelesen. */
.pvp-einladung-einsatz {
  display: block; margin: 4px 0; font-weight: 800; color: var(--rost) !important;
}
.pvp-einladung-einsatz.freundlich { color: var(--ink-3) !important; font-weight: 600; }
.pvp-lohn {
  display: block; margin-top: var(--s2); font-style: normal;
  font-size: var(--t-body); font-weight: 800; color: var(--gold);
}

/* WER DA IST. Ein gruener Punkt am Kuerzel — die Zeile selbst bleibt in
   Schriftfarbe, sonst konkurriert der Zustand mit dem Namen. */
.opponent-avatar.anwesend { position: relative; }
.opponent-avatar.anwesend::after {
  content: ''; position: absolute; right: -2px; bottom: -2px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--lime); border: 2px solid var(--flaeche);
}
.tier-row.offline { opacity: .55; cursor: not-allowed; }
.tier-row.offline .pvp-fordern-marke { color: var(--ink-3); }
.battle-grund { display: block; font-size: var(--t-micro); color: var(--ink-3); font-style: normal; }
.battle-lohn {
  font-size: var(--t-small); font-weight: 900; color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.pvp-warte { text-align: center; color: #7f887e; font-size: var(--t-small); padding: var(--s5) 0; }

.pvp-zu { padding: 12px; border: 1px solid rgba(255,255,255,.2); border-radius: var(--r-kante);
  background: transparent; color: #c3c9bf; font: inherit; font-size: var(--t-micro);
  font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase;
  cursor: pointer; }

.kartenplatz {
  cursor: pointer;
  /* Die Karte traegt ihre eigene Perspektive. Ein `transform` hier wuerde
     einen zweiten Stapelkontext aufmachen und ihre 3-D-Wirkung flachlegen —
     `translate` als eigene Eigenschaft tut das nicht. */
  transition: translate .16s ease;
}
.kartenplatz:active { translate: 0 2px; }
.kartenplatz:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
/* In der Wischreihe traegt der Platz eine feste Breite, im Raster die des
   Rasterfelds. */
.kartenreihe .kartenplatz { flex: none; width: 172px; scroll-snap-align: start; }
/* NICHT `.species-tile` — den Namen traegt bereits die Katalogkachel weiter
   unten. Ein zweiter Block unter demselben Namen erbt deren Regeln (44 px
   hoch, zentrierte 0,58-rem-Schrift, zweizeilig geklemmt) und sieht auf den
   ersten Blick nur "irgendwie falsch" aus, ohne dass eine Zeile davon hier
   steht. */
.collection-tile {
  position: relative; display: flex; flex-direction: column;
  padding: 0; border: 1px solid var(--line-hair); background: var(--paper);
  color: var(--ink); text-align: left; cursor: pointer; overflow: hidden;
  transition: transform .14s ease, box-shadow .14s ease;
}
.collection-tile:active { transform: scale(.985); }
.collection-tile.is-new {
  border-color: color-mix(in srgb, var(--tile-accent) 62%, var(--line-dark));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--tile-accent) 22%, transparent),
    0 10px 28px rgba(16,20,17,.11);
}
/* Seltenheit als Kante oben. Farbe traegt hier Information und steht deshalb
   zusaetzlich als Wort in der Kachel — Farbe allein trennt die fuenf Stufen
   fuer Farbenblinde nicht. */
.collection-tile::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 3px;
  background: var(--tile-accent, transparent); z-index: 2;
}
.collection-tile-photo-frame {
  position: relative; display: block; width: 100%; aspect-ratio: 4 / 3;
  overflow: hidden; background: var(--paper-2);
}
.collection-tile-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; background: var(--paper-2);
  filter: saturate(.92) contrast(1.04);
}
.collection-tile-photo.placeholder {
  display: grid; place-items: center; font-weight: 950; font-size: 2rem;
  color: rgba(16,20,17,.22);
}
.collection-tile-copy { display: grid; gap: 2px; padding: var(--s2) var(--s3) var(--s3); min-width: 0; }
.collection-tile-copy strong {
  font-size: var(--t-body); font-weight: 800; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.collection-tile-copy em {
  font-family: Georgia, serif; font-size: var(--t-micro); font-style: italic;
  color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.collection-tile-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s2);
  margin-top: var(--s1);
  font-size: var(--t-micro); letter-spacing: var(--track-label); font-weight: 850;
  text-transform: uppercase;
}
.collection-tile-foot .rarity { color: var(--tile-accent, var(--ink-3)); }
.collection-tile-foot .state { color: var(--ink-3); font-weight: 700; letter-spacing: .06em; text-transform: none; }
/* Stapelzahl. Nur ab zwei Exemplaren — eine "1" auf jeder Kachel waere Rauschen
   und genau die Zahl, die nichts bedeutet. */
.stack-badge {
  position: absolute; top: var(--s2); right: var(--s2); z-index: 2;
  min-width: 26px; padding: 3px 7px; border-radius: var(--r-soft);
  background: rgba(16,20,17,.82); color: var(--paper);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: .04em;
  text-align: center; font-variant-numeric: tabular-nums;
  backdrop-filter: blur(3px);
}
/* „heute" auf der Kachel: Tinte statt Neon.
   Es sass als Lime-Klotz auf dem Foto und war damit das Bunteste im ganzen
   Raster — lauter als die Seltenheit, die dort tatsaechlich etwas bedeutet. */
.collection-new-badge {
  position: absolute; top: var(--s2); left: var(--s2); z-index: 3;
  padding: 4px 7px; border: 0;
  background: rgba(16,20,17,.82); color: var(--paper);
  box-shadow: inset 0 -2px 0 var(--lime);
  font-size: var(--t-micro); font-weight: 950; letter-spacing: .09em;
  text-transform: uppercase;
}
.collection-tile.is-new .tile-marks { top: 38px; }
/* Heute schon gefangen: die Art ist bis Mitternacht gesperrt. Als Eckmarke,
   nicht als weitere Textzeile — sie betrifft den naechsten Scan, nicht das
   Exemplar. */
/* NICHT `.tile-flag` — den Namen belegt die Katalogkachel weiter unten, und
   weil sie spaeter im Dokument steht, gewinnt sie bei gleicher Spezifitaet.

   Die Marken stehen GESTAPELT, nicht als eine. Vorher trug die Kachel genau
   ein Kennzeichen und "heute" gewann per else-Zweig gegen "unterwegs" — eine
   Art, die heute gefangen UND auf Expedition ist, sah aus wie jede andere.
   Genau das war die gemeldete Unzuverlaessigkeit. */
.tile-marks {
  position: absolute; top: var(--s2); left: var(--s2); z-index: 2;
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
}
/* Marke auf dem Foto: Tinte mit Lime-Kante. Als Lime-Flaeche war „heute" das
   Bunteste im ganzen Raster — lauter als die Seltenheitsfarbe daneben, die
   dort tatsaechlich etwas bedeutet. */
.tile-mark {
  padding: 3px 6px; border-radius: var(--r-kante);
  background: rgba(16,20,17,.82); color: var(--paper);
  box-shadow: inset 0 -2px 0 var(--lime);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: .04em;
  white-space: nowrap;
}
.tile-mark.locked { background: rgba(255,208,106,.94); }
.rarity-COMMON { color: #586158; }
.rarity-UNCOMMON { color: #387d58; }
.rarity-RARE { color: #2879a8; }
.rarity-EPIC { color: #7d4ab4; }
.rarity-MYTHIC { color: #ad7422; }

.mission-row, .trade-row {
  padding: var(--s5) 0; border-top: 1px solid var(--line-hair);
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--s4);
}
/* Die Abschlusslinie NUR, wenn oben eine Zeile steht. `:last-child` traf auch
   den leeren Zustand, der als einziges Kind in der Liste liegt — dann stand
   dort eine Linie ueber und eine unter einem Satz, ohne dass es etwas zu
   trennen gab. */
.mission-row:last-child, .trade-row:last-child { border-bottom: 1px solid var(--line-hair); }
.mission-list > .empty-state, .trade-list > .empty-state { border: 0; }
.mission-row h3, .trade-row h3 {
  margin: 0 0 var(--s1); font-size: var(--t-title); font-weight: 850; letter-spacing: -.035em;
}
.mission-row p, .trade-row p {
  margin: 0; color: var(--ink-3); line-height: 1.5; font-size: var(--t-small); max-width: 620px;
}
/* Anforderungen als Begriffsreihe mit Trennlinie — dieselbe Sprache wie die
   Traits am Tier, weil es dieselbe Sache ist. */
.requirement-line {
  margin-top: var(--s3); display: flex; flex-wrap: wrap; gap: 5px;
  font-size: var(--t-micro); letter-spacing: .08em; font-weight: 850; color: var(--ink-2);
}
.requirement-line span {
  padding: 3px var(--s2); border: 1px solid var(--line-hair);
  background: rgba(16,20,17,.04); border-radius: var(--r-soft);
}
.action-square {
  align-self: center; min-width: 96px; cursor: pointer;
  border: 1px solid var(--ink); background: transparent; color: var(--ink);
  padding: var(--s3) var(--s4); font-size: var(--t-micro);
  letter-spacing: var(--track-label); font-weight: 900;
  transition: background .15s ease, color .15s ease;
}
.action-square.primary { background: var(--ink); color: var(--paper); }
/* Handlung oben, Lohn darunter — der Knopf sagt sonst nur, was er einbringt,
   aber nicht, was er tut. */
.action-square strong, .action-square em { display: block; font-style: normal; }
.action-square strong { text-transform: uppercase; letter-spacing: var(--track-label); }
.action-square em { margin-top: 3px; font-size: var(--t-micro); font-weight: 800; opacity: .72; }
.action-square:active { background: var(--ink); color: var(--paper); }
.action-square:disabled { opacity: .32; cursor: not-allowed; }
/* Laufende Expedition. Der gruene Punkt sagt "aktiv" schon vor dem Lesen. */
.assignment-line {
  margin-top: var(--s3); display: flex; align-items: center; gap: var(--s2);
  font-size: var(--t-small); color: #3c704e; font-weight: 750;
}
.assignment-line::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: #3c704e; flex: none;
}
/* Tauschvorschau. Statt einer Kachel mit Anfangsbuchstabe (die genau nichts
   aussagt) steht hier, was gegen was geht — mit einem Pfeil dazwischen. Die
   Richtung ist die Information, nicht ein Platzhalterbild. */
.trade-preview {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--s3); margin-top: var(--s4);
  padding: var(--s3) 0; border-top: 1px solid var(--line-hair);
}
.trade-preview .give, .trade-preview .get { display: grid; gap: 2px; min-width: 0; }
.trade-preview .get { text-align: right; }
.trade-preview strong {
  font-size: var(--t-body); font-weight: 800;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trade-preview span {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  font-weight: 850; color: var(--ink-3);
}
/* Spieler-Angebot auf dem Brett: Kern und Aktion.
   Das Foto ist nicht mehr eine eigene Kachel daneben, sondern steckt in der
   `.tier-row` — dieselbe Karte wie in Arena und Auswahl, mit denselben
   Werten. Vorher lagen hier ein Foto und drei Textzeilen, und was das Tier
   taugt, stand nirgends. */
.trade-row { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
.trade-body { display: grid; gap: var(--s2); min-width: 0; }
/* Direktangebot: der Wunsch ist ein bestimmtes Exemplar und steht als eigene
   Karte darunter, eingerueckt und mit Pfeilmarke, damit die Leserichtung
   "ich gebe das, ich will das" ohne Beschriftung erkennbar bleibt. */
.trade-wunsch { padding-left: var(--s4); border-left: 2px solid var(--line-dark); }
.trade-pfeil {
  display: block; margin-bottom: 3px; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.trade-row.direkt { border-left: 3px solid var(--lime); padding-left: var(--s3); }
/* Ein Angebot AN MICH verlangt eine Antwort — alles andere darf stehen
   bleiben. Deshalb der einzige farbige Grund in dieser Liste. */
.trade-row.an-mich { background: rgba(120,160,90,.10); padding-right: var(--s3); }
.trade-frist { font-size: var(--t-micro); color: var(--ink-3); font-weight: 700; }
/* min-width: 0 auf jedem flex:1-Element — sonst schrumpfen sie nie unter ihre
   Inhaltsbreite und die Leiste laeuft auf 340 px ueber (Regel 1 im Kopf). */
.trade-buttons { display: flex; flex-wrap: wrap; gap: var(--s2); align-self: center; }
.trade-buttons .action-square { flex: 1 1 auto; min-width: 0; }
.fremd-bild {
  width: 100%; max-height: 220px; object-fit: cover; display: block;
  border-radius: var(--r-card); margin-bottom: var(--s3);
}
.trade-actions { display: flex; flex-wrap: wrap; gap: var(--s2); }
.trade-actions .action-square { flex: 1 1 160px; min-width: 0; }
.trade-body h3 em { font-style: normal; font-size: var(--t-small); font-weight: 700; color: var(--ink-3); }
.trade-wanted strong { color: var(--ink); }
.trade-create { margin: 0 0 var(--s4); }

/* Wunschauswahl im Picker-Dialog. */
.wanted-groups { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
.ghost-chip {
  padding: var(--s2) var(--s3); border-radius: var(--r-soft); cursor: pointer;
  border: 1px solid var(--line-dark); background: none;
  font-family: inherit; font-size: var(--t-small); font-weight: 700; color: var(--ink);
}
.ghost-chip:hover { border-color: var(--ink); }
.wanted-results { display: grid; gap: var(--s1); margin-top: var(--s2); }

.section-rule { height: 1px; background: rgba(255,255,255,.18); margin: 22px 0; }
.dark-rule { background: var(--line-dark); max-width: 900px; margin-left: auto; margin-right: auto; }
.section-label { margin: 0 0 12px; font-size: .63rem; letter-spacing: .18em; font-weight: 900; color: rgba(255,255,255,.58); }
.dark-label { color: #70766e; max-width: 900px; margin-left: auto; margin-right: auto; }
.plain-list { display: grid; }
.plain-list > div { display: flex; justify-content: space-between; gap: 20px; padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,.12); font-size: .8rem; }
.plain-list > div:last-child { border-bottom: 0; }
.plain-list span { color: rgba(255,255,255,.66); }
.plain-list strong { text-align: right; }
.dark-list { max-width: 900px; margin: 0 auto; }
.dark-list > div { border-bottom-color: var(--line-dark); }
.dark-list span { color: #747a73; }

.view-result { background: #101411; color: white; }
.result-hero { position: relative; min-height: 54dvh; overflow: hidden; background: #252c25; }
.result-hero img { width: 100%; height: 58dvh; min-height: 430px; object-fit: cover; display: block; }
.result-image-fallback {
  width: 100%; height: 58dvh; min-height: 430px; display: grid; place-content: center;
  justify-items: center; gap: var(--s3); padding: var(--s6); color: rgba(255,255,255,.7);
  text-align: center; background:
    linear-gradient(135deg, rgba(255,255,255,.035) 25%, transparent 25%) 0 0 / 18px 18px,
    #202720;
}
.result-image-fallback[hidden] { display: none; }
.result-image-fallback svg { width: 36px; height: 36px; fill: currentColor; opacity: .7; }
.result-image-fallback span { max-width: 260px; font-size: var(--t-small); line-height: 1.5; }
.result-hero.no-image { min-height: 320px; }
.result-hero.no-image .result-image-fallback {
  position: absolute; z-index: 2;
  top: calc(18px + env(safe-area-inset-top)); right: 16px;
  width: min(230px, calc(100% - 92px)); height: auto; min-height: 44px;
  display: flex; align-items: center; justify-content: flex-start; gap: 10px;
  padding: 9px 11px; text-align: left;
  border: 1px solid rgba(255,255,255,.22);
  background: rgba(16,20,17,.76); backdrop-filter: blur(8px);
}
.result-hero.no-image .result-image-fallback svg { width: 22px; height: 22px; flex: 0 0 auto; }
.result-hero.no-image .result-image-fallback span { font-size: .7rem; line-height: 1.3; }
.result-gradient { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,.25), transparent 30%, rgba(16,20,17,.18) 55%, #101411 100%); }
.back-button { position: absolute; top: calc(18px + env(safe-area-inset-top)); left: 16px; width: 44px; height: 44px; display: grid; place-items: center; color: white; background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.35); cursor: pointer; backdrop-filter: blur(8px); }
.back-button svg { width: 24px; }
.result-title { position: absolute; left: 20px; right: 20px; bottom: 16px; }
.result-title span { color: var(--lime); font-size: .64rem; letter-spacing: .18em; font-weight: 900; }
.result-title #resultStatus[data-verdict="retry"] { color: #e8c36a; }
.result-title #resultStatus[data-verdict="rejected"] { color: #ff9287; }
.result-title #resultStatus[data-verdict="unknown"] { color: rgba(255,255,255,.68); }
.result-title h1 { margin: 7px 0 3px; font-size: clamp(2.5rem, 11vw, 5rem); line-height: .88; letter-spacing: -.07em; }
.result-title em { font-family: Georgia, serif; color: rgba(255,255,255,.72); }
.result-body { max-width: 820px; margin: 0 auto; padding: 18px 20px calc(var(--nav-height) + 36px); }
.result-primary-line { display: grid; grid-template-columns: repeat(3, 1fr); }
.result-primary-line > div { padding: 8px 12px 8px 0; border-right: 1px solid rgba(255,255,255,.18); }
.result-primary-line > div:not(:first-child) { padding-left: 12px; }
.result-primary-line > div:last-child { border-right: 0; }
.result-primary-line span { display: block; font-size: .54rem; letter-spacing: .12em; color: rgba(255,255,255,.55); margin-bottom: 4px; }
.result-primary-line strong { font-size: .9rem; }
/* Traits. Keine Pillen, keine Sternchen — sie stehen als gesetzte Begriffe
   nebeneinander, getrennt durch eine feine Linie. Das liest sich wie ein
   Bestimmungsschlüssel und nicht wie eine Tag-Wolke. */
/* MERKMALE ALS PLAETTCHEN, NICHT MIT TRENNSTRICH.
   Vorher trennte ein `border-left` die Eintraege, und `:first-child` nahm den
   ersten aus. Das trifft aber nur den ersten der GANZEN Liste — jeder
   Eintrag, der nach einem Umbruch eine neue Zeile beginnt, behaelt seinen
   Strich und steht dann mit einem fuehrenden Trenner da. "Welches Element
   beginnt eine umgebrochene Zeile" laesst sich in CSS nicht ausdruecken; der
   Fehler ist mit einem Trennstrich also nicht behebbar, nur vermeidbar.
   Plaettchen brauchen keinen Trenner. Bei zehn Merkmalen sind sie ausserdem
   lesbarer als eine durchgehende Kette. */
.trait-line { display: flex; flex-wrap: wrap; gap: 5px; }
/* KIND-Selektor mit Ausnahme, nicht Nachfahren-Selektor.
   In dieser Zeile steht seit dem 29.07. auch die Werte-Zeile, und die besteht
   selbst aus <span>. Mit `.trait-line span` haetten ihre Zahlen das Aussehen
   von Merkmalen bekommen — zwei verschiedene Dinge, die gleich aussehen. */
.trait-line > span:not(.werte-zeile) {
  font-size: var(--t-small); font-weight: 700; letter-spacing: .02em;
  padding: 3px var(--s2); border: 1px solid var(--line-hair);
  background: rgba(16,20,17,.04); border-radius: var(--r-soft);
}
/* Eigene Zeile: die Werte sind eine Aussage ueber das Tier, die Merkmale eine
   andere. Nebeneinander laesen sie sich wie eine Liste. */
.trait-line > .werte-zeile { flex: 0 0 100%; margin: 0 0 var(--s2); }
/* Auf dunklem Grund (Fang-Moment) kehren sich Kante und Fuellung um. */
.reveal-traits span { border-color: rgba(255,255,255,.16); background: rgba(255,255,255,.07); }
.primary-bar, .secondary-bar { width: 100%; min-height: 52px; border: 0; padding: 13px 16px; font-weight: 900; cursor: pointer; }
/* HAUPTWEG IST LIMETTE. Zweitweg ist STILL.
   ===========================================================================
   Hier stand: „Tinte, Lime nur als Kante unten — vollflaechig war dieser Knopf
   das auffaelligste Neon der App." Das galt fuer das Papierregister, wo
   `--ink` dunkel war: ein dunkler Knopf auf hellem Grund.

   Mit dem Nachtregister ist `--ink` hell, und aus dem Argument wurde sein
   Gegenteil — ZWEI handgrosse cremefarbene Bloecke uebereinander, und
   „Abbrechen" schrie dabei genauso laut wie die eigentliche Handlung. In
   jedem Dialog, auf jedem Bildschirm.

   Die Systemregel loest beides: `--lime` heisst AKTION und nichts sonst. Sie
   traegt hier dieselbe Rolle wie am Scan-Knopf in der Leiste und am
   Hauptknopf auf „Heute" — ein Nutzer lernt eine Farbe, nicht drei Muster.

   Der Zweitweg bekommt gar keine Flaeche. Ein Abbruch ist kein Angebot; er
   muss auffindbar sein, nicht angeboten. */
.primary-bar {
  background: var(--lime); color: var(--auf-akzent); margin-top: 26px;
  border-radius: var(--r-kante);
}
.secondary-bar {
  background: transparent; color: var(--ink-2); margin-top: var(--s2);
  border: 1px solid var(--line-dark); border-radius: var(--r-kante);
}
/* Es gab app-weit KEINE Regel fuer deaktivierte Hauptknoepfe. Ein gesperrter
   Knopf leuchtete in voller Limette und war das auffaelligste Element auf dem
   Schirm — bei etwas, das man gerade nicht tun kann. Limette heisst in dieser
   App "Handlung"; ohne moegliche Handlung darf sie nicht erscheinen. */
.primary-bar:disabled, .secondary-bar:disabled {
  background: var(--paper-2); color: var(--ink-3); cursor: not-allowed;
  border-color: var(--line-hair); box-shadow: none;
}

.profile-heading { align-items: center; }
.profile-orbit { width: 62px; height: 62px; border: 1px solid var(--ink); border-radius: 50%; display: grid; place-items: center; font-weight: 950; font-size: 1.4rem; }

/* Level als Ring: der Bogen zeigt den Fortschritt im laufenden Level, die Zahl
   das Level selbst. Ersetzt die Initiale — die trug keine Information. */
.level-orbit { position: relative; width: 62px; height: 62px; display: grid; place-items: center; }
.level-orbit svg { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); fill: none; }
.level-track { stroke: var(--line-dark); stroke-width: 3; }
.level-arc { stroke: var(--ink); stroke-width: 3; stroke-linecap: round; transition: stroke-dashoffset .5s ease; }
.level-orbit strong { font-weight: 950; font-size: 1.4rem; letter-spacing: -.04em; }

/* Artenbreite. Beschriftet in Arten statt Punkten, weil XP ausschliesslich aus
   neuen Arten kommen (docs/ECONOMY.md §4). */
.level-bar { max-width: 900px; margin: 0 auto 22px; }
.level-bar-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 9px; }
.level-bar-head span { display: inline-flex; align-items: center; gap: 7px; font-size: .62rem; letter-spacing: .13em; font-weight: 850; color: #727871; }
.level-bar-head strong { font-size: .76rem; font-variant-numeric: tabular-nums; }
.inline-icon { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
.level-track-line { height: 4px; border-radius: var(--r-soft); background: rgba(16,20,17,.12); overflow: hidden; }
.level-fill { height: 100%; background: var(--ink); border-radius: var(--r-soft); transition: width .5s ease; }
.level-hint { margin: 9px 0 0; font-size: .74rem; color: var(--ink-3); }

/* Tageskiste. Der Zustand steckt in der Grafik: bereit pulsiert der Ring,
   geleert steht die Karte still und nennt den Countdown. */
.chest-card {
  max-width: 900px; width: 100%; margin: 0 auto 20px; display: flex; align-items: center; gap: 15px;
  padding: 15px 17px; border: 1px solid var(--line-dark); border-radius: var(--r-card);
  background: transparent; color: var(--ink); text-align: left; cursor: pointer;
}
.chest-card.ready { border-color: var(--ink); background: rgba(16,20,17,.04); }
.chest-card:disabled { cursor: default; opacity: .62; }
.chest-visual { position: relative; width: 42px; height: 42px; display: grid; place-items: center; flex: none; }
.chest-icon { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.chest-pulse { position: absolute; inset: 0; border-radius: 50%; border: 1px solid var(--ink); opacity: 0; }
.chest-card.ready .chest-pulse { animation: chestPulse 2.4s ease-out infinite; }
@keyframes chestPulse {
  0% { transform: scale(.72); opacity: .55; }
  70% { transform: scale(1.18); opacity: 0; }
  100% { opacity: 0; }
}
.chest-copy { flex: 1; min-width: 0; }
.chest-copy strong { display: block; font-size: .92rem; margin-bottom: 3px; }
.chest-copy span { font-size: .74rem; color: var(--ink-3); }
.chest-chevron { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; opacity: .45; }
.chest-card:disabled .chest-chevron { opacity: .18; }

/* Oeffnungsmoment: dieselbe Buehne wie der Fang, nur ohne Zierrahmen. */
.chest-open-visual { display: grid; place-items: center; margin: 6px 0 18px; animation: revealUp .45s ease .1s both; }
.chest-open-icon {
  width: 74px; height: 74px; fill: none; stroke: var(--reveal-accent, #8fa894);
  stroke-width: 1.4; stroke-linejoin: round; stroke-linecap: round;
  filter: drop-shadow(0 0 18px color-mix(in srgb, var(--reveal-accent, #8fa894) 55%, transparent));
}

/* Abkuerzen einer Expedition. Symbol plus Preis — ohne Zahl waere unklar, was
   der Knopf kostet. */
.action-square.rush { display: inline-flex; align-items: center; gap: 7px; justify-content: center; }
.rush-icon { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
/* Profilzahlen. Wie der Streifen in der Sammlung: kein Gitter aus Kaesten,
   sondern eine Zeile Zahlen unter einer Linie. */
/* 88 px Spaltenbreite war zu knapp: "FORSCHUNGSPUNKTE" ist als gesperrte
   Versalie rund 120 px breit und lief in die Nachbarspalte — im Feld sichtbar
   als Ueberlappung von Forschungspunkten und Gems. Statt die Spalte zu
   verbreitern (dann passen auf ein schmales Geraet nur noch zwei) bricht die
   Beschriftung jetzt um; die Zahl bleibt die tragende Aussage. */
/* Feste Spaltenzahl statt auto-fit. Bei fuenf Werten liess auto-fit auf
   390 px eine Reihe mit drei und eine mit zwei stehen — die zweite Reihe sah
   aus wie ein Rest, der uebrig geblieben ist. Zwei mal drei mit einer
   leeren Zelle ist eine RUHIGE Aufteilung, kein Bruch.
   Die Etiketten sind ausserdem auf ein Wort gekuerzt (renderProfile); der
   frueher noetige Trennstrich-Umbruch entfaellt damit. */
.profile-stats {
  max-width: 900px; margin: 0 auto var(--s5);
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--s5) var(--s3);
  padding-top: var(--s4); border-top: 1px solid var(--line-dark);
}
@media (min-width: 560px) { .profile-stats { grid-template-columns: repeat(5, 1fr); } }
.profile-stats > div { display: grid; gap: var(--s1); min-width: 0; align-content: start; }
.profile-stats span {
  color: var(--ink-3); font-size: var(--t-micro);
  letter-spacing: var(--track-label); font-weight: 850;
  text-transform: uppercase; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.profile-stats strong { font-size: 1.5rem; font-weight: 850; letter-spacing: -.04em; }
/* Anmeldeschranke. Liegt ueber allem, bis eine Sitzung besteht. Bewusst ruhig
   und ohne Effekte: es ist der erste Bildschirm, den jemand sieht, und er soll
   erklaeren statt zu beeindrucken. */
.auth-gate {
  position: fixed; inset: 0; z-index: 200;
  /* Tiefe statt Flaeche: ein warmer Lichtkegel oben, der nach unten in
     Nachtgruen ausläuft. Zwei Verläufe, keine Bilddatei — die Schranke ist der
     erste Bildschirm und darf keinen Ladevorgang haben. */
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(201,255,66,.14) 0%, transparent 58%),
    radial-gradient(90% 60% at 50% 108%, rgba(119,242,177,.08) 0%, transparent 60%),
    var(--night);
  /* minmax statt 1fr: Die Grafikzeile hat damit von der ersten gerenderten
     Zeile an eine Hoehe. Mit blossem 1fr war sie null, solange das SVG noch
     nicht vermessen war — der Inhalt klebte kurz am unteren Rand und sprang
     dann in die Mitte. */
  display: grid; grid-template-rows: minmax(180px, 1fr) auto; align-items: end;
  padding: var(--s6) var(--s5) calc(var(--s5) + env(safe-area-inset-bottom));
  overflow: hidden;
}
.auth-gate[hidden] { display: none; }

/* Grafik. Sucherwinkel plus Tierspur — dieselbe Aussage wie das App-Icon:
   etwas Lebendiges wird erfasst. Bewusst SVG und nicht generiert: es skaliert
   auf jede Bildschirmgroesse, kostet keinen Ladevorgang und laesst sich mit
   den Farbvariablen steuern. */
.gate-scene { display: grid; place-items: center; min-height: 0; }
/* Hoehe explizit (Seitenverhaeltnis 1:1): mit height:auto hat das SVG vor der
   Vermessung eine Hoehe von 0, und der Inhalt darunter springt nach dem ersten
   Layout um genau diese Hoehe nach oben. */
.gate-art { width: min(60vw, 230px); height: min(60vw, 230px); opacity: .9; }
.gate-art-frame {
  stroke: var(--lime); stroke-width: 7; stroke-linecap: square; fill: none;
  opacity: .85;
}
.gate-art-paw { fill: var(--lime); opacity: .95; }
.gate-art-pulse {
  stroke: var(--lime); stroke-width: 1.5; fill: none; opacity: .18;
  transform-origin: 160px 160px; animation: gatePulse 3.4s ease-out infinite;
}
@keyframes gatePulse {
  0%   { transform: scale(.82); opacity: .30; }
  70%  { transform: scale(1.14); opacity: 0; }
  100% { transform: scale(1.14); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gate-art-pulse { animation: none; opacity: .2; }
}

.auth-gate-inner { width: min(420px, 100%); display: grid; gap: var(--s4); justify-self: center; }
.auth-gate h1 {
  margin: 0; font-size: clamp(2rem, 9vw, 2.9rem); line-height: 1.02;
  letter-spacing: -.045em; font-weight: 850; color: #f4f5ef;
}
.auth-gate p { margin: 0; font-size: var(--t-body); line-height: 1.6; color: rgba(244,245,239,.6); }
.auth-gate .account-button { margin-top: var(--s3); }
.gate-legal {
  margin: var(--s5) auto 0; max-width: 420px; text-align: center;
  font-size: var(--t-micro) !important; color: rgba(244,245,239,.34) !important; line-height: 1.5;
}

/* Prüfzustand beim Start.
   Die Schranke liegt von der ersten gerenderten Zeile an oben — sonst blitzte
   die Spieloberflaeche auf, bis /api/auth/config geantwortet hat. Solange noch
   nicht feststeht, ob eine Anmeldung noetig ist, bleibt alles Bedienbare
   ausgeblendet: ein Anmeldeknopf, der nach 200 ms wieder verschwindet, ist
   schlimmer als gar keiner. */
/* visibility statt display: Die Elemente behalten ihren Platz. Mit display:none
   wuchs der Inhalt beim Aufdecken um Knopf, Codefeld und Fusszeile — der ganze
   Block sass erst unten am Rand und ruckte dann hoch. Reservierter Platz heisst:
   vom ersten Bild an steht alles da, wo es bleibt. */
.auth-gate.is-checking .account-button,
.auth-gate.is-checking .gate-code,
.auth-gate.is-checking .gate-legal,
.auth-gate.is-checking .gate-lead { visibility: hidden; }
.gate-checking {
  display: none; align-items: center; gap: var(--s2);
  font-size: var(--t-small); color: rgba(244,245,239,.5);
}
.auth-gate.is-checking .gate-checking { display: flex; }
.auth-gate-hint { font-size: var(--t-small); color: var(--warning); }
.auth-gate-hint:empty { display: none; }
.text-link.light { color: rgba(244,245,239,.42); justify-self: center; }

/* Wartezustand waehrend der Anmeldung im Systembrowser. Ohne dieses Zeichen
   sieht der zurueckkehrende Nutzer eine unveraenderte Schranke und schliesst
   daraus, die Anmeldung habe nicht geklappt — genau der Eindruck, der den
   Fehlerbericht ausgeloest hat. */
.gate-waiting {
  display: flex; align-items: center; gap: var(--s2);
  font-size: var(--t-small); color: rgba(244,245,239,.55);
}
.gate-waiting[hidden] { display: none; }
.gate-spinner {
  width: 13px; height: 13px; flex: none; border-radius: 50%;
  border: 2px solid rgba(201,255,66,.25); border-top-color: var(--lime);
  animation: gateSpin .8s linear infinite;
}
@keyframes gateSpin { to { transform: rotate(360deg); } }

/* Code-Eingabe. Der Ausnahmeweg — normal holt die App das Ergebnis selbst ab —,
   aber sichtbar: die Abschlussseite im Browser verweist darauf, und vorher gab
   es das Feld ueberhaupt nicht. Die Trennlinie ordnet es dem Google-Knopf unter,
   ohne es zu verstecken. */
.gate-code {
  font-size: var(--t-small); padding-top: var(--s4);
  border-top: 1px solid rgba(244,245,239,.1);
}
.gate-code-help { margin: 0 0 var(--s2); font-size: var(--t-micro) !important; }
.gate-code-row { display: grid; grid-template-columns: 1fr auto; gap: var(--s2); }
.gate-code-row input {
  padding: var(--s3); border-radius: var(--r-soft); font-family: ui-monospace, Menlo, monospace;
  background: rgba(255,255,255,.06); border: 1px solid rgba(244,245,239,.18);
  color: #f4f5ef; font-size: .95rem; letter-spacing: .12em; text-transform: uppercase;
  min-width: 0;
}
.gate-code-row input:focus { outline: none; border-color: var(--lime); }
.ghost-button {
  padding: var(--s3) var(--s4); border-radius: var(--r-soft); cursor: pointer;
  background: transparent; border: 1px solid rgba(244,245,239,.28);
  color: #f4f5ef; font-family: inherit; font-size: var(--t-small); font-weight: 700;
}
.ghost-button:hover { border-color: var(--lime); color: var(--lime); }

/* Namensschritt. Erbt die Schranken-Optik; eigene Ebene unter der Anmeldung,
   damit beim Uebergang nie die App durchblitzt. */
.name-gate { z-index: 190; grid-template-rows: 1fr auto; }
.name-gate .gate-code-row { margin-top: var(--s3); }
.name-gate input {
  padding: var(--s3); border-radius: var(--r-soft); min-width: 0;
  background: rgba(255,255,255,.06); border: 1px solid rgba(244,245,239,.18);
  color: #f4f5ef; font-size: 1rem; font-family: inherit;
}
.name-gate input:focus { outline: none; border-color: var(--lime); }
.gate-primary {
  padding: var(--s3) var(--s5); border-radius: var(--r-soft); cursor: pointer;
  background: var(--ink); border: 0; color: var(--paper);
  box-shadow: inset 0 -2px 0 var(--lime);
  font-family: inherit; font-size: var(--t-small); font-weight: 800;
  letter-spacing: .02em;
}
.gate-primary:disabled { opacity: .4; cursor: not-allowed; }
/* Der Status traegt seine Aussage in der Farbe: Limette = frei, Rot = vergeben
   oder ungueltig. Feste Hoehe, damit die Zeile beim Wechsel nicht springt. */
.name-gate-status { margin: 0; min-height: 1.4em; font-size: var(--t-small); color: rgba(244,245,239,.55); }
.name-gate-status.ist-frei { color: var(--lime); }
.name-gate-status.ist-belegt { color: var(--danger); }

/* Startfehler. Liegt ueber allem, weil eine App, die nicht starten konnte,
   nichts Wichtigeres anzuzeigen hat als den Grund. Auf dem Geraet gibt es keine
   Konsole — ohne diesen Kasten lautet der einzige Befund "reagiert nicht". */
.start-error {
  position: fixed; left: var(--s4); right: var(--s4); z-index: 900;
  /* Ueber der Navigationsleiste, nicht darauf: sonst verdeckt die Meldung
     genau die Knoepfe, mit denen man pruefen wuerde, was noch geht. */
  bottom: calc(var(--nav-height) + var(--s4) + env(safe-area-inset-bottom));
  display: grid; gap: var(--s3); padding: var(--s4);
  background: #2a1512; border: 1px solid var(--danger); border-radius: var(--r-card);
  color: #ffe8e5; font-size: var(--t-small); line-height: 1.5;
}
.start-error code {
  display: block; padding: var(--s2); border-radius: var(--r-soft);
  background: rgba(0,0,0,.35); font-size: var(--t-micro); word-break: break-word;
  font-family: ui-monospace, Menlo, monospace;
}
/* Protokoll der Verbindungsversuche. Klein, aber vollstaendig: es ist das
   Einzige, was auf einem Geraet ohne Konsole sagt, welche Adresse mit welchem
   Fehler gescheitert ist — und es laesst sich abfotografieren. */
.start-error-log {
  padding: var(--s2); border-radius: var(--r-soft); background: rgba(0,0,0,.3);
  font-family: ui-monospace, Menlo, monospace; font-size: var(--t-micro);
  line-height: 1.6; color: rgba(255,232,229,.8); word-break: break-all;
  max-height: 30vh; overflow-y: auto;
}
.start-error-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }
.start-error button {
  padding: var(--s2) var(--s4); border-radius: var(--r-soft); cursor: pointer;
  background: var(--danger); border: 0; color: #2a1512;
  font-family: inherit; font-size: var(--t-small); font-weight: 800;
}
.start-error button.ghost {
  background: transparent; border: 1px solid rgba(255,232,229,.3); color: #ffe8e5;
}

/* Entwicklerbereich im Profil. Sichtbar erst nach sieben Tippern auf die
   Versionszeile — er enthaelt die Serveradresse und das Loeschen der Sammlung,
   beides nichts, was ein Spieler versehentlich treffen darf. */
#devSection[hidden] { display: none; }
.dev-note {
  margin: 0 0 var(--s2); font-size: var(--t-micro); color: var(--ink-3);
  text-transform: uppercase; letter-spacing: var(--track-label); font-weight: 800;
}
.version-line {
  display: block; width: 100%; margin-top: var(--s5); padding: var(--s3);
  background: none; border: 0; cursor: pointer; font-family: inherit;
  font-size: var(--t-micro); color: var(--ink-3); opacity: .55;
  text-align: center; letter-spacing: .06em;
}

/* Anmeldung im Profil. Der Knopf traegt Googles Marke und darf deshalb nicht umgestaltet
   werden — alles andere bleibt in der Formsprache der App: Kanten statt Pillen,
   Typografie statt Container. */
.account-section { max-width: 900px; margin: 0 auto 26px; }
.account-state { display: grid; gap: 14px; }
.account-state[hidden] { display: none; }
.account-copy { margin: 0; font-size: .86rem; line-height: 1.55; color: var(--ink-3); }
.account-button {
  display: flex; align-items: center; justify-content: center; gap: 11px;
  width: 100%; padding: 14px; cursor: pointer;
  background: white; color: #1f1f1f; border: 1px solid rgba(16,20,17,.22);
  font-size: .9rem; font-weight: 700;
}
.account-button:disabled { opacity: .45; cursor: not-allowed; }
.google-mark { width: 18px; height: 18px; flex: none; }
.account-hint { margin: 0; font-size: .78rem; color: #b3572f; }
.account-hint[hidden] { display: none; }

.account-identity { display: grid; gap: 3px; }
.account-label { font-size: .58rem; letter-spacing: .16em; font-weight: 850; color: #8a908a; }
.account-identity strong { font-size: 1.15rem; letter-spacing: -.02em; }
.account-mail { font-size: .78rem; color: #7a807a; }

/* Nutzercode. Gross und in Versalien, weil er vorgelesen und abgetippt wird. */
.code-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 13px 15px; cursor: pointer; text-align: left;
  background: rgba(16,20,17,.04); border: 1px dashed rgba(16,20,17,.25); color: var(--ink);
}
.code-row span { font-size: .58rem; letter-spacing: .16em; font-weight: 850; color: #7a807a; }
.code-row strong { font-size: 1.05rem; letter-spacing: .14em; font-variant-numeric: tabular-nums; }
.text-link {
  justify-self: start; padding: 0; border: 0; background: none; cursor: pointer;
  font-size: .8rem; font-weight: 700; color: #8a908a; text-decoration: underline;
}

.friend-add { display: flex; gap: 9px; margin-bottom: 16px; }
.friend-add .text-input { flex: 1; margin: 0; letter-spacing: .12em; text-transform: uppercase; }

/* Pruefpunkte als Balkenreihe. Eine Punktetabelle ("Bewegungs-Challenge:
   20 Punkte") ist eine Protokollzeile — der Nutzer will sehen, WO es geklemmt
   hat, und dafuer genuegt der Fuellstand. */
.check-row {
  display: grid; grid-template-columns: 88px 1fr; align-items: center; gap: var(--s3);
  padding: var(--s2) 0;
}
.check-row span:first-child {
  font-size: var(--t-micro); letter-spacing: .1em; font-weight: 800; color: var(--ink-3);
  text-transform: uppercase;
}
/* GRID SCHLAEGT FLEX — und muss es hier ausdruecklich tun.
   `.plain-list > div` setzt display:flex und ist spezifischer als `.check-row`.
   Unter flex bekommt der leere Balken-Span Breite 0 und verschwindet: die
   ganze Reihe "Aufnahmequalitaet" stand am 04.08.2026 als Liste nackter
   Woerter ohne einen einzigen Balken da. Nachgemessen im Browser, nicht
   vermutet. */
.plain-list > .check-row { display: grid; grid-template-columns: 88px 1fr; gap: var(--s3); }
/* Beide Farben fuer DUNKLEN Grund. .check-row gibt es nur auf der
   Ergebnisseite (#101411), und rgba(16,20,17,…) ist genau dieser Wert —
   Bahn und Fuellung waren damit unsichtbar, egal wie hoch der Wert stand. */
.check-track { height: 2px; background: rgba(242,241,232,.16); overflow: hidden; }
.check-track i { display: block; height: 100%; background: var(--paper); transition: width .5s ease; }
.check-row.weak .check-track i { background: var(--danger); }
.check-row.weak span:first-child { color: var(--danger); }
/* Torwaechterzeile: statt eines Balkens steht rechts ein Befund im Klartext.
   Sie teilt sich die Rasterspalten mit der Punktereihe darueber, damit beide
   Bloecke buendig untereinander stehen. */
.check-row strong {
  /* HIER STAND --paper, und das war eine Kruecke.
     Begruendung damals: die Ergebnisseite hatte #101411 als Grund, und --ink
     war derselbe Wert — der Befund stand mit exakt der Hintergrundfarbe da.
     Im Browser nachgemessen am 04.08.2026: das DOM trug "unauffaellig",
     "erkannt", "Art · 100 %", der Bildschirm zeigte drei leere Zeilen.

     Mit dem einen Register ist die Kruecke NICHT nur unnoetig, sondern selbst
     der Fehler: --paper ist jetzt der dunkle Grund, und der Befund war am
     06.08. wieder unsichtbar (1,06:1). Genau dieselbe Zeile, genau derselbe
     Effekt, nur andersherum.

     Das ist die Lehre aus dem ganzen Umbau: eine Ausnahme, die eine
     Registerdifferenz ausgleicht, kippt mit dem Register. Ein Register, ein
     Token — hier --ink, und die Ausnahme entfaellt ersatzlos. */
  font-size: var(--t-micro); font-weight: 700; color: var(--ink); text-align: right;
}
.check-row.weak strong { color: var(--danger); }

/* Zugaenge zu Unteransichten. Zwei nebeneinander, kantig, mit Symbol —
   ersetzen die weggefallenen Navigationspunkte fuer Forschung und Tausch. */
/* Zurueck-Verweis fuer Ansichten ohne Navigationspunkt und ohne Umschalter
   (Werkstatt, Expeditionen, Tausch). Ohne ihn ist der einzige Rueckweg die
   Navigationsleiste, die dabei auf einem Punkt steht, der gar nicht die
   aktuelle Ansicht ist — das liest sich wie ein Fehler. */
/* Der Zurueck-Verweis steht seit dem 04.08.2026 UEBER der Kopfzeile.
   Darunter war er die dritte Zeile der Ansicht — gelesen wird von oben, und
   auf einem schmalen Geraet stand er teils unterhalb der Falz. Er traegt
   ausserdem den Ort ("Sammlung") und macht damit das Marken-Etikett
   ueberfluessig, das dort vorher doppelt dasselbe sagte. */
/* 32 px hoch statt 27 — siehe `.segment`. Der Zurueckweg ist der einzige Weg
   aus Werkstatt, Studio, Missionen und Tausch heraus; ihn zu verfehlen sieht
   aus wie eine Ansicht, die sich nicht schliessen laesst. */
.back-link {
  display: inline-flex; align-items: center; gap: 6px; min-height: var(--ziel-min);
  margin: 0 0 var(--s2); padding: 6px 10px 6px 4px;
  border: 0; background: transparent; color: var(--ink-3); cursor: pointer;
  font-family: inherit; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.back-link svg {
  width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.back-link:active { color: var(--ink); }

/* Symbolzeile fuer die Nebenwege (Expeditionen, Tausch, Werkstatt).
   Ersetzt die frueheren .section-link-Kaesten: die waren zweizeilig mit
   Unterzeile und brauchten zu dritt rund 130 px. Als Symbol ueber Wort sind
   es 64 px — wenig genug, um oben bei der uebrigen Navigation zu stehen,
   statt unter der ganzen Sammlung zu verschwinden.
   `grid-auto-flow: column` statt fester Spaltenzahl: dieselbe Regel wie bei
   .segmented, damit ein vierter Eintrag die Zeile nicht bricht. */
.quick-links {
  max-width: 900px; margin: 0 auto var(--s4);
  display: flex; gap: 0;
}
/* SCHNELLWEGE: Zeile mit Trennstrichen statt drei Quadraten.
   Sie sind Navigation, keine Inhalte — drei gerahmte Kacheln haben ihnen
   dasselbe Gewicht gegeben wie den Faengen darunter. Nebenbei kostet die
   Zeile 34 px statt 64. */
.quick-link {
  position: relative; min-width: 0; min-height: 32px;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: var(--s2) var(--s3); cursor: pointer;
  border: 0; border-right: 1px solid var(--line-hair);
  background: transparent; color: var(--ink-2);
  font-family: inherit;
}
.quick-link:last-child { border-right: 0; }
.quick-link:active { color: var(--ink); }
.quick-link svg {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round;
}
.quick-link span {
  font-size: var(--t-micro); font-weight: 850; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* Zaehler-Ecke: nur sichtbar, wenn sie etwas zu sagen hat. Ein leeres
   Abzeichen auf jedem Knopf waere Rauschen. */
.quick-link em:empty { display: none; }
.quick-link em {
  position: absolute; top: -6px; right: -6px;
  min-width: 18px; padding: 2px 5px; border-radius: var(--r-soft);
  background: var(--lime); color: var(--ink);
  font-size: 9px; font-style: normal; font-weight: 900; text-align: center;
  font-variant-numeric: tabular-nums;
}
.section-link {
  display: flex; align-items: center; gap: var(--s3); text-align: left; cursor: pointer;
  padding: var(--s3) var(--s4); background: transparent; color: var(--ink);
  border: 1px solid var(--line-dark);
}
.section-link svg {
  width: 19px; height: 19px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round;
}
.section-link span { display: grid; gap: 1px; min-width: 0; }
.section-link strong { font-size: var(--t-small); font-weight: 800; }
.section-link em {
  font-size: var(--t-micro); font-style: normal; letter-spacing: .08em;
  color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Artenkatalog ---------------------------------------------------------
   Frueher eine flache Liste ueber alle 746 Arten mit einem Fortschrittsbalken
   darueber. Die Liste war unnavigierbar und der Balken sagte nur "wenig" —
   ohne Einordnung, ohne Zwischenziel und ohne Grund, ihn anzusehen.

   Jetzt drei Ebenen: Gesamtring, fuenf Gruppenringe, aufklappbare Sektionen.
   Der Ring traegt Anteil (Bogen), Stand (Zahl) und Rang (Farbe) auf einer
   Flaeche — ein Balken braeuchte dafuer drei Plaetze.

   Zur Farbwahl: Medaillenfarben stehen in medalStyle in app.js und kommen
   ausschliesslich per Inline-Variable hier an. Im CSS nachgebaut wuerden sie
   von den Seltenheitsfarben driften, die schon aus rarityStyle kommen. */
/* Umschalter Sammlung / Katalog. Ersetzt den sechsten Navigationspunkt:
   sechs Punkte brachen die Leiste auf schmalen Geraeten in eine zweite Reihe,
   und die beiden Ansichten beantworten ohnehin dieselbe Frage von zwei
   Seiten.

   DIE SPALTENZAHL WIRD NICHT MEHR GENANNT. Vorher stand hier
   `grid-template-columns: 1fr 1fr` — hart auf zwei. Als ein drittes Segment
   dazukam, wurde diese Zeile nicht mitgezogen, und das dritte rutschte auf
   375 px in eine zweite Reihe. Genau der Umbruch, den der Umschalter
   vermeiden sollte. `grid-auto-flow: column` zaehlt selbst; damit kann die
   Leiste bei keiner Segmentzahl mehr umbrechen.

   `min-width: 0` auf dem Segment ist Pflicht: Gitterspalten haben
   `min-width: auto`, richten sich also nach dem laengsten Wort und sprengen
   die Leiste, statt die Schrift zu kuerzen. */
/* SEGMENTLEISTE: Unterstrich, kein Kasten im Kasten.
   Vorher war es eine Rinne mit einer erhabenen Kachel darin — drei
   Flaechenebenen fuer eine Entscheidung zwischen zwei Ansichten. Ein
   Unterstrich sagt dasselbe mit einer. */
.segmented {
  max-width: 900px; margin: 0 auto var(--s4);
  display: flex; gap: var(--s5);
  border-bottom: 1px solid var(--line-dark);
}
/* MINDESTENS 32 px HOCH. Gemessen waren es 26 — und ein Segment, das man
   daneben trifft, schaltet auf einem Handy die halbe Sammlung um. Die Hoehe
   kommt aus dem oberen Innenabstand, nicht aus `min-height`: der Strich unten
   liegt AUF der Linie der Leiste, und `min-height` haette ihn mit einer
   Zentrierung nach oben geschoben. */
.segment {
  min-width: 0; flex: 0 1 auto;
  display: flex; align-items: center; gap: 7px;
  padding: 6px 0 9px; border: 0; border-radius: 0;
  /* Der Strich liegt AUF der Linie der Leiste, nicht darunter — sonst
     entsteht eine 2 px hohe Stufe. */
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  background: transparent; color: var(--ink-3);
  font-family: inherit; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; cursor: pointer;
  transition: color .18s ease, border-color .18s ease;
}
.segment span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.segment svg { flex: none; width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.segment.active { color: var(--ink); border-bottom-color: var(--ink); }
/* Unter 360 px traegt das Symbol nichts, was das Wort nicht schon sagt — und
   es kostet genau den Platz, an dem das Wort abgeschnitten wuerde. */
@media (max-width: 359px) {
  .segment { gap: 0; letter-spacing: .08em; }
  .segment svg { display: none; }
}

.progress-hero {
  max-width: 900px; margin: 0 auto var(--s5);
  display: grid; grid-template-columns: auto 1fr; align-items: center; gap: var(--s5);
}
/* Der Bogen wird von 0 auf seinen Wert gezogen (animiereRinge in app.js setzt
   das Ziel einen Frame spaeter). Ohne Uebergang saesse der Ring sofort da und
   der Fortschritt waere eine Zahl statt einer Bewegung. */
.ring-bogen { transition: stroke-dasharray 1.1s cubic-bezier(.22,.9,.28,1); }
/* Marke der naechsten Medaillenstufe. Sie macht aus dem Anteil ein Ziel. */
.ring-marke { stroke: var(--ink-3); stroke-width: 2; opacity: .55; }
@media (prefers-reduced-motion: reduce) {
  .ring-bogen { transition: none; }
}
.ring { display: block; }
.ring-value {
  font-family: inherit; font-weight: 900; font-size: 26px;
  fill: var(--ink); letter-spacing: -.02em;
}
.group-ring .ring-value { font-size: 15px; }
.progress-hero-copy { display: grid; gap: var(--s1); min-width: 0; }
.progress-hero-copy strong { font-size: var(--t-lead); font-weight: 900; }
.hero-medal {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.hero-medal .muted { color: var(--ink-3); }
.progress-hero-copy em {
  font-size: var(--t-small); font-style: normal; color: var(--ink-3);
}
.medal-badge { flex: none; }

/* Die fuenf Oberkategorien. Waagerecht scrollend statt umbrechend: ein
   Umbruch auf zwei Zeilen macht aus einer Reihe eine Tabelle, und die
   Reihenfolge ist dann nicht mehr ablesbar. */
.group-rings {
  max-width: 900px; margin: 0 auto var(--s5);
  display: flex; gap: var(--s3); overflow-x: auto; padding-bottom: var(--s2);
  scrollbar-width: none;
}
.group-rings::-webkit-scrollbar { display: none; }
.group-ring {
  flex: none; width: 84px; display: grid; justify-items: center; gap: 2px;
  padding: var(--s2) 0; border: 1px solid var(--line-hair); border-radius: var(--r-card);
  background: var(--paper); font-family: inherit; color: inherit; cursor: pointer;
}
.group-ring:hover { border-color: var(--line-dark); }
.group-ring.aktiv { border-color: var(--ink); background: rgba(16,20,17,.04); }
.group-ring strong { font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase; }
.group-zahl { font-size: var(--t-micro); color: var(--ink-3); font-variant-numeric: tabular-nums; }
/* Das Gruppensymbol sitzt IM Ring. Daneben waere die Karte 20 px hoeher, und
   die Reihe passt dann nicht mehr neben den Gesamtring. */
.group-ring { position: relative; }
.group-symbol {
  position: absolute; top: 22px; left: 50%; transform: translateX(-50%);
  width: 20px; height: 20px; pointer-events: none; opacity: .30;
}
.group-symbol svg, .section-symbol svg, .tile-silhouette svg {
  width: 100%; height: 100%; fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}

.catalog-tools { max-width: 900px; margin: 0 auto var(--s4); display: grid; gap: var(--s2); }
#catalogSearch { margin: 0; display: block; }
.toggle-line {
  display: flex; align-items: center; gap: var(--s2); min-height: 32px;
  font-size: var(--t-small); color: var(--ink-3); cursor: pointer;
}
/* DAS KAESTCHEN BLEIBT 16 px — die Zielflaeche ist das `<label>`, und das
   ist ueber `min-height` 32 px hoch. Ein Klick auf den Text schaltet mit.
   `padding` auf einer nativen Checkbox zu setzen half nicht: Chrome zeichnet
   sie in der Contentbox und die Klickflaeche waechst nicht mit. */
.toggle-line input { accent-color: var(--lime); width: 16px; height: 16px; }

.catalog-sections { max-width: 900px; margin: 0 auto; display: grid; gap: var(--s2); }
.catalog-section { border-bottom: 1px solid var(--line-hair); padding-bottom: var(--s2); }
.section-head {
  width: 100%; display: grid; grid-template-columns: 26px 1fr auto auto 18px;
  align-items: center; gap: var(--s2);
  border: 0; background: none; padding: var(--s3) 0;
  font-family: inherit; color: inherit; text-align: left; cursor: pointer;
}
.section-symbol { width: 24px; height: 24px; color: var(--section-accent, var(--ink-3)); }
.section-title { display: grid; gap: 1px; min-width: 0; }
.section-title strong { font-size: var(--t-body); font-weight: 800; }
.section-title em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
.section-quota {
  font-size: var(--t-small); font-weight: 850; font-variant-numeric: tabular-nums;
  color: var(--section-accent, var(--ink-3));
}
.section-chevron {
  width: 16px; height: 16px; fill: none; stroke: var(--ink-3); stroke-width: 2;
  transition: transform .2s ease;
}
.catalog-section.open .section-chevron { transform: rotate(90deg); }
.section-bar { height: 2px; background: rgba(16,20,17,.10); }
.section-bar i { display: block; height: 100%; background: var(--ink); transition: width .5s ease; }
.section-next {
  margin: var(--s2) 0 0; font-size: var(--t-micro); color: var(--ink-3);
  letter-spacing: var(--track-label); text-transform: uppercase;
}

/* Kacheln NUR innerhalb einer aufgeklappten Sektion. Als Raster ueber alle 746
   Arten waeren sie unlesbar (das war die Begruendung der alten Liste) — in
   einem Abschnitt von 8 bis 110 Eintraegen tragen sie dagegen genau das, was
   eine Liste nicht kann: das eigene Foto. */
.species-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--s2); margin-top: var(--s3);
}
.species-tile {
  display: grid; gap: 3px; justify-items: center; position: relative;
  padding: var(--s2) 4px; border: 1px solid var(--line-hair); border-radius: var(--r-card);
  background: var(--paper); font-family: inherit; color: inherit; cursor: pointer;
  overflow: hidden; min-width: 0;
}
.species-tile.found { border-color: var(--tile-accent, var(--line-dark)); }
.species-tile strong {
  font-size: var(--t-micro); font-weight: 700; text-align: center;
  color: var(--ink-3); line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.species-tile.found strong { color: var(--ink); font-weight: 850; }
.tile-photo {
  width: 100%; aspect-ratio: 1; display: block; overflow: hidden;
  border-radius: var(--r-soft); background: var(--paper-2);
}
.tile-photo { position: relative; }
.tile-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Das KATALOGFOTO (GBIF) einer noch nicht gefundenen Art ist entsaettigt und
   abgedunkelt. Es sagt "so sieht es aus", nicht "das hast du" — und dieser
   Unterschied muss auf einen Blick lesbar bleiben, sonst verliert die
   Sammlung ihre Aussage. Das eigene Foto bleibt in voller Farbe. */
.tile-photo img.stock { filter: grayscale(.85) brightness(.82) contrast(.95); }
.species-tile.found .tile-photo img { filter: none; }
/* Gruener Haken auf gefundenen Arten. Farbe allein traegt die Unterscheidung
   nicht — Rot-Gruen-Schwaeche ist haeufig, und entsaettigt gegen farbig ist
   auf einem Handy im Sonnenlicht ohnehin schwach. */
.tile-haken {
  position: absolute; right: 3px; bottom: 3px; width: 17px; height: 17px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--lime); color: var(--ink);
}
.tile-haken svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
/* Fehlt auch ein Katalogfoto, traegt das Sektionssymbol. */
.tile-silhouette {
  width: 100%; height: 100%; display: grid; place-items: center;
  color: rgba(107,113,106,.30); padding: 18%;
}
.tile-silhouette.large { padding: 22%; }
.tile-count {
  position: absolute; top: 6px; right: 6px;
  font-size: var(--t-micro); font-weight: 900; padding: 1px 4px;
  background: var(--ink); color: var(--paper); border-radius: var(--r-kante);
}
/* Haus- und Zootiere kennzeichnen. Ohne den Hinweis sucht jemand den Loewen
   im Wald und den Haushund unter den Wildtieren — die Sektion ist
   taxonomisch richtig (Carnivora = Raubtiere), beantwortet die Frage aber
   nicht. */
.tile-flag {
  position: absolute; top: 6px; left: 6px;
  font-size: 8px; font-weight: 900; letter-spacing: .06em; text-transform: uppercase;
  padding: 1px 3px; background: rgba(16,20,17,.72); color: var(--paper);
  border-radius: var(--r-kante);
}
.art-flag {
  justify-self: start; margin-top: 4px;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
  padding: 2px 6px; border: 1px solid var(--line-dark); border-radius: var(--r-kante);
}
.photo-credit {
  margin: -6px 0 0; font-size: 10px; color: var(--ink-3); line-height: 1.4;
}
.photo-credit a { color: inherit; }

/* ---- Artendetail ---------------------------------------------------------- */
.species-head { display: grid; gap: var(--s3); margin-bottom: var(--s4); }
.species-hero {
  width: 100%; aspect-ratio: 16/10; overflow: hidden;
  border-radius: var(--r-card); background: var(--paper-2);
  border: 2px solid var(--hero-accent, var(--line-hair));
}
.species-hero.loading { display: grid; place-items: center; color: var(--ink-3); }
.species-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.species-headline { display: grid; gap: 2px; }
.species-headline h2 { margin: 0; }
.species-headline em { font-size: var(--t-small); font-style: italic; color: var(--ink-3); }
/* Gefunden oder nicht muss VOR dem Namen lesbar sein — das ist die Frage, mit
   der man die Karte oeffnet. */
.species-status {
  justify-self: start; font-size: var(--t-micro); font-weight: 900;
  letter-spacing: var(--track-label); text-transform: uppercase;
  padding: 2px 6px; border-radius: var(--r-kante);
}
.species-status.ja {
  color: var(--ink); font-weight: 850;
  display: inline-flex; align-items: center; gap: 5px;
}
.species-status.ja::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--lime); flex: none;
}
.species-status.nein { background: rgba(16,20,17,.08); color: var(--ink-3); }
.species-body { display: grid; gap: var(--s4); }
.instance-strip { display: flex; gap: var(--s2); overflow-x: auto; scrollbar-width: none; }
.instance-strip::-webkit-scrollbar { display: none; }
/* overflow:hidden und min-width:0 sind hier NICHT kosmetisch: ein Grid-Kind hat
   die Mindestbreite seines Inhalts, und ein Bild mit width:100% wuchs deshalb
   ueber die Kachel hinaus statt sich einzupassen. */
.species-instance {
  flex: none; width: 76px; display: grid; gap: 3px; justify-items: center;
  padding: 4px; border: 1px solid var(--tile-accent, var(--line-hair));
  border-radius: var(--r-card); background: var(--paper);
  font-family: inherit; color: inherit; cursor: pointer;
  overflow: hidden; min-width: 0;
}
.species-instance img {
  width: 100%; max-width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--r-soft); display: block; min-width: 0;
}
.species-instance span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.species-instance span {
  font-size: var(--t-micro); font-weight: 800; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--tile-accent, var(--ink-3));
}

/* ---- Aufnahmen eines Exemplars -------------------------------------------- */
/* .gallery-block, .gallery-strip und .gallery-item sind ENTFALLEN. Die
   Aufnahmen lagen doppelt vor: als Briefmarken in einem eigenen Abschnitt und
   als grosses Bild oben, das immer nur eine davon zeigte. Jetzt traegt das
   Deckbild sie alle (.detail-hero). */
.gallery-hint { font-size: var(--t-small); color: var(--ink-3); margin: 0; }
/* Fundort als fester Abschluss jeder Tierkarte. Die Karte zeigt bewusst einen
   Bereich statt eines Pins: die gespeicherten Koordinaten sind auf ca. 100 m
   gerundet und duerfen nicht praeziser aussehen, als sie sind. */
.detail-location-block {
  display: grid; gap: var(--s2); margin-top: var(--s1); padding-top: var(--s4);
  border-top: 1px solid var(--line-hair);
}
.detail-location-head { display: flex; align-items: end; justify-content: space-between; gap: var(--s3); }
.detail-location-head .section-label { display: block; margin-bottom: 3px; }
.detail-location-head h3 { margin: 0; font-size: 1.08rem; letter-spacing: -.02em; }
.detail-location-head > span {
  max-width: 45%; text-align: right; color: var(--ink-3);
  font-size: var(--t-micro); line-height: 1.35;
}
.detail-location-map {
  width: 100%; height: 190px; overflow: hidden; border-radius: var(--r-card);
  border: 1px solid var(--line-hair); background: var(--paper-2);
}
.detail-location-preview { position: relative; min-height: 190px; overflow: hidden; border-radius: var(--r-card); }
.detail-location-preview .detail-location-map { position: absolute; inset: 0; height: 100%; }
.detail-location-preview > button,
.buehne-v2-fundort-vorschau > button {
  position: absolute; z-index: 500; inset: 0 0 24px; display: grid; place-items: end;
  width: 100%; padding: 10px; border: 0; color: #124b50; background: transparent;
  cursor: zoom-in;
}
.detail-location-preview > button > span,
.buehne-v2-fundort-vorschau > button > span {
  display: inline-flex; align-items: center; gap: 7px; min-height: 38px; padding: 8px 11px;
  border: 1px solid rgba(22,101,96,.22); background: rgba(246,255,251,.92);
  box-shadow: 0 8px 24px rgba(12,56,61,.14); font-size: var(--t-micro);
  font-weight: 850; letter-spacing: .04em; text-transform: uppercase;
}
.detail-location-preview > button svg,
.buehne-v2-fundort-vorschau > button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.detail-location-preview > button:focus-visible > span,
.buehne-v2-fundort-vorschau > button:focus-visible > span { outline: 3px solid #f3bd45; outline-offset: 2px; }
.detail-location-map.is-unavailable {
  display: grid; place-items: center; padding: var(--s4); text-align: center;
  color: var(--ink-3); font-size: var(--t-small);
}
.detail-location-note {
  display: flex; align-items: center; gap: 6px; margin: 0 !important;
  color: var(--ink-3) !important; font-size: var(--t-micro) !important;
}
.detail-location-note svg, .detail-location-empty > svg {
  width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.detail-location-empty {
  min-height: 112px; display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4); border: 1px dashed var(--line-dark); border-radius: var(--r-card);
  background: linear-gradient(135deg, var(--paper-2), rgba(255,255,255,.45)); color: var(--ink-3);
}
.detail-location-empty > svg { width: 28px; height: 28px; }
.detail-location-empty span { display: grid; gap: 3px; font-size: var(--t-small); line-height: 1.4; }
.detail-location-empty strong { color: var(--ink); font-size: var(--t-body); }
.detail-location-adopt { margin-top: var(--s1); }

/* Fundort im Top-Layer: die Karte selbst ist die Flaeche, nicht ein weiterer
   Kasten. Nur Navigation und Erklaerung schweben als schmale Werkzeuge ueber
   ihr. 100dvh verhindert auf Android den abgeschnittenen unteren Rand. */
.location-viewer {
  width: 100vw; max-width: none; height: 100dvh; max-height: none;
  margin: 0; padding: 0; border: 0; overflow: hidden; color: #eaf8f4;
  background: #c8e3df; grid-template-rows: auto minmax(0,1fr);
}
.location-viewer[open] { display: grid; }
.location-viewer:not([open]) { display: none; }
.location-viewer::backdrop { background: rgba(4,17,21,.86); }
.location-viewer-map { grid-area: 1 / 1 / -1 / -1; min-width: 0; min-height: 0; background: #c8e3df; }
.location-viewer-map.is-unavailable {
  display: grid; place-items: center; padding: 28px; color: #174650;
  font-weight: 800; text-align: center;
}
.location-viewer-bar {
  grid-area: 1 / 1; z-index: 800; display: grid; grid-template-columns: auto 1fr auto;
  align-items: center; min-height: 70px; padding: calc(8px + env(safe-area-inset-top)) 10px 8px;
  pointer-events: none;
}
.location-viewer-action {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  min-width: 48px; min-height: 46px; padding: 8px 12px; border: 1px solid rgba(18,76,79,.18);
  color: #12474e; background: rgba(246,255,251,.9); box-shadow: 0 8px 25px rgba(13,53,59,.14);
  font: inherit; font-size: .69rem; font-weight: 850; cursor: pointer; pointer-events: auto;
}
.location-viewer-action svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.location-viewer-heading {
  justify-self: center; display: grid; gap: 2px; max-width: min(48vw,420px); padding: 8px 18px;
  color: #143f49; background: rgba(242,253,249,.82); text-align: center;
  box-shadow: 0 8px 30px rgba(16,65,70,.11); pointer-events: none;
}
.location-viewer-heading small { color: #087b63; font-size: .55rem; font-weight: 900; letter-spacing: .15em; }
.location-viewer-heading strong { overflow: hidden; font-size: .9rem; text-overflow: ellipsis; white-space: nowrap; }
.location-viewer-note {
  grid-area: 2 / 1; z-index: 800; align-self: end; justify-self: center; display: flex;
  align-items: center; gap: 10px; margin: 0 12px calc(14px + env(safe-area-inset-bottom));
  padding: 10px 14px; color: #133f48; background: rgba(246,255,251,.92);
  box-shadow: 0 12px 34px rgba(12,52,58,.17); pointer-events: none;
}
.location-viewer-note > span { width: 9px; height: 9px; border: 2px solid #fff; border-radius: 50%; background: #27a78d; box-shadow: 0 0 0 3px rgba(39,167,141,.28); }
.location-viewer-note p { display: grid; gap: 2px; margin: 0; }
.location-viewer-note strong { font-size: .72rem; }
.location-viewer-note small { color: #55727a; font-size: .62rem; }
.location-viewer .leaflet-control-zoom { margin-top: 92px !important; border: 0 !important; box-shadow: 0 8px 25px rgba(13,53,59,.16) !important; }
.location-viewer .leaflet-control-zoom a { width: 46px !important; height: 46px !important; line-height: 44px !important; color: #12474e !important; background: rgba(246,255,251,.94) !important; font-size: 1.2rem !important; }
.location-viewer .leaflet-control-attribution { padding: 3px 6px !important; background: rgba(246,255,251,.78) !important; font-size: 10px !important; }
@media (max-width: 520px) {
  .location-viewer-action span { display: none; }
  .location-viewer-heading { max-width: 55vw; padding-inline: 12px; }
  .location-viewer-note { max-width: calc(100vw - 24px); }
}

/* ---- Bildbetrachter -------------------------------------------------------
   MUSS ein <dialog> mit showModal() sein. Als gewoehnliches Element lag er im
   normalen Stapelkontext, waehrend die Detailkarte als modaler Dialog im
   TOP-LAYER liegt — er oeffnete unsichtbar dahinter, und z-index half nicht,
   weil der Top-Layer ausserhalb jeder z-index-Ordnung steht. */
.photo-viewer {
  width: 100%; max-width: none; height: 100%; max-height: none;
  margin: 0; padding: 0; border: 0;
  background: rgba(10,12,10,.97); color: #f2f1ec;
  display: grid; grid-template-rows: auto 1fr auto;
}
.photo-viewer:not([open]) { display: none; }
.photo-viewer::backdrop { background: rgba(0,0,0,.9); }
.viewer-bar {
  display: grid; grid-template-columns: 44px 1fr auto; align-items: center;
  padding: calc(var(--s2) + env(safe-area-inset-top)) var(--s2) var(--s2);
}
.viewer-tools { display: flex; }
.viewer-label {
  text-align: center; font-size: var(--t-micro); font-weight: 800;
  letter-spacing: var(--track-label); text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.viewer-button {
  width: 44px; height: 44px; display: grid; place-items: center;
  border: 0; background: none; color: inherit; cursor: pointer;
}
.viewer-button[hidden] { display: none; }
.viewer-button svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* Die Zoomflaeche faengt die Gesten ab. touch-action:none ist noetig, sonst
   nimmt der Browser das Ziehen als Seitenscrollen und die Pointer-Events
   brechen mitten in der Geste ab. */
.viewer-canvas {
  min-height: 0; overflow: hidden; display: grid; place-items: center;
  touch-action: none; cursor: zoom-in;
}
.viewer-canvas.gezoomt { cursor: grab; }
.viewer-image {
  max-width: 100%; max-height: 100%; object-fit: contain;
  transform-origin: center center; will-change: transform;
  user-select: none; -webkit-user-drag: none;
}
.viewer-strip {
  display: flex; gap: var(--s2); overflow-x: auto; scrollbar-width: none;
  padding: var(--s3) var(--s3) calc(var(--s3) + env(safe-area-inset-bottom));
}
.viewer-strip::-webkit-scrollbar { display: none; }
.strip-item {
  flex: none; width: 54px; height: 54px; padding: 0; cursor: pointer;
  border: 2px solid transparent; background: none; border-radius: var(--r-soft); overflow: hidden;
}
.strip-item.active { border-color: var(--lime); }
.strip-item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Fundkarte ------------------------------------------------------------
   Eigene Zeichnung, keine Kartenbibliothek: eine echte Karte laedt Kacheln von
   fremden Servern, und die Punkte hier SIND Standortdaten. */
/* ---- Kartenansicht: fuellt den Schirm, scrollt nicht ----------------------
   Die erste Fassung rechnete die Kartenhoehe gegen feste Pixelwerte und liess
   die Fundliste darunter im Fluss stehen. Ergebnis auf dem Geraet: die Seite
   war hoeher als der Schirm, man musste scrollen, um die Navigationsleiste
   ueberhaupt zu sehen, und in der Breite liess sie sich verschieben.

   Jetzt traegt die Ansicht selbst die Hoehe: fester Rahmen ueber die volle
   Anzeige, Kopf und Werkzeuge oben, die Karte nimmt den Rest. Nichts scrollt —
   die Fundliste ist in ein Sheet gewandert. */
/* `.active` MUSS im Selektor stehen. `.view { display: none }` hat die
   Spezifität 0-1-0; eine Regel mit ID (1-1-0) gewinnt dagegen — die Karte war
   damit IMMER eingeblendet und lag ueber Feed, Profil und allem anderen.
   Ein Layout-Selektor darf die Sichtbarkeitsregel nie ueberstimmen. */
#mapView.content-view.active {
  height: 100dvh;
  padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom));
  display: flex; flex-direction: column;
  overflow: hidden;
  /* Der eigentliche Grund fuer das seitliche Verschieben: ein Kind war breiter
     als der Rahmen. overflow-x am Rahmen faengt das ab, statt es auf die
     ganze Seite durchschlagen zu lassen. */
  overflow-x: hidden;
}
#mapView .content-header { flex: none; }
#mapView .map-tools { flex: none; }
.map-frame {
  position: relative; width: 100%; max-width: 900px; margin: 0 auto;
  /* flex:1 mit min-height:0 — ohne die Mindesthoehe waechst ein Flex-Kind auf
     seine Inhaltsgroesse und schiebt den Rest aus dem Bild. */
  flex: 1 1 auto; min-height: 0;
  border: 1px solid var(--line-dark);
  background: rgba(16,20,17,.02); overflow: hidden; border-radius: var(--r-card);
}
.map-real { position: absolute; inset: 0; }
.map-empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: var(--t-body); color: var(--ink-3);
  /* Ueber der Karte, aber ohne sie zu blockieren — die leere Karte bleibt
     schwenkbar. */
  pointer-events: none; z-index: 500;
}

/* Foto-Pins. Quadrat mit Fussspitze — Kanten statt Pillen (Regel 1); die
   Rahmenfarbe traegt die Seltenheit und kommt inline aus rarityStyle-Farben. */
.map-pin-halter { background: none; border: 0; }
.map-pin {
  position: relative; display: block; width: 48px; height: 48px; box-sizing: border-box;
  border: 3px solid var(--ink-3); border-radius: var(--r-soft);
  background: var(--paper);
  box-shadow: 0 2px 10px rgba(0,0,0,.35);
}
/* KEIN overflow:hidden am Pin — es wuerde die Fussspitze (::after) kappen.
   Das Foto beschneidet sich selbst.

   DER LANGE SELEKTOR IST ABSICHT — Leaflet gewinnt sonst. Sein eigenes CSS
   traegt

     .leaflet-container .leaflet-marker-pane img { width: auto }

   mit Spezifitaet 0,2,1 gegen die 0,1,1 von `.map-pin img`. Die Folge war am
   04.08.2026 im UI-Katalog messbar: die Fotos behielten ihr Seitenverhaeltnis
   und standen als 17, 20, 25 und 26 px breite Streifen in einem 42-px-Rahmen.
   Jeder Pin sah anders aus, und bei der Groesse war kein Tier mehr erkennbar —
   obwohl `object-fit: cover` genau dafuer dasteht und auch griff. Nur die
   BREITE kam nie an.

   0,3,1 schlaegt 0,2,1 sicher, unabhaengig von der Ladereihenfolge der beiden
   Dateien. `!important` waere kuerzer und wuerde die naechste Kollision
   verstecken statt sie zu benennen. */
.leaflet-container .leaflet-marker-pane .map-pin img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: 1px;
}
/* Leaflets Zoomknoepfe sind 30x30 und damit unter der Grenze, ab der ein
   Finger zuverlaessig trifft. Sie sind Fremdcode — deshalb wird hier nur die
   GROESSE gesetzt, nicht das Aussehen: Leaflet zeichnet Rahmen, Rundung und
   Trennlinie selbst, und wer die mit ueberschreibt, baut sie beim naechsten
   Versionssprung neu.

   DREI KLASSEN IM SELEKTOR, und das ist gemessen noetig: Leaflet setzt die
   Groesse in `.leaflet-touch .leaflet-bar a` mit Spezifitaet 0,3,0. Ein
   `.leaflet-bar a.leaflet-control-zoom-in` liegt bei 0,2,1 und verliert —
   Klassen zaehlen vor Elementen. Genau dieselbe Falle steht im Absatz
   darueber fuer `.map-pin img`, und sie hat hier ein zweites Mal
   zugeschlagen. */
.leaflet-touch .leaflet-bar a.leaflet-control-zoom-in,
.leaflet-touch .leaflet-bar a.leaflet-control-zoom-out,
.leaflet-bar a.leaflet-control-zoom-in,
.leaflet-bar a.leaflet-control-zoom-out {
  width: 32px; height: 32px; line-height: 32px;
}
.map-pin-leer {
  display: grid; place-items: center; font-weight: 900; font-size: 1.2rem;
}
.map-pin-stack { box-shadow: 5px -5px 0 var(--paper), 6px -6px 0 rgba(16,20,17,.24), 0 2px 10px rgba(0,0,0,.35); }
.map-pin-count {
  position: absolute; z-index: 2; right: -9px; top: -10px;
  min-width: 23px; height: 23px; padding: 0 5px; box-sizing: border-box;
  display: grid; place-items: center; border: 2px solid var(--paper); border-radius: 999px;
  background: var(--ink); color: var(--paper); font-size: 11px; line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* Fussspitze auf dem Fundort. */
.map-pin::after {
  content: ''; position: absolute; left: 50%; top: 46px;
  width: 10px; height: 10px; transform: translateX(-50%) rotate(45deg);
  background: inherit; border: inherit; border-top: 0; border-left: 0;
}

/* ---- Karte: Werkzeuge und Markierungen ------------------------------------ */
/* Die Werkzeuge scrollen waagerecht. Der Innenabstand oben und unten ist
   noetig, weil `overflow-x: auto` auch senkrecht abschneidet: die Knopfrahmen
   und der aktive Zustand lagen sonst genau auf der Schnittkante und wirkten
   abgeschnitten. */
.map-tools {
  max-width: 900px; width: 100%; margin: 0 auto var(--s2);
  display: flex; gap: var(--s2);
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  padding: 3px 0 var(--s2);
  -webkit-overflow-scrolling: touch;
}
.map-tools::-webkit-scrollbar { display: none; }
.map-tool {
  flex: none; display: flex; align-items: center; gap: 6px;
  padding: 8px 12px; border: 1px solid var(--line-hair); border-radius: var(--r-card);
  background: var(--paper); color: var(--ink-3); cursor: pointer;
  font-family: inherit; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; white-space: nowrap;
}
.map-tool svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.map-tool.aktiv { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.map-tool.primary { border-color: var(--lime); color: var(--ink); }
.map-tool.primary.aktiv { background: var(--lime); border-color: var(--lime); color: var(--ink); }
.map-tool-count {
  display: grid; place-items: center; min-width: 20px; height: 20px; padding: 0 5px;
  border-radius: 999px; background: rgba(16,20,17,.09); color: inherit;
  font-size: 10px; font-variant-numeric: tabular-nums;
}
.map-tool.aktiv .map-tool-count { background: rgba(255,255,255,.16); }
/* Im Markiermodus zeigt der Zeiger, dass ein Tipp jetzt etwas anderes tut. */
.map-real.markiermodus { cursor: crosshair; }
/* Schlanker Kopf auf der Kartenseite: dort zaehlt die Flaeche, nicht die
   Ueberschrift. */
.map-header { margin-bottom: var(--s2); }
.map-header h1 { display: none; }

/* Hinweisleiste im Zeichenmodus. Sie liegt UEBER der Karte, nicht darueber im
   Fluss — sonst aendert sich die Kartenhoehe beim Ein- und Ausblenden, und
   Leaflet muesste jedes Mal neu vermessen. */
.map-hint {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(var(--s3) + 92px); z-index: 500;
  max-width: min(92%, 460px); padding: 9px 13px;
  background: rgba(16,20,17,.90); color: #f2f1ec;
  border-radius: var(--r-card); font-size: var(--t-small); line-height: 1.4;
  box-shadow: 0 4px 16px rgba(0,0,0,.28);
}
.map-hint[hidden] { display: none; }
.map-hint strong { color: var(--lime); }
.hint-button {
  margin-left: 6px; padding: 3px 8px; border: 1px solid rgba(242,241,236,.5);
  border-radius: var(--r-kante); background: none; color: inherit;
  font-family: inherit; font-size: var(--t-micro); font-weight: 800; cursor: pointer;
}
.hint-button.warn { color: #ffd5cf; border-color: rgba(255,213,207,.62); }

/* Anfasser fuer die direkte Geometriebearbeitung. Grosse, nummerierte Punkte
   sind auf dem Handy besser ziehbar als die kleinen Zeichenpunkte des
   Neuanlegen-Modus; die aktive Ecke traegt die Loeschaktion. */
.mark-edit-handle-wrap, .find-edit-pin-wrap { background: none; border: 0; }
.mark-edit-handle {
  width: 28px; height: 28px; display: grid; place-items: center;
  border: 2px solid #c2911a; border-radius: 50%;
  background: var(--paper); color: var(--ink); box-shadow: 0 2px 7px rgba(0,0,0,.28);
  font-size: 10px; font-weight: 900;
}
.mark-edit-handle.aktiv { background: #c2911a; color: var(--paper); transform: scale(1.12); }
.find-edit-pin {
  position: relative; width: 40px; height: 40px; display: grid; place-items: center;
  border: 2px solid var(--ink); border-radius: 50% 50% 50% 4px;
  background: var(--lime); color: var(--ink); transform: rotate(-45deg);
  box-shadow: 0 3px 10px rgba(0,0,0,.3);
}
.find-edit-pin svg {
  width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8;
  transform: rotate(45deg);
}

/* Etikett an einer Markierung: Sektionssymbol und Artname direkt auf der
   Karte. Ohne das muesste man jede Flaeche antippen, um zu wissen, wofuer sie
   steht. */
.mark-etikett-wrapper { background: none; border: 0; }
.mark-etikett {
  display: inline-flex; align-items: center; gap: 4px;
  /* 58×24 gemessen. Das Etikett IST der Knopf — es antippen oeffnet die
     Markierung. Groesser wird es ueber den Innenabstand, nicht ueber die
     Schrift: die Karte traegt schon zwoelf davon nebeneinander. */
  min-height: 32px; padding: 6px 7px; white-space: nowrap;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--mark-accent, var(--line-dark));
  border-radius: var(--r-card);
  font-size: 10px; font-weight: 800;
  box-shadow: 0 1px 4px rgba(16,20,17,.18);
  /* Der Marker sitzt auf dem Mittelpunkt; ohne Versatz verdeckt das Etikett
     genau die Stelle, die es beschreibt. */
  transform: translate(-50%, -50%);
}
.mark-etikett svg {
  width: 14px; height: 14px; flex: none;
  fill: none; stroke: var(--mark-accent, currentColor);
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.mark-etikett em { font-style: normal; max-width: 110px; overflow: hidden; text-overflow: ellipsis; }
/* Knoepfe in Leaflet-Popups. Sie liegen ausserhalb des App-DOM und erben
   deshalb nichts. */
.popup-button {
  margin-top: 6px; margin-right: 4px; padding: 5px 9px;
  border: 1px solid var(--line-dark); border-radius: var(--r-kante);
  background: var(--paper); font-family: inherit; font-size: 11px; font-weight: 800;
  cursor: pointer;
}
.popup-button.warn { color: #b33b32; border-color: #d8b2ae; }
.feed-detail-map { height: 180px; border-radius: var(--r-card); overflow: hidden; }
.feed-ortmarke { width: 11px; height: 11px; margin-left: 4px; fill: none; stroke: currentColor; stroke-width: 2; vertical-align: -1px; }
.feed-owner-link {
  border: 0; background: none; padding: 0; text-align: left; cursor: pointer;
  font-family: inherit; color: inherit;
}
.feld-label {
  display: block; margin: var(--s3) 0 4px;
  font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.range-input { width: 100%; accent-color: var(--lime); }
.dialog-actions { display: grid; gap: var(--s2); margin-top: var(--s4); }

/* ---- Profil: Bloecke, Freundesvorschau ------------------------------------ */
.profile-block { max-width: 900px; margin: 0 auto var(--s5); }
.block-heading {
  display: flex; align-items: center; gap: 7px; margin: 0 0 var(--s3);
  font-size: var(--t-micro); font-weight: 900;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
  padding-bottom: var(--s2); border-bottom: 1px solid var(--line-dark);
}
.block-heading svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.wide-button {
  width: 100%; display: flex; align-items: center; gap: var(--s3);
  padding: 13px var(--s3); border: 1px solid var(--line-dark); border-radius: var(--r-card);
  background: var(--paper); color: var(--ink); cursor: pointer;
  font-family: inherit; font-size: var(--t-small); font-weight: 800;
}
.wide-button svg { width: 17px; height: 17px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.wide-button span { flex: 1; text-align: left; }
.wide-button strong { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.wide-button.danger { color: #b33b32; border-color: #d8b2ae; }
.wide-button:hover { border-color: var(--ink); }
.settings-row .row-icon { width: 15px; height: 15px; margin-right: 8px; vertical-align: -2px; fill: none; stroke: currentColor; stroke-width: 1.7; }
.settings-hint { margin: 0 0 var(--s2); color: var(--ink-3); font-size: var(--t-small); line-height: 1.45; }
/* Eine eingeschaltete Ortsfreigabe wird sichtbar hervorgehoben — sie ist die
   einzige Einstellung, die Daten ueber den eigenen Aufenthalt weitergibt. */
.settings-row.warnung strong { color: #b3762f; }

/* PWA-Installation steht im Profil statt als Banner ueber dem Sucher. Auf iOS
   ist sie eine kurze Anleitung, auf Chromium oeffnet derselbe Knopf den
   nativen Installationsdialog. */
.pwa-install-block { margin-top: var(--s4); }
/* #f1f5e7 stand hier hart und war ein helles Papierweiss — auf dem
   Nachtgrund traegt es die helle --ink-Schrift mit 1,10:1, also gar nicht.
   Einer von 505 Werten, die am Tokensystem vorbeigingen; er ist der Beleg
   dafuer, warum die Umgehung und nicht der Wert das Problem ist. */
.pwa-install-button { align-items: center; border-color: var(--lime-kante); background: var(--flaeche-2); }
.pwa-install-button > span { display: grid; gap: 2px; min-width: 0; }
.pwa-install-button > span strong { color: var(--ink); font-size: var(--t-small); }
.pwa-install-button > span em { color: var(--ink-3); font-size: var(--t-micro); font-style: normal; font-weight: 600; line-height: 1.35; }
.pwa-install-button > b { color: var(--ink-3); font-size: 1.4rem; font-weight: 500; }
.app-update-block { margin-top: var(--s4); }
.app-update-progress { display: block; width: 100%; height: 5px; margin-top: var(--s2); accent-color: var(--ink); }
.app-update-progress[hidden] { display: none; }
.app-update-block .wide-button:disabled { cursor: wait; opacity: .72; }
.apk-download-link { text-decoration: none; }
.apk-download-link:focus-visible { outline: 2px solid var(--lime); outline-offset: 3px; }
.pwa-install-steps { counter-reset: pwa-step; display: grid; gap: var(--s3); margin: var(--s2) 0 var(--s3); padding: 0; list-style: none; }
.pwa-install-steps li { counter-increment: pwa-step; display: grid; grid-template-columns: 34px 1fr; column-gap: var(--s3); align-items: center; }
.pwa-install-steps li::before { content: counter(pwa-step); grid-row: 1 / span 2; width: 32px; height: 32px; display: grid; place-items: center; border-radius: 50%; background: var(--ink); color: var(--lime); font-size: var(--t-small); font-weight: 900; }
.pwa-install-steps strong { font-size: var(--t-small); }
.pwa-install-steps span { color: var(--ink-3); font-size: var(--t-micro); line-height: 1.4; }

.friend-preview { display: grid; gap: var(--s2); margin-bottom: var(--s3); }
.friend-chip {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: 9px var(--s3); border: 1px solid var(--line-hair); border-radius: var(--r-card);
  background: var(--paper); color: inherit; font-family: inherit; cursor: pointer; text-align: left;
}
.friend-chip.anfrage { border-color: var(--lime); }
.friend-initial {
  width: 34px; height: 34px; flex: none; display: grid; place-items: center;
  border-radius: 50%; background: var(--ink); color: var(--paper);
  font-weight: 900; font-size: var(--t-small);
}
.friend-chip-copy { display: grid; gap: 1px; min-width: 0; }
.friend-chip-copy strong { font-size: var(--t-small); font-weight: 800; }
.friend-chip-copy em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }

/* Profilkopf eines Freundes im Sammlungs-Sheet. */
.friend-profile { display: grid; grid-template-columns: auto 1fr; gap: var(--s4); align-items: center; margin-bottom: var(--s3); }
.friend-profile-zahlen { display: grid; gap: var(--s2); min-width: 0; }
.friend-level { display: flex; align-items: center; gap: 5px; font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase; }
.friend-level svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.friend-kennzahlen { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2); }
.friend-kennzahlen div { display: grid; gap: 1px; min-width: 0; }
.friend-kennzahlen strong { font-size: var(--t-body); font-weight: 900; font-variant-numeric: tabular-nums; }
.friend-kennzahlen span { font-size: 9px; color: var(--ink-3); letter-spacing: var(--track-label); text-transform: uppercase; }
.friend-medaille { display: flex; align-items: center; gap: 5px; font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase; }
.friend-profile-title { flex: none; }
.friend-profile-title h2 { margin-bottom: 0; }
.friend-profile-dialog #friendCollectionMeta { flex: none; }
.friend-collection-section {
  display: flex; flex: 1 1 auto; min-height: 0; flex-direction: column;
  gap: var(--s2); padding-top: var(--s3); border-top: 1px solid var(--line-hair);
}
.friend-collection-heading { display: flex; align-items: end; justify-content: space-between; gap: var(--s3); }
.friend-collection-heading .section-label { display: block; margin-bottom: 3px; }
.friend-collection-heading h3 { margin: 0; font-size: 1.15rem; letter-spacing: -.025em; }
.friend-collection-heading > strong {
  font-size: var(--t-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.friend-profile-dialog .gallery-hint { margin: 0 0 var(--s1); }
.friend-profile-dialog .friend-animal-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  width: 100%; max-width: none; margin-inline: 0; padding: 1px 2px var(--s2);
}
.friend-manager-row.pending { cursor: default; opacity: .62; }

.map-list { max-width: 900px; margin: 0 auto; }
.map-row-shell {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: var(--s2); border-bottom: 1px solid var(--line-hair);
}
.map-row {
  display: grid; grid-template-columns: 10px 1fr auto; align-items: center; gap: var(--s3);
  padding: var(--s3) 0; border-bottom: 0;
  /* Als Knopf: volle Breite, Ausgangsoptik der Zeile behalten. */
  width: 100%; background: none; border-left: 0; border-right: 0; border-top: 0;
  font-family: inherit; text-align: left; color: inherit; cursor: pointer;
}
.map-row:hover strong { color: var(--ink); text-decoration: underline; }
/* Ein Punkt DARF rund sein — er stellt einen Ort dar. Siehe Regel 1 oben. */
.map-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-3); }
.map-dot.rarity-COMMON { background: #8fa894; }
.map-dot.rarity-UNCOMMON { background: #5bbd84; }
.map-dot.rarity-RARE { background: #4aa6e0; }
.map-dot.rarity-EPIC { background: #a877f0; }
.map-dot.rarity-MYTHIC { background: #f0c04a; }
.map-row-copy { display: grid; gap: 1px; min-width: 0; }
.map-row-copy strong { font-size: var(--t-body); font-weight: 700; }
.map-row-copy em { font-size: var(--t-small); font-style: normal; color: var(--ink-3); }
.map-coords { font-size: var(--t-small); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.map-row-edit {
  display: inline-flex; align-items: center; gap: 5px; min-height: 36px; padding: 0 9px;
  border: 1px solid var(--line-hair); border-radius: var(--r-soft);
  background: var(--paper); color: var(--ink-3); cursor: pointer;
  font: inherit; font-size: var(--t-micro); font-weight: 800;
}
.map-row-edit svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.map-location-hint { margin-top: calc(var(--s2) * -1); }

/* Ein Ortsstapel öffnet eine kurze, fingerfreundliche Fangliste. Das Popup
   bleibt nur die Auswahl; alle Fakten stehen anschließend im Detail-Sheet. */
.map-stack-popup { min-width: 220px; }
.map-stack-popup > strong { display: block; margin-bottom: 6px; font-size: var(--t-small); }
.map-stack-list { display: grid; max-height: 260px; overflow-y: auto; }
.map-stack-list button {
  display: grid; grid-template-columns: 9px 1fr; align-items: center; gap: 9px;
  width: 100%; padding: 9px 2px; border: 0; border-bottom: 1px solid var(--line-hair);
  background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer;
}
.map-stack-list button:last-child { border-bottom: 0; }
.map-stack-list button span:last-child { display: grid; gap: 1px; }
.map-stack-list b { font-size: var(--t-small); }
.map-stack-list em { color: var(--ink-3); font-size: var(--t-micro); font-style: normal; }
.history-note { padding: 0 var(--s3); }

@media (max-width: 430px) {
  .map-row-shell { grid-template-columns: minmax(0, 1fr) 38px; }
  .map-row { grid-template-columns: 10px minmax(0, 1fr); gap: var(--s2) var(--s3); }
  .map-coords { grid-column: 2; margin-top: -5px; font-size: var(--t-micro); }
  .map-row-edit { width: 36px; padding: 0; justify-content: center; }
  .map-row-edit span { display: none; }
}

/* Feed. Eine Karte je Fang: Zuschnitt links, Art und Fänger in der Mitte,
   Seltenheit rechts. Die Akzentfarbe kommt als --card-accent aus rarityStyle in
   app.js — Seltenheitsfarben werden hier NIE nachgebaut (Gestaltungsregel 3),
   sonst driftet die Staffelung, sobald jemand eine Stufe anfasst. */
.feed-list { max-width: 900px; margin: 0 auto; display: grid; gap: var(--s3); }
.feed-card {
  display: grid; grid-template-columns: 58px 1fr auto; align-items: center; gap: var(--s3);
  padding: var(--s3); border: 1px solid var(--line-hair);
  border-left: 3px solid var(--card-accent, var(--ink-3));
  border-radius: var(--r-card); background: var(--paper);
  width: 100%; text-align: left; font-family: inherit; color: inherit;
}
.feed-card.tappable { cursor: pointer; }
.feed-card.tappable:hover { border-color: var(--line-dark); border-left-color: var(--card-accent, var(--ink-3)); }
.feed-card.tappable:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 2px;
  border-left-color: var(--card-accent, var(--ink-3));
}
.feed-photo {
  width: 58px; height: 58px; object-fit: cover; display: block;
  border-radius: var(--r-soft); background: var(--paper-2);
}
.feed-photo.placeholder {
  display: grid; place-items: center; font-weight: 900; font-size: 1.3rem; color: var(--ink-3);
}
.feed-copy { display: grid; gap: 1px; min-width: 0; }
.feed-copy strong { font-size: var(--t-body); font-weight: 700; }
.feed-owner { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); letter-spacing: var(--track-label); text-transform: uppercase; }
.feed-species { font-size: var(--t-small); color: var(--ink-3); font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.feed-rarity {
  font-size: var(--t-micro); font-weight: 800; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--card-accent, var(--ink-3)); white-space: nowrap;
}
/* Freundeszahl im Feed-Kopf. Als Knopf, weil ein leerer Feed sagen muss, was
   dagegen hilft — und das ist genau dieser Dialog. */
.feed-friends {
  display: grid; justify-items: end; gap: 1px; border: 0; background: none;
  font-family: inherit; color: inherit; cursor: pointer; padding: 0;
}
.feed-friends strong { font-size: var(--t-title); font-weight: 900; line-height: 1; }
.feed-friends span { font-size: var(--t-micro); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3); }

.settings-section { max-width: 900px; margin: 0 auto; }
.settings-row { width: 100%; border: 0; border-bottom: 1px solid var(--line-dark); background: transparent; color: var(--ink); padding: 16px 0; display: flex; justify-content: space-between; gap: 16px; text-align: left; cursor: pointer; }
.settings-row strong { font-size: .78rem; }
.danger-row strong { color: #b33b32; }
.technical-note { max-width: 900px; margin: 30px auto 0; color: var(--ink-3); font-size: .76rem; line-height: 1.55; }

/* Bottom-Sheet. Vorher fehlten Hoehenbegrenzung und Scrollen: langer Inhalt lief
   aus dem Bild, kurzer Inhalt ergab einen kleinen Kasten am unteren Rand — auf
   dem Handy wirkte das wie ein halb geoeffnetes Fenster.
   Jetzt: volle Breite, bis 92 % der Hoehe, Kopf und Fussknopf bleiben stehen,
   nur der Inhalt dazwischen scrollt. */
.sheet-dialog {
  width: 100%; max-width: 540px; max-height: 92dvh;
  border: 0; margin: auto auto 0; padding: 0;
  border-radius: var(--r-card) 22px 0 0; overflow: hidden;
  background: var(--paper); color: var(--ink);
}
.sheet-dialog::backdrop { background: rgba(0,0,0,.64); backdrop-filter: blur(4px); }

/* WISCHFLAECHEN. Ohne `touch-action: none` gehoert die senkrechte Geste dem
   BROWSER: er beginnt zu scrollen beziehungsweise zu ueberdehnen und schickt
   `pointercancel`, noch bevor die Schwelle erreicht ist. Die Karte liess sich
   deshalb auf dem Handy nur ueber den Knopf schliessen — mit der Maus lief
   dieselbe Geste einwandfrei, weshalb sie am Rechner nie auffiel.
   Betroffen sind die NICHT scrollenden Bereiche oben: Griff, Ueberschriften
   und das Heldenbild. Der scrollbare Inhalt darunter behaelt sein Scrollen. */
.sheet-dialog > .sheet-handle,
.sheet-dialog form > .sheet-handle,
.sheet-dialog > .brand-mark,
.sheet-dialog form > .brand-mark,
.sheet-dialog > h2,
.sheet-dialog form > h2,
.detail-hero { touch-action: none; }

/* Der Hintergrund darf nicht mitscrollen, wenn der Inhalt der Karte sein Ende
   erreicht. Ohne `overscroll-behavior: contain` schiebt die Geste die Liste
   DAHINTER weiter — der Nutzer schliesst die Karte und steht an einer ganz
   anderen Stelle der Sammlung. */
.sheet-dialog .option-list,
.sheet-dialog .plain-list,
.sheet-dialog .animal-picker-list,
.sheet-dialog .detail-body,
.sheet-dialog form > .species-body,
.sheet-dialog form > .feed-list { overscroll-behavior: contain; }
.sheet-dialog form {
  display: flex; flex-direction: column; max-height: 92dvh;
  padding: 10px 18px calc(24px + env(safe-area-inset-bottom));
}
/* Alles ausser Griff, Ueberschriften und dem letzten Knopf darf scrollen.
   Ohne die Schrumpfangabe behaelt ein Bereich seine Inhaltshoehe, und der
   Knopf am Fuss steht ausserhalb des Bildes.

   `.opponent-list` UND `.stake-grid` STANDEN HIER UND HABEN DAS SCROLLEN AM
   FINGER VOLLSTAENDIG BLOCKIERT (07.08.2026).

   `.opponent-list` ist an allen vier Stellen ein KIND von `.option-list`,
   nie ein Geschwister — `openAnimalPicker()` und die drei Schwesterfunktionen
   bauen sie als Raster IN die Liste hinein. Aus zwei Angaben wurde damit ein
   zweiter Scrollbereich mit der vollen Inhaltshoehe: er hat selbst nichts zu
   scrollen, und `overscroll-behavior: contain` (Regel darueber) verbot ihm,
   die Geste an den Elternteil weiterzugeben. Die Geste versickerte.

   MIT DER MAUS FIEL DAS NIE AUF. Das Rad und ein gesetztes `scrollTop`
   arbeiten am aeusseren Bereich vorbei; nur die Fingergeste sucht sich ihren
   Scrollbereich ueber den Treffpunkt. Gemessen mit echten Touch-Ereignissen
   gegen headless Chrome, 60 Tiere im Waehler:

     unveraendert ............................  0 px
     innere Liste auf `overflow: visible` .... 165 px
     nur `overscroll-behavior: auto` ......... 165 px
     zurueck .................................  0 px

   Die Gegenprobe gehoert dazu: dieselbe Geste scrollt die Sammlung um
   165 px. Ohne sie misst man die Messmethode.

   `.stake-grid` gibt es im Markup gar nicht mehr — sie kam mit der
   Ranglisten-Arena am 06.08.2026 weg. */
.sheet-dialog .option-list,
.sheet-dialog .plain-list,
.sheet-dialog .animal-picker-list { overflow-y: auto; -webkit-overflow-scrolling: touch; min-height: 0; }
.sheet-dialog .secondary-bar, .sheet-dialog .primary-bar { flex: none; margin-top: 14px; }
.sheet-handle { width: 44px; height: 4px; margin: 0 auto 22px; background: #8a9088; }
/* Kopfzeile mit Schliessen-Kreuz.
   Der Knopf am Fuss der Karte ist der richtige Weg — aber er steht hinter
   einer langen Liste, und wer die Wischgeste nicht kennt, sieht ihn nicht.
   Das Kreuz oben ist der sichtbare zweite Weg. */
.sheet-kopf {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); touch-action: none;
}
.sheet-schliessen {
  width: 44px; height: 44px; flex: none; border: 1px solid var(--line-hair);
  background: transparent; color: var(--ink-2); font-size: 20px; line-height: 1;
  cursor: pointer; font-family: inherit; border-radius: var(--r-soft);
}
.sheet-schliessen:active { background: var(--paper-2); }

/* Der gewaehlte Gegner, waehrend man sein eigenes Tier sucht.
   Ohne ihn muesste man sich Element, Staerke und Kampflevel merken — und
   genau diese Werte entscheiden die Wahl. */
.gegner-merker {
  flex: none; margin-bottom: var(--s3); padding: var(--s3);
  border: 1px solid var(--line-hair); border-left: 3px solid var(--ink);
  border-radius: var(--r-card); background: rgba(16,20,17,.04);
}
.gegner-label {
  display: block; margin-bottom: var(--s2); font-size: var(--t-micro);
  font-weight: 850; letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-3);
}
.gegner-merker .tier-row { border: 0; padding: 0; background: none; }
.gegner-konter {
  margin: var(--s2) 0 0 !important; font-size: var(--t-small); font-weight: 800;
  color: var(--ink) !important;
}
.gegner-merker .trait-line { margin-top: var(--s2); }
.sheet-dialog h2 { margin: 3px 0 8px; font-size: 2rem; letter-spacing: -.05em; }
/* `var(--ink-3)`, NICHT `#676d66`. Die feste Farbe stammt aus der Zeit vor
   dem Systemblatt und war auf dem Nachtregister ein blinder Fleck: 3,72 : 1
   gegen die geforderten 4,5, während das Register selbst 5,74 : 1 liefert.
   Weil `.sheet-dialog p` spezifischer ist als `.merge-hint`, schlug sie
   dessen `var(--ink-3)` — jeder Hinweistext in jedem Blattdialog war
   betroffen. Gemessener Preis: **15 der 49 Kontrastbefunde** hingen an diesen
   zwei Literalen. Genau die Falle aus CLAUDE.md: „eine feste dunkle Farbe ist
   auf dem Nachtregister ein blinder Fleck." */
.sheet-dialog p { margin: 0 0 20px; color: var(--ink-3); font-size: .82rem; line-height: 1.45; }
.new-catch-dialog form { gap: 0; padding: 10px 20px calc(24px + env(safe-area-inset-bottom)); }
.new-catch-dialog .sheet-handle { margin-bottom: var(--s4); }
/* HERVORGEHOBENER BLOCK, NICHT INVERTIERT.
   Hier stand `background: var(--ink); color: var(--paper)` — im Papierregister
   ein dunkler Block auf hellem Grund. Im Nachtregister ist --ink hell, und die
   hart eingetragene weisse Beschriftung darin stand mit 1,22:1 auf hellem
   Grund, also unsichtbar.

   Eine Umkehrung ist kein Baustein, sondern eine Wette auf das Register. Der
   Systemweg ist eine Flaechenstufe hoeher plus die Lime-Kante als Betonung —
   der haelt in jedem Register, weil er keine Farbe umdreht. */
.new-catch-heading {
  display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s3);
  margin: var(--s2) 0 var(--s4); padding: var(--s4);
  background: var(--ink); color: var(--paper); border-left: 4px solid var(--lime);
}
.new-catch-heading > div { display: flex; align-items: baseline; gap: var(--s2); }
.new-catch-heading strong {
  font-size: clamp(2.8rem, 16vw, 4.8rem); line-height: .8; letter-spacing: -.08em;
  font-variant-numeric: tabular-nums;
}
.new-catch-heading > div span {
  max-width: 90px; color: var(--ink-2); font-size: var(--t-small);
  font-weight: 850; line-height: 1.1; text-transform: uppercase;
}
.new-catch-top-rarity {
  max-width: 155px; color: var(--lime); font-size: var(--t-micro);
  font-weight: 900; line-height: 1.25; letter-spacing: .08em; text-align: right;
  text-transform: uppercase;
}
.new-catch-dialog h2 { margin-top: 0; }
.new-catch-dialog p { margin-bottom: var(--s3); }
.new-catch-rarity-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(82px, 1fr));
  border-top: 1px solid var(--line-dark); border-bottom: 1px solid var(--line-dark);
}
.new-catch-rarity-summary > span {
  display: grid; gap: 1px; padding: 10px var(--s2);
  border-left: 3px solid var(--catch-accent, var(--ink-3));
}
.new-catch-rarity-summary strong { font-size: var(--t-lead); font-variant-numeric: tabular-nums; }
.new-catch-rarity-summary em {
  color: var(--ink-3); font-size: var(--t-micro); font-style: normal;
  font-weight: 850; letter-spacing: .06em; text-transform: uppercase;
}
.new-catch-preview {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s2); margin-top: var(--s4); overflow-y: auto; min-height: 0;
}
.new-catch-card {
  position: relative; display: grid; min-width: 0; border-top: 3px solid var(--catch-accent);
  background: var(--paper-2); overflow: hidden;
}
.new-catch-card img, .new-catch-placeholder {
  width: 100%; aspect-ratio: 1; object-fit: cover; background: #dfe4dc;
}
.new-catch-placeholder { display: grid; place-items: center; color: var(--ink-3); font-size: 1.8rem; }
.new-catch-card strong, .new-catch-card em {
  overflow: hidden; padding: 0 var(--s2); text-overflow: ellipsis; white-space: nowrap;
}
.new-catch-card strong { padding-top: 7px; font-size: var(--t-small); }
.new-catch-card em {
  padding-bottom: 8px; color: var(--ink-3); font-size: var(--t-micro);
  font-style: normal; font-weight: 800;
}
.scan-queue-sheet {
  max-width: 620px; color: #193f49;
  background:
    linear-gradient(180deg, rgba(221,248,245,.96), rgba(255,248,219,.985) 42%, #f6f4e9),
    url('/assets/worlds/wildtrack-luminous-world-v1.webp') 50% 13% / cover no-repeat;
  box-shadow: 0 -24px 70px rgba(8,45,54,.28);
}
.scan-queue-sheet::backdrop { background: rgba(11,43,51,.48); backdrop-filter: blur(9px) saturate(.9); }
.scan-queue-sheet form { padding: 10px 20px max(20px, env(safe-area-inset-bottom)); }
.scan-queue-sheet .sheet-handle { margin-bottom: var(--s3); background: rgba(38,100,105,.32); }
.scan-queue-sheet .sheet-kopf { align-items: start; }
.scan-queue-sheet .brand-mark,
#scanQueueDialog .brand-mark.dark { color: #1f765f; }
.scan-queue-sheet h2 { margin-bottom: 5px; color: #173e49; font-size: clamp(2rem, 9vw, 3rem); }
.scan-queue-sheet p { max-width: 500px; margin-bottom: 15px; color: #52717a; }
.scan-queue-sheet .sheet-schliessen { border-color: rgba(39,103,111,.19); color: #315f68; background: rgba(255,255,255,.34); }
.scan-queue-summary-row { flex: none; display: grid; gap: 8px; margin-bottom: 10px; }
.scan-queue-summary {
  flex: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-block: 1px solid rgba(38,104,112,.17);
  color: #183f49; background: rgba(255,255,255,.19);
}
.scan-queue-summary > span {
  position: relative; min-width: 0; display: grid; gap: 2px; padding: 12px 12px 11px;
  border-left: 1px solid rgba(38,104,112,.13);
}
.scan-queue-summary > span:first-child { border-left: 0; }
.scan-queue-summary > span::after { content: ''; position: absolute; right: 12px; bottom: -1px; left: 12px; height: 2px; background: var(--summary-accent); }
.scan-queue-summary .is-card { --summary-accent: #3aa47a; }
.scan-queue-summary .is-note { --summary-accent: #d09b35; }
.scan-queue-summary .is-pending { --summary-accent: #4c91ae; }
.scan-queue-summary strong { font-size: 1.28rem; font-variant-numeric: tabular-nums; }
.scan-queue-summary em { color: #58727a; font-size: .56rem; font-style: normal; font-weight: 900; letter-spacing: .09em; text-transform: uppercase; }
.scan-queue-mark-all {
  justify-self: end; min-height: 34px; display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 11px; border: 1px solid rgba(35,110,92,.23); border-radius: 999px;
  color: #236e5c; background: rgba(255,255,255,.32); font: inherit;
  font-size: .63rem; font-weight: 900; cursor: pointer;
}
.scan-queue-mark-all[hidden] { display: none; }
.scan-queue-mark-all:disabled { cursor: wait; opacity: .56; }
.scan-queue-mark-all svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.scan-queue-mark-all:focus-visible { outline: 2px solid #2c8e76; outline-offset: 2px; }
.scan-history-filters {
  flex: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: 8px; border-bottom: 1px solid rgba(38,104,112,.17);
}
.scan-history-filters button {
  position: relative; min-width: 0; min-height: 45px; padding: 7px 5px; border: 0;
  color: #5d7880; background: transparent; font: inherit; font-size: .68rem;
  font-weight: 900; cursor: pointer;
}
.scan-history-filters button::after { content: ''; position: absolute; right: 22%; bottom: -1px; left: 22%; height: 3px; background: transparent; transform: scaleX(.3); transition: transform .2s ease, background .2s ease; }
.scan-history-filters button[aria-pressed="true"] { color: #1d695b; background: radial-gradient(ellipse at 50% 100%, rgba(69,174,124,.14), transparent 70%); }
.scan-history-filters button[aria-pressed="true"]::after { background: linear-gradient(90deg, #3aa77c, #d5a536); transform: scaleX(1); }
.scan-history-filters button:focus-visible { outline: 2px solid #2c8e76; outline-offset: -4px; }
.scan-history-filters[aria-busy="true"] { opacity: .68; }
.scan-queue-list { min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-color: rgba(43,108,113,.28) transparent; }
.scan-list-heading {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding: 15px 1px 8px; border-bottom: 1px solid rgba(38,104,112,.15);
}
.scan-list-heading strong { color: #236e5c; font-size: .61rem; font-weight: 950; letter-spacing: .14em; text-transform: uppercase; }
.scan-list-heading span { color: #678087; font-size: .61rem; text-align: right; }
.scan-list-heading:not(:first-child) { margin-top: 10px; border-top: 0; }
.scan-history-loading, .scan-history-error { padding: 18px 1px; color: #647d84; font-size: var(--t-small); border-bottom: 1px solid rgba(38,104,112,.15); }
.scan-history-error { color: #a94e45; }
.scan-history-photo-note { margin: 10px 0 2px; padding: 10px 12px; border-left: 2px solid #7d9894; color: #647d84; background: linear-gradient(90deg, rgba(88,138,132,.09), transparent); font-size: .66rem; line-height: 1.45; }
.scan-history-photo-note strong { color: #244f58; }
.scan-history-more { width: 100%; min-height: 48px; margin-top: 14px; border: 0; border-block: 1px solid rgba(38,104,112,.19); color: #286c68; background: rgba(255,255,255,.2); font: inherit; font-size: .74rem; font-weight: 900; cursor: pointer; }
.scan-history-more[aria-disabled="true"] { cursor: wait; opacity: .62; }
.scan-queue-row { display: grid; grid-template-columns: 58px minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 12px 2px; border-bottom: 1px solid rgba(38,104,112,.14); }
.scan-queue-group.is-pending .scan-queue-row { background: linear-gradient(90deg, rgba(82,157,184,.08), transparent 70%); }
.scan-queue-preview { width: 58px; height: 58px; object-fit: cover; display: block; background: rgba(44,105,112,.08); border-radius: 15px 5px 15px 5px; }
.scan-queue-preview.is-missing { display: grid; place-items: center; color: #78908f; background: linear-gradient(135deg, rgba(30,98,102,.06) 25%, transparent 25%) 0 0 / 12px 12px, rgba(255,255,255,.32); }
.scan-queue-preview.is-missing svg { width: 23px; height: 23px; fill: none; stroke: currentColor; stroke-width: 1.35; stroke-linecap: round; stroke-linejoin: round; }
.scan-queue-preview.is-missing svg path:first-child { fill: currentColor; stroke: none; opacity: .45; }
.scan-queue-preview.is-missing svg .slash { stroke-width: 2; opacity: .9; }
.scan-queue-copy { min-width: 0; display: grid; gap: 3px; }
.scan-queue-copy strong { color: #193f49; font-size: .86rem; line-height: 1.1; }
.scan-queue-copy span { color: #607980; font-size: .67rem; line-height: 1.38; }
.scan-queue-copy em, .scan-queue-copy i { color: #75898d; font-size: .58rem; font-style: normal; font-variant-numeric: tabular-nums; line-height: 1.32; }
.scan-queue-row.is-error .scan-queue-copy span { color: #a94e45; }
.scan-queue-actions { display: grid; justify-items: end; gap: 6px; }
.scan-queue-action { min-height: 42px; padding: 8px 9px; border: 0; border-bottom: 1px solid rgba(38,104,112,.25); color: #2f6870; background: transparent; font-size: .62rem; font-weight: 900; cursor: pointer; }
.scan-queue-action.primary { color: #fff; background: #236e5c; border-color: #236e5c; }
.scan-history-row { width: 100%; border: 0; border-bottom: 1px solid rgba(38,104,112,.14); color: #193f49; background: transparent; text-align: left; font-family: inherit; cursor: pointer; transition: background .14s ease, transform .14s ease; }
.scan-history-row:hover { background: rgba(255,255,255,.26); }
.scan-history-row:active { background: rgba(38,104,112,.07); transform: translateX(2px); }
.scan-history-row:focus-visible { outline: 2px solid #2c8e76; outline-offset: -2px; }
.scan-journal-card-entry {
  position:relative; width:100%; display:grid;
  grid-template-columns:clamp(146px,38vw,174px) minmax(0,1fr); align-items:center; gap:18px;
  padding:18px 6px 20px; border:0; border-bottom:1px solid rgba(38,104,112,.17);
  color:#193f49; background:linear-gradient(105deg,color-mix(in srgb,var(--journal-accent) 8%,transparent),transparent 55%);
  text-align:left; font:inherit; cursor:pointer;
  content-visibility:auto; contain:layout paint style; contain-intrinsic-block-size:auto 270px;
}
.scan-journal-card-entry::before{content:'';position:absolute;top:18px;bottom:20px;left:0;width:3px;background:var(--journal-accent);box-shadow:0 0 18px color-mix(in srgb,var(--journal-accent),transparent 38%)}
.scan-journal-card-entry:hover{background:linear-gradient(105deg,color-mix(in srgb,var(--journal-accent) 13%,transparent),rgba(255,255,255,.18) 70%)}
.scan-journal-card-entry:focus-visible,.scan-journal-outcome:focus-visible{outline:2px solid #2c8e76;outline-offset:-2px}
.scan-journal-card-frame{display:block;width:100%;margin-left:4px;pointer-events:none}
.scan-journal-card-frame .karte{width:100%;animation:none!important;contain:layout paint style}
.scan-journal-card-fallback{width:100%;aspect-ratio:63/88;display:grid;place-items:center;overflow:hidden;border:2px solid var(--journal-accent);border-radius:8px;background:#dce9df;color:#173e49}
.scan-journal-card-fallback img,.scan-journal-card-fallback .scan-queue-preview{grid-area:1/1;width:100%;height:100%;object-fit:cover;border-radius:0}
.scan-journal-card-fallback b{grid-area:1/1;align-self:end;width:100%;padding:8px;background:rgba(10,20,14,.78);color:#fff;text-align:center;text-transform:uppercase;letter-spacing:.12em;font-size:.58rem}
.scan-journal-card-copy{min-width:0;display:grid;align-content:center;gap:7px}
.scan-journal-card-copy em,.scan-journal-outcome-copy em{color:#698087;font-size:.58rem;font-style:normal;font-variant-numeric:tabular-nums;letter-spacing:.04em}
.scan-journal-card-copy strong{font-size:clamp(1.12rem,4vw,1.42rem);line-height:1.05;letter-spacing:-.025em;color:#173e49}
.scan-journal-rarity{color:color-mix(in srgb,var(--journal-accent),#1b4a51 48%);font-size:.72rem;font-weight:950}
.scan-journal-card-copy i{color:#6f8588;font-size:.64rem;font-style:normal;line-height:1.4}
.scan-journal-card-action,.scan-journal-outcome-action{width:max-content;display:inline-flex;align-items:center;gap:6px;margin-top:5px;padding:8px 0 5px;border-bottom:2px solid var(--journal-accent);color:#1f685e;font-size:.68rem;font-weight:950}
.scan-journal-card-action svg,.scan-journal-outcome-action svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.scan-journal-card-action.is-new::before,.scan-journal-outcome-action.is-new::before{content:'';width:7px;height:7px;border-radius:50%;background:var(--journal-accent);box-shadow:0 0 9px var(--journal-accent)}
.scan-journal-outcome{position:relative;width:100%;display:grid;grid-template-columns:66px minmax(0,1fr);gap:12px;align-items:center;padding:12px 5px;border:0;border-bottom:1px solid rgba(38,104,112,.17);color:#193f49;background:linear-gradient(90deg,color-mix(in srgb,var(--journal-accent) 8%,transparent),transparent 72%);text-align:left;font:inherit;cursor:pointer;content-visibility:auto;contain:layout paint style;contain-intrinsic-block-size:auto 98px}
.scan-journal-outcome::before{content:'';position:absolute;top:12px;bottom:12px;left:0;width:3px;background:var(--journal-accent)}
.scan-journal-outcome:hover{background:linear-gradient(90deg,color-mix(in srgb,var(--journal-accent) 12%,transparent),rgba(255,255,255,.2) 74%)}
.scan-journal-outcome-visual{position:relative;width:58px;height:74px;margin-left:4px;display:block}
.scan-journal-outcome-visual .scan-queue-preview{width:58px;height:74px;border:1px solid color-mix(in srgb,var(--journal-accent),white 58%);border-radius:13px 5px 13px 5px;filter:saturate(.58) contrast(.9)}
.scan-journal-outcome-mark{position:absolute;right:-10px;bottom:-4px;width:42px;height:42px;display:block}
.scan-journal-outcome-mark img{display:block;width:42px;height:42px;object-fit:contain;filter:drop-shadow(0 4px 5px rgba(18,61,57,.24))}
.scan-journal-outcome-copy{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:3px 9px}
.scan-journal-outcome-copy>em,.scan-journal-outcome-copy>strong,.scan-journal-outcome-copy>i{grid-column:1/-1}
.scan-journal-outcome-state{grid-column:1;width:max-content;max-width:100%;padding:3px 7px;border-left:3px solid var(--journal-accent);background:color-mix(in srgb,var(--journal-accent) 10%,transparent);color:color-mix(in srgb,var(--journal-accent),#274e55 45%);font-size:.52rem;font-weight:950;letter-spacing:.055em}
.scan-journal-outcome-copy strong{margin-top:1px;color:#173e49;font-size:.94rem;line-height:1.12;letter-spacing:-.015em}
.scan-journal-outcome-copy>span:not(.scan-journal-outcome-state):not(.scan-journal-outcome-action){color:#516f76;font-size:.68rem;line-height:1.4}
.scan-journal-outcome-copy i{color:#74878a;font-size:.57rem;font-style:normal;line-height:1.3}
.scan-journal-outcome-action{grid-column:2;grid-row:2;margin:0;padding:4px 0 3px;font-size:.61rem}
.scan-queue-empty { padding: 34px 2px; color: #607980; font-size: .76rem; text-align: center; }
@media (max-width: 380px) {
  .scan-queue-sheet form { padding-inline: 16px; }
  .scan-queue-summary > span { padding-inline: 8px; }
  .scan-queue-summary em { font-size: .5rem; }
  .scan-queue-row { grid-template-columns: 50px minmax(0, 1fr); }
  .scan-queue-row .scan-queue-preview { width: 50px; height: 50px; }
  .scan-queue-actions { grid-column: 2; grid-auto-flow: column; justify-content: start; }
  .scan-journal-card-entry{grid-template-columns:clamp(116px,36vw,136px) minmax(0,1fr);gap:13px;padding-inline:4px}
  .scan-journal-card-copy{gap:5px}
  .scan-journal-outcome{grid-template-columns:61px minmax(0,1fr);gap:10px;padding-inline:4px}
  .scan-journal-outcome-visual,.scan-journal-outcome-visual .scan-queue-preview{width:55px;height:71px}
}
@media (prefers-reduced-motion: reduce) {
  .scan-history-row, .scan-history-filters button::after { transition: none; }
}
.option-list { border-top: 1px solid var(--line-dark); }
.option-row { width: 100%; border: 0; border-bottom: 1px solid var(--line-dark); background: transparent; color: var(--ink); padding: 16px 0; display: flex; justify-content: space-between; gap: 16px; text-align: left; cursor: pointer; }
.option-row strong { font-size: .9rem; }
/* Gewaehlte Zeile in der Korrekturauswahl. Ohne Markierung ist bei 295 Arten
   nicht erkennbar, ob der Tipp angekommen ist. */
.option-row.selected { background: rgba(120,160,90,.16); box-shadow: inset 3px 0 0 var(--lime); padding-left: 10px; }
.option-row span { color: #71766f; font-size: .72rem; }
.animal-picker-list { flex: 1 1 auto; }

.toast { position: fixed; z-index: 100; left: 50%; bottom: calc(90px + env(safe-area-inset-bottom)); transform: translate(-50%, 16px); width: min(calc(100% - 36px), 520px); background: #121714; color: white; padding: 13px 15px; border-left: 3px solid var(--lime); opacity: 0; pointer-events: none; transition: opacity .2s ease, transform .2s ease; box-shadow: 0 14px 40px rgba(0,0,0,.32); font-size: .8rem; }
.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* Auf dem Scanner liegen Licht, Ausloeser und Zoom ueber der Navigation. Der
   normale Bottom-Toast stand deshalb mitten auf diesen drei Werkzeugen. Jede
   Meldung im Scan-Kontext nutzt jetzt die bereits freigehaltene Anweisungszone
   und ersetzt deren Text voruebergehend. So sind auch Kamera-, Offline- und
   Speicherfehler sicher, nicht nur einzelne Erfolgsmeldungen. */
.toast.scan-context { bottom: calc(var(--nav-height) + 124px + env(safe-area-inset-bottom)); }
.app-shell.scan-active .toast.scan-context { bottom: calc(124px + env(safe-area-inset-bottom)); }
.app-shell.scan-toast-visible .scan-instruction { opacity: 0; }
/* OHNE Linien oben und unten. Ein leerer Zustand ist eine MITTEILUNG, keine
   Listenzeile — er trug hier aber beide Kanten und stand zugleich in einer
   Liste, die selbst schon Trennlinien setzt. Sichtbar wurden daraus drei
   Striche um einen einzigen Satz herum, die nichts gliederten. */
.empty-state { padding: 36px 0 30px; color: #737970; }
.empty-state strong { display: block; color: var(--ink); font-size: 1.25rem; margin-bottom: 6px; }

@media (min-width: 760px) {
  .camera-stage { max-width: 600px; margin: 0 auto; box-shadow: 0 0 0 1px rgba(255,255,255,.08), 0 0 80px rgba(0,0,0,.8); }
  .view-scan { background: radial-gradient(circle at 50% 40%, #20291f 0, #090c0a 55%); }
  .content-view { padding-left: 34px; padding-right: 34px; }
  .sheet-dialog { margin-left: auto; margin-right: auto; }
}

@media (max-width: 520px) {
  /* Das Raster regelt seine Spalten ueber auto-fill selbst — hier steht
     deshalb keine Spaltenzahl mehr. Die frueheren Regeln fuer .stat-strip,
     .collection-row und .row-meta sind mit diesen Klassen entfallen. */
  .collection-grid { gap: var(--s2); }
  .mission-row, .trade-row { grid-template-columns: 1fr; }
  .action-square { justify-self: start; }
  .result-primary-line strong { font-size: .75rem; }
  .result-body { padding-left: 16px; padding-right: 16px; }
  .scan-queue-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .new-catch-preview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Fang-Moment ----------
   Der Ergebnis-Screen zeigte bisher nur Textfelder. Der Moment, in dem ein
   Exemplar entsteht, ist aber der Kern der Belohnungsschleife (MVP-SPEC Kap. 3):
   Die Seltenheit wird gestaffelt aufgedeckt statt beilaeufig eingeblendet. */
.capture-reveal { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; }
.capture-reveal[hidden] { display: none; }
.reveal-backdrop {
  position: absolute; inset: 0; background: rgba(4, 7, 5, .82);
  backdrop-filter: blur(14px); animation: revealFade .28s ease both;
}
.reveal-rays {
  position: absolute; width: min(150vw, 900px); aspect-ratio: 1; opacity: 0;
  background: conic-gradient(from 0deg, transparent 0 8deg, var(--reveal-accent, #6f8f74) 8deg 10deg, transparent 10deg 30deg);
  mask-image: radial-gradient(circle, black 0 18%, transparent 62%);
}
/* Je Stufe eigene Keyframes mit festem Endwert.
   Zwei Fallstricke, die hier bereits zugeschlagen haben:
   1. Ein gemeinsames Fade auf opacity:1 macht alle Stufen gleich hell und die
      Abstufung selten/episch/mythisch wirkungslos.
   2. var(--x) als Keyframe-Endwert wird NICHT aufgeloest — die Deklaration wird
      ungueltig und die Strahlen bleiben unsichtbar. Custom Properties sind in
      Keyframes nicht verlaesslich, deshalb hier feste Zahlen. */
/* Opazitaet statisch, nur die Rotation animiert.
   Zwei gescheiterte Varianten davor, damit sie niemand erneut probiert:
   1. Gemeinsames Fade auf opacity:1 machte alle Stufen gleich hell.
   2. Getrennte Keyframes je Stufe blieben wirkungslos — die Deckkraft blieb 0,
      obwohl Regel, Klasse und animation-name nachweislich korrekt gesetzt waren.
   Statisch ist hier schlicht zuverlaessiger, der Effekt bleibt derselbe. */
.capture-reveal.tier-rare .reveal-rays { opacity: .18; animation: revealSpin 16s linear infinite; }
.capture-reveal.tier-epic .reveal-rays { opacity: .3; animation: revealSpin 13s linear infinite; }
.capture-reveal.tier-mythic .reveal-rays { opacity: .45; animation: revealSpin 10s linear infinite; }

.reveal-card {
  position: relative; width: min(90vw, 380px); padding: 26px 22px 20px; border-radius: var(--r-card);
  background: linear-gradient(180deg, rgba(23,30,25,.96), rgba(12,16,13,.98));
  border: 1px solid rgba(255,255,255,.09); text-align: center;
  box-shadow: 0 30px 80px rgba(0,0,0,.6), 0 0 0 1px var(--reveal-accent, transparent) inset;
  animation: revealCard .42s cubic-bezier(.16,1,.3,1) both;
  /* Im dichtesten Fall — Zierrahmen, Traits, Tageslimit mit Freikauf und
     Meldeknopf — wird die Karte 787px hoch. Auf einem 812px-Geraet passt das
     knapp, darunter nicht mehr. Lieber scrollen als den Weiter-Knopf
     abschneiden. */
  max-height: 94dvh; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
}
.reveal-eyebrow {
  display: block; font-size: .64rem; letter-spacing: .22em; font-weight: 800;
  color: rgba(255,255,255,.5); animation: revealUp .3s ease .1s both;
}
/* Der Zierrahmen ist 244px breit und damit knapp 50px groesser als das Foto.
   Der Abstand nach oben und unten haelt ihn von Ueberschrift und Seltenheit
   fern — ohne ihn liegen goldene Ornamente auf dem Text. */
.reveal-photo-wrap { position: relative; width: 148px; height: 148px; margin: 56px auto 52px; }
.reveal-photo-wrap[hidden], .reveal-card-stage[hidden] { display: none; }
.reveal-card-stage {
  position: relative; width: min(70vw, 276px); margin: 18px auto 14px;
  perspective: 1300px; isolation: isolate;
}
.reveal-card-halo {
  position: absolute; z-index: -1; inset: 12% -22% 4%; border-radius: 50%;
  background: radial-gradient(ellipse, color-mix(in srgb, var(--reveal-accent) 42%, transparent), transparent 68%);
  filter: blur(26px); opacity: .82; animation: revealHalo 1.8s ease-in-out .7s both;
}
.reveal-card-flight { width: 100%; transform-style: preserve-3d; }
.reveal-card-mount, .reveal-kartendreher { width: 100%; transform-style: preserve-3d; }
.reveal-card-mount .karte, .reveal-card-mount .kartenrueck > .karte { width: 100% !important; }
.reveal-card-flight.fliegt-ein {
  animation: revealCardApproach 1.05s cubic-bezier(.16,.82,.22,1) both;
}
.reveal-card-flight.fliegt-ein .reveal-kartendreher {
  animation: revealCardTurn 1.05s cubic-bezier(.16,.82,.22,1) both;
}
@keyframes revealCardApproach {
  0% { opacity: 0; transform: translate3d(0, 34vh, -520px) scale(.48); }
  72% { opacity: 1; transform: translate3d(0, -7px, 0) scale(1.025); }
  100% { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}
@keyframes revealCardTurn {
  0% { transform: rotateY(-380deg) rotateZ(-7deg); }
  72% { transform: rotateY(8deg) rotateZ(.8deg); }
  100% { transform: rotateY(0deg) rotateZ(0deg); }
}
@keyframes revealHalo {
  0% { opacity: 0; transform: scale(.55); }
  58% { opacity: 1; transform: scale(1.08); }
  100% { opacity: .82; transform: scale(1); }
}
.reveal-photo {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
  animation: revealPop .5s cubic-bezier(.16,1,.3,1) .12s both;
}
.reveal-ring {
  position: absolute; inset: -8px; border-radius: 50%;
  border: 2px solid var(--reveal-accent, #6f8f74);
  animation: revealRing .7s cubic-bezier(.16,1,.3,1) .18s both;
}
.capture-reveal.tier-epic .reveal-ring,
.capture-reveal.tier-mythic .reveal-ring { box-shadow: 0 0 26px var(--reveal-accent), 0 0 60px var(--reveal-accent) inset; }
/* Ab episch pulsiert der Ring weiter, statt nur einmal aufzuploppen. */
.capture-reveal.tier-epic .reveal-ring { animation: revealRing .7s cubic-bezier(.16,1,.3,1) .18s both, ringPulse 2.4s ease-in-out .9s infinite; }
.capture-reveal.tier-mythic .reveal-ring { animation: revealRing .7s cubic-bezier(.16,1,.3,1) .18s both, ringPulse 1.6s ease-in-out .9s infinite; }

/* Zierrahmen. Quadratisch um das runde Foto — das ist die uebliche Kartenoptik
   und laesst den Rahmen unabhaengig vom Bildinhalt wirken. */
/* Alle fuenf Rahmendateien sind auf ein einheitliches Lochmass normalisiert:
   die Oeffnung ist genau 62 % der Bildkante, die Rahmenstaerke unterscheidet
   sich. 244px * 0.62 = 151px Oeffnung, das Foto ist 148px — der Rahmen legt
   sich also knapp aussen an, statt es zu beschneiden. Wer die Fotogroesse
   aendert, muss diesen Wert mitziehen. */
.reveal-frame {
  position: absolute; left: 50%; top: 50%; width: 244px; height: 244px;
  transform: translate(-50%, -50%); object-fit: contain; pointer-events: none;
  /* Eigene Keyframes: revealPop endet auf transform:none und wuerde die
     Zentrierung ueber translate(-50%,-50%) wieder aufheben. */
  animation: framePop .6s cubic-bezier(.16,1,.3,1) .22s both;
}
@keyframes framePop {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.72); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.reveal-frame[hidden] { display: none; }
.capture-reveal.tier-mythic .reveal-frame { filter: drop-shadow(0 0 12px var(--reveal-accent)); }

/* Kurzer Weissblitz im Moment der Aufdeckung. Nur ab episch — bei haeufigen
   Funden waere das reine Ermuedung. */
.reveal-flash {
  position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none;
}
.capture-reveal.tier-epic .reveal-flash { animation: revealFlash .5s ease-out .1s both; }
.capture-reveal.tier-mythic .reveal-flash { animation: revealFlash .75s ease-out .05s both; }

/* Aufsteigende Funken. Anzahl kommt aus app.js, damit die Staffelung an einer
   Stelle steht und nicht auf CSS und JS verteilt ist. */
.reveal-particles { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.reveal-particle {
  position: absolute; bottom: -12px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--reveal-accent, #8fa894); opacity: 0;
  animation: particleRise var(--rise-duration, 2.6s) ease-out var(--rise-delay, 0s) infinite;
}

/* Lichtwisch ueber die Karte. Ab selten einmalig, ab mythisch wiederholt. */
.capture-reveal.tier-rare .reveal-card::after,
.capture-reveal.tier-epic .reveal-card::after,
.capture-reveal.tier-mythic .reveal-card::after {
  content: ''; position: absolute; inset: 0; border-radius: var(--r-card); pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255,255,255,.16) 50%, transparent 62%);
  background-size: 260% 100%;
  animation: cardSheen 1.5s ease-out .5s 1 both;
}
.capture-reveal.tier-mythic .reveal-card::after { animation: cardSheen 2.6s ease-out .5s infinite; }

/* Nur mythisch ruettelt. Das ist der 0,2-%-Fall — er darf sich koerperlich
   anders anfuehlen als alles darunter. */
.capture-reveal.tier-mythic .reveal-card {
  animation: revealCard .42s cubic-bezier(.16,1,.3,1) both, cardShake .5s ease-in-out .42s 2;
}

@keyframes ringPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.06); opacity: .72; } }
@keyframes revealFlash { from { opacity: .85; } to { opacity: 0; } }
@keyframes cardSheen { from { background-position: 190% 0; } to { background-position: -90% 0; } }
@keyframes cardShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}
@keyframes particleRise {
  0% { opacity: 0; transform: translateY(0) scale(.6); }
  12% { opacity: .95; }
  100% { opacity: 0; transform: translateY(-78vh) scale(.25); }
}

.reveal-rarity {
  display: block; font-size: 1.05rem; font-weight: 900; letter-spacing: .18em;
  color: var(--reveal-accent, #8fa894); animation: revealUp .38s ease .34s both;
}
.reveal-name { margin: 6px 0 2px; font-size: 1.5rem; line-height: 1.15; animation: revealUp .38s ease .44s both; }
.reveal-scientific { display: block; font-size: .82rem; opacity: .55; animation: revealUp .38s ease .5s both; }
.reveal-resolution {
  margin-top: 8px; font-size: .68rem; letter-spacing: .12em; font-weight: 700;
  color: rgba(255,255,255,.42); animation: revealUp .38s ease .56s both;
}
/* Traits als Aufzaehlung, nicht als Kacheln. Runde Pillen liessen jede
   Eigenschaft wie einen Knopf aussehen, obwohl keine anklickbar ist. */
.reveal-traits {
  display: flex; flex-wrap: wrap; gap: 6px 18px; justify-content: center; margin: 14px 0 2px;
  animation: revealUp .4s ease .62s both;
}
.capture-reveal .reveal-traits span {
  font-size: .78rem; font-weight: 700; padding: 0; border: 0; background: none; border-radius: 0;
}
.capture-reveal .reveal-traits span::before { content: "✦"; color: var(--tier-accent, var(--lime)); margin-right: 6px; }

/* Der Fund — wo und wie gescannt wurde. Bewusst leiser als die Artmerkmale
   darueber: Er beschreibt den Scan, nicht das Tier. Eigene Zeile ueber die
   volle Breite, damit er nicht in der Aufzaehlung mitschwimmt. */
.fund-siegel {
  flex-basis: 100%; margin: 6px 0 0; text-align: center;
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--muted); opacity: .75;
}
.reveal-continue {
  margin-top: 18px; width: 100%; padding: 13px; border: 0; border-radius: var(--r-card); cursor: pointer;
  font-weight: 800; font-size: .92rem; color: #08120b; background: var(--reveal-accent, #8fa894);
  animation: revealUp .4s ease .7s both;
}

.reveal-flag { position: absolute; top: 12px; right: 12px; z-index: 2; animation: revealUp .4s ease .8s both; }
.reveal-flag[hidden] { display: none; }

/* Seltenheitsleiter auf dunklem Grund. Gleiche Form wie in der Sammlung, nur
   invertierte Kontraste — der Nutzer soll sie wiedererkennen. */
.rarity-meter.on-dark {
  margin: 0 0 12px; text-align: left;
  background: color-mix(in srgb, var(--tier-accent, #8fa894) 16%, transparent);
  border-color: color-mix(in srgb, var(--tier-accent, #8fa894) 42%, transparent);
  animation: revealUp .4s ease .44s both;
}
.rarity-meter.on-dark .rarity-name { color: var(--tier-accent, #8fa894); font-size: 1.05rem; }
.rarity-meter.on-dark .rarity-odds { color: rgba(255,255,255,.55); }
.rarity-meter.on-dark .rarity-bars i { background: rgba(255,255,255,.16); }
.rarity-meter.on-dark .rarity-bars i.on { background: var(--tier-accent, #8fa894); }

/* Gems mit Symbol statt als weitere Textkachel — der Ertrag ist eine eigene
   Aussage und keine Eigenschaft des Tiers. */
.reveal-gems {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 14px;
  padding: 7px 13px; border-radius: var(--r-soft);
  background: rgba(240,192,74,.14); border: 1px solid rgba(240,192,74,.38); color: #f0c04a;
  animation: revealUp .4s ease .68s both;
}
.reveal-gems[hidden] { display: none; }
.reveal-gems svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linejoin: round; }
.reveal-gems strong { font-size: .86rem; font-weight: 900; font-variant-numeric: tabular-nums; }

/* Ertrag als Zahlenzeile statt als Pillen.
   MVP-SPEC Kap. 7 fordert ausdruecklich "keine dekorativen Glas-Pills ohne
   Funktion" — die Zahl traegt die Aussage, nicht der Container um sie herum.
   Gems in Gold (Taetigkeit), XP in Limette (Artenbreite): zwei Waehrungen,
   zwei Farben, damit niemand sie fuer dieselbe Zahl haelt. */
.reveal-earnings {
  display: flex; justify-content: center; gap: 34px; margin-top: 20px;
}
.earn-item {
  display: grid; justify-items: center; gap: 3px;
  animation: revealUp .4s ease .66s both;
}
.earn-item[hidden] { display: none; }
.earn-icon {
  width: 18px; height: 18px; fill: none; stroke: #f0c04a;
  stroke-width: 1.5; stroke-linejoin: round; margin-bottom: 3px;
}
.earn-item.xp .earn-icon { stroke: var(--lime); }
.earn-value {
  display: flex; align-items: baseline; color: #f0c04a;
  font-variant-numeric: tabular-nums; letter-spacing: -.03em;
}
.earn-item.xp .earn-value { color: var(--lime); }
.earn-value i { font-style: normal; font-size: .9rem; font-weight: 700; opacity: .7; margin-right: 1px; }
.earn-value strong { font-size: 1.45rem; font-weight: 900; line-height: 1; }
.earn-label {
  font-size: .55rem; letter-spacing: .16em; font-weight: 850;
  color: rgba(244,245,239,.42);
}

/* XP-Fortschritt im Fang-Moment. Kantig, nicht als Pille: der Balken ist eine
   Messanzeige, kein Zierelement. */
.reveal-progress { margin-top: 22px; animation: revealUp .4s ease .78s both; }
.reveal-progress[hidden] { display: none; }
.reveal-progress-head {
  display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 7px;
}
.reveal-progress-head span {
  font-size: .58rem; letter-spacing: .16em; font-weight: 850; color: rgba(244,245,239,.45);
}
.reveal-progress-head strong {
  font-size: .68rem; font-weight: 800; font-variant-numeric: tabular-nums;
  color: rgba(244,245,239,.62);
}
.reveal-progress-track {
  height: 3px; background: rgba(255,255,255,.1); overflow: hidden;
}
.reveal-progress-fill {
  height: 100%; width: 0%; background: var(--lime);
  transition: width .85s cubic-bezier(.22,.9,.28,1);
}
/* Der Moment, in dem der Balken ueberlaeuft. Ohne ihn merkt niemand, dass er
   voll war — er spraenge einfach auf den neuen Stand. */
.reveal-progress-fill.overflow {
  box-shadow: 0 0 14px var(--lime), 0 0 4px var(--lime);
  background: #eaffb0;
}

.reveal-levelup {
  margin-top: 16px; align-self: center; padding: 9px 0;
  font-size: .74rem; font-weight: 900; letter-spacing: .2em;
  color: var(--lime); text-align: center;
  border-top: 1px solid rgba(201,255,66,.3);
  border-bottom: 1px solid rgba(201,255,66,.3);
  animation: levelUpIn .5s cubic-bezier(.2,.9,.3,1) both;
}
.reveal-levelup[hidden] { display: none; }
.reveal-levelup::before { content: 'LEVEL '; opacity: .55; letter-spacing: .16em; }
@keyframes levelUpIn {
  0% { opacity: 0; transform: scale(.94); letter-spacing: .5em; }
  60% { opacity: 1; }
  100% { opacity: 1; transform: none; letter-spacing: .2em; }
}

.reveal-limit {
  margin-top: 16px; padding: 13px; border-radius: var(--r-card); text-align: center;
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1);
  animation: revealUp .4s ease .66s both;
}
.reveal-limit-text { margin: 0; font-size: .84rem; line-height: 1.45; }
/* Der Splitterertrag eines Fangs über dem Tageslimit. Gold wie der Ertrag
   überall sonst (Gestaltungsregel 3: eine Akzentfarbe je Aussage). Ist der
   Tagesdeckel erreicht, fällt die Zeile auf den gedämpften Ton zurück —
   sie sagt dann nichts Erfreuliches, aber etwas Nützliches. */
.reveal-shards {
  margin: 9px 0 0; font-size: .8rem; font-weight: 850; line-height: 1.45;
  color: #e0b447;
}
.reveal-shards.leer { color: rgba(255,255,255,.62); font-weight: 700; }
.reveal-limit-countdown {
  margin: 7px 0 0; font-size: .78rem; font-weight: 800; letter-spacing: .04em;
  color: var(--reveal-accent, #8fa894);
}
.reveal-buyout {
  margin-top: 11px; width: 100%; padding: 11px; border-radius: var(--r-card); cursor: pointer;
  font-weight: 800; font-size: .85rem; color: #f3f7f4;
  background: rgba(255,255,255,.09); border: 1px solid rgba(255,255,255,.18);
}
.reveal-buyout:disabled { opacity: .5; cursor: not-allowed; }

/* Ablehnung. Bewusst NICHT die Fang-Bühne: kein Zierrahmen, keine Funken, kein
   Foto, keine Seltenheitsleiter. Der Feldbefund war, dass eine Fangkarte mit
   leeren Feldern trotzdem wie eine Belohnung aussieht und der Grund erst
   darunter in einer Punkteliste auftauchte. Hier trägt das Symbol die Aussage,
   der Grund steht als Überschrift, und darunter steht genau ein Satz, was zu
   tun ist. */
.reject-overlay {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center; padding: 24px;
}
.reject-overlay[hidden] { display: none; }
.reject-card {
  position: relative; width: min(420px, 100%); text-align: center;
  padding: 42px 28px 28px; border: 1px solid rgba(255,255,255,.12); border-left: 4px solid var(--danger);
  border-radius: 22px 8px 22px 8px;
  background: linear-gradient(145deg,rgba(18,24,20,.97),rgba(9,14,11,.94));
  box-shadow: 0 28px 90px rgba(0,0,0,.48); backdrop-filter: blur(16px);
  max-height: calc(100dvh - 32px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  overflow-y: auto; overscroll-behavior: contain;
  animation: rejectIn .32s cubic-bezier(.2, .9, .3, 1) both;
}
.reject-close{position:absolute;top:10px;right:10px;width:44px;height:44px;display:grid;place-items:center;padding:0;border:1px solid rgba(255,255,255,.13);border-radius:12px 5px 12px 5px;background:rgba(255,255,255,.04);color:rgba(244,245,239,.82);font:inherit;font-size:1.35rem;cursor:pointer}
.reject-close:focus-visible,.reject-retry:focus-visible,.reject-details:focus-visible{outline:2px solid var(--lime);outline-offset:3px}
@keyframes rejectIn {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
.reject-mark {
  width: 84px; height: 84px; margin: 0 auto 22px;
  display: grid; place-items: center; border-radius: 50%;
  border: 1px solid rgba(255,115,103,.45); background: rgba(255,115,103,.1);
}
.reject-mark svg {
  width: 40px; height: 40px; fill: none; stroke: var(--danger);
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.reject-eyebrow {
  display: block; font-size: .64rem; letter-spacing: .18em; font-weight: 900;
  color: var(--danger); margin-bottom: 12px; text-transform:none;
}
.reject-reason {
  margin: 0; font-size: 1.12rem; line-height: 1.4; font-weight: 800;
  letter-spacing: -.01em; color: #f4f5ef;
}
.reject-hint { margin: 12px 0 0; font-size: .88rem; line-height: 1.5; color: rgba(244,245,239,.62); }
.reject-hint[hidden] { display: none; }
.reject-retry {
  margin-top: 26px; width: 100%; padding: 15px; border: 0; border-radius: var(--r-card);
  font-size: .92rem; font-weight: 850; cursor: pointer;
  background: var(--lime); color: #101411;
}
.reject-details {
  margin-top: 10px; width: 100%; padding: 12px; border: 0; border-radius: var(--r-card);
  font-size: .82rem; font-weight: 800; cursor: pointer;
  background: rgba(255,255,255,.04); color: rgba(244,245,239,.72);
}

/* Ablehnungsgrund im Ergebnis. Steht direkt unter der Statuszeile, damit der
   Nutzer zuerst liest, WARUM abgelehnt wurde — nicht welche Art vermutet wurde. */
.result-notice {
  margin: 10px 0 0; padding: 12px 14px; border-radius: var(--r-card);
  font-size: .84rem; font-weight: 700; line-height: 1.4;
  background: rgba(255,208,106,.14); border: 1px solid rgba(255,208,106,.42); color: var(--warning);
}
.result-notice.hard {
  background: rgba(255,115,103,.14); border-color: rgba(255,115,103,.45); color: var(--danger);
}
.result-notice[hidden] { display: none; }

.today-badge {
  font-size: .64rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  padding: 2px 7px; border-radius: var(--r-soft); margin-top: 3px;
  background: rgba(240,192,74,.15); border: 1px solid rgba(240,192,74,.35); color: #f0c04a;
}

@keyframes revealFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes revealCard { from { opacity: 0; transform: translateY(26px) scale(.94); } to { opacity: 1; transform: none; } }
@keyframes revealUp { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }
@keyframes revealPop { from { opacity: 0; transform: scale(.72); } to { opacity: 1; transform: none; } }
@keyframes revealRing { from { opacity: 0; transform: scale(1.3); } to { opacity: 1; transform: none; } }
@keyframes revealSpin { to { transform: rotate(360deg); } }

/* Bewegungsreduktion respektieren: die Inhalte bleiben, nur die Bewegung entfaellt. */
@media (prefers-reduced-motion: reduce) {
  .capture-reveal *, .capture-reveal { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
  .reveal-card-flight, .reveal-kartendreher { transform: none !important; }
}

/* Eingabefeld im Sheet-Dialog. Ersetzt window.prompt, das im Android-WebView
   unzuverlaessig ist — die Eingabe kam dort nicht an, die App fragte weiter
   sich selbst ab und bekam HTML statt JSON zurueck. */
/* Reines Weiss war der einzige Ton im Blatt, der ausserhalb der Palette lag —
   neben #ece9dd/#f7f6ee sticht es heraus wie ein Fremdkoerper. Ein Eingabefeld
   ist eine Flaeche wie jede andere. */
.text-input {
  width: 100%; margin-top: 14px; padding: 14px 15px; border-radius: var(--r-card);
  border: 1px solid var(--line-dark); background: var(--paper-2); color: var(--ink);
  font-size: 1rem; font-weight: 600;
}
.text-input:focus { outline: 2px solid #4f6335; outline-offset: 1px; }
.input-note { margin: 9px 0 0; font-size: .76rem; opacity: .62; }

/* Umriss des segmentierten Tieres. Der Template-Tracker verfolgt nur ein
   19x19-Pixel-Muster und weiss nicht, wo das Tier anfaengt — diese Ebene zeigt
   die tatsaechliche Kontur aus der Point-Prompt-Segmentierung. */
.focus-peaking-canvas {
  position: absolute; z-index: 6; inset: 0; width: 100%; height: 100%;
  opacity: 0; pointer-events: none; mix-blend-mode: screen;
  filter: drop-shadow(0 0 2px rgba(0,0,0,.72));
  transition: opacity .1s linear;
}
.focus-peaking-canvas.is-active { opacity: .94; }
.mask-canvas { position: absolute; z-index: 6; inset: 0; width: 100%; height: 100%; pointer-events: none; }

/* Detailansicht eines Exemplars */
/* ============================================================
   Iconsatz
   Strichzeichnungen, gefaerbt ueber currentColor. Kein fill —
   sonst laufen die offenen Pfade (Fahne, Kreuz) voll.
   ============================================================ */
.icon-sprite { display: none; }
svg use, .icon-button svg, .icon-action svg, .fact-row svg {
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon-button svg, .icon-action svg { width: 20px; height: 20px; display: block; }
.icon-button {
  display: grid; place-items: center; width: 38px; height: 38px; padding: 0;
  border-radius: 50%; cursor: pointer; color: #f4f5ef;
  background: rgba(10,13,11,.55); border: 1px solid rgba(255,255,255,.22);
  backdrop-filter: blur(6px);
}
.icon-button.danger { color: var(--danger); border-color: rgba(255,115,103,.45); }

/* ============================================================
   Detailansicht eines Exemplars
   Das eigene Foto traegt die Ansicht, deshalb randlos und gross.
   ============================================================ */
.sheet-dialog.detail-sheet form { padding: 0; }
/* Das Deckbild IST die Galerie: waagerecht wischen blaettert durch alle
   Aufnahmen, ein Tipp oeffnet sie gross.
   HOEHE 210 px — dieselbe Zahl steckt in DECKBILD_VERHAELTNIS in app.js, damit
   die Ausschnitt-Vorschau denselben Rahmen zeigt wie die Karte danach. Wer
   hier aendert, muss dort mitziehen. */
.detail-hero { position: relative; flex: none; height: 210px; overflow: hidden; cursor: zoom-in; }
.detail-hero.no-photo {
  cursor: default;
  background:
    radial-gradient(circle at 72% 18%, rgba(164,214,63,.18), transparent 34%),
    linear-gradient(145deg, #233126, #111813);
}
.detail-hero.no-photo::before {
  content: 'Kein Foto freigegeben';
  position: absolute; inset: 0; display: grid; place-items: center;
  color: rgba(244,245,239,.52); font-size: var(--t-small); font-weight: 750;
  letter-spacing: .04em;
}
.detail-hero-track { width: 100%; height: 100%; transition: transform .22s cubic-bezier(.16,1,.3,1); }
.detail-hero-photo { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Punkte statt Pfeilen: bei bis zu zehn Aufnahmen sagen sie in einer Zeile,
   wieviele es sind und wo man steht — und sie kosten keine Tippflaeche auf
   dem Bild, das man gerade ansehen will. */
.detail-hero-dots {
  position: absolute; left: 0; right: 0; bottom: 8px; z-index: 3;
  display: flex; gap: 5px; justify-content: center; pointer-events: none;
}
.detail-hero-dots i {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(255,255,255,.45); transition: background .2s ease, width .2s ease;
}
.detail-hero-dots i.aktiv { background: #fff; width: 14px; border-radius: 3px; }
.detail-hero-veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(10,13,11,.30) 0%, rgba(10,13,11,0) 34%, rgba(10,13,11,.88) 100%);
}
.detail-hero-flag { position: absolute; top: 14px; right: 14px; }
/* Steht die Karte darunter, tragen Name und wissenschaftlicher Name schon
   auf ihr — hier stuenden sie zweimal direkt uebereinander. Der Hero bleibt
   als Galerie: wischbar, antippbar, mit Punkten und Meldeknopf.
   Er wird zugleich flacher, weil er dann Bild ist und keine Ueberschrift. */
.detail-hero.karte-darunter .detail-hero-copy,
.detail-hero.karte-darunter .detail-hero-veil { display: none; }
.detail-hero.karte-darunter { height: 132px; }
.detail-hero-copy { position: absolute; left: 18px; right: 68px; bottom: 14px; color: #f4f5ef; }
.detail-hero-copy h2 { margin: 0; font-size: 1.7rem; line-height: 1.05; letter-spacing: -.04em; }
.detail-hero-copy em { display: block; font-size: .8rem; opacity: .72; margin-top: 3px; }
/* flex:1 1 auto ist hier die eigentliche Absicherung, nicht nur overflow.
   Ohne die Wachstums-/Schrumpfangabe behaelt der Bereich seine Inhaltshoehe,
   die Karte waechst ueber ihre max-height hinaus, und der Schliessen-Knopf am
   Fuss rutscht aus dem Bild — auf dem Geraet sah das aus, als liesse sich die
   Karte gar nicht mehr schliessen. Aufgefallen ist es, als die Galerieleiste
   dazukam und den Inhalt hoeher machte. */
/* `display: grid` mit gap statt Randabstaenden an den Kindern: die vier
   Bloecke (Seltenheitsleiter, Merkmale, Aufnahmen, Angaben) hatten keinerlei
   Abstand voneinander — gemessen 0 px zwischen Merkmalen und der Ueberschrift
   "Aufnahmen", die dadurch an den Plaettchen klebte. Eine gap am Elternteil
   gilt fuer alle vier und auch fuer jeden, der spaeter dazukommt. */
.detail-body {
  padding: 16px 18px 12px; overflow-y: auto; min-height: 0; flex: 1 1 auto;
  display: grid; align-content: start; gap: var(--s4);
}
/* Dasselbe fuer die uebrigen Karten: der Inhaltsbereich scrollt, Kopf und
   Fussknopf bleiben stehen. */
.sheet-dialog form > .species-body,
.sheet-dialog form > .feed-list { overflow-y: auto; min-height: 0; flex: 1 1 auto; }
/* Waehrend der Wischgeste folgt die Karte dem Finger; danach faehrt sie
   weich zurueck. Die Angabe steht hier und nicht im JavaScript, damit das
   Zuruecksetzen von style.transition den Uebergang wiederherstellt. */
.sheet-dialog, .photo-viewer { transition: transform .22s cubic-bezier(.22,.9,.28,1), opacity .22s ease; }
@media (prefers-reduced-motion: reduce) {
  .sheet-dialog, .photo-viewer { transition: none; }
}

/* ============================================================
   Seltenheitsleiter
   Fuenf Stufen, gefuellt bis zur erreichten. Daneben die ECHTE
   Quote aus rarityFromSeed() in server.js — die Verteilung ist
   fest kodiert und nachpruefbar, also darf sie sichtbar sein.
   Das macht aus einer Vokabel ("Episch") eine Aussage.
   ============================================================ */
.rarity-meter {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s1) var(--s3); align-items: baseline;
  /* Kein Kasten mehr, sondern eine Kante links: die Stufe soll einordnen, nicht
     als eingefaerbte Flaeche mit dem Foto konkurrieren. */
  padding: var(--s2) 0 var(--s2) var(--s3);
  border-left: 2px solid var(--tier-accent, #8fa894);
}
.rarity-meter .rarity-name {
  font-size: 1rem; font-weight: 900; letter-spacing: -.01em;
  color: color-mix(in srgb, var(--tier-accent, #8fa894) 72%, #101411);
}
.rarity-meter .rarity-odds { font-size: .7rem; font-weight: 700; color: #6b706a; font-variant-numeric: tabular-nums; }
.rarity-bars { grid-column: 1 / -1; display: flex; gap: 4px; margin-top: 2px; }
.rarity-bars i { flex: 1; height: 5px; border-radius: 3px; background: rgba(16,20,17,.14); }
.rarity-bars i.on { background: var(--tier-accent, #8fa894); }
/* Nur die zuletzt erreichte Stufe leuchtet — sonst ist nicht ablesbar, wo die
   Skala endet. */
.rarity-bars i.on:last-of-type,
.rarity-bars i.on + i:not(.on) { box-shadow: none; }
.rarity-bars i.tip { box-shadow: 0 0 8px var(--tier-accent, #8fa894); }

/* ============================================================
   Faktenliste
   Icon, Bezeichnung, Wert. Das Icon ersetzt keine Beschriftung,
   es fuehrt das Auge an der Spalte entlang.
   ============================================================ */
.fact-list { display: grid; margin-top: 14px; }
.fact-row {
  display: grid; grid-template-columns: 20px 1fr auto; gap: 12px; align-items: center;
  padding: 11px 0; border-bottom: 1px solid var(--line-dark); font-size: .82rem;
}
.fact-row:last-child { border-bottom: 0; }
.fact-row svg { width: 18px; height: 18px; color: #8a9088; }
/* Dieselbe feste Farbe, derselbe blinde Fleck — siehe `.sheet-dialog p`. */
.fact-row span { color: var(--ink-3); }
.fact-row strong { text-align: right; font-weight: 750; }
.fact-row.mono strong { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .78rem; letter-spacing: .02em; }

/* ============================================================
   Aktionszeile
   Vorher drei volle Balken untereinander. Sie sahen gleich
   wichtig aus, obwohl Abgeben unumkehrbar ist und Schliessen
   folgenlos. Jetzt eine Zeile, Icon plus kurzes Wort.
   ============================================================ */
/* Der untere Rand haelt MINDESTENS 20 px frei, auch wenn
   `env(safe-area-inset-bottom)` 0 meldet. Genau das tut es im Android-WebView,
   solange die App nicht randlos konfiguriert ist — die Leiste sass dann exakt
   auf der Bildkante und verschwand hinter der Gestenleiste des Systems. */
.detail-actions {
  display: flex; gap: 10px; flex: none;
  padding: 12px 18px max(20px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line-dark);
}
/* `min-width: 0` ist hier PFLICHT, nicht Feinschliff.
   Flex-Elemente haben `min-width: auto` und schrumpfen deshalb nie unter ihre
   Inhaltsbreite — bei drei Knoepfen lief die Leiste auf 340 px um 47 px ueber,
   und "Schliessen" stand vollstaendig ausserhalb des Dialogs. Gemessen, nicht
   vermutet. Die Beschriftung kuerzt jetzt, statt den Knopf aus dem Bild zu
   schieben. */
.icon-action {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 46px; padding: 10px 12px; border-radius: var(--r-card); cursor: pointer;
  font-size: .82rem; font-weight: 800; color: var(--ink);
  background: rgba(16,20,17,.06); border: 1px solid var(--line-dark);
}
.icon-action span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.icon-action svg { flex: none; }
.icon-action:disabled { opacity: .45; cursor: not-allowed; }
.icon-action[hidden] { display: none; }

/* Perspektiven-Anzeige waehrend des Scans */
.coverage { position: absolute; z-index: 8; left: 17px; top: 56%; width: 118px; color: white; text-shadow: 0 1px 12px #000; }
.coverage-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 5px; }
.coverage-head span { font-size: .58rem; letter-spacing: .16em; color: var(--lime); font-weight: 900; }
.coverage-head strong { font-size: .82rem; font-variant-numeric: tabular-nums; }
.coverage-track { height: 4px; border-radius: 2px; background: rgba(255,255,255,.24); overflow: hidden; }
.coverage-bar { height: 100%; width: 0; border-radius: 2px; background: var(--lime); transition: width .3s ease; box-shadow: 0 0 10px var(--lime); }

/* Halte-Ring. Ein kurzer Tipp loeste bisher sofort einen Scan aus. */
.press-indicator { position: absolute; z-index: 9; width: 78px; height: 78px; transform: translate(-50%, -50%); pointer-events: none; }
.press-indicator svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.press-track { fill: rgba(0,0,0,.28); stroke: rgba(255,255,255,.42); stroke-width: 2.5; }
.press-progress {
  fill: none; stroke: var(--lime); stroke-width: 3.5; stroke-linecap: round;
  /* 2*pi*r bei r=20 */
  stroke-dasharray: 125.66; stroke-dashoffset: 125.66;
  filter: drop-shadow(0 0 6px var(--lime));
}

/* Tap-to-Focus waehrend eines laufenden Scans. Die Markierung sitzt am
   Tippunkt statt in einem Toast am unteren Rand, wo sie Licht und Zoom
   verdecken wuerde. */
.focus-reticle {
  --focus-color: #fff;
  position: absolute; z-index: 10; width: 58px; height: 58px;
  transform: translate(-50%, -50%) scale(1.34); opacity: 0;
  border: 2px solid var(--focus-color); border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0,0,0,.32), 0 0 18px rgba(0,0,0,.34);
  pointer-events: none;
}
.focus-reticle::before,
.focus-reticle::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  background: var(--focus-color); transform: translate(-50%, -50%);
  box-shadow: 0 1px 2px rgba(0,0,0,.45);
}
.focus-reticle::before { width: 14px; height: 2px; }
.focus-reticle::after { width: 2px; height: 14px; }
.focus-reticle span {
  position: absolute; left: 50%; top: 50%; width: 5px; height: 5px;
  transform: translate(-50%, -50%); border-radius: 50%;
  background: var(--focus-color);
}
.focus-reticle.is-visible {
  animation: focus-reticle-settle 1.15s cubic-bezier(.2,.8,.2,1) both;
}
.focus-reticle.is-success { --focus-color: var(--lime); }
.focus-reticle.is-unconfirmed { --focus-color: #ffcf70; }
.focus-reticle.is-unsupported { --focus-color: #ffcf70; }
@keyframes focus-reticle-settle {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(1.34); }
  14%, 72% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(.94); }
}
@media (prefers-reduced-motion: reduce) {
  .focus-peaking-canvas { transition: none; }
  .focus-reticle.is-visible { animation-duration: .7s; }
}

/* Fehlt die Berechtigung, wird der Kamerawechsel zum Startknopf und faellt
   farblich auf. */
.camera-toggle.needs-permission { background: var(--warning); border-color: var(--warning); color: var(--ink); }

/* "Alle" als erste Kachel der Ringreihe. Sie traegt keinen Ring, weil sie
   keinen eigenen Fortschritt hat — die Zahl allein ist die Aussage. */
.group-ring.alle { justify-content: center; padding: var(--s3) 0; }
.group-alle-zahl {
  font-size: 1.5rem; font-weight: 900; line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: -.03em;
}

/* Hintergrund festhalten, solange ein Sheet offen ist.
   Ein modaler <dialog> macht den Rest des Dokuments zwar inert, verhindert
   aber NICHT das Scrollen dahinter — auf dem Geraet lief die Sammlungsliste
   unter der offenen Detailkarte weiter, sobald man in der Karte wischte.
   :has() traegt das ohne JavaScript; wo es fehlt, bleibt es beim alten
   Verhalten statt etwas kaputtzumachen. */
html:has(dialog[open]) body { overflow: hidden; }

/* Markierungsliste im Karten-Sheet. Sie ist der verlaessliche Weg zum Loeschen:
   ein Knopf im Kartenpopup sitzt am Rand eines Kreises und ist mit dem Finger
   kaum zu treffen. */
.mark-list { display: grid; gap: var(--s2); margin-bottom: var(--s4); }
.mark-row {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) 36px auto auto; align-items: center; gap: var(--s2);
  padding: var(--s2) 0; border-bottom: 1px solid var(--line-hair);
}
.mark-row-symbol { width: 24px; height: 24px; color: var(--ink-3); }
.mark-row-symbol svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.mark-row-copy { display: grid; gap: 1px; min-width: 0; }
.mark-row-copy strong { font-size: var(--t-small); font-weight: 800; }
.mark-row-copy em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
.mark-row-zeigen, .mark-row-edit, .mark-row-weg {
  width: 34px; height: 34px; display: grid; place-items: center;
  border: 1px solid var(--line-hair); border-radius: var(--r-soft);
  background: var(--paper); color: var(--ink-3); cursor: pointer;
}
.mark-row-edit {
  width: auto; grid-auto-flow: column; gap: 5px; padding: 0 9px;
  font: inherit; font-size: var(--t-micro); font-weight: 800;
}
.mark-row-weg {
  width: auto; grid-auto-flow: column; gap: 5px; padding: 0 9px;
  color: #b33b32; border-color: #e0c4c1; font: inherit;
  font-size: var(--t-micro); font-weight: 800;
}
.mark-row-zeigen svg, .mark-row-edit svg, .mark-row-weg svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

@media (max-width: 520px) {
  .mark-row { grid-template-columns: 24px minmax(0, 1fr) 34px 34px 34px; gap: 6px; }
  .mark-row-edit, .mark-row-weg { width: 34px; padding: 0; grid-auto-flow: row; }
  .mark-row-edit span, .mark-row-weg span { display: none; }
}

/* ---- Werkstatt (Merge) ----------------------------------------------------
   Der Splitterbalken traegt die Ansicht: er beantwortet "wie weit bin ich"
   ohne eine Zeile Text. Die Karten darunter sind das Ergebnis. */
/* Uebersicht: eine Zeile je Art, die in das artgebundene Sheet fuehrt. Sie
   traegt selbst keine Bedienung mehr — vorher stand hier die vollstaendige
   Werkstatt je Art untereinander. */
/* ===========================================================================
   KARTENSTUDIO

   Die Karte oben, die Wahl darunter. Beim Scrollen bleibt die Karte stehen:
   wer eine Veredelung antippt, will das Ergebnis sehen und nicht danach
   suchen. Das ist der einzige Grund fuer `position:sticky` hier — es ist
   keine Kopfzeile, sondern das Ergebnis.
   =========================================================================== */
.studio-buehne {
  position: sticky; top: 0; z-index: 4;
  display: grid; grid-template-columns: minmax(0, 300px) 1fr;
  gap: var(--s3); align-items: center;
  max-width: 900px; margin: 0 auto var(--s2);
  padding-block: var(--s2);
  background: var(--paper);
}
#studioJourney:not(:empty) {
  position:relative;
  z-index:12;
  height:90px;
  margin:calc(-28px - env(safe-area-inset-top)) -18px var(--s2);
}
#studioJourney:not(:empty) ~ .back-link,
#studioJourney:not(:empty) ~ .content-header { display:none; }
/* DIE KARTE SCHWEBT UND LAESST SICH DREHEN — sie ist nicht mehr der Knopf zu
   einer zweiten Buehne, sondern selbst eine.

   `perspective` gehoert HIERHER, nicht an den Dreher: eine Perspektive auf dem
   drehenden Element selbst waere eine Fluchtpunktprojektion, die sich
   mitdreht, und die Karte liefe beim Umschlagen auseinander. */
.studio-karte {
  perspective: 1400px; display: grid; place-items: center; width: 100%;
  /* LUFT FUER DIE SCHRAEGE LAGE. Eine geneigte Karte ragt aus ihrem
     Layoutkasten heraus — der Kasten misst die ungedrehte Flaeche. Bei 262 ×
     366 px und 7 Grad um die Querachse sind das
     366·cos7 + 262·sin7 = 395 px, also rund 15 px oben und unten zu viel.
     Ohne diesen Rand schob sich die Kartenkante ueber die Zahlenzeile
     darunter, und „1 von 156" war halb verdeckt. */
  padding-block: 16px 20px;
}
.studio-karte .kartendreher {
  width: 100%; cursor: grab;
  /* `none`, seit die Karte sich in BEIDE Achsen drehen laesst.
     Hier stand `pan-y`: es gab das senkrechte Scrollen an den Browser ab und
     behielt nur das waagerechte Ziehen — genau die Haelfte, die es damals
     gab. Mit der zweiten Achse ist derselbe Wert das Gegenteil einer
     Loesung: Android nimmt den senkrechten Zug fuer sich, bricht ihn mit
     `pointercancel` ab, und die Karte kippt nicht.

     Der Preis ist bekannt und klein: auf der Karte selbst laesst sich die
     Seite nicht mehr scrollen. Sie steht ohnehin oben fest, gescrollt wird
     in der Wahl darunter. */
  touch-action: none;
}
.studio-karte .kartendreher:active { cursor: grabbing; }
.studio-zahl { display: grid; gap: 6px; align-content: start; }
.studio-zahl-kopf {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
}
.studio-zahl-kopf b { font-weight: 900; color: var(--selt-farbe, var(--ink-2)); }
.studio-zahl-kopf b[data-stufe="COMMON"]   { --selt-farbe: #8fa894 }
.studio-zahl-kopf b[data-stufe="UNCOMMON"] { --selt-farbe: #5bbd84 }
.studio-zahl-kopf b[data-stufe="RARE"]     { --selt-farbe: #4aa6e0 }
.studio-zahl-kopf b[data-stufe="EPIC"]     { --selt-farbe: #a877f0 }
.studio-zahl-kopf b[data-stufe="MYTHIC"]   { --selt-farbe: #f0c04a }
/* Die grosse Zahl ist die Antwort auf die einzige Frage der Seite. */
.studio-zahl-wert {
  font-size: clamp(1.6rem, 7vw, 2.4rem); font-weight: 900; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.studio-zahl-wert small {
  font-size: .42em; font-weight: 700; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: var(--track-label);
  margin-inline: .15em;
}
/* Der Satz, der die Zahl uebersetzt. Er ist der eigentliche Inhalt: „1 von
   12.500" sagt fast niemandem etwas, „etwa alle elf Jahre einer" schon. */
.studio-zahl-satz {
  font-size: var(--t-small); color: var(--ink-2); line-height: 1.4; max-width: 34ch;
}
.studio-zahl-fuss {
  display: flex; gap: 4px 14px; flex-wrap: wrap;
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
}
.studio-zahl-fuss i { font-style: normal; font-variant-numeric: tabular-nums; opacity: .7; }
.studio-wahl { max-width: 900px; margin: 0 auto; display: grid; gap: var(--s3); }
/* `min-width:0` IST DER GANZE UNTERSCHIED zwischen einer Reihe, die scrollt,
   und einer Seite, die seitlich wegkippt.

   Ein Rasterfeld hat in der Zeilenachse von Haus aus `min-width:auto`, also
   „mindestens so breit wie mein Inhalt". Die waagerechte Knopfreihe darin
   nimmt sich damit ihre volle Laenge, `overflow-x:auto` greift nie, und die
   Breite schlaegt bis zum `<body>` durch. Gemessen: die Reihe stand mit
   1593 px in einem 375-px-Fenster. */
.studio-wahl > * { min-width: 0; }
.studio-buehne > * { min-width: 0; }
.studio-gruppe h2 {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
  margin: 0 0 8px; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap;
}
.studio-gruppe h2 em {
  font-style: normal; text-transform: none; letter-spacing: 0;
  font-weight: 600; color: var(--ink-3); opacity: .8;
}
/* EINE REIHE JE ACHSE, WAAGERECHT SCROLLBAR.

   Gestapelt ergaben die 37 Knoepfe eine Wand von rund 520 px. Zusammen mit
   einer Karte, die gross genug ist, um eine Veredelung zu zeigen, passt das
   auf kein Handy: man scrollt dann durch einen Schlitz und verliert dabei das
   Ergebnis aus dem Bild — also genau das, wofuer die Seite da ist.

   Waagerecht kostet jede Achse eine Zeile. Der angeschnittene naechste Knopf
   am rechten Rand ist der Hinweis, dass es weitergeht; eine Zeile, die exakt
   buendig endet, liest sich als vollstaendig. */
.studio-knoepfe {
  display: flex; gap: 6px;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: thin; padding-bottom: 4px;
  scroll-snap-type: x proximity;
}
.studio-knopf {
  display: grid; gap: 2px; justify-items: start; text-align: left;
  padding: 8px 10px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line-hair); background: transparent; color: var(--ink-2);
  font: inherit; font-size: var(--t-small);
  flex: 0 0 auto; scroll-snap-align: start; white-space: nowrap;
}
.studio-knopf b { font-weight: 700; }
/* Der Anteil in Prozent steht AN der Wahl, nicht in einer Legende daneben:
   die Frage „wie selten ist das" stellt sich genau beim Antippen. */
.studio-knopf i {
  font-style: normal; font-size: var(--t-micro); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.studio-knopf.aktiv {
  border-color: var(--lime); color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--lime);
}
.studio-schluss {
  margin: 0; padding: var(--s2); border-radius: 10px;
  border: 1px solid var(--line-hair);
  font-size: var(--t-small); line-height: 1.5; color: var(--ink-2);
  display: grid; gap: 6px;
}
.studio-schluss span { color: var(--ink-3); }
/* AUF DEM HANDY GEHOERT DIE HOEHE DER KARTE — gemessen, nicht geschaetzt.

   Erster Versuch: Karte gross und allein oben, 407 von 812 px. Das war zu
   viel, aber nicht wegen der Karte — wegen der Wahl darunter, die als Wand
   aus 37 Knoepfen 520 px brauchte. Die Antwort darauf war, die Karte auf
   132 px zu schrumpfen, und die war falsch: bei 132 px Breite ist eine
   Veredelung nicht mehr zu erkennen, und genau das ist der Zweck der Seite.

   Jetzt schrumpft die WAHL (waagerechte Reihen, eine je Achse, rund 300 px
   statt 520), und die Karte bekommt ihren Platz zurueck:

     Karte 262 px breit  ->  366 px hoch   (45 % der Hoehe)
     Zahlenzeile                    54 px
     Wahl, fuenf Reihen            300 px
                                   ------
                                   720 px von 812

   Der Kopf der Ansicht faellt dafuer weg: auf dieser Seite IST die Karte die
   Ueberschrift. */
@media (max-width: 620px) {
  #studioView .lead-copy { display: none; }
  #studioView .content-header { margin-bottom: var(--s1); }
  .studio-buehne {
    grid-template-columns: 1fr; justify-items: center;
    gap: var(--s1); padding-block: var(--s1);
  }
  .studio-karte { width: min(72vw, 262px); }
  /* Die Zahl wird zur Zeile: Stufe, Veredelung und „1 von N" nebeneinander.
     Gestapelt kostete sie 96 px fuer drei Angaben, die zusammengehoeren. */
  .studio-zahl {
    display: flex; flex-wrap: wrap; align-items: baseline;
    justify-content: center; gap: 4px 10px; text-align: center;
  }
  .studio-zahl-satz { display: none; }   /* der Satz steht unter der Wahl */
  .studio-zahl-fuss { display: none; }   /* die Anteile stehen an den Knoepfen */
  .studio-zahl-wert { font-size: 1.5rem; }
}
/* Der Hinweis erscheint nur da, wo er noetig ist: am Zeiger sieht man den
   Mauszeiger sich aendern, auf dem Handy sieht man gar nichts. */
.studio-tipp {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
}
@media (hover: hover) { .studio-tipp { display: none; } }

.merge-list { max-width: 900px; margin: 0 auto; display: grid; gap: var(--s2); }
.merge-row {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: var(--s3);
  width: 100%; padding: var(--s3); border: 1px solid var(--line-hair);
  background: var(--paper); color: var(--ink); text-align: left; cursor: pointer;
  font-family: inherit;
}
.merge-row:active { background: rgba(16,20,17,.03); }
.merge-row-copy { display: grid; gap: 3px; min-width: 0; }
.merge-row-copy strong { font-size: var(--t-body); font-weight: 800; }
.merge-row-copy em {
  font-size: var(--t-micro); font-style: normal; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.merge-row-meta { display: grid; gap: 2px; justify-items: end; }
.merge-row-meta em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); white-space: nowrap; }
.merge-shards { font-size: var(--t-lead); font-weight: 900; font-variant-numeric: tabular-nums; line-height: 1; }
/* Limette = Handlung. Hier: es reicht zum Schmieden. */
.merge-shards.bereit { color: #4d7a11; }
.merge-bar { height: 3px; background: rgba(16,20,17,.10); overflow: hidden; margin-top: 2px; }
.merge-bar i { display: block; height: 100%; background: var(--lime); transition: width .5s ease; }

/* ---- Werkstatt-Verweis in der Detailkarte ---------------------------------
   Statt eines dritten Knopfes in der Fussleiste (der sie sprengte) ein Block
   im Karteninhalt, der gleich den Splitterstand traegt. */
.merge-teaser {
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: var(--s3);
  width: 100%; padding: var(--s3); cursor: pointer; font-family: inherit;
  border: 1px solid var(--line-hair); background: var(--paper); color: var(--ink); text-align: left;
}
.merge-teaser:active { background: rgba(16,20,17,.04); }
.merge-teaser-icon {
  display: grid; place-items: center; width: 34px; height: 34px; flex: none;
  background: rgba(16,20,17,.06);
}
.merge-teaser-icon svg { width: 18px; height: 18px; fill: currentColor; stroke: none; color: var(--ink-2); }
.merge-teaser-copy { display: grid; gap: 5px; min-width: 0; }
.merge-teaser-copy strong { font-size: var(--t-small); font-weight: 850; }
.merge-teaser-copy em {
  font-size: var(--t-micro); font-style: normal; color: var(--ink-3); letter-spacing: .04em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.merge-teaser-bar { display: block; height: 3px; background: rgba(16,20,17,.10); overflow: hidden; }
.merge-teaser-bar i { display: block; height: 100%; background: var(--lime); transition: width .5s ease; }

/* ---- Artgebundene Werkstatt (Sheet) --------------------------------------- */
.merge-sheet { display: flex; flex-direction: column; padding: 10px 18px max(20px, env(safe-area-inset-bottom)); }
/* Nur die Listen scrollen; Kopf, Splitterstand und die beiden Knoepfe bleiben
   stehen. Ohne diesen Bereich waechst das Sheet ueber seine Hoehe hinaus und
   der Schliessen-Knopf rutscht aus dem Bild — derselbe Fehler wie frueher in
   der Detailkarte. */
.merge-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.sheet-sub { display: block; margin: -2px 0 var(--s4); font-family: Georgia, serif; font-size: var(--t-small); color: var(--ink-3); }
.shard-meter { margin-bottom: var(--s3); }
.shard-bar { height: 8px; background: rgba(16,20,17,.10); overflow: hidden; }
.shard-bar i { display: block; height: 100%; background: var(--lime); transition: width .5s ease; }
.forge-bar { display: flex; align-items: center; justify-content: center; gap: var(--s2); }
.forge-bar svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

/* Die eigenen Exemplare. Waagerecht scrollend statt umbrechend: bei zehn
   Faengen waeren es sonst drei Reihen, und die Karten darunter waeren aus dem
   Bild geschoben. */
.specimen-strip {
  display: flex; gap: var(--s2); overflow-x: auto; scrollbar-width: none;
  padding-bottom: var(--s2); margin-bottom: var(--s4);
}
.specimen-strip::-webkit-scrollbar { display: none; }
/* 86 px, nicht 72: "UNGEWOEHNLICH" ist das laengste Seltenheitswort und wurde
   bei 72 px zu "UNGEWOEHN…" gekuerzt. Die Farbkante allein traegt die Stufe
   nicht — Farbe braucht hier ihr Wort daneben. */
.specimen-lock {
  position: absolute; top: 4px; left: 4px; padding: 1px 4px;
  background: rgba(255,208,106,.94); color: var(--ink);
  font-size: 8px; font-weight: 900; letter-spacing: .04em;
}
.shard-legend {
  display: flex; justify-content: space-between; gap: var(--s3); margin-top: 6px;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: .06em; color: var(--ink-3);
}
.shard-diff { text-transform: uppercase; letter-spacing: var(--track-label); }
.merge-hint { margin: 0 0 var(--s3); font-size: var(--t-micro); color: var(--ink-3); letter-spacing: .04em; }
.merge-cards { display: grid; gap: var(--s4); }
.merge-tier { display: grid; gap: var(--s2); }
.merge-tier-label {
  display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--tier-color, var(--ink-3));
}
.merge-tier-label span {
  padding: 1px 6px; background: var(--tier-color, var(--ink-3)); color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.merge-tier-cards { display: flex; flex-wrap: wrap; gap: var(--s2); }
.merge-tier-hint { margin: 0; font-size: var(--t-micro); color: var(--ink-3); letter-spacing: .04em; }
/* Die Karte traegt einen ZIERRAHMEN je Stufe, kein farbiges Kaestchen.
   Die drei Rahmen (assets/rarity/tier-1..3.png) sind wie die
   Seltenheitsrahmen auf eine Oeffnung von genau 62 % der Bildkante
   normalisiert (scripts/normalize-frames.mjs) — nur die Rahmenstaerke
   unterscheidet sich. Deshalb genuegt EINE Groesse fuer alle drei:
   96 x 0,62 = 59,5 px Oeffnung. Das Foto ist mit 62 px minimal groesser, damit
   an der Kante kein heller Spalt steht.
   Wer die Kartengroesse aendert, muss die Fotogroesse mitziehen. */
.merge-card {
  position: relative; width: 96px; padding: 0; cursor: pointer;
  border: 0; background: transparent;
  transition: transform .14s ease, opacity .14s ease, filter .14s ease;
}
.merge-card-art { position: relative; display: block; width: 96px; height: 96px; }
.merge-card-foto {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 62px; height: 62px; object-fit: cover; display: block;
  background: var(--paper-2);
}
.merge-card-rahmen {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  pointer-events: none;
}
.merge-card.erholt { opacity: .5; filter: grayscale(.7); }
/* touch-action: die Karte darf nicht selbst scrollen, sonst bricht der Browser
   die Zeigerfolge mitten in der Ziehgeste ab (pointercancel). Der Wisch zum
   Scrollen laeuft ueber den Bereich DANEBEN — deshalb steht das nur auf der
   ziehbaren Karte, nicht auf dem Behaelter. */
.merge-card.ziehbar { touch-action: none; }
.merge-card.wird-gezogen { opacity: .35; }
/* Das Ablegeziel bekommt einen Limettenschein UM den Rahmen. `box-shadow` auf
   der Karte selbst zoege ein Rechteck um die durchsichtigen Ecken des
   Zierrahmens — `drop-shadow` folgt der Silhouette. */
.merge-card.zieh-ziel {
  transform: scale(1.08);
  filter: drop-shadow(0 0 3px var(--lime)) drop-shadow(0 0 8px var(--lime));
}
/* Der Geist folgt dem Finger. pointer-events: none ist Pflicht — sonst findet
   elementFromPoint immer nur ihn und nie das Ablegeziel darunter. */
.zieh-geist {
  position: fixed; z-index: 9999; pointer-events: none;
  transform: translate(-50%, -50%) scale(1.1) rotate(-3deg);
  box-shadow: 0 12px 28px rgba(16,20,17,.4);
  opacity: .95;
}
.merge-card-bilanz {
  display: block; font-size: 9px; font-weight: 800; padding: 3px;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
.merge-card-bilanz i { font-style: normal; opacity: .6; }
.merge-card-erholt {
  position: absolute; inset: auto 0 24px; font-size: 8px; font-weight: 800;
  background: rgba(16,20,17,.78); color: var(--paper); padding: 1px 0;
  text-transform: uppercase; letter-spacing: .04em; text-align: center;
}

/* ---- Arena ---------------------------------------------------------------- */
.opponent-list { max-width: 900px; margin: 0 auto var(--s4); display: grid; gap: var(--s2); }

/* ============================================================================
   EIN EXEMPLAR IST EINE KARTE — ueberall (06.08.2026)

   Fuenf Orte zeigten dasselbe Ding auf fuenf Weisen: Fotokachel in der
   Sammlung, Listenzeile in Arena, Feed und Tausch, Datenblatt im Detail. Wer
   sein Tier an einer Stelle wiedererkennen wollte, musste es an jeder neu
   lernen.

   DIE LISTE STELLT SICH SELBST UM. `:has(.karten-kachel)` greift nur, wenn
   tatsaechlich Karten drinstehen — faellt `sammelkarte()` aus (karte.js nicht
   geladen, Exemplar ohne Werte), bleibt der Container die Liste, die er war.
   Zwei Klassen von aussen zu setzen waere dieselbe Regel an zwei Orten, und
   einer davon wird vergessen.

   `minmax(160px, …)`: unter 170 px blendet die Karte ihre Wertkuerzel aus.
   Dieselbe Grenze wie im Sammlungsraster, aus demselben Grund — acht nackte
   Zahlen sagen niemandem, welche welche ist.
   ============================================================================ */
.opponent-list:has(.karten-kachel),
.feed-list:has(.karten-kachel) {
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--s5) var(--s2); align-items: start;
}
/* Was keine Karte ist, nimmt die volle Breite: Zwischenüberschriften, der
   Herausforderungsblock, ein Leerzustand. Ohne diese Zeile bekamen sie eine
   Rasterspalte und standen halb so breit neben einer Karte. */
.opponent-list:has(.karten-kachel) > :not(.karten-kachel),
.feed-list:has(.karten-kachel) > :not(.karten-kachel) { grid-column: 1 / -1; }
.karten-kachel {
  display: flex; flex-direction: column; gap: 6px; align-items: stretch;
  padding: 0; border: 0; background: transparent; color: inherit; font: inherit;
  text-align: left; cursor: pointer; width: 100%;
  transition: translate .16s ease;
}
/* Die Karte traegt ihre eigene Perspektive. Ein `transform` auf der Kachel
   machte einen zweiten Stapelkontext auf und legte sie flach — dieselbe
   Falle wie beim Zierrahmen und in der Kartenbuehne. */
.karten-kachel:active { translate: 0 2px; }
.karten-kachel:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.karten-kachel > .karte { width: 100%; }
.karten-kachel.gesperrt { opacity: .45; cursor: not-allowed; }
.karten-kachel[disabled] { cursor: not-allowed; }
/* Darunter steht NUR, was nicht zum Tier gehoert, sondern zur Situation: wer
   es besitzt, wann es gefangen wurde, warum es gesperrt ist. Name, Seltenheit
   und Werte stehen auf der Karte — sie danebenzusetzen war der Fehler, den
   die Kampfbuehne schon einmal gemacht hat. */
.kachel-fuss {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
  display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap;
  padding-inline: 2px;
}
/* 45×12 px gemessen — die kleinste Zielflaeche der ganzen App. Der Name
   fuehrt in die Sammlung des anderen; er ist ein echter Weg, kein Beiwerk.
   Der Innenabstand ist SENKRECHT ausgeglichen und der Fuss zieht ihn ueber
   negative Aussenabstaende wieder heraus, sonst waechst jede Feedkachel um
   20 px. */
.kachel-fuss .feed-owner-link {
  border: 0; background: transparent; font: inherit; color: var(--ink-2);
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
  padding: 10px 4px; margin: -10px -4px;
}
/* Auf dunklem Grund (Kampfbuehne, Enthuellung) kehrt sich die Fussfarbe um. */
#pvpBuehne .kachel-fuss, .night .kachel-fuss { color: #7f887e; }

/* --- WIE SELTEN IST DIESE KARTE WIRKLICH ---
   Steht UNTER der Karte, nie darauf. Die Sterne auf der Karte kennen nur die
   Seltenheitsstufe; die Veredelung wird unabhaengig gezogen und ist bei den
   oberen Stufen der groessere Faktor. Beides zusammen ist die Zahl, die
   jemand meint, wenn er fragt „wie selten ist die".

   Die Farbe kommt aus der Stufe, damit die Zeile ohne Lesen einordbar ist —
   dieselben fuenf Werte wie `rarityStyle` in app.js und `SELT` in karte.js.
   Ein vierter Ort waere ein vierter, der driftet; deshalb stehen sie hier
   ausdruecklich als Ableitung und nicht als neue Staffel. */
/* ZWEI ZEILEN, IMMER. Nebeneinander passt es nur auf breiten Karten: bei
   160 px Kartenbreite brach „Gewöhnlich · Kehrholo   1 zu 71" um, und der
   Umbruch fiel je Karte anders — mal einzeilig, mal zweizeilig, mal die Zahl
   allein. Ein Raster, dessen Zeilenhöhe je Kachel springt, liest sich als
   Fehler. Zwei feste Zeilen sind eine Zeile mehr und keine Überraschung. */
.kachel-selt {
  display: grid; gap: 1px; justify-items: start;
  padding-inline: 2px; margin-top: 6px;
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; font-weight: 800; color: var(--ink-3);
}
.kachel-selt b { color: var(--selt-farbe, var(--ink-2)); font-weight: 900; }
.kachel-selt i {
  font-style: normal;
  /* Zahlen in Tabellenziffern: „1 zu 1429" und „1 zu 156" stehen unter-
     einander im Raster, und springende Ziffernbreiten lesen sich als Zittern. */
  font-variant-numeric: tabular-nums; color: var(--ink-3); opacity: .75;
}
.kachel-selt[data-stufe="COMMON"]   { --selt-farbe: #8fa894 }
.kachel-selt[data-stufe="UNCOMMON"] { --selt-farbe: #5bbd84 }
.kachel-selt[data-stufe="RARE"]     { --selt-farbe: #4aa6e0 }
.kachel-selt[data-stufe="EPIC"]     { --selt-farbe: #a877f0 }
.kachel-selt[data-stufe="MYTHIC"]   { --selt-farbe: #f0c04a }
/* Im Raster steht die Zeile im Kartenplatz, nicht daneben. */
.kartenplatz { display: flex; flex-direction: column; }

/* Die Karte im Detaildialog. Sie ersetzt Seltenheitsmesser, Werte-Zeile und
   Merkmalspillen — alle drei stehen auf ihr. Zentriert und gedeckelt, damit
   sie auf einem Tablet nicht auf Plakatgroesse waechst: ueber 320 px traegt
   die Karte keine zusaetzliche Information, sie wird nur groesser. */
.detail-karte { width: min(100%, 320px); margin: 0 auto var(--s3); }
.detail-karte > .karte { width: 100%; }
.tier-row {
  display: grid; grid-template-columns: 52px minmax(0, 1fr) auto; align-items: center; gap: var(--s3);
  padding: var(--s2); width: 100%; text-align: left; cursor: pointer;
  border: 1px solid var(--line-hair); border-left: 3px solid var(--tier-color, var(--line-dark));
  border-radius: var(--r-card); background: var(--paper); color: inherit; font-family: inherit;
}
/* Der Seltenheitsrahmen ums Bild.
   Fuer den Kampf ist die Seltenheit fast bedeutungslos — sie geht nur ueber
   die Staerke ein. Sehen will man sie trotzdem: es ist die Eigenschaft, an
   der ein Fang haengt. Als RAHMEN statt als Wort, weil sie hier Beiwerk ist
   und keine Kennzahl; die drei Zahlen darunter sollen die Zeile fuehren. */
.tier-bild {
  width: 52px; height: 52px; display: block; padding: 2px;
  border: 2px solid var(--tier-color, var(--line-dark)); border-radius: var(--r-soft);
}
.tier-row img { width: 100%; height: 100%; object-fit: cover; border-radius: 1px; display: block; }
.tier-row .tile-silhouette { width: 100%; height: 100%; }
.tier-copy { display: grid; gap: 1px; min-width: 0; }
.tier-copy strong { font-size: var(--t-small); font-weight: 800; }
.tier-copy strong em { font-style: normal; font-weight: 700; color: var(--ink-3); font-size: var(--t-micro); }
.tier-copy > em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
/* Element AUSGESCHRIEBEN statt als Anfangsbuchstabe im Punkt.
   Vorher stand dort ein einzelnes "S", "E" oder "W" — im Feldtest kam prompt
   die Frage, was das sein soll. Ein Kuerzel muss man lernen; es spart hier
   vier Zeichen und kostet die Verstaendlichkeit. */
.element-marke {
  display: inline-block; padding: 3px var(--s2); border-radius: var(--r-kante);
  background: var(--element); color: #fff; font-size: var(--t-micro);
  font-weight: 900; letter-spacing: var(--track-label); text-transform: uppercase;
  white-space: nowrap;
}
.battle-log { max-width: 900px; margin: 0 auto; display: grid; gap: 2px; }
.battle-log[hidden] { display: none; }
/* Der Verlauf ist antippbar: "warum habe ich verloren" ist eine Frage, die man
   sich nach dem Kampf stellt, nicht waehrend. */
.battle-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center;
  gap: 2px var(--s3); padding: var(--s3) 0; border: 0;
  border-bottom: 1px solid var(--line-hair); width: 100%; text-align: left;
  background: none; color: inherit; font-family: inherit; cursor: pointer;
}
.battle-row:active { background: var(--paper-2); }
.battle-row .duell { grid-column: 1; }
.battle-copy { grid-column: 1; display: grid; gap: 1px; min-width: 0; }
.battle-copy em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
.battle-row .battle-lohn { grid-column: 2; grid-row: 1 / span 2; align-self: center; }

/* ===========================================================================
   DER VERLAUF — hinter einem Knopf, und als Duell statt als Protokollzeile

   Er stand in der Arena ausgeklappt darunter und war der längste Block der
   Seite. Die Arena beantwortet „wogegen trete ich jetzt an"; was war, ist eine
   andere Frage und bekommt ihre eigene Fläche.
   =========================================================================== */
.verlauf-knopf {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%; max-width: 900px; margin: 0 auto var(--s3);
  padding: var(--s3) 0; min-height: 44px;
  border: 0; border-top: 1px solid var(--line-hair); border-bottom: 1px solid var(--line-hair);
  background: transparent; color: var(--ink); font: inherit; cursor: pointer;
}
.verlauf-knopf-text {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-2);
}
/* DIE BILANZ AUF DEM KNOPF. „Verlauf" allein ist eine Beschriftung ohne
   Inhalt — man weiß vorher nicht, ob dahinter etwas steht. */
.verlauf-knopf-bilanz {
  margin-left: auto; font-variant-numeric: tabular-nums;
  font-size: var(--t-small); font-weight: 850; color: var(--ink-3);
}
.verlauf-knopf-bilanz b { color: var(--ink); }
.verlauf-knopf-bilanz i { font-style: normal; opacity: .45; margin: 0 2px; }
.verlauf-knopf .chest-chevron { width: 18px; height: 18px; opacity: .5; }

.verlauf-dialog h2 { margin: 0 0 var(--s3); }
/* Der Rückweg ist ein Pfeil im Kopf, kein zweiter Dialog: ein <dialog> über
   einem <dialog> liegt im Top-Layer über allem und fängt jeden Klick ab. */
.sheet-zurueck {
  width: 34px; height: 34px; margin-right: var(--s2); padding: 0;
  border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer;
}
.sheet-zurueck svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; }
.sheet-zurueck[hidden] { display: none; }

/* KARTE GEGEN KARTE. Ein Kampf ist eine Begegnung zweier Tiere; als Textzeile
   mit einem Buchstaben davor war er eine Protokollzeile. Man erkennt Tiere am
   Bild schneller als am Namen. */
.duell { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: var(--s2); }
.duell-seite { display: flex; align-items: center; gap: 10px; min-width: 0; }
.duell-seite.gegner { flex-direction: row-reverse; text-align: right; }
.duell-bild {
  flex: 0 0 40px; width: 40px; height: 40px; border-radius: 12px; overflow: hidden;
  background: var(--flaeche-2); display: grid; place-items: center;
}
.duell-bild img { width: 100%; height: 100%; object-fit: cover; }
.duell-bild svg { width: 18px; height: 18px; fill: none; stroke: var(--ink-3); stroke-width: 1.6; }
.duell-name {
  font-size: var(--t-small); font-weight: 800; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Der Rundenstand statt eines Buchstabens: „2 : 3" sagt zusätzlich, wie knapp
   es war — und genau das ist die Frage an einen vergangenen Kampf. */
.duell-stand {
  font-variant-numeric: tabular-nums; font-weight: 900; font-size: var(--t-lead);
  letter-spacing: -.03em; padding: 0 var(--s2);
}
.duell-stand i { font-style: normal; opacity: .35; margin: 0 1px; }
.battle-row.sieg .duell-stand { color: var(--lime); }
.battle-row.niederlage .duell-stand { color: var(--rost); }
.battle-row.remis .duell-stand { color: var(--ink-3); }

/* ===========================================================================
   DER KAMPFBERICHT — was gewählt wurde und was dabei herauskam

   Eine Runde besteht aus einer Entscheidung (welcher Wert) und zwei Einsätzen
   darauf. Ohne die Zerlegung steht dort nur ein Ergebnis, und der Kampf ist
   nicht nacherzählbar.
   =========================================================================== */
.kampf-report { display: grid; gap: var(--s4); }
.kampf-report[hidden] { display: none; }
.report-kopf {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: start; gap: var(--s2);
  padding-bottom: var(--s3); border-bottom: 1px solid var(--line-hair);
}
.report-seite { display: grid; justify-items: center; gap: 4px; text-align: center; min-width: 0; }
.report-bild {
  width: 72px; height: 72px; border-radius: 18px; overflow: hidden;
  background: var(--flaeche-2); display: grid; place-items: center;
}
.report-bild img { width: 100%; height: 100%; object-fit: cover; }
.report-bild svg { width: 24px; height: 24px; fill: none; stroke: var(--ink-3); stroke-width: 1.6; }
.report-seite strong { font-size: var(--t-small); font-weight: 850; }
.report-seite em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
.report-stand {
  align-self: center; font-variant-numeric: tabular-nums;
  font-weight: 900; font-size: 1.6rem; letter-spacing: -.04em;
}
.report-stand i { font-style: normal; opacity: .3; margin: 0 3px; }
.report-kopf.sieg .report-stand { color: var(--lime); }
.report-kopf.niederlage .report-stand { color: var(--rost); }
.report-art { margin: 0; font-size: var(--t-micro); color: var(--ink-3); text-align: center; }

.report-runden { display: grid; gap: var(--s3); }
/* Die Runde trägt links eine Haarlinie in der Farbe des Ausgangs — kein
   Kasten. Sechs umrandete Blöcke übereinander sind sechs Ränder, die um
   Aufmerksamkeit konkurrieren; dieselbe Lehre wie in der Arena. */
.report-runde { display: grid; gap: 4px; padding-left: var(--s3); border-left: 2px solid var(--line-dark); }
.report-runde.sieg { border-left-color: var(--lime); }
.report-runde.niederlage { border-left-color: var(--rost); }
.runde-kopf { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.runde-nr {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.runde-wert { font-size: var(--t-small); font-weight: 850; }
.runde-wer { margin-left: auto; font-size: var(--t-micro); color: var(--ink-3); }
.runde-zeile {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px var(--s2);
  align-items: baseline; font-size: var(--t-micro); color: var(--ink-3);
}
.runde-name { font-weight: 800; color: var(--ink-2); grid-column: 1; }
.runde-rechnung { grid-column: 1; font-variant-numeric: tabular-nums; }
.runde-summe {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
  font-size: var(--t-body); font-weight: 900; font-variant-numeric: tabular-nums; color: var(--ink-2);
}
.runde-zeile.gewinnt .runde-summe { color: var(--lime); }
.runde-zeile.gewinnt .runde-name { color: var(--ink); }

/* ---- Wochenaufgaben ------------------------------------------------------- */
.quest-list { display: grid; gap: var(--s2); }
.quest-row {
  display: grid; grid-template-columns: 1fr 70px auto; align-items: center; gap: var(--s3);
  padding: var(--s2) 0; border-bottom: 1px solid var(--line-hair);
}
.quest-copy { display: grid; gap: 1px; min-width: 0; }
.quest-copy strong { font-size: var(--t-small); font-weight: 700; }
.quest-copy em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }
.quest-row.erfuellt .quest-copy strong { color: var(--ink); font-weight: 850; }
.quest-bar { height: 4px; background: rgba(16,20,17,.10); border-radius: var(--r-kante); overflow: hidden; }
.quest-bar i { display: block; height: 100%; background: var(--lime); transition: width .5s ease; }
.quest-claim {
  padding: 5px 10px; border: 0; border-radius: var(--r-kante); background: var(--lime);
  color: var(--ink); font-family: inherit; font-size: var(--t-micro); font-weight: 900; cursor: pointer;
}
.quest-lohn { font-size: var(--t-micro); font-weight: 800; color: var(--ink-3); }
.quest-lohn.abgeholt { color: #5f8f42; }

/* ============================================================================
   Arena: Modi, Einsatz und Kampfbuehne
   ============================================================================ */

/* Der Moduswechsel traegt den Unterschied im Text, nicht nur im Namen: in der
   Rangliste wechselt die Karte des Verlierers den Besitzer, und das muss vor
   dem ersten Tipp klar sein. Deshalb zwei Zeilen je Knopf statt eines Worts. */
.mode-switch {
  max-width: 900px; margin: 0 auto var(--s4);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s2);
}
.mode-tab {
  min-width: 0; display: grid; gap: 2px; padding: var(--s3);
  border: 1px solid var(--line-hair); background: var(--paper); color: var(--ink-3);
  text-align: left; cursor: pointer; font-family: inherit;
}
/* DREI REITER STATT ZWEI (Wildnis kam am 06.08.2026 dazu). Bei 390 px bleiben
   je 112 px, und `FREUNDSCHAFT` stand mit der weiten Laufweite als
   „FREUNDSCH…" da — ein abgeschnittener Reitername liest sich wie ein
   Darstellungsfehler, nicht wie eine Kampfart. Enger gesetzt passt das
   laengste Wort; abgeschnitten wird nur noch, was ohnehin nicht ginge. */
.mode-tab strong {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: .03em;
  text-transform: uppercase;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mode-tab em {
  font-size: var(--t-micro); font-style: normal; opacity: .8; line-height: 1.3;
}
.mode-tab.active {
  border-color: var(--ink); color: var(--ink);
  box-shadow: inset 0 -3px 0 var(--lime);
}

/* EINSATZ UND ERTRAG — was der Kampf kostet und bringt.

   Vier Zeilen mit festem Kopf links: Einsatz, Risiko, Ertrag, Deckel. Als
   Fliesstext haette es niemand gelesen, und als Tabelle mit Kopfzeile waere
   der Kopf bei vier Zeilen laenger als der Inhalt.

   `.warnung` faerbt ausschliesslich die Risikozeile der Rangliste. Dort geht
   bei einer Niederlage eine Karte an den Gegner — die einzige Angabe auf
   dieser Seite, deren Uebersehen etwas kostet. */
.einsatz-tafel {
  max-width: 900px; margin: 0 auto var(--s4);
  display: grid; gap: 1px; background: var(--line-hair);
  border: 1px solid var(--line-hair);
}
.einsatz-zeile {
  display: grid; grid-template-columns: 82px 1fr; gap: var(--s2);
  align-items: baseline; padding: 10px var(--s3); background: var(--flaeche);
}
.einsatz-kopf {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.einsatz-text { font-size: var(--t-small); color: var(--ink); line-height: 1.35; }
.einsatz-zeile.warnung { background: color-mix(in srgb, var(--rost) 12%, var(--flaeche)); }
.einsatz-zeile.warnung .einsatz-kopf { color: var(--rost); }

/* DEIN KADER — eine waagerecht scrollbare Reihe von Karten.

   Gestapelt braeuchten zwoelf Karten rund 4400 px, und der Gegner darunter
   waere unerreichbar. Dieselbe Loesung wie im Kartenstudio, samt der Falle,
   die dort zugeschlagen hat: `min-width: 0` auf dem Rasterfeld, sonst nimmt
   sich die Reihe ihre volle Laenge und die Breite schlaegt bis zum `<body>`
   durch.

   Der angeschnittene naechste Platz am rechten Rand ist der Hinweis, dass es
   weitergeht — eine buendig endende Reihe liest sich als vollstaendig. */
.kader-block { max-width: 900px; margin: 0 auto var(--s4); min-width: 0; }
/* Die Zahl steht NEBEN der Ueberschrift, nicht darin verklebt. Ohne diese
   Zeile las sie sich als „Dein Kader22 von 22 bereit" — ein Wort, das keines
   ist, und der erste Eindruck war ein Darstellungsfehler. */
.kader-block .block-title {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
#kaderZahl {
  font-size: var(--t-micro); font-weight: 800; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.kader-reihe {
  display: flex; gap: var(--s2); min-width: 0;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: thin; padding-bottom: 6px;
  scroll-snap-type: x proximity;
}
.kader-platz { flex: 0 0 156px; min-width: 0; scroll-snap-align: start; }
.kader-platz > * { width: 100%; }
/* Der leere Zustand gehoert ueber die volle Breite, nicht in einen 156-px-
   Streifen — dort braeche der Satz mit einem Wort je Zeile um. Dieselbe Falle
   wie im `auto-fill`-Raster darunter. */
.kader-reihe > .empty-state { flex: 1 1 100%; }

/* WILDNIS: drei Begegnungen nebeneinander statt untereinander.
   `.opponent-list` ist ein einspaltiges Raster; drei Karten in voller Breite
   waeren 1300 px, und die Regeln darueber staenden allein im Bild. */
.wild-regel {
  max-width: 900px; margin: 0 auto var(--s3); font-size: var(--t-small);
  line-height: 1.45; color: var(--ink-2);
}
.wild-regel strong { color: var(--ink); font-weight: 800; }
.wild-regel #wildWechsel { display: block; margin-top: 4px; color: var(--ink-3); }
#wildListe { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
#wildListe > .empty-state { grid-column: 1 / -1; }
.wild-kachel .kachel-fuss {
  font-size: var(--t-micro); color: var(--ink-3); letter-spacing: .04em;
}

/* Einsatz und Reserve. Raster statt Liste, weil eine Karte hier ein Bild ist
   und keine Zeile. */
.stake-grid {
  max-width: 900px; margin: 0 auto var(--s4);
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: var(--s2);
}
/* Der leere Zustand gehoert ueber die GANZE Breite.
   Ohne diese Zeile landet er in einer 104-px-Rasterspalte, und der Satz
   bricht mit einem Wort je Zeile um — er sieht dann aus wie ein Fehler, nicht
   wie eine Erklaerung. Dieselbe Falle steckt in jedem `auto-fill`-Raster. */
.stake-grid > .empty-state, .stake-grid > .gallery-hint,
.collection-grid > .empty-state { grid-column: 1 / -1; }
.stake-card {
  position: relative; display: grid; gap: 2px; justify-items: center;
  padding: var(--s2) 6px var(--s3); cursor: pointer; font-family: inherit;
  border: 1px solid var(--line-hair); background: var(--paper); color: var(--ink);
  transition: transform .14s ease, border-color .14s ease;
}
.stake-card:active { transform: scale(.97); }
.stake-card:disabled { cursor: default; opacity: .45; }
.stake-art { position: relative; display: block; width: 84px; height: 84px; }
.stake-art img:first-child, .stake-art .tile-silhouette {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 54px; height: 54px; object-fit: cover; background: var(--paper-2);
}
.stake-rahmen { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.stake-card strong {
  font-size: var(--t-micro); font-weight: 850; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stake-card em {
  font-size: 9px; font-style: normal; color: var(--ink-3); letter-spacing: .04em;
}
.stake-erholt {
  position: absolute; top: 6px; left: 6px; padding: 1px 5px;
  background: rgba(255,208,106,.94); color: var(--ink);
  font-size: 8px; font-weight: 900; letter-spacing: .04em;
}

/* Reihenfolge der Aufstellung.
   Sie ist im Blind-Duell die eigentliche Entscheidung: aus fuenf Karten
   kaempfen drei, und keiner sieht die Reihenfolge des anderen. `lineup_slot`
   lag von Anfang an in der Datenbank, war aber nicht einstellbar — der Server
   sortierte ersatzweise nach Stufe.

   Pfeile statt Ziehen: die Kacheln sind 104 px breit und liegen in einem
   umbrechenden Raster. Eine Ziehgeste muesste dort ueber Zeilengrenzen
   funktionieren, und der Zielplatz waere waehrend des Ziehens nicht
   erkennbar. Zwei Pfeile sagen genau, was passiert. */
.stake-slot { display: grid; gap: 2px; }
.stake-order {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 2px;
}
.ordner {
  border: 1px solid var(--line-hair); background: var(--paper); color: var(--ink-2);
  font-size: 11px; line-height: 1; padding: 4px 0; cursor: pointer; font-family: inherit;
  min-width: 0;
}
.ordner:active { background: var(--paper-2); }
.stake-platz {
  min-width: 22px; text-align: center; font-size: 9px; font-weight: 900;
  letter-spacing: var(--track-label); color: var(--ink-3);
}

/* Die Regeln der Rangliste. Nummeriert, weil sie aufeinander aufbauen:
   ohne Einsatz keine Liga, ohne Liga kein Gefecht. */
.regel-liste {
  max-width: 900px; margin: 0 auto var(--s4); padding: 0; list-style: none;
  counter-reset: regel; display: grid; gap: 1px;
}
.regel-liste li {
  counter-increment: regel; display: grid; grid-template-columns: 26px 1fr;
  gap: var(--s3) var(--s2); align-items: baseline;
  padding: var(--s3) 0; border-top: 1px solid var(--line-hair);
  font-size: var(--t-small); color: var(--ink-3); line-height: 1.45;
}
.regel-liste li::before {
  content: counter(regel); grid-row: span 2; align-self: center;
  font-size: var(--t-title); font-weight: 900; color: var(--lime-ink, var(--ink-2));
  font-variant-numeric: tabular-nums;
}
.regel-liste strong { display: block; color: var(--ink); font-weight: 850; }
.regel-liste li:last-child { border-bottom: 1px solid var(--line-hair); }

.liga-hinweis { max-width: 900px; margin: calc(-1 * var(--s2)) auto var(--s4); }
.liga-hinweis p { margin: 0; font-size: var(--t-small); line-height: 1.45; }
.liga-ok { color: #4d7a11; font-weight: 800; }
.liga-fehlt { color: var(--ink-3); }
.liga-fehlt strong { color: var(--ink); }

.opponent-avatar {
  display: grid; place-items: center; width: 48px; height: 48px; flex: none;
  background: var(--paper-2); color: var(--ink-2);
  font-weight: 900; font-size: 1.1rem;
}
.warte-karte {
  max-width: 900px; margin: 0 auto var(--s4); display: grid; gap: var(--s2); justify-items: start;
  padding: var(--s4); border: 1px dashed var(--line-dark); background: rgba(201,255,66,.08);
}
.warte-karte strong { font-size: var(--t-body); font-weight: 850; }
.warte-karte em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }

/* Ablaufbalken der Herausforderung. Sie gilt 90 s — eine Zahl allein liest im
   Zweifel niemand, ein schrumpfender Balken schon. */
.challenge-timer { height: 4px; background: rgba(16,20,17,.10); overflow: hidden; margin-bottom: var(--s4); }
.challenge-timer i { display: block; height: 100%; width: 100%; background: var(--lime); transition: width .25s linear; }
.challenge-actions { margin-top: var(--s2); }

/* ---- Kampfbuehne -----------------------------------------------------------
   Vollbild und dunkel: waehrend der Runden soll nichts anderes um
   Aufmerksamkeit konkurrieren, und die Kartenrahmen kommen auf dunklem Grund
   erst zur Geltung.
   Die Aufteilung ist fest DREIGETEILT (Gegner oben, Mitte, eigene Karte
   unten) — bei zwei Kaempfern nebeneinander waeren die Karten auf 360 px
   halb so gross, und die Rundenanzeige haette keinen Platz. */
.battle-stage {
  width: 100%; max-width: none; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0; overflow: hidden;
  background: #0a0d0b; color: #f4f5ef;
  /* Gestapelt und als Block mittig, NICHT mit Dehnungsreihen zwischen den
     Kaempfern. Mit `1fr`-Reihen trieben Gegner und eigene Karte auf einem
     hohen Schirm bis an die Raender, und zwischen ihnen stand eine leere
     Flaeche, in der die Rundenanzeige verloren wirkte. Feste Abstaende halten
     die drei Teile als eine Szene zusammen — auf jeder Hoehe gleich. */
  display: grid; grid-template-rows: auto auto auto auto;
  align-content: center; gap: var(--s6);
}
.battle-stage::backdrop { background: #0a0d0b; }
.stage-sky {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(120,160,90,.20), transparent 60%),
    radial-gradient(120% 60% at 50% 100%, rgba(201,255,66,.14), transparent 60%);
}
.stage-modus {
  position: relative; justify-self: center;
  padding: 4px 12px; border: 1px solid rgba(255,255,255,.18);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: rgba(255,255,255,.62);
}
.stage-modus.rangliste { border-color: var(--gold); color: var(--gold); }

.stage-kaempfer {
  position: relative; display: grid; align-items: center; gap: var(--s3);
  padding: 0 var(--s5); width: 100%; max-width: 560px; justify-self: center;
}
.stage-gegner { grid-template-columns: auto 1fr; }
.stage-eigen { grid-template-columns: 1fr auto; }
.stage-eigen .stage-info { text-align: right; justify-items: end; }

.stage-karte { position: relative; width: 128px; height: 128px; transition: transform .3s cubic-bezier(.16,1,.3,1); }
.stage-foto {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 80px; height: 80px; object-fit: cover; background: #1a201b;
}
.stage-rahmen { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Der Zusammenstoss: beide fahren zur Mitte. Nur `transform`, damit kein
   Layout angefasst wird und die Buehne ruhig bleibt. */
.stage-gegner .stage-karte.stoss { transform: translateY(26px) scale(1.06); }
.stage-eigen .stage-karte.stoss { transform: translateY(-26px) scale(1.06); }
.stage-karte.treffer { animation: stageTreffer .42s ease both; }
@keyframes stageTreffer {
  0% { transform: translateX(0); filter: none; }
  25% { transform: translateX(-8px) rotate(-3deg); filter: brightness(1.8) saturate(.3); }
  55% { transform: translateX(6px) rotate(2deg); }
  100% { transform: translateX(0); filter: none; }
}

.stage-info { display: grid; gap: 5px; min-width: 0; }
.stage-info strong {
  font-size: var(--t-lead); font-weight: 850; letter-spacing: -.02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stage-meta { display: flex; gap: var(--s2); flex-wrap: wrap; }
.stage-meta em {
  font-style: normal; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border: 1px solid rgba(255,255,255,.16);
  color: var(--element, rgba(255,255,255,.72));
}
/* Die drei Merkmale auf der Buehne.
   Sie ENTSCHEIDEN den Kampf, standen dort aber nicht — man sah nur das
   abgeleitete Element, also das Ergebnis ohne seine Begruendung. Das Merkmal,
   das die laufende Runde bestreitet, leuchtet auf; damit ist sichtbar, WARUM
   die Zahl in der Mitte gerade so aussieht. */
.stage-merkmale { display: flex; gap: 4px; flex-wrap: wrap; }
.stage-merkmale i {
  font-style: normal; font-size: 9px; font-weight: 800; letter-spacing: .03em;
  padding: 2px 6px; border-radius: var(--r-kante);
  background: rgba(255,255,255,.08); color: rgba(255,255,255,.62);
  transition: background .2s ease, color .2s ease, transform .2s ease;
}
.stage-merkmale i.aktiv {
  background: var(--lime); color: #101411; transform: scale(1.06);
}

/* Rundenpunkte. Drei Marken je Seite — daran liest man den Stand ab, ohne
   mitzuzaehlen. */
.stage-punkte { display: flex; gap: 5px; }
.stage-punkte i {
  width: 16px; height: 4px; background: rgba(255,255,255,.18);
  transition: background .3s ease;
}
.stage-punkte i.voll { background: var(--lime); }

.stage-mitte {
  position: relative; justify-self: center; text-align: center;
  padding: 0 var(--s5); width: 100%; max-width: 560px;
  display: grid; gap: 6px; justify-items: center;
}
.stage-runde {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--lime); min-height: 14px;
}
/* DIE EINE ZAHL, auf die es in der Runde ankommt.
   Hier stand eine Frage ("Wer kommt besser heran?") in Kleinschrift — bei
   1,5 Sekunden je Runde liest die niemand. Die Chance in Prozent ist die
   einzige Angabe, die man in der Zeit erfassen kann, und sie beantwortet
   dieselbe Frage besser. */
.stage-frage {
  font-size: 2.1rem; font-weight: 900; letter-spacing: -.04em; min-height: 40px;
  color: rgba(255,255,255,.82); font-variant-numeric: tabular-nums;
}
.stage-frage.gut { color: var(--lime); }
.stage-faktoren { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; min-height: 22px; }
/* EIN Wort statt der Faktorliste.
   Waehrend des Kampfes standen hier bis zu vier Zeilen mit Merkmalsnamen. Sie
   waren in der Rundenzeit nicht lesbar, und wer es doch versuchte, verpasste
   die Animation. Die vollstaendige Rechnung steht im Abschlussbild und
   dauerhaft im Verlauf — dort hat man Zeit dafuer. */
.stage-tendenz {
  display: inline-flex; padding: 3px 10px; border: 1px solid rgba(255,255,255,.14);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: rgba(255,255,255,.78);
  animation: stageFaktor .3s ease both;
}
.stage-tendenz.plus { color: var(--lime); border-color: rgba(160,220,90,.4); }
.stage-tendenz.minus { color: var(--danger); border-color: rgba(200,70,60,.4); }
@keyframes stageFaktor { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.stage-blitz {
  position: absolute; inset: -40px 0; pointer-events: none; opacity: 0;
  background: radial-gradient(closest-side, rgba(255,255,255,.85), transparent 70%);
}
.stage-blitz.an { animation: stageBlitz .32s ease both; }
@keyframes stageBlitz { 0% { opacity: 0; transform: scale(.6); } 40% { opacity: 1; } 100% { opacity: 0; transform: scale(1.25); } }

.stage-abbruch {
  position: absolute; top: calc(var(--s4) + env(safe-area-inset-top)); right: var(--s4);
  padding: 6px 12px; border: 1px solid rgba(255,255,255,.2); background: transparent;
  color: rgba(255,255,255,.6); font-family: inherit;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: .06em; cursor: pointer;
}

/* Abschluss. Liegt UEBER der Buehne statt sie zu ersetzen: die beiden Karten
   bleiben im Bild, und der Ausgang bezieht sich sichtbar auf sie. */
.stage-ende {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(10,13,11,.88); backdrop-filter: blur(3px);
  animation: stageEnde .4s ease both;
}
@keyframes stageEnde { from { opacity: 0; } to { opacity: 1; } }
.stage-ende-inner {
  text-align: center; padding: var(--s5); max-width: 420px; width: 100%;
  display: grid; gap: var(--s3); justify-items: center;
  /* Mit der Rundenrechnung wird das Abschlussbild hoeher als der Schirm.
     Ohne diese drei Zeilen stuende der Weiter-Knopf ausserhalb des Bildes —
     dieselbe Falle wie beim Schliessen-Knopf der Detailkarte. */
  max-height: 100dvh; overflow-y: auto; overscroll-behavior: contain;
}
.stage-rechnung { width: 100%; }
.stage-ende-eyebrow {
  font-size: var(--t-title); font-weight: 900; font-variant-numeric: tabular-nums;
  letter-spacing: .06em; color: rgba(255,255,255,.5);
}
.stage-ende h2 { margin: 0; font-size: 2.4rem; letter-spacing: -.05em; color: rgba(255,255,255,.72); }
.stage-ende.sieg h2 { color: var(--lime); }
.stage-ende p { margin: 0; font-size: var(--t-small); color: rgba(255,255,255,.6); line-height: 1.5; }
.stage-beute { display: grid; gap: 6px; justify-items: center; }
.beute-stueck {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px; border: 1px solid rgba(255,255,255,.16);
  font-size: var(--t-small); font-weight: 800;
}
.beute-stueck svg { width: 15px; height: 15px; fill: currentColor; }
.beute-stueck.gewinn { border-color: var(--lime); color: var(--lime); }
.beute-stueck.verlust { border-color: var(--danger); color: var(--danger); }
.stage-ende .primary-bar { margin-top: var(--s3); }

/* Schmale Geraete: die Karten kleiner, damit die Mitte ihren Platz behaelt. */
@media (max-height: 700px), (max-width: 360px) {
  .stage-karte { width: 104px; height: 104px; }
  .stage-foto { width: 64px; height: 64px; }
  .stage-ende h2 { font-size: 1.9rem; }
}

/* Wer Bewegung reduziert haben will, bekommt den Ausgang ohne Ruckeln — die
   Runden laufen trotzdem, nur ohne Stoss und Blitz. */
@media (prefers-reduced-motion: reduce) {
  .stage-karte, .stage-karte.stoss { transition: none; transform: none; }
  .stage-karte.treffer, .stage-blitz.an, .stage-faktor, .stage-ende { animation: none; }
}

/* ---- Werkstatt-Sheet, neu geordnet -----------------------------------------
   Vorher: Schmieden-Knopf ganz oben, Tierstreifen darunter, Karten unten,
   Schliessen erst nach dem Scrollen ganz am Ende — und dazwischen drei
   Trennlinien, die den Inhalt zerhackten, ohne etwas zu gliedern.

   Jetzt zwei Bloecke mit klarer Aufgabe: SCHMIEDEN (Bestand, Deckbildwahl,
   Knopf — alles, was zu einer Entscheidung gehoert, beieinander) und
   DEINE KARTEN (Leiter, Hinweis, Karten). Getrennt werden sie durch Abstand
   und Flaeche, nicht durch Striche. */

.sheet-head { flex: none; padding-bottom: var(--s3); }
.sheet-head-row { display: grid; grid-template-columns: 1fr auto; align-items: start; gap: var(--s3); }
.sheet-head-copy { min-width: 0; }
.sheet-head-copy h2 { margin: 3px 0 0; }
/* Der Ausweg oben rechts. Vorher gab es nur den Knopf am Ende der Liste — bei
   mehreren Karten musste man erst durch alles scrollen, um herauszukommen. */
.sheet-head .icon-button { margin-top: 6px; }

.forge-block {
  display: grid; gap: var(--s3);
  padding: var(--s4); margin-bottom: var(--s5);
  background: rgba(16,20,17,.04); border: 1px solid var(--line-hair);
}
.forge-zahl { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); }
.forge-haben { display: inline-flex; align-items: baseline; gap: 6px; font-size: var(--t-small); }
.forge-haben svg { width: 14px; height: 14px; fill: currentColor; color: var(--ink-3); align-self: center; }
.forge-haben strong { font-size: var(--t-title); font-weight: 900; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.forge-kosten {
  font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3); white-space: nowrap;
}
/* Die eine Zahl, die wirklich interessiert. "120 von 50 Splittern" las sich
   falsch herum — als fehle etwas, obwohl es reicht. */
.forge-stand { margin: 0; font-size: var(--t-small); color: var(--ink-3); }
.forge-stand.bereit { color: #4d7a11; font-weight: 800; }
/* Woher Splitter kommen. Eine Zahl ohne Herkunft liest sich wie ein Fehler:
   "2 von 50", obwohl man die Art genau einmal gefangen hat. */
.splitter-quellen {
  margin: 0; padding: 0; list-style: none; display: grid; gap: 3px;
  font-size: var(--t-micro); color: var(--ink-3); line-height: 1.45;
}
.splitter-quellen li { padding-left: var(--s3); position: relative; }
.splitter-quellen li::before {
  content: ''; position: absolute; left: 0; top: .55em;
  width: 5px; height: 5px; background: var(--ink-3);
}
/* Tagesdeckel erreicht: die Zeile trägt dann die Aussage, nicht mehr nur die
   Aufzählung. */
.splitter-quellen li.erschoepft { color: var(--ink); font-weight: 800; }
.splitter-quellen li.erschoepft::before { background: var(--ink); }
.forge-auswahl { display: grid; gap: var(--s2); }
.forge-auswahl h4 {
  margin: 0; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.forge-block .primary-bar { margin-top: 0; }
.forge-note { margin: 0; font-size: var(--t-micro); color: var(--ink-3); line-height: 1.4; }

/* Deckbildwahl. Die Exemplare sind hier eine echte Wahl — vorher standen sie
   nur als Streifen da und sahen trotzdem nach einer aus. */
.specimen {
  position: relative; flex: none; width: 86px; margin: 0; padding: 0;
  display: grid; gap: 3px; justify-items: center; cursor: pointer;
  background: transparent; border: 2px solid transparent; font-family: inherit; color: inherit;
  transition: border-color .14s ease;
}
.specimen img, .specimen .tile-silhouette {
  display: block; width: 82px; height: 74px; object-fit: cover;
  background: var(--paper-2); border-bottom: 3px solid var(--tile-accent, transparent);
}
.specimen-name {
  font-size: 8px; font-weight: 850; letter-spacing: .02em; text-transform: uppercase;
  color: var(--tile-accent, var(--ink-3)); max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.specimen.gewaehlt { border-color: var(--ink); }
.specimen.gewaehlt::after {
  content: '✓'; position: absolute; top: 3px; right: 3px;
  width: 17px; height: 17px; display: grid; place-items: center;
  background: var(--ink); color: var(--paper); font-size: 10px; font-weight: 900;
}

.karten-block { display: grid; gap: var(--s3); }
.karten-kopf h3 {
  margin: 0; font-size: var(--t-micro); font-weight: 850;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
}
.karten-kopf h3 span { font-variant-numeric: tabular-nums; }

/* Stufenleiter. Sie erklaert das System, statt es vorauszusetzen: was es zu
   erreichen gibt, was es kostet und wo man steht — auf einen Blick.
   Vorher stand ueber den Kaertchen nur "Stufe 1", und was eine Stufe
   ueberhaupt ist, stand nirgends. */
.stufen-leiter {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s2);
}
.stufe {
  position: relative; display: grid; justify-items: center; gap: 2px;
  padding: var(--s2) 4px; border: 1px solid var(--line-hair); background: var(--paper);
  opacity: .5;
}
.stufe.erreicht { opacity: 1; border-color: var(--tier-color, var(--line-dark)); }
.stufe img { width: 40px; height: 40px; }
.stufe strong {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: .04em;
  text-transform: uppercase; color: var(--tier-color, var(--ink-3));
}
.stufe em { font-size: 9px; font-style: normal; color: var(--ink-3); }
/* WAS DER RANG AN VITRINE BRINGT (07.08.2026) — in Gold, weil es ein ERTRAG
   ist. Die Zeile darueber sagt, was er KOSTET; beide gleich zu faerben hiesse,
   den Unterschied wegzunehmen, um den es geht. Gold ist in dieser App
   durchgehend „Ertrag" (Gestaltungsgrundlage weiter oben). */
.stufe-vitrine { font-size: 9px; font-weight: 800; color: var(--gold); }
.stufe-vitrine.leer { color: var(--ink-3); font-weight: 500; }
/* Der Pfeil zwischen den Stufen: er macht aus drei Kaesten eine Abfolge. */
.stufe + .stufe::before {
  content: '›'; position: absolute; left: calc(var(--s2) / -2 - 3px); top: 50%;
  transform: translateY(-50%); color: var(--ink-3); font-size: 14px; font-weight: 900;
}

/* ---- Ausschnitt des Deckbilds ----------------------------------------------
   Der Rahmen hat GENAU das Seitenverhaeltnis, das die Detailkarte spaeter
   zeigt. Verschieben und Zoom wirken direkt in derselben Vorschau. */
.viewer-frame {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-items: center; padding: var(--s5);
  background: rgba(10,13,11,.94); backdrop-filter: blur(4px);
}
.frame-inner { width: 100%; max-width: 460px; display: grid; gap: var(--s4); }
.frame-hint { margin: 0; text-align: center; font-size: var(--t-small); color: rgba(255,255,255,.66); line-height: 1.45; }
.frame-stage {
  position: relative; width: 100%; overflow: hidden; cursor: grab;
  background: #11150f; border: 1px solid rgba(255,255,255,.24);
  /* Der Finger zieht das Bild — der Browser darf hier nicht scrollen. */
  touch-action: none;
}
.frame-stage:active { cursor: grabbing; }
.frame-image {
  width: 100%; height: 100%; object-fit: cover; display: block;
  pointer-events: none; user-select: none; will-change: transform;
}
/* Zwei Hilfslinien im Drittelraster. Sie sagen ohne Worte, dass hier
   ausgerichtet wird und nicht nur betrachtet. */
.frame-stage::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to right, transparent 33.33%, rgba(255,255,255,.18) 33.33%, rgba(255,255,255,.18) calc(33.33% + 1px), transparent calc(33.33% + 1px),
      transparent 66.66%, rgba(255,255,255,.18) 66.66%, rgba(255,255,255,.18) calc(66.66% + 1px), transparent calc(66.66% + 1px)),
    linear-gradient(to bottom, transparent 50%, rgba(255,255,255,.18) 50%, rgba(255,255,255,.18) calc(50% + 1px), transparent calc(50% + 1px));
}
.frame-zoom { display: grid; gap: var(--s2); color: #fff; }
.frame-zoom > span {
  display: flex; align-items: center; justify-content: space-between;
  font-size: var(--t-small); font-weight: 800;
}
.frame-zoom output {
  min-width: 3.2rem; text-align: right; color: var(--lime);
  font-variant-numeric: tabular-nums;
}
.frame-zoom input {
  width: 100%; min-height: 32px; margin: 0; accent-color: var(--lime);
  cursor: ew-resize;
}
.frame-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
.frame-actions .primary-bar, .frame-actions .secondary-bar { margin-top: 0; min-height: 48px; }

/* ---- Die drei Werte eines Tiers --------------------------------------------
   Ausdauer (wie oft), Staerke (wie stark), Merge-Wert (was es in der Werkstatt
   bringt). Sie messen Verschiedenes und stehen deshalb NEBENEINANDER — als
   Liste untereinander sahen sie aus wie drei Auspraegungen derselben Sache.

   Ausdauer als Punkte statt als Zahl: "2/3" muss man lesen, drei Punkte von
   denen zwei gefuellt sind, sieht man. */
/* RICHTUNGSPFEILE IM SUCHER.

   Vier halbtransparente Pfeile um die Bildmitte, die langsam nach aussen
   wandern — eine Andeutung, keine Ansage. Sie ersetzen den Anweisungstext,
   den waehrend eines 2,3-Sekunden-Scans ohnehin niemand liest (Begruendung
   bei aktualisiereBewegungspfeile in app.js).

   `pointer-events: none` und `touch-action: none`: die Pfeile liegen ueber der
   Kamerabuehne, auf der die Tipp- und Ziehgesten laufen. Ohne beides faengt
   die Ueberlagerung genau die Geste ab, mit der man das Tier antippt. */
.move-hints {
  position: absolute; inset: 0; pointer-events: none; touch-action: none;
  z-index: 4;
}
.move-arrow {
  position: absolute; left: 50%; top: 50%;
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  display: grid; place-items: center;
  opacity: .3; transition: opacity .18s ease;
}
.move-arrow svg { width: 24px; height: 24px; fill: #fff; stroke: none; filter: drop-shadow(0 1px 2px rgba(0,0,0,.6)); }
/* Die Ruhebewegung ist bewusst langsam und klein: sie soll den Blick nicht vom
   Tier wegziehen, sondern nur sagen, dass hier etwas gemeint ist. */
.move-arrow.oben   { animation: pfeilOben 2.6s ease-in-out infinite; }
.move-arrow.unten  { animation: pfeilUnten 2.6s ease-in-out infinite; }
.move-arrow.links  { animation: pfeilLinks 2.6s ease-in-out infinite; }
.move-arrow.rechts { animation: pfeilRechts 2.6s ease-in-out infinite; }
@keyframes pfeilOben   { 0%,100% { transform: translateY(-74px); } 50% { transform: translateY(-88px); } }
@keyframes pfeilUnten  { 0%,100% { transform: translateY(74px); }  50% { transform: translateY(88px); } }
@keyframes pfeilLinks  { 0%,100% { transform: translateX(-74px); } 50% { transform: translateX(-88px); } }
@keyframes pfeilRechts { 0%,100% { transform: translateX(74px); }  50% { transform: translateX(88px); } }
/* Erkannte Richtung. Limette heisst in dieser App "Handlung" — und gemeint ist
   hier die Handlung des NUTZERS, nicht eine Aufforderung an ihn. */
.move-arrow.aktiv { opacity: 1; }
.move-arrow.aktiv svg {
  fill: var(--lime);
  filter: drop-shadow(0 0 6px rgba(164,214,63,.75)) drop-shadow(0 1px 2px rgba(0,0,0,.6));
}
/* Wer Bewegung reduziert haben will, bekommt die Pfeile ohne Wanderung — die
   Aussage steckt in Position und Aufleuchten, nicht in der Animation. */
@media (prefers-reduced-motion: reduce) {
  .move-arrow { animation: none; }
  .move-arrow.oben { transform: translateY(-80px); }
  .move-arrow.unten { transform: translateY(80px); }
  .move-arrow.links { transform: translateX(-80px); }
  .move-arrow.rechts { transform: translateX(80px); }
}

.werte-zeile {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  margin-top: 5px; font-size: var(--t-micro); font-weight: 850;
  font-variant-numeric: tabular-nums; color: var(--ink-3);
}
.wert { display: inline-flex; align-items: center; gap: 3px; }
.wert svg { width: 11px; height: 11px; fill: currentColor; }
.wert.staerke { color: #7a5cb8; }
.wert.merge { color: #a8791c; }
/* Kampflevel und Merge-Stufe sind ZAEHLER, keine Messwerte — deshalb als
   Plaettchen mit Rahmen statt als Zahl mit Symbol. Der Unterschied ist der
   zwischen "so stark ist es" und "so oft ist etwas passiert". */
.wert.level, .wert.stufe {
  padding: 1px 5px; border: 1px solid currentColor; border-radius: var(--r-kante);
  font-size: 9px; letter-spacing: .04em;
}
.wert.stufe { color: #a8791c; }

/* DAS KAMPFLEVEL IN DREI STUFEN.

   Vorher stand dort "L2" — ein Buchstabe, den man sich erklaeren lassen muss,
   und eine Zahl, die wie eine Sortierkennung aussieht. Dabei ist das Level das
   Einzige an einem Tier, das man sich ueber Siege ERARBEITET und bei einer
   einzigen Niederlage komplett verliert.

   Die Staffelung bildet die Mechanik ab, nicht den Geschmack (Begruendung bei
   kampflevelPlakette in app.js): 1 ist der Ausgangszustand, 2-5 die wachsende
   Serie, ab 6 ist der Bonus am Anschlag — und dort gilt zugleich, dass wer
   diese Serie bricht, gleich zwei Level gewinnt. Ein Tier auf der Hoechststufe
   traegt ein Ziel auf dem Ruecken, und das soll man ihm ansehen.

   Die Farbe bleibt Limette. Gold heisst in dieser App "Ertrag"; ein Kampflevel
   ist Fortschritt. Die Steigerung laeuft deshalb ueber FUELLUNG und Gewicht,
   nicht ueber einen Farbwechsel — dieselbe Regel wie bei den Seltenheiten,
   wo die Form die Aussage traegt. */
.wert.level {
  color: #4d7a11; gap: 3px; font-weight: 900;
  font-size: 10px; letter-spacing: .02em;
}
.wert.level svg { width: 9px; height: 9px; fill: currentColor; stroke: none; }
/* Ruhig: noch keine Serie. Der Stern ist da, aber zurueckgenommen — sonst
   sieht der Ausgangszustand nach Auszeichnung aus. */
.wert.level.ruhig { opacity: .72; }
/* Aktiv: die Serie laeuft. Gefuellter Grund, damit sie sich vom Ausgangs-
   zustand abhebt, ohne schon nach Endstufe auszusehen. */
.wert.level.aktiv { background: rgba(77,122,17,.13); border-color: rgba(77,122,17,.55); }
/* Hoechststufe: umgekehrter Kontrast plus Schein. Das ist die einzige Stelle
   in der Werteleiste, die leuchtet — sie ist auch die einzige, die selten ist. */
.wert.level.max {
  background: var(--lime); border-color: var(--lime); color: #101411;
  box-shadow: 0 0 0 1px rgba(164,214,63,.35), 0 0 10px rgba(164,214,63,.45);
}
/* Auf der Kampfbuehne, wo die Plakette allein steht statt in einer Leiste. */
.wert.level.gross { font-size: 12px; padding: 3px 8px; gap: 4px; }
.wert.level.gross svg { width: 11px; height: 11px; }
/* Dunkler Grund (Kampfbuehne, Betrachter): der Grundton kehrt sich um. */
.battle-stage .wert.level, .photo-viewer .wert.level { color: var(--lime); }
.battle-stage .wert.level.aktiv { background: rgba(164,214,63,.16); border-color: rgba(164,214,63,.5); }
.battle-stage .wert.level.max { color: #101411; }
/* `.wert.ausdauer` STAND HIER (bis 07.08.2026) — drei Punkte je Exemplar in
   der Werteleiste. Ausdauer gehoert jetzt dem Spieler; die Punkte leben
   weiter, aber genau einmal, in `.ausdauer-punkte` in der Arena. */

/* `.tier-row` ist die EINE Zeile fuer ein Exemplar — Arena, Tierauswahl und
   Tauschbrett benutzen dieselbe. Vorher hiess sie `.opponent-row` und stand
   nur in der Arena; die Auswahl im Tausch war eine reine Textzeile, in der
   weder Foto noch Werte vorkamen. Zwei Darstellungen desselben Gegenstands
   driften auseinander, sobald jemand eine anfasst.

   Nicht handelbar (auf Expedition oder in einem fremden Angebot gebunden):
   sichtbar, aber nicht waehlbar. Ausblenden waere schlechter — der Spieler
   sucht ein Tier, das er kennt, und faende es einfach nicht. */
.tier-row[disabled] { opacity: .42; cursor: not-allowed; }
.tier-row .tier-sperre {
  font-size: var(--t-micro); font-weight: 850; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3); white-space: nowrap;
}

/* ---- Kampfrechnung: warum es so ausgegangen ist ---------------------------
   Sie steht im Abschlussbild und im Verlauf, NICHT waehrend des Kampfes.
   Dort standen Rundenname, Frage und bis zu vier Faktorzeilen; bei 1,5
   Sekunden je Runde war das nicht lesbar, und wer es doch versuchte,
   verpasste die Animation. Hier hat man Zeit — und genau hier stellt sich die
   Frage "warum habe ich verloren".

   Ein Kampfsystem, dessen Zahlen niemand nachrechnen kann, wirkt manipuliert,
   auch wenn es fair ist. */
.rechnung-liste {
  margin: var(--s3) 0 0; padding: 0; list-style: none; display: grid; gap: 2px;
  text-align: left;
}
.rechnung-liste li {
  padding: var(--s2) 0; border-top: 1px solid var(--line-hair);
  display: grid; gap: 5px;
}
.rechnung-kopf {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: var(--s2);
  align-items: baseline;
}
.rechnung-kopf strong {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.rechnung-kopf em {
  font-style: normal; font-size: var(--t-title); font-weight: 900;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.rechnung-kopf b {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.rechnung-liste li.gewonnen .rechnung-kopf b { color: #4d7a11; }
.rechnung-liste li.verloren .rechnung-kopf b { color: var(--danger); }
.rechnung-faktoren { display: flex; flex-wrap: wrap; gap: 4px; }
.rechnung-faktor {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px;
  border: 1px solid var(--line-hair); border-radius: var(--r-kante);
  font-size: var(--t-micro); font-weight: 700; color: var(--ink-3);
}
.rechnung-faktor i { font-style: normal; font-weight: 900; font-variant-numeric: tabular-nums; }
.rechnung-faktor.plus i { color: #4d7a11; }
.rechnung-faktor.minus i { color: var(--danger); }

/* Im Abschlussbild liegt die Rechnung auf dunklem Grund. */
.stage-rechnung .rechnung-liste li { border-top-color: rgba(255,255,255,.12); }
.stage-rechnung .rechnung-kopf strong,
.stage-rechnung .rechnung-faktor { color: rgba(255,255,255,.7); }
.stage-rechnung .rechnung-kopf em { color: rgba(255,255,255,.92); }
.stage-rechnung .rechnung-faktor { border-color: rgba(255,255,255,.14); }
.stage-rechnung .rechnung-liste li.gewonnen .rechnung-kopf b,
.stage-rechnung .rechnung-faktor.plus i { color: var(--lime); }

/* ===========================================================================
   HEUTE — die Startseite
   ===========================================================================
   Jeder Wert kommt aus system.css. Keine einzige Farbe steht hier hart: das
   ist die Regel, an der die 505 Umgehungen im Bestand gemessen werden.
--------------------------------------------------------------------------- */
.view-heute { padding-top: calc(20px + env(safe-area-inset-top)); }

.heute-kopf {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s5);
}
.heute-gruss h1 {
  margin: var(--s1) 0 0; font-size: var(--t-display); line-height: 1.05;
  letter-spacing: -.02em;
}
.heute-gruss .label-micro { color: var(--ink-3); }

/* Das Profil sitzt hier statt in der Leiste. Die Stufe steht IM Knopf: sie ist
   der einzige Grund, warum jemand aufs Profil schaut, und spart die Zeile
   darunter. */
.heute-avatar {
  position: relative; flex: none; width: 46px; height: 46px;
  border-radius: var(--r-rund); border: 1px solid var(--line-dark);
  background: var(--paper-2); color: var(--ink);
  display: grid; place-items: center; cursor: pointer;
}
.heute-avatar svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.6; }
.heute-stufe {
  position: absolute; right: -4px; bottom: -4px; min-width: 20px; height: 20px;
  padding: 0 5px; border-radius: var(--r-rund);
  background: var(--lime); color: var(--auf-akzent);
  font-size: 11px; font-weight: 850; line-height: 20px; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* DIE HAUPTAKTION IST DAS GROESSTE ELEMENT DER SEITE — und das ist die ganze
   Aussage. Vorher war der Scan ein Reiter unter fuenf gleich grossen. */
.heute-aktion {
  width: 100%; display: flex; align-items: center; gap: var(--s4);
  padding: var(--s4); margin-bottom: var(--s4);
  border: 0; border-radius: var(--r-card); cursor: pointer; text-align: left;
  background: var(--lime); color: var(--auf-akzent);
  box-shadow: var(--hebung);
}
.heute-aktion-symbol { flex: none; display: grid; place-items: center; width: 40px; height: 40px; }
.heute-aktion-symbol svg { width: 30px; height: 30px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.heute-aktion-text { display: grid; gap: 2px; min-width: 0; }
.heute-aktion-text strong { font-size: var(--t-lead); font-weight: 850; letter-spacing: -.01em; }
.heute-aktion-text em {
  font-style: normal; font-size: var(--t-small); font-weight: 650;
  /* Auf Limette, nicht auf dem Grund: deshalb eine gedaempfte Fassung von
     --auf-akzent statt --ink-2, das hier unlesbar waere. */
  color: color-mix(in srgb, var(--auf-akzent) 68%, var(--lime));
}

/* Vier Kacheln, je ein Verweis. Eine Zahl ohne Weg dahin ist eine Sackgasse. */
.heute-kacheln { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s2); margin-bottom: var(--s4); }
/* Der Balken spannt ueber alle vier Spalten. Er steht hier und nicht in einer
   Kachel: „bis Stufe 10" brach dort dreizeilig um, und die volle Breite
   beantwortet „wie weit noch" ohnehin besser als eine Restzahl. */
.heute-stufenbalken {
  grid-column: 1 / -1; display: grid; gap: var(--s2); cursor: pointer;
  padding: var(--s3); border: 1px solid var(--line-dark);
  border-radius: var(--r-card); background: var(--paper-2);
}
.heute-stufenbalken-spur { height: 4px; border-radius: var(--r-rund); background: var(--line-dark); overflow: hidden; }
.heute-stufenbalken-spur i { display: block; height: 100%; background: var(--lime); border-radius: inherit; }
.heute-stufenbalken span {
  font-size: var(--t-micro); font-weight: 750; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.heute-stufenbalken b { color: var(--ink); font-weight: 850; }
.heute-stufenbalken:focus-visible { outline: 2px solid var(--lime); outline-offset: 2px; }
.heute-kachel {
  display: grid; align-content: start; gap: 2px; padding: var(--s3) var(--s2);
  border: 1px solid var(--line-dark); border-radius: var(--r-card);
  background: var(--paper-2); color: var(--ink); cursor: pointer; text-align: left;
  min-height: var(--ziel-min);
}
.heute-kachel strong { font-size: var(--t-lead); font-weight: 850; line-height: 1.1; }
.heute-kachel .einheit { font-size: var(--t-micro); font-weight: 800; color: var(--ink-3); margin-left: 2px; }
.heute-kachel em {
  font-style: normal; font-size: var(--t-micro); font-weight: 750;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--ink-3);
  line-height: 1.25;
}
.heute-kachel.akzent-gold strong { color: var(--gold); }

.heute-block { margin-top: var(--s5); }
.heute-block-kopf { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s3); }
.heute-block-kopf h2 { margin: 0; font-size: var(--t-lead); font-weight: 800; letter-spacing: -.01em; }
.heute-mehr {
  border: 0; background: transparent; cursor: pointer; padding: var(--s1) 0;
  font-size: var(--t-micro); font-weight: 800; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--lime);
}
.heute-feed { display: grid; gap: 1px; background: var(--line-hair); border-radius: var(--r-card); overflow: hidden; }
.heute-feed-zeile {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) var(--s4); background: var(--paper-2);
}
.heute-feed-zeile strong { font-size: var(--t-body); font-weight: 750; }
.heute-feed-zeile em { font-style: normal; font-size: var(--t-micro); color: var(--ink-3); }

/* ---- Der erhabene Scan-Knopf in der Leiste -------------------------------
   Er ist kein Ziel unter anderen, sondern die Handlung, um die alles kreist:
   zehnmal am Tag gegen einmal bei den uebrigen. Deshalb ragt er heraus und
   traegt als einziger die Aktionsfarbe.

   Kreis und Symbol haengen an DENSELBEN zwei Zahlen und werden beide vom
   oberen Rand aus gesetzt. Vorher stand der Kreis absolut bei top:-14px, das
   Symbol dagegen im Raster mit margin-top:-14px — zwei verschiedene Bezuege
   auf dieselbe Zahl. Gemessen sass es dadurch **10,5 px zu tief** im Kreis,
   und der Fehler waechst mit jeder Aenderung an --nav-height: das Raster
   zentriert in der Knopfhoehe, der Kreis nicht. Wer --scan-hub oder
   --scan-kreis aendert, zieht das Symbol jetzt automatisch mit. */
.nav-scan {
  --scan-kreis: 52px; --scan-hub: -14px; --scan-symbol: 26px;
  position: relative; display: grid; place-items: center; cursor: pointer;
  border: 0; background: transparent; padding: 0;
}
.nav-scan::before {
  content: ''; position: absolute; width: var(--scan-kreis); height: var(--scan-kreis);
  top: var(--scan-hub); left: 50%; transform: translateX(-50%);
  border-radius: var(--r-rund); background: var(--lime);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--lime) 30%, transparent);
}
.nav-scan svg {
  position: absolute; width: var(--scan-symbol); height: var(--scan-symbol);
  top: calc(var(--scan-hub) + (var(--scan-kreis) - var(--scan-symbol)) / 2);
  left: 50%; transform: translateX(-50%);
  fill: none; stroke: var(--auf-akzent); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Die Zentrierung MUSS mit — ein blankes scale() hier ersetzt das
   translateX(-50%) und schoebe den Kreis beim Antippen um 26 px nach rechts. */
.nav-scan:active::before { transform: translateX(-50%) scale(.94); }

/* ===========================================================================
   VITRINEN

   Eine Wand ist ein Sammelstück: ein Material mit einer Seltenheit. Wer eine
   hat, legt sie auf beliebig viele eigene Karten — sie wird nicht verbraucht.
   =========================================================================== */

/* Die Wahl im Datenblatt. Waagerecht scrollbar aus demselben Grund wie der
   Kader und die Studio-Wahl: gestapelt schöbe sie alles darunter aus dem
   Bild. `min-width: 0` ist dabei Pflicht — sonst nimmt sich die Reihe ihre
   volle Länge und die Breite schlägt bis zum <body> durch. */
.detail-vitrine { margin: var(--s4) 0 0; min-width: 0; }
.detail-vitrine .merge-hint { margin: 4px 0 var(--s2); }
.wand-reihe {
  display: flex; gap: var(--s2); min-width: 0;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: thin; padding-bottom: 6px;
}
.wand-knopf {
  flex: 0 0 auto; display: grid; gap: 4px; justify-items: center;
  padding: 6px; border: 1px solid var(--line-hair); border-radius: var(--r-kante);
  background: var(--flaeche); color: var(--ink-3); font: inherit;
  font-size: var(--t-micro); cursor: pointer; min-width: 62px;
}
.wand-knopf.aktiv { border-color: var(--lime); color: var(--ink); }
/* Eine belegte Wand wird GEZEIGT, nicht versteckt — ausgeblendet sähe es aus,
   als hätte man sie verloren. Stumpf und ohne Zeiger, aber lesbar: der Grund
   steht als Text im Knopf, sonst tippt man dagegen und bekommt eine Absage
   ohne Erklärung. Die Deckkraft bleibt über 0,5, weil der Name sonst unter
   die Kontrastgrenze fällt. */
.wand-knopf.belegt { opacity: .55; cursor: not-allowed; }
.wand-knopf .wand-zahl {
  font-style: normal; font-size: 9px; font-weight: 700;
  color: color-mix(in srgb, currentColor 78%, transparent);
}
/* Die Probe zeigt das MATERIAL, nicht nur die Farbe — deshalb dieselbe
   Textur wie im Raum, überlagert vom Farbton der Wand. Ohne Textur wären
   Schiefer, Moosgrün und Tiefseeblau drei Farbflecken, obwohl sie sich im
   Raum deutlich unterscheiden. */
.wand-probe {
  display: block; width: 40px; height: 40px; border-radius: 4px;
  background-image: var(--wandtextur, none); background-size: cover;
  background-color: var(--ton, var(--flaeche-3));
  /* `luminosity`, NICHT `color` — und das war ein echter Fehler, kein
     Geschmacksurteil. `color` nimmt Farbton und Sättigung von der QUELLE
     (dem Bild) und nur die Helligkeit vom Grund; der Farbton der Wand
     bewirkte damit fast nichts. Im Bild sahen **Tabakleder und Moosgrün
     identisch braun** aus, obwohl die Farbe korrekt gesetzt war — die
     Diagnose meldete `--ton: #3c5a3a`, und trotzdem stand dort Holz.

     `luminosity` dreht es um: Farbton und Sättigung vom Grund, Helligkeit
     vom Bild. Genau das ist eine eingefärbte Textur.

     UMGESCHALTET WIRD IN `wandProbe()`, nicht hier: ohne Farbton fiele
     `background-color` auf `--flaeche-3` zurück, und `luminosity` machte
     dann aus Eichenholz, Marmor, Silberspiegel und Goldsaal vier graue
     Flächen — die vier, die ihre Farbe gerade aus dem Material beziehen. */
  border: 1px solid var(--line-hair);
}
.wand-probe.getoent { background-blend-mode: luminosity; }
.wand-probe.leer {
  background: repeating-linear-gradient(45deg,
    var(--flaeche-2) 0 5px, var(--flaeche-3) 5px 10px);
}

/* --- Der Bestand als eigene Fläche (07.08.2026) ---------------------------
   Dritter Reiter der Sammlung. Bis dahin gab es KEINEN Ort, an dem der eigene
   Bestand stand — zählen ging nur, indem man im Kartenstudio durch eine
   waagerechte Knopfreihe scrollte.

   Ein Raster, keine Reihe: hier wird nichts durchgeblättert, sondern
   überblickt. Und gruppiert nach Seltenheit, damit eine Wand beim ersten Fund
   nicht an eine andere Stelle wandert — sonst ist die Fläche nach jedem Fund
   eine andere und man lernt sie nie. */
.items-liste { display: grid; gap: var(--s5); margin-top: var(--s3); }
/* Die ITEMART ist die erste Ebene, die Seltenheit die zweite. Heute gibt es
   genau eine Art; die Überschrift trägt sie trotzdem, damit eine zweite
   später nur eine weitere Sektion ist und kein Umbau der bestehenden.
   Sie ist bewusst größer als die Stufenzeilen darunter — zwei gleich
   aussehende Ebenen sind keine Hierarchie, sondern eine Liste. */
.item-art { display: grid; gap: var(--s4); }
.item-art-kopf {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: 0 var(--s2); margin: 0; font-size: var(--t-lead); font-weight: 800;
}
.item-art-kopf em {
  font-style: normal; font-variant-numeric: tabular-nums;
  font-size: var(--t-small); color: var(--text-2);
}
.item-art-kopf small {
  grid-column: 1 / -1; font-size: var(--t-micro); font-weight: 400;
  color: var(--text-3);
}
.vitrinen-stufe h2 {
  display: flex; align-items: baseline; gap: var(--s2);
  margin: 0 0 var(--s2); font-size: var(--t-micro);
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-2);
}
.vitrinen-stufe h2 em {
  margin-left: auto; font-style: normal; font-variant-numeric: tabular-nums;
  letter-spacing: 0; color: var(--text-3);
}
/* Die Seltenheit färbt die Überschrift, und die Farbe kommt PER INLINE-STIL
   aus `rarityStyle` in app.js. Sie hier noch einmal zu setzen wäre ein
   zweiter Wahrheitsort für dieselbe Skala — genau das, was CLAUDE.md für die
   Seltenheits-Inszenierung ausdrücklich verbietet: „verteilt auf CSS und JS
   driftet sie auseinander, sobald jemand eine Stufe anfasst."
   Nachgerechnet auf dem Nachtgrund: 6,10 : 1 (Episch) bis 11,48 : 1 (Mythisch),
   alle fünf über 4,5 : 1. */
.vitrinen-raster {
  display: grid; gap: var(--s2);
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
}
.vitrinen-kachel {
  display: grid; gap: 4px; justify-items: center; text-align: center;
  padding: var(--s2); border-radius: var(--r-kante);
  background: var(--flaeche); border: 1px solid var(--line-hair);
}
.vitrinen-kachel strong { font-size: var(--t-micro); font-weight: 700; }
.vitrinen-kachel span {
  font-size: 9px; color: var(--text-3); font-variant-numeric: tabular-nums;
}
.vitrinen-kachel .wand-probe { width: 100%; height: 52px; }
/* Was fehlt, steht MIT DA und verschattet — „was fehlt mir noch" treibt das
   Sammeln an, „was habe ich" ist eine Quittung. Der Name bleibt dabei
   lesbar; nur die Probe wird zurückgenommen, denn sie ist die Belohnung. */
.vitrinen-kachel.fehlt { border-style: dashed; }
.vitrinen-kachel.fehlt .wand-probe { filter: grayscale(1); opacity: .3; }
.vitrinen-kachel.fehlt strong { color: var(--text-2); font-weight: 500; }

/* ===========================================================================
   DAS HUD ÜBER DER KARTENBÜHNE (07.08.2026)

   Ein Bogen um den Daumen, kein Streifen unter der Karte. Die Begründung
   steht bei `hudMarkup()` in app.js; hier nur, was die Umsetzung teuer machen
   könnte:

   NUR `transform` UND `opacity` BEWEGEN SICH. Alles andere lässt den Browser
   je Bild neu rechnen, und das HUD liegt über einer Karte, die ohnehin schon
   zwei `screen`-Ebenen mischt. Dieselbe Lehre wie bei der Ruheschaukel: eine
   angemeldete Custom Property in `@keyframes` kostete dort 940 ms je Sekunde.

   DIE LAGE KOMMT VON AUSSEN. `--lage-x` / `--lage-y` schreibt `karte-leben.js`
   aufs Wurzelelement; hier wird nur gelesen. Gegenläufig zur Karte, damit
   zwei Ebenen entstehen statt einer.
   =========================================================================== */
.karten-hud {
  position: fixed; z-index: 4;
  right: max(var(--s4), env(safe-area-inset-right));
  bottom: calc(var(--s5) + env(safe-area-inset-bottom));
  width: 56px; height: 56px;
  /* Gegenläufig (negatives Vorzeichen) und schwächer als die Karte: 10 px
     Versatz auf ±1 Lage. Mehr wirkt wie ein Wackelkontakt. */
  transform: translate3d(calc(var(--lage-x, 0) * -10px), calc(var(--lage-y, 0) * -8px), 0);
  transition: transform .18s ease-out;
}
.hud-knopf {
  position: absolute; inset: 0; display: grid; place-items: center;
  border: 0; border-radius: 50%; padding: 0; cursor: pointer;
  background: radial-gradient(circle at 34% 28%,
    color-mix(in srgb, var(--lime) 34%, transparent), transparent 62%),
    color-mix(in srgb, var(--grund) 82%, transparent);
  backdrop-filter: blur(10px) saturate(1.3);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--lime) 34%, transparent),
    0 10px 30px rgba(0, 0, 0, .55);
}
/* Der Ring dreht sich langsam — das einzige, was ohne Zutun läuft, und
   deshalb bewusst fast unsichtbar (0,45 Umdrehungen je Minute). Ein HUD, das
   blinkt, zieht Aufmerksamkeit von der Karte ab; eines, das gar nichts tut,
   sieht aus wie ein Bild. */
.hud-ring {
  position: absolute; inset: 5px; border-radius: 50%;
  border: 1.5px dashed color-mix(in srgb, var(--lime) 52%, transparent);
  border-right-color: transparent; border-bottom-color: transparent;
  animation: hudRing 22s linear infinite;
}
@keyframes hudRing { to { transform: rotate(360deg); } }
/* Das Kreuz wird beim Öffnen zum X — eine Form, zwei Bedeutungen, ohne
   zweites Symbol.

   DER TRÄGER IST QUADRATISCH, die Balken sind seine Pseudoelemente. Zuerst
   war der Träger selbst ein Balken (18 × 2 px) und trug den zweiten als
   `::before` — beide sind dann in einem 2 px hohen Kasten verankert, und statt
   eines Kreuzes entsteht ein Winkel: im Bild ein Haken, also ausgerechnet das
   Zeichen für „bestätigen". `inset: 0; margin: auto` zentriert beide Balken
   im selben Quadrat, und damit kreuzen sie sich zwangsläufig. */
.hud-kreuz { position: absolute; width: 18px; height: 18px; }
.hud-kreuz::before, .hud-kreuz::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 18px; height: 2px; border-radius: 2px; background: var(--lime);
}
.hud-kreuz::after { transform: rotate(90deg); }
.karten-hud[data-offen="true"] .hud-knopf { transform: rotate(135deg); }
.karten-hud[data-offen="true"] .hud-ring { border-color: color-mix(in srgb, var(--lime) 72%, transparent); }

/* Der Bogen. Die Ziele liegen auf einem Viertelkreis nach links oben —
   dorthin, wohin der Daumen von unten rechts greift. */
.hud-bogen { position: absolute; inset: 0; pointer-events: none; }
.hud-ziel {
  --winkel: calc(118deg + var(--i) * 34deg);
  --weite: 104px;
  position: absolute; inset: 0; margin: auto;
  width: 52px; height: 52px; padding: 0;
  display: grid; place-items: center; gap: 1px;
  border: 0; border-radius: 16px; cursor: pointer;
  background: color-mix(in srgb, var(--grund) 88%, transparent);
  backdrop-filter: blur(10px) saturate(1.2);
  box-shadow: 0 0 0 1px var(--kante-haar), 0 8px 22px rgba(0, 0, 0, .5);
  color: var(--text);
  opacity: 0; pointer-events: none;
  /* Im Ruhezustand unter dem Knopf, nicht daneben: so fährt es hervor statt
     einzublenden. */
  transform: translate3d(0, 0, 0) scale(.6);
  transition: transform .26s cubic-bezier(.2, .9, .3, 1.25), opacity .16s ease-out;
}
.hud-ziel svg { width: 19px; height: 19px; fill: none; stroke: var(--lime); stroke-width: 1.7; }
.hud-ziel span { font-size: 8px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; }
.karten-hud[data-offen="true"] .hud-ziel {
  opacity: 1; pointer-events: auto;
  transform: translate3d(calc(cos(var(--winkel)) * var(--weite)),
                         calc(sin(var(--winkel)) * var(--weite) * -1), 0) scale(1);
  /* Gestaffelt, damit der Bogen aufblättert statt aufzupoppen. 45 ms sind
     spürbar und bleiben unter der Grenze, ab der es sich zäh anfühlt. */
  transition-delay: calc(var(--i) * 45ms);
}
.hud-ziel:active { transform: translate3d(calc(cos(var(--winkel)) * var(--weite)),
                         calc(sin(var(--winkel)) * var(--weite) * -1), 0) scale(.92); }

/* Solange der Bogen offen ist, tritt der Hinweis zurück. Er beschreibt eine
   Geste, die man in diesem Moment nicht ausführt — und der oberste Chip lag
   sonst genau darauf. Ausgeblendet statt verschoben: ein Text, der beim
   Öffnen wegspringt, zieht den Blick auf sich. */
#kartenBuehne:has(.karten-hud[data-offen="true"]) .buehne-hinweis { opacity: 0; }
.buehne-hinweis { transition: opacity .2s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .karten-hud { transition: none; }
  .hud-ring { animation: none; }
  .hud-ziel { transition-duration: .01ms; transition-delay: 0ms !important; }
  .buehne-hinweis { transition: none; }
}

/* Die Wandwahl im Tierwähler — dasselbe Kachelbild wie im Bestand, damit man
   dieselbe Wand an beiden Orten wiedererkennt. */
.wand-wahl-raster {
  display: grid; gap: var(--s2); padding: var(--s2) 0;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
}
.wand-wahl-raster .vitrinen-kachel.aktiv { box-shadow: 0 0 0 2px var(--lime); }
/* Wo eine Wand gerade steckt. Ohne diese Zeile bliebe bei „0 frei" offen, WO
   die Wände hängen — man müsste die ganze Sammlung durchtippen. */
.wand-traeger {
  font-style: normal; font-size: 9px; color: var(--lime);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
button.vitrinen-kachel { font: inherit; cursor: pointer; text-align: center; }
button.vitrinen-kachel:disabled { cursor: not-allowed; }

/* ===========================================================================
   DER AUSRICHTER (07.08.2026)

   Vollbild statt in der Karte. Die Begründung steht bei `oeffneAusrichter()`
   in app.js; hier nur das, was die Umsetzung teuer machen könnte:

   `touch-action: none` auf dem Rahmen ist PFLICHT. Ohne sie bricht Android
   den Zug nach wenigen Pixeln mit `pointercancel` ab, sobald er leicht schräg
   läuft — und zwei Finger scrollen die Seite, statt zu zoomen. Dieselbe Falle
   wie beim Dreher im Kartenstudio.
   =========================================================================== */
#ausrichter {
  position: fixed; inset: 0; z-index: 95;
  display: grid; align-content: center; justify-items: center; gap: var(--s3);
  padding: max(20px, env(safe-area-inset-top)) 16px calc(20px + env(safe-area-inset-bottom));
  background: rgba(4, 6, 5, .97);
}
.ausricht-kopf { display: grid; justify-items: center; gap: 2px; text-align: center; }
.ausricht-kopf strong {
  font-size: var(--t-micro); text-transform: uppercase; letter-spacing: .1em;
  color: var(--lime);
}
.ausricht-kopf span { font-size: var(--t-lead); font-weight: 800; }
/* 2:1 wie `.kbild` auf der Karte. Ein anderes Verhältnis zeigte einen
   Ausschnitt, den die Karte nie annimmt. */
.ausricht-rahmen {
  position: relative; width: min(94vw, 520px); aspect-ratio: 2/1;
  overflow: hidden; border-radius: var(--r-soft);
  outline: 2px dashed color-mix(in srgb, var(--lime) 60%, transparent);
  outline-offset: -2px; cursor: grab; touch-action: none;
  background: var(--flaeche);
}
.ausricht-rahmen:active { cursor: grabbing; }
.ausricht-bild {
  position: absolute; inset: 0; width: 100%; height: 100%;
  /* Exakt dieselbe Geometrie wie das scharfe Kartenfoto. `cover` zeigte im
     Editor einen anderen Grundzuschnitt als die Karte und machte korrekt
     gespeicherte Fokuswerte sichtbar falsch. */
  object-fit: contain; transform-origin: 50% 50%;
  user-select: none; -webkit-user-drag: none; pointer-events: none;
}
/* Drittelraster. Es hilft beim Ausrichten und kostet nichts — reine
   Linien, kein zweites Bild. */
.ausricht-raster {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to right, transparent calc(33.33% - .5px),
      rgba(255,255,255,.22) calc(33.33% - .5px) calc(33.33% + .5px), transparent calc(33.33% + .5px)),
    linear-gradient(to right, transparent calc(66.66% - .5px),
      rgba(255,255,255,.22) calc(66.66% - .5px) calc(66.66% + .5px), transparent calc(66.66% + .5px)),
    linear-gradient(to bottom, transparent calc(50% - .5px),
      rgba(255,255,255,.22) calc(50% - .5px) calc(50% + .5px), transparent calc(50% + .5px));
}
.ausricht-regler {
  display: flex; align-items: center; gap: var(--s2);
  width: min(94vw, 520px);
}
.ausricht-regler svg { width: 18px; height: 18px; fill: none; stroke: var(--lime); stroke-width: 1.7; flex: none; }
.ausricht-regler input { flex: 1; accent-color: var(--lime); min-height: var(--ziel-min); }
.ausricht-regler b {
  font-variant-numeric: tabular-nums; font-size: var(--t-small);
  min-width: 3.4em; text-align: right;
}
/* DER STREIFEN DER AUFNAHMEN.
   Waagerecht scrollend statt umbrechend: bei 6 bis 10 Bildern wuerde ein
   Raster den Rahmen darueber nach oben schieben — und der Rahmen ist das,
   worauf man sieht. `min-width: 0` ist Pflicht, sonst nimmt sich die Reihe
   ihre volle Laenge und `overflow-x` greift nie (dieselbe Falle wie bei den
   Wahlreihen im Kartenstudio). */
.ausricht-streifen {
  display: flex; gap: var(--s2); width: min(94vw, 520px); min-width: 0;
  overflow-x: auto; scrollbar-width: none; padding-bottom: 2px;
}
.ausricht-streifen::-webkit-scrollbar { display: none; }
.ausricht-streifen button {
  flex: none; width: 56px; height: 56px; padding: 0; overflow: hidden;
  border-radius: var(--r2); border: 2px solid transparent;
  background: var(--flaeche-2); cursor: pointer;
}
.ausricht-streifen button.aktiv { border-color: var(--lime); }
.ausricht-streifen img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ausricht-hinweis { margin: 0; font-size: var(--t-micro); color: var(--text-3); }
.ausricht-fuss { display: flex; gap: var(--s3); width: min(94vw, 520px); }
.ausricht-fuss button { flex: 1; }

/* Der Fund. Er fällt alle paar Tage — was so selten passiert, darf einen
   Moment bekommen statt einer Zeile im Toast. */
#vitrinenFund {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center; padding: var(--s4);
  background: rgba(3, 5, 4, .92);
}
.vitrine-innen {
  max-width: 340px; width: 100%; display: grid; gap: var(--s2); justify-items: center;
  text-align: center;
}
.vitrine-eyebrow {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--lime);
}
.vitrine-innen h2 { margin: 0; font-size: var(--t-title); font-weight: 900; }
.vitrine-selt { margin: 0; font-size: var(--t-small); color: var(--ink-2); }
.vitrine-probe { width: 200px; margin: var(--s2) 0; }
.vitrine-hinweis {
  margin: 0 0 var(--s2); font-size: var(--t-small); color: var(--ink-2); line-height: 1.4;
}
.vitrine-zu { width: 100%; }

/* ===========================================================================
   DIE ARENA — ein Fluss, kein Formular (06.08.2026)

   Vorher: eine Tafel mit vier Kastenzeilen, darunter drei Listen, darunter
   der Kader, darunter der Verlauf. Sechs umrandete Blöcke übereinander, und
   die eigentliche Handlung lag hinter zwei Dialogen.

   Jetzt: Linien statt Rahmen, eine Fläche statt drei, und die Reihenfolge
   folgt dem Gedanken — welche Kampfart, womit trete ich an, gegen wen.
   =========================================================================== */

.arena { padding-bottom: var(--s7); }

.arena-kopf {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3); max-width: 900px; margin: 0 auto var(--s4);
}
.arena-kopf h1 { margin: 0; font-size: var(--t-display); font-weight: 900; letter-spacing: -.03em; }
.arena-zaehler { font-size: var(--t-small); color: var(--ink-3); white-space: nowrap; }
.arena-zaehler b { color: var(--ink); font-weight: 900; font-variant-numeric: tabular-nums; }

/* SEGMENTLEISTE statt drei Kästen. Ein Pillenschalter sagt „eins von drei"
   ohne jede Erklärung; drei umrandete Kacheln mit Untertitel sagen es in
   sechs Zeilen und sahen aus wie drei Angebote. */
.arena-modi {
  display: flex; gap: 2px; max-width: 900px; margin: 0 auto var(--s3);
  padding: 3px; border-radius: 999px; background: var(--flaeche);
}
.arena-modus {
  /* 11 px, nicht 9: bei var(--t-micro) ergibt 9 px eine Hoehe von 30 px, und
     der Befundlauf fuehrt alles unter 32 px als zu kleine Zielflaeche. */
  flex: 1; min-width: 0; padding: 11px var(--s2); border: 0; border-radius: 999px;
  background: transparent; color: var(--ink-3); font: inherit;
  font-size: var(--t-micro); font-weight: 850; letter-spacing: .02em;
  text-transform: uppercase; cursor: pointer;
  transition: background .16s ease, color .16s ease;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.arena-modus.active { background: var(--flaeche-3); color: var(--ink); }

/* EINSATZ UND ERTRAG als Chips in EINER Zeile. Als vierzeilige Tafel nahmen
   dieselben Angaben 300 px und lasen sich wie ein Datenblatt — dabei sind es
   drei kurze Tatsachen, die man einmal liest. */
.arena-einsatz {
  display: flex; flex-wrap: wrap; gap: 6px;
  max-width: 900px; margin: 0 auto var(--s4);
}
.arena-einsatz .chip {
  padding: 4px 10px; border-radius: 999px; font-size: var(--t-micro);
  font-weight: 700; background: var(--flaeche-2); color: var(--ink-2);
}
.arena-einsatz .chip.warnung { background: color-mix(in srgb, var(--rost) 18%, var(--flaeche)); color: var(--rost); }
.arena-einsatz .chip.lohn { background: color-mix(in srgb, var(--gold) 16%, var(--flaeche)); color: var(--gold); }
.arena-einsatz .chip.ruhig { color: var(--ink-3); }

/* WAS BEREITSTEHT — eine Zeile, keine stehende Wahl mehr (06.08.2026).
   Das Tier wird beim Antreten gewaehlt: welches man schickt, haengt davon ab,
   gegen wen es antritt.

   SEIT DEM 07.08.2026 STEHT HIER DER AUSDAUERVORRAT. „3 Tiere bereit" war die
   Antwort auf eine Frage, die niemand mehr stellt — jedes Tier kann antreten.
   Was begrenzt, ist der eigene Vorrat. */
.arena-kaempfer { max-width: 900px; margin: 0 auto var(--s4); }
.kader-zeile {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s3);
  padding: var(--s3) 0;
  border-top: 1px solid var(--line-hair); border-bottom: 1px solid var(--line-hair);
}
.kader-stand { font-size: var(--t-body); font-weight: 850; }
.kader-stand b { font-size: var(--t-lead); font-weight: 900; letter-spacing: -.02em; }
.kader-hinweis { font-size: var(--t-small); color: var(--ink-3); }
.kader-zeile.leer .kader-stand { color: var(--rost); }

/* DIE PUNKTE NEBEN DER ZAHL, nicht statt ihr. Bei drei Punkten sieht man den
   Stand, bei zwoelf wird die Reihe zur Perlenkette — deshalb beides. Sie sind
   `aria-hidden`: die Zahl daneben sagt dasselbe, und eine Vorlesefunktion, die
   zwoelf Punkte aufzaehlt, ist keine Hilfe. */
.ausdauer-punkte { display: inline-flex; gap: 2px; margin-left: 8px; vertical-align: middle; }
/* DER LEERE PUNKT HAENGT AN `currentColor`, NICHT AN EINEM FESTEN GRAU. Die
   alte Regel stand auf `rgba(16,20,17,.16)` — dunkles Grau auf hellem Papier.
   Auf dem Nachtregister ist das unsichtbar: im Bild standen sieben gruene
   Punkte und daneben NICHTS, also sah ein halb geleerter Vorrat aus wie ein
   voller mit sieben Plaetzen. `PAPIER_ANSICHTEN` ist seit dem Systemblatt
   leer; jede feste dunkle Farbe ist damit ein blinder Fleck. */
.ausdauer-punkte i {
  width: 6px; height: 6px; border-radius: 50%;
  background: color-mix(in srgb, currentColor 22%, transparent);
}
.ausdauer-punkte i.voll { background: var(--lime); }
.kader-zeile.leer .ausdauer-punkte i.voll { background: var(--rost); }

/* AUFFUELLEN STEHT AN DER ZAHL, DIE ES AENDERT. Gold heisst in dieser App
   „Ertrag" — hier wird Ertrag ausgegeben, nicht verdient, deshalb ein
   zurueckhaltender Umriss statt einer Goldflaeche. */
.kader-kauf {
  margin-left: auto; min-height: var(--ziel-min);
  padding: 6px 12px; border-radius: var(--r-pille);
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  background: transparent; color: var(--gold);
  font: inherit; font-size: var(--t-small); font-weight: 850; cursor: pointer;
}
.kader-kauf:active { transform: scale(.97); }
/* `.kader-beute` ist WEG (06.08.2026). Die Beute war nie eine Auskunft ueber
   den eigenen Kader — sie steht jetzt im Wildnis-Kopf, direkt ueber den
   Begegnungen. Siehe `.wild-beute`. */

/* DIE ANGRIFFE IN DER ERSATZWAHL. Sie erscheint nur, wenn `karte.js` nicht
   geladen ist — also genau auf dem Geraet und nie am Rechner. Ohne die drei
   Faehigkeiten bleibt dort ein reiner Zahlenvergleich uebrig, und das sieht
   von aussen aus wie ein kaputtes Kampfsystem. */
.pvp-ersatzwahl .ersatz-angriff {
  display: flex; align-items: baseline; gap: 8px; width: 100%;
  padding: 10px 12px; border-radius: 12px;
  border: 1px solid var(--line-dark); background: transparent; color: var(--ink);
  font: inherit; font-size: var(--t-small); font-weight: 800; cursor: pointer;
}
.pvp-ersatzwahl .ersatz-angriff b { color: var(--gold); }
.pvp-ersatzwahl .ersatz-angriff em {
  margin-left: auto; font-style: normal; font-size: var(--t-micro);
  font-weight: 700; color: var(--ink-3);
}
.pvp-ersatzwahl .ersatz-angriff.an { border-color: var(--gold); }

/* DIE EINSATZWAHL — zweite Frage, kleiner als die erste.
   Der Reiter darueber fragt „gegen wen", diese Reihe „mit welchem Einsatz".
   Sie ist bewusst eine Chipreihe und keine zweite Segmentleiste: zwei gleich
   aussehende Leisten uebereinander liest niemand als zwei Ebenen. */
.arena-einsatzwahl {
  display: flex; gap: var(--s2); max-width: 900px; margin: 0 auto var(--s2);
}
.arena-einsatzwahl[hidden] { display: none; }
.einsatz-knopf {
  /* 32 px MINDESTHOEHE, nicht 28. Der Befundlauf misst genau das, und ein
     Chip, den man daneben trifft, ist auf einem Handy kein Schoenheitsfehler
     — er ist die Stelle, an der man versehentlich seine Karte einsetzt. */
  min-height: 32px; padding: 8px 16px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line-dark); background: transparent; color: var(--ink-2);
  font: inherit; font-size: var(--t-micro); font-weight: 800;
  letter-spacing: var(--track-label);
}
.einsatz-knopf.aktiv {
  border-color: var(--akzent); color: var(--akzent);
  background: color-mix(in srgb, var(--akzent) 14%, transparent);
}

/* DER VERLAUF IST NACH KAMPFART GETRENNT. Was fuer alle Zeilen eines Blocks
   gilt — die Kampfart —, steht einmal oben statt in jeder Zeile. Die Bilanz
   daneben beantwortet die Frage, die man an einen Verlauf stellt. */
.verlauf-block { max-width: 900px; margin: 0 auto var(--s4); }
.verlauf-block h3 {
  display: flex; align-items: baseline; gap: var(--s2);
  margin: 0 0 var(--s2); padding-bottom: 6px;
  border-bottom: 1px solid var(--line-hair);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-2);
}
.verlauf-block h3 em {
  margin-left: auto; font-style: normal; font-variant-numeric: tabular-nums;
  font-size: var(--t-small); font-weight: 850; color: var(--ink-3);
}

.arena-titel {
  max-width: 900px; margin: 0 auto var(--s2);
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
}
.arena-hinweis {
  max-width: 900px; margin: 0 auto var(--s3);
  font-size: var(--t-small); color: var(--ink-2); line-height: 1.4;
}

/* GEGNER als Zeilen mit Trennlinie, nicht als Kästen. Sechs Rahmen
   untereinander sind sechs Ränder, die um Aufmerksamkeit konkurrieren —
   eine Haarlinie reicht, um „nächster Eintrag" zu sagen. */
.arena-gegner { max-width: 900px; margin: 0 auto var(--s5); }
.gegner-reihe {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s3) 0; border: 0; border-bottom: 1px solid var(--line-hair);
  background: transparent; color: var(--ink); font: inherit; text-align: left;
  cursor: pointer;
}
.gegner-reihe:disabled { cursor: not-allowed; opacity: .45; }
.gegner-marke {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; position: relative;
  background: var(--flaeche-2); color: var(--ink-2);
  font-size: var(--t-body); font-weight: 900;
}
.gegner-marke.online::after {
  content: ''; position: absolute; right: -1px; bottom: -1px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--lime); border: 2px solid var(--grund);
}
.gegner-text { flex: 1; min-width: 0; display: grid; gap: 2px; }
.gegner-text strong { font-size: var(--t-body); font-weight: 800; }
.gegner-text em { font-size: var(--t-small); font-style: normal; color: var(--ink-3); }
.gegner-tat {
  flex: none; font-size: var(--t-micro); font-weight: 900;
  letter-spacing: var(--track-label); text-transform: uppercase; color: var(--lime);
}
.gegner-reihe:disabled .gegner-tat { color: var(--ink-3); }

/* WILDNIS: die Begegnungen als Karten nebeneinander. Hier ist die Karte die
   Auskunft — die Fragezeichen auf ihr sind die Spielidee.

   DER KOPF DARUEBER traegt die zwei Angaben, die vorher an drei anderen
   Stellen der Seite standen: was ein Kampf kostet und was ein Sieg bringt.
   Linien statt Rahmen, wie ueberall in der Arena — sechs umrandete Kaesten
   uebereinander waren sechs Raender, die um Aufmerksamkeit konkurrieren. */
.wild-kopf { display: grid; gap: var(--s2); margin-bottom: var(--s4); }
.wild-satz { margin: 0; font-size: var(--t-small); color: var(--ink-2); }
.wild-satz b { color: var(--ink); font-weight: 850; }
.wild-beute {
  display: grid; gap: 2px; padding: var(--s3);
  border-left: 3px solid var(--gold); border-radius: 0 var(--r-card) var(--r-card) 0;
  background: color-mix(in srgb, var(--gold) 8%, var(--flaeche));
}
.wild-beute-stand { font-size: var(--t-body); font-weight: 850; color: var(--gold); }
.wild-beute-stand b { font-size: var(--t-lead); font-weight: 900; letter-spacing: -.02em; }
.wild-beute-folge { font-size: var(--t-small); color: var(--ink-2); }
/* Erklaert die Fragezeichen auf den Karten und steht deshalb direkt davor. */
.wild-sicht { margin: 0; font-size: var(--t-small); color: var(--ink-3); }
/* Leeres Fenster ist kein Fehler, sondern ein anderer Ertrag — Rost statt
   Gold, aber derselbe Bau. Ein Warnkasten waere hier gelogen. */
.wild-beute.leer { border-left-color: var(--rost); background: color-mix(in srgb, var(--rost) 8%, var(--flaeche)); }
.wild-beute.leer .wild-beute-stand { color: var(--rost); }

.wild-raster {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s3);
}
.wild-karte {
  display: grid; gap: 6px; padding: 0; border: 0; background: transparent;
  color: var(--ink); font: inherit; cursor: pointer; text-align: center;
  min-width: 0;
}
.wild-karte:disabled { cursor: not-allowed; opacity: .5; }
/* DER ERTRAG AN DER KARTE, nicht einmal oben ueber allen dreien. Hier faellt
   die Entscheidung — „gehoert dir" gegen „3 Gems" ist genau die Frage, die
   der Reiter vorher nirgends beantwortet hat. */
.wild-lohn {
  font-size: var(--t-micro); font-weight: 850; color: var(--gold);
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.wild-lohn.gems { color: var(--rost); }
.wild-fuss { font-size: var(--t-micro); color: var(--ink-3); }
.wild-tat {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--lime);
}
.wild-karte:disabled .wild-tat { color: var(--ink-3); }
