@hyperframes/studio 0.8.133 → 0.8.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9465,15 +9465,24 @@ function focusedField() {
9465
9465
  function hasStudioPendingEdits() {
9466
9466
  return pendingEdits.size > 0 || focusedField() !== null;
9467
9467
  }
9468
+ var shownRestores = /* @__PURE__ */ new Set();
9469
+ function beginStudioShownRestore() {
9470
+ let end = () => {
9471
+ };
9472
+ const landed = new Promise((resolve) => end = resolve);
9473
+ shownRestores.add(landed);
9474
+ void landed.then(() => shownRestores.delete(landed));
9475
+ return end;
9476
+ }
9468
9477
  function isStudioEditSaving() {
9469
- return pendingEdits.size > 0;
9478
+ return pendingEdits.size > 0 || shownRestores.size > 0;
9470
9479
  }
9471
9480
  function afterStudioPendingEdits(run) {
9472
9481
  let waiting = true;
9473
9482
  const check = () => {
9474
9483
  if (!waiting) return;
9475
9484
  if (isStudioEditSaving()) {
9476
- void Promise.allSettled([...pendingEdits.keys()]).then(check);
9485
+ void Promise.allSettled([...pendingEdits.keys(), ...shownRestores]).then(check);
9477
9486
  return;
9478
9487
  }
9479
9488
  waiting = false;
@@ -17122,6 +17131,12 @@ function setElementGsapScale(element, x, y) {
17122
17131
  gsap3.set(element, { scaleX: x, scaleY: y });
17123
17132
  return true;
17124
17133
  }
17134
+ function setElementGsapSize(element, width, height) {
17135
+ const gsap3 = gsapOf(element);
17136
+ if (!gsap3?.set) return false;
17137
+ gsap3.set(element, { width, height });
17138
+ return true;
17139
+ }
17125
17140
  function elementTargets(tween) {
17126
17141
  return (tween.targets?.() ?? []).filter(
17127
17142
  (t2) => Boolean(t2?.style && t2.nodeType)
@@ -30342,9 +30357,12 @@ var gestureSaves = /* @__PURE__ */ new WeakMap();
30342
30357
  function studioManualEditSavesIn(doc) {
30343
30358
  return gestureSaves.get(doc) ?? 0;
30344
30359
  }
30360
+ function countStudioPreviewChange(doc) {
30361
+ gestureSaves.set(doc, studioManualEditSavesIn(doc) + 1);
30362
+ }
30345
30363
  function countStudioManualEditSave(element, save) {
30346
30364
  const doc = element.ownerDocument;
30347
- const count = () => void gestureSaves.set(doc, studioManualEditSavesIn(doc) + 1);
30365
+ const count = () => countStudioPreviewChange(doc);
30348
30366
  count();
30349
30367
  const result = save();
30350
30368
  void Promise.resolve(result).then(count, count);
@@ -37412,22 +37430,31 @@ function readCssRotation(element, withRotate = true) {
37412
37430
  return angle(transform);
37413
37431
  }
37414
37432
  }
37415
- function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
37433
+ function readDragStamp(element) {
37434
+ const read2 = (name) => Number.parseFloat(element.getAttribute(name) ?? "");
37435
+ return {
37436
+ origX: read2("data-hf-drag-initial-offset-x") || 0,
37437
+ origY: read2("data-hf-drag-initial-offset-y") || 0,
37438
+ baseX: read2("data-hf-drag-gsap-base-x"),
37439
+ baseY: read2("data-hf-drag-gsap-base-y")
37440
+ };
37441
+ }
37442
+ var freezeDragStamp = (element) => ({
37443
+ ...readDragStamp(element),
37444
+ frozen: true
37445
+ });
37446
+ function computeDraggedGsapPosition(element, studioOffset, fallbackBase, stamp = readDragStamp(element)) {
37416
37447
  const rotStyle = element.style.getPropertyValue("--hf-studio-rotation");
37417
37448
  const rotDeg = Number.parseFloat(rotStyle) || 0;
37418
37449
  const rad = -rotDeg * Math.PI / 180;
37419
37450
  const cos = Math.cos(rad);
37420
37451
  const sin = Math.sin(rad);
37421
- const origX = Number.parseFloat(element.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
37422
- const origY = Number.parseFloat(element.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
37423
- const deltaX = studioOffset.x - origX;
37424
- const deltaY = studioOffset.y - origY;
37452
+ const deltaX = studioOffset.x - stamp.origX;
37453
+ const deltaY = studioOffset.y - stamp.origY;
37425
37454
  const adjX = deltaX * cos - deltaY * sin;
37426
37455
  const adjY = deltaX * sin + deltaY * cos;
37427
- const parsedBaseX = Number.parseFloat(element.getAttribute("data-hf-drag-gsap-base-x") ?? "");
37428
- const parsedBaseY = Number.parseFloat(element.getAttribute("data-hf-drag-gsap-base-y") ?? "");
37429
- const baseGsapX = Number.isFinite(parsedBaseX) ? parsedBaseX : fallbackBase.x;
37430
- const baseGsapY = Number.isFinite(parsedBaseY) ? parsedBaseY : fallbackBase.y;
37456
+ const baseGsapX = Number.isFinite(stamp.baseX) ? stamp.baseX : fallbackBase.x;
37457
+ const baseGsapY = Number.isFinite(stamp.baseY) ? stamp.baseY : fallbackBase.y;
37431
37458
  return {
37432
37459
  newX: roundTo3(baseGsapX + adjX),
37433
37460
  newY: roundTo3(baseGsapY + adjY),
@@ -37435,10 +37462,10 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
37435
37462
  baseGsapY
37436
37463
  };
37437
37464
  }
37438
- function restoreDragOffset(element) {
37439
- const origin2 = (axis) => Number.parseFloat(element.getAttribute(`data-hf-drag-initial-offset-${axis}`) ?? "") || 0;
37440
- element.style.setProperty("--hf-studio-offset-x", `${origin2("x")}px`);
37441
- element.style.setProperty("--hf-studio-offset-y", `${origin2("y")}px`);
37465
+ function restoreDragOffset(element, stamp = readDragStamp(element)) {
37466
+ if (stamp.frozen) return;
37467
+ element.style.setProperty("--hf-studio-offset-x", `${stamp.origX}px`);
37468
+ element.style.setProperty("--hf-studio-offset-y", `${stamp.origY}px`);
37442
37469
  element.removeAttribute("data-hf-drag-initial-offset-x");
37443
37470
  element.removeAttribute("data-hf-drag-initial-offset-y");
37444
37471
  }
@@ -37512,6 +37539,12 @@ function readTranslatePx(el) {
37512
37539
  y: evaluateLength(y, side("height", "top", "bottom"))
37513
37540
  };
37514
37541
  }
37542
+ function readTranslatePxLeavingPercent(el) {
37543
+ const value = el.ownerDocument.defaultView?.getComputedStyle(el).translate || "none";
37544
+ const [x = "0px", y = "0px"] = value === "none" ? [] : splitTopLevelWhitespace(value);
37545
+ const px = (part) => roundTo3(evaluateLength(part, 0)) || 0;
37546
+ return { x: px(x), y: px(y) };
37547
+ }
37515
37548
  var UNREADABLE_TRANSLATE = "Studio can't read this layer's translate. Move it in the Code tab.";
37516
37549
  function formatTranslatePx(p) {
37517
37550
  return `${roundTo3(p.x)}px ${roundTo3(p.y)}px`;
@@ -45740,7 +45773,12 @@ function refuseStepListRewrite(anim) {
45740
45773
  if (step) throw new GsapEditBlockedError("keyframes-uneditable", step);
45741
45774
  }
45742
45775
  async function commitStaticGsapPosition(selection, studioOffset, gsapPos, selector, existingSet, callbacks) {
45743
- const { newX, newY } = computeDraggedGsapPosition(selection.element, studioOffset, gsapPos);
45776
+ const { newX, newY } = computeDraggedGsapPosition(
45777
+ selection.element,
45778
+ studioOffset,
45779
+ gsapPos,
45780
+ callbacks.stamp
45781
+ );
45744
45782
  if (existingSet) {
45745
45783
  if (existingSet.keyframes) {
45746
45784
  await replaceKeyframedPositionHold(
@@ -45859,21 +45897,16 @@ async function commitStaticGsapSize(selection, size, selector, existingSet, call
45859
45897
  }
45860
45898
  async function commitWholePathOffset(selection, anim, studioOffset, gsapPos, iframe, selector, callbacks) {
45861
45899
  const el = selection.element;
45900
+ const stamp = callbacks.stamp ?? readDragStamp(el);
45862
45901
  const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
45863
45902
  el,
45864
45903
  studioOffset,
45865
- gsapPos
45904
+ gsapPos,
45905
+ stamp
45866
45906
  );
45867
45907
  const deltaX = newX - baseGsapX;
45868
45908
  const deltaY = newY - baseGsapY;
45869
- const origX = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
45870
- const origY = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
45871
- const restoreOffset = () => {
45872
- el.style.setProperty("--hf-studio-offset-x", `${origX}px`);
45873
- el.style.setProperty("--hf-studio-offset-y", `${origY}px`);
45874
- el.removeAttribute("data-hf-drag-initial-offset-x");
45875
- el.removeAttribute("data-hf-drag-initial-offset-y");
45876
- };
45909
+ const restoreOffset = () => restoreDragOffset(el, stamp);
45877
45910
  let effectiveAnim = anim;
45878
45911
  if (anim.keyframes) {
45879
45912
  refuseStepListRewrite(anim);
@@ -47811,19 +47844,14 @@ function gsapPositionFromDragOutcome(selection, anim, studioOffset, gsapPos, ifr
47811
47844
  }
47812
47845
  async function commitGsapPositionFromDrag(selection, anim, studioOffset, gsapPos, iframe, callbacks) {
47813
47846
  const el = selection.element;
47847
+ const stamp = callbacks.stamp ?? readDragStamp(el);
47814
47848
  const { newX, newY, baseGsapX, baseGsapY } = computeDraggedGsapPosition(
47815
47849
  el,
47816
47850
  studioOffset,
47817
- gsapPos
47851
+ gsapPos,
47852
+ stamp
47818
47853
  );
47819
- const origX = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-x") ?? "") || 0;
47820
- const origY = Number.parseFloat(el.getAttribute("data-hf-drag-initial-offset-y") ?? "") || 0;
47821
- const restoreOffset = () => {
47822
- el.style.setProperty("--hf-studio-offset-x", `${origX}px`);
47823
- el.style.setProperty("--hf-studio-offset-y", `${origY}px`);
47824
- el.removeAttribute("data-hf-drag-initial-offset-x");
47825
- el.removeAttribute("data-hf-drag-initial-offset-y");
47826
- };
47854
+ const restoreOffset = () => restoreDragOffset(el, stamp);
47827
47855
  if (anim.arcPath?.enabled) {
47828
47856
  const { activeKeyframePct, currentTime, setActiveKeyframePct } = usePlayerStore.getState();
47829
47857
  const tweenStart = resolveTweenStart(anim);
@@ -48086,7 +48114,8 @@ async function tryGsapDragIntercept(selection, offset, allAnimations, iframe, ge
48086
48114
  const existingSet = posAnim && isInstantHold(posAnim) && posAnim.targetSelector === selector ? posAnim : findExistingPositionWrite(resolvedAnimations, selector, selection.element);
48087
48115
  await commitStaticGsapPosition(selection, offset, gsapPos, selector, existingSet, {
48088
48116
  commitMutation,
48089
- fetchAnimations: fetchFallbackAnimations
48117
+ fetchAnimations: fetchFallbackAnimations,
48118
+ stamp: options?.stamp
48090
48119
  });
48091
48120
  return { status: "persisted" };
48092
48121
  }
@@ -48104,7 +48133,7 @@ async function tryGsapDragIntercept(selection, offset, allAnimations, iframe, ge
48104
48133
  posAnim = freshMatch;
48105
48134
  }
48106
48135
  }
48107
- const cbs = { commitMutation, fetchAnimations: fetchFallbackAnimations };
48136
+ const cbs = { commitMutation, fetchAnimations: fetchFallbackAnimations, stamp: options?.stamp };
48108
48137
  if (route === "whole-path") {
48109
48138
  await commitWholePathOffset(selection, posAnim, offset, gsapPos, iframe, selector, cbs);
48110
48139
  } else {
@@ -74787,7 +74816,8 @@ function showRestoreInPlace(iframe, activeCompPath, files, currentTime) {
74787
74816
  const doc = iframe?.contentDocument;
74788
74817
  const win = iframe?.contentWindow;
74789
74818
  const plan = doc && win ? planRestoreTargets(doc, activeCompPath ?? "index.html", files) : null;
74790
- if (!iframe || !win || !plan || plan.scripted) return null;
74819
+ if (!iframe || !doc || !win || !plan || plan.scripted) return null;
74820
+ countStudioPreviewChange(doc);
74791
74821
  const before = plan.targets.map(
74792
74822
  ({ live, restored }) => [live, live.cloneNode(false), restored]
74793
74823
  );
@@ -74821,6 +74851,7 @@ function applyUndoRestoreToPreview(iframe, activeCompPath, files, currentTime, r
74821
74851
  reloadPreview();
74822
74852
  return "full";
74823
74853
  }
74854
+ countStudioPreviewChange(doc);
74824
74855
  for (const target of plan.targets)
74825
74856
  syncElementAttributes(target.live, target.restored, target.previous);
74826
74857
  const active = files[activeDocPath];
@@ -78180,7 +78211,7 @@ function useTimelineMoveEditsHandler(handleTimelineGroupMove) {
78180
78211
  }
78181
78212
 
78182
78213
  // src/hooks/useDomEditSession.ts
78183
- import { useCallback as useCallback148 } from "react";
78214
+ import { useCallback as useCallback149 } from "react";
78184
78215
 
78185
78216
  // src/hooks/useAskAgentModal.ts
78186
78217
  import { useState as useState117, useCallback as useCallback125, useRef as useRef140, useEffect as useEffect115 } from "react";
@@ -83164,7 +83195,7 @@ function useGsapAwareGroupMove({
83164
83195
  }
83165
83196
 
83166
83197
  // src/hooks/useGsapAwareEditing.ts
83167
- import { useCallback as useCallback146 } from "react";
83198
+ import { useCallback as useCallback147 } from "react";
83168
83199
 
83169
83200
  // src/hooks/gsapResizePreflight.ts
83170
83201
  function resizeRoute(animations, fetchedAnimations) {
@@ -83196,9 +83227,339 @@ function preflightGsapResizeIntercept(selection, animations, iframe, fetchedAnim
83196
83227
  return { status: "persisted" };
83197
83228
  }
83198
83229
 
83230
+ // src/hooks/useEnableKeyframes.ts
83231
+ import { useCallback as useCallback145 } from "react";
83232
+ var enableKeyframesTransactionCounter = 0;
83233
+ function animatedProps(anim) {
83234
+ if (!anim) return ["x", "y"];
83235
+ const own = Object.keys(anim.properties ?? {});
83236
+ if (own.length > 0) return own;
83237
+ const stops = anim.keyframes?.keyframes;
83238
+ if (stops?.length) {
83239
+ const keys = /* @__PURE__ */ new Set();
83240
+ for (const stop2 of stops) for (const k of Object.keys(stop2.properties ?? {})) keys.add(k);
83241
+ if (keys.size > 0) return [...keys];
83242
+ }
83243
+ return ["x", "y"];
83244
+ }
83245
+ function isPlayheadWithinTween(anim, currentTime) {
83246
+ const start = resolveTweenStart(anim);
83247
+ if (start === null) return true;
83248
+ return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
83249
+ }
83250
+ async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
83251
+ const position = readElementPosition(iframe, sel, setAnim);
83252
+ if (Object.keys(position).length === 0) {
83253
+ for (const [key2, held] of Object.entries(setAnim.properties ?? {})) {
83254
+ if (typeof held === "number") position[key2] = held;
83255
+ }
83256
+ }
83257
+ if (Object.keys(position).length === 0 || !session.commitMutation) return;
83258
+ const range = resolveNewTweenRange(sel.dataAttributes?.start, sel.dataAttributes?.duration, t2);
83259
+ await session.commitMutation(
83260
+ {
83261
+ type: "replace-with-keyframes",
83262
+ animationId: setAnim.id,
83263
+ targetSelector: selector,
83264
+ position: roundTo3(range.start),
83265
+ duration: roundTo3(range.duration),
83266
+ keyframes: [{ percentage: 0, properties: position }],
83267
+ ease: setAnim.ease
83268
+ },
83269
+ { label: "Enable keyframes", keyframeAction: "add", softReload: true }
83270
+ );
83271
+ }
83272
+ function readElementPosition(iframe, sel, anim) {
83273
+ const result = {};
83274
+ if (!iframe?.contentWindow) return result;
83275
+ let gsap3;
83276
+ try {
83277
+ gsap3 = iframe.contentWindow.gsap;
83278
+ } catch {
83279
+ return result;
83280
+ }
83281
+ const element = sel.element;
83282
+ if (!element?.isConnected || !gsap3?.getProperty) return result;
83283
+ const props = animatedProps(anim);
83284
+ for (const prop of props) {
83285
+ const val = Number(gsap3.getProperty(element, prop));
83286
+ if (!Number.isFinite(val)) continue;
83287
+ result[prop] = POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
83288
+ }
83289
+ return result;
83290
+ }
83291
+ function resolveNewTweenRange(authoredStart, authoredDuration, currentTime) {
83292
+ const t2 = Math.max(0, roundTo3(currentTime));
83293
+ const start = authoredStart != null ? Number.parseFloat(authoredStart) : Number.NaN;
83294
+ const duration = authoredDuration != null ? Number.parseFloat(authoredDuration) : Number.NaN;
83295
+ if (!Number.isFinite(start) || !Number.isFinite(duration) || duration <= 0) {
83296
+ return { start: t2, duration: 1 };
83297
+ }
83298
+ const end = start + duration;
83299
+ const clampedStart = Math.min(Math.max(t2, start), end);
83300
+ return { start: clampedStart, duration: Math.max(0.5, roundTo3(end - clampedStart)) };
83301
+ }
83302
+ function singleKeyTweenMutation(sel, properties, currentTime) {
83303
+ const targetSelector = writeTargetSelector(sel);
83304
+ if (!targetSelector) return null;
83305
+ const { start, duration } = resolveNewTweenRange(
83306
+ sel.dataAttributes?.start,
83307
+ sel.dataAttributes?.duration,
83308
+ currentTime
83309
+ );
83310
+ return {
83311
+ type: "add-with-keyframes",
83312
+ targetSelector,
83313
+ position: roundTo3(start),
83314
+ duration: roundTo3(duration),
83315
+ keyframes: [{ percentage: 0, properties }]
83316
+ };
83317
+ }
83318
+ async function tryFetchAnimationsForElement(sel) {
83319
+ const projectId = window.location.hash.match(/project\/([^?/]+)/)?.[1];
83320
+ if (!projectId) return null;
83321
+ const sourceFile = sel.sourceFile || "index.html";
83322
+ const parsed = await fetchParsedAnimations(projectId, sourceFile);
83323
+ if (!parsed) return null;
83324
+ return getAnimationsForElement(parsed.animations, {
83325
+ id: sel.id,
83326
+ selector: sel.selector
83327
+ });
83328
+ }
83329
+ async function fetchAnimationsForElement(sel) {
83330
+ return await tryFetchAnimationsForElement(sel) ?? [];
83331
+ }
83332
+ async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, duration, iframe, commitOverrides) {
83333
+ const selector = existingTweenTargetSelector(anim, sel);
83334
+ const position = readElementPosition(iframe, sel, anim);
83335
+ if (!selector || Object.keys(position).length === 0 || !session.commitMutation) return;
83336
+ const extended = buildExtendedKeyframes(anim, currentTime, position, duration);
83337
+ await session.commitMutation(
83338
+ {
83339
+ type: "replace-with-keyframes",
83340
+ animationId: anim.id,
83341
+ targetSelector: selector,
83342
+ position: extended.position,
83343
+ duration: extended.duration,
83344
+ keyframes: extended.keyframes,
83345
+ ...keyframeEases(anim)
83346
+ },
83347
+ {
83348
+ label: "Add keyframe",
83349
+ keyframeAction: "add",
83350
+ softReload: true,
83351
+ ...commitOverrides
83352
+ }
83353
+ );
83354
+ }
83355
+ async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
83356
+ const keepAuthoredDuration = kfAnim.durationUnresolved === true;
83357
+ if (keepAuthoredDuration) {
83358
+ kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
83359
+ }
83360
+ if (keepAuthoredDuration && kfAnim.duration == null) return;
83361
+ const duration = resolveTweenDuration(kfAnim);
83362
+ const start = resolveTweenStart(kfAnim);
83363
+ if (start !== null && !isTimeWithinTween(t2, start, duration)) {
83364
+ if (keepAuthoredDuration) return;
83365
+ await extendKeyframedTweenToPlayhead(
83366
+ session,
83367
+ sel,
83368
+ kfAnim,
83369
+ t2,
83370
+ duration,
83371
+ iframe,
83372
+ commitOverrides
83373
+ );
83374
+ return;
83375
+ }
83376
+ const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
83377
+ const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
83378
+ if (existing) {
83379
+ session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
83380
+ return;
83381
+ }
83382
+ if (session.handleGsapAddKeyframeBatch) {
83383
+ const position = readElementPosition(iframe, sel, kfAnim);
83384
+ if (Object.keys(position).length > 0) {
83385
+ await session.handleGsapAddKeyframeBatch(kfAnim.id, pct, position, commitOverrides);
83386
+ }
83387
+ }
83388
+ }
83389
+ async function promoteSetToKeyframes(session, sel, setAnim, t2, iframe) {
83390
+ const selector = existingTweenTargetSelector(setAnim, sel);
83391
+ const setStart = resolveTweenStart(setAnim) ?? 0;
83392
+ if (!selector || !session.commitMutation) return;
83393
+ if (t2 <= setStart) {
83394
+ await replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector);
83395
+ return;
83396
+ }
83397
+ const endPosition = readElementPosition(iframe, sel, setAnim);
83398
+ if (Object.keys(endPosition).length === 0) return;
83399
+ const startPosition = {};
83400
+ for (const key2 of Object.keys(endPosition)) {
83401
+ const held = setAnim.properties?.[key2];
83402
+ if (typeof held === "number") startPosition[key2] = held;
83403
+ }
83404
+ await session.commitMutation(
83405
+ {
83406
+ type: "replace-with-keyframes",
83407
+ animationId: setAnim.id,
83408
+ targetSelector: selector,
83409
+ position: roundTo3(setStart),
83410
+ duration: roundTo3(t2 - setStart),
83411
+ keyframes: [
83412
+ {
83413
+ percentage: 0,
83414
+ properties: Object.keys(startPosition).length > 0 ? startPosition : endPosition,
83415
+ auto: true
83416
+ },
83417
+ { percentage: 100, properties: endPosition }
83418
+ ],
83419
+ ease: setAnim.ease
83420
+ },
83421
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83422
+ );
83423
+ }
83424
+ async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
83425
+ if (!session.commitMutation) return;
83426
+ const targetSelector = existingTweenTargetSelector(arcAnim, sel);
83427
+ if (!targetSelector) return;
83428
+ const start = resolveTweenStart(arcAnim) ?? 0;
83429
+ const duration = resolveTweenDuration(arcAnim);
83430
+ if (!isTimeWithinTween(t2, start, duration)) {
83431
+ const held = readElementPosition(iframe, sel, arcAnim);
83432
+ if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
83433
+ const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
83434
+ await session.commitMutation(
83435
+ {
83436
+ type: "replace-with-keyframes",
83437
+ animationId: arcAnim.id,
83438
+ targetSelector,
83439
+ position: extended.position,
83440
+ duration: extended.duration,
83441
+ keyframes: extended.keyframes,
83442
+ ease: "none"
83443
+ },
83444
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83445
+ );
83446
+ return;
83447
+ }
83448
+ const nodes = linearArcKeyframes(arcAnim);
83449
+ const playheadPercentage = absoluteToPercentage(t2, start, duration);
83450
+ const timedNodeIndex = nodes.findIndex(
83451
+ (node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
83452
+ );
83453
+ if (timedNodeIndex !== -1) {
83454
+ if (timedNodeIndex > 0 && timedNodeIndex < nodes.length - 1) {
83455
+ await session.commitMutation(
83456
+ {
83457
+ type: "replace-with-keyframes",
83458
+ animationId: arcAnim.id,
83459
+ targetSelector,
83460
+ position: roundTo3(start),
83461
+ duration: roundTo3(duration),
83462
+ keyframes: nodes.filter((_, index) => index !== timedNodeIndex),
83463
+ ease: "none"
83464
+ },
83465
+ { label: "Remove keyframe", softReload: true }
83466
+ );
83467
+ }
83468
+ return;
83469
+ }
83470
+ const live = readElementPosition(iframe, sel, arcAnim);
83471
+ if (typeof live.x !== "number" || typeof live.y !== "number") return;
83472
+ await session.commitMutation(
83473
+ {
83474
+ type: "replace-with-keyframes",
83475
+ animationId: arcAnim.id,
83476
+ targetSelector,
83477
+ position: roundTo3(start),
83478
+ duration: roundTo3(duration),
83479
+ keyframes: buildTemporalArcKeyframes(arcAnim, playheadPercentage, {
83480
+ x: live.x,
83481
+ y: live.y
83482
+ }),
83483
+ ease: "none"
83484
+ },
83485
+ { label: "Add keyframe", keyframeAction: "add", softReload: true }
83486
+ );
83487
+ }
83488
+ function useEnableKeyframes(sessionRef) {
83489
+ return useCallback145(async () => {
83490
+ const session = sessionRef.current;
83491
+ if (!session) return;
83492
+ const sel = session.domEditSelection;
83493
+ if (!sel) return;
83494
+ const t2 = usePlayerStore.getState().currentTime;
83495
+ const iframe = session.previewIframeRef?.current ?? null;
83496
+ const fetched = await tryFetchAnimationsForElement(sel);
83497
+ const anims = fetched ?? session.selectedGsapAnimations;
83498
+ const arcAnim = anims.find((a) => a.arcPath);
83499
+ const kfAnim = anims.find((a) => a.keyframes && !a.arcPath);
83500
+ const setAnim = anims.find((a) => isInstantHold(a) && !a.keyframes && !a.arcPath);
83501
+ const flatAnim = anims.find((a) => !a.keyframes && !a.arcPath && !isInstantHold(a));
83502
+ if (arcAnim) {
83503
+ await applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe);
83504
+ } else if (kfAnim) {
83505
+ await applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe);
83506
+ } else if (setAnim) {
83507
+ await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
83508
+ } else if (flatAnim) {
83509
+ const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
83510
+ enableKeyframesTransactionCounter += 1;
83511
+ const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
83512
+ const convertCommitOverrides = {
83513
+ keyframeTelemetry: false,
83514
+ skipReload: true,
83515
+ coalesceKey,
83516
+ coalesceMs: Number.POSITIVE_INFINITY
83517
+ };
83518
+ await session.handleGsapConvertToKeyframes(
83519
+ flatAnim.id,
83520
+ void 0,
83521
+ void 0,
83522
+ convertCommitOverrides
83523
+ );
83524
+ const converted = (await fetchAnimationsForElement(sel)).find((a) => a.keyframes);
83525
+ if (converted) {
83526
+ const applyCommitOverrides = {
83527
+ softReload: true,
83528
+ coalesceKey,
83529
+ // Must match the convert phase's window: editHistory keys coalescing
83530
+ // off the incoming entry, so without Infinity here the apply (landing
83531
+ // after two POSTs + a fetch) falls back to the 300ms default and the
83532
+ // conversion splits into two undo entries under real latency.
83533
+ coalesceMs: Number.POSITIVE_INFINITY
83534
+ };
83535
+ await applyKeyframeAtPlayhead(
83536
+ session,
83537
+ sel,
83538
+ converted,
83539
+ t2,
83540
+ iframe,
83541
+ applyCommitOverrides,
83542
+ flatLive
83543
+ );
83544
+ }
83545
+ } else {
83546
+ const position = readElementPosition(iframe, sel, null);
83547
+ if (Object.keys(position).length === 0)
83548
+ Object.assign(position, readTranslatePxLeavingPercent(sel.element));
83549
+ const mutation = singleKeyTweenMutation(sel, position, t2);
83550
+ if (mutation && session.commitMutation) {
83551
+ await session.commitMutation(mutation, { label: "Enable keyframes", softReload: true });
83552
+ } else {
83553
+ session.handleGsapAddAnimation("to");
83554
+ }
83555
+ }
83556
+ }, [sessionRef]);
83557
+ }
83558
+
83199
83559
  // src/hooks/gsapResizeIntercept.ts
83200
83560
  var SIZE_PROPS = /* @__PURE__ */ new Set(["width", "height"]);
83201
83561
  var POSITION_XY = /* @__PURE__ */ new Set(["x", "y"]);
83562
+ var MOVED_OR_SIZED = /* @__PURE__ */ new Set(["x", "y", "xPercent", "yPercent", "width", "height"]);
83202
83563
  function originalBoxSize(el, measuredAttr, inlineProperty) {
83203
83564
  const measured = Number.parseFloat(el?.getAttribute(measuredAttr) ?? "");
83204
83565
  if (Number.isFinite(measured) && measured > 0) return measured;
@@ -83218,7 +83579,8 @@ async function commitSizeAtPlayhead(selection, anim, size, iframe, dragOffset, c
83218
83579
  const anchor2 = moves && animationWritesAnyProperty(anim, POSITION_XY) ? computeDraggedGsapPosition(
83219
83580
  selection.element,
83220
83581
  dragOffset,
83221
- readGsapPositionFromIframe(iframe, selector) ?? { x: 0, y: 0 }
83582
+ readGsapPositionFromIframe(iframe, selector) ?? { x: 0, y: 0 },
83583
+ callbacks.stamp
83222
83584
  ) : null;
83223
83585
  const written = await commitValueAtPlayhead(
83224
83586
  selection,
@@ -83232,11 +83594,28 @@ async function commitSizeAtPlayhead(selection, anim, size, iframe, dragOffset, c
83232
83594
  ...preGestureBoxSize(selection.element),
83233
83595
  ...anchor2 && { x: anchor2.baseGsapX, y: anchor2.baseGsapY }
83234
83596
  },
83235
- ...anchor2 && { beforeReload: () => restoreDragOffset(selection.element) }
83597
+ ...anchor2 && { beforeReload: () => restoreDragOffset(selection.element, callbacks.stamp) }
83236
83598
  }
83237
83599
  );
83238
83600
  return written.status === "persisted" && anchor2 ? { ...written, ownsDragOffset: true } : written;
83239
83601
  }
83602
+ function handOverDraftSize(selection, written, size, draw) {
83603
+ const { width, height } = size;
83604
+ if (written.status !== "persisted" || width == null || height == null) return written;
83605
+ draw(() => {
83606
+ clearStudioBoxSize(selection.element);
83607
+ setElementGsapSize(selection.element, width, height);
83608
+ });
83609
+ return written;
83610
+ }
83611
+ function firstSizeKey(selection, size, resizeGroup, animations) {
83612
+ const { autoKeyframeEnabled, currentTime } = usePlayerStore.getState();
83613
+ const keyframed = animations.some(
83614
+ (a) => a.keyframes && animationWritesAnyProperty(a, MOVED_OR_SIZED)
83615
+ );
83616
+ if (resizeGroup !== "size" || !autoKeyframeEnabled || !keyframed) return null;
83617
+ return singleKeyTweenMutation(selection, size, currentTime);
83618
+ }
83240
83619
  function tweenUsesScaleLonghands(anim) {
83241
83620
  const isLonghand = (name) => name === "scaleX" || name === "scaleY";
83242
83621
  const inKeyframes = (anim?.keyframes?.keyframes ?? []).some(
@@ -83244,7 +83623,7 @@ function tweenUsesScaleLonghands(anim) {
83244
83623
  );
83245
83624
  return inKeyframes || Object.keys(anim?.properties ?? {}).some(isLonghand);
83246
83625
  }
83247
- async function tryGsapResizeIntercept(selection, size, animations, iframe, commitMutation, fetchFallbackAnimations, dragOffset, draw = (run) => run()) {
83626
+ async function tryGsapResizeIntercept(selection, size, animations, iframe, commitMutation, fetchFallbackAnimations, dragOffset, draw = (run) => run(), stamp) {
83248
83627
  const fetchedAnimations = fetchFallbackAnimations ? await fetchFallbackAnimations() : [];
83249
83628
  const outcome = preflightGsapResizeIntercept(selection, animations, iframe, fetchedAnimations);
83250
83629
  if (outcome.status === "blocked") return outcome;
@@ -83269,10 +83648,16 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83269
83648
  size
83270
83649
  });
83271
83650
  if (!anim || isInstantHold(anim)) {
83651
+ const sized = { width: roundToLayoutPx(size.width), height: roundToLayoutPx(size.height) };
83272
83652
  const scriptWritesSize = allKnownAnimations.some(
83273
83653
  (a) => animationWritesAnyProperty(a, SIZE_PROPS)
83274
83654
  );
83275
- if (!scriptWritesSize) return { status: "element-size" };
83655
+ if (!scriptWritesSize) {
83656
+ const firstKey = firstSizeKey(selection, sized, resizeGroup, workingAnimations);
83657
+ if (!firstKey) return { status: "element-size" };
83658
+ await commitMutation(selection, firstKey, { label: "Resize", softReload: true });
83659
+ return handOverDraftSize(selection, { status: "persisted" }, sized, draw);
83660
+ }
83276
83661
  const sel = selectorFromSelection(selection) ?? writeTargetSelector(selection);
83277
83662
  if (!sel) return { status: "blocked", reason: "no-selector" };
83278
83663
  const sizeSet = resizeGroup === "size" ? anim ?? findSizeSetAnimation(workingAnimations, sel, selection.element) : findSizeSetAnimation(workingAnimations, sel, selection.element);
@@ -83284,11 +83669,19 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83284
83669
  );
83285
83670
  if (animatedTween) {
83286
83671
  logResize("intercept-route", { route: "keyframed-size", tweenId: animatedTween.id });
83287
- const sized = { width: roundToLayoutPx(size.width), height: roundToLayoutPx(size.height) };
83288
- return commitSizeAtPlayhead(selection, animatedTween, sized, iframe, dragOffset, {
83289
- commitMutation,
83290
- fetchAnimations: fetchFallbackAnimations
83291
- });
83672
+ const written2 = await commitSizeAtPlayhead(
83673
+ selection,
83674
+ animatedTween,
83675
+ sized,
83676
+ iframe,
83677
+ dragOffset,
83678
+ {
83679
+ commitMutation,
83680
+ fetchAnimations: fetchFallbackAnimations,
83681
+ stamp
83682
+ }
83683
+ );
83684
+ return handOverDraftSize(selection, written2, sized, draw);
83292
83685
  }
83293
83686
  }
83294
83687
  logResize("intercept-route", { route: "static-size-set", hadSizeSet: !!sizeSet, resizeGroup });
@@ -83367,7 +83760,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83367
83760
  const { baseGsapX, baseGsapY } = computeDraggedGsapPosition(
83368
83761
  selection.element,
83369
83762
  { x: 0, y: 0 },
83370
- gsapPos
83763
+ gsapPos,
83764
+ stamp
83371
83765
  );
83372
83766
  const base3 = { x: baseGsapX, y: baseGsapY };
83373
83767
  setElementGsapPosition(draftEl, base3.x, base3.y);
@@ -83408,7 +83802,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83408
83802
  assertGsapEditPersisted(
83409
83803
  await commitGsapPositionFromDrag(selection, positionTween, delta, base2, iframe, {
83410
83804
  commitMutation,
83411
- fetchAnimations: fetchFallbackAnimations
83805
+ fetchAnimations: fetchFallbackAnimations,
83806
+ stamp
83412
83807
  })
83413
83808
  );
83414
83809
  return true;
@@ -83416,7 +83811,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83416
83811
  const existingSet = findExistingPositionWrite(currentAnimations, selector, selection.element);
83417
83812
  await commitStaticGsapPosition(selection, delta, base2, selector, existingSet, {
83418
83813
  commitMutation,
83419
- fetchAnimations: fetchFallbackAnimations
83814
+ fetchAnimations: fetchFallbackAnimations,
83815
+ stamp
83420
83816
  });
83421
83817
  return true;
83422
83818
  };
@@ -83433,9 +83829,18 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83433
83829
  );
83434
83830
  return { status: "persisted", ownsDragOffset: await finalizeScaleResizeCommit() };
83435
83831
  }
83436
- const callbacks = { commitMutation, fetchAnimations: fetchFallbackAnimations };
83437
- if (resizeGroup === "size")
83438
- return commitSizeAtPlayhead(selection, anim, resizeProps, iframe, dragOffset, callbacks);
83832
+ const callbacks = { commitMutation, fetchAnimations: fetchFallbackAnimations, stamp };
83833
+ if (resizeGroup === "size") {
83834
+ const written2 = await commitSizeAtPlayhead(
83835
+ selection,
83836
+ anim,
83837
+ resizeProps,
83838
+ iframe,
83839
+ dragOffset,
83840
+ callbacks
83841
+ );
83842
+ return handOverDraftSize(selection, written2, resizeProps, draw);
83843
+ }
83439
83844
  const written = await commitValueAtPlayhead(selection, anim, resizeProps, iframe, callbacks, {
83440
83845
  label: "Resize",
83441
83846
  backfill: resizeBackfill
@@ -83445,7 +83850,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
83445
83850
  }
83446
83851
 
83447
83852
  // src/hooks/useAnimatedPropertyCommit.ts
83448
- import { useCallback as useCallback145 } from "react";
83853
+ import { useCallback as useCallback146 } from "react";
83449
83854
  import { classifyPropertyGroup as classifyPropertyGroup4 } from "@hyperframes/core/gsap-parser";
83450
83855
  function pickBestAnimation(animations, selector, property) {
83451
83856
  const targetGroup = property ? classifyPropertyGroup4(property) : void 0;
@@ -83683,7 +84088,7 @@ async function commitKeyframeProps(selection, anim, props, propEntries, primaryP
83683
84088
  }
83684
84089
  function useAnimatedPropertyCommit(deps) {
83685
84090
  const { selectedGsapAnimations, gsapCommitMutation, previewIframeRef, bumpGsapCache } = deps;
83686
- const commitAnimatedProperties = useCallback145(
84091
+ const commitAnimatedProperties = useCallback146(
83687
84092
  // This is the single routing boundary for set, keyframe, whole-tween, and first-group writes.
83688
84093
  // fallow-ignore-next-line complexity
83689
84094
  async (selection, props, commit2 = gsapCommitMutation) => {
@@ -83789,7 +84194,7 @@ function useAnimatedPropertyCommit(deps) {
83789
84194
  },
83790
84195
  [selectedGsapAnimations, gsapCommitMutation, previewIframeRef, bumpGsapCache]
83791
84196
  );
83792
- const commitAnimatedProperty = useCallback145(
84197
+ const commitAnimatedProperty = useCallback146(
83793
84198
  (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
83794
84199
  [commitAnimatedProperties]
83795
84200
  );
@@ -83816,14 +84221,14 @@ function useGsapAwareEditing({
83816
84221
  setArcPath,
83817
84222
  updateArcSegment
83818
84223
  }) {
83819
- const getGsapAnimationsForSelection = useCallback146(
84224
+ const getGsapAnimationsForSelection = useCallback147(
83820
84225
  (selection) => {
83821
84226
  if (domEditSelection?.element === selection.element) return selectedGsapAnimations;
83822
84227
  return makeFetchFallback(selection, { failOnFetchError: true })();
83823
84228
  },
83824
84229
  [domEditSelection, selectedGsapAnimations, makeFetchFallback]
83825
84230
  );
83826
- const handleGsapAwarePathOffsetCommit = useCallback146(
84231
+ const handleGsapAwarePathOffsetCommit = useCallback147(
83827
84232
  async (selection, next, modifiers) => {
83828
84233
  const writes = observeGsapGesture(gsapCommitMutation);
83829
84234
  if (modifiers?.plainTranslate ?? editsPlainCss(selection.element, "move")) {
@@ -83874,8 +84279,9 @@ function useGsapAwareEditing({
83874
84279
  stageElementPositionOffset,
83875
84280
  showToast
83876
84281
  });
83877
- const handleGsapAwareBoxSizeCommit = useCallback146(
84282
+ const handleGsapAwareBoxSizeCommit = useCallback147(
83878
84283
  async (selection, next, offset, restore2 = () => void 0, route) => {
84284
+ const stamp = freezeDragStamp(selection.element);
83879
84285
  const writes = observeGsapGesture(gsapCommitMutation);
83880
84286
  if (route?.plainTranslate ?? editsPlainCss(selection.element, "resize")) {
83881
84287
  const result = await handleDomBoxSizeCommit(selection, next, offset, restore2);
@@ -83916,7 +84322,8 @@ function useGsapAwareEditing({
83916
84322
  targetAnimations,
83917
84323
  previewIframeRef.current,
83918
84324
  commitMutation2,
83919
- makeFetchFallback(selection)
84325
+ makeFetchFallback(selection),
84326
+ { stamp }
83920
84327
  );
83921
84328
  await saveMove(dragOutcome, async () => {
83922
84329
  const plainAfterSettle = editsPlainCss(selection.element, "move");
@@ -83937,7 +84344,12 @@ function useGsapAwareEditing({
83937
84344
  x: 0,
83938
84345
  y: 0
83939
84346
  };
83940
- const { newX, newY } = computeDraggedGsapPosition(selection.element, offset, gsapPos);
84347
+ const { newX, newY } = computeDraggedGsapPosition(
84348
+ selection.element,
84349
+ offset,
84350
+ gsapPos,
84351
+ stamp
84352
+ );
83941
84353
  logResize("sync-settle", { gsapPos, offset, newX, newY });
83942
84354
  setElementGsapPosition(selection.element, newX, newY);
83943
84355
  });
@@ -83954,7 +84366,8 @@ function useGsapAwareEditing({
83954
84366
  commitMutation2,
83955
84367
  makeFetchFallback(selection),
83956
84368
  offset,
83957
- writes.drawKeepingUndone
84369
+ writes.drawKeepingUndone,
84370
+ stamp
83958
84371
  );
83959
84372
  assertGsapEditPersisted(outcome);
83960
84373
  if (outcome.status === "element-size") {
@@ -84008,7 +84421,7 @@ function useGsapAwareEditing({
84008
84421
  getGsapAnimationsForSelection
84009
84422
  ]
84010
84423
  );
84011
- const handleGsapAwareRotationCommit = useCallback146(
84424
+ const handleGsapAwareRotationCommit = useCallback147(
84012
84425
  async (selection, next) => {
84013
84426
  const writes = observeGsapGesture(gsapCommitMutation);
84014
84427
  if (next.plain === void 0 ? editsPlainCss(selection.element, "rotate") : next.plain) {
@@ -84051,7 +84464,7 @@ function useGsapAwareEditing({
84051
84464
  previewIframeRef,
84052
84465
  bumpGsapCache
84053
84466
  });
84054
- const commitAnimatedProperties = useCallback146(
84467
+ const commitAnimatedProperties = useCallback147(
84055
84468
  async (selection, properties) => {
84056
84469
  try {
84057
84470
  await writeSizeWithCrop(
@@ -84073,25 +84486,25 @@ function useGsapAwareEditing({
84073
84486
  trackGsapInteractionFailure
84074
84487
  ]
84075
84488
  );
84076
- const commitAnimatedProperty = useCallback146(
84489
+ const commitAnimatedProperty = useCallback147(
84077
84490
  (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
84078
84491
  [commitAnimatedProperties]
84079
84492
  );
84080
- const handleSetArcPath = useCallback146(
84493
+ const handleSetArcPath = useCallback147(
84081
84494
  (animId, config) => {
84082
84495
  if (!domEditSelection) return;
84083
84496
  setArcPath(domEditSelection, animId, config);
84084
84497
  },
84085
84498
  [domEditSelection, setArcPath]
84086
84499
  );
84087
- const handleUpdateArcSegment = useCallback146(
84500
+ const handleUpdateArcSegment = useCallback147(
84088
84501
  (animId, segmentIndex, update) => {
84089
84502
  if (!domEditSelection) return;
84090
84503
  updateArcSegment(domEditSelection, animId, segmentIndex, update);
84091
84504
  },
84092
84505
  [domEditSelection, updateArcSegment]
84093
84506
  );
84094
- const noopCommit = useCallback146(async () => {
84507
+ const noopCommit = useCallback147(async () => {
84095
84508
  }, []);
84096
84509
  const trackGsapSaveFailure = useGsapSaveFailureTelemetry(null);
84097
84510
  const safeGsapCommit = useSafeGsapCommitMutation(
@@ -84099,14 +84512,14 @@ function useGsapAwareEditing({
84099
84512
  trackGsapSaveFailure,
84100
84513
  showToast
84101
84514
  );
84102
- const commitMutation = useCallback146(
84515
+ const commitMutation = useCallback147(
84103
84516
  async (mutation, options, selection = domEditSelection) => {
84104
84517
  if (!selection) return;
84105
84518
  await safeGsapCommit(selection, mutation, options);
84106
84519
  },
84107
84520
  [domEditSelection, safeGsapCommit]
84108
84521
  );
84109
- const handleUnroll = useCallback146(() => {
84522
+ const handleUnroll = useCallback147(() => {
84110
84523
  void commitMutation(
84111
84524
  { type: "unroll-timeline" },
84112
84525
  { label: "Unroll to literal tweens", softReload: true }
@@ -84257,12 +84670,12 @@ function useStudioSelectionPublisher({
84257
84670
  }
84258
84671
 
84259
84672
  // src/hooks/useKeyframeEaseCommits.ts
84260
- import { useCallback as useCallback147 } from "react";
84673
+ import { useCallback as useCallback148 } from "react";
84261
84674
  function useKeyframeEaseCommits({
84262
84675
  gsapCommitMutation,
84263
84676
  domEditSelectionRef
84264
84677
  }) {
84265
- const handleUpdateSegmentEase = useCallback147(
84678
+ const handleUpdateSegmentEase = useCallback148(
84266
84679
  (targets, ease) => {
84267
84680
  const selection = domEditSelectionRef.current;
84268
84681
  if (!selection || targets.length === 0) return;
@@ -84296,11 +84709,11 @@ function useKeyframeEaseCommits({
84296
84709
  },
84297
84710
  [gsapCommitMutation, domEditSelectionRef]
84298
84711
  );
84299
- const handleUpdateKeyframeEase = useCallback147(
84712
+ const handleUpdateKeyframeEase = useCallback148(
84300
84713
  (animationId, percentage, ease) => handleUpdateSegmentEase([{ animationId, tweenPercentage: percentage }], ease),
84301
84714
  [handleUpdateSegmentEase]
84302
84715
  );
84303
- const handleSetAllKeyframeEases = useCallback147(
84716
+ const handleSetAllKeyframeEases = useCallback148(
84304
84717
  (animationId, ease) => {
84305
84718
  const sel = domEditSelectionRef.current;
84306
84719
  if (!sel) return;
@@ -84736,7 +85149,7 @@ function useDomEditSession({
84736
85149
  forceReloadSdkSession,
84737
85150
  timelineElements
84738
85151
  });
84739
- const handleDomEditElementDelete = useCallback148(
85152
+ const handleDomEditElementDelete = useCallback149(
84740
85153
  async (selection, options) => {
84741
85154
  if (isRecordingRef?.current) {
84742
85155
  showToast("Cannot edit timeline while recording", "error");
@@ -84759,7 +85172,7 @@ function useDomEditSession({
84759
85172
  timelineElements
84760
85173
  ]
84761
85174
  );
84762
- const handleGroupSelection = useCallback148(() => {
85175
+ const handleGroupSelection = useCallback149(() => {
84763
85176
  const group = domEditGroupSelectionsRef.current;
84764
85177
  const single = domEditSelectionRef.current;
84765
85178
  const members = group.length > 0 ? group : single ? [single] : [];
@@ -84777,7 +85190,7 @@ function useDomEditSession({
84777
85190
  trackStudioEvent("group", { action: "create", count: members.length });
84778
85191
  void groupSelection(members);
84779
85192
  }, [domEditGroupSelectionsRef, domEditSelectionRef, groupSelection, showToast]);
84780
- const handleUngroupSelection = useCallback148(() => {
85193
+ const handleUngroupSelection = useCallback149(() => {
84781
85194
  const sel = domEditSelectionRef.current;
84782
85195
  if (!sel?.element.hasAttribute("data-hf-group")) {
84783
85196
  showToast("Select a group to ungroup", "info");
@@ -84885,7 +85298,7 @@ function useDomEditSession({
84885
85298
  setArcPath,
84886
85299
  updateArcSegment
84887
85300
  });
84888
- const handleDomGroupMoveBy = useCallback148(
85301
+ const handleDomGroupMoveBy = useCallback149(
84889
85302
  (moves) => moveDomGroupBy(
84890
85303
  moves,
84891
85304
  (updates) => handleGsapAwareGroupPathOffsetCommit(updates, { refusalToast: false })
@@ -85011,7 +85424,7 @@ function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections)
85011
85424
  import { useEffect as useEffect126 } from "react";
85012
85425
 
85013
85426
  // src/hooks/useSdkSession.ts
85014
- import { useState as useState121, useEffect as useEffect125, useCallback as useCallback149, useRef as useRef154 } from "react";
85427
+ import { useState as useState121, useEffect as useEffect125, useCallback as useCallback150, useRef as useRef154 } from "react";
85015
85428
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
85016
85429
 
85017
85430
  // src/hooks/externalFileReloadBus.ts
@@ -85225,8 +85638,8 @@ function useSdkSession(projectId, activeCompPath, fileTree = [], fileTreeLoaded
85225
85638
  }
85226
85639
  };
85227
85640
  }, [projectId, activeCompPath, reloadToken]);
85228
- const forceReload = useCallback149(() => setReloadToken((t2) => t2 + 1), []);
85229
- const publish = useCallback149(({ candidate, expectedSession, targetPath }) => {
85641
+ const forceReload = useCallback150(() => setReloadToken((t2) => t2 + 1), []);
85642
+ const publish = useCallback150(({ candidate, expectedSession, targetPath }) => {
85230
85643
  const expectedOwner = sessionOwnersRef.current.get(expectedSession);
85231
85644
  const current = ownedSessionRef.current;
85232
85645
  if (!isSessionOwnerActive(
@@ -85269,7 +85682,7 @@ function useStudioSdkSessions(projectId, activeCompPath, masterCompPath, fileTre
85269
85682
  }
85270
85683
 
85271
85684
  // src/hooks/useStudioExternalFileChanges.ts
85272
- import { useCallback as useCallback151 } from "react";
85685
+ import { useCallback as useCallback152 } from "react";
85273
85686
 
85274
85687
  // src/utils/externalConflictStorage.ts
85275
85688
  var DB_NAME = "hyperframes-studio-external-conflicts";
@@ -85370,7 +85783,7 @@ async function deleteExternalConflictSnapshot(projectId, filePath) {
85370
85783
  }
85371
85784
 
85372
85785
  // src/hooks/useExternalFileChangeCoordinator.ts
85373
- import { useCallback as useCallback150, useEffect as useEffect127, useRef as useRef155, useState as useState122 } from "react";
85786
+ import { useCallback as useCallback151, useEffect as useEffect127, useRef as useRef155, useState as useState122 } from "react";
85374
85787
  function testHotAdapter() {
85375
85788
  const value = globalThis.__HF_STUDIO_HOT_TEST_ADAPTER__;
85376
85789
  if (!value || typeof value !== "object") return null;
@@ -85431,7 +85844,7 @@ function useExternalFileChangeCoordinator({
85431
85844
  const drainingRef = useRef155(false);
85432
85845
  const pendingPayloadRef = useRef155(null);
85433
85846
  blockedRef.current = blocked;
85434
- const setBlocked = useCallback150((next) => {
85847
+ const setBlocked = useCallback151((next) => {
85435
85848
  setBlockedState(
85436
85849
  (held) => next && held ? { ...next, payload: mergeFileChangeAffectedCompositions(held.payload, next.payload) } : next
85437
85850
  );
@@ -85486,7 +85899,7 @@ function useExternalFileChangeCoordinator({
85486
85899
  cancelled = true;
85487
85900
  };
85488
85901
  }, [loadConflictSnapshot, projectId, recoveryFilePath, setBlocked]);
85489
- const reloadAcceptedGeneration = useCallback150(
85902
+ const reloadAcceptedGeneration = useCallback151(
85490
85903
  (path, affectsPreview = true) => {
85491
85904
  logReload(affectsPreview ? "reload" : "file-tree only", {
85492
85905
  path,
@@ -85498,7 +85911,7 @@ function useExternalFileChangeCoordinator({
85498
85911
  },
85499
85912
  [reloadPreview, reloadSdkSession, refreshFileTree]
85500
85913
  );
85501
- const persistSnapshotInOrder = useCallback150(async (write) => {
85914
+ const persistSnapshotInOrder = useCallback151(async (write) => {
85502
85915
  const next = snapshotWriteTailRef.current.catch(() => void 0).then(write);
85503
85916
  snapshotWriteTailRef.current = next.then(
85504
85917
  () => void 0,
@@ -85506,7 +85919,7 @@ function useExternalFileChangeCoordinator({
85506
85919
  );
85507
85920
  await next;
85508
85921
  }, []);
85509
- const drainOnePending = useCallback150(
85922
+ const drainOnePending = useCallback151(
85510
85923
  // fallow-ignore-next-line complexity
85511
85924
  async (payload) => {
85512
85925
  const path = readStudioFileChangePath(payload);
@@ -85605,7 +86018,7 @@ function useExternalFileChangeCoordinator({
85605
86018
  onAcceptedPersistedFileChange
85606
86019
  ]
85607
86020
  );
85608
- const startDrainLoop = useCallback150(async () => {
86021
+ const startDrainLoop = useCallback151(async () => {
85609
86022
  if (drainingRef.current) return;
85610
86023
  drainingRef.current = true;
85611
86024
  try {
@@ -85619,7 +86032,7 @@ function useExternalFileChangeCoordinator({
85619
86032
  drainingRef.current = false;
85620
86033
  }
85621
86034
  }, [drainOnePending]);
85622
- const processChange = useCallback150(
86035
+ const processChange = useCallback151(
85623
86036
  // fallow-ignore-next-line complexity
85624
86037
  (payload) => {
85625
86038
  const path = readStudioFileChangePath(payload);
@@ -85678,14 +86091,14 @@ function useExternalFileChangeCoordinator({
85678
86091
  }
85679
86092
  return sseFileChangeChannel(handler);
85680
86093
  }, []);
85681
- const retry = useCallback150(async () => {
86094
+ const retry = useCallback151(async () => {
85682
86095
  const current = blockedRef.current;
85683
86096
  if (!current || current.status === "conflict" || current.recovered) return;
85684
86097
  resetSaveQueues?.();
85685
86098
  lastEventIdentityRef.current = null;
85686
86099
  processChange(current.payload);
85687
86100
  }, [processChange, resetSaveQueues]);
85688
- const useExternalFile = useCallback150(
86101
+ const useExternalFile = useCallback151(
85689
86102
  // fallow-ignore-next-line complexity
85690
86103
  async () => {
85691
86104
  const current = blockedRef.current;
@@ -85713,7 +86126,7 @@ function useExternalFileChangeCoordinator({
85713
86126
  resetSaveQueues
85714
86127
  ]
85715
86128
  );
85716
- const keepStudioFile = useCallback150(async () => {
86129
+ const keepStudioFile = useCallback151(async () => {
85717
86130
  const current = blockedRef.current;
85718
86131
  if (!current || !projectId) return;
85719
86132
  if (current.generation !== generationRef.current) return;
@@ -85784,19 +86197,19 @@ function useStudioExternalFileChanges({
85784
86197
  const { flushPendingSourceSave, discardPendingSourceSave } = fileManager;
85785
86198
  const { drainPendingDomEditSaves, resetDomEditSaveQueueBreaker } = previewPersistence;
85786
86199
  const bumpThumbnailRevisions = usePlayerStore((state) => state.bumpThumbnailRevisions);
85787
- const onAcceptedPersistedFileChange = useCallback151(
86200
+ const onAcceptedPersistedFileChange = useCallback152(
85788
86201
  (_path, affectedCompositions) => {
85789
86202
  onOutsideChange?.();
85790
86203
  bumpThumbnailRevisions(affectedCompositions);
85791
86204
  },
85792
86205
  [bumpThumbnailRevisions, onOutsideChange]
85793
86206
  );
85794
- const drainPendingChanges = useCallback151(async () => {
86207
+ const drainPendingChanges = useCallback152(async () => {
85795
86208
  const source = await flushPendingSourceSave();
85796
86209
  if (source.status !== "clean") return source;
85797
86210
  return drainPendingDomEditSaves();
85798
86211
  }, [drainPendingDomEditSaves, flushPendingSourceSave]);
85799
- const discardPendingChanges = useCallback151(() => {
86212
+ const discardPendingChanges = useCallback152(() => {
85800
86213
  discardPendingSourceSave();
85801
86214
  resetDomEditSaveQueueBreaker();
85802
86215
  }, [discardPendingSourceSave, resetDomEditSaveQueueBreaker]);
@@ -85824,7 +86237,7 @@ function useStudioExternalFileChanges({
85824
86237
  }
85825
86238
 
85826
86239
  // src/hooks/useBlockHandlers.ts
85827
- import { useCallback as useCallback152, useMemo as useMemo75, useRef as useRef156, useState as useState123 } from "react";
86240
+ import { useCallback as useCallback153, useMemo as useMemo75, useRef as useRef156, useState as useState123 } from "react";
85828
86241
 
85829
86242
  // src/utils/blockInstaller.ts
85830
86243
  function getMaxZIndexFromIframe(iframe) {
@@ -86043,7 +86456,7 @@ function useBlockHandlers({
86043
86456
  );
86044
86457
  const installingBlockRef = useRef156(false);
86045
86458
  const { showToast, dismissToast } = blockCtxDeps;
86046
- const runBlockInstall = useCallback152(
86459
+ const runBlockInstall = useCallback153(
86047
86460
  async (blockName, install) => {
86048
86461
  if (installingBlockRef.current) {
86049
86462
  showToast("A block is already installing \u2014 one moment\u2026", "info");
@@ -86060,7 +86473,7 @@ function useBlockHandlers({
86060
86473
  },
86061
86474
  [showToast, dismissToast]
86062
86475
  );
86063
- const handleAddBlock = useCallback152(
86476
+ const handleAddBlock = useCallback153(
86064
86477
  (blockName) => {
86065
86478
  if (!projectId) return;
86066
86479
  void (async () => {
@@ -86090,7 +86503,7 @@ function useBlockHandlers({
86090
86503
  },
86091
86504
  [projectId, blockCtx, previewIframeRef, runBlockInstall, setRightCollapsed, setRightPanelTab]
86092
86505
  );
86093
- const handleTimelineBlockDrop = useCallback152(
86506
+ const handleTimelineBlockDrop = useCallback153(
86094
86507
  (blockName, placement) => {
86095
86508
  if (!projectId) return;
86096
86509
  void runBlockInstall(
@@ -86109,7 +86522,7 @@ function useBlockHandlers({
86109
86522
  },
86110
86523
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
86111
86524
  );
86112
- const handleAddMediaOverlay = useCallback152(
86525
+ const handleAddMediaOverlay = useCallback153(
86113
86526
  async (blockName, placement) => {
86114
86527
  if (!projectId) return;
86115
86528
  const { compositionPath, ...timelinePlacement } = placement;
@@ -86128,7 +86541,7 @@ function useBlockHandlers({
86128
86541
  },
86129
86542
  [projectId, blockCtx, previewIframeRef, runBlockInstall]
86130
86543
  );
86131
- const handlePreviewBlockDrop = useCallback152(
86544
+ const handlePreviewBlockDrop = useCallback153(
86132
86545
  (blockName, position) => {
86133
86546
  if (!projectId) return;
86134
86547
  void runBlockInstall(
@@ -86158,7 +86571,7 @@ function useBlockHandlers({
86158
86571
  }
86159
86572
 
86160
86573
  // src/hooks/useAppHotkeys.ts
86161
- import { useCallback as useCallback154, useEffect as useEffect128, useRef as useRef157 } from "react";
86574
+ import { useCallback as useCallback155, useEffect as useEffect128, useRef as useRef157 } from "react";
86162
86575
 
86163
86576
  // src/hooks/appHotkeysDispatch.ts
86164
86577
  function handleUndoRedoKey(event, onUndo, onRedo) {
@@ -86355,7 +86768,7 @@ function dispatchPlainKey(event, key2, cb) {
86355
86768
  }
86356
86769
 
86357
86770
  // src/hooks/useEditHistoryActions.ts
86358
- import { useCallback as useCallback153, useMemo as useMemo76 } from "react";
86771
+ import { useCallback as useCallback154, useMemo as useMemo76 } from "react";
86359
86772
  function useEditHistoryActions({
86360
86773
  editHistory,
86361
86774
  readOptionalProjectFile,
@@ -86369,20 +86782,21 @@ function useEditHistoryActions({
86369
86782
  activeCompPath,
86370
86783
  forceReloadSdkSession
86371
86784
  }) {
86372
- const readHistoryFile = useCallback153(
86785
+ const readHistoryFile = useCallback154(
86373
86786
  (path) => path === STUDIO_MOTION_PATH ? readOptionalProjectFile(path) : readProjectFile(path),
86374
86787
  [readOptionalProjectFile, readProjectFile]
86375
86788
  );
86376
- const serializeHistoryFiles = useCallback153(
86789
+ const serializeHistoryFiles = useCallback154(
86377
86790
  (paths, task) => serializeStudioFileMutations(writeProjectFile, paths, task),
86378
86791
  [writeProjectFile]
86379
86792
  );
86380
- const apply = useCallback153(
86793
+ const apply = useCallback154(
86381
86794
  async (direction) => {
86382
86795
  const noun = direction === "undo" ? "Undo" : "Redo";
86383
86796
  const pendingEditShown = direction === "undo" ? paintBackNewestStudioPendingEdit() : null;
86384
86797
  const predicted = pendingEditShown ? null : editHistory.predict?.(direction) ?? null;
86385
86798
  const predictedShown = predicted ? showHistoryRestoreNow?.(predicted.files) ?? null : null;
86799
+ const shownLanded = predictedShown ? beginStudioShownRestore() : null;
86386
86800
  const putBack = predictedShown ?? pendingEditShown?.showAgain;
86387
86801
  const claimedAfter = pendingEditShown?.claimsAtBegin ?? (direction === "undo" && hasStudioPendingEdits() ? editHistory.claims?.() : void 0);
86388
86802
  let result = { ok: false, reason: "failed" };
@@ -86402,6 +86816,7 @@ function useEditHistoryActions({
86402
86816
  serverSteppedShown = predictedShown ? stepped && result.undoes === predicted?.id : stepped && editClaimed;
86403
86817
  } finally {
86404
86818
  if (putBack && !serverSteppedShown && !revertIsTheUndo) putBack();
86819
+ shownLanded?.();
86405
86820
  }
86406
86821
  if (!result.ok && result.reason === "content-mismatch") {
86407
86822
  showToast(
@@ -86438,8 +86853,8 @@ function useEditHistoryActions({
86438
86853
  forceReloadSdkSession
86439
86854
  ]
86440
86855
  );
86441
- const undo = useCallback153(() => apply("undo"), [apply]);
86442
- const redo = useCallback153(() => apply("redo"), [apply]);
86856
+ const undo = useCallback154(() => apply("undo"), [apply]);
86857
+ const redo = useCallback154(() => apply("redo"), [apply]);
86443
86858
  return useMemo76(() => ({ undo, redo }), [undo, redo]);
86444
86859
  }
86445
86860
  function fromShown(files, shown2) {
@@ -86528,7 +86943,7 @@ function useAppHotkeys({
86528
86943
  activeCompPath,
86529
86944
  forceReloadSdkSession
86530
86945
  });
86531
- const applyHistory = useCallback154(
86946
+ const applyHistory = useCallback155(
86532
86947
  async (direction) => {
86533
86948
  const captionState = useCaptionStore.getState();
86534
86949
  if (captionState.isEditMode && isCaptionPreviewVisible()) {
@@ -86544,8 +86959,8 @@ function useAppHotkeys({
86544
86959
  },
86545
86960
  [editHistory.state, fileHistory, showToast]
86546
86961
  );
86547
- const handleUndo = useCallback154(() => applyHistory("undo"), [applyHistory]);
86548
- const handleRedo = useCallback154(() => applyHistory("redo"), [applyHistory]);
86962
+ const handleUndo = useCallback155(() => applyHistory("undo"), [applyHistory]);
86963
+ const handleRedo = useCallback155(() => applyHistory("redo"), [applyHistory]);
86549
86964
  const cbRef = useRef157(null);
86550
86965
  cbRef.current = {
86551
86966
  handleTimelineElementsDelete,
@@ -86568,7 +86983,7 @@ function useAppHotkeys({
86568
86983
  showToast,
86569
86984
  readOnlyPreview
86570
86985
  };
86571
- const handleAppKeyDown = useCallback154((event) => {
86986
+ const handleAppKeyDown = useCallback155((event) => {
86572
86987
  const cb = cbRef.current;
86573
86988
  const key2 = event.key.toLowerCase();
86574
86989
  if (dispatchLinkShortcut(event, cb)) return;
@@ -86582,7 +86997,7 @@ function useAppHotkeys({
86582
86997
  window.addEventListener("keydown", handleAppKeyDown, true);
86583
86998
  return () => window.removeEventListener("keydown", handleAppKeyDown, true);
86584
86999
  }, [handleAppKeyDown]);
86585
- const syncPreviewHotkeys = useCallback154(
87000
+ const syncPreviewHotkeys = useCallback155(
86586
87001
  (iframe) => {
86587
87002
  previewHistoryCleanupRef.current?.();
86588
87003
  previewHistoryCleanupRef.current = null;
@@ -86612,7 +87027,7 @@ function useAppHotkeys({
86612
87027
  }
86613
87028
 
86614
87029
  // src/hooks/useClipboard.ts
86615
- import { useCallback as useCallback155, useRef as useRef158 } from "react";
87030
+ import { useCallback as useCallback156, useRef as useRef158 } from "react";
86616
87031
  import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants as walkCompositionDescendants2 } from "@hyperframes/parsers/hf-ids";
86617
87032
 
86618
87033
  // src/utils/clipboardPayload.ts
@@ -86892,7 +87307,7 @@ function useClipboard({
86892
87307
  const clipboardRef = useRef158(null);
86893
87308
  const projectIdRef = useRef158(projectId);
86894
87309
  projectIdRef.current = projectId;
86895
- const readSaved = useCallback155(
87310
+ const readSaved = useCallback156(
86896
87311
  async (path) => {
86897
87312
  const pid = projectIdRef.current;
86898
87313
  if (!pid) throw new Error("No project is open.");
@@ -86906,7 +87321,7 @@ function useClipboard({
86906
87321
  },
86907
87322
  [waitForPendingDomEditSaves, writeProjectFile]
86908
87323
  );
86909
- const findSelectedClips = useCallback155(() => {
87324
+ const findSelectedClips = useCallback156(() => {
86910
87325
  const selected = getSelectedElements();
86911
87326
  if (selected.length === 0) return null;
86912
87327
  const lives = [];
@@ -86920,7 +87335,7 @@ function useClipboard({
86920
87335
  }
86921
87336
  return { elements: selected, lives };
86922
87337
  }, [activeCompPath, previewIframeRef, showToast]);
86923
- const readClips = useCallback155(
87338
+ const readClips = useCallback156(
86924
87339
  async (targets) => {
86925
87340
  const paths = targets.elements.map((el) => el.sourceFile || activeCompPath || "index.html");
86926
87341
  const markup = await readSavedMarkup(readSaved, paths, targets.lives);
@@ -86936,7 +87351,7 @@ function useClipboard({
86936
87351
  },
86937
87352
  [activeCompPath, readSaved]
86938
87353
  );
86939
- const copyTimelineSelection = useCallback155(() => {
87354
+ const copyTimelineSelection = useCallback156(() => {
86940
87355
  const targets = findSelectedClips();
86941
87356
  if (!targets) return null;
86942
87357
  const sourceFile = targets.elements[0]?.sourceFile || activeCompPath || "index.html";
@@ -86951,7 +87366,7 @@ function useClipboard({
86951
87366
  };
86952
87367
  });
86953
87368
  }, [activeCompPath, findSelectedClips, readClips, showToast]);
86954
- const copyDomSelection = useCallback155(
87369
+ const copyDomSelection = useCallback156(
86955
87370
  (domSelection) => {
86956
87371
  const live = getSelectedDomElement(previewIframeRef, domSelection, activeCompPath);
86957
87372
  if (!live) {
@@ -86973,7 +87388,7 @@ function useClipboard({
86973
87388
  },
86974
87389
  [activeCompPath, previewIframeRef, readSaved, showToast]
86975
87390
  );
86976
- const copyToClipboard = useCallback155(() => {
87391
+ const copyToClipboard = useCallback156(() => {
86977
87392
  const domSelection = domEditSelectionRef.current;
86978
87393
  let pending2;
86979
87394
  if (usePlayerStore.getState().selectedElementId) pending2 = copyTimelineSelection();
@@ -86995,8 +87410,8 @@ function useClipboard({
86995
87410
  });
86996
87411
  return own;
86997
87412
  }, [copyDomSelection, copyTimelineSelection, domEditSelectionRef, showToast]);
86998
- const handleCopy2 = useCallback155(() => copyToClipboard() !== null, [copyToClipboard]);
86999
- const handlePaste2 = useCallback155(
87413
+ const handleCopy2 = useCallback156(() => copyToClipboard() !== null, [copyToClipboard]);
87414
+ const handlePaste2 = useCallback156(
87000
87415
  async (options) => {
87001
87416
  const payload = await clipboardRef.current;
87002
87417
  if (!payload) {
@@ -87044,7 +87459,7 @@ function useClipboard({
87044
87459
  },
87045
87460
  [activeCompPath, recordEdit, reloadPreview, showToast, writeProjectFile]
87046
87461
  );
87047
- const handleDuplicate = useCallback155(
87462
+ const handleDuplicate = useCallback156(
87048
87463
  async (options) => {
87049
87464
  const targets = findSelectedClips();
87050
87465
  if (!targets) return false;
@@ -87096,7 +87511,7 @@ function useClipboard({
87096
87511
  writeProjectFile
87097
87512
  ]
87098
87513
  );
87099
- const handleCut = useCallback155(async () => {
87514
+ const handleCut = useCallback156(async () => {
87100
87515
  const selected = getSelectedElements();
87101
87516
  const domSelection = domEditSelectionRef.current;
87102
87517
  const copied = copyToClipboard();
@@ -87116,7 +87531,7 @@ function useClipboard({
87116
87531
  handleTimelineElementsDelete,
87117
87532
  handleDomEditElementDelete
87118
87533
  ]);
87119
- const canPaste = useCallback155(() => clipboardRef.current !== null, []);
87534
+ const canPaste = useCallback156(() => clipboardRef.current !== null, []);
87120
87535
  return { handleCopy: handleCopy2, handlePaste: handlePaste2, handleCut, handleDuplicate, canPaste };
87121
87536
  }
87122
87537
 
@@ -87475,11 +87890,11 @@ function useCaptionDetection({
87475
87890
  }
87476
87891
 
87477
87892
  // src/hooks/useConsoleErrorCapture.ts
87478
- import { useCallback as useCallback156, useEffect as useEffect130, useRef as useRef160, useState as useState124 } from "react";
87893
+ import { useCallback as useCallback157, useEffect as useEffect130, useRef as useRef160, useState as useState124 } from "react";
87479
87894
  function useConsoleErrorCapture(previewIframe) {
87480
87895
  const [consoleErrors, setConsoleErrors] = useState124(null);
87481
87896
  const consoleErrorsRef = useRef160([]);
87482
- const resetErrors = useCallback156(() => {
87897
+ const resetErrors = useCallback157(() => {
87483
87898
  consoleErrorsRef.current = [];
87484
87899
  setConsoleErrors(null);
87485
87900
  }, []);
@@ -87548,7 +87963,7 @@ function useConsoleErrorCapture(previewIframe) {
87548
87963
  }
87549
87964
 
87550
87965
  // src/hooks/useFrameCapture.ts
87551
- import { useMemo as useMemo77, useState as useState125, useCallback as useCallback157, useRef as useRef161 } from "react";
87966
+ import { useMemo as useMemo77, useState as useState125, useCallback as useCallback158, useRef as useRef161 } from "react";
87552
87967
  function useFrameCapture({
87553
87968
  projectId,
87554
87969
  activeCompPath,
@@ -87566,16 +87981,16 @@ function useFrameCapture({
87566
87981
  livePlayheadRef.current = time;
87567
87982
  })
87568
87983
  );
87569
- const playheadTime = useCallback157(() => {
87984
+ const playheadTime = useCallback158(() => {
87570
87985
  const { isPlaying, currentTime } = usePlayerStore.getState();
87571
87986
  return isPlaying ? livePlayheadRef.current : currentTime;
87572
87987
  }, []);
87573
87988
  const [captureStamp, setCaptureStamp] = useState125(Date.now);
87574
- const refreshCaptureFrameTime = useCallback157(() => {
87989
+ const refreshCaptureFrameTime = useCallback158(() => {
87575
87990
  setCaptureFrameTime(playheadTime());
87576
87991
  setCaptureStamp(Date.now());
87577
87992
  }, [playheadTime]);
87578
- const handleCaptureFrameClick = useCallback157(
87993
+ const handleCaptureFrameClick = useCallback158(
87579
87994
  async (event) => {
87580
87995
  if (!projectId) return;
87581
87996
  event.preventDefault();
@@ -87659,7 +88074,7 @@ function useFrameCapture({
87659
88074
  }
87660
88075
 
87661
88076
  // src/hooks/useLintModal.ts
87662
- import { useState as useState126, useCallback as useCallback158, useEffect as useEffect131, useRef as useRef162, useMemo as useMemo78 } from "react";
88077
+ import { useState as useState126, useCallback as useCallback159, useEffect as useEffect131, useRef as useRef162, useMemo as useMemo78 } from "react";
87663
88078
  function parseFinding(f) {
87664
88079
  return {
87665
88080
  severity: f.severity === "error" ? "error" : "warning",
@@ -87679,7 +88094,7 @@ function useLintModal(projectId, refreshKey) {
87679
88094
  const [linting, setLinting] = useState126(false);
87680
88095
  const [backgroundFindings, setBackgroundFindings] = useState126([]);
87681
88096
  const autoLintRanRef = useRef162(false);
87682
- const runBackgroundLint = useCallback158(async () => {
88097
+ const runBackgroundLint = useCallback159(async () => {
87683
88098
  if (!projectId) return;
87684
88099
  if (!isPreviewBooted(projectId) && !await whenPreviewBooted(projectId)) return;
87685
88100
  try {
@@ -87687,7 +88102,7 @@ function useLintModal(projectId, refreshKey) {
87687
88102
  } catch {
87688
88103
  }
87689
88104
  }, [projectId]);
87690
- const handleLint = useCallback158(async () => {
88105
+ const handleLint = useCallback159(async () => {
87691
88106
  if (!projectId) return;
87692
88107
  setLinting(true);
87693
88108
  try {
@@ -87716,8 +88131,8 @@ function useLintModal(projectId, refreshKey) {
87716
88131
  const timer = setTimeout(() => void runBackgroundLint(), 1e3);
87717
88132
  return () => clearTimeout(timer);
87718
88133
  }, [projectId, refreshKey, runBackgroundLint]);
87719
- const closeLintModal = useCallback158(() => setLintModal(null), []);
87720
- const groupFindings = useCallback158(
88134
+ const closeLintModal = useCallback159(() => setLintModal(null), []);
88135
+ const groupFindings = useCallback159(
87721
88136
  (keyFn) => {
87722
88137
  const map = /* @__PURE__ */ new Map();
87723
88138
  for (const f of backgroundFindings) {
@@ -87754,7 +88169,7 @@ function useLintModal(projectId, refreshKey) {
87754
88169
  }
87755
88170
 
87756
88171
  // src/hooks/useToast.ts
87757
- import { useState as useState127, useCallback as useCallback159, useRef as useRef163 } from "react";
88172
+ import { useState as useState127, useCallback as useCallback160, useRef as useRef163 } from "react";
87758
88173
  var AUTO_DISMISS_MS2 = 4e3;
87759
88174
  var EXIT_MS2 = 160;
87760
88175
  var MAX_TOASTS = 3;
@@ -87762,21 +88177,21 @@ var nextToastId = 1;
87762
88177
  function useToast() {
87763
88178
  const [toasts, setToasts] = useState127([]);
87764
88179
  const timersRef = useRef163(/* @__PURE__ */ new Map());
87765
- const clearTimer = useCallback159((id) => {
88180
+ const clearTimer = useCallback160((id) => {
87766
88181
  const timer = timersRef.current.get(id);
87767
88182
  if (timer) {
87768
88183
  clearTimeout(timer);
87769
88184
  timersRef.current.delete(id);
87770
88185
  }
87771
88186
  }, []);
87772
- const removeToast = useCallback159(
88187
+ const removeToast = useCallback160(
87773
88188
  (id) => {
87774
88189
  clearTimer(id);
87775
88190
  setToasts((prev) => prev.filter((t2) => t2.id !== id));
87776
88191
  },
87777
88192
  [clearTimer]
87778
88193
  );
87779
- const dismissToast = useCallback159(
88194
+ const dismissToast = useCallback160(
87780
88195
  (id) => {
87781
88196
  clearTimer(id);
87782
88197
  setToasts((prev) => prev.map((t2) => t2.id === id ? { ...t2, leaving: true } : t2));
@@ -87785,7 +88200,7 @@ function useToast() {
87785
88200
  },
87786
88201
  [clearTimer, removeToast]
87787
88202
  );
87788
- const showToast = useCallback159(
88203
+ const showToast = useCallback160(
87789
88204
  (message, tone = "error") => {
87790
88205
  const id = nextToastId++;
87791
88206
  setToasts((prev) => {
@@ -87812,7 +88227,7 @@ function useToast() {
87812
88227
  }
87813
88228
 
87814
88229
  // src/hooks/useStudioUrlState.ts
87815
- import { useCallback as useCallback160, useEffect as useEffect132, useRef as useRef164, useState as useState128 } from "react";
88230
+ import { useCallback as useCallback161, useEffect as useEffect132, useRef as useRef164, useState as useState128 } from "react";
87816
88231
 
87817
88232
  // src/utils/studioUrlState.ts
87818
88233
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
@@ -88051,7 +88466,7 @@ function useStudioUrlState({
88051
88466
  const pendingSelectionRef = useRef164(initialState2.selection);
88052
88467
  const stableTimeRef = useRef164(initialState2.currentTime);
88053
88468
  const selectionApplySeqRef = useRef164(0);
88054
- const buildUrlState = useCallback160(
88469
+ const buildUrlState = useCallback161(
88055
88470
  () => ({
88056
88471
  activeCompPath,
88057
88472
  currentTime: stableTimeRef.current,
@@ -88062,7 +88477,7 @@ function useStudioUrlState({
88062
88477
  }),
88063
88478
  [activeCompPath, domEditGroupSelections, domEditSelection, rightCollapsed, rightPanelTab]
88064
88479
  );
88065
- const applyUrlSelection = useCallback160(
88480
+ const applyUrlSelection = useCallback161(
88066
88481
  (selection) => {
88067
88482
  const applySeq = ++selectionApplySeqRef.current;
88068
88483
  if (!selection) {
@@ -88207,7 +88622,7 @@ function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
88207
88622
  }
88208
88623
 
88209
88624
  // src/hooks/useStudioContextValue.ts
88210
- import { useCallback as useCallback161, useMemo as useMemo80 } from "react";
88625
+ import { useCallback as useCallback162, useMemo as useMemo80 } from "react";
88211
88626
  function buildStudioContextValue(input) {
88212
88627
  return {
88213
88628
  projectId: input.projectId,
@@ -88252,11 +88667,11 @@ function useInspectorState(rightPanel, isPlaying, domEditSelection, isGestureRec
88252
88667
  }, [rightPanel, isPlaying, isGestureRecording, domEditSelection]);
88253
88668
  }
88254
88669
  function useFileDropTarget(onImportFiles) {
88255
- const onDragOver = useCallback161((e) => {
88670
+ const onDragOver = useCallback162((e) => {
88256
88671
  if (!e.dataTransfer.types.includes("Files")) return;
88257
88672
  e.preventDefault();
88258
88673
  }, []);
88259
- const onDrop = useCallback161(
88674
+ const onDrop = useCallback162(
88260
88675
  (e) => {
88261
88676
  if (e.defaultPrevented) return;
88262
88677
  e.preventDefault();
@@ -88267,7 +88682,7 @@ function useFileDropTarget(onImportFiles) {
88267
88682
  return { onDragOver, onDrop };
88268
88683
  }
88269
88684
  function useGlobalFileDrop(handleTimelineFileDrop) {
88270
- const onDrop = useCallback161(
88685
+ const onDrop = useCallback162(
88271
88686
  (files) => {
88272
88687
  const start = usePlayerStore.getState().currentTime;
88273
88688
  void handleTimelineFileDrop(Array.from(files), { start, track: 0 });
@@ -88754,10 +89169,10 @@ var StudioHeader = memo44(function StudioHeader2({
88754
89169
  });
88755
89170
 
88756
89171
  // src/hooks/useGestureCommit.ts
88757
- import { useState as useState131, useCallback as useCallback163, useRef as useRef167, useEffect as useEffect135 } from "react";
89172
+ import { useState as useState131, useCallback as useCallback164, useRef as useRef167, useEffect as useEffect135 } from "react";
88758
89173
 
88759
89174
  // src/hooks/useGestureRecording.ts
88760
- import { useCallback as useCallback162, useEffect as useEffect134, useRef as useRef166, useState as useState130 } from "react";
89175
+ import { useCallback as useCallback163, useEffect as useEffect134, useRef as useRef166, useState as useState130 } from "react";
88761
89176
  function readBasePosition(element, iframeEl) {
88762
89177
  let baseOpacity = 1;
88763
89178
  let baseScale = 1;
@@ -88914,7 +89329,7 @@ function useGestureRecording() {
88914
89329
  r.cleanup = null;
88915
89330
  };
88916
89331
  }, []);
88917
- const startRecording = useCallback162(
89332
+ const startRecording = useCallback163(
88918
89333
  (element, iframeEl, elementEndTime) => {
88919
89334
  if (isRecordingRef.current) return;
88920
89335
  isRecordingRef.current = true;
@@ -89013,7 +89428,7 @@ function useGestureRecording() {
89013
89428
  []
89014
89429
  // No deps — uses refs only for all mutable state
89015
89430
  );
89016
- const stopRecording = useCallback162(() => {
89431
+ const stopRecording = useCallback163(() => {
89017
89432
  if (!isRecordingRef.current) return [];
89018
89433
  isRecordingRef.current = false;
89019
89434
  const r = refs.current;
@@ -89025,7 +89440,7 @@ function useGestureRecording() {
89025
89440
  setIsRecording(false);
89026
89441
  return frozen;
89027
89442
  }, []);
89028
- const clearSamples = useCallback162(() => {
89443
+ const clearSamples = useCallback163(() => {
89029
89444
  const r = refs.current;
89030
89445
  r.samples = [];
89031
89446
  r.trail = [];
@@ -89297,7 +89712,7 @@ function useGestureCommit({
89297
89712
  const capturedSelectionRef = useRef167(null);
89298
89713
  const capturedAnimationsRef = useRef167([]);
89299
89714
  useEffect135(() => () => clearInterval(recordingAutoStopRef.current), []);
89300
- const cancelRecording = useCallback163(() => {
89715
+ const cancelRecording = useCallback164(() => {
89301
89716
  clearInterval(recordingAutoStopRef.current);
89302
89717
  gestureRecording.stopRecording();
89303
89718
  gestureRecording.clearSamples();
@@ -89309,7 +89724,7 @@ function useGestureCommit({
89309
89724
  useEffect135(() => {
89310
89725
  if (readOnlyPreview && gestureStateRef.current === "recording") cancelRecording();
89311
89726
  }, [cancelRecording, readOnlyPreview]);
89312
- const stopAndCommitRecording = useCallback163(async () => {
89727
+ const stopAndCommitRecording = useCallback164(async () => {
89313
89728
  clearInterval(recordingAutoStopRef.current);
89314
89729
  if (commitInFlightRef.current) {
89315
89730
  return;
@@ -89488,7 +89903,7 @@ function useGestureCommit({
89488
89903
  commitInFlightRef.current = false;
89489
89904
  }
89490
89905
  }, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
89491
- const armAutoStop = useCallback163(
89906
+ const armAutoStop = useCallback164(
89492
89907
  (elementEnd) => {
89493
89908
  clearInterval(recordingAutoStopRef.current);
89494
89909
  const autoStopAt = elementEnd ?? Infinity;
@@ -89502,7 +89917,7 @@ function useGestureCommit({
89502
89917
  },
89503
89918
  [stopAndCommitRecording]
89504
89919
  );
89505
- const startRecording = useCallback163(
89920
+ const startRecording = useCallback164(
89506
89921
  (method) => {
89507
89922
  if (readOnlyPreview) return;
89508
89923
  const sel = domEditSessionRef.current.domEditSelection;
@@ -89537,7 +89952,7 @@ function useGestureCommit({
89537
89952
  armAutoStop
89538
89953
  ]
89539
89954
  );
89540
- const handleToggleRecording = useCallback163(
89955
+ const handleToggleRecording = useCallback164(
89541
89956
  (method = "button") => {
89542
89957
  if (commitInFlightRef.current) return;
89543
89958
  if (gestureStateRef.current === "recording") {
@@ -89699,7 +90114,7 @@ function useGestureTrailOverlay({
89699
90114
  import { useMemo as useMemo88 } from "react";
89700
90115
 
89701
90116
  // src/components/StudioLeftPanels.tsx
89702
- import { memo as memo49, useCallback as useCallback171 } from "react";
90117
+ import { memo as memo49, useCallback as useCallback172 } from "react";
89703
90118
 
89704
90119
  // src/components/MediaPreview.tsx
89705
90120
  import { useState as useState132 } from "react";
@@ -89818,7 +90233,7 @@ function MediaPreview({ projectId, filePath }) {
89818
90233
  }
89819
90234
 
89820
90235
  // src/components/sidebar/AssetsTab.tsx
89821
- import { memo as memo46, useState as useState138, useCallback as useCallback167, useRef as useRef172, useMemo as useMemo84, useEffect as useEffect141 } from "react";
90236
+ import { memo as memo46, useState as useState138, useCallback as useCallback168, useRef as useRef172, useMemo as useMemo84, useEffect as useEffect141 } from "react";
89822
90237
 
89823
90238
  // src/components/ui/SearchInput.tsx
89824
90239
  import { jsx as jsx210, jsxs as jsxs167 } from "react/jsx-runtime";
@@ -89929,11 +90344,11 @@ var CATEGORY_LABELS = {
89929
90344
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
89930
90345
 
89931
90346
  // src/components/sidebar/AudioRow.tsx
89932
- import { useState as useState134, useRef as useRef170, useEffect as useEffect137, useCallback as useCallback165 } from "react";
90347
+ import { useState as useState134, useRef as useRef170, useEffect as useEffect137, useCallback as useCallback166 } from "react";
89933
90348
  import { classifyWebAudioMediaRoute } from "@hyperframes/core/runtime/web-audio-route";
89934
90349
 
89935
90350
  // src/components/sidebar/AssetContextMenu.tsx
89936
- import { useCallback as useCallback164, useEffect as useEffect136, useLayoutEffect as useLayoutEffect14, useRef as useRef169, useState as useState133 } from "react";
90351
+ import { useCallback as useCallback165, useEffect as useEffect136, useLayoutEffect as useLayoutEffect14, useRef as useRef169, useState as useState133 } from "react";
89937
90352
  import { Fragment as Fragment59, jsx as jsx211, jsxs as jsxs168 } from "react/jsx-runtime";
89938
90353
  function isValidAssetName(name) {
89939
90354
  return name.length > 0 && !/[/\\]/.test(name) && !name.includes("..");
@@ -89993,7 +90408,7 @@ function ContextMenu3({
89993
90408
  menuRef.current?.querySelector('[role="menuitem"]')?.focus();
89994
90409
  }
89995
90410
  }, [mode]);
89996
- const commitRename = useCallback164(() => {
90411
+ const commitRename = useCallback165(() => {
89997
90412
  const trimmed = renameDraft.trim();
89998
90413
  if (trimmed === filename(asset)) {
89999
90414
  onClose();
@@ -90209,7 +90624,7 @@ function AudioRow({
90209
90624
  const serveUrl = resolveMediaPreviewUrl(asset, projectId);
90210
90625
  const isCopied = copyFeedback?.path === asset && copyFeedback.ok;
90211
90626
  const copyFailed = copyFeedback?.path === asset && !copyFeedback.ok;
90212
- const stopPlayback = useCallback165(() => {
90627
+ const stopPlayback = useCallback166(() => {
90213
90628
  audioRef.current?.pause();
90214
90629
  setPlaying(false);
90215
90630
  cancelAnimationFrame(animRef.current);
@@ -90220,7 +90635,7 @@ function AudioRow({
90220
90635
  const elements = usePlayerStore((s) => s.elements);
90221
90636
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
90222
90637
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
90223
- const activateRow = useCallback165(() => {
90638
+ const activateRow = useCallback166(() => {
90224
90639
  if (used) {
90225
90640
  const clip = findClipForAsset(elements, asset);
90226
90641
  if (clip) {
@@ -90242,10 +90657,10 @@ function AudioRow({
90242
90657
  setPreviewAsset,
90243
90658
  clearPreviewAsset
90244
90659
  ]);
90245
- const handlePointerDown = useCallback165((e) => {
90660
+ const handlePointerDown = useCallback166((e) => {
90246
90661
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
90247
90662
  }, []);
90248
- const handlePointerUp = useCallback165(
90663
+ const handlePointerUp = useCallback166(
90249
90664
  (e) => {
90250
90665
  const origin2 = pointerDownRef.current;
90251
90666
  pointerDownRef.current = null;
@@ -90291,7 +90706,7 @@ function AudioRow({
90291
90706
  }
90292
90707
  return () => cancelAnimationFrame(animRef.current);
90293
90708
  }, [playing]);
90294
- const togglePlay = useCallback165(async () => {
90709
+ const togglePlay = useCallback166(async () => {
90295
90710
  if (playing) {
90296
90711
  stopPlayback();
90297
90712
  if (stopCurrentPreview === stopPlayback) stopCurrentPreview = null;
@@ -90489,7 +90904,7 @@ function GlobalAssetsView({ searchQuery }) {
90489
90904
  }
90490
90905
 
90491
90906
  // src/components/sidebar/AssetCard.tsx
90492
- import { useState as useState137, useEffect as useEffect140, useRef as useRef171, useCallback as useCallback166 } from "react";
90907
+ import { useState as useState137, useEffect as useEffect140, useRef as useRef171, useCallback as useCallback167 } from "react";
90493
90908
 
90494
90909
  // src/components/ui/VideoFrameThumbnail.tsx
90495
90910
  import { useState as useState136, useEffect as useEffect139 } from "react";
@@ -90636,10 +91051,10 @@ function AssetCard({
90636
91051
  const elements = usePlayerStore((s) => s.elements);
90637
91052
  const setPreviewAsset = useAssetPreviewStore((s) => s.setPreviewAsset);
90638
91053
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
90639
- const handlePointerDown = useCallback166((e) => {
91054
+ const handlePointerDown = useCallback167((e) => {
90640
91055
  pointerDownRef.current = { x: e.clientX, y: e.clientY };
90641
91056
  }, []);
90642
- const activateCard = useCallback166(() => {
91057
+ const activateCard = useCallback167(() => {
90643
91058
  if (used) {
90644
91059
  const clip = findClipForAsset(elements, asset);
90645
91060
  if (clip) {
@@ -90661,7 +91076,7 @@ function AssetCard({
90661
91076
  setPreviewAsset,
90662
91077
  clearPreviewAsset
90663
91078
  ]);
90664
- const handlePointerUp = useCallback166(
91079
+ const handlePointerUp = useCallback167(
90665
91080
  (e) => {
90666
91081
  const origin2 = pointerDownRef.current;
90667
91082
  pointerDownRef.current = null;
@@ -90993,7 +91408,7 @@ var AssetsTab = memo46(function AssetsTab2({
90993
91408
  cancelled = true;
90994
91409
  };
90995
91410
  }, [projectId, assetsKey]);
90996
- const handleImport = useCallback167(
91411
+ const handleImport = useCallback168(
90997
91412
  async (files) => {
90998
91413
  if (!onImport) return;
90999
91414
  setImporting(true);
@@ -91005,7 +91420,7 @@ var AssetsTab = memo46(function AssetsTab2({
91005
91420
  },
91006
91421
  [onImport]
91007
91422
  );
91008
- const handleDrop = useCallback167(
91423
+ const handleDrop = useCallback168(
91009
91424
  (e) => {
91010
91425
  e.preventDefault();
91011
91426
  setDragOver(false);
@@ -91013,7 +91428,7 @@ var AssetsTab = memo46(function AssetsTab2({
91013
91428
  },
91014
91429
  [handleImport]
91015
91430
  );
91016
- const handleCopyPath = useCallback167(async (path) => {
91431
+ const handleCopyPath = useCallback168(async (path) => {
91017
91432
  const copied = await copyTextToClipboard(path);
91018
91433
  setCopyFeedback({ path, ok: copied });
91019
91434
  setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
@@ -91223,11 +91638,11 @@ var AssetsTab = memo46(function AssetsTab2({
91223
91638
  });
91224
91639
 
91225
91640
  // src/components/sidebar/BlocksTab.tsx
91226
- import { memo as memo47, useState as useState140, useCallback as useCallback169, useRef as useRef174, useEffect as useEffect143 } from "react";
91641
+ import { memo as memo47, useState as useState140, useCallback as useCallback170, useRef as useRef174, useEffect as useEffect143 } from "react";
91227
91642
  import { createPortal as createPortal13 } from "react-dom";
91228
91643
 
91229
91644
  // src/components/sidebar/PromptPreviewModal.tsx
91230
- import { useState as useState139, useCallback as useCallback168, useEffect as useEffect142, useRef as useRef173 } from "react";
91645
+ import { useState as useState139, useCallback as useCallback169, useEffect as useEffect142, useRef as useRef173 } from "react";
91231
91646
  import { jsx as jsx217, jsxs as jsxs173 } from "react/jsx-runtime";
91232
91647
  function PromptPreviewModal({
91233
91648
  title,
@@ -91253,7 +91668,7 @@ function PromptPreviewModal({
91253
91668
  textareaRef.current?.focus();
91254
91669
  });
91255
91670
  }, []);
91256
- const handleCopy2 = useCallback168(async () => {
91671
+ const handleCopy2 = useCallback169(async () => {
91257
91672
  try {
91258
91673
  await navigator.clipboard.writeText(valueRef.current);
91259
91674
  setCopyState("copied");
@@ -91539,13 +91954,13 @@ function BlockCard({
91539
91954
  const hoverTimer = useRef174(null);
91540
91955
  const colors = getCategoryColors(category);
91541
91956
  const needsWebGL = tags?.includes("html-in-canvas") || tags?.includes("webgl");
91542
- const handleEnter = useCallback169(() => {
91957
+ const handleEnter = useCallback170(() => {
91543
91958
  hoverTimer.current = setTimeout(() => {
91544
91959
  setHovered(true);
91545
91960
  onPreview?.({ videoUrl, posterUrl, title });
91546
91961
  }, 300);
91547
91962
  }, [onPreview, videoUrl, posterUrl, title]);
91548
- const handleLeave = useCallback169(() => {
91963
+ const handleLeave = useCallback170(() => {
91549
91964
  if (hoverTimer.current) {
91550
91965
  clearTimeout(hoverTimer.current);
91551
91966
  hoverTimer.current = null;
@@ -91558,7 +91973,7 @@ function BlockCard({
91558
91973
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
91559
91974
  };
91560
91975
  }, []);
91561
- const handleAdd = useCallback169(
91976
+ const handleAdd = useCallback170(
91562
91977
  async (e) => {
91563
91978
  e.stopPropagation();
91564
91979
  if (addState !== "idle" || !onAdd) return;
@@ -91574,7 +91989,7 @@ function BlockCard({
91574
91989
  [onAdd, addState]
91575
91990
  );
91576
91991
  const { activeCompPath, compositionDimensions } = useStudioShellContext();
91577
- const handleShowPrompt = useCallback169(
91992
+ const handleShowPrompt = useCallback170(
91578
91993
  (e) => {
91579
91994
  e.stopPropagation();
91580
91995
  const state = usePlayerStore.getState();
@@ -91714,7 +92129,7 @@ function BlockCard({
91714
92129
  }
91715
92130
 
91716
92131
  // src/components/sidebar/CompositionsTab.tsx
91717
- import { memo as memo48, useCallback as useCallback170, useEffect as useEffect144, useLayoutEffect as useLayoutEffect15, useMemo as useMemo85, useRef as useRef175, useState as useState141 } from "react";
92132
+ import { memo as memo48, useCallback as useCallback171, useEffect as useEffect144, useLayoutEffect as useLayoutEffect15, useMemo as useMemo85, useRef as useRef175, useState as useState141 } from "react";
91718
92133
  import { jsx as jsx219, jsxs as jsxs175 } from "react/jsx-runtime";
91719
92134
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
91720
92135
  var CARD_W = 80;
@@ -91825,7 +92240,7 @@ function CompCard({
91825
92240
  const hoverTimer = useRef175(null);
91826
92241
  const syncTimer = useRef175(null);
91827
92242
  const draggedRef = useRef175(false);
91828
- const requestIframePlaybackSync = useCallback170((shouldPlay) => {
92243
+ const requestIframePlaybackSync = useCallback171((shouldPlay) => {
91829
92244
  if (syncTimer.current) {
91830
92245
  clearTimeout(syncTimer.current);
91831
92246
  syncTimer.current = null;
@@ -92205,7 +92620,7 @@ var StudioLeftPanels = memo49(function StudioLeftPanels2({
92205
92620
  handleImportFiles,
92206
92621
  handleContentChange
92207
92622
  } = useFileManagerContext();
92208
- const handleRenderComposition = useCallback171(
92623
+ const handleRenderComposition = useCallback172(
92209
92624
  async (comp) => {
92210
92625
  if (renderQueue.ffmpegMissing) {
92211
92626
  useDockLayoutStore.getState().activatePanel("renders");
@@ -92315,13 +92730,13 @@ function CodeBody({
92315
92730
  }
92316
92731
 
92317
92732
  // src/components/StudioRightPanels.tsx
92318
- import { memo as memo59, useCallback as useCallback188 } from "react";
92733
+ import { memo as memo59, useCallback as useCallback189 } from "react";
92319
92734
 
92320
92735
  // src/components/editor/LayersPanel.tsx
92321
- import { memo as memo50, useState as useState143, useCallback as useCallback173, useEffect as useEffect145, useRef as useRef177 } from "react";
92736
+ import { memo as memo50, useState as useState143, useCallback as useCallback174, useEffect as useEffect145, useRef as useRef177 } from "react";
92322
92737
 
92323
92738
  // src/components/editor/useLayerDrag.ts
92324
- import { useRef as useRef176, useState as useState142, useCallback as useCallback172 } from "react";
92739
+ import { useRef as useRef176, useState as useState142, useCallback as useCallback173 } from "react";
92325
92740
  var DRAG_THRESHOLD_PX4 = 4;
92326
92741
  function isLayerDraggable(layer) {
92327
92742
  if (!(layer.selector || layer.id)) return false;
@@ -92391,7 +92806,7 @@ function useLayerDrag({
92391
92806
  const dragRef = useRef176(null);
92392
92807
  const [dragKey, setDragKey] = useState142(null);
92393
92808
  const [insertionLineY, setInsertionLineY] = useState142(null);
92394
- const handleRowPointerDown = useCallback172(
92809
+ const handleRowPointerDown = useCallback173(
92395
92810
  (layerIndex, e) => {
92396
92811
  if (e.button !== 0) return;
92397
92812
  const layer = visibleLayers[layerIndex];
@@ -92418,7 +92833,7 @@ function useLayerDrag({
92418
92833
  },
92419
92834
  [visibleLayers, scrollContainerRef, onSingleSibling]
92420
92835
  );
92421
- const handleContainerPointerMove = useCallback172(
92836
+ const handleContainerPointerMove = useCallback173(
92422
92837
  (e) => {
92423
92838
  const drag = dragRef.current;
92424
92839
  if (!drag) return;
@@ -92444,7 +92859,7 @@ function useLayerDrag({
92444
92859
  },
92445
92860
  [visibleLayers, scrollContainerRef]
92446
92861
  );
92447
- const handleContainerPointerUp = useCallback172(() => {
92862
+ const handleContainerPointerUp = useCallback173(() => {
92448
92863
  const drag = dragRef.current;
92449
92864
  dragRef.current = null;
92450
92865
  setDragKey(null);
@@ -92619,7 +93034,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92619
93034
  });
92620
93035
  const isMasterView = !activeCompPath || activeCompPath === "index.html";
92621
93036
  const livePreviewIframe = useLivePreviewIframe();
92622
- const collectLayers = useCallback173(() => {
93037
+ const collectLayers = useCallback174(() => {
92623
93038
  const iframe = previewIframeRef.current;
92624
93039
  if (!iframe) return;
92625
93040
  let doc = null;
@@ -92667,7 +93082,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92667
93082
  throttle.cancel();
92668
93083
  };
92669
93084
  }, [collectLayers]);
92670
- const resolveSelection = useCallback173(
93085
+ const resolveSelection = useCallback174(
92671
93086
  (layer) => {
92672
93087
  const doc = previewIframeRef.current?.contentDocument;
92673
93088
  const el = liveLayerElement(layer, doc, activeCompPath);
@@ -92680,7 +93095,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92680
93095
  },
92681
93096
  [activeCompPath, isMasterView, previewIframeRef, activeGroupElement]
92682
93097
  );
92683
- const seekToLayer = useCallback173(
93098
+ const seekToLayer = useCallback174(
92684
93099
  async (layer) => {
92685
93100
  const selection = await resolveSelection(layer);
92686
93101
  if (!selection) return;
@@ -92709,7 +93124,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92709
93124
  },
92710
93125
  [currentTime, resolveSelection, timelineElements]
92711
93126
  );
92712
- const handleSelectLayer = useCallback173(
93127
+ const handleSelectLayer = useCallback174(
92713
93128
  async (layer) => {
92714
93129
  const selection = await resolveSelection(layer);
92715
93130
  if (!selection) return;
@@ -92719,7 +93134,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92719
93134
  },
92720
93135
  [resolveSelection, applyDomSelection, seekToLayer, scheduleReveal]
92721
93136
  );
92722
- const handleLayerDoubleClick = useCallback173(
93137
+ const handleLayerDoubleClick = useCallback174(
92723
93138
  async (layer) => {
92724
93139
  const selection = await resolveSelection(layer);
92725
93140
  if (selection?.element.hasAttribute("data-hf-group")) {
@@ -92730,7 +93145,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92730
93145
  },
92731
93146
  [resolveSelection, setActiveGroupElement, handleSelectLayer]
92732
93147
  );
92733
- const handleLayerHover = useCallback173(
93148
+ const handleLayerHover = useCallback174(
92734
93149
  async (layer) => {
92735
93150
  if (!layer) {
92736
93151
  updateDomEditHoverSelection(null);
@@ -92741,11 +93156,11 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92741
93156
  },
92742
93157
  [resolveSelection, updateDomEditHoverSelection]
92743
93158
  );
92744
- const toggleCollapse = useCallback173((key2, e) => {
93159
+ const toggleCollapse = useCallback174((key2, e) => {
92745
93160
  e.stopPropagation();
92746
93161
  setCollapsed((prev) => ({ ...prev, [key2]: !prev[key2] }));
92747
93162
  }, []);
92748
- const handleReorder = useCallback173(
93163
+ const handleReorder = useCallback174(
92749
93164
  (event) => {
92750
93165
  const { siblingLayers, fromIndex, toIndex } = event;
92751
93166
  const reordered = [...siblingLayers];
@@ -92808,7 +93223,7 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92808
93223
  );
92809
93224
  const selectedKey = domEditSelection ? getDomEditLayerKey(domEditSelection) : null;
92810
93225
  const visibleLayers = getVisibleLayers(layers, collapsed);
92811
- const handleSingleSibling = useCallback173(() => {
93226
+ const handleSingleSibling = useCallback174(() => {
92812
93227
  showToast("Only one layer at this level", "info");
92813
93228
  }, [showToast]);
92814
93229
  const {
@@ -92940,10 +93355,10 @@ var LayersPanel = memo50(function LayersPanel2({ host }) {
92940
93355
  });
92941
93356
 
92942
93357
  // src/captions/components/CaptionPropertyPanel.tsx
92943
- import { memo as memo52, useCallback as useCallback175, useState as useState145 } from "react";
93358
+ import { memo as memo52, useCallback as useCallback176, useState as useState145 } from "react";
92944
93359
 
92945
93360
  // src/captions/components/CaptionAnimationPanel.tsx
92946
- import { memo as memo51, useCallback as useCallback174 } from "react";
93361
+ import { memo as memo51, useCallback as useCallback175 } from "react";
92947
93362
 
92948
93363
  // src/captions/components/shared.tsx
92949
93364
  import { useEffect as useEffect146, useRef as useRef178, useState as useState144 } from "react";
@@ -93168,25 +93583,25 @@ var CaptionAnimationPanel = memo51(function CaptionAnimationPanel2() {
93168
93583
  }
93169
93584
  const group = resolvedGroupId ? model?.groups.get(resolvedGroupId) : void 0;
93170
93585
  const animation = group?.animation;
93171
- const handleEntranceChange = useCallback174(
93586
+ const handleEntranceChange = useCallback175(
93172
93587
  (update) => {
93173
93588
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "entrance", update);
93174
93589
  },
93175
93590
  [resolvedGroupId, updateGroupAnimation]
93176
93591
  );
93177
- const handleHighlightChange = useCallback174(
93592
+ const handleHighlightChange = useCallback175(
93178
93593
  (update) => {
93179
93594
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "highlight", update);
93180
93595
  },
93181
93596
  [resolvedGroupId, updateGroupAnimation]
93182
93597
  );
93183
- const handleExitChange = useCallback174(
93598
+ const handleExitChange = useCallback175(
93184
93599
  (update) => {
93185
93600
  if (resolvedGroupId) updateGroupAnimation(resolvedGroupId, "exit", update);
93186
93601
  },
93187
93602
  [resolvedGroupId, updateGroupAnimation]
93188
93603
  );
93189
- const handleApplyToAll = useCallback174(() => {
93604
+ const handleApplyToAll = useCallback175(() => {
93190
93605
  if (animation) applyAnimationToAll(animation);
93191
93606
  }, [animation, applyAnimationToAll]);
93192
93607
  if (!group || !resolvedGroupId || !animation) {
@@ -93286,7 +93701,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93286
93701
  ...groupStyle,
93287
93702
  ...segmentOverrides
93288
93703
  };
93289
- const applyToIframeDom = useCallback175(
93704
+ const applyToIframeDom = useCallback176(
93290
93705
  (updates) => {
93291
93706
  const iframe = iframeRef.current;
93292
93707
  if (!iframe || !model) return;
@@ -93360,7 +93775,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93360
93775
  },
93361
93776
  [iframeRef, model, selectedSegmentIds]
93362
93777
  );
93363
- const handleStyleChange = useCallback175(
93778
+ const handleStyleChange = useCallback176(
93364
93779
  (updates) => {
93365
93780
  if (selectedGroupId) {
93366
93781
  updateGroupStyle(selectedGroupId, updates);
@@ -93465,7 +93880,7 @@ var CaptionPropertyPanel = memo52(function CaptionPropertyPanel2({
93465
93880
  });
93466
93881
 
93467
93882
  // src/components/editor/BlockParamsPanel.tsx
93468
- import { memo as memo53, useCallback as useCallback176, useEffect as useEffect147, useRef as useRef179, useState as useState146 } from "react";
93883
+ import { memo as memo53, useCallback as useCallback177, useEffect as useEffect147, useRef as useRef179, useState as useState146 } from "react";
93469
93884
  import { jsx as jsx227, jsxs as jsxs182 } from "react/jsx-runtime";
93470
93885
  var BlockParamsPanel = memo53(function BlockParamsPanel2({
93471
93886
  blockName,
@@ -93488,7 +93903,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93488
93903
  );
93489
93904
  const [commitState, setCommitState] = useState146({ tone: "idle" });
93490
93905
  const commitTimersRef = useRef179(/* @__PURE__ */ new Map());
93491
- const commitParamNow = useCallback176(
93906
+ const commitParamNow = useCallback177(
93492
93907
  async (key2, nextValue) => {
93493
93908
  if (!fileManager) return;
93494
93909
  const previous = appliedRef.current[key2];
@@ -93533,7 +93948,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93533
93948
  },
93534
93949
  [fileManager, compositionPath, setRefreshKey, blockName]
93535
93950
  );
93536
- const commitParam = useCallback176(
93951
+ const commitParam = useCallback177(
93537
93952
  (key2, nextValue) => fileManager ? serializeStudioFileMutation(
93538
93953
  fileManager.writeProjectFile,
93539
93954
  compositionPath,
@@ -93541,7 +93956,7 @@ var BlockParamsPanel = memo53(function BlockParamsPanel2({
93541
93956
  ) : Promise.resolve(),
93542
93957
  [commitParamNow, compositionPath, fileManager]
93543
93958
  );
93544
- const handleChange = useCallback176(
93959
+ const handleChange = useCallback177(
93545
93960
  (key2, value) => {
93546
93961
  setValues((prev) => ({ ...prev, [key2]: value }));
93547
93962
  const timers = commitTimersRef.current;
@@ -93704,7 +94119,7 @@ import { createPortal as createPortal14 } from "react-dom";
93704
94119
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
93705
94120
 
93706
94121
  // src/components/renders/RenderQueueItem.tsx
93707
- import { memo as memo54, useCallback as useCallback177, useState as useState147 } from "react";
94122
+ import { memo as memo54, useCallback as useCallback178, useState as useState147 } from "react";
93708
94123
  import { Fragment as Fragment64, jsx as jsx228, jsxs as jsxs183 } from "react/jsx-runtime";
93709
94124
  function formatDuration2(ms) {
93710
94125
  if (ms < 1e3) return `${ms}ms`;
@@ -93727,10 +94142,10 @@ var RenderQueueItem = memo54(function RenderQueueItem2({
93727
94142
  const [videoReady, setVideoReady] = useState147(false);
93728
94143
  const [confirmingDelete, setConfirmingDelete] = useState147(false);
93729
94144
  const fileSrc = buildProjectApiPath(projectId, `/renders/file/${job.filename}`);
93730
- const handleOpen = useCallback177(() => {
94145
+ const handleOpen = useCallback178(() => {
93731
94146
  window.open(fileSrc, "_blank");
93732
94147
  }, [fileSrc]);
93733
- const handleDownload = useCallback177(
94148
+ const handleDownload = useCallback178(
93734
94149
  (e) => {
93735
94150
  e.stopPropagation();
93736
94151
  const a = document.createElement("a");
@@ -94478,11 +94893,11 @@ var RenderQueuePanel = memo57(function RenderQueuePanel2() {
94478
94893
  });
94479
94894
 
94480
94895
  // src/components/panels/SlideshowPanel.tsx
94481
- import { useState as useState151, useEffect as useEffect150, useCallback as useCallback179, useRef as useRef182 } from "react";
94896
+ import { useState as useState151, useEffect as useEffect150, useCallback as useCallback180, useRef as useRef182 } from "react";
94482
94897
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
94483
94898
 
94484
94899
  // src/components/panels/SlideshowSubPanels.tsx
94485
- import { useState as useState150, useCallback as useCallback178, useId as useId6 } from "react";
94900
+ import { useState as useState150, useCallback as useCallback179, useId as useId6 } from "react";
94486
94901
  import { jsx as jsx232, jsxs as jsxs186 } from "react/jsx-runtime";
94487
94902
  function SectionHeader({
94488
94903
  children,
@@ -94670,7 +95085,7 @@ function BranchTree({
94670
95085
  }) {
94671
95086
  const [newLabel, setNewLabel] = useState150("");
94672
95087
  const inputId = useId6();
94673
- const handleCreate = useCallback178(() => {
95088
+ const handleCreate = useCallback179(() => {
94674
95089
  const label = newLabel.trim();
94675
95090
  if (!label) return;
94676
95091
  onCreateSequence(label);
@@ -94732,7 +95147,7 @@ function BranchItem({
94732
95147
  const [editing, setEditing] = useState150(false);
94733
95148
  const [draft, setDraft] = useState150(seq.label);
94734
95149
  const [confirmingDelete, setConfirmingDelete] = useState150(false);
94735
- const commitRename = useCallback178(() => {
95150
+ const commitRename = useCallback179(() => {
94736
95151
  const label = draft.trim();
94737
95152
  if (label && label !== seq.label) onRename(seq.id, label);
94738
95153
  setEditing(false);
@@ -94865,7 +95280,7 @@ function HotspotTool({
94865
95280
  const selectedElementId = domEditSelection?.element?.id ?? null;
94866
95281
  const selectedHfId = domEditSelection?.hfId ?? null;
94867
95282
  const elementKey3 = selectedElementId || selectedHfId;
94868
- const handleMakeHotspot = useCallback178(() => {
95283
+ const handleMakeHotspot = useCallback179(() => {
94869
95284
  if (!selectedSceneId || !targetSequenceId || !elementKey3) return;
94870
95285
  const id = `hotspot-${elementKey3}-${generateId()}`;
94871
95286
  const label = hotspotLabel.trim() || elementKey3;
@@ -95190,7 +95605,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95190
95605
  setUndoDepth(0);
95191
95606
  setPersistError(false);
95192
95607
  }, [compHtml]);
95193
- const applyManifest = useCallback179(
95608
+ const applyManifest = useCallback180(
95194
95609
  async (next, opts) => {
95195
95610
  const merged = notesCtrlRef.current.mergeIntoDiscrete(next);
95196
95611
  if (!opts?.skipUndo) {
@@ -95210,14 +95625,14 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95210
95625
  },
95211
95626
  [onPersist]
95212
95627
  );
95213
- const handleUndo = useCallback179(() => {
95628
+ const handleUndo = useCallback180(() => {
95214
95629
  const prev = undoStackRef.current.pop();
95215
95630
  if (!prev) return;
95216
95631
  setUndoDepth(undoStackRef.current.length);
95217
95632
  applyManifest(prev, { skipUndo: true }).catch(() => {
95218
95633
  });
95219
95634
  }, [applyManifest]);
95220
- const handleRetryPersist = useCallback179(async () => {
95635
+ const handleRetryPersist = useCallback180(async () => {
95221
95636
  setRetrying(true);
95222
95637
  try {
95223
95638
  await onPersist(manifestRef.current);
@@ -95228,7 +95643,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95228
95643
  setRetrying(false);
95229
95644
  }
95230
95645
  }, [onPersist]);
95231
- const applyNotesManifest = useCallback179(
95646
+ const applyNotesManifest = useCallback180(
95232
95647
  (next) => {
95233
95648
  setManifest(next);
95234
95649
  manifestRef.current = next;
@@ -95242,7 +95657,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95242
95657
  ctrl.flush();
95243
95658
  };
95244
95659
  }, []);
95245
- const toggleSection = useCallback179((key2) => {
95660
+ const toggleSection = useCallback180((key2) => {
95246
95661
  setExpandedSections((prev) => {
95247
95662
  const next = new Set(prev);
95248
95663
  if (next.has(key2)) {
@@ -95256,25 +95671,25 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95256
95671
  const activeSlides = selectedSequenceId ? (manifest.slideSequences ?? []).find((s) => s.id === selectedSequenceId)?.slides ?? [] : manifest.slides;
95257
95672
  const selectedSlide = activeSlides.find((s) => s.sceneId === selectedSceneId);
95258
95673
  const sequences = manifest.slideSequences ?? [];
95259
- const handleSelectBranchSlide = useCallback179((sequenceId, sceneId) => {
95674
+ const handleSelectBranchSlide = useCallback180((sequenceId, sceneId) => {
95260
95675
  setSelectedSceneId(sceneId);
95261
95676
  setSelectedSequenceId(sequenceId);
95262
95677
  }, []);
95263
- const handleToggleSlide = useCallback179(
95678
+ const handleToggleSlide = useCallback180(
95264
95679
  (sceneId) => {
95265
95680
  applyManifest(toggleMainLineSlide(manifestRef.current, sceneId)).catch(() => {
95266
95681
  });
95267
95682
  },
95268
95683
  [applyManifest]
95269
95684
  );
95270
- const handleReorder = useCallback179(
95685
+ const handleReorder = useCallback180(
95271
95686
  (sceneId, dir) => {
95272
95687
  applyManifest(reorderMainLineSlide(manifestRef.current, sceneId, dir)).catch(() => {
95273
95688
  });
95274
95689
  },
95275
95690
  [applyManifest]
95276
95691
  );
95277
- const handleSetNotes = useCallback179(
95692
+ const handleSetNotes = useCallback180(
95278
95693
  (notes) => {
95279
95694
  if (!selectedSceneId) return;
95280
95695
  applyNotesManifest(
@@ -95283,7 +95698,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95283
95698
  },
95284
95699
  [selectedSceneId, selectedSequenceId, applyNotesManifest]
95285
95700
  );
95286
- const handleMarkFragment = useCallback179(() => {
95701
+ const handleMarkFragment = useCallback180(() => {
95287
95702
  if (!selectedSceneId) return;
95288
95703
  applyManifest(
95289
95704
  addFragment(
@@ -95295,7 +95710,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95295
95710
  ).catch(() => {
95296
95711
  });
95297
95712
  }, [selectedSceneId, selectedSequenceId, currentTime, applyManifest]);
95298
- const handleRemoveFragment = useCallback179(
95713
+ const handleRemoveFragment = useCallback180(
95299
95714
  (time) => {
95300
95715
  if (!selectedSceneId) return;
95301
95716
  applyManifest(
@@ -95305,7 +95720,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95305
95720
  },
95306
95721
  [selectedSceneId, selectedSequenceId, applyManifest]
95307
95722
  );
95308
- const handleCreateSequence = useCallback179(
95723
+ const handleCreateSequence = useCallback180(
95309
95724
  (label) => {
95310
95725
  const id = `seq-${generateId()}`;
95311
95726
  applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {
@@ -95313,21 +95728,21 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95313
95728
  },
95314
95729
  [applyManifest]
95315
95730
  );
95316
- const handleRenameSequence = useCallback179(
95731
+ const handleRenameSequence = useCallback180(
95317
95732
  (id, label) => {
95318
95733
  applyManifest(renameSequence(manifestRef.current, id, label)).catch(() => {
95319
95734
  });
95320
95735
  },
95321
95736
  [applyManifest]
95322
95737
  );
95323
- const handleDeleteSequence = useCallback179(
95738
+ const handleDeleteSequence = useCallback180(
95324
95739
  (id) => {
95325
95740
  applyManifest(deleteSequence(manifestRef.current, id)).catch(() => {
95326
95741
  });
95327
95742
  },
95328
95743
  [applyManifest]
95329
95744
  );
95330
- const handleAssign = useCallback179(
95745
+ const handleAssign = useCallback180(
95331
95746
  (sequenceId, sceneId, assign) => {
95332
95747
  applyManifest(assignToBranch(manifestRef.current, sequenceId, sceneId, assign)).catch(
95333
95748
  () => {
@@ -95336,7 +95751,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95336
95751
  },
95337
95752
  [applyManifest]
95338
95753
  );
95339
- const handleAddHotspot = useCallback179(
95754
+ const handleAddHotspot = useCallback180(
95340
95755
  (sceneId, hotspot) => {
95341
95756
  applyManifest(
95342
95757
  addHotspot(manifestRef.current, sceneId, hotspot, selectedSequenceId ?? void 0)
@@ -95345,7 +95760,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95345
95760
  },
95346
95761
  [selectedSequenceId, applyManifest]
95347
95762
  );
95348
- const handleRemoveHotspot = useCallback179(
95763
+ const handleRemoveHotspot = useCallback180(
95349
95764
  (sceneId, hotspotId) => {
95350
95765
  applyManifest(
95351
95766
  removeHotspot(manifestRef.current, sceneId, hotspotId, selectedSequenceId ?? void 0)
@@ -95495,10 +95910,10 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
95495
95910
  }
95496
95911
 
95497
95912
  // src/components/panels/VariablesPanel.tsx
95498
- import { memo as memo58, useCallback as useCallback183, useEffect as useEffect152, useMemo as useMemo86, useState as useState155 } from "react";
95913
+ import { memo as memo58, useCallback as useCallback184, useEffect as useEffect152, useMemo as useMemo86, useState as useState155 } from "react";
95499
95914
 
95500
95915
  // src/hooks/useVariablesPersist.ts
95501
- import { useCallback as useCallback180 } from "react";
95916
+ import { useCallback as useCallback181 } from "react";
95502
95917
  function useVariablesPersist({
95503
95918
  sdkSession,
95504
95919
  activeCompPath,
@@ -95508,7 +95923,7 @@ function useVariablesPersist({
95508
95923
  reloadPreview,
95509
95924
  publishSdkSession
95510
95925
  }) {
95511
- return useCallback180(
95926
+ return useCallback181(
95512
95927
  async (label, mutate) => {
95513
95928
  if (!sdkSession) return false;
95514
95929
  const path = activeCompPath ?? "index.html";
@@ -95543,10 +95958,10 @@ function useVariablesPersist({
95543
95958
  }
95544
95959
 
95545
95960
  // src/components/panels/VariablesOtherCompositions.tsx
95546
- import { useCallback as useCallback182, useState as useState154 } from "react";
95961
+ import { useCallback as useCallback183, useState as useState154 } from "react";
95547
95962
 
95548
95963
  // src/hooks/useProjectCompositionVariables.ts
95549
- import { useCallback as useCallback181, useEffect as useEffect151, useState as useState152 } from "react";
95964
+ import { useCallback as useCallback182, useEffect as useEffect151, useState as useState152 } from "react";
95550
95965
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
95551
95966
  async function readGroup(path, readProjectFile) {
95552
95967
  let content;
@@ -95589,7 +96004,7 @@ function useProjectCompositionVariables(compositionPaths, excludePath, readProje
95589
96004
  }
95590
96005
  function useEditVariablesInFile(deps) {
95591
96006
  const { readProjectFile, writeProjectFile, recordEdit, reloadPreview } = deps;
95592
- return useCallback181(
96007
+ return useCallback182(
95593
96008
  async (path, label, mutate) => {
95594
96009
  const originalContent = await readProjectFile(path);
95595
96010
  await persistSdkSerialize(
@@ -95966,7 +96381,7 @@ function VariablesOtherCompositions({
95966
96381
  reloadPreview
95967
96382
  });
95968
96383
  const [editingKey, setEditingKey] = useState154(null);
95969
- const onSave = useCallback182(
96384
+ const onSave = useCallback183(
95970
96385
  (path, decl) => {
95971
96386
  setEditingKey(null);
95972
96387
  void editInFile(
@@ -95977,7 +96392,7 @@ function VariablesOtherCompositions({
95977
96392
  },
95978
96393
  [editInFile]
95979
96394
  );
95980
- const onRemove = useCallback182(
96395
+ const onRemove = useCallback183(
95981
96396
  (path, id) => {
95982
96397
  void editInFile(
95983
96398
  path,
@@ -96211,7 +96626,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96211
96626
  // eslint-disable-next-line react-hooks/exhaustive-deps
96212
96627
  [sdkSession, previewValues, refreshKey, revision]
96213
96628
  );
96214
- const copyToClipboard = useCallback183(
96629
+ const copyToClipboard = useCallback184(
96215
96630
  (text, what) => {
96216
96631
  void copyTextToClipboard(text).then(
96217
96632
  (ok) => showToast(
@@ -96222,7 +96637,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96222
96637
  },
96223
96638
  [showToast]
96224
96639
  );
96225
- const dropPreviewOverride = useCallback183(
96640
+ const dropPreviewOverride = useCallback184(
96226
96641
  (id) => {
96227
96642
  if (previewValues && id in previewValues) {
96228
96643
  const next = { ...previewValues };
@@ -96232,7 +96647,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96232
96647
  },
96233
96648
  [previewValues, setPreviewValues]
96234
96649
  );
96235
- const commitPreviewValue = useCallback183(
96650
+ const commitPreviewValue = useCallback184(
96236
96651
  (id, value, declDefault) => {
96237
96652
  const next = { ...previewValues ?? {} };
96238
96653
  if (JSON.stringify(value) === JSON.stringify(declDefault)) {
@@ -96245,7 +96660,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96245
96660
  },
96246
96661
  [previewValues, setPreviewValues, reloadPreview]
96247
96662
  );
96248
- const runSchemaEdit = useCallback183(
96663
+ const runSchemaEdit = useCallback184(
96249
96664
  async (label, mutate) => {
96250
96665
  try {
96251
96666
  const changed = await persistVariables(label, mutate);
@@ -96259,7 +96674,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96259
96674
  },
96260
96675
  [persistVariables, showToast]
96261
96676
  );
96262
- const handleAdd = useCallback183(
96677
+ const handleAdd = useCallback184(
96263
96678
  (decl) => {
96264
96679
  if (!sdkSession) return;
96265
96680
  const check = sdkSession.can({ type: "declareVariable", declaration: decl });
@@ -96272,7 +96687,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96272
96687
  },
96273
96688
  [sdkSession, runSchemaEdit, showToast]
96274
96689
  );
96275
- const handleUpdate = useCallback183(
96690
+ const handleUpdate = useCallback184(
96276
96691
  (decl) => {
96277
96692
  if (!sdkSession) return;
96278
96693
  const check = sdkSession.can({
@@ -96292,7 +96707,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96292
96707
  },
96293
96708
  [sdkSession, runSchemaEdit, showToast]
96294
96709
  );
96295
- const handleRemove = useCallback183(
96710
+ const handleRemove = useCallback184(
96296
96711
  (id) => {
96297
96712
  if (!sdkSession) return;
96298
96713
  const check = sdkSession.can({ type: "removeVariableDeclaration", id });
@@ -96308,18 +96723,18 @@ var VariablesPanel = memo58(function VariablesPanel2({
96308
96723
  },
96309
96724
  [sdkSession, runSchemaEdit, dropPreviewOverride, showToast]
96310
96725
  );
96311
- const handleSetDefault = useCallback183(
96726
+ const handleSetDefault = useCallback184(
96312
96727
  (id, value) => {
96313
96728
  void runSchemaEdit(`Set default for "${id}"`, (s) => s.setVariableValue(id, value));
96314
96729
  dropPreviewOverride(id);
96315
96730
  },
96316
96731
  [runSchemaEdit, dropPreviewOverride]
96317
96732
  );
96318
- const resetPreview = useCallback183(() => {
96733
+ const resetPreview = useCallback184(() => {
96319
96734
  setPreviewValues(null);
96320
96735
  reloadPreview();
96321
96736
  }, [setPreviewValues, reloadPreview]);
96322
- const handleBind = useCallback183(
96737
+ const handleBind = useCallback184(
96323
96738
  // Guard chain (session, selection, type-compat) — one branch per guard.
96324
96739
  // fallow-ignore-next-line complexity
96325
96740
  (action, id) => {
@@ -96431,7 +96846,7 @@ var VariablesPanel = memo58(function VariablesPanel2({
96431
96846
  });
96432
96847
 
96433
96848
  // src/hooks/useSlideshowPersist.ts
96434
- import { useCallback as useCallback184 } from "react";
96849
+ import { useCallback as useCallback185 } from "react";
96435
96850
 
96436
96851
  // src/utils/setSlideshowManifest.ts
96437
96852
  import {
@@ -96488,7 +96903,7 @@ function useSlideshowPersist({
96488
96903
  publishSdkSession,
96489
96904
  coalesceKey
96490
96905
  }) {
96491
- return useCallback184(
96906
+ return useCallback185(
96492
96907
  async (manifest) => {
96493
96908
  if (!sdkSession) return;
96494
96909
  const path = activeCompPath ?? "index.html";
@@ -96546,7 +96961,7 @@ function useSlideshowTabState(params) {
96546
96961
  }
96547
96962
 
96548
96963
  // src/components/DesignPanelPromoteProvider.tsx
96549
- import { useCallback as useCallback185 } from "react";
96964
+ import { useCallback as useCallback186 } from "react";
96550
96965
  import { jsx as jsx238 } from "react/jsx-runtime";
96551
96966
  function DesignPanelPromoteProvider({
96552
96967
  selection,
@@ -96565,7 +96980,7 @@ function DesignPanelPromoteProvider({
96565
96980
  publishSdkSession: handle.publish,
96566
96981
  activeCompPath: targetPath
96567
96982
  });
96568
- const persist2 = useCallback185(
96983
+ const persist2 = useCallback186(
96569
96984
  async (label, mutate) => {
96570
96985
  const committed = await rawPersist(label, mutate);
96571
96986
  if (committed) forceReloadSharedSdkSession?.();
@@ -96573,7 +96988,7 @@ function DesignPanelPromoteProvider({
96573
96988
  },
96574
96989
  [rawPersist, forceReloadSharedSdkSession]
96575
96990
  );
96576
- const handlePersistError = useCallback185(
96991
+ const handlePersistError = useCallback186(
96577
96992
  (error) => showToast(getStudioSaveErrorMessage(error), "error"),
96578
96993
  [showToast]
96579
96994
  );
@@ -96590,7 +97005,7 @@ function DesignPanelPromoteProvider({
96590
97005
  }
96591
97006
 
96592
97007
  // src/hooks/useRemoveBackground.ts
96593
- import { useCallback as useCallback186, useEffect as useEffect153, useRef as useRef183 } from "react";
97008
+ import { useCallback as useCallback187, useEffect as useEffect153, useRef as useRef183 } from "react";
96594
97009
 
96595
97010
  // src/components/studioMediaJobs.ts
96596
97011
  var MEDIA_JOB_RECONNECT_TIMEOUT_MS = 15e3;
@@ -96703,7 +97118,7 @@ function useRemoveBackground(projectId, refreshFileTree, showToast) {
96703
97118
  },
96704
97119
  []
96705
97120
  );
96706
- return useCallback186(
97121
+ return useCallback187(
96707
97122
  // fallow-ignore-next-line complexity
96708
97123
  async (inputPath, options) => {
96709
97124
  const response = await studioApiFetch(
@@ -96742,7 +97157,7 @@ function useRemoveBackground(projectId, refreshFileTree, showToast) {
96742
97157
  }
96743
97158
 
96744
97159
  // src/hooks/useApplyColorGradingScope.ts
96745
- import { useCallback as useCallback187 } from "react";
97160
+ import { useCallback as useCallback188 } from "react";
96746
97161
 
96747
97162
  // src/components/editor/colorGradingScopePatch.ts
96748
97163
  var MEDIA_TAG_RE = /<\s*(video|img)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
@@ -96846,7 +97261,7 @@ function useApplyColorGradingScope(recordEdit, reloadPreview) {
96846
97261
  const { projectId, activeCompPath, showToast, waitForPendingDomEditSaves } = useStudioShellContext();
96847
97262
  const { domEditSelection } = useDomEditContext();
96848
97263
  const { readProjectFile, writeProjectFile, compositions } = useFileManagerContext();
96849
- return useCallback187(
97264
+ return useCallback188(
96850
97265
  async (scope, value) => applyColorGradingScopeUpdate({
96851
97266
  scope,
96852
97267
  value,
@@ -96994,11 +97409,11 @@ var StudioRightPanels = memo59(function StudioRightPanels2({
96994
97409
  useCaptionDesignFocus(captionEditMode);
96995
97410
  const handleApplyColorGradingScope = useApplyColorGradingScope(recordEdit, reloadPreview);
96996
97411
  const handleRemoveBackground = useRemoveBackground(projectId, refreshFileTree, showToast);
96997
- const setAttributeWhileDragging = useCallback188(
97412
+ const setAttributeWhileDragging = useCallback189(
96998
97413
  (attr, value) => handleDomAttributeLiveCommit(attr, value, void 0, { previewOnly: true }),
96999
97414
  [handleDomAttributeLiveCommit]
97000
97415
  );
97001
- const handleHideAllSelected = useCallback188(() => {
97416
+ const handleHideAllSelected = useCallback189(() => {
97002
97417
  if (!canHideSelections(domEditGroupSelections)) {
97003
97418
  showToast("Audio can't be hidden \u2014 use the group's own controls", "info");
97004
97419
  return;
@@ -97007,7 +97422,7 @@ var StudioRightPanels = memo59(function StudioRightPanels2({
97007
97422
  const keys = timelineKeysForSelections(domEditGroupSelections, elements, activeCompPath);
97008
97423
  if (keys.length > 0) void onToggleElementHidden?.(keys, true);
97009
97424
  }, [domEditGroupSelections, showToast, activeCompPath, onToggleElementHidden]);
97010
- const convertToKeyframes = useCallback188(
97425
+ const convertToKeyframes = useCallback189(
97011
97426
  (animId, duration) => handleGsapConvertToKeyframes(animId, void 0, duration),
97012
97427
  [handleGsapConvertToKeyframes]
97013
97428
  );
@@ -97163,340 +97578,6 @@ import {
97163
97578
  MagnifyingGlassPlus
97164
97579
  } from "@phosphor-icons/react";
97165
97580
 
97166
- // src/hooks/useEnableKeyframes.ts
97167
- import { useCallback as useCallback189 } from "react";
97168
- var enableKeyframesTransactionCounter = 0;
97169
- function animatedProps(anim) {
97170
- if (!anim) return ["x", "y"];
97171
- const own = Object.keys(anim.properties ?? {});
97172
- if (own.length > 0) return own;
97173
- const stops = anim.keyframes?.keyframes;
97174
- if (stops?.length) {
97175
- const keys = /* @__PURE__ */ new Set();
97176
- for (const stop2 of stops) for (const k of Object.keys(stop2.properties ?? {})) keys.add(k);
97177
- if (keys.size > 0) return [...keys];
97178
- }
97179
- return ["x", "y"];
97180
- }
97181
- function isPlayheadWithinTween(anim, currentTime) {
97182
- const start = resolveTweenStart(anim);
97183
- if (start === null) return true;
97184
- return isTimeWithinTween(currentTime, start, resolveTweenDuration(anim));
97185
- }
97186
- async function replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector) {
97187
- const position = readElementPosition(iframe, sel, setAnim);
97188
- if (Object.keys(position).length === 0) {
97189
- for (const [key2, held] of Object.entries(setAnim.properties ?? {})) {
97190
- if (typeof held === "number") position[key2] = held;
97191
- }
97192
- }
97193
- if (Object.keys(position).length === 0 || !session.commitMutation) return;
97194
- const range = resolveNewTweenRange(sel.dataAttributes?.start, sel.dataAttributes?.duration, t2);
97195
- await session.commitMutation(
97196
- {
97197
- type: "replace-with-keyframes",
97198
- animationId: setAnim.id,
97199
- targetSelector: selector,
97200
- position: roundTo3(range.start),
97201
- duration: roundTo3(range.duration),
97202
- keyframes: [{ percentage: 0, properties: position }],
97203
- ease: setAnim.ease
97204
- },
97205
- { label: "Enable keyframes", keyframeAction: "add", softReload: true }
97206
- );
97207
- }
97208
- function readElementPosition(iframe, sel, anim) {
97209
- const result = {};
97210
- if (!iframe?.contentWindow) return result;
97211
- let gsap3;
97212
- try {
97213
- gsap3 = iframe.contentWindow.gsap;
97214
- } catch {
97215
- return result;
97216
- }
97217
- const element = sel.element;
97218
- if (!element?.isConnected || !gsap3?.getProperty) return result;
97219
- const props = animatedProps(anim);
97220
- for (const prop of props) {
97221
- const val = Number(gsap3.getProperty(element, prop));
97222
- if (!Number.isFinite(val)) continue;
97223
- result[prop] = POSITION_PROPS.has(prop) ? Math.round(val) : roundTo3(val);
97224
- }
97225
- return result;
97226
- }
97227
- function resolveNewTweenRange(authoredStart, authoredDuration, currentTime) {
97228
- const t2 = Math.max(0, roundTo3(currentTime));
97229
- const start = authoredStart != null ? Number.parseFloat(authoredStart) : Number.NaN;
97230
- const duration = authoredDuration != null ? Number.parseFloat(authoredDuration) : Number.NaN;
97231
- if (!Number.isFinite(start) || !Number.isFinite(duration) || duration <= 0) {
97232
- return { start: t2, duration: 1 };
97233
- }
97234
- const end = start + duration;
97235
- const clampedStart = Math.min(Math.max(t2, start), end);
97236
- return { start: clampedStart, duration: Math.max(0.5, roundTo3(end - clampedStart)) };
97237
- }
97238
- async function tryFetchAnimationsForElement(sel) {
97239
- const projectId = window.location.hash.match(/project\/([^?/]+)/)?.[1];
97240
- if (!projectId) return null;
97241
- const sourceFile = sel.sourceFile || "index.html";
97242
- const parsed = await fetchParsedAnimations(projectId, sourceFile);
97243
- if (!parsed) return null;
97244
- return getAnimationsForElement(parsed.animations, {
97245
- id: sel.id,
97246
- selector: sel.selector
97247
- });
97248
- }
97249
- async function fetchAnimationsForElement(sel) {
97250
- return await tryFetchAnimationsForElement(sel) ?? [];
97251
- }
97252
- async function extendKeyframedTweenToPlayhead(session, sel, anim, currentTime, duration, iframe, commitOverrides) {
97253
- const selector = existingTweenTargetSelector(anim, sel);
97254
- const position = readElementPosition(iframe, sel, anim);
97255
- if (!selector || Object.keys(position).length === 0 || !session.commitMutation) return;
97256
- const extended = buildExtendedKeyframes(anim, currentTime, position, duration);
97257
- await session.commitMutation(
97258
- {
97259
- type: "replace-with-keyframes",
97260
- animationId: anim.id,
97261
- targetSelector: selector,
97262
- position: extended.position,
97263
- duration: extended.duration,
97264
- keyframes: extended.keyframes,
97265
- ...keyframeEases(anim)
97266
- },
97267
- {
97268
- label: "Add keyframe",
97269
- keyframeAction: "add",
97270
- softReload: true,
97271
- ...commitOverrides
97272
- }
97273
- );
97274
- }
97275
- async function applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe, commitOverrides, liveTween) {
97276
- const keepAuthoredDuration = kfAnim.durationUnresolved === true;
97277
- if (keepAuthoredDuration) {
97278
- kfAnim = withLiveTiming(kfAnim, liveTween ?? findParsedTween(iframe, sel.element, kfAnim));
97279
- }
97280
- if (keepAuthoredDuration && kfAnim.duration == null) return;
97281
- const duration = resolveTweenDuration(kfAnim);
97282
- const start = resolveTweenStart(kfAnim);
97283
- if (start !== null && !isTimeWithinTween(t2, start, duration)) {
97284
- if (keepAuthoredDuration) return;
97285
- await extendKeyframedTweenToPlayhead(
97286
- session,
97287
- sel,
97288
- kfAnim,
97289
- t2,
97290
- duration,
97291
- iframe,
97292
- commitOverrides
97293
- );
97294
- return;
97295
- }
97296
- const pct = start === null ? computeElementPercentage(t2, sel) : absoluteToPercentageForAnimation(t2, kfAnim) ?? 0;
97297
- const existing = kfAnim.keyframes?.keyframes.find((k) => playsNear(kfAnim, k.percentage, pct));
97298
- if (existing) {
97299
- session.handleGsapRemoveKeyframe(kfAnim.id, existing.percentage);
97300
- return;
97301
- }
97302
- if (session.handleGsapAddKeyframeBatch) {
97303
- const position = readElementPosition(iframe, sel, kfAnim);
97304
- if (Object.keys(position).length > 0) {
97305
- await session.handleGsapAddKeyframeBatch(kfAnim.id, pct, position, commitOverrides);
97306
- }
97307
- }
97308
- }
97309
- async function promoteSetToKeyframes(session, sel, setAnim, t2, iframe) {
97310
- const selector = existingTweenTargetSelector(setAnim, sel);
97311
- const setStart = resolveTweenStart(setAnim) ?? 0;
97312
- if (!selector || !session.commitMutation) return;
97313
- if (t2 <= setStart) {
97314
- await replaceSetWithSingleKeyframe(session, sel, setAnim, t2, iframe, selector);
97315
- return;
97316
- }
97317
- const endPosition = readElementPosition(iframe, sel, setAnim);
97318
- if (Object.keys(endPosition).length === 0) return;
97319
- const startPosition = {};
97320
- for (const key2 of Object.keys(endPosition)) {
97321
- const held = setAnim.properties?.[key2];
97322
- if (typeof held === "number") startPosition[key2] = held;
97323
- }
97324
- await session.commitMutation(
97325
- {
97326
- type: "replace-with-keyframes",
97327
- animationId: setAnim.id,
97328
- targetSelector: selector,
97329
- position: roundTo3(setStart),
97330
- duration: roundTo3(t2 - setStart),
97331
- keyframes: [
97332
- {
97333
- percentage: 0,
97334
- properties: Object.keys(startPosition).length > 0 ? startPosition : endPosition,
97335
- auto: true
97336
- },
97337
- { percentage: 100, properties: endPosition }
97338
- ],
97339
- ease: setAnim.ease
97340
- },
97341
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97342
- );
97343
- }
97344
- async function applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe) {
97345
- if (!session.commitMutation) return;
97346
- const targetSelector = existingTweenTargetSelector(arcAnim, sel);
97347
- if (!targetSelector) return;
97348
- const start = resolveTweenStart(arcAnim) ?? 0;
97349
- const duration = resolveTweenDuration(arcAnim);
97350
- if (!isTimeWithinTween(t2, start, duration)) {
97351
- const held = readElementPosition(iframe, sel, arcAnim);
97352
- if (t2 <= start || typeof held.x !== "number" || typeof held.y !== "number") return;
97353
- const extended = buildExtendedKeyframes(arcAnim, t2, { x: held.x, y: held.y }, duration, "none");
97354
- await session.commitMutation(
97355
- {
97356
- type: "replace-with-keyframes",
97357
- animationId: arcAnim.id,
97358
- targetSelector,
97359
- position: extended.position,
97360
- duration: extended.duration,
97361
- keyframes: extended.keyframes,
97362
- ease: "none"
97363
- },
97364
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97365
- );
97366
- return;
97367
- }
97368
- const nodes = linearArcKeyframes(arcAnim);
97369
- const playheadPercentage = absoluteToPercentage(t2, start, duration);
97370
- const timedNodeIndex = nodes.findIndex(
97371
- (node) => Math.abs(node.percentage - playheadPercentage) <= KEYFRAME_PCT_MATCH
97372
- );
97373
- if (timedNodeIndex !== -1) {
97374
- if (timedNodeIndex > 0 && timedNodeIndex < nodes.length - 1) {
97375
- await session.commitMutation(
97376
- {
97377
- type: "replace-with-keyframes",
97378
- animationId: arcAnim.id,
97379
- targetSelector,
97380
- position: roundTo3(start),
97381
- duration: roundTo3(duration),
97382
- keyframes: nodes.filter((_, index) => index !== timedNodeIndex),
97383
- ease: "none"
97384
- },
97385
- { label: "Remove keyframe", softReload: true }
97386
- );
97387
- }
97388
- return;
97389
- }
97390
- const live = readElementPosition(iframe, sel, arcAnim);
97391
- if (typeof live.x !== "number" || typeof live.y !== "number") return;
97392
- await session.commitMutation(
97393
- {
97394
- type: "replace-with-keyframes",
97395
- animationId: arcAnim.id,
97396
- targetSelector,
97397
- position: roundTo3(start),
97398
- duration: roundTo3(duration),
97399
- keyframes: buildTemporalArcKeyframes(arcAnim, playheadPercentage, {
97400
- x: live.x,
97401
- y: live.y
97402
- }),
97403
- ease: "none"
97404
- },
97405
- { label: "Add keyframe", keyframeAction: "add", softReload: true }
97406
- );
97407
- }
97408
- function useEnableKeyframes(sessionRef) {
97409
- return useCallback189(async () => {
97410
- const session = sessionRef.current;
97411
- if (!session) return;
97412
- const sel = session.domEditSelection;
97413
- if (!sel) return;
97414
- const t2 = usePlayerStore.getState().currentTime;
97415
- const iframe = session.previewIframeRef?.current ?? null;
97416
- const fetched = await tryFetchAnimationsForElement(sel);
97417
- const anims = fetched ?? session.selectedGsapAnimations;
97418
- const arcAnim = anims.find((a) => a.arcPath);
97419
- const kfAnim = anims.find((a) => a.keyframes && !a.arcPath);
97420
- const setAnim = anims.find((a) => isInstantHold(a) && !a.keyframes && !a.arcPath);
97421
- const flatAnim = anims.find((a) => !a.keyframes && !a.arcPath && !isInstantHold(a));
97422
- if (arcAnim) {
97423
- await applyArcKeyframeAtPlayhead(session, sel, arcAnim, t2, iframe);
97424
- } else if (kfAnim) {
97425
- await applyKeyframeAtPlayhead(session, sel, kfAnim, t2, iframe);
97426
- } else if (setAnim) {
97427
- await promoteSetToKeyframes(session, sel, setAnim, t2, iframe);
97428
- } else if (flatAnim) {
97429
- const flatLive = flatAnim.durationUnresolved ? findParsedTween(iframe, sel.element, flatAnim) : void 0;
97430
- enableKeyframesTransactionCounter += 1;
97431
- const coalesceKey = `enable-keyframes:${flatAnim.id}:${enableKeyframesTransactionCounter}`;
97432
- const convertCommitOverrides = {
97433
- keyframeTelemetry: false,
97434
- skipReload: true,
97435
- coalesceKey,
97436
- coalesceMs: Number.POSITIVE_INFINITY
97437
- };
97438
- await session.handleGsapConvertToKeyframes(
97439
- flatAnim.id,
97440
- void 0,
97441
- void 0,
97442
- convertCommitOverrides
97443
- );
97444
- const converted = (await fetchAnimationsForElement(sel)).find((a) => a.keyframes);
97445
- if (converted) {
97446
- const applyCommitOverrides = {
97447
- softReload: true,
97448
- coalesceKey,
97449
- // Must match the convert phase's window: editHistory keys coalescing
97450
- // off the incoming entry, so without Infinity here the apply (landing
97451
- // after two POSTs + a fetch) falls back to the 300ms default and the
97452
- // conversion splits into two undo entries under real latency.
97453
- coalesceMs: Number.POSITIVE_INFINITY
97454
- };
97455
- await applyKeyframeAtPlayhead(
97456
- session,
97457
- sel,
97458
- converted,
97459
- t2,
97460
- iframe,
97461
- applyCommitOverrides,
97462
- flatLive
97463
- );
97464
- }
97465
- } else {
97466
- const position = readElementPosition(iframe, sel, null);
97467
- const { start: elStart, duration: elDuration } = resolveNewTweenRange(
97468
- sel.dataAttributes?.start,
97469
- sel.dataAttributes?.duration,
97470
- t2
97471
- );
97472
- const selector = writeTargetSelector(sel);
97473
- if (!selector) {
97474
- session.handleGsapAddAnimation("to");
97475
- return;
97476
- }
97477
- if (Object.keys(position).length === 0) {
97478
- position.x = 0;
97479
- position.y = 0;
97480
- }
97481
- const keyframes = [{ percentage: 0, properties: { ...position } }];
97482
- if (session.commitMutation) {
97483
- await session.commitMutation(
97484
- {
97485
- type: "add-with-keyframes",
97486
- targetSelector: selector,
97487
- position: roundTo3(elStart),
97488
- duration: roundTo3(elDuration),
97489
- keyframes
97490
- },
97491
- { label: "Enable keyframes", softReload: true }
97492
- );
97493
- } else {
97494
- session.handleGsapAddAnimation("to");
97495
- }
97496
- }
97497
- }, [sessionRef]);
97498
- }
97499
-
97500
97581
  // src/hooks/useKeyframeKeyboard.ts
97501
97582
  import { useEffect as useEffect154, useCallback as useCallback190 } from "react";
97502
97583
  function useKeyframeKeyboard({ enabled, onAddKeyframe }) {