@hyperframes/studio 0.8.143 → 0.8.145

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 (72) hide show
  1. package/dist/assets/{hyperframes-player-BXSuksFh.js → hyperframes-player-DZ5JrMDg.js} +3 -3
  2. package/dist/assets/{index-FgyO_5Kv.js → index-BOs6C0aZ.js} +1 -1
  3. package/dist/assets/{index-BgOVCqjo.js → index-BqFRhNZa.js} +1 -1
  4. package/dist/assets/{index-J7jpL4pj.js → index-D8W0a0Z9.js} +149 -149
  5. package/dist/{chunk-TFLU2MGI.js → chunk-6JY3MTIX.js} +108 -2
  6. package/dist/chunk-6JY3MTIX.js.map +1 -0
  7. package/dist/{chunk-H2DUOXTF.js → chunk-NZPJUZKT.js} +100 -4
  8. package/dist/chunk-NZPJUZKT.js.map +1 -0
  9. package/dist/{chunk-UYWON5AX.js → chunk-TP6D3U37.js} +7 -100
  10. package/dist/chunk-TP6D3U37.js.map +1 -0
  11. package/dist/{chunk-LOVBLSMR.js → chunk-Y2OHHNM2.js} +35 -19
  12. package/dist/chunk-Y2OHHNM2.js.map +1 -0
  13. package/dist/{domEditingLayers-KBTPQKGA.js → domEditingLayers-PTP2ICGM.js} +3 -3
  14. package/dist/index.d.ts +8 -5
  15. package/dist/index.html +1 -1
  16. package/dist/index.js +545 -495
  17. package/dist/index.js.map +1 -1
  18. package/dist/player.js +3 -3
  19. package/dist/ui.d.ts +4 -5
  20. package/dist/ui.js +1 -1
  21. package/package.json +8 -8
  22. package/src/components/StudioLeftPanels.tsx +2 -2
  23. package/src/components/editor/DomEditSelectionChrome.test.tsx +103 -0
  24. package/src/components/editor/DomEditSelectionChrome.tsx +42 -16
  25. package/src/components/editor/LayersPanel.test.ts +4 -4
  26. package/src/components/editor/LayersPanel.tsx +2 -2
  27. package/src/components/editor/TopologyLens.test.tsx +5 -4
  28. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +36 -0
  29. package/src/components/editor/domEditOverlayGestures.ts +1 -0
  30. package/src/components/editor/domEditOverlayStartGesture.ts +5 -0
  31. package/src/components/editor/domEditResizeLocal.test.ts +81 -1
  32. package/src/components/editor/domEditResizeLocal.ts +27 -3
  33. package/src/components/editor/domEditingLayers.ts +4 -1
  34. package/src/components/editor/domEditingTextFields.test.ts +18 -0
  35. package/src/components/editor/manualEditsDom.ts +12 -12
  36. package/src/components/editor/previewGestureStarts.ts +14 -5
  37. package/src/components/editor/resizeDraft.ts +13 -4
  38. package/src/components/editor/useDomEditOverlayGestures.ts +5 -3
  39. package/src/components/renders/GpuEncodingToggle.tsx +28 -0
  40. package/src/components/renders/RenderQueue.test.tsx +20 -2
  41. package/src/components/renders/RenderQueue.tsx +13 -1
  42. package/src/components/renders/RenderQueuePanel.tsx +2 -1
  43. package/src/components/renders/renderSettings.ts +5 -2
  44. package/src/components/renders/useRenderQueue.ts +3 -0
  45. package/src/components/renders/useRenderQueueComposition.test.tsx +12 -0
  46. package/src/components/ui/Menu.test.tsx +67 -5
  47. package/src/components/ui/Menu.tsx +21 -4
  48. package/src/components/ui/Popover.tsx +11 -5
  49. package/src/hooks/gsapResizeIntercept.test.ts +46 -11
  50. package/src/hooks/gsapResizeIntercept.ts +25 -20
  51. package/src/hooks/useConsoleErrorCapture.test.tsx +21 -2
  52. package/src/hooks/useConsoleErrorCapture.ts +3 -2
  53. package/src/hooks/useThumbnailStripSize.test.tsx +123 -0
  54. package/src/hooks/useThumbnailStripSize.ts +19 -8
  55. package/src/index.ts +1 -0
  56. package/src/player/components/CompositionThumbnail.test.ts +3 -0
  57. package/src/player/components/ImageThumbnail.test.tsx +4 -1
  58. package/src/player/components/Player.test.ts +44 -0
  59. package/src/player/components/Player.tsx +11 -0
  60. package/src/player/components/VideoThumbnail.test.tsx +13 -2
  61. package/src/player/hooks/timelineSyncHydration.ts +2 -1
  62. package/src/player/lib/playbackTypes.ts +1 -0
  63. package/src/player/sceneSwap.test.ts +27 -1
  64. package/src/player/sceneSwap.ts +16 -2
  65. package/src/utils/authoredSource.test.ts +25 -0
  66. package/src/utils/authoredSource.ts +3 -5
  67. package/src/utils/rounding.ts +7 -0
  68. package/dist/chunk-H2DUOXTF.js.map +0 -1
  69. package/dist/chunk-LOVBLSMR.js.map +0 -1
  70. package/dist/chunk-TFLU2MGI.js.map +0 -1
  71. package/dist/chunk-UYWON5AX.js.map +0 -1
  72. /package/dist/{domEditingLayers-KBTPQKGA.js.map → domEditingLayers-PTP2ICGM.js.map} +0 -0
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@ import {
7
7
  PANEL_IDS,
8
8
  Player,
9
9
  STUDIO_PREVIEW_FPS,
10
+ announcePreviewDocumentLoaded,
11
+ announcePreviewPromoted,
10
12
  applyPreviewVariablesToUrl,
11
13
  clampTimelineZoomPercent,
12
14
  clampToDuration,
@@ -26,10 +28,15 @@ import {
26
28
  isPreviewBooted,
27
29
  isRevealedAudioFxRequestCurrent,
28
30
  liveTime,
31
+ markScenesStale,
32
+ onPreviewContentReplaced,
33
+ onPreviewDocumentLoaded,
34
+ onPreviewPromoted,
29
35
  panelsInZone,
30
36
  parseDockLayout,
31
37
  patchElements,
32
38
  readStudioUiPreferences,
39
+ sceneSwapFor,
33
40
  secondsToFrame,
34
41
  stepFrameTime,
35
42
  thumbnailRevisionOf,
@@ -40,7 +47,7 @@ import {
40
47
  usePreviewVariablesStore,
41
48
  whenPreviewBooted,
42
49
  writeStudioUiPreferences
43
- } from "./chunk-H2DUOXTF.js";
50
+ } from "./chunk-NZPJUZKT.js";
44
51
  import {
45
52
  Button,
46
53
  ContextMenu,
@@ -72,13 +79,9 @@ import {
72
79
  fieldBase,
73
80
  fieldText,
74
81
  popupSurface
75
- } from "./chunk-LOVBLSMR.js";
82
+ } from "./chunk-Y2OHHNM2.js";
76
83
  import {
77
84
  DOM_EDIT_LAYER_OBSERVER_INIT,
78
- STUDIO_FLAT_INSPECTOR_ENABLED,
79
- STUDIO_SDK_CUTOVER_ENABLED,
80
- STUDIO_SDK_CUTOVER_FAMILIES,
81
- STUDIO_SDK_RESOLVER_SHADOW_ENABLED,
82
85
  buildDefaultDomEditTextField,
83
86
  buildDomEditPatchTarget,
84
87
  buildDomEditRichTextPatchOperation,
@@ -96,19 +99,20 @@ import {
96
99
  getDomEditTargetKey,
97
100
  getDomLayerPatchTarget,
98
101
  isElementComputedVisible,
99
- isSdkFamilyEnabled,
100
102
  isTextEditableSelection,
101
103
  liveLayerElement,
102
- manualEditingAvailability_exports,
103
104
  readHfId,
104
105
  resolveAllVisualDomEditTargets,
105
106
  resolveDomEditSelection,
106
- serializeDomEditTextFields,
107
- studioApiFetch
108
- } from "./chunk-UYWON5AX.js";
107
+ serializeDomEditTextFields
108
+ } from "./chunk-TP6D3U37.js";
109
109
  import {
110
110
  LAYER_REVEAL_PRIOR_POSITION_ATTR,
111
111
  LAYER_REVEAL_PRIOR_Z_ATTR,
112
+ STUDIO_FLAT_INSPECTOR_ENABLED,
113
+ STUDIO_SDK_CUTOVER_ENABLED,
114
+ STUDIO_SDK_CUTOVER_FAMILIES,
115
+ STUDIO_SDK_RESOLVER_SHADOW_ENABLED,
112
116
  applyMediaMetadataFromElement,
113
117
  buildProjectApiPath,
114
118
  buildProjectHash,
@@ -136,8 +140,10 @@ import {
136
140
  isAudibleVideoNode,
137
141
  isElementVisibleThroughAncestors,
138
142
  isHtmlElement,
143
+ isSdkFamilyEnabled,
139
144
  isTimelineIgnoredElement,
140
145
  isVideoAudible,
146
+ manualEditingAvailability_exports,
141
147
  normalizeTimelineCompositionSource,
142
148
  parseCssColor,
143
149
  parseProjectHashRoute,
@@ -158,13 +164,15 @@ import {
158
164
  roundTo3,
159
165
  roundToCenti,
160
166
  roundToLayoutPx,
167
+ roundToLayoutSize,
161
168
  runtimeAudioId,
162
169
  sameTimelineText,
163
170
  setCompositionSourceMap,
164
171
  splitTimelineElementKey,
172
+ studioApiFetch,
165
173
  toHexColor,
166
174
  wrapTimeline
167
- } from "./chunk-TFLU2MGI.js";
175
+ } from "./chunk-6JY3MTIX.js";
168
176
  import "./chunk-PZ5AY32C.js";
169
177
 
170
178
  // src/components/EditorShell.tsx
@@ -16486,17 +16494,24 @@ var measure = (entries) => isTimelineZoomPreviewing() || commit(
16486
16494
  })
16487
16495
  );
16488
16496
  var onPresence = (entries) => measure(entries.filter((entry2) => entry2.isIntersecting));
16489
- var onResize = (entries) => measure(
16490
- entries.map(({ target, contentRect: { width, height } }) => ({
16491
- target,
16492
- size: { width, height }
16493
- }))
16494
- );
16497
+ var pendingSizes = /* @__PURE__ */ new Map();
16498
+ var pendingSizesFrame = 0;
16499
+ var applyPendingSizes = () => {
16500
+ pendingSizesFrame = 0;
16501
+ const entries = [...pendingSizes].map(([target, size]) => ({ target, size }));
16502
+ pendingSizes.clear();
16503
+ measure(entries);
16504
+ };
16505
+ var applyResizesNextFrame = (entries) => {
16506
+ for (const { target, contentRect } of entries)
16507
+ pendingSizes.set(target, { width: contentRect.width, height: contentRect.height });
16508
+ pendingSizesFrame ||= requestAnimationFrame(applyPendingSizes);
16509
+ };
16495
16510
  var observeIntersections = (callback) => typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver(callback, GAP_WARNING);
