/* ════════════════════════════════════════════════════════════════════════
   components-gear-planner.css — Gear-Ziel-Planer (Stufe 1: Mockup-Schale)
   Layout-Vorbild: .project/mockup/mockup-gear.html (ohne Stats), adaptiert auf
   v1-Tokens. Content: Charakter-Header + Raute-Doll (1/2/3/3/2/1) + „Was du
   brauchst". Sidebar (Charaktere/Klassen-Filter/Guides) reused .sidebar-* +
   .gp-class-*. Nutzt ausschließlich Tokens aus tokens.css; @layer components.
   Klassen gp-* (feature-namespaced, kollisionsfrei).
   ════════════════════════════════════════════════════════════════════════ */
@layer components {
  /* ── Sidebar: Account-/Charakter-Liste (Reuse .sidebar-label/-item/-divider) ──
     Eine Zeile = Auswahl-Button (+ Klassen-Dot/Sub bei Charakteren). Sekundär-Aktionen
     (Umbenennen · Kopieren · Löschen) im Rechtsklick-Kontextmenü, Umbenennen inline. */
  .gp-row {
    display: flex;
    gap: var(--sp-1);
    align-items: center;
  }

  .gp-row-sel {
    display: flex;
    flex: 1;
    gap: var(--sp-2);
    align-items: center;
    min-width: 0;
    padding: var(--sp-2);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-2);
    text-align: left;
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: var(--radius);
    transition: color var(--t-fast), background var(--t-fast);
  }

  .gp-row-sel:hover {
    color: var(--text-1);
    background: var(--bg-card-2);
  }

  .gp-row.active .gp-row-sel {
    color: var(--accent-bright);
    background: var(--accent-glow);
  }

  .gp-row-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Leerzustand einer Liste (keine Konten/Charaktere) — GETEILT (Planer + Simulator, emptyRow). */
  .gp-row-empty {
    margin: 0;
    padding: 7px var(--sp-2);
    color: var(--text-3);
    font-size: var(--fs-sm);
  }

  /* Welt-Emblem der Konto-Zeile (PWI/EVPW) — steht am Icon-Platz, wo die Charakter-Zeile ihr
     Klassen-Emblem trägt, und ist deshalb exakt so groß wie dieses (22px, s. .gp-class-icon).
     `object-fit: contain` hält quadratische wie breitere Logos unverzerrt; die Quellen sind
     verschieden groß (PWI 32px, EVPW 16px) und werden auf dieselbe Kante gebracht. */
  .gp-world-icon {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    object-fit: contain;
  }

  /* Klassenname dezent hinter dem Charakternamen. */
  /* Klassen-Dot vorerst einheitlich Akzent (pro-Klasse-Farben = Politur-Todo §7.10). */
  /* Sidebar-Charliste: einfacher Akzent-Dot (Klasse steht als Sub-Label daneben). */
  /* Sidebar-Konto-Punkt (.gp-acc-dot, geteilt in components-gear.css): aktiver Eintrag → voller Punkt. */
  .gp-row.active .gp-acc-dot {
    opacity: 1;
  }

  /* Klassen-Emblem (statisch: /img/gear-planner/classes-icon/<key>.png). Im Klassen-Picker als Chip,
     im Char-Header groß als Avatar (s. .gp-char-avatar .gp-class-icon). */
  .gp-class-icon {
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    object-fit: contain;
  }

  /* Speicher-Hinweis für Gäste (unter der Account-Liste). */
  .gp-storage-hint {
    margin: var(--sp-2) 0 0;
    padding: 0 var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  /* „Charakter anlegen" gesperrt, solange kein Account aktiv ist. */
  .gp-char-add:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }

  /* ── Anlege-Formulare (Account/Charakter) — Felder im .gp-crt-Overlay über „Was du brauchst" ── */
  .gp-dialog-label {
    display: block;
    margin-bottom: var(--sp-1);
    font-size: var(--fs-sm);
    color: var(--text-2);
  }

  .gp-dialog-input {
    width: 100%;
    margin-bottom: var(--sp-4);
    padding: var(--sp-2) var(--sp-3);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-1);
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-dialog-input:focus {
    border-color: var(--border-accent);
  }

  /* Gesperrte Felder KLAR erkennbar (z. B. Ausbeute/Gold an Zutaten ohne Rezept bzw. kaufbaren):
     ausgegraut + durchgestrichener Cursor — nicht nur der Browser-Default. */
  .gp-dialog-input:disabled {
    color: var(--text-3);
    cursor: not-allowed;
    background: var(--bg-card-2);
    border-style: dashed;
    opacity: 0.55;
  }

  /* Validierung: fehlender Name → roter Rahmen + roter Placeholder-Hinweis IM Feld. */
  .gp-dialog-input.gp-input-error {
    border-color: var(--danger);
  }

  .gp-dialog-input.gp-input-error::placeholder {
    color: var(--danger);
  }

  /* Validierung: fehlende Klassenwahl → Hinweis direkt neben dem „Klasse"-Label. */
  .gp-field-err {
    margin-left: var(--sp-2);
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--danger);
    text-transform: none;
  }

  /* Name + pwdb-ID nebeneinander (Vorbild Calc-Modal .calc-name-row). */
  /* Feld-Zeile: Abstand trägt die ZEILE (nicht jedes Feld einzeln) → eine Höhenlinie, Schalter
     und schmale Felder sitzen mittig statt am unteren Rand (Wildwuchs-Fix 2026-07-09). */
  .gp-dialog-name-row {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-4);
  }

  .gp-dialog-name-row .gp-dialog-input {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 30ch;
    margin-bottom: 0;
  }

  /* evpw-Felder in denselben Zeilen: flex-basis 0 = GLEICHE Spaltenbreiten für Select UND
     Input (bei basis:auto startet ein Select bei seiner Inhaltsbreite und wird schmaler als
     die Text-Felder daneben — User-Rüge v1.237.5). Fixbreiten-Felder (gp-tag-input,
     gp-mat-coins) überschreiben das weiter unten mit flex 0 0 <Breite>. */
  .gp-dialog-name-row .evpw-input,
  .gp-dialog-name-row .evpw-select {
    flex: 1 1 0;
    min-width: 0;
    max-width: 30ch;
  }

  .gp-dialog-name-row .gp-tag-input {
    flex: 0 0 5.5rem;
    width: 5.5rem;
    text-align: center;
  }

  /* Schmale Zahlen-Felder der Kopfzeile (Sockel 0-6, elements.data-ID) — wie gp-tag-input
     fixbreit, damit sie die Text-Felder daneben nicht zusammenstauchen. */
  .gp-dialog-name-row .gp-num-input {
    flex: 0 0 5rem;
    width: 5rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }

  /* Die ID ist Herkunft, nicht Eingabe: sichtbar, aber erkennbar nicht bearbeitbar. */
  .gp-dialog-name-row .gp-num-input[readonly] {
    color: var(--text-3);
    cursor: default;
  }

  /* Formular im „Charakter anlegen"-Overlay: füllt die Rest-Höhe unter dem Kopf (Overlay hat auf dem
     Desktop feste Höhe, bottom:0) → Name/Geschlecht oben fix, Klassen-Liste scrollt, Fußleiste unten fix.
     So ragen „Anlegen/Abbrechen" nie heraus, auch wenn eine Rasse eine 3. Klasse (z. B. Nightshade) bekommt. */
  .gp-crt-form {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
  }

  /* Klassenwahl nach Rasse gruppiert: Stapel aus Rassen-Blöcken (Überschrift + Klassen-Reihe).
     Einziger Scroll-Bereich des Formulars (füllt zwischen Name und Fußleiste). */
  .gp-dialog-classes {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--sp-3);
    min-height: 0;
    margin-bottom: var(--sp-4);
    overflow-y: auto;
    padding-right: var(--sp-2);
  }

  .gp-class-group {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }

  /* Rassen-Überschrift über jeder Klassengruppe (EN-Spieldaten). */
  .gp-class-race {
    padding-bottom: var(--sp-1);
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    border-bottom: 1px solid var(--border);
  }

  /* Klassen einer Rasse: 3 nebeneinander (Panel-Breite gibt das her). */
  .gp-class-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-1);
  }

  .gp-class-pick {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    padding: var(--sp-2);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-2);
    text-align: left;
    cursor: pointer;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: color var(--t-fast), border-color var(--t-fast);
  }

  .gp-class-pick:hover {
    color: var(--text-1);
    border-color: var(--border-accent);
  }

  .gp-class-pick.active {
    color: var(--accent-bright);
    background: var(--accent-glow);
    border-color: var(--accent);
  }

  /* Geschlecht: zwei gleichbreite Buttons (button-orientiert, keine Pillen). */
  /* Namensfeld + Geschlecht (♂/♀) in EINER Zeile. */
  .gp-crt-namerow {
    display: flex;
    gap: var(--sp-2);
    align-items: stretch;
    margin-bottom: var(--sp-4);
  }

  .gp-crt-namerow .gp-dialog-input {
    flex: 1 1 auto;
    min-width: 0;
    margin-bottom: 0;
  }

  .gp-gender-row {
    display: flex;
    flex: 0 0 auto;
    gap: var(--sp-1);
  }

  /* Kompakte ♂/♀-Symbol-Buttons neben dem Namensfeld (quadratisch, füllen die Feldhöhe). */
  .gp-gender-pick {
    display: grid;
    place-items: center;
    width: 2.6rem;
    padding: 0;
    font-family: inherit;
    font-size: var(--fs-lg);
    line-height: 1;
    color: var(--text-2);
    cursor: pointer;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: color var(--t-fast), border-color var(--t-fast);
  }

  .gp-gender-pick:hover {
    color: var(--text-1);
    border-color: var(--border-accent);
  }

  .gp-gender-pick.active {
    color: var(--accent-bright);
    background: var(--accent-glow);
    border-color: var(--accent);
  }

  /* Welt-Pflichtwahl im „Spielaccount anlegen"-Dialog (Retail PWI / Evolved PW). Gleiche Mechanik
     wie ♂/♀ oben (keine Vorauswahl → .active erst nach Klick), aber Text statt Symbol: die Welten
     kommen als Eigennamen aus der Stammtabelle und dürfen beliebig lang/viele sein → umbrechen
     statt quetschen. */
  .gp-world-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin-top: var(--sp-1);
  }

  .gp-world-pick {
    padding: var(--sp-2) var(--sp-3);
    font-family: inherit;
    font-size: var(--fs-sm);
    line-height: 1;
    color: var(--text-2);
    cursor: pointer;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: color var(--t-fast), border-color var(--t-fast);
  }

  .gp-world-pick:hover {
    color: var(--text-1);
    border-color: var(--border-accent);
  }

  .gp-world-pick.active {
    color: var(--accent-bright);
    background: var(--accent-glow);
    border-color: var(--accent);
  }

  /* Dialog-Hinweis, wenn eine Pflicht-Wahl gar nicht angeboten werden kann (Stammdaten nicht
     geladen) — sagen statt einen unabschließbaren Dialog zeigen. */
  .gp-dialog-hint {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  .gp-dialog-foot {
    display: flex;
    flex-shrink: 0;
    gap: var(--sp-2);
    justify-content: flex-end;
  }

  /* Löschen (nur im Bibliotheks-Item-Editor, via onDelete): von Speichern/Abbrechen abgesetzt;
     Look = evpw-btn--danger (§1), hier nur die Platzierung. */
  .gp-item-del-btn {
    margin-left: auto;
  }

  /* ── Charakter-Header ── */
  /* Charakter-Leiste = geteilte Kopfleiste (.gp-headbar in components-gear.css) + nur der Abstand
     zwischen Avatar · Info · Rassen-Emblem. Optik/Höhe/Position kommen aus .gp-headbar (DRY). */
  .gp-char-header {
    gap: var(--sp-5);
  }

  /* Symmetrische Embleme um den Namen: Klasse (Avatar, links) + Rasse (rechts). */
  .gp-char-avatar,
  .gp-char-race {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: var(--gp-emblem);
    height: var(--gp-emblem);
    font-family: var(--font-head);
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--accent-bright);
    background: linear-gradient(135deg, var(--bg-card-2), var(--bg-base));
    border: 2px solid var(--border-accent);
    border-radius: var(--radius);
  }

  /* Klassen-Emblem (Quelle 36×36, randlos) füllt die Box bei 41px.
     display:block entfernt die Inline-Baseline-Lücke → exakt mittig in der Box. */
  .gp-char-avatar .gp-class-icon {
    display: block;
    width: 42px;
    height: 42px;
    object-fit: contain;
  }

  /* Rassen-Emblem: die Quelle (104×108) trägt viel transparenten Innenrand, das sichtbare Emblem füllt nur
     ~68% — Klassen-Icons sind randlos. Damit das SICHTBARE Rassen-Emblem so groß wie das Klassen-Emblem
     (~41px, gemessen) wird, muss das <img> auf 60px (bei 55px blieb das Emblem nur 37px). Zwei Fallstricke,
     beide hier bewusst behandelt:
     1) Die globale Reset-Regel `img { max-width: 100% }` (base.css) klemmt das <img> sonst auf die 52px-
        Rahmenbreite (darum bewirkte eine reine Größenänderung zuvor NICHTS) → `max-width: none`.
     2) Das 60px-<img> ist GRÖSSER als der 52px-Rahmen; ein übergroßes Grid-Item bleibt unter
        `place-items: center` NICHT zentriert (ragt nach rechts/unten) → hier absolut + translate zentrieren,
        das hält den transparenten Überstand symmetrisch. Icon-Datei bleibt unberührt. */
  .gp-char-race {
    position: relative;
  }

  .gp-char-race .gp-race-icon {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 60px;
    height: 60px;
    max-width: none;
    object-fit: contain;
    transform: translate(-50%, -50%);
  }

  .gp-char-info {
    flex: 1;
    min-width: 0;
  }

  .gp-char-name {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    font-family: var(--font-head);
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-1);
  }

  .gp-char-sub {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    font-size: var(--fs-xs);
    color: var(--text-2);
  }

  /* Klassen-Label (Klartext) unter dem Namen — nicht jeder kennt die Embleme. */
  .gp-char-tag {
    padding: 2px var(--sp-2);
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--jade);
    background: var(--jade-dim);
    border: 1px solid rgb(77 217 192 / 20%);
    border-radius: var(--radius-sm);
  }

  /* Set-Badges = selbst gespeicherte Loadouts (Klick lädt das Set). Die Leiste FÜLLT die Zeile
     (flex:1), damit der Rechtsklick „auf der gesamten Zeile" die „Set erstellen"-Aktion trifft —
     nicht nur auf dem schmalen „Sets:"-Label (das „+" entfiel; Erstellen liegt im Zeilen-Kontextmenü). */
  .gp-char-sets {
    display: flex;
    flex: 1 1 auto;
    gap: var(--sp-1);
    align-items: center;
    min-height: 22px;
  }

  .gp-char-sets-label {
    color: var(--text-3);
  }

  /* „Teilen" (Plan-Share): rechtsbündig in der Unterzeile; Button in Tab-/Sekundär-Optik,
     der erzeugte Link erscheint daneben als schmales readonly-Feld zum Kopieren. */
  .gp-share {
    display: inline-flex;
    gap: var(--sp-2);
    align-items: center;
    margin-inline-start: auto;
  }

  /* [hidden] muss das inline-flex überstimmen (sonst bleibt „Teilen" sichtbar, obwohl nichts zu teilen). */
  .gp-share[hidden] {
    display: none;
  }

  .gp-share-btn {
    padding: var(--sp-1) var(--sp-3);
    font-family: var(--font-body); /* nicht die Cinzel-Überschrift der Namenszeile erben */
    font-size: var(--fs-xs);
    font-weight: 400;
    color: var(--text-2);
    cursor: pointer;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-share-btn:hover {
    color: var(--text-1);
    border-color: var(--accent-bright);
  }

  .gp-share-link {
    width: min(46ch, 40vw);
    padding: var(--sp-1) var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-2);
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-share-link[hidden] {
    display: none;
  }

  /* ── Read-only geteilte Plan-Ansicht (?share=<id>) ──────────────────────────
     Bearbeitungs-UI aus, aber die Sidebar bleibt als LEERE Struktur stehen (nur ihr Inhalt
     ausgeblendet): so kippt das Layout beim Laden NICHT auf Vollbreite/weiß — links die dunkle
     (leere) Sidebar, im Hauptbereich der Lade-Platzhalter wie gewohnt. Scoped auf html.gp-share-view. */
  html.gp-share-view .sidebar > * {
    display: none;
  }

  /* Modus-Umschalter (Hamburger), Set-/Teilen-Aktionen, generischer Intro-Hero raus. */
  html.gp-share-view .nav-toggle,
  html.gp-share-view .gp-char-sets,
  html.gp-share-view .gp-share,
  html.gp-share-view .gp-view-planer .hero {
    display: none;
  }

  /* Nicht gefunden: Charakter-Header + Grid weg, nur das Fehler-Banner bleibt. */
  html.gp-share-notfound .gp-char-header,
  html.gp-share-notfound .gp-grid {
    display: none;
  }

  .gp-share-banner {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: center;
    margin-bottom: var(--sp-5);
    padding: var(--sp-3) var(--sp-5);
    font-size: var(--fs-sm);
    color: var(--text-1);
    background: var(--accent-glow);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-lg);
  }

  .gp-share-banner--err {
    color: var(--danger);
    background: var(--danger-dim);
    border-color: var(--danger);
  }

  /* Bedarfs-Platzhalter der geteilten Ansicht: das Bedarf-Panel ist ab dem ersten Paint sichtbar
     (Snapshot server-eingebettet, kein data-hydrate mehr) — dieser Text füllt es, bis renderNeed die
     berechneten Zeilen einsetzt (ersetzt ihn via replaceChildren). Kein leeres Fach beim Laden. */
  .gp-need-loading {
    padding: var(--sp-5);
    color: var(--text-3);
    font-size: var(--fs-sm);
  }

  /* „In mein Konto übernehmen" — Primäraktion rechts im Banner (Vorbild XivGear-Import). */
  .gp-share-import {
    margin-inline-start: auto;
    padding: var(--sp-1) var(--sp-4);
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--bg-base);
    cursor: pointer;
    background: var(--accent-bright);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius);
  }

  .gp-share-import:hover {
    background: var(--accent);
  }

  .gp-share-hint {
    font-size: var(--fs-xs);
    color: var(--danger);
  }

  .gp-share-hint[hidden] {
    display: none;
  }

  /* „Verwerfen" (merge-on-login #4): Sekundäraktion neben „Übernehmen" — schlicht, gerahmt. */
  .gp-merge-drop {
    padding: var(--sp-1) var(--sp-4);
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    color: var(--text-2);
    cursor: pointer;
    background: none;
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-merge-drop:hover {
    color: var(--text-1);
    border-color: var(--text-3);
  }

  .gp-set-badge {
    padding: 1px var(--sp-2);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text-2);
    cursor: pointer;
    background: none;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
  }

  .gp-set-badge:hover {
    color: var(--text-1);
    border-color: var(--text-3);
  }

  /* Aktives Set: Gold hervorgehoben → man sieht immer, an welchem Set man gerade arbeitet (der grüne
     Dirty-Punkt sitzt per Definition an genau diesem). Nicht-aktive Sets bleiben dezent. */
  .gp-set.active .gp-set-load {
    color: var(--accent-bright);
    background: var(--accent-glow);
    border-color: var(--border-accent);
  }

  /* Inline-Löschbestätigung: der Name-Button wird zum roten „Set löschen?"-Knopf (platzneutral in der
     engen Chip-Leiste; ein Klick löscht, Esc/Blur bricht ab). Vorbild GitHub „Confirm delete". */
  .gp-set-confirm-del {
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border-color: color-mix(in srgb, var(--danger) 50%, transparent);
  }

  .gp-set-confirm-del:hover {
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 22%, transparent);
    border-color: var(--danger);
  }

  /* Sichtbarer „+ Set erstellen"-Trigger (Vorbild Browser-Tabs/Notion): dezenter gestrichelter Ghost-Knopf
     am Ende der Leiste. Leerzustand trägt den vollen Text, sonst nur „+" (--compact = quadratisch). */
  .gp-set-create {
    padding: 1px var(--sp-2);
    font-family: inherit;
    font-size: var(--fs-xs);
    color: var(--text-3);
    cursor: pointer;
    background: none;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    transition: color var(--t-fast), border-color var(--t-fast);
  }

  .gp-set-create--compact {
    min-width: 22px;
    padding-inline: 0;
    text-align: center;
  }

  .gp-set-create:hover:not(:disabled) {
    color: var(--accent-bright);
    border-color: var(--border-accent);
  }

  .gp-set-create:disabled {
    cursor: not-allowed;
    opacity: 0.45;
  }

  /* Set-Chip: nur Name (laden, = .gp-set-badge) + „|". Aktionen (Umbenennen/Überspeichern/Duplizieren/
     Löschen) liegen im Rechtsklick-Kontextmenü; „Set erstellen" im Zeilen-Menü. */
  .gp-set {
    display: inline-flex;
    gap: var(--sp-1);
    align-items: center;
  }

  /* Dirty (geladenes Set geändert): dezenter grüner Punkt am Namen — ersetzt den früheren ✓-Button
     (Überspeichern liegt jetzt im Kontextmenü). */
  .gp-set.is-dirty .gp-set-load::after {
    margin-inline-start: var(--sp-1);
    color: var(--success);
    content: "●";
  }

  /* Inline-Umbenennen: schmales Eingabefeld an Stelle des Namens (Enter/Blur speichern, Esc verwirft). */
  .gp-set-rename {
    width: 10ch;
    padding: 1px var(--sp-2);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--text-1);
    background: var(--bg-base);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-sm);
  }

  .gp-set-rename:focus-visible {
    outline: none;
  }

  /* Inline-Umbenennen der Sidebar-Zeilen (Konto/Charakter): Eingabefeld an Stelle des Namens,
     füllt die Zeilenbreite (vertikale Liste). Enter/Blur speichern, Esc verwirft. */
  .gp-row-renaming {
    cursor: default;
  }

  .gp-row-rename {
    flex: 1;
    min-width: 0;
    padding: 1px var(--sp-2);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-1);
    background: var(--bg-base);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-sm);
  }

  .gp-row-rename:focus-visible {
    outline: none;
  }

  /* Inline-Löschbestätigung der Sidebar-Zeilen (Konto/Charakter): ersetzt das confirmModal —
     dieselbe state-getriebene Mechanik wie das Umbenennen. Vorbild: Datei-Explorer. Die Zeile
     wird zum vertikalen Block (Frage · optionale Warnung · Löschen|Abbrechen). */
  .gp-row-confirming {
    cursor: default;
  }

  .gp-row-confirm {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--sp-1);
    min-width: 0;
    padding: var(--sp-2);
    background: var(--bg-card-2);
    border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent);
    border-radius: var(--radius);
  }

  .gp-row-confirm-msg {
    font-size: var(--fs-sm);
    color: var(--text-1);
  }

  .gp-row-confirm-warn {
    font-size: var(--fs-xs);
    color: var(--danger);
  }

  .gp-row-confirm-btns {
    display: flex;
    gap: var(--sp-1);
  }

  .gp-row-confirm-ok,
  .gp-row-confirm-cancel {
    flex: 1;
    padding: var(--sp-1) var(--sp-2);
    font-family: inherit;
    font-size: var(--fs-sm);
    cursor: pointer;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    transition: color var(--t-fast), background var(--t-fast), border-color var(--t-fast);
  }

  /* Löschen = danger (gleiches Muster wie das Kontextmenü). */
  .gp-row-confirm-ok {
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
    border-color: color-mix(in srgb, var(--danger) 50%, transparent);
  }

  .gp-row-confirm-ok:hover {
    background: color-mix(in srgb, var(--danger) 22%, transparent);
    border-color: var(--danger);
  }

  .gp-row-confirm-cancel {
    color: var(--text-2);
    background: none;
  }

  .gp-row-confirm-cancel:hover {
    color: var(--text-1);
    background: var(--bg-base);
  }

  .gp-set-sep {
    color: var(--text-3);
    user-select: none;
  }

  /* ── Haupt-Grid: Ausrüstung (links) + Was du brauchst (rechts) ── */
  .gp-grid {
    /* Feste Höhe der Hauptfläche (--gp-area-h) ist jetzt geteilt (components-gear.css, .page-gear) →
       Planer, Inventar und Bibliothek fluchten. Hier nur noch geerbt; Ausrüstung, „Was du brauchst"
       und die Anlege-Overlays sind ALLE gleich hoch (kein Laufzeit-Messen → kein FOUC). Greift ≥821px. */

    /* EINE Item-Icon-Größe für den ganzen Planer: Ausrüstungs-Slots, „Nach Item"-Kopf und alle Bedarf-
       Material-Zeilen (Zusammenfassung/Nach Item/Baum) teilen sie. Hier auf .gp-grid → beide Panels erben. */
    --gp-ic: 34px; /* Box = 32px Bild + 1px Rahmen rundum */
    --gp-ic-img: 32px; /* native Icon-Größe (pwdb-icons sind 32×32) → pixelscharf, nicht skaliert */

    /* Slot-Kantenlänge (quadratisch) — eine Wahrheit für Doll-Slots UND die „Nach Item"-Card-Höhe. Eine Card
       ist genau 2 Slot-Reihen hoch (2 Slots + 1 Reihen-Gap) → 3 Card-Reihen füllen die 6-reihige Doll. */
    --gp-slot-size: 84px;
    --gp-card-h: calc(var(--gp-slot-size) * 2 + var(--sp-3));

    /* Höhe einer Material-Zeile im „Nach Item"-Fach so, dass GENAU 3 Zeilen den Card-Rest füllen (kein
       Anschnitt der 4.): Card-Höhe minus Kopf (Icon) minus Kopf-Abstand minus Card-Padding minus Rahmen,
       geteilt durch 3. Das Fach ist dann fix 3 × diese Höhe. */
    --gp-mat-row-h: calc(
      (var(--gp-card-h) - var(--gp-ic) - var(--sp-1) - var(--sp-2) * 2 - 2px) / 3
    );

    display: grid;
    grid-template-columns: minmax(460px, 540px) 1fr;
    gap: var(--sp-6);
    align-items: start;
  }

  .gp-panel {
    padding: var(--sp-5);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
  }

  .gp-panel-h {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: var(--sp-4);
  }

  .gp-panel-h b {
    font-family: var(--font-head);
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-1);
  }

  /* Nur der direkte Hinweis-Span im Kopf (z. B. gp_equip_hint) — NICHT verschachtelte Spans wie die
     Gesamtkosten (.gp-agg-total-lbl/-val), die sonst grau + fs-xs überschrieben würden. */
  .gp-panel-h > span {
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  /* Ausrüstungs-Panel = Flex-Spalte: Kopf oben, die Doll vertikal MITTIG im (durch die feste Referenz-
     höhe = Overlay-Höhe) höheren Bereich — statt oben zu kleben. margin-block:auto zentriert die Doll. */
  .gp-equip-panel {
    display: flex;
    flex-direction: column;
  }

  .gp-equip-panel .gp-doll {
    margin-block: auto;
  }

  /* ── Inventory-Doll: zwei Slot-Spalten + Klassen-Figur in der Mitte (Vorbild PW-„Inventory") ── */
  .gp-doll {
    display: grid;
    grid-template-columns: auto minmax(120px, 1fr) auto;
    gap: var(--sp-3);
    align-items: stretch;
    justify-content: center;
  }

  .gp-doll-col {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  /* Mittlere Figur = menschliche Silhouette (SVG, per data-gender umgeschaltet). */
  .gp-doll-fig {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background:
      radial-gradient(ellipse 60% 45% at 50% 92%, rgb(0 0 0 / 55%), transparent 70%),
      radial-gradient(ellipse 55% 60% at 50% 40%, var(--accent-glow), transparent 70%),
      linear-gradient(180deg, rgb(90 110 150 / 8%), rgb(20 26 44 / 25%));
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  /* SVG-Silhouette: füllt die Höhe der Doll-Mitte; dezente Steel-Füllung (Ghost-Figur). */
  .gp-fig {
    max-width: 100%;
    height: 100%;
    padding: var(--sp-2) 0;
    fill: rgb(165 185 220 / 30%);
  }

  /* Nur die zum data-gender passende Silhouette zeigen (Standard m). */
  .gp-doll-fig[data-gender="f"] .gp-fig--m,
  .gp-doll-fig[data-gender="m"] .gp-fig--f {
    display: none;
  }

  .gp-slot {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    align-items: center;
    justify-content: center;
    width: var(--gp-slot-size);
    aspect-ratio: 1;
    padding: var(--sp-2);
    background: linear-gradient(180deg, var(--bg-card-2), var(--bg-base));
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--t-fast), transform var(--t-fast);
  }

  .gp-slot:hover {
    border-color: var(--border-accent);
    transform: translateY(-2px);
  }

  .gp-slot.is-selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
  }

  .gp-slot-ic {
    display: grid;
    place-items: center;
    width: var(--gp-ic);
    height: var(--gp-ic);
    font-size: var(--fs-xs);
    font-weight: 700;
    cursor: pointer;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }

  /* Katalog-Item-Icon im Slot (sonst steht hier das Stufen-Kürzel als Text). */
  .gp-slot-ic img {
    display: block;
    width: var(--gp-ic-img);
    height: var(--gp-ic-img);
    object-fit: contain;
  }

  .gp-ic-legend {
    color: var(--pw-q3);
    background: rgb(255 96 0 / 12%);
    border-color: rgb(255 96 0 / 40%);
  }

  .gp-ic-epic {
    color: var(--pw-q2);
    background: rgb(170 50 255 / 12%);
    border-color: rgb(170 50 255 / 40%);
  }

  .gp-ic-empty {
    color: var(--text-3);
    background: var(--bg-base);
    border-style: dashed;
  }

  /* Ersatz für ein Icon, das nicht laden konnte — ↻ zum erneuten Anfordern.
     Füllt die jeweilige Icon-Box (Slot/Picker/Bedarfszeile/Chip) und zentriert das Glyph. */
  .gp-ic-reload {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    padding: 0;
    font-size: var(--fs-sm);
    line-height: 1;
    color: var(--text-3);
    cursor: pointer;
    background: transparent;
    border: 0;

    &:hover {
      color: var(--accent-bright);
    }
  }

  /* Initialen-Platzhalter (item-icon.js makeInitials): ersetzt ein nicht ladbares Icon, wenn
     kein sinnvoller Reload möglich ist (Gast/erschöpfter Neuversuch — z. B. Snapshot zeigt auf
     geprunte Datei). Füllt die Icon-Box wie das ↻, ist aber bewusst NICHT interaktiv. */
  .gp-ic-initials {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-size: var(--fs-xs);
    line-height: 1;
    color: var(--text-3);
    user-select: none;
  }

  .gp-slot-name {
    font-size: var(--fs-xs);
    line-height: 1.2;
    color: var(--text-3);
    text-align: center;
  }

  /* Stufen-Badge oben rechts am Slot. */
  .gp-slot-tier {
    position: absolute;
    top: var(--sp-1);
    right: var(--sp-1);
    padding: 0 var(--sp-1);
    font-size: var(--fs-xs);
    font-weight: 700;
    color: #000;
    background: var(--accent);
    border-radius: var(--radius-sm);
  }

  .gp-slot-tier.is-r9 {
    color: #fff;
    background: var(--violet);
  }

  /* Icon-Wrapper = Hover-Anker fürs X (sonst flackert das X beim Rübergehen). */
  .gp-slot-icwrap {
    position: relative;
    display: grid;
    place-items: center;
  }

  /* ── Slot-Picker: Overlay über „Was du brauchst" (planner.js) ── */
  /* Flex-Spalte: Kopf + Fach (füllt) + Foot. Die Panel-Höhe wird nebeneinander per JS an die Ausrüstung
     gekoppelt (syncNeedHeight) → beide Panels gleich hoch, das Fach (.gp-need-scroll) scrollt in sich. */
  .gp-need-panel {
    position: relative;
    display: flex;
    flex-direction: column;
  }

  /* Picker offen → das ganze „Was du brauchst"-Panel ausblenden (Box + Header +
     Bedarfszeilen), nur der Picker bleibt sichtbar (analog zur Progression-Info-
     Card). visibility:hidden am Panel erhält die Höhe als Positionierungsbezug
     fürs absolute Overlay; der Picker wird per visible wieder eingeblendet. */
  .gp-need-panel.gp-picking {
    visibility: hidden;
  }

  .gp-need-panel.gp-picking > .gp-picker {
    visibility: visible;
  }

  /* Overlay: Panel-Breite (oben/links/rechts gepinnt), Höhe = Inhalt
     (bottom auto → wächst nach unten, kein Scroll; max ~10 Items/Slot). */
  .gp-picker {
    position: absolute;
    inset: 0 0 auto;
    z-index: 5;
    display: flex;
    flex-direction: column;
    padding: var(--sp-5);
    background: var(--bg-card);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-lg);
  }

  /* „Anlegen"-Fenster (Account/Charakter): Overlay über „Was du brauchst" (needPanel) — dieselbe Fläche
     und Mechanik wie der Slot-Picker (visibility-Trick erhält die Höhe als Positionierungsbezug); die
     Ausrüstungs-Doll bleibt sichtbar. Deckt das Panel (inset:0), wächst bei Bedarf nach unten. */
  .gp-need-panel.gp-crt-open {
    visibility: hidden;
  }

  .gp-need-panel.gp-crt-open > .gp-crt {
    visibility: visible;
  }

  .gp-crt {
    position: absolute;
    inset: 0 0 auto;
    z-index: 6;
    display: flex;
    flex-direction: column;
    padding: var(--sp-5);
    background: var(--bg-card);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-lg);
  }


  .gp-picker-head {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-4);
  }

  .gp-picker-head b {
    font-family: var(--font-head);
    font-size: var(--fs-md);
    color: var(--text-1);
  }

  /* Box sauber zentrieren; das ×-Glyph sitzt in der Schrift tiefer als die
     Großbuchstaben (Mathe-Achse) → minimal anheben, damit es auf „Helm"-Höhe liegt
     (Box ohne sichtbaren Rand → nur das Glyph wandert). */
  .gp-picker-close {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 0;
    font-size: var(--fs-xl);
    line-height: 1;
    color: var(--text-3);
    cursor: pointer;
    background: none;
    border: 0;
    transform: translateY(-0.06em);
  }

  .gp-picker-close:hover {
    color: var(--text-1);
  }

  /* Suchfeld oben im Picker (filtert die Item-Liste live nach Name) — volle Breite unter dem Kopf. */
  .gp-picker-search {
    flex-shrink: 0;
    width: 100%;
    margin-bottom: var(--sp-3);
    padding: var(--sp-2) var(--sp-3);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-1);
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-picker-search:focus-visible {
    border-color: var(--accent-bright);
    outline: none;
  }

  /* Füllt die Rest-Höhe unter dem Kopf und scrollt DARIN (wie .gp-need-scroll) — greift, wenn der Picker
     feste Höhe hat (Desktop-Overlay); ohne feste Höhe (schmal, wächst) bleibt flex/overflow wirkungslos. */
  .gp-picker-list {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: var(--sp-1);
    min-height: 0;
    overflow-y: auto;
    padding-right: var(--sp-2);
  }

  /* Item-Karte (trägt den Rahmen): Icon + Name. Nicht klickbar — nur das Icon ist es (Auswahl). */
  .gp-pick {
    display: flex;
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-2) var(--sp-3);
    font-family: inherit;
    text-align: left;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  /* Icon = klickbare Auswahl (Button). */
  .gp-pick-ic {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 38px;
    height: 38px;
    padding: 0;
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--accent-bright);
    cursor: pointer;
    background: var(--accent-glow);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius-sm);
    transition: border-color var(--t-fast), background var(--t-fast);
  }

  .gp-pick-ic:hover {
    background: var(--bg-card-2);
    border-color: var(--accent-bright);
  }

  /* Katalog-Item-Icon im Picker (sonst steht hier das Stufen-Kürzel als Text). */
  .gp-pick-ic img {
    display: block;
    width: 32px;
    height: 32px;
    object-fit: contain;
  }

  .gp-pick-n {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: var(--fs-sm);
    color: var(--text-1);
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* „Set"-Knopf in der Name-Box (§31: ganzes Set anlegen) — Matrix-Zelle `evpw-btn--sm`, hier nur
     das Layout: nicht schrumpfen/wachsen, der Name (flex 1 1 auto) weicht per Ellipsis. */
  .gp-pick .evpw-btn {
    flex: 0 0 auto;
  }

  /* Inline-Bestätigung unter der Picker-Zeile („… bleibt unverändert. Rest anlegen?"). Kein Modal
     (DESIGN-SYSTEM §3: Inline hat Vorrang; der Picker ist selbst ein Overlay) und NICHT rot wie
     `.gp-row-confirm` — hier wird nichts gelöscht, nur ergänzt. */
  .gp-pick-confirm {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    padding: var(--sp-2);
    background: var(--bg-card-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-pick-confirm-btns {
    display: flex;
    gap: var(--sp-1);
  }

  .gp-pick-confirm-btns .evpw-btn {
    flex: 1;
  }

  /* Stufe („Titel") als EIGENE Box (eigener Rahmen wie die Name-Box), linksbündig.
     Zweite Grid-Spur (Breite regelt .gp-pick-row). Spieldaten → bleibt EN. */
  .gp-pick-tier {
    display: flex;
    align-items: center;
    min-width: 0;
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--text-1);
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  /* Stufen-Text: kürzt mit … statt die feste Spalte zu sprengen (wie .gp-pick-n). */
  .gp-pick-tier-n {
    overflow: hidden;
    min-width: 0;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Fehlt das Kürzel (Kürzel-Box wird nur bei item.grade gerendert → Stufe ist dann letztes
     Element der Zeile), füllt die Stufe-Box Spur 2+3 bis zum rechten Rand — kein leerer
     Kürzel-Streifen. Mit Kürzel bleibt sie in Spur 2. */
  .gp-pick-tier:last-child {
    grid-column: 2 / -1;
  }

  /* Zeile = festes 3-Spalten-Raster: Name | Stufe | Kürzel. Grid statt Flex, damit die Spalten
     über ALLE Zeilen fluchten und ihre Breite behalten — auch wenn die Kürzel-Zelle fehlt (nur
     bei item.grade gerendert) oder der Text variiert. Kürzel-Spur fest; Name/Stufe teilen den
     Rest im Verhältnis 11:9 (früher flex 55:45). min-width:0 auf den fr-Zellen (s. u.) hält die
     Spuren, statt langen Inhalt die Breite aufblähen zu lassen. stretch → Boxen gleich hoch. */
  .gp-pick-row {
    display: grid;
    grid-template-columns: 11fr 9fr 5.5rem;
    gap: var(--sp-1);
    align-items: stretch;
  }

  /* [hidden] muss das grid überstimmen (Live-Filter der Suche blendet Nicht-Treffer aus). */
  .gp-pick-row[hidden] {
    display: none;
  }

  /* Name-Box: erste Grid-Spur; min-width:0 erlaubt der inneren Ellipsis (.gp-pick-n) zu greifen. */
  .gp-pick-row .gp-pick {
    min-width: 0;
  }

  /* Kürzel (TT90 …): dritte, feste Grid-Spur rechts — sitzt auf dem Platz des früheren
     Rezepte-Toggles (Craft-Wege wählt seit v1.239.0 der Baum). min-width:0 + overflow:hidden
     clippen überlangen Freitext, statt die Spur zu sprengen. Spieldaten → bleibt EN. */
  .gp-pick-grade {
    display: flex;
    align-items: center;
    min-width: 0;
    overflow: hidden;
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text-1);
    white-space: nowrap;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  /* Item-Editor-Formular (geteiltes Modul buildItemEditor): im Planer nicht mehr genutzt (Picker ist
     Auswahl-only), aber die Bibliothek („Items") rendert es. Kind-Styles teilt es mit dem Charakter-
     Dialog (.gp-dialog-*). */
  .gp-add-form {
    margin: var(--sp-1) 0 var(--sp-2);
    padding: var(--sp-4);
    background: var(--bg-base);
    border: 1px solid var(--border-accent);
    border-radius: var(--radius);
  }

  /* Material-Zeilen in der Add-Form: Name (flex) + Menge + Coins (schmal) + ×. */
  .gp-mat-rows {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
  }

  .gp-mat-row {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
  }

  /* Eine Zeile: Material-Auswahl (flex, gibt bei Platzmangel nach) + Menge (schmal) + ×. Kein Umbruch. */
  .gp-mat-line {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
  }

  /* Zutaten-Zeile trägt evpw-Primitive (§1/§7: evpw-select/-input --sm + evpw-btn — EINE
     Control-Höhe, keine gp-Buttons/-Pillen mehr); hier leben nur noch die BREITEN im Zeilen-Flex.
     Fee-Schalter = evpw-btn + aria-pressed (An-Schalter), ×/↳ = evpw-btn--square. */
  .gp-mat-select {
    flex: 1 1 0;
    min-width: 0;
  }

  .gp-mat-qty,
  .gp-mat-coins {
    flex: 0 0 4.5rem;
    width: 4.5rem;
    text-align: center;
  }

  /* „+ Zutat" = evpw-btn (§1); hier nur der Abstand unter der Zeilen-Liste. */
  .gp-mat-add {
    margin-bottom: var(--sp-4);
  }

  /* Ladefehler eingeloggt: der leere Planer ist dann NICHT die Wahrheit — deutlicher Hinweis oben in
     der Sidebar (Speichern ist gleichzeitig gesperrt, s. planner.js save-Wächter). */
  .gp-load-error {
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--danger);
    border: 1px solid var(--danger);
    border-radius: var(--radius-sm);
  }

  /* Rezept-Reiter (Craft-Wege) im Item-Editor = evpw-tabs/-tab (§10, aria-selected); die
     Aktions-Knöpfe am Leisten-Ende (+ Rezept/+ aus Vorlage…) sind evpw-btn--ghost.
     Hier lebt nur noch die Leisten-Ausrichtung (Reiter und 28-px-Knöpfe auf einer Linie). */
  .gp-recipe-tabs {
    align-items: center;
    margin-bottom: var(--sp-2);
  }

  /* „+ aus Vorlage…"-Combobox (unter den Reitern, volle Breite): Suchfeld, darunter die
     Trefferliste als evpw-menu-Overlay (scrollbar) — Select mit Suche. */
  .gp-recipe-tpl {
    position: relative;
    margin-bottom: var(--sp-2);
  }

  .gp-recipe-tpl[hidden] {
    display: none;
  }

  .gp-recipe-tpl .evpw-input {
    width: 100%;
  }

  .gp-recipe-tpl-list {
    inset-inline: 0;
    top: calc(100% + 2px);

    /* Alle Rezepte in der Liste, Höhe deckelt das Scrollen (Vorbild Select2: max-height 200px):
       ≈5 Zeilen sichtbar, die angeschnittene sechste signalisiert „hier scrollt's" — der frühere
       5-Treffer-Deckel machte Einträge ohne Tippen unerreichbar. */
    max-block-size: 12rem;
    overflow-y: auto;
  }

  .gp-recipe-tpl-more {
    padding: var(--sp-1) var(--sp-3);
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  /* Geteiltes-Rezept-Zeile: „gilt für N Items" (Hover = Namen) + Abspalten-Knopf. */
  .gp-recipe-shared {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  .gp-recipe-shared[hidden] {
    display: none;
  }

  .gp-recipe-ctl {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-2);
  }

  .gp-recipe-ctl[hidden] {
    display: none;
  }

  /* Verwaltungs-Zeile trägt evpw-Primitive (Felder evpw-input--sm, Standard/Abspalten/Löschen =
     evpw-btn-Matrix, Standard als An-Schalter via aria-pressed) — hier nur noch die Breiten. */
  .gp-recipe-name {
    flex: 1 1 0;
    min-width: 0;
  }

  /* Ausbeute des aktiven Wegs: feste schmale Box zwischen Name und Standard. */
  .gp-recipe-yield {
    flex: 0 0 7rem;
    width: 7rem;
    text-align: center;
  }

  /* Icon-Upload (Fallback §7.15 #2): ein Icon, bei Rüstungs-Slots automatisch m/f (zwei Slots). */
  .gp-icon-slots {
    display: flex;
    gap: var(--sp-4);
    justify-content: center;
    margin-bottom: var(--sp-4);
  }

  .gp-icon-slot {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
  }

  /* Vorschau in Default-Icon-Größe (wie Slot/Picker: 38px-Box, 32px-Bild). */
  .gp-icon-prev {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    overflow: hidden;
    font-size: var(--fs-sm);
    color: var(--text-3);
    background: var(--bg-card-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
  }

  .gp-icon-prev img {
    display: block;
    width: 32px;
    height: 32px;
    object-fit: contain;
  }

  .gp-icon-up {
    padding: var(--sp-2) var(--sp-3);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-2);
    cursor: pointer;
    background: none;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    transition: color var(--t-fast), border-color var(--t-fast);
  }

  .gp-icon-up:hover {
    color: var(--text-1);
    border-color: var(--border-accent);
  }

  /* ── „Was du brauchst" — Leerzustand (Fach ist eine Flex-Spalte, s. u.) ── */
  .gp-need-empty {
    padding: var(--sp-4) var(--sp-1);
    font-size: var(--fs-sm);
    color: var(--text-3);
  }

  /* Baum-Tab ohne gewählten Slot: zentrierter Hinweis, der zur Slot-Auswahl in der Ausrüstung führt.
     (Füllt die feste is-tree-Grid-Fläche.) */
  .gp-tree-empty {
    display: grid;
    place-items: center;
    padding: var(--sp-4);
    font-size: var(--fs-sm);
    color: var(--text-3);
    text-align: center;
  }

  @media (width <= 820px) {
    .gp-grid {
      grid-template-columns: 1fr;
    }

    /* Gestapelt (Doll oben, Bedarf unten): keine Kopplung an die Doll-Höhe (JS setzt height zurück) →
       das Fach auf einen Viewport-Anteil deckeln, damit es weiter in sich scrollt statt endlos zu wachsen. */
    .gp-need-scroll {
      max-height: 60vh;
    }
  }

  /* Schmal: Figur weg, beide Slot-Spalten zentriert nebeneinander. */
  @media (width <= 520px) {
    .gp-doll {
      grid-template-columns: auto auto;
    }

    .gp-doll-fig {
      display: none;
    }
  }

  /* ══════════ Layout-Prototypen (planner-1 / planner-2) ══════════
     Nutzen dieselben .gp-slot[data-slot]-Elemente (Engine unverändert), nur andere Anordnung. */

  /* planner-1: Body-Map / Kreuz — positionelles 3-Spalten-Raster ohne Figur.
     Leere Rasterplätze (.gp-cross-empty) brauchen kein CSS: sie halten die Kreuzform. */
  .gp-doll--cross {
    display: grid;
    grid-template-columns: repeat(3, var(--gp-slot-size));
    gap: var(--sp-3);
    justify-content: center;
  }

  /* Kreuz: das schmale Kreuz braucht keine Paper-Doll-Breite → linke Spalte an den Inhalt schrumpfen,
     „Was du brauchst" bekommt den Rest. Nur breit; darunter greift der 820px-Stapel-Umbruch. */
  @media (width >= 821px) {
    .gp-page--cross {
      grid-template-columns: 320px minmax(0, 1fr);
    }

    /* Alle drei gleich hoch (feste --gp-area-h): „Was du brauchst" bekommt eine feste height → das Fach
       (.gp-need-scroll) scrollt DARIN (einzige Scroll-Stelle); die Ausrüstung min-height → die Doll
       zentriert sich, wird nie geklappt; die Overlays füllen die Höhe (bottom:0). Der Item-Picker scrollt
       intern (.gp-picker-list) — die Add-Form sprengt sonst die Höhe; die Anlege-Overlays passen ohne Scroll. */
    .gp-need-panel {
      height: var(--gp-area-h);
    }

    /* Bedarf-Fach exakt 3 Card-Reihen hoch → nach der 3. „Nach Item"-Reihe ist Schluss (kein Anschnitt der
       4. Reihe); mehr scrollt. 3 Reihen = 3 × Card-Höhe + 2 × Reihen-Gap (deckt sich mit der 6-reihigen Doll). */
    .gp-need-scroll {
      max-height: calc(var(--gp-card-h) * 3 + var(--sp-3) * 2);
    }

    .gp-equip-panel {
      min-height: var(--gp-area-h);
    }

    .gp-crt--fill,
    .gp-picker {
      bottom: 0;
    }
  }

  /* Bedarfs-Fach = Flex-Spalte: das scrollende Fach (füllt) + darunter die angepinnten Gesamtkosten.
     flex:1 füllt die feste Panel-Höhe (--gp-area-h) zwischen Kopf und Foot; die Kinder werden voll gedehnt
     (align-items default stretch) → Zusammenfassung/Karten nutzen die volle Breite. */
  .gp-need-list {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
  }

  /* Umschalter Zusammenfassung ↔ Nach Item (nie beides zugleich) — sitzt rechts im Panel-Header. */
  /* Rechter Cluster im Bedarfs-Kopf: „Kopieren"-Button + Tab-Leiste nebeneinander. */
  .gp-need-tools {
    display: inline-flex;
    flex: 0 0 auto;
    gap: var(--sp-2);
    align-items: center;
  }

  /* „Bedarfsliste kopieren" — schlichter Sekundär-Button in Tab-Optik (fs-xs, gerahmt). */
  .gp-need-copy {
    padding: var(--sp-1) var(--sp-3);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--text-2);
    cursor: pointer;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-need-copy:hover {
    color: var(--text-1);
    border-color: var(--accent-bright);
  }

  .gp-need-copy[hidden] {
    display: none;
  }

  .gp-need-tabs {
    display: inline-flex;
    flex: 0 0 auto;
    gap: 2px;
    padding: 3px;
    background: var(--bg-base);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-need-tab {
    padding: var(--sp-1) var(--sp-3);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--text-2);
    cursor: pointer;
    background: none;
    border: 0;
    border-radius: var(--radius-sm);
  }

  .gp-need-tab:hover {
    color: var(--text-1);
  }

  .gp-need-tab.is-on {
    color: var(--bg-base);
    background: var(--accent-bright);
    font-weight: 600;
  }

  /* Gedeckeltes Fach: scrollt in sich, statt die Seite aufzuschieben. Höhe = Rest der Panel-Höhe. */
  .gp-need-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding-right: var(--sp-2);
  }

  /* ── „Set-Boni"-Ansicht (4. Tab): je angelegtem Set eine Karte, Kopf + Staffelung.
     ⚠ HIER KEINE FARBE: Set-Ton und erfüllt/offen kommen aus der PW-Palette (.gp-pw-*, via
     applyPwTone) — der Server liefert den Schlüssel, tokens.css den Wert. */
  .gp-suites {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
  }

  .gp-suite-card {
    padding: var(--sp-3);
    background: var(--bg-card-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-suite-h {
    display: flex;
    gap: var(--sp-2);
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: var(--sp-2);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--border);
  }

  .gp-suite-n {
    font-weight: 600;
  }

  .gp-suite-count {
    flex: 0 0 auto;
    font-size: var(--fs-xs);
    color: var(--text-3);
  }

  /* Geteiltes Grid: Stufen-Präfix | Bonus-Text — fluchtet über alle Zeilen der Karte. */
  .gp-suite-tiers {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--sp-1) var(--sp-2);
    font-size: var(--fs-sm);
  }

  .gp-suite-row {
    display: contents;
  }

  /* Baum-Tab: das Fach scrollt NICHT selbst — die Slot-Liste steht fix, nur die Baum-Spalte scrollt. */
  .gp-need-scroll.is-tree {
    display: grid;
    padding-right: 0;
    overflow: hidden;
  }

  /* Wrapper, wenn das Teil mehrere Craft-Wege hat: Umschalter-Zeile fix oben, darunter scrollt der Baum. */
  .gp-tree-wrap {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }

  .gp-tree-recipes {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin-bottom: var(--sp-2);
  }

  /* Dezente, dünne Scrollbar für ALLE scrollenden Planer-Fächer — ein Ort statt pro Fach. Ruhige Farbe
     (--border, nicht der Gold-Akzent). Gecko: „thin" ist die dünnste Stufe (nicht dünner machbar);
     Chromium/Vivaldi bekommt via ::-webkit die schmalere 6px-Bahn. */
  .gp-need-scroll,
  .gp-picker-list,
  .gp-bd-mats,
  .gp-tree-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
  }

  .gp-need-scroll::-webkit-scrollbar,
  .gp-picker-list::-webkit-scrollbar,
  .gp-bd-mats::-webkit-scrollbar,
  .gp-tree-scroll::-webkit-scrollbar {
    width: 6px;
  }

  .gp-need-scroll::-webkit-scrollbar-thumb,
  .gp-picker-list::-webkit-scrollbar-thumb,
  .gp-bd-mats::-webkit-scrollbar-thumb,
  .gp-tree-scroll::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 3px;
  }

  .gp-agg {
    display: grid;
    grid-template-columns: minmax(0, 1fr); /* Spalte auf volle Breite dehnen, sonst kollabiert der Name auf 0 */
    gap: 2px;
  }

  /* Gruppen als Spalten → nutzt die Panel-Breite, Höhe = längste Gruppe (statt Summe aller Gruppen). */
  .gp-agg--cols {
    grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
    gap: var(--sp-4) var(--sp-5);
    align-items: start;
  }

  /* Herkunfts-Spalte = Mini-Tabelle mit GETEILTEN Spalten (Icon · Menge · Kosten), damit alles über
     alle Zeilen fluchtet; die Zeilen legen sich per Subgrid in diese Spalten (bleiben aber Boxen für
     Hover/„gedeckt"). Mit Kopplung eine zusätzliche Bestand-Spalte davor. */
  .gp-agg-col {
    display: grid;
    grid-template-columns: 34px 1fr auto;
    gap: 2px var(--sp-3);
    align-content: start;
  }

  .gp-agg-col--stock {
    grid-template-columns: 34px auto 1fr auto;
  }

  .gp-agg--cols .gp-agg-group-h {
    grid-column: 1 / -1;
    margin-top: 0;
  }

  /* „Nach Item"-Aufschlüsselung — je belegtem Slot eine Karte (Item-Kopf + Materialien als Name × Menge),
     im Spaltenraster (gp-agg--cols). 1:1 wie das Mockup gear-planner-was-du-brauchst-laenge. */
  .gp-bd {
    display: grid;
    gap: var(--sp-2);
  }

  .gp-bd.gp-agg--cols {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-3) var(--sp-4);
    align-items: start;
  }

  /* Feste Card-Höhe = 2 Slot-Reihen (--gp-card-h), unabhängig vom Inhalt → 3 Card-Reihen fluchten mit der
     6-reihigen Doll (Standard 2×3). Flex-Spalte: Kopf fix oben, das Mats-Fach füllt den Rest und scrollt. */
  .gp-bd-item {
    display: flex;
    flex-direction: column;
    height: var(--gp-card-h);
    padding: var(--sp-2) var(--sp-3);
    overflow: hidden; /* Sicherheitsnetz gegen Sub-Pixel-Überlauf → nie ein Anschnitt der 4. Zeile */
    background: var(--bg-card-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }

  .gp-bd-h {
    display: flex;
    flex-shrink: 0;
    gap: var(--sp-2);
    align-items: center;
    margin-bottom: var(--sp-1);
    font-weight: 600;
  }

  /* Kopf 1-zeilig halten → konstante Card-Höhe (langer Name wird abgeschnitten, voll im Tooltip). */
  .gp-bd-nm {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .gp-bd-ic {
    display: grid;
    place-items: center;
    width: var(--gp-ic);
    height: var(--gp-ic);
    font-size: var(--fs-xs);
    font-weight: 700;
    background: var(--bg-base);
    border-radius: var(--radius-sm);
  }

  .gp-bd-ic img {
    width: var(--gp-ic-img);
    height: var(--gp-ic-img);
    object-fit: contain;
  }

  /* Mats-Fach fix genau 3 Material-Zeilen hoch (kein Anschnitt der 4.); mehr scrollt. */
  /* Material-Fach: 3 Zeilen sichtbar, Rest scrollt IN der Karte — die Karte selbst hat FESTE
     Höhe (--gp-card-h, fluchtet mit der Doll) + overflow:hidden. ⚠ Diesen Innen-Scroll NICHT
     entfernen (v1.233.5→.7 gelernt): ohne ihn wird der Rest abgeschnitten und ist unerreichbar. */
  .gp-bd-mats {
    height: calc(var(--gp-mat-row-h) * 3);
    padding-right: var(--sp-2); /* Abstand Menge ↔ Scrollbar (klebte sonst) */
    overflow-y: auto;
  }

  /* Jede Baum-Zeile sitzt in einem Knoten (.gp-bd-node = Zeile + optionale Kinder-Box) — der Wrapper macht
     :first-/:last-child der Winkel-Konnektoren (s. .gp-bd-kids) sauber. */
  .gp-bd-node {
    position: relative;
  }

  /* Feste Zeilenhöhe überall (Nach-Item-Fach UND Baum-Tab): 3 Zeilen füllen das Nach-Item-Fach exakt,
     und die Winkel-Konnektoren treffen die Icon-Mitte (= halbe Zeilenhöhe). Namen sind einzeilig (Ellipsis). */
  .gp-bd-mat {
    display: grid;
    grid-template-columns: var(--gp-ic) minmax(0, 1fr) auto; /* Icon · Name · Menge */
    gap: var(--sp-2);
    align-items: center;
    height: var(--gp-mat-row-h);
    font-size: var(--fs-sm);
    color: var(--text-2);
  }

  /* Eigenes Item-Icon je Material-Zeile — einheitliche Planer-Größe (--gp-ic).
     Fallback (kein Icon) = Initialen als Text. */
  .gp-bd-mat-ic {
    display: grid;
    place-items: center;
    width: var(--gp-ic);
    height: var(--gp-ic);
    overflow: hidden;
    font-size: var(--fs-xs);
    font-weight: 700;
  }

  .gp-bd-mat-ic img {
    width: var(--gp-ic-img);
    height: var(--gp-ic-img);
    object-fit: contain;
  }

  .gp-bd-mat-nm {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .gp-bd-mat b {
    color: var(--text-1);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }


  /* Sub-Materialien eingerückt (eine Icon-Breite je Ebene). Die Verbindung zeichnet jeder Kind-Knoten selbst
     als Winkel (├─ / └─) ZURÜCK auf die Eltern-Icon-Achse — Vorbild klassischer Dateibaum (tree). */
  .gp-bd-kids {
    padding-left: var(--gp-ic);
  }

  /* Weg-Umschalter am Baum-Knoten (nur Baum-Tab, Knoten mit mehreren Rezepten): kompakte
     An-Schalter-Gruppe unter der Zeile, auf die Namens-Spalte eingerückt (Icon + Grid-Lücke). */
  .gp-bd-ways {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1);
    margin: 0 0 var(--sp-1) calc(var(--gp-ic) + var(--sp-2));
  }

  /* Winkel-Führungslinie „mit Abstand" (konsistent, KEINE Verbindung ins Eltern-Icon — die läge unter dem Icon
     und müsste hindurchlaufen). Senkrechte Achse gp-ic/2 links der Kind-Icons; die Querstriche (::after) knicken
     zu jedem Icon ab. Alle Segmente zwischen zwei Abzweigen sind gleich lang (eine Zeilenhöhe). Standard =
     durchgehend nach unten zum Nachbarn. */
  .gp-bd-kids > .gp-bd-node::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--gp-ic) / -2);
    border-left: 1px solid var(--text-3); /* neutrale, klar sichtbare Führungslinie (–border ist zu schwach) */
  }

  /* Erstes von MEHREREN Kindern: Senkrechte startet an der eigenen Icon-Mitte (kein Stummel nach oben →
     gleiche Segmentlängen) und läuft nach unten weiter zum Nachbarn. NICHT bei einem EINZELNEN Kind — das
     braucht die Senkrechte von oben (sonst hinge nur der Querstrich am Icon) → :not(:last-child). */
  .gp-bd-kids > .gp-bd-node:first-child:not(:last-child)::before {
    top: calc(var(--gp-mat-row-h) / 2);
  }

  /* Letztes ODER einziges Kind: Senkrechte von oben bis zur eigenen Icon-Mitte → └. */
  .gp-bd-kids > .gp-bd-node:last-child::before {
    bottom: auto;
    height: calc(var(--gp-mat-row-h) / 2);
  }

  /* Waagerechte von der Senkrechten Richtung Kind-Icon, auf Icon-Mitte — endet mit kleinem Abstand VOR dem
     Icon (klebt sonst direkt an der Kante). */
  .gp-bd-kids > .gp-bd-node::after {
    content: '';
    position: absolute;
    top: calc(var(--gp-mat-row-h) / 2);
    left: calc(var(--gp-ic) / -2);
    width: calc(var(--gp-ic) / 2 - var(--sp-1));
    border-top: 1px solid var(--text-3); /* gleiche Führungslinien-Farbe wie die Senkrechte */
  }

  /* ── „Baum"-Tab: NUR der Craft-Baum des gewählten Teils, über die volle Breite (scrollt eigenständig).
     Die frühere Item-Card-Spalte links entfällt — der Slot wird direkt in der Ausrüstung gewählt.
     Baum selbst = die gp-bd-*-Zeilen (via bdNode). Im gp-tree-wrap (Rezept-Umschalter darüber)
     nimmt die Baum-Spalte den Rest der Höhe (flex). */
  .gp-tree-scroll {
    flex: 1 1 auto;
    min-height: 0;
    padding-right: var(--sp-2);
    overflow-y: auto;
  }


  .gp-agg-group-h {
    margin-top: var(--sp-3);
    margin-bottom: var(--sp-1);
    padding-bottom: var(--sp-1);
    font-size: var(--fs-xs);
    color: var(--text-3);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--border);
  }

  /* Erste Gruppe (vertikal) bzw. Kopf jeder Spalte (wide) ohne oberen Abstand. */
  .gp-agg-group-h:first-child {
    margin-top: 0;
  }

  /* Name nur als Tooltip auf dem Icon (kein Text mehr) → 3 Spalten: Icon links, Menge + Coins rechts. */
  .gp-agg-row {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    align-items: center;
    padding-block: var(--sp-2);
    border-radius: var(--radius-sm);
  }

  .gp-agg-row:hover {
    background: var(--bg-card-2);
  }

  .gp-agg-ic {
    display: grid;
    place-items: center;
    width: var(--gp-ic);
    height: var(--gp-ic);
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text-3);
    cursor: help;
    border-radius: var(--radius-sm);
  }

  .gp-agg-ic img {
    width: var(--gp-ic-img);
    height: var(--gp-ic-img);
    object-fit: contain;
  }

  /* Mengen-Spalte (geteiltes Subgrid): rechtsbündig, fluchtet über alle Zeilen. */
  .gp-agg-q {
    font-size: var(--fs-base);
    color: var(--text-2);
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  /* Bestand-Spalte: mittig; Inhalt = Karton (Hover „wer hat wie viel") + optional die gedeckte Menge
     als Details-Link. Größen an die Zeile angepasst (Karton wie Nachbar-Icons, Zahl wie die Mengen-
     Zelle). Leere Zelle, wenn nichts gedeckt (Spalte fluchtet trotzdem). */
  .gp-agg-stock {
    display: inline-flex;
    gap: 0.35em;
    align-items: center;
    justify-self: start;
  }

  /* Karton kodiert die Besitz-Achse: Basis = ausgemalt (Item selbst im Bestand), Char-Hover. Feste
     Slot-Breite → Karton UND Zahl fluchten über alle Zeilen. Das „erledigt" (abgedunkelt) kommt aus
     der Zeilen-Dämpfung (.gp-agg-row--covered) — zusammen die 4 Zustände. */
  .gp-agg-stock-box {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    width: 18px;
    color: var(--text-2);
    cursor: help;
  }

  /* Nur über besessene Produkte gedeckt (kein eigener Bestand) → Umriss (box statt boxFull), kein Hover. */
  .gp-agg-stock-box.is-via {
    color: var(--text-3);
    cursor: default;
  }

  .gp-agg-stock-box svg {
    width: 18px;
    height: 18px;
  }

  /* Gedeckte Menge = LINK → Details-Ansicht. Gold, gestrichelt unterstrichen wie die übrigen
     Text-Links; Zahl in Zeilengröße (--fs-base wie gp-agg-q). */
  .gp-agg-stock-link {
    display: inline-flex;
    align-items: center;
    padding: 0;
    color: var(--accent);
    font-size: var(--fs-base);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    background: none;
    border: 0;
    text-decoration: underline dashed;
    text-underline-offset: 2px;
  }

  .gp-agg-stock-link:hover {
    color: var(--accent-bright);
    text-decoration-style: solid;
  }

  /* Details-Ansicht (Klick auf die Bestand-Kiste): volle, benannte Aufschlüsselung im scrollenden Fach.
     Bedarf − vorhanden − je Zwischenprodukt (namentlich) = offen. */
  .gp-details {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    padding: var(--sp-1) var(--sp-1) var(--sp-2);
  }

  .gp-details-h {
    display: flex;
    gap: var(--sp-3);
    align-items: baseline;
  }

  .gp-details-back {
    padding: 0;
    color: var(--accent);
    font-size: var(--fs-sm);
    cursor: pointer;
    background: none;
    border: 0;
  }

  .gp-details-back:hover {
    color: var(--accent-bright);
  }

  /* Herleitungs-Baum (gp-ct): Wurzel = Material (Icon + Name + Bedarf), darunter die eingerückten
     Deckungsquellen mit echten Item-Icons + Weg. Vorbild: unser Baum-Tab / FFXIV-Teamcraft-Bäume. */
  .gp-ct {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    max-inline-size: 30rem;
  }

  .gp-ct-root {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    font-weight: 600;
  }

  .gp-ct-need {
    margin-inline-start: auto;
    color: var(--text-3);
    font-size: var(--fs-sm);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
  }

  /* Eingerückte Kinder (nur Einrückung, keine Verbindungslinie). */
  .gp-ct-kids {
    display: flex;
    flex-direction: column;
    gap: var(--sp-1);
    margin-inline-start: 10px;
    padding-inline-start: var(--sp-3);
  }

  .gp-ct-node {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 0 var(--sp-2);
    align-items: center;
  }

  /* Alle Item-Icons GLEICH groß, native Planer-Größe (--gp-ic/--gp-ic-img wie gp-bd-mat-ic /
     gp-agg-ic): pwdb-Icons sind 32×32 → pixelscharf, nicht skaliert. Bild wie SVG wie Initialen. */
  .gp-ct-ic {
    display: grid;
    place-items: center;
    width: var(--gp-ic);
    height: var(--gp-ic);
    overflow: hidden;
    color: var(--text-3);
    font-size: var(--fs-xs);
    font-weight: 700;
  }

  .gp-ct-ic svg,
  .gp-ct-ic img {
    width: var(--gp-ic-img);
    height: var(--gp-ic-img);
    object-fit: contain;
  }

  /* Name + „(×N)" gehören zusammen (eine Zelle), (×N) dezent direkt hinter dem Namen. */
  .gp-ct-nmwrap {
    display: inline-flex;
    gap: 0.35em;
    align-items: baseline;
  }

  .gp-ct-nm {
    color: var(--text-1);
  }

  /* „(×N)" hinter dem Item-Namen (gesparte Crafts) — gleiche Größe wie die Werte rechts (.gp-ct-val,
     erbt die Basis-Größe), NICHT verkleinert. */
  .gp-ct-mult {
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
  }

  .gp-ct-val {
    justify-self: end;
    color: var(--text-1);
    font-variant-numeric: tabular-nums;
  }

  /* Der „Weg" unter dem Knoten: „je Vorgang: ⟨Zutat-Icons⟩" — ALLE Zutaten des Zwischenprodukts.
     Startet unter dem Namen (nicht unterm Icon); umbricht bei vielen Zutaten (z. B. 7 Warssouls). */
  .gp-ct-way {
    display: flex;
    grid-column: 2 / -1;
    flex-wrap: wrap;
    gap: 0.3em 0.45em;
    align-items: center;
    color: var(--text-3);
    /* Größe NICHT verkleinern → Weg-Zeilen-Zahlen gleich groß wie die Werte rechts. */
    font-variant-numeric: tabular-nums;
  }

  /* Eine Zutat-Chip in der Weg-Zeile: optionale „N×"-Menge direkt vor dem Icon. */
  /* Gap Icon↔„je N" wie der Weg-Zeilen-Gap (0.45em) — sonst klebt der Text am Icon. */
  .gp-ct-ing {
    display: inline-flex;
    gap: 0.45em;
    align-items: center;
  }

  /* Zwischenprodukt-Name in Gold (die Antwort auf „woher"). */
  .gp-ct-via > .gp-ct-nm {
    color: var(--accent);
  }

  /* „vorhanden": Herkunft je Charakter im Hover → gepunktet unterstrichen als Affordanz. */
  .gp-ct-have .gp-ct-nm {
    cursor: help;
    text-decoration: underline dotted;
    text-underline-offset: 2px;
  }

  /* Tiefe Kette: der Zwischenprodukt-Name wird ein Drill-Link (öffnet seinen eigenen Baum). */
  .gp-ct-drill {
    padding: 0;
    color: var(--accent);
    font: inherit;
    text-align: start;
    cursor: pointer;
    background: none;
    border: 0;
    text-decoration: underline dashed;
    text-underline-offset: 2px;
  }

  .gp-ct-drill:hover {
    color: var(--accent-bright);
    text-decoration-style: solid;
  }

  /* Summenzeile „offen" abgesetzt. */
  .gp-ct-open {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    margin-top: var(--sp-1);
    padding-top: var(--sp-2);
    font-weight: 600;
    border-top: 1px solid var(--border);
  }

  .gp-ct-open .gp-ct-val {
    margin-inline-start: auto;
  }

  /* Kosten = Münz-Icon (Zahl im Tooltip). Kein Preis → leere Zelle. Ruhig (text-3), Gold beim Hovern. */
  .gp-agg-coins {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    color: var(--text-3);
    cursor: help;
  }

  .gp-agg-coins svg {
    width: 16px;
    height: 16px;
  }

  .gp-agg-coins:hover {
    color: var(--accent-bright);
  }

  /* „Bestand einrechnen"-Schalter im Panel-Kopf (nur Summary-Tab). Kompakt, links der Tab-Leiste. */
  .gp-need-stock {
    display: inline-flex;
    gap: var(--sp-1);
    align-items: center;
    font-size: var(--fs-xs);
    color: var(--text-2);
    cursor: pointer;
    user-select: none;
  }

  /* [hidden] muss das inline-flex überstimmen (sonst bleibt der Schalter in Nicht-Summary-Tabs sichtbar). */
  .gp-need-stock[hidden] {
    display: none;
  }

  /* Voll gedeckte Zeile (offen 0): gedämpft (Vorbild Teamcraft-Strike, hier ruhiger) — dunkelt auch
     den Karton mit ab (die „abgedunkelten" Karton-Zustände #3/#4). */
  .gp-agg-row--covered {
    opacity: 0.55;
  }

  /* „Gesamtkosten" sitzt im Panel-Kopf (mittig zwischen Titel und Tab-Leiste) — Label + Coin-Zahl gleich groß. */
  /* Statische Fußleiste unter dem scrollenden Fach (wie die Speichern/Abbrechen-Leiste der
     Overlays): Gesamtkosten links, Shop-Alternative rechts; scrollt NICHT mit. */
  .gp-need-foot {
    display: flex;
    flex-shrink: 0;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    align-items: baseline;
    justify-content: space-between;
    padding-top: var(--sp-3);
    margin-top: var(--sp-2);
    border-top: 1px solid var(--border);
  }

  .gp-agg-total {
    display: flex;
    align-items: baseline;
    gap: var(--sp-2);
  }

  .gp-agg-total-lbl {
    font-size: var(--fs-sm);
    color: var(--text-2);
  }

  /* Wert-Größe = Panel-Fließtext (fs-base) — fs-md wirkte in der Fußleiste zu wuchtig (User). */
  .gp-agg-total-val {
    font-family: var(--font-head);
    font-size: var(--fs-base);
    color: var(--accent-bright);
    font-variant-numeric: tabular-nums;
  }

  /* Shop-Alternative (Cubi-Gold C / Event-Gold EG): Zusatz-Info, bewusst dezenter als die
     Coins-Gesamtsumme — sie ist NICHT verrechnet, nur „ginge auch im Shop". */
  .gp-agg-total-alt {
    font-size: var(--fs-sm);
    color: var(--text-2);
  }

  /* ── Item-Info-Hover (Spiel-Tooltip, gear/item-tooltip.js; Panel = evpw-tooltip--item §12).
     Zeilenfarben tragen SPIEL-Semantik (wie name_color aus der DB, kein UI-Akzent): Add-Blau =
     zufällige Item-Adds (PW-Palette item_color1), grau = Meta (Subtype/Amount), Gold = Flavor-
     Text (Leitfarbe; im Spiel ebenfalls goldgelb). Der Name färbt via CSSOM (name_color). ── */
  .gp-tt-name {
    font-weight: 700;
    color: var(--text-1);
    margin-block-end: var(--sp-1);
  }

  .gp-tt-line {
    color: var(--text-1);
  }

  /* Leerzeile = Gruppentrenner im Overlay (das Spiel trennt nur so, keine Linien). */
  .gp-tt-gap {
    block-size: 9px;
  }

  /* Fallback-Zeilen für Items OHNE Katalog-Tooltip (hand-gepflegt): unsere Theme-Farben,
     nicht die PW-Palette — sie geben keine Spiel-Daten wieder. */
  .gp-tt-sub {
    color: var(--text-2);
  }

  .gp-tt-desc {
    color: var(--accent);
  }

}
