/* ============================================================
   Sammlung – Oberfläche
   Mobil zuerst. Die Farbstimmung wählt man unter „Mehr → Aussehen".
   ============================================================ */

/* ---------- Farbstimmungen -----------------------------------
   Welche gilt, sagt `data-thema` am <html>. Gesetzt wird das vom
   kleinen Skript im <head> von index.html, noch bevor das erste Bild
   steht – „Automatisch" ist dort schon zu hell oder dunkel
   aufgelöst.

   Deshalb steht hier keine `prefers-color-scheme`-Abfrage mehr: sie
   müsste die dunkle Palette ein zweites Mal enthalten, und zwei
   Listen derselben Farben laufen früher oder später auseinander.

   Jede Stimmung setzt denselben vollständigen Satz. Wer eine neue
   anlegt, kopiert einen Block, ändert die Werte und trägt sie in
   THEMEN in app.js ein – sonst taucht sie nirgends zur Auswahl auf.

   --auf-akzent ist die Schrift AUF dem Akzent. Die braucht eine
   eigene Variable, weil der Akzent je nach Stimmung hell oder dunkel
   ist: fest verdrahtetes Weiß wäre auf dem hellen Sandton des
   Dunkelmodus nicht zu lesen.

   `color-scheme` gehört dazu. Ohne die Angabe malt der Browser
   Eingabefelder, Auswahllisten, den Datumsdialog und die Bildlauf-
   leisten weiter in seiner eigenen Voreinstellung – in einer dunklen
   Stimmung dann leuchtend weiß.

   Gedämpfte, warme Töne als Grundlage. Kein reines Weiß, kein reines
   Schwarz – beides wirkt in einer Wohnzimmer-App hart und billig. */

/* Hell: die Voreinstellung. Steht ohne Attribut da, damit die App
   auch dann Farbe hat, wenn das Skript im Kopf nicht zum Zug kam.
   Hier stehen außerdem die Maße, die für alle Stimmungen gelten. */
:root, :root[data-thema="hell"] {
  color-scheme: light;
  --bg:        #f7f3ec;   /* warmes Off-White */
  --surface:   #fffdf8;   /* Karten: Creme, hebt sich leicht ab */
  --surface-2: #ece5d9;
  --line:      #ddd3c3;
  --text:      #2e2520;   /* sehr dunkles Braun statt Schwarz */
  --text-dim:  #6f6258;
  --accent:    #557a63;   /* gedecktes Grün: Knöpfe und Verweise */
  --auf-akzent:#fffdf8;
  --accent-dim:#e3ebe4;
  --ok:        #4a7a5c;
  --warn:      #a97b2e;
  --danger:    #a3544a;
  --star:      #d6a84f;   /* warmes Senfgelb, die Hervorhebung */
  --brett:     #a87552;   /* helles Holz: die Vorderkante */
  --brett-tief:#6b4532;   /* dunkles, warmes Holz */
  --radius:    14px;
  /* Höhe der einen Zeile in der Kopfleiste. Suchfeld und Reitername
     lösen einander dort ab und müssen exakt gleich hoch sein – sonst
     springt beim Wechsel alles darunter. Eine Zahl an einer Stelle,
     damit die beiden nicht auseinanderlaufen können. */
  --kopf-zeile: 42px;
  /* Sicherheitsabstände für Geräte mit Kerbe/abgerundeten Ecken.
     Greifen nur, wenn die App vom Home-Bildschirm gestartet wird –
     im Browser sind sie 0. */
  --safe-t:    env(safe-area-inset-top, 0px);
  --safe-b:    env(safe-area-inset-bottom, 0px);
  /* --topbar-h ist entfallen: die einzige Nutzerin war die klebende
     Filterzeile, und die steht jetzt in der Liste. */
  /* Höhe der eingeblendeten Bildschirmtastatur. Setzt app.js aus
     visualViewport; ohne Tastatur oder auf älteren Geräten bleibt es 0. */
  --tastatur:  0px;
}

/* Dunkel: dasselbe Zimmer bei ausgeschaltetem Licht. */
:root[data-thema="dunkel"] {
  color-scheme: dark;
  --bg:        #221b17;   /* sehr dunkles, warmes Braun statt Schwarz */
  --surface:   #2c2320;
  --surface-2: #382d27;
  --line:      #453830;
  --text:      #f4efe6;
  --text-dim:  #a3958a;
  --accent:    #82b193;   /* dasselbe Grün, für dunklen Grund aufgehellt */
  --auf-akzent:#1e1815;   /* darauf wieder das dunkle Braun */
  --accent-dim:#2f3c35;
  --ok:        #86b491;
  --warn:      #d6a84f;
  --danger:    #cf8b7e;
  --star:      #d6a84f;
  --brett:     #8b5f43;   /* helles Holz, hier etwas zurückgenommen */
  --brett-tief:#4a2e21;   /* dunkles Holz */
}

/* Neutral: dieselbe App ohne Holz und ohne Wärme. Grau und
   Blaugrau – für alle, denen die Creme zu gemütlich ist. */
:root[data-thema="neutral"] {
  color-scheme: light;
  --bg:        #f2f3f4;
  --surface:   #ffffff;
  --surface-2: #e7e9eb;
  --line:      #d6d9dd;
  --text:      #22262a;
  --text-dim:  #62696f;
  --accent:    #4a6b86;
  --auf-akzent:#ffffff;
  --accent-dim:#e2e9ef;
  --ok:        #3f7a5f;
  --warn:      #8f7327;
  --danger:    #a04e46;
  --star:      #b98c33;
  --brett:     #9aa1a7;   /* das Regal aus Metall statt aus Holz */
  --brett-tief:#6f767c;
}

/* Hello Kitty: Weiß, Rosa, ein rotes Schleifchen. Der Akzent ist
   absichtlich dunkler als das Rosa der Bretter – auf ihm steht
   Schrift, das blasse Rosa wäre darunter nicht zu lesen. */
:root[data-thema="kitty"] {
  color-scheme: light;
  --bg:        #fdeef2;
  --surface:   #fffafc;
  --surface-2: #fadbe4;
  --line:      #f3c3d1;
  --text:      #4a2333;
  --text-dim:  #8d5d70;
  --accent:    #cf3f6b;
  --auf-akzent:#fffafc;
  --accent-dim:#fbdde6;
  --ok:        #3f8f72;
  --warn:      #c07f2a;
  --danger:    #c4283c;
  --star:      #e8b32e;
  --brett:     #f5a8c0;
  --brett-tief:#d9718f;
}

/* Game Boy: die vier Töne des alten Bildschirms, mehr hatte er
   nicht. Passt zu einer Sammlung, in der Module liegen. */
:root[data-thema="gameboy"] {
  color-scheme: light;
  --bg:        #9bbc0f;
  --surface:   #a8c72a;
  --surface-2: #8bac0f;
  --line:      #6f8f0c;
  --text:      #0f380f;
  --text-dim:  #2f5a20;
  --accent:    #306230;
  --auf-akzent:#c9dc6e;
  --accent-dim:#8bac0f;
  --ok:        #306230;
  --warn:      #5d5a08;
  --danger:    #5c2010;
  --star:      #4a6b0a;
  --brett:     #6f8f0c;
  --brett-tief:#42580a;
}

