@rslsp1/fa-app-tools 9.4.4 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -135,7 +135,7 @@ __export(index_exports, {
135
135
  module.exports = __toCommonJS(index_exports);
136
136
 
137
137
  // package.json
138
- var version = "9.4.4";
138
+ var version = "10.0.1";
139
139
 
140
140
  // src/hooks/useOnClickOutside.ts
141
141
  var import_react = require("react");
@@ -174,7 +174,7 @@ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
174
174
  }
175
175
 
176
176
  // src/features/szenen/szenenViewStyles.generated.ts
177
- var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-erweitert,\n.szenen-prompts,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__hinweis {\n margin: var(--space-xs) 0 0;\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n/* Im Kopf traegt die Zeile selbst den Abstand \u2014 sonst kaeme er doppelt. */\n.szenen-runs__promptkopf > h5 {\n margin: 0;\n}\n\n/* Ueberschrift und Kopierknopf in einer Zeile. Der Knopf gehoert an den Text,\n den er kopiert \u2014 nicht an den Rand der Runde, wo er neben dem falschen\n Abschnitt stuende. */\n.szenen-runs__promptkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n margin: 0 0 var(--space-xs);\n}\n\n/* Ein kompakter Knopf, kein zweiter Titel: Der Prompt ist die Hauptsache des\n Abschnitts, die Aktion daneben darf ihn nicht ueberstimmen. Rahmen und\n Hintergrund kommen aus `.szenen-view button` und bleiben \u2014 dieselbe Optik wie\n jeder andere Knopf der Ansicht; hier wird nur die Groesse zurueckgenommen.\n Zwei Klassen im Selektor, weil `.szenen-view button` sonst spezifischer ist. */\n.szenen-runs__promptkopf .szenen-runs__kopieren {\n min-height: 0;\n padding: 0.1rem 0.45rem;\n font-size: 0.72rem;\n}\n\n/* Regel 11: die Bestaetigung nennt die Zahl, nicht nur \u201EErfolgreich". */\n.szenen-runs__kopiert {\n color: var(--color-text-tertiary, var(--color-text-secondary));\n font-size: 0.72rem;\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-bilder__modellwahl,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n/* \u201ENeues Modell hinzufuegen" im Importdialog \u2014 dieselben Abstaende wie jedes\n andere Formular dieser Ansicht, damit es kein Fremdkoerper wird. */\n.szenen-feld__zielmodell-neu {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n padding: var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-feld__zielmodell-neu-aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n\n/* Bereich `Prompts` \u2014 die herkunftsuebergreifende Sicht (P1-A).\n *\n * Die Eintraege selbst rendert `RundenEintrag` und tragen damit die\n * `szenen-runs__*`-Regeln. Hier stehen nur Rahmen und Kennzeichen; eine zweite\n * Laufdarstellung gibt es nicht, also braucht sie auch kein zweites Aussehen. */\n.szenen-prompts__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-prompts__heading {\n margin: 0;\n font-size: 1.05rem;\n margin-right: auto;\n}\n\n.szenen-view__fokus {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-prompts__fokus {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-accent, #0d9488);\n border-radius: var(--radius-sm, 4px);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__filter {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__eintrag {\n list-style: none;\n margin-bottom: var(--space-md);\n}\n\n.szenen-prompts__lauf {\n padding-left: 0;\n}\n\n.szenen-prompts__herkunft {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-prompts__hinweis {\n font-style: italic;\n}\n\n.szenen-prompts__ausgeblendet {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n ZIP-MODELLVORSCHLAG (modaler Best\xE4tigungsdialog)\n ========================================================================== */\n\n/* Wie die grosse Bildansicht: eigener Grund, `position: fixed`, damit der\n Dialog im Flow-Canvas und in React gleichermassen \xFCber allem liegt. */\n.szenen-zipmodell {\n position: fixed;\n inset: 0;\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-md);\n background: rgba(0, 0, 0, 0.72);\n}\n\n.szenen-zipmodell__karte {\n display: grid;\n gap: var(--space-sm);\n width: min(34rem, 100%);\n max-height: 100%;\n overflow-y: auto;\n padding: var(--space-md);\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-zipmodell__titel {\n margin: 0;\n font-size: 1rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__treffer,\n.szenen-zipmodell__vertrauen,\n.szenen-zipmodell__fallback {\n margin: 0;\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__vertrauen {\n color: var(--color-text-secondary);\n}\n\n/* Ein schwacher oder mehrdeutiger Treffer muss sich vom sicheren unterscheiden,\n sonst ist die Warnung nur ein weiterer Satz. */\n.szenen-zipmodell__warnung {\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n color: var(--color-text-primary);\n background: rgba(234, 179, 8, 0.14);\n border-left: 3px solid #eab308;\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen {\n display: grid;\n gap: 0.2rem;\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen > label {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.szenen-zipmodell__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n/* ==========================================================================\n MEHR LADEN \u2014 der gemeinsame Fuss jeder seitenweise geladenen Liste\n ========================================================================== */\n\n.szenen-mehr-laden {\n display: grid;\n gap: var(--space-xs);\n justify-items: start;\n padding: var(--space-xs) 0;\n}\n\n.szenen-mehr-laden__button {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.szenen-mehr-laden__button:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.szenen-mehr-laden__fehler {\n margin: 0;\n color: var(--color-danger, #f87171);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n PROMPTKARTE \u2014 Tags, Vorschau und Bildzahl\n (Kette `szenen-prompt-anzeige-bilder-tags`)\n\n Der Zuschnitt ist der der Library-Karte (`.library-item` + der absolut\n gesetzte Bildstreifen in `LibraryTab`): Textteil links, Deckbild rechts am\n Rand ueber die ganze Kartenhoehe, Bildzahl als Plakette darauf. Dieselben\n Werte (7rem Streifenbreite, linke Trennlinie, Plakette unten rechts), nur\n hier statt in Tailwind-Klassen \u2014 die Szenen-Ansicht teilt dieses Stylesheet\n byteweise mit Flow, und Flow hat kein Tailwind.\n ========================================================================== */\n\n.szenen-promptkarte {\n position: relative;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--space-sm);\n}\n\n/* Nur wo ein Bild liegt, wird Platz dafuer freigehalten. Ohne Bild nutzt der\n Textteil die ganze Breite \u2014 keine leere Spalte. */\n.szenen-promptkarte--mitbild .szenen-promptkarte__inhalt {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-promptkarte__bild {\n position: absolute;\n top: 0;\n right: 0;\n width: 7rem;\n height: 100%;\n /* Hoechstens so hoch wie breit \u2014 der Streifen bleibt eine Kachel.\n `height: 100%` allein band ihn an die **Kartenhoehe**, und die ist in der\n aufgeklappten Runde die Hoehe des ganzen Prompts: Das Deckbild sprang\n dann als mehrere hundert Pixel hoher, auf Streifenbreite beschnittener\n Balken an den rechten Rand. Geschlossen aendert die Grenze nichts \u2014 dort\n ist die Karte niedriger als 7rem. */\n max-height: 7rem;\n border-left: 1px solid var(--color-border);\n background: #000;\n}\n\n/* Die gemeinsame Kachel bringt `width: 100%` und `aspect-ratio: 1` mit \u2014 hier\n fuellt sie stattdessen den Streifen, genau wie das Library-Deckbild. */\n.szenen-promptkarte__bild .szenen-bildkachel {\n width: 100%;\n height: 100%;\n aspect-ratio: auto;\n}\n\n.szenen-promptkarte__bildzahl {\n position: absolute;\n right: 0.25rem;\n bottom: 0.25rem;\n padding: 0.1rem 0.35rem;\n border-radius: var(--radius-sm);\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n font-size: 0.62rem;\n font-weight: 700;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/* Ein Kasten je Karte, nicht zwei.\n *\n * `.szenen-view button` (master.css) gibt **jedem** Knopf der Ansicht Rahmen\n * und Hintergrund und schlaegt dabei `.szenen-runs__kopf` (eine Klasse gegen\n * Klasse plus Element). In der Laufhistorie ist das richtig: Dort ist der Kopf\n * der Eintrag. In der Promptkarte ist die Karte der Eintrag \u2014 ein zweiter\n * Kasten darin waere die doppelte Umrandung, die die Library nicht hat.\n * Der Hover braucht eine eigene Zeile, weil die Regel dort spezifischer ist. */\n.szenen-promptkarte .szenen-runs__kopf,\n.szenen-promptkarte .szenen-runs__kopf:hover:not(:disabled) {\n min-height: 0;\n padding: var(--space-xs) 0;\n background: transparent;\n border: none;\n}\n\n/* Dieselbe Karte in der Elementliste.\n *\n * Dort ist der Auswahlknopf die Karte \u2014 er traegt Rahmen, Hintergrund und die\n * Auswahlmarkierung. Der Bildstreifen liegt deshalb **darueber** statt darin:\n * Ein `<button>` nimmt nur Phrasing Content auf, ein Streifen-`div` darin\n * waere ungueltiges Markup. Damit der rechte Kartenrand trotzdem auswaehlbar\n * bleibt, laesst der Streifen Klicks durch. */\n.szenen-elementkarte {\n position: relative;\n}\n\n.szenen-elementkarte--mitbild > button {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-elementkarte > .szenen-promptkarte__bild {\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n}\n\n.szenen-promptkarte__bild--durchklickbar {\n pointer-events: none;\n}\n\n/* Null Bilder: die Zahl, keine Flaeche. */\n.szenen-promptkarte__ohnebild {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.72rem;\n}\n\n/* Die Tagliste bricht um. Auf einem Telefon ist das der Unterschied zwischen\n einer kompakten Karte und einer Zeile, die seitlich herauslaeuft. */\n.szenen-tagliste {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n margin: 0 0 var(--space-xs);\n padding: 0;\n list-style: none;\n}\n\n.szenen-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.05rem 0.4rem;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n font-size: 0.72rem;\n line-height: 1.5;\n white-space: nowrap;\n}\n\n/* Herkunft sichtbar, aber ohne zweite Bedeutungsebene: Ein Prompt-Tag steht\n voll, ein reiner Library-Tag zurueckgenommen. Die Karte soll schnell lesbar\n sein, nicht zwei Farbcodes lehren. */\n.szenen-tag[data-herkunft="library"] {\n border-style: dashed;\n opacity: 0.85;\n}\n\n.szenen-tag--rest {\n border-style: none;\n opacity: 0.7;\n}\n\n.szenen-tag__weg {\n border: none;\n background: none;\n padding: 0;\n color: var(--color-text-secondary, inherit);\n font-size: 0.85rem;\n line-height: 1;\n cursor: pointer;\n}\n\n.szenen-tag__weg:disabled {\n cursor: progress;\n opacity: 0.5;\n}\n\n.szenen-taggeber {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-xs);\n margin-bottom: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-taggeber__zeile {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: 0;\n}\n\n.szenen-taggeber__label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n opacity: 0.75;\n}\n\n.szenen-taggeber__leer,\n.szenen-taggeber__hinweis {\n font-size: 0.72rem;\n opacity: 0.75;\n}\n\n.szenen-taggeber input[type="text"] {\n flex: 1 1 8rem;\n min-width: 0;\n padding: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n BILDAUFTR\xC4GE \u2014 die bestandsweite Arbeitsqueue\n Kette `szenen-bildauftraege-arbeitsqueue`, Schritt 01.\n ========================================================================== */\n\n.szenen-bildauftraege {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bildauftraege__steuerung,\n.szenen-bildauftraege__filter,\n.szenen-bildauftraege__aktionen {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n}\n\n/* Die Vorschau steht oben und beh\xE4lt ihren Platz, auch solange noch kein Bild\n vorliegt \u2014 sie ist die Kontrollfl\xE4che des zweiten Browserfensters (\xA75). */\n.szenen-bildauftraege__letztes {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-bildauftraege__letztes h3 {\n margin: 0;\n font-size: 0.95rem;\n}\n\n.szenen-bildauftraege__letztes .szenen-bildkachel {\n width: min(100%, 22rem);\n height: auto;\n}\n\n.szenen-bildauftraege__lauf {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__lauf p {\n margin: 0.1rem 0;\n}\n\n.szenen-bildauftraege__liste {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildauftraege__zeile {\n display: grid;\n gap: 0.25rem;\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__kopf,\n.szenen-bildauftraege__prompt {\n margin: 0;\n}\n\n.szenen-bildauftraege__meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.15rem var(--space-sm);\n margin: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-bildauftraege__prompt {\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n';
177
+ var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-erweitert,\n.szenen-prompts,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__hinweis {\n margin: var(--space-xs) 0 0;\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n/* Im Kopf traegt die Zeile selbst den Abstand \u2014 sonst kaeme er doppelt. */\n.szenen-runs__promptkopf > h5 {\n margin: 0;\n}\n\n/* Ueberschrift und Kopierknopf in einer Zeile. Der Knopf gehoert an den Text,\n den er kopiert \u2014 nicht an den Rand der Runde, wo er neben dem falschen\n Abschnitt stuende. */\n.szenen-runs__promptkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n margin: 0 0 var(--space-xs);\n}\n\n/* Ein kompakter Knopf, kein zweiter Titel: Der Prompt ist die Hauptsache des\n Abschnitts, die Aktion daneben darf ihn nicht ueberstimmen. Rahmen und\n Hintergrund kommen aus `.szenen-view button` und bleiben \u2014 dieselbe Optik wie\n jeder andere Knopf der Ansicht; hier wird nur die Groesse zurueckgenommen.\n Zwei Klassen im Selektor, weil `.szenen-view button` sonst spezifischer ist. */\n.szenen-runs__promptkopf .szenen-runs__kopieren {\n min-height: 0;\n padding: 0.1rem 0.45rem;\n font-size: 0.72rem;\n}\n\n/* Regel 11: die Bestaetigung nennt die Zahl, nicht nur \u201EErfolgreich". */\n.szenen-runs__kopiert {\n color: var(--color-text-tertiary, var(--color-text-secondary));\n font-size: 0.72rem;\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-bilder__modellwahl,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n/* \u201ENeues Modell hinzufuegen" im Importdialog \u2014 dieselben Abstaende wie jedes\n andere Formular dieser Ansicht, damit es kein Fremdkoerper wird. */\n.szenen-feld__zielmodell-neu {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n padding: var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-feld__zielmodell-neu-aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n\n/* Bereich `Prompts` \u2014 die herkunftsuebergreifende Sicht (P1-A).\n *\n * Die Eintraege selbst rendert `RundenEintrag` und tragen damit die\n * `szenen-runs__*`-Regeln. Hier stehen nur Rahmen und Kennzeichen; eine zweite\n * Laufdarstellung gibt es nicht, also braucht sie auch kein zweites Aussehen. */\n.szenen-prompts__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-prompts__heading {\n margin: 0;\n font-size: 1.05rem;\n margin-right: auto;\n}\n\n.szenen-view__fokus {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-prompts__fokus {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-accent, #0d9488);\n border-radius: var(--radius-sm, 4px);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__filter {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__eintrag {\n list-style: none;\n margin-bottom: var(--space-md);\n}\n\n.szenen-prompts__lauf {\n padding-left: 0;\n}\n\n.szenen-prompts__herkunft {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-prompts__hinweis {\n font-style: italic;\n}\n\n.szenen-prompts__ausgeblendet {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n ZIP-MODELLVORSCHLAG (modaler Best\xE4tigungsdialog)\n ========================================================================== */\n\n/* Wie die grosse Bildansicht: eigener Grund, `position: fixed`, damit der\n Dialog im Flow-Canvas und in React gleichermassen \xFCber allem liegt. */\n.szenen-zipmodell {\n position: fixed;\n inset: 0;\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-md);\n background: rgba(0, 0, 0, 0.72);\n}\n\n.szenen-zipmodell__karte {\n display: grid;\n gap: var(--space-sm);\n width: min(34rem, 100%);\n max-height: 100%;\n overflow-y: auto;\n padding: var(--space-md);\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-zipmodell__titel {\n margin: 0;\n font-size: 1rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__treffer,\n.szenen-zipmodell__vertrauen,\n.szenen-zipmodell__fallback {\n margin: 0;\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__vertrauen {\n color: var(--color-text-secondary);\n}\n\n/* Ein schwacher oder mehrdeutiger Treffer muss sich vom sicheren unterscheiden,\n sonst ist die Warnung nur ein weiterer Satz. */\n.szenen-zipmodell__warnung {\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n color: var(--color-text-primary);\n background: rgba(234, 179, 8, 0.14);\n border-left: 3px solid #eab308;\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen {\n display: grid;\n gap: 0.2rem;\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen > label {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.szenen-zipmodell__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n/* ==========================================================================\n MEHR LADEN \u2014 der gemeinsame Fuss jeder seitenweise geladenen Liste\n ========================================================================== */\n\n.szenen-mehr-laden {\n display: grid;\n gap: var(--space-xs);\n justify-items: start;\n padding: var(--space-xs) 0;\n}\n\n.szenen-mehr-laden__button {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.szenen-mehr-laden__button:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.szenen-mehr-laden__fehler {\n margin: 0;\n color: var(--color-danger, #f87171);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n PROMPTKARTE \u2014 Tags, Vorschau und Bildzahl\n (Kette `szenen-prompt-anzeige-bilder-tags`)\n\n Der Zuschnitt ist der der Library-Karte (`.library-item` + der absolut\n gesetzte Bildstreifen in `LibraryTab`): Textteil links, Deckbild rechts am\n Rand ueber die ganze Kartenhoehe, Bildzahl als Plakette darauf. Dieselben\n Werte (7rem Streifenbreite, linke Trennlinie, Plakette unten rechts), nur\n hier statt in Tailwind-Klassen \u2014 die Szenen-Ansicht teilt dieses Stylesheet\n byteweise mit Flow, und Flow hat kein Tailwind.\n ========================================================================== */\n\n.szenen-promptkarte {\n position: relative;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--space-sm);\n}\n\n/* Nur wo ein Bild liegt, wird Platz dafuer freigehalten. Ohne Bild nutzt der\n Textteil die ganze Breite \u2014 keine leere Spalte. */\n.szenen-promptkarte--mitbild .szenen-promptkarte__inhalt {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-promptkarte__bild {\n position: absolute;\n top: 0;\n right: 0;\n width: 7rem;\n height: 100%;\n /* Hoechstens so hoch wie breit \u2014 der Streifen bleibt eine Kachel.\n `height: 100%` allein band ihn an die **Kartenhoehe**, und die ist in der\n aufgeklappten Runde die Hoehe des ganzen Prompts: Das Deckbild sprang\n dann als mehrere hundert Pixel hoher, auf Streifenbreite beschnittener\n Balken an den rechten Rand. Geschlossen aendert die Grenze nichts \u2014 dort\n ist die Karte niedriger als 7rem. */\n max-height: 7rem;\n border-left: 1px solid var(--color-border);\n background: #000;\n}\n\n/* Die gemeinsame Kachel bringt `width: 100%` und `aspect-ratio: 1` mit \u2014 hier\n fuellt sie stattdessen den Streifen, genau wie das Library-Deckbild. */\n.szenen-promptkarte__bild .szenen-bildkachel {\n width: 100%;\n height: 100%;\n aspect-ratio: auto;\n}\n\n.szenen-promptkarte__bildzahl {\n position: absolute;\n right: 0.25rem;\n bottom: 0.25rem;\n padding: 0.1rem 0.35rem;\n border-radius: var(--radius-sm);\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n font-size: 0.62rem;\n font-weight: 700;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/* Ein Kasten je Karte, nicht zwei.\n *\n * `.szenen-view button` (master.css) gibt **jedem** Knopf der Ansicht Rahmen\n * und Hintergrund und schlaegt dabei `.szenen-runs__kopf` (eine Klasse gegen\n * Klasse plus Element). In der Laufhistorie ist das richtig: Dort ist der Kopf\n * der Eintrag. In der Promptkarte ist die Karte der Eintrag \u2014 ein zweiter\n * Kasten darin waere die doppelte Umrandung, die die Library nicht hat.\n * Der Hover braucht eine eigene Zeile, weil die Regel dort spezifischer ist. */\n.szenen-promptkarte .szenen-runs__kopf,\n.szenen-promptkarte .szenen-runs__kopf:hover:not(:disabled) {\n min-height: 0;\n padding: var(--space-xs) 0;\n background: transparent;\n border: none;\n}\n\n/* Dieselbe Karte in der Elementliste.\n *\n * Dort ist der Auswahlknopf die Karte \u2014 er traegt Rahmen, Hintergrund und die\n * Auswahlmarkierung. Der Bildstreifen liegt deshalb **darueber** statt darin:\n * Ein `<button>` nimmt nur Phrasing Content auf, ein Streifen-`div` darin\n * waere ungueltiges Markup. Damit der rechte Kartenrand trotzdem auswaehlbar\n * bleibt, laesst der Streifen Klicks durch. */\n.szenen-elementkarte {\n position: relative;\n}\n\n.szenen-elementkarte--mitbild > button {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-elementkarte > .szenen-promptkarte__bild {\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n}\n\n.szenen-promptkarte__bild--durchklickbar {\n pointer-events: none;\n}\n\n/* Null Bilder: die Zahl, keine Flaeche. */\n.szenen-promptkarte__ohnebild {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.72rem;\n}\n\n/* Die Tagliste bricht um. Auf einem Telefon ist das der Unterschied zwischen\n einer kompakten Karte und einer Zeile, die seitlich herauslaeuft. */\n.szenen-tagliste {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n margin: 0 0 var(--space-xs);\n padding: 0;\n list-style: none;\n}\n\n.szenen-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.05rem 0.4rem;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n font-size: 0.72rem;\n line-height: 1.5;\n white-space: nowrap;\n}\n\n/* Herkunft sichtbar, aber ohne zweite Bedeutungsebene: Ein Prompt-Tag steht\n voll, ein reiner Library-Tag zurueckgenommen. Die Karte soll schnell lesbar\n sein, nicht zwei Farbcodes lehren. */\n.szenen-tag[data-herkunft="library"] {\n border-style: dashed;\n opacity: 0.85;\n}\n\n.szenen-tag--rest {\n border-style: none;\n opacity: 0.7;\n}\n\n.szenen-tag__weg {\n border: none;\n background: none;\n padding: 0;\n color: var(--color-text-secondary, inherit);\n font-size: 0.85rem;\n line-height: 1;\n cursor: pointer;\n}\n\n.szenen-tag__weg:disabled {\n cursor: progress;\n opacity: 0.5;\n}\n\n.szenen-taggeber {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-xs);\n margin-bottom: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-taggeber__zeile {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: 0;\n}\n\n.szenen-taggeber__label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n opacity: 0.75;\n}\n\n.szenen-taggeber__leer,\n.szenen-taggeber__hinweis {\n font-size: 0.72rem;\n opacity: 0.75;\n}\n\n.szenen-taggeber input[type="text"] {\n flex: 1 1 8rem;\n min-width: 0;\n padding: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n BILDAUFTR\xC4GE \u2014 die bestandsweite Arbeitsqueue\n Kette `szenen-bildauftraege-arbeitsqueue`, Schritt 01.\n ========================================================================== */\n\n.szenen-bildauftraege {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bildauftraege__steuerung,\n.szenen-bildauftraege__filter,\n.szenen-bildauftraege__aktionen,\n.szenen-bildauftraege__nachladen,\n.szenen-bildauftraege__blaettern {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n}\n\n/* Der manuelle Refresh \u2014 Schritt 03 \xA72. Er steht oben und sichtbar, weil er\n die einzige Stelle ist, an der dieser Bereich nachlaedt: Es gibt keinen\n Timer mehr, den er ersetzen koennte. */\n.szenen-bildauftraege__nachladen {\n justify-content: flex-start;\n}\n\n.szenen-bildauftraege__nachladen span {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* Die Blaetterleiste der Bildkontrolle \u2014 Schritt 03 \xA73.1. */\n.szenen-bildauftraege__blaettern {\n margin: 0;\n}\n\n/* \u201EWeitere Bilder" \u2014 Schritt 03 \xA74. Eingerueckt, weil es zu genau einer\n Auftragszeile gehoert und kein eigener Bereich ist. */\n.szenen-bildauftraege__weitere {\n display: grid;\n gap: var(--space-xs);\n margin-top: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__weitere p {\n margin: 0;\n}\n\n/* Die Vorschau steht oben und beh\xE4lt ihren Platz, auch solange noch kein Bild\n vorliegt \u2014 sie ist die Kontrollfl\xE4che des zweiten Browserfensters (\xA75). */\n.szenen-bildauftraege__letztes {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-bildauftraege__letztes h3 {\n margin: 0;\n font-size: 0.95rem;\n}\n\n.szenen-bildauftraege__letztes .szenen-bildkachel {\n width: min(100%, 22rem);\n height: auto;\n}\n\n.szenen-bildauftraege__lauf {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__lauf p {\n margin: 0.1rem 0;\n}\n\n.szenen-bildauftraege__liste {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildauftraege__zeile {\n display: grid;\n gap: 0.25rem;\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__kopf,\n.szenen-bildauftraege__prompt {\n margin: 0;\n}\n\n.szenen-bildauftraege__meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.15rem var(--space-sm);\n margin: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-bildauftraege__prompt {\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n';
178
178
 
179
179
  // src/lib/utils.ts
180
180
  var getFormattedTimestamp = () => {
@@ -20150,7 +20150,6 @@ async function fuehreQueueAus(optionen) {
20150
20150
 
20151
20151
  // src/features/szenen/areas/ImageJobsArea.tsx
20152
20152
  var import_jsx_runtime52 = require("react/jsx-runtime");
20153
- var AKTUALISIERUNG_MS = 5e3;
20154
20153
  var STATUS_TEXT2 = {
20155
20154
  pending: "wartet auf \xDCbernahme",
20156
20155
  claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
@@ -20166,7 +20165,10 @@ var FILTER = [
20166
20165
  { id: "failed", titel: "Fehlgeschlagen" },
20167
20166
  { id: "all", titel: "Alle" }
20168
20167
  ];
20169
- function useDauerAbfrage(frage, abhaengigkeiten) {
20168
+ function istAbgeschlossen(job) {
20169
+ return job.status !== "pending" && job.status !== "claimed";
20170
+ }
20171
+ function useNachladbareAbfrage(frage, abhaengigkeiten) {
20170
20172
  const [stand, setStand] = (0, import_react59.useState)(
20171
20173
  () => ({ wert: null, fehler: null, laedt: true })
20172
20174
  );
@@ -20198,10 +20200,11 @@ function useDauerAbfrage(frage, abhaengigkeiten) {
20198
20200
  var ImageJobsArea = ({
20199
20201
  port,
20200
20202
  onPromptOeffnen,
20201
- aktualisierungMs = AKTUALISIERUNG_MS,
20202
- warte
20203
+ warte,
20204
+ jetzt
20203
20205
  }) => {
20204
20206
  const auftraege = port.imageJobs;
20207
+ const uhr = jetzt ?? (() => /* @__PURE__ */ new Date());
20205
20208
  const [filter, setFilter] = (0, import_react59.useState)("pending");
20206
20209
  const [pauseSekunden, setPauseSekunden] = (0, import_react59.useState)(PAUSE_SEKUNDEN_VORGABE);
20207
20210
  const [takt, setTakt] = (0, import_react59.useState)(0);
@@ -20211,22 +20214,21 @@ var ImageJobsArea = ({
20211
20214
  const abbruchRef = (0, import_react59.useRef)(false);
20212
20215
  const [einzeln, setEinzeln] = (0, import_react59.useState)(null);
20213
20216
  const [notiz, setNotiz] = (0, import_react59.useState)(null);
20214
- (0, import_react59.useEffect)(() => {
20215
- if (aktualisierungMs <= 0) return void 0;
20216
- const uhr = setInterval(() => setTakt((t) => t + 1), aktualisierungMs);
20217
- return () => clearInterval(uhr);
20218
- }, [aktualisierungMs]);
20219
- const liste = useDauerAbfrage(
20217
+ const liste = useNachladbareAbfrage(
20220
20218
  () => auftraege?.listAllJobs ? auftraege.listAllJobs({ status: filter }) : Promise.resolve({
20221
20219
  kind: "ok",
20222
20220
  value: { eintraege: [], nextCursor: null, gesamt: 0, offen: 0 }
20223
20221
  }),
20224
20222
  [auftraege, filter, takt]
20225
20223
  );
20226
- const vorschau = useDauerAbfrage(
20224
+ const vorschau = useNachladbareAbfrage(
20227
20225
  () => auftraege?.latestImage ? auftraege.latestImage() : Promise.resolve({ kind: "ok", value: null }),
20228
20226
  [auftraege, takt]
20229
20227
  );
20228
+ const [zuletzt, setZuletzt] = (0, import_react59.useState)(null);
20229
+ (0, import_react59.useEffect)(() => {
20230
+ if (!liste.laedt && liste.wert !== null) setZuletzt(uhr().toISOString());
20231
+ }, [liste.laedt, liste.wert]);
20230
20232
  const seite = liste.wert;
20231
20233
  const eintraege = seite?.eintraege ?? [];
20232
20234
  const offen = seite?.offen ?? 0;
@@ -20294,16 +20296,28 @@ var ImageJobsArea = ({
20294
20296
  }
20295
20297
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "szenen-bildauftraege", "data-testid": "bereich-bildauftraege", children: [
20296
20298
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-area__lead", children: [
20297
- "Alle Bildauftr\xE4ge dieses Bestands \u2014 unabh\xE4ngig davon, zu welchem Promptlauf sie geh\xF6ren. Der Bereich ist als dauerhaft ge\xF6ffnetes Arbeitsfenster gedacht und liest den Bestand alle",
20298
- " ",
20299
- Math.round(aktualisierungMs / 1e3),
20300
- " Sekunden nach."
20299
+ "Alle Bildauftr\xE4ge dieses Bestands \u2014 unabh\xE4ngig davon, zu welchem Promptlauf sie geh\xF6ren. Der Bereich ist als dauerhaft ge\xF6ffnetes Arbeitsfenster gedacht und l\xE4dt ",
20300
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("strong", { children: "nur auf Klick" }),
20301
+ " nach: Es l\xE4uft kein Timer und keine Hintergrundabfrage."
20302
+ ] }),
20303
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("section", { className: "szenen-bildauftraege__nachladen", "aria-label": "Nachladen", children: [
20304
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20305
+ "button",
20306
+ {
20307
+ type: "button",
20308
+ "data-testid": "bildauftraege-aktualisieren",
20309
+ onClick: neuLesen,
20310
+ title: "Auftr\xE4ge, Zust\xE4nde und neue Bilder neu vom Bestand lesen",
20311
+ children: "\u27F3 Aktualisieren"
20312
+ }
20313
+ ),
20314
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" })
20301
20315
  ] }),
20302
20316
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20303
- LetztesBild,
20317
+ Bildkontrolle,
20304
20318
  {
20305
20319
  port,
20306
- bild: vorschau.wert ?? null,
20320
+ stand: vorschau.wert ?? null,
20307
20321
  fehler: vorschau.fehler,
20308
20322
  laedt: vorschau.laedt && vorschau.wert === null,
20309
20323
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
@@ -20390,6 +20404,10 @@ var ImageJobsArea = ({
20390
20404
  gesperrt: queueLaeuft || einzeln !== null,
20391
20405
  laufend: einzeln === eintrag.job.job_id || lauf?.aktuell === eintrag.job.job_id,
20392
20406
  onStart: () => void einzelStart(eintrag.job.job_id),
20407
+ onAngelegt: (text) => {
20408
+ setNotiz({ tone: "ok", text });
20409
+ neuLesen();
20410
+ },
20393
20411
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
20394
20412
  },
20395
20413
  eintrag.job.job_id
@@ -20441,11 +20459,14 @@ var AuftragsZeile = ({
20441
20459
  gesperrt,
20442
20460
  laufend,
20443
20461
  onStart,
20462
+ onAngelegt,
20444
20463
  onPromptOeffnen
20445
20464
  }) => {
20446
20465
  const { job, herkunft } = eintrag;
20447
20466
  const [ergebnisOffen, setErgebnisOffen] = (0, import_react59.useState)(false);
20467
+ const [weitereOffen, setWeitereOffen] = (0, import_react59.useState)(false);
20448
20468
  const hatErgebnis = Boolean(job.generation_batch_id) && (job.status === "succeeded" || job.status === "partial");
20469
+ const kannWeitere = Boolean(port.imageJobs?.requestMoreImages) && istAbgeschlossen(job);
20449
20470
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20450
20471
  "li",
20451
20472
  {
@@ -20484,6 +20505,13 @@ var AuftragsZeile = ({
20484
20505
  "Erstellt ",
20485
20506
  job.created_at || "\u2014"
20486
20507
  ] }),
20508
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { "data-testid": `bildauftrag-lauf-bilder-${job.job_id}`, children: [
20509
+ "Bisher ",
20510
+ herkunft.bilder_im_lauf,
20511
+ " ",
20512
+ herkunft.bilder_im_lauf === 1 ? "Bild" : "Bilder",
20513
+ " in diesem Promptlauf"
20514
+ ] }),
20487
20515
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { className: "szenen-bilder__auftragskennung", children: job.job_id })
20488
20516
  ] }),
20489
20517
  herkunft.prompt_auszug && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
@@ -20523,8 +20551,18 @@ var AuftragsZeile = ({
20523
20551
  onClick: () => setErgebnisOffen((o) => !o),
20524
20552
  children: ergebnisOffen ? "Ergebnisse ausblenden" : "Ergebnisse ansehen"
20525
20553
  }
20554
+ ),
20555
+ kannWeitere && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20556
+ "button",
20557
+ {
20558
+ type: "button",
20559
+ "data-testid": `bildauftrag-weitere-${job.job_id}`,
20560
+ onClick: () => setWeitereOffen((o) => !o),
20561
+ children: weitereOffen ? "Weitere Bilder schlie\xDFen" : "Weitere Bilder"
20562
+ }
20526
20563
  )
20527
20564
  ] }),
20565
+ kannWeitere && weitereOffen && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(WeitereBilder, { port, job, onAngelegt }),
20528
20566
  ergebnisOffen && job.generation_batch_id && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20529
20567
  AuftragsErgebnis,
20530
20568
  {
@@ -20537,8 +20575,135 @@ var AuftragsZeile = ({
20537
20575
  }
20538
20576
  );
20539
20577
  };
20578
+ var WeitereBilder = ({ port, job, onAngelegt }) => {
20579
+ const [anzahl, setAnzahl] = (0, import_react59.useState)(1);
20580
+ const [laeuft, setLaeuft] = (0, import_react59.useState)(false);
20581
+ const [notiz, setNotiz] = (0, import_react59.useState)(null);
20582
+ const wahl = useNachladbareAbfrage(
20583
+ () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(job.prompt_run_id) : Promise.resolve({ kind: "ok", value: null }),
20584
+ [port, job.prompt_run_id]
20585
+ );
20586
+ const gewaehlt = wahl.wert ? findExecutableImageModel(
20587
+ wahl.wert,
20588
+ job.execution_target_model_key,
20589
+ job.execution_profile_id
20590
+ ) : null;
20591
+ const maxBilder = gewaehlt?.max_images ?? null;
20592
+ const anlegen = async (wieViele) => {
20593
+ const anfordern = port.imageJobs?.requestMoreImages;
20594
+ if (!anfordern || laeuft) return;
20595
+ setLaeuft(true);
20596
+ setNotiz(null);
20597
+ try {
20598
+ const ergebnis = await anfordern({
20599
+ sourceJobId: job.job_id,
20600
+ imageCount: wieViele
20601
+ });
20602
+ if (ergebnis.kind === "ok") {
20603
+ onAngelegt(
20604
+ `Neuer Bildauftrag ${ergebnis.job.job_id} f\xFCr Promptlauf ${ergebnis.job.prompt_run_id}: ${ergebnis.job.image_count} ${ergebnis.job.image_count === 1 ? "Bild" : "Bilder"} mit ${ergebnis.job.execution_target_model_key} (Profil ${ergebnis.job.execution_profile_id}). Auftrag ${job.job_id} bleibt unver\xE4ndert abgeschlossen.`
20605
+ );
20606
+ } else {
20607
+ setNotiz({
20608
+ tone: ergebnis.kind === "rejected" ? "rejected" : "error",
20609
+ text: ergebnis.message
20610
+ });
20611
+ }
20612
+ } catch (e) {
20613
+ setNotiz({
20614
+ tone: "error",
20615
+ text: `Der neue Bildauftrag zu ${job.job_id} konnte nicht angelegt werden: ` + (e instanceof Error ? e.message : String(e))
20616
+ });
20617
+ } finally {
20618
+ setLaeuft(false);
20619
+ }
20620
+ };
20621
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20622
+ "section",
20623
+ {
20624
+ className: "szenen-bildauftraege__weitere",
20625
+ "data-testid": `bildauftrag-weitere-panel-${job.job_id}`,
20626
+ "aria-label": "Weitere Bilder",
20627
+ children: [
20628
+ wahl.fehler && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", "data-testid": `bildauftrag-weitere-fehler-${job.job_id}`, children: [
20629
+ "Die ausf\xFChrbaren Bildmodelle waren nicht lesbar: ",
20630
+ wahl.fehler
20631
+ ] }),
20632
+ wahl.laedt && wahl.wert === null && !wahl.fehler && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { "data-testid": `bildauftrag-weitere-laden-${job.job_id}`, children: "Profil wird gepr\xFCft \u2026" }),
20633
+ wahl.wert !== null && !gewaehlt && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", "data-testid": `bildauftrag-weitere-ungueltig-${job.job_id}`, children: [
20634
+ "Das Profil ",
20635
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: job.execution_profile_id }),
20636
+ " von",
20637
+ " ",
20638
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: job.execution_target_model_key }),
20639
+ " ist hier nicht mehr ausf\xFChrbar. Es wird kein Ersatz geraten \u2014",
20640
+ " ",
20641
+ wahl.wert.models.length > 0 ? `ausf\xFChrbar w\xE4ren: ${wahl.wert.models.map((m) => m.selection_id).join(", ")}. Bitte den Promptlauf \xF6ffnen und dort ausdr\xFCcklich w\xE4hlen.` : `derzeit ist hier gar kein Bildmodell ausf\xFChrbar: ${wahl.wert.reason ?? "kein sichtbares Zielmodell tr\xE4gt ein passendes Profil."}`
20642
+ ] }),
20643
+ gewaehlt && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
20644
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { "data-testid": `bildauftrag-weitere-profil-${job.job_id}`, children: [
20645
+ gewaehlt.display_name,
20646
+ " bei ",
20647
+ gewaehlt.provider,
20648
+ " \xB7 bis zu",
20649
+ " ",
20650
+ gewaehlt.max_images,
20651
+ " Bilder je Auftrag \xB7 Format",
20652
+ " ",
20653
+ job.aspect_ratio ?? "\u2014"
20654
+ ] }),
20655
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__aktionen", children: [
20656
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20657
+ "button",
20658
+ {
20659
+ type: "button",
20660
+ "data-testid": `bildauftrag-plus1-${job.job_id}`,
20661
+ disabled: laeuft,
20662
+ onClick: () => void anlegen(1),
20663
+ children: "+1 Bild"
20664
+ }
20665
+ ),
20666
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { htmlFor: `bildauftrag-weitere-anzahl-${job.job_id}`, children: "Anzahl" }),
20667
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20668
+ "select",
20669
+ {
20670
+ id: `bildauftrag-weitere-anzahl-${job.job_id}`,
20671
+ "data-testid": `bildauftrag-weitere-anzahl-${job.job_id}`,
20672
+ value: anzahl,
20673
+ disabled: laeuft,
20674
+ onChange: (e) => setAnzahl(Number(e.target.value)),
20675
+ children: Array.from(
20676
+ { length: Math.max(1, maxBilder ?? 1) },
20677
+ (_, i) => i + 1
20678
+ ).map((n) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("option", { value: n, children: n }, n))
20679
+ }
20680
+ ),
20681
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20682
+ "button",
20683
+ {
20684
+ type: "button",
20685
+ "data-testid": `bildauftrag-weitere-start-${job.job_id}`,
20686
+ disabled: laeuft,
20687
+ onClick: () => void anlegen(anzahl),
20688
+ children: laeuft ? "Wird angelegt \u2026" : "Erzeugen"
20689
+ }
20690
+ )
20691
+ ] })
20692
+ ] }),
20693
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20694
+ "p",
20695
+ {
20696
+ role: notiz.tone === "error" ? "alert" : void 0,
20697
+ "data-testid": `bildauftrag-weitere-notiz-${job.job_id}`,
20698
+ children: notiz.text
20699
+ }
20700
+ )
20701
+ ]
20702
+ }
20703
+ );
20704
+ };
20540
20705
  var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
