hyperframes 0.8.97 → 0.8.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/{add-JL2CNPBO.js → add-GHUB7AVH.js} +18 -17
  2. package/dist/{animatedGifPrep-5KUI3AAL.js → animatedGifPrep-RZVYVAXD.js} +8 -7
  3. package/dist/{asset-W5JGVKGN.js → asset-MRT34PQQ.js} +2 -2
  4. package/dist/{assetCataloger-FUXNMJYE.js → assetCataloger-BQREHRS2.js} +4 -3
  5. package/dist/{auth-P34WRL2O.js → auth-BBWBKAZF.js} +2 -2
  6. package/dist/{autoUpdate-YE3SSDCH.js → autoUpdate-BMLRSJPE.js} +2 -2
  7. package/dist/{backgroundChecks-GCBMV6KT.js → backgroundChecks-I5JE6HZK.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-2BIKSPOP.js → batchRender-AQN5PRXM.js} +5 -4
  10. package/dist/{beats-HGLLHLRZ.js → beats-F2YQUUDN.js} +12 -11
  11. package/dist/{benchmark-B6S4K2DA.js → benchmark-RVBTP5TB.js} +14 -13
  12. package/dist/{browser-B7HTQ57Z.js → browser-247ZM3UP.js} +11 -10
  13. package/dist/{bundleWithLocalizedFonts-QYS67RJT.js → bundleWithLocalizedFonts-JKJKZR2S.js} +2 -2
  14. package/dist/{capture-Z47X2EG6.js → capture-5BCJWVC7.js} +2 -2
  15. package/dist/{capture-HOXFOHCG.js → capture-QYLHIYKJ.js} +9 -8
  16. package/dist/{catalog-36N6EXPL.js → catalog-NFO3XKI2.js} +19 -18
  17. package/dist/{check-URG7KBMW.js → check-S4WORRJY.js} +34 -32
  18. package/dist/{checkBrowser-TCAW27AV.js → checkBrowser-UMS7OM7G.js} +33 -31
  19. package/dist/{chunk-45WXLLUL.js → chunk-32Y3HRS3.js} +2 -2
  20. package/dist/{chunk-M7Y4BQAC.js → chunk-4WAMN4IF.js} +1 -1
  21. package/dist/chunk-4Z5O7BTD.js +114 -0
  22. package/dist/{chunk-2PQFJE6F.js → chunk-5LLSHC7N.js} +8 -8
  23. package/dist/{chunk-H5ZRB3SN.js → chunk-6CN2QLB5.js} +3 -3
  24. package/dist/{chunk-6SXRFLBV.js → chunk-6F6EEQNB.js} +5 -12
  25. package/dist/{chunk-BEKKVBD5.js → chunk-7D2QWSXG.js} +1 -1
  26. package/dist/{chunk-OIT6L7OY.js → chunk-AZQLJHAO.js} +1 -1
  27. package/dist/{chunk-IXUGVQ2D.js → chunk-CHCKIZRG.js} +8 -8
  28. package/dist/{chunk-6AY3BKZI.js → chunk-EUSLU4BH.js} +1 -1
  29. package/dist/chunk-FOKUJKBN.js +3420 -0
  30. package/dist/{chunk-MAYKKN2W.js → chunk-FTPQUXVG.js} +3 -3
  31. package/dist/{chunk-EA5GAMEY.js → chunk-GMECXFAN.js} +1 -1
  32. package/dist/{chunk-QVGB3PQ2.js → chunk-GOI4GCUB.js} +1 -1
  33. package/dist/{chunk-ULER6XBR.js → chunk-HFFR7TN7.js} +1 -1
  34. package/dist/{chunk-J7QMXLNY.js → chunk-HUHJYNST.js} +1 -1
  35. package/dist/{chunk-4CMGNIJD.js → chunk-I6M4GXMC.js} +1 -1
  36. package/dist/{chunk-QPN3GV5R.js → chunk-I77CAR4W.js} +2 -2
  37. package/dist/{chunk-TAP72R26.js → chunk-IJ677ALU.js} +9 -9
  38. package/dist/{chunk-UPGW6TTK.js → chunk-IQ4UCWV7.js} +1 -1
  39. package/dist/{chunk-BJOPPINF.js → chunk-J5ES3BN2.js} +3 -3
  40. package/dist/{chunk-VSVNEDUL.js → chunk-JXXE3SWC.js} +10 -10
  41. package/dist/{chunk-3WWTEA4S.js → chunk-LBZ54I42.js} +1 -1
  42. package/dist/{chunk-KF4BPO7K.js → chunk-M4KS6MJN.js} +4 -4
  43. package/dist/{chunk-IOM5EJZB.js → chunk-MCABWC2G.js} +2 -2
  44. package/dist/{chunk-T6PPTDQR.js → chunk-MDADXVOE.js} +4 -4
  45. package/dist/{chunk-D2WFTJML.js → chunk-MM7KX3QX.js} +2 -2
  46. package/dist/{chunk-TGKEAZUB.js → chunk-NDRJHYG6.js} +2 -2
  47. package/dist/{chunk-E6ABDLBN.js → chunk-O5TBFZ7X.js} +3 -3
  48. package/dist/chunk-OFQWTKPW.js +117 -0
  49. package/dist/{chunk-WY7DTUVY.js → chunk-PB2OX2JY.js} +1 -1
  50. package/dist/{chunk-NY4NHA3Y.js → chunk-PUB5XZYL.js} +1 -1
  51. package/dist/{chunk-6P7TUF6Z.js → chunk-R5RF3LGS.js} +1 -1
  52. package/dist/{chunk-JY2YGTOU.js → chunk-R6KCZGDI.js} +1 -1
  53. package/dist/{chunk-XMNWWURD.js → chunk-RDMAU7AJ.js} +14 -0
  54. package/dist/{chunk-5MRETCSV.js → chunk-T6OPV3KB.js} +3 -3
  55. package/dist/{chunk-TXY724ZK.js → chunk-TEXPOC5V.js} +2 -2
  56. package/dist/{chunk-YVGQM466.js → chunk-TH4APNRP.js} +6 -4
  57. package/dist/{chunk-WFO4PSIZ.js → chunk-TLYU5625.js} +9 -0
  58. package/dist/{chunk-YM2WZIJ7.js → chunk-TMLEIURT.js} +1 -1
  59. package/dist/{chunk-EJQTOUPG.js → chunk-TYA65W4U.js} +4 -4
  60. package/dist/{chunk-XKHIUGY3.js → chunk-USWOTTSP.js} +1 -1
  61. package/dist/{chunk-KVA65K7M.js → chunk-UVUAU3CH.js} +2 -2
  62. package/dist/{chunk-NBQ7RWSG.js → chunk-VBTX4DPW.js} +3 -3
  63. package/dist/{chunk-H7EMX7GA.js → chunk-VW5OMJNG.js} +3 -3
  64. package/dist/{chunk-UQ2H23VR.js → chunk-XFVS7YWL.js} +1 -1
  65. package/dist/{chunk-UBSAIOMW.js → chunk-XL2CUGEG.js} +105 -92
  66. package/dist/{chunk-IASEB3EM.js → chunk-XMENEJZ5.js} +5 -5
  67. package/dist/{chunk-5BJFUD4T.js → chunk-XZSH5GS7.js} +4 -4
  68. package/dist/{chunk-CLW66MZ7.js → chunk-Y2CZEJPK.js} +2 -2
  69. package/dist/{clean-RKSEJIE5.js → clean-65SGSRD3.js} +18 -16
  70. package/dist/cli.js +50 -50
  71. package/dist/{cloud-XWRBDZ32.js → cloud-CBUJO3BE.js} +4 -4
  72. package/dist/{cloudrun-6OB4LUGS.js → cloudrun-ISE6GSUO.js} +6 -5
  73. package/dist/{compare-N26BDH7P.js → compare-55Y5B73R.js} +28 -26
  74. package/dist/{compiler-CYMKVOVA.js → compiler-QMEZK6CU.js} +4 -3
  75. package/dist/{component-VPAI6ABP.js → component-QXUJVQCV.js} +3 -3
  76. package/dist/{compositions-HBYRCYJW.js → compositions-ZU4LJ2VE.js} +13 -12
  77. package/dist/{delete-Q47CA6AF.js → delete-YCFTXLIQ.js} +5 -3
  78. package/dist/{deterministicFonts-RMJFQXDW.js → deterministicFonts-XS3PGWH5.js} +2 -2
  79. package/dist/{dist-HJFL4ZN5.js → dist-E44WU6LH.js} +1 -1
  80. package/dist/{dist-XAD3AJS3.js → dist-R24TLUYO.js} +13 -11
  81. package/dist/{doctor-JBD7H32F.js → doctor-R4MX64N7.js} +12 -11
  82. package/dist/{events-QLCDRT25.js → events-AMCPKS2O.js} +10 -9
  83. package/dist/{events-JXIDJQZN.js → events-G2M67K5X.js} +11 -10
  84. package/dist/{feedback-SFMS4QAG.js → feedback-7PDQW7BY.js} +12 -11
  85. package/dist/{figma-BA66S2WD.js → figma-V3CJKPEP.js} +3 -3
  86. package/dist/fontLocalizeCli.js +11 -10
  87. package/dist/{get-ETWF2GUD.js → get-SVFBRH24.js} +5 -3
  88. package/dist/{gpuPolicy-HKUPBZJK.js → gpuPolicy-BQFPO7UA.js} +8 -7
  89. package/dist/{grade-compare-247R46JI.js → grade-compare-KDJ6L775.js} +28 -26
  90. package/dist/{gsapParser-IQS5TBGJ.js → gsapParser-EZGJSSV5.js} +46 -11
  91. package/dist/{gsapParserAcorn-OYHIHFF5.js → gsapParserAcorn-SZANFUAQ.js} +3 -1
  92. package/dist/{help-7X3O3TTP.js → help-EW2PIPB5.js} +1 -1
  93. package/dist/{history-ILSW2YFD.js → history-V7FE6H7U.js} +18 -16
  94. package/dist/hyperframe-runtime.js +1 -1
  95. package/dist/hyperframe.manifest.json +1 -1
  96. package/dist/hyperframe.runtime.iife.js +1 -1
  97. package/dist/hyperframes-player.global.js +1 -1
  98. package/dist/{info-K2HTKDTA.js → info-U32TSQMU.js} +13 -12
  99. package/dist/{init-2GSDAMQJ.js → init-3S4Y6J5G.js} +23 -22
  100. package/dist/{inspect-SVARILPN.js → inspect-WWJPKRGL.js} +29 -27
  101. package/dist/{installer-3EXYDXIP.js → installer-UZSVMUKT.js} +7 -6
  102. package/dist/{keyframes-SWB3HDQX.js → keyframes-Z4R6UFAQ.js} +23 -13
  103. package/dist/{lambda-AORIF7O3.js → lambda-U4WIJQCJ.js} +7 -6
  104. package/dist/{layout-6XUKBVQK.js → layout-LXW2H5JT.js} +29 -27
  105. package/dist/{lint-JDNINLZW.js → lint-OK2DN3VX.js} +14 -13
  106. package/dist/{lintProject-ESEUOGXF.js → lintProject-4P4C6AN2.js} +1 -1
  107. package/dist/{list-LRJWMJQS.js → list-DTY2BOKI.js} +5 -3
  108. package/dist/{login-EK45I6IK.js → login-AEKQ24BU.js} +4 -4
  109. package/dist/{media-treatment-4X2VQTZQ.js → media-treatment-FQQAY66F.js} +14 -13
  110. package/dist/{models-QKXFAS6W.js → models-LXUIU5DC.js} +9 -8
  111. package/dist/{motionShot-BMDELKKS.js → motionShot-XRSPP3G6.js} +12 -11
  112. package/dist/{normalize-audio-IGFAZ7QO.js → normalize-audio-KWWA6IIN.js} +12 -11
  113. package/dist/{pipeline-HB3B54ID.js → pipeline-OFFKTFXS.js} +8 -7
  114. package/dist/{play-D5MHDYX7.js → play-HWFIDZWY.js} +24 -22
  115. package/dist/{preflight-A5GAQUXY.js → preflight-TVCVP5P2.js} +9 -8
  116. package/dist/{present-MKO4FPMZ.js → present-EBMTSB5D.js} +19 -17
  117. package/dist/{preview-OBUTVZG7.js → preview-HAWENKYG.js} +20 -19
  118. package/dist/{producer-JX3R2SJD.js → producer-LGG3TOUI.js} +1 -1
  119. package/dist/{projectConfig-D72ZCV4U.js → projectConfig-V53SEFIS.js} +9 -8
  120. package/dist/{publication-NXK5YHOQ.js → publication-7RKKPKLM.js} +6 -5
  121. package/dist/{publish-5OD66ODR.js → publish-TRRFCEMQ.js} +11 -10
  122. package/dist/{remove-background-LBVJV7OI.js → remove-background-HJFT46Z5.js} +9 -8
  123. package/dist/{render-RBHQOXRI.js → render-Q4NHGHBP.js} +16 -14
  124. package/dist/{render-RCKUDK22.js → render-TDTMQSYX.js} +24 -23
  125. package/dist/{render-ZI2B2XFE.js → render-TPZ5FXHS.js} +5 -4
  126. package/dist/{render-batch-S3H4HVUJ.js → render-batch-4ZF5C2LN.js} +5 -4
  127. package/dist/{renderCancellation-O6XHCGN6.js → renderCancellation-4PYGAN5D.js} +8 -7
  128. package/dist/renderSetupWorker.js +1 -1
  129. package/dist/{resolver-Y3I7ADOB.js → resolver-4JRJPC6S.js} +7 -6
  130. package/dist/{sherpa-NIP27UXM.js → sherpa-MNCWRFD4.js} +11 -9
  131. package/dist/{skills-G3J3RP4T.js → skills-FKUY5J3V.js} +13 -12
  132. package/dist/{skillsUpdateCheck-F3XJNGFS.js → skillsUpdateCheck-VRGY5IRQ.js} +3 -3
  133. package/dist/{snapshot-YFDEUMDC.js → snapshot-OSW3KFRL.js} +29 -27
  134. package/dist/{src-BH62F364.js → src-QF6RYX76.js} +10 -9
  135. package/dist/{src-3CKPQCXW.js → src-QX5PPANW.js} +7 -6
  136. package/dist/{staticProjectServer-2XMNYJTA.js → staticProjectServer-TEVGFDXU.js} +18 -16
  137. package/dist/{status-RP5AM4YY.js → status-C3IDZFDG.js} +8 -7
  138. package/dist/studio/assets/{hyperframes-player-Biu5_W0l.js → hyperframes-player-Rml34FRW.js} +1 -1
  139. package/dist/studio/assets/{index-BdV5T-QI.js → index-CVDArg3m.js} +188 -188
  140. package/dist/studio/assets/{index-B5Z-1RW9.js → index-C_fi-jTe.js} +1 -1
  141. package/dist/studio/assets/{index-CWuozokW.js → index-D9YzDuBx.js} +1 -1
  142. package/dist/studio/index.d.ts +16 -4
  143. package/dist/studio/index.html +1 -1
  144. package/dist/studio/index.js +758 -607
  145. package/dist/studio/index.js.map +1 -1
  146. package/dist/{studio-api-OGPSLKV7.js → studio-api-VNIEVFVL.js} +12 -10
  147. package/dist/{studioServer-2IOOYEU2.js → studioServer-YGYE5AZF.js} +48 -44
  148. package/dist/{system-SHCBJHHU.js → system-OAB2LYBV.js} +8 -7
  149. package/dist/{telemetry-EC2XOCFB.js → telemetry-SW2VUF5A.js} +11 -10
  150. package/dist/{timeline-BSQQU2AJ.js → timeline-KXD4SKGX.js} +18 -16
  151. package/dist/{tokens-FVA47YFM.js → tokens-TVDW7X7Q.js} +2 -2
  152. package/dist/{transcribe-IK5WYH4B.js → transcribe-AL3E5MBZ.js} +9 -8
  153. package/dist/{transcribe-JBNTXHII.js → transcribe-WNVFCNC5.js} +14 -13
  154. package/dist/{updateCheck-GDYVUZTN.js → updateCheck-DAF2B2QZ.js} +2 -2
  155. package/dist/{upgrade-Z3PPUNXB.js → upgrade-ZGJPNQOO.js} +2 -2
  156. package/dist/{urlDownloader-EMO4Y44J.js → urlDownloader-U3TNOPD5.js} +7 -6
  157. package/dist/{validate-3DQW7O2T.js → validate-AY5MWLFK.js} +17 -16
  158. package/dist/{video-5WCMLASZ.js → video-KAHL76HG.js} +8 -7
  159. package/package.json +1 -1
  160. package/dist/chunk-M5YNZQXZ.js +0 -3528