/* Bibliothek: grüne Leselampe, Messing, gebundene Bände. Dunkel,
   aber nicht kühl – das Gegenstück zu Mitternacht. */
:root[data-thema="bibliothek"] {
  color-scheme: dark;
  --bg:        #17201c;
  --surface:   #1f2a24;
  --surface-2: #29362e;
  --line:      #35443b;
  --text:      #eceee6;
  --text-dim:  #9aa89c;
  --accent:    #c8a24a;   /* Messing */
  --auf-akzent:#17201c;
  --accent-dim:#2c3a2f;
  --ok:        #7fb58c;
  --warn:      #d8b45e;
  --danger:    #cf8b7e;
  --star:      #d8b45e;
  --brett:     #6b4b2e;
  --brett-tief:#3d2a1a;
}

/* Papier: fast nur Schrift. Für alle, denen jede Farbe zu viel ist –
   die Hüllen bringen ohnehin genug mit. */
:root[data-thema="papier"] {
  color-scheme: light;
  --bg:        #faf9f6;
  --surface:   #ffffff;
  --surface-2: #f0efea;
  --line:      #e0ded6;
  --text:      #1f1e1b;
  --text-dim:  #6b6862;
  --accent:    #2f2e2a;   /* fast schwarz: die Schrift ist der Akzent */
  --auf-akzent:#faf9f6;
  --accent-dim:#eceae2;
  --ok:        #4a7a5c;
  --warn:      #96762c;
  --danger:    #a04a42;
  --star:      #b08f3c;
  --brett:     #cfcabb;
  --brett-tief:#a8a294;
}

/* Sonnenuntergang: warmes Orange auf Pflaume. Kräftig, aber nicht
   grell – die Hüllen müssen darauf noch lesbar bleiben. */
:root[data-thema="sonne"] {
  color-scheme: dark;
  --bg:        #2a1c2b;
  --surface:   #362438;
  --surface-2: #432c44;
  --line:      #533656;
  --text:      #f6ebe4;
  --text-dim:  #b599ad;
  --accent:    #e8834a;
  --auf-akzent:#2a1c2b;
  --accent-dim:#43293a;
  --ok:        #7fb896;
  --warn:      #e8b04a;
  --danger:    #e0736f;
  --star:      #e8b04a;
  --brett:     #8a4f3c;
  --brett-tief:#57302a;
}

/* Meer: kühles Blaugrün, heller Sand als Regal. Der ruhigste der
   hellen Entwürfe. */
:root[data-thema="meer"] {
  color-scheme: light;
  --bg:        #eef4f4;
  --surface:   #ffffff;
  --surface-2: #dce8e8;
  --line:      #c6d8d8;
  --text:      #1c2b2e;
  --text-dim:  #5c7275;
  --accent:    #2f7d80;
  --auf-akzent:#ffffff;
  --accent-dim:#dcebeb;
  --ok:        #3f8a6a;
  --warn:      #9a7a2c;
  --danger:    #a8544c;
  --star:      #c2913a;
  --brett:     #c9b493;
  --brett-tief:#9c8768;
}

/* Mitternacht: dunkel, aber kühl – für Leute, denen das warme
   Braun zu sehr nach Wohnzimmer aussieht. */
:root[data-thema="mitternacht"] {
  color-scheme: dark;
  --bg:        #131a26;
  --surface:   #1b2433;
  --surface-2: #243041;
  --line:      #2f3d51;
  --text:      #e8edf5;
  --text-dim:  #93a1b5;
  --accent:    #7aa2d8;
  --auf-akzent:#131a26;
  --accent-dim:#22334a;
  --ok:        #6fb894;
  --warn:      #d8b25e;
  --danger:    #d98a86;
  --star:      #d8b25e;
  --brett:     #3a4a63;
  --brett-tief:#26324a;
}

/* touch-action: manipulation nimmt iOS das Doppeltippen zum Zoomen ab.
   Ohne das sprang die Ansicht heran, sobald man irgendwo zweimal
   schnell tippte – beim Durchklicken von Einträgen dauernd, und beim
   Geheimnis in der Kopfleiste sowieso.

   Es ist NICHT `none`: Wischen, Scrollen und Zusammenkneifen bleiben
   erlaubt. Nur die Doppeltipp-Geste fällt weg, die eine App mit
   mitwachsendem Layout ohnehin nicht braucht. Das Zusammenkneifen
   bleibt ausdrücklich erhalten – wer schlecht sieht, soll die Seite
   weiterhin größer ziehen können. Deshalb steht auch kein
   `user-scalable=no` im Viewport von index.html.

   Nebenbei entfällt damit die alte 300-ms-Wartezeit vor jedem Klick:
   der Browser muss nicht mehr abwarten, ob noch ein zweiter Tipp
   kommt. Die ganze App reagiert dadurch spürbar direkter. */
* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* ---------- Waagerecht gehört uns ----------------------------
   `pan-y` sagt dem Browser: senkrecht darfst du scrollen, waagerecht
   passiert hier nie etwas von dir aus. Damit reicht er eine
   waagerechte Bewegung SOFORT an das Skript weiter, statt erst
   abzuwarten, ob daraus ein Scrollen wird.

   Gefunden über eine Beobachtung vom Gerät: von der Kamera zur
   Sammlung wischt es flüssig, andersherum hakt es. Der Unterschied
   ist nicht die Größe der Liste, sondern dass die Sammlung eine
   scrollbare Seite ist (6550 Bildpunkte) und der Scan-Reiter nicht
   (844 – genau der Bildschirm). Nur auf der scrollbaren Seite muss
   iOS überhaupt entscheiden, wem die Geste gehört, und genau diese
   Entscheidung ist die gemessene Totzeit von 60 ms.

   `pinch-zoom` bleibt stehen: das Zusammenkneifen soll weiter gehen.

   NICHT auf .filters und nicht auf einen Vorfahren davon – die Zeile
   scrollt selbst waagerecht, und ein `pan-y` weiter oben nähme ihr
   das. Die erlaubten Gesten werden von oben nach unten immer weiter
   eingeschränkt; ein Kind kann nicht zurückholen, was ein Vorfahre
   verboten hat. Deshalb stehen hier die Geschwister der Filterzeile
   einzeln statt einfach `.view`. */
#list, #listMeta, #listEmpty, #vorschlaege,
#scanView, #moreView {
  touch-action: pan-y pinch-zoom;
}

/* Kein Federn am oberen und unteren Rand. Steht bewusst auf BEIDEN,
   html und body: der Wert wandert nur dann von body zum Fenster
   weiter, wenn html selbst auf `auto` steht – und darauf soll sich
   hier nichts verlassen müssen. Verhindert es das Federn trotzdem
   nicht (im Standalone-Betrieb tut es das nicht immer), fängt es die
   Ziehgeste in app.js ab. */
html { overscroll-behavior-y: none; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  overscroll-behavior-y: none;

  /* Die Seite füllt den sichtbaren Bildschirm.

     dvh und nicht svh: auf dem Gerät gemessen sind das 844 gegen 797
     Bildpunkte. svh meint den kleinsten Zustand des Fensters – die
     Seite blieb damit 46 Bildpunkte unter dem, was tatsächlich zu sehen
     ist. Die vh-Zeile davor ist der Rückfall für Browser ohne dvh.

     Das ist reine Kosmetik, damit unter kurzem Inhalt keine Kante
     entsteht. Für den Sitz der Fußleiste taugt sie NICHT – zwei
     Anläufe über die Seitenhöhe (erst 100svh, dann ein Bildpunkt mehr)
     haben daran nichts geändert. Dafür ist fussleisteAusrichten() in
     app.js zuständig. */
  min-height: 100vh;
  min-height: 100dvh;
}

