@hyperframes/studio 0.7.79 → 0.7.80

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 (50) hide show
  1. package/dist/assets/{hyperframes-player-mFah2TZE.js → hyperframes-player-Csai0_vh.js} +1 -1
  2. package/dist/assets/{index-Bqj3h_1a.js → index-B5aYTg-4.js} +1 -1
  3. package/dist/assets/{index-DlZMDyYs.js → index-BhdJN49y.js} +200 -200
  4. package/dist/assets/{index-hmnoiSEV.js → index-By-YiRxq.js} +1 -1
  5. package/dist/assets/index-D379YOKT.css +1 -0
  6. package/dist/index.d.ts +45 -3
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +652 -465
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/src/components/StudioRightPanel.tsx +2 -1
  12. package/src/components/editor/AnimationCard.test.tsx +225 -18
  13. package/src/components/editor/AnimationCard.tsx +24 -3
  14. package/src/components/editor/EaseCurveSection.test.tsx +147 -2
  15. package/src/components/editor/EaseCurveSection.tsx +77 -15
  16. package/src/components/editor/GsapAnimationSection.test.tsx +102 -0
  17. package/src/components/editor/GsapAnimationSection.tsx +6 -1
  18. package/src/components/editor/KeyframeEaseList.tsx +4 -0
  19. package/src/components/editor/MotionPathOverlay.tsx +54 -19
  20. package/src/components/editor/PropertyPanel.tsx +4 -0
  21. package/src/components/editor/PropertyPanelFlat.tsx +6 -91
  22. package/src/components/editor/gsapAnimationCallbacks.test.ts +8 -1
  23. package/src/components/editor/gsapAnimationCallbacks.ts +8 -0
  24. package/src/components/editor/holdEaseSeek.test.ts +37 -0
  25. package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +69 -0
  26. package/src/components/editor/propertyPanelFlatMotionSection.tsx +2 -1
  27. package/src/components/editor/propertyPanelFlatProps.ts +91 -0
  28. package/src/components/editor/propertyPanelTypes.ts +2 -0
  29. package/src/contexts/DomEditContext.tsx +4 -0
  30. package/src/hooks/gsapKeyframeCacheHelpers.test.ts +132 -43
  31. package/src/hooks/gsapKeyframeCacheHelpers.ts +150 -50
  32. package/src/hooks/gsapTweenSynth.test.ts +70 -15
  33. package/src/hooks/gsapTweenSynth.ts +50 -23
  34. package/src/hooks/keyframeCacheAstLoad.ts +4 -12
  35. package/src/hooks/useDomEditSession.test.tsx +152 -3
  36. package/src/hooks/useDomEditSession.ts +4 -53
  37. package/src/hooks/useGsapTweenCache.ts +40 -34
  38. package/src/hooks/useKeyframeEaseCommits.ts +78 -0
  39. package/src/player/components/KeyframeDiamondContextMenu.test.tsx +21 -0
  40. package/src/player/components/KeyframeDiamondContextMenu.tsx +19 -12
  41. package/src/player/components/TimelineClipDiamonds.test.tsx +18 -6
  42. package/src/player/components/TimelineDiamondConnectors.tsx +118 -85
  43. package/src/player/components/timelineDiamondTypes.ts +4 -3
  44. package/src/player/components/timelineKeyframeIdentity.ts +3 -0
  45. package/src/player/components/useTimelineKeyframeHandlers.test.tsx +52 -32
  46. package/src/player/components/useTimelineKeyframeHandlers.ts +1 -0
  47. package/src/player/hooks/useExpandedTimelineElements.test.ts +29 -0
  48. package/src/player/hooks/useExpandedTimelineElements.ts +24 -0
  49. package/src/player/store/keyframeSlice.ts +15 -5
  50. package/dist/assets/index-gGVKuFg5.css +0 -1
package/dist/index.js CHANGED
@@ -3586,7 +3586,15 @@ function clampChildToParent(child, parentStart, parentEnd) {
3586
3586
  const clampedDuration = Math.min(childEnd, parentEnd) - clampedStart;
3587
3587
  return clampedDuration > 0 ? { start: clampedStart, duration: clampedDuration } : null;
3588
3588
  }