20541
- const abfrage = useDauerAbfrage(
20706
+ const abfrage = useNachladbareAbfrage(
20542
20707
  () => port.imageBatchHistory ? port.imageBatchHistory.getBatch(generationBatchId) : Promise.resolve({ kind: "ok", value: null }),
20543
20708
  [port, generationBatchId]
20544
20709
  );
@@ -20577,54 +20742,108 @@ var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
20577
20742
  }
20578
20743
  ) });
20579
20744
  };
20580
- var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
20581
- const { quellen, fehler: bildFehler } = useBildQuellen(port, bild ? [bild.bild] : []);
20745
+ var Bildkontrolle = ({ port, stand, fehler: fehler5, laedt, onPromptOeffnen }) => {
20746
+ const folge = stand?.folge ?? [];
20747
+ const neuestes = folge.length > 0 ? folge[folge.length - 1].bild.media_ref : null;
20748
+ const [gewaehlt, setGewaehlt] = (0, import_react59.useState)(null);
20749
+ const neuestesVorher = (0, import_react59.useRef)(null);
20750
+ (0, import_react59.useEffect)(() => {
20751
+ if (neuestes !== neuestesVorher.current) {
20752
+ neuestesVorher.current = neuestes;
20753
+ setGewaehlt(neuestes);
20754
+ return;
20755
+ }
20756
+ if (gewaehlt !== null && !folge.some((b) => b.bild.media_ref === gewaehlt)) {
20757
+ setGewaehlt(neuestes);
20758
+ }
20759
+ }, [neuestes, folge, gewaehlt]);
20760
+ const stelle = folge.findIndex((b) => b.bild.media_ref === gewaehlt);
20761
+ const index = stelle >= 0 ? stelle : folge.length - 1;
20762
+ const aktuell = index >= 0 ? folge[index] ?? null : null;
20763
+ const { quellen, fehler: bildFehler } = useBildQuellen(port, aktuell ? [aktuell.bild] : []);
20764
+ const blaettern = (schritt) => {
20765
+ const ziel = folge[index + schritt];
20766
+ if (ziel) setGewaehlt(ziel.bild.media_ref);
20767
+ };
20582
20768
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20583
20769
  "section",
20584
20770
  {
20585
20771
  className: "szenen-bildauftraege__letztes",
20586
20772
  "data-testid": "bildauftraege-letztes-bild",
20587
- "aria-label": "Letztes erzeugtes Bild",
20773
+ "aria-label": "Bildkontrolle",
20588
20774
  children: [
20589
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h3", { children: "Letztes erzeugtes Bild" }),
20775
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h3", { children: "Bildkontrolle" }),
20590
20776
  fehler5 && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", "data-testid": "bildauftraege-letztes-fehler", children: [
20591
- "Das zuletzt erzeugte Bild war nicht lesbar: ",
20777
+ "Der Bildstand war nicht lesbar: ",
20592
20778
  fehler5
20593
20779
  ] }),
