/* ============================================================================
   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. */
  --ink: #101411;
  --ink-2: #3d443e;
  --ink-3: #6b716a;
  --paper: #f2f1e8;
  --paper-2: #e7e6dc;
  --night: #0a0d0b;
  --night-2: #141914;

  /* Akzente. Jede hat genau eine Aufgabe. */
  --lime: #c9ff42;      /* Fortschritt, Aktion */
  --gold: #f0c04a;      /* Ertrag (Gems) */
  --mint: #77f2b1;      /* Signal aktiv */
  --warning: #ffd06a;
  --danger: #ff7367;

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

  /* Radien: klein und wenige. Siehe Regel 1. */
  --r-sharp: 0;
  --r-soft: 3px;
  --r-card: 4px;

  /* Abstände in einem 4er-Raster — beliebige Zwischenwerte erzeugen genau die
     leichte Unordnung, die ein Layout billig wirken lässt. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 64px;

  /* Typografische Skala. */
  --t-micro: .58rem;   /* Versalien-Etiketten */
  --t-small: .74rem;
  --t-body: .88rem;
  --t-lead: 1.05rem;
  --t-title: 1.5rem;
  --t-display: 2.4rem;

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

  --nav-height: 68px;
}

/* 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; }
html.native-camera-active,
html.native-camera-active body,
html.native-camera-active .app-shell,
html.native-camera-active .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(--lime); color: var(--ink); font: inherit; font-size: var(--t-small);
  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-cancel {
  position: absolute; z-index: 12; top: calc(58px + env(safe-area-inset-top)); left: 18px;
  min-height: 44px; display: inline-flex; align-items: center; gap: 7px; padding: 8px 12px;
  border: 1px solid rgba(255,255,255,.28); background: rgba(8,11,9,.62);
  color: #fff; backdrop-filter: blur(12px); font-family: inherit;
  font-size: var(--t-small); font-weight: 800; 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: absolute; z-index: 12; top: calc(54px + env(safe-area-inset-top)); right: 14px;
  width: 62px; min-height: 66px; 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,.38); background: rgba(8,11,9,.58);
  backdrop-filter: blur(12px); box-shadow: 0 8px 24px rgba(0,0,0,.2);
}
.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; }
.brand-mark { display: block; font-size: .68rem; letter-spacing: .22em; font-weight: 900; color: var(--lime); margin-bottom: 6px; }
.brand-mark.dark { color: #4f6335; }
.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: absolute; z-index: 8; top: calc(54px + env(safe-area-inset-top)); left: 14px; width: 46px; height: 46px; border: 1px solid rgba(255,255,255,.38); background: rgba(8,11,9,.58); color: white; display: grid; place-items: center; cursor: pointer; backdrop-filter: blur(12px); box-shadow: 0 8px 24px rgba(0,0,0,.2); }
.camera-toggle svg { width: 20px; }
.app-shell.locking-scan .camera-toggle,
.app-shell.scanning .camera-toggle { visibility: hidden; pointer-events: none; }

.scan-instruction { position: absolute; z-index: 8; left: 24px; right: 24px; bottom: calc(204px + env(safe-area-inset-bottom)); text-align: center; color: white; pointer-events: none; text-shadow: 0 2px 16px rgba(0,0,0,.78); 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: .64rem; font-weight: 900; letter-spacing: .2em; margin-bottom: 7px; }
.scan-instruction strong { display: block; font-size: clamp(1.55rem, 7vw, 2.6rem); letter-spacing: -.045em; line-height: 1; }
#scanSubline { margin-top: 9px; color: rgba(255,255,255,.82); font-size: .85rem; }

.camera-footer { position: absolute; z-index: 9; left: 0; right: 0; bottom: calc(var(--nav-height) + env(safe-area-inset-bottom)); min-height: 112px; padding: 13px 28px 18px; display: grid; grid-template-columns: repeat(2, minmax(52px, 1fr)); align-items: end; gap: 20px; }
.camera-footer #torchButton { grid-column: 1; grid-row: 1; }
.camera-footer #zoomQuickButton { grid-column: 2; grid-row: 1; justify-self: end; }
.camera-footer #lensButton { grid-column: 2; grid-row: 1; justify-self: end; transform: translateY(-48px); }
/* 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-instruction { left: 92px; right: 92px; }
  .scan-instruction strong { font-size: clamp(1.35rem, 5vw, 1.8rem); }
}
.ghost-action { min-width: 44px; min-height: 44px; border: 0; background: transparent; color: white; display: grid; justify-items: center; align-content: center; gap: 5px; font-size: .68rem; font-weight: 800; cursor: pointer; text-shadow: 0 1px 10px #000; }
.ghost-action:first-child { justify-self: start; }
.ghost-action:last-child { justify-self: end; }
.ghost-action svg { width: 25px; height: 25px; }
/* 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)); }
/* 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: 0; }
.app-shell.scan-active .scan-instruction { bottom: calc(142px + 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;
}
.nav-item.active { color: var(--lime); }
.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);
}
.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. */
.fold-value.bereit { color: var(--ink); background: var(--lime); padding: 3px 7px; border-radius: var(--r-soft); }