3589
- function buildChildElements(siblings, display, editBasis, expandedHostKey) {
3589
+ function hostElementState(flat) {
3590
+ if (!flat) return {};
3591
+ return {
3592
+ hidden: flat.hidden,
3593
+ timelineLocked: flat.timelineLocked,
3594
+ timelineRole: flat.timelineRole
3595
+ };
3596
+ }
3597
+ function buildChildElements(siblings, display, editBasis, expandedHostKey, elements) {
3590
3598
  const result = [];
3591
3599
  for (const child of siblings) {
3592
3600
  const clamped = clampChildToParent(child, display.start, display.end);
@@ -3607,6 +3615,7 @@ function buildChildElements(siblings, display, editBasis, expandedHostKey) {
3607
3615
  });
3608
3616
  result.push({
3609
3617
  ...base2,
3618
+ ...hostElementState(elements.find((element) => element.key === key)),
3610
3619
  key,
3611
3620
  start: clamped.start,
3612
3621
  duration: clamped.duration,
@@ -3681,7 +3690,8 @@ function buildExpandedElements(elements, manifest, parentMap, topLevelId, siblin
3681
3690
  track: topLevelElement.track
3682
3691
  },
3683
3692
  editBasis,
3684
- parentKey
3693
+ parentKey,
3694
+ elements
3685
3695
  );
3686
3696
  if (expanded.length === 0) return filterToTopLevel(elements, parentMap);
3687
3697
  const drillPath = /* @__PURE__ */ new Set();
@@ -7212,23 +7222,50 @@ function EaseParameterField({
7212
7222
  if (wiggleConfig !== null) return /* @__PURE__ */ jsx18(WiggleField, { config: wiggleConfig, onCommit });
7213
7223
  return /* @__PURE__ */ jsx18(EaseBezierField, { tuple, onCommit });
7214
7224
  }
7225
+ var PENDING_EASE_TIMEOUT_MS = 2e3;
7215
7226
  function EaseCurveSection({
7216
7227
  ease,
7217
- onCustomEaseCommit
7228
+ onCustomEaseCommit,
7229
+ collidingAnimationTargets
7218
7230
  }) {
7219
- const springBounce = parseSpringBounce3(ease);
7231
+ const [pendingEase, setPendingEase] = useState9(null);
7232
+ const inFlightEasesRef = useRef12([]);
7233
+ const displayedEase = pendingEase ?? ease;
7234
+ const springBounce = parseSpringBounce3(displayedEase);
7220
7235
  const isSpring = springBounce !== null;
7221
- const wiggleConfig = parseWiggleEase3(ease);
7236
+ const wiggleConfig = parseWiggleEase3(displayedEase);
7222
7237
  const isWiggle = wiggleConfig !== null;
7223
7238
  const mode = isSpring ? "spring" : isWiggle ? "wiggle" : "curve";
7224
- const curve = resolveEditableCurve(ease, springBounce);
7239
+ const curve = resolveEditableCurve(displayedEase, springBounce);
7225
7240
  const [draft, setDraft] = useState9(null);
7226
7241
  const [hover, setHover] = useState9(null);
7227
7242
  const draggingRef = useRef12(null);
7228
7243
  const svgRef = useRef12(null);
7229
7244
  useEffect11(() => {
7230
7245
  setDraft(null);
7246
+ const inFlight = inFlightEasesRef.current;
7247
+ const landed = inFlight.indexOf(ease);
7248
+ if (landed < 0) {
7249
+ inFlight.length = 0;
7250
+ setPendingEase(null);
7251
+ return;
7252
+ }
7253
+ inFlight.splice(0, landed + 1);
7254
+ if (inFlight.length === 0) setPendingEase(null);
7231
7255
  }, [ease]);
7256
+ useEffect11(() => {
7257
+ if (pendingEase === null) return;
7258
+ const timer = setTimeout(() => {
7259
+ inFlightEasesRef.current.length = 0;
7260
+ setPendingEase(null);
7261
+ }, PENDING_EASE_TIMEOUT_MS);
7262
+ return () => clearTimeout(timer);
7263
+ }, [pendingEase]);
7264
+ const commitEase = (nextEase) => {
7265
+ inFlightEasesRef.current.push(nextEase);
7266
+ setPendingEase(nextEase);
7267
+ onCustomEaseCommit(nextEase);
7268
+ };
7232
7269
  const activeTuple = draft ?? curve;
7233
7270
  const displayTuple = activeTuple ?? DEFAULT_CURVE;
7234
7271
  const [x1, y1, x2, y2] = displayTuple;
@@ -7236,8 +7273,8 @@ function EaseCurveSection({
7236
7273
  const a1 = { x: xToSvg(1), y: yToSvg(1) };
7237
7274
  const p1 = { x: xToSvg(x1), y: yToSvg(clampView(y1)) };
7238
7275
  const p2 = { x: xToSvg(x2), y: yToSvg(clampView(y2)) };
7239
- const curvePath2 = curvePathFor(ease, springBounce, wiggleConfig, displayTuple);
7240
- const showGraph = activeTuple !== null || isWiggle || ease === "hold";
7276
+ const curvePath2 = curvePathFor(displayedEase, springBounce, wiggleConfig, displayTuple);
7277
+ const showGraph = activeTuple !== null || isWiggle || displayedEase === "hold";
7241
7278
  const showHandles = curve !== null && !isSpring && !isWiggle;
7242
7279
  const handlePointerDown = (handle, e) => {
7243
7280
  e.preventDefault();
@@ -7262,8 +7299,7 @@ function EaseCurveSection({
7262
7299
  if (!draggingRef.current || !draft) return;
7263
7300
  draggingRef.current = null;
7264
7301
  const path = `M0,0 C${draft[0]},${draft[1]} ${draft[2]},${draft[3]} 1,1`;
7265
- onCustomEaseCommit(`custom(${path})`);
7266
- queueMicrotask(() => setDraft(null));
7302
+ commitEase(`custom(${path})`);
7267
7303
  };
7268
7304
  const handleKeyDown = (handle, event) => {
7269
7305
  const next = nudgeCurve(displayTuple, handle, event.key, event.shiftKey ? 0.1 : 0.01);
@@ -7271,17 +7307,21 @@ function EaseCurveSection({
7271
7307
  event.preventDefault();
7272
7308
  event.stopPropagation();
7273
7309
  setDraft(next);
7274
- onCustomEaseCommit(`custom(M0,0 C${next[0]},${next[1]} ${next[2]},${next[3]} 1,1)`);
7275
- queueMicrotask(() => setDraft(null));
7310
+ commitEase(`custom(M0,0 C${next[0]},${next[1]} ${next[2]},${next[3]} 1,1)`);
7276
7311
  };
7277
7312
  const top = yToSvg(1);
7278
7313
  const bottom = yToSvg(0);
7279
7314
  const left = xToSvg(0);
7280
7315
  const right = xToSvg(1);
7281
- const label = resolveEditorLabel(ease, springBounce, isWiggle);
7316
+ const label = resolveEditorLabel(displayedEase, springBounce, isWiggle);
7282
7317
  return /* @__PURE__ */ jsxs15("div", { className: "rounded-lg bg-neutral-900/50 p-2", children: [
7283
- /* @__PURE__ */ jsx18(EaseTypeDropdown, { kind: mode, ease, label, onSelect: onCustomEaseCommit }),
7284
- /* @__PURE__ */ jsx18(EaseModeToggle, { mode, onCommit: onCustomEaseCommit }),
7318
+ /* @__PURE__ */ jsx18(EaseTypeDropdown, { kind: mode, ease: displayedEase, label, onSelect: commitEase }),
7319
+ collidingAnimationTargets && collidingAnimationTargets.length > 1 && /* @__PURE__ */ jsxs15("p", { className: "mb-1 text-[9px] text-neutral-500", children: [
7320
+ "Applies to ",
7321
+ collidingAnimationTargets.length,
7322
+ " animations"
7323
+ ] }),
7324
+ /* @__PURE__ */ jsx18(EaseModeToggle, { mode, onCommit: commitEase }),
7285
7325
  /* @__PURE__ */ jsxs15("span", { className: "sr-only", "aria-live": "polite", children: [
7286
7326
  MODE_LABELS[mode],
7287
7327
  " ease editor selected"
@@ -7441,7 +7481,7 @@ function EaseCurveSection({
7441
7481
  springBounce,
7442
7482
  wiggleConfig,
7443
7483
  tuple: displayTuple,
7444
- onCommit: onCustomEaseCommit
7484
+ onCommit: commitEase
7445
7485
  }
7446
7486
  )
7447
7487
  ] }) : /* @__PURE__ */ jsxs15("p", { className: "px-0.5 py-1.5 text-[10px] leading-relaxed text-neutral-500", children: [
@@ -7463,7 +7503,6 @@ function TimelineDiamondConnectors({
7463
7503
  keyframeTarget: keyframeTarget2,
7464
7504
  onSelectSegment
7465
7505
  }) {
7466
- const pressXRef = useRef13(null);
7467
7506
  return /* @__PURE__ */ jsx19(Fragment9, { children: markers.map((marker, i) => {
7468
7507
  const previous = markers[i - 1];
7469
7508
  if (!previous) return null;
@@ -7473,9 +7512,6 @@ function TimelineDiamondConnectors({
7473
7512
  if (x2 - x1 < 1) return null;
7474
7513
  const connectorLeft = x1 + previous.visualSize / 2;
7475
7514
  const connectorWidth = x2 - x1 - previous.visualSize / 2 - marker.visualSize / 2;
7476
- const target = keyframeTarget2(kf);
7477
- const ease = kf.ease ?? globalEase;
7478
- const roomForFullTarget = connectorWidth >= 24;
7479
7515
  return /* @__PURE__ */ jsxs16(Fragment8, { children: [
7480
7516
  /* @__PURE__ */ jsx19(
7481
7517
  "div",
@@ -7494,66 +7530,90 @@ function TimelineDiamondConnectors({
7494
7530
  }
7495
7531
  }
7496
7532
  ),
7497
- onSelectSegment && !kf.easeAmbiguous && kf.animationId !== void 0 && /* @__PURE__ */ jsx19(
7498
- "div",
7533
+ onSelectSegment && showsEaseControl(kf) && /* @__PURE__ */ jsx19(
7534
+ SegmentEaseControl,
7535
+ {
7536
+ left: x1,
7537
+ width: x2 - x1,
7538
+ centerY,
7539
+ ease: kf.ease ?? globalEase,
7540
+ target: keyframeTarget2(kf),
7541
+ roomForFullTarget: connectorWidth >= 24,
7542
+ onSelectSegment
7543
+ }
7544
+ )
7545
+ ] }, `line-${i}-${previous.keyframe.percentage}-${kf.percentage}`);
7546
+ }) });
7547
+ }
7548
+ function showsEaseControl(kf) {
7549
+ return kf.animationId !== void 0;
7550
+ }
7551
+ function SegmentEaseControl({
7552
+ left,
7553
+ width,
7554
+ centerY,
7555
+ ease,
7556
+ target,
7557
+ roomForFullTarget,
7558
+ onSelectSegment
7559
+ }) {
7560
+ const pressXRef = useRef13(null);
7561
+ return /* @__PURE__ */ jsx19(
7562
+ "div",
7563
+ {
7564
+ className: "group absolute",
7565
+ "data-keyframe-ease-segment": "",
7566
+ style: {
7567
+ left,
7568
+ top: centerY,
7569
+ width,
7570
+ height: 18,
7571
+ transform: "translateY(-50%)",
7572
+ // Own a stacking context above the diamond buttons. At fit zoom the 16px
7573
+ // ease control can overlap its neighbouring diamond; without a z-index
7574
+ // here the later diamond wins the hit test even though the child button
7575
+ // has z-index 3.
7576
+ zIndex: 3,
7577
+ // Only the centered control is interactive. The transparent segment
7578
+ // wrapper must not swallow connector/clip gestures.
7579
+ pointerEvents: "none"
7580
+ },
7581
+ children: /* @__PURE__ */ jsx19(
7582
+ "button",
7499
7583
  {
7500
- className: "group absolute",
7501
- "data-keyframe-ease-segment": "",
7584
+ type: "button",
7585
+ "data-keyframe-ease-button": "",
7586
+ "aria-label": `Edit ${ease} easing`,
7587
+ title: `Edit ${ease} easing`,
7588
+ className: `absolute flex items-center justify-center rounded opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 ${roomForFullTarget ? "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']" : ""}`,
7502
7589
  style: {
7503
- left: x1,
7504
- top: centerY,
7505
- width: x2 - x1,
7506
- height: 18,
7507
- transform: "translateY(-50%)",
7508
- // Own a stacking context above the diamond buttons. At fit
7509
- // zoom the 16px ease control can overlap its neighbouring
7510
- // diamond; without a z-index here the later diamond wins the
7511
- // hit test even though the child button has z-index 3.
7590
+ left: "50%",
7591
+ top: "50%",
7592
+ width: 16,
7593
+ height: 16,
7594
+ transform: "translate(-50%, -50%)",
7512
7595
  zIndex: 3,
7513
- // Only the centered control is interactive. The transparent
7514
- // segment wrapper must not swallow connector/clip gestures.
7515
- pointerEvents: "none"
7596
+ pointerEvents: "auto",
7597
+ padding: 0,
7598
+ border: "1px solid rgba(255, 255, 255, 0.14)",
7599
+ background: "#171717",
7600
+ cursor: "pointer"
7516
7601
  },
7517
- children: /* @__PURE__ */ jsx19(
7518
- "button",
7519
- {
7520
- type: "button",
7521
- "data-keyframe-ease-button": "",
7522
- "aria-label": `Edit ${ease} easing`,
7523
- title: `Edit ${ease} easing`,
7524
- className: `absolute flex items-center justify-center rounded opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 ${roomForFullTarget ? "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']" : ""}`,
7525
- style: {
7526
- left: "50%",
7527
- top: "50%",
7528
- width: 16,
7529
- height: 16,
7530
- transform: "translate(-50%, -50%)",
7531
- zIndex: 3,
7532
- pointerEvents: "auto",
7533
- padding: 0,
7534
- border: "1px solid rgba(255, 255, 255, 0.14)",
7535
- background: "#171717",
7536
- cursor: "pointer"
7537
- },
7538
- onPointerDown: (e) => {
7539
- pressXRef.current = e.clientX;
7540
- },
7541
- onClick: (e) => {
7542
- e.stopPropagation();
7543
- const pressX = pressXRef.current;
7544
- pressXRef.current = null;
7545
- if (pressX !== null && Math.abs(e.clientX - pressX) >= KEYFRAME_DRAG_THRESHOLD_PX) {
7546
- return;
7547
- }
7548
- onSelectSegment(target);
7549
- },
7550
- children: /* @__PURE__ */ jsx19(MiniCurveSvg, { ease, active: true, size: 12 })
7551
- }
7552
- )
7602
+ onPointerDown: (e) => {
7603
+ pressXRef.current = e.clientX;
7604
+ },
7605
+ onClick: (e) => {
7606
+ e.stopPropagation();
7607
+ const pressX = pressXRef.current;
7608
+ pressXRef.current = null;
7609
+ if (pressX !== null && Math.abs(e.clientX - pressX) >= KEYFRAME_DRAG_THRESHOLD_PX) return;
7610
+ onSelectSegment(target);
7611
+ },
7612
+ children: /* @__PURE__ */ jsx19(MiniCurveSvg, { ease, active: true, size: 12 })
7553
7613
  }
7554
7614
  )
7555
- ] }, `line-${i}-${previous.keyframe.percentage}-${kf.percentage}`);
7556
- }) });
7615
+ }
7616
+ );
7557
7617
  }
7558
7618
 
7559
7619
  // src/components/editor/KeyframeNavigation.tsx
@@ -7800,7 +7860,8 @@ function keyframeTarget(keyframe) {
7800
7860
  percentage: keyframe.percentage,
7801
7861
  tweenPercentage: keyframe.tweenPercentage,
7802
7862
  propertyGroup: keyframe.propertyGroup,
7803
- animationId: keyframe.animationId
7863
+ animationId: keyframe.animationId,
7864
+ collidingAnimationTargets: keyframe.collidingAnimationTargets
7804
7865
  };
7805
7866
  }
7806
7867
 
@@ -8440,16 +8501,24 @@ function isStaticPositionHold(anim) {
8440
8501
  const propKeys = Object.keys(anim.properties).filter((k) => k !== "immediateRender");
8441
8502
  return propKeys.length > 0 && propKeys.every((k) => k === "x" || k === "y");
8442
8503
  }
8504
+ function accumulateCollidingAnimationTargets(keyframe, incoming) {
8505
+ const primaryId = keyframe.animationId;
8506
+ if (primaryId === incoming.animationId) return;
8507
+ const collisionTargets = keyframe.collidingAnimationTargets;
8508
+ if (collisionTargets?.some((target) => target.animationId === incoming.animationId)) return;
8509
+ keyframe.collidingAnimationTargets = [
8510
+ ...collisionTargets === void 0 || collisionTargets.length === 0 ? [{ animationId: primaryId, tweenPercentage: keyframe.tweenPercentage }] : collisionTargets,
8511
+ { animationId: incoming.animationId, tweenPercentage: incoming.tweenPercentage }
8512
+ ];
8513
+ }
8443
8514
  function deduplicateKeyframes(keyframes) {
8444
8515
  const byPct = /* @__PURE__ */ new Map();
8445
8516
  for (const kf of keyframes) {
8446
8517
  const existing = byPct.get(kf.percentage);
8447
8518
  if (existing) {
8448
8519
  existing.properties = { ...existing.properties, ...kf.properties };
8449
- if (existing.animationId !== void 0 && kf.animationId !== void 0 && existing.animationId !== kf.animationId) {
8450
- existing.easeAmbiguous = true;
8451
- }
8452
- if (existing.easeAmbiguous) delete existing.ease;
8520
+ accumulateCollidingAnimationTargets(existing, kf);
8521
+ if ((existing.collidingAnimationTargets?.length ?? 0) > 1) delete existing.ease;
8453
8522
  else if (kf.ease) existing.ease = kf.ease;
8454
8523
  } else {
8455
8524
  byPct.set(kf.percentage, { ...kf, properties: { ...kf.properties } });
@@ -13519,7 +13588,7 @@ var KeyframeDiamondContextMenu = memo11(function KeyframeDiamondContextMenu2({
13519
13588
  animationId: state.animationId
13520
13589
  };
13521
13590
  const menuWidth = 200;
13522
- const menuHeight = onMoveToPlayhead ? 100 : 70;
13591
+ const menuHeight = 10 + (1 + (onMoveToPlayhead ? 1 : 0) + (onDelete ? 1 : 0)) * 30;
13523
13592
  const overflowY = state.y + menuHeight - window.innerHeight;
13524
13593
  const adjustedX = state.x + menuWidth > window.innerWidth ? state.x - menuWidth : state.x;
13525
13594
  const adjustedY = overflowY > 0 ? state.y - overflowY - 8 : state.y;
@@ -13543,7 +13612,7 @@ var KeyframeDiamondContextMenu = memo11(function KeyframeDiamondContextMenu2({
13543
13612
  children: "Move to Playhead"
13544
13613
  }
13545
13614
  ),
13546
- /* @__PURE__ */ jsx32(
13615
+ onDelete && /* @__PURE__ */ jsx32(
13547
13616
  "button",
13548
13617
  {
13549
13618
  type: "button",
@@ -14025,7 +14094,8 @@ function DomEditProvider({
14025
14094
  commitMutation,
14026
14095
  applyMarqueeSelection,
14027
14096
  handleUpdateKeyframeEase,
14028
- handleSetAllKeyframeEases
14097
+ handleSetAllKeyframeEases,
14098
+ handleUpdateSegmentEase
14029
14099
  },
14030
14100
  children
14031
14101
  }) {
@@ -14102,6 +14172,7 @@ function DomEditProvider({
14102
14172
  commitMutation: stableCommitMutation,
14103
14173
  applyMarqueeSelection,
14104
14174
  handleUpdateKeyframeEase,
14175
+ handleUpdateSegmentEase,
14105
14176
  handleSetAllKeyframeEases
14106
14177
  }),
14107
14178
  [
@@ -14170,6 +14241,7 @@ function DomEditProvider({
14170
14241
  stableCommitMutation,
14171
14242
  applyMarqueeSelection,
14172
14243
  handleUpdateKeyframeEase,
14244
+ handleUpdateSegmentEase,
14173
14245
  handleSetAllKeyframeEases
14174
14246
  ]
14175
14247
  );
@@ -14835,6 +14907,7 @@ function useTimelineKeyframeHandlers({
14835
14907
  if (target.animationId !== void 0 && target.tweenPercentage !== void 0) {
14836
14908
  usePlayerStore.getState().setFocusedEaseSegment({
14837
14909
  animationId: target.animationId,
14910
+ collidingAnimationTargets: target.collidingAnimationTargets,
14838
14911
  tweenPercentage: target.tweenPercentage,
14839
14912
  elementId: elId
14840
14913
  });
@@ -24898,6 +24971,154 @@ var DomEditOverlay = memo22(function DomEditOverlay2({
24898
24971
  );
24899
24972
  });
24900
24973
 
24974
+ // src/hooks/gsapKeyframeCacheHelpers.ts
24975
+ function sameEntries(before, after) {
24976
+ if (before.size !== after.size) return false;
24977
+ for (const [key, value] of before) {
24978
+ if (after.get(key) !== value) return false;
24979
+ }
24980
+ return true;
24981
+ }
24982
+ function publishKeyframeCache(edit) {
24983
+ const { keyframeCache, gsapAnimations } = usePlayerStore.getState();
24984
+ const draft = {
24985
+ keyframeCache: new Map(keyframeCache),
24986
+ gsapAnimations: new Map(gsapAnimations)
24987
+ };
24988
+ edit(draft);
24989
+ if (sameEntries(keyframeCache, draft.keyframeCache) && sameEntries(gsapAnimations, draft.gsapAnimations))
24990
+ return;
24991
+ usePlayerStore.setState({
24992
+ keyframeCache: draft.keyframeCache,
24993
+ gsapAnimations: draft.gsapAnimations
24994
+ });
24995
+ }
24996
+ function updateKeyframeCacheFromParsed(animations, targetPath, selectionId, mutation, doc) {
24997
+ const { elements, domClipChildren } = usePlayerStore.getState();
24998
+ const idsWithKeyframes = /* @__PURE__ */ new Set();
24999
+ const merged = /* @__PURE__ */ new Map();
25000
+ const sourceAnimations = /* @__PURE__ */ new Map();
25001
+ for (const anim of animations) {
25002
+ const kfSource = anim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(anim)?.keyframes ?? [];
25003
+ if (kfSource.length === 0) continue;
25004
+ for (const id of resolveSelectorElementIds(anim.targetSelector, doc)) {
25005
+ idsWithKeyframes.add(id);
25006
+ sourceAnimations.set(id, [...sourceAnimations.get(id) ?? [], anim]);
25007
+ const { elStart, elDuration } = resolveClipTimingBasis(
25008
+ id,
25009
+ targetPath,
25010
+ elements,
25011
+ domClipChildren
25012
+ );
25013
+ const clipKeyframes = toClipKeyframes(kfSource, anim, elStart, elDuration);
25014
+ const existing = merged.get(id);
25015
+ if (existing) {
25016
+ existing.keyframes = deduplicateKeyframes([...existing.keyframes, ...clipKeyframes]);
25017
+ } else {
25018
+ merged.set(id, {
25019
+ ...anim.keyframes,
25020
+ format: anim.keyframes?.format ?? "percentage",
25021
+ keyframes: clipKeyframes
25022
+ });
25023
+ }
25024
+ }
25025
+ }
25026
+ const mutationSelector = mutation.targetSelector;
25027
+ const mutated = mutationSelector ? resolveSelectorElementIds(mutationSelector, doc) : [];
25028
+ const targetIds = mutated.length > 0 ? mutated : selectionId ? [selectionId] : [];
25029
+ publishKeyframeCache((draft) => {
25030
+ for (const [id, entry] of merged) {
25031
+ writeElementIntoDraft(draft, targetPath, id, entry, sourceAnimations.get(id));
25032
+ }
25033
+ for (const targetId of targetIds) {
25034
+ if (!idsWithKeyframes.has(targetId)) deleteElementFromDraft(draft, targetPath, targetId);
25035
+ }
25036
+ });
25037
+ }
25038
+ function writeElementIntoDraft(draft, sourceFile, elementId, entry, animations) {
25039
+ for (const key of elementCacheKeys(sourceFile, elementId)) {
25040
+ draft.keyframeCache.set(key, entry);
25041
+ if (animations) draft.gsapAnimations.set(key, animations);
25042
+ else draft.gsapAnimations.delete(key);
25043
+ }
25044
+ }
25045
+ function deleteElementFromDraft(draft, sourceFile, elementId) {
25046
+ for (const key of elementCacheKeys(sourceFile, elementId)) {
25047
+ draft.keyframeCache.delete(key);
25048
+ draft.gsapAnimations.delete(key);
25049
+ }
25050
+ }
25051
+ function clearKeyframeCacheForElement(sourceFile, elementId) {
25052
+ publishKeyframeCache((draft) => deleteElementFromDraft(draft, sourceFile, elementId));
25053
+ }
25054
+ function cachedElementIdsForFile(sourceFile, keyframeCache, gsapAnimations) {
25055
+ const sfPrefix = `${sourceFile}#`;
25056
+ const ids = /* @__PURE__ */ new Set();
25057
+ for (const key of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
25058
+ if (!key.startsWith(sfPrefix)) continue;
25059
+ ids.add(key.slice(sfPrefix.length));
25060
+ }
25061
+ return ids;
25062
+ }
25063
+ function pruneKeyframeCacheToFiles(files) {
25064
+ const keep = new Set(files);
25065
+ const { keyframeCache, gsapAnimations } = usePlayerStore.getState();
25066
+ const stale = /* @__PURE__ */ new Map();
25067
+ for (const key of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
25068
+ const hash = key.indexOf("#");
25069
+ if (hash < 0) continue;
25070
+ const sourceFile = key.slice(0, hash);
25071
+ if (keep.has(sourceFile)) continue;
25072
+ const ids = stale.get(sourceFile) ?? /* @__PURE__ */ new Set();
25073
+ ids.add(key.slice(hash + 1));
25074
+ stale.set(sourceFile, ids);
25075
+ }
25076
+ publishKeyframeCache((draft) => {
25077
+ for (const [sourceFile, ids] of stale) {
25078
+ for (const id of ids) deleteElementFromDraft(draft, sourceFile, id);
25079
+ }
25080
+ });
25081
+ }
25082
+ function scopedElementKey(element) {
25083
+ return `${element.sourceFile || "index.html"}#${element.id}`;
25084
+ }
25085
+ function elementCacheKeys(sourceFile, elementId) {
25086
+ return sourceFile === "index.html" ? [`index.html#${elementId}`, elementId] : [`${sourceFile}#${elementId}`, `index.html#${elementId}`, elementId];
25087
+ }
25088
+ function replaceKeyframeCacheForFile(sourceFile, entries, animationsByElement) {
25089
+ publishKeyframeCache((draft) => {
25090
+ for (const id of cachedElementIdsForFile(
25091
+ sourceFile,
25092
+ draft.keyframeCache,
25093
+ draft.gsapAnimations
25094
+ )) {
25095
+ deleteElementFromDraft(draft, sourceFile, id);
25096
+ }
25097
+ for (const [id, entry] of entries) {
25098
+ writeElementIntoDraft(draft, sourceFile, id, entry, animationsByElement.get(id));
25099
+ }
25100
+ });
25101
+ }
25102
+ function writeGsapAnimationsForElement(sourceFile, elementId, animations) {
25103
+ publishKeyframeCache((draft) => {
25104
+ for (const key of elementCacheKeys(sourceFile, elementId)) {
25105
+ if (animations) draft.gsapAnimations.set(key, animations);
25106
+ else draft.gsapAnimations.delete(key);
25107
+ }
25108
+ });
25109
+ }
25110
+ function buildCacheKey(sourceFile, elementId) {
25111
+ return `${sourceFile}#${elementId}`;
25112
+ }
25113
+ function readKeyframeSnapshot(sourceFile, elementId) {
25114
+ if (!elementId) return void 0;
25115
+ return usePlayerStore.getState().keyframeCache.get(buildCacheKey(sourceFile, elementId));
25116
+ }
25117
+ function writeKeyframeCache(sourceFile, elementId, data) {
25118
+ if (!elementId) return;
25119
+ usePlayerStore.getState().setKeyframeCache(buildCacheKey(sourceFile, elementId), data);
25120
+ }
25121
+
24901
25122
  // src/components/editor/MotionPathOverlay.tsx
24902
25123
  import { memo as memo23, useEffect as useEffect30, useRef as useRef43, useState as useState32 } from "react";
24903
25124
 
@@ -26017,7 +26238,7 @@ var MotionPathOverlay = memo23(function MotionPathOverlay2({
26017
26238
  const activeKeyframePct = usePlayerStore((s) => s.activeKeyframePct);
26018
26239
  const timelineElement = usePlayerStore((state) => {
26019
26240
  if (!selection) return void 0;
26020
- const sourceScopedId = `${selection.sourceFile || "index.html"}#${selection.id}`;
26241
+ const sourceScopedId = scopedElementKey(selection);
26021
26242
  return state.elements.find(
26022
26243
  (element) => (element.key ?? element.id) === sourceScopedId || element.id === selection.id
26023
26244
  );
@@ -26242,18 +26463,31 @@ var MotionPathOverlay = memo23(function MotionPathOverlay2({
26242
26463
  };
26243
26464
  const elementId = selection?.id ?? null;
26244
26465
  const onNodeContextMenu = (e, ref) => {
26245
- if (ref.type !== "keyframe" || !animId || !elementId || !timelineElement) return;
26466
+ if (!animId || !elementId || !timelineElement) return;
26246
26467
  e.preventDefault();
26247
26468
  e.stopPropagation();
26248
26469
  setKfMenu({
26249
- x: e.clientX,
26250
- y: e.clientY,
26251
- element: timelineElement,
26252
- elementId,
26253
- percentage: ref.pct,
26254
- tweenPercentage: ref.pct
26470
+ ref,
26471
+ state: {
26472
+ x: e.clientX,
26473
+ y: e.clientY,
26474
+ element: timelineElement,
26475
+ elementId,
26476
+ // A waypoint carries no percentage of its own: one tween-level ease owns
26477
+ // every segment, and its index is the identity the delete acts on. The
26478
+ // menu never reads this for a waypoint, because the only entry that
26479
+ // would (Move to Playhead) is hidden below.
26480
+ percentage: ref.type === "keyframe" ? ref.pct : 0,
26481
+ tweenPercentage: ref.type === "keyframe" ? ref.pct : 0
26482
+ }
26255
26483
  });
26256
26484
  };
26485
+ const menuRef = kfMenu?.ref;
26486
+ const onMenuDelete = menuRef === void 0 || !animId ? void 0 : menuRef.type === "keyframe" ? (_elId, keyframe) => {
26487
+ handleGsapRemoveKeyframe(animId, keyframe.percentage);
26488
+ } : removable ? () => {
26489
+ void commitRemoveWaypoint(animId, menuRef.index, commitMutation);
26490
+ } : void 0;
26257
26491
  return /* @__PURE__ */ jsxs48(Fragment21, { children: [
26258
26492
  /* @__PURE__ */ jsxs48(
26259
26493
  "svg",
@@ -26342,11 +26576,11 @@ var MotionPathOverlay = memo23(function MotionPathOverlay2({
26342
26576
  kfMenu && /* @__PURE__ */ jsx62(
26343
26577
  KeyframeDiamondContextMenu,
26344
26578
  {
26345
- state: kfMenu,
26579
+ state: kfMenu.state,
26346
26580
  onClose: () => setKfMenu(null),
26347
- onDelete: (_elId, keyframe) => animId && handleGsapRemoveKeyframe(animId, keyframe.percentage),
26581
+ onDelete: onMenuDelete,
26348
26582
  onDeleteAll: () => animId && handleGsapRemoveAllKeyframes(animId),
26349
- onMoveToPlayhead: (_element, keyframe) => animId && handleGsapMoveKeyframeToPlayhead(animId, keyframe.percentage)
26583
+ onMoveToPlayhead: kfMenu.ref.type === "keyframe" ? (_element, keyframe) => animId && handleGsapMoveKeyframeToPlayhead(animId, keyframe.percentage) : void 0
26350
26584
  }
26351
26585
  )
26352
26586
  ] });
@@ -29238,106 +29472,6 @@ async function tryGsapRotationIntercept(selection, angle, animations, iframe, co
29238
29472
  return true;
29239
29473
  }
29240
29474
 
29241
- // src/hooks/gsapKeyframeCacheHelpers.ts
29242
- function updateKeyframeCacheFromParsed(animations, targetPath, selectionId, mutation, doc) {
29243
- const { setKeyframeCache, elements, domClipChildren } = usePlayerStore.getState();
29244
- const idsWithKeyframes = /* @__PURE__ */ new Set();
29245
- const merged = /* @__PURE__ */ new Map();
29246
- const sourceAnimations = /* @__PURE__ */ new Map();
29247
- for (const anim of animations) {
29248
- const kfSource = anim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(anim)?.keyframes ?? [];
29249
- if (kfSource.length === 0) continue;
29250
- for (const id of resolveSelectorElementIds(anim.targetSelector, doc)) {
29251
- idsWithKeyframes.add(id);
29252
- sourceAnimations.set(id, [...sourceAnimations.get(id) ?? [], anim]);
29253
- const { elStart, elDuration } = resolveClipTimingBasis(
29254
- id,
29255
- targetPath,
29256
- elements,
29257
- domClipChildren
29258
- );
29259
- const clipKeyframes = toClipKeyframes(kfSource, anim, elStart, elDuration);
29260
- const existing = merged.get(id);
29261
- if (existing) {
29262
- existing.keyframes = deduplicateKeyframes([...existing.keyframes, ...clipKeyframes]);
29263
- } else {
29264
- merged.set(id, {
29265
- ...anim.keyframes,
29266
- format: anim.keyframes?.format ?? "percentage",
29267
- keyframes: clipKeyframes
29268
- });
29269
- }
29270
- }
29271
- }
29272
- for (const [id, entry] of merged) {
29273
- for (const key of elementCacheKeys(targetPath, id)) setKeyframeCache(key, entry);
29274
- writeGsapAnimationsForElement(targetPath, id, sourceAnimations.get(id));
29275
- }
29276
- const mutationSelector = mutation.targetSelector;
29277
- const mutated = mutationSelector ? resolveSelectorElementIds(mutationSelector, doc) : [];
29278
- const targetIds = mutated.length > 0 ? mutated : selectionId ? [selectionId] : [];
29279
- for (const targetId of targetIds) {
29280
- if (!idsWithKeyframes.has(targetId)) clearKeyframeCacheForElement(targetPath, targetId);
29281
- }
29282
- }
29283
- function clearKeyframeCacheForElement(sourceFile, elementId) {
29284
- const { keyframeCache, setKeyframeCache, gsapAnimations, setGsapAnimations } = usePlayerStore.getState();
29285
- const keys = elementCacheKeys(sourceFile, elementId);
29286
- for (const key of keys) {
29287
- if (keyframeCache.has(key)) setKeyframeCache(key, void 0);
29288
- if (gsapAnimations.has(key)) setGsapAnimations(key, void 0);
29289
- }
29290
- }
29291
- function clearKeyframeCacheForFile(sourceFile) {
29292
- const { keyframeCache, gsapAnimations } = usePlayerStore.getState();
29293
- const sfPrefix = `${sourceFile}#`;
29294
- const ids = /* @__PURE__ */ new Set();
29295
- for (const key of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
29296
- if (!key.startsWith(sfPrefix)) continue;
29297
- ids.add(key.slice(sfPrefix.length));
29298
- }
29299
- for (const id of ids) {
29300
- clearKeyframeCacheForElement(sourceFile, id);
29301
- }
29302
- }
29303
- function pruneKeyframeCacheToFiles(files) {
29304
- const keep = new Set(files);
29305
- const { keyframeCache, gsapAnimations } = usePlayerStore.getState();
29306
- const stale = /* @__PURE__ */ new Map();
29307
- for (const key of [...keyframeCache.keys(), ...gsapAnimations.keys()]) {
29308
- const hash = key.indexOf("#");
29309
- if (hash < 0) continue;
29310
- const sourceFile = key.slice(0, hash);
29311
- if (keep.has(sourceFile)) continue;
29312
- const ids = stale.get(sourceFile) ?? /* @__PURE__ */ new Set();
29313
- ids.add(key.slice(hash + 1));
29314
- stale.set(sourceFile, ids);
29315
- }
29316
- for (const [sourceFile, ids] of stale) {
29317
- for (const id of ids) clearKeyframeCacheForElement(sourceFile, id);
29318
- }
29319
- }
29320
- function elementCacheKeys(sourceFile, elementId) {
29321
- return sourceFile === "index.html" ? [`index.html#${elementId}`, elementId] : [`${sourceFile}#${elementId}`, `index.html#${elementId}`, elementId];
29322
- }
29323
- function writeGsapAnimationsForElement(sourceFile, elementId, animations) {
29324
- const { setGsapAnimations } = usePlayerStore.getState();
29325
- for (const key of elementCacheKeys(sourceFile, elementId)) {
29326
- setGsapAnimations(key, animations);
29327
- }
29328
- }
29329
- function buildCacheKey(sourceFile, elementId) {
29330
- return `${sourceFile}#${elementId}`;
29331
- }
29332
- function readKeyframeSnapshot(sourceFile, elementId) {
29333
- if (!elementId) return void 0;
29334
- return usePlayerStore.getState().keyframeCache.get(buildCacheKey(sourceFile, elementId));
29335
- }
29336
- function writeKeyframeCache(sourceFile, elementId, data) {
29337
- if (!elementId) return;
29338
- usePlayerStore.getState().setKeyframeCache(buildCacheKey(sourceFile, elementId), data);
29339
- }
29340
-
29341
29475
  // src/hooks/keyframeCacheAstLoad.ts
29342
29476
  import { isStudioHoldSet } from "@hyperframes/core/gsap-parser";
29343
29477
  function hasAnimations(value) {
@@ -29362,8 +29496,6 @@ async function fetchParsedAnimations(projectId, sourceFile) {
29362
29496
  async function populateKeyframeCacheFromAst(projectId, sf, doc) {
29363
29497
  const parsed = await fetchParsedAnimations(projectId, sf);
29364
29498
  if (!parsed) return;
29365
- const { setKeyframeCache } = usePlayerStore.getState();
29366
- clearKeyframeCacheForFile(sf);
29367
29499
  const { elements, domClipChildren } = usePlayerStore.getState();
29368
29500
  const mergedByElement = /* @__PURE__ */ new Map();
29369
29501
  const sourceByElement = /* @__PURE__ */ new Map();
@@ -29384,10 +29516,7 @@ async function populateKeyframeCacheFromAst(projectId, sf, doc) {
29384
29516
  }
29385
29517
  }
29386
29518
  }
29387
- for (const [id, kfData] of mergedByElement) {
29388
- for (const key of elementCacheKeys(sf, id)) setKeyframeCache(key, kfData);
29389
- writeGsapAnimationsForElement(sf, id, sourceByElement.get(id));
29390
- }
29519
+ replaceKeyframeCacheForFile(sf, mergedByElement, sourceByElement);
29391
29520
  }
29392
29521
 
29393
29522
  // src/hooks/useGsapTweenCache.ts
@@ -29593,9 +29722,10 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
29593
29722
  ...ease ? { ease } : {},
29594
29723
  ...easeEach ? { easeEach } : {}
29595
29724
  };
29596
- const { setKeyframeCache } = usePlayerStore.getState();
29597
- setKeyframeCache(`${sourceFile}#${elementId}`, merged);
29598
- setKeyframeCache(elementId, merged);
29725
+ publishKeyframeCache((draft) => {
29726
+ draft.keyframeCache.set(`${sourceFile}#${elementId}`, merged);
29727
+ draft.keyframeCache.set(elementId, merged);
29728
+ });
29599
29729
  }, [elementId, sourceFile, animations, domClipChildrenKey]);
29600
29730
  return { animations, multipleTimelines, unsupportedTimelinePattern };
29601
29731
  }
@@ -29646,25 +29776,26 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
29646
29776
  }
29647
29777
  const scanned = scanAllRuntimeKeyframes(iframe, clipById);
29648
29778
  if (scanned.size === 0) return false;
29649
- const { setKeyframeCache, keyframeCache } = usePlayerStore.getState();
29650
- for (const [id, data] of scanned) {
29651
- const cacheKey = `${sf}#${id}`;
29652
- const fallbackKey = `index.html#${id}`;
29653
- const alreadyCached = keyframeCache.has(cacheKey) || keyframeCache.has(fallbackKey) || keyframeCache.has(id);
29654
- if (alreadyCached) continue;
29655
- const isPosOnly = data.keyframes.length === 1 && Object.keys(data.keyframes[0].properties).every((k) => k === "x" || k === "y");
29656
- if (isPosOnly) {
29657
- continue;
29779
+ publishKeyframeCache((draft) => {
29780
+ for (const [id, data] of scanned) {
29781
+ const cacheKey = `${sf}#${id}`;
29782
+ const fallbackKey = `index.html#${id}`;
29783
+ const alreadyCached = draft.keyframeCache.has(cacheKey) || draft.keyframeCache.has(fallbackKey) || draft.keyframeCache.has(id);
29784
+ if (alreadyCached) continue;
29785
+ const isPosOnly = data.keyframes.length === 1 && Object.keys(data.keyframes[0].properties).every((k) => k === "x" || k === "y");
29786
+ if (isPosOnly) {
29787
+ continue;
29788
+ }
29789
+ const entry = {
29790
+ format: "percentage",
29791
+ keyframes: data.keyframes,
29792
+ ...data.easeEach ? { easeEach: data.easeEach } : {}
29793
+ };
29794
+ draft.keyframeCache.set(cacheKey, entry);
29795
+ if (sf !== "index.html") draft.keyframeCache.set(fallbackKey, entry);
29796
+ draft.keyframeCache.set(id, entry);
29658
29797
  }
29659
- const entry = {
29660
- format: "percentage",
29661
- keyframes: data.keyframes,
29662
- ...data.easeEach ? { easeEach: data.easeEach } : {}
29663
- };
29664
- setKeyframeCache(cacheKey, entry);
29665
- if (sf !== "index.html") setKeyframeCache(fallbackKey, entry);
29666
- setKeyframeCache(id, entry);
29667
- }
29798
+ });
29668
29799
  runtimeScanDoneRef.current = `kf-cache:${projectId}:${sf}:${version}`;
29669
29800
  return true;
29670
29801
  };
@@ -37558,6 +37689,7 @@ function KeyframeEaseList({
37558
37689
  keyframes,
37559
37690
  globalEase,
37560
37691
  expandedPct,
37692
+ collidingAnimationTargets,
37561
37693
  onToggle,
37562
37694
  onEaseCommit,
37563
37695
  onApplyAll
@@ -37622,6 +37754,7 @@ function KeyframeEaseList({
37622
37754
  EaseCurveSection,
37623
37755
  {
37624
37756
  ease: segEase,
37757
+ collidingAnimationTargets,
37625
37758
  onCustomEaseCommit: (ease) => onEaseCommit(kf.percentage, ease)
37626
37759
  }
37627
37760
  ) })
@@ -37831,6 +37964,7 @@ var AnimationCard = memo29(function AnimationCard2({
37831
37964
  onSetArcPath,
37832
37965
  onUpdateArcSegment,
37833
37966
  onUpdateKeyframeEase,
37967
+ onUpdateSegmentEase,
37834
37968
  onSetAllKeyframeEases,
37835
37969
  onUnroll
37836
37970
  }) {
@@ -37838,6 +37972,7 @@ var AnimationCard = memo29(function AnimationCard2({
37838
37972
  const [addingProp, setAddingProp] = useState55(false);
37839
37973
  const [addingFromProp, setAddingFromProp] = useState55(false);
37840
37974
  const [expandedKfPct, setExpandedKfPct] = useState55(null);
37975
+ const [focusedCollidingAnimationTargets, setFocusedCollidingAnimationTargets] = useState55();
37841
37976
  const cardRef = useRef62(null);
37842
37977
  const pendingAutoScrollRef = useRef62(false);
37843
37978
  useEffect49(() => {
@@ -37845,6 +37980,7 @@ var AnimationCard = memo29(function AnimationCard2({
37845
37980
  setExpanded(true);
37846
37981
  pendingAutoScrollRef.current = true;
37847
37982
  setExpandedKfPct(focusedSegment.tweenPercentage);
37983
+ setFocusedCollidingAnimationTargets(focusedSegment.collidingAnimationTargets);
37848
37984
  onFocusSegmentConsumed?.();
37849
37985
  }, [focusedSegment, onFocusSegmentConsumed]);
37850
37986
  useEffect49(() => {
@@ -38057,9 +38193,17 @@ var AnimationCard = memo29(function AnimationCard2({
38057
38193
  keyframes: animation.keyframes.keyframes,
38058
38194
  globalEase: animation.keyframes.easeEach ?? animation.ease ?? "none",
38059
38195
  expandedPct: expandedKfPct,
38060
- onToggle: setExpandedKfPct,
38196
+ collidingAnimationTargets: focusedCollidingAnimationTargets,
38197
+ onToggle: (pct) => {
38198
+ setExpandedKfPct(pct);
38199
+ setFocusedCollidingAnimationTargets(void 0);
38200
+ },
38061
38201
  onEaseCommit: (pct, ease) => {
38062
- onUpdateKeyframeEase(animation.id, pct, ease);
38202
+ if (focusedCollidingAnimationTargets && focusedCollidingAnimationTargets.length > 1 && onUpdateSegmentEase) {
38203
+ onUpdateSegmentEase(focusedCollidingAnimationTargets, ease);
38204
+ } else {
38205
+ onUpdateKeyframeEase(animation.id, pct, ease);
38206
+ }
38063
38207
  trackStudioSegmentEaseEdit({ action: "commit", ease });
38064
38208
  },
38065
38209
  onApplyAll: onSetAllKeyframeEases ? (ease) => onSetAllKeyframeEases(animation.id, ease) : void 0
@@ -38243,6 +38387,10 @@ function withTrackedGsapAnimationCallbacks(callbacks, track) {
38243
38387
  } : void 0,
38244
38388
  onLivePreview: callbacks.onLivePreview,
38245
38389
  onLivePreviewEnd: callbacks.onLivePreviewEnd,
38390
+ onUpdateSegmentEase: callbacks.onUpdateSegmentEase ? (targets, ease) => {
38391
+ track("select", "Segment ease");
38392
+ callbacks.onUpdateSegmentEase?.(targets, ease);
38393
+ } : void 0,
38246
38394
  onSetArcPath: callbacks.onSetArcPath ? (animationId, config) => {
38247
38395
  track("toggle", config.autoRotate !== void 0 ? "Auto rotate" : "Arc motion");
38248
38396
  callbacks.onSetArcPath?.(animationId, config);
@@ -38327,6 +38475,7 @@ import { jsx as jsx93, jsxs as jsxs75 } from "react/jsx-runtime";
38327
38475
  import { createElement } from "react";
38328
38476
  var GsapAnimationSection = memo30(function GsapAnimationSection2({
38329
38477
  animations,
38478
+ elementId,
38330
38479
  multipleTimelines,
38331
38480
  unsupportedTimelinePattern,
38332
38481
  onAddAnimation,
@@ -38347,7 +38496,7 @@ var GsapAnimationSection = memo30(function GsapAnimationSection2({
38347
38496
  key: anim.id,
38348
38497
  animation: anim,
38349
38498
  defaultExpanded: index === 0,
38350
- focusedSegment: focusedEaseSegment?.animationId === anim.id ? focusedEaseSegment : null,
38499
+ focusedSegment: focusedEaseSegment?.elementId === elementId && focusedEaseSegment.animationId === anim.id ? focusedEaseSegment : null,
38351
38500
  onFocusSegmentConsumed: clearFocusedEaseSegment
38352
38501
  }
38353
38502
  )),
@@ -40971,7 +41120,7 @@ function FlatMotionSection({
40971
41120
  const [addMenuOpen, setAddMenuOpen] = useState62(false);
40972
41121
  const trackedCallbacks = withTrackedGsapAnimationCallbacks(callbacks, track);
40973
41122
  const focusedEaseSegment = usePlayerStore((s) => s.focusedEaseSegment);
40974
- const renderedElementId = `${element.sourceFile}#${element.id}`;
41123
+ const renderedElementId = scopedElementKey(element);
40975
41124
  const focusedHere = focusedEaseSegment && focusedEaseSegment.elementId === renderedElementId ? focusedEaseSegment : null;
40976
41125
  return /* @__PURE__ */ jsxs87("div", { className: "space-y-3", children: [
40977
41126
  showTiming && /* @__PURE__ */ jsx105(
@@ -44785,13 +44934,14 @@ function PropertyPanelFlat({
44785
44934
  onUpdateArcSegment,
44786
44935
  onUnroll,
44787
44936
  onUpdateKeyframeEase,
44937
+ onUpdateSegmentEase,
44788
44938
  onSetAllKeyframeEases
44789
44939
  }) {
44790
44940
  const [openGroupId, setOpenGroupId] = useState73(
44791
44941
  () => isTextEditableSelection(element) ? "text" : showEditableSections ? "style" : sections.media ? "media" : "layout"
44792
44942
  );
44793
44943
  const focusedEaseSegment = usePlayerStore((s) => s.focusedEaseSegment);
44794
- const renderedElementId = `${element.sourceFile}#${element.id}`;
44944
+ const renderedElementId = scopedElementKey(element);
44795
44945
  const [consumedFocus, setConsumedFocus] = useState73(focusedEaseSegment);
44796
44946
  if (focusedEaseSegment !== consumedFocus) {
44797
44947
  setConsumedFocus(focusedEaseSegment);
@@ -44848,6 +44998,7 @@ function PropertyPanelFlat({
44848
44998
  onUpdateArcSegment,
44849
44999
  onUnroll,
44850
45000
  onUpdateKeyframeEase,
45001
+ onUpdateSegmentEase,
44851
45002
  onSetAllKeyframeEases
44852
45003
  } : null;
44853
45004
  const showMotionEffects = gsapEffectHandlers !== null;
@@ -45368,6 +45519,7 @@ var PropertyPanel = memo31(function PropertyPanel2(props) {
45368
45519
  onUpdateArcSegment,
45369
45520
  onUnroll,
45370
45521
  onUpdateKeyframeEase,
45522
+ onUpdateSegmentEase,
45371
45523
  onSetAllKeyframeEases,
45372
45524
  onAddKeyframe,
45373
45525
  onRemoveKeyframe,
@@ -45764,6 +45916,7 @@ var PropertyPanel = memo31(function PropertyPanel2(props) {
45764
45916
  STUDIO_GSAP_PANEL_ENABLED && onUpdateGsapProperty && onUpdateGsapMeta && onDeleteGsapAnimation && onAddGsapProperty && onAddGsapAnimation && /* @__PURE__ */ jsx122(
45765
45917
  GsapAnimationSection,
45766
45918
  {
45919
+ elementId: scopedElementKey(element),
45767
45920
  animations: gsapAnimations,
45768
45921
  multipleTimelines: gsapMultipleTimelines,
45769
45922
  unsupportedTimelinePattern: gsapUnsupportedTimelinePattern,
@@ -45781,7 +45934,8 @@ var PropertyPanel = memo31(function PropertyPanel2(props) {
45781
45934
  onUpdateArcSegment,
45782
45935
  onUnroll,
45783
45936
  onUpdateKeyframeEase,
45784
- onSetAllKeyframeEases
45937
+ onSetAllKeyframeEases,
45938
+ onUpdateSegmentEase
45785
45939
  }
45786
45940
  ),
45787
45941
  showEditableSections && /* @__PURE__ */ jsx122(
@@ -46607,7 +46761,7 @@ var FileTree = memo33(function FileTree2({
46607
46761
  });
46608
46762
 
46609
46763
  // src/App.tsx
46610
- import { useState as useState132, useCallback as useCallback143, useRef as useRef134, useMemo as useMemo53, useEffect as useEffect116, useLayoutEffect as useLayoutEffect4 } from "react";
46764
+ import { useState as useState132, useCallback as useCallback144, useRef as useRef134, useMemo as useMemo53, useEffect as useEffect116, useLayoutEffect as useLayoutEffect4 } from "react";
46611
46765
 
46612
46766
  // src/components/renders/useRenderQueue.ts
46613
46767
  import { useState as useState77, useEffect as useEffect66, useCallback as useCallback59, useRef as useRef77, useMemo as useMemo35 } from "react";
@@ -50602,7 +50756,7 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
50602
50756
  }
50603
50757
 
50604
50758
  // src/hooks/useDomEditSession.ts
50605
- import { useCallback as useCallback95 } from "react";
50759
+ import { useCallback as useCallback96 } from "react";
50606
50760
 
50607
50761
  // src/hooks/useAskAgentModal.ts
50608
50762
  import { useState as useState86, useCallback as useCallback74, useRef as useRef90, useEffect as useEffect71 } from "react";
@@ -55945,6 +56099,69 @@ function useStudioSelectionPublisher({
55945
56099
  }, [domEditSelectionRef, previewDocumentVersion, refreshDomEditSelectionFromPreview]);
55946
56100
  }
55947
56101
 
56102
+ // src/hooks/useKeyframeEaseCommits.ts
56103
+ import { useCallback as useCallback95 } from "react";
56104
+ function useKeyframeEaseCommits({
56105
+ gsapCommitMutation,
56106
+ domEditSelectionRef
56107
+ }) {
56108
+ const handleUpdateSegmentEase = useCallback95(
56109
+ (targets, ease) => {
56110
+ const selection = domEditSelectionRef.current;
56111
+ if (!selection || targets.length === 0) return;
56112
+ const options = {
56113
+ label: targets.length === 1 ? "Update keyframe ease" : "Update segment ease",
56114
+ softReload: true
56115
+ };
56116
+ const calls = targets.map(({ animationId, tweenPercentage }) => ({
56117
+ selection,
56118
+ mutation: {
56119
+ type: "update-keyframe",
56120
+ animationId,
56121
+ percentage: tweenPercentage,
56122
+ properties: {},
56123
+ ease
56124
+ },
56125
+ options
56126
+ }));
56127
+ if (calls.length === 1) {
56128
+ const call = calls[0];
56129
+ if (call) void gsapCommitMutation(call.selection, call.mutation, call.options);
56130
+ return;
56131
+ }
56132
+ const batch = gsapCommitMutation.batch;
56133
+ if (batch) {
56134
+ void batch(calls, options);
56135
+ return;
56136
+ }
56137
+ for (const call of calls)
56138
+ void gsapCommitMutation(call.selection, call.mutation, call.options);
56139
+ },
56140
+ [gsapCommitMutation, domEditSelectionRef]
56141
+ );
56142
+ const handleUpdateKeyframeEase = useCallback95(
56143
+ (animationId, percentage, ease) => handleUpdateSegmentEase([{ animationId, tweenPercentage: percentage }], ease),
56144
+ [handleUpdateSegmentEase]
56145
+ );
56146
+ const handleSetAllKeyframeEases = useCallback95(
56147
+ (animationId, ease) => {
56148
+ const sel = domEditSelectionRef.current;
56149
+ if (!sel) return;
56150
+ gsapCommitMutation(
56151
+ sel,
56152
+ {
56153
+ type: "update-meta",
56154
+ animationId,
56155
+ updates: { easeEach: ease, resetKeyframeEases: true }
56156
+ },
56157
+ { label: "Apply ease to all segments", softReload: true }
56158
+ );
56159
+ },
56160
+ [gsapCommitMutation, domEditSelectionRef]
56161
+ );
56162
+ return { handleUpdateSegmentEase, handleUpdateKeyframeEase, handleSetAllKeyframeEases };
56163
+ }
56164
+
55948
56165
  // src/hooks/useDomEditSession.ts
55949
56166
  function useDomEditSession({
55950
56167
  projectId,
@@ -56170,7 +56387,7 @@ function useDomEditSession({
56170
56387
  clearDomSelection,
56171
56388
  forceReloadSdkSession
56172
56389
  });
56173
- const handleGroupSelection = useCallback95(() => {
56390
+ const handleGroupSelection = useCallback96(() => {
56174
56391
  const group = domEditGroupSelectionsRef.current;
56175
56392
  const single = domEditSelectionRef.current;
56176
56393
  const members = group.length > 0 ? group : single ? [single] : [];
@@ -56181,7 +56398,7 @@ function useDomEditSession({
56181
56398
  trackStudioEvent("group", { action: "create", count: members.length });
56182
56399
  void groupSelection(members);
56183
56400
  }, [domEditGroupSelectionsRef, domEditSelectionRef, groupSelection, showToast]);
56184
- const handleUngroupSelection = useCallback95(() => {
56401
+ const handleUngroupSelection = useCallback96(() => {
56185
56402
  const sel = domEditSelectionRef.current;
56186
56403
  if (!sel?.element.hasAttribute("data-hf-group")) {
56187
56404
  showToast("Select a group to ungroup", "info");
@@ -56301,40 +56518,7 @@ function useDomEditSession({
56301
56518
  setArcPath,
56302
56519
  updateArcSegment
56303
56520
  });
56304
- const handleUpdateKeyframeEase = useCallback95(
56305
- (animationId, percentage, ease) => {
56306
- const sel = domEditSelectionRef.current;
56307
- if (!sel) return;
56308
- gsapCommitMutation(
56309
- sel,
56310
- {
56311
- type: "update-keyframe",
56312
- animationId,
56313
- percentage,
56314
- properties: {},
56315
- ease
56316
- },
56317
- { label: "Update keyframe ease", softReload: true }
56318
- );
56319
- },
56320
- [gsapCommitMutation, domEditSelectionRef]
56321
- );
56322
- const handleSetAllKeyframeEases = useCallback95(
56323
- (animationId, ease) => {
56324
- const sel = domEditSelectionRef.current;
56325
- if (!sel) return;
56326
- gsapCommitMutation(
56327
- sel,
56328
- {
56329
- type: "update-meta",
56330
- animationId,
56331
- updates: { easeEach: ease, resetKeyframeEases: true }
56332
- },
56333
- { label: "Apply ease to all segments", softReload: true }
56334
- );
56335
- },
56336
- [gsapCommitMutation, domEditSelectionRef]
56337
- );
56521
+ const { handleUpdateSegmentEase, handleUpdateKeyframeEase, handleSetAllKeyframeEases } = useKeyframeEaseCommits({ gsapCommitMutation, domEditSelectionRef });
56338
56522
  return {
56339
56523
  // State
56340
56524
  domEditSelection,
@@ -56413,6 +56597,7 @@ function useDomEditSession({
56413
56597
  commitAnimatedProperty,
56414
56598
  commitAnimatedProperties,
56415
56599
  handleSetArcPath,
56600
+ handleUpdateSegmentEase,
56416
56601
  handleUpdateArcSegment,
56417
56602
  handleUnroll,
56418
56603
  invalidateGsapCache: bumpGsapCache,
@@ -56438,7 +56623,7 @@ function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections)
56438
56623
  import { useEffect as useEffect80 } from "react";
56439
56624
 
56440
56625
  // src/hooks/useSdkSession.ts
56441
- import { useState as useState88, useEffect as useEffect79, useCallback as useCallback96, useRef as useRef102 } from "react";
56626
+ import { useState as useState88, useEffect as useEffect79, useCallback as useCallback97, useRef as useRef102 } from "react";
56442
56627
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
56443
56628
  async function readProjectFileOptional(projectId, path) {
56444
56629
  if (path.includes("\0") || path.includes("..")) return void 0;
@@ -56575,8 +56760,8 @@ function useSdkSession(projectId, activeCompPath, domEditSaveTimestampRef) {
56575
56760
  }
56576
56761
  };
56577
56762
  }, [projectId, activeCompPath, reloadToken]);
56578
- const forceReload = useCallback96(() => setReloadToken((t) => t + 1), []);
56579
- const publish = useCallback96(({ candidate, expectedSession, targetPath }) => {
56763
+ const forceReload = useCallback97(() => setReloadToken((t) => t + 1), []);
56764
+ const publish = useCallback97(({ candidate, expectedSession, targetPath }) => {
56580
56765
  const expectedOwner = sessionOwnersRef.current.get(expectedSession);
56581
56766
  const current = ownedSessionRef.current;
56582
56767
  if (!isSessionOwnerActive(
@@ -56617,7 +56802,7 @@ function useStudioSdkSessions(projectId, activeCompPath, domEditSaveTimestampRef
56617
56802
  }
56618
56803
 
56619
56804
  // src/hooks/useBlockHandlers.ts
56620
- import { useCallback as useCallback97, useMemo as useMemo40, useRef as useRef103, useState as useState89 } from "react";
56805
+ import { useCallback as useCallback98, useMemo as useMemo40, useRef as useRef103, useState as useState89 } from "react";
56621
56806
 
56622
56807
  // src/utils/blockInstaller.ts
56623
56808
  function getMaxZIndexFromIframe(iframe) {
@@ -56827,7 +57012,7 @@ function useBlockHandlers({
56827
57012
  ]
56828
57013
  );
56829
57014
  const installingBlockRef = useRef103(false);
56830
- const runBlockInstall = useCallback97(
57015
+ const runBlockInstall = useCallback98(
56831
57016
  async (blockName, install) => {
56832
57017
  if (installingBlockRef.current) {
56833
57018
  blockCtx.showToast("A block is already installing \u2014 one moment\u2026", "info");
@@ -56843,7 +57028,7 @@ function useBlockHandlers({
56843
57028
  },
56844
57029
  [blockCtx]
56845
57030
  );
56846
- const handleAddBlock = useCallback97(
57031
+ const handleAddBlock = useCallback98(
56847
57032
  (blockName) => {
56848
57033
  if (!projectId) return;
56849
57034
  void (async () => {
@@ -56873,7 +57058,7 @@ function useBlockHandlers({
56873
57058
  },
56874
57059
  [projectId, blockCtx, previewIframeRef, runBlockInstall, setRightCollapsed, setRightPanelTab]
56875
57060
  );
56876
- const handleTimelineBlockDrop = useCallback97(
57061
+ const handleTimelineBlockDrop = useCallback98(
56877
57062
  (blockName, placement) => {
56878
57063
  if (!projectId) return;
56879
57064
  void runBlockInstall(
@@ -56890,7 +57075,7 @@ function useBlockHandlers({
56890
57075
  },
56891
57076
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
56892
57077
  );
56893
- const handleAddMediaOverlay = useCallback97(
57078
+ const handleAddMediaOverlay = useCallback98(
56894
57079
  async (blockName, placement) => {
56895
57080
  if (!projectId) return;
56896
57081
  const { compositionPath, ...timelinePlacement } = placement;
@@ -56909,7 +57094,7 @@ function useBlockHandlers({
56909
57094
  },
56910
57095
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
56911
57096
  );
56912
- const handlePreviewBlockDrop = useCallback97(
57097
+ const handlePreviewBlockDrop = useCallback98(
56913
57098
  (blockName, position) => {
56914
57099
  if (!projectId) return;
56915
57100
  void runBlockInstall(
@@ -56937,7 +57122,7 @@ function useBlockHandlers({
56937
57122
  }
56938
57123
 
56939
57124
  // src/hooks/useAppHotkeys.ts
56940
- import { useCallback as useCallback98, useEffect as useEffect81, useRef as useRef104 } from "react";
57125
+ import { useCallback as useCallback99, useEffect as useEffect81, useRef as useRef104 } from "react";
56941
57126
  function iframeContentWindow(iframe) {
56942
57127
  try {
56943
57128
  return iframe?.contentWindow ?? null;
@@ -57147,22 +57332,22 @@ function useAppHotkeys({
57147
57332
  }) {
57148
57333
  const previewHotkeyWindowRef = useRef104(null);
57149
57334
  const previewHistoryCleanupRef = useRef104(null);
57150
- const readHistoryFile = useCallback98(
57335
+ const readHistoryFile = useCallback99(
57151
57336
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
57152
57337
  [readOptionalProjectFile, readProjectFile]
57153
57338
  );
57154
- const writeHistoryFile = useCallback98(
57339
+ const writeHistoryFile = useCallback99(
57155
57340
  async (path, content) => {
57156
57341
  domEditSaveTimestampRef.current = Date.now();
57157
57342
  await writeProjectFile(path, content);
57158
57343
  },
57159
57344
  [domEditSaveTimestampRef, writeProjectFile]
57160
57345
  );
57161
- const serializeHistoryFiles = useCallback98(
57346
+ const serializeHistoryFiles = useCallback99(
57162
57347
  (paths, task) => serializeStudioFileMutations(writeProjectFile, paths, task),
57163
57348
  [writeProjectFile]
57164
57349
  );
57165
- const applyHistory = useCallback98(
57350
+ const applyHistory = useCallback99(
57166
57351
  async (direction) => {
57167
57352
  if (tryApplyBeatHistory(direction, editHistory.state, showToast)) return;
57168
57353
  await waitForPendingDomEditSaves();
@@ -57200,8 +57385,8 @@ function useAppHotkeys({
57200
57385
  forceReloadSdkSession
57201
57386
  ]
57202
57387
  );
57203
- const handleUndo = useCallback98(() => applyHistory("undo"), [applyHistory]);
57204
- const handleRedo = useCallback98(() => applyHistory("redo"), [applyHistory]);
57388
+ const handleUndo = useCallback99(() => applyHistory("undo"), [applyHistory]);
57389
+ const handleRedo = useCallback99(() => applyHistory("redo"), [applyHistory]);
57205
57390
  const cbRef = useRef104(null);
57206
57391
  cbRef.current = {
57207
57392
  handleTimelineElementDelete,
@@ -57221,7 +57406,7 @@ function useAppHotkeys({
57221
57406
  domEditSelectionRef,
57222
57407
  showToast
57223
57408
  };
57224
- const handleAppKeyDown = useCallback98((event) => {
57409
+ const handleAppKeyDown = useCallback99((event) => {
57225
57410
  const cb = cbRef.current;
57226
57411
  const key = event.key.toLowerCase();
57227
57412
  if (event.metaKey || event.ctrlKey) {
@@ -57234,7 +57419,7 @@ function useAppHotkeys({
57234
57419
  window.addEventListener("keydown", handleAppKeyDown, true);
57235
57420
  return () => window.removeEventListener("keydown", handleAppKeyDown, true);
57236
57421
  }, [handleAppKeyDown]);
57237
- const syncPreviewTimelineHotkey = useCallback98(
57422
+ const syncPreviewTimelineHotkey = useCallback99(
57238
57423
  (iframe) => {
57239
57424
  const nextWindow = iframeContentWindow(iframe);
57240
57425
  if (previewHotkeyWindowRef.current === nextWindow) return;
@@ -57259,7 +57444,7 @@ function useAppHotkeys({
57259
57444
  },
57260
57445
  [handleAppKeyDown]
57261
57446
  );
57262
- const handleHistoryHotkey = useCallback98((event) => {
57447
+ const handleHistoryHotkey = useCallback99((event) => {
57263
57448
  if (!(event.metaKey || event.ctrlKey) || shouldIgnoreHistoryShortcut(event.target)) return;
57264
57449
  handleUndoRedoKey(
57265
57450
  event,
@@ -57267,7 +57452,7 @@ function useAppHotkeys({
57267
57452
  () => void cbRef.current.handleRedo()
57268
57453
  );
57269
57454
  }, []);
57270
- const syncPreviewHistoryHotkey = useCallback98(
57455
+ const syncPreviewHistoryHotkey = useCallback99(
57271
57456
  (iframe) => {
57272
57457
  previewHistoryCleanupRef.current?.();
57273
57458
  previewHistoryCleanupRef.current = null;
@@ -57305,7 +57490,7 @@ function useAppHotkeys({
57305
57490
  }
57306
57491
 
57307
57492
  // src/hooks/useClipboard.ts
57308
- import { useCallback as useCallback99, useRef as useRef105 } from "react";
57493
+ import { useCallback as useCallback100, useRef as useRef105 } from "react";
57309
57494
 
57310
57495
  // src/utils/clipboardPayload.ts
57311
57496
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -57415,7 +57600,7 @@ function useClipboard({
57415
57600
  const clipboardRef = useRef105(null);
57416
57601
  const projectIdRef = useRef105(projectId);
57417
57602
  projectIdRef.current = projectId;
57418
- const handleCopy = useCallback99(() => {
57603
+ const handleCopy = useCallback100(() => {
57419
57604
  const { selectedElementId, elements } = usePlayerStore.getState();
57420
57605
  if (selectedElementId) {
57421
57606
  const element = elements.find((el) => (el.key ?? el.id) === selectedElementId);
@@ -57469,7 +57654,7 @@ function useClipboard({
57469
57654
  }
57470
57655
  return false;
57471
57656
  }, [activeCompPath, domEditSelectionRef, previewIframeRef, showToast]);
57472
- const handlePaste = useCallback99(async () => {
57657
+ const handlePaste = useCallback100(async () => {
57473
57658
  const payload = clipboardRef.current;
57474
57659
  if (!payload) {
57475
57660
  showToast("Nothing to paste.", "info");
@@ -57525,7 +57710,7 @@ function useClipboard({
57525
57710
  showToast,
57526
57711
  writeProjectFile
57527
57712
  ]);
57528
- const handleCut = useCallback99(async () => {
57713
+ const handleCut = useCallback100(async () => {
57529
57714
  const copied = handleCopy();
57530
57715
  if (!copied) return false;
57531
57716
  const { selectedElementId, elements } = usePlayerStore.getState();
@@ -57887,11 +58072,11 @@ function useCaptionDetection({
57887
58072
  }
57888
58073
 
57889
58074
  // src/hooks/useRenderClipContent.ts
57890
- import { useCallback as useCallback102 } from "react";
58075
+ import { useCallback as useCallback103 } from "react";
57891
58076
  import { createElement as createElement4 } from "react";
57892
58077
 
57893
58078
  // src/player/components/AudioWaveform.tsx
57894
- import { memo as memo34, useRef as useRef106, useState as useState90, useCallback as useCallback100, useEffect as useEffect83 } from "react";
58079
+ import { memo as memo34, useRef as useRef106, useState as useState90, useCallback as useCallback101, useEffect as useEffect83 } from "react";
57895
58080
  import { jsx as jsx132, jsxs as jsxs112 } from "react/jsx-runtime";
57896
58081
  var BAR_W = 2;
57897
58082
  var GAP = 1;
@@ -57968,7 +58153,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
57968
58153
  cancelled = true;
57969
58154
  };
57970
58155
  }, [audioUrl, waveformUrl, cacheKey, peaks]);
57971
- const draw = useCallback100(() => {
58156
+ const draw = useCallback101(() => {
57972
58157
  const container = containerRef.current;
57973
58158
  const barsEl = barsRef.current;
57974
58159
  if (!container || !barsEl || !peaks) return;
@@ -57989,7 +58174,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
57989
58174
  }
57990
58175
  barsEl.innerHTML = html2;
57991
58176
  }, [peaks, trimStartFraction, trimEndFraction]);
57992
- const setContainerRef = useCallback100(
58177
+ const setContainerRef = useCallback101(
57993
58178
  (el) => {
57994
58179
  roRef.current?.disconnect();
57995
58180
  containerRef.current = el;
@@ -58033,7 +58218,7 @@ var AudioWaveform = memo34(function AudioWaveform2({
58033
58218
  });
58034
58219
 
58035
58220
  // src/player/components/ImageThumbnail.tsx
58036
- import { memo as memo35, useRef as useRef107, useState as useState91, useCallback as useCallback101, useEffect as useEffect84 } from "react";
58221
+ import { memo as memo35, useRef as useRef107, useState as useState91, useCallback as useCallback102, useEffect as useEffect84 } from "react";
58037
58222
  import { jsx as jsx133, jsxs as jsxs113 } from "react/jsx-runtime";
58038
58223
  var ImageThumbnail = memo35(function ImageThumbnail2({
58039
58224
  imageSrc,
@@ -58046,7 +58231,7 @@ var ImageThumbnail = memo35(function ImageThumbnail2({
58046
58231
  const [aspect, setAspect] = useState91(16 / 9);
58047
58232
  const ioRef = useRef107(null);
58048
58233
  const roRef = useRef107(null);
58049
- const setContainerRef = useCallback101((el) => {
58234
+ const setContainerRef = useCallback102((el) => {
58050
58235
  ioRef.current?.disconnect();
58051
58236
  roRef.current?.disconnect();
58052
58237
  if (!el) return;
@@ -58199,7 +58384,7 @@ function useRenderClipContent({
58199
58384
  activePreviewUrl,
58200
58385
  effectiveTimelineDuration
58201
58386
  }) {
58202
- return useCallback102(
58387
+ return useCallback103(
58203
58388
  // Pre-existing clip-content dispatcher; reduced by extracting renderAudioClip.
58204
58389
  // fallow-ignore-next-line complexity
58205
58390
  (el, style) => {
@@ -58271,11 +58456,11 @@ function useRenderClipContent({
58271
58456
  }
58272
58457
 
58273
58458
  // src/hooks/useConsoleErrorCapture.ts
58274
- import { useCallback as useCallback103, useEffect as useEffect85, useRef as useRef108, useState as useState92 } from "react";
58459
+ import { useCallback as useCallback104, useEffect as useEffect85, useRef as useRef108, useState as useState92 } from "react";
58275
58460
  function useConsoleErrorCapture(previewIframe) {
58276
58461
  const [consoleErrors, setConsoleErrors] = useState92(null);
58277
58462
  const consoleErrorsRef = useRef108([]);
58278
- const resetErrors = useCallback103(() => {
58463
+ const resetErrors = useCallback104(() => {
58279
58464
  consoleErrorsRef.current = [];
58280
58465
  setConsoleErrors(null);
58281
58466
  }, []);
@@ -58344,7 +58529,7 @@ function useConsoleErrorCapture(previewIframe) {
58344
58529
  }
58345
58530
 
58346
58531
  // src/hooks/useFrameCapture.ts
58347
- import { useState as useState93, useCallback as useCallback104, useRef as useRef109 } from "react";
58532
+ import { useState as useState93, useCallback as useCallback105, useRef as useRef109 } from "react";
58348
58533
 
58349
58534
  // src/utils/frameCapture.ts
58350
58535
  function normalizeCompositionPath(compositionPath) {
@@ -58387,10 +58572,10 @@ function useFrameCapture({
58387
58572
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
58388
58573
  return liveTime.subscribe(setCaptureFrameTime);
58389
58574
  });
58390
- const refreshCaptureFrameTime = useCallback104(() => {
58575
+ const refreshCaptureFrameTime = useCallback105(() => {
58391
58576
  setCaptureFrameTime(usePlayerStore.getState().currentTime);
58392
58577
  }, []);
58393
- const handleCaptureFrameClick = useCallback104(
58578
+ const handleCaptureFrameClick = useCallback105(
58394
58579
  async (event) => {
58395
58580
  if (!projectId) return;
58396
58581
  event.preventDefault();
@@ -58467,7 +58652,7 @@ function useFrameCapture({
58467
58652
  }
58468
58653
 
58469
58654
  // src/hooks/useLintModal.ts
58470
- import { useState as useState94, useCallback as useCallback105, useEffect as useEffect86, useRef as useRef110, useMemo as useMemo41 } from "react";
58655
+ import { useState as useState94, useCallback as useCallback106, useEffect as useEffect86, useRef as useRef110, useMemo as useMemo41 } from "react";
58471
58656
  function parseFinding(f) {
58472
58657
  return {
58473
58658
  severity: f.severity === "error" ? "error" : "warning",
@@ -58482,7 +58667,7 @@ function useLintModal(projectId, refreshKey) {
58482
58667
  const [linting, setLinting] = useState94(false);
58483
58668
  const [backgroundFindings, setBackgroundFindings] = useState94([]);
58484
58669
  const autoLintRanRef = useRef110(false);
58485
- const runLint = useCallback105(
58670
+ const runLint = useCallback106(
58486
58671
  async (opts) => {
58487
58672
  if (!projectId) return;
58488
58673
  if (!opts?.background) setLinting(true);
@@ -58507,7 +58692,7 @@ function useLintModal(projectId, refreshKey) {
58507
58692
  },
58508
58693
  [projectId]
58509
58694
  );
58510
- const handleLint = useCallback105(() => runLint(), [runLint]);
58695
+ const handleLint = useCallback106(() => runLint(), [runLint]);
58511
58696
  const prevProjectIdRef = useRef110(projectId);
58512
58697
  useEffect86(() => {
58513
58698
  if (projectId !== prevProjectIdRef.current) {
@@ -58523,8 +58708,8 @@ function useLintModal(projectId, refreshKey) {
58523
58708
  const timer = setTimeout(() => void runLint({ background: true }), 1e3);
58524
58709
  return () => clearTimeout(timer);
58525
58710
  }, [projectId, refreshKey, runLint]);
58526
- const closeLintModal = useCallback105(() => setLintModal(null), []);
58527
- const groupFindings = useCallback105(
58711
+ const closeLintModal = useCallback106(() => setLintModal(null), []);
58712
+ const groupFindings = useCallback106(
58528
58713
  (keyFn) => {
58529
58714
  const map = /* @__PURE__ */ new Map();
58530
58715
  for (const f of backgroundFindings) {
@@ -58556,7 +58741,7 @@ function useLintModal(projectId, refreshKey) {
58556
58741
  }
58557
58742
 
58558
58743
  // src/hooks/useToast.ts
58559
- import { useState as useState95, useCallback as useCallback106, useRef as useRef111 } from "react";
58744
+ import { useState as useState95, useCallback as useCallback107, useRef as useRef111 } from "react";
58560
58745
  var AUTO_DISMISS_MS2 = 4e3;
58561
58746
  var EXIT_MS = 160;
58562
58747
  var MAX_TOASTS = 3;
@@ -58564,21 +58749,21 @@ var nextToastId = 1;
58564
58749
  function useToast() {
58565
58750
  const [toasts, setToasts] = useState95([]);
58566
58751
  const timersRef = useRef111(/* @__PURE__ */ new Map());
58567
- const clearTimer = useCallback106((id) => {
58752
+ const clearTimer = useCallback107((id) => {
58568
58753
  const timer = timersRef.current.get(id);
58569
58754
  if (timer) {
58570
58755
  clearTimeout(timer);
58571
58756
  timersRef.current.delete(id);
58572
58757
  }
58573
58758
  }, []);
58574
- const removeToast = useCallback106(
58759
+ const removeToast = useCallback107(
58575
58760
  (id) => {
58576
58761
  clearTimer(id);
58577
58762
  setToasts((prev) => prev.filter((t) => t.id !== id));
58578
58763
  },
58579
58764
  [clearTimer]
58580
58765
  );
58581
- const dismissToast = useCallback106(
58766
+ const dismissToast = useCallback107(
58582
58767
  (id) => {
58583
58768
  clearTimer(id);
58584
58769
  setToasts((prev) => prev.map((t) => t.id === id ? { ...t, leaving: true } : t));
@@ -58587,7 +58772,7 @@ function useToast() {
58587
58772
  },
58588
58773
  [clearTimer, removeToast]
58589
58774
  );
58590
- const showToast = useCallback106(
58775
+ const showToast = useCallback107(
58591
58776
  (message, tone = "error") => {
58592
58777
  const id = nextToastId++;
58593
58778
  setToasts((prev) => {
@@ -58613,14 +58798,14 @@ function useToast() {
58613
58798
  }
58614
58799
 
58615
58800
  // src/hooks/useCompositionContentLoader.ts
58616
- import { useCallback as useCallback107 } from "react";
58801
+ import { useCallback as useCallback108 } from "react";
58617
58802
  function useCompositionContentLoader({
58618
58803
  projectId,
58619
58804
  setEditingFile,
58620
58805
  setActiveCompPath,
58621
58806
  showToast
58622
58807
  }) {
58623
- return useCallback107(
58808
+ return useCallback108(
58624
58809
  (comp) => {
58625
58810
  setActiveCompPath(comp.endsWith(".html") ? comp : null);
58626
58811
  setEditingFile({ path: comp, content: null });
@@ -58639,7 +58824,7 @@ function useCompositionContentLoader({
58639
58824
  }
58640
58825
 
58641
58826
  // src/hooks/useStudioUrlState.ts
58642
- import { useCallback as useCallback108, useEffect as useEffect87, useRef as useRef112, useState as useState96 } from "react";
58827
+ import { useCallback as useCallback109, useEffect as useEffect87, useRef as useRef112, useState as useState96 } from "react";
58643
58828
 
58644
58829
  // src/utils/studioUrlState.ts
58645
58830
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
@@ -58772,7 +58957,7 @@ function useStudioUrlState({
58772
58957
  const [selectionHydrated, setSelectionHydrated] = useState96(initialState2.selection == null);
58773
58958
  const pendingSelectionRef = useRef112(initialState2.selection);
58774
58959
  const stableTimeRef = useRef112(initialState2.currentTime);
58775
- const buildUrlState = useCallback108(
58960
+ const buildUrlState = useCallback109(
58776
58961
  () => ({
58777
58962
  activeCompPath,
58778
58963
  currentTime: stableTimeRef.current,
@@ -58783,7 +58968,7 @@ function useStudioUrlState({
58783
58968
  }),
58784
58969
  [activeCompPath, domEditSelection, rightCollapsed, rightPanelTab]
58785
58970
  );
58786
- const applyUrlSelection = useCallback108(
58971
+ const applyUrlSelection = useCallback109(
58787
58972
  (selection) => {
58788
58973
  if (!selection) {
58789
58974
  applyDomSelection(null, { revealPanel: false });
@@ -58899,7 +59084,7 @@ function useStudioUrlState({
58899
59084
  }
58900
59085
 
58901
59086
  // src/hooks/useStudioContextValue.ts
58902
- import { useCallback as useCallback109, useMemo as useMemo42, useRef as useRef113, useState as useState97 } from "react";
59087
+ import { useCallback as useCallback110, useMemo as useMemo42, useRef as useRef113, useState as useState97 } from "react";
58903
59088
  function buildStudioContextValue(input) {
58904
59089
  return {
58905
59090
  projectId: input.projectId,
@@ -58956,21 +59141,21 @@ function useInspectorState(rightPanelTab, rightInspectorPanes, rightCollapsed, i
58956
59141
  function useDragOverlay(onImportFiles) {
58957
59142
  const [active, setActive] = useState97(false);
58958
59143
  const counterRef = useRef113(0);
58959
- const onDragOver = useCallback109((e) => {
59144
+ const onDragOver = useCallback110((e) => {
58960
59145
  if (!e.dataTransfer.types.includes("Files")) return;
58961
59146
  e.preventDefault();
58962
59147
  }, []);
58963
- const onDragEnter = useCallback109((e) => {
59148
+ const onDragEnter = useCallback110((e) => {
58964
59149
  if (!e.dataTransfer.types.includes("Files")) return;
58965
59150
  e.preventDefault();
58966
59151
  counterRef.current++;
58967
59152
  setActive(true);
58968
59153
  }, []);
58969
- const onDragLeave = useCallback109(() => {
59154
+ const onDragLeave = useCallback110(() => {
58970
59155
  counterRef.current--;
58971
59156
  if (counterRef.current === 0) setActive(false);
58972
59157
  }, []);
58973
- const onDrop = useCallback109(
59158
+ const onDrop = useCallback110(
58974
59159
  (e) => {
58975
59160
  counterRef.current = 0;
58976
59161
  setActive(false);
@@ -58983,7 +59168,7 @@ function useDragOverlay(onImportFiles) {
58983
59168
  return { active, onDragOver, onDragEnter, onDragLeave, onDrop };
58984
59169
  }
58985
59170
  function useGlobalFileDrop(handleTimelineFileDrop) {
58986
- const onDrop = useCallback109(
59171
+ const onDrop = useCallback110(
58987
59172
  (files) => {
58988
59173
  const start = usePlayerStore.getState().currentTime;
58989
59174
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -59070,7 +59255,7 @@ function PanelLayoutProvider({
59070
59255
  // src/contexts/ViewModeContext.tsx
59071
59256
  import {
59072
59257
  createContext as createContext9,
59073
- useCallback as useCallback110,
59258
+ useCallback as useCallback111,
59074
59259
  useContext as useContext9,
59075
59260
  useEffect as useEffect88,
59076
59261
  useMemo as useMemo44,
@@ -59104,7 +59289,7 @@ function useViewModeState() {
59104
59289
  const [viewMode, setMode] = useState98(() => readViewModeFromUrl());
59105
59290
  const guardsRef = useRef114(/* @__PURE__ */ new Set());
59106
59291
  const acceptedLocationRef = useRef114(readHistoryLocation());
59107
- const canSetViewMode = useCallback110((mode) => {
59292
+ const canSetViewMode = useCallback111((mode) => {
59108
59293
  for (const guard of guardsRef.current) {
59109
59294
  if (!guard(mode)) return false;
59110
59295
  }
@@ -59126,7 +59311,7 @@ function useViewModeState() {
59126
59311
  window.addEventListener("popstate", onPopState);
59127
59312
  return () => window.removeEventListener("popstate", onPopState);
59128
59313
  }, [canSetViewMode, viewMode]);
59129
- const setViewMode = useCallback110(
59314
+ const setViewMode = useCallback111(
59130
59315
  (mode) => {
59131
59316
  if (!canSetViewMode(mode)) return false;
59132
59317
  setMode(mode);
@@ -59136,7 +59321,7 @@ function useViewModeState() {
59136
59321
  },
59137
59322
  [canSetViewMode]
59138
59323
  );
59139
- const registerViewModeGuard = useCallback110((guard) => {
59324
+ const registerViewModeGuard = useCallback111((guard) => {
59140
59325
  guardsRef.current.add(guard);
59141
59326
  return () => {
59142
59327
  guardsRef.current.delete(guard);
@@ -59581,10 +59766,10 @@ function StudioHeader({
59581
59766
  }
59582
59767
 
59583
59768
  // src/hooks/useGestureCommit.ts
59584
- import { useState as useState100, useCallback as useCallback112, useRef as useRef117, useEffect as useEffect90 } from "react";
59769
+ import { useState as useState100, useCallback as useCallback113, useRef as useRef117, useEffect as useEffect90 } from "react";
59585
59770
 
59586
59771
  // src/hooks/useGestureRecording.ts
59587
- import { useCallback as useCallback111, useEffect as useEffect89, useRef as useRef116, useState as useState99 } from "react";
59772
+ import { useCallback as useCallback112, useEffect as useEffect89, useRef as useRef116, useState as useState99 } from "react";
59588
59773
  function readBasePosition(element, iframeEl) {
59589
59774
  let baseOpacity = 1;
59590
59775
  let baseScale = 1;
@@ -59715,7 +59900,7 @@ function useGestureRecording() {
59715
59900
  isRecordingRef.current = false;
59716
59901
  };
59717
59902
  }, []);
59718
- const startRecording = useCallback111(
59903
+ const startRecording = useCallback112(
59719
59904
  (element, iframeEl, elementEndTime) => {
59720
59905
  if (isRecordingRef.current) return;
59721
59906
  isRecordingRef.current = true;
@@ -59815,7 +60000,7 @@ function useGestureRecording() {
59815
60000
  []
59816
60001
  // No deps — uses refs only for all mutable state
59817
60002
  );
59818
- const stopRecording = useCallback111(() => {
60003
+ const stopRecording = useCallback112(() => {
59819
60004
  if (!isRecordingRef.current) return [];
59820
60005
  isRecordingRef.current = false;
59821
60006
  const r = refs.current;
@@ -59845,7 +60030,7 @@ function useGestureRecording() {
59845
60030
  setIsRecording(false);
59846
60031
  return frozen;
59847
60032
  }, []);
59848
- const clearSamples = useCallback111(() => {
60033
+ const clearSamples = useCallback112(() => {
59849
60034
  const r = refs.current;
59850
60035
  r.samples = [];
59851
60036
  r.trail = [];
@@ -60108,7 +60293,7 @@ function useGestureCommit({
60108
60293
  const commitInFlightRef = useRef117(false);
60109
60294
  const capturedSelectionRef = useRef117(null);
60110
60295
  useEffect90(() => () => clearInterval(recordingAutoStopRef.current), []);
60111
- const stopAndCommitRecording = useCallback112(async () => {
60296
+ const stopAndCommitRecording = useCallback113(async () => {
60112
60297
  clearInterval(recordingAutoStopRef.current);
60113
60298
  if (commitInFlightRef.current) {
60114
60299
  return;
@@ -60281,7 +60466,7 @@ function useGestureCommit({
60281
60466
  commitInFlightRef.current = false;
60282
60467
  }
60283
60468
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
60284
- const handleToggleRecording = useCallback112(() => {
60469
+ const handleToggleRecording = useCallback113(() => {
60285
60470
  if (gestureStateRef.current === "recording") {
60286
60471
  void stopAndCommitRecording();
60287
60472
  return;
@@ -60439,20 +60624,20 @@ var GestureTrailOverlay = memo36(function GestureTrailOverlay2({
60439
60624
  });
60440
60625
 
60441
60626
  // src/components/StudioLeftSidebar.tsx
60442
- import { useCallback as useCallback119 } from "react";
60627
+ import { useCallback as useCallback120 } from "react";
60443
60628
 
60444
60629
  // src/components/sidebar/LeftSidebar.tsx
60445
60630
  import {
60446
60631
  memo as memo40,
60447
60632
  useState as useState108,
60448
- useCallback as useCallback118,
60633
+ useCallback as useCallback119,
60449
60634
  useImperativeHandle,
60450
60635
  useRef as useRef123,
60451
60636
  forwardRef as forwardRef3
60452
60637
  } from "react";
60453
60638
 
60454
60639
  // src/components/sidebar/CompositionsTab.tsx
60455
- import { memo as memo37, useCallback as useCallback113, useEffect as useEffect91, useRef as useRef118, useState as useState101 } from "react";
60640
+ import { memo as memo37, useCallback as useCallback114, useEffect as useEffect91, useRef as useRef118, useState as useState101 } from "react";
60456
60641
  import { jsx as jsx138, jsxs as jsxs116 } from "react/jsx-runtime";
60457
60642
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
60458
60643
  var CARD_W = 80;
@@ -60528,7 +60713,7 @@ function CompCard({
60528
60713
  const hoverTimer = useRef118(null);
60529
60714
  const syncTimer = useRef118(null);
60530
60715
  const draggedRef = useRef118(false);
60531
- const requestIframePlaybackSync = useCallback113((shouldPlay) => {
60716
+ const requestIframePlaybackSync = useCallback114((shouldPlay) => {
60532
60717
  if (syncTimer.current) {
60533
60718
  clearTimeout(syncTimer.current);
60534
60719
  syncTimer.current = null;
@@ -60749,7 +60934,7 @@ var CompositionsTab = memo37(function CompositionsTab2({
60749
60934
  });
60750
60935
 
60751
60936
  // src/components/sidebar/AssetsTab.tsx
60752
- import { memo as memo38, useState as useState106, useCallback as useCallback116, useRef as useRef121, useMemo as useMemo47, useEffect as useEffect96 } from "react";
60937
+ import { memo as memo38, useState as useState106, useCallback as useCallback117, useRef as useRef121, useMemo as useMemo47, useEffect as useEffect96 } from "react";
60753
60938
 
60754
60939
  // src/components/sidebar/assetHelpers.ts
60755
60940
  function getCategory(path) {
@@ -60799,7 +60984,7 @@ var CATEGORY_LABELS = {
60799
60984
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
60800
60985
 
60801
60986
  // src/components/sidebar/AudioRow.tsx
60802
- import { useState as useState102, useRef as useRef119, useEffect as useEffect92, useCallback as useCallback114 } from "react";
60987
+ import { useState as useState102, useRef as useRef119, useEffect as useEffect92, useCallback as useCallback115 } from "react";
60803
60988
 
60804
60989
  // src/components/sidebar/AssetContextMenu.tsx
60805
60990
  import { jsx as jsx139, jsxs as jsxs117 } from "react/jsx-runtime";
@@ -60942,10 +61127,10 @@ function AudioRow({
60942
61127
  const elements = usePlayerStore((s) => s.elements);
60943
61128
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
60944
61129
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
60945
- const handlePointerDown = useCallback114((e) => {
61130
+ const handlePointerDown = useCallback115((e) => {
60946
61131
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
60947
61132
  }, []);
60948
- const handlePointerUp = useCallback114(
61133
+ const handlePointerUp = useCallback115(
60949
61134
  (e) => {
60950
61135
  const origin = pointerDownRef.current;
60951
61136
  pointerDownRef.current = null;
@@ -61009,7 +61194,7 @@ function AudioRow({
61009
61194
  }
61010
61195
  return () => cancelAnimationFrame(animRef.current);
61011
61196
  }, [playing]);
61012
- const togglePlay = useCallback114(async () => {
61197
+ const togglePlay = useCallback115(async () => {
61013
61198
  if (playing) {
61014
61199
  audioRef.current?.pause();
61015
61200
  setPlaying(false);
@@ -61178,7 +61363,7 @@ function GlobalAssetsView({ searchQuery }) {
61178
61363
  }
61179
61364
 
61180
61365
  // src/components/sidebar/AssetCard.tsx
61181
- import { useState as useState105, useEffect as useEffect95, useRef as useRef120, useCallback as useCallback115 } from "react";
61366
+ import { useState as useState105, useEffect as useEffect95, useRef as useRef120, useCallback as useCallback116 } from "react";
61182
61367
 
61183
61368
  // src/components/ui/VideoFrameThumbnail.tsx
61184
61369
  import { useState as useState104, useEffect as useEffect94 } from "react";
@@ -61309,10 +61494,10 @@ function AssetCard({
61309
61494
  const elements = usePlayerStore((s) => s.elements);
61310
61495
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
61311
61496
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
61312
- const handlePointerDown = useCallback115((e) => {
61497
+ const handlePointerDown = useCallback116((e) => {
61313
61498
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
61314
61499
  }, []);
61315
- const handlePointerUp = useCallback115(
61500
+ const handlePointerUp = useCallback116(
61316
61501
  (e) => {
61317
61502
  const origin = pointerDownRef.current;
61318
61503
  pointerDownRef.current = null;
@@ -61542,7 +61727,7 @@ var AssetsTab = memo38(function AssetsTab2({
61542
61727
  cancelled = true;
61543
61728
  };
61544
61729
  }, [projectId, assetsKey]);
61545
- const handleDrop = useCallback116(
61730
+ const handleDrop = useCallback117(
61546
61731
  (e) => {
61547
61732
  e.preventDefault();
61548
61733
  setDragOver(false);
@@ -61550,7 +61735,7 @@ var AssetsTab = memo38(function AssetsTab2({
61550
61735
  },
61551
61736
  [onImport]
61552
61737
  );
61553
- const handleCopyPath = useCallback116(async (path) => {
61738
+ const handleCopyPath = useCallback117(async (path) => {
61554
61739
  const copied = await copyTextToClipboard(path);
61555
61740
  if (copied) {
61556
61741
  setCopiedPath(path);
@@ -61834,7 +62019,7 @@ var AssetsTab = memo38(function AssetsTab2({
61834
62019
  });
61835
62020
 
61836
62021
  // src/components/sidebar/BlocksTab.tsx
61837
- import { memo as memo39, useState as useState107, useCallback as useCallback117, useRef as useRef122, useEffect as useEffect97 } from "react";
62022
+ import { memo as memo39, useState as useState107, useCallback as useCallback118, useRef as useRef122, useEffect as useEffect97 } from "react";
61838
62023
  import { createPortal as createPortal7 } from "react-dom";
61839
62024
  import { jsx as jsx145, jsxs as jsxs122 } from "react/jsx-runtime";
61840
62025
  var BlocksTab = memo39(function BlocksTab2({ onAddBlock, onPreviewBlock }) {
@@ -62049,13 +62234,13 @@ function BlockCard({
62049
62234
  const hoverTimer = useRef122(null);
62050
62235
  const colors = getCategoryColors(category);
62051
62236
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
62052
- const handleEnter = useCallback117(() => {
62237
+ const handleEnter = useCallback118(() => {
62053
62238
  hoverTimer.current = setTimeout(() => {
62054
62239
  setHovered(true);
62055
62240
  onPreview?.({ videoUrl, posterUrl, title });
62056
62241
  }, 300);
62057
62242
  }, [onPreview, videoUrl, posterUrl, title]);
62058
- const handleLeave = useCallback117(() => {
62243
+ const handleLeave = useCallback118(() => {
62059
62244
  if (hoverTimer.current) {
62060
62245
  clearTimeout(hoverTimer.current);
62061
62246
  hoverTimer.current = null;
@@ -62068,7 +62253,7 @@ function BlockCard({
62068
62253
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
62069
62254
  };
62070
62255
  }, []);
62071
- const handleAdd = useCallback117(
62256
+ const handleAdd = useCallback118(
62072
62257
  (e) => {
62073
62258
  e.stopPropagation();
62074
62259
  if (adding || !onAdd) return;
@@ -62079,7 +62264,7 @@ function BlockCard({
62079
62264
  [onAdd, adding]
62080
62265
  );
62081
62266
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
62082
- const handleShowPrompt = useCallback117(
62267
+ const handleShowPrompt = useCallback118(
62083
62268
  (e) => {
62084
62269
  e.stopPropagation();
62085
62270
  const state = usePlayerStore.getState();
@@ -62228,7 +62413,7 @@ function PromptPreviewModal({
62228
62413
  useEffect97(() => {
62229
62414
  requestAnimationFrame(() => textareaRef.current?.focus());
62230
62415
  }, []);
62231
- const handleCopy = useCallback117(() => {
62416
+ const handleCopy = useCallback118(() => {
62232
62417
  navigator.clipboard.writeText(value);
62233
62418
  setCopied(true);
62234
62419
  setTimeout(() => setCopied(false), 1500);
@@ -62355,12 +62540,12 @@ var LeftSidebar = memo40(
62355
62540
  const [tab, setTab] = useState108(getPersistedTab);
62356
62541
  const tabRef = useRef123(tab);
62357
62542
  tabRef.current = tab;
62358
- const selectTab = useCallback118((t) => {
62543
+ const selectTab = useCallback119((t) => {
62359
62544
  setTab(t);
62360
62545
  localStorage.setItem(STORAGE_KEY, t);
62361
62546
  trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
62362
62547
  }, []);
62363
- const getTab = useCallback118(() => tabRef.current, []);
62548
+ const getTab = useCallback119(() => tabRef.current, []);
62364
62549
  useImperativeHandle(ref, () => ({ selectTab, getTab }), [selectTab, getTab]);
62365
62550
  return /* @__PURE__ */ jsx146(
62366
62551
  "div",
@@ -62703,7 +62888,7 @@ function StudioLeftSidebar({
62703
62888
  handleImportFiles,
62704
62889
  handleContentChange
62705
62890
  } = useFileManagerContext();
62706
- const handleRenderComposition = useCallback119(
62891
+ const handleRenderComposition = useCallback120(
62707
62892
  async (comp) => {
62708
62893
  await waitForPendingDomEditSaves();
62709
62894
  const { format, quality, fps } = getPersistedRenderSettings();
@@ -62823,13 +63008,13 @@ function StudioLeftSidebar({
62823
63008
  }
62824
63009
 
62825
63010
  // src/components/StudioRightPanel.tsx
62826
- import { useCallback as useCallback135, useEffect as useEffect104, useRef as useRef129 } from "react";
63011
+ import { useCallback as useCallback136, useEffect as useEffect104, useRef as useRef129 } from "react";
62827
63012
 
62828
63013
  // src/components/editor/LayersPanel.tsx
62829
- import { memo as memo41, useState as useState111, useCallback as useCallback121, useEffect as useEffect98, useRef as useRef125 } from "react";
63014
+ import { memo as memo41, useState as useState111, useCallback as useCallback122, useEffect as useEffect98, useRef as useRef125 } from "react";
62830
63015
 
62831
63016
  // src/components/editor/useLayerDrag.ts
62832
- import { useRef as useRef124, useState as useState110, useCallback as useCallback120 } from "react";
63017
+ import { useRef as useRef124, useState as useState110, useCallback as useCallback121 } from "react";
62833
63018
  var DRAG_THRESHOLD_PX3 = 4;
62834
63019
  function isLayerDraggable(layer) {
62835
63020
  if (!(layer.selector || layer.id)) return false;
@@ -62899,7 +63084,7 @@ function useLayerDrag({
62899
63084
  const dragRef = useRef124(null);
62900
63085
  const [dragKey, setDragKey] = useState110(null);
62901
63086
  const [insertionLineY, setInsertionLineY] = useState110(null);
62902
- const handleRowPointerDown = useCallback120(
63087
+ const handleRowPointerDown = useCallback121(
62903
63088
  (layerIndex, e) => {
62904
63089
  if (e.button !== 0) return;
62905
63090
  const layer = visibleLayers[layerIndex];
@@ -62926,7 +63111,7 @@ function useLayerDrag({
62926
63111
  },
62927
63112
  [visibleLayers, scrollContainerRef, onSingleSibling]
62928
63113
  );
62929
- const handleContainerPointerMove = useCallback120(
63114
+ const handleContainerPointerMove = useCallback121(
62930
63115
  (e) => {
62931
63116
  const drag = dragRef.current;
62932
63117
  if (!drag) return;
@@ -62952,7 +63137,7 @@ function useLayerDrag({
62952
63137
  },
62953
63138
  [visibleLayers, scrollContainerRef]
62954
63139
  );
62955
- const handleContainerPointerUp = useCallback120(() => {
63140
+ const handleContainerPointerUp = useCallback121(() => {
62956
63141
  const drag = dragRef.current;
62957
63142
  dragRef.current = null;
62958
63143
  setDragKey(null);
@@ -63109,7 +63294,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63109
63294
  selectedElement: domEditSelection?.element ?? null
63110
63295
  });
63111
63296
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
63112
- const collectLayers = useCallback121(() => {
63297
+ const collectLayers = useCallback122(() => {
63113
63298
  const iframe = previewIframeRef.current;
63114
63299
  if (!iframe) return;
63115
63300
  let doc = null;
@@ -63156,7 +63341,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63156
63341
  throttle.cancel();
63157
63342
  };
63158
63343
  }, [collectLayers]);
63159
- const resolveSelection = useCallback121(
63344
+ const resolveSelection = useCallback122(
63160
63345
  (layer) => {
63161
63346
  let el = layer.element;
63162
63347
  if (!el.isConnected) {
@@ -63176,7 +63361,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63176
63361
  },
63177
63362
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
63178
63363
  );
63179
- const seekToLayer = useCallback121(
63364
+ const seekToLayer = useCallback122(
63180
63365
  async (layer) => {
63181
63366
  const selection = await resolveSelection(layer);
63182
63367
  if (!selection) return;
@@ -63205,7 +63390,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63205
63390
  },
63206
63391
  [currentTime, resolveSelection, timelineElements]
63207
63392
  );
63208
- const handleSelectLayer = useCallback121(
63393
+ const handleSelectLayer = useCallback122(
63209
63394
  async (layer) => {
63210
63395
  const selection = await resolveSelection(layer);
63211
63396
  if (!selection) return;
@@ -63215,7 +63400,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63215
63400
  },
63216
63401
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
63217
63402
  );
63218
- const handleLayerDoubleClick = useCallback121(
63403
+ const handleLayerDoubleClick = useCallback122(
63219
63404
  async (layer) => {
63220
63405
  const selection = await resolveSelection(layer);
63221
63406
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -63226,7 +63411,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63226
63411
  },
63227
63412
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
63228
63413
  );
63229
- const handleLayerHover = useCallback121(
63414
+ const handleLayerHover = useCallback122(
63230
63415
  async (layer) => {
63231
63416
  if (!layer) {
63232
63417
  updateDomEditHoverSelection(null);
@@ -63237,11 +63422,11 @@ var LayersPanel = memo41(function LayersPanel2() {
63237
63422
  },
63238
63423
  [resolveSelection, updateDomEditHoverSelection]
63239
63424
  );
63240
- const toggleCollapse = useCallback121((key, e) => {
63425
+ const toggleCollapse = useCallback122((key, e) => {
63241
63426
  e.stopPropagation();
63242
63427
  setCollapsed((prev) => ({ ...prev, [key]: !prev[key] }));
63243
63428
  }, []);
63244
- const handleReorder = useCallback121(
63429
+ const handleReorder = useCallback122(
63245
63430
  (event) => {
63246
63431
  const { siblingLayers, fromIndex, toIndex } = event;
63247
63432
  const reordered = [...siblingLayers];
@@ -63304,7 +63489,7 @@ var LayersPanel = memo41(function LayersPanel2() {
63304
63489
  );
63305
63490
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
63306
63491
  const visibleLayers = getVisibleLayers(layers, collapsed);
63307
- const handleSingleSibling = useCallback121(() => {
63492
+ const handleSingleSibling = useCallback122(() => {
63308
63493
  showToast("Only one layer at this level", "info");
63309
63494
  }, [showToast]);
63310
63495
  const {
@@ -63433,10 +63618,10 @@ var LayersPanel = memo41(function LayersPanel2() {
63433
63618
  });
63434
63619
 
63435
63620
  // src/captions/components/CaptionPropertyPanel.tsx
63436
- import { memo as memo43, useCallback as useCallback123, useState as useState112 } from "react";
63621
+ import { memo as memo43, useCallback as useCallback124, useState as useState112 } from "react";
63437
63622
 
63438
63623
  // src/captions/components/CaptionAnimationPanel.tsx
63439
- import { memo as memo42, useCallback as useCallback122 } from "react";
63624
+ import { memo as memo42, useCallback as useCallback123 } from "react";
63440
63625
 
63441
63626
  // src/captions/components/shared.tsx
63442
63627
  import { jsx as jsx150, jsxs as jsxs127 } from "react/jsx-runtime";
@@ -63599,25 +63784,25 @@ var CaptionAnimationPanel = memo42(function CaptionAnimationPanel2() {
63599
63784
  }
63600
63785
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
63601
63786
  const animation = group?.animation;
63602
- const handleEntranceChange = useCallback122(
63787
+ const handleEntranceChange = useCallback123(
63603
63788
  (update) => {
63604
63789
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
63605
63790
  },
63606
63791
  [resolvedGroupId, updateGroupAnimation]
63607
63792
  );
63608
- const handleHighlightChange = useCallback122(
63793
+ const handleHighlightChange = useCallback123(
63609
63794
  (update) => {
63610
63795
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
63611
63796
  },
63612
63797
  [resolvedGroupId, updateGroupAnimation]
63613
63798
  );
63614
- const handleExitChange = useCallback122(
63799
+ const handleExitChange = useCallback123(
63615
63800
  (update) => {
63616
63801
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
63617
63802
  },
63618
63803
  [resolvedGroupId, updateGroupAnimation]
63619
63804
  );
63620
- const handleApplyToAll = useCallback122(() => {
63805
+ const handleApplyToAll = useCallback123(() => {
63621
63806
  if (animation) applyAnimationToAll(animation);
63622
63807
  }, [animation, applyAnimationToAll]);
63623
63808
  if (!group || !resolvedGroupId || !animation) {
@@ -63695,7 +63880,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
63695
63880
  ...groupStyle,
63696
63881
  ...segmentOverrides
63697
63882
  };
63698
- const applyToIframeDom = useCallback123(
63883
+ const applyToIframeDom = useCallback124(
63699
63884
  (updates) => {
63700
63885
  const iframe = iframeRef.current;
63701
63886
  if (!iframe || !model) return;
@@ -63769,7 +63954,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
63769
63954
  },
63770
63955
  [iframeRef, model, selectedSegmentIds]
63771
63956
  );
63772
- const handleStyleChange = useCallback123(
63957
+ const handleStyleChange = useCallback124(
63773
63958
  (updates) => {
63774
63959
  if (selectedGroupId) {
63775
63960
  updateGroupStyle(selectedGroupId, updates);
@@ -63869,7 +64054,7 @@ var CaptionPropertyPanel = memo43(function CaptionPropertyPanel2({
63869
64054
  });
63870
64055
 
63871
64056
  // src/components/editor/BlockParamsPanel.tsx
63872
- import { memo as memo44, useState as useState113, useCallback as useCallback124 } from "react";
64057
+ import { memo as memo44, useState as useState113, useCallback as useCallback125 } from "react";
63873
64058
  import { jsx as jsx153, jsxs as jsxs130 } from "react/jsx-runtime";
63874
64059
  var BlockParamsPanel = memo44(function BlockParamsPanel2({
63875
64060
  blockTitle,
@@ -63884,7 +64069,7 @@ var BlockParamsPanel = memo44(function BlockParamsPanel2({
63884
64069
  }
63885
64070
  return initial;
63886
64071
  });
63887
- const handleChange = useCallback124((key, value) => {
64072
+ const handleChange = useCallback125((key, value) => {
63888
64073
  setValues((prev) => ({ ...prev, [key]: value }));
63889
64074
  }, []);
63890
64075
  return /* @__PURE__ */ jsxs130("div", { className: "flex flex-col h-full", children: [
@@ -63997,7 +64182,7 @@ function ParamControl({
63997
64182
  import { memo as memo46, useState as useState115, useRef as useRef126, useEffect as useEffect99, useId as useId6 } from "react";
63998
64183
 
63999
64184
  // src/components/renders/RenderQueueItem.tsx
64000
- import { memo as memo45, useCallback as useCallback125, useState as useState114 } from "react";
64185
+ import { memo as memo45, useCallback as useCallback126, useState as useState114 } from "react";
64001
64186
  import { Fragment as Fragment48, jsx as jsx154, jsxs as jsxs131 } from "react/jsx-runtime";
64002
64187
  function formatDuration2(ms) {
64003
64188
  if (ms < 1e3) return `${ms}ms`;
@@ -64020,10 +64205,10 @@ var RenderQueueItem = memo45(function RenderQueueItem2({
64020
64205
  const [videoReady, setVideoReady] = useState114(false);
64021
64206
  const [confirmingDelete, setConfirmingDelete] = useState114(false);
64022
64207
  const fileSrc = `/api/projects/${projectId}/renders/file/${job.filename}`;
64023
- const handleOpen = useCallback125(() => {
64208
+ const handleOpen = useCallback126(() => {
64024
64209
  window.open(fileSrc, "_blank");
64025
64210
  }, [fileSrc]);
64026
- const handleDownload = useCallback125(
64211
+ const handleDownload = useCallback126(
64027
64212
  (e) => {
64028
64213
  e.stopPropagation();
64029
64214
  const a = document.createElement("a");
@@ -64607,11 +64792,11 @@ var RenderQueue = memo46(function RenderQueue2({
64607
64792
  });
64608
64793
 
64609
64794
  // src/components/panels/SlideshowPanel.tsx
64610
- import { useState as useState117, useEffect as useEffect100, useCallback as useCallback127, useRef as useRef127 } from "react";
64795
+ import { useState as useState117, useEffect as useEffect100, useCallback as useCallback128, useRef as useRef127 } from "react";
64611
64796
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
64612
64797
 
64613
64798
  // src/components/panels/SlideshowSubPanels.tsx
64614
- import { useState as useState116, useCallback as useCallback126, useId as useId7 } from "react";
64799
+ import { useState as useState116, useCallback as useCallback127, useId as useId7 } from "react";
64615
64800
  import { jsx as jsx156, jsxs as jsxs133 } from "react/jsx-runtime";
64616
64801
  function SectionHeader({
64617
64802
  children,
@@ -64796,7 +64981,7 @@ function BranchTree({
64796
64981
  }) {
64797
64982
  const [newLabel, setNewLabel] = useState116("");
64798
64983
  const inputId = useId7();
64799
- const handleCreate = useCallback126(() => {
64984
+ const handleCreate = useCallback127(() => {
64800
64985
  const label = newLabel.trim();
64801
64986
  if (!label) return;
64802
64987
  onCreateSequence(label);
@@ -64857,7 +65042,7 @@ function BranchItem({
64857
65042
  }) {
64858
65043
  const [editing, setEditing] = useState116(false);
64859
65044
  const [draft, setDraft] = useState116(seq.label);
64860
- const commitRename = useCallback126(() => {
65045
+ const commitRename = useCallback127(() => {
64861
65046
  const label = draft.trim();
64862
65047
  if (label && label !== seq.label) onRename(seq.id, label);
64863
65048
  setEditing(false);
@@ -64955,7 +65140,7 @@ function HotspotTool({
64955
65140
  const selectedElementId = domEditSelection?.element?.id ?? null;
64956
65141
  const selectedHfId = domEditSelection?.hfId ?? null;
64957
65142
  const elementKey2 = selectedElementId || selectedHfId;
64958
- const handleMakeHotspot = useCallback126(() => {
65143
+ const handleMakeHotspot = useCallback127(() => {
64959
65144
  if (!selectedSceneId || !targetSequenceId || !elementKey2) return;
64960
65145
  const id = `hotspot-${elementKey2}-${generateId()}`;
64961
65146
  const label = hotspotLabel.trim() || elementKey2;
@@ -65259,7 +65444,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65259
65444
  manifestRef.current = parsed;
65260
65445
  setSelectedSequenceId(null);
65261
65446
  }, [compHtml]);
65262
- const applyManifest = useCallback127(
65447
+ const applyManifest = useCallback128(
65263
65448
  async (next) => {
65264
65449
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
65265
65450
  setManifest(merged);
@@ -65272,7 +65457,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65272
65457
  },
65273
65458
  [onPersist]
65274
65459
  );
65275
- const applyNotesManifest = useCallback127(
65460
+ const applyNotesManifest = useCallback128(
65276
65461
  (next) => {
65277
65462
  setManifest(next);
65278
65463
  manifestRef.current = next;
@@ -65286,7 +65471,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65286
65471
  ctrl.flush();
65287
65472
  };
65288
65473
  }, []);
65289
- const toggleSection = useCallback127((key) => {
65474
+ const toggleSection = useCallback128((key) => {
65290
65475
  setExpandedSections((prev) => {
65291
65476
  const next = new Set(prev);
65292
65477
  if (next.has(key)) {
@@ -65300,25 +65485,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65300
65485
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
65301
65486
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
65302
65487
  const sequences = manifest.slideSequences ?? [];
65303
- const handleSelectBranchSlide = useCallback127((sequenceId, sceneId) => {
65488
+ const handleSelectBranchSlide = useCallback128((sequenceId, sceneId) => {
65304
65489
  setSelectedSceneId(sceneId);
65305
65490
  setSelectedSequenceId(sequenceId);
65306
65491
  }, []);
65307
- const handleToggleSlide = useCallback127(
65492
+ const handleToggleSlide = useCallback128(
65308
65493
  (sceneId) => {
65309
65494
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
65310
65495
  });
65311
65496
  },
65312
65497
  [applyManifest]
65313
65498
  );
65314
- const handleReorder = useCallback127(
65499
+ const handleReorder = useCallback128(
65315
65500
  (sceneId, dir) => {
65316
65501
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
65317
65502
  });
65318
65503
  },
65319
65504
  [applyManifest]
65320
65505
  );
65321
- const handleSetNotes = useCallback127(
65506
+ const handleSetNotes = useCallback128(
65322
65507
  (notes) => {
65323
65508
  if (!selectedSceneId) return;
65324
65509
  applyNotesManifest(
@@ -65327,7 +65512,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65327
65512
  },
65328
65513
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
65329
65514
  );
65330
- const handleMarkFragment = useCallback127(() => {
65515
+ const handleMarkFragment = useCallback128(() => {
65331
65516
  if (!selectedSceneId) return;
65332
65517
  applyManifest(
65333
65518
  addFragment(
@@ -65339,7 +65524,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65339
65524
  ).catch(() => {
65340
65525
  });
65341
65526
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
65342
- const handleRemoveFragment = useCallback127(
65527
+ const handleRemoveFragment = useCallback128(
65343
65528
  (time) => {
65344
65529
  if (!selectedSceneId) return;
65345
65530
  applyManifest(
@@ -65349,7 +65534,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65349
65534
  },
65350
65535
  [selectedSceneId, selectedSequenceId, applyManifest]
65351
65536
  );
65352
- const handleCreateSequence = useCallback127(
65537
+ const handleCreateSequence = useCallback128(
65353
65538
  (label) => {
65354
65539
  const id = `seq-${generateId()}`;
65355
65540
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -65357,14 +65542,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65357
65542
  },
65358
65543
  [applyManifest]
65359
65544
  );
65360
- const handleRenameSequence = useCallback127(
65545
+ const handleRenameSequence = useCallback128(
65361
65546
  (id, label) => {
65362
65547
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
65363
65548
  });
65364
65549
  },
65365
65550
  [applyManifest]
65366
65551
  );
65367
- const handleDeleteSequence = useCallback127(
65552
+ const handleDeleteSequence = useCallback128(
65368
65553
  (id) => {
65369
65554
  const seq = (manifestRef.current.slideSequences ?? []).find((s) => s.id === id);
65370
65555
  const count = seq?.slides.length ?? 0;
@@ -65378,7 +65563,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65378
65563
  },
65379
65564
  [applyManifest]
65380
65565
  );
65381
- const handleAssign = useCallback127(
65566
+ const handleAssign = useCallback128(
65382
65567
  (sequenceId, sceneId, assign) => {
65383
65568
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
65384
65569
  () => {
@@ -65387,7 +65572,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65387
65572
  },
65388
65573
  [applyManifest]
65389
65574
  );
65390
- const handleAddHotspot = useCallback127(
65575
+ const handleAddHotspot = useCallback128(
65391
65576
  (sceneId, hotspot) => {
65392
65577
  applyManifest(
65393
65578
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -65396,7 +65581,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65396
65581
  },
65397
65582
  [selectedSequenceId, applyManifest]
65398
65583
  );
65399
- const handleRemoveHotspot = useCallback127(
65584
+ const handleRemoveHotspot = useCallback128(
65400
65585
  (sceneId, hotspotId) => {
65401
65586
  applyManifest(
65402
65587
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -65500,10 +65685,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
65500
65685
  }
65501
65686
 
65502
65687
  // src/components/panels/VariablesPanel.tsx
65503
- import { memo as memo47, useCallback as useCallback131, useEffect as useEffect102, useMemo as useMemo48, useState as useState121 } from "react";
65688
+ import { memo as memo47, useCallback as useCallback132, useEffect as useEffect102, useMemo as useMemo48, useState as useState121 } from "react";
65504
65689
 
65505
65690
  // src/hooks/useVariablesPersist.ts
65506
- import { useCallback as useCallback128 } from "react";
65691
+ import { useCallback as useCallback129 } from "react";
65507
65692
  function useVariablesPersist({
65508
65693
  sdkSession,
65509
65694
  activeCompPath,
@@ -65514,7 +65699,7 @@ function useVariablesPersist({
65514
65699
  domEditSaveTimestampRef,
65515
65700
  publishSdkSession
65516
65701
  }) {
65517
- return useCallback128(
65702
+ return useCallback129(
65518
65703
  async (label, mutate) => {
65519
65704
  if (!sdkSession) return false;
65520
65705
  const path = activeCompPath ?? "index.html";
@@ -65551,10 +65736,10 @@ function useVariablesPersist({
65551
65736
  }
65552
65737
 
65553
65738
  // src/components/panels/VariablesOtherCompositions.tsx
65554
- import { useCallback as useCallback130, useState as useState120 } from "react";
65739
+ import { useCallback as useCallback131, useState as useState120 } from "react";
65555
65740
 
65556
65741
  // src/hooks/useProjectCompositionVariables.ts
65557
- import { useCallback as useCallback129, useEffect as useEffect101, useState as useState118 } from "react";
65742
+ import { useCallback as useCallback130, useEffect as useEffect101, useState as useState118 } from "react";
65558
65743
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
65559
65744
  async function readGroup(path, readProjectFile) {
65560
65745
  let content;
@@ -65597,7 +65782,7 @@ function useProjectCompositionVariables(fileTree, excludePath, readProjectFile,
65597
65782
  }
65598
65783
  function useEditVariablesInFile(deps) {
65599
65784
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview, domEditSaveTimestampRef } = deps;
65600
- return useCallback129(
65785
+ return useCallback130(
65601
65786
  async (path, label, mutate) => {
65602
65787
  const originalContent = await readProjectFile(path);
65603
65788
  await persistSdkSerialize(
@@ -65977,7 +66162,7 @@ function VariablesOtherCompositions({
65977
66162
  domEditSaveTimestampRef
65978
66163
  });
65979
66164
  const [editingKey, setEditingKey] = useState120(null);
65980
- const onSave = useCallback130(
66165
+ const onSave = useCallback131(
65981
66166
  (path, decl) => {
65982
66167
  setEditingKey(null);
65983
66168
  void editInFile(
@@ -65988,7 +66173,7 @@ function VariablesOtherCompositions({
65988
66173
  },
65989
66174
  [editInFile]
65990
66175
  );
65991
- const onRemove = useCallback130(
66176
+ const onRemove = useCallback131(
65992
66177
  (path, id) => {
65993
66178
  void editInFile(
65994
66179
  path,
@@ -66224,7 +66409,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66224
66409
  // eslint-disable-next-line react-hooks/exhaustive-deps
66225
66410
  [sdkSession, previewValues, refreshKey, revision]
66226
66411
  );
66227
- const copyToClipboard = useCallback131(
66412
+ const copyToClipboard = useCallback132(
66228
66413
  (text, what) => {
66229
66414
  void copyTextToClipboard(text).then(
66230
66415
  (ok) => showToast(
@@ -66235,7 +66420,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66235
66420
  },
66236
66421
  [showToast]
66237
66422
  );
66238
- const dropPreviewOverride = useCallback131(
66423
+ const dropPreviewOverride = useCallback132(
66239
66424
  (id) => {
66240
66425
  if (previewValues && id in previewValues) {
66241
66426
  const next = { ...previewValues };
@@ -66245,7 +66430,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66245
66430
  },
66246
66431
  [previewValues, setPreviewValues]
66247
66432
  );
66248
- const commitPreviewValue = useCallback131(
66433
+ const commitPreviewValue = useCallback132(
66249
66434
  (id, value, declDefault) => {
66250
66435
  const next = { ...previewValues ?? {} };
66251
66436
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -66258,7 +66443,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66258
66443
  },
66259
66444
  [previewValues, setPreviewValues, reloadPreview]
66260
66445
  );
66261
- const runSchemaEdit = useCallback131(
66446
+ const runSchemaEdit = useCallback132(
66262
66447
  async (label, mutate) => {
66263
66448
  try {
66264
66449
  const changed = await persistVariables(label, mutate);
@@ -66272,7 +66457,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66272
66457
  },
66273
66458
  [persistVariables, showToast]
66274
66459
  );
66275
- const handleAdd = useCallback131(
66460
+ const handleAdd = useCallback132(
66276
66461
  (decl) => {
66277
66462
  if (!sdkSession) return;
66278
66463
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -66285,7 +66470,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66285
66470
  },
66286
66471
  [sdkSession, runSchemaEdit, showToast]
66287
66472
  );
66288
- const handleUpdate = useCallback131(
66473
+ const handleUpdate = useCallback132(
66289
66474
  (decl) => {
66290
66475
  if (!sdkSession) return;
66291
66476
  const check = sdkSession.can({
@@ -66305,7 +66490,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
66305
66490
  },
66306
66491
  [sdkSession, runSchemaEdit, showToast]
66307
66492
  );
66308
- const handleRemove = useCallback131(
66493
+ const handleRemove = useCallback132(
66309
66494
  (id) => {
66310
66495
  if (!sdkSession) return;
66311
66496
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -66321,18 +66506,18 @@ var VariablesPanel = memo47(function VariablesPanel2({
66321
66506
  },
66322
66507
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
66323
66508
  );
66324
- const handleSetDefault = useCallback131(
66509
+ const handleSetDefault = useCallback132(
66325
66510
  (id, value) => {
66326
66511
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
66327
66512
  dropPreviewOverride(id);
66328
66513
  },
66329
66514
  [runSchemaEdit, dropPreviewOverride]
66330
66515
  );
66331
- const resetPreview = useCallback131(() => {
66516
+ const resetPreview = useCallback132(() => {
66332
66517
  setPreviewValues(null);
66333
66518
  reloadPreview();
66334
66519
  }, [setPreviewValues, reloadPreview]);
66335
- const handleBind = useCallback131(
66520
+ const handleBind = useCallback132(
66336
66521
  // Guard chain (session, selection, type-compat) — one branch per guard.
66337
66522
  // fallow-ignore-next-line complexity
66338
66523
  (action, id) => {
@@ -66465,7 +66650,7 @@ function PanelTabButton({
66465
66650
  }
66466
66651
 
66467
66652
  // src/hooks/useSlideshowPersist.ts
66468
- import { useCallback as useCallback132 } from "react";
66653
+ import { useCallback as useCallback133 } from "react";
66469
66654
 
66470
66655
  // src/utils/setSlideshowManifest.ts
66471
66656
  import {
@@ -66523,7 +66708,7 @@ function useSlideshowPersist({
66523
66708
  publishSdkSession,
66524
66709
  coalesceKey
66525
66710
  }) {
66526
- return useCallback132(
66711
+ return useCallback133(
66527
66712
  async (manifest) => {
66528
66713
  if (!sdkSession) return;
66529
66714
  const path = activeCompPath ?? "index.html";
@@ -66588,7 +66773,7 @@ function useSlideshowTabState(params) {
66588
66773
  }
66589
66774
 
66590
66775
  // src/components/DesignPanelPromoteProvider.tsx
66591
- import { useCallback as useCallback133 } from "react";
66776
+ import { useCallback as useCallback134 } from "react";
66592
66777
  import { jsx as jsx163 } from "react/jsx-runtime";
66593
66778
  function DesignPanelPromoteProvider({
66594
66779
  selection,
@@ -66607,7 +66792,7 @@ function DesignPanelPromoteProvider({
66607
66792
  publishSdkSession: handle.publish,
66608
66793
  activeCompPath: targetPath
66609
66794
  });
66610
- const persist2 = useCallback133(
66795
+ const persist2 = useCallback134(
66611
66796
  async (label, mutate) => {
66612
66797
  const committed = await rawPersist(label, mutate);
66613
66798
  if (committed) forceReloadSharedSdkSession?.();
@@ -66615,7 +66800,7 @@ function DesignPanelPromoteProvider({
66615
66800
  },
66616
66801
  [rawPersist, forceReloadSharedSdkSession]
66617
66802
  );
66618
- const handlePersistError = useCallback133(
66803
+ const handlePersistError = useCallback134(
66619
66804
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
66620
66805
  [showToast]
66621
66806
  );
@@ -66850,14 +67035,14 @@ async function applyColorGradingScopeUpdate({
66850
67035
  }
66851
67036
 
66852
67037
  // src/hooks/useInspectorSplitResize.ts
66853
- import { useCallback as useCallback134, useRef as useRef128, useState as useState122 } from "react";
67038
+ import { useCallback as useCallback135, useRef as useRef128, useState as useState122 } from "react";
66854
67039
  var MIN_INSPECTOR_SPLIT_PERCENT = 20;
66855
67040
  var MAX_INSPECTOR_SPLIT_PERCENT = 75;
66856
67041
  function useInspectorSplitResize() {
66857
67042
  const [layersPanePercent, setLayersPanePercent] = useState122(40);
66858
67043
  const splitContainerRef = useRef128(null);
66859
67044
  const splitDragRef = useRef128(null);
66860
- const handleInspectorSplitResizeStart = useCallback134(
67045
+ const handleInspectorSplitResizeStart = useCallback135(
66861
67046
  (event) => {
66862
67047
  event.preventDefault();
66863
67048
  event.currentTarget.setPointerCapture(event.pointerId);
@@ -66870,7 +67055,7 @@ function useInspectorSplitResize() {
66870
67055
  },
66871
67056
  [layersPanePercent]
66872
67057
  );
66873
- const handleInspectorSplitResizeMove = useCallback134((event) => {
67058
+ const handleInspectorSplitResizeMove = useCallback135((event) => {
66874
67059
  const drag = splitDragRef.current;
66875
67060
  if (!drag || drag.height <= 0) return;
66876
67061
  const deltaPercent = (event.clientY - drag.startY) / drag.height * 100;
@@ -66880,7 +67065,7 @@ function useInspectorSplitResize() {
66880
67065
  );
66881
67066
  setLayersPanePercent(next);
66882
67067
  }, []);
66883
- const handleInspectorSplitResizeEnd = useCallback134(() => {
67068
+ const handleInspectorSplitResizeEnd = useCallback135(() => {
66884
67069
  splitDragRef.current = null;
66885
67070
  }, []);
66886
67071
  return {
@@ -66970,6 +67155,7 @@ function StudioRightPanel({
66970
67155
  handleUpdateArcSegment,
66971
67156
  handleUnroll,
66972
67157
  handleUpdateKeyframeEase,
67158
+ handleUpdateSegmentEase,
66973
67159
  handleSetAllKeyframeEases,
66974
67160
  handleGsapAddKeyframe,
66975
67161
  handleGsapRemoveKeyframe,
@@ -67044,7 +67230,7 @@ function StudioRightPanel({
67044
67230
  }
67045
67231
  toggleRightInspectorPane(pane);
67046
67232
  };
67047
- const handleApplyColorGradingScope = useCallback135(
67233
+ const handleApplyColorGradingScope = useCallback136(
67048
67234
  async (scope, value) => applyColorGradingScopeUpdate({
67049
67235
  scope,
67050
67236
  value,
@@ -67079,7 +67265,7 @@ function StudioRightPanel({
67079
67265
  writeProjectFile
67080
67266
  ]
67081
67267
  );
67082
- const handleRemoveBackground = useCallback135(
67268
+ const handleRemoveBackground = useCallback136(
67083
67269
  // fallow-ignore-next-line complexity
67084
67270
  async (inputPath, options) => {
67085
67271
  const response = await fetch(
@@ -67191,6 +67377,7 @@ function StudioRightPanel({
67191
67377
  onUnroll: handleUnroll,
67192
67378
  onUpdateKeyframeEase: handleUpdateKeyframeEase,
67193
67379
  onSetAllKeyframeEases: handleSetAllKeyframeEases,
67380
+ onUpdateSegmentEase: handleUpdateSegmentEase,
67194
67381
  recordingState,
67195
67382
  recordingDuration,
67196
67383
  onToggleRecording
@@ -67389,7 +67576,7 @@ import { useEffect as useEffect106, useRef as useRef130 } from "react";
67389
67576
  import { Magnet, MagnifyingGlassMinus, MagnifyingGlassPlus } from "@phosphor-icons/react";
67390
67577
 
67391
67578
  // src/hooks/useEnableKeyframes.ts
67392
- import { useCallback as useCallback136 } from "react";
67579
+ import { useCallback as useCallback137 } from "react";
67393
67580
  var enableKeyframesTransactionCounter = 0;
67394
67581
  function animatedProps(anim) {
67395
67582
  if (!anim) return ["x", "y"];
@@ -67642,7 +67829,7 @@ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t, iframe) {
67642
67829
  );
67643
67830
  }
67644
67831
  function useEnableKeyframes(sessionRef) {
67645
- return useCallback136(async () => {
67832
+ return useCallback137(async () => {
67646
67833
  const session = sessionRef.current;
67647
67834
  if (!session) return;
67648
67835
  const sel = session.domEditSelection;
@@ -67724,7 +67911,7 @@ function useEnableKeyframes(sessionRef) {
67724
67911
  }
67725
67912
 
67726
67913
  // src/hooks/useKeyframeKeyboard.ts
67727
- import { useEffect as useEffect105, useCallback as useCallback137 } from "react";
67914
+ import { useEffect as useEffect105, useCallback as useCallback138 } from "react";
67728
67915
  function isTextInput(el) {
67729
67916
  if (!el) return false;
67730
67917
  const tag = el.tagName;
@@ -67741,7 +67928,7 @@ function useKeyframeKeyboard({
67741
67928
  onToggleExpand,
67742
67929
  onNudgeKeyframe
67743
67930
  }) {
67744
- const handler = useCallback137(
67931
+ const handler = useCallback138(
67745
67932
  (e) => {
67746
67933
  if (!enabled2) return;
67747
67934
  if (isTextInput(document.activeElement)) return;
@@ -68134,7 +68321,7 @@ import { useState as useState130 } from "react";
68134
68321
  import { Copy as Copy2, Check as Check2 } from "@phosphor-icons/react";
68135
68322
 
68136
68323
  // src/hooks/useStoryboard.ts
68137
- import { useCallback as useCallback138, useEffect as useEffect107, useRef as useRef131, useState as useState123 } from "react";
68324
+ import { useCallback as useCallback139, useEffect as useEffect107, useRef as useRef131, useState as useState123 } from "react";
68138
68325
  function useStoryboard(projectId) {
68139
68326
  const [data, setData] = useState123(null);
68140
68327
  const [loading, setLoading] = useState123(true);
@@ -68142,7 +68329,7 @@ function useStoryboard(projectId) {
68142
68329
  const [reloadKey, setReloadKey] = useState123(0);
68143
68330
  const hasDataRef = useRef131(false);
68144
68331
  const lastProjectRef = useRef131(null);
68145
- const reload = useCallback138(() => setReloadKey((k) => k + 1), []);
68332
+ const reload = useCallback139(() => setReloadKey((k) => k + 1), []);
68146
68333
  useEffect107(() => {
68147
68334
  if (!projectId) return;
68148
68335
  let cancelled = false;
@@ -68452,7 +68639,7 @@ function StoryboardScriptPanel({ script }) {
68452
68639
  }
68453
68640
 
68454
68641
  // src/components/storyboard/StoryboardSourceEditor.tsx
68455
- import { useCallback as useCallback139, useEffect as useEffect110, useMemo as useMemo50, useRef as useRef133, useState as useState125 } from "react";
68642
+ import { useCallback as useCallback140, useEffect as useEffect110, useMemo as useMemo50, useRef as useRef133, useState as useState125 } from "react";
68456
68643
  import { marked } from "marked";
68457
68644
  import DOMPurify from "dompurify";
68458
68645
  import { jsx as jsx171, jsxs as jsxs143 } from "react/jsx-runtime";
@@ -68482,7 +68669,7 @@ function useEditableFile(path, onSaved) {
68482
68669
  cancelled = true;
68483
68670
  };
68484
68671
  }, [path, readProjectFile]);
68485
- const save = useCallback139(() => {
68672
+ const save = useCallback140(() => {
68486
68673
  if (saving) return;
68487
68674
  setSaving(true);
68488
68675
  setError(null);
@@ -68607,7 +68794,7 @@ function StoryboardSourceEditor({
68607
68794
  }
68608
68795
 
68609
68796
  // src/components/storyboard/StoryboardFrameFocus.tsx
68610
- import { useCallback as useCallback140, useEffect as useEffect112, useState as useState127 } from "react";
68797
+ import { useCallback as useCallback141, useEffect as useEffect112, useState as useState127 } from "react";
68611
68798
  import { setFrameVoiceover } from "@hyperframes/core/storyboard";
68612
68799
 
68613
68800
  // src/components/storyboard/AgentChatMessageButton.tsx
@@ -68667,7 +68854,7 @@ function StoryboardFrameFocus({
68667
68854
  const [savedVoiceover, setSavedVoiceover] = useState127(frame.voiceover ?? "");
68668
68855
  const [busy, setBusy] = useState127(false);
68669
68856
  const [error, setError] = useState127(null);
68670
- const applyEdit = useCallback140(
68857
+ const applyEdit = useCallback141(
68671
68858
  async (edit) => {
68672
68859
  if (busy) return false;
68673
68860
  setBusy(true);
@@ -68689,7 +68876,7 @@ function StoryboardFrameFocus({
68689
68876
  const title = frame.title ?? `Frame ${frame.index}`;
68690
68877
  const dirty = draft !== savedVoiceover;
68691
68878
  const canOpenPreview = frame.status !== "outline" && frame.srcExists && Boolean(frame.src);
68692
- const saveVoiceover = useCallback140(async () => {
68879
+ const saveVoiceover = useCallback141(async () => {
68693
68880
  const saved = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
68694
68881
  if (saved) setSavedVoiceover(draft);
68695
68882
  }, [applyEdit, frame.index, draft]);
@@ -68701,7 +68888,7 @@ function StoryboardFrameFocus({
68701
68888
  window.addEventListener("beforeunload", onBeforeUnload);
68702
68889
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
68703
68890
  }, [dirty]);
68704
- const confirmLeave = useCallback140(
68891
+ const confirmLeave = useCallback141(
68705
68892
  () => !dirty || window.confirm("Discard unsaved voiceover changes?"),
68706
68893
  [dirty]
68707
68894
  );
@@ -69184,7 +69371,7 @@ function progressLabel(summary) {
69184
69371
  }
69185
69372
 
69186
69373
  // src/components/storyboard/useFrameComments.ts
69187
- import { useCallback as useCallback141, useEffect as useEffect113, useMemo as useMemo51, useState as useState128 } from "react";
69374
+ import { useCallback as useCallback142, useEffect as useEffect113, useMemo as useMemo51, useState as useState128 } from "react";
69188
69375
 
69189
69376
  // src/components/storyboard/frameComments.ts
69190
69377
  var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
@@ -69250,7 +69437,7 @@ function useFrameComments(frames) {
69250
69437
  const [submitState, setSubmitState] = useState128("idle");
69251
69438
  const [submitError, setSubmitError] = useState128(null);
69252
69439
  const [pending, setPending] = useState128(null);
69253
- const refreshPending = useCallback141(async () => {
69440
+ const refreshPending = useCallback142(async () => {
69254
69441
  try {
69255
69442
  const parsed = parseCommentsFile(await readOptionalProjectFile(FRAME_COMMENTS_PATH));
69256
69443
  setPending(parsed && parsed.comments.length > 0 ? parsed.comments : null);
@@ -69263,14 +69450,14 @@ function useFrameComments(frames) {
69263
69450
  window.addEventListener("focus", onFocus);
69264
69451
  return () => window.removeEventListener("focus", onFocus);
69265
69452
  }, [refreshPending]);
69266
- const setDraft = useCallback141((index, text) => {
69453
+ const setDraft = useCallback142((index, text) => {
69267
69454
  setDrafts((prev) => ({ ...prev, [index]: text }));
69268
69455
  }, []);
69269
69456
  const draftCount = useMemo51(
69270
69457
  () => Object.values(drafts).filter((text) => text.trim().length > 0).length,
69271
69458
  [drafts]
69272
69459
  );
69273
- const submit = useCallback141(async () => {
69460
+ const submit = useCallback142(async () => {
69274
69461
  if (draftCount === 0 || submitState === "saving") return false;
69275
69462
  setSubmitState("saving");
69276
69463
  setSubmitError(null);
@@ -69723,12 +69910,12 @@ function useServerConnection() {
69723
69910
  }
69724
69911
 
69725
69912
  // src/hooks/useTimelineAddAtPlayhead.ts
69726
- import { useCallback as useCallback142 } from "react";
69913
+ import { useCallback as useCallback143 } from "react";
69727
69914
  function useTimelineAddAtPlayhead(addAsset, addComposition) {
69728
69915
  const placement = () => ({ start: usePlayerStore.getState().currentTime, track: 0 });
69729
69916
  return {
69730
- addAssetAtPlayhead: useCallback142((path) => addAsset(path, placement()), [addAsset]),
69731
- addCompositionAtPlayhead: useCallback142(
69917
+ addAssetAtPlayhead: useCallback143((path) => addAsset(path, placement()), [addAsset]),
69918
+ addCompositionAtPlayhead: useCallback143(
69732
69919
  (path) => addComposition(path, placement()),
69733
69920
  [addComposition]
69734
69921
  )
@@ -69784,7 +69971,7 @@ function StudioApp() {
69784
69971
  const handleDomZIndexReorderCommitRef = useRef134(null);
69785
69972
  const pendingTimelineEditPathRef = useRef134(/* @__PURE__ */ new Set());
69786
69973
  const isGestureRecordingRef = useRef134(false);
69787
- const reloadPreview = useCallback143(() => setRefreshKey((k) => k + 1), []);
69974
+ const reloadPreview = useCallback144(() => setRefreshKey((k) => k + 1), []);
69788
69975
  const fileManager = useFileManager({
69789
69976
  projectId,
69790
69977
  showToast,
@@ -69826,7 +70013,7 @@ function StudioApp() {
69826
70013
  });
69827
70014
  const invalidateGsapCacheRef = useRef134(() => {
69828
70015
  });
69829
- const invalidateGsapCache = useCallback143(() => invalidateGsapCacheRef.current(), []);
70016
+ const invalidateGsapCache = useCallback144(() => invalidateGsapCacheRef.current(), []);
69830
70017
  const timelineEditing = useTimelineEditing({
69831
70018
  projectId,
69832
70019
  activeCompPath,
@@ -69847,7 +70034,7 @@ function StudioApp() {
69847
70034
  invalidateGsapCache,
69848
70035
  handleDomZIndexReorderCommitRef
69849
70036
  });
69850
- const handleTimelineElementsMove = useCallback143(
70037
+ const handleTimelineElementsMove = useCallback144(
69851
70038
  async (edits, coalesceKey, operation = "timing", coalesceMs) => {
69852
70039
  const deps = { handleTimelineGroupMove: timelineEditing.handleTimelineGroupMove };
69853
70040
  await persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps, coalesceMs);
@@ -70043,7 +70230,7 @@ function StudioApp() {
70043
70230
  }
70044
70231
  canvasRectRef.current = previewIframe.getBoundingClientRect();
70045
70232
  }, [gestureState, previewIframe]);
70046
- const handlePreviewIframeRef = useCallback143(
70233
+ const handlePreviewIframeRef = useCallback144(
70047
70234
  (iframe) => {
70048
70235
  previewIframeRef.current = iframe;
70049
70236
  setPreviewIframe(iframe);
@@ -70267,32 +70454,32 @@ function StudioApp() {
70267
70454
  }
70268
70455
 
70269
70456
  // src/hooks/useElementPicker.ts
70270
- import { useState as useState133, useCallback as useCallback144, useRef as useRef135 } from "react";
70457
+ import { useState as useState133, useCallback as useCallback145, useRef as useRef135 } from "react";
70271
70458
  function useElementPicker(iframeRef, options) {
70272
70459
  const [isPickMode, setIsPickMode] = useState133(false);
70273
70460
  const [pickedElement, setPickedElement] = useState133(null);
70274
70461
  const activeOverrideRef = useRef135(null);
70275
- const getActiveIframe = useCallback144(() => {
70462
+ const getActiveIframe = useCallback145(() => {
70276
70463
  return activeOverrideRef.current ?? iframeRef.current;
70277
70464
  }, [iframeRef]);
70278
- const setActiveIframe = useCallback144((el) => {
70465
+ const setActiveIframe = useCallback145((el) => {
70279
70466
  activeOverrideRef.current = el;
70280
70467
  }, []);
70281
- const enablePick = useCallback144(() => {
70468
+ const enablePick = useCallback145(() => {
70282
70469
  try {
70283
70470
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "enable-pick-mode");
70284
70471
  setIsPickMode(true);
70285
70472
  } catch {
70286
70473
  }
70287
70474
  }, [getActiveIframe]);
70288
- const disablePick = useCallback144(() => {
70475
+ const disablePick = useCallback145(() => {
70289
70476
  try {
70290
70477
  postRuntimeControlMessage(getActiveIframe()?.contentWindow, "disable-pick-mode");
70291
70478
  } catch {
70292
70479
  }
70293
70480
  setIsPickMode(false);
70294
70481
  }, [getActiveIframe]);
70295
- const clearPick = useCallback144(() => {
70482
+ const clearPick = useCallback145(() => {
70296
70483
  setPickedElement(null);
70297
70484
  }, []);
70298
70485
  useMountEffect(() => {
@@ -70347,7 +70534,7 @@ function useElementPicker(iframeRef, options) {
70347
70534
  });
70348
70535
  const optionsRef = useRef135(options);
70349
70536
  optionsRef.current = options;
70350
- const syncToSource = useCallback144(
70537
+ const syncToSource = useCallback145(
70351
70538
  (elementId, selector, op) => {
70352
70539
  const opts = optionsRef.current;
70353
70540
  if (!opts?.workspaceFiles || !opts.onSyncFiles || !elementId) return;
@@ -70363,7 +70550,7 @@ function useElementPicker(iframeRef, options) {
70363
70550
  },
70364
70551
  []
70365
70552
  );
70366
- const setStyle = useCallback144(
70553
+ const setStyle = useCallback145(
70367
70554
  (prop, value) => {
70368
70555
  const activeIframe = getActiveIframe();
70369
70556
  if (!pickedElement?.selector || !activeIframe) return;
@@ -70405,7 +70592,7 @@ function useElementPicker(iframeRef, options) {
70405
70592
  },
70406
70593
  [pickedElement, getActiveIframe, syncToSource]
70407
70594
  );
70408
- const setDataAttr = useCallback144(
70595
+ const setDataAttr = useCallback145(
70409
70596
  (attr, value) => {
70410
70597
  const activeIframe = getActiveIframe();
70411
70598
  if (!pickedElement?.selector || !activeIframe) return;
@@ -70433,7 +70620,7 @@ function useElementPicker(iframeRef, options) {
70433
70620
  },
70434
70621
  [pickedElement, getActiveIframe, syncToSource]
70435
70622
  );
70436
- const setTextContent = useCallback144(
70623
+ const setTextContent = useCallback145(
70437
70624
  (text) => {
70438
70625
  const activeIframe = getActiveIframe();
70439
70626
  if (!pickedElement?.selector || !activeIframe) return;