@rslsp1/fa-app-tools 6.1.0 → 7.2.0

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.mjs CHANGED
@@ -2,37 +2,26 @@ import {
2
2
  SzenenView,
3
3
  alsFlowTextZugang,
4
4
  applyEvents,
5
- createFlowSzenenViewPort
6
- } from "./chunk-GHKN2VX3.mjs";
7
- import "./chunk-FUY2OCVC.mjs";
8
- import {
9
- exportProjectToZip,
10
- importProjectFromZip,
11
- injectXMPMetadata
12
- } from "./chunk-UFXDXENC.mjs";
13
- import {
5
+ createFlowSzenenViewPort,
14
6
  getHFToken,
15
7
  getSessionClientId,
16
8
  hfBatchArchive,
17
9
  hfBootstrapFromLegacy,
18
- hfDeleteProject,
19
10
  hfDownloadBinaryByPath,
20
- hfDownloadProject,
21
11
  hfListDir,
22
- hfListProjects,
23
12
  hfLoadImageAsBase64,
24
13
  hfUploadImage,
25
- hfUploadProjectForm,
26
14
  hfUploadSmallFile,
27
15
  loadHFState,
28
16
  loadPendingEvents,
29
17
  setHFToken,
30
18
  tsFromEventPath,
31
19
  writeHFEvent
32
- } from "./chunk-NBUS7AWR.mjs";
20
+ } from "./chunk-5QA6DTJ6.mjs";
21
+ import "./chunk-FUY2OCVC.mjs";
33
22
 
34
23
  // package.json
35
- var version = "6.1.0";
24
+ var version = "7.2.0";
36
25
 
37
26
  // src/hooks/useOnClickOutside.ts
38
27
  import { useEffect } from "react";
@@ -71,7 +60,7 @@ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
71
60
  }
72
61
 
73
62
  // src/features/szenen/szenenViewStyles.generated.ts
74
- 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: end;\n justify-content: space-between;\n gap: var(--space-md);\n padding: clamp(1rem, 3vw, 1.75rem);\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: var(--space-xs);\n font-size: clamp(1.25rem, 4vw, 2rem);\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n}\n\n.szenen-view__eyebrow {\n margin-bottom: var(--space-xs) !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.72rem;\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.35rem 0.65rem;\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.78rem;\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-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__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.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-sm);\n padding: var(--space-md);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.25rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.9rem;\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-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\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';
63
+ 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-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__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.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-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\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';
75
64
 
76
65
  // src/lib/utils.ts