.hidden { display: none !important; }
.muted  { color: var(--text-dim); }
.small  { font-size: 13px; }
.error  { color: var(--danger); min-height: 1.2em; font-size: 14px; }
.ok-text { color: var(--ok); }

/* ---------- Anmeldung ---------- */
.login {
  position: fixed; inset: 0; display: grid; place-items: center;
  padding: 24px; background: var(--bg); z-index: 50;
}
.login-box { width: 100%; max-width: 340px; text-align: center; }
/* Das Symbol bringt seinen eigenen Grund mit – hier nur die Ecke
   runden und mittig setzen. */
.login-logo {
  width: 76px; height: 76px; margin: 0 auto 14px;
  display: block; border-radius: 20px;
}
.login h1 { margin: 0 0 4px; font-size: 26px; }
.login p  { margin: 0 0 26px; }
.login label { display: block; text-align: left; margin-bottom: 14px; font-size: 14px; color: var(--text-dim); }
.login input {
  width: 100%; margin-top: 5px; padding: 12px 14px; font-size: 16px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--text);
}

/* ---------- Kopfleiste ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; gap: 8px; align-items: center;
  /* Oben Platz für die Kerbe, unten normal.
     (Vorher stand der obere Abstand versehentlich im unteren.) */
  padding: calc(10px + var(--safe-t)) 12px 10px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
/* Beide Kinder, die einander in der Kopfleiste ablösen, sind exakt
   gleich hoch – nicht ungefähr. Die Höhe steht als eine Zahl in
   --kopf-zeile, damit sie nicht an zwei Stellen auseinanderlaufen
   kann; das Suchfeld weiter unten nimmt dieselbe. */
.search-wrap {
  position: relative; flex: 1; height: var(--kopf-zeile);
  display: flex; align-items: center;
}
/* Auf Scannen und Mehr steht hier der Name des Reiters statt der Suche.
   Dieselbe Zeile, derselbe Platz – nur ein anderer Inhalt. */
.topbar-titel {
  flex: 1; margin: 0; height: var(--kopf-zeile);
  display: flex; align-items: center;
  font-size: 19px; font-weight: 650; color: var(--text); padding: 0 2px;
}
.search-icon {
  position: absolute; left: 11px; width: 17px; height: 17px;
  fill: var(--text-dim); pointer-events: none;
}
/* Höhe gesetzt, nicht ausgerechnet.

   Vorher ergab sie sich aus Innenabstand plus Zeilenhöhe – und die
   steht auf `normal`, hängt also an den Maßen der Systemschrift. Am
   Rechner kommen damit genau 42 Bildpunkte heraus wie beim Titel
   daneben, auf dem iPhone aber 43,09: San Francisco ist bei 16px
   rund 19,1 Bildpunkte hoch statt 18. Diesen einen Bildpunkt sprang
   die Kopfleiste beim Wechsel auf Scannen oder Mehr, und das war als
   Ruckler zu sehen.

   Deshalb `height` statt senkrechtem Innenabstand: einzeilige Felder
   zentrieren ihren Text von selbst. `appearance: none` muss dazu –
   sonst legt iOS dem Suchfeld seine eigenen Maße an und die feste
   Höhe wäre wieder nur ein Vorschlag. Die 16px bleiben: darunter
   zoomt Safari beim Antippen ins Feld hinein. */
#search {
  width: 100%; height: var(--kopf-zeile); padding: 0 36px 0 34px;
  font-size: 16px; -webkit-appearance: none; appearance: none;
  border: 1px solid var(--line); border-radius: 11px;
  background: var(--surface); color: var(--text);
}
#clearSearch { position: absolute; right: 4px; }

.icon-btn {
  background: none; border: 0; color: var(--text-dim);
  font-size: 17px; padding: 8px 10px; cursor: pointer; border-radius: 9px;
}
#syncBtn { position: relative; width: 40px; height: 40px; }
/* ---------- Der Stand des Abgleichs ----------
   Vorher ein farbiger Punkt. Der sagte zwar dasselbe, sah aber aus
   wie aus einer Netzwerkeinstellung – hier steht ein Regal.

   Die Bücher nehmen currentColor, das Brett bleibt Holz. So trägt
   der Knopf die Farbe des Zustands, und das Brett geht in jeder
   Farbstimmung mit. */
.regal-icon {
  display: block; width: 22px; height: 22px; margin: 0 auto;
  color: var(--ok); transition: color .2s;
}
.regal-icon .regal-brett { fill: var(--brett); }
.regal-icon .buch        { fill: currentColor; }
/* transform-box: fill-box – sonst bezöge sich der Drehpunkt auf das
   ganze Bild und das Buch schwänge quer durchs Regal statt auf seiner
   eigenen unteren Kante zu kippen. */
.regal-icon .buch-schief {
  transform-box: fill-box; transform-origin: 100% 100%;
  transition: transform .35s ease;
}

.sync-regal.pending .regal-icon, .sync-regal.syncing .regal-icon { color: var(--warn); }
.sync-regal.offline .regal-icon { color: var(--text-dim); }
.sync-regal.pending .buch-schief,
.sync-regal.offline .buch-schief { transform: rotate(15deg); }
/* Während gesendet wird, räumt sich das Buch ein ums andere Mal ein. */
.sync-regal.syncing .buch-schief { animation: einraeumen 1.2s ease-in-out infinite; }
@keyframes einraeumen {
  0%, 100% { transform: rotate(15deg); }
  55%      { transform: rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .regal-icon .buch-schief { transition: none; }
  .sync-regal.syncing .buch-schief { animation: none; transform: rotate(15deg); }
}

/* ---------- Filter ---------- */
/* Die Filterzeile steht jetzt IN der Liste und läuft mit ihr mit.
   Vorher klebte sie als feste Leiste unter der Kopfleiste; damit
   änderte sich die Höhe des festen Teils je nach Reiter, und alles
   darunter sprang. Dafür scrollt sie beim Blättern nach oben weg –
   ein bewusster Tausch. */
.filters {
  display: flex; align-items: center; gap: 7px;
  overflow-x: auto; overscroll-behavior-x: contain;
  padding: 10px 12px; scrollbar-width: none; background: var(--bg);
  border-bottom: 1px solid var(--line);
}
.filters::-webkit-scrollbar { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto; padding: 6px 13px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--text-dim); font: inherit; font-size: 13.5px;
  cursor: pointer; white-space: nowrap;
  transition: transform .13s cubic-bezier(.22,.61,.36,1),
              background-color .13s, border-color .13s, color .13s;
}
/* Ein Chip, der sich unter dem Finger kurz duckt, fühlt sich an wie
   eine Taste. Ohne das wirkt jede Wahl wie ein Zufall. */
.chip:active { transform: scale(.94); }
.chip.on { background: var(--accent); border-color: var(--accent); color: var(--auf-akzent); }