/* 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; }
.filter {
  flex: none; scroll-snap-align: start;
  border: 0; border-bottom: 3px solid transparent; background: transparent;
  color: #72766f; padding: 0 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);
}
/* 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);
}
.collection-new-badge {
  position: absolute; top: var(--s2); left: var(--s2); z-index: 3;
  padding: 4px 7px; border: 1px solid rgba(16,20,17,.2);
  background: var(--lime); color: var(--ink);
  font-size: var(--t-micro); font-weight: 950; letter-spacing: .09em;
  text-transform: uppercase; box-shadow: 0 2px 10px rgba(0,0,0,.16);
}
.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;
}
.tile-mark {
  padding: 3px 6px; border-radius: var(--r-soft);
  background: rgba(201,255,66,.92); color: var(--ink);
  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; }
.primary-bar { background: var(--lime); color: #101411; margin-top: 26px; }
.secondary-bar { background: #d9dbd1; color: #1c221d; margin-top: 12px; }
/* Es gab app-weit KEINE Regel fuer deaktivierte Hauptknoepfe. Ein gesperrter
   Knopf leuchtete deshalb 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: rgba(16,20,17,.07); color: var(--ink-3); cursor: not-allowed;
}

.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: #6b716a; }

/* 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: #6b716a; }
.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(--lime); border: 0; color: var(--ink);
  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: #6b716a; }
.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;
}
.check-track { height: 2px; background: rgba(16,20,17,.12); overflow: hidden; }
.check-track i { display: block; height: 100%; background: var(--ink); transition: width .5s ease; }
.check-row.weak .check-track i { background: var(--danger); }
.check-row.weak span:first-child { 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. */
.back-link {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0 0 var(--s4); 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: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--s2);
}
.quick-link {
  position: relative; min-width: 0;
  display: grid; justify-items: center; gap: 5px;
  padding: var(--s3) var(--s2); cursor: pointer;
  border: 1px solid var(--line-hair); background: var(--paper); color: var(--ink);
  font-family: inherit;
}
.quick-link:active { background: rgba(16,20,17,.04); }
.quick-link svg {
  width: 20px; height: 20px; 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. */
.segmented {
  max-width: 900px; margin: 0 auto var(--s4);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 2px;
  padding: 2px; background: rgba(16,20,17,.07); border-radius: var(--r-card);
}
.segment {
  min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 6px; border: 0; border-radius: var(--r-soft);
  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: background .18s ease, 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 { background: var(--paper); color: var(--ink); box-shadow: 0 1px 2px rgba(16,20,17,.10); }
/* 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);
  font-size: var(--t-small); color: var(--ink-3); cursor: pointer;
}
.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-sharp);
}
/* 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-sharp);
}
.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-sharp);
}
.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-sharp);
}
.species-status.ja { background: var(--lime); color: var(--ink); }
.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-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); }

/* ---- 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. */
.map-pin img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  border-radius: 1px;
}
.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-sharp); 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;
  padding: 3px 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-sharp);
  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); }
.pwa-install-button { align-items: center; border-color: rgba(102, 126, 49, .42); background: #f1f5e7; }
.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; }
.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: #6b716a; 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 .opponent-list,
.sheet-dialog .stake-grid,
.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.
   `.opponent-list` und `.stake-grid` FEHLTEN hier — mit 16 Tieren wuchs der
   Kampfdialog ueber seine `max-height` hinaus, und "Abbrechen" stand
   ausserhalb des Bildes. Genau derselbe Fehler wie beim Schliessen-Knopf der
   Detailkarte: ohne die Schrumpfangabe behaelt ein Bereich seine
   Inhaltshoehe. */