20594
- !bild && laedt && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
20595
- !bild && !laedt && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
20596
- bild && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
20780
+ !aktuell && laedt && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
20781
+ !aktuell && !laedt && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
20782
+ aktuell && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(import_jsx_runtime52.Fragment, { children: [
20597
20783
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20598
20784
  Kachel,
20599
20785
  {
20600
- quelle: quellen.get(bild.bild.media_ref) ?? null,
20601
- alt: `Letztes erzeugtes Bild ${bild.bild.media_ref}`,
20602
- ...bildFehler.get(bild.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
20786
+ quelle: quellen.get(aktuell.bild.media_ref) ?? null,
20787
+ alt: `Bild ${index + 1} von ${folge.length} \u2014 ${aktuell.bild.media_ref}`,
20788
+ ...bildFehler.get(aktuell.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
20603
20789
  }
20604
20790
  ),
20605
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
20791
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__blaettern", children: [
20792
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20793
+ "button",
20794
+ {
20795
+ type: "button",
20796
+ "data-testid": "bildauftraege-zurueck",
20797
+ disabled: index <= 0,
20798
+ onClick: () => blaettern(-1),
20799
+ children: "\u2190 Zur\xFCck"
20800
+ }
20801
+ ),
20606
20802
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { "data-testid": "bildauftraege-letztes-position", children: [
20607
- bild.position,
20803
+ index + 1,
20608
20804
  " / ",
20609
- bild.anzahl
20805
+ folge.length
20610
20806
  ] }),
20807
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20808
+ "button",
20809
+ {
20810
+ type: "button",
20811
+ "data-testid": "bildauftraege-vor",
20812
+ disabled: index >= folge.length - 1,
20813
+ onClick: () => blaettern(1),
20814
+ children: "Vor \u2192"
20815
+ }
20816
+ )
20817
+ ] }),
20818
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
20611
20819
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20612
20820
  "Auftrag",