/* Die Zahl im Chip: da, aber nicht laut. Sie ist die Antwort auf
   „lohnt das", nicht die Beschriftung. */
.chip-n {
  font-size: 11.5px; font-variant-numeric: tabular-nums;
  opacity: .62; font-weight: 600;
}
.chip.on .chip-n { opacity: .8; }

/* Trennt die zweite Ebene (Plattform, Format) von den Typen ab. */
.chip-trenner {
  flex: 0 0 auto; width: 1px; align-self: stretch;
  margin: 3px 3px; background: var(--line);
}

/* „Mehr …" – kein Filter, sondern eine Tür. Deshalb ohne Rahmen. */
.chip-mehr { border-style: dashed; }

/* Ein Filter aus dem Blatt, hier zum Abwählen. Der wichtigste Chip
   der Zeile: ohne ihn wüsste niemand, warum die Liste so kurz ist.
   Zwei Klassen im Selektor, damit er das gleich starke `.chip.on`
   schlägt – sonst säßen zwei satt gefärbte Chips nebeneinander und
   der zum Abwählen sähe aus wie eine Auswahl. */
.chip.chip-weg {
  background: var(--accent-dim); border-color: var(--accent);
  color: var(--accent); font-weight: 600;
}

/* Die mitzählende Zahl im Filterblatt. */
.filter-zahl {
  margin: 14px 0 2px; padding: 10px 12px; border-radius: 10px;
  background: var(--surface-2); color: var(--text);
  font-size: 14px; font-weight: 600; text-align: center;
}
.filter-zahl.leer { color: var(--danger); }

/* ---------- Liste ---------- */
main { padding-bottom: calc(84px + var(--safe-b)); }

/* ---------- Reiter am Finger ziehen ----------
   Während der Geste liegen zwei Ansichten übereinander: die alte
   bleibt im Fluss stehen, die neue wird absolut darübergelegt und um
   eine Bildschirmbreite versetzt. Bewegt wird ausschließlich per
   transform – Layout und Bildlauf bleiben damit unangetastet, und die
   Seite springt beim Loslassen nicht.

   `overflow-x: clip` statt `hidden`: hidden würde auch senkrecht einen
   eigenen Bildlauf erzeugen und die klebende Kopfleiste abschneiden. */
/* flow-root: der obere Außenabstand der ersten Karte bleibt IM Element,
   statt nach außen durchzuschlagen. Ohne das stand eine Ansicht im
   Fluss zwölf Bildpunkte anders als dieselbe Ansicht absolut
   positioniert während des Wischens – absolut positionierte Elemente
   lassen Außenabstände nicht durch. Genau dieser Betrag rutschte beim
   Umschalten nach. Am sichtbaren Ergebnis ändert sich sonst nichts. */
.view { display: flow-root; }

/* Den Platz für die Kerbe hält jetzt wieder die Kopfleiste frei – sie
   steht ja auf jedem Reiter. Die Sonderpolsterung für Scannen und Mehr
   ist damit hinfällig; bliebe sie stehen, wäre der Abstand dort
   doppelt. */

/* `position: relative` steht hier dauerhaft, NICHT erst unter
   `.wischt` – und das ist kein Schönheitsfehler, sondern der Grund,
   warum das Wischen ohne Ruckler anfängt.

   Es macht main zum Bezugspunkt für offsetTop. Vorher kam der erst
   mit der Klasse `.wischt` dazu: hoehenAngleichen() musste also NACH
   dem Klassenwechsel messen, und weil der das Layout ungültig macht,
   erzwang die Messung ein vollständiges Neurechnen der Seite. Bei
   300 Einträgen ist die 25.000 Bildpunkte hoch – nachgemessen 8 ms
   allein dafür, spürbar als Hängen beim Ansetzen der Geste.

   Jetzt steht der Bezugspunkt schon vorher fest, zugStarten() misst
   vor dem Klassenwechsel aus dem sauberen Layout, und dieselbe
   Messung kostet 0,0 ms. Der Wert ist derselbe – nachgemessen mit
   und ohne den Hinweis „Zum Home-Bildschirm", oben und mitten in der
   Liste.

   Ohne Versatz verschiebt `position: relative` nichts und erzeugt
   auch keinen eigenen Stapelzusammenhang; im Fluss ändert sich
   dadurch nichts. */
main { position: relative; }
main.wischt { overflow-x: clip; }
main.wischt > .view { will-change: transform; backface-visibility: hidden; }
main.wischt > .view.wisch-neu {
  position: absolute; top: 0; left: 0; right: 0;
  background: var(--bg);
}
/* Beim Loslassen übernimmt der Übergang; während des Ziehens ist er
   ausgeschaltet, sonst hinkte die Ansicht dem Finger hinterher. */
.view.wisch-gleitet { transition: transform .24s cubic-bezier(.22,.61,.36,1); }

/* Der Plusknopf gehört nur zu den Listen. Er ist fest positioniert und
   ändert an der Höhe nichts – er darf deshalb einfach ausblenden,
   statt zu verschwinden. */
.fab { transition: opacity .18s ease-out; }
.fab.fab-weg { opacity: 0; pointer-events: none; }

.list-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px 8px; font-size: 13px; color: var(--text-dim);
}
.list { padding: 0 12px; }

/* ============================================================
   Das Regal

   Jeder Eintrag steckt in der Hülle seiner echten Verpackung: ein
   Switch-Spiel in Rot, ein DS-Spiel in Weiß, eine Blu-ray in Blau.
   Die Farben stehen in app.js unter HUELLEN – das ist die einzige
   Stelle, an der sie geändert werden müssen.

   Drei Ansichten: liste (schmal), regal (drei nebeneinander auf
   einem Brett), gross (zwei nebeneinander, Rücken beschriftet).
   ============================================================ */

/* --- Reihe und Brett ---------------------------------------- */
/* align-items: stretch – alle Kacheln einer Reihe werden so hoch wie
   die höchste. Ohne das verschieben sich die Hüllen bei unterschiedlich
   langen Titeln gegeneinander und stehen nicht mehr auf einer Linie. */
.reihe {
  display: grid; gap: 12px; align-items: stretch; padding: 0 2px;
}
/* minmax(0, 1fr) statt 1fr: "1fr" heißt in Wahrheit minmax(auto, 1fr),
   und "auto" als Untergrenze lässt einen breiten Inhalt – etwa einen
   langen Titel – die Spalte aufziehen. Dann wären die Hüllen einer
   Reihe unterschiedlich groß. Mit 0 als Untergrenze bleiben alle
   Spalten exakt gleich breit. */