16496
16511
  function acquire() {
16497
16512
  if (users++ === 0) {
16498
16513
  shared = {
16499
- resize: new ResizeObserver(onResize),
16514
+ resize: new ResizeObserver(applyResizesNextFrame),
16500
16515
  presence: observeIntersections(onPresence),
16501
16516
  gaps: observeIntersections(
16502
16517
  (entries) => entries.some((entry2) => entry2.isIntersecting) && scheduleRefresh()
@@ -16517,6 +16532,9 @@ function release() {
16517
16532
  window.removeEventListener("scroll", scheduleRefresh, { capture: true });
16518
16533
  cancelAnimationFrame(frame2);
16519
16534
  frame2 = 0;
16535
+ cancelAnimationFrame(pendingSizesFrame);
16536
+ pendingSizesFrame = 0;
16537
+ pendingSizes.clear();
16520
16538
  }
16521
16539
  var watchGap = (gap) => {
16522
16540
  if (!gap) return;
@@ -16557,6 +16575,7 @@ function useThumbnailStripSize() {
16557
16575
  resize.unobserve(target);
16558
16576
  presence?.unobserve(target);
16559
16577
  strips.delete(target);
16578
+ pendingSizes.delete(target);
16560
16579
  release();
16561
16580
  };
16562
16581
  }, []);
@@ -17575,8 +17594,8 @@ import { motionPathPluginUrl } from "@hyperframes/core/gsap-cdn";
17575
17594
  // src/utils/authoredSource.ts
17576
17595
  import { ensureHfIds, isCompositionTemplate } from "@hyperframes/parsers/hf-ids";
17577
17596
  import {
17578
- STUDIO_PREVIEW_LAZY_ATTR,
17579
- STUDIO_PREVIEW_UPCOMING_ATTR
17597
+ PREVIEW_ONLY_ATTRS,
17598
+ STUDIO_PREVIEW_LAZY_ATTR
17580
17599
  } from "@hyperframes/core/studio-preview-mark";
17581
17600
  function parseSavedSource(html2) {
17582
17601
  return new DOMParser().parseFromString(ensureHfIds(html2), "text/html");
@@ -17657,10 +17676,8 @@ function authoredMarkup(authored, live, sourceFile) {
17657
17676
  function liveMarkupWithoutPreviewMarks(live) {
17658
17677
  const copy = live.cloneNode(true);
17659
17678
  for (const el of [copy, ...Array.from(copy.querySelectorAll("*"))]) {
17660
- el.removeAttribute(STUDIO_PREVIEW_UPCOMING_ATTR);
17661
- if (!el.hasAttribute(STUDIO_PREVIEW_LAZY_ATTR)) continue;
17662
- el.removeAttribute("loading");
17663
- el.removeAttribute(STUDIO_PREVIEW_LAZY_ATTR);
17679
+ if (el.hasAttribute(STUDIO_PREVIEW_LAZY_ATTR)) el.removeAttribute("loading");
17680
+ for (const attr of PREVIEW_ONLY_ATTRS) el.removeAttribute(attr);
17664
17681
  }
17665
17682
  return copy.outerHTML;
17666
17683
  }
@@ -29909,9 +29926,10 @@ function buildStandaloneRootTimelineElement(params) {
29909
29926
 
29910
29927
  // src/player/hooks/timelineSyncHydration.ts
29911
29928
  import { resolveCssStackingContextId as resolveCssStackingContextId2 } from "@hyperframes/core/runtime/stacking-context";
29929
+ import { LOOP_INFLATED_TIMELINE_SECONDS } from "@hyperframes/core/runtime/composition-length";
29912
29930
  import { HF_AUDIO_GROUP_ATTR as HF_AUDIO_GROUP_ATTR3 } from "@hyperframes/core/audio-groups";
29913
29931
  function sanitizeDurationSeconds(value) {
29914
- return Number.isFinite(value) && value > 0 && value < 7200 ? value : 0;
29932
+ return Number.isFinite(value) && value > 0 && value < LOOP_INFLATED_TIMELINE_SECONDS ? value : 0;
29915
29933
  }
29916
29934
  function readChildAudioGroupState(child) {
29917
29935
  const audioGroup = child.getAttribute(HF_AUDIO_GROUP_ATTR3);
@@ -31289,7 +31307,7 @@ function readParentFlexBasisPixels(element, size) {
31289
31307
  const display2 = readStyleOrComputed(parent, "display").trim();
31290
31308
  if (display2 !== "flex" && display2 !== "inline-flex") return null;
31291
31309
  const direction = readStyleOrComputed(parent, "flex-direction").trim();
31292
- return roundToLayoutPx(Math.max(1, direction.startsWith("column") ? size.height : size.width));
31310
+ return direction.startsWith("column") ? size.height : size.width;
31293
31311
  }
31294
31312
  function restoreStaleStudioScaleResize(element) {
31295
31313
  if (!element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) return;
@@ -31352,17 +31370,16 @@ function writeStudioBoxSizeVars(element, size) {
31352
31370
  element.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, element.style.getPropertyValue("display"));
31353
31371
  }
31354
31372
  element.setAttribute(STUDIO_BOX_SIZE_ATTR, "true");
31355
- element.style.setProperty(STUDIO_WIDTH_PROP, `${roundToLayoutPx(Math.max(1, size.width))}px`);
31356
- element.style.setProperty(STUDIO_HEIGHT_PROP, `${roundToLayoutPx(Math.max(1, size.height))}px`);
31373
+ element.style.setProperty(STUDIO_WIDTH_PROP, `${size.width}px`);
31374
+ element.style.setProperty(STUDIO_HEIGHT_PROP, `${size.height}px`);
31357
31375
  }
31358
- function applyStudioBoxSizeDimensions(element, size) {
31376
+ function applyStudioBoxSizeDimensions(element, wantedSize) {
31377
+ const size = roundToLayoutSize(wantedSize);
31359
31378
  writeStudioBoxSizeVars(element, size);
31360
31379
  restoreStaleStudioScaleResize(element);
31361
- const width = roundToLayoutPx(Math.max(1, size.width));
31362
- const height = roundToLayoutPx(Math.max(1, size.height));
31363
31380
  element.style.setProperty("box-sizing", "border-box");
31364
- element.style.setProperty("width", `${width}px`);
31365
- element.style.setProperty("height", `${height}px`);
31381
+ element.style.setProperty("width", `${size.width}px`);
31382
+ element.style.setProperty("height", `${size.height}px`);
31366
31383
  element.style.setProperty("min-width", "0px");
31367
31384
  element.style.setProperty("min-height", "0px");
31368
31385
  element.style.setProperty("max-width", "none");
@@ -31377,6 +31394,7 @@ function applyStudioBoxSizeDimensions(element, size) {
31377
31394
  if (computedDisplay === "inline") {
31378
31395
  element.style.setProperty("display", "inline-block");
31379
31396
  }
31397
+ return size;
31380
31398
  }
31381
31399
  function applyStudioBoxSize(element, size) {
31382
31400
  promoteInlineForTransform(element);
@@ -31394,7 +31412,7 @@ function applyStudioBoxSizeDraft(element, size) {
31394
31412
  boxSizeDraftBases.set(element, { width: element.offsetWidth, height: element.offsetHeight });
31395
31413
  }
31396
31414
  promoteInlineForTransform(element);
31397
- applyStudioBoxSizeDimensions(element, size);
31415
+ return applyStudioBoxSizeDimensions(element, size);
31398
31416
  }
31399
31417
  function isSimpleRotateAngle(value) {
31400
31418
  return /^-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|rad|turn|grad)$/.test(value.trim());
@@ -31503,79 +31521,6 @@ function isPreviewChanging() {
31503
31521
  return changing() && !givenUp;
31504
31522
  }
31505
31523
 
31506
- // src/player/sceneSwap.ts
31507
- var SCENES_SWAPPED = "hf-scenes-swapped";
31508
- var SCENE_SWAP_MS = 5e3;
31509
- function onPreviewContentReplaced(iframe, onReplaced) {
31510
- iframe.addEventListener("load", onReplaced);
31511
- iframe.addEventListener(SCENES_SWAPPED, onReplaced);
31512
- return () => {
31513
- iframe.removeEventListener("load", onReplaced);
31514
- iframe.removeEventListener(SCENES_SWAPPED, onReplaced);
31515
- };
31516
- }
31517
- var PREVIEW_PROMOTED = "hf-preview-promoted";
31518
- function announcePreviewPromoted(promotion) {
31519
- promotion.live.ownerDocument.dispatchEvent(
31520
- new CustomEvent(PREVIEW_PROMOTED, { detail: promotion })
31521
- );
31522
- }
31523
- function onPreviewPromoted(doc, listener2) {
31524
- const handle = (event) => listener2(event.detail);
31525
- doc.addEventListener(PREVIEW_PROMOTED, handle);
31526
- return () => doc.removeEventListener(PREVIEW_PROMOTED, handle);
31527
- }
31528
- function markScenesStale(iframe, files) {
31529
- const doc = iframe?.contentDocument;
31530
- const meta = doc?.querySelector('meta[name="hf-scene-parts"]');
31531
- if (!doc || !meta) return;
31532
- let parts;
31533
- try {
31534
- parts = JSON.parse(meta.content);
31535
- } catch {
31536
- return;
31537
- }
31538
- for (const file of files) {
31539
- const hosts = doc.querySelectorAll(`[data-composition-file="${CSS.escape(file)}"]`);
31540
- if (hosts.length === 0 && file.endsWith(".html")) parts.shared = "";
31541
- for (const host of hosts) {
31542
- const scene = host.closest("[data-hf-scene]")?.getAttribute("data-hf-scene");
31543
- if (scene && scene in parts.scenes) parts.scenes[scene] = "";
31544
- }
31545
- }
31546
- meta.content = JSON.stringify(parts);
31547
- }
31548
- function sceneSwapFor(iframe) {
31549
- const win = iframe.contentWindow;
31550
- const swap = win?.__hfSwapScenes;
31551
- if (typeof swap !== "function") return null;
31552
- return async (url, isCurrent, cancel) => {
31553
- if (cancel?.aborted) throw cancel.reason;
31554
- const deadline = new AbortController();
31555
- const timer = setTimeout(
31556
- () => deadline.abort(new Error("the preview took too long to swap")),
31557
- SCENE_SWAP_MS
31558
- );
31559
- cancel?.addEventListener("abort", () => deadline.abort(cancel.reason), { once: true });
31560
- const swapping = (async () => {
31561
- const response = await studioApiFetch(url, { signal: deadline.signal });
31562
- if (!response.ok) throw new Error(`preview request failed with ${response.status}`);
31563
- const html2 = await response.text();
31564
- if (!isCurrent()) throw new Error("superseded by a newer reload");
31565
- await swap.call(win, html2, deadline.signal);
31566
- })();
31567
- const expired = new Promise(
31568
- (_resolve, reject) => deadline.signal.addEventListener("abort", () => reject(deadline.signal.reason))
31569
- );
31570
- try {
31571
- await Promise.race([swapping, expired]);
31572
- } finally {
31573
- clearTimeout(timer);
31574
- }
31575
- iframe.dispatchEvent(new Event(SCENES_SWAPPED));
31576
- };
31577
- }
31578
-
31579
31524
  // src/player/hooks/useShadowPreviewReload.ts
31580
31525
  var SHADOW_READY_TIMEOUT_MS = 15e3;
31581
31526
  var SHADOW_READY_BUDGETS = 3;
@@ -37564,7 +37509,7 @@ function OffCanvasIndicators({
37564
37509
  const selectOffCanvas = async () => {
37565
37510
  const el = elements.current.get(r.key);
37566
37511
  if (!el) return;
37567
- const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-KBTPQKGA.js");
37512
+ const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-PTP2ICGM.js");
37568
37513
  const acp = activeCompositionPathRef.current ?? "index.html";
37569
37514
  const sel = await resolveDomEditSelection2(el, {
37570
37515
  activeCompositionPath: acp,
@@ -37620,7 +37565,7 @@ function ChildRectOutlines({
37620
37565
 
37621
37566
  // src/components/editor/domEditResizeLocal.ts
37622
37567
  var MIN_RESIZE_LOCAL_PX = 1;
37623
- var DEGENERATE_START_DIST_PX = 3;
37568
+ var DEGENERATE_START_DIST_PX = 0.5;
37624
37569
  function resolveCenterResizeScale(input) {
37625
37570
  const startDist = Math.hypot(
37626
37571
  input.pointerStart.x - input.centerStart.x,
@@ -37675,6 +37620,17 @@ function resolveRotatedResizeCursor(handle, rotationDeg) {
37675
37620
  const bucket = Math.round(normalized / 45) % 8;
37676
37621
  return CURSORS_8[bucket];
37677
37622
  }
37623
+ function resizeRemainderShift(corners, grab, { wanted, written }) {
37624
+ const u = { x: corners.ne.x - corners.nw.x, y: corners.ne.y - corners.nw.y };
37625
+ const v = { x: corners.sw.x - corners.nw.x, y: corners.sw.y - corners.nw.y };
37626
+ const det = u.x * v.y - u.y * v.x;
37627
+ if (!det) return { x: 0, y: 0 };
37628
+ const su = Math.sign((grab.x * v.y - grab.y * v.x) / det);
37629
+ const sv = Math.sign((u.x * grab.y - u.y * grab.x) / det);
37630
+ const kw = su * (wanted.width - written.width) / (2 * written.width);
37631
+ const kh = sv * (wanted.height - written.height) / (2 * written.height);
37632
+ return { x: u.x * kw + v.x * kh, y: u.y * kw + v.y * kh };
37633
+ }
37678
37634
  function computeNextResizeAnchor(prev, fixedStart, fixedNow) {
37679
37635
  const base2 = prev ?? { dx: 0, dy: 0 };
37680
37636
  return { dx: base2.dx + (fixedStart.x - fixedNow.x), dy: base2.dy + (fixedStart.y - fixedNow.y) };
@@ -39602,6 +39558,7 @@ function startGesture(kind, e, opts, options) {
39602
39558
  manualEditDragToken,
39603
39559
  snapContext,
39604
39560
  resizeHandle: kind === "resize" ? options?.resizeHandle ?? "se" : void 0,
39561
+ resizePressFromCorner: kind === "resize" && options?.resizeCorner ? { x: e.clientX - options.resizeCorner.x, y: e.clientY - options.resizeCorner.y } : void 0,
39605
39562
  resizeFixedCenterStart
39606
39563
  };
39607
39564
  if (kind === "resize") {
@@ -39695,7 +39652,7 @@ function createPreviewGestureStarts(opts, moveActiveGesture, releaseActiveGestur
39695
39652
  offerUndo();
39696
39653
  };
39697
39654
  const drawPressedBox = (origin2) => origin2 && ((dx, dy) => opts.setOverlayRect(shiftedOverlayRect(origin2, dx, dy)));
39698
- const drawResizedBox = (origin2, press) => {
39655
+ const drawResizedBox = (origin2, grabbed) => {
39699
39656
  if (!origin2) return null;
39700
39657
  const bounds = opts.overlayRef.current?.getBoundingClientRect();
39701
39658
  const centre = {
@@ -39703,8 +39660,12 @@ function createPreviewGestureStarts(opts, moveActiveGesture, releaseActiveGestur
39703
39660
  y: (bounds?.top ?? 0) + origin2.top + origin2.height / 2
39704
39661
  };
39705
39662
  return (dx, dy) => {
39706
- const pointer = { x: press.x + dx, y: press.y + dy };
39707
- const scale = resolveCenterResizeScale({ pointer, pointerStart: press, centerStart: centre });
39663
+ const pointer = { x: grabbed.x + dx, y: grabbed.y + dy };
39664
+ const scale = resolveCenterResizeScale({
39665
+ pointer,
39666
+ pointerStart: grabbed,
39667
+ centerStart: centre
39668
+ });
39708
39669
  const [width, height] = [origin2.width * scale, origin2.height * scale];
39709
39670
  opts.setOverlayRect({
39710
39671
  ...origin2,
@@ -39823,7 +39784,10 @@ function createPreviewGestureStarts(opts, moveActiveGesture, releaseActiveGestur
39823
39784
  let draw = null;
39824
39785
  if (kind === "drag") draw = drawPressedBox(opts.overlayRectRef.current);
39825
39786
  if (kind === "resize")
39826
- draw = drawResizedBox(opts.overlayRectRef.current, { x: e.clientX, y: e.clientY });
39787
+ draw = drawResizedBox(
39788
+ opts.overlayRectRef.current,
39789
+ options?.resizeCorner ?? { x: e.clientX, y: e.clientY }
39790
+ );
39827
39791
  return startOnShownPreview(
39828
39792
  e,
39829
39793
  () => {
@@ -39838,6 +39802,7 @@ function createPreviewGestureStarts(opts, moveActiveGesture, releaseActiveGestur
39838
39802
  selection,
39839
39803
  rect,
39840
39804
  resizeHandle: options?.resizeHandle,
39805
+ resizeCorner: options?.resizeCorner,
39841
39806
  at
39842
39807
  });
39843
39808
  },
@@ -39848,10 +39813,14 @@ function createPreviewGestureStarts(opts, moveActiveGesture, releaseActiveGestur
39848
39813
  }
39849
39814
 
39850
39815
  // src/components/editor/resizeDraft.ts
39851
- function resolveResizeAnchor(g, corners, measureOrientedRect) {
39816
+ function resolveResizeAnchor(g, corners, measureOrientedRect, sizes) {
39852
39817
  const fixedStart = g.resizeFixedCenterStart;
39853
39818
  if (corners && fixedStart) {
39854
- return computeNextResizeAnchor(g.lastResizeAnchor, fixedStart, overlayCornersCentroid(corners));
39819
+ const press = g.resizePressFromCorner ?? { x: 0, y: 0 };
39820
+ const grab = { x: g.startX - press.x - g.centerX, y: g.startY - press.y - g.centerY };
39821
+ const shift = resizeRemainderShift(corners, grab, sizes);
39822
+ const target = { x: fixedStart.x + shift.x, y: fixedStart.y + shift.y };
39823
+ return computeNextResizeAnchor(g.lastResizeAnchor, target, overlayCornersCentroid(corners));
39855
39824
  }
39856
39825
  const fallbackRect = measureOrientedRect();
39857
39826
  return resolveResizeCenterAnchorOffset({
@@ -39861,9 +39830,9 @@ function resolveResizeAnchor(g, corners, measureOrientedRect) {
39861
39830
  overlayHeight: fallbackRect ? fallbackRect.height : g.originHeight
39862
39831
  });
39863
39832
  }
39864
- function resolveAnchoredResizeDraft(g, member, element, overlayEl, iframe, measureOrientedRect) {
39833
+ function resolveAnchoredResizeDraft(g, member, element, overlayEl, iframe, measureOrientedRect, sizes) {
39865
39834
  const corners = overlayEl && iframe ? elementCornerOverlayPoints(overlayEl, iframe, element) : null;
39866
- const anchor2 = resolveResizeAnchor(g, corners, measureOrientedRect);
39835
+ const anchor2 = resolveResizeAnchor(g, corners, measureOrientedRect, sizes);
39867
39836
  g.lastResizeAnchor = anchor2;
39868
39837
  applyManualOffsetDragDraft(member, anchor2.dx, anchor2.dy);
39869
39838
  return measureOrientedRect() ?? {
@@ -39876,7 +39845,7 @@ function resolveAnchoredResizeDraft(g, member, element, overlayEl, iframe, measu
39876
39845
  angle: g.actualRotation
39877
39846
  };
39878
39847
  }
39879
- function resolveResizeDraftRect(g, element, overlayEl, iframe, measureOrientedRect) {
39848
+ function resolveResizeDraftRect(g, element, overlayEl, iframe, measureOrientedRect, sizes) {
39880
39849
  if (g.pathOffsetMember) {
39881
39850
  return resolveAnchoredResizeDraft(
39882
39851
  g,
@@ -39884,7 +39853,8 @@ function resolveResizeDraftRect(g, element, overlayEl, iframe, measureOrientedRe
39884
39853
  element,
39885
39854
  overlayEl,
39886
39855
  iframe,
39887
- measureOrientedRect
39856
+ measureOrientedRect,
39857
+ sizes
39888
39858
  );
39889
39859
  }
39890
39860
  return measureOrientedRect() ?? {
@@ -40209,14 +40179,15 @@ function createDomEditOverlayGestureHandlers(opts) {
40209
40179
  }
40210
40180
  if (g.pathOffsetMember) applyManualOffsetDragDraft(g.pathOffsetMember, dx, dy);
40211
40181
  } else {
40182
+ const grab = g.resizePressFromCorner ?? { x: 0, y: 0 };
40212
40183
  const nextSize = resolveCenterResizeSize({
40213
40184
  baseWidth: g.actualWidth,
40214
40185
  baseHeight: g.actualHeight,
40215
- pointer: { x: e.clientX, y: e.clientY },
40216
- pointerStart: { x: g.startX, y: g.startY },
40186
+ pointer: { x: e.clientX - grab.x, y: e.clientY - grab.y },
40187
+ pointerStart: { x: g.startX - grab.x, y: g.startY - grab.y },
40217
40188
  centerStart: { x: g.centerX, y: g.centerY }
40218
40189
  });
40219
- applyStudioBoxSizeDraft(sel.element, nextSize);
40190
+ const writtenSize = applyStudioBoxSizeDraft(sel.element, nextSize);
40220
40191
  const overlayEl = opts.overlayRef.current;
40221
40192
  const iframe = opts.iframeRef.current;
40222
40193
  const measureOrientedRect = () => overlayEl && iframe ? orientedOverlayRect(overlayEl, iframe, sel.element) : null;
@@ -40225,7 +40196,8 @@ function createDomEditOverlayGestureHandlers(opts) {
40225
40196
  sel.element,
40226
40197
  overlayEl,
40227
40198
  iframe,
40228
- measureOrientedRect
40199
+ measureOrientedRect,
40200
+ { wanted: nextSize, written: writtenSize }
40229
40201
  );
40230
40202
  logResizeMove({
40231
40203
  pointer: { x: e.clientX, y: e.clientY },
@@ -41302,13 +41274,22 @@ var RESIZE_HANDLE_DEFS = [
41302
41274
  { handle: "se", cursor: "nwse-resize", x: "right", y: "bottom" }
41303
41275
  ];
41304
41276
  var NO_CROP_INSET = { top: 0, right: 0, bottom: 0, left: 0 };
41305
- function resizeHandleStyle(def, overlayRect, cropInset) {
41277
+ function resizeHandleLayout(def, overlayRect, cropInset) {
41306
41278
  const half = RESIZE_HANDLE_HIT_PX / 2;
41307
41279
  const inset = cropInset ?? NO_CROP_INSET;
41308
- const style = { cursor: def.cursor, touchAction: "none" };
41309
- style.left = def.x === "left" ? overlayRect.left + inset.left - half : overlayRect.left + overlayRect.width - inset.right - half;
41310
- style.top = def.y === "top" ? overlayRect.top + inset.top - half : overlayRect.top + overlayRect.height - inset.bottom - half;
41311
- return style;
41280
+ const cornerX = def.x === "left" ? overlayRect.left + inset.left : overlayRect.left + overlayRect.width - inset.right;
41281
+ const cornerY = def.y === "top" ? overlayRect.top + inset.top : overlayRect.top + overlayRect.height - inset.bottom;
41282
+ const slideOutToFreeMiddleX = (def.x === "left" ? -1 : 1) * Math.max(0, half - (overlayRect.width - inset.left - inset.right) / 4);
41283
+ const slideOutToFreeMiddleY = (def.y === "top" ? -1 : 1) * Math.max(0, half - (overlayRect.height - inset.top - inset.bottom) / 4);
41284
+ return {
41285
+ style: {
41286
+ cursor: def.cursor,
41287
+ touchAction: "none",
41288
+ left: cornerX - half + slideOutToFreeMiddleX,
41289
+ top: cornerY - half + slideOutToFreeMiddleY
41290
+ },
41291
+ anchor: { left: half - slideOutToFreeMiddleX, top: half - slideOutToFreeMiddleY }
41292
+ };
41312
41293
  }
41313
41294
  function DomEditGroupChrome({
41314
41295
  groupOverlayItems,
@@ -41440,26 +41421,42 @@ function DomEditSelectionChrome({
41440
41421
  },
41441
41422
  selectionKey
41442
41423
  ),
41443
- canManipulate && !editing && selection.capabilities.canApplyManualSize && RESIZE_HANDLE_DEFS.map(
41444
- (def) => def.handle !== "se" && !selection.capabilities.canApplyManualOffset ? null : /* @__PURE__ */ jsx99(
41424
+ canManipulate && !editing && selection.capabilities.canApplyManualSize && RESIZE_HANDLE_DEFS.map((def) => {
41425
+ if (def.handle !== "se" && !selection.capabilities.canApplyManualOffset) return null;
41426
+ const layout = resizeHandleLayout(def, overlayRect, cropOutlineInsetPx ?? void 0);
41427
+ return /* @__PURE__ */ jsxs69(
41445
41428
  "div",
41446
41429
  {
41447
41430
  className: "pointer-events-auto absolute flex h-4 w-4 items-center justify-center",
41448
41431
  style: {
41449
- ...resizeHandleStyle(def, overlayRect, cropOutlineInsetPx ?? void 0),
41432
+ ...layout.style,
41450
41433
  // Cursor rotates with the object: bucket the corner's base
41451
41434
  // diagonal + element rotation into the 8 CSS resize cursors.
41452
41435
  cursor: resolveRotatedResizeCursor(def.handle, overlayRect.angle ?? 0)
41453
41436
  },
41454
41437
  onPointerDown: (e) => {
41455
41438
  e.stopPropagation();
41456
- gestures.startGesture("resize", e, { resizeHandle: def.handle });
41439
+ const corner = e.currentTarget.querySelector("[data-resize-corner]")?.getBoundingClientRect();
41440
+ gestures.startGesture("resize", e, {
41441
+ resizeHandle: def.handle,
41442
+ resizeCorner: corner && { x: corner.left, y: corner.top }
41443
+ });
41457
41444
  },
41458
- children: /* @__PURE__ */ jsx99("div", { className: "pointer-events-none h-[12px] w-[12px] rounded-full border-[1.5px] border-studio-accent bg-white shadow-[0_0_3px_rgba(0,0,0,0.45)]" })
41445
+ children: [
41446
+ /* @__PURE__ */ jsx99(
41447
+ "span",
41448
+ {
41449
+ "data-resize-corner": true,
41450
+ className: "pointer-events-none absolute",
41451
+ style: layout.anchor
41452
+ }
41453
+ ),
41454
+ /* @__PURE__ */ jsx99("div", { className: "pointer-events-none h-[12px] w-[12px] rounded-full border-[1.5px] border-studio-accent bg-white shadow-[0_0_3px_rgba(0,0,0,0.45)]" })
41455
+ ]
41459
41456
  },
41460
41457
  def.handle
41461
- )
41462
- )
41458
+ );
41459
+ })
41463
41460
  ]
41464
41461
  }
41465
41462
  ),
@@ -73303,6 +73300,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
73303
73300
  }
73304
73301
  if (resolution && resolution !== "auto") body.resolution = resolution;
73305
73302
  if (composition) body.composition = composition;
73303
+ if (opts.gpu) body.gpu = true;
73306
73304
  if (opts.variables && Object.keys(opts.variables).length > 0) {
73307
73305
  body.variables = opts.variables;
73308
73306
  }
@@ -84776,6 +84774,9 @@ function tweenUsesScaleLonghands(anim) {
84776
84774
  );
84777
84775
  return inKeyframes || Object.keys(anim?.properties ?? {}).some(isLonghand);
84778
84776
  }
84777
+ function rectCentre(rect) {
84778
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
84779
+ }
84779
84780
  async function tryGsapResizeIntercept(selection, size, animations, iframe, commitMutation, fetchFallbackAnimations, dragOffset, draw = (run) => run(), stamp) {
84780
84781
  const moment = editMoment(stamp);
84781
84782
  const fetchedAnimations = fetchFallbackAnimations ? await fetchFallbackAnimations() : [];
@@ -84890,8 +84891,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
84890
84891
  scaleDraftEl = el;
84891
84892
  committedScale = useScaleLonghands ? { x: newScaleX, y: newScaleY } : { x: newScaleX, y: newScaleX };
84892
84893
  if (el) {
84893
- const dropRect = draw(() => el.getBoundingClientRect());
84894
- scaleDraftDropPoint = { x: dropRect.x, y: dropRect.y };
84894
+ scaleDraftDropPoint = draw(() => rectCentre(el.getBoundingClientRect()));
84895
84895
  }
84896
84896
  } else {
84897
84897
  resizeProps = {
@@ -84922,31 +84922,31 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
84922
84922
  );
84923
84923
  const base3 = { x: baseGsapX, y: baseGsapY };
84924
84924
  setElementGsapPosition(draftEl, base3.x, base3.y);
84925
- const post2 = draftEl.getBoundingClientRect();
84926
- const residual = { x: dropPoint.x - post2.x, y: dropPoint.y - post2.y };
84927
- if (!Number.isFinite(residual.x) || !Number.isFinite(residual.y)) return null;
84928
- if (Math.abs(residual.x) < 0.5 && Math.abs(residual.y) < 0.5) {
84929
- logResize("scale-finalize", { skipped: "already-on-drop-point", residual, base: base3 });
84930
- return "settled";
84931
- }
84925
+ const post2 = rectCentre(draftEl.getBoundingClientRect());
84926
+ const residual2 = { x: dropPoint.x - post2.x, y: dropPoint.y - post2.y };
84927
+ if (!Number.isFinite(residual2.x) || !Number.isFinite(residual2.y)) return null;
84932
84928
  const corrected2 = {
84933
- x: roundTo3(base3.x + residual.x),
84934
- y: roundTo3(base3.y + residual.y)
84929
+ x: roundTo3(base3.x + residual2.x),
84930
+ y: roundTo3(base3.y + residual2.y)
84935
84931
  };
84932
+ if (corrected2.x === roundTo3(base3.x) && corrected2.y === roundTo3(base3.y)) {
84933
+ logResize("scale-finalize", { skipped: "already-on-drop-point", residual: residual2, base: base3 });
84934
+ return "settled";
84935
+ }
84936
84936
  logResize("scale-finalize", {
84937
84937
  dropPoint,
84938
- post: { x: post2.x, y: post2.y },
84939
- residual,
84938
+ post: post2,
84939
+ residual: residual2,
84940
84940
  gsapPos,
84941
84941
  base: base3,
84942
84942
  corrected: corrected2
84943
84943
  });
84944
84944
  setElementGsapPosition(draftEl, corrected2.x, corrected2.y);
84945
- return { base: base3, corrected: corrected2 };
84945
+ return { base: base3, corrected: corrected2, residual: residual2 };
84946
84946
  });
84947
84947
  if (measured === null) return false;
84948
84948
  if (measured === "settled") return true;
84949
- const { base: base2, corrected } = measured;
84949
+ const { base: base2, corrected, residual } = measured;
84950
84950
  const currentAnimations = fetchFallbackAnimations ? await fetchFallbackAnimations() : resolved2?.animations ?? animations;
84951
84951
  const delta = { x: corrected.x - base2.x, y: corrected.y - base2.y };
84952
84952
  const positionTween = pickClosestToPlayhead(
@@ -84956,6 +84956,10 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
84956
84956
  moment.time
84957
84957
  );
84958
84958
  if (positionTween) {
84959
+ if (Math.abs(residual.x) < 0.5 && Math.abs(residual.y) < 0.5) {
84960
+ draw(() => setElementGsapPosition(draftEl, base2.x, base2.y));
84961
+ return true;
84962
+ }
84959
84963
  logResize("scale-finalize", { route: "position-keyframe", tweenId: positionTween.id });
84960
84964
  assertGsapEditPersisted(
84961
84965
  await commitGsapPositionFromDrag(selection, positionTween, delta, base2, iframe, {
@@ -89138,9 +89142,9 @@ function useConsoleErrorCapture(previewIframe) {
89138
89142
  }
89139
89143
  };
89140
89144
  attachErrorCapture();
89141
- previewIframe.addEventListener("load", attachErrorCapture);
89145
+ const stopLoaded = onPreviewDocumentLoaded(previewIframe, attachErrorCapture);
89142
89146
  return () => {
89143
- previewIframe.removeEventListener("load", attachErrorCapture);
89147
+ stopLoaded();
89144
89148
  detachErrorCapture();
89145
89149
  };
89146
89150
  }, [previewIframe, resetErrors]);
@@ -93779,16 +93783,17 @@ function getPersistedRenderSettings() {
93779
93783
  return {
93780
93784
  format: ["mp4", "webm", "mov"].includes(parsed.format) ? parsed.format : "mp4",
93781
93785
  quality: ["draft", "standard", "high"].includes(parsed.quality) ? parsed.quality : "standard",
93782
- fps: [24, 30, 60].includes(parsed.fps) ? parsed.fps : 30
93786
+ fps: [24, 30, 60].includes(parsed.fps) ? parsed.fps : 30,
93787
+ gpu: parsed.gpu === true
93783
93788
  };
93784
93789
  }
93785
93790
  } catch {
93786
93791
  }
93787
- return { format: "mp4", quality: "standard", fps: 30 };
93792
+ return { format: "mp4", quality: "standard", fps: 30, gpu: false };
93788
93793
  }
93789
- function persistRenderSettings(format, quality, fps) {
93794
+ function persistRenderSettings(format, quality, fps, gpu = getPersistedRenderSettings().gpu) {
93790
93795
  try {
93791
- localStorage.setItem(RENDER_SETTINGS_KEY, JSON.stringify({ format, quality, fps }));
93796
+ localStorage.setItem(RENDER_SETTINGS_KEY, JSON.stringify({ format, quality, fps, gpu }));
93792
93797
  } catch {
93793
93798
  }
93794
93799
  }
@@ -93837,8 +93842,8 @@ var StudioLeftPanels = memo50(function StudioLeftPanels2({
93837
93842
  return;
93838
93843
  }
93839
93844
  await waitForPendingDomEditSaves();
93840
- const { format, quality, fps } = getPersistedRenderSettings();
93841
- await renderQueue.startRender({ composition: comp, format, quality, fps });
93845
+ const { format, quality, fps, gpu } = getPersistedRenderSettings();
93846
+ await renderQueue.startRender({ composition: comp, format, quality, fps, gpu });
93842
93847
  },
93843
93848
  [renderQueue, waitForPendingDomEditSaves]
93844
93849
  );
@@ -95644,8 +95649,38 @@ var FfmpegRequiredNotice = memo56(function FfmpegRequiredNotice2({
95644
95649
  );
95645
95650
  });
95646
95651
 
95647
- // src/components/renders/RenderQueue.tsx
95652
+ // src/components/renders/GpuEncodingToggle.tsx
95648
95653
  import { jsx as jsx233, jsxs as jsxs185 } from "react/jsx-runtime";
95654
+ function GpuEncodingToggle({
95655
+ format,
95656
+ checked,
95657
+ disabled,
95658
+ onToggle
95659
+ }) {
95660
+ if (format !== "mp4") return null;
95661
+ return /* @__PURE__ */ jsxs185(
95662
+ "label",
95663
+ {
95664
+ className: `flex items-center gap-2 text-step-10 text-text-4 ${disabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"}`,
95665
+ children: [
95666
+ /* @__PURE__ */ jsx233(
95667
+ "input",
95668
+ {
95669
+ type: "checkbox",
95670
+ checked,
95671
+ disabled,
95672
+ onChange: onToggle,
95673
+ className: "accent-studio-accent"
95674
+ }
95675
+ ),
95676
+ /* @__PURE__ */ jsx233("span", { children: "Use GPU encoding" })
95677
+ ]
95678
+ }
95679
+ );
95680
+ }
95681
+
95682
+ // src/components/renders/RenderQueue.tsx
95683
+ import { jsx as jsx234, jsxs as jsxs186 } from "react/jsx-runtime";
95649
95684
  var SCALE_OPTION_ORDER = ["auto", "1080p", "4k"];
95650
95685
  var SCALE_LABEL = {
95651
95686
  auto: "Auto",
@@ -95733,8 +95768,8 @@ function FormatInfoTooltip({ format }) {
95733
95768
  return () => document.removeEventListener("keydown", onKeyDown);
95734
95769
  }, [open]);
95735
95770
  const info = FORMAT_INFO[format];
95736
- return /* @__PURE__ */ jsxs185("div", { ref: triggerRef, className: "relative", onPointerEnter: show, onPointerLeave: hide, children: [
95737
- /* @__PURE__ */ jsx233(
95771
+ return /* @__PURE__ */ jsxs186("div", { ref: triggerRef, className: "relative", onPointerEnter: show, onPointerLeave: hide, children: [
95772
+ /* @__PURE__ */ jsx234(
95738
95773
  "button",
95739
95774
  {
95740
95775
  type: "button",
@@ -95745,7 +95780,7 @@ function FormatInfoTooltip({ format }) {
95745
95780
  onBlur: hide,
95746
95781
  onClick: () => setOpen((prev) => !prev),
95747
95782
  className: "flex items-center justify-center p-0.5 -m-0.5 rounded-sm text-text-5 hover:text-text-3 transition-colors cursor-help outline-hidden focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-accent",
95748
- children: /* @__PURE__ */ jsxs185(
95783
+ children: /* @__PURE__ */ jsxs186(
95749
95784
  "svg",
95750
95785
  {
95751
95786
  width: "12",
@@ -95758,16 +95793,16 @@ function FormatInfoTooltip({ format }) {
95758
95793
  strokeLinejoin: "round",
95759
95794
  "aria-hidden": "true",
95760
95795
  children: [
95761
- /* @__PURE__ */ jsx233("circle", { cx: "12", cy: "12", r: "10" }),
95762
- /* @__PURE__ */ jsx233("path", { d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" }),
95763
- /* @__PURE__ */ jsx233("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
95796
+ /* @__PURE__ */ jsx234("circle", { cx: "12", cy: "12", r: "10" }),
95797
+ /* @__PURE__ */ jsx234("path", { d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" }),
95798
+ /* @__PURE__ */ jsx234("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
95764
95799
  ]
95765
95800
  }
95766
95801
  )
95767
95802
  }
95768
95803
  ),
95769
95804
  open && createPortal14(
95770
- /* @__PURE__ */ jsxs185(
95805
+ /* @__PURE__ */ jsxs186(
95771
95806
  "div",
95772
95807
  {
95773
95808
  id: panelId,
@@ -95777,10 +95812,10 @@ function FormatInfoTooltip({ format }) {
95777
95812
  className: "fixed w-52 p-2 rounded-sm bg-input border border-border-input shadow-menu z-200",
95778
95813
  style: { left: position?.left ?? -9999, top: position?.top ?? -9999 },
95779
95814
  children: [
95780
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 font-semibold text-text-1 mb-0.5", children: info.label }),
95781
- /* @__PURE__ */ jsx233("p", { className: "text-step-9 text-text-3 leading-tight", children: info.desc }),
95782
- /* @__PURE__ */ jsx233("div", { className: "mt-1.5 pt-1.5 border-t border-border", children: ["mp4", "mov", "webm"].filter((f) => f !== format).map((f) => /* @__PURE__ */ jsxs185("p", { className: "text-step-9 text-text-4 leading-relaxed", children: [
95783
- /* @__PURE__ */ jsx233("span", { className: "text-text-3 font-medium", children: FORMAT_INFO[f].label }),
95815
+ /* @__PURE__ */ jsx234("p", { className: "text-step-10 font-semibold text-text-1 mb-0.5", children: info.label }),
95816
+ /* @__PURE__ */ jsx234("p", { className: "text-step-9 text-text-3 leading-tight", children: info.desc }),
95817
+ /* @__PURE__ */ jsx234("div", { className: "mt-1.5 pt-1.5 border-t border-border", children: ["mp4", "mov", "webm"].filter((f) => f !== format).map((f) => /* @__PURE__ */ jsxs186("p", { className: "text-step-9 text-text-4 leading-relaxed", children: [
95818
+ /* @__PURE__ */ jsx234("span", { className: "text-text-3 font-medium", children: FORMAT_INFO[f].label }),
95784
95819
  " \u2014 ",
95785
95820
  FORMAT_INFO[f].desc
95786
95821
  ] }, f)) })
@@ -95824,10 +95859,11 @@ function FormatExportButton({
95824
95859
  const [quality, setQuality] = useState153(persisted.quality);
95825
95860
  const [resolution, setResolution] = useState153("auto");
95826
95861
  const [fps, setFps] = useState153(persisted.fps);
95862
+ const [gpu, setGpu] = useState153(persisted.gpu);
95827
95863
  const missingFfmpeg = ffmpeg && !ffmpeg.ok ? ffmpeg : null;
95828
95864
  const showQuality = format !== "mov";
95829
- return /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-3", children: [
95830
- missingFfmpeg && /* @__PURE__ */ jsx233(
95865
+ return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3", children: [
95866
+ missingFfmpeg && /* @__PURE__ */ jsx234(
95831
95867
  FfmpegRequiredNotice,
95832
95868
  {
95833
95869
  status: missingFfmpeg,
@@ -95835,13 +95871,13 @@ function FormatExportButton({
95835
95871
  onRecheck: onRecheckFfmpeg
95836
95872
  }
95837
95873
  ),
95838
- /* @__PURE__ */ jsxs185("div", { className: "grid grid-cols-2 gap-2", children: [
95839
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95840
- /* @__PURE__ */ jsxs185("div", { className: "flex items-center gap-1", children: [
95841
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Format" }),
95842
- /* @__PURE__ */ jsx233(FormatInfoTooltip, { format })
95874
+ /* @__PURE__ */ jsxs186("div", { className: "grid grid-cols-2 gap-2", children: [
95875
+ /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
95876
+ /* @__PURE__ */ jsxs186("div", { className: "flex items-center gap-1", children: [
95877
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-text-4", children: "Format" }),
95878
+ /* @__PURE__ */ jsx234(FormatInfoTooltip, { format })
95843
95879
  ] }),
95844
- /* @__PURE__ */ jsx233(
95880
+ /* @__PURE__ */ jsx234(
95845
95881
  Select,
95846
95882
  {
95847
95883
  label: "Format",
@@ -95856,9 +95892,9 @@ function FormatExportButton({
95856
95892
  }
95857
95893
  )
95858
95894
  ] }),
95859
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95860
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Resolution" }),
95861
- /* @__PURE__ */ jsx233(
95895
+ /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
95896
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-text-4", children: "Resolution" }),
95897
+ /* @__PURE__ */ jsx234(
95862
95898
  Select,
95863
95899
  {
95864
95900
  label: "Resolution",
@@ -95873,9 +95909,9 @@ function FormatExportButton({
95873
95909
  }
95874
95910
  )
95875
95911
  ] }),
95876
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95877
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Frame rate" }),
95878
- /* @__PURE__ */ jsx233(
95912
+ /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
95913
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-text-4", children: "Frame rate" }),
95914
+ /* @__PURE__ */ jsx234(
95879
95915
  Select,
95880
95916
  {
95881
95917
  label: "Frame rate",
@@ -95890,9 +95926,9 @@ function FormatExportButton({
95890
95926
  }
95891
95927
  )
95892
95928
  ] }),
95893
- showQuality && /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95894
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Quality" }),
95895
- /* @__PURE__ */ jsx233(
95929
+ showQuality && /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
95930
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-text-4", children: "Quality" }),
95931
+ /* @__PURE__ */ jsx234(
95896
95932
  Select,
95897
95933
  {
95898
95934
  label: "Quality",
@@ -95908,7 +95944,19 @@ function FormatExportButton({
95908
95944
  )
95909
95945
  ] })
95910
95946
  ] }),
95911
- /* @__PURE__ */ jsx233(
95947
+ /* @__PURE__ */ jsx234(
95948
+ GpuEncodingToggle,
95949
+ {
95950
+ format,
95951
+ checked: gpu,
95952
+ disabled: isRendering,
95953
+ onToggle: () => {
95954
+ setGpu(!gpu);
95955
+ persistRenderSettings(format, quality, fps, !gpu);
95956
+ }
95957
+ }
95958
+ ),
95959
+ /* @__PURE__ */ jsx234(
95912
95960
  Button,
95913
95961
  {
95914
95962
  variant: "primary",
@@ -95921,13 +95969,13 @@ function FormatExportButton({
95921
95969
  if (isRendering || missingFfmpeg) return;
95922
95970
  const outputResolution = resolveResolution(resolution, compositionDimensions);
95923
95971
  trackStudioEvent("render_start", { format, quality, resolution: outputResolution, fps });
95924
- void onStartRender(format, quality, outputResolution, fps);
95972
+ void onStartRender(format, quality, outputResolution, fps, gpu);
95925
95973
  },
95926
95974
  className: "w-full",
95927
95975
  children: isRendering ? "Rendering\u2026" : "Export"
95928
95976
  }
95929
95977
  ),
95930
- lastRenderDurationMs !== void 0 && !isRendering && /* @__PURE__ */ jsxs185("p", { className: "text-step-9 text-text-5 text-center -mt-1.5", children: [
95978
+ lastRenderDurationMs !== void 0 && !isRendering && /* @__PURE__ */ jsxs186("p", { className: "text-step-9 text-text-5 text-center -mt-1.5", children: [
95931
95979
  "Last render took ",
95932
95980
  formatEta(lastRenderDurationMs)
95933
95981
  ] })
@@ -95958,8 +96006,8 @@ var RenderQueue = memo57(function RenderQueue2({
95958
96006
  }, [jobs.length]);
95959
96007
  const completedCount = jobs.filter((j) => j.status !== "rendering").length;
95960
96008
  const lastRenderDurationMs = [...jobs].reverse().find((j) => j.status === "complete" && j.durationMs !== void 0)?.durationMs;
95961
- return /* @__PURE__ */ jsxs185("div", { className: "flex flex-col h-full", children: [
95962
- /* @__PURE__ */ jsx233("div", { className: "px-3 py-3 border-b border-border shrink-0", children: /* @__PURE__ */ jsx233(
96009
+ return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col h-full", children: [
96010
+ /* @__PURE__ */ jsx234("div", { className: "px-3 py-3 border-b border-border shrink-0", children: /* @__PURE__ */ jsx234(
95963
96011
  FormatExportButton,
95964
96012
  {
95965
96013
  onStartRender,
@@ -95971,21 +96019,21 @@ var RenderQueue = memo57(function RenderQueue2({
95971
96019
  onRecheckFfmpeg
95972
96020
  }
95973
96021
  ) }),
95974
- actionError && /* @__PURE__ */ jsxs185(
96022
+ actionError && /* @__PURE__ */ jsxs186(
95975
96023
  "div",
95976
96024
  {
95977
96025
  role: "alert",
95978
96026
  className: "flex items-start justify-between gap-2 px-3 py-2 border-b border-border bg-danger/10",
95979
96027
  children: [
95980
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-danger-ink", children: actionError }),
95981
- onDismissActionError && /* @__PURE__ */ jsx233(
96028
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-danger-ink", children: actionError }),
96029
+ onDismissActionError && /* @__PURE__ */ jsx234(
95982
96030
  IconButton,
95983
96031
  {
95984
96032
  size: "sm",
95985
96033
  onClick: onDismissActionError,
95986
96034
  "aria-label": "Dismiss error",
95987
96035
  className: "shrink-0",
95988
- icon: /* @__PURE__ */ jsx233(
96036
+ icon: /* @__PURE__ */ jsx234(
95989
96037
  "svg",
95990
96038
  {
95991
96039
  width: "10",
@@ -95996,7 +96044,7 @@ var RenderQueue = memo57(function RenderQueue2({
95996
96044
  strokeWidth: "2",
95997
96045
  strokeLinecap: "round",
95998
96046
  "aria-hidden": "true",
95999
- children: /* @__PURE__ */ jsx233("path", { d: "M18 6L6 18M6 6l12 12" })
96047
+ children: /* @__PURE__ */ jsx234("path", { d: "M18 6L6 18M6 6l12 12" })
96000
96048
  }
96001
96049
  )
96002
96050
  }
@@ -96004,11 +96052,11 @@ var RenderQueue = memo57(function RenderQueue2({
96004
96052
  ]
96005
96053
  }
96006
96054
  ),
96007
- /* @__PURE__ */ jsx233("div", { ref: listRef, className: "flex-1 overflow-y-auto", children: loadError && jobs.length === 0 ? /* @__PURE__ */ jsxs185("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-2", role: "alert", children: [
96008
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 text-danger-ink text-center", children: loadError }),
96009
- onRetryLoad && /* @__PURE__ */ jsx233(Button, { size: "sm", variant: "secondary", onClick: onRetryLoad, children: "Retry" })
96010
- ] }) : jobs.length === 0 ? /* @__PURE__ */ jsxs185("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-2", children: [
96011
- /* @__PURE__ */ jsxs185(
96055
+ /* @__PURE__ */ jsx234("div", { ref: listRef, className: "flex-1 overflow-y-auto", children: loadError && jobs.length === 0 ? /* @__PURE__ */ jsxs186("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-2", role: "alert", children: [
96056
+ /* @__PURE__ */ jsx234("p", { className: "text-step-10 text-danger-ink text-center", children: loadError }),
96057
+ onRetryLoad && /* @__PURE__ */ jsx234(Button, { size: "sm", variant: "secondary", onClick: onRetryLoad, children: "Retry" })
96058
+ ] }) : jobs.length === 0 ? /* @__PURE__ */ jsxs186("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-2", children: [
96059
+ /* @__PURE__ */ jsxs186(
96012
96060
  "svg",
96013
96061
  {
96014
96062
  width: "20",
@@ -96019,7 +96067,7 @@ var RenderQueue = memo57(function RenderQueue2({
96019
96067
  strokeWidth: "1.5",
96020
96068
  className: "text-text-5",
96021
96069
  children: [
96022
- /* @__PURE__ */ jsx233(
96070
+ /* @__PURE__ */ jsx234(
96023
96071
  "rect",
96024
96072
  {
96025
96073
  x: "2",
@@ -96032,7 +96080,7 @@ var RenderQueue = memo57(function RenderQueue2({
96032
96080
  strokeLinejoin: "round"
96033
96081
  }
96034
96082
  ),
96035
- /* @__PURE__ */ jsx233(
96083
+ /* @__PURE__ */ jsx234(
96036
96084
  "path",
96037
96085
  {
96038
96086
  d: "M7 2v20M17 2v20M2 12h20M2 7h5M2 17h5M17 17h5M17 7h5",
@@ -96043,17 +96091,17 @@ var RenderQueue = memo57(function RenderQueue2({
96043
96091
  ]
96044
96092
  }
96045
96093
  ),
96046
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 text-text-5 text-center", children: "No renders yet" })
96047
- ] }) : /* @__PURE__ */ jsxs185("div", { children: [
96048
- completedCount > 0 && /* @__PURE__ */ jsxs185("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border", children: [
96049
- /* @__PURE__ */ jsxs185("span", { className: "text-step-10 text-text-4", children: [
96094
+ /* @__PURE__ */ jsx234("p", { className: "text-step-10 text-text-5 text-center", children: "No renders yet" })
96095
+ ] }) : /* @__PURE__ */ jsxs186("div", { children: [
96096
+ completedCount > 0 && /* @__PURE__ */ jsxs186("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border", children: [
96097
+ /* @__PURE__ */ jsxs186("span", { className: "text-step-10 text-text-4", children: [
96050
96098
  jobs.length,
96051
96099
  " render",
96052
96100
  jobs.length === 1 ? "" : "s"
96053
96101
  ] }),
96054
- /* @__PURE__ */ jsx233(Tooltip, { label: "Hide finished renders from this list (files stay on disk)", children: /* @__PURE__ */ jsx233(Button, { size: "sm", variant: "ghost", onClick: onClearCompleted, children: "Hide finished" }) })
96102
+ /* @__PURE__ */ jsx234(Tooltip, { label: "Hide finished renders from this list (files stay on disk)", children: /* @__PURE__ */ jsx234(Button, { size: "sm", variant: "ghost", onClick: onClearCompleted, children: "Hide finished" }) })
96055
96103
  ] }),
96056
- jobs.map((job) => /* @__PURE__ */ jsx233(
96104
+ jobs.map((job) => /* @__PURE__ */ jsx234(
96057
96105
  RenderQueueItem,
96058
96106
  {
96059
96107
  job,
@@ -96068,10 +96116,10 @@ var RenderQueue = memo57(function RenderQueue2({
96068
96116
  });
96069
96117
 
96070
96118
  // src/components/renders/RenderQueuePanel.tsx
96071
- import { jsx as jsx234 } from "react/jsx-runtime";
96119
+ import { jsx as jsx235 } from "react/jsx-runtime";
96072
96120
  var RenderQueuePanel = memo58(function RenderQueuePanel2() {
96073
96121
  const { projectId, compositionDimensions, waitForPendingDomEditSaves, renderQueue } = useStudioShellContext();
96074
- return /* @__PURE__ */ jsx234(
96122
+ return /* @__PURE__ */ jsx235(
96075
96123
  RenderQueue,
96076
96124
  {
96077
96125
  jobs: renderQueue.jobs,
@@ -96086,13 +96134,14 @@ var RenderQueuePanel = memo58(function RenderQueuePanel2() {
96086
96134
  ffmpeg: renderQueue.ffmpeg,
96087
96135
  ffmpegChecking: renderQueue.ffmpegChecking,
96088
96136
  onRecheckFfmpeg: renderQueue.recheckFfmpeg,
96089
- onStartRender: async (format, quality, resolution, fps) => {
96137
+ onStartRender: async (format, quality, resolution, fps, gpu) => {
96090
96138
  await waitForPendingDomEditSaves();
96091
96139
  await renderQueue.startRender({
96092
96140
  fps,
96093
96141
  quality,
96094
96142
  format,
96095
96143
  resolution,
96144
+ gpu,
96096
96145
  // Render what the user is previewing: active variable overrides
96097
96146
  // from the Variables panel ride along (undefined = defaults).
96098
96147
  variables: usePreviewVariablesStore.getState().values ?? void 0
@@ -96110,13 +96159,13 @@ import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
96110
96159
 
96111
96160
  // src/components/panels/SlideshowSubPanels.tsx
96112
96161
  import { useState as useState154, useCallback as useCallback178, useId as useId6 } from "react";
96113
- import { jsx as jsx235, jsxs as jsxs186 } from "react/jsx-runtime";
96162
+ import { jsx as jsx236, jsxs as jsxs187 } from "react/jsx-runtime";
96114
96163
  function SectionHeader({
96115
96164
  children,
96116
96165
  expanded,
96117
96166
  onToggle
96118
96167
  }) {
96119
- return /* @__PURE__ */ jsxs186(
96168
+ return /* @__PURE__ */ jsxs187(
96120
96169
  "button",
96121
96170
  {
96122
96171
  type: "button",
@@ -96124,8 +96173,8 @@ function SectionHeader({
96124
96173
  onClick: onToggle,
96125
96174
  "aria-expanded": expanded,
96126
96175
  children: [
96127
- /* @__PURE__ */ jsx235("span", { children }),
96128
- /* @__PURE__ */ jsx235("span", { className: "text-[10px] text-neutral-600", children: expanded ? "\u25B2" : "\u25BC" })
96176
+ /* @__PURE__ */ jsx236("span", { children }),
96177
+ /* @__PURE__ */ jsx236("span", { className: "text-[10px] text-neutral-600", children: expanded ? "\u25B2" : "\u25BC" })
96129
96178
  ]
96130
96179
  }
96131
96180
  );
@@ -96143,12 +96192,12 @@ function SlideList({
96143
96192
  const orderedSlideScenes = slides.map((sl) => sceneById.get(sl.sceneId)).filter((s) => s !== void 0);
96144
96193
  const nonSlideScenes = scenes.filter((sc) => !slideIds.has(sc.id));
96145
96194
  const rows = [...orderedSlideScenes, ...nonSlideScenes];
96146
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-px", children: [
96195
+ return /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-px", children: [
96147
96196
  rows.map((scene) => {
96148
96197
  const isSlide = slideIds.has(scene.id);
96149
96198
  const isSelected2 = selectedSceneId === scene.id;
96150
96199
  const slideIndex = slides.findIndex((s) => s.sceneId === scene.id);
96151
- return /* @__PURE__ */ jsxs186(
96200
+ return /* @__PURE__ */ jsxs187(
96152
96201
  "div",
96153
96202
  {
96154
96203
  role: "button",
@@ -96163,7 +96212,7 @@ function SlideList({
96163
96212
  }
96164
96213
  },
96165
96214
  children: [
96166
- /* @__PURE__ */ jsx235(
96215
+ /* @__PURE__ */ jsx236(
96167
96216
  "input",
96168
96217
  {
96169
96218
  type: "checkbox",
@@ -96174,9 +96223,9 @@ function SlideList({
96174
96223
  className: "accent-studio-accent shrink-0"
96175
96224
  }
96176
96225
  ),
96177
- /* @__PURE__ */ jsx235("span", { className: "flex-1 truncate", children: scene.label || scene.id }),
96178
- isSlide && /* @__PURE__ */ jsxs186("span", { className: "flex gap-0.5 shrink-0", children: [
96179
- /* @__PURE__ */ jsx235(
96226
+ /* @__PURE__ */ jsx236("span", { className: "flex-1 truncate", children: scene.label || scene.id }),
96227
+ isSlide && /* @__PURE__ */ jsxs187("span", { className: "flex gap-0.5 shrink-0", children: [
96228
+ /* @__PURE__ */ jsx236(
96180
96229
  "button",
96181
96230
  {
96182
96231
  type: "button",
@@ -96191,7 +96240,7 @@ function SlideList({
96191
96240
  children: "\u25B2"
96192
96241
  }
96193
96242
  ),
96194
- /* @__PURE__ */ jsx235(
96243
+ /* @__PURE__ */ jsx236(
96195
96244
  "button",
96196
96245
  {
96197
96246
  type: "button",
@@ -96212,7 +96261,7 @@ function SlideList({
96212
96261
  scene.id
96213
96262
  );
96214
96263
  }),
96215
- scenes.length === 0 && /* @__PURE__ */ jsx235("p", { className: "px-3 py-2 text-[11px] text-neutral-500 italic", children: "No scenes found" })
96264
+ scenes.length === 0 && /* @__PURE__ */ jsx236("p", { className: "px-3 py-2 text-[11px] text-neutral-500 italic", children: "No scenes found" })
96216
96265
  ] });
96217
96266
  }
96218
96267
  function SlideInspector({
@@ -96224,14 +96273,14 @@ function SlideInspector({
96224
96273
  onRemoveFragment
96225
96274
  }) {
96226
96275
  const fragments = slide?.fragments ?? [];
96227
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96228
- /* @__PURE__ */ jsxs186("p", { className: "text-[10px] text-neutral-500 font-medium uppercase tracking-wide truncate", children: [
96276
+ return /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96277
+ /* @__PURE__ */ jsxs187("p", { className: "text-[10px] text-neutral-500 font-medium uppercase tracking-wide truncate", children: [
96229
96278
  "Scene: ",
96230
96279
  sceneId
96231
96280
  ] }),
96232
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
96233
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Notes" }),
96234
- /* @__PURE__ */ jsx235(
96281
+ /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-1", children: [
96282
+ /* @__PURE__ */ jsx236("label", { className: "text-[11px] text-neutral-400", children: "Notes" }),
96283
+ /* @__PURE__ */ jsx236(
96235
96284
  "textarea",
96236
96285
  {
96237
96286
  className: "bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1.5 text-[11px] text-text-0 resize-none placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden",
@@ -96242,10 +96291,10 @@ function SlideInspector({
96242
96291
  }
96243
96292
  )
96244
96293
  ] }),
96245
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96246
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center justify-between", children: [
96247
- /* @__PURE__ */ jsx235("span", { className: "text-[11px] text-neutral-400", children: "Fragment hold-points" }),
96248
- /* @__PURE__ */ jsxs186(
96294
+ /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-1.5", children: [
96295
+ /* @__PURE__ */ jsxs187("div", { className: "flex items-center justify-between", children: [
96296
+ /* @__PURE__ */ jsx236("span", { className: "text-[11px] text-neutral-400", children: "Fragment hold-points" }),
96297
+ /* @__PURE__ */ jsxs187(
96249
96298
  "button",
96250
96299
  {
96251
96300
  type: "button",
@@ -96260,14 +96309,14 @@ function SlideInspector({
96260
96309
  }
96261
96310
  )
96262
96311
  ] }),
96263
- fragments.length > 0 ? /* @__PURE__ */ jsx235("div", { className: "flex flex-wrap gap-1", children: fragments.map((t2, i) => /* @__PURE__ */ jsxs186(
96312
+ fragments.length > 0 ? /* @__PURE__ */ jsx236("div", { className: "flex flex-wrap gap-1", children: fragments.map((t2, i) => /* @__PURE__ */ jsxs187(
96264
96313
  "span",
96265
96314
  {
96266
96315
  className: "inline-flex items-center gap-1 bg-neutral-700 rounded-sm px-1.5 py-0.5 text-[10px] text-neutral-200",
96267
96316
  children: [
96268
96317
  t2.toFixed(2),
96269
96318
  "s",
96270
- /* @__PURE__ */ jsx235(
96319
+ /* @__PURE__ */ jsx236(
96271
96320
  "button",
96272
96321
  {
96273
96322
  type: "button",
@@ -96280,7 +96329,7 @@ function SlideInspector({
96280
96329
  ]
96281
96330
  },
96282
96331
  `frag-${i}`
96283
- )) }) : /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-600 italic", children: "No hold-points yet" })
96332
+ )) }) : /* @__PURE__ */ jsx236("p", { className: "text-[10px] text-neutral-600 italic", children: "No hold-points yet" })
96284
96333
  ] })
96285
96334
  ] });
96286
96335
  }
@@ -96303,9 +96352,9 @@ function BranchTree({
96303
96352
  onCreateSequence(label);
96304
96353
  setNewLabel("");
96305
96354
  }, [newLabel, onCreateSequence]);
96306
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96307
- /* @__PURE__ */ jsxs186("div", { className: "flex gap-1.5", children: [
96308
- /* @__PURE__ */ jsx235(
96355
+ return /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96356
+ /* @__PURE__ */ jsxs187("div", { className: "flex gap-1.5", children: [
96357
+ /* @__PURE__ */ jsx236(
96309
96358
  "input",
96310
96359
  {
96311
96360
  id: inputId,
@@ -96320,7 +96369,7 @@ function BranchTree({
96320
96369
  "aria-label": "New branch sequence name"
96321
96370
  }
96322
96371
  ),
96323
- /* @__PURE__ */ jsx235(
96372
+ /* @__PURE__ */ jsx236(
96324
96373
  "button",
96325
96374
  {
96326
96375
  type: "button",
@@ -96330,7 +96379,7 @@ function BranchTree({
96330
96379
  }
96331
96380
  )
96332
96381
  ] }),
96333
- sequences.length === 0 ? /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-600 italic", children: "No branches yet" }) : /* @__PURE__ */ jsx235("div", { className: "flex flex-col gap-3", children: sequences.map((seq) => /* @__PURE__ */ jsx235(
96382
+ sequences.length === 0 ? /* @__PURE__ */ jsx236("p", { className: "text-[10px] text-neutral-600 italic", children: "No branches yet" }) : /* @__PURE__ */ jsx236("div", { className: "flex flex-col gap-3", children: sequences.map((seq) => /* @__PURE__ */ jsx236(
96334
96383
  BranchItem,
96335
96384
  {
96336
96385
  seq,
@@ -96364,9 +96413,9 @@ function BranchItem({
96364
96413
  if (label && label !== seq.label) onRename(seq.id, label);
96365
96414
  setEditing(false);
96366
96415
  }, [draft, onRename, seq.id, seq.label]);
96367
- return /* @__PURE__ */ jsxs186("div", { className: "border border-neutral-700/60 rounded-sm p-2 flex flex-col gap-2", children: [
96368
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center gap-1", children: [
96369
- editing ? /* @__PURE__ */ jsx235(
96416
+ return /* @__PURE__ */ jsxs187("div", { className: "border border-neutral-700/60 rounded-sm p-2 flex flex-col gap-2", children: [
96417
+ /* @__PURE__ */ jsxs187("div", { className: "flex items-center gap-1", children: [
96418
+ editing ? /* @__PURE__ */ jsx236(
96370
96419
  "input",
96371
96420
  {
96372
96421
  className: "flex-1 bg-neutral-800 border border-neutral-600 rounded-sm px-1.5 py-0.5 text-[11px] text-text-0 focus:border-studio-accent/60 focus:outline-hidden",
@@ -96380,7 +96429,7 @@ function BranchItem({
96380
96429
  },
96381
96430
  "aria-label": `Rename branch ${seq.label}`
96382
96431
  }
96383
- ) : /* @__PURE__ */ jsx235(
96432
+ ) : /* @__PURE__ */ jsx236(
96384
96433
  "span",
96385
96434
  {
96386
96435
  role: "button",
@@ -96397,7 +96446,7 @@ function BranchItem({
96397
96446
  children: seq.label
96398
96447
  }
96399
96448
  ),
96400
- /* @__PURE__ */ jsx235(
96449
+ /* @__PURE__ */ jsx236(
96401
96450
  "button",
96402
96451
  {
96403
96452
  type: "button",
@@ -96408,16 +96457,16 @@ function BranchItem({
96408
96457
  }
96409
96458
  )
96410
96459
  ] }),
96411
- confirmingDelete && /* @__PURE__ */ jsxs186("div", { className: "px-2 py-1.5 bg-danger/15 border-l-2 border-red-500 flex flex-col gap-1 rounded-xs", children: [
96412
- /* @__PURE__ */ jsxs186("span", { className: "text-[10px] text-danger-ink", children: [
96460
+ confirmingDelete && /* @__PURE__ */ jsxs187("div", { className: "px-2 py-1.5 bg-danger/15 border-l-2 border-red-500 flex flex-col gap-1 rounded-xs", children: [
96461
+ /* @__PURE__ */ jsxs187("span", { className: "text-[10px] text-danger-ink", children: [
96413
96462
  "Delete branch \u201C",
96414
96463
  seq.label,
96415
96464
  "\u201D",
96416
96465
  seq.slides.length > 0 ? ` and its ${seq.slides.length} slide${seq.slides.length === 1 ? "" : "s"}` : "",
96417
96466
  "? Hotspots pointing to it will no longer resolve."
96418
96467
  ] }),
96419
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center justify-end gap-1", children: [
96420
- /* @__PURE__ */ jsx235(
96468
+ /* @__PURE__ */ jsxs187("div", { className: "flex items-center justify-end gap-1", children: [
96469
+ /* @__PURE__ */ jsx236(
96421
96470
  "button",
96422
96471
  {
96423
96472
  type: "button",
@@ -96429,7 +96478,7 @@ function BranchItem({
96429
96478
  children: "Delete"
96430
96479
  }
96431
96480
  ),
96432
- /* @__PURE__ */ jsx235(
96481
+ /* @__PURE__ */ jsx236(
96433
96482
  "button",
96434
96483
  {
96435
96484
  type: "button",
@@ -96440,16 +96489,16 @@ function BranchItem({
96440
96489
  )
96441
96490
  ] })
96442
96491
  ] }),
96443
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-px pl-2", children: [
96492
+ /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-px pl-2", children: [
96444
96493
  scenes.map((scene) => {
96445
96494
  const assigned = seq.slides.some((s) => s.sceneId === scene.id);
96446
96495
  const isSelected2 = selectedSequenceId === seq.id && selectedSceneId === scene.id;
96447
- return /* @__PURE__ */ jsxs186(
96496
+ return /* @__PURE__ */ jsxs187(
96448
96497
  "div",
96449
96498
  {
96450
96499
  className: "flex items-center gap-1.5 py-0.5 text-[11px] text-neutral-400",
96451
96500
  children: [
96452
- /* @__PURE__ */ jsx235(
96501
+ /* @__PURE__ */ jsx236(
96453
96502
  "input",
96454
96503
  {
96455
96504
  type: "checkbox",
@@ -96459,7 +96508,7 @@ function BranchItem({
96459
96508
  className: "accent-studio-accent shrink-0"
96460
96509
  }
96461
96510
  ),
96462
- assigned ? /* @__PURE__ */ jsx235(
96511
+ assigned ? /* @__PURE__ */ jsx236(
96463
96512
  "button",
96464
96513
  {
96465
96514
  type: "button",
@@ -96468,13 +96517,13 @@ function BranchItem({
96468
96517
  onClick: () => onSelectBranchSlide(seq.id, scene.id),
96469
96518
  children: scene.label || scene.id
96470
96519
  }
96471
- ) : /* @__PURE__ */ jsx235("span", { className: "flex-1 truncate", children: scene.label || scene.id })
96520
+ ) : /* @__PURE__ */ jsx236("span", { className: "flex-1 truncate", children: scene.label || scene.id })
96472
96521
  ]
96473
96522
  },
96474
96523
  scene.id
96475
96524
  );
96476
96525
  }),
96477
- scenes.length === 0 && /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-600 italic", children: "No scenes" })
96526
+ scenes.length === 0 && /* @__PURE__ */ jsx236("p", { className: "text-[10px] text-neutral-600 italic", children: "No scenes" })
96478
96527
  ] })
96479
96528
  ] });
96480
96529
  }
@@ -96500,19 +96549,19 @@ function HotspotTool({
96500
96549
  setHotspotLabel("");
96501
96550
  }, [selectedSceneId, targetSequenceId, elementKey3, hotspotLabel, onAddHotspot]);
96502
96551
  if (!selectedSceneId) {
96503
- return /* @__PURE__ */ jsx235("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsx235("p", { className: "text-[11px] text-neutral-500 italic", children: "Select a scene in the Slides list" }) });
96552
+ return /* @__PURE__ */ jsx236("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsx236("p", { className: "text-[11px] text-neutral-500 italic", children: "Select a scene in the Slides list" }) });
96504
96553
  }
96505
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96506
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96507
- /* @__PURE__ */ jsxs186("p", { className: "text-[11px] text-neutral-400", children: [
96554
+ return /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96555
+ /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-1.5", children: [
96556
+ /* @__PURE__ */ jsxs187("p", { className: "text-[11px] text-neutral-400", children: [
96508
96557
  "Selected element:",
96509
96558
  " ",
96510
- /* @__PURE__ */ jsx235("span", { className: "text-neutral-200 font-mono", children: elementKey3 ?? "none" })
96559
+ /* @__PURE__ */ jsx236("span", { className: "text-neutral-200 font-mono", children: elementKey3 ?? "none" })
96511
96560
  ] }),
96512
- !elementKey3 && /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-500 italic", children: "Click an element on the canvas to choose the hotspot target." }),
96513
- sequences.length === 0 && /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-500 italic", children: "Create a branch in the Branches section first \u2014 hotspots jump to a branch." }),
96514
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Hotspot label" }),
96515
- /* @__PURE__ */ jsx235(
96561
+ !elementKey3 && /* @__PURE__ */ jsx236("p", { className: "text-[10px] text-neutral-500 italic", children: "Click an element on the canvas to choose the hotspot target." }),
96562
+ sequences.length === 0 && /* @__PURE__ */ jsx236("p", { className: "text-[10px] text-neutral-500 italic", children: "Create a branch in the Branches section first \u2014 hotspots jump to a branch." }),
96563
+ /* @__PURE__ */ jsx236("label", { className: "text-[11px] text-neutral-400", children: "Hotspot label" }),
96564
+ /* @__PURE__ */ jsx236(
96516
96565
  "input",
96517
96566
  {
96518
96567
  type: "text",
@@ -96523,8 +96572,8 @@ function HotspotTool({
96523
96572
  "aria-label": "Hotspot label"
96524
96573
  }
96525
96574
  ),
96526
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Target branch" }),
96527
- /* @__PURE__ */ jsxs186(
96575
+ /* @__PURE__ */ jsx236("label", { className: "text-[11px] text-neutral-400", children: "Target branch" }),
96576
+ /* @__PURE__ */ jsxs187(
96528
96577
  "select",
96529
96578
  {
96530
96579
  className: "bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-text-0 focus:border-studio-accent/60 focus:outline-hidden",
@@ -96532,12 +96581,12 @@ function HotspotTool({
96532
96581
  onChange: (e) => setTargetSequenceId(e.target.value),
96533
96582
  "aria-label": "Target branch sequence",
96534
96583
  children: [
96535
- /* @__PURE__ */ jsx235("option", { value: "", children: "\u2014 select branch \u2014" }),
96536
- sequences.map((seq) => /* @__PURE__ */ jsx235("option", { value: seq.id, children: seq.label }, seq.id))
96584
+ /* @__PURE__ */ jsx236("option", { value: "", children: "\u2014 select branch \u2014" }),
96585
+ sequences.map((seq) => /* @__PURE__ */ jsx236("option", { value: seq.id, children: seq.label }, seq.id))
96537
96586
  ]
96538
96587
  }
96539
96588
  ),
96540
- /* @__PURE__ */ jsx235(
96589
+ /* @__PURE__ */ jsx236(
96541
96590
  "button",
96542
96591
  {
96543
96592
  type: "button",
@@ -96549,21 +96598,21 @@ function HotspotTool({
96549
96598
  }
96550
96599
  )
96551
96600
  ] }),
96552
- hotspots.length > 0 && /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96553
- /* @__PURE__ */ jsx235("p", { className: "text-[11px] text-neutral-400 font-medium", children: "Hotspots on this slide" }),
96601
+ hotspots.length > 0 && /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-1.5", children: [
96602
+ /* @__PURE__ */ jsx236("p", { className: "text-[11px] text-neutral-400 font-medium", children: "Hotspots on this slide" }),
96554
96603
  hotspots.map((h) => {
96555
96604
  const seqLabel = sequences.find((s) => s.id === h.target)?.label ?? h.target;
96556
- return /* @__PURE__ */ jsxs186(
96605
+ return /* @__PURE__ */ jsxs187(
96557
96606
  "div",
96558
96607
  {
96559
96608
  className: "flex items-center gap-2 bg-neutral-800 rounded-sm px-2 py-1",
96560
96609
  children: [
96561
- /* @__PURE__ */ jsxs186("span", { className: "flex-1 text-[11px] text-neutral-200 truncate", children: [
96610
+ /* @__PURE__ */ jsxs187("span", { className: "flex-1 text-[11px] text-neutral-200 truncate", children: [
96562
96611
  h.label,
96563
96612
  " \u2192 ",
96564
- /* @__PURE__ */ jsx235("span", { className: "text-neutral-400", children: seqLabel })
96613
+ /* @__PURE__ */ jsx236("span", { className: "text-neutral-400", children: seqLabel })
96565
96614
  ] }),
96566
- /* @__PURE__ */ jsx235(
96615
+ /* @__PURE__ */ jsx236(
96567
96616
  "button",
96568
96617
  {
96569
96618
  type: "button",
@@ -96713,7 +96762,7 @@ function removeHotspot(manifest, sceneId, hotspotId, sequenceId) {
96713
96762
  }
96714
96763
 
96715
96764
  // src/components/panels/SlideshowPanel.tsx
96716
- import { Fragment as Fragment65, jsx as jsx236, jsxs as jsxs187 } from "react/jsx-runtime";
96765
+ import { Fragment as Fragment65, jsx as jsx237, jsxs as jsxs188 } from "react/jsx-runtime";
96717
96766
  function safeParseManifest(html2) {
96718
96767
  try {
96719
96768
  return parseSlideshowManifest(html2) ?? { slides: [] };
@@ -96981,7 +97030,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
96981
97030
  },
96982
97031
  [selectedSequenceId, applyManifest]
96983
97032
  );
96984
- return /* @__PURE__ */ jsxs187(
97033
+ return /* @__PURE__ */ jsxs188(
96985
97034
  "div",
96986
97035
  {
96987
97036
  className: "flex flex-col h-full overflow-y-auto text-text-0",
@@ -96992,14 +97041,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
96992
97041
  handleUndo();
96993
97042
  },
96994
97043
  children: [
96995
- persistError && /* @__PURE__ */ jsxs187(
97044
+ persistError && /* @__PURE__ */ jsxs188(
96996
97045
  "div",
96997
97046
  {
96998
97047
  role: "alert",
96999
97048
  className: "flex items-center justify-between gap-2 px-3 py-2 bg-danger/15 border-b border-red-500/40",
97000
97049
  children: [
97001
- /* @__PURE__ */ jsx236("span", { className: "text-[11px] text-danger-ink", children: "Changes not saved" }),
97002
- /* @__PURE__ */ jsx236(
97050
+ /* @__PURE__ */ jsx237("span", { className: "text-[11px] text-danger-ink", children: "Changes not saved" }),
97051
+ /* @__PURE__ */ jsx237(
97003
97052
  "button",
97004
97053
  {
97005
97054
  type: "button",
@@ -97012,7 +97061,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97012
97061
  ]
97013
97062
  }
97014
97063
  ),
97015
- undoDepth > 0 && /* @__PURE__ */ jsx236("div", { className: "flex items-center justify-end px-3 py-1 border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs187(
97064
+ undoDepth > 0 && /* @__PURE__ */ jsx237("div", { className: "flex items-center justify-end px-3 py-1 border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs188(
97016
97065
  "button",
97017
97066
  {
97018
97067
  type: "button",
@@ -97026,7 +97075,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97026
97075
  ]
97027
97076
  }
97028
97077
  ) }),
97029
- /* @__PURE__ */ jsxs187(
97078
+ /* @__PURE__ */ jsxs188(
97030
97079
  SectionHeader,
97031
97080
  {
97032
97081
  expanded: expandedSections.has("slides"),
@@ -97038,7 +97087,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97038
97087
  ]
97039
97088
  }
97040
97089
  ),
97041
- expandedSections.has("slides") && /* @__PURE__ */ jsx236("div", { className: "py-1", children: /* @__PURE__ */ jsx236(
97090
+ expandedSections.has("slides") && /* @__PURE__ */ jsx237("div", { className: "py-1", children: /* @__PURE__ */ jsx237(
97042
97091
  SlideList,
97043
97092
  {
97044
97093
  scenes,
@@ -97052,7 +97101,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97052
97101
  onReorder: handleReorder
97053
97102
  }
97054
97103
  ) }),
97055
- /* @__PURE__ */ jsx236(
97104
+ /* @__PURE__ */ jsx237(
97056
97105
  SectionHeader,
97057
97106
  {
97058
97107
  expanded: expandedSections.has("inspector"),
@@ -97060,7 +97109,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97060
97109
  children: "Slide Inspector"
97061
97110
  }
97062
97111
  ),
97063
- expandedSections.has("inspector") && /* @__PURE__ */ jsx236(Fragment65, { children: selectedSceneId ? /* @__PURE__ */ jsx236(
97112
+ expandedSections.has("inspector") && /* @__PURE__ */ jsx237(Fragment65, { children: selectedSceneId ? /* @__PURE__ */ jsx237(
97064
97113
  SlideInspector,
97065
97114
  {
97066
97115
  sceneId: selectedSceneId,
@@ -97070,8 +97119,8 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97070
97119
  onMarkFragment: handleMarkFragment,
97071
97120
  onRemoveFragment: handleRemoveFragment
97072
97121
  }
97073
- ) : /* @__PURE__ */ jsx236("p", { className: "px-3 py-2 text-[11px] text-neutral-500 italic", children: "Select a scene above to inspect" }) }),
97074
- /* @__PURE__ */ jsxs187(
97122
+ ) : /* @__PURE__ */ jsx237("p", { className: "px-3 py-2 text-[11px] text-neutral-500 italic", children: "Select a scene above to inspect" }) }),
97123
+ /* @__PURE__ */ jsxs188(
97075
97124
  SectionHeader,
97076
97125
  {
97077
97126
  expanded: expandedSections.has("branches"),
@@ -97083,7 +97132,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97083
97132
  ]
97084
97133
  }
97085
97134
  ),
97086
- expandedSections.has("branches") && /* @__PURE__ */ jsx236(
97135
+ expandedSections.has("branches") && /* @__PURE__ */ jsx237(
97087
97136
  BranchTree,
97088
97137
  {
97089
97138
  sequences,
@@ -97097,7 +97146,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97097
97146
  onSelectBranchSlide: handleSelectBranchSlide
97098
97147
  }
97099
97148
  ),
97100
- /* @__PURE__ */ jsx236(
97149
+ /* @__PURE__ */ jsx237(
97101
97150
  SectionHeader,
97102
97151
  {
97103
97152
  expanded: expandedSections.has("hotspot"),
@@ -97105,7 +97154,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97105
97154
  children: "Hotspot Tool"
97106
97155
  }
97107
97156
  ),
97108
- expandedSections.has("hotspot") && /* @__PURE__ */ jsx236(
97157
+ expandedSections.has("hotspot") && /* @__PURE__ */ jsx237(
97109
97158
  HotspotTool,
97110
97159
  {
97111
97160
  selectedSceneId,
@@ -97247,7 +97296,7 @@ function useEditVariablesInFile(deps) {
97247
97296
 
97248
97297
  // src/components/panels/VariablesDeclarationForm.tsx
97249
97298
  import { useState as useState157 } from "react";
97250
- import { jsx as jsx237, jsxs as jsxs188 } from "react/jsx-runtime";
97299
+ import { jsx as jsx238, jsxs as jsxs189 } from "react/jsx-runtime";
97251
97300
  var VARIABLE_TYPES = [
97252
97301
  "string",
97253
97302
  "number",
@@ -97349,8 +97398,8 @@ function declarationFromDraft(draft) {
97349
97398
  }
97350
97399
  }
97351
97400
  function Field({ label, children }) {
97352
- return /* @__PURE__ */ jsxs188("div", { className: "space-y-1", children: [
97353
- /* @__PURE__ */ jsx237("label", { className: "text-[9px] font-medium text-neutral-500", children: label }),
97401
+ return /* @__PURE__ */ jsxs189("div", { className: "space-y-1", children: [
97402
+ /* @__PURE__ */ jsx238("label", { className: "text-[9px] font-medium text-neutral-500", children: label }),
97354
97403
  children
97355
97404
  ] });
97356
97405
  }
@@ -97359,20 +97408,20 @@ function DefaultField({
97359
97408
  onChange
97360
97409
  }) {
97361
97410
  if (draft.type === "boolean") {
97362
- return /* @__PURE__ */ jsxs188(
97411
+ return /* @__PURE__ */ jsxs189(
97363
97412
  "select",
97364
97413
  {
97365
97414
  value: draft.defaultRaw === "true" ? "true" : "false",
97366
97415
  onChange: (e) => onChange(e.target.value),
97367
97416
  className: VARIABLES_INPUT_CLASS,
97368
97417
  children: [
97369
- /* @__PURE__ */ jsx237("option", { value: "false", children: "false" }),
97370
- /* @__PURE__ */ jsx237("option", { value: "true", children: "true" })
97418
+ /* @__PURE__ */ jsx238("option", { value: "false", children: "false" }),
97419
+ /* @__PURE__ */ jsx238("option", { value: "true", children: "true" })
97371
97420
  ]
97372
97421
  }
97373
97422
  );
97374
97423
  }
97375
- return /* @__PURE__ */ jsx237(
97424
+ return /* @__PURE__ */ jsx238(
97376
97425
  "input",
97377
97426
  {
97378
97427
  type: "text",
@@ -97401,9 +97450,9 @@ function DeclarationForm({
97401
97450
  setError(null);
97402
97451
  onSubmit(result);
97403
97452
  };
97404
- return /* @__PURE__ */ jsxs188("div", { className: "space-y-2 rounded-lg border border-neutral-800 bg-neutral-900/60 p-2", children: [
97405
- /* @__PURE__ */ jsxs188("div", { className: "grid grid-cols-2 gap-2", children: [
97406
- /* @__PURE__ */ jsx237(Field, { label: "ID", children: /* @__PURE__ */ jsx237(
97453
+ return /* @__PURE__ */ jsxs189("div", { className: "space-y-2 rounded-lg border border-neutral-800 bg-neutral-900/60 p-2", children: [
97454
+ /* @__PURE__ */ jsxs189("div", { className: "grid grid-cols-2 gap-2", children: [
97455
+ /* @__PURE__ */ jsx238(Field, { label: "ID", children: /* @__PURE__ */ jsx238(
97407
97456
  "input",
97408
97457
  {
97409
97458
  type: "text",
@@ -97414,7 +97463,7 @@ function DeclarationForm({
97414
97463
  className: `${VARIABLES_INPUT_CLASS} font-mono disabled:opacity-50`
97415
97464
  }
97416
97465
  ) }),
97417
- /* @__PURE__ */ jsx237(Field, { label: "Label", children: /* @__PURE__ */ jsx237(
97466
+ /* @__PURE__ */ jsx238(Field, { label: "Label", children: /* @__PURE__ */ jsx238(
97418
97467
  "input",
97419
97468
  {
97420
97469
  type: "text",
@@ -97425,8 +97474,8 @@ function DeclarationForm({
97425
97474
  }
97426
97475
  ) })
97427
97476
  ] }),
97428
- /* @__PURE__ */ jsxs188("div", { className: "grid grid-cols-2 gap-2", children: [
97429
- /* @__PURE__ */ jsx237(Field, { label: "Type", children: /* @__PURE__ */ jsx237(
97477
+ /* @__PURE__ */ jsxs189("div", { className: "grid grid-cols-2 gap-2", children: [
97478
+ /* @__PURE__ */ jsx238(Field, { label: "Type", children: /* @__PURE__ */ jsx238(
97430
97479
  "select",
97431
97480
  {
97432
97481
  value: draft.type,
@@ -97435,12 +97484,12 @@ function DeclarationForm({
97435
97484
  if (type) set({ type });
97436
97485
  },
97437
97486
  className: VARIABLES_INPUT_CLASS,
97438
- children: VARIABLE_TYPES.map((t2) => /* @__PURE__ */ jsx237("option", { value: t2, children: t2 }, t2))
97487
+ children: VARIABLE_TYPES.map((t2) => /* @__PURE__ */ jsx238("option", { value: t2, children: t2 }, t2))
97439
97488
  }
97440
97489
  ) }),
97441
- /* @__PURE__ */ jsx237(Field, { label: "Default", children: /* @__PURE__ */ jsx237(DefaultField, { draft, onChange: (defaultRaw) => set({ defaultRaw }) }) })
97490
+ /* @__PURE__ */ jsx238(Field, { label: "Default", children: /* @__PURE__ */ jsx238(DefaultField, { draft, onChange: (defaultRaw) => set({ defaultRaw }) }) })
97442
97491
  ] }),
97443
- draft.type === "number" && /* @__PURE__ */ jsx237("div", { className: "grid grid-cols-3 gap-2", children: ["min", "max", "step"].map((key2) => /* @__PURE__ */ jsx237(Field, { label: key2, children: /* @__PURE__ */ jsx237(
97492
+ draft.type === "number" && /* @__PURE__ */ jsx238("div", { className: "grid grid-cols-3 gap-2", children: ["min", "max", "step"].map((key2) => /* @__PURE__ */ jsx238(Field, { label: key2, children: /* @__PURE__ */ jsx238(
97444
97493
  "input",
97445
97494
  {
97446
97495
  type: "text",
@@ -97449,7 +97498,7 @@ function DeclarationForm({
97449
97498
  className: `${VARIABLES_INPUT_CLASS} tabular-nums`
97450
97499
  }
97451
97500
  ) }, key2)) }),
97452
- draft.type === "enum" && /* @__PURE__ */ jsx237(Field, { label: "Options (one per line, value:Label)", children: /* @__PURE__ */ jsx237(
97501
+ draft.type === "enum" && /* @__PURE__ */ jsx238(Field, { label: "Options (one per line, value:Label)", children: /* @__PURE__ */ jsx238(
97453
97502
  "textarea",
97454
97503
  {
97455
97504
  value: draft.optionsRaw,
@@ -97458,7 +97507,7 @@ function DeclarationForm({
97458
97507
  className: `${VARIABLES_INPUT_CLASS} resize-y font-mono`
97459
97508
  }
97460
97509
  ) }),
97461
- /* @__PURE__ */ jsx237(Field, { label: "Description (optional)", children: /* @__PURE__ */ jsx237(
97510
+ /* @__PURE__ */ jsx238(Field, { label: "Description (optional)", children: /* @__PURE__ */ jsx238(
97462
97511
  "input",
97463
97512
  {
97464
97513
  type: "text",
@@ -97467,9 +97516,9 @@ function DeclarationForm({
97467
97516
  className: VARIABLES_INPUT_CLASS
97468
97517
  }
97469
97518
  ) }),
97470
- error && /* @__PURE__ */ jsx237("p", { className: "text-[10px] text-danger-ink", children: error }),
97471
- /* @__PURE__ */ jsxs188("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
97472
- /* @__PURE__ */ jsx237(
97519
+ error && /* @__PURE__ */ jsx238("p", { className: "text-[10px] text-danger-ink", children: error }),
97520
+ /* @__PURE__ */ jsxs189("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
97521
+ /* @__PURE__ */ jsx238(
97473
97522
  "button",
97474
97523
  {
97475
97524
  type: "button",
@@ -97478,7 +97527,7 @@ function DeclarationForm({
97478
97527
  children: "Cancel"
97479
97528
  }
97480
97529
  ),
97481
- /* @__PURE__ */ jsx237(
97530
+ /* @__PURE__ */ jsx238(
97482
97531
  "button",
97483
97532
  {
97484
97533
  type: "button",
@@ -97492,14 +97541,14 @@ function DeclarationForm({
97492
97541
  }
97493
97542
 
97494
97543
  // src/components/panels/VariablesRowAction.tsx
97495
- import { jsx as jsx238 } from "react/jsx-runtime";
97544
+ import { jsx as jsx239 } from "react/jsx-runtime";
97496
97545
  function RowAction({
97497
97546
  label,
97498
97547
  title,
97499
97548
  danger,
97500
97549
  onClick
97501
97550
  }) {
97502
- return /* @__PURE__ */ jsx238(
97551
+ return /* @__PURE__ */ jsx239(
97503
97552
  "button",
97504
97553
  {
97505
97554
  type: "button",
@@ -97512,7 +97561,7 @@ function RowAction({
97512
97561
  }
97513
97562
 
97514
97563
  // src/components/panels/VariablesOtherCompositions.tsx
97515
- import { jsx as jsx239, jsxs as jsxs189 } from "react/jsx-runtime";
97564
+ import { jsx as jsx240, jsxs as jsxs190 } from "react/jsx-runtime";
97516
97565
  function CompositionSection({
97517
97566
  group,
97518
97567
  editingKey,
@@ -97520,8 +97569,8 @@ function CompositionSection({
97520
97569
  onSave,
97521
97570
  onRemove
97522
97571
  }) {
97523
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-1.5", children: [
97524
- /* @__PURE__ */ jsx239(
97572
+ return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5", children: [
97573
+ /* @__PURE__ */ jsx240(
97525
97574
  "p",
97526
97575
  {
97527
97576
  className: "truncate text-[9px] font-medium uppercase tracking-wider text-neutral-500",
@@ -97532,12 +97581,12 @@ function CompositionSection({
97532
97581
  group.variables.map((decl) => {
97533
97582
  const key2 = `${group.path}::${decl.id}`;
97534
97583
  const editing = editingKey === key2;
97535
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97536
- /* @__PURE__ */ jsxs189("div", { className: "flex items-center gap-1.5", children: [
97537
- /* @__PURE__ */ jsx239("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97538
- /* @__PURE__ */ jsx239("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97539
- /* @__PURE__ */ jsxs189("span", { className: "ml-auto flex items-center gap-1", children: [
97540
- /* @__PURE__ */ jsx239(
97584
+ return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97585
+ /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-1.5", children: [
97586
+ /* @__PURE__ */ jsx240("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97587
+ /* @__PURE__ */ jsx240("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97588
+ /* @__PURE__ */ jsxs190("span", { className: "ml-auto flex items-center gap-1", children: [
97589
+ /* @__PURE__ */ jsx240(
97541
97590
  RowAction,
97542
97591
  {
97543
97592
  label: "Edit",
@@ -97545,7 +97594,7 @@ function CompositionSection({
97545
97594
  onClick: () => onToggleEdit(editing ? null : key2)
97546
97595
  }
97547
97596
  ),
97548
- /* @__PURE__ */ jsx239(
97597
+ /* @__PURE__ */ jsx240(
97549
97598
  RowAction,
97550
97599
  {
97551
97600
  label: "\u2715",
@@ -97556,8 +97605,8 @@ function CompositionSection({
97556
97605
  )
97557
97606
  ] })
97558
97607
  ] }),
97559
- decl.description && /* @__PURE__ */ jsx239("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97560
- editing && /* @__PURE__ */ jsx239(
97608
+ decl.description && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97609
+ editing && /* @__PURE__ */ jsx240(
97561
97610
  DeclarationForm,
97562
97611
  {
97563
97612
  initial: draftFromDeclaration(decl),
@@ -97615,9 +97664,9 @@ function VariablesOtherCompositions({
97615
97664
  [editInFile]
97616
97665
  );
97617
97666
  if (groups.length === 0) return null;
97618
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-3 border-t border-neutral-800 pt-3", children: [
97619
- /* @__PURE__ */ jsx239("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-600", children: "Other compositions" }),
97620
- groups.map((group) => /* @__PURE__ */ jsx239(
97667
+ return /* @__PURE__ */ jsxs190("div", { className: "space-y-3 border-t border-neutral-800 pt-3", children: [
97668
+ /* @__PURE__ */ jsx240("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-600", children: "Other compositions" }),
97669
+ groups.map((group) => /* @__PURE__ */ jsx240(
97621
97670
  CompositionSection,
97622
97671
  {
97623
97672
  group,
@@ -97633,7 +97682,7 @@ function VariablesOtherCompositions({
97633
97682
 
97634
97683
  // src/components/panels/VariablesPanel.tsx
97635
97684
  import { isScalarVariableValue as isScalar } from "@hyperframes/core/variables";
97636
- import { jsx as jsx240, jsxs as jsxs190 } from "react/jsx-runtime";
97685
+ import { jsx as jsx241, jsxs as jsxs191 } from "react/jsx-runtime";
97637
97686
  function shellSingleQuote(value) {
97638
97687
  return `'${value.replace(/'/g, `'\\''`)}'`;
97639
97688
  }
@@ -97649,7 +97698,7 @@ function formatIssue(issue) {
97649
97698
  }
97650
97699
  function ValidationStrip({ issues }) {
97651
97700
  if (issues.length === 0) return null;
97652
- return /* @__PURE__ */ jsx240("div", { className: "space-y-1 rounded-lg border border-danger/40 bg-danger/15 p-2", children: issues.map((issue) => /* @__PURE__ */ jsx240("p", { className: "text-[10px] text-danger-ink", children: formatIssue(issue) }, `${issue.kind}:${issue.variableId}`)) });
97701
+ return /* @__PURE__ */ jsx241("div", { className: "space-y-1 rounded-lg border border-danger/40 bg-danger/15 p-2", children: issues.map((issue) => /* @__PURE__ */ jsx241("p", { className: "text-[10px] text-danger-ink", children: formatIssue(issue) }, `${issue.kind}:${issue.variableId}`)) });
97653
97702
  }
97654
97703
  function VariableRow({
97655
97704
  decl,
@@ -97663,11 +97712,11 @@ function VariableRow({
97663
97712
  onSaveEdit,
97664
97713
  onRemove
97665
97714
  }) {
97666
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97667
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-1.5", children: [
97668
- /* @__PURE__ */ jsx240("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97669
- /* @__PURE__ */ jsx240("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97670
- unused && /* @__PURE__ */ jsx240(
97715
+ return /* @__PURE__ */ jsxs191("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97716
+ /* @__PURE__ */ jsxs191("div", { className: "flex items-center gap-1.5", children: [
97717
+ /* @__PURE__ */ jsx241("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97718
+ /* @__PURE__ */ jsx241("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97719
+ unused && /* @__PURE__ */ jsx241(
97671
97720
  "span",
97672
97721
  {
97673
97722
  className: "rounded-sm bg-amber-500/15 px-1 py-px text-[8px] text-warning-ink",
@@ -97675,9 +97724,9 @@ function VariableRow({
97675
97724
  children: "unused"
97676
97725
  }
97677
97726
  ),
97678
- overridden && /* @__PURE__ */ jsx240("span", { className: "h-1.5 w-1.5 rounded-full bg-studio-accent" }),
97679
- /* @__PURE__ */ jsxs190("span", { className: "ml-auto flex items-center gap-1", children: [
97680
- overridden && isScalar(value) && /* @__PURE__ */ jsx240(
97727
+ overridden && /* @__PURE__ */ jsx241("span", { className: "h-1.5 w-1.5 rounded-full bg-studio-accent" }),
97728
+ /* @__PURE__ */ jsxs191("span", { className: "ml-auto flex items-center gap-1", children: [
97729
+ overridden && isScalar(value) && /* @__PURE__ */ jsx241(
97681
97730
  RowAction,
97682
97731
  {
97683
97732
  label: "Set default",
@@ -97685,12 +97734,12 @@ function VariableRow({
97685
97734
  onClick: () => onSetDefault(value)
97686
97735
  }
97687
97736
  ),
97688
- /* @__PURE__ */ jsx240(RowAction, { label: "Edit", title: "Edit declaration", onClick: onToggleEdit }),
97689
- /* @__PURE__ */ jsx240(RowAction, { label: "\u2715", title: "Remove declaration", danger: true, onClick: onRemove })
97737
+ /* @__PURE__ */ jsx241(RowAction, { label: "Edit", title: "Edit declaration", onClick: onToggleEdit }),
97738
+ /* @__PURE__ */ jsx241(RowAction, { label: "\u2715", title: "Remove declaration", danger: true, onClick: onRemove })
97690
97739
  ] })
97691
97740
  ] }),
97692
- decl.description && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97693
- editing ? /* @__PURE__ */ jsx240(
97741
+ decl.description && /* @__PURE__ */ jsx241("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97742
+ editing ? /* @__PURE__ */ jsx241(
97694
97743
  DeclarationForm,
97695
97744
  {
97696
97745
  initial: draftFromDeclaration(decl),
@@ -97698,7 +97747,7 @@ function VariableRow({
97698
97747
  onSubmit: (edited) => onSaveEdit(mergeDeclarationEdit(decl, edited)),
97699
97748
  onCancel: onToggleEdit
97700
97749
  }
97701
- ) : /* @__PURE__ */ jsx240(PreviewValueControl, { decl, value, onCommit: onCommitPreview })
97750
+ ) : /* @__PURE__ */ jsx241(PreviewValueControl, { decl, value, onCommit: onCommitPreview })
97702
97751
  ] });
97703
97752
  }
97704
97753
  function UndeclaredReads({
@@ -97706,11 +97755,11 @@ function UndeclaredReads({
97706
97755
  onDeclare
97707
97756
  }) {
97708
97757
  if (!usage || usage.undeclaredReads.length === 0) return null;
97709
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97710
- /* @__PURE__ */ jsx240("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Read by scripts, not declared" }),
97711
- usage.undeclaredReads.map((id) => /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97712
- /* @__PURE__ */ jsx240("code", { className: "font-mono text-[10px] text-neutral-400", children: id }),
97713
- /* @__PURE__ */ jsx240(
97758
+ return /* @__PURE__ */ jsxs191("div", { className: "space-y-1 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97759
+ /* @__PURE__ */ jsx241("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Read by scripts, not declared" }),
97760
+ usage.undeclaredReads.map((id) => /* @__PURE__ */ jsxs191("div", { className: "flex items-center gap-2", children: [
97761
+ /* @__PURE__ */ jsx241("code", { className: "font-mono text-[10px] text-neutral-400", children: id }),
97762
+ /* @__PURE__ */ jsx241(
97714
97763
  RowAction,
97715
97764
  {
97716
97765
  label: "Declare",
@@ -97726,10 +97775,10 @@ function PreviewModeHeader({
97726
97775
  onReset
97727
97776
  }) {
97728
97777
  const hasOverrides = overrideCount > 0;
97729
- return /* @__PURE__ */ jsxs190("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
97730
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97731
- /* @__PURE__ */ jsx240("span", { className: "text-[11px] font-semibold text-neutral-200", children: "Variables" }),
97732
- /* @__PURE__ */ jsx240(
97778
+ return /* @__PURE__ */ jsxs191("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
97779
+ /* @__PURE__ */ jsxs191("div", { className: "flex items-center gap-2", children: [
97780
+ /* @__PURE__ */ jsx241("span", { className: "text-[11px] font-semibold text-neutral-200", children: "Variables" }),
97781
+ /* @__PURE__ */ jsx241(
97733
97782
  "span",
97734
97783
  {
97735
97784
  className: `rounded-full px-2 py-0.5 text-[9px] font-medium ${hasOverrides ? "bg-studio-accent/20 text-accent-ink" : "bg-neutral-800 text-neutral-500"}`,
@@ -97737,7 +97786,7 @@ function PreviewModeHeader({
97737
97786
  }
97738
97787
  )
97739
97788
  ] }),
97740
- hasOverrides && /* @__PURE__ */ jsx240(
97789
+ hasOverrides && /* @__PURE__ */ jsx241(
97741
97790
  "button",
97742
97791
  {
97743
97792
  type: "button",
@@ -97755,11 +97804,11 @@ function HandoffFooter({
97755
97804
  }) {
97756
97805
  const json = JSON.stringify(effectiveValues);
97757
97806
  const command = `npx hyperframes render ${shellSingleQuote(compPath)} --variables ${shellSingleQuote(json)}`;
97758
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97759
- /* @__PURE__ */ jsx240("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Use this template" }),
97760
- /* @__PURE__ */ jsx240("code", { className: "block truncate font-mono text-[9px] text-neutral-500", title: command, children: command }),
97761
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97762
- /* @__PURE__ */ jsx240(
97807
+ return /* @__PURE__ */ jsxs191("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97808
+ /* @__PURE__ */ jsx241("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Use this template" }),
97809
+ /* @__PURE__ */ jsx241("code", { className: "block truncate font-mono text-[9px] text-neutral-500", title: command, children: command }),
97810
+ /* @__PURE__ */ jsxs191("div", { className: "flex items-center gap-2", children: [
97811
+ /* @__PURE__ */ jsx241(
97763
97812
  RowAction,
97764
97813
  {
97765
97814
  label: "Copy render command",
@@ -97767,7 +97816,7 @@ function HandoffFooter({
97767
97816
  onClick: () => onCopy(command, "Render command")
97768
97817
  }
97769
97818
  ),
97770
- /* @__PURE__ */ jsx240(
97819
+ /* @__PURE__ */ jsx241(
97771
97820
  RowAction,
97772
97821
  {
97773
97822
  label: "Copy values JSON",
@@ -97778,15 +97827,15 @@ function HandoffFooter({
97778
97827
  ] })
97779
97828
  ] });
97780
97829
  }
97781
- var EMPTY_STATE = /* @__PURE__ */ jsxs190("p", { className: "text-[10px] leading-relaxed text-neutral-500", children: [
97830
+ var EMPTY_STATE = /* @__PURE__ */ jsxs191("p", { className: "text-[10px] leading-relaxed text-neutral-500", children: [
97782
97831
  "No variables declared. Variables make parts of this composition dynamic \u2014 declare them here (or in ",
97783
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "data-composition-variables" }),
97832
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "data-composition-variables" }),
97784
97833
  "), read them with",
97785
97834
  " ",
97786
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "getVariables()" }),
97835
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "getVariables()" }),
97787
97836
  ", and pass values at render time with",
97788
97837
  " ",
97789
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "--variables" }),
97838
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "--variables" }),
97790
97839
  "."
97791
97840
  ] });
97792
97841
  var VariablesPanel = memo59(function VariablesPanel2({
@@ -97970,18 +98019,18 @@ var VariablesPanel = memo59(function VariablesPanel2({
97970
98019
  );
97971
98020
  const bindableSelection = domEditSelection?.hfId && domEditSelection.sourceFile === (activeCompPath ?? "index.html") ? domEditSelection : null;
97972
98021
  if (!sdkSession) {
97973
- return /* @__PURE__ */ jsx240("div", { className: "flex h-full items-center justify-center px-6 text-center", children: /* @__PURE__ */ jsx240("p", { className: "text-xs text-neutral-500", children: "Open a composition to manage its variables." }) });
98022
+ return /* @__PURE__ */ jsx241("div", { className: "flex h-full items-center justify-center px-6 text-center", children: /* @__PURE__ */ jsx241("p", { className: "text-xs text-neutral-500", children: "Open a composition to manage its variables." }) });
97974
98023
  }
97975
- return /* @__PURE__ */ jsxs190("div", { className: "flex h-full flex-col", children: [
97976
- /* @__PURE__ */ jsx240(
98024
+ return /* @__PURE__ */ jsxs191("div", { className: "flex h-full flex-col", children: [
98025
+ /* @__PURE__ */ jsx241(
97977
98026
  PreviewModeHeader,
97978
98027
  {
97979
98028
  overrideCount: previewValues ? Object.keys(previewValues).length : 0,
97980
98029
  onReset: resetPreview
97981
98030
  }
97982
98031
  ),
97983
- /* @__PURE__ */ jsxs190("div", { className: "flex-1 space-y-3 overflow-y-auto p-3", children: [
97984
- bindableSelection && /* @__PURE__ */ jsx240(
98032
+ /* @__PURE__ */ jsxs191("div", { className: "flex-1 space-y-3 overflow-y-auto p-3", children: [
98033
+ bindableSelection && /* @__PURE__ */ jsx241(
97985
98034
  VariablesBindElement,
97986
98035
  {
97987
98036
  selection: bindableSelection,
@@ -97990,9 +98039,9 @@ var VariablesPanel = memo59(function VariablesPanel2({
97990
98039
  },
97991
98040
  bindableSelection.hfId
97992
98041
  ),
97993
- /* @__PURE__ */ jsx240(ValidationStrip, { issues }),
98042
+ /* @__PURE__ */ jsx241(ValidationStrip, { issues }),
97994
98043
  declarations.length === 0 && !addOpen && EMPTY_STATE,
97995
- declarations.map((decl) => /* @__PURE__ */ jsx240(
98044
+ declarations.map((decl) => /* @__PURE__ */ jsx241(
97996
98045
  VariableRow,
97997
98046
  {
97998
98047
  decl,
@@ -98008,15 +98057,15 @@ var VariablesPanel = memo59(function VariablesPanel2({
98008
98057
  },
98009
98058
  decl.id
98010
98059
  )),
98011
- /* @__PURE__ */ jsx240(
98060
+ /* @__PURE__ */ jsx241(
98012
98061
  UndeclaredReads,
98013
98062
  {
98014
98063
  usage,
98015
98064
  onDeclare: (id) => handleAdd({ id, type: "string", label: id, default: "" })
98016
98065
  }
98017
98066
  ),
98018
- usage?.scanIncomplete && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-600", children: "Scripts access variables dynamically \u2014 usage info may be incomplete." }),
98019
- declarations.length > 0 && /* @__PURE__ */ jsx240(
98067
+ usage?.scanIncomplete && /* @__PURE__ */ jsx241("p", { className: "text-[9px] text-neutral-600", children: "Scripts access variables dynamically \u2014 usage info may be incomplete." }),
98068
+ declarations.length > 0 && /* @__PURE__ */ jsx241(
98020
98069
  HandoffFooter,
98021
98070
  {
98022
98071
  effectiveValues,
@@ -98024,7 +98073,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98024
98073
  onCopy: copyToClipboard
98025
98074
  }
98026
98075
  ),
98027
- addOpen ? /* @__PURE__ */ jsx240(
98076
+ addOpen ? /* @__PURE__ */ jsx241(
98028
98077
  DeclarationForm,
98029
98078
  {
98030
98079
  initial: EMPTY_DRAFT,
@@ -98032,7 +98081,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98032
98081
  onSubmit: handleAdd,
98033
98082
  onCancel: () => setAddOpen(false)
98034
98083
  }
98035
- ) : /* @__PURE__ */ jsx240(
98084
+ ) : /* @__PURE__ */ jsx241(
98036
98085
  "button",
98037
98086
  {
98038
98087
  type: "button",
@@ -98041,7 +98090,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98041
98090
  children: "+ Add variable"
98042
98091
  }
98043
98092
  ),
98044
- /* @__PURE__ */ jsx240(
98093
+ /* @__PURE__ */ jsx241(
98045
98094
  VariablesOtherCompositions,
98046
98095
  {
98047
98096
  compositionPaths: compositions,
@@ -98174,7 +98223,7 @@ function useSlideshowTabState(params) {
98174
98223
 
98175
98224
  // src/components/DesignPanelPromoteProvider.tsx
98176
98225
  import { useCallback as useCallback185 } from "react";
98177
- import { jsx as jsx241 } from "react/jsx-runtime";
98226
+ import { jsx as jsx242 } from "react/jsx-runtime";
98178
98227
  function DesignPanelPromoteProvider({
98179
98228
  selection,
98180
98229
  projectId,
@@ -98204,7 +98253,7 @@ function DesignPanelPromoteProvider({
98204
98253
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
98205
98254
  [showToast]
98206
98255
  );
98207
- return /* @__PURE__ */ jsx241(
98256
+ return /* @__PURE__ */ jsx242(
98208
98257
  VariablePromoteProvider,
98209
98258
  {
98210
98259
  session: handle.session,
@@ -98509,7 +98558,7 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
98509
98558
  }
98510
98559
 
98511
98560
  // src/components/StudioRightPanels.tsx
98512
- import { Fragment as Fragment66, jsx as jsx242, jsxs as jsxs191 } from "react/jsx-runtime";
98561
+ import { Fragment as Fragment66, jsx as jsx243, jsxs as jsxs192 } from "react/jsx-runtime";
98513
98562
  var seekToTime = (t2) => usePlayerStore.getState().requestSeek(t2);
98514
98563
  var StudioRightPanels = memo60(function StudioRightPanels2({
98515
98564
  activeBlockParams,
@@ -98638,7 +98687,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98638
98687
  (animId, duration) => handleGsapConvertToKeyframes(animId, void 0, duration),
98639
98688
  [handleGsapConvertToKeyframes]
98640
98689
  );
98641
- const propertyPanel = /* @__PURE__ */ jsx242(
98690
+ const propertyPanel = /* @__PURE__ */ jsx243(
98642
98691
  DesignPanelPromoteProvider,
98643
98692
  {
98644
98693
  selection: domEditGroupSelections.length > 1 ? null : domEditSelection,
@@ -98650,7 +98699,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98650
98699
  recordEdit,
98651
98700
  reloadPreview,
98652
98701
  forceReloadSharedSdkSession: forceReloadSdkSession,
98653
- children: /* @__PURE__ */ jsx242(
98702
+ children: /* @__PURE__ */ jsx243(
98654
98703
  PropertyPanel,
98655
98704
  {
98656
98705
  projectId,
@@ -98721,9 +98770,9 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98721
98770
  );
98722
98771
  let designBody = propertyPanel;
98723
98772
  if (captionEditMode) {
98724
- designBody = /* @__PURE__ */ jsx242(CaptionPropertyPanel, { iframeRef: previewIframeRef });
98773
+ designBody = /* @__PURE__ */ jsx243(CaptionPropertyPanel, { iframeRef: previewIframeRef });
98725
98774
  } else if (activeBlockParams) {
98726
- designBody = /* @__PURE__ */ jsx242(
98775
+ designBody = /* @__PURE__ */ jsx243(
98727
98776
  BlockParamsPanel,
98728
98777
  {
98729
98778
  blockName: activeBlockParams.blockName,
@@ -98735,18 +98784,18 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98735
98784
  }
98736
98785
  );
98737
98786
  }
98738
- return /* @__PURE__ */ jsxs191(Fragment66, { children: [
98739
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "design", children: designBody }),
98740
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "layers", children: /* @__PURE__ */ jsx242(LayersPanel, {}) }),
98741
- /* @__PURE__ */ jsx242(
98787
+ return /* @__PURE__ */ jsxs192(Fragment66, { children: [
98788
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "design", children: designBody }),
98789
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "layers", children: /* @__PURE__ */ jsx243(LayersPanel, {}) }),
98790
+ /* @__PURE__ */ jsx243(
98742
98791
  Dock.Panel,
98743
98792
  {
98744
98793
  id: "renders",
98745
98794
  title: renderJobs.length > 0 ? `Renders (${renderJobs.length})` : void 0,
98746
- children: /* @__PURE__ */ jsx242(RenderQueuePanel, {})
98795
+ children: /* @__PURE__ */ jsx243(RenderQueuePanel, {})
98747
98796
  }
98748
98797
  ),
98749
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx242(
98798
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx243(
98750
98799
  VariablesPanel,
98751
98800
  {
98752
98801
  sdkSession,
@@ -98755,7 +98804,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98755
98804
  recordEdit
98756
98805
  }
98757
98806
  ) }),
98758
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx242(
98807
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx243(
98759
98808
  SlideshowPanel,
98760
98809
  {
98761
98810
  scenes: slideshowScenes,
@@ -98767,14 +98816,14 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98767
98816
  });
98768
98817
 
98769
98818
  // src/hooks/useStudioSidePanels.tsx
98770
- import { Fragment as Fragment67, jsx as jsx243, jsxs as jsxs192 } from "react/jsx-runtime";
98819
+ import { Fragment as Fragment67, jsx as jsx244, jsxs as jsxs193 } from "react/jsx-runtime";
98771
98820
  function useStudioSidePanels(left, right, projectId) {
98772
98821
  const stableLeft = useStableHandlers(left, projectId);
98773
98822
  const stableRight = useStableHandlers(right, projectId);
98774
98823
  return useMemo88(
98775
- () => /* @__PURE__ */ jsxs192(Fragment67, { children: [
98776
- /* @__PURE__ */ jsx243(StudioLeftPanels, { ...stableLeft }),
98777
- /* @__PURE__ */ jsx243(StudioRightPanels, { ...stableRight })
98824
+ () => /* @__PURE__ */ jsxs193(Fragment67, { children: [
98825
+ /* @__PURE__ */ jsx244(StudioLeftPanels, { ...stableLeft }),
98826
+ /* @__PURE__ */ jsx244(StudioRightPanels, { ...stableRight })
98778
98827
  ] }),
98779
98828
  [stableLeft, stableRight]
98780
98829
  );
@@ -98813,9 +98862,9 @@ function useKeyframeKeyboard({ enabled, onAddKeyframe }) {
98813
98862
  }
98814
98863
 
98815
98864
  // src/components/icons/AudioMetersIcon.tsx
98816
- import { jsx as jsx244 } from "react/jsx-runtime";
98865
+ import { jsx as jsx245 } from "react/jsx-runtime";
98817
98866
  function AudioMetersIcon({ size = 16 }) {
98818
- return /* @__PURE__ */ jsx244(
98867
+ return /* @__PURE__ */ jsx245(
98819
98868
  "svg",
98820
98869
  {
98821
98870
  width: size,
@@ -98826,15 +98875,15 @@ function AudioMetersIcon({ size = 16 }) {
98826
98875
  strokeWidth: 1.5,
98827
98876
  strokeLinecap: "round",
98828
98877
  "aria-hidden": "true",
98829
- children: /* @__PURE__ */ jsx244("path", { d: "M2.5 13h2m-2-3h2M7 13h2m-2-3h2m-2-3h2m-2-3h2M11.5 13h2m-2-3h2m-2-3h2" })
98878
+ children: /* @__PURE__ */ jsx245("path", { d: "M2.5 13h2m-2-3h2M7 13h2m-2-3h2m-2-3h2m-2-3h2M11.5 13h2m-2-3h2m-2-3h2" })
98830
98879
  }
98831
98880
  );
98832
98881
  }
98833
98882
 
98834
98883
  // src/components/icons/RippleEditIcon.tsx
98835
- import { jsx as jsx245, jsxs as jsxs193 } from "react/jsx-runtime";
98884
+ import { jsx as jsx246, jsxs as jsxs194 } from "react/jsx-runtime";
98836
98885
  function RippleEditIcon({ size = 16 }) {
98837
- return /* @__PURE__ */ jsxs193(
98886
+ return /* @__PURE__ */ jsxs194(
98838
98887
  "svg",
98839
98888
  {
98840
98889
  width: size,
@@ -98847,20 +98896,20 @@ function RippleEditIcon({ size = 16 }) {
98847
98896
  strokeLinejoin: "round",
98848
98897
  "aria-hidden": "true",
98849
98898
  children: [
98850
- /* @__PURE__ */ jsx245("rect", { x: "1.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98851
- /* @__PURE__ */ jsx245("rect", { x: "10.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98852
- /* @__PURE__ */ jsx245("path", { d: "M13 4H6m2-2L6 4l2 2" })
98899
+ /* @__PURE__ */ jsx246("rect", { x: "1.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98900
+ /* @__PURE__ */ jsx246("rect", { x: "10.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98901
+ /* @__PURE__ */ jsx246("path", { d: "M13 4H6m2-2L6 4l2 2" })
98853
98902
  ]
98854
98903
  }
98855
98904
  );
98856
98905
  }
98857
98906
 
98858
98907
  // src/components/TimelineHistoryButtons.tsx
98859
- import { Fragment as Fragment68, jsx as jsx246, jsxs as jsxs194 } from "react/jsx-runtime";
98908
+ import { Fragment as Fragment68, jsx as jsx247, jsxs as jsxs195 } from "react/jsx-runtime";
98860
98909
  function HistoryButton({ action, can, label, onClick }) {
98861
98910
  const Icon = action === "undo" ? RotateCcw : RotateCw;
98862
98911
  const enabled = Boolean(onClick) && can;
98863
- return /* @__PURE__ */ jsx246(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx246(
98912
+ return /* @__PURE__ */ jsx247(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx247(
98864
98913
  "button",
98865
98914
  {
98866
98915
  type: "button",
@@ -98871,7 +98920,7 @@ function HistoryButton({ action, can, label, onClick }) {
98871
98920
  trackStudioEvent("toolbar_action", { action });
98872
98921
  void onClick?.();
98873
98922
  },
98874
- children: /* @__PURE__ */ jsx246(Icon, { size: 16 })
98923
+ children: /* @__PURE__ */ jsx247(Icon, { size: 16 })
98875
98924
  }
98876
98925
  ) });
98877
98926
  }
@@ -98879,8 +98928,8 @@ function TimelineHistoryButtons(props) {
98879
98928
  const shell = useStudioShellContextOptional();
98880
98929
  const onUndo = props.onUndo ?? shell?.handleUndo;
98881
98930
  const onRedo = props.onRedo ?? shell?.handleRedo;
98882
- return /* @__PURE__ */ jsxs194(Fragment68, { children: [
98883
- /* @__PURE__ */ jsx246(
98931
+ return /* @__PURE__ */ jsxs195(Fragment68, { children: [
98932
+ /* @__PURE__ */ jsx247(
98884
98933
  HistoryButton,
98885
98934
  {
98886
98935
  action: "undo",
@@ -98889,7 +98938,7 @@ function TimelineHistoryButtons(props) {
98889
98938
  onClick: onUndo
98890
98939
  }
98891
98940
  ),
98892
- /* @__PURE__ */ jsx246(
98941
+ /* @__PURE__ */ jsx247(
98893
98942
  HistoryButton,
98894
98943
  {
98895
98944
  action: "redo",
@@ -98903,9 +98952,9 @@ function TimelineHistoryButtons(props) {
98903
98952
 
98904
98953
  // src/components/TimelineToolPicker.tsx
98905
98954
  import { ArrowLineLeft, ArrowLineRight, CaretDown } from "@phosphor-icons/react";
98906
- import { jsx as jsx247, jsxs as jsxs195 } from "react/jsx-runtime";
98955
+ import { jsx as jsx248, jsxs as jsxs196 } from "react/jsx-runtime";
98907
98956
  function SelectIcon() {
98908
- return /* @__PURE__ */ jsx247("svg", { width: "16", height: "16", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx247("path", { d: "M2 0.5L10 6L6.5 6.5L8.5 11L6.5 11.5L4.5 7L2 9Z" }) });
98957
+ return /* @__PURE__ */ jsx248("svg", { width: "16", height: "16", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx248("path", { d: "M2 0.5L10 6L6.5 6.5L8.5 11L6.5 11.5L4.5 7L2 9Z" }) });
98909
98958
  }
98910
98959
  function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98911
98960
  const activeTool = usePlayerStore((s) => s.activeTool);
@@ -98914,14 +98963,14 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98914
98963
  {
98915
98964
  label: "Select",
98916
98965
  shortcut: "V",
98917
- icon: /* @__PURE__ */ jsx247(SelectIcon, {}),
98966
+ icon: /* @__PURE__ */ jsx248(SelectIcon, {}),
98918
98967
  active: activeTool === "select",
98919
98968
  run: () => store().setActiveTool("select")
98920
98969
  },
98921
98970
  {
98922
98971
  label: "Split",
98923
98972
  shortcut: "B",
98924
- icon: /* @__PURE__ */ jsx247(Scissors, { size: 16 }),
98973
+ icon: /* @__PURE__ */ jsx248(Scissors, { size: 16 }),
98925
98974
  active: activeTool === "razor",
98926
98975
  run: () => store().setActiveTool("razor")
98927
98976
  }
@@ -98931,25 +98980,25 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98931
98980
  {
98932
98981
  label: "Select leftward",
98933
98982
  shortcut: "[",
98934
- icon: /* @__PURE__ */ jsx247(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
98983
+ icon: /* @__PURE__ */ jsx248(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
98935
98984
  active: false,
98936
98985
  run: () => store().selectLeftward()
98937
98986
  },
98938
98987
  {
98939
98988
  label: "Select rightward",
98940
98989
  shortcut: "]",
98941
- icon: /* @__PURE__ */ jsx247(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
98990
+ icon: /* @__PURE__ */ jsx248(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
98942
98991
  active: false,
98943
98992
  run: () => store().selectRightward()
98944
98993
  }
98945
98994
  );
98946
98995
  }
98947
98996
  const current = rows.find((row) => row.active) ?? rows[0];
98948
- return /* @__PURE__ */ jsx247(
98997
+ return /* @__PURE__ */ jsx248(
98949
98998
  Menu,
98950
98999
  {
98951
99000
  "aria-label": "Timeline tools",
98952
- trigger: /* @__PURE__ */ jsxs195(
99001
+ trigger: /* @__PURE__ */ jsxs196(
98953
99002
  "button",
98954
99003
  {
98955
99004
  type: "button",
@@ -98957,18 +99006,18 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98957
99006
  className: cn(flatIdle, "gap-0.5"),
98958
99007
  children: [
98959
99008
  current.icon,
98960
- /* @__PURE__ */ jsx247(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
99009
+ /* @__PURE__ */ jsx248(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
98961
99010
  ]
98962
99011
  }
98963
99012
  ),
98964
- children: rows.map((row) => /* @__PURE__ */ jsx247(
99013
+ children: rows.map((row) => /* @__PURE__ */ jsx248(
98965
99014
  MenuItem,
98966
99015
  {
98967
99016
  shortcut: row.shortcut,
98968
99017
  onClick: row.run,
98969
99018
  "data-active": row.active || void 0,
98970
99019
  className: row.active ? "bg-press text-text-0" : void 0,
98971
- children: /* @__PURE__ */ jsxs195("span", { className: "flex items-center gap-2", children: [
99020
+ children: /* @__PURE__ */ jsxs196("span", { className: "flex items-center gap-2", children: [
98972
99021
  row.icon,
98973
99022
  row.label
98974
99023
  ] })
@@ -98983,14 +99032,14 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98983
99032
  import { useCallback as useCallback190, useState as useState160 } from "react";
98984
99033
  import { createPortal as createPortal15 } from "react-dom";
98985
99034
  import { LinkSimple } from "@phosphor-icons/react";
98986
- import { Fragment as Fragment69, jsx as jsx248, jsxs as jsxs196 } from "react/jsx-runtime";
99035
+ import { Fragment as Fragment69, jsx as jsx249, jsxs as jsxs197 } from "react/jsx-runtime";
98987
99036
  function SyncIndicatorMenu({ x, y, onClose }) {
98988
99037
  const menuRef = useContextMenuDismiss(onClose);
98989
99038
  useMenuKeyboardNav(menuRef);
98990
99039
  const visible = useLinkedClipPreferences((s) => s.syncIndicatorsVisible);
98991
99040
  const setVisible = useLinkedClipPreferences((s) => s.setSyncIndicatorsVisible);
98992
99041
  return createPortal15(
98993
- /* @__PURE__ */ jsx248(
99042
+ /* @__PURE__ */ jsx249(
98994
99043
  "div",
98995
99044
  {
98996
99045
  ref: menuRef,
@@ -98998,7 +99047,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
98998
99047
  "aria-label": "Linked clips",
98999
99048
  className: `${menuClasses.panel} fixed z-200 min-w-[200px]`,
99000
99049
  style: { left: x, top: y },
99001
- children: /* @__PURE__ */ jsxs196(
99050
+ children: /* @__PURE__ */ jsxs197(
99002
99051
  "button",
99003
99052
  {
99004
99053
  type: "button",
@@ -99010,7 +99059,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99010
99059
  onClose();
99011
99060
  },
99012
99061
  children: [
99013
- /* @__PURE__ */ jsx248("span", { className: "w-3 text-studio-accent", children: visible ? "\u2713" : "" }),
99062
+ /* @__PURE__ */ jsx249("span", { className: "w-3 text-studio-accent", children: visible ? "\u2713" : "" }),
99014
99063
  "Show out-of-sync indicators"
99015
99064
  ]
99016
99065
  }
@@ -99025,12 +99074,12 @@ function LinkedSelectionToggle() {
99025
99074
  const setLinkedSelection = useLinkedClipPreferences((s) => s.setLinkedSelection);
99026
99075
  const [menuAt, setMenuAt] = useState160(null);
99027
99076
  const closeMenu = useCallback190(() => setMenuAt(null), []);
99028
- return /* @__PURE__ */ jsxs196(Fragment69, { children: [
99029
- /* @__PURE__ */ jsx248(
99077
+ return /* @__PURE__ */ jsxs197(Fragment69, { children: [
99078
+ /* @__PURE__ */ jsx249(
99030
99079
  Tooltip,
99031
99080
  {
99032
99081
  label: linkedSelection ? "Linked Selection on \u2014 a click selects both halves (\u2325-click for one)" : "Linked Selection off \u2014 a click selects one clip",
99033
- children: /* @__PURE__ */ jsx248(
99082
+ children: /* @__PURE__ */ jsx249(
99034
99083
  "button",
99035
99084
  {
99036
99085
  type: "button",
@@ -99042,17 +99091,17 @@ function LinkedSelectionToggle() {
99042
99091
  "aria-label": "Linked Selection",
99043
99092
  "aria-pressed": linkedSelection,
99044
99093
  className: linkedSelection ? flatActive : flatIdle,
99045
- children: /* @__PURE__ */ jsx248(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99094
+ children: /* @__PURE__ */ jsx249(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99046
99095
  }
99047
99096
  )
99048
99097
  }
99049
99098
  ),
99050
- menuAt && /* @__PURE__ */ jsx248(SyncIndicatorMenu, { x: menuAt.x, y: menuAt.y, onClose: closeMenu })
99099
+ menuAt && /* @__PURE__ */ jsx249(SyncIndicatorMenu, { x: menuAt.x, y: menuAt.y, onClose: closeMenu })
99051
99100
  ] });
99052
99101
  }
99053
99102
 
99054
99103
  // src/components/TimelineToolbar.tsx
99055
- import { Fragment as Fragment70, jsx as jsx249, jsxs as jsxs197 } from "react/jsx-runtime";
99104
+ import { Fragment as Fragment70, jsx as jsx250, jsxs as jsxs198 } from "react/jsx-runtime";
99056
99105
  var NO_KEYFRAME_TOGGLE = {
99057
99106
  state: "none",
99058
99107
  isMotionPath: false,
@@ -99165,13 +99214,13 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99165
99214
  window.addEventListener("keydown", onKeyDown);
99166
99215
  return () => window.removeEventListener("keydown", onKeyDown);
99167
99216
  }, []);
99168
- return /* @__PURE__ */ jsx249("div", { className: "border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs197("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
99169
- /* @__PURE__ */ jsxs197("div", { className: "flex items-center gap-0.5", children: [
99170
- showHistory && /* @__PURE__ */ jsx249(TimelineHistoryButtons, { ...history2 }),
99171
- /* @__PURE__ */ jsx249(TimelineToolPicker, { showSelectAroundPlayhead }),
99172
- /* @__PURE__ */ jsx249("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
99173
- /* @__PURE__ */ jsx249(LinkedSelectionToggle, {}),
99174
- /* @__PURE__ */ jsx249(Tooltip, { label: timelineSnapEnabled ? "Snapping on (N)" : "Snapping off (N)", children: /* @__PURE__ */ jsx249(
99217
+ return /* @__PURE__ */ jsx250("div", { className: "border-b border-neutral-800/60", children: /* @__PURE__ */ jsxs198("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
99218
+ /* @__PURE__ */ jsxs198("div", { className: "flex items-center gap-0.5", children: [
99219
+ showHistory && /* @__PURE__ */ jsx250(TimelineHistoryButtons, { ...history2 }),
99220
+ /* @__PURE__ */ jsx250(TimelineToolPicker, { showSelectAroundPlayhead }),
99221
+ /* @__PURE__ */ jsx250("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
99222
+ /* @__PURE__ */ jsx250(LinkedSelectionToggle, {}),
99223
+ /* @__PURE__ */ jsx250(Tooltip, { label: timelineSnapEnabled ? "Snapping on (N)" : "Snapping off (N)", children: /* @__PURE__ */ jsx250(
99175
99224
  "button",
99176
99225
  {
99177
99226
  type: "button",
@@ -99179,14 +99228,14 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99179
99228
  "aria-label": "Toggle timeline snapping",
99180
99229
  "aria-pressed": timelineSnapEnabled,
99181
99230
  className: timelineSnapEnabled ? flatActive : flatIdle,
99182
- children: /* @__PURE__ */ jsx249(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99231
+ children: /* @__PURE__ */ jsx250(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99183
99232
  }
99184
99233
  ) }),
99185
- /* @__PURE__ */ jsx249(
99234
+ /* @__PURE__ */ jsx250(
99186
99235
  Tooltip,
99187
99236
  {
99188
99237
  label: rippleEditEnabled ? "Ripple on \u2014 keeps the main track gapless" : "Ripple off \u2014 deleting a main-track clip leaves a gap",
99189
- children: /* @__PURE__ */ jsx249(
99238
+ children: /* @__PURE__ */ jsx250(
99190
99239
  "button",
99191
99240
  {
99192
99241
  type: "button",
@@ -99194,12 +99243,12 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99194
99243
  "aria-label": "Toggle ripple edit",
99195
99244
  "aria-pressed": rippleEditEnabled,
99196
99245
  className: rippleEditEnabled ? flatActive : flatIdle,
99197
- children: /* @__PURE__ */ jsx249(RippleEditIcon, { size: 16 })
99246
+ children: /* @__PURE__ */ jsx250(RippleEditIcon, { size: 16 })
99198
99247
  }
99199
99248
  )
99200
99249
  }
99201
99250
  ),
99202
- projectHasAudio && /* @__PURE__ */ jsx249(Tooltip, { label: audioMetersVisible ? "Hide audio meters" : "Show audio meters", children: /* @__PURE__ */ jsx249(
99251
+ projectHasAudio && /* @__PURE__ */ jsx250(Tooltip, { label: audioMetersVisible ? "Hide audio meters" : "Show audio meters", children: /* @__PURE__ */ jsx250(
99203
99252
  "button",
99204
99253
  {
99205
99254
  type: "button",
@@ -99207,15 +99256,15 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99207
99256
  "aria-label": "Toggle audio meters",
99208
99257
  "aria-pressed": audioMetersVisible,
99209
99258
  className: audioMetersVisible ? flatActive : flatIdle,
99210
- children: /* @__PURE__ */ jsx249(AudioMetersIcon, { size: 16 })
99259
+ children: /* @__PURE__ */ jsx250(AudioMetersIcon, { size: 16 })
99211
99260
  }
99212
99261
  ) }),
99213
- showKeyframes && /* @__PURE__ */ jsxs197(Fragment70, { children: [
99214
- /* @__PURE__ */ jsx249(
99262
+ showKeyframes && /* @__PURE__ */ jsxs198(Fragment70, { children: [
99263
+ /* @__PURE__ */ jsx250(
99215
99264
  Tooltip,
99216
99265
  {
99217
99266
  label: keyframePathEndpoint ? "Motion path endpoints cannot be removed" : !onToggleKeyframe ? "Select an animated element to add keyframes" : keyframeIsMotionPath ? keyframeWillExtend ? "Extend motion path to playhead (K)" : keyframeState === "active" ? "Remove waypoint from motion path (K)" : "Add waypoint to motion path (K)" : keyframeState === "active" ? "Remove keyframe at playhead (K)" : keyframeState === "inactive" ? keyframeWillExtend ? "Add keyframe at playhead, extends animation (K)" : "Add keyframe at playhead (K)" : "Add keyframe (K)",
99218
- children: /* @__PURE__ */ jsx249(
99267
+ children: /* @__PURE__ */ jsx250(
99219
99268
  "button",
99220
99269
  {
99221
99270
  type: "button",
@@ -99223,7 +99272,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99223
99272
  onClick: onToggleKeyframe,
99224
99273
  "aria-label": keyframePathEndpoint ? "Motion path endpoint" : keyframeIsMotionPath ? keyframeState === "active" ? "Remove motion path waypoint" : keyframeWillExtend ? "Extend motion path to playhead" : "Add motion path waypoint" : keyframeState === "active" ? "Remove keyframe at playhead" : "Add keyframe at playhead",
99225
99274
  className: !onToggleKeyframe ? flatDisabled : `${flatBtn} active:scale-[0.98] hover:bg-hover ${keyframeState === "active" ? "text-accent-ink" : keyframeState === "inactive" ? "text-neutral-400 hover:text-accent-ink" : "text-neutral-600 hover:text-neutral-400"}`,
99226
- children: /* @__PURE__ */ jsx249("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx249("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx249(
99275
+ children: /* @__PURE__ */ jsx250("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "currentColor", children: keyframeState === "active" ? /* @__PURE__ */ jsx250("path", { d: "M5 0.5L9.5 5L5 9.5L0.5 5Z" }) : /* @__PURE__ */ jsx250(
99227
99276
  "path",
99228
99277
  {
99229
99278
  d: "M5 1.2L8.8 5L5 8.8L1.2 5Z",
@@ -99236,11 +99285,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99236
99285
  )
99237
99286
  }
99238
99287
  ),
99239
- /* @__PURE__ */ jsx249(
99288
+ /* @__PURE__ */ jsx250(
99240
99289
  Tooltip,
99241
99290
  {
99242
99291
  label: autoKeyframeEnabled ? "Auto-record manual edits as keyframes (click to turn off)" : "Manual edits will not be recorded as keyframes (click to turn on)",
99243
- children: /* @__PURE__ */ jsx249(
99292
+ children: /* @__PURE__ */ jsx250(
99244
99293
  "button",
99245
99294
  {
99246
99295
  type: "button",
@@ -99248,9 +99297,9 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99248
99297
  "aria-label": "Auto-record manual edits as keyframes",
99249
99298
  "aria-pressed": autoKeyframeEnabled,
99250
99299
  className: `${flatBtn} active:scale-[0.98] hover:bg-hover ${autoKeyframeEnabled ? "text-danger-ink" : "text-neutral-600 hover:text-neutral-400"}`,
99251
- children: /* @__PURE__ */ jsxs197("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
99252
- /* @__PURE__ */ jsx249("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
99253
- /* @__PURE__ */ jsx249(
99300
+ children: /* @__PURE__ */ jsxs198("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
99301
+ /* @__PURE__ */ jsx250("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
99302
+ /* @__PURE__ */ jsx250(
99254
99303
  "circle",
99255
99304
  {
99256
99305
  cx: "5",
@@ -99271,11 +99320,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99271
99320
  const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null;
99272
99321
  const splittable = el != null && canSplitElement(el);
99273
99322
  const canSplit = splittable && currentTime > el.start && currentTime < el.start + el.duration;
99274
- return /* @__PURE__ */ jsx249(
99323
+ return /* @__PURE__ */ jsx250(
99275
99324
  Tooltip,
99276
99325
  {
99277
99326
  label: canSplit ? `Split at playhead (${splitShortcut})` : splittable ? "Move the playhead inside the clip to split" : "Select a clip to split",
99278
- children: /* @__PURE__ */ jsx249(
99327
+ children: /* @__PURE__ */ jsx250(
99279
99328
  "button",
99280
99329
  {
99281
99330
  type: "button",
@@ -99285,7 +99334,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99285
99334
  if (canSplit && el) onSplitElement(el, currentTime);
99286
99335
  },
99287
99336
  className: canSplit ? flatIdle : flatDisabled,
99288
- children: /* @__PURE__ */ jsxs197(
99337
+ children: /* @__PURE__ */ jsxs198(
99289
99338
  "svg",
99290
99339
  {
99291
99340
  width: "16",
@@ -99298,8 +99347,8 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99298
99347
  strokeLinejoin: "round",
99299
99348
  "aria-hidden": "true",
99300
99349
  children: [
99301
- /* @__PURE__ */ jsx249("path", { d: "M5 3 L7 3 L7 13 L5 13" }),
99302
- /* @__PURE__ */ jsx249("path", { d: "M11 3 L9 3 L9 13 L11 13" })
99350
+ /* @__PURE__ */ jsx250("path", { d: "M5 3 L7 3 L7 13 L5 13" }),
99351
+ /* @__PURE__ */ jsx250("path", { d: "M11 3 L9 3 L9 13 L11 13" })
99303
99352
  ]
99304
99353
  }
99305
99354
  )
@@ -99311,11 +99360,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99311
99360
  (() => {
99312
99361
  if (!showAddBeat) return null;
99313
99362
  const canAdd = beatAnalysisReady && canAddBeatAt(currentTime);
99314
- return /* @__PURE__ */ jsx249(
99363
+ return /* @__PURE__ */ jsx250(
99315
99364
  Tooltip,
99316
99365
  {
99317
99366
  label: !beatAnalysisReady ? "Add a music track with beat analysis to place beats" : canAdd ? "Add beat at playhead" : "A beat already exists at the playhead",
99318
- children: /* @__PURE__ */ jsx249(
99367
+ children: /* @__PURE__ */ jsx250(
99319
99368
  "button",
99320
99369
  {
99321
99370
  type: "button",
@@ -99325,7 +99374,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99325
99374
  if (canAdd) addBeatAtCompositionTime(currentTime);
99326
99375
  },
99327
99376
  className: canAdd ? `${flatBtn} text-text-2 hover:bg-hover hover:text-accent-ink active:scale-[0.98]` : flatDisabled,
99328
- children: /* @__PURE__ */ jsx249("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx249(
99377
+ children: /* @__PURE__ */ jsx250("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx250(
99329
99378
  "path",
99330
99379
  {
99331
99380
  d: "M21 10C21 12.2091 16.9706 14 12 14M21 10C21 7.79086 16.9706 6 12 6C7.02944 6 3 7.79086 3 10M21 10V16C21 18.2091 16.9706 20 12 20M12 14C7.02944 14 3 12.2091 3 10M12 14V20M3 10V16C3 18.2091 7.02944 20 12 20M7 19.3264V13.3264M17 19.3264V13.3264M12 10L20 4",
@@ -99341,13 +99390,13 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99341
99390
  );
99342
99391
  })()
99343
99392
  ] }),
99344
- /* @__PURE__ */ jsxs197("div", { className: "flex items-center gap-0.5", children: [
99393
+ /* @__PURE__ */ jsxs198("div", { className: "flex items-center gap-0.5", children: [
99345
99394
  rightActions,
99346
- /* @__PURE__ */ jsx249(
99395
+ /* @__PURE__ */ jsx250(
99347
99396
  Tooltip,
99348
99397
  {
99349
99398
  label: thumbnailsVisible ? "Hide thumbnails \u2014 labels only" : "Show thumbnails \u2014 posters stay visible; richer previews appear on interaction",
99350
- children: /* @__PURE__ */ jsx249(
99399
+ children: /* @__PURE__ */ jsx250(
99351
99400
  "button",
99352
99401
  {
99353
99402
  type: "button",
@@ -99355,12 +99404,12 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99355
99404
  "aria-pressed": thumbnailsVisible,
99356
99405
  onClick: () => setThumbnailMode(thumbnailsVisible ? "hidden" : "adaptive"),
99357
99406
  className: `h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${thumbnailsVisible ? "bg-studio-accent/10 text-accent-ink" : "text-neutral-400 hover:bg-hover hover:text-neutral-200"}`,
99358
- children: /* @__PURE__ */ jsx249(Image3, { size: 16, "aria-hidden": "true" })
99407
+ children: /* @__PURE__ */ jsx250(Image3, { size: 16, "aria-hidden": "true" })
99359
99408
  }
99360
99409
  )
99361
99410
  }
99362
99411
  ),
99363
- /* @__PURE__ */ jsx249(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx249(
99412
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx250(
99364
99413
  "button",
99365
99414
  {
99366
99415
  type: "button",
@@ -99368,20 +99417,20 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99368
99417
  "aria-label": "Fit timeline to width",
99369
99418
  "aria-pressed": zoomMode === "fit",
99370
99419
  className: `h-7 px-2 rounded-md text-[11px] font-medium transition-colors ${zoomMode === "fit" ? "bg-studio-accent/10 text-accent-ink" : "text-neutral-400 hover:bg-hover hover:text-neutral-200"}`,
99371
- children: /* @__PURE__ */ jsx249(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
99420
+ children: /* @__PURE__ */ jsx250(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
99372
99421
  }
99373
99422
  ) }),
99374
- /* @__PURE__ */ jsx249(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx249(
99423
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx250(
99375
99424
  "button",
99376
99425
  {
99377
99426
  type: "button",
99378
99427
  "aria-label": "Zoom out",
99379
99428
  onClick: () => zoomTimelineStep("out"),
99380
99429
  className: flatIdle,
99381
- children: /* @__PURE__ */ jsx249(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
99430
+ children: /* @__PURE__ */ jsx250(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
99382
99431
  }
99383
99432
  ) }),
99384
- /* @__PURE__ */ jsx249(
99433
+ /* @__PURE__ */ jsx250(
99385
99434
  "input",
99386
99435
  {
99387
99436
  type: "range",
@@ -99396,17 +99445,17 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99396
99445
  className: "mx-1 h-6 w-[96px] cursor-pointer appearance-none bg-transparent [&::-webkit-slider-runnable-track]:h-[2px] [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-neutral-700 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-[10px] [&::-webkit-slider-thumb]:h-[10px] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-text-0 [&::-webkit-slider-thumb]:-mt-1 [&::-webkit-slider-thumb]:shadow-[0_0_0_2px_#0a0a0a,0_1px_3px_rgba(0,0,0,0.5)] [&::-webkit-slider-thumb]:cursor-grab [&::-webkit-slider-thumb:active]:cursor-grabbing"
99397
99446
  }
99398
99447
  ),
99399
- /* @__PURE__ */ jsx249(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx249(
99448
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx250(
99400
99449
  "button",
99401
99450
  {
99402
99451
  type: "button",
99403
99452
  "aria-label": "Zoom in",
99404
99453
  onClick: () => zoomTimelineStep("in"),
99405
99454
  className: flatIdle,
99406
- children: /* @__PURE__ */ jsx249(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
99455
+ children: /* @__PURE__ */ jsx250(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
99407
99456
  }
99408
99457
  ) }),
99409
- /* @__PURE__ */ jsx249(
99458
+ /* @__PURE__ */ jsx250(
99410
99459
  "span",
99411
99460
  {
99412
99461
  className: "ml-1 w-[38px] text-right font-mono text-[11px] tabular-nums text-neutral-500 select-none",
@@ -99419,18 +99468,18 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99419
99468
  });
99420
99469
 
99421
99470
  // src/components/StudioSplash.tsx
99422
- import { jsx as jsx250, jsxs as jsxs198 } from "react/jsx-runtime";
99471
+ import { jsx as jsx251, jsxs as jsxs199 } from "react/jsx-runtime";
99423
99472
  function StudioSplash({ waiting }) {
99424
- return /* @__PURE__ */ jsx250("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs198("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99425
- /* @__PURE__ */ jsx250("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
99426
- /* @__PURE__ */ jsxs198("p", { className: "text-xs text-neutral-600", children: [
99473
+ return /* @__PURE__ */ jsx251("div", { className: "h-full w-full bg-neutral-950 flex items-center justify-center", children: waiting ? /* @__PURE__ */ jsxs199("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99474
+ /* @__PURE__ */ jsx251("div", { className: "w-4 h-4 rounded-full border-2 border-neutral-700 border-t-neutral-500 animate-spin motion-reduce:animate-none" }),
99475
+ /* @__PURE__ */ jsxs199("p", { className: "text-xs text-neutral-600", children: [
99427
99476
  "Waiting for preview server\u2026 run",
99428
99477
  " ",
99429
- /* @__PURE__ */ jsx250("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
99478
+ /* @__PURE__ */ jsx251("code", { className: "text-neutral-500 font-mono", children: "npm run dev" })
99430
99479
  ] })
99431
- ] }) : /* @__PURE__ */ jsxs198("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99432
- /* @__PURE__ */ jsx250("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
99433
- /* @__PURE__ */ jsx250("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
99480
+ ] }) : /* @__PURE__ */ jsxs199("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99481
+ /* @__PURE__ */ jsx251("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
99482
+ /* @__PURE__ */ jsx251("p", { className: "text-xs text-neutral-600", children: "Connecting to project\u2026" })
99434
99483
  ] }) });
99435
99484
  }
99436
99485
 
@@ -99700,7 +99749,7 @@ function useActiveComposition({
99700
99749
  }
99701
99750
 
99702
99751
  // src/App.tsx
99703
- import { jsx as jsx251, jsxs as jsxs199 } from "react/jsx-runtime";
99752
+ import { jsx as jsx252, jsxs as jsxs200 } from "react/jsx-runtime";
99704
99753
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
99705
99754
  function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
99706
99755
  const { projectId, resolving, waitingForServer } = useServerConnection();
@@ -100071,7 +100120,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100071
100120
  refreshPreviewDocumentVersion
100072
100121
  });
100073
100122
  const timelineToolbar = useMemo89(
100074
- () => /* @__PURE__ */ jsx251(
100123
+ () => /* @__PURE__ */ jsx252(
100075
100124
  TimelineToolbar,
100076
100125
  {
100077
100126
  domEditSession: toolbarSession,
@@ -100119,15 +100168,15 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100119
100168
  projectId
100120
100169
  );
100121
100170
  if (resolving || waitingForServer || !projectId)
100122
- return /* @__PURE__ */ jsx251(StudioSplash, { waiting: waitingForServer });
100123
- return /* @__PURE__ */ jsx251(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx251(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx251(PanelLayoutProvider, { value: layout, children: /* @__PURE__ */ jsx251(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx251(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs199(
100171
+ return /* @__PURE__ */ jsx252(StudioSplash, { waiting: waitingForServer });
100172
+ return /* @__PURE__ */ jsx252(StudioShellProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx252(StudioPlaybackProvider, { value: studioCtxValue, children: /* @__PURE__ */ jsx252(PanelLayoutProvider, { value: layout, children: /* @__PURE__ */ jsx252(FileManagerProvider, { value: fileManager, children: /* @__PURE__ */ jsx252(DomEditProvider, { value: domEditSession, children: /* @__PURE__ */ jsxs200(
100124
100173
  "div",
100125
100174
  {
100126
100175
  className: "flex flex-col h-full w-full bg-neutral-950 relative",
100127
100176
  onDragOver: fileDrop.onDragOver,
100128
100177
  onDrop: fileDrop.onDrop,
100129
100178
  children: [
100130
- /* @__PURE__ */ jsx251(
100179
+ /* @__PURE__ */ jsx252(
100131
100180
  StudioHeader,
100132
100181
  {
100133
100182
  captureFrameHref: frameCapture.captureFrameHref,
@@ -100140,17 +100189,17 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100140
100189
  onExport: handleExport
100141
100190
  }
100142
100191
  ),
100143
- previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx251(
100192
+ previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx252(
100144
100193
  SaveQueuePausedBanner,
100145
100194
  {
100146
100195
  message: previewPersistence.domEditSaveQueuePaused,
100147
100196
  onRetry: previewPersistence.resetDomEditSaveQueueBreaker
100148
100197
  }
100149
100198
  ),
100150
- /* @__PURE__ */ jsx251(ExternalFileConflictBanner, { coordinator: externalFileChanges }),
100151
- sdkHandle.unreachableProject && /* @__PURE__ */ jsx251(ProjectUnreachableBanner, { projectId: sdkHandle.unreachableProject }),
100152
- sdkHandle.compositionMissing && activeCompPath && /* @__PURE__ */ jsx251(CompositionMissingBanner, { path: activeCompPath }),
100153
- /* @__PURE__ */ jsx251(
100199
+ /* @__PURE__ */ jsx252(ExternalFileConflictBanner, { coordinator: externalFileChanges }),
100200
+ sdkHandle.unreachableProject && /* @__PURE__ */ jsx252(ProjectUnreachableBanner, { projectId: sdkHandle.unreachableProject }),
100201
+ sdkHandle.compositionMissing && activeCompPath && /* @__PURE__ */ jsx252(CompositionMissingBanner, { path: activeCompPath }),
100202
+ /* @__PURE__ */ jsx252(
100154
100203
  EditorShell,
100155
100204
  {
100156
100205
  readOnlyPreview,
@@ -100194,7 +100243,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100194
100243
  gestureOverlay
100195
100244
  }
100196
100245
  ),
100197
- /* @__PURE__ */ jsx251(
100246
+ /* @__PURE__ */ jsx252(
100198
100247
  StudioOverlays,
100199
100248
  {
100200
100249
  projectId,
@@ -100829,6 +100878,7 @@ export {
100829
100878
  Toggle,
100830
100879
  Tooltip,
100831
100880
  VideoThumbnail,
100881
+ announcePreviewDocumentLoaded,
100832
100882
  applyPatch,
100833
100883
  buildCompositionSnapTarget,
100834
100884
  buildGridSnapEdges,