@norskvideo/norsk-studio-built-ins 1.27.0-2026-01-10-23683704 → 1.27.0-2026-01-15-ecb87e67

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.
Files changed (34) hide show
  1. package/client/info.js +1262 -404
  2. package/lib/input.srt-listener/_gen/types.d.ts +1 -0
  3. package/lib/input.srt-listener/_gen/yaml-docs.js +3 -0
  4. package/lib/input.srt-listener/_gen/yaml-docs.js.map +1 -1
  5. package/lib/input.srt-listener/_gen/zod.js +1 -0
  6. package/lib/input.srt-listener/_gen/zod.js.map +1 -1
  7. package/lib/input.srt-listener/info.d.ts +1 -0
  8. package/lib/input.srt-listener/info.js +9 -0
  9. package/lib/input.srt-listener/info.js.map +1 -1
  10. package/lib/input.srt-listener/runtime.d.ts +1 -0
  11. package/lib/input.srt-listener/runtime.js +7 -0
  12. package/lib/input.srt-listener/runtime.js.map +1 -1
  13. package/lib/input.srt-listener/types.yaml +2 -0
  14. package/lib/processor.browserOverlay/runtime.js +1 -1
  15. package/lib/processor.browserOverlay/runtime.js.map +1 -1
  16. package/lib/processor.onscreenGraphic/runtime.js +1 -1
  17. package/lib/processor.onscreenGraphic/runtime.js.map +1 -1
  18. package/lib/processor.smartSourceSwitch/inline-view.d.ts +3 -5
  19. package/lib/processor.smartSourceSwitch/inline-view.js +16 -4
  20. package/lib/processor.smartSourceSwitch/inline-view.js.map +1 -1
  21. package/lib/processor.videoCompose/fullscreen.js +28 -10
  22. package/lib/processor.videoCompose/fullscreen.js.map +1 -1
  23. package/lib/processor.videoCompose/preset-transition-panel.js +37 -8
  24. package/lib/processor.videoCompose/preset-transition-panel.js.map +1 -1
  25. package/lib/processor.videoCompose/presets.d.ts +8 -3
  26. package/lib/processor.videoCompose/presets.js +1215 -346
  27. package/lib/processor.videoCompose/presets.js.map +1 -1
  28. package/lib/processor.videoCompose/runtime.js +58 -44
  29. package/lib/processor.videoCompose/runtime.js.map +1 -1
  30. package/lib/processor.videoCompose/visual-preview.js +3 -1
  31. package/lib/processor.videoCompose/visual-preview.js.map +1 -1
  32. package/lib/processor.zoomTo/runtime.js +1 -1
  33. package/lib/processor.zoomTo/runtime.js.map +1 -1
  34. package/package.json +3 -3
package/client/info.js CHANGED
@@ -25992,7 +25992,7 @@ var require_dist6 = __commonJS({
25992
25992
  unstable_createCollection: () => createCollection2
25993
25993
  });
25994
25994
  module.exports = __toCommonJS2(index_exports);
25995
- var import_react33 = __toESM2(require_react());
25995
+ var import_react34 = __toESM2(require_react());
25996
25996
  var import_react_context = require_dist3();
25997
25997
  var import_react_compose_refs = require_dist4();
25998
25998
  var import_react_slot = require_dist5();
