@hyperframes/studio 0.8.144 → 0.8.146

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 (119) hide show
  1. package/dist/assets/{hyperframes-player-Bw_jIaSX.js → hyperframes-player-tkxyWf5H.js} +3 -3
  2. package/dist/assets/{index-Cj7W_c7p.js → index-BrdqOkqP.js} +1 -1
  3. package/dist/assets/{index-BVM9NuWl.js → index-Cg48BxAo.js} +145 -145
  4. package/dist/assets/{index-DyH9dT7b.js → index-Dllxg4aU.js} +1 -1
  5. package/dist/{chunk-EEXSYY5F.js → chunk-C5BDT7NG.js} +0 -1
  6. package/dist/{chunk-PEAKCOUW.js → chunk-HZDCMUG4.js} +2 -100
  7. package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
  8. package/dist/{chunk-G3EDKRZH.js → chunk-PHGBG7OG.js} +100 -5
  9. package/dist/{chunk-Y2OHHNM2.js → chunk-PVFBY7KR.js} +0 -1
  10. package/dist/{chunk-3GHTHPZD.js → chunk-T43DM7Q6.js} +101 -3
  11. package/dist/{domEditingLayers-PYST4G45.js → domEditingLayers-SMVA3FEW.js} +3 -4
  12. package/dist/index.css +0 -1
  13. package/dist/index.d.ts +8 -5
  14. package/dist/index.html +1 -1
  15. package/dist/index.js +509 -529
  16. package/dist/player.js +4 -5
  17. package/dist/styles/tailwind-preset.js +1 -2
  18. package/dist/ui.js +3 -4
  19. package/package.json +8 -8
  20. package/src/captions/components/CaptionOverlay.tsx +1 -1
  21. package/src/captions/hooks/useCaptionSync.ts +1 -1
  22. package/src/components/LinkedSelectionToggle.tsx +2 -2
  23. package/src/components/LintModal.tsx +1 -5
  24. package/src/components/MediaPreview.tsx +2 -2
  25. package/src/components/ProjectUnreachableBanner.tsx +1 -1
  26. package/src/components/StudioLeftPanels.tsx +2 -2
  27. package/src/components/StudioRightPanels.tsx +1 -1
  28. package/src/components/TimelineToolbar.test.tsx +2 -2
  29. package/src/components/TimelineToolbar.tsx +6 -6
  30. package/src/components/editor/AnimationCard.tsx +3 -3
  31. package/src/components/editor/AnimationCardParts.tsx +1 -1
  32. package/src/components/editor/BlockParamsPanel.tsx +3 -3
  33. package/src/components/editor/ComputedTweenNotice.tsx +2 -2
  34. package/src/components/editor/FileTree.tsx +1 -1
  35. package/src/components/editor/GsapAnimationList.tsx +1 -1
  36. package/src/components/editor/GsapAnimationSection.tsx +2 -2
  37. package/src/components/editor/LayersPanel.test.ts +4 -4
  38. package/src/components/editor/LayersPanel.tsx +2 -2
  39. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  40. package/src/components/editor/PromotableControl.tsx +1 -1
  41. package/src/components/editor/PropertyPanel.tsx +2 -2
  42. package/src/components/editor/SnapToolbar.tsx +2 -2
  43. package/src/components/editor/TopologyLens.test.tsx +5 -4
  44. package/src/components/editor/Transform3DCube.tsx +1 -1
  45. package/src/components/editor/gsapAnimationConstants.ts +2 -2
  46. package/src/components/editor/gsapAnimationHelpers.test.ts +1 -1
  47. package/src/components/editor/gsapAnimationHelpers.ts +1 -1
  48. package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
  49. package/src/components/editor/propertyPanelFill.tsx +1 -1
  50. package/src/components/editor/propertyPanelFlatMotionSection.tsx +3 -3
  51. package/src/components/editor/propertyPanelFlatPrimitives.tsx +2 -2
  52. package/src/components/editor/propertyPanelFlatSelectRow.tsx +1 -1
  53. package/src/components/editor/propertyPanelFlatTextSection.tsx +1 -1
  54. package/src/components/editor/propertyPanelFont.tsx +3 -3
  55. package/src/components/editor/propertyPanelFxBandRuler.tsx +2 -2
  56. package/src/components/editor/propertyPanelFxNodeOpenBody.tsx +2 -2
  57. package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
  58. package/src/components/editor/propertyPanelTransformCommit.ts +3 -3
  59. package/src/components/editor/useColorGradingController.ts +1 -1
  60. package/src/components/panels/SlideshowSubPanels.tsx +2 -2
  61. package/src/components/panels/VariablesBindElement.tsx +1 -1
  62. package/src/components/panels/VariablesPanel.tsx +3 -3
  63. package/src/components/renders/GpuEncodingToggle.tsx +28 -0
  64. package/src/components/renders/RenderQueue.test.tsx +20 -2
  65. package/src/components/renders/RenderQueue.tsx +15 -3
  66. package/src/components/renders/RenderQueuePanel.tsx +2 -1
  67. package/src/components/renders/renderSettings.ts +5 -2
  68. package/src/components/renders/useRenderQueue.ts +6 -3
  69. package/src/components/renders/useRenderQueueComposition.test.tsx +12 -0
  70. package/src/components/sidebar/AssetCard.tsx +2 -2
  71. package/src/components/sidebar/AudioRow.tsx +1 -1
  72. package/src/components/sidebar/BlocksTab.tsx +2 -2
  73. package/src/components/sidebar/CompositionsTab.tsx +2 -2
  74. package/src/components/sidebar/PromptPreviewModal.tsx +1 -1
  75. package/src/hooks/timelineAudioGroupCreate.ts +1 -1
  76. package/src/hooks/timelineEditingHelpers.ts +1 -1
  77. package/src/hooks/useBlockHandlers.ts +1 -1
  78. package/src/hooks/useConsoleErrorCapture.test.tsx +21 -2
  79. package/src/hooks/useConsoleErrorCapture.ts +3 -2
  80. package/src/hooks/useDomEditSession.ts +1 -1
  81. package/src/hooks/useEditorSave.ts +1 -1
  82. package/src/hooks/useFrameCapture.ts +1 -1
  83. package/src/hooks/useGestureCommit.ts +4 -4
  84. package/src/hooks/useGsapScriptCommits.ts +1 -1
  85. package/src/index.ts +1 -0
  86. package/src/player/components/AudioGainDialog.test.tsx +1 -1
  87. package/src/player/components/AudioGainDialog.tsx +2 -1
  88. package/src/player/components/EditModal.tsx +2 -2
  89. package/src/player/components/KeyframeDiamondContextMenu.tsx +1 -1
  90. package/src/player/components/OutOfSyncBadge.tsx +1 -1
  91. package/src/player/components/Player.test.ts +44 -0
  92. package/src/player/components/Player.tsx +12 -1
  93. package/src/player/components/ShortcutsPanel.tsx +2 -2
  94. package/src/player/components/Timeline.virtualization.test.tsx +1 -1
  95. package/src/player/components/TimelineAutomationLaneSlot.tsx +1 -1
  96. package/src/player/components/TimelineClip.tsx +1 -1
  97. package/src/player/components/TimelineFxButton.tsx +2 -2
  98. package/src/player/components/TimelineTrackHeader.test.tsx +3 -3
  99. package/src/player/components/timelineClipDragCommit.ts +2 -2
  100. package/src/player/components/trackHeaderLabelRows.tsx +1 -1
  101. package/src/player/lib/playbackAdapter.ts +1 -1
  102. package/src/player/lib/playbackTypes.ts +1 -0
  103. package/src/player/sceneSwap.test.ts +27 -1
  104. package/src/player/sceneSwap.ts +16 -2
  105. package/src/utils/hmrStableContext.ts +1 -1
  106. package/src/utils/razorSplitTransaction.ts +0 -2
  107. package/src/utils/sdkCutover.linkedTiming.test.ts +1 -1
  108. package/dist/chunk-3GHTHPZD.js.map +0 -1
  109. package/dist/chunk-EEXSYY5F.js.map +0 -1
  110. package/dist/chunk-G3EDKRZH.js.map +0 -1
  111. package/dist/chunk-PEAKCOUW.js.map +0 -1
  112. package/dist/chunk-PZ5AY32C.js.map +0 -1
  113. package/dist/chunk-Y2OHHNM2.js.map +0 -1
  114. package/dist/domEditingLayers-PYST4G45.js.map +0 -1
  115. package/dist/index.css.map +0 -1
  116. package/dist/index.js.map +0 -1
  117. package/dist/player.js.map +0 -1
  118. package/dist/styles/tailwind-preset.js.map +0 -1
  119. package/dist/ui.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  HyperframesLogo
3
- } from "./chunk-EEXSYY5F.js";
3
+ } from "./chunk-C5BDT7NG.js";
4
4
  import {
5
5
  DOCK_PANEL_COMPONENT,
6
6
  PANEL_DEFINITIONS,
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-G3EDKRZH.js";
50
+ } from "./chunk-PHGBG7OG.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-Y2OHHNM2.js";
82
+ } from "./chunk-PVFBY7KR.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-PEAKCOUW.js";
107
+ serializeDomEditTextFields
108
+ } from "./chunk-HZDCMUG4.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,
@@ -163,10 +169,11 @@ import {
163
169
  sameTimelineText,
164
170
  setCompositionSourceMap,
165
171
  splitTimelineElementKey,
172
+ studioApiFetch,
166
173
  toHexColor,
167
174
  wrapTimeline
168
- } from "./chunk-3GHTHPZD.js";
169
- import "./chunk-PZ5AY32C.js";
175
+ } from "./chunk-T43DM7Q6.js";
176
+ import "./chunk-MLKGABMK.js";
170
177
 
171
178
  // src/components/EditorShell.tsx
172
179
  import { memo as memo34, useCallback as useCallback74 } from "react";
@@ -871,7 +878,7 @@ var ShortcutsPanel = memo(function ShortcutsPanel2({
871
878
  )
872
879
  }
873
880
  ) })
874
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-600", children: "\u2014" }) })
881
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-600", children: "Not set" }) })
875
882
  ] }),
876
883
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
877
884
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -901,7 +908,7 @@ var ShortcutsPanel = memo(function ShortcutsPanel2({
901
908
  )
902
909
  }
903
910
  ) })
904
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-600", children: "\u2014" }) })
911
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-[10px] text-neutral-600", children: "Not set" }) })
905
912
  ] })
906
913
  ] })
907
914
  ] }),