.sheet-dialog .option-list,
.sheet-dialog .plain-list,
.sheet-dialog .opponent-list,
.sheet-dialog .stake-grid,
.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; }
.sheet-dialog p { margin: 0 0 20px; color: #676d66; 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); }
.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: rgba(255,255,255,.7); 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 form { padding-bottom: calc(18px + env(safe-area-inset-bottom)); }
.scan-queue-sheet .sheet-handle { margin-bottom: var(--s3); }
.scan-queue-sheet h2 { margin-bottom: var(--s2); }
.scan-queue-sheet p { margin-bottom: var(--s4); }
.scan-queue-summary {
  flex: none; display: grid; grid-template-columns: repeat(4, 1fr); margin-bottom: var(--s2);
  border: 1px solid var(--line-dark); border-left: 3px solid var(--lime);
  background: var(--ink); color: var(--paper);
}
.scan-queue-summary > span {
  min-width: 0; display: grid; gap: 2px; padding: 10px var(--s3);
  border-left: 1px solid rgba(255,255,255,.13);
}
.scan-queue-summary > span:first-child { border-left: 0; }
.scan-queue-summary strong { font-size: var(--t-lead); font-variant-numeric: tabular-nums; }
.scan-queue-summary em {
  color: rgba(255,255,255,.62); font-size: var(--t-micro); font-style: normal;
  font-weight: 850; letter-spacing: .1em; text-transform: uppercase;
}
.scan-history-filters {
  flex: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-bottom: var(--s2); border: 1px solid var(--line-dark);
}
.scan-history-filters button {
  min-width: 0; min-height: 46px; padding: 7px 6px; border: 0;
  border-left: 1px solid var(--line-dark); background: transparent; color: var(--ink-2);
  font: inherit; font-size: .68rem; font-weight: 850; line-height: 1.15;
  cursor: pointer;
}
.scan-history-filters button:first-child { border-left: 0; }
.scan-history-filters button[aria-pressed="true"] {
  background: var(--ink); color: var(--paper); box-shadow: inset 0 -3px 0 var(--lime);
}
.scan-history-filters button:focus-visible { outline: 2px solid var(--lime); outline-offset: -3px; }
.scan-history-filters[aria-busy="true"] { opacity: .68; }
.scan-queue-list {
  min-height: 0; overflow-y: auto; overscroll-behavior: contain;
}
.scan-list-heading {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) 0 var(--s2); border-bottom: 1px solid var(--line-dark);
}
.scan-list-heading strong {
  font-size: var(--t-micro); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink);
}
.scan-list-heading span { font-size: var(--t-micro); color: var(--ink-3); text-align: right; }
.scan-list-heading:not(:first-child) { margin-top: var(--s4); border-top: 1px solid var(--line-dark); }
.scan-history-loading, .scan-history-error {
  padding: var(--s4) 0; color: var(--ink-3); font-size: var(--t-small);
  border-bottom: 1px solid var(--line-dark);
}
.scan-history-error { color: #a74438; }
.scan-history-photo-note {
  padding: var(--s3) 0 var(--s3) var(--s3); border-bottom: 1px solid var(--line-dark);
  border-left: 3px solid #8b9189; color: var(--ink-3); font-size: var(--t-micro);
  line-height: 1.45;
}
.scan-history-photo-note strong { color: var(--ink); }
.scan-history-more {
  width: 100%; min-height: 44px; margin-top: var(--s3); border: 1px solid var(--line-dark);
  background: transparent; color: var(--ink); font: inherit; font-size: var(--t-small);
  font-weight: 800; 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: var(--s3); padding: var(--s3) 0;
  border-bottom: 1px solid var(--line-dark);
}
.scan-queue-preview {
  width: 58px; height: 58px; object-fit: cover; display: block;
  background: var(--paper-2); border-radius: var(--r-soft);
}
.scan-queue-preview.is-missing {
  display: grid; place-items: center; color: #858b83;
  background:
    linear-gradient(135deg, rgba(16,20,17,.05) 25%, transparent 25%) 0 0 / 12px 12px,
    var(--paper-2);
}
.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 { font-size: var(--t-small); }
.scan-queue-copy span { color: var(--ink-3); font-size: var(--t-micro); line-height: 1.35; }
.scan-queue-copy em {
  color: var(--ink-3); font-size: .62rem; font-style: normal;
  font-variant-numeric: tabular-nums;
}
.scan-queue-row.is-done .scan-queue-copy span { color: #4f6335; font-weight: 800; }
.scan-queue-row.is-error .scan-queue-copy span { color: #a74438; }
.scan-queue-actions { display: grid; justify-items: end; gap: 6px; }
.scan-queue-action {
  border: 1px solid var(--line-hair); background: transparent; color: var(--ink);
  min-height: 44px; padding: 9px 10px; font-size: var(--t-micro); font-weight: 850; cursor: pointer;
}
.scan-queue-action.primary { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.scan-queue-row.is-rejected .scan-queue-copy span { color: #a74438; font-weight: 800; }
.scan-queue-row.is-retry .scan-queue-copy span { color: #886c20; font-weight: 800; }
.scan-history-row {
  width: 100%; grid-template-columns: 58px minmax(0, 1fr) 28px;
  border: 0; border-bottom: 1px solid var(--line-dark); background: transparent;
  color: var(--ink); text-align: left; font-family: inherit; cursor: pointer;
  transition: background .14s ease, padding-left .14s ease;
}
.scan-history-row:hover { background: rgba(16,20,17,.035); }
.scan-history-row:active { background: rgba(16,20,17,.07); padding-left: 4px; }
.scan-history-row:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.scan-history-row.is-done .scan-queue-preview { box-shadow: inset 3px 0 0 #5f8f42; }
.scan-history-row.is-retry .scan-queue-preview { box-shadow: inset 3px 0 0 #b68d21; }
.scan-history-row.is-rejected .scan-queue-preview { box-shadow: inset 3px 0 0 #b9483e; }
.scan-history-open { width: 28px; height: 44px; display: grid; place-items: center; color: var(--ink-3); }
.scan-history-open svg {
  width: 18px; height: 18px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
.scan-history-open.is-new { color: var(--ink); box-shadow: inset 0 -3px 0 var(--lime); }
.scan-queue-empty { padding: var(--s5) 0; color: var(--ink-3); font-size: var(--t-small); }
@media (max-width: 380px) {
  .scan-queue-row { grid-template-columns: 50px minmax(0, 1fr); }
  .scan-history-row { grid-template-columns: 50px minmax(0, 1fr) 24px; }
  .scan-queue-preview { width: 50px; height: 50px; }
  .scan-queue-actions { grid-column: 2; grid-auto-flow: column; justify-content: start; }
}
.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(2, 1fr); }
  .scan-queue-summary > span:nth-child(3) { border-left: 0; border-top: 1px solid rgba(255,255,255,.13); }
  .scan-queue-summary > span:nth-child(4) { border-top: 1px solid rgba(255,255,255,.13); }
  .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-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 {
  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(400px, 100%); text-align: center;
  padding: 34px 26px 26px; border-radius: var(--r-card);
  background: #12171300; backdrop-filter: blur(2px);
  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;
}
@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;
}
.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: 700; cursor: pointer;
  background: transparent; color: rgba(244,245,239,.5);
}

/* 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; }
}

/* 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. */
.text-input {
  width: 100%; margin-top: 14px; padding: 14px 15px; border-radius: var(--r-card);
  border: 1px solid var(--line-dark); background: #fff; 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. */
.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; }
.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; }
.fact-row span { color: #676d66; }
.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-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-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. */
.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); }
.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-sharp);
  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; }
/* 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: 26px minmax(0, 1fr) auto; align-items: center;
  gap: var(--s3); padding: var(--s2) 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-marke {
  width: 22px; height: 22px; display: grid; place-items: center; border-radius: var(--r-sharp);
  font-size: 11px; font-weight: 900; color: var(--paper);
}
.battle-row.sieg .battle-marke { background: #5f8f42; }
.battle-row.niederlage .battle-marke { background: #b33b32; }
.battle-copy { display: grid; gap: 1px; min-width: 0; }
.battle-copy strong { font-size: var(--t-small); font-weight: 700; }
.battle-copy em { font-size: var(--t-micro); font-style: normal; color: var(--ink-3); }

/* ---- 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-sharp); 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-sharp); 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;
}
.mode-tab strong {
  font-size: var(--t-micro); font-weight: 900; letter-spacing: var(--track-label);
  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 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-sharp);
  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); }
/* 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-sharp);
  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 { gap: 2px; }
.wert.ausdauer i { width: 6px; height: 6px; border-radius: 50%; background: rgba(16,20,17,.16); }
.wert.ausdauer i.voll { background: #4d7a11; }
/* Auf dunklem Grund (Kampfbuehne, Betrachter) kehren sich die Grundtoene um. */
.battle-stage .wert.ausdauer i { background: rgba(255,255,255,.2); }
.battle-stage .wert.ausdauer i.voll { background: var(--lime); }

/* `.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-sharp);
  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); }