20613
20821
  " ",
20614
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { "data-testid": "bildauftraege-letztes-job", children: bild.job?.job_id ?? "ohne Auftrag" })
20822
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { "data-testid": "bildauftraege-letztes-job", children: aktuell.job_id ?? "ohne Auftrag" })
20823
+ ] }),
20824
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20825
+ "Batch",
20826
+ " ",
20827
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { "data-testid": "bildauftraege-letztes-batch", children: aktuell.generation_batch_id }),
20828
+ " ",
20829
+ "(Bild ",
20830
+ aktuell.position_im_batch,
20831
+ " von ",
20832
+ aktuell.anzahl_im_batch,
20833
+ ")"
20615
20834
  ] }),
20616
20835
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20617
20836
  "Promptlauf ",
20618
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: bild.prompt_run_id })
20837
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: aktuell.prompt_run_id })
20619
20838
  ] }),
20620
20839
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20621
20840
  "Modell",
20622
20841
  " ",
20623
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: bild.execution_target_model_key ?? bild.bild.model ?? "\u2014" })
20842
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: aktuell.execution_target_model_key ?? aktuell.bild.model ?? "\u2014" })
20624
20843
  ] }),
20625
20844
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20626
20845
  "Gespeichert ",
20627
- bild.gespeichert_am ?? "\u2014"
20846
+ aktuell.gespeichert_am ?? "\u2014"
20628
20847
  ] })