77
66
  var getFormattedTimestamp = () => {
@@ -88,11 +77,6 @@ var GLOBAL_STYLES = `
88
77
  .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }
89
78
  @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
90
79
  .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
91
- .flow-szenen-entry { display: grid; gap: 6px; width: 100%; max-width: 280px; text-align: center; }
92
- .flow-szenen-entry button, .flow-szenen-shell__header button { min-height: 44px; padding: 0 16px; color: #e7e5e4; background: #1d4ed8; border: 1px solid #3b82f6; border-radius: 12px; font: inherit; font-weight: 700; cursor: pointer; }
93
- .flow-szenen-entry button { display: flex; align-items: center; justify-content: center; gap: 8px; }
94
- .flow-szenen-entry button:disabled { color: rgba(255,255,255,.3); background: transparent; border-color: rgba(255,255,255,.1); cursor: not-allowed; }
95
- .flow-szenen-entry small { color: rgba(255,255,255,.35); font-size: 10px; }
96
80
  .flow-workspace-picker { position: fixed; inset: 0; display: grid; place-items: center; padding: 16px; color: #f5f5f5; background: #050505; overflow: auto; }
97
81
  .flow-workspace-picker__card { box-sizing: border-box; display: grid; gap: 16px; width: min(100%, 448px); padding: 24px; background: #171717; border: 1px solid #404040; border-radius: 12px; box-shadow: 0 25px 50px rgba(0,0,0,.55); }
98
82
  .flow-workspace-picker__card header { display: grid; gap: 4px; }
@@ -103,7 +87,7 @@ var GLOBAL_STYLES = `
103
87
  .flow-workspace-picker__choices button:hover { border-color: #3b82f6; background: #262626; }
104
88
  .flow-workspace-picker__back { justify-self: start; padding: 0; color: #a3a3a3; background: transparent; border: 0; font: inherit; cursor: pointer; }
105
89
  .flow-workspace-picker__warning { margin: 0; padding: 10px; color: #fbbf24; background: rgba(146,64,14,.2); border: 1px solid rgba(217,119,6,.4); border-radius: 8px; font-size: 12px; }
106
- .flow-szenen-shell {
90
+ .flow-szenen-pane {
107
91
  --sz-bg: #0e0e0e; --sz-surface: #171717; --sz-elevated: #262626;
108
92
  --sz-border: #404040; --sz-text: #f5f5f5; --sz-muted: #a3a3a3;
109
93
  --color-bg-app: #000; --color-bg-surface: #0a0a0a; --color-bg-surface-hover: #171717; --color-bg-elevated: #171717;
@@ -117,17 +101,132 @@ var GLOBAL_STYLES = `
117
101
  --radius-sm: .25rem; --radius-md: .5rem; --radius-lg: .75rem; --radius-full: 9999px;
118
102
  --font-sans: system-ui, -apple-system, sans-serif;
119
103
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
120
- position: fixed; inset: 0; display: flex; flex-direction: column;
121
- color: var(--sz-text); background: var(--sz-bg); overflow: hidden;
104
+ flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 16px;
105
+ color: var(--sz-text); background: var(--sz-bg); overflow: auto;
122
106
  }
123
- .flow-szenen-shell__header { display: flex; align-items: center; gap: 16px; min-height: 58px; padding: 8px 16px; background: #050505; border-bottom: 1px solid var(--sz-border); }
124
- .flow-szenen-shell__header button { min-height: 36px; background: var(--sz-elevated); border-color: var(--sz-border); }
125
- .flow-szenen-shell__header div { display: grid; gap: 2px; }
126
- .flow-szenen-shell__header small { color: var(--sz-muted); font-family: monospace; }
127
- .flow-szenen-shell__content { flex: 1; min-height: 0; padding: 16px; overflow: auto; }
128
- @media (max-width: 760px) { .flow-szenen-shell__content { padding: 8px; } }
107
+ .flow-szenen-pane--empty { align-items: center; justify-content: center; text-align: center; color: var(--sz-muted); font-size: 12px; }
108
+ @media (max-width: 760px) { .flow-szenen-pane { padding: 8px; } }
129
109
  ` + SZENEN_VIEW_STYLES;
130
110
 
111
+ // src/lib/metadata.ts
112
+ var crcTable = (() => {
113
+ let c;
114
+ const table = new Uint32Array(256);
115
+ for (let n = 0; n < 256; n++) {
116
+ c = n;
117
+ for (let k = 0; k < 8; k++) {
118
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
119
+ }
120
+ table[n] = c;
121
+ }
122
+ return table;
123
+ })();
124
+ function calculateCRC(bytes) {
125
+ let crc = 4294967295;
126
+ for (let i = 0; i < bytes.length; i++) {
127
+ crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
128
+ }
129
+ return (crc ^ 4294967295) >>> 0;
130
+ }
131
+ function indexOfBytes(buffer, search, start = 0) {
132
+ for (let i = start; i <= buffer.length - search.length; i++) {
133
+ let found = true;
134
+ for (let j = 0; j < search.length; j++) {
135
+ if (buffer[i + j] !== search[j]) {
136
+ found = false;
137
+ break;
138
+ }
139
+ }
140
+ if (found) return i;
141
+ }
142
+ return -1;
143
+ }
144
+ function createXMPPacket(prompt, seed, model, id, tags = [], hierarchy) {
145
+ const sanitize = (str) => (str || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
146
+ const escPrompt = sanitize(prompt);
147
+ const escModel = sanitize(model || "AI Model");
148
+ const escHierarchy = sanitize(hierarchy || "");
149
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
150
+ const allTags = [.../* @__PURE__ */ new Set([...tags, "Avatar Architect", escModel])];
151
+ const rdfTags = allTags.map((t) => `<rdf:li>${sanitize(t)}</rdf:li>`).join("");
152
+ const BOM = "\uFEFF";
153
+ return `<?xpacket begin="${BOM}" id="W5M0MpCehiHzreSzNTczkc9d"?>
154
+ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140">
155
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
156
+ <rdf:Description rdf:about=""
157
+ xmlns:xmp="http://ns.adobe.com/xap/1.0/"
158
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
159
+ xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
160
+ xmlns:ai="http://ns.flow.creative/ai/1.0/">
161
+ <dc:format>image/png</dc:format>
162
+ <dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escPrompt}</rdf:li></rdf:Alt></dc:description>
163
+ <dc:subject><rdf:Bag>${rdfTags}</rdf:Bag></dc:subject>
164
+ <photoshop:Instructions>Seed: ${seed} | Model: ${escModel}</photoshop:Instructions>
165
+ <xmp:CreateDate>${timestamp}</xmp:CreateDate>
166
+ <ai:prompt>${escPrompt}</ai:prompt>
167
+ <ai:seed>${seed ?? "0"}</ai:seed>
168
+ <ai:model>${escModel}</ai:model>
169
+ <ai:hierarchy>${escHierarchy}</ai:hierarchy>
170
+ </rdf:Description>
171
+ </rdf:RDF>
172
+ </x:xmpmeta>${" ".repeat(1024)}<?xpacket end="w"?>`;
173
+ }
174
+ function bytesToBase64(bytes) {
175
+ let binary = "";
176
+ const len = bytes.byteLength;
177
+ for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
178
+ return btoa(binary);
179
+ }
180
+ function base64ToBytes(base64) {
181
+ const clean = base64.includes(",") ? base64.split(",")[1] : base64.trim();
182
+ try {
183
+ const binary = atob(clean);
184
+ const bytes = new Uint8Array(binary.length);
185
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
186
+ return bytes;
187
+ } catch (e) {
188
+ return new Uint8Array(0);
189
+ }
190
+ }
191
+ function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy) {
192
+ try {
193
+ const bytes = base64ToBytes(base64);
194
+ if (bytes.length < 8 || bytes[0] !== 137 || bytes[1] !== 80) {
195
+ console.warn("Metadaten-Injektion \xFCbersprungen: Datei ist kein PNG.");
196
+ return base64;
197
+ }
198
+ const xmpData = createXMPPacket(prompt, seed, model, id, tags, hierarchy);
199
+ const encoder = new TextEncoder();
200
+ const keyword = encoder.encode("XML:com.adobe.xmp");
201
+ const textBytes = encoder.encode(xmpData);
202
+ const chunkData = new Uint8Array(keyword.length + 5 + textBytes.length);
203
+ chunkData.set(keyword, 0);
204
+ chunkData.set(textBytes, keyword.length + 5);
205
+ const type = encoder.encode("iTXt");
206
+ const crcInput = new Uint8Array(type.length + chunkData.length);
207
+ crcInput.set(type, 0);
208
+ crcInput.set(chunkData, type.length);
209
+ const crc = calculateCRC(crcInput);
210
+ const fullChunk = new Uint8Array(4 + 4 + chunkData.length + 4);
211
+ const view = new DataView(fullChunk.buffer);
212
+ view.setUint32(0, chunkData.length);
213
+ fullChunk.set(type, 4);
214
+ fullChunk.set(chunkData, 8);
215
+ view.setUint32(8 + chunkData.length, crc);
216
+ const IDAT = encoder.encode("IDAT");
217
+ const idatPos = indexOfBytes(bytes, IDAT);
218
+ const insertPos = idatPos !== -1 ? idatPos - 4 : 8;
219
+ const result = new Uint8Array(bytes.length + fullChunk.length);
220
+ result.set(bytes.slice(0, insertPos), 0);
221
+ result.set(fullChunk, insertPos);
222
+ result.set(bytes.slice(insertPos), insertPos + fullChunk.length);
223
+ return bytesToBase64(result);
224
+ } catch (e) {
225
+ console.error("Fehler beim Einbetten der Metadaten:", e);
226
+ return base64;
227
+ }
228
+ }
229
+
131
230
  // src/lib/aiHelpers.ts
132
231
  function buildGenerationPrompt(hierarchyText, mode = "creative") {
133
232
  const safe = hierarchyText.length > 2500 ? hierarchyText.slice(0, 2500) + "..." : hierarchyText;
@@ -1446,7 +1545,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1446
1545
  }
1447
1546
 
1448
1547
  // src/components/AvatarArchitectApp.tsx
1449
- import { useState as useState25, useCallback as useCallback4, useMemo as useMemo6, useEffect as useEffect10, useRef as useRef11 } from "react";
1548
+ import { useState as useState25, useCallback as useCallback4, useMemo as useMemo6, useEffect as useEffect9, useRef as useRef10 } from "react";
1450
1549
 
1451
1550
  // src/components/PromptTab.tsx
1452
1551
  import { useRef as useRef5, useState as useState8 } from "react";
@@ -2033,374 +2132,50 @@ var TaskExecutor = ({ openTasks, onExecute, onTaskCompleted }) => {
2033
2132
  ] });
2034
2133
  };
2035
2134
 
2036
- // src/components/ProjectSyncTab.tsx
2037
- import { useRef as useRef7, useState as useState10, useEffect as useEffect6 } from "react";
2038
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2039
- var ProjectSyncTab = ({
2040
- topSlot,
2041
- onProjectExport,
2042
- onProjectImport,
2043
- onWorkspaceImport,
2044
- projectActionState,
2045
- serverProjects,
2046
- onServerSave,
2047
- onServerLoad,
2048
- onServerDelete,
2049
- onRefreshServerProjects,
2050
- onComputeSyncDiff,
2051
- onExecuteSync,
2052
- hfToken,
2053
- onHfLoad,
2054
- onProjectExportBase64,
2055
- onHfInitialSync
2056
- }) => {
2057
- const projectInputRef = useRef7(null);
2058
- const workspaceInputRef = useRef7(null);
2059
- const [saveName, setSaveName] = useState10("");
2060
- const [isSaving, setIsSaving] = useState10(false);
2061
- const [isExporting, setIsExporting] = useState10(false);
2062
- const [syncState, setSyncState] = useState10("idle");
2063
- const [syncDiff, setSyncDiff] = useState10(null);
2064
- const [selectedLocalIds, setSelectedLocalIds] = useState10(/* @__PURE__ */ new Set());
2065
- const handleExport = async () => {
2066
- if (!onProjectExport) return;
2067
- setIsExporting(true);
2068
- try {
2069
- await onProjectExport();
2070
- } finally {
2071
- setIsExporting(false);
2072
- }
2073
- };
2074
- const handleComputeDiff = async () => {
2075
- if (!onComputeSyncDiff) return;
2076
- setSyncState("computing");
2077
- setSyncDiff(null);
2078
- try {
2079
- const diff = await onComputeSyncDiff();
2080
- setSyncDiff(diff);
2081
- setSelectedLocalIds(new Set(diff.localOnly.map((g) => g.id)));
2082
- setSyncState("ready");
2083
- } catch {
2084
- setSyncState("error");
2085
- setTimeout(() => setSyncState("idle"), 4e3);
2086
- }
2087
- };
2088
- const handleExecuteSync = async () => {
2089
- if (!onExecuteSync || !syncDiff) return;
2090
- setSyncState("executing");
2091
- try {
2092
- await onExecuteSync([...selectedLocalIds]);
2093
- setSyncState("done");
2094
- setSyncDiff(null);
2095
- setTimeout(() => setSyncState("idle"), 3e3);
2096
- } catch {
2097
- setSyncState("error");
2098
- setTimeout(() => setSyncState("idle"), 4e3);
2099
- }
2100
- };
2101
- const toggleLocalId = (id) => {
2102
- setSelectedLocalIds((prev) => {
2103
- const next = new Set(prev);
2104
- if (next.has(id)) next.delete(id);
2105
- else next.add(id);
2106
- return next;
2107
- });
2108
- };
2109
- const isWorking = projectActionState === "working" || projectActionState === "working-full";
2110
- const [hfProjects, setHfProjects] = useState10([]);
2111
- const [hfLoading, setHfLoading] = useState10(false);
2112
- const [hfSaving, setHfSaving] = useState10(false);
2113
- const [hfError, setHfError] = useState10(null);
2114
- const [hfSaveName, setHfSaveName] = useState10("");
2115
- const [hfSyncProgress, setHfSyncProgress] = useState10(null);
2116
- const [hfSyncing, setHfSyncing] = useState10(false);
2117
- const loadHfProjects = async (token) => {
2118
- setHfLoading(true);
2119
- setHfError(null);
2120
- try {
2121
- setHfProjects(await hfListProjects(token));
2122
- } catch (e) {
2123
- setHfError(e.message);
2124
- } finally {
2125
- setHfLoading(false);
2126
- }
2127
- };
2128
- useEffect6(() => {
2129
- if (hfToken) loadHfProjects(hfToken);
2130
- }, [hfToken]);
2135
+ // src/components/HfMediaSyncTab.tsx
2136
+ import { useState as useState10 } from "react";
2137
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2138
+ var HfMediaSyncTab = ({ topSlot, onHfInitialSync }) => {
2139
+ const [error, setError] = useState10(null);
2140
+ const [progress, setProgress] = useState10(null);
2141
+ const [syncing, setSyncing] = useState10(false);
2131
2142
  return /* @__PURE__ */ jsx15("div", { className: "absolute inset-0 overflow-y-auto dark-scrollbar", children: /* @__PURE__ */ jsxs13("div", { className: "p-6 flex flex-col gap-8", children: [
2132
2143
  topSlot && /* @__PURE__ */ jsx15("div", { children: topSlot }),
2133
- (onProjectExport || onProjectImport || onWorkspaceImport) && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-4", children: [
2134
- /* @__PURE__ */ jsx15(SectionLabel, { children: "Projekt-ZIP" }),
2144
+ onHfInitialSync && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-4", children: [
2145
+ /* @__PURE__ */ jsx15(SectionLabel, { children: "Hugging Face" }),
2135
2146
  /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-2", children: [
2136
- onProjectExport && /* @__PURE__ */ jsx15(PillButton, { variant: "filled", icon: "download", loading: isExporting || isWorking, onClick: handleExport, children: "Exportieren (.zip)" }),
2137
- onProjectImport && /* @__PURE__ */ jsxs13(Fragment5, { children: [
2138
- /* @__PURE__ */ jsx15(PillButton, { variant: "outline", icon: "upload", onClick: () => projectInputRef.current?.click(), children: "ZIP laden" }),
2139
- /* @__PURE__ */ jsx15(
2140
- "input",
2141
- {
2142
- ref: projectInputRef,
2143
- type: "file",
2144
- accept: ".zip",
2145
- className: "hidden",
2146
- onChange: (e) => {
2147
- const f = e.target.files?.[0];
2148
- if (f) onProjectImport(f);
2149
- e.target.value = "";
2150
- }
2151
- }
2152
- )
2153
- ] })
2154
- ] }),
2155
- onWorkspaceImport && /* @__PURE__ */ jsxs13(Fragment5, { children: [
2156
- /* @__PURE__ */ jsx15(PillButton, { variant: "outline", icon: "upload_file", onClick: () => workspaceInputRef.current?.click(), children: "Tags laden (.json)" }),
2157
- /* @__PURE__ */ jsx15(
2158
- "input",
2159
- {
2160
- ref: workspaceInputRef,
2161
- type: "file",
2162
- accept: ".json",
2163
- className: "hidden",
2164
- onChange: (e) => {
2165
- const f = e.target.files?.[0];
2166
- if (f) onWorkspaceImport(f);
2167
- e.target.value = "";
2168
- }
2169
- }
2170
- )
2171
- ] })
2172
- ] }),
2173
- (onServerSave || serverProjects || onServerDelete) && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-4", children: [
2174
- /* @__PURE__ */ jsx15(SectionLabel, { children: "Server-Speicher" }),
2175
- onServerSave && /* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
2176
- /* @__PURE__ */ jsx15(
2177
- "input",
2178
- {
2179
- type: "text",
2180
- value: saveName,
2181
- onChange: (e) => setSaveName(e.target.value),
2182
- placeholder: "Name (optional)",
2183
- className: "flex-1 bg-white/5 border border-white/10 rounded-xl px-3 text-[12px] text-white/70 outline-none placeholder:text-white/20",
2184
- style: { height: 40 }
2185
- }
2186
- ),
2187
- /* @__PURE__ */ jsx15(
2188
- PillButton,
2189
- {
2190
- variant: "filled",
2191
- icon: "cloud_upload",
2192
- loading: isSaving,
2193
- onClick: async () => {
2194
- setIsSaving(true);
2195
- try {
2196
- await onServerSave(saveName || void 0);
2197
- setSaveName("");
2198
- await onRefreshServerProjects?.();
2199
- } finally {
2200
- setIsSaving(false);
2201
- }
2202
- },
2203
- children: "Speichern"
2204
- }
2205
- )
2206
- ] }),
2207
- (!serverProjects || serverProjects.length === 0) && /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-white/20 px-2", children: "Noch nichts gespeichert." }),
2208
- serverProjects && serverProjects.length > 0 && /* @__PURE__ */ jsx15("div", { className: "flex flex-col gap-1", children: serverProjects.map((p) => /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/5", children: [
2209
- /* @__PURE__ */ jsxs13("div", { className: "flex-1 min-w-0", children: [
2210
- /* @__PURE__ */ jsx15("p", { className: "text-[11px] text-white/70 font-bold truncate", children: p.name }),
2211
- /* @__PURE__ */ jsxs13("p", { className: "text-[9px] text-white/25", children: [
2212
- new Date(p.saved_at).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" }),
2213
- " \xB7 ",
2214
- (p.size / 1024).toFixed(0),
2215
- " KB"
2216
- ] })
2217
- ] }),
2218
- onServerLoad && /* @__PURE__ */ jsx15("button", { onClick: () => onServerLoad(p.id), className: "w-8 h-8 flex items-center justify-center text-white/30 active:text-white transition-colors", children: /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[18px]", children: "cloud_download" }) }),
2219
- /* @__PURE__ */ jsx15("button", { onClick: () => onServerDelete?.(p.id), className: "w-8 h-8 flex items-center justify-center text-white/20 active:text-red-400 transition-colors", children: /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[18px]", children: "delete" }) })
2220
- ] }, p.id)) })
2221
- ] }),
2222
- hfToken && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-4", children: [
2223
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between", children: [
2224
- /* @__PURE__ */ jsx15(SectionLabel, { children: "Hugging Face" }),
2225
- /* @__PURE__ */ jsx15("button", { onClick: () => loadHfProjects(hfToken), className: "text-white/30 hover:text-white/60 transition", children: /* @__PURE__ */ jsx15("span", { className: `material-symbols-outlined text-[18px]${hfLoading ? " animate-spin" : ""}`, children: "refresh" }) })
2226
- ] }),
2227
- onProjectExportBase64 && /* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
2228
- /* @__PURE__ */ jsx15(
2229
- "input",
2230
- {
2231
- type: "text",
2232
- value: hfSaveName,
2233
- onChange: (e) => setHfSaveName(e.target.value),
2234
- placeholder: "Name (optional)",
2235
- className: "flex-1 bg-white/5 border border-white/10 rounded-xl px-3 text-[12px] text-white/70 outline-none placeholder:text-white/20",
2236
- style: { height: 40 }
2237
- }
2238
- ),
2239
- /* @__PURE__ */ jsx15(
2240
- PillButton,
2241
- {
2242
- variant: "filled",
2243
- icon: "cloud_upload",
2244
- loading: hfSaving,
2245
- onClick: async () => {
2246
- if (!onProjectExportBase64) return;
2247
- setHfSaving(true);
2248
- setHfError(null);
2249
- try {
2250
- const base64 = await onProjectExportBase64();
2251
- const name = hfSaveName.trim() || `project_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", "_").replace(":", "")}`;
2252
- await hfUploadProjectForm(base64, name, hfToken);
2253
- setHfSaveName("");
2254
- await loadHfProjects(hfToken);
2255
- } catch (e) {
2256
- setHfError(e.message);
2257
- } finally {
2258
- setHfSaving(false);
2259
- }
2260
- },
2261
- children: "Speichern"
2262
- }
2263
- )
2264
- ] }),
2265
- onHfInitialSync && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-2", children: [
2266
2147
  /* @__PURE__ */ jsx15(
2267
2148
  PillButton,
2268
2149
  {
2269
2150
  variant: "outline",
2270
2151
  icon: "cloud_sync",
2271
- loading: hfSyncing,
2152
+ loading: syncing,
2272
2153
  onClick: async () => {
2273
- setHfSyncing(true);
2274
- setHfError(null);
2275
- setHfSyncProgress({ done: 0, total: 0 });
2154
+ setSyncing(true);
2155
+ setError(null);
2156
+ setProgress({ done: 0, total: 0 });
2276
2157
  try {
2277
- await onHfInitialSync((done, total) => setHfSyncProgress({ done, total }));
2278
- setHfSyncProgress(null);
2158
+ await onHfInitialSync((done, total) => setProgress({ done, total }));
2159
+ setProgress(null);
2279
2160
  } catch (e) {
2280
- setHfError(e.message);
2281
- setHfSyncProgress(null);
2161
+ setError(e instanceof Error ? e.message : String(e));
2162
+ setProgress(null);
2282
2163
  } finally {
2283
- setHfSyncing(false);
2164
+ setSyncing(false);
2284
2165
  }
2285
2166
  },
2286
- children: hfSyncing && hfSyncProgress ? `${hfSyncProgress.done} / ${hfSyncProgress.total} Bilder` : "Initial-Sync nach HF"
2167
+ children: syncing && progress ? `${progress.done} / ${progress.total} Bilder` : "Initial-Sync nach HF"
2287
2168
  }
2288
2169
  ),