.regal-3 .reihe { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.regal-2 .reihe { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.brett {
  height: 10px; margin: 0 -4px 22px; border-radius: 2px;
  background: linear-gradient(180deg,
    var(--brett) 0%, var(--brett) 55%, var(--brett-tief) 100%);
  box-shadow: 0 6px 10px -6px rgba(0,0,0,.5);
}

/* --- Ein Stück im Regal ------------------------------------- */
.row-item {
  display: flex; flex-direction: column; width: 100%;
  padding: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.row-item > .huelle { flex: 0 0 auto; }
.row-item > .row-main { flex: 1 1 auto; }
.row-item:active .huelle { transform: translateY(1px); }

.huelle {
  position: relative; display: block; width: 100%;
  aspect-ratio: 2 / 3; overflow: hidden;
  border-radius: 2px 3px 3px 2px;
  background: var(--huelle, var(--surface-2));
  box-shadow: 0 3px 6px -2px rgba(0,0,0,.45),
              0 1px 0 rgba(255,255,255,.10) inset;
  transition: transform .12s;
}

/* Der Rücken links – das Erkennungsmerkmal der Hülle. */
.ruecken {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 11%; min-width: 7px; background: var(--huelle, var(--surface-2));
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; z-index: 2;
}
.ruecken::after {
  content: ''; position: absolute; right: 0; top: 0; bottom: 0;
  width: 1px; background: rgba(0,0,0,.28);
}
/* Farbiges Band oben am Rücken – 3DS, Xbox 360, PS2 und PS5 haben so eines */
.ruecken-band {
  position: absolute; left: 0; right: 0; top: 0; height: 16%;
  border-bottom: 1px solid rgba(0,0,0,.22);
}
.ruecken-schrift {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-size: 10px; font-weight: 700; letter-spacing: .08em;
  color: var(--huelle-schrift, var(--text)); text-transform: uppercase;
  white-space: nowrap; overflow: hidden; max-height: 78%;
}

/* Kunststoff glänzt, Pappe und Papier bleiben matt. */
.material-kunststoff::before {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: linear-gradient(105deg,
    rgba(255,255,255,.20) 0%, rgba(255,255,255,.05) 14%,
    rgba(255,255,255,0) 34%, rgba(255,255,255,0) 100%);
}
.material-papier::before, .material-pappe::before {
  content: ''; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background: linear-gradient(90deg, rgba(0,0,0,.16) 0%, rgba(0,0,0,0) 16%);
}

/* Die Vorderseite. Das Cover wird NICHT beschnitten, sondern ganz
   gezeigt und sitzt mit Luft in der Hülle – ringsum bleibt deren
   Farbe sichtbar, wie das Papier in einer echten Hülle. */
.front {
  position: absolute; left: 11%; right: 0; top: 0; bottom: 0;
  padding: 5% 5% 5% 3.5%;
  display: flex; align-items: center; justify-content: center;
  background: var(--huelle, var(--surface-2));
}
.front img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; display: block;
  box-shadow: 0 0 0 1px rgba(0,0,0,.3), 0 1px 5px rgba(0,0,0,.3);
}

/* Ohne Cover wird die Hülle selbst zum Schild. */
.front-ersatz {
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 9% 8%; background: var(--huelle, var(--surface-2));
  color: var(--huelle-schrift, var(--text));
}
.front-titel {
  font-weight: 700; font-size: 13px; line-height: 1.22; letter-spacing: -0.01em;
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;
  overflow: hidden;
}
.front-quelle {
  font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; opacity: .72;
}

/* Verliehen: eine Papierbanderole, wie um ein Buch gelegt. */
.banderole {
  position: absolute; z-index: 4; left: -22%; top: 16%;
  transform: rotate(-24deg);
  background: #e8dcc0; color: #4a3a18;
  font-size: 9.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; text-align: center; padding: 3px 30px;
  box-shadow: 0 1px 3px rgba(0,0,0,.35);
}

/* Wunschliste: steht noch nicht im Regal – eine leere Stelle. */
.row-item.wunsch .huelle {
  background: color-mix(in srgb, var(--brett) 16%, transparent);
  box-shadow: none;
  border: 2px dashed color-mix(in srgb, var(--brett) 85%, var(--text));
}
.row-item.wunsch .front img { opacity: .45; filter: grayscale(.7); }
.row-item.wunsch .ruecken { opacity: .55; }
.row-item.wunsch .front-ersatz { background: transparent; color: var(--text-dim); }

/* --- Der fehlende Band: eine Lücke im Regal ------------------
   Bewusst kein Kasten, sondern ein Loch. Die Hülle fehlt, man sieht
   die Rückwand – deshalb kein Schatten nach außen, sondern einer
   nach innen. Genau so sieht ein Regal aus, aus dem etwas heraus
   ist, und genau das soll die Ansicht zeigen. */
.huelle-luecke {
  border: 1px dashed color-mix(in srgb, var(--brett-tief) 55%, transparent);
  background: color-mix(in srgb, var(--brett-tief) 12%, transparent);
  box-shadow: inset 0 2px 9px -3px rgba(0,0,0,.5);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
  color: color-mix(in srgb, var(--brett-tief) 70%, var(--text-dim));
}
.luecke-nr   { font-size: 26px; font-weight: 700; line-height: 1; opacity: .55; }
.luecke-wort {
  font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase; opacity: .7;
}
.row-item.luecke .row-title,
.row-item.luecke .row-sub { color: var(--text-dim); font-style: italic; }
.regal-1 .luecke-nr { font-size: 20px; }

/* --- Ansicht „Liste": Hülle links, Text rechts -------------- */
/* flex-direction ausdrücklich zurücksetzen: die Grundregel stellt für
   die Rasteransichten auf "column". Ohne das Zurücksetzen stünde der
   Text unter dem Cover, und flex-basis wirkte auf die Höhe statt auf
   die Breite – die Hülle würde quadratisch. */
.regal-1 .row-item {
  display: flex; flex-direction: row; align-items: center; gap: 15px;
  padding: 11px 2px; border-bottom: 1px solid var(--line);
}
.regal-1 .huelle { width: 70px; flex: 0 0 70px; }
.regal-1 .front { padding: 4% 4% 4% 3%; }
.regal-1 .ruecken-schrift { font-size: 8px; }
.regal-1 .banderole { font-size: 8px; padding: 2px 26px; left: -26%; }
.regal-1 .row-main { flex: 1; min-width: 0; }

/* --- Ansicht „Groß": Rücken mit Beschriftung ---------------- */
.regal-2 .ruecken { width: 13%; }
.regal-2 .front { left: 13%; }
.regal-2 .front-titel { font-size: 16px; }
.regal-2 .row-title { font-size: 15px; }

/* --- Umschalter der Ansichten ------------------------------- */
.ansicht-wahl { display: flex; gap: 4px; }
.ansicht-wahl button {
  padding: 5px 9px; font: inherit; font-size: 12px;
  border: 1px solid var(--line); background: transparent;
  color: var(--text-dim); border-radius: 999px; cursor: pointer;
}
.ansicht-wahl button.an {
  background: var(--accent); border-color: var(--accent);
  color: var(--auf-akzent); font-weight: 600;
}

.cover {
  width: 46px; height: 66px; flex: 0 0 46px; border-radius: 7px;
  object-fit: cover; background: var(--surface-2);
  display: grid; place-items: center; font-size: 20px; color: var(--text-dim);
}
.row-main { min-width: 0; padding: 7px 1px 0; }
.regal-1 .row-main { padding: 0; }
/* Immer zwei Zeilen Platz, auch bei kurzen Titeln – sonst wären die
   Kacheln unterschiedlich hoch und die Regalbretter verschöben sich. */
.row-title {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  font-weight: 600; font-size: 13.5px; line-height: 1.25;
  min-height: calc(2 * 1.25em); overflow: hidden;
}
.regal-1 .row-title { font-size: 16px; line-height: 1.3; min-height: 0; }
.row-sub {
  font-size: 11.5px; color: var(--text-dim); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.regal-1 .row-sub { font-size: 13px; margin-top: 3px; }
.row-tags { display: flex; gap: 5px; margin-top: 5px; flex-wrap: wrap; }
.tag {
  font-size: 11px; padding: 2px 7px; border-radius: 5px;
  background: var(--surface-2); color: var(--text-dim);
}
.tag.wish   { background: var(--accent-dim); color: var(--accent); }
.tag.lent   { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); }
.tag.prio1  { background: color-mix(in srgb, var(--danger) 18%, transparent); color: var(--danger); }

.empty { padding: 60px 30px; text-align: center; color: var(--text-dim); }
.empty-big { font-size: 42px; margin-bottom: 10px; opacity: .5; }
/* Während der Suche: Platz sparen, darunter kommen die Vorschläge. */
.empty.empty-knapp { padding: 26px 30px 4px; font-size: 14px; }

/* ---------- Karten ---------- */
/* --- Hinweisstreifen, z. B. „Zum Home-Bildschirm" --- */
.notice {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 12px 12px 0; padding: 12px 12px 12px 14px;
  background: var(--accent-dim); color: var(--text);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  border-radius: var(--radius);
  font-size: 13.5px; line-height: 1.45;
}
.notice-text { flex: 1; min-width: 0; }
.notice-text b { display: block; margin-bottom: 3px; }
.notice .icon-btn { flex: none; margin: -3px -4px 0 0; }

/* --- Zeilen mit Beschriftung links, Wert rechts (Karte „Speicher") --- */
.kv-row {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; padding: 8px 0; font-size: 14px;
  border-bottom: 1px solid var(--line);
}
.kv-row:last-of-type { border-bottom: 0; }
.badge {
  flex: none; padding: 3px 9px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600;
}
.badge.ok   { background: color-mix(in srgb, var(--ok) 20%, transparent);   color: var(--ok); }
.badge.warn { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); }

.card {
  margin: 12px; padding: 16px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.card h3 { margin: 0 0 8px; font-size: 15px; }

/* ---------- Auswahl der Farbstimmung ----------
   minmax(0, 1fr), nicht 1fr: ein langer Name wie „Mitternacht" würde
   seine Spalte sonst aufziehen und die Kacheln ungleich breit machen. */
.themen {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px; margin-top: 4px;
}
.themen > * { min-width: 0; }
.thema {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--text); font: inherit; font-size: 13.5px;
  text-align: left; cursor: pointer;
}
.thema.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.thema-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Die Probe zeigt drei Streifen: Grund, Akzent, Regal. Das reicht,
   um eine Stimmung zu erkennen, ohne sie erst einzuschalten. */
.probe {
  flex: 0 0 auto; display: flex; width: 26px; height: 26px;
  border-radius: 7px; overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--text) 25%, transparent);
}
.probe > i { flex: 1; }