20629
20848
  ] }),
20630
20849
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { className: "szenen-bildauftraege__aktionen", children: onPromptOeffnen && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
@@ -20632,7 +20851,7 @@ var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
20632
20851
  {
20633
20852
  type: "button",
20634
20853
  "data-testid": "bildauftraege-letztes-prompt",
20635
- onClick: () => onPromptOeffnen(bild.prompt_run_id),
20854
+ onClick: () => onPromptOeffnen(aktuell.prompt_run_id),
20636
20855
  children: "Prompt \xF6ffnen \xB7 alle Ergebnisse ansehen"
20637
20856
  }
20638
20857
  ) })
@@ -25233,7 +25452,8 @@ function kuerze(text, zeichen) {
25233
25452
  const sauber = text.replace(/\s+/g, " ").trim();
25234
25453
  return sauber.length <= zeichen ? sauber : `${sauber.slice(0, zeichen - 1)}\u2026`;
25235
25454
  }
25236
- function herkunftFuer(job, laeufe, elementTitel) {
25455
+ function herkunftFuer(job, laeufe, elementTitel, bilderJeLauf2) {
25456
+ const bilder_im_lauf = bilderJeLauf2.get(job.prompt_run_id)?.length ?? 0;
25237
25457
  const lauf = laeufe.get(job.prompt_run_id);
25238
25458
  if (!lauf) {
25239
25459
  return {
@@ -25241,7 +25461,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
25241
25461
  szene_titel: null,
25242
25462
  szene_element_id: null,
25243
25463
  prompt_auszug: null,
25244
- lauf_gefunden: false
25464
+ lauf_gefunden: false,
25465
+ bilder_im_lauf
25245
25466
  };
25246
25467
  }
25247
25468
  const elementId = benennendesElement(lauf);
@@ -25251,7 +25472,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
25251
25472
  szene_titel: elementId ? elementTitel.get(elementId) ?? null : null,
25252
25473
  szene_element_id: elementId,
25253
25474
  prompt_auszug: istText8(positiv) ? kuerze(positiv, PROMPT_AUSZUG_ZEICHEN) : null,
25254
- lauf_gefunden: true
25475
+ lauf_gefunden: true,
25476
+ bilder_im_lauf
25255
25477
  };
25256
25478
  }
