hyperframes 0.8.133 → 0.8.135

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 (169) hide show
  1. package/dist/{add-KIAMI737.js → add-7E2GRDW5.js} +16 -16
  2. package/dist/{animatedGifPrep-P2T6YM7Z.js → animatedGifPrep-3AKQSXPU.js} +7 -7
  3. package/dist/{asset-KRQ4APXQ.js → asset-43SXDTJI.js} +2 -2
  4. package/dist/{assetCataloger-VBLPHX24.js → assetCataloger-KBN5LNK4.js} +3 -3
  5. package/dist/{auth-M4XWYQDV.js → auth-BWQ4HPK7.js} +2 -2
  6. package/dist/{autoUpdate-J7LNL4BP.js → autoUpdate-JTHQKHG5.js} +2 -2
  7. package/dist/{backgroundChecks-LLNHXZPY.js → backgroundChecks-H43RKGRE.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-FT7AP7XH.js → batchRender-VFXGGGJG.js} +4 -4
  10. package/dist/{beats-NOF2NADU.js → beats-HAOQC7GJ.js} +13 -13
  11. package/dist/{benchmark-XVHUNNCM.js → benchmark-ANUXIEPD.js} +13 -13
  12. package/dist/{browser-URZ3PY73.js → browser-HEICRDNT.js} +10 -10
  13. package/dist/{bundleWithLocalizedFonts-5QOPOB5D.js → bundleWithLocalizedFonts-5ATL25BF.js} +2 -2
  14. package/dist/{capture-RLZA6EMR.js → capture-DYBK5LGE.js} +10 -10
  15. package/dist/{capture-SZZ4IYJK.js → capture-UEJG6BUX.js} +2 -2
  16. package/dist/{catalog-EJBGGESW.js → catalog-2F5L72ZT.js} +16 -16
  17. package/dist/{check-RXLKRRB2.js → check-WZ5QMBBF.js} +33 -33
  18. package/dist/{checkBrowser-DNUQDCGD.js → checkBrowser-FXVVADP6.js} +32 -32
  19. package/dist/{chunk-MWB3ZIFM.js → chunk-2BJCQN7K.js} +1 -1
  20. package/dist/{chunk-LYNI3FOL.js → chunk-32QEFPDS.js} +1 -1
  21. package/dist/{chunk-4NWOGZZS.js → chunk-374QQGBQ.js} +26 -3
  22. package/dist/{chunk-QC4FJVLM.js → chunk-37EDNWLQ.js} +1 -1
  23. package/dist/{chunk-KBJUXVOJ.js → chunk-3NCZ2SCH.js} +3 -3
  24. package/dist/{chunk-IUZFXWSV.js → chunk-4AXZWUQ6.js} +1 -1
  25. package/dist/{chunk-3ZP5DXE7.js → chunk-527WRFUQ.js} +1 -1
  26. package/dist/{chunk-ZMXUQLI2.js → chunk-5ALWCRBU.js} +47 -25
  27. package/dist/{chunk-JEFU7IHM.js → chunk-AV7FBRWP.js} +2 -2
  28. package/dist/{chunk-2YN64IN5.js → chunk-BHOKLX4F.js} +2 -2
  29. package/dist/{chunk-HRUGC23L.js → chunk-BSU7EOWJ.js} +1 -1
  30. package/dist/{chunk-FCSIGXXP.js → chunk-BWJ27MQP.js} +1 -1
  31. package/dist/{chunk-PDAMK4Q5.js → chunk-D2KNWIQ6.js} +1 -1
  32. package/dist/{chunk-RMHUQGRK.js → chunk-GK5W3SIJ.js} +4 -4
  33. package/dist/{chunk-GK3QVRBM.js → chunk-GN6GW6IP.js} +9 -9
  34. package/dist/{chunk-6CTIGJY7.js → chunk-H4UIYRZZ.js} +2 -2
  35. package/dist/{chunk-DIFXJN2V.js → chunk-HNUHWAHR.js} +1 -1
  36. package/dist/{chunk-S55PCW5S.js → chunk-I54AADBN.js} +1 -1
  37. package/dist/{chunk-5OZGQCAB.js → chunk-KZ7B3XK7.js} +1 -1
  38. package/dist/{chunk-7JZ4FXP2.js → chunk-KZJPREW7.js} +1 -1
  39. package/dist/{chunk-ARR5QKPH.js → chunk-LKCICE2T.js} +10 -10
  40. package/dist/{chunk-YQZNILVH.js → chunk-LO7L5PJ3.js} +5 -5
  41. package/dist/{chunk-S7NPLFWV.js → chunk-LYJNFBD6.js} +792 -671
  42. package/dist/{chunk-7XKF2OB5.js → chunk-N3DZEPNT.js} +1 -1
  43. package/dist/{chunk-K2FTXJYH.js → chunk-N3KRRPL7.js} +81 -24
  44. package/dist/{chunk-ETWKKKJI.js → chunk-NDKVPWBH.js} +3 -3
  45. package/dist/{chunk-XS4EJSKU.js → chunk-OSSVNAI5.js} +1 -1
  46. package/dist/{chunk-AVUUN2XD.js → chunk-OZUQF4NB.js} +5 -4
  47. package/dist/{chunk-Z6B37SNM.js → chunk-PGFP4FLW.js} +12 -158
  48. package/dist/{chunk-SF4GTH2H.js → chunk-PULS7UW7.js} +2 -2
  49. package/dist/{chunk-A2Z4Z4VL.js → chunk-RTURM45Q.js} +1 -1
  50. package/dist/{chunk-TCLGPL65.js → chunk-RVG35AL2.js} +2 -2
  51. package/dist/{chunk-HKGEEUZV.js → chunk-SMWYGLTD.js} +7 -7
  52. package/dist/chunk-SRXQEQWZ.js +3742 -0
  53. package/dist/{chunk-K2EQN4VU.js → chunk-T5LPCY6I.js} +1 -1
  54. package/dist/{chunk-MAWC5PUQ.js → chunk-TKSAWNNI.js} +3 -3
  55. package/dist/{chunk-Q7IU7MOR.js → chunk-V26IB6L2.js} +2 -2
  56. package/dist/{chunk-CWPVM2QV.js → chunk-V2J2ZWPR.js} +2 -2
  57. package/dist/{chunk-ZJUKFZ2P.js → chunk-VDQCNWQI.js} +2 -2
  58. package/dist/{chunk-CFKU7HTK.js → chunk-VNC6YQOC.js} +1 -1
  59. package/dist/{chunk-WPHMNO44.js → chunk-WJFCDDP7.js} +1 -1
  60. package/dist/{chunk-VTUOQTFU.js → chunk-WVRNPWVW.js} +1 -1
  61. package/dist/{chunk-7HG4BFIN.js → chunk-XE6S5CE6.js} +1 -1
  62. package/dist/{chunk-U7CKZ7BR.js → chunk-XFKPUKL7.js} +4 -4
  63. package/dist/{chunk-UEZXMWG3.js → chunk-XSWKLDVC.js} +2 -2
  64. package/dist/{chunk-64TQHBM7.js → chunk-XZVPQYLU.js} +5 -5
  65. package/dist/{chunk-LHSR3M4M.js → chunk-Z3EFUXL5.js} +2 -2
  66. package/dist/{chunk-MJ5XLLXY.js → chunk-Z3KAYWE5.js} +3 -3
  67. package/dist/{clean-XMQXSQI5.js → clean-SZMPUNNQ.js} +17 -17
  68. package/dist/cli.js +51 -51
  69. package/dist/{cloud-BAC5KWSE.js → cloud-L7RFQB5E.js} +4 -4
  70. package/dist/{cloudrun-AJUTHUEA.js → cloudrun-FA4XHPZS.js} +5 -5
  71. package/dist/{compare-QAV6WNL4.js → compare-DN5GHUAZ.js} +27 -27
  72. package/dist/{compiler-QO36EXLS.js → compiler-XZ654ZH2.js} +13 -11
  73. package/dist/{component-HAJPPCFF.js → component-RYYGPCCC.js} +3 -3
  74. package/dist/{compositions-A5PN3ILU.js → compositions-TF42YRGQ.js} +12 -12
  75. package/dist/{delete-L5LYQF6D.js → delete-KYGP772F.js} +2 -2
  76. package/dist/{deterministicFonts-HAEMV4TM.js → deterministicFonts-U2EXY2KQ.js} +2 -2
  77. package/dist/{dist-S3PJNPXQ.js → dist-HVJAGLHF.js} +5 -3
  78. package/dist/{dist-FNNGCKFS.js → dist-QS5WEGBE.js} +1 -1
  79. package/dist/{doctor-VAALU6RK.js → doctor-AWERBYTS.js} +11 -11
  80. package/dist/{events-53SD6Z6G.js → events-QMT3TXJ2.js} +10 -10
  81. package/dist/{events-FSZQPLB5.js → events-W4AILK4F.js} +9 -9
  82. package/dist/{feedback-HVIGQIBA.js → feedback-KNKJFGOK.js} +11 -11
  83. package/dist/{figma-OMHCIFA6.js → figma-D76N4WD7.js} +3 -3
  84. package/dist/fontLocalizeCli.js +10 -10
  85. package/dist/{get-VVCHOTHZ.js → get-JHNZVDVT.js} +2 -2
  86. package/dist/{gpuPolicy-2ZUZE5D6.js → gpuPolicy-ON3RMSWD.js} +7 -7
  87. package/dist/{grade-compare-VEQUV7SJ.js → grade-compare-22SRAVPO.js} +27 -27
  88. package/dist/{gsapParser-DIYYX6JT.js → gsapParser-2OL54XZ5.js} +61 -23
  89. package/dist/{gsapParserAcorn-E3GEIRZB.js → gsapParserAcorn-VXZ3ZIF2.js} +5 -3
  90. package/dist/{help-VWAFVPC6.js → help-CKKLDL3G.js} +1 -1
  91. package/dist/{history-NYADNWVR.js → history-44SS2VJQ.js} +17 -17
  92. package/dist/hyperframe-runtime.js +49 -49
  93. package/dist/hyperframe.manifest.json +1 -1
  94. package/dist/hyperframe.runtime.iife.js +49 -49
  95. package/dist/hyperframes-player.global.js +1 -1
  96. package/dist/{info-42A4ZXQZ.js → info-A2CXIT7G.js} +12 -12
  97. package/dist/{init-YUGOYCN6.js → init-6D2GNNON.js} +23 -23
  98. package/dist/{inspect-C66X5KGK.js → inspect-QLYCCDHV.js} +28 -28
  99. package/dist/{installer-K6O5WF44.js → installer-DDJVY4TO.js} +5 -5
  100. package/dist/{keyframes-CC4SVYGQ.js → keyframes-TBS3BUZ5.js} +13 -16
  101. package/dist/{lambda-D7UMN4DJ.js → lambda-LPLYOEYY.js} +6 -6
  102. package/dist/{layout-OBTKALAB.js → layout-YLBD2653.js} +28 -28
  103. package/dist/{lint-CDJ4BIB5.js → lint-6XERRN4E.js} +13 -13
  104. package/dist/{lintProject-OHBBM3DJ.js → lintProject-EPVG2EFE.js} +1 -1
  105. package/dist/{list-AMHMJLVP.js → list-DPVTICQX.js} +2 -2
  106. package/dist/{login-3FHM5JUX.js → login-FJJH3ZPU.js} +4 -4
  107. package/dist/{media-treatment-SGIJZZSW.js → media-treatment-CQWZZRHR.js} +13 -13
  108. package/dist/{models-C4E4IWJD.js → models-54NL5PRB.js} +8 -8
  109. package/dist/{motionShot-S4MIGMT5.js → motionShot-BSI4U6X7.js} +13 -13
  110. package/dist/{normalize-audio-U4QNHC3R.js → normalize-audio-2QQBNTAW.js} +12 -12
  111. package/dist/{open-DRIBRDFD.js → open-YCWBIUNN.js} +11 -11
  112. package/dist/{pipeline-TNGWUZQC.js → pipeline-P7QRGI5H.js} +7 -7
  113. package/dist/{play-NOBUJING.js → play-5OX76SR5.js} +22 -22
  114. package/dist/{preflight-SMNV6RW2.js → preflight-X3XFRJPQ.js} +8 -8
  115. package/dist/{present-Q4Q4V6JJ.js → present-UFG6NPCO.js} +18 -18
  116. package/dist/{preview-SEH3BNYZ.js → preview-AGS4TAWM.js} +18 -18
  117. package/dist/{producer-ECLEN5RP.js → producer-KQNJKM7F.js} +1 -1
  118. package/dist/{projectConfig-HYJMJ5BY.js → projectConfig-EAAQYRMP.js} +7 -7
  119. package/dist/{publication-HQ5Y3KBM.js → publication-H3G346EX.js} +3 -3
  120. package/dist/{publish-VHDYQASC.js → publish-NEGKIS35.js} +9 -9
  121. package/dist/{remove-background-W2YX2NVH.js → remove-background-HU4S4HH5.js} +8 -8
  122. package/dist/{render-AO5VX3YI.js → render-F3CNO2IE.js} +4 -4
  123. package/dist/{render-ZJYL6ZPT.js → render-KEZFDRDA.js} +22 -22
  124. package/dist/{render-XVKTOY2L.js → render-POQI3EKS.js} +13 -13
  125. package/dist/{render-batch-YH7CTWNY.js → render-batch-63CBIJYR.js} +4 -4
  126. package/dist/{renderCancellation-JPIHE47X.js → renderCancellation-FT3JJ7GB.js} +7 -7
  127. package/dist/renderSetupWorker.js +1 -1
  128. package/dist/{resolver-IZ647GBZ.js → resolver-YJJKOGQF.js} +5 -5
  129. package/dist/{sherpa-KKLNW6U2.js → sherpa-3BQDMJPJ.js} +8 -8
  130. package/dist/{skills-IIHWUS56.js → skills-L7SZ45NC.js} +12 -12
  131. package/dist/{skillsUpdateCheck-HG5HOSBY.js → skillsUpdateCheck-HP3GJFBP.js} +3 -3
  132. package/dist/{snapshot-SRSWE7B2.js → snapshot-CYB2PZPW.js} +28 -28
  133. package/dist/{src-PVYUYV3M.js → src-CUR36DBE.js} +8 -6
  134. package/dist/{src-6YQBJGWR.js → src-ZPAJ44CP.js} +9 -9
  135. package/dist/{staticProjectServer-7EU666GS.js → staticProjectServer-U5DMWKWA.js} +16 -16
  136. package/dist/{status-YM52BQ6S.js → status-6CGT2PJT.js} +7 -7
  137. package/dist/studio/assets/{hyperframes-player-Cjcwveg0.js → hyperframes-player-DFv9heE6.js} +1 -1
  138. package/dist/studio/assets/{index-CwgWmfgx.js → index-Buy06mZy.js} +1 -1
  139. package/dist/studio/assets/{index-ME-Z5jh6.js → index-CTp4loy9.js} +1 -1
  140. package/dist/studio/assets/{index-Dj21zXWA.js → index-N_4Ij4X6.js} +184 -183
  141. package/dist/studio/assets/{index-D11atkBt.css → index-syMK0_9C.css} +1 -1
  142. package/dist/studio/{chunk-5VSXRGIX.js → chunk-NM7BXNZ7.js} +1 -1
  143. package/dist/studio/chunk-NM7BXNZ7.js.map +1 -0
  144. package/dist/studio/{chunk-FOEID72M.js → chunk-QRLKWENQ.js} +2 -2
  145. package/dist/studio/index.d.ts +3 -1
  146. package/dist/studio/index.html +2 -2
  147. package/dist/studio/index.js +911 -691
  148. package/dist/studio/index.js.map +1 -1
  149. package/dist/studio/player.js +2 -2
  150. package/dist/studio/styles.css +1 -1
  151. package/dist/studio/ui.d.ts +3 -2
  152. package/dist/studio/ui.js +1 -1
  153. package/dist/{studio-api-PNQZRPX5.js → studio-api-KMBTUYG5.js} +10 -10
  154. package/dist/{studioServer-SKEFBCNM.js → studioServer-LOJ6IOWA.js} +42 -42
  155. package/dist/{system-EZYZNUFD.js → system-3BTZGJNK.js} +7 -7
  156. package/dist/{telemetry-TN6HXCPW.js → telemetry-MCEZ5JYB.js} +10 -10
  157. package/dist/{timeline-Y6RJY467.js → timeline-XWH4I5CF.js} +17 -17
  158. package/dist/{tokens-HWMQVD2T.js → tokens-6UTTSLVN.js} +2 -2
  159. package/dist/{transcribe-BNSJRR4M.js → transcribe-HC35FL6W.js} +8 -8
  160. package/dist/{transcribe-PZP553TY.js → transcribe-S3NI7SYU.js} +13 -13
  161. package/dist/{updateCheck-2HHCJUTZ.js → updateCheck-3YAKEXSX.js} +2 -2
  162. package/dist/{upgrade-Y55PMAGH.js → upgrade-JQPBH5RY.js} +2 -2
  163. package/dist/{urlDownloader-H6WRI2UE.js → urlDownloader-A5TT3AFE.js} +6 -6
  164. package/dist/{validate-BFYU5VTW.js → validate-BLUPV5H5.js} +17 -17
  165. package/dist/{video-FGSGKRSV.js → video-USXMJDEZ.js} +7 -7
  166. package/package.json +1 -1
  167. package/dist/chunk-HWKCIWQT.js +0 -3578
  168. package/dist/studio/chunk-5VSXRGIX.js.map +0 -1
  169. /package/dist/studio/{chunk-FOEID72M.js.map → chunk-QRLKWENQ.js.map} +0 -0
