hyperframes 0.8.106 → 0.8.108

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 (177) hide show
  1. package/dist/{add-QRAI4CAF.js → add-OH75E7FX.js} +16 -16
  2. package/dist/{animatedGifPrep-RZVYVAXD.js → animatedGifPrep-ZDAWSJYL.js} +7 -7
  3. package/dist/{asset-WFBQ5VS6.js → asset-BVDO4DEW.js} +2 -2
  4. package/dist/{assetCataloger-BQREHRS2.js → assetCataloger-FXEHOUA3.js} +3 -3
  5. package/dist/{auth-LVD6TWYG.js → auth-K5GUKO5S.js} +2 -2
  6. package/dist/{autoUpdate-HGE2PUZY.js → autoUpdate-77ROWK5G.js} +2 -2
  7. package/dist/{backgroundChecks-63CSE4XR.js → backgroundChecks-3Q3D5WBJ.js} +3 -3
  8. package/dist/backgroundChecksWorker.js +3 -3
  9. package/dist/{batchRender-AQN5PRXM.js → batchRender-ZTHFMHZY.js} +4 -4
  10. package/dist/{beats-OBCN5OSQ.js → beats-HQZPR6KE.js} +19 -13
  11. package/dist/{benchmark-DYHJSR7W.js → benchmark-BQOWARGU.js} +13 -13
  12. package/dist/{browser-JLYV5T7O.js → browser-45CK7HXI.js} +10 -10
  13. package/dist/{bundleWithLocalizedFonts-JKJKZR2S.js → bundleWithLocalizedFonts-3M2LKRHK.js} +2 -2
  14. package/dist/{capture-5BCJWVC7.js → capture-6IVZXM7G.js} +2 -2
  15. package/dist/{capture-QYLHIYKJ.js → capture-VGEUOP7N.js} +8 -8
  16. package/dist/{catalog-6UYNKENR.js → catalog-VHD7B6FD.js} +16 -16
  17. package/dist/{check-YOGAZ6UE.js → check-UPNZZJXV.js} +32 -31
  18. package/dist/{checkBrowser-M73YHPNM.js → checkBrowser-KWTHKOOP.js} +31 -30
  19. package/dist/{chunk-JYRPUDVZ.js → chunk-5N4KKGSZ.js} +1 -1
  20. package/dist/{normalize-audio-H3SI5V63.js → chunk-5ON5GH3X.js} +101 -209
  21. package/dist/{chunk-NRMYDPFQ.js → chunk-5R5UHSXN.js} +9 -9
  22. package/dist/{chunk-OSZJCBZX.js → chunk-AFGDOKRI.js} +7 -7
  23. package/dist/{chunk-6CN2QLB5.js → chunk-AWUZ3AUD.js} +1 -1
  24. package/dist/{chunk-TEXPOC5V.js → chunk-B25H4M4V.js} +2 -2
  25. package/dist/{chunk-JXXE3SWC.js → chunk-C6XQER6H.js} +28 -14
  26. package/dist/{chunk-Y2CZEJPK.js → chunk-CFC3EEDQ.js} +2 -2
  27. package/dist/{chunk-PB2OX2JY.js → chunk-D7SCIX4D.js} +1 -1
  28. package/dist/{chunk-AXWBDWAV.js → chunk-FZGUZIK5.js} +3 -3
  29. package/dist/{chunk-4WAMN4IF.js → chunk-GYYFNAVA.js} +1 -1
  30. package/dist/{chunk-TYA65W4U.js → chunk-HQGJLZB6.js} +2 -2
  31. package/dist/{chunk-LAHK4P7Q.js → chunk-J7XPPADY.js} +3 -3
  32. package/dist/{chunk-I77CAR4W.js → chunk-KVOWTRD5.js} +2 -2
  33. package/dist/{chunk-GVZYTDOD.js → chunk-KZ5OSG4G.js} +1 -1
  34. package/dist/{chunk-IBCGKQB5.js → chunk-KZJ6V7RT.js} +3 -3
  35. package/dist/{chunk-HUHJYNST.js → chunk-LAQEZHOH.js} +1 -1
  36. package/dist/{chunk-HFFR7TN7.js → chunk-LPXM5L6S.js} +2 -1
  37. package/dist/{chunk-2IZZZJDZ.js → chunk-LTYI4E42.js} +4 -4
  38. package/dist/{chunk-J3Q3YC6P.js → chunk-LZPAZZZP.js} +1 -1
  39. package/dist/{chunk-5TBFBLIX.js → chunk-MGTECHI3.js} +2 -2
  40. package/dist/{chunk-4I6AQUWG.js → chunk-NGVH2QYT.js} +8 -8
  41. package/dist/{chunk-M4KS6MJN.js → chunk-NJLFRKHX.js} +4 -4
  42. package/dist/{chunk-6LPWMCPY.js → chunk-NROJESOK.js} +2 -2
  43. package/dist/{chunk-UVUAU3CH.js → chunk-ONY5CJXS.js} +48 -26
  44. package/dist/{chunk-O5TBFZ7X.js → chunk-OVRFBUXU.js} +2 -2
  45. package/dist/{chunk-6F6EEQNB.js → chunk-PSGDNMM6.js} +1 -1
  46. package/dist/{chunk-AZQLJHAO.js → chunk-PZTEY73C.js} +1 -1
  47. package/dist/{chunk-VW5OMJNG.js → chunk-QB6OM2BP.js} +89 -70
  48. package/dist/{chunk-I6M4GXMC.js → chunk-QBHT234I.js} +1 -1
  49. package/dist/{chunk-TMLEIURT.js → chunk-QRXICW5Q.js} +1 -1
  50. package/dist/{chunk-4NJZZHYT.js → chunk-RPMXMOFU.js} +1 -1
  51. package/dist/{chunk-XZSH5GS7.js → chunk-RSL3XFMO.js} +4 -4
  52. package/dist/{chunk-PUB5XZYL.js → chunk-SIVCJJHE.js} +1 -1
  53. package/dist/{chunk-UUE5BZGE.js → chunk-SQ42NHXP.js} +620 -74
  54. package/dist/{chunk-GOI4GCUB.js → chunk-THVNQFYG.js} +1 -1
  55. package/dist/{chunk-IQ4UCWV7.js → chunk-TIZQC44K.js} +1 -1
  56. package/dist/{chunk-FTXU3TQ4.js → chunk-US7SWKGC.js} +1 -1
  57. package/dist/{chunk-TLYU5625.js → chunk-UUDQD62L.js} +6 -6
  58. package/dist/{chunk-2LCIQS3K.js → chunk-VBXJSCHR.js} +3 -3
  59. package/dist/{chunk-DVCYGZ7R.js → chunk-VLHN34SB.js} +2 -2
  60. package/dist/{chunk-4Z5O7BTD.js → chunk-WEATBQTR.js} +10 -1
  61. package/dist/{chunk-TH4APNRP.js → chunk-X763EUBO.js} +2 -2
  62. package/dist/{chunk-32Y3HRS3.js → chunk-XIGK4LNU.js} +2 -2
  63. package/dist/{chunk-R5RF3LGS.js → chunk-XMAL2A7Y.js} +1 -1
  64. package/dist/{chunk-USWOTTSP.js → chunk-XYVFPDNU.js} +1 -1
  65. package/dist/chunk-YLVI7VL5.js +3541 -0
  66. package/dist/{chunk-YVWILZCO.js → chunk-ZFL6FVQZ.js} +5 -5
  67. package/dist/{clean-GRX3GXKR.js → clean-LCRWNIWO.js} +17 -16
  68. package/dist/cli.js +50 -50
  69. package/dist/{cloud-TQCNBM6H.js → cloud-ITRFE5UN.js} +4 -4
  70. package/dist/{cloudrun-ISE6GSUO.js → cloudrun-LVZ4OPPC.js} +5 -5
  71. package/dist/{compare-QAWB3O4U.js → compare-NPVMUILH.js} +26 -25
  72. package/dist/{compiler-QMEZK6CU.js → compiler-YYOV2BJ7.js} +3 -3
  73. package/dist/{component-NXNNSXN7.js → component-HJFMHPNW.js} +3 -3
  74. package/dist/{compositions-I4PIPJKO.js → compositions-I34RN5WD.js} +12 -12
  75. package/dist/{delete-ZC4ZRBBF.js → delete-WN3J74BD.js} +4 -4
  76. package/dist/{deterministicFonts-XS3PGWH5.js → deterministicFonts-A4CNCSAL.js} +2 -2
  77. package/dist/{dist-E44WU6LH.js → dist-WCMFH74H.js} +1 -1
  78. package/dist/{dist-R24TLUYO.js → dist-YUIPB7OX.js} +9 -3
  79. package/dist/docs/data-attributes.md +1 -1
  80. package/dist/{doctor-EFQ4XNT3.js → doctor-MYC2S3J3.js} +11 -11
  81. package/dist/{events-AVPSJNYU.js → events-7JVV4746.js} +10 -10
  82. package/dist/{events-COIPJKSK.js → events-AEUV2JPA.js} +9 -9
  83. package/dist/{feedback-3DYZDMLC.js → feedback-X7DDSZPU.js} +11 -11
  84. package/dist/{figma-7XPHSMNG.js → figma-ISI4PPTF.js} +3 -3
  85. package/dist/fontLocalizeCli.js +10 -10
  86. package/dist/{get-TSBHDMKL.js → get-IYALAE4I.js} +4 -4
  87. package/dist/{gpuPolicy-BQFPO7UA.js → gpuPolicy-KSWTVU5C.js} +7 -7
  88. package/dist/{grade-compare-5Y4LXQFG.js → grade-compare-YWPIWQLK.js} +26 -25
  89. package/dist/{gsapParser-EZGJSSV5.js → gsapParser-KVMPC2SC.js} +5 -1
  90. package/dist/{help-5YBRP3C4.js → help-S2KJRMAJ.js} +1 -1
  91. package/dist/{history-SRNXKL6D.js → history-CQDDCY4J.js} +17 -16
  92. package/dist/hyperframe-runtime.js +54 -54
  93. package/dist/hyperframe.manifest.json +1 -1
  94. package/dist/hyperframe.runtime.iife.js +54 -54
  95. package/dist/hyperframes-player.global.js +3 -3
  96. package/dist/{info-U4BOXV2I.js → info-4KFNUWYN.js} +12 -12
  97. package/dist/{init-T4JYHIBM.js → init-I7GMRHFW.js} +98 -42
  98. package/dist/{inspect-XYLD2BMM.js → inspect-DUCLCHSD.js} +27 -26
  99. package/dist/{installer-UZSVMUKT.js → installer-NBEN3ZIP.js} +5 -5
  100. package/dist/{keyframes-BGPXWP7O.js → keyframes-F3VDEN66.js} +13 -13
  101. package/dist/{lambda-U4WIJQCJ.js → lambda-BFHJMMCG.js} +6 -6
  102. package/dist/{layout-UREZQ37U.js → layout-RNCXJI6C.js} +27 -26
  103. package/dist/{lint-LSTEZZ6T.js → lint-3NKKYCMH.js} +13 -13
  104. package/dist/{lintProject-4P4C6AN2.js → lintProject-F2OTN2IR.js} +1 -1
  105. package/dist/{list-6VICFTAM.js → list-Q7SOE3NF.js} +4 -4
  106. package/dist/{login-MXDIDOOG.js → login-TGRGWS5E.js} +4 -4
  107. package/dist/{media-treatment-ZVCWEACM.js → media-treatment-5TUXWHWE.js} +13 -13
  108. package/dist/{models-LXUIU5DC.js → models-YJUTM6TH.js} +8 -8
  109. package/dist/{motionShot-2LYUQF3T.js → motionShot-5QUMZ7J3.js} +11 -11
  110. package/dist/normalize-audio-63BI5BPL.js +276 -0
  111. package/dist/{pipeline-OFFKTFXS.js → pipeline-4NN5T7Q3.js} +7 -7
  112. package/dist/{play-P56CWNVC.js → play-SRDESSZI.js} +22 -21
  113. package/dist/{preflight-TVCVP5P2.js → preflight-SVN4PSFO.js} +8 -8
  114. package/dist/{present-ATMOWKMH.js → present-TDYR5LRW.js} +18 -17
  115. package/dist/{preview-7U6HYHYE.js → preview-Z5MFRZUM.js} +18 -18
  116. package/dist/{producer-LGG3TOUI.js → producer-RS7CB3XJ.js} +1 -1
  117. package/dist/{projectConfig-V53SEFIS.js → projectConfig-JHEWPAA5.js} +7 -7
  118. package/dist/{publication-7RKKPKLM.js → publication-7XA2H7UB.js} +3 -3
  119. package/dist/{publish-TRRFCEMQ.js → publish-2R22XX77.js} +9 -9
  120. package/dist/{remove-background-HJFT46Z5.js → remove-background-KT2SWYCC.js} +8 -8
  121. package/dist/{render-TPZ5FXHS.js → render-4C3YKVZI.js} +4 -4
  122. package/dist/{render-YB7BARWL.js → render-7GDLEGXT.js} +15 -15
  123. package/dist/{render-2QZBNE2V.js → render-Y2Y7N5WD.js} +22 -22
  124. package/dist/{render-batch-4ZF5C2LN.js → render-batch-AFDBPDMM.js} +4 -4
  125. package/dist/{renderCancellation-4PYGAN5D.js → renderCancellation-RQ3UVNVJ.js} +7 -7
  126. package/dist/renderSetupWorker.js +1 -1
  127. package/dist/{resolver-4JRJPC6S.js → resolver-S72XBTEQ.js} +5 -5
  128. package/dist/{sherpa-MNCWRFD4.js → sherpa-PR3I4KGM.js} +10 -10
  129. package/dist/{skills-ETBA5B46.js → skills-BEYZZWY6.js} +12 -12
  130. package/dist/{skillsUpdateCheck-DQOUIYV5.js → skillsUpdateCheck-3USXDPWV.js} +3 -3
  131. package/dist/{snapshot-W3HTBAYH.js → snapshot-S77MQOV2.js} +27 -26
  132. package/dist/{src-QX5PPANW.js → src-6R4DC2YI.js} +6 -6
  133. package/dist/{src-QF6RYX76.js → src-IOJXK4VC.js} +9 -9
  134. package/dist/{staticProjectServer-WBXW2CA4.js → staticProjectServer-RXAFGDK7.js} +16 -15
  135. package/dist/{status-C3IDZFDG.js → status-CRS7H6FA.js} +7 -7
  136. package/dist/studio/assets/{hyperframes-player-dl6K6ABp.js → hyperframes-player-BakGew45.js} +1 -1
  137. package/dist/studio/assets/{index-ooaZZhKf.js → index-C5dyUHwk.js} +1 -1
  138. package/dist/studio/assets/{index-CMiYVGv5.js → index-C842tcVa.js} +203 -208
  139. package/dist/studio/assets/{index-DKT5NF1Y.js → index-CPe7d_wO.js} +1 -1
  140. package/dist/studio/assets/{index-lrT-gfHa.css → index-ncVJ5IVu.css} +1 -1
  141. package/dist/studio/chunk-6LUNLTHR.js +611 -0
  142. package/dist/studio/chunk-6LUNLTHR.js.map +1 -0
  143. package/dist/studio/{chunk-ICTZJPQQ.js → chunk-AOWVEVEU.js} +3 -494
  144. package/dist/studio/chunk-AOWVEVEU.js.map +1 -0
  145. package/dist/studio/{chunk-46XP7J3B.js → chunk-PQFEBX32.js} +5 -3
  146. package/dist/studio/chunk-PQFEBX32.js.map +1 -0
  147. package/dist/studio/{domEditingLayers-AQBDFNUC.js → domEditingLayers-JQPQGGT7.js} +3 -3
  148. package/dist/studio/index.d.ts +377 -12
  149. package/dist/studio/index.html +2 -2
  150. package/dist/studio/index.js +13445 -11874
  151. package/dist/studio/index.js.map +1 -1
  152. package/dist/studio/player.js +2 -2
  153. package/dist/studio/styles.css +1 -1
  154. package/dist/{studio-api-6IJPJVG6.js → studio-api-3FH7OSSS.js} +10 -9
  155. package/dist/{studioServer-77CY4XPO.js → studioServer-4E5RRPU7.js} +49 -42
  156. package/dist/{system-OAB2LYBV.js → system-YMANAUAM.js} +7 -7
  157. package/dist/{telemetry-XTN5BO5S.js → telemetry-46XSB2VN.js} +10 -10
  158. package/dist/templates/_shared/AGENTS.md +1 -1
  159. package/dist/templates/_shared/CLAUDE.md +1 -1
  160. package/dist/templates/from-file/index.html +3 -2
  161. package/dist/templates/warm-grain/index.html +2 -10
  162. package/dist/{timeline-YKKTGPL7.js → timeline-XKLKHGD2.js} +17 -16
  163. package/dist/{tokens-LU25F3IJ.js → tokens-JCZKPUPT.js} +2 -2
  164. package/dist/{transcribe-O7OL4RRU.js → transcribe-FIFJ3BBB.js} +13 -13
  165. package/dist/{transcribe-AL3E5MBZ.js → transcribe-FQ4YC2AS.js} +10 -8
  166. package/dist/{updateCheck-MZZML4DL.js → updateCheck-PDIH4GZW.js} +2 -2
  167. package/dist/{upgrade-PQRTEJJY.js → upgrade-TTGO6JCF.js} +2 -2
  168. package/dist/{urlDownloader-U3TNOPD5.js → urlDownloader-32JRHOPN.js} +6 -6
  169. package/dist/{validate-KQETGCIR.js → validate-G5F56ONL.js} +16 -16
  170. package/dist/{video-KAHL76HG.js → video-SGBEMEKA.js} +7 -7
  171. package/package.json +1 -1
  172. package/dist/chunk-FOKUJKBN.js +0 -3420
  173. package/dist/studio/chunk-46XP7J3B.js.map +0 -1
  174. package/dist/studio/chunk-DI4LCJHL.js +0 -102
  175. package/dist/studio/chunk-DI4LCJHL.js.map +0 -1
  176. package/dist/studio/chunk-ICTZJPQQ.js.map +0 -1
  177. /package/dist/studio/{domEditingLayers-AQBDFNUC.js.map → domEditingLayers-JQPQGGT7.js.map} +0 -0