@@ -26006,14 +26006,14 @@ var require_dist6 = __commonJS({
26006
26006
  );
26007
26007
  const CollectionProvider = (props) => {
26008
26008
  const { scope, children } = props;
26009
- const ref = import_react33.default.useRef(null);
26010
- const itemMap = import_react33.default.useRef(/* @__PURE__ */ new Map()).current;
26009
+ const ref = import_react34.default.useRef(null);
26010
+ const itemMap = import_react34.default.useRef(/* @__PURE__ */ new Map()).current;
26011
26011
  return /* @__PURE__ */ (0, import_jsx_runtime75.jsx)(CollectionProviderImpl, { scope, itemMap, collectionRef: ref, children });
26012
26012
  };
26013
26013
  CollectionProvider.displayName = PROVIDER_NAME;
26014
26014
  const COLLECTION_SLOT_NAME = name + "CollectionSlot";
26015
26015
  const CollectionSlotImpl = (0, import_react_slot.createSlot)(COLLECTION_SLOT_NAME);
26016
- const CollectionSlot = import_react33.default.forwardRef(
26016
+ const CollectionSlot = import_react34.default.forwardRef(
26017
26017
  (props, forwardedRef) => {
26018
26018
  const { scope, children } = props;
26019
26019
  const context = useCollectionContext(COLLECTION_SLOT_NAME, scope);
@@ -26025,13 +26025,13 @@ var require_dist6 = __commonJS({
26025
26025
  const ITEM_SLOT_NAME = name + "CollectionItemSlot";
26026
26026
  const ITEM_DATA_ATTR = "data-radix-collection-item";
26027
26027
  const CollectionItemSlotImpl = (0, import_react_slot.createSlot)(ITEM_SLOT_NAME);
26028
- const CollectionItemSlot = import_react33.default.forwardRef(
26028
+ const CollectionItemSlot = import_react34.default.forwardRef(
26029
26029
  (props, forwardedRef) => {
26030
26030
  const { scope, children, ...itemData } = props;
26031
- const ref = import_react33.default.useRef(null);
26031
+ const ref = import_react34.default.useRef(null);
26032
26032
  const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, ref);
26033
26033
  const context = useCollectionContext(ITEM_SLOT_NAME, scope);
26034
- import_react33.default.useEffect(() => {
26034
+ import_react34.default.useEffect(() => {
26035
26035
  context.itemMap.set(ref, { ref, ...itemData });
26036
26036
  return () => void context.itemMap.delete(ref);
26037
26037
  });
@@ -26041,7 +26041,7 @@ var require_dist6 = __commonJS({
26041
26041
  CollectionItemSlot.displayName = ITEM_SLOT_NAME;
26042
26042
  function useCollection(scope) {
26043
26043
  const context = useCollectionContext(name + "CollectionConsumer", scope);
26044
- const getItems = import_react33.default.useCallback(() => {
26044
+ const getItems = import_react34.default.useCallback(() => {
26045
26045
  const collectionNode = context.collectionRef.current;
26046
26046
  if (!collectionNode) return [];
26047
26047
  const orderedNodes = Array.from(collectionNode.querySelectorAll(`[${ITEM_DATA_ATTR}]`));
@@ -62393,6 +62393,15 @@ function info_default6({ defineComponent, mappingsToStreams: mappingsToStreams2,
62393
62393
  global: unique("sourceName")
62394
62394
  }
62395
62395
  },
62396
+ burstProtection: {
62397
+ advanced: true,
62398
+ help: "Drop data until input is completely stable",
62399
+ hint: {
62400
+ defaultValue: false,
62401
+ type: "boolean",
62402
+ optional: true
62403
+ }
62404
+ },
62396
62405
  decodeOutputs: (0, import_client_types3.DecodeOutputsForm)(),
62397
62406
  streamMappings: StreamMappingForm(defaultStreamMapping3, {
62398
62407
  sourceNames: (cfg) => cfg.streamIds ?? []
@@ -73377,13 +73386,17 @@ var import_jsx_runtime55 = __toESM(require_jsx_runtime());
73377
73386
  var import_FaTimes = __toESM(require_FaTimes());
73378
73387
  var import_FaPlay2 = __toESM(require_FaPlay());
73379
73388
  var import_FaCircle = __toESM(require_FaCircle());
73380
- function InlineView18({ state, config }) {
73389
+ var import_react25 = __toESM(require_react());
73390
+ function InlineView18({ state, config, sendCommand }) {
73381
73391
  const priorityOrder = state.sourcePriority || config.sources;
73382
- return (0, import_jsx_runtime55.jsxs)(import_jsx_runtime55.Fragment, { children: [(0, import_jsx_runtime55.jsx)("h5", { className: "text-gray-900 dark:text-white font-medium", children: "Sources:" }), (0, import_jsx_runtime55.jsxs)("ul", { className: "space-y-2 mt-2", children: [config.sources.map((s, i) => {
73392
+ const handlePriorityChange = (0, import_react25.useCallback)((newOrder) => {
73393
+ sendCommand({ type: "set-priority-order", sources: newOrder });
73394
+ }, [sendCommand]);
73395
+ return (0, import_jsx_runtime55.jsxs)(import_jsx_runtime55.Fragment, { children: [(0, import_jsx_runtime55.jsx)("h5", { className: "text-gray-900 dark:text-white font-medium", children: "Sources:" }), (0, import_jsx_runtime55.jsxs)("ul", { className: "space-y-2 mt-2", children: [priorityOrder.map((s, index3) => {
73383
73396
  const isActive = state.activeSource === s;
73384
73397
  const isAvailable = state.availableSources.includes(s);
73385
73398
  const isOffline = !isActive && !isAvailable;
73386
- const priorityRank = priorityOrder.indexOf(s) + 1;
73399
+ const priorityRank = index3 + 1;
73387
73400
  return (0, import_jsx_runtime55.jsxs)("li", { className: "flex items-center gap-2 w-full", children: [(0, import_jsx_runtime55.jsx)("div", { className: "relative w-4 h-4 flex items-center justify-center flex-shrink-0", children: isOffline ? (
73388
73401
  // Red X for offline sources
73389
73402
  (0, import_jsx_runtime55.jsx)(import_FaTimes.FaTimes, { className: "w-4 h-4 text-red-500" })
@@ -73393,7 +73406,15 @@ function InlineView18({ state, config }) {
73393
73406
  ) : (
73394
73407
  // Green circle for available but not active
73395
73408
  (0, import_jsx_runtime55.jsx)(import_FaCircle.FaCircle, { className: "w-2 h-2 text-green-500" })
73396
- ) }), (0, import_jsx_runtime55.jsx)("span", { className: "text-gray-900 dark:text-white flex-1", children: s }), (0, import_jsx_runtime55.jsx)("span", { className: "text-gray-500 dark:text-gray-400 text-sm", children: priorityRank })] }, i);
73409
+ ) }), (0, import_jsx_runtime55.jsx)("span", { className: "text-gray-900 dark:text-white flex-1", children: s }), (0, import_jsx_runtime55.jsxs)("div", { className: "flex items-center gap-1", children: [index3 > 0 && (0, import_jsx_runtime55.jsx)("button", { onClick: () => {
73410
+ const newOrder = [...priorityOrder];
73411
+ [newOrder[index3], newOrder[index3 - 1]] = [newOrder[index3 - 1], newOrder[index3]];
73412
+ handlePriorityChange(newOrder);
73413
+ }, className: "w-4 h-4 flex items-center justify-center text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", title: "Move up", children: (0, import_jsx_runtime55.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 2, stroke: "currentColor", className: "w-3 h-3", children: (0, import_jsx_runtime55.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 15.75l7.5-7.5 7.5 7.5" }) }) }), index3 < priorityOrder.length - 1 && (0, import_jsx_runtime55.jsx)("button", { onClick: () => {
73414
+ const newOrder = [...priorityOrder];
73415
+ [newOrder[index3], newOrder[index3 + 1]] = [newOrder[index3 + 1], newOrder[index3]];
73416
+ handlePriorityChange(newOrder);
73417
+ }, className: "w-4 h-4 flex items-center justify-center text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200", title: "Move down", children: (0, import_jsx_runtime55.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 2, stroke: "currentColor", className: "w-3 h-3", children: (0, import_jsx_runtime55.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) }) }), (0, import_jsx_runtime55.jsx)("span", { className: "text-gray-500 dark:text-gray-400 text-sm w-4 text-center", children: priorityRank })] })] }, s);
73397
73418
  }), (0, import_jsx_runtime55.jsxs)("li", { className: "flex items-center gap-2", children: [(0, import_jsx_runtime55.jsx)("div", { className: "relative w-4 h-4 flex items-center justify-center", children: state.activeSource === "fallback" ? (
73398
73419
  // Green play button for active fallback
73399
73420
  (0, import_jsx_runtime55.jsxs)(import_jsx_runtime55.Fragment, { children: [(0, import_jsx_runtime55.jsx)(import_FaPlay2.FaPlay, { className: "w-3 h-3 text-green-500" }), (0, import_jsx_runtime55.jsx)("div", { className: "absolute inset-0 flex items-center justify-center opacity-30", children: (0, import_jsx_runtime55.jsx)("div", { className: "w-4 h-4 bg-green-500 animate-ping duration-150" }) })] })
@@ -73406,12 +73427,12 @@ var inline_view_default18 = InlineView18;
73406
73427
 
73407
73428
  // build/client/processor.smartSourceSwitch/fullscreen.js
73408
73429
  var import_jsx_runtime56 = __toESM(require_jsx_runtime());
73409
- var import_react25 = __toESM(require_react());
73430
+ var import_react26 = __toESM(require_react());
73410
73431
  function FullscreenView2({ state, config, sendCommand }) {
73411
- const handleMakeActive = (0, import_react25.useCallback)((source) => {
73432
+ const handleMakeActive = (0, import_react26.useCallback)((source) => {
73412
73433
  sendCommand({ type: "make-source-active", source });
73413
73434
  }, [sendCommand]);
73414
- const handlePriorityChange = (0, import_react25.useCallback)((newOrder) => {
73435
+ const handlePriorityChange = (0, import_react26.useCallback)((newOrder) => {
73415
73436
  sendCommand({ type: "set-priority-order", sources: newOrder });
73416
73437
  }, [sendCommand]);
73417
73438
  const displaySources = config.sources;
@@ -73776,9 +73797,9 @@ var import_config11 = __toESM(require_config2());
73776
73797
 
73777
73798
  // build/client/processor.syncExternalAudio/inline-view.js
73778
73799
  var import_jsx_runtime58 = __toESM(require_jsx_runtime());
73779
- var import_react26 = __toESM(require_react());
73800
+ var import_react27 = __toESM(require_react());
73780
73801
  function InlineView19({ state, config, raise }) {
73781
- (0, import_react26.useEffect)(() => {
73802
+ (0, import_react27.useEffect)(() => {
73782
73803
  if (raise) {
73783
73804
  raise();
73784
73805
  }
@@ -73790,19 +73811,19 @@ var inline_view_default19 = InlineView19;
73790
73811
 
73791
73812
  // build/client/processor.syncExternalAudio/fullscreen-view.js
73792
73813
  var import_jsx_runtime59 = __toESM(require_jsx_runtime());
73793
- var import_react27 = __toESM(require_react());
73814
+ var import_react28 = __toESM(require_react());
73794
73815
  function SyncExternalAudioFullscreenView({ state, config, sendCommand }) {
73795
- const videoContainerRef = (0, import_react27.useRef)(null);
73796
- const whepClientRef = (0, import_react27.useRef)(null);
73816
+ const videoContainerRef = (0, import_react28.useRef)(null);
73817
+ const whepClientRef = (0, import_react28.useRef)(null);
73797
73818
  const commentaries = Object.entries(state.commentaries || {});
73798
- const [selectedCommentary, setSelectedCommentary] = (0, import_react27.useState)(commentaries[0]?.[0] || "");
73799
- (0, import_react27.useEffect)(() => {
73819
+ const [selectedCommentary, setSelectedCommentary] = (0, import_react28.useState)(commentaries[0]?.[0] || "");
73820
+ (0, import_react28.useEffect)(() => {
73800
73821
  if (commentaries.length > 0 && !state.commentaries[selectedCommentary]) {
73801
73822
  setSelectedCommentary(commentaries[0][0]);
73802
73823
  }
73803
73824
  }, [commentaries, selectedCommentary, state.commentaries]);
73804
73825
  const commentary = state.commentaries[selectedCommentary];
73805
- (0, import_react27.useEffect)(() => {
73826
+ (0, import_react28.useEffect)(() => {
73806
73827
  if (commentary?.whepUrl && videoContainerRef.current) {
73807
73828
  if (whepClientRef.current) {
73808
73829
  whepClientRef.current = null;
@@ -74038,11 +74059,11 @@ var summary_view_default11 = SummaryView11;
74038
74059
 
74039
74060
  // build/client/processor.videoCompose/fullscreen.js
74040
74061
  var import_jsx_runtime66 = __toESM(require_jsx_runtime());
74041
- var import_react30 = __toESM(require_react());
74062
+ var import_react31 = __toESM(require_react());
74042
74063
 
74043
74064
  // build/client/processor.videoCompose/preset-transition-panel.js
74044
74065
  var import_jsx_runtime63 = __toESM(require_jsx_runtime());
74045
- var import_react28 = __toESM(require_react());
74066
+ var import_react29 = __toESM(require_react());
74046
74067
 
74047
74068
  // build/client/processor.videoCompose/presets-metadata.js
74048
74069
  var PRESET_METADATA = {
@@ -74482,7 +74503,7 @@ var PRESETS = {
74482
74503
  }))
74483
74504
  }
74484
74505
  }),
74485
- quarters: (sources, _config, res) => {
74506
+ quarters: (sources, _config, res, transitionContext) => {
74486
74507
  const half_w = res.width / 2;
74487
74508
  const half_h = res.height / 2;
74488
74509
  const quarters = sources.slice(0, 4);
@@ -74542,22 +74563,78 @@ var PRESETS = {
74542
74563
  ]
74543
74564
  },
74544
74565
  to: {
74545
- fullscreen: [
74546
- // Reverse: move all to center, fade out all but first
74547
- {
74548
- durationMs: 500,
74549
- layout: {
74550
- layers: quarters.map((s, i) => ({
74551
- sourceName: s,
74552
- zIndex: i,
74553
- opacity: i === 0 ? 1 : 0,
74554
- id: s,
74555
- sourceRect: void 0,
74556
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
74557
- }))
74558
- }
74566
+ fullscreen: (() => {
74567
+ const targetSources = transitionContext?.targetSources ?? [];
74568
+ const targetSource = targetSources[0];
74569
+ const targetInQuarters = targetSource && quarters.includes(targetSource);
74570
+ if (targetInQuarters) {
74571
+ return [
74572
+ {
74573
+ durationMs: 500,
74574
+ layout: {
74575
+ layers: quarters.map((s, i) => ({
74576
+ sourceName: s,
74577
+ zIndex: i,
74578
+ opacity: s === targetSource ? 1 : 0,
74579
+ id: s,
74580
+ sourceRect: void 0,
74581
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74582
+ }))
74583
+ }
74584
+ },
74585
+ // Final step: target source only
74586
+ {
74587
+ durationMs: 0,
74588
+ layout: {
74589
+ layers: [{
74590
+ sourceName: targetSource,
74591
+ zIndex: 0,
74592
+ opacity: 1,
74593
+ id: targetSource,
74594
+ sourceRect: void 0,
74595
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74596
+ }]
74597
+ }
74598
+ }
74599
+ ];
74600
+ } else {
74601
+ return [
74602
+ // Step 1: Fade out all quarters
74603
+ {
74604
+ durationMs: 250,
74605
+ layout: {
74606
+ layers: quarters.map((s, i) => ({
74607
+ sourceName: s,
74608
+ zIndex: i,
74609
+ opacity: 0,
74610
+ id: s,
74611
+ sourceRect: void 0,
74612
+ destRect: {
74613
+ x: i % 2 * half_w,
74614
+ y: Math.floor(i / 2) * half_h,
74615
+ width: half_w,
74616
+ height: half_h
74617
+ }
74618
+ }))
74619
+ }
74620
+ },
74621
+ // Step 2: Show target source (if available)
74622
+ ...targetSource ? [{
74623
+ durationMs: 250,
74624
+ layout: {
74625
+ layers: [{
74626
+ sourceName: targetSource,
74627
+ zIndex: 0,
74628
+ opacity: 1,
74629
+ id: targetSource,
74630
+ sourceRect: void 0,
74631
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74632
+ }]
74633
+ }
74634
+ }] : []
74635
+ ];
74559
74636
  }
74560
- ],
74637
+ })(),
74561
74638
  "mosaic-9": [
74562
74639
  // Quarters → Mosaic-9: move 4 sources to their 3×3 grid positions
74563
74640
  {
@@ -74670,8 +74747,9 @@ var PRESETS = {
74670
74747
  } : void 0
74671
74748
  };
74672
74749
  },
74673
- pip: (sources, config, res) => {
74750
+ pip: (sources, config, res, transitionContext) => {
74674
74751
  const [mainSource, pipSource] = sources;
74752
+ const pipSources = [mainSource, pipSource].filter(Boolean);
74675
74753
  const pipConfig = config;
74676
74754
  const corner = pipConfig?.corner ?? "bottom-right";
74677
74755
  const pipWidth = pipConfig?.width ?? Math.round(res.width * 0.2);
@@ -74805,37 +74883,91 @@ var PRESETS = {
74805
74883
  ]
74806
74884
  },
74807
74885
  to: {
74808
- fullscreen: [
74809
- // Reverse: move to center and fade out
74810
- {
74811
- durationMs: 500,
74812
- layout: {
74813
- layers: [
74814
- {
74815
- sourceName: mainSource,
74816
- zIndex: 0,
74817
- opacity: 1,
74818
- id: mainSource,
74819
- sourceRect: void 0,
74820
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
74821
- },
74822
- {
74823
- sourceName: pipSource,
74824
- zIndex: 1,
74825
- opacity: 0,
74826
- id: pipSource,
74827
- sourceRect: void 0,
74828
- destRect: {
74829
- x: res.width / 2 - pipWidth / 2,
74830
- y: res.height / 2 - pipHeight / 2,
74831
- width: pipWidth,
74832
- height: pipHeight
74833
- }
74886
+ fullscreen: (() => {
74887
+ const targetSources = transitionContext?.targetSources ?? [];
74888
+ const targetSource = targetSources[0];
74889
+ const targetInPip = targetSource && pipSources.includes(targetSource);
74890
+ if (targetInPip) {
74891
+ return [
74892
+ {
74893
+ durationMs: 500,
74894
+ layout: {
74895
+ layers: [
74896
+ {
74897
+ sourceName: mainSource,
74898
+ zIndex: 0,
74899
+ opacity: mainSource === targetSource ? 1 : 0,
74900
+ id: mainSource,
74901
+ sourceRect: void 0,
74902
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74903
+ },
74904
+ ...pipSource ? [{
74905
+ sourceName: pipSource,
74906
+ zIndex: 1,
74907
+ opacity: pipSource === targetSource ? 1 : 0,
74908
+ id: pipSource,
74909
+ sourceRect: void 0,
74910
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74911
+ }] : []
74912
+ ]
74834
74913
  }
74835
- ]
74836
- }
74914
+ },
74915
+ // Final step: target source only
74916
+ {
74917
+ durationMs: 0,
74918
+ layout: {
74919
+ layers: [{
74920
+ sourceName: targetSource,
74921
+ zIndex: 0,
74922
+ opacity: 1,
74923
+ id: targetSource,
74924
+ sourceRect: void 0,
74925
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74926
+ }]
74927
+ }
74928
+ }
74929
+ ];
74930
+ } else {
74931
+ return [
74932
+ {
74933
+ durationMs: 250,
74934
+ layout: {
74935
+ layers: [
74936
+ {
74937
+ sourceName: mainSource,
74938
+ zIndex: 0,
74939
+ opacity: 0,
74940
+ id: mainSource,
74941
+ sourceRect: void 0,
74942
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74943
+ },
74944
+ ...pipSource ? [{
74945
+ sourceName: pipSource,
74946
+ zIndex: 1,
74947
+ opacity: 0,
74948
+ id: pipSource,
74949
+ sourceRect: void 0,
74950
+ destRect: { x: pipX, y: pipY, width: pipWidth, height: pipHeight }
74951
+ }] : []
74952
+ ]
74953
+ }
74954
+ },
74955
+ ...targetSource ? [{
74956
+ durationMs: 250,
74957
+ layout: {
74958
+ layers: [{
74959
+ sourceName: targetSource,
74960
+ zIndex: 0,
74961
+ opacity: 1,
74962
+ id: targetSource,
74963
+ sourceRect: void 0,
74964
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
74965
+ }]
74966
+ }
74967
+ }] : []
74968
+ ];
74837
74969
  }
74838
- ],
74970
+ })(),
74839
74971
  "side-by-side": [
74840
74972
  // PiP → Side-by-side: background compresses to left, overlay expands to right
74841
74973
  {
@@ -74923,8 +75055,9 @@ var PRESETS = {
74923
75055
  } : void 0
74924
75056
  };
74925
75057
  },
74926
- "lower-third": (sources, config, res) => {
75058
+ "lower-third": (sources, config, res, transitionContext) => {
74927
75059
  const [mainSource, lowerThirdSource] = sources;
75060
+ const lowerThirdSources = [mainSource, lowerThirdSource].filter(Boolean);
74928
75061
  const lowerThirdConfig = config;
74929
75062
  const alignment = lowerThirdConfig?.alignment ?? "left";
74930
75063
  const lowerThirdWidth = lowerThirdConfig?.width ?? Math.round(res.width * 0.3);
@@ -75018,32 +75151,91 @@ var PRESETS = {
75018
75151
  ]
75019
75152
  },
75020
75153
  to: {
75021
- fullscreen: [
75022
- // Reverse: slide down off screen
75023
- {
75024
- durationMs: 500,
75025
- layout: {
75026
- layers: [
75027
- {
75028
- sourceName: mainSource,
75029
- zIndex: 0,
75030
- opacity: 1,
75031
- id: mainSource,
75032
- sourceRect: void 0,
75033
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75034
- },
75035
- {
75036
- sourceName: lowerThirdSource,
75037
- zIndex: 1,
75038
- opacity: 1,
75039
- id: lowerThirdSource,
75040
- sourceRect: void 0,
75041
- destRect: { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
75154
+ fullscreen: (() => {
75155
+ const targetSources = transitionContext?.targetSources ?? [];
75156
+ const targetSource = targetSources[0];
75157
+ const targetInLowerThird = targetSource && lowerThirdSources.includes(targetSource);
75158
+ if (targetInLowerThird) {
75159
+ return [
75160
+ {
75161
+ durationMs: 500,
75162
+ layout: {
75163
+ layers: [
75164
+ {
75165
+ sourceName: mainSource,
75166
+ zIndex: 0,
75167
+ opacity: mainSource === targetSource ? 1 : 0,
75168
+ id: mainSource,
75169
+ sourceRect: void 0,
75170
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75171
+ },
75172
+ {
75173
+ sourceName: lowerThirdSource,
75174
+ zIndex: 1,
75175
+ opacity: lowerThirdSource === targetSource ? 1 : 0,
75176
+ id: lowerThirdSource,
75177
+ sourceRect: void 0,
75178
+ destRect: lowerThirdSource === targetSource ? { x: 0, y: 0, width: res.width, height: res.height } : { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
75179
+ }
75180
+ ]
75042
75181
  }
75043
- ]
75044
- }
75182
+ },
75183
+ // Final step: target source only
75184
+ {
75185
+ durationMs: 0,
75186
+ layout: {
75187
+ layers: [{
75188
+ sourceName: targetSource,
75189
+ zIndex: 0,
75190
+ opacity: 1,
75191
+ id: targetSource,
75192
+ sourceRect: void 0,
75193
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75194
+ }]
75195
+ }
75196
+ }
75197
+ ];
75198
+ } else {
75199
+ return [
75200
+ {
75201
+ durationMs: 250,
75202
+ layout: {
75203
+ layers: [
75204
+ {
75205
+ sourceName: mainSource,
75206
+ zIndex: 0,
75207
+ opacity: 0,
75208
+ id: mainSource,
75209
+ sourceRect: void 0,
75210
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75211
+ },
75212
+ {
75213
+ sourceName: lowerThirdSource,
75214
+ zIndex: 1,
75215
+ opacity: 0,
75216
+ id: lowerThirdSource,
75217
+ sourceRect: void 0,
75218
+ destRect: { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
75219
+ }
75220
+ ]
75221
+ }
75222
+ },
75223
+ ...targetSource ? [{
75224
+ durationMs: 250,
75225
+ layout: {
75226
+ layers: [{
75227
+ sourceName: targetSource,
75228
+ zIndex: 0,
75229
+ opacity: 1,
75230
+ id: targetSource,
75231
+ sourceRect: void 0,
75232
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75233
+ }]
75234
+ }
75235
+ }] : []
75236
+ ];
75045
75237
  }
75046
- ],
75238
+ })(),
75047
75239
  pip: [
75048
75240
  // Lower-third → PiP: move overlay from bottom to corner, resize
75049
75241
  {
@@ -75079,9 +75271,10 @@ var PRESETS = {
75079
75271
  } : void 0
75080
75272
  };
75081
75273
  },
75082
- "side-by-side": (sources, _config, res) => {
75274
+ "side-by-side": (sources, _config, res, transitionContext) => {
75083
75275
  const [source1, source2] = sources.slice(0, 2);
75084
75276
  const halfWidth = res.width / 2;
75277
+ const sideBySideSources = [source1, source2].filter(Boolean);
75085
75278
  return {
75086
75279
  name: "side-by-side",
75087
75280
  finalConfig: {
@@ -75184,32 +75377,91 @@ var PRESETS = {
75184
75377
  ]
75185
75378
  },
75186
75379
  to: {
75187
- fullscreen: [
75188
- // Reverse: slide second off and expand first
75189
- {
75190
- durationMs: 500,
75191
- layout: {
75192
- layers: [
75193
- {
75194
- sourceName: source1,
75195
- zIndex: 0,
75196
- opacity: 1,
75197
- id: source1,
75198
- sourceRect: void 0,
75199
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75200
- },
75201
- {
75202
- sourceName: source2,
75203
- zIndex: 1,
75204
- opacity: 1,
75205
- id: source2,
75206
- sourceRect: void 0,
75207
- destRect: { x: res.width, y: 0, width: halfWidth, height: res.height }
75380
+ fullscreen: (() => {
75381
+ const targetSources = transitionContext?.targetSources ?? [];
75382
+ const targetSource = targetSources[0];
75383
+ const targetInSideBySide = targetSource && sideBySideSources.includes(targetSource);
75384
+ if (targetInSideBySide) {
75385
+ return [
75386
+ {
75387
+ durationMs: 500,
75388
+ layout: {
75389
+ layers: [
75390
+ {
75391
+ sourceName: source1,
75392
+ zIndex: 0,
75393
+ opacity: 1,
75394
+ id: source1,
75395
+ sourceRect: void 0,
75396
+ destRect: source1 === targetSource ? { x: 0, y: 0, width: res.width, height: res.height } : { x: -halfWidth, y: 0, width: halfWidth, height: res.height }
75397
+ },
75398
+ ...source2 ? [{
75399
+ sourceName: source2,
75400
+ zIndex: 1,
75401
+ opacity: 1,
75402
+ id: source2,
75403
+ sourceRect: void 0,
75404
+ destRect: source2 === targetSource ? { x: 0, y: 0, width: res.width, height: res.height } : { x: res.width, y: 0, width: halfWidth, height: res.height }
75405
+ }] : []
75406
+ ]
75208
75407
  }
75209
- ]
75210
- }
75408
+ },
75409
+ // Final step: target source only
75410
+ {
75411
+ durationMs: 0,
75412
+ layout: {
75413
+ layers: [{
75414
+ sourceName: targetSource,
75415
+ zIndex: 0,
75416
+ opacity: 1,
75417
+ id: targetSource,
75418
+ sourceRect: void 0,
75419
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75420
+ }]
75421
+ }
75422
+ }
75423
+ ];
75424
+ } else {
75425
+ return [
75426
+ {
75427
+ durationMs: 250,
75428
+ layout: {
75429
+ layers: [
75430
+ {
75431
+ sourceName: source1,
75432
+ zIndex: 0,
75433
+ opacity: 0,
75434
+ id: source1,
75435
+ sourceRect: void 0,
75436
+ destRect: { x: 0, y: 0, width: halfWidth, height: res.height }
75437
+ },
75438
+ ...source2 ? [{
75439
+ sourceName: source2,
75440
+ zIndex: 1,
75441
+ opacity: 0,
75442
+ id: source2,
75443
+ sourceRect: void 0,
75444
+ destRect: { x: halfWidth, y: 0, width: halfWidth, height: res.height }
75445
+ }] : []
75446
+ ]
75447
+ }
75448
+ },
75449
+ ...targetSource ? [{
75450
+ durationMs: 250,
75451
+ layout: {
75452
+ layers: [{
75453
+ sourceName: targetSource,
75454
+ zIndex: 0,
75455
+ opacity: 1,
75456
+ id: targetSource,
75457
+ sourceRect: void 0,
75458
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75459
+ }]
75460
+ }
75461
+ }] : []
75462
+ ];
75211
75463
  }
75212
- ],
75464
+ })(),
75213
75465
  pip: [
75214
75466
  // Side-by-side → PiP: left expands to fullscreen, right shrinks to corner
75215
75467
  {
@@ -75297,13 +75549,14 @@ var PRESETS = {
75297
75549
  } : void 0
75298
75550
  };
75299
75551
  },
75300
- "lbar": (sources, config, res) => {
75552
+ "lbar": (sources, config, res, transitionContext) => {
75301
75553
  const [videoSource, overlaySource] = sources;
75302
75554
  const { videoWidth, videoHeight, videoX = 50, videoY = 50 } = config;
75303
- return createLBarPresetDefinition([videoSource, overlaySource], { videoWidth, videoHeight, videoX, videoY }, res);
75555
+ return createLBarPresetDefinition([videoSource, overlaySource], { videoWidth, videoHeight, videoX, videoY }, res, transitionContext);
75304
75556
  },
75305
- "qr-lbar": (sources, config, res) => {
75557
+ "qr-lbar": (sources, config, res, transitionContext) => {
75306
75558
  const [videoSource, qrSource, overlaySource] = sources;
75559
+ const qrLbarSources = [videoSource, qrSource, overlaySource].filter(Boolean);
75307
75560
  const { qrDurationMs, qrCorner, qrWidth, qrHeight, qrPadding = 20, videoWidth, videoHeight, videoX = 0, videoY = 0 } = config;
75308
75561
  const qrPosition = (() => {
75309
75562
  switch (qrCorner) {
@@ -75423,45 +75676,112 @@ var PRESETS = {
75423
75676
  ]
75424
75677
  },
75425
75678
  to: {
75426
- fullscreen: [
75427
- // Reverse: expand video, hide overlay and QR
75428
- {
75429
- durationMs: 500,
75430
- layout: {
75431
- layers: [
75432
- {
75433
- sourceName: overlaySource,
75434
- id: "overlay",
75435
- zIndex: 0,
75436
- opacity: 0,
75437
- sourceRect: void 0,
75438
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75439
- },
75440
- {
75441
- sourceName: videoSource,
75442
- id: "video",
75443
- zIndex: 1,
75444
- opacity: 1,
75445
- sourceRect: void 0,
75446
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75447
- },
75448
- {
75449
- sourceName: qrSource,
75450
- id: "qr",
75451
- zIndex: 2,
75452
- opacity: 0,
75453
- sourceRect: void 0,
75454
- destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
75679
+ fullscreen: (() => {
75680
+ const targetSources = transitionContext?.targetSources ?? [];
75681
+ const targetSource = targetSources[0];
75682
+ const targetInQrLbar = targetSource && qrLbarSources.includes(targetSource);
75683
+ if (targetInQrLbar) {
75684
+ return [
75685
+ {
75686
+ durationMs: 500,
75687
+ layout: {
75688
+ layers: [
75689
+ {
75690
+ sourceName: overlaySource,
75691
+ id: "overlay",
75692
+ zIndex: 0,
75693
+ opacity: overlaySource === targetSource ? 1 : 0,
75694
+ sourceRect: void 0,
75695
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75696
+ },
75697
+ {
75698
+ sourceName: videoSource,
75699
+ id: "video",
75700
+ zIndex: 1,
75701
+ opacity: videoSource === targetSource ? 1 : 0,
75702
+ sourceRect: void 0,
75703
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75704
+ },
75705
+ {
75706
+ sourceName: qrSource,
75707
+ id: "qr",
75708
+ zIndex: 2,
75709
+ opacity: qrSource === targetSource ? 1 : 0,
75710
+ sourceRect: void 0,
75711
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75712
+ }
75713
+ ]
75455
75714
  }
75456
- ]
75457
- }
75715
+ },
75716
+ // Final step: target source only
75717
+ {
75718
+ durationMs: 0,
75719
+ layout: {
75720
+ layers: [{
75721
+ sourceName: targetSource,
75722
+ zIndex: 0,
75723
+ opacity: 1,
75724
+ id: targetSource,
75725
+ sourceRect: void 0,
75726
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75727
+ }]
75728
+ }
75729
+ }
75730
+ ];
75731
+ } else {
75732
+ return [
75733
+ {
75734
+ durationMs: 250,
75735
+ layout: {
75736
+ layers: [
75737
+ {
75738
+ sourceName: overlaySource,
75739
+ id: "overlay",
75740
+ zIndex: 0,
75741
+ opacity: 0,
75742
+ sourceRect: void 0,
75743
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75744
+ },
75745
+ {
75746
+ sourceName: videoSource,
75747
+ id: "video",
75748
+ zIndex: 1,
75749
+ opacity: 0,
75750
+ sourceRect: void 0,
75751
+ destRect: { x: videoX, y: videoY, width: videoWidth, height: videoHeight }
75752
+ },
75753
+ {
75754
+ sourceName: qrSource,
75755
+ id: "qr",
75756
+ zIndex: 2,
75757
+ opacity: 0,
75758
+ sourceRect: void 0,
75759
+ destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
75760
+ }
75761
+ ]
75762
+ }
75763
+ },
75764
+ ...targetSource ? [{
75765
+ durationMs: 250,
75766
+ layout: {
75767
+ layers: [{
75768
+ sourceName: targetSource,
75769
+ zIndex: 0,
75770
+ opacity: 1,
75771
+ id: targetSource,
75772
+ sourceRect: void 0,
75773
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75774
+ }]
75775
+ }
75776
+ }] : []
75777
+ ];
75458
75778
  }
75459
- ]
75779
+ })()
75460
75780
  }
75461
75781
  }
75462
75782
  };
75463
75783
  },
75464
- "3-split": (sources, _config, res) => {
75784
+ "3-split": (sources, _config, res, transitionContext) => {
75465
75785
  const third_w = res.width / 3;
75466
75786
  const splits = sources.slice(0, 3);
75467
75787
  return {
@@ -75561,21 +75881,76 @@ var PRESETS = {
75561
75881
  ]
75562
75882
  },
75563
75883
  to: {
75564
- fullscreen: [
75565
- {
75566
- durationMs: 500,
75567
- layout: {
75568
- layers: splits.map((s, i) => ({
75569
- sourceName: s,
75570
- zIndex: i,
75571
- opacity: i === 0 ? 1 : 0,
75572
- id: s,
75573
- sourceRect: void 0,
75574
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75575
- }))
75576
- }
75884
+ fullscreen: (() => {
75885
+ const targetSources = transitionContext?.targetSources ?? [];
75886
+ const targetSource = targetSources[0];
75887
+ const targetInSplits = targetSource && splits.includes(targetSource);
75888
+ if (targetInSplits) {
75889
+ return [
75890
+ {
75891
+ durationMs: 500,
75892
+ layout: {
75893
+ layers: splits.map((s, i) => ({
75894
+ sourceName: s,
75895
+ zIndex: i,
75896
+ opacity: s === targetSource ? 1 : 0,
75897
+ id: s,
75898
+ sourceRect: void 0,
75899
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75900
+ }))
75901
+ }
75902
+ },
75903
+ // Final step: target source only
75904
+ {
75905
+ durationMs: 0,
75906
+ layout: {
75907
+ layers: [{
75908
+ sourceName: targetSource,
75909
+ zIndex: 0,
75910
+ opacity: 1,
75911
+ id: targetSource,
75912
+ sourceRect: void 0,
75913
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75914
+ }]
75915
+ }
75916
+ }
75917
+ ];
75918
+ } else {
75919
+ return [
75920
+ {
75921
+ durationMs: 250,
75922
+ layout: {
75923
+ layers: splits.map((s, i) => ({
75924
+ sourceName: s,
75925
+ zIndex: i,
75926
+ opacity: 0,
75927
+ id: s,
75928
+ sourceRect: void 0,
75929
+ destRect: {
75930
+ x: i * third_w,
75931
+ y: 0,
75932
+ width: third_w,
75933
+ height: res.height
75934
+ }
75935
+ }))
75936
+ }
75937
+ },
75938
+ ...targetSource ? [{
75939
+ durationMs: 250,
75940
+ layout: {
75941
+ layers: [{
75942
+ sourceName: targetSource,
75943
+ zIndex: 0,
75944
+ opacity: 1,
75945
+ id: targetSource,
75946
+ sourceRect: void 0,
75947
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
75948
+ }]
75949
+ }
75950
+ }] : []
75951
+ ];
75577
75952
  }
75578
- ],
75953
+ })(),
75579
75954
  "side-by-side": [
75580
75955
  // 3-split → Side-by-side: expand first 2 from 33.33% to 50%, fade out third
75581
75956
  {
@@ -75644,9 +76019,10 @@ var PRESETS = {
75644
76019
  } : void 0
75645
76020
  };
75646
76021
  },
75647
- "2-crop": (sources, config, res) => {
76022
+ "2-crop": (sources, config, res, transitionContext) => {
75648
76023
  const [left, right] = sources.slice(0, 2);
75649
76024
  const half_w = res.width / 2;
76025
+ const cropSources = [left, right].filter(Boolean);
75650
76026
  const crops = config.crops || {};
75651
76027
  return {
75652
76028
  name: "2-crop",
@@ -75722,31 +76098,91 @@ var PRESETS = {
75722
76098
  ]
75723
76099
  },
75724
76100
  to: {
75725
- fullscreen: [
75726
- {
75727
- durationMs: 500,
75728
- layout: {
75729
- layers: [
75730
- {
75731
- sourceName: left,
75732
- zIndex: 0,
75733
- opacity: 1,
75734
- id: left,
75735
- sourceRect: void 0,
75736
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75737
- },
75738
- {
75739
- sourceName: right,
75740
- zIndex: 1,
75741
- opacity: 0,
75742
- id: right,
75743
- sourceRect: void 0,
75744
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76101
+ fullscreen: (() => {
76102
+ const targetSources = transitionContext?.targetSources ?? [];
76103
+ const targetSource = targetSources[0];
76104
+ const targetInCrops = targetSource && cropSources.includes(targetSource);
76105
+ if (targetInCrops) {
76106
+ return [
76107
+ {
76108
+ durationMs: 500,
76109
+ layout: {
76110
+ layers: [
76111
+ {
76112
+ sourceName: left,
76113
+ zIndex: 0,
76114
+ opacity: left === targetSource ? 1 : 0,
76115
+ id: left,
76116
+ sourceRect: void 0,
76117
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76118
+ },
76119
+ {
76120
+ sourceName: right,
76121
+ zIndex: 1,
76122
+ opacity: right === targetSource ? 1 : 0,
76123
+ id: right,
76124
+ sourceRect: void 0,
76125
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76126
+ }
76127
+ ]
75745
76128
  }
75746
- ]
75747
- }
76129
+ },
76130
+ // Final step: target source only
76131
+ {
76132
+ durationMs: 0,
76133
+ layout: {
76134
+ layers: [{
76135
+ sourceName: targetSource,
76136
+ zIndex: 0,
76137
+ opacity: 1,
76138
+ id: targetSource,
76139
+ sourceRect: void 0,
76140
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76141
+ }]
76142
+ }
76143
+ }
76144
+ ];
76145
+ } else {
76146
+ return [
76147
+ {
76148
+ durationMs: 250,
76149
+ layout: {
76150
+ layers: [
76151
+ {
76152
+ sourceName: left,
76153
+ zIndex: 0,
76154
+ opacity: 0,
76155
+ id: left,
76156
+ sourceRect: crops[left] || void 0,
76157
+ destRect: { x: 0, y: 0, width: half_w, height: res.height }
76158
+ },
76159
+ {
76160
+ sourceName: right,
76161
+ zIndex: 1,
76162
+ opacity: 0,
76163
+ id: right,
76164
+ sourceRect: crops[right] || void 0,
76165
+ destRect: { x: half_w, y: 0, width: half_w, height: res.height }
76166
+ }
76167
+ ]
76168
+ }
76169
+ },
76170
+ ...targetSource ? [{
76171
+ durationMs: 250,
76172
+ layout: {
76173
+ layers: [{
76174
+ sourceName: targetSource,
76175
+ zIndex: 0,
76176
+ opacity: 1,
76177
+ id: targetSource,
76178
+ sourceRect: void 0,
76179
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76180
+ }]
76181
+ }
76182
+ }] : []
76183
+ ];
75748
76184
  }
75749
- ],
76185
+ })(),
75750
76186
  "3-crop": [
75751
76187
  // 2-crop → 3-crop: compress both from 50% to 33.33% width, adjust crop
75752
76188
  {
@@ -75803,7 +76239,7 @@ var PRESETS = {
75803
76239
  } : void 0
75804
76240
  };
75805
76241
  },
75806
- "3-crop": (sources, config, res) => {
76242
+ "3-crop": (sources, config, res, transitionContext) => {
75807
76243
  const third_w = res.width / 3;
75808
76244
  const cropSources = sources.slice(0, 3);
75809
76245
  const cropConfig = config.crops || {};
@@ -75861,21 +76297,76 @@ var PRESETS = {
75861
76297
  ]
75862
76298
  },
75863
76299
  to: {
75864
- fullscreen: [
75865
- {
75866
- durationMs: 500,
75867
- layout: {
75868
- layers: cropSources.map((s, i) => ({
75869
- sourceName: s,
75870
- zIndex: i,
75871
- opacity: i === 0 ? 1 : 0,
75872
- id: s,
75873
- sourceRect: void 0,
75874
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
75875
- }))
75876
- }
76300
+ fullscreen: (() => {
76301
+ const targetSources = transitionContext?.targetSources ?? [];
76302
+ const targetSource = targetSources[0];
76303
+ const targetInCrops = targetSource && cropSources.includes(targetSource);
76304
+ if (targetInCrops) {
76305
+ return [
76306
+ {
76307
+ durationMs: 500,
76308
+ layout: {
76309
+ layers: cropSources.map((s, i) => ({
76310
+ sourceName: s,
76311
+ zIndex: i,
76312
+ opacity: s === targetSource ? 1 : 0,
76313
+ id: s,
76314
+ sourceRect: void 0,
76315
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76316
+ }))
76317
+ }
76318
+ },
76319
+ // Final step: target source only
76320
+ {
76321
+ durationMs: 0,
76322
+ layout: {
76323
+ layers: [{
76324
+ sourceName: targetSource,
76325
+ zIndex: 0,
76326
+ opacity: 1,
76327
+ id: targetSource,
76328
+ sourceRect: void 0,
76329
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76330
+ }]
76331
+ }
76332
+ }
76333
+ ];
76334
+ } else {
76335
+ return [
76336
+ {
76337
+ durationMs: 250,
76338
+ layout: {
76339
+ layers: cropSources.map((s, i) => ({
76340
+ sourceName: s,
76341
+ zIndex: i,
76342
+ opacity: 0,
76343
+ id: s,
76344
+ sourceRect: cropConfig[s] || void 0,
76345
+ destRect: {
76346
+ x: i * third_w,
76347
+ y: 0,
76348
+ width: third_w,
76349
+ height: res.height
76350
+ }
76351
+ }))
76352
+ }
76353
+ },
76354
+ ...targetSource ? [{
76355
+ durationMs: 250,
76356
+ layout: {
76357
+ layers: [{
76358
+ sourceName: targetSource,
76359
+ zIndex: 0,
76360
+ opacity: 1,
76361
+ id: targetSource,
76362
+ sourceRect: void 0,
76363
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76364
+ }]
76365
+ }
76366
+ }] : []
76367
+ ];
75877
76368
  }
75878
- ],
76369
+ })(),
75879
76370
  "2-crop": [
75880
76371
  // 3-crop → 2-crop: expand first 2 from 33.33% to 50%, adjust crop, fade out 3rd
75881
76372
  {
@@ -75909,8 +76400,9 @@ var PRESETS = {
75909
76400
  } : void 0
75910
76401
  };
75911
76402
  },
75912
- "2-crop-left": (sources, config, res) => {
76403
+ "2-crop-left": (sources, config, res, transitionContext) => {
75913
76404
  const [background, left, right] = sources.slice(0, 3);
76405
+ const cropLeftSources = [background, left, right].filter(Boolean);
75914
76406
  const cropConfig = config.crops || {};
75915
76407
  const leftW = res.width * 0.34;
75916
76408
  const rightW = res.width * 0.66666;
@@ -76015,39 +76507,107 @@ var PRESETS = {
76015
76507
  ]
76016
76508
  },
76017
76509
  to: {
76018
- fullscreen: [
76019
- {
76020
- durationMs: 500,
76021
- layout: {
76022
- layers: [
76023
- {
76024
- sourceName: background,
76025
- zIndex: 0,
76026
- opacity: 1,
76027
- id: background,
76028
- sourceRect: void 0,
76029
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76030
- },
76031
- {
76032
- sourceName: left,
76033
- zIndex: 1,
76034
- opacity: 0,
76035
- id: left,
76036
- sourceRect: void 0,
76037
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76038
- },
76039
- {
76040
- sourceName: right,
76041
- zIndex: 2,
76042
- opacity: 0,
76043
- id: right,
76044
- sourceRect: void 0,
76045
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76510
+ fullscreen: (() => {
76511
+ const targetSources = transitionContext?.targetSources ?? [];
76512
+ const targetSource = targetSources[0];
76513
+ const targetInCrops = targetSource && cropLeftSources.includes(targetSource);
76514
+ if (targetInCrops) {
76515
+ return [
76516
+ {
76517
+ durationMs: 500,
76518
+ layout: {
76519
+ layers: [
76520
+ {
76521
+ sourceName: background,
76522
+ zIndex: 0,
76523
+ opacity: background === targetSource ? 1 : 0,
76524
+ id: background,
76525
+ sourceRect: void 0,
76526
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76527
+ },
76528
+ {
76529
+ sourceName: left,
76530
+ zIndex: 1,
76531
+ opacity: left === targetSource ? 1 : 0,
76532
+ id: left,
76533
+ sourceRect: void 0,
76534
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76535
+ },
76536
+ {
76537
+ sourceName: right,
76538
+ zIndex: 2,
76539
+ opacity: right === targetSource ? 1 : 0,
76540
+ id: right,
76541
+ sourceRect: void 0,
76542
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76543
+ }
76544
+ ]
76046
76545
  }
76047
- ]
76048
- }
76546
+ },
76547
+ // Final step: target source only
76548
+ {
76549
+ durationMs: 0,
76550
+ layout: {
76551
+ layers: [{
76552
+ sourceName: targetSource,
76553
+ zIndex: 0,
76554
+ opacity: 1,
76555
+ id: targetSource,
76556
+ sourceRect: void 0,
76557
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76558
+ }]
76559
+ }
76560
+ }
76561
+ ];
76562
+ } else {
76563
+ return [
76564
+ {
76565
+ durationMs: 250,
76566
+ layout: {
76567
+ layers: [
76568
+ {
76569
+ sourceName: background,
76570
+ zIndex: 0,
76571
+ opacity: 0,
76572
+ id: background,
76573
+ sourceRect: void 0,
76574
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76575
+ },
76576
+ {
76577
+ sourceName: left,
76578
+ zIndex: 1,
76579
+ opacity: 0,
76580
+ id: left,
76581
+ sourceRect: cropConfig[left] || void 0,
76582
+ destRect: { x: 0, y: yPos, width: leftW, height: boxH }
76583
+ },
76584
+ {
76585
+ sourceName: right,
76586
+ zIndex: 2,
76587
+ opacity: 0,
76588
+ id: right,
76589
+ sourceRect: void 0,
76590
+ destRect: { x: rightX, y: yPos, width: rightW, height: boxH }
76591
+ }
76592
+ ]
76593
+ }
76594
+ },
76595
+ ...targetSource ? [{
76596
+ durationMs: 250,
76597
+ layout: {
76598
+ layers: [{
76599
+ sourceName: targetSource,
76600
+ zIndex: 0,
76601
+ opacity: 1,
76602
+ id: targetSource,
76603
+ sourceRect: void 0,
76604
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76605
+ }]
76606
+ }
76607
+ }] : []
76608
+ ];
76049
76609
  }
76050
- ],
76610
+ })(),
76051
76611
  "2-crop-right": [
76052
76612
  // 2-crop-left → 2-crop-right: swap positions and crop states
76053
76613
  {
@@ -76088,8 +76648,9 @@ var PRESETS = {
76088
76648
  } : void 0
76089
76649
  };
76090
76650
  },
76091
- "2-crop-right": (sources, config, res) => {
76651
+ "2-crop-right": (sources, config, res, transitionContext) => {
76092
76652
  const [background, left, right] = sources.slice(0, 3);
76653
+ const cropRightSources = [background, left, right].filter(Boolean);
76093
76654
  const cropConfig = config.crops || {};
76094
76655
  const leftW = res.width * 0.67;
76095
76656
  const rightW = res.width * 0.33333;
@@ -76194,39 +76755,107 @@ var PRESETS = {
76194
76755
  ]
76195
76756
  },
76196
76757
  to: {
76197
- fullscreen: [
76198
- {
76199
- durationMs: 500,
76200
- layout: {
76201
- layers: [
76202
- {
76203
- sourceName: background,
76204
- zIndex: 0,
76205
- opacity: 1,
76206
- id: background,
76207
- sourceRect: void 0,
76208
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76209
- },
76210
- {
76211
- sourceName: left,
76212
- zIndex: 1,
76213
- opacity: 0,
76214
- id: left,
76215
- sourceRect: void 0,
76216
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76217
- },
76218
- {
76219
- sourceName: right,
76220
- zIndex: 2,
76221
- opacity: 0,
76222
- id: right,
76223
- sourceRect: void 0,
76224
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76758
+ fullscreen: (() => {
76759
+ const targetSources = transitionContext?.targetSources ?? [];
76760
+ const targetSource = targetSources[0];
76761
+ const targetInCrops = targetSource && cropRightSources.includes(targetSource);
76762
+ if (targetInCrops) {
76763
+ return [
76764
+ {
76765
+ durationMs: 500,
76766
+ layout: {
76767
+ layers: [
76768
+ {
76769
+ sourceName: background,
76770
+ zIndex: 0,
76771
+ opacity: background === targetSource ? 1 : 0,
76772
+ id: background,
76773
+ sourceRect: void 0,
76774
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76775
+ },
76776
+ {
76777
+ sourceName: left,
76778
+ zIndex: 1,
76779
+ opacity: left === targetSource ? 1 : 0,
76780
+ id: left,
76781
+ sourceRect: void 0,
76782
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76783
+ },
76784
+ {
76785
+ sourceName: right,
76786
+ zIndex: 2,
76787
+ opacity: right === targetSource ? 1 : 0,
76788
+ id: right,
76789
+ sourceRect: void 0,
76790
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76791
+ }
76792
+ ]
76225
76793
  }
76226
- ]
76227
- }
76794
+ },
76795
+ // Final step: target source only
76796
+ {
76797
+ durationMs: 0,
76798
+ layout: {
76799
+ layers: [{
76800
+ sourceName: targetSource,
76801
+ zIndex: 0,
76802
+ opacity: 1,
76803
+ id: targetSource,
76804
+ sourceRect: void 0,
76805
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76806
+ }]
76807
+ }
76808
+ }
76809
+ ];
76810
+ } else {
76811
+ return [
76812
+ {
76813
+ durationMs: 250,
76814
+ layout: {
76815
+ layers: [
76816
+ {
76817
+ sourceName: background,
76818
+ zIndex: 0,
76819
+ opacity: 0,
76820
+ id: background,
76821
+ sourceRect: void 0,
76822
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76823
+ },
76824
+ {
76825
+ sourceName: left,
76826
+ zIndex: 1,
76827
+ opacity: 0,
76828
+ id: left,
76829
+ sourceRect: void 0,
76830
+ destRect: { x: 0, y: yPos, width: leftW, height: boxH }
76831
+ },
76832
+ {
76833
+ sourceName: right,
76834
+ zIndex: 2,
76835
+ opacity: 0,
76836
+ id: right,
76837
+ sourceRect: cropConfig[right] || void 0,
76838
+ destRect: { x: rightX, y: yPos, width: rightW, height: boxH }
76839
+ }
76840
+ ]
76841
+ }
76842
+ },
76843
+ ...targetSource ? [{
76844
+ durationMs: 250,
76845
+ layout: {
76846
+ layers: [{
76847
+ sourceName: targetSource,
76848
+ zIndex: 0,
76849
+ opacity: 1,
76850
+ id: targetSource,
76851
+ sourceRect: void 0,
76852
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
76853
+ }]
76854
+ }
76855
+ }] : []
76856
+ ];
76228
76857
  }
76229
- ],
76858
+ })(),
76230
76859
  "2-crop-left": [
76231
76860
  // 2-crop-right → 2-crop-left: swap positions and crop states
76232
76861
  {
@@ -76267,9 +76896,10 @@ var PRESETS = {
76267
76896
  } : void 0
76268
76897
  };
76269
76898
  },
76270
- "5-split": (sources, config, res) => {
76899
+ "5-split": (sources, config, res, transitionContext) => {
76271
76900
  const [background, ...vidSources] = sources.slice(0, 6);
76272
76901
  const fiveSources = vidSources.slice(0, 5);
76902
+ const allSplitSources = [background, ...fiveSources].filter(Boolean);
76273
76903
  const cropConfig = config.crops || {};
76274
76904
  const boxW = res.width * 0.34;
76275
76905
  const boxH = res.height * 0.5;
@@ -76361,36 +76991,101 @@ var PRESETS = {
76361
76991
  ]
76362
76992
  },
76363
76993
  to: {
76364
- fullscreen: [
76365
- {
76366
- durationMs: 500,
76367
- layout: {
76368
- layers: [
76369
- {
76370
- sourceName: background,
76371
- zIndex: 0,
76372
- opacity: 1,
76373
- id: background,
76374
- sourceRect: void 0,
76375
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76376
- },
76377
- ...fiveSources.map((s, i) => ({
76378
- sourceName: s,
76379
- zIndex: i + 1,
76380
- opacity: 0,
76381
- id: s,
76382
- sourceRect: void 0,
76383
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76384
- }))
76385
- ]
76386
- }
76994
+ fullscreen: (() => {
76995
+ const targetSources = transitionContext?.targetSources ?? [];
76996
+ const targetSource = targetSources[0];
76997
+ const targetInSplit = targetSource && allSplitSources.includes(targetSource);
76998
+ if (targetInSplit) {
76999
+ return [
77000
+ {
77001
+ durationMs: 500,
77002
+ layout: {
77003
+ layers: [
77004
+ {
77005
+ sourceName: background,
77006
+ zIndex: 0,
77007
+ opacity: background === targetSource ? 1 : 0,
77008
+ id: background,
77009
+ sourceRect: void 0,
77010
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77011
+ },
77012
+ ...fiveSources.map((s, i) => ({
77013
+ sourceName: s,
77014
+ zIndex: i + 1,
77015
+ opacity: s === targetSource ? 1 : 0,
77016
+ id: s,
77017
+ sourceRect: void 0,
77018
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77019
+ }))
77020
+ ]
77021
+ }
77022
+ },
77023
+ // Final step: target source only
77024
+ {
77025
+ durationMs: 0,
77026
+ layout: {
77027
+ layers: [{
77028
+ sourceName: targetSource,
77029
+ zIndex: 0,
77030
+ opacity: 1,
77031
+ id: targetSource,
77032
+ sourceRect: void 0,
77033
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77034
+ }]
77035
+ }
77036
+ }
77037
+ ];
77038
+ } else {
77039
+ return [
77040
+ {
77041
+ durationMs: 250,
77042
+ layout: {
77043
+ layers: [
77044
+ {
77045
+ sourceName: background,
77046
+ zIndex: 0,
77047
+ opacity: 0,
77048
+ id: background,
77049
+ sourceRect: void 0,
77050
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77051
+ },
77052
+ ...fiveSources.map((s, i) => {
77053
+ const isTopRow = i < 2;
77054
+ const xPos = isTopRow ? topPositions[i] : bottomPositions[i - 2];
77055
+ const yPosCalc = isTopRow ? topY : bottomY;
77056
+ return {
77057
+ sourceName: s,
77058
+ zIndex: i + 1,
77059
+ opacity: 0,
77060
+ id: s,
77061
+ sourceRect: cropConfig[s] || void 0,
77062
+ destRect: { x: xPos - boxW / 2, y: yPosCalc - boxH / 2, width: boxW, height: boxH }
77063
+ };
77064
+ })
77065
+ ]
77066
+ }
77067
+ },
77068
+ ...targetSource ? [{
77069
+ durationMs: 250,
77070
+ layout: {
77071
+ layers: [{
77072
+ sourceName: targetSource,
77073
+ zIndex: 0,
77074
+ opacity: 1,
77075
+ id: targetSource,
77076
+ sourceRect: void 0,
77077
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77078
+ }]
77079
+ }
77080
+ }] : []
77081
+ ];
76387
77082
  }
76388
- ]
77083
+ })()
76389
77084
  }
76390
77085
  } : void 0
76391
77086
  };
76392
77087
  },
76393
- "6-split": (sources, config, res) => {
77088
+ "6-split": (sources, config, res, transitionContext) => {
76394
77089
  const sixSources = sources.slice(0, 6);
76395
77090
  const cropConfig = config.crops || {};
76396
77091
  const boxW = res.width * 0.34;
@@ -76454,21 +77149,77 @@ var PRESETS = {
76454
77149
  ]
76455
77150
  },
76456
77151
  to: {
76457
- fullscreen: [
76458
- {
76459
- durationMs: 500,
76460
- layout: {
76461
- layers: sixSources.map((s, i) => ({
76462
- sourceName: s,
76463
- zIndex: i,
76464
- opacity: i === 0 ? 1 : 0,
76465
- id: s,
76466
- sourceRect: void 0,
76467
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76468
- }))
76469
- }
77152
+ fullscreen: (() => {
77153
+ const targetSources = transitionContext?.targetSources ?? [];
77154
+ const targetSource = targetSources[0];
77155
+ const targetInSplit = targetSource && sixSources.includes(targetSource);
77156
+ if (targetInSplit) {
77157
+ return [
77158
+ {
77159
+ durationMs: 500,
77160
+ layout: {
77161
+ layers: sixSources.map((s, i) => ({
77162
+ sourceName: s,
77163
+ zIndex: i,
77164
+ opacity: s === targetSource ? 1 : 0,
77165
+ id: s,
77166
+ sourceRect: void 0,
77167
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77168
+ }))
77169
+ }
77170
+ },
77171
+ // Final step: target source only
77172
+ {
77173
+ durationMs: 0,
77174
+ layout: {
77175
+ layers: [{
77176
+ sourceName: targetSource,
77177
+ zIndex: 0,
77178
+ opacity: 1,
77179
+ id: targetSource,
77180
+ sourceRect: void 0,
77181
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77182
+ }]
77183
+ }
77184
+ }
77185
+ ];
77186
+ } else {
77187
+ return [
77188
+ {
77189
+ durationMs: 250,
77190
+ layout: {
77191
+ layers: sixSources.map((s, i) => {
77192
+ const row = Math.floor(i / 3);
77193
+ const col = i % 3;
77194
+ const yPos = row === 0 ? topY : bottomY;
77195
+ const xPos = xPositions[col];
77196
+ return {
77197
+ sourceName: s,
77198
+ zIndex: i,
77199
+ opacity: 0,
77200
+ id: s,
77201
+ sourceRect: cropConfig[s] || void 0,
77202
+ destRect: { x: xPos - boxW / 2, y: yPos - boxH / 2, width: boxW, height: boxH }
77203
+ };
77204
+ })
77205
+ }
77206
+ },
77207
+ ...targetSource ? [{
77208
+ durationMs: 250,
77209
+ layout: {
77210
+ layers: [{
77211
+ sourceName: targetSource,
77212
+ zIndex: 0,
77213
+ opacity: 1,
77214
+ id: targetSource,
77215
+ sourceRect: void 0,
77216
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77217
+ }]
77218
+ }
77219
+ }] : []
77220
+ ];
76470
77221
  }
76471
- ],
77222
+ })(),
76472
77223
  "mosaic-9": [
76473
77224
  // 6-split → mosaic-9: move to grid positions, fade to no crop
76474
77225
  {
@@ -76503,8 +77254,9 @@ var PRESETS = {
76503
77254
  };
76504
77255
  }
76505
77256
  };
76506
- function createLBarPresetDefinition(sources, config, res) {
77257
+ function createLBarPresetDefinition(sources, config, res, transitionContext) {
76507
77258
  const [videoSource, overlaySource] = sources;
77259
+ const lbarSources = [videoSource, overlaySource].filter(Boolean);
76508
77260
  const { videoWidth, videoHeight, videoX = 0, videoY = 0 } = config;
76509
77261
  return {
76510
77262
  name: "lbar",
@@ -76584,32 +77336,91 @@ function createLBarPresetDefinition(sources, config, res) {
76584
77336
  },
76585
77337
  // Transition from L-bar to fullscreen (reverse)
76586
77338
  to: {
76587
- fullscreen: [
76588
- // Step 1: Expand video and hide overlay (500ms)
76589
- {
76590
- durationMs: 500,
76591
- layout: {
76592
- layers: [
76593
- {
76594
- sourceName: overlaySource,
76595
- id: "overlay",
76596
- zIndex: 0,
76597
- opacity: 0,
76598
- sourceRect: void 0,
76599
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
76600
- },
76601
- {
76602
- sourceName: videoSource,
76603
- id: "video",
76604
- zIndex: 1,
76605
- opacity: 1,
76606
- sourceRect: void 0,
76607
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
77339
+ fullscreen: (() => {
77340
+ const targetSources = transitionContext?.targetSources ?? [];
77341
+ const targetSource = targetSources[0];
77342
+ const targetInLbar = targetSource && lbarSources.includes(targetSource);
77343
+ if (targetInLbar) {
77344
+ return [
77345
+ {
77346
+ durationMs: 500,
77347
+ layout: {
77348
+ layers: [
77349
+ {
77350
+ sourceName: overlaySource,
77351
+ id: "overlay",
77352
+ zIndex: 0,
77353
+ opacity: overlaySource === targetSource ? 1 : 0,
77354
+ sourceRect: void 0,
77355
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77356
+ },
77357
+ {
77358
+ sourceName: videoSource,
77359
+ id: "video",
77360
+ zIndex: 1,
77361
+ opacity: videoSource === targetSource ? 1 : 0,
77362
+ sourceRect: void 0,
77363
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77364
+ }
77365
+ ]
76608
77366
  }
76609
- ]
76610
- }
77367
+ },
77368
+ // Final step: target source only
77369
+ {
77370
+ durationMs: 0,
77371
+ layout: {
77372
+ layers: [{
77373
+ sourceName: targetSource,
77374
+ zIndex: 0,
77375
+ opacity: 1,
77376
+ id: targetSource,
77377
+ sourceRect: void 0,
77378
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77379
+ }]
77380
+ }
77381
+ }
77382
+ ];
77383
+ } else {
77384
+ return [
77385
+ {
77386
+ durationMs: 250,
77387
+ layout: {
77388
+ layers: [
77389
+ {
77390
+ sourceName: overlaySource,
77391
+ id: "overlay",
77392
+ zIndex: 0,
77393
+ opacity: 0,
77394
+ sourceRect: void 0,
77395
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77396
+ },
77397
+ {
77398
+ sourceName: videoSource,
77399
+ id: "video",
77400
+ zIndex: 1,
77401
+ opacity: 0,
77402
+ sourceRect: void 0,
77403
+ destRect: { x: videoX, y: videoY, width: videoWidth, height: videoHeight }
77404
+ }
77405
+ ]
77406
+ }
77407
+ },
77408
+ ...targetSource ? [{
77409
+ durationMs: 250,
77410
+ layout: {
77411
+ layers: [{
77412
+ sourceName: targetSource,
77413
+ zIndex: 0,
77414
+ opacity: 1,
77415
+ id: targetSource,
77416
+ sourceRect: void 0,
77417
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
77418
+ }]
77419
+ }
77420
+ }] : []
77421
+ ];
76611
77422
  }
76612
- ]
77423
+ })()
76613
77424
  }
76614
77425
  }
76615
77426
  };
@@ -76651,16 +77462,16 @@ function PresetVisualPreview({ layers, size: size4 = "small", referenceResolutio
76651
77462
 
76652
77463
  // build/client/processor.videoCompose/preset-transition-panel.js
76653
77464
  function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPreset, sourceSelection, onSourceSelectionChange, presetConfig, onPresetConfigChange }) {
76654
- const [transition, setTransition] = (0, import_react28.useState)({
77465
+ const [transition, setTransition] = (0, import_react29.useState)({
76655
77466
  durationMs: 1e3,
76656
77467
  easing: "ease_in_out"
76657
77468
  });
76658
- const lastPresetRef = (0, import_react28.useRef)(null);
76659
- const loadingSceneRef = (0, import_react28.useRef)(false);
77469
+ const lastPresetRef = (0, import_react29.useRef)(null);
77470
+ const loadingSceneRef = (0, import_react29.useRef)(false);
76660
77471
  const metadata = selectedPreset ? PRESET_METADATA[selectedPreset] : null;
76661
77472
  const reference = state.sources.find((s) => s.sourceName === state.reference);
76662
- const onlineSourceNames = (0, import_react28.useMemo)(() => state.sources.filter((s) => s.status === "online").map((s) => s.sourceName).join(","), [state.sources]);
76663
- (0, import_react28.useEffect)(() => {
77473
+ const onlineSourceNames = (0, import_react29.useMemo)(() => state.sources.filter((s) => s.status === "online").map((s) => s.sourceName).join(","), [state.sources]);
77474
+ (0, import_react29.useEffect)(() => {
76664
77475
  if (!selectedPreset || !metadata)
76665
77476
  return;
76666
77477
  if (lastPresetRef.current === selectedPreset)
@@ -76693,13 +77504,13 @@ function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPre
76693
77504
  onPresetConfigChange(defaults2);
76694
77505
  }
76695
77506
  }, [metadata, onPresetConfigChange, onSourceSelectionChange, onlineSourceNames, reference?.resolution, selectedPreset, state.sources]);
76696
- (0, import_react28.useEffect)(() => {
77507
+ (0, import_react29.useEffect)(() => {
76697
77508
  if (!selectedPreset || sourceSelection.length === 0)
76698
77509
  return;
76699
77510
  const storageKey = `videoCompose.preset.${selectedPreset}.sources`;
76700
77511
  localStorage.setItem(storageKey, JSON.stringify(sourceSelection));
76701
77512
  }, [selectedPreset, sourceSelection]);
76702
- const handlePreview = (0, import_react28.useCallback)(() => {
77513
+ const handlePreview = (0, import_react29.useCallback)(() => {
76703
77514
  if (!selectedPreset || !metadata)
76704
77515
  return;
76705
77516
  const layout = {
@@ -76713,11 +77524,20 @@ function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPre
76713
77524
  layout
76714
77525
  });
76715
77526
  }, [selectedPreset, metadata, sourceSelection, presetConfig, sendCommand]);
76716
- const handleCancelPreview = (0, import_react28.useCallback)(() => {
77527
+ const handleCancelPreview = (0, import_react29.useCallback)(() => {
76717
77528
  sendCommand({ type: "disable-preview" });
76718
77529
  }, [sendCommand]);
76719
- const handleApplyPreview = (0, import_react28.useCallback)(() => {
76720
- const hasOrchestration = selectedPreset && metadata && PRESETS[selectedPreset]?.(sourceSelection, presetConfig, { width: 1920, height: 1080 })?.orchestrations;
77530
+ const hasOrchestration = (0, import_react29.useMemo)(() => {
77531
+ if (!selectedPreset)
77532
+ return false;
77533
+ try {
77534
+ const presetDef = PRESETS[selectedPreset]?.(sourceSelection, presetConfig, { width: 1920, height: 1080 });
77535
+ return !!presetDef?.orchestrations;
77536
+ } catch {
77537
+ return false;
77538
+ }
77539
+ }, [selectedPreset, sourceSelection, presetConfig]);
77540
+ const handleApplyPreview = (0, import_react29.useCallback)(() => {
76721
77541
  sendCommand({
76722
77542
  type: "apply-preview",
76723
77543
  // Only pass transition if there's no orchestration
@@ -76728,8 +77548,8 @@ function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPre
76728
77548
  }
76729
77549
  }
76730
77550
  });
76731
- }, [sendCommand, transition.durationMs, transition.easing, selectedPreset, metadata, sourceSelection, presetConfig]);
76732
- const handleApplyDirect = (0, import_react28.useCallback)(() => {
77551
+ }, [sendCommand, transition.durationMs, transition.easing, hasOrchestration]);
77552
+ const handleApplyDirect = (0, import_react29.useCallback)(() => {
76733
77553
  if (!selectedPreset || !metadata)
76734
77554
  return;
76735
77555
  const layout = {
@@ -76738,7 +77558,6 @@ function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPre
76738
77558
  sourceSelection: sourceSelection.length > 0 ? sourceSelection : void 0,
76739
77559
  ...metadata.hasConfig ? { config: presetConfig } : {}
76740
77560
  };
76741
- const hasOrchestration = PRESETS[selectedPreset]?.(sourceSelection, presetConfig, { width: 1920, height: 1080 })?.orchestrations;
76742
77561
  sendCommand({
76743
77562
  type: "update-layout",
76744
77563
  layout,
@@ -76750,8 +77569,8 @@ function PresetTransitionPanel({ state, sendCommand, selectedPreset, onSelectPre
76750
77569
  }
76751
77570
  }
76752
77571
  });
76753
- }, [selectedPreset, metadata, sourceSelection, presetConfig, sendCommand, transition]);
76754
- const referenceResolution = (0, import_react28.useMemo)(() => {
77572
+ }, [selectedPreset, metadata, sourceSelection, presetConfig, sendCommand, transition, hasOrchestration]);
77573
+ const referenceResolution = (0, import_react29.useMemo)(() => {
76755
77574
  const reference2 = state.sources.find((s) => s.sourceName === state.reference);
76756
77575
  return reference2?.resolution;
76757
77576
  }, [state.sources, state.reference]);
@@ -76776,7 +77595,7 @@ function SceneCard({ scene, isActive, onSelect, onDelete }) {
76776
77595
  }, className: "absolute top-1 right-1 p-1 rounded bg-red-600 text-white hover:bg-red-700\n opacity-0 group-hover:opacity-100 transition-opacity", title: "Delete scene", children: (0, import_jsx_runtime63.jsx)("svg", { className: "w-3 h-3", fill: "currentColor", viewBox: "0 0 20 20", children: (0, import_jsx_runtime63.jsx)("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) }) })] });
76777
77596
  }
76778
77597
  function PresetCard({ presetKey, metadata, onClick, referenceResolution }) {
76779
- const previewLayout = (0, import_react28.useMemo)(() => {
77598
+ const previewLayout = (0, import_react29.useMemo)(() => {
76780
77599
  if (!referenceResolution)
76781
77600
  return null;
76782
77601
  const generator = PRESETS[presetKey];
@@ -76794,7 +77613,7 @@ function PresetCard({ presetKey, metadata, onClick, referenceResolution }) {
76794
77613
  return (0, import_jsx_runtime63.jsxs)("button", { className: "p-1.5 rounded border border-gray-300 dark:border-gray-600\n hover:border-blue-500 dark:hover:border-blue-400\n hover:bg-blue-50 dark:hover:bg-blue-900/20\n bg-white dark:bg-gray-800\n text-left transition-colors flex flex-col gap-1.5", onClick, children: [(0, import_jsx_runtime63.jsx)("div", { className: "w-full flex justify-center", children: previewLayout && referenceResolution ? (0, import_jsx_runtime63.jsx)(PresetVisualPreview, { layers: previewLayout.layers, size: "small", referenceResolution }) : (0, import_jsx_runtime63.jsx)("div", { className: "w-16 h-9 bg-gray-700 dark:bg-gray-900 rounded flex items-center justify-center", children: (0, import_jsx_runtime63.jsx)("span", { className: "text-xs text-gray-400", children: "?" }) }) }), (0, import_jsx_runtime63.jsxs)("div", { className: "w-full", children: [(0, import_jsx_runtime63.jsx)("h3", { className: "font-medium text-xs text-gray-900 dark:text-white truncate text-center", children: metadata.name }), (0, import_jsx_runtime63.jsxs)("div", { className: "flex items-center justify-center gap-1 mt-0.5", children: [(0, import_jsx_runtime63.jsx)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: metadata.minSources === metadata.maxSources ? `${metadata.minSources} src${metadata.minSources > 1 ? "s" : ""}` : `${metadata.minSources}-${metadata.maxSources} srcs` }), metadata.hasConfig && (0, import_jsx_runtime63.jsx)("span", { className: "text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300\n px-1 py-0.5 rounded", children: "Config" })] })] })] });
76795
77614
  }
76796
77615
  function SaveSceneForm({ onSave, onCancel }) {
76797
- const [name, setName] = (0, import_react28.useState)("");
77616
+ const [name, setName] = (0, import_react29.useState)("");
76798
77617
  return (0, import_jsx_runtime63.jsxs)("div", { className: "space-y-2 p-3 bg-gray-50 dark:bg-gray-900 rounded", children: [(0, import_jsx_runtime63.jsx)("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: "Scene Name" }), (0, import_jsx_runtime63.jsx)("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "Enter scene name...", className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600\n rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white", autoFocus: true }), (0, import_jsx_runtime63.jsxs)("div", { className: "flex gap-2", children: [(0, import_jsx_runtime63.jsx)("button", { onClick: () => {
76799
77618
  if (name.trim()) {
76800
77619
  onSave(name.trim());
@@ -76807,9 +77626,29 @@ function SaveSceneForm({ onSave, onCancel }) {
76807
77626
  }
76808
77627
  function PresetConfiguration(props) {
76809
77628
  const { metadata, onPreview, onCancelPreview, onApplyPreview, onApplyDirect, onCancel, isTransitioning, isPreviewActive, sourceSelection, state: _state, sendCommand } = props;
76810
- const [activeTab, setActiveTab] = (0, import_react28.useState)("sources");
76811
- const [showSaveForm, setShowSaveForm] = (0, import_react28.useState)(false);
77629
+ const [activeTab, setActiveTab] = (0, import_react29.useState)("sources");
77630
+ const [showSaveForm, setShowSaveForm] = (0, import_react29.useState)(false);
77631
+ const [isPending, setIsPending] = (0, import_react29.useState)(false);
77632
+ (0, import_react29.useEffect)(() => {
77633
+ if (isTransitioning) {
77634
+ setIsPending(false);
77635
+ }
77636
+ }, [isTransitioning]);
77637
+ (0, import_react29.useEffect)(() => {
77638
+ if (isPending) {
77639
+ const timeout = setTimeout(() => setIsPending(false), 1e4);
77640
+ return () => clearTimeout(timeout);
77641
+ }
77642
+ }, [isPending]);
76812
77643
  const canPreview = sourceSelection.length >= metadata.minSources;
77644
+ const handleApplyPreviewWithPending = (0, import_react29.useCallback)(() => {
77645
+ setIsPending(true);
77646
+ onApplyPreview();
77647
+ }, [onApplyPreview]);
77648
+ const handleApplyDirectWithPending = (0, import_react29.useCallback)(() => {
77649
+ setIsPending(true);
77650
+ onApplyDirect();
77651
+ }, [onApplyDirect]);
76813
77652
  const handleSaveScene = (name) => {
76814
77653
  const layout = {
76815
77654
  type: "preset",
@@ -76820,7 +77659,7 @@ function PresetConfiguration(props) {
76820
77659
  sendCommand({ type: "create-scene", name, layout });
76821
77660
  setShowSaveForm(false);
76822
77661
  };
76823
- return (0, import_jsx_runtime63.jsxs)("div", { className: "h-full flex flex-col", children: [(0, import_jsx_runtime63.jsxs)("div", { className: "p-6 border-b border-gray-200 dark:border-gray-700", children: [(0, import_jsx_runtime63.jsx)("button", { onClick: onCancel, className: "text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700\n dark:hover:text-blue-300 mb-2", children: "\u2190 Back to Presets" }), (0, import_jsx_runtime63.jsx)("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: metadata.name }), (0, import_jsx_runtime63.jsx)("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1", children: metadata.description })] }), (0, import_jsx_runtime63.jsx)("div", { className: "flex-1 overflow-y-auto", children: metadata.hasConfig ? (0, import_jsx_runtime63.jsxs)("div", { children: [(0, import_jsx_runtime63.jsx)("div", { className: "border-b border-gray-200 dark:border-gray-700 px-6", children: (0, import_jsx_runtime63.jsxs)("div", { className: "flex gap-6", children: [(0, import_jsx_runtime63.jsx)("button", { onClick: () => setActiveTab("sources"), className: `py-3 border-b-2 font-medium text-sm transition-colors ${activeTab === "sources" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"}`, children: "Source Ordering" }), (0, import_jsx_runtime63.jsx)("button", { onClick: () => setActiveTab("config"), className: `py-3 border-b-2 font-medium text-sm transition-colors ${activeTab === "config" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"}`, children: "Configuration" })] }) }), (0, import_jsx_runtime63.jsx)("div", { className: "p-6", children: activeTab === "sources" ? (0, import_jsx_runtime63.jsx)(SourceOrderingPanel, { ...props }) : (0, import_jsx_runtime63.jsx)(ConfigPanel, { presetKey: props.presetKey, config: props.config, onConfigChange: props.onConfigChange }) })] }) : (0, import_jsx_runtime63.jsx)("div", { className: "p-6", children: (0, import_jsx_runtime63.jsx)(SourceOrderingPanel, { ...props }) }) }), (0, import_jsx_runtime63.jsxs)("div", { className: "border-t border-gray-200 dark:border-gray-700 p-6 space-y-4", children: [showSaveForm ? (0, import_jsx_runtime63.jsx)(SaveSceneForm, { onSave: handleSaveScene, onCancel: () => setShowSaveForm(false) }) : (0, import_jsx_runtime63.jsx)("button", { onClick: () => setShowSaveForm(true), disabled: !canPreview, className: "w-full px-4 py-2 text-sm rounded border-2 border-dashed\n border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400\n hover:border-green-500 hover:text-green-600 dark:hover:border-green-400\n dark:hover:text-green-400 transition-colors\n disabled:opacity-50 disabled:cursor-not-allowed", children: "Save as Scene" }), (0, import_jsx_runtime63.jsx)(PreviewControls, { transition: props.transition, onTransitionChange: props.onTransitionChange, onPreview, onCancelPreview, onApplyPreview, onApplyDirect, isTransitioning, isPreviewActive, canPreview, presetKey: props.presetKey, sourceSelection, config: props.config })] })] });
77662
+ return (0, import_jsx_runtime63.jsxs)("div", { className: "h-full flex flex-col", children: [(0, import_jsx_runtime63.jsxs)("div", { className: "p-6 border-b border-gray-200 dark:border-gray-700", children: [(0, import_jsx_runtime63.jsx)("button", { onClick: onCancel, className: "text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700\n dark:hover:text-blue-300 mb-2", children: "\u2190 Back to Presets" }), (0, import_jsx_runtime63.jsx)("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: metadata.name }), (0, import_jsx_runtime63.jsx)("p", { className: "text-sm text-gray-600 dark:text-gray-400 mt-1", children: metadata.description })] }), (0, import_jsx_runtime63.jsx)("div", { className: "flex-1 overflow-y-auto", children: metadata.hasConfig ? (0, import_jsx_runtime63.jsxs)("div", { children: [(0, import_jsx_runtime63.jsx)("div", { className: "border-b border-gray-200 dark:border-gray-700 px-6", children: (0, import_jsx_runtime63.jsxs)("div", { className: "flex gap-6", children: [(0, import_jsx_runtime63.jsx)("button", { onClick: () => setActiveTab("sources"), className: `py-3 border-b-2 font-medium text-sm transition-colors ${activeTab === "sources" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"}`, children: "Source Ordering" }), (0, import_jsx_runtime63.jsx)("button", { onClick: () => setActiveTab("config"), className: `py-3 border-b-2 font-medium text-sm transition-colors ${activeTab === "config" ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"}`, children: "Configuration" })] }) }), (0, import_jsx_runtime63.jsx)("div", { className: "p-6", children: activeTab === "sources" ? (0, import_jsx_runtime63.jsx)(SourceOrderingPanel, { ...props }) : (0, import_jsx_runtime63.jsx)(ConfigPanel, { presetKey: props.presetKey, config: props.config, onConfigChange: props.onConfigChange }) })] }) : (0, import_jsx_runtime63.jsx)("div", { className: "p-6", children: (0, import_jsx_runtime63.jsx)(SourceOrderingPanel, { ...props }) }) }), (0, import_jsx_runtime63.jsxs)("div", { className: "border-t border-gray-200 dark:border-gray-700 p-6 space-y-4", children: [showSaveForm ? (0, import_jsx_runtime63.jsx)(SaveSceneForm, { onSave: handleSaveScene, onCancel: () => setShowSaveForm(false) }) : (0, import_jsx_runtime63.jsx)("button", { onClick: () => setShowSaveForm(true), disabled: !canPreview, className: "w-full px-4 py-2 text-sm rounded border-2 border-dashed\n border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-400\n hover:border-green-500 hover:text-green-600 dark:hover:border-green-400\n dark:hover:text-green-400 transition-colors\n disabled:opacity-50 disabled:cursor-not-allowed", children: "Save as Scene" }), (0, import_jsx_runtime63.jsx)(PreviewControls, { transition: props.transition, onTransitionChange: props.onTransitionChange, onPreview, onCancelPreview, onApplyPreview: handleApplyPreviewWithPending, onApplyDirect: handleApplyDirectWithPending, isTransitioning, isPreviewActive, canPreview, presetKey: props.presetKey, sourceSelection, config: props.config, isPending })] })] });
76824
77663
  }
76825
77664
  function SourceOrderingPanel({ metadata, sourceSelection, onSourceSelectionChange, availableSources }) {
76826
77665
  const usedSourceNames = new Set(sourceSelection);
@@ -76878,8 +77717,9 @@ function ConfigPanel({ presetKey, config, onConfigChange }) {
76878
77717
  }
76879
77718
  return null;
76880
77719
  }
76881
- function PreviewControls({ transition, onTransitionChange, onPreview, onCancelPreview, onApplyPreview, onApplyDirect, isTransitioning, isPreviewActive, canPreview, presetKey, sourceSelection, config }) {
76882
- const hasOrchestration = (0, import_react28.useMemo)(() => {
77720
+ function PreviewControls({ transition, onTransitionChange, onPreview, onCancelPreview, onApplyPreview, onApplyDirect, isTransitioning, isPreviewActive, canPreview, presetKey, sourceSelection, config, isPending }) {
77721
+ const isBusy = isPending || isTransitioning;
77722
+ const hasOrchestration = (0, import_react29.useMemo)(() => {
76883
77723
  try {
76884
77724
  const presetDef = PRESETS[presetKey]?.(sourceSelection, config, { width: 1920, height: 1080 });
76885
77725
  return !!presetDef?.orchestrations;
@@ -76893,22 +77733,22 @@ function PreviewControls({ transition, onTransitionChange, onPreview, onCancelPr
76893
77733
  }), className: "w-full accent-blue-600" }), (0, import_jsx_runtime63.jsxs)("div", { className: "flex justify-between text-xs text-gray-500", children: [(0, import_jsx_runtime63.jsx)("span", { children: "Instant" }), (0, import_jsx_runtime63.jsx)("span", { children: "3s" })] })] }), (0, import_jsx_runtime63.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime63.jsx)("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: "Easing" }), (0, import_jsx_runtime63.jsxs)("select", { value: transition.easing, onChange: (e) => onTransitionChange({
76894
77734
  ...transition,
76895
77735
  easing: e.target.value
76896
- }), className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600\n rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white", children: [(0, import_jsx_runtime63.jsx)("option", { value: "linear", children: "Linear" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_in", children: "Ease In" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_out", children: "Ease Out" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_in_out", children: "Ease In Out" })] })] })] }), (0, import_jsx_runtime63.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-3 rounded-lg font-medium\n bg-green-600 text-white hover:bg-green-700\n disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors", onClick: isPreviewActive ? onApplyPreview : onApplyDirect, disabled: !canPreview || isTransitioning, children: isTransitioning ? "Transitioning..." : "Take to Live" }), isPreviewActive ? (0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-2 rounded-lg font-medium\n bg-red-600 text-white hover:bg-red-700\n transition-colors", onClick: onCancelPreview, children: "Cancel Preview" }) : (0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-2 rounded-lg font-medium\n border border-blue-600 text-blue-600 dark:text-blue-400\n hover:bg-blue-50 dark:hover:bg-blue-900/20\n disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors", onClick: onPreview, disabled: !canPreview || isTransitioning, children: "Preview Video" })] })] });
77736
+ }), className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600\n rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-white", children: [(0, import_jsx_runtime63.jsx)("option", { value: "linear", children: "Linear" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_in", children: "Ease In" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_out", children: "Ease Out" }), (0, import_jsx_runtime63.jsx)("option", { value: "ease_in_out", children: "Ease In Out" })] })] })] }), (0, import_jsx_runtime63.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-3 rounded-lg font-medium\n bg-green-600 text-white hover:bg-green-700\n disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors", onClick: isPreviewActive ? onApplyPreview : onApplyDirect, disabled: !canPreview || isBusy, children: isBusy ? "Transitioning..." : "Take to Live" }), isPreviewActive ? (0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-2 rounded-lg font-medium\n bg-red-600 text-white hover:bg-red-700\n transition-colors", onClick: onCancelPreview, children: "Cancel Preview" }) : (0, import_jsx_runtime63.jsx)("button", { className: "w-full px-4 py-2 rounded-lg font-medium\n border border-blue-600 text-blue-600 dark:text-blue-400\n hover:bg-blue-50 dark:hover:bg-blue-900/20\n disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors", onClick: onPreview, disabled: !canPreview || isBusy, children: "Preview Video" })] })] });
76897
77737
  }
76898
77738
 
76899
77739
  // build/client/processor.videoCompose/webrtc-preview.js
76900
77740
  var import_jsx_runtime64 = __toESM(require_jsx_runtime());
76901
- var import_react29 = __toESM(require_react());
77741
+ var import_react30 = __toESM(require_react());
76902
77742
  var import_webrtc_client2 = __toESM(require_webrtc_client());
76903
77743
  function WebRTCPreview({ url, id, onPlaying }) {
76904
- const [_client, setClient] = (0, import_react29.useState)(null);
76905
- const [error, setError] = (0, import_react29.useState)(null);
76906
- const [connecting, setConnecting] = (0, import_react29.useState)(false);
76907
- const onPlayingRef = (0, import_react29.useRef)(onPlaying);
76908
- (0, import_react29.useEffect)(() => {
77744
+ const [_client, setClient] = (0, import_react30.useState)(null);
77745
+ const [error, setError] = (0, import_react30.useState)(null);
77746
+ const [connecting, setConnecting] = (0, import_react30.useState)(false);
77747
+ const onPlayingRef = (0, import_react30.useRef)(onPlaying);
77748
+ (0, import_react30.useEffect)(() => {
76909
77749
  onPlayingRef.current = onPlaying;
76910
77750
  }, [onPlaying]);
76911
- const createClient = (0, import_react29.useCallback)((url2) => {
77751
+ const createClient = (0, import_react30.useCallback)((url2) => {
76912
77752
  console.log(`[WebRTCPreview ${id}] Creating WHEP client for URL:`, url2);
76913
77753
  setConnecting(true);
76914
77754
  const client = new import_webrtc_client2.WhepClient({
@@ -76949,7 +77789,7 @@ function WebRTCPreview({ url, id, onPlaying }) {
76949
77789
  });
76950
77790
  return client;
76951
77791
  }, [id]);
76952
- const cleanupClient = (0, import_react29.useCallback)(() => {
77792
+ const cleanupClient = (0, import_react30.useCallback)(() => {
76953
77793
  setClient((client) => {
76954
77794
  if (!client)
76955
77795
  return null;
@@ -76966,7 +77806,7 @@ function WebRTCPreview({ url, id, onPlaying }) {
76966
77806
  setError(null);
76967
77807
  setConnecting(false);
76968
77808
  }, []);
76969
- (0, import_react29.useEffect)(() => {
77809
+ (0, import_react30.useEffect)(() => {
76970
77810
  if (url) {
76971
77811
  setError(null);
76972
77812
  const client = createClient(url);
@@ -77012,7 +77852,9 @@ function VisualPreview({ layout, sourceJpegUrls, reference, title }) {
77012
77852
  height: `${heightPercent}%`,
77013
77853
  opacity: layer.opacity,
77014
77854
  zIndex: layer.zIndex
77015
- }, children: [jpegUrl ? (0, import_jsx_runtime65.jsx)("img", { src: jpegUrl, alt: layer.sourceName, className: "w-full h-full object-cover", onError: (e) => {
77855
+ }, children: [jpegUrl ? (0, import_jsx_runtime65.jsx)("img", { src: jpegUrl, alt: layer.sourceName, className: "w-full h-full object-cover", loading: "lazy", decoding: "async", onLoad: (e) => {
77856
+ e.currentTarget.style.display = "";
77857
+ }, onError: (e) => {
77016
77858
  e.currentTarget.style.display = "none";
77017
77859
  } }) : (0, import_jsx_runtime65.jsx)("div", { className: "w-full h-full bg-gray-700 flex items-center justify-center", children: (0, import_jsx_runtime65.jsxs)("div", { className: "text-center p-2", children: [(0, import_jsx_runtime65.jsx)("div", { className: "text-xs text-gray-400 truncate", children: layer.sourceName }), (0, import_jsx_runtime65.jsx)("div", { className: "text-xs text-gray-500 mt-1", children: "No preview" })] }) }), (0, import_jsx_runtime65.jsx)("div", { className: "absolute top-1 left-1 px-1.5 py-0.5\n bg-black/60 rounded text-xs text-white\n font-mono", children: layer.sourceName }), (0, import_jsx_runtime65.jsx)("div", { className: "absolute top-1 right-1 w-5 h-5\n bg-blue-600 rounded-full flex items-center justify-center\n text-xs text-white font-semibold", children: layer.zIndex }), layer.opacity < 1 && (0, import_jsx_runtime65.jsxs)("div", { className: "absolute bottom-1 right-1 px-1.5 py-0.5\n bg-black/60 rounded text-xs text-white", children: [(layer.opacity * 100).toFixed(0), "%"] })] }, idx);
77018
77860
  }) })] });
@@ -77020,19 +77862,34 @@ function VisualPreview({ layout, sourceJpegUrls, reference, title }) {
77020
77862
 
77021
77863
  // build/client/processor.videoCompose/fullscreen.js
77022
77864
  function FullscreenView3({ state, config: _config, sendCommand }) {
77023
- const [showVideoPreview, setShowVideoPreview] = (0, import_react30.useState)(false);
77024
- const [pendingPreviewCommand, setPendingPreviewCommand] = (0, import_react30.useState)(null);
77025
- const [selectedPreset, setSelectedPreset] = (0, import_react30.useState)(null);
77026
- const [sourceSelection, setSourceSelection] = (0, import_react30.useState)([]);
77027
- const [presetConfig, setPresetConfig] = (0, import_react30.useState)({});
77028
- const prevConfigRef = (0, import_react30.useRef)({ selectedPreset, sourceSelection, presetConfig });
77029
- const handleSourceSelectionChange = (0, import_react30.useCallback)((sources) => {
77865
+ const [showVideoPreview, setShowVideoPreview] = (0, import_react31.useState)(false);
77866
+ const [pendingPreviewCommand, setPendingPreviewCommand] = (0, import_react31.useState)(null);
77867
+ const [selectedPreset, setSelectedPreset] = (0, import_react31.useState)(null);
77868
+ const [sourceSelection, setSourceSelection] = (0, import_react31.useState)([]);
77869
+ const [presetConfig, setPresetConfig] = (0, import_react31.useState)({});
77870
+ const prevConfigRef = (0, import_react31.useRef)({ selectedPreset, sourceSelection, presetConfig });
77871
+ const [throttledJpegUrls, setThrottledJpegUrls] = (0, import_react31.useState)({});
77872
+ const lastJpegUpdateRef = (0, import_react31.useRef)(0);
77873
+ (0, import_react31.useEffect)(() => {
77874
+ const now = Date.now();
77875
+ if (now - lastJpegUpdateRef.current > 2e3) {
77876
+ lastJpegUpdateRef.current = now;
77877
+ setThrottledJpegUrls(state.sourceJpegUrls ?? {});
77878
+ }
77879
+ }, [state.sourceJpegUrls]);
77880
+ const [imagesReady, setImagesReady] = (0, import_react31.useState)(false);
77881
+ (0, import_react31.useEffect)(() => {
77882
+ const timer = setTimeout(() => setImagesReady(true), 100);
77883
+ return () => clearTimeout(timer);
77884
+ }, []);
77885
+ const onlineSourceCount = (0, import_react31.useMemo)(() => state.sources.filter((s) => s.status === "online").length, [state.sources]);
77886
+ const handleSourceSelectionChange = (0, import_react31.useCallback)((sources) => {
77030
77887
  setSourceSelection(sources);
77031
77888
  }, []);
77032
- const handlePresetConfigChange = (0, import_react30.useCallback)((config) => {
77889
+ const handlePresetConfigChange = (0, import_react31.useCallback)((config) => {
77033
77890
  setPresetConfig(config);
77034
77891
  }, []);
77035
- (0, import_react30.useEffect)(() => {
77892
+ (0, import_react31.useEffect)(() => {
77036
77893
  const prev = prevConfigRef.current;
77037
77894
  const configChanged = prev.selectedPreset !== selectedPreset || JSON.stringify(prev.sourceSelection) !== JSON.stringify(sourceSelection) || JSON.stringify(prev.presetConfig) !== JSON.stringify(presetConfig);
77038
77895
  if (configChanged) {
@@ -77044,23 +77901,20 @@ function FullscreenView3({ state, config: _config, sendCommand }) {
77044
77901
  prevConfigRef.current = { selectedPreset, sourceSelection, presetConfig };
77045
77902
  }
77046
77903
  }, [selectedPreset, sourceSelection, presetConfig, state.previewMode, sendCommand]);
77047
- const handlePreviewPlaying = (0, import_react30.useCallback)(() => {
77904
+ const handlePreviewPlaying = (0, import_react31.useCallback)(() => {
77048
77905
  console.log("[FullscreenView] Preview video started playing, sending pending command");
77049
77906
  if (pendingPreviewCommand) {
77050
77907
  sendCommand(pendingPreviewCommand);
77051
77908
  setPendingPreviewCommand(null);
77052
77909
  }
77053
77910
  }, [pendingPreviewCommand, sendCommand]);
77054
- const handleSendCommand = (0, import_react30.useCallback)((cmd) => {
77911
+ const handleSendCommand = (0, import_react31.useCallback)((cmd) => {
77055
77912
  if (cmd.type === "enable-preview") {
77056
- console.log("[FullscreenView] Deferring enable-preview until video starts playing");
77057
77913
  setShowVideoPreview(true);
77058
77914
  setPendingPreviewCommand(cmd);
77059
77915
  setTimeout(() => {
77060
- console.log("[FullscreenView] Timeout reached, checking if command still pending");
77061
77916
  setPendingPreviewCommand((pending) => {
77062
77917
  if (pending) {
77063
- console.log("[FullscreenView] Sending command due to timeout");
77064
77918
  sendCommand(pending);
77065
77919
  return null;
77066
77920
  }
@@ -77075,22 +77929,26 @@ function FullscreenView3({ state, config: _config, sendCommand }) {
77075
77929
  sendCommand(cmd);
77076
77930
  }
77077
77931
  }, [sendCommand]);
77078
- const previewLayout = (() => {
77932
+ const referenceResolution = (0, import_react31.useMemo)(() => {
77933
+ const ref = state.sources.find((s) => s.sourceName === state.reference);
77934
+ return ref?.resolution;
77935
+ }, [state.sources, state.reference]);
77936
+ const referenceResolutionKey = referenceResolution ? `${referenceResolution.width}x${referenceResolution.height}` : null;
77937
+ const previewLayout = (0, import_react31.useMemo)(() => {
77079
77938
  if (!selectedPreset || sourceSelection.length === 0)
77080
77939
  return void 0;
77081
- const reference = state.sources.find((s) => s.sourceName === state.reference);
77082
- if (!reference?.resolution)
77940
+ if (!referenceResolution)
77083
77941
  return void 0;
77084
77942
  const generator = PRESETS[selectedPreset];
77085
77943
  if (!generator)
77086
77944
  return void 0;
77087
77945
  try {
77088
- const presetDef = generator(sourceSelection, presetConfig, { width: reference.resolution.width, height: reference.resolution.height });
77946
+ const presetDef = generator(sourceSelection, presetConfig, { width: referenceResolution.width, height: referenceResolution.height });
77089
77947
  return presetDef.finalConfig;
77090
77948
  } catch (_e) {
77091
77949
  return void 0;
77092
77950
  }
77093
- })();
77951
+ }, [selectedPreset, sourceSelection, presetConfig, referenceResolutionKey]);
77094
77952
  if (!state) {
77095
77953
  return (0, import_jsx_runtime66.jsx)("div", { className: "flex h-full items-center justify-center bg-gray-50 dark:bg-gray-900", children: (0, import_jsx_runtime66.jsxs)("div", { className: "text-center", children: [(0, import_jsx_runtime66.jsx)("div", { className: "text-xl font-semibold text-gray-900 dark:text-white mb-2", children: "Loading Video Compose..." }), (0, import_jsx_runtime66.jsx)("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Connecting to component state..." })] }) });
77096
77954
  }
@@ -77101,7 +77959,7 @@ function FullscreenView3({ state, config: _config, sendCommand }) {
77101
77959
  }, sendCommand: handleSendCommand, selectedPreset, onSelectPreset: setSelectedPreset, sourceSelection, onSourceSelectionChange: handleSourceSelectionChange, presetConfig, onPresetConfigChange: handlePresetConfigChange }) }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 flex flex-col", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 flex gap-6 p-6 min-h-0", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 flex flex-col min-w-0", children: [(0, import_jsx_runtime66.jsx)("div", { className: "flex items-center justify-between mb-4", children: (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-3", children: [(0, import_jsx_runtime66.jsx)("h2", { className: "text-xl font-bold text-gray-900 dark:text-white", children: showVideoPreview ? "Preview Transition" : "Preview Output" }), state.previewTransitioning && (0, import_jsx_runtime66.jsx)("span", { className: "text-sm text-blue-600 dark:text-blue-400 font-medium", children: "Transitioning..." })] }) }), showVideoPreview ? (0, import_jsx_runtime66.jsx)(WebRTCPreview, { url: state.previewPreviewUrl, id: `${_config.id}-preview`, onPlaying: handlePreviewPlaying }, state.previewPreviewUrl) : (0, import_jsx_runtime66.jsx)("div", { className: "flex-1 flex items-center justify-center bg-gray-900 rounded-lg overflow-hidden", children: (0, import_jsx_runtime66.jsx)("div", { className: "w-full h-full flex items-center justify-center p-4", children: (0, import_jsx_runtime66.jsx)("div", { className: "w-full max-w-full max-h-full", children: (0, import_jsx_runtime66.jsx)(VisualPreview, { layout: (
77102
77960
  // Show preset being built, or preview composition, or current layout
77103
77961
  previewLayout ? previewLayout : state.previewMode && state.previewResolvedLayout ? state.previewResolvedLayout : state.resolvedLayout
77104
- ), sourceJpegUrls: state.sourceJpegUrls, reference: state.sources.find((s) => s.sourceName === state.reference)?.resolution, title: previewLayout ? "Preset Preview (Building...)" : state.previewMode ? "Preview Composition" : "Current Layout" }) }) }) })] }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 flex flex-col min-w-0", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, import_jsx_runtime66.jsx)("h2", { className: "text-xl font-bold text-gray-900 dark:text-white", children: "Live Output" }), state.transitioning && (0, import_jsx_runtime66.jsx)("span", { className: "text-sm text-blue-600 dark:text-blue-400 font-medium", children: "Transitioning..." }), !state.previewUrl && !state.referenceAvailable && (0, import_jsx_runtime66.jsx)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Waiting for reference stream..." })] }), (0, import_jsx_runtime66.jsx)(WebRTCPreview, { url: state.previewUrl, id: `${_config.id}-live` })] })] }), (0, import_jsx_runtime66.jsxs)("div", { className: "border-t border-gray-200 dark:border-gray-700\n bg-white dark:bg-gray-800 h-64 flex flex-col", children: [(0, import_jsx_runtime66.jsx)("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700", children: (0, import_jsx_runtime66.jsx)("h2", { className: "text-lg font-bold text-gray-900 dark:text-white", children: "Status" }) }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between", children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Sources" }), (0, import_jsx_runtime66.jsxs)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [state.sources.filter((s) => s.status === "online").length, " / ", state.sources.length, " online"] })] }), (0, import_jsx_runtime66.jsx)("div", { className: "space-y-1", children: state.sources.map((source, i) => (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 p-1.5 rounded bg-gray-50 dark:bg-gray-900", children: [(0, import_jsx_runtime66.jsx)("div", { className: `w-1.5 h-1.5 rounded-full flex-shrink-0 ${source.status === "online" ? "bg-live" : "bg-offline"}` }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 min-w-0", children: [(0, import_jsx_runtime66.jsxs)("div", { className: `text-xs font-medium ${source.sourceName === state.reference ? "text-blue-600 dark:text-blue-400" : "text-gray-900 dark:text-white"}`, children: [source.sourceName, source.sourceName === state.reference && " (ref)"] }), source.resolution && (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [source.resolution.width, "\xD7", source.resolution.height, source.frameRate && ` @ ${source.frameRate.frames}/${source.frameRate.seconds}fps`] })] })] }, i)) })] }), state.resolvedLayout && state.resolvedLayout.layers.length > 0 && (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime66.jsxs)("div", { children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Active Layout" }), (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-600 dark:text-gray-400 mt-0.5", children: [state.layout?.type === "preset" ? state.layout.preset : "Custom", " \u2022 ", state.resolvedLayout.layers.length, " ", state.resolvedLayout.layers.length === 1 ? "layer" : "layers"] })] }), (0, import_jsx_runtime66.jsx)("div", { className: "space-y-1", children: state.resolvedLayout.layers.map((layer, i) => (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs p-1.5 rounded bg-gray-50 dark:bg-gray-900", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "font-medium text-gray-900 dark:text-white", children: ["L", layer.zIndex, ": ", layer.sourceName] }), (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [(layer.opacity * 100).toFixed(0), "%", layer.destRect && (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [" \u2022 (", layer.destRect.x.toFixed(0), ", ", layer.destRect.y.toFixed(0), ")"] })] })] }, i)) })] }), state.transitioning && (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Transition" }), (0, import_jsx_runtime66.jsx)("div", { className: "text-xs text-gray-600 dark:text-gray-400", children: "In progress..." })] })] })] })] })] });
77962
+ ), sourceJpegUrls: imagesReady ? throttledJpegUrls : {}, reference: referenceResolution, title: previewLayout ? "Preset Preview (Building...)" : state.previewMode ? "Preview Composition" : "Current Layout" }) }) }) })] }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 flex flex-col min-w-0", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between mb-4", children: [(0, import_jsx_runtime66.jsx)("h2", { className: "text-xl font-bold text-gray-900 dark:text-white", children: "Live Output" }), state.transitioning && (0, import_jsx_runtime66.jsx)("span", { className: "text-sm text-blue-600 dark:text-blue-400 font-medium", children: "Transitioning..." }), !state.previewUrl && !state.referenceAvailable && (0, import_jsx_runtime66.jsx)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Waiting for reference stream..." })] }), (0, import_jsx_runtime66.jsx)(WebRTCPreview, { url: state.previewUrl, id: `${_config.id}-live` })] })] }), (0, import_jsx_runtime66.jsxs)("div", { className: "border-t border-gray-200 dark:border-gray-700\n bg-white dark:bg-gray-800 h-64 flex flex-col", children: [(0, import_jsx_runtime66.jsx)("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700", children: (0, import_jsx_runtime66.jsx)("h2", { className: "text-lg font-bold text-gray-900 dark:text-white", children: "Status" }) }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 overflow-y-auto p-4 space-y-4", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between", children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Sources" }), (0, import_jsx_runtime66.jsxs)("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [onlineSourceCount, " / ", state.sources.length, " online"] })] }), (0, import_jsx_runtime66.jsx)("div", { className: "space-y-1", children: state.sources.map((source, i) => (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 p-1.5 rounded bg-gray-50 dark:bg-gray-900", children: [(0, import_jsx_runtime66.jsx)("div", { className: `w-1.5 h-1.5 rounded-full flex-shrink-0 ${source.status === "online" ? "bg-live" : "bg-offline"}` }), (0, import_jsx_runtime66.jsxs)("div", { className: "flex-1 min-w-0", children: [(0, import_jsx_runtime66.jsxs)("div", { className: `text-xs font-medium ${source.sourceName === state.reference ? "text-blue-600 dark:text-blue-400" : "text-gray-900 dark:text-white"}`, children: [source.sourceName, source.sourceName === state.reference && " (ref)"] }), source.resolution && (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [source.resolution.width, "\xD7", source.resolution.height, source.frameRate && ` @ ${source.frameRate.frames}/${source.frameRate.seconds}fps`] })] })] }, i)) })] }), state.resolvedLayout && state.resolvedLayout.layers.length > 0 && (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [(0, import_jsx_runtime66.jsxs)("div", { children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Active Layout" }), (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-600 dark:text-gray-400 mt-0.5", children: [state.layout?.type === "preset" ? state.layout.preset : "Custom", " \u2022 ", state.resolvedLayout.layers.length, " ", state.resolvedLayout.layers.length === 1 ? "layer" : "layers"] })] }), (0, import_jsx_runtime66.jsx)("div", { className: "space-y-1", children: state.resolvedLayout.layers.map((layer, i) => (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs p-1.5 rounded bg-gray-50 dark:bg-gray-900", children: [(0, import_jsx_runtime66.jsxs)("div", { className: "font-medium text-gray-900 dark:text-white", children: ["L", layer.zIndex, ": ", layer.sourceName] }), (0, import_jsx_runtime66.jsxs)("div", { className: "text-xs text-gray-500 dark:text-gray-400", children: [(layer.opacity * 100).toFixed(0), "%", layer.destRect && (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [" \u2022 (", layer.destRect.x.toFixed(0), ", ", layer.destRect.y.toFixed(0), ")"] })] })] }, i)) })] }), state.transitioning && (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [(0, import_jsx_runtime66.jsx)("h3", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: "Transition" }), (0, import_jsx_runtime66.jsx)("div", { className: "text-xs text-gray-600 dark:text-gray-400", children: "In progress..." })] })] })] })] })] });
77105
77963
  }
77106
77964
  var fullscreen_default2 = FullscreenView3;
77107
77965
 
@@ -77733,7 +78591,7 @@ var import_util15 = __toESM(require_util());
77733
78591
 
77734
78592
  // build/client/processor.webRtcDuplex/fullscreen.js
77735
78593
  var import_jsx_runtime71 = __toESM(require_jsx_runtime());
77736
- var import_react31 = __toESM(require_react());
78594
+ var import_react32 = __toESM(require_react());
77737
78595
  var import_webrtc_client3 = __toESM(require_webrtc_client());
77738
78596
  var DuplexWhepClient = class extends NorskWhepClient {
77739
78597
  showVideo;
@@ -77774,11 +78632,11 @@ var DuplexWhipClient = class extends import_webrtc_client3.WhipClient {
77774
78632
  }
77775
78633
  };
77776
78634
  function FullscreenView4({ state, config }) {
77777
- const [client, setClient] = (0, import_react31.useState)(void 0);
77778
- const [sender, setSender] = (0, import_react31.useState)(void 0);
77779
- const container = (0, import_react31.useRef)(null);
77780
- const button = (0, import_react31.useRef)(null);
77781
- const go = (0, import_react31.useCallback)(async () => {
78635
+ const [client, setClient] = (0, import_react32.useState)(void 0);
78636
+ const [sender, setSender] = (0, import_react32.useState)(void 0);
78637
+ const container = (0, import_react32.useRef)(null);
78638
+ const button = (0, import_react32.useRef)(null);
78639
+ const go = (0, import_react32.useCallback)(async () => {
77782
78640
  if (!state.publishUrl)
77783
78641
  return;
77784
78642
  if (!client && state.outputUrl && container.current) {
@@ -78070,7 +78928,7 @@ function assertUnreachable30(_) {
78070
78928
 
78071
78929
  // build/client/util.stats.latency/inline-view.js
78072
78930
  var import_jsx_runtime73 = __toESM(require_jsx_runtime());
78073
- var import_react32 = __toESM(require_react());
78931
+ var import_react33 = __toESM(require_react());
78074
78932
 
78075
78933
  // ../../node_modules/chart.js/auto/auto.js
78076
78934
  Chart.register(...registerables);
@@ -78078,8 +78936,8 @@ var auto_default = Chart;
78078
78936
 
78079
78937
  // build/client/util.stats.latency/inline-view.js
78080
78938
  function InlineView22({ state, config: _2 }) {
78081
- const chartContainer = (0, import_react32.useRef)(null);
78082
- const [chartControl, setChartControl] = (0, import_react32.useState)(void 0);
78939
+ const chartContainer = (0, import_react33.useRef)(null);
78940
+ const [chartControl, setChartControl] = (0, import_react33.useState)(void 0);
78083
78941
  function makeDataSet(key, color2, values) {
78084
78942
  return {
78085
78943
  label: key,
@@ -78090,13 +78948,13 @@ function InlineView22({ state, config: _2 }) {
78090
78948
  data: values
78091
78949
  };
78092
78950
  }
78093
- const makeData = (0, import_react32.useCallback)((state2) => {
78951
+ const makeData = (0, import_react33.useCallback)((state2) => {
78094
78952
  return {
78095
78953
  labels: new Array(state2.values.length).fill(0).map((_, i) => i).map((_) => ""),
78096
78954
  datasets: [makeDataSet("latency", "rgba(255, 0, 0, 255)", state2.values)]
78097
78955
  };
78098
78956
  }, []);
78099
- (0, import_react32.useEffect)(() => {
78957
+ (0, import_react33.useEffect)(() => {
78100
78958
  if (!chartContainer.current)
78101
78959
  return;
78102
78960
  auto_default.defaults.color = "#FFF";
@@ -78134,7 +78992,7 @@ function InlineView22({ state, config: _2 }) {
78134
78992
  chart.update();
78135
78993
  }, 100);
78136
78994
  }, [chartContainer, makeData, state]);
78137
- (0, import_react32.useEffect)(() => {
78995
+ (0, import_react33.useEffect)(() => {
78138
78996
  if (!chartControl)
78139
78997
  return;
78140
78998
  chartControl.data = makeData(state);