@@ -21,7 +21,7 @@ import {
21
21
  splitElementInHtml,
22
22
  unwrapElementsFromHtml,
23
23
  wrapElementsInHtml
24
- } from "./chunk-RMHUQGRK.js";
24
+ } from "./chunk-GK5W3SIJ.js";
25
25
  import {
26
26
  atomicTempPath,
27
27
  createFileAtomically,
@@ -34,7 +34,7 @@ import {
34
34
  measureAudio,
35
35
  requiredFfmpeg,
36
36
  resolveLocalAudioPath
37
- } from "./chunk-QC4FJVLM.js";
37
+ } from "./chunk-37EDNWLQ.js";
38
38
  import {
39
39
  PROXY_PARAMS_VERSION,
40
40
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -60,11 +60,11 @@ import {
60
60
  shouldPrewarmProxy,
61
61
  waitForProxy,
62
62
  walkDir
63
- } from "./chunk-TCLGPL65.js";
63
+ } from "./chunk-RVG35AL2.js";
64
64
  import {
65
65
  findTimelineScript,
66
66
  parseGsapScriptAcorn
67
- } from "./chunk-4NWOGZZS.js";
67
+ } from "./chunk-374QQGBQ.js";
68
68
  import {
69
69
  STUDIO_MANUAL_EDITS_PATH
70
70
  } from "./chunk-KC5XXX3X.js";