@@ -2173,6 +2173,9 @@ function getTimelinePlayheadTransform(time, pixelsPerSecond, contentOrigin, atRe
2173
2173
  function roundTo3(val) {
2174
2174
  return Math.round(val * 1e3) / 1e3;
2175
2175
  }
2176
+ function roundToLayoutPx(val) {
2177
+ return Math.round(val);
2178
+ }
2176
2179
  function roundToCenti(val) {
2177
2180
  return Math.round(val * 100) / 100;
2178
2181
  }
@@ -12872,6 +12875,7 @@ function useTimelineProviderState({
12872
12875
  onPasteClip,
12873
12876
  onDuplicateClip,
12874
12877
  canPasteClip,
12878
+ clipMenuItems,
12875
12879
  theme: themeOverrides,
12876
12880
  showAudioEffects = true,
12877
12881
  showKeyframes = true,
@@ -13170,6 +13174,7 @@ function useTimelineProviderState({
13170
13174
  onPasteClip,
13171
13175
  onDuplicateClip,
13172
13176
  canPasteClip,
13177
+ clipMenuItems,
13173
13178
  onSelectElement,
13174
13179
  selectedElementId,
13175
13180
  setRangeSelectionRef,
@@ -19806,6 +19811,8 @@ async function postGsapMutation(projectId, filePath, mutation, fallback) {
19806
19811
  }
19807
19812
 
19808
19813
  // src/hooks/timelineTimingSync.ts
19814
+ import { findTimelineScript } from "@hyperframes/core/gsap-parser-acorn";
19815
+ import { walkCompositionDescendants } from "@hyperframes/parsers/hf-ids";
19809
19816
  async function readFileContent(projectId, targetPath) {
19810
19817
  if (targetPath.includes("\0") || targetPath.includes("..")) {
19811
19818
  throw new Error(`Unsafe path: ${targetPath}`);
@@ -20005,8 +20012,19 @@ async function scaleGsapPositions(projectId, filePath, elementId2, oldStart, old
20005
20012
  "scale-positions failed"
20006
20013
  );
20007
20014
  }
20015
+ function sdkTimingGsapSync(result) {
20016
+ const timelineText = (html2) => {
20017
+ const scripts = [];
20018
+ walkCompositionDescendants(new DOMParser().parseFromString(html2, "text/html"), (el) => {
20019
+ if (el.tagName.toLowerCase() === "script") scripts.push(el);
20020
+ });
20021
+ return findTimelineScript(scripts)?.textContent ?? null;
20022
+ };
20023
+ if (timelineText(result.before) === timelineText(result.after)) return null;
20024
+ return { mutated: true, scriptText: extractGsapScriptText(result.after) };
20025
+ }
20008
20026
  function finishClipTimingFallback(input) {
20009
- const { projectId, targetPath, domId, edit } = input;
20027
+ const { projectId, targetPath, domId, edit, sdkGsap } = input;
20010
20028
  const timingChanged = edit.kind === "shift" ? edit.delta !== 0 : edit.from.start !== edit.to.start || edit.from.duration !== edit.to.duration;
20011
20029
  const runMutation = (pid, id) => edit.kind === "shift" ? shiftGsapPositions(pid, targetPath, id, edit.delta) : scaleGsapPositions(
20012
20030
  pid,
@@ -20021,7 +20039,7 @@ function finishClipTimingFallback(input) {
20021
20039
  return finishTimelineTimingFallback({
20022
20040
  iframe: input.iframe,
20023
20041
  reloadPreview: input.reloadPreview,
20024
- gsapMutation: timingChanged && domId && projectId ? () => foldGsapMutationIntoHistory({
20042
+ gsapMutation: sdkGsap ? () => Promise.resolve(sdkGsap) : timingChanged && domId && projectId ? () => foldGsapMutationIntoHistory({
20025
20043
  writeFile: input.writeProjectFile,
20026
20044
  paths: [targetPath],
20027
20045
  projectId,
@@ -20041,6 +20059,17 @@ async function finishGroupTimingGsapFallback(input) {
20041
20059
  (change) => input.resolveChangePath(change.element) !== activePath
20042
20060
  );
20043
20061
  const onGsapError = (err) => console.error(`[Timeline] ${input.errorLabel}`, err);
20062
+ const { sdkGsap } = input;
20063
+ if (sdkGsap) {
20064
+ await finishTimelineTimingFallback({
20065
+ iframe: input.iframe,
20066
+ reloadPreview: input.reloadPreview,
20067
+ gsapMutation: () => Promise.resolve(sdkGsap),
20068
+ onGsapError,
20069
+ rebindWhenUnmutated: !otherFileChanged
20070
+ });
20071
+ return;
20072
+ }
20044
20073
  await finishTimelineTimingFallback({
20045
20074
  iframe: input.iframe,
20046
20075
  reloadPreview: input.reloadPreview,
@@ -21575,7 +21604,7 @@ var TimelineCanvas = memo13(function TimelineCanvas2() {
21575
21604
  });
21576
21605
 
21577
21606
  // src/player/components/TimelineOverlays.tsx
21578
- import { useEffect as useEffect30 } from "react";
21607
+ import { useEffect as useEffect30, useMemo as useMemo32 } from "react";
21579
21608
 
21580
21609
  // src/player/components/EditModal.tsx
21581
21610
  import { useState as useState24, useCallback as useCallback33, useMemo as useMemo31, useRef as useRef40 } from "react";
@@ -21831,7 +21860,9 @@ function useMenuKeyboardNav(menuRef) {
21831
21860
  menu.addEventListener("keydown", onKeyDown);
21832
21861
  return () => {
21833
21862
  menu.removeEventListener("keydown", onKeyDown);
21834
- if (previouslyFocused instanceof HTMLElement && document.contains(previouslyFocused)) {
21863
+ const active = document.activeElement;
21864
+ const focusLeftWithMenu = active === null || active === document.body || menu.contains(active);
21865
+ if (focusLeftWithMenu && previouslyFocused instanceof HTMLElement && document.contains(previouslyFocused)) {
21835
21866
  previouslyFocused.focus();
21836
21867
  }
21837
21868
  };
@@ -21967,6 +21998,36 @@ function KeyframeDiamondContextMenu({
21967
21998
  import { memo as memo14 } from "react";
21968
21999
  import { createPortal as createPortal4 } from "react-dom";
21969
22000
  import { Fragment as Fragment18, jsx as jsx58, jsxs as jsxs38 } from "react/jsx-runtime";
22001
+ var itemClass = (enabled) => `w-full flex items-center justify-between px-3 py-1.5 text-xs text-left outline-none${enabled ? " focus-visible:bg-neutral-800 text-neutral-300 hover:bg-neutral-800 cursor-pointer" : " text-neutral-600 cursor-not-allowed"}`;
22002
+ function HostItems({
22003
+ items,
22004
+ onClose
22005
+ }) {
22006
+ return /* @__PURE__ */ jsxs38(Fragment18, { children: [
22007
+ items.map((item) => /* @__PURE__ */ jsxs38(
22008
+ "button",
22009
+ {
22010
+ type: "button",
22011
+ role: "menuitem",
22012
+ className: itemClass(!item.disabled),
22013
+ disabled: item.disabled,
22014
+ onClick: () => {
22015
+ onClose();
22016
+ item.onSelect();
22017
+ },
22018
+ children: [
22019
+ /* @__PURE__ */ jsxs38("span", { className: "flex items-center gap-2", children: [
22020
+ item.icon,
22021
+ item.label
22022
+ ] }),
22023
+ item.shortcut && /* @__PURE__ */ jsx58("span", { className: "text-neutral-500 text-[10px] ml-3", children: item.shortcut })
22024
+ ]
22025
+ },
22026
+ item.id
22027
+ )),
22028
+ /* @__PURE__ */ jsx58("div", { className: "my-1 border-t border-neutral-700/60" })
22029
+ ] });
22030
+ }
21970
22031
  var ClipContextMenu = memo14(function ClipContextMenu2({
21971
22032
  x,
21972
22033
  y,
@@ -21978,7 +22039,8 @@ var ClipContextMenu = memo14(function ClipContextMenu2({
21978
22039
  onCopy,
21979
22040
  onPaste,
21980
22041
  onDuplicate,
21981
- canPaste
22042
+ canPaste,
22043
+ hostItems = []
21982
22044
  }) {
21983
22045
  const menuRef = useContextMenuDismiss(onClose);
21984
22046
  useMenuKeyboardNav(menuRef);
@@ -21990,14 +22052,14 @@ var ClipContextMenu = memo14(function ClipContextMenu2({
21990
22052
  const canSplit = isSplittable && currentTime > element.start && currentTime < element.start + element.duration;
21991
22053
  const splitLabel = !isSplittable ? null : canSplit ? `Split at ${currentTime.toFixed(2)}s` : "Split (move playhead inside clip)";
21992
22054
  const clipboardItemCount = [onCopy, onPaste, onDuplicate].filter(Boolean).length;
21993
- const rowCount = (splitLabel ? 1 : 0) + clipboardItemCount + 1;
21994
- const dividerCount = (splitLabel ? 1 : 0) + (clipboardItemCount > 0 ? 1 : 0);
22055
+ const hostRows = hostItems.length;
22056
+ const rowCount = hostRows + (splitLabel ? 1 : 0) + clipboardItemCount + 1;
22057
+ const dividerCount = (hostRows > 0 ? 1 : 0) + (splitLabel ? 1 : 0) + (clipboardItemCount > 0 ? 1 : 0);
21995
22058
  const menuWidth = 200;
21996
22059
  const menuHeight = rowCount * 30 + dividerCount * 9 + 8;
21997
22060
  const overflowY = y + menuHeight - window.innerHeight;
21998
22061
  const adjustedX = x + menuWidth > window.innerWidth ? x - menuWidth : x;
21999
22062
  const adjustedY = overflowY > 0 ? y - overflowY - 8 : y;
22000
- const itemClass = (enabled) => `w-full flex items-center justify-between px-3 py-1.5 text-xs text-left outline-none${enabled ? " focus-visible:bg-neutral-800 text-neutral-300 hover:bg-neutral-800 cursor-pointer" : " text-neutral-600 cursor-not-allowed"}`;
22001
22063
  return createPortal4(
22002
22064
  /* @__PURE__ */ jsxs38(
22003
22065
  "div",
@@ -22008,6 +22070,7 @@ var ClipContextMenu = memo14(function ClipContextMenu2({
22008
22070
  className: "fixed z-200 bg-neutral-900 border border-neutral-700 rounded-md shadow-lg py-1 min-w-[180px]",
22009
22071
  style: { left: adjustedX, top: adjustedY },
22010
22072
  children: [
22073
+ hostRows > 0 && /* @__PURE__ */ jsx58(HostItems, { items: hostItems, onClose }),
22011
22074
  splitLabel && /* @__PURE__ */ jsxs38(Fragment18, { children: [
22012
22075
  /* @__PURE__ */ jsxs38(
22013
22076
  "button",
@@ -22128,7 +22191,7 @@ var TrackGapContextMenu = memo15(function TrackGapContextMenu2({
22128
22191
  const overflowY = y + menuHeight - window.innerHeight;
22129
22192
  const adjustedX = x + menuWidth > window.innerWidth ? x - menuWidth : x;
22130
22193
  const adjustedY = overflowY > 0 ? y - overflowY - 8 : y;
22131
- const itemClass = (enabled) => `w-full flex items-center justify-between px-3 py-1.5 text-xs text-left ${enabled ? "text-neutral-300 hover:bg-neutral-800 cursor-pointer" : "text-neutral-600 cursor-not-allowed"}`;
22194
+ const itemClass2 = (enabled) => `w-full flex items-center justify-between px-3 py-1.5 text-xs text-left ${enabled ? "text-neutral-300 hover:bg-neutral-800 cursor-pointer" : "text-neutral-600 cursor-not-allowed"}`;
22132
22195
  const closeGapTitle = canCloseGap ? void 0 : gapWidth == null ? "No gap here" : "A clip on this track can't be moved";
22133
22196
  const closeAllTitle = canCloseAllGaps ? void 0 : hasAnyGaps ? "A clip on this track can't be moved" : "No gaps on this track";
22134
22197
  return createPortal5(
@@ -22144,7 +22207,7 @@ var TrackGapContextMenu = memo15(function TrackGapContextMenu2({
22144
22207
  "button",
22145
22208
  {
22146
22209
  type: "button",
22147
- className: itemClass(canCloseGap),
22210
+ className: itemClass2(canCloseGap),
22148
22211
  disabled: !canCloseGap,
22149
22212
  title: closeGapTitle,
22150
22213
  onPointerEnter: () => onHoverAction(canCloseGap ? "close-gap" : null),
@@ -22166,7 +22229,7 @@ var TrackGapContextMenu = memo15(function TrackGapContextMenu2({
22166
22229
  "button",
22167
22230
  {
22168
22231
  type: "button",
22169
- className: itemClass(canCloseAllGaps),
22232
+ className: itemClass2(canCloseAllGaps),
22170
22233
  disabled: !canCloseAllGaps,
22171
22234
  title: closeAllTitle,
22172
22235
  onPointerEnter: () => onHoverAction(canCloseAllGaps ? "close-all" : null),
@@ -22330,6 +22393,11 @@ function TimelineClipMenuOverlay() {
22330
22393
  useEffect30(() => {
22331
22394
  if (clipContextMenu && !element) setClipContextMenu(null);
22332
22395
  }, [element, clipContextMenu, setClipContextMenu]);
22396
+ const { clipMenuItems } = overlay;
22397
+ const hostItems = useMemo32(
22398
+ () => element ? clipMenuItems?.(element) : void 0,
22399
+ [clipMenuItems, element]
22400
+ );
22333
22401
  if (!clipContextMenu || !element) return null;
22334
22402
  const readCurrentElement = () => resolveTimelineContextElement({
22335
22403
  capturedElement: element,
@@ -22360,7 +22428,8 @@ function TimelineClipMenuOverlay() {
22360
22428
  onCopy: overlay.onCopyClip,
22361
22429
  onPaste: overlay.onPasteClip,
22362
22430
  onDuplicate: overlay.onDuplicateClip,
22363
- canPaste: overlay.canPasteClip?.() ?? false
22431
+ canPaste: overlay.canPasteClip?.() ?? false,
22432
+ hostItems
22364
22433
  }
22365
22434
  );
22366
22435
  }
@@ -22570,7 +22639,7 @@ var Timeline = Object.assign(TimelineComposed, {
22570
22639
  });
22571
22640
 
22572
22641
  // src/player/components/VideoThumbnail.tsx
22573
- import { memo as memo17, useMemo as useMemo32 } from "react";
22642
+ import { memo as memo17, useMemo as useMemo33 } from "react";
22574
22643
 
22575
22644
  // src/hooks/useThumbnailLease.ts
22576
22645
  import { useCallback as useCallback34, useLayoutEffect as useLayoutEffect7, useRef as useRef41, useSyncExternalStore as useSyncExternalStore3 } from "react";
@@ -23260,7 +23329,7 @@ var VideoThumbnail = memo17(function VideoThumbnail2({
23260
23329
  const requestFrameCount = quantizeThumbnailFrameCount(
23261
23330
  computeThumbnailStrip(container.width, 16 / 9, container.height).frameCount
23262
23331
  );
23263
- const requestProps = useMemo32(
23332
+ const requestProps = useMemo33(
23264
23333
  () => ({
23265
23334
  videoSrc,
23266
23335
  sourceStart,
@@ -23272,11 +23341,11 @@ var VideoThumbnail = memo17(function VideoThumbnail2({
23272
23341
  }),
23273
23342
  [duration, priority, projectId, sessionEpoch, sourceRangeDuration, sourceStart, videoSrc]
23274
23343
  );
23275
- const posterRequest = useMemo32(
23344
+ const posterRequest = useMemo33(
23276
23345
  () => createVideoThumbnailRequest(requestProps, 1, false),
23277
23346
  [requestProps]
23278
23347
  );
23279
- const richRequest = useMemo32(
23348
+ const richRequest = useMemo33(
23280
23349
  () => createVideoThumbnailRequest(requestProps, requestFrameCount, true),
23281
23350
  [requestFrameCount, requestProps]
23282
23351
  );
@@ -23340,7 +23409,7 @@ var VideoThumbnail = memo17(function VideoThumbnail2({
23340
23409
  });
23341
23410
 
23342
23411
  // src/player/components/CompositionThumbnail.tsx
23343
- import { memo as memo18, useMemo as useMemo33 } from "react";
23412
+ import { memo as memo18, useMemo as useMemo34 } from "react";
23344
23413
  import { jsx as jsx66, jsxs as jsxs45 } from "react/jsx-runtime";
23345
23414
  var THUMBNAIL_URL_VERSION = "v3";
23346
23415
  var THUMBNAIL_SEEK_TIME_SECONDS = 3;
@@ -23419,7 +23488,7 @@ var CompositionThumbnail = memo18(function CompositionThumbnail2({
23419
23488
  origin: window.location.origin,
23420
23489
  contentRevision
23421
23490
  });
23422
- const request = useMemo33(
23491
+ const request = useMemo34(
23423
23492
  () => ({
23424
23493
  key: createThumbnailKey({ kind: "composition", url }),
23425
23494
  projectId,
@@ -26406,7 +26475,7 @@ function useTimelinePlayer({
26406
26475
  }
26407
26476
 
26408
26477
  // src/player/hooks/usePlayerHandle.ts
26409
- import { useCallback as useCallback43, useMemo as useMemo34 } from "react";
26478
+ import { useCallback as useCallback43, useMemo as useMemo35 } from "react";
26410
26479
 
26411
26480
  // src/components/nle/NLEContext.tsx
26412
26481
  import { useContext as useContext7, useState as useState29, useCallback as useCallback42, useRef as useRef48, useEffect as useEffect33 } from "react";
@@ -26784,7 +26853,7 @@ function usePlayerHandle() {
26784
26853
  const subscribeTime = useCallback43((listener2) => {
26785
26854
  return liveTime.subscribe(listener2);
26786
26855
  }, []);
26787
- const handleElements = useMemo34(() => elements.map(toHandleElement), [elements]);
26856
+ const handleElements = useMemo35(() => elements.map(toHandleElement), [elements]);
26788
26857
  return {
26789
26858
  ready,
26790
26859
  playing,
@@ -27697,7 +27766,7 @@ function CompositionBreadcrumb({ stack, onNavigate }) {
27697
27766
  import { useCallback as useCallback48, useRef as useRef53, useState as useState32 } from "react";
27698
27767
 
27699
27768
  // src/components/editor/previewReadOnlyContext.tsx
27700
- import { useContext as useContext8, useMemo as useMemo35 } from "react";
27769
+ import { useContext as useContext8, useMemo as useMemo36 } from "react";
27701
27770
  import { jsx as jsx72 } from "react/jsx-runtime";
27702
27771
  var DEFAULT_REASON = "Preview is read-only.";
27703
27772
  var PreviewReadOnlyContext = createStableContext("PreviewReadOnlyContext", {
@@ -27709,7 +27778,7 @@ function PreviewReadOnlyProvider({
27709
27778
  reason,
27710
27779
  children
27711
27780
  }) {
27712
- const value = useMemo35(() => ({ readOnly, reason: reason ?? DEFAULT_REASON }), [readOnly, reason]);
27781
+ const value = useMemo36(() => ({ readOnly, reason: reason ?? DEFAULT_REASON }), [readOnly, reason]);
27713
27782
  return /* @__PURE__ */ jsx72(PreviewReadOnlyContext, { value, children });
27714
27783
  }
27715
27784
  function usePreviewReadOnly() {
@@ -28457,7 +28526,7 @@ import {
28457
28526
  memo as memo21,
28458
28527
  useCallback as useCallback51,
28459
28528
  useEffect as useEffect38,
28460
- useMemo as useMemo37,
28529
+ useMemo as useMemo38,
28461
28530
  useRef as useRef58
28462
28531
  } from "react";
28463
28532
  import {
@@ -28480,7 +28549,7 @@ var useAudioMetersVisible = create5((set) => ({
28480
28549
  }));
28481
28550
 
28482
28551
  // src/utils/audioMeterMath.ts
28483
- import { useMemo as useMemo36 } from "react";
28552
+ import { useMemo as useMemo37 } from "react";
28484
28553
  import { audioDbToGain, audioGainToDb } from "@hyperframes/core/audio-gain";
28485
28554
  var METER_DB_MARKS = [0, -3, -6, -12, -24];
28486
28555
  var FLOOR_DB = -60;
@@ -28520,7 +28589,7 @@ function hasProjectAudio(elements) {
28520
28589
  }
28521
28590
  function useProjectHasAudio() {
28522
28591
  const elements = usePlayerStore((s) => s.elements);
28523
- return useMemo36(() => hasProjectAudio(elements), [elements]);
28592
+ return useMemo37(() => hasProjectAudio(elements), [elements]);
28524
28593
  }
28525
28594
 
28526
28595
  // src/components/nle/AudioMeterStrip.tsx
@@ -28531,7 +28600,7 @@ var RED_AT = markFraction(-3);
28531
28600
  function useStrips() {
28532
28601
  const elements = usePlayerStore((s) => s.elements);
28533
28602
  const masterVolume = usePlayerStore((s) => s.audioVolume);
28534
- return useMemo37(() => {
28603
+ return useMemo38(() => {
28535
28604
  const labels = /* @__PURE__ */ new Map();
28536
28605
  const volumes = /* @__PURE__ */ new Map();
28537
28606
  for (const el of elements) {
@@ -28878,7 +28947,8 @@ function TimelinePane({
28878
28947
  onCopyClip,
28879
28948
  onPasteClip,
28880
28949
  onDuplicateClip,
28881
- canPasteClip
28950
+ canPasteClip,
28951
+ clipMenuItems
28882
28952
  }) {
28883
28953
  const {
28884
28954
  seek,
@@ -28933,7 +29003,8 @@ function TimelinePane({
28933
29003
  onCopyClip,
28934
29004
  onPasteClip,
28935
29005
  onDuplicateClip,
28936
- canPasteClip
29006
+ canPasteClip,
29007
+ clipMenuItems
28937
29008
  }
28938
29009
  ) }),
28939
29010
  /* @__PURE__ */ jsx78(AudioMeterStrip, {})
@@ -29594,7 +29665,7 @@ var CaptionOverlay = memo22(function CaptionOverlay2({ iframeRef }) {
29594
29665
  });
29595
29666
 
29596
29667
  // src/components/editor/DomEditOverlay.tsx
29597
- import { memo as memo25, useEffect as useEffect49, useMemo as useMemo38, useRef as useRef69 } from "react";
29668
+ import { memo as memo25, useEffect as useEffect49, useMemo as useMemo39, useRef as useRef69 } from "react";
29598
29669
 
29599
29670
  // src/components/editor/marqueeCommit.ts
29600
29671
  import { useCallback as useCallback53, useEffect as useEffect40, useRef as useRef60, useState as useState36 } from "react";
@@ -30115,12 +30186,52 @@ function cropRectFromInsets(rect, insets, scaleX, scaleY) {
30115
30186
  height: Math.max(0, rect.height - (insets.top + insets.bottom) * sy)
30116
30187
  };
30117
30188
  }
30118
- function readElementCropInsets(element) {
30189
+ var cropLifts = /* @__PURE__ */ new WeakMap();
30190
+ function liftSelector(element) {
30191
+ const quote = (value) => `"${value.replace(/["\\]/g, "\\$&")}"`;
30192
+ const hfId = element.getAttribute("data-hf-id");
30193
+ if (hfId) return `[data-hf-id=${quote(hfId)}]`;
30194
+ return element.id ? `[id=${quote(element.id)}]` : null;
30195
+ }
30196
+ function liftElementCrop(element) {
30197
+ const selector = liftSelector(element);
30198
+ if (!selector || cropLifts.has(element)) return;
30199
+ const doc = element.ownerDocument;
30200
+ const rule = doc.createElement("style");
30201
+ rule.textContent = `${selector}{clip-path:none!important}`;
30202
+ (doc.head ?? doc.documentElement).append(rule);
30203
+ cropLifts.set(element, { rule });
30204
+ }
30205
+ function dropElementCropLift(element) {
30206
+ cropLifts.get(element)?.rule.remove();
30207
+ cropLifts.delete(element);
30208
+ }
30209
+ function isElementCropLifted(element) {
30210
+ return cropLifts.has(element);
30211
+ }
30212
+ var computedClipPath = (element) => element.ownerDocument.defaultView?.getComputedStyle(element).clipPath.trim() || "";
30213
+ function readElementClipPath(element) {
30119
30214
  const inline = element.style.getPropertyValue("clip-path").trim();
30120
- const value = inline || element.ownerDocument.defaultView?.getComputedStyle(element).clipPath.trim() || "";
30215
+ const lift = cropLifts.get(element);
30216
+ if (inline || !lift) return inline || computedClipPath(element);
30217
+ if (lift.sheetClip === void 0) {
30218
+ const { parentNode, nextSibling } = lift.rule;
30219
+ lift.rule.remove();
30220
+ lift.sheetClip = computedClipPath(element);
30221
+ parentNode?.insertBefore(lift.rule, nextSibling);
30222
+ }
30223
+ return lift.sheetClip;
30224
+ }
30225
+ function parseCropClipPath(value) {
30121
30226
  if (!value || value === "none") return { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
30122
30227
  return parseInsetClipPathSides(value);
30123
30228
  }
30229
+ function readElementCropInsets(element) {
30230
+ return parseCropClipPath(readElementClipPath(element));
30231
+ }
30232
+ function hasCropInsets(insets) {
30233
+ return insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
30234
+ }
30124
30235
  function clampInset(value, max) {
30125
30236
  if (!Number.isFinite(value)) return 0;
30126
30237
  return Math.min(Math.max(0, value), Math.max(0, max));
@@ -30162,7 +30273,7 @@ function resolveCropInsetFromMoveDrag(input) {
30162
30273
  return { left, right: totalX - left, top, bottom: totalY - top };
30163
30274
  }
30164
30275
  function hugRectForElement(rect, element) {
30165
- const insets = readElementCropInsets(element);
30276
+ const insets = isElementCropLifted(element) ? null : readElementCropInsets(element);
30166
30277
  if (!insets || insets.top <= 0 && insets.right <= 0 && insets.bottom <= 0 && insets.left <= 0)
30167
30278
  return rect;
30168
30279
  return cropRectFromInsets(rect, insets, rect.editScaleX, rect.editScaleY);
@@ -30900,9 +31011,6 @@ function pointerAngleDegrees(centerX, centerY, x, y) {
30900
31011
  function normalizeAngleDelta(delta) {
30901
31012
  return ((delta + 180) % 360 + 360) % 360 - 180;
30902
31013
  }
30903
- function roundAngle(angle) {
30904
- return Math.round(angle * 10) / 10;
30905
- }
30906
31014
  function resolveDomEditRotationGesture(input) {
30907
31015
  const startAngle = pointerAngleDegrees(input.centerX, input.centerY, input.startX, input.startY);
30908
31016
  const currentAngle = pointerAngleDegrees(
@@ -30914,7 +31022,7 @@ function resolveDomEditRotationGesture(input) {
30914
31022
  const delta = normalizeAngleDelta(currentAngle - startAngle);
30915
31023
  const angle = input.actualAngle + delta;
30916
31024
  return {
30917
- angle: input.snap ? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES : roundAngle(angle)
31025
+ angle: input.snap ? Math.round(angle / ROTATION_SNAP_DEGREES) * ROTATION_SNAP_DEGREES : roundTo3(angle)
30918
31026
  };
30919
31027
  }
30920
31028
  function hasDomEditRotationChanged(initialAngle, nextAngle) {
@@ -31273,9 +31381,6 @@ var STUDIO_ROTATION_TRANSFORM_ORIGIN = "center center";
31273
31381
  function finiteNumber2(value) {
31274
31382
  return typeof value === "number" && Number.isFinite(value) ? value : null;
31275
31383
  }
31276
- function roundRotationAngle(angle) {
31277
- return Math.round(angle * 10) / 10;
31278
- }
31279
31384
  function normalizeStudioFileChangePath(path) {
31280
31385
  return path.trim().replace(/\\/g, "/").replace(/^\.?\//, "");
31281
31386
  }
@@ -31479,8 +31584,8 @@ function prepareStudioPathOffsetBase(element, updateBase) {
31479
31584
  function writeStudioPathOffsetVars(element, offset, options = {}) {
31480
31585
  prepareStudioPathOffsetBase(element, options.updateBase ?? true);
31481
31586
  element.setAttribute(STUDIO_PATH_OFFSET_ATTR, "true");
31482
- element.style.setProperty(STUDIO_OFFSET_X_PROP, `${Math.round(offset.x)}px`);
31483
- element.style.setProperty(STUDIO_OFFSET_Y_PROP, `${Math.round(offset.y)}px`);
31587
+ element.style.setProperty(STUDIO_OFFSET_X_PROP, `${roundTo3(offset.x)}px`);
31588
+ element.style.setProperty(STUDIO_OFFSET_Y_PROP, `${roundTo3(offset.y)}px`);
31484
31589
  }
31485
31590
  function isIdentityAfterTranslateStrip(m) {
31486
31591
  return m.is2D && m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1;
@@ -31550,7 +31655,7 @@ function applyStudioPathOffsetDraft(element, offset) {
31550
31655
  if (applyStudioPathOffsetViaGsap(element, offset)) return;
31551
31656
  element.style.setProperty(
31552
31657
  "translate",
31553
- composeTranslateValue(element, `${Math.round(offset.x)}px`, `${Math.round(offset.y)}px`)
31658
+ composeTranslateValue(element, `${roundTo3(offset.x)}px`, `${roundTo3(offset.y)}px`)
31554
31659
  );
31555
31660
  stripGsapTranslateFromTransform(element);
31556
31661
  }
@@ -31560,7 +31665,7 @@ function readParentFlexBasisPixels(element, size) {
31560
31665
  const display2 = readStyleOrComputed(parent, "display").trim();
31561
31666
  if (display2 !== "flex" && display2 !== "inline-flex") return null;
31562
31667
  const direction = readStyleOrComputed(parent, "flex-direction").trim();
31563
- return Math.round(Math.max(1, direction.startsWith("column") ? size.height : size.width));
31668
+ return roundToLayoutPx(Math.max(1, direction.startsWith("column") ? size.height : size.width));
31564
31669
  }
31565
31670
  function restoreStaleStudioScaleResize(element) {
31566
31671
  if (!element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) return;
@@ -31623,14 +31728,14 @@ function writeStudioBoxSizeVars(element, size) {
31623
31728
  element.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, element.style.getPropertyValue("display"));
31624
31729
  }
31625
31730
  element.setAttribute(STUDIO_BOX_SIZE_ATTR, "true");
31626
- element.style.setProperty(STUDIO_WIDTH_PROP, `${Math.round(Math.max(1, size.width))}px`);
31627
- element.style.setProperty(STUDIO_HEIGHT_PROP, `${Math.round(Math.max(1, size.height))}px`);
31731
+ element.style.setProperty(STUDIO_WIDTH_PROP, `${roundToLayoutPx(Math.max(1, size.width))}px`);
31732
+ element.style.setProperty(STUDIO_HEIGHT_PROP, `${roundToLayoutPx(Math.max(1, size.height))}px`);
31628
31733
  }
31629
31734
  function applyStudioBoxSizeDimensions(element, size) {
31630
31735
  writeStudioBoxSizeVars(element, size);
31631
31736
  restoreStaleStudioScaleResize(element);
31632
- const width = Math.round(Math.max(1, size.width));
31633
- const height = Math.round(Math.max(1, size.height));
31737
+ const width = roundToLayoutPx(Math.max(1, size.width));
31738
+ const height = roundToLayoutPx(Math.max(1, size.height));
31634
31739
  element.style.setProperty("box-sizing", "border-box");
31635
31740
  element.style.setProperty("width", `${width}px`);
31636
31741
  element.style.setProperty("height", `${height}px`);
@@ -31653,7 +31758,17 @@ function applyStudioBoxSize(element, size) {
31653
31758
  promoteInlineForTransform(element);
31654
31759
  applyStudioBoxSizeDimensions(element, size);
31655
31760
  }
31761
+ var boxSizeDraftBases = /* @__PURE__ */ new WeakMap();
31762
+ function readStudioBoxSizeDraftBase(element) {
31763
+ return boxSizeDraftBases.get(element) ?? null;
31764
+ }
31765
+ function forgetStudioBoxSizeDraftBase(element) {
31766
+ boxSizeDraftBases.delete(element);
31767
+ }
31656
31768
  function applyStudioBoxSizeDraft(element, size) {
31769
+ if (!boxSizeDraftBases.has(element)) {
31770
+ boxSizeDraftBases.set(element, { width: element.offsetWidth, height: element.offsetHeight });
31771
+ }
31657
31772
  promoteInlineForTransform(element);
31658
31773
  applyStudioBoxSizeDimensions(element, size);
31659
31774
  }
@@ -31691,7 +31806,7 @@ function prepareStudioRotationBase(element, updateBase) {
31691
31806
  function writeStudioRotationVars(element, rotation, options = {}) {
31692
31807
  prepareStudioRotationBase(element, options.updateBase ?? true);
31693
31808
  element.setAttribute(STUDIO_ROTATION_ATTR, "true");
31694
- element.style.setProperty(STUDIO_ROTATION_PROP, `${roundRotationAngle(rotation.angle)}deg`);
31809
+ element.style.setProperty(STUDIO_ROTATION_PROP, `${roundTo3(rotation.angle)}deg`);
31695
31810
  element.style.setProperty("transform-origin", STUDIO_ROTATION_TRANSFORM_ORIGIN);
31696
31811
  }
31697
31812
  function applyStudioRotation(element, rotation) {
@@ -31709,7 +31824,7 @@ function applyStudioRotationDraft(element, rotation) {
31709
31824
  element.setAttribute(STUDIO_ROTATION_DRAFT_ATTR, "true");
31710
31825
  element.style.setProperty(
31711
31826
  "rotate",
31712
- composeStudioRotationValue(element, `${roundRotationAngle(rotation.angle)}deg`)
31827
+ composeStudioRotationValue(element, `${roundTo3(rotation.angle)}deg`)
31713
31828
  );
31714
31829
  }
31715
31830
 
@@ -31877,6 +31992,7 @@ function restoreStyleProperty(element, property, value) {
31877
31992
  else element.style.removeProperty(property);
31878
31993
  }
31879
31994
  function restoreStudioBoxSize(element, previous) {
31995
+ forgetStudioBoxSizeDraftBase(element);
31880
31996
  restoreStyleProperty(element, "width", previous.width);
31881
31997
  restoreStyleProperty(element, "height", previous.height);
31882
31998
  restoreStyleProperty(element, "min-width", previous.minWidth);
@@ -32006,6 +32122,7 @@ function clearStudioRotation(element) {
32006
32122
  element.removeAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR);
32007
32123
  }
32008
32124
  function clearStudioBoxSize(element) {
32125
+ forgetStudioBoxSizeDraftBase(element);
32009
32126
  if (element.hasAttribute(STUDIO_BOX_SIZE_ATTR) || styleUsesStudioSize(element.style.getPropertyValue("width")) || styleUsesStudioSize(element.style.getPropertyValue("height")) || element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR)) {
32010
32127
  restoreOriginalBoxSizeProperty(element, "width", STUDIO_ORIGINAL_WIDTH_ATTR);
32011
32128
  restoreOriginalBoxSizeProperty(element, "height", STUDIO_ORIGINAL_HEIGHT_ATTR);
@@ -32259,8 +32376,8 @@ function computeDraggedGsapPosition(element, studioOffset, fallbackBase) {
32259
32376
  const baseGsapX = Number.isFinite(parsedBaseX) ? parsedBaseX : fallbackBase.x;
32260
32377
  const baseGsapY = Number.isFinite(parsedBaseY) ? parsedBaseY : fallbackBase.y;
32261
32378
  return {
32262
- newX: Math.round(baseGsapX + adjX),
32263
- newY: Math.round(baseGsapY + adjY),
32379
+ newX: roundTo3(baseGsapX + adjX),
32380
+ newY: roundTo3(baseGsapY + adjY),
32264
32381
  baseGsapX,
32265
32382
  baseGsapY
32266
32383
  };
@@ -34257,6 +34374,340 @@ var SnapGuideOverlay = memo23(function SnapGuideOverlay2({
34257
34374
 
34258
34375
  // src/components/editor/DomEditCropHandles.tsx
34259
34376
  import { useEffect as useEffect44, useRef as useRef65, useState as useState40 } from "react";
34377
+
34378
+ // src/hooks/gsapRuntimeKeyframes.ts
34379
+ import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
34380
+ var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
34381
+ "ease",
34382
+ "duration",
34383
+ "delay",
34384
+ "stagger",
34385
+ "motionPath",
34386
+ "overwrite",
34387
+ "immediateRender",
34388
+ "onComplete",
34389
+ "onUpdate",
34390
+ "onStart",
34391
+ "keyframes"
34392
+ ]);
34393
+ function timelinesOf(iframe) {
34394
+ if (!iframe?.contentWindow) return null;
34395
+ try {
34396
+ return iframe.contentWindow.__timelines ?? null;
34397
+ } catch {
34398
+ return null;
34399
+ }
34400
+ }
34401
+ function isXY(p) {
34402
+ return !!p && typeof p.x === "number" && typeof p.y === "number";
34403
+ }
34404
+ function isZeroDurationSet(duration) {
34405
+ return !(duration > 0);
34406
+ }
34407
+ function coordsFromMotionPath(mp) {
34408
+ if (!mp || typeof mp !== "object") return null;
34409
+ const obj = mp;
34410
+ const pathVal = Array.isArray(mp) ? mp : obj.path;
34411
+ if (!Array.isArray(pathVal)) return null;
34412
+ const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
34413
+ if (coords.length < 2) return null;
34414
+ const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
34415
+ const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
34416
+ return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
34417
+ }
34418
+ function flatTweenKeyframes(vars) {
34419
+ const properties = {};
34420
+ for (const [k, v] of Object.entries(vars)) {
34421
+ if (FLAT_SKIP_KEYS.has(k)) continue;
34422
+ if (typeof v === "number") properties[k] = roundTo3(v);
34423
+ else if (typeof v === "string") properties[k] = v;
34424
+ }
34425
+ if (Object.keys(properties).length === 0) return null;
34426
+ return [
34427
+ { percentage: 0, properties },
34428
+ { percentage: 100, properties }
34429
+ ];
34430
+ }
34431
+ function readTween(vars) {
34432
+ if (vars.keyframes && typeof vars.keyframes === "object") {
34433
+ const parsed = parsePercentageKeyframes(vars.keyframes);
34434
+ if (parsed) return parsed;
34435
+ }
34436
+ const mp = coordsFromMotionPath(vars.motionPath);
34437
+ if (mp) {
34438
+ const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
34439
+ if (shape) {
34440
+ const n = shape.waypoints.length;
34441
+ const keyframes = shape.waypoints.map((wp, i) => ({
34442
+ percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
34443
+ properties: { x: wp.x, y: wp.y }
34444
+ }));
34445
+ return { keyframes, arcPath: shape.arcPath };
34446
+ }
34447
+ }
34448
+ const flat = flatTweenKeyframes(vars);
34449
+ return flat ? { keyframes: flat } : null;
34450
+ }
34451
+ function matchesElement(tween, el) {
34452
+ if (!tween.targets) return false;
34453
+ for (const t of tween.targets()) {
34454
+ if (t === el || el.id && t.id === el.id) return true;
34455
+ }
34456
+ return false;
34457
+ }
34458
+ function tweenTiming(tween) {
34459
+ const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
34460
+ const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
34461
+ return {
34462
+ start: Number.isFinite(rawStart) ? rawStart : 0,
34463
+ duration: Number.isFinite(rawDur) ? rawDur : 0
34464
+ };
34465
+ }
34466
+ function varsCarryChannel(vars, channels) {
34467
+ if (!vars) return false;
34468
+ for (const ch of channels) {
34469
+ if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
34470
+ }
34471
+ return false;
34472
+ }
34473
+ function keyframeVarsCarryChannel(vars, channels) {
34474
+ const kf = vars?.keyframes;
34475
+ if (!kf || typeof kf !== "object") return false;
34476
+ if (!Array.isArray(kf) && channels.some((ch) => Object.prototype.hasOwnProperty.call(kf, ch)))
34477
+ return true;
34478
+ const steps = Array.isArray(kf) ? kf : Object.values(kf);
34479
+ return steps.some(
34480
+ (step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
34481
+ );
34482
+ }
34483
+ function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
34484
+ const timelines = timelinesOf(iframe);
34485
+ if (!timelines) return null;
34486
+ let targetEl = null;
34487
+ try {
34488
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
34489
+ } catch {
34490
+ return null;
34491
+ }
34492
+ if (!targetEl) return null;
34493
+ const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
34494
+ const wantChannels = channels && channels.length > 0 ? channels : null;
34495
+ let first = null;
34496
+ let channelMatch = null;
34497
+ for (const tlId of tlIds) {
34498
+ const timeline = timelines[tlId];
34499
+ if (!timeline?.getChildren) continue;
34500
+ const now2 = typeof timeline.time === "function" ? timeline.time() : null;
34501
+ for (const tween of timeline.getChildren(true)) {
34502
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
34503
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
34504
+ const isSet = !(dur > 0);
34505
+ if (kind === "set" ? !isSet : isSet) continue;
34506
+ if (wantChannels) {
34507
+ const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
34508
+ if (carries) {
34509
+ if (channelMatch === null) channelMatch = { tween, timeline };
34510
+ } else if (first === null) {
34511
+ first = { tween, timeline };
34512
+ }
34513
+ continue;
34514
+ }
34515
+ if (first === null) first = { tween, timeline };
34516
+ if (kind === "keyframe" && now2 != null) {
34517
+ const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
34518
+ if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
34519
+ }
34520
+ }
34521
+ }
34522
+ return channelMatch ?? first;
34523
+ }
34524
+ function readCarriesChannel(read, channels) {
34525
+ return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
34526
+ }
34527
+ function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
34528
+ const timelines = timelinesOf(iframe);
34529
+ if (!timelines) return null;
34530
+ let targetEl = null;
34531
+ try {
34532
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
34533
+ } catch {
34534
+ return null;
34535
+ }
34536
+ if (!targetEl) return null;
34537
+ const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
34538
+ if (tlIds.length === 0) return null;
34539
+ let firstRead = null;
34540
+ for (const tlId of tlIds) {
34541
+ const timeline = timelines[tlId];
34542
+ if (!timeline?.getChildren) continue;
34543
+ const now2 = typeof timeline.time === "function" ? timeline.time() : null;
34544
+ for (const tween of timeline.getChildren(true)) {
34545
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
34546
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
34547
+ if (isZeroDurationSet(dur)) continue;
34548
+ const read = readTween(tween.vars);
34549
+ if (!read) continue;
34550
+ if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
34551
+ if (firstRead === null) firstRead = read;
34552
+ if (now2 != null) {
34553
+ const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
34554
+ if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
34555
+ }
34556
+ }
34557
+ }
34558
+ return firstRead;
34559
+ }
34560
+ function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
34561
+ let targetEl = null;
34562
+ try {
34563
+ targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
34564
+ } catch {
34565
+ return false;
34566
+ }
34567
+ return !!targetEl && hasNonHoldTween(timelinesOf(iframe), targetEl, channels, compositionId);
34568
+ }
34569
+ function elementHasNonHoldTween(el, channels) {
34570
+ const win = el.ownerDocument.defaultView;
34571
+ return hasNonHoldTween(win?.__timelines ?? null, el, channels);
34572
+ }
34573
+ function hasNonHoldTween(timelines, targetEl, channels, compositionId) {
34574
+ if (!timelines) return false;
34575
+ for (const tlId of compositionId ? [compositionId] : Object.keys(timelines)) {
34576
+ for (const tween of timelines[tlId]?.getChildren?.(true) ?? []) {
34577
+ if (!tween.vars || !matchesElement(tween, targetEl)) continue;
34578
+ const dur = typeof tween.duration === "function" ? tween.duration() : 0;
34579
+ if (isZeroDurationSet(dur)) continue;
34580
+ if (channels && keyframeVarsCarryChannel(tween.vars, channels)) return true;
34581
+ const read = readTween(tween.vars);
34582
+ if (read && (!channels || readCarriesChannel(read, channels))) return true;
34583
+ }
34584
+ }
34585
+ return false;
34586
+ }
34587
+ function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
34588
+ if (!clip || clip.duration <= 0) return keyframes;
34589
+ return keyframes.map((kf) => {
34590
+ const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
34591
+ return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
34592
+ });
34593
+ }
34594
+ function buildEntry(read, start, duration, clip) {
34595
+ return {
34596
+ keyframes: toClipRelative(read.keyframes, start, duration, clip),
34597
+ tweenStart: start,
34598
+ tweenDuration: duration,
34599
+ ...read.easeEach ? { easeEach: read.easeEach } : {},
34600
+ ...read.arcPath ? { arcPath: read.arcPath } : {}
34601
+ };
34602
+ }
34603
+ function addScanEntry(result, tween, clipById) {
34604
+ if (!tween.targets || !tween.vars) return;
34605
+ const { start, duration } = tweenTiming(tween);
34606
+ if (isZeroDurationSet(duration)) return;
34607
+ const read = readTween(tween.vars);
34608
+ if (!read) return;
34609
+ for (const target of tween.targets()) {
34610
+ const id = target.id;
34611
+ if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
34612
+ }
34613
+ }
34614
+ function scanAllRuntimeKeyframes(iframe, clipById) {
34615
+ const result = /* @__PURE__ */ new Map();
34616
+ const timelines = timelinesOf(iframe);
34617
+ if (!timelines) return result;
34618
+ for (const timeline of Object.values(timelines)) {
34619
+ if (!timeline?.getChildren) continue;
34620
+ for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
34621
+ }
34622
+ return result;
34623
+ }
34624
+
34625
+ // src/components/editor/cropResize.ts
34626
+ var boxOf = (element) => ({
34627
+ width: element.offsetWidth,
34628
+ height: element.offsetHeight
34629
+ });
34630
+ function cropFollows(element) {
34631
+ const clip = gsapAnimatesProperty(element, "clipPath");
34632
+ return {
34633
+ width: !clip && !elementHasNonHoldTween(element, ["width"]),
34634
+ height: !clip && !elementHasNonHoldTween(element, ["height"])
34635
+ };
34636
+ }
34637
+ function scaleCrop(crop, from, to, follows) {
34638
+ const sx = follows.width && from.width > 0 ? to.width / from.width : 1;
34639
+ const sy = follows.height && from.height > 0 ? to.height / from.height : 1;
34640
+ return {
34641
+ top: crop.top * sy,
34642
+ right: crop.right * sx,
34643
+ bottom: crop.bottom * sy,
34644
+ left: crop.left * sx,
34645
+ radius: crop.radius
34646
+ };
34647
+ }
34648
+ function readCropFollowingResize(element) {
34649
+ const crop = readElementCropInsets(element);
34650
+ const base2 = readStudioBoxSizeDraftBase(element);
34651
+ return crop && base2 ? scaleCrop(crop, base2, boxOf(element), cropFollows(element)) : crop;
34652
+ }
34653
+ function prepareCropResize(element) {
34654
+ const from = readStudioBoxSizeDraftBase(element) ?? boxOf(element);
34655
+ const before = element.style.getPropertyValue("clip-path");
34656
+ const priority = element.style.getPropertyPriority("clip-path");
34657
+ const follows = cropFollows(element);
34658
+ const crop = readElementCropInsets(element);
34659
+ return () => {
34660
+ forgetStudioBoxSizeDraftBase(element);
34661
+ const to = boxOf(element);
34662
+ const untouched = element.style.getPropertyValue("clip-path") === before;
34663
+ if (!crop || !hasCropInsets(crop) || !untouched || !(to.width > 0 && to.height > 0))
34664
+ return null;
34665
+ const clip = buildInsetClipPathSides(scaleCrop(crop, from, to, follows), crop.radius);
34666
+ if (clip === buildInsetClipPathSides(crop, crop.radius)) return null;
34667
+ element.style.setProperty("clip-path", clip, priority);
34668
+ return {
34669
+ patch: {
34670
+ type: "inline-style",
34671
+ property: "clip-path",
34672
+ value: priority ? `${clip} !important` : clip
34673
+ },
34674
+ revert: () => {
34675
+ if (before) element.style.setProperty("clip-path", before, priority);
34676
+ else element.style.removeProperty("clip-path");
34677
+ }
34678
+ };
34679
+ };
34680
+ }
34681
+ async function saveCropResize(stage, selection, commit, coalesceKey) {
34682
+ const crop = stage();
34683
+ if (!crop) return;
34684
+ try {
34685
+ await commit(selection, [crop.patch], {
34686
+ label: "Resize layer",
34687
+ coalesceKey,
34688
+ coalesceMs: Number.POSITIVE_INFINITY
34689
+ });
34690
+ } catch (error) {
34691
+ crop.revert();
34692
+ throw error;
34693
+ }
34694
+ }
34695
+ var sizeWriteCounter = 0;
34696
+ async function writeSizeWithCrop(selection, properties, mutation, commit, write) {
34697
+ if (!mutation || !("width" in properties || "height" in properties)) return write();
34698
+ const stage = prepareCropResize(selection.element);
34699
+ const coalesceKey = `size-write:${++sizeWriteCounter}`;
34700
+ const undoStep = { coalesceKey, coalesceMs: Number.POSITIVE_INFINITY };
34701
+ const keyed = (s, m, options) => mutation(s, m, { ...options, ...undoStep });
34702
+ if (mutation.batch) {
34703
+ const batch = mutation.batch;
34704
+ keyed.batch = (calls, options) => batch(calls, { ...options, ...undoStep });
34705
+ }
34706
+ await write(keyed);
34707
+ await saveCropResize(stage, selection, commit, coalesceKey);
34708
+ }
34709
+
34710
+ // src/components/editor/DomEditCropHandles.tsx
34260
34711
  import { Fragment as Fragment26, jsx as jsx84, jsxs as jsxs57 } from "react/jsx-runtime";
34261
34712
  var EDGE_HIT_THICKNESS = 12;
34262
34713
  var EDGE_HIT_LENGTH = 32;
@@ -34297,6 +34748,7 @@ function repositionHandleSize(rect) {
34297
34748
  return Math.min(REPOSITION_HANDLE_PX, Math.max(rect.width, rect.height) - RESIZE_HANDLE_HIT_PX);
34298
34749
  }
34299
34750
  var EDGES = ["top", "right", "bottom", "left"];
34751
+ var NO_CROP = { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
34300
34752
  function DomEditCropHandles({
34301
34753
  selection,
34302
34754
  overlayRect,
@@ -34307,38 +34759,28 @@ function DomEditCropHandles({
34307
34759
  const [hotEdge, setHotEdge] = useState40(null);
34308
34760
  const cropStateFor = (element) => {
34309
34761
  const parsed = readElementCropInsets(element);
34310
- const { radius, ...insets } = parsed ?? { top: 0, right: 0, bottom: 0, left: 0, radius: 0 };
34311
- return { element, croppable: parsed !== null, insets, radius };
34762
+ const { top, right, bottom, left } = parsed ?? NO_CROP;
34763
+ return { element, croppable: parsed !== null, insets: { top, right, bottom, left } };
34312
34764
  };
34313
34765
  const [state, setState] = useState40(() => cropStateFor(selection.element));
34314
34766
  if (state.element !== selection.element) {
34315
34767
  setState(cropStateFor(selection.element));
34316
34768
  }
34317
- const hasCrop = state.insets.top > 0 || state.insets.right > 0 || state.insets.bottom > 0 || state.insets.left > 0;
34318
- const liftedRef = useRef65(false);
34319
- const preLiftInlineClipRef = useRef65("");
34320
- const committedClipRef = useRef65(null);
34769
+ const committed = readCropFollowingResize(selection.element) ?? NO_CROP;
34770
+ const insets = dragging ? state.insets : committed;
34771
+ const hasCrop = hasCropInsets(insets);
34321
34772
  useEffect44(() => {
34322
34773
  const el = selection.element;
34323
34774
  if (readElementCropInsets(el) === null) return;
34324
- preLiftInlineClipRef.current = el.style.getPropertyValue("clip-path");
34325
- committedClipRef.current = null;
34326
- el.style.setProperty("clip-path", "none");
34327
- liftedRef.current = true;
34328
- return () => {
34329
- liftedRef.current = false;
34330
- const committed = committedClipRef.current;
34331
- const restore = committed !== null ? committed || null : preLiftInlineClipRef.current || null;
34332
- if (restore) el.style.setProperty("clip-path", restore);
34333
- else el.style.removeProperty("clip-path");
34334
- };
34775
+ liftElementCrop(el);
34776
+ return () => dropElementCropLift(el);
34335
34777
  }, [selection.element]);
34336
34778
  const frame = readElementCropFrame(selection.element, overlayRect);
34337
34779
  const width = frame.width / frame.scaleX;
34338
34780
  const height = frame.height / frame.scaleY;
34339
34781
  const cropRect = cropRectFromInsets(
34340
34782
  { left: 0, top: 0, width: frame.width, height: frame.height },
34341
- state.insets,
34783
+ insets,
34342
34784
  frame.scaleX,
34343
34785
  frame.scaleY
34344
34786
  );
@@ -34348,17 +34790,20 @@ function DomEditCropHandles({
34348
34790
  event.preventDefault();
34349
34791
  event.stopPropagation();
34350
34792
  event.currentTarget.setPointerCapture(event.pointerId);
34793
+ const pressed = readCropFollowingResize(selection.element) ?? NO_CROP;
34351
34794
  gestureRef.current = {
34352
34795
  edge,
34353
34796
  pointerId: event.pointerId,
34354
34797
  startX: event.clientX,
34355
34798
  startY: event.clientY,
34356
- startInsets: state.insets,
34357
- insets: state.insets,
34799
+ startInsets: pressed,
34800
+ insets: pressed,
34801
+ radius: pressed.radius,
34358
34802
  angleDeg: frame.angleDeg,
34359
34803
  scaleX: frame.scaleX,
34360
34804
  scaleY: frame.scaleY
34361
34805
  };
34806
+ setState((prev) => ({ ...prev, insets: pressed }));
34362
34807
  setDragging(true);
34363
34808
  };
34364
34809
  const updateCropGesture = (event) => {
@@ -34394,19 +34839,10 @@ function DomEditCropHandles({
34394
34839
  const finishCropGesture = (event) => {
34395
34840
  const gesture = endCropGesture(event);
34396
34841
  if (!gesture) return;
34397
- const el = selection.element;
34398
- const reLift = () => {
34399
- if (liftedRef.current) el.style.setProperty("clip-path", "none");
34400
- };
34401
- const { insets } = gesture;
34402
- const committedValue = buildInsetClipPathSides(insets, state.radius);
34403
- if (committedValue === buildInsetClipPathSides(gesture.startInsets, state.radius)) return;
34404
- const cropped = insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
34405
- const commit = onStyleCommit?.("clip-path", committedValue);
34406
- reLift();
34407
- void Promise.resolve(commit).then(() => {
34408
- committedClipRef.current = cropped ? committedValue : "";
34409
- }, reLift);
34842
+ const value = buildInsetClipPathSides(gesture.insets, gesture.radius);
34843
+ if (value === buildInsetClipPathSides(gesture.startInsets, gesture.radius)) return;
34844
+ const commit = onStyleCommit?.("clip-path", value);
34845
+ void Promise.resolve(commit).catch(() => void 0);
34410
34846
  };
34411
34847
  const cancelCropGesture = (event) => {
34412
34848
  const gesture = endCropGesture(event);
@@ -34600,7 +35036,6 @@ function DomEditGroupChrome({
34600
35036
  allowBodyDrag,
34601
35037
  groupCanMove,
34602
35038
  gestures,
34603
- onBoxMouseDown,
34604
35039
  onBoxClick
34605
35040
  }) {
34606
35041
  const canManipulate = allowCanvasMovement && !usePreviewReadOnly();
@@ -34635,7 +35070,6 @@ function DomEditGroupChrome({
34635
35070
  if (!canManipulate || !allowBodyDrag || e.shiftKey && !groupCanMove) return;
34636
35071
  gestures.startGroupDrag(e);
34637
35072
  },
34638
- onMouseDown: onBoxMouseDown,
34639
35073
  onClick: onBoxClick
34640
35074
  }
34641
35075
  )
@@ -34654,7 +35088,6 @@ function DomEditSelectionChrome({
34654
35088
  groupSelectionCount,
34655
35089
  gestures,
34656
35090
  onStyleCommit,
34657
- onBoxMouseDown,
34658
35091
  onBoxClick,
34659
35092
  inlineText
34660
35093
  }) {
@@ -34709,7 +35142,6 @@ function DomEditSelectionChrome({
34709
35142
  }
34710
35143
  if (!e.shiftKey) gestures.startBlockedMove(e, selection);
34711
35144
  },
34712
- onMouseDown: onBoxMouseDown,
34713
35145
  onClick: onBoxClick,
34714
35146
  children: cropOutlineInsetPx && /* @__PURE__ */ jsx86(
34715
35147
  "div",
@@ -34763,19 +35195,19 @@ function DomEditSelectionChrome({
34763
35195
  // src/hooks/useCropOverlay.ts
34764
35196
  function useCropOverlay(params) {
34765
35197
  const { selection, overlayRect } = params;
34766
- const cropInsets = selection ? readElementCropInsets(selection.element) : null;
34767
- const hasCropInsets = Boolean(
35198
+ const cropInsets = selection && !isElementCropLifted(selection.element) ? readElementCropInsets(selection.element) : null;
35199
+ const hasCropInsets2 = Boolean(
34768
35200
  cropInsets && (cropInsets.top > 0 || cropInsets.right > 0 || cropInsets.bottom > 0 || cropInsets.left > 0)
34769
35201
  );
34770
35202
  const sx = overlayRect && overlayRect.editScaleX > 0 ? overlayRect.editScaleX : 1;
34771
35203
  const sy = overlayRect && overlayRect.editScaleY > 0 ? overlayRect.editScaleY : 1;
34772
- const cropOutlineInsetPx = cropInsets && hasCropInsets ? {
35204
+ const cropOutlineInsetPx = cropInsets && hasCropInsets2 ? {
34773
35205
  top: cropInsets.top * sy,
34774
35206
  right: cropInsets.right * sx,
34775
35207
  bottom: cropInsets.bottom * sy,
34776
35208
  left: cropInsets.left * sx
34777
35209
  } : void 0;
34778
- return { hasCropInsets, cropOutlineInsetPx };
35210
+ return { hasCropInsets: hasCropInsets2, cropOutlineInsetPx };
34779
35211
  }
34780
35212
 
34781
35213
  // src/components/editor/domEditOverlayShape.ts
@@ -36163,8 +36595,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36163
36595
  const groupGestureRef = useRef69(null);
36164
36596
  const blockedMoveRef = useRef69(null);
36165
36597
  const suppressNextBoxClickRef = useRef69(false);
36166
- const suppressNextBoxMouseDownRef = useRef69(false);
36167
- const suppressNextOverlayMouseDownRef = useRef69(false);
36168
36598
  const snapGuidesRef = useRef69(null);
36169
36599
  const rafPausedRef = useRef69(false);
36170
36600
  const selectionRef = useRef69(selection);
@@ -36232,11 +36662,11 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36232
36662
  const compRect = useDomEditCompositionRect({ iframeRef, overlayRef });
36233
36663
  const compRectRef = useRef69(compRect);
36234
36664
  compRectRef.current = compRect;
36235
- const { hasCropInsets, cropOutlineInsetPx } = useCropOverlay({
36665
+ const { hasCropInsets: hasCropInsets2, cropOutlineInsetPx } = useCropOverlay({
36236
36666
  selection,
36237
36667
  overlayRect
36238
36668
  });
36239
- const boxClipPath = hasCropInsets ? void 0 : selectionShapeStyles.clipPath;
36669
+ const boxClipPath = hasCropInsets2 ? void 0 : selectionShapeStyles.clipPath;
36240
36670
  const boxChromeClass = resolveBoxChromeClass(Boolean(cropOutlineInsetPx), boxClipPath);
36241
36671
  const { offCanvasRects, offCanvasElementsRef } = useOffCanvasIndicators({
36242
36672
  iframeRef,
@@ -36295,11 +36725,11 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36295
36725
  selectionRef,
36296
36726
  gestures
36297
36727
  });
36298
- const selectionKey = useMemo38(() => {
36728
+ const selectionKey = useMemo39(() => {
36299
36729
  if (!selection) return "none";
36300
36730
  return `${selection.sourceFile}:${selection.id ?? selection.selector ?? selection.label}:${selection.selectorIndex ?? 0}`;
36301
36731
  }, [selection]);
36302
- const groupBounds = useMemo38(
36732
+ const groupBounds = useMemo39(
36303
36733
  () => resolveDomEditGroupOverlayRect(groupOverlayItems.map((item) => item.rect)),
36304
36734
  [groupOverlayItems]
36305
36735
  );
@@ -36307,24 +36737,11 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36307
36737
  const groupCanMove = hasGroupSelection && groupOverlayItems.length > 1 && groupOverlayItems.every((item) => item.selection.capabilities.canApplyManualOffset);
36308
36738
  const handleOverlayMouseDown = (event) => {
36309
36739
  if (!allowCanvasMovement) return;
36310
- if (suppressNextOverlayMouseDownRef.current) {
36311
- logSelect("mousedown-suppressed", { shift: event.shiftKey });
36312
- suppressNextOverlayMouseDownRef.current = false;
36313
- suppressNextBoxMouseDownRef.current = false;
36314
- suppressNextBoxClickRef.current = false;
36315
- event.preventDefault();
36316
- event.stopPropagation();
36317
- return;
36318
- }
36319
36740
  const target = event.target;
36320
36741
  const onBox = Boolean(target?.closest('[data-dom-edit-selection-box="true"]'));
36321
36742
  logSelect("mousedown", { shift: event.shiftKey, onBox });
36322
36743
  if (onBox) return;
36323
36744
  onCanvasMouseDown(event, { hoverSelection: hoverSelectionRef.current });
36324
- if (event.shiftKey) {
36325
- suppressNextBoxMouseDownRef.current = true;
36326
- suppressNextBoxClickRef.current = true;
36327
- }
36328
36745
  };
36329
36746
  const handleOverlayPointerDown = (event) => {
36330
36747
  if (!allowCanvasMovement || hostInput || event.button !== 0) return;
@@ -36342,8 +36759,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36342
36759
  if (!candidate) return;
36343
36760
  event.preventDefault();
36344
36761
  event.stopPropagation();
36345
- suppressNextOverlayMouseDownRef.current = true;
36346
- suppressNextBoxMouseDownRef.current = true;
36347
36762
  suppressNextBoxClickRef.current = true;
36348
36763
  onSelectionChangeRef.current(candidate, { additive: true });
36349
36764
  return;
@@ -36367,7 +36782,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36367
36782
  if (overlayRef.current) {
36368
36783
  event.preventDefault();
36369
36784
  event.stopPropagation();
36370
- suppressNextOverlayMouseDownRef.current = true;
36371
36785
  marquee.begin(event);
36372
36786
  return;
36373
36787
  }
@@ -36383,12 +36797,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36383
36797
  }
36384
36798
  onCanvasMouseDown(event, { hoverSelection: hoverSelectionRef.current });
36385
36799
  };
36386
- const suppressBoxMouseDown = (e) => {
36387
- if (!suppressNextBoxMouseDownRef.current) return;
36388
- suppressNextBoxMouseDownRef.current = false;
36389
- e.preventDefault();
36390
- e.stopPropagation();
36391
- };
36392
36800
  const { contextMenu, closeContextMenu, handleContextMenu } = useCanvasContextMenuState({
36393
36801
  selection,
36394
36802
  selectionRef,
@@ -36446,7 +36854,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36446
36854
  allowBodyDrag: bodyDrag,
36447
36855
  groupCanMove,
36448
36856
  gestures,
36449
- onBoxMouseDown: suppressBoxMouseDown,
36450
36857
  onBoxClick: handleBoxClick
36451
36858
  }
36452
36859
  ),
@@ -36466,7 +36873,6 @@ var DomEditOverlay = memo25(function DomEditOverlay2({
36466
36873
  groupSelectionCount: groupSelections.length,
36467
36874
  gestures,
36468
36875
  onStyleCommit: onStyleCommitRef.current,
36469
- onBoxMouseDown: suppressBoxMouseDown,
36470
36876
  onBoxClick: handleBoxClick
36471
36877
  }
36472
36878
  ),
@@ -37562,7 +37968,12 @@ async function commitCandidateEdit(edit, targetPath, originalContent, deps, opti
37562
37968
  });
37563
37969
  }
37564
37970
  if (refreshTarget) refreshCommittedEdit(edit.after, deps, options);
37565
- return { status: "committed", version: hashContent(edit.after) };
37971
+ return {
37972
+ status: "committed",
37973
+ version: hashContent(edit.after),
37974
+ before: originalContent,
37975
+ after: edit.after
37976
+ };
37566
37977
  }
37567
37978
  async function persistSdkCandidateMutation(live, targetPath, originalContent, deps, mutate, options, sourceSnapshot) {
37568
37979
  return serializeStudioFileMutation(
@@ -39840,6 +40251,10 @@ var GsapEditBlockedError = class extends Error {
39840
40251
  function assertGsapEditPersisted(outcome) {
39841
40252
  if (outcome.status === "blocked") throw new GsapEditBlockedError(outcome.reason, outcome.detail);
39842
40253
  }
40254
+ async function saveMove(outcome, saveOnElement) {
40255
+ if (outcome.status === "element-offset") return saveOnElement();
40256
+ assertGsapEditPersisted(outcome);
40257
+ }
39843
40258
  function assertGsapAnimationDirectlyEditable(animation) {
39844
40259
  const editability = editabilityForProvenance(animation.provenance);
39845
40260
  if (editability === "unroll") throw new GsapEditBlockedError("unroll-required");
@@ -40020,8 +40435,8 @@ async function commitStaticGsapRotation(selection, newRotation, selector, existi
40020
40435
  );
40021
40436
  }
40022
40437
  async function commitStaticGsapSize(selection, size, selector, existingSet, callbacks) {
40023
- const width = Math.round(size.width);
40024
- const height = Math.round(size.height);
40438
+ const width = roundToLayoutPx(size.width);
40439
+ const height = roundToLayoutPx(size.height);
40025
40440
  if (existingSet) {
40026
40441
  await callbacks.commitMutation(
40027
40442
  selection,
@@ -40049,7 +40464,7 @@ async function commitStaticGsapSize(selection, size, selector, existingSet, call
40049
40464
  );
40050
40465
  }
40051
40466
  function positiveOr(n, fallback) {
40052
- return Number.isFinite(n) && n > 0 ? Math.round(n) : fallback;
40467
+ return Number.isFinite(n) && n > 0 ? roundToLayoutPx(n) : fallback;
40053
40468
  }
40054
40469
  function resolvePriorSize(sizeSet, el, fallbackW, fallbackH) {
40055
40470
  if (sizeSet) {
@@ -40066,8 +40481,8 @@ async function commitKeyframedSizeFromResize(selection, size, selector, sizeSet,
40066
40481
  const ts = resolveTweenStart(animatedTween) ?? 0;
40067
40482
  const td = resolveTweenDuration(animatedTween);
40068
40483
  if (!(td > 0)) return false;
40069
- const newW = Math.round(size.width);
40070
- const newH = Math.round(size.height);
40484
+ const newW = roundToLayoutPx(size.width);
40485
+ const newH = roundToLayoutPx(size.height);
40071
40486
  const prior = resolvePriorSize(sizeSet, selection.element, newW, newH);
40072
40487
  const ct = usePlayerStore.getState().currentTime;
40073
40488
  const pct = Math.max(0, Math.min(100, Math.round((ct - ts) / td * 1e3) / 10));
@@ -40397,249 +40812,6 @@ function commitCreatePath(targetSelector, position, x, y, commit) {
40397
40812
 
40398
40813
  // src/components/editor/useMotionPathData.ts
40399
40814
  import { useEffect as useEffect52, useRef as useRef73, useState as useState45 } from "react";
40400
-
40401
- // src/hooks/gsapRuntimeKeyframes.ts
40402
- import { buildArcPath } from "@hyperframes/core/gsap-parser-acorn";
40403
- var FLAT_SKIP_KEYS = /* @__PURE__ */ new Set([
40404
- "ease",
40405
- "duration",
40406
- "delay",
40407
- "stagger",
40408
- "motionPath",
40409
- "overwrite",
40410
- "immediateRender",
40411
- "onComplete",
40412
- "onUpdate",
40413
- "onStart",
40414
- "keyframes"
40415
- ]);
40416
- function timelinesOf(iframe) {
40417
- if (!iframe?.contentWindow) return null;
40418
- try {
40419
- return iframe.contentWindow.__timelines ?? null;
40420
- } catch {
40421
- return null;
40422
- }
40423
- }
40424
- function isXY(p) {
40425
- return !!p && typeof p.x === "number" && typeof p.y === "number";
40426
- }
40427
- function isZeroDurationSet(duration) {
40428
- return !(duration > 0);
40429
- }
40430
- function coordsFromMotionPath(mp) {
40431
- if (!mp || typeof mp !== "object") return null;
40432
- const obj = mp;
40433
- const pathVal = Array.isArray(mp) ? mp : obj.path;
40434
- if (!Array.isArray(pathVal)) return null;
40435
- const coords = pathVal.filter(isXY).map((p) => ({ x: p.x, y: p.y }));
40436
- if (coords.length < 2) return null;
40437
- const curviness = typeof obj.curviness === "number" ? obj.curviness : 1;
40438
- const autoRotate = typeof obj.autoRotate === "number" ? obj.autoRotate : obj.autoRotate === true;
40439
- return { coords, curviness, autoRotate, isCubic: obj.type === "cubic" };
40440
- }
40441
- function flatTweenKeyframes(vars) {
40442
- const properties = {};
40443
- for (const [k, v] of Object.entries(vars)) {
40444
- if (FLAT_SKIP_KEYS.has(k)) continue;
40445
- if (typeof v === "number") properties[k] = roundTo3(v);
40446
- else if (typeof v === "string") properties[k] = v;
40447
- }
40448
- if (Object.keys(properties).length === 0) return null;
40449
- return [
40450
- { percentage: 0, properties },
40451
- { percentage: 100, properties }
40452
- ];
40453
- }
40454
- function readTween(vars) {
40455
- if (vars.keyframes && typeof vars.keyframes === "object") {
40456
- const parsed = parsePercentageKeyframes(vars.keyframes);
40457
- if (parsed) return parsed;
40458
- }
40459
- const mp = coordsFromMotionPath(vars.motionPath);
40460
- if (mp) {
40461
- const shape = buildArcPath(mp.coords, mp.curviness, mp.autoRotate, mp.isCubic);
40462
- if (shape) {
40463
- const n = shape.waypoints.length;
40464
- const keyframes = shape.waypoints.map((wp, i) => ({
40465
- percentage: n > 1 ? Math.round(i / (n - 1) * 100) : 0,
40466
- properties: { x: wp.x, y: wp.y }
40467
- }));
40468
- return { keyframes, arcPath: shape.arcPath };
40469
- }
40470
- }
40471
- const flat = flatTweenKeyframes(vars);
40472
- return flat ? { keyframes: flat } : null;
40473
- }
40474
- function matchesElement(tween, el) {
40475
- if (!tween.targets) return false;
40476
- for (const t of tween.targets()) {
40477
- if (t === el || el.id && t.id === el.id) return true;
40478
- }
40479
- return false;
40480
- }
40481
- function tweenTiming(tween) {
40482
- const rawStart = typeof tween.startTime === "function" ? tween.startTime() : 0;
40483
- const rawDur = typeof tween.duration === "function" ? tween.duration() : 0;
40484
- return {
40485
- start: Number.isFinite(rawStart) ? rawStart : 0,
40486
- duration: Number.isFinite(rawDur) ? rawDur : 0
40487
- };
40488
- }
40489
- function varsCarryChannel(vars, channels) {
40490
- if (!vars) return false;
40491
- for (const ch of channels) {
40492
- if (Object.prototype.hasOwnProperty.call(vars, ch)) return true;
40493
- }
40494
- return false;
40495
- }
40496
- function keyframeVarsCarryChannel(vars, channels) {
40497
- const kf = vars?.keyframes;
40498
- if (!kf || typeof kf !== "object") return false;
40499
- const steps = Array.isArray(kf) ? kf : Object.values(kf);
40500
- return steps.some(
40501
- (step) => step != null && typeof step === "object" && channels.some((ch) => Object.prototype.hasOwnProperty.call(step, ch))
40502
- );
40503
- }
40504
- function resolveRuntimeTween(iframe, selector, kind, compositionId, channels) {
40505
- const timelines = timelinesOf(iframe);
40506
- if (!timelines) return null;
40507
- let targetEl = null;
40508
- try {
40509
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40510
- } catch {
40511
- return null;
40512
- }
40513
- if (!targetEl) return null;
40514
- const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
40515
- const wantChannels = channels && channels.length > 0 ? channels : null;
40516
- let first = null;
40517
- let channelMatch = null;
40518
- for (const tlId of tlIds) {
40519
- const timeline = timelines[tlId];
40520
- if (!timeline?.getChildren) continue;
40521
- const now2 = typeof timeline.time === "function" ? timeline.time() : null;
40522
- for (const tween of timeline.getChildren(true)) {
40523
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40524
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40525
- const isSet = !(dur > 0);
40526
- if (kind === "set" ? !isSet : isSet) continue;
40527
- if (wantChannels) {
40528
- const carries = kind === "set" ? varsCarryChannel(tween.vars, wantChannels) : keyframeVarsCarryChannel(tween.vars, wantChannels);
40529
- if (carries) {
40530
- if (channelMatch === null) channelMatch = { tween, timeline };
40531
- } else if (first === null) {
40532
- first = { tween, timeline };
40533
- }
40534
- continue;
40535
- }
40536
- if (first === null) first = { tween, timeline };
40537
- if (kind === "keyframe" && now2 != null) {
40538
- const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
40539
- if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return { tween, timeline };
40540
- }
40541
- }
40542
- }
40543
- return channelMatch ?? first;
40544
- }
40545
- function readCarriesChannel(read, channels) {
40546
- return read.keyframes.some((kf) => channels.some((c) => kf.properties[c] != null));
40547
- }
40548
- function readRuntimeKeyframes(iframe, selector, compositionId, requireChannels) {
40549
- const timelines = timelinesOf(iframe);
40550
- if (!timelines) return null;
40551
- let targetEl = null;
40552
- try {
40553
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40554
- } catch {
40555
- return null;
40556
- }
40557
- if (!targetEl) return null;
40558
- const tlIds = compositionId ? [compositionId] : Object.keys(timelines).filter((k) => typeof timelines[k]?.getChildren === "function");
40559
- if (tlIds.length === 0) return null;
40560
- let firstRead = null;
40561
- for (const tlId of tlIds) {
40562
- const timeline = timelines[tlId];
40563
- if (!timeline?.getChildren) continue;
40564
- const now2 = typeof timeline.time === "function" ? timeline.time() : null;
40565
- for (const tween of timeline.getChildren(true)) {
40566
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40567
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40568
- if (isZeroDurationSet(dur)) continue;
40569
- const read = readTween(tween.vars);
40570
- if (!read) continue;
40571
- if (requireChannels && !readCarriesChannel(read, requireChannels)) continue;
40572
- if (firstRead === null) firstRead = read;
40573
- if (now2 != null) {
40574
- const start = typeof tween.startTime === "function" ? tween.startTime() : 0;
40575
- if (now2 >= start - 1e-3 && now2 <= start + dur + 1e-3) return read;
40576
- }
40577
- }
40578
- }
40579
- return firstRead;
40580
- }
40581
- function hasNonHoldTweenForElement(iframe, selector, compositionId, channels) {
40582
- const timelines = timelinesOf(iframe);
40583
- if (!timelines) return false;
40584
- const tlId = compositionId || Object.keys(timelines).find((k) => typeof timelines[k]?.getChildren === "function");
40585
- if (!tlId) return false;
40586
- const timeline = timelines[tlId];
40587
- if (!timeline?.getChildren) return false;
40588
- let targetEl = null;
40589
- try {
40590
- targetEl = iframe?.contentDocument?.querySelector(selector) ?? null;
40591
- } catch {
40592
- return false;
40593
- }
40594
- if (!targetEl) return false;
40595
- for (const tween of timeline.getChildren(true)) {
40596
- if (!tween.vars || !matchesElement(tween, targetEl)) continue;
40597
- const dur = typeof tween.duration === "function" ? tween.duration() : 0;
40598
- if (isZeroDurationSet(dur)) continue;
40599
- const read = readTween(tween.vars);
40600
- if (read && (!channels || readCarriesChannel(read, channels))) return true;
40601
- }
40602
- return false;
40603
- }
40604
- function toClipRelative(keyframes, tweenStart, tweenDuration, clip) {
40605
- if (!clip || clip.duration <= 0) return keyframes;
40606
- return keyframes.map((kf) => {
40607
- const abs = toAbsoluteTime(tweenStart, tweenDuration, kf.percentage);
40608
- return { ...kf, percentage: Math.round((abs - clip.start) / clip.duration * 1e5) / 1e3 };
40609
- });
40610
- }
40611
- function buildEntry(read, start, duration, clip) {
40612
- return {
40613
- keyframes: toClipRelative(read.keyframes, start, duration, clip),
40614
- tweenStart: start,
40615
- tweenDuration: duration,
40616
- ...read.easeEach ? { easeEach: read.easeEach } : {},
40617
- ...read.arcPath ? { arcPath: read.arcPath } : {}
40618
- };
40619
- }
40620
- function addScanEntry(result, tween, clipById) {
40621
- if (!tween.targets || !tween.vars) return;
40622
- const { start, duration } = tweenTiming(tween);
40623
- if (isZeroDurationSet(duration)) return;
40624
- const read = readTween(tween.vars);
40625
- if (!read) return;
40626
- for (const target of tween.targets()) {
40627
- const id = target.id;
40628
- if (id && !result.has(id)) result.set(id, buildEntry(read, start, duration, clipById?.get(id)));
40629
- }
40630
- }
40631
- function scanAllRuntimeKeyframes(iframe, clipById) {
40632
- const result = /* @__PURE__ */ new Map();
40633
- const timelines = timelinesOf(iframe);
40634
- if (!timelines) return result;
40635
- for (const timeline of Object.values(timelines)) {
40636
- if (!timeline?.getChildren) continue;
40637
- for (const tween of timeline.getChildren(true)) addScanEntry(result, tween, clipById);
40638
- }
40639
- return result;
40640
- }
40641
-
40642
- // src/components/editor/useMotionPathData.ts
40643
40815
  function transformTranslate(el) {
40644
40816
  const t = el.ownerDocument?.defaultView?.getComputedStyle(el).transform;
40645
40817
  if (!t || t === "none") return { x: 0, y: 0 };
@@ -41558,10 +41730,10 @@ function PreviewOverlays({
41558
41730
  }
41559
41731
 
41560
41732
  // src/components/nle/useTimelineEditCallbacks.ts
41561
- import { useCallback as useCallback65, useMemo as useMemo40 } from "react";
41733
+ import { useCallback as useCallback65, useMemo as useMemo41 } from "react";
41562
41734
 
41563
41735
  // src/hooks/useGsapTweenCache.ts
41564
- import { useEffect as useEffect56, useMemo as useMemo39, useRef as useRef76, useState as useState49, useCallback as useCallback64 } from "react";
41736
+ import { useEffect as useEffect56, useMemo as useMemo40, useRef as useRef76, useState as useState49, useCallback as useCallback64 } from "react";
41565
41737
 
41566
41738
  // src/hooks/gsapRuntimeReaders.ts
41567
41739
  import { classifyPropertyGroup as classifyPropertyGroup3 } from "@hyperframes/core/gsap-parser";
@@ -42437,7 +42609,7 @@ async function tryGsapRotationIntercept(selection, angle, animations, iframe, co
42437
42609
  if (!anim) {
42438
42610
  anim = workingAnimations.find((a) => animationWritesAnyProperty(a, ROTATION_CHANNEL_SET)) ?? null;
42439
42611
  }
42440
- const newRotation = Math.round(angle);
42612
+ const newRotation = roundTo3(angle);
42441
42613
  if (!anim || isInstantHold(anim)) {
42442
42614
  const existingSet = anim ?? findRotationSetAnimation(resolvedAnimations, selector, selection.element);
42443
42615
  await commitStaticGsapRotation(selection, newRotation, selector, existingSet, {
@@ -42557,7 +42729,6 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
42557
42729
  const [multipleTimelines, setMultipleTimelines] = useState49(false);
42558
42730
  const [unsupportedTimelinePattern, setUnsupportedTimelinePattern] = useState49(false);
42559
42731
  const lastFetchKeyRef = useRef76("");
42560
- const retryTimerRef = useRef76(null);
42561
42732
  const domClipChildrenKey = usePlayerStore(
42562
42733
  (s) => s.domClipChildren.map((c) => `${c.id}<${c.hostId}`).join("|")
42563
42734
  );
@@ -42566,10 +42737,6 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
42566
42737
  const fetchKey = `${projectId}:${sourceFile}:${version}:${targetKey}`;
42567
42738
  if (fetchKey === lastFetchKeyRef.current) return;
42568
42739
  lastFetchKeyRef.current = fetchKey;
42569
- if (retryTimerRef.current) {
42570
- clearTimeout(retryTimerRef.current);
42571
- retryTimerRef.current = null;
42572
- }
42573
42740
  if (!projectId) {
42574
42741
  setAllAnimations([]);
42575
42742
  setMultipleTimelines(false);
@@ -42590,29 +42757,14 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
42590
42757
  setAllAnimations(parsed.animations);
42591
42758
  setMultipleTimelines(parsed.multipleTimelines === true);
42592
42759
  setUnsupportedTimelinePattern(parsed.unsupportedTimelinePattern === true);
42593
- if (parsed.animations.length === 0 && targetKey) {
42594
- retryTimerRef.current = setTimeout(() => {
42595
- if (cancelled) return;
42596
- fetchParsedAnimations(projectId, sourceFile).then((retryParsed) => {
42597
- if (cancelled) return;
42598
- if (retryParsed && retryParsed.animations.length > 0) {
42599
- setAllAnimations(retryParsed.animations);
42600
- }
42601
- });
42602
- }, 800);
42603
- }
42604
42760
  });
42605
42761
  return () => {
42606
42762
  cancelled = true;
42607
- if (retryTimerRef.current) {
42608
- clearTimeout(retryTimerRef.current);
42609
- retryTimerRef.current = null;
42610
- }
42611
42763
  };
42612
42764
  }, [projectId, sourceFile, version, target?.id, target?.selector]);
42613
42765
  const targetId = target?.id ?? null;
42614
42766
  const targetSelector = target?.selector ?? null;
42615
- const rawAnimations = useMemo39(() => {
42767
+ const rawAnimations = useMemo40(() => {
42616
42768
  if (!targetId && !targetSelector) return [];
42617
42769
  const element = target ? findPreviewNode(iframeRef?.current?.contentDocument, target) : null;
42618
42770
  return getAnimationsForElement(
@@ -42621,7 +42773,7 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
42621
42773
  element
42622
42774
  );
42623
42775
  }, [allAnimations, target, version, iframeRef]);
42624
- const animations = useMemo39(() => {
42776
+ const animations = useMemo40(() => {
42625
42777
  const iframe = iframeRef?.current;
42626
42778
  let result = rawAnimations;
42627
42779
  if (iframe) {
@@ -42947,7 +43099,7 @@ function useTimelineEditCallbacks({
42947
43099
  },
42948
43100
  [handleGsapRemoveKeyframe]
42949
43101
  );
42950
- return useMemo40(
43102
+ return useMemo41(
42951
43103
  () => ({
42952
43104
  onMoveElement: handleTimelineElementMove,
42953
43105
  onMoveElements: handleTimelineElementsMove,
@@ -43254,7 +43406,7 @@ var CaptionTimeline = memo29(function CaptionTimeline2({
43254
43406
  });
43255
43407
 
43256
43408
  // src/hooks/useTimelineSelectionPreviewSync.ts
43257
- import { useEffect as useEffect57, useMemo as useMemo41, useRef as useRef77 } from "react";
43409
+ import { useEffect as useEffect57, useMemo as useMemo42, useRef as useRef77 } from "react";
43258
43410
  var MAX_UNRESOLVED_SYNC_RETRIES = 3;
43259
43411
  function orderSelectedIds(ids, anchor2) {
43260
43412
  const ordered = [...ids];
@@ -43301,7 +43453,7 @@ function useTimelineSelectionPreviewSync({
43301
43453
  applyMarqueeSelection,
43302
43454
  onSelectionNotFound
43303
43455
  }) {
43304
- const selectedIds = useMemo41(
43456
+ const selectedIds = useMemo42(
43305
43457
  () => orderSelectedIds(selectedElementIds, selectedElementId),
43306
43458
  [selectedElementId, selectedElementIds]
43307
43459
  );
@@ -43390,7 +43542,7 @@ function useTimelineSelectionPreviewSync({
43390
43542
  }
43391
43543
 
43392
43544
  // src/webmcp/StudioAgentTools.tsx
43393
- import { useCallback as useCallback67, useEffect as useEffect59, useMemo as useMemo42 } from "react";
43545
+ import { useCallback as useCallback67, useEffect as useEffect59, useMemo as useMemo43 } from "react";
43394
43546
 
43395
43547
  // src/webmcp/useStudioAgentTools.ts
43396
43548
  import { useEffect as useEffect58, useRef as useRef78 } from "react";
@@ -44713,7 +44865,7 @@ function StudioAgentTools() {
44713
44865
  editHistory,
44714
44866
  previewIframeRef
44715
44867
  ]);
44716
- const deps = useMemo42(
44868
+ const deps = useMemo43(
44717
44869
  () => ({
44718
44870
  getSnapshot,
44719
44871
  getPreviewDocument: () => previewIframeRef.current?.contentDocument ?? null,
@@ -45522,6 +45674,7 @@ function EditorShell({
45522
45674
  onPasteClip,
45523
45675
  onDuplicateClip,
45524
45676
  canPasteClip,
45677
+ clipMenuItems,
45525
45678
  setCompIdToSrc,
45526
45679
  setCompositionLoading,
45527
45680
  shouldShowMotionPath,
@@ -45609,6 +45762,7 @@ function EditorShell({
45609
45762
  onPasteClip,
45610
45763
  onDuplicateClip,
45611
45764
  canPasteClip,
45765
+ clipMenuItems,
45612
45766
  previewOverlay: /* @__PURE__ */ jsx102(
45613
45767
  PreviewOverlays,
45614
45768
  {
@@ -45643,7 +45797,8 @@ function EditorShellBody({
45643
45797
  onCopyClip,
45644
45798
  onPasteClip,
45645
45799
  onDuplicateClip,
45646
- canPasteClip
45800
+ canPasteClip,
45801
+ clipMenuItems
45647
45802
  }) {
45648
45803
  const { compositionStack, updateCompositionStack, containerRef } = useNLEContext();
45649
45804
  const seekCaptionTime = useCallback71((time) => {
@@ -45689,6 +45844,7 @@ function EditorShellBody({
45689
45844
  onPasteClip,
45690
45845
  onDuplicateClip,
45691
45846
  canPasteClip,
45847
+ clipMenuItems,
45692
45848
  onSelectTimelineElement,
45693
45849
  timelineFooter: captionEditMode ? /* @__PURE__ */ jsxs72("div", { className: "border-t border-neutral-800/30 shrink-0", style: { height: 60 }, children: [
45694
45850
  /* @__PURE__ */ jsx102("div", { className: "flex items-center gap-1.5 px-2 py-0.5", children: /* @__PURE__ */ jsx102("span", { className: "text-[9px] font-medium text-neutral-500 uppercase tracking-wider", children: "Captions" }) }),
@@ -45704,7 +45860,7 @@ function EditorShellBody({
45704
45860
  }
45705
45861
 
45706
45862
  // src/player/components/AudioWaveform.tsx
45707
- import { memo as memo30, useCallback as useCallback72, useEffect as useEffect61, useMemo as useMemo43, useRef as useRef80 } from "react";
45863
+ import { memo as memo30, useCallback as useCallback72, useEffect as useEffect61, useMemo as useMemo44, useRef as useRef80 } from "react";
45708
45864
 
45709
45865
  // src/player/components/audioWaveformPeaks.ts
45710
45866
  var LOUDNESS_OPACITY_FLOOR = 0.64;
@@ -45839,7 +45995,7 @@ var AudioWaveform = memo30(function AudioWaveform2({
45839
45995
  const canvasRef = useRef80(null);
45840
45996
  const observerRef = useRef80(null);
45841
45997
  const cacheKey = waveformUrl ?? audioUrl;
45842
- const request = useMemo43(
45998
+ const request = useMemo44(
45843
45999
  () => ({
45844
46000
  key: createThumbnailKey({ kind: "waveform", source: cacheKey }),
45845
46001
  projectId,
@@ -45946,7 +46102,7 @@ var AudioWaveform = memo30(function AudioWaveform2({
45946
46102
  });
45947
46103
 
45948
46104
  // src/player/components/ImageThumbnail.tsx
45949
- import { memo as memo31, useMemo as useMemo44 } from "react";
46105
+ import { memo as memo31, useMemo as useMemo45 } from "react";
45950
46106
 
45951
46107
  // src/player/lib/thumbnailImageDecoder.ts
45952
46108
  function thumbnailSource(source) {
@@ -46024,7 +46180,7 @@ var ImageThumbnail = memo31(function ImageThumbnail2({
46024
46180
  rich = false
46025
46181
  }) {
46026
46182
  const [container, setContainerRef] = useThumbnailStripSize();
46027
- const request = useMemo44(
46183
+ const request = useMemo45(
46028
46184
  () => ({
46029
46185
  key: createThumbnailKey({ kind: "image", source: imageSrc, rich: Number(rich) }),
46030
46186
  projectId,
@@ -46418,13 +46574,13 @@ var SourceEditor = memo32(function SourceEditor2({
46418
46574
  });
46419
46575
 
46420
46576
  // src/components/editor/PropertyPanel.tsx
46421
- import { memo as memo36, useMemo as useMemo60, useRef as useRef112, useState as useState94 } from "react";
46577
+ import { memo as memo36, useMemo as useMemo61, useRef as useRef112, useState as useState94 } from "react";
46422
46578
 
46423
46579
  // src/components/editor/InspectorHeaderActions.tsx
46424
46580
  import { Eye as Eye3, EyeSlash as EyeSlash2 } from "@phosphor-icons/react";
46425
46581
 
46426
46582
  // src/contexts/DesignPanelInputContext.tsx
46427
- import { useCallback as useCallback75, useContext as useContext11, useMemo as useMemo45 } from "react";
46583
+ import { useCallback as useCallback75, useContext as useContext11, useMemo as useMemo46 } from "react";
46428
46584
 
46429
46585
  // src/utils/designInputTracking.ts
46430
46586
  var COALESCE_WINDOW_MS = 600;
@@ -46467,7 +46623,7 @@ function DesignPanelInputProvider({
46467
46623
  children
46468
46624
  }) {
46469
46625
  const parent = useContext11(DesignPanelInputContext);
46470
- const value = useMemo45(
46626
+ const value = useMemo46(
46471
46627
  () => ({ ui: ui ?? parent.ui, section: section ?? parent.section }),
46472
46628
  [ui, section, parent.ui, parent.section]
46473
46629
  );
@@ -48084,7 +48240,7 @@ function MediaSection({
48084
48240
  import { isHfColorGradingActive as isHfColorGradingActive2 } from "@hyperframes/core/color-grading";
48085
48241
 
48086
48242
  // src/components/editor/propertyPanelColorGradingControls.tsx
48087
- import { useMemo as useMemo46, useRef as useRef86, useState as useState56 } from "react";
48243
+ import { useMemo as useMemo47, useRef as useRef86, useState as useState56 } from "react";
48088
48244
  import {
48089
48245
  HF_COLOR_GRADING_PRESETS,
48090
48246
  normalizeHfColorGrading
@@ -48528,7 +48684,7 @@ function ColorGradingControls({
48528
48684
  const [detailSettings, setDetailSettings] = useState56(null);
48529
48685
  const [lutImporting, setLutImporting] = useState56(false);
48530
48686
  const [lutImportError, setLutImportError] = useState56(null);
48531
- const lutAssets = useMemo46(
48687
+ const lutAssets = useMemo47(
48532
48688
  () => assets.filter((asset) => LUT_EXT.test(asset)).sort((a, b) => a.localeCompare(b)),
48533
48689
  [assets]
48534
48690
  );
@@ -48808,7 +48964,7 @@ function ColorGradingControls({
48808
48964
  }
48809
48965
 
48810
48966
  // src/components/editor/useColorGradingController.ts
48811
- import { useCallback as useCallback80, useEffect as useEffect69, useMemo as useMemo47, useRef as useRef88, useState as useState58 } from "react";
48967
+ import { useCallback as useCallback80, useEffect as useEffect69, useMemo as useMemo48, useRef as useRef88, useState as useState58 } from "react";
48812
48968
  import {
48813
48969
  HF_COLOR_GRADING_ATTR,
48814
48970
  hasHfColorGradingAuthoredValues,
@@ -49229,7 +49385,7 @@ function useColorGradingController({
49229
49385
  state: "pending",
49230
49386
  message: "Waiting for runtime"
49231
49387
  }));
49232
- const selectedAssetPath = useMemo47(
49388
+ const selectedAssetPath = useMemo48(
49233
49389
  () => selectedMediaAssetPath(element, projectId),
49234
49390
  [element, projectId]
49235
49391
  );
@@ -49276,7 +49432,7 @@ function useColorGradingController({
49276
49432
  trackStudioPendingEdit(onSetAttributeLive(COLOR_GRADING_DATA_KEY, value));
49277
49433
  };
49278
49434
  }, [identityKey]);
49279
- const target = useMemo47(
49435
+ const target = useMemo48(
49280
49436
  () => ({
49281
49437
  id: element.id ?? null,
49282
49438
  hfId: element.hfId ?? null,
@@ -50256,7 +50412,7 @@ function ColorField({
50256
50412
  }
50257
50413
 
50258
50414
  // src/components/editor/propertyPanelFont.tsx
50259
- import { useEffect as useEffect71, useMemo as useMemo48, useRef as useRef90, useState as useState60 } from "react";
50415
+ import { useEffect as useEffect71, useMemo as useMemo49, useRef as useRef90, useState as useState60 } from "react";
50260
50416
 
50261
50417
  // src/components/editor/fontAssets.ts
50262
50418
  var FONT_EXT_RE = /\.(eot|otf|ttc|ttf|woff2?)$/i;
@@ -50480,13 +50636,14 @@ function FontFamilyField({
50480
50636
  setImportingFonts(false);
50481
50637
  }
50482
50638
  };
50483
- const projectFontAssets = useMemo48(
50639
+ const projectFontAssets = useMemo49(
50484
50640
  () => uniqueFontOptions(
50485
50641
  importedFonts.map((f) => ({ family: f.family, source: "Imported" }))
50486
50642
  ),
50487
50643
  [importedFonts]
50488
50644
  );
50489
- const options = useMemo48(() => {
50645
+ const options = useMemo49(() => {
50646
+ if (!open) return [];
50490
50647
  const documentFonts = collectDocumentFontFamilies();
50491
50648
  const googleSet = new Set(googleFonts.map((f) => f.toLowerCase()));
50492
50649
  const taggedLocal = localFonts.map(
@@ -50505,8 +50662,8 @@ function FontFamilyField({
50505
50662
  ...DEFAULT_FONT_FAMILIES.map((f) => ({ family: f, source: "System" }))
50506
50663
  ])
50507
50664
  );
50508
- }, [currentFamily, googleFonts, localFonts, projectFontAssets]);
50509
- const filteredOptions = useMemo48(() => {
50665
+ }, [open, currentFamily, googleFonts, localFonts, projectFontAssets]);
50666
+ const filteredOptions = useMemo49(() => {
50510
50667
  const matches = options.filter((o) => fontMatchesQuery(o.family, query));
50511
50668
  if (query.trim()) return matches.slice(0, 200);
50512
50669
  const bySource = /* @__PURE__ */ new Map();
@@ -50742,7 +50899,7 @@ function FontFamilyField({
50742
50899
  import { useEffect as useEffect73 } from "react";
50743
50900
 
50744
50901
  // src/components/panels/VariablesBindElement.tsx
50745
- import { useMemo as useMemo49, useState as useState62 } from "react";
50902
+ import { useMemo as useMemo50, useState as useState62 } from "react";
50746
50903
 
50747
50904
  // src/components/panels/VariablesValueControls.tsx
50748
50905
  import { useState as useState61 } from "react";
@@ -51024,7 +51181,7 @@ function VariablesBindElement({
51024
51181
  }) {
51025
51182
  const [activeKey, setActiveKey] = useState62(null);
51026
51183
  const [idDraft, setIdDraft] = useState62("");
51027
- const actions = useMemo49(() => buildBindActions(selection, sdkSession), [selection, sdkSession]);
51184
+ const actions = useMemo50(() => buildBindActions(selection, sdkSession), [selection, sdkSession]);
51028
51185
  if (actions.length === 0) return null;
51029
51186
  const active = actions.find((a) => a.key === activeKey) ?? null;
51030
51187
  const trimmedId = sanitizeId(idDraft);
@@ -51135,7 +51292,7 @@ function uniqueId(base2, existing) {
51135
51292
  }
51136
51293
 
51137
51294
  // src/contexts/VariablePromoteContext.tsx
51138
- import { useContext as useContext12, useEffect as useEffect72, useMemo as useMemo50, useState as useState63 } from "react";
51295
+ import { useContext as useContext12, useEffect as useEffect72, useMemo as useMemo51, useState as useState63 } from "react";
51139
51296
  import { jsx as jsx118 } from "react/jsx-runtime";
51140
51297
  var VariablePromoteContext = createStableContext(
51141
51298
  "VariablePromoteContext",
@@ -51158,15 +51315,15 @@ function VariablePromoteProvider({
51158
51315
  if (!session) return;
51159
51316
  return session.on("change", () => setRevision((r) => r + 1));
51160
51317
  }, [session]);
51161
- const actions = useMemo50(() => {
51318
+ const actions = useMemo51(() => {
51162
51319
  if (!session || !selection) return [];
51163
51320
  return buildBindActions(selection, session);
51164
51321
  }, [session, selection, revision]);
51165
- const declarations = useMemo50(() => {
51322
+ const declarations = useMemo51(() => {
51166
51323
  if (!session) return [];
51167
51324
  return session.getVariableDeclarations();
51168
51325
  }, [session, revision]);
51169
- const value = useMemo50(
51326
+ const value = useMemo51(
51170
51327
  () => ({ session, selection, actions, declarations, persist: persist2, onPersistError }),
51171
51328
  [session, selection, actions, declarations, persist2, onPersistError]
51172
51329
  );
@@ -51175,7 +51332,7 @@ function VariablePromoteProvider({
51175
51332
  function useVariablePromoteChannel(channel) {
51176
51333
  const ctx3 = useContext12(VariablePromoteContext);
51177
51334
  const key2 = channel.kind === "style" ? `style:${channel.prop}` : channel.kind;
51178
- return useMemo50(() => {
51335
+ return useMemo51(() => {
51179
51336
  if (!ctx3 || !ctx3.session || !ctx3.selection?.hfId) return null;
51180
51337
  const { session, selection, actions, declarations, persist: persist2, onPersistError } = ctx3;
51181
51338
  const hfId = selection.hfId;
@@ -51610,7 +51767,7 @@ function parseColorString(value) {
51610
51767
  }
51611
51768
 
51612
51769
  // src/components/editor/propertyPanelFill.tsx
51613
- import { useMemo as useMemo51, useRef as useRef91, useState as useState64 } from "react";
51770
+ import { useMemo as useMemo52, useRef as useRef91, useState as useState64 } from "react";
51614
51771
 
51615
51772
  // src/components/icons/ReverseGradientIcon.tsx
51616
51773
  import { jsx as jsx120, jsxs as jsxs86 } from "react/jsx-runtime";
@@ -51685,8 +51842,8 @@ function ImageFillField({
51685
51842
  const fileInputRef = useRef91(null);
51686
51843
  const [uploading, setUploading] = useState64(false);
51687
51844
  const [uploadError, setUploadError] = useState64(null);
51688
- const imageAssets = useMemo51(() => assets.filter((a) => IMAGE_EXT.test(a)), [assets]);
51689
- const selectedAsset = useMemo51(
51845
+ const imageAssets = useMemo52(() => assets.filter((a) => IMAGE_EXT.test(a)), [assets]);
51846
+ const selectedAsset = useMemo52(
51690
51847
  () => resolveSelectedAsset(value, sourceFile, imageAssets),
51691
51848
  [imageAssets, sourceFile, value]
51692
51849
  );
@@ -53168,7 +53325,7 @@ import { useCallback as useCallback85, useState as useState69 } from "react";
53168
53325
  import { useShallow } from "zustand/react/shallow";
53169
53326
 
53170
53327
  // src/components/editor/AnimationCard.tsx
53171
- import { memo as memo34, useCallback as useCallback84, useEffect as useEffect76, useMemo as useMemo52, useRef as useRef94, useState as useState68 } from "react";
53328
+ import { memo as memo34, useCallback as useCallback84, useEffect as useEffect76, useMemo as useMemo53, useRef as useRef94, useState as useState68 } from "react";
53172
53329
  import { SUPPORTED_EASES, SUPPORTED_PROPS } from "@hyperframes/core/gsap-constants";
53173
53330
 
53174
53331
  // src/components/editor/gsapAnimationHelpers.ts
@@ -53709,21 +53866,21 @@ var AnimationCard = memo34(function AnimationCard2({
53709
53866
  segment?.scrollIntoView({ block: "nearest", behavior: "smooth" });
53710
53867
  pendingAutoScrollRef.current = false;
53711
53868
  }, [expandedKfPct]);
53712
- const usedProps = useMemo52(
53869
+ const usedProps = useMemo53(
53713
53870
  () => new Set(Object.keys(animation.properties)),
53714
53871
  [animation.properties]
53715
53872
  );
53716
- const availableProps = useMemo52(
53873
+ const availableProps = useMemo53(
53717
53874
  () => SUPPORTED_PROPS.filter(
53718
53875
  (p) => !usedProps.has(p) && (animation.method === "set" || !BOOLEAN_PROPS.has(p))
53719
53876
  ),
53720
53877
  [usedProps, animation.method]
53721
53878
  );
53722
- const usedFromProps = useMemo52(
53879
+ const usedFromProps = useMemo53(
53723
53880
  () => new Set(Object.keys(animation.fromProperties ?? {})),
53724
53881
  [animation.fromProperties]
53725
53882
  );
53726
- const availableFromProps = useMemo52(
53883
+ const availableFromProps = useMemo53(
53727
53884
  () => SUPPORTED_PROPS.filter((p) => !usedFromProps.has(p) && !BOOLEAN_PROPS.has(p)),
53728
53885
  [usedFromProps]
53729
53886
  );
@@ -53770,7 +53927,7 @@ var AnimationCard = memo34(function AnimationCard2({
53770
53927
  const easeName = (animation.keyframes ? animation.keyframes.easeEach : void 0) ?? animation.ease ?? "none";
53771
53928
  const easeLabel = easeName.startsWith("custom(") ? "Custom curve" : EASE_LABELS[easeName] ?? easeName;
53772
53929
  const endTime = typeof animation.position === "number" ? animation.position + (animation.duration ?? 0) : animation.position;
53773
- const summary = useMemo52(() => buildTweenSummary(animation), [animation]);
53930
+ const summary = useMemo53(() => buildTweenSummary(animation), [animation]);
53774
53931
  const setKeys = Object.keys(animation.properties);
53775
53932
  if (animation.method === "set" && // `every` is vacuously true on an empty bag — require at least one key so a
53776
53933
  // property-less set doesn't masquerade as a position row.
@@ -54918,7 +55075,7 @@ function PropertyPanel3dTransform({
54918
55075
  }
54919
55076
 
54920
55077
  // src/components/editor/PropertyPanelFlat.tsx
54921
- import { useEffect as useEffect97, useMemo as useMemo59, useRef as useRef111, useState as useState93 } from "react";
55078
+ import { useEffect as useEffect97, useMemo as useMemo60, useRef as useRef111, useState as useState93 } from "react";
54922
55079
  import { useShallow as useShallow2 } from "zustand/react/shallow";
54923
55080
 
54924
55081
  // src/components/editor/audioFxSummary.ts
@@ -57088,7 +57245,7 @@ function motionSectionLabel(args) {
57088
57245
  }
57089
57246
 
57090
57247
  // src/components/editor/propertyPanelAudioFxGroup.tsx
57091
- import { useMemo as useMemo55, useState as useState81 } from "react";
57248
+ import { useMemo as useMemo56, useState as useState81 } from "react";
57092
57249
  import {
57093
57250
  HF_AUDIO_FX_ATTR as HF_AUDIO_FX_ATTR6,
57094
57251
  HF_AUDIO_FX_DATA_KEY as HF_AUDIO_FX_DATA_KEY2,
@@ -57108,7 +57265,7 @@ import {
57108
57265
  } from "@hyperframes/core/audio-automation";
57109
57266
 
57110
57267
  // src/components/editor/propertyPanelFxSection.tsx
57111
- import { useCallback as useCallback88, useEffect as useEffect84, useMemo as useMemo54, useRef as useRef99, useState as useState79 } from "react";
57268
+ import { useCallback as useCallback88, useEffect as useEffect84, useMemo as useMemo55, useRef as useRef99, useState as useState79 } from "react";
57112
57269
 
57113
57270
  // src/components/editor/audioFxRevealTarget.ts
57114
57271
  import { parseAutomationTarget as parseAutomationTarget2 } from "@hyperframes/core/audio-automation";
@@ -58055,7 +58212,7 @@ function FxEqModule({
58055
58212
  import { getAudioFxPreset as getAudioFxPreset3 } from "@hyperframes/core/audio-fx-presets";
58056
58213
 
58057
58214
  // src/components/editor/propertyPanelFxNodeRow.tsx
58058
- import { useMemo as useMemo53, useState as useState78 } from "react";
58215
+ import { useMemo as useMemo54, useState as useState78 } from "react";
58059
58216
  import {
58060
58217
  defaultAudioFxParams as defaultAudioFxParams3,
58061
58218
  getAudioFxDef as getAudioFxDef3
@@ -58481,11 +58638,11 @@ function FxNodeRow({
58481
58638
  trackKind
58482
58639
  }) {
58483
58640
  const registryDef = getAudioFxDef3(node.type);
58484
- const def = useMemo53(() => registryDef ? plainDef(registryDef) : null, [registryDef]);
58641
+ const def = useMemo54(() => registryDef ? plainDef(registryDef) : null, [registryDef]);
58485
58642
  const primary = registryDef ? primaryParamOf(registryDef) : null;
58486
58643
  const profile = getAudioFxProfile2(node.type);
58487
- const derived = useMemo53(() => profileParam(node.type), [node.type]);
58488
- const onlyPrimary = useMemo53(
58644
+ const derived = useMemo54(() => profileParam(node.type), [node.type]);
58645
+ const onlyPrimary = useMemo54(
58489
58646
  () => def && primary ? { ...def, params: def.params.filter((p) => p.key === primary) } : def,
58490
58647
  [def, primary]
58491
58648
  );
@@ -59131,12 +59288,12 @@ function FxSection({
59131
59288
  [chain.nodes, mutate, onRemoveNodeAutomation, trackKind]
59132
59289
  );
59133
59290
  const [carveOpen, setCarveOpen] = useState79(true);
59134
- const carveNodes = useMemo54(() => chain.nodes.filter((n) => n.fromCarve), [chain.nodes]);
59135
- const handBuilt = useMemo54(
59291
+ const carveNodes = useMemo55(() => chain.nodes.filter((n) => n.fromCarve), [chain.nodes]);
59292
+ const handBuilt = useMemo55(
59136
59293
  () => chain.nodes.map((node, i) => ({ node, i })).filter(({ node }) => !node.fromCarve && !node.fromEq),
59137
59294
  [chain.nodes]
59138
59295
  );
59139
- const runs = useMemo54(() => {
59296
+ const runs = useMemo55(() => {
59140
59297
  const out = [];
59141
59298
  for (const item of handBuilt) {
59142
59299
  const preset = item.node.fromPreset;
@@ -59147,8 +59304,8 @@ function FxSection({
59147
59304
  return out;
59148
59305
  }, [handBuilt]);
59149
59306
  const [collapsedRuns, setCollapsedRuns] = useState79(/* @__PURE__ */ new Set());
59150
- const eqIds = useMemo54(() => audioEqIds(chain), [chain]);
59151
- const positions = useMemo54(() => {
59307
+ const eqIds = useMemo55(() => audioEqIds(chain), [chain]);
59308
+ const positions = useMemo55(() => {
59152
59309
  const map = /* @__PURE__ */ new Map();
59153
59310
  let at = (showCarve ? 1 : 0) + eqIds.length;
59154
59311
  for (const { i } of handBuilt) map.set(i, ++at);
@@ -59941,7 +60098,7 @@ function AudioFxGroup({
59941
60098
  const removeNodeAutomation = (nodeId) => removeNodesAutomation([nodeId]);
59942
60099
  const [analysing, setAnalysing] = useState81(false);
59943
60100
  const storeElements = usePlayerStore((s) => s.elements);
59944
- const signalPath = useMemo55(() => {
60101
+ const signalPath = useMemo56(() => {
59945
60102
  const doc = element.element?.ownerDocument;
59946
60103
  return audioFxSignalPath(
59947
60104
  element.tagName?.toLowerCase(),
@@ -60609,7 +60766,7 @@ function createGsapLivePreview(iframeRef) {
60609
60766
  }
60610
60767
 
60611
60768
  // src/components/editor/propertyPanelFlatColorGradingSection.tsx
60612
- import { useEffect as useEffect94, useMemo as useMemo57, useRef as useRef110, useState as useState89 } from "react";
60769
+ import { useEffect as useEffect94, useMemo as useMemo58, useRef as useRef110, useState as useState89 } from "react";
60613
60770
  import {
60614
60771
  HF_COLOR_GRADING_GRADE_PRESETS,
60615
60772
  normalizeHfColorGrading as normalizeHfColorGrading5,
@@ -60644,7 +60801,7 @@ function presetPreviewHandlers({
60644
60801
  import { useState as useState85 } from "react";
60645
60802
 
60646
60803
  // src/components/editor/propertyPanelColorCurveGraph.tsx
60647
- import { useMemo as useMemo56, useRef as useRef103 } from "react";
60804
+ import { useMemo as useMemo57, useRef as useRef103 } from "react";
60648
60805
  import {
60649
60806
  compileHfColorCurve,
60650
60807
  compileHfHueCurve,
@@ -60832,8 +60989,8 @@ function CurveGraph({
60832
60989
  onCancel
60833
60990
  }) {
60834
60991
  const pointerRef = useRef103(null);
60835
- const samples = useMemo56(() => samplesFor(points, tab), [points, tab]);
60836
- const path = useMemo56(() => curvePath(samples, tab), [samples, tab]);
60992
+ const samples = useMemo57(() => samplesFor(points, tab), [points, tab]);
60993
+ const path = useMemo57(() => curvePath(samples, tab), [samples, tab]);
60837
60994
  const selectRelativePoint = (offset) => {
60838
60995
  if (points.length === 0) return;
60839
60996
  const current = selectedIndex ?? (offset > 0 ? -1 : 0);
@@ -62630,14 +62787,14 @@ function FlatColorGradingSection({
62630
62787
  const lutInputRef = useRef110(null);
62631
62788
  const [lutOpen, setLutOpen] = useState89(false);
62632
62789
  const [detailSettingsOpen, setDetailSettingsOpen] = useState89(null);
62633
- const lutAssets = useMemo57(
62790
+ const lutAssets = useMemo58(
62634
62791
  () => assets.filter((asset) => LUT_EXT.test(asset)).sort((a, b) => a.localeCompare(b)),
62635
62792
  [assets]
62636
62793
  );
62637
62794
  const lut = grading.lut;
62638
62795
  const selectedLutName = lut?.src ? lut.src.split("/").pop() ?? lut.src : null;
62639
- const resolvedGrading = useMemo57(() => resolveColorGrading(grading), [grading]);
62640
- const secondaryInputGrading = useMemo57(
62796
+ const resolvedGrading = useMemo58(() => resolveColorGrading(grading), [grading]);
62797
+ const secondaryInputGrading = useMemo58(
62641
62798
  () => resolveColorGrading({
62642
62799
  intensity: 1,
62643
62800
  adjust: resolvedGrading.adjust,
@@ -62655,7 +62812,7 @@ function FlatColorGradingSection({
62655
62812
  ]
62656
62813
  );
62657
62814
  const actions = createColorGradingActions(grading, onCommitColorGrading);
62658
- const scopesRefreshKey = useMemo57(() => serializeHfColorGrading2(grading), [grading]);
62815
+ const scopesRefreshKey = useMemo58(() => serializeHfColorGrading2(grading), [grading]);
62659
62816
  useEffect94(() => {
62660
62817
  if (presetPreviews.status === "idle") onRequestPresetPreviews();
62661
62818
  }, [onRequestPresetPreviews, presetPreviews.status]);
@@ -63785,7 +63942,7 @@ function FlatEffectsSection({
63785
63942
  import { useState as useState92 } from "react";
63786
63943
 
63787
63944
  // src/hooks/useBlockCatalog.ts
63788
- import { useState as useState91, useEffect as useEffect96, useMemo as useMemo58 } from "react";
63945
+ import { useState as useState91, useEffect as useEffect96, useMemo as useMemo59 } from "react";
63789
63946
 
63790
63947
  // src/utils/blockCategories.ts
63791
63948
  import {
@@ -63852,7 +64009,7 @@ function useBlockCatalog() {
63852
64009
  cancelled = true;
63853
64010
  };
63854
64011
  }, []);
63855
- const filteredBlocks = useMemo58(() => {
64012
+ const filteredBlocks = useMemo59(() => {
63856
64013
  let result = blocks;
63857
64014
  if (category) {
63858
64015
  result = result.filter((b) => b.category === category);
@@ -64168,7 +64325,7 @@ function PropertyPanelFlat({
64168
64325
  currentTime,
64169
64326
  onSetAttributeQuiet ?? onSetAttributeLive
64170
64327
  );
64171
- const audioGroupLabel = useMemo59(() => {
64328
+ const audioGroupLabel = useMemo60(() => {
64172
64329
  const doc = element.element?.ownerDocument;
64173
64330
  const id = element.id;
64174
64331
  if (!doc || !id) return void 0;
@@ -64729,12 +64886,12 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
64729
64886
  const cacheEntry = usePlayerStore((s) => s.keyframeCache.get(cacheElementKey));
64730
64887
  const iframeRef = previewIframeRef ?? { current: null };
64731
64888
  const gsapAnimIdForMemo = element ? gsapAnimations?.find((a) => a.keyframes)?.id ?? gsapAnimations?.[0]?.id ?? null : null;
64732
- const gsapRuntimeValues = useMemo60(
64889
+ const gsapRuntimeValues = useMemo61(
64733
64890
  () => element ? readGsapRuntimeValuesForPanel(gsapAnimIdForMemo, gsapAnimations, element, iframeRef) : null,
64734
64891
  // eslint-disable-next-line react-hooks/exhaustive-deps -- iframeRef is stable; currentTime drives re-reads during playback
64735
64892
  [gsapAnimIdForMemo, gsapAnimations, element, currentTime]
64736
64893
  );
64737
- const gsapBorderRadius = useMemo60(
64894
+ const gsapBorderRadius = useMemo61(
64738
64895
  () => element ? readGsapBorderRadiusForPanel(gsapRuntimeValues, gsapAnimations, element, iframeRef) : null,
64739
64896
  // eslint-disable-next-line react-hooks/exhaustive-deps
64740
64897
  [gsapRuntimeValues, gsapAnimations, element, currentTime]
@@ -65122,11 +65279,11 @@ var PropertyPanel = memo36(function PropertyPanel2(props) {
65122
65279
  });
65123
65280
 
65124
65281
  // src/components/editor/FileTree.tsx
65125
- import { memo as memo38, useState as useState96, useCallback as useCallback90, useMemo as useMemo62, useRef as useRef114 } from "react";
65282
+ import { memo as memo38, useState as useState96, useCallback as useCallback90, useMemo as useMemo63, useRef as useRef114 } from "react";
65126
65283
  import { Plus as Plus2, FolderSimplePlus as FolderSimplePlus2 } from "@phosphor-icons/react";
65127
65284
 
65128
65285
  // src/components/editor/FileTreeNodes.tsx
65129
- import { memo as memo37, useState as useState95, useCallback as useCallback89, useMemo as useMemo61, useRef as useRef113, useEffect as useEffect98 } from "react";
65286
+ import { memo as memo37, useState as useState95, useCallback as useCallback89, useMemo as useMemo62, useRef as useRef113, useEffect as useEffect98 } from "react";
65130
65287
  import {
65131
65288
  PencilSimple,
65132
65289
  Copy,
@@ -65609,7 +65766,7 @@ var TreeFolder = memo37(function TreeFolder2({
65609
65766
  }) {
65610
65767
  const [isOpen, setIsOpen] = useState95(defaultOpen);
65611
65768
  const toggle = useCallback89(() => setIsOpen((v) => !v), []);
65612
- const children = useMemo61(() => sortChildren(node.children), [node.children]);
65769
+ const children = useMemo62(() => sortChildren(node.children), [node.children]);
65613
65770
  const Chevron = isOpen ? ChevronDown : ChevronRight;
65614
65771
  const isDragOver = dragOverFolder === node.fullPath;
65615
65772
  const isRenaming = inlineInput?.mode === "rename" && inlineInput.originalPath === node.fullPath;
@@ -65740,8 +65897,8 @@ var FileTree = memo38(function FileTree2({
65740
65897
  onImportFiles,
65741
65898
  lintFindingsByFile
65742
65899
  }) {
65743
- const tree = useMemo62(() => buildTree(files), [files]);
65744
- const children = useMemo62(() => sortChildren(tree.children), [tree]);
65900
+ const tree = useMemo63(() => buildTree(files), [files]);
65901
+ const children = useMemo63(() => sortChildren(tree.children), [tree]);
65745
65902
  const [contextMenu, setContextMenu] = useState96(null);
65746
65903
  const [inlineInput, setInlineInput] = useState96(null);
65747
65904
  const [deleteTarget, setDeleteTarget] = useState96(null);
@@ -66008,7 +66165,7 @@ var FileTree = memo38(function FileTree2({
66008
66165
  });
66009
66166
 
66010
66167
  // src/App.tsx
66011
- import { useState as useState149, useCallback as useCallback185, useRef as useRef181, useMemo as useMemo80, useLayoutEffect as useLayoutEffect13 } from "react";
66168
+ import { useState as useState149, useCallback as useCallback185, useRef as useRef181, useMemo as useMemo81, useLayoutEffect as useLayoutEffect13 } from "react";
66012
66169
 
66013
66170
  // src/hooks/useRightPanelIntents.ts
66014
66171
  import { useCallback as useCallback91, useEffect as useEffect99, useRef as useRef115 } from "react";
@@ -66059,7 +66216,7 @@ function useRightPanelIntent() {
66059
66216
  }
66060
66217
 
66061
66218
  // src/components/renders/useRenderQueue.ts
66062
- import { useState as useState98, useEffect as useEffect101, useCallback as useCallback93, useRef as useRef117, useMemo as useMemo63 } from "react";
66219
+ import { useState as useState98, useEffect as useEffect101, useCallback as useCallback93, useRef as useRef117, useMemo as useMemo64 } from "react";
66063
66220
 
66064
66221
  // src/components/renders/serverError.ts
66065
66222
  async function readServerError(res) {
@@ -66582,7 +66739,7 @@ function useRenderQueue(projectId, activeCompPathRef) {
66582
66739
  };
66583
66740
  }, [projectId]);
66584
66741
  const isRendering = jobs.some((j) => j.status === "rendering");
66585
- return useMemo63(
66742
+ return useMemo64(
66586
66743
  () => ({
66587
66744
  jobs,
66588
66745
  isRendering,
@@ -67868,7 +68025,7 @@ function useCaptionSync(projectId) {
67868
68025
  }
67869
68026
 
67870
68027
  // src/hooks/usePersistentEditHistory.ts
67871
- import { useCallback as useCallback97, useEffect as useEffect105, useMemo as useMemo64, useRef as useRef124, useState as useState104 } from "react";
68028
+ import { useCallback as useCallback97, useEffect as useEffect105, useMemo as useMemo65, useRef as useRef124, useState as useState104 } from "react";
67872
68029
  var EMPTY3 = { entries: [], back: null, forward: null };
67873
68030
  var DEFAULT_COALESCE_MS = 300;
67874
68031
  function historyUrl(projectId, path = "") {
@@ -67992,7 +68149,7 @@ function usePersistentEditHistory({ projectId }) {
67992
68149
  );
67993
68150
  const undo = useCallback97((callbacks) => step("undo", callbacks), [step]);
67994
68151
  const redo = useCallback97((callbacks) => step("redo", callbacks), [step]);
67995
- const state = useMemo64(() => {
68152
+ const state = useMemo65(() => {
67996
68153
  const backAt = Math.max(view.back?.endedAt ?? 0, heldClaimRef.current?.at ?? 0);
67997
68154
  const redoAt = redoneAt(view);
67998
68155
  return {
@@ -68072,7 +68229,7 @@ function usePanelLayout(initialState2) {
68072
68229
  import { useState as useState106, useCallback as useCallback102, useRef as useRef128 } from "react";
68073
68230
 
68074
68231
  // src/hooks/useFileTree.ts
68075
- import { useState as useState105, useCallback as useCallback99, useEffect as useEffect107, useMemo as useMemo65, useRef as useRef126 } from "react";
68232
+ import { useState as useState105, useCallback as useCallback99, useEffect as useEffect107, useMemo as useMemo66, useRef as useRef126 } from "react";
68076
68233
  var EMPTY_FILE_LIST = [];
68077
68234
  function useFileTree({ projectId, projectIdRef }) {
68078
68235
  const [fetched, setFetched] = useState105(null);
@@ -68136,11 +68293,11 @@ function useFileTree({ projectId, projectIdRef }) {
68136
68293
  );
68137
68294
  }
68138
68295
  }, [projectIdRef]);
68139
- const assets = useMemo65(
68296
+ const assets = useMemo66(
68140
68297
  () => fileTree.filter((f) => !f.endsWith(".html") && !f.endsWith(".md") && !f.endsWith(".json")),
68141
68298
  [fileTree]
68142
68299
  );
68143
- const fontAssets = useMemo65(
68300
+ const fontAssets = useMemo66(
68144
68301
  () => (projectId ? assets : []).filter((asset) => FONT_EXT.test(asset)).map((asset) => ({
68145
68302
  family: fontFamilyFromAssetPath(asset),
68146
68303
  path: asset,
@@ -68284,9 +68441,9 @@ function useEditorSave({
68284
68441
  }
68285
68442
 
68286
68443
  // src/hooks/useProjectFileWriter.ts
68287
- import { useCallback as useCallback101, useMemo as useMemo66 } from "react";
68444
+ import { useCallback as useCallback101, useMemo as useMemo67 } from "react";
68288
68445
  function useProjectFileWriter({ projectId }) {
68289
- const fileVersionScope = useMemo66(
68446
+ const fileVersionScope = useMemo67(
68290
68447
  () => ({ projectId, versions: /* @__PURE__ */ new Map() }),
68291
68448
  [projectId]
68292
68449
  );
@@ -68391,7 +68548,7 @@ function useProjectFileWriter({ projectId }) {
68391
68548
  },
68392
68549
  [fileVersions, projectId]
68393
68550
  );
68394
- return useMemo66(
68551
+ return useMemo67(
68395
68552
  () => ({
68396
68553
  fileVersions,
68397
68554
  readProjectFile,
@@ -70977,7 +71134,7 @@ function useTimelineGroupEditing({
70977
71134
  async (input) => {
70978
71135
  const sharedPath = allChangesSharePath(input.changes, activeCompPath);
70979
71136
  const canUseSdk = !input.needsExtension && sharedPath !== null && input.eligible && input.sdkChanges.every((change) => change !== null);
70980
- if (!canUseSdk) return false;
71137
+ if (!canUseSdk) return null;
70981
71138
  const result = await sdkTimingBatchPersist(
70982
71139
  input.sdkChanges.filter((change) => change !== null),
70983
71140
  sharedPath,
@@ -70997,7 +71154,7 @@ function useTimelineGroupEditing({
70997
71154
  skipRefresh: true
70998
71155
  }
70999
71156
  );
71000
- return cutoverCommittedOrThrow(result);
71157
+ return cutoverCommittedOrThrow(result) ? { sdkGsap: sdkTimingGsapSync(result) } : null;
71001
71158
  },
71002
71159
  [
71003
71160
  activeCompPath,
@@ -71034,7 +71191,7 @@ function useTimelineGroupEditing({
71034
71191
  const label = options?.label ?? "Move timeline clips";
71035
71192
  return enqueueGroupOperation(label, async (projectId) => {
71036
71193
  await options?.beforeTiming;
71037
- const handledBySdk = await trySdkBatchPersist({
71194
+ const sdk = await trySdkBatchPersist({
71038
71195
  changes,
71039
71196
  sdkChanges: toSdkTimingChanges(changes, (change) => ({
71040
71197
  start: toAuthoredStart(change.element, change.start)
@@ -71045,7 +71202,7 @@ function useTimelineGroupEditing({
71045
71202
  coalesceKey,
71046
71203
  coalesceMs
71047
71204
  });
71048
- if (!handledBySdk) {
71205
+ if (!sdk) {
71049
71206
  await persistServerBatch(
71050
71207
  projectId,
71051
71208
  label,
@@ -71076,6 +71233,7 @@ function useTimelineGroupEditing({
71076
71233
  writeProjectFile,
71077
71234
  activeCompPath,
71078
71235
  changes,
71236
+ sdkGsap: sdk?.sdkGsap,
71079
71237
  resolveChangePath: (element) => targetPathFor(element, activeCompPath),
71080
71238
  mutateChange: (change, changePath) => {
71081
71239
  const delta = change.start - change.element.start;
@@ -71135,7 +71293,7 @@ function useTimelineGroupEditing({
71135
71293
  const coalesceMs = options?.coalesceMs;
71136
71294
  return enqueueGroupOperation("Resize timeline clips", async (projectId) => {
71137
71295
  await options?.beforeTiming;
71138
- const handledBySdk = await trySdkBatchPersist({
71296
+ const sdk = await trySdkBatchPersist({
71139
71297
  changes,
71140
71298
  sdkChanges: toSdkTimingChanges(changes, (change) => ({
71141
71299
  start: toAuthoredStart(change.element, change.start),
@@ -71147,7 +71305,7 @@ function useTimelineGroupEditing({
71147
71305
  coalesceKey,
71148
71306
  coalesceMs
71149
71307
  });
71150
- if (!handledBySdk) {
71308
+ if (!sdk) {
71151
71309
  await persistServerBatch(
71152
71310
  projectId,
71153
71311
  "Resize timeline clips",
@@ -71175,6 +71333,7 @@ function useTimelineGroupEditing({
71175
71333
  writeProjectFile,
71176
71334
  activeCompPath,
71177
71335
  changes,
71336
+ sdkGsap: sdk?.sdkGsap,
71178
71337
  resolveChangePath: (element) => targetPathFor(element, activeCompPath),
71179
71338
  mutateChange: (change, changePath) => {
71180
71339
  const domId = change.element.domId;
@@ -71379,10 +71538,8 @@ function useTimelineEditing({
71379
71538
  );
71380
71539
  };
71381
71540
  const coalesceKey = `timeline-move:${element.hfId ?? element.id}`;
71382
- const finishMoveGsapSync = () => (
71383
- // Every timing writer converges the same GSAP positions after its
71384
- // durable clip-start commit. The SDK owns the attribute write; this
71385
- // sync owns only the dependent animation rewrite and preview refresh.
71541
+ const finishMoveGsapSync = (sdkGsap) => (
71542
+ // One GSAP sync per edit: the SDK commit's own (sdkGsap), else the server rewrite here.
71386
71543
  finishClipTimingFallback({
71387
71544
  iframe: previewIframeRef.current,
71388
71545
  reloadPreview,
@@ -71393,11 +71550,12 @@ function useTimelineEditing({
71393
71550
  coalesceKey,
71394
71551
  recordEdit,
71395
71552
  writeProjectFile,
71396
- edit: { kind: "shift", delta: updates.start - element.start }
71553
+ edit: { kind: "shift", delta: updates.start - element.start },
71554
+ sdkGsap
71397
71555
  }).finally(() => invalidateGsapCache?.())
71398
71556
  );
71399
71557
  const moveFallback = () => enqueueEdit(element, "Move timeline clip", buildMovePatches, coalesceKey).then(
71400
- finishMoveGsapSync
71558
+ () => finishMoveGsapSync()
71401
71559
  );
71402
71560
  return reorderDone.then(() => {
71403
71561
  if (sdkSession && element.hfId && !needsExtension && !trackChanged) {
@@ -71419,7 +71577,7 @@ function useTimelineEditing({
71419
71577
  { label: "Move timeline clip", coalesceKey, skipRefresh: true }
71420
71578
  ).then((result) => {
71421
71579
  if (!cutoverCommittedOrThrow(result)) return moveFallback();
71422
- return finishMoveGsapSync();
71580
+ return finishMoveGsapSync(sdkTimingGsapSync(result));
71423
71581
  });
71424
71582
  }
71425
71583
  return moveFallback();
@@ -71468,7 +71626,7 @@ function useTimelineEditing({
71468
71626
  };
71469
71627
  const hasPbsAdjustment = updates.playbackStart != null || updates.start !== element.start && element.playbackStart != null;
71470
71628
  const coalesceKey = `timeline-resize:${element.hfId ?? element.id}`;
71471
- const finishResizeGsapSync = () => finishClipTimingFallback({
71629
+ const finishResizeGsapSync = (sdkGsap) => finishClipTimingFallback({
71472
71630
  iframe: previewIframeRef.current,
71473
71631
  reloadPreview,
71474
71632
  projectId: projectIdRef.current,
@@ -71482,10 +71640,11 @@ function useTimelineEditing({
71482
71640
  kind: "scale",
71483
71641
  from: { start: toCompositionTime(element, element.start), duration: element.duration },
71484
71642
  to: { start: toCompositionTime(element, updates.start), duration: updates.duration }
71485
- }
71643
+ },
71644
+ sdkGsap
71486
71645
  }).finally(() => invalidateGsapCache?.());
71487
71646
  const resizeFallback = () => enqueueEdit(element, "Resize timeline clip", buildResizePatches, coalesceKey).then(
71488
- finishResizeGsapSync
71647
+ () => finishResizeGsapSync()
71489
71648
  );
71490
71649
  const persistDone = sdkSession && element.hfId && !hasPbsAdjustment && !needsExtension ? sdkTimingPersist(
71491
71650
  element.hfId,
@@ -71505,7 +71664,7 @@ function useTimelineEditing({
71505
71664
  { label: "Resize timeline clip", coalesceKey, skipRefresh: true }
71506
71665
  ).then((result) => {
71507
71666
  if (!cutoverCommittedOrThrow(result)) return resizeFallback();
71508
- return finishResizeGsapSync();
71667
+ return finishResizeGsapSync(sdkTimingGsapSync(result));
71509
71668
  }) : resizeFallback();
71510
71669
  return persistDone.catch((error) => {
71511
71670
  rollbackDuration();
@@ -73926,8 +74085,8 @@ function readDragStart(el, axis) {
73926
74085
  function applyElementPositionOffset(el, gestureOffset) {
73927
74086
  const cs = el.ownerDocument.defaultView?.getComputedStyle(el);
73928
74087
  if (!cs) return "anchored";
73929
- const dx = Math.round(gestureOffset.x - readDragStart(el, "x"));
73930
- const dy = Math.round(gestureOffset.y - readDragStart(el, "y"));
74088
+ const dx = roundTo3(gestureOffset.x - readDragStart(el, "x"));
74089
+ const dy = roundTo3(gestureOffset.y - readDragStart(el, "y"));
73931
74090
  const lifted = el.getAttribute(LAYER_REVEAL_PRIOR_POSITION_ATTR) === "static";
73932
74091
  const isStatic = lifted || !/^(relative|absolute|fixed|sticky)$/.test(cs.position);
73933
74092
  if (!isStatic && (cs.left.endsWith("%") || cs.top.endsWith("%"))) return "percent";
@@ -73936,8 +74095,8 @@ function applyElementPositionOffset(el, gestureOffset) {
73936
74095
  const previous = { position: el.style.position, left: el.style.left, top: el.style.top };
73937
74096
  const before = layoutBox(el);
73938
74097
  if (isStatic) el.style.position = "relative";
73939
- el.style.left = `${Math.round(baseLeft) + dx}px`;
73940
- el.style.top = `${Math.round(baseTop) + dy}px`;
74098
+ el.style.left = `${roundTo3(baseLeft + dx)}px`;
74099
+ el.style.top = `${roundTo3(baseTop + dy)}px`;
73941
74100
  const after = layoutBox(el);
73942
74101
  const shiftedExactly = Math.abs(after.left - before.left - dx) <= 1 && Math.abs(after.top - before.top - dy) <= 1 && Math.abs(after.width - before.width) <= 1 && Math.abs(after.height - before.height) <= 1;
73943
74102
  if (!shiftedExactly) {
@@ -74239,8 +74398,11 @@ function useDomGeometryCommits({
74239
74398
  if (gsapFallback) return gsapFallback;
74240
74399
  const beforeSize = captureStudioBoxSize(selection.element);
74241
74400
  const beforeOffset = offset ? captureStudioPathOffset(selection.element) : null;
74401
+ const stageCrop = prepareCropResize(selection.element);
74242
74402
  applyStudioBoxSize(selection.element, next);
74403
+ const crop = stageCrop();
74243
74404
  const patches = buildBoxSizePatches(selection.element);
74405
+ if (crop) patches.push(crop.patch);
74244
74406
  if (offset) {
74245
74407
  applyStudioPathOffset(selection.element, offset);
74246
74408
  patches.push(...buildPathOffsetPatches(selection.element));
@@ -74251,6 +74413,7 @@ function useDomGeometryCommits({
74251
74413
  }).catch((error) => {
74252
74414
  restoreStudioBoxSize(selection.element, beforeSize);
74253
74415
  if (beforeOffset) restoreStudioPathOffset(selection.element, beforeOffset);
74416
+ crop?.revert();
74254
74417
  throw error;
74255
74418
  });
74256
74419
  },
@@ -74521,6 +74684,7 @@ function useDomEditCommits({
74521
74684
  handleDomAddTextField,
74522
74685
  handleDomRemoveTextField,
74523
74686
  stageElementPositionOffset,
74687
+ commitPositionPatchToHtml,
74524
74688
  handleDomPathOffsetCommit,
74525
74689
  handleDomBoxSizeCommit,
74526
74690
  handleDomRotationCommit,
@@ -74664,7 +74828,7 @@ function useGroupCommits(params) {
74664
74828
  }
74665
74829
 
74666
74830
  // src/hooks/useGsapScriptCommits.ts
74667
- import { useCallback as useCallback132, useMemo as useMemo67, useRef as useRef144 } from "react";
74831
+ import { useCallback as useCallback132, useMemo as useMemo68, useRef as useRef144 } from "react";
74668
74832
  import { findUnsafeMutationValues } from "@hyperframes/core/studio-api/finite-mutation";
74669
74833
 
74670
74834
  // src/hooks/gsapRuntimePatch.ts
@@ -75987,7 +76151,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
75987
76151
  const { instantPatch: _instantPatch, ...batchOptions } = options;
75988
76152
  await finalizeSuccessfulMutation(pid, compositionPath, last.selection, last.mutation, targetPath, result, instantPatches.length > 0 ? { ...batchOptions, instantPatches } : batchOptions);
75989
76153
  }, [showToast, finalizeSuccessfulMutation]);
75990
- const commitMutation = useMemo67(() => {
76154
+ const commitMutation = useMemo68(() => {
75991
76155
  const serializeFile = (file, task) => {
75992
76156
  if (writeProjectFile) {
75993
76157
  return serializeStudioFileMutation(writeProjectFile, file, task);
@@ -76052,7 +76216,7 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
76052
76216
  },
76053
76217
  [activeProjectId]
76054
76218
  );
76055
- const sdkDeps = useMemo67(
76219
+ const sdkDeps = useMemo68(
76056
76220
  () => writeProjectFile ? {
76057
76221
  editHistory: { recordEdit: editHistory.recordEdit },
76058
76222
  writeProjectFile,
@@ -76199,8 +76363,6 @@ function useDomEditPreviewSync({
76199
76363
 
76200
76364
  // src/hooks/useGsapAnimationFetchFallback.ts
76201
76365
  import { useCallback as useCallback133 } from "react";
76202
- var COLD_PARSE_RETRIES = 5;
76203
- var COLD_PARSE_DELAY_MS = 120;
76204
76366
  var FETCH_ERROR_RETRIES = 1;
76205
76367
  var FETCH_ERROR_DELAY_MS = 120;
76206
76368
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -76209,32 +76371,22 @@ function gsapSourceFileForSelection(selection) {
76209
76371
  }
76210
76372
  function selectElementAnimationsOrRetry(parsed, target, element) {
76211
76373
  if (!parsed) return { kind: "fetch-error" };
76212
- if (parsed.animations.length === 0) return { kind: "cold" };
76213
76374
  return {
76214
76375
  kind: "resolved",
76215
76376
  animations: getAnimationsForElement(parsed.animations, target, element)
76216
76377
  };
76217
76378
  }
76218
76379
  async function fetchElementAnimationsWithRetry(projectId, gsapSourceFile, target, element, failOnFetchError, fresh) {
76219
- let coldAttempts = 0;
76220
- let errorAttempts = 0;
76221
- for (; ; ) {
76380
+ for (let errorAttempts = 0; ; errorAttempts++) {
76222
76381
  const parsed = await fetchParsedAnimations(projectId, gsapSourceFile, { fresh });
76223
76382
  fresh = false;
76224
76383
  const outcome = selectElementAnimationsOrRetry(parsed, target, element);
76225
76384
  if (outcome.kind === "resolved") return outcome.animations;
76226
- if (outcome.kind === "fetch-error") {
76227
- if (errorAttempts >= FETCH_ERROR_RETRIES) {
76228
- if (failOnFetchError) throw new Error("GSAP animation ownership could not be verified");
76229
- return [];
76230
- }
76231
- errorAttempts++;
76232
- await delay(FETCH_ERROR_DELAY_MS);
76233
- continue;
76385
+ if (errorAttempts >= FETCH_ERROR_RETRIES) {
76386
+ if (failOnFetchError) throw new Error("GSAP animation ownership could not be verified");
76387
+ return [];
76234
76388
  }
76235
- if (coldAttempts >= COLD_PARSE_RETRIES) return [];
76236
- coldAttempts++;
76237
- await delay(COLD_PARSE_DELAY_MS);
76389
+ await delay(FETCH_ERROR_DELAY_MS);
76238
76390
  }
76239
76391
  }
76240
76392
  function useGsapAnimationFetchFallback(projectId) {
@@ -76887,8 +77039,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
76887
77039
  }
76888
77040
  } else {
76889
77041
  resizeProps = {
76890
- width: Math.round(size.width),
76891
- height: Math.round(size.height)
77042
+ width: roundToLayoutPx(size.width),
77043
+ height: roundToLayoutPx(size.height)
76892
77044
  };
76893
77045
  }
76894
77046
  const finalizeScaleResizeCommit = async () => {
@@ -76914,8 +77066,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
76914
77066
  return true;
76915
77067
  }
76916
77068
  const corrected = {
76917
- x: Math.round(base2.x + residual.x),
76918
- y: Math.round(base2.y + residual.y)
77069
+ x: roundTo3(base2.x + residual.x),
77070
+ y: roundTo3(base2.y + residual.y)
76919
77071
  };
76920
77072
  logResize("scale-finalize", {
76921
77073
  dropPoint: scaleDraftDropPoint,
@@ -77304,8 +77456,8 @@ function useAnimatedPropertyCommit(deps) {
77304
77456
  const commitAnimatedProperties = useCallback137(
77305
77457
  // This is the single routing boundary for set, keyframe, whole-tween, and first-group writes.
77306
77458
  // fallow-ignore-next-line complexity
77307
- async (selection, props) => {
77308
- if (!gsapCommitMutation) return;
77459
+ async (selection, props, commit = gsapCommitMutation) => {
77460
+ if (!commit) return;
77309
77461
  const propEntries = Object.entries(props);
77310
77462
  if (propEntries.length === 0) return;
77311
77463
  const primaryProp = propEntries[0][0];
@@ -77342,7 +77494,7 @@ function useAnimatedPropertyCommit(deps) {
77342
77494
  ),
77343
77495
  pct,
77344
77496
  iframe,
77345
- { commitMutation: gsapCommitMutation },
77497
+ { commitMutation: commit },
77346
77498
  `Edit ${primaryProp} (whole animation)`
77347
77499
  );
77348
77500
  return;
@@ -77355,18 +77507,12 @@ function useAnimatedPropertyCommit(deps) {
77355
77507
  primaryProp,
77356
77508
  selector,
77357
77509
  iframe,
77358
- gsapCommitMutation
77510
+ commit
77359
77511
  );
77360
77512
  return;
77361
77513
  }
77362
77514
  if (!elementHasKeyframes) {
77363
- await commitStaticSet(
77364
- selection,
77365
- propEntries,
77366
- selector,
77367
- selectedGsapAnimations,
77368
- gsapCommitMutation
77369
- );
77515
+ await commitStaticSet(selection, propEntries, selector, selectedGsapAnimations, commit);
77370
77516
  return;
77371
77517
  }
77372
77518
  if (anim && isInstantHold(anim)) {
@@ -77376,7 +77522,7 @@ function useAnimatedPropertyCommit(deps) {
77376
77522
  propEntries,
77377
77523
  selector,
77378
77524
  selectedGsapAnimations,
77379
- gsapCommitMutation
77525
+ commit
77380
77526
  );
77381
77527
  return;
77382
77528
  }
@@ -77392,7 +77538,7 @@ function useAnimatedPropertyCommit(deps) {
77392
77538
  { percentage: 0, properties: { ...newProps, _auto: 1 } },
77393
77539
  { percentage: pct, properties: newProps }
77394
77540
  ];
77395
- await gsapCommitMutation(
77541
+ await commit(
77396
77542
  selection,
77397
77543
  {
77398
77544
  type: "add-with-keyframes",
@@ -77421,10 +77567,6 @@ function useAnimatedPropertyCommit(deps) {
77421
77567
  }
77422
77568
 
77423
77569
  // src/hooks/useGsapAwareEditing.ts
77424
- async function saveMove(outcome, saveOnElement) {
77425
- if (outcome.status === "element-offset") return saveOnElement();
77426
- assertGsapEditPersisted(outcome);
77427
- }
77428
77570
  var groupDragCommitCounter = 0;
77429
77571
  function firstPreflightFailure(results, updates) {
77430
77572
  for (const [index, result] of results.entries()) {
@@ -77445,6 +77587,7 @@ function useGsapAwareEditing({
77445
77587
  trackGsapInteractionFailure,
77446
77588
  stageElementPositionOffset,
77447
77589
  handleDomBoxSizeCommit,
77590
+ commitPositionPatchToHtml,
77448
77591
  addGsapAnimation,
77449
77592
  convertToKeyframes,
77450
77593
  setArcPath,
@@ -77622,6 +77765,8 @@ function useGsapAwareEditing({
77622
77765
  animGroups: targetAnimations.map((a) => `${a.propertyGroup}:${a.method}`)
77623
77766
  });
77624
77767
  let anchorMove = null;
77768
+ const stageCrop = prepareCropResize(selection.element);
77769
+ let cropUndoKey = null;
77625
77770
  return runGestureTransaction({
77626
77771
  element: selection.element,
77627
77772
  label: "Resize layer",
@@ -77648,6 +77793,7 @@ function useGsapAwareEditing({
77648
77793
  makeFetchFallback(selection)
77649
77794
  );
77650
77795
  assertGsapEditPersisted(outcome);
77796
+ cropUndoKey = coalesceKey;
77651
77797
  const ownsDragOffset = outcome.status === "persisted" && outcome.ownsDragOffset === true;
77652
77798
  logResize("intercept-handled", {
77653
77799
  scaleRoute,
@@ -77682,7 +77828,12 @@ function useGsapAwareEditing({
77682
77828
  logResizeSettle(selection.element, "dom-route");
77683
77829
  await handleDomBoxSizeCommit(selection, next, offset);
77684
77830
  },
77685
- afterBufferedCommitsSaved: () => anchorMove?.save() ?? Promise.resolve(),
77831
+ afterBufferedCommitsSaved: async () => {
77832
+ await anchorMove?.save();
77833
+ if (cropUndoKey) {
77834
+ await saveCropResize(stageCrop, selection, commitPositionPatchToHtml, cropUndoKey);
77835
+ }
77836
+ },
77686
77837
  restore: () => {
77687
77838
  anchorMove?.rollback();
77688
77839
  restore();
@@ -77692,6 +77843,7 @@ function useGsapAwareEditing({
77692
77843
  },
77693
77844
  [
77694
77845
  handleDomBoxSizeCommit,
77846
+ commitPositionPatchToHtml,
77695
77847
  stageElementPositionOffset,
77696
77848
  gsapCommitMutation,
77697
77849
  previewIframeRef,
@@ -77729,10 +77881,7 @@ function useGsapAwareEditing({
77729
77881
  getGsapAnimationsForSelection
77730
77882
  ]
77731
77883
  );
77732
- const {
77733
- commitAnimatedProperty: commitAnimatedPropertyRaw,
77734
- commitAnimatedProperties: commitAnimatedPropertiesRaw
77735
- } = useAnimatedPropertyCommit({
77884
+ const { commitAnimatedProperties: commitAnimatedPropertiesRaw } = useAnimatedPropertyCommit({
77736
77885
  selectedGsapAnimations,
77737
77886
  gsapCommitMutation,
77738
77887
  addGsapAnimation: (sel, method, time) => addGsapAnimation(sel, method, time),
@@ -77743,24 +77892,28 @@ function useGsapAwareEditing({
77743
77892
  const commitAnimatedProperties = useCallback138(
77744
77893
  async (selection, properties) => {
77745
77894
  try {
77746
- await commitAnimatedPropertiesRaw(selection, properties);
77895
+ await writeSizeWithCrop(
77896
+ selection,
77897
+ properties,
77898
+ gsapCommitMutation,
77899
+ commitPositionPatchToHtml,
77900
+ (keyed) => commitAnimatedPropertiesRaw(selection, properties, keyed)
77901
+ );
77747
77902
  } catch (error) {
77748
77903
  trackGsapInteractionFailure(error, selection, "property", "Edit animated property");
77749
77904
  throw error;
77750
77905
  }
77751
77906
  },
77752
- [commitAnimatedPropertiesRaw, trackGsapInteractionFailure]
77907
+ [
77908
+ commitAnimatedPropertiesRaw,
77909
+ commitPositionPatchToHtml,
77910
+ gsapCommitMutation,
77911
+ trackGsapInteractionFailure
77912
+ ]
77753
77913
  );
77754
77914
  const commitAnimatedProperty = useCallback138(
77755
- async (selection, property, value) => {
77756
- try {
77757
- await commitAnimatedPropertyRaw(selection, property, value);
77758
- } catch (error) {
77759
- trackGsapInteractionFailure(error, selection, "property", "Edit animated property");
77760
- throw error;
77761
- }
77762
- },
77763
- [commitAnimatedPropertyRaw, trackGsapInteractionFailure]
77915
+ (selection, property, value) => commitAnimatedProperties(selection, { [property]: value }),
77916
+ [commitAnimatedProperties]
77764
77917
  );
77765
77918
  const handleSetArcPath = useCallback138(
77766
77919
  (animId, config) => {
@@ -78005,15 +78158,10 @@ function useKeyframeEaseCommits({
78005
78158
  }
78006
78159
 
78007
78160
  // src/hooks/useCommitPreflightCapabilities.ts
78008
- import { useEffect as useEffect116, useMemo as useMemo68, useRef as useRef149, useState as useState112 } from "react";
78161
+ import { useEffect as useEffect116, useMemo as useMemo69, useRef as useRef149, useState as useState112 } from "react";
78009
78162
  function runCommitPreflights(selection, fileAnimations, iframe, group) {
78010
78163
  const target = { id: selection.id ?? null, selector: selection.selector ?? null };
78011
- const matched = selectElementAnimationsOrRetry(
78012
- { animations: fileAnimations },
78013
- target,
78014
- selection.element
78015
- );
78016
- const animations = matched.kind === "resolved" ? matched.animations : [];
78164
+ const animations = getAnimationsForElement(fileAnimations, target, selection.element);
78017
78165
  return {
78018
78166
  offset: dragEditOutcome(selection, animations, iframe, [], group),
78019
78167
  size: preflightGsapResizeIntercept(selection, animations, iframe),
@@ -78079,7 +78227,7 @@ function useCommitPreflightCapabilities({
78079
78227
  });
78080
78228
  }
78081
78229
  }, [enabled, projectId, selection, groupSelections, version]);
78082
- return useMemo68(() => {
78230
+ return useMemo69(() => {
78083
78231
  void parseTick;
78084
78232
  if (!enabled || !projectId) return { selection, groupSelections };
78085
78233
  const group = groupSelections.length > 1;
@@ -78270,6 +78418,7 @@ function useDomEditSession({
78270
78418
  handleDomAddTextField,
78271
78419
  handleDomRemoveTextField,
78272
78420
  stageElementPositionOffset,
78421
+ commitPositionPatchToHtml,
78273
78422
  handleDomBoxSizeCommit,
78274
78423
  handleDomManualEditsReset,
78275
78424
  handleDomEditElementsDelete,
@@ -78500,6 +78649,7 @@ function useDomEditSession({
78500
78649
  trackGsapInteractionFailure,
78501
78650
  stageElementPositionOffset,
78502
78651
  handleDomBoxSizeCommit,
78652
+ commitPositionPatchToHtml,
78503
78653
  addGsapAnimation,
78504
78654
  convertToKeyframes,
78505
78655
  setArcPath,
@@ -79411,7 +79561,7 @@ function useStudioExternalFileChanges({
79411
79561
  }
79412
79562
 
79413
79563
  // src/hooks/useBlockHandlers.ts
79414
- import { useCallback as useCallback144, useMemo as useMemo69, useRef as useRef152, useState as useState115 } from "react";
79564
+ import { useCallback as useCallback144, useMemo as useMemo70, useRef as useRef152, useState as useState115 } from "react";
79415
79565
 
79416
79566
  // src/utils/blockInstaller.ts
79417
79567
  function getMaxZIndexFromIframe(iframe) {
@@ -79605,7 +79755,7 @@ function useBlockHandlers({
79605
79755
  setRightPanelTab
79606
79756
  }) {
79607
79757
  const [activeBlockParams, setActiveBlockParams] = useState115(null);
79608
- const blockCtx = useMemo69(
79758
+ const blockCtx = useMemo70(
79609
79759
  () => ({
79610
79760
  activeCompPath: blockCtxDeps.activeCompPath,
79611
79761
  timelineElements: blockCtxDeps.timelineElements,
@@ -79935,7 +80085,7 @@ function dispatchPlainKey(event, key2, cb) {
79935
80085
  }
79936
80086
 
79937
80087
  // src/hooks/useEditHistoryActions.ts
79938
- import { useCallback as useCallback145, useMemo as useMemo70 } from "react";
80088
+ import { useCallback as useCallback145, useMemo as useMemo71 } from "react";
79939
80089
  function useEditHistoryActions({
79940
80090
  editHistory,
79941
80091
  readOptionalProjectFile,
@@ -79999,7 +80149,7 @@ function useEditHistoryActions({
79999
80149
  );
80000
80150
  const undo = useCallback145(() => apply("undo"), [apply]);
80001
80151
  const redo = useCallback145(() => apply("redo"), [apply]);
80002
- return useMemo70(() => ({ undo, redo }), [undo, redo]);
80152
+ return useMemo71(() => ({ undo, redo }), [undo, redo]);
80003
80153
  }
80004
80154
 
80005
80155
  // src/hooks/useAppHotkeys.ts
@@ -80173,7 +80323,7 @@ function useAppHotkeys({
80173
80323
 
80174
80324
  // src/hooks/useClipboard.ts
80175
80325
  import { useCallback as useCallback147, useRef as useRef154 } from "react";
80176
- import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants } from "@hyperframes/parsers/hf-ids";
80326
+ import { EXCLUDED_TAGS, mintHfId, walkCompositionDescendants as walkCompositionDescendants2 } from "@hyperframes/parsers/hf-ids";
80177
80327
 
80178
80328
  // src/utils/clipboardPayload.ts
80179
80329
  function insertAsSibling(source, newHtml, selector, selectorIndex) {
@@ -80372,7 +80522,7 @@ function remintHfIds(html2, parser, taken) {
80372
80522
  return html2.replace(/\sdata-hf-id=("[^"]*"|'[^']*')/g, "");
80373
80523
  }
80374
80524
  const elements = [root];
80375
- walkCompositionDescendants(root, (el) => elements.push(el));
80525
+ walkCompositionDescendants2(root, (el) => elements.push(el));
80376
80526
  for (const el of elements) {
80377
80527
  if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) el.removeAttribute("data-hf-id");
80378
80528
  else el.setAttribute("data-hf-id", mintHfId(el, taken));
@@ -81192,7 +81342,7 @@ function useFrameCapture({
81192
81342
  }
81193
81343
 
81194
81344
  // src/hooks/useLintModal.ts
81195
- import { useState as useState118, useCallback as useCallback150, useEffect as useEffect124, useRef as useRef158, useMemo as useMemo71 } from "react";
81345
+ import { useState as useState118, useCallback as useCallback150, useEffect as useEffect124, useRef as useRef158, useMemo as useMemo72 } from "react";
81196
81346
  function parseFinding(f) {
81197
81347
  return {
81198
81348
  severity: f.severity === "error" ? "error" : "warning",
@@ -81265,8 +81415,8 @@ function useLintModal(projectId, refreshKey) {
81265
81415
  },
81266
81416
  [backgroundFindings]
81267
81417
  );
81268
- const findingsByElement = useMemo71(() => groupFindings((f) => f.elementId), [groupFindings]);
81269
- const findingsByFile = useMemo71(() => groupFindings((f) => f.file), [groupFindings]);
81418
+ const findingsByElement = useMemo72(() => groupFindings((f) => f.elementId), [groupFindings]);
81419
+ const findingsByFile = useMemo72(() => groupFindings((f) => f.file), [groupFindings]);
81270
81420
  const badgeFindings = lintModal ?? backgroundFindings;
81271
81421
  const lintFindingCount = badgeFindings.length;
81272
81422
  const hasLintError = badgeFindings.some((f) => f.severity === "error");
@@ -81731,16 +81881,16 @@ function useStudioUrlState({
81731
81881
  }
81732
81882
 
81733
81883
  // src/hooks/useEffectiveTimelineDuration.ts
81734
- import { useMemo as useMemo72 } from "react";
81884
+ import { useMemo as useMemo73 } from "react";
81735
81885
  function useEffectiveTimelineDuration(timelineDuration2, timelineElements) {
81736
- return useMemo72(
81886
+ return useMemo73(
81737
81887
  () => getEffectiveTimelineDuration(timelineDuration2, timelineElements),
81738
81888
  [timelineDuration2, timelineElements]
81739
81889
  );
81740
81890
  }
81741
81891
 
81742
81892
  // src/hooks/useStudioContextValue.ts
81743
- import { useCallback as useCallback153, useMemo as useMemo73 } from "react";
81893
+ import { useCallback as useCallback153, useMemo as useMemo74 } from "react";
81744
81894
  function buildStudioContextValue(input) {
81745
81895
  return {
81746
81896
  projectId: input.projectId,
@@ -81768,7 +81918,7 @@ function buildStudioContextValue(input) {
81768
81918
  };
81769
81919
  }
81770
81920
  function useInspectorState(rightPanel, isPlaying, domEditSelection, isGestureRecording) {
81771
- return useMemo73(() => {
81921
+ return useMemo74(() => {
81772
81922
  const inspectorPanelActive = rightPanel === "layers" || rightPanel === "design";
81773
81923
  return {
81774
81924
  inspectorPanelActive,
@@ -81811,7 +81961,7 @@ function useGlobalFileDrop(handleTimelineFileDrop) {
81811
81961
  }
81812
81962
 
81813
81963
  // src/contexts/PanelLayoutContext.tsx
81814
- import { useContext as useContext13, useMemo as useMemo74 } from "react";
81964
+ import { useContext as useContext13, useMemo as useMemo75 } from "react";
81815
81965
  import { jsx as jsx188 } from "react/jsx-runtime";
81816
81966
  var PanelLayoutContext = createStableContext("PanelLayoutContext", null);
81817
81967
  function usePanelLayoutContext() {
@@ -81823,7 +81973,7 @@ function PanelLayoutProvider({
81823
81973
  value: { rightCollapsed, setRightCollapsed, rightPanelTab, setRightPanelTab },
81824
81974
  children
81825
81975
  }) {
81826
- const stable = useMemo74(
81976
+ const stable = useMemo75(
81827
81977
  () => ({ rightCollapsed, setRightCollapsed, rightPanelTab, setRightPanelTab }),
81828
81978
  [rightCollapsed, setRightCollapsed, rightPanelTab, setRightPanelTab]
81829
81979
  );
@@ -82766,7 +82916,7 @@ function useGestureCommit({
82766
82916
  }
82767
82917
 
82768
82918
  // src/components/editor/GestureTrailOverlay.tsx
82769
- import { memo as memo40, useMemo as useMemo75 } from "react";
82919
+ import { memo as memo40, useMemo as useMemo76 } from "react";
82770
82920
  import { Fragment as Fragment53, jsx as jsx191, jsxs as jsxs150 } from "react/jsx-runtime";
82771
82921
  var GestureTrailOverlay = memo40(function GestureTrailOverlay2({
82772
82922
  samples,
@@ -82778,7 +82928,7 @@ var GestureTrailOverlay = memo40(function GestureTrailOverlay2({
82778
82928
  mode,
82779
82929
  accentColor = "#3CE6AC"
82780
82930
  }) {
82781
- const trailPoints = useMemo75(() => {
82931
+ const trailPoints = useMemo76(() => {
82782
82932
  if (!canvasRect) return "";
82783
82933
  if (trail2 && trail2.length > 1) {
82784
82934
  return trail2.map((p) => `${p.x - canvasRect.left},${p.y - canvasRect.top}`).join(" ");
@@ -82786,7 +82936,7 @@ var GestureTrailOverlay = memo40(function GestureTrailOverlay2({
82786
82936
  if (samples.length === 0) return "";
82787
82937
  return samples.filter((s) => s.properties.x != null && s.properties.y != null).map((s) => `${s.properties.x},${s.properties.y}`).join(" ");
82788
82938
  }, [samples, trail2, sampleCount, canvasRect?.left, canvasRect?.top]);
82789
- const simplifiedPath = useMemo75(() => {
82939
+ const simplifiedPath = useMemo76(() => {
82790
82940
  if (!simplifiedPoints || simplifiedPoints.size === 0) return "";
82791
82941
  const pts = [];
82792
82942
  for (const [pct, props] of simplifiedPoints) {
@@ -82798,7 +82948,7 @@ var GestureTrailOverlay = memo40(function GestureTrailOverlay2({
82798
82948
  if (pts.length === 0) return "";
82799
82949
  return pts.map((p) => `${p.x},${p.y}`).join(" ");
82800
82950
  }, [simplifiedPoints]);
82801
- const diamondPositions = useMemo75(() => {
82951
+ const diamondPositions = useMemo76(() => {
82802
82952
  if (!simplifiedPoints || simplifiedPoints.size === 0) return [];
82803
82953
  const pts = [];
82804
82954
  for (const [pct, props] of simplifiedPoints) {
@@ -82997,7 +83147,7 @@ function MediaPreview({ projectId, filePath }) {
82997
83147
  }
82998
83148
 
82999
83149
  // src/components/sidebar/AssetsTab.tsx
83000
- import { memo as memo41, useState as useState129, useCallback as useCallback159, useRef as useRef166, useMemo as useMemo77, useEffect as useEffect133 } from "react";
83150
+ import { memo as memo41, useState as useState129, useCallback as useCallback159, useRef as useRef166, useMemo as useMemo78, useEffect as useEffect133 } from "react";
83001
83151
 
83002
83152
  // src/components/ui/SearchInput.tsx
83003
83153
  import { jsx as jsx193, jsxs as jsxs152 } from "react/jsx-runtime";
@@ -83609,7 +83759,7 @@ function AudioRow({
83609
83759
  }
83610
83760
 
83611
83761
  // src/components/sidebar/GlobalAssetsView.tsx
83612
- import { useEffect as useEffect130, useMemo as useMemo76, useState as useState126 } from "react";
83762
+ import { useEffect as useEffect130, useMemo as useMemo77, useState as useState126 } from "react";
83613
83763
  import { jsx as jsx196, jsxs as jsxs155 } from "react/jsx-runtime";
83614
83764
  function globalAssetRows(records, query = "") {
83615
83765
  const q = query.trim().toLowerCase();
@@ -83636,7 +83786,7 @@ function GlobalAssetsView({ searchQuery }) {
83636
83786
  cancelled = true;
83637
83787
  };
83638
83788
  }, []);
83639
- const rows = useMemo76(() => globalAssetRows(records ?? [], searchQuery), [records, searchQuery]);
83789
+ const rows = useMemo77(() => globalAssetRows(records ?? [], searchQuery), [records, searchQuery]);
83640
83790
  if (records === null) {
83641
83791
  return /* @__PURE__ */ jsx196("p", { className: "px-4 py-3 text-[11px] text-panel-text-5", children: "Loading global assets\u2026" });
83642
83792
  }
@@ -84198,12 +84348,12 @@ var AssetsTab = memo41(function AssetsTab2({
84198
84348
  setTimeout(() => setCopyFeedback(null), copied ? 1500 : 3e3);
84199
84349
  }, []);
84200
84350
  const elements = usePlayerStore((s) => s.elements);
84201
- const usedPaths = useMemo77(() => deriveUsedPaths(elements), [elements]);
84202
- const allMediaAssets = useMemo77(
84351
+ const usedPaths = useMemo78(() => deriveUsedPaths(elements), [elements]);
84352
+ const allMediaAssets = useMemo78(
84203
84353
  () => assets.filter((a) => MEDIA_EXT.test(a) || FONT_EXT.test(a)),
84204
84354
  [assets]
84205
84355
  );
84206
- const mediaAssets = useMemo77(() => {
84356
+ const mediaAssets = useMemo78(() => {
84207
84357
  const all = filterByUsage(allMediaAssets, usedPaths, usageFilter);
84208
84358
  if (!searchQuery) return all;
84209
84359
  const q = searchQuery.toLowerCase();
@@ -84213,7 +84363,7 @@ var AssetsTab = memo41(function AssetsTab2({
84213
84363
  return rec?.description?.toLowerCase().includes(q);
84214
84364
  });
84215
84365
  }, [allMediaAssets, searchQuery, manifest, usageFilter, usedPaths]);
84216
- const categorized = useMemo77(() => {
84366
+ const categorized = useMemo78(() => {
84217
84367
  const groups = { audio: [], images: [], video: [], fonts: [] };
84218
84368
  for (const a of mediaAssets) {
84219
84369
  const cat = getCategory(a);
@@ -84228,12 +84378,12 @@ var AssetsTab = memo41(function AssetsTab2({
84228
84378
  }
84229
84379
  return groups;
84230
84380
  }, [mediaAssets, usedPaths]);
84231
- const counts = useMemo77(() => {
84381
+ const counts = useMemo78(() => {
84232
84382
  const c = { all: mediaAssets.length };
84233
84383
  for (const cat of FILTER_ORDER) c[cat] = categorized[cat].length;
84234
84384
  return c;
84235
84385
  }, [mediaAssets, categorized]);
84236
- const usageCounts = useMemo77(
84386
+ const usageCounts = useMemo78(
84237
84387
  () => countUsage(
84238
84388
  assets.filter((a) => MEDIA_EXT.test(a) || FONT_EXT.test(a)),
84239
84389
  usedPaths
@@ -88614,7 +88764,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
88614
88764
  }
88615
88765
 
88616
88766
  // src/components/panels/VariablesPanel.tsx
88617
- import { memo as memo52, useCallback as useCallback175, useEffect as useEffect144, useMemo as useMemo78, useState as useState146 } from "react";
88767
+ import { memo as memo52, useCallback as useCallback175, useEffect as useEffect144, useMemo as useMemo79, useState as useState146 } from "react";
88618
88768
 
88619
88769
  // src/hooks/useVariablesPersist.ts
88620
88770
  import { useCallback as useCallback172 } from "react";
@@ -89310,22 +89460,22 @@ var VariablesPanel = memo52(function VariablesPanel2({
89310
89460
  reloadPreview,
89311
89461
  publishSdkSession
89312
89462
  });
89313
- const declarations = useMemo78(
89463
+ const declarations = useMemo79(
89314
89464
  () => sdkSession?.getVariableDeclarations() ?? [],
89315
89465
  // eslint-disable-next-line react-hooks/exhaustive-deps
89316
89466
  [sdkSession, refreshKey, revision]
89317
89467
  );
89318
- const usage = useMemo78(
89468
+ const usage = useMemo79(
89319
89469
  () => sdkSession?.getVariableUsage() ?? null,
89320
89470
  // eslint-disable-next-line react-hooks/exhaustive-deps
89321
89471
  [sdkSession, refreshKey, revision]
89322
89472
  );
89323
- const issues = useMemo78(
89473
+ const issues = useMemo79(
89324
89474
  () => previewValues && sdkSession ? sdkSession.validateVariableValues(previewValues) : [],
89325
89475
  // eslint-disable-next-line react-hooks/exhaustive-deps
89326
89476
  [sdkSession, previewValues, refreshKey, revision]
89327
89477
  );
89328
- const effectiveValues = useMemo78(
89478
+ const effectiveValues = useMemo79(
89329
89479
  () => sdkSession?.getVariableValues(previewValues ?? void 0) ?? {},
89330
89480
  // eslint-disable-next-line react-hooks/exhaustive-deps
89331
89481
  [sdkSession, previewValues, refreshKey, revision]
@@ -89640,15 +89790,15 @@ function useSlideshowPersist({
89640
89790
  }
89641
89791
 
89642
89792
  // src/hooks/useSlideshowTabState.ts
89643
- import { useMemo as useMemo79 } from "react";
89793
+ import { useMemo as useMemo80 } from "react";
89644
89794
  import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
89645
89795
  function useSlideshowTabState(params) {
89646
89796
  const { editingFileContent, previewIframeRef, refreshKey, slideshowVisible } = params;
89647
- const isSlideshowComposition = useMemo79(() => {
89797
+ const isSlideshowComposition = useMemo80(() => {
89648
89798
  if (!editingFileContent || !editingFileContent.includes(SLIDESHOW_ISLAND_TYPE2)) return false;
89649
89799
  return slideshowIslandRegex2("i").test(editingFileContent);
89650
89800
  }, [editingFileContent]);
89651
- const slideshowScenes = useMemo79(() => {
89801
+ const slideshowScenes = useMemo80(() => {
89652
89802
  try {
89653
89803
  const win = previewIframeRef.current?.contentWindow;
89654
89804
  return (win?.__clipManifest?.scenes ?? []).map((s) => ({
@@ -91501,7 +91651,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
91501
91651
  recordEdit: editHistory.recordEdit,
91502
91652
  setRefreshKey
91503
91653
  });
91504
- const masterCompPath = useMemo80(
91654
+ const masterCompPath = useMemo81(
91505
91655
  () => resolveMasterCompositionPath(fileManager.compositions),
91506
91656
  [fileManager.compositions]
91507
91657
  );
@@ -91605,7 +91755,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
91605
91755
  });
91606
91756
  const dismissBlockParams = useCallback185(() => setActiveBlockParams(null), [setActiveBlockParams]);
91607
91757
  const setRightPanelTab = useDismissingTabSetter(panelLayout.setRightPanelTab, dismissBlockParams);
91608
- const layout = useMemo80(
91758
+ const layout = useMemo81(
91609
91759
  () => ({ ...panelLayout, setRightPanelTab }),
91610
91760
  [panelLayout, setRightPanelTab]
91611
91761
  );
@@ -91823,7 +91973,7 @@ function StudioApp({ readOnlyPreview = false, readOnlyPreviewReason } = {}) {
91823
91973
  handlePreviewIframeRef,
91824
91974
  refreshPreviewDocumentVersion
91825
91975
  });
91826
- const timelineToolbar = useMemo80(
91976
+ const timelineToolbar = useMemo81(
91827
91977
  () => /* @__PURE__ */ jsx229(
91828
91978
  TimelineToolbar,
91829
91979
  {
@@ -92340,7 +92490,7 @@ function findElementBlock(html2, elementId2) {
92340
92490
  }
92341
92491
 
92342
92492
  // src/hooks/useDomStyleCommit.ts
92343
- import { useCallback as useCallback187, useMemo as useMemo81, useRef as useRef183, useState as useState151 } from "react";
92493
+ import { useCallback as useCallback187, useMemo as useMemo82, useRef as useRef183, useState as useState151 } from "react";
92344
92494
  var noop = () => {
92345
92495
  };
92346
92496
  function useDomStyleCommit({
@@ -92356,7 +92506,7 @@ function useDomStyleCommit({
92356
92506
  const [queue2] = useState151(createDomEditSaveQueue);
92357
92507
  useMountEffect(() => () => queue2.destroy());
92358
92508
  const versions = useRef183(/* @__PURE__ */ new Map()).current;
92359
- const editHistory = useMemo81(() => ({ recordEdit }), [recordEdit]);
92509
+ const editHistory = useMemo82(() => ({ recordEdit }), [recordEdit]);
92360
92510
  const persistDomEditOperations = useDomEditPersist({
92361
92511
  activeCompPath,
92362
92512
  previewIframeRef: iframeRef,
@@ -92388,7 +92538,7 @@ function useDomStyleCommit({
92388
92538
  }
92389
92539
 
92390
92540
  // src/hooks/useDomGeometryCommit.ts
92391
- import { useCallback as useCallback188, useMemo as useMemo82, useRef as useRef184, useState as useState152 } from "react";
92541
+ import { useCallback as useCallback188, useMemo as useMemo83, useRef as useRef184, useState as useState152 } from "react";
92392
92542
  var noop2 = () => {
92393
92543
  };
92394
92544
  var NO_SELECTED_ANIMATIONS = [];
@@ -92405,7 +92555,7 @@ function useDomGeometryCommit({
92405
92555
  const projectIdRef = useRef184(projectId);
92406
92556
  projectIdRef.current = projectId;
92407
92557
  const pending2 = useRef184(/* @__PURE__ */ new Set()).current;
92408
- const editHistory = useMemo82(() => ({ recordEdit }), [recordEdit]);
92558
+ const editHistory = useMemo83(() => ({ recordEdit }), [recordEdit]);
92409
92559
  const reload = useCallback188(
92410
92560
  () => reloadPreview ? reloadPreview() : iframeRef.current?.contentWindow?.location.reload(),
92411
92561
  [reloadPreview, iframeRef]
@@ -92473,6 +92623,7 @@ function useDomGeometryCommit({
92473
92623
  trackGsapInteractionFailure,
92474
92624
  stageElementPositionOffset,
92475
92625
  handleDomBoxSizeCommit: noDomBoxSizeRoute,
92626
+ commitPositionPatchToHtml: commitWithFreshQueue,
92476
92627
  addGsapAnimation: gsap3.addGsapAnimation,
92477
92628
  convertToKeyframes: gsap3.convertToKeyframes,
92478
92629
  setArcPath: gsap3.setArcPath,
@@ -92497,7 +92648,7 @@ function useDomGeometryCommit({
92497
92648
  },
92498
92649
  [pending2, showToast]
92499
92650
  );
92500
- return useMemo82(
92651
+ return useMemo83(
92501
92652
  () => ({
92502
92653
  commitPathOffset: (selection, next, modifiers) => saved2(() => handleGsapAwarePathOffsetCommit(selection, next, modifiers)),
92503
92654
  commitGroupPathOffset: (updates) => saved2(() => handleGsapAwareGroupPathOffsetCommit(updates)),