@@ -15,11 +15,20 @@ import {
15
15
  splitElementInHtml,
16
16
  unwrapElementsFromHtml,
17
17
  wrapElementsInHtml
18
- } from "./chunk-M4KS6MJN.js";
18
+ } from "./chunk-NJLFRKHX.js";
19
19
  import {
20
+ atomicTempPath,
20
21
  createFileAtomically,
22
+ isAtomicTempPath,
21
23
  replaceFileAtomically
22
- } from "./chunk-4Z5O7BTD.js";
24
+ } from "./chunk-WEATBQTR.js";
25
+ import {
26
+ DEFAULT_TARGET_LUFS,
27
+ absoluteLoudnessPlan,
28
+ measureAudio,
29
+ requiredFfmpeg,
30
+ resolveLocalAudioPath
31
+ } from "./chunk-5ON5GH3X.js";
23
32
  import {
24
33
  PROXY_PARAMS_VERSION,
25
34
  PROXY_PENDING_RETRY_AFTER_SECONDS,
@@ -45,7 +54,7 @@ import {
45
54
  shouldPrewarmProxy,
46
55
  waitForProxy,
47
56
  walkDir
48
- } from "./chunk-O5TBFZ7X.js";
57
+ } from "./chunk-OVRFBUXU.js";
49
58
  import {
50
59
  findTimelineScript,
51
60
  parseGsapScriptAcorn
@@ -64,24 +73,26 @@ import {
64
73
  formatRenderOutputTimestamp,
65
74
  parseFps,
66
75
  sameInstant
67
- } from "./chunk-TEXPOC5V.js";
76
+ } from "./chunk-B25H4M4V.js";
68
77
  import {
69
78
  MagicString,
70
79
  VALID_CANVAS_RESOLUTIONS,
71
80
  unrollComputedTimeline
72
- } from "./chunk-TLYU5625.js";
81
+ } from "./chunk-UUDQD62L.js";
73
82
  import {
74
83
  addScenePartsManifest,
75
84
  ensureHfIds
76
- } from "./chunk-TH4APNRP.js";
85
+ } from "./chunk-X763EUBO.js";
77
86
  import {
78
87
  findStartTags,
79
88
  injectScriptsIntoHtml,
80
89
  injectTagsAtHeadStart,
81
90
  insertBeforeCloseTag,
82
91
  isFullHtmlDocument,
92
+ resolveRateSpec,
93
+ sourceTimeAt,
83
94
  stripEmbeddedRuntimeScripts
84
- } from "./chunk-FOKUJKBN.js";
95
+ } from "./chunk-YLVI7VL5.js";
85
96
  import {
86
97
  ProjectRootMissingError,
87
98
  folderGone,
@@ -94,12 +105,13 @@ import {
94
105
  import {
95
106
  isWithinProjectRoot,
96
107
  readProjectFile
97
- } from "./chunk-UVUAU3CH.js";
108
+ } from "./chunk-ONY5CJXS.js";
98
109
  import {
99
110
  FFPROBE_PATH_ENV,
100
111
  findFfBinary
101
112
  } from "./chunk-PJ3HDUCY.js";
102
113
  import {
114
+ readMediaOffsetSeconds,
103
115
  rewriteAssetPaths,
104
116
  rewriteCssAssetUrls,
105
117
  rewriteInlineStyleAssetUrls
@@ -198,7 +210,7 @@ function stampFileHfIds(filePath) {
198
210
  }
199
211
  }
200
212
 
201
- // ../studio-server/dist/chunk-IQK4ANAI.js
213
+ // ../studio-server/dist/chunk-KUEG5MYA.js
202
214
  import { randomUUID as randomUUID6 } from "crypto";
203
215
  import { mkdir as mkdir2, readdir as readdir3, rm as rm3, rmdir, writeFile } from "fs/promises";
204
216
  import {
@@ -338,7 +350,7 @@ var STUDIO_SIGNATURE_MANIFEST_PATHS = [
338
350
  ];
339
351
  function affectsProjectSignature(projectDir, changedPath) {
340
352
  const relativePath = relative(resolve(projectDir), resolve(changedPath));
341
- if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath)) {
353
+ if (relativePath === "" || relativePath.startsWith("..") || isAbsolute(relativePath) || isAtomicTempPath(relativePath)) {
342
354
  return false;
343
355
  }
344
356
  const segments = relativePath.split(sep);
@@ -354,6 +366,7 @@ function isPathWithin(parentDir, childPath) {
354
366
  function isTextContentEligible(file, size) {
355
367
  return SIGNATURE_TEXT_EXTENSIONS.has(extname(file).toLowerCase()) && size <= MAX_SIGNATURE_TEXT_BYTES;
356
368
  }
369
+ var isSkippedEntry = (entry) => SIGNATURE_EXCLUDED_DIRS.has(entry) || isAtomicTempPath(entry);
357
370
  function collectProjectSignatureFiles(projectDir, dir, files) {
358
371
  let entries;
359
372
  try {
@@ -362,7 +375,7 @@ function collectProjectSignatureFiles(projectDir, dir, files) {
362
375
  return;
363
376
  }
364
377
  for (const entry of entries) {
365
- if (SIGNATURE_EXCLUDED_DIRS.has(entry)) continue;
378
+ if (isSkippedEntry(entry)) continue;
366
379
  const file = resolve(dir, entry);
367
380
  if (!isPathWithin(projectDir, file)) continue;
368
381
  let stat22;
@@ -491,7 +504,7 @@ async function hashFile(path) {
491
504
  }
492
505
  async function cloneOrCopy(from, to, beforeReplace) {
493
506
  await mkdir(dirname2(to), { recursive: true });
494
- const temp = `${to}.${randomUUID2()}.tmp`;
507
+ const temp = atomicTempPath(to);
495
508
  try {
496
509
  await copyFile(from, temp, constants2.COPYFILE_FICLONE);
497
510
  beforeReplace?.();
@@ -2133,6 +2146,10 @@ var PROP_TO_GROUP2 = /* @__PURE__ */ new Map();
2133
2146
  for (const [group, props] of Object.entries(PROPERTY_GROUPS2)) {
2134
2147
  for (const p of props) PROP_TO_GROUP2.set(p, group);
2135
2148
  }
2149
+ function isXYPositionWrite(a) {
2150
+ const xy = (props) => !!props && ("x" in props || "y" in props);
2151
+ return a.propertyGroup === "position" && (xy(a.properties) || xy(a.fromProperties) || !!a.keyframes?.keyframes.some((k) => xy(k.properties)));
2152
+ }
2136
2153
  function classifyPropertyGroup2(prop) {
2137
2154
  return PROP_TO_GROUP2.get(prop) ?? "other";
2138
2155
  }
@@ -3745,7 +3762,7 @@ function dedupePositionWritesInScript(script, selector, keepId) {
3745
3762
  const parsed = parseGsapScriptAcornForWrite(script);
3746
3763
  if (!parsed) return script;
3747
3764
  const posWrites = parsed.located.filter(
3748
- (l) => l.animation.targetSelector === selector && l.animation.propertyGroup === "position"
3765
+ (l) => l.animation.targetSelector === selector && isXYPositionWrite(l.animation)
3749
3766
  );
3750
3767
  if (posWrites.length <= 1) return script;
3751
3768
  const keeper = posWrites.find((l) => l.id === keepId) ?? posWrites[posWrites.length - 1];
@@ -5099,16 +5116,25 @@ import { createHash as createHash32, randomUUID as randomUUID22 } from "crypto";
5099
5116
  import { readFileSync as readFileSync8 } from "fs";
5100
5117
  import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
5101
5118
  import { join as join12, resolve as resolve52 } from "path";
5119
+ import { execFile } from "child_process";
5120
+ import { existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
5121
+ import { dirname as dirname52, join as join13, relative as relative4, sep as sep22 } from "path";
5122
+ import { createHash as createHash42, randomBytes } from "crypto";
5123
+ import { existsSync as existsSync9 } from "fs";
5124
+ import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
5125
+ import { join as join14, resolve as resolve6 } from "path";
5126
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
5127
+ import { promisify } from "util";
5102
5128
  import { closeSync as closeSync32, constants as constants3, fstatSync as fstatSync3, openSync as openSync32, readSync } from "fs";
5103
5129
  import { streamSSE as streamSSE2 } from "hono/streaming";
5104
- import { existsSync as existsSync8 } from "fs";
5105
- import { basename as basename22, dirname as dirname52, extname as extname2, join as join13, posix } from "path";
5106
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
5130
+ import { existsSync as existsSync11 } from "fs";
5131
+ import { basename as basename22, dirname as dirname6, extname as extname2, join as join15, posix } from "path";
5132
+ import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
5107
5133
  import { homedir as homedir2 } from "os";
5108
- import { join as join14 } from "path";
5109
- import { createHash as createHash42 } from "crypto";
5110
- import { readFileSync as readFileSync12, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
5111
- import { join as join15 } from "path";
5134
+ import { join as join16 } from "path";
5135
+ import { createHash as createHash5 } from "crypto";
5136
+ import { readFileSync as readFileSync14, readdirSync as readdirSync5, rmSync as rmSync5 } from "fs";
5137
+ import { join as join17 } from "path";
5112
5138
  async function filterCompositionFiles(projectDir, files) {
5113
5139
  const htmlFiles = files.filter((f) => f.endsWith(".html") && !isInHiddenOrVendorDir(f));
5114
5140
  const checks = await Promise.all(
@@ -5505,6 +5531,29 @@ function assertExpectedVersion(mutation, current) {
5505
5531
  const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
5506
5532
  if (stale) throw new FileChangedError(mutation.sourceFile);
5507
5533
  }
5534
+ var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
5535
+ var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
5536
+ function isStudioFontFaceCss(css) {
5537
+ return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
5538
+ }
5539
+ function ensureStudioFontFaceCss(html, css) {
5540
+ if (html.includes(css)) return html;
5541
+ const block = FONT_STYLE_RE.exec(html);
5542
+ if (block) {
5543
+ const next = `${(block[2] ?? "").trim()}
5544
+ ${css}`.trim();
5545
+ return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
5546
+ ${next}
5547
+ </style>`);
5548
+ }
5549
+ const styleTag = `<style data-hf-studio-fonts="true">
5550
+ ${css}
5551
+ </style>`;
5552
+ if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
5553
+ </head>`);
5554
+ return `${styleTag}
5555
+ ${html}`;
5556
+ }
5508
5557
  var CompositionInsertionError = class extends Error {
5509
5558
  constructor(message, status) {
5510
5559
  super(message);
@@ -5764,7 +5813,7 @@ function requestSubPath(url, route) {
5764
5813
  return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
5765
5814
  }
5766
5815
  async function loadGsapParser() {
5767
- return import("./gsapParser-EZGJSSV5.js");
5816
+ return import("./gsapParser-KVMPC2SC.js");
5768
5817
  }
5769
5818
  function isDanglingSymlinkInProject(projectDir, lexicalPath) {
5770
5819
  if (!isSafePath(projectDir, dirname32(lexicalPath))) return false;
@@ -6122,7 +6171,7 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6122
6171
  const rad = grot * Math.PI / 180;
6123
6172
  const cos = Math.cos(rad);
6124
6173
  const sin = Math.sin(rad);
6125
- const round3 = (n) => Math.round(n * 1e3) / 1e3;
6174
+ const round32 = (n) => Math.round(n * 1e3) / 1e3;
6126
6175
  let script = block.scriptText;
6127
6176
  for (const m of members) {
6128
6177
  const memberSel = `#${m.id}`;
@@ -6139,27 +6188,27 @@ function bakeGroupTransformIntoMembers(html, groupId, members, groupCenter) {
6139
6188
  const visY = groupCenter.cy + gscale * (sin * dx + cos * dy) + gy;
6140
6189
  const newProps = {
6141
6190
  ...mProps,
6142
- x: round3(visX - m.cx),
6143
- y: round3(visY - m.cy)
6191
+ x: round32(visX - m.cx),
6192
+ y: round32(visY - m.cy)
6144
6193
  };
6145
6194
  if (gz !== 0) newProps.z = (typeof mProps.z === "number" ? mProps.z : 0) + gz;
6146
6195
  if (grot !== 0) {
6147
- newProps.rotation = round3(
6196
+ newProps.rotation = round32(
6148
6197
  (typeof mProps.rotation === "number" ? mProps.rotation : 0) + grot
6149
6198
  );
6150
6199
  }
6151
6200
  if (gscale !== 1) {
6152
- newProps.scale = round3((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6201
+ newProps.scale = round32((typeof mProps.scale === "number" ? mProps.scale : 1) * gscale);
6153
6202
  }
6154
6203
  const pivoted = /* @__PURE__ */ new Set(["x", "y", "z", "rotation", "scale"]);
6155
6204
  for (const [k, v] of Object.entries(gt)) {
6156
6205
  if (pivoted.has(k) || typeof v !== "number") continue;
6157
6206
  if (k === "scaleX" || k === "scaleY") {
6158
- if (v !== 1) newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6207
+ if (v !== 1) newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 1) * v);
6159
6208
  } else if (k === "transformPerspective") {
6160
6209
  if (typeof mProps[k] !== "number") newProps[k] = v;
6161
6210
  } else if (v !== 0) {
6162
- newProps[k] = round3((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6211
+ newProps[k] = round32((typeof mProps[k] === "number" ? mProps[k] : 0) + v);
6163
6212
  }
6164
6213
  }
6165
6214
  for (const s of [...sets].reverse()) {
@@ -6330,7 +6379,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
6330
6379
  let block = extractGsapScriptBlock(html);
6331
6380
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
6332
6381
  const compId = html.match(/data-composition-id="([^"]+)"/)?.[1] ?? "main";
6333
- const { GSAP_CDN } = await import("./dist-R24TLUYO.js");
6382
+ const { GSAP_CDN } = await import("./dist-YUIPB7OX.js");
6334
6383
  const bootstrap = [
6335
6384
  `<script src="${GSAP_CDN}"></script>`,
6336
6385
  "<script>",
@@ -6340,6 +6389,7 @@ async function prepareGsapMutationScript(c, res, firstMutation) {
6340
6389
  "</script>"
6341
6390
  ].join("\n");
6342
6391
  html = insertBeforeCloseTag(html, "body", `${bootstrap}
6392
+ `) ?? insertBeforeCloseTag(html, "template", `${bootstrap}
6343
6393
  `) ?? `${html}
6344
6394
  ${bootstrap}`;
6345
6395
  block = extractGsapScriptBlock(html);
@@ -7668,6 +7718,10 @@ function registerFileRoutes(api, adapter) {
7668
7718
  if (!Array.isArray(parsed.body.operations) || parsed.body.operations.length === 0) {
7669
7719
  return c.json({ error: "target and operations required" }, 400);
7670
7720
  }
7721
+ const { fontFaceCss } = parsed.body;
7722
+ if (fontFaceCss !== void 0 && !isStudioFontFaceCss(fontFaceCss)) {
7723
+ return c.json({ error: "fontFaceCss must be one @font-face rule" }, 400);
7724
+ }
7671
7725
  const unsafeFields = findUnsafeDomPatchValues(parsed.body);
7672
7726
  if (unsafeFields.length > 0) {
7673
7727
  return rejectUnsafeMutationValues(c, unsafeFields);
@@ -7679,11 +7733,9 @@ function registerFileRoutes(api, adapter) {
7679
7733
  } catch {
7680
7734
  return c.json({ error: "not found" }, 404);
7681
7735
  }
7682
- const { html: patched, matched } = patchElementInHtml(
7683
- originalContent,
7684
- parsed.target,
7685
- parsed.body.operations
7686
- );
7736
+ const element = patchElementInHtml(originalContent, parsed.target, parsed.body.operations);
7737
+ const { matched } = element;
7738
+ const patched = matched && isStudioFontFaceCss(fontFaceCss) ? ensureStudioFontFaceCss(element.html, fontFaceCss) : element.html;
7687
7739
  if (patched === originalContent) {
7688
7740
  const version2 = fileContentVersion(originalContent);
7689
7741
  c.header("ETag", version2);
@@ -8911,7 +8963,8 @@ function registerRenderRoutes(api, adapter) {
8911
8963
  progress: current.progress,
8912
8964
  status: current.status,
8913
8965
  stage: current.stage,
8914
- error: current.error
8966
+ error: current.error,
8967
+ ...current.status === "complete" && current.audioLoweredDb !== void 0 ? { audioLoweredDb: current.audioLoweredDb } : {}
8915
8968
  })
8916
8969
  });
8917
8970
  if (current.status !== "rendering") break;
@@ -9020,11 +9073,13 @@ function registerRenderRoutes(api, adapter) {
9020
9073
  const metaPath = join10(rendersDir, `${rid}.meta.json`);
9021
9074
  let status = "complete";
9022
9075
  let durationMs;
9076
+ let audioLoweredDb;
9023
9077
  if (existsSync6(metaPath)) {
9024
9078
  try {
9025
9079
  const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
9026
9080
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
9027
9081
  if (meta.durationMs) durationMs = meta.durationMs;
9082
+ if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
9028
9083
  } catch {
9029
9084
  }
9030
9085
  }
@@ -9034,7 +9089,8 @@ function registerRenderRoutes(api, adapter) {
9034
9089
  size: stat22.size,
9035
9090
  createdAt: stat22.mtimeMs,
9036
9091
  status,
9037
- durationMs
9092
+ durationMs,
9093
+ ...audioLoweredDb !== void 0 ? { audioLoweredDb } : {}
9038
9094
  };
9039
9095
  }).sort((a, b) => b.createdAt - a.createdAt);
9040
9096
  for (const file of files) {
@@ -9067,10 +9123,10 @@ var ThumbnailGenerationCoordinator = class {
9067
9123
  if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
9068
9124
  let entry = this.entries.get(key);
9069
9125
  if (!entry) {
9070
- let resolve6;
9126
+ let resolve7;
9071
9127
  let reject;
9072
9128
  const promise = new Promise((resolvePromise, rejectPromise) => {
9073
- resolve6 = resolvePromise;
9129
+ resolve7 = resolvePromise;
9074
9130
  reject = rejectPromise;
9075
9131
  });
9076
9132
  entry = {
@@ -9080,7 +9136,7 @@ var ThumbnailGenerationCoordinator = class {
9080
9136
  state: "queued",
9081
9137
  work,
9082
9138
  promise,
9083
- resolve: resolve6,
9139
+ resolve: resolve7,
9084
9140
  reject
9085
9141
  };
9086
9142
  this.entries.set(key, entry);
@@ -9094,7 +9150,7 @@ var ThumbnailGenerationCoordinator = class {
9094
9150
  return /* @__PURE__ */ new Set([...this.entries.keys(), ...[...this.activeEntries].map((entry) => entry.key)]);
9095
9151
  }
9096
9152
  lease(entry, signal) {
9097
- return new Promise((resolve6, reject) => {
9153
+ return new Promise((resolve7, reject) => {
9098
9154
  let released = false;
9099
9155
  const release = () => {
9100
9156
  if (released) return;
@@ -9118,7 +9174,7 @@ var ThumbnailGenerationCoordinator = class {
9118
9174
  entry.promise.then(
9119
9175
  (value) => {
9120
9176
  release();
9121
- resolve6(value);
9177
+ resolve7(value);
9122
9178
  },
9123
9179
  (reason) => {
9124
9180
  release();
@@ -9193,14 +9249,14 @@ async function generateThumbnail(path, signal) {
9193
9249
  async function resolveSource(adapter, id, url) {
9194
9250
  const project = await adapter.resolveProject(id);
9195
9251
  if (!project) throw new HTTPException(404);
9196
- let relative4;
9252
+ let relative5;
9197
9253
  try {
9198
- relative4 = requestSubPath(url, "projects/:id/image-thumbnail");
9254
+ relative5 = requestSubPath(url, "projects/:id/image-thumbnail");
9199
9255
  } catch {
9200
9256
  throw new HTTPException(400);
9201
9257
  }
9202
- if (!/\.jpe?g$/i.test(relative4)) throw new HTTPException(415);
9203
- const path = pinWithinProject(project.dir, relative4);
9258
+ if (!/\.jpe?g$/i.test(relative5)) throw new HTTPException(415);
9259
+ const path = pinWithinProject(project.dir, relative5);
9204
9260
  if (!path) throw new HTTPException(404);
9205
9261
  const info = await stat2(path).catch(() => {
9206
9262
  throw new HTTPException(404);
@@ -9541,6 +9597,493 @@ function registerWaveformRoutes(api, adapter) {
9541
9597
  return c.json({ peaks });
9542
9598
  });
9543
9599
  }
9600
+ var FREEZE_HOLD_SECONDS = 2;
9601
+ var LINK_ATTR = "data-link";
9602
+ var EPSILON = 1e-3;
9603
+ var round3 = (value) => Math.round(value * 1e3) / 1e3;
9604
+ function numberAttr(el, name) {
9605
+ const raw = el.getAttribute(name);
9606
+ if (raw === null || raw.trim() === "") return null;
9607
+ const value = Number(raw);
9608
+ return Number.isFinite(value) ? value : null;
9609
+ }
9610
+ function trackOf(el) {
9611
+ return numberAttr(el, "data-track-index") ?? numberAttr(el, "data-layer") ?? 0;
9612
+ }
9613
+ function freezeFrameMediaTime(input) {
9614
+ const rate = resolveRateSpec(input.automation, input.playbackRate);
9615
+ return input.mediaStart + sourceTimeAt(rate, Math.max(0, input.playhead - input.clipStart));
9616
+ }
9617
+ function freezeExtractArgs(src, mediaTime, output) {
9618
+ return ["-n", "-ss", String(round3(mediaTime)), "-i", src, "-frames:v", "1", output];
9619
+ }
9620
+ var randomStillToken = () => randomBytes(4).toString("hex");
9621
+ function freezeStillFileName(clipId, playhead, token = randomStillToken()) {
9622
+ const stem = clipId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 48) || "clip";
9623
+ const idHash = createHash42("sha256").update(clipId).digest("hex").slice(0, 10);
9624
+ return `${stem}-${idHash}-${Math.round(playhead * 1e3)}-${token}.png`;
9625
+ }
9626
+ function readFreezeSource(html, target, playhead) {
9627
+ const { document: document2 } = parseSourceDocument(html);
9628
+ const el = findTargetElement(document2, target);
9629
+ if (!el || el.tagName.toLowerCase() !== "video") return null;
9630
+ const src = el.getAttribute("src");
9631
+ const start = numberAttr(el, "data-start");
9632
+ const duration = numberAttr(el, "data-duration");
9633
+ if (!src || start === null || duration === null) return null;
9634
+ if (playhead <= start + EPSILON || playhead >= start + duration - EPSILON) return null;
9635
+ return {
9636
+ id: el.getAttribute("id") || "clip",
9637
+ src,
9638
+ mediaTime: freezeFrameMediaTime({
9639
+ clipStart: start,
9640
+ playhead,
9641
+ mediaStart: readMediaOffsetSeconds((name) => el.getAttribute(name)),
9642
+ playbackRate: numberAttr(el, "data-playback-rate") ?? 1,
9643
+ automation: el.getAttribute("data-automation")
9644
+ })
9645
+ };
9646
+ }
9647
+ function targetOf(el) {
9648
+ const hfId = el.getAttribute("data-hf-id");
9649
+ return { ...hfId ? { hfId } : {}, id: el.getAttribute("id") };
9650
+ }
9651
+ function spans(el, time) {
9652
+ const start = numberAttr(el, "data-start");
9653
+ const duration = numberAttr(el, "data-duration");
9654
+ if (start === null || duration === null) return false;
9655
+ return time > start + EPSILON && time < start + duration - EPSILON;
9656
+ }
9657
+ function uniqueId(document2, base) {
9658
+ let id = base;
9659
+ for (let n = 2; document2.getElementById(id); n++) id = `${base}-${n}`;
9660
+ return id;
9661
+ }
9662
+ function shiftTracks(document2, tracks, from, by) {
9663
+ for (const el of document2.querySelectorAll("[data-start]")) {
9664
+ if (el.hasAttribute("data-composition-id") || !tracks.has(trackOf(el))) continue;
9665
+ const start = numberAttr(el, "data-start");
9666
+ if (start === null || start < from - EPSILON) continue;
9667
+ el.setAttribute("data-start", String(round3(start + by)));
9668
+ }
9669
+ }
9670
+ function growRootToFit(document2) {
9671
+ const root = document2.querySelector("[data-composition-id]");
9672
+ const rootDuration = root ? numberAttr(root, "data-duration") : null;
9673
+ if (!root || rootDuration === null) return;
9674
+ let end = rootDuration;
9675
+ for (const el of root.querySelectorAll("[data-start][data-duration]")) {
9676
+ const start = numberAttr(el, "data-start");
9677
+ const duration = numberAttr(el, "data-duration");
9678
+ if (start !== null && duration !== null) end = Math.max(end, start + duration);
9679
+ }
9680
+ if (end > rootDuration + EPSILON) root.setAttribute("data-duration", String(round3(end)));
9681
+ }
9682
+ var COPIED_PICTURE_ATTRS = ["class", "style", "data-color-grading"];
9683
+ function buildStill(document2, video, input) {
9684
+ const img = document2.createElement("img");
9685
+ img.setAttribute("id", input.id);
9686
+ for (const name of COPIED_PICTURE_ATTRS) {
9687
+ const value = video.getAttribute(name);
9688
+ if (value !== null) img.setAttribute(name, value);
9689
+ }
9690
+ if (!img.classList.contains("clip")) img.classList.add("clip");
9691
+ img.setAttribute("src", input.imageSrc);
9692
+ img.setAttribute("alt", "");
9693
+ img.setAttribute("data-start", String(round3(input.playhead)));
9694
+ img.setAttribute("data-duration", String(input.holdSeconds));
9695
+ img.setAttribute("data-track-index", String(trackOf(video)));
9696
+ img.setAttribute("data-timeline-label", "Freeze");
9697
+ return img;
9698
+ }
9699
+ function linkedPartners(document2, video) {
9700
+ const link = video.getAttribute(LINK_ATTR);
9701
+ if (!link) return [];
9702
+ return Array.from(document2.querySelectorAll(`[${LINK_ATTR}]`)).filter(
9703
+ (el) => el !== video && el.getAttribute(LINK_ATTR) === link
9704
+ );
9705
+ }
9706
+ function splitAll(html, elements, time) {
9707
+ const rightHalfIds = [];
9708
+ let next = html;
9709
+ for (const el of elements) {
9710
+ const split = splitElementInHtml(
9711
+ next,
9712
+ targetOf(el),
9713
+ time,
9714
+ `${el.getAttribute("id") || "clip"}-split`,
9715
+ {
9716
+ start: numberAttr(el, "data-start") ?? 0,
9717
+ duration: numberAttr(el, "data-duration") ?? 0,
9718
+ track: trackOf(el)
9719
+ }
9720
+ );
9721
+ if (!split.matched || !split.newId) return null;
9722
+ next = split.html;
9723
+ rightHalfIds.push(split.newId);
9724
+ }
9725
+ return { html: next, rightHalfIds };
9726
+ }
9727
+ function relinkRightHalves(document2, link, ids) {
9728
+ if (!link) return;
9729
+ const rightLink = uniqueLink(document2, link);
9730
+ for (const id of ids) document2.getElementById(id)?.setAttribute(LINK_ATTR, rightLink);
9731
+ }
9732
+ function applyFreezeFrameToHtml(html, input) {
9733
+ const hold = input.holdSeconds ?? FREEZE_HOLD_SECONDS;
9734
+ const { document: before } = parseSourceDocument(html);
9735
+ const video = findTargetElement(before, input.target);
9736
+ if (!video || !isHTMLElement(video) || !spans(video, input.playhead)) return null;
9737
+ const partners = linkedPartners(before, video);
9738
+ const tracks = /* @__PURE__ */ new Set([trackOf(video), ...partners.map(trackOf)]);
9739
+ const cut = splitAll(
9740
+ html,
9741
+ [video, ...partners.filter((el) => spans(el, input.playhead))],
9742
+ input.playhead
9743
+ );
9744
+ if (!cut) return null;
9745
+ const { document: document2, wrappedFragment } = parseSourceDocument(cut.html);
9746
+ relinkRightHalves(document2, video.getAttribute(LINK_ATTR), cut.rightHalfIds);
9747
+ shiftTracks(document2, tracks, input.playhead, hold);
9748
+ const leftHalf = findTargetElement(document2, input.target);
9749
+ if (!leftHalf?.parentElement) return null;
9750
+ const freezeId = uniqueId(document2, `${video.getAttribute("id") || "clip"}-freeze`);
9751
+ const still = buildStill(document2, leftHalf, {
9752
+ id: freezeId,
9753
+ imageSrc: input.imageSrc,
9754
+ playhead: input.playhead,
9755
+ holdSeconds: hold
9756
+ });
9757
+ leftHalf.parentElement.insertBefore(still, leftHalf.nextSibling);
9758
+ growRootToFit(document2);
9759
+ return {
9760
+ html: ensureHfIds(wrappedFragment ? document2.body.innerHTML || "" : document2.toString()),
9761
+ freezeId
9762
+ };
9763
+ }
9764
+ function uniqueLink(document2, base) {
9765
+ const taken = new Set(
9766
+ Array.from(document2.querySelectorAll(`[${LINK_ATTR}]`)).map((el) => el.getAttribute(LINK_ATTR))
9767
+ );
9768
+ let candidate = `${base}-2`;
9769
+ for (let n = 3; taken.has(candidate); n++) candidate = `${base}-${n}`;
9770
+ return candidate;
9771
+ }
9772
+ var EXTRACT_TIMEOUT_MS = 3e4;
9773
+ var ffmpegExtractor = (args) => {
9774
+ const ffmpeg = findFfBinary("ffmpeg", { configuredMustExist: true });
9775
+ if (!ffmpeg) return Promise.resolve({ ok: false, error: "ffmpeg not found" });
9776
+ return new Promise((resolvePromise) => {
9777
+ execFile(
9778
+ ffmpeg,
9779
+ args,
9780
+ { timeout: EXTRACT_TIMEOUT_MS, windowsHide: true },
9781
+ (error, _o, stderr) => resolvePromise(
9782
+ error ? { ok: false, error: String(stderr || error.message) } : { ok: true }
9783
+ )
9784
+ );
9785
+ });
9786
+ };
9787
+ function isFreezeFrameRequest(value) {
9788
+ if (typeof value !== "object" || value === null) return false;
9789
+ const body = value;
9790
+ return typeof body.path === "string" && body.path.length > 0 && typeof body.expectedVersion === "string" && typeof body.target === "object" && body.target !== null && typeof body.playhead === "number" && Number.isFinite(body.playhead);
9791
+ }
9792
+ var FREEZE_DIR = ["assets", "freeze"];
9793
+ function readExpected(absPath, expectedVersion) {
9794
+ let content;
9795
+ try {
9796
+ content = readFileSync11(absPath, "utf-8");
9797
+ } catch {
9798
+ return { error: "not found", status: 404 };
9799
+ }
9800
+ return fileContentVersion(content) === expectedVersion ? { content } : { error: "file conflict", status: 409 };
9801
+ }
9802
+ async function extractStill(projectDir, absPath, source, playhead, tools) {
9803
+ const fileDir = dirname52(absPath);
9804
+ const mediaPath = pinWithinProject(projectDir, relative4(projectDir, join13(fileDir, source.src)));
9805
+ if (!mediaPath) return { error: `forbidden media path: ${source.src}`, status: 403 };
9806
+ const freezeDir = join13(projectDir, ...FREEZE_DIR);
9807
+ const fileName = freezeStillFileName(source.id, playhead, tools.stillToken());
9808
+ mkdirWithinProject(projectDir, freezeDir);
9809
+ const imagePath = pinWithinProject(projectDir, join13(...FREEZE_DIR, fileName));
9810
+ if (!imagePath || dirname52(imagePath) !== freezeDir) {
9811
+ return { error: `forbidden freeze path: ${fileName}`, status: 403 };
9812
+ }
9813
+ if (existsSync8(imagePath))
9814
+ return { error: `freeze still already exists: ${fileName}`, status: 409 };
9815
+ const extracted = await tools.extract(freezeExtractArgs(mediaPath, source.mediaTime, imagePath));
9816
+ if (!extracted.ok) {
9817
+ return {
9818
+ error: `Could not extract the frame: ${extracted.error ?? "ffmpeg failed"}`,
9819
+ status: 500
9820
+ };
9821
+ }
9822
+ const depth = relative4(projectDir, fileDir).split(sep22).filter(Boolean).length;
9823
+ return { imageSrc: `${"../".repeat(depth)}${FREEZE_DIR.join("/")}/${fileName}` };
9824
+ }
9825
+ function writeFolded(projectDir, absPath, path, before, after, options) {
9826
+ if (readFileSync11(absPath, "utf-8") !== before) {
9827
+ return { error: `file conflict: ${path}`, status: 409 };
9828
+ }
9829
+ const backup = snapshotBeforeWrite(projectDir, absPath);
9830
+ if (backup.error) return { error: `Failed to create backup: ${backup.error}`, status: 500 };
9831
+ const writeToken = createWriteToken(options.token);
9832
+ replaceFileAtomically(absPath, after, statSync8(absPath).mode);
9833
+ const version = fileContentVersion(after);
9834
+ recordFileWriteReceipt(absPath, { path, version, writeToken, overwrote: before });
9835
+ return { version, writeToken, backupPath: backupPathForResponse(projectDir, backup.backupPath) };
9836
+ }
9837
+ function registerFreezeFrameRoutes(api, adapter, extract = ffmpegExtractor, stillToken = randomStillToken) {
9838
+ api.post("/projects/:id/file-mutations/freeze-frame", async (c) => {
9839
+ const body = await c.req.json().catch(() => null);
9840
+ if (!isFreezeFrameRequest(body)) {
9841
+ return c.json({ error: "path, expectedVersion, target and playhead required" }, 400);
9842
+ }
9843
+ const project = await adapter.resolveProject(c.req.param("id"));
9844
+ if (!project) return c.json({ error: "not found" }, 404);
9845
+ const absPath = pinWithinProject(project.dir, body.path);
9846
+ if (!absPath) return c.json({ error: `forbidden path: ${body.path}` }, 403);
9847
+ const read = readExpected(absPath, body.expectedVersion);
9848
+ if ("error" in read) return c.json({ error: `${read.error}: ${body.path}` }, read.status);
9849
+ const before = read.content;
9850
+ const source = readFreezeSource(before, body.target, body.playhead);
9851
+ if (!source) return c.json({ error: "Move the playhead inside a video clip to freeze" }, 400);
9852
+ const still = await extractStill(project.dir, absPath, source, body.playhead, {
9853
+ extract,
9854
+ stillToken
9855
+ });
9856
+ if ("error" in still) return c.json({ error: still.error }, still.status);
9857
+ const { imageSrc } = still;
9858
+ const folded = applyFreezeFrameToHtml(before, {
9859
+ target: body.target,
9860
+ playhead: body.playhead,
9861
+ imageSrc
9862
+ });
9863
+ if (!folded) return c.json({ error: "Freeze target was not found in the file" }, 400);
9864
+ const written = writeFolded(project.dir, absPath, body.path, before, folded.html, {
9865
+ token: body.transactionToken ?? c.req.header("X-Hyperframes-Write-Token")
9866
+ });
9867
+ if ("error" in written) return c.json({ error: written.error }, written.status);
9868
+ const { version, writeToken, backupPath } = written;
9869
+ return c.json({
9870
+ ok: true,
9871
+ path: body.path,
9872
+ before,
9873
+ after: folded.html,
9874
+ version,
9875
+ writeToken,
9876
+ backupPath,
9877
+ freezeId: folded.freezeId,
9878
+ imageSrc
9879
+ });
9880
+ });
9881
+ }
9882
+ var defaultMeasure = (file, window2) => measureAudio(requiredFfmpeg(), file, window2);
9883
+ var InvalidField = class extends Error {
9884
+ };
9885
+ function numberField(body, name, fallback, valid) {
9886
+ const raw = body[name];
9887
+ if (raw === void 0 || raw === null) return fallback;
9888
+ if (typeof raw === "number" && Number.isFinite(raw) && valid(raw)) return raw;
9889
+ throw new InvalidField(`${name} is out of range`);
9890
+ }
9891
+ function readNormalizeRequest(body) {
9892
+ const src = typeof body.src === "string" ? body.src.trim() : "";
9893
+ if (!src) throw new InvalidField("src is required");
9894
+ const duration = numberField(body, "duration", 0, (v) => v > 0);
9895
+ return {
9896
+ id: typeof body.id === "string" && body.id ? body.id : src,
9897
+ src,
9898
+ window: {
9899
+ mediaStart: numberField(body, "mediaStart", 0, (v) => v >= 0),
9900
+ duration: duration > 0 ? duration : null,
9901
+ playbackRate: numberField(body, "playbackRate", 1, (v) => v > 0)
9902
+ },
9903
+ volume: numberField(body, "volume", 1, (v) => v >= 0),
9904
+ targetLufs: numberField(body, "targetLufs", DEFAULT_TARGET_LUFS, (v) => v < 0)
9905
+ };
9906
+ }
9907
+ function parseNormalizeRequest(body) {
9908
+ try {
9909
+ return readNormalizeRequest(body);
9910
+ } catch (error) {
9911
+ if (error instanceof InvalidField) return error.message;
9912
+ throw error;
9913
+ }
9914
+ }
9915
+ function isRecord(value) {
9916
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9917
+ }
9918
+ function registerLoudnessRoutes(api, adapter, measure = defaultMeasure) {
9919
+ api.post("/projects/:id/loudness/normalize", async (c) => {
9920
+ const project = await adapter.resolveProject(c.req.param("id"));
9921
+ if (!project) return c.json({ error: "not found" }, 404);
9922
+ const body = await c.req.json().catch(() => null);
9923
+ if (!isRecord(body)) return c.json({ error: "invalid body" }, 400);
9924
+ const request = parseNormalizeRequest(body);
9925
+ if (typeof request === "string") return c.json({ error: request }, 400);
9926
+ let file;
9927
+ try {
9928
+ file = resolveLocalAudioPath(project.dir, request.src);
9929
+ } catch (error) {
9930
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
9931
+ }
9932
+ if (!existsSync9(file)) return c.json({ error: "file not found" }, 404);
9933
+ try {
9934
+ const measurement = await measure(file, request.window);
9935
+ const plan = absoluteLoudnessPlan(
9936
+ { id: request.id, volume: request.volume, ...measurement },
9937
+ request.targetLufs
9938
+ );
9939
+ return c.json({ plan });
9940
+ } catch (error) {
9941
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 422);
9942
+ }
9943
+ });
9944
+ }
9945
+ var PEAK_BIN_SECONDS = 0.05;
9946
+ var PEAK_SAMPLE_RATE = 48e3;
9947
+ var execFileAsync = promisify(execFile2);
9948
+ var PEAK_MAP_CACHE_VERSION = "peaks-v2";
9949
+ function createPeakAccumulator(sampleRate = PEAK_SAMPLE_RATE, binSeconds = PEAK_BIN_SECONDS, channels = 2) {
9950
+ return {
9951
+ bins: [],
9952
+ frame: 0,
9953
+ carry: Buffer.alloc(0),
9954
+ framesPerBin: Math.max(1, Math.round(sampleRate * binSeconds)),
9955
+ channels: Math.max(1, channels)
9956
+ };
9957
+ }
9958
+ function framePeak(data, offset, channels) {
9959
+ let peak = 0;
9960
+ for (let ch = 0; ch < channels; ch++) {
9961
+ peak = Math.max(peak, Math.abs(data.readFloatLE(offset + ch * 4)));
9962
+ }
9963
+ return peak;
9964
+ }
9965
+ function accumulatePeaks(acc, chunk) {
9966
+ const data = acc.carry.length > 0 ? Buffer.concat([acc.carry, chunk]) : chunk;
9967
+ const frameBytes = 4 * acc.channels;
9968
+ const whole = data.length - data.length % frameBytes;
9969
+ for (let offset = 0; offset < whole; offset += frameBytes) {
9970
+ const peak = framePeak(data, offset, acc.channels);
9971
+ const bin = Math.floor(acc.frame / acc.framesPerBin);
9972
+ if (acc.bins.length <= bin) acc.bins.push(0);
9973
+ if (peak > (acc.bins[bin] ?? 0)) acc.bins[bin] = peak;
9974
+ acc.frame += 1;
9975
+ }
9976
+ acc.carry = Buffer.from(data.subarray(whole));
9977
+ }
9978
+ function finishPeaks(acc) {
9979
+ return acc.bins.map((peak) => Number(peak.toFixed(4)));
9980
+ }
9981
+ async function probeChannels(mediaPath) {
9982
+ const { stdout } = await execFileAsync(
9983
+ findFfBinary("ffprobe") ?? "ffprobe",
9984
+ [
9985
+ "-v",
9986
+ "error",
9987
+ "-select_streams",
9988
+ "a:0",
9989
+ "-show_entries",
9990
+ "stream=channels",
9991
+ "-of",
9992
+ "csv=p=0",
9993
+ "--",
9994
+ mediaPath
9995
+ ],
9996
+ { encoding: "utf8", timeout: 3e4, windowsHide: true }
9997
+ );
9998
+ const channels = Number.parseInt(stdout.trim(), 10);
9999
+ if (!Number.isFinite(channels) || channels < 1) throw new Error("no audio stream");
10000
+ return channels;
10001
+ }
10002
+ async function decodePeakMap(mediaPath) {
10003
+ const channels = await probeChannels(mediaPath);
10004
+ return new Promise((resolvePromise, reject) => {
10005
+ const proc = spawn2(
10006
+ findFfBinary("ffmpeg") ?? "ffmpeg",
10007
+ [
10008
+ "-v",
10009
+ "error",
10010
+ "-i",
10011
+ mediaPath,
10012
+ "-map",
10013
+ "0:a:0",
10014
+ "-vn",
10015
+ "-ar",
10016
+ String(PEAK_SAMPLE_RATE),
10017
+ "-f",
10018
+ "f32le",
10019
+ "pipe:1"
10020
+ ],
10021
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
10022
+ );
10023
+ const acc = createPeakAccumulator(PEAK_SAMPLE_RATE, PEAK_BIN_SECONDS, channels);
10024
+ proc.stdout?.on("data", (chunk) => accumulatePeaks(acc, chunk));
10025
+ proc.on("close", (code) => {
10026
+ if (code !== 0) {
10027
+ reject(new Error(`ffmpeg failed decoding audio (exit ${code})`));
10028
+ return;
10029
+ }
10030
+ if (acc.frame === 0) {
10031
+ reject(new Error("ffmpeg produced no audio samples"));
10032
+ return;
10033
+ }
10034
+ resolvePromise(finishPeaks(acc));
10035
+ });
10036
+ proc.on("error", reject);
10037
+ });
10038
+ }
10039
+ function buildPeakMapCacheKey(assetPath, fingerprint) {
10040
+ const name = assetPath.replace(/[/\\]/g, "_");
10041
+ return `${PEAK_MAP_CACHE_VERSION}_${name}_${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}.json`;
10042
+ }
10043
+ var decodesInFlight = /* @__PURE__ */ new Map();
10044
+ function decodeOnce(cachePath, mediaPath, decode) {
10045
+ const pending = decodesInFlight.get(cachePath);
10046
+ if (pending) return pending;
10047
+ const started = decode(mediaPath).finally(() => decodesInFlight.delete(cachePath));
10048
+ decodesInFlight.set(cachePath, started);
10049
+ return started;
10050
+ }
10051
+ function readCachedBins(cacheDir, cachePath) {
10052
+ try {
10053
+ if (!isWaveformCacheDirectory(cacheDir) || !existsSync10(cachePath)) return null;
10054
+ const parsed = JSON.parse(readFileSync12(cachePath, "utf-8"));
10055
+ return Array.isArray(parsed) && parsed.every((n) => typeof n === "number") ? parsed : null;
10056
+ } catch {
10057
+ return null;
10058
+ }
10059
+ }
10060
+ function registerPeakRoutes(api, adapter, decode = decodePeakMap) {
10061
+ api.get("/projects/:id/peaks/*", async (c) => {
10062
+ const project = await adapter.resolveProject(c.req.param("id"));
10063
+ if (!project) return c.json({ error: "not found" }, 404);
10064
+ const assetPath = requestSubPath(c.req.url, "projects/:id/peaks");
10065
+ const mediaPath = resolve6(project.dir, assetPath);
10066
+ if (!isWithinProjectRoot(project.dir, mediaPath))
10067
+ return c.json({ error: "file not found" }, 404);
10068
+ const stats = statSync9(mediaPath, { throwIfNoEntry: false });
10069
+ if (!stats?.isFile()) return c.json({ error: "file not found" }, 404);
10070
+ const cacheDir = join14(project.dir, ".waveform-cache");
10071
+ const cachePath = join14(cacheDir, buildPeakMapCacheKey(assetPath, stats));
10072
+ const cached = readCachedBins(cacheDir, cachePath);
10073
+ if (cached) return c.json({ binSeconds: PEAK_BIN_SECONDS, bins: cached });
10074
+ let bins;
10075
+ try {
10076
+ bins = await decodeOnce(cachePath, mediaPath, decode);
10077
+ } catch {
10078
+ return c.json({ error: "failed to decode audio" }, 500);
10079
+ }
10080
+ try {
10081
+ writeWaveformCache(cachePath, bins);
10082
+ } catch {
10083
+ }
10084
+ return c.json({ binSeconds: PEAK_BIN_SECONDS, bins });
10085
+ });
10086
+ }
9544
10087
  var MAX_FONT_RESULTS = 2e3;
9545
10088
  var GOOGLE_FONTS_METADATA_URL = "https://fonts.google.com/metadata/fonts";
9546
10089
  var GOOGLE_FONTS_FETCH_TIMEOUT_MS = 3e3;
@@ -9568,7 +10111,7 @@ var GOOGLE_FONT_FALLBACKS = [
9568
10111
  "Outfit",
9569
10112
  "JetBrains Mono"
9570
10113
  ];
9571
- function isRecord(value) {
10114
+ function isRecord2(value) {
9572
10115
  return typeof value === "object" && value !== null;
9573
10116
  }
9574
10117
  function collectFontsFromDir(dir) {
@@ -9592,10 +10135,10 @@ function listInstalledFontFamilies() {
9592
10135
  return cachedFonts;
9593
10136
  }
9594
10137
  function parseGoogleFontMetadata(value) {
9595
- if (!isRecord(value) || !Array.isArray(value.familyMetadataList)) return [];
10138
+ if (!isRecord2(value) || !Array.isArray(value.familyMetadataList)) return [];
9596
10139
  const families = [];
9597
10140
  for (const entry of value.familyMetadataList) {
9598
- if (!isRecord(entry) || typeof entry.family !== "string") continue;
10141
+ if (!isRecord2(entry) || typeof entry.family !== "string") continue;
9599
10142
  families.push(entry.family);
9600
10143
  }
9601
10144
  return families;
@@ -9695,14 +10238,14 @@ function registerRegistryRoutes(api, adapter) {
9695
10238
  }
9696
10239
  });
9697
10240
  }
9698
- function isRecord2(value) {
10241
+ function isRecord3(value) {
9699
10242
  return typeof value === "object" && value !== null && !Array.isArray(value);
9700
10243
  }
9701
10244
  function isFiniteNumber(value) {
9702
10245
  return typeof value === "number" && Number.isFinite(value);
9703
10246
  }
9704
10247
  function isStringRecord(value) {
9705
- return isRecord2(value) && Object.values(value).every((v) => typeof v === "string");
10248
+ return isRecord3(value) && Object.values(value).every((v) => typeof v === "string");
9706
10249
  }
9707
10250
  function hasString(value, key) {
9708
10251
  return typeof value[key] === "string";
@@ -9720,20 +10263,20 @@ function hasOptionalNumber(value, key) {
9720
10263
  return value[key] === void 0 || isFiniteNumber(value[key]);
9721
10264
  }
9722
10265
  function isBoundingBox(value) {
9723
- return isRecord2(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
10266
+ return isRecord3(value) && ["x", "y", "width", "height"].every((key) => isFiniteNumber(value[key]));
9724
10267
  }
9725
10268
  function isTarget(value) {
9726
- if (!isRecord2(value)) return false;
10269
+ if (!isRecord3(value)) return false;
9727
10270
  return hasOptionalNullableString(value, "id") && hasOptionalString(value, "hfId") && hasOptionalString(value, "selector") && hasOptionalNumber(value, "selectorIndex");
9728
10271
  }
9729
10272
  function isTextField(value) {
9730
- return isRecord2(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
10273
+ return isRecord3(value) && hasRequiredStrings(value, ["key", "label", "value", "tagName"]) && ["self", "child", "text-node"].includes(value.source);
9731
10274
  }
9732
10275
  function isTextFields(value) {
9733
10276
  return Array.isArray(value) && value.every(isTextField);
9734
10277
  }
9735
10278
  function isSelectionSnapshot(value) {
9736
- if (!isRecord2(value)) return false;
10279
+ if (!isRecord3(value)) return false;
9737
10280
  const checks = [
9738
10281
  value.schemaVersion === 1 && hasRequiredStrings(value, [
9739
10282
  "projectId",
@@ -9751,7 +10294,7 @@ function isSelectionSnapshot(value) {
9751
10294
  isStringRecord(value.inlineStyles),
9752
10295
  isStringRecord(value.computedStyles),
9753
10296
  isTextFields(value.textFields),
9754
- isRecord2(value.capabilities)
10297
+ isRecord3(value.capabilities)
9755
10298
  ];
9756
10299
  return checks.every(Boolean);
9757
10300
  }
@@ -9775,7 +10318,7 @@ function registerSelectionRoutes(api, adapter) {
9775
10318
  } catch {
9776
10319
  return c.json({ error: "invalid json" }, 400);
9777
10320
  }
9778
- if (!isRecord2(body) || !("selection" in body)) {
10321
+ if (!isRecord3(body) || !("selection" in body)) {
9779
10322
  return c.json({ error: "missing selection" }, 400);
9780
10323
  }
9781
10324
  if (body.selection === null) {
@@ -9827,7 +10370,7 @@ function uniqueAssetPath(projectDir, assetPath, taken) {
9827
10370
  const ext = extname2(assetPath);
9828
10371
  const withoutExt = assetPath.slice(0, -ext.length);
9829
10372
  let candidate = assetPath;
9830
- for (let index = 2; taken.has(candidate) || existsSync8(join13(projectDir, candidate)); index++) {
10373
+ for (let index = 2; taken.has(candidate) || existsSync11(join15(projectDir, candidate)); index++) {
9831
10374
  candidate = `${withoutExt}-${index}${ext}`;
9832
10375
  }
9833
10376
  return candidate;
@@ -9888,7 +10431,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9888
10431
  }
9889
10432
  const filePath = resolveWithinProject(project.dir, assetPath);
9890
10433
  if (!filePath) return c.json({ error: "forbidden" }, 403);
9891
- if (!existsSync8(filePath)) return c.json({ error: "media not found" }, 404);
10434
+ if (!existsSync11(filePath)) return c.json({ error: "media not found" }, 404);
9892
10435
  return c.json({ path: assetPath, metadata: await readMediaMetadata(filePath) });
9893
10436
  });
9894
10437
  api.post(
@@ -9910,7 +10453,7 @@ function registerMediaRoutes(api, adapter, options = {}) {
9910
10453
  }
9911
10454
  const inputPath = resolveWithinProject(project.dir, inputAssetPath);
9912
10455
  if (!inputPath) return c.json({ error: "forbidden" }, 403);
9913
- if (!existsSync8(inputPath)) return c.json({ error: "input media not found" }, 404);
10456
+ if (!existsSync11(inputPath)) return c.json({ error: "input media not found" }, 404);
9914
10457
  const inputIsVideo = isVideoPath(inputAssetPath);
9915
10458
  const inputIsImage = isImagePath(inputAssetPath);
9916
10459
  if (!inputIsVideo && !inputIsImage) {
@@ -9946,8 +10489,8 @@ function registerMediaRoutes(api, adapter, options = {}) {
9946
10489
  return c.json({ error: "forbidden" }, 403);
9947
10490
  }
9948
10491
  }
9949
- mkdirWithinProject(project.dir, dirname52(outputPath));
9950
- if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname52(backgroundOutputPath));
10492
+ mkdirWithinProject(project.dir, dirname6(outputPath));
10493
+ if (backgroundOutputPath) mkdirWithinProject(project.dir, dirname6(backgroundOutputPath));
9951
10494
  const jobId = makeJobId(project.id, mediaJobs);
9952
10495
  const state = adapter.startBackgroundRemoval({
9953
10496
  project,
@@ -10003,10 +10546,10 @@ function registerMediaRoutes(api, adapter, options = {}) {
10003
10546
  });
10004
10547
  }
10005
10548
  function readGlobalAssets(home = homedir2()) {
10006
- const manifestPath = join14(home, ".media", "manifest.jsonl");
10007
- if (!existsSync9(manifestPath)) return [];
10549
+ const manifestPath = join16(home, ".media", "manifest.jsonl");
10550
+ if (!existsSync12(manifestPath)) return [];
10008
10551
  const out = [];
10009
- for (const line of readFileSync11(manifestPath, "utf8").split("\n")) {
10552
+ for (const line of readFileSync13(manifestPath, "utf8").split("\n")) {
10010
10553
  if (!line.trim()) continue;
10011
10554
  try {
10012
10555
  const rec = JSON.parse(line);
@@ -10189,6 +10732,9 @@ function createStudioApi(adapter) {
10189
10732
  registerSelectionRoutes(api, adapter);
10190
10733
  registerMediaRoutes(api, adapter);
10191
10734
  registerWaveformRoutes(api, adapter);
10735
+ registerFreezeFrameRoutes(api, adapter);
10736
+ registerLoudnessRoutes(api, adapter);
10737
+ registerPeakRoutes(api, adapter);
10192
10738
  registerFontRoutes(api);
10193
10739
  registerRegistryRoutes(api, adapter);
10194
10740
  registerGlobalAssetRoutes(api);
@@ -10204,23 +10750,23 @@ function duplicateElementInHtml(source, target, newId, at) {
10204
10750
  if (duration === null || numericAttribute(element, "data-start") === null) {
10205
10751
  return { html: source, matched: false, newId: null };
10206
10752
  }
10207
- const uniqueId = nextUniqueId(document2, newId);
10753
+ const uniqueId2 = nextUniqueId(document2, newId);
10208
10754
  rippleElements(document2, element, at, duration, track);
10209
10755
  const clone = element.cloneNode(true);
10210
10756
  if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };
10211
- clone.setAttribute("id", uniqueId);
10757
+ clone.setAttribute("id", uniqueId2);
10212
10758
  dedupeClonedCompositionId(document2, clone);
10213
10759
  stripStableIds(clone);
10214
10760
  clone.setAttribute("data-start", String(at));
10215
10761
  element.parentElement.insertBefore(clone, element.nextSibling);
10216
10762
  const html = wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
10217
- return { html: ensureHfIds(html), matched: true, newId: uniqueId };
10763
+ return { html: ensureHfIds(html), matched: true, newId: uniqueId2 };
10218
10764
  }
10219
10765
  function nextUniqueId(document2, newId) {
10220
- let uniqueId = newId;
10766
+ let uniqueId2 = newId;
10221
10767
  let suffix = 2;
10222
- while (document2.getElementById(uniqueId)) uniqueId = `${newId}-${suffix++}`;
10223
- return uniqueId;
10768
+ while (document2.getElementById(uniqueId2)) uniqueId2 = `${newId}-${suffix++}`;
10769
+ return uniqueId2;
10224
10770
  }
10225
10771
  function rippleElements(document2, element, at, duration, track) {
10226
10772
  for (const candidate of Array.from(document2.querySelectorAll("[data-start][data-duration]"))) {
@@ -10245,13 +10791,13 @@ function numericAttribute(element, name) {
10245
10791
  return Number.isFinite(value) ? value : null;
10246
10792
  }
10247
10793
  function createPreviewDocumentStore(projectDir, salt) {
10248
- const dir = join15(projectDir, ".hyperframes", "preview");
10249
- const fileFor = (key) => join15(dir, `${createHash42("sha256").update(`${salt}
10794
+ const dir = join17(projectDir, ".hyperframes", "preview");
10795
+ const fileFor = (key) => join17(dir, `${createHash5("sha256").update(`${salt}
10250
10796
  ${key}`).digest("hex").slice(0, 32)}.html`);
10251
10797
  return {
10252
10798
  read(key) {
10253
10799
  try {
10254
- return readFileSync12(fileFor(key), "utf-8");
10800
+ return readFileSync14(fileFor(key), "utf-8");
10255
10801
  } catch {
10256
10802
  return null;
10257
10803
  }
@@ -10262,7 +10808,7 @@ ${key}`).digest("hex").slice(0, 32)}.html`);
10262
10808
  const file = fileFor(key);
10263
10809
  replaceFileAtomically(file, html, 420);
10264
10810
  for (const name of readdirSync5(dir)) {
10265
- if (join15(dir, name) !== file) rmSync5(join15(dir, name), { force: true });
10811
+ if (join17(dir, name) !== file) rmSync5(join17(dir, name), { force: true });
10266
10812
  }
10267
10813
  } catch {
10268
10814
  }