2289
- hfSyncing && hfSyncProgress && hfSyncProgress.total > 0 && /* @__PURE__ */ jsx15("div", { className: "w-full h-1 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ jsx15(
2170
+ syncing && progress && progress.total > 0 && /* @__PURE__ */ jsx15("div", { className: "w-full h-1 bg-white/10 rounded-full overflow-hidden", children: /* @__PURE__ */ jsx15(
2290
2171
  "div",
2291
2172
  {
2292
2173
  className: "h-full bg-blue-400 rounded-full transition-all",
2293
- style: { width: `${Math.round(hfSyncProgress.done / hfSyncProgress.total * 100)}%` }
2174
+ style: { width: `${Math.round(progress.done / progress.total * 100)}%` }
2294
2175
  }
2295
2176
  ) })
2296
2177
  ] }),
2297
- hfError && /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-red-400 font-mono px-1", children: hfError }),
2298
- !hfLoading && hfProjects.length === 0 && /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-white/20 px-2", children: "Noch nichts auf HF gespeichert." }),
2299
- hfProjects.map((p) => /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/5", children: [
2300
- /* @__PURE__ */ jsxs13("div", { className: "flex-1 min-w-0", children: [
2301
- /* @__PURE__ */ jsx15("p", { className: "text-[11px] text-white/70 font-bold truncate", children: p.name }),
2302
- /* @__PURE__ */ jsxs13("p", { className: "text-[9px] text-white/25", children: [
2303
- (p.size / 1024 / 1024).toFixed(1),
2304
- " MB",
2305
- p.isLfs ? " \xB7 LFS" : ""
2306
- ] })
2307
- ] }),
2308
- onHfLoad && /* @__PURE__ */ jsx15(
2309
- "button",
2310
- {
2311
- onClick: async () => {
2312
- try {
2313
- const { hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-W7KO2FOH.mjs");
2314
- const file = await hfDownloadProject2(p.path, hfToken);
2315
- onHfLoad(file);
2316
- } catch (e) {
2317
- setHfError(e.message);
2318
- }
2319
- },
2320
- className: "w-8 h-8 flex items-center justify-center text-white/30 active:text-white transition-colors",
2321
- children: /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[18px]", children: "cloud_download" })
2322
- }
2323
- ),
2324
- /* @__PURE__ */ jsx15(
2325
- "button",
2326
- {
2327
- onClick: async () => {
2328
- if (!confirm(`"${p.name}" auf HF l\xF6schen?`)) return;
2329
- try {
2330
- await hfDeleteProject(p.path, hfToken);
2331
- await loadHfProjects(hfToken);
2332
- } catch (e) {
2333
- setHfError(e.message);
2334
- }
2335
- },
2336
- className: "w-8 h-8 flex items-center justify-center text-white/20 active:text-red-400 transition-colors",
2337
- children: /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[18px]", children: "delete" })
2338
- }
2339
- )
2340
- ] }, p.id))
2341
- ] }),
2342
- onComputeSyncDiff && onExecuteSync && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-4", children: [
2343
- /* @__PURE__ */ jsx15(SectionLabel, { children: "Sync" }),
2344
- (syncState === "idle" || syncState === "computing") && /* @__PURE__ */ jsx15(PillButton, { variant: "outline", icon: "compare_arrows", loading: syncState === "computing", onClick: handleComputeDiff, children: "Diff berechnen" }),
2345
- syncState === "done" && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 px-2 text-[11px] text-green-400", children: [
2346
- /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[16px]", children: "check_circle" }),
2347
- "Sync abgeschlossen"
2348
- ] }),
2349
- syncState === "error" && /* @__PURE__ */ jsx15("p", { className: "text-[11px] text-red-400 px-2", children: "Fehler beim Sync." }),
2350
- syncDiff && (syncState === "ready" || syncState === "executing") && /* @__PURE__ */ jsxs13(Fragment5, { children: [
2351
- syncDiff.localOnly.length === 0 && syncDiff.serverOnly.length === 0 && /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-white/30 px-2", children: "Alles synchron \u2014 keine Unterschiede." }),
2352
- syncDiff.localOnly.length > 0 && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-2", children: [
2353
- /* @__PURE__ */ jsxs13("p", { className: "text-[9px] text-white/30 uppercase tracking-widest px-1 font-bold", children: [
2354
- "Nur lokal (",
2355
- syncDiff.localOnly.length,
2356
- ")"
2357
- ] }),
2358
- syncDiff.localOnly.map((gen) => /* @__PURE__ */ jsxs13(
2359
- "button",
2360
- {
2361
- onClick: () => toggleLocalId(gen.id),
2362
- className: `flex items-center gap-3 px-3 py-2 rounded-xl border transition-all text-left ${selectedLocalIds.has(gen.id) ? "bg-blue-500/10 border-blue-500/30" : "bg-white/3 border-white/5 opacity-60"}`,
2363
- children: [
2364
- /* @__PURE__ */ jsx15("div", { className: `w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 ${selectedLocalIds.has(gen.id) ? "bg-blue-500 border-blue-500" : "border-white/20"}`, children: selectedLocalIds.has(gen.id) && /* @__PURE__ */ jsx15("span", { className: "material-symbols-outlined text-[11px] text-white", children: "check" }) }),
2365
- gen.base64 && /* @__PURE__ */ jsx15("img", { src: gen.base64, className: "w-10 h-10 rounded object-cover flex-shrink-0" }),
2366
- /* @__PURE__ */ jsxs13("div", { className: "flex-1 min-w-0", children: [
2367
- /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-white/60 truncate", children: gen.prompt || "Kein Prompt" }),
2368
- /* @__PURE__ */ jsx15("p", { className: "text-[9px] text-white/25", children: new Date(gen.timestamp).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" }) })
2369
- ] })
2370
- ]
2371
- },
2372
- gen.id
2373
- ))
2374
- ] }),
2375
- syncDiff.serverOnly.length > 0 && /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-2", children: [
2376
- /* @__PURE__ */ jsxs13("p", { className: "text-[9px] text-white/30 uppercase tracking-widest px-1 font-bold", children: [
2377
- "Nur auf Server (",
2378
- syncDiff.serverOnly.length,
2379
- ")"
2380
- ] }),
2381
- syncDiff.serverOnly.map((gen) => /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3 px-3 py-2 rounded-xl bg-white/3 border border-white/5 opacity-40", children: [
2382
- gen.base64 && /* @__PURE__ */ jsx15("img", { src: gen.base64, className: "w-10 h-10 rounded object-cover flex-shrink-0" }),
2383
- /* @__PURE__ */ jsxs13("div", { className: "flex-1 min-w-0", children: [
2384
- /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-white/60 truncate", children: gen.prompt || "Kein Prompt" }),
2385
- /* @__PURE__ */ jsx15("p", { className: "text-[9px] text-white/25", children: new Date(gen.timestamp).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" }) })
2386
- ] })
2387
- ] }, gen.id))
2388
- ] }),
2389
- (syncDiff.localOnly.length > 0 || syncDiff.serverOnly.length > 0) && /* @__PURE__ */ jsxs13(
2390
- PillButton,
2391
- {
2392
- variant: "solid",
2393
- icon: "sync",
2394
- loading: syncState === "executing",
2395
- disabled: selectedLocalIds.size === 0,
2396
- onClick: handleExecuteSync,
2397
- children: [
2398
- selectedLocalIds.size,
2399
- " \xFCbertragen"
2400
- ]
2401
- }
2402
- )
2403
- ] })
2178
+ error && /* @__PURE__ */ jsx15("p", { className: "text-[10px] text-red-400 font-mono px-1", children: error })
2404
2179
  ] })
2405
2180
  ] }) });
2406
2181
  };
@@ -2532,7 +2307,7 @@ function toPromptImages(images) {
2532
2307
  }
2533
2308
 
2534
2309
  // src/hooks/useHFState.ts
2535
- import { useState as useState11, useEffect as useEffect7, useRef as useRef8, useCallback } from "react";
2310
+ import { useState as useState11, useEffect as useEffect6, useRef as useRef7, useCallback } from "react";
2536
2311
 
2537
2312
  // src/lib/hfDag.ts