.stat-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 4px; }
.stat { background: var(--surface-2); border-radius: 10px; padding: 12px; }
.stat-num { font-size: 22px; font-weight: 650; }
.stat-lab { font-size: 12px; color: var(--text-dim); }
.bar-row { display: flex; align-items: center; gap: 8px; margin-top: 7px; font-size: 13px; }
.bar-lab { width: 78px; flex: 0 0 78px; color: var(--text-dim); }
.bar { flex: 1; height: 7px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--accent); }
.bar-num { width: 34px; text-align: right; color: var(--text-dim); }

/* ---------- Knöpfe ---------- */
.btn {
  padding: 11px 16px; font-size: 15px; font-family: inherit;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-2); color: var(--text); cursor: pointer;
}
.btn:active { opacity: .75; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--auf-akzent); }
.btn.wide    { width: 100%; margin-top: 8px; }
.btn.danger-outline { color: var(--danger); border-color: var(--danger); background: transparent; }
.btn.ghost   { background: transparent; }
.btn.sm      { padding: 7px 12px; font-size: 13.5px; }

/* --fuss-fehler gleicht aus, dass iOS fest positionierte Elemente je
   nach Reiter und Scrollstand zu hoch setzt. Auf dem Gerät gemessen:
   clientHeight 797 gegen innerHeight 844 – die 47 Bildpunkte des oberen
   Sicherheitsabstands. Feste Elemente hängen am Layout-Fenster, und
   dessen Ursprung liegt in diesem Zustand daneben.

   Den Wert setzt fussleisteAusrichten() in app.js: es misst, wie weit
   die Leiste danebensitzt, und trägt genau diesen Betrag ein. Stimmt
   alles, steht dort 0 und nichts verschiebt sich. */
.fab {
  position: fixed; right: 16px; bottom: calc(74px + var(--safe-b));
  transform: translateY(var(--fuss-fehler, 0px));
  width: 54px; height: 54px; border-radius: 50%; border: 0;
  background: var(--accent); color: var(--auf-akzent); font-size: 30px; line-height: 1;
  box-shadow: 0 5px 18px rgba(0,0,0,.28); cursor: pointer; z-index: 21;
}

/* ---------- Tableiste ---------- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 25;
  transform: translateY(var(--fuss-fehler, 0px));   /* siehe oben bei .fab */
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--surface); border-top: 1px solid var(--line);
  padding-bottom: var(--safe-b);
}
.tab {
  background: none; border: 0; padding: 8px 2px 7px; cursor: pointer;
  color: var(--text-dim); font-size: 11px; font-family: inherit;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
/* Zusätzlich fetter, nicht nur farbig: im Hellmodus liegen das dunkle
   Braun des Akzents und das gedämpfte Grau der übrigen Tabs zu nah
   beieinander, um den aktiven allein an der Farbe zu erkennen. */
.tab.active { color: var(--accent); font-weight: 650; }
.tab-icon { font-size: 19px; line-height: 1; }

/* ---------- Scanner ---------- */
.scan-stage {
  position: relative; background: #000; aspect-ratio: 4/3;
  display: grid; place-items: center; overflow: hidden;
}
#video { width: 100%; height: 100%; object-fit: cover; }
.scan-frame {
  position: absolute; width: 74%; height: 32%;
  border: 2px solid rgba(255,255,255,.85); border-radius: 12px;
  box-shadow: 0 0 0 100vmax rgba(0,0,0,.35);
}
.scan-hint {
  position: absolute; bottom: 12px; left: 0; right: 0; text-align: center;
  color: #fff; font-size: 13px; text-shadow: 0 1px 4px #000; padding: 0 20px;
}
.scan-controls { padding: 14px 16px; }
.or { text-align: center; color: var(--text-dim); font-size: 12.5px; margin: 14px 0 8px; }
.row { display: flex; gap: 8px; }
.row input {
  flex: 1; padding: 11px 13px; font-size: 16px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface); color: var(--text);
}
.scan-result { padding: 0 12px; }

/* --- Der Befund nach dem Scannen -----------------------------
   Eine Zeile, kein Kasten. Vorher nahmen Befund und die zwei großen
   Knöpfe darunter die halbe untere Bildhälfte ein – und der
   Vorschlag aus dem Netz, also genau das, wonach man gescannt hat,
   stand darunter außerhalb des Bildes. Es sah aus, als gäbe es
   keinen. Was hier gespart wird, gewinnen die Vorschläge. */