25257
25479
  function sortiere(jobs, logIndex, aelteste_zuerst) {
@@ -25284,57 +25506,86 @@ function selectImageJobs(events, storeId, query = {}) {
25284
25506
  const ausschnitt = geordnet.slice(ab, ab + limit);
25285
25507
  const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
25286
25508
  const elementTitel = titelJeElement(events);
25509
+ const { jeLauf: bilder } = bilderJeLauf(events, alle);
25287
25510
  return {
25288
25511
  eintraege: ausschnitt.map((job) => ({
25289
25512
  job,
25290
- herkunft: herkunftFuer(job, laeufe, elementTitel)
25513
+ herkunft: herkunftFuer(job, laeufe, elementTitel, bilder)
25291
25514
  })),
25292
25515
  nextCursor: ab + ausschnitt.length < geordnet.length ? ausschnitt[ausschnitt.length - 1].job_id : null,
25293
25516
  gesamt: geordnet.length,
25294
25517
  offen
25295
25518
  };
25296
25519
  }
25297
- function letztesErzeugtesBild(events, storeId) {
25298
- const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
25520
+ function bildRefAus(roh, batchId) {
25521
+ return {
25522
+ media_ref: roh.media_ref,
25523
+ position: roh.position,
25524
+ generation_batch_id: batchId,
25525
+ ...istText8(roh.asset_id) ? { asset_id: roh.asset_id } : {},
25526
+ ...istText8(roh.mime_type) ? { mime_type: roh.mime_type } : {},
25527
+ model: roh.model ?? null,
25528
+ seed: roh.seed ?? null,
25529
+ width: roh.width ?? null,
25530
+ height: roh.height ?? null
25531
+ };
25532
+ }
25533
+ function bilderJeLauf(events, jobs) {
25299
25534
  const jobJeBatch = new Map(jobs.map((j) => [generationBatchIdOf(j.job_id), j]));
25300
- for (let i = events.length - 1; i >= 0; i -= 1) {
25301
- const event = events[i];
25535
+ const jeLauf = /* @__PURE__ */ new Map();
25536
+ const gesehen = /* @__PURE__ */ new Set();
25537
+ let juengsterLauf = null;
25538
+ for (const event of events) {
25302
25539
  if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
25303
25540
  const p = event.payload;
25304
25541
  if (!p || !istText8(p.item_id) || !istText8(p.prompt_run_id) || !istText8(p.generation_batch_id)) continue;
25305
- const bilder = Array.isArray(p.images) ? p.images : [];
25306
- const letztes = bilder.filter((b) => !!b && istText8(b.media_ref) && typeof b.position === "number").reduce(
25307
- (bisher, b) => bisher === null || b.position > bisher.position ? b : bisher,
25308
- null
25309
- );
25310
- if (!letztes) continue;
25542
+ if (gesehen.has(p.generation_batch_id)) continue;
25543
+ gesehen.add(p.generation_batch_id);
25544
+ const roh = (Array.isArray(p.images) ? p.images : []).filter((b) => !!b && istText8(b.media_ref) && typeof b.position === "number").sort((a, b) => a.position - b.position);
25545
+ if (roh.length === 0) continue;
25311
25546
  const job = jobJeBatch.get(p.generation_batch_id) ?? null;
25312
- const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
25313
- const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events)) : null;
25314
- return {
25315
- job,
25316
- prompt_run_id: p.prompt_run_id,
25317
- item_id: p.item_id,
25318
- generation_batch_id: p.generation_batch_id,
25319
- bild: {
25320
- media_ref: letztes.media_ref,
25321
- position: letztes.position,
25547
+ const liste = jeLauf.get(p.prompt_run_id) ?? [];
25548
+ for (const einzeln of roh) {
25549
+ liste.push({
25550
+ bild: bildRefAus(einzeln, p.generation_batch_id),
25551
+ prompt_run_id: p.prompt_run_id,
25552
+ item_id: p.item_id,
25322
25553
  generation_batch_id: p.generation_batch_id,
25323
- ...istText8(letztes.asset_id) ? { asset_id: letztes.asset_id } : {},
25324
- ...istText8(letztes.mime_type) ? { mime_type: letztes.mime_type } : {},
25325
- model: letztes.model ?? null,
25326
- seed: letztes.seed ?? null,
25327
- width: letztes.width ?? null,
25328
- height: letztes.height ?? null
25329
- },
25330
- position: letztes.position + 1,
25331
- anzahl: bilder.length,
25332
- gespeichert_am: istText8(p.created_at) ? p.created_at : null,
25333
- execution_target_model_key: job?.execution_target_model_key ?? null,
25334
- herkunft
25335
- };
25554
+ job_id: job?.job_id ?? null,
25555
+ position_im_batch: einzeln.position + 1,
25556
+ anzahl_im_batch: roh.length,
25557
+ gespeichert_am: istText8(p.created_at) ? p.created_at : null,
25558
+ execution_target_model_key: job?.execution_target_model_key ?? null
25559
+ });
25560
+ }
25561
+ jeLauf.set(p.prompt_run_id, liste);
25562
+ juengsterLauf = p.prompt_run_id;
25336
25563
  }
25337
- return null;
25564
+ return { jeLauf, juengsterLauf };
25565
+ }
25566
+ function letztesErzeugtesBild(events, storeId) {
25567
+ const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
25568
+ const { jeLauf, juengsterLauf } = bilderJeLauf(events, jobs);
25569
+ if (!juengsterLauf) return null;
25570
+ const folge = jeLauf.get(juengsterLauf) ?? [];
25571
+ const letztes = folge[folge.length - 1];
25572
+ if (!letztes) return null;
25573
+ const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
25574
+ const job = letztes.job_id ? jobs.find((j) => j.job_id === letztes.job_id) ?? null : null;
25575
+ const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events), jeLauf) : null;
25576
+ return {
25577
+ job,
25578
+ prompt_run_id: letztes.prompt_run_id,
25579
+ item_id: letztes.item_id,
25580
+ generation_batch_id: letztes.generation_batch_id,
25581
+ bild: letztes.bild,
25582
+ position: letztes.position_im_batch,
25583
+ anzahl: letztes.anzahl_im_batch,
25584
+ gespeichert_am: letztes.gespeichert_am,
25585
+ execution_target_model_key: letztes.execution_target_model_key,
25586
+ herkunft,
25587
+ folge
25588
+ };
25338
25589
  }