2538
2313
  function buildDag(events) {
@@ -2622,9 +2397,9 @@ function useHFState(token, namespace) {
2622
2397
  const [pendingBufferCount, setPendingBufferCount] = useState11(readOfflineBuffer().length);
2623
2398
  const [lastEventTs, setLastEventTs] = useState11(0);
2624
2399
  const [hasStateZip, setHasStateZip] = useState11(false);
2625
- const knownEventPaths = useRef8(/* @__PURE__ */ new Set());
2626
- const knownFilePaths = useRef8(/* @__PURE__ */ new Set());
2627
- const allEventsRef = useRef8([]);
2400
+ const knownEventPaths = useRef7(/* @__PURE__ */ new Set());
2401
+ const knownFilePaths = useRef7(/* @__PURE__ */ new Set());
2402
+ const allEventsRef = useRef7([]);
2628
2403
  const applyNewEvents = useCallback((snapshot, newEvents) => {
2629
2404
  if (!newEvents.length && allEventsRef.current.length === 0) {
2630
2405
  setEventCount(0);
@@ -2701,10 +2476,10 @@ function useHFState(token, namespace) {
2701
2476
  } catch {
2702
2477
  }
2703
2478
  }, [token, namespace, state, applyNewEvents]);
2704
- useEffect7(() => {
2479
+ useEffect6(() => {
2705
2480
  if (token && namespace) loadFull();
2706
2481
  }, [token, namespace]);
2707
- useEffect7(() => {
2482
+ useEffect6(() => {
2708
2483
  if (!token || !namespace) return;
2709
2484
  const id = setInterval(pollNew, POLL_INTERVAL_MS);
2710
2485
  return () => clearInterval(id);
@@ -2759,7 +2534,7 @@ function useHFState(token, namespace) {
2759
2534
  }
2760
2535
 
2761
2536
  // src/hooks/useTagBase.ts
2762
- import { useState as useState12, useEffect as useEffect8, useCallback as useCallback2, useRef as useRef9 } from "react";
2537
+ import { useState as useState12, useEffect as useEffect7, useCallback as useCallback2, useRef as useRef8 } from "react";
2763
2538
 
2764
2539
  // src/lib/hfTagBaseService.ts
2765
2540
  import JSZip from "jszip";
@@ -2895,13 +2670,13 @@ function useTagBase(token, namespace) {
2895
2670
  const [selectedContextId, setSelectedContextId] = useState12(() => readStoredId(CONTEXT_ID_KEY));
2896
2671
  const [pickerMessage, setPickerMessage] = useState12(null);
2897
2672
  const [pendingTags, setPendingTags] = useState12([]);
2898
- const pendingIdCounter = useRef9(-1);
2899
- const selectedUserIdRef = useRef9(selectedUserId);
2900
- const selectedContextIdRef = useRef9(selectedContextId);
2901
- useEffect8(() => {
2673
+ const pendingIdCounter = useRef8(-1);
2674
+ const selectedUserIdRef = useRef8(selectedUserId);
2675
+ const selectedContextIdRef = useRef8(selectedContextId);
2676
+ useEffect7(() => {
2902
2677
  selectedUserIdRef.current = selectedUserId;
2903
2678
  }, [selectedUserId]);
2904
- useEffect8(() => {
2679
+ useEffect7(() => {
2905
2680
  selectedContextIdRef.current = selectedContextId;
2906
2681
  }, [selectedContextId]);
2907
2682
  const load = useCallback2(async () => {
@@ -2946,7 +2721,7 @@ function useTagBase(token, namespace) {
2946
2721
  setIsLoading(false);
2947
2722
  }
2948
2723
  }, [token, namespace]);
2949
- useEffect8(() => {
2724
+ useEffect7(() => {
2950
2725
  load();
2951
2726
  }, [load]);
2952
2727
  const selectUser = useCallback2((userId) => {
@@ -3045,7 +2820,7 @@ import { useState as useState15 } from "react";
3045
2820
 
3046
2821
  // src/components/labs/LabImagePicker.tsx
3047
2822
  import { useState as useState13 } from "react";
3048
- import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2823
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
3049
2824
  var LabImagePicker = ({
3050
2825
  availableItems,
3051
2826
  recentItems,
@@ -3142,7 +2917,7 @@ var LabImagePicker = ({
3142
2917
  }
3143
2918
  ) }),
3144
2919
  /* @__PURE__ */ jsxs14("div", { className: "flex-1 overflow-y-auto dark-scrollbar px-3 pb-3", children: [
3145
- recentItems.length > 0 && !search && /* @__PURE__ */ jsxs14(Fragment6, { children: [
2920
+ recentItems.length > 0 && !search && /* @__PURE__ */ jsxs14(Fragment5, { children: [
3146
2921
  /* @__PURE__ */ jsx16("p", { className: "text-[9px] font-bold uppercase tracking-widest text-white/30 mb-2 mt-1", children: "Zuletzt verwendet" }),
3147
2922
  /* @__PURE__ */ jsx16("div", { className: "grid grid-cols-3 gap-2 mb-4", children: recentItems.slice(0, 6).map(renderItemCard) }),
3148
2923
  /* @__PURE__ */ jsx16("p", { className: "text-[9px] font-bold uppercase tracking-widest text-white/30 mb-2", children: "Alle Bilder" })
@@ -3257,7 +3032,7 @@ function useGenerationSettings() {
3257
3032
  }
3258
3033
 
3259
3034
  // src/components/labs/LabRemix.tsx
3260
- import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
3035
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
3261
3036
  var LabRemix = ({ services, onResult }) => {
3262
3037
  const [showPicker, setShowPicker] = useState15(false);
3263
3038
  const [selected, setSelected] = useState15(null);
@@ -3413,10 +3188,10 @@ var LabRemix = ({ services, onResult }) => {
3413
3188
  onClick: handleGeneratePrompt,
3414
3189
  disabled: !selected || !instruction.trim() || isGeneratingPrompt,
3415
3190
  className: "w-full py-3 rounded-xl bg-white/10 text-white font-bold text-[12px] uppercase tracking-wide disabled:opacity-30 active:bg-white/15 flex items-center justify-center gap-2",
3416
- children: isGeneratingPrompt ? /* @__PURE__ */ jsxs16(Fragment7, { children: [
3191
+ children: isGeneratingPrompt ? /* @__PURE__ */ jsxs16(Fragment6, { children: [
3417
3192
  /* @__PURE__ */ jsx18("div", { className: "w-3 h-3 border-t border-white rounded-full animate-spin" }),
3418
3193
  /* @__PURE__ */ jsx18("span", { children: "Generiere Prompt..." })
3419
- ] }) : /* @__PURE__ */ jsxs16(Fragment7, { children: [
3194
+ ] }) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
3420
3195
  /* @__PURE__ */ jsx18("span", { className: "material-symbols-outlined text-[16px]", children: "auto_fix_high" }),
3421
3196
  /* @__PURE__ */ jsx18("span", { children: "Prompt generieren" })
3422
3197
  ] })
@@ -3463,7 +3238,7 @@ var LabRemix = ({ services, onResult }) => {
3463
3238
 
3464
3239
  // src/components/labs/LabBlend.tsx
3465
3240
  import { useState as useState16 } from "react";
3466
- import { Fragment as Fragment8, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
3241
+ import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
3467
3242
  var LabBlend = ({ services, onResult }) => {
3468
3243
  const [showPickerFor, setShowPickerFor] = useState16(null);
3469
3244
  const [selectedImages, setSelectedImages] = useState16([]);
@@ -3558,7 +3333,7 @@ var LabBlend = ({ services, onResult }) => {
3558
3333
  /* @__PURE__ */ jsx19("p", { className: "text-[9px] font-bold uppercase tracking-widest text-white/40", children: "Bilder" }),
3559
3334
  selectedImages.map((img, i) => /* @__PURE__ */ jsxs17("div", { className: "flex gap-3 p-3 rounded-xl border border-white/10 bg-white/5", children: [
3560
3335
  /* @__PURE__ */ jsx19("span", { className: "text-[10px] font-bold text-white/40 w-6 shrink-0 pt-1", children: autoLabel(i) }),
3561
- img ? /* @__PURE__ */ jsxs17(Fragment8, { children: [
3336
+ img ? /* @__PURE__ */ jsxs17(Fragment7, { children: [
3562
3337
  /* @__PURE__ */ jsx19("img", { src: img.frame.base64, className: "w-12 h-12 object-cover rounded-lg shrink-0", onClick: () => setShowPickerFor(i) }),
3563
3338
  /* @__PURE__ */ jsxs17("div", { className: "flex-1 min-w-0 flex flex-col gap-1", children: [
3564
3339
  /* @__PURE__ */ jsx19("p", { className: "text-[10px] text-white/50 line-clamp-1", children: img.item.prompt }),
@@ -3624,10 +3399,10 @@ var LabBlend = ({ services, onResult }) => {
3624
3399
  onClick: handleGeneratePrompt,
3625
3400
  disabled: selectedImages.filter(Boolean).length < 2 || !instruction.trim() || isGeneratingPrompt,
3626
3401
  className: "w-full py-3 rounded-xl bg-white/10 text-white font-bold text-[12px] uppercase tracking-wide disabled:opacity-30 active:bg-white/15 flex items-center justify-center gap-2",
3627
- children: isGeneratingPrompt ? /* @__PURE__ */ jsxs17(Fragment8, { children: [
3402
+ children: isGeneratingPrompt ? /* @__PURE__ */ jsxs17(Fragment7, { children: [
3628
3403
  /* @__PURE__ */ jsx19("div", { className: "w-3 h-3 border-t border-white rounded-full animate-spin" }),
3629
3404
  /* @__PURE__ */ jsx19("span", { children: "Blend l\xE4uft..." })
3630
- ] }) : /* @__PURE__ */ jsxs17(Fragment8, { children: [
3405
+ ] }) : /* @__PURE__ */ jsxs17(Fragment7, { children: [
3631
3406
  /* @__PURE__ */ jsx19("span", { className: "material-symbols-outlined text-[16px]", children: "merge" }),
3632
3407
  /* @__PURE__ */ jsx19("span", { children: "Prompts blenden" })
3633
3408
  ] })
@@ -3674,7 +3449,7 @@ var LabBlend = ({ services, onResult }) => {
3674
3449
 
3675
3450
  // src/components/labs/LabCompare.tsx
3676
3451
  import { useState as useState17 } from "react";
3677
- import { Fragment as Fragment9, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
3452
+ import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
3678
3453
  var LabCompare = ({ services, onResult }) => {
3679
3454
  const [showPickerFor, setShowPickerFor] = useState17(null);
3680
3455
  const [selectedImages, setSelectedImages] = useState17([]);
@@ -3756,7 +3531,7 @@ var LabCompare = ({ services, onResult }) => {
3756
3531
  /* @__PURE__ */ jsx20("p", { className: "text-[9px] font-bold uppercase tracking-widest text-white/40", children: "Bilder zum Vergleichen" }),
3757
3532
  selectedImages.map((img, i) => /* @__PURE__ */ jsxs18("div", { className: "flex gap-3 p-3 rounded-xl border border-white/10 bg-white/5", children: [
3758
3533
  /* @__PURE__ */ jsx20("span", { className: "text-[10px] font-bold text-white/40 w-6 shrink-0 pt-1", children: autoLabel(i) }),
3759
- img ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
3534
+ img ? /* @__PURE__ */ jsxs18(Fragment8, { children: [
3760
3535
  /* @__PURE__ */ jsx20("img", { src: img.frame.base64, className: "w-12 h-12 object-cover rounded-lg shrink-0 cursor-pointer", onClick: () => setShowPickerFor(i) }),
3761
3536
  /* @__PURE__ */ jsxs18("div", { className: "flex-1 min-w-0 flex flex-col gap-1", children: [
3762
3537
  /* @__PURE__ */ jsx20("p", { className: "text-[10px] text-white/50 line-clamp-1", children: img.item.prompt }),
@@ -3800,10 +3575,10 @@ var LabCompare = ({ services, onResult }) => {
3800
3575
  onClick: handleAnalyze,
3801
3576
  disabled: selectedImages.filter(Boolean).length < 1 || !instruction.trim() || isAnalyzing,
3802
3577
  className: "w-full py-3 rounded-xl bg-white/10 text-white font-bold text-[12px] uppercase tracking-wide disabled:opacity-30 active:bg-white/15 flex items-center justify-center gap-2",
3803
- children: isAnalyzing ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
3578
+ children: isAnalyzing ? /* @__PURE__ */ jsxs18(Fragment8, { children: [
3804
3579
  /* @__PURE__ */ jsx20("div", { className: "w-3 h-3 border-t border-white rounded-full animate-spin" }),
3805
3580
  /* @__PURE__ */ jsx20("span", { children: "Analysiere..." })
3806
- ] }) : /* @__PURE__ */ jsxs18(Fragment9, { children: [
3581
+ ] }) : /* @__PURE__ */ jsxs18(Fragment8, { children: [
3807
3582
  /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined text-[16px]", children: "compare" }),
3808
3583
  /* @__PURE__ */ jsx20("span", { children: "Analysieren" })
3809
3584
  ] })
@@ -3830,10 +3605,10 @@ var LabCompare = ({ services, onResult }) => {
3830
3605
  onClick: handleGenerateImage,
3831
3606
  disabled: !generatedPrompt.trim() || isGeneratingImage,
3832
3607
  className: "w-full mt-2 py-3 rounded-xl bg-blue-600/80 text-white font-bold text-[12px] uppercase tracking-wide disabled:opacity-30 active:bg-blue-600 flex items-center justify-center gap-2",
3833
- children: isGeneratingImage ? /* @__PURE__ */ jsxs18(Fragment9, { children: [
3608
+ children: isGeneratingImage ? /* @__PURE__ */ jsxs18(Fragment8, { children: [
3834
3609
  /* @__PURE__ */ jsx20("div", { className: "w-3 h-3 border-t border-white rounded-full animate-spin" }),
3835
3610
  /* @__PURE__ */ jsx20("span", { children: "Generiere..." })
3836
- ] }) : /* @__PURE__ */ jsxs18(Fragment9, { children: [
3611
+ ] }) : /* @__PURE__ */ jsxs18(Fragment8, { children: [
3837
3612
  /* @__PURE__ */ jsx20("span", { className: "material-symbols-outlined text-[16px]", children: "image" }),
3838
3613
  /* @__PURE__ */ jsx20("span", { children: "Bild generieren" })
3839
3614
  ] })
@@ -4017,7 +3792,7 @@ var LabLoop = ({ services, onResult }) => {
4017
3792
  };
4018
3793
 
4019
3794
  // src/components/labs/LabFrameExtractor.tsx
4020
- import { useRef as useRef10, useState as useState19, useCallback as useCallback3 } from "react";
3795
+ import { useRef as useRef9, useState as useState19, useCallback as useCallback3 } from "react";
4021
3796
  import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
4022
3797
  var formatTime = (s) => {
4023
3798
  const m = Math.floor(s / 60);
@@ -4031,9 +3806,9 @@ var LabFrameExtractor = ({
4031
3806
  onResult,
4032
3807
  resolveVideoUrl
4033
3808
  }) => {
4034
- const videoRef = useRef10(null);
4035
- const canvasRef = useRef10(null);
4036
- const cancelledRef = useRef10(false);
3809
+ const videoRef = useRef9(null);
3810
+ const canvasRef = useRef9(null);
3811
+ const cancelledRef = useRef9(false);
4037
3812
  const [selectedItem, setSelectedItem] = useState19(null);
4038
3813
  const [videoSrc, setVideoSrc] = useState19(null);
4039
3814
  const [videoReady, setVideoReady] = useState19(false);
@@ -5425,7 +5200,7 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
5425
5200
  }
5426
5201
 
5427
5202
  // src/components/ServerTab.tsx
5428
- import { useState as useState23, useEffect as useEffect9 } from "react";
5203
+ import { useState as useState23, useEffect as useEffect8 } from "react";
5429
5204
  import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
5430
5205
  function StarRating({ rating = 0 }) {
5431
5206
  return /* @__PURE__ */ jsx27("div", { className: "flex gap-[2px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx27("span", { className: `material-symbols-outlined text-[12px] ${i <= rating ? "text-yellow-400" : "text-white/15"}`, children: "star" }, i)) });
@@ -5452,7 +5227,7 @@ function ServerTab({ serverBaseUrl }) {
5452
5227
  const [libError, setLibError] = useState23(null);
5453
5228
  const [activeTag, setActiveTag] = useState23(null);
5454
5229
  const [preview, setPreview] = useState23(null);
5455
- useEffect9(() => {
5230
+ useEffect8(() => {
5456
5231
  if (!serverBaseUrl) return;
5457
5232
  setUsersLoading(true);
5458
5233
  setUsersError(null);
@@ -5604,7 +5379,7 @@ function ServerTab({ serverBaseUrl }) {
5604
5379
 
5605
5380
  // src/components/FlowWorkspacePicker.tsx
5606
5381
  import { useMemo as useMemo5, useState as useState24 } from "react";
5607
- import { Fragment as Fragment10, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
5382
+ import { Fragment as Fragment9, jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
5608
5383
  function FlowWorkspacePicker({
5609
5384
  tagBase,
5610
5385
  selectedUserId,
@@ -5646,7 +5421,7 @@ function FlowWorkspacePicker({
5646
5421
  tagBase.meta.version,
5647
5422
  " enth\xE4lt keine Benutzer."
5648
5423
  ] })
5649
- ] }) : /* @__PURE__ */ jsxs26(Fragment10, { children: [
5424
+ ] }) : /* @__PURE__ */ jsxs26(Fragment9, { children: [
5650
5425
  /* @__PURE__ */ jsx28("button", { className: "flow-workspace-picker__back", type: "button", onClick: back, children: "\u2190 Anderen Benutzer w\xE4hlen" }),
5651
5426
  /* @__PURE__ */ jsxs26("div", { className: "flow-workspace-picker__choices", "aria-label": "Kontext w\xE4hlen", children: [
5652
5427
  contexts.map((context) => /* @__PURE__ */ jsxs26("button", { type: "button", onClick: () => onSelectContext(context.id), children: [
@@ -5725,9 +5500,9 @@ function resolveBootstrapTarget(input) {
5725
5500
  }
5726
5501
 
5727
5502
  // src/components/AvatarArchitectApp.tsx
5728
- import { Fragment as Fragment11, jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
5729
- function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, requireWorkspaceSelection = false, serverBaseUrl, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
5730
- useEffect10(() => {
5503
+ import { Fragment as Fragment10, jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
5504
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, requireWorkspaceSelection = false, serverBaseUrl }) {
5505
+ useEffect9(() => {
5731
5506
  const id = "flow-styles";
5732
5507
  if (!document.getElementById(id)) {
5733
5508
  const style = document.createElement("style");
@@ -5737,7 +5512,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5737
5512
  }
5738
5513
  }, []);
5739
5514
  const [showStart, setShowStart] = useState25(true);
5740
- const [showSzenen, setShowSzenen] = useState25(false);
5741
5515
  const [layoutChoice, setLayoutChoice] = useState25(() => {
5742
5516
  try {
5743
5517
  return localStorage.getItem("aa-layout") || null;
@@ -5745,10 +5519,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5745
5519
  return null;
5746
5520
  }
5747
5521
  });
5748
- const [projectLoaded, setProjectLoaded] = useState25(false);
5749
5522
  const [hfToken, setHfToken] = useState25(initialHfToken || "");
5750
5523
  const [hfTokenInput, setHfTokenInput] = useState25(initialHfToken || "");
5751
- const [isLoadingFromHF, setIsLoadingFromHF] = useState25(false);
5752
5524
  const [hfNamespaceLocal, setHfNamespaceLocal] = useState25(() => {
5753
5525
  try {
5754
5526
  return normalizeSelectableNamespace(localStorage.getItem("aa-hf-namespace"));
@@ -5758,7 +5530,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5758
5530
  });
5759
5531
  const [hfNamespaceFromServer, setHfNamespaceFromServer] = useState25(null);
5760
5532
  const [serverProbeFailed, setServerProbeFailed] = useState25(false);
5761
- useEffect10(() => {
5533
+ useEffect9(() => {
5762
5534
  if (hfNamespace !== void 0 || !serverBaseUrl) return;
5763
5535
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
5764
5536
  if (typeof d.hfNamespace === "string" && d.hfNamespace) setHfNamespaceFromServer(d.hfNamespace);
@@ -5776,7 +5548,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5776
5548
  isStartDialogOpen: isDialogTarget && !targetConfirmed
5777
5549
  });
5778
5550
  const effectiveNamespace = bootstrapTarget.namespace ?? "";
5779
- const kontextRef = useRef11(null);
5551
+ const kontextRef = useRef10(null);
5780
5552
  const szenenPort = useMemo6(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
5781
5553
  generateText: onGeneratePrompt,
5782
5554
  token: hfToken,
@@ -5803,7 +5575,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5803
5575
  const [bootstrapLog, setBootstrapLog] = useState25([]);
5804
5576
  const [isBootstrapping, setIsBootstrapping] = useState25(false);
5805
5577
  const [hfMissingImages, setHfMissingImages] = useState25([]);
5806
- const syncTopSlot = /* @__PURE__ */ jsxs27(Fragment11, { children: [
5578
+ const syncTopSlot = /* @__PURE__ */ jsxs27(Fragment10, { children: [
5807
5579
  localOnlyCount > 0 && /* @__PURE__ */ jsxs27("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: [
5808
5580
  "\u26A0 ",
5809
5581
  localOnlyCount,
@@ -5852,7 +5624,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5852
5624
  bootstrapLog.length > 0 && /* @__PURE__ */ jsx29("div", { style: { marginTop: 6, fontFamily: "monospace", fontSize: 10, color: "#78716c", lineHeight: 1.6 }, children: bootstrapLog.map((l, i) => /* @__PURE__ */ jsx29("div", { children: l }, i)) })
5853
5625
  ] })
5854
5626
  ] });
5855
- const wsInputRef = useRef11(null);
5856
5627
  const startApp = (choice) => {
5857
5628
  try {
5858
5629
  localStorage.setItem("aa-layout", choice);
@@ -5866,11 +5637,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5866
5637
  const [edges, setEdges] = useState25([]);
5867
5638
  const [history, setHistory] = useState25([]);
5868
5639
  const [galleryItems, setGalleryItems] = useState25([]);
5869
- const galleryItemsRef = useRef11([]);
5870
- useEffect10(() => {
5640
+ const galleryItemsRef = useRef10([]);
5641
+ useEffect9(() => {
5871
5642
  galleryItemsRef.current = galleryItems;
5872
5643
  }, [galleryItems]);
5873
- const hfImageNotFoundRef = useRef11(/* @__PURE__ */ new Map());
5644
+ const hfImageNotFoundRef = useRef10(/* @__PURE__ */ new Map());
5874
5645
  const [galleryVisibleCount, setGalleryVisibleCount] = useState25(20);
5875
5646
  const [historyVisibleCount, setHistoryVisibleCount] = useState25(20);
5876
5647
  const loadThumbnailsForEntries = useCallback4(async (entries) => {
@@ -5892,7 +5663,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5892
5663
  }
5893
5664
  }
5894
5665
  }, [hfToken, effectiveNamespace]);
5895
- useEffect10(() => {
5666
+ useEffect9(() => {
5896
5667
  if (!hfState) return;
5897
5668
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
5898
5669
  const hfIds = new Set(hfState.metadata.map((m) => m.id));
@@ -5919,14 +5690,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5919
5690
  return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5920
5691
  });
5921
5692
  }, [hfState]);
5922
- useEffect10(() => {
5693
+ useEffect9(() => {
5923
5694
  if (!hfState) return;
5924
5695
  const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5925
5696
  const skeletons = sortedMeta.map((m) => ({ id: m.id, prompt: m.prompt, timestamp: m.timestamp, status: "done", nodeId: m.id, tags: m.tags || [] }));
5926
5697
  const repIds = new Set(groupByPrompt(skeletons).slice(0, galleryVisibleCount).map((g) => g.representative.id));
5927
5698
  loadThumbnailsForEntries(sortedMeta.filter((e) => repIds.has(e.id)));
5928
5699
  }, [hfState, galleryVisibleCount, loadThumbnailsForEntries]);
5929
- useEffect10(() => {
5700
+ useEffect9(() => {
5930
5701
  if (!hfState) return;
5931
5702
  const sortedMeta = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5932
5703
  loadThumbnailsForEntries(sortedMeta.slice(0, historyVisibleCount));
@@ -5987,11 +5758,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5987
5758
  }
5988
5759
  });
5989
5760
  const [isPromptCollapsed, setIsPromptCollapsed] = useState25(false);
5990
- const [projectActionState, setProjectActionState] = useState25("idle");
5991
- const syncServerDataRef = useRef11(null);
5992
5761
  const [workspaceTags, setWorkspaceTags] = useState25(null);
5993
- const [serverProjects, setServerProjects] = useState25([]);
5994
- const [isLoadingFromServer, setIsLoadingFromServer] = useState25(false);
5995
5762
  const [highContrast, setHighContrast] = useState25(() => {
5996
5763
  try {
5997
5764
  return localStorage.getItem("aa-contrast") === "high";
@@ -6006,9 +5773,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6006
5773
  const [isFullscreen, setIsFullscreen] = useState25(false);
6007
5774
  const [zoomScale, setZoomScale] = useState25(1);
6008
5775
  const [zoomOffset, setZoomOffset] = useState25({ x: 0, y: 0 });
6009
- const lastPinchDist = useRef11(null);
6010
- const lastTapTime = useRef11(0);
6011
- const dragStart = useRef11(null);
5776
+ const lastPinchDist = useRef10(null);
5777
+ const lastTapTime = useRef10(0);
5778
+ const dragStart = useRef10(null);
6012
5779
  const openFullscreen = () => {
6013
5780
  setIsFullscreen(true);
6014
5781
  setZoomScale(1);
@@ -6186,7 +5953,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6186
5953
  const group = groups.find((gr) => gr.prompt === currentResult.prompt);
6187
5954
  return group ? group.items : [];
6188
5955
  }, [galleryItems, currentResult?.prompt]);
6189
- useEffect10(() => {
5956
+ useEffect9(() => {
6190
5957
  if (!currentResult?.prompt || !hfToken || !effectiveNamespace || !hfState) return;
6191
5958
  const siblings = galleryItemsRef.current.filter(
6192
5959
  (g) => g.prompt === currentResult.prompt && !g.base64 && !hfImageNotFoundRef.current.has(g.id)
@@ -6439,51 +6206,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6439
6206
  console.error("Download Error", e);
6440
6207
  }
6441
6208
  };
6442
- const handleProjectExport = async () => {
6443
- setProjectActionState("working-full");
6444
- try {
6445
- const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id));
6446
- await onDownload(base64, "application/zip", `avatar_project_${getFormattedTimestamp()}.zip`);
6447
- setProjectActionState("done");
6448
- setTimeout(() => setProjectActionState("idle"), 3e3);
6449
- } catch {
6450
- setProjectActionState("error");
6451
- }
6452
- };
6453
- const handleProjectImport = async (input) => {
6454
- const file = input instanceof FileList ? input[0] : input;
6455
- if (!file) return;
6456
- setProjectActionState("working-full");
6457
- try {
6458
- const data = await importProjectFromZip(file);
6459
- if (data.nodes) setNodes(data.nodes);
6460
- if (data.edges) setEdges(data.edges);
6461
- if (data.history) {
6462
- setHistory(data.history);
6463
- if (data.history.length > 0) setCurrentResult(data.history[0]);
6464
- }
6465
- if (data.galleryItems) setGalleryItems(data.galleryItems);
6466
- if (data.settings) {
6467
- setAspectRatio(data.settings.aspectRatio || "1:1");
6468
- setSelectedModel(data.settings.selectedModel || "\u{1F34C} Nano Banana Pro");
6469
- setSeed(data.settings.seed || 0);
6470
- setSeedMode(data.settings.seedMode || "random");
6471
- }
6472
- if (data.workspaceTags?.by_category) setWorkspaceTags(data.workspaceTags);
6473
- if (data.recentLabItemIds?.length) {
6474
- const allGens = [...data.history || [], ...data.galleryItems || []];
6475
- const labItems = groupGenerationsToLabItems(allGens);
6476
- const recent = data.recentLabItemIds.map((id) => labItems.find((item) => item.id === id)).filter(Boolean);
6477
- setRecentLabItems(recent);
6478
- }
6479
- setProjectActionState("done");
6480
- setProjectLoaded(true);
6481
- setTimeout(() => setProjectActionState("idle"), 2e3);
6482
- } catch {
6483
- setProjectActionState("error");
6484
- setTimeout(() => setProjectActionState("idle"), 4e3);
6485
- }
6486
- };
6487
6209
  const handleWorkspaceImport = (file) => {
6488
6210
  const reader = new FileReader();
6489
6211
  reader.onload = (ev) => {
@@ -6505,37 +6227,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6505
6227
  };
6506
6228
  reader.readAsText(file);
6507
6229
  };
6508
- const fetchServerProjects = async () => {
6509
- if (!onFetchServerProjects) return;
6510
- try {
6511
- setServerProjects(await onFetchServerProjects());
6512
- } catch {
6513
- }
6514
- };
6515
- const handleServerSave = async (name) => {
6516
- if (!onServerSave) return;
6517
- setProjectActionState("working-full");
6518
- try {
6519
- const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id));
6520
- await onServerSave(base64, name || `avatar_project_${getFormattedTimestamp()}`);
6521
- await fetchServerProjects();
6522
- setProjectActionState("done");
6523
- setTimeout(() => setProjectActionState("idle"), 2e3);
6524
- } catch {
6525
- setProjectActionState("error");
6526
- setTimeout(() => setProjectActionState("idle"), 4e3);
6527
- }
6528
- };
6529
- const handleServerLoad = async (id) => {
6530
- if (!onServerLoad) return;
6531
- const file = await onServerLoad(id);
6532
- await handleProjectImport(file);
6533
- };
6534
- const handleServerDelete = async (id) => {
6535
- if (!onServerDelete) return;
6536
- await onServerDelete(id);
6537
- await fetchServerProjects();
6538
- };
6539
6230
  const handleHfInitialSync = async (onProgress) => {
6540
6231
  if (!hfToken || !effectiveNamespace) return;
6541
6232
  const existingIds = new Set((hfState?.metadata || []).map((m) => m.id));
@@ -6563,45 +6254,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6563
6254
  onProgress(done, total);
6564
6255
  }
6565
6256
  };
6566
- const handleComputeSyncDiff = async () => {
6567
- if (!onFetchServerProjects || !onServerLoad) throw new Error("Server nicht konfiguriert");
6568
- const projects = await onFetchServerProjects();
6569
- if (projects.length === 0) return { localOnly: [...history], serverOnly: [] };
6570
- const file = await onServerLoad(projects[0].id);
6571
- const serverData = await importProjectFromZip(file);
6572
- syncServerDataRef.current = serverData;
6573
- const localOnly = history.filter((h) => !(serverData.history || []).find((s) => s.id === h.id));
6574
- const serverOnly = (serverData.history || []).filter((s) => !history.find((h) => h.id === s.id));
6575
- return { localOnly, serverOnly };
6576
- };
6577
- const handleExecuteSync = async (selectedLocalIds) => {
6578
- const serverData = syncServerDataRef.current;
6579
- if (!serverData || !onServerSave) return;
6580
- setProjectActionState("working-full");
6581
- try {
6582
- const selectedLocal = history.filter((h) => selectedLocalIds.includes(h.id));
6583
- const mergedHistory = [...serverData.history || [], ...selectedLocal];
6584
- const { base64 } = await exportProjectToZip(
6585
- serverData.nodes || nodes,
6586
- serverData.edges || edges,
6587
- mergedHistory,
6588
- serverData.galleryItems || galleryItems,
6589
- serverData.settings || { aspectRatio, selectedModel, seed, seedMode },
6590
- serverData.workspaceTags || workspaceTags,
6591
- serverData.recentLabItemIds || recentLabItems.map((i) => i.id)
6592
- );
6593
- await onServerSave(base64, `sync_${getFormattedTimestamp()}`);
6594
- await fetchServerProjects();
6595
- setProjectActionState("done");
6596
- setTimeout(() => setProjectActionState("idle"), 2e3);
6597
- } catch {
6598
- setProjectActionState("error");
6599
- setTimeout(() => setProjectActionState("idle"), 4e3);
6600
- }
6601
- };
6602
- useEffect10(() => {
6603
- if (activeTab === "setup" || activeTab === "sync") fetchServerProjects();
6604
- }, [activeTab]);
6605
6257
  const mergeWorkspaceTags = (local, remote) => {
6606
6258
  if (!remote?.by_category) return local;
6607
6259
  const merged = {};
@@ -6654,7 +6306,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6654
6306
  setZoomScale(1);
6655
6307
  setZoomOffset({ x: 0, y: 0 });
6656
6308
  }, className: "absolute top-4 left-4 w-10 h-10 flex items-center justify-center rounded-full bg-black/70 border border-white/20 z-10", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[20px]", children: "zoom_out_map" }) }),
6657
- history.length > 1 && /* @__PURE__ */ jsxs27(Fragment11, { children: [
6309
+ history.length > 1 && /* @__PURE__ */ jsxs27(Fragment10, { children: [
6658
6310
  /* @__PURE__ */ jsx29("button", { onClick: () => {
6659
6311
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
6660
6312
  }, disabled: currentIndex <= 0, className: "absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center rounded-full bg-black/60 border border-white/10 disabled:opacity-0", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[22px]", children: "chevron_left" }) }),
@@ -6672,31 +6324,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6672
6324
  }
6673
6325
  );
6674
6326
  }
6675
- if (showSzenen && szenenPort) {
6676
- return /* @__PURE__ */ jsxs27("main", { className: "flow-szenen-shell", children: [
6677
- /* @__PURE__ */ jsxs27("header", { className: "flow-szenen-shell__header", children: [
6678
- /* @__PURE__ */ jsx29("button", { type: "button", onClick: () => setShowSzenen(false), children: "Zur\xFCck" }),
6679
- /* @__PURE__ */ jsxs27("div", { children: [
6680
- /* @__PURE__ */ jsx29("strong", { children: "Szenen" }),
6681
- /* @__PURE__ */ jsxs27("small", { children: [
6682
- effectiveNamespace,
6683
- " \xB7 User ",
6684
- tagBaseResult.selectedUserId,
6685
- " \xB7 Context ",
6686
- tagBaseResult.selectedContextId
6687
- ] })
6688
- ] })
6689
- ] }),
6690
- /* @__PURE__ */ jsx29("div", { className: "flow-szenen-shell__content", children: /* @__PURE__ */ jsx29(SzenenView, { port: szenenPort, contextId: szenenKontextId }) })
6691
- ] });
6692
- }
6693
6327
  if (showStart) {
6694
6328
  return /* @__PURE__ */ jsxs27("div", { className: "fixed inset-0 bg-[#0e0e0e] flex flex-col items-center justify-center p-6", style: { gap: 28, ...hcStyle }, children: [
6695
- /* @__PURE__ */ jsx29("input", { ref: wsInputRef, type: "file", accept: ".zip", className: "hidden", onChange: (e) => {
6696
- const f = e.target.files?.[0];
6697
- if (f) handleProjectImport(f);
6698
- e.target.value = "";
6699
- } }),
6700
6329
  /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center gap-1", children: [
6701
6330
  /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-white/15 text-[44px]", children: "palette" }),
6702
6331
  /* @__PURE__ */ jsx29("span", { className: "text-white/25 text-[10px] font-bold uppercase tracking-[0.25em]", children: "Avatar Architect" }),
@@ -6720,21 +6349,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6720
6349
  ]
6721
6350
  }
6722
6351
  ),
6723
- /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
6724
- /* @__PURE__ */ jsxs27(
6725
- "button",
6726
- {
6727
- onClick: () => wsInputRef.current?.click(),
6728
- className: "w-full flex items-center justify-center gap-3 rounded-2xl font-bold text-[14px] uppercase tracking-wide text-white active:scale-95 transition-transform",
6729
- style: { height: 56, background: projectLoaded ? "#16a34a" : "#0284c7" },
6730
- children: [
6731
- /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[22px]", children: projectLoaded ? "check_circle" : "folder_zip" }),
6732
- projectLoaded ? "Projekt geladen \u2713" : "Projekt laden (.zip)"
6733
- ]
6734
- }
6735
- ),
6736
- !projectLoaded && /* @__PURE__ */ jsx29("span", { className: "text-white/20 text-[10px] text-center", children: "Baum, Bilder und Einstellungen wiederherstellen" })
6737
- ] }),
6738
6352
  /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
6739
6353
  !initialHfToken && /* @__PURE__ */ jsxs27("div", { className: "flex gap-2 w-full", children: [
6740
6354
  /* @__PURE__ */ jsx29(
@@ -6785,79 +6399,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6785
6399
  },
6786
6400
  ns
6787
6401
  ))
6788
- ] }),
6789
- hfToken && /* @__PURE__ */ jsxs27(
6790
- "button",
6791
- {
6792
- disabled: isLoadingFromHF,
6793
- onClick: async () => {
6794
- setIsLoadingFromHF(true);
6795
- try {
6796
- const { hfListProjects: hfListProjects2, hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-W7KO2FOH.mjs");
6797
- const projects = await hfListProjects2(hfToken);
6798
- if (projects.length > 0) {
6799
- const file = await hfDownloadProject2(projects[0].path, hfToken);
6800
- await handleProjectImport(file);
6801
- }
6802
- } catch (e) {
6803
- alert("HF Fehler: " + e.message);
6804
- } finally {
6805
- setIsLoadingFromHF(false);
6806
- }
6807
- },
6808
- className: "w-full flex items-center justify-center gap-3 rounded-2xl font-bold text-[14px] uppercase tracking-wide text-white active:scale-95 transition-transform disabled:opacity-50",
6809
- style: { height: 56, background: "#f59e0b" },
6810
- children: [
6811
- /* @__PURE__ */ jsx29("span", { className: `material-symbols-outlined text-[22px]${isLoadingFromHF ? " animate-spin" : ""}`, children: isLoadingFromHF ? "sync" : "cloud_download" }),
6812
- isLoadingFromHF ? "Laden\u2026" : "Von HF laden"
6813
- ]
6814
- }
6815
- ),
6816
- hfToken && /* @__PURE__ */ jsx29("span", { className: "text-white/20 text-[10px] text-center", children: "Letzten Stand von Hugging Face laden" })
6817
- ] }),
6818
- onFetchServerProjects && /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
6819
- /* @__PURE__ */ jsxs27(
6820
- "button",
6821
- {
6822
- disabled: isLoadingFromServer,
6823
- onClick: async () => {
6824
- setIsLoadingFromServer(true);
6825
- try {
6826
- const projects = await onFetchServerProjects();
6827
- setServerProjects(projects);
6828
- if (projects.length > 0 && onServerLoad) {
6829
- const file = await onServerLoad(projects[0].id);
6830
- await handleProjectImport(file);
6831
- }
6832
- } catch {
6833
- } finally {
6834
- setIsLoadingFromServer(false);
6835
- }
6836
- },
6837
- className: "w-full flex items-center justify-center gap-3 rounded-2xl font-bold text-[14px] uppercase tracking-wide text-white active:scale-95 transition-transform disabled:opacity-50",
6838
- style: { height: 56, background: "#7c3aed" },
6839
- children: [
6840
- /* @__PURE__ */ jsx29("span", { className: `material-symbols-outlined text-[22px]${isLoadingFromServer ? " animate-spin" : ""}`, children: isLoadingFromServer ? "sync" : "cloud_download" }),
6841
- isLoadingFromServer ? "Laden\u2026" : "Vom Server laden"
6842
- ]
6843
- }
6844
- ),
6845
- /* @__PURE__ */ jsx29("span", { className: "text-white/20 text-[10px] text-center", children: "Letzten Stand vom Server wiederherstellen" })
6846
- ] }),
6847
- /* @__PURE__ */ jsxs27("div", { className: "flow-szenen-entry", children: [
6848
- /* @__PURE__ */ jsxs27(
6849
- "button",
6850
- {
6851
- type: "button",
6852
- disabled: !szenenPort || !bootstrapTarget.ready,
6853
- onClick: () => setShowSzenen(true),
6854
- children: [
6855
- /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined", children: "movie" }),
6856
- "Szenen \xF6ffnen"
6857
- ]
6858
- }
6859
- ),
6860
- !szenenPort && /* @__PURE__ */ jsx29("small", { children: "F\xFCr Szenen zuerst den HF-Token setzen." })
6402
+ ] })
6861
6403
  ] }),
6862
6404
  /* @__PURE__ */ jsxs27("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
6863
6405
  /* @__PURE__ */ jsx29("span", { className: "text-white/25 text-[10px] uppercase tracking-widest font-bold", children: "Layout w\xE4hlen & starten" }),
@@ -6926,6 +6468,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6926
6468
  );
6927
6469
  }
6928
6470
  }
6471
+ const szenenPane = szenenPort ? /* @__PURE__ */ jsx29("div", { className: "flow-szenen-pane", children: /* @__PURE__ */ jsx29(SzenenView, { port: szenenPort, contextId: szenenKontextId }) }) : /* @__PURE__ */ jsx29("div", { className: "flow-szenen-pane flow-szenen-pane--empty", children: /* @__PURE__ */ jsxs27("p", { children: [
6472
+ "Szenen brauchen einen HF-Token f\xFCr Namespace ",
6473
+ effectiveNamespace || "(noch nicht bestimmt)",
6474
+ " \u2014 auf der Flow-Startseite setzen."
6475
+ ] }) });
6929
6476
  if (layoutChoice === "mobile" || layoutChoice === "mobile-desktop") {
6930
6477
  const mdMode = layoutChoice === "mobile-desktop";
6931
6478
  const mdScale = mdMode ? window.innerWidth / 430 : 1;
@@ -6957,6 +6504,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6957
6504
  onTagMove: handleTagMove
6958
6505
  }
6959
6506
  ) }),
6507
+ mobileTab === "szenen" && szenenPane,
6960
6508
  mobileTab === "stage" && /* @__PURE__ */ jsxs27("div", { className: "flex flex-col flex-1 min-h-0", children: [
6961
6509
  /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2 px-3 border-b border-white/5 bg-black/30 shrink-0", style: { height: 52 }, children: [
6962
6510
  /* @__PURE__ */ jsx29(CompactDropdown, { value: aspectRatio, onChange: setAspectRatio, options: [{ label: "1:1", value: "1:1" }, { label: "16:9", value: "16:9" }, { label: "9:16", value: "9:16" }] }),
@@ -7029,7 +6577,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7029
6577
  /* @__PURE__ */ jsx29("span", { className: "text-[11px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
7030
6578
  ] }),
7031
6579
  currentResult?.status === "done" && /* @__PURE__ */ jsx29("button", { onClick: openFullscreen, className: "absolute top-2 right-2 w-8 h-8 flex items-center justify-center rounded-full bg-black/60 border border-white/10 z-10", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[18px]", children: "fullscreen" }) }),
7032
- history.length > 1 && currentResult && /* @__PURE__ */ jsxs27(Fragment11, { children: [
6580
+ history.length > 1 && currentResult && /* @__PURE__ */ jsxs27(Fragment10, { children: [
7033
6581
  /* @__PURE__ */ jsx29("button", { onClick: goToPrev, disabled: currentIndex <= 0, className: "absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center rounded-full bg-black/60 border border-white/10 disabled:opacity-0 transition-opacity", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[22px]", children: "chevron_left" }) }),
7034
6582
  /* @__PURE__ */ jsx29("button", { onClick: goToNext, disabled: currentIndex >= history.length - 1, className: "absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center rounded-full bg-black/60 border border-white/10 disabled:opacity-0 transition-opacity", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[22px]", children: "chevron_right" }) }),
7035
6583
  /* @__PURE__ */ jsxs27("div", { className: "absolute bottom-2 left-1/2 -translate-x-1/2 bg-black/60 rounded-full px-3 py-0.5 text-[10px] text-white/40 font-mono", children: [
@@ -7200,26 +6748,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7200
6748
  }, onTagCreate: handleTagCreate, onTagUpdate: handleTagUpdate, onTagDelete: handleTagDelete, onScanImage: handleScanImage, isScanning: isScanningImage }) }),
7201
6749
  activeTab === "setup" && /* @__PURE__ */ jsx29(SetupPanel, { onWorkspaceImport: handleWorkspaceImport, buildInfo, tagBase: tagBaseResult }),
7202
6750
  activeTab === "sync" && /* @__PURE__ */ jsx29(
7203
- ProjectSyncTab,
6751
+ HfMediaSyncTab,
7204
6752
  {
7205
6753
  topSlot: syncTopSlot,
7206
- onProjectExport: handleProjectExport,
7207
- onProjectImport: (f) => handleProjectImport(f),
7208
- onWorkspaceImport: handleWorkspaceImport,
7209
- projectActionState,
7210
- serverProjects: onFetchServerProjects ? serverProjects : void 0,
7211
- onServerSave: onServerSave ? handleServerSave : void 0,
7212
- onServerLoad: onServerLoad ? handleServerLoad : void 0,
7213
- onServerDelete: onServerDelete ? handleServerDelete : void 0,
7214
- onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
7215
- onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
7216
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
7217
- hfToken: hfToken || void 0,
7218
- onHfLoad: (f) => handleProjectImport(f),
7219
- onProjectExportBase64: async () => {
7220
- const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id));
7221
- return base64;
7222
- },
7223
6754
  onHfInitialSync: hfToken ? handleHfInitialSync : void 0
7224
6755
  }
7225
6756
  ),
@@ -7257,7 +6788,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7257
6788
  ...workspaceTags ? [{ id: "tags", icon: "label", label: "Tags" }] : [],
7258
6789
  { id: "browse", icon: "photo_library", label: "Galerie" },
7259
6790
  ...serverBaseUrl ? [{ id: "server", icon: "storage", label: "Server" }] : []
7260
- ].map((tab) => /* @__PURE__ */ jsxs27("button", { onClick: () => tab.id === "szenen" ? setShowSzenen(true) : setMobileTab(tab.id), className: `flex-none min-w-[64px] flex flex-col items-center justify-center gap-0.5 transition-colors ${mobileTab === tab.id ? "text-white" : "text-white/30"}`, children: [
6791
+ ].map((tab) => /* @__PURE__ */ jsxs27("button", { onClick: () => setMobileTab(tab.id), className: `flex-none min-w-[64px] flex flex-col items-center justify-center gap-0.5 transition-colors ${mobileTab === tab.id ? "text-white" : "text-white/30"}`, children: [
7261
6792
  /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[24px]", children: tab.icon }),
7262
6793
  /* @__PURE__ */ jsx29("span", { className: "text-[10px] font-bold uppercase tracking-wide", children: tab.label })
7263
6794
  ] }, tab.id)) })
@@ -7338,7 +6869,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7338
6869
  /* @__PURE__ */ jsx29("span", { style: { fontSize: 11, fontWeight: "bold", textTransform: "uppercase", letterSpacing: "0.2em" }, children: "Avatar Architect" })
7339
6870
  ] }),
7340
6871
  currentResult?.status === "done" && /* @__PURE__ */ jsx29("button", { onClick: openFullscreen, style: { position: "absolute", top: 8, right: 8, width: 32, height: 32, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "50%", background: "rgba(0,0,0,0.6)", border: "1px solid rgba(255,255,255,0.1)", cursor: "pointer", color: "#fff" }, children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined", style: { fontSize: 18 }, children: "fullscreen" }) }),
7341
- history.length > 1 && currentResult && /* @__PURE__ */ jsxs27(Fragment11, { children: [
6872
+ history.length > 1 && currentResult && /* @__PURE__ */ jsxs27(Fragment10, { children: [
7342
6873
  /* @__PURE__ */ jsx29("button", { onClick: goToPrev, disabled: currentIndex <= 0, style: { position: "absolute", left: 8, top: "50%", transform: "translateY(-50%)", width: 36, height: 36, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "50%", background: "rgba(0,0,0,0.6)", border: "1px solid rgba(255,255,255,0.1)", cursor: "pointer", color: "#fff", opacity: currentIndex <= 0 ? 0 : 1, transition: "opacity 0.2s" }, children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined", style: { fontSize: 20 }, children: "chevron_left" }) }),
7343
6874
  /* @__PURE__ */ jsx29("button", { onClick: goToNext, disabled: currentIndex >= history.length - 1, style: { position: "absolute", right: 8, top: "50%", transform: "translateY(-50%)", width: 36, height: 36, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "50%", background: "rgba(0,0,0,0.6)", border: "1px solid rgba(255,255,255,0.1)", cursor: "pointer", color: "#fff", opacity: currentIndex >= history.length - 1 ? 0 : 1, transition: "opacity 0.2s" }, children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined", style: { fontSize: 20 }, children: "chevron_right" }) }),
7344
6875
  /* @__PURE__ */ jsxs27("div", { style: { position: "absolute", bottom: 8, left: "50%", transform: "translateX(-50%)", background: "rgba(0,0,0,0.6)", borderRadius: 999, padding: "2px 12px", fontSize: 10, color: "rgba(255,255,255,0.4)", fontFamily: "monospace" }, children: [
@@ -7452,6 +6983,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7452
6983
  className: `px-3 h-7 rounded-lg text-[9px] font-bold uppercase tracking-wide transition-colors ${middlePanel === "labs" ? "bg-white/10 text-white" : "text-white/30 hover:text-white/50"}`,
7453
6984
  children: "Labs"
7454
6985
  }
6986
+ ),
6987
+ /* @__PURE__ */ jsx29(
6988
+ "button",
6989
+ {
6990
+ onClick: () => setMiddlePanel("szenen"),
6991
+ className: `px-3 h-7 rounded-lg text-[9px] font-bold uppercase tracking-wide transition-colors ${middlePanel === "szenen" ? "bg-white/10 text-white" : "text-white/30 hover:text-white/50"}`,
6992
+ children: "Szenen"
6993
+ }
7455
6994
  )
7456
6995
  ] }),
7457
6996
  /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2", children: [
@@ -7474,7 +7013,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7474
7013
  /* @__PURE__ */ jsx29("textarea", { value: activePrompt, onChange: (e) => setActivePrompt(e.target.value), className: "w-full bg-transparent border-none outline-none text-[12px] leading-relaxed text-white/80 resize-none h-20 dark-scrollbar", placeholder: "W\xE4hle einen Knoten oder tippe einen Prompt..." }),
7475
7014
  activePrompt && !isSynthesizing && /* @__PURE__ */ jsx29("button", { onClick: () => setActivePrompt(""), className: "absolute top-2 right-2 w-6 h-6 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center transition-colors text-white/20 hover:text-white", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[14px]", children: "close" }) })
7476
7015
  ] }) }),
7477
- middlePanel === "labs" ? /* @__PURE__ */ jsx29("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx29(LabsTab, { services: labServices, onResult: (item) => {
7016
+ middlePanel === "szenen" ? szenenPane : middlePanel === "labs" ? /* @__PURE__ */ jsx29("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsx29(LabsTab, { services: labServices, onResult: (item) => {
7478
7017
  const frame = item.frames[0];
7479
7018
  if (frame?.base64) setCurrentResult(frameToGeneration(frame, item));
7480
7019
  } }) }) : /* @__PURE__ */ jsxs27("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
@@ -7559,8 +7098,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7559
7098
  serverBaseUrl && /* @__PURE__ */ jsx29("button", { onClick: () => {
7560
7099
  setActiveTab("server");
7561
7100
  setIsRightCollapsed(false);
7562
- }, className: `w-10 flex items-center justify-center relative transition-colors ${activeTab === "server" ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[20px]", children: "storage" }) }),
7563
- /* @__PURE__ */ jsx29("button", { onClick: () => setShowSzenen(true), className: "w-10 flex items-center justify-center relative text-white/20 hover:text-white", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[20px]", children: "movie" }) })
7101
+ }, className: `w-10 flex items-center justify-center relative transition-colors ${activeTab === "server" ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[20px]", children: "storage" }) })
7564
7102
  ] }),
7565
7103
  hfToken && /* @__PURE__ */ jsx29("button", { onClick: () => refreshHF(), disabled: isHfRefreshing, className: "w-10 flex items-center justify-center text-white/20 hover:text-white/60 transition-colors disabled:opacity-30", children: /* @__PURE__ */ jsx29("span", { className: `material-symbols-outlined text-[18px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) }),
7566
7104
  /* @__PURE__ */ jsx29("button", { onClick: () => setIsRightCollapsed(!isRightCollapsed), className: "w-10 flex items-center justify-center text-white/20 hover:text-white", children: /* @__PURE__ */ jsx29("span", { className: "material-symbols-outlined text-[18px]", children: isRightCollapsed ? "chevron_left" : "chevron_right" }) })
@@ -7601,26 +7139,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7601
7139
  activeTab === "inspect" && /* @__PURE__ */ jsx29(InspectPanel, { currentResult, history, onSelect: setCurrentResult }),
7602
7140
  activeTab === "setup" && /* @__PURE__ */ jsx29(SetupPanel, { onWorkspaceImport: handleWorkspaceImport, buildInfo, tagBase: tagBaseResult }),
7603
7141
  activeTab === "sync" && /* @__PURE__ */ jsx29(
7604
- ProjectSyncTab,
7142
+ HfMediaSyncTab,
7605
7143
  {
7606
7144
  topSlot: syncTopSlot,
7607
- onProjectExport: handleProjectExport,
7608
- onProjectImport: (f) => handleProjectImport(f),
7609
- onWorkspaceImport: handleWorkspaceImport,
7610
- projectActionState,
7611
- serverProjects: onFetchServerProjects ? serverProjects : void 0,
7612
- onServerSave: onServerSave ? handleServerSave : void 0,
7613
- onServerLoad: onServerLoad ? handleServerLoad : void 0,
7614
- onServerDelete: onServerDelete ? handleServerDelete : void 0,
7615
- onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
7616
- onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
7617
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
7618
- hfToken: hfToken || void 0,
7619
- onHfLoad: (f) => handleProjectImport(f),
7620
- onProjectExportBase64: async () => {
7621
- const { base64 } = await import("./project-O4ORKXY5.mjs").then((m) => m.exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id)));
7622
- return base64;
7623
- },
7624
7145
  onHfInitialSync: hfToken ? handleHfInitialSync : void 0
7625
7146
  }
7626
7147
  ),
@@ -7631,7 +7152,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
7631
7152
  }
7632
7153
 
7633
7154
  // src/components/FaApp.tsx
7634
- import { useState as useState26, useEffect as useEffect11, useMemo as useMemo7 } from "react";
7155
+ import { useState as useState26, useEffect as useEffect10, useMemo as useMemo7 } from "react";
7635
7156
  import { jsx as jsx30 } from "react/jsx-runtime";
7636
7157
  function FaApp({
7637
7158
  onGenerateImage,
@@ -7645,14 +7166,10 @@ function FaApp({
7645
7166
  libToken,
7646
7167
  allowDevNamespace,
7647
7168
  serverBaseUrl,
7648
- onFetchServerProjects,
7649
- onServerSave,
7650
- onServerLoad,
7651
- onServerDelete,
7652
7169
  buildInfo
7653
7170
  }) {
7654
7171
  const [hfNamespace, setHfNamespace] = useState26(void 0);
7655
- useEffect11(() => {
7172
+ useEffect10(() => {
7656
7173
  if (!serverBaseUrl) return;
7657
7174
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
7658
7175
  if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
@@ -7675,10 +7192,6 @@ function FaApp({
7675
7192
  allowDevNamespace: !hfNamespace && allowDevNamespace,
7676
7193
  requireWorkspaceSelection: true,
7677
7194
  serverBaseUrl,
7678
- onFetchServerProjects,
7679
- onServerSave,
7680
- onServerLoad,
7681
- onServerDelete,
7682
7195
  buildInfo
7683
7196
  }
7684
7197
  );
@@ -8252,6 +7765,7 @@ export {
8252
7765
  FaToolsBadge,
8253
7766
  FlowWorkspacePicker,
8254
7767
  GLOBAL_STYLES,
7768
+ HfMediaSyncTab,
8255
7769
  HistoryPanel,
8256
7770
  InspectPanel,
8257
7771
  LIBRARY_CUTOVER_MARKER,
@@ -8268,7 +7782,6 @@ export {
8268
7782
  ListView,
8269
7783
  MediaLibrary,
8270
7784
  PillButton,
8271
- ProjectSyncTab,
8272
7785
  PromptTab,
8273
7786
  SCOPE_MAP,
8274
7787
  SectionLabel,
@@ -8302,7 +7815,6 @@ export {
8302
7815
  createTagsDefault,
8303
7816
  deriveTagBaseWorkspaceTags,
8304
7817
  deriveTagViewModel,
8305
- exportProjectToZip,
8306
7818
  formatTreeToMarkdown,
8307
7819
  frameToGeneration,
8308
7820
  getFormattedTimestamp,
@@ -8311,15 +7823,10 @@ export {
8311
7823
  groupGenerationsToLabItems,
8312
7824
  hfBatchArchive,
8313
7825
  hfBootstrapFromLegacy,
8314
- hfDeleteProject,
8315
- hfDownloadProject,
8316
7826
  hfListDir,
8317
- hfListProjects,
8318
7827
  hfLoadImageAsBase64,
8319
7828
  hfUploadImage,
8320
- hfUploadProjectForm,
8321
7829
  hfUploadSmallFile,
8322
- importProjectFromZip,
8323
7830
  injectXMPMetadata,
8324
7831
  interpretSdkError,
8325
7832
  libraryContract,