@@ -1462,7 +1469,7 @@ function createStableContext(name, defaultValue) {
1462
1469
  const store = registry();
1463
1470
  if (store.has(name) && seenNames.get(name) !== defaultValue) {
1464
1471
  console.warn(
1465
- `[hmrStableContext] "${name}" was registered twice with different defaults \u2014 two contexts are sharing one identity. Use module path + export name.`
1472
+ `[hmrStableContext] "${name}" was registered twice with different defaults: two contexts are sharing one identity. Use module path + export name.`
1466
1473
  );
1467
1474
  }
1468
1475
  seenNames.set(name, defaultValue);
@@ -5994,7 +6001,7 @@ var METHOD_LABELS = {
5994
6001
  fromTo: "From \u2192 To"
5995
6002
  };
5996
6003
  var METHOD_TOOLTIPS = {
5997
- set: "Instantly snap to these values \u2014 no transition",
6004
+ set: "Instantly snap to these values, no transition",
5998
6005
  to: "Smoothly animate the element to these target values",
5999
6006
  from: "Element starts at these values and transitions to its normal state",
6000
6007
  fromTo: "Animate from one state to another"
@@ -6064,7 +6071,7 @@ var PROP_TOOLTIPS = {
6064
6071
  rotationY: "Rotate around the vertical Y axis",
6065
6072
  rotationZ: "Rotate around the screen-facing Z axis",
6066
6073
  perspective: "3D depth context for child elements; set it on a parent when rotating children in 3D",
6067
- transformPerspective: "3D depth for THIS element's own X/Y rotation \u2014 lower = stronger perspective (try 600\u20131000)",
6074
+ transformPerspective: "3D depth for THIS element's own X/Y rotation: lower = stronger perspective (try 600 to 1000)",
6068
6075
  transformOrigin: "Pivot point for transforms, for example center center or 50% 50%",
6069
6076
  width: "Element width",
6070
6077
  height: "Element height",
@@ -10132,7 +10139,7 @@ function persistMoveEdits(edits, deps, coalesceKey, operation = "timing") {
10132
10139
  const { updateElement, onMoveElement, onMoveElements } = deps;
10133
10140
  if (!onMoveElements) {
10134
10141
  console.warn(
10135
- onMoveElement ? `[Timeline] persistMoveEdits: only single-clip onMoveElement wired \u2014 this ${edits.length}-clip move degrades to a per-clip persist race (no atomic single-undo)` : `[Timeline] persistMoveEdits: no move persist handler wired \u2014 ${edits.length} edit(s) applied to the store only, not saved`
10142
+ onMoveElement ? `[Timeline] persistMoveEdits: only single-clip onMoveElement wired, so this ${edits.length}-clip move degrades to a per-clip persist race (no atomic single-undo)` : `[Timeline] persistMoveEdits: no move persist handler wired, so ${edits.length} edit(s) applied to the store only, not saved`
10136
10143
  );
10137
10144
  }
10138
10145
  const prev = edits.map((e) => ({
@@ -18882,7 +18889,7 @@ function resolveResizePlaybackStart(original, target, element, updates) {
18882
18889
  function applyTimelineMoveAttributes(original, target, start, duration, track2, audioGroup) {
18883
18890
  if (!Number.isFinite(start) || !Number.isFinite(duration)) {
18884
18891
  console.warn(
18885
- `[Timeline] applyTimelineMoveAttributes: non-finite timing (start=${start}, duration=${duration}) \u2014 patch skipped`
18892
+ `[Timeline] applyTimelineMoveAttributes: non-finite timing (start=${start}, duration=${duration}). Patch skipped`
18886
18893
  );
18887
18894
  return original;
18888
18895
  }
@@ -19392,7 +19399,7 @@ var OutOfSyncBadge = memo12(function OutOfSyncBadge2({ el }) {
19392
19399
  tabIndex: -1,
19393
19400
  "aria-haspopup": "menu",
19394
19401
  "aria-label": `Out of sync with ${partnerName} by ${label} frames`,
19395
- title: `Out of sync with ${partnerName} by ${label} \u2014 click for Move or Slip into Sync`,
19402
+ title: `Out of sync with ${partnerName} by ${label}. Click for Move or Slip into Sync`,
19396
19403
  "data-testid": "out-of-sync-badge",
19397
19404
  className: "absolute bottom-0.5 left-1 z-[12] cursor-pointer rounded-[3px] bg-red-600 px-1 font-mono text-[9px] leading-[14px] text-white tabular-nums",
19398
19405
  onPointerDown: stop,
@@ -19546,7 +19553,7 @@ var TimelineClip = memo13(function TimelineClip2({
19546
19553
  "aria-description": isGestureActor || !capabilities.canMove ? void 0 : "Space picks up. Up and Down choose a new track. Enter drops. Escape cancels.",
19547
19554
  className: clipClassName,
19548
19555
  style,
19549
- title: isComposition ? `${el.compositionSrc} \u2022 Double-click to open` : `${displayLabel} \u2022 ${startLabel}s \u2013 ${endLabel}s`,
19556
+ title: isComposition ? `${el.compositionSrc} \u2022 Double-click to open` : `${displayLabel} \u2022 ${startLabel}s to ${endLabel}s`,
19550
19557
  onPointerEnter: onHoverStart,
19551
19558
  onPointerLeave: onHoverEnd,
19552
19559
  onPointerDown,
@@ -21556,7 +21563,7 @@ function ClipAutomationLanes({
21556
21563
  onSelect: bound.onSelect,
21557
21564
  snapTimes,
21558
21565
  readOnly: bound.readOnly || isCarveLane(lane.target, bound.chain),
21559
- readOnlyNote: isCarveLane(lane.target, bound.chain) ? "Owned by the voiceover carve \u2014 re-derived on every analysis. Change strength in the FX rack, or turn the carve off to edit these by hand." : bound.readOnly ? "Read-only here." : void 0,
21566
+ readOnlyNote: isCarveLane(lane.target, bound.chain) ? "Owned by the voiceover carve and re-derived on every analysis. Change strength in the FX rack, or turn the carve off to edit these by hand." : bound.readOnly ? "Read-only here." : void 0,
21560
21567
  rangeSelection: bound.selection?.target === lane.target ? {
21561
21568
  t0: bound.selection.t0,
21562
21569
  t1: bound.selection.t1,
@@ -22870,7 +22877,7 @@ function TimelineFxButton(props) {
22870
22877
  type: "button",
22871
22878
  tabIndex: -1,
22872
22879
  ref: buttonRef,
22873
- "aria-label": "Effects \u2014 group these clips first",
22880
+ "aria-label": "Effects: group these clips first",
22874
22881
  title: "Group these clips to add effects to all of them",
22875
22882
  className: "flex h-6 items-center justify-center rounded-sm border-0 bg-transparent px-1 text-[10px] font-semibold text-[var(--timeline-text-faint)] hover:text-[var(--timeline-text-soft)]",
22876
22883
  onPointerDown: (event) => event.stopPropagation(),
@@ -22909,7 +22916,7 @@ function TimelineFxButton(props) {
22909
22916
  type: "button",
22910
22917
  tabIndex: -1,
22911
22918
  ref: buttonRef,
22912
- "aria-label": nodeCount > 0 ? `Effects \u2014 ${nodeCount} applied` : "Effects",
22919
+ "aria-label": nodeCount > 0 ? `Effects: ${nodeCount} applied` : "Effects",
22913
22920
  title: "Effects",
22914
22921
  className: `flex h-6 items-center justify-center gap-0.5 rounded border-0 bg-transparent px-1 text-[10px] font-semibold transition-colors ${open || nodeCount > 0 ? "text-[var(--timeline-accent)]" : "text-[var(--timeline-text-faint)] hover:text-[var(--timeline-text-soft)]"}`,
22915
22922
  onPointerDown: (event) => event.stopPropagation(),
@@ -23385,7 +23392,7 @@ function AutomationLaneHeaderRow({
23385
23392
  {
23386
23393
  "data-automation-lane-also": "",
23387
23394
  className: "truncate text-[9px] text-warning-ink",
23388
- title: `${alsoAutomatedBy} is also fading this \u2014 the two multiply.`,
23395
+ title: `${alsoAutomatedBy} is also fading this. The two multiply.`,
23389
23396
  children: [
23390
23397
  alsoAutomatedBy,
23391
23398
  " is also fading this."
@@ -25689,8 +25696,9 @@ function AudioGainDialog({
25689
25696
  }
25690
25697
  ),
25691
25698
  /* @__PURE__ */ jsxs40("div", { className: "mt-3 text-neutral-400", "data-testid": "audio-gain-peak", children: [
25692
- "Peak Amplitude: ",
25693
- peaks === null ? "measuring\u2026" : peakDb === null ? "\u2014" : dbText(peakDb)
25699
+ "Peak Amplitude:",
25700
+ " ",
25701
+ peaks === null ? "measuring\u2026" : peakDb === null ? "Unavailable" : dbText(peakDb)
25694
25702
  ] }),
25695
25703
  outOfRange && /* @__PURE__ */ jsx63("div", { className: "mt-1 text-red-400", children: "Peak targets must be 0 dB or lower." }),
25696
25704
  /* @__PURE__ */ jsxs40("div", { className: "mt-4 flex justify-end gap-2", children: [
@@ -25898,7 +25906,7 @@ function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }) {
25898
25906
  /* @__PURE__ */ jsx64("div", { className: "w-1.5 h-1.5 rounded-full bg-studio-accent" }),
25899
25907
  /* @__PURE__ */ jsxs41("span", { className: "text-[11px] font-medium text-neutral-300", children: [
25900
25908
  formatTime(start),
25901
- " \u2014 ",
25909
+ " to ",
25902
25910
  formatTime(end)
25903
25911
  ] })
25904
25912
  ] }),
@@ -25932,7 +25940,7 @@ function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }) {
25932
25940
  className: "w-full px-3 py-2 text-xs bg-neutral-800/60 border border-neutral-700/40 rounded-lg text-neutral-200 placeholder:text-neutral-600 resize-none focus:outline-hidden focus:border-studio-accent/40 transition-colors"
25933
25941
  }
25934
25942
  ) }),
25935
- copyError && /* @__PURE__ */ jsx64("p", { className: "px-3 pb-2 text-[10px] text-danger-ink", role: "alert", children: "Copy failed \u2014 check clipboard permissions and try again." }),
25943
+ copyError && /* @__PURE__ */ jsx64("p", { className: "px-3 pb-2 text-[10px] text-danger-ink", role: "alert", children: "Copy failed. Check clipboard permissions and try again." }),
25936
25944
  /* @__PURE__ */ jsxs41("div", { className: "grid grid-cols-2 gap-2 px-3 pb-3", children: [
25937
25945
  /* @__PURE__ */ jsx64(
25938
25946
  "button",
@@ -26047,7 +26055,7 @@ function KeyframeDiamondContextMenu({
26047
26055
  onClick: () => {
26048
26056
  void handleCopyProperties();
26049
26057
  },
26050
- children: copyStatus === "copied" ? "Copied!" : copyStatus === "failed" ? "Copy failed \u2014 check permissions" : "Copy Properties"
26058
+ children: copyStatus === "copied" ? "Copied!" : copyStatus === "failed" ? "Copy failed. Check permissions" : "Copy Properties"
26051
26059
  }
26052
26060
  ),
26053
26061
  onDelete && /* @__PURE__ */ jsx65(
@@ -31514,79 +31522,6 @@ function isPreviewChanging() {
31514
31522
  return changing() && !givenUp;
31515
31523
  }
31516
31524
 
31517
- // src/player/sceneSwap.ts
31518
- var SCENES_SWAPPED = "hf-scenes-swapped";
31519
- var SCENE_SWAP_MS = 5e3;
31520
- function onPreviewContentReplaced(iframe, onReplaced) {
31521
- iframe.addEventListener("load", onReplaced);
31522
- iframe.addEventListener(SCENES_SWAPPED, onReplaced);
31523
- return () => {
31524
- iframe.removeEventListener("load", onReplaced);
31525
- iframe.removeEventListener(SCENES_SWAPPED, onReplaced);
31526
- };
31527
- }
31528
- var PREVIEW_PROMOTED = "hf-preview-promoted";
31529
- function announcePreviewPromoted(promotion) {
31530
- promotion.live.ownerDocument.dispatchEvent(
31531
- new CustomEvent(PREVIEW_PROMOTED, { detail: promotion })
31532
- );
31533
- }
31534
- function onPreviewPromoted(doc, listener2) {
31535
- const handle = (event) => listener2(event.detail);
31536
- doc.addEventListener(PREVIEW_PROMOTED, handle);
31537
- return () => doc.removeEventListener(PREVIEW_PROMOTED, handle);
31538
- }
31539
- function markScenesStale(iframe, files) {
31540
- const doc = iframe?.contentDocument;
31541
- const meta = doc?.querySelector('meta[name="hf-scene-parts"]');
31542
- if (!doc || !meta) return;
31543
- let parts;
31544
- try {
31545
- parts = JSON.parse(meta.content);
31546
- } catch {
31547
- return;
31548
- }
31549
- for (const file of files) {
31550
- const hosts = doc.querySelectorAll(`[data-composition-file="${CSS.escape(file)}"]`);
31551
- if (hosts.length === 0 && file.endsWith(".html")) parts.shared = "";
31552
- for (const host of hosts) {
31553
- const scene = host.closest("[data-hf-scene]")?.getAttribute("data-hf-scene");
31554
- if (scene && scene in parts.scenes) parts.scenes[scene] = "";
31555
- }
31556
- }
31557
- meta.content = JSON.stringify(parts);
31558
- }
31559
- function sceneSwapFor(iframe) {
31560
- const win = iframe.contentWindow;
31561
- const swap = win?.__hfSwapScenes;
31562
- if (typeof swap !== "function") return null;
31563
- return async (url, isCurrent, cancel) => {
31564
- if (cancel?.aborted) throw cancel.reason;
31565
- const deadline = new AbortController();
31566
- const timer = setTimeout(
31567
- () => deadline.abort(new Error("the preview took too long to swap")),
31568
- SCENE_SWAP_MS
31569
- );
31570
- cancel?.addEventListener("abort", () => deadline.abort(cancel.reason), { once: true });
31571
- const swapping = (async () => {
31572
- const response = await studioApiFetch(url, { signal: deadline.signal });
31573
- if (!response.ok) throw new Error(`preview request failed with ${response.status}`);
31574
- const html2 = await response.text();
31575
- if (!isCurrent()) throw new Error("superseded by a newer reload");
31576
- await swap.call(win, html2, deadline.signal);
31577
- })();
31578
- const expired = new Promise(
31579
- (_resolve, reject) => deadline.signal.addEventListener("abort", () => reject(deadline.signal.reason))
31580
- );
31581
- try {
31582
- await Promise.race([swapping, expired]);
31583
- } finally {
31584
- clearTimeout(timer);
31585
- }
31586
- iframe.dispatchEvent(new Event(SCENES_SWAPPED));
31587
- };
31588
- }
31589
-
31590
31525
  // src/player/hooks/useShadowPreviewReload.ts
31591
31526
  var SHADOW_READY_TIMEOUT_MS = 15e3;
31592
31527
  var SHADOW_READY_BUDGETS = 3;
@@ -35924,7 +35859,7 @@ var CaptionOverlay = memo26(function CaptionOverlay2({ iframeRef }) {
35924
35859
  onPointerUp: handlePointerUp,
35925
35860
  onLostPointerCapture: handlePointerUp,
35926
35861
  children: [
35927
- wordBoxes.length === 0 && model && model.segments.size > 0 && /* @__PURE__ */ jsx91("div", { className: "absolute inset-x-0 top-3 flex justify-center pointer-events-none", children: /* @__PURE__ */ jsx91("span", { className: "px-2.5 py-1 rounded-full bg-black/60 border border-neutral-700 text-2xs text-neutral-300", children: "No captions visible at this frame \u2014 scrub to a caption, or select one in the track below" }) }),
35862
+ wordBoxes.length === 0 && model && model.segments.size > 0 && /* @__PURE__ */ jsx91("div", { className: "absolute inset-x-0 top-3 flex justify-center pointer-events-none", children: /* @__PURE__ */ jsx91("span", { className: "px-2.5 py-1 rounded-full bg-black/60 border border-neutral-700 text-2xs text-neutral-300", children: "No captions visible at this frame. Scrub to a caption, or select one in the track below" }) }),
35928
35863
  wordBoxes.map((box) => {
35929
35864
  const isSelected2 = selectedSegmentIds.has(box.segmentId);
35930
35865
  return /* @__PURE__ */ jsx91(
@@ -37575,7 +37510,7 @@ function OffCanvasIndicators({
37575
37510
  const selectOffCanvas = async () => {
37576
37511
  const el = elements.current.get(r.key);
37577
37512
  if (!el) return;
37578
- const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-PYST4G45.js");
37513
+ const { resolveDomEditSelection: resolveDomEditSelection2 } = await import("./domEditingLayers-SMVA3FEW.js");
37579
37514
  const acp = activeCompositionPathRef.current ?? "index.html";
37580
37515
  const sel = await resolveDomEditSelection2(el, {
37581
37516
  activeCompositionPath: acp,
@@ -37596,7 +37531,7 @@ function OffCanvasIndicators({
37596
37531
  "aria-label": `Select off-canvas element ${r.key}`,
37597
37532
  className: "pointer-events-auto absolute inset-0 border-2 border-dashed border-studio-accent/10 rounded-md cursor-pointer hover:border-studio-accent hover:bg-studio-accent/10 transition-colors",
37598
37533
  style: clipOutside ? { clipPath: clipOutside } : void 0,
37599
- title: `Off-canvas: ${r.key} \u2014 click to select`,
37534
+ title: `Off-canvas: ${r.key}. Click to select`,
37600
37535
  onClick: handleClick,
37601
37536
  onKeyDown: (e) => {
37602
37537
  if (e.key === "Enter" || e.key === " ") {
@@ -48485,7 +48420,7 @@ var SnapToolbar = memo31(function SnapToolbar2() {
48485
48420
  if (gridPopoverOpen) settleGridSpacing();
48486
48421
  setGridPopoverOpen((v) => !v);
48487
48422
  },
48488
- title: prefs.gridVisible ? "Grid visible (G) \u2014 right-click for spacing options" : "Grid hidden (G) \u2014 right-click for spacing options",
48423
+ title: prefs.gridVisible ? "Grid visible (G). Right-click for spacing options" : "Grid hidden (G). Right-click for spacing options",
48489
48424
  "aria-label": "Toggle grid",
48490
48425
  children: /* @__PURE__ */ jsx109(GridFour, { size: 16, weight: prefs.gridVisible ? "fill" : "regular" })
48491
48426
  }
@@ -54560,7 +54495,7 @@ function createTransformCommitHandlers({
54560
54495
  if (await commitAnimatedTransformValue(
54561
54496
  axis,
54562
54497
  parsed,
54563
- "This element's position can't be edited here yet \u2014 it is driven by its animation"
54498
+ "This element's position can't be edited here yet: its animation drives it"
54564
54499
  ))
54565
54500
  return;
54566
54501
  const plainTranslate = editsPlainCss(element.element, "move");
@@ -54583,7 +54518,7 @@ function createTransformCommitHandlers({
54583
54518
  return;
54584
54519
  }
54585
54520
  if (hasGsapAnimation) {
54586
- showToast?.("This element's size can't be edited here yet \u2014 it is driven by its animation");
54521
+ showToast?.("This element's size can't be edited here yet: its animation drives it");
54587
54522
  return;
54588
54523
  }
54589
54524
  const current = readStudioBoxSize(element.element);
@@ -54602,7 +54537,7 @@ function createTransformCommitHandlers({
54602
54537
  if (await commitAnimatedTransformValue(
54603
54538
  "rotation",
54604
54539
  parsed,
54605
- "This element's rotation can't be edited here yet \u2014 it is driven by its animation"
54540
+ "This element's rotation can't be edited here yet: its animation drives it"
54606
54541
  ))
54607
54542
  return;
54608
54543
  await Promise.resolve(onSetManualRotation(element, { angle: parsed }));
@@ -55599,7 +55534,7 @@ function ColorGradingControls({
55599
55534
  setLutImporting(true);
55600
55535
  setLutImportError(null);
55601
55536
  void actions.importLut(files, onImportAssets, () => track2("button", "Import LUT")).catch(
55602
- () => setLutImportError("LUT import failed \u2014 check the .cube file and try again.")
55537
+ () => setLutImportError("LUT import failed. Check the .cube file and try again.")
55603
55538
  ).finally(() => setLutImporting(false));
55604
55539
  event.currentTarget.value = "";
55605
55540
  }
@@ -56209,7 +56144,7 @@ function useColorGradingController({
56209
56144
  const reverted = confirmedGradingRef.current;
56210
56145
  latestGradingRef.current = reverted;
56211
56146
  setGrading(reverted);
56212
- setRuntimeStatus({ state: "unavailable", message: "Save failed \u2014 reverted" });
56147
+ setRuntimeStatus({ state: "unavailable", message: "Save failed, changes reverted" });
56213
56148
  };
56214
56149
  const result = setAttributeLive(COLOR_GRADING_DATA_KEY, value ?? null, applySettled);
56215
56150
  return trackStudioPendingEdit(
@@ -57367,7 +57302,7 @@ function FontFamilyField({
57367
57302
  setFontNotice("No supported font files were imported.");
57368
57303
  }
57369
57304
  } catch {
57370
- setFontNotice("Font import failed \u2014 the files were not added. Try again.");
57305
+ setFontNotice("Font import failed. The files were not added. Try again.");
57371
57306
  } finally {
57372
57307
  setImportingFonts(false);
57373
57308
  }
@@ -57445,10 +57380,10 @@ function FontFamilyField({
57445
57380
  setQuery("");
57446
57381
  setOpen(false);
57447
57382
  } else {
57448
- setFontNotice(`Couldn't import "${option.family}" \u2014 the font was not applied.`);
57383
+ setFontNotice(`Couldn't import "${option.family}", so the font was not applied.`);
57449
57384
  }
57450
57385
  } catch {
57451
- setFontNotice(`Couldn't import "${option.family}" \u2014 the font was not applied.`);
57386
+ setFontNotice(`Couldn't import "${option.family}", so the font was not applied.`);
57452
57387
  } finally {
57453
57388
  setImportingFonts(false);
57454
57389
  }
@@ -57947,7 +57882,7 @@ function VariablesBindElement({
57947
57882
  String(existingDecl.default),
57948
57883
  ")"
57949
57884
  ] }),
57950
- `, not the element's own \u2014 binding won't change "`,
57885
+ `, not the element's own, so binding won't change "`,
57951
57886
  trimmedId,
57952
57887
  '".'
57953
57888
  ] }),
@@ -58107,7 +58042,7 @@ function PromotableControl({
58107
58042
  useEffect79(() => {
58108
58043
  if (danglingId != null) {
58109
58044
  console.warn(
58110
- `[hyperframes] Control is bound to variable "${danglingId}", but no such declaration exists. The element still carries the binding on disk \u2014 re-declare the variable or unbind the element.`
58045
+ `[hyperframes] Control is bound to variable "${danglingId}", but no such declaration exists. The element still carries the binding on disk. Re-declare the variable or unbind the element.`
58111
58046
  );
58112
58047
  }
58113
58048
  }, [danglingId]);
@@ -58602,7 +58537,7 @@ function ImageFillField({
58602
58537
  commitImage(toProjectRootAssetPath(nextImage), true);
58603
58538
  }
58604
58539
  } catch {
58605
- setUploadError("Upload failed \u2014 check the file and try again.");
58540
+ setUploadError("Upload failed. Check the file and try again.");
58606
58541
  } finally {
58607
58542
  setUploading(false);
58608
58543
  }
@@ -60110,7 +60045,7 @@ function buildTweenSummary(animation2) {
60110
60045
  const label = (PROP_LABELS[p] ?? p).toLowerCase();
60111
60046
  return `${label} ${formatPropValue(p, v)}`;
60112
60047
  });
60113
- const fromText = fromDescs.length > 0 ? fromDescs.join(", ") : "\u2014";
60048
+ const fromText = fromDescs.length > 0 ? fromDescs.join(", ") : "none";
60114
60049
  return `Starting at ${pos}s, over ${dur}s, ${target} animates from [${fromText}] to [${propText}] using a ${ease.toLowerCase()} curve.`;
60115
60050
  }
60116
60051
  return `Starting at ${pos}s, over ${dur}s, animate ${target}'s ${propText} using a ${ease.toLowerCase()} curve.`;
@@ -60253,14 +60188,14 @@ function ComputedTweenNotice({
60253
60188
  const editability = editabilityForProvenance2(provenance);
60254
60189
  if (editability === "direct") return null;
60255
60190
  if (editability === "source") {
60256
- return /* @__PURE__ */ jsx142("div", { className: "rounded-md border border-neutral-800 bg-neutral-900/50 px-2 py-1.5 text-[9px] text-neutral-400", children: "Computed value \u2014 edit it in the Code tab." });
60191
+ return /* @__PURE__ */ jsx142("div", { className: "rounded-md border border-neutral-800 bg-neutral-900/50 px-2 py-1.5 text-[9px] text-neutral-400", children: "Computed value. Edit it in the Code tab." });
60257
60192
  }
60258
60193
  const source = provenance?.fn ? `${provenance.fn}()` : "a loop";
60259
60194
  return /* @__PURE__ */ jsxs104("div", { className: "flex items-center justify-between gap-2 rounded-md border border-neutral-800 bg-neutral-900/50 px-2 py-1.5 text-[9px] text-neutral-400", children: [
60260
60195
  /* @__PURE__ */ jsxs104("span", { children: [
60261
60196
  "Generated by ",
60262
60197
  source,
60263
- " \u2014 not directly editable."
60198
+ ", not directly editable."
60264
60199
  ] }),
60265
60200
  onUnroll && /* @__PURE__ */ jsx142(
60266
60201
  "button",
@@ -60468,7 +60403,7 @@ function PropertyRow({
60468
60403
  onClick: () => onCommit(isVisible ? "hidden" : "visible"),
60469
60404
  className: "shrink-0 rounded-full transition-colors duration-200 relative",
60470
60405
  style: { width: 28, height: 16, background: isVisible ? P.accent : P.borderInput },
60471
- title: isVisible ? "Visible \u2014 click to hide" : "Hidden \u2014 click to show",
60406
+ title: isVisible ? "Visible: click to hide" : "Hidden: click to show",
60472
60407
  children: /* @__PURE__ */ jsx144(
60473
60408
  "span",
60474
60409
  {
@@ -60732,7 +60667,7 @@ var AnimationCard = memo38(function AnimationCard2({
60732
60667
  children: [
60733
60668
  typeof animation2.position === "number" ? `${parseFloat(animation2.position.toFixed(3))}s` : animation2.position,
60734
60669
  " ",
60735
- "\u2013 ",
60670
+ "to ",
60736
60671
  typeof endTime === "number" ? `${parseFloat(endTime.toFixed(3))}s` : endTime
60737
60672
  ]
60738
60673
  }
@@ -60782,7 +60717,7 @@ var AnimationCard = memo38(function AnimationCard2({
60782
60717
  }
60783
60718
  }
60784
60719
  ),
60785
- "Keyframed \u2014 click a segment below to edit its curve"
60720
+ "Keyframed: click a segment below to edit its curve"
60786
60721
  ] }),
60787
60722
  runEase && runEase !== "none" && /* @__PURE__ */ jsxs107("p", { className: "mt-1 text-[9px] text-neutral-500", "data-card-run-ease": true, children: [
60788
60723
  "Run ease: ",
@@ -60799,7 +60734,7 @@ var AnimationCard = memo38(function AnimationCard2({
60799
60734
  setTimeout(() => setCopied(false), 1500);
60800
60735
  },
60801
60736
  className: "shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300",
60802
- title: "Copy description to clipboard \u2014 paste into agent prompts",
60737
+ title: "Copy description for agent prompts",
60803
60738
  children: copied ? "Copied" : "Copy"
60804
60739
  }
60805
60740
  )
@@ -61138,7 +61073,7 @@ function GsapAnimationList({
61138
61073
  }
61139
61074
  }, [focusedHere]);
61140
61075
  return /* @__PURE__ */ jsxs109("div", { className: "space-y-2", children: [
61141
- animations.length === 0 && /* @__PURE__ */ jsx147("p", { className: "text-[11px] leading-4 text-neutral-500", children: "No animations on this element yet \u2014 add an effect below to animate it." }),
61076
+ animations.length === 0 && /* @__PURE__ */ jsx147("p", { className: "text-[11px] leading-4 text-neutral-500", children: "No animations on this element yet. Add an effect below to animate it." }),
61142
61077
  animations.map((animation2, index) => /* @__PURE__ */ createElement2(
61143
61078
  AnimationCard,
61144
61079
  {
@@ -61175,7 +61110,7 @@ var GsapAnimationSection = memo39(function GsapAnimationSection2({
61175
61110
  ...callbacks
61176
61111
  }) {
61177
61112
  return /* @__PURE__ */ jsxs110(Section, { title: "Animation", icon: /* @__PURE__ */ jsx148(Film, { size: 15 }), children: [
61178
- multipleTimelines && /* @__PURE__ */ jsx148("p", { className: "mb-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: "This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss \u2014 consolidate into a single timeline to enable editing." }),
61113
+ multipleTimelines && /* @__PURE__ */ jsx148("p", { className: "mb-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: "This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss. Consolidate into a single timeline to enable editing." }),
61179
61114
  unsupportedTimelinePattern && /* @__PURE__ */ jsx148("p", { className: "mb-2 rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: `This timeline uses a computed key (window.__timelines[variable]) the editor can't resolve statically. Use a string-literal key (window.__timelines["id"]) or a variable declaration (const tl = gsap.timeline()) to enable editing.` }),
61180
61115
  multipleTimelines || unsupportedTimelinePattern ? null : /* @__PURE__ */ jsx148(
61181
61116
  GsapAnimationList,
@@ -61515,7 +61450,7 @@ function Transform3DCube({
61515
61450
  {
61516
61451
  type: "button",
61517
61452
  onClick: onKeyframe,
61518
- title: keyframed ? "3D transform is keyframed \u2014 click a field diamond to add keyframes" : "Keyframe the 3D transform (animate it over time)",
61453
+ title: keyframed ? "3D transform is keyframed. Click a field diamond to add keyframes" : "Keyframe the 3D transform (animate it over time)",
61519
61454
  "aria-label": "Keyframe 3D transform",
61520
61455
  "aria-pressed": keyframed,
61521
61456
  className: `absolute left-1.5 top-1.5 rounded p-0.5 hover:bg-neutral-800 ${keyframed ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"}`,
@@ -62235,7 +62170,7 @@ function FlatSelectRow({
62235
62170
  {
62236
62171
  type: "button",
62237
62172
  "data-flat-select-reset": "true",
62238
- title: "Remove \u2014 fall back to default",
62173
+ title: "Remove and fall back to default",
62239
62174
  disabled,
62240
62175
  onClick: () => {
62241
62176
  track2("button", `Reset ${trackName}`);
@@ -62296,7 +62231,7 @@ function FlatRow({
62296
62231
  {
62297
62232
  type: "button",
62298
62233
  "data-flat-row-reset": "true",
62299
- title: "Remove \u2014 fall back to default",
62234
+ title: "Remove and fall back to default",
62300
62235
  onClick: () => {
62301
62236
  track2("button", `Reset ${label}`);
62302
62237
  onReset();
@@ -62623,7 +62558,7 @@ function FlatSlider({
62623
62558
  {
62624
62559
  type: "button",
62625
62560
  "data-flat-slider-reset": "true",
62626
- title: "Remove \u2014 fall back to default",
62561
+ title: "Remove and fall back to default",
62627
62562
  disabled,
62628
62563
  onClick: () => {
62629
62564
  track2("button", `Reset ${label}`);
@@ -62664,7 +62599,7 @@ var ALIGN_OPTIONS = [
62664
62599
  { key: "justify", label: "justify", node: "J" }
62665
62600
  ];
62666
62601
  var CASE_OPTIONS = [
62667
- { key: "none", label: "none", node: "\u2013" },
62602
+ { key: "none", label: "none", node: "None" },
62668
62603
  { key: "uppercase", label: "uppercase", node: "AG" },
62669
62604
  { key: "lowercase", label: "lowercase", node: "ag" },
62670
62605
  { key: "capitalize", label: "capitalize", node: "Ag" }
@@ -63890,7 +63825,7 @@ function TimingSection({
63890
63825
  onCommit: commitDuration
63891
63826
  }
63892
63827
  ) }),
63893
- derived && /* @__PURE__ */ jsx161("p", { className: "mt-2 text-[10px] leading-snug text-neutral-500", children: "Inferred from this element\u2019s animation \u2014 edit to pin an explicit clip range." })
63828
+ derived && /* @__PURE__ */ jsx161("p", { className: "mt-2 text-[10px] leading-snug text-neutral-500", children: "Inferred from this element\u2019s animation. Edit to pin an explicit clip range." })
63894
63829
  ] });
63895
63830
  }
63896
63831
 
@@ -63984,7 +63919,7 @@ function FlatTimingRow({
63984
63919
  cell("Start", formatTimingValue(start), commitStart),
63985
63920
  cell("End", formatTimingValue(end), commitEnd),
63986
63921
  cell("Duration", formatTimingValue(duration), commitDuration),
63987
- derived && /* @__PURE__ */ jsx162("p", { className: "col-span-3 mt-1 text-[10px] leading-snug text-panel-text-3", children: "Inferred from this element's animation \u2014 edit to pin an explicit clip range." })
63922
+ derived && /* @__PURE__ */ jsx162("p", { className: "col-span-3 mt-1 text-[10px] leading-snug text-panel-text-3", children: "Inferred from this element's animation. Edit to pin an explicit clip range." })
63988
63923
  ] });
63989
63924
  }
63990
63925
  function FlatMotionSection({
@@ -64011,7 +63946,7 @@ function FlatMotionSection({
64011
63946
  }
64012
63947
  ),
64013
63948
  showEffects && /* @__PURE__ */ jsxs122(Fragment46, { children: [
64014
- multipleTimelines && /* @__PURE__ */ jsx162("p", { className: "rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: "This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss \u2014 consolidate into a single timeline to enable editing." }),
63949
+ multipleTimelines && /* @__PURE__ */ jsx162("p", { className: "rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: "This file has multiple GSAP timelines. Animation editing is disabled to prevent data loss. Consolidate into a single timeline to enable editing." }),
64015
63950
  unsupportedTimelinePattern && /* @__PURE__ */ jsx162("p", { className: "rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink", children: "This timeline uses a computed key the editor can't resolve statically." }),
64016
63951
  !multipleTimelines && !unsupportedTimelinePattern && /* @__PURE__ */ jsx162(
64017
63952
  GsapAnimationList,
@@ -64030,7 +63965,7 @@ function motionSectionLabel(args) {
64030
63965
  if (args.timingOnly) {
64031
63966
  return {
64032
63967
  title: "Timing",
64033
- summary: `${formatTimingValue(args.start)} \u2013 ${formatTimingValue(args.start + args.duration)}`
63968
+ summary: `${formatTimingValue(args.start)} to ${formatTimingValue(args.start + args.duration)}`
64034
63969
  };
64035
63970
  }
64036
63971
  return {
@@ -65052,7 +64987,7 @@ function FxBandRuler({ band, at }) {
65052
64987
  return /* @__PURE__ */ jsx166(
65053
64988
  "span",
65054
64989
  {
65055
- title: `${range.name} \u2014 ${range.says}`,
64990
+ title: `${range.name}: ${range.says}`,
65056
64991
  className: range.name === here.name ? "hf-fx-ruler-band bg-panel-accent" : reachable ? "hf-fx-ruler-band bg-panel-text-4/50" : "hf-fx-ruler-band bg-panel-text-4/15",
65057
64992
  style: {
65058
64993
  width: `${(positionOf(range.to) - positionOf(range.from)) * 100}%`
@@ -65063,7 +64998,7 @@ function FxBandRuler({ band, at }) {
65063
64998
  }) }),
65064
64999
  /* @__PURE__ */ jsxs126("p", { className: "hf-fx-ruler-label truncate pt-0.5 text-[9px] text-panel-text-2", children: [
65065
65000
  /* @__PURE__ */ jsx166("span", { className: "hf-fx-ruler-name text-panel-text-1", children: here.name }),
65066
- " \u2014 ",
65001
+ ": ",
65067
65002
  here.says
65068
65003
  ] })
65069
65004
  ] });
@@ -65286,12 +65221,12 @@ function FxNodeOpenBody({
65286
65221
  onClick: onToggleDetails,
65287
65222
  children: [
65288
65223
  /* @__PURE__ */ jsx168("span", { "aria-hidden": "true", children: details ? "\u25BE" : "\u25B8" }),
65289
- "Details \u2014 ",
65224
+ "Details: ",
65290
65225
  registryDef.label
65291
65226
  ]
65292
65227
  }
65293
65228
  ) : /* @__PURE__ */ jsxs127("p", { className: "hf-fx-node-mechanism border-t border-panel-border-input px-1.5 pt-1 font-mono text-[9px] uppercase tracking-wide text-panel-text-2", children: [
65294
- "Details \u2014 ",
65229
+ "Details: ",
65295
65230
  registryDef.label
65296
65231
  ] }),
65297
65232
  details || !oneKnob ? /* @__PURE__ */ jsx168(
@@ -71680,12 +71615,12 @@ var PropertyPanel = memo40(function PropertyPanel2(props) {
71680
71615
  const handleCopyElementInfo = () => {
71681
71616
  const text = buildElementInfoText(element, sourceLabel, gsapAnimations, previewIframeRef);
71682
71617
  navigator.clipboard.writeText(text).then(() => {
71683
- showToast(`Copied element info for ${element.label} \u2014 paste into any AI agent`, "info");
71618
+ showToast(`Copied element info for ${element.label}. Paste it into any AI agent`, "info");
71684
71619
  setClipboardCopied(true);
71685
71620
  clearTimeout(clipboardTimerRef.current);
71686
71621
  clipboardTimerRef.current = setTimeout(() => setClipboardCopied(false), 1500);
71687
71622
  }).catch(() => {
71688
- showToast("Couldn't copy to the clipboard \u2014 check browser permissions", "error");
71623
+ showToast("Couldn't copy to the clipboard. Check browser permissions", "error");
71689
71624
  });
71690
71625
  };
71691
71626
  if (STUDIO_FLAT_INSPECTOR_ENABLED) {
@@ -72822,7 +72757,7 @@ var FileTree = memo42(function FileTree2({
72822
72757
  ),
72823
72758
  children.length === 0 && !inlineInput && /* @__PURE__ */ jsxs150("div", { className: "px-3 py-4 text-center text-[11px] text-neutral-600", children: [
72824
72759
  "No files yet",
72825
- hasFileOps ? " \u2014 use + above to create one" : "",
72760
+ hasFileOps ? ". Use + above to create one" : "",
72826
72761
  "."
72827
72762
  ] }),
72828
72763
  children.map(
@@ -73366,6 +73301,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
73366
73301
  }
73367
73302
  if (resolution && resolution !== "auto") body.resolution = resolution;
73368
73303
  if (composition) body.composition = composition;
73304
+ if (opts.gpu) body.gpu = true;
73369
73305
  if (opts.variables && Object.keys(opts.variables).length > 0) {
73370
73306
  body.variables = opts.variables;
73371
73307
  }
@@ -73466,7 +73402,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
73466
73402
  try {
73467
73403
  const res = await studioApiFetch(`/api/render/${jobId}/cancel`, { method: "POST" });
73468
73404
  if (!res.ok && res.status !== 404) {
73469
- setActionError("Couldn't cancel on the server \u2014 the render may still be running.");
73405
+ setActionError("Couldn't cancel on the server. The render may still be running.");
73470
73406
  return;
73471
73407
  }
73472
73408
  if (res.ok) {
@@ -73476,7 +73412,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
73476
73412
  }
73477
73413
  }
73478
73414
  } catch {
73479
- setActionError("Couldn't reach the server to cancel \u2014 the render may still be running.");
73415
+ setActionError("Couldn't reach the server to cancel. The render may still be running.");
73480
73416
  }
73481
73417
  },
73482
73418
  [closeActiveEventSource, loadRenders]
@@ -73488,7 +73424,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
73488
73424
  try {
73489
73425
  const res = await studioApiFetch(`/api/render/${jobId}`, { method: "DELETE" });
73490
73426
  if (!res.ok) {
73491
- setActionError("Couldn't delete the render \u2014 it's still on disk.");
73427
+ setActionError("Couldn't delete the render. It's still on disk.");
73492
73428
  return;
73493
73429
  }
73494
73430
  } catch {
@@ -73727,7 +73663,7 @@ ${pathLine}${lines.join("\n\n")}`;
73727
73663
  {
73728
73664
  onClick: handleCopyToAgent,
73729
73665
  className: `px-3 py-1 text-xs font-medium rounded-lg transition-colors active:scale-[0.98] ${copied ? "bg-accent text-on-accent" : copyFailed ? "bg-danger text-on-danger" : "bg-accent hover:bg-accent-hover text-on-accent"}`,
73730
- children: copied ? "Copied!" : copyFailed ? "Copy failed \u2014 check permissions" : "Copy to Agent"
73666
+ children: copied ? "Copied!" : copyFailed ? "Copy failed. Check permissions" : "Copy to Agent"
73731
73667
  }
73732
73668
  ) }),
73733
73669
  /* @__PURE__ */ jsxs151("div", { className: "flex-1 overflow-y-auto px-5 py-3", children: [
@@ -74628,7 +74564,7 @@ function ProjectUnreachableBanner({ projectId }) {
74628
74564
  ]
74629
74565
  }
74630
74566
  )
74631
- ] }) : /* @__PURE__ */ jsx202("span", { children: "Couldn't open this project \u2014 it may have been renamed, moved, or deleted." })
74567
+ ] }) : /* @__PURE__ */ jsx202("span", { children: "Couldn't open this project. It may have been renamed, moved, or deleted." })
74632
74568
  }
74633
74569
  );
74634
74570
  }
@@ -74809,7 +74745,7 @@ function useCaptionSync(projectId) {
74809
74745
  suppressSaveRef.current = true;
74810
74746
  useCaptionStore.getState().setModel({ ...model, segments: newSegments });
74811
74747
  } catch {
74812
- useCaptionStore.getState().setSyncError("caption-overrides.json is corrupt \u2014 earlier caption edits didn't load");
74748
+ useCaptionStore.getState().setSyncError("caption-overrides.json is corrupt, so earlier caption edits didn't load");
74813
74749
  }
74814
74750
  }, []);
74815
74751
  return { save, loadOverrides };
@@ -75268,7 +75204,7 @@ function useEditorSave({
75268
75204
  if (lastFailureToastAtRef.current === null || now2 - lastFailureToastAtRef.current >= FAILURE_BURST_MS) {
75269
75205
  lastFailureToastAtRef.current = now2;
75270
75206
  showToast(
75271
- `Couldn't save ${path} \u2014 your latest edits are NOT persisted. Check the preview server; editing again retries the save.`,
75207
+ `Couldn't save ${path}. Your latest edits are NOT persisted. Check the preview server; editing again retries the save.`,
75272
75208
  "error"
75273
75209
  );
75274
75210
  }
@@ -76386,7 +76322,6 @@ function buildCutTarget(element, target, splitTime) {
76386
76322
  elementDuration: element.duration,
76387
76323
  ...element.playbackStart != null ? { playbackStart: element.playbackStart } : {},
76388
76324
  ...element.playbackRate != null ? { playbackRate: element.playbackRate } : {},
76389
- ...element.kind === "composition" ? { isComposition: true } : {},
76390
76325
  // Pin both halves to the current track: unstamped, the runtime's positional
76391
76326
  // fallback (parseAuthoredTrack) renumbers the new sibling onto a new row.
76392
76327
  track: resolveElementTrack(element)
@@ -78409,7 +78344,7 @@ async function createAudioGroupAndAssignMembers({
78409
78344
  pendingTimelineEditPathRef
78410
78345
  }) {
78411
78346
  if (elements.length < 2) {
78412
- throw new Error(`Cannot group ${elements.length} clip(s) \u2014 a group needs at least two`);
78347
+ throw new Error(`Cannot group ${elements.length} clip(s): a group needs at least two`);
78413
78348
  }
78414
78349
  if (!GROUP_ID_PATTERN.test(groupId)) {
78415
78350
  throw new Error(`Invalid audio group id ${JSON.stringify(groupId)}`);
@@ -83315,7 +83250,7 @@ function showUnchangedMutationFeedback(mutations, result, showToast) {
83315
83250
  async function runMutationRequest(mutations, options, showToast, request2) {
83316
83251
  const unsafeFields = mutations.flatMap((mutation) => findUnsafeMutationValues(mutation));
83317
83252
  if (unsafeFields.length > 0) {
83318
- showToast?.("Couldn't read element layout \u2014 try again at a different playhead time", "error");
83253
+ showToast?.("Couldn't read element layout. Try again at a different playhead time", "error");
83319
83254
  if (options.skipReload) return;
83320
83255
  throw markStudioSaveErrorAlreadyToasted(
83321
83256
  new Error(
@@ -86419,7 +86354,7 @@ function useDomEditSession({
86419
86354
  }
86420
86355
  if (members.some((m) => isAudioDomElement(m.element))) {
86421
86356
  showToast(
86422
- members.every((m) => isAudioDomElement(m.element)) ? "Audio clips group into a bus \u2014 use FX on the track header" : "Can't group audio clips with layout elements",
86357
+ members.every((m) => isAudioDomElement(m.element)) ? "Audio clips group into a bus. Use FX on the track header" : "Can't group audio clips with layout elements",
86423
86358
  "info"
86424
86359
  );
86425
86360
  return;
@@ -87716,7 +87651,7 @@ function useBlockHandlers({
87716
87651
  const runBlockInstall = useCallback152(
87717
87652
  async (blockName, install) => {
87718
87653
  if (installingBlockRef.current) {
87719
- showToast("A block is already installing \u2014 one moment\u2026", "info");
87654
+ showToast("A block is already installing. One moment\u2026", "info");
87720
87655
  return null;
87721
87656
  }
87722
87657
  installingBlockRef.current = true;
@@ -89207,9 +89142,9 @@ function useConsoleErrorCapture(previewIframe) {
89207
89142
  }
89208
89143
  };
89209
89144
  attachErrorCapture();
89210
- previewIframe.addEventListener("load", attachErrorCapture);
89145
+ const stopLoaded = onPreviewDocumentLoaded(previewIframe, attachErrorCapture);
89211
89146
  return () => {
89212
- previewIframe.removeEventListener("load", attachErrorCapture);
89147
+ stopLoaded();
89213
89148
  detachErrorCapture();
89214
89149
  };
89215
89150
  }, [previewIframe, resetErrors]);
@@ -89295,7 +89230,7 @@ function useFrameCapture({
89295
89230
  } catch (fetchErr) {
89296
89231
  clearTimeout(timeout);
89297
89232
  if (fetchErr instanceof DOMException && fetchErr.name === "AbortError") {
89298
- throw new Error("Capture timed out \u2014 the server took too long to respond");
89233
+ throw new Error("Capture timed out: the server took too long to respond");
89299
89234
  }
89300
89235
  throw fetchErr;
89301
89236
  }
@@ -91004,11 +90939,11 @@ function useGestureCommit({
91004
90939
  }
91005
90940
  const duration = frozenSamples.length > 0 ? frozenSamples[frozenSamples.length - 1]?.time ?? 0 : 0;
91006
90941
  if (frozenSamples.length <= 2) {
91007
- showToast("No gesture detected \u2014 move the pointer while recording", "error");
90942
+ showToast("No gesture detected. Move the pointer while recording", "error");
91008
90943
  return;
91009
90944
  }
91010
90945
  if (duration <= 0) {
91011
- showToast("Recording too short \u2014 try again", "error");
90946
+ showToast("Recording too short. Try again", "error");
91012
90947
  return;
91013
90948
  }
91014
90949
  const simplified = simplifyGestureSamples(frozenSamples, duration, (key2) => {
@@ -91023,7 +90958,7 @@ function useGestureCommit({
91023
90958
  }
91024
90959
  const selector = sel.id ? idSelector(sel.id) : sel.selector;
91025
90960
  if (!selector) {
91026
- showToast("Cannot save \u2014 element has no selector", "error");
90961
+ showToast("Cannot save: element has no selector", "error");
91027
90962
  return;
91028
90963
  }
91029
90964
  const writeSelector = writeTargetSelector(sel);
@@ -91191,7 +91126,7 @@ function useGestureCommit({
91191
91126
  }
91192
91127
  const iframe = previewIframeRef.current;
91193
91128
  if (!iframe) {
91194
- showToast("Preview not ready \u2014 try again", "error");
91129
+ showToast("Preview not ready. Try again", "error");
91195
91130
  return;
91196
91131
  }
91197
91132
  const store = usePlayerStore.getState();
@@ -91406,7 +91341,7 @@ function MediaErrorPanel({ name, filePath }) {
91406
91341
  ),
91407
91342
  /* @__PURE__ */ jsx212("span", { className: "text-sm text-neutral-400 font-medium", children: name }),
91408
91343
  /* @__PURE__ */ jsx212("span", { className: "text-[11px] text-neutral-600 font-mono", children: filePath }),
91409
- /* @__PURE__ */ jsx212("span", { className: "text-[10px] text-neutral-500", children: "Couldn't load this file \u2014 it may be missing or corrupt" })
91344
+ /* @__PURE__ */ jsx212("span", { className: "text-[10px] text-neutral-500", children: "Couldn't load this file. It may be missing or corrupt" })
91410
91345
  ] });
91411
91346
  }
91412
91347
  function MediaPreview({ projectId, filePath }) {
@@ -91493,7 +91428,7 @@ function MediaPreview({ projectId, filePath }) {
91493
91428
  ),
91494
91429
  /* @__PURE__ */ jsx212("span", { className: "text-sm text-neutral-400 font-medium", children: name }),
91495
91430
  /* @__PURE__ */ jsx212("span", { className: "text-[11px] text-neutral-600 font-mono", children: filePath }),
91496
- /* @__PURE__ */ jsx212("span", { className: "text-[10px] text-neutral-600", children: "Binary file \u2014 preview not available" })
91431
+ /* @__PURE__ */ jsx212("span", { className: "text-[10px] text-neutral-600", children: "Binary file: preview not available" })
91497
91432
  ] });
91498
91433
  }
91499
91434
 
@@ -92019,7 +91954,7 @@ function AudioRow({
92019
91954
  onPointerUp: handlePointerUp,
92020
91955
  role: "button",
92021
91956
  tabIndex: 0,
92022
- "aria-label": `${name} \u2014 open, drag to timeline, right-click for actions`,
91957
+ "aria-label": `${name}: open, drag to timeline, right-click for actions`,
92023
91958
  onKeyDown: (e) => {
92024
91959
  if (e.target !== e.currentTarget) return;
92025
91960
  if (e.key === "Enter" || e.key === " ") {
@@ -92358,7 +92293,7 @@ function AssetCard({
92358
92293
  draggable: true,
92359
92294
  role: "button",
92360
92295
  tabIndex: 0,
92361
- "aria-label": `${name} \u2014 open, drag to timeline, right-click for actions`,
92296
+ "aria-label": `${name}: open, drag to timeline, right-click for actions`,
92362
92297
  onPointerDown: handlePointerDown,
92363
92298
  onPointerUp: handlePointerUp,
92364
92299
  onKeyDown: (e) => {
@@ -92452,7 +92387,7 @@ function FontRow({
92452
92387
  draggable: true,
92453
92388
  role: "button",
92454
92389
  tabIndex: 0,
92455
- "aria-label": `${name} \u2014 copy path, drag to timeline, right-click for actions`,
92390
+ "aria-label": `${name}: copy path, drag to timeline, right-click for actions`,
92456
92391
  onClick: () => onCopy(asset),
92457
92392
  onKeyDown: (e) => {
92458
92393
  if (e.target !== e.currentTarget) return;
@@ -92953,7 +92888,7 @@ function PromptPreviewModal({
92953
92888
  ref: containerRef,
92954
92889
  role: "dialog",
92955
92890
  "aria-modal": "true",
92956
- "aria-label": `Ask agent \u2014 ${title}`,
92891
+ "aria-label": `Ask agent: ${title}`,
92957
92892
  tabIndex: -1,
92958
92893
  className: "w-[560px] max-h-[80vh] flex flex-col rounded-2xl border border-neutral-800 bg-neutral-950 shadow-2xl outline-hidden",
92959
92894
  onClick: (e) => e.stopPropagation(),
@@ -93144,7 +93079,7 @@ function formatCompositionContext(ctx3) {
93144
93079
  "",
93145
93080
  `Elements visible at ${formatTime(ctx3.currentTime)}:`,
93146
93081
  ...visibleNow.map(
93147
- (el) => `- ${el.label || el.id} (track ${el.track}, ${formatTime(el.start)}\u2013${formatTime(el.start + el.duration)}${el.compositionSrc ? `, src: ${el.compositionSrc}` : ""})`
93082
+ (el) => `- ${el.label || el.id} (track ${el.track}, ${formatTime(el.start)} to ${formatTime(el.start + el.duration)}${el.compositionSrc ? `, src: ${el.compositionSrc}` : ""})`
93148
93083
  )
93149
93084
  );
93150
93085
  }
@@ -93170,7 +93105,7 @@ function buildAgentPrompt(title, name, description, category, blockType, context
93170
93105
  transitions: [
93171
93106
  `Using /hyperframes, add the "${title}" transition (registry: ${name}) between my scenes.`,
93172
93107
  `${description}`,
93173
- `Place this transition at the cut point between the current scene and the next. Set the duration to 0.5\u20131s, position it at the scene boundary on the timeline, and make sure the z-index is above both scenes. Adjust colors to match my palette.`
93108
+ `Place this transition at the cut point between the current scene and the next. Set the duration to 0.5 to 1s, position it at the scene boundary on the timeline, and make sure the z-index is above both scenes. Adjust colors to match my palette.`
93174
93109
  ].join("\n\n"),
93175
93110
  effects: [
93176
93111
  `Using /hyperframes, add the "${title}" effect (registry: ${name}) as an overlay on my composition.`,
@@ -93671,8 +93606,8 @@ function CompCard({
93671
93606
  isRoot && /* @__PURE__ */ jsx222(
93672
93607
  "span",
93673
93608
  {
93674
- "aria-label": "Root composition \u2014 opens automatically on load",
93675
- title: "Root composition \u2014 opens automatically on load",
93609
+ "aria-label": "Root composition: opens automatically on load",
93610
+ title: "Root composition: opens automatically on load",
93676
93611
  className: "flex-shrink-0 rounded-full bg-neutral-700/60 px-1.5 py-px text-[8px] font-bold uppercase tracking-wide text-neutral-300",
93677
93612
  children: "Root"
93678
93613
  }
@@ -93848,16 +93783,17 @@ function getPersistedRenderSettings() {
93848
93783
  return {
93849
93784
  format: ["mp4", "webm", "mov"].includes(parsed.format) ? parsed.format : "mp4",
93850
93785
  quality: ["draft", "standard", "high"].includes(parsed.quality) ? parsed.quality : "standard",
93851
- 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
93852
93788
  };
93853
93789
  }
93854
93790
  } catch {
93855
93791
  }
93856
- return { format: "mp4", quality: "standard", fps: 30 };
93792
+ return { format: "mp4", quality: "standard", fps: 30, gpu: false };
93857
93793
  }
93858
- function persistRenderSettings(format, quality, fps) {
93794
+ function persistRenderSettings(format, quality, fps, gpu = getPersistedRenderSettings().gpu) {
93859
93795
  try {
93860
- localStorage.setItem(RENDER_SETTINGS_KEY, JSON.stringify({ format, quality, fps }));
93796
+ localStorage.setItem(RENDER_SETTINGS_KEY, JSON.stringify({ format, quality, fps, gpu }));
93861
93797
  } catch {
93862
93798
  }
93863
93799
  }
@@ -93906,8 +93842,8 @@ var StudioLeftPanels = memo50(function StudioLeftPanels2({
93906
93842
  return;
93907
93843
  }
93908
93844
  await waitForPendingDomEditSaves();
93909
- const { format, quality, fps } = getPersistedRenderSettings();
93910
- 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 });
93911
93847
  },
93912
93848
  [renderQueue, waitForPendingDomEditSaves]
93913
93849
  );
@@ -95200,7 +95136,7 @@ var BlockParamsPanel = memo54(function BlockParamsPanel2({
95200
95136
  if (matches === 0) {
95201
95137
  setCommitState({
95202
95138
  tone: "error",
95203
- message: `Couldn't find the current value in ${compositionPath} \u2014 it may have been edited by hand.`
95139
+ message: `Couldn't find the current value in ${compositionPath}. It may have been edited by hand.`
95204
95140
  });
95205
95141
  trackBlockParamCommit({ tone: "error", blockName, key: key2 });
95206
95142
  return;
@@ -95208,7 +95144,7 @@ var BlockParamsPanel = memo54(function BlockParamsPanel2({
95208
95144
  if (matches > 1) {
95209
95145
  setCommitState({
95210
95146
  tone: "error",
95211
- message: `"${previous}" appears ${matches}\xD7 in ${compositionPath} \u2014 the panel can't tell which one belongs to this parameter, so it won't risk changing unrelated content. Edit the file directly to disambiguate.`
95147
+ message: `"${previous}" appears ${matches}\xD7 in ${compositionPath}. The panel can't tell which one belongs to this parameter, so it won't risk changing unrelated content. Edit the file directly to disambiguate.`
95212
95148
  });
95213
95149
  trackBlockParamCommit({ tone: "error", blockName, key: key2 });
95214
95150
  return;
@@ -95297,7 +95233,7 @@ var BlockParamsPanel = memo54(function BlockParamsPanel2({
95297
95233
  /* @__PURE__ */ jsxs182("div", { className: "flex-1 overflow-y-auto p-3 space-y-3", children: [
95298
95234
  /* @__PURE__ */ jsx230("div", { className: "text-[9px] font-medium text-neutral-500 uppercase tracking-wider", children: "Parameters" }),
95299
95235
  params.length === 0 && /* @__PURE__ */ jsx230("div", { className: "text-[10px] text-neutral-500", children: "This block has no editable parameters." }),
95300
- !fileManager && params.length > 0 && /* @__PURE__ */ jsx230("div", { className: "text-[10px] text-warning-ink", children: "Block params can't be edited here \u2014 no project file access." }),
95236
+ !fileManager && params.length > 0 && /* @__PURE__ */ jsx230("div", { className: "text-[10px] text-warning-ink", children: "Block params can't be edited here: no project file access." }),
95301
95237
  params.map((param) => /* @__PURE__ */ jsx230(
95302
95238
  ParamControl,
95303
95239
  {
@@ -95713,8 +95649,38 @@ var FfmpegRequiredNotice = memo56(function FfmpegRequiredNotice2({
95713
95649
  );
95714
95650
  });
95715
95651
 
95716
- // src/components/renders/RenderQueue.tsx
95652
+ // src/components/renders/GpuEncodingToggle.tsx
95717
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";
95718
95684
  var SCALE_OPTION_ORDER = ["auto", "1080p", "4k"];
95719
95685
  var SCALE_LABEL = {
95720
95686
  auto: "Auto",
@@ -95750,7 +95716,7 @@ function scaleOptionLabel(scale, dims) {
95750
95716
  const resolved2 = resolvedDimensions(scale, dims);
95751
95717
  const base2 = resolved2 ? `${SCALE_LABEL[scale]} \xB7 ${resolved2.width}\xD7${resolved2.height}` : SCALE_LABEL[scale];
95752
95718
  if (dims && !scaleApplies(scale, dims)) {
95753
- return `${base2} \u2014 not an integer scale of ${dims.width}\xD7${dims.height}`;
95719
+ return `${base2} (not an integer scale of ${dims.width}\xD7${dims.height})`;
95754
95720
  }
95755
95721
  return base2;
95756
95722
  }
@@ -95802,8 +95768,8 @@ function FormatInfoTooltip({ format }) {
95802
95768
  return () => document.removeEventListener("keydown", onKeyDown);
95803
95769
  }, [open]);
95804
95770
  const info = FORMAT_INFO[format];
95805
- return /* @__PURE__ */ jsxs185("div", { ref: triggerRef, className: "relative", onPointerEnter: show, onPointerLeave: hide, children: [
95806
- /* @__PURE__ */ jsx233(
95771
+ return /* @__PURE__ */ jsxs186("div", { ref: triggerRef, className: "relative", onPointerEnter: show, onPointerLeave: hide, children: [
95772
+ /* @__PURE__ */ jsx234(
95807
95773
  "button",
95808
95774
  {
95809
95775
  type: "button",
@@ -95814,7 +95780,7 @@ function FormatInfoTooltip({ format }) {
95814
95780
  onBlur: hide,
95815
95781
  onClick: () => setOpen((prev) => !prev),
95816
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",
95817
- children: /* @__PURE__ */ jsxs185(
95783
+ children: /* @__PURE__ */ jsxs186(
95818
95784
  "svg",
95819
95785
  {
95820
95786
  width: "12",
@@ -95827,16 +95793,16 @@ function FormatInfoTooltip({ format }) {
95827
95793
  strokeLinejoin: "round",
95828
95794
  "aria-hidden": "true",
95829
95795
  children: [
95830
- /* @__PURE__ */ jsx233("circle", { cx: "12", cy: "12", r: "10" }),
95831
- /* @__PURE__ */ jsx233("path", { d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" }),
95832
- /* @__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" })
95833
95799
  ]
95834
95800
  }
95835
95801
  )
95836
95802
  }
95837
95803
  ),
95838
95804
  open && createPortal14(
95839
- /* @__PURE__ */ jsxs185(
95805
+ /* @__PURE__ */ jsxs186(
95840
95806
  "div",
95841
95807
  {
95842
95808
  id: panelId,
@@ -95846,11 +95812,11 @@ function FormatInfoTooltip({ format }) {
95846
95812
  className: "fixed w-52 p-2 rounded-sm bg-input border border-border-input shadow-menu z-200",
95847
95813
  style: { left: position?.left ?? -9999, top: position?.top ?? -9999 },
95848
95814
  children: [
95849
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 font-semibold text-text-1 mb-0.5", children: info.label }),
95850
- /* @__PURE__ */ jsx233("p", { className: "text-step-9 text-text-3 leading-tight", children: info.desc }),
95851
- /* @__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: [
95852
- /* @__PURE__ */ jsx233("span", { className: "text-text-3 font-medium", children: FORMAT_INFO[f].label }),
95853
- " \u2014 ",
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 }),
95819
+ ": ",
95854
95820
  FORMAT_INFO[f].desc
95855
95821
  ] }, f)) })
95856
95822
  ]
@@ -95893,10 +95859,11 @@ function FormatExportButton({
95893
95859
  const [quality, setQuality] = useState153(persisted.quality);
95894
95860
  const [resolution, setResolution] = useState153("auto");
95895
95861
  const [fps, setFps] = useState153(persisted.fps);
95862
+ const [gpu, setGpu] = useState153(persisted.gpu);
95896
95863
  const missingFfmpeg = ffmpeg && !ffmpeg.ok ? ffmpeg : null;
95897
95864
  const showQuality = format !== "mov";
95898
- return /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-3", children: [
95899
- missingFfmpeg && /* @__PURE__ */ jsx233(
95865
+ return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3", children: [
95866
+ missingFfmpeg && /* @__PURE__ */ jsx234(
95900
95867
  FfmpegRequiredNotice,
95901
95868
  {
95902
95869
  status: missingFfmpeg,
@@ -95904,13 +95871,13 @@ function FormatExportButton({
95904
95871
  onRecheck: onRecheckFfmpeg
95905
95872
  }
95906
95873
  ),
95907
- /* @__PURE__ */ jsxs185("div", { className: "grid grid-cols-2 gap-2", children: [
95908
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95909
- /* @__PURE__ */ jsxs185("div", { className: "flex items-center gap-1", children: [
95910
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Format" }),
95911
- /* @__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 })
95912
95879
  ] }),
95913
- /* @__PURE__ */ jsx233(
95880
+ /* @__PURE__ */ jsx234(
95914
95881
  Select,
95915
95882
  {
95916
95883
  label: "Format",
@@ -95925,9 +95892,9 @@ function FormatExportButton({
95925
95892
  }
95926
95893
  )
95927
95894
  ] }),
95928
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95929
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Resolution" }),
95930
- /* @__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(
95931
95898
  Select,
95932
95899
  {
95933
95900
  label: "Resolution",
@@ -95942,9 +95909,9 @@ function FormatExportButton({
95942
95909
  }
95943
95910
  )
95944
95911
  ] }),
95945
- /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95946
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Frame rate" }),
95947
- /* @__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(
95948
95915
  Select,
95949
95916
  {
95950
95917
  label: "Frame rate",
@@ -95959,9 +95926,9 @@ function FormatExportButton({
95959
95926
  }
95960
95927
  )
95961
95928
  ] }),
95962
- showQuality && /* @__PURE__ */ jsxs185("div", { className: "flex flex-col gap-1", children: [
95963
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-text-4", children: "Quality" }),
95964
- /* @__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(
95965
95932
  Select,
95966
95933
  {
95967
95934
  label: "Quality",
@@ -95977,7 +95944,19 @@ function FormatExportButton({
95977
95944
  )
95978
95945
  ] })
95979
95946
  ] }),
95980
- /* @__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(
95981
95960
  Button,
95982
95961
  {
95983
95962
  variant: "primary",
@@ -95990,13 +95969,13 @@ function FormatExportButton({
95990
95969
  if (isRendering || missingFfmpeg) return;
95991
95970
  const outputResolution = resolveResolution(resolution, compositionDimensions);
95992
95971
  trackStudioEvent("render_start", { format, quality, resolution: outputResolution, fps });
95993
- void onStartRender(format, quality, outputResolution, fps);
95972
+ void onStartRender(format, quality, outputResolution, fps, gpu);
95994
95973
  },
95995
95974
  className: "w-full",
95996
95975
  children: isRendering ? "Rendering\u2026" : "Export"
95997
95976
  }
95998
95977
  ),
95999
- 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: [
96000
95979
  "Last render took ",
96001
95980
  formatEta(lastRenderDurationMs)
96002
95981
  ] })
@@ -96027,8 +96006,8 @@ var RenderQueue = memo57(function RenderQueue2({
96027
96006
  }, [jobs.length]);
96028
96007
  const completedCount = jobs.filter((j) => j.status !== "rendering").length;
96029
96008
  const lastRenderDurationMs = [...jobs].reverse().find((j) => j.status === "complete" && j.durationMs !== void 0)?.durationMs;
96030
- return /* @__PURE__ */ jsxs185("div", { className: "flex flex-col h-full", children: [
96031
- /* @__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(
96032
96011
  FormatExportButton,
96033
96012
  {
96034
96013
  onStartRender,
@@ -96040,21 +96019,21 @@ var RenderQueue = memo57(function RenderQueue2({
96040
96019
  onRecheckFfmpeg
96041
96020
  }
96042
96021
  ) }),
96043
- actionError && /* @__PURE__ */ jsxs185(
96022
+ actionError && /* @__PURE__ */ jsxs186(
96044
96023
  "div",
96045
96024
  {
96046
96025
  role: "alert",
96047
96026
  className: "flex items-start justify-between gap-2 px-3 py-2 border-b border-border bg-danger/10",
96048
96027
  children: [
96049
- /* @__PURE__ */ jsx233("span", { className: "text-step-10 text-danger-ink", children: actionError }),
96050
- onDismissActionError && /* @__PURE__ */ jsx233(
96028
+ /* @__PURE__ */ jsx234("span", { className: "text-step-10 text-danger-ink", children: actionError }),
96029
+ onDismissActionError && /* @__PURE__ */ jsx234(
96051
96030
  IconButton,
96052
96031
  {
96053
96032
  size: "sm",
96054
96033
  onClick: onDismissActionError,
96055
96034
  "aria-label": "Dismiss error",
96056
96035
  className: "shrink-0",
96057
- icon: /* @__PURE__ */ jsx233(
96036
+ icon: /* @__PURE__ */ jsx234(
96058
96037
  "svg",
96059
96038
  {
96060
96039
  width: "10",
@@ -96065,7 +96044,7 @@ var RenderQueue = memo57(function RenderQueue2({
96065
96044
  strokeWidth: "2",
96066
96045
  strokeLinecap: "round",
96067
96046
  "aria-hidden": "true",
96068
- children: /* @__PURE__ */ jsx233("path", { d: "M18 6L6 18M6 6l12 12" })
96047
+ children: /* @__PURE__ */ jsx234("path", { d: "M18 6L6 18M6 6l12 12" })
96069
96048
  }
96070
96049
  )
96071
96050
  }
@@ -96073,11 +96052,11 @@ var RenderQueue = memo57(function RenderQueue2({
96073
96052
  ]
96074
96053
  }
96075
96054
  ),
96076
- /* @__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: [
96077
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 text-danger-ink text-center", children: loadError }),
96078
- onRetryLoad && /* @__PURE__ */ jsx233(Button, { size: "sm", variant: "secondary", onClick: onRetryLoad, children: "Retry" })
96079
- ] }) : jobs.length === 0 ? /* @__PURE__ */ jsxs185("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-2", children: [
96080
- /* @__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(
96081
96060
  "svg",
96082
96061
  {
96083
96062
  width: "20",
@@ -96088,7 +96067,7 @@ var RenderQueue = memo57(function RenderQueue2({
96088
96067
  strokeWidth: "1.5",
96089
96068
  className: "text-text-5",
96090
96069
  children: [
96091
- /* @__PURE__ */ jsx233(
96070
+ /* @__PURE__ */ jsx234(
96092
96071
  "rect",
96093
96072
  {
96094
96073
  x: "2",
@@ -96101,7 +96080,7 @@ var RenderQueue = memo57(function RenderQueue2({
96101
96080
  strokeLinejoin: "round"
96102
96081
  }
96103
96082
  ),
96104
- /* @__PURE__ */ jsx233(
96083
+ /* @__PURE__ */ jsx234(
96105
96084
  "path",
96106
96085
  {
96107
96086
  d: "M7 2v20M17 2v20M2 12h20M2 7h5M2 17h5M17 17h5M17 7h5",
@@ -96112,17 +96091,17 @@ var RenderQueue = memo57(function RenderQueue2({
96112
96091
  ]
96113
96092
  }
96114
96093
  ),
96115
- /* @__PURE__ */ jsx233("p", { className: "text-step-10 text-text-5 text-center", children: "No renders yet" })
96116
- ] }) : /* @__PURE__ */ jsxs185("div", { children: [
96117
- completedCount > 0 && /* @__PURE__ */ jsxs185("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border", children: [
96118
- /* @__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: [
96119
96098
  jobs.length,
96120
96099
  " render",
96121
96100
  jobs.length === 1 ? "" : "s"
96122
96101
  ] }),
96123
- /* @__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" }) })
96124
96103
  ] }),
96125
- jobs.map((job) => /* @__PURE__ */ jsx233(
96104
+ jobs.map((job) => /* @__PURE__ */ jsx234(
96126
96105
  RenderQueueItem,
96127
96106
  {
96128
96107
  job,
@@ -96137,10 +96116,10 @@ var RenderQueue = memo57(function RenderQueue2({
96137
96116
  });
96138
96117
 
96139
96118
  // src/components/renders/RenderQueuePanel.tsx
96140
- import { jsx as jsx234 } from "react/jsx-runtime";
96119
+ import { jsx as jsx235 } from "react/jsx-runtime";
96141
96120
  var RenderQueuePanel = memo58(function RenderQueuePanel2() {
96142
96121
  const { projectId, compositionDimensions, waitForPendingDomEditSaves, renderQueue } = useStudioShellContext();
96143
- return /* @__PURE__ */ jsx234(
96122
+ return /* @__PURE__ */ jsx235(
96144
96123
  RenderQueue,
96145
96124
  {
96146
96125
  jobs: renderQueue.jobs,
@@ -96155,13 +96134,14 @@ var RenderQueuePanel = memo58(function RenderQueuePanel2() {
96155
96134
  ffmpeg: renderQueue.ffmpeg,
96156
96135
  ffmpegChecking: renderQueue.ffmpegChecking,
96157
96136
  onRecheckFfmpeg: renderQueue.recheckFfmpeg,
96158
- onStartRender: async (format, quality, resolution, fps) => {
96137
+ onStartRender: async (format, quality, resolution, fps, gpu) => {
96159
96138
  await waitForPendingDomEditSaves();
96160
96139
  await renderQueue.startRender({
96161
96140
  fps,
96162
96141
  quality,
96163
96142
  format,
96164
96143
  resolution,
96144
+ gpu,
96165
96145
  // Render what the user is previewing: active variable overrides
96166
96146
  // from the Variables panel ride along (undefined = defaults).
96167
96147
  variables: usePreviewVariablesStore.getState().values ?? void 0
@@ -96179,13 +96159,13 @@ import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
96179
96159
 
96180
96160
  // src/components/panels/SlideshowSubPanels.tsx
96181
96161
  import { useState as useState154, useCallback as useCallback178, useId as useId6 } from "react";
96182
- import { jsx as jsx235, jsxs as jsxs186 } from "react/jsx-runtime";
96162
+ import { jsx as jsx236, jsxs as jsxs187 } from "react/jsx-runtime";
96183
96163
  function SectionHeader({
96184
96164
  children,
96185
96165
  expanded,
96186
96166
  onToggle
96187
96167
  }) {
96188
- return /* @__PURE__ */ jsxs186(
96168
+ return /* @__PURE__ */ jsxs187(
96189
96169
  "button",
96190
96170
  {
96191
96171
  type: "button",
@@ -96193,8 +96173,8 @@ function SectionHeader({
96193
96173
  onClick: onToggle,
96194
96174
  "aria-expanded": expanded,
96195
96175
  children: [
96196
- /* @__PURE__ */ jsx235("span", { children }),
96197
- /* @__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" })
96198
96178
  ]
96199
96179
  }
96200
96180
  );
@@ -96212,12 +96192,12 @@ function SlideList({
96212
96192
  const orderedSlideScenes = slides.map((sl) => sceneById.get(sl.sceneId)).filter((s) => s !== void 0);
96213
96193
  const nonSlideScenes = scenes.filter((sc) => !slideIds.has(sc.id));
96214
96194
  const rows = [...orderedSlideScenes, ...nonSlideScenes];
96215
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-px", children: [
96195
+ return /* @__PURE__ */ jsxs187("div", { className: "flex flex-col gap-px", children: [
96216
96196
  rows.map((scene) => {
96217
96197
  const isSlide = slideIds.has(scene.id);
96218
96198
  const isSelected2 = selectedSceneId === scene.id;
96219
96199
  const slideIndex = slides.findIndex((s) => s.sceneId === scene.id);
96220
- return /* @__PURE__ */ jsxs186(
96200
+ return /* @__PURE__ */ jsxs187(
96221
96201
  "div",
96222
96202
  {
96223
96203
  role: "button",
@@ -96232,7 +96212,7 @@ function SlideList({
96232
96212
  }
96233
96213
  },
96234
96214
  children: [
96235
- /* @__PURE__ */ jsx235(
96215
+ /* @__PURE__ */ jsx236(
96236
96216
  "input",
96237
96217
  {
96238
96218
  type: "checkbox",
@@ -96243,9 +96223,9 @@ function SlideList({
96243
96223
  className: "accent-studio-accent shrink-0"
96244
96224
  }
96245
96225
  ),
96246
- /* @__PURE__ */ jsx235("span", { className: "flex-1 truncate", children: scene.label || scene.id }),
96247
- isSlide && /* @__PURE__ */ jsxs186("span", { className: "flex gap-0.5 shrink-0", children: [
96248
- /* @__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(
96249
96229
  "button",
96250
96230
  {
96251
96231
  type: "button",
@@ -96260,7 +96240,7 @@ function SlideList({
96260
96240
  children: "\u25B2"
96261
96241
  }
96262
96242
  ),
96263
- /* @__PURE__ */ jsx235(
96243
+ /* @__PURE__ */ jsx236(
96264
96244
  "button",
96265
96245
  {
96266
96246
  type: "button",
@@ -96281,7 +96261,7 @@ function SlideList({
96281
96261
  scene.id
96282
96262
  );
96283
96263
  }),
96284
- 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" })
96285
96265
  ] });
96286
96266
  }
96287
96267
  function SlideInspector({
@@ -96293,14 +96273,14 @@ function SlideInspector({
96293
96273
  onRemoveFragment
96294
96274
  }) {
96295
96275
  const fragments = slide?.fragments ?? [];
96296
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96297
- /* @__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: [
96298
96278
  "Scene: ",
96299
96279
  sceneId
96300
96280
  ] }),
96301
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1", children: [
96302
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Notes" }),
96303
- /* @__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(
96304
96284
  "textarea",
96305
96285
  {
96306
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",
@@ -96311,10 +96291,10 @@ function SlideInspector({
96311
96291
  }
96312
96292
  )
96313
96293
  ] }),
96314
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96315
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center justify-between", children: [
96316
- /* @__PURE__ */ jsx235("span", { className: "text-[11px] text-neutral-400", children: "Fragment hold-points" }),
96317
- /* @__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(
96318
96298
  "button",
96319
96299
  {
96320
96300
  type: "button",
@@ -96329,14 +96309,14 @@ function SlideInspector({
96329
96309
  }
96330
96310
  )
96331
96311
  ] }),
96332
- 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(
96333
96313
  "span",
96334
96314
  {
96335
96315
  className: "inline-flex items-center gap-1 bg-neutral-700 rounded-sm px-1.5 py-0.5 text-[10px] text-neutral-200",
96336
96316
  children: [
96337
96317
  t2.toFixed(2),
96338
96318
  "s",
96339
- /* @__PURE__ */ jsx235(
96319
+ /* @__PURE__ */ jsx236(
96340
96320
  "button",
96341
96321
  {
96342
96322
  type: "button",
@@ -96349,7 +96329,7 @@ function SlideInspector({
96349
96329
  ]
96350
96330
  },
96351
96331
  `frag-${i}`
96352
- )) }) : /* @__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" })
96353
96333
  ] })
96354
96334
  ] });
96355
96335
  }
@@ -96372,9 +96352,9 @@ function BranchTree({
96372
96352
  onCreateSequence(label);
96373
96353
  setNewLabel("");
96374
96354
  }, [newLabel, onCreateSequence]);
96375
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96376
- /* @__PURE__ */ jsxs186("div", { className: "flex gap-1.5", children: [
96377
- /* @__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(
96378
96358
  "input",
96379
96359
  {
96380
96360
  id: inputId,
@@ -96389,7 +96369,7 @@ function BranchTree({
96389
96369
  "aria-label": "New branch sequence name"
96390
96370
  }
96391
96371
  ),
96392
- /* @__PURE__ */ jsx235(
96372
+ /* @__PURE__ */ jsx236(
96393
96373
  "button",
96394
96374
  {
96395
96375
  type: "button",
@@ -96399,7 +96379,7 @@ function BranchTree({
96399
96379
  }
96400
96380
  )
96401
96381
  ] }),
96402
- 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(
96403
96383
  BranchItem,
96404
96384
  {
96405
96385
  seq,
@@ -96433,9 +96413,9 @@ function BranchItem({
96433
96413
  if (label && label !== seq.label) onRename(seq.id, label);
96434
96414
  setEditing(false);
96435
96415
  }, [draft, onRename, seq.id, seq.label]);
96436
- return /* @__PURE__ */ jsxs186("div", { className: "border border-neutral-700/60 rounded-sm p-2 flex flex-col gap-2", children: [
96437
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center gap-1", children: [
96438
- 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(
96439
96419
  "input",
96440
96420
  {
96441
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",
@@ -96449,7 +96429,7 @@ function BranchItem({
96449
96429
  },
96450
96430
  "aria-label": `Rename branch ${seq.label}`
96451
96431
  }
96452
- ) : /* @__PURE__ */ jsx235(
96432
+ ) : /* @__PURE__ */ jsx236(
96453
96433
  "span",
96454
96434
  {
96455
96435
  role: "button",
@@ -96466,7 +96446,7 @@ function BranchItem({
96466
96446
  children: seq.label
96467
96447
  }
96468
96448
  ),
96469
- /* @__PURE__ */ jsx235(
96449
+ /* @__PURE__ */ jsx236(
96470
96450
  "button",
96471
96451
  {
96472
96452
  type: "button",
@@ -96477,16 +96457,16 @@ function BranchItem({
96477
96457
  }
96478
96458
  )
96479
96459
  ] }),
96480
- 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: [
96481
- /* @__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: [
96482
96462
  "Delete branch \u201C",
96483
96463
  seq.label,
96484
96464
  "\u201D",
96485
96465
  seq.slides.length > 0 ? ` and its ${seq.slides.length} slide${seq.slides.length === 1 ? "" : "s"}` : "",
96486
96466
  "? Hotspots pointing to it will no longer resolve."
96487
96467
  ] }),
96488
- /* @__PURE__ */ jsxs186("div", { className: "flex items-center justify-end gap-1", children: [
96489
- /* @__PURE__ */ jsx235(
96468
+ /* @__PURE__ */ jsxs187("div", { className: "flex items-center justify-end gap-1", children: [
96469
+ /* @__PURE__ */ jsx236(
96490
96470
  "button",
96491
96471
  {
96492
96472
  type: "button",
@@ -96498,7 +96478,7 @@ function BranchItem({
96498
96478
  children: "Delete"
96499
96479
  }
96500
96480
  ),
96501
- /* @__PURE__ */ jsx235(
96481
+ /* @__PURE__ */ jsx236(
96502
96482
  "button",
96503
96483
  {
96504
96484
  type: "button",
@@ -96509,16 +96489,16 @@ function BranchItem({
96509
96489
  )
96510
96490
  ] })
96511
96491
  ] }),
96512
- /* @__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: [
96513
96493
  scenes.map((scene) => {
96514
96494
  const assigned = seq.slides.some((s) => s.sceneId === scene.id);
96515
96495
  const isSelected2 = selectedSequenceId === seq.id && selectedSceneId === scene.id;
96516
- return /* @__PURE__ */ jsxs186(
96496
+ return /* @__PURE__ */ jsxs187(
96517
96497
  "div",
96518
96498
  {
96519
96499
  className: "flex items-center gap-1.5 py-0.5 text-[11px] text-neutral-400",
96520
96500
  children: [
96521
- /* @__PURE__ */ jsx235(
96501
+ /* @__PURE__ */ jsx236(
96522
96502
  "input",
96523
96503
  {
96524
96504
  type: "checkbox",
@@ -96528,7 +96508,7 @@ function BranchItem({
96528
96508
  className: "accent-studio-accent shrink-0"
96529
96509
  }
96530
96510
  ),
96531
- assigned ? /* @__PURE__ */ jsx235(
96511
+ assigned ? /* @__PURE__ */ jsx236(
96532
96512
  "button",
96533
96513
  {
96534
96514
  type: "button",
@@ -96537,13 +96517,13 @@ function BranchItem({
96537
96517
  onClick: () => onSelectBranchSlide(seq.id, scene.id),
96538
96518
  children: scene.label || scene.id
96539
96519
  }
96540
- ) : /* @__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 })
96541
96521
  ]
96542
96522
  },
96543
96523
  scene.id
96544
96524
  );
96545
96525
  }),
96546
- 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" })
96547
96527
  ] })
96548
96528
  ] });
96549
96529
  }
@@ -96569,19 +96549,19 @@ function HotspotTool({
96569
96549
  setHotspotLabel("");
96570
96550
  }, [selectedSceneId, targetSequenceId, elementKey3, hotspotLabel, onAddHotspot]);
96571
96551
  if (!selectedSceneId) {
96572
- 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" }) });
96573
96553
  }
96574
- return /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-3 px-3 py-2", children: [
96575
- /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96576
- /* @__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: [
96577
96557
  "Selected element:",
96578
96558
  " ",
96579
- /* @__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" })
96580
96560
  ] }),
96581
- !elementKey3 && /* @__PURE__ */ jsx235("p", { className: "text-[10px] text-neutral-500 italic", children: "Click an element on the canvas to choose the hotspot target." }),
96582
- 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." }),
96583
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Hotspot label" }),
96584
- /* @__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. Hotspots jump to a branch." }),
96563
+ /* @__PURE__ */ jsx236("label", { className: "text-[11px] text-neutral-400", children: "Hotspot label" }),
96564
+ /* @__PURE__ */ jsx236(
96585
96565
  "input",
96586
96566
  {
96587
96567
  type: "text",
@@ -96592,8 +96572,8 @@ function HotspotTool({
96592
96572
  "aria-label": "Hotspot label"
96593
96573
  }
96594
96574
  ),
96595
- /* @__PURE__ */ jsx235("label", { className: "text-[11px] text-neutral-400", children: "Target branch" }),
96596
- /* @__PURE__ */ jsxs186(
96575
+ /* @__PURE__ */ jsx236("label", { className: "text-[11px] text-neutral-400", children: "Target branch" }),
96576
+ /* @__PURE__ */ jsxs187(
96597
96577
  "select",
96598
96578
  {
96599
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",
@@ -96601,12 +96581,12 @@ function HotspotTool({
96601
96581
  onChange: (e) => setTargetSequenceId(e.target.value),
96602
96582
  "aria-label": "Target branch sequence",
96603
96583
  children: [
96604
- /* @__PURE__ */ jsx235("option", { value: "", children: "\u2014 select branch \u2014" }),
96605
- sequences.map((seq) => /* @__PURE__ */ jsx235("option", { value: seq.id, children: seq.label }, seq.id))
96584
+ /* @__PURE__ */ jsx236("option", { value: "", children: "Select branch" }),
96585
+ sequences.map((seq) => /* @__PURE__ */ jsx236("option", { value: seq.id, children: seq.label }, seq.id))
96606
96586
  ]
96607
96587
  }
96608
96588
  ),
96609
- /* @__PURE__ */ jsx235(
96589
+ /* @__PURE__ */ jsx236(
96610
96590
  "button",
96611
96591
  {
96612
96592
  type: "button",
@@ -96618,21 +96598,21 @@ function HotspotTool({
96618
96598
  }
96619
96599
  )
96620
96600
  ] }),
96621
- hotspots.length > 0 && /* @__PURE__ */ jsxs186("div", { className: "flex flex-col gap-1.5", children: [
96622
- /* @__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" }),
96623
96603
  hotspots.map((h) => {
96624
96604
  const seqLabel = sequences.find((s) => s.id === h.target)?.label ?? h.target;
96625
- return /* @__PURE__ */ jsxs186(
96605
+ return /* @__PURE__ */ jsxs187(
96626
96606
  "div",
96627
96607
  {
96628
96608
  className: "flex items-center gap-2 bg-neutral-800 rounded-sm px-2 py-1",
96629
96609
  children: [
96630
- /* @__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: [
96631
96611
  h.label,
96632
96612
  " \u2192 ",
96633
- /* @__PURE__ */ jsx235("span", { className: "text-neutral-400", children: seqLabel })
96613
+ /* @__PURE__ */ jsx236("span", { className: "text-neutral-400", children: seqLabel })
96634
96614
  ] }),
96635
- /* @__PURE__ */ jsx235(
96615
+ /* @__PURE__ */ jsx236(
96636
96616
  "button",
96637
96617
  {
96638
96618
  type: "button",
@@ -96782,7 +96762,7 @@ function removeHotspot(manifest, sceneId, hotspotId, sequenceId) {
96782
96762
  }
96783
96763
 
96784
96764
  // src/components/panels/SlideshowPanel.tsx
96785
- 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";
96786
96766
  function safeParseManifest(html2) {
96787
96767
  try {
96788
96768
  return parseSlideshowManifest(html2) ?? { slides: [] };
@@ -97050,7 +97030,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97050
97030
  },
97051
97031
  [selectedSequenceId, applyManifest]
97052
97032
  );
97053
- return /* @__PURE__ */ jsxs187(
97033
+ return /* @__PURE__ */ jsxs188(
97054
97034
  "div",
97055
97035
  {
97056
97036
  className: "flex flex-col h-full overflow-y-auto text-text-0",
@@ -97061,14 +97041,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97061
97041
  handleUndo();
97062
97042
  },
97063
97043
  children: [
97064
- persistError && /* @__PURE__ */ jsxs187(
97044
+ persistError && /* @__PURE__ */ jsxs188(
97065
97045
  "div",
97066
97046
  {
97067
97047
  role: "alert",
97068
97048
  className: "flex items-center justify-between gap-2 px-3 py-2 bg-danger/15 border-b border-red-500/40",
97069
97049
  children: [
97070
- /* @__PURE__ */ jsx236("span", { className: "text-[11px] text-danger-ink", children: "Changes not saved" }),
97071
- /* @__PURE__ */ jsx236(
97050
+ /* @__PURE__ */ jsx237("span", { className: "text-[11px] text-danger-ink", children: "Changes not saved" }),
97051
+ /* @__PURE__ */ jsx237(
97072
97052
  "button",
97073
97053
  {
97074
97054
  type: "button",
@@ -97081,7 +97061,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97081
97061
  ]
97082
97062
  }
97083
97063
  ),
97084
- 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(
97085
97065
  "button",
97086
97066
  {
97087
97067
  type: "button",
@@ -97095,7 +97075,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97095
97075
  ]
97096
97076
  }
97097
97077
  ) }),
97098
- /* @__PURE__ */ jsxs187(
97078
+ /* @__PURE__ */ jsxs188(
97099
97079
  SectionHeader,
97100
97080
  {
97101
97081
  expanded: expandedSections.has("slides"),
@@ -97107,7 +97087,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97107
97087
  ]
97108
97088
  }
97109
97089
  ),
97110
- 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(
97111
97091
  SlideList,
97112
97092
  {
97113
97093
  scenes,
@@ -97121,7 +97101,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97121
97101
  onReorder: handleReorder
97122
97102
  }
97123
97103
  ) }),
97124
- /* @__PURE__ */ jsx236(
97104
+ /* @__PURE__ */ jsx237(
97125
97105
  SectionHeader,
97126
97106
  {
97127
97107
  expanded: expandedSections.has("inspector"),
@@ -97129,7 +97109,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97129
97109
  children: "Slide Inspector"
97130
97110
  }
97131
97111
  ),
97132
- expandedSections.has("inspector") && /* @__PURE__ */ jsx236(Fragment65, { children: selectedSceneId ? /* @__PURE__ */ jsx236(
97112
+ expandedSections.has("inspector") && /* @__PURE__ */ jsx237(Fragment65, { children: selectedSceneId ? /* @__PURE__ */ jsx237(
97133
97113
  SlideInspector,
97134
97114
  {
97135
97115
  sceneId: selectedSceneId,
@@ -97139,8 +97119,8 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97139
97119
  onMarkFragment: handleMarkFragment,
97140
97120
  onRemoveFragment: handleRemoveFragment
97141
97121
  }
97142
- ) : /* @__PURE__ */ jsx236("p", { className: "px-3 py-2 text-[11px] text-neutral-500 italic", children: "Select a scene above to inspect" }) }),
97143
- /* @__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(
97144
97124
  SectionHeader,
97145
97125
  {
97146
97126
  expanded: expandedSections.has("branches"),
@@ -97152,7 +97132,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97152
97132
  ]
97153
97133
  }
97154
97134
  ),
97155
- expandedSections.has("branches") && /* @__PURE__ */ jsx236(
97135
+ expandedSections.has("branches") && /* @__PURE__ */ jsx237(
97156
97136
  BranchTree,
97157
97137
  {
97158
97138
  sequences,
@@ -97166,7 +97146,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97166
97146
  onSelectBranchSlide: handleSelectBranchSlide
97167
97147
  }
97168
97148
  ),
97169
- /* @__PURE__ */ jsx236(
97149
+ /* @__PURE__ */ jsx237(
97170
97150
  SectionHeader,
97171
97151
  {
97172
97152
  expanded: expandedSections.has("hotspot"),
@@ -97174,7 +97154,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
97174
97154
  children: "Hotspot Tool"
97175
97155
  }
97176
97156
  ),
97177
- expandedSections.has("hotspot") && /* @__PURE__ */ jsx236(
97157
+ expandedSections.has("hotspot") && /* @__PURE__ */ jsx237(
97178
97158
  HotspotTool,
97179
97159
  {
97180
97160
  selectedSceneId,
@@ -97316,7 +97296,7 @@ function useEditVariablesInFile(deps) {
97316
97296
 
97317
97297
  // src/components/panels/VariablesDeclarationForm.tsx
97318
97298
  import { useState as useState157 } from "react";
97319
- import { jsx as jsx237, jsxs as jsxs188 } from "react/jsx-runtime";
97299
+ import { jsx as jsx238, jsxs as jsxs189 } from "react/jsx-runtime";
97320
97300
  var VARIABLE_TYPES = [
97321
97301
  "string",
97322
97302
  "number",
@@ -97418,8 +97398,8 @@ function declarationFromDraft(draft) {
97418
97398
  }
97419
97399
  }
97420
97400
  function Field({ label, children }) {
97421
- return /* @__PURE__ */ jsxs188("div", { className: "space-y-1", children: [
97422
- /* @__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 }),
97423
97403
  children
97424
97404
  ] });
97425
97405
  }
@@ -97428,20 +97408,20 @@ function DefaultField({
97428
97408
  onChange
97429
97409
  }) {
97430
97410
  if (draft.type === "boolean") {
97431
- return /* @__PURE__ */ jsxs188(
97411
+ return /* @__PURE__ */ jsxs189(
97432
97412
  "select",
97433
97413
  {
97434
97414
  value: draft.defaultRaw === "true" ? "true" : "false",
97435
97415
  onChange: (e) => onChange(e.target.value),
97436
97416
  className: VARIABLES_INPUT_CLASS,
97437
97417
  children: [
97438
- /* @__PURE__ */ jsx237("option", { value: "false", children: "false" }),
97439
- /* @__PURE__ */ jsx237("option", { value: "true", children: "true" })
97418
+ /* @__PURE__ */ jsx238("option", { value: "false", children: "false" }),
97419
+ /* @__PURE__ */ jsx238("option", { value: "true", children: "true" })
97440
97420
  ]
97441
97421
  }
97442
97422
  );
97443
97423
  }
97444
- return /* @__PURE__ */ jsx237(
97424
+ return /* @__PURE__ */ jsx238(
97445
97425
  "input",
97446
97426
  {
97447
97427
  type: "text",
@@ -97470,9 +97450,9 @@ function DeclarationForm({
97470
97450
  setError(null);
97471
97451
  onSubmit(result);
97472
97452
  };
97473
- return /* @__PURE__ */ jsxs188("div", { className: "space-y-2 rounded-lg border border-neutral-800 bg-neutral-900/60 p-2", children: [
97474
- /* @__PURE__ */ jsxs188("div", { className: "grid grid-cols-2 gap-2", children: [
97475
- /* @__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(
97476
97456
  "input",
97477
97457
  {
97478
97458
  type: "text",
@@ -97483,7 +97463,7 @@ function DeclarationForm({
97483
97463
  className: `${VARIABLES_INPUT_CLASS} font-mono disabled:opacity-50`
97484
97464
  }
97485
97465
  ) }),
97486
- /* @__PURE__ */ jsx237(Field, { label: "Label", children: /* @__PURE__ */ jsx237(
97466
+ /* @__PURE__ */ jsx238(Field, { label: "Label", children: /* @__PURE__ */ jsx238(
97487
97467
  "input",
97488
97468
  {
97489
97469
  type: "text",
@@ -97494,8 +97474,8 @@ function DeclarationForm({
97494
97474
  }
97495
97475
  ) })
97496
97476
  ] }),
97497
- /* @__PURE__ */ jsxs188("div", { className: "grid grid-cols-2 gap-2", children: [
97498
- /* @__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(
97499
97479
  "select",
97500
97480
  {
97501
97481
  value: draft.type,
@@ -97504,12 +97484,12 @@ function DeclarationForm({
97504
97484
  if (type) set({ type });
97505
97485
  },
97506
97486
  className: VARIABLES_INPUT_CLASS,
97507
- 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))
97508
97488
  }
97509
97489
  ) }),
97510
- /* @__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 }) }) })
97511
97491
  ] }),
97512
- 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(
97513
97493
  "input",
97514
97494
  {
97515
97495
  type: "text",
@@ -97518,7 +97498,7 @@ function DeclarationForm({
97518
97498
  className: `${VARIABLES_INPUT_CLASS} tabular-nums`
97519
97499
  }
97520
97500
  ) }, key2)) }),
97521
- 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(
97522
97502
  "textarea",
97523
97503
  {
97524
97504
  value: draft.optionsRaw,
@@ -97527,7 +97507,7 @@ function DeclarationForm({
97527
97507
  className: `${VARIABLES_INPUT_CLASS} resize-y font-mono`
97528
97508
  }
97529
97509
  ) }),
97530
- /* @__PURE__ */ jsx237(Field, { label: "Description (optional)", children: /* @__PURE__ */ jsx237(
97510
+ /* @__PURE__ */ jsx238(Field, { label: "Description (optional)", children: /* @__PURE__ */ jsx238(
97531
97511
  "input",
97532
97512
  {
97533
97513
  type: "text",
@@ -97536,9 +97516,9 @@ function DeclarationForm({
97536
97516
  className: VARIABLES_INPUT_CLASS
97537
97517
  }
97538
97518
  ) }),
97539
- error && /* @__PURE__ */ jsx237("p", { className: "text-[10px] text-danger-ink", children: error }),
97540
- /* @__PURE__ */ jsxs188("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
97541
- /* @__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(
97542
97522
  "button",
97543
97523
  {
97544
97524
  type: "button",
@@ -97547,7 +97527,7 @@ function DeclarationForm({
97547
97527
  children: "Cancel"
97548
97528
  }
97549
97529
  ),
97550
- /* @__PURE__ */ jsx237(
97530
+ /* @__PURE__ */ jsx238(
97551
97531
  "button",
97552
97532
  {
97553
97533
  type: "button",
@@ -97561,14 +97541,14 @@ function DeclarationForm({
97561
97541
  }
97562
97542
 
97563
97543
  // src/components/panels/VariablesRowAction.tsx
97564
- import { jsx as jsx238 } from "react/jsx-runtime";
97544
+ import { jsx as jsx239 } from "react/jsx-runtime";
97565
97545
  function RowAction({
97566
97546
  label,
97567
97547
  title,
97568
97548
  danger,
97569
97549
  onClick
97570
97550
  }) {
97571
- return /* @__PURE__ */ jsx238(
97551
+ return /* @__PURE__ */ jsx239(
97572
97552
  "button",
97573
97553
  {
97574
97554
  type: "button",
@@ -97581,7 +97561,7 @@ function RowAction({
97581
97561
  }
97582
97562
 
97583
97563
  // src/components/panels/VariablesOtherCompositions.tsx
97584
- import { jsx as jsx239, jsxs as jsxs189 } from "react/jsx-runtime";
97564
+ import { jsx as jsx240, jsxs as jsxs190 } from "react/jsx-runtime";
97585
97565
  function CompositionSection({
97586
97566
  group,
97587
97567
  editingKey,
@@ -97589,8 +97569,8 @@ function CompositionSection({
97589
97569
  onSave,
97590
97570
  onRemove
97591
97571
  }) {
97592
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-1.5", children: [
97593
- /* @__PURE__ */ jsx239(
97572
+ return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5", children: [
97573
+ /* @__PURE__ */ jsx240(
97594
97574
  "p",
97595
97575
  {
97596
97576
  className: "truncate text-[9px] font-medium uppercase tracking-wider text-neutral-500",
@@ -97601,12 +97581,12 @@ function CompositionSection({
97601
97581
  group.variables.map((decl) => {
97602
97582
  const key2 = `${group.path}::${decl.id}`;
97603
97583
  const editing = editingKey === key2;
97604
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97605
- /* @__PURE__ */ jsxs189("div", { className: "flex items-center gap-1.5", children: [
97606
- /* @__PURE__ */ jsx239("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97607
- /* @__PURE__ */ jsx239("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97608
- /* @__PURE__ */ jsxs189("span", { className: "ml-auto flex items-center gap-1", children: [
97609
- /* @__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(
97610
97590
  RowAction,
97611
97591
  {
97612
97592
  label: "Edit",
@@ -97614,7 +97594,7 @@ function CompositionSection({
97614
97594
  onClick: () => onToggleEdit(editing ? null : key2)
97615
97595
  }
97616
97596
  ),
97617
- /* @__PURE__ */ jsx239(
97597
+ /* @__PURE__ */ jsx240(
97618
97598
  RowAction,
97619
97599
  {
97620
97600
  label: "\u2715",
@@ -97625,8 +97605,8 @@ function CompositionSection({
97625
97605
  )
97626
97606
  ] })
97627
97607
  ] }),
97628
- decl.description && /* @__PURE__ */ jsx239("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97629
- editing && /* @__PURE__ */ jsx239(
97608
+ decl.description && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97609
+ editing && /* @__PURE__ */ jsx240(
97630
97610
  DeclarationForm,
97631
97611
  {
97632
97612
  initial: draftFromDeclaration(decl),
@@ -97684,9 +97664,9 @@ function VariablesOtherCompositions({
97684
97664
  [editInFile]
97685
97665
  );
97686
97666
  if (groups.length === 0) return null;
97687
- return /* @__PURE__ */ jsxs189("div", { className: "space-y-3 border-t border-neutral-800 pt-3", children: [
97688
- /* @__PURE__ */ jsx239("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-600", children: "Other compositions" }),
97689
- 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(
97690
97670
  CompositionSection,
97691
97671
  {
97692
97672
  group,
@@ -97702,7 +97682,7 @@ function VariablesOtherCompositions({
97702
97682
 
97703
97683
  // src/components/panels/VariablesPanel.tsx
97704
97684
  import { isScalarVariableValue as isScalar } from "@hyperframes/core/variables";
97705
- import { jsx as jsx240, jsxs as jsxs190 } from "react/jsx-runtime";
97685
+ import { jsx as jsx241, jsxs as jsxs191 } from "react/jsx-runtime";
97706
97686
  function shellSingleQuote(value) {
97707
97687
  return `'${value.replace(/'/g, `'\\''`)}'`;
97708
97688
  }
@@ -97718,7 +97698,7 @@ function formatIssue(issue) {
97718
97698
  }
97719
97699
  function ValidationStrip({ issues }) {
97720
97700
  if (issues.length === 0) return null;
97721
- 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}`)) });
97722
97702
  }
97723
97703
  function VariableRow({
97724
97704
  decl,
@@ -97732,11 +97712,11 @@ function VariableRow({
97732
97712
  onSaveEdit,
97733
97713
  onRemove
97734
97714
  }) {
97735
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 p-2", children: [
97736
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-1.5", children: [
97737
- /* @__PURE__ */ jsx240("span", { className: "truncate text-[10px] font-medium text-neutral-300", children: decl.label }),
97738
- /* @__PURE__ */ jsx240("span", { className: "rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500", children: decl.type }),
97739
- 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(
97740
97720
  "span",
97741
97721
  {
97742
97722
  className: "rounded-sm bg-amber-500/15 px-1 py-px text-[8px] text-warning-ink",
@@ -97744,9 +97724,9 @@ function VariableRow({
97744
97724
  children: "unused"
97745
97725
  }
97746
97726
  ),
97747
- overridden && /* @__PURE__ */ jsx240("span", { className: "h-1.5 w-1.5 rounded-full bg-studio-accent" }),
97748
- /* @__PURE__ */ jsxs190("span", { className: "ml-auto flex items-center gap-1", children: [
97749
- 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(
97750
97730
  RowAction,
97751
97731
  {
97752
97732
  label: "Set default",
@@ -97754,12 +97734,12 @@ function VariableRow({
97754
97734
  onClick: () => onSetDefault(value)
97755
97735
  }
97756
97736
  ),
97757
- /* @__PURE__ */ jsx240(RowAction, { label: "Edit", title: "Edit declaration", onClick: onToggleEdit }),
97758
- /* @__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 })
97759
97739
  ] })
97760
97740
  ] }),
97761
- decl.description && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97762
- editing ? /* @__PURE__ */ jsx240(
97741
+ decl.description && /* @__PURE__ */ jsx241("p", { className: "text-[9px] text-neutral-500", children: decl.description }),
97742
+ editing ? /* @__PURE__ */ jsx241(
97763
97743
  DeclarationForm,
97764
97744
  {
97765
97745
  initial: draftFromDeclaration(decl),
@@ -97767,7 +97747,7 @@ function VariableRow({
97767
97747
  onSubmit: (edited) => onSaveEdit(mergeDeclarationEdit(decl, edited)),
97768
97748
  onCancel: onToggleEdit
97769
97749
  }
97770
- ) : /* @__PURE__ */ jsx240(PreviewValueControl, { decl, value, onCommit: onCommitPreview })
97750
+ ) : /* @__PURE__ */ jsx241(PreviewValueControl, { decl, value, onCommit: onCommitPreview })
97771
97751
  ] });
97772
97752
  }
97773
97753
  function UndeclaredReads({
@@ -97775,11 +97755,11 @@ function UndeclaredReads({
97775
97755
  onDeclare
97776
97756
  }) {
97777
97757
  if (!usage || usage.undeclaredReads.length === 0) return null;
97778
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97779
- /* @__PURE__ */ jsx240("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Read by scripts, not declared" }),
97780
- usage.undeclaredReads.map((id) => /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97781
- /* @__PURE__ */ jsx240("code", { className: "font-mono text-[10px] text-neutral-400", children: id }),
97782
- /* @__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(
97783
97763
  RowAction,
97784
97764
  {
97785
97765
  label: "Declare",
@@ -97795,10 +97775,10 @@ function PreviewModeHeader({
97795
97775
  onReset
97796
97776
  }) {
97797
97777
  const hasOverrides = overrideCount > 0;
97798
- return /* @__PURE__ */ jsxs190("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
97799
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97800
- /* @__PURE__ */ jsx240("span", { className: "text-[11px] font-semibold text-neutral-200", children: "Variables" }),
97801
- /* @__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(
97802
97782
  "span",
97803
97783
  {
97804
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"}`,
@@ -97806,7 +97786,7 @@ function PreviewModeHeader({
97806
97786
  }
97807
97787
  )
97808
97788
  ] }),
97809
- hasOverrides && /* @__PURE__ */ jsx240(
97789
+ hasOverrides && /* @__PURE__ */ jsx241(
97810
97790
  "button",
97811
97791
  {
97812
97792
  type: "button",
@@ -97824,11 +97804,11 @@ function HandoffFooter({
97824
97804
  }) {
97825
97805
  const json = JSON.stringify(effectiveValues);
97826
97806
  const command = `npx hyperframes render ${shellSingleQuote(compPath)} --variables ${shellSingleQuote(json)}`;
97827
- return /* @__PURE__ */ jsxs190("div", { className: "space-y-1.5 rounded-lg border border-neutral-800/70 bg-neutral-900/40 p-2", children: [
97828
- /* @__PURE__ */ jsx240("p", { className: "text-[9px] font-medium uppercase tracking-wider text-neutral-500", children: "Use this template" }),
97829
- /* @__PURE__ */ jsx240("code", { className: "block truncate font-mono text-[9px] text-neutral-500", title: command, children: command }),
97830
- /* @__PURE__ */ jsxs190("div", { className: "flex items-center gap-2", children: [
97831
- /* @__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(
97832
97812
  RowAction,
97833
97813
  {
97834
97814
  label: "Copy render command",
@@ -97836,7 +97816,7 @@ function HandoffFooter({
97836
97816
  onClick: () => onCopy(command, "Render command")
97837
97817
  }
97838
97818
  ),
97839
- /* @__PURE__ */ jsx240(
97819
+ /* @__PURE__ */ jsx241(
97840
97820
  RowAction,
97841
97821
  {
97842
97822
  label: "Copy values JSON",
@@ -97847,15 +97827,15 @@ function HandoffFooter({
97847
97827
  ] })
97848
97828
  ] });
97849
97829
  }
97850
- var EMPTY_STATE = /* @__PURE__ */ jsxs190("p", { className: "text-[10px] leading-relaxed text-neutral-500", children: [
97851
- "No variables declared. Variables make parts of this composition dynamic \u2014 declare them here (or in ",
97852
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "data-composition-variables" }),
97830
+ var EMPTY_STATE = /* @__PURE__ */ jsxs191("p", { className: "text-[10px] leading-relaxed text-neutral-500", children: [
97831
+ "No variables declared. Variables make parts of this composition dynamic. Declare them here (or in ",
97832
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "data-composition-variables" }),
97853
97833
  "), read them with",
97854
97834
  " ",
97855
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "getVariables()" }),
97835
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "getVariables()" }),
97856
97836
  ", and pass values at render time with",
97857
97837
  " ",
97858
- /* @__PURE__ */ jsx240("code", { className: "font-mono", children: "--variables" }),
97838
+ /* @__PURE__ */ jsx241("code", { className: "font-mono", children: "--variables" }),
97859
97839
  "."
97860
97840
  ] });
97861
97841
  var VariablesPanel = memo59(function VariablesPanel2({
@@ -98024,7 +98004,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98024
98004
  const wanted = action.declaration(id).type;
98025
98005
  if (existing && existing.type !== wanted) {
98026
98006
  showToast(
98027
- `"${id}" is already a ${existing.type} variable \u2014 pick another id for this ${wanted} binding`,
98007
+ `"${id}" is already a ${existing.type} variable. Pick another id for this ${wanted} binding`,
98028
98008
  "error"
98029
98009
  );
98030
98010
  return;
@@ -98039,18 +98019,18 @@ var VariablesPanel = memo59(function VariablesPanel2({
98039
98019
  );
98040
98020
  const bindableSelection = domEditSelection?.hfId && domEditSelection.sourceFile === (activeCompPath ?? "index.html") ? domEditSelection : null;
98041
98021
  if (!sdkSession) {
98042
- 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." }) });
98043
98023
  }
98044
- return /* @__PURE__ */ jsxs190("div", { className: "flex h-full flex-col", children: [
98045
- /* @__PURE__ */ jsx240(
98024
+ return /* @__PURE__ */ jsxs191("div", { className: "flex h-full flex-col", children: [
98025
+ /* @__PURE__ */ jsx241(
98046
98026
  PreviewModeHeader,
98047
98027
  {
98048
98028
  overrideCount: previewValues ? Object.keys(previewValues).length : 0,
98049
98029
  onReset: resetPreview
98050
98030
  }
98051
98031
  ),
98052
- /* @__PURE__ */ jsxs190("div", { className: "flex-1 space-y-3 overflow-y-auto p-3", children: [
98053
- bindableSelection && /* @__PURE__ */ jsx240(
98032
+ /* @__PURE__ */ jsxs191("div", { className: "flex-1 space-y-3 overflow-y-auto p-3", children: [
98033
+ bindableSelection && /* @__PURE__ */ jsx241(
98054
98034
  VariablesBindElement,
98055
98035
  {
98056
98036
  selection: bindableSelection,
@@ -98059,9 +98039,9 @@ var VariablesPanel = memo59(function VariablesPanel2({
98059
98039
  },
98060
98040
  bindableSelection.hfId
98061
98041
  ),
98062
- /* @__PURE__ */ jsx240(ValidationStrip, { issues }),
98042
+ /* @__PURE__ */ jsx241(ValidationStrip, { issues }),
98063
98043
  declarations.length === 0 && !addOpen && EMPTY_STATE,
98064
- declarations.map((decl) => /* @__PURE__ */ jsx240(
98044
+ declarations.map((decl) => /* @__PURE__ */ jsx241(
98065
98045
  VariableRow,
98066
98046
  {
98067
98047
  decl,
@@ -98077,15 +98057,15 @@ var VariablesPanel = memo59(function VariablesPanel2({
98077
98057
  },
98078
98058
  decl.id
98079
98059
  )),
98080
- /* @__PURE__ */ jsx240(
98060
+ /* @__PURE__ */ jsx241(
98081
98061
  UndeclaredReads,
98082
98062
  {
98083
98063
  usage,
98084
98064
  onDeclare: (id) => handleAdd({ id, type: "string", label: id, default: "" })
98085
98065
  }
98086
98066
  ),
98087
- usage?.scanIncomplete && /* @__PURE__ */ jsx240("p", { className: "text-[9px] text-neutral-600", children: "Scripts access variables dynamically \u2014 usage info may be incomplete." }),
98088
- declarations.length > 0 && /* @__PURE__ */ jsx240(
98067
+ usage?.scanIncomplete && /* @__PURE__ */ jsx241("p", { className: "text-[9px] text-neutral-600", children: "Scripts access variables dynamically, so usage info may be incomplete." }),
98068
+ declarations.length > 0 && /* @__PURE__ */ jsx241(
98089
98069
  HandoffFooter,
98090
98070
  {
98091
98071
  effectiveValues,
@@ -98093,7 +98073,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98093
98073
  onCopy: copyToClipboard
98094
98074
  }
98095
98075
  ),
98096
- addOpen ? /* @__PURE__ */ jsx240(
98076
+ addOpen ? /* @__PURE__ */ jsx241(
98097
98077
  DeclarationForm,
98098
98078
  {
98099
98079
  initial: EMPTY_DRAFT,
@@ -98101,7 +98081,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98101
98081
  onSubmit: handleAdd,
98102
98082
  onCancel: () => setAddOpen(false)
98103
98083
  }
98104
- ) : /* @__PURE__ */ jsx240(
98084
+ ) : /* @__PURE__ */ jsx241(
98105
98085
  "button",
98106
98086
  {
98107
98087
  type: "button",
@@ -98110,7 +98090,7 @@ var VariablesPanel = memo59(function VariablesPanel2({
98110
98090
  children: "+ Add variable"
98111
98091
  }
98112
98092
  ),
98113
- /* @__PURE__ */ jsx240(
98093
+ /* @__PURE__ */ jsx241(
98114
98094
  VariablesOtherCompositions,
98115
98095
  {
98116
98096
  compositionPaths: compositions,
@@ -98243,7 +98223,7 @@ function useSlideshowTabState(params) {
98243
98223
 
98244
98224
  // src/components/DesignPanelPromoteProvider.tsx
98245
98225
  import { useCallback as useCallback185 } from "react";
98246
- import { jsx as jsx241 } from "react/jsx-runtime";
98226
+ import { jsx as jsx242 } from "react/jsx-runtime";
98247
98227
  function DesignPanelPromoteProvider({
98248
98228
  selection,
98249
98229
  projectId,
@@ -98273,7 +98253,7 @@ function DesignPanelPromoteProvider({
98273
98253
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
98274
98254
  [showToast]
98275
98255
  );
98276
- return /* @__PURE__ */ jsx241(
98256
+ return /* @__PURE__ */ jsx242(
98277
98257
  VariablePromoteProvider,
98278
98258
  {
98279
98259
  session: handle.session,
@@ -98578,7 +98558,7 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
98578
98558
  }
98579
98559
 
98580
98560
  // src/components/StudioRightPanels.tsx
98581
- 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";
98582
98562
  var seekToTime = (t2) => usePlayerStore.getState().requestSeek(t2);
98583
98563
  var StudioRightPanels = memo60(function StudioRightPanels2({
98584
98564
  activeBlockParams,
@@ -98696,7 +98676,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98696
98676
  );
98697
98677
  const handleHideAllSelected = useCallback188(() => {
98698
98678
  if (!canHideSelections(domEditGroupSelections)) {
98699
- showToast("Audio can't be hidden \u2014 use the group's own controls", "info");
98679
+ showToast("Audio can't be hidden. Use the group's own controls", "info");
98700
98680
  return;
98701
98681
  }
98702
98682
  const { elements } = usePlayerStore.getState();
@@ -98707,7 +98687,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98707
98687
  (animId, duration) => handleGsapConvertToKeyframes(animId, void 0, duration),
98708
98688
  [handleGsapConvertToKeyframes]
98709
98689
  );
98710
- const propertyPanel = /* @__PURE__ */ jsx242(
98690
+ const propertyPanel = /* @__PURE__ */ jsx243(
98711
98691
  DesignPanelPromoteProvider,
98712
98692
  {
98713
98693
  selection: domEditGroupSelections.length > 1 ? null : domEditSelection,
@@ -98719,7 +98699,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98719
98699
  recordEdit,
98720
98700
  reloadPreview,
98721
98701
  forceReloadSharedSdkSession: forceReloadSdkSession,
98722
- children: /* @__PURE__ */ jsx242(
98702
+ children: /* @__PURE__ */ jsx243(
98723
98703
  PropertyPanel,
98724
98704
  {
98725
98705
  projectId,
@@ -98790,9 +98770,9 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98790
98770
  );
98791
98771
  let designBody = propertyPanel;
98792
98772
  if (captionEditMode) {
98793
- designBody = /* @__PURE__ */ jsx242(CaptionPropertyPanel, { iframeRef: previewIframeRef });
98773
+ designBody = /* @__PURE__ */ jsx243(CaptionPropertyPanel, { iframeRef: previewIframeRef });
98794
98774
  } else if (activeBlockParams) {
98795
- designBody = /* @__PURE__ */ jsx242(
98775
+ designBody = /* @__PURE__ */ jsx243(
98796
98776
  BlockParamsPanel,
98797
98777
  {
98798
98778
  blockName: activeBlockParams.blockName,
@@ -98804,18 +98784,18 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98804
98784
  }
98805
98785
  );
98806
98786
  }
98807
- return /* @__PURE__ */ jsxs191(Fragment66, { children: [
98808
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "design", children: designBody }),
98809
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "layers", children: /* @__PURE__ */ jsx242(LayersPanel, {}) }),
98810
- /* @__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(
98811
98791
  Dock.Panel,
98812
98792
  {
98813
98793
  id: "renders",
98814
98794
  title: renderJobs.length > 0 ? `Renders (${renderJobs.length})` : void 0,
98815
- children: /* @__PURE__ */ jsx242(RenderQueuePanel, {})
98795
+ children: /* @__PURE__ */ jsx243(RenderQueuePanel, {})
98816
98796
  }
98817
98797
  ),
98818
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx242(
98798
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "variables", children: /* @__PURE__ */ jsx243(
98819
98799
  VariablesPanel,
98820
98800
  {
98821
98801
  sdkSession,
@@ -98824,7 +98804,7 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98824
98804
  recordEdit
98825
98805
  }
98826
98806
  ) }),
98827
- /* @__PURE__ */ jsx242(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx242(
98807
+ /* @__PURE__ */ jsx243(Dock.Panel, { id: "slideshow", children: /* @__PURE__ */ jsx243(
98828
98808
  SlideshowPanel,
98829
98809
  {
98830
98810
  scenes: slideshowScenes,
@@ -98836,14 +98816,14 @@ var StudioRightPanels = memo60(function StudioRightPanels2({
98836
98816
  });
98837
98817
 
98838
98818
  // src/hooks/useStudioSidePanels.tsx
98839
- 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";
98840
98820
  function useStudioSidePanels(left, right, projectId) {
98841
98821
  const stableLeft = useStableHandlers(left, projectId);
98842
98822
  const stableRight = useStableHandlers(right, projectId);
98843
98823
  return useMemo88(
98844
- () => /* @__PURE__ */ jsxs192(Fragment67, { children: [
98845
- /* @__PURE__ */ jsx243(StudioLeftPanels, { ...stableLeft }),
98846
- /* @__PURE__ */ jsx243(StudioRightPanels, { ...stableRight })
98824
+ () => /* @__PURE__ */ jsxs193(Fragment67, { children: [
98825
+ /* @__PURE__ */ jsx244(StudioLeftPanels, { ...stableLeft }),
98826
+ /* @__PURE__ */ jsx244(StudioRightPanels, { ...stableRight })
98847
98827
  ] }),
98848
98828
  [stableLeft, stableRight]
98849
98829
  );
@@ -98882,9 +98862,9 @@ function useKeyframeKeyboard({ enabled, onAddKeyframe }) {
98882
98862
  }
98883
98863
 
98884
98864
  // src/components/icons/AudioMetersIcon.tsx
98885
- import { jsx as jsx244 } from "react/jsx-runtime";
98865
+ import { jsx as jsx245 } from "react/jsx-runtime";
98886
98866
  function AudioMetersIcon({ size = 16 }) {
98887
- return /* @__PURE__ */ jsx244(
98867
+ return /* @__PURE__ */ jsx245(
98888
98868
  "svg",
98889
98869
  {
98890
98870
  width: size,
@@ -98895,15 +98875,15 @@ function AudioMetersIcon({ size = 16 }) {
98895
98875
  strokeWidth: 1.5,
98896
98876
  strokeLinecap: "round",
98897
98877
  "aria-hidden": "true",
98898
- 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" })
98899
98879
  }
98900
98880
  );
98901
98881
  }
98902
98882
 
98903
98883
  // src/components/icons/RippleEditIcon.tsx
98904
- import { jsx as jsx245, jsxs as jsxs193 } from "react/jsx-runtime";
98884
+ import { jsx as jsx246, jsxs as jsxs194 } from "react/jsx-runtime";
98905
98885
  function RippleEditIcon({ size = 16 }) {
98906
- return /* @__PURE__ */ jsxs193(
98886
+ return /* @__PURE__ */ jsxs194(
98907
98887
  "svg",
98908
98888
  {
98909
98889
  width: size,
@@ -98916,20 +98896,20 @@ function RippleEditIcon({ size = 16 }) {
98916
98896
  strokeLinejoin: "round",
98917
98897
  "aria-hidden": "true",
98918
98898
  children: [
98919
- /* @__PURE__ */ jsx245("rect", { x: "1.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98920
- /* @__PURE__ */ jsx245("rect", { x: "10.5", y: "8", width: "4", height: "5", rx: "0.5" }),
98921
- /* @__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" })
98922
98902
  ]
98923
98903
  }
98924
98904
  );
98925
98905
  }
98926
98906
 
98927
98907
  // src/components/TimelineHistoryButtons.tsx
98928
- 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";
98929
98909
  function HistoryButton({ action, can, label, onClick }) {
98930
98910
  const Icon = action === "undo" ? RotateCcw : RotateCw;
98931
98911
  const enabled = Boolean(onClick) && can;
98932
- return /* @__PURE__ */ jsx246(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx246(
98912
+ return /* @__PURE__ */ jsx247(Tooltip, { label: historyTooltipLabel(action, label), children: /* @__PURE__ */ jsx247(
98933
98913
  "button",
98934
98914
  {
98935
98915
  type: "button",
@@ -98940,7 +98920,7 @@ function HistoryButton({ action, can, label, onClick }) {
98940
98920
  trackStudioEvent("toolbar_action", { action });
98941
98921
  void onClick?.();
98942
98922
  },
98943
- children: /* @__PURE__ */ jsx246(Icon, { size: 16 })
98923
+ children: /* @__PURE__ */ jsx247(Icon, { size: 16 })
98944
98924
  }
98945
98925
  ) });
98946
98926
  }
@@ -98948,8 +98928,8 @@ function TimelineHistoryButtons(props) {
98948
98928
  const shell = useStudioShellContextOptional();
98949
98929
  const onUndo = props.onUndo ?? shell?.handleUndo;
98950
98930
  const onRedo = props.onRedo ?? shell?.handleRedo;
98951
- return /* @__PURE__ */ jsxs194(Fragment68, { children: [
98952
- /* @__PURE__ */ jsx246(
98931
+ return /* @__PURE__ */ jsxs195(Fragment68, { children: [
98932
+ /* @__PURE__ */ jsx247(
98953
98933
  HistoryButton,
98954
98934
  {
98955
98935
  action: "undo",
@@ -98958,7 +98938,7 @@ function TimelineHistoryButtons(props) {
98958
98938
  onClick: onUndo
98959
98939
  }
98960
98940
  ),
98961
- /* @__PURE__ */ jsx246(
98941
+ /* @__PURE__ */ jsx247(
98962
98942
  HistoryButton,
98963
98943
  {
98964
98944
  action: "redo",
@@ -98972,9 +98952,9 @@ function TimelineHistoryButtons(props) {
98972
98952
 
98973
98953
  // src/components/TimelineToolPicker.tsx
98974
98954
  import { ArrowLineLeft, ArrowLineRight, CaretDown } from "@phosphor-icons/react";
98975
- import { jsx as jsx247, jsxs as jsxs195 } from "react/jsx-runtime";
98955
+ import { jsx as jsx248, jsxs as jsxs196 } from "react/jsx-runtime";
98976
98956
  function SelectIcon() {
98977
- 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" }) });
98978
98958
  }
98979
98959
  function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98980
98960
  const activeTool = usePlayerStore((s) => s.activeTool);
@@ -98983,14 +98963,14 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
98983
98963
  {
98984
98964
  label: "Select",
98985
98965
  shortcut: "V",
98986
- icon: /* @__PURE__ */ jsx247(SelectIcon, {}),
98966
+ icon: /* @__PURE__ */ jsx248(SelectIcon, {}),
98987
98967
  active: activeTool === "select",
98988
98968
  run: () => store().setActiveTool("select")
98989
98969
  },
98990
98970
  {
98991
98971
  label: "Split",
98992
98972
  shortcut: "B",
98993
- icon: /* @__PURE__ */ jsx247(Scissors, { size: 16 }),
98973
+ icon: /* @__PURE__ */ jsx248(Scissors, { size: 16 }),
98994
98974
  active: activeTool === "razor",
98995
98975
  run: () => store().setActiveTool("razor")
98996
98976
  }
@@ -99000,25 +98980,25 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99000
98980
  {
99001
98981
  label: "Select leftward",
99002
98982
  shortcut: "[",
99003
- icon: /* @__PURE__ */ jsx247(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
98983
+ icon: /* @__PURE__ */ jsx248(ArrowLineLeft, { size: 16, "aria-hidden": "true" }),
99004
98984
  active: false,
99005
98985
  run: () => store().selectLeftward()
99006
98986
  },
99007
98987
  {
99008
98988
  label: "Select rightward",
99009
98989
  shortcut: "]",
99010
- icon: /* @__PURE__ */ jsx247(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
98990
+ icon: /* @__PURE__ */ jsx248(ArrowLineRight, { size: 16, "aria-hidden": "true" }),
99011
98991
  active: false,
99012
98992
  run: () => store().selectRightward()
99013
98993
  }
99014
98994
  );
99015
98995
  }
99016
98996
  const current = rows.find((row) => row.active) ?? rows[0];
99017
- return /* @__PURE__ */ jsx247(
98997
+ return /* @__PURE__ */ jsx248(
99018
98998
  Menu,
99019
98999
  {
99020
99000
  "aria-label": "Timeline tools",
99021
- trigger: /* @__PURE__ */ jsxs195(
99001
+ trigger: /* @__PURE__ */ jsxs196(
99022
99002
  "button",
99023
99003
  {
99024
99004
  type: "button",
@@ -99026,18 +99006,18 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99026
99006
  className: cn(flatIdle, "gap-0.5"),
99027
99007
  children: [
99028
99008
  current.icon,
99029
- /* @__PURE__ */ jsx247(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
99009
+ /* @__PURE__ */ jsx248(CaretDown, { size: 10, weight: "bold", "aria-hidden": "true" })
99030
99010
  ]
99031
99011
  }
99032
99012
  ),
99033
- children: rows.map((row) => /* @__PURE__ */ jsx247(
99013
+ children: rows.map((row) => /* @__PURE__ */ jsx248(
99034
99014
  MenuItem,
99035
99015
  {
99036
99016
  shortcut: row.shortcut,
99037
99017
  onClick: row.run,
99038
99018
  "data-active": row.active || void 0,
99039
99019
  className: row.active ? "bg-press text-text-0" : void 0,
99040
- children: /* @__PURE__ */ jsxs195("span", { className: "flex items-center gap-2", children: [
99020
+ children: /* @__PURE__ */ jsxs196("span", { className: "flex items-center gap-2", children: [
99041
99021
  row.icon,
99042
99022
  row.label
99043
99023
  ] })
@@ -99052,14 +99032,14 @@ function TimelineToolPicker({ showSelectAroundPlayhead = true }) {
99052
99032
  import { useCallback as useCallback190, useState as useState160 } from "react";
99053
99033
  import { createPortal as createPortal15 } from "react-dom";
99054
99034
  import { LinkSimple } from "@phosphor-icons/react";
99055
- 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";
99056
99036
  function SyncIndicatorMenu({ x, y, onClose }) {
99057
99037
  const menuRef = useContextMenuDismiss(onClose);
99058
99038
  useMenuKeyboardNav(menuRef);
99059
99039
  const visible = useLinkedClipPreferences((s) => s.syncIndicatorsVisible);
99060
99040
  const setVisible = useLinkedClipPreferences((s) => s.setSyncIndicatorsVisible);
99061
99041
  return createPortal15(
99062
- /* @__PURE__ */ jsx248(
99042
+ /* @__PURE__ */ jsx249(
99063
99043
  "div",
99064
99044
  {
99065
99045
  ref: menuRef,
@@ -99067,7 +99047,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99067
99047
  "aria-label": "Linked clips",
99068
99048
  className: `${menuClasses.panel} fixed z-200 min-w-[200px]`,
99069
99049
  style: { left: x, top: y },
99070
- children: /* @__PURE__ */ jsxs196(
99050
+ children: /* @__PURE__ */ jsxs197(
99071
99051
  "button",
99072
99052
  {
99073
99053
  type: "button",
@@ -99079,7 +99059,7 @@ function SyncIndicatorMenu({ x, y, onClose }) {
99079
99059
  onClose();
99080
99060
  },
99081
99061
  children: [
99082
- /* @__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" : "" }),
99083
99063
  "Show out-of-sync indicators"
99084
99064
  ]
99085
99065
  }
@@ -99094,12 +99074,12 @@ function LinkedSelectionToggle() {
99094
99074
  const setLinkedSelection = useLinkedClipPreferences((s) => s.setLinkedSelection);
99095
99075
  const [menuAt, setMenuAt] = useState160(null);
99096
99076
  const closeMenu = useCallback190(() => setMenuAt(null), []);
99097
- return /* @__PURE__ */ jsxs196(Fragment69, { children: [
99098
- /* @__PURE__ */ jsx248(
99077
+ return /* @__PURE__ */ jsxs197(Fragment69, { children: [
99078
+ /* @__PURE__ */ jsx249(
99099
99079
  Tooltip,
99100
99080
  {
99101
- label: linkedSelection ? "Linked Selection on \u2014 a click selects both halves (\u2325-click for one)" : "Linked Selection off \u2014 a click selects one clip",
99102
- children: /* @__PURE__ */ jsx248(
99081
+ label: linkedSelection ? "Linked Selection on: a click selects both halves (\u2325-click for one)" : "Linked Selection off: a click selects one clip",
99082
+ children: /* @__PURE__ */ jsx249(
99103
99083
  "button",
99104
99084
  {
99105
99085
  type: "button",
@@ -99111,17 +99091,17 @@ function LinkedSelectionToggle() {
99111
99091
  "aria-label": "Linked Selection",
99112
99092
  "aria-pressed": linkedSelection,
99113
99093
  className: linkedSelection ? flatActive : flatIdle,
99114
- children: /* @__PURE__ */ jsx248(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99094
+ children: /* @__PURE__ */ jsx249(LinkSimple, { size: 16, weight: "bold", "aria-hidden": "true" })
99115
99095
  }
99116
99096
  )
99117
99097
  }
99118
99098
  ),
99119
- 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 })
99120
99100
  ] });
99121
99101
  }
99122
99102
 
99123
99103
  // src/components/TimelineToolbar.tsx
99124
- 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";
99125
99105
  var NO_KEYFRAME_TOGGLE = {
99126
99106
  state: "none",
99127
99107
  isMotionPath: false,
@@ -99234,13 +99214,13 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99234
99214
  window.addEventListener("keydown", onKeyDown);
99235
99215
  return () => window.removeEventListener("keydown", onKeyDown);
99236
99216
  }, []);
99237
- 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: [
99238
- /* @__PURE__ */ jsxs197("div", { className: "flex items-center gap-0.5", children: [
99239
- showHistory && /* @__PURE__ */ jsx249(TimelineHistoryButtons, { ...history2 }),
99240
- /* @__PURE__ */ jsx249(TimelineToolPicker, { showSelectAroundPlayhead }),
99241
- /* @__PURE__ */ jsx249("div", { "aria-hidden": "true", className: "mx-1 h-4 w-px bg-neutral-800" }),
99242
- /* @__PURE__ */ jsx249(LinkedSelectionToggle, {}),
99243
- /* @__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(
99244
99224
  "button",
99245
99225
  {
99246
99226
  type: "button",
@@ -99248,14 +99228,14 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99248
99228
  "aria-label": "Toggle timeline snapping",
99249
99229
  "aria-pressed": timelineSnapEnabled,
99250
99230
  className: timelineSnapEnabled ? flatActive : flatIdle,
99251
- children: /* @__PURE__ */ jsx249(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99231
+ children: /* @__PURE__ */ jsx250(Magnet, { size: 16, weight: "bold", "aria-hidden": "true" })
99252
99232
  }
99253
99233
  ) }),
99254
- /* @__PURE__ */ jsx249(
99234
+ /* @__PURE__ */ jsx250(
99255
99235
  Tooltip,
99256
99236
  {
99257
- label: rippleEditEnabled ? "Ripple on \u2014 keeps the main track gapless" : "Ripple off \u2014 deleting a main-track clip leaves a gap",
99258
- children: /* @__PURE__ */ jsx249(
99237
+ label: rippleEditEnabled ? "Ripple on: keeps the main track gapless" : "Ripple off: deleting a main-track clip leaves a gap",
99238
+ children: /* @__PURE__ */ jsx250(
99259
99239
  "button",
99260
99240
  {
99261
99241
  type: "button",
@@ -99263,12 +99243,12 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99263
99243
  "aria-label": "Toggle ripple edit",
99264
99244
  "aria-pressed": rippleEditEnabled,
99265
99245
  className: rippleEditEnabled ? flatActive : flatIdle,
99266
- children: /* @__PURE__ */ jsx249(RippleEditIcon, { size: 16 })
99246
+ children: /* @__PURE__ */ jsx250(RippleEditIcon, { size: 16 })
99267
99247
  }
99268
99248
  )
99269
99249
  }
99270
99250
  ),
99271
- 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(
99272
99252
  "button",
99273
99253
  {
99274
99254
  type: "button",
@@ -99276,15 +99256,15 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99276
99256
  "aria-label": "Toggle audio meters",
99277
99257
  "aria-pressed": audioMetersVisible,
99278
99258
  className: audioMetersVisible ? flatActive : flatIdle,
99279
- children: /* @__PURE__ */ jsx249(AudioMetersIcon, { size: 16 })
99259
+ children: /* @__PURE__ */ jsx250(AudioMetersIcon, { size: 16 })
99280
99260
  }
99281
99261
  ) }),
99282
- showKeyframes && /* @__PURE__ */ jsxs197(Fragment70, { children: [
99283
- /* @__PURE__ */ jsx249(
99262
+ showKeyframes && /* @__PURE__ */ jsxs198(Fragment70, { children: [
99263
+ /* @__PURE__ */ jsx250(
99284
99264
  Tooltip,
99285
99265
  {
99286
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)",
99287
- children: /* @__PURE__ */ jsx249(
99267
+ children: /* @__PURE__ */ jsx250(
99288
99268
  "button",
99289
99269
  {
99290
99270
  type: "button",
@@ -99292,7 +99272,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99292
99272
  onClick: onToggleKeyframe,
99293
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",
99294
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"}`,
99295
- 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(
99296
99276
  "path",
99297
99277
  {
99298
99278
  d: "M5 1.2L8.8 5L5 8.8L1.2 5Z",
@@ -99305,11 +99285,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99305
99285
  )
99306
99286
  }
99307
99287
  ),
99308
- /* @__PURE__ */ jsx249(
99288
+ /* @__PURE__ */ jsx250(
99309
99289
  Tooltip,
99310
99290
  {
99311
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)",
99312
- children: /* @__PURE__ */ jsx249(
99292
+ children: /* @__PURE__ */ jsx250(
99313
99293
  "button",
99314
99294
  {
99315
99295
  type: "button",
@@ -99317,9 +99297,9 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99317
99297
  "aria-label": "Auto-record manual edits as keyframes",
99318
99298
  "aria-pressed": autoKeyframeEnabled,
99319
99299
  className: `${flatBtn} active:scale-[0.98] hover:bg-hover ${autoKeyframeEnabled ? "text-danger-ink" : "text-neutral-600 hover:text-neutral-400"}`,
99320
- children: /* @__PURE__ */ jsxs197("svg", { width: "16", height: "16", viewBox: "0 0 10 10", fill: "none", children: [
99321
- /* @__PURE__ */ jsx249("path", { d: "M5 0.7L9.3 5L5 9.3L0.7 5Z", stroke: "currentColor", strokeWidth: "1" }),
99322
- /* @__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(
99323
99303
  "circle",
99324
99304
  {
99325
99305
  cx: "5",
@@ -99340,11 +99320,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99340
99320
  const el = selectedElementId ? elements.find((e) => (e.key ?? e.id) === selectedElementId) : null;
99341
99321
  const splittable = el != null && canSplitElement(el);
99342
99322
  const canSplit = splittable && currentTime > el.start && currentTime < el.start + el.duration;
99343
- return /* @__PURE__ */ jsx249(
99323
+ return /* @__PURE__ */ jsx250(
99344
99324
  Tooltip,
99345
99325
  {
99346
99326
  label: canSplit ? `Split at playhead (${splitShortcut})` : splittable ? "Move the playhead inside the clip to split" : "Select a clip to split",
99347
- children: /* @__PURE__ */ jsx249(
99327
+ children: /* @__PURE__ */ jsx250(
99348
99328
  "button",
99349
99329
  {
99350
99330
  type: "button",
@@ -99354,7 +99334,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99354
99334
  if (canSplit && el) onSplitElement(el, currentTime);
99355
99335
  },
99356
99336
  className: canSplit ? flatIdle : flatDisabled,
99357
- children: /* @__PURE__ */ jsxs197(
99337
+ children: /* @__PURE__ */ jsxs198(
99358
99338
  "svg",
99359
99339
  {
99360
99340
  width: "16",
@@ -99367,8 +99347,8 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99367
99347
  strokeLinejoin: "round",
99368
99348
  "aria-hidden": "true",
99369
99349
  children: [
99370
- /* @__PURE__ */ jsx249("path", { d: "M5 3 L7 3 L7 13 L5 13" }),
99371
- /* @__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" })
99372
99352
  ]
99373
99353
  }
99374
99354
  )
@@ -99380,11 +99360,11 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99380
99360
  (() => {
99381
99361
  if (!showAddBeat) return null;
99382
99362
  const canAdd = beatAnalysisReady && canAddBeatAt(currentTime);
99383
- return /* @__PURE__ */ jsx249(
99363
+ return /* @__PURE__ */ jsx250(
99384
99364
  Tooltip,
99385
99365
  {
99386
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",
99387
- children: /* @__PURE__ */ jsx249(
99367
+ children: /* @__PURE__ */ jsx250(
99388
99368
  "button",
99389
99369
  {
99390
99370
  type: "button",
@@ -99394,7 +99374,7 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99394
99374
  if (canAdd) addBeatAtCompositionTime(currentTime);
99395
99375
  },
99396
99376
  className: canAdd ? `${flatBtn} text-text-2 hover:bg-hover hover:text-accent-ink active:scale-[0.98]` : flatDisabled,
99397
- 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(
99398
99378
  "path",
99399
99379
  {
99400
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",
@@ -99410,26 +99390,26 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99410
99390
  );
99411
99391
  })()
99412
99392
  ] }),
99413
- /* @__PURE__ */ jsxs197("div", { className: "flex items-center gap-0.5", children: [
99393
+ /* @__PURE__ */ jsxs198("div", { className: "flex items-center gap-0.5", children: [
99414
99394
  rightActions,
99415
- /* @__PURE__ */ jsx249(
99395
+ /* @__PURE__ */ jsx250(
99416
99396
  Tooltip,
99417
99397
  {
99418
- label: thumbnailsVisible ? "Hide thumbnails \u2014 labels only" : "Show thumbnails \u2014 posters stay visible; richer previews appear on interaction",
99419
- children: /* @__PURE__ */ jsx249(
99398
+ label: thumbnailsVisible ? "Hide thumbnails: labels only" : "Show thumbnails: posters stay visible; richer previews appear on interaction",
99399
+ children: /* @__PURE__ */ jsx250(
99420
99400
  "button",
99421
99401
  {
99422
99402
  type: "button",
99423
- "aria-label": thumbnailsVisible ? "Hide thumbnails \u2014 labels only" : "Show thumbnails \u2014 posters stay visible; richer previews appear on interaction",
99403
+ "aria-label": thumbnailsVisible ? "Hide thumbnails: labels only" : "Show thumbnails: posters stay visible; richer previews appear on interaction",
99424
99404
  "aria-pressed": thumbnailsVisible,
99425
99405
  onClick: () => setThumbnailMode(thumbnailsVisible ? "hidden" : "adaptive"),
99426
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"}`,
99427
- children: /* @__PURE__ */ jsx249(Image3, { size: 16, "aria-hidden": "true" })
99407
+ children: /* @__PURE__ */ jsx250(Image3, { size: 16, "aria-hidden": "true" })
99428
99408
  }
99429
99409
  )
99430
99410
  }
99431
99411
  ),
99432
- /* @__PURE__ */ jsx249(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx249(
99412
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Fit timeline to width", children: /* @__PURE__ */ jsx250(
99433
99413
  "button",
99434
99414
  {
99435
99415
  type: "button",
@@ -99437,20 +99417,20 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99437
99417
  "aria-label": "Fit timeline to width",
99438
99418
  "aria-pressed": zoomMode === "fit",
99439
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"}`,
99440
- children: /* @__PURE__ */ jsx249(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
99420
+ children: /* @__PURE__ */ jsx250(ArrowsOutLineHorizontal, { size: 16, "aria-hidden": "true" })
99441
99421
  }
99442
99422
  ) }),
99443
- /* @__PURE__ */ jsx249(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx249(
99423
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Zoom out", children: /* @__PURE__ */ jsx250(
99444
99424
  "button",
99445
99425
  {
99446
99426
  type: "button",
99447
99427
  "aria-label": "Zoom out",
99448
99428
  onClick: () => zoomTimelineStep("out"),
99449
99429
  className: flatIdle,
99450
- children: /* @__PURE__ */ jsx249(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
99430
+ children: /* @__PURE__ */ jsx250(MagnifyingGlassMinus, { size: 16, "aria-hidden": "true" })
99451
99431
  }
99452
99432
  ) }),
99453
- /* @__PURE__ */ jsx249(
99433
+ /* @__PURE__ */ jsx250(
99454
99434
  "input",
99455
99435
  {
99456
99436
  type: "range",
@@ -99465,17 +99445,17 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99465
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"
99466
99446
  }
99467
99447
  ),
99468
- /* @__PURE__ */ jsx249(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx249(
99448
+ /* @__PURE__ */ jsx250(Tooltip, { label: "Zoom in", children: /* @__PURE__ */ jsx250(
99469
99449
  "button",
99470
99450
  {
99471
99451
  type: "button",
99472
99452
  "aria-label": "Zoom in",
99473
99453
  onClick: () => zoomTimelineStep("in"),
99474
99454
  className: flatIdle,
99475
- children: /* @__PURE__ */ jsx249(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
99455
+ children: /* @__PURE__ */ jsx250(MagnifyingGlassPlus, { size: 16, "aria-hidden": "true" })
99476
99456
  }
99477
99457
  ) }),
99478
- /* @__PURE__ */ jsx249(
99458
+ /* @__PURE__ */ jsx250(
99479
99459
  "span",
99480
99460
  {
99481
99461
  className: "ml-1 w-[38px] text-right font-mono text-[11px] tabular-nums text-neutral-500 select-none",
@@ -99488,18 +99468,18 @@ var TimelineToolbar = memo61(function TimelineToolbar2({
99488
99468
  });
99489
99469
 
99490
99470
  // src/components/StudioSplash.tsx
99491
- import { jsx as jsx250, jsxs as jsxs198 } from "react/jsx-runtime";
99471
+ import { jsx as jsx251, jsxs as jsxs199 } from "react/jsx-runtime";
99492
99472
  function StudioSplash({ waiting }) {
99493
- 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: [
99494
- /* @__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" }),
99495
- /* @__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: [
99496
99476
  "Waiting for preview server\u2026 run",
99497
99477
  " ",
99498
- /* @__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" })
99499
99479
  ] })
99500
- ] }) : /* @__PURE__ */ jsxs198("div", { className: "flex flex-col items-center gap-3 text-center px-6", role: "status", children: [
99501
- /* @__PURE__ */ jsx250("div", { className: "w-4 h-4 rounded-full bg-studio-accent animate-pulse motion-reduce:animate-none" }),
99502
- /* @__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" })
99503
99483
  ] }) });
99504
99484
  }
99505
99485
 
@@ -99769,7 +99749,7 @@ function useActiveComposition({
99769
99749
  }
99770
99750
 
99771
99751
  // src/App.tsx
99772
- import { jsx as jsx251, jsxs as jsxs199 } from "react/jsx-runtime";
99752
+ import { jsx as jsx252, jsxs as jsxs200 } from "react/jsx-runtime";
99773
99753
  var getTimelineSelectionSet = () => usePlayerStore.getState().selectedElementIds;
99774
99754
  function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
99775
99755
  const { projectId, resolving, waitingForServer } = useServerConnection();
@@ -100140,7 +100120,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100140
100120
  refreshPreviewDocumentVersion
100141
100121
  });
100142
100122
  const timelineToolbar = useMemo89(
100143
- () => /* @__PURE__ */ jsx251(
100123
+ () => /* @__PURE__ */ jsx252(
100144
100124
  TimelineToolbar,
100145
100125
  {
100146
100126
  domEditSession: toolbarSession,
@@ -100188,15 +100168,15 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100188
100168
  projectId
100189
100169
  );
100190
100170
  if (resolving || waitingForServer || !projectId)
100191
- return /* @__PURE__ */ jsx251(StudioSplash, { waiting: waitingForServer });
100192
- 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(
100193
100173
  "div",
100194
100174
  {
100195
100175
  className: "flex flex-col h-full w-full bg-neutral-950 relative",
100196
100176
  onDragOver: fileDrop.onDragOver,
100197
100177
  onDrop: fileDrop.onDrop,
100198
100178
  children: [
100199
- /* @__PURE__ */ jsx251(
100179
+ /* @__PURE__ */ jsx252(
100200
100180
  StudioHeader,
100201
100181
  {
100202
100182
  captureFrameHref: frameCapture.captureFrameHref,
@@ -100209,17 +100189,17 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100209
100189
  onExport: handleExport
100210
100190
  }
100211
100191
  ),
100212
- previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx251(
100192
+ previewPersistence.domEditSaveQueuePaused && !externalFileChanges.blocked && /* @__PURE__ */ jsx252(
100213
100193
  SaveQueuePausedBanner,
100214
100194
  {
100215
100195
  message: previewPersistence.domEditSaveQueuePaused,
100216
100196
  onRetry: previewPersistence.resetDomEditSaveQueueBreaker
100217
100197
  }
100218
100198
  ),
100219
- /* @__PURE__ */ jsx251(ExternalFileConflictBanner, { coordinator: externalFileChanges }),
100220
- sdkHandle.unreachableProject && /* @__PURE__ */ jsx251(ProjectUnreachableBanner, { projectId: sdkHandle.unreachableProject }),
100221
- sdkHandle.compositionMissing && activeCompPath && /* @__PURE__ */ jsx251(CompositionMissingBanner, { path: activeCompPath }),
100222
- /* @__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(
100223
100203
  EditorShell,
100224
100204
  {
100225
100205
  readOnlyPreview,
@@ -100263,7 +100243,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
100263
100243
  gestureOverlay
100264
100244
  }
100265
100245
  ),
100266
- /* @__PURE__ */ jsx251(
100246
+ /* @__PURE__ */ jsx252(
100267
100247
  StudioOverlays,
100268
100248
  {
100269
100249
  projectId,
@@ -100898,6 +100878,7 @@ export {
100898
100878
  Toggle,
100899
100879
  Tooltip,
100900
100880
  VideoThumbnail,
100881
+ announcePreviewDocumentLoaded,
100901
100882
  applyPatch,
100902
100883
  buildCompositionSnapTarget,
100903
100884
  buildGridSnapEdges,
@@ -100968,4 +100949,3 @@ export {
100968
100949
  useTimelinePlayer,
100969
100950
  zoomTimelineToRange
100970
100951
  };
100971
- //# sourceMappingURL=index.js.map