/* ═══════════════════════════════════════════════════════════════
   SETZZ 3.0 · Stylesheet
   Three-zone layout: left rail (48px) + left panel (220px) +
   board (flex) + right panel (240px, on-click only).
   Design language: near-black surfaces, single amber signal color,
   hairline borders, 0px radius, three type roles:
     display  → Rajdhani
     label    → JetBrains Mono
     data     → JetBrains Mono
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* NEU (2026-08-19, "acht Korrekturen", Punkt 5), GEPRÜFT UND
     EINGESCHRÄNKT (2026-08-19, Folgerunde): sagt dem Browser, dass die
     Seite ein dunkles Farbschema erwartet — der einzige Hebel, den CSS
     für BROWSER-EIGENE, nicht stylebare Elemente hat.

     WIRKT NICHT auf das Datalist-Popup, das der Auslöser für diese
     Regel war (Source/Tool, AI Model im Detail-Panel, `<input list>` +
     `<datalist>`) — SimplifyerX hat auf Staging in beiden Themes
     geprüft: die Liste bleibt schwarz mit weißer Schrift, kein
     Unterschied. Chromium rendert Datalist-Vorschläge über denselben
     internen Autofill-Popup-Mechanismus wie Adress-/Formular-
     Autovervollständigung, NICHT über den regulären Formular-Rendering-
     Pfad, den `color-scheme` sonst steuert — bestätigt live: der Wert
     selbst steht korrekt am `:root` und wechselt mit dem Theme
     (getComputedStyle(document.documentElement).colorScheme, gemessen
     "dark" bzw. "light"), Chrome (hier Chromium 148) unterstützt die
     Eigenschaft grundsätzlich seit Version 81 — die Regel selbst ist
     also nicht falsch, sie hat nur keinen Empfänger für DIESEN Fall.
     Für das Datalist-Popup gibt es keine CSS-Lösung, siehe Backlog
     ("Auswahllisten … nicht gestaltbar") — nur ein eigenes Popover kann
     es farblich anpassen.

     BLEIBT TROTZDEM: zwei Scrollbereiche im Stylesheet haben KEINE
     eigene scrollbar-color/-width-Regel (.pdf-canvas-host bei
     gezoomtem PDF, .list-table-wrap bei breiten List-Karten-Tabellen)
     — alle anderen ~44 Scrollbereiche sind bereits einzeln
     durchgestylt und von dieser Regel unberührt. Für diese zwei
     bewirkt color-scheme, dass der native Browser-Scrollbalken dem
     Theme folgt statt einem harten Vorgabewert — nicht sichtbar per
     Screenshot bestätigt (native Scrollbalken-Bemalung ist außerhalb
     des per CSSOM Messbaren), aber die Kaskade dafür ist eindeutig
     (keine überschreibende Regel vorhanden, Chromium-Standardverhalten
     seit Version 89 folgt color-scheme für unbehandelte Scrollbalken). */
  color-scheme: dark;
  /* surfaces */
  --bg:        #292929;   /* app canvas */
  /* GEÄNDERT (2026-08-04, Nutzer-Vorgabe): war #060607. WICHTIG: dies ist
     die GLOBALE Variable, nicht nur der Wert für .floating-panel aus
     dieser Runde — sie wird bereits an vielen weiteren, vorbestehenden
     Stellen im Dark Theme verwendet (u.a. Medien-Weiten, Selects,
     World-Kacheln ohne Cover, Preset-Icon-Hintergründe). Die Änderung
     wirkt dort überall mit, nicht nur bei den schwebenden Panels. */
  --bg-deep:   #232323;   /* media wells */
  --panel:     #363636;   /* panel body */
  --panel-2:   #17171b;   /* raised chips, list rows */
  --line:      #494949;   /* hairline border */
  --line-soft: #1c1c21;
  --asset-outline: #3a3a3a;   /* fine outline around uploaded assets */
  /* NEU (2026-08-13, Nutzer-Vorgabe/SimplifyerX): eigene Tokens statt
     --panel/--panel-2 wiederzuverwenden — --panel-2 haengt an ~40
     anderen Stellen (Hover-Flaechen app-weit), eine Wertaenderung dort
     haette weit ueber die Asset-Zeilen hinausgewirkt. Normale Zeile
     jetzt deutlicher heller als --bg (1.52:1 statt vorher 1.20:1
     zwischen --bg und --panel) — sonst verschwimmt die Liste zur
     Flaeche. Ausgewaehlte Zeile deutlich dunkler als die normale Zeile
     (1.85:1). Enthaelt keine Akzentfarbe. */
  --asset-row-bg:          #454545;
  --asset-row-selected-bg: #181818;
  /* NEU (2026-08-15, Nutzer-Vorgabe/SimplifyerX, Light-Theme-Grundflaechen-
     Runde): eigener Token fuer Eingabefelder (.rp-input/.rp-textarea/
     .rp-select), vorher teilten sie sich var(--bg) mit der App-Grundflaeche
     — im Light Theme praktisch ununterscheidbar (siehe dort). Dark bleibt
     bewusst UNVERAENDERT: derselbe Wert wie --bg, das heutige Verhalten
     (Feld = Grundflaeche) war im Dark Theme nie das gemeldete Problem. */
  --field-bg: var(--bg);
  /* NEU (2026-08-15, Feldfarben-Runde): eigener Hover-Ton fuer Felder,
     Dark bewusst UNVERAENDERT — Sprung --field-bg(=--bg) -> --panel ist
     hier schon moderat (#292929 -> #363636), kein gemeldetes Problem. */
  --field-hover-bg: var(--panel);

  /* ink */
  --ink-hi:    #ececec;   /* NEU (2026-08-12, Nutzer-Vorgabe): Hierarchie-Stufe
     ÜBER --ink — "hier bist du gerade" (aktive Auswahl, primäre Überschrift
     im Fokus, aktiver Zustand). Kein Ersatz für --amber: Amber bleibt die
     Signalfarbe für Interaktion/Akzent, --ink-hi ist reine Hierarchie. Nicht
     vermischen. */
  --ink:       #c9c9c9;   /* primary text */
  --ink-dim:   #9a9aa2;   /* secondary */
  --ink-faint: #999999;   /* tertiary / placeholders */

  /* signals */
  --amber:     #C8A23F;
  --amber-hi:  #ddbb5f;
  --amber-dim: rgba(200,162,63,.14);
  --green:     #46d160;
  --red:       #ff5c4d;

  /* elevation — panels and sidebar icons share one shadow so a theme
     only has to define it once */
  --panel-shadow: 6px 6px 6px rgba(0,0,0,0.1);
  --tile-shadow:  3px 6px 6px rgba(0,0,0,0.25);
  --overlay-rgb:  6, 6, 7;      /* rgb triplet behind media-control chips, popovers, view-label gradient */

  /* NEU (2026-08-13, Tooltip-Umbau, SimplifyerX-Vorgabe): eigene Tokens
     statt Literalen in .setzz-tooltip, damit eine spaetere Aufspaltung
     nach Theme keine Regel-Suche braucht, nur diese zwei Zeilen (hier
     und im [data-theme="light"]-Block unten). Heute BEWUSST in beiden
     Themes identisch — keine vergessene Anpassung.
     GEAENDERT (2026-08-13, zweite Runde): --tooltip-fg #cccccc → #ffffff
     — 2.21:1 war schlechter als die diesen Morgen als "kaum lesbar"
     korrigierten Feldbeschriftungen (2.37:1). #ffffff ist der hellste
     ueberhaupt moegliche Wert und bleibt bei 3.54:1 unter AA (4.5:1) —
     das ist gegen #888888 nicht erreichbar, siehe Kontrast-Kommentar
     bei .setzz-tooltip. */
  --tooltip-bg: #888888;
  --tooltip-fg: #ffffff;

  /* type
     GEÄNDERT (2026-08-10): --f-display Sofia Sans Condensed → Rajdhani,
     --f-data Space Mono → JetBrains Mono. Ausgeliefert werden nur
     Rajdhani 500/600 (siehe fonts.css für die Gewichts-Begründung).

     --f-data UND --f-label zeigen jetzt auf dieselbe Familie. Die
     Variablen bleiben trotzdem GETRENNT, und das ist der Punkt: sie
     unterschieden bis hierher an zwei sichtbaren Stellen zwei Dinge —
     im Detail-Panel Feldname (.rp-label, data) gegen Feldwert
     (.rp-input, label), und im Zoom-HUD den Wert (.zoom-hud-pct, data)
     gegen die Knöpfe (.zoom-hud-btn, label). Diese Unterscheidung
     verschwindet optisch, bleibt aber strukturell erhalten: ein
     einziger geänderter Wert hier holt sie zurück, ohne 190 Fundstellen
     anzufassen. */
  --f-display: "Rajdhani", sans-serif;
  --f-label:   "JetBrains Mono", monospace;
  --f-data:    "JetBrains Mono", monospace;

  /* ── Button-Groessen-Tokens ────────────────────────────────────
     NEU (2026-08-12, Sitzung A / Etappe 1 des Button-System-Umbaus).

     WARUM ES DIESE TOKENS GIBT — das ist der eigentliche Punkt:
     Am 2026-07-18 wurde ein Button-Styleguide festgelegt (32/24px,
     Icon ~55%, Radius 0) und in drei Commits umgesetzt (fbd4dd6,
     d93423b, 89abae0). ZWEI TAGE SPAETER war er gebrochen:
     .row-ctrl-btn ging am 20.07. in einem Sammel-Commit (69a73b0,
     "diverse CSS-Anpassungen") kommentarlos von 32px auf 46px.

     Die Ursache war nicht Unachtsamkeit, sondern die Bauform: der
     Styleguide von 2026-07-18 hat SIEBEN EINZELNE KLASSEN je einzeln
     auf dieselben Werte gesetzt. Keine Basisklasse, kein Token. "32px"
     stand danach 33x woertlich im CSS und sah aus wie jede andere Zahl.
     Wer eine davon aendert, aendert eine Zeile — nichts sticht heraus.

     Farben laufen in SETzz seit Monaten NICHT auseinander. Der Grund
     ist genau dieser: sie stehen als Tokens einmal da. Groessen hatten
     bis heute kein einziges Token (nachgemessen 2026-08-12: 49 CSS-
     Variablen definiert, davon 0 fuer Groessen) — und sind auf 23
     Kantenlaengen und 15 Icon-Groessen auseinandergelaufen.

     Ab hier gilt: eine Abweichung ist "width: 46px" mitten unter lauter
     var(--btn-size). Sie taucht im Diff auf, statt sich zu tarnen.

     ETAPPE 1 (dieser Stand): eingefuehrt, von NIEMANDEM genutzt.
     Der Fingerabdruck (docs/baselines/buttons.json) muss unveraendert
     bleiben — das ist der Beweis vor der Umstellung. */
  --btn-size:     32px;   /* Werkzeug/Icon-Button, Standard */
  --btn-size-sm:  24px;   /* Werkzeug/Icon-Button, kompakt (enge Menuezeilen) */
  --btn-icon:     18px;   /* Icon im Standard-Button (~55%) */
  --btn-icon-sm:  14px;   /* Icon im kompakten Button (~58%) */
  /* GEÄNDERT (2026-08-13, Nutzer-Vorgabe/SimplifyerX): --btn-pad war EINE
     Groesse fuer alle Textknoepfe. Jetzt drei Stufen. Der bisherige Wert
     ist unveraendert die mittlere Stufe — dadurch aendert sich an den
     Dialog-Knoepfen (Save/Cancel) nichts, nur small/large kommen dazu.
     Small ersetzt die bisher HART CODIERTEN 9px/3px-9px/6px-12px im
     Assets-Manager (die sich damit jeder Messung entzogen), large traegt
     die Wichtigkeit des SAVE-Knopfes ueber die Flaeche statt ueber eine
     eigene dritte Variante. */
  --btn-pad-sm:   4px 10px;
  --btn-pad-md:   8px 16px;   /* = frueher --btn-pad, unveraendert */
  --btn-pad-lg:   12px 22px;
  /* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, vierte Runde): 9/10/13 → 10/12/14.
     Vorher am gerenderten Knopf gemessen: die Hoehe waechst dadurch um
     +1.45px (sm/lg) bzw. +2.9px (md) — RESET/SAVE im BOARD-SETTINGS-Panel
     gehen von 24.7/34px auf ~26.1/37px, beide Stufen gemeinsam, der
     Abstand zueinander bleibt erhalten (siehe Bestandsaufnahme). */
  --btn-fs-sm:    10px;
  --btn-fs-md:    12px;
  --btn-fs-lg:    14px;
  --btn-gap:      6px;    /* Abstand zwischen benachbarten Buttons */
  /* NEU (2026-08-12, Etappe 4): Hauptaktion ist bewusst NICHT aus
     --ink/--bg abgeleitet — das wuerde sich mit dem Theme umkehren
     (in Dark waere --ink ein helles Grau, das Ergebnis ein HELLER
     Knopf mit dunklem Text — exakt das alte Weiss-auf-Dunkel-Muster,
     das die Vorrunde an .rp-save bewusst wegkorrigiert hat).

     KORRIGIERT (2026-08-13, Nutzer-Vorgabe/SimplifyerX, zweite Runde):
     GENAU DAS war der Fehler — nicht am Muster selbst, sondern daran,
     dass es fuer BEIDE Themes galt. Befund: ein Knopf wirkt als
     Bedienelement, wenn er im GEGENSATZ zur Umgebung steht, nicht im
     Einklang. Der alte fixe Wert (#000, beide Themes) traf das nur im
     Light Theme zufaellig richtig (schwarz auf hellem Panel = Kontrast)
     — im Dark Theme war er falsch (schwarz auf dunklem Panel =
     verschwimmt). Jetzt bewusst PRO THEME verschieden, mit
     vertauschten Rollen: Dark bekommt einen HELLEN Knopf (dieselbe
     Umkehr, die vorher schon einmal am SAVE-Knopf allein stand, siehe
     Kommentar oben — jetzt trägt sie das ganze System, kein
     Ausreisser mehr). Werte = die bestehenden --ink-Toene beider
     Themes, nicht neu erfunden. */
  --btn-primary-bg: #c9c9c9;
  --btn-primary-fg: #1a1a1a;
  /* Hover geht zum Extrem (Nutzer-Vorgabe): Dark heller bis Weiss,
     Light dunkler bis Schwarz. Die Textfarbe bleibt dieselbe wie im
     Ruhezustand — #1a1a1a auf Weiss und #ececec auf Schwarz sind
     beide >17:1, ein eigener Hover-Textton ist nicht noetig. */
  --btn-hover-bg:   #ffffff;
  /* NEU (2026-08-13, zweite Runde): eigener, THEME-UNABHAENGIGER Hover
     nur fuer .btn-danger (das "X"). Haengte bisher an --btn-hover-bg
     — mit dessen Umkehr waere Rot im Dark Theme auf Weiss gelandet
     (3.05:1, unter AA). Alter gemeinsamer Wert, jetzt entkoppelt:
     Rot bleibt in beiden Themes auf dunklem Grund (4.97:1 / 4.50:1
     gegen --red). */
  --btn-danger-hover-bg: #262626;
  /* NEU (2026-08-13): Rahmen der Variante "extra". EIN Wert fuer beide
     Themes, und das ist kein Versehen: #8a8a8a liegt zwischen den
     Panel-Hintergruenden beider Themes und ist dadurch in Dark HELLER
     als das Panel (3.50:1 gegen --panel, 4.21:1 gegen --bg) und in Light
     DUNKLER (3.45:1 / 3.28:1) — die geforderte Umkehr entsteht von
     selbst, ohne zweite Definition. Bewusst schwaecher als --ink-dim
     (4.33:1 / 5.10:1), aber ueberall ueber 3:1: der Rahmen soll zeigen,
     dass DIESER Knopf wichtiger ist als die anderen — dass es ueberhaupt
     ein Bedienelement ist, traegt seit dieser Runde der Schatten.
     UNVERAENDERT in dieser Runde: traegt die "Gegensatz zur Umgebung"-
     Logik bereits seit letzter Runde, siehe Nutzer-Bestaetigung
     2026-08-13. */
  --btn-extra-border: #8a8a8a;

  --gap: 14px;
  --pad: 10px;
  --rail-w: 54px;   /* GEÄNDERT (2026-08-12, Nutzer-Vorgabe): war 48px */
  --demo-bar-h: 41px;   /* NEU (2026-08-16): gemessene Hoehe von .demo-bar (Buttons bestimmen sie, nicht die Textzeile) — zentral, damit .topnav (siehe dort) nicht auseinanderlaufen kann */
  --left-panel-w: 220px;
  --right-panel-w: 350px;

  /* NEU: BOARD Setting — vier unabhängige Gap-Stufen (docs/setzz-board-
     customizing-notes.md). "Medium" = der jeweils bisherige, bestehende
     Wert dieses Abstands (dedizierte eigene Variable statt Wiederverwendung
     von --gap, das an anderen, unabhängigen Stellen — Panel-Padding,
     Group-Wrap-Margin — schon anderweitig genutzt wird und dafür
     unverändert bleiben soll). Werte werden zur Laufzeit per JS
     (applyGapSettings(), app.js) je nach gespeicherter Stufe überschrieben;
     diese hier sind die Defaults = "Medium". */
  --gap-card: 0.65rem;   /* CARD Gap: vertikal zwischen Cards in einer Column (.col-body) */
  --gap-col:  14px;      /* COL Gap: horizontal zwischen Columns in einem SET (.group-body) */
  --gap-set:  50px;      /* SET Gap: horizontal zwischen SETs in einer Row (.group-row) */
  --gap-row:  50px;      /* ROW Gap: vertikal zwischen Rows (.columns-scroll) */
  /* NEU (2026-07): SET-eigenes Innen-Padding, jetzt einstellbar statt
     fest (siehe .group-wrap) — h = links+rechts, v = oben+unten. */
  --pad-set-h: 20px;     /* SET h-padding: links/rechts innerhalb der SET-Karte (.group-wrap) */
  --pad-set-v: 30px;     /* SET v-padding: oben/unten innerhalb der SET-Karte (.group-wrap) */
  --pad-card: 5px;       /* CARD Padding: Innenabstand jeder Card (.panel) */

  /* NEU: Custom Board-Hintergrund — Tint-Ebene über einer garantierten
     Basis (siehe .workspace unten). Transparent = kein Tint,
     Standardzustand ohne gespeicherte Einstellung. Wird je nach
     aktivem Theme unterschiedlich gesetzt (applyBoardBackgroundTint(),
     app.js), daher hier nur ein neutraler Default. */
  --board-bg-tint: transparent;
  /* NEU (2026-08-27, Lightbox-Hintergrund): dieselbe Board-Farbe wie
     --board-bg-tint, aber mit einer EIGENEN, fest gewählten Deckkraft
     (.90) statt der vom Nutzer gewählten Board-Deckkraft — siehe
     applyBoardBackgroundTint() (app.js) für die Berechnung und den
     Bericht dieser Runde für die Kontrast-Messung, die zu .90 führte.
     Default hier ist WORTGLEICH der bisherige feste Wert von .lightbox
     (rgba(var(--overlay-rgb), .95)) — ohne gesetzten Board-Farbton bleibt
     die Lightbox dadurch unverändert, --overlay-rgb macht den Default
     bereits automatisch hell/dunkel-spezifisch, keine zweite Deklaration
     im [data-theme="light"]-Block nötig. */
  --board-bg-tint-lightbox: rgba(var(--overlay-rgb), .95);
  /* NEU (2026-08-27, Lightbox-Bedienelemente einheitlich): gemeinsame
     Platte für ALLE DREI Bedienelemente (Pfeile, Schließen, Beschriftung)
     — Entscheidung SimplifyerX, Variante (a). Bewusst NICHT aus
     --board-bg-tint/-lightbox abgeleitet: genau das Ableiten war das
     Problem (Farbe, die bei einem Board-Ton trägt, versagt beim
     nächsten — siehe Bericht der Hintergrund-Runde, "kein Alpha-Wert
     kann das für gesättigte Töne lösen"). --overlay-rgb macht sie
     automatisch hell/dunkel-spezifisch, aber IMMER fest, nie vom Board
     abhängig — das ist die eigentliche Entkopplung, nicht die Farbe an
     sich. .8 GEMESSEN (Bericht dieser Runde): niedrigster Wert, bei dem
     der Kontrast Symbol-gegen-Platte in JEDER getesteten Kombination
     (vier Board-Töne × beide Themes) über 4,5:1 bleibt — niedriger
     (wie das bisherige .6 der Pfeile) unterschreitet das bei hellen
     bzw. gesättigten Tönen deutlich. */
  --lightbox-plate-bg: rgba(var(--overlay-rgb), .8);
  /* NEU (2026-07): analoger Tint für den CARD-Hintergrund (.panel) —
     legt sich über die theme-eigene --panel-Basisfarbe, siehe
     applyCardBgTint(), app.js. */
  --card-bg-tint: transparent;
  /* NEU (2026-08-24, Teil 4): Zebra-Ueberlagerung fuer alternierende
     List-Zeilen (.list-td:nth-child(even), siehe dort) — hell oder
     dunkel, je nach applyCardBgTint()'s Helligkeitsmessung. transparent
     als Default, bis das Skript einmal gelaufen ist (analog
     --card-bg-tint oben). */
  --card-bg-zebra: transparent;
  /* NEU (2026-09-04, "Der Codeblock folgt der Kartenflaeche"): anders als
     --card-bg-zebra kein "transparent"-Default — der Codeblock braucht
     seine Flaeche auch ohne gesetzten Kartenton (siehe applyCardBgTint(),
     app.js). Hier der DARK-Default (dieses :root ist das Dark Theme,
     siehe Kopfkommentar der Datei): ungetoentes --panel (#363636) ist
     dunkel, also aufhellen. Light-Theme-Gegenwert siehe
     :root[data-theme="light"] weiter unten. Nur ein Platzhalter, bis das
     Skript einmal gelaufen ist -- praktisch sofort beim ersten Render. */
  --card-bg-code: rgba(255, 255, 255, .035);
  /* NEU: die Basis-Ebene selbst ist jetzt ebenfalls dynamisch — sehr
     dunkles Grau OHNE Custom-Farbe (damit graue CARDS sichtbar
     bleiben), aber reines Schwarz, SOBALD eine Custom-Farbe aktiv ist
     (damit der Tint wie ursprünglich gedacht darüber blendet, siehe
     applyBoardBackgroundTint()). */
  --board-bg-base: #141414;
  /* NEU: automatischer Kontrast-Ausgleich für Text/Icons AUSSERHALB der
     Cards (Row-Titel, Set-Beschriftung, sonstige lose Board-Elemente
     direkt auf dem Workspace-Hintergrund — siehe docs/setzz-board-
     customizing-notes.md, Abschnitt "Ergänzung"). Default = die ganz
     normalen, theme-eigenen Ink-Farben (kein Unterschied ohne Custom-
     Farbe); applyBoardBackgroundTint() (app.js) überschreibt beide
     Variablen per Inline-Style, sobald eine Custom-Farbe aktiv ist und
     die berechnete Mischfarbe einen Farbsatz-Wechsel verlangt. */
  --board-ink-faint: var(--ink-faint);
  --board-ink-dim: var(--ink-dim);
  --board-ink: var(--ink);

  /* NEU: Transparenz von SET-Style "Border"/"Background" (Light/Dark
     getrennt einstellbar, siehe BOARD SETTINGS → "SET Style
     Transparency"). Default 0.3 = bisheriger fest kodierter Wert. */
  --set-style-alpha-light: 0.3;
  --set-style-alpha-dark: 0.3;

  /* NEU: "CARD Style Custom" (docs/setzz-card-style-and-hide-notes.md) —
     Ersatz-Hintergrund für CARDS mit aktivem "Card Style: Custom"
     (⋮-Menü). Wird je nach aktivem Theme gesetzt (applyCardStyleCustomTint(),
     app.js); Default hier nur ein neutraler Platzhalter, bevor JS läuft. */
  --card-style-custom-bg: var(--panel);

  /* ══ Layer-Ordnung (2026-08, docs/setzz-floating-panels-notes.md) ══
     Benannte z-index-Stufen statt verstreuter Rohzahlen — genau EINE
     Quelle für die globale App-Chrome-Reihenfolge, damit eine künftige
     neue Ebene bewusst einsortiert wird statt geraten zu werden
     (dasselbe Prinzip wie bei den handgepflegten Listen im JS, nur mit
     Zahlen). Absteigend sortiert, oben = vorne:

     0. --z-tooltip         NEU (2026-08-13, Tooltip-Umbau Etappe 2).
        Ueber JEDER anderen Stufe, auch Confirm Dialog: ein title-
        tragendes Element kann in jeder Ebene stecken (ein Knopf in
        einem Confirm-Dialog hat z.B. title="..."), der Tooltip muss
        also immer oben schwimmen, unabhaengig davon, was gerade
        offen ist.
     1. --z-confirm-dialog  Die generische confirmDialog()-Modal — wird
        von sehr vielen, nicht einzeln auditierbaren Stellen aufgerufen
        (auch VERSCHACHTELT in anderen Ebenen, z.B. innerhalb des
        MD-Import-Dialogs oder während einer Free-Board-Marquee-Auswahl).
        Deshalb bewusst der einzige sichere Standard: IMMER zuoberst,
        auch über der Free-Board-Gesten-Ebene (9996–9999, siehe unten),
        die selbst unverändert bleibt. Kein Sonderfall soll sie je
        verdecken können.
     2. --z-modal-dialog    Eigenständige, block­ierende App-Dialoge
        (aktuell: MD-Import). Unter Confirm Dialog, weil genau dieser
        Dialog selbst wieder confirmDialog() aufruft (Ersetzen-Bestätigung)
        und darüber sichtbar bleiben muss.
     3. --z-toast           Bewusst über Floating Panels UND über der
        Immersive-View-Stufe: beide haben nahezu blickdichte Blenden
        (Lightbox/Focus-Overlay 90–95% Opazität), die einen Toast sonst
        spurlos verschlucken würden — der unauffälligste aller Fehler,
        weil man (anders als beim Dialog) gar nicht merkt, dass etwas
        fehlt. Unter Dialog/Modal: eine passive Meldung darf nie die
        Buttons einer aktiven Entscheidung verdecken.
     4. --z-immersive-view  Lightbox + Focus-Overlay (gemeinsame Stufe,
        da beide exklusiv sind und nie gegeneinander sortiert werden
        müssen). Aktuell löst keine Lightbox-Aktion einen verschachtelten
        Dialog aus — die Confirm-Dialog-Regel oben deckt diesen Fall aber
        vorsorglich ab, falls sich das künftig ändert.
     5. --z-popover-menu    Alle Kontextmenüs/Popover (Karten-⋮-Menü,
        Ratio/Fields/Meta-Editor, Share-Link/Group-Template/Color) in
        einer Stufe — sie schließen sich per Outside-Click gegenseitig,
        brauchen also keine eigene Reihenfolge, nur Vorrang vor Floating
        Panels.
     6. --z-floating-panel  Basis der schwebenden Panels (wie bisher;
        einzelne Panels erhalten per JS – floatingZTop, app.js – weiterhin
        aufsteigende Werte ab dieser Basis). Bleibt über Board/Rail/
        angedockten Panels, aber unter allen Stufen 1–5 — genau das war
        vorher NICHT der Fall (Confirm Dialog, Toast, Popover und
        Lightbox/Focus-Overlay standen alle unter 2000 und wurden von
        jedem offenen Floating Panel verdeckt).

     NICHT Teil dieser Ordnung: rein lokale/gepaarte Stacking-Contexts
     (z.B. .board-overlay↔.right-panel, Resize-Griffe innerhalb eines
     Panels/COLs, Medien-Kontrollen innerhalb einer Card, Dashboard-Kachel-
     interne Ebenen, die Free-Board-Gesten-Ebene 9996–9999) — deren rohe
     Zahlen bleiben unverändert, da sie nur gegen ihre unmittelbare
     Umgebung sortieren, nicht gegen die globale App-Chrome-Reihenfolge. */
  --z-floating-panel: 2000;   /* unverändert — muss mit FLOATING_Z_BASE in app.js synchron bleiben */
  --z-popover-menu:   3000;
  --z-immersive-view: 4000;
  --z-toast:           5000;
  --z-modal-dialog:   6000;
  --z-confirm-dialog: 10000;  /* > 9999, damit auch über der Free-Board-Gesten-Ebene */
  --z-tooltip:        10001;  /* > --z-confirm-dialog, siehe Layer-Ordnung oben */
}

/* ── Light theme — same structure, no rounded corners (matches the
   dark theme exactly), separation via soft shadows instead of
   borders. Sidebar shares the page background rather than a
   distinct shade. Toggled via [data-theme="light"] on <html>. ── */
:root[data-theme="light"] {
  /* GEÄNDERT (2026-08-15, Nutzer-Vorgabe/SimplifyerX, zweite Runde): war
     #f9f9f9 (1.05:1 gegen Weiss — kaum wahrnehmbar), dann kurz #f0f0f0
     versucht und wieder verworfen (liess --ink-dim auf --bg unter 4.5:1
     fallen, 4.84 -> 4.47). Diesmal --bg UND --ink-dim gemeinsam bestimmt,
     siehe dortiger Kommentar. #e6e6e6 gegen Weiss: 1.25:1 — deutlich
     wahrnehmbar, naeher an der Elevations-Stufe des Dark Themes selbst
     (--bg/--panel dort: 1.20:1) als am vorherigen, zu leisen Versuch. */
  /* Gegenstück zu color-scheme:dark auf :root — siehe dort für den
     vollständigen, geprüften Befund (wirkt NICHT auf das Datalist-
     Popup, das der Auslöser war; bleibt für zwei ungestylte native
     Scrollbalken). */
  color-scheme: light;
  --bg:        #e6e6e6;
  --bg-deep:   #ececec;
  --board-bg-base: #f0f0f0;
  --panel:     #ffffff;
  --panel-2:   #fbfbfb;
  /* Gegenwert zum Dark-Default im :root oben (--card-bg-code): ungetoentes
     --panel ist hier weiss, also verdunkeln statt aufhellen. */
  --card-bg-code: rgba(0, 0, 0, .035);
  --line:      #e2e2e2;
  --line-soft: #ececec;
  --asset-outline: #ffffff;   /* fine outline around uploaded assets */
  /* NEU (2026-08-13): normale Zeile bleibt am physikalischen Maximum
     (#ffffff, kann nicht heller werden) — die eigentliche Trennung
     leistet hier die dunklere Auswahl-Flaeche (1.27:1 zur Zeile,
     1.21:1 zum Panel-Hintergrund). Siehe Dark-Theme-Definition oben. */
  --asset-row-bg:          #ffffff;
  --asset-row-selected-bg: #e4e4e4;
  /* NEU (2026-08-15): eigene, dunklere Flaeche fuer Eingabefelder — siehe
     --field-bg im :root oben. Muss unter --bg UND unter --panel(weiss)
     liegen, damit ein leeres Feld allein durch seine Flaeche erkennbar
     ist (Nutzer-Befund: "Current password"/"New password" waren ohne
     Inhalt fast unsichtbar). Kontrast --ink-dim (neuer Wert unten) auf
     diesem Feld: 4.97:1 — ueber 4.5:1 mit Reserve, nicht auf Kante.
     GEÄNDERT (2026-08-15, gleicher Tag, Folgerunde): #dedede -> #eeeeee,
     Nutzer-Wunsch "weicher". ACHTUNG: liegt jetzt UEBER --bg (#e6e6e6)
     statt darunter — Feld ist die hellere Flaeche, nicht mehr die
     dunklere. --bg vs Feld nur noch 1.076:1 Kontrast (nahe am vom
     Nutzer selbst genannten "praktisch unsichtbar"-Wert 1.05). Exakt
     wie angefragt umgesetzt, nicht eigenmaechtig nachjustiert — siehe
     Meldung an den Nutzer. */
  --field-bg: #eeeeee;
  /* NEU (2026-08-15, gleiche Runde): eigener Hover-Ton statt Sprung auf
     --panel (#ffffff, reines Weiss) — der wirkte laut Nutzer-Befund wie
     ein Wechsel der Flaeche, nicht wie Hover. Kleiner, spuerbarer Schritt
     dunkler als --field-bg (#eeeeee -> #e8e8e8), bleibt erkennbar eigene
     Feld-Flaeche statt zur Panel-Flaeche zu springen. */
  --field-hover-bg: #e8e8e8;

  /* GEÄNDERT (2026-08-15): --ink-dim war #6e6e6e — auf dem gedunkelten
     --bg (#e6e6e6, s.o.) waere das 4.09:1, unter AA. Gemeinsam mit --bg
     neu bestimmt: #5c5c5c haelt 5.36:1 auf --bg und 4.97:1 auf --field-bg,
     beides mit Reserve statt auf der Kante. Bleibt klar von --ink
     (#1a1a1a) unterschieden (2.60:1 Kontrast dazwischen) — die
     Hierarchie-Stufe geht nicht verloren. */
  --ink:       #1a1a1a;
  --ink-hi:    #1a1a1a;   /* = --ink: Light braucht (noch) keine eigene Hierarchie-Stufe, siehe Bericht */
  --ink-dim:   #5c5c5c;
  --ink-faint: #9a9a9a;

  /* NEU (2026-08-13, zweite Runde): Umkehr-Rollen fuer Light. Dark war
     bisher (zufaellig) schon richtig gepolt (schwarz auf hellem Panel
     = Kontrast) — Light bekommt jetzt bewusst die Gegenrichtung:
     dunkler Knopf normal, im Hover zum Extrem Schwarz. Siehe
     Begruendung + Kontraste im Dark-Block oben. */
  --btn-primary-bg: #1a1a1a;
  --btn-primary-fg: #ececec;
  --btn-hover-bg:   #000000;

  --amber:     #C8A23F;
  --amber-hi:  #a8760f;
  --amber-dim: rgba(138,93,0,.10);
  --green:     #1f9e44;
  --red:       #d63a2e;

  --panel-shadow: 0 6px 18px rgba(0,0,0,0.05);
  --tile-shadow:  0 3px 7px rgba(0,0,0,.12);
  --overlay-rgb:  255, 255, 255;

  /* Bewusst identisch zum Dark-Block oben — siehe Kommentar dort. */
  --tooltip-bg: #888888;
  --tooltip-fg: #ffffff;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
/* NEU (2026-08-19, "Detail-Panel neu ordnen", Punkt d): Kontrollkästchen
   trugen app-weit das native Browser-Blau — die einzige Farbe im ganzen
   Werkzeug, die nicht aus der Projektpalette stammt (gemessen am
   2026-08-19: accent-color:auto, appearance:auto an allen Fundstellen,
   es gab NIRGENDS eine Checkbox-Regel).

   EINE Regel fuer alle acht Fundstellen statt acht Einzelregeln — sie
   sind ueber vier Dateien verstreut (AI/Real und Custom-Detail-Felder im
   Detail-Panel, Referenzen im Share-Dialog, Auto-Save in Export/Backup
   UND in der Rail, Tooltips in den Einstellungen, Schrift-Einbetten in
   der Typography-Karte, Feld-Sichtbarkeit pro World-Group). Eine
   Sammelregel auf dem Element-Selektor ist hier richtig und nicht zu
   grob: es gibt keine Checkbox, die absichtlich anders aussehen soll,
   und jede kuenftige bekommt die Projektfarbe automatisch mit.

   accent-color faerbt Fuellung und Haken in einem Zug und laesst das
   native Kaestchen ansonsten unangetastet — bewusst KEIN
   appearance:none mit nachgebautem Haken: das waere ein zweiter,
   handgepflegter Zustandsapparat (checked/indeterminate/disabled/focus)
   fuer einen reinen Farbwunsch.

   --amber-hi statt --amber, und das ist gemessen, nicht geschaetzt:
   --amber (#C8A23F) traegt im LIGHT-Theme gegen die Panel-Flaeche
   (#e6e6e6) nur 1.93:1 — deutlich unter den 3:1, die eine gefuellte
   Bedienflaeche gegen ihren Untergrund braucht, und schlechter als das
   native Blau, das ersetzt wird. Genau der Verdacht aus der Freigabe
   ("koennte im Light Theme zu blass wirken"), er hat sich bestaetigt.
   --amber-hi ist pro Theme in die jeweils kontrastreichere Richtung
   definiert (Dark #ddbb5f heller, Light #a8760f dunkler) und misst
   3.20:1 im Light- und 7.86:1 im Dark-Theme. Es ist damit der einzige
   Amber-Token, der in BEIDEN Themes traegt — --amber bleibt unangetastet
   die Signalfarbe fuer Raender, Text und Badges. */
input[type="checkbox"] { accent-color: var(--amber-hi); }

/* ── Button-Basisklassen ──────────────────────────────────────────
   NEU (2026-08-12, Sitzung A / Etappe 2), KOMPLETT UMGEBAUT
   (2026-08-13, Nutzer-Vorgabe/SimplifyerX).

   ══ WAS SICH GEAENDERT HAT UND WARUM ═══════════════════════════
   Sitzung A hatte vier ROLLEN (.btn-primary/.btn-secondary/
   .btn-icon/.btn-toggle) — abgeleitet aus der Frage "wie wichtig
   ist diese Aktion?". Das hat sich nicht getragen: die Rolle
   "Nebenaktion" endete als 2px-Rahmen ohne Fuellung, und der
   Nutzer-Befund dazu war eindeutig — solche Knoepfe wirken wie
   Umrandungen, nicht wie Bedienelemente.

   Ersetzt durch zwei VARIANTEN x drei GROESSEN:
     .btn            default — gefuellter Hintergrund, Rahmen
                     transparent. Die normale Form.
     .btn.btn-extra  extra   — 2px Rahmen, kein Hintergrund. Die
                     prominentere Form, bewusst SELTEN.
     .btn-sm / (—) / .btn-lg   small / medium / large.
                     Medium ist der Grundzustand ohne Modifikator.

   .btn-primary und .btn-secondary sind ERSATZLOS ENTFALLEN: beide
   hatten nachweislich null Verwendungen im Markup (geprueft in
   app.js und index.html), es gab also nichts zu migrieren. Die
   Wichtigkeit einer Aktion traegt jetzt die GROESSE (SAVE ist
   large), nicht eine eigene Variante — dadurch bleibt "extra"
   frei fuer genau die eine Aussage "dieser Knopf hier ist der
   besondere". Aktuell traegt ihn nur IMPORT URL, und das ist der
   Beweis, dass die Unterscheidung traegt: waere die Haelfte
   prominent, waere keiner mehr prominent.

   .btn-icon (Werkzeug) und .btn-toggle (Umschalter) bleiben
   unberuehrt — das sind keine Textknoepfe, die 49 Werkzeug-Icons
   sind ausdruecklich Sitzung B.

   ══ SCHRIFT ════════════════════════════════════════════════════
   var(--f-label) statt --f-data, letter-spacing: 0 statt .06em.

   WICHTIG fuer spaetere Leser: --f-label und --f-data zeigen
   HEUTE auf dieselbe Familie (JetBrains Mono, siehe :root) — die
   Umstellung ist visuell also ein No-Op und sieht wie eine
   sinnlose Aenderung aus. Sie ist trotzdem richtig: ein
   Button-Text ist ein LABEL ("Speichern"), kein DATENWERT
   ("1280x720", "3 KB", "2026-08-13"). --f-data traegt die
   Bedeutung "hier steht ein gemessener Wert". Beide Tokens
   existieren getrennt, damit genau diese zwei Dinge spaeter
   unabhaengig voneinander gesetzt werden koennen — sobald das
   passiert, wuerde jeder Knopf, der noch an --f-data haengt,
   stillschweigend mitwandern. Bitte NICHT zurueckdrehen, nur
   weil es heute keinen Unterschied macht. */
.btn {
  padding: var(--btn-pad-md);
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  /* 2px transparent statt border: 0 — haelt die Aussenmasse von
     default und extra identisch, damit ein Wechsel der Variante
     (oder ein Hover) nichts verschiebt. */
  border: 2px solid transparent;
  border-radius: 0;
  font-family: var(--f-label);
  font-size: var(--btn-fs-md);
  /* NEU (2026-08-13, Nutzer-Vorgabe, vierte Runde): geprueft, welche
     JetBrains-Mono-Schnitte tatsaechlich ausgeliefert werden (nicht
     angenommen) — 400/500/600 sind echte Dateien (siehe fonts.css),
     700 waere eine Browser-Fälschung von 600 (Deckungstest per Canvas:
     600 und 700 legen bitgleich viel Farbe). 500 ist damit der naechste
     ECHTE Schnitt ueber 400, kein synthetisches Fett. */
  font-weight: 500;
  letter-spacing: 0;
  text-transform: uppercase;
  cursor: pointer;
  /* Der Schatten macht den Knopf ueberhaupt erst als Bedienelement
     lesbar (Nutzer-Befund). --tile-shadow war bis heute ein TOTES
     Token — in :root definiert, nirgends verwendet. Genau dafuer
     war es benannt (kleine, abgesetzte Elemente), und ein totes
     Token wiederzubeleben ist besser, als einen zweiten
     Schatten-Stil daneben zu erfinden: SETzz ist eckig und
     flaechig, --panel-shadow ist die grosse Flaeche (Dialoge,
     Popover), --tile-shadow die kleine. */
  box-shadow: var(--tile-shadow);
  transition: color .12s, background .12s, border-color .12s;
}
.btn:hover { background: var(--btn-hover-bg); }
.btn-extra {
  background: transparent;
  color: var(--ink);
  border-color: var(--btn-extra-border);
  /* NACHGEMESSEN (2026-08-13, am gerenderten Knopf auf Staging, nicht
     vorab entschieden): der Schatten aus .btn WIRKT hier doppelt. Beim
     gefuellten Knopf liest er sich als Erhebung — es gibt einen
     sichtbaren Koerper, der ihn wirft. Beim Rahmen-Knopf ist der
     Koerper durchsichtig; der Schatten sitzt dann als zweite,
     unscharfe Linie NEBEN der scharfen Rahmenlinie und liest sich
     wie ein verrutschter doppelter Rand, nicht wie Hoehe. Deshalb
     hier abgeschaltet: bei dieser Variante traegt der Rahmen die
     Erkennbarkeit ohnehin allein, dafuer ist er da. */
  box-shadow: none;
}
/* Im Hover fuellt sich der Knopf — ab da gibt es einen Koerper, der
   einen Schatten werfen kann, also kommt er zurueck. */
.btn-extra:hover { box-shadow: var(--tile-shadow); }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe): Rahmen ging beim Hover bisher
   NICHT mit auf die Fuellfarbe — im Dark Theme gemessen 2.08:1
   Rahmen-gegen-Fuellung (praktisch unsichtbar), im Light 6.08:1 (klar
   sichtbar). Zwei Erscheinungsformen fuer denselben Hover-Zustand, je
   nach Theme, ohne dass das beabsichtigt war. Jetzt zieht der Rahmen
   bewusst auf --btn-hover-bg, verschmilzt in beiden Themes absichtlich
   mit der Fuellung statt zufaellig nur in einem. Alternative (Rahmen
   sichtbar halten) haette eine dritte Erscheinungsform eingefuehrt,
   die niemand verlangt hat. */
.btn-extra:hover {
  background: var(--btn-primary-bg);
  color: var(--btn-primary-fg);
  border-color: var(--btn-hover-bg);
}
/* NEU (2026-08-13, Nutzer-Vorgabe): einheitliche Deaktiviert-Behandlung
   fuer ALLE .btn/.btn-extra-Knoepfe, statt fuenf verschiedener Ad-hoc-
   Regeln an einzelnen Knoepfen (UPDATE THEME, RESTORE/X, Next set,
   Duplicate ×2 — je eigene Deckkraft, eigener Cursor, teils ohne
   Hover-Sperre). Ein deaktivierter Knopf soll wie eine zurueckgenommene
   Form desselben Knopfes aussehen, nicht wie eine dritte Variante.

   ZWEI Deckkraftwerte, nicht einer: .btn ist eine gefuellte Flaeche mit
   viel Kontrastreserve (100% Rahmen/Flaeche gegen Panel: 3.45:1 Light /
   3.50:1 Dark), .btn-extra traegt die Erkennbarkeit einzig ueber einen
   2px-Rahmen. Bei dem urspruenglich vorgeschlagenen EINEN Wert (40%)
   faellt der Rahmen auf 1.54:1 / 1.70:1 — praktisch nicht mehr da. Bei
   55% bleibt er bei 1.84:1 / 2.04:1, schwaecher als aktiv, aber noch
   als Rahmen lesbar. .btn bleibt bei 40% (Nutzer-Vorgabe), weil eine
   gefuellte Flaeche diese Reserve verkraftet. NICHT auf einen
   gemeinsamen Wert "vereinheitlichen" ohne diese Messung erneut zu
   pruefen — der Rahmen ist der Grund fuer den Unterschied. */
.btn:disabled { opacity: .4; cursor: not-allowed; }
.btn:disabled:hover { background: var(--btn-primary-bg); }
.btn-extra:disabled { opacity: .55; cursor: not-allowed; }
.btn-extra:disabled:hover {
  background: transparent;
  color: var(--ink);
  border-color: var(--btn-extra-border);
  box-shadow: none;
}
.btn-sm { padding: var(--btn-pad-sm); font-size: var(--btn-fs-sm); }
.btn-lg { padding: var(--btn-pad-lg); font-size: var(--btn-fs-lg); }
/* Destruktive Aktion (das "X" in der Auswahl-Leiste). Bleibt im
   Ruhezustand ein ganz normaler default-Knopf — das war auch
   vorher schon so (.lp-assets-bulk-btn-danger unterschied sich
   ausschliesslich im Hover), ist also keine Verschlechterung.
   --red ist ein eigener Token, NICHT --amber: die Akzentfarbe
   bleibt auch hier aussen vor. */
.btn-danger:hover { background: var(--btn-danger-hover-bg); color: var(--red); }
.btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-size);
  height: var(--btn-size);
  background: none;
  border: 0;
  color: var(--ink-faint);
}
.btn-icon:hover { color: var(--amber); }
.btn-icon.btn-icon-sm { width: var(--btn-size-sm); height: var(--btn-size-sm); }
/* GEÄNDERT (2026-08-13, Sitzung B): 4px 10px / 11px waren HART CODIERT,
   obwohl die Token daneben stehen — vom neuen check-buttons.php beim
   ersten Lauf gefunden. Die Klasse traegt heute KEIN Element (0 Treffer
   im Markup, siehe den Umschalter-Abschnitt in ui-design-system-notes.md),
   die Umstellung ist damit ohne sichtbare Wirkung — aber genau so faengt
   Drift an: wer spaeter --btn-fs-sm aendert, uebersieht diese Stelle, und
   der erste Umschalter, der die Klasse benutzt, faellt aus der Reihe. */
.btn-toggle {
  padding: var(--btn-pad-sm);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink-faint);
  font-family: var(--f-data);
  font-size: var(--btn-fs-sm);
}
.btn-toggle:hover { color: var(--ink); }
.btn-toggle.active { color: var(--ink-hi); border-bottom-color: var(--amber); }

/* NEU: generelle Schrift-Baseline für Buttons in den linken Menü-
   Panels (Cards/SET Presets/Assets/Search/Tools/Text-to-BOARD/
   Export&Backup/Settings/BOARD Settings). Niedrige Spezifität —
   bereits bestehende, bewusst kompaktere Buttons (z.B. .lp-bs-seg-btn,
   .lp-bs-save-btn) behalten ihre eigene, spezifischere Font-Regel. */
.left-panel button,
.floating-panel button {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
}
/* NACHGEMESSEN UND KORRIGIERT (2026-08-13): die Regel darueber hat die
   Spezifitaet (0,1,1) und schlaegt damit .btn (0,1,0). Folge, im
   Browser gemessen, nicht vermutet: JEDER .btn in einem linken oder
   schwebenden Panel — UPLOAD, IMPORT URL, EXPORT, IMPORT, das X, der
   BOARD-SETTINGS-SAVE, die Text-to-BOARD-Knoepfe — kam als 14px
   Rajdhani 500 heraus statt in --f-label und der Token-Groesse. Das
   ganze Groessen-System waere damit genau dort wirkungslos gewesen, wo
   es am meisten gebraucht wird. Aufgefallen erst beim Nachmessen im
   echten Panel: die injizierte Testseite hatte keinen .left-panel-
   Vorfahren, dort stimmte alles.

   Bewusst NICHT ueber :where() an der Regel oben geloest, obwohl deren
   eigener Kommentar "niedrige Spezifitaet" behauptet und die
   Nachbarregel darunter es so macht: das Absenken auf (0,0,1) wuerde
   die Rangfolge fuer JEDEN anderen Knopf in diesen Panels umdrehen
   (Segment-Knoepfe, Typ-Filter, Theme-Zeilen …), also weit ueber diese
   Runde hinaus. Dieser Zuschnitt hier trifft ausschliesslich .btn.
   Die eigentliche Spezifitaets-Schieflage der Regel oben bleibt als
   Befund fuer eine eigene Runde stehen.

   font-weight: 400 ist kein neuer Wert, sondern der, mit dem .rp-save
   im rechten Panel (ohne diese Baseline) schon heute rendert — damit
   sehen alle Knoepfe gleich aus, statt je nach Panel unterschiedlich. */
.left-panel .btn,
.floating-panel .btn {
  font-family: var(--f-label);
  font-size: var(--btn-fs-md);
  font-weight: 400;
}
.left-panel .btn-sm,
.floating-panel .btn-sm { font-size: var(--btn-fs-sm); }
.left-panel .btn-lg,
.floating-panel .btn-lg { font-size: var(--btn-fs-lg); }
/* NEU: Normal-/Hover-Hintergrund für Buttons in den linken Menü-Panels
   (dunkel Grau/Schwarz bei Dark, hell Grau/Weiß bei Light). :where()
   hält die Spezifität bei genau einem Element-Selektor, damit bereits
   spezifisch gestylte Buttons (Segment-Buttons, Icon-Buttons, Save-
   Button etc.) diese Baseline weiterhin unverändert überschreiben. */
:where(.left-panel, .floating-panel) button {
  background: transparent;
  transition: background .12s;
}
:where(.left-panel, .floating-panel) button:hover {
  background: #000;
}
:where([data-theme="light"]) :where(.left-panel, .floating-panel) button:hover {
  background: #fff;
}
a { color: inherit; text-decoration: none; }
ul { list-style: none; }
img, svg { display: block; }

html, body { height: 100%; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--f-label);
  font-size: 13px;
  line-height: 1.45;
  height: 100vh;
  overflow: hidden;
}

/* NEU (2026-07, Nutzer-Rückmeldung): 100% Deckkraft wirkte zu hart.
   color-mix() liest --amber LIVE — funktioniert also automatisch mit
   jeder gewählten Accent-Farbe (Standard oder per applyWorldAccent()
   überschrieben), ohne dass diese Regel je wieder angefasst werden
   muss. Erste Zeile ist der Fallback für Browser ohne color-mix()
   (ungültige Deklaration wird ignoriert, die gültige davor bleibt
   bestehen) — 20% eines mittleren Amber-Tons als fester Näherungswert. */
::selection { background: rgba(200, 162, 63, .2); background: color-mix(in srgb, var(--amber) 20%, transparent); color: #000; }

/* NEU (2026-07): dezente Textauswahl-Farbe speziell in Eingabefeldern
   — statt der globalen Amber-Akzentfarbe (die bleibt für den Rest der
   Anwendung, z.B. Board-Text, unverändert bestehen). */
input::selection, textarea::selection { background: var(--line); color: var(--ink); }

/* ── App grid: 3-zone layout ────────────────────────────────
   rail (fixed 48px) | left-panel (0→220px) | main (flex) | right-panel (0→240px)
   All zones are flex children on the root .app row. ─────── */
.app {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

/* ── Left Rail ──────────────────────────────────────────── */
.left-rail {
  width: var(--rail-w);
  flex-shrink: 0;
  background: var(--bg);
  border-right: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px 0;
  gap: 2px;
  z-index: 30;
}
/* NEU (2026-08-04): Abwurfzone beim Ziehen eines schwebenden Panels
   zurück auf die Rail — Gegenstück zum Titelzeilen-Drag. Gleiche
   Sprache wie .upload-drop-ready (Amber-Innenrahmen), zusätzlich ein
   Hintergrund-Tönung, da die Rail nur 48px breit ist und ein reiner
   Rahmen dort leicht übersehen würde. */
.left-rail.left-rail-dock-target {
  background: var(--amber-dim, rgba(200,163,74,.14));
  outline: 2px solid var(--amber);
  outline-offset: -2px;
}
/* GEÄNDERT (2026-08-17, Branding-Release logo-04): das Bild schrumpft
   von 36px auf 26px (Nutzer-Vorgabe), die Trefferfläche soll das nicht
   mitmachen. .rail-logo hatte vorher KEIN eigenes Padding/Breite — die
   Box war exakt so breit wie ihr breitestes Kind (das Bild, 36px,
   gemessen). Padding statt fester Breite hätte hier ueberschossen: das
   Label ("Alpha", ~30px) ist bereits breiter als das neue 26px-Bild und
   waere selbst zum breitesten Kind geworden, +10px Padding haette dann
   ~40px ergeben statt 36px (auf Staging gemessen, korrigiert). Feste
   Breite umgeht das: unabhaengig davon, welches Kind gerade breiter
   ist, bleibt die Box exakt 36px. */
.rail-logo { margin-bottom: 10px; width: 36px; display: flex; flex-direction: column; align-items: center; gap: 3px; }
/* NEU (2026-08-03): das Logo ist der Weg zurueck ins Dashboard — der
   Zeiger sagt das an, solange es einen Weg zurueck gibt. Im Dashboard
   selbst (data-app ohne .board-open) bleibt es ein Bild. */
.app.board-open .rail-logo { cursor: pointer; }
.rail-logo-img { width: 26px; height: 26px; object-fit: contain; display: block; }
.rail-logo-label { display: block; opacity: 0; font-family: var(--f-display); font-size: 13px; letter-spacing: 0; color: inherit; text-transform: none; }
:root[data-theme="light"] .rail-logo-img { filter: invert(1); }

/* Zurück-zum-Dashboard-Pfeil (NEU 2026-07): sitzt unterhalb des Logos,
   mit etwas Abstand — nur sichtbar, solange ein Board geöffnet ist. */
.rail-back-btn {
  width: 32px; height: 32px;
  margin: 6px auto 0;
  display: grid; place-items: center;
  border: 0; border-radius: 0; background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .15s, background .15s;
}
.rail-back-btn svg { width: 16px; height: 16px; }
.rail-back-btn:hover { color: var(--amber); background: var(--panel-2); }
/* NEU: visibility statt display:none — Button bleibt im Layout erhalten,
   damit die Rail-Nav-Icons darunter nicht nach oben rutschen, sobald der
   Zurück-Pfeil bei offenem Menü-Panel unsichtbar wird (siehe
   updateRailBackBtnVisibility(), app.js). Nicht klickbar währenddessen. */
.rail-back-btn.rail-back-btn-invisible { visibility: hidden; pointer-events: none; }

.rail-nav { display: flex; flex-direction: column; align-items: center; gap: 2px; flex: 1; margin-top: 1.2rem; }
.rail-bottom { display: flex; flex-direction: column; align-items: center; gap: 6px; padding-bottom: 4px; }
.rail-sep { width: 24px; height: 1px; background: var(--line-soft); margin: 4px 0; }
.rail-btn {
  width: 38px; height: 38px;   /* GEÄNDERT (2026-08-12): war 32px, +6px Nutzer-Vorgabe */
  display: grid; place-items: center;
  border: 0; border-radius: 0; background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .15s, background .15s;
}
/* NEU (2026-08-19, "Detail-Panel: fünf Nachbesserungen", Punkt 1):
   .rail-btn ist KEINE reine Rail-Klasse — dieselbe Klasse trägt auch die
   fünf Icons der Send-Asset-Zeile im Detail-Panel (rp-send-asset-find/
   -icons, siehe openRightPanelForUpload() in app.js: Show/Locate/
   Open-Tab/Crop/More). Eine Änderung auf .rail-btn selbst hätte also
   beide Stellen getroffen. Gezielt auf .left-rail gescopt (der
   Wrapper <aside class="left-rail">, umschließt .rail-nav UND
   .rail-bottom — deckt damit auch "data", den Sync-Knopf und den
   Theme-Toggle ab, die außerhalb von .rail-nav in .rail-bottom liegen),
   damit die im letzten Detail-Panel-Umbau sorgfältig vermessene
   Icon-Zeile (245px nutzbar bei 280px Panelbreite, 196px belegt)
   unverändert bleibt. 30px unterschreitet den dokumentierten
   --btn-size-Standard (32px, "Werkzeug/Icon-Button") knapp und trifft
   auch nicht --btn-size-sm (24px) — ein bewusster Zwischenwert, kein
   bestehender Token. Bei nur 2px Abstand zwischen den Knöpfen
   (.rail-nav/.rail-bottom gap) schrumpft der Klick-Rhythmus von 40px
   auf 32px Pitch (-20%) — auf einer staendig genutzten Rail spuerbar,
   aber oberhalb der WCAG-2.2-Mindestgroesse (24px). Icon-Groesse
   proportional mitskaliert, um das Verhaeltnis aus dem Ursprungs-
   kommentar ("~55% der Button-Groesse, Styleguide-Mass") zu erhalten:
   21/38 ≈ 0.55, davon 30px ≈ 16.6px, aufgerundet 17px. */
.left-rail .rail-btn { width: 30px; height: 30px; }
.left-rail .rail-btn svg { width: 17px; height: 17px; }
.rail-btn svg { width: 21px; height: 21px; }   /* GEÄNDERT: war 18px — ~55% der Button-Größe, Styleguide-Maß */
.rail-btn:hover { color: var(--ink); background: var(--panel-2); }
.rail-btn.active { color: var(--ink-hi); }   /* GEÄNDERT (2026-08-12): war --ink, identisch mit :hover — jetzt eigene, hellste Stufe für "aktiv" */
/* NEU (2026-07, Free BOARD): "SET Presets"/"Text to BOARD" bauen auf der
   Row/SET/Column-Struktur auf, die ein Free BOARD nicht hat. Bewusst
   sichtbar-deaktiviert statt ausgeblendet — ein verschwundener Knopf
   wirkt wie ein Fehler, ein ausgegrauter erklärt sich (Tooltip nennt
   den Grund, siehe updateRailAvailability() in app.js). */
.rail-btn-disabled {
  opacity: .3 !important;
  pointer-events: none;
}

/* NEU: rail-nav-Icons sind im Dashboard ausgeblendet und blenden sich
   beim Öffnen eines BOARDS gestaffelt ein (oben nach unten, jedes Icon
   von links nach rechts einschwebend) — Klasse "board-open" wird von
   showDashboard()/showBoardView() (world-sync.js) auf [data-app] gesetzt.
   GEFUNDEN (2026-08-15): stand hier urspruenglich nur als `opacity: 0`
   — blendete optisch aus, liess die Icons aber anklickbar und
   hoverbar stehen (Tooltip erschien, ein Klick im Dashboard oeffnete
   das zugehoerige Panel ueber dem Dashboard-Inhalt). `display: none`
   legt sie jetzt wirklich still, gleiches Muster wie der korrekt
   gebaute Nachbar zwei Regeln weiter unten (#sync-now-btn,
   .rail-autosave-switch). Die Animation bricht dadurch NICHT: sobald
   .board-open erscheint, wechselt display synchron auf grid UND die
   Animation startet im selben Augenblick bei ihrem eigenen from-
   Keyframe (opacity:0) — anders als bei einer transition gibt es bei
   einer animation keinen Zwischenzustand, der aufblitzen koennte. Die
   separate `opacity: 0`-Basisregel ist deshalb ueberfluessig geworden
   und entfaellt. */
.rail-nav .rail-btn { display: none; }
@keyframes railIconIn {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}
.app.board-open .rail-nav .rail-btn {
  display: grid;
  animation: railIconIn .3s ease forwards;
}
.app.board-open .rail-nav .rail-btn:nth-of-type(1) { animation-delay: .00s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(2) { animation-delay: .05s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(3) { animation-delay: .10s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(4) { animation-delay: .15s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(5) { animation-delay: .20s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(6) { animation-delay: .25s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(7) { animation-delay: .30s; }
.app.board-open .rail-nav .rail-btn:nth-of-type(8) { animation-delay: .35s; }
.rail-avatar { background: transparent; border: 0; cursor: pointer; padding: 2px; }
/* NEU (2026-08-15, Befund 5a): oeffentliche Demo hat kein Konto, also kein
   Foto — initDemoChrome() (app.js) ersetzt das <img> durch ein Zahnrad-Icon
   und setzt diese Klasse ZUSAETZLICH. Bewusst additiv statt .rail-avatar
   selbst umzubauen: angemeldete Konten zeigen weiterhin ihr echtes,
   rundes Fotos (.avatar, 30px, border-radius:100%) unveraendert. Keine
   eigene runde Fassung fuers Icon (Kandidaten mit Kreisrahmen probeweise
   im Rail-Kontext gegenübergestellt) — bare, gleiche Groesse/Hover wie die
   drei Werkzeug-Knoepfe direkt darueber (.rail-btn, 38px/21px) passt besser:
   der Knopf fuehrt jetzt zu Einstellungen, nicht mehr zu einem Profil. */
.rail-avatar.rail-avatar-icon {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  color: var(--ink-faint);
}
.rail-avatar.rail-avatar-icon:hover { color: var(--ink); background: var(--panel-2); }
.rail-avatar.rail-avatar-icon svg { width: 21px; height: 21px; }

/* NEU (2026-07): Auto-Save-Schalter und "Sync Now" sind nur relevant,
   solange ein BOARD offen ist — im Dashboard ausgeblendet (gleiche
   .board-open-Klasse, die auch die rail-nav-Icons steuert, siehe
   showDashboard()/showBoardView() in world-sync.js). */
#sync-now-btn,
.rail-autosave-switch { display: none; }
.app.board-open #sync-now-btn { display: grid; }
.app.board-open .rail-autosave-switch { display: block; }

/* Auto-Save switch, permanently docked in the rail (siehe .ep-autosave-*
   im Export/Backup-Panel für das größere Original mit Label/Status-Text)
   — hier bewusst ohne jeden Beschreibungstext und etwas kleiner. */
.rail-autosave-switch {
  position: relative;
  width: 24px; height: 14px;
  margin: 4px 0;
  cursor: pointer;
  flex-shrink: 0;
}
.rail-autosave-switch input { display: none; }
.rail-autosave-slider {
  position: relative;
  display: block;
  width: 100%; height: 100%;
  background: var(--line);
  border-radius: 8px;
  transition: background .15s;
}
.rail-autosave-slider::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 10px; height: 10px;
  background: #fff;
  border-radius: 50%;
  transition: transform .15s;
}
.rail-autosave-switch input:checked + .rail-autosave-slider { background: var(--amber); }
.rail-autosave-switch input:checked + .rail-autosave-slider::after { transform: translateX(10px); }

/* NEU (2026-08, Auto-Save-Wahlfreiheit als Pro-Gate, UI-Ebene — siehe
   docs/setzz-plans-demo-standard-pro-notes.md). Dezenter "P"-Punkt statt
   vollem Textbadge, da der Rail-Switch nur 24x14px misst. */
.rail-pro-dot {
  display: none;
  position: absolute;
  top: -5px; right: -5px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--panel);
  border: 1px solid var(--amber);
  color: var(--amber);
  font-family: var(--f-data);
  font-size: 7px;
  line-height: 8px;
  text-align: center;
}
.rail-autosave-switch-locked { opacity: .55; cursor: not-allowed; }
.rail-autosave-switch-locked .rail-pro-dot { display: block; }

/* NEU (2026-07): kompletter Ein-/Ausblende-Mechanismus für den Left
   Rail (Strg+H oder Minimize-Button unter dem Avatar). */
.rail-minimize-btn {
  width: 34px; height: 22px;
  display: grid; place-items: center;
  border: 0; background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .15s, background .15s;
}
.rail-minimize-btn:hover { color: var(--amber); background: var(--panel-2); }
.rail-minimize-btn svg { width: 14px; height: 14px; }

.app.left-rail-hidden .left-rail { display: none; }
/* NEU: Minimize-Klick blendet jetzt auch die TopNav mit aus (übersteht
   den gleichen Reveal-Button/Toggle wie der Rail selbst). */
.app.left-rail-hidden .topnav { display: none; }

/* NEU (2026-07): Zusatz-Absicherung, unabhängig davon, warum genau
   ein Rest-Schatten/Hintergrund des Left Panels sichtbar blieb, wenn
   der Rail ausgeblendet ist — closeLeftPanel() entfernt zwar die
   "open"-Klasse (transform: translateX(-100%)), aber offenbar reichte
   das nicht in jedem Fall. Doppelt hält besser: opacity 0 UND noch
   weiter aus dem sichtbaren Bereich geschoben (-150% statt -100%),
   ungeachtet des offen/geschlossen-Zustands, solange der Rail
   ausgeblendet ist. */
.app.left-rail-hidden .left-panel {
  opacity: 0;
  pointer-events: none;
  transform: translateX(-150%) !important;
}

.left-rail-reveal-btn {
  position: fixed;
  left: 0; top: auto;
  bottom: 1%;
  transform: translateY(-50%);
  width: 14px; height: 48px;
  display: none;
  align-items: center; justify-content: center;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-left: 0;
  color: var(--ink-faint);
  cursor: pointer;
  z-index: 40;
  transition: color .15s, background .15s;
}
.app.left-rail-hidden .left-rail-reveal-btn { display: flex; }
.left-rail-reveal-btn:hover { color: var(--amber); background: var(--panel); }
.left-rail-reveal-btn svg { width: 10px; height: 10px; }

/* ── Left Panel ─────────────────────────────────────────── */
.left-panel {
  position: fixed;
  left: var(--rail-w);
  top: 0;
  bottom: 0;
  width: var(--left-panel-w);
  background: var(--bg);
  border-right: 1px solid var(--line-soft);
  overflow: hidden;
  z-index: 20;
  transform: translateX(-100%);
  transition: transform .2s ease;
  box-shadow: 4px 0 16px rgba(0,0,0,.18);
  display: flex;
  flex-direction: column;
  /* NEU: Font-Baseline für gewöhnlichen Fließtext in den linken Menü-
     Panels — wird per Vererbung an alle Kind-Elemente weitergegeben.
     Headlines (.lp-section-title, .lp-subsection-title etc.) und
     Buttons (.left-panel button) haben bereits eigene, spezifischere
     Font-Regeln und überschreiben diesen Default unverändert. */
  font-family: var(--f-label);
  font-size: 11px;
}
.left-panel.open { transform: translateX(0); }
/* each target can override the width */
.left-panel[data-panel-target="panels"] { width: calc(var(--left-panel-w) * 1.1); margin-left: 0px; }
.left-panel[data-panel-target="presets"] { width: calc(var(--left-panel-w) * 2); }
/* NEU (2026-07): Breite jetzt per Anfasser veränderbar (siehe
   .assets-panel-resize weiter unten + bindAssetsPanelResize() in
   app.js) — --assets-panel-w wird nur von DIESEM Target gelesen (nur
   im ANGEDOCKTEN Zustand relevant — der eigene Anfasser ist per CSS
   nur dort sichtbar, das schwebende Fenster hat seinen generischen
   Eck-Anfasser), alle anderen Left-Panel-Targets bleiben bei ihrer
   festen calc()-Breite. */
.left-panel[data-panel-target="assets"] {
  width: var(--assets-panel-w, calc(var(--left-panel-w) * 2));
}
.left-panel[data-panel-target="textboard"] { width: calc(var(--left-panel-w) * 2); }
.left-panel[data-panel-target="sets"] { width: calc(var(--left-panel-w) * 2); }
/* NEU (2026-08-23, 1d): EXPORT/BACKUP hatte bisher KEINE eigene Regel
   und fiel damit auf die einfache --left-panel-w zurueck — halb so
   breit wie BOARD SETTINGS. Gleicher Faktor wie dort; die Breite wird
   je Panel gesetzt, es gibt keine gemeinsame Groesse, die hier nur
   nicht gegriffen haette. Seit 1d traegt das Panel zusaetzlich den
   Share-Link samt Adresse, und die braucht die Breite. */
.left-panel[data-panel-target="export"] { width: calc(var(--left-panel-w) * 2); }
.left-panel[data-panel-target="data"] { width: calc(var(--left-panel-w) * 3); }
.left-panel[data-panel-target="settings"] { width: calc(var(--left-panel-w) * 2); }
.left-panel-inner { width: 100%; height: 100%; display: flex; flex-direction: column; overflow: hidden; }
/* NEU (2026-07): Anfasser am rechten Rand — nur für das ASSETS-Target
   sichtbar (siehe bindAssetsPanelResize() in app.js), analog zu
   .right-panel-resize, nur spiegelverkehrt. */
.assets-panel-resize {
  display: none;
  position: absolute;
  top: 0; bottom: 0; right: -5px;
  width: 9px;
  cursor: ew-resize;
  z-index: 21;
  touch-action: none;
}
.left-panel[data-panel-target="assets"] .assets-panel-resize { display: block; }
.assets-panel-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  background: transparent;
  transition: background .15s;
}
.assets-panel-resize:hover::after,
.left-panel.resizing .assets-panel-resize::after { background: var(--amber); }
.left-panel.resizing { transition: none; user-select: none; }

/* ── Main area ──────────────────────────────────────────── */
.main-area { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.main-area[hidden] { display: none; }
.main-area:not([hidden]) { animation: view-fade-in .6s ease; }

/* ── Dashboard — Startansicht nach Login (NEU 2026-07) ──────
   Vollflächige Board-Galerie, ersetzt main-area solange kein Board
   geöffnet ist. Eigenes Flex-Kind auf Höhe von .app, nimmt denselben
   Platz wie main-area ein. */
.dashboard-view {
  flex: 1; min-width: 0;
  overflow-y: auto;
  padding: 48px 56px;
}
.dashboard-view[hidden] { display: none; }
.dashboard-view:not([hidden]) { animation: view-fade-in .6s ease; }
@keyframes view-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.dashboard-head {
  margin-bottom: 6px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}
.dashboard-title {
  font-family: var(--f-display);
  font-size: 18px;
  font-weight: 300;
  letter-spacing: .01em;
  color: var(--ink-dim);
}
/* GEÄNDERT (2026-08-19, "Dashboard: Filterknöpfe und Import-Rahmen"):
   stand bisher neben der Überschrift (siehe .dashboard-head oben), jetzt
   Teil von renderDashboard() (world-sync.js) — eigene Zeile direkt über
   dem gestrichelten Trenner der Ungrouped-Row (.world-group-row[data-
   group-id], border-top weiter unten). Rechtsbündig statt links, damit
   die Zeile optisch mit den rechten Icon-Clustern der Group-Header
   fluchtet. NAME/CREATED/EDIT bleiben als .dashboard-sort-group mit dem
   engen 4px-Abstand zusammen (sie sortieren gemeinsam); THEME sitzt als
   eigenständiger Umschalter (kein Sortierfeld) mit 20px Abstand davon
   ab — deutlich mehr als die 4px innerhalb der Sortiergruppe, aber ohne
   eigene Zeile. flex-wrap sichert schmale Fenster ab (bricht dann
   zweizeilig um, bevor Text abgeschnitten würde). */
.dashboard-sort {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 20px;
  margin-bottom: 10px;
}
.dashboard-sort-group {
  display: flex;
  gap: 4px;
}
/* GEÄNDERT (2026-08-19): der 1px-Rahmen entfiel — er war das einzige
   Aktiv-Signal für EDIT/THEME, jetzt uebernimmt die Farbe (unten) diese
   Rolle allein. border:1px solid transparent statt eines entfernten
   border, damit Hover/Active keine 2px-Größenverschiebung mehr auslösen
   könnten, falls hier je wieder eine Rahmenfarbe ergänzt wird. */
.dashboard-sort-btn {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid transparent;
  padding: 6px 10px;
  transition: color .15s;
}
.dashboard-sort-btn:hover { color: var(--ink); }
.dashboard-sort-btn.active { color: var(--amber); }
/* NEU (2026-08-19): THEME ist ein An/Aus-Umschalter, NAME/CREATED/EDIT
   sind eine Auswahl (immer genau einer aktiv). Bei den Sortier-Knöpfen
   macht der angehängte Pfeil (▲/▼, siehe renderDashboardSortBar() in
   world-sync.js) den aktiven bereits eindeutig unterscheidbar — Farbe
   reicht dort als zweites Signal. THEME hat kein Pfeil-Äquivalent, war
   also nach dem Rahmen-Wegfall auf die reine Textfarbe allein angewiesen
   — zu schwach für einen Umschalter. Eigener Punkt, gleiche Bauform wie
   .rp-prompt-dot (amber, rund), macht den An-Zustand unabhängig von der
   Farbwahrnehmung erkennbar. */
.dashboard-theme-preview-btn.active::after {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--amber);
  margin-left: 6px;
  vertical-align: middle;
}
/* NEU (2026-07): WORLDS als Ordner-Ebene über Boards (siehe
   docs/worlds-grouping-notes.md) — .dashboard-grid ist jetzt ein
   vertikaler Stapel aus Rows (.world-group-row), statt selbst das
   Tile-Grid zu sein. Das eigentliche Tile-Grid (frühere Regel hier)
   lebt jetzt eine Ebene tiefer in .world-group-body. */
.dashboard-grid {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* NEU (2026-08, Team-Sichtbarkeit — Block 1). Bleibt in diesem Schritt
   ueber [hidden] im Markup verschlossen (siehe index.html) — die Regeln
   hier sind bereits fertig, damit Block 2 nur das hidden-Attribut
   entfernen muss, ohne CSS nachzuziehen. */
.dashboard-section { margin-top: 28px; }
.dashboard-section-title {
  font-family: var(--f-display);
  font-size: 16px;
  font-weight: 300;
  letter-spacing: .01em;
  color: var(--ink-dim);
  margin-bottom: 10px;
}
.team-worlds-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 20px;
}
/* Eigenstaendig statt worldTileMarkup()-Wiederverwendung — kein Cover,
   kein Pin, kein Kebab, kein Klick-Verhalten (siehe renderTeamWorlds()
   in world-sync.js). Sichtbar als "das ist etwas anderes als meine
   eigenen Boards", nicht nur zufaellig anders. */
.team-world-tile { cursor: default; }
.team-world-tile .world-tile-cover { cursor: default; }
.team-world-tile .world-tile-owner {
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
  margin-top: 2px;
}
.world-group-row--top {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
}
.world-group-row--top .world-tile-add {
  width: 100px;
}
.world-group-row--top .world-tile-import {
  width: 220px;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe): 220px → 280px, damit bei
   großer Auflösung 4 statt 5 Spalten entstehen (nachgemessen: bei
   1391px verfügbarer Breite ergab 220px exakt 5 Spalten à 262px,
   280px ergibt exakt 4). Bleibt auto-fill/minmax — keine Media-Query,
   die Staffelung darunter (3, 2, 1 Spalten) verschiebt sich dadurch
   nur proportional zu höheren Breiten, bricht nicht. */
.world-group-body {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
  margin-left: 20%;
}
.world-group-body[hidden] { display: none; }

.world-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  margin-bottom: 6px;
  cursor: pointer;
}
/* NEU (2026-07): Reihenfolge von links nach rechts ist jetzt Titel,
   Count, dann als geschlossener Block ganz rechts Pin → Menü →
   Auf-/Zuklappen → Drag-Handle (Drag-Handle ganz außen rechts). Nur
   der Pin-Button (erster Button des rechten Blocks) bekommt
   margin-left:auto, um den gesamten Block ans rechte Ende zu drücken —
   die übrigen bleiben bewusst auf margin-left:0, sonst verteilt
   Flexbox den freien Raum auf JEDEN auto-margin einzeln und reißt
   sichtbare Lücken zwischen die Icons. */
.world-group-drag-handle {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  cursor: grab;
  flex-shrink: 0;
  margin-left: 0;
}
.world-group-drag-handle svg { width: 19px; height: 19px; }
.world-group-drag-handle:hover { color: var(--ink-dim); }
.world-group-title {
  font-family: var(--f-display);
  font-size: 25px;
  font-weight: 400;
  letter-spacing: 0em;
  color: var(--ink-faint);
  cursor: pointer;
}
.world-group-title--fixed {
  color: var(--ink-faint);
  cursor: pointer;
  text-transform: uppercase;
  font-size: 13px;
  letter-spacing: .04em;
}
.world-group-count {
  font-family: var(--f-data);
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
  border: 0px solid var(--line);
  padding: 1px 7px;
}
.world-group-pin-btn,
.world-group-ratio-btn,
.world-group-fields-btn,
.world-group-menu-btn,
.world-group-collapse-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  background: none;
  border: 1px solid transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .15s, border-color .15s;
  margin-left: 0;
  flex-shrink: 0;
}
.world-group-pin-btn { margin-left: auto; }
.world-group-pin-btn svg { width: 18px; height: 18px; }
.world-group-ratio-btn svg { width: 16px; height: 16px; }
.world-group-fields-btn svg { width: 16px; height: 16px; }
.world-group-menu-btn svg { width: 19px; height: 19px; }
.world-group-collapse-btn svg { width: 19px; height: 19px; transition: transform .15s; }
.world-group-pin-btn:hover,
.world-group-ratio-btn:hover,
.world-group-fields-btn:hover,
.world-group-menu-btn:hover,
.world-group-collapse-btn:hover { color: var(--amber); border-color: var(--amber); }
.world-group-pin-btn.pinned { color: var(--amber); }
.world-group-pin-btn.pinned svg { fill: currentColor; }
.world-group-collapse-btn.collapsed svg { transform: rotate(-90deg); }
/* Ungrouped-Row hat weder Pin- noch Drag-Handle — der Ratio-Button ist
   dort der erste rechte Button und braucht seinen eigenen Schub ans
   rechte Ende (Collapse-Button folgt einfach direkt danach). */
.world-group-row--ungrouped .world-group-ratio-btn { margin-left: auto; }

.world-group-row[data-group-id] {
  transition: opacity .12s;
  border-top: 1px dashed #88888866;
}
.world-group-row.world-group-dragging { opacity: .4; }
.world-group-row.drop-group-before { box-shadow: inset 0 2px 0 var(--amber); }
.world-group-row.drop-group-after { box-shadow: inset 0 -2px 0 var(--amber); }

.move-to-world-menu button,
.world-group-kebab-menu button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.move-to-world-menu button svg,
.world-group-kebab-menu button svg { width: 12px; height: 12px; margin-right: 6px; vertical-align: -2px; color: var(--ink-faint); }
.move-to-world-menu button:hover svg,
.world-group-kebab-menu button:hover svg { color: var(--amber); }
.move-to-world-menu .menu-danger:hover,
.world-group-kebab-menu .menu-danger:hover { color: var(--red, #d04040); }
.move-to-world-menu .menu-danger:hover svg,
.world-group-kebab-menu .menu-danger:hover svg { color: var(--red, #d04040); }
.move-to-world-menu button[disabled] { color: var(--ink-faint); opacity: .5; cursor: default; }

/* Breadcrumb: optionales WORLD-Segment ganz vorne (Board.groupId
   gesetzt) — dezenter als der Board-Name selbst, um die Hierarchie
   (Ordner > Board) erkennbar zu machen. */
.topnav-group-name { color: var(--board-ink-faint); }

.lp-empty-hint {
  font-family: var(--f-data);
  font-size: 10.5px;
  color: var(--ink-faint);
  line-height: 1.5;
  padding: 4px 0 14px;
}

/* ── BOARD Setting (BOARDS-Panel) ─────────────────────────────
   docs/setzz-board-customizing-notes.md — Custom Background Color
   (Light/Dark, je Farbe+Transparenz) und vier Gap-Stufen
   (CARD/COL/SET/ROW, je Small/Medium/Large). ── */
.lp-board-setting { padding: 4px 0 16px; display: flex; flex-direction: column; gap: 2px; }
/* NEU: BOARD SETTINGS-Sections als Toggles (auf/zu) — alle standardmäßig
   geschlossen bei jedem frischen Öffnen des Panels (siehe
   boardSettingsSectionsOpen, app.js). */
.lp-bs-section { border-bottom: 1px solid var(--line-soft); }
.lp-bs-section:first-child { border-top: 1px solid var(--line-soft); }
.lp-bs-section-header {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 2px 2px;
  background: transparent;
  font-family: var(--f-display);
  /* TOT (belegt 2026-08-20): greift nicht — ".left-panel button,
     .floating-panel button" (spezifischer) ueberschreibt auf 14px/500.
     .lp-bs-section-header wird ausschliesslich innerhalb von .left-panel
     (angedockt) oder .floating-panel (schwebend) gerendert, es gibt
     keinen dritten Kontext — die Angabe kann also nie gewinnen. Bewusst
     stehen gelassen statt entfernt: faellt die Button-Regel je weg,
     wird diese hier wieder wirksam. Siehe docs/workflow-learn-and-tipps.md,
     "Der Wert im Quelltext ist nicht der gerenderte Wert". */
  font-size: 13px;
  color: var(--ink-dim);   /* GEÄNDERT (2026-08-12, Nutzer-Vorgabe, Runde 2): war --ink —
     geschlossen dezenter, damit "was gerade gilt, ist heller" auch hier
     gilt. Kontrast auf --bg (angedockt) ~5.2:1, auf --bg-deep
     (schwebend) ~5.6:1 — beide über AA (4.5:1). */
  cursor: pointer;
}
/* NEU (2026-08-12, Nutzer-Vorgabe, Runde 2): offener Abschnitt hebt
   seinen eigenen Titel auf --ink-hi — betrifft alle 6 Toggle-Titel
   dieses Panels (Choose a THEME, BOARD Colors, BOARD Paddings,
   META Colors, SET Styles, CARD Style Custom), sie teilen sich
   ausschließlich diese eine Klasse. */
.lp-bs-section.open .lp-bs-section-header { color: var(--ink-hi); }
/* NEU (2026-07, Nutzer-Wunsch): kein Hover-Hintergrund auf diesem
   speziellen Button — überschreibt die globale, spezifitätslose
   :where(.left-panel) button:hover-Regel oben. */
.lp-bs-section-header:hover { background: transparent; }
.lp-bs-section-chevron { display: flex; transition: transform .15s; color: var(--ink-faint); }
.lp-bs-section-chevron svg { width: 18px; height: 18px; }
.lp-bs-section.open .lp-bs-section-chevron { transform: rotate(180deg); }
.lp-bs-section-body {
  display: none;
  flex-direction: column;
  gap: 2px;
  padding: 0 2px 14px;
}
.lp-bs-section.open .lp-bs-section-body { display: flex; }
/* NEU: "SETTINGS Open/Save" ist immer sichtbar, kein Auf-/Zuklappen —
   Header sieht optisch identisch aus, ist aber nicht klickbar (kein
   Chevron, kein Pointer-Cursor). */
.lp-bs-section-header-static { cursor: default; }
.lp-bs-bg-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.lp-bs-label {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .03em;
  color: var(--ink-dim);
  flex: 1;
  min-width: 0;
}
.lp-bs-color {
  width: 22px; height: 22px;
  padding: 0;
  border: 1px solid var(--line-soft);
  background: none;
  cursor: pointer;
  flex-shrink: 0;
}
.lp-bs-color::-webkit-color-swatch-wrapper { padding: 2px; }
.lp-bs-color::-webkit-color-swatch { border: 0; }
.lp-bs-range {
  width: 72px;
  flex-shrink: 0;
  accent-color: var(--amber);
  -webkit-appearance: none;
  appearance: none;
  height: 14px;
  background: transparent;
  cursor: pointer;
}
.lp-bs-range::-webkit-slider-runnable-track { height: 2px; background: var(--line); border-radius: 1px; }
.lp-bs-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--amber);
  margin-top: -4px;
  cursor: pointer;
}
.lp-bs-range::-moz-range-track { height: 2px; background: var(--line); border-radius: 1px; }
.lp-bs-range::-moz-range-thumb { width: 10px; height: 10px; border: 0; border-radius: 50%; background: var(--amber); cursor: pointer; }
.lp-bs-pct {
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  width: 34px;
  flex-shrink: 0;
  text-align: right;
  padding: 2px 3px;
  -moz-appearance: textfield;
}
.lp-bs-pct::-webkit-outer-spin-button,
.lp-bs-pct::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.lp-bs-pct:focus { outline: none; border-color: var(--amber); }
.lp-bs-sep { border-top: 1px solid var(--line-soft); margin: 2px 0; }
.lp-bs-gap-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 6px;
}
/* NEU (2026-08-19, "Wirkungslose Zeilen im Open BOARD sperren"): gleiches
   Bild wie .settings-new-field-locked (DATA-Panel) — opacity+not-allowed
   auf den Interaktionselementen, die Zeile selbst bleibt normal lesbar
   (Label nicht gedämpft, damit die Zeile in der Übersicht auffindbar
   bleibt, nur ihre Bedienung ist gesperrt). */
.lp-bs-gap-row-locked .lp-bs-seg-btn,
.lp-bs-gap-row-locked .lp-bs-gap-input,
.lp-bs-gap-row-locked .lp-bs-gap-step-btn { cursor: not-allowed; opacity: .6; }
/* NEU (2026-07, korrigiert): die drei Gap-Stufen (Small/Medium/Large)
   sind selbst editierbar — Auswahl-Buttons (.lp-bs-seg) und die
   dazugehörigen Zahlenfelder (.lp-bs-gap-custom) jetzt NEBENEINANDER
   in derselben Zeile statt untereinander (width:100% erzwang vorher
   einen Zeilenumbruch). */
.lp-bs-gap-custom { display: flex; gap: 3px; flex-shrink: 0; }
/* NEU (2026-07): Stepper-Pfeile (▲▼) neben jedem Gap-Custom-Feld, siehe
   bindGapStepper() in app.js — Klick oder Klicken+Halten ändert den Wert
   live um ±1px. Input bekommt rechts Platz (padding-right) für die
   absolut positionierten Pfeile, ohne die sichtbare Zahl zu verdecken. */
.lp-bs-gap-input-wrap { position: relative; display: flex; flex-shrink: 0; }
.lp-bs-gap-input {
  width: calc(3ch + 14px);
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-dim);
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  border-radius: 0;
  text-align: center;
  padding: 5px 9px 5px 3px;
  /* NEU (2026-08-19, "Zahlenwerte per Ziehen ändern"): ew-resize im
     Ruhezustand signalisiert "ziehbar", analog .group-factor-input —
     im Fokus (echter Klick, kein Zug, siehe bindGapScrub() in app.js)
     zeigt der Cursor stattdessen "text", dieselbe Begründung wie dort:
     die Zahl kann beides, aber nicht beides gleichzeitig anzeigen. */
  cursor: ew-resize;
}
.lp-bs-gap-input:focus { outline: none; border-color: var(--amber); cursor: text; }
/* Während des Zugs bekommt das Feld KEINEN nativen Fokus (bindGapScrub()
   unterdrückt ihn per preventDefault(), damit ein Zug nicht versehentlich
   die Tastatur-Bearbeitung öffnet) — ohne eigene Regel bliebe das
   gezogene Feld optisch nicht von den anderen 20 zu unterscheiden. */
.lp-bs-gap-input.gap-dragging { border-color: var(--amber); color: var(--ink); }
.lp-bs-gap-stepper {
  position: absolute;
  top: 1px; right: 1px; bottom: 1px;
  width: 8px;
  display: flex;
  flex-direction: column;
}
.lp-bs-gap-step-btn {
  flex: 1;
  min-height: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lp-bs-gap-step-btn svg { width: 6px; height: auto; display: block; }
.lp-bs-gap-step-btn:hover { color: var(--amber); }
/* NEU: SAVE SETTINGS + Reset stehen jetzt nebeneinander in einer Zeile
   (Reset links, Save rechts), statt Save allein rechtsbündig. */
.lp-bs-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 4px;
}
/* NEU (2026-07): Reset/SAVE stehen jetzt ausserhalb aller Toggle-
   Sektionen ganz unten im Panel (siehe renderSetsPanel()) — brauchen
   dort etwas Luft und eine Trennlinie nach oben, damit sie nicht wie
   ein Teil der letzten Sektion wirken.
   GEÄNDERT (2026-08-13): die Trennlinie liegt jetzt vor .lp-bs-theme-
   update-row (die neue, oberste der drei immer sichtbaren Zeilen) —
   hier bleibt nur noch ein kleiner Abstand zu dieser Zeile, kein
   zweiter Strich zwischen zwei Zeilen, die inhaltlich zusammengehören
   (beides ausserhalb der Toggle-Sektionen). */
.lp-bs-actions-row-footer {
  margin-top: 8px;
}
/* NEU (2026-08-13): UPDATE THEME zieht aus der Themes-Sektion hierher
   (siehe renderSetsPanel()) — eigene Zeile statt Eingliederung in die
   Fussleiste selbst, weil RESET/SAVE die Board-Settings betreffen und
   UPDATE THEME ein Theme; drei Knoepfe fuer drei verschiedene Dinge
   sollen nicht wie eine Aktionsgruppe wirken. Rechtsbuendig direkt
   ueber SAVE (justify-content statt space-between — nur ein Kind). */
.lp-bs-theme-update-row {
  justify-content: flex-end;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung (inkl. Rot-Hover) entfernt, jetzt .btn/.btn-sm. RESET wirft
   nur unbestätigte lokale Änderungen weg, kein gespeicherter Datenverlust
   — bewusst kein .btn-danger (sparsam damit, wie bei extra). */
/* Rolle: Hauptaktion (2026-08-12, Sitzung A / Etappe 4). Trifft sowohl
   den literalen "SAVE"-Knopf (data-board-settings-save) als auch
   "APPLY TO N BOARDS" (data-theme-apply-all, .lp-theme-spread-btn) —
   beide sind Nutzer-Entscheidung 2026-08-12 gleich behandelt: jeweils
   die einzige Aktion in ihrer Zeile, beide Hauptaktion ihres Kontexts.
   Ersetzt die bisherige theme-bedingte Umkehr (Light schwarz/Dark
   weiss) durch die Token-Werte — dieselbe Vereinfachung wie bei
   .rp-save unten: --btn-primary-bg/-fg sind absichtlich nur EINMAL in
   :root gesetzt, bleiben dadurch in beiden Themes gleich.
   GEAENDERT (2026-08-13): Erscheinung liegt jetzt in .btn + .btn-lg,
   die Klassen stehen im Markup. Hier bleibt nur der Zeilenabstand. */
.lp-bs-save-btn {
  margin-top: 4px;
}
/* NEU: "SET Style" — Default-Style-Auswahl (None/Border/Background)
   für neu erstellte SETs, mit kleiner Miniatur-Vorschau je Option. */
.lp-bs-substep-title { padding-top: 4px !important; }
.lp-bs-style-hint {
  font-family: var(--f-data);
  font-size: 9px;
  color: var(--ink-faint);
  margin: -4px 0 8px;
}
.lp-bs-style-row { display: flex; gap: 14px; margin-bottom: 4px; }
.lp-bs-style-opt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  font-family: var(--f-data);
  font-size: 9px;
  color: var(--ink-faint);
  text-align: center;
}
.lp-bs-style-opt input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.lp-bs-style-thumb {
  width: 64px;
  height: 44px;
  border-radius: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border: 1px solid var(--line-soft);
  transition: border-color .12s, box-shadow .12s;
}
.lp-bs-style-thumb i { display: block; width: 20px; height: 26px; border-radius: 1px; background: var(--panel); }
.lp-bs-style-thumb-none { background: transparent; }
.lp-bs-style-thumb-border { background: transparent; border: 2px dashed var(--ink-faint); }
.lp-bs-style-thumb-background { background: var(--line-soft); border-color: transparent; }
.lp-bs-style-opt input:checked + .lp-bs-style-thumb { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber); }
.lp-bs-style-opt input:checked ~ .lp-bs-style-name { color: var(--amber); }
.lp-bs-seg {
  display: flex;
  border: 1px solid var(--line-soft);
  flex-shrink: 0;
}
.lp-bs-seg-btn {
  width: 26px;
  padding: 4px 0;
  text-align: center;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
  border-right: 1px solid var(--line-soft);
  transition: color .12s, background .12s;
}
.lp-bs-seg-btn:last-child { border-right: 0; }
.lp-bs-seg-btn:hover { color: var(--ink); }
.lp-bs-seg-btn.active { color: var(--bg); background: var(--amber); }

/* ── Right Panel ────────────────────────────────────────── */
.right-panel {
  position: fixed;
  right: 0; top: 0; bottom: 0;
  width: var(--right-panel-w);
  /* GEÄNDERT (2026-08-15, Nutzer-Vorgabe/SimplifyerX): war --bg-deep, ohne
     erkennbaren Kommentar/Absicht an dieser Stelle (nachgeprueft — keine
     Begruendung im Verlauf gefunden). Jetzt --bg, damit das Detail-Panel
     dieselbe Grundflaeche traegt wie der Rest des Tools. Geprueft, was das
     NICHT beruehrt: .lp-asset-row-revealed (die vom Detail-Panel aus per
     "Show" hervorgehobene Zeile) nutzt --bg-deep als EIGENE, unveraenderte
     Deklaration in einem komplett anderen DOM-Bereich (Assets-Manager-
     Liste, nicht das Detail-Panel) — der Token selbst aendert sich hier
     nicht, nur WAS das Detail-Panel referenziert. Bleibt also unberuehrt,
     ebenso .lp-asset-row-selected (eigener Token --asset-row-selected-bg).
     Dark-Theme-Auswirkung gemessen: --ink-dim auf --bg statt --bg-deep
     5.63:1 -> 5.21:1 (weiterhin komfortabel ueber 4.5:1), Kontrast der
     weissen Feld-Gruppe gegen die Wrapper-Flaeche 1.30:1 -> 1.20:1 (etwas
     leiser, nichts wird unsichtbar) — bewusst fuer BEIDE Themes, nicht nur
     Light: keine der beiden Aenderungen bricht etwas. */
  background: var(--bg);
  border-left: 1px solid var(--line-soft);
  overflow: hidden;
  transition: transform .2s ease;
  display: flex;
  flex-direction: column;
  z-index: 20;
  transform: translateX(100%);
  box-shadow: -4px 0 16px rgba(0,0,0,.18);
}
.right-panel.open { transform: translateX(0); }
/* NEU (2026-07, docs/setzz-asset-export-notes.md): Anfasser am linken
   Rand des Detail-Panels — gleiches Muster wie .col-resize (Hitbox +
   sichtbare Linie per ::after), nur links statt rechts. Maximalbreite
   wird per JS geclampt (bindRightPanelResize(), app.js), nicht per CSS. */
.right-panel-resize {
  position: absolute;
  top: 0; bottom: 0; left: -5px;
  width: 9px;
  cursor: ew-resize;
  z-index: 21;
  touch-action: none;
}
.right-panel-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  background: transparent;
  transition: background .15s;
}
.right-panel-resize:hover::after,
.right-panel.resizing .right-panel-resize::after { background: var(--amber); }
.right-panel.resizing { transition: none; user-select: none; }
.right-panel-head {
  position: relative;   /* NEU (2026-08-19, "acht Korrekturen", Punkt 1): Anker für .rp-head-nav */
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--line-soft);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.right-panel-level {
  font-size: 13px;   /* GEÄNDERT (2026-08-12): war 16px */
  letter-spacing: 0em;
  text-transform: uppercase;
  color: var(--ink-dim);   /* GEÄNDERT: war --ink */
  font-family: var(--f-data);
  flex-shrink: 0;
  display: flex;
  align-items: center;
}
.right-panel-level svg { width: 18px; height: 18px; }
/* breadcrumb in head: col-name › panel-name */
.rp-head-breadcrumb {
  display: none;   /* NEU (2026-07): ausgeblendet — Navigation läuft weiterhin über Klicks im Board selbst */
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  flex: 1;
  min-width: 0;
}
.rp-head-bc-item {
  font-family: var(--f-display);
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
  opacity: 0.6;
  white-space: nowrap;
}
.rp-head-bc-item.clickable { cursor: pointer; }
.rp-head-bc-item.clickable:hover { opacity: 1; text-decoration: underline; }
.rp-head-bc-sep { color: var(--line); font-size: 11px; flex-shrink: 0; }
/* NEU (2026-07): eigene Aktions-Gruppe (Download + Schließen), fest
   an den rechten Rand gedrückt (margin-left:auto) — unabhängig davon,
   ob die (jetzt ausgeblendete) Breadcrumb Platz beansprucht oder nicht. */
.rp-head-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; flex-shrink: 0; }
.rp-head-download-slot:empty { display: none; }
/* NEU (2026-07): Delete- und Download-Icon im Header ausgeblendet — beide
   sitzen jetzt (auch) in der neuen "SEND ASSET"-Zeile bzw. sind dort
   redundant; Markup/Bindings bleiben unverändert, nur visuell versteckt. */
.rp-head-delete-slot,
.rp-head-download-slot {
  display: none;
}
.rp-head-download {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border-radius: 0;
  color: var(--ink-faint);
  transition: color .12s;
}
.rp-head-download:hover { color: var(--amber); }
.rp-head-download svg { width: 18px; height: 18px; }
/* NEU (2026-07): "Delete Upload" jetzt als Icon im Header, links vom
   Download-Icon, statt als voller Button unten im Panel. */
.rp-head-delete {
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .12s;
}
.rp-head-delete:hover { color: var(--red, #d04040); }
.rp-head-delete svg { width: 18px; height: 18px; }
.right-panel-title { display: none; }  /* replaced by rp-head-breadcrumb */

/* ── Rolle: Schliessen ────────────────────────────────────────────
   NEU (2026-08-12, Sitzung A / Etappe 3 des Button-System-Umbaus).
   Sechs Klassen, eine Rolle: .right-panel-close (Detail-Panel),
   .lp-panel-close-x (Panel-Kopf), .lightbox-close, .md-import-close,
   .ref-picker-close, .crop-close (Dialoge). Bisher vier verschiedene
   Kantenlaengen (15x17 / 24x24 / 32x32 / 34x34) und zwei verschiedene
   Ruhefarben (--ink-faint / --ink-dim) — durch einen gemeinsamen
   Selektor ersetzt, damit eine kuenftige Groessenaenderung
   STRUKTURELL alle sechs trifft, statt (wie am 20.07. bei
   .row-ctrl-btn) nur eine einzelne Klasse in einem Sammel-Commit.

   32px statt 24px: die vier Dialoge (crop/md-import/ref-picker) sind
   mit 540-560px BREITER als das angedockte Panel (440px) — die
   Vermutung "Dialog ist das kleinere Ding, verdient also den
   kompakten Knopf" haelt der Messung nicht stand (Nutzer-Entscheidung
   2026-08-12, auf dieser Messung basierend). Dazu kommt: Schliessen
   ist der Knopf, den man am beilaeufigsten trifft — das spricht fuer
   die groessere Flaeche, nicht die kleinere.

   .right-panel-close hatte bisher GAR KEINE Groessenregel — 15x17px
   waren reines Textmetrik-Ergebnis (Etappe-0-Fund, siehe
   docs/baselines/buttons.json). Bekommt hier zum ersten Mal eine
   echte Groesse. */
.right-panel-close,
.lp-panel-close-x,
.lightbox-close,
.md-import-close,
.ref-picker-close,
.crop-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-size);
  height: var(--btn-size);
  border-radius: 0;
  background: none;
  border: 0;
  color: var(--ink-faint);
  font-size: 16px;
  cursor: pointer;
  transition: color .12s;
}
.right-panel-close:hover,
.lp-panel-close-x:hover,
.lightbox-close:hover,
.md-import-close:hover,
.ref-picker-close:hover,
.crop-close:hover {
  color: var(--amber);
}
/* AUSNAHME: .lightbox-close behaelt einen Rahmen — als einziger der
   sechs sitzt er frei ueber beliebigem Bildinhalt (position:absolute
   im Lightbox-Overlay), nicht auf bekanntem App-Chrome. Ohne Kante
   koennte das Kreuz gegen ein helles oder texturiertes Bild
   verschwinden; die anderen fuenf stehen auf der eigenen, bekannten
   Panel-/Dialogflaeche und brauchen das nicht. */
.lightbox-close { border: 1px solid var(--line); }
.lightbox-close:hover { border-color: var(--amber); }

/* Unique Restwerte je Klasse — nur, was NICHT zur gemeinsamen
   Erscheinung gehoert (Position, Abstand zu Nachbarelementen). */
.right-panel-close { flex-shrink: 0; margin-top: 1px; }

/* name block below the first grey line in the body */
.rp-name-block {
  padding-bottom: 6px;
  margin-bottom: 1px;
  border-bottom: 0px solid var(--line-soft);
}
.rp-name-custom {
  font-family: var(--f-display);
  font-size: 22px;
  font-weight: 500;
  color: var(--ink-hi);   /* GEÄNDERT (2026-08-12): war --ink — primäre Überschrift im Fokus */
  line-height: 1.2;
  margin-bottom: 3px;
}
.rp-name-original {
  font-family: var(--f-display);
  font-size: 11px;
  color: var(--ink-faint);
  font-weight: 400;
}
/* GEÄNDERT (2026-08-13): unten war 0 — der ehemalige SAVE-Balken sass
   selbst im Scrollbereich und brauchte keinen Abstand zu sich selbst.
   Jetzt sitzt SAVE im eigenen .rp-footer AUSSERHALB dieses Bereichs;
   ohne Puffer wuerde das letzte Feld (Notes/Custom) direkt an dessen
   obere Trennlinie stossen. */
/* GEÄNDERT (2026-08-19, "acht Korrekturen", Punkt 6): padding-top war
   5px (Teil des 5px 5px 10px-Shorthands) — auf 2px verkürzt, um den
   Abstand zwischen Kopfzeile und der ersten Zeile im Body (Icon-Zeile
   .rp-send-asset bzw. bei der Panel-Summary-Ansicht deren erster
   Block) zu straffen. Nicht auf 0px: die Kopfzeile trägt bereits
   padding-bottom:8px (siehe .right-panel-head) UND eine
   border-bottom-Linie — 0px direkt darunter ließe den Inhalt an der
   Linie kleben, 2px lässt sie erkennbar abgesetzt. */
.right-panel-body { flex: 1; overflow-y: auto; padding: 2px 5px 10px; scrollbar-width: none; display: flex; flex-direction: column; }
.right-panel-body::-webkit-scrollbar { display: none; }

/* ── Detail-Panel-Footer (NEU 2026-08-13, Nutzer-Vorgabe) ──────────
   Denselben Zweck wie .lp-assets-bulk-toolbar (Leiste am unteren
   Panelrand mit Aktions-Knoepfen), aber NICHT dieselbe Bauart — siehe
   Bestandsaufnahme: .lp-assets-bulk-toolbar ist position:absolute und
   nutzt .left-panels position:fixed als Containing Block, dazu eine
   Sichtbarkeits-Klasse fuer das Ein-/Ausfahren bei Auswahl.
   .right-panel ist schon eine feste Flex-Spalte — der Footer ist
   hier einfach ein normales, nicht schrumpfendes Flex-Kind NACH
   .right-panel-body, dauerhaft sichtbar, kein Absolute-Trick, keine
   Animation noetig. Optik (Panel-Hintergrund, Trennlinie, Schatten
   nach oben) bewusst gleich wie beim Assets-Manager-Footer, damit
   beide als dieselbe Bauform erkennbar bleiben. */
.rp-footer {
  flex-shrink: 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
  box-shadow: 0 -4px 16px rgba(0,0,0,.18);
}
/* [hidden] allein reicht nicht: die eigene display:flex-Regel oben hat
   als Autor-CSS Vorrang vor dem UA-Stylesheet, das [hidden] sonst
   ausblendet. */
.rp-footer[hidden] { display: none; }

/* right panel form elements */
.rp-field { margin-bottom: 1px; }
.rp-label { font-size: 9px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-dim); font-family: var(--f-data); margin-bottom: 4px; }
/* GEÄNDERT (2026-08-12, Nutzer-Vorgabe): die permanente untere Linie fiel
   weg ("zu viel") — Felder sind im Ruhezustand jetzt unauffällig, zeigen
   sich aber beim Überfahren (Hintergrund minimal heller, dezent) und
   tragen weiterhin eine Amber-Linie im Fokus (transparent→amber, kein
   Sprung), sonst wäre "wo tippe ich gerade" nicht mehr erkennbar. */
.rp-input { width: 100%; background: var(--field-bg); border: 0; border-bottom: 1px solid transparent; color: var(--ink-dim); font-family: var(--f-label); font-weight: 500; font-size: 12px; padding: 8px 4px; outline: none; transition: background .12s, border-color .12s; }
.rp-input:hover { background: var(--field-hover-bg); }
.rp-input:focus { border-bottom-color: var(--amber); }
.rp-select { width: 100%; background: var(--bg-deep); border: 0; border-bottom: 1px solid var(--line); color: var(--ink); font-family: var(--f-data); font-size: 10px; padding: 5px 0; outline: none; cursor: pointer; }
.rp-textarea { width: 100%; background: var(--field-bg); border: 0; border-bottom: 1px solid transparent; color: var(--ink); font-family: var(--f-label); font-weight: 500; font-size: 12px; padding: 8px 4px; min-height: 72px; resize: none; outline: none; transition: background .12s, border-color .12s; }
.rp-textarea:hover { background: var(--field-hover-bg); }
/* GEÄNDERT (2026-08-19, "fünf Nachbesserungen", Punkt 5): war 200px.
   resize:vertical bleibt — wer mehr Platz braucht, zieht selbst grösser,
   das Feld merkt sich nichts (native Browser-resize, kein eigener
   Zustand). Startwert per Messung gewählt, siehe Nachmessung im
   Untersuchungsbericht: Ziel war, dass das Feld bei 768px Fensterhoehe
   (haeufigstes Notebook-Mass) trotz des zurueckgeholten
   "Usage & References"-Abschnitts (Punkt 3, ~48px zugeklappt) wieder
   VOLLSTAENDIG sichtbar ist, ohne unter die Zwei-Zeilen-Schwelle zu
   fallen, vor der SimplifyerX warnte. */
.rp-textarea.rp-prompt { min-height: 140px; resize: vertical; padding: 15px; }
.rp-textarea:focus { border-bottom-color: var(--amber); }
/* NEU (2026-07): Feld-Beschriftung liegt jetzt IM Eingabefeld (wie ein
   Platzhalter) statt darüber, und blendet beim Klick ins Feld aus —
   rein per CSS über :placeholder-shown (jedes Feld bekommt dafür ein
   leeres placeholder=" ", damit die Pseudoklasse greift), keine neue
   JS-Logik nötig. Bleibt auch ausgeblendet, sobald ein Wert eingetragen
   ist (:not(:placeholder-shown) matched dann nicht mehr). */
.rp-field-floating { position: relative; }
/* GEÄNDERT (2026-08-19, "acht Korrekturen", Punkt 7): war left:4px,
   font-size:11px — deckungsgleich mit dem padding-left:4px von
   .rp-input/.rp-textarea, dadurch dicht am Feldrand. left auf 9px, um
   erkennbar Luft zum Rand zu schaffen (die Eingabe selbst bleibt bei
   4px padding-left, das betrifft nur den Platzhalter-Zustand — sobald
   fokussiert/befüllt, blendet das Label ohnehin aus, siehe
   :not(:placeholder-shown) unten, keine Verschiebung im Tippzustand).
   font-size 11→13px. */
.rp-label-overlay {
  position: absolute;
  left: 9px;
  top: 14px;
  font-size: 13px;
  color: var(--ink-faint);
  /* GEÄNDERT (2026-08-12, Nutzer-Vorgabe): war --f-label (Mono) — identisch
     mit .rp-input/.rp-textarea, kein Unterschied zum Eingabetext. Jetzt
     umgekehrt: Platzhalter in --f-display (Rajdhani, UI-Beschriftung),
     Eingabetext bleibt --f-label (Mono, wie echte Werte überall sonst).
     Die Unterscheidung liegt jetzt in der Schriftart statt in der Farbe —
     bleibt so auch erhalten, wenn Kontraste später neu justiert werden. */
  font-family: var(--f-display);
  text-decoration: none;
  pointer-events: none;
  transition: opacity .12s;
}
.rp-field-floating .rp-input ~ .rp-label-overlay { top: 50%; transform: translateY(-50%); }
.rp-input:focus ~ .rp-label-overlay,
.rp-input:not(:placeholder-shown) ~ .rp-label-overlay,
.rp-textarea:focus ~ .rp-label-overlay,
.rp-textarea:not(:placeholder-shown) ~ .rp-label-overlay { opacity: 0; }
/* Rolle: Hauptaktion (2026-08-12, Sitzung A / Etappe 4). Trifft ALLE
   vier Knoepfe unter dieser Klasse gleich: "Save" (Asset-Detail),
   "Save name", "Save email", "Change password" (Account-Settings) —
   Nutzer-Entscheidung 2026-08-12: alle vier sind Hauptaktionen ihres
   jeweiligen Kontexts, zwei Speichern-Knoepfe in unterschiedlicher
   Optik war genau das Problem, das diese Etappe loest.

   GEAENDERT (2026-08-13): Erscheinung liegt jetzt komplett in
   .btn + .btn-lg (siehe Button-Basisklassen oben) — die Knoepfe
   tragen die Klassen im Markup. Hier bleibt nur, was NICHT die
   Variante/Groesse ist, sondern die Platzierung im Panel-Fuss:
   volle Breite, unten klebend. Large ist Nutzer-Vorgabe
   2026-08-13: die Wichtigkeit von SAVE traegt die Groesse, nicht
   eine eigene Variante. */
.rp-save {
  width: 100%;
  margin-top: auto;
  position: sticky;
  bottom: 0;
}
/* NEU: Settings-Panel — Eingabefeld + Save-Button nebeneinander statt
   untereinander (Name-Zeile / Passwort-Zeile). Panel ist jetzt doppelt
   so breit (.left-panel[data-panel-target="settings"]), daher passt
   das nebeneinander. .rp-field wächst, der Button behält seine
   Eigenbreite und richtet sich an der Unterkante des Eingabefelds aus. */
.settings-inline-row { display: flex; align-items: flex-end; gap: 8px; }
.settings-inline-row .rp-field { flex: 1; min-width: 0; }
.settings-inline-save {
  position: static;
  width: auto;
  flex-shrink: 0;
}
.rp-summary { background: var(--panel-2); padding: 8px 10px; margin-bottom: 12px; }
.rp-summary-row { display: flex; justify-content: space-between; align-items: center; padding: 3px 0; border-bottom: 1px solid var(--line-soft); font-size: 10px; }
.rp-summary-row:last-child { border: none; }
.rp-summary-key { color: var(--ink-faint); font-family: var(--f-data); }
.rp-summary-val { color: var(--ink); font-family: var(--f-data); font-weight: 500; }
.rp-prompt-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); margin-left: 4px; vertical-align: middle; }
.rp-prompt-footer { margin: -8px 0 10px; }
.rp-breadcrumb { display: none; }  /* replaced by rp-head-breadcrumb + rp-name-block */
.rp-bc-link { color: var(--amber); cursor: pointer; font-size: 15px; font-weight: 700; }
.rp-bc-link:hover { text-decoration: underline; }
.rp-checkrow { display: flex; gap: 12px; padding: 8px; justify-content: flex-end; }
.rp-check { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--ink-dim); font-family: var(--f-data); cursor: pointer; }
/* NEU (2026-08-19, "acht Korrekturen", Punkt 4): AI/Real eigens gestaltet
   statt der geteilten `input[type="checkbox"] { accent-color: ... }`-
   Regel (styles.css, siehe dort) — accent-color lässt weder eckige Ecken
   noch eine erzwungene Häkchenfarbe zu, das ist Sache des Browsers.
   GEPRÜFT, ob das die sieben anderen accent-color-Fundstellen mitzieht:
   NEIN — bewusst über .rp-checkrow gescoped (trägt nachweislich NUR die
   AI/Real-Zeile, ein einziges Vorkommen im ganzen app.js geprüft), NICHT
   über die geteilte .rp-check-Klasse, die AUCH die Custom-Detail-Feld-
   Checkbox trägt (eine der sieben anderen Stellen) und sonst
   versehentlich mitgezogen worden wäre. Die anderen sieben bleiben
   native accent-color-Checkboxen (rund, Browser-Haken). ANZUMERKEN, nicht
   entschieden: das ergibt jetzt zwei verschiedene Checkbox-Bauformen im
   selben Panel (AI/Real eckig+eigener Haken, das Custom-Feld-Checkbox-
   Feld an anderer Stelle im selben Panel weiterhin rund+nativ) — siehe
   Bericht.
   appearance:none statt einer dritten Bauform: derselbe Ansatz wie die
   .lp-bs-range-Regler (Slider) an anderer Stelle, kein neues Muster.
   Haken per zwei rotierten Rahmenkanten (CSS-only, kein Icon/Bild-Asset
   nötig) — die im Haus übliche Vektor-Optik (stroke-linecap:square) in
   reinem CSS nachgebildet. */
.rp-checkrow input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 14px; height: 14px;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--field-bg);
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
}
.rp-checkrow input[type="checkbox"]:hover { border-color: var(--ink-dim); }
.rp-checkrow input[type="checkbox"]:checked {
  background: var(--amber-hi);
  border-color: var(--amber-hi);
}
.rp-checkrow input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 4px; top: 1px;
  width: 4px; height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
/* NEU (2026-08-16, KI-Label Etappe 1): sichtbar abgesetzt von der AI/Real-
   Zeile darueber — eigene Trennlinie statt derselben rechtsbuendigen
   .rp-checkrow, weil AI/Real beschreiben, was das Bild IST, diese
   Markierung dagegen bestimmt, was bei der AUSGABE passiert (siehe
   docs/setzz-ai-label-notes.md). Linksbuendig, volle Breite — bewusst
   keine zweite Checkbox daneben, es ist ein Einzelschalter. */
/* GEÄNDERT (2026-08-20, "Nachtrag zwei Padding-Werte"): war 8px 8px 4px —
   Nutzer-Vorgabe, kein gemessener Bezug zu einer anderen Stelle. */
.rp-label-output-field { border-top: 1px solid var(--line-soft); padding: 8px 0px 4px; }
.rp-label-output-field .rp-check { font-size: 11px; color: var(--ink-hi); }

/* NEU (2026-08-16, KI-Label Etappe 3b): die Checkbox ist einer Auswahl
   gewichen (Knopf + Popover). Warum kein <select>: ein <option> kann
   kein Bild tragen — siehe Kommentarblock an renderLabelPickField()
   (app.js). */
.rp-label-pick-caption {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 5px;
}
/* GEÄNDERT (2026-08-20, "drei Korrekturen", Punkt 3): war 4px — eine
   der wenigen Stellen im Tool, die von der im Kopf dieser Datei
   festgehaltenen Designsprache abweichen ("hairline borders, 0px
   radius"). Betraf den Knopf UND das Popover dahinter (.rp-label-opt
   unten, dieselbe alte 4px) — beide auf 0. */
.rp-label-pick-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 0;
  cursor: pointer;
  text-align: left;
}
.rp-label-pick-btn:hover { border-color: var(--ink-dim); }
/* Ein Label, das die Bibliothek nicht mehr kennt: der Export bricht bei
   diesem Asset ab (bewusst, siehe label-library-access.php). Das soll
   man vorher sehen und nicht erst am abgebrochenen Export. */
.rp-label-pick-btn.is-missing { border-color: var(--red, #d04040); }
.rp-label-pick-btn.is-missing .rp-label-pick-name { color: var(--red, #d04040); }
.rp-label-pick-name {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  color: var(--ink-hi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rp-label-pick-caret { display: inline-flex; flex-shrink: 0; opacity: .6; }
.rp-label-pick-caret svg { width: 12px; height: 12px; }

/* Label sind transparente PNG/WEBP. Ohne eigene Flaeche unter der
   Miniatur waere ein helles Label auf hellem Panel unsichtbar —
   deshalb dieselbe --panel-2-Unterlage wie in der Bibliothek-Liste. */
.rp-label-pick-thumb,
.rp-label-opt-thumb {
  width: 34px;
  height: 22px;
  object-fit: contain;
  background: var(--panel-2);
  border-radius: 3px;
  flex-shrink: 0;
}
.rp-label-pick-btn .rp-label-pick-thumb { background: var(--panel); }
.rp-label-pick-thumb.is-empty,
.rp-label-opt-thumb.is-empty {
  display: inline-block;
  border: 1px dashed var(--line);
  background: transparent;
}

/* position:fixed wie .mosaic-popover — deshalb ist die Panelbreite
   (315 px nutzbar) keine Schranke fuer diese Liste. */
.rp-label-popover {
  position: fixed;
  z-index: var(--z-popover-menu);
  display: none;
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.rp-label-popover.open { display: block; }
.rp-label-popover::-webkit-scrollbar { width: 6px; }
.rp-label-popover::-webkit-scrollbar-track { background: transparent; }
.rp-label-popover::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.rp-label-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 0;
  cursor: pointer;
  text-align: left;
}
.rp-label-opt:hover { background: var(--panel); }
/* BUGFIX (2026-08-17, gefunden beim 3d-Test): stand seit Etappe 3b auf
   var(--accent) — DEN TOKEN GIBT ES IN DIESEM PROJEKT NICHT (der Akzent
   heisst --amber). Derselbe Fehlgriff wie seinerzeit beim
   Fortschrittsbalken, siehe den Kommentar bei .restore-progress-bar.

   WIRKUNG, genau: ein var() auf einen undefinierten Token ist "invalid
   at computed-value time"; border-color faellt dann auf seinen
   Anfangswert currentColor zurueck. Der gewaehlte Eintrag bekam also
   einen GRAUEN Rahmen in Textfarbe (auf Staging gemessen:
   rgb(201,201,201)) statt des Akzents. Er war damit vom nicht
   gewaehlten (transparenter Rahmen) durchaus zu unterscheiden — nur
   eben in der falschen Farbe, nicht im Akzent wie ueberall sonst.
   Kein unsichtbarer Zustand, aber ein stiller Stilbruch. */
.rp-label-opt.is-active { border-color: var(--amber); }
.rp-label-opt.is-archived { opacity: .55; }
.rp-label-opt-name {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  color: var(--ink-hi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ══ Gemeinsames Auswahl-Popover (2026-08-20, Etappe 1) ═══════════════
   Gehoert zu openSuggestPopover() in app.js — siehe den ausfuehrlichen
   Kommentar dort. DIES IST DIE GEMEINSAME REGEL: Etappe 2 haengt 19
   weitere Felder an dasselbe Bauteil, ohne hier etwas zu kopieren.
   Alle Farben ueber Tokens, damit sich das Aussehen spaeter an EINER
   Stelle global aendern laesst.

   MEHRSPALTIG statt scrollender Liste: ~20 Eintraege in einer Spalte
   sind dasselbe Bedienmuster wie die native Datalist, nur huebscher —
   in drei Spalten sind sie ein Block, den man auf einen Blick
   ueberschaut. auto-fit + minmax() nach dem Muster von
   .panel-type-rows (CARDS-Waehler, siehe dort): die Spaltenzahl
   ergibt sich aus der Breite, keine feste Zahl, keine Media Query.
   210px = laengster gemessener eingebauter Eintrag (194px) plus
   Innenabstand. */
.suggest-popover {
  position: fixed;
  z-index: var(--z-popover-menu);
  display: none;
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  /* Schatten vom Floating-Panel-Vorbild, NICHT der schlichte Rahmen des
     Label-Popovers: dieses hier schwebt ueber unvorhersehbarem
     Board-Inhalt (es ragt bewusst ueber den Panel-Rand hinaus), ein
     1px-Rahmen wuerde ueber einem hellen Board verschwimmen. */
  box-shadow: 0 8px 32px rgba(0,0,0,.28);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.suggest-popover.open {
  display: grid;
  /* GEÄNDERT (2026-08-20, Etappe 2): war minmax(210px, 1fr) — bei den
     zwei Etappe-1-Feldern nie aufgefallen, weil beide zweistellig viele
     eingebaute Eintraege tragen. Custom-Felder koennen aber sehr kurze
     Listen haben (2-3 Eintraege): die Breite des Popovers selbst
     bleibt unabhaengig von der Trefferzahl (siehe positionSuggestPopover()
     in app.js, bis zu SUGGEST_MAX_W), 1fr streckte wenige Eintraege
     dadurch zu wenigen sehr breiten Knoepfen. auto-fit loest das NICHT
     von selbst — es bestimmt nur die SpaltenZAHL aus der Breite, nicht
     die Spaltenbreite selbst. max-content statt 1fr plus
     justify-content:start: jede Spalte bleibt so breit wie ihr
     Inhalt, ungenutzter Rest bleibt schlicht leer statt verzerrt zu
     werden — bei vielen Eintraegen (das eigentliche Muster) unveraendert. */
  grid-template-columns: repeat(auto-fit, minmax(210px, max-content));
  justify-content: start;
  gap: 2px;
  align-content: start;
}
.suggest-popover::-webkit-scrollbar { width: 6px; }
.suggest-popover::-webkit-scrollbar-track { background: transparent; }
.suggest-popover::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.suggest-opt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  min-width: 0;
  padding: 5px 6px;
  background: transparent;
  border: 1px solid transparent;
  /* GEÄNDERT (2026-08-20, "drei Korrekturen", Punkt 3): war 4px, 1:1
     von .rp-label-opt uebernommen beim Bau in Etappe 1 — dieselbe
     Korrektur trifft beide gleichzeitig, siehe Kommentar dort. */
  border-radius: 0;
  cursor: pointer;
  text-align: left;
}
.suggest-opt:hover { background: var(--panel); border-color: var(--amber); }
/* Lange Eintraege abschneiden statt umbrechen — den vollen Text traegt
   das title-Attribut (siehe renderSuggestOptions()). Dasselbe Muster
   wie bei den "Used in"-Chips: der laengste gemessene Eintrag war ein
   selbst getippter (216px), nicht einer der eingebauten. */
.suggest-opt-name,
.suggest-opt-note {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suggest-opt-name { font-size: 12px; color: var(--ink-hi); }
/* Optionale zweite Zeile — in Etappe 1 nutzt sie keine Quelle, Etappe 2
   braucht sie fuer die Typography-Vorschlaege. Bewusst jetzt schon
   vorhanden statt spaeter nachgeruestet. */
.suggest-opt-note { font-family: var(--f-data); font-size: 9px; color: var(--ink-faint); }
.suggest-empty {
  grid-column: 1 / -1;
  padding: 8px 6px;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
}

/* NEU (2026-08-17, KI-Label Etappe 3c): board-eigenes Label in
   BOARD SETTINGS. Eigene Klassen statt der settings-label-*-Reihe der
   Konto-Bibliothek: dort ist es eine LISTE mit bis zu zehn Zeilen, hier
   genau eine — dieselben Klassen zu benutzen hiesse, eine Listenzeile
   fuer einen Einzelfall zu missbrauchen. */
.bs-boardlabel-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border: 1px solid var(--line-soft);
  border-radius: 4px;
}
.bs-boardlabel-thumb {
  width: 44px;
  height: 28px;
  object-fit: contain;
  /* Label sind transparente PNG/WEBP — ohne eigene Flaeche waere ein
     helles Label auf hellem Panel unsichtbar (gleiche Ueberlegung wie
     bei .rp-label-pick-thumb). */
  background: var(--panel-2);
  border-radius: 3px;
  flex-shrink: 0;
}
.bs-boardlabel-thumb.is-empty {
  display: inline-block;
  border: 1px dashed var(--line);
  background: transparent;
}
.bs-boardlabel-state {
  flex: 1;
  min-width: 0;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.bs-boardlabel-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* NEU (2026-08-17, KI-Label Etappe 3d): der Konfigurator unter dem
   Upload — Vorschau, Positionsraster, und darunter vier Zeilen im
   bereits vorhandenen .lp-bs-bg-row-Muster (Regler + %-Feld), die
   deshalb hier KEINE eigenen Regeln brauchen. */
.bs-labelpreview-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 6px;
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  /* Schachbrett als Grund: die Vorschau kann ein transparentes PNG
     zeigen, und ohne erkennbaren Untergrund waere nicht zu sehen, wo
     das Bild aufhoert und das Panel anfaengt. */
  background:
    linear-gradient(45deg, var(--panel-2) 25%, transparent 25%, transparent 75%, var(--panel-2) 75%),
    linear-gradient(45deg, var(--panel-2) 25%, transparent 25%, transparent 75%, var(--panel-2) 75%);
  background-position: 0 0, 6px 6px;
  background-size: 12px 12px;
}
.bs-labelpreview {
  display: block;
  width: 100%;
  max-width: 320px;
  height: auto;
  border-radius: 3px;
  /* Die Vorschau ist ein Abbild, kein Bedienelement. */
  pointer-events: none;
}
.bs-labelpreview-note {
  font-family: var(--f-data);
  font-size: 9px;
  line-height: 1.4;
  text-align: center;
  color: var(--ink-faint);
}

.bs-labelpos-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
}
.bs-labelpos-row .lp-bs-label { flex: 1; min-width: 0; }
.bs-labelpos-grid {
  display: grid;
  grid-template-columns: repeat(3, 22px);
  grid-template-rows: repeat(3, 22px);
  gap: 3px;
  flex-shrink: 0;
}
.bs-labelpos-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel-2);
  cursor: pointer;
}
/* Der Punkt in der Zelle sitzt dort, wo das Label sitzen wird —
   die Zelle selbst ist das Bild, der Punkt das Label. */
.bs-labelpos-cell > i {
  width: 6px;
  height: 6px;
  border-radius: 1px;
  background: var(--ink-faint);
}
.bs-labelpos-cell:hover { border-color: var(--amber-dim); }
.bs-labelpos-cell:hover > i { background: var(--ink-dim); }
/* Gewaehlter Platz: dasselbe Muster wie die SET-Style-Auswahl weiter
   oben in DIESEM Panel (.lp-bs-style-opt input:checked) — Rahmen in
   --amber plus 1px box-shadow, keine eigene Flaeche. Bewusst das
   vorhandene Haus-Idiom statt einer eigenen Formulierung. */
.bs-labelpos-cell.is-active { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber); }
.bs-labelpos-cell.is-active > i { background: var(--amber); }
/* Die Mitte ist kein Knopf, sondern eine Luecke mit Begruendung
   (siehe LABEL_POSITION_GRID in app.js) — sie wird als leeres,
   gedaempftes Feld gezeigt statt weggelassen, damit das Raster als
   3x3 lesbar bleibt. */
.bs-labelpos-cell.is-blocked {
  border-style: dashed;
  border-color: var(--line-soft);
  background: transparent;
  cursor: default;
}
.bs-labelcfg-actions {
  display: flex;
  justify-content: flex-end;
  padding-top: 6px;
}
/* GEÄNDERT (2026-08-15, Nutzer-Vorgabe/SimplifyerX, Settings-Panel-Runde):
   trug bisher eine eigene Flaeche (--panel-2, 6px/8px Polster) — neben
   Eingabefeldern sah das wie ein weiteres, fuenftes/sechstes Feld statt
   wie ein Hinweis darunter aus (gemeldeter Befund im Settings-Panel, wo
   mehrere .rp-hint direkt unter Feldern stehen; im Detail-Panel fiel es
   nicht auf, weil dort nur ein einzelnes .rp-hint am Ende steht, ohne
   direkt benachbarte Felder). Jetzt reiner Text ohne eigene Flaeche —
   kleiner und gedaempfter (--ink-faint statt --ink-dim) als Feldtext,
   damit "Hinweis" nicht mit "Feld" verwechselt wird. */
.rp-hint { font-size: 9px; color: var(--ink-faint); line-height: 1.5; font-family: var(--f-data); }

/* left-panel content areas */
.lp-section-title { font-size: 13px; font-weight: 500; letter-spacing: 0; text-transform: uppercase; color: var(--ink-dim); font-family: var(--f-data); padding: 12px 12px 8px; border-bottom: 1px solid var(--line-soft); flex-shrink: 0; }   /* GEÄNDERT (2026-08-12): war 16px/--ink — Titel sind Überschriften, kein aktiver Inhalt */
/* NEU (2026-07): Variante mit Schließen-Button rechts (aktuell nur
   Text to BOARD, da hier Dateninhalt bei versehentlichem Schließen
   verloren gehen könnte). */
/* NEU (2026-07): kompaktere Kopfzeile — weniger Innenabstand, engerer
   Abstand zwischen den Icons. Betrifft alle Panels mit dieser Klasse,
   bewusst einheitlich. */
.lp-section-title-with-close {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px 8px;
}
/* Icons rechtsbündig: der Titel schluckt den gesamten Freiraum, alle
   Knöpfe rücken dadurch als Gruppe an den rechten Rand.
   Ausgenommen die ASSETS-Kopfzeile (.lp-section-title-with-tabs) — die
   ist ein eigenes 3-Spalten-Grid, das seine Ausrichtung selbst regelt
   und durch ein auto-Margin verschoben würde. */
.lp-section-title-with-close:not(.lp-section-title-with-tabs) > :first-child { margin-right: auto; }
/* Ohne diese Rücknahme teilte sich der Freiraum 50/50 zwischen Titel
   und Schließen-X auf (zwei konkurrierende auto-Margins) — das X stünde
   dann isoliert weit rechts, getrennt von den übrigen Icons. */
.lp-section-title-with-close:not(.lp-section-title-with-tabs) .lp-panel-close-x { margin-left: 0; }
.lp-panel-close-x {
  /* Erscheinung (Groesse/Farbe/Radius) jetzt in der gemeinsamen
     Schliessen-Regel oben bei .right-panel-close. Hier nur noch, was
     NUR diese Klasse braucht: margin-left:auto statt
     justify-content:space-between auf dem Elternelement — so bleibt
     der X-Button immer ganz rechts, auch wenn (wie im ASSETS-Panel)
     noch ein drittes Element (die View-Tabs) zwischen Titel und
     Button steht. */
  margin-left: auto;
}
/* ⚠️ ASSETS IST DAS EINZIGE PANEL MIT EINER EIGENEN KOPFZEILEN-STRUKTUR.
   (NEU 2026-07, Ergänzung 2026-08-04.) Grund: nur hier gibt es einen
   dritten Bestandteil in der Titelzeile (die BROWSE/Trash-View-Tabs) —
   eigenes 3-Spalten-Grid statt des generischen Flex/gap von
   .lp-section-title-with-close oben, damit die Tabs unabhängig von der
   Titel-Textlänge wirklich MITTIG sitzen. margin-left:auto auf
   .lp-panel-close-x (siehe oben) wird hier per grid-column überschrieben,
   bleibt für alle anderen .lp-section-title-with-close-Panels aktiv.

   DIESE EINE Sonderstruktur ist auch der Grund für eine ZWEITE
   Sonderbehandlung weiter unten: als das Andock-Icon einheitlich auf
   alle schwebbaren Panels ausgerollt wurde (dockToggleMarkup(),
   app.js), fiel es hier als viertes, unpositioniertes Grid-Kind in
   Zeile 2 (Grid-Auto-Placement findet in Zeile 1 keine freie Spalte
   mehr — alle drei sind explizit belegt). Bei jedem anderen Panel
   reiht sich ein zusätzliches Geschwister im Flex einfach mit ein,
   nur hier nicht. Siehe .lp-section-title-actions unten — EINE
   Ursache, zwei Sonderfälle, nicht zwei unabhängige Zufälle. */
.lp-section-title-with-tabs {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 0;
}
.lp-section-title-with-tabs > span { grid-column: 1; }
.lp-section-title-with-tabs .lp-assets-viewtabs { grid-column: 2; justify-self: center; }
/* URSACHE (2026-08-04): dieses Grid ist FEST auf drei Spalten ausgelegt
   (Titel/Tabs/Schließen) — jede der drei hat ihre eigene explizite
   grid-column. Ein viertes Geschwisterelement OHNE eigene grid-column
   (der neu ergänzte Andock-Knopf) bekommt vom Grid-Auto-Placement keine
   freie Spalte in Zeile 1 mehr zugewiesen, alle drei sind belegt — es
   rutscht in Zeile 2, Spalte 1. Das ist eine strukturelle Eigenheit
   NUR dieser Kopfzeile: alle übrigen Panels nutzen die flexbox-basierte
   .lp-section-title-with-close, wo ein zusätzliches Geschwister einfach
   in dieselbe Zeile einreiht.
   LÖSUNG: Andock-Knopf und Schließen-X teilen sich jetzt EINEN
   Grid-Platz (Spalte 3) über einen gemeinsamen Flex-Wrapper
   (.lp-section-title-actions, siehe Markup in renderAssetsPanel()) —
   das Grid bleibt dreispaltig, der vierte Knopf sitzt INNERHALB der
   dritten Spalte statt eine vierte zu beanspruchen. */
.lp-section-title-with-tabs .lp-section-title-actions {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 2px;
}
/* NEU (2026-07): Untergliederung innerhalb eines Panels (z.B. Themen
   in der Group-Presets-Bibliothek). */
.lp-subsection-title {
  font-size: 16px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  font-family: var(--f-display);
  padding: 14px 12px 4px 2px;
}
.lp-subsection-title:first-of-type { padding-top: 10px; }
/* NEU (2026-08-03): die Titelzeile des ANGEDOCKTEN Panels ist
   Ziehfläche — losziehen macht das Panel schwebend
   (bindDockedPanelHeaderDrag(), app.js). `move` ist dieselbe Anzeige,
   die das schwebende Fenster an derselben Stelle schon hat; die
   Bedienelemente darin behalten ihren Zeiger. */
.left-panel .lp-section-title { cursor: move; }
/* NEU (2026-08-04): EXPORT/TOOLS/TEXT-TO-BOARD sind nicht schwebbar
   (NON_FLOATABLE_RAIL_TARGETS, app.js) — der Zeiger soll das nicht
   versprechen. WICHTIG: CSS kann das JS-Set nicht lesen, diese drei
   Namen müssen von Hand synchron gehalten werden. */
.left-panel[data-panel-target="export"] .lp-section-title,
.left-panel[data-panel-target="tools"] .lp-section-title,
.left-panel[data-panel-target="textboard"] .lp-section-title { cursor: default; }
.left-panel .lp-section-title button,
.left-panel .lp-section-title input,
.left-panel .lp-section-title a { cursor: pointer; }
/* NEU: "Expand/collapse all"-Icon rechts neben "Customize your BOARD" */
.lp-bs-header-row { display: flex; align-items: center; justify-content: space-between; }
.lp-bs-header-row .lp-subsection-title { padding-right: 0; }
.lp-bs-toggle-all-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-faint);
  padding: 4px;
  cursor: pointer;
  transition: color .12s;
}
.lp-bs-toggle-all-btn:hover { color: var(--amber); }
.lp-bs-toggle-all-btn svg { width: 16px; height: 16px; }
/* Werks-Presets minimal anders markiert — kein Lösch-/Umbenennen-X,
   dafür ein dezent gestrichelter Rahmen statt durchgezogen. */
.group-preset-builtin { border-style: dashed; }
.lp-scroll { flex: 1; overflow-y: auto; padding: 10px 12px; scrollbar-width: none; }
.lp-scroll::-webkit-scrollbar { display: none; }

/* ── Workspace ──────────────────────────────────────────── */
.workspace {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;   /* for zoom HUD positioning */
  /* NEU: Custom Board-Hintergrund (docs/setzz-board-customizing-notes.md)
     — garantierte Basis-Ebene (schwarz im Dark-, weiß im Light-Theme,
     siehe [data-theme="light"]-Override unten), plus die vom Nutzer
     gewählte Tint-Farbe/Transparenz als Ebene DARÜBER (--board-bg-tint,
     per JS gesetzt — applyBoardBackgroundTint(), app.js). Zwei
     background-image-Stops derselben Farbe ergeben eine deckende Fläche,
     die ganz normal alpha-über die background-color darunter blendet. */
  /* NEU: Basis ist jetzt --board-bg-base statt fixem Schwarz/Weiß —
     sehr dunkles/sehr helles Grau OHNE Custom-Tint (damit CARDS
     sichtbar bleiben), aber reines Schwarz/Weiß SOBALD eine
     Custom-Farbe aktiv ist (applyBoardBackgroundTint() setzt die
     Variable dann per Inline-Style um). */
  background-color: var(--board-bg-base);
  background-image: linear-gradient(var(--board-bg-tint), var(--board-bg-tint));
}
/* NEU (2026-07): .workspace hat eine eigene, unbedingte display:flex-
   Regel — die überschreibt (bei gleicher Spezifität, aber späterer
   Reihenfolge im eigenen Stylesheet) das native Standardverhalten des
   hidden-Attributs. Ohne diese Zeile blieb .workspace beim Öffnen
   eines Tools trotz hidden=true weiterhin sichtbar, wodurch sich Board
   und Tool-Ansicht per Flexbox automatisch die Höhe hälftig teilten —
   genau das gemeldete "nur zur Hälfte sichtbar". */
.workspace[hidden] { display: none; }

/* ── Zoom viewport wrapper ──────────────────────────────── */
.zoom-viewport {
  flex: 1;
  min-height: 0;
  overflow: auto;
  position: relative;
}

/* ══ Gestaltete Scrollleiste, dünn — GETEILTE Regel ═══════════════
   Bestandsaufnahme 2026-08-04 (vor dem Herausheben): es gibt FÜNF
   Stellen mit `scrollbar-color`, aber sie sind NICHT dieselbe Kopie —
   das war die Annahme, sie hat nicht gestimmt:

     .zoom-viewport            thin · 8px + height 8px · Thumb
     .col-body                 NONE · 8px              · Thumb
     .settings-meta-rows       thin · 6px + Track + radius + Hover
     .lp-assets-scroll         thin · 6px + Track + radius + Hover
     .worldgroup-fields-popover thin · (gar keine webkit-Regel)

   Vereinheitlicht wird deshalb NUR die Ausprägung "dünn, 8px, beide
   Achsen" — sie ist die einzige mit `height`, also die einzige, die
   eine WAAGERECHTE Leiste gestaltet, und genau die brauchte der
   Bild-Streifen im Palette-Picker.

   Die 6px-Familie (.settings-meta-rows / .lp-assets-scroll) wurde
   inzwischen (2026-08, Aufräumrunde) zusammengelegt, siehe dort;
   .col-body und .worldgroup-fields-popover bleiben bewusst wie sie
   sind (anderes Firefox-Verhalten bzw. unvollständig). Sie hier
   mitzunehmen wäre eine optische Änderung an Stellen, um die es in
   dieser Runde nicht geht. */
.scroll-thin,
.zoom-viewport {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.scroll-thin::-webkit-scrollbar,
.zoom-viewport::-webkit-scrollbar { width: 8px; height: 8px; }
.scroll-thin::-webkit-scrollbar-thumb,
.zoom-viewport::-webkit-scrollbar-thumb { background: var(--line); }

/* zoom-sizer: a div whose size matches the scaled content,
   giving the viewport a correct scrollable area */
.zoom-sizer {
  position: relative;
  /* width/height set by JS */
}

.columns-scroll {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-row);
  padding: 50px 80px;
  min-height: 100%;
  position: absolute;
  top: 0; left: 0;
  transform-origin: top left;
  /* transform: scale() set by JS */
  transition: padding-left .2s ease;
}

/* ── FREE BOARD (2026-07, docs/setzz-free-board-notes.md) ────────────
   Zweiter Board-Typ: CARDS liegen frei auf einer Fläche statt in
   Row → SET → Column. Die Fläche hat keine intrinsische Größe und
   wächst unbegrenzt aus dem Inhalt mit — Breite/Höhe werden von
   applyFreeCanvasSize() (app.js) aus max(x+w, y+h) gesetzt.
   Wichtig: .free-canvas ist der Positionierungs-Bezug (position:
   relative) für die absolut platzierten CARDS. Das umgebende
   .columns-scroll behält seine transform:scale() für den Board-Zoom —
   deshalb sind alle x/y hier Layout-Pixel, nicht Bildschirm-Pixel. */
.free-canvas {
  position: relative;
  min-width: 100%;
  min-height: 100%;
}
.free-card {
  position: absolute;
  /* Breite kommt inline aus card.w (siehe renderFreeBoard()); die Höhe
     bleibt bewusst automatisch aus dem Inhalt, genau wie im
     strukturierten Board (z.B. wachsend mit zusätzlichen Grid-Slots). */
}
/* Anfasser an der rechten Kante — dasselbe Muster wie .col-resize beim
   Column-Resize, nur pro CARD. */
.free-card-resize {
  position: absolute;
  top: 0; right: -3px;
  width: 8px;
  height: 100%;
  cursor: ew-resize;
  z-index: 5;
}
.free-card-resize::after {
  content: "";
  position: absolute;
  top: 0; left: 3px;
  width: 2px;
  height: 100%;
  background: transparent;
  transition: background .12s;
}
.free-card:hover .free-card-resize::after,
.free-card.resizing .free-card-resize::after {
  background: var(--amber, var(--line));
}
.free-card.resizing {
  user-select: none;
}
/* PHASE 1: freies Verschieben. Der Kopf ist die Ziehfläche (siehe
   bindFreeCardDrag() in app.js) — Titel und Knöpfe darin behalten ihren
   eigenen Cursor, damit erkennbar bleibt, was zieht und was klickt. */
.free-card > .panel-head {
  cursor: grab;
  /* Die Kopfzeile ist Ziehfläche — dort darf der Browser keine
     Text-Markierung beginnen. Sonst passiert Folgendes (Nutzer-
     Rückmeldung): Drückt man auf den TITEL und zieht, kehrt
     bindFreeCardDrag() vorher zurück (Titel gehört dem Umbenennen),
     ohne preventDefault() aufzurufen — der Browser startet dann eine
     ganz normale Text-Markierung, die quer über die Seite läuft und
     Brotkrume, Titel und Platzhaltertexte anderer CARDS in der
     Auswahlfarbe einfärbt (::selection = amber, ganz oben). */
  user-select: none;
  -webkit-user-select: none;
}
/* Beim Umbenennen wird der Titel durch ein <input> ersetzt
   (inlineRename()) — dort MUSS Markieren wieder möglich sein. */
.free-card > .panel-head .title-edit {
  user-select: text;
  -webkit-user-select: text;
}
.free-card > .panel-head h2,
.free-card > .panel-head button,
.free-card > .panel-head [data-panel-info] {
  cursor: pointer;
}
.free-card.free-dragging {
  cursor: grabbing;
  user-select: none;
  /* Während des Ziehens sichtbar abheben — sonst ist bei überlappenden
     CARDS nicht erkennbar, welche gerade an der Maus hängt. */
  box-shadow: 0 8px 28px rgba(0,0,0,.18);
  opacity: .92;
}
.free-card.free-dragging > .panel-head {
  cursor: grabbing;
}
/* NEU (Nutzer-Feedback): Im Free BOARD ist die Kopfzeile Zieh- UND
   Auswahlfläche — ein Klick darauf darf die CARD deshalb NICHT mehr
   auf-/zuklappen (siehe isFreeBoard()-Guard beim "panel-head
   background"-Handler in app.js). Stattdessen wird der ohnehin im
   Markup vorhandene Chevron-Knopf sichtbar gemacht, der im
   strukturierten Board ausgeblendet bleibt (dort klappt die Kopfzeile
   selbst). Damit sind Geste und Klapp-Bedienung sauber getrennt. */
.free-card > .panel-head .head-btn.collapse-toggle {
  display: grid;
  /* Rechts einreihen, direkt links neben dem toolbar-toggle-btn
     (Nutzer-Wunsch). Der rechte Knopf-Block entsteht dadurch, dass sein
     ERSTES Element margin-left:auto trägt — im Free BOARD ist das jetzt
     der Chevron statt des toolbar-toggle-btn. Bei den nachfolgenden
     Knöpfen muss ihr eigenes margin-left:auto deshalb neutralisiert
     werden, sonst würden sie ein zweites Mal nach rechts geschoben und
     der Chevron bliebe allein in der Mitte stehen. */
  margin-left: auto;
}
.free-card > .panel-head .collapse-toggle ~ .toolbar-toggle-btn,
.free-card > .panel-head .collapse-toggle ~ .head-btn.panel-menu-btn {
  margin-left: 0;
}
/* PHASE 2b: Mehrfachauswahl und Gruppenzugehörigkeit.
   Zwei getrennte Zustände, die gleichzeitig auftreten können:
   .free-selected = gerade ausgewählt (flüchtig, überlebt keinen Reload)
   .free-grouped  = gehört dauerhaft zu einer Card Group (Teil des Boards)
   Deshalb bewusst unterschiedliche Signale — Auswahl kräftig, Gruppe
   dezent gestrichelt —, damit beides nebeneinander lesbar bleibt. */
.free-card.free-selected {
  outline: 2px solid var(--amber, #c8a34a);
  outline-offset: 1px;
}

/* Gemeinsamer Rahmen um eine ganze Card Group (Nutzer-Feedback: vorher
   bekam jede CARD einen eigenen Rahmen, was sich als "mehrere Einzelne"
   las statt als eine Einheit). Liegt HINTER den CARDS — die beginnen bei
   z-index 1, siehe renderFreeBoard().
   Die Optik selbst kommt aus den bestehenden SET-Klassen
   (.group-style-none/-border/-background), die hier zusätzlich gesetzt
   werden: dadurch greifen die vorhandenen BOARD-SETTINGS-Regler für die
   SET-Transparenz automatisch mit, ohne eigene Einstellung. Die Klassen
   arbeiten mit !important, deshalb steht hier bewusst nur, was sie NICHT
   setzen (Position, Maße, Radius). */
.free-group-frame {
  position: absolute;
  z-index: 0;
  border: 3px dashed transparent;
  border-radius: 10px;
  /* Der Rahmen ist selbst eine Ziehfläche für die ganze Gruppe
     (bindFreeGroupFrameDrag(), app.js). Er liegt HINTER den CARDS —
     anfassbar ist damit genau das Abstandsband rings herum und die
     Lücken zwischen den CARDS; jede CARD behält ihre eigene Geste am
     Kopf. Das Aufziehen eines Marquees startet weiterhin nur auf der
     wirklich leeren Fläche. */
  pointer-events: auto;
  cursor: grab;
}
.free-group-frame:active { cursor: grabbing; }
/* Der Style-Knopf ist ein GESCHWISTER des Rahmens, kein Kind davon:
   der Rahmen liegt hinter den CARDS (z-index 0) und bildet damit einen
   eigenen Stapel-Kontext — ein Kind darin könnte nie über einer CARD
   liegen und wäre je nach Lage der Gruppe verdeckt. Siehe
   renderFreeGroupFrames() in app.js, das ihn separat positioniert.
   Sitzt im Abstandsband des Rahmens (FREE_GROUP_FRAME_PAD), überdeckt
   also keine Card-Inhalte. */
.free-group-style-btn {
  position: absolute;
  z-index: 9996;
  /* 24px Fläche bei 13px Icon — gibt dem Symbol Luft und vergrößert
     zugleich das Klickziel. */
  width: 24px; height: 24px;
  display: grid; place-items: center;
  padding: 0;
  background: none;
  border: 0;
  color: var(--board-ink-faint, var(--ink-faint));
  opacity: .45;
  cursor: pointer;
  transition: opacity .12s, color .12s;
}
.free-group-style-btn:hover { opacity: 1; color: var(--amber); }
.free-group-style-btn svg { width: 13px; height: 13px; }

/* NEU (2026-09-01): Titel einer Card Group. Werte NICHT neu gewaehlt,
   sondern von .group-title (SET-Titel im Row-BOARD) uebernommen —
   Schriftfamilie, Groesse, Gewicht, Laufweite, Versalien, Farbe und
   cursor:text sind dort abgeschrieben. Zwei Titel, die sich verschieden
   anfuehlen, waren in der Lightbox-Reihe der teuerste Fehler.

   Als GESCHWISTER des Rahmens, nicht als Kind — exakt dieselbe
   Begruendung wie beim Style-Knopf darueber (Rahmen liegt bei z-index 0
   und bildet einen eigenen Stapel-Kontext). Der Nebeneffekt ist hier
   sogar erwuenscht: der Ziehbereich der Gruppe bleibt der Rahmen
   allein, der Titel vergroessert ihn nicht.

   Sitzt im Abstandsband ueber der linken oberen Ecke; die Position
   rechnet renderFreeGroupFrames(). white-space:nowrap, weil ein
   umbrechender Titel den berechneten Versatz nach oben unbrauchbar
   machen wuerde. */
.free-group-title {
  position: absolute;
  z-index: 9996;
  font-family: var(--f-display);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--board-ink-faint);
  cursor: text;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Beim Umbenennen sitzt ein <input> darin — dieselbe Verkleinerung auf
   14px wie bei .group-title .title-edit, damit sich das Bearbeiten in
   beiden Board-Typen gleich anfuehlt. */
.free-group-title .title-edit {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
  text-transform: uppercase;
}

/* Aktionsleiste über der Auswahl — beantwortet direkt am Objekt, wie
   sich mehrere CARDS gruppieren lassen (das Tastenkürzel allein war
   nicht auffindbar). Liegt über den CARDS, muss also klickbar sein. */
.free-selection-bar {
  position: absolute;
  z-index: 9997;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 6px 5px 10px;
  background: var(--panel);
  border: 0px solid var(--line-soft);
  border-radius: 0px;
  box-shadow: 0 3px 12px rgba(0,0,0,.12);
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-faint);
  white-space: nowrap;
}
.free-selection-bar button {
  /* GEÄNDERT (2026-08-13, Fund von check-buttons.php OFFEN/free-selection-bar,
     Nutzer-Entscheidung): war background: var(--amber-dim, var(--line-soft))
     im Ruhezustand — Amber ist die Akzentfarbe, vom Nutzer frei waehlbar
     (BOARD SETTINGS), und markiert im System "hier bist du gerade" (aktive
     Auswahl/aktiver Tab). Ein Knopf, der sie dauerhaft traegt, ist erstens
     nicht verlaesslich lesbar (haengt von der gewaehlten Farbe ab) und
     zweitens semantisch falsch (empfohlen ≠ ausgewaehlt). Es gibt hier nur
     EINEN Knopf dieser Klasse (Group ODER Ungroup, nie beide gleichzeitig)
     neben einem Text-Label und dem eigens gedaempften .free-sel-delete —
     die Position traegt die Hervorhebung bereits. */
  padding: 3px 10px;
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink);
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: 3px;
  cursor: pointer;
  transition: border-color .12s, color .12s;
}
.free-selection-bar button:hover {
  color: var(--ink);
  border-color: var(--amber, var(--ink-faint));
}
/* NEU (2026-07, docs/setzz-card-groups-interactions-notes.md): Löschen.
   Bewusst ZURÜCKHALTEND gestaltet und NICHT von Haus aus rot — es sitzt
   direkt neben "Ungroup", und die beiden dürfen nicht verwechselbar
   sein. Die Warnfarbe kommt erst beim Überfahren, das Nachfragefenster
   ist die eigentliche Absicherung. Schmaler als die Textknöpfe daneben,
   damit es als Nebenaktion liest. */
.free-selection-bar .free-sel-delete {
  padding: 3px 7px;
  color: var(--ink-faint);
  background: transparent;
  font-size: 10px;
  line-height: 1;
}
.free-selection-bar .free-sel-delete:hover {
  color: var(--red, #d04040);
  border-color: var(--red, #d04040);
}

/* Auswahlrahmen beim Aufziehen über die freie Fläche. */
.free-marquee {
  position: absolute;
  z-index: 9998;
  border: 1px solid var(--amber, #c8a34a);
  background: var(--amber-dim, rgba(200,163,74,.12));
  pointer-events: none;
}

/* PHASE 2: Vorschau-Rahmen beim Ziehen einer Card-Typ-Kachel auf die
   freie Fläche. Zeigt die bereits eingerastete Zielposition — das
   Drag-Bild des Browsers folgt dem Zeiger stufenlos und verrät deshalb
   nicht, wo die CARD tatsächlich landet. Höhe bewusst nur angedeutet:
   die echte Höhe ergibt sich erst aus dem Inhalt der neuen CARD. */
.free-drop-ghost {
  position: absolute;
  height: 120px;
  border: 1px dashed var(--amber, var(--ink-faint));
  background: var(--amber-dim, rgba(0,0,0,.03));
  pointer-events: none;   /* darf die Drop-Erkennung nicht selbst abfangen */
  z-index: 9999;
}

/* BUGFIX (2026-07, siehe docs/setzz-board-customizing-notes.md, "COL-Gap
   asymmetrisch"): flex `gap` erzeugt nur Abstand ZWISCHEN Geschwister-
   Elementen, nie an den äußeren Rändern des ersten/letzten Elements. Das
   erste/letzte SET einer Row sass dadurch mit nur seinem eigenen, vom
   SET-Gap-Regler UNABHÄNGIGEN Innen-Padding (.group-wrap, 20/10px) am
   Rand, während SET Gap bei S/M/L (24/50/80px) keinerlei Wirkung auf
   diesen Rand hatte — bei COL fiel das sofort auf, bei SET wurde es
   durch das grosszügige, ebenfalls feste Padding von .columns-scroll
   (80px) zusätzlich kaschiert (per Nutzer-Vorgabe verifiziert, nicht nur
   für COL angenommen).
   Fix: die Hälfte von --gap-set zusätzlich als Padding auf dem RAHMEN
   selbst (.group-row), NICHT auf .group-wrap — .group-wrap ist die
   sichtbare SET-"Karte" (eigener Rahmen/Hintergrund), Padding direkt
   darauf würde die Karte selbst vergrössern statt Platz ZWISCHEN
   Karten zu schaffen. .group-row dagegen ist unsichtbar (kein Rahmen/
   Hintergrund), Padding hier wirkt optisch identisch zu einem
   zusätzlichen Geschwister-Abstand. KORREKTUR (2026-07): Aussenabstand
   muss genau so breit sein wie der Abstand zwischen zwei SETs, nicht nur
   halb so breit — daher volles --gap-set als Padding, nicht die Hälfte.
   Zwischen zwei SETs bleibt der Abstand unverändert bei --gap-set (reiner
   Flex-Gap, hier nicht angetastet); zusätzlich bekommt jetzt auch das
   erste/letzte SET denselben vollen Abstand zu seinem jeweiligen
   äusseren Rand — exakt dieselbe Rechnung wie bei COL (siehe .group-body
   unten), nur ohne Eingriff in die sichtbare SET-Karte oder deren
   Drag&Drop-Markup. position:relative als Anker für das jetzt absolut
   positionierte .group-add (siehe dort). ZUSATZFUND beim Testen mit
   mehreren SETs pro Reihe: width:auto (Default) berechnete die
   tatsächliche Breite von .group-row teils ~150-200px schmaler als ihre
   eigene intrinsische (max-content) Breite — ein Browser-Inkonsistenz-
   Effekt beim verschachtelten Auto-Sizing von Flex-Containern mit
   calc()/var()-Padding, NICHT verursacht durch dieses Padding selbst
   (per Vergleich mit width:max-content bestätigt: behebt es vollständig,
   ohne an Gap/Padding-Werten etwas zu ändern). Bei nur einem SET pro
   Reihe trat der Effekt nicht auf, daher vorher nicht aufgefallen. */
.group-row {
  position: relative;
  display: flex;
  align-items: stretch;
  width: max-content;
  gap: var(--gap-set);
  padding-left: var(--gap-set);
  /* NEU (2026-08): verdoppelt gegenüber padding-left — der SET-Rand-
     Anfasser (.group-resize, siehe .group-wrap unten) des LETZTEN SETs
     einer Row braucht dieselbe --gap-set-breite Zone wie zwischen zwei
     SETs, plus Platz für "+SET" (.group-add) danach. Absichtlich NICHT
     die right-Formel von .group-add geändert: da right relativ zu
     DIESER (jetzt breiteren) Padding-Box gemessen wird, rutscht der
     Button allein durch die Verdopplung automatisch um ein volles
     --gap-set nach rechts — in die neu entstandene zweite Zone, exakt
     mit demselben Verhältnis wie bisher zentriert. Siehe
     docs/setzz-md-roundtrip-notes.md für die Herleitung inkl. Messwerten. */
  padding-right: calc(var(--gap-set) * 2);
}

/* When a wide left panel (assets, textboard) is open,
   push the board content right by the panel width so the
   first column is never hidden behind the overlay */
.zoom-viewport.board-panel-offset .columns-scroll {
  padding-left: calc(var(--gap) + calc(var(--left-panel-w) * 2) + 4px);
}

.col {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.col-body {
  flex: 1;
  min-height: 60px;
  display: flex;
  flex-direction: column;
  gap: var(--gap-card);
  padding: 10px 5px 10px 5px;
  overflow-y: auto;
  scrollbar-width: none;
  scrollbar-color: var(--line) transparent;
}
.col-body::-webkit-scrollbar { width: 8px; }
.col-body::-webkit-scrollbar-thumb { background: var(--line); }
.col-body.drop-into { outline: 1px dashed var(--amber); outline-offset: -6px; }
.col::-webkit-scrollbar { width: 8px; }
.col::-webkit-scrollbar-thumb { background: var(--line); }

/* column widths are set inline by the board engine (drag right edge) */

/* ── Shared type utilities ─────────────────────────────────── */
.eyebrow, .p-label, .side-title, .metrics-title, .m-name, .m-unit,
.g-label, .g-note, .view-all, .status-live, .media-caption, .ph-tag,
.overview-meta, .profile-id em {
  font-family: var(--f-data);
  text-transform: uppercase;
  letter-spacing: .12em;
}

.warn { color: var(--red); }

/* ── Panels ────────────────────────────────────────────────── */
.panel {
  /* NEU (2026-07): CARD Color-Tint über der theme-eigenen --panel-Basis —
     gleiche Zwei-Ebenen-Technik wie .workspace (--board-bg-base +
     --board-bg-tint), siehe applyCardBgTint() in app.js. */
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  border: 0px solid var(--line);
  box-shadow: var(--panel-shadow);
  padding: var(--pad-card);
  flex-shrink: 0;
}

/* ── CARD Style Custom / Hide Text / Hide CARD ────────────────
   docs/setzz-card-style-and-hide-notes.md */
.panel.card-style-custom {
  background: var(--card-style-custom-bg) !important;
  box-shadow: none !important;
}
/* "No Style" — dritter Wert am selben Schalter (toggleCardStyleCustom()),
   app.js. Entfernt Hintergrund/Rahmen/Schatten wie .panel-spacer es
   bereits tut, UND versteckt die Kopfzeile im Ruhezustand komplett —
   beide erst bei :hover/:focus-within sichtbar, damit sich die Karte
   weiterhin ziehen und bedienen laesst.
   GEMESSEN (Untersuchungsrunde vor dieser): visibility:hidden waere
   hier die FALSCHE Wahl gewesen — eine echte Klick-Messung auf einer
   Testseite zeigte, dass ein per visibility:hidden verstecktes Element
   seine Trefferflaeche verliert (Klick faellt durch auf das, was
   darunterliegt), waehrend opacity:0 sie behaelt. bindFreeCardDrag()s
   `head.addEventListener("pointerdown", …)` haette mit visibility also
   nie gefeuert. Deshalb hier opacity + pointer-events, nicht visibility
   — dasselbe Muster wie die Video-/Framer-Bedienleiste
   (.video-playbar/.framer-playbar), nur am ELTERNTEIL (.panel)
   gehangen, nicht an der Kopfzeile selbst: sonst liesse sich das
   Unsichtbare nicht durch Darueberfahren finden. `> .panel-head` statt
   nur `.panel-head`, weil dieselbe Regel sowohl im Row-BOARD
   (`.panel > .panel-head`) als auch im Open BOARD gilt — dort ist
   `.free-card` dieselbe Klasse auf demselben Element wie `.panel`
   (siehe renderFreeBoard(), `el.classList.add("free-card")` auf dem
   von buildPanel() gebauten Element), keine zwei Strukturen. */
.panel.card-style-none {
  background: none !important;
  box-shadow: none !important;
  border: none !important;
}
.panel.card-style-none > .panel-head {
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}
/* :focus-within haelt die Kopfzeile fuer die Tastatur erreichbar (Tab
   auf Kebab-/Chevron-/Schloss-Button) — gleiche Begruendung wie bei
   .video-playbar: ohne das waere sie per Tab anwaehlbar, aber
   unsichtbar. */
.panel.card-style-none:hover > .panel-head,
.panel.card-style-none:focus-within > .panel-head {
  opacity: 1;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .panel.card-style-none > .panel-head { transition: none; }
}
/* "No Style" erreicht die INNEREN Inhaltsflaechen — GEFUNDEN
   (SimplifyerX, Bildschirmfoto, Staging 2026-09-02): bei text, md,
   audio-list, list/list-media und typography (fonts) blieb eine
   farbige Flaeche hinter dem Inhalt stehen, PLUS pdf (dort unbemerkt,
   aber ebenso betroffen — gemessen, siehe Bericht). `.panel.card-
   style-none { background: none }` oben wirkt nur auf `.panel` selbst
   — diese Flaechen sitzen SEIT DER KARTEN-FARBTON-REIHE (2026-08-24,
   docs/setzz-new-card-type-checklist.md "#13") eine Ebene TIEFER, mit
   je einer EIGENEN, vom Farbton unabhaengigen Grundfarbe (--bg-deep
   oder --panel) UNTER dem Farbverlauf — genau die Flaeche, die stehen
   blieb, ist meist schon diese Grundfarbe, nicht erst der Farbton.

   DIESELBE SELEKTORLISTE wie applyCardBgTint()/die Farbton-Reihe
   wiederverwendet, keine zweite danebengelegt — siehe dort fuer die
   Begruendung jeder einzelnen Flaeche. Es gibt dafuer KEINEN
   gemeinsamen Klassennamen (jede Flaeche traegt --card-bg-tint
   einzeln, eine seit der Farbton-Reihe selbst schon handgepflegte
   Liste) — ein Sammel-Marker im Markup (z. B. eine gemeinsame
   .card-content-surface-Klasse an jeder dieser Stellen) waere die
   sauberere, aber eigene Umbau-Runde; als Backlog-Punkt vermerkt statt
   hier nebenbei mitgebaut.

   text/md BEWUSST NUR die ANZEIGE, nicht der EDITOR (SimplifyerX-
   Praezisierung): .text-editor/.text-highlight (Bearbeiten, inkl. der
   Markdown-Syntax-Hervorhebung UEBER dem transparent gestellten
   Textarea) und .CodeMirror bleiben aussen vor und behalten ihre
   Flaeche — nur .text-formatted-preview bzw. .editor-preview/
   -preview-side (Anzeigen) verlieren sie. GEMESSEN per
   syncTextViewMode()/mode-*-Klassen, nicht aus dem Selektornamen
   geschlossen: .text-highlight liegt strukturell im Markdown-
   BEARBEITEN-Modus (.mode-markdown), nicht im Anzeigen-Modus.
   .list-text-input (List-Karte, "Text to LIST"-Rohtext-Eingabe) ist
   aus demselben Grund ebenfalls ein Editor-Feld und bleibt deshalb
   AUSSEN VOR, obwohl es in der Farbton-Reihe mitgetoent wurde — mit
   derselben Begruendung wie .text-editor: ein Eingabefeld ohne
   erkennbare Flaeche waere beim Tippen schwerer zu lesen.

   .audio-slot (die Abspielleiste selbst) bleibt unberuehrt — sie
   trug schon vor dieser Aenderung `background: transparent` (eigene
   Bedeutung, Wellenform/Bedienelement, nicht Untergrund), nur die
   Zeile DARUM (.audio-row) verliert ihre Flaeche.
   .list-th/.list-td deckt automatisch auch die Zebra-Zeilen ab (die
   Alternanz ist eine ZUSAETZLICHE Ebene auf genau dieser Flaeche,
   siehe .list-table tbody tr:nth-child(even) .list-td weiter unten;
   Spezifitaet von drei Klassen hier schlaegt die zwei Element- plus
   zwei Klassen-/Pseudoselektoren dort). Ohne Zebra ist eine "No
   Style"-Liste eine reine Textliste — siehe Bericht.

   .typo-locked/.palette-locked GEHOEREN NICHT ZUR FARBTON-REIHE — neu
   GEFUNDEN beim Durchgehen ALLER Kartentypen (nicht nur der gemeldeten):
   die Pro-Gate-Ansicht (Demo/Standard-Konto ohne Pro) traegt eine
   EIGENE, feste Flaeche (`background: var(--panel)`, kein
   --card-bg-tint) — unabhaengig vom Board-Farbton, deshalb auch auf
   einem Board ganz ohne gesetzten Farbton sichtbar. Kein Aequivalent
   in export.js: die gesperrte Ansicht ist ein reines Live-Konto-
   Konzept, eine ausgelieferte Seite zeigt immer den echten Inhalt. */
.panel.card-style-none .pdf-frame,
.panel.card-style-none .text-formatted-preview,
.panel.card-style-none .md-editor-host .editor-preview,
.panel.card-style-none .md-editor-host .editor-preview-side,
/* NEU (2026-09-03, GEMESSEN SimplifyerX): der Codeblock (`pre`) war
   bewusst von der "No Style"-Regel ausgenommen — Begruendung im
   Kopfkommentar bei "CODEBLOECKE, VIER ORTE" weiter unten: "ein
   Codeblock DARF sich vom Fliesstext abheben, das ist seine Aufgabe".
   SimplifyerX will das anders: unter "No Style" soll auch der Codeblock
   keine eigene Flaeche zeigen, wie jede andere Inhaltsflaeche der Karte.
   GEMESSEN, was dann als Unterscheidung bleibt: der Fliesstext beider
   Karten laeuft ueber --f-label, und das ist in DIESEM Projekt
   JetBrains Mono, nicht Rajdhani (Rajdhani ist --f-display, nur fuer
   Ueberschriften) — "Schrift allein" unterscheidet einen kurzen,
   einzeiligen Codeblock von einem Absatz deshalb NICHT: gleiche
   Schriftfamilie, gleiche Groesse, gleiche Farbe. Uebrig bleibt nur
   `white-space: pre-wrap` mit haengender Einrueckung — sichtbar EINZIG,
   wenn der Block tatsaechlich umbricht. Ein kurzer Codeblock ist unter
   "No Style" damit optisch nicht mehr von einem Absatz unterscheidbar.
   Gebaut wie gewuenscht — die Einschraenkung steht im Bericht. */
.panel.card-style-none .text-formatted-preview pre,
.panel.card-style-none .md-editor-host .editor-preview pre,
.panel.card-style-none .md-editor-host .editor-preview-side pre,
.panel.card-style-none .list-surface,
.panel.card-style-none .list-th,
.panel.card-style-none .list-td,
.panel.card-style-none .audio-list,
.panel.card-style-none .audio-row,
.panel.card-style-none .typo-list,
.panel.card-style-none .typo-row,
.panel.card-style-none .typo-single,
.panel.card-style-none .typo-charset,
.panel.card-style-none .typo-locked,
.panel.card-style-none .palette-locked {
  background: none !important;
}
/* GEFUNDEN (SimplifyerX, Bildschirmfoto, Staging 2026-09-02): bei der
   MD-Karte mit "No Style" schien im ANZEIGEN-Zustand der Editor-Text
   durch die jetzt durchsichtige Vorschau durch — beide Texte lagen
   sichtbar uebereinander.
   URSACHE, GEMESSEN (nicht angenommen): EasyMDEs Vorschau (Klasse
   .editor-preview-full, easymde.min.css) ist eine EIGENE, absolut
   positionierte UEBERLAGERUNG (position:absolute; inset 0; z-index:7),
   die per display:none/.editor-preview-active EIN-/AUSGEBLENDET wird
   — sie ERSETZT den Editor nie. .CodeMirror (z-index:0) bleibt darunter
   die ganze Zeit gerendert. Vorher blieb das unsichtbar, weil
   .editor-preview selbst blickdicht war (background-color:var(--panel)
   + Farbton) und den Editor darunter komplett abdeckte — genau das
   entfernt "No Style" oben jetzt bewusst. Bei "Default"/"Custom"
   besteht dieselbe Ueberlagerung unveraendert fort, faellt dort aber
   nicht auf, weil die Vorschau dort weiterhin blickdicht ist — kein
   neuer, sondern ein aelterer, vorher unsichtbarer Zustand.
   Bei der Text-Karte besteht dieses Risiko NICHT: dort ERSETZT
   .text-editor-wrap.mode-formatted .text-editor { display: none } den
   Editor tatsaechlich (siehe weiter unten) — kein Deckungsfall.
   FIX AN DER RICHTIGEN STELLE (nicht die Vorschau wieder blickdicht
   machen — das war ausdruecklich nicht gewollt): der Editor selbst wird
   im Anzeigen-Zustand verborgen, ueber die bereits vorhandene
   Sync-Klasse .md-preview-active (panels.js, syncPreviewClass() —
   spiegelt EasyMDEs .editor-preview-active zuverlaessig, nach jedem
   Klick im Editor-Host neu abgeglichen, deckt alle drei Wege ab, die
   Vorschau zu aktivieren).

   ⚠️ ERSTER ANLAUF FALSCH, GEMESSEN UND KORRIGIERT: visibility:hidden
   auf .CodeMirror selbst schien richtig, hat aber genau die Vorschau
   mitgenommen, die sichtbar bleiben soll — GEMESSEN per
   elementsFromPoint(): .editor-preview-full ist strukturell ein KIND
   von .CodeMirror, kein Geschwister daneben (direkte Kinder von
   .CodeMirror, gemessen: die beiden Scrollbar-Divs, der Gutter-Filler,
   .CodeMirror-scroll UND .editor-preview-full nebeneinander). visibility
   ist eine VERERBTE CSS-Eigenschaft — .CodeMirror unsichtbar zu setzen
   macht damit auch die eigene Vorschau-Kindebene unsichtbar, ohne eine
   gegenteilige Regel. Ergebnis vor der Korrektur: WEDER Editor NOCH
   Vorschau sichtbar, schlimmer als der gemeldete Fehler. Getroffen
   wird deshalb NUR .CodeMirror-scroll — das ist die eigentliche
   Text-/Gutter-Flaeche und ein GESCHWISTER von .editor-preview-full,
   kein Vorfahre davon, vererbt also nichts dorthin.
   visibility:hidden statt display:none, AUS GUTEM GRUND GEPRUEFT:
   CodeMirror braucht eine von 0 verschiedene Container-Groesse fuer
   seine eigene virtuelle Scroll-/Zeilen-Berechnung — ein display:none-
   Container liefert beim naechsten Zurueckwechseln eine kollabierte
   Breite/Hoehe und verlangt einen expliziten
   editor.codemirror.refresh(), sonst bleibt das Layout verzerrt
   (bekannte CodeMirror-Falle, nicht SETZZ-spezifisch). visibility:hidden
   haelt die Box in ihrer echten Groesse im Layout — GEMESSEN auf
   Staging: Bildlaufstand und Cursor blieben nach mehrfachem Hin- und
   Herwechseln erhalten, kein refresh() noetig.
   Betrifft NUR .editor-preview-full/.md-preview-active — die Side-by-
   Side-Vorschau (.editor-preview-side, nur im EasyMDE-Vollbildmodus)
   liegt geometrisch NEBEN dem Editor, nicht darueber (eigene Spalten,
   siehe die Vollbild-Regeln weiter unten) und braucht deshalb keine
   Entsprechung — dort gibt es keine Deckung, die zu einem
   durchscheinenden Editor fuehren koennte. */
.panel.card-style-none .md-editor-host.md-preview-active .EasyMDEContainer .CodeMirror-scroll {
  visibility: hidden;
}
/* NEU (2026-09-04, GEMESSEN SimplifyerX): eine ANDERE Flaeche als oben —
   dort ging es um den durchscheinenden EDITORTEXT (.CodeMirror-scroll,
   visibility), hier um .CodeMirrors eigenen ZWEISCHICHT-HINTERGRUND
   (background-color: var(--panel) + linear-gradient(--card-bg-tint),
   siehe .md-editor-host .EasyMDEContainer .CodeMirror weiter unten).
   .editor-preview-full liegt absolut ueber .CodeMirror, ersetzt es nie
   — unter "No Style" wird die Vorschau selbst transparent und gibt den
   Blick frei auf .CodeMirrors Flaeche DAHINTER, unter dem GESAMTEN
   Vorschauinhalt (nicht nur hinter Code-Bloecken, die pre-Regel oben
   deckt nur die eigentlichen Codebloecke ab).
   GEFAHRLOS anders als der visibility-Fall oben: background wird NICHT
   vererbt, ein transparenter Elternhintergrund nimmt der Kindebene
   (.editor-preview-full, bereits eigenes background:none) nichts weg.
   Dieselbe Sync-Klasse .md-preview-active wie oben — betrifft dadurch
   nur den ANZEIGEN-Zustand unter "No Style"; im BEARBEITEN-Zustand
   fehlt die Klasse, .CodeMirror behaelt seine Flaeche unveraendert.
   "Default"/"Custom" tragen .card-style-none nicht, bleiben unberuehrt. */
.panel.card-style-none .md-editor-host.md-preview-active .EasyMDEContainer .CodeMirror {
  background: none !important;
}
/* visibility:hidden statt display:none (gleiches Muster wie bereits bei
   .panel-spacer .panel-head h2, siehe unten) — Element bleibt im Layout
   erhalten, damit die übrigen Header-Icons nicht nachrücken/springen. */
.panel.hide-text .panel-head h2 { visibility: hidden; pointer-events: none; }
/* NEU: "Hide Text" für COLs — analoge Funktion eine Ebene höher (Spalten-
   Kontextmenü). Nur Text (Spalten-ID, -Titel, Breitenfaktor) verschwindet,
   Icons (Chevron/⋮-Menü/Drag-Handle) bleiben immer sichtbar UND an
   derselben Position (kein Layout-Sprung). */
.col.hide-text .col-head-text,
.col.hide-text .col-width-factor { visibility: hidden; pointer-events: none; }
/* NEU: "Hide Text" für SETs — gleiche Methode, eine Ebene höher (SET-
   Kontextmenü). Nur der SET-Titel verschwindet, alle Header-Icons
   bleiben sichtbar und an Ort und Stelle. */
.group-wrap.hide-text .group-title { visibility: hidden; pointer-events: none; }
/* "Hide CARD" wirkt NICHT auf den Live-Board (Card bleibt immer normal
   sichtbar/bearbeitbar) — die Klasse .hide-card-flag steuert hier nur
   den kleinen Indikator-Icon im Header, keine Sichtbarkeit. Export/
   Share werten dasselbe Flag (panel.hideCard) in ihrer eigenen,
   separaten Rendering-Pipeline aus (siehe export.js). */
.panel-hidecard-icon {
  display: none;
  color: var(--red);
  align-items: center;
  margin: 0 2px;
}
.panel-hidecard-icon svg { width: 13px; height: 13px; }
.panel.hide-card-flag .panel-hidecard-icon { display: inline-flex; }

/* "Locked" — sperrt nur Verschieben/Löschen, Inhalt bleibt editierbar
   (siehe toggleLockPanel() in app.js). Gleiches An-/Aus-Muster wie
   .panel-hidecard-icon. BUGFIX: zunächst var(--amber) — das ist aber
   NICHT "amber" als feste Farbe, sondern die per-Board "Accent Color"
   (applyWorldAccent() schreibt --amber mit !important um, siehe
   app.js). Ein Board mit individuellem Accent zeigte das Schloss
   dadurch in einer beliebigen, vom Nutzer gewählten Farbe statt einer
   festen. Jetzt dieselbe feste Farbe wie die Geschwister-Icons
   (.head-btn, .panel-icon) — bleibt unabhängig vom Board-Accent. */
.panel-lock-icon {
  display: none;
  color: var(--board-ink-faint);
  align-items: center;
  margin: 0 2px;
}
.panel-lock-icon svg { width: 13px; height: 13px; }
.panel.panel-locked .panel-lock-icon { display: inline-flex; }

/* ── Panel reordering (drag & drop within a column) ─────────── */
.panel.draggable { cursor: grab; }

.panel.dragging {
  opacity: .35;
  cursor: grabbing;
}
.panel.drop-before { box-shadow: inset 0 3px 0 0 var(--amber); }
.panel.drop-after  { box-shadow: inset 0 -3px 0 0 var(--amber); }

/* ── Column reordering (drag anywhere on a column) ─────────── */
.col.col-dragging { opacity: .35; }
.col.drop-col-before { box-shadow: inset 3px 0 0 0 var(--amber); }
.col.drop-col-after  { box-shadow: inset -3px 0 0 0 var(--amber); }

/* NEU (2026-07): visuelles Feedback beim Group-/SET-Drag, gleiches
   Prinzip wie beim Spalten-Drag. */
.group-wrap.group-dragging { opacity: .35; }

/* NEU: eigenständige Marker-Linie ZWISCHEN zwei SETs statt einer Border
   direkt am Rahmen des Ziel-SETs (die vorher kaum sichtbar war, weil sie
   am ohnehin schon vorhandenen Rahmen "klebte") — analog zur sichtbaren
   Linie zwischen COLs (.col-resize::after). Position wird per JS
   (showGroupDropMarker(), app.js) anhand des Ziel-SETs berechnet. */
.group-drop-marker {
  position: fixed;
  width: 3px;
  border-radius: 2px;
  background: var(--amber);
  box-shadow: 0 0 6px rgba(200,160,60,.6);
  z-index: 140;
  pointer-events: none;
  display: none;
}
.group-drop-marker.visible { display: block; }
.col-drag-handle {
  flex-shrink: 0;
}
.col-drag-handle svg { width: 14px; height: 14px; }

.drag-handle {
  display: none !important;
  display: inline-flex;
  align-items: center;
  padding-left: 1px;
  cursor: grab;
  flex-shrink: 0;
  color: var(--ink-faint);
  transition: color .15s;
}
.drag-handle:hover { color: var(--amber); }
.drag-handle svg { width: 15px; height: 15px; }
/* panel-head background: pointer (click toggles); child controls set their own cursor */

/* header utility buttons: collapse + focus, matched scale to the drag-handle */
.head-btn {
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 0;
  color: var(--board-ink-faint);
  font-size: 12px;
  flex-shrink: 0;
  transition: color .15s;
}
.head-btn:hover { color: var(--amber); }
.head-btn.collapse-toggle { margin-left: 14px; display: none; }
.head-btn.collapse-toggle svg { width: 14px; height: 14px; transition: transform .2s ease; }
.panel.collapsed .collapse-toggle svg { transform: rotate(180deg); }

/* ── Collapsible panels ─────────────────────────────────────── */
.panel.collapsed > *:not(.panel-head) { display: none; }
.panel.collapsed .panel-head { margin-bottom: 0; }

/* ── Focus / fullscreen mode ────────────────────────────────── */
.focus-overlay {
  position: fixed; inset: 0;
  background: rgba(var(--overlay-rgb), .92);
  z-index: var(--z-immersive-view);   /* war 90 — siehe Layer-Ordnung in :root */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px;
}
.focus-overlay[hidden] { display: none; }
.focus-overlay-inner {
  width: 100%;
  max-width: 1240px;
  max-height: 100%;
  overflow-y: auto;
}
.focus-overlay-inner .panel { width: 100%; box-shadow: 0 0 0 1px var(--amber); }
.panel-focused { width: 100% !important; }
.focus-exit {
  position: absolute;
  top: 24px; right: 28px;
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  border: 1px solid var(--line);
  padding: 8px 14px;
  transition: color .15s, border-color .15s;
}
.focus-exit:hover { color: var(--amber); border-color: var(--amber); }

/* ── Upload zones (any [data-upload-key] media figure) ──────── */
.media[data-upload-key] { cursor: pointer; }
.upload-overlay {
  /* NEU: ausgeblendet — Upload in ASSETS-Slots läuft jetzt über den
     "Upload ASSETS"-Menüpunkt im "⋮"-Kontextmenü (media-controls) statt
     über diesen Hover-Hinweis. Drag & Drop auf den Slot funktioniert
     unverändert weiter, unabhängig von diesem Overlay. */
  display: none;
  position: absolute;
  top: 55%; left: 25%; right: 25%; bottom: 8%;   /* sits below center, clear of the ASSET label */
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle, rgba(var(--overlay-rgb), .65) 0%, rgba(var(--overlay-rgb), 0) 72%);
  border-radius: 50px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
  z-index: 3;
}
.asset-thumb .upload-overlay span { padding: 6px 9px; font-size: 9px; }
.media[data-upload-key]:hover .upload-overlay,
.media[data-upload-key].drag-over .upload-overlay { opacity: 1; }
.upload-overlay span {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--amber);
  border: 1px dashed var(--amber);
  padding: 9px 14px;
  pointer-events: auto;
  cursor: pointer;
}
.media[data-upload-key].drag-over { outline: 1px dashed var(--amber); outline-offset: -1px; }

/* top-right control cluster: ratio selector + download badge, side by side */
.media-resolution {
  position: absolute;
  bottom: 70px; left: 1px;
  z-index: 5;
  background: rgba(var(--overlay-rgb), .85);
  color: var(--ink-dim);
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .04em;
  padding: 3px 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}

.media-controls {
  position: absolute;
  top: 6px; right: 6px;
  display: flex;
  gap: 1px;
  z-index: 5;
}
.media-controls > button, .media-controls > a {
  position: static;   /* neutralize old absolute positioning when reused inside the cluster */
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: rgba(var(--overlay-rgb), .85);
  border: 0;
  color: var(--ink-dim);
  font-size: 12px;
  opacity: 0;
  transition: color .15s, opacity .15s;
}
.media[data-upload-key]:hover .media-controls > button,
.media[data-upload-key]:hover .media-controls > a,
.ratio-btn.controls-open,
.quadrant-btn.controls-open { opacity: 1; }
.audio-row:hover > .media-controls > button,
.audio-row:hover > .media-controls > a { opacity: 1; }
.ratio-btn.controls-open,
.quadrant-btn.controls-open { color: var(--amber); }
.media-controls > button:hover, .media-controls > a:hover { color: var(--amber); }
.media-controls svg { width: 12px; height: 12px; }

.ratio-popover {
  position: fixed;
  width: 168px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  display: none;
  grid-template-columns: repeat(3, 1fr);
  z-index: var(--z-popover-menu);   /* war 130 — auch von .worldgroup-fields-popover mitgenutzt, siehe Layer-Ordnung in :root */
}
.ratio-popover.open { display: grid; }
.ratio-popover button {
  padding: 8px 4px;
  text-align: center;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-dim);
  border-bottom: 1px solid var(--line-soft);
  border-right: 1px solid var(--line-soft);
  transition: color .15s, background .15s;
}
/* GEAENDERT (2026-09-02, dritte Fassung — und deshalb die letzte):
   Die Regel hiess erst :nth-child(3n), dann 3n+1, und mit EACH ORIGINAL
   waere jetzt 3n+2 faellig gewesen. :nth-child zaehlt GESCHWISTER, also
   verschiebt jeder neue Sonderknopf die Rechnung. Beim dritten Mal ist
   das ein Muster: die Regel gehoert unabhaengig von der Position.
   Die zwoelf Werte stehen deshalb in einem eigenen Behaelter, und
   :nth-child zaehlt nur noch innerhalb davon — wieder schlicht 3n, und
   ein vierter Sonderknopf aendert daran nichts mehr.
   display:contents ist der Kern: der Behaelter selbst erzeugt keine Box,
   die zwoelf Knoepfe bleiben direkte Rasterzellen des Popovers. Ohne das
   waeren sie EINE Zelle und das dreispaltige Raster waere zerstoert. */
.ratio-popover .ratio-values { display: contents; }
.ratio-popover .ratio-values button:nth-child(3n) { border-right: 0; }
/* ORIGINAL und EACH ORIGINAL ueber die volle Breite, oben; DEFAULT
   ebenso, aber unten — siehe getRatioPopover() (app.js). EACH ORIGINAL
   traegt die Trennlinie nach unten, ORIGINAL darueber keine, damit die
   beiden als EIN Block gegen die Zahlenwerte stehen. */
.ratio-popover button.ratio-original {
  grid-column: 1 / -1;
  border-right: 0;
  border-bottom: 0;
}
.ratio-popover button.ratio-each-original {
  border-bottom: 1px solid var(--line-soft);
}
.ratio-popover button:hover { color: var(--amber); background: var(--panel); }
.ratio-popover button.active { color: var(--amber); }
.ratio-popover button.disabled {
  color: #4a4a50;
  pointer-events: none;
  cursor: not-allowed;
}
.ratio-popover button.disabled:hover { background: none; }
.ratio-popover button.ratio-default {
  grid-column: 1 / -1;
  border-right: 0;
  border-bottom: 0;
  border-top: 1px solid var(--line-soft);
}

/* NEU (2026-08, Custom-Feld-Sichtbarkeit pro WORLD, Schritt 2 — siehe
   docs/setzz-custom-detail-fields-notes.md). Nutzt Position/Rahmen/
   z-index von .ratio-popover mit, ersetzt aber dessen 3-Spalten-Raster
   durch eine einspaltige Checkbox-Liste. */
.worldgroup-fields-popover {
  width: 232px;
  grid-template-columns: 1fr;
  max-height: 320px;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
/* NEU (2026-08): ab WORLDGROUP_FIELDS_TWO_COL_THRESHOLD Feldern
   (world-sync.js) — Breite verdoppelt plus Spaltenabstand, damit jede
   Spalte weiterhin ihre volle 232px-Zeilenbreite behält statt gequetscht
   zu werden. */
.worldgroup-fields-popover.worldgroup-fields-popover--wide { width: 472px; }
.worldgroup-fields-title {
  padding: 8px 10px;
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line-soft);
}
/* Ein-/zweispaltig über CSS-Mehrspaltenlayout statt Grid: füllt Spalte 1
   erst vollständig von oben nach unten, dann Spalte 2 — bei 15 Feldern
   also 8 oben links + 7 oben rechts, nicht kreuzweise verteilt. */
.worldgroup-fields-rows { column-count: 1; }
.worldgroup-fields-rows--two-col {
  column-count: 2;
  column-gap: 0;
  column-rule: 1px solid var(--line-soft);
}
.worldgroup-fields-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-dim);
  cursor: pointer;
  transition: color .15s, background .15s;
  break-inside: avoid;
}
.worldgroup-fields-row:hover { color: var(--ink); background: var(--panel); }
.worldgroup-fields-row input { flex-shrink: 0; cursor: pointer; }
.worldgroup-fields-row span {
  min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.worldgroup-fields-empty {
  padding: 10px;
  font-family: var(--f-label);
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink-faint);
}

.media-side-controls {
  position: absolute;
  top: 30%; left: 6px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  z-index: 4;
  opacity: 0;
  transition: opacity .15s;
}
.media[data-upload-key]:hover .media-side-controls { opacity: 1; }
.media-side-controls > button {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  background: rgba(var(--overlay-rgb), .85);
  border: 0;
  color: var(--ink-dim);
  transition: color .15s;
}
.media-side-controls > button:hover { color: var(--amber); }
.media-side-controls > button svg { width: 12px; height: 12px; }
.map-ctrls .media-sound { position: static; opacity: 1; width: 28px; height: 28px; background: rgba(var(--overlay-rgb), .8); }

.panel-head {
  display: flex;
  align-items: center;
  gap: 1px;
  margin-bottom: 0px;
  padding: 6px;
  cursor: grab;                /* draggable background; click-through also toggles */
}
.panel-head:active { cursor: grabbing; }
.panel.dragging .panel-head { cursor: grabbing; }
.panel-head h2,
.panel-head .head-btn,
.panel-head .panel-icon { cursor: pointer; transition: color .15s, opacity .15s; }
.panel-head h2 { cursor: text; }        /* title: rename affordance wins */
.panel-head .drag-handle { cursor: grab; display: none; }
.panel-head .drag-handle:active { cursor: grabbing; }
.panel-head h2 {
  cursor: text;
  font-family: var(--f-display);
  /* GEAENDERT (Karten-Bedienelemente-Runde): war 17px. Nebenbefund: der
     Umbenennen-Zustand (.panel-head h2 .title-edit, weiter unten) trug
     schon vorher 15px — Anzeige und Bearbeitung sprangen beim Klick
     stillschweigend zwischen zwei Groessen hin und her. Jetzt gleich. */
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.02rem;
  line-height: 95%;
  text-transform: none;
  text-align: left;
  margin-right: auto;
  /* GEAENDERT (2026-08-03, Nutzer-Vorgabe): war --ink-dim. --board-ink-faint
     ist die board-eigene Variante (siehe :root), zieht also mit einem
     BOARD-Theme mit, waehrend --ink-dim die Oberflaeche meint. */
  color: var(--board-ink-faint);
  transition: color .15s;
}
.panel-head h2 { cursor: text; }
.view-all {
  font-size: 10px;
  color: var(--ink-dim);
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  transition: color .15s, border-color .15s;
}
.view-all:hover { color: var(--amber); border-color: var(--amber); }

.brand { text-align: center; margin-bottom: 26px; }
.brand-mark { width: 40px; height: 40px; margin: 0 auto 1px; display: block; object-fit: contain; }
:root[data-theme="light"] .brand-mark { filter: invert(1); }
.brand-name {
  font-family: var(--f-label);
  font-size: 16px;
  letter-spacing: 0;
  line-height: 1.15;
  text-transform: none;
}
.brand-name sup { display: none;
  font-family: var(--f-label);
  font-size: .5em;
  letter-spacing: .04em;
  text-transform: lowercase;
  vertical-align: super;
}

.side-title {
  font-size: 10px;
  color: var(--amber);
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.side-title .tick {
  display: none;
  width: 7px; height: 7px;
  border: 1px solid var(--amber);
  transform: rotate(45deg);
  flex-shrink: 0;
}
.side-title.accent { color: var(--amber-hi); }

/* ── World gallery: cover-image tiles, replaces the old dropdown ── */
/* ── Panel-type icon grid: drag source for adding panels ─────
   Square tiles, 3 per row, big icon + mini label — grouped into
   Single-item types and the one Group type (Scene). */
.panel-types-head { margin-bottom: 10px; }
.side-sep { border-top: 1px solid var(--line-soft); margin: 6px 0 14px; }
.panel-types-head { margin-top: 18px; }
.panel-type-grid { margin-bottom: 22px; }
.panel-type-section-label {
  height: 1px;
  background: var(--line-soft);
  margin: 10px 0 6px;
  border: none;
}
.panel-type-section-label:first-child { margin-top: 0; }
/* NEU (2026-07, Floating Panels): Spaltenzahl richtet sich nach der
   verfügbaren Breite, statt fest 3 zu sein. Wird das (schwebende)
   Panel breiter gezogen, passen dadurch MEHR Kacheln nebeneinander —
   die einzelne Kachel wächst nicht mit.
   auto-fill (nicht auto-fit): eine nur teilweise gefüllte letzte Reihe
   behält ihre Spalten, die verbleibenden Kacheln bleiben also genauso
   groß wie die darüber, statt sich über die ganze Breite zu dehnen.
   64px ist so gewählt, dass das ANGEDOCKTE Panel (Inhaltsbreite 218px)
   weiterhin exakt 3 Spalten zeigt — dort ändert sich nichts. */
.panel-type-rows {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 2px;
  margin-top: 0.6rem;
}
/* ══ Floating Panels (docs/setzz-floating-panels-notes.md, Etappe B) ══
   z-index bewusst über allem Board-UI/angedockten Panels. KORRIGIERT
   (2026-08, Layer-Ordnung siehe :root): die alte Behauptung hier ("unter
   Lightbox/Modals 9996–9999") stimmte nie — 9996–9999 ist die Free-Board-
   Gesten-Ebene, nicht Lightbox/Modals (Lightbox lag vorher bei 150, also
   TIEFER als diese Ebene, nicht höher). Dialog/Modal/Toast/Lightbox/
   Popover liegen jetzt bewusst per --z-* alle ÜBER --z-floating-panel.
   Die Ebene selbst ist klickdurchlässig, damit sie das Board nicht
   blockiert. */
.floating-panel-layer {
  position: fixed;
  inset: 0;
  z-index: var(--z-floating-panel);
  pointer-events: none;
}
/* Im Dashboard ausgeblendet (setFloatingPanelsHidden(), app.js).
   Ausdrücklich hingeschrieben statt sich auf das UA-Standardverhalten
   von [hidden] zu verlassen: sobald hier irgendwann ein display-Wert
   gesetzt würde, gewänne er gegen das UA-Stylesheet und die Ebene
   bliebe sichtbar. */
.floating-panel-layer[hidden] { display: none; }
/* GEÄNDERT (2026-08-04, Nutzer-Vorgabe): war var(--bg) — geprüft, .floating-panel
   ist ein einziger, ausschließlich hier verwendeter Klassenname (nur
   von openFloatingPanel() vergeben), betrifft also NUR schwebende
   Fenster, kein anderes Panel und keinen angedockten Zustand. */
.floating-panel {
  position: absolute;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  background: var(--bg-deep);
  border: 1px solid var(--line-soft);
  box-shadow: 0 8px 32px rgba(0,0,0,.28);
  overflow: hidden;
  font-family: var(--f-label);
  font-size: 11px;
  min-width: 180px;
  min-height: 160px;
}
.floating-panel-inner { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }

/* ── Minimierte Miniatur (Etappe C, 2026-08-10) ──────────────────
   Position/Grösse setzt layoutMinimizedPanels() in app.js (Stapel
   unten links neben der Rail) — hier nur das Aussehen. min-width/
   min-height der .floating-panel müssen ausdrücklich zurückgenommen
   werden, sonst hielte die 180x160-Untergrenze die Kachel gross. */
.floating-panel.floating-panel-minimized {
  min-width: 0;
  min-height: 0;
  /* Doppelte Klasse bewusst: die allgemeine `.floating-panel`-Regel
     mit `cursor: move` steht WEITER UNTEN in dieser Datei und hätte
     bei gleicher Spezifität gewonnen. Eine Miniatur lässt sich nicht
     ziehen — sie wird geklickt. */
  cursor: pointer;
  background: var(--panel);
  border-color: var(--line);
}
.floating-panel-minimized:hover { border-color: var(--amber); }
.floating-panel-mini {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background: none; border: 0; padding: 0;
  color: var(--ink-dim);
  cursor: pointer;
}
.floating-panel-minimized:hover .floating-panel-mini { color: var(--amber); }
.floating-panel-mini svg { width: 16px; height: 16px; }
/* Ziehfläche ist JEDE freie Panel-Fläche (Titelzeile, Lücken zwischen
   den Kacheln, Bereich unter der letzten Reihe) — siehe
   FLOATING_DRAG_EXCLUDE in app.js. In der Titelzeile allein blieb neben
   den vier Icons zu wenig Platz zum Anfassen. `move` unterscheidet die
   Fenster-Geste sichtbar vom `grab` der Card-Kacheln, die eine ganz
   andere Aktion auslösen (Card aufs Board ziehen). */
.floating-panel,
.floating-panel .lp-section-title,
.floating-panel .lp-scroll,
.floating-panel .panel-type-rows { cursor: move; user-select: none; }
.floating-panel.dragging { cursor: grabbing; }
/* Kacheln und Bedienelemente behalten ihren eigenen Cursor. */
.floating-panel .panel-type-tile { cursor: grab; }
.floating-panel .panel-type-tile:active { cursor: grabbing; }
.floating-panel button { cursor: pointer; }
/* Abstand zwischen Titel und den Icon-Knöpfen — schafft zusätzlich
   Anfassfläche in der Titelzeile (Nutzer-Wunsch: 20px). Bewusst als
   padding statt margin: der Titel trägt seit der kompakten Kopfzeile
   `margin-right: auto` (schiebt die Icons nach rechts), ein zweites
   margin-right würde davon überschrieben. padding garantiert die 20px
   auch dann, wenn der auto-Freiraum bei schmalem Panel auf 0 schrumpft. */
.lp-cards-title { padding-right: 20px; }
.floating-panel.dragging, .floating-panel.resizing { transition: none; }
/* Anfasser unten rechts — verändert Breite UND Höhe gleichzeitig. */
/* GEAENDERT (2026-08-14, "Resize-Griff ist verdeckt"): war z-index:2 —
   lag damit UNTER .lp-assets-bulk-toolbar (z-index:5, siehe dort),
   die sich bei Auswahl einblendet und als volle Zeile links:0/right:0
   am unteren Rand genau die 16x16-Ecke des Griffs mit ueberdeckt.
   Betraf nur den SCHWEBENDEN Assets-Manager: dessen Griff ist der
   EINZIGE Anfasser dieser Bauart (Ecke, Breite+Hoehe gleichzeitig).
   Der angedockte Anfasser (.assets-panel-resize) ist eine andere
   Bauform — volle Kantenhoehe rechts, ausserhalb des Panels, weit
   hoeherer z-index:21 — und war nie betroffen; ebenso das
   Detail-Panel: dessen .rp-footer ist laut eigenem Kommentar dort
   bewusst ein normales Flex-Kind statt position:absolute, kollidiert
   also strukturell gar nicht erst mit dessen (ebenfalls kanten-langem)
   .right-panel-resize. Hier reicht daher ein hoeherer z-index, damit
   der Griff auch bei eingeblendetem Footer ueber ihm liegt und
   klickbar bleibt. */
.floating-panel-resize {
  position: absolute;
  right: 0; bottom: 0;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  touch-action: none;
  z-index: 6;
}
.floating-panel-resize::after {
  content: "";
  position: absolute;
  right: 3px; bottom: 3px;
  width: 7px; height: 7px;
  border-right: 2px solid var(--line);
  border-bottom: 2px solid var(--line);
  transition: border-color .15s;
}
.floating-panel-resize:hover::after,
.floating-panel.resizing .floating-panel-resize::after {
  border-right-color: var(--amber);
  border-bottom-color: var(--amber);
}
/* Rail-Icon markieren, solange sein Panel schwebt (Mini-Indikator
   oben rechts über dem Icon). */
.rail-btn.rail-btn-floating { position: relative; }
.rail-btn.rail-btn-floating::after {
  content: "";
  position: absolute;
  top: 3px; right: 3px;
  width: 5px; height: 5px;
  background: var(--amber);
  border-radius: 50%;
}

/* NEU (2026-07, docs/setzz-floating-panels-notes.md, Etappe A):
   Umschalt-Icons normal/compact im CARDS-Panel-Titel, links vom
   X-Icon. Gleiche Maße/Reset wie .lp-panel-close-x, aber mit
   aktiv-Zustand statt reiner Hover-Farbe. */
.lp-panel-mode-btn {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  background: none;
  border: 0;
  border-radius: 0;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .12s;
  flex-shrink: 0;
}
.lp-panel-mode-btn svg { width: 14px; height: 14px; }
.lp-panel-mode-btn:hover { color: var(--ink); }
.lp-panel-mode-btn.active { color: var(--amber); }
/* NEU (2026-08-14, "Panel-Header-Icons verkleinern", Punkt 1,
   SimplifyerX-Entscheidung): nur im SCHWEBENDEN Panel schmaler — die
   drei Modus-Icons (Klapp/Minimieren/Andocken) auf --btn-size-sm (24px,
   die bereits VORHANDENE kompakte Stufe). Bewusst NICHT 20px: eine
   vierte Groessenstufe fuer vier Pixel waere nach zwei Sitzungen, die
   23 Groessen auf drei reduziert haben, der falsche Weg. Icon bleibt
   bei 14px (= --btn-icon-sm, ~58% von 24px) — das ist bereits die
   fuer diese Stufe dokumentierte Groesse, kein Mitschrumpfen noetig.
   .lp-panel-close-x (Schliessen) bleibt bei --btn-size/32px, AUSDRUECKLICH
   ausgenommen: am 2026-08-12 wurden sechs Schliessen-Klassen bewusst auf
   32px vereinheitlicht, u.a. weil ein beilaeufig getroffener Knopf
   Flaeche braucht — dasselbe Argument gilt fuer ein schwebendes Panel
   mindestens so stark. Angedockt bleibt --lp-panel-mode-btn unveraendert
   bei 26px (SimplifyerX hat ausdruecklich nur "schwebend" genannt). */
.floating-panel .lp-panel-mode-btn {
  width: var(--btn-size-sm);
  height: var(--btn-size-sm);
}
/* GEÄNDERT (2026-08-12, Nutzer-Vorgabe): dieselbe .active-Klasse markierte
   bisher ZWEI verschiedene Dinge gleichzeitig amber — das Darstellungs-Icon
   (Normal/Compact, bleibt oben amber) UND den Andock-/Lösen-Knopf (schwebt
   dieses Panel gerade). Zwei Hervorhebungen gleichzeitig sind irritierend,
   nur eine bleibt. Der Andock-Knopf fällt zurück auf normale Icon-Optik —
   das Icon selbst ändert sich beim Umschalten nicht, der schwebende
   Zustand ist an der Fensterposition ohnehin sichtbar. */
.lp-panel-mode-btn[data-panel-dock-toggle].active { color: var(--ink-faint); }
/* compact: Beschriftung ausblenden, mehr Kacheln pro Reihe, quadratisch
   statt 4:3 (kein Platz mehr für den Namen darunter). */
/* Kompakt: gleiche Logik, nur kleineres Mindestmaß — 40px hält das
   angedockte Panel bei den bisherigen 5 Spalten, im breit gezogenen
   schwebenden Panel werden es entsprechend mehr. */
.lp-panel-type-body[data-panel-type-body-mode="compact"] .panel-type-rows {
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
}
.lp-panel-type-body[data-panel-type-body-mode="compact"] .panel-type-name {
  display: none;
}
.lp-panel-type-body[data-panel-type-body-mode="compact"] .panel-type-tile {
  aspect-ratio: 1;
  gap: 0;
}
.lp-panel-type-body[data-panel-type-body-mode="compact"] .panel-type-icon {
  width: 18px; height: 18px;
  opacity: 0.7;
}
/* NEU (2026-07): Group Presets — eigenständige Gestaltung statt
   Wiederverwendung der generischen Card-Typ-Kachel-Optik. Kacheln
   sind doppelt so groß wie Card-Icons, Hintergrund hellgrau/dunkel
   passend zum Theme (dieselbe --bg-deep-Variable wie die Group selbst
   nutzt), Icon als amberfarbenes Badge mit weißem Glyphen — bleibt in
   beiden Themes gleich gut lesbar, da die Badge-Farbe fix ist. */
/* NEU (2026-07): Mini-Tools-Kacheln — gleiches Prinzip wie die
   Group-Preset-Kacheln, eigenständig gehalten. */
.tool-tile-rows {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-top: 0.6rem;
}
.tool-tile {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px;
  cursor: pointer;
  transition: border-color .15s;
}
.tool-tile:hover { border-color: var(--amber); }
.tool-tile-icon {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: var(--panel);
  border-radius: 0;
  color: var(--ink);
  flex-shrink: 0;
}
.tool-tile-icon svg { width: 60%; height: 60%; }
.tool-tile-name {
  font-family: var(--f-display);
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--ink-dim);
  text-align: center;
  line-height: 1.25;
}

/* NEU (2026-07): Tool-View — ersetzt das Board, solange ein Mini-Tool
   geöffnet ist. Eigenständiges iframe, kein Kontakt zum SETzz-Scope. */
.tool-view {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.tool-view[hidden] { display: none; }
.tool-view-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0px 15px;
  border-bottom: 1px solid var(--line-soft);
  flex-shrink: 0;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover), jetzt .btn/.btn-sm. Erst bei
   der maschinellen Nachpruefung gefunden — beim ersten Durchgang durch
   die 22 uebersehen, siehe Bericht. */
.tool-view-name {
  font-family: var(--f-display);
  font-size: 13px;
  color: var(--ink);
}
.tool-view-frame {
  flex: 1;
  border: 0;
  width: 100%;
  min-height: 0;
}

/* GEAENDERT (2026-08-03): EINE Spalte ist jetzt der Normalfall, die
   Kachel fuellt die Spaltenbreite aus. Zwei Spalten entstehen erst,
   wenn das Panel wirklich breit genug ist — siehe @container-Regel
   unten. Vorher war "2 Spalten" fest verdrahtet und musste ueber zwei
   Icons in der Titelzeile von Hand umgeschaltet werden; die Breite des
   Panels wusste von dieser Entscheidung nichts. */
.group-preset-rows {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px;
  margin-top: 0.6rem;
}
/* Zwei Spalten ab 520px INNENBREITE des Presets-Bereichs (der Container
   sitzt auf dem .lp-scroll-Wrapper, siehe [data-presets-body] weiter
   unten) — damit greift die Regel im angedockten Panel UND im
   schwebenden Fenster, das man per Anfasser breiter ziehen kann.
   WICHTIG, gleiche Falle wie bei assets-panel: @container-Bedingungen
   koennen keine CSS-Variablen aufloesen, deshalb fester Pixelwert.
   520 liegt bewusst UEBER der angedockten Panelbreite (2 x 220 = 440),
   sonst waere das angedockte Panel entgegen der Vorgabe sofort
   zweispaltig.

   GEMESSEN (echte CSS, DOM-Kette .left-panel/.lp-scroll/.group-preset-rows):
     Panel 220px -> 1 Spalte, Kachel 195,1 x 38,0 px
     Panel 440px -> 1 Spalte, Kachel 415,1 x 38,0 px   (angedockt)
     Panel 520px -> 1 Spalte
     Panel 620px -> 2 Spalten, Kachel 295,5 x 38,0 px
   Die Bedingung misst die INNENbreite des Scroll-Bereichs, nicht die
   Panelbreite — der Umschlagpunkt liegt dadurch bei rund 545px
   Panelbreite, nicht bei 520. Wer den Wert aendert, sollte das im Kopf
   behalten. */
@container presets-panel (min-width: 520px) {
  .group-preset-rows { grid-template-columns: 1fr 1fr; }
}
.group-preset-tile {
  position: relative;
  /* GEAENDERT (2026-08-03): max-width 280px + margin auto sind
     entfallen — die Kachel soll die Spalte ausfuellen, egal wie breit
     sie ist. Statt aspect-ratio 6/1 eine feste Mindesthoehe: bei voller
     Panelbreite haette das Seitenverhaeltnis die Zeile auf ~70px
     aufgeblasen, obwohl der Inhalt (Miniatur 44x26 + Name) unveraendert
     bleibt. Die Hoehe haengt jetzt am Inhalt, nicht an der Breite. */
  min-height: 38px;
  /* GEÄNDERT (2026-08-04, Nutzer-Vorgabe): background var(--bg-deep) → var(--panel),
     border-radius 4px → 0px. Geprüft — .group-preset-tile wird nur im
     PRESETS-Panel gerendert (eigene wie Werks-Presets, .group-preset-builtin
     ist nur eine Zusatzklasse OBEN drauf, keine eigene Basis), in
     beiden Zuständen (angedockt/schwebend), kein anderes Panel
     verwendet diesen Klassennamen. */
  background: var(--panel);
  border: 0px solid var(--line-soft);
  border-radius: 0px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 5px;
  cursor: grab;
  transition: border-color .15s;
}
.group-preset-tile:hover { border-color: var(--amber); }
.group-preset-tile:active { cursor: grabbing; }
/* Container fuer die Spalten-Regel oben. Sitzt am Scroll-Wrapper des
   PRESETS-Panels, weil es DEN in beiden Zustaenden gibt — angedockt wie
   schwebend. (Beim ASSETS-Panel haengt der Container am angedockten
   .left-panel selbst und greift im schwebenden Fenster deshalb nicht;
   diese Einschraenkung wird hier bewusst nicht wiederholt.) */
[data-presets-body] {
  container-type: inline-size;
  container-name: presets-panel;
}
/* Aufklappbare Themen-Sektion der Werks-Presets. Beim Oeffnen des
   Panels sind alle zu (Nutzer-Vorgabe) — die 18 Werks-Presets sollen
   die eigenen nicht zuschuetten. */
.lp-preset-theme-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  padding: 10px 2px 4px;
  color: var(--ink);
  font-family: var(--f-display);
  font-size: 16px;
}
.lp-preset-theme-toggle:hover { color: var(--amber); }
.lp-preset-theme-chevron {
  flex-shrink: 0;
  width: 10px;
  color: var(--ink-faint);
  transition: transform .15s;
}
.lp-preset-theme-toggle[aria-expanded="true"] .lp-preset-theme-chevron { transform: rotate(90deg); }
.lp-preset-theme-count {
  margin-left: auto;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
}
/* NEU (2026-08): breiter als hoch (44x26 statt 24x24) — die Miniatur
   bildet ein BOARD ab, und das ist im Querformat. Bei 5 Spalten (dem
   gemessenen Maximum der 18 Werks-Presets) bleiben so ~7px je Spalte,
   klar unterscheidbar; bei 24px waeren es 3px gewesen. */
.group-preset-icon {
  width: 44px; height: 26px;
  display: grid; place-items: center;
  background: transparent;
  border-radius: 0;
  color: var(--ink-faint);
  flex-shrink: 0;
}
/* Gilt nur noch fuer den Rueckfall aufs generische Icon (nicht
   parsebares Markdown) — die Miniatur selbst fuellt den Platz ganz. */
.group-preset-icon svg { width: 60%; height: 60%; }
.group-preset-name {
  font-family: var(--f-display);
  font-size: 14px;
  letter-spacing: .02em;
  color: var(--ink-dim);
  text-align: center;
  line-height: 1.25;
}
.group-preset-tile[data-group-preset]:hover .group-preset-name,
.group-preset-name:hover { color: var(--ink); cursor: text; }
.group-preset-name .title-edit { font-size: 11px; text-align: center; }
.group-preset-delete {
  position: absolute;
  top: 4px; right: 4px;
  width: 16px; height: 16px;
  display: none;
  align-items: center; justify-content: center;
  background: rgba(var(--overlay-rgb), .8);
  border: 1px solid var(--line);
  color: var(--ink-faint);
  font-size: 8px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.group-preset-tile:hover .group-preset-delete { display: flex; }
.group-preset-delete:hover { color: var(--red, #d04040); border-color: var(--red, #d04040); }

/* NEU (2026-08): Mini-Wireframe als Preset-Miniatur — fuer EIGENE und
   Werks-Presets gleichermassen (siehe presetWireframeHtml()). Ersetzt
   zwei Dinge: das generische Icon, das alle 18 Werks-Presets identisch
   aussehen liess, und die erste, textbasierte Vorschau, die aus der
   Kachel herauslief.

   Der Ueberlauf kann hier bauartbedingt nicht wiederkehren: die Spalten
   haben flex-grow und KEIN min-width, die Miniatur fuellt also immer
   genau den vorhandenen Platz — unabhaengig von der Spaltenzahl. */
.group-preset-icon .preset-mini {
  display: flex;
  align-items: stretch;
  gap: 2px;
  width: 100%;
  height: 100%;
}
.preset-mini-col {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex-basis: 0;          /* Breite rein ueber flex-grow = Breitenfaktor */
  min-width: 0;           /* erlaubt echtes Schrumpfen, verhindert Ueberlauf */
  /* Leere Spalte bleibt als Streifen sichtbar — sonst waere eine Spalte
     ohne Karten von "gar keine Spalte" nicht zu unterscheiden.

     BEWUSST aus --ink-faint abgeleitet (derselben Farbe wie die Balken)
     statt aus einem eigenen Token: --line-soft war der erste Versuch und
     ist im HELLEN Theme exakt identisch mit dem Kachelhintergrund
     (beide rgb(236,236,236)) — die leere Spalte war dort unsichtbar.
     Eine Ableitung aus der Balkenfarbe kann per Konstruktion nicht mit
     dem Untergrund zusammenfallen, in keinem Theme. */
  background: color-mix(in srgb, var(--ink-faint) 22%, transparent);
  border-radius: 1px;
}
.preset-mini-col i {
  display: block;
  flex: 1 1 0;
  min-height: 2px;
  background: var(--ink-faint);
  border-radius: 1px;
  opacity: .75;
}
.group-preset-tile:hover .preset-mini-col i { opacity: 1; }

/* Drag-Over-Markierung, wenn ein Group-Preset über eine bestehende
   Group gezogen wird — signalisiert "hier wird direkt danach
   eingefügt". */
.group-wrap.group-drop-target { outline: 2px dashed var(--amber); outline-offset: 2px; }

/* GEÄNDERT (2026-08-19, "CARDS-Panel: Höhe, Kacheln, Sortierung"):
   aspect-ratio war 4/3 (breiter als hoch), jetzt quadratisch — dieselbe
   Form, die der Kompakt-Modus weiter unten schon nutzte. */
.panel-type-tile {
  background: var(--panel);
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 0px solid;
  box-shadow: 3px 3px 4px rgba(0,0,0,0.09);
  cursor: grab;
  padding: 4px;
}
.panel-type-tile:active { cursor: grabbing; }
/* GEÄNDERT (2026-08-19): war 15px/opacity 0.5 — gemessener Kontrast
   (WCAG-Luminanzformel, --ink-dim auf --panel im Ruhezustand) lag bei
   2.18:1 (dark) / 2.22:1 (light), unter der 3:1-Mindestschwelle für
   grafische Objekte. Bei 20px/0.8 liegt Dark (der bindende Fall) bei
   3.32:1, Light bei 4.14:1 — beide über der Schwelle. Das Label
   (.panel-type-name unten) bleibt bewusst bei opacity 0.5: das Icon
   soll den Kartentyp erkennbar machen, das Label bestätigt nur —
   der Deckkraft-Unterschied zwischen beiden ist Absicht, kein
   Versehen, das noch angeglichen werden müsste. */
.panel-type-icon {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  color: var(--ink-dim);
  opacity: 0.8;
  transition: opacity .15s;
}
.panel-type-tile:hover .panel-type-icon { opacity: 1; }
.panel-type-icon svg { width: 100%; height: 100%; }
.panel-type-tile:hover .panel-type-icon { color: var(--ink); }
.panel-type-name {
  display: inherit;
  font-family: var(--f-display);
  font-size: 11px;
  letter-spacing: 0em;
  text-transform: none;
  color: var(--ink-dim);
  text-align: center;
  line-height: 1.2;
  opacity: 0.5;
  transition: color .15s;
}
.panel-type-tile:hover .panel-type-name { color: var(--ink); }

/* column highlight while a panel-type tile is being dragged over it */
.col.panel-drop-target { outline: 2px dashed var(--amber); outline-offset: -2px; }

/* ── World gallery pagination ─────────────────────────────── */
.world-gallery-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
}
.world-add-btn {
  width: 24px; height: 24px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  transition: color .15s, border-color .15s;
}
.world-add-btn svg { width: 12px; height: 12px; }
.world-add-btn:hover { color: var(--amber); border-color: var(--amber); }
/* R3 (2026-08-15): siehe .world-tile-add:disabled oben — gleicher Fall,
   zweite Fundstelle (Footer-Pager statt Dashboard-Kachelreihe). */
.world-add-btn:disabled { opacity: .55; cursor: not-allowed; }
.world-add-btn:disabled:hover { color: var(--ink-faint); border-color: var(--line); }
.world-pager-nums { display: flex; gap: 6px; }
.world-pager-btn {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  transition: color .15s, border-color .15s;
}
.world-pager-btn:hover { color: var(--ink); border-color: var(--line); }
.world-pager-btn.active { color: var(--amber); border-color: var(--amber); }

.world-gallery {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.world-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 10px;
  cursor: pointer;
  /* NEU (2026-07): die Theme-Miniatur ragt bei Hover unten aus dem
     Cover heraus — ohne diese Anhebung würde sie von der im Raster
     nachfolgenden Kachel überdeckt. Nur im Hover-Zustand, damit die
     normale Stapelreihenfolge unberührt bleibt. */
  position: relative;
}
.world-tile:hover { z-index: 5; }
/* NEU (2026-07, docs/setzz-themes-notes.md): Kachel mit Coverbild
   liegt auf var(--panel) — neutral und unabhängig vom Board-Theme,
   damit das Dashboard im Ruhezustand clean bleibt. Kacheln OHNE Cover
   überschreiben das weiter unten mit var(--bg-deep). */
.world-tile-cover {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--panel);
  border: 1px solid var(--line);
  /* NEU (2026-07): war overflow:hidden — die Theme-Miniatur soll unten
     bewusst ein Stück über den Cover-Rand hinausragen. Das Cover-Bild
     selbst braucht das Clipping nicht (object-fit:cover beschneidet
     bereits innerhalb der eigenen Box), und alle übrigen Elemente im
     Cover (Auswahl-Haken, Farbpunkt, Aktions-Knöpfe, Initiale) liegen
     ohnehin vollständig innerhalb. */
  overflow: visible;
  cursor: pointer;               /* clicking the cover switches to this world */
  transition: border-color .15s;
  /* FIX (2026-08-15, Regression aus genau dem overflow:visible-Wechsel
     oben, e535cf2, 2026-07-30): .world-tile-cover ist ein Flex-Item
     (.world-tile ist display:flex; flex-direction:column). Ein
     Flex-Item OHNE overflow:hidden bekommt ein automatisches
     min-height, das sich am eigenen Inhalt orientiert — hier am
     Coverbild (object-fit:cover schneidet zwar zu, aber erst NACHDEM
     die Box ihre Höhe hat). Ist das Bild höher als die eingestellte
     aspect-ratio (Hochformat/quadratisch bei breiter Ratio-Vorgabe),
     gewinnt diese Mindesthöhe gegen aspect-ratio, und die Kachel
     rutscht auf das Bildverhältnis statt auf die Welt-Ratio. Mit
     overflow:hidden (der Zustand VOR e535cf2) griff dasselbe
     min-height nie, weil es dort automatisch auf 0 fällt — das war
     der eigentliche, unausgesprochene Träger der Funktion, nicht nur
     das Clipping. min-height:0 stellt exakt das wieder her, ohne das
     bewusste overflow:visible der Theme-Miniatur (unten, bottom:-12px)
     anzutasten. Gemessen: FaceFlow Animations (Hochformat-Cover
     0.75, Welt-Ratio 1:1) sprang vorher auf 295×394 (=Bildverhältnis)
     statt 295×295. */
  min-height: 0;
}
/* NEU: Cover-Ratio je WORLD-Gruppe (Dashboard-Ordner) — .world-group-body
   setzt --worldgroup-cover-ratio per Inline-Style (renderDashboard(),
   world-sync.js), falls die Gruppe eine eigene Ratio hat. Ohne gesetzte
   Gruppen-Ratio (z.B. "New BOARD"/"New WORLD"-Kacheln außerhalb jeder
   Gruppe) gilt weiterhin der 16:9-Standard oben. */
.world-group-body .world-tile-cover { aspect-ratio: var(--worldgroup-cover-ratio, 16 / 9); }
.world-tile.active .world-tile-cover { border-color: var(--amber); }
.world-tile-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Kachel OHNE Coverbild: neutraler var(--bg-deep) statt des früheren
   warmen Amber-Schleiers (rgba(200,162,63,.1)) — gleiches
   "clean by default"-Prinzip wie die Hover-Vorschau. */
.world-tile-initial {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--f-display);
  font-size: 28px;
  color: var(--ink-faint);
  background: var(--bg-deep);
}

/* ── Theme-Miniatur auf der Board-Kachel ──────────────────────
   Dieselbe Vorschau-Komponente wie in der Theme-Auswahl
   (.lp-theme-preview), hier nur eingeblendet solange die Kachel
   überfahren wird. Rechts angeschlagen und leicht über das
   Coverbild geschoben, wie in den Notizen festgelegt. */
.world-tile-theme-preview {
  position: absolute;
  right: 4px;
  /* Unten angeschlagen und bewusst ein Stück über die Cover-Unterkante
     hinaus (siehe overflow:visible oben) — überlagert dabei den
     Accent-Farbpunkt (.world-tile-color-swatch, z-index 4). Das ist
     gewollt: die Miniatur zeigt die Accent Color selbst als Kreis,
     die Information geht also nicht verloren. Höherer z-index als
     Farbpunkt und Aktions-Knöpfe, damit sie sicher obenauf liegt. */
  bottom: -12px;
  transform: translateX(6px);
  z-index: 6;
  opacity: 0;
  pointer-events: none;          /* nie den Klick aufs Cover abfangen */
  transition: opacity .15s, transform .15s;
}
.world-tile-cover:hover .world-tile-theme-preview {
  opacity: 1;
  transform: translateX(0);
}
/* Mit Schlagschatten, damit sie als aufliegende Ebene über dem Cover
   lesbar bleibt.
   GEÄNDERT (2026-08-13, Nutzer-Vorgabe): zeigte Light+Dark als Paar
   (siehe dashThemePreviewMarkup()) statt einer Karte — 96px je
   Ausprägung, Gesamtbreite des Paars damit ~200px (96+96+8 Gap). War
   zu breit.
   GEÄNDERT (2026-08-15, Nutzer-Vorgabe): quadratisch statt 96×51 —
   46×46 je Ausprägung, Gesamtbreite des Paars damit ~100px (46+46+8),
   etwa die Hälfte. Nur DIESER Override betrifft die Dashboard-Vorschau
   — die Basisklasse .lp-theme-preview (45×60, siehe oben im BOARD-
   SETTINGS-Panel) bleibt unverändert, beide teilen sich zwar die
   Klasse, aber nicht die effektive Größe. */
.world-tile-theme-preview .lp-theme-preview {
  width: 46px;
  height: 46px;
  padding: 4px;
  border-color: rgba(var(--overlay-rgb), .55);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
}
.world-tile-theme-preview .lp-theme-preview-pair { gap: 8px; }
/* Touch-Geräte haben keinen Hover — die Miniatur wäre dort entweder
   unsichtbar oder müsste den Tipp abfangen, der die Kachel öffnet.
   Bewusste Entscheidung: auf Zeigern ohne Hover gar nicht anzeigen,
   statt die primäre Öffnen-Geste zu verkomplizieren. Es ist ein
   optionales Extra, keine Kernfunktion. */
@media (hover: none) {
  .world-tile-theme-preview { display: none; }
}

/* small color swatch, bottom-right of the cover, hover-only */
.world-tile-color-swatch {
  position: absolute;
  right: 4px; bottom: 4px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--swatch-color, var(--amber));
  border: 1px solid rgba(var(--overlay-rgb), .6);
  opacity: 0;
  transition: opacity .15s, transform .15s;
  z-index: 4;
}
.world-tile-cover:hover .world-tile-color-swatch { opacity: 1; }
.world-tile-color-swatch:hover { transform: scale(1.15); }

/* NEU: Mehrfachauswahl-Checkbox, top-left of the cover — gleiche
   Optik wie .lp-asset-select im Assets Manager, aber hover-only
   sichtbar und bleibt eingeblendet solange "checked". */
.world-tile-select {
  position: absolute;
  left: 6px; top: 6px;
  width: 15px; height: 15px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: rgba(var(--overlay-rgb), .85);
  cursor: pointer;
  z-index: 4;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  opacity: 0;
  transition: opacity .15s, border-color .12s, background-color .12s;
}
.world-tile-cover:hover .world-tile-select,
.world-tile-select.checked { opacity: 1; }
.world-tile-select:hover { border-color: var(--amber); }
.world-tile-select.checked { background: var(--amber); border-color: var(--amber); }
.world-tile-select.checked::after {
  content: "";
  width: 4px; height: 7px;
  border: solid rgba(0,0,0,.75);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}

/* NEU (2026-07): "GROUP to Text" / "Text to GROUP" — Popup am
   Group-Header, dasselbe Positionierungs-Muster wie .color-popover
   (fixed, an den auslösenden Button geankert). Eigene, einfache
   Textarea-Styles statt .lp-tb-input wiederzuverwenden — dessen
   Text ist bewusst transparent (color: transparent) und setzt ein
   separates Syntax-Highlighting-Overlay voraus, das dieses Popup
   nicht mitbringt. */
/* NEU (2026-07): Ergebnis-Popup nach "Share" — zeigt den fertigen
   Link + Copy-Button, gleiches Positionierungs-Muster wie
   .group-template-popover/.color-popover (fixed, am auslösenden
   Button geankert). */
.share-link-popover {
  position: fixed;
  z-index: var(--z-popover-menu);   /* war 200 — siehe Layer-Ordnung in :root */
  display: none;
  flex-direction: column;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--panel-shadow);
  padding: 12px;
  width: 320px;
}
.share-link-popover.open { display: flex; }
.share-link-input {
  width: 100%;
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink);
  background: var(--bg-deep);
  border: 1px solid var(--line);
  padding: 8px 10px;
  box-sizing: border-box;
}
.share-link-manage {
  display: flex;
  gap: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--line-soft);
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm fuer beide (.share-link-delete
   zusaetzlich .btn-danger, einer der drei Rot-Knoepfe). flex:1 bleibt
   strukturell (gleich breite Geschwister in einer Zeile).
   .is-disabled-state BEWUSST NICHT entfernt, Nutzer-Bestaetigung
   2026-08-13: markiert den aktuellen Zustand des Toggles ("Link ist
   aus"), keine Hover-Deko — dieselbe legitime Verwendung wie die
   zwoelf Aktiv-Zustand-Ausnahmen dieser Runde (Amber als Zustands-,
   nicht Aktionsfarbe), nur hier erst waehrend der Umstellung entdeckt
   statt vorab in der Ausnahmeliste. KEIN Hausregel-Verstoss bei der
   naechsten Amber-Pruefung — absichtlich stehen gelassen, nicht
   uebersehen. */
.share-link-toggle, .share-link-delete { flex: 1; }
.share-link-toggle.is-disabled-state { color: var(--amber); border-color: var(--amber); }

.group-template-popover {
  position: fixed;
  z-index: var(--z-popover-menu);   /* war 200 — siehe Layer-Ordnung in :root */
  display: none;
  flex-direction: column;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--panel-shadow);
  padding: 12px;
  width: 320px;
}
.group-template-popover.open { display: flex; }
.gtp-section-title {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.gtp-actions { display: flex; align-items: center; gap: 6px; }
.gtp-output, .gtp-input {
  width: 100%;
  height: 100px;
  font-family: var(--f-data);
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg-deep);
  border: 1px solid var(--line);
  padding: 8px 10px;
  resize: vertical;
  box-sizing: border-box;
}
.gtp-output { color: var(--ink-dim); }
.gtp-footer { display: flex; align-items: center; gap: 8px; }

.color-popover {
  position: fixed;
  z-index: var(--z-popover-menu);   /* war 200 — siehe Layer-Ordnung in :root */
  display: none;
  flex-direction: column;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--panel-shadow);
  padding: 10px;
  width: 170px;
}
.color-popover.open { display: flex; }
.color-popover [data-color-wheel] {
  width: 100%; height: 32px;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
}
.color-hex-input {
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--ink);
  background: var(--bg-deep);
  border: 1px solid var(--line);
  padding: 5px 7px;
  text-transform: uppercase;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover), jetzt .btn/.btn-sm fuer
   beide Verwender. Blockbreite im Dashboard-Farbwaehler-Popover kommt
   weiterhin ueber align-items:stretch von .color-popover (flex column,
   kein eigenes width noetig) — unveraendert durch die Umstellung. */
.lp-bs-accent-reset-btn { flex-shrink: 0; }

/* NEU (2026-07): ersetzt die vorherigen zwei separaten Buttons
   (world-tile-cover-edit unten rechts, world-tile-delete oben rechts)
   durch einen einzigen Kebab-Trigger — Rename/Cover/Delete/Duplicate
   leben jetzt gemeinsam im world-tile-menu (Dropdown, siehe .panel-menu
   als Basis-Klasse). Alte Einzel-Button-Regeln entfernt. */
.world-tile-actions {
  position: absolute;
  top: 4px; right: 4px;
  display: flex;
  gap: 4px;
  z-index: 4;
}
.world-tile-menu-btn {
  width: 24px; height: 24px;
  border-radius: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(var(--overlay-rgb), .8);
  border: 1px solid var(--line);
  color: var(--ink-faint);
  transition: color .15s, border-color .15s;
  cursor: pointer;
}
.world-tile:hover .world-tile-menu-btn { display: flex; }
.world-tile-menu-btn svg { width: 14px; height: 14px; }
.world-tile-menu-btn:hover { color: var(--amber); border-color: var(--amber); }

/* Pin-Button (NEU 2026-07): wie der Kebab nur bei Hover sichtbar,
   AUSSER die Board ist bereits gepinnt — dann bleibt es (gefüllt,
   amber) dauerhaft sichtbar, damit der Zustand auch ohne Hover
   erkennbar ist. */
.world-tile-pin-btn {
  width: 24px; height: 24px;
  border-radius: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(var(--overlay-rgb), .8);
  border: 1px solid var(--line);
  color: var(--ink-faint);
  transition: color .15s, border-color .15s;
  cursor: pointer;
}
.world-tile:hover .world-tile-pin-btn,
.world-tile-pin-btn.pinned { display: flex; }
.world-tile-pin-btn svg { width: 13px; height: 13px; }
.world-tile-pin-btn:hover { color: var(--amber); border-color: var(--amber); }
.world-tile-pin-btn.pinned { color: var(--amber); border-color: var(--amber); }
.world-tile-pin-btn.pinned svg { fill: currentColor; }

.world-tile-menu button svg { width: 12px; height: 12px; margin-right: 6px; vertical-align: -2px; color: var(--ink-faint); }
.world-tile-menu button:hover svg { color: var(--amber); }
.world-tile-menu .menu-danger:hover { color: var(--red, #d04040); }
.world-tile-menu .menu-danger:hover svg { color: var(--red, #d04040); }

.world-tile-name {
  font-family: var(--f-data);
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--ink-dim);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color .15s;
}
.world-tile.active .world-tile-name { color: var(--amber); }
/* NEU (2026-08-12, --ink-hi): nur Dark Theme — Light behaelt den
   bestehenden Amber-Namen unveraendert, siehe Bericht. */
:root:not([data-theme="light"]) .world-tile.active .world-tile-name { color: var(--ink-hi); }
.world-tile[data-world-rename] .world-tile-name:hover { color: var(--ink); cursor: text; }
.world-tile-name .title-edit { font-size: 10.5px; text-align: left; padding: 2px 4px; }

/* title now lives ON the cover, bottom-left, with a solid backing
   so it stays legible over any image */
.world-tile-title {
  display: block;
  width: 100%;
  padding: 5px 4px 2px;
  font-family: var(--f-display);
  /* GEÄNDERT (2026-08-13, Nutzer-Vorgabe): war 13px. */
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color .15s;
}
.world-tile.active .world-tile-title { color: var(--amber); }
:root:not([data-theme="light"]) .world-tile.active .world-tile-title { color: var(--ink-hi); }
/* NEU (2026-07): kennzeichnet ein Open BOARD in der Dashboard-Kachel —
   sonst ließe sich der Board-Typ erst nach dem Öffnen erkennen. "Open"
   ist bewusst nur der UI-Begriff; intern heißt der Typ weiterhin "free"
   (siehe createWorld() in world-sync.js). */
.world-tile-layout-badge {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 5px;
  vertical-align: 1px;
  font-family: var(--f-data, var(--f-panel));
  font-size: 9px;
  font-weight: 500;
  letter-spacing: .06em;
  color: var(--ink-faint);
  background: var(--line-soft);
  border-radius: 2px;
}
/* NEU (2026-08, Team-Freigabe-Knopf): gleiches Badge-Muster wie
   .world-tile-layout-badge (OPEN), aber mit Akzentfarbe statt Grau —
   anders als der reine Typ-Hinweis "OPEN" markiert dieses Badge einen
   AKTIVEN, vom Besitzer bewusst gesetzten Zustand ("andere können
   das sehen"), das soll aus der neutralen Optik herausstechen. */
.world-tile-team-badge {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 5px;
  vertical-align: 1px;
  font-family: var(--f-data, var(--f-panel));
  font-size: 9px;
  font-weight: 500;
  letter-spacing: .06em;
  color: var(--amber);
  background: var(--amber-dim);
  border-radius: 2px;
}
.world-tile-title[data-world-rename] { cursor: text; }
.world-tile-title[data-world-rename]:hover { color: var(--ink); }
.world-tile-title .title-edit {
  font-family: var(--f-display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .02em;
  text-align: left;
  padding: 2px 4px;
}

/* NEU (2026-07): Images/Videos/Audio-Zählung unter dem Board-Namen —
   Zahl links vom Icon, kleine, gedimmte Darstellung. */
.world-tile-counts {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  padding: 0 4px;
}
.world-tile-count-item {
  display: flex;
  align-items: center;
  gap: 3px;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
}
.world-tile-count-item svg { width: 11px; height: 11px; }
.world-tile-count-item span { font-family: var(--f-label); font-size: 11px; font-weight: 600; color: var(--ink-dim); }

/* reorder: up/down, right edge of the cover, vertically centered */
.world-tile-reorder {
  position: absolute;
  right: 4px; top: 50%;
  transform: translateY(-50%);
  display: none;
  flex-direction: column;
  gap: 3px;
  z-index: 3;
}
.world-tile-move {
  width: 16px; height: 16px;
  display: grid; place-items: center;
  background: rgba(var(--overlay-rgb), .78);
  color: var(--ink-dim);
  font-size: 7px;
  border: 0;
  opacity: 0;
  transition: opacity .15s, color .15s;
}
.world-tile-cover:hover .world-tile-move { opacity: 1; }
.world-tile-move:hover { color: var(--amber); }
.world-tile-move:disabled { opacity: .15 !important; pointer-events: none; }

.world-tile-add {
  align-items: center;
  justify-content: center;
}
.world-tile-add .world-tile-cover {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 12 / 4;
  background: transparent;
  border: 0;
  color: var(--ink-faint);
  transition: color .15s;
}
.world-tile-add:hover .world-tile-cover { color: var(--amber); }
.world-tile-add-icon svg { width: 18px; height: 18px; }
/* R3 (2026-08-15): "New BOARD"/"New WORLD" sichtbar+gesperrt fuer Demo
   statt ausgeblendet — gleiche Werte wie die gesperrte CARDS-Kachel
   (.panel-type-pro-locked, app.js). */
.world-tile-add:disabled { opacity: .55; cursor: not-allowed; }
.world-tile-add:disabled:hover .world-tile-cover { color: var(--ink-faint); }

/* ── "Import BOARD" tile (Ungrouped-Row, Dashboard) — click opens a
   file picker, drag&drop of a BACKUP .zip works directly on it. ── */
.world-tile-import {
  display: flex;
  flex-direction: column;
  gap: 6px;
  justify-content: center;
  cursor: pointer;
}
.world-tile-import .world-tile-cover {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 12 / 4;
  background: transparent;
  border: 3px dashed var(--line);
  color: var(--ink-faint);
  transition: color .15s, border-color .15s, background .15s;
}
.world-tile-import:hover .world-tile-cover,
.world-tile-import.drag-over .world-tile-cover {
  color: var(--amber);
  border-color: var(--amber);
  background: rgba(200, 160, 60, .08);
}
/* NEU (2026-08-19, "Dashboard: Filterknöpfe und Import-Rahmen"): var(--line)
   im Light Theme (#e2e2e2) gegen den Dashboard-Hintergrund (#e6e6e6) lag
   bei ~1.02:1 Kontrast — praktisch unsichtbar. #494949 gegen den dunklen
   Hintergrund (#292929) liegt bereits bei ~1.6:1, also bewusst NUR hier im
   Light Theme angehoben, Dark Theme unveraendert (var(--line) bleibt dort
   in Kraft). --line selbst traegt an vier weiteren Stellen im File
   gestrichelte Rahmen (Label-Vorschau-Leerzustand, Listen-"Spalte
   hinzufügen", Embed-Leerzustand) — eine Aenderung an --line selbst haette
   alle vier mitgezogen; dieser feste Wert ist bewusst NUR auf diese eine
   Kachel begrenzt. #808080 gegen #e6e6e6 misst ~3.17:1 (WCAG-Luminanz),
   über der 3:1-Mindestschwelle fuer grafische Objekte.

   ⚠️ NICHT auf var(--line) "vereinheitlichen" — das ist kein Aufräumen,
   sondern zieht die anderen vier Stellen ungewollt mit (siehe oben) und
   macht dort exakt das gleiche Kontrastproblem neu auf, das diese Regel
   hier beheben sollte. Falls einer der vier anderen Orte irgendwann
   denselben Fix braucht: eigene Regel dort, aus genau diesem Grund. */
:root[data-theme="light"] .world-tile-import .world-tile-cover { border-color: #808080; }
.world-tile-import-icon svg { width: 20px; height: 20px; }
.world-tile-import .world-tile-title { color: var(--ink-faint); text-align: center; }
.world-tile-import:hover .world-tile-title,
.world-tile-import.drag-over .world-tile-title { color: var(--amber); }

/* ═══ TOP NAV ══════════════════════════════════════════════ */
/* BUGFIX (2026-08-03): openTool() setzt topnav.hidden = true, um die
   Board-TopNav waehrend der Tool-Ansicht auszublenden — wirkungslos,
   solange hier `display: flex` steht: eine Autoren-Regel schlaegt das
   `display: none`, das der Browser fuer [hidden] mitbringt. Die
   TopNav ueberlappte dadurch den Zurueck-Knopf der Tool-Ansicht. Genau
   diese Falle steht schon einmal in dieser Datei dokumentiert
   (.floating-panel-layer[hidden]) — sie ist beim naechsten Element mit
   eigenem display-Wert trotzdem wieder aufgetreten. Wer display setzt,
   muss [hidden] mitschreiben. */
/* ── Konflikt-Banner (NEU 2026-08, optimistische Sperre) ─────────────
   Siehe [data-board-conflict] in index.html und
   handleBoardSaveConflict() in world-sync.js.

   Sitzt UNTER der topnav (top: 38px = deren Höhe), nicht darüber: der
   Board-Name im Breadcrumb bleibt lesbar, während der Hinweis steht —
   man soll ja erkennen können, um WELCHES Board es geht.

   Rohe z-index-Zahl statt einer --z-*-Variable, bewusst: das Banner
   sortiert nur gegen die lokale Board-Chrome (.topnav = 5), nicht gegen
   die globale App-Reihenfolge (Toast/Dialog/Popover). Genau die
   Unterscheidung, die der Kommentar an den --z-*-Variablen oben trifft. */
.board-conflict {
  position: absolute;
  top: 38px;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 9px 14px;
  background: var(--panel-2);
  border-top: 1px solid var(--amber);
  border-bottom: 1px solid var(--amber);
}
.board-conflict[hidden] { display: none; }
.board-conflict-text {
  font-family: var(--f-data);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink);
  flex: 1 1 240px;
}
.board-conflict-text strong {
  color: var(--amber);
  font-weight: 600;
  margin-right: 5px;
}
.board-conflict-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover bei "Load other version"),
   jetzt .btn/.btn-sm. "Overwrite anyway" traegt zusaetzlich .btn-danger
   im Markup — eines der drei Rot-Knoepfe dieser Runde, roter Hover
   kommt jetzt aus der globalen .btn-danger:hover-Regel oben, hier
   nichts Eigenes mehr noetig. */

/* ── Sitzungsverlust-Banner (NEU 2026-08-12) ──────────────────────────
   Siehe [data-session-lost] in index.html und handleAutoSaveAuthLoss()
   in world-sync.js.

   Anders als .board-conflict (lokal, sitzt unter der Board-Topnav,
   rohe z-index: 20 gegen die Board-Chrome) ist dieser Banner KONTOWEIT:
   fixed am oberen Viewport-Rand, ueber Dashboard UND Board sichtbar.
   --z-toast statt einer rohen Zahl, weil er wie ein Toast auch ueber
   Floating Panels und der Lightbox/Focus-Overlay stehen muss — nur
   dauerhaft statt verschwindend (siehe Layer-Ordnung oben in :root).
   Rot statt Amber (anders als .board-conflict): keine Verhandlungssache
   zwischen zwei Optionen, sondern ein Zustand, der eine Aktion
   ausserhalb des Boards verlangt (Neuanmeldung). */
.session-lost-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 9px 14px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--red, #d04040);
}
.session-lost-banner[hidden] { display: none; }
.session-lost-text {
  font-family: var(--f-data);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink);
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm. War dauerhaft rot, aber
   "Sign in again" ist eine Wiederherstellungs-Aktion, keine destruktive
   — gehoert NICHT zu den drei .btn-danger-Knoepfen dieser Runde. Die
   Dringlichkeit traegt weiterhin der rote Banner-Text, nicht mehr der
   Knopf selbst. text-decoration:none bleibt strukturell (Anker-Tag). */
.session-lost-btn { text-decoration: none; flex-shrink: 0; }

.topnav[hidden] { display: none; }
.topnav {
  flex-shrink: 0;
  height: 38px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px 0 14px;
  border-bottom: 0px solid var(--line-soft);
  position: absolute;
  top: 0px;
  left: 50px;
  z-index: 5;
  /* gleicher Hintergrund wie das Board (.workspace) und die Minimap
     (.board-minimap) — garantierte Basis-Ebene je Theme + die vom
     Nutzer gewählte Tint-Farbe darüber (--board-bg-tint). */
  background-color: var(--board-bg-base);
  background-image: linear-gradient(var(--board-bg-tint), var(--board-bg-tint));
}
.topnav-board-name {
  font-family: var(--f-label);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-dim);
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.topnav-world-name { color: var(--board-ink-faint); }
.topnav-sep { color: var(--line); font-size: 12px; }
.topnav-tools { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.topnav-profile { margin-left: auto; flex-shrink: 0; display: flex; align-items: center; gap: 10px; }

/* NEU (2026-07): kleiner Share-Button, ersetzt die vorherige
   Username/Usertype-Anzeige an dieser Stelle (die lebt jetzt im
   Settings-Panel, siehe .settings-profile weiter unten). Bewusst
   invertierte Farben je Theme: hell im Light-Theme (weißer
   Hintergrund, schwarzer Text), dunkel im Dark-Theme (schwarzer
   Hintergrund, weißer Text) — hebt sich dadurch bewusst von den
   übrigen, eher zurückhaltenden Icon-Buttons ab. */
.share-board-btn {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 6px 14px;
  border: 1px solid var(--ink);
  cursor: pointer;
  transition: opacity .15s;
}
.share-board-btn:hover { opacity: .8; }
:root[data-theme="dark"] .share-board-btn,
[data-theme="dark"] .share-board-btn { background: #000; color: #fff; border-color: #000; }
:root[data-theme="light"] .share-board-btn,
[data-theme="light"] .share-board-btn { background: #fff; color: #000; border-color: #fff; }

/* Settings-Panel: großes Profilbild + Name + Typ oben, ersetzt die
   vorherige Topnav-Anzeige. */
.settings-profile {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 12px;
  border-bottom: 1px solid var(--line-soft);
}
/* NEU: Avatar im Settings-Panel ist jetzt anklickbar (Label um ein
   verstecktes Datei-Feld) — "Change"-Hinweis blendet sich nur bei
   Hover über den Avatar ein, gleiches hover-only Muster wie an
   anderen Stellen der App (z.B. Board-Kachel-Aktionen). */
.settings-profile-avatar-wrap {
  position: relative;
  width: 56px; height: 56px;
  flex-shrink: 0;
  cursor: pointer;
  display: block;
}
.settings-profile-avatar {
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--line);
  flex-shrink: 0;
  transition: opacity .15s;
}
.settings-profile-avatar-wrap:hover .settings-profile-avatar { opacity: .5; }
.settings-profile-avatar-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .04em;
  color: var(--ink);
  opacity: 0;
  transition: opacity .15s;
  pointer-events: none;
}
.settings-profile-avatar-wrap:hover .settings-profile-avatar-hint { opacity: 1; }
.settings-profile-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.settings-profile-name {
  font-family: var(--f-label);
  font-size: 14px;
  color: var(--amber);
  letter-spacing: .02em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.settings-profile-type {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
/* NEU (2026-07): Logout als letztes Element im Settings-Panel, statt
   per PHP nachträglich in die Rail eingefügtem Icon-Button. */
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn + .btn-danger (einer der drei
   Rot-Knoepfe) — bewusst OHNE Groessen-Modifier: er ist die einzige
   Aktion in seiner Zeile (Kontext = Footer/Panel, nicht Toolbar), bleibt
   also medium trotz voller Breite. Layout (volle Breite, Icon+Text-Zeile)
   bleibt strukturell.
   GEAENDERT (2026-08-15, Nutzer-Vorgabe/SimplifyerX): jetzt .btn-sm +
   .btn-extra (Rahmen statt Flaeche), passend zu den drei Save-Knoepfen
   darueber. width:100% entfaellt dabei bewusst — ein duenner Rahmen ueber
   volle Panel-Breite waere eine neue, vierte Erscheinungsform gewesen,
   die niemand verlangt hat; natuerliche Breite reiht ihn stattdessen in
   dieselbe Familie wie SAVE NAME/SAVE EMAIL/CHANGE PASSWORD ein.
   .btn-danger bleibt: Logout ist weiterhin eine Aktion mit Folgen (beendet
   die Sitzung), der rote Hover bleibt das Signal dafuer — nur die Flaeche
   darunter aendert sich. */
.settings-logout-btn { display: flex; align-items: center; gap: 8px; margin-top: 12px; margin-left: auto; }
.settings-logout-btn svg { width: 16px; height: 16px; flex-shrink: 0; }

/* ── KI-Label Bibliothek (NEU 2026-08-16, Etappe 3a) ──
   docs/setzz-ai-label-notes.md. Eigene rp-detail-group zwischen Konto
   und Tooltip-Schalter — siehe renderLabelLibraryBlock() (app.js). */
.settings-label-header { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.settings-label-title {
  font-family: var(--f-label);
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-hi);
}
.settings-label-count {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-dim);
}
.settings-label-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.settings-label-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border: 1px solid var(--line-soft);
  border-radius: 4px;
}
.settings-label-row.is-archived { opacity: .55; }
.settings-label-thumb {
  width: 32px;
  height: 32px;
  object-fit: contain;
  background: var(--panel-2);
  border-radius: 3px;
  flex-shrink: 0;
}
.settings-label-name {
  flex: 1;
  min-width: 0;
  font-size: 12px;
  color: var(--ink-hi);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-label-default-badge,
.settings-label-archived-badge {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  flex-shrink: 0;
}
.settings-label-default-badge { color: var(--amber); border: 1px solid var(--amber); }
.settings-label-archived-badge { color: var(--ink-dim); border: 1px solid var(--line-soft); }
.settings-label-row-actions { display: flex; gap: 6px; flex-shrink: 0; }
/* .settings-label-upload-btn.is-disabled-state: eigene Regel statt die
   bestehende .share-link-toggle.is-disabled-state zu erweitern — dieselbe
   Bedeutung (Grenze erreicht), aber ein anderes Element (Label statt
   Checkbox), deshalb ein eigener, aber bewusst gleich aussehender Ausdruck. */
.settings-label-upload-btn.is-disabled-state { color: var(--amber); border-color: var(--amber); pointer-events: none; opacity: .7; }

/* ── Source/Tool & AI-Modell-Listen-Verwaltung (NEU 2026-07) ── */
.settings-meta-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 20px;
}
.settings-meta-block { margin-bottom: 18px; }
.settings-meta-title {
  font-family: var(--f-display);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0em;
  text-transform: none;
  color: var(--ink);
  margin-bottom: 6px;
}
.settings-meta-rows {
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 208px;
  overflow-y: auto;
  border: 1px solid var(--line-soft);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
/* Zusammengelegt (2026-08, Aufräumrunde) mit .lp-assets-scroll — echter
   Doppelgänger, siehe Bestandsaufnahme oben bei .zoom-viewport. Das
   zusätzliche `display: block` von .lp-assets-scroll bleibt dort als
   eigene, spezifische Regel bestehen. */
.settings-meta-rows::-webkit-scrollbar,
.lp-assets-scroll::-webkit-scrollbar { width: 6px; }
.settings-meta-rows::-webkit-scrollbar-track,
.lp-assets-scroll::-webkit-scrollbar-track { background: transparent; }
.settings-meta-rows::-webkit-scrollbar-thumb,
.lp-assets-scroll::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.settings-meta-rows::-webkit-scrollbar-thumb:hover,
.lp-assets-scroll::-webkit-scrollbar-thumb:hover { background: var(--ink-dim); }
.settings-meta-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px;
  background: var(--panel);
}
.settings-meta-row button { font-size: 10px; }
.settings-meta-pin {
  width: 18px; height: 18px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 0; background: none;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .12s;
}
.settings-meta-pin svg { width: 10px; height: 10px; }
.settings-meta-pin:hover { color: var(--amber); }
.settings-meta-pin.pinned { color: var(--amber); }
.settings-meta-pin.pinned svg { fill: currentColor; }
.settings-meta-name {
  flex: 1; min-width: 0;
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: text;
}
.settings-meta-name:hover { color: var(--ink); }
.settings-meta-name .title-edit { font-size: 10px; padding: 1px 2px; width: 100%; }
.settings-meta-move {
  width: 14px; height: 14px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 0; background: none;
  color: var(--ink-faint);
  font-size: 8px;
  line-height: 1;
  cursor: pointer;
  transition: color .12s;
}
.settings-meta-move:hover:not(:disabled) { color: var(--amber); }

/* ── Themes (BOARD SETTINGS, docs/setzz-themes-notes.md) ──────
   Baut auf den bestehenden .settings-meta-row-Zeilen auf (gleiche
   Liste, gleiche Pin-/Verschieben-/Löschen-Knöpfe) — hier nur die
   Ergänzungen: Miniatur-Vorschau, Aktiv-Markierung, und die beiden
   eingebauten Standard-Themes oben. */
/* Höchstens 4 Themes gleichzeitig sichtbar, darüber scrollt die Liste.
   Eine Zeile misst 64px (60px Vorschau inkl. Rahmen — box-sizing ist
   global border-box — plus 2*2px Zeilen-Padding), dazu je 1px Abstand
   zwischen den Zeilen. Nachgemessen, nicht geschätzt: bei einem zu
   grosszügigen Wert lugt die fünfte Zeile sichtbar hervor. */
.lp-theme-rows { max-height: calc(4 * 64px + 3 * 1px); }
.lp-theme-row { gap: 6px; }
.lp-theme-row.active { background: var(--panel-2); }
/* NEU (2026-08-09): die ganze Zeile loest das Anwenden aus, nicht mehr
   nur der Name-Text — ein Klick auf Vorschau oder Zeilenrand war vorher
   wirkungslos. Der Zeiger muss das jetzt auch zeigen. Die inneren
   Knoepfe (Pin/Export/Pfeile/Loeschen) gewinnen weiterhin ueber
   closest(), ihr eigener cursor bleibt unberuehrt. */
.lp-theme-row[data-bsp-action],
.lp-theme-row-builtin[data-theme-action] { cursor: pointer; }
/* Werkszeile zeigt BEIDE Vorschauen (hell + dunkel) nebeneinander —
   sie ersetzen die frueheren zwei getrennten Zeilen. */
.lp-theme-preview-pair {
  display: inline-flex;
  gap: 3px;
  flex-shrink: 0;
}
.lp-theme-row-builtin {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px;
  background: var(--panel);
}
.lp-theme-row-builtin.active { background: var(--panel-2); }
.lp-theme-row-builtin button { font-size: 10px; }
/* Builtins haben keinen Pin-Knopf — Einrückung trotzdem gleich, damit
   Vorschau und Name bündig unter denen der eigenen Themes stehen. */
.lp-theme-row-builtin::before {
  content: "";
  width: 18px;
  flex-shrink: 0;
}
.lp-theme-name {
  flex: 1; min-width: 0;
  font-family: var(--f-label);
  font-size: 11px;
  color: var(--ink-dim);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
.lp-theme-name:hover { color: var(--ink); }
.lp-theme-row.active .lp-theme-name,
.lp-theme-row-builtin.active .lp-theme-name { color: var(--ink); }
.lp-theme-active-dot {
  width: 6px; height: 6px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--amber);
}
/* NEU (2026-07): Live-Einstellungen weichen vom gespeicherten Theme ab —
   hohler statt gefüllter Punkt, damit der ungespeicherte Zustand auch
   ohne Blick auf den UPDATE-THEME-Knopf auffällt. */
.lp-theme-active-dot-dirty {
  background: transparent;
  border: 1.5px solid var(--amber);
}
/* "APPLY TO N BOARDS" — verteilt das aktive Theme ausdrücklich auf die
   anderen Boards, die es tragen. Eigene, volle Zeile unter den drei
   Theme-Knöpfen, weil die Aktion weiter reicht als alles andere im
   Panel (sie verändert FREMDE Boards) und nicht versehentlich neben
   SAVE/UPDATE getroffen werden soll. */
.lp-theme-spread-row { justify-content: stretch; }
.lp-theme-spread-btn {
  width: 100%;
  text-align: center;
}
/* Ausgegraute Theme-Aktionen (z.B. UPDATE THEME ohne eigenes aktives
   Theme oder ohne Änderung) — sichtbar vorhanden, aber klar inaktiv. */
.settings-meta-add-btn:disabled {
  opacity: .35;
  cursor: default;
}
.settings-meta-add-btn:disabled:hover {
  color: var(--ink-faint);
  border-color: var(--line);
}
/* Vereinfachte Vorschau: EIN SET auf dem Board-Hintergrund, im Stil
   "Background" mit eigenem SET-Titel-Platzhalter (siehe
   themePreviewMarkup() in app.js — zeigt bewusst diese Variante, nicht
   "None", weil sie zusaetzlich Titelbalken + SET-Hintergrund demonstriert).
   Licht/Dunkel wird durch ZWEI dieser Kacheln nebeneinander gezeigt
   (.lp-theme-preview-pair), nicht durch zwei Karten in einer Kachel —
   die Kachel ist deshalb schmaler als frueher (war: 92px fuer zwei
   Karten nebeneinander). Abstände sind hier bewusst FEST und spiegeln
   NICHT die Padding-/Gap-Einstellungen des Themes — nur Farben und
   Stile werden abgebildet. */
.lp-theme-preview {
  position: relative;
  width: 45px; height: 60px;
  flex-shrink: 0;
  border: 1px solid var(--line-soft);
  padding: 5px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* Zeilen-Titel oben links, in der META-Farbe des Themes */
.lp-theme-preview-meta {
  width: 20px; height: 3px;
  flex-shrink: 0;
  margin-bottom: 4px;
  opacity: .9;
}
.lp-theme-preview-set {
  flex: 1;
  min-height: 0;
  min-width: 0;
  padding: 3px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.lp-theme-preview-settitle {
  height: 3px;
  width: 65%;
  flex-shrink: 0;
  opacity: .9;
}
.lp-theme-preview-card {
  flex: 1;
  min-height: 0;
  padding: 3px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.lp-theme-preview-fig {
  flex: 1;
  min-height: 0;
  background: rgba(128, 128, 128, .35);
  box-sizing: border-box;
}
.lp-theme-preview-line {
  height: 2px;
  flex-shrink: 0;
  background: rgba(128, 128, 128, .55);
}
.lp-theme-preview-line-short { width: 60%; }
/* Accent Color des Themes — kleiner, ausgefüllter Kreis oben rechts,
   IMMER sichtbar (ohne eigenen Accent zeigt er den Werks-Amber). */
.lp-theme-preview-accent {
  position: absolute;
  top: 4px; right: 4px;
  width: 8px; height: 8px;
  border-radius: 50%;
}
.settings-meta-move:disabled { opacity: .2; cursor: default; }
.settings-meta-delete {
  width: 16px; height: 16px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 0; background: none;
  color: var(--ink-faint);
  font-size: 10px;
  cursor: pointer;
  transition: color .12s;
}
.settings-meta-delete:hover { color: var(--red, #d04040); }
.settings-meta-add {
  display: flex;
  gap: 4px;
  margin-top: 6px;
}
.settings-meta-add input { flex: 1; min-width: 0; }
.settings-meta-add-btn {
  width: 32px; height: 32px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  background: none;
  color: var(--ink-faint);
  font-size: 15px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.settings-meta-add-btn:hover { color: var(--amber); border-color: var(--amber); }

/* NEU (2026-08, Custom Asset-Detail-Felder, Schritt 1 — siehe
   docs/setzz-custom-detail-fields-notes.md). Baut auf den bestehenden
   .settings-meta-*-Klassen auf (Wiederverwendung der Listen-UI für
   Custom-Listen-Felder, wie in der Aufgabe gefordert). */
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm. War dauerhaft rot, aber
   Archivieren ist nicht destruktiv (rueckgaengig zu machen) — gehoert
   NICHT zu den drei .btn-danger-Knoepfen dieser Runde (sparsam damit). */
.settings-meta-archive { float: right; }
/* Nicht-Listen-Custom-Felder (Text/Checkbox/Zahl/Datum) brauchen nur eine
   schmale Titelzeile, keine Optionen-Liste/Add-Zeile. */
.settings-meta-block-simple .settings-meta-title {
  border: 1px solid var(--line-soft);
  padding: 6px 8px;
  margin-bottom: 0;
}
.settings-field-type-tag {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 1px 4px;
  margin-left: 4px;
  vertical-align: middle;
}
.settings-new-field { grid-column: 1 / -1; }
.settings-new-field-row { display: flex; gap: 4px; margin-top: 6px; }
.settings-new-field-row [data-new-field-label] { flex: 1; min-width: 0; }
.settings-new-field-type { flex-shrink: 0; width: 108px; }
.settings-new-field-locked [data-new-field-label],
.settings-new-field-locked .settings-new-field-type,
.settings-new-field-locked [data-field-action="create"] { cursor: not-allowed; opacity: .6; }

/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): war ein
   Textzusatz zu .settings-meta-add-btn, nur um dessen 32x32-Quadratform
   und Amber-Hover fuer SAVE/UPDATE/IMPORT THEME zu ueberschreiben —
   jetzt eigenstaendig auf .btn/.btn-sm umgestellt (BOARD SETTINGS → sm),
   .settings-meta-add-btn bleibt fuer die echten Icon-"+"-Knoepfe
   (Sitzung B) unveraendert stehen, nicht mehr Teil der Klassenliste
   dieser drei Knoepfe.
   GEÄNDERT (2026-08-13, vierte Runde): die eigene :disabled-Regel ist
   entfallen — .btn/.btn-extra kennen jetzt einen eigenen einheitlichen
   :disabled-Zustand (siehe dort). UPDATE THEME traegt seither .btn-extra
   statt .btn (verarbeitet keinen Eingabewert, siehe data-theme-update). */
.bsp-save-btn { white-space: nowrap; }

.topnav-divider { width: 1px; height: 14px; background: var(--line); margin: 0 2px; flex-shrink: 0; }

.topnav-tools { display: flex; align-items: center; gap: 4px; }
.icon-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  color: var(--ink-dim);
  font-size: 15px;
  border: 1px solid transparent;
  transition: color .15s, border-color .15s;
  position: relative;
}
.icon-btn:hover { color: var(--ink); border-color: var(--line); }
.icon-btn.sm { width: 26px; height: 26px; font-size: 13px; }
.icon-btn.has-dot::after {
  content: "";
  position: absolute; top: 5px; right: 5px;
  width: 5px; height: 5px;
  background: var(--amber);
}
.topnav-divider { width: 1px; height: 20px; background: var(--line); margin: 0 4px; }

.profile {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 0px solid;
  padding: 1px;
  margin: 8px 8px 8px 0;
  font-family: var(--f-display);
}
.avatar {
  width: 30px; height: 30px;
  border-radius: 100%;
  object-fit: cover;
  border: 0;
}
.profile-id { text-align: left; line-height: 1.15; }
.profile-id strong { display: block; font-family: var(--f-label); font-size: 10px; color: var(--amber); letter-spacing: .06em; }
.profile-id em { display: block; font-style: normal; font-size: 9px; color: var(--ink-dim); }
.chev { color: var(--amber); font-size: 11px; }

/* ═══ CHARACTERS ═══════════════════════════════════════════ */
.char-carousel { position: relative; overflow: hidden; cursor: grab; touch-action: pan-y; }
.char-carousel:active { cursor: grabbing; }
.char-track { display: flex; transition: transform .45s cubic-bezier(.4,0,.2,1); }
/* two images stacked, not side by side */
.char-slide { flex: 0 0 100%; display: flex; flex-direction: row; gap: 10px; }

.char-card {
  flex: 0 0 calc((100% - (var(--per-slide) - 1) * 10px) / var(--per-slide));
  min-width: 0;
  border: 1px solid var(--line);
  background: var(--panel-2);
}
.char-card .media {
  width: 100%;
  background: transparent;
  /* height is set explicitly by JS (layoutSliderCards) — no aspect-ratio here */
}
.char-card .media svg, .char-card .media img, .char-card .media video {
  object-fit: cover;
}

/* editable overlay label at the bottom of the card image (e.g. "Front View") */
/* NEU: vorerst ausgeblendet (Nutzer-Anfrage) — Markup/Logik bleiben
   unverändert bestehen, nur die Sichtbarkeit ist deaktiviert. */
.view-label { display: none; }
.view-label {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 8px 10px;
  background: linear-gradient(0deg, rgba(var(--overlay-rgb), .85), rgba(var(--overlay-rgb), .45) 75%, transparent);
  z-index: 4;
  cursor: text;
  opacity: 0;
  transition: opacity .15s;
}
.media[data-upload-key]:hover .view-label,
.view-label:has(input) { opacity: 1; }
.view-label-text {
  display: block;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
  min-height: 1.2em;
}
.view-label-text:empty::before { content: "Edit Title …"; color: var(--ink-faint); }
.view-label .title-edit { font-size: 10px; padding: 3px 5px; }

/* footer row below the slider: prev / dots / next, same height,
   sitting in the panel's own frame rather than over the image */
/* GEAENDERT (2026-09-03, GEMESSEN SimplifyerX): war die Kurzform
   `background: var(--panel)`, ohne den Karten-Farbton — der einzige
   UNTERGRUND (siehe Massstab in docs/setzz-new-card-type-checklist.md,
   "#13"), der von der Farbton-Reihe 2026-08-24 ausgelassen wurde.
   Damals bewusst geprueft und dokumentiert als "kein Untergrund
   vorhanden (Dots-Pager = Bedienelement)" — das war keine Uebersehung,
   sondern eine Einordnung, die sich beim Ansehen im Board als falsch
   herausgestellt hat: die FLAECHE dahinter (die den Dots-Pager traegt)
   ist sehr wohl ein Untergrund, nur die Dots selbst sind das
   Bedienelement. Zwei-Ebenen-Technik wie ueberall in der Reihe — die
   Kurzform wuerde background-image zuruecksetzen und den Verlauf
   loeschen. Betrifft alle vier Kartentypen gleichermassen:
   characters/vehicles rendern das direkt, slider-h/slider-v
   delegieren an hydrateCharacters()/hydrateVehicles() (panels.js) und
   damit an dasselbe Markup. */
.carousel-footer {
  display: flex;
  align-items: center;
  gap: 6px;
  position: absolute;
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  margin-top: -22px;
  right: 16px;
  padding: 8px;
}
.dots { display: flex; justify-content: flex-start; gap: 5px; flex: 1; }
/* GEAENDERT (2026-09-03, GEMESSEN SimplifyerX): war var(--ink-faint) /
   var(--amber) — eine Karten-Ebene-Farbe (Akzent) auf einem Bauteil, das
   seit dieser Woche selbst den BOARD-Farbton traegt (.carousel-footer).
   Jetzt durchgehend --board-ink-faint, wie die Row-/SET-/COL-/CARD-Titel
   (METAS Style) — der Dots-Pager ist ein Meta-Element AUSSERHALB des
   Karteninhalts, kein Inhalt selbst.
   UNTERSCHEIDUNG aktiv/inaktiv jetzt allein ueber die FUELLUNG: inaktiv
   hohl (transparent, nur Rahmen), aktiv voll (Flaeche UND Rahmen in
   derselben Farbe) — GEMESSEN, dass das traegt: ein gefuellter 7x7-Punkt
   gegen einen hohlen mit 1px-Rahmen bleibt bei jeder gemessenen
   --board-ink-faint-Staerke klar unterscheidbar (Bericht). */
.dots button {
  width: 7px; height: 7px;
  border-radius: 0;
  border: 1px solid var(--board-ink-faint);
  transition: background .15s, border-color .15s;
}
.dots button.active { background: var(--board-ink-faint); border-color: var(--board-ink-faint); }

.carousel-prev, .carousel-next {
  width: 22px; height: 22px;
  display: grid; place-items: center;
  color: var(--ink-dim);              /* brighter resting state than the old ink-faint */
  border: 0px solid var(--line);
  transition: color .15s, border-color .15s;
}
/* prev/next now sit naturally at the right end of the flex row */
.carousel-prev:hover, .carousel-next:hover { color: var(--amber); }
.carousel-prev svg, .carousel-next svg { width: 10px; height: 10px; }

/* Slider H/V wrapper: positioning context for the edge-nav arrows
   and the bottom-center +/- pair, both hover-reveal. */
.slider-slot-btns-host { position: relative; }
.carousel-edge-nav {
  position: absolute;
  top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--panel);
  color: var(--ink);
  box-shadow: 0 2px 8px rgba(0,0,0,.25);
  opacity: 0;
  transition: opacity .12s, background .12s, color .12s;
  z-index: 5;
}
.carousel-edge-nav.carousel-prev { left: 0; transform: translate(-50%, -50%); }
.carousel-edge-nav.carousel-next { right: 0; transform: translate(50%, -50%); }
.slider-slot-btns-host:hover .carousel-edge-nav { opacity: 1; }
.carousel-edge-nav:hover { color: var(--amber); }
.carousel-edge-nav svg { width: 11px; height: 11px; }

/* layout toggle toolbar (1 vs 2 per slide), mirrors the Assets one */
.carousel-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
  margin-bottom: 10px;
  padding: 0px 10px;
}
.carousel-layout-btn {
  width: 24px; height: 24px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  border: 0;
  transition: color .15s;
}
.carousel-layout-btn svg { width: 13px; height: 13px; }
.carousel-layout-btn:hover { color: var(--amber); }
.carousel-layout-btn.active { color: #eeeeee; background: transparent; }

/* ═══ VEHICLES ═════════════════════════════════════════════ */
.vehicle-carousel { position: relative; overflow: hidden; cursor: grab; touch-action: pan-y; }
.vehicle-carousel:active { cursor: grabbing; }
.vehicle-track { display: flex; transition: transform .45s cubic-bezier(.4,0,.2,1); }
.vehicle-slide { flex: 0 0 100%; display: flex; flex-direction: column; gap: 10px; }

.vehicle-card {
  border: 1px solid var(--line);
  background: var(--panel-2);
}
.vehicle-card .media {
  width: 100%;
  background: transparent;
  /* height is set explicitly by JS (layoutSliderCards) — no aspect-ratio here */
}
.vehicle-card .media svg, .vehicle-card .media img, .vehicle-card .media video {
  object-fit: cover;
}

/* ═══ MEDIA SLOTS ══════════════════════════════════════════ */
.media { background: transparent; border: 0px solid var(--line); position: relative; overflow: visible; width: 100%; }
/* NEU: "CARD Style Custom" → Figure Outline (BOARD SETTINGS) ist jetzt
   direkt in --asset-outline verdrahtet (siehe applyCardFigureOutlineTint(),
   app.js) — das ist die Variable, die .single-media/.asset-thumb/
   .slider-thumb/.list-thumb/.audio-slot bereits verwenden. Keine
   zusätzliche generische .media-Regel nötig/sinnvoll (wäre ohnehin von
   diesen spezifischeren Selektoren überschrieben worden). */
/* display:block gegen den Zeilenrest unter dem Medium (2026-08-03):
   <img>/<video> sind von Haus aus inline-level und sitzen damit auf der
   Textgrundlinie — darunter bleibt der Platz fuer Unterlaengen stehen.
   In Containern mit fester Hoehe faellt das nicht auf, in einem
   automatisch hohen schon: in der Framer-CARD blieb dadurch ein
   schmaler heller Streifen unter dem Video. Der Streifen war nie ein
   Abstand, den jemand gesetzt hat, sondern die Grundlinie selbst —
   deshalb war er auch ueber padding/margin nicht zu finden. */
.media svg, .media img, .media video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Assets, die selbst als "immer vollständig sichtbar, nie zugeschnitten"
   markiert sind (z.B. PDF-Seiten-Exporte mit ungewöhnlich breitem/hohem
   Seitenverhältnis) — überschreibt die generelle cover-Regel oben. */
.media img.media-fit-contain, .media video.media-fit-contain { object-fit: contain; }
.media-fill { position: absolute; inset: 0; }
.hero-media, .map-media { min-height: 0; }
.single-media { width: 100%; aspect-ratio: 16 / 9; overflow: hidden; display: block; position: relative; outline: 1px solid var(--asset-outline); outline-offset: -1px; }
.single-media .ph-slot-wrap { position: absolute; inset: 0; height: auto; }
.ph-slot-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  display: block;
}
.ph-slot-wrap > svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.ph-tag {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  font-size: 11px;
  color: var(--ink-faint);
  letter-spacing: .18em;
  text-align: center;
  font-family: var(--f-data);
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

.media-caption {
  display: none;   /* hidden for now, per request — markup/logic kept intact */
  position: absolute;
  right: 16px; bottom: 14px;
  left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
  gap: 5px;
  font-family: var(--f-display);
  font-size: 12px;
  text-transform: none;
  letter-spacing: 0.01em;
  line-height: 1.15;
  color: var(--ink-dim);
  pointer-events: none;            /* upload hover stays reachable */
  z-index: 4;
  text-shadow: 0 1px 3px rgba(6,6,7,.9), 0 0 14px rgba(6,6,7,.7);   /* shadows arbitrary photo content, stays dark regardless of theme */
}
.media-caption b { color: var(--ink); font-weight: 700; }

/* ═══ TOOLTIPS ═════════════════════════════════════════════
   NEU (2026-08-13, Tooltip-Umbau Etappe 2). Ersetzt den nativen
   Browser-Tooltip (title-Attribut) durch ein eigenes, dezentes
   Element — der native war der Anlass der ganzen Runde: zu dominant
   fuer Screenrecordings (grosser dunkler Kasten, heller Rahmen,
   Systemschrift), zu frueh eingeblendet, nicht gestaltbar.
   title bleibt im Markup stehen (Zugaenglichkeit — siehe
   design/tooltips/tooltip-audit-dom.js: 147 Elemente haben KEIN
   aria-label und KEINEN sichtbaren Text, title ist ihr einziger
   zugaenglicher Name) und wird nur fuer die Dauer des Hovers
   ausgeliehen (siehe setzzTooltipBind() in app.js). Position wird
   per JS berechnet (getBoundingClientRect + Kantenkorrektur), daher
   hier bewusst KEIN transform/margin-basiertes Zentrieren — links/
   oben kommen inline vom Skript.

   GEAENDERT (2026-08-13, SimplifyerX-Gestaltung): --tooltip-bg/-fg
   statt --bg-deep/--ink-dim (siehe Token-Definition in :root) — auf
   Wunsch bewusst NICHT die bestehenden Grau-Tokens, damit der
   Tooltip sich unabhaengig von Panel-/Textfarben abstimmen laesst.
   KONTRAST GEMELDET: #cccccc auf #888888 war 2.21:1. Nachgerechnet
   fuer die beiden vorgeschlagenen helleren Werte (dieselbe WCAG-
   Formel wie zuvor) — mit einer Korrektur: #f0f0f0 ergibt 3.11:1
   und #ffffff ergibt 3.54:1, NICHT 3.9:1 bzw. 4.6:1 wie im Auftrag
   angenommen. Gegengerechnet: gegen #888888 ist AA fuer Normaltext
   (4.5:1) mit KEINER Textfarbe erreichbar — selbst reines Weiss
   (Lum. 1.0, das Maximum) kommt nur auf 3.54:1. Gewaehlt: #ffffff,
   der hoechste ueberhaupt erreichbare Wert bei dieser Flaeche.
   Schatten: --tile-shadow (bestehendes Token fuer kleine,
   abgesetzte Elemente, siehe .btn) statt eines neuen
   Schatten-Stils — selbe Begruendung wie dort. */
.setzz-tooltip {
  position: fixed;
  left: -9999px; top: -9999px;
  z-index: var(--z-tooltip);
  max-width: 220px;
  padding: 4px 7px;
  background: var(--tooltip-bg);
  color: var(--tooltip-fg);
  border-radius: 3px;
  font-family: var(--f-label);
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: normal;
  text-transform: none;
  opacity: 0;
  pointer-events: none;
  /* Slide+Fade: 4px in Richtung des Zielelements, damit der Tooltip
     beim Einblenden aus dessen Richtung "kommt" statt aus dem Nichts
     zu erscheinen — dezent, keine Distanz-Wucht. --slide-y wird von
     setzzPositionTooltip() inline gesetzt: NEGATIV (Start etwas
     hoeher, naeher am Ziel), wenn der Tooltip UNTER dem Ziel sitzt —
     POSITIV (Start etwas tiefer, naeher am Ziel), wenn er darueber
     ausweichen musste. In CSS ist positives Y "nach unten", das
     Vorzeichen folgt also der jeweils entgegengesetzten Richtung zum
     Ziel hin. Ohne inline gesetztes --slide-y (erster Frame vor der
     Positionierung) bewegt sich nichts — 0px ist ein sicherer
     Default.
     GEAENDERT (2026-08-13, Slide-Richtung an der Rail): --slide-x
     dazu, gleiche Vorzeichen-Logik, nur waagerecht — gesetzt, wenn
     setzzPositionTooltip() den Tooltip NEBEN statt UNTER/UEBER dem
     Ziel platziert (randnahe Ziele wie die Rail, siehe dort). Beide
     Achsen sind in jedem Fall exklusiv befuellt (die jeweils andere
     bekommt 0px) — nie beide gleichzeitig ungleich 0. */
  --slide-x: 0px;
  --slide-y: 0px;
  transform: translate(var(--slide-x), var(--slide-y));
  transition: opacity .2s ease-out, transform .2s ease-out;
  box-shadow: var(--tile-shadow);
}
.setzz-tooltip.shown { opacity: 1; transform: translateY(0); }
/* NEU (2026-08-13, Rail einbeziehen): Rail-Tooltips in Versalien, wie
   die Panel-Titel, die sie oeffnen (.lp-section-title, ebenfalls per
   text-transform, nicht im Text selbst). Modifier statt globaler
   Regel: nur Rail-Ziele bekommen das, "Select"/"Duplicate this asset"
   im Assets-Manager bleiben normal geschrieben. Wird von
   setzzShowTooltip() gesetzt (app.js), letter-spacing bewusst wie
   .lp-section-title bei 0 belassen. */
.setzz-tooltip-rail { text-transform: uppercase; }

@media (prefers-reduced-motion: reduce) {
  /* Die globale Regel weiter unten kappt bereits jede
     transition-duration auf .01ms — hier zusaetzlich die Bewegung
     selbst weggenommen (nicht nur beschleunigt), wie gefordert: nur
     Deckkraft, keine Verschiebung. */
  .setzz-tooltip { transition: opacity .2s ease-out; transform: none !important; }
}

/* ═══ TOASTS ═══════════════════════════════════════════════ */
.toasts {
  position: fixed;
  right: 20px; bottom: 20px;
  display: grid;
  gap: 8px;
  z-index: var(--z-toast);   /* war 120 — bewusst über Floating Panels UND Immersive View, siehe Layer-Ordnung in :root */
}
.toast {
  background: var(--panel-2);
  border: 1px solid var(--amber);
  border-left-width: 3px;
  padding: 12px 16px;
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .05em;
  max-width: 320px;
  animation: slide-in .3s ease-out;
}
.toast.leaving { opacity: 0; transition: opacity .3s; }

/* ═══ FOCUS & MOTION ═══════════════════════════════════════ */
:focus-visible { outline: 1px solid var(--amber); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ═══ BOARD ENGINE · columns, panel menu, scene/map/assets ═══ */

/* ── Column header: ID chip · editable title · actions ─────── */
.col-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: 0px;
  padding: 12px var(--gap) 0px;
  flex-shrink: 0;
}
.col-head-text {
  display: flex;
  flex-direction: row;
  gap: 6px;
  margin-right: auto;
  min-width: 0;
  position: relative;
}
.col-title {
  font-family: var(--f-display);
  font-size: 16px;
  font-weight: 400;
  padding: 5px 0px;
  margin-left: 0px;
  letter-spacing: 0px;
  line-height: 1;
  text-transform: none;
  /* NEU: automatischer Kontrast-Ausgleich — Column-Titel sitzt außerhalb
     der Cards direkt auf dem Workspace-Hintergrund. */
  color: var(--board-ink-faint);
  opacity: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  cursor: text;
  transition: opacity .15s, color .15s;
}
.col-title:hover { opacity: 1; color: var(--amber, #C8A23F); }
/* NEU (korrigiert): der umgebende Wrapper (.col-title) behält seine
   eigene Padding (5px 0) auch im Edit-Modus bei — replaceChildren()
   tauscht nur den Inhalt, nicht die Wrapper-Klasse. Padding hier
   zusätzlich zu setzen addierte sich also zur Wrapper-Padding und liess
   die Zeile "springen". Fix: Eingabefeld bekommt vertikal KEINE eigene
   Padding mehr, die Wrapper-Padding allein bestimmt die Höhe wie im
   Normalzustand. */
/* NEU: explizite Höhe statt "auto" via line-height — Chrome berechnet
   die intrinsische Höhe eines <input type=text> teils zusätzlich anhand
   der Font-Metriken (Ascent/Descent), wodurch das Feld hier trotz
   identischer line-height/padding ein paar Pixel höher blieb als der
   reine Text und die Zeile beim Klick zum Umbenennen noch minimal
   sprang. explizite Höhe = font-size behebt das exakt. */
.col-title .title-edit { padding: 0 2px; height: 16px; }
.col-btn {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  color: var(--board-ink-faint);
  font-size: 13px;
  flex-shrink: 0;
  transition: color .15s;
}
.col-btn:hover { color: var(--amber); }
/* NEU (2026-07): Breiten-Faktor (z.B. "x2.2"), links vom Collapse-
   Toggle — klein, klickbar zum Bearbeiten (siehe inlineRename-Aufruf
   für .col-width-factor in app.js). */
.col-width-factor {
  font-family: var(--f-data);
  font-size: 11px;
  font-weight: 600;
  color: var(--board-ink-faint);
  opacity: 0;
  transition: opacity .15s;
  flex-shrink: 0;
  /* NEU (2026-08): ew-resize hinweist auf "ziehbar" im Ruhezustand,
     analog .group-factor-input — Klick zum Tippen (inlineRename())
     bleibt unverändert möglich, siehe bindColFactorScrub() in app.js.
     Im Bearbeiten-Modus zeigt das eingefügte <input> ohnehin seinen
     eigenen Text-Cursor, kein Override nötig. */
  cursor: ew-resize;
  padding: 0 2px;
  user-select: none;
}
/* NEU (2026-08): nur bei Hover über dem GANZEN SET sichtbar, nicht erst
   bei Hover über der einzelnen COL — beide Faktoren (SET + COL) sollen
   gemeinsam erscheinen/verschwinden. Gleiche drei Trigger wie
   .group-factor oben (Hover / :focus-within fürs Tippen /
   .factor-dragging fürs Ziehen des SET-Faktors), aus denselben Gründen
   im Gleichlauf gepflegt. */
.group-wrap:hover .col-width-factor,
.col-width-factor:focus-within,
.group-wrap.factor-dragging .col-width-factor { opacity: 1; }
.col-width-factor:hover { color: var(--ink-dim); }
/* Padding im Edit-Modus an den normalen Text (oben, "0 2px") angeglichen,
   damit die Zeilenhöhe beim Klick zum Bearbeiten nicht springt. */
.col-width-factor .title-edit { width: 34px; font-family: inherit; font-size: 11px; font-weight: 600; text-align: center; padding: 0 2px; }
.col-collapse-toggle svg { width: 16px; height: 16px; transition: transform .2s ease; }
/* NEU (2026-07): vorerst ausgeblendet — "Open/Close all CARDS" im
   Column-Kontextmenü übernimmt diese Funktion jetzt gebündelt. */
.col-collapse-toggle { display: none; }
.col-menu-btn { font-size: 15px; letter-spacing: 0; transform: translateY(-1px); }
/* keep long titles clear of the ✕ button */
.col-head-text { max-width: calc(100% - 120px); }

/* inline rename input — shared by column and panel titles */
.title-edit {
  -webkit-appearance: none;
  appearance: none;
  font: inherit;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px dotted var(--line);
  padding: 1px 2px;
  width: 100%;
  min-width: 90px;
  outline: none;
  text-transform: inherit;
}
.title-edit:focus { border-bottom-color: var(--ink-dim); }
.panel-head h2 .title-edit { font-size: 15px; }

/* ── Column resize grip (right edge) ───────────────────────── */
/* NEU (2026-07): right/width waren feste Pixelwerte (-10px/9px), nur für
   den alten kleinen Default-Gap grob mittig. Jetzt an --gap-col gekoppelt:
   die Hitbox füllt exakt den Flex-Gap zwischen den beiden Columns (min.
   9px bei sehr kleinem Gap), die sichtbare Linie sitzt per left:50% immer
   exakt in der Mitte, unabhängig vom Gap-Wert (S/M/L/Custom). */
.col-resize {
  position: absolute;
  top: 0; bottom: 0;
  right: calc(-1 * max(var(--gap-col), 9px));
  width: max(var(--gap-col), 9px);
  cursor: col-resize;
  z-index: 10;
  touch-action: none;
}
.col-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  background: transparent;
  transition: background .15s;
}
.col-resize:hover::after,
.col.resizing .col-resize::after { background: var(--amber); }
.col.resizing { user-select: none; }

/* ── Add-column strip at the end of the board ──────────────── */
/* NEU (2026-07): war ein Flex-Item mit negativen Margins (kompensiert
   für Gap + eigene Breite). In mehrspaltigen SETs führte das zu einem
   Browser-Rundungs-/Intrinsic-Width-Effekt: die (auto-breite) .group-body
   wurde dabei teils ein paar Pixel schmaler berechnet als die Summe ihrer
   Teile, wodurch der Button leicht über den rechten Rand hinausragte.
   Robuster gelöst: Button komplett aus dem Flex-Fluss genommen
   (position:absolute) — er kann die intrinsische Breite von .group-body
   dadurch gar nicht mehr beeinflussen, unabhängig von Column-Anzahl.
   right zentriert die 44px-Breite exakt im --gap-col breiten
   Aussen-Padding-Bereich. */
.col-add {
  position: absolute;
  top: 0; bottom: 0;
  right: calc((var(--gap-col) - 44px) / 2);
  width: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border: 2px dashed transparent;
  color: var(--ink-faint);
  opacity: 0;
  transition: color .15s, border-color .15s, opacity .15s;
  /* NEU (2026-07, Nutzer-Fund): .col-resize (letzte Column, z-index:10)
     lag über diesem Button und blockierte den Klick. z-index höher, UND
     pointer-events:none auf dem ganzen Button (44px breit) — nur der
     sichtbare "+"-Kreis (span, siehe unten) bekommt pointer-events:auto
     zurück. Der Rest der 44px-Fläche lässt Klicks damit zum darunter-
     liegenden Resize-Handle durch, wie gewünscht. */
  z-index: 11;
  pointer-events: none;
}
.group-wrap:hover .col-add,
.group-wrap:hover .panel-add-btn { opacity: 1; }
.col-add span { font-size: 24px; line-height: 15px; padding: 10px; border-radius: 100px; background: #111111; pointer-events: auto; }
:root[data-theme="light"] .col-add span { background: #ffffff; }
.col-add em {
  display: none;
  font-family: var(--f-data);
  font-style: normal;
  font-size: 9px;
  letter-spacing: .18em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
  opacity: 0;
  transition: opacity .15s;
}
.col-add:hover { color: var(--amber); border-color: transparent; }

/* ── Add-panel type menu (body-level popover) ──────────────── */
.panel-menu {
  position: fixed;
  min-width: 148px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  display: none;
  flex-direction: column;
  z-index: var(--z-popover-menu);   /* war 130 — Karten-Kontextmenü, siehe Layer-Ordnung in :root */
}
.panel-menu.open { display: flex; }
.panel-menu button,
.panel-menu a {
  padding: 8px 12px;
  text-align: left;
  font-family: var(--f-label);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  color: var(--ink-dim);
  background: var(--panel);
  border-bottom: 1px solid var(--line-soft);
  transition: color .15s, background .15s;
  cursor: pointer;
}
.panel-menu button:last-child,
.panel-menu a:last-child { border-bottom: 0; }
.panel-menu button:hover,
.panel-menu a:hover { color: var(--amber); background: var(--panel-2); }

/* NEU: Add-Panel-Typ-Auswahl 2-spaltig, kleines Card-Icon je
   Menüpunkt rechts orientiert (statt der einspaltigen Liste, die
   alle anderen .panel-menu-basierten Menüs — GROUP/Row/World-Tile —
   weiterhin nutzen). */
.panel-menu.panel-type-menu { min-width: 240px; }
.panel-menu.panel-type-menu.open {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}
.panel-menu.panel-type-menu button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.panel-type-menu-icon { display: flex; flex-shrink: 0; color: var(--ink-faint); opacity: 0.35; }
.panel-type-menu-icon svg { width: 14px; height: 14px; display: block; }
.panel-menu.panel-type-menu button:hover .panel-type-menu-icon { color: var(--amber); }
/* NEU (2026-08, Tier-Runde R1): gesperrter Eintrag im Add-Panel-Menü.
   Dasselbe Bild wie die gesperrte Picker-Kachel (.panel-type-pro-locked)
   — gedimmt statt versteckt, damit der Eintrag als Angebot lesbar
   bleibt. Nur die NÄCHSTE Stufe kann hier überhaupt auftauchen, fernere
   sind bereits herausgefiltert (siehe isPanelTypeVisible()). */
.panel-menu.panel-type-menu button.panel-type-menu-locked {
  opacity: .55; cursor: not-allowed;
}
.panel-menu.panel-type-menu button.panel-type-menu-locked:hover .panel-type-menu-icon { color: var(--ink-faint); }
/* R2 (2026-08): Hexagon-Icon statt Text-Chip, gleiche Flex-Position
   (rechts vom Label, links vom Typ-Icon) — reiner Content-Swap. */
.panel-type-menu-tier {
  display: flex; align-items: center; margin-left: auto; flex-shrink: 0;
  color: var(--ink);
}
.panel-type-menu-tier svg { width: 10px; height: 10px; display: block; }
/* NEU (2026-07): SVG-Icon im World-Tile-Menü an den rechten Rand
   drücken, statt direkt hinter dem Text zu fließen — bewusst nur auf
   dieses eine Menü begrenzt, nicht auf alle .panel-menu-Instanzen. */
.world-tile-menu button {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* NEU (2026-08-15, Send-Asset "More actions"): gleiche Bauform wie
   .world-tile-menu (Text + Icon rechts) — hier zusaetzlich auf <a>
   ausgeweitet, weil "Download original file" ein echtes <a href
   download> bleiben muss (natives Download-Verhalten, kein JS-Klick-
   Handler). min-width groesser als der .panel-menu-Standard (148px):
   die laengste Beschriftung ("Drag to another tab or app") braucht
   mehr Raum, um nicht umzubrechen. */
.send-asset-menu { min-width: 220px; }
.send-asset-menu button,
.send-asset-menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.send-asset-menu svg { width: 12px; height: 12px; margin-left: 6px; color: var(--ink-faint); }
.send-asset-menu button:hover svg,
.send-asset-menu a:hover svg { color: var(--amber); }

/* delete button in the panel head */
.head-btn.panel-delete { font-size: 10px; margin-left: 2px; }

/* ── Image / Video / Map panels (media-first) ──────────────── */
/* Header keeps the same box model as every other panel type —
   only the media itself bleeds to the panel's outer edge. */
.panel-image .panel-body,
.panel-video .panel-body,
.panel-map .panel-body,
.panel-single .panel-body {
  display: block;
  overflow: hidden;
}
.hero-media  { aspect-ratio: 1280 / 520; }
.map-media   { aspect-ratio: 1280 / 340; }
.video-frame { aspect-ratio: 16 / 9; }

/* ── Assets grid (6 upload slots) ──────────────────────────── */
.assets-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0px;
  overflow: hidden;
}
.asset-thumb {
  /* Height is set as an explicit PIXEL value by JS (layoutAssetGrid).
     This sidesteps a known Chromium bug where percentage padding-bottom
     inside CSS Grid auto-rows resolves inconsistently across cells
     (Firefox computes this correctly; Chromium/Brave do not). */
  position: relative;
  overflow: hidden;
  display: block;
  height: 160px; /* fallback before JS runs */
  outline: 3px solid var(--asset-outline);
  outline-offset: -1px;
}

/* ══ Mosaic-Card (2026-08, Etappe 1) ═══════════════════════════════
   Siehe docs/setzz-mosaic-card-notes.md. Das Raster trägt bewusst auch
   .assets-grid (damit die bestehenden Layout-Aufrufe es miterfassen,
   siehe die Weiche in layoutAssetGrid()); Spalten UND Zeilen werden
   dort per JS gesetzt, hier steht nur, was davon unabhängig ist. */
.mosaic-grid {
  gap: 0;
}
.mosaic-slot {
  /* WICHTIG: .asset-thumb bringt height:160px als Fallback mit. Eine
     explizite Höhe verhindert, dass das Grid-Item seine Zeilen-Tracks
     ausfüllt — der Slot wäre dann unabhängig von seiner Spannweite
     immer 160px hoch, ein 2×2-Feld also genauso hoch wie ein 1×1.
     height:auto gibt die Höhe an die von layoutMosaicGrid() gesetzten
     grid-template-rows zurück, wo sie hingehört. */
  height: auto;
  min-height: 0;
}
/* .asset-thumb bringt eine 3px --asset-outline mit (siehe Zeile ~4384).
   Bei Gap>0 + Hintergrundfarbe wird die Outline sichtbar (weißer/grauer
   Rand um jeden Slot) — Grid Sets unterdrückt das bereits (.gridset-grid
   .asset-thumb weiter unten), Mosaic fehlte die analoge Regel. */
.mosaic-grid .asset-thumb { outline: 0px solid transparent; }
/* Prev/Next-Leiste (2026-08-07): teilt sich .gridset-slot-nav mit Grid
   Sets. Mosaic-Zellen koennen deutlich kleiner sein — gemessen 113px
   bei 3x3 auf einer 360px-Karte, ~50px bei 4x4 in einer schmalen
   Spalte, wo die Standardleiste (2x20px + 3px) mit 43px fast die ganze
   Breite einnaehme. Deshalb hier kleiner. */
.mosaic-slot .gridset-move-btn { width: 16px; height: 16px; }
.mosaic-slot .gridset-move-btn svg { width: 9px; height: 9px; }
/* ── Eck-Label live auf dem Slot (2026-08-07) ──────────────────────
   Mosaic UND Grid Sets. Die Werte spiegeln bewusst den Canvas-Zweig
   von composeSlotGridImage() (panels.js): weiss mit Schatten, weil das
   Label ueber beliebigem Bildinhalt liegt und in BEIDEN Themes lesbar
   bleiben muss — dieselbe Begruendung wie bei .asset-name, deshalb
   hier ebenfalls eine feste Farbe statt einer Theme-Variablen.

   line-height:1 ist NICHT kosmetisch: nur damit trifft die Oberkante
   des CSS-Kastens dieselbe Linie wie ctx.textBaseline="top" im Canvas.
   Mit einem groesseren line-height saesse das Live-Label tiefer als im
   komponierten Bild, und genau die Deckungsgleichheit ist der Zweck.

   z-index 3: unter .media-controls (5) und .gridset-slot-nav (6), damit
   die Bedienelemente bei Hover weiterhin obenauf liegen. Die obere
   linke Ecke ist sonst unbelegt (geprueft), es gibt keine Kollision.
   Groesse und Padding kommen als Inline-Style aus dem Panel-Feld. */
.slot-corner-label {
  position: absolute;
  top: 0; left: 0;
  z-index: 3;
  font-family: var(--f-data);
  line-height: 1;
  color: #fff;
  text-shadow: 1px 1px 3px rgba(0,0,0,.7);
  pointer-events: none;
  white-space: nowrap;
}

.mosaic-template-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.mosaic-template-btn svg { width: 14px; height: 14px; }
/* Basisraster-Anzeige statt eines Karten-Ratio-Buttons — bewusst ein
   <span>, kein Button: bei Mosaic gibt es nichts einzustellen (siehe
   mosaicSectionMarkup() in panels.js). */
.mosaic-base-label {
  display: inline-flex;
  align-items: center;
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  color: var(--ink-dim);
  padding: 0 4px;
  white-space: nowrap;
}
.mosaic-popover {
  position: fixed;
  z-index: var(--z-popover-menu);
  display: none;
  max-height: 60vh;
  overflow-y: auto;
  padding: 10px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.mosaic-popover.open { display: block; }
.mosaic-popover::-webkit-scrollbar { width: 6px; }
.mosaic-popover::-webkit-scrollbar-track { background: transparent; }
.mosaic-popover::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.mosaic-popover::-webkit-scrollbar-thumb:hover { background: var(--ink-dim); }
.mosaic-popover-section + .mosaic-popover-section { margin-top: 12px; }
.mosaic-popover-head {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 6px;
}
.mosaic-popover-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.mosaic-tpl-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 64px;
  padding: 7px 4px;
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--ink);
  cursor: pointer;
}
.mosaic-tpl-tile:hover { border-color: var(--line); background: var(--panel); }
.mosaic-tpl-tile.active { border-color: var(--amber); color: var(--amber); }
.mosaic-tpl-glyph { display: flex; color: var(--board-ink-faint); }
.mosaic-tpl-glyph svg { width: 34px; height: 34px; }
.mosaic-tpl-label {
  font-family: var(--f-display);
  font-size: 10px;
  font-weight: 600;
  line-height: 1em;
  letter-spacing: 0em;
  text-align: center;
  color: var(--ink-dim);
}
.mosaic-tpl-tile.active .mosaic-tpl-label { color: var(--amber); }

.asset-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
/* Assets, die selbst als "immer vollständig sichtbar, nie zugeschnitten"
   markiert sind (z.B. PDF-Seiten-Exporte mit ungewöhnlich breitem/hohem
   Seitenverhältnis) — überschreibt background-size:cover oben. */
.asset-bg-contain { background-size: contain; }

/* ── Bildraster-Quadranten (2×2) ────────────────────────────
   Zeigt display-only EIN Viertel eines 2×2-Rasterbilds (z.B.
   Midjourney-Grid) im Slot, ohne die Quelldatei physisch zu
   beschneiden. .quadrant-clip sitzt zwischen der Figure (.media,
   position:relative) und dem unveränderten Bild-Markup (<img>
   oder .asset-bg); .quadrant-frame ist doppelt so groß wie der
   sichtbare Ausschnitt und wird um genau eine Boxbreite/-höhe
   verschoben — das bestehende width:100%/height:100%-Sizing von
   .media img/video und .asset-bg greift dabei unverändert, da
   beide Selektoren als Nachfahren weiterhin matchen. */
.quadrant-clip { position: absolute; inset: 0; width: 100%; height: 100%; overflow: hidden; }
.quadrant-frame { position: absolute; top: 0; left: 0; width: 200%; height: 200%; }
/* -50%, nicht -100%: der Frame ist 200% der Clip-Box (2W x 2H) gross —
   Translate-Prozente rechnen gegen die EIGENE Groesse, also verschiebt
   -50% um W bzw. H (genau eine Clip-Breite/-Hoehe, das gewuenschte
   Mass). -100% verschiebt um 2W/2H, doppelt zu weit, Ergebnis: leeres
   Bild (gemessen per getComputedStyle beim Staging-Test, 2026-08-04). */
.quadrant-frame[data-quadrant="A"] { transform: translate(0%, 0%); }
.quadrant-frame[data-quadrant="B"] { transform: translate(-50%, 0%); }
.quadrant-frame[data-quadrant="C"] { transform: translate(0%, -50%); }
.quadrant-frame[data-quadrant="D"] { transform: translate(-50%, -50%); }

/* ── Slot-Bildausschnitt (Pan + Zoom) ───────────────────────
   Gleiche Bauart wie .quadrant-clip/.quadrant-frame darueber: der Clip
   bringt das overflow:hidden mit, das .media selbst bewusst NICHT hat
   (dort ist overflow:visible noetig, damit media-controls/zoom-hint
   ueberstehen duerfen). Der Frame ist hier 100%/100% statt 200% — die
   Vergroesserung macht das transform:scale() aus slotFramingStyle()
   (app.js), nicht die Box-Groesse.

   transform-origin explizit auf center: die Rechnung in
   slotFramingStyle() geht vom mittigen Ursprung aus (Ueberhang
   haelftig auf beiden Seiten). Der Browser-Standard ist zwar schon
   50% 50%, aber die Rechnung haengt daran — deshalb festgeschrieben
   statt geerbt.

   Verschachtelbar mit den Quadranten: der Frame-Wrapper sitzt INNEN.
   Der ZOOM wirkt damit innerhalb des Quadranten-Ausschnitts. Fuer den
   PAN gilt das AUSDRUECKLICH NICHT (Korrektur 2026-08-06, gemessen) —
   siehe den Block darunter. Die width/height:100%-Regeln von
   .media img/video und .asset-bg greifen unveraendert weiter, da beide
   Selektoren als Nachfahren matchen. */
.slot-frame-clip { position: absolute; inset: 0; width: 100%; height: 100%; overflow: hidden; }
.slot-frame { position: absolute; top: 0; left: 0; width: 100%; height: 100%; transform-origin: 50% 50%; }

/* Pan des COVER-Ueberhangs (2026-08-06) — der Teil des Bildausschnitts,
   der schon OHNE Zoom existiert, sobald Slot-Ratio und Bildformat
   auseinanderfallen. --fx/--fy kommen aus slotFramingStyle() (app.js);
   der Fallback 50% haelt Wrapper ohne die Variablen exakt bei "zentriert"
   und damit optisch identisch zum Zustand vor dieser Aenderung.

   Zwei Eigenschaften statt einer, weil die Medien unterschiedlich
   gefuellt werden (.asset-bg per background-size:cover, img/video per
   object-fit:cover) — der Aufrufer bleibt trotzdem medienagnostisch,
   weil beide Regeln dieselbe Variable vom selben Wrapper lesen.

   Spezifitaet: schlaegt .asset-bg{background-position:center} (0,1,0)
   mit 0,2,0. Das Inline-object-fit:cover der Video-Zweige kollidiert
   nicht, es ist eine andere Eigenschaft. */
.slot-frame .asset-bg { background-position: var(--fx, 50%) var(--fy, 50%); }
.slot-frame img,
.slot-frame video { object-position: var(--fx, 50%) var(--fy, 50%); }

/* Popover: legt sich exakt über die Bildfläche (Rect vom Ziel-Slot
   übernommen, siehe openQuadrantPopover()), zeigt vier anklickbare
   Viertel direkt auf dem Bild plus einen "Full"-Reset darunter —
   bewusst kein Dropdown wie beim Ratio-Popover, da die Position der
   Quadranten selbst die Bedienoberfläche ist. */
.quadrant-popover { position: fixed; z-index: var(--z-popover-menu); display: none; }
.quadrant-popover.open { display: block; }
.quadrant-popover-grid {
  position: absolute; inset: 0;
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 2px; background: rgba(0, 0, 0, 0.55);
}
.quadrant-popover-grid button {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.5);
  cursor: pointer;
  padding: 0;
}
.quadrant-popover-grid button:hover { background: rgba(255, 255, 255, 0.3); }
.quadrant-popover-grid button.active { background: rgba(255, 176, 32, 0.45); border-color: var(--amber); }
/* position:fixed statt absolute+bottom:-34px, geklemmt in
   openQuadrantPopover() (app.js) — bei einer Karte nahe am Viewport-
   Rand landete der Button sonst außerhalb des Sichtfelds; ein
   Scroll-Versuch dorthin schließt das Popover sofort (siehe
   Scroll-Handler), der Button war dann faktisch unerreichbar. */
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover), jetzt .btn/.btn-sm.
   GEÄNDERT (2026-08-13, Regressions-Runde): wieder aus .btn genommen.
   Der Knopf bekommt per JS .active zugewiesen (openQuadrantPopover(),
   app.js) — er ist kein Aktionsknopf, sondern der Text-Vertreter
   derselben Vier-plus-eins-Auswahlgruppe wie die Symbol-Knoepfe A-D
   (.quadrant-popover-grid button). Als .btn hatte er nie eine sichtbare
   Auswahl-Markierung (auch nicht vor der Migration, siehe Bericht) —
   jetzt bekommt er dieselbe Amber-Markierung wie seine Geschwister
   (.quadrant-popover-grid button.active), damit die Gruppe als eine
   zusammengehoerige Auswahl lesbar ist. Amber-Hover bleibt bewusst
   draussen (Hausregel) — Hover ist neutral wie bei .mosaic-tpl-tile,
   derselben Kachel-artigen Nachbarschaft. position:fixed bleibt
   strukturell (Klemmung gegen den Viewport-Rand, siehe oben). */
.quadrant-full-btn {
  position: fixed;
  white-space: nowrap;
  padding: 6px 14px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--f-data);
  font-size: 11px;
  cursor: pointer;
  transition: border-color .12s, background .12s;
}
.quadrant-full-btn:hover { border-color: var(--line); background: var(--panel); }
.quadrant-full-btn.active { background: rgba(255, 176, 32, .45); border-color: var(--amber); }

/* Framing-Popover: gleiche Bauart wie das Quadrant-Popover darueber —
   fixed ueber der Bildflaeche, Bedienleiste per JS gegen den Viewport
   geklemmt (siehe openFramingPopover(), app.js, gleiche Begruendung
   wie beim "Full"-Button). Die Zieh-Flaeche ist bewusst das Popover
   selbst und nicht der Slot: so treffen die Pointer-Events ein Element
   ausserhalb des .panel-Teilbaums, der Karten-Drag kann gar nicht
   erst mithoeren. */
.framing-popover { position: fixed; z-index: var(--z-popover-menu); display: none; }
.framing-popover.open { display: block; }
.framing-pad {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.5);
  cursor: grab; touch-action: none;
}
.framing-pad:active { cursor: grabbing; }
/* Zoom 1x: kein Ueberhang zum Verschieben (siehe normalizeFraming(),
   app.js) — Flaeche sichtbar inaktiv statt Ziehen ins Leere. */
.framing-pad.framing-pad-idle { cursor: not-allowed; background: rgba(0, 0, 0, 0.18); }
.framing-pad.framing-pad-idle .framing-crosshair { opacity: .35; }
.framing-crosshair {
  position: absolute; width: 14px; height: 14px;
  margin: -7px 0 0 -7px;
  border: 1.5px solid var(--amber);
  border-radius: 50%;
  background: rgba(255, 176, 32, 0.35);
  pointer-events: none;
}
.framing-crosshair::before,
.framing-crosshair::after {
  content: ""; position: absolute; background: var(--amber); opacity: .7;
}
.framing-crosshair::before { left: 50%; top: -6px; width: 1px; height: 5px; }
.framing-crosshair::after { top: 50%; left: -6px; height: 1px; width: 5px; }
.framing-controls {
  position: fixed;
  display: flex; align-items: center; gap: 10px;
  padding: 5px 10px;
  background: var(--panel-2); border: 1px solid var(--line);
  white-space: nowrap;
}
.framing-zoom-row { display: flex; align-items: center; gap: 7px; }
.framing-zoom-label,
.framing-zoom-value { font-family: var(--f-data); font-size: 10px; color: var(--board-ink-faint); }
.framing-zoom-value { min-width: 38px; text-align: right; }
.framing-zoom { width: 96px; accent-color: var(--amber); cursor: pointer; }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover), jetzt .btn/.btn-sm. */

/* ── SVG CARD — Custom-Farbe/Deckkraft per CSS-Maske ──────────
   (siehe svgMaskMarkup(), app.js, für die Sicherheits-Begründung
   pro mask-image statt Inline-SVG). Etwas eingerückt (statt volle
   Fläche wie .asset-bg), damit Icons/Logos nicht am Rahmenrand kleben. */
.svg-mask {
  position: absolute;
  inset: 8%;
  width: 84%;
  height: 84%;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.svg-color-label { font-family: var(--f-data); font-size: 9px; color: var(--board-ink-faint); flex-shrink: 0; margin-left: 4px; }
.svg-color-input {
  width: 22px; height: 22px;
  padding: 0; border: 1px solid var(--line-soft); background: transparent;
  cursor: pointer; flex-shrink: 0;
}
.svg-opacity-range { width: 60px; flex-shrink: 0; }
.svg-opacity-pct {
  width: 3ch;
  background: var(--panel-2); border: 1px solid var(--line-soft); color: var(--ink);
  font-family: var(--f-data); font-size: 10px; text-align: center; padding: 2px 3px;
  flex-shrink: 0;
}

.asset-thumb > svg,
.asset-thumb > .ph-slot-wrap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.asset-thumb > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── Panel type icon: left of the title, 1px gap ───────────── */
.panel-icon {
  /* Trefferflaeche bewusst unveraendert bei 17x17 — nur das SVG
     darin wird kleiner (Nachtrag Karten-Bedienelemente-Runde). Ein
     Bedienelement soll nicht schwerer zu treffen werden, nur weil
     es leiser aussieht. */
  width: 17px; height: 17px;
  display: grid; place-items: center;
  color: var(--board-ink-faint);
  /* GEAENDERT (Nutzer-Korrektur nach Kontrastmessung): 0.35 lag bei
     1,38:1 (Light) / 1,72:1 (Dark) — zu leise. Jetzt 0.5, gemessen
     1,60:1 (Light) / 2,15:1 (Dark) — Light bleibt bewusst unter 2:1,
     vom Nutzer nach dieser Messung ausdruecklich so belassen (kein
     Kontrast-Nachbessern gewuenscht). Bitte NICHT als Versehen lesen
     und "reparieren".

     GLEICHMAESSIG, OHNE Hover-Ausnahme — dafuer wurden zwei getrennte
     Hover-Regeln entfernt, die beide denselben Effekt erzeugten:
       1. .panel:hover .panel-icon { opacity: 1 }
          (aus der Karten-Bedienelemente-Runde, wieder entfernt)
       2. .panel-head .panel-icon:hover { opacity: 1 !important; color: var(--ink); }
          (aelter, seit dem allerersten Commit — ist bei Punkt 1 nicht
          aufgefallen, weil ein anderer Selektor; !important haette
          sie ohnehin gegen diese Regel gewinnen lassen. Erst beim
          Schreiben dieses Kommentars gefunden.)
     Beide sind weg, das Icon hellt jetzt unter keinen Umstaenden mehr
     auf. Siehe auch die zweite, spaeter im File stehende .panel-icon-
     Regel (".panel-icon: dimmed by default"), die dieselbe Eigenschaft
     nochmal setzt und als spaetere Regel gleicher Spezifitaet
     tatsaechlich gewinnt. Beide hier auf 0.5 gebracht, damit keine
     der beiden stillschweigend den falschen Wert traegt. */
  opacity: 0.5;
  flex-shrink: 0;
  padding-right: 5px;
}
/* GEAENDERT: war 15px — SVG kleiner, Wrapper (siehe oben) bleibt 17px. */
.panel-icon svg { width: 12px; height: 12px; }

/* ── Assets panel: column selector + add-slot button ───────── */
.assets-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 4px;
  margin-bottom: 10px;
  padding: 0px 10px;
}
.assets-cols-btn {
  width: 24px; height: 24px;
  display: grid; place-items: center;
  color: var(--board-ink-faint);
  opacity: 0.6;
  border: 0;
  transition: color .15s, border-color .15s;
}
.assets-cols-btn svg { width: 13px; height: 13px; }
.assets-cols-btn:hover { color: var(--amber); border-color: var(--amber); }
.assets-cols-btn.active { color: var(--board-ink-faint); opacity: 1; border-color: var(--amber); background: transparent; }

.assets-grid-wrap { position: relative; }
/* Shared hover-reveal "+"/"−" button pair — sits centered at the
   bottom edge of a card, overlapping it slightly. Used wherever a
   card's slot list can be extended or shrunk (Grid, Slider H/V,
   List, List + Image, Audio Playlist). Parent must have
   position:relative for the wrapper's centering to work correctly. */
.card-slot-btns {
  position: absolute;
  left: 50%; bottom: 0;
  transform: translate(-50%, 50%);
  display: flex;
  gap: 3px;
  opacity: 0;
  transition: opacity .12s;
  z-index: 6;
}
.assets-grid-wrap:hover .card-slot-btns,
.list-surface:hover .card-slot-btns,
.audio-panel-inner:hover .card-slot-btns,
.slider-slot-btns-host:hover .card-slot-btns,
.card-slot-btns:focus-within { opacity: 1; }

.card-add-btn, .card-remove-btn {
  width: 22px; height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: var(--panel);
  color: var(--ink);
  font-size: 13px;
  line-height: 1;
  box-shadow: 0 2px 8px rgba(0,0,0,.18);
  cursor: pointer;
  transition: transform .12s, box-shadow .12s;
}
.card-add-btn:hover, .card-remove-btn:hover { transform: scale(1.08); box-shadow: 0 3px 12px rgba(0,0,0,.24); }
.card-remove-btn { color: #d04040; }

/* NEU (2026-08-18, Runde B): Slot-Zähler zwischen − und + bei Grid
   Sets. Trägt echte Auskunft, keine Zierde: weicht die Anzahl von
   jeder Vorlage ab, leuchtet oben in der Leiste bewusst KEIN
   Vorlagenknopf — dann ist diese Zahl die einzige Stelle, an der der
   Zustand ablesbar ist. Gleiche Pillenform und Schattierung wie die
   beiden Knöpfe, aber ohne Interaktion (kein Hover, kein Zeiger). */
/* NEU (2026-08-18, Runde C): "Grid Format" — Verhaeltnis des GANZEN
   Sets. Nur aktiv, wenn panel.gsFormat gesetzt ist (Klasse kommt dann
   aus gridSetSectionMarkup(), zusammen mit --gs-format).

   ⚠️ Das Format ist eine MINDESThoehe, kein Deckel. Deshalb traegt es
   ein Platzhalter-Pseudoelement in DERSELBEN Rasterzelle wie das Raster
   — die Zeile nimmt dann das groessere der beiden. Ein schlichtes
   `aspect-ratio` am Wrapper waere falsch: gemessen staucht es die Karte
   auf die Formathoehe, und weil .assets-grid `overflow: hidden` traegt
   (Mindestgroesse damit 0), wird der Ueberschuss stillschweigend
   abgeschnitten — genau das, was fuer Grid Format verworfen wurde.
   `min-height: fit-content`/`max-content` helfen nicht (beide geprueft).

   grid-template-columns ausdruecklich gesetzt: ohne das zieht das
   Pseudoelement die Spalte auf seine eigene Breite und verzerrt die
   Slotbreiten (gemessen: Zellen 118px statt 100px). */
.gridset-framed { display: grid; grid-template-columns: minmax(0, 1fr); }
.gridset-framed::before {
  content: "";
  grid-area: 1 / 1;
  aspect-ratio: var(--gs-format);
  min-width: 0;
}
.gridset-framed > .gridset-grid { grid-area: 1 / 1; align-self: center; }

.gridset-slot-count {
  min-width: 22px; height: 22px;
  padding: 0 6px;
  border-radius: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--panel);
  color: var(--ink-dim);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  box-shadow: 0 2px 8px rgba(0,0,0,.18);
  user-select: none;
}

/* ── Grid Sets card: slot move-left/right nav + compose-image row ── */
.gridset-slot-nav {
  position: absolute;
  left: 50%; bottom: 4px;
  transform: translateX(-50%);
  display: flex;
  gap: 3px;
  opacity: 0;
  transition: opacity .12s;
  z-index: 6;
}
.asset-thumb:hover .gridset-slot-nav { opacity: 1; }
.gridset-move-btn {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border: 1px solid var(--line-soft);
  background: var(--panel);
  color: var(--ink-faint);
  border-radius: 3px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.gridset-move-btn svg { width: 10px; height: 10px; }
.gridset-move-btn:hover { color: var(--amber); border-color: var(--amber); }
.gridset-move-btn:disabled { opacity: .3; cursor: default; }
.gridset-move-btn:disabled:hover { color: var(--ink-faint); border-color: var(--line-soft); }

.gridset-generate-row {
  /* display: none/flex toggled by .panel.toolbar-open, see the
     "toolbar hidden by default" rule further down */
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* GEAENDERT (Karten-Bedienelemente-Runde): war 8px 0px 0 — Felder/
     Knoepfe stiessen am Kartenrand an, unabhaengig vom --pad-card
     dieser Karte (0px links/rechts war hier fest verdrahtet, keine
     Vererbung). 10px links/rechts wie .assets-toolbar direkt darueber
     in derselben Karte — dasselbe Mass statt eines neuen.

     NACHTRAG: unten fehlte dasselbe noch (war weiterhin 0) — dieselbe
     Karte stiess unten an. 10px statt der oberen 8px gewaehlt, weil
     oben/unten hier verschiedene Dinge messen: der obere Wert ist der
     Abstand zum Geschwister-Element DARUEBER in derselben Karte (dem
     Grid/Mosaic), der untere und beide seitlichen sind der Abstand zum
     KARTENRAND (--pad-card, 5px, kommt vom .panel selbst noch dazu).
     10px unten macht den Gesamtabstand zum Kartenrand unten (5+10=15px)
     gleich dem seitlichen (5+10=15px) — passend zum oberen 8px waere
     er mit 13px knapp daneben gelegen. */
  padding: 8px 10px 10px;
}
.gridset-opts {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 1;
  min-width: 0;
}
.gridset-labels-input {
  width: 25%;
  min-width: 60px;
}
.gridset-labels-input,
.gridset-num-input,
.gridset-color-input {
  height: 24px;
  padding: 0 6px;
  /* GEAENDERT (Karten-Bedienelemente-Runde): Flaeche entfernt (war
     var(--panel)) — nur noch Kontur. Bleibt trotzdem auffindbar: jedes
     Feld traegt bereits einen 1px-Rahmen UND einen Platzhaltertext
     ("fs"/"px"/"pd"/"#"/"Slot 1, Slot 2, …"), anders als der leere-
     Felder-Fall vom 2026-08-15, der nur die Flaeche als Signal hatte. */
  background: none;
  border: 1px solid var(--line-soft);
  color: var(--ink);
  font-family: var(--f-data);
  font-size: 11px;
  border-radius: 0;
}
.gridset-num-input { width: calc(3ch + 12px); text-align: center; padding: 0 3px; }
.gridset-grid .asset-thumb { outline: 0px solid transparent; }
.gridset-color-input { width: calc(9ch + 12px); }
.gridset-opt-label { font-size: 10px; color: var(--ink-faint); white-space: nowrap; }
.gridset-generate-actions { display: flex; gap: 4px; flex-shrink: 0; }
.gridset-generate-btn {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border: 1px solid var(--line-soft);
  background: var(--panel);
  color: var(--ink-faint);
  /* GEAENDERT (Karten-Bedienelemente-Runde): war 4px, eine der vier
     am 2026-08-13 als "ungeklaert" erhobenen Radius-Abweichungen —
     Kartensteuerung (Footer-Aktion, wirkt aufs ganze Set), nicht
     Slot-Ebene. Siehe docs/ui-design-system-notes.md. */
  border-radius: 0;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.gridset-generate-btn svg { width: 13px; height: 13px; }
.gridset-generate-btn:hover { color: var(--amber); border-color: var(--amber); }

/* duplicate button glyph size */
.head-btn.panel-duplicate svg { width: 14px; height: 14px; }

/* Ratio wirkte optisch größer/fetter als Edit (Stift), obwohl gleicher
   viewBox — liegt am ausfüllenden Rechteck-Glyph. Etwas kleiner, damit
   sich das Cluster gleichmäßig anfühlt. */
.media-controls > .ratio-btn svg { width: 10px; height: 10px; }
.media-controls > .media-menu-btn svg { width: 11px; height: 11px; }
/* NEU (2026-08-19, "acht Korrekturen", Punkt 8): dasselbe Phänomen wie
   beim Ratio-Icon direkt darüber, stärker ausgeprägt. GEMESSEN am
   SVG-Quellcode (icons.generated.js, ui-framing): kein eigener
   Größen-Override vorhanden — das Icon lief bislang auf dem Cluster-
   Standard 12px (styles.css, .media-controls svg), demselben Wert wie
   Stift/Raster. Die 12×12-Box selbst ist also NICHT größer; das Motiv
   füllt nur mehr Fläche: ein Rahmen-Rechteck über ~78% des 16×16-
   viewBox (1.714 bis 14.286) plus ein Kreuz darin — gegenüber Ratios
   eigenem, kleineren Rechteck (~71%×50%) oder dem duennen Stift-Strich
   nochmal deutlich dichter. Gleiche Lösung wie bei Ratio: Box verkleinert
   statt das Sprite-Motiv selbst zu bearbeiten (Sprite ist Build-Output
   aus icons.generated.js, siehe docs/setzz-icon-sprites-notes.md — ein
   Groessen-Override hier ist der uebliche, risikoarme Hebel). */
.media-controls > .framing-btn svg { width: 10px; height: 10px; }

/* ── Asset name label (assets grid, bottom overlay) ────────── */
.asset-name {
  position: absolute;
  left: 8px; right: 28px; bottom: 8px;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-shadow: 0 1px 3px rgba(6,6,7,.9);   /* shadows arbitrary photo content, stays dark regardless of theme */
  pointer-events: none;
  z-index: 4;
  opacity: 0;
  transition: opacity .15s;
}
.media[data-upload-key]:hover .asset-name { opacity: 1; }

.media-controls > .meta-btn svg { width: 12px; height: 12px; }
.media-controls > .meta-btn { position: relative; }
.media-controls > .quadrant-btn svg { width: 12px; height: 12px; }
.media-controls > .quadrant-btn { position: relative; }
/* Dauerhaft (nicht nur bei geöffnetem Popover) amber eingefärbt, solange
   ein Quadrant aktiv ist — der Punkt allein (::after) war beim Test
   über die Oberfläche zu unauffällig, um als "hier ist ein Zustand
   aktiv" erkannt zu werden (dieselbe Lehre wie beim Palette-Schloss). */
.media-controls > .quadrant-btn.has-data { color: var(--amber); }
.media-controls > .quadrant-btn.has-data::after {
  content: "";
  position: absolute;
  top: -1px; right: -1px;
  width: 5px; height: 5px;
  background: var(--amber);
}
.media-controls > .meta-btn.has-data::after {
  content: "";
  position: absolute;
  top: -1px; right: -1px;
  width: 5px; height: 5px;
  background: var(--amber);
}

/* ── Panel actions kebab + dropdown (Focus · Duplicate · Delete) ── */
.head-btn.panel-menu-btn {
  font-size: 15px;
  letter-spacing: 0;
  transform: translateY(-1px);   /* optical centering of the ⋮ glyph */
}
/* GEAENDERT (Karten-Bedienelemente-Runde): kein Farbwechsel bei Hover
   mehr — gezielte Ausnahme von der geteilten .head-btn:hover-Regel
   (var(--amber)), NICHT diese Regel selbst angefasst: Kollabieren,
   Werkzeug-Toggle, Duplizieren, Loeschen u.a. teilen sich .head-btn
   und behalten ihre Hover-Rueckmeldung unveraendert. Bleibt trotzdem
   als klickbar erkennbar: nativer button-Reset setzt app-weit bereits
   cursor:pointer (siehe `button { … cursor: pointer; }` oben). */
.head-btn.panel-menu-btn:hover { color: var(--board-ink-faint); }
.panel-actions-menu button {
  text-align: left;
  white-space: nowrap;
}
.panel-actions-menu [data-panel-action="delete"]:hover { color: var(--red); background: var(--panel); }
.col-actions-menu [data-col-menu-action="delete"]:hover { color: var(--red); background: var(--panel); }
.group-actions-menu [data-group-menu-action="delete"]:hover { color: var(--red); background: var(--panel); }
.group-clear-menu [data-group-clear="all"]:hover { color: var(--red); background: var(--panel); }
.media-menu button { text-align: left; white-space: nowrap; }
.media-menu [data-mm="delete"]:hover { color: var(--red); background: var(--panel); }

/* ── Video panel: minimal play bar (bottom overlay strip) ──── */
.video-frame { position: relative; }
.video-playbar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: linear-gradient(0deg, rgba(var(--overlay-rgb), .92), rgba(var(--overlay-rgb), .7) 80%, transparent);
  z-index: 4;
}

/* NEU (2026-08-25): Die Bedienleiste eines VIDEO-Assets erscheint erst
   bei Hover — Einblenden mit Aufwaertsgleiten, Verlassen mit
   Ausblenden und Abwaertsgleiten. Entscheidung SimplifyerX. Damit wird
   die Grenze zwischen "Leiste bedienen" und "Lightbox oeffnen"
   SICHTBAR, statt erklaert werden zu muessen: die Leiste ist nur da,
   wenn der Zeiger ueber dem Video steht.

   pointer-events NICHT vergessen: eine bloss durchsichtige Leiste
   laege weiterhin ueber dem unteren Bildrand und finge genau die
   Klicks ab, die die Lightbox oeffnen sollen — derselbe Fehler wie
   ein vergessenes display:none, nur unsichtbarer.

   ZWEI Ausnahmen, jede einzeln begruendet:
   - [data-framer] — die Framer-Leiste IST das Werkzeug (Scrubben,
     Frame-Zaehler, Get Frame). Sie auszublenden hiesse, die Karte
     unbedienbar zu machen.
   - Audio (.audio-playbar-compact, sitzt unter [data-audio-player],
     nicht unter [data-video-player]) — ein Audio-Slot hat keine
     Bildflaeche. Ohne Leiste bliebe eine leere Kachel uebrig; es gibt
     dort nichts, was die Leiste verdecken koennte. Bereits durch den
     Selektor unten ausgeschlossen, hier nur benannt, damit die
     Ausklammerung nicht spaeter fuer ein Versehen gehalten wird. */
[data-video-player]:not([data-framer]) > .video-playbar {
  opacity: 0;
  transform: translateY(100%);
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
/* :focus-within haelt die Leiste fuer die Tastatur erreichbar — die
   Figur traegt tabindex="0", ohne das waere sie per Tab anwaehlbar,
   aber unsichtbar. */
[data-video-player]:not([data-framer]):hover > .video-playbar,
[data-video-player]:not([data-framer]):focus-within > .video-playbar {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  [data-video-player]:not([data-framer]) > .video-playbar { transition: none; }
}
.vp-btn {
  width: 22px; height: 22px;
  flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--ink-dim);
  transition: color .15s;
}
.vp-btn:hover { color: var(--amber); }
.vp-btn svg { width: 11px; height: 11px; }
.vp-mute svg { width: 13px; height: 13px; }
.vp-seek, .vp-vol {
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  background: rgba(255,255,255,.22);
  outline: none;
  cursor: pointer;
}
.vp-seek { flex: 1; min-width: 0; }
.vp-vol { width: 56px; flex-shrink: 0; }
.vp-seek::-webkit-slider-thumb, .vp-vol::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 10px; height: 5px;
  border-radius: 3px;
  background: var(--amber);
  cursor: pointer;
}
.vp-seek::-moz-range-thumb, .vp-vol::-moz-range-thumb {
  width: 10px; height: 5px;
  border-radius: 3px;
  border: 0;
  background: var(--amber);
  cursor: pointer;
}
.vp-time {
  font-family: var(--f-data);
  font-size: 9.5px;
  letter-spacing: .04em;
  color: var(--ink-dim);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ── "Framer" CARD: vergrößerte, reine Scrub-Playbar (kein Play/Sound),
   großer Frame-Counter oben links über dem Video, "Get Frame"-Button ── */
.framer-playbar {
  flex-direction: column;
  align-items: stretch;
  padding: 14px 12px;
  gap: 8px;
  /* NEU: die gesamte Playbar nur bei Hover über der CARD einblenden
     (analog .media-resolution/.media-controls), statt dauerhaft sichtbar. */
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}
.panel:hover .framer-playbar { opacity: 1; pointer-events: auto; }
.framer-seek {
  width: 100%;
  height: 5px;
}
.framer-seek::-webkit-slider-thumb {
  width: 14px; height: 9px;
  border-radius: 3px;
}
.framer-seek::-moz-range-thumb {
  width: 14px; height: 9px;
  border-radius: 3px;
}
.framer-playbar-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.framer-frame-overlay {
  position: absolute;
  top: 10px; left: 12px;
  z-index: 4;
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-family: var(--f-display);
  font-size: 25px;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,.6), 0 0 2px rgba(0,0,0,.5);
  line-height: 1;
}
.framer-frame-sep, .framer-frame-total { pointer-events: none; }
.framer-frame-current {
  width: 2.6ch;
  min-width: 34px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: #fff;
  font-family: inherit;
  font-size: inherit;
  text-shadow: inherit;
  line-height: 1;
  padding: 0;
  -moz-appearance: textfield;
}
.framer-frame-current::-webkit-outer-spin-button,
.framer-frame-current::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.framer-frame-current:hover, .framer-frame-current:focus { border-bottom-color: var(--amber); }
.framer-frame-current:focus { outline: none; }
.framer-time { white-space: nowrap; }
.framer-fps-field {
  display: flex;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
}
.framer-fps-input {
  width: 42px;
  background: rgba(255,255,255,.08);
  border: 1px solid var(--line-soft);
  color: var(--ink);
  font-family: var(--f-data);
  font-size: 9.5px;
  text-align: right;
  padding: 2px 3px;
  -moz-appearance: textfield;
}
.framer-fps-input::-webkit-outer-spin-button,
.framer-fps-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.framer-fps-input:focus { outline: none; border-color: var(--amber); }
.framer-fps-suffix {
  font-family: var(--f-data);
  font-size: 9.5px;
  color: var(--ink-faint);
}
.framer-frame-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;   /* NEU: Buttons an den rechten Rand der Zeile schieben */
  flex-shrink: 0;
}
.framer-get-frame,
.framer-copy-frame {
  width: 26px; height: 26px;
  flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--ink-dim);
  border: 1px solid transparent;
  transition: color .15s, border-color .15s;
}
.framer-get-frame:hover,
.framer-copy-frame:hover { color: var(--amber); border-color: var(--amber); }
.framer-get-frame svg,
.framer-copy-frame svg { width: 15px; height: 15px; }

/* focus outline for the keyboard-controllable video figure */
[data-video-player]:focus-visible { outline: 1px solid var(--amber); outline-offset: -1px; }
[data-video-player]:focus { outline: 1px solid var(--amber); outline-offset: -1px; }

/* ── "PDF" CARD ─────────────────────────────────────────────
   Toolbar oben (.assets-toolbar-Konvention, Ratio-Button ganz rechts),
   Canvas darunter — siehe hydratePdf()/buildPdfViewer() (panels.js). */
.pdf-toolbar { flex-wrap: wrap; }
.pdf-nav-btn {
  width: 24px; height: 24px;
  flex-shrink: 0;
  display: grid; place-items: center;
  color: var(--ink-dim);
  border: 1px solid transparent;
  font-size: 13px;
  transition: color .15s, border-color .15s;
}
.pdf-nav-btn:hover { color: var(--amber); border-color: var(--amber); }
.pdf-nav-btn svg { width: 12px; height: 12px; }
.pdf-page-indicator {
  display: flex;
  align-items: center;
  gap: 3px;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-dim);
  flex-shrink: 0;
}
.pdf-page-input {
  width: 3ch;
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  color: var(--ink);
  font-family: inherit;
  font-size: inherit;
  text-align: center;
  padding: 2px 3px;
  -moz-appearance: textfield;
}
.pdf-page-input::-webkit-outer-spin-button,
.pdf-page-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.pdf-page-input:focus { outline: none; border-color: var(--amber); }
.pdf-zoom-pct {
  display: inline-flex;
  align-items: center;
  height: 24px;
  font-family: var(--f-data);
  font-size: 9px;
  color: var(--ink-faint);
  min-width: 3ch;
  justify-content: center;
  flex-shrink: 0;
}
/* GEAENDERT (2026-08-24, Teil 4): trug var(--bg-deep) fest, ungetoent
   -- ein echter Well-Untergrund (Letterbox hinter der PDF.js-Seite,
   sichtbar bei jedem Seitenformat, das nicht exakt 3:4 trifft), aber
   strukturell einfach: keine Nachbarflaeche wie bei List, die auf eine
   bestimmte Randfarbe angewiesen ist. */
.pdf-frame {
  aspect-ratio: 3 / 4;
  background-color: var(--bg-deep);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.pdf-canvas-host {
  width: 100%; height: 100%;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  /* NEU (2026-07): overflow bewusst NICHT mehr dauerhaft "auto" — der
     Canvas ist über max-width/max-height:100% (siehe .pdf-page-stack
     unten) rechnerisch bereits so bemessen, dass er den Host NIE
     überragen kann. Blieb overflow:auto trotzdem permanent aktiv,
     konnte ein Sub-Pixel-Rundungsfehler kurz eine Scrollbar auslösen →
     Host schrumpft um die Scrollbar-Breite → Canvas passt sich (über
     die Prozent-Basis) neu ein → Scrollbar verschwindet → Host wächst
     zurück → usw. — eine rein CSS-getriebene Variante der gleichen
     Rückkopplungsschleife, die vorher schon einmal JS-seitig auftrat
     ("PDF zittert bei bestimmten Spaltengrößen"). Scrollen ist nur
     beim expliziten Reinzoomen (zoomMultiplier > 1) überhaupt nötig,
     dort erlaubt :has() overflow gezielt wieder. */
  overflow: hidden;
}
.pdf-canvas-host:has(.pdf-page-stack-zoomed) { overflow: auto; }
.pdf-canvas { display: block; box-shadow: 0 2px 10px rgba(0,0,0,.2); }
/* Wrapper um Canvas + Text-Layer. NEU (2026-07): Anpassung an eine
   geänderte CARD-/Spaltengröße läuft jetzt komplett über CSS statt über
   einen aktiven Re-Render (siehe Kommentar bei renderPdfPage() in
   panels.js) — max-width/max-height:100% auf dem intrinsisch (per
   canvas.width/height) bemessenen Canvas lässt ihn sich wie ein ganz
   normales <img> verhalten: automatisch "contain"-eingepasst, ohne
   JS-Zutun, ohne ResizeObserver, ohne jede Verzögerung. */
.pdf-page-stack { position: relative; display: inline-block; max-width: 100%; max-height: 100%; }
.pdf-page-stack .pdf-canvas { position: relative; max-width: 100%; max-height: 100%; width: auto; height: auto; }
/* Beim expliziten Reinzoomen (Zoom-Buttons) darf der Canvas seine
   native Pixelgröße bewusst überschreiten — dafür ist .pdf-canvas-host
   mit overflow:auto scrollbar. */
.pdf-page-stack.pdf-page-stack-zoomed,
.pdf-page-stack.pdf-page-stack-zoomed .pdf-canvas { max-width: none; max-height: none; }
/* Text-Layer: unsichtbarer, aber klick-/selektierbarer Text über dem
   gerenderten Seitenbild — Standard-PDF.js-Textlayer-Konvention
   (transparente, absolut positionierte Spans, die exakt über den
   sichtbaren Buchstaben liegen). */
.pdf-text-layer.textLayer {
  position: absolute;
  inset: 0;
  overflow: hidden;
  line-height: 1;
  text-align: initial;
  transform-origin: 0 0;
  z-index: 2;
  -webkit-user-drag: none;
  user-select: text;
  -webkit-user-select: text;
  -moz-user-select: text;
}
.pdf-text-layer.textLayer span,
.pdf-text-layer.textLayer br {
  color: transparent;
  position: absolute;
  white-space: pre;
  cursor: text;
  transform-origin: 0% 0%;
  -webkit-user-drag: none;
  user-select: text;
  -webkit-user-select: text;
  -moz-user-select: text;
}
.pdf-text-layer.textLayer ::selection { background: rgba(255, 176, 32, .35); }
/* Slider-CARD-Muster für die Seiten-Navigation: links/rechts am
   Rahmenrand, vertikal zentriert, nur bei Hover über die ganze CARD
   sichtbar (gleiche Klassen/States wie .carousel-edge-nav). */
.pdf-slot-btns-host { position: relative; }
.pdf-slot-btns-host:hover .carousel-edge-nav { opacity: 1; }
.lp-asset-thumb-pdf { display: grid; place-items: center; width: 100%; height: 100%; }

/* ── Add-panel button: bottom of each column, under the last panel ── */
.panel-add-btn {
  width: 100%;
  margin-top: 4px;
  padding: 0px 0px;
  display: inherit;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0px solid transparent !important;
  color: var(--board-ink-faint);
  font-size: 20px;
  opacity: 0;
  flex-shrink: 0;
  transition: color .15s, border-color .15s, opacity .15s;
}
.panel-add-btn em {
  font-family: var(--f-data);
  font-style: normal;
  font-size: 9px;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.panel-add-btn:hover { color: var(--amber); border: 1px dashed var(--amber); }

/* ── Stage panning: hold Space + drag to scroll the board ──── */
.columns-scroll.space-pan-ready,
.zoom-viewport.space-pan-ready { cursor: grab; }
.columns-scroll.space-panning,
.zoom-viewport.space-panning { cursor: grabbing; user-select: none; }

/* ── Text panel: markdown source + character count + export ── */
.panel-text .panel-body { display: flex; flex-direction: column; gap: 1px; }
.text-editor {
  width: 100%;
  box-sizing: border-box;   /* full width regardless of flex context — was flex:1-only, which
                               does nothing for width inside Scene's non-flex section body */
  flex: none;               /* flex:1 fought with resize:vertical — flex-grow kept overriding
                               the manually dragged height on every layout pass */
  min-height: 160px;
  resize: vertical;
  /* GEAENDERT (2026-08-24): Karten-Farbton (--card-bg-tint) traegt jetzt
     bis hierher — dieselbe Zwei-Ebenen-Technik wie .panel selbst
     (background-color = Basis, background-image = Toenungs-Verlauf).
     KORRIGIERT (2026-08-24, Teil 2): der Rahmen war im VORIGEN Anlauf
     bewusst STEHEN GEBLIEBEN, mit der Begruendung, die :focus-Regel
     darunter faerbe ihn amber — eine reale Bedienzustands-Rueckmeldung.
     Das war nur die halbe Messung: die :focus-Regel AENDERT nur die
     FARBE, sie ERZEUGT den Rahmen nicht. GEMESSEN (SimplifyerX, Staging):
     border blieb im Ruhezustand (kein Fokus, weder Plain- noch Markdown-
     Modus) 0.909091px solid var(--line) — sichtbar als schwacher Rand
     um die Karten-Flaeche, obwohl die Karte gar nicht angeklickt war.
     Jetzt transparent im Ruhezustand (Breite 1px bleibt erhalten, sonst
     wuerde der Fokus-Rahmen die Flaeche um 2px verschieben), erst
     :focus faerbt ihn ein — "hier schreibe ich gerade" statt "hier kann
     man klicken" (die Karte ist ohnehin ueberall bedienbar). */
  background-color: var(--bg-deep);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  border: 1px solid transparent;
  color: var(--ink);
  font-family: var(--f-label);
  font-size: 12px;
  line-height: 1.55;
  padding: 12px;
  outline: none;
}
.text-editor:focus { border-color: var(--amber); }
.text-editor::placeholder { color: var(--ink-faint); }
/* GEAENDERT (2026-08-24, Teil 3): trug var(--bg) fest -- ein eigener,
   vom Karten-Farbton abgekoppelter Ton direkt unter dem getoenten
   .text-editor, sichtbar als heller Streifen (SimplifyerX-Fund).
   Gleiche Zwei-Ebenen-Technik wie .text-formatted-preview: Basis
   bleibt var(--bg-deep) (dieselbe Familie wie der Editor darueber),
   der Farbverlauf legt sich als zweite Ebene drueber. */
.text-footer {
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 10px;
  background-color: var(--bg-deep);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
}
.panel.toolbar-open .text-footer { display: flex; }
.text-charcount {
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .06em;
  color: var(--ink-dim);
}
.text-footer-actions { display: flex; align-items: center; gap: 6px; }
.text-import,
.text-export {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  transition: color .15s, border-color .15s;
}
.text-import svg, .text-export svg { width: 14px; height: 14px; }
.text-import:hover,
.text-export:hover { color: var(--amber); border-color: var(--amber); }

/* panel-icon: gleichmaessig dezent, bewusst OHNE Hover-Ausnahme.
   GEAENDERT (Nutzer-Korrektur): zwei Hover-Regeln entfernt, die beide
   denselben Effekt erzeugten — ".panel:hover .panel-icon { opacity: 1 }"
   und die aeltere ".panel-head .panel-icon:hover { opacity: 1
   !important; }" (seit dem allerersten Commit, erst beim Schreiben des
   Kommentars an der ersten .panel-icon-Regel gefunden — siehe dort für
   Details). Das Icon soll nicht beim Ueberfahren der Karte ODER des
   Icons selbst aufspringen. Die 0.5 hier ist die aktive Ruhe-Deckkraft
   (diese Regel steht spaeter im File als die gleichnamige oben und
   gewinnt bei gleicher Spezifitaet) — beide synchron gehalten. */
.panel-icon { opacity: 0.5; }

/* ── Scene panel: grouped Image / Video / Assets / Text sections ── */
/* Nested media keeps its full border — no edge-bleed here, since
   multiple stacked sections need a clear boundary against each other. */
.scene-media.hero-media,
.scene-media.video-frame {
  border: 1px solid var(--line);
}

.scene-section { border-top: 1px solid var(--line-soft); }
.scene-section:first-child { border-top: 0; }
.scene-section-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px var(--pad);
  text-align: left;
}
.scene-section-icon {
  width: 15px; height: 15px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  flex-shrink: 0;
}
.scene-section-icon svg { width: 13px; height: 13px; }
.scene-section-label {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 400;
  color: var(--ink-dim);
  margin-right: auto;
  transition: color .15s;
}
.scene-section-chevron {
  width: 14px; height: 14px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  flex-shrink: 0;
}
.scene-section-chevron svg { width: 11px; height: 11px; transition: transform .2s ease; }
/* same convention as the panel-level chevron: down = open, flips to up = collapsed */
.scene-section.collapsed .scene-section-chevron svg { transform: rotate(180deg); }
.scene-section-head:hover .scene-section-label,
.scene-section-head:hover .scene-section-chevron { color: var(--amber); }

/* GEAENDERT (2026-08-24): `margin: 0 calc(-1 * var(--pad))` ENTFERNT.
   Der Gedanke war, die Flaeche per negativem Rand bis an den Kartenrand
   auszudehnen und dann per padding:6px wieder passend hereinzuruecken --
   dafuer haette der negative Rand aber `--pad-card` (das TATSAECHLICHE
   Innenpolster von .panel, siehe dort: `padding: var(--pad-card)`)
   kuerzen muessen, nicht `--pad` (ein fester 10px-Wert fuer andere Zwecke,
   z. B. SET/COL-Abstaende). GEMESSEN bei --pad-card in seiner
   Werkseinstellung (5px): die Flaeche selbst ragte bereits 5px ueber den
   Kartenrand, der Slot-Inhalt sass nur noch 1px statt korrekt 5px
   eingerueckt. Bei CARD Padding "Small" (0px, ueber BOARD SETTINGS
   einstellbar) betrug der tatsaechliche Ueberstand des Slot-Inhalts
   gemessen 3.6px.
   Uebernommener Weg statt Neubau: Single/Grid/alle anderen Kartentypen
   kaempfen gar nicht gegen das Karten-Padding an, ihr Slot ist einfach
   ein normales Kind von .panel-body und erbt --pad-card ganz von selbst
   (siehe hydrateSingle()/zone(), panels.js/app.js). Diese Regel tut jetzt
   dasselbe -- kein negativer Rand mehr, die Flaeche bleibt innerhalb des
   Karten-Innenabstands, exakt wie bei Single. */
.scene-section-body {
  padding: 6px;
  background: rgba(0, 0, 0, .18);   /* subtly darker than the panel, sets content apart from the header */
}
.scene-section.collapsed .scene-section-body { display: none; }

/* ── Spacer panel: fixed-height empty block ────────────────── */
.spacer-block { height: 60px; }
.panel-spacer { background: none; box-shadow: none; padding: 0; }
.panel-spacer .panel-head { margin-bottom: 0; }
.panel-spacer .panel-head h2 { visibility: hidden; pointer-events: none; }
.panel-spacer .collapse-toggle { display: none; }

/* ── Lightbox: large media view with left/right navigation ──── */
.lightbox {
  position: fixed; inset: 0;
  /* GEÄNDERT (2026-08-27): war der feste Wert rgba(var(--overlay-rgb), .95)
     direkt hier — jetzt über --board-bg-tint-lightbox, das genau diesen
     Wert als Default trägt (siehe :root) und bei gesetztem Board-Farbton
     die individuelle Board-Farbe übernimmt (applyBoardBackgroundTint(),
     app.js). Ohne gesetzten Farbton unverändert. */
  background: var(--board-bg-tint-lightbox);
  z-index: var(--z-immersive-view);   /* war 150 — siehe Layer-Ordnung in :root */
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox[hidden] { display: none; }
/* GEAENDERT (2026-08-28, Beschriftung am Medium): position:relative NEU —
   .lightbox-caption haengt jetzt hier als Kind und bindet ihre Breite ueber
   left:0;right:0 an GENAU diese Box, nicht an den Viewport. Das funktioniert,
   weil .lightbox-stage sich (unveraendert seit je) exakt auf die Groesse
   ihres Medien-Kindes schrumpft (kein eigenes width, nur max-width/-height) —
   die Buehne IST bereits der "gemeinsame Behaelter", nach dem der Auftrag
   fragte. max-height um 40px gekuerzt (82vh -> 82vh-40px): reserviert Platz
   fuer die Beschriftung UNTER dem Bild, damit ein hohes Hochformat-Bild bei
   voller Hoehe nicht dazu fuehrt, dass die Beschriftung unten aus dem
   Viewport laeuft. */
.lightbox-stage {
  position: relative;
  max-width: 86vw;
  max-height: calc(82vh - 40px);
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox-stage img,
.lightbox-stage video { max-width: 86vw; max-height: calc(82vh - 40px); object-fit: contain; }
.lightbox-stage audio { width: 420px; max-width: 80vw; }
/* NEU (2026-08-28): reiner Einhaenge-Punkt fuer renderLightbox()
   (media.innerHTML = ...) — display:contents nimmt das Element komplett aus
   dem Layout, seine Kinder (img/video/audio) zaehlen fuer Flex/Groesse als
   direkte Kinder von .lightbox-stage, exakt wie vor dieser Runde. Ohne
   display:contents würde dieser Wrapper selbst eine Box aufspannen und die
   "Buehne = Mediengroesse"-Eigenschaft verwaessern. */
.lightbox-media { display: contents; }
.lightbox-close {
  /* Erscheinung + Rahmen-Ausnahme jetzt in der gemeinsamen
     Schliessen-Regel oben bei .right-panel-close. Hier nur noch die
     Position im Overlay. */
  position: absolute;
  top: 20px; right: 24px;
  /* NEU (2026-08-27, Bedienelemente einheitlich): dieselbe Platte wie
     .lightbox-nav/.lightbox-caption — bisher trug NUR der Rahmen
     (.right-panel-close-Regel), keine Füllung. Überschreibt bewusst das
     "background: none" der gemeinsamen Schließen-Regel, NUR für diese
     eine Klasse. */
  background: var(--lightbox-plate-bg);
  /* NEU (2026-08-27): war --ink-faint (aus der gemeinsamen Schließen-
     Regel geerbt, gilt für alle sechs Klassen dort). GEMESSEN gegen die
     neue Platte: --ink-faint ist im Hell-Theme zu hell für 4,5:1
     (2,81:1) — --ink-dim (wie bei Pfeilen/Beschriftung) liegt bei
     6,69:1. Nur hier überschrieben, die anderen fünf Klassen der
     Sammelregel behalten --ink-faint (siehe Bericht). */
  color: var(--ink-dim);
  /* GEAENDERT (2026-08-28, keine Rahmen): war border:1px solid var(--line)
     (die einzige AUSNAHME der sechs Schliessen-Klassen, siehe die
     gemeinsame Regel oben) -- Auftrag SimplifyerX: alle Lightbox-
     Bedienelemente ohne Rahmen. GEMESSEN, ob das den Kontrast
     Platte-gegen-Bild kostet: bei .8 Deckkraft komponiert die Platte
     gegen ein FARBLICH AEHNLICHES Bildareal (dunkle Platte auf dunklem
     Bildbereich, helle Platte auf hellem) auf nur ~1,0-1,15:1 -- der
     Rahmen HAT dort tatsaechlich getragen. Hoehere Deckkraft als Ersatz
     GEPRUEFT und verworfen: .8 -> .95 aendert diesen Wert praktisch
     nicht (1,12 -> 1,13), weil das Problem Farbaehnlichkeit ist, nicht
     zu wenig Deckkraft -- derselbe Grundbefund wie in der allerersten
     Hintergrund-Runde ("kein Alpha-Wert loest das"). Das SYMBOL selbst
     bleibt in jedem Fall lesbar (Kontrast Symbol-gegen-Platte ist fest
     und geprueft, siehe vorige Runden) -- nur die Wahrnehmbarkeit der
     Knopf-FLAECHE auf genau diesem seltenen Bildton leidet. Siehe
     Bericht dieser Runde fuer die volle Messung und Empfehlung. */
  border: 0;
}
/* GEAENDERT (2026-08-27): war ein Textzeichen (✕, U+2715) — GEMESSEN
   per document.fonts.check()/Canvas: der Font (JetBrains Mono) traegt
   das Glyph durchaus, aber mit derart duennen Strichen, dass es bei
   16px/32px-Knopf kaum wahrnehmbar wird (SimplifyerX-Fund: im Hellen
   ein blasser Strich, im Dunklen gar nichts — bestaetigt auch im
   Board, nicht nur im Share, bei genauem Hinsehen ebenso betroffen).
   Jetzt dieselbe Technik wie .lightbox-prev/-next::before: zwei feste,
   garantiert 2px dicke Striche statt eines Font-Glyphs, das nie
   garantiert eine Mindeststrichstaerke hat. currentColor folgt
   automatisch dem :hover-Farbwechsel, keine eigene Hover-Regel noetig.
   NUR .lightbox-close betroffen — die anderen fuenf Klassen der
   gemeinsamen Schliessen-Regel (.right-panel-close usw.) behalten das
   Textzeichen, siehe Bericht dieser Runde fuer den ungeprueften Rest. */
.lightbox-close::before,
.lightbox-close::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 14px; height: 2px;
  background: currentColor;
}
.lightbox-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.lightbox-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }
.lightbox-nav {
  position: absolute;
  top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  color: var(--ink-dim);
  /* GEAENDERT (2026-08-28, keine Rahmen): war border:1px solid var(--line)
     -- siehe Kommentar bei .lightbox-close fuer die Kontrastmessung
     (Platte-gegen-Bild ohne Rahmen), die fuer alle Bedienelemente
     gemeinsam gilt. */
  border: 0;
  /* GEAENDERT (2026-08-27): war rgba(var(--overlay-rgb), .6) direkt hier
     -- jetzt dieselbe Platte wie .lightbox-close/.lightbox-caption, ueber
     ein gemeinsames Token statt einem eigenen Wert. SimplifyerX-Fund:
     die Pfeile wirkten "kraeftiger als noetig" -- GEMESSEN traegt .6
     aber bereits zu WENIG Deckkraft fuer sichere Lesbarkeit bei hellen/
     gesaettigten Board-Toenen (Kontrast faellt auf 2,46:1, siehe Bericht).
     .8 ist damit numerisch staerker, nicht schwaecher -- der Unterschied
     zwischen "wirkt kraeftig" und "ist tatsaechlich zu schwach fuer den
     ungeguenstigsten Fall" ist die eigentliche Erkenntnis, siehe Bericht. */
  background: var(--lightbox-plate-bg);
  transition: color .15s;
}
.lightbox-nav:hover { color: var(--amber); }
.lightbox-nav svg { width: 16px; height: 16px; }
.lightbox-prev { left: 24px; }
.lightbox-next { right: 24px; }
/* GEAENDERT (2026-08-28, Pfeile zentrieren, RUNDE 2): die Vorrunde setzte
   die SPITZE (den Ankerpunkt/transform-origin) exakt auf die Knopfmitte
   und hielt das fuer hinreichend -- SimplifyerX widersprach zurecht: ein
   Winkel ist keine symmetrische Form, das Auge sieht die sichtbare MASSE
   der beiden Schenkel, nicht den Punkt, an dem sie sich treffen. PIXELTEST
   (Canvas, 20-fach ueberabgetastet, exakt dieselbe Geometrie wie unten
   nachgebaut, Alpha-Schwerpunkt + umschliessende Box der sichtbaren
   Pixel ausgewertet -- dieselbe Technik wie beim ✕-Glyph-Fund):
   Ankerpunkt lag exakt auf der Mitte (Versatz ~0), aber die umschliessende
   Box der sichtbaren Striche lag bei PREV 2,83px RECHTS der Mitte, bei
   NEXT 2,83px LINKS -- weil die Spitze der linkeste (prev) bzw. rechteste
   (next) Punkt der Form ist und beide Schenkel nur in eine Richtung
   ausschwingen. Analytisch bestaetigt (exakte Rotationsgeometrie fuer
   zwei 8x2-Balken bei ±45°): der Schwerpunkt-Versatz ist exakt 2√2 ≈
   2,8284px, unabhaengig von der Bildschirmaufloesung.
   FIX: derselbe Ankerpunkt-Trick wie in Runde 1 bleibt (Rotation um einen
   festen Punkt, keine Kalibrierung an sich) -- aber dieser Punkt wird um
   genau 2,8284px in die der Spitze entgegengesetzte Richtung verschoben,
   sodass NICHT die Spitze, sondern die sichtbare Box zentriert landet.
   Bei PREV (Spitze links, Masse rechts davon): Ankerpunkt 2,8284px NACH
   LINKS von der Mitte (calc(50% - 2.8284px)). Bei NEXT (Spitze rechts,
   Masse links davon): Ankerpunkt 2,8284px NACH RECHTS
   (calc(50% - 2.8284px) auf `right`, was den rechten Rand naeher an die
   Knopf-Kante und damit den Ankerpunkt nach rechts schiebt). GEGENGEPRUEFT
   mit demselben Pixeltest nach dem Fix, siehe Bericht: Box-Mittelpunkt
   jetzt auf ~0,00px Versatz. */
.lightbox-prev::before, .lightbox-prev::after,
.lightbox-next::before, .lightbox-next::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 8px; height: 2px;
  margin-top: -1px;
  background: currentColor;
}
.lightbox-prev::before, .lightbox-prev::after { left: calc(50% - 2.8284px); transform-origin: left center; }
.lightbox-prev::before { transform: rotate(45deg); }
.lightbox-prev::after  { transform: rotate(-45deg); }
.lightbox-next::before, .lightbox-next::after { right: calc(50% - 2.8284px); transform-origin: right center; }
.lightbox-next::before { transform: rotate(-45deg); }
.lightbox-next::after  { transform: rotate(45deg); }
/* GEAENDERT (2026-08-28, Beschriftung am Medium): war eine frei im
   Viewport schwebende, textbreite Pille (left:50%+transform, bottom:24px
   relativ zur GANZEN Lightbox) -- SimplifyerX-Fund aus der Vorrunde selbst
   war falsch: "eine Pille schwebt, eine Leiste gehoert dazu". Jetzt Kind
   von .lightbox-stage (siehe index.html) und ueber left:0;right:0 an GENAU
   deren Breite gebunden -- die Buehne schrumpft bereits exakt auf die
   Mediengroesse (unveraendert), die Beschriftung uebernimmt das per CSS,
   ohne dass irgendwo die tatsaechliche Pixelbreite des geladenen Bildes
   gemessen werden muss.
   top:100% statt bottom:0: sitzt UNTER der Buehne, nicht darueber gelegt --
   bewusste Entscheidung (Auftrag liess offen, "auf dem Rand oder darunter"
   frei). Ueberlagernd (bottom:0) wuerde bei einem Storyboard-Raster
   Bildinhalt am unteren Rand verdecken, und wuerde bei Video zwangslaeufig
   mit den nativen Bedienelementen kollidieren, die der Browser dort selbst
   platziert. Direkt-darunter kollidiert mit keinem von beidem, weil die
   Beschriftung dann schlicht ausserhalb der Medien-Box liegt -- structurell
   unmoeglich, sich zu ueberlappen, nicht nur unwahrscheinlich.
   white-space:nowrap+ellipsis bleibt (Sicherheitsnetz aus der Vorrunde) --
   bewusst NICHT auf Umbruch umgestellt: eine zweizeilige Beschriftung haette
   eine variable Hoehe, die den unter .lightbox-stage reservierten Platz
   (siehe max-height dort) pro Bild neu haette treffen muessen. Eine feste
   Zeilenhoehe ist vorhersagbar, wie jede andere Leiste im Tool auch.
   Farbe/Platte unveraendert seit der Vorrunde (--ink-dim/--lightbox-plate-bg,
   beide fest, fuer konstanten Kontrast). */
/* GEAENDERT (2026-08-28, Abstand auf 0): GEMESSEN, welche Eigenschaft den
   Zwischenraum erzeugte (getBoundingClientRect() vor der Aenderung): exakt
   8px, exakt gleich dem margin-top -- top:100% selbst traegt 0 (setzt die
   Beschriftung exakt an die untere Kante der Buehne, ohne eigenen Versatz),
   padding ist das INNENpolster (bleibt, siehe unten). margin-top entfernt,
   kein Ersatzwert noetig (globale Reset-Regel `*{margin:0}` greift bereits).
   Rahmen ENTFERNT (border:0) -- siehe Kommentar bei .lightbox-close fuer
   die Kontrastmessung Platte-gegen-Bild. */
.lightbox-caption {
  position: absolute;
  left: 0; right: 0;
  top: 100%;
  padding: 5px 10px;
  border: 0;
  background: var(--lightbox-plate-bg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: center;
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .06em;
  color: var(--ink-dim);
}

/* zoom affordance on media that can actually be opened in the lightbox —
   a real, visible icon rather than relying on a cursor change, which
   could be visually masked by an ancestor's own cursor (e.g. the
   Characters/Vehicles carousel sets cursor:grab on its wrapper).
   Excluded on the dedicated Video-panel player — it has its own controls. */
.media[data-upload-key]:not([data-video-player]) img,
.media[data-upload-key]:not([data-video-player]) video,
.media[data-upload-key]:not([data-video-player]) audio { cursor: zoom-in; }

.media-zoom-hint {
  position: absolute;
  right: 8px; bottom: 8px;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  background: rgba(var(--overlay-rgb), .78);
  color: var(--ink-dim);
  z-index: 4;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
  display: none;   /* hidden for now, per feedback — markup/logic kept intact */
}
.media-zoom-hint svg { width: 12px; height: 12px; }
.media[data-upload-key]:not([data-video-player]):has(> img, > video, > audio):hover .media-zoom-hint { opacity: 1; }

/* ── Sidebar: "+ Structure" trigger next to the Project heading ── */
.world-gallery-head { display: flex; align-items: center; gap: 6px; }
.md-import-open {
  display: block;
  width: 100%;
  margin-bottom: 12px;
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  padding: 7px 6px;
  transition: color .15s, border-color .15s;
}
.md-import-open:hover { color: var(--amber); border-color: var(--amber); }

/* ── Markdown board-import dialog ────────────────────────────── */
.md-import-dialog {
  position: fixed; inset: 0;
  background: rgba(var(--overlay-rgb), .92);
  z-index: var(--z-modal-dialog);   /* war 170 — eigenständiger blockierender Dialog (nicht confirmDialog()), verschachtelt aber selbst einen confirmDialog()-Aufruf beim Ersetzen des Boards (importBoardFromMarkdown()), siehe Layer-Ordnung in :root */
  display: flex;
  align-items: center;
  justify-content: center;
}
.md-import-dialog[hidden] { display: none; }
.md-import-box {
  width: min(560px, 90vw);
  max-height: 85vh;
  overflow-y: auto;
  background: var(--panel);
  box-shadow: var(--panel-shadow);
}
.md-import-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.md-import-head h2 { font-family: var(--f-display); font-size: 16px; font-weight: 500; color: var(--ink); }
/* .md-import-close: Erscheinung komplett in der gemeinsamen
   Schliessen-Regel bei .right-panel-close, hier nichts Eigenes mehr. */
.md-import-hint {
  padding: 14px 18px 0;
  font-family: var(--f-data);
  font-size: 11px;
  line-height: 1.6;
  color: var(--ink-dim);
}
.md-import-example {
  margin: 10px 18px 0;
  padding: 10px 12px;
  background: var(--bg-deep);
  font-family: var(--f-data);
  font-size: 10.5px;
  line-height: 1.6;
  color: var(--ink-faint);
  overflow-x: auto;
}
.md-import-actions-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px 0;
}
/* GEAENDERT (2026-08-13): trug bis heute einen AMBER-Hover und ist
   damit beim Umbau als Regelverstoss aufgefallen — die Akzentfarbe
   darf an Buttons nirgends vorkommen, auch nicht im Hover. Der Knopf
   stand in der Bestandsaufnahme dieser Runde zunaechst nicht drin
   (Sitzung A hatte ihn nie angefasst), gehoert aber offensichtlich in
   dieselbe Rolle wie sein Nachbar "Create" im selben Dialog: jetzt
   .btn im Markup, default/medium. Hier bleibt nichts Eigenes. */
.md-import-or {
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
}

/* editor: a highlighted <pre> sits behind a transparent-text
   <textarea> with the same font metrics — the textarea stays fully
   editable/selectable/focusable, the pre only supplies color */
.md-editor {
  position: relative;
  margin: 12px 18px 0;
  height: 220px;
  border: 1px solid var(--line);
}
.md-highlight, .md-input {
  position: absolute; inset: 0;
  margin: 0;
  padding: 10px 12px;
  font-family: var(--f-data);
  font-size: 12px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: auto;
  box-sizing: border-box;
}
.md-highlight { color: var(--ink); pointer-events: none; background: var(--bg-deep); }
.md-highlight code {
  font: inherit;            /* browsers give <code> its own default monospace font,
                                which silently wins over the inherited --f-data and
                                throws off character widths vs. the textarea above it */
  white-space: inherit;
  display: block;
}
.md-input {
  background: transparent;
  color: transparent;
  caret-color: var(--ink);
  resize: none;
  outline: none;
}
.md-input::placeholder { color: var(--ink-faint); }
.mdh-heading { color: #2b5ea7; }
:root[data-theme="light"] .mdh-heading { color: #1a3d7a; }
.mdh-bullet-marker { color: var(--ink-faint); }
.mdh-code { color: var(--green); }

.md-import-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 18px 18px;
}
.md-import-status {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--ink-faint);
}
.md-import-status-error { color: var(--red); }
/* ── Rolle: Hauptaktion (Dialoge) ─────────────────────────────────
   NEU (2026-08-12, Sitzung A / Etappe 4). Drei Bestaetigen-Knoepfe in
   drei verschiedenen Dialogen (MD-Import, Referenz-Bild-Waehler,
   Crop-Zuschnitt), bisher pixelgleich bis auf letter-spacing (.06em
   vs .08em) — durch einen gemeinsamen Selektor ersetzt.

   Der Amber-Rahmen faellt bei allen dreien: Akzentfarbe bleibt
   ausschliesslich Aktiv-Markierung (Nutzer-Vorgabe aus der Vorrunde,
   hier konsequent zu Ende gefuehrt) — ein Akzent-Rahmen um einen
   Aktionsknopf waere keine Markierung mehr, sondern Dekoration, und
   funktioniert nicht mehr, sobald der Nutzer die Akzentfarbe wechselt. */
/* GEAENDERT (2026-08-13): beide Bloecke — Bestaetigen wie Abbrechen —
   sind ersatzlos entfallen. Die drei Bestaetigen-Knoepfe und die zwei
   Abbrechen-Knoepfe tragen jetzt .btn im Markup und sind damit
   default/medium; es gibt an ihnen nichts Eigenes mehr zu setzen.

   Nebenbei mit erledigt: den beiden Abbrechen-Knoepfen fehlte seit
   dem Zusammenlegen am 2026-08-13 das text-transform (Versehen beim
   Merge — es fiel nur deshalb nicht auf, weil ihr HTML-Text zufaellig
   schon gross geschrieben war). .btn setzt es jetzt fuer alle. */

/* ── Crop-to-Asset (NEU 2026-08) ───────────────────────────────────────
   Eigenes Geschwister-Overlay nach der .md-import-dialog-Konvention
   (fixed inset, --z-modal-dialog, zentrierte Box) — bewusst kein
   Wiederverwenden von .lightbox/.focus-overlay, siehe Untersuchung. */
/* ── Referenz-Overlay (2026-08-06, Asset-Referenzen Auswahlweg 2) ──
   Hülle bewusst identisch zum Crop-Overlay darunter (gleiche
   Positionierung, gleiche z-Variable, gleiches [hidden]-Muster) —
   zwei Overlays, die sich unterschiedlich anfühlen, wären grundlos
   verwirrend. Nur der Kasten ist schmaler: hier steht eine Liste,
   kein Bildeditor. */
.ref-picker-overlay {
  position: fixed; inset: 0;
  background: rgba(var(--overlay-rgb), .92);
  z-index: var(--z-modal-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ref-picker-overlay[hidden] { display: none; }
.ref-picker-box {
  width: min(560px, 92vw);
  max-height: 82vh;
  background: var(--panel);
  box-shadow: var(--panel-shadow);
  display: flex;
  flex-direction: column;
}
.ref-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.ref-picker-head h2 { font-family: var(--f-display); font-size: 16px; font-weight: 500; color: var(--ink); }
/* .ref-picker-close: Erscheinung komplett in der gemeinsamen
   Schliessen-Regel bei .right-panel-close, hier nichts Eigenes mehr. */
.ref-picker-target {
  padding: 8px 18px 0;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
  letter-spacing: .04em;
}
.ref-picker-search {
  margin: 10px 18px;
  background: var(--bg-deep);
  border: 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font-family: var(--f-label);
  font-size: 12px;
  padding: 7px 4px;
  outline: none;
}
.ref-picker-search:focus { border-bottom-color: var(--amber); }
/* Die Liste ist der einzige wachsende Teil — Kopf, Suchfeld und
   Aktionszeile bleiben immer sichtbar, auch bei vielen Treffern. */
.ref-picker-list {
  flex: 1;
  overflow-y: auto;
  padding: 0 10px;
  min-height: 120px;
}
.ref-picker-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 8px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  text-align: left;
}
.ref-picker-row:hover { background: var(--line-soft); }
.ref-picker-row.checked { background: var(--amber-dim); }
.ref-picker-check {
  width: 14px; height: 14px;
  flex: 0 0 14px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  color: var(--amber);
  font-size: 9px;
}
.ref-picker-row.checked .ref-picker-check { border-color: var(--amber); }
/* GEAENDERT (2026-08-29, SimplifyerX-Auftrag): war 28x28 + object-fit:
   cover. GEMESSEN am realen Fall (storyboard-grid3x3-Assets, echte
   Quellaufloesung 2480x3312, KEIN separat generiertes Vorschaubild --
   dieselbe blob:-URL wie ueberall sonst): bei 28px war ein 3x3-Raster
   ausschliesslich ein unscharfer Fleck, unabhaengig vom Bildinhalt.
   120px (= dieselbe Breite wie .lp-asset-thumb, die etablierte Groesse
   der Assets-Manager-Liste) macht das Raster samt Bildunterschriften
   GEMESSEN lesbar. object-fit von cover auf contain gewechselt: cover
   schnitt bei einem Hochformat-Bild dieser Aufloesung (Seitenverhaeltnis
   0,749) in einer QUADRATISCHEN Box rechnerisch rund 25% der Hoehe ab
   (12,5% oben + 12,5% unten) -- genau der Teil, in dem bei einem 3x3-
   Raster die aeusseren Reihen liegen. contain zeigt das Bild immer
   vollstaendig, auf Kosten von Rand-Freiflaechen bei Hoch- ODER
   Querformat -- deshalb der Hintergrund als sichtbare Flaeche, statt
   dass ein Seitenrand wie ein Rendering-Fehler aussieht. Ladezeit-
   Wirkung der reinen Groessenaenderung GEMESSEN vernachlaessigbar
   (43 Bilder, warmer Cache: 5,3ms bei 28px vs. 7,8ms bei 120px) --
   Decodierkosten haengen von der QUELLAUFLOESUNG ab, nicht von der
   Darstellungsgroesse; das eigentliche Ladezeit-Risiko bei vielen
   Assets ist das fehlende loading="lazy" in renderRefPicker() (app.js),
   dort behoben. */
.ref-picker-thumb {
  width: 120px; height: 120px;
  flex: 0 0 120px;
  object-fit: contain;
  background: var(--line-soft);
  display: block;
}
/* NEU (2026-08-29, SimplifyerX-Auftrag, Folgerunde): war reine leere
   Flaeche fuer Audio -- bei 120px auffaellig gross. display:flex neu,
   damit der Notennoten-Glyph (siehe app.js) zentriert sitzt, analog
   .lp-asset-thumb-audio im Assets Manager. */
.ref-picker-thumb-empty { background: var(--line-soft); display: flex; align-items: center; justify-content: center; }
.ref-picker-thumb-icon { font-size: 14px; color: var(--ink-faint); }
/* NEU (2026-08-29, SimplifyerX-Auftrag): <img src="video.mp4"> konnte nie
   ein Bild dekodieren -- naturalWidth blieb 0, die Zeile zeigte nur die
   leere Hintergrundflaeche. Bei 28px kaum sichtbar, bei 120px eine grosse
   leere Flaeche je Video. Dasselbe Muster wie .lp-asset-thumb-video im
   Assets Manager (populateAssetsPanel()): echtes <video>-Element statt
   <img>, plus Play-Symbol. */
.ref-picker-thumb-video {
  position: relative;
  width: 120px; height: 120px;
  flex: 0 0 120px;
  background: var(--line-soft);
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.ref-picker-thumb-video video { width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
.ref-picker-name {
  flex: 1;
  font-family: var(--f-label);
  font-size: 12px;
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ref-picker-type {
  font-family: var(--f-data);
  font-size: 9px;
  color: var(--ink-faint);
  letter-spacing: .04em;
}
.ref-picker-empty {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
  padding: 20px;
  text-align: center;
}
.ref-picker-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--line-soft);
}
.ref-picker-count {
  flex: 1;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
}
/* .ref-picker-cancel / .ref-picker-save: Erscheinung komplett in den
   gemeinsamen Rollen-Regeln oben (Hauptaktion/Nebenaktion), hier
   nichts Eigenes mehr. */

.crop-overlay {
  position: fixed; inset: 0;
  background: rgba(var(--overlay-rgb), .92);
  z-index: var(--z-modal-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
}
.crop-overlay[hidden] { display: none; }
.crop-box {
  width: min(920px, 92vw);
  max-height: 92vh;
  background: var(--panel);
  box-shadow: var(--panel-shadow);
  display: flex;
  flex-direction: column;
}
.crop-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line-soft);
}
.crop-head h2 { font-family: var(--f-display); font-size: 16px; font-weight: 500; color: var(--ink); }
/* .crop-close: Erscheinung komplett in der gemeinsamen
   Schliessen-Regel bei .right-panel-close, hier nichts Eigenes mehr. */

.crop-stage {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg-deep);
  overflow: auto;
}
.crop-imgwrap { position: relative; display: inline-block; line-height: 0; }
.crop-img {
  display: block;
  max-width: min(78vw, 820px);
  max-height: 62vh;
  user-select: none;
  -webkit-user-drag: none;
}
/* Rahmen: box-shadow-Trick statt separater Abdunkelungs-Ebene — alles
   ausserhalb des Rahmens wird verdunkelt, ohne ein eigenes Overlay-Element. */
.crop-frame {
  position: absolute;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .6);
  border: 1px solid var(--amber);
  cursor: move;
  touch-action: none;
}
.crop-thirds {
  position: absolute;
  background: rgba(255, 255, 255, .35);
  pointer-events: none;
}
.crop-thirds-v1 { left: 33.333%; top: 0; bottom: 0; width: 1px; }
.crop-thirds-v2 { left: 66.666%; top: 0; bottom: 0; width: 1px; }
.crop-thirds-h1 { top: 33.333%; left: 0; right: 0; height: 1px; }
.crop-thirds-h2 { top: 66.666%; left: 0; right: 0; height: 1px; }
.crop-handle {
  position: absolute;
  width: 14px; height: 14px;
  margin: -7px;
  background: var(--amber);
  border: 1.5px solid var(--bg-deep);
  border-radius: 50%;
  touch-action: none;
}
.crop-handle-nw { left: 0; top: 0; cursor: nwse-resize; }
.crop-handle-se { left: 100%; top: 100%; cursor: nwse-resize; }
.crop-handle-ne { left: 100%; top: 0; cursor: nesw-resize; }
.crop-handle-sw { left: 0; top: 100%; cursor: nesw-resize; }

.crop-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px 18px;
  border-top: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.crop-lock-btn {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  transition: color .15s, border-color .15s;
}
.crop-lock-btn svg { width: 15px; height: 15px; }
.crop-lock-btn.crop-lock-active { color: var(--amber); border-color: var(--amber); }
.crop-ratio-select {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--line);
  padding: 6px 8px;
}
.crop-dim-label {
  display: flex; align-items: center; gap: 4px;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  color: var(--ink-dim);
}
.crop-dim-input {
  width: 64px;
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink);
  background: var(--panel-2);
  border: 1px solid var(--line);
  padding: 5px 6px;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm — reiht sich damit neben die
   bereits umgestellten .crop-cancel-btn/.crop-save-btn (Etappe 4) ein. */
.crop-actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
}
/* .crop-cancel-btn / .crop-save-btn: Erscheinung komplett in den
   gemeinsamen Rollen-Regeln oben (Hauptaktion/Nebenaktion), hier
   nichts Eigenes mehr. */

/* ── Prompt dot on upload thumbnails ──────────────────────── */
.prompt-dot {
  position: absolute;
  top: 4px; right: 4px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #cc3333;        /* red = default (no data) */
  pointer-events: none;
  z-index: 5;
  box-shadow: 0 0 0 1.5px var(--panel);
}
.prompt-dot.dot-yellow { background: #d4a017; }
.prompt-dot.dot-green  { background: #3a9e5f; }

/* ── Left Panel: Assets gallery ──────────────────────────── */
.lp-assets-counts {
  padding: 6px 12px 0;
  display: flex;
  align-items: center;
  gap: 22px;
  color: var(--ink-faint);
  flex-shrink: 0;
}
/* NEU (2026-07): Text-Labels ("Images"/"Videos"/"Audios") durch Icons
   ersetzt, Icons + Zahl um 2.5x vergrößert (vorher 10px Text/14px Icon
   im .panel-icon-Kontext → jetzt 25px/35px). */
.lp-assets-count-item { display: flex; align-items: center; gap: 8px; }
.lp-assets-count-icon svg { width: 20px; height: 20px; display: block; }
.lp-assets-count-num { font-family: var(--f-display); font-size: 25px; color: var(--ink); }
/* NEU (2026-07): Gesamtgröße, rechtsbündig — margin-left:auto schiebt
   sie ans Ende der Reihe, unabhängig davon wie viele Count-Items davor
   stehen. */
.lp-assets-count-size {
  margin-left: auto;
  font-family: var(--f-panel);
  font-size: 16px;
  color: var(--ink);
}
.lp-assets-toolbar {
  display: flex;
  gap: 4px;
  padding: 8px 12px 6px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--line-soft);
}
.lp-assets-sort {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 6px 12px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--line-soft);
}
.lp-assets-sort-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* NEU (2026-08-14, Kachelansicht): fasst Umschalter + Sortierknoepfe +
   Select-All als EINE rechtsbuendige Gruppe zusammen — vorher waren das
   drei direkte Kinder von .lp-assets-sort, ueber justify-content:
   space-between verteilt (die Sortierknoepfe landeten dadurch in der
   Mitte, nicht rechts, wie vom Auftrag jetzt verlangt). Mit nur noch
   ZWEI Kindern (links/rechts) bleibt space-between auf .lp-assets-sort
   unveraendert richtig — es drueckt die Gruppe einfach an den rechten
   Rand, ohne sie selbst aufzuspalten. */
.lp-assets-sort-right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.lp-assets-viewmode { display: flex; align-items: center; gap: 2px; }
.lp-assets-sort-btns { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.asset-sort-btn {
  display: flex; align-items: center; gap: 4px;
  background: none; border: 0;
  color: var(--ink-dim);
  font-family: var(--f-data); font-size: 10px; letter-spacing: .02em;
  padding: 3px 2px;
  cursor: pointer;
  transition: color .12s;
}
.asset-sort-btn:hover { color: var(--ink); }
.asset-sort-btn.active { color: var(--amber); }
:root:not([data-theme="light"]) .asset-sort-btn.active { color: var(--ink-hi); }
.asset-sort-arrow svg { width: 8px; height: 11px; display: block; }
.asset-sort-arrow .sort-arrow-up,
.asset-sort-arrow .sort-arrow-down { color: var(--ink-faint); opacity: .45; transition: color .12s, opacity .12s; }
.asset-sort-arrow.sort-active-asc .sort-arrow-up { color: var(--amber); opacity: 1; }
.asset-sort-arrow.sort-active-desc .sort-arrow-down { color: var(--amber); opacity: 1; }
/* GEÄNDERT (2026-08-04): container-type/-name saßen bisher auf
   .left-panel[data-panel-target="assets"] — dem ANGEDOCKTEN Wrapper.
   Das schwebende Fenster nutzt einen anderen Wrapper
   (.floating-panel-inner), der Container existierte dort nie, die
   Liste blieb schwebend immer einspaltig. .lp-assets-scroll liegt im
   Panel-EIGENEN Markup (renderAssetsPanel()) und damit in BEIDEN
   Zuständen — exakt dieselbe Korrektur wie gestern bei
   [data-presets-body] (siehe dort). */
.lp-assets-scroll {
  padding-top: 6px;
  padding-bottom: 46px;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  container-type: inline-size;
  container-name: assets-panel;
}
/* width/track/thumb/hover: siehe gemeinsame Regel bei .settings-meta-rows.
   display:block bleibt hier eigene, spezifische Regel (nicht Teil der
   geteilten Scrollbar-Optik). */
.lp-assets-scroll::-webkit-scrollbar { display: block; }
.asset-filter-btn {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  padding: 3px 7px;
  transition: color .12s, border-color .12s, background .12s;
}
.asset-filter-btn:hover { color: var(--amber); border-color: var(--amber); }
.asset-filter-btn.active { color: var(--amber); border-color: var(--amber); background: var(--amber-dim); }
.lp-asset-row {
  display: flex;
  /* NEU: stretch statt flex-start — .lp-asset-info muss die volle
     Zeilenhöhe einnehmen, damit .lp-asset-file-info per margin-top:auto
     tatsächlich unten andocken kann. .lp-asset-thumb hat eine feste
     Höhe und bleibt davon unberührt. */
  align-items: stretch;
  gap: 10px;
  /* GEAENDERT (2026-08-03): war "8px 0" — Text und Vorschaubild klebten
     dadurch an beiden Panelraendern, waehrend jede andere Zeile im
     Panel (Kopfzeile, Werkzeugleiste, Sortierzeile, Suchfeld) 12px
     Innenabstand hat. Die Zeile war der einzige Ausreisser. Faellt
     zusaetzlich seit der Show-Markierung auf, deren Rahmen sonst genau
     auf den Panelkanten liegt. */
  padding: 8px 12px 8px 9px;   /* GEÄNDERT (2026-08-12): links 12px→9px, kompensiert den neuen 3px border-left unten — kein Sprung beim Hervorheben */
  border-left: 3px solid transparent;   /* NEU: reserviert den Platz für .lp-asset-row-revealed, siehe dort */
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  /* NEU (2026-08-04, Nutzer-Vorgabe): geprüft — .lp-asset-row wird
     ausschließlich in populateAssetsPanel() gerendert, keine andere
     Stelle im Code nutzt diesen Klassennamen. Wirkt in BEIDEN Zuständen
     des ASSETS-Panels (angedockt wie schwebend, dieselbe Markierung),
     nicht in irgendeinem anderen Panel. */
  /* GEÄNDERT (2026-08-13, Nutzer-Vorgabe/SimplifyerX): war var(--panel),
     nur 1.20:1 gegen den Panel-Hintergrund (--bg) — die Liste verschwamm
     zu einer Flaeche. --asset-row-bg siehe :root-Definition. */
  background: var(--asset-row-bg);
}
.lp-asset-row:last-child { border-bottom: none; }   /* GEÄNDERT (2026-08-12): war "border: none" (Shorthand) — löschte den neuen border-left mit, sobald die letzte Zeile zugleich hervorgehoben ist */
.lp-asset-row:hover { background: var(--panel-2); }
.lp-asset-thumb {
  width: 120px; height: 90px;
  background: var(--line-soft);
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  overflow: hidden;
  /* NEU: Thumbnail rechts in der Zeile positionieren, statt links */
  order: 1;
}
.lp-asset-thumb-img { width: 100%; height: 100%; object-fit: contain; display: block; }
.lp-asset-thumb-icon { font-size: 14px; color: var(--ink-faint); }
.lp-asset-info {
  flex: 1; min-width: 0;
  /* NEU: Spalte, damit .lp-asset-file-info per margin-top:auto nach
     unten ausgerichtet werden kann (siehe unten) */
  display: flex;
  flex-direction: column;
}
.lp-asset-name {
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lp-asset-meta { font-family: var(--f-data); font-size: 9px; color: var(--ink-faint); margin-top: 1px; }
.lp-assets-empty { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); padding: 12px 0; }

/* NEU (2026-08-14, Kachelansicht — Anlass: Screenrecordings, nur das
   Vorschaubild soll die Aufmerksamkeit haben). Doppelte Klasse
   (.lp-asset-list.lp-asset-list-grid) hat hoehere Spezifitaet als
   .lp-asset-list allein und gewinnt damit auch gegen die
   @container-Regel oben (die feste 2-Spalten-Ansicht ab 636px) — die
   Kachel-Ansicht soll IMMER auto-fill sein, unabhaengig von der
   Panelbreite, nicht bei einer bestimmten Breite auf das Listen-Raster
   zurueckfallen. 120px = dieselbe Breite wie .lp-asset-thumb heute (der
   Auftrag verlangt ausdruecklich "aehnlich den heutigen Listen-
   Thumbnails") — auto-fill statt einer festen Spaltenzahl, gleiches
   Muster wie .world-group-body (Dashboard-Raster), nur mit kleinerem
   Mindestmass fuer die kleineren Thumbnails hier. */
.lp-asset-list.lp-asset-list-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 8px;
}
/* .lp-asset-row bleibt die AEUSSERE Klasse (siehe Kommentar in
   populateAssetsPanel()) — .lp-asset-tile ueberschreibt hier gezielt nur
   das Layout, nicht die Klick-/Auswahl-Zugehoerigkeit. */
.lp-asset-tile {
  display: block;
  position: relative;   /* Bezugsrahmen fuer .lp-asset-select (position:absolute) — .lp-asset-thumb uebernimmt diese Rolle in der Liste, hier gibt es diesen Wrapper nicht */
  aspect-ratio: 4 / 3;   /* 120x90 aus der Liste als Seitenverhaeltnis, nicht als feste Groesse — passt sich auto-fill an */
  padding: 0;
  border: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);   /* ueberschreibt .lp-asset-row:last-child { border-bottom:none }: in einem Raster ist "letztes Element" keine sinnvolle Kante mehr */
  background: var(--line-soft);
  cursor: pointer;
  overflow: hidden;
}
.lp-asset-tile:hover { background: var(--panel-2); }
.lp-asset-tile .lp-asset-thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lp-asset-tile .lp-asset-thumb-video { width: 100%; height: 100%; position: relative; }
.lp-asset-tile .lp-asset-select { top: 4px; right: 4px; }
/* Nicht-Bild (PDF/MD/Audio/SVG/Font): grosses Icon + Titel am unteren
   Rand, sonst waere eine Kachel von der naechsten nicht unterscheidbar
   (Auftrag, ausdruecklich). .rp-thumb-doc(-icon) ist dasselbe Bauteil
   wie im Detail-Panel (openRightPanelForUpload()) — wiederverwendet
   statt neu gezeichnet, nur die Kachel-Grösse via aspect-ratio anders. */
.lp-asset-tile-doc { width: 100%; height: 100%; }
.lp-asset-tile-doc .rp-thumb-doc-icon { width: 32px; height: 32px; }
.lp-asset-tile-caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 3px 5px;
  font-family: var(--f-data);
  font-size: 9px;
  color: var(--ink);
  background: rgba(var(--overlay-rgb), .85);   /* dieselbe Flaeche wie die Checkbox — bereits als "schwebt ueber dem Bild, bleibt lesbar" etabliert */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* NEU (2026-07): ab einer ASSETS-Panel-Breite von 3x --left-panel-w
   zeigt die Liste 2 Spalten statt einer — reagiert auf die eigene Breite
   des Panels (container-type:inline-size auf .lp-assets-scroll, siehe
   dort — GEÄNDERT 2026-08-04, stand vorher auf dem angedockten Wrapper,
   siehe Kommentar dort), nicht auf die Viewport-Breite. WICHTIG:
   @container-Bedingungen können keine CSS-Variablen auflösen (var()
   wird hier von keinem Browser unterstützt, anders als bei normalen
   Deklarationen) — deshalb fest verdrahteter Pixelwert.

   GEÄNDERT (2026-08-04): war 660 (= 3 * 220px, die reine Panelbreite).
   Seit der Container auf .lp-assets-scroll sitzt, misst die Bedingung
   dessen INNENbreite — der Container erbt padding:10px 12px von
   .lp-scroll, macht 24px links+rechts weniger als die Panelbreite.
   GEMESSEN (echte CSS, DOM-Kette .left-panel/.lp-assets-scroll/.lp-asset-list):
   der Umschlagpunkt lag bei "660" unveraendert erst bei 685px
   Panelbreite, nicht bei 660 — deshalb hier 636 (660 - 24), damit die
   dokumentierte Absicht (3 * --left-panel-w Panelbreite) unveraendert
   bleibt. Exakt dieselbe Falle wie bei [data-presets-body] oben, siehe
   dort ausführlicher. Ändert sich --left-panel-w (aktuell 220px, siehe
   :root) oder das Padding von .lp-scroll je, muss dieser Wert von Hand
   nachgezogen werden. */
@container assets-panel (min-width: 636px) {
  .lp-asset-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 12px;
  }
}

/* ── Papierkorb / Trash (NEU 2026-07) ────────────────────────
   View-Umschalter ganz oben, bewusst deutlich von der darunter
   liegenden Typ-Filter-Zeile abgesetzt (eigene Zeile, eigener
   Trennstrich), damit "Zustand der Ansicht" vs. "Typ-Filter"
   optisch klar getrennt bleibt. */
/* BUGFIX (2026-07): .lp-assets-active-view/.lp-assets-trash-view sind reine
   Wrapper-<div>s ohne eigenes CSS — als normale Block-Elemente unterbrachen
   sie die Flex-Kette zwischen .left-panel-inner (flex column) und der
   verschachtelten .lp-scroll-Liste. Ohne diese Kette hatte .lp-scrolls
   flex:1 keine begrenzte Elternhöhe, gegen die es sich ausrichten konnte —
   die Liste wuchs stattdessen unbegrenzt, .left-panel-inners overflow:hidden
   schnitt den Überschuss einfach ab, statt ihn scrollbar zu machen. */
.lp-assets-active-view,
.lp-assets-trash-view {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
/* NEU (2026-08-14, "sechs Optimierungen fuer schmale Panels", Punkt 3+4):
   eigener Container fuer die Kopfzeilen (Sortier-Zeile + Typ-Filter),
   getrennter Name als der Grid-Container oben (.lp-assets-scroll /
   "assets-panel") — beide sind ineinander verschachtelt (dieser hier
   ist der AEUSSERE), ein gemeinsamer Name wuerde @container-Anfragen
   in .lp-assets-scroll versehentlich auf DIESEN aeusseren Container
   umlenken. .lp-assets-active-view selbst traegt kein eigenes Padding
   (siehe GEMESSEN unten) — die inline-size hier entspricht daher direkt
   der Panel-Innenbreite, keine Umrechnung noetig. */
.lp-assets-active-view { container-type: inline-size; container-name: assets-header; }
/* Beide werden über das hidden-Attribut umgeschaltet (siehe app.js) — ohne
   diese Regel würde obiges "display: flex" das UA-Default für [hidden]
   überschreiben und die jeweils inaktive Ansicht bliebe sichtbar. */
.lp-assets-active-view[hidden],
.lp-assets-trash-view[hidden] {
  display: none;
}
/* NEU (2026-07): sitzt jetzt IM .lp-section-title (zwischen Titel und
   Close-Button), nicht mehr als eigene Zeile darunter — daher kein
   eigenes Padding/Trennstrich mehr, siehe HTML in populateAssetsPanel(). */
.lp-assets-viewtabs {
  display: flex;
  align-items: center;
  gap: 6px;
}
.lp-assets-viewtab {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 3px 4px 5px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe/SimplifyerX): Tabs zeigen an, wo
   man ist, sie lösen nichts aus — der Hover-Hintergrund behandelte sie
   wie Knöpfe. Kam nicht von hier, sondern von der globalen Regel
   ":where(.left-panel, .floating-panel) button:hover" weiter oben, die
   JEDEN Knopf in einem Panel auf Hover schwarz/weiß füllt — hier
   bewusst mit background:transparent überschrieben, die globale Regel
   bleibt für alle echten Knöpfe unangetastet. Die Textfarbe hellt
   weiterhin auf (unverändert), das genügt als Rückmeldung. */
.lp-assets-viewtab:hover { color: var(--ink); background: transparent; }
.lp-assets-viewtab.active { color: var(--ink-hi); border-bottom-color: var(--amber); }
/* NEU (2026-07): Trash-Icon im Tab ausgeblendet, Text "Trash" bleibt. */
.lp-assets-viewtab svg { display: none; }
/* NEU (2026-08-14, Kachelansicht): List/Grid-Umschalter — dieselbe Klasse
   wie BROWSE/TRASH oben (.lp-assets-viewtab), aber IKONE statt Text, die
   svg also wieder sichtbar machen (die Regel direkt darueber blendet sie
   sonst app-weit aus, war fuer den Trash-Tab gedacht). Eigene, engere
   Aussenabstaende — die Text-Tabs oben brauchen mehr Luft je Seite,
   ein blosses Icon-Quadrat wirkt bei denselben Werten schief. */
.lp-assets-viewmode-btn { padding: 3px 3px 5px; }
.lp-assets-viewmode-btn svg { display: block; width: 14px; height: 14px; }
/* GEAENDERT (2026-08-14, "Panel-Header-Icons verkleinern + Akzent-
   Unterstreichungen", Punkt 2, SimplifyerX-Entscheidung): Amber-
   Unterstreichung hier entfernt — welche Ansicht laeuft, sieht man an
   der Liste selbst, die Unterstreichung markierte etwas ohnehin
   Sichtbares doppelt. GEZIELT auf .lp-assets-viewmode-btn, NICHT auf
   die Basisklasse .lp-assets-viewtab.active: beide teilen sich dieselbe
   Klasse (siehe Kommentar oben, "dieselbe Klasse wie BROWSE/TRASH"),
   dort im Panel-Kopf bleibt die Unterstreichung bestehen — sie trennt
   dort zwei echte Orte (ALL/Papierkorb), keine Redundanz. Verbleibendes
   Signal: die Textfarbe (--ink-hi/--ink-faint, geerbt von
   .lp-assets-viewtab.active/:hover) — gemessen 2.41:1 (Dark) / 6.16:1
   (Light), fuer ein kleines, sofort mit der Liste abgeglichenes Element
   als ausreichend eingestuft. */
.lp-assets-viewmode-btn.active { border-bottom-color: transparent; }
/* GEAENDERT (2026-08-14, "sechs Optimierungen fuer schmale Panels",
   Punkt 5): TRASH-Tab jetzt ebenfalls Icon statt Text (Text-Knopf war
   der eigentliche Anlass fuer die blanket-hide-Regel zwei Kommentare
   weiter oben) — dieselbe Freischalt-/Verengungs-Technik wie beim
   List/Grid-Umschalter direkt darueber, eigene Klasse statt sie dort
   mit anzuhaengen (die badge-tragende Zaehler-Ecke braucht keine
   engeren Aussenabstaende, nur die svg-Sichtbarkeit). */
.lp-assets-viewtab-icon svg { display: block; width: 14px; height: 14px; }
/* Kopfbereich ein-/ausklappbar (Punkt 6) — Import-Zeile + Typ-Filter.
   GEFUNDEN (2026-08-14, Staging-Test): eine reine .lp-assets-header-
   hidden-Regel verlor gegen .lp-assets-import/.lp-assets-typefilter
   selbst — beides Einzelklassen-Selektoren mit derselben Spezifitaet
   (0,1,0), und deren eigenes "display: flex" steht WEITER UNTEN im
   Stylesheet (Abschnitt "Assets-Panel-Redesign"), gewinnt bei gleicher
   Spezifitaet also als spaetere Regel. Mit .lp-assets-active-view davor
   kombiniert (0,2,0) gewinnt die Regel unabhaengig von der Reihenfolge
   im Stylesheet — robuster als die Regel nur an die richtige Stelle zu
   verschieben. */
.lp-assets-active-view .lp-assets-header-hidden { display: none; }
.lp-assets-trash-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 15px; height: 15px;
  padding: 0 4px;
  border-radius: 8px;
  /* GEÄNDERT (2026-08-12): war Rot/Weiß — zeigt nur eine Anzahl, keine
     Warnung. Gleiches Muster wie .lp-asset-uses (Verwendungs-Zähler). */
  background: var(--line-soft);
  color: var(--ink-faint);
  font-family: var(--f-data);
  font-size: 9px;
  line-height: 1;
}
.lp-assets-trash-badge[hidden] { display: none; }
.lp-assets-trash-hint {
  font-family: var(--f-label);
  font-size: 10px;
  color: var(--ink-faint);
  line-height: 1.5;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.lp-trash-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
}
.lp-trash-row:last-child { border: none; }
.lp-trash-row:hover { background: var(--panel-2); }
.lp-trash-thumb { opacity: .7; }
/* NEU (2026-08-07, Papierkorb-Mehrfachauswahl): eigener innerer Wrapper
   fuer den asynchron nachgeladenen Thumbnail-Inhalt — der Thumbnail-
   Lader in populateTrashList() ersetzt dessen innerHTML komplett, sobald
   die Datei vom Server da ist. Die Checkbox (.lp-asset-select) sitzt
   bewusst DANEBEN, nicht darin, sonst würde sie beim Nachladen mit
   weggewischt. .lp-asset-thumb selbst zentriert per flex nicht auf
   100% Höhe/Breite (align-items:center statt stretch) — deshalb hier
   explizit gesetzt, damit Icon/Bild/Video optisch unverändert bleiben. */
.lp-trash-thumb-inner { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.lp-trash-countdown { color: var(--ink-dim); }
.lp-trash-countdown-soon { color: var(--red, #c0392b); }
.lp-trash-restore {
  align-self: center;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  background: none;
  border: 1px solid var(--line);
  color: var(--ink-dim);
  cursor: pointer;
  flex-shrink: 0;
  transition: color .12s, border-color .12s;
}
.lp-trash-restore svg { width: 13px; height: 13px; }
.lp-trash-restore:hover { color: var(--amber); border-color: var(--amber); }
.lp-trash-purge { align-self: center; }

/* ── Multi-file drop highlight on panel body ─────────────── */
.panel-multi-drop { outline: 2px dashed var(--amber); outline-offset: -4px; }

/* ── Asset panel video thumbnail ─────────────────────────── */
.lp-asset-thumb-video {
  position: relative;
  width: 100%; height: 100%;
  background: var(--line-soft);
  display: flex; align-items: center; justify-content: center;
}
.lp-asset-thumb-audio {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
}
.lp-asset-play-icon {
  position: absolute;
  font-size: 18px;
  color: rgba(255,255,255,.85);
  pointer-events: none;
  text-shadow: 0 1px 4px rgba(0,0,0,.5);
}

/* ENTFERNT (2026-08-13, Fund von check-buttons.php OFFEN/ep-btn-accent):
   .ep-section/.ep-btn* (Export/Backup panel) rendert nirgends mehr —
   grep über app.js/panels.js/world-sync.js/index.html liefert keine
   einzige Fundstelle. Abgelöst durch .ep-btn2 (siehe dort), die den
   Export-Panel-Markup heute traegt. Totes CSS, kein Umbau noetig. */

/* ── Text to BOARD inline panel ─────────────────────────── */
.lp-tb-scroll { display: flex; flex-direction: column; flex: 1; min-height: 0; padding-bottom: 12px; }
/* .lp-tb-hint wird weiterhin für Leerzustände in anderen Panels
   genutzt (Tools, Group Presets) — hier nur der Text-to-BOARD-eigene
   Beispiel-Block ersetzt, siehe .lp-tb-preview-* oben. */
.lp-tb-hint { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); line-height: 1.5; margin-bottom: 10px; }

/* NEU (2026-07): Live-Vorschau der Board-Struktur, ersetzt den
   vorherigen statischen Hinweistext/Beispiel-Block. Group = helle,
   abgerundete Fläche (dieselbe Optik wie echte Groups im Light-Theme,
   --bg-deep), Card = weiße, eckige Fläche mit Titel + Icon + Typ-Name
   — beides zusammen, damit sich später eins von beiden per CSS
   gezielt ausblenden lässt (siehe .lp-tb-preview-card-icon /
   .lp-tb-preview-card-type). */
.lp-tb-preview {
  margin-bottom: 12px;
  overflow-x: auto;
  padding-bottom: 4px;
}
.lp-tb-preview-empty, .lp-tb-preview-error {
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink-faint);
  padding: 10px 0;
}
.lp-tb-preview-error { color: var(--red, #d04040); }
.lp-tb-preview-row { margin-bottom: 10px; }
.lp-tb-preview-row-label {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 6px;
}
.lp-tb-preview-groups-row { display: flex; gap: 8px; flex-wrap: wrap; }
.lp-tb-preview-group {
  background: var(--bg-deep);
  border-radius: 10px;
  padding: 8px;
  flex-shrink: 0;
}
.lp-tb-preview-group-title {
  font-family: var(--f-display);
  font-size: 10px;
  color: var(--ink-dim);
  margin-bottom: 6px;
  padding: 0 2px;
}
.lp-tb-preview-cols-row { display: flex; gap: 6px; }
.lp-tb-preview-col { display: flex; flex-direction: column; gap: 4px; min-width: 70px; }
.lp-tb-preview-col-title {
  font-family: var(--f-data);
  font-size: 8px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  padding: 0 2px;
}
.lp-tb-preview-cards { display: flex; flex-direction: column; gap: 4px; }
.lp-tb-preview-card {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  padding: 5px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.lp-tb-preview-card-empty { color: var(--ink-faint); font-size: 8px; font-family: var(--f-data); }
.lp-tb-preview-card-icon {
  position: absolute;
  top: 4px; right: 4px;
  display: block;
  width: 12px; height: 12px;
  color: var(--ink-faint);
}
.lp-tb-preview-card-icon svg { width: 100%; height: 100%; }
.lp-tb-preview-card-title {
  font-family: var(--f-display);
  font-size: 10px;
  color: var(--ink);
  white-space: nowrap;
  padding-right: 16px;
}
.lp-tb-preview-card-type {
  display: none;
  font-family: var(--f-data);
  font-size: 8px;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.lp-tb-actions { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
/* GEAENDERT (2026-08-13): hatte ebenfalls einen Amber-Hover (siehe
   .md-import-upload-btn). Jetzt .btn .btn-sm im Markup. */
.lp-tb-or { font-family: var(--f-data); font-size: 9px; color: var(--ink-faint); }
.lp-tb-editor-wrap {
  flex: 1;
  min-height: 180px;
  position: relative;
  border: 1px solid var(--line);
  background: var(--bg-deep);
  overflow: hidden;
  margin-bottom: 8px;
}
.lp-tb-highlight, .lp-tb-input {
  position: absolute; inset: 0;
  margin: 0;
  padding: 8px 10px;
  font-family: var(--f-data);
  font-size: 11px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: auto;
  box-sizing: border-box;
}
.lp-tb-highlight { color: var(--ink); pointer-events: none; background: var(--bg-deep); }
.lp-tb-highlight code { font: inherit; white-space: inherit; display: block; }
.lp-tb-input { background: transparent; border: none; outline: none; color: transparent; caret-color: var(--ink); resize: none; }
.lp-tb-footer { display: flex; align-items: center; gap: 8px; }
.lp-tb-status { flex: 1; font-family: var(--f-data); font-size: 9px; color: var(--ink-faint); }
.lp-tb-status-error { color: var(--red, #d04040); }
/* NEU (2026-07): eigenes Bestätigungs-Popup statt window.confirm() —
   zentriertes Overlay im Tool-Design. */
.confirm-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-confirm-dialog);   /* war 300 — bewusst unconditional zuoberst, auch über der Free-Board-Gesten-Ebene (9996–9999), siehe Layer-Ordnung in :root */
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.55);
}
.confirm-dialog-overlay.open { display: flex; }
.confirm-dialog-box {
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--panel-shadow);
  padding: 24px;
  width: 380px;
  max-width: calc(100vw - 40px);
}
.confirm-dialog-message {
  font-family: var(--f-display);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: 20px;
  white-space: normal;
}
.confirm-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): alle
   drei Knoepfe des generischen Bestaetigungs-Dialogs (confirmDialog()/
   confirmDialog3()) trugen eigene Erscheinung, .confirm-dialog-ok sogar
   Akzentfarbe bei Ruhe UND Hover — genau die Hausregel-Verletzung, die
   diese Runde beseitigt. Jetzt alle drei .btn/.btn-sm, wie schon bei
   den strukturell identischen Bestaetigen/Abbrechen-Paaren aus MD-Import/
   Referenz-Waehler/Crop (Etappe 4, siehe Kommentar "Rolle: Hauptaktion
   (Dialoge)" weiter oben) — Amber als Auszeichnung des OK-Knopfes faellt
   aus demselben Grund weg wie dort: kein Akzent-Rahmen um einen
   Aktionsknopf, keine dritte Erscheinungsform per Sonderregel fuer den
   "Alt"-Knopf. Einzige eigene Regel, die bleibt: [hidden] gewinnt ueber
   display, sonst zeigte der Flex-Container ihn auch ausgeblendet. */
.confirm-dialog-alt[hidden] { display: none; }

/* ── Restore-Fortschritt ────────────────────────────────────
   NEU (2026-08-14): eigenes Overlay fuer die Server-Uebertragung nach
   einem Restore (showRestoreProgress(), app.js). Erbt Lage, Abdunklung
   und Kasten von .confirm-dialog-overlay/-box — hier stehen nur die
   Unterschiede: kein Knopf (der Vorgang ist nicht abbrechbar, ein
   halb uebertragener Stand waere schlechter als ein ganzer) und eine
   Fortschrittsleiste. */
.restore-progress-overlay .confirm-dialog-message { margin-bottom: 0; }
.restore-progress-title {
  font-family: var(--f-display);
  font-size: 14px;
  color: var(--ink);
}
.restore-progress-detail {
  font-size: 12px;
  color: var(--ink-dim);
  margin-top: 6px;
  min-height: 16px;   /* haelt die Hoehe, wenn zwischen zwei Boards kein Name ansteht — sonst zuckt der Kasten bei jedem Schritt */
  overflow-wrap: anywhere;
}
.restore-progress-track {
  margin-top: 14px;
  height: 4px;
  background: var(--line);
}
.restore-progress-bar {
  height: 100%;
  width: 0;
  /* --amber ist der Akzent-Token dieses Projekts. Erste Fassung schrieb
     hier var(--accent) — den Namen gibt es nicht, die Leiste war
     transparent und der Fortschritt damit unsichtbar, obwohl die Breite
     korrekt mitzaehlte (auf Staging gemessen: 79% Breite, Farbe
     rgba(0,0,0,0)). */
  background: var(--amber);
  transition: width .2s linear;
}
@media (prefers-reduced-motion: reduce) {
  .restore-progress-bar { transition: none; }
}

/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): trug
   Akzentfarbe im Ruhezustand UND Hover (schwerste Auspraegung der
   Hausregel-Verletzung dieser Runde), jetzt .btn/.btn-sm (Dashboard-
   Toolbar-Kontext). */

/* ── Right panel upload thumbnail ───────────────────────── */
/* NEU (2026-07): Thumbnail + Name-Block + Datei-Info als eine visuell
   gruppierte "ASSET"-Sektion mit eigenem Panel-Hintergrund, statt lose
   nacheinander im Right-Panel-Body zu stehen. */
.rp-asset-group {
  background: var(--panel);
  padding: 16px 12px 12px;
  margin: 0px;
  box-shadow: var(--panel-shadow);
}
/* NEU (2026-07): AI/Real, Name/Label, Source/Tool, AI Model, Prompt,
   (FPS,) Seed/Ref und Notes als eine "Asset Detail"-Gruppe — gleiche
   Optik wie .rp-asset-group, direkt darunter im Right-Panel-Body. */
/* GEÄNDERT (2026-08-19, "acht Korrekturen", Punkt 2): war 12px, dann
   fälschlich auf 4px gesetzt in der Annahme, angrenzende Margins würden
   mit .rp-lineage-section' unterem Rand (ebenfalls 4px) kollabieren —
   LIVE GEMESSEN ergab das aber 8px, nicht 4px: .right-panel-body ist
   ein Flex-Container (display:flex; flex-direction:column;), und
   Flex-Kinder kollabieren ihre Margins NIE, sie addieren sich einfach
   (CSS-Spezifikation, keine Ausnahme für flex-direction:column). Auf 0
   korrigiert — die 4px kommen jetzt ausschließlich vom unteren Rand von
   .rp-lineage-section, macht alle drei Übergänge einheitlich 4px. */
.rp-detail-group {
  background: var(--panel);
  padding: 12px;
  margin: 0;
  box-shadow: var(--panel-shadow);
}
/* NEU (2026-07, Send ASSET): eigene Zeile zwischen Asset- und Detail-Gruppe
   fuer die vier Transport-Icons (Drag, Copy Link, Open in tab, Download).
   GEAENDERT (2026-08-03): links steht jetzt zusaetzlich das Sprung-Icon
   in den ASSETS MANAGER. Beide Seiten BEWUSST OHNE Beschriftung — die
   Icons erklaeren sich selbst bzw. ueber ihr title-Attribut. Die frueher
   hier stehende Textzeile "SEND ASSET" ist damit ersatzlos entfallen;
   justify-content: space-between setzt die beiden Seiten an die Raender,
   ohne dass eine Gruppe dazwischen noetig waere. */
/* GEAENDERT (2026-08-15, "vier Icons, Rest ins Kebab-Menue"): Flaeche +
   Schatten entfernt (Nutzer-Wunsch) — mit nur noch fuenf Elementen
   (vier Icons + Kebab) trug die eigene Panel-Flaeche kaum noch etwas
   bei, siehe Bericht fuer die visuelle Einschaetzung. */
.rp-send-asset {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 12px;
  margin: 0 0 2px;
}
/* NEU (2026-08-03): die beiden "wo ist es?"-Knoepfe links — ASSETS
   MANAGER und Fundstelle im BOARD. Bewusst als eigene Gruppe und nicht
   einzeln: sie beantworten dieselbe Frage an zwei Orten und gehoeren
   damit optisch zusammen, waehrend rechts die Transport-Icons stehen. */
.rp-send-asset-find {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.rp-send-asset-icons {
  display: flex;
  align-items: center;
  gap: 2px;
}
.rp-thumb-wrap {
  width: 80%;
  margin: 0 auto 12px;
}
/* NEU (2026-08, Asset Replace — Drag&Drop): gleiche Optik wie die
   bestehenden Board-Upload-Zonen (.media[data-upload-key].drag-over). */
.rp-thumb-wrap.drag-over .rp-thumb { outline: 1px dashed var(--amber); outline-offset: -1px; }
/* NEU (2026-07): Download-Button liegt jetzt oberhalb des Bildes,
   rechtsbündig am Rand — vorher als Overlay ÜBER dem Thumbnail
   platziert. Farbe jetzt themebewusst (--ink-dim) statt fest weiß,
   da er nicht mehr auf dem Bild selbst sitzt und weiß im Light-Theme
   auf dem hellen Panel-Hintergrund kaum sichtbar war. */
.rp-thumb {
  aspect-ratio: 16 / 9;
  background: var(--panel-2);
  overflow: hidden;
  position: relative;
  flex-shrink: 0;
}
/* NEU (2026-07): Download-Icon zog in den Panel-Header um (siehe
   .rp-head-download unten) — die alte In-Thumbnail-Variante entfällt. */
.rp-thumb img, .rp-thumb video {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
  background: var(--bg-deep);
}
.rp-thumb img { cursor: zoom-in; }
.rp-thumb audio {
  position: absolute;
  bottom: 8px; left: 8px; right: 8px;
  width: calc(100% - 16px);
}
.rp-thumb-empty {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-faint);
  font-family: var(--f-data);
  font-size: 10px;
}

/* ── Right panel prev/next nav ───────────────────────────── */
/* NEU (2026-07): Dateiname + Vor/Zurück-Zähler in einer gemeinsamen
   Zeile direkt unter dem Medium. */
/* GEÄNDERT (2026-08-19, "Detail-Panel neu ordnen"): .rp-nav als eigene
   Body-Zeile entfällt — die Navigationsknöpfe sitzen jetzt im Kopfzeilen-
   Slot .rp-head-nav (siehe index.html). Die Regel bleibt bewusst
   bestehen: sie trägt keinen Schaden, und .rp-nav-btn/.rp-nav-pos
   darunter sind unverändert in Gebrauch. */
/* GEÄNDERT (2026-08-19, "acht Korrekturen", Punkt 1): war ein normales
   Flex-Kind zwischen .right-panel-level und .rp-head-actions — dadurch
   nicht mittig in der Kopfzeile, sondern mittig in der LÜCKE zwischen
   den beiden (die selbst unterschiedlich breit sind: "DETAILS" ~55px,
   die Aktionen rechts ~32px). Absolut positioniert und über left:50%/
   transform zentriert, unabhängig von der Breite der Geschwister —
   .right-panel-head trägt dafür jetzt position:relative. Bei 280px
   Panelbreite (168px frei laut Messung vom selben Tag) bleibt beidseitig
   Luft: Nav-Breite max. ~98px (dreistelliger Zähler), zentriert auf
   139,5px Headermitte reicht von ~90 bis ~189px, "DETAILS" endet bei
   ~55px, die Aktionen beginnen bei ~247px — keine Überschneidung. */
.rp-head-nav {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.rp-head-nav:empty { display: none; }
/* Kompakter als im alten Body-Platz: die Kopfzeile ist 52px hoch und
   trägt daneben "DETAILS" und das Schliessen-Kreuz. Gemessen bei
   Panel-Minimalbreite 280px: 168px frei, Bedarf hier 74px. */
.rp-head-nav .rp-nav-btn { width: 26px; height: 26px; font-size: 13px; }
.rp-head-nav .rp-nav-pos { font-size: 12px; min-width: 34px; text-align: center; }

.rp-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0px 0px 2px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 1px;
}
.rp-nav-btn {
  background: none;
  border: 1px solid var(--line);
  color: var(--ink-dim);
  width: 28px; height: 28px;
  display: grid; place-items: center;
  cursor: pointer;
  font-size: 14px;
  transition: color .12s, border-color .12s;
}
.rp-nav-btn:disabled { opacity: .25; cursor: default; }
.rp-nav-btn:not(:disabled):hover { color: var(--amber); border-color: var(--amber); }
.rp-nav-pos {
  font-family: var(--f-display);
  font-size: 14px;
  color: var(--ink-faint);
}

/* ── Bottom controls: Undo/Redo + Zoom HUD + Minimap toggle, grouped
   together as one borderless/backgroundless/shadowless menu row. Each
   sub-group (board-undo-redo, zoom-hud, minimap-toggle) keeps its own
   look — only the shared wrapper itself has no chrome. ── */
.board-bottom-controls {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%) translateY(0);
  display: flex;
  align-items: center;
  gap: 0px;
  background: none;
  border: none;
  /* box-shadow sitzt jetzt nur einmal auf der gesamten Gruppe, nicht
     mehr zusätzlich auf jeder einzelnen Untergruppe (siehe unten). */
  box-shadow: 0 2px 12px rgba(0,0,0,.18);
  z-index: 15;
  /* Slide+Fade, gleiche Technik wie .lp-assets-bulk-toolbar weiter unten
     in dieser Datei: KEIN display:none mehr (das würde die Transition
     abschneiden, siehe frühere BUGFIX-Notiz hier), stattdessen bleibt
     das Element immer im Layout und wird nur per Klasse verschoben/
     ausgeblendet. pointer-events:none im ausgeblendeten Zustand
     verhindert Klicks auf das unsichtbare Element. */
  opacity: 1;
  pointer-events: auto;
  transition: transform .2s ease, opacity .2s ease;
}
.board-bottom-controls.board-bottom-controls-hidden {
  transform: translateX(-50%) translateY(16px);
  opacity: 0;
  pointer-events: none;
}
/* Undo/Redo styled exactly like the Zoom HUD's own buttons — same
   bordered/background group, same button look and hover state (der
   Schatten liegt jetzt nur noch auf .board-bottom-controls). */
.board-undo-redo {
  display: flex;
  align-items: center;
  gap: 0;
  background: var(--panel);
  border: 1px solid var(--line-soft);
}
.board-undo-redo .icon-btn {
  width: 32px; height: 28px;
  display: grid; place-items: center;
  background: none;
  border: none;
  color: var(--ink-dim);
  font-size: 16px;
  cursor: pointer;
  transition: color .12s, background .12s;
  line-height: 1;
}
.board-undo-redo .icon-btn:hover { color: var(--amber); background: var(--panel-2); }
.board-undo-redo .icon-btn:first-child { border-right: 1px solid var(--line-soft); }

/* ── Zoom HUD ───────────────────────────────────────────── */
.zoom-hud {
  display: flex;
  align-items: center;
  gap: 0;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  user-select: none;
}
.zoom-hud-btn {
  width: 32px; height: 28px;
  display: grid; place-items: center;
  background: none;
  border: none;
  color: var(--ink-dim);
  font-size: 16px;
  cursor: pointer;
  transition: color .12s, background .12s;
  line-height: 1;
}
.zoom-hud-btn:hover { color: var(--amber); background: var(--panel-2); }
.zoom-hud-pct {
  min-width: 52px;
  height: 28px;
  display: grid; place-items: center;
  background: none;
  border: none;
  border-left: 1px solid var(--line-soft);
  border-right: 1px solid var(--line-soft);
  color: var(--ink);
  font-family: var(--f-data);
  font-size: 10px;
  cursor: pointer;
  transition: color .12s;
}
.zoom-hud-pct:hover { color: var(--amber); }

/* ── Board-Lade-Anzeige (NEU 2026-07) ────────────────────────
   Ersetzt die frühere "X / Y Assets geladen"-Anzeige durch eine
   einfache "LOADING …"-Badge, in der Topnav links neben den
   Undo/Redo/Sync-Icons (Teil von .topnav-tools, siehe index.html).
   Theme-invertiert wie Save/Share: Light-Theme → dunkler Chip mit
   hellem Text, Dark-Theme → heller Chip mit dunklem Text — bewusst
   literal Schwarz/Weiß statt der üblichen --bg/--ink-Variablen, damit
   die Anzeige unabhängig vom sonstigen Theme-Feintuning immer denselben
   starken Kontrast hat. Langsames Pulsieren signalisiert laufende
   Aktivität, ohne unruhig zu wirken. */
.board-loading-hud {
  font-family: var(--f-label);
  font-size: 9px;
  letter-spacing: .04em;
  padding: 2px 6px;
  margin-right: 4px;
  pointer-events: none;
  user-select: none;
  animation: board-loading-pulse 1.8s ease-in-out infinite;
  /* Dark ist das Default-Theme in dieser App (kein data-theme-Attribut
     gesetzt, siehe applyTheme() in app.js — nur Light setzt explizit
     data-theme="light"). Darum hier als Basis die Dark-Theme-Farben,
     Light überschreibt gezielt über das Attribut-Selektor. */
  background: #fff;
  color: #000;
}
.board-loading-hud[hidden] { display: none; }
:root[data-theme="light"] .board-loading-hud { background: #000; color: #fff; }

/* NEU: kurzzeitiger "New"-Badge neben dem Ungrouped-Zähler (Dashboard),
   zeigt für 2s pulsierend an, dass gerade ein neues Board dort gelandet
   ist — gleiches Theme-invertierte Schwarz/Weiß-Muster + Puls-Animation
   wie .board-loading-hud. */
.ungrouped-new-badge {
  font-family: var(--f-label);
  font-size: 9px;
  letter-spacing: .04em;
  padding: 2px 6px;
  pointer-events: none;
  user-select: none;
  animation: board-loading-pulse 1.8s ease-in-out infinite;
  background: #fff;
  color: #000;
}
:root[data-theme="light"] .ungrouped-new-badge { background: #000; color: #fff; }
@keyframes board-loading-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .45; }
}

/* ── Pulsierender Platzhalter pro Bild/Video-Feld (NEU 2026-07) ──
   Sitzt als Geschwister-Element direkt neben dem img/video (siehe
   mediaLoadWrap() in app.js) innerhalb der jeweiligen .media-Figure,
   die bereits als Positionierungs-Kontext für Overlays dient. */
.media-ph {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel-2);
  animation: media-ph-pulse 1.6s ease-in-out infinite;
  pointer-events: none;
}
.media-ph-error {
  display: none;
  color: var(--red, #d04040);
}
.media-ph-error svg { width: 28px; height: 28px; }
@keyframes media-ph-pulse {
  0%, 100% { opacity: .55; }
  50% { opacity: .9; }
}
/* fertig geladen: Platzhalter komplett ausblenden */
img[data-media-load="done"] + .media-ph,
video[data-media-load="done"] + .media-ph { display: none; }
/* Fehler: Puls stoppen, statisches Fehler-Icon zeigen */
img[data-media-load="error"] + .media-ph,
video[data-media-load="error"] + .media-ph { animation: none; opacity: 1; }
img[data-media-load="error"] + .media-ph .media-ph-error,
video[data-media-load="error"] + .media-ph .media-ph-error { display: flex; }

/* NEU (2026-07): Board-Minimap — schwebt unten links, Abstand zum
   Rand und zur Left Rail, mit Toggle-Icon zum Ein-/Ausblenden. Zeigt
   das gesamte Board stark vereinfacht (nur Group-Blöcke, keine echten
   Card-Inhalte — das wäre zu aufwendig zu rendern), mit einem
   ziehbaren Rahmen für den aktuell sichtbaren Board-Ausschnitt. */
.board-minimap {
  /* GEAENDERT (2026-08-28): war right:0 -- richtete die Minimap an der
     rechten Kante von .board-bottom-controls aus (der Kante des
     Minimap-Toggle-Buttons als letztem Kind), NICHT an dessen Mitte.
     .board-bottom-controls selbst ist ueber left:50%/translateX(-50%)
     auf dem Viewport zentriert (siehe dort) -- schrumpft aber per
     Flex auf seinen Inhalt, seine eigene Mitte liegt also nicht bei
     right:0. GEMESSEN (SimplifyerX-Bildschirmfoto): dadurch sass die
     Minimap sichtbar nach rechts versetzt ueber der Werkzeugleiste,
     nicht mittig. Fix: dieselbe Zentrierungstechnik wie der
     Elternrahmen selbst -- left:50%+translateX(-50%), jetzt relativ
     zu .board-bottom-controls statt zum Viewport. Dadurch bleibt die
     Minimap bei jeder Fensterbreite exakt so zentriert wie die
     Werkzeugleiste selbst, weil beide denselben Mechanismus nutzen. */
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(100% + 10px);
  /* GEAENDERT (2026-09-02, Minimap wieder feste Form): war zwischenzeitlich
     an die Boardform angepasst (bis 320x220, siehe Git-Historie) -- ein
     hochformatiges Board machte dabei auch die Kachel hochformatig, was
     SimplifyerX in der Bedienung als irritierend zurückmeldete: die
     Minimap zeigt den Ausschnitt eines Fensters im Bildschirmformat, nicht
     die Form der Inhalte, und soll an ihrem Platz gleich bleiben. Jetzt
     fest 336x189 (16:9, siehe MINIMAP_MAX_W/_H in app.js) -- reine CSS-
     Größe, renderMinimap() setzt hier keinen Inline-Style mehr.
     GEAENDERT (2026-09-03, GEMESSEN SimplifyerX): war 480x270, etwa 30%
     kleiner auf Wunsch. ⚠️ ZWEI STELLEN, EINE GROESSE: diese Zeile UND
     MINIMAP_MAX_W/_H (app.js) muessen beim naechsten Aendern GEMEINSAM
     angepasst werden -- renderMinimap() rechnet mit der JS-Konstante,
     der sichtbare Kasten hier mit dieser CSS-Regel, keine Seite liest
     die andere. Beim Bauen dieser Runde selbst uebersehen und erst beim
     Bildschirmfoto bemerkt (Kasten blieb 480x270 gross, obwohl die
     JS-Konstante schon 336x189 war) -- deshalb dieser Warnhinweis. */
  width: 336px;
  height: 189px;
  /* gleicher Hintergrund wie das Board selbst (.workspace) — garantierte
     Basis-Ebene (--board-bg-base) + die vom Nutzer gewählte Tint-Farbe
     darüber (--board-bg-tint, siehe applyBoardBackgroundTint()). */
  background-color: var(--board-bg-base);
  background-image: linear-gradient(var(--board-bg-tint), var(--board-bg-tint));
  border: 1px solid rgba(255,255,255,.4);
  box-shadow: 0 2px 12px rgba(0,0,0,.18);
  overflow: hidden;
  transition: opacity .15s, transform .15s;
}
.board-minimap[hidden] { display: none; }
.minimap-canvas {
  position: relative;
  width: 100%;
  height: 100%;
}
/* GEAENDERT (2026-08-28, Minimap folgt dem Board-Theme): war fest
   var(--line) -- unsichtbar bzw. kontrastarm gegen einen individuellen
   Board-Farbton (die Minimap-Flaeche folgt --board-bg-tint bereits,
   siehe .board-minimap oben, nur die Bloecke nicht). --minimap-block
   wird in applyBoardBackgroundTint() GEMESSEN gesetzt (echte
   WCAG-Kontrastrechnung gegen die tatsaechlich komponierte Flaeche,
   nicht nur ein Schwellwert), fallback var(--line) ohne gesetzten
   Farbton -- siehe Kommentar dort. */
/* GEAENDERT (2026-08-28): war 4px -- bei den kleinen Massstaeben der
   Minimap (Bloecke oft nur 8-25px) wirkte das wie eine Kachel/Pille,
   nicht wie eine Board-Flaeche. 1,5px zeigt weiterhin eine bewusste,
   dezente Rundung (kein hartes rechteckiges Kachelraster), bleibt aber
   erkennbar ein Block. */
.minimap-group {
  position: absolute;
  background: var(--minimap-block, var(--line));
  border-radius: 1.5px;
}
/* GEAENDERT (2026-08-28): Entscheidung SimplifyerX, gepruefte
   Zustimmung -- EIN Ton fuer alle Bloecke, unabhaengig vom SET-Stil
   (border/background/none). Die Minimap zeigt WO etwas ist, nicht WAS
   es ist; drei nachgezeichnete Stile wuerden das nur unruhiger machen,
   ohne beim Orientieren zu helfen -- und jeder der drei muesste separat
   gegen den Farbton geprueft werden, ohne zusaetzlichen Nutzen. Beide
   Klassen jetzt auf denselben gefuellten Block wie .minimap-group
   selbst, keine eigene Erscheinung mehr. */
.minimap-group-border,
.minimap-group-none {
  background: var(--minimap-block, var(--line));
  border: 0;
}
/* GEAENDERT (2026-08-28, Runde 3): Runde 2 legte eine feste schwarze
   Kontur (box-shadow, aussen+innen) hinter die Amber-Linie -- SimplifyerX-
   Rueckmeldung: sieht mit drei Linien (Kontur aussen, Amber, Kontur
   innen) nicht gut aus. Zurueck auf EINE Linie, jetzt 2px statt 1,5px.
   Der Befund von Runde 2 bleibt unveraendert wahr: Amber traegt gegen
   die (seit der Theme-Richtungs-Korrektur durchgehend aufgehellten)
   Dunkel-Theme-Bloecke nur 1,7-2,2:1, schlechtester Fall 1,72:1
   (sehr dunkler Farbton, Dunkel-Theme) -- eine dickere Linie aendert
   daran nichts, Kontrast ist eine Frage der FARBE, nicht der Breite.
   Bewusst so belassen: der Rahmen ist eine geschlossene Form ueber
   mehreren Bloecken hinweg (die vier Kanten treffen sich, es entsteht
   ein Rechteck) -- eine Form bleibt bei schwachem Kontrast erkennbar,
   anders als ein einzelnes Symbol oder Text (siehe Bericht dieser
   Runde, Bildschirmfoto des schlechtesten Falls). */
.minimap-viewport {
  position: absolute;
  border: 2px solid var(--amber);
  background: rgba(200, 160, 60, .12);
  cursor: grab;
  box-sizing: border-box;
}
.minimap-viewport:active { cursor: grabbing; }
/* Styled exactly like a Zoom HUD button — same bordered/background
   group, same size and hover state (der Schatten liegt jetzt nur noch
   auf .board-bottom-controls). */
.minimap-toggle {
  width: 32px; height: 28px;
  display: grid; place-items: center;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  color: var(--ink-dim);
  cursor: pointer;
  transition: color .12s, background .12s;
  flex-shrink: 0;
}
.minimap-toggle:hover { color: var(--amber); background: var(--panel-2); }
.minimap-toggle svg { width: 14px; height: 14px; }

/* Toolbar ein-/ausblenden (N-Taste oder Klick) — gleiche Maße/Optik wie
   .minimap-toggle, letztes Element der Gruppe ganz rechts. */
.toolbar-hide-toggle {
  width: 32px; height: 28px;
  display: grid; place-items: center;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  color: var(--ink-dim);
  font-size: 13px;
  cursor: pointer;
  transition: color .12s, background .12s;
  flex-shrink: 0;
}
.toolbar-hide-toggle:hover { color: var(--amber); background: var(--panel-2); }

/* ── Board overlay when right detail panel is open ──────── */
.board-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0);
  backdrop-filter: blur(0px);
  -webkit-backdrop-filter: blur(0px);
  pointer-events: none;
  z-index: 19;   /* below right panel (z-index 20) but above board */
  transition: background .25s ease, backdrop-filter .25s ease;
}
.right-panel.open ~ .board-overlay,
.board-overlay.active {
  background: rgba(0,0,0,.18);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

/* ── Asset panel: custom name above original filename ────── */
.lp-asset-custom-name {
  font-family: var(--f-display);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 1px;
}
.lp-asset-custom-name + .lp-asset-name {
  font-size: 9px;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* NEU (2026-07, Redesign): die zweite, graue Original-Name-Zeile pro Asset
     wird visuell ausgeblendet (nicht aus dem Markup entfernt) — der
     Original-Name bleibt im Detail-Panel rechts einsehbar. Reduziert die
     visuelle Redundanz in der Liste. */
  display: none;
}

.rp-bc-row { display: flex; align-items: center; gap: 5px; }
.rp-bc-sep { color: var(--line); font-size: 14px; }
.rp-bc-original { font-family: var(--f-data); font-size: 10px; font-weight: 400; color: var(--ink-faint); margin-top: 1px; }
.rp-bc-col { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); letter-spacing: .06em; text-transform: uppercase; }

/* ── Roadmap card tiles: dimmed ─────────────────────────── */
.panel-type-soon { opacity: 0.3; }
.panel-type-soon .panel-type-name { color: var(--ink-faint); }

/* ── Delete upload button in right panel ─────────────────── */
.rp-delete-upload {
  width: 100%;
  padding: 8px;
  border: 0;
  border-top: 1px solid var(--line-soft);
  background: transparent;
  color: var(--ink-faint);
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .15s, background .15s;
  margin-top: 4px;
}
.rp-delete-upload:hover {
  color: var(--red, #d04040);
  background: rgba(208,64,64,.06);
}

/* ── Delete button in asset list ─────────────────────────── */
.lp-asset-delete {
  background: none;
  border: none;
  color: var(--ink-faint);
  font-size: 11px;
  cursor: pointer;
  padding: 4px 6px;
  flex-shrink: 0;
  opacity: 0;
  transition: opacity .12s, color .12s;
}
.lp-asset-row:hover .lp-asset-delete { opacity: 1; }
/* BUGFIX (2026-08-07): fehlte hier — .lp-trash-row (Papierkorb) ist eine
   andere Zeilenklasse als .lp-asset-row, die Hover-Regel oben griff dort
   nie. Der Knopf war dadurch funktionsfaehig, aber dauerhaft unsichtbar
   (opacity:0 ohne Aufdeck-Trigger) und sass 10px neben dem Restore-Knopf —
   ein Fehlklick landete unbemerkt auf "endgueltig loeschen" statt
   "wiederherstellen" (durch den Bestaetigungsdialog dort entschaerft,
   aber unnoetig). */
.lp-trash-row:hover .lp-asset-delete { opacity: 1; }
.lp-asset-delete:hover { color: var(--red, #d04040); }
/* NEU: Duplicate/Delete-Icon-Buttons in der Assets-Manager-Liste ausblenden
   (nur dort — der optisch gleiche "lp-asset-delete"-Button im Papierkorb,
   .lp-trash-purge, ist bewusst NICHT betroffen). */
.lp-asset-row .lp-asset-delete { display: none; }

/* ── Duplicate button in asset list (hover-reveal, left of ✕) ─ */
.lp-asset-duplicate {
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 4px 4px;
  flex-shrink: 0;
  opacity: 0;
  transition: opacity .12s, color .12s;
}
/* NEU: siehe .lp-asset-row .lp-asset-delete oben */
.lp-asset-row .lp-asset-duplicate { display: none; }
.lp-asset-duplicate svg { width: 12px; height: 12px; display: block; }
.lp-asset-row:hover .lp-asset-duplicate { opacity: 1; }
.lp-asset-duplicate:hover { color: var(--amber); }

/* ── Delete All button in assets toolbar ─────────────────── */
.lp-asset-delete-all {
  margin-left: auto;
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border: 1px solid var(--line-soft);
  padding: 3px 7px;
  background: transparent;
  cursor: pointer;
  transition: color .12s, border-color .12s;
  flex-shrink: 0;
}
.lp-asset-delete-all:hover {
  color: var(--red, #d04040);
  border-color: var(--red, #d04040);
}

/* ── Mehrfachauswahl-Checkbox auf dem Asset-Thumbnail ────────
   Bewusst kein natives <input type="checkbox">, sondern ein simpler
   "styled" Button — passend zur restlichen minimalen Amber/Line-Optik
   der Assets Manager UI. */
.lp-asset-select {
  position: absolute;
  top: 6px; right: 6px;
  width: 15px; height: 15px;
  /* GEÄNDERT (2026-08-13, Nutzer-Vorgabe: "Kontrast ist nicht erkennbar"):
     war 1px var(--line) — gegen die eigene rgba(--overlay-rgb,.85)-Fläche
     nur 2.25:1 (Dark) bzw. 1.30:1 (Light), praktisch unsichtbar. --line
     ist als Panel-Trennlinie gedacht, nicht als Bedienelement-Rahmen.
     --ink-dim gibt 7.25:1 (Dark) / 5.10:1 (Light) — deutlich als
     klickbares Kaestchen erkennbar, auch ungeprueft. */
  border: 1px solid var(--ink-dim);
  border-radius: 3px;
  background: rgba(var(--overlay-rgb), .85);
  cursor: pointer;
  z-index: 2;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  transition: border-color .12s, background-color .12s, opacity .12s;
}
/* NEU (2026-08-14, "Checkboxen erst bei Bedarf"): standardmaessig
   unsichtbar, erscheint einzeln nur beim Hover der eigenen Zeile/
   Kachel — sobald IRGENDEINE Auswahl besteht, bleiben alle sichtbar
   (Klasse .lp-asset-list-has-selection, siehe populateAssetsPanel()
   in app.js, dort schon der zentrale Ort: JEDER Weg, der die Auswahl
   aendert, ruft am Ende populateAssetsPanel() auf). Bewusst auf
   .lp-asset-list beschraenkt (Browse-Liste/-Raster) — .lp-trash-list
   nutzt dieselbe .lp-asset-select-Klasse fuer ihre eigenen Checkboxen,
   soll aber unveraendert immer sichtbar bleiben, das war nicht Teil
   dieses Auftrags. */
.lp-asset-list .lp-asset-select { opacity: 0; }
.lp-asset-list .lp-asset-row:hover .lp-asset-select,
.lp-asset-list .lp-asset-tile:hover .lp-asset-select,
.lp-asset-list .lp-asset-select.checked,
.lp-asset-list.lp-asset-list-has-selection .lp-asset-select {
  opacity: 1;
}
.lp-asset-select:hover { border-color: var(--amber); }
.lp-asset-select.checked { background: var(--panel); color: var(--ink); border-color: var(--ink); }
.lp-asset-select.checked::after {
  content: "";
  width: 4px; height: 7px;
  /* GEÄNDERT (2026-08-13): war fest rgba(0,0,0,.75) — im Dark Theme ein
     fast schwarzes Haekchen auf der fast-schwarzen .checked-Flaeche
     (var(--panel) #363636), Kontrast 1.74:1. --ink-hi ist in beiden
     Themes richtig gepolt (Dark hell auf dunklem Kaestchen, Light dunkel
     auf hellem Kaestchen) — 10.8:1 (Dark) / 17.4:1 (Light). */
  border: solid var(--ink-hi);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-1px, -1px);
}
/* GEÄNDERT (2026-08-13): war var(--panel-2) — dieser Token haengt an ~40
   weiteren Hover-Flaechen app-weit, hier bewusst durch einen eigenen
   Token ersetzt (siehe :root). Jetzt deutlich dunkler als die normale
   Zeile (1.85:1 Dark / 1.27:1 Light) statt wie vorher heller/gleich. */
.lp-asset-row-selected { background: var(--asset-row-selected-bg); }

/* ── Hover-Symbole auf dem Asset-Thumbnail (SimplifyerX-Auftrag) ─────
   Unten links auf dem Vorschaubild, erscheinen nur beim Hover — dieselbe
   Optik/Reveal-Mechanik wie .lp-asset-select oben (halbtransparente
   Chip-Flaeche, opacity 0→1 bei :hover der Zeile/Kachel), nur als
   Knopfreihe statt Checkbox. Siehe assetHoverActionsMarkup() in app.js
   fuer die Funktionen selbst (Vergroessern/Scroll to Asset/Open in a new
   tab) — hier nur die Darstellung. */
.lp-asset-hover-actions {
  position: absolute;
  left: 6px; bottom: 6px;
  display: flex; gap: 3px;
  z-index: 2;
  opacity: 0;
  transition: opacity .12s;
}
/* Kachelansicht, Nicht-Bild-Typen (PDF/SVG/MD/Font/Audio — siehe isDoc in
   populateAssetsPanel()): .lp-asset-tile-caption liegt bereits ueber die
   VOLLE Breite am unteren Rand (bottom:0) und wuerde sich mit den Symbolen
   dort ueberlappen. Ausnahmsweise oben links statt unten links, symmetrisch
   zur Auswahl-Checkbox oben rechts (.lp-asset-tile .lp-asset-select). */
.lp-asset-hover-actions-top { bottom: auto; top: 6px; }
.lp-asset-list .lp-asset-row:hover .lp-asset-hover-actions,
.lp-asset-list .lp-asset-tile:hover .lp-asset-hover-actions {
  opacity: 1;
}
.lp-asset-hover-btn {
  width: 18px; height: 18px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--ink-dim);
  border-radius: 3px;
  background: rgba(var(--overlay-rgb), .85);
  color: var(--ink-dim);
  cursor: pointer;
  transition: border-color .12s, color .12s;
}
.lp-asset-hover-btn svg { width: 11px; height: 11px; }
.lp-asset-hover-btn:hover { border-color: var(--amber); color: var(--ink); }
/* Gleiche Behandlung wie .rail-btn-disabled (Detail-Panel-Pendant, siehe
   dort): sichtbar, aber gedaempft und unklickbar, statt ganz zu fehlen —
   "Not placed in the BOARD" erklaert sich per title, ein fehlender Knopf
   wirkt dagegen wie ein Fehler. */
.lp-asset-hover-btn-disabled { opacity: .3; pointer-events: none; }

/* ── Bulk-Toolbar (Export/Import/Delete) ─────────────────────
   Immer sichtbar am unteren Rand des Assets-Panels fixiert (.left-panel
   ist position:fixed → dient als containing block für dieses absolute). */
/* NEU (2026-07): die Bulk-Toolbar ist jetzt standardmäßig ausgeblendet und
   fährt nur ein, sobald mindestens ein Asset ausgewählt ist (Klasse
   .lp-assets-bulk-toolbar-visible, siehe populateAssetsPanel() in app.js) —
   animiert von unten nach oben rein / von oben nach unten wieder raus,
   statt permanent den unteren Panel-Rand zu belegen. */
.lp-assets-bulk-toolbar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: var(--panel);
  border-top: 1px solid var(--line-soft);
  box-shadow: 0 -4px 16px rgba(0,0,0,.18);
  z-index: 5;
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
  transition: transform .2s ease, opacity .2s ease;
}
.lp-assets-bulk-toolbar.lp-assets-bulk-toolbar-visible {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}
/* NEU (2026-08-14, "Resize-Griff ist verdeckt"): nur im SCHWEBENDEN
   Panel zusaetzlicher Rand-Abstand, damit das rechte "X" (Delete) nicht
   mit der 16x16-Hitbox von .floating-panel-resize in der Ecke
   konkurriert — 14px Basis-Padding + 16px Griffbreite plus etwas Luft.
   Angedockt bleibt es bei 14px: dort sitzt der Anfasser als schmaler
   Streifen ausserhalb der Panelkante, keine Ueberschneidung. */
.floating-panel-inner .lp-assets-bulk-toolbar { padding-right: 26px; }
.lp-assets-bulk-count {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--ink-dim);
  flex-shrink: 0;
}
.lp-assets-bulk-btns { display: flex; gap: 8px; }
/* GEAENDERT (2026-08-13): EXPORT/IMPORT/RESTORE/→REF und das "X"
   tragen jetzt .btn .btn-sm im Markup (default/small); das "X"
   zusaetzlich .btn-danger fuer den roten Hover. EXPORT und IMPORT
   sind bewusst BEIDE default: gleichrangige Parallel-Knoepfe in
   derselben Zeile, keiner verdient mehr Aufmerksamkeit als der
   andere (Nutzer-Entscheidung 2026-08-13, korrigiert die vorherige
   Annahme, EXPORT sei der wichtigere). Hier bleibt nur der
   Sperr-Zustand. */
/* NEU (2026-08-07, Papierkorb-Mehrfachauswahl): Delete bleibt sichtbar,
   aber gesperrt, solange die Auswahl (auch) über Select All zustandekam
   — siehe trashSelectionViaSelectAll (app.js).
   GEÄNDERT (2026-08-13, vierte Runde): eigene :disabled-Regel entfallen,
   RESTORE/X sind .btn/.btn-sm und tragen jetzt den einheitlichen
   :disabled-Zustand von .btn (siehe dort, inkl. Hover-Sperre). */

/* ── Papierkorb: Select-All-Kopfzeile (Mehrfachauswahl) ──────
   Schlankes Gegenstück zu .lp-assets-sort im BROWSE-Bereich — dort
   sitzen zusaetzlich Suche/Sortierung, hier nur die eine Checkbox. */
.lp-assets-trash-headrow {
  display: flex;
  justify-content: flex-end;
  padding: 6px 14px;
  border-bottom: 1px solid var(--line-soft);
}

/* ── Asset Library: Import ───────────────────────────────
   NEU (2026-07): Drag&Drop-Import gilt jetzt fürs ganze Panel statt
   einer eigenen kleinen Dropzone-Box (siehe bindAssetLibrary(), app.js)
   — die Box entfiel dadurch. .assets-drag-over hebt stattdessen das
   ganze Panel per Rahmen hervor, solange eine Datei darüber gezogen
   wird. .lp-asset-import-btn ersetzt die Box als Klick-zum-Durchsuchen-
   Alternative, gleiche Optik wie .lp-asset-delete-all daneben. */
.left-panel[data-panel-target="assets"].assets-drag-over,
.floating-panel[data-floating-panel="assets"].assets-drag-over {
  outline: 2px dashed var(--amber);
  outline-offset: -2px;
}
/* GEAENDERT (2026-08-13): Erscheinung komplett in .btn + .btn-sm
   (default/small). Das frueher hier hart codierte "font-size: 9px;
   padding: 3px 9px" ist in --btn-fs-sm/--btn-pad-sm aufgegangen —
   solche Zahlen entziehen sich sonst jeder Messung. Es bleibt nur,
   was Layout in der Import-Zeile ist. */
.lp-asset-import-btn {
  white-space: nowrap;
  flex-shrink: 0;
}

/* ── Assets-Panel-Redesign (NEU 2026-07) ─────────────────────
   Import-Bereich oben (Local + URL), kombinierte Typ-Filter-Zeile,
   Suche + Select-All in der Sortier-Zeile. */
.lp-assets-import {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--line-soft);
}
.lp-asset-url-row { display: flex; gap: 6px; }
.lp-asset-url-input {
  flex: 1; min-width: 0;
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink);
  background: var(--bg-deep);
  border: 1px solid var(--line);
  padding: 4px 8px;
  outline: none;
  transition: border-color .12s;
}
.lp-asset-url-input:focus { border-color: var(--amber); }
.lp-asset-url-input::placeholder { color: var(--ink-faint); }
/* IMPORT URL — der EINZIGE Knopf mit .btn-extra in der ganzen
   Oberflaeche (Nutzer-Entscheidung 2026-08-13). Er sitzt direkt am
   Eingabefeld und ist die Los-Aktion dieses zusammengesetzten
   Steuerelements; genau dafuer ist die prominentere Variante da.
   Dass es nur einer ist, ist kein Mangel, sondern der Beleg, dass
   die Unterscheidung traegt. Erscheinung: .btn .btn-extra .btn-sm
   im Markup, hier bleibt nur Layout + Ladezustand. */
.lp-asset-url-btn {
  white-space: nowrap;
}
.lp-asset-url-btn.loading { opacity: .5; pointer-events: none; }
.lp-asset-url-error {
  font-family: var(--f-data);
  font-size: 10px;
  line-height: 1.4;
  color: var(--red, #c0392b);
}
.lp-asset-url-error[hidden] { display: none; }

/* Kombinierte Counter+Filter-Zeile: sechs klickbare Typ-Zähler + Größe */
.lp-assets-typefilter {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 8px 10px;
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none;
  border-bottom: 1px solid var(--line-soft);
}
.lp-assets-typefilter::-webkit-scrollbar { display: none; }
.lp-assets-typebtn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  min-width: 40px;
  padding: 4px 5px 3px;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.lp-assets-typebtn:hover { color: var(--ink); }
/* GEAENDERT (2026-08-14, "Panel-Header-Icons verkleinern + Akzent-
   Unterstreichungen", Punkt 2, SimplifyerX-Entscheidung): Amber-
   Unterstreichung entfernt (border-bottom-color faellt weg, Rand bleibt
   beim geerbten "2px solid transparent" der Basisklasse). Eigene Klasse,
   keine Kollision mit dem Panel-Kopf (siehe .lp-assets-viewtab dort). */
.lp-assets-typebtn.active { color: var(--ink); }
/* GEAENDERT (2026-08-14, dieselbe Runde): Zahl faerbte bisher IMMER
   kraeftig (var(--ink) fest, unabhaengig vom Hover-/Active-Zustand) —
   Ursache fuer den zu schwachen Rest-Kontrast nach dem Entfernen der
   Unterstreichung: nur das Icon (60% Deckkraft) trug den Zustand, die
   Zahl blieb stumm, obwohl sie die groessere, fettere Haelfte des
   Knopfes ist. Kein inhaltlicher Grund dafuer gefunden (Commit 0410c76
   beschreibt nur die Umstellung, keine Begruendung ueber "Redesign
   Assets Manager panel" hinaus) — die Zahl gehoert zum selben
   Filter-Zustand wie das Icon direkt darueber. Faerbt jetzt mit: kein
   eigenes color mehr, erbt via currentColor von .lp-assets-typebtn
   (--ink-faint inaktiv, --ink aktiv/hover) — GEMESSEN nach der
   Umstellung: 1.72:1 (Dark) / 6.16:1 (Light), identisch zum Icon, da
   beide jetzt denselben Farbwert erben. Schwach bleibt Dark-Theme fuer
   sich genommen, aber Icon+Zahl bewegen sich jetzt gemeinsam statt nur
   die kleinere Haelfte — SimplifyerX-Entscheidung, damit als
   ausreichend eingestuft. */
.lp-assets-type-num { font-family: var(--f-label); font-size: 12px; font-weight: 600; line-height: 1; }
.lp-assets-type-icon { width: 24px; height: 24px; display: grid; place-items: center; }
.lp-assets-type-icon svg { width: 18px; height: 18px; opacity: 0.6; }
.lp-assets-type-label { display: none; }
.lp-assets-type-size {
  margin-left: auto;
  padding-left: 8px;
  font-family: var(--f-data);
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  white-space: nowrap;
  align-self: center;
}

/* Suche in der Sortier-Zeile (links) */
.lp-asset-search-wrap {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  border: 1px solid var(--line);
  padding: 2px 7px;
  transition: border-color .12s;
}
.lp-asset-search-wrap:focus-within { border-color: var(--amber); }
.lp-asset-search-wrap svg { width: 11px; height: 11px; color: var(--ink-faint); flex-shrink: 0; }
.lp-asset-search {
  min-width: 0; width: 110px;
  font-family: var(--f-data);
  font-size: 10px;
  color: var(--ink);
  background: transparent;
  border: 0;
  outline: none;
}
.lp-asset-search::placeholder { color: var(--ink-faint); }

/* Select-All-Checkbox in der Sortier-Zeile — nutzt die vorhandene
   .lp-asset-select-Optik, nur nicht absolut positioniert. */
.lp-asset-select-all {
  position: static;
  flex-shrink: 0;
}

/* ── Schmale Panels: Kopfzeile umbrechen + Typ-Filter einklappen ──
   NEU (2026-08-14, "sechs Optimierungen fuer schmale Panels", Punkt 3+4).
   GEMESSEN (echte CSS, DOM-Kette .lp-assets-active-view/.lp-assets-sort,
   schwebendes UND angedocktes Panel, identisch): .lp-asset-search bleibt
   bei voller, unkomprimierter Breite (110px) bis genau 413px
   Container-Breite, ab 411px beginnt sie sich zu stauchen (Bruchstelle
   binaer eingegrenzt in 1px-Schritten). 412px liegt dazwischen und ist
   der hier verwendete Wert — ab da (und darunter) zweizeilig, bei 413px
   und darueber bleibt es bei einer Zeile. SimplifyerX' Referenzbreiten
   (440px angedockt, ~500px schwebend) liegen beide deutlich darueber,
   bleiben also im "Normalfall" einzeilig wie gefordert.
   EINE gemeinsame Schwelle statt zwei gestaffelter: die Typ-Filter-Zeile
   blendet unterhalb davon ohnehin ALLE Typ-Knoepfe bis auf "All" aus —
   die Anzahl der bei mehr Typen tatsaechlich noetigen Platz variiert
   je nach Board (bis zu 6 Typen + "All"), das faellt aber weg, sobald
   nur noch 1 Knopf uebrig bleibt. Denselben Umbruchpunkt zu nehmen wie
   die Sortier-Zeile ist daher nicht nur einfacher zu pflegen, sondern
   auch inhaltlich richtig: beide Zeilen werden eng aus demselben Grund
   (zu wenig Panel-Breite), eine geteilte Schwelle raeumt das Kopfende
   des Panels an genau einer Stelle konsistent auf. */
@container assets-header (max-width: 412px) {
  .lp-assets-sort {
    flex-wrap: wrap;
  }
  .lp-assets-sort-left { flex-basis: 100%; }
  .lp-assets-sort-right { flex-basis: 100%; justify-content: flex-end; }
  /* "etwas kleiner": .asset-sort-btn ist ein zustandsbehafteter
     Umschalter, kein .btn — dafuer existiert keine eigene Groessenstufe.
     9px ist im Projekt durchgaengig die naechstkleinere Stufe unter 10px
     fuer .f-data-Beschriftungen (u.a. .lp-asset-meta, .lp-assets-trash-
     badge im selben Panel) — hier wiederverwendet statt einen neuen,
     eigenen Wert einzufuehren. */
  .lp-assets-sort-btns .asset-sort-btn { font-size: 9px; }
  /* Punkt 4: Typ-Icons bis auf "All" ausblenden — loest nebenbei auch
     den abgeschnittenen Speicherwert (".lp-assets-type-size"), der
     bisher hinter den unsichtbaren Rest-Knoepfen ausserhalb des
     sichtbaren Bereichs lag (.lp-assets-typefilter scrollt horizontal,
     aber ohne sichtbaren Scrollbalken — kein Hinweis, dass da noch was
     ist). Mit nur noch einem Knopf ist reichlich Platz fuer "All" +
     Groesse nebeneinander, kein Abschneiden mehr. */
  .lp-assets-typebtn:not(:first-child) { display: none; }
}

/* NEU (2026-08-03): per "Show" aus dem Asset-Detailpanel hervorgehobene
   Zeile. BLEIBT stehen, bis ein anderes Asset gezeigt oder eine Zeile
   angeklickt wird — anders als .lp-asset-row-flash unten, der als
   Hervorhebung in der Praxis unsichtbar blieb (14 % Deckkraft, in
   Sekundenbruchteilen ausgeblendet).
   Deutlich abgesetzt von .lp-asset-row-selected (nur Hintergrund): das
   ist ein anderer Zustand — ausgewaehlt heisst bereit fuer Export oder
   Loeschen, hervorgehoben heisst nur gefunden. outline statt border,
   damit die Zeilenhoehe und damit das Listen-Layout unveraendert
   bleibt. */
/* GEÄNDERT (2026-08-12, Nutzer-Vorgabe): war Amber-Rahmen rundum + Amber-
   Hintergrundfläche — zu dominant. Nur noch linker Rand (border-left auf
   der Basisregel .lp-asset-row reserviert dafür schon den Platz, keine
   Verschiebung beim Umschalten). Klasse wird ausschließlich für diese
   Zeile genutzt (geprüft), keine Dashboard-Kacheln/Board-Auswahl teilen
   sich das — nichts zu trennen. */
.lp-asset-row-revealed {
  border-left-color: var(--amber);
  /* NEU (2026-08-12, Nutzer-Vorgabe, Runde 2): dunklerer Hintergrund
     zusaetzlich zur Amber-Kante. --bg-deep statt --panel-2, weil
     --panel-2 bereits Hover- UND .lp-asset-row-selected-Hintergrund ist —
     Wiederverwendung haette die hervorgehobene Zeile ununterscheidbar
     von Hover/Auswahl gemacht. --bg-deep ist bereits an anderer Stelle
     die "vertiefte Flaeche" (Media-Wells) und enthaelt keine
     Akzentfarbe — funktioniert unveraendert, wenn der Nutzer den Akzent
     von Amber auf eine andere Farbe umstellt. Kontrast --ink-hi auf
     --bg-deep (Dark): ca. 13.3:1, deutlich ueber AAA. */
  background: var(--bg-deep);
}
/* NEU (2026-08-12, --ink-hi): die hervorgehobene Zeile trug bisher nur den
   Amber-Rahmen, der Name selbst blieb normal hell. */
.lp-asset-row-revealed .lp-asset-name { color: var(--ink-hi); }
/* NEU (2026-08-15, Befund aus der Kachelansicht-Nachpruefung): .lp-asset-
   tile setzt seinerseits `border: 1px solid var(--line-soft)` (Rahmen auf
   allen vier Seiten der Kachel) — das schrumpft den oben reservierten
   3px-Rand auf 1px, GENAU AUF DIESER hervorgehobenen Kachel (Farbe blieb
   korrekt Amber, nur die Breite ging verloren). In der Liste faellt das
   nicht auf (dort hat nur .lp-asset-row-revealed ueberhaupt einen
   sichtbaren border-left), in der Kachel schon, weil jede Nachbarkachel
   ohnehin schon einen 1px-Rahmen traegt — der Amber-Unterschied wurde
   dadurch fast unsichtbar. Explizite Breite fuer diesen Fall. */
.lp-asset-tile.lp-asset-row-revealed { border-left-width: 3px; }

/* frisch per URL importiertes Asset kurz hervorheben */
.lp-asset-row-flash { animation: lpAssetFlash 1.5s ease-out; }
@keyframes lpAssetFlash {
  0% { background: var(--amber-dim); }
  100% { background: transparent; }
}

/* ── Asset row: drag state ───────────────────────────────── */
.lp-asset-row[draggable="true"] { cursor: grab; }
.lp-asset-row.asset-dragging { opacity: .4; }

/* ── Card slot: drop ready highlight ────────────────────── */
.upload-drop-ready {
  outline: 2px solid var(--amber) !important;
  outline-offset: -2px;
}

/* ── Global ratio button in card toolbar ─────────────────── */
.assets-global-ratio-btn {
  margin-left: inherit;
  display: grid; place-items: center;
  min-width: 34px; height: 22px;
  padding: 0 7px;
  background: none;
  /* GEAENDERT (Karten-Bedienelemente-Runde): Rahmen/Textfarbe waren
     var(--line-soft)/var(--ink-faint) — eine andere Variable als die
     Layout-Vorlagen-Icons links in derselben Leiste (.assets-cols-btn,
     var(--board-ink-faint)). Jetzt dieselbe Variable wie dort: eine
     Leiste, eine Farbquelle. var(--board-ink-faint) ist board-eigen
     (Default = var(--ink-faint), per META-Colors-Einstellung
     ueberschreibbar) und traegt in beiden Themes. */
  border: 1px solid var(--board-ink-faint);
  color: var(--board-ink-faint);
  cursor: pointer;
  transition: color .12s, border-color .12s;
  flex-shrink: 0;
}
.assets-global-ratio-label {
  font-family: var(--f-data);
  font-size: 9px;
  letter-spacing: .02em;
  white-space: nowrap;
  color: var(--board-ink-faint);
}
.assets-global-ratio-btn svg { width: 12px; height: 12px; }
.assets-global-ratio-btn:hover,
.assets-global-ratio-btn.controls-open { color: var(--amber); border-color: var(--amber); }

/* ── Export / Backup: simplified full-width buttons ─────── */
.ep-btn2-stack { display: flex; flex-direction: column; gap: 0px; }

/* NEU (2026-08-23, 1d): Der Share-Link steht fest im EXPORT/BACKUP-
   Panel statt in einem Overlay — siehe renderShareState()
   (world-sync.js). Bewusst schlicht und an den bestehenden
   Panel-Bedienelementen ausgerichtet: dieselbe .share-link-input, die
   das fruehere Popover schon nutzte, damit es genau eine Gestalt fuer
   die Link-Anzeige gibt. */
.ep-share-state {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 0 4px;
}
.ep-share-state[hidden] { display: none; }
.ep-share-state-title {
  font-family: var(--f-label);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.ep-share-state-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.ep-btn2-sep { border: none; border-top: 0px solid; margin: 4px 0; }

/* NEU (2026-08-07, Referenzen im Share): Opt-in-Zeile ueber dem
   SHARE-LINK-Knopf. Der Hinweistext ist bewusst vollstaendig sichtbar
   und nicht als title-Tooltip versteckt — er beschreibt eine
   Zugriffsfolge, keine Bedienhilfe. */
.ep-share-refs {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--line-soft);
  border-bottom: none;
  cursor: pointer;
}
.ep-share-refs input { margin-top: 2px; flex-shrink: 0; cursor: pointer; }
.ep-share-refs-text { display: flex; flex-direction: column; gap: 2px; }
.ep-share-refs-label {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.ep-share-refs-hint { font-size: 10px; line-height: 1.45; color: var(--ink-faint); }

.ep-btn2 {
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 34px 12px 14px;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  cursor: pointer;
  text-align: left;
  transition: border-color .15s, background .15s;
}
.ep-btn2:hover { border-color: var(--ink-dim); background: var(--panel-2); }

.ep-btn2-label-stack {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.ep-btn2-label {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: .01em;
}

.ep-btn2-sublabel {
  font-family: var(--f-data);
  font-size: 9px;
  font-weight: 400;
  color: var(--ink-faint);
  letter-spacing: .01em;
}

.ep-btn2-icon {
  flex-shrink: 0;
  width: 18px; height: 18px;
  color: var(--ink-faint);
  display: grid;
  place-items: center;
}
.ep-btn2-icon svg { width: 100%; height: 100%; }
.ep-btn2:hover .ep-btn2-icon { color: var(--amber); }

/* NEU (2026-08-23, Share-Treue 1e): Ladering im SHARE-Knopf, rechts.
   margin-left:auto schiebt ihn ans rechte Ende der Flex-Zeile; das
   34px breite rechte Polster von .ep-btn2 bleibt dabei frei, dort
   sitzt der Info-Knopf. Wird von shareSpinnerAn()/-Aus()
   (world-sync.js) als eigenes Element ein- und ausgehaengt. */
.ep-share-spinner {
  flex-shrink: 0;
  margin-left: auto;
  width: 14px;
  height: 14px;
  box-sizing: border-box;
  border: 2px solid var(--line-soft);
  border-top-color: var(--amber);
  border-radius: 50%;
  animation: ep-share-spin .8s linear infinite;
}
@keyframes ep-share-spin { to { transform: rotate(360deg); } }
/* Wer Bewegung abgeschaltet hat, sieht denselben Ring ohne Drehung —
   die Anwesenheit des Elements ist die eigentliche Aussage. */
@media (prefers-reduced-motion: reduce) {
  .ep-share-spinner { animation: none; }
}

.ep-btn2-info {
  position: absolute;
  bottom: 8px; right: 10px;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 0;
  color: var(--ink-faint);
  font-family: var(--f-data);
  font-size: 9px;
  font-style: italic;
  display: grid;
  place-items: center;
  cursor: help;
  transition: color .12s;
}
.ep-btn2-info:hover { color: var(--amber); }
/* ENTFERNT (2026-08-13, Tooltip-Umbau Etappe 2): eigener ::after-Tooltip
   ist im gemeinsamen .setzz-tooltip-System aufgegangen (siehe unten) —
   das Markup traegt jetzt title= statt data-info=. Zwei Systeme, die
   dasselbe (eine Info-Fläche zeigen) taten, waeren die Doppelung
   gewesen, die der Button-/Icon-Umbau an anderer Stelle schon
   beseitigt hat. */

/* ── Slider cards (char/vehicle): pixel-exact height, same as grid ── */
/* ENTFERNT (2026-09-03, GEMESSEN SimplifyerX): `outline: 3px solid
   var(--asset-outline)` — GEPRUEFT vor dem Entfernen, nicht angenommen:
   der Rahmen war ueber --asset-outline vollstaendig ins Theme-System
   eingebunden (identischer Wert bei Slider-H, Slider-V UND den
   Karussells, reagiert korrekt auf Hell/Dunkel und auf die BOARD
   SETTINGS "Card Figure Outline") — er fiel NICHT aus der
   Theme-Anpassung heraus, er war ihr Teil. Ob er etwas TRAEGT, ebenso
   geprueft: die Folien liegen in einer Reihe nebeneinander (Slider =
   ein Bild sichtbar, die anderen ausserhalb des sichtbaren Bereichs) —
   kein Nutzer sieht je zwei Slots gleichzeitig, es gibt also keine
   Naht, die der Rahmen offenhalten muesste. Trotzdem auf Wunsch entfernt.
   `.single-media`/`.asset-thumb`/`.list-thumb`/`.audio-slot` behalten
   ihre `--asset-outline` unveraendert — nur der Slider/Karussell-Fall. */
.slider-thumb {
  position: relative;
  overflow: hidden;
  display: block;
  width: 100%;
  height: 200px; /* fallback before JS runs */
  outline: none;
}

/* ── Column Groups ────────────────────────────────────────── */
/* KORREKTUR (2026-07): war fest padding:30px 10px 0px 20px — die festen
   10px/20px links/rechts addierten sich zum neuen padding-left/-right
   von .group-body (jetzt voller --gap-col-Wert) und machten den
   sichtbaren Abstand SET-Rand-zu-erster/letzter-Column breiter als den
   Abstand zwischen zwei Columns, besonders auffällig bei kleinen
   Gap-Werten (z.B. S). .group-head hat ein eigenes, unabhängiges
   padding-left/right:15px (siehe dort) und ist von dieser Änderung nicht
   betroffen. Zunächst auf 0 gesetzt, dann auf Nutzer-Wunsch (2026-07)
   durch zwei eigene, einstellbare Kategorien ersetzt (BOARD SETTINGS →
   BOARD Paddings → "SET h-padding"/"SET v-padding") — bewusst additiv
   zum .group-body-Padding, nicht mehr Teil der COL-Gap-Symmetrie-Fixes
   oben: wer hier einen Wert > 0 wählt, entscheidet sich damit aktiv für
   einen SET-Rand, der breiter ist als der COL-Gap zwischen Columns. */
.group-wrap {
  position: relative;   /* Anker fuer .group-resize, siehe unten */
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  padding: var(--pad-set-v) var(--pad-set-h);
  border-radius: 16px;
  border: 3px solid transparent;
  box-shadow: 0 4px 24px rgba(48, 48, 48, .25);
  background: var(--panel);
  /* KORREKTUR (2026-07): war margin-right:var(--gap) (fixe 14px, Relikt
     aus der Zeit vor dem --gap-set-Regler) — addierte sich zusätzlich zum
     Flex-Gap (--gap-set) und machte den Innenabstand zwischen zwei SETs
     (--gap-set + 14px) breiter als den neuen, korrekten Aussenabstand
     (exakt --gap-set). Entfernt: --gap-set (reiner Flex-Gap auf
     .group-row) bestimmt jetzt allein den SET-zu-SET-Abstand, exakt
     gleich dem Aussenabstand. */
  align-self: flex-start;
}
:root[data-theme="light"] .group-wrap { box-shadow: 0 4px 24px rgba(0,0,0,.00); background: var(--bg-deep); }
:root:not([data-theme="light"]) .group-wrap { background: rgba(48, 48, 48, .55); }

.group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: -10px;
  margin-top: 10px;
  padding-left: 15px;
  padding-right: 15px;
}
.group-title {
  font-family: var(--f-display);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: .03em;
  text-transform: uppercase;
  /* NEU: automatischer Kontrast-Ausgleich (siehe --board-ink-faint
     oben) — SET-Beschriftung sitzt bei allen drei Group-Styles
     (None/Border/Background) direkt oder halbtransparent auf dem
     Workspace-Hintergrund, nie auf einem festen Card-Panel. */
  color: var(--board-ink-faint);
  opacity: 1;
  cursor: text;
  min-width: 0;
}
.group-title .title-edit {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
  text-transform: uppercase;
}
.group-head-actions {
  display: flex; align-items: center; gap: 6px; flex-shrink: 0; margin-top: 5px;
  opacity: 0;
  transition: opacity .15s;
}
.group-wrap:hover .group-head-actions { opacity: 1; }
/* NEU (2026-07): Move-Icons (links/rechts/hoch/runter) vorerst ausgeblendet
   — Funktion bleibt im Code bestehen, nur die Buttons sind versteckt. */
.group-head-actions [data-group-action="move-up"],
.group-head-actions [data-group-action="move-down"],
.group-head-actions [data-group-action="row-up"],
.group-head-actions [data-group-action="row-down"] { display: none; }
/* SET-Breitenfaktor, LINKS direkt beim SET-Titel. Optisch bewusst an
   .col-width-factor angelehnt (gleiche --f-data-Schrift, gleiche 11px)
   — beide meinen dasselbe, nur auf verschiedenen Ebenen, und beide
   sitzen deshalb auch an derselben Stelle relativ zu ihrem Titel.
   Bewusst KEIN separater Drag-Anfasser: der rechte SET-Rand ist bei
   breiten SETs nicht ohne Scrollen erreichbar (siehe
   docs/setzz-md-roundtrip-notes.md), und ein zweites Element neben der
   Zahl wäre Redundanz — die Zahl ist selbst ziehbar, siehe
   .group-factor-input weiter unten und bindGroupUI() in app.js. */
.group-head-text {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;   /* damit der Titel schrumpfen darf statt zu überlaufen */
}
/* NEU (2026-08): Faktor-Anzeigen nur bei Hover über dem SET — Klarheit
   vor Dauerpräsenz, der Faktor ist am Ergebnis (breitere Spalten) ohnehin
   ablesbar. opacity statt display:none, damit der reservierte Platz
   erhalten bleibt und nichts springt. Drei Trigger, nicht nur Hover:
   - .group-wrap:hover      normaler Fall
   - :focus-within          Tippen im Feld — der Fokus hängt nicht an der
                             Maus, ohne das würde ein Feld, in das man
                             gerade tippt, unsichtbar sobald die Maus den
                             SET-Bereich verlässt
   - .group-wrap.factor-dragging   Ziehen (siehe unten) — bindGroupUI()
                             setzt die Klasse, weil der Wert beim Ziehen
                             per Sensitivity skaliert wird und die Maus
                             deshalb NICHT 1:1 unter dem Element bleibt;
                             ohne das würde der Wert mitten im Zug
                             ausblenden, während man ihn noch beobachtet
   Gilt genauso für .col-width-factor weiter unten — beide Regeln daher
   im Gleichlauf gepflegt. */
.group-factor {
  display: inline-flex; align-items: center; gap: 0;
  flex-shrink: 0;
  font-family: var(--f-data);
  font-size: 11px;
  font-weight: 600;
  color: var(--board-ink-faint);
  opacity: 0;
  transition: opacity .15s;
}
.group-wrap:hover .group-factor,
.group-factor:focus-within,
.group-wrap.factor-dragging .group-factor { opacity: 1; }
.group-factor:hover { color: var(--ink-dim); }
.group-factor-x { cursor: pointer; user-select: none; }
/* Linksbündig und schmal, damit "x" und Zahl als EIN Token gelesen
   werden ("x1.4") — genau wie beim COL-Faktor. Zentriert im breiten
   Feld sah die Zahl aus, als gehöre sie nicht zum x. */
.group-factor-input {
  -webkit-appearance: none;
  appearance: none;
  width: 34px;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0 1px;
  text-align: left;
  /* ew-resize hinweist auf "ziehbar" im Ruhezustand; im Fokus (aktive
     Texteingabe nach einem echten Klick, siehe bindGroupUI()) zeigt der
     Cursor stattdessen "text" — die Zahl kann beides, aber nicht beides
     gleichzeitig signalisieren. */
  cursor: ew-resize;
}
.group-factor-input:hover { border-bottom-color: var(--board-ink-faint); }
.group-factor-input:focus { outline: none; border-bottom-color: var(--ink-dim); color: var(--ink); cursor: text; }
/* Spinner-Pfeile ausblenden — die Tastatur-Pfeiltasten funktionieren
   weiterhin, aber die nativen Buttons würden das schmale Feld sprengen. */
.group-factor-input::-webkit-outer-spin-button,
.group-factor-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.group-factor-input { -moz-appearance: textfield; }
/* Während des Ziehens (siehe bindGroupUI()) folgt der Cursor NICHT
   1:1 der Maus (Sensitivity-Skalierung statt direktem Tracking wie bei
   .col-resize), daher reicht der :hover-Cursor auf dem Element allein
   nicht — er würde verschwinden, sobald die Maus während des Zugs vom
   kleinen Eingabefeld abweicht. Body-weiter Cursor + user-select:none
   während der aktiven Geste, analog zu .col.resizing. */
body.factor-scrubbing { cursor: ew-resize; user-select: none; }

.group-btn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  background: none;
  border-radius: 0;
  border: 0px solid;
  color: var(--board-ink-faint);
  font-size: 11px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.group-btn svg { width: 18px; height: 18px; }
.group-btn:hover { color: inherit; border-color: inherit; }
.group-btn-danger:hover { color: var(--red, #d04040); border-color: var(--red, #d04040); }

/* Smaller left/right move arrows, sit to the left of the 3-dash menu */
.group-btn-arrow {
  width: 24px; height: 24px;
  font-size: 10px;
}
.group-btn-arrow svg { width: 14px; height: 14px; }
.group-menu-btn svg { width: 18px; height: 18px; }

/* BUGFIX (2026-07, siehe docs/setzz-board-customizing-notes.md, "COL-Gap
   asymmetrisch" — ursprünglicher Fund, gleiche Begründung wie bei
   .group-row oben): flex `gap` erzeugt nur Abstand ZWISCHEN Columns,
   nie am äusseren Rand der ersten/letzten Column — die sass bisher nur
   mit dem Innen-Padding von .group-wrap (fest 20/10px, unabhängig vom
   COL-Gap-Regler) am Rand. Padding auf .group-body statt auf jeder
   .col einzeln (wie ursprünglich vorgeschlagen): .group-body ist
   ebenso wie .col unsichtbar (kein eigener Rahmen/Hintergrund), eine
   einzelne Regel hier ist rechnerisch identisch zu "Hälfte auf jeder
   Column" (nowrap, also keine Zeilenumbrüche, bei denen sich das
   unterscheiden könnte), aber ohne 12+ Column-Instanzen einzeln
   anzufassen — weniger Angriffsfläche für Card-Drag&Drop/Minimap, die
   an .col's Rechteck hängen. KORREKTUR (2026-07): Aussenabstand muss
   genau so breit sein wie der Abstand zwischen zwei Columns, nicht nur
   halb so breit — daher volles --gap-col als Padding, nicht die Hälfte.
   Zwischen zwei Columns bleibt der Abstand unverändert bei --gap-col
   (reiner Flex-Gap, hier nicht verändert); zusätzlich bekommt die
   erste/letzte Column jetzt denselben vollen Abstand zu ihrem
   jeweiligen äusseren Rand. position:relative als Anker für das jetzt
   absolut positionierte .col-add (siehe dort). */
.group-body {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: var(--gap-col);
  padding-left: var(--gap-col);
  padding-right: var(--gap-col);
}

/* NEU (2026-07): analog .col-add — aus dem Flex-Fluss genommen
   (position:absolute), damit er die intrinsische Breite von .group-row
   nicht mehr beeinflussen kann (Browser-Rundungseffekt bei mehreren SETs
   pro Reihe, siehe .col-add). right zentriert die 44px-Breite exakt im
   --gap-set breiten Aussen-Padding-Bereich. */
.group-add {
  position: absolute;
  top: 0; bottom: 0;
  right: calc((var(--gap-set) - 44px) / 2);
  width: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  border-radius: 10px;
  border: 2px dashed transparent;
  color: var(--board-ink-faint);
  background: none;
  cursor: pointer;
  transition: color .15s, border-color .15s;
  /* NEU (2026-08): der neue .group-resize-Anfasser (z-index:10, siehe
     unten) des letzten SETs einer Row liegt in derselben, jetzt
     eigenen Zone. Bei den eingebauten Presets Medium/Large bleibt
     genug Abstand (siehe Messwerte in docs/setzz-md-roundtrip-notes.md),
     bei Small (24px) und beim empfohlenen Custom-Minimum (20px)
     überlappen sich beide um ca. 10-12px. z-index höher haelt
     .group-add dort bewusst vollstaendig klickbar wie bisher (keine
     optische Änderung an diesem Button) — der Anfasser bleibt im
     Gegenzug ausserhalb der Überlappung erreichbar (bei Small noch
     14px, beim Minimum noch 8px eigene Zone), also in jedem Fall
     grabbar, nur dort etwas schmaler. */
  z-index: 11;
}
.group-add span { font-size: 24px; line-height: 1; }
.group-add:hover em { opacity: 1; }
.group-add em {
  opacity: 0;
  transition: opacity .15s;
  font-family: var(--f-data);
  font-style: normal;
  font-size: 9px;
  letter-spacing: .18em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
}
.group-add:hover { color: var(--amber); border-color: var(--amber); }

/* NEU (2026-08): Rand-Anfasser fuer den SET-Breitenfaktor — zweiter
   Bedienweg auf denselben Wert wie .group-factor-input (Scrubben),
   konsistent zu .col-resize: eine SET-Breite hat eine sichtbare Kante,
   die man dort greift, wo man sie sieht. Gleiche Technik wie
   .col-resize (Hitbox = voller --gap-set-Zwischenraum, sichtbare Linie
   per ::after), aber zusaetzlich komplett unsichtbar UND nicht
   interaktiv ausserhalb des SET-Hovers ("wie die Faktor-Anzeigen",
   nicht nur die Akzentfarbe wie bei COL). Da der Anfasser ein DOM-Kind
   von .group-wrap ist, loest Hover ueber ihm automatisch auch
   .group-wrap:hover aus — die Zahl am Kopf bleibt beim Ziehen sichtbar
   und aktualisiert sich mit, ganz ohne Zusatzcode.
   Rechenweg fuer die Bindung (siehe bindGroupUI() in app.js): DIREKTES
   1:1-Tracking der Maus, nicht die Sensitivity-Skalierung des
   Scrubbens — die dargestellte SET-Breite ist linear im Faktor, Δf =
   dx / Σ(gespeicherte COL-Breiten). Ohne die volle --gap-set-Breite als
   Hitbox (statt z.B. nur eine duenne Linie) waere der Anfasser bei
   kleinen Gaps kaum noch treffbar. */
.group-resize {
  position: absolute;
  top: 0; bottom: 0;
  right: calc(-1 * var(--gap-set));
  width: var(--gap-set);
  cursor: ew-resize;
  z-index: 10;
  touch-action: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
}
.group-wrap:hover .group-resize,
.group-wrap.factor-dragging .group-resize { opacity: 1; pointer-events: auto; }
.group-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  background: transparent;
  transition: background .15s;
}
.group-resize:hover::after,
.group-wrap.factor-dragging .group-resize::after { background: var(--amber); }
.group-wrap.factor-dragging { user-select: none; }

/* ── Group design variants ───────────────────────────────── */
.group-style-none {
  background: transparent !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
/* NEU: Hover über einem SET zeigt jetzt IMMER die "SET Border" in der
   Farbe des gerade eingestellten SET Border Style (unabhängig davon,
   ob dieses SET selbst Style None/Border/Background nutzt) — gibt so
   eine einheitliche visuelle Rückmeldung, welches SET gerade unter der
   Maus liegt. Höhere Spezifität (Pseudoklasse) gewinnt automatisch
   gegen die !important-Regeln von .group-style-none/-background oben. */
.group-wrap:hover {
  border-color: rgba(20, 20, 20, var(--set-style-alpha-dark)) !important;
}
:root[data-theme="light"] .group-wrap:hover { border-color: rgba(245, 245, 245, var(--set-style-alpha-light)) !important; }

/* NEU: Transparenz von "Style Border"/"Style Background" jetzt per
   Slider einstellbar (BOARD SETTINGS, "SET Style Transparency" — Light/
   Dark getrennt) statt fest 0.3 — siehe --set-style-alpha-dark/-light
   Default oben in :root, überschrieben von applySetStyleTransparency()
   (app.js) je nach board.setStyleTransparency. */
.group-style-border {
  background: transparent !important;
  border: 3px dashed rgba(20, 20, 20, var(--set-style-alpha-dark)) !important;
  box-shadow: none !important;
}
:root[data-theme="light"] .group-style-border { border-color: rgba(245, 245, 245, var(--set-style-alpha-light)) !important; }

/* NEU: "Style Background" — fast schwarz (Dark) / fast weiß (Light),
   dieselben Farbwerte wie oben bei "Style Border" verwendet. */
.group-style-background {
  background: rgba(20, 20, 20, var(--set-style-alpha-dark)) !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
:root[data-theme="light"] .group-style-background { background: rgba(245, 245, 245, var(--set-style-alpha-light)) !important; }

/* ── Text card: view mode toggle (Plain / Markdown / Formatted) ── */
.text-view-toggle {
  display: none;
  justify-content: flex-end;
  gap: 4px;
  padding: 0px 10px;
  margin-bottom: 8px;
}
.panel.toolbar-open .text-view-toggle { display: flex; }
.text-view-btn {
  min-width: 26px; height: 22px;
  padding: 0 6px;
  display: grid; place-items: center;
  background: none;
  border: 0;
  color: var(--board-ink-faint);
  opacity: 0.6;
  font-family: var(--f-data);
  font-size: 11px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.text-view-btn:hover { color: var(--ink); }
.text-view-btn.active { color: var(--board-ink-faint); background: var(--amber-dim); }

/* ── Text editor wrap: textarea / highlight overlay / formatted preview ── */
.text-editor-wrap {
  position: relative;
  flex: none;
  min-height: 160px;
  display: flex;
}
.text-highlight,
.text-formatted-preview {
  display: none;
}

/* Plain mode: just the textarea, nothing else */
.text-editor-wrap.mode-plain .text-editor { display: block; }

/* Markdown mode: textarea stays editable, transparent text so the
   highlight overlay behind it shows the colored syntax */
.text-editor-wrap.mode-markdown .text-editor {
  display: block;
  color: transparent;
  caret-color: var(--ink);
  background: transparent;
  position: relative;
  z-index: 1;
}
.text-editor-wrap.mode-markdown .text-highlight {
  display: block;
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 8px 10px;
  overflow: auto;
  pointer-events: none;
  /* GEAENDERT (2026-08-24): Karten-Farbton traegt jetzt (siehe .text-editor
     oben fuer die volle Begruendung der Zwei-Ebenen-Technik). Rahmen
     ENTFERNT: GEMESSEN keine :hover/:focus-Regel auf dieser reinen
     Anzeigeflaeche (pointer-events:none, sie ist ohnehin nie direktes
     Zeigeziel — die eigentliche Eingabe passiert im unsichtbaren
     .text-editor darueber) — der Rahmen zeigte nie einen Bedienzustand,
     nur eine weisse/graue Flaeche gegen die Karte ab. Traegt die Flaeche
     jetzt den Farbton, gibt es nichts mehr abzugrenzen. */
  background-color: var(--bg-deep);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  font-family: var(--f-label);
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--ink);
}
.text-highlight-code { font: inherit; white-space: inherit; display: block; }
.mdh-bold  { color: var(--ink); font-weight: 700; }
.mdh-italic{ color: var(--ink-dim); font-style: italic; }
.mdh-quote { color: var(--ink-faint); }
.mdh-link  { color: var(--amber); }

/* Formatted mode: hide textarea, show rendered HTML preview */
.text-editor-wrap.mode-formatted .text-editor { display: none; }
.text-editor-wrap.mode-formatted .text-formatted-preview {
  display: block;
  width: 100%;
  flex: none;
  min-height: 160px;
  max-height: 600px;
  padding: 8px 10px;
  /* GEAENDERT (2026-08-24): Karten-Farbton traegt jetzt (siehe .text-editor
     oben). Rahmen ENTFERNT: GEMESSEN keine :hover/:focus-Regel — cursor:text
     signalisiert die Klick-zum-Bearbeiten-Funktion bereits unabhaengig vom
     Rahmen, der Rahmen selbst stand immer gleich da, ob im Fokus oder
     nicht. Ohne Farbton (Opacity 0) bleibt die Flaeche weiterhin erkennbar:
     --bg-deep ist eine eigene, von --panel verschiedene Karten-Basis (die
     "Well"-Optik), der Kontrast dazu bleibt bestehen (GEMESSEN 2026-08-24,
     Dunkel: ~33 Graustufen-Abstand ohne jeden Farbton). */
  background-color: var(--bg-deep);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  color: var(--ink);
  font-family: var(--f-label);
  font-size: 12px;
  line-height: 1.6;
  overflow: auto;
  resize: vertical;
  cursor: text;
}
.text-formatted-preview h1,
.text-formatted-preview h2,
.text-formatted-preview h3,
.text-formatted-preview h4,
.text-formatted-preview h5,
.text-formatted-preview h6 {
  font-family: var(--f-display);
  color: var(--ink);
  /* GEAENDERT (2026-09-02): war 12px 0 6px — groessere Ueberschriften
     brauchen mehr Luft, sonst kleben sie am Absatz darueber. */
  margin: 16px 0 8px;
  line-height: 1.25;
}
/* ══ UEBERSCHRIFTEN-GROESSEN (2026-09-02) ══════════════════════════════
   Diese Karte ist die VORLAGE: die MD-Karte uebernimmt dieselben Werte
   (siehe .md-editor-host .editor-preview weiter unten), damit derselbe
   Markdown-Quelltext in beiden Karten deckungsgleich aussieht.

   GEMESSEN als Rahmen, in dem sich eine Ueberschrift IN einer Karte
   bewegen darf — die Struktur des Boards daneben:
     Row-Titel 30px · COL-Titel 16px · CARD-Titel 15px (alle --f-display)
   Die Grenze ist der ROW-Titel, nicht der CARD-Titel: COL- und
   CARD-Titel sind kleine Beschriftungen am Rahmen, keine inhaltlichen
   Ueberschriften — an ihnen gemessen duerfte eine h1 gar nicht wachsen.
   Der Row-Titel dagegen ist die naechsthoehere INHALTS-Ebene, und
   darunter muss eine Karten-Ueberschrift sichtbar bleiben.
   Deshalb h1 = 26px: vier Punkt groesser als bisher (22px, die
   gewuenschte Vergroesserung), aber vier Punkt UNTER dem Row-Titel, die
   Rangfolge Row > h1 bleibt also erhalten.

   Die unteren Ebenen waren zusaetzlich fehlerhaft: h4-h6 hatten gar
   keine eigene Groesse und fielen auf die em-Vorgaben des Browsers
   zurueck — bezogen auf 12px Grundschrift ergab das h5 = 10px und
   h6 = 8px, also Ueberschriften KLEINER als der Fliesstext. Jetzt endet
   die Reihe bei der Groesse des Fliesstextes (12px); unterschieden
   bleiben sie durch Schnitt (700) und Schriftart. */
.text-formatted-preview h1 { font-size: 26px; }
.text-formatted-preview h2 { font-size: 21px; }
.text-formatted-preview h3 { font-size: 17px; }
.text-formatted-preview h4 { font-size: 15px; }
.text-formatted-preview h5 { font-size: 13px; }
.text-formatted-preview h6 { font-size: 12px; }
.text-formatted-preview p  { margin: 0 0 8px; }
.text-formatted-preview strong { color: var(--ink); font-weight: 700; }
.text-formatted-preview em { font-style: italic; color: var(--ink-dim); }
/* GEPRUEFT (2026-09-04, "Der Codeblock folgt der Kartenflaeche"): bewusst
   NICHT auf --card-bg-tint umgestellt, obwohl der Auftrag genau das fuer
   den BLOCK (pre, weiter unten) verlangt. Inline-Code ist strukturell
   ein ANDERES Zeichen, kein kleinerer Codeblock: eigener Rahmen UND
   eigenes Polster, ein paar Zeichen INNERHALB eines Satzes, nicht der
   Hauptinhalt der Karte. Genau die Rolle, die der Auftrag selbst Zitat
   und Tabelle ausdruecklich zugesteht ("das sind die Zeichen von Tabelle
   und Zitat") -- die Begruendung fuer den Block ("etwas, das die Karte
   fuellt, muss sich nicht abheben") trifft auf ein paar Woerter in
   Rueckstrichen nicht zu. Bleibt deshalb unveraendert, wie es unter
   "No Style" schon heute bleibt (dort ist nur `pre`, nie `code`, in der
   Ausnahmeliste). */
.text-formatted-preview code {
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  padding: 1px 5px;
  font-family: var(--f-data);
  font-size: 11px;
}
.text-formatted-preview ul, .text-formatted-preview ol { margin: 0 0 8px 20px; padding: 0; }
.text-formatted-preview li { margin-bottom: 3px; }
.text-formatted-preview blockquote {
  border-left: 2px solid var(--amber);
  margin: 8px 0;
  padding: 2px 0 2px 12px;
  color: var(--ink-dim);
}
.text-formatted-preview a { color: var(--amber); text-decoration: underline; }

/* ── Add-row button (vertical group grid, below all rows) ──── */
.row-add {
  flex-shrink: 0;
  align-self: flex-start;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-width: 200px;
  height: 44px;
  border-radius: 10px;
  border: 2px dashed transparent;
  /* NEU: automatischer Kontrast-Ausgleich — sitzt direkt auf dem
     Workspace-Hintergrund, kein eigenes Card-Panel. */
  color: var(--board-ink-faint);
  background: none;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.row-add span { font-size: 24px; line-height: 1; }
.row-add:hover em { opacity: 1; }
.row-add em {
  opacity: 0;
  transition: opacity .15s;
  font-family: var(--f-data);
  font-style: normal;
  font-size: 9px;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.row-add:hover { color: var(--amber); border-color: var(--amber); }

/* ── Selected card (Delete / Ctrl+D shortcuts apply to this) ──
   No colored border — the title text itself becomes the highlight,
   turning full-contrast black (light) / white (dark). */
.panel.panel-selected .panel-head h2 { color: var(--ink); }

/* ── Row controls: move whole row up/down, delete row ────────
   Sits at the left edge of each .group-row, stretches to match
   the row's height like the +Group button does. */
/* GEAENDERT (2026-08-23, Bauteil 1): echte `width` statt blosser
   `max-width`.

   VORHER (GEMESSEN am 2026-08-23, Demo-Board): Ohne `width` war die
   Sidebar ein shrink-to-fit-Flex-Element — ihre Breite ergab sich aus
   dem BREITESTEN KIND, gedeckelt bei 200px. Das waren in der Praxis
   ZWEI konkurrierende Quellen, keine davon beabsichtigt:
     - der Titel (white-space: nowrap, 30px): "BRAND SETTINGS" = 217,3px
     - das <textarea> ueber sein HTML-Standardattribut cols=20 = 156,4px
   Gemessene Folgen: Eine Row mit kurzem Titel war 156,4px breit (das
   textarea gab den Ton an), eine mit langem Titel 200px — und der
   Titel ragte dann 17,3px aus der Sidebar heraus, weil overflow
   sichtbar ist. Die Textbreite haengte damit an einer Groesse, die
   inhaltlich nichts mit ihr zu tun hat.

   JETZT: eine gesetzte Breite aus board.rowSidebarWidth. Die Variable
   sitzt auf .columns-scroll (gemeinsamer Vorfahre ALLER Sidebars,
   gemessen) — siehe applyRowSidebarWidth() in app.js. Der Fallback
   200px haelt die Darstellung heil, falls die Variable fehlt. */
.row-sidebar {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 12px;
  width: var(--row-sidebar-w, 200px);
  position: relative;
  padding-top: 26px;   /* Platz für den Collapse-Toggle oben rechts */
}
/* NEU (2026-07): Auf-/Zuklapp-Button oben rechts an der Sidebar.
   GEAENDERT (2026-08-23, Bauteil 2): .row-description → .row-description-wrap
   — seit Bauteil 2 traegt dieser Wrapper BEIDE Modi (Bearbeiten/
   Anzeigen, siehe dort). Die alte Regel traf nur die Textarea; die
   Anzeige-Ansicht (.row-description-view) waere beim Einklappen
   sichtbar geblieben. */
.row-sidebar.row-sidebar-collapsed .row-title,
.row-sidebar.row-sidebar-collapsed .row-description-wrap {
  display: none;
}
.row-sidebar.row-sidebar-collapsed {
  width: 32px;
}
.row-title {
  cursor: text;
  writing-mode: inherit;
  transform: none;
  /* Der Titel darf die Breite nicht mehr bestimmen — er bekommt sie
     jetzt vorgegeben und fuellt sie. */
  width: 100%;
  /* GEAENDERT (2026-08-23, Bauteil 1b/1c). 1b setzte hier 12px (9px
     gemessene Ueberdeckung durch .row-controls + 3px Luft). 1c hebt auf
     24px an — GEMESSEN mit einem Worst-Case-Titel (langes,
     ununterbrochenes Wort, erzwingt einen Umbruch exakt an der
     Boxkante, `overflow-wrap: break-word`): bei 12px kam eine
     umgebrochene Zeile im Knopf-Band bis auf 3,1px an die Knoepfe
     heran — praktisch eine Kollision. Bei 24px liegt derselbe
     Worst-Case bei 17,7px Abstand. SimplifyerX' Vermutung, 12px
     koennte beim Titel reichen, trifft NICHT zu — der Titel bricht
     genauso nah an die rechte Kante um wie die Beschreibung, nur mit
     groesseren Zeichen. Kosten bei 120px (schmalste Breite): 7 → 5
     Zeichen je Zeile. Siehe Bericht. */
  padding-right: 24px;
}
.row-title span {
  font-family: var(--f-display);
  font-size: 30px;
  font-weight: 500;
  letter-spacing: .03em;
  text-transform: uppercase;
  /* NEU: automatischer Kontrast-Ausgleich — Row-Titel sitzt direkt auf
     dem Workspace-Hintergrund. */
  color: var(--board-ink-faint);
  opacity: 1;
  /* GEAENDERT (2026-08-23): war `nowrap`. Der Titel BRICHT jetzt UM,
     statt die Breite zu bestimmen und aus der Sidebar herauszuragen.
     Bewusst Umbruch und NICHT Kuerzung per text-overflow: ellipsis —
     die Sidebar ist die Erklaerebene des Boards (u.a. fuer
     Bildschirmaufnahmen), und eine abgeschnittene Ueberschrift ist
     genau dort wertlos. Gemessen: realistische Titel liegen ueber der
     Vorgabebreite ("BRAND SETTINGS" 217px, "CHARACTER DESIGN" 252px
     bei 200px Vorgabe) — Kuerzung waere also der Regelfall, nicht der
     Ausnahmefall. Umbruch zeigt bei jeder Breite den ganzen Titel. */
  display: block;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.05;
}
.row-title:hover span { color: var(--amber); }
.row-title .title-edit { writing-mode: inherit; }

/* NEU (2026-07): freies Beschreibungsfeld unter dem Row-Titel — ohne
   Hintergrund/Rahmen, wie im Referenz-Screenshot.
   GEAENDERT (2026-08-23, Bauteil 2): zwei Modi statt eines einzigen
   sichtbaren <textarea> — Textarea (Rohtext) beim Bearbeiten,
   gerendertes Markdown (renderMarkdownToHtml(), app.js) beim Anzeigen.
   .row-description-wrap traegt beide, umgeschaltet per Klasse
   ".editing" (siehe app.js, Fokus/Blur-gesteuert). */
.row-description-wrap {
  width: 100%;
  position: relative;
}
.row-description {
  width: 100%;
  background: none;
  border: none;
  outline: none;
  resize: none;
  overflow: hidden;
  font-family: var(--f-data);
  font-size: 13px;
  line-height: 1.5;
  /* NEU: automatischer Kontrast-Ausgleich — sitzt direkt auf dem
     Workspace-Hintergrund, kein eigenes Card-Panel. */
  color: var(--board-ink);
  opacity: 1;
  /* GEAENDERT (2026-08-23, Bauteil 1b/1c). 1b setzte hier `padding: 0
     12px 0 0` (war zuvor `padding: 0`) — GEMESSEN, dass der
     Beschreibungstext bei y=151 beginnt und damit im Knopf-Band
     (y=90-160) liegt. 1c hebt den Wert auf 24px an (SimplifyerX:
     12px reichte nicht). NUR EINE Deklaration, bewusst — 1b hatte hier
     zunaechst ein separates `padding-right: 12px` gesetzt, das ein
     `padding: 0`-Shorthand WEITER UNTEN in dieser selben Regel wieder
     zurueckgesetzt hat. Diese Runde per computed style (nicht Quelltext)
     gegengeprueft, siehe Bericht. */
  padding: 0 24px 0 0;
  min-height: 20px;
  /* NEU (2026-08-23, Bauteil 2): standardmaessig verborgen — sichtbar
     wird die Textarea nur im Bearbeiten-Modus (.editing). Anders als
     beim Zuklappen (das komplette anders geloest ist, siehe oben)
     bewusst per Klasse am WRAPPER geschaltet, nicht am Element selbst,
     damit ein einziger Umschaltpunkt beide Kindelemente steuert. */
  display: none;
}
.row-description-wrap.editing .row-description { display: block; }
.row-description-wrap.editing .row-description-view { display: none; }
.row-description::placeholder { color: var(--board-ink-faint); }

/* NEU (2026-08-23, Bauteil 2): Anzeige-Modus — gerendertes Markdown.
   Dieselben Basiswerte (Schrift, Zeilenhoehe, Farbe, Abstand) wie
   .row-description, damit der Wechsel zwischen den Modi optisch nicht
   springt. ANDERS ALS DIE TEXTAREA: ein ganz gewoehnlicher Block, der
   sich seiner Hoehe nach dem Inhalt selbst anpasst — keine
   scrollHeight-Rechnung noetig (siehe Bauteil 1c), das war ausschliesslich
   ein Textarea-Problem. */
.row-description-view {
  width: 100%;
  font-family: var(--f-data);
  font-size: 13px;
  line-height: 1.5;
  /* GEAENDERT (2026-08-23, Bauteil 2b): war --board-ink (volle Farbe).
     Gedaempft, DAMIT <strong> (weiterhin --board-ink, siehe dort) sich
     farblich abhebt — notwendig geworden, weil GEMESSEN wurde, dass die
     Gewichts-Abstufung allein (600 statt des angeforderten 700, siehe
     dort) zu schwach ist, um bei 13px zuverlaessig als "fett" erkannt
     zu werden. Es gibt keine Stufe UEBER --board-ink im Farbsystem
     (nur board-ink-faint < board-ink-dim < board-ink) — der Kontrast
     wird deshalb von der ANDEREN Seite hergestellt: der Fliesstext
     tritt zurueck, <strong> bleibt bei voller Staerke. */
  color: var(--board-ink-dim);
  padding: 0 24px 0 0;
  min-height: 20px;
  cursor: text;
  overflow-wrap: break-word;
}
.row-description-placeholder { color: var(--board-ink-faint); }
/* Absatz je Zeile (renderMarkdownToHtml() baut EIN <p> je Roh-Zeile,
   auch ohne Leerzeile dazwischen — das ist bewusst dieselbe Regel wie
   im Text-Karten-Vorschaumodus, .text-formatted-preview weiter unten,
   nicht neu erfunden). Letztes Kind ohne unteren Rand, sonst haette
   die Sidebar darunter einen doppelten Abstand (eigener Rand + das
   bestehende gap:12px von .row-sidebar). */
.row-description-view p { margin: 0 0 8px; }
.row-description-view > *:last-child { margin-bottom: 0; }
/* GEAENDERT (2026-08-23, Bauteil 2c): 2b hatte 20/17/15/14/13/13 —
   SimplifyerX' Vorgabe kehrt die Rangfolge jetzt bewusst um:
     H1 DEUTLICH GROESSER als der Row-Sidebar-Titel (30px)
     H2 GLEICH GROSS wie der Row-Sidebar-Titel (30px)
   2b war von der Annahme ausgegangen, eine Markdown-Ueberschrift muesse
   UNTER dem Row-Titel bleiben ("damit die Rangfolge erhalten bleibt") —
   diese Annahme ist damit ausdruecklich verworfen. Der Row-Titel ist
   die Beschriftung der Zeile, eine H1 im Text darf lauter sein.

   GEWAEHLT: 38 / 30 / 24 / 19 / 16 / 14. Abstaende 8/6/5/3/2 — streng
   fallend, jede Stufe von der naechsten unterscheidbar. H6=14px liegt
   bewusst noch UEBER dem Fliesstext (13px, SimplifyerX: "duerfen nicht
   kleiner als der Fliesstext werden"), sodass jede Stufe sich auch
   ohne Vergleich vom Fliesstext abhebt — zusaetzlich zur ohnehin
   anderen Schriftfamilie (Rajdhani 500 vs. JetBrains Mono 400,
   GEMESSEN 2026-08-23).

   GEMESSEN, Zeichen je Zeile in Rajdhani 500 gegen die nutzbare Breite
   (Sidebar minus 24px padding-right):
              120px   140px   180px   200px
     H1 38px     5       6       9      10
     H2 30px     7       8      11      13
   Bei 120px bricht damit praktisch jedes reale Ueberschriftenwort
   mitten im Wort um. overflow-wrap: break-word faengt den Ueberlauf
   technisch ab (GEMESSEN: kein Ueberlauf bei keiner Groesse), aber
   lesbar ist das nicht — siehe Bericht, Empfehlung zu
   ROW_SIDEBAR_W_MIN. Die Untergrenze selbst bleibt hier UNVERAENDERT
   bei 120px; sie zu heben ist SimplifyerX' Entscheidung. */
.row-description-view h1,
.row-description-view h2,
.row-description-view h3,
.row-description-view h4,
.row-description-view h5,
.row-description-view h6 {
  font-family: var(--f-display);
  color: var(--board-ink);
  font-weight: 500;
  line-height: 1.2;
  margin: 8px 0 4px;
  overflow-wrap: break-word;
}
.row-description-view > h1:first-child,
.row-description-view > h2:first-child,
.row-description-view > h3:first-child,
.row-description-view > h4:first-child,
.row-description-view > h5:first-child,
.row-description-view > h6:first-child { margin-top: 0; }
.row-description-view h1 { font-size: 38px; }
.row-description-view h2 { font-size: 30px; }
.row-description-view h3 { font-size: 24px; }
.row-description-view h4 { font-size: 19px; }
.row-description-view h5 { font-size: 16px; }
.row-description-view h6 { font-size: 14px; }
/* GEAENDERT (2026-08-23, Bauteil 2b, BEFUND 1): war font-weight:700.
   GEMESSEN (Canvas-Pixeldichte, robuster als ein Breitenvergleich —
   JetBrains Mono ist Monospace, jedes Zeichen hat per Definition
   dieselbe VORSCHUBBREITE unabhaengig vom Schriftschnitt, ein
   Breitenvergleich waere dort blind): "700 13px" und "600 13px"
   ergaben IDENTISCHE Pixeldichte (179 von je 200x40 Testpixeln,
   getContext('2d')), weil unter _assets/fonts/ nur die Schnitte
   400/500/600 lokal vorliegen (_assets/css/fonts.css) — kein 700, also
   auch keine 700-Datei zu laden, KEINE CDN-Ausnahme dafuer gemacht.
   Der Browser matcht 700 automatisch auf das naechstliegende
   verfuegbare Gewicht (600) OHNE es zusaetzlich synthetisch
   nachzuzeichnen — anders als bei *kursiv* (siehe .row-description-view
   em unten), wo der Browser IMMER ein Schraeg-Transform anwendet, egal
   ob eine echte Kursiv-Datei existiert. Deshalb "geht" kursiv sichtbar
   und fett nicht: kursiv wird erzwungen synthetisiert, fett wird nur
   still ersetzt. font-weight:600 hier macht das jetzt explizit (nutzt
   die tatsaechlich vorhandene Datei, kein stiller Fallback) — reicht
   aber allein nicht (400 vs. effektiv-600 bei 13px ist nur ein
   marginaler Unterschied, GEMESSEN ~11% mehr Pixeldichte). Die Farbe
   traegt deshalb den Hauptteil der Betonung: --board-ink-dim
   (gedaempfter Fliesstext, siehe .row-description-view oben) gegen
   --board-ink (volle Staerke) hier — ein Kontrast, der unabhaengig von
   Schriftverfuegbarkeit funktioniert. */
.row-description-view strong { font-weight: 600; color: var(--board-ink); }
.row-description-view em { font-style: italic; color: var(--board-ink-faint); }
.row-description-view code {
  background: var(--panel-2);
  border: 1px solid var(--line-soft);
  padding: 1px 4px;
  font-family: var(--f-data);
  font-size: 12px;
}
/* GEAENDERT (2026-08-23, Bauteil 2b, BEFUND 3): `ul { list-style: none; }`
   (styles.css, globaler Reset — siehe dort) galt bis hierher ungeprueft
   mit durch, Aufzaehlungspunkte fehlten. `ol` ist von diesem Reset NICHT
   betroffen (nur `ul` steht dort), Ziffern erschienen deshalb bereits
   vorher korrekt. `list-style` jetzt beide Male EXPLIZIT gesetzt, statt
   sich auf den Browser-Default zu verlassen (der bei `ol` zufaellig
   passte, bei `ul` aber vom Reset ueberschrieben wurde).
   padding-left statt margin-left fuers Einruecken (GEAENDERT — Bauteil 2
   hatte margin: 0 0 8px 18px): GEMESSEN, dass der "outside"-Marker bei
   padding-left zuverlaessig INNERHALB der eigenen Listen-Box bleibt,
   nicht davor haengt — bei margin-left waere das vom Zusammenspiel mit
   dem Marker-Rendering des jeweiligen Browsers abhaengig gewesen. Bei
   120px Breite nachgemessen: kein Punkt haengt links aus dem Kasten. */
.row-description-view ul,
.row-description-view ol { margin: 0 0 8px; padding: 0 0 0 18px; }
.row-description-view ul { list-style: disc; }
.row-description-view ol { list-style: decimal; }
.row-description-view li { margin-bottom: 2px; }
.row-description-view blockquote {
  border-left: 2px solid var(--amber);
  margin: 0 0 8px;
  padding: 0 0 0 10px;
  color: var(--board-ink-faint);
}
.row-description-view a {
  color: var(--amber);
  text-decoration: underline;
  /* Eine lange URL als Linktext darf die schmale Spalte nicht
     sprengen — break-word allein reicht bei URLs (keine Leerzeichen)
     oft nicht, deshalb zusaetzlich break-all fuer dieses Element. */
  word-break: break-all;
}

/* GEAENDERT (2026-08-23, Bauteil 1c): Der Griff straddelte bisher die
   Sidebar-Kante symmetrisch (halb innen, halb aussen) — SimplifyerX:
   das wirkte "hineingequetscht", weil er sich exakt die Zone mit
   .row-controls teilte (siehe 1b). Jetzt sitzt er RECHTS von den
   Knoepfen, mit eigenem Luftraum.

   GEMESSEN (2026-08-23): .row-controls ueberragt die Sidebar-Kante um
   konstant 23px (right:-8px + margin-right:-15px, unveraendert seit
   1b, px-basiert und damit unabhaengig von der Sidebar-Breite). Der
   Griff beginnt 6px dahinter, ist 9px breit (derselbe Mindestwert wie
   die Hitbox von .col-resize) — endet also bei 23+6+9 = 38px hinter
   der Kante. WICHTIG, falls .row-controls' Ueberstand sich je aendert:
   dieser Wert (-38px) haengt direkt daran und muss dann mitgezogen
   werden — keine automatische Kopplung, bewusst als fester Wert wie
   der Rest dieser Positionierung.

   GEMESSEN, wo diese 38px landen, je nach --gap-set (der Flex-Abstand
   zwischen Sidebar und erstem SET, in dem der Griff jetzt liegt):
     Small  (24px, GAP_SET_MIN 20px liegt noch tiefer) — reicht NICHT.
            Schon .row-controls' 23px allein fuellen die 24px fast
            vollstaendig; fuer Luft+Griff bleibt praktisch nichts, der
            Griff laege sichtbar auf dem ersten SET. Nicht geloest,
            hier bewusst nicht erzwungen — siehe Bericht.
     Medium (50px, Vorgabe) — 27px Luft zwischen .row-controls und dem
            ersten SET, der Griff (38-23=15px benoetigt) passt bequem.
     Large  (80px) — 57px Luft, reichlich Platz.
   Der Griff bleibt damit ein Kompromiss fuer Medium/Large, nicht fuer
   jede moegliche Board-Einstellung — siehe Bericht.

   Bewusst dieselbe Bauart wie .col-resize (unsichtbare Hitbox im
   Zwischenraum, sichtbare 3px-Linie per ::after, Akzentfarbe beim
   Hover). Anders als .col-resize verstellt dieser Griff ALLE
   Row-Sidebars auf einmal, siehe bindRowSidebarResize() in app.js. */
.row-sidebar-resize {
  position: absolute;
  top: 0; bottom: 0;
  right: -38px;
  width: 9px;
  cursor: col-resize;
  z-index: 10;
  touch-action: none;
}
.row-sidebar-resize::after {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 3px;
  background: transparent;
  transition: background .15s;
}
.row-sidebar-resize:hover::after,
body.row-sidebar-resizing .row-sidebar-resize::after { background: var(--amber); }
/* Waehrend des Ziehens keine Textauswahl im ganzen Board — der Zeiger
   wandert dabei zwangslaeufig ueber Titel und Beschreibungsfelder. */
body.row-sidebar-resizing { user-select: none; }

.row-controls {
  position: absolute;
  top: 40px; right: -8px;
  margin-right: -15px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* NEU (2026-08-23, Bauteil 1b), STAND 1c: z-index 11 UEBER dem
     Ziehgriff (.row-sidebar-resize, z-index:10). In 1b noetig, weil
     der Griff damals dieselbe Zone wie diese Knoepfe straddelte und
     sie verdeckte (GEMESSEN per elementFromPoint()). Seit 1c liegt der
     Griff rechts von den Knoepfen (siehe .row-sidebar-resize) und
     ueberlappt sie an Medium/Large-Gaps gar nicht mehr — der z-index
     bleibt trotzdem stehen: er schadet nicht, und er schuetzt, falls
     eine kuenftige Aenderung die beiden wieder in dieselbe Zone rueckt
     (z. B. eine engere Positionierung bei kleinem --gap-set, siehe
     Bericht). GLEICHES MUSTER wie der bereits akzeptierte Fall
     .group-add (z-index:11) ueber .group-resize (z-index:10). */
  z-index: 11;
}
.row-ctrl-btn {
  /* GEAENDERT (2026-08-12, Launch-Bereitschaft): zurueck auf den
     Styleguide-Wert vom 18.07. (fbd4dd6) — 46px kam am 20.07. (69a73b0)
     ohne Begruendung in einem Sammel-Commit ("diverse CSS-Anpassungen"),
     kein Kommentar dazu im Diff. Bringt .row-ctrl-btn svg zugleich in
     Einklang mit .row-ctrl-danger svg, das die ganze Zeit bei 18px
     stand — beide Icons derselben Zeile waren seit dem 20.07.
     unterschiedlich gross. */
  width: 32px; height: 32px;
  gap: 1px;
  display: grid; place-items: center;
  background: none;
  border-radius: 0;
  border: 0px solid var(--line);
  color: var(--board-ink-faint);
  font-size: 9px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.row-ctrl-btn svg { width: 18px; height: 18px; }
.row-ctrl-danger svg { width: 18px; height: 18px; }
.row-ctrl-btn:hover { color: var(--amber); }
.row-ctrl-danger:hover { color: var(--red, #d04040); }

/* ── Toolbar toggle: columns + ratio controls hidden by default ── */
.assets-toolbar,
.carousel-toolbar,
.compare-toolbar,
.gridset-generate-row {
  display: none;
}
.panel.toolbar-open .assets-toolbar,
.panel.toolbar-open .carousel-toolbar,
.panel.toolbar-open .compare-toolbar,
.panel.toolbar-open .gridset-generate-row {
  display: flex;
}
.toolbar-toggle-btn svg { width: 13px; height: 13px; }
.panel.toolbar-open .toolbar-toggle-btn { color: var(--board-ink-faint); }

/* ── Unified group context menu: text left, icon right ──── */
.group-unified-menu button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.group-unified-menu button svg {
  width: 14px; height: 14px;
  flex-shrink: 0;
  color: var(--ink-faint);
}
.group-unified-menu button:hover svg { color: var(--amber); }
/* NEU (2026-08-11, Bereich F): "SET to Preset" fuer Nicht-Pro gesperrt
   (siehe getGroupMenu() in app.js) — Opacity/Cursor nach demselben
   Muster wie die uebrigen :disabled-Buttons im Projekt (z.B.
   .lp-assets-bulk-btn:disabled, .settings-new-field-locked). */
.group-unified-menu button:disabled { opacity: .5; cursor: not-allowed; }
.group-unified-menu button:disabled:hover { color: var(--ink-dim); }
.group-unified-menu button:disabled:hover svg { color: var(--ink-faint); }

/* ── SET Style popover: nur 3 Icons nebeneinander (Border/Background/
   None), kompakter als das große SET-Kontextmenü ── */
.group-style-menu.open {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.group-style-menu button {
  padding: 8px 12px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.group-style-menu svg { width: 12px; height: 12px; color: var(--ink-faint); }
.group-style-menu button:hover svg { color: var(--amber); }
.group-unified-menu .menu-danger:hover { color: var(--red, #d04040); }
.group-unified-menu .menu-danger:hover svg { color: var(--red, #d04040); }
.panel-menu-sep {
  border: none;
  border-top: 1px solid var(--line-soft);
  margin: 3px 0;
}

/* ── TopNav breadcrumb: Row / Group live context ─────────── */
.topnav-bc-item {
  cursor: pointer;
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--ink-dim);
  transition: color .12s;
}
.topnav-bc-item:hover { color: var(--amber); }

/* ── Asset file-info line: "2.4 MB | PNG | 2024-03-15 | 2048×1536" ── */
.lp-asset-file-info {
  font-family: var(--f-data);
  font-size: 8.5px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* NEU: an den unteren Rand der Zeile andocken (.lp-asset-info ist jetzt
     eine Flex-Spalte, siehe oben) statt direkt unter .lp-asset-meta zu
     kleben — nutzt den durch das größere Thumbnail entstandenen Freiraum. */
  margin-top: auto;
  padding-top: 2px;
}
/* Detail panel: file-info line above the thumbnail */
.rp-file-info {
  font-family: var(--f-data);
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  padding: 10px 0px 0px;
  border-bottom: 0px solid var(--line-soft);
  border-top: 1px solid var(--line-soft);
  margin-bottom: 0px;
  word-break: break-all;
}

/* ── Detail-Panel: Abstammung (Phase 4) ───────────────────────
   "Original" (woher stammt dieses Asset) und "Copies" (was ist daraus
   entstanden). Erscheint nur, wenn es tatsächlich eine Abstammung gibt —
   ein gewöhnliches Asset zeigt hier gar nichts, damit das Panel nicht
   mit leeren Kästen zugestellt wird. Siehe
   docs/setzz-asset-uniqueness-notes.md. */
/* NEU (2026-08-19, "Detail-Panel neu ordnen"): gemeinsamer Zuklapp-
   Abschnitt fuer "Used in" + "References". Bauform bewusst wie
   .lp-bs-section (BOARD SETTINGS): Kopf als Knopf, Chevron dreht sich,
   Koerper per display geschaltet — dasselbe Muster, das der Nutzer aus
   dem BOARD-SETTINGS-Panel kennt.
   GEÄNDERT ("fünf Nachbesserungen", Punkt 3, selber Tag): sitzt jetzt
   zwischen Vorschau/Name und den Eingabefeldern statt am Ende (siehe
   content in app.js) — Standard bleibt trotzdem zu. Eigene
   var(--panel)-Flaeche statt nur einer Trennlinie: an dieser Position,
   zwischen zwei anderen Bloecken statt am Panel-Ende, braucht der
   Abschnitt eine eigene Kontur, um sich abzusetzen — sonst liesse er
   sich beim ueberfliegen kaum vom folgenden .rp-detail-group
   unterscheiden.
   GEÄNDERT ("acht Korrekturen", Punkt 2, selber Tag): war 10px, jetzt
   4px auf beiden Seiten — kollabiert mit .rp-asset-groups margin:0
   (oben) bzw. .rp-detail-groups margin-top:4px (unten, siehe dort) zu
   durchgängig 4px an allen drei Übergängen. */
.rp-lineage-section {
  background: var(--panel);
  margin: 4px 0;
}
/* GEÄNDERT (2026-08-20, "drei Korrekturen"; Punkt 2): links/rechts von
   8px auf 2px — an .lp-bs-section-header (BOARD SETTINGS) gemessen und
   übernommen, siehe dortiger Wert (padding: 8px 2px 2px). Oben/unten
   bewusst unverändert (10px) — nur "links und rechts" war die Vorgabe,
   und die Zeile trägt hier zusätzlich die Zähler-Badges, die BOARD
   SETTINGS' Titelzeile nicht hat; anderes vertikales Gewicht ist kein
   Bruch.
   NACHTRAG (2026-08-20): 2px war zu wenig — der BOARD-SETTINGS-Wert
   passt hier nicht, das Detail-Panel hat andere Ränder. Nutzer-Vorgabe:
   10px 12px (kein gemessener Bezug mehr zu .lp-bs-section-header). */
.rp-lineage-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  color: var(--ink-dim);
}
/* Im leeren Fall ist der Kopf ein <div>, kein Knopf (siehe
   lineageHasContent in app.js) — dann auch nicht als klickbar zeigen. */
.rp-lineage-section-fixed .rp-lineage-toggle { cursor: default; }
.rp-lineage-toggle:hover { color: var(--ink); }
/* GEÄNDERT (2026-08-20, "drei Korrekturen", Punkt 1): war var(--f-data)
   10px uppercase mit .08em Laufweite — wirkte neben den BOARD-SETTINGS-
   Titeln ("Choose a THEME", "BOARD Colors", "BOARD Paddings") zu duenn
   und zu weit auseinandergezogen, weil es eine andere Rolle imitierte
   (Datenzeile) statt der Abschnittstitel-Rolle, die es tatsaechlich
   hat. WICHTIG beim Messen entdeckt: .lp-bs-section-header selbst
   deklariert zwar font-size:13px ohne font-weight, verliert damit aber
   die Kaskade gegen die spezifischere Regel ".left-panel button,
   .floating-panel button" (font-size:14px, font-weight:500) — dort
   gemessen (getComputedStyle) kommen tatsaechlich 14px/500 rein, nicht
   die im Quelltext sichtbaren 13px. Da das Detail-Panel weder
   .left-panel noch .floating-panel ist, bekommt dieser Titel jene Regel
   nicht automatisch — deshalb hier die GERENDERTEN Werte explizit
   uebernommen: var(--f-display), 14px, 500, natuerliche Gross-/
   Kleinschreibung, kein letter-spacing. KEINE gemeinsame Klasse:
   .lp-bs-section-header sitzt in BOARD SETTINGS direkt auf dem <button>
   (Layout-Eigenschaften wie padding/display:flex/justify-content fuer
   die GANZE Kopfzeile inklusive), hier traegt das Pendant-Element
   (.rp-lineage-toggle) eine eigene Kopfzeile mit einem dritten Kind (den
   Zaehler-Badges, margin-left:auto statt space-between) — dieselbe
   Klasse auf die Titel-Spanne haette die Button-Praeambel-Eigenschaften
   (padding u.a.) ein zweites Mal ueber die bereits vorhandenen von
   .rp-lineage-toggle gelegt. Deshalb nur die Typo-Werte hier
   uebernommen, nicht die Klasse selbst. */
.rp-lineage-toggle-title {
  font-family: var(--f-display);
  font-size: 14px;
  font-weight: 500;
}
.rp-lineage-badges { display: flex; align-items: center; gap: 4px; margin-left: auto; }
/* GEÄNDERT (2026-08-19, "fünf Nachbesserungen", Punkt 4): war eckig,
   Rahmen statt Fläche. SimplifyerX-Vorgabe: rund, mit Hintergrund, Zahl
   in Kontrastfarbe dazu — UND dieselbe Bauform wiederverwenden, keine
   neue. Geprüft: zwei Vorbilder existieren, beide gedämpft-neutral statt
   kontrastreich (.gridset-slot-count: var(--panel)/var(--ink-dim);
   .lp-assets-trash-badge, der "Trash-Zähler": var(--line-soft)/
   var(--ink-faint) — beide bewusst 2026-08-12 von Rot/Weiß auf neutral
   umgestellt, "zeigt nur eine Anzahl, keine Warnung"). Fläche+Kontrast
   nach Vorgabe fordert etwas anderes: Bauform (rund, min-width/height/
   radius) von .gridset-slot-count übernommen, Farbpaar vom bestehenden
   Aktiv-Zustand-Idiom .lp-bs-seg-btn.active (background: var(--amber);
   color: var(--bg);).

   KORRIGIERT nach LIVE-Messung, nicht nach Schätzung: der erste Versuch
   dieser Zeile setzte background: var(--amber); color: var(--bg); —
   exakt wie .lp-bs-seg-btn.active — und wurde mit "7.14:1 dark, 3.06:1
   light" kommentiert, OHNE das tatsächlich gemessen zu haben. Live
   gemessen ergab sich fürs Light-Theme 1.93:1 — derselbe schwache Wert
   wie beim Checkbox-Befund vom selben Tag, aus demselben Grund: --bg im
   Light-Theme (#e6e6e6) ist ein HELLES Grau, gegen das mittelhelle
   Amber (#C8A23F) kaum trägt. Das betrifft rechnerisch auch das
   Vorbild .lp-bs-seg-btn.active selbst (dort bislang unbeanstandet,
   vermutlich weil der Buchstabe "S/M/L" auf 30px Knopf weniger auffällt
   als eine Zahl auf einer freistehenden Pille) — hier aber bewusst
   NICHT übernommen, sondern wie beim Checkbox-Fix auf var(--amber-hi)
   gewechselt: derselbe Token, der bereits pro Theme in die
   kontrastreichere Richtung zeigt. GEMESSEN (2026-08-19): 7.86:1 im
   Dark-, 3.20:1 im Light-Theme — beide über der 3:1-Schwelle für
   gefüllte Flächen, deutlich vor --amber. KEINE Unterscheidung
   zwischen "Used in" und "References" mehr (vorher: Amber-Rahmen vs.
   gedämpfter Rahmen) — die Vorgabe fragt nach einer einheitlichen
   Bauform für beide Zähler. */
/* GEÄNDERT (2026-08-19, "acht Korrekturen", Punkt 3): WEISS WURDE
   VERSUCHT UND WIEDER VERWORFEN — bitte nicht erneut probieren, ohne
   diesen Befund zu lesen. SimplifyerX-Vorgabe war zunächst "fetter und
   weiß" statt des themeabhängigen var(--bg). Kontrast gegen
   var(--amber-hi) GEMESSEN (2026-08-19, per Formel und live
   gegengeprüft): Weiß erreichte NUR 1.85:1 im Dark-Theme (amber-hi
   dort hell, #ddbb5f — Weiß auf Hellgelb ist die schwache Richtung)
   und 3.99:1 im Light-Theme — beide unter der 4.5:1-Schwelle für
   Fliesstext, der Dark-Wert praktisch unlesbar und schlechter als der
   vorherige Zustand. Nach diesem Befund zurück auf var(--bg) entschieden
   (SimplifyerX, selber Tag): pro Theme der Gegenpol der Palette (dunkel
   im Dark-, hell im Light-Theme), GEMESSEN 7.86:1 dark / 3.20:1 light —
   deutlich vor Weiß in beide Richtungen. font-weight:700 bleibt, das war
   unabhängig vom Farbbefund gewünscht. */
.rp-lineage-badge {
  min-width: 18px; height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--f-data);
  font-size: 9px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--amber-hi);
  color: var(--bg);
}
.rp-lineage-chevron { display: flex; align-items: center; transition: transform .15s; }
.rp-lineage-chevron svg { width: 12px; height: 12px; }
.rp-lineage-section.open .rp-lineage-chevron { transform: rotate(180deg); }
.rp-lineage-section-body { display: none; padding: 0 8px 8px; }
.rp-lineage-section.open .rp-lineage-section-body { display: block; }
/* Die erste .rp-lineage im Abschnitt braucht ihre eigene Trennlinie
   nicht mehr — der Abschnittskopf traegt sie bereits. */
.rp-lineage-section-body > .rp-lineage:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.rp-lineage {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.rp-lineage-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.rp-lineage-label {
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  gap: 6px;
}
.rp-lineage-count {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 8px;
  background: var(--line-soft);
  color: var(--ink-faint);
}
.rp-lineage-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px;
  border: 1px solid var(--line-soft);
  background: transparent;
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: border-color .12s, background .12s;
}
.rp-lineage-item:hover {
  border-color: var(--amber, var(--ink-faint));
  background: var(--line-soft);
}
.rp-lineage-item-compact {
  width: auto;
  padding: 3px;
}
.rp-lineage-thumb {
  width: 34px;
  height: 34px;
  object-fit: cover;
  flex: 0 0 34px;
  display: block;
  background: var(--line-soft);   /* NEU (2026-08-12): geladene Bilder hatten gar keinen Hintergrund */
}
.rp-lineage-thumb-empty {
  background: var(--line-soft);
}
.rp-lineage-name {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Kopien nebeneinander, umbrechend — bei vielen Kopien soll die Reihe
   wachsen statt das Panel horizontal zu sprengen. */
.rp-lineage-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.rp-lineage-missing {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
}

/* ── Referenzen (2026-08-06, docs/setzz-asset-references-notes.md) ──
   Bewusst EIGENE Klasse statt .rp-lineage-thumbs mitzubenutzen: nur
   dieser Block braucht den Innenscroll. Bis zu 50 Referenzen sind
   vorgesehen; bei 34px-Miniaturen + 4px Abstand passen im 350px
   breiten Panel rund 7 pro Reihe, 50 Stueck waeren also ~8 Reihen und
   wuerden alles darunter aus dem Blick schieben. Die Kopien-Reihe des
   Abstammungs-Blocks soll dagegen ausdruecklich weiter frei wachsen. */
.rp-references-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-height: 160px;
  overflow-y: auto;
  scrollbar-width: thin;
}
/* Miniatur + Entfernen-Kreuz als eine Einheit, damit das Kreuz beim
   Umbruch nicht von seinem Bild getrennt wird. */
.rp-ref-item { position: relative; display: inline-flex; }
.rp-ref-remove {
  position: absolute;
  top: -3px; right: -3px;
  width: 14px; height: 14px;
  display: none;
  align-items: center; justify-content: center;
  background: var(--bg-deep);
  border: 1px solid var(--line);
  color: var(--ink-dim);
  font-size: 9px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}
.rp-ref-item:hover .rp-ref-remove { display: inline-flex; }
.rp-ref-remove:hover { color: var(--amber); border-color: var(--amber); }
/* Referenz, deren Ziel-Asset geloescht wurde — sichtbar gelassen,
   damit die Luecke im Herkunfts-Nachweis auffaellt. */
.rp-ref-missing { outline: 1px dashed var(--ink-faint); outline-offset: -1px; }
.rp-ref-empty {
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-faint);
  font-style: italic;
}
/* GEAENDERT (2026-08-13): hatte ebenfalls einen Amber-Hover (siehe
   .md-import-upload-btn). Jetzt .btn .btn-sm im Markup.
   GEAENDERT (2026-08-13, zweite Runde): Variante auf .btn-extra
   umgestellt (Markup) — der gefuellte Balken war fuer eine
   Nebenaktion in einem leeren Abschnitt ("No reference files
   recorded") zu dominant. .rp-lineage-row ist flex-direction:column,
   das Kind streckt sich per Default (align-items:stretch) auf volle
   Breite — ohne Gegenregel waere der Rahmen-Knopf wieder derselbe
   Balken, nur mit Umrandung. align-self:flex-start gibt ihm seine
   natuerliche Breite zurueck. */
.rp-ref-add-btn { align-self: flex-start; }
/* "Used in N" — Chips der CARDS, die dieses Asset zeigen (Phase 3) */
.rp-usage-panels {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.rp-usage-chip {
  font-family: var(--f-data);
  font-size: 10px;
  padding: 2px 6px;
  border: 1px solid var(--line-soft);
  color: var(--ink-faint);
  white-space: nowrap;
  /* NEU (2026-08-19, "Detail-Panel neu ordnen"): CARD-Titel sind freier
     Text und die Kachel hatte weder max-width noch Ellipsis — bei langen
     Titeln lief sie aus dem Panel heraus. Gemessen (2026-08-19): die
     Breite waechst linear mit 14px + 6px je Zeichen, ab ~38 Zeichen
     ueberlief sie schon die volle Spalte bei Panel-Minimalbreite 280px
     (245px nutzbar). 140px = ~21 Zeichen, passt damit auch bei 280px
     sicher und laesst bei Standardbreite zwei Kacheln nebeneinander.
     Den vollen Titel traegt das title-Attribut (siehe usageBlock in
     app.js) — ohne das waere nach dem Abschneiden nicht mehr erkennbar,
     welche Karte gemeint ist. */
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Verwendungs-Zähler in der Assets-Liste (Phase 3). Bewusst nur sichtbar,
   wenn das Asset mindestens einmal verwendet wird — siehe Begründung in
   populateAssetsPanel(). */
.lp-asset-uses {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  font-size: 9px;
  letter-spacing: .04em;
  border-radius: 8px;
  background: var(--line-soft);
  color: var(--ink-faint);
  vertical-align: 1px;
}

/* ── MD-CARD (2026-07, docs/setzz-md-card-notes.md) ──────────────────
   EasyMDE bringt seine eigene Optik mit (.EasyMDEContainer) — hier nur
   der Rahmen (Card-Body, eigene Mini-Toolbar für Import/Export/Details,
   Höhe/Scroll-Verhalten) sowie ein paar gezielte Overrides, damit sich
   der Editor optisch ins bestehende Panel-Design einfügt (Schrift,
   Rahmenfarben, Light/Dark). */
.md-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* NEU: ein-/ausblendbar wie .text-view-toggle bei der Text-Card — Standard
   zu, eingeblendet über denselben Kopf-Button (toolbar-toggle-btn), siehe
   TOOLBAR_PANEL_TYPES (app.js) und hydrateMd() (panels.js). */
.md-card-toolbar-extra {
  display: none;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.panel.toolbar-open .md-card-toolbar-extra { display: flex; }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover-Fallback), jetzt .btn/.btn-sm
   fuer alle drei Knoepfe (Details/Import/Export) — vereinheitlicht
   nebenbei die bisher zufaellig unterschiedliche Groesse zwischen
   .md-card-toolbar-extra (11px/4px 8px) und .md-status-actions
   (10px/3px 7px, siehe entfallene Override-Regel unten). Display:flex
   + gap bleiben strukturell (Icon-vor-Text-Layout). .md-card-btn-icon
   ist mit der Padding-Vereinheitlichung wirkungslos geworden, Regel
   und Markup-Klasse entfernt. */
.md-card-btn { display: inline-flex; align-items: center; gap: 5px; }
/* NEU (2026-08-10, Bugfix): fehlende Größenangabe — die MD-Toolbar-Icons
   (heute icon("ctrl-md-import")/("ctrl-md-export"), bis Etappe 6 die
   Fabrik MD_TOOLBAR_ICON) und META_ICON liefern rohe
   <svg viewBox="…">-Fragmente ohne eigenes
   width/height (Konvention in diesem Projekt: der globale Reset
   `img, svg { display: block; }` setzt bewusst nur das display, die
   Größe kommt immer von einer eigenen Selektor-Regel wie dieser hier).
   Ohne sie rendert das svg bei 0×0 — betrifft alle drei Tasten dieser
   Klasse (Details, Import, Export). */
.md-card-btn svg { width: 12px; height: 12px; }
.md-card-status {
  font-family: var(--f-label);
  font-size: 10px;
  color: var(--ink-faint);
  min-width: 34px;
  margin-left: auto;
}
/* NEU (Nutzer-Feedback): MD/Formatted-Umschalter, rechts orientiert wie
   .text-view-toggle bei der Text-Card (gleiche Optik/Maße wiederverwendet).
   margin-left:auto sitzt bereits auf .md-card-status davor — dieser Block
   folgt einfach im Fluss und landet dadurch mit am rechten Rand. */
.md-view-toggle {
  display: flex;
  gap: 4px;
}
.md-view-btn {
  min-width: 26px; height: 22px;
  padding: 0 8px;
  display: grid; place-items: center;
  background: none;
  border: 0;
  color: var(--board-ink-faint, var(--ink-faint));
  opacity: 0.6;
  font-family: var(--f-data, var(--f-panel));
  font-size: 11px;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.md-view-btn:hover { color: var(--ink); }
.md-view-btn.active { color: var(--board-ink-faint, var(--ink)); background: var(--amber-dim); opacity: 1; }
.md-editor-host {
  position: relative;
  /* GEAENDERT (2026-09-02): war 240px. GEMESSEN: die EasyMDE-Werkzeug-
     leiste ist zweizeilig und 64,5px hoch — bei 120px bleiben also rund
     55px fuer den Text, gut drei Zeilen. Knapp, aber schreibbar; wer
     mehr braucht, zieht die Karte groesser. Der Wert ist eine UNTER-
     grenze: Karten mit eigener, groesserer Hoehe bleiben unberuehrt. */
  min-height: 120px;
}
/* NEU (Nutzer-Feedback): Edit-Icon oben rechts über der Formatted-Preview
   — nur bei Card-Hover UND nur solange die Toggle-Zeile (.md-card-
   toolbar-extra) geschlossen ist (sonst sind Edit/Preview-Buttons dort
   schon sichtbar, das Icon wäre redundant). Ein Klick öffnet die
   Toggle-Zeile und wechselt direkt in den MD-Quelltext-Modus (siehe
   [data-md-preview-edit] in bindMdCard(), panels.js). z-index:20 hebt
   es über .editor-preview-full (EasyMDE, z-index:7, aber in eigenem
   Stacking-Context durch .CodeMirror{z-index:0}) hinaus. */
.md-preview-edit-btn {
  display: none;
  position: absolute;
  top: 8px; right: 8px;
  z-index: 20;
  width: 26px; height: 26px;
  align-items: center; justify-content: center;
  padding: 0;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 6px;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
.md-preview-edit-btn svg { width: 13px; height: 13px; }
.md-preview-edit-btn:hover { color: var(--ink); border-color: var(--amber, var(--ink-faint)); }
.panel:not(.toolbar-open):hover .md-editor-host.md-preview-active .md-preview-edit-btn {
  display: flex;
}
/* EasyMDE-Overrides: an die bestehende Panel-Typografie/Farben anpassen,
   statt EasyMDEs eigenes Weiß/Standard-Schwarz beizubehalten — sonst
   wirkt die Card wie ein Fremdkörper, besonders im Dark Mode. */
/* GEAENDERT (2026-08-24, Teil 3): trug var(--panel) fest, ungetoent —
   die eigentliche Schreibflaeche der MD-Karte, direkt analog zu
   .text-editor bei der Text-Karte, war die einzige Flaeche in dieser
   Runde, die den Farbton nie bekam (nur .editor-preview daneben wurde
   in Teil 2 gefixt). Sichtbar als graues Rechteck mitten in der
   getoenten Karte, GEMESSEN per Bildschirmfoto. .editor-statusbar
   darunter hat selbst KEINEN eigenen Hintergrund (bis zu .panel hoch
   durchgehend transparent) und zeigt den Bruch deshalb nur MITTELBAR:
   sie sitzt sichtbar neben/unter dieser ungetoenten Flaeche. Fix hier
   behebt beides in einem Schritt, keine eigene Regel fuer die
   Statusleiste noetig. KEINE Verwechslung mit .editor-preview pre
   (Code-Chip-Konvention, bewusst var(--panel-2), bleibt unberuehrt) —
   das hier ist die GESAMTE Schreibflaeche, keine Inline-Code-Markierung. */
.md-editor-host .EasyMDEContainer .CodeMirror {
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  color: var(--ink);
  border: 0px solid var(--line-soft);
  font-family: var(--f-mono, monospace);
  font-size: 13px;
  /* NEU: höhenskalierbar wie das Text-Card-Textarea (.text-editor,
     resize:vertical) — feste height statt "auto" ist Voraussetzung dafür,
     dass der Resize-Griff greift. --md-height wird von hydrateMd()
     (panels.js) aus panel.mdHeight gesetzt, sobald der Nutzer einmal
     manuell skaliert hat (ResizeObserver in mountMdEditor()). */
  height: var(--md-height, 200px);
  min-height: 120px;
  resize: vertical;
  /* BUGFIX (Nutzer-Feedback): overflow:auto hier erzeugte eine ZWEITE,
     redundante Scrollbar zusätzlich zu CodeMirrors eigener interner
     (.CodeMirror-scroll/.CodeMirror-vscrollbar) — das eigentliche
     Scrollen passiert schon innerhalb von CodeMirror, der äußere
     Container braucht nur "hidden", um den überstehenden Rest beim
     Resize abzuschneiden. */
  overflow: hidden;
}
.md-editor-host .editor-toolbar {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  padding: 1px;
  opacity: 1;
}
/* NEU (Nutzer-Feedback): Werkzeugleiste ausblenden, solange die reine
   Lesevorschau aktiv ist (siehe MutationObserver in mountMdEditor(),
   panels.js, der md-preview-active hier auf dem Host togglet). */
.md-editor-host.md-preview-active .editor-toolbar {
  display: none;
}
/* BUGFIX (Nutzer-Feedback): Side-by-Side (\-Icon) aktiviert in EasyMDE
   standardmäßig ZUSÄTZLICH echten Vollbild-Modus (sideBySideFullscreen,
   siehe easymde.min.js) — .editor-toolbar.fullscreen/.CodeMirror-
   fullscreen werden dabei position:fixed über 0/0/0/0, decken also den
   Anzeigebereich randlos ab. 10% Abstand zum ECHTEN Browser-Viewport
   gewünscht — bewusst vw/vh statt Prozent: SETzz' Board läuft in einem
   skalierten/transformierten Zoom-Viewport-Container, der für
   position:fixed zum "containing block" wird, sobald ein Vorfahre eine
   CSS-transform trägt — Prozentwerte wären dann relativ zu DESSEN
   (verzerrter) Box statt zum echten Fenster, was genau die erste,
   fehlgeschlagene Version dieses Fixes zeigte. vw/vh bleiben davon
   unberührt und beziehen sich immer auf das tatsächliche Browser-
   Fenster.
   !important ist hier zwingend nötig (nicht nur wegen Selektor-
   Spezifität): CodeMirror verwaltet bei .CodeMirror-fullscreen selbst
   fortlaufend Inline-Styles am Element (u.a. beim Neuberechnen von
   height/clip-path während Tippen/Resize) und überschreibt dabei ggf.
   das gesamte style-Attribut, wodurch zuvor per JS gesetzte Inline-
   Werte sofort wieder verloren gingen (bereits erfolglos versucht).
   Eine externe Regel MIT !important gewinnt dagegen zuverlässig gegen
   jeden nicht-!important Inline-Style, egal wie oft CodeMirror das
   Attribut neu schreibt.
   Die Werte selbst kommen NICHT aus festen vw/vh (das traf zwar die
   richtige LÄNGE, aber die falsche POSITION — siehe applyMdFullscreenInset()
   in panels.js für die volle Erklärung: .columns-scroll trägt eine
   CSS-transform und wird dadurch zum "containing block", wodurch top/
   left gegen DESSEN unskalierte Box statt gegen den echten Viewport
   aufgelöst werden), sondern aus CSS-Custom-Properties auf <html>, die
   panels.js beim Öffnen des Vollbildmodus exakt zurückrechnet. vw/vh
   bleiben nur als Fallback stehen, falls die JS-Werte (noch) nicht
   gesetzt sind.
   BUGFIX (Nutzer-Feedback, 2. Runde): die Side-by-Side-Vorschau
   (.editor-preview-side) ist ein KOMPLETT EIGENES, unabhängig
   position:fixed-positioniertes Element (rechte Hälfte, siehe
   easymde.min.css: right:0/width:50%/top:50px/bottom:0) — von der
   ersten Fassung dieses Fixes gar nicht erfasst, blieb also randlos am
   echten Fensterrand kleben, während nur die linke Editor-Hälfte
   (.CodeMirror-fullscreen) den Rand bekam. Zusätzlich hatte die erste
   Fassung ihre eigene width-Regel ohne Rücksicht auf
   .CodeMirror-sided{width:50%!important} (easymde.min.css) gesetzt —
   durch höhere Selektor-Spezifität gewann meine Regel und blies den
   Editor auf die VOLLE Inset-Breite auf, statt ihm nur die Hälfte zu
   lassen. Jetzt: eine gemeinsame Inset-Breite (--md-fs-width) wird in
   --md-fs-half-width halbiert, der Editor bekommt sie nur, wenn er
   TATSÄCHLICH im Side-by-Side-Modus ist (.CodeMirror-sided), sonst die
   volle Breite (reiner Vollbildmodus ohne Split) — und die Vorschau
   bekommt dieselbe halbe Breite, positioniert direkt rechts daneben
   (--md-fs-preview-left = --md-fs-left + --md-fs-half-width). */
.md-editor-host .editor-toolbar.fullscreen {
  top: var(--md-fs-top, 10vh) !important;
  left: var(--md-fs-left, 10vw) !important;
  right: auto !important;
  width: var(--md-fs-width, 80vw) !important;
}
.md-editor-host .CodeMirror.CodeMirror-fullscreen {
  top: var(--md-fs-cm-top, calc(10vh + 50px)) !important;
  left: var(--md-fs-left, 10vw) !important;
  right: auto !important;
  bottom: auto !important;
  height: var(--md-fs-cm-height, calc(80vh - 50px)) !important;
}
.md-editor-host .CodeMirror.CodeMirror-fullscreen:not(.CodeMirror-sided) {
  width: var(--md-fs-width, 80vw) !important;
}
.md-editor-host .CodeMirror.CodeMirror-fullscreen.CodeMirror-sided {
  width: var(--md-fs-half-width, 40vw) !important;
}
.md-editor-host .editor-preview-side {
  top: var(--md-fs-cm-top, calc(10vh + 50px)) !important;
  left: var(--md-fs-preview-left, 55vw) !important;
  right: auto !important;
  bottom: auto !important;
  width: var(--md-fs-half-width, 40vw) !important;
  height: var(--md-fs-cm-height, calc(80vh - 50px)) !important;
}
.md-editor-host .editor-toolbar a,
.md-editor-host .editor-toolbar button {
  color: var(--ink-faint) !important;
}
.md-editor-host .editor-toolbar a:hover,
.md-editor-host .editor-toolbar a.active,
.md-editor-host .editor-toolbar button:hover,
.md-editor-host .editor-toolbar button.active {
  background: var(--line-soft);
  border-color: transparent;
}
.md-editor-host .editor-toolbar i.separator {
  border-left: 1px solid var(--line-soft);
  border-right: none;
}
/* GEAENDERT (2026-08-24, Teil 2): Karten-Farbton traegt jetzt auf der
   GESAMTFLAECHE der MD-Vorschau (Basis war bereits var(--panel), nicht
   --bg-deep -- bei Farbton 0 sah das schon immer richtig aus, erst bei
   gesetztem Farbton wich es ab, da kein Verlauf ergaenzt wurde). Der
   Codeblock DARIN (.editor-preview pre, weiter unten) bleibt bewusst
   bei var(--panel-2) -- Entscheidung aus der vorigen Runde, unveraendert:
   kein Codeblock-Muster in diesem Projekt folgt der Karten-Toenung. */
.md-editor-host .editor-preview,
.md-editor-host .editor-preview-side {
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  color: var(--ink);
  padding: 20px;
}
/* NEU (2026-08-24): easymde.min.css (Fremd-Bibliothek, unveraendert
   gelassen) setzt `.editor-preview pre{background:#eee}` — ein FESTER
   Wert, der im Dunkel-Theme als heller Fleck auf dem Codeblock steht.
   GEPRUEFT: kommt aus EasyMDE selbst, nicht aus einer SETZZ-eigenen
   Regel — deshalb hier UEBERSCHRIEBEN (hoehere Selektor-Spezifitaet,
   0,2,0 gegen EasyMDEs 0,1,0), nicht in der Vendor-Datei geaendert.
   `code`/`blockquote` haben in .editor-preview KEINE eigene Flaeche
   (GEPRUEFT, easymde.min.css enthaelt keine Regel dafuer) — nichts zu
   tun dort. var(--panel-2) statt Karten-Farbton: ein Codeblock DARF
   sich vom Fliesstext abheben (das ist seine Aufgabe), und genau dieses
   Muster — ein neutraler, theme-eigener Chip statt Karten-Tonung —
   benutzt dieselbe Codebasis bereits an zwei Stellen (Text-Karte
   `.text-formatted-preview code`, Ausgabe `.ex-text-formatted code`),
   beide mit demselben theme-eigenen Zweitton statt Karten-Farbton. */
/* GEAENDERT (2026-09-02): die Flaeche steht nicht mehr hier, sondern
   zusammen mit allen uebrigen Codeblock-Eigenschaften im gemeinsamen
   Block "CODEBLOECKE, VIER ORTE, ZWEI ERZEUGER" weiter unten — zwei
   Regeln, die dieselbe Eigenschaft auf denselben Selektor setzen, waeren
   genau die Doppelung, die irgendwann auseinanderlaeuft. Die Begruendung
   von 2026-08-24 gilt unveraendert weiter und steht dort. */
/* BUGFIX (Nutzer-Feedback): der globale Reset `* { margin: 0; }`
   (styles.css, ganz oben) setzt auch die Absatz-/Block-Margins
   INNERHALB der gerenderten Preview auf 0 — im reinen Textmodus (dem
   CodeMirror-Editor) sind Leerzeilen zwischen Absätzen dagegen echte
   Zeilenumbrüche im Text und daher vom Reset unberührt. Ohne diese
   Regel wirken Absätze in der Preview optisch zusammenhanglos
   zusammengeklebt, obwohl der Quelltext Leerzeilen enthält. */
.md-editor-host .editor-preview p,
.md-editor-host .editor-preview-side p,
.md-editor-host .editor-preview ul,
.md-editor-host .editor-preview-side ul,
.md-editor-host .editor-preview ol,
.md-editor-host .editor-preview-side ol,
.md-editor-host .editor-preview blockquote,
.md-editor-host .editor-preview-side blockquote,
.md-editor-host .editor-preview pre,
.md-editor-host .editor-preview-side pre,
.md-editor-host .editor-preview table,
.md-editor-host .editor-preview-side table {
  margin: 0 0 16px;
}
/* ══ ANZEIGE DER MD-KARTE FOLGT DER TEXT-KARTE (2026-09-02) ═══════════
   Beide Karten zeigen denselben Markdown-Quelltext — er soll auch gleich
   aussehen. Vorlage ist die TEXT-Karte: ihre Ueberschriften tragen mit
   `--f-display` bereits die Schrift des Werkzeugs, waehrend die MD-Karte
   hier nur EasyMDEs Vorgabe zeigte (generisches `monospace`, Groessen aus
   em-Vorgaben des Browsers: 26/19,5/15,2/13/10,8/8,7px).

   ⚠️ NUR DIE VORSCHAU, NICHT DER EDITOR. Der Editor (.CodeMirror) behaelt
   ausdruecklich seine Festbreitenschrift: Markdown ist der eine Ort, an
   dem man Zeichen zaehlt — Trennzeilen, Sternchen, senkrechte Striche.
   In einer Tabelle stehen die Striche nur in einer Festbreitenschrift
   untereinander, in einer Proportionalschrift verrutschen sie. Dieselbe
   Trennung wie bei den Hintergruenden: Editor-Feld behaelt, Anzeige
   aendert sich.

   Fliesstext, Listen, Zitate, Tabellenzellen bleiben ebenfalls in der
   Datenschrift — genau wie in der Text-Karte, deren Fliesstext gemessen
   JetBrains Mono ist. Uebernommen wird also die Ueberschriften-Schrift,
   nicht die gesamte Typografie. Code behaelt Mono ohnehin (eigene Regel
   weiter unten). */
.md-editor-host .editor-preview,
.md-editor-host .editor-preview-side {
  font-family: var(--f-data);
  font-size: 12px;
}
.md-editor-host .editor-preview h1,
.md-editor-host .editor-preview-side h1,
.md-editor-host .editor-preview h2,
.md-editor-host .editor-preview-side h2,
.md-editor-host .editor-preview h3,
.md-editor-host .editor-preview-side h3,
.md-editor-host .editor-preview h4,
.md-editor-host .editor-preview-side h4,
.md-editor-host .editor-preview h5,
.md-editor-host .editor-preview-side h5,
.md-editor-host .editor-preview h6,
.md-editor-host .editor-preview-side h6 {
  font-family: var(--f-display);
  color: var(--ink);
  /* Dieselben Werte wie .text-formatted-preview — siehe Begruendung dort. */
  margin: 16px 0 8px;
  line-height: 1.25;
}
.md-editor-host .editor-preview h1,
.md-editor-host .editor-preview-side h1 { font-size: 26px; }
.md-editor-host .editor-preview h2,
.md-editor-host .editor-preview-side h2 { font-size: 21px; }
.md-editor-host .editor-preview h3,
.md-editor-host .editor-preview-side h3 { font-size: 17px; }
.md-editor-host .editor-preview h4,
.md-editor-host .editor-preview-side h4 { font-size: 15px; }
.md-editor-host .editor-preview h5,
.md-editor-host .editor-preview-side h5 { font-size: 13px; }
.md-editor-host .editor-preview h6,
.md-editor-host .editor-preview-side h6 { font-size: 12px; }
/* Code behaelt die Festbreitenschrift — ein Codebeispiel in einer
   Proportionalschrift ist unlesbar. Gilt fuer Bloecke UND Inline-Code. */
.md-editor-host .editor-preview code,
.md-editor-host .editor-preview pre,
.md-editor-host .editor-preview-side code,
.md-editor-host .editor-preview-side pre {
  font-family: var(--f-data);
}
/* Inline-Code eine Spur kleiner als der Fliesstext — uebernommen aus
   .text-formatted-preview code (11px), damit auch dieses Detail in
   beiden Karten gleich aussieht. Der Codeblock (pre) behaelt die volle
   Groesse. */
.md-editor-host .editor-preview code,
.md-editor-host .editor-preview-side code { font-size: 11px; }
.md-editor-host .editor-preview pre code,
.md-editor-host .editor-preview-side pre code { font-size: inherit; }
.md-editor-host .editor-preview li,
.md-editor-host .editor-preview-side li {
  margin: 0 0 4px;
}
/* ══ TABELLEN IN DER MD-VORSCHAU (2026-09-02) ═══════════════════════
   GEMESSEN (SimplifyerX, Bildschirmfoto): die Tabelle kam als volles
   Gitter — Rahmen um jede Zelle. Das stammt NICHT aus diesem Projekt,
   sondern aus `easymde.min.css`:
     .editor-preview table td,.editor-preview table th{border:1px solid #ddd;padding:5px}
   Fremdes CSS, das ein Bibliotheks-Update ueberschreibt — deshalb hier
   UEBERSCHRIEBEN statt dort geaendert. Die Selektoren tragen mit
   `.md-editor-host` ein Element mehr und gewinnen damit ohne !important.

   Gewuenscht: nur waagerechte Linien, je eine oben an jeder Zeile.
   Ausdruecklich KEINE Zebra-Streifen (Entscheidung SimplifyerX: "wird zu
   dominant sein") — die `list`-Karte hat sie, hier bewusst nicht.

   Linienstaerke NICHT neu gewaehlt, sondern an der vergleichbaren
   Stelle abgelesen: `.list-th` (Kopftrennlinie der LIST-Karte) nutzt
   `1px solid var(--line)`. --line ist theme-abhaengig definiert
   (#494949 dunkel / #e2e2e2 hell) und zieht damit bei Theme-Wechsel und
   Board-Farbton mit, ohne eigenen Wert.

   Innenabstand: EasyMDE gab 5px rundum. Ohne senkrechte Linien traegt
   allein der Abstand die Spaltentrennung, deshalb rechts deutlich mehr
   (20px) und links 0 — so steht die erste Spalte buendig unter dem
   uebrigen Text, statt eingerueckt zu wirken. Die letzte Zelle bekommt
   den rechten Abstand wieder abgezogen, sonst haengt die Tabelle
   rechts in der Luft. */
/* ⚠️ ZWEI ERZEUGER, EINE GESTALTUNG. Die MD-Karte im Board rendert ueber
   EasyMDEs eigenen Renderer (.editor-preview), die Text-Karte, die
   Row-Beschreibung und Share/Export ueber renderMarkdownToHtml() (app.js).
   Beide erzeugen dieselbe Struktur (table > thead/tbody > tr > th/td),
   deshalb steht die Gestaltung hier EINMAL fuer alle Board-Kontexte.
   Das Gegenstueck fuer Share/Export sitzt in buildExportCSS()
   (export.js) — wer hier etwas aendert, muss es dort mitziehen; die
   beiden Stellen verweisen deshalb wechselseitig aufeinander. */
.md-editor-host .editor-preview table,
.md-editor-host .editor-preview-side table,
.text-formatted-preview table,
.row-description-view table {
  border-collapse: collapse;
  width: 100%;
}
/* Linienfarbe: GEAENDERT (2026-09-02) von `var(--line)` auf die
   Textfarbe mit Deckkraft. Grund (Entscheidung SimplifyerX): die Linie
   soll die Farbe des Textes annehmen und zuruecktreten, statt einen
   eigenen Grauwert mitzubringen. `currentColor` folgt dem Text, und der
   folgt bereits dem Theme — dieselbe Lehre wie aus der Lightbox-Reihe:
   eine feste Farbe auf einstellbarem Grund traegt nicht.
   DECKKRAFT GEMESSEN, nicht geschaetzt: 12,9 % (dunkel) bzw. 12,7 %
   (hell) ergeben rechnerisch exakt den bisherigen --line-Wert
   (rgb(73,73,73) / rgb(226,226,226)) ueber der Kartenflaeche. Gewaehlt
   sind 15 % — bleibt mit 5 (dunkel) bzw. 9 (hell) RGB-Einheiten
   Abstand praktisch beim bewaehrten Eindruck, laesst der Linie aber
   etwas Reserve auf eingefaerbtem Grund, wo der Text naeher an der
   Flaeche liegen kann. 20 % waeren mit 17 bzw. 29 Einheiten sichtbar
   kraeftiger und draengen sich vor. */
.md-editor-host .editor-preview table th,
.md-editor-host .editor-preview table td,
.md-editor-host .editor-preview-side table th,
.md-editor-host .editor-preview-side table td,
.text-formatted-preview table th,
.text-formatted-preview table td,
.row-description-view table th,
.row-description-view table td {
  border: 0;                              /* Gitter aus easymde.min.css abraeumen */
  border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent);
  padding: 6px 20px 6px 0;
  text-align: left;
  vertical-align: top;
}
/* Die letzte Spalte bekommt den ganzen Rest der Breite. GEMESSEN, warum:
   mit `width:100%` allein teilen sich zwei Spalten die Kartenbreite je
   zur Haelfte — bei SimplifyerX' eigentlichem Fall (`| Age | 24 |`)
   stand der Wert dadurch weit rechts, getrennt von seiner Beschriftung.
   Ganz ohne `width:100%` schrumpft die Tabelle dagegen auf 91px, und die
   waagerechten Linien werden zu kurzen Stummeln. Mit dieser Zeile sind
   die vorderen Spalten inhaltsbreit (gemessen 70/49/70px) und nur die
   letzte dehnt sich — die Linien laufen weiter ueber die volle Breite.
   Der rechte Innenabstand faellt hier ebenfalls weg, sonst haengt die
   Tabelle rechts in der Luft. */
/* ⚠️ Ausrichtungsangaben (`:---:` usw.) — GEMESSEN, dass die beiden
   Erzeuger sie VERSCHIEDEN ausdruecken: renderMarkdownToHtml() schreibt
   `style="text-align:center"` (Inline-Style), EasyMDEs Renderer dagegen
   das alte HTML-Attribut `align="center"`. Ein Inline-Style gewinnt
   gegen die Regel oben, ein Praesentationsattribut NICHT — `align` wird
   vom Browser mit niedrigster Prioritaet abgebildet und verlor damit
   gegen unser `text-align: left`. Ohne diese drei Zeilen stand in der
   MD-Karte alles linksbuendig, waehrend die Text-Karte korrekt
   zentrierte: derselbe Quelltext, zwei Ergebnisse. */
.md-editor-host .editor-preview table [align="center"],
.md-editor-host .editor-preview-side table [align="center"],
.text-formatted-preview table [align="center"],
.row-description-view table [align="center"] { text-align: center; }
.md-editor-host .editor-preview table [align="right"],
.md-editor-host .editor-preview-side table [align="right"],
.text-formatted-preview table [align="right"],
.row-description-view table [align="right"] { text-align: right; }
.md-editor-host .editor-preview table [align="left"],
.md-editor-host .editor-preview-side table [align="left"],
.text-formatted-preview table [align="left"],
.row-description-view table [align="left"] { text-align: left; }
.md-editor-host .editor-preview table th:last-child,
.md-editor-host .editor-preview table td:last-child,
.md-editor-host .editor-preview-side table th:last-child,
.md-editor-host .editor-preview-side table td:last-child,
.text-formatted-preview table th:last-child,
.text-formatted-preview table td:last-child,
.row-description-view table th:last-child,
.row-description-view table td:last-child {
  width: 100%;
  padding-right: 0;
}
/* ⚠️ Sonderfall Row-Beschreibung: dort ist die TEXTFARBE selbst schon
   halbtransparent (gemessen: rgba(195,184,136,.51)). currentColor traegt
   diese Deckkraft mit, 15 % davon ergaben effektiv nur 7,6 % — die Linie
   kam auf dem dunklen Grund auf ein Kontrastverhaeltnis von 1,09 und war
   damit praktisch unsichtbar (nachgemessen auf Staging). Hier deshalb
   der doppelte Wert, damit dieselbe Linie sichtbar bleibt; das ist kein
   zweiter Geschmack, sondern die Gegenrechnung zur halben Textdeckkraft. */
.row-description-view table th,
.row-description-view table td {
  border-top-color: color-mix(in srgb, currentColor 30%, transparent);
}
/* Kopfzeile, in der ALLE Zellen leer sind, nicht zeichnen.
   Hintergrund: Markdown kennt keine Tabelle OHNE Kopfzeile — die
   Trennzeile definiert die Spalten, darueber MUSS etwas stehen. Wer
   keinen Kopf braucht, half sich bisher mit "." und ".." als Platzhalter.
   GEMESSEN am erzeugten Markup: EasyMDEs Renderer trimmt die Zellen und
   liefert bei `|      |` echtes `<th></th>` OHNE Leerzeichen — `:empty`
   greift also zuverlaessig, ein Eingriff in die Erzeugung ist nicht
   noetig. `:has()` ist im Zielbrowser vorhanden (zur Sicherheit live
   geprueft: CSS.supports("selector(:has(*))") === true).
   Die Regel liest sich als "Zeile, die KEINE nicht-leere Zelle enthaelt"
   — eine Kopfzeile mit auch nur EINER gefuellten Zelle bleibt damit
   sichtbar, wie gewuenscht. Faellt der Kopf weg, traegt die erste
   tbody-Zeile die oberste Linie, die Tabelle beginnt also weiterhin
   mit einem Strich. */
.md-editor-host .editor-preview thead tr:not(:has(th:not(:empty))),
.md-editor-host .editor-preview-side thead tr:not(:has(th:not(:empty))),
.text-formatted-preview thead tr:not(:has(th:not(:empty))),
.row-description-view thead tr:not(:has(th:not(:empty))) {
  display: none;
}
/* ══ CODEBLOECKE, VIER ORTE, ZWEI ERZEUGER (2026-09-02) ═══════════════
   Gegenstueck zum Zaun-Zweig in renderMarkdownToHtml() (app.js). Wie bei
   den Tabellen erzeugen ZWEI Renderer dasselbe Markup — EasyMDE in der
   MD-Karte, renderMarkdownToHtml() in Text-Karte, Row-Beschreibung und
   Share/Export —, also muss es auch gleich AUSSEHEN. Die MD-Karte ist
   hier die Vorlage, weil sie den Block schon vorher zeichnete; ihre
   Werte sind GEMESSEN und nicht abgeleitet:
     Schrift  JetBrains Mono (var(--f-data))
     Groesse  wie der Fliesstext des jeweiligen Ortes, NICHT die 11px
              des Inline-Codes (`.editor-preview pre code{font-size:inherit}`
              stand schon da) — Karten 12px, Row-Beschreibung 13px
     Flaeche  var(--panel-2), bewusst NICHT der Karten-Farbton
     Abstand  0 0 16px
   Der Block bekommt bewusst KEINEN Rahmen und KEINE linke Leiste — das
   sind die Zeichen von Tabelle und Zitat, und der Auftrag verlangt
   ausdruecklich, dass er wie keines von beiden aussieht. Die Flaeche
   allein traegt die Unterscheidung.

   FLAECHE AUCH BEI "No Style": GEMESSEN am bereits ausgelieferten Stand
   der MD-Karte — `.panel.card-style-none` nimmt der VORSCHAU ihre
   Flaeche (transparent), dem Codeblock darin aber nicht (bleibt
   var(--panel-2)). Das ist kein Widerspruch, sondern dieselbe
   Unterscheidung, die dort schon dokumentiert ist: "No Style" raeumt
   die Dekoration der KARTE weg, die Flaeche des Codeblocks ist dagegen
   Teil des Inhalts ("ein Codeblock DARF sich vom Fliesstext abheben,
   das ist seine Aufgabe"). Der Text-Karte hier etwas anderes zu geben,
   haette die beiden Karten wieder auseinanderlaufen lassen.

   ROLLEN STATT UMBRECHEN — GEMESSEN, nicht angenommen: easymde.min.css
   setzt fuer `.editor-preview pre` KEIN white-space, der Browser-Default
   fuer <pre> ist `pre`. An einer 496 Zeichen langen JSON-Zeile
   nachgemessen: eine Zeile, 17px hoch, scrollWidth 3571 gegen
   clientWidth 1332 — es wird gerollt, nicht umgebrochen. Das ist fuer
   einen JSON-Prompt auch das Richtige: ein umgebrochener Prompt
   verliert seine Struktur, und Zeilenumbrueche sind darin bedeutungs-
   tragend. Uebernommen wird die ENTSCHEIDUNG, nicht der Mechanismus:
   bei EasyMDE rollt die GESAMTE Vorschau mit (overflow-x der
   .editor-preview), der Fliesstext daneben wandert also mit aus dem
   Bild. `overflow-x: auto` auf dem Block selbst haelt das Rollen dort,
   wo es hingehoert — und wird DESHALB auch der MD-Karte gegeben, sonst
   waere genau das der eine sichtbare Unterschied zwischen beiden. */
.md-editor-host .editor-preview pre,
.md-editor-host .editor-preview-side pre,
.text-formatted-preview pre,
.row-description-view pre {
  font-family: var(--f-data);
  margin: 0 0 16px;
  /* GEAENDERT (2026-09-02, Nachtrag) — war `pre` mit waagerechtem Rollen.
     Die Entscheidung von heute Vormittag war an EasyMDE gemessen und
     fuer Code richtig, dessen Bedeutung an der Zeile haengt. Fuer
     SimplifyerX' JSON-Prompts ist sie falsch: dort traegt die
     EINRUECKUNG die Struktur, jede Zeile ist ein Satz, und er musste
     jede lange Zeile einzeln nach rechts rollen — bei zwanzig Shots
     unbrauchbar. GEMESSEN an seinem Fall (9 logische Zeilen, Karte
     350px): vorher 734px Rollbreite gegen 350px Sichtbreite, also mehr
     als das Doppelte; jetzt 18 sichtbare Zeilen, nichts laeuft ueber.
     `overflow-wrap: break-word` ist das Netz fuer eine Zeile OHNE
     Leerzeichen: ein 200 Zeichen langer Schluessel ohne Trennstelle
     lief sonst mit 1440px hinaus, mit dem Netz bricht er auf 5 Zeilen
     um und behaelt die Fortsetzungs-Einrueckung. */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  /* BLEIBT als letzter Riegel, obwohl er mit dem Netz darueber GEMESSEN
     nie mehr ausloest (auch nicht beim 200-Zeichen-Schluessel): faellt
     `overflow-wrap` je aus — durch eine spaetere Aenderung oder eine
     Umgebung ohne Unterstuetzung —, rollt der Block, statt aus der
     Karte zu laufen. Ein `auto`-Roller, der nie greift, ist unsichtbar
     und kostet nichts. */
  overflow-x: auto;
}
/* GEAENDERT (2026-09-04, GEMESSEN SimplifyerX, "Der Codeblock folgt der
   Kartenflaeche"): war fuer alle vier Orte gemeinsam var(--panel-2) --
   ein fester, theme-eigener Chip, unabhaengig vom Kartenfarbton. Bei
   einer kraeftig getoenten Karte stach der Block dadurch heraus (im
   hellen Theme sehr hell, im dunklen sehr dunkel), obwohl der uebrige
   Markdown-Inhalt der Kartenfarbe folgt -- GEMESSEN, dass SimplifyerX'
   Karten den Block oft als HAUPTINHALT tragen (JSON-Prompts), nicht als
   Zitat im Fliesstext: "Etwas, das die Karte fuellt, muss sich nicht
   abheben."
   GEWAEHLTER WEG (b von drei im Auftrag): dieselbe Zwei-Ebenen-Technik
   wie ueberall im Farbton-System (Basis + Farbton-Verlauf), plus eine
   DRITTE Ebene --card-bg-code -- exakt das Muster der alternierenden
   List-Zeilen (.list-td:nth-child(even) weiter unten), nur ohne deren
   "nur bei gesetztem Farbton"-Gate (Begruendung bei --card-bg-code selbst,
   app.js). Verworfen: (a) gar keine eigene Flaeche -- GEMESSEN, dass der
   Block dann nur noch an der Schrift erkennbar waere, und Fliesstext UND
   Codeblock tragen beide bereits --f-label/JetBrains Mono, also
   ununterscheidbar; (c) eine Linie statt Flaeche -- waere das Zeichen von
   Zitat/Tabelle, der Auftrag verlangt ausdruecklich ein drittes Aussehen.
   Row-Beschreibung bewusst AUSSEN VOR (eigene Regel gleich im Anschluss):
   GEPRUEFT, sie sitzt NICHT auf einer Karten-Flaeche -- weder
   .row-description-view noch .row-sidebar tragen eine eigene
   background-Regel, die Flaeche darunter ist die Board-/Workspace-Ebene
   (--board-bg-base), nicht --panel/--card-bg-tint. Dem Kartenfarbton zu
   folgen haette dort eine Farbe gezeigt, die mit der tatsaechlichen
   Umgebung nichts zu tun hat -- bleibt deshalb bei --panel-2, wie zuvor. */
.md-editor-host .editor-preview pre,
.md-editor-host .editor-preview-side pre,
.text-formatted-preview pre {
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)), linear-gradient(var(--card-bg-code), var(--card-bg-code));
}
.row-description-view pre {
  background: var(--panel-2);
}
/* HAENGENDE EINRUECKUNG DER FORTSETZUNG.
   Ohne sie faengt eine umgebrochene Zeile am linken Rand an und sieht
   aus wie eine neue — bei eingerueckten JSON-Listen unlesbar.
   `text-indent` gilt normalerweise nur fuer die erste Zeile des GANZEN
   Blocks; ein <pre> mit Zeilenumbruechen ist EIN Block, nicht viele.
   Das Schluesselwort `each-line` wendet es nach JEDEM erzwungenen
   Umbruch erneut an — GEMESSEN in dieser Umgebung: logische Zeilen
   beginnen bei x=0, die Fortsetzung bei x=22 (3ch). Ohne `each-line`
   bekaeme nur die allererste Zeile den negativen Wert.
   DREI Zeichen, bewusst ungerade: JSON wird mit zwei oder vier
   Leerzeichen eingerueckt, eine Fortsetzung bei 3ch liegt damit
   ZWISCHEN den Verschachtelungsstufen und kann fuer keine davon
   gehalten werden. In `ch` statt Pixeln, damit sie mit der Schrift
   waechst — die Row-Beschreibung ist 13px statt 12px.
   In @supports gekapselt, weil beide Zeilen NUR gemeinsam sinnvoll
   sind: eine Umgebung ohne `each-line` verwirft die text-indent-Zeile
   (unbekanntes Schluesselwort macht die Deklaration ungueltig) und
   behielte sonst allein das padding-left — der Block waere dann
   gleichmaessig eingerueckt statt haengend. So bekommt sie stattdessen
   den schlichten Umbruch ohne Einrueckung.
   ⚠️ GRENZE, ausdruecklich festgehalten: die Einrueckung ist FEST und
   bezieht sich auf den linken Rand des Blocks, nicht auf die
   Einrueckung der jeweiligen Zeile. Die Fortsetzung einer tief
   eingerueckten Zeile beginnt deshalb links von deren eigenem Text.
   Eine mitwachsende Einrueckung ginge nur, wenn jede logische Zeile
   ein eigenes Element waere — das erzeugt EasyMDE aber nicht, und ein
   Nachbau nur fuer die drei anderen Orte haette sie auseinanderlaufen
   lassen. */
@supports (text-indent: -3ch each-line) {
  .md-editor-host .editor-preview pre,
  .md-editor-host .editor-preview-side pre,
  .text-formatted-preview pre,
  .row-description-view pre {
    padding-left: 3ch;
    text-indent: -3ch each-line;
  }
}
/* Der Inline-Code-Stil darf INNERHALB eines Blocks nicht gelten:
   `.text-formatted-preview code` und `.row-description-view code`
   tragen Rahmen, Polster und eine eigene Groesse — im Block ergaebe das
   einen Kasten im Kasten und eine zweite Schriftgroesse. Zurueckgesetzt
   statt neu gesetzt, damit der Block die Groesse seines Ortes erbt
   (Karten 12px, Row-Beschreibung 13px) — dieselbe Bauart wie die
   bereits vorhandene Zeile fuer die MD-Karte darueber. */
.text-formatted-preview pre code,
.row-description-view pre code {
  font-size: inherit;
  background: none;
  border: 0;
  padding: 0;
}
/* GEMESSEN im Vergleich beider Karten: in der MD-Vorschau nimmt
   `.editor-preview > *:last-child` dem letzten Element den unteren
   Abstand — eine Karte, die NUR einen Codeblock enthaelt, hatte dort
   also 0, in der Text-Karte dagegen 16px. Das war der einzige
   verbliebene Unterschied zwischen den beiden. Bewusst eng gefasst auf
   `pre:last-child` statt einer allgemeinen `> *:last-child`-Regel: die
   haette auch Absaetze, Listen und Tabellen betroffen und damit den
   Abstand bereits ausgelieferter Karten veraendert, um die es hier
   nicht geht. */
.text-formatted-preview pre:last-child,
.row-description-view pre:last-child {
  margin-bottom: 0;
}
.md-editor-host .editor-preview > *:first-child,
.md-editor-host .editor-preview-side > *:first-child {
  margin-top: 0;
}
.md-editor-host .editor-preview > *:last-child,
.md-editor-host .editor-preview-side > *:last-child {
  margin-bottom: 0;
}
.md-editor-host .CodeMirror-cursor {
  border-left-color: var(--ink);
}
.md-editor-host .cm-header { color: var(--ink); }
/* NEU: lines/words/characters linksbündig (EasyMDEs Default ist
   rechtsbündiger Inline-Block-Fluss, siehe easymde.min.css), Import/
   Export-Buttons rechtsbündig über margin-left:auto auf dem letzten
   Flex-Kind (.md-status-actions, siehe mountMdEditor(), panels.js). */
/* NEU (Nutzer-Feedback): die Statusleiste selbst ist jetzt TEIL dessen,
   was der Kopf-Toggle ein-/ausblendet — genau wie .md-card-toolbar-extra
   oben. Ohne .panel.toolbar-open bleibt sie unsichtbar. */
.md-editor-host .editor-statusbar {
  display: none;
  align-items: center;
  text-align: left;
  color: var(--ink-faint);
  font-family: var(--f-label);
  font-size: 10px;
}
.panel.toolbar-open .md-editor-host .editor-statusbar { display: flex; }
.md-editor-host .editor-statusbar span.lines,
.md-editor-host .editor-statusbar span.words,
.md-editor-host .editor-statusbar span.md-chars {
  margin-left: 0;
  margin-right: 14px;
}
.md-status-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
}
/* BUGFIX (2026-07, beim Staging-Test entdeckt): EasyMDE rendert seine
   Toolbar-Icons standardmäßig als Font-Awesome-Icon-Font-Zeichen
   (<i class="fa fa-bold">), lädt diesen Font aber NICHT automatisch mit
   (autoDownloadFontAwesome:false in hydrateMd() — bewusst so, sonst
   Laufzeit-Abhängigkeit von einem externen CDN, siehe Vendoring-
   Prinzip). Ohne die echte Font-Awesome-Datei bleiben die Buttons leer.
   Eigene, einfache Text-/Symbol-Glyphen statt dessen — build-frei,
   funktioniert unabhängig davon, welche Systemfonts installiert sind. */
.md-editor-host .editor-toolbar i.fa {
  font-family: inherit;
  font-style: normal;
  width: auto;
}
.md-editor-host .fa-bold::before { content: "B"; font-weight: 700; }
.md-editor-host .fa-italic::before { content: "I"; font-style: italic; }
.md-editor-host .fa-strikethrough::before { content: "S"; text-decoration: line-through; }
.md-editor-host .fa-header::before { content: "H"; font-weight: 700; }
.md-editor-host .fa-quote-left::before { content: "\201C"; font-size: 15px; }
.md-editor-host .fa-list-ul::before { content: "\2022"; font-size: 15px; }
.md-editor-host .fa-list-ol::before { content: "1."; font-size: 11px; }
.md-editor-host .fa-link::before { content: "\1F517"; font-size: 12px; }
.md-editor-host .fa-image::before { content: "\1F5BC"; font-size: 12px; }
.md-editor-host .fa-table::before { content: "\229E"; }
.md-editor-host .fa-minus::before { content: "\2014"; }
.md-editor-host .fa-eye::before { content: "\1F441"; font-size: 12px; }
.md-editor-host .fa-columns::before { content: "\29F9"; }
.md-editor-host .fa-question-circle::before { content: "?"; font-weight: 700; }

/* Assets-Manager-Liste: MD-Zeile (analog .lp-asset-thumb-pdf) */
.lp-asset-thumb-md {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}

/* NEU (2026-08-03, Typography Stufe 2 Phase 1): Schrift-Zeile.
   Gleiches Muster wie MD/PDF — eine Schriftdatei ist kein Bild, ein
   <img> darauf zeigte nur ein kaputtes Bild-Icon. Die Vorschau in der
   Schrift SELBST (statt eines Icons) braucht ein geladenes @font-face
   und kommt in Phase 2. */
.lp-asset-thumb-font {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}

/* Detail-Panel rechts: Icon-Vorschau statt kaputtem <img> (siehe
   openRightPanelForUpload() thumbHtml, app.js) — Markdown-Blobs sind
   kein Bild, ein <img src="blob:…"> würde nur ein Broken-Image-Icon
   zeigen. Analog zu .lp-asset-thumb-md oben, nur in der Detail-Panel-
   Größe. */
.rp-thumb-doc {
  display: grid;
  place-items: center;
  background: var(--line-soft);
}
.rp-thumb-doc-icon {
  width: 42px;
  height: 42px;
  color: var(--ink-faint);
}
.rp-thumb-doc-icon svg { width: 100%; height: 100%; }

/* Share-Seite (buildSharePanelBody(), export.js): Download-Link statt
   Vorschau für MD-Assets — siehe Begründung dort (Share-Pipeline ist
   synchron, keine Formatierungs-Vorschau in dieser Sitzung). */
.ex-md-download {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 12px;
}
.ex-md-download-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  border: 1px solid var(--line-soft, #ccc);
  color: inherit;
  text-decoration: none;
  font-size: 13px;
}
.ex-md-download-link svg { width: 16px; height: 16px; }

/* ── List Card: table with optional slot column ──────────── */
.list-panel-inner { padding: 4px 0; }

/* toolbar hidden by default — shown via the same toolbar-toggle-btn
   mechanism as Grid's columns/ratio row and Text's view-mode row */
.list-toolbar { display: none; justify-content: flex-end; gap: 6px; margin-bottom: 8px; padding: 0 10px; }
.panel.toolbar-open .list-toolbar { display: flex; }
/* GEAENDERT (2026-08-13): hatte ebenfalls einen Amber-Hover (siehe
   .md-import-upload-btn). Jetzt .btn .btn-sm im Markup. */

/* outer surface: light-grey background (matches the Text card editor),
   10px padding to the card body's outer edge */
/* GEPRUEFT, NICHT GEBAUT (2026-08-24, Teil 2): erster Versuch war
   .list-surface allein zu toenen, wie .text-formatted-preview. GEMESSEN
   und per Bildschirmfoto verworfen: .list-th/.list-td (die Zeilen-Zellen
   INNERHALB dieser Flaeche) trugen DAMALS background:var(--panel),
   UNGETOENT, plus border-bottom:2px solid var(--bg-deep) als "Luecke
   passend zum Untergrund" (Kommentar dort). Nur die Aussenflaeche zu
   toenen liess die Zellen als unverbundene graue Bloecke auf getoentem
   Grund stehen -- sichtbar schlechter als der Ausgangszustand.
   GEAENDERT (2026-08-24, Teil 5): diese Ursache ist seit Teil 3/4 weg --
   .list-th/.list-td toenen jetzt selbst (Zell-Toenung + Zeilen-Alternanz),
   der alte border-bottom-Verschmelzungstrick ist komplett entfallen. Die
   Aussenflaeche blieb dabei schlicht UNANGEFASST stehen (Teil 3/4 haben
   nur die Zellen geaendert, nie diese Regel) -- sichtbar als helle
   Umrandung/Polsterflaeche um die jetzt getoente Tabelle (SimplifyerX-
   Fund, Teil 5). Jetzt nachgezogen, gleiche Zwei-Ebenen-Technik. */
.list-surface { background-color: var(--bg-deep); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)); padding: 10px 10px 22px; margin: 0px; border-radius: 0px; position: relative; }
.list-table-wrap { width: 100%; overflow-x: auto; }   /* fixes columns breaking out of the card past 3+ */

.list-table { border-collapse: separate; border-spacing: 0; table-layout: auto; width: 100%; }   /* full card width — text columns still auto-size relative to each other within that width */
/* GEAENDERT (2026-08-24, Teil 4): Zell-Toenung allein (Teil 3, per
   Bildschirmfoto verworfen) ergab einen "fast einheitlichen roten
   Block" -- Zeilen nicht mehr unterscheidbar. SimplifyerX-Entscheidung:
   alternierende Zeilentoene statt Randfarbe. Jede zweite Datenzeile
   bekommt eine ZUSAETZLICHE, dritte Verlaufs-Ebene (--card-bg-zebra,
   siehe applyCardBgTint() in app.js) OBEN AUF der normalen Karten-
   Toenung -- additiv, nicht ersetzend (Falle 1 im Teil-4-Auftrag: der
   Endwert je Zeilentyp ist gemessen, nicht nur die Regel gelesen).
   --card-bg-zebra selbst ist HELL oder DUNKEL, je nach gemessener
   effektiver Helligkeit von Untergrund plus Farbton -- sonst
   verschwindet der Unterschied bei sehr hellen oder sehr dunklen
   Farbtoenen (siehe workflow-learn-and-tipps.md, Teil 4).
   Der fruehere border-bottom mit der Verschmelzungsfarbe
   (var(--bg-deep), "2px gap effect") diente NUR dazu, die (damals
   ungetoenten) Zellen optisch vom Untergrund abzusetzen -- jetzt, wo
   die Zellen selbst toenen, entfaellt dieser Zweck vollstaendig. Ein
   Trennstrich bleibt trotzdem sinnvoll, aber an einer ANDEREN Stelle:
   siehe .list-th weiter unten (Kopf/Koerper-Grenze), nicht mehr
   zwischen Datenzeilen. */
.list-th, .list-td {
  background-color: var(--panel);
  background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  border: 0;
  padding: 4px 6px; vertical-align: middle; position: relative;
}
/* Jede zweite Datenzeile (nicht die Kopfzeile) — .list-td-slot (Bild-
   spalte bei list-media) traegt dieselbe .list-td-Klasse und ist damit
   automatisch eingeschlossen, keine eigene Regel noetig. */
.list-table tbody tr:nth-child(even) .list-td {
  background-image: linear-gradient(var(--card-bg-zebra), var(--card-bg-zebra)), linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
}
/* slot column width now driven by <col data-col-id="_slot"> — resizable, no fixed width here */
/* Kopf/Koerper-Grenze (Teil 4): eigener, fester Trennstrich statt der
   frueheren Verschmelzungsfarbe -- die Kopfzeile bleibt bewusst AUSSEN
   VOR der Zebra-Alternanz (kein :nth-child auf .list-th), sie ist
   keine Datenzeile. var(--line) ist der allgemeine Trennstrich-Token
   des Projekts ("hairline border"), nicht mehr an einen bestimmten
   Untergrund gekoppelt. */
.list-th { border-bottom: 1px solid var(--line); }
.list-th-add { width: 28px; text-align: center; }

.list-td-rowdel { width: 24px; text-align: center; }

/* header cell: label input + icon group, icon group always pushed to
   the right edge of the cell regardless of label width */
.list-th-row { display: flex; align-items: center; gap: 4px; }
.list-th-icons { display: flex; align-items: center; gap: 4px; margin-left: auto; flex-shrink: 0; }
.list-col-label {
  background: transparent; border: 0; border-bottom: 1px dotted var(--line);
  font-family: var(--f-data); font-size: 10px; color: var(--ink);
  padding: 2px;
}   /* sized via the size="" attribute (kept in sync with its value in JS), not flex-stretch — lets table-layout:auto size the column to fit content */
.list-col-label::selection { background: transparent; }

.list-sort-btn, .list-col-del, .list-row-del {
  flex-shrink: 0;
  background: none; border: 0; color: var(--ink-faint); font-size: 9px; cursor: pointer;
  opacity: 0; transition: opacity .12s, color .12s;
}
.list-th:hover .list-sort-btn, .list-th:hover .list-col-del, tr:hover .list-row-del { opacity: 1; }
.list-col-del:hover, .list-row-del:hover { color: var(--red, #d04040); }
.list-sort-btn:hover { color: var(--amber); }

/* column resize handle — thin drag zone on the right edge of each header */
.list-col-resize {
  position: absolute; top: 0; right: -3px; bottom: 0; width: 6px;
  cursor: col-resize; z-index: 20;
  touch-action: none;
}
.list-col-resize:hover { background: var(--amber); opacity: .3; }

.list-col-add {
  background: none; border: 1px dashed var(--line); color: var(--ink-faint);
  cursor: pointer; transition: color .12s, border-color .12s;
  width: 22px; height: 22px; border-radius: 4px; font-size: 13px;
}
.list-col-add:hover { color: var(--amber); border-color: var(--amber); }

.list-cell-input {
  background: transparent; border: 0; outline: none;
  font-family: var(--f-data); font-size: 10.5px; color: var(--ink);
  padding: 2px;
}   /* sized via size="" attribute — see list-col-label note above */
.list-cell-input::selection { background: transparent; }

/* Text to LIST — paste-markdown view that replaces the table */
.list-text-mode { padding: 0 10px; }
/* GEAENDERT (2026-08-24, Teil 2): Karten-Farbton traegt jetzt.
   Rahmen ENTFERNT statt nur transparent-bei-Ruhe (anders als
   .text-editor): GEPRUEFT, dieses <textarea> hat KEINE :focus-Regel --
   der Rahmen haette also nie einen Bedienzustand angezeigt, in keinem
   Zustand. Eine neue Fokus-Faerbung war nicht beauftragt und waere hier
   ein neues Merkmal, keine Angleichung. */
.list-text-input {
  width: 100%; min-height: 140px; resize: vertical;
  background-color: var(--bg-deep); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  /* border:0 EXPLIZIT, nicht nur die alte Deklaration geloescht: ein
     <textarea> traegt sonst den BROWSER-STANDARDRAHMEN, GEMESSEN
     rgb(133,133,133) solid -- keine dieser Projektfarben, das war ein
     eigener Messfehler in dieser Runde, hier korrigiert. */
  border: 0;
  color: var(--ink); font-family: var(--f-data); font-size: 11px; line-height: 1.5;
  padding: 8px; margin-bottom: 8px;
}
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): trug
   Akzentfarbe im Ruhezustand UND Hover (schwerste Auspraegung der
   Hausregel-Verletzung dieser Runde, wie .lp-tb-create/.palette-picker-
   apply), jetzt .btn/.btn-sm. Vollbreite + Abstand zur naechsten Zeile
   bleiben strukturell. */
.list-text-apply { width: 100%; margin-bottom: 8px; }

.list-thumb {
  width: 100%; height: auto; position: relative; overflow: hidden; display: block;
  outline: 1px solid var(--asset-outline); outline-offset: -1px;
}

/* ── Audio Playlist Card ──────────────────────────────────── */
.audio-panel-inner { padding: 4px 0 22px; position: relative; }
/* GEAENDERT (2026-08-24, Teil 3, jetzt freigegeben): dieselbe Bauform
   wie .typo-list/.typo-row -- Untergrund (var(--bg-deep)) plus flex-
   gap:2px zwischen den Zeilen, KEIN border-bottom-Verschmelzungstrick
   wie bei .list-th/.list-td (siehe dortiger Kommentar). Die Zeilen
   (.audio-row) bleiben bewusst UNGETOENT (var(--panel)), genau wie
   .typo-row -- nur der Untergrund toent, die 2px-Luecke zeigt ihn
   zwischen den Zeilen. Per Bildschirmfoto geprueft: bleibt lesbar,
   kein Verschmelzungsproblem, weil hier keine Randfarbe mit dem
   Untergrund mithalten muss. */
.audio-list {
  display: flex; flex-direction: column; gap: 2px;
  background-color: var(--bg-deep); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  padding: 10px; margin: 0px;
}
/* GEAENDERT (2026-08-24, Teil 7): trug var(--panel) fest, ungetoent --
   nach demselben (in Teil 6 widerlegten) Vorbild wie .typo-row gebaut:
   "Untergrund bleibt neutral, damit Bedienelemente lesbar bleiben" war
   keine Inhalt/Untergrund-Unterscheidung, sondern eine Vermutung. Ist
   aber kein Inhalt (kein Farbwert, kein Bild) -- Untergrund fuer
   Titel-Eingabe und Wellenform, exakt wie .typo-row.
   VERSCHACHTELUNG GEPRUEFT: liegt innerhalb von .audio-list, das
   selbst schon toent -- keine doppelte Verstaerkung, eigene
   UNDURCHSICHTIGE background-color deckt die Liste darunter
   vollstaendig ab (nur die 2px-Luecke zwischen Zeilen zeigt weiterhin
   .audio-list). Endwert gemessen, siehe Bericht. Die Wellenform
   (.audio-slot) traegt eigene Bedeutung und bleibt unberuehrt. */
.audio-row {
  display: flex; flex-direction: column; gap: 4px;
  background-color: var(--panel); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint));
  padding: 8px;
  position: relative;   /* offset-parent for the media-controls cluster — anchors it to the whole row, not just the slot */
}
.audio-row-move {
  display: flex; flex-direction: column; gap: 1px; flex-shrink: 0;
}
.audio-move-btn {
  width: 16px; height: 14px; display: grid; place-items: center;
  background: none; border: 0; color: var(--ink-faint); cursor: pointer;
  transition: color .12s;
}
.audio-move-btn svg { width: 12px; height: 12px; }
.audio-move-btn:hover:not(:disabled) { color: var(--amber); }
.audio-move-btn:disabled { opacity: .25; cursor: default; }

/* ── Typography card (Pro, Stufe 1) ───────────────────────────
   NEU (2026-08-03), siehe docs/setzz-typography-card-notes.md.
   Grundsatz für alle Vorschau-Elemente hier: KEINE eigene
   font-family/-size/-weight-Vorgabe — die kommt ausschließlich aus dem
   Inline-Style der jeweiligen Zeile, sonst würde das Stylesheet die
   Schrift überschreiben, die die Karte gerade zeigen soll. */
.typo-panel-inner { padding: 4px 0 22px; position: relative; }
/* GEAENDERT (2026-08-24, Teil 2): Karten-Farbton traegt jetzt (siehe
   .text-formatted-preview fuer die Begruendung der Technik). */
.typo-list { display: flex; flex-direction: column; gap: 2px; background-color: var(--bg-deep); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)); padding: 2px; }
/* GEAENDERT (2026-08-24, Teil 6): trug var(--panel) fest, ungetoent --
   in Teil 2 faelschlich unter "eigene Bedeutung" mit .typo-color/
   .typo-pick-btn einsortiert. Ist aber KEIN Inhalt (kein Farbmuster,
   kein Bild, kein auswaehlbarer Chip) -- sie ist der Untergrund, auf
   dem die Schriftprobe steht, exakt wie .typo-list/.typo-single,
   die beide schon toenen (SimplifyerX-Fund, Teil 6).
   VERSCHACHTELUNG GEPRUEFT: .typo-row liegt INNERHALB von .typo-list,
   das selbst schon toent -- keine doppelte Verstaerkung, weil .typo-row
   eine EIGENE, UNDURCHSICHTIGE background-color (var(--panel)) traegt
   und die Flaeche der Liste darunter damit vollstaendig verdeckt (nur
   die 2px-Luecken zwischen den Zeilen zeigen weiterhin .typo-list).
   Endwert gemessen (nicht nur die Regel gelesen), siehe Bericht. */
.typo-row { display: flex; flex-direction: column; gap: 6px; background-color: var(--panel); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)); padding: 10px; }

/* NEU (2026-08-03, zweite Runde): Einstellungen sind per Default
   ausgeblendet — die Karte zeigt Schrift, nicht Formulare, dasselbe
   Prinzip wie die Hover-only SET-/COL-Faktoren. Eingeblendet wird über
   denselben Toolbar-Toggle wie der List/Single-Umschalter unten
   (`.panel.toolbar-open`), NICHT mehr über Hover — die vorherige
   Hover-Regel für .card-slot-btns ist deshalb ersatzlos entfernt.
   Betroffen: Familie-Eingabe + Reorder-Pfeile (typo-row-head),
   Beispieltext-Eingabe, die Feldreihe (Größe/Gewicht/Zeilenhöhe/
   Laufweite/Farbe) und Add/Remove. NICHT betroffen: das Ehrlichkeits-
   Abzeichen (typo-badge) — das ist keine Einstellung, sondern die
   Zusage "du siehst, ob es die echte Schrift ist", und bleibt deshalb
   immer sichtbar (siehe typo-preview-wrap weiter unten). */
.panel-typography .typo-row-head,
.panel-typography .typo-sample-input,
.panel-typography .typo-fields,
.panel-typography .card-slot-btns {
  display: none;
}
.panel-typography.toolbar-open .typo-row-head { display: flex; }
.panel-typography.toolbar-open .typo-sample-input { display: block; }
.panel-typography.toolbar-open .typo-fields { display: flex; }
.panel-typography.toolbar-open .card-slot-btns { display: flex; opacity: 1; }

.typo-row-head { align-items: center; gap: 6px; flex-wrap: wrap; }

.typo-family-input {
  flex: 1; min-width: 120px;
  font-family: var(--f-data); font-size: 12px;
  padding: 4px 6px; color: var(--ink);
  background: var(--bg-deep); border: 1px solid var(--line-soft);
}
.typo-sample-input {
  width: 100%; box-sizing: border-box;
  font-family: var(--f-data); font-size: 11px;
  padding: 4px 6px; color: var(--ink-soft);
  background: var(--bg-deep); border: 1px solid var(--line-soft);
}
.typo-family-input:focus, .typo-sample-input:focus { outline: 1px solid var(--amber); border-color: transparent; }

/* Vorschau + Ehrlichkeits-Abzeichen als Einheit. Das Abzeichen sitzt als
   Overlay auf der Vorschau (nicht mehr in der ausblendbaren
   typo-row-head) — so bleibt es sichtbar, auch wenn die Einstellungen
   geschlossen sind (siehe Kommentar oben). */
.typo-preview-wrap { position: relative; }
.typo-preview { overflow-wrap: anywhere; padding: 4px 0; }
.typo-preview-lg { padding: 10px 0; }

/* NEU (2026-08-03, dritte Runde): rundes Abzeichen statt Textzeile —
   der Hinweistext (typo-badge-label) erscheint erst bei Hover, das
   Abzeichen selbst ist nur ein farbiger Punkt in der oberen rechten
   Ecke der Vorschau. "missing" und "synth" sind die WARNENDEN
   Zustände und heben sich deshalb farblich ab — sie sind der Grund,
   warum es das Abzeichen überhaupt gibt. */
.typo-badge {
  position: absolute; top: 4px; right: 4px;
  width: 10px; height: 10px; border-radius: 50%;
  background: currentColor; cursor: help;
}
.typo-badge-product { color: var(--ink-faint); opacity: .8; }
.typo-badge-local   { color: var(--ink-faint); opacity: .8; }
.typo-badge-synth   { color: var(--amber); }
.typo-badge-missing { color: #c2410c; }
:root[data-theme="dark"] .typo-badge-missing { color: #fb923c; }
/* NEU (Stufe 2, Phase 2): eigene, hochgeladene Schrift. KEIN
   Warnzustand — die Schrift ist geladen, das Gezeigte ist das
   Richtige. Eigene Farbe statt Amber: Amber gehört bereits "synth",
   und zwei Zustände mit demselben Punkt wären genau die stille
   Mehrdeutigkeit, gegen die das Abzeichen gebaut ist. */
/* Phase 3: der eine Zustand zerfällt in zwei. Eingebettet = die
   Zusage gilt auch für Betrachter; nur lokal = sie gilt nur hier.
   Beides sind KEINE Warnzustände (die Schrift ist geladen und richtig),
   deshalb dieselbe Farbfamilie, der lokale gedämpft. */
.typo-badge-own-embedded { color: #0f766e; }
.typo-badge-own-local    { color: #0f766e; opacity: .55; }
:root[data-theme="dark"] .typo-badge-own-embedded { color: #5eead4; }
:root[data-theme="dark"] .typo-badge-own-local    { color: #5eead4; opacity: .55; }

/* Einbett-Schalter in der Zeile — Teil der Einstellungen, sitzt in der
   typo-row-head und wird deshalb vom Toolbar-Toggle mitversteckt. */
.typo-embed {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: 4px;
  height: 22px; padding: 0 6px;
  font-family: var(--f-data); font-size: 10px;
  background: var(--bg-deep); border: 1px solid var(--line-soft);
  color: var(--ink-faint); cursor: pointer; transition: color .12s, border-color .12s;
}
.typo-embed input { margin: 0; cursor: pointer; }
.typo-embed:hover { color: var(--ink); }
.typo-embed.on { color: #0f766e; border-color: currentColor; }
:root[data-theme="dark"] .typo-embed.on { color: #5eead4; }
/* Gesperrt durch die harte Kappe — sichtbar anders als "aus", damit
   niemand vergeblich klickt. Der Grund steht im title. */
.typo-embed.blocked { opacity: .4; cursor: not-allowed; }
.typo-embed.blocked input { cursor: not-allowed; }

.typo-badge-label {
  position: absolute; top: 100%; right: 0; margin-top: 4px;
  font-family: var(--f-data); font-size: 9px; letter-spacing: .04em;
  padding: 1px 5px; border: 1px solid currentColor; white-space: nowrap;
  background: var(--panel); color: var(--ink);
  opacity: 0; pointer-events: none; transition: opacity .12s;
  z-index: 1;
}
.typo-badge:hover .typo-badge-label { opacity: 1; }

/* NEU (Stufe 2, Phase 2): Schrift-Datei einer Zeile. Sitzt in der
   typo-row-head und wird damit vom bestehenden Toolbar-Toggle
   mitversteckt — es ist eine Einstellung, kein Ehrlichkeits-Hinweis. */
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt (inkl. Amber-Hover), jetzt .btn/.btn-sm. Feste
   Hoehe (22px) faellt mit ihr weg — .btn-sm setzt die Hoehe ueber sein
   eigenes Padding; auf Staging pruefen, ob das neben .typo-font-file
   (Datei-Badge daneben, behaelt seine feste 22px-Hoehe) noch fluchtet. */
.typo-font-btn { flex-shrink: 0; }

.typo-font-file {
  flex-shrink: 1; min-width: 0; display: inline-flex; align-items: center; gap: 4px;
  max-width: 160px; height: 22px; padding: 0 4px 0 6px;
  font-family: var(--f-data); font-size: 10px;
  background: var(--bg-deep); border: 1px solid var(--line-soft); color: var(--ink-soft);
}
.typo-font-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Zugeordnet, aber die Datei ist nicht da — dieselbe Warnfarbe wie das
   "missing"-Abzeichen, damit beide Anzeigen dieselbe Sprache sprechen. */
.typo-font-file-missing { color: #c2410c; border-color: currentColor; }
:root[data-theme="dark"] .typo-font-file-missing { color: #fb923c; }
.typo-font-remove {
  flex-shrink: 0; width: 14px; height: 14px; display: grid; place-items: center;
  font-size: 10px; line-height: 1;
  background: none; border: 0; color: var(--ink-faint); cursor: pointer; transition: color .12s;
}
.typo-font-remove:hover { color: #c2410c; }

.typo-row-move { display: flex; flex-direction: column; gap: 1px; flex-shrink: 0; margin-left: auto; }
.typo-move-btn {
  width: 16px; height: 14px; display: grid; place-items: center;
  background: none; border: 0; color: var(--ink-faint); cursor: pointer; transition: color .12s;
}
.typo-move-btn svg { width: 12px; height: 12px; }
.typo-move-btn:hover:not(:disabled) { color: var(--amber); }
.typo-move-btn:disabled { opacity: .25; cursor: default; }

.typo-fields { display: flex; flex-wrap: wrap; gap: 8px; }
.typo-field { display: flex; flex-direction: column; gap: 2px; }
.typo-field > span { font-family: var(--f-data); font-size: 9px; color: var(--ink-faint); letter-spacing: .05em; text-transform: uppercase; }
.typo-input {
  width: 52px; font-family: var(--f-data); font-size: 11px;
  padding: 3px 5px; color: var(--ink);
  background: var(--bg); border: 1px solid var(--line-soft);
}
.typo-field-color { position: relative; }
.typo-color { width: 32px; height: 22px; padding: 0; border: 1px solid var(--line-soft); background: var(--bg-deep); cursor: pointer; }
.typo-color-reset {
  position: absolute; right: -26px; bottom: 2px;
  font-family: var(--f-data); font-size: 9px;
  background: none; border: 0; color: var(--ink-faint); cursor: pointer;
}
.typo-color-reset:hover { color: var(--amber); }

/* Einzelansicht + Zeichensatz-Raster. Ohne das Raster wäre die
   Einzelansicht nur "dasselbe, größer" — es ist ihr eigentlicher Zweck. */
/* GEAENDERT (2026-08-24, Teil 3): trug var(--panel) fest, ungetoent —
   genau die Flaeche hinter dem grossen Schriftmuster ("The quick
   brown fox..."), die SimplifyerX per Bildschirmfoto noch auf weissem/
   neutralem Grund fand, obwohl .typo-list/.typo-charset in Teil 2
   schon getoent wurden. .typo-single ist eine eigene, dritte Flaeche
   (Einzelansicht), keine Wiederholung der beiden anderen. Gleiche
   Zwei-Ebenen-Technik, Basis bleibt var(--panel) (unveraendert), nur
   der Verlauf kommt dazu -- .typo-color/.typo-pick-btn (eigene
   Bedeutung, Schriftfarbe/Auswahl) bleiben bewusst unberuehrt. */
.typo-single { display: flex; flex-direction: column; gap: 10px; background-color: var(--panel); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)); padding: 12px; }
.typo-pick-row { display: flex; flex-wrap: wrap; gap: 4px; }
.typo-pick-btn {
  font-family: var(--f-data); font-size: 10px; padding: 3px 8px;
  background: var(--bg-deep); border: 1px solid var(--line-soft);
  color: var(--ink-faint); cursor: pointer; transition: color .12s, border-color .12s;
}
.typo-pick-btn:hover { color: var(--ink); }
.typo-pick-btn.active { color: var(--ink); border-color: var(--amber); }
/* GEAENDERT (2026-08-24, Teil 2): Karten-Farbton traegt jetzt.
   .typo-color/.typo-pick-btn BEWUSST NICHT angefasst -- eigene
   Bedeutung (Farb-Vorschau bzw. auswaehlbarer Schrift-Chip mit
   eigenem Hover/Active-Zustand), kein Untergrund. */
.typo-charset { display: flex; flex-direction: column; gap: 6px; padding: 10px; background-color: var(--bg-deep); background-image: linear-gradient(var(--card-bg-tint), var(--card-bg-tint)); }
.typo-charset-row { display: flex; flex-wrap: wrap; gap: 2px; font-size: 20px; line-height: 1.4; }
.typo-char {
  min-width: 22px; text-align: center; padding: 1px 2px;
  color: var(--ink); transition: background .12s;
}
.typo-char:hover { background: var(--bg); }

.typo-empty { font-family: var(--f-data); font-size: 11px; color: var(--ink-faint); padding: 14px; text-align: center; }

/* View-Umschalter — versteckt bis die Karten-Toolbar geöffnet ist,
   exakt wie .text-view-toggle bei der Text-Karte. */
.typo-view-toggle { display: none; gap: 3px; padding: 0 10px; margin-bottom: 8px; justify-content: flex-end; }
.panel.toolbar-open .typo-view-toggle { display: flex; }
.typo-view-btn {
  min-width: 26px; height: 22px; padding: 0 8px;
  font-family: var(--f-data); font-size: 10px;
  background: var(--bg-deep); border: 1px solid var(--line-soft);
  color: var(--ink-faint); cursor: pointer; transition: color .12s, border-color .12s;
}
.typo-view-btn:hover { color: var(--ink); }
.typo-view-btn.active { color: var(--ink); border-color: var(--amber); }

/* Pro-Sperre (Gate Ebene 2): die Karte bleibt VOLLSTÄNDIG erhalten,
   nur die Bearbeitung fehlt. Bewusst nicht ausgegraut bis zur
   Unkenntlichkeit — der Nutzer soll sehen, was in der Karte steckt. */
.typo-locked { position: relative; display: flex; flex-direction: column; gap: 8px; background: var(--panel); padding: 14px; }
.typo-locked-badge {
  position: absolute; top: 8px; right: 8px;
  font-family: var(--f-data); font-size: 9px; letter-spacing: .08em;
  padding: 2px 6px; border: 1px solid var(--amber); color: var(--amber);
}
.typo-locked-line { overflow-wrap: anywhere; opacity: .75; }
.typo-locked-more { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); }
.typo-locked-note {
  font-family: var(--f-data); font-size: 10px; line-height: 1.5;
  color: var(--ink-faint); margin: 4px 0 0; padding-top: 8px;
  border-top: 1px solid var(--line-soft);
}

/* ══ Palette-Card (2026-08-04, Pro Card 2/3) ═══════════════════════
   Siehe docs/setzz-palette-card-notes.md.

   ⚠️ Die Streifen bekommen Hintergrund- UND Textfarbe ausschließlich
   per Inline-Style aus paletteStripMarkup() (panels.js) — die Textfarbe
   wird aus der Luminanz der Farbe berechnet, damit die Beschriftung auf
   Hell wie Dunkel lesbar bleibt. Deshalb steht hier bewusst KEINE
   color/background-Regel für .palette-strip, die das überschreiben
   könnte. Aus demselben Grund braucht dieser Block auch keine
   Theme-Varianten: die Streifen tragen ihre eigene Farbwelt, unabhängig
   von Light/Dark. */
.palette-strips {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  /* Höhe verstellbar wie bei der Text-Karte (.text-editor).
     ⚠️ flex:none ist PFLICHT, nicht Kosmetik: flex-grow überschreibt die
     von Hand gezogene Höhe bei jedem Layout-Durchlauf — dieselbe Lehre
     steht wörtlich am .text-editor. overflow:auto ist ebenfalls Pflicht,
     denn resize wirkt nur auf Elemente mit overflow ≠ visible. */
  flex: none;
  height: 140px;
  min-height: 60px;      /* = PALETTE_MIN_H (app.js) — harter Boden */
  resize: vertical;
  overflow: auto;
}
/* Höhen-Stufen, gesetzt vom ResizeObserver in bindPaletteResize().
   Derselbe Grundsatz wie die 70px-Breitenregel: was seinen Wert nicht
   mehr lesbar zeigen kann, blendet ihn aus statt ihn abzuschneiden. */
.palette-strips.palette-h-no-name  .palette-strip-name  { display: none; }
.palette-strips.palette-h-no-value .palette-strip-value { display: none; }
/* 70px ist die GRENZE, nicht ein Geschmackswert: darunter passt ein
   Hex-Wert in der Monospace-Schrift nicht mehr lesbar auf den Streifen,
   und ein Streifen, der seinen eigenen Wert nicht zeigen kann, ist
   keiner mehr. flex-wrap statt Container-Query/JS-Messung: es
   verschlechtert sich stufenlos statt an einem Sprungpunkt, und es
   braucht keine Rechnung über die Anzahl der Farben. */
.palette-strip {
  position: relative;
  flex: 1 1 70px;
  min-width: 70px;
  min-height: 130px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 8px 7px;
  transition: flex-grow .15s;
}
.panel.toolbar-open .palette-strip[draggable="true"] { cursor: grab; }
.palette-strip.palette-dragging { opacity: .4; }

/* GESPERRT: Rahmen am Streifen selbst, nicht nur am 13px-Icon.
   Ein Rahmen umschliesst den Streifen und liest sich als "festgehalten".
   Die Farbe kommt per --palette-frame aus paletteFrameOn() (app.js),
   also aus derselben Luminanz-Entscheidung wie die Textfarbe: heller
   Rahmen auf dunklen Farben, dunkler auf hellen. Halbtransparent und
   2px schmal — erkennbar, aber die Farbfläche bleibt die Hauptsache.
   inset box-shadow statt border: verändert die Kastenmasse nicht und
   stört das Flex-Layout der Streifen deshalb gar nicht. */
.palette-strip.is-locked { box-shadow: inset 0 0 0 2px var(--palette-frame); }

/* NACH .is-locked, damit die Drop-Anzeige beim Umsortieren gewinnt —
   gleiche Spezifität, spätere Regel schlägt die frühere. Deshalb steht
   der Rahmen oben auch NICHT als Inline-Style am Element (der würde
   jede Klassenregel schlagen und die Drop-Linie verschlucken). */
.palette-strip.palette-drop-before { box-shadow: inset 3px 0 0 0 var(--amber); }
.palette-strip.palette-drop-after  { box-shadow: inset -3px 0 0 0 var(--amber); }

/* Schloss: immer sichtbar UND immer bedienbar, auch bei geschlossenen
   Einstellungen. Es ist kein Formularfeld, sondern ein Zustand am
   Inhalt — und die einzige Bedienung, die Würfeln und Bildpicken
   steuerbar macht. Gleiche Begründung wie beim immer sichtbaren
   Ehrlichkeits-Abzeichen der Typography-Karte. */
.palette-lock {
  position: absolute; top: 6px; right: 6px;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  background: none; border: 0; padding: 0;
  color: inherit; cursor: pointer;
  opacity: .35;
  transition: opacity .12s;
}
.palette-lock svg { width: 13px; height: 13px; }
.palette-strip:hover .palette-lock { opacity: .7; }
.palette-strip.is-locked .palette-lock { opacity: 1; }

.palette-strip-foot { display: flex; flex-direction: column; gap: 2px; }
.palette-strip-name {
  font-family: var(--f-label); font-size: 10px; font-weight: 600;
  overflow-wrap: anywhere;
}
.palette-strip-value {
  background: none; border: 0; padding: 0;
  font-family: var(--f-data); font-size: 10px;
  color: inherit; opacity: .85; text-align: left; cursor: pointer;
  overflow-wrap: anywhere;
}
.palette-strip-value:hover { opacity: 1; text-decoration: underline; }

/* ══ Sichtbarkeit der Bedienelemente ═══════════════════════════════
   ⚠️ HIER, NICHT IM RENDERER. hydratePalette() gibt alle Bedienelemente
   IMMER aus; ein- und ausgeblendet werden sie ausschliesslich über
   `.panel.toolbar-open` — dasselbe Muster wie .text-footer,
   .list-toolbar, .md-card-toolbar-extra, .typo-fields.

   Der Grund ist kein Stilgeschmack: der Toolbar-Toggle in app.js
   schaltet NUR die Klasse um und hydriert nicht neu. Bedingt gerenderte
   Elemente können dadurch nie entstehen — genau daran war die erste
   Fassung der Karte vollständig unbedienbar. */
.palette-toolbar,
.palette-strip-edit,
.panel-palette .card-slot-btns { display: none; }
.panel.toolbar-open .palette-toolbar { display: flex; }
.panel.toolbar-open .palette-strip-edit { display: flex; }
.panel.toolbar-open .panel-palette .card-slot-btns,
.panel-palette.toolbar-open .card-slot-btns { display: flex; opacity: 1; }

.palette-strip-edit { flex-direction: column; gap: 4px; margin-top: 8px; }
.palette-color {
  width: 100%; height: 22px; padding: 0;
  background: none; border: 1px solid rgba(128,128,128,.4); cursor: pointer;
}
.palette-name-input {
  width: 100%; min-width: 0;
  font-family: var(--f-data); font-size: 10px;
  padding: 3px 4px;
  color: inherit;
  background: rgba(255,255,255,.14);
  border: 1px solid rgba(128,128,128,.4);
}
.palette-name-input::placeholder { color: inherit; opacity: .5; }
.palette-strip-remove {
  align-self: flex-end;
  width: 20px; height: 20px; line-height: 1;
  background: rgba(255,255,255,.14); border: 1px solid rgba(128,128,128,.4);
  color: inherit; cursor: pointer; font-size: 13px;
}

/* ⚠️ KEIN `display` hier — das setzt ausschliesslich der Sichtbarkeits-
   Block weiter oben (.palette-toolbar { display:none } +
   .panel.toolbar-open .palette-toolbar { display:flex }). Eine
   display-Angabe an dieser Stelle hat die Ausblendung geschlagen (gleiche
   Spezifität, spätere Regel gewinnt) und die Toolbar dauerhaft sichtbar
   gemacht — auf Staging gemessen. Gleiche Falle wie [hidden] vs. display
   beim Bild-Picker: wer display an zwei Orten schreibt, verliert an
   einem davon. */
.palette-toolbar {
  align-items: center; justify-content: space-between;
  gap: 8px; flex-wrap: wrap; padding: 0 0 8px;
}
.palette-fmt, .palette-actions { display: flex; gap: 2px; }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme):
   .palette-fmt-btn und .palette-act-btn teilten sich bisher EINE Regel.
   .palette-fmt-btn ist ein Umschalter (HEX/RGB, .active-Zustand unten)
   und bleibt unveraendert eigenstaendig (Sitzung A, ausserhalb dieser
   Runde). .palette-act-btn (Image/Reroll/Next set) ist einer der 22
   umgestellten Knoepfe — Erscheinung kommt jetzt von .btn/.btn-sm.
   Regel aufgeteilt, damit die Umstellung die Toggle-Erscheinung von
   .palette-fmt-btn nicht beruehrt. */
.palette-fmt-btn {
  font-family: var(--f-data); font-size: 9px; letter-spacing: .05em;
  padding: 3px 8px;
  background: var(--panel-2); border: 1px solid var(--line-soft);
  color: var(--ink-dim); cursor: pointer;
  transition: color .12s, border-color .12s;
}
.palette-fmt-btn:hover { color: var(--amber); border-color: var(--amber); }
.palette-fmt-btn.active { color: var(--amber); border-color: var(--amber); }
/* Gesperrter "Next set"-Knopf: sichtbar wirkungslos statt still
   wirkungslos — bei einem Bild mit ≤5 Farben gäbe es keine zweite
   Gruppe, und ein Knopf, der nichts tut aber normal aussieht, wäre
   Schein-Bedienung.
   GEÄNDERT (2026-08-13, vierte Runde): eigene :disabled-Regel entfallen
   — Next set ist .btn/.btn-sm und traegt jetzt den einheitlichen
   :disabled-Zustand von .btn. */
.palette-empty { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); padding: 10px; }

/* Gesperrte Ansicht (Pro-Gate Ebene 2) — baugleich zu .typo-locked. */
.palette-locked { position: relative; display: flex; flex-direction: column; gap: 8px; background: var(--panel); padding: 14px; }
.palette-locked-badge {
  position: absolute; top: 8px; right: 8px; z-index: 1;
  font-family: var(--f-data); font-size: 9px; letter-spacing: .08em;
  padding: 2px 6px; border: 1px solid var(--amber); color: var(--amber);
}
.palette-locked-note {
  font-family: var(--f-data); font-size: 10px; line-height: 1.5;
  color: var(--ink-faint); margin: 4px 0 0; padding-top: 8px;
  border-top: 1px solid var(--line-soft);
}

/* Bild-Picker. z-index über die BENANNTE Ebenen-Ordnung, nicht als
   Rohzahl: --z-modal-dialog, weil das eine blockierende Auswahl mit
   eigener Blende ist. Confirm-Dialoge und Toasts liegen bewusst darüber.
   Erste neue Ebene seit Einführung der Ordnung — bewusst einsortiert
   statt geraten, genau dafür ist sie da (siehe :root). */
.palette-picker-overlay {
  position: fixed; inset: 0;
  z-index: var(--z-modal-dialog);
  background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.palette-picker-box {
  width: min(760px, 100%); max-height: 86vh;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: var(--panel-shadow); padding: 14px;
  overflow-y: auto;
}
.palette-picker-head {
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--f-display); font-size: 15px; color: var(--ink);
}
.palette-picker-close {
  background: none; border: 0; color: var(--ink-faint);
  font-size: 14px; cursor: pointer;
}
.palette-picker-close:hover { color: var(--amber); }
/* EINE waagerechte Reihe statt mehrzeiligem Raster: dadurch hängt die
   Popup-Höhe nicht mehr an der Bildzahl — bei 20 Assets ist sie genauso
   hoch wie bei 5. Die gestaltete Leiste kommt aus der geteilten
   .scroll-thin-Regel oben (nicht erneut kopiert). */
.palette-picker-grid {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 6px;
}
.palette-picker-thumb {
  flex: 0 0 96px;        /* feste Breite, kein Schrumpfen — sonst quetscht
                            die Reihe alle Bilder statt zu scrollen */
  height: 96px;
  padding: 0; cursor: pointer; overflow: hidden;
  background: var(--bg-deep); border: 1px solid var(--line-soft);
}
.palette-picker-thumb:hover { border-color: var(--amber); }
.palette-picker-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.palette-picker-stage { display: flex; flex-direction: column; gap: 10px; }
/* ⚠️ PFLICHT, nicht Redundanz: die display-Regel darüber gewinnt gegen
   das UA-Stylesheet, das [hidden] normalerweise auf display:none setzt —
   die Stage blieb dadurch sichtbar, obwohl das Attribut gesetzt war (auf
   Staging gemessen: hidden=true, display=flex, Höhe 64px). Exakt die
   Falle, die bei .floating-panel-layer[hidden] schon dokumentiert ist:
   sobald irgendwo ein display-Wert gesetzt wird, muss [hidden] ihn
   ausdrücklich zurücknehmen. */
.palette-picker-stage[hidden] { display: none; }
/* align-items: center ist PFLICHT, nicht Kosmetik: mit dem
   Flex-Standard `stretch` würde das <img> auf die Containerhöhe gedehnt
   und damit verzerrt. */
.palette-picker-canvas-wrap {
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-deep); padding: 8px;
}
/* ⚠️ NUR max-* — KEINE feste `height` und KEIN `object-fit: contain`.
   Das ist die Pipetten-Falle: `palettePickPixel()` rechnet RELATIV
   (Klickposition ÷ Elementbreite × naturalWidth). Das stimmt exakt,
   solange die Element-Box das Bild IST. Mit erzwungener Höhe plus
   object-fit:contain entstünden Leerränder (Letterboxing) — die Box
   wäre größer als das Bild, und die Pipette läge systematisch daneben,
   ohne dass man es dem Bild ansieht.

   Mit reinen Maximalwerten schrumpft die Box seitenverhältnistreu mit,
   es gibt keine Leerränder, und die relative Rechnung bleibt richtig.
   Nach dem Umbau nachgemessen (Box vs. naturalWidth/Height + Pipette an
   bekannter Stelle), siehe docs/setzz-palette-card-notes.md. */
.palette-picker-stage img {
  max-width: 100%;
  max-height: 320px;
  cursor: crosshair;
}
.palette-picker-found { display: flex; flex-wrap: wrap; gap: 4px; }
.palette-picker-chip {
  font-family: var(--f-data); font-size: 10px;
  padding: 5px 9px; border: 1px solid rgba(128,128,128,.35);
}
.palette-picker-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.palette-picker-hint { font-family: var(--f-data); font-size: 10px; color: var(--ink-faint); }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): trug
   Akzentfarbe im Ruhezustand UND Hover, jetzt .btn/.btn-sm. */
.palette-picker-empty { font-family: var(--f-data); font-size: 11px; color: var(--ink-faint); padding: 20px 8px; text-align: center; }

/* Pro-Kachel im CARDS-Panel (Gate Ebene 1). Eigene Klasse statt
   panel-type-soon: "coming soon" heißt "gibt es noch nicht", hier
   heißt es "gibt es, aber nicht für dich". */
.panel-type-tile { position: relative; }
.panel-type-pro-locked { opacity: .55; cursor: not-allowed; }
.panel-type-pro-locked .panel-type-name { color: var(--ink-faint); }
/* R2 (2026-08): Hexagon-Icon statt Text-Chip — ersetzt das bisherige
   "Pro"/"Standard"-Textfeld an derselben Ecke. */
.panel-type-pro-badge {
  position: absolute; top: 2px; right: 2px;
  display: flex; color: var(--ink);
}
.panel-type-pro-badge svg { width: 12px; height: 12px; display: block; }

.audio-row-lower { display: flex; align-items: center; gap: 8px; }
.audio-row-move { margin-left: auto; }   /* pushed to the far right edge of the row */
.audio-slot {
  max-width: 95%; flex: 1; height: 28px; position: relative; overflow: visible;
  outline: 1px solid var(--asset-outline); outline-offset: -1px;
  color: var(--ink-faint);
  background: transparent;
}
.audio-slot audio { display: none; }   /* native element stays for playback/events only — custom playbar is the visible UI */
.audio-playbar-compact {
  top: 0;
  padding: 3px 6px;
  gap: 6px;
  background: transparent;
}
.audio-playbar-compact .vp-btn { width: 18px; height: 18px; }
.audio-playbar-compact .vp-btn svg { width: 11px; height: 11px; }
.audio-playbar-compact .vp-mute svg { width: 12px; height: 12px; }
.audio-playbar-compact .vp-time { font-size: 10px; }
.audio-playbar-compact .vp-vol { width: 36px; }
.audio-playbar-compact .vp-seek::-webkit-slider-thumb, .audio-playbar-compact .vp-vol::-webkit-slider-thumb { width: 8px; height: 4px; }
.audio-playbar-compact .vp-seek::-moz-range-thumb, .audio-playbar-compact .vp-vol::-moz-range-thumb { width: 8px; height: 4px; }
/* the hover-to-replace overlay is meant for image/video thumbnails —
   for audio it just visually blocks the native player's scrubber and
   controls. Hiding it doesn't disable uploads: click-to-upload is
   bound to the whole [data-upload-key] figure, not the overlay. */
.audio-slot .upload-overlay { display: none; }
.audio-row > .media-controls {
  top: 0; right: 0;   /* direct child of .audio-row now — anchors top-right of the title row */
  gap: 0;
}
.audio-row > .media-controls > button, .audio-row > .media-controls > a { width: 18px; height: 18px; }
.ph-waveform-wrap {
  width: 100%; height: 100%; position: relative;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-deep); cursor: pointer;
  overflow: hidden;
}
.ph-waveform-wrap svg { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--line); }
.ph-waveform-icon {
  position: relative; z-index: 1;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--panel); color: var(--ink-faint);
  display: grid; place-items: center; font-size: 8px;
  box-shadow: 0 0 0 1px var(--line-soft);
}

.audio-title-input {
  width: 100%; max-width: 75%;
  background: transparent; border: 0; border-bottom: 0px dotted var(--line);
  font-family: var(--f-display); font-size: 16px; font-weight: 500; color: var(--ink);
  padding: 2px 4px;
}
.audio-title-input::selection { background: transparent; }



.audio-filetype {
  flex-shrink: 0;
  font-family: var(--f-data);
  font-size: 8.5px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  text-align: right;
  min-width: 26px;
}

/* ── Export panel: Flat / Layered folder structure toggle ─── */
.ep-structure-toggle {
  border: 1px solid var(--line-soft);
  padding: 10px;
  margin-bottom: 10px;
}
.ep-structure-label {
  font-family: var(--f-data); font-size: 9px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: 8px;
}
.ep-structure-option {
  display: flex; align-items: baseline; gap: 6px;
  padding: 4px 0; cursor: pointer;
}
.ep-structure-option input { accent-color: var(--amber); flex-shrink: 0; }
.ep-structure-name {
  font-family: var(--f-data); font-size: 11px; color: var(--ink);
}
.ep-structure-desc {
  font-family: var(--f-data); font-size: 9px; color: var(--ink-faint);
}

/* ── Search Panel ─────────────────────────────────────────── */
.search-input {
  width: 100%;
  background: var(--bg-deep); border: 1px solid var(--line);
  color: var(--ink); font-family: var(--f-data); font-size: 12px;
  padding: 8px 10px; margin-bottom: 10px;
  outline: none;
}
.search-input:focus { border-color: var(--amber); }
.search-empty {
  font-family: var(--f-data); font-size: 10px; color: var(--ink-faint);
  padding: 12px 2px;
}
.search-results { display: flex; flex-direction: column; gap: 2px; }
.search-hit {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; text-align: left;
  background: var(--panel); border: 1px solid transparent;
  padding: 8px 10px; cursor: pointer;
  transition: border-color .12s, background .12s;
}
.search-hit:hover { border-color: var(--amber); background: var(--bg-deep); }
.search-hit-type {
  font-family: var(--f-data); font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--amber);
}
.search-hit-label {
  font-family: var(--f-display); font-size: 13px; color: var(--ink);
}
.search-hit-context {
  font-family: var(--f-data); font-size: 10px; color: var(--ink-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}

/* pulse highlight when a search hit scrolls a card into view */
@keyframes search-pulse-anim {
  0%   { outline-color: var(--amber); outline-offset: -1px; }
  50%  { outline-color: var(--amber); outline-offset: 4px; }
  100% { outline-color: transparent; outline-offset: -1px; }
}
.search-pulse { outline: 2px solid var(--amber); animation: search-pulse-anim 1.4s ease-out; }

/* ── Export panel: Auto-Save to Server toggle ─────────────── */
.ep-autosave-toggle {
  border: 1px solid var(--line-soft);
  padding: 10px;
  margin-bottom: 10px;
}
.ep-autosave-switch {
  display: flex; align-items: center; gap: 8px;
  cursor: pointer;
}
.ep-autosave-switch input { display: none; }
.ep-autosave-slider {
  position: relative;
  width: 32px; height: 18px;
  background: var(--line);
  border-radius: 10px;
  transition: background .15s;
  flex-shrink: 0;
}
.ep-autosave-slider::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  background: #fff;
  border-radius: 50%;
  transition: transform .15s;
}
.ep-autosave-switch input:checked + .ep-autosave-slider { background: var(--amber); }
.ep-autosave-switch input:checked + .ep-autosave-slider::after { transform: translateX(14px); }
.ep-autosave-state {
  font-family: var(--f-data); font-size: 10px; letter-spacing: .04em;
  color: var(--ink);
}
.ep-autosave-status {
  font-family: var(--f-data); font-size: 9px; letter-spacing: .04em;
  color: var(--ink-faint);
  margin-top: 8px;
}
.ep-autosave-status.saving { color: var(--amber); }
.ep-autosave-status.saved  { color: #3a9e5f; }
.ep-autosave-status.offline { color: #cc3333; }

/* NEU (2026-08, Auto-Save-Wahlfreiheit als Pro-Gate, UI-Ebene — siehe
   docs/setzz-plans-demo-standard-pro-notes.md). */
.pro-badge {
  display: inline-block;
  font-family: var(--f-data);
  font-size: 8px;
  letter-spacing: .06em;
  color: var(--amber);
  border: 1px solid var(--amber);
  border-radius: 3px;
  padding: 1px 4px;
  margin-left: 6px;
  vertical-align: middle;
}
/* ── Manual sync-now button ──────────────────────────────── */
#sync-now-btn.syncing { animation: sync-spin 0.8s linear infinite; color: var(--amber); }
@keyframes sync-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* ── Compare Card ─────────────────────────────────────────── */
.compare-toolbar {
  gap: 4px; align-items: center;
  justify-content: flex-end;
  padding: 6px 0 10px;
}
.compare-mode-btn {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  background: none; border: 1px solid var(--line); color: var(--ink-faint);
  transition: color .12s, border-color .12s;
}
.compare-mode-btn svg { width: 13px; height: 13px; }
.compare-mode-btn:hover { color: var(--ink); }
.compare-mode-btn.active { color: var(--amber); border-color: var(--amber); }

.compare-stage {
  position: relative;
  width: 100%;
  overflow: hidden;
  background: var(--bg-deep);
}
.compare-side, .compare-overlay-stage { display: none; width: 100%; height: 100%; }
.compare-stage[data-compare-mode="side"] .compare-side { display: block; position: relative; }
.compare-stage[data-compare-mode="overlay"] .compare-overlay-stage { display: block; position: relative; }

.compare-side .compare-slot { flex: 1 1 0; min-width: 0; height: 100%; }
/* NEU (2026-07): "Single"-Modus zeigt nur EIN Asset — beide Figuren
   bleiben permanent im DOM (Bugfix: sonst verlieren sie ihre
   media-controls/Auflösungs-Anzeige bei jedem Wechsel, siehe
   panels.js hydrateCompare()-Kommentar), nur die jeweils inaktive
   wird per CSS ausgeblendet. */
.compare-side .compare-single-slot { position: absolute; inset: 0; display: none; }
.compare-stage[data-compare-visible-slot="0"] .compare-side .compare-single-slot[data-upload-key$=":0"] { display: block; }
.compare-stage[data-compare-visible-slot="1"] .compare-side .compare-single-slot[data-upload-key$=":1"] { display: block; }

/* NEU (2026-07): "1 | 2"-Toggle fest unten mittig auf dem Asset, NICHT
   im einklappbaren Toolbar — bleibt dadurch immer erreichbar, auch bei
   geschlossener Toolbar. */
.compare-slot-toggle {
  position: absolute;
  left: 50%; bottom: 10px;
  transform: translateX(-50%);
  z-index: 5;
  padding: 4px 10px;
  background: rgba(var(--overlay-rgb), .99);
  border: 1px solid var(--line);
  border-radius: 50px;
  color: var(--ink-dim);
  font-family: var(--f-data);
  font-size: 10px;
  letter-spacing: .04em;
  transition: color .12s, border-color .12s;
}
.compare-slot-toggle:hover { color: var(--amber); border-color: var(--amber); }

.compare-base, .compare-top {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
}
.compare-top { will-change: clip-path; }

.compare-handle {
  position: absolute;
  top: 0; bottom: 0;
  transform: translateX(-50%);
  display: flex; align-items: center; justify-content: center;
  cursor: ew-resize;
  z-index: 10;
  touch-action: none;
}
.compare-handle-line {
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; transform: translateX(-50%);
  background: #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.25);
}
.compare-handle-grip {
  position: relative;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: #fff;
  color: #333;
  display: grid; place-items: center;
  box-shadow: 0 2px 8px rgba(0,0,0,.3);
}
.compare-handle-grip svg { width: 13px; height: 13px; }

/* ═══ READ-ONLY-ANSICHT (Share-Gast, 2026-08) ══════════════════════
   Gesetzt von initReadOnlyChrome() (app.js) als .is-readonly auf
   [data-app]. Die Navigations-/Konto-Elemente sind zu diesem Zeitpunkt
   bereits AUS DEM DOM entfernt. Die Editier-Bedienelemente im Board
   entfernt seit 2026-08 der Beobachter aus installGuestEditControlGate()
   (app.js) — die Regeln hier verhindern zusaetzlich, dass sie in dem
   einen Frame zwischen Einfuegen und Entfernen kurz aufblitzen.
   Die belastbare Sperre ist saveBoard() + die Push-Naehte + der
   401 des Servers — das hier ist Ehrlichkeit der Oberflaeche. */

/* POSITIVLISTE, gekoppelt an EDIT_CONTROL_SELECTOR in app.js — wer
   dort etwas aendert, aendert es hier mit. Alles Interaktive im Board
   gilt als Editier-Bedienelement; Ausnahmen tragen data-view-ok
   (heute genau eine: der Row-Sidebar-Umschalter).
   Bewusst NICHT enthalten: [draggable] und [contenteditable] — die
   sitzen auf .panel/.col/.group-wrap bzw. an sichtbarem Text; sie
   auszublenden wuerde den Board-INHALT verstecken. Diese beiden
   entschaerft der Beobachter per Attribut. */
.is-readonly [data-columns-scroll] :is(
  button, input, select, textarea,
  [data-group-action], [data-row-action], [data-panel-action],
  [data-col-action], [data-gridset-move],
  .col-add, .group-add, .row-add, .col-resize, .group-resize, .drag-handle
):not([data-view-ok]):not([data-view-ok] *):not([data-view-content]) { display: none !important; }
.readonly-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-toast);
  display: flex; align-items: center; gap: 12px;
  padding: 7px 16px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-dim);
}
.readonly-bar-badge {
  padding: 3px 8px;
  border: 1px solid var(--amber);
  color: var(--amber);
  letter-spacing: .08em;
  text-transform: uppercase;
  font-size: 9px;
  flex-shrink: 0;
}
.readonly-bar-name {
  color: var(--ink);
  font-family: var(--f-display);
  font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.readonly-bar-hint { flex: 1; min-width: 0; }
@media (max-width: 640px) { .readonly-bar-hint { display: none; } }
.readonly-bar-signin {
  flex-shrink: 0;
  padding: 5px 12px;
  border: 1px solid var(--amber);
  color: var(--amber);
  text-decoration: none;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-size: 10px;
}
.readonly-bar-signin:hover { background: var(--amber); color: var(--bg); }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm.
   GEÄNDERT (2026-08-13, vierte Runde): eigene :disabled-Regel entfallen
   — traegt jetzt den einheitlichen :disabled-Zustand von .btn.
   text-decoration:none bleibt strukturell (Anker-Tag). */
.readonly-bar-duplicate { text-decoration: none; flex-shrink: 0; }

/* NEU (2026-08, Duplicate fuer Team-Boards): dieselbe Optik wie
   .readonly-bar, aber eigene Klasse -- diese Bar sitzt NICHT fixed
   ueber der ganzen Seite (der Gast-Boot hat kein topnav, der
   eingeloggte App-Kontext schon), sondern im normalen Fluss direkt
   unter <main>, wie .board-conflict direkt darueber. Default
   display:none, ausschliesslich per style.display in
   applyForeignBoardBar() (app.js) umgeschaltet -- siehe dortiger
   Kommentar zur [hidden]-Spezifitaetsfalle. */
.foreign-board-bar {
  display: none;
  align-items: center; gap: 12px;
  padding: 7px 16px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-dim);
}
.foreign-board-bar-badge {
  padding: 3px 8px;
  border: 1px solid var(--amber);
  color: var(--amber);
  letter-spacing: .08em;
  text-transform: uppercase;
  font-size: 9px;
  flex-shrink: 0;
}
.foreign-board-bar-owner {
  color: var(--ink);
  font-family: var(--f-display);
  font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.foreign-board-bar-hint { flex: 1; min-width: 0; }
@media (max-width: 640px) { .foreign-board-bar-hint { display: none; } }
/* GEÄNDERT (2026-08-13, Nutzer-Vorgabe, Panel-Bestandsaufnahme): eigene
   Erscheinung entfernt, jetzt .btn/.btn-sm — identisch zu
   .readonly-bar-duplicate begruendet (gleiche Bauform, siehe dortiger
   Kommentar). Eigene :disabled-Regel seit der vierten Runde ebenfalls
   entfallen (einheitlicher :disabled-Zustand von .btn). */
.foreign-board-bar-duplicate { text-decoration: none; flex-shrink: 0; }

/* Platz fuer die Leiste schaffen, damit sie nichts ueberdeckt. */
.is-readonly { padding-top: 34px; }

/* GEAENDERT (2026-08-15, Nachtrag): die Demo-Leiste loeste dasselbe
   Ueberdeckt-das-Logo-Problem urspruenglich nach demselben Muster
   (Klasse "is-demo-bar", padding-top:41px) — durch die neue
   Positionierung NEBEN der Rail (siehe .demo-bar, `left: calc(var(--rail-w) + 16px)`)
   ueberfluessig geworden und ersatzlos entfernt: die Rail wird gar
   nicht mehr ueberdeckt, es muss ihr also auch kein Platz mehr
   gemacht werden. */

/* Rail-Fussbereich bleibt unten — im Editor besorgt das .rail-nav mit
   seinem flex:1, und genau die entfaellt fuer Gaeste (KILL-Liste in
   initReadOnlyChrome). Ohne Ersatz rutschten Theme-Schalter und
   Sidebar-Umschalter nach oben. margin-top:auto ist derselbe
   Mechanismus, nur ohne den Platzhalter dazwischen. */
.is-readonly .rail-bottom { margin-top: auto; }

/* Das Logo ist fuer Gaeste kein Weg mehr ins Dashboard (siehe
   initReadOnlyChrome) — der Zeiger darf das nicht laenger behaupten.
   Zwei Klassen noetig, um .app.board-open .rail-logo zu schlagen —
   gleiche Spezifitaet, aber weiter unten in der Datei. */
.app.is-readonly .rail-logo { cursor: default; }

/* Editier-Bedienelemente in den Karten. Bewusst eine einzige
   Sammel-Regel statt verstreuter Einzelfaelle — kommt ein neuer
   Kartentyp mit einem dieser Muster dazu, ist er automatisch mit
   erfasst (dieselbe Ueberlegung wie "ableiten statt aufzaehlen"). */
.is-readonly .card-slot-btns,
.is-readonly .panel-add-btn,
.is-readonly .col-add-btn,
.is-readonly .group-add-btn,
.is-readonly .row-add-btn,
.is-readonly .assets-toolbar,
.is-readonly .carousel-toolbar,
.is-readonly .compare-toolbar,
.is-readonly .palette-toolbar,
.is-readonly .media-controls,
.is-readonly .upload-overlay,
.is-readonly .col-resize,
.is-readonly .group-resize,
.is-readonly [data-panel-menu],
.is-readonly [data-col-menu],
.is-readonly [data-group-menu],
.is-readonly [data-row-menu] { display: none !important; }

/* Ausnahme zur Sammelregel oben (2026-08-11, Detail-Panel im
   read-only-Kontext erreichbar): der Stift (.meta-btn) bleibt der
   einzige Weg zu Prompt/Quelle/Modell, wenn kein Kartenkopf-Bedien-
   element existiert (fremdes Team-Board) oder gar keine Rail (Gast).
   .media-controls selbst wieder einblenden reicht NICHT — die uebrigen
   Kinder (Ratio/Quadrant/Framing/Menue) muessen verdeckt BLEIBEN, sonst
   waeren das erneut Editier-Bedienelemente. Beide Regeln stehen bewusst
   NACH der Sammelregel: gleiche Spezifitaet, die Reihenfolge im Quelltext
   entscheidet zwischen zwei !important-Deklarationen. */
.is-readonly .media-controls { display: flex !important; }
.is-readonly .media-controls > *:not(.meta-btn) { display: none !important; }

/* Inhalt bleibt lesbar, aber nicht bearbeitbar.

   HISTORIE, damit diese Regel nicht ein zweites Mal ausgehebelt wird:
   sie stammt aus 13d8edf und war die urspruengliche Absicht. Das
   spaeter darueber gelegte Gast-Gate (5f52f7e, display:none + remove())
   hat sie wirkungslos gemacht — Kartentext, Listenzellen,
   Spaltenueberschriften, Audio-Titel und Zeilenbeschreibung
   verschwanden fuer Gaeste ersatzlos. Seit 2026-08 nimmt
   [data-view-content] die betroffenen Felder von der Entfernung aus
   (siehe VIEW_CONTENT_ATTR in app.js); erst dadurch kommt diese Regel
   ueberhaupt wieder zum Tragen.

   Die frueher hier stehende Ausnahme-Klasse .readonly-allow ist
   ersatzlos entfallen — sie hatte in der ganzen Codebasis nie einen
   Verwender. [data-view-content] ist ihr echter Nachfolger und wird
   sowohl hier als auch in app.js ausgewertet. */
.is-readonly .text-editor,
.is-readonly [contenteditable] { pointer-events: none; }
.is-readonly input,
.is-readonly textarea { pointer-events: none; background: transparent; }

/* ══ GAST-CHROME (2026-08-12) ══════════════════════════════════════
   Hier standen bis Etappe 4 Runde 2 SECHS gezielte Selektoren
   (Theme-Umschalter, Rail minimieren, Minimap-Umschalter, Toolbar
   ausblenden, Undo, Redo). Sie sind ersatzlos entfallen: die
   Sammelregel unten trifft alle sechs — vor dem Entfernen einzeln
   nachgemessen, 6 von 6. Zwei Listen fuer dieselbe Menge waeren
   genau die Doppelpflege, gegen die dieser Umbau gebaut ist.

   Was aus jener Runde WEITERGILT und deshalb hier bleibt:

   ══ WARUM .is-guest UND NICHT .is-readonly ═══════════════════════
   Der Fund, ohne den der Umbau falsch gebaut worden waere.
   .is-readonly steht AUCH auf einem eingeloggten Nutzer, der ein
   fremdes Team-Board ansieht (applyBoardChrome()). Dort lief der
   normale Boot, alle Chrome-Binder haengen, und dieselben Knoepfe
   FUNKTIONIEREN. Unter .is-readonly haette die Regel Team-Mitgliedern
   Theme, Rail, Minimap, Toolbar und Undo/Redo weggenommen — an einer
   Stelle, an der sie arbeiten, und es waere erst aufgefallen, wenn
   jemand ein fremdes Board oeffnet. .is-guest setzt nur
   initReadOnlyChrome(), also nur der Gast-Boot.

   ══ WARUM KLASSENREGEL UND NICHT [hidden] ════════════════════════
   Gemessen, nicht vermutet: die betroffenen Basisregeln (.rail-btn,
   .rail-minimize-btn, .minimap-toggle, .toolbar-hide-toggle,
   .icon-btn) setzen display:grid mit EINER Klasse. [hidden] ist ein
   Attributselektor derselben Staerke und steht weiter oben in der
   Kaskade — es haette in 5 von 5 Faellen verloren.

   ══ DIE MINIMAP BLEIBT ═══════════════════════════════════════════
   Nur ihr Umschalter geht. [data-board-minimap] ist kein Element der
   Zielfamilie und wird von der Sammelregel nicht getroffen —
   nachgemessen, 153x102, vollstaendig im Bild. Der Gast soll die
   Uebersicht behalten, er soll sie nur nicht vergeblich wegklicken.

   ══ DER THEME-UMSCHALTER ═════════════════════════════════════════
   Wird ausgeblendet, nicht gerettet (Entscheidung SimplifyerX,
   2026-08-12). Ein gast-tauglicher Theme-Wechsel ist ein eigener
   Backlog-Punkt, kein Nebenprodukt dieses Umbaus. */

/* ══ DIE CHROME-SAMMELREGEL (2026-08-12, Etappe 4 Runde 2) ═════════
   Die Umkehrung, auf die der ganze Umbau zulief: ausserhalb des
   Boards ist fuer den Gast per VORGABE nichts bedienbar. Wer bleiben
   soll, sagt es mit data-view-ok.

   ══ WARUM UMKEHRUNG STATT AUFZAEHLUNG ════════════════════════════
   Bis hierher schuetzte die Chrome eine handgepflegte KILL-Liste von
   sechs Selektoren in initReadOnlyChrome(). Eine solche Liste kennt
   nur, was beim Schreiben bekannt war. Am 2026-08-11 fehlte
   bindMetaEditor() auf der Gast-Liste, am 2026-08-12 fanden sich
   sechs tote Knoepfe und ein erreichbarer Crop-Dialog — dieselbe
   Fehlerklasse wie buildExportMeta, MD_TYPE_REVERSE,
   FLOATING_DRAG_EXCLUDE und ENV_BY_DB: zwei Listen, die
   auseinanderlaufen, ohne dass jemand es merkt.

   Eine Umkehrung kann das nicht. Ein NEUES Bedienelement ausserhalb
   des Boards ist ab jetzt vorgabegemaess unsichtbar, bis jemand es
   ausdruecklich markiert. Der Fehler faellt beim Bauen auf ("warum
   sieht der Gast meinen Knopf nicht?") statt Monate spaeter im
   Betrieb. Genau dieses Muster schuetzt den Board-Bereich seit
   installGuestEditControlGate() — hier bekommt die Chrome dasselbe.

   ══ DIE VIER AUSNAHMEN, EINZELN BEGRUENDET ═══════════════════════
   :not([data-columns-scroll] *)
       Das Board gehoert dem Gate, nicht dieser Regel. Dort steht der
       INHALT — Kartentexte, Listenzellen, Spaltenkoepfe. Eine
       display:none-Regel darueber wuerde das Board leerraeumen.

   :not([data-view-ok])
       Der Opt-in selbst. Vier Werte, siehe VIEW_OK_SONDERWERTE in
       app.js.

   :not([data-view-ok] *)
       Ein markierter CONTAINER schuetzt seine Kinder mit. Bewusst so,
       weil isViewOk() in app.js ebenfalls per closest() sucht — die
       CSS-Regel und der JS-Leser muessen dieselbe Reichweite haben,
       sonst waeren Gate und Chrome uneins.

   Die Elementfamilie ist dieselbe wie in EDIT_CONTROL_SELECTOR, plus
   a[href]: ausserhalb des Boards sind Links echte Bedienelemente (die
   readonly-bar besteht aus zweien).

   ══ SPEZIFITAET ══════════════════════════════════════════════════
   Rund (0,5,0) — .is-guest, :is() (dessen staerkstes Argument
   [draggable="true"] ist) und drei :not(). Das schlaegt jede
   Einzelregel im Stylesheet ohne !important. Bewusst kein
   !important: die Regel gewinnt ohnehin, und ein !important auf
   Vorrat macht nur den naechsten echten Konflikt schwerer.

   Umgekehrt bedeutet dieselbe Spezifitaet: wer ein Element hier
   herausnehmen will, tut das NICHT mit einer staerkeren CSS-Regel,
   sondern mit dem Marker. Das ist die Absicht, kein Mangel.

   ══ WAS SIE NICHT LEISTET ════════════════════════════════════════
   Sie blendet aus, sie sichert nicht ab. Handler, die im Renderpfad
   per addEventListener haengen (Crop, Replace file), bleiben
   gebunden — sie sind nur nicht mehr erreichbar. Der eigentliche
   Riegel gehoert an den Eingang von openCropOverlay() und
   replaceAsset(); das ist ein eigener Backlog-Punkt und ausdruecklich
   NICHT Teil dieser Regel. Wer sich auf CSS als Sicherheitsgrenze
   verlaesst, hat sie missverstanden. */
/* SCHREIBWEISE, nicht Kosmetik: zwischen ) und :not darf KEIN
   Zeilenumbruch stehen. Whitespace ist dort der Nachfahren-Kombinator
   — die Regel traefe dann unmarkierte KINDER von Knoepfen statt der
   Knoepfe selbst und waere still wirkungslos. Innerhalb der
   :is()-Liste ist Umbruch dagegen unbedenklich (Komma-Trennung).
   Beim Schreiben dieser Regel genau einmal passiert. */
.is-guest :is(button, input, select, textarea, a[href], [draggable="true"],
              [contenteditable=""], [contenteditable="true"]):not([data-columns-scroll] *):not([data-view-ok]):not([data-view-ok] *) { display: none; }

/* ══ EMBED-CARD (2026-08-14) ═══════════════════════════════════════
   Siehe den Sandbox-Kommentar an sanitizeEmbedSource() in app.js.
   Farben ausschliesslich ueber die vorhandenen Tokens — die Karte muss
   in beiden Themes und unter jedem Nutzer-Theme tragen. */
.embed-card { display: flex; flex-direction: column; gap: 8px; }

.embed-frame { position: relative; width: 100%; overflow: hidden;
               background: var(--panel-2); border-radius: 4px; }
.embed-iframe { position: absolute; inset: 0; width: 100%; height: 100%;
                border: 0; display: block; }

/* Domain-Badge (2026-08-15, Etappe 3, Nachbesserung SimplifyerX):
   Radius 0 (wie alle Elemente ausser Kacheln), voll deckender
   Hintergrund (kein opacity < 1 mehr auf dem Element selbst), und nur
   bei Hover auf der KARTE sichtbar — nicht dauerhaft. Hover auf der
   ganzen Karte statt nur auf dem Rahmen: leichter zu treffen, und in
   der Praxis fast immer schon erfuellt, wenn jemand ratlos vor einem
   leeren Rahmen sitzt (der Zeiger ist dann ohnehin meist ueber der
   Karte) — loest die Spannung zum eigentlichen Zweck (Quelle zeigen,
   wenn sie nicht laedt) nicht vollstaendig auf, mildert sie aber, ohne
   die drei Vorgaben zu brechen. pointer-events:none bleibt, damit die
   Badge den iFrame-Inhalt darunter nicht blockiert. */
.embed-domain-badge { position: absolute; top: 6px; left: 6px; z-index: 1;
                       max-width: calc(100% - 12px); padding: 3px 8px;
                       border-radius: 0; font-size: 11px; line-height: 1.4;
                       color: var(--ink); background: var(--panel-2);
                       border: 1px solid var(--line); overflow: hidden;
                       text-overflow: ellipsis; white-space: nowrap;
                       pointer-events: none; opacity: 0; }
.embed-card:hover .embed-domain-badge { opacity: 1; }

/* Anfangszustand: kein Fehler, sondern "hier fehlt noch etwas". */
.embed-empty { display: flex; align-items: center; justify-content: center;
               border: 1px dashed var(--line); }
.embed-placeholder { display: flex; flex-direction: column; align-items: center;
                     gap: 8px; color: var(--ink-faint); text-align: center; padding: 12px; }
.embed-placeholder-icon { opacity: .5; }
.embed-placeholder-icon svg { width: 28px; height: 28px; }
.embed-placeholder-text { font-size: 12px; }

/* Etappe-1-Hinweis. Faellt mit EMBED_FRAME_SRC_LIVE weg. */
.embed-staged-note { position: absolute; inset: auto 0 0 0; padding: 4px 8px;
                     font-size: 11px; text-align: center;
                     color: var(--ink-faint); background: var(--panel-2); }

/* Fußzeile hinter dem Toolbar-Umschalter (Kopf-Knopf), wie bei jedem
   anderen Kartentyp mit Toolbar — siehe .list-toolbar/.compare-toolbar/
   .palette-toolbar fuer dasselbe Muster. Fehlte hier ganz: die Zeile war
   bislang UNBEDINGT display:flex, der Umschalter griff also nirgends.
   Waagerechtes Polster analog .list-toolbar (0 10px) — dieselbe Bauform
   Eingabefeld+Knoepfe in einer Zeile, kein neuer Wert erfunden.

   NACHTRAG (Karten-Bedienelemente-Runde, Embed-Befund): unten fehlte das
   Polster ganz (0), .embed-source-current (Quellen-Echo, sichtbar wenn
   bereits eine URL gesetzt ist) hatte UEBERHAUPT kein eigenes Polster.
   In der ueblichen Praxis (URL bereits gesetzt) ist NICHT die Zeile
   selbst das letzte Kind von .embed-card, sondern diese Echo-Zeile
   darunter — sie war es also, die tatsaechlich am Kartenrand anstiess,
   nicht das Eingabefeld/APPLY direkt. :last-child traegt das untere
   Polster deshalb an BEIDEN moeglichen letzten Kindern, aber nur an dem,
   das es gerade tatsaechlich ist — sonst haette .embed-source-row bei
   gesetzter URL zusaetzlich zum bestehenden 8px-Flex-Gap von .embed-card
   ein eigenes Polster addiert und die beiden Zeilen unnoetig
   auseinandergezogen. 10px, dieselbe Distanz zum Kartenrand wie bei
   Grid Sets/Mosaic (siehe .gridset-generate-row) — nicht dieselbe
   Klasse, aber derselbe Wert fuer dieselbe Frage (Fusszeile zu
   Kartenrand). */
.embed-source-row { display: none; gap: 6px; align-items: center; padding: 0 10px; }
.panel.toolbar-open .embed-source-row { display: flex; }
.embed-source-input {
  flex: 1 1 auto; min-width: 0;
  /* NACHTRAG (Embed-Befund): bislang KEINE eigene Regel — das Feld lief
     auf reinen Browser-Vorgaben (Schriftgroesse, Hoehe, Rahmen,
     Hintergrund alle UA-Default), sichtbar groesser/anders als die
     Grid-Sets-Fusszeilenfelder direkt daneben im Vergleich. Jetzt
     dieselbe Bauform wie .gridset-labels-input/.gridset-num-input/
     .gridset-color-input — reine Kontur, kein neuer Stil erfunden. */
  height: 24px;
  padding: 0 6px;
  background: none;
  border: 1px solid var(--line-soft);
  color: var(--ink);
  font-family: var(--f-data);
  font-size: 11px;
  border-radius: 0;
}
.embed-source-current { font-size: 11px; color: var(--ink-faint);
                        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
                        padding: 0 10px; }
.embed-source-row:last-child,
.embed-source-current:last-child { padding-bottom: 10px; }

/* Trenner ueber dem Provenienz-Block im Detail-Panel. */
.rp-sep { height: 1px; background: var(--line); margin: 14px 0 10px; }

/* ── Demo-Leiste (NEU 2026-08-15, oeffentlicher Demo-Zugang) ──────
   Bauform bewusst wie .readonly-bar des Gast-Kontexts: dieselbe
   Rolle (erklaeren, worin man sich befindet), deshalb dieselbe
   Erscheinung. BEWUSST OHNE Akzentfarbe an Rahmen oder Knoepfen —
   Amber steht im Werkzeug fuer Auswahl und aktiven Zustand; sie hier
   fuer Werbung einzusetzen wuerde diese Bedeutung verwaessern.
   Das Abzeichen traegt sie als einziges Element, in derselben
   gedaempften Rolle wie das "Read-only"-Abzeichen daneben.
   GEAENDERT (2026-08-15, Nachtrag): stand hier urspruenglich bei
   `left: 0` mit einem eigenen `padding-top` auf [data-app] (Klasse
   "is-demo-bar") als Gegenstueck, das der ganzen App Platz macht —
   entfernt. Die Leiste beginnt jetzt ERST rechts von der Rail
   (`--rail-w`, zentral definiert), die Rail bleibt unangetastet, kein
   Verschieben, keine Hoehenberechnung mehr noetig. */
.demo-bar {
  position: fixed;
  top: 0; left: calc(var(--rail-w) + 16px); right: 0;
  min-height: var(--demo-bar-h);
  z-index: var(--z-toast);
  display: flex; align-items: center; gap: 12px;
  padding: 7px 16px;
  background: var(--panel-2);
  border-bottom: 1px solid var(--line);
  font-family: var(--f-data);
  font-size: 11px;
  color: var(--ink-dim);
}
/* NEU (2026-08-16, Befund 5.2): die Leiste beginnt zwar erst rechts von
   der Rail (siehe oben) und ueberdeckt die Rail-Icons deshalb nicht —
   aber .topnav-board-name (im Board) steht WEITER RECHTS, in derselben
   Zeile bei top:0, und liegt damit im Ueberdeckungsbereich der Leiste.
   Die readonly-bar des Gastes hat dieses Problem nicht: sie schiebt via
   `.is-readonly { padding-top }` die GESAMTE App (inkl. Rail) nach unten.
   Hier bewusst NICHT derselbe Weg fuer die ganze App — nur .topnav
   bekommt Platz gemacht (existiert ohnehin nur bei offenem BOARD, das
   Dashboard hat kein topnav-Element und ist von dieser Regel unberuehrt).
   is-public-demo wird von initDemoChrome() (app.js) auf [data-app]
   gesetzt. */
.app.is-public-demo .topnav { margin-top: var(--demo-bar-h); }
/* NEU (2026-08-19, "Demo-Leiste überlappt den Panel-Kopf"): dieselbe
   Lücke wie oben bei .topnav, nur für angedockte Panels — die Leiste
   deckte den Kopf jedes Panels ab (Titel + die drei Icons), weil
   .left-panel unconditional bei top:0 beginnt (styles.css:946) und mit
   z-index:20 unter der Leiste liegt (z-index: var(--z-toast), 5000).
   Betrifft ausnahmslos alle angedockten Panels — sie teilen sich alle
   dieselbe .left-panel-Basisregel, keins hat eine eigene top-Angabe.
   Bewusst margin-top statt top: .left-panel setzt sowohl top:0 als
   auch bottom:0 bei height:auto — die Box-Höhe wird daraus abgeleitet
   (containing-block-Höhe minus top/bottom/margins). margin-top nimmt
   also nur oben Platz weg, das untere Ende bleibt bei bottom:0 exakt
   am Viewport-Rand, kein Abschneiden unten. */
.app.is-public-demo .left-panel { margin-top: var(--demo-bar-h); }
/* NEU (2026-08-19, beim Staging-Test des Detail-Panel-Umbaus gefunden):
   dieselbe Lücke, dritter Fall — .right-panel steht wie .left-panel
   unconditional bei top:0 (siehe dort) und lag im Demo-Kontext unter der
   41px hohen Leiste. Gemessen: elementFromPoint() auf dem "DETAILS"-Titel
   lieferte .demo-bar-hint, nicht den Panel-Kopf.

   BESTAND, nicht neu: Titel und Schliessen-Kreuz waren dort schon immer
   verdeckt. Es fiel nur nicht auf, weil im Kopf nichts stand, was man
   BRAUCHT. Mit der Upload-Navigation (diese Runde aus dem Body hier
   herauf) waere daraus ein echter Funktionsverlust geworden: ein
   Demo-Besucher haette nicht mehr zwischen den Uploads einer CARD
   blaettern koennen. Deshalb hier mitbehoben statt als Backlog-Punkt
   liegen gelassen — dieselbe eine Zeile wie bei .left-panel, gleiche
   margin-top-statt-top-Begruendung (top:0 + bottom:0 bei height:auto).
   Betrifft AUSSCHLIESSLICH die oeffentliche Demo; in jedem angemeldeten
   Konto gibt es keine Demo-Leiste und die Regel greift nicht. */
.app.is-public-demo .right-panel { margin-top: var(--demo-bar-h); }
.demo-bar-badge {
  padding: 3px 8px;
  border: 1px solid var(--line);
  color: var(--ink-dim);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.demo-bar-hint { flex: 1; }
.demo-bar-hint strong { color: var(--ink); font-weight: 600; }
/* Der zuruecksetzende Knopf steht links und zurueckhaltend, der
   werbende rechts — damit niemand das eine trifft, wenn er das
   andere wollte. */
.demo-bar-reset { margin-left: auto; }
.demo-bar-signup { text-decoration: none; }
/* GEFUNDEN (2026-08-15, Nachtrag-Staging-Test): bei wenig Platz (getestet
   480px) bricht der lange Hint-Text um, die Leiste waechst auf ~180px
   und ueberdeckt Board-Inhalt. Dieselbe Regel wie beim Vorbild
   .readonly-bar-hint oben (identischer Schwellwert). */
@media (max-width: 640px) { .demo-bar-hint { display: none; } }