25339
25590
 
25340
25591
  // src/lib/szenen/imageJobPort.ts
@@ -25347,6 +25598,7 @@ function fehler4(error, aktion) {
25347
25598
  function createImageJobPort(options) {
25348
25599
  const { channel, imageGeneration, storeId, clientId } = options;
25349
25600
  const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
25601
+ const newJobId = options.newJobId ?? (() => crypto.randomUUID());
25350
25602
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
25351
25603
  return {
25352
25604
  async listJobs(runId) {
@@ -25388,6 +25640,113 @@ function createImageJobPort(options) {
25388
25640
  };
25389
25641
  }
25390
25642
  },
25643
+ async requestMoreImages(input) {
25644
+ const { sourceJobId, imageCount } = input;
25645
+ let quelle;
25646
+ try {
25647
+ quelle = findImageJob(await channel.readEvents(), sourceJobId);
25648
+ } catch (error) {
25649
+ return {
25650
+ kind: "error",
25651
+ ...fehler4(error, `Der Ausgangsauftrag ${sourceJobId} konnte nicht gelesen werden`),
25652
+ retryable: true
25653
+ };
25654
+ }
25655
+ if (!quelle) {
25656
+ return {
25657
+ kind: "rejected",
25658
+ message: `Der Bildauftrag ${sourceJobId} steht nicht im autoritativen Log dieses Bestands. Ohne ihn gibt es keine Ausfuehrungsparameter, die uebernommen werden koennten.`
25659
+ };
25660
+ }
25661
+ if (quelle.store_id !== storeId) {
25662
+ return {
25663
+ kind: "rejected",
25664
+ message: `Der Bildauftrag ${sourceJobId} gilt fuer den Bestand "${quelle.store_id}", dieser Host arbeitet auf "${storeId}".`
25665
+ };
25666
+ }
25667
+ if (!Number.isInteger(imageCount) || imageCount < 1) {
25668
+ return {
25669
+ kind: "rejected",
25670
+ message: `Die Bildanzahl "${imageCount}" ist keine ganze Zahl >= 1.`
25671
+ };
25672
+ }
25673
+ const wahl = await imageGeneration.imageModelChoiceForRun(quelle.prompt_run_id);
25674
+ if (wahl.kind !== "ok") {
25675
+ return {
25676
+ kind: "error",
25677
+ message: `Die ausfuehrbaren Bildmodelle des Laufs ${quelle.prompt_run_id} waren nicht lesbar: ${wahl.message}`,
25678
+ retryable: wahl.kind === "error" ? wahl.retryable : true
25679
+ };
25680
+ }
25681
+ const gewaehlt = findExecutableImageModel(
25682
+ wahl.value,
25683
+ quelle.execution_target_model_key,
25684
+ quelle.execution_profile_id
25685
+ );
25686
+ if (!gewaehlt) {
25687
+ const auswahl = wahl.value.models.map((m) => m.selection_id);
25688
+ return {
25689
+ kind: "rejected",
25690
+ message: `Das Profil "${quelle.execution_profile_id}" von "${quelle.execution_target_model_key}" ist in dieser Umgebung nicht (mehr) ausfuehrbar. Es wird kein Ersatz geraten. ` + (auswahl.length > 0 ? `Ausfuehrbar waeren: ${auswahl.join(", ")} \u2014 bitte den Lauf oeffnen und dort ausdruecklich waehlen.` : `Derzeit ist hier gar kein Bildmodell ausfuehrbar: ${wahl.value.reason ?? "kein sichtbares Zielmodell traegt ein passendes Profil."}`)
25691
+ };
25692
+ }
25693
+ const befunde = checkImageModelParameters(gewaehlt, {
25694
+ imageCount,
25695
+ ...quelle.aspect_ratio ? { aspectRatio: quelle.aspect_ratio } : {}
25696
+ });
25697
+ if (befunde.length > 0) {
25698
+ return {
25699
+ kind: "rejected",
25700
+ message: `Der neue Auftrag passt nicht zu ${gewaehlt.display_name} (Profil ${gewaehlt.profile_id}, bis zu ${gewaehlt.max_images} Bilder): ` + befunde.join(" ")
25701
+ };
25702
+ }
25703
+ const jobId = input.jobId ?? newJobId();
25704
+ let vorhanden;
25705
+ try {
25706
+ vorhanden = findImageJob(await channel.readEvents(), jobId);
25707
+ } catch (error) {
25708
+ return {
25709
+ kind: "error",
25710
+ ...fehler4(error, `Der Bestand war vor dem Anlegen von ${jobId} nicht lesbar`),
25711
+ retryable: true
25712
+ };
25713
+ }
25714
+ if (vorhanden) {
25715
+ return {
25716
+ kind: "rejected",
25717
+ message: `Zur Auftragskennung ${jobId} gibt es bereits einen Auftrag (${vorhanden.status}). Ein Auftrag ist unveraenderlich.`
25718
+ };
25719
+ }
25720
+ const payload = {
25721
+ job_id: jobId,
25722
+ prompt_run_id: quelle.prompt_run_id,
25723
+ execution_target_model_key: gewaehlt.target_model_key,
25724
+ execution_profile_id: gewaehlt.profile_id,
25725
+ image_count: imageCount,
25726
+ ...quelle.aspect_ratio ? { aspect_ratio: quelle.aspect_ratio } : {},
25727
+ // **Nicht geraten und nicht erfunden.** Dieser Host kennt keinen
25728
+ // Nutzerschluessel — der Flow-Client hat keine Identitaet, nur einen
25729
+ // Bestand. Der Fortsetzungsauftrag traegt deshalb den Auftraggeber des
25730
+ // Auftrags, den er fortsetzt; ein ausgedachter Schluessel waere genau
25731
+ // der Fall, den Regel 6 verbietet.
25732
+ requested_by: quelle.requested_by,
25733
+ store_id: storeId,
25734
+ created_at: now().toISOString()
25735
+ };
25736
+ try {
25737
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobRequested, payload);
25738
+ } catch (error) {
25739
+ return {
25740
+ kind: "error",
25741
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht angelegt werden`),
25742
+ retryable: true
25743
+ };
25744
+ }
25745
+ return {
25746
+ kind: "ok",
25747
+ job: { ...payload, status: "pending", claim_attempts: [] }
25748
+ };
25749
+ },
25391
25750
  async claimAndRun(jobId) {
25392
25751
  let letzterGrund = "";
25393
25752
  for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {