@rslsp1/fa-app-tools 10.0.0 → 10.1.3

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 = "10.0.0";
138
+ var version = "10.1.3";
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.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';
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 seit Schritt 06 eine **Leiste in der Zeile**, kein\n aufklappbares Formular: Modell, Anzahl, Knopf nebeneinander. */\n.szenen-bildauftraege__weitere {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: var(--space-xs) 0 0;\n font-size: 0.85rem;\n}\n\n.szenen-bildauftraege__weitere label {\n color: var(--color-text-secondary);\n}\n\n/* Der Lauf-Zaehler: sichtbar, aber nicht im Weg (Schritt 06). */\n.szenen-bildauftraege__laufzaehler {\n padding: 0.05rem var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm, 4px);\n font-size: 0.8rem;\n font-variant-numeric: tabular-nums;\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 = () => {
@@ -1659,7 +1659,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1659
1659
  }
1660
1660
 
1661
1661
  // src/components/AvatarArchitectApp.tsx
1662
- var import_react61 = require("react");
1662
+ var import_react62 = require("react");
1663
1663
 
1664
1664
  // src/components/PromptTab.tsx
1665
1665
  var import_react15 = require("react");
@@ -3405,14 +3405,92 @@ function useTagBase(token, namespace) {
3405
3405
  };
3406
3406
  }
3407
3407
 
3408
+ // src/lib/imagePrefetch.ts
3409
+ var import_react20 = require("react");
3410
+ function computePrefetchVisibility(input) {
3411
+ const { layoutChoice, appShellVisible, mobileTab, middlePanel, activeTab, isRightCollapsed } = input;
3412
+ const isMobile = layoutChoice === "mobile" || layoutChoice === "mobile-desktop";
3413
+ const isTabletLandscape = layoutChoice === "tablet-landscape";
3414
+ const szenenActive = isMobile ? mobileTab === "szenen" : isTabletLandscape ? false : middlePanel === "szenen";
3415
+ if (!appShellVisible) {
3416
+ return { historyVisible: false, galleryVisible: false, szenenActive };
3417
+ }
3418
+ if (isMobile) {
3419
+ const browseVisible = mobileTab === "browse";
3420
+ return {
3421
+ historyVisible: browseVisible && activeTab === "history",
3422
+ galleryVisible: browseVisible && activeTab === "gallery",
3423
+ szenenActive
3424
+ };
3425
+ }
3426
+ if (isTabletLandscape) {
3427
+ return { historyVisible: true, galleryVisible: false, szenenActive };
3428
+ }
3429
+ return {
3430
+ historyVisible: !isRightCollapsed && activeTab === "history",
3431
+ galleryVisible: !isRightCollapsed && activeTab === "gallery",
3432
+ szenenActive
3433
+ };
3434
+ }
3435
+ function sortByTimestampDesc(entries) {
3436
+ return [...entries].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
3437
+ }
3438
+ function selectHistoryPrefetchEntries(metadata, historyVisibleCount) {
3439
+ return sortByTimestampDesc(metadata).slice(0, historyVisibleCount);
3440
+ }
3441
+ function selectGalleryPrefetchEntries(metadata, galleryVisibleCount) {
3442
+ const sorted = sortByTimestampDesc(metadata);
3443
+ const skeletons = sorted.map((m) => ({
3444
+ id: m.id,
3445
+ prompt: m.prompt,
3446
+ timestamp: m.timestamp,
3447
+ status: "done",
3448
+ nodeId: m.id,
3449
+ tags: m.tags || []
3450
+ }));
3451
+ const repIds = new Set(groupByPrompt(skeletons).slice(0, galleryVisibleCount).map((g) => g.representative.id));
3452
+ return sorted.filter((e) => repIds.has(e.id));
3453
+ }
3454
+ function useThumbnailPrefetch(options) {
3455
+ const { metadata, historyVisible, galleryVisible, historyVisibleCount, galleryVisibleCount, loadThumbnailsForEntries } = options;
3456
+ (0, import_react20.useEffect)(() => {
3457
+ if (!metadata || !galleryVisible) return;
3458
+ loadThumbnailsForEntries(selectGalleryPrefetchEntries(metadata, galleryVisibleCount));
3459
+ }, [metadata, galleryVisible, galleryVisibleCount, loadThumbnailsForEntries]);
3460
+ (0, import_react20.useEffect)(() => {
3461
+ if (!metadata || !historyVisible) return;
3462
+ loadThumbnailsForEntries(selectHistoryPrefetchEntries(metadata, historyVisibleCount));
3463
+ }, [metadata, historyVisible, historyVisibleCount, loadThumbnailsForEntries]);
3464
+ }
3465
+ async function loadThumbnailsSequentially(entries, io) {
3466
+ for (const entry of entries) {
3467
+ if (io.inFlight.has(entry.id)) continue;
3468
+ if (io.isAlreadyLoaded(entry)) continue;
3469
+ if (io.isKnownMissing(entry)) continue;
3470
+ io.inFlight.add(entry.id);
3471
+ try {
3472
+ const b64 = await io.loadImage(entry);
3473
+ if (!b64) {
3474
+ io.onMissing(entry);
3475
+ continue;
3476
+ }
3477
+ io.onLoaded(entry, b64);
3478
+ } catch {
3479
+ io.onError(entry);
3480
+ } finally {
3481
+ io.inFlight.delete(entry.id);
3482
+ }
3483
+ }
3484
+ }
3485
+
3408
3486
  // src/components/labs/LabsTab.tsx
3409
- var import_react27 = require("react");
3487
+ var import_react28 = require("react");
3410
3488
 
3411
3489
  // src/components/labs/LabRemix.tsx
3412
- var import_react22 = require("react");
3490
+ var import_react23 = require("react");
3413
3491
 
3414
3492
  // src/components/labs/LabImagePicker.tsx
3415
- var import_react20 = require("react");
3493
+ var import_react21 = require("react");
3416
3494
  var import_jsx_runtime16 = require("react/jsx-runtime");