@@ -81,20 +81,20 @@ import {
81
81
  motionPathPluginUrl,
82
82
  parseFps,
83
83
  sameInstant
84
- } from "./chunk-2YN64IN5.js";
84
+ } from "./chunk-BHOKLX4F.js";
85
85
  import {
86
86
  MagicString,
87
87
  VALID_CANVAS_RESOLUTIONS,
88
88
  unrollComputedTimeline
89
- } from "./chunk-AVUUN2XD.js";
89
+ } from "./chunk-OZUQF4NB.js";
90
90
  import {
91
- AFTER_FONTS_SCRIPT_TYPE,
92
91
  addScenePartsManifest,
93
- deferScriptsUntilFonts,
94
92
  ensureHfIds
95
- } from "./chunk-Z6B37SNM.js";
93
+ } from "./chunk-PGFP4FLW.js";
96
94
  import {
95
+ AFTER_FONTS_SCRIPT_TYPE,
97
96
  RUNTIME_BOOTSTRAP_ATTR,
97
+ deferScriptsUntilFonts,
98
98
  findStartTags,
99
99
  hasCompositionOutsideTemplates,
100
100
  injectTagsAtHeadStart,
@@ -104,7 +104,7 @@ import {
104
104
  resolveRateSpec,
105
105
  sourceTimeAt,
106
106
  stripEmbeddedRuntimeScripts
107
- } from "./chunk-HWKCIWQT.js";
107
+ } from "./chunk-SRXQEQWZ.js";
108
108
  import {
109
109
  ProjectRootMissingError,
110
110
  folderGone,
@@ -117,7 +117,7 @@ import {
117
117
  import {
118
118
  isWithinProjectRoot,
119
119
  readProjectFile
120
- } from "./chunk-ZJUKFZ2P.js";
120
+ } from "./chunk-VDQCNWQI.js";
121
121
  import {
122
122
  FFPROBE_PATH_ENV,
123
123
  findFfBinary
@@ -2227,10 +2227,10 @@ import { join as join6 } from "path";
2227
2227
  import { bodyLimit } from "hono/body-limit";
2228
2228
  import {
2229
2229
  closeSync as closeSync3,
2230
- existsSync as existsSync3,
2230
+ existsSync as existsSync4,
2231
2231
  lstatSync as lstatSync22,
2232
2232
  openSync as openSync3,
2233
- readFileSync as readFileSync32,
2233
+ readFileSync as readFileSync52,
2234
2234
  readlinkSync,
2235
2235
  unlinkSync as unlinkSync2,
2236
2236
  rmSync as rmSync3,
@@ -2240,10 +2240,17 @@ import {
2240
2240
  readdirSync as readdirSync22,
2241
2241
  realpathSync as realpathSync2
2242
2242
  } from "fs";
2243
- import { resolve as resolve22, dirname as dirname32, join as join52 } from "path";
2243
+ import { resolve as resolve22, dirname as dirname32, join as join62 } from "path";
2244
+ import { readFileSync as readFileSync23 } from "fs";
2245
+ import { createHash as createHash4 } from "crypto";
2246
+ import { existsSync as existsSync3, readFileSync as readFileSync7 } from "fs";
2247
+ import { join as join23 } from "path";
2248
+ import { existsSync as existsSync22 } from "fs";
2249
+ import { randomUUID as randomUUID7 } from "crypto";
2250
+ import { dirname as dirname5, relative as relative3, resolve as resolve5, sep as sep3 } from "path";
2244
2251
  import { spawn } from "child_process";
2245
2252
  import {
2246
- existsSync as existsSync4,
2253
+ existsSync as existsSync32,
2247
2254
  writeFileSync as writeFileSync4,
2248
2255
  mkdirSync as mkdirSync5,
2249
2256
  lstatSync as lstatSync3,
@@ -2252,15 +2259,15 @@ import {
2252
2259
  rmSync as rmSync4,
2253
2260
  statSync as statSync4
2254
2261
  } from "fs";
2255
- import { dirname as dirname5, join as join23 } from "path";
2262
+ import { dirname as dirname23, join as join32 } from "path";
2256
2263
  import { spawnSync } from "child_process";
2257
2264
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync22, writeFileSync as writeFileSync23 } from "fs";
2258
2265
  import { tmpdir as tmpdir2 } from "os";
2259
- import { basename as basename2, join as join32 } from "path";
2260
- import { readdirSync as readdirSync4, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync32 } from "fs";
2266
+ import { basename as basename2, join as join42 } from "path";
2267
+ import { readdirSync as readdirSync4, readFileSync as readFileSync32, unlinkSync, writeFileSync as writeFileSync32 } from "fs";
2261
2268
  import { Buffer as Buffer2 } from "buffer";
2262
- import { join as join42, relative as relative3 } from "path";
2263
- import { readFileSync as readFileSync23, statSync as statSync22 } from "fs";
2269
+ import { join as join52, relative as relative22 } from "path";
2270
+ import { readFileSync as readFileSync42, statSync as statSync22 } from "fs";
2264
2271
 
2265
2272
  // ../parsers/dist/gsapConstants.js
2266
2273
  var PROPERTY_GROUPS = {
@@ -2335,23 +2342,43 @@ function knownStart(animation) {
2335
2342
  if (animation.resolvedStart !== void 0) return animation.resolvedStart;
2336
2343
  return typeof animation.position === "number" ? animation.position : void 0;
2337
2344
  }
2338
- function positionHoldForAnimation(animation, animations) {
2345
+ var tweenSignature = (a) => JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);
2346
+ function holdScope(before, previous) {
2347
+ const kept = new Set(previous?.map(tweenSignature));
2348
+ const held = /* @__PURE__ */ new Map();
2349
+ for (const a of before.filter((b) => b.method === "set" && b.properties?.data === "hf-hold")) {
2350
+ const props = held.get(a.targetSelector) ?? /* @__PURE__ */ new Map();
2351
+ for (const [property, value] of Object.entries(a.properties)) props.set(property, value);
2352
+ held.set(a.targetSelector, props);
2353
+ }
2354
+ return {
2355
+ touched: (a) => previous !== null && !kept.has(tweenSignature(a)),
2356
+ held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
2357
+ };
2358
+ }
2359
+ function keyframeHoldForAnimation(animation, animations, scope) {
2339
2360
  if (!animation.keyframes) return null;
2340
- const start = knownStart(animation) ?? 0;
2341
- if (!(start > 1e-3)) return null;
2361
+ const start = knownStart(animation);
2362
+ const lone = animation.keyframes.keyframes.length === 1;
2363
+ if (start === void 0 || !(start > 1e-3) && !lone) return null;
2342
2364
  const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
2343
2365
  if (!atStart) return null;
2344
2366
  const earlier = animations.filter((other) => {
2345
2367
  const otherStart = knownStart(other);
2346
2368
  return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
2347
2369
  });
2348
- const position = {};
2370
+ const touched = scope.touched(animation);
2371
+ const pinned = scope.held(animation.targetSelector);
2372
+ const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
2373
+ const pins = (group, property, value) => group === "position" && start > 1e-3 || (group === "position" || group === "size") && (lone && touched || kept(property, value));
2374
+ const hold = {};
2349
2375
  for (const [property, value] of Object.entries(atStart.properties)) {
2350
- if (classifyPropertyGroup2(property) !== "position" || typeof value !== "number") continue;
2376
+ if (!pins(classifyPropertyGroup2(property), property, value) || typeof value !== "number")
2377
+ continue;
2351
2378
  if (earlier.some((other) => writesProperty(other, property))) continue;
2352
- position[property] = value;
2379
+ hold[property] = value;
2353
2380
  }
2354
- return Object.keys(position).length > 0 ? position : null;
2381
+ return Object.keys(hold).length > 0 ? hold : null;
2355
2382
  }
2356
2383
  function authorsKeyframes(anim) {
2357
2384
  return anim.keyframes !== void 0 || anim.hasUnresolvedKeyframes === true;
@@ -2394,6 +2421,10 @@ function buildArcPath(coords, curviness, autoRotate, isCubic) {
2394
2421
  }
2395
2422
  return { arcPath: { enabled: true, autoRotate, segments }, waypoints };
2396
2423
  }
2424
+ function plainPercentKey(percentage) {
2425
+ const text2 = Math.abs(percentage) < 1e-6 ? percentage.toFixed(20).replace(/\.?0+$/, "") : String(percentage);
2426
+ return `${text2}%`;
2427
+ }
2397
2428
  var CSS_IDENTITY = {
2398
2429
  opacity: 1,
2399
2430
  autoAlpha: 1,
@@ -3333,9 +3364,6 @@ function tweenCallToAnimation(call, scope, source, identifierBindings) {
3333
3364
  properties[key] = val;
3334
3365
  }
3335
3366
  }
3336
- if (keyframesData && typeof vars.easeEach === "string") {
3337
- keyframesData.easeEach = vars.easeEach;
3338
- }
3339
3367
  if (motionPathResult) {
3340
3368
  const { waypoints } = motionPathResult;
3341
3369
  if (!keyframesData) {
@@ -4311,7 +4339,7 @@ function convertArrayKeyframesToObject(script, target) {
4311
4339
  if (!timing) return script;
4312
4340
  const entries = els.map((el, i) => {
4313
4341
  const { duration: _duration, ...record } = records[i];
4314
- return `${JSON.stringify(`${timing.percentages[i]}%`)}: ${recordToCode(record)}`;
4342
+ return `${JSON.stringify(plainPercentKey(timing.percentages[i]))}: ${recordToCode(record)}`;
4315
4343
  });
4316
4344
  const ms = new MagicString(script);
4317
4345
  ms.overwrite(
@@ -4373,7 +4401,13 @@ function addKeyframeToScript(script, animationId, percentage, properties, ease,
4373
4401
  ms.overwrite(existing.prop.value.start, existing.prop.value.end, recordToCode(targetRecord));
4374
4402
  }
4375
4403
  } else {
4376
- insertNewKeyframe(ms, kfNode, percentage, `${percentage}%`, recordToCode(targetRecord));
4404
+ insertNewKeyframe(
4405
+ ms,
4406
+ kfNode,
4407
+ percentage,
4408
+ plainPercentKey(percentage),
4409
+ recordToCode(targetRecord)
4410
+ );
4377
4411
  }
4378
4412
  for (const [prop, rec] of [...endpointOverwrites, ...backfillOverwrites]) {
4379
4413
  ms.overwrite(prop.value.start, prop.value.end, recordToCode(rec));
@@ -4472,7 +4506,12 @@ function moveKeyframeInScript(script, animationId, fromPercentage, toPercentage)
4472
4506
  }
4473
4507
  entries.push({ pct: toPercentage, record: valueNodeToRecord(match.prop.value, src) });
4474
4508
  entries.sort((a, b) => a.pct - b.pct);
4475
- const body = entries.map((e) => `${JSON.stringify(`${e.pct}%`)}: ${recordToCode(e.record)}`).join(", ");
4509
+ const pctProps = new Set(percentagePropsOf(kfNode));
4510
+ const kept = (kfNode.properties ?? []).filter((p) => !pctProps.has(p)).map((p) => src.slice(p.start, p.end));
4511
+ const body = [
4512
+ ...entries.map((e) => `${JSON.stringify(plainPercentKey(e.pct))}: ${recordToCode(e.record)}`),
4513
+ ...kept
4514
+ ].join(", ");
4476
4515
  const ms = new MagicString(src);
4477
4516
  ms.overwrite(kfNode.start, kfNode.end, `{ ${body} }`);
4478
4517
  return ms.toString();
@@ -4491,7 +4530,7 @@ function resizeKeyframedTweenInScript(script, animationId, newPosition, newDurat
4491
4530
  }
4492
4531
  const ms = new MagicString(src);
4493
4532
  for (const { keyNode, to } of edits) {
4494
- ms.overwrite(keyNode.start, keyNode.end, JSON.stringify(`${to}%`));
4533
+ ms.overwrite(keyNode.start, keyNode.end, JSON.stringify(plainPercentKey(to)));
4495
4534
  }
4496
4535
  overwritePosition(ms, target.call, newPosition);
4497
4536
  upsertProp(ms, target.call.varsArg, "duration", newDuration);
@@ -4594,7 +4633,7 @@ function buildKeyframeObjectCode(keyframes, easeEach) {
4594
4633
  const props = Object.entries(kf.properties).map(([k, v]) => `${safeKey(k)}: ${valueToCode(v)}`);
4595
4634
  if (kf.ease) props.push(`ease: ${valueToCode(kf.ease)}`);
4596
4635
  if (kf.auto) props.push(`_auto: 1`);
4597
- return `${JSON.stringify(`${kf.percentage}%`)}: { ${props.join(", ")} }`;
4636
+ return `${JSON.stringify(plainPercentKey(kf.percentage))}: { ${props.join(", ")} }`;
4598
4637
  });
4599
4638
  if (easeEach) entries.push(`easeEach: ${valueToCode(easeEach)}`);
4600
4639
  return `{ ${entries.join(", ")} }`;
@@ -4748,7 +4787,8 @@ function addGroupAnimToScript(script, anim, propSet) {
4748
4787
  pos,
4749
4788
  anim.duration ?? 0.5,
4750
4789
  groupKeyframes,
4751
- anim.keyframes.easeEach ?? anim.ease
4790
+ anim.keyframes.ease ?? anim.ease,
4791
+ anim.keyframes.easeEach
4752
4792
  );
4753
4793
  }
4754
4794
  const groupProperties = filterGroupProperties(anim.properties, propSet);
@@ -5021,15 +5061,20 @@ function removeStudioHoldSets(script, parsed) {
5021
5061
  for (const hold of staleHolds) removeCallFromMagicString(ms, hold.call, script);
5022
5062
  return ms.toString();
5023
5063
  }
5024
- function syncPositionHoldsBeforeKeyframes(script) {
5064
+ function syncPositionHoldsBeforeKeyframes(script, previous) {
5025
5065
  const parsed = parseGsapScriptAcornForWrite(script);
5026
5066
  if (!parsed) return script;
5067
+ const tweensOf = (text2) => parseGsapScriptAcornForWrite(text2)?.located.map((entry) => entry.animation) ?? null;
5068
+ const scope = holdScope(
5069
+ parsed.located.map((entry) => entry.animation),
5070
+ previous === void 0 ? null : tweensOf(previous)
5071
+ );
5027
5072
  let result = removeStudioHoldSets(script, parsed);
5028
5073
  const current = parseGsapScriptAcornForWrite(result);
5029
5074
  if (!current) return result;
5030
5075
  const animations = current.located.map((entry) => entry.animation);
5031
5076
  for (const animation of animations) {
5032
- const position = positionHoldForAnimation(animation, animations);
5077
+ const position = keyframeHoldForAnimation(animation, animations, scope);
5033
5078
  if (!position) continue;
5034
5079
  result = insertInheritedStateSetInScript(result, animation.targetSelector, 0, {
5035
5080
  ...position,
@@ -5038,6 +5083,62 @@ function syncPositionHoldsBeforeKeyframes(script) {
5038
5083
  }
5039
5084
  return result;
5040
5085
  }
5086
+ var roundPct = (pct) => Math.round(pct * 1e3) / 1e3;
5087
+ var LINEAR_RUN = /* @__PURE__ */ new Set(["none", "linear"]);
5088
+ var PLACED_BY_TIMELINE = /\.(?:add|addLabel|addPause|call|repeat|yoyo|duration|timeScale)\s*\(|\b(?:repeat|yoyo)\s*:/;
5089
+ function linearPercentageKeys(animation) {
5090
+ const data = animation.keyframes;
5091
+ if (data?.format !== "percentage" || data.fromMotionPath || animation.arcPath) return null;
5092
+ if (animation.durationUnresolved || !LINEAR_RUN.has(animation.ease || "none")) return null;
5093
+ return data.keyframes;
5094
+ }
5095
+ var loops = (animation) => Boolean(animation.extras?.repeat || animation.extras?.yoyo);
5096
+ function trimmableKeyframes(animation) {
5097
+ const keyframes = loops(animation) ? null : linearPercentageKeys(animation);
5098
+ const { duration, position } = animation;
5099
+ if (!keyframes || typeof position !== "number" || typeof duration !== "number") return null;
5100
+ return duration > 0 ? { keyframes, duration, position } : null;
5101
+ }
5102
+ function trailingSpanTrim(animation) {
5103
+ const tween = trimmableKeyframes(animation);
5104
+ if (!tween) return null;
5105
+ const last = Math.max(...tween.keyframes.map((keyframe) => keyframe.percentage));
5106
+ if (tween.keyframes.length < 2 || !(last > 0) || last >= 99.999) return null;
5107
+ return {
5108
+ position: tween.position,
5109
+ duration: Math.round(tween.duration * last * 10) / 1e3,
5110
+ pctRemap: tween.keyframes.map(({ percentage: from }) => ({
5111
+ from,
5112
+ to: roundPct(from / last * 100)
5113
+ }))
5114
+ };
5115
+ }
5116
+ function trimTrailingKeyframeSpans(previous, script) {
5117
+ if (script === previous) return script;
5118
+ const located = parseGsapScriptAcornForWrite(script)?.located ?? [];
5119
+ const placedAfter = ({ animation: a }) => !a.global && (a.implicitPosition || typeof a.position !== "number");
5120
+ if (PLACED_BY_TIMELINE.test(script) || located.some(placedAfter)) return script;
5121
+ const trims = located.flatMap((entry) => {
5122
+ const trim = trailingSpanTrim(entry.animation);
5123
+ return trim ? [{ entry, trim }] : [];
5124
+ });
5125
+ if (!trims.length) return script;
5126
+ const source = (text2, { call }) => `${call.method}${text2.slice(call.node.arguments[0]?.start, call.node.end)}`;
5127
+ const previousTweens = parseGsapScriptAcornForWrite(previous)?.located ?? [];
5128
+ const untouched = new Set(previousTweens.map((entry) => source(previous, entry)));
5129
+ let result = script;
5130
+ for (const { entry, trim } of trims) {
5131
+ if (untouched.has(source(script, entry))) continue;
5132
+ result = resizeKeyframedTweenInScript(
5133
+ result,
5134
+ entry.id,
5135
+ trim.position,
5136
+ trim.duration,
5137
+ trim.pctRemap
5138
+ );
5139
+ }
5140
+ return result;
5141
+ }
5041
5142
  function computeForwardBaselines(matching, splitTime) {
5042
5143
  const before = [];
5043
5144
  const acc = {};
@@ -5330,31 +5431,26 @@ function unrollDynamicAnimations(script, animationId, elements) {
5330
5431
  }
5331
5432
 
5332
5433
  // ../studio-server/dist/index.js
5333
- import { existsSync as existsSync22 } from "fs";
5334
- import { randomUUID as randomUUID7 } from "crypto";
5335
- import { dirname as dirname23, relative as relative22, resolve as resolve5, sep as sep3 } from "path";
5336
- import { createReadStream as createReadStream2, existsSync as existsSync5, readFileSync as readFileSync52, statSync as statSync42 } from "fs";
5434
+ import { createReadStream as createReadStream2, existsSync as existsSync5, readFileSync as readFileSync62, statSync as statSync42 } from "fs";
5337
5435
  import { Readable } from "stream";
5338
5436
  import { join as join8, resolve as resolve42 } from "path";
5339
- import { createHash as createHash4 } from "crypto";
5437
+ import { createHash as createHash23 } from "crypto";
5340
5438
 
5341
5439
  // ../core/dist/studioPreviewMark.js
5342
5440
  var STUDIO_PREVIEW_MARK_META = "hyperframes-studio-preview";
5343
5441
  var STUDIO_PREVIEW_LAZY_ATTR = "data-hf-preview-lazy";
5344
5442
 
5345
5443
  // ../studio-server/dist/index.js
5346
- import { existsSync as existsSync42, readFileSync as readFileSync42 } from "fs";
5347
- import { join as join62 } from "path";
5348
5444
  import { readlinkSync as readlinkSync2 } from "fs";
5349
5445
  import { dirname as dirname42, join as join7, parse as parse2, relative as relative32, resolve as resolve32 } from "path";
5350
- import { readFileSync as readFileSync62 } from "fs";
5446
+ import { readFileSync as readFileSync72 } from "fs";
5351
5447
  import { join as join9 } from "path";
5352
5448
  import { streamSSE } from "hono/streaming";
5353
- import { existsSync as existsSync6, readFileSync as readFileSync72, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync5 } from "fs";
5449
+ import { existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync5 } from "fs";
5354
5450
  import { join as join10 } from "path";
5355
5451
  import { HTTPException } from "hono/http-exception";
5356
5452
  import { open, stat as stat2 } from "fs/promises";
5357
- import { createHash as createHash23 } from "crypto";
5453
+ import { createHash as createHash32 } from "crypto";
5358
5454
  import {
5359
5455
  closeSync as closeSync22,
5360
5456
  fstatSync as fstatSync22,
@@ -5369,8 +5465,6 @@ import {
5369
5465
  } from "fs";
5370
5466
  import { join as join11 } from "path";
5371
5467
  import { createHash as createHash42, randomUUID as randomUUID22 } from "crypto";
5372
- import { readFileSync as readFileSync8 } from "fs";
5373
- import { createHash as createHash32 } from "crypto";
5374
5468
  import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
5375
5469
  import { join as join12, resolve as resolve52 } from "path";
5376
5470
  import { execFile } from "child_process";
@@ -5477,339 +5571,175 @@ function getMimeType(path) {
5477
5571
  function isAudioFile(name) {
5478
5572
  return (getMimeType(name) ?? "").startsWith("audio/");
5479
5573
  }
5480
- var SAMPLE_RATE = 4e3;
5481
- var PEAK_COUNT = 4e3;
5482
- var WAVEFORM_CACHE_VERSION = "v2";
5483
- function buildWaveformCacheKey(assetPath, fingerprint) {
5484
- const name = assetPath.replace(/[/\\]/g, "_");
5485
- if (!fingerprint) return `${WAVEFORM_CACHE_VERSION}_${name}.json`;
5486
- const stamp = `${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}`;
5487
- return `${WAVEFORM_CACHE_VERSION}_${name}_${stamp}.json`;
5488
- }
5489
- function computePeaks(floats, count) {
5490
- const step = floats.length / count;
5491
- const peaks = [];
5492
- for (let i = 0; i < count; i++) {
5493
- const start = Math.floor(i * step);
5494
- const end = Math.min(Math.floor((i + 1) * step), floats.length);
5495
- let max = 0;
5496
- for (let j = start; j < end; j++) {
5497
- const abs = Math.abs(floats[j] ?? 0);
5498
- if (abs > max) max = abs;
5499
- }
5500
- peaks.push(max);
5574
+ function rewriteRelativePaths(root, compPath, projectDir) {
5575
+ const assetExists = (path) => existsSync3(join23(projectDir, path));
5576
+ rewriteAssetPaths(
5577
+ root.querySelectorAll("[src], [href]"),
5578
+ compPath,
5579
+ (el, attr) => el.getAttribute(attr),
5580
+ (el, attr, value) => el.setAttribute(attr, value),
5581
+ assetExists
5582
+ );
5583
+ rewriteInlineStyleAssetUrls(
5584
+ root.querySelectorAll("[style]"),
5585
+ compPath,
5586
+ (el) => el.getAttribute("style"),
5587
+ (el, value) => el.setAttribute("style", value),
5588
+ assetExists
5589
+ );
5590
+ for (const styleEl of root.querySelectorAll("style")) {
5591
+ styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
5501
5592
  }
5502
- const maxPeak = Math.max(...peaks, 1e-3);
5503
- return peaks.map((p) => p / maxPeak);
5504
- }
5505
- function decodeAudioPeaks(audioPath) {
5506
- return new Promise((resolvePromise, reject) => {
5507
- const proc = spawn(
5508
- findFfBinary("ffmpeg") ?? "ffmpeg",
5509
- [
5510
- "-i",
5511
- audioPath,
5512
- "-af",
5513
- "atrim=start_sample=1152",
5514
- "-f",
5515
- "f32le",
5516
- "-ac",
5517
- "1",
5518
- "-ar",
5519
- String(SAMPLE_RATE),
5520
- "-vn",
5521
- "pipe:1"
5522
- ],
5523
- { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
5524
- );
5525
- const chunks = [];
5526
- proc.stdout?.on("data", (chunk) => chunks.push(chunk));
5527
- proc.on("close", (code) => {
5528
- if (code !== 0 && chunks.length === 0) {
5529
- reject(new Error(`ffmpeg exited with code ${code}`));
5530
- return;
5531
- }
5532
- const buf = Buffer.concat(chunks);
5533
- const numSamples = Math.floor(buf.length / 4);
5534
- if (numSamples === 0) {
5535
- reject(new Error("ffmpeg produced no audio samples"));
5536
- return;
5537
- }
5538
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4);
5539
- resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT));
5540
- });
5541
- proc.on("error", reject);
5542
- });
5543
- }
5544
- async function generateWaveformCache(projectDir, assetPath) {
5545
- const audioPath = join23(projectDir, assetPath);
5546
- if (!existsSync4(audioPath)) return;
5547
- const stats = statSync4(audioPath);
5548
- const cacheDir = join23(projectDir, ".waveform-cache");
5549
- const cachePath = join23(cacheDir, buildWaveformCacheKey(assetPath, stats));
5550
- if (isWaveformCacheDirectory(cacheDir) && existsSync4(cachePath)) return;
5551
- const peaks = await decodeAudioPeaks(audioPath);
5552
- writeWaveformCache(cachePath, peaks);
5553
5593
  }
5554
- function isWaveformCacheDirectory(cacheDir) {
5555
- return lstatSync3(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
5594
+ function escapeLeadingDigitIdent(id) {
5595
+ return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`;
5556
5596
  }
5557
- function writeWaveformCache(cachePath, peaks) {
5558
- const cacheDir = dirname5(cachePath);
5559
- try {
5560
- mkdirSync5(cacheDir, { mode: 448 });
5561
- } catch (error) {
5562
- if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
5563
- }
5564
- if (!isWaveformCacheDirectory(cacheDir)) {
5565
- throw new Error("Waveform cache must be a directory, not a symlink");
5597
+ var REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g;
5598
+ function fixDigitLeadingIdSelectors(root) {
5599
+ const digitIds = /* @__PURE__ */ new Set();
5600
+ for (const el of root.querySelectorAll("[id]")) {
5601
+ const id = el.getAttribute("id");
5602
+ if (id && /^\d/.test(id)) digitIds.add(id);
5566
5603
  }
5567
- const stagingDir = mkdtempSync(join23(cacheDir, ".waveform-"));
5568
- try {
5569
- const stagingPath = join23(stagingDir, "peaks.json");
5570
- writeFileSync4(stagingPath, JSON.stringify(peaks), { flag: "wx" });
5571
- renameSync3(stagingPath, cachePath);
5572
- } finally {
5573
- try {
5574
- rmSync4(stagingDir, { recursive: true, force: true });
5575
- } catch {
5604
+ if (digitIds.size === 0) return;
5605
+ for (const styleEl of root.querySelectorAll("style")) {
5606
+ let css = styleEl.textContent || "";
5607
+ for (const id of digitIds) {
5608
+ const pattern = new RegExp(`#${id.replace(REGEXP_SPECIALS, "\\$&")}(?![\\w-])`, "g");
5609
+ css = css.replace(pattern, `#${escapeLeadingDigitIdent(id)}`);
5576
5610
  }
5611
+ styleEl.textContent = css;
5577
5612
  }
5578
5613
  }
5579
- var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
5580
- var AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac|flac)$/i;
5581
- var defaultRunner = spawnSync;
5582
- var FFPROBE_MISSING = "not checked: ffprobe was not found. Install FFmpeg or set HYPERFRAMES_FFPROBE_PATH.";
5583
- function validateUploadedMedia(filePath, runner = defaultRunner) {
5584
- const isVideo = VIDEO_EXT.test(filePath);
5585
- const isAudio = AUDIO_EXT.test(filePath);
5586
- if (!isVideo && !isAudio) {
5587
- return { ok: true };
5588
- }
5589
- const ffprobe = findFfBinary("ffprobe") ?? (runner === defaultRunner ? void 0 : "ffprobe");
5590
- if (!ffprobe) return { ok: true, unchecked: FFPROBE_MISSING };
5591
- const result = runner(
5592
- ffprobe,
5593
- ["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
5594
- { windowsHide: true }
5595
- );
5596
- if (result.error && process.env[FFPROBE_PATH_ENV]?.trim()) {
5597
- return {
5598
- ok: false,
5599
- reason: `${FFPROBE_PATH_ENV} names "${ffprobe}", which cannot run: fix it or unset it`
5600
- };
5601
- }
5602
- if (result.error?.code === "ENOENT") {
5603
- return { ok: true, unchecked: FFPROBE_MISSING };
5604
- }
5605
- if (result.status !== 0) {
5606
- return { ok: false, reason: "ffprobe failed to read the media file" };
5614
+ function extractFullDocumentParts(rawHtml, compPath, projectDir) {
5615
+ const { document: doc } = parseHTML(rawHtml);
5616
+ const rewriteTargets = [doc.head, doc.body].filter(Boolean);
5617
+ for (const target of rewriteTargets) {
5618
+ rewriteRelativePaths(target, compPath, projectDir);
5607
5619
  }
5608
- return checkStreams(String(result.stdout || "{}"), isVideo, isAudio);
5620
+ fixDigitLeadingIdSelectors(doc);
5621
+ const headContent = doc.head?.innerHTML ?? "";
5622
+ const bodyContent = doc.body?.innerHTML ?? "";
5623
+ const htmlEl = doc.documentElement;
5624
+ const htmlAttrs = extractElementAttrs(htmlEl);
5625
+ const bodyAttrs = doc.body ? extractElementAttrs(doc.body) : "";
5626
+ return { headContent, bodyContent, htmlAttrs, bodyAttrs };
5609
5627
  }
5610
- function checkStreams(stdout, isVideo, isAudio) {
5611
- try {
5612
- const parsed = JSON.parse(stdout);
5613
- const streams = parsed.streams ?? [];
5614
- const hasVideo = streams.some((stream) => stream.codec_type === "video");
5615
- const hasAudio = streams.some((stream) => stream.codec_type === "audio");
5616
- if (isVideo && !hasVideo) {
5617
- return { ok: false, reason: "no supported video stream found" };
5618
- }
5619
- if (isAudio && !hasAudio) {
5620
- return { ok: false, reason: "no supported audio stream found" };
5628
+ function extractTemplateInnerHtml(rawComp) {
5629
+ const { document: doc } = parseHTML(rawComp);
5630
+ const template = doc.querySelector("template");
5631
+ return template ? template.innerHTML : null;
5632
+ }
5633
+ function escapeAttrValue(value) {
5634
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
5635
+ }
5636
+ function extractElementAttrs(el) {
5637
+ const parts = [];
5638
+ for (let i = 0; i < el.attributes.length; i++) {
5639
+ const attr = el.attributes[i];
5640
+ if (attr.value === "") {
5641
+ parts.push(attr.name);
5642
+ } else {
5643
+ parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
5621
5644
  }
5622
- return { ok: true };
5623
- } catch {
5624
- return { ok: false, reason: "ffprobe returned unreadable media metadata" };
5625
5645
  }
5646
+ return parts.join(" ");
5626
5647
  }
5627
- function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
5628
- const tempDir = mkdtempSync2(join32(tmpdir2(), "hyperframes-upload-"));
5629
- const tempPath = join32(tempDir, basename2(fileName));
5630
- try {
5631
- writeFileSync23(tempPath, buffer);
5632
- return validateUploadedMedia(tempPath, runner);
5633
- } finally {
5634
- rmSync22(tempDir, { recursive: true, force: true });
5648
+ var NON_RENDERED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"]);
5649
+ function promoteTemplateCompositionId(rawComp, body) {
5650
+ let templateCompositionId;
5651
+ for (const tag of rawComp.matchAll(/<template\b[^>]*/gi)) {
5652
+ const id = /\bdata-composition-id\s*=\s*["']([^"']+)["']/i.exec(tag[0] ?? "")?.[1];
5653
+ if (id) {
5654
+ templateCompositionId = id;
5655
+ break;
5656
+ }
5635
5657
  }
5658
+ if (!templateCompositionId) return;
5659
+ if (body.querySelector("[data-composition-id]")) return;
5660
+ const root = Array.from(body.children).find((el) => !NON_RENDERED_TAGS.has(el.tagName));
5661
+ root?.setAttribute("data-composition-id", templateCompositionId);
5636
5662
  }
5637
- var DEFAULT_KEEP_PER_FILE = 10;
5638
- function backupKeyForPath(path) {
5639
- return Buffer2.from(path, "utf-8").toString("base64url");
5663
+ function tagRootCompositionFile(bodyHtml, compPath) {
5664
+ const match = bodyHtml.match(/<[a-zA-Z][^>]*\bdata-composition-id=/);
5665
+ if (match?.index == null) return bodyHtml;
5666
+ const tagEnd = bodyHtml.indexOf(">", match.index);
5667
+ if (tagEnd === -1) return bodyHtml;
5668
+ if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
5669
+ return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
5640
5670
  }
5641
- function timestampPrefix() {
5642
- return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5671
+ function rootHeadContent(rootHtml) {
5672
+ return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
5643
5673
  }
5644
- function backupPathForResponse(projectDir, backupPath) {
5645
- if (!backupPath) return null;
5646
- const rel = relative3(projectDir, backupPath);
5647
- if (!rel || rel.startsWith("..")) return null;
5648
- return rel.split("\\").join("/");
5649
- }
5650
- function snapshotBeforeWrite(projectDir, absPath, options = {}) {
5651
- if (!isSafePath(projectDir, absPath)) return { backupPath: null };
5652
- try {
5653
- const content = readFileSync7(absPath);
5654
- const relativePath = relative3(projectDir, absPath);
5655
- const backupDir = join42(projectDir, ".hyperframes", "backup");
5656
- mkdirWithinProject(projectDir, backupDir);
5657
- const backupKey = backupKeyForPath(relativePath);
5658
- const backupPath = nextBackupPath(backupDir, backupKey);
5659
- writeFileSync32(backupPath, content);
5660
- pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
5661
- return { backupPath };
5662
- } catch (error) {
5663
- if (isProjectRootMissing(error)) throw error;
5664
- if (error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) {
5665
- return { backupPath: null };
5666
- }
5667
- return { backupPath: null, error: error instanceof Error ? error.message : String(error) };
5668
- }
5669
- }
5670
- function nextBackupPath(backupDir, backupKey) {
5671
- const base = `${timestampPrefix()}-${backupKey}`;
5672
- let candidate = join42(backupDir, base);
5673
- let counter = 2;
5674
- while (true) {
5675
- try {
5676
- readFileSync7(candidate);
5677
- } catch (error) {
5678
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
5679
- return candidate;
5680
- }
5681
- throw error;
5682
- }
5683
- candidate = join42(backupDir, `${base}-${counter}`);
5684
- counter += 1;
5685
- }
5686
- }
5687
- function pruneBackups(backupDir, backupKey, keepPerFile) {
5688
- const keep = Math.max(1, Math.floor(keepPerFile));
5689
- const suffix = `-${backupKey}`;
5690
- const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
5691
- const matches = readdirSync4(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join42(backupDir, name)).sort((a, b) => {
5692
- return b.localeCompare(a);
5693
- });
5694
- for (const file of matches.slice(keep)) {
5695
- try {
5696
- unlinkSync(file);
5697
- } catch {
5698
- }
5699
- }
5700
- }
5701
- var PROJECT_DIR_MISSING = { error: "not found", why: "project_dir_missing" };
5702
- var projectDirMissing = (c) => c.json(PROJECT_DIR_MISSING, 404);
5703
- function replaceWithProjectDirMissing(c, hostHeaders) {
5704
- hostHeaders.delete("content-type");
5705
- c.res = void 0;
5706
- c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
5707
- }
5708
- function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (path, content) => replaceFileAtomically(path, content, statSync22(path).mode)) {
5709
- const prepared = mutations.map((mutation) => ({
5710
- ...mutation,
5711
- before: mutation.before ?? readFileSync23(mutation.absPath, "utf-8")
5712
- }));
5713
- const results = [];
5714
- const attempted = [];
5715
- for (const mutation of prepared)
5716
- assertExpectedVersion(mutation, readFileSync23(mutation.absPath, "utf-8"));
5717
- try {
5718
- for (const mutation of prepared) {
5719
- results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted));
5720
- }
5721
- return results;
5722
- } catch (error) {
5723
- const rollbackErrors = [];
5724
- for (const mutation of attempted.reverse()) {
5725
- try {
5726
- if (!mutation.written || readFileSync23(mutation.absPath, "utf-8") === mutation.after)
5727
- writeFile2(mutation.absPath, mutation.before, "utf-8");
5728
- clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
5729
- } catch (rollbackError) {
5730
- rollbackErrors.push(rollbackError);
5731
- }
5732
- }
5733
- if (rollbackErrors.length > 0) {
5734
- throw new AggregateError(
5735
- [error, ...rollbackErrors],
5736
- "File mutation failed and rollback did not complete"
5737
- );
5738
- }
5739
- throw error;
5674
+ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
5675
+ const compFile = join23(projectDir, compPath);
5676
+ if (!existsSync3(compFile)) return null;
5677
+ const rawComp = rawOverride ?? readFileSync7(compFile, "utf-8");
5678
+ let compHeadContent = "";
5679
+ let rewrittenContent;
5680
+ let htmlAttrs = "";
5681
+ let bodyAttrs = "";
5682
+ const templateInner = extractTemplateInnerHtml(rawComp);
5683
+ if (templateInner != null) {
5684
+ const { document: contentDoc } = parseHTML(
5685
+ `<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
5686
+ );
5687
+ rewriteRelativePaths(contentDoc, compPath, projectDir);
5688
+ fixDigitLeadingIdSelectors(contentDoc);
5689
+ promoteTemplateCompositionId(rawComp, contentDoc.body);
5690
+ rewrittenContent = contentDoc.body.innerHTML || templateInner;
5691
+ } else if (isFullHtmlDocument(rawComp)) {
5692
+ const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
5693
+ compHeadContent = parts.headContent;
5694
+ rewrittenContent = parts.bodyContent;
5695
+ htmlAttrs = parts.htmlAttrs;
5696
+ bodyAttrs = parts.bodyAttrs;
5697
+ } else {
5698
+ const { document: contentDoc } = parseHTML(
5699
+ `<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
5700
+ );
5701
+ rewriteRelativePaths(contentDoc, compPath, projectDir);
5702
+ fixDigitLeadingIdSelectors(contentDoc);
5703
+ rewrittenContent = contentDoc.body.innerHTML || rawComp;
5740
5704
  }
5741
- }
5742
- function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted) {
5743
- const current = readFileSync23(mutation.absPath, "utf-8");
5744
- assertExpectedVersion(mutation, current);
5745
- if (mutation.after === mutation.before) {
5746
- return {
5747
- ...mutation,
5748
- before: current,
5749
- changed: false,
5750
- backupPath: null,
5751
- version: fileContentVersion(mutation.before),
5752
- writeToken: null
5753
- };
5705
+ rewrittenContent = stripEmbeddedRuntimeScripts(rewrittenContent);
5706
+ const { document: scriptsDoc } = parseHTML(
5707
+ `<!DOCTYPE html><html><head></head><body>${rewrittenContent}</body></html>`
5708
+ );
5709
+ deferScriptsUntilFonts(scriptsDoc);
5710
+ rewrittenContent = scriptsDoc.body.innerHTML;
5711
+ rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
5712
+ const indexPath = join23(projectDir, "index.html");
5713
+ let headContent = "";
5714
+ if (existsSync3(indexPath)) headContent = rootHeadContent(readFileSync7(indexPath, "utf-8"));
5715
+ if (baseHref && !hasBaseElement(headContent)) {
5716
+ headContent = `<base href="${baseHref}">
5717
+ ${headContent}`;
5754
5718
  }
5755
- const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
5756
- if (backup.error) throw new Error(`backup failed: ${backup.error}`);
5757
- assertExpectedVersion(mutation, readFileSync23(mutation.absPath, "utf-8"));
5758
- const before = current;
5759
- const version = fileContentVersion(mutation.after);
5760
- const writeToken = createWriteToken(requestToken);
5761
- const attempt = { ...mutation, before, version, writeToken, written: false };
5762
- attempted.push(attempt);
5763
- writeFile2(mutation.absPath, mutation.after, "utf-8");
5764
- attempt.written = true;
5765
- recordFileWriteReceipt(mutation.absPath, {
5766
- path: mutation.sourceFile,
5767
- version,
5768
- writeToken,
5769
- overwrote: before
5770
- });
5771
- return {
5772
- ...mutation,
5773
- before,
5774
- changed: true,
5775
- backupPath: backupPathForResponse(projectDir, backup.backupPath),
5776
- version,
5777
- writeToken
5778
- };
5779
- }
5780
- var FileChangedError = class extends Error {
5781
- constructor(sourceFile) {
5782
- super("file changed since the timeline was read");
5783
- this.sourceFile = sourceFile;
5719
+ if (compHeadContent) headContent += `
5720
+ ${compHeadContent}`;
5721
+ headContent = stripEmbeddedRuntimeScripts(headContent);
5722
+ headContent += `
5723
+ <script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
5724
+ if (!headContent.includes("gsap")) {
5725
+ headContent += `
5726
+ <script src="${gsapCdnDist()}gsap.min.js"></script>`;
5784
5727
  }
5785
- sourceFile;
5786
- };
5787
- function assertExpectedVersion(mutation, current) {
5788
- const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
5789
- if (stale) throw new FileChangedError(mutation.sourceFile);
5790
- }
5791
- var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
5792
- var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
5793
- function isStudioFontFaceCss(css) {
5794
- return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
5728
+ const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
5729
+ const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
5730
+ return `<!DOCTYPE html>
5731
+ ${htmlOpen}
5732
+ <head>
5733
+ ${headContent}
5734
+ </head>
5735
+ ${bodyOpen}
5736
+ <script>window.__timelines=window.__timelines||{};</script>
5737
+ ${rewrittenContent}
5738
+ </body>
5739
+ </html>`;
5795
5740
  }
5796
- function ensureStudioFontFaceCss(html, css) {
5797
- if (html.includes(css)) return html;
5798
- const block = FONT_STYLE_RE.exec(html);
5799
- if (block) {
5800
- const next = `${(block[2] ?? "").trim()}
5801
- ${css}`.trim();
5802
- return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
5803
- ${next}
5804
- </style>`);
5805
- }
5806
- const styleTag = `<style data-hf-studio-fonts="true">
5807
- ${css}
5808
- </style>`;
5809
- if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
5810
- </head>`);
5811
- return `${styleTag}
5812
- ${html}`;
5741
+ function hasBaseElement(html) {
5742
+ return parseHTML(html).document.querySelector("base") !== null;
5813
5743
  }
5814
5744
  var CompositionInsertionError = class extends Error {
5815
5745
  constructor(message, status) {
@@ -5875,7 +5805,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
5875
5805
  validateSourcePath(sourcePath);
5876
5806
  return canonicalProjectPath(
5877
5807
  projectDir,
5878
- resolveWithinProject(projectDir, relative22(projectDir, resolve5(dirname23(ownerAbs), sourcePath)))
5808
+ resolveWithinProject(projectDir, relative3(projectDir, resolve5(dirname5(ownerAbs), sourcePath)))
5879
5809
  );
5880
5810
  }
5881
5811
  function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
@@ -5948,7 +5878,7 @@ function uniqueHostId(root, base) {
5948
5878
  return `${base}_${suffix}`;
5949
5879
  }
5950
5880
  function relativeSourcePath(targetAbs, sourceAbs) {
5951
- return relative22(dirname23(targetAbs), sourceAbs).split(sep3).join("/");
5881
+ return relative3(dirname5(targetAbs), sourceAbs).split(sep3).join("/");
5952
5882
  }
5953
5883
  function insertCompositionIntoSource(input) {
5954
5884
  const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
@@ -6001,7 +5931,443 @@ function insertCompositionIntoSource(input) {
6001
5931
  const name = root.hasAttribute("data-duration") ? "data-duration" : "data-composition-duration";
6002
5932
  root.setAttribute(name, String(Math.round((input.start + duration) * 100) / 100));
6003
5933
  }
6004
- return { html: document2.toString(), hostId, track, duration };
5934
+ return { html: document2.toString(), hostId, track, duration };
5935
+ }
5936
+ var ROOT_COMPOSITION = "index.html";
5937
+ var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
5938
+ function projectReader(projectDir) {
5939
+ const sources = /* @__PURE__ */ new Map();
5940
+ return (compPath) => {
5941
+ if (!sources.has(compPath)) {
5942
+ const file = resolveWithinProject(projectDir, compPath);
5943
+ let source = null;
5944
+ try {
5945
+ source = file ? readFileSync23(file, "utf-8") : null;
5946
+ } catch {
5947
+ }
5948
+ sources.set(compPath, source);
5949
+ }
5950
+ return sources.get(compPath) ?? null;
5951
+ };
5952
+ }
5953
+ function normalizeSource(src) {
5954
+ if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
5955
+ const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
5956
+ return path || null;
5957
+ }
5958
+ function closureOf(read, compPath) {
5959
+ const closure = /* @__PURE__ */ new Set();
5960
+ const visit = (path) => {
5961
+ if (closure.has(path)) return;
5962
+ closure.add(path);
5963
+ for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
5964
+ const mounted = normalizeSource(match[2] ?? "");
5965
+ if (mounted) visit(mounted);
5966
+ }
5967
+ };
5968
+ visit(compPath);
5969
+ return closure;
5970
+ }
5971
+ function lengthIsTimeline(projectDir, compPath) {
5972
+ const read = projectReader(projectDir);
5973
+ const documents = /* @__PURE__ */ new Map();
5974
+ const elements = (path, selector) => {
5975
+ let document2 = documents.get(path);
5976
+ if (!document2) documents.set(path, document2 = parseHTML(read(path) ?? "").document);
5977
+ return descendants(document2, selector);
5978
+ };
5979
+ const comp = normalizeSource(compPath) ?? compPath;
5980
+ const unsized = (path) => {
5981
+ const [root] = elements(path, "[data-composition-id]");
5982
+ return Boolean(root && !root.hasAttribute("data-duration"));
5983
+ };
5984
+ if (unsized(ROOT_COMPOSITION)) return true;
5985
+ const hosts = [...closureOf(read, ROOT_COMPOSITION)].flatMap(
5986
+ (path) => elements(path, "[data-composition-src]").filter(
5987
+ (host) => normalizeSource(host.getAttribute("data-composition-src") ?? "") === comp
5988
+ )
5989
+ );
5990
+ if (comp === ROOT_COMPOSITION || hosts.length === 0) return unsized(comp);
5991
+ return hosts.some(
5992
+ (host) => !host.hasAttribute("data-duration") && !host.hasAttribute("data-end")
5993
+ );
5994
+ }
5995
+ var inputSignatures = /* @__PURE__ */ new Map();
5996
+ var rootMemos = /* @__PURE__ */ new Map();
5997
+ var sha1 = (text2) => createHash4("sha1").update(text2).digest("hex");
5998
+ function rootHashes(read) {
5999
+ const source = read(ROOT_COMPOSITION) ?? "";
6000
+ return { root: sha1(source), head: sha1(rootHeadContent(source)) };
6001
+ }
6002
+ function compositionInputSignature(projectDir, compPath, projectSignature) {
6003
+ const key = `${projectDir}\0${compPath}`;
6004
+ const known = inputSignatures.get(key);
6005
+ if (known?.projectSignature === projectSignature) return known.inputSignature;
6006
+ const read = projectReader(projectDir);
6007
+ const comp = normalizeSource(compPath) ?? compPath;
6008
+ const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
6009
+ const excluded = new Set(
6010
+ [...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
6011
+ );
6012
+ let inputSignature;
6013
+ if (comp === ROOT_COMPOSITION) {
6014
+ inputSignature = createProjectSignature(projectDir, excluded);
6015
+ } else {
6016
+ const hashes = rootHashes(read);
6017
+ if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
6018
+ inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
6019
+ }
6020
+ inputSignatures.set(key, { projectSignature, inputSignature });
6021
+ return inputSignature;
6022
+ }
6023
+ function compositionsAffectedBy(projectDir, changedPath) {
6024
+ const changed = changedPath.replace(/\\/g, "/");
6025
+ const read = projectReader(projectDir);
6026
+ if (changed === ROOT_COMPOSITION) {
6027
+ const now = rootHashes(read);
6028
+ const memo = rootMemos.get(projectDir);
6029
+ if (memo?.root === now.root) return memo.affected;
6030
+ const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
6031
+ rootMemos.set(projectDir, { ...now, affected });
6032
+ return affected;
6033
+ }
6034
+ const mounted = closureOf(read, ROOT_COMPOSITION);
6035
+ if (!mounted.has(changed)) return null;
6036
+ return [...mounted].filter((path) => closureOf(read, path).has(changed));
6037
+ }
6038
+ var SAMPLE_RATE = 4e3;
6039
+ var PEAK_COUNT = 4e3;
6040
+ var WAVEFORM_CACHE_VERSION = "v2";
6041
+ function buildWaveformCacheKey(assetPath, fingerprint) {
6042
+ const name = assetPath.replace(/[/\\]/g, "_");
6043
+ if (!fingerprint) return `${WAVEFORM_CACHE_VERSION}_${name}.json`;
6044
+ const stamp = `${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}`;
6045
+ return `${WAVEFORM_CACHE_VERSION}_${name}_${stamp}.json`;
6046
+ }
6047
+ function computePeaks(floats, count) {
6048
+ const step = floats.length / count;
6049
+ const peaks = [];
6050
+ for (let i = 0; i < count; i++) {
6051
+ const start = Math.floor(i * step);
6052
+ const end = Math.min(Math.floor((i + 1) * step), floats.length);
6053
+ let max = 0;
6054
+ for (let j = start; j < end; j++) {
6055
+ const abs = Math.abs(floats[j] ?? 0);
6056
+ if (abs > max) max = abs;
6057
+ }
6058
+ peaks.push(max);
6059
+ }
6060
+ const maxPeak = Math.max(...peaks, 1e-3);
6061
+ return peaks.map((p) => p / maxPeak);
6062
+ }
6063
+ function decodeAudioPeaks(audioPath) {
6064
+ return new Promise((resolvePromise, reject) => {
6065
+ const proc = spawn(
6066
+ findFfBinary("ffmpeg") ?? "ffmpeg",
6067
+ [
6068
+ "-i",
6069
+ audioPath,
6070
+ "-af",
6071
+ "atrim=start_sample=1152",
6072
+ "-f",
6073
+ "f32le",
6074
+ "-ac",
6075
+ "1",
6076
+ "-ar",
6077
+ String(SAMPLE_RATE),
6078
+ "-vn",
6079
+ "pipe:1"
6080
+ ],
6081
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
6082
+ );
6083
+ const chunks = [];
6084
+ proc.stdout?.on("data", (chunk) => chunks.push(chunk));
6085
+ proc.on("close", (code) => {
6086
+ if (code !== 0 && chunks.length === 0) {
6087
+ reject(new Error(`ffmpeg exited with code ${code}`));
6088
+ return;
6089
+ }
6090
+ const buf = Buffer.concat(chunks);
6091
+ const numSamples = Math.floor(buf.length / 4);
6092
+ if (numSamples === 0) {
6093
+ reject(new Error("ffmpeg produced no audio samples"));
6094
+ return;
6095
+ }
6096
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4);
6097
+ resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT));
6098
+ });
6099
+ proc.on("error", reject);
6100
+ });
6101
+ }
6102
+ async function generateWaveformCache(projectDir, assetPath) {
6103
+ const audioPath = join32(projectDir, assetPath);
6104
+ if (!existsSync32(audioPath)) return;
6105
+ const stats = statSync4(audioPath);
6106
+ const cacheDir = join32(projectDir, ".waveform-cache");
6107
+ const cachePath = join32(cacheDir, buildWaveformCacheKey(assetPath, stats));
6108
+ if (isWaveformCacheDirectory(cacheDir) && existsSync32(cachePath)) return;
6109
+ const peaks = await decodeAudioPeaks(audioPath);
6110
+ writeWaveformCache(cachePath, peaks);
6111
+ }
6112
+ function isWaveformCacheDirectory(cacheDir) {
6113
+ return lstatSync3(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
6114
+ }
6115
+ function writeWaveformCache(cachePath, peaks) {
6116
+ const cacheDir = dirname23(cachePath);
6117
+ try {
6118
+ mkdirSync5(cacheDir, { mode: 448 });
6119
+ } catch (error) {
6120
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
6121
+ }
6122
+ if (!isWaveformCacheDirectory(cacheDir)) {
6123
+ throw new Error("Waveform cache must be a directory, not a symlink");
6124
+ }
6125
+ const stagingDir = mkdtempSync(join32(cacheDir, ".waveform-"));
6126
+ try {
6127
+ const stagingPath = join32(stagingDir, "peaks.json");
6128
+ writeFileSync4(stagingPath, JSON.stringify(peaks), { flag: "wx" });
6129
+ renameSync3(stagingPath, cachePath);
6130
+ } finally {
6131
+ try {
6132
+ rmSync4(stagingDir, { recursive: true, force: true });
6133
+ } catch {
6134
+ }
6135
+ }
6136
+ }
6137
+ var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
6138
+ var AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac|flac)$/i;
6139
+ var defaultRunner = spawnSync;
6140
+ var FFPROBE_MISSING = "not checked: ffprobe was not found. Install FFmpeg or set HYPERFRAMES_FFPROBE_PATH.";
6141
+ function validateUploadedMedia(filePath, runner = defaultRunner) {
6142
+ const isVideo = VIDEO_EXT.test(filePath);
6143
+ const isAudio = AUDIO_EXT.test(filePath);
6144
+ if (!isVideo && !isAudio) {
6145
+ return { ok: true };
6146
+ }
6147
+ const ffprobe = findFfBinary("ffprobe") ?? (runner === defaultRunner ? void 0 : "ffprobe");
6148
+ if (!ffprobe) return { ok: true, unchecked: FFPROBE_MISSING };
6149
+ const result = runner(
6150
+ ffprobe,
6151
+ ["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
6152
+ { windowsHide: true }
6153
+ );
6154
+ if (result.error && process.env[FFPROBE_PATH_ENV]?.trim()) {
6155
+ return {
6156
+ ok: false,
6157
+ reason: `${FFPROBE_PATH_ENV} names "${ffprobe}", which cannot run: fix it or unset it`
6158
+ };
6159
+ }
6160
+ if (result.error?.code === "ENOENT") {
6161
+ return { ok: true, unchecked: FFPROBE_MISSING };
6162
+ }
6163
+ if (result.status !== 0) {
6164
+ return { ok: false, reason: "ffprobe failed to read the media file" };
6165
+ }
6166
+ return checkStreams(String(result.stdout || "{}"), isVideo, isAudio);
6167
+ }
6168
+ function checkStreams(stdout, isVideo, isAudio) {
6169
+ try {
6170
+ const parsed = JSON.parse(stdout);
6171
+ const streams = parsed.streams ?? [];
6172
+ const hasVideo = streams.some((stream) => stream.codec_type === "video");
6173
+ const hasAudio = streams.some((stream) => stream.codec_type === "audio");
6174
+ if (isVideo && !hasVideo) {
6175
+ return { ok: false, reason: "no supported video stream found" };
6176
+ }
6177
+ if (isAudio && !hasAudio) {
6178
+ return { ok: false, reason: "no supported audio stream found" };
6179
+ }
6180
+ return { ok: true };
6181
+ } catch {
6182
+ return { ok: false, reason: "ffprobe returned unreadable media metadata" };
6183
+ }
6184
+ }
6185
+ function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
6186
+ const tempDir = mkdtempSync2(join42(tmpdir2(), "hyperframes-upload-"));
6187
+ const tempPath = join42(tempDir, basename2(fileName));
6188
+ try {
6189
+ writeFileSync23(tempPath, buffer);
6190
+ return validateUploadedMedia(tempPath, runner);
6191
+ } finally {
6192
+ rmSync22(tempDir, { recursive: true, force: true });
6193
+ }
6194
+ }
6195
+ var DEFAULT_KEEP_PER_FILE = 10;
6196
+ function backupKeyForPath(path) {
6197
+ return Buffer2.from(path, "utf-8").toString("base64url");
6198
+ }
6199
+ function timestampPrefix() {
6200
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6201
+ }
6202
+ function backupPathForResponse(projectDir, backupPath) {
6203
+ if (!backupPath) return null;
6204
+ const rel = relative22(projectDir, backupPath);
6205
+ if (!rel || rel.startsWith("..")) return null;
6206
+ return rel.split("\\").join("/");
6207
+ }
6208
+ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
6209
+ if (!isSafePath(projectDir, absPath)) return { backupPath: null };
6210
+ try {
6211
+ const content = readFileSync32(absPath);
6212
+ const relativePath = relative22(projectDir, absPath);
6213
+ const backupDir = join52(projectDir, ".hyperframes", "backup");
6214
+ mkdirWithinProject(projectDir, backupDir);
6215
+ const backupKey = backupKeyForPath(relativePath);
6216
+ const backupPath = nextBackupPath(backupDir, backupKey);
6217
+ writeFileSync32(backupPath, content);
6218
+ pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
6219
+ return { backupPath };
6220
+ } catch (error) {
6221
+ if (isProjectRootMissing(error)) throw error;
6222
+ if (error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) {
6223
+ return { backupPath: null };
6224
+ }
6225
+ return { backupPath: null, error: error instanceof Error ? error.message : String(error) };
6226
+ }
6227
+ }
6228
+ function nextBackupPath(backupDir, backupKey) {
6229
+ const base = `${timestampPrefix()}-${backupKey}`;
6230
+ let candidate = join52(backupDir, base);
6231
+ let counter = 2;
6232
+ while (true) {
6233
+ try {
6234
+ readFileSync32(candidate);
6235
+ } catch (error) {
6236
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
6237
+ return candidate;
6238
+ }
6239
+ throw error;
6240
+ }
6241
+ candidate = join52(backupDir, `${base}-${counter}`);
6242
+ counter += 1;
6243
+ }
6244
+ }
6245
+ function pruneBackups(backupDir, backupKey, keepPerFile) {
6246
+ const keep = Math.max(1, Math.floor(keepPerFile));
6247
+ const suffix = `-${backupKey}`;
6248
+ const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
6249
+ const matches = readdirSync4(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join52(backupDir, name)).sort((a, b) => {
6250
+ return b.localeCompare(a);
6251
+ });
6252
+ for (const file of matches.slice(keep)) {
6253
+ try {
6254
+ unlinkSync(file);
6255
+ } catch {
6256
+ }
6257
+ }
6258
+ }
6259
+ var PROJECT_DIR_MISSING = { error: "not found", why: "project_dir_missing" };
6260
+ var projectDirMissing = (c) => c.json(PROJECT_DIR_MISSING, 404);
6261
+ function replaceWithProjectDirMissing(c, hostHeaders) {
6262
+ hostHeaders.delete("content-type");
6263
+ c.res = void 0;
6264
+ c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
6265
+ }
6266
+ function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (path, content) => replaceFileAtomically(path, content, statSync22(path).mode)) {
6267
+ const prepared = mutations.map((mutation) => ({
6268
+ ...mutation,
6269
+ before: mutation.before ?? readFileSync42(mutation.absPath, "utf-8")
6270
+ }));
6271
+ const results = [];
6272
+ const attempted = [];
6273
+ for (const mutation of prepared)
6274
+ assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
6275
+ try {
6276
+ for (const mutation of prepared) {
6277
+ results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted));
6278
+ }
6279
+ return results;
6280
+ } catch (error) {
6281
+ const rollbackErrors = [];
6282
+ for (const mutation of attempted.reverse()) {
6283
+ try {
6284
+ if (!mutation.written || readFileSync42(mutation.absPath, "utf-8") === mutation.after)
6285
+ writeFile2(mutation.absPath, mutation.before, "utf-8");
6286
+ clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
6287
+ } catch (rollbackError) {
6288
+ rollbackErrors.push(rollbackError);
6289
+ }
6290
+ }
6291
+ if (rollbackErrors.length > 0) {
6292
+ throw new AggregateError(
6293
+ [error, ...rollbackErrors],
6294
+ "File mutation failed and rollback did not complete"
6295
+ );
6296
+ }
6297
+ throw error;
6298
+ }
6299
+ }
6300
+ function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted) {
6301
+ const current = readFileSync42(mutation.absPath, "utf-8");
6302
+ assertExpectedVersion(mutation, current);
6303
+ if (mutation.after === mutation.before) {
6304
+ return {
6305
+ ...mutation,
6306
+ before: current,
6307
+ changed: false,
6308
+ backupPath: null,
6309
+ version: fileContentVersion(mutation.before),
6310
+ writeToken: null
6311
+ };
6312
+ }
6313
+ const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
6314
+ if (backup.error) throw new Error(`backup failed: ${backup.error}`);
6315
+ assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
6316
+ const before = current;
6317
+ const version = fileContentVersion(mutation.after);
6318
+ const writeToken = createWriteToken(requestToken);
6319
+ const attempt = { ...mutation, before, version, writeToken, written: false };
6320
+ attempted.push(attempt);
6321
+ writeFile2(mutation.absPath, mutation.after, "utf-8");
6322
+ attempt.written = true;
6323
+ recordFileWriteReceipt(mutation.absPath, {
6324
+ path: mutation.sourceFile,
6325
+ version,
6326
+ writeToken,
6327
+ overwrote: before
6328
+ });
6329
+ return {
6330
+ ...mutation,
6331
+ before,
6332
+ changed: true,
6333
+ backupPath: backupPathForResponse(projectDir, backup.backupPath),
6334
+ version,
6335
+ writeToken
6336
+ };
6337
+ }
6338
+ var FileChangedError = class extends Error {
6339
+ constructor(sourceFile) {
6340
+ super("file changed since the timeline was read");
6341
+ this.sourceFile = sourceFile;
6342
+ }
6343
+ sourceFile;
6344
+ };
6345
+ function assertExpectedVersion(mutation, current) {
6346
+ const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
6347
+ if (stale) throw new FileChangedError(mutation.sourceFile);
6348
+ }
6349
+ var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
6350
+ var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
6351
+ function isStudioFontFaceCss(css) {
6352
+ return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
6353
+ }
6354
+ function ensureStudioFontFaceCss(html, css) {
6355
+ if (html.includes(css)) return html;
6356
+ const block = FONT_STYLE_RE.exec(html);
6357
+ if (block) {
6358
+ const next = `${(block[2] ?? "").trim()}
6359
+ ${css}`.trim();
6360
+ return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
6361
+ ${next}
6362
+ </style>`);
6363
+ }
6364
+ const styleTag = `<style data-hf-studio-fonts="true">
6365
+ ${css}
6366
+ </style>`;
6367
+ if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
6368
+ </head>`);
6369
+ return `${styleTag}
6370
+ ${html}`;
6005
6371
  }
6006
6372
  var differential = (family) => ({
6007
6373
  family,
@@ -6070,7 +6436,7 @@ function requestSubPath(url, route) {
6070
6436
  return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
6071
6437
  }
6072
6438
  async function loadGsapParser() {
6073
- return import("./gsapParser-DIYYX6JT.js");
6439
+ return import("./gsapParser-2OL54XZ5.js");
6074
6440
  }
6075
6441
  function isDanglingSymlinkInProject(projectDir, lexicalPath) {
6076
6442
  if (!isSafePath(projectDir, dirname32(lexicalPath))) return false;
@@ -6110,7 +6476,7 @@ async function resolveProjectPath(c, adapter, route, opts) {
6110
6476
  }
6111
6477
  return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
6112
6478
  }
6113
- if (opts?.mustExist && !existsSync3(absPath)) {
6479
+ if (opts?.mustExist && !existsSync4(absPath)) {
6114
6480
  return { error: c.json({ error: "not found" }, 404) };
6115
6481
  }
6116
6482
  return { project, filePath, absPath };
@@ -6176,7 +6542,7 @@ function projectHtmlIds(projectDir) {
6176
6542
  if (!rel.endsWith(".html") || isInHiddenOrVendorDir(rel)) continue;
6177
6543
  let html;
6178
6544
  try {
6179
- html = readFileSync32(join52(projectDir, rel), "utf-8");
6545
+ html = readFileSync52(join62(projectDir, rel), "utf-8");
6180
6546
  } catch {
6181
6547
  continue;
6182
6548
  }
@@ -6205,7 +6571,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
6205
6571
  resolvedPaths.add(absPath);
6206
6572
  let before;
6207
6573
  try {
6208
- before = readFileSync32(absPath, "utf-8");
6574
+ before = readFileSync52(absPath, "utf-8");
6209
6575
  } catch {
6210
6576
  return { error: "not-found", sourceFile: batch.sourceFile };
6211
6577
  }
@@ -6261,7 +6627,7 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
6261
6627
  );
6262
6628
  }
6263
6629
  function writeFileWithReceipt(c, filePath, absPath, html) {
6264
- const overwrote = readFileSync32(absPath);
6630
+ const overwrote = readFileSync52(absPath);
6265
6631
  replaceFileAtomically(absPath, html, statSync32(absPath).mode);
6266
6632
  const version = fileContentVersion(html);
6267
6633
  const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
@@ -6271,7 +6637,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
6271
6637
  function writeMutationResult(c, projectDir, filePath, absPath, html, original) {
6272
6638
  const backup = snapshotBeforeWrite(projectDir, absPath);
6273
6639
  if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
6274
- if (readFileSync32(absPath, "utf-8") !== original) {
6640
+ if (readFileSync52(absPath, "utf-8") !== original) {
6275
6641
  return c.json({ error: "file changed", conflict: true, path: filePath }, 409);
6276
6642
  }
6277
6643
  const { version } = writeFileWithReceipt(c, filePath, absPath, html);
@@ -6326,7 +6692,7 @@ function generateCopyPath(projectDir, originalPath) {
6326
6692
  const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
6327
6693
  let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
6328
6694
  let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
6329
- while (existsSync3(resolve22(projectDir, candidate))) {
6695
+ while (existsSync4(resolve22(projectDir, candidate))) {
6330
6696
  num++;
6331
6697
  candidate = `${cleanBase} (copy ${num})${ext}`;
6332
6698
  }
@@ -6335,7 +6701,7 @@ function generateCopyPath(projectDir, originalPath) {
6335
6701
  function walkFiles(dir, filter) {
6336
6702
  const results = [];
6337
6703
  for (const entry of readableEntries(dir)) {
6338
- const full2 = join52(dir, entry.name);
6704
+ const full2 = join62(dir, entry.name);
6339
6705
  if (entry.isDirectory()) {
6340
6706
  if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
6341
6707
  continue;
@@ -6360,7 +6726,7 @@ function readableEntries(dir) {
6360
6726
  }
6361
6727
  function readableText(file) {
6362
6728
  try {
6363
- return readFileSync32(file, "utf-8");
6729
+ return readFileSync52(file, "utf-8");
6364
6730
  } catch (error) {
6365
6731
  if (skippable(error)) return null;
6366
6732
  throw error;
@@ -6418,7 +6784,7 @@ function occurrences(text2, part) {
6418
6784
  }
6419
6785
  function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
6420
6786
  return readableEntries(dir).flatMap((entry) => {
6421
- const [path, full2] = [`${prefix}${entry.name}`, join52(dir, entry.name)];
6787
+ const [path, full2] = [`${prefix}${entry.name}`, join62(dir, entry.name)];
6422
6788
  const real = entry.name === "node_modules" ? null : directoryReal(entry, full2);
6423
6789
  if (real === null || ancestors.has(real) || !isSafePath(root, full2)) return [path];
6424
6790
  return [path, ...projectPaths(root, full2, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
@@ -6726,12 +7092,12 @@ function containsRawGsapExpression(value) {
6726
7092
  return Object.values(value).some(containsRawGsapExpression);
6727
7093
  }
6728
7094
  async function prepareGsapMutationScript(c, res, firstMutation) {
6729
- const beforeHtml = readFileSync32(res.absPath, "utf-8");
7095
+ const beforeHtml = readFileSync52(res.absPath, "utf-8");
6730
7096
  let html = beforeHtml;
6731
7097
  let block = extractGsapScriptBlock(html);
6732
7098
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
6733
7099
  const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
6734
- const { GSAP_CDN } = await import("./dist-S3PJNPXQ.js");
7100
+ const { GSAP_CDN } = await import("./dist-HVJAGLHF.js");
6735
7101
  const bootstrap = [
6736
7102
  `<script src="${GSAP_CDN}"></script>`,
6737
7103
  "<script>",
@@ -6801,6 +7167,7 @@ async function applyGsapMutations(c, res, mutations) {
6801
7167
  const prepared = await prepareGsapMutationScript(c, res, firstMutation);
6802
7168
  if (prepared instanceof Response) return prepared;
6803
7169
  const { html, beforeHtml, block } = prepared;
7170
+ let lengthIsAuthored;
6804
7171
  const initialScript = block.scriptText;
6805
7172
  const skippedSelectors = /* @__PURE__ */ new Set();
6806
7173
  const respond = (data, status) => status ? c.json(data, status) : c.json(data);
@@ -6832,7 +7199,9 @@ async function applyGsapMutations(c, res, mutations) {
6832
7199
  for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
6833
7200
  }
6834
7201
  if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
6835
- newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript);
7202
+ lengthIsAuthored ??= !lengthIsTimeline(res.project.dir, res.filePath);
7203
+ if (lengthIsAuthored) newScript = trimTrailingKeyframeSpans(previousScript, newScript);
7204
+ newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript, previousScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript, previousScript);
6836
7205
  }
6837
7206
  mutationChanges.push(newScript !== previousScript);
6838
7207
  block.scriptText = newScript;
@@ -6840,7 +7209,7 @@ async function applyGsapMutations(c, res, mutations) {
6840
7209
  const changed = block.scriptText !== initialScript;
6841
7210
  const newHtml = changed ? block.replaceScript(block.scriptText) : html;
6842
7211
  let backupPath = null;
6843
- if (readFileSync32(res.absPath, "utf-8") !== beforeHtml) {
7212
+ if (readFileSync52(res.absPath, "utf-8") !== beforeHtml) {
6844
7213
  return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
6845
7214
  }
6846
7215
  if (changed) {
@@ -7531,7 +7900,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
7531
7900
  for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
7532
7901
  }
7533
7902
  if (script !== block.scriptText) {
7534
- script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script);
7903
+ script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script, block.scriptText) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script, block.scriptText);
7535
7904
  after = block.replaceScript(script);
7536
7905
  }
7537
7906
  }
@@ -7570,9 +7939,9 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
7570
7939
  const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
7571
7940
  const MAX_COPY_INDEX = 1e4;
7572
7941
  let n = 1;
7573
- if (existsSync3(finalPath)) {
7942
+ if (existsSync4(finalPath)) {
7574
7943
  n = 2;
7575
- while (n < MAX_COPY_INDEX && existsSync3(resolve22(targetDir, `${base} (${n})${ext}`))) n++;
7944
+ while (n < MAX_COPY_INDEX && existsSync4(resolve22(targetDir, `${base} (${n})${ext}`))) n++;
7576
7945
  if (n >= MAX_COPY_INDEX) {
7577
7946
  skipped.push(name);
7578
7947
  continue;
@@ -7606,7 +7975,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
7606
7975
  if (n >= MAX_COPY_INDEX) skipped.push(name);
7607
7976
  continue;
7608
7977
  }
7609
- const relativePath = subDir ? join52(subDir, finalName) : finalName;
7978
+ const relativePath = subDir ? join62(subDir, finalName) : finalName;
7610
7979
  uploaded.push(relativePath);
7611
7980
  if (validation.unchecked) unchecked.push({ name: finalName, reason: validation.unchecked });
7612
7981
  if (isAudioFile(finalName)) {
@@ -7639,7 +8008,7 @@ function registerFileRoutes(api, adapter) {
7639
8008
  if (stat22.size > MAX_TEXT_READ_BYTES) {
7640
8009
  return c.json({ error: "too large to read as text", why: "too_large" }, 413);
7641
8010
  }
7642
- const content = readFileSync32(fd);
8011
+ const content = readFileSync52(fd);
7643
8012
  const version = fileContentVersion(content);
7644
8013
  c.header("ETag", version);
7645
8014
  if (content.subarray(0, GIT_BINARY_SNIFF_BYTES).includes(0)) {
@@ -7664,7 +8033,7 @@ function registerFileRoutes(api, adapter) {
7664
8033
  if (expectedVersion === null && !createOnly) {
7665
8034
  let currentContent = null;
7666
8035
  try {
7667
- currentContent = readFileSync32(res.absPath);
8036
+ currentContent = readFileSync52(res.absPath);
7668
8037
  } catch (error) {
7669
8038
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
7670
8039
  throw error;
@@ -7690,7 +8059,7 @@ function registerFileRoutes(api, adapter) {
7690
8059
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
7691
8060
  throw error;
7692
8061
  }
7693
- const currentContent = readFileSync32(res.absPath);
8062
+ const currentContent = readFileSync52(res.absPath);
7694
8063
  return c.json(
7695
8064
  {
7696
8065
  error: "file conflict",
@@ -7720,7 +8089,7 @@ function registerFileRoutes(api, adapter) {
7720
8089
  );
7721
8090
  }
7722
8091
  try {
7723
- const currentContent = readFileSync32(fd);
8092
+ const currentContent = readFileSync52(fd);
7724
8093
  overwrote = currentContent;
7725
8094
  const currentVersion = fileContentVersion(currentContent);
7726
8095
  if (expectedVersion !== currentVersion) {
@@ -7796,7 +8165,7 @@ function registerFileRoutes(api, adapter) {
7796
8165
  }
7797
8166
  let before;
7798
8167
  try {
7799
- before = readFileSync32(ctx.absPath, "utf-8");
8168
+ before = readFileSync52(ctx.absPath, "utf-8");
7800
8169
  } catch (error) {
7801
8170
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
7802
8171
  throw error;
@@ -7825,7 +8194,7 @@ function registerFileRoutes(api, adapter) {
7825
8194
  }
7826
8195
  const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
7827
8196
  if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
7828
- const current = readFileSync32(ctx.absPath, "utf-8");
8197
+ const current = readFileSync52(ctx.absPath, "utf-8");
7829
8198
  if (current !== before) {
7830
8199
  const currentVersion2 = fileContentVersion(current);
7831
8200
  return c.json({ error: "file conflict", currentVersion: currentVersion2, currentContent: current }, 409);
@@ -7853,12 +8222,12 @@ function registerFileRoutes(api, adapter) {
7853
8222
  api.post("/projects/:id/file-mutations/remove-element/*", async (c) => {
7854
8223
  const ctx = await resolveFileMutationContext(c, adapter, "remove-element");
7855
8224
  if ("error" in ctx) return ctx.error;
7856
- if (!existsSync3(ctx.absPath)) {
8225
+ if (!existsSync4(ctx.absPath)) {
7857
8226
  return c.json({ error: "not found" }, 404);
7858
8227
  }
7859
8228
  const parsed = await parseMutationBody(c);
7860
8229
  if ("error" in parsed) return parsed.error;
7861
- const originalContent = readFileSync32(ctx.absPath, "utf-8");
8230
+ const originalContent = readFileSync52(ctx.absPath, "utf-8");
7862
8231
  return writeIfChanged(
7863
8232
  c,
7864
8233
  ctx.project.dir,
@@ -7871,7 +8240,7 @@ function registerFileRoutes(api, adapter) {
7871
8240
  api.post("/projects/:id/file-mutations/remove-elements/*", async (c) => {
7872
8241
  const ctx = await resolveFileMutationContext(c, adapter, "remove-elements");
7873
8242
  if ("error" in ctx) return ctx.error;
7874
- if (!existsSync3(ctx.absPath)) {
8243
+ if (!existsSync4(ctx.absPath)) {
7875
8244
  return c.json({ error: "not found" }, 404);
7876
8245
  }
7877
8246
  const body = await c.req.json().catch(() => null);
@@ -7879,7 +8248,7 @@ function registerFileRoutes(api, adapter) {
7879
8248
  if (!Array.isArray(targets) || targets.length === 0) {
7880
8249
  return c.json({ error: "targets required" }, 400);
7881
8250
  }
7882
- const originalContent = readFileSync32(ctx.absPath, "utf-8");
8251
+ const originalContent = readFileSync52(ctx.absPath, "utf-8");
7883
8252
  return writeIfChanged(
7884
8253
  c,
7885
8254
  ctx.project.dir,
@@ -7915,7 +8284,7 @@ function registerFileRoutes(api, adapter) {
7915
8284
  seen.add(absPath);
7916
8285
  let before;
7917
8286
  try {
7918
- before = readFileSync32(absPath, "utf-8");
8287
+ before = readFileSync52(absPath, "utf-8");
7919
8288
  } catch {
7920
8289
  return c.json({ error: `not found: ${file.path}` }, 404);
7921
8290
  }
@@ -7942,7 +8311,7 @@ function registerFileRoutes(api, adapter) {
7942
8311
  prepared.push(folded);
7943
8312
  }
7944
8313
  for (const file of prepared) {
7945
- const current = readFileSync32(file.absPath, "utf-8");
8314
+ const current = readFileSync52(file.absPath, "utf-8");
7946
8315
  if (current !== file.before) {
7947
8316
  return c.json(
7948
8317
  {
@@ -7985,7 +8354,7 @@ function registerFileRoutes(api, adapter) {
7985
8354
  const conflicts = [];
7986
8355
  for (const file of written.reverse()) {
7987
8356
  try {
7988
- const current = readFileSync32(file.absPath, "utf-8");
8357
+ const current = readFileSync52(file.absPath, "utf-8");
7989
8358
  if (current !== file.after) {
7990
8359
  conflicts.push(file.path);
7991
8360
  continue;
@@ -8033,7 +8402,7 @@ function registerFileRoutes(api, adapter) {
8033
8402
  const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
8034
8403
  let originalContent;
8035
8404
  try {
8036
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8405
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8037
8406
  } catch {
8038
8407
  return c.json({ error: "not found" }, 404);
8039
8408
  }
@@ -8095,7 +8464,7 @@ function registerFileRoutes(api, adapter) {
8095
8464
  for (let attempt = 1; ; attempt += 1) {
8096
8465
  let originalContent;
8097
8466
  try {
8098
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8467
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8099
8468
  } catch {
8100
8469
  return c.json({ error: "not found" }, 404);
8101
8470
  }
@@ -8207,7 +8576,7 @@ function registerFileRoutes(api, adapter) {
8207
8576
  }
8208
8577
  let originalContent;
8209
8578
  try {
8210
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8579
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8211
8580
  } catch {
8212
8581
  return c.json({ error: "not found" }, 404);
8213
8582
  }
@@ -8263,7 +8632,7 @@ function registerFileRoutes(api, adapter) {
8263
8632
  ).map((entry) => ({ target: entry.target, track: entry.track }));
8264
8633
  let originalContent;
8265
8634
  try {
8266
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8635
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8267
8636
  } catch {
8268
8637
  return c.json({ error: "not found" }, 404);
8269
8638
  }
@@ -8292,7 +8661,7 @@ function registerFileRoutes(api, adapter) {
8292
8661
  if ("error" in parsed) return parsed.error;
8293
8662
  let content;
8294
8663
  try {
8295
- content = readFileSync32(ctx.absPath, "utf-8");
8664
+ content = readFileSync52(ctx.absPath, "utf-8");
8296
8665
  } catch {
8297
8666
  return c.json({ exists: false });
8298
8667
  }
@@ -8306,7 +8675,7 @@ function registerFileRoutes(api, adapter) {
8306
8675
  if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
8307
8676
  let content;
8308
8677
  try {
8309
- content = readFileSync32(ctx.absPath, "utf-8");
8678
+ content = readFileSync52(ctx.absPath, "utf-8");
8310
8679
  } catch {
8311
8680
  return c.json({ exists: body.targets.map(() => false) });
8312
8681
  }
@@ -8323,7 +8692,7 @@ function registerFileRoutes(api, adapter) {
8323
8692
  if (!newAbs) {
8324
8693
  return c.json({ error: "forbidden" }, 403);
8325
8694
  }
8326
- if (existsSync3(newAbs)) {
8695
+ if (existsSync4(newAbs)) {
8327
8696
  return c.json({ error: "already exists" }, 409);
8328
8697
  }
8329
8698
  const isDirectory = statSync32(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
@@ -8340,7 +8709,7 @@ function registerFileRoutes(api, adapter) {
8340
8709
  return c.json({ error: "path required" }, 400);
8341
8710
  }
8342
8711
  const srcAbs = resolveWithinProject(project.dir, body.path);
8343
- if (!srcAbs || !existsSync3(srcAbs)) {
8712
+ if (!srcAbs || !existsSync4(srcAbs)) {
8344
8713
  return c.json({ error: "not found" }, 404);
8345
8714
  }
8346
8715
  const copyPath = generateCopyPath(project.dir, body.path);
@@ -8350,7 +8719,7 @@ function registerFileRoutes(api, adapter) {
8350
8719
  }
8351
8720
  ensureDir(project.dir, destAbs);
8352
8721
  try {
8353
- createFileAtomically(destAbs, readFileSync32(srcAbs));
8722
+ createFileAtomically(destAbs, readFileSync52(srcAbs));
8354
8723
  } catch (error) {
8355
8724
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
8356
8725
  throw error;
@@ -8394,7 +8763,7 @@ function registerFileRoutes(api, adapter) {
8394
8763
  mustExist: true
8395
8764
  });
8396
8765
  if ("error" in res) return res.error;
8397
- const html = readFileSync32(res.absPath, "utf-8");
8766
+ const html = readFileSync52(res.absPath, "utf-8");
8398
8767
  const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
8399
8768
  const headers = { ETag: etag, "Cache-Control": "no-cache" };
8400
8769
  if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
@@ -8451,7 +8820,7 @@ function registerFileRoutes(api, adapter) {
8451
8820
  if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
8452
8821
  return c.json({ error: "expected and restore contents required" }, 400);
8453
8822
  }
8454
- const current = readFileSync32(res.absPath, "utf-8");
8823
+ const current = readFileSync52(res.absPath, "utf-8");
8455
8824
  if (current !== body.expected) {
8456
8825
  return c.json({ ok: true, restored: false, conflict: true });
8457
8826
  }
@@ -8459,176 +8828,6 @@ function registerFileRoutes(api, adapter) {
8459
8828
  return c.json({ ok: true, restored: true, conflict: false });
8460
8829
  });
8461
8830
  }
8462
- function rewriteRelativePaths(root, compPath, projectDir) {
8463
- const assetExists = (path) => existsSync42(join62(projectDir, path));
8464
- rewriteAssetPaths(
8465
- root.querySelectorAll("[src], [href]"),
8466
- compPath,
8467
- (el, attr) => el.getAttribute(attr),
8468
- (el, attr, value) => el.setAttribute(attr, value),
8469
- assetExists
8470
- );
8471
- rewriteInlineStyleAssetUrls(
8472
- root.querySelectorAll("[style]"),
8473
- compPath,
8474
- (el) => el.getAttribute("style"),
8475
- (el, value) => el.setAttribute("style", value),
8476
- assetExists
8477
- );
8478
- for (const styleEl of root.querySelectorAll("style")) {
8479
- styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
8480
- }
8481
- }
8482
- function escapeLeadingDigitIdent(id) {
8483
- return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`;
8484
- }
8485
- var REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g;
8486
- function fixDigitLeadingIdSelectors(root) {
8487
- const digitIds = /* @__PURE__ */ new Set();
8488
- for (const el of root.querySelectorAll("[id]")) {
8489
- const id = el.getAttribute("id");
8490
- if (id && /^\d/.test(id)) digitIds.add(id);
8491
- }
8492
- if (digitIds.size === 0) return;
8493
- for (const styleEl of root.querySelectorAll("style")) {
8494
- let css = styleEl.textContent || "";
8495
- for (const id of digitIds) {
8496
- const pattern = new RegExp(`#${id.replace(REGEXP_SPECIALS, "\\$&")}(?![\\w-])`, "g");
8497
- css = css.replace(pattern, `#${escapeLeadingDigitIdent(id)}`);
8498
- }
8499
- styleEl.textContent = css;
8500
- }
8501
- }
8502
- function extractFullDocumentParts(rawHtml, compPath, projectDir) {
8503
- const { document: doc } = parseHTML(rawHtml);
8504
- const rewriteTargets = [doc.head, doc.body].filter(Boolean);
8505
- for (const target of rewriteTargets) {
8506
- rewriteRelativePaths(target, compPath, projectDir);
8507
- }
8508
- fixDigitLeadingIdSelectors(doc);
8509
- const headContent = doc.head?.innerHTML ?? "";
8510
- const bodyContent = doc.body?.innerHTML ?? "";
8511
- const htmlEl = doc.documentElement;
8512
- const htmlAttrs = extractElementAttrs(htmlEl);
8513
- const bodyAttrs = doc.body ? extractElementAttrs(doc.body) : "";
8514
- return { headContent, bodyContent, htmlAttrs, bodyAttrs };
8515
- }
8516
- function extractTemplateInnerHtml(rawComp) {
8517
- const { document: doc } = parseHTML(rawComp);
8518
- const template = doc.querySelector("template");
8519
- return template ? template.innerHTML : null;
8520
- }
8521
- function escapeAttrValue(value) {
8522
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
8523
- }
8524
- function extractElementAttrs(el) {
8525
- const parts = [];
8526
- for (let i = 0; i < el.attributes.length; i++) {
8527
- const attr = el.attributes[i];
8528
- if (attr.value === "") {
8529
- parts.push(attr.name);
8530
- } else {
8531
- parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
8532
- }
8533
- }
8534
- return parts.join(" ");
8535
- }
8536
- var NON_RENDERED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"]);
8537
- function promoteTemplateCompositionId(rawComp, body) {
8538
- let templateCompositionId;
8539
- for (const tag of rawComp.matchAll(/<template\b[^>]*/gi)) {
8540
- const id = /\bdata-composition-id\s*=\s*["']([^"']+)["']/i.exec(tag[0] ?? "")?.[1];
8541
- if (id) {
8542
- templateCompositionId = id;
8543
- break;
8544
- }
8545
- }
8546
- if (!templateCompositionId) return;
8547
- if (body.querySelector("[data-composition-id]")) return;
8548
- const root = Array.from(body.children).find((el) => !NON_RENDERED_TAGS.has(el.tagName));
8549
- root?.setAttribute("data-composition-id", templateCompositionId);
8550
- }
8551
- function tagRootCompositionFile(bodyHtml, compPath) {
8552
- const match = bodyHtml.match(/<[a-zA-Z][^>]*\bdata-composition-id=/);
8553
- if (match?.index == null) return bodyHtml;
8554
- const tagEnd = bodyHtml.indexOf(">", match.index);
8555
- if (tagEnd === -1) return bodyHtml;
8556
- if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
8557
- return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
8558
- }
8559
- function rootHeadContent(rootHtml) {
8560
- return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
8561
- }
8562
- function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
8563
- const compFile = join62(projectDir, compPath);
8564
- if (!existsSync42(compFile)) return null;
8565
- const rawComp = rawOverride ?? readFileSync42(compFile, "utf-8");
8566
- let compHeadContent = "";
8567
- let rewrittenContent;
8568
- let htmlAttrs = "";
8569
- let bodyAttrs = "";
8570
- const templateInner = extractTemplateInnerHtml(rawComp);
8571
- if (templateInner != null) {
8572
- const { document: contentDoc } = parseHTML(
8573
- `<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
8574
- );
8575
- rewriteRelativePaths(contentDoc, compPath, projectDir);
8576
- fixDigitLeadingIdSelectors(contentDoc);
8577
- promoteTemplateCompositionId(rawComp, contentDoc.body);
8578
- rewrittenContent = contentDoc.body.innerHTML || templateInner;
8579
- } else if (isFullHtmlDocument(rawComp)) {
8580
- const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
8581
- compHeadContent = parts.headContent;
8582
- rewrittenContent = parts.bodyContent;
8583
- htmlAttrs = parts.htmlAttrs;
8584
- bodyAttrs = parts.bodyAttrs;
8585
- } else {
8586
- const { document: contentDoc } = parseHTML(
8587
- `<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
8588
- );
8589
- rewriteRelativePaths(contentDoc, compPath, projectDir);
8590
- fixDigitLeadingIdSelectors(contentDoc);
8591
- rewrittenContent = contentDoc.body.innerHTML || rawComp;
8592
- }
8593
- rewrittenContent = stripEmbeddedRuntimeScripts(rewrittenContent);
8594
- const { document: scriptsDoc } = parseHTML(
8595
- `<!DOCTYPE html><html><head></head><body>${rewrittenContent}</body></html>`
8596
- );
8597
- deferScriptsUntilFonts(scriptsDoc);
8598
- rewrittenContent = scriptsDoc.body.innerHTML;
8599
- rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
8600
- const indexPath = join62(projectDir, "index.html");
8601
- let headContent = "";
8602
- if (existsSync42(indexPath)) headContent = rootHeadContent(readFileSync42(indexPath, "utf-8"));
8603
- if (baseHref && !hasBaseElement(headContent)) {
8604
- headContent = `<base href="${baseHref}">
8605
- ${headContent}`;
8606
- }
8607
- if (compHeadContent) headContent += `
8608
- ${compHeadContent}`;
8609
- headContent = stripEmbeddedRuntimeScripts(headContent);
8610
- headContent += `
8611
- <script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
8612
- if (!headContent.includes("gsap")) {
8613
- headContent += `
8614
- <script src="${gsapCdnDist()}gsap.min.js"></script>`;
8615
- }
8616
- const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
8617
- const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
8618
- return `<!DOCTYPE html>
8619
- ${htmlOpen}
8620
- <head>
8621
- ${headContent}
8622
- </head>
8623
- ${bodyOpen}
8624
- <script>window.__timelines=window.__timelines||{};</script>
8625
- ${rewrittenContent}
8626
- </body>
8627
- </html>`;
8628
- }
8629
- function hasBaseElement(html) {
8630
- return parseHTML(html).document.querySelector("base") !== null;
8631
- }
8632
8831
  var ALWAYS_AFFECTS = ["hyperframes.json", ...STUDIO_SIGNATURE_MANIFEST_PATHS];
8633
8832
  var REFERENCE = /\b(?:src|href|poster|data-composition-src)\s*=\s*(?:"([^"\n]*)"|'([^'\n]*)')|url\(\s*(?:"([^"\n]*)"|'([^'\n]*)'|([^"'\s)]+))/gi;
8634
8833
  var pathKey = process.platform === "darwin" || process.platform === "win32" ? (path) => resolve32(path).toLowerCase() : (path) => resolve32(path);
@@ -8763,7 +8962,7 @@ function readStudioMotionManifestContent(projectDir) {
8763
8962
  const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
8764
8963
  if (!existsSync5(manifestPath)) return "";
8765
8964
  try {
8766
- return readFileSync52(manifestPath, "utf-8");
8965
+ return readFileSync62(manifestPath, "utf-8");
8767
8966
  } catch {
8768
8967
  return "";
8769
8968
  }
@@ -8905,7 +9104,7 @@ function parsePreviewVariablesParam(raw) {
8905
9104
  }
8906
9105
  function variablesEtagSalt(raw) {
8907
9106
  if (!raw) return "";
8908
- return `:vars:${createHash4("sha1").update(raw).digest("hex").slice(0, 12)}`;
9107
+ return `:vars:${createHash23("sha1").update(raw).digest("hex").slice(0, 12)}`;
8909
9108
  }
8910
9109
  function previewVariablesFromRequest(rawVariables) {
8911
9110
  const parse22 = parsePreviewVariablesParam(rawVariables);
@@ -8945,13 +9144,13 @@ function resolveProjectMainHtml(projectDir, projectId) {
8945
9144
  recordPreviewRead(projectDir, blockHtmlPath);
8946
9145
  if (existsSync5(indexPath)) {
8947
9146
  return {
8948
- html: readFileSync52(indexPath, "utf-8"),
9147
+ html: readFileSync62(indexPath, "utf-8"),
8949
9148
  compositionPath: "index.html"
8950
9149
  };
8951
9150
  }
8952
9151
  if (existsSync5(blockHtmlPath)) {
8953
9152
  return {
8954
- html: readFileSync52(blockHtmlPath, "utf-8"),
9153
+ html: readFileSync62(blockHtmlPath, "utf-8"),
8955
9154
  compositionPath: `${projectId}.html`
8956
9155
  };
8957
9156
  }
@@ -9082,7 +9281,7 @@ ${etag}`;
9082
9281
  function pinSubCompHfIds(compFile, compPath) {
9083
9282
  if (!/\.html?$/i.test(compPath)) return void 0;
9084
9283
  try {
9085
- return ensureHfIds(readFileSync52(compFile, "utf-8"));
9284
+ return ensureHfIds(readFileSync62(compFile, "utf-8"));
9086
9285
  } catch {
9087
9286
  return null;
9088
9287
  }
@@ -9101,7 +9300,7 @@ ${etag}`;
9101
9300
  if (!compFile || !existsSync5(compFile) || !statSync42(compFile).isFile()) {
9102
9301
  return c.text("not found", 404);
9103
9302
  }
9104
- const compPathHash = createHash4("sha1").update(compPath).digest("hex");
9303
+ const compPathHash = createHash23("sha1").update(compPath).digest("hex");
9105
9304
  const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
9106
9305
  const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
9107
9306
  const ifNoneMatch = c.req.header("If-None-Match");
@@ -9193,7 +9392,7 @@ ${etag}`;
9193
9392
  recordProxyRequest();
9194
9393
  servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
9195
9394
  }
9196
- const textBuffer = isText ? Buffer.from(readFileSync52(file, "utf-8"), "utf-8") : null;
9395
+ const textBuffer = isText ? Buffer.from(readFileSync62(file, "utf-8"), "utf-8") : null;
9197
9396
  const totalSize = textBuffer ? textBuffer.length : statSync42(servedPath).size;
9198
9397
  const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
9199
9398
  // Node's web stream type and the DOM one do not overlap for tsc on
@@ -9252,7 +9451,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
9252
9451
  const findings = [];
9253
9452
  for (const file of htmlFiles) {
9254
9453
  if (coveredFiles.has(file)) continue;
9255
- const content = readFileSync62(join9(projectDir, file), "utf-8");
9454
+ const content = readFileSync72(join9(projectDir, file), "utf-8");
9256
9455
  const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
9257
9456
  for (const finding of result?.findings ?? []) {
9258
9457
  findings.push({ ...finding, file });
@@ -9418,7 +9617,7 @@ function registerRenderRoutes(api, adapter) {
9418
9617
  }
9419
9618
  const contentType = renderContentType(job.outputPath);
9420
9619
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
9421
- const content = readFileSync72(job.outputPath);
9620
+ const content = readFileSync8(job.outputPath);
9422
9621
  return new Response(content, {
9423
9622
  headers: {
9424
9623
  "Content-Type": contentType,
@@ -9436,7 +9635,7 @@ function registerRenderRoutes(api, adapter) {
9436
9635
  }
9437
9636
  const contentType = renderContentType(job.outputPath);
9438
9637
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
9439
- const content = readFileSync72(job.outputPath);
9638
+ const content = readFileSync8(job.outputPath);
9440
9639
  return new Response(content, {
9441
9640
  headers: {
9442
9641
  "Content-Type": contentType,
@@ -9469,7 +9668,7 @@ function registerRenderRoutes(api, adapter) {
9469
9668
  if (!fp) return c.json({ error: "forbidden" }, 403);
9470
9669
  if (!existsSync6(fp)) return c.json({ error: "not found" }, 404);
9471
9670
  const contentType = renderContentType(fp);
9472
- const content = readFileSync72(fp);
9671
+ const content = readFileSync8(fp);
9473
9672
  return new Response(content, {
9474
9673
  headers: {
9475
9674
  "Content-Type": contentType,
@@ -9494,7 +9693,7 @@ function registerRenderRoutes(api, adapter) {
9494
9693
  let audioLoweredDb;
9495
9694
  if (existsSync6(metaPath)) {
9496
9695
  try {
9497
- const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
9696
+ const meta = JSON.parse(readFileSync8(metaPath, "utf-8"));
9498
9697
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
9499
9698
  if (meta.durationMs) durationMs = meta.durationMs;
9500
9699
  if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
@@ -9702,7 +9901,7 @@ function registerImageThumbnailRoutes(api, adapter) {
9702
9901
  api.get("/projects/:id/image-thumbnail/*", async (c) => {
9703
9902
  try {
9704
9903
  const { path, info } = await resolveSource(adapter, c.req.param("id"), c.req.url);
9705
- const key = createHash23("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
9904
+ const key = createHash32("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
9706
9905
  const etag = `"${key}"`;
9707
9906
  c.header("ETag", etag);
9708
9907
  c.header("Cache-Control", "private, no-cache");
@@ -9727,84 +9926,6 @@ function registerImageThumbnailRoutes(api, adapter) {
9727
9926
  }
9728
9927
  });
9729
9928
  }
9730
- var ROOT_COMPOSITION = "index.html";
9731
- var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
9732
- function projectReader(projectDir) {
9733
- const sources = /* @__PURE__ */ new Map();
9734
- return (compPath) => {
9735
- if (!sources.has(compPath)) {
9736
- const file = resolveWithinProject(projectDir, compPath);
9737
- let source = null;
9738
- try {
9739
- source = file ? readFileSync8(file, "utf-8") : null;
9740
- } catch {
9741
- }
9742
- sources.set(compPath, source);
9743
- }
9744
- return sources.get(compPath) ?? null;
9745
- };
9746
- }
9747
- function normalizeSource(src) {
9748
- if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
9749
- const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
9750
- return path || null;
9751
- }
9752
- function closureOf(read, compPath) {
9753
- const closure = /* @__PURE__ */ new Set();
9754
- const visit = (path) => {
9755
- if (closure.has(path)) return;
9756
- closure.add(path);
9757
- for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
9758
- const mounted = normalizeSource(match[2] ?? "");
9759
- if (mounted) visit(mounted);
9760
- }
9761
- };
9762
- visit(compPath);
9763
- return closure;
9764
- }
9765
- var inputSignatures = /* @__PURE__ */ new Map();
9766
- var rootMemos = /* @__PURE__ */ new Map();
9767
- var sha1 = (text2) => createHash32("sha1").update(text2).digest("hex");
9768
- function rootHashes(read) {
9769
- const source = read(ROOT_COMPOSITION) ?? "";
9770
- return { root: sha1(source), head: sha1(rootHeadContent(source)) };
9771
- }
9772
- function compositionInputSignature(projectDir, compPath, projectSignature) {
9773
- const key = `${projectDir}\0${compPath}`;
9774
- const known = inputSignatures.get(key);
9775
- if (known?.projectSignature === projectSignature) return known.inputSignature;
9776
- const read = projectReader(projectDir);
9777
- const comp = normalizeSource(compPath) ?? compPath;
9778
- const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
9779
- const excluded = new Set(
9780
- [...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
9781
- );
9782
- let inputSignature;
9783
- if (comp === ROOT_COMPOSITION) {
9784
- inputSignature = createProjectSignature(projectDir, excluded);
9785
- } else {
9786
- const hashes = rootHashes(read);
9787
- if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
9788
- inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
9789
- }
9790
- inputSignatures.set(key, { projectSignature, inputSignature });
9791
- return inputSignature;
9792
- }
9793
- function compositionsAffectedBy(projectDir, changedPath) {
9794
- const changed = changedPath.replace(/\\/g, "/");
9795
- const read = projectReader(projectDir);
9796
- if (changed === ROOT_COMPOSITION) {
9797
- const now = rootHashes(read);
9798
- const memo = rootMemos.get(projectDir);
9799
- if (memo?.root === now.root) return memo.affected;
9800
- const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
9801
- rootMemos.set(projectDir, { ...now, affected });
9802
- return affected;
9803
- }
9804
- const mounted = closureOf(read, ROOT_COMPOSITION);
9805
- if (!mounted.has(changed)) return null;
9806
- return [...mounted].filter((path) => closureOf(read, path).has(changed));
9807
- }
9808
9929
  var THUMBNAIL_CACHE_VERSION = "v5";
9809
9930
  var THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
9810
9931
  var THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
@@ -11381,12 +11502,12 @@ export {
11381
11502
  createStudioMotionRenderBodyScript,
11382
11503
  MIME_TYPES,
11383
11504
  getMimeType,
11505
+ buildSubCompositionHtml,
11506
+ compositionsAffectedBy,
11384
11507
  applyFileMutations,
11385
11508
  requestSubPath,
11386
- buildSubCompositionHtml,
11387
11509
  affectsPreview,
11388
11510
  PREVIEW_BUNDLE_OPTIONS,
11389
- compositionsAffectedBy,
11390
11511
  createStudioApi,
11391
11512
  duplicateElementInHtml,
11392
11513
  createPreviewDocumentStore,