.befund {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  padding: 9px 12px; margin-bottom: 10px;
  border-radius: 10px; border: 1px solid var(--line);
  background: var(--surface);
}
.befund-satz { font-size: 14.5px; font-weight: 650; }
.befund-code {
  font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.befund.haben {
  border-color: var(--ok);
  background: color-mix(in srgb, var(--ok) 10%, var(--surface));
}
.befund.wunsch { border-color: var(--accent); background: var(--accent-dim); }

/* Die Überschrift der Vorschläge trägt die Anzahl und einen Strich,
   der bis zum Rand läuft: eine Kante, die auch dann als „hier geht es
   weiter" zu lesen ist, wenn nur sie ins Bild ragt. */
.vorschlag-zahl {
  display: flex; align-items: center; gap: 9px;
  margin: 12px 0 9px; font-size: 13px; font-weight: 650; color: var(--accent);
}
.vorschlag-zahl::after {
  content: ''; flex: 1; height: 1px;
  background: color-mix(in srgb, var(--accent) 35%, transparent);
}
.scan-sucht { font-size: 13px; color: var(--text-dim); padding: 8px 2px; }
.scan-hand  { margin-top: 6px; }

/* ---------- Formularblatt ---------- */
.sheet { position: fixed; inset: 0; z-index: 40; }
.sheet-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,.45); }
.sheet-body {
  /* Um die Tastaturhöhe angehoben: iOS legt die Tastatur über die Seite,
     ohne das Fenster zu verkleinern – die unteren Felder lägen sonst
     darunter und wären beim Tippen nicht zu sehen. */
  position: absolute; left: 0; right: 0; bottom: var(--tastatur);
  max-height: calc(92vh - var(--tastatur)); overflow-y: auto;
  /* Am Ende der Liste nicht die Seite dahinter mitziehen – sonst
     wackelt beim Zuziehen der ganze Hintergrund mit. */
  overscroll-behavior: contain;
  background: var(--bg); border-radius: 18px 18px 0 0;
  padding: 8px 16px calc(26px + var(--safe-b));
  animation: rise .22s ease-out;
}
@keyframes rise { from { transform: translateY(24px); opacity: .4; } }
.sheet-handle {
  width: 38px; height: 4px; border-radius: 3px; background: var(--line);
  margin: 4px auto 14px;
  /* Der Strich ist der sichtbare Hinweis auf die Ziehgeste. Der Griff
     selbst reagiert auf der ganzen Blattbreite, nicht nur hier. */
}
.sheet h2 { margin: 0 0 4px; font-size: 19px; }

.field { margin-bottom: 12px; }
.field label { display: block; font-size: 12.5px; color: var(--text-dim); margin-bottom: 4px; }
.field input, .field select, .field textarea {
  width: 100%; padding: 10px 12px; font-size: 16px; font-family: inherit;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); color: var(--text);
}
.field textarea { min-height: 74px; resize: vertical; }