3417
3495
  var LabImagePicker = ({
3418
3496
  availableItems,
@@ -3421,8 +3499,8 @@ var LabImagePicker = ({
3421
3499
  onClose,
3422
3500
  title = "Bild w\xE4hlen"
3423
3501
  }) => {
3424
- const [search, setSearch] = (0, import_react20.useState)("");
3425
- const [drillItem, setDrillItem] = (0, import_react20.useState)(null);
3502
+ const [search, setSearch] = (0, import_react21.useState)("");
3503
+ const [drillItem, setDrillItem] = (0, import_react21.useState)(null);
3426
3504
  const filtered = availableItems.filter(
3427
3505
  (item) => !search || item.prompt.toLowerCase().includes(search.toLowerCase())
3428
3506
  );
@@ -3575,23 +3653,23 @@ var GenerationControls = ({
3575
3653
  ] });
3576
3654
 
3577
3655
  // src/hooks/useGenerationSettings.ts
3578
- var import_react21 = require("react");
3656
+ var import_react22 = require("react");
3579
3657
  function useGenerationSettings() {
3580
- const [aspectRatio, setAspectRatio] = (0, import_react21.useState)(() => {
3658
+ const [aspectRatio, setAspectRatio] = (0, import_react22.useState)(() => {
3581
3659
  try {
3582
3660
  return localStorage.getItem("aa-lab-aspect") || "1:1";
3583
3661
  } catch {
3584
3662
  return "1:1";
3585
3663
  }
3586
3664
  });
3587
- const [model, setModel] = (0, import_react21.useState)(() => {
3665
+ const [model, setModel] = (0, import_react22.useState)(() => {
3588
3666
  try {
3589
3667
  return localStorage.getItem("aa-lab-model") || "\u{1F34C} Nano Banana Pro";
3590
3668
  } catch {
3591
3669
  return "\u{1F34C} Nano Banana Pro";
3592
3670
  }
3593
3671
  });
3594
- const [imageCount, setImageCount] = (0, import_react21.useState)(() => {
3672
+ const [imageCount, setImageCount] = (0, import_react22.useState)(() => {
3595
3673
  try {
3596
3674
  const v = parseInt(localStorage.getItem("aa-image-count") || "", 10);
3597
3675
  return v >= 1 && v <= 8 ? v : 4;
@@ -3627,13 +3705,13 @@ function useGenerationSettings() {
3627
3705
  // src/components/labs/LabRemix.tsx
3628
3706
  var import_jsx_runtime18 = require("react/jsx-runtime");
3629
3707
  var LabRemix = ({ services, onResult }) => {
3630
- const [showPicker, setShowPicker] = (0, import_react22.useState)(false);
3631
- const [selected, setSelected] = (0, import_react22.useState)(null);
3632
- const [instruction, setInstruction] = (0, import_react22.useState)("");
3633
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react22.useState)("");
3634
- const [resultImages, setResultImages] = (0, import_react22.useState)([]);
3635
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react22.useState)(false);
3636
- const [runningCount, setRunningCount] = (0, import_react22.useState)(0);
3708
+ const [showPicker, setShowPicker] = (0, import_react23.useState)(false);
3709
+ const [selected, setSelected] = (0, import_react23.useState)(null);
3710
+ const [instruction, setInstruction] = (0, import_react23.useState)("");
3711
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react23.useState)("");
3712
+ const [resultImages, setResultImages] = (0, import_react23.useState)([]);
3713
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react23.useState)(false);
3714
+ const [runningCount, setRunningCount] = (0, import_react23.useState)(0);
3637
3715
  const { aspectRatio, model, imageCount, updateAspectRatio, updateModel, updateImageCount } = useGenerationSettings();
3638
3716
  const handleSelectImage = (item, frame) => {
3639
3717
  services.onItemUsed(item);
@@ -3830,16 +3908,16 @@ var LabRemix = ({ services, onResult }) => {
3830
3908
  };
3831
3909
 
3832
3910
  // src/components/labs/LabBlend.tsx
3833
- var import_react23 = require("react");
3911
+ var import_react24 = require("react");
3834
3912
  var import_jsx_runtime19 = require("react/jsx-runtime");
3835
3913
  var LabBlend = ({ services, onResult }) => {
3836
- const [showPickerFor, setShowPickerFor] = (0, import_react23.useState)(null);
3837
- const [selectedImages, setSelectedImages] = (0, import_react23.useState)([]);
3838
- const [instruction, setInstruction] = (0, import_react23.useState)("");
3839
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react23.useState)("");
3840
- const [resultImages, setResultImages] = (0, import_react23.useState)([]);
3841
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react23.useState)(false);
3842
- const [runningCount, setRunningCount] = (0, import_react23.useState)(0);
3914
+ const [showPickerFor, setShowPickerFor] = (0, import_react24.useState)(null);
3915
+ const [selectedImages, setSelectedImages] = (0, import_react24.useState)([]);
3916
+ const [instruction, setInstruction] = (0, import_react24.useState)("");
3917
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react24.useState)("");
3918
+ const [resultImages, setResultImages] = (0, import_react24.useState)([]);
3919
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react24.useState)(false);
3920
+ const [runningCount, setRunningCount] = (0, import_react24.useState)(0);
3843
3921
  const { aspectRatio, model, imageCount, updateAspectRatio, updateModel, updateImageCount } = useGenerationSettings();
3844
3922
  const handleSelectImage = (index, item, frame) => {
3845
3923
  services.onItemUsed(item);
@@ -4041,17 +4119,17 @@ var LabBlend = ({ services, onResult }) => {
4041
4119
  };
4042
4120
 
4043
4121
  // src/components/labs/LabCompare.tsx
4044
- var import_react24 = require("react");
4122
+ var import_react25 = require("react");
4045
4123
  var import_jsx_runtime20 = require("react/jsx-runtime");
4046
4124
  var LabCompare = ({ services, onResult }) => {
4047
- const [showPickerFor, setShowPickerFor] = (0, import_react24.useState)(null);
4048
- const [selectedImages, setSelectedImages] = (0, import_react24.useState)([]);
4049
- const [instruction, setInstruction] = (0, import_react24.useState)("");
4050
- const [analysis, setAnalysis] = (0, import_react24.useState)("");
4051
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react24.useState)("");
4052
- const [resultImage, setResultImage] = (0, import_react24.useState)(null);
4053
- const [isAnalyzing, setIsAnalyzing] = (0, import_react24.useState)(false);
4054
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react24.useState)(false);
4125
+ const [showPickerFor, setShowPickerFor] = (0, import_react25.useState)(null);
4126
+ const [selectedImages, setSelectedImages] = (0, import_react25.useState)([]);
4127
+ const [instruction, setInstruction] = (0, import_react25.useState)("");
4128
+ const [analysis, setAnalysis] = (0, import_react25.useState)("");
4129
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react25.useState)("");
4130
+ const [resultImage, setResultImage] = (0, import_react25.useState)(null);
4131
+ const [isAnalyzing, setIsAnalyzing] = (0, import_react25.useState)(false);
4132
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react25.useState)(false);
4055
4133
  const handleSelectImage = (index, item, frame) => {
4056
4134
  services.onItemUsed(item);
4057
4135
  const newImg = {
@@ -4214,14 +4292,14 @@ var LabCompare = ({ services, onResult }) => {
4214
4292
  };
4215
4293
 
4216
4294
  // src/components/labs/LabLoop.tsx
4217
- var import_react25 = require("react");
4295
+ var import_react26 = require("react");
4218
4296
  var import_jsx_runtime21 = require("react/jsx-runtime");
4219
4297
  var LabLoop = ({ services, onResult }) => {
4220
- const [rounds, setRounds] = (0, import_react25.useState)([]);
4221
- const [currentInstruction, setCurrentInstruction] = (0, import_react25.useState)("");
4222
- const [showPickerForRound, setShowPickerForRound] = (0, import_react25.useState)(null);
4223
- const [pendingImages, setPendingImages] = (0, import_react25.useState)([]);
4224
- const [isGenerating, setIsGenerating] = (0, import_react25.useState)(false);
4298
+ const [rounds, setRounds] = (0, import_react26.useState)([]);
4299
+ const [currentInstruction, setCurrentInstruction] = (0, import_react26.useState)("");
4300
+ const [showPickerForRound, setShowPickerForRound] = (0, import_react26.useState)(null);
4301
+ const [pendingImages, setPendingImages] = (0, import_react26.useState)([]);
4302
+ const [isGenerating, setIsGenerating] = (0, import_react26.useState)(false);
4225
4303
  const { aspectRatio, model, updateAspectRatio, updateModel } = useGenerationSettings();
4226
4304
  const currentPrompt = rounds.length > 0 ? rounds[rounds.length - 1].prompt : "";
4227
4305
  const handleAddImage = (item, frame) => {
@@ -4385,7 +4463,7 @@ var LabLoop = ({ services, onResult }) => {
4385
4463
  };
4386
4464
 
4387
4465
  // src/components/labs/LabFrameExtractor.tsx
4388
- var import_react26 = require("react");
4466
+ var import_react27 = require("react");
4389
4467
  var import_jsx_runtime22 = require("react/jsx-runtime");
4390
4468
  var formatTime = (s) => {
4391
4469
  const m = Math.floor(s / 60);
@@ -4399,15 +4477,15 @@ var LabFrameExtractor = ({
4399
4477
  onResult,
4400
4478
  resolveVideoUrl
4401
4479
  }) => {
4402
- const videoRef = (0, import_react26.useRef)(null);
4403
- const canvasRef = (0, import_react26.useRef)(null);
4404
- const cancelledRef = (0, import_react26.useRef)(false);
4405
- const [selectedItem, setSelectedItem] = (0, import_react26.useState)(null);
4406
- const [videoSrc, setVideoSrc] = (0, import_react26.useState)(null);
4407
- const [videoReady, setVideoReady] = (0, import_react26.useState)(false);
4408
- const [frames, setFrames] = (0, import_react26.useState)([]);
4409
- const [isExtracting, setIsExtracting] = (0, import_react26.useState)(false);
4410
- const [intervalSec, setIntervalSec] = (0, import_react26.useState)("1");
4480
+ const videoRef = (0, import_react27.useRef)(null);
4481
+ const canvasRef = (0, import_react27.useRef)(null);
4482
+ const cancelledRef = (0, import_react27.useRef)(false);
4483
+ const [selectedItem, setSelectedItem] = (0, import_react27.useState)(null);
4484
+ const [videoSrc, setVideoSrc] = (0, import_react27.useState)(null);
4485
+ const [videoReady, setVideoReady] = (0, import_react27.useState)(false);
4486
+ const [frames, setFrames] = (0, import_react27.useState)([]);
4487
+ const [isExtracting, setIsExtracting] = (0, import_react27.useState)(false);
4488
+ const [intervalSec, setIntervalSec] = (0, import_react27.useState)("1");
4411
4489
  const handleVideoSelect = (item) => {
4412
4490
  const mediaId = item.frames[0]?.mediaId;
4413
4491
  if (!mediaId) return;
@@ -4417,7 +4495,7 @@ var LabFrameExtractor = ({
4417
4495
  setVideoReady(false);
4418
4496
  cancelledRef.current = false;
4419
4497
  };
4420
- const captureAt = (0, import_react26.useCallback)(
4498
+ const captureAt = (0, import_react27.useCallback)(
4421
4499
  (t, label) => new Promise((resolve) => {
4422
4500
  const video = videoRef.current;
4423
4501
  const canvas = canvasRef.current;
@@ -4644,7 +4722,7 @@ var BASE_TABS = [
4644
4722
  ];
4645
4723
  var FRAMES_TAB = { key: "frames", label: "Frames", icon: "crop_original" };
4646
4724
  var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
4647
- const [activeTab, setActiveTab] = (0, import_react27.useState)("remix");
4725
+ const [activeTab, setActiveTab] = (0, import_react28.useState)("remix");
4648
4726
  const showFrames = !!(videoItems && resolveVideoUrl);
4649
4727
  const tabs = showFrames ? [...BASE_TABS, FRAMES_TAB] : BASE_TABS;
4650
4728
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
@@ -4678,10 +4756,10 @@ var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
4678
4756
  };
4679
4757
 
4680
4758
  // src/components/TagManagerPanel.tsx
4681
- var import_react29 = require("react");
4759
+ var import_react30 = require("react");
4682
4760
 
4683
4761
  // src/features/tags/TagManager.tsx
4684
- var import_react28 = require("react");
4762
+ var import_react29 = require("react");
4685
4763
 
4686
4764
  // src/features/tags/tagSelectors.ts
4687
4765
  var CATEGORY_DEFAULTS = {
@@ -4864,17 +4942,17 @@ function listCategories(model) {
4864
4942
  // src/features/tags/TagManager.tsx
4865
4943
  var import_jsx_runtime24 = require("react/jsx-runtime");
4866
4944
  var TagManager = ({ tags, commands = {}, selection, onSelectionChange, emptyText = "Keine Tags in dieser Kategorie.", newCategoryName, ensureScopes }) => {
4867
- const [internalSelection, setInternalSelection] = (0, import_react28.useState)({ scopeKey: null, categoryKey: null });
4868
- const [notice, setNotice] = (0, import_react28.useState)(null);
4869
- const [editingId, setEditingId] = (0, import_react28.useState)(null);
4870
- const [editState, setEditState] = (0, import_react28.useState)({ label: "", value: "" });
4871
- const [newTag, setNewTag] = (0, import_react28.useState)({ label: "", value: "" });
4872
- const [newCategoryInput, setNewCategoryInput] = (0, import_react28.useState)("");
4873
- const [movingId, setMovingId] = (0, import_react28.useState)(null);
4874
- const [moveTarget, setMoveTarget] = (0, import_react28.useState)("");
4875
- const [showInherited, setShowInherited] = (0, import_react28.useState)(true);
4945
+ const [internalSelection, setInternalSelection] = (0, import_react29.useState)({ scopeKey: null, categoryKey: null });
4946
+ const [notice, setNotice] = (0, import_react29.useState)(null);
4947
+ const [editingId, setEditingId] = (0, import_react29.useState)(null);
4948
+ const [editState, setEditState] = (0, import_react29.useState)({ label: "", value: "" });
4949
+ const [newTag, setNewTag] = (0, import_react29.useState)({ label: "", value: "" });
4950
+ const [newCategoryInput, setNewCategoryInput] = (0, import_react29.useState)("");
4951
+ const [movingId, setMovingId] = (0, import_react29.useState)(null);
4952
+ const [moveTarget, setMoveTarget] = (0, import_react29.useState)("");
4953
+ const [showInherited, setShowInherited] = (0, import_react29.useState)(true);
4876
4954
  const ensureScopesKey = (ensureScopes ?? []).join("|");
4877
- const derived = (0, import_react28.useMemo)(() => {
4955
+ const derived = (0, import_react29.useMemo)(() => {
4878
4956
  try {
4879
4957
  let model = deriveTagViewModel(tags);
4880
4958
  if (ensureScopes?.length) model = ensureScopesInModel(model, ensureScopes);
@@ -5176,7 +5254,7 @@ var TagManager = ({ tags, commands = {}, selection, onSelectionChange, emptyText
5176
5254
  var import_jsx_runtime25 = require("react/jsx-runtime");
5177
5255
  var OK = { kind: "ok", value: void 0 };
5178
5256
  function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete, onTagReorder, onTagMove }) {
5179
- const adapters = (0, import_react29.useMemo)(() => {
5257
+ const adapters = (0, import_react30.useMemo)(() => {
5180
5258
  const byId = /* @__PURE__ */ new Map();
5181
5259
  const entries = [];
5182
5260
  Object.entries(workspaceTags.by_category).forEach(([category, tags]) => {
@@ -5188,7 +5266,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
5188
5266
  });
5189
5267
  return { byId, entries };
5190
5268
  }, [workspaceTags]);
5191
- const commands = (0, import_react29.useMemo)(() => ({
5269
+ const commands = (0, import_react30.useMemo)(() => ({
5192
5270
  createTag: async ({ category, label, value }) => {
5193
5271
  onTagCreate({ label, value, category, is_user_created: true });
5194
5272
  return OK;
@@ -5228,7 +5306,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
5228
5306
  }
5229
5307
 
5230
5308
  // src/components/HFTestTab.tsx
5231
- var import_react30 = require("react");
5309
+ var import_react31 = require("react");
5232
5310
  var import_jsx_runtime26 = require("react/jsx-runtime");
5233
5311
  var HF_BASE2 = "https://huggingface.co";
5234
5312
  var HF_REPO2 = "RolandSch/fa-app-state";
@@ -5421,7 +5499,7 @@ function tryFmt(s) {
5421
5499
  }
5422
5500
  }
5423
5501
  function CopyBtn({ text }) {
5424
- const [done, setDone] = (0, import_react30.useState)(false);
5502
+ const [done, setDone] = (0, import_react31.useState)(false);
5425
5503
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
5426
5504
  "button",
5427
5505
  {
@@ -5593,9 +5671,9 @@ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadSta
5593
5671
  ] });
5594
5672
  }
5595
5673
  function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map(), missingImages = [] }) {
5596
- const [selected, setSelected] = (0, import_react30.useState)(null);
5597
- const [results, setResults] = (0, import_react30.useState)({});
5598
- const [expanded, setExpanded] = (0, import_react30.useState)({});
5674
+ const [selected, setSelected] = (0, import_react31.useState)(null);
5675
+ const [results, setResults] = (0, import_react31.useState)({});
5676
+ const [expanded, setExpanded] = (0, import_react31.useState)({});
5599
5677
  const withResults = galleryItems.filter((g) => g.base64 && g.status === "done");
5600
5678
  const setRunning = (id) => setResults((r) => ({ ...r, [id]: { status: "running", steps: [], totalMs: 0 } }));
5601
5679
  const setDone = (id, steps, t0) => {
@@ -5793,7 +5871,7 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
5793
5871
  }
5794
5872
 
5795
5873
  // src/components/ServerTab.tsx
5796
- var import_react31 = require("react");
5874
+ var import_react32 = require("react");
5797
5875
  var import_jsx_runtime27 = require("react/jsx-runtime");
5798
5876
  function StarRating({ rating = 0 }) {
5799
5877
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "flex gap-[2px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: `material-symbols-outlined text-[12px] ${i <= rating ? "text-yellow-400" : "text-white/15"}`, children: "star" }, i)) });
@@ -5806,21 +5884,21 @@ async function serverGet(baseUrl, path) {
5806
5884
  return json && typeof json === "object" && "data" in json ? json.data : json;
5807
5885
  }
5808
5886
  function ServerTab({ serverBaseUrl }) {
5809
- const [step, setStep] = (0, import_react31.useState)("user");
5810
- const [users, setUsers] = (0, import_react31.useState)([]);
5811
- const [usersLoading, setUsersLoading] = (0, import_react31.useState)(false);
5812
- const [usersError, setUsersError] = (0, import_react31.useState)(null);
5813
- const [selectedUser, setSelectedUser] = (0, import_react31.useState)(null);
5814
- const [contexts, setContexts] = (0, import_react31.useState)([]);
5815
- const [contextsLoading, setContextsLoading] = (0, import_react31.useState)(false);
5816
- const [selectedContext, setSelectedContext] = (0, import_react31.useState)(null);
5817
- const [tags, setTags] = (0, import_react31.useState)([]);
5818
- const [items, setItems] = (0, import_react31.useState)([]);
5819
- const [libLoading, setLibLoading] = (0, import_react31.useState)(false);
5820
- const [libError, setLibError] = (0, import_react31.useState)(null);
5821
- const [activeTag, setActiveTag] = (0, import_react31.useState)(null);
5822
- const [preview, setPreview] = (0, import_react31.useState)(null);
5823
- (0, import_react31.useEffect)(() => {
5887
+ const [step, setStep] = (0, import_react32.useState)("user");
5888
+ const [users, setUsers] = (0, import_react32.useState)([]);
5889
+ const [usersLoading, setUsersLoading] = (0, import_react32.useState)(false);
5890
+ const [usersError, setUsersError] = (0, import_react32.useState)(null);
5891
+ const [selectedUser, setSelectedUser] = (0, import_react32.useState)(null);
5892
+ const [contexts, setContexts] = (0, import_react32.useState)([]);
5893
+ const [contextsLoading, setContextsLoading] = (0, import_react32.useState)(false);
5894
+ const [selectedContext, setSelectedContext] = (0, import_react32.useState)(null);
5895
+ const [tags, setTags] = (0, import_react32.useState)([]);
5896
+ const [items, setItems] = (0, import_react32.useState)([]);
5897
+ const [libLoading, setLibLoading] = (0, import_react32.useState)(false);
5898
+ const [libError, setLibError] = (0, import_react32.useState)(null);
5899
+ const [activeTag, setActiveTag] = (0, import_react32.useState)(null);
5900
+ const [preview, setPreview] = (0, import_react32.useState)(null);
5901
+ (0, import_react32.useEffect)(() => {
5824
5902
  if (!serverBaseUrl) return;
5825
5903
  setUsersLoading(true);
5826
5904
  setUsersError(null);
@@ -5971,7 +6049,7 @@ function ServerTab({ serverBaseUrl }) {
5971
6049
  }
5972
6050
 
5973
6051
  // src/components/FlowWorkspacePicker.tsx
5974
- var import_react32 = require("react");
6052
+ var import_react33 = require("react");
5975
6053
  var import_jsx_runtime28 = require("react/jsx-runtime");
5976
6054
  function FlowWorkspacePicker({
5977
6055
  tagBase,
@@ -5980,9 +6058,9 @@ function FlowWorkspacePicker({
5980
6058
  onSelectUser,
5981
6059
  onSelectContext
5982
6060
  }) {
5983
- const [step, setStep] = (0, import_react32.useState)(selectedUserId === null ? "user" : "context");
6061
+ const [step, setStep] = (0, import_react33.useState)(selectedUserId === null ? "user" : "context");
5984
6062
  const selectedUser = tagBase.users.find((user) => user.id === selectedUserId) ?? null;
5985
- const contexts = (0, import_react32.useMemo)(() => {
6063
+ const contexts = (0, import_react33.useMemo)(() => {
5986
6064
  if (!selectedUser) return [];
5987
6065
  if (!Array.isArray(selectedUser.context_ids)) return [];
5988
6066
  const allowed = new Set(selectedUser.context_ids);
@@ -6042,7 +6120,7 @@ function FlowWorkspacePicker({
6042
6120
  }
6043
6121
 
6044
6122
  // src/features/szenen/SzenenView.tsx
6045
- var import_react60 = require("react");
6123
+ var import_react61 = require("react");
6046
6124
 
6047
6125
  // src/lib/szenen/elementEvents.ts
6048
6126
  var SZENEN_EVENT_PREFIX = "szenen.";
@@ -10003,10 +10081,10 @@ function noticeFor(ergebnis, was) {
10003
10081
  }
10004
10082
 
10005
10083
  // src/features/szenen/areas/ElementsArea.tsx
10006
- var import_react52 = require("react");
10084
+ var import_react53 = require("react");
10007
10085
 
10008
10086
  // src/features/szenen/areas/useVorschauen.ts
10009
- var import_react33 = require("react");
10087
+ var import_react34 = require("react");
10010
10088
 
10011
10089
  // src/lib/szenen/bildVorschau.ts
10012
10090
  async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
@@ -10035,16 +10113,16 @@ async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
10035
10113
  // src/features/szenen/areas/useVorschauen.ts
10036
10114
  function useVorschauen(port, wuensche) {
10037
10115
  const bestand = port.imageLibrary;
10038
- const [stand, setStand] = (0, import_react33.useState)(
10116
+ const [stand, setStand] = (0, import_react34.useState)(
10039
10117
  () => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() })
10040
10118
  );
10041
- const gehalten = (0, import_react33.useRef)(/* @__PURE__ */ new Map());
10042
- const gescheitert = (0, import_react33.useRef)(/* @__PURE__ */ new Map());
10119
+ const gehalten = (0, import_react34.useRef)(/* @__PURE__ */ new Map());
10120
+ const gescheitert = (0, import_react34.useRef)(/* @__PURE__ */ new Map());
10043
10121
  const auftrag = wuensche.filter(
10044
10122
  (e) => typeof e.mediaRef === "string"
10045
10123
  );
10046
10124
  const schluessel = auftrag.map((e) => `${e.id}|${e.mediaRef}`).join(" ");
10047
- (0, import_react33.useEffect)(() => {
10125
+ (0, import_react34.useEffect)(() => {
10048
10126
  if (!bestand?.loadPreview) return void 0;
10049
10127
  const lauf = { abgebrochen: false };
10050
10128
  const offen = auftrag.filter(
@@ -10083,7 +10161,7 @@ function useVorschauen(port, wuensche) {
10083
10161
  }
10084
10162
 
10085
10163
  // src/features/szenen/areas/PromptKarteMeta.tsx
10086
- var import_react36 = require("react");
10164
+ var import_react37 = require("react");
10087
10165
 
10088
10166
  // src/lib/szenen/promptRunTags.ts
10089
10167
  function tagRefsAus(roh, quelle) {
@@ -10145,7 +10223,7 @@ function labelsOf(tags) {
10145
10223
  }
10146
10224
 
10147
10225
  // src/features/szenen/areas/Bildbausteine.tsx
10148
- var import_react34 = require("react");
10226
+ var import_react35 = require("react");
10149
10227
  var import_jsx_runtime29 = require("react/jsx-runtime");
10150
10228
  var Kachel = ({
10151
10229
  quelle,
@@ -10162,11 +10240,11 @@ var BildViewer = ({
10162
10240
  }) => {
10163
10241
  const anzahl = bilder.length;
10164
10242
  const bild = bilder[position];
10165
- const blaettern = (0, import_react34.useCallback)((schritt) => {
10243
+ const blaettern = (0, import_react35.useCallback)((schritt) => {
10166
10244
  if (anzahl === 0) return;
10167
10245
  onWechseln((position + schritt + anzahl) % anzahl);
10168
10246
  }, [anzahl, position, onWechseln]);
10169
- (0, import_react34.useEffect)(() => {
10247
+ (0, import_react35.useEffect)(() => {
10170
10248
  const taste = (e) => {
10171
10249
  if (e.key === "Escape") onSchliessen();
10172
10250
  else if (e.key === "ArrowLeft") blaettern(-1);
@@ -10249,8 +10327,8 @@ var BildViewer = ({
10249
10327
  );
10250
10328
  };
10251
10329
  var BildGalerie = ({ bilder, testId, satzTitel }) => {
10252
- const [offen, setOffen] = (0, import_react34.useState)(null);
10253
- (0, import_react34.useEffect)(() => {
10330
+ const [offen, setOffen] = (0, import_react35.useState)(null);
10331
+ (0, import_react35.useEffect)(() => {
10254
10332
  setOffen((vorher) => vorher !== null && vorher >= bilder.length ? null : vorher);
10255
10333
  }, [bilder.length]);
10256
10334
  if (bilder.length === 0) return null;
@@ -10294,12 +10372,12 @@ var BildGalerie = ({ bilder, testId, satzTitel }) => {
10294
10372
  };
10295
10373
 
10296
10374
  // src/features/szenen/areas/usePortQuery.ts
10297
- var import_react35 = require("react");
10375
+ var import_react36 = require("react");
10298
10376
  function usePortQuery(frage, abhaengigkeiten) {
10299
- const [phase, setPhase] = (0, import_react35.useState)({ status: "loading" });
10300
- const [token, setToken] = (0, import_react35.useState)(0);
10301
- const sequenz = (0, import_react35.useRef)(0);
10302
- (0, import_react35.useEffect)(() => {
10377
+ const [phase, setPhase] = (0, import_react36.useState)({ status: "loading" });
10378
+ const [token, setToken] = (0, import_react36.useState)(0);
10379
+ const sequenz = (0, import_react36.useRef)(0);
10380
+ (0, import_react36.useEffect)(() => {
10303
10381
  const meine = sequenz.current += 1;
10304
10382
  let abgemeldet = false;
10305
10383
  setPhase({ status: "loading" });
@@ -10332,7 +10410,7 @@ function bilderText(anzahl) {
10332
10410
  return anzahl === 1 ? "1 Bild" : `${anzahl} Bilder`;
10333
10411
  }
10334
10412
  var PromptKarteTags = ({ runId, promptTags, annotation, testId }) => {
10335
- const tags = (0, import_react36.useMemo)(() => karteTags(promptTags, annotation), [promptTags, annotation]);
10413
+ const tags = (0, import_react37.useMemo)(() => karteTags(promptTags, annotation), [promptTags, annotation]);
10336
10414
  if (tags.length === 0) return null;
10337
10415
  const gezeigt = tags.slice(0, TAGS_IN_KARTE);
10338
10416
  const weitere = tags.length - gezeigt.length;
@@ -10393,15 +10471,15 @@ var PromptTagEditor = ({
10393
10471
  onGespeichert
10394
10472
  }) => {
10395
10473
  const faehig = capabilitiesOf(port);
10396
- const [eingabe, setEingabe] = (0, import_react36.useState)("");
10397
- const [laeuft, setLaeuft] = (0, import_react36.useState)(false);
10398
- const [notiz, setNotiz] = (0, import_react36.useState)(null);
10474
+ const [eingabe, setEingabe] = (0, import_react37.useState)("");
10475
+ const [laeuft, setLaeuft] = (0, import_react37.useState)(false);
10476
+ const [notiz, setNotiz] = (0, import_react37.useState)(null);
10399
10477
  const [vokabelPhase] = usePortQuery(
10400
10478
  () => port.tags ? port.tags.listTags() : Promise.resolve({ kind: "ok", value: [] }),
10401
10479
  [port]
10402
10480
  );
10403
10481
  const vokabel = vokabelPhase.status === "ready" ? vokabelPhase.value : [];
10404
- const speichern = (0, import_react36.useCallback)(async (labels) => {
10482
+ const speichern = (0, import_react37.useCallback)(async (labels) => {
10405
10483
  if (!port.runs.setRunTags) return;
10406
10484
  setLaeuft(true);
10407
10485
  setNotiz(null);
@@ -10416,7 +10494,7 @@ var PromptTagEditor = ({
10416
10494
  setLaeuft(false);
10417
10495
  }
10418
10496
  }, [port, runId, onGespeichert]);
10419
- const hinzufuegen = (0, import_react36.useCallback)(() => {
10497
+ const hinzufuegen = (0, import_react37.useCallback)(() => {
10420
10498
  const label = eingabe.trim();
10421
10499
  if (label === "") return;
10422
10500
  if (promptTags.some((t) => t.l === label)) {
@@ -10494,15 +10572,15 @@ var PromptTagEditor = ({
10494
10572
  };
10495
10573
 
10496
10574
  // src/features/szenen/areas/usePagedPortQuery.ts
10497
- var import_react37 = require("react");
10575
+ var import_react38 = require("react");
10498
10576
  function usePagedPortQuery(seite, abhaengigkeiten, limit = LOAD_MORE_PAGE_SIZE) {
10499
- const [phase, setPhase] = (0, import_react37.useState)({ status: "loading" });
10500
- const [seiten, setSeiten] = (0, import_react37.useState)([]);
10501
- const [laedtMehr, setLaedtMehr] = (0, import_react37.useState)(false);
10502
- const [mehrFehler, setMehrFehler] = (0, import_react37.useState)(null);
10503
- const [token, setToken] = (0, import_react37.useState)(0);
10504
- const sequenz = (0, import_react37.useRef)(0);
10505
- (0, import_react37.useEffect)(() => {
10577
+ const [phase, setPhase] = (0, import_react38.useState)({ status: "loading" });
10578
+ const [seiten, setSeiten] = (0, import_react38.useState)([]);
10579
+ const [laedtMehr, setLaedtMehr] = (0, import_react38.useState)(false);
10580
+ const [mehrFehler, setMehrFehler] = (0, import_react38.useState)(null);
10581
+ const [token, setToken] = (0, import_react38.useState)(0);
10582
+ const sequenz = (0, import_react38.useRef)(0);
10583
+ (0, import_react38.useEffect)(() => {
10506
10584
  const meine = sequenz.current += 1;
10507
10585
  let abgemeldet = false;
10508
10586
  setPhase({ status: "loading" });
@@ -10535,7 +10613,7 @@ function usePagedPortQuery(seite, abhaengigkeiten, limit = LOAD_MORE_PAGE_SIZE)
10535
10613
  }, [...abhaengigkeiten, token, limit]);
10536
10614
  const letzte = seiten.length > 0 ? seiten[seiten.length - 1] : null;
10537
10615
  const hasMore = letzte?.hasMore === true;
10538
- const mehrLaden = (0, import_react37.useCallback)(() => {
10616
+ const mehrLaden = (0, import_react38.useCallback)(() => {
10539
10617
  if (!letzte || !letzte.hasMore || laedtMehr) return;
10540
10618
  const meine = sequenz.current;
10541
10619
  const cursor = letzte.nextCursor;
@@ -10587,7 +10665,7 @@ var MehrLaden = ({ zustand, geladen, gesamt, testId }) => {
10587
10665
  };
10588
10666
 
10589
10667
  // src/features/szenen/areas/ElementDetail.tsx
10590
- var import_react50 = require("react");
10668
+ var import_react51 = require("react");
10591
10669
 
10592
10670
  // src/features/szenen/elementLabels.ts
10593
10671
  var LABELS = {
@@ -10852,7 +10930,7 @@ function commonEnumOptions(zeiger) {
10852
10930
  }
10853
10931
 
10854
10932
  // src/features/szenen/areas/SchemaFields.tsx
10855
- var import_react39 = require("react");
10933
+ var import_react40 = require("react");
10856
10934
 
10857
10935
  // src/lib/szenen/versionDag.ts
10858
10936
  var VersionDagError = class extends Error {
@@ -11163,7 +11241,7 @@ async function selectableTargetModels(queries, contextChain) {
11163
11241
  }
11164
11242
 
11165
11243
  // src/features/szenen/areas/Zielmodellwahl.tsx
11166
- var import_react38 = require("react");
11244
+ var import_react39 = require("react");
11167
11245
  var import_jsx_runtime32 = (
11168
11246
  // Der Schluessel traegt den Key mit: Seit `unselectable`
11169
11247
  // keybezogen ist, erzeugt ein Fork mit zwei verschieden
@@ -11238,12 +11316,12 @@ function slugVorschlag(name) {
11238
11316
  return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
11239
11317
  }
11240
11318
  var NeuesZielmodellFormular = ({ testId, laeuft, onAnlegen, onAbbrechen }) => {
11241
- const [name, setName] = (0, import_react38.useState)("");
11242
- const [key, setKey] = (0, import_react38.useState)("");
11243
- const [provider, setProvider] = (0, import_react38.useState)("");
11244
- const [sprache, setSprache] = (0, import_react38.useState)("en");
11245
- const [negativ, setNegativ] = (0, import_react38.useState)(false);
11246
- const [keyBer\u00FChrt, setKeyBer\u00FChrt] = (0, import_react38.useState)(false);
11319
+ const [name, setName] = (0, import_react39.useState)("");
11320
+ const [key, setKey] = (0, import_react39.useState)("");
11321
+ const [provider, setProvider] = (0, import_react39.useState)("");
11322
+ const [sprache, setSprache] = (0, import_react39.useState)("en");
11323
+ const [negativ, setNegativ] = (0, import_react39.useState)(false);
11324
+ const [keyBer\u00FChrt, setKeyBer\u00FChrt] = (0, import_react39.useState)(false);
11247
11325
  const wirksamerKey = keyBer\u00FChrt ? key : slugVorschlag(name);
11248
11326
  const vollstaendig = name.trim() !== "" && wirksamerKey !== "" && provider.trim() !== "";
11249
11327
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "szenen-feld__zielmodell-neu", "data-testid": testId, children: [
@@ -11621,7 +11699,7 @@ var KetteNichtVerfuegbar = ({
11621
11699
  return null;
11622
11700
  };
11623
11701
  var VersionsAuswahl = ({ port, kontextkette, zieltypen, testId, onPick, disabled }) => {
11624
- const [elementId, setElementId] = (0, import_react39.useState)("");
11702
+ const [elementId, setElementId] = (0, import_react40.useState)("");
11625
11703
  const typenSchluessel = zieltypen.join(",");
11626
11704
  const kette = kontextkette.status === "ready" ? kontextkette.chain : null;
11627
11705
  const kettenSchluessel = kette ? kette.join(">") : `!${kontextkette.status}`;
@@ -11734,7 +11812,7 @@ var VersionRefFeld = ({
11734
11812
  zieltypen
11735
11813
  }) => {
11736
11814
  const aktuell = leseJson(wert);
11737
- const [wechseln, setWechseln] = (0, import_react39.useState)(false);
11815
+ const [wechseln, setWechseln] = (0, import_react40.useState)(false);
11738
11816
  const gesetzt = istVersionRef(aktuell) ? aktuell : null;
11739
11817
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "szenen-feld__ref", children: [
11740
11818
  gesetzt ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(RefAnzeige, { port, ref_: gesetzt, testId: `ref-${feld.path}-aktuell` }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { "data-testid": `ref-${feld.path}-leer`, children: "Keine Referenz gesetzt." }),
@@ -11780,7 +11858,7 @@ var VersionRefFeld = ({
11780
11858
  };
11781
11859
  var VersionRefListeFeld = ({ port, kontextkette, feld, wert, onChange, readOnly, zieltypen, mitRolle, mitGewicht }) => {
11782
11860
  const liste = leseListe(wert);
11783
- const rollen = (0, import_react39.useMemo)(
11861
+ const rollen = (0, import_react40.useMemo)(
11784
11862
  () => mitRolle === "styleRole" ? commonEnumOptions("styleRole") : [],
11785
11863
  [mitRolle]
11786
11864
  );
@@ -12024,7 +12102,7 @@ var KameraFeld = ({
12024
12102
  () => setupVersionId ? port.elements.getVersion(setupVersionId) : Promise.resolve({ kind: "ok", value: null }),
12025
12103
  [port, setupVersionId]
12026
12104
  );
12027
- const kameras = (0, import_react39.useMemo)(() => {
12105
+ const kameras = (0, import_react40.useMemo)(() => {
12028
12106
  if (phase.status !== "ready" || !phase.value) return [];
12029
12107
  const cameras = valueAtPath(phase.value.payload, "camera_rig.cameras");
12030
12108
  if (!Array.isArray(cameras)) return [];
@@ -12158,7 +12236,7 @@ var StringListeFeld = ({ feld, wert, onChange, readOnly }) => {
12158
12236
  };
12159
12237
  var MediaRefListeFeld = ({ feld, wert, onChange, readOnly }) => {
12160
12238
  const liste = leseListe(wert);
12161
- const rollen = (0, import_react39.useMemo)(() => commonEnumOptions("$defs/mediaRef/properties/role"), []);
12239
+ const rollen = (0, import_react40.useMemo)(() => commonEnumOptions("$defs/mediaRef/properties/role"), []);
12162
12240
  if (liste === null) {
12163
12241
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
12164
12242
  "textarea",
@@ -12593,10 +12671,10 @@ var SchemaFieldRow = (props) => {
12593
12671
  };
12594
12672
 
12595
12673
  // src/features/szenen/areas/SceneEditor.tsx
12596
- var import_react41 = require("react");
12674
+ var import_react42 = require("react");
12597
12675
 
12598
12676
  // src/features/szenen/areas/ReferencePicker.tsx
12599
- var import_react40 = require("react");
12677
+ var import_react41 = require("react");
12600
12678
  var import_jsx_runtime34 = require("react/jsx-runtime");
12601
12679
  var ReferencePicker = ({
12602
12680
  port,
@@ -12606,7 +12684,7 @@ var ReferencePicker = ({
12606
12684
  testId,
12607
12685
  onPick
12608
12686
  }) => {
12609
- const [elementId, setElementId] = (0, import_react40.useState)("");
12687
+ const [elementId, setElementId] = (0, import_react41.useState)("");
12610
12688
  const [liste] = usePortQuery(
12611
12689
  () => port.elements.listElements({ contextId, type }),
12612
12690
  [port, contextId, type]
@@ -12731,11 +12809,11 @@ var SceneEditor = ({
12731
12809
  }) => {
12732
12810
  const nodes = payload.nodes ?? [];
12733
12811
  const relations = payload.relations ?? [];
12734
- const [neuerNodeOffen, setNeuerNodeOffen] = (0, import_react41.useState)(false);
12735
- const [neueBeschreibung, setNeueBeschreibung] = (0, import_react41.useState)("");
12736
- const [neuerTyp, setNeuerTyp] = (0, import_react41.useState)("subject");
12737
- const [refZielNode, setRefZielNode] = (0, import_react41.useState)(null);
12738
- const [neueBeziehungOffen, setNeueBeziehungOffen] = (0, import_react41.useState)(false);
12812
+ const [neuerNodeOffen, setNeuerNodeOffen] = (0, import_react42.useState)(false);
12813
+ const [neueBeschreibung, setNeueBeschreibung] = (0, import_react42.useState)("");
12814
+ const [neuerTyp, setNeuerTyp] = (0, import_react42.useState)("subject");
12815
+ const [refZielNode, setRefZielNode] = (0, import_react42.useState)(null);
12816
+ const [neueBeziehungOffen, setNeueBeziehungOffen] = (0, import_react42.useState)(false);
12739
12817
  const schreibe = (next) => {
12740
12818
  if (readOnly) return;
12741
12819
  onChange(next);
@@ -12957,9 +13035,9 @@ var SceneEditor = ({
12957
13035
  );
12958
13036
  };
12959
13037
  var BeziehungAnlegen = ({ nodes, onAdd, onAbbrechen }) => {
12960
- const [von, setVon] = (0, import_react41.useState)(nodes[0].node_id);
12961
- const [typ, setTyp] = (0, import_react41.useState)("left_of");
12962
- const [nach, setNach] = (0, import_react41.useState)(nodes[1].node_id);
13038
+ const [von, setVon] = (0, import_react42.useState)(nodes[0].node_id);
13039
+ const [typ, setTyp] = (0, import_react42.useState)("left_of");
13040
+ const [nach, setNach] = (0, import_react42.useState)(nodes[1].node_id);
12963
13041
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "szenen-scene-editor__add-relation", children: [
12964
13042
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("select", { "data-testid": "relation-von", value: von, onChange: (e) => setVon(e.target.value), children: nodes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("option", { value: n.node_id, children: n.node_id }, n.node_id)) }),
12965
13043
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("select", { "data-testid": "relation-neu-typ", value: typ, onChange: (e) => setTyp(e.target.value), children: RELATION_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("option", { value: t, children: RELATION_TYPE_LABELS[t] }, t)) }),
@@ -12980,7 +13058,7 @@ var BeziehungAnlegen = ({ nodes, onAdd, onAbbrechen }) => {
12980
13058
  };
12981
13059
 
12982
13060
  // src/features/szenen/areas/CapabilityPanel.tsx
12983
- var import_react42 = require("react");
13061
+ var import_react43 = require("react");
12984
13062
 
12985
13063
  // src/lib/szenen/placement.ts
12986
13064
  var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -14121,9 +14199,9 @@ var ERKLAERUNG = {
14121
14199
  unknown: "Die Pr\xFCfung konnte nicht laufen. Das ist keine Absage \u2014 die Frage ist offen."
14122
14200
  };
14123
14201
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
14124
- const [bericht, setBericht] = (0, import_react42.useState)(null);
14125
- const [fehler5, setFehler] = (0, import_react42.useState)(null);
14126
- (0, import_react42.useEffect)(() => {
14202
+ const [bericht, setBericht] = (0, import_react43.useState)(null);
14203
+ const [fehler5, setFehler] = (0, import_react43.useState)(null);
14204
+ (0, import_react43.useEffect)(() => {
14127
14205
  let abgemeldet = false;
14128
14206
  setBericht(null);
14129
14207
  setFehler(null);
@@ -14179,7 +14257,7 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
14179
14257
  };
14180
14258
 
14181
14259
  // src/features/szenen/areas/PromptStartAction.tsx
14182
- var import_react43 = require("react");
14260
+ var import_react44 = require("react");
14183
14261
  var import_jsx_runtime37 = require("react/jsx-runtime");
14184
14262
  var PromptStartAction = ({
14185
14263
  port,
@@ -14188,10 +14266,10 @@ var PromptStartAction = ({
14188
14266
  blocker = null,
14189
14267
  onLauf
14190
14268
  }) => {
14191
- const [lauf, setLauf] = (0, import_react43.useState)(null);
14192
- const [meldung, setMeldung] = (0, import_react43.useState)(null);
14193
- const [ausgang, setAusgang] = (0, import_react43.useState)(null);
14194
- const [laeuft, setLaeuft] = (0, import_react43.useState)(false);
14269
+ const [lauf, setLauf] = (0, import_react44.useState)(null);
14270
+ const [meldung, setMeldung] = (0, import_react44.useState)(null);
14271
+ const [ausgang, setAusgang] = (0, import_react44.useState)(null);
14272
+ const [laeuft, setLaeuft] = (0, import_react44.useState)(false);
14195
14273
  const runs = port.runs;
14196
14274
  const ausKette = source.kind === "draft_chain";
14197
14275
  const id = (name) => ausKette ? `kette-${name}` : `szene-${name}`;
@@ -14266,7 +14344,7 @@ var PromptStartAction = ({
14266
14344
  };
14267
14345
 
14268
14346
  // src/features/szenen/areas/ManuellerLaufAction.tsx
14269
- var import_react44 = require("react");
14347
+ var import_react45 = require("react");
14270
14348
  var import_jsx_runtime38 = require("react/jsx-runtime");
14271
14349
  var OHNE_ZIELMODELL = "";
14272
14350
  var OHNE_URSPRUNG = "";
@@ -14282,15 +14360,15 @@ var ManuellerLaufAction = ({
14282
14360
  author,
14283
14361
  onLauf
14284
14362
  }) => {
14285
- const [offen, setOffen] = (0, import_react44.useState)(false);
14286
- const [prompttext, setPrompttext] = (0, import_react44.useState)("");
14287
- const [zielmodell, setZielmodell] = (0, import_react44.useState)(OHNE_ZIELMODELL);
14288
- const [notiz, setNotiz] = (0, import_react44.useState)("");
14289
- const [ursprung, setUrsprung] = (0, import_react44.useState)(OHNE_URSPRUNG);
14290
- const [laeuft, setLaeuft] = (0, import_react44.useState)(false);
14291
- const [meldung, setMeldung] = (0, import_react44.useState)(null);
14292
- const [ausgang, setAusgang] = (0, import_react44.useState)(null);
14293
- const [angelegt, setAngelegt] = (0, import_react44.useState)(null);
14363
+ const [offen, setOffen] = (0, import_react45.useState)(false);
14364
+ const [prompttext, setPrompttext] = (0, import_react45.useState)("");
14365
+ const [zielmodell, setZielmodell] = (0, import_react45.useState)(OHNE_ZIELMODELL);
14366
+ const [notiz, setNotiz] = (0, import_react45.useState)("");
14367
+ const [ursprung, setUrsprung] = (0, import_react45.useState)(OHNE_URSPRUNG);
14368
+ const [laeuft, setLaeuft] = (0, import_react45.useState)(false);
14369
+ const [meldung, setMeldung] = (0, import_react45.useState)(null);
14370
+ const [ausgang, setAusgang] = (0, import_react45.useState)(null);
14371
+ const [angelegt, setAngelegt] = (0, import_react45.useState)(null);
14294
14372
  const [modellePhase] = useZielmodelle(port, kontextkette);
14295
14373
  const [laeufePhase] = usePortQuery(
14296
14374
  () => port.runs.listRunsForScene(sceneElementId),
@@ -14575,7 +14653,7 @@ var VersionNavigator = ({
14575
14653
  };
14576
14654
 
14577
14655
  // src/features/szenen/areas/RunsPanel.tsx
14578
- var import_react49 = require("react");
14656
+ var import_react50 = require("react");
14579
14657
 
14580
14658
  // src/lib/szenen/manualPromptRun.ts
14581
14659
  var MANUAL_PROVIDER = "manual";
@@ -14675,16 +14753,16 @@ function manualEntryOf(run) {
14675
14753
  }
14676
14754
 
14677
14755
  // src/features/szenen/areas/useLaufTitel.ts
14678
- var import_react45 = require("react");
14756
+ var import_react46 = require("react");
14679
14757
  function useLaufTitel(port, laeufe, bekannt = {}) {
14680
- const [titelJeElement2, setTitelJeElement] = (0, import_react45.useState)(
14758
+ const [titelJeElement2, setTitelJeElement] = (0, import_react46.useState)(
14681
14759
  () => /* @__PURE__ */ new Map()
14682
14760
  );
14683
14761
  const gebraucht = [...new Set(
14684
14762
  laeufe.filter((lauf) => bekannt[lauf.run_id] === void 0).map(namingElementOf).filter((id) => id !== null)
14685
14763
  )].sort();
14686
14764
  const schluessel = gebraucht.join(",");
14687
- (0, import_react45.useEffect)(() => {
14765
+ (0, import_react46.useEffect)(() => {
14688
14766
  let abgemeldet = false;
14689
14767
  const offen = gebraucht.filter((id) => !titelJeElement2.has(id));
14690
14768
  if (offen.length === 0) return void 0;
@@ -14724,7 +14802,7 @@ function useLaufTitel(port, laeufe, bekannt = {}) {
14724
14802
  }
14725
14803
 
14726
14804
  // src/features/szenen/areas/RunImagesPanel.tsx
14727
- var import_react48 = __toESM(require("react"));
14805
+ var import_react49 = __toESM(require("react"));
14728
14806
 
14729
14807
  // src/lib/szenen/seedListe.ts
14730
14808
  var DEFAULT_SEED_VORLAGE = {
@@ -14959,7 +15037,7 @@ function schlageZipModellVor(dateiname, modelle) {
14959
15037
  }
14960
15038
 
14961
15039
  // src/features/szenen/areas/ZipModellDialog.tsx
14962
- var import_react46 = require("react");
15040
+ var import_react47 = require("react");
14963
15041
  var import_jsx_runtime40 = require("react/jsx-runtime");
14964
15042
  var VERTRAUEN_TEXT = {
14965
15043
  hoch: "hoch \u2014 der Dateiname nennt dieses Modell praktisch w\xF6rtlich",
@@ -14968,8 +15046,8 @@ var VERTRAUEN_TEXT = {
14968
15046
  };
14969
15047
  var ZipModellDialog = ({ testId, dateiname, bildAnzahl, vorschlag, laeuft, onUebernehmen, onImportieren, onSchliessen }) => {
14970
15048
  const auswahlbar = vorschlag.art === "treffer" ? [vorschlag.beste, ...vorschlag.alternativen] : [vorschlag.beste];
14971
- const [gewaehlt, setGewaehlt] = (0, import_react46.useState)(vorschlag.beste.key);
14972
- (0, import_react46.useEffect)(() => {
15049
+ const [gewaehlt, setGewaehlt] = (0, import_react47.useState)(vorschlag.beste.key);
15050
+ (0, import_react47.useEffect)(() => {
14973
15051
  const taste = (e) => {
14974
15052
  if (e.key === "Escape") onSchliessen();
14975
15053
  };
@@ -15088,14 +15166,14 @@ var ZipModellDialog = ({ testId, dateiname, bildAnzahl, vorschlag, laeuft, onUeb
15088
15166
  };
15089
15167
 
15090
15168
  // src/features/szenen/areas/useBildQuellen.ts
15091
- var import_react47 = require("react");
15169
+ var import_react48 = require("react");
15092
15170
  function useBildQuellen(port, bilder) {
15093
- const [stand, setStand] = (0, import_react47.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
15094
- const gehalten = (0, import_react47.useRef)(/* @__PURE__ */ new Map());
15095
- const gescheitert = (0, import_react47.useRef)(/* @__PURE__ */ new Map());
15096
- const [versuch, setVersuch] = (0, import_react47.useState)(0);
15171
+ const [stand, setStand] = (0, import_react48.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
15172
+ const gehalten = (0, import_react48.useRef)(/* @__PURE__ */ new Map());
15173
+ const gescheitert = (0, import_react48.useRef)(/* @__PURE__ */ new Map());
15174
+ const [versuch, setVersuch] = (0, import_react48.useState)(0);
15097
15175
  const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
15098
- (0, import_react47.useEffect)(() => {
15176
+ (0, import_react48.useEffect)(() => {
15099
15177
  const bestand = port.imageLibrary;
15100
15178
  if (!bestand) return void 0;
15101
15179
  let abgebrochen = false;
@@ -15156,11 +15234,11 @@ var AUFTRAG_TEXT = {
15156
15234
  };
15157
15235
  var RunImagesPanel = ({ port, lauf }) => {
15158
15236
  const faehig = capabilitiesOf(port);
15159
- const [nachladen, setNachladen] = (0, import_react48.useState)(0);
15237
+ const [nachladen, setNachladen] = (0, import_react49.useState)(0);
15160
15238
  const neuLesen = () => setNachladen((n) => n + 1);
15161
- const [erzeugenOffen, setErzeugenOffen] = (0, import_react48.useState)(null);
15162
- const [zuordnenOffen, setZuordnenOffen] = (0, import_react48.useState)(null);
15163
- const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react48.useState)(null);
15239
+ const [erzeugenOffen, setErzeugenOffen] = (0, import_react49.useState)(null);
15240
+ const [zuordnenOffen, setZuordnenOffen] = (0, import_react49.useState)(null);
15241
+ const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react49.useState)(null);
15164
15242
  const quelle = sourceOf(lauf);
15165
15243
  const [wahlPhase] = usePortQuery(
15166
15244
  () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
@@ -15562,7 +15640,7 @@ var BatchKachel = ({ port, batch }) => {
15562
15640
  /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
15563
15641
  "Batches",
15564
15642
  " ",
15565
- batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react48.default.Fragment, { children: [
15643
+ batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react49.default.Fragment, { children: [
15566
15644
  i > 0 ? ", " : "",
15567
15645
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { children: h.generation_batch_id }),
15568
15646
  ` (${HERKUNFT_KURZ[h.source_kind]})`
@@ -15591,11 +15669,11 @@ var ProfilBefunde = ({ wahl, runId }) => {
15591
15669
  ] });
15592
15670
  };
15593
15671
  var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
15594
- const [modellWahl, setModellWahl] = (0, import_react48.useState)(null);
15595
- const [anzahlText, setAnzahlText] = (0, import_react48.useState)("1");
15596
- const [seitenverhaeltnis, setSeitenverhaeltnis] = (0, import_react48.useState)("");
15597
- const [laeuft, setLaeuft] = (0, import_react48.useState)(false);
15598
- const [notiz, setNotiz] = (0, import_react48.useState)(null);
15672
+ const [modellWahl, setModellWahl] = (0, import_react49.useState)(null);
15673
+ const [anzahlText, setAnzahlText] = (0, import_react49.useState)("1");
15674
+ const [seitenverhaeltnis, setSeitenverhaeltnis] = (0, import_react49.useState)("");
15675
+ const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
15676
+ const [notiz, setNotiz] = (0, import_react49.useState)(null);
15599
15677
  if (!port.imageGeneration) {
15600
15678
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { "data-testid": `erzeugen-unsupported-${lauf.run_id}`, children: "Dieser Host erzeugt keine Bilder \u2014 entweder fehlt der Providerzugang oder der gesicherte Speicherweg. Externe Bilder lassen sich trotzdem zuordnen." });
15601
15679
  }
@@ -15772,26 +15850,26 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
15772
15850
  };
15773
15851
  var DEFAULT_VORLAGE_KEY = "__default__";
15774
15852
  var ZuordnenAktion = ({ port, lauf, onFertig }) => {
15775
- const [dateien, setDateien] = (0, import_react48.useState)([]);
15776
- const [seeds, setSeeds] = (0, import_react48.useState)({});
15777
- const [zip, setZip] = (0, import_react48.useState)(null);
15778
- const [zipLaedt, setZipLaedt] = (0, import_react48.useState)(false);
15779
- const [seedListe, setSeedListe] = (0, import_react48.useState)("");
15780
- const [vorlagen, setVorlagen] = (0, import_react48.useState)([DEFAULT_SEED_VORLAGE]);
15781
- const [vorlageKey, setVorlageKey] = (0, import_react48.useState)(DEFAULT_VORLAGE_KEY);
15782
- const [vorlagenStand, setVorlagenStand] = (0, import_react48.useState)(0);
15783
- const [vorlageBenennen, setVorlageBenennen] = (0, import_react48.useState)(false);
15784
- const [vorlageName, setVorlageName] = (0, import_react48.useState)("");
15785
- const [modell, setModell] = (0, import_react48.useState)("");
15786
- const [modellNeu, setModellNeu] = (0, import_react48.useState)(false);
15787
- const [modellLaeuft, setModellLaeuft] = (0, import_react48.useState)(false);
15788
- const [vorschlagOffen, setVorschlagOffen] = (0, import_react48.useState)(null);
15789
- const [vorschlag, setVorschlag] = (0, import_react48.useState)(null);
15790
- const [itemId, setItemId] = (0, import_react48.useState)("");
15791
- const [laeuft, setLaeuft] = (0, import_react48.useState)(false);
15792
- const [notiz, setNotiz] = (0, import_react48.useState)(null);
15793
- const dateifeld = (0, import_react48.useRef)(null);
15794
- const auswahlMarke = (0, import_react48.useRef)(0);
15853
+ const [dateien, setDateien] = (0, import_react49.useState)([]);
15854
+ const [seeds, setSeeds] = (0, import_react49.useState)({});
15855
+ const [zip, setZip] = (0, import_react49.useState)(null);
15856
+ const [zipLaedt, setZipLaedt] = (0, import_react49.useState)(false);
15857
+ const [seedListe, setSeedListe] = (0, import_react49.useState)("");
15858
+ const [vorlagen, setVorlagen] = (0, import_react49.useState)([DEFAULT_SEED_VORLAGE]);
15859
+ const [vorlageKey, setVorlageKey] = (0, import_react49.useState)(DEFAULT_VORLAGE_KEY);
15860
+ const [vorlagenStand, setVorlagenStand] = (0, import_react49.useState)(0);
15861
+ const [vorlageBenennen, setVorlageBenennen] = (0, import_react49.useState)(false);
15862
+ const [vorlageName, setVorlageName] = (0, import_react49.useState)("");
15863
+ const [modell, setModell] = (0, import_react49.useState)("");
15864
+ const [modellNeu, setModellNeu] = (0, import_react49.useState)(false);
15865
+ const [modellLaeuft, setModellLaeuft] = (0, import_react49.useState)(false);
15866
+ const [vorschlagOffen, setVorschlagOffen] = (0, import_react49.useState)(null);
15867
+ const [vorschlag, setVorschlag] = (0, import_react49.useState)(null);
15868
+ const [itemId, setItemId] = (0, import_react49.useState)("");
15869
+ const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
15870
+ const [notiz, setNotiz] = (0, import_react49.useState)(null);
15871
+ const dateifeld = (0, import_react49.useRef)(null);
15872
+ const auswahlMarke = (0, import_react49.useRef)(0);
15795
15873
  const eingefroreneKette = lauf.input_payload?.context_chain;
15796
15874
  const kontextkette = Array.isArray(eingefroreneKette) && eingefroreneKette.length > 0 ? { status: "ready", chain: eingefroreneKette } : {
15797
15875
  status: "error",
@@ -15800,11 +15878,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
15800
15878
  const [modellePhase, modelleNeuLesen] = useZielmodelle(port, kontextkette);
15801
15879
  const modelle = modellePhase.status === "ready" ? modellePhase.value.selectable : [];
15802
15880
  const modelleGesperrt = modellePhase.status === "ready" ? modellePhase.value.unselectable : [];
15803
- const modelleRef = (0, import_react48.useRef)(modelle);
15881
+ const modelleRef = (0, import_react49.useRef)(modelle);
15804
15882
  modelleRef.current = modelle;
15805
15883
  const anlageKontext = kontextkette.status === "ready" ? kontextkette.chain[0] ?? null : null;
15806
15884
  const zipAktiv = zip !== null;
15807
- (0, import_react48.useEffect)(() => {
15885
+ (0, import_react49.useEffect)(() => {
15808
15886
  if (!zipAktiv) return;
15809
15887
  let abgebrochen = false;
15810
15888
  void (async () => {
@@ -15819,7 +15897,7 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
15819
15897
  };
15820
15898
  }, [zipAktiv, port, vorlagenStand]);
15821
15899
  const vorschlagPhase = modellePhase.status;
15822
- (0, import_react48.useEffect)(() => {
15900
+ (0, import_react49.useEffect)(() => {
15823
15901
  if (!vorschlagOffen) return;
15824
15902
  if (vorschlagPhase === "loading") return;
15825
15903
  setVorschlagOffen(null);
@@ -16370,8 +16448,8 @@ var ChatAuftraegeAktion = ({
16370
16448
  laedt,
16371
16449
  onFertig
16372
16450
  }) => {
16373
- const [laufend, setLaufend] = (0, import_react48.useState)(null);
16374
- const [notiz, setNotiz] = (0, import_react48.useState)(null);
16451
+ const [laufend, setLaufend] = (0, import_react49.useState)(null);
16452
+ const [notiz, setNotiz] = (0, import_react49.useState)(null);
16375
16453
  async function uebernehmen(jobId) {
16376
16454
  if (!port.imageJobs) return;
16377
16455
  setLaufend(jobId);
@@ -16455,16 +16533,16 @@ var RunsPanel = ({
16455
16533
  contextId,
16456
16534
  initialOpenRunId = null
16457
16535
  }) => {
16458
- const [anweisung, setAnweisung] = (0, import_react49.useState)("");
16459
- const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
16460
- const [notiz, setNotiz] = (0, import_react49.useState)(null);
16461
- const [nachladen, setNachladen] = (0, import_react49.useState)(0);
16536
+ const [anweisung, setAnweisung] = (0, import_react50.useState)("");
16537
+ const [laeuft, setLaeuft] = (0, import_react50.useState)(false);
16538
+ const [notiz, setNotiz] = (0, import_react50.useState)(null);
16539
+ const [nachladen, setNachladen] = (0, import_react50.useState)(0);
16462
16540
  const faehig = capabilitiesOf(port);
16463
16541
  const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
16464
- const aktuelleQuelle = (0, import_react49.useRef)({ kind: source.kind, id: quellSchluessel });
16542
+ const aktuelleQuelle = (0, import_react50.useRef)({ kind: source.kind, id: quellSchluessel });
16465
16543
  aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
16466
- const laden = (0, import_react49.useMemo)(() => runsPageLoader(port), [port]);
16467
- const seite = (0, import_react49.useCallback)(
16544
+ const laden = (0, import_react50.useMemo)(() => runsPageLoader(port), [port]);
16545
+ const seite = (0, import_react50.useCallback)(
16468
16546
  (after, limit) => laden({
16469
16547
  source: source.kind === "published" ? { kind: "published", shotVersionId: source.shotVersionId } : source.kind === "draft_chain" ? { kind: "draft_chain", chainId: source.chainId } : { kind: "scene", sceneElementId: source.sceneElementId },
16470
16548
  filter: { status: "all" },
@@ -16496,26 +16574,26 @@ var RunsPanel = ({
16496
16574
  setNachladen((n) => n + 1);
16497
16575
  neuLaden();
16498
16576
  };
16499
- const laeufe = (0, import_react49.useMemo)(() => liste.seiten.flatMap((s) => s.runs), [liste.seiten]);
16500
- const runden = (0, import_react49.useMemo)(() => {
16577
+ const laeufe = (0, import_react50.useMemo)(() => liste.seiten.flatMap((s) => s.runs), [liste.seiten]);
16578
+ const runden = (0, import_react50.useMemo)(() => {
16501
16579
  const alle = {};
16502
16580
  for (const s of liste.seiten) Object.assign(alle, s.rounds);
16503
16581
  return alle;
16504
16582
  }, [liste.seiten]);
16505
16583
  const gesamt = phase.status === "ready" ? phase.value.total : laeufe.length;
16506
- const seitenTitel = (0, import_react49.useMemo)(() => {
16584
+ const seitenTitel = (0, import_react50.useMemo)(() => {
16507
16585
  const alle = {};
16508
16586
  for (const s of liste.seiten) Object.assign(alle, s.titles ?? {});
16509
16587
  return alle;
16510
16588
  }, [liste.seiten]);
16511
16589
  const titel = useLaufTitel(port, laeufe, seitenTitel);
16512
- const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react49.useState)(void 0);
16590
+ const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react50.useState)(void 0);
16513
16591
  const neueste = laeufe.length > 0 ? laeufe[0].run_id : null;
16514
- const startQuelle = (0, import_react49.useRef)(quellSchluessel);
16592
+ const startQuelle = (0, import_react50.useRef)(quellSchluessel);
16515
16593
  const angesteuert = initialOpenRunId && startQuelle.current === quellSchluessel ? initialOpenRunId : null;
16516
16594
  const vorauswahl = angesteuert ? laeufe.some((lauf) => lauf.run_id === angesteuert) ? angesteuert : null : neueste;
16517
16595
  const offeneRunde = gewaehlteRunde === void 0 ? vorauswahl : gewaehlteRunde;
16518
- (0, import_react49.useEffect)(() => {
16596
+ (0, import_react50.useEffect)(() => {
16519
16597
  setGewaehlteRunde(void 0);
16520
16598
  setAnweisung("");
16521
16599
  setNotiz(null);
@@ -16585,8 +16663,8 @@ var STATUS_TEXT = {
16585
16663
  };
16586
16664
  var LANGER_TEXT = 240;
16587
16665
  var PromptAbschnitt = ({ titel, text, ersatz, testId, kopierTestId, className }) => {
16588
- const [kopiert, setKopiert] = (0, import_react49.useState)(false);
16589
- const absatz = (0, import_react49.useRef)(null);
16666
+ const [kopiert, setKopiert] = (0, import_react50.useState)(false);
16667
+ const absatz = (0, import_react50.useRef)(null);
16590
16668
  async function kopieren() {
16591
16669
  if (text === null) return;
16592
16670
  try {
@@ -16623,7 +16701,7 @@ var PromptAbschnitt = ({ titel, text, ersatz, testId, kopierTestId, className })
16623
16701
  ] });
16624
16702
  };
16625
16703
  var Sekundaertext = ({ titel, text, testId }) => {
16626
- const [ganz, setGanz] = (0, import_react49.useState)(false);
16704
+ const [ganz, setGanz] = (0, import_react50.useState)(false);
16627
16705
  const lang = text.length > LANGER_TEXT;
16628
16706
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
16629
16707
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h5", { children: titel }),
@@ -16642,7 +16720,7 @@ var Sekundaertext = ({ titel, text, testId }) => {
16642
16720
  };
16643
16721
  var LISTE_ANFANG = 3;
16644
16722
  var SekundaerListe = ({ titel, eintraege, testId, className }) => {
16645
- const [alle, setAlle] = (0, import_react49.useState)(false);
16723
+ const [alle, setAlle] = (0, import_react50.useState)(false);
16646
16724
  const lang = eintraege.length > LISTE_ANFANG;
16647
16725
  const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
16648
16726
  return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
@@ -16661,9 +16739,9 @@ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
16661
16739
  ] });
16662
16740
  };
16663
16741
  var RundenEintrag = ({ port, lauf, runde, titel, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
16664
- const [notiz, setNotiz] = (0, import_react49.useState)(null);
16665
- const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
16666
- const [nachladen, setNachladen] = (0, import_react49.useState)(0);
16742
+ const [notiz, setNotiz] = (0, import_react50.useState)(null);
16743
+ const [laeuft, setLaeuft] = (0, import_react50.useState)(false);
16744
+ const [nachladen, setNachladen] = (0, import_react50.useState)(0);
16667
16745
  const [itemPhase] = usePortQuery(
16668
16746
  () => offen && port.items ? port.items.getItemForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
16669
16747
  [port, lauf.run_id, nachladen, offen]
@@ -16873,43 +16951,43 @@ var ElementDetail = ({
16873
16951
  onChanged,
16874
16952
  initialOpenRunId = null
16875
16953
  }) => {
16876
- const [nachladen, setNachladen] = (0, import_react50.useState)(0);
16954
+ const [nachladen, setNachladen] = (0, import_react51.useState)(0);
16877
16955
  const [phase] = usePortQuery(
16878
16956
  () => port.elements.getElement(elementId),
16879
16957
  [port, elementId, nachladen]
16880
16958
  );
16881
- const [entwurf, setEntwurf] = (0, import_react50.useState)({});
16882
- const [notiz, setNotiz] = (0, import_react50.useState)(null);
16883
- const [laeuft, setLaeuft] = (0, import_react50.useState)(false);
16884
- const [wahl, setWahl] = (0, import_react50.useState)(null);
16885
- const [basiswechsel, setBasiswechsel] = (0, import_react50.useState)(null);
16959
+ const [entwurf, setEntwurf] = (0, import_react51.useState)({});
16960
+ const [notiz, setNotiz] = (0, import_react51.useState)(null);
16961
+ const [laeuft, setLaeuft] = (0, import_react51.useState)(false);
16962
+ const [wahl, setWahl] = (0, import_react51.useState)(null);
16963
+ const [basiswechsel, setBasiswechsel] = (0, import_react51.useState)(null);
16886
16964
  const [kettenPhase] = usePortQuery(
16887
16965
  () => port.context.chainFor(contextId),
16888
16966
  [port, contextId]
16889
16967
  );
16890
- const kontextkette = (0, import_react50.useMemo)(() => {
16968
+ const kontextkette = (0, import_react51.useMemo)(() => {
16891
16969
  if (kettenPhase.status === "ready") return { status: "ready", chain: kettenPhase.value };
16892
16970
  if (kettenPhase.status === "error") return { status: "error", message: kettenPhase.message };
16893
16971
  return { status: "loading" };
16894
16972
  }, [kettenPhase]);
16895
16973
  const kette = kontextkette.status === "ready" ? kontextkette.chain : null;
16896
16974
  const schnappschuss = phase.status === "ready" ? phase.value : null;
16897
- const kopfErgebnis = (0, import_react50.useMemo)(
16975
+ const kopfErgebnis = (0, import_react51.useMemo)(
16898
16976
  () => schnappschuss && kette ? effectivePreferredHeadSafe(schnappschuss, kette) : null,
16899
16977
  [schnappschuss, kette]
16900
16978
  );
16901
16979
  const kopfAufloesung = resolutionOf(kopfErgebnis);
16902
16980
  const bevorzugteVersion = selectedHeadVersionId(kopfAufloesung);
16903
- const felder = (0, import_react50.useMemo)(
16981
+ const felder = (0, import_react51.useMemo)(
16904
16982
  () => schnappschuss ? fieldsForType(schnappschuss.element.type) : [],
16905
16983
  [schnappschuss?.element.type]
16906
16984
  );
16907
- (0, import_react50.useEffect)(() => {
16985
+ (0, import_react51.useEffect)(() => {
16908
16986
  setWahl(null);
16909
16987
  setBasiswechsel(null);
16910
16988
  setNotiz(null);
16911
16989
  }, [elementId]);
16912
- const automatisch = (0, import_react50.useMemo)(() => {
16990
+ const automatisch = (0, import_react51.useMemo)(() => {
16913
16991
  if (!schnappschuss) return { kind: "keiner" };
16914
16992
  if (schnappschuss.draft) return { kind: "draft" };
16915
16993
  if (bevorzugteVersion) return { kind: "version", versionId: bevorzugteVersion };
@@ -16919,14 +16997,14 @@ var ElementDetail = ({
16919
16997
  if (schnappschuss.versions.length === 0) return { kind: "draft" };
16920
16998
  return { kind: "keiner" };
16921
16999
  }, [schnappschuss, bevorzugteVersion]);
16922
- const angezeigt = (0, import_react50.useMemo)(() => {
17000
+ const angezeigt = (0, import_react51.useMemo)(() => {
16923
17001
  if (!wahl) return automatisch;
16924
17002
  if (wahl.kind === "version" && !schnappschuss?.versions.some((v) => v.version_id === wahl.versionId)) {
16925
17003
  return automatisch;
16926
17004
  }
16927
17005
  return wahl;
16928
17006
  }, [wahl, automatisch, schnappschuss]);
16929
- const angezeigtePayload = (0, import_react50.useMemo)(() => {
17007
+ const angezeigtePayload = (0, import_react51.useMemo)(() => {
16930
17008
  if (!schnappschuss) return {};
16931
17009
  if (angezeigt.kind === "draft") return schnappschuss.draft?.payload ?? {};
16932
17010
  if (angezeigt.kind === "version") {
@@ -16935,7 +17013,7 @@ var ElementDetail = ({
16935
17013
  return {};
16936
17014
  }, [schnappschuss, angezeigt]);
16937
17015
  const anzeigeSchluessel = angezeigt.kind === "version" ? `v:${angezeigt.versionId}` : angezeigt.kind;
16938
- (0, import_react50.useEffect)(() => {
17016
+ (0, import_react51.useEffect)(() => {
16939
17017
  if (!schnappschuss) return;
16940
17018
  setEntwurf(formFromPayload(felder, angezeigtePayload));
16941
17019
  }, [
@@ -16948,7 +17026,7 @@ var ElementDetail = ({
16948
17026
  const bearbeitbar = angezeigt.kind === "draft";
16949
17027
  const setzeFeld = (pfad, wert) => setEntwurf((vorher) => ({ ...vorher, [pfad]: wert }));
16950
17028
  const payloadAusFormular = () => payloadFromForm(felder, entwurf);
16951
- const strukturPayload = (0, import_react50.useMemo)(() => {
17029
+ const strukturPayload = (0, import_react51.useMemo)(() => {
16952
17030
  const lies = (name) => {
16953
17031
  try {
16954
17032
  const roh = JSON.parse(entwurf[name] || "[]");
@@ -16959,19 +17037,19 @@ var ElementDetail = ({
16959
17037
  };
16960
17038
  return { nodes: lies("nodes"), relations: lies("relations") };
16961
17039
  }, [entwurf.nodes, entwurf.relations]);
16962
- const kettenfeld = (0, import_react50.useMemo)(() => {
17040
+ const kettenfeld = (0, import_react51.useMemo)(() => {
16963
17041
  const kette2 = schnappschuss?.draft?.chain;
16964
17042
  if (!kette2) return null;
16965
17043
  return kette2.links[0]?.field ?? null;
16966
17044
  }, [schnappschuss?.draft?.chain]);
16967
- const laufStand = (0, import_react50.useMemo)(() => {
17045
+ const laufStand = (0, import_react51.useMemo)(() => {
16968
17046
  if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
16969
17047
  if (schnappschuss.draft) return schnappschuss.draft.payload;
16970
17048
  const kopf = usableHeadVersionId(kopfAufloesung);
16971
17049
  if (!kopf) return null;
16972
17050
  return schnappschuss.versions.find((v) => v.version_id === kopf)?.payload ?? null;
16973
17051
  }, [schnappschuss, kopfAufloesung]);
16974
- const szenenBlocker = (0, import_react50.useMemo)(() => {
17052
+ const szenenBlocker = (0, import_react51.useMemo)(() => {
16975
17053
  if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
16976
17054
  if (laufStand === null) {
16977
17055
  return schnappschuss.versions.length > 0 ? "Diese Szene hat keinen Entwurf und in diesem Kontext keinen bevorzugten Kopf. Welcher Stand gilt, entscheidet keine Reihenfolge (D-36) \u2014 bitte oben eine Version als bevorzugt setzen." : "Diese Szene hat noch keinen gespeicherten Stand. Ein Lauf friert ein, was gespeichert ist \u2014 bitte zuerst den Entwurf speichern.";
@@ -16981,7 +17059,7 @@ var ElementDetail = ({
16981
17059
  const grund = flag.reasons[0];
16982
17060
  return grund && "message" in grund ? grund.message : "Kein Lauf moeglich.";
16983
17061
  }, [schnappschuss, laufStand]);
16984
- const vorschau = (0, import_react50.useMemo)(() => {
17062
+ const vorschau = (0, import_react51.useMemo)(() => {
16985
17063
  if (!schnappschuss) return null;
16986
17064
  try {
16987
17065
  const roh = validatePayload(schnappschuss.element.type, payloadAusFormular());
@@ -17378,15 +17456,15 @@ var ElementDetail = ({
17378
17456
  };
17379
17457
 
17380
17458
  // src/features/szenen/areas/useSchmalesDisplay.ts
17381
- var import_react51 = require("react");
17459
+ var import_react52 = require("react");
17382
17460
  var SCHMALE_ANSICHT = "(max-width: 760px)";
17383
17461
  function messen(abfrage) {
17384
17462
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
17385
17463
  return window.matchMedia(abfrage).matches;
17386
17464
  }
17387
17465
  function useSchmalesDisplay(abfrage = SCHMALE_ANSICHT) {
17388
- const [schmal, setSchmal] = (0, import_react51.useState)(() => messen(abfrage));
17389
- (0, import_react51.useEffect)(() => {
17466
+ const [schmal, setSchmal] = (0, import_react52.useState)(() => messen(abfrage));
17467
+ (0, import_react52.useEffect)(() => {
17390
17468
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
17391
17469
  const liste = window.matchMedia(abfrage);
17392
17470
  const aktualisieren = () => setSchmal(liste.matches);
@@ -17416,16 +17494,16 @@ var ElementsArea = ({
17416
17494
  const gemischt = auswahlTypen.length > 1;
17417
17495
  const ueberschrift = listTitle ?? (einzelTyp ? elementTypPlural(einzelTyp) : "Bausteine");
17418
17496
  const einzahl = einzelTyp ? elementTypLabel(einzelTyp) : "Objekt";
17419
- const [typ, setTyp] = (0, import_react52.useState)(einzelTyp ?? "");
17420
- const [zeigeStillgelegte, setZeigeStillgelegte] = (0, import_react52.useState)(false);
17421
- const [gewaehlt, setGewaehlt] = (0, import_react52.useState)(initialSelected);
17422
- const [neuerTitel, setNeuerTitel] = (0, import_react52.useState)("");
17423
- const [neuerTyp, setNeuerTyp] = (0, import_react52.useState)(auswahlTypen[0]);
17424
- const [notiz, setNotiz] = (0, import_react52.useState)(null);
17425
- const [anlegend, setAnlegend] = (0, import_react52.useState)(false);
17426
- const [anlageOffen, setAnlageOffen] = (0, import_react52.useState)(false);
17497
+ const [typ, setTyp] = (0, import_react53.useState)(einzelTyp ?? "");
17498
+ const [zeigeStillgelegte, setZeigeStillgelegte] = (0, import_react53.useState)(false);
17499
+ const [gewaehlt, setGewaehlt] = (0, import_react53.useState)(initialSelected);
17500
+ const [neuerTitel, setNeuerTitel] = (0, import_react53.useState)("");
17501
+ const [neuerTyp, setNeuerTyp] = (0, import_react53.useState)(auswahlTypen[0]);
17502
+ const [notiz, setNotiz] = (0, import_react53.useState)(null);
17503
+ const [anlegend, setAnlegend] = (0, import_react53.useState)(false);
17504
+ const [anlageOffen, setAnlageOffen] = (0, import_react53.useState)(false);
17427
17505
  const schmal = useSchmalesDisplay();
17428
- const [ansicht, setAnsicht] = (0, import_react52.useState)(
17506
+ const [ansicht, setAnsicht] = (0, import_react53.useState)(
17429
17507
  initialSelected ? "detail" : "liste"
17430
17508
  );
17431
17509
  const zeigeListe = !schmal || ansicht === "liste";
@@ -17435,9 +17513,9 @@ var ElementsArea = ({
17435
17513
  setAnsicht("detail");
17436
17514
  onSelect?.(elementId);
17437
17515
  };
17438
- const laden = (0, import_react52.useMemo)(() => elementsPageLoader(port), [port]);
17516
+ const laden = (0, import_react53.useMemo)(() => elementsPageLoader(port), [port]);
17439
17517
  const typenSchluessel = restrictToTypes ? restrictToTypes.join(",") : "";
17440
- const seite = (0, import_react52.useCallback)(
17518
+ const seite = (0, import_react53.useCallback)(
17441
17519
  (after, limit) => laden(
17442
17520
  {
17443
17521
  contextId,
@@ -17451,9 +17529,9 @@ var ElementsArea = ({
17451
17529
  );
17452
17530
  const liste = usePagedPortQuery(seite, [seite]);
17453
17531
  const { phase, neuLaden } = liste;
17454
- const elemente = (0, import_react52.useMemo)(() => liste.seiten.flatMap((s) => s.items), [liste.seiten]);
17532
+ const elemente = (0, import_react53.useMemo)(() => liste.seiten.flatMap((s) => s.items), [liste.seiten]);
17455
17533
  const gesamt = phase.status === "ready" ? phase.value.total : elemente.length;
17456
- const annotationen = (0, import_react52.useMemo)(() => {
17534
+ const annotationen = (0, import_react53.useMemo)(() => {
17457
17535
  const alle = {};
17458
17536
  for (const s of liste.seiten) Object.assign(alle, s.annotations ?? {});
17459
17537
  return alle;
@@ -17699,10 +17777,10 @@ var ElementsArea = ({
17699
17777
  };
17700
17778
 
17701
17779
  // src/features/szenen/areas/SceneArea.tsx
17702
- var import_react56 = require("react");
17780
+ var import_react57 = require("react");
17703
17781
 
17704
17782
  // src/features/szenen/areas/DeriveArea.tsx
17705
- var import_react55 = require("react");
17783
+ var import_react56 = require("react");
17706
17784
 
17707
17785
  // src/lib/szenen/derivationExamples.ts
17708
17786
  var PROPOSAL_EXAMPLE = {
@@ -18541,7 +18619,7 @@ async function runSceneDerivation(input, port, userPrompt) {
18541
18619
  }
18542
18620
 
18543
18621
  // src/features/szenen/areas/RecoveryPanel.tsx
18544
- var import_react53 = require("react");
18622
+ var import_react54 = require("react");
18545
18623
  var import_jsx_runtime45 = require("react/jsx-runtime");
18546
18624
  var Diagnosen = ({ befunde, testId }) => {
18547
18625
  if (befunde.length === 0) return null;
@@ -18592,11 +18670,11 @@ var Ausbeute = ({ bewertung }) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)
18592
18670
  ] })
18593
18671
  ] });
18594
18672
  var RecoveryPanel = ({ port, lauf }) => {
18595
- const [arbeitskopie, setArbeitskopie] = (0, import_react53.useState)(null);
18596
- const [bewertung, setBewertung] = (0, import_react53.useState)(null);
18597
- const [gesichert, setGesichert] = (0, import_react53.useState)(null);
18598
- const [meldung, setMeldung] = (0, import_react53.useState)(null);
18599
- const [laeuft, setLaeuft] = (0, import_react53.useState)(false);
18673
+ const [arbeitskopie, setArbeitskopie] = (0, import_react54.useState)(null);
18674
+ const [bewertung, setBewertung] = (0, import_react54.useState)(null);
18675
+ const [gesichert, setGesichert] = (0, import_react54.useState)(null);
18676
+ const [meldung, setMeldung] = (0, import_react54.useState)(null);
18677
+ const [laeuft, setLaeuft] = (0, import_react54.useState)(false);
18600
18678
  if (!port.derivation) return null;
18601
18679
  if (typeof lauf.raw_output !== "string" || lauf.raw_output.length === 0) return null;
18602
18680
  const derivation = port.derivation;
@@ -18719,12 +18797,12 @@ var RecoveryPanel = ({ port, lauf }) => {
18719
18797
  };
18720
18798
 
18721
18799
  // src/features/szenen/areas/ChainActions.tsx
18722
- var import_react54 = require("react");
18800
+ var import_react55 = require("react");
18723
18801
  var import_jsx_runtime46 = require("react/jsx-runtime");
18724
18802
  var ChainActions = ({ port, runId, recoveryId }) => {
18725
- const [graph, setGraph] = (0, import_react54.useState)(null);
18726
- const [meldung, setMeldung] = (0, import_react54.useState)(null);
18727
- const [laeuft, setLaeuft] = (0, import_react54.useState)(false);
18803
+ const [graph, setGraph] = (0, import_react55.useState)(null);
18804
+ const [meldung, setMeldung] = (0, import_react55.useState)(null);
18805
+ const [laeuft, setLaeuft] = (0, import_react55.useState)(false);
18728
18806
  if (!port.derivation) return null;
18729
18807
  const derivation = port.derivation;
18730
18808
  const runs = port.runs;
@@ -18810,8 +18888,8 @@ var MODUS_TEXT = {
18810
18888
  image_prompt: "Bild und Prompt"
18811
18889
  };
18812
18890
  var BelegFeld = ({ titel, text, testId, offen }) => {
18813
- const [kopiert, setKopiert] = (0, import_react55.useState)(false);
18814
- const feld = (0, import_react55.useRef)(null);
18891
+ const [kopiert, setKopiert] = (0, import_react56.useState)(false);
18892
+ const feld = (0, import_react56.useRef)(null);
18815
18893
  async function kopieren() {
18816
18894
  try {
18817
18895
  await navigator.clipboard.writeText(text);
@@ -18908,9 +18986,9 @@ function readAsBase642(datei) {
18908
18986
  }
18909
18987
  var ERSTE_KACHELN = 12;
18910
18988
  function useSichtbareKacheln(alle, wurzel) {
18911
- const [gesehen, setGesehen] = (0, import_react55.useState)([]);
18989
+ const [gesehen, setGesehen] = (0, import_react56.useState)([]);
18912
18990
  const schluessel = alle.join(" ");
18913
- (0, import_react55.useEffect)(() => {
18991
+ (0, import_react56.useEffect)(() => {
18914
18992
  const ziel = wurzel.current;
18915
18993
  if (!ziel || typeof IntersectionObserver === "undefined") return void 0;
18916
18994
  const beobachter = new IntersectionObserver((eintraege) => {
@@ -18922,17 +19000,17 @@ function useSichtbareKacheln(alle, wurzel) {
18922
19000
  ziel.querySelectorAll("[data-media-ref]").forEach((el) => beobachter.observe(el));
18923
19001
  return () => beobachter.disconnect();
18924
19002
  }, [wurzel, schluessel]);
18925
- return (0, import_react55.useMemo)(
19003
+ return (0, import_react56.useMemo)(
18926
19004
  () => [.../* @__PURE__ */ new Set([...alle.slice(0, ERSTE_KACHELN), ...gesehen])],
18927
19005
  [schluessel, gesehen]
18928
19006
  );
18929
19007
  }
18930
19008
  function useVorschauen2(port, mediaRefs) {
18931
- const [stand, setStand] = (0, import_react55.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), gescheitert: /* @__PURE__ */ new Set() }));
18932
- const gehalten = (0, import_react55.useRef)(/* @__PURE__ */ new Map());
18933
- const ohneBild = (0, import_react55.useRef)(/* @__PURE__ */ new Set());
19009
+ const [stand, setStand] = (0, import_react56.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), gescheitert: /* @__PURE__ */ new Set() }));
19010
+ const gehalten = (0, import_react56.useRef)(/* @__PURE__ */ new Map());
19011
+ const ohneBild = (0, import_react56.useRef)(/* @__PURE__ */ new Set());
18934
19012
  const schluessel = mediaRefs.join(" ");
18935
- (0, import_react55.useEffect)(() => {
19013
+ (0, import_react56.useEffect)(() => {
18936
19014
  const bestand = port.imageLibrary;
18937
19015
  if (!bestand?.loadPreview) return void 0;
18938
19016
  const refs = schluessel ? schluessel.split(" ") : [];
@@ -18970,21 +19048,21 @@ function useVorschauen2(port, mediaRefs) {
18970
19048
  return stand;
18971
19049
  }
18972
19050
  var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
18973
- const [suche, setSuche] = (0, import_react55.useState)("");
18974
- const [fokus, setFokus] = (0, import_react55.useState)(null);
18975
- const browser = (0, import_react55.useRef)(null);
19051
+ const [suche, setSuche] = (0, import_react56.useState)("");
19052
+ const [fokus, setFokus] = (0, import_react56.useState)(null);
19053
+ const browser = (0, import_react56.useRef)(null);
18976
19054
  const [items] = usePortQuery(
18977
19055
  () => port.imageLibrary.listItems({ search: suche.trim() || void 0, limit: 60 }),
18978
19056
  [port, suche]
18979
19057
  );
18980
19058
  const werke = items.status === "ready" ? items.value : [];
18981
19059
  const werk = werke.find((i) => i.itemRef === fokus) ?? null;
18982
- const deckbilder = (0, import_react55.useMemo)(
19060
+ const deckbilder = (0, import_react56.useMemo)(
18983
19061
  () => werke.map((i) => i.images[0]?.mediaRef).filter((r) => Boolean(r)),
18984
19062
  [werke]
18985
19063
  );
18986
19064
  const sichtbareDeckbilder = useSichtbareKacheln(deckbilder, browser);
18987
- const anzufordern = (0, import_react55.useMemo)(() => [.../* @__PURE__ */ new Set([
19065
+ const anzufordern = (0, import_react56.useMemo)(() => [.../* @__PURE__ */ new Set([
18988
19066
  ...sichtbareDeckbilder,
18989
19067
  ...werk ? werk.images.map((b) => b.mediaRef) : [],
18990
19068
  ...gewaehlt.map((b) => b.mediaRef)
@@ -19134,28 +19212,28 @@ async function digestOf(datei) {
19134
19212
  return [...new Uint8Array(hash)].map((b) => b.toString(16).padStart(2, "0")).join("");
19135
19213
  }
19136
19214
  var DeriveArea = ({ port, contextId }) => {
19137
- const [policyVersionId, setPolicyVersionId] = (0, import_react55.useState)("");
19138
- const [modus, setModus] = (0, import_react55.useState)("prompt");
19139
- const [promptText, setPromptText] = (0, import_react55.useState)("");
19140
- const [dateien, setDateien] = (0, import_react55.useState)([]);
19141
- const [bestandsbilder, setBestandsbilder] = (0, import_react55.useState)([]);
19142
- const [laeuft, setLaeuft] = (0, import_react55.useState)(false);
19143
- const [lauf, setLauf] = (0, import_react55.useState)(null);
19144
- const [meldung, setMeldung] = (0, import_react55.useState)(null);
19145
- const [uebernommen, setUebernommen] = (0, import_react55.useState)({});
19146
- const [titel, setTitel] = (0, import_react55.useState)({});
19147
- const [referenzen, setReferenzen] = (0, import_react55.useState)({});
19148
- const [beanstandung, setBeanstandung] = (0, import_react55.useState)(null);
19149
- const [zusatz, setZusatz] = (0, import_react55.useState)("");
19215
+ const [policyVersionId, setPolicyVersionId] = (0, import_react56.useState)("");
19216
+ const [modus, setModus] = (0, import_react56.useState)("prompt");
19217
+ const [promptText, setPromptText] = (0, import_react56.useState)("");
19218
+ const [dateien, setDateien] = (0, import_react56.useState)([]);
19219
+ const [bestandsbilder, setBestandsbilder] = (0, import_react56.useState)([]);
19220
+ const [laeuft, setLaeuft] = (0, import_react56.useState)(false);
19221
+ const [lauf, setLauf] = (0, import_react56.useState)(null);
19222
+ const [meldung, setMeldung] = (0, import_react56.useState)(null);
19223
+ const [uebernommen, setUebernommen] = (0, import_react56.useState)({});
19224
+ const [titel, setTitel] = (0, import_react56.useState)({});
19225
+ const [referenzen, setReferenzen] = (0, import_react56.useState)({});
19226
+ const [beanstandung, setBeanstandung] = (0, import_react56.useState)(null);
19227
+ const [zusatz, setZusatz] = (0, import_react56.useState)("");
19150
19228
  const [regeln] = usePortQuery(
19151
19229
  () => port.derivation.listPolicies(contextId),
19152
19230
  [port, contextId]
19153
19231
  );
19154
- const sortiert = (0, import_react55.useMemo)(() => regeln.status === "ready" ? [...regeln.value].sort((a, b) => {
19232
+ const sortiert = (0, import_react56.useMemo)(() => regeln.status === "ready" ? [...regeln.value].sort((a, b) => {
19155
19233
  const szeneZuerst = Number(b.produces.includes("scene")) - Number(a.produces.includes("scene"));
19156
19234
  return szeneZuerst !== 0 ? szeneZuerst : a.title.localeCompare(b.title, "de");
19157
19235
  }) : [], [regeln]);
19158
- (0, import_react55.useEffect)(() => {
19236
+ (0, import_react56.useEffect)(() => {
19159
19237
  if (policyVersionId) return;
19160
19238
  const fuerSzene = sortiert.find((r) => r.produces.includes("scene"));
19161
19239
  if (fuerSzene) setPolicyVersionId(fuerSzene.versionId);
@@ -19539,7 +19617,7 @@ var SceneArea = ({
19539
19617
  initialOpenRunId = null
19540
19618
  }) => {
19541
19619
  const kannAbleiten = capabilitiesOf(port).derivation;
19542
- const [ableiten, setAbleiten] = (0, import_react56.useState)(false);
19620
+ const [ableiten, setAbleiten] = (0, import_react57.useState)(false);
19543
19621
  const zeigeDerive = kannAbleiten && ableiten;
19544
19622
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "szenen-scene", "data-testid": "bereich-szene", children: [
19545
19623
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "szenen-elements__toolbar", children: [
@@ -19587,7 +19665,7 @@ var AdvancedArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runti
19587
19665
  ] });
19588
19666
 
19589
19667
  // src/features/szenen/areas/PromptsArea.tsx
19590
- var import_react57 = require("react");
19668
+ var import_react58 = require("react");
19591
19669
  var import_jsx_runtime50 = require("react/jsx-runtime");
19592
19670
  var HERKUNFT_TEXT2 = {
19593
19671
  published: "aus ver\xF6ffentlichtem Shot",
@@ -19615,15 +19693,15 @@ var PromptsArea = ({
19615
19693
  fokusRunId = null
19616
19694
  }) => {
19617
19695
  const faehig = capabilitiesOf(port);
19618
- const [nachladen, setNachladen] = (0, import_react57.useState)(0);
19619
- const [offen, setOffen] = (0, import_react57.useState)(null);
19620
- const [alleKontexte, setAlleKontexte] = (0, import_react57.useState)(false);
19621
- const [mitFehllaeufen, setMitFehllaeufen] = (0, import_react57.useState)(false);
19622
- const [herkunft, setHerkunft] = (0, import_react57.useState)("alle");
19623
- const [fokus, setFokus] = (0, import_react57.useState)(
19696
+ const [nachladen, setNachladen] = (0, import_react58.useState)(0);
19697
+ const [offen, setOffen] = (0, import_react58.useState)(null);
19698
+ const [alleKontexte, setAlleKontexte] = (0, import_react58.useState)(false);
19699
+ const [mitFehllaeufen, setMitFehllaeufen] = (0, import_react58.useState)(false);
19700
+ const [herkunft, setHerkunft] = (0, import_react58.useState)("alle");
19701
+ const [fokus, setFokus] = (0, import_react58.useState)(
19624
19702
  fokusRunId ? { status: "laden", runId: fokusRunId } : null
19625
19703
  );
19626
- (0, import_react57.useEffect)(() => {
19704
+ (0, import_react58.useEffect)(() => {
19627
19705
  if (!fokusRunId) {
19628
19706
  setFokus(null);
19629
19707
  return;
@@ -19659,9 +19737,9 @@ var PromptsArea = ({
19659
19737
  aktuell = false;
19660
19738
  };
19661
19739
  }, [fokusRunId, port]);
19662
- const laden = (0, import_react57.useMemo)(() => runsPageLoader(port), [port]);
19740
+ const laden = (0, import_react58.useMemo)(() => runsPageLoader(port), [port]);
19663
19741
  const imFokus = fokus?.status === "gefunden";
19664
- const seite = (0, import_react57.useCallback)(
19742
+ const seite = (0, import_react58.useCallback)(
19665
19743
  (after, limit) => laden({
19666
19744
  source: imFokus ? fokus.source : { kind: "all" },
19667
19745
  filter: imFokus ? { status: "all" } : {
@@ -19678,35 +19756,35 @@ var PromptsArea = ({
19678
19756
  [seite, nachladen]
19679
19757
  );
19680
19758
  const { phase } = liste;
19681
- const gezeigt = (0, import_react57.useMemo)(
19759
+ const gezeigt = (0, import_react58.useMemo)(
19682
19760
  () => liste.seiten.flatMap((s) => s.runs),
19683
19761
  [liste.seiten]
19684
19762
  );
19685
- const runden = (0, import_react57.useMemo)(() => {
19763
+ const runden = (0, import_react58.useMemo)(() => {
19686
19764
  const alle = {};
19687
19765
  for (const s of liste.seiten) Object.assign(alle, s.rounds);
19688
19766
  return alle;
19689
19767
  }, [liste.seiten]);
19690
- const [bearbeitet, setBearbeitet] = (0, import_react57.useState)({});
19691
- const promptTags = (0, import_react57.useMemo)(() => {
19768
+ const [bearbeitet, setBearbeitet] = (0, import_react58.useState)({});
19769
+ const promptTags = (0, import_react58.useMemo)(() => {
19692
19770
  const alle = {};
19693
19771
  for (const s of liste.seiten) Object.assign(alle, s.promptTags ?? {});
19694
19772
  return { ...alle, ...bearbeitet };
19695
19773
  }, [liste.seiten, bearbeitet]);
19696
- const annotationen = (0, import_react57.useMemo)(() => {
19774
+ const annotationen = (0, import_react58.useMemo)(() => {
19697
19775
  const alle = {};
19698
19776
  for (const s of liste.seiten) Object.assign(alle, s.annotations ?? {});
19699
19777
  return alle;
19700
19778
  }, [liste.seiten]);
19701
19779
  const sicht = phase.status === "ready" ? phase.value : null;
19702
19780
  const fokusSichtbar = imFokus && gezeigt.some((l) => l.run_id === fokus.runId);
19703
- (0, import_react57.useEffect)(() => {
19781
+ (0, import_react58.useEffect)(() => {
19704
19782
  if (!imFokus || fokusSichtbar) return;
19705
19783
  if (phase.status !== "ready" || liste.laedtMehr || !liste.hasMore) return;
19706
19784
  liste.mehrLaden();
19707
19785
  }, [imFokus, fokusSichtbar, phase.status, liste]);
19708
- const angescrollt = (0, import_react57.useRef)(null);
19709
- (0, import_react57.useEffect)(() => {
19786
+ const angescrollt = (0, import_react58.useRef)(null);
19787
+ (0, import_react58.useEffect)(() => {
19710
19788
  if (!imFokus || !fokusSichtbar) return;
19711
19789
  const runId = fokus.runId;
19712
19790
  if (angescrollt.current === runId) return;
@@ -19716,7 +19794,7 @@ var PromptsArea = ({
19716
19794
  knoten.scrollIntoView({ block: "center" });
19717
19795
  }
19718
19796
  }, [imFokus, fokusSichtbar, fokus]);
19719
- const seitenTitel = (0, import_react57.useMemo)(() => {
19797
+ const seitenTitel = (0, import_react58.useMemo)(() => {
19720
19798
  const alle = {};
19721
19799
  for (const s of liste.seiten) Object.assign(alle, s.titles ?? {});
19722
19800
  return alle;
@@ -19917,21 +19995,21 @@ var PromptEintrag = ({
19917
19995
  };
19918
19996
 
19919
19997
  // src/features/szenen/areas/MediaArea.tsx
19920
- var import_react58 = require("react");
19998
+ var import_react59 = require("react");
19921
19999
  var import_jsx_runtime51 = require("react/jsx-runtime");
19922
20000
  var leer = async () => ({ kind: "ok", value: [] });
19923
20001
  var MediaArea = ({ port }) => {
19924
20002
  const media = port.media;
19925
- const [versionInput, setVersionInput] = (0, import_react58.useState)("");
19926
- const [versionId, setVersionId] = (0, import_react58.useState)("");
19927
- const [itemInput, setItemInput] = (0, import_react58.useState)("");
19928
- const [itemId, setItemId] = (0, import_react58.useState)("");
19929
- const [assetId, setAssetId] = (0, import_react58.useState)("");
19930
- const [role, setRole] = (0, import_react58.useState)("reference");
19931
- const [position, setPosition] = (0, import_react58.useState)("0");
19932
- const [notiz, setNotiz] = (0, import_react58.useState)(null);
19933
- const [busy, setBusy] = (0, import_react58.useState)(false);
19934
- const [refresh, setRefresh] = (0, import_react58.useState)(0);
20003
+ const [versionInput, setVersionInput] = (0, import_react59.useState)("");
20004
+ const [versionId, setVersionId] = (0, import_react59.useState)("");
20005
+ const [itemInput, setItemInput] = (0, import_react59.useState)("");
20006
+ const [itemId, setItemId] = (0, import_react59.useState)("");
20007
+ const [assetId, setAssetId] = (0, import_react59.useState)("");
20008
+ const [role, setRole] = (0, import_react59.useState)("reference");
20009
+ const [position, setPosition] = (0, import_react59.useState)("0");
20010
+ const [notiz, setNotiz] = (0, import_react59.useState)(null);
20011
+ const [busy, setBusy] = (0, import_react59.useState)(false);
20012
+ const [refresh, setRefresh] = (0, import_react59.useState)(0);
19935
20013
  const [versionPhase] = usePortQuery(
19936
20014
  () => versionId ? media.listVersionMedia(versionId) : leer(),
19937
20015
  [media, versionId, refresh]
@@ -20057,7 +20135,7 @@ var MediaArea = ({ port }) => {
20057
20135
  };
20058
20136
 
20059
20137
  // src/features/szenen/areas/ImageJobsArea.tsx
20060
- var import_react59 = require("react");
20138
+ var import_react60 = require("react");
20061
20139
 
20062
20140
  // src/lib/szenen/imageJobQueueRunner.ts
20063
20141
  var PAUSE_SEKUNDEN_OPTIONEN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
@@ -20169,13 +20247,13 @@ function istAbgeschlossen(job) {
20169
20247
  return job.status !== "pending" && job.status !== "claimed";
20170
20248
  }
20171
20249
  function useNachladbareAbfrage(frage, abhaengigkeiten) {
20172
- const [stand, setStand] = (0, import_react59.useState)(
20250
+ const [stand, setStand] = (0, import_react60.useState)(
20173
20251
  () => ({ wert: null, fehler: null, laedt: true })
20174
20252
  );
20175
- const sequenz = (0, import_react59.useRef)(0);
20176
- const frageRef = (0, import_react59.useRef)(frage);
20253
+ const sequenz = (0, import_react60.useRef)(0);
20254
+ const frageRef = (0, import_react60.useRef)(frage);
20177
20255
  frageRef.current = frage;
20178
- (0, import_react59.useEffect)(() => {
20256
+ (0, import_react60.useEffect)(() => {
20179
20257
  const meine = sequenz.current += 1;
20180
20258
  let abgemeldet = false;
20181
20259
  setStand((v) => ({ ...v, laedt: true }));
@@ -20205,15 +20283,20 @@ var ImageJobsArea = ({
20205
20283
  }) => {
20206
20284
  const auftraege = port.imageJobs;
20207
20285
  const uhr = jetzt ?? (() => /* @__PURE__ */ new Date());
20208
- const [filter, setFilter] = (0, import_react59.useState)("pending");
20209
- const [pauseSekunden, setPauseSekunden] = (0, import_react59.useState)(PAUSE_SEKUNDEN_VORGABE);
20210
- const [takt, setTakt] = (0, import_react59.useState)(0);
20211
- const neuLesen = (0, import_react59.useCallback)(() => setTakt((t) => t + 1), []);
20212
- const [lauf, setLauf] = (0, import_react59.useState)(null);
20213
- const laeuftRef = (0, import_react59.useRef)(false);
20214
- const abbruchRef = (0, import_react59.useRef)(false);
20215
- const [einzeln, setEinzeln] = (0, import_react59.useState)(null);
20216
- const [notiz, setNotiz] = (0, import_react59.useState)(null);
20286
+ const [filter, setFilter] = (0, import_react60.useState)("pending");
20287
+ const [pauseSekunden, setPauseSekunden] = (0, import_react60.useState)(PAUSE_SEKUNDEN_VORGABE);
20288
+ const [takt, setTakt] = (0, import_react60.useState)(0);
20289
+ const neuLesen = (0, import_react60.useCallback)(() => setTakt((t) => t + 1), []);
20290
+ const [lauf, setLauf] = (0, import_react60.useState)(null);
20291
+ const laeuftRef = (0, import_react60.useRef)(false);
20292
+ const abbruchRef = (0, import_react60.useRef)(false);
20293
+ const [einzeln, setEinzeln] = (0, import_react60.useState)(null);
20294
+ const [notiz, setNotiz] = (0, import_react60.useState)(null);
20295
+ const [modellLauf, setModellLauf] = (0, import_react60.useState)(null);
20296
+ const wahl = useNachladbareAbfrage(
20297
+ () => port.imageGeneration && modellLauf ? port.imageGeneration.imageModelChoiceForRun(modellLauf) : Promise.resolve({ kind: "ok", value: null }),
20298
+ [port, modellLauf]
20299
+ );
20217
20300
  const liste = useNachladbareAbfrage(
20218
20301
  () => auftraege?.listAllJobs ? auftraege.listAllJobs({ status: filter }) : Promise.resolve({
20219
20302
  kind: "ok",
@@ -20225,15 +20308,20 @@ var ImageJobsArea = ({
20225
20308
  () => auftraege?.latestImage ? auftraege.latestImage() : Promise.resolve({ kind: "ok", value: null }),
20226
20309
  [auftraege, takt]
20227
20310
  );
20228
- const [zuletzt, setZuletzt] = (0, import_react59.useState)(null);
20229
- (0, import_react59.useEffect)(() => {
20311
+ const [zuletzt, setZuletzt] = (0, import_react60.useState)(null);
20312
+ (0, import_react60.useEffect)(() => {
20230
20313
  if (!liste.laedt && liste.wert !== null) setZuletzt(uhr().toISOString());
20231
20314
  }, [liste.laedt, liste.wert]);
20232
20315
  const seite = liste.wert;
20233
20316
  const eintraege = seite?.eintraege ?? [];
20234
20317
  const offen = seite?.offen ?? 0;
20318
+ const ersterLauf = eintraege[0]?.job.prompt_run_id ?? null;
20319
+ (0, import_react60.useEffect)(() => {
20320
+ if (modellLauf === null && ersterLauf !== null) setModellLauf(ersterLauf);
20321
+ }, [modellLauf, ersterLauf]);
20235
20322
  const queueLaeuft = lauf !== null && (lauf.zustand === "laeuft" || lauf.zustand === "pause");
20236
- async function einzelStart(jobId) {
20323
+ const eigeneLaufend = (einzeln !== null ? 1 : 0) + (queueLaeuft && lauf?.aktuell && lauf.aktuell !== einzeln ? 1 : 0);
20324
+ async function einzelStart(jobId, vorspann = "") {
20237
20325
  if (!auftraege || laeuftRef.current || einzeln !== null) return;
20238
20326
  setEinzeln(jobId);
20239
20327
  setNotiz(null);
@@ -20242,7 +20330,7 @@ var ImageJobsArea = ({
20242
20330
  if (ergebnis.kind === "ok") {
20243
20331
  setNotiz({
20244
20332
  tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
20245
- text: `Auftrag ${jobId}: ${STATUS_TEXT2[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
20333
+ text: `${vorspann}Auftrag ${jobId}: ${STATUS_TEXT2[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
20246
20334
  });
20247
20335
  } else if (ergebnis.kind === "lost" || ergebnis.kind === "rejected") {
20248
20336
  setNotiz({ tone: "rejected", text: ergebnis.message });
@@ -20259,6 +20347,46 @@ var ImageJobsArea = ({
20259
20347
  neuLesen();
20260
20348
  }
20261
20349
  }
20350
+ async function erzeugenUndStarten(quelle, wieViele, modell) {
20351
+ const anfordern = auftraege?.requestMoreImages;
20352
+ if (!anfordern || laeuftRef.current || einzeln !== null) return;
20353
+ setNotiz(null);
20354
+ let neuer;
20355
+ try {
20356
+ const ergebnis = await anfordern({
20357
+ sourceJobId: quelle.job_id,
20358
+ imageCount: wieViele,
20359
+ // Nur mitgeben, wenn wirklich gewechselt wurde — sonst gilt der
20360
+ // Ausgangsauftrag, und das steht so im Port.
20361
+ ...modell && (modell.target_model_key !== quelle.execution_target_model_key || modell.profile_id !== quelle.execution_profile_id) ? {
20362
+ executionTargetModelKey: modell.target_model_key,
20363
+ executionProfileId: modell.profile_id
20364
+ // Ein Formatwechsel ist nicht gefragt; traegt das neue
20365
+ // Modell das alte Format nicht, lehnt der Port ab und
20366
+ // sagt warum, statt still zu ersetzen.
20367
+ } : {}
20368
+ });
20369
+ if (ergebnis.kind !== "ok") {
20370
+ setNotiz({
20371
+ tone: ergebnis.kind === "rejected" ? "rejected" : "error",
20372
+ text: ergebnis.message
20373
+ });
20374
+ return;
20375
+ }
20376
+ neuer = ergebnis.job;
20377
+ } catch (e) {
20378
+ setNotiz({
20379
+ tone: "error",
20380
+ text: `Der neue Bildauftrag zu ${quelle.job_id} konnte nicht angelegt werden: ${e instanceof Error ? e.message : String(e)}`
20381
+ });
20382
+ return;
20383
+ }
20384
+ neuLesen();
20385
+ await einzelStart(
20386
+ neuer.job_id,
20387
+ `Neuer Auftrag \xFCber ${neuer.image_count} ${neuer.image_count === 1 ? "Bild" : "Bilder"} mit ${neuer.execution_target_model_key}; `
20388
+ );
20389
+ }
20262
20390
  async function alleOffenenStarten() {
20263
20391
  if (!auftraege?.listAllJobs || laeuftRef.current) return;
20264
20392
  laeuftRef.current = true;
@@ -20311,7 +20439,26 @@ var ImageJobsArea = ({
20311
20439
  children: "\u27F3 Aktualisieren"
20312
20440
  }
20313
20441
  ),
20314
- /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" })
20442
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" }),
20443
+ eigeneLaufend > 0 && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20444
+ "span",
20445
+ {
20446
+ className: "szenen-bildauftraege__laufzaehler",
20447
+ "data-testid": "bildauftraege-laufzaehler",
20448
+ title: `${eigeneLaufend} Auftrag${eigeneLaufend === 1 ? "" : "e"} dieses Tabs l\xE4uft gerade`,
20449
+ children: [
20450
+ "\u27F3 ",
20451
+ eigeneLaufend,
20452
+ " l\xE4uft",
20453
+ eigeneLaufend === 1 ? "" : "en"
20454
+ ]
20455
+ }
20456
+ )
20457
+ ] }),
20458
+ (wahl.wert?.invalid_profiles.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", "data-testid": "bildauftraege-profilbefunde", children: [
20459
+ "Nicht angeboten, weil das Profil unvollst\xE4ndig ist:",
20460
+ " ",
20461
+ wahl.wert.invalid_profiles.join(" \xB7 ")
20315
20462
  ] }),
20316
20463
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20317
20464
  Bildkontrolle,
@@ -20404,10 +20551,9 @@ var ImageJobsArea = ({
20404
20551
  gesperrt: queueLaeuft || einzeln !== null,
20405
20552
  laufend: einzeln === eintrag.job.job_id || lauf?.aktuell === eintrag.job.job_id,
20406
20553
  onStart: () => void einzelStart(eintrag.job.job_id),
20407
- onAngelegt: (text) => {
20408
- setNotiz({ tone: "ok", text });
20409
- neuLesen();
20410
- },
20554
+ onErzeugen: (anzahl, modell) => erzeugenUndStarten(eintrag.job, anzahl, modell),
20555
+ modelle: wahl.wert?.models ?? null,
20556
+ modelleLaden: wahl.wert === null && wahl.fehler === null,
20411
20557
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
20412
20558
  },
20413
20559
  eintrag.job.job_id
@@ -20459,12 +20605,13 @@ var AuftragsZeile = ({
20459
20605
  gesperrt,
20460
20606
  laufend,
20461
20607
  onStart,
20462
- onAngelegt,
20463
- onPromptOeffnen
20608
+ onErzeugen,
20609
+ onPromptOeffnen,
20610
+ modelle,
20611
+ modelleLaden
20464
20612
  }) => {
20465
20613
  const { job, herkunft } = eintrag;
20466
- const [ergebnisOffen, setErgebnisOffen] = (0, import_react59.useState)(false);
20467
- const [weitereOffen, setWeitereOffen] = (0, import_react59.useState)(false);
20614
+ const [ergebnisOffen, setErgebnisOffen] = (0, import_react60.useState)(false);
20468
20615
  const hatErgebnis = Boolean(job.generation_batch_id) && (job.status === "succeeded" || job.status === "partial");
20469
20616
  const kannWeitere = Boolean(port.imageJobs?.requestMoreImages) && istAbgeschlossen(job);
20470
20617
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
@@ -20476,6 +20623,12 @@ var AuftragsZeile = ({
20476
20623
  children: [
20477
20624
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__kopf", children: [
20478
20625
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("strong", { "data-testid": `bildauftrag-status-${job.job_id}`, children: STATUS_TEXT2[job.status] }),
20626
+ " \xB7 ",
20627
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("strong", { "data-testid": `bildauftrag-anzahl-${job.job_id}`, children: [
20628
+ job.image_count,
20629
+ " ",
20630
+ job.image_count === 1 ? "Bild" : "Bilder"
20631
+ ] }),
20479
20632
  " \u2014 ",
20480
20633
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { children: herkunft.szene_titel ?? (herkunft.lauf_gefunden ? "Lauf ohne benennbares Element" : "Lauf nicht in diesem Bestand") })
20481
20634
  ] }),
@@ -20492,11 +20645,6 @@ var AuftragsZeile = ({
20492
20645
  "Profil ",
20493
20646
  /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: job.execution_profile_id })
20494
20647
  ] }),
20495
- /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20496
- job.image_count,
20497
- " ",
20498
- job.image_count === 1 ? "Bild" : "Bilder"
20499
- ] }),
20500
20648
  /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
20501
20649
  "Format ",
20502
20650
  job.aspect_ratio ?? "\u2014"
@@ -20531,7 +20679,7 @@ var AuftragsZeile = ({
20531
20679
  "data-testid": `bildauftrag-start-${job.job_id}`,
20532
20680
  disabled: gesperrt,
20533
20681
  onClick: onStart,
20534
- children: laufend ? "L\xE4uft \u2026" : "Start (kostet einen Bildaufruf)"
20682
+ children: laufend ? "L\xE4uft \u2026" : "Start"
20535
20683
  }
20536
20684
  ),
20537
20685
  onPromptOeffnen && herkunft.lauf_gefunden && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
@@ -20551,18 +20699,18 @@ var AuftragsZeile = ({
20551
20699
  onClick: () => setErgebnisOffen((o) => !o),
20552
20700
  children: ergebnisOffen ? "Ergebnisse ausblenden" : "Ergebnisse ansehen"
20553
20701
  }
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
- }
20563
20702
  )
20564
20703
  ] }),
20565
- kannWeitere && weitereOffen && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(WeitereBilder, { port, job, onAngelegt }),
20704
+ kannWeitere && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20705
+ WeitereBilder,
20706
+ {
20707
+ job,
20708
+ gesperrt,
20709
+ modelle,
20710
+ modelleLaden,
20711
+ onErzeugen
20712
+ }
20713
+ ),
20566
20714
  ergebnisOffen && job.generation_batch_id && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20567
20715
  AuftragsErgebnis,
20568
20716
  {
@@ -20575,123 +20723,106 @@ var AuftragsZeile = ({
20575
20723
  }
20576
20724
  );
20577
20725
  };
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;
20726
+ var WeitereBilder = ({ job, gesperrt, modelle, modelleLaden, onErzeugen }) => {
20727
+ const [anzahl, setAnzahl] = (0, import_react60.useState)(1);
20728
+ const [wahlId, setWahlId] = (0, import_react60.useState)(null);
20729
+ const [laeuft, setLaeuft] = (0, import_react60.useState)(false);
20730
+ const [notiz, setNotiz] = (0, import_react60.useState)(null);
20731
+ const uebernommen = modelle ? modelle.find((m) => m.target_model_key === job.execution_target_model_key && m.profile_id === job.execution_profile_id) ?? null : null;
20732
+ const aktiv = wahlId && modelle?.find((m) => m.selection_id === wahlId) || uebernommen;
20733
+ const maxBilder = aktiv?.max_images ?? 1;
20734
+ const starten = async (wieViele) => {
20735
+ if (laeuft || gesperrt || !aktiv) return;
20595
20736
  setLaeuft(true);
20596
20737
  setNotiz(null);
20597
20738
  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
- }
20739
+ await onErzeugen(wieViele, aktiv);
20612
20740
  } catch (e) {
20613
20741
  setNotiz({
20614
20742
  tone: "error",
20615
- text: `Der neue Bildauftrag zu ${job.job_id} konnte nicht angelegt werden: ` + (e instanceof Error ? e.message : String(e))
20743
+ text: `Weitere Bilder zu ${job.job_id} sind gescheitert: ` + (e instanceof Error ? e.message : String(e))
20616
20744
  });
20617
20745
  } finally {
20618
20746
  setLaeuft(false);
20619
20747
  }
20620
20748
  };
20749
+ if (modelleLaden && modelle === null) {
20750
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20751
+ "p",
20752
+ {
20753
+ className: "szenen-bildauftraege__weitere",
20754
+ "data-testid": `bildauftrag-weitere-laden-${job.job_id}`,
20755
+ children: "Bildmodelle werden gepr\xFCft \u2026"
20756
+ }
20757
+ );
20758
+ }
20759
+ if (modelle !== null && modelle.length === 0) {
20760
+ return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20761
+ "p",
20762
+ {
20763
+ className: "szenen-bildauftraege__weitere",
20764
+ role: "alert",
20765
+ "data-testid": `bildauftrag-weitere-keine-${job.job_id}`,
20766
+ children: "Hier ist derzeit kein Bildmodell ausf\xFChrbar \u2014 weitere Bilder sind nicht m\xF6glich. Es wird kein Ersatz geraten."
20767
+ }
20768
+ );
20769
+ }
20621
20770
  return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20622
- "section",
20771
+ "p",
20623
20772
  {
20624
20773
  className: "szenen-bildauftraege__weitere",
20625
20774
  "data-testid": `bildauftrag-weitere-panel-${job.job_id}`,
20626
- "aria-label": "Weitere Bilder",
20627
20775
  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
- ] }),
20776
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { htmlFor: `bildauftrag-modell-${job.job_id}`, children: "Weitere Bilder mit" }),
20777
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
20778
+ "select",
20779
+ {
20780
+ id: `bildauftrag-modell-${job.job_id}`,
20781
+ "data-testid": `bildauftrag-modell-${job.job_id}`,
20782
+ value: aktiv?.selection_id ?? "",
20783
+ disabled: laeuft || gesperrt,
20784
+ onChange: (e) => {
20785
+ setWahlId(e.target.value);
20786
+ setNotiz(null);
20787
+ },
20788
+ children: [
20789
+ !aktiv && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("option", { value: "", children: [
20790
+ job.execution_profile_id,
20791
+ " von ",
20792
+ job.execution_target_model_key,
20793
+ " \u2014 nicht mehr ausf\xFChrbar, bitte w\xE4hlen"
20794
+ ] }),
20795
+ (modelle ?? []).map((m) => /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("option", { value: m.selection_id, children: [
20796
+ m.display_name,
20797
+ m.target_model_key === job.execution_target_model_key && m.profile_id === job.execution_profile_id ? " (wie gehabt)" : ""
20798
+ ] }, m.selection_id))
20799
+ ]
20800
+ }
20801
+ ),
20802
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { htmlFor: `bildauftrag-weitere-anzahl-${job.job_id}`, children: "\xD7" }),
20803
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20804
+ "select",
20805
+ {
20806
+ id: `bildauftrag-weitere-anzahl-${job.job_id}`,
20807
+ "data-testid": `bildauftrag-weitere-anzahl-${job.job_id}`,
20808
+ value: Math.min(anzahl, maxBilder),
20809
+ disabled: laeuft || gesperrt || !aktiv,
20810
+ onChange: (e) => setAnzahl(Number(e.target.value)),
20811
+ children: Array.from({ length: Math.max(1, maxBilder) }, (_, i) => i + 1).map((n) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("option", { value: n, children: n }, n))
20812
+ }
20813
+ ),
20814
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20815
+ "button",
20816
+ {
20817
+ type: "button",
20818
+ "data-testid": `bildauftrag-weitere-start-${job.job_id}`,
20819
+ disabled: laeuft || gesperrt || !aktiv,
20820
+ onClick: () => void starten(Math.min(anzahl, maxBilder)),
20821
+ children: laeuft ? "L\xE4uft \u2026" : "Erzeugen"
20822
+ }
20823
+ ),
20693
20824
  notiz && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
20694
- "p",
20825
+ "span",
20695
20826
  {
20696
20827
  role: notiz.tone === "error" ? "alert" : void 0,
20697
20828
  "data-testid": `bildauftrag-weitere-notiz-${job.job_id}`,
@@ -20745,9 +20876,9 @@ var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
20745
20876
  var Bildkontrolle = ({ port, stand, fehler: fehler5, laedt, onPromptOeffnen }) => {
20746
20877
  const folge = stand?.folge ?? [];
20747
20878
  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)(() => {
20879
+ const [gewaehlt, setGewaehlt] = (0, import_react60.useState)(null);
20880
+ const neuestesVorher = (0, import_react60.useRef)(null);
20881
+ (0, import_react60.useEffect)(() => {
20751
20882
  if (neuestes !== neuestesVorher.current) {
20752
20883
  neuestesVorher.current = neuestes;
20753
20884
  setGewaehlt(neuestes);
@@ -20874,13 +21005,13 @@ var SzenenView = ({
20874
21005
  fokus = null,
20875
21006
  onFokusVerlassen
20876
21007
  }) => {
20877
- const bereiche = (0, import_react60.useMemo)(() => areasFor(port), [port]);
20878
- const faehig = (0, import_react60.useMemo)(() => capabilitiesOf(port), [port]);
20879
- const [aktiv, setAktiv] = (0, import_react60.useState)(
21008
+ const bereiche = (0, import_react61.useMemo)(() => areasFor(port), [port]);
21009
+ const faehig = (0, import_react61.useMemo)(() => capabilitiesOf(port), [port]);
21010
+ const [aktiv, setAktiv] = (0, import_react61.useState)(
20880
21011
  () => resolveArea(port, fokus ? areaForFokus(fokus) : initialArea)
20881
21012
  );
20882
- const [interneRunWahl, setInterneRunWahl] = (0, import_react60.useState)(null);
20883
- (0, import_react60.useEffect)(() => {
21013
+ const [interneRunWahl, setInterneRunWahl] = (0, import_react61.useState)(null);
21014
+ (0, import_react61.useEffect)(() => {
20884
21015
  if (!fokus) return;
20885
21016
  setAktiv(resolveArea(port, areaForFokus(fokus)));
20886
21017
  }, [fokus, port]);
@@ -25678,21 +25809,26 @@ function createImageJobPort(options) {
25678
25809
  retryable: wahl.kind === "error" ? wahl.retryable : true
25679
25810
  };
25680
25811
  }
25681
- const gewaehlt = findExecutableImageModel(
25682
- wahl.value,
25683
- quelle.execution_target_model_key,
25684
- quelle.execution_profile_id
25685
- );
25812
+ const zielModell = input.executionTargetModelKey ?? quelle.execution_target_model_key;
25813
+ const zielProfil = input.executionProfileId ?? quelle.execution_profile_id;
25814
+ if (input.executionTargetModelKey === void 0 !== (input.executionProfileId === void 0)) {
25815
+ return {
25816
+ kind: "rejected",
25817
+ message: `Zielmodell und Ausfuehrungsprofil gehoeren zusammen: Es wurde nur eines von beiden angegeben (Modell "${input.executionTargetModelKey ?? "\u2014"}", Profil "${input.executionProfileId ?? "\u2014"}"). Ein Zielmodell kann mehrere Profile tragen; welches gemeint ist, wird nicht geraten.`
25818
+ };
25819
+ }
25820
+ const gewaehlt = findExecutableImageModel(wahl.value, zielModell, zielProfil);
25686
25821
  if (!gewaehlt) {
25687
25822
  const auswahl = wahl.value.models.map((m) => m.selection_id);
25688
25823
  return {
25689
25824
  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."}`)
25825
+ message: `Das Profil "${zielProfil}" von "${zielModell}" 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
25826
  };
25692
25827
  }
25828
+ const format = input.aspectRatio === void 0 ? quelle.aspect_ratio ?? null : input.aspectRatio;
25693
25829
  const befunde = checkImageModelParameters(gewaehlt, {
25694
25830
  imageCount,
25695
- ...quelle.aspect_ratio ? { aspectRatio: quelle.aspect_ratio } : {}
25831
+ ...format ? { aspectRatio: format } : {}
25696
25832
  });
25697
25833
  if (befunde.length > 0) {
25698
25834
  return {
@@ -25723,7 +25859,7 @@ function createImageJobPort(options) {
25723
25859
  execution_target_model_key: gewaehlt.target_model_key,
25724
25860
  execution_profile_id: gewaehlt.profile_id,
25725
25861
  image_count: imageCount,
25726
- ...quelle.aspect_ratio ? { aspect_ratio: quelle.aspect_ratio } : {},
25862
+ ...format ? { aspect_ratio: format } : {},
25727
25863
  // **Nicht geraten und nicht erfunden.** Dieser Host kennt keinen
25728
25864
  // Nutzerschluessel — der Flow-Client hat keine Identitaet, nur einen
25729
25865
  // Bestand. Der Fortsetzungsauftrag traegt deshalb den Auftraggeber des
@@ -26063,7 +26199,7 @@ function resolveBootstrapTarget(input) {
26063
26199
  // src/components/AvatarArchitectApp.tsx
26064
26200
  var import_jsx_runtime54 = require("react/jsx-runtime");
26065
26201
  function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, requireWorkspaceSelection = false, serverBaseUrl }) {
26066
- (0, import_react61.useEffect)(() => {
26202
+ (0, import_react62.useEffect)(() => {
26067
26203
  const id = "flow-styles";
26068
26204
  if (!document.getElementById(id)) {
26069
26205
  const style = document.createElement("style");
@@ -26072,33 +26208,33 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26072
26208
  document.head.appendChild(style);
26073
26209
  }
26074
26210
  }, []);
26075
- const [showStart, setShowStart] = (0, import_react61.useState)(true);
26076
- const [layoutChoice, setLayoutChoice] = (0, import_react61.useState)(() => {
26211
+ const [showStart, setShowStart] = (0, import_react62.useState)(true);
26212
+ const [layoutChoice, setLayoutChoice] = (0, import_react62.useState)(() => {
26077
26213
  try {
26078
26214
  return localStorage.getItem("aa-layout") || null;
26079
26215
  } catch {
26080
26216
  return null;
26081
26217
  }
26082
26218
  });
26083
- const [hfToken, setHfToken] = (0, import_react61.useState)(initialHfToken || "");
26084
- const [hfTokenInput, setHfTokenInput] = (0, import_react61.useState)(initialHfToken || "");
26085
- const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react61.useState)(() => {
26219
+ const [hfToken, setHfToken] = (0, import_react62.useState)(initialHfToken || "");
26220
+ const [hfTokenInput, setHfTokenInput] = (0, import_react62.useState)(initialHfToken || "");
26221
+ const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react62.useState)(() => {
26086
26222
  try {
26087
26223
  return normalizeSelectableNamespace(localStorage.getItem("aa-hf-namespace"));
26088
26224
  } catch {
26089
26225
  return DEFAULT_NAMESPACE;
26090
26226
  }
26091
26227
  });
26092
- const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react61.useState)(null);
26093
- const [serverProbeFailed, setServerProbeFailed] = (0, import_react61.useState)(false);
26094
- (0, import_react61.useEffect)(() => {
26228
+ const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react62.useState)(null);
26229
+ const [serverProbeFailed, setServerProbeFailed] = (0, import_react62.useState)(false);
26230
+ (0, import_react62.useEffect)(() => {
26095
26231
  if (hfNamespace !== void 0 || !serverBaseUrl) return;
26096
26232
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
26097
26233
  if (typeof d.hfNamespace === "string" && d.hfNamespace) setHfNamespaceFromServer(d.hfNamespace);
26098
26234
  else setServerProbeFailed(true);
26099
26235
  }).catch(() => setServerProbeFailed(true));
26100
26236
  }, [hfNamespace, serverBaseUrl]);
26101
- const [targetConfirmed, setTargetConfirmed] = (0, import_react61.useState)(false);
26237
+ const [targetConfirmed, setTargetConfirmed] = (0, import_react62.useState)(false);
26102
26238
  const isDialogTarget = hfNamespace === void 0 && hfNamespaceFromServer === null && (!serverBaseUrl || serverProbeFailed);
26103
26239
  const isProbingServer = hfNamespace === void 0 && !!serverBaseUrl && hfNamespaceFromServer === null && !serverProbeFailed;
26104
26240
  const bootstrapTarget = resolveBootstrapTarget({
@@ -26109,8 +26245,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26109
26245
  isStartDialogOpen: isDialogTarget && !targetConfirmed
26110
26246
  });
26111
26247
  const effectiveNamespace = bootstrapTarget.namespace ?? "";
26112
- const kontextRef = (0, import_react61.useRef)(null);
26113
- const szenenPort = (0, import_react61.useMemo)(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
26248
+ const kontextRef = (0, import_react62.useRef)(null);
26249
+ const szenenPort = (0, import_react62.useMemo)(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
26114
26250
  generateText: onGeneratePrompt,
26115
26251
  token: hfToken,
26116
26252
  namespace: effectiveNamespace,
@@ -26132,10 +26268,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26132
26268
  const tagBaseResult = useTagBase(hfToken, effectiveNamespace);
26133
26269
  const szenenKontextId = tagBaseResult.selectedContextId === null ? null : String(tagBaseResult.selectedContextId);
26134
26270
  kontextRef.current = szenenKontextId;
26135
- const [imageUploadStatus, setImageUploadStatus] = (0, import_react61.useState)(/* @__PURE__ */ new Map());
26136
- const [bootstrapLog, setBootstrapLog] = (0, import_react61.useState)([]);
26137
- const [isBootstrapping, setIsBootstrapping] = (0, import_react61.useState)(false);
26138
- const [hfMissingImages, setHfMissingImages] = (0, import_react61.useState)([]);
26271
+ const [imageUploadStatus, setImageUploadStatus] = (0, import_react62.useState)(/* @__PURE__ */ new Map());
26272
+ const [bootstrapLog, setBootstrapLog] = (0, import_react62.useState)([]);
26273
+ const [isBootstrapping, setIsBootstrapping] = (0, import_react62.useState)(false);
26274
+ const [hfMissingImages, setHfMissingImages] = (0, import_react62.useState)([]);
26139
26275
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
26140
26276
  localOnlyCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { style: { background: "rgba(234,179,8,0.15)", border: "1px solid rgba(234,179,8,0.3)", padding: "4px 10px", fontSize: 11, color: "#fbbf24", borderRadius: 4, marginBottom: 4 }, children: [
26141
26277
  "\u26A0 ",
@@ -26194,37 +26330,39 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26194
26330
  setTargetConfirmed(true);
26195
26331
  setShowStart(false);
26196
26332
  };
26197
- const [nodes, setNodes] = (0, import_react61.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
26198
- const [edges, setEdges] = (0, import_react61.useState)([]);
26199
- const [history, setHistory] = (0, import_react61.useState)([]);
26200
- const [galleryItems, setGalleryItems] = (0, import_react61.useState)([]);
26201
- const galleryItemsRef = (0, import_react61.useRef)([]);
26202
- (0, import_react61.useEffect)(() => {
26333
+ const [nodes, setNodes] = (0, import_react62.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
26334
+ const [edges, setEdges] = (0, import_react62.useState)([]);
26335
+ const [history, setHistory] = (0, import_react62.useState)([]);
26336
+ const [galleryItems, setGalleryItems] = (0, import_react62.useState)([]);
26337
+ const galleryItemsRef = (0, import_react62.useRef)([]);
26338
+ (0, import_react62.useEffect)(() => {
26203
26339
  galleryItemsRef.current = galleryItems;
26204
26340
  }, [galleryItems]);
26205
- const hfImageNotFoundRef = (0, import_react61.useRef)(/* @__PURE__ */ new Map());
26206
- const [galleryVisibleCount, setGalleryVisibleCount] = (0, import_react61.useState)(20);
26207
- const [historyVisibleCount, setHistoryVisibleCount] = (0, import_react61.useState)(20);
26208
- const loadThumbnailsForEntries = (0, import_react61.useCallback)(async (entries) => {
26209
- for (const entry of entries) {
26210
- if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
26211
- if (hfImageNotFoundRef.current.has(entry.id)) continue;
26212
- try {
26213
- const b64 = await hfLoadImageAsBase64(entry.id, hfToken, effectiveNamespace, entry.filename, void 0, entry.mimeType, entry.hasThumb);
26214
- if (!b64) {
26215
- hfImageNotFoundRef.current.set(entry.id, Date.now());
26216
- setHfMissingImages((prev) => prev.find((e) => e.id === entry.id) ? prev : [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType ?? "image/jpeg", timestamp: entry.timestamp ?? 0 }]);
26217
- continue;
26218
- }
26341
+ const hfImageNotFoundRef = (0, import_react62.useRef)(/* @__PURE__ */ new Map());
26342
+ const [galleryVisibleCount, setGalleryVisibleCount] = (0, import_react62.useState)(20);
26343
+ const [historyVisibleCount, setHistoryVisibleCount] = (0, import_react62.useState)(20);
26344
+ const hfInFlightRef = (0, import_react62.useRef)(/* @__PURE__ */ new Set());
26345
+ const loadThumbnailsForEntries = (0, import_react62.useCallback)(async (entries) => {
26346
+ await loadThumbnailsSequentially(entries, {
26347
+ inFlight: hfInFlightRef.current,
26348
+ isAlreadyLoaded: (entry) => !!galleryItemsRef.current.find((g) => g.id === entry.id)?.base64,
26349
+ isKnownMissing: (entry) => hfImageNotFoundRef.current.has(entry.id),
26350
+ loadImage: (entry) => hfLoadImageAsBase64(entry.id, hfToken, effectiveNamespace, entry.filename, void 0, entry.mimeType, entry.hasThumb),
26351
+ onLoaded: (entry, b64) => {
26219
26352
  const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
26220
26353
  setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
26221
26354
  setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
26222
- } catch {
26355
+ },
26356
+ onMissing: (entry) => {
26357
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
26358
+ setHfMissingImages((prev) => prev.find((e) => e.id === entry.id) ? prev : [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType ?? "image/jpeg", timestamp: entry.timestamp ?? 0 }]);
26359
+ },
26360
+ onError: (entry) => {
26223
26361
  hfImageNotFoundRef.current.set(entry.id, Date.now());
26224
26362
  }
26225
- }
26363
+ });
26226
26364
  }, [hfToken, effectiveNamespace]);
26227
- (0, import_react61.useEffect)(() => {
26365
+ (0, import_react62.useEffect)(() => {
26228
26366
  if (!hfState) return;
26229
26367
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
26230
26368
  const hfIds = new Set(hfState.metadata.map((m) => m.id));
@@ -26251,36 +26389,24 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26251
26389
  return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
26252
26390
  });
26253
26391
  }, [hfState]);
26254
- (0, import_react61.useEffect)(() => {
26255
- if (!hfState) return;
26256
- const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
26257
- const skeletons = sortedMeta.map((m) => ({ id: m.id, prompt: m.prompt, timestamp: m.timestamp, status: "done", nodeId: m.id, tags: m.tags || [] }));
26258
- const repIds = new Set(groupByPrompt(skeletons).slice(0, galleryVisibleCount).map((g) => g.representative.id));
26259
- loadThumbnailsForEntries(sortedMeta.filter((e) => repIds.has(e.id)));
26260
- }, [hfState, galleryVisibleCount, loadThumbnailsForEntries]);
26261
- (0, import_react61.useEffect)(() => {
26262
- if (!hfState) return;
26263
- const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
26264
- loadThumbnailsForEntries(sortedMeta.slice(0, historyVisibleCount));
26265
- }, [hfState, historyVisibleCount, loadThumbnailsForEntries]);
26266
- const [activePrompt, setActivePrompt] = (0, import_react61.useState)("");
26267
- const [isSynthesizing, setIsSynthesizing] = (0, import_react61.useState)(false);
26268
- const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react61.useState)(0);
26269
- const [currentResult, setCurrentResult] = (0, import_react61.useState)(null);
26270
- const [focusedNodeId, setFocusedNodeId] = (0, import_react61.useState)(null);
26271
- const [leftTab, setLeftTab] = (0, import_react61.useState)("prompt");
26272
- const [promptFeedback, setPromptFeedback] = (0, import_react61.useState)(null);
26273
- const [lastPromptPayload, setLastPromptPayload] = (0, import_react61.useState)(null);
26274
- const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react61.useState)(false);
26275
- const [activeTab, setActiveTab] = (0, import_react61.useState)("history");
26276
- const [mobileTab, setMobileTab] = (0, import_react61.useState)("stage");
26277
- const [middlePanel, setMiddlePanel] = (0, import_react61.useState)("stage");
26278
- const [recentLabItems, setRecentLabItems] = (0, import_react61.useState)([]);
26279
- const [aspectRatio, setAspectRatio] = (0, import_react61.useState)("1:1");
26280
- const [selectedModel, setSelectedModel] = (0, import_react61.useState)("\u{1F34C} Nano Banana Pro");
26281
- const [seed, setSeed] = (0, import_react61.useState)(Math.floor(Math.random() * 1e6));
26282
- const [seedMode, setSeedMode] = (0, import_react61.useState)("random");
26283
- const [imageCount, setImageCount] = (0, import_react61.useState)(() => {
26392
+ const [activePrompt, setActivePrompt] = (0, import_react62.useState)("");
26393
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react62.useState)(false);
26394
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react62.useState)(0);
26395
+ const [currentResult, setCurrentResult] = (0, import_react62.useState)(null);
26396
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react62.useState)(null);
26397
+ const [leftTab, setLeftTab] = (0, import_react62.useState)("prompt");
26398
+ const [promptFeedback, setPromptFeedback] = (0, import_react62.useState)(null);
26399
+ const [lastPromptPayload, setLastPromptPayload] = (0, import_react62.useState)(null);
26400
+ const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react62.useState)(false);
26401
+ const [activeTab, setActiveTab] = (0, import_react62.useState)("history");
26402
+ const [mobileTab, setMobileTab] = (0, import_react62.useState)("stage");
26403
+ const [middlePanel, setMiddlePanel] = (0, import_react62.useState)("stage");
26404
+ const [recentLabItems, setRecentLabItems] = (0, import_react62.useState)([]);
26405
+ const [aspectRatio, setAspectRatio] = (0, import_react62.useState)("1:1");
26406
+ const [selectedModel, setSelectedModel] = (0, import_react62.useState)("\u{1F34C} Nano Banana Pro");
26407
+ const [seed, setSeed] = (0, import_react62.useState)(Math.floor(Math.random() * 1e6));
26408
+ const [seedMode, setSeedMode] = (0, import_react62.useState)("random");
26409
+ const [imageCount, setImageCount] = (0, import_react62.useState)(() => {
26284
26410
  try {
26285
26411
  const v = parseInt(localStorage.getItem("aa-image-count") || "", 10);
26286
26412
  return v >= 1 && v <= 8 ? v : 4;
@@ -26302,41 +26428,57 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26302
26428
  { label: "4 Bilder", value: "4" },
26303
26429
  { label: "8 Bilder", value: "8" }
26304
26430
  ];
26305
- const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react61.useState)(false);
26306
- const [isRightCollapsed, setIsRightCollapsed] = (0, import_react61.useState)(false);
26307
- const [leftPanelWidth, setLeftPanelWidth] = (0, import_react61.useState)(() => {
26431
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react62.useState)(false);
26432
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react62.useState)(false);
26433
+ const [leftPanelWidth, setLeftPanelWidth] = (0, import_react62.useState)(() => {
26308
26434
  try {
26309
26435
  return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
26310
26436
  } catch {
26311
26437
  return 260;
26312
26438
  }
26313
26439
  });
26314
- const [rightPanelWidth, setRightPanelWidth] = (0, import_react61.useState)(() => {
26440
+ const [rightPanelWidth, setRightPanelWidth] = (0, import_react62.useState)(() => {
26315
26441
  try {
26316
26442
  return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
26317
26443
  } catch {
26318
26444
  return 320;
26319
26445
  }
26320
26446
  });
26321
- const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react61.useState)(false);
26322
- const [workspaceTags, setWorkspaceTags] = (0, import_react61.useState)(null);
26323
- const [highContrast, setHighContrast] = (0, import_react61.useState)(() => {
26447
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react62.useState)(false);
26448
+ const [workspaceTags, setWorkspaceTags] = (0, import_react62.useState)(null);
26449
+ const [highContrast, setHighContrast] = (0, import_react62.useState)(() => {
26324
26450
  try {
26325
26451
  return localStorage.getItem("aa-contrast") === "high";
26326
26452
  } catch {
26327
26453
  return false;
26328
26454
  }
26329
26455
  });
26330
- const [activeReferenceId, setActiveReferenceId] = (0, import_react61.useState)(null);
26331
- const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react61.useState)(null);
26332
- const [isScanningImage, setIsScanningImage] = (0, import_react61.useState)(false);
26333
- const [touchStartX, setTouchStartX] = (0, import_react61.useState)(null);
26334
- const [isFullscreen, setIsFullscreen] = (0, import_react61.useState)(false);
26335
- const [zoomScale, setZoomScale] = (0, import_react61.useState)(1);
26336
- const [zoomOffset, setZoomOffset] = (0, import_react61.useState)({ x: 0, y: 0 });
26337
- const lastPinchDist = (0, import_react61.useRef)(null);
26338
- const lastTapTime = (0, import_react61.useRef)(0);
26339
- const dragStart = (0, import_react61.useRef)(null);
26456
+ const [activeReferenceId, setActiveReferenceId] = (0, import_react62.useState)(null);
26457
+ const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react62.useState)(null);
26458
+ const [isScanningImage, setIsScanningImage] = (0, import_react62.useState)(false);
26459
+ const [touchStartX, setTouchStartX] = (0, import_react62.useState)(null);
26460
+ const [isFullscreen, setIsFullscreen] = (0, import_react62.useState)(false);
26461
+ const [zoomScale, setZoomScale] = (0, import_react62.useState)(1);
26462
+ const [zoomOffset, setZoomOffset] = (0, import_react62.useState)({ x: 0, y: 0 });
26463
+ const { historyVisible, galleryVisible } = computePrefetchVisibility({
26464
+ layoutChoice,
26465
+ appShellVisible: !showStart && !requireWorkspaceSelection && !(isFullscreen && !!currentResult?.base64),
26466
+ mobileTab,
26467
+ middlePanel,
26468
+ activeTab,
26469
+ isRightCollapsed
26470
+ });
26471
+ useThumbnailPrefetch({
26472
+ metadata: hfState?.metadata,
26473
+ historyVisible,
26474
+ galleryVisible,
26475
+ historyVisibleCount,
26476
+ galleryVisibleCount,
26477
+ loadThumbnailsForEntries
26478
+ });
26479
+ const lastPinchDist = (0, import_react62.useRef)(null);
26480
+ const lastTapTime = (0, import_react62.useRef)(0);
26481
+ const dragStart = (0, import_react62.useRef)(null);
26340
26482
  const openFullscreen = () => {
26341
26483
  setIsFullscreen(true);
26342
26484
  setZoomScale(1);
@@ -26399,7 +26541,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26399
26541
  setActiveReferenceId(null);
26400
26542
  setActiveReferenceThumbnail(null);
26401
26543
  };
26402
- const labServices = (0, import_react61.useMemo)(() => {
26544
+ const labServices = (0, import_react62.useMemo)(() => {
26403
26545
  const available = groupGenerationsToLabItems([...galleryItems, ...history]);
26404
26546
  return {
26405
26547
  availableItems: available,
@@ -26479,14 +26621,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26479
26621
  setIsScanningImage(false);
26480
26622
  }
26481
26623
  };
26482
- const currentIndex = (0, import_react61.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
26483
- const goToPrev = (0, import_react61.useCallback)(() => {
26624
+ const currentIndex = (0, import_react62.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
26625
+ const goToPrev = (0, import_react62.useCallback)(() => {
26484
26626
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
26485
26627
  }, [currentIndex, history]);
26486
- const goToNext = (0, import_react61.useCallback)(() => {
26628
+ const goToNext = (0, import_react62.useCallback)(() => {
26487
26629
  if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
26488
26630
  }, [currentIndex, history]);
26489
- const handleGallerySelect = (0, import_react61.useCallback)((g) => {
26631
+ const handleGallerySelect = (0, import_react62.useCallback)((g) => {
26490
26632
  setCurrentResult(g);
26491
26633
  setMobileTab("stage");
26492
26634
  if (g.hasThumb && g.filename && hfToken && !g.fullBase64) {
@@ -26499,7 +26641,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26499
26641
  });
26500
26642
  }
26501
26643
  }, [hfToken, effectiveNamespace]);
26502
- const handleTitleSet = (0, import_react61.useCallback)((id) => {
26644
+ const handleTitleSet = (0, import_react62.useCallback)((id) => {
26503
26645
  const ts = Date.now();
26504
26646
  setGalleryItems((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
26505
26647
  setHistory((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
@@ -26508,13 +26650,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26508
26650
  });
26509
26651
  }
26510
26652
  }, [hfToken, effectiveNamespace, hfWriteEvent]);
26511
- const currentGroup = (0, import_react61.useMemo)(() => {
26653
+ const currentGroup = (0, import_react62.useMemo)(() => {
26512
26654
  if (!currentResult?.prompt) return [];
26513
26655
  const groups = groupByPrompt(galleryItems.filter((g) => g.status === "done" && !!g.base64));
26514
26656
  const group = groups.find((gr) => gr.prompt === currentResult.prompt);
26515
26657
  return group ? group.items : [];
26516
26658
  }, [galleryItems, currentResult?.prompt]);
26517
- (0, import_react61.useEffect)(() => {
26659
+ (0, import_react62.useEffect)(() => {
26518
26660
  if (!currentResult?.prompt || !hfToken || !effectiveNamespace || !hfState) return;
26519
26661
  const siblings = galleryItemsRef.current.filter(
26520
26662
  (g) => g.prompt === currentResult.prompt && !g.base64 && !hfImageNotFoundRef.current.has(g.id)
@@ -26553,7 +26695,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26553
26695
  }
26554
26696
  ) : null;
26555
26697
  useKeyboardNavigation(history, currentResult, setCurrentResult);
26556
- const getSubtreeFormat = (0, import_react61.useCallback)((nodeId, depth = 0) => {
26698
+ const getSubtreeFormat = (0, import_react62.useCallback)((nodeId, depth = 0) => {
26557
26699
  const node = nodes.find((n) => n.id === nodeId);
26558
26700
  if (!node) return "";
26559
26701
  const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
@@ -26561,7 +26703,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
26561
26703
  return `${indent}- ${node.data.label || "(unbenannt)"}
26562
26704
  ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
26563
26705
  }, [nodes, edges]);
26564
- const activePath = (0, import_react61.useMemo)(() => {
26706
+ const activePath = (0, import_react62.useMemo)(() => {
26565
26707
  if (!focusedNodeId) return /* @__PURE__ */ new Set();
26566
26708
  const path = /* @__PURE__ */ new Set([focusedNodeId]);
26567
26709
  let currId = focusedNodeId;
@@ -27713,7 +27855,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
27713
27855
  }
27714
27856
 
27715
27857
  // src/components/FaApp.tsx
27716
- var import_react62 = require("react");
27858
+ var import_react63 = require("react");
27717
27859
  var import_jsx_runtime55 = require("react/jsx-runtime");
27718
27860
  function FaApp({
27719
27861
  onGenerateImage,
@@ -27729,15 +27871,15 @@ function FaApp({
27729
27871
  serverBaseUrl,
27730
27872
  buildInfo
27731
27873
  }) {
27732
- const [hfNamespace, setHfNamespace] = (0, import_react62.useState)(void 0);
27733
- (0, import_react62.useEffect)(() => {
27874
+ const [hfNamespace, setHfNamespace] = (0, import_react63.useState)(void 0);
27875
+ (0, import_react63.useEffect)(() => {
27734
27876
  if (!serverBaseUrl) return;
27735
27877
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
27736
27878
  if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
27737
27879
  }).catch(() => {
27738
27880
  });
27739
27881
  }, [serverBaseUrl]);
27740
- const wrappedPrompt = (0, import_react62.useMemo)(
27882
+ const wrappedPrompt = (0, import_react63.useMemo)(
27741
27883
  () => alsFlowTextZugang(onGeneratePrompt),
27742
27884
  [onGeneratePrompt]
27743
27885
  );
@@ -27991,15 +28133,15 @@ function createInMemoryWorkspacePersistence(now = () => Date.now()) {
27991
28133
  }
27992
28134
 
27993
28135
  // node_modules/zustand/esm/react.mjs
27994
- var import_react63 = __toESM(require("react"), 1);
28136
+ var import_react64 = __toESM(require("react"), 1);
27995
28137
  var identity = (arg) => arg;
27996
28138
  function useStore(api, selector = identity) {
27997
- const slice = import_react63.default.useSyncExternalStore(
28139
+ const slice = import_react64.default.useSyncExternalStore(
27998
28140
  api.subscribe,
27999
- import_react63.default.useCallback(() => selector(api.getState()), [api, selector]),
28000
- import_react63.default.useCallback(() => selector(api.getInitialState()), [api, selector])
28141
+ import_react64.default.useCallback(() => selector(api.getState()), [api, selector]),
28142
+ import_react64.default.useCallback(() => selector(api.getInitialState()), [api, selector])
28001
28143
  );
28002
- import_react63.default.useDebugValue(slice);
28144
+ import_react64.default.useDebugValue(slice);
28003
28145
  return slice;
28004
28146
  }
28005
28147