/* --- Eigenes Foto im Formular --- */
.foto-zeile { display: flex; gap: 12px; align-items: flex-start; }
.foto-vorschau {
  width: 62px; height: 88px; flex: 0 0 62px; object-fit: cover;
  border-radius: 8px; background: var(--surface-2); border: 1px solid var(--line);
}
.foto-knoepfe { display: flex; flex-direction: column; gap: 8px; flex: 1; }
.foto-knoepfe .btn { width: 100%; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.grid2 .field { margin-bottom: 0; }

.detail-head { display: flex; gap: 14px; margin-bottom: 16px; }
/* Die Hülle in der Detailansicht */
.detail-huelle { width: 104px; flex: 0 0 104px; }
.detail-huelle .ruecken { width: 13%; }
.detail-huelle .front { left: 13%; }

.detail-cover {
  width: 92px; height: 132px; flex: 0 0 92px; border-radius: 10px;
  object-fit: cover; background: var(--surface-2);
  display: grid; place-items: center; font-size: 32px; color: var(--text-dim);
}
.detail-facts { margin: 14px 0; }
.fact { display: flex; padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.fact-k { width: 118px; flex: 0 0 118px; color: var(--text-dim); }
.fact-v { flex: 1; }
.sheet-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.sheet-actions .btn { flex: 1; min-width: 120px; }

.cand {
  display: flex; gap: 11px; align-items: center; padding: 9px;
  border: 1px solid var(--line); border-radius: 11px;
  background: var(--surface); margin-bottom: 8px; cursor: pointer;
}
.cand:active { background: var(--surface-2); }
.cand img, .cand .cover { width: 40px; height: 58px; flex: 0 0 40px; }

/* Der Eintrag im Ergänzen-Assistenten: nur zum Zeigen, nicht zum
   Antippen – deshalb kein .cand und kein Zeigefinger-Cursor. */
.ergaenz-item { display: flex; gap: 11px; align-items: center; margin-bottom: 10px; }
.ergaenz-item .cover { width: 40px; height: 58px; flex: 0 0 40px; }

/* Ein Wort, das wie ein Link aussieht statt wie ein Knopf – für ein
   „zurücksetzen" mitten im Fließtext, kein eigener Kasten wert. */
.link-btn {
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  color: var(--accent); font: inherit; font-size: inherit;
  text-decoration: underline;
}

/* ---------- Reihen -------------------------------------------
   Drei Orte, dieselbe Sprache: der Hinweis in der Detailansicht,
   die Karte unter „Mehr" und die Liste im Blatt. Fehlendes ist
   gestrichelt, Vorhandenes steht fest.
   ------------------------------------------------------------ */

/* Der Hinweis unter einem Eintrag: „Bände 3 und 5 fehlen euch". */
.reihe-hinweis {
  display: block; width: 100%; text-align: left;
  margin: 0 0 12px; padding: 12px 14px;
  border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--surface); color: inherit; font: inherit; cursor: pointer;
}
.reihe-hinweis.offen {
  background: color-mix(in srgb, var(--star) 13%, var(--surface));
  border-color: color-mix(in srgb, var(--star) 45%, transparent);
}
.reihe-hinweis:active { background: var(--surface-2); }
.reihe-name  { font-weight: 650; font-size: 14.5px; }
.reihe-stand { font-size: 13px; color: var(--text-dim); margin-top: 2px; }

/* Die Karte unter „Mehr": Name, Stand, Balken. */
.reihe-karte {
  display: block; width: 100%; text-align: left;
  margin-bottom: 9px; padding: 11px 12px;
  border: 1px solid var(--line); border-radius: 11px;
  background: var(--surface-2); color: inherit; font: inherit; cursor: pointer;
}
.reihe-karte:active { background: var(--surface); }
.reihe-karte-kopf {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
}
.reihe-karte-kopf .reihe-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reihe-zahl {
  flex: none; font-size: 12.5px; font-weight: 700; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.reihe-balken {
  height: 6px; margin: 8px 0 6px; border-radius: 999px;
  background: var(--line); overflow: hidden;
}
.reihe-balken span { display: block; height: 100%; background: var(--accent); }

/* Die ganze Reihe im Blatt, Band für Band. */
.reihe-liste { margin: 14px 0 4px; }
.reihe-zeile {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 9px 4px; border: 0; border-bottom: 1px solid var(--line);
  background: none; color: inherit; font: inherit; font-size: 14px;
  text-align: left; cursor: pointer;
}
.reihe-zeile:active { background: var(--surface-2); }
.reihe-nr {
  flex: 0 0 26px; text-align: center; font-weight: 700;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
.reihe-titel { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reihe-tat { flex: none; font-size: 12px; color: var(--text-dim); }
.reihe-zeile.wunsch .reihe-tat { color: var(--accent); }
/* Fehlt: gestrichelt umrandete Nummer, blasse Zeile – dieselbe
   Sprache wie die Lücke im Regal. */
.reihe-zeile.fehlt { color: var(--text-dim); font-style: italic; }
.reihe-zeile.fehlt .reihe-nr {
  border: 1px dashed var(--line); border-radius: 6px; font-style: normal;
}
.reihe-zeile.fehlt .reihe-tat { color: var(--accent); font-style: normal; }

/* ---------- Dublettenwarnung im Formular ----------
   Steht direkt unter dem Titelfeld und wächst beim Tippen herein.
   Kein Rot: es ist kein Fehler, zwei Ausgaben zu besitzen – nur
   selten Absicht. */
.dublette-box { margin: -4px 0 12px; }
.dublette-box:not(.an) { display: none; }
.dublette-kopf {
  font-size: 12.5px; font-weight: 650; color: var(--warn);
  margin-bottom: 7px;
}
.dublette-zeile {
  display: flex; gap: 10px; align-items: center;
  padding: 8px 10px; margin-bottom: 6px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--warn) 34%, transparent);
}
.dublette-zeile .row-main { padding: 0; }
.dublette-zeile .row-title { font-size: 13.5px; min-height: 0; -webkit-line-clamp: 1; }
.cover-klein { width: 34px; height: 49px; flex: 0 0 34px; font-size: 15px; }

/* ---------- Vorschläge unter der Liste ----------
   Deutlich abgesetzt vom eigenen Bestand darüber: was hier steht,
   gehört euch noch nicht. Die Linie oben ist der Trennstrich. */
.vorschlaege {
  padding: 4px 12px calc(20px + var(--safe-b));
  margin-top: 18px; border-top: 1px solid var(--line);
}
/* Meldung über eine Quelle, die nicht antwortet oder den Schlüssel
   ablehnt. Auffällig genug, um nicht übersehen zu werden, aber keine
   rote Fehlermeldung – die Sammlung selbst ist ja in Ordnung. */
.quellen-hinweis {
  margin: 10px 0; padding: 9px 11px; border-radius: 10px;
  font-size: 13px; line-height: 1.45;
  background: color-mix(in srgb, var(--warn) 15%, transparent);
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
}
.quellen-schlecht { color: var(--warn); text-align: right; }

.vorschlag-kopf {
  font-size: 13px; color: var(--text-dim);
  margin: 14px 0 9px; text-transform: uppercase; letter-spacing: .05em;
}

.stars { display: flex; gap: 3px; font-size: 19px; color: var(--line); }
.stars .on { color: var(--star); }

/* ---------- Ziehen zum Aktualisieren ----------
   Liegt oberhalb des sichtbaren Bereichs und wird beim Ziehen
   hereingeschoben. Der Abstand nach oben rechnet die Kerbe mit,
   sonst klebte die Anzeige auf Geräten mit Aussparung darunter. */
/* z-index über der Kopfleiste (die hat 20). Darunter läge die Anzeige
   hinter der undurchsichtigen Leiste und wäre beim Ziehen unsichtbar –
   genau so war es zuerst gebaut und im Bild nicht zu finden.

   Eigener Grund, weil sie über der Leiste schwebt: ohne den stünde die
   Schrift auf dem Suchfeld. */
.zieh-laden {
  position: fixed; left: 50%; top: calc(var(--safe-t) + 6px);
  z-index: 21; pointer-events: none;
  display: flex; align-items: center; gap: 7px;
  padding: 7px 15px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 4px 14px -6px rgba(0,0,0,.45);
  font-size: 13px; color: var(--text-dim); white-space: nowrap;
  opacity: 0; transform: translate(-50%, -46px);
}
/* Während der Geste steuert app.js Weg und Deckkraft Bild für Bild –
   ein Übergang würde da nur hinterherhinken. Beim Loslassen wird er
   per Klasse wieder eingeschaltet. */
.zieh-laden.sanft { transition: transform .22s ease-out, opacity .22s ease-out; }
.zieh-pfeil { display: inline-block; font-size: 15px; transition: transform .18s ease; }
.zieh-laden.bereit .zieh-pfeil { transform: rotate(180deg); }
.zieh-laden.laeuft .zieh-pfeil { animation: zieh-dreht .9s linear infinite; }
@keyframes zieh-dreht { to { transform: rotate(360deg); } }

/* ---------- Hinweis ---------- */
.toast {
  position: fixed; left: 50%; bottom: calc(86px + var(--safe-b));
  transform: translate(-50%, 14px);
  background: var(--text); color: var(--bg);
  padding: 10px 18px; border-radius: 22px; font-size: 14px;
  opacity: 0; pointer-events: none; transition: all .22s; z-index: 60;
  max-width: 88vw; text-align: center;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ---------- Ein kleines Geheimnis ----------------------------
   Was es auslöst, steht in app.js bei herzenRegnen(). Hier nur das
   Aussehen. Über allem (die Meldung unten hat 60) und ohne jede
   Klickfläche: `pointer-events: none` – sonst läge während der paar
   Sekunden eine unsichtbare Scheibe über der ganzen App.

   Bewegt wird ausschließlich per transform und opacity, damit die
   zwei Dutzend Herzen auf dem Telefon nicht ins Stocken geraten. */
.herzen {
  position: fixed; inset: 0; z-index: 70;
  pointer-events: none; overflow: hidden;
}
.herz {
  position: absolute; top: -10vh;
  animation-name: herz-fallen; animation-timing-function: linear;
  animation-fill-mode: forwards; will-change: transform, opacity;
}
@keyframes herz-fallen {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 0; }
  10%  { opacity: .95; }
  80%  { opacity: .95; }
  100% {
    transform: translate3d(var(--drift, 0px), 118vh, 0) rotate(var(--dreh, 20deg));
    opacity: 0;
  }
}

/* Der Spruch: EINE Animation über die ganze Lebensdauer, nicht zwei
   hintereinander. Zwei Animationen auf demselben `transform` fallen
   sich gegenseitig ins Wort – die zweite überschreibt mit ihrem
   Rückwärts-Füllen schon während ihrer Verzögerung die erste. */
.herz-spruch {
  position: fixed; left: 50%; top: 42%; z-index: 71;
  pointer-events: none; white-space: nowrap;
  font-size: 24px; font-weight: 700; color: var(--text);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: 13px 24px;
  box-shadow: 0 12px 34px -10px rgba(0,0,0,.5);
  animation: herz-spruch 3.4s ease-out forwards;
}
@keyframes herz-spruch {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.82); }
  12%  { opacity: 1; transform: translate(-50%, -50%) scale(1.06); }
  22%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  78%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -62%) scale(.96); }
}

@media (min-width: 720px) {
  .list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 8px; }
  .row-item { margin-bottom: 0; }
  main { max-width: 1080px; margin: 0 auto; }
  .sheet-body { max-width: 620px; margin: 0 auto; border-radius: 18px; bottom: 4vh; max-height: 88vh; }
}
