hyperframes 0.8.132 → 0.8.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/{add-LR3ZR2A5.js → add-RHGOHK4Z.js} +5 -5
  2. package/dist/{asset-BE2DOV72.js → asset-OTWT2XTV.js} +2 -2
  3. package/dist/{auth-RCQB2ZT7.js → auth-VER56QYQ.js} +1 -1
  4. package/dist/{autoUpdate-RJAX3DAZ.js → autoUpdate-XHFKOSVF.js} +2 -2
  5. package/dist/{backgroundChecks-H2LCUIMJ.js → backgroundChecks-GAAKX5BU.js} +3 -3
  6. package/dist/backgroundChecksWorker.js +3 -3
  7. package/dist/{beats-Y2EOP72P.js → beats-QM23K5LR.js} +4 -4
  8. package/dist/{benchmark-MZ7GY3JW.js → benchmark-GPU6HIVH.js} +5 -5
  9. package/dist/{browser-HUTAMRQE.js → browser-RUFRY3QG.js} +3 -3
  10. package/dist/{catalog-KYU55EWR.js → catalog-L6E2TFFY.js} +5 -5
  11. package/dist/{check-TCG4PXLR.js → check-TTV2DXDB.js} +12 -12
  12. package/dist/{checkBrowser-RPMO4OW5.js → checkBrowser-WWSIKC72.js} +10 -10
  13. package/dist/{chunk-UEZXMWG3.js → chunk-4XSWF3CD.js} +1 -1
  14. package/dist/{chunk-RDNNAKEP.js → chunk-6IXB7QBG.js} +2 -2
  15. package/dist/{chunk-4THIAPBL.js → chunk-6SRQ4ZQ5.js} +2 -2
  16. package/dist/{chunk-5IAKAQA7.js → chunk-AJU25DZ2.js} +2 -2
  17. package/dist/{chunk-2EGFTPWD.js → chunk-ELEWD7K4.js} +3 -3
  18. package/dist/{chunk-JTHMTOMQ.js → chunk-GVRLL3SB.js} +1 -1
  19. package/dist/{chunk-IGNAAHS3.js → chunk-ICDMEFU2.js} +1 -1
  20. package/dist/{chunk-KBJUXVOJ.js → chunk-KBUGELQE.js} +1 -1
  21. package/dist/{chunk-S3D6GE5W.js → chunk-L2PGUI5F.js} +1 -1
  22. package/dist/{chunk-H6QMKUYA.js → chunk-MUORWS65.js} +3 -3
  23. package/dist/{chunk-QNYYOUU3.js → chunk-NIDIM7IR.js} +1 -1
  24. package/dist/{chunk-FLWXXDNS.js → chunk-NWMOKW7Q.js} +2 -2
  25. package/dist/{chunk-HW6OYASV.js → chunk-PUYDBQ3R.js} +1 -1
  26. package/dist/{chunk-S7NPLFWV.js → chunk-SIU7QCPK.js} +758 -650
  27. package/dist/{chunk-EISRN4E6.js → chunk-SUY33ISN.js} +1 -1
  28. package/dist/{chunk-F3WZGU7C.js → chunk-TPBT32X6.js} +2 -2
  29. package/dist/{chunk-XH4DSOS5.js → chunk-WAGW42UH.js} +1 -1
  30. package/dist/{chunk-PPQUYWTW.js → chunk-Z46SC5ZL.js} +2 -2
  31. package/dist/{clean-W57E5YQ3.js → clean-YNXRTMJU.js} +4 -4
  32. package/dist/cli.js +45 -45
  33. package/dist/{cloud-FFTJGZ24.js → cloud-6ZOWBODT.js} +4 -4
  34. package/dist/{compare-AYCTT55F.js → compare-I2W3LIPI.js} +7 -7
  35. package/dist/{component-VFFR2O7T.js → component-F5V44RLH.js} +3 -3
  36. package/dist/{compositions-QTVN3U2A.js → compositions-KECVMHW5.js} +5 -5
  37. package/dist/{delete-ZAKYCB7I.js → delete-JGF4FREG.js} +2 -2
  38. package/dist/{doctor-AI7C3FON.js → doctor-HDIEQSHY.js} +3 -3
  39. package/dist/{events-7LALYNKZ.js → events-T5YLKXZ4.js} +3 -3
  40. package/dist/{events-FQY3HGKZ.js → events-Z5QEJRZT.js} +2 -2
  41. package/dist/{feedback-WCMDMSTK.js → feedback-OCX7QBHA.js} +4 -4
  42. package/dist/{figma-64WY462P.js → figma-O54GV6BZ.js} +3 -3
  43. package/dist/fontLocalizeCli.js +1 -1
  44. package/dist/{get-FPOMJMZB.js → get-PZRBJXZT.js} +2 -2
  45. package/dist/{grade-compare-E2CKPST4.js → grade-compare-CTWZSR4P.js} +7 -7
  46. package/dist/{gsapParser-DIYYX6JT.js → gsapParser-GHMVJQLH.js} +40 -9
  47. package/dist/{help-YRQIDDML.js → help-ZPLDYQJT.js} +1 -1
  48. package/dist/{history-PP5NDRV3.js → history-2UWBYPTV.js} +6 -6
  49. package/dist/hyperframes-player.global.js +1 -1
  50. package/dist/{info-SEX3B7JL.js → info-WEMJI32J.js} +5 -5
  51. package/dist/{init-U3AKHO5B.js → init-SCEXX6T2.js} +6 -6
  52. package/dist/{inspect-Q3K6EDEK.js → inspect-ZBYJUBMD.js} +9 -9
  53. package/dist/{keyframes-DC64JCI3.js → keyframes-FRL5BJBG.js} +6 -6
  54. package/dist/{layout-XSPPDHOO.js → layout-3W2JK43G.js} +9 -9
  55. package/dist/{lint-7DQZE7L3.js → lint-HIUPGNCY.js} +6 -6
  56. package/dist/{list-4K5MVDQX.js → list-SMD55UPW.js} +2 -2
  57. package/dist/{login-NTX4OWXL.js → login-S3ONWYCS.js} +4 -4
  58. package/dist/{media-treatment-5FYAOPNL.js → media-treatment-SFW5MCC6.js} +5 -5
  59. package/dist/{motionShot-S4MIGMT5.js → motionShot-AKIMKJYD.js} +1 -1
  60. package/dist/{normalize-audio-CSFTXLXD.js → normalize-audio-U5UXJWRR.js} +4 -4
  61. package/dist/{open-2ZD6BHZG.js → open-3E4ZL3XD.js} +4 -4
  62. package/dist/{play-CTMUXX6E.js → play-33PZ4CD6.js} +7 -7
  63. package/dist/{present-WQVHV5CR.js → present-ODOKWMTB.js} +7 -7
  64. package/dist/{preview-JOJXTH75.js → preview-V3CJ75JK.js} +5 -5
  65. package/dist/{render-LNO62LEL.js → render-B7L7XGI7.js} +5 -5
  66. package/dist/{render-TTQI7TGN.js → render-ZUFWRU2R.js} +5 -5
  67. package/dist/{skills-MJ2J4GEM.js → skills-XZSQQ6O3.js} +5 -5
  68. package/dist/{skillsUpdateCheck-TKTYQN5R.js → skillsUpdateCheck-I4OHSWVJ.js} +3 -3
  69. package/dist/{snapshot-KIVDUUMN.js → snapshot-RCEGREHZ.js} +7 -7
  70. package/dist/{staticProjectServer-7EU666GS.js → staticProjectServer-RIFIORHW.js} +3 -3
  71. package/dist/studio/assets/{hyperframes-player-Cp8e_Zst.js → hyperframes-player-Bro1gj0Z.js} +1 -1
  72. package/dist/studio/assets/{index-CASEBNy2.js → index-BwNDTmAD.js} +1 -1
  73. package/dist/studio/assets/{index-DMTt9nTf.js → index-DnVGIbEC.js} +1 -1
  74. package/dist/studio/assets/{index-oE1pOdAy.js → index-wnXyQjW-.js} +143 -143
  75. package/dist/studio/index.html +1 -1
  76. package/dist/studio/index.js +717 -702
  77. package/dist/studio/index.js.map +1 -1
  78. package/dist/{studio-api-PNQZRPX5.js → studio-api-D2KGZOYL.js} +1 -1
  79. package/dist/{studioServer-UWRBPIOJ.js → studioServer-PXNJXHMV.js} +5 -5
  80. package/dist/{telemetry-QMYM4WME.js → telemetry-SGIIMXZ3.js} +3 -3
  81. package/dist/{timeline-77Z3BBQM.js → timeline-QCU4QE6S.js} +6 -6
  82. package/dist/{tokens-OBGTYE4Z.js → tokens-LXW56KNH.js} +2 -2
  83. package/dist/{transcribe-7NVFHCGA.js → transcribe-BTZH3ZVO.js} +3 -3
  84. package/dist/{updateCheck-UDWIJ4ZQ.js → updateCheck-7XAIYPMV.js} +2 -2
  85. package/dist/{upgrade-CT3CPO3E.js → upgrade-BMJ32AYR.js} +2 -2
  86. package/dist/{validate-65QDZJWZ.js → validate-DKXIOSJC.js} +6 -6
  87. package/package.json +1 -1
@@ -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;
@@ -5021,15 +5048,20 @@ function removeStudioHoldSets(script, parsed) {
5021
5048
  for (const hold of staleHolds) removeCallFromMagicString(ms, hold.call, script);
5022
5049
  return ms.toString();
5023
5050
  }
5024
- function syncPositionHoldsBeforeKeyframes(script) {
5051
+ function syncPositionHoldsBeforeKeyframes(script, previous) {
5025
5052
  const parsed = parseGsapScriptAcornForWrite(script);
5026
5053
  if (!parsed) return script;
5054
+ const tweensOf = (text2) => parseGsapScriptAcornForWrite(text2)?.located.map((entry) => entry.animation) ?? null;
5055
+ const scope = holdScope(
5056
+ parsed.located.map((entry) => entry.animation),
5057
+ previous === void 0 ? null : tweensOf(previous)
5058
+ );
5027
5059
  let result = removeStudioHoldSets(script, parsed);
5028
5060
  const current = parseGsapScriptAcornForWrite(result);
5029
5061
  if (!current) return result;
5030
5062
  const animations = current.located.map((entry) => entry.animation);
5031
5063
  for (const animation of animations) {
5032
- const position = positionHoldForAnimation(animation, animations);
5064
+ const position = keyframeHoldForAnimation(animation, animations, scope);
5033
5065
  if (!position) continue;
5034
5066
  result = insertInheritedStateSetInScript(result, animation.targetSelector, 0, {
5035
5067
  ...position,
@@ -5038,6 +5070,62 @@ function syncPositionHoldsBeforeKeyframes(script) {
5038
5070
  }
5039
5071
  return result;
5040
5072
  }
5073
+ var roundPct = (pct) => Math.round(pct * 1e3) / 1e3;
5074
+ var LINEAR_RUN = /* @__PURE__ */ new Set(["none", "linear"]);
5075
+ var PLACED_BY_TIMELINE = /\.(?:add|addLabel|addPause|call|repeat|yoyo|duration|timeScale)\s*\(|\b(?:repeat|yoyo)\s*:/;
5076
+ function linearPercentageKeys(animation) {
5077
+ const data = animation.keyframes;
5078
+ if (data?.format !== "percentage" || data.fromMotionPath || animation.arcPath) return null;
5079
+ if (animation.durationUnresolved || !LINEAR_RUN.has(animation.ease || "none")) return null;
5080
+ return data.keyframes;
5081
+ }
5082
+ var loops = (animation) => Boolean(animation.extras?.repeat || animation.extras?.yoyo);
5083
+ function trimmableKeyframes(animation) {
5084
+ const keyframes = loops(animation) ? null : linearPercentageKeys(animation);
5085
+ const { duration, position } = animation;
5086
+ if (!keyframes || typeof position !== "number" || typeof duration !== "number") return null;
5087
+ return duration > 0 ? { keyframes, duration, position } : null;
5088
+ }
5089
+ function trailingSpanTrim(animation) {
5090
+ const tween = trimmableKeyframes(animation);
5091
+ if (!tween) return null;
5092
+ const last = Math.max(...tween.keyframes.map((keyframe) => keyframe.percentage));
5093
+ if (tween.keyframes.length < 2 || !(last > 0) || last >= 99.999) return null;
5094
+ return {
5095
+ position: tween.position,
5096
+ duration: Math.round(tween.duration * last * 10) / 1e3,
5097
+ pctRemap: tween.keyframes.map(({ percentage: from }) => ({
5098
+ from,
5099
+ to: roundPct(from / last * 100)
5100
+ }))
5101
+ };
5102
+ }
5103
+ function trimTrailingKeyframeSpans(previous, script) {
5104
+ if (script === previous) return script;
5105
+ const located = parseGsapScriptAcornForWrite(script)?.located ?? [];
5106
+ const placedAfter = ({ animation: a }) => !a.global && (a.implicitPosition || typeof a.position !== "number");
5107
+ if (PLACED_BY_TIMELINE.test(script) || located.some(placedAfter)) return script;
5108
+ const trims = located.flatMap((entry) => {
5109
+ const trim = trailingSpanTrim(entry.animation);
5110
+ return trim ? [{ entry, trim }] : [];
5111
+ });
5112
+ if (!trims.length) return script;
5113
+ const source = (text2, { call }) => `${call.method}${text2.slice(call.node.arguments[0]?.start, call.node.end)}`;
5114
+ const previousTweens = parseGsapScriptAcornForWrite(previous)?.located ?? [];
5115
+ const untouched = new Set(previousTweens.map((entry) => source(previous, entry)));
5116
+ let result = script;
5117
+ for (const { entry, trim } of trims) {
5118
+ if (untouched.has(source(script, entry))) continue;
5119
+ result = resizeKeyframedTweenInScript(
5120
+ result,
5121
+ entry.id,
5122
+ trim.position,
5123
+ trim.duration,
5124
+ trim.pctRemap
5125
+ );
5126
+ }
5127
+ return result;
5128
+ }
5041
5129
  function computeForwardBaselines(matching, splitTime) {
5042
5130
  const before = [];
5043
5131
  const acc = {};
@@ -5330,31 +5418,26 @@ function unrollDynamicAnimations(script, animationId, elements) {
5330
5418
  }
5331
5419
 
5332
5420
  // ../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";
5421
+ import { createReadStream as createReadStream2, existsSync as existsSync5, readFileSync as readFileSync62, statSync as statSync42 } from "fs";
5337
5422
  import { Readable } from "stream";
5338
5423
  import { join as join8, resolve as resolve42 } from "path";
5339
- import { createHash as createHash4 } from "crypto";
5424
+ import { createHash as createHash23 } from "crypto";
5340
5425
 
5341
5426
  // ../core/dist/studioPreviewMark.js
5342
5427
  var STUDIO_PREVIEW_MARK_META = "hyperframes-studio-preview";
5343
5428
  var STUDIO_PREVIEW_LAZY_ATTR = "data-hf-preview-lazy";
5344
5429
 
5345
5430
  // ../studio-server/dist/index.js
5346
- import { existsSync as existsSync42, readFileSync as readFileSync42 } from "fs";
5347
- import { join as join62 } from "path";
5348
5431
  import { readlinkSync as readlinkSync2 } from "fs";
5349
5432
  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";
5433
+ import { readFileSync as readFileSync72 } from "fs";
5351
5434
  import { join as join9 } from "path";
5352
5435
  import { streamSSE } from "hono/streaming";
5353
- import { existsSync as existsSync6, readFileSync as readFileSync72, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync5 } from "fs";
5436
+ import { existsSync as existsSync6, readFileSync as readFileSync8, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync5 } from "fs";
5354
5437
  import { join as join10 } from "path";
5355
5438
  import { HTTPException } from "hono/http-exception";
5356
5439
  import { open, stat as stat2 } from "fs/promises";
5357
- import { createHash as createHash23 } from "crypto";
5440
+ import { createHash as createHash32 } from "crypto";
5358
5441
  import {
5359
5442
  closeSync as closeSync22,
5360
5443
  fstatSync as fstatSync22,
@@ -5369,8 +5452,6 @@ import {
5369
5452
  } from "fs";
5370
5453
  import { join as join11 } from "path";
5371
5454
  import { createHash as createHash42, randomUUID as randomUUID22 } from "crypto";
5372
- import { readFileSync as readFileSync8 } from "fs";
5373
- import { createHash as createHash32 } from "crypto";
5374
5455
  import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
5375
5456
  import { join as join12, resolve as resolve52 } from "path";
5376
5457
  import { execFile } from "child_process";
@@ -5477,339 +5558,175 @@ function getMimeType(path) {
5477
5558
  function isAudioFile(name) {
5478
5559
  return (getMimeType(name) ?? "").startsWith("audio/");
5479
5560
  }
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);
5561
+ function rewriteRelativePaths(root, compPath, projectDir) {
5562
+ const assetExists = (path) => existsSync3(join23(projectDir, path));
5563
+ rewriteAssetPaths(
5564
+ root.querySelectorAll("[src], [href]"),
5565
+ compPath,
5566
+ (el, attr) => el.getAttribute(attr),
5567
+ (el, attr, value) => el.setAttribute(attr, value),
5568
+ assetExists
5569
+ );
5570
+ rewriteInlineStyleAssetUrls(
5571
+ root.querySelectorAll("[style]"),
5572
+ compPath,
5573
+ (el) => el.getAttribute("style"),
5574
+ (el, value) => el.setAttribute("style", value),
5575
+ assetExists
5576
+ );
5577
+ for (const styleEl of root.querySelectorAll("style")) {
5578
+ styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath, assetExists);
5501
5579
  }
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
5580
  }
5554
- function isWaveformCacheDirectory(cacheDir) {
5555
- return lstatSync3(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
5581
+ function escapeLeadingDigitIdent(id) {
5582
+ return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`;
5556
5583
  }
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");
5584
+ var REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g;
5585
+ function fixDigitLeadingIdSelectors(root) {
5586
+ const digitIds = /* @__PURE__ */ new Set();
5587
+ for (const el of root.querySelectorAll("[id]")) {
5588
+ const id = el.getAttribute("id");
5589
+ if (id && /^\d/.test(id)) digitIds.add(id);
5566
5590
  }
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 {
5591
+ if (digitIds.size === 0) return;
5592
+ for (const styleEl of root.querySelectorAll("style")) {
5593
+ let css = styleEl.textContent || "";
5594
+ for (const id of digitIds) {
5595
+ const pattern = new RegExp(`#${id.replace(REGEXP_SPECIALS, "\\$&")}(?![\\w-])`, "g");
5596
+ css = css.replace(pattern, `#${escapeLeadingDigitIdent(id)}`);
5576
5597
  }
5598
+ styleEl.textContent = css;
5577
5599
  }
5578
5600
  }
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" };
5601
+ function extractFullDocumentParts(rawHtml, compPath, projectDir) {
5602
+ const { document: doc } = parseHTML(rawHtml);
5603
+ const rewriteTargets = [doc.head, doc.body].filter(Boolean);
5604
+ for (const target of rewriteTargets) {
5605
+ rewriteRelativePaths(target, compPath, projectDir);
5607
5606
  }
5608
- return checkStreams(String(result.stdout || "{}"), isVideo, isAudio);
5607
+ fixDigitLeadingIdSelectors(doc);
5608
+ const headContent = doc.head?.innerHTML ?? "";
5609
+ const bodyContent = doc.body?.innerHTML ?? "";
5610
+ const htmlEl = doc.documentElement;
5611
+ const htmlAttrs = extractElementAttrs(htmlEl);
5612
+ const bodyAttrs = doc.body ? extractElementAttrs(doc.body) : "";
5613
+ return { headContent, bodyContent, htmlAttrs, bodyAttrs };
5609
5614
  }
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" };
5615
+ function extractTemplateInnerHtml(rawComp) {
5616
+ const { document: doc } = parseHTML(rawComp);
5617
+ const template = doc.querySelector("template");
5618
+ return template ? template.innerHTML : null;
5619
+ }
5620
+ function escapeAttrValue(value) {
5621
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
5622
+ }
5623
+ function extractElementAttrs(el) {
5624
+ const parts = [];
5625
+ for (let i = 0; i < el.attributes.length; i++) {
5626
+ const attr = el.attributes[i];
5627
+ if (attr.value === "") {
5628
+ parts.push(attr.name);
5629
+ } else {
5630
+ parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
5621
5631
  }
5622
- return { ok: true };
5623
- } catch {
5624
- return { ok: false, reason: "ffprobe returned unreadable media metadata" };
5625
5632
  }
5633
+ return parts.join(" ");
5626
5634
  }
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 });
5635
+ var NON_RENDERED_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT"]);
5636
+ function promoteTemplateCompositionId(rawComp, body) {
5637
+ let templateCompositionId;
5638
+ for (const tag of rawComp.matchAll(/<template\b[^>]*/gi)) {
5639
+ const id = /\bdata-composition-id\s*=\s*["']([^"']+)["']/i.exec(tag[0] ?? "")?.[1];
5640
+ if (id) {
5641
+ templateCompositionId = id;
5642
+ break;
5643
+ }
5635
5644
  }
5645
+ if (!templateCompositionId) return;
5646
+ if (body.querySelector("[data-composition-id]")) return;
5647
+ const root = Array.from(body.children).find((el) => !NON_RENDERED_TAGS.has(el.tagName));
5648
+ root?.setAttribute("data-composition-id", templateCompositionId);
5636
5649
  }
5637
- var DEFAULT_KEEP_PER_FILE = 10;
5638
- function backupKeyForPath(path) {
5639
- return Buffer2.from(path, "utf-8").toString("base64url");
5640
- }
5641
- function timestampPrefix() {
5642
- return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5650
+ function tagRootCompositionFile(bodyHtml, compPath) {
5651
+ const match = bodyHtml.match(/<[a-zA-Z][^>]*\bdata-composition-id=/);
5652
+ if (match?.index == null) return bodyHtml;
5653
+ const tagEnd = bodyHtml.indexOf(">", match.index);
5654
+ if (tagEnd === -1) return bodyHtml;
5655
+ if (bodyHtml.slice(match.index, tagEnd).includes("data-composition-file")) return bodyHtml;
5656
+ return bodyHtml.slice(0, tagEnd) + ` data-composition-file="${compPath}"` + bodyHtml.slice(tagEnd);
5643
5657
  }
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("/");
5658
+ function rootHeadContent(rootHtml) {
5659
+ return rootHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
5649
5660
  }
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;
5661
+ function buildSubCompositionHtml(projectDir, compPath, runtimeUrl, baseHref, rawOverride) {
5662
+ const compFile = join23(projectDir, compPath);
5663
+ if (!existsSync3(compFile)) return null;
5664
+ const rawComp = rawOverride ?? readFileSync7(compFile, "utf-8");
5665
+ let compHeadContent = "";
5666
+ let rewrittenContent;
5667
+ let htmlAttrs = "";
5668
+ let bodyAttrs = "";
5669
+ const templateInner = extractTemplateInnerHtml(rawComp);
5670
+ if (templateInner != null) {
5671
+ const { document: contentDoc } = parseHTML(
5672
+ `<!DOCTYPE html><html><head></head><body>${templateInner}</body></html>`
5673
+ );
5674
+ rewriteRelativePaths(contentDoc, compPath, projectDir);
5675
+ fixDigitLeadingIdSelectors(contentDoc);
5676
+ promoteTemplateCompositionId(rawComp, contentDoc.body);
5677
+ rewrittenContent = contentDoc.body.innerHTML || templateInner;
5678
+ } else if (isFullHtmlDocument(rawComp)) {
5679
+ const parts = extractFullDocumentParts(rawComp, compPath, projectDir);
5680
+ compHeadContent = parts.headContent;
5681
+ rewrittenContent = parts.bodyContent;
5682
+ htmlAttrs = parts.htmlAttrs;
5683
+ bodyAttrs = parts.bodyAttrs;
5684
+ } else {
5685
+ const { document: contentDoc } = parseHTML(
5686
+ `<!DOCTYPE html><html><head></head><body>${rawComp}</body></html>`
5687
+ );
5688
+ rewriteRelativePaths(contentDoc, compPath, projectDir);
5689
+ fixDigitLeadingIdSelectors(contentDoc);
5690
+ rewrittenContent = contentDoc.body.innerHTML || rawComp;
5740
5691
  }
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
- };
5692
+ rewrittenContent = stripEmbeddedRuntimeScripts(rewrittenContent);
5693
+ const { document: scriptsDoc } = parseHTML(
5694
+ `<!DOCTYPE html><html><head></head><body>${rewrittenContent}</body></html>`
5695
+ );
5696
+ deferScriptsUntilFonts(scriptsDoc);
5697
+ rewrittenContent = scriptsDoc.body.innerHTML;
5698
+ rewrittenContent = tagRootCompositionFile(rewrittenContent, compPath);
5699
+ const indexPath = join23(projectDir, "index.html");
5700
+ let headContent = "";
5701
+ if (existsSync3(indexPath)) headContent = rootHeadContent(readFileSync7(indexPath, "utf-8"));
5702
+ if (baseHref && !hasBaseElement(headContent)) {
5703
+ headContent = `<base href="${baseHref}">
5704
+ ${headContent}`;
5754
5705
  }
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;
5706
+ if (compHeadContent) headContent += `
5707
+ ${compHeadContent}`;
5708
+ headContent = stripEmbeddedRuntimeScripts(headContent);
5709
+ headContent += `
5710
+ <script ${RUNTIME_BOOTSTRAP_ATTR}="1" src="${runtimeUrl}"></script>`;
5711
+ if (!headContent.includes("gsap")) {
5712
+ headContent += `
5713
+ <script src="${gsapCdnDist()}gsap.min.js"></script>`;
5784
5714
  }
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);
5715
+ const htmlOpen = htmlAttrs ? `<html ${htmlAttrs}>` : "<html>";
5716
+ const bodyOpen = bodyAttrs ? `<body ${bodyAttrs}>` : "<body>";
5717
+ return `<!DOCTYPE html>
5718
+ ${htmlOpen}
5719
+ <head>
5720
+ ${headContent}
5721
+ </head>
5722
+ ${bodyOpen}
5723
+ <script>window.__timelines=window.__timelines||{};</script>
5724
+ ${rewrittenContent}
5725
+ </body>
5726
+ </html>`;
5795
5727
  }
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}`;
5728
+ function hasBaseElement(html) {
5729
+ return parseHTML(html).document.querySelector("base") !== null;
5813
5730
  }
5814
5731
  var CompositionInsertionError = class extends Error {
5815
5732
  constructor(message, status) {
@@ -5875,7 +5792,7 @@ function canonicalDependency(projectDir, ownerAbs, sourcePath) {
5875
5792
  validateSourcePath(sourcePath);
5876
5793
  return canonicalProjectPath(
5877
5794
  projectDir,
5878
- resolveWithinProject(projectDir, relative22(projectDir, resolve5(dirname23(ownerAbs), sourcePath)))
5795
+ resolveWithinProject(projectDir, relative3(projectDir, resolve5(dirname5(ownerAbs), sourcePath)))
5879
5796
  );
5880
5797
  }
5881
5798
  function validateDependencyGraph(projectDir, targetAbs, sourceAbs) {
@@ -5948,7 +5865,7 @@ function uniqueHostId(root, base) {
5948
5865
  return `${base}_${suffix}`;
5949
5866
  }
5950
5867
  function relativeSourcePath(targetAbs, sourceAbs) {
5951
- return relative22(dirname23(targetAbs), sourceAbs).split(sep3).join("/");
5868
+ return relative3(dirname5(targetAbs), sourceAbs).split(sep3).join("/");
5952
5869
  }
5953
5870
  function insertCompositionIntoSource(input) {
5954
5871
  const targetAbs = canonicalProjectFile(input.projectDir, input.targetPath);
@@ -6001,7 +5918,443 @@ function insertCompositionIntoSource(input) {
6001
5918
  const name = root.hasAttribute("data-duration") ? "data-duration" : "data-composition-duration";
6002
5919
  root.setAttribute(name, String(Math.round((input.start + duration) * 100) / 100));
6003
5920
  }
6004
- return { html: document2.toString(), hostId, track, duration };
5921
+ return { html: document2.toString(), hostId, track, duration };
5922
+ }
5923
+ var ROOT_COMPOSITION = "index.html";
5924
+ var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
5925
+ function projectReader(projectDir) {
5926
+ const sources = /* @__PURE__ */ new Map();
5927
+ return (compPath) => {
5928
+ if (!sources.has(compPath)) {
5929
+ const file = resolveWithinProject(projectDir, compPath);
5930
+ let source = null;
5931
+ try {
5932
+ source = file ? readFileSync23(file, "utf-8") : null;
5933
+ } catch {
5934
+ }
5935
+ sources.set(compPath, source);
5936
+ }
5937
+ return sources.get(compPath) ?? null;
5938
+ };
5939
+ }
5940
+ function normalizeSource(src) {
5941
+ if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
5942
+ const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
5943
+ return path || null;
5944
+ }
5945
+ function closureOf(read, compPath) {
5946
+ const closure = /* @__PURE__ */ new Set();
5947
+ const visit = (path) => {
5948
+ if (closure.has(path)) return;
5949
+ closure.add(path);
5950
+ for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
5951
+ const mounted = normalizeSource(match[2] ?? "");
5952
+ if (mounted) visit(mounted);
5953
+ }
5954
+ };
5955
+ visit(compPath);
5956
+ return closure;
5957
+ }
5958
+ function lengthIsTimeline(projectDir, compPath) {
5959
+ const read = projectReader(projectDir);
5960
+ const documents = /* @__PURE__ */ new Map();
5961
+ const elements = (path, selector) => {
5962
+ let document2 = documents.get(path);
5963
+ if (!document2) documents.set(path, document2 = parseHTML(read(path) ?? "").document);
5964
+ return descendants(document2, selector);
5965
+ };
5966
+ const comp = normalizeSource(compPath) ?? compPath;
5967
+ const unsized = (path) => {
5968
+ const [root] = elements(path, "[data-composition-id]");
5969
+ return Boolean(root && !root.hasAttribute("data-duration"));
5970
+ };
5971
+ if (unsized(ROOT_COMPOSITION)) return true;
5972
+ const hosts = [...closureOf(read, ROOT_COMPOSITION)].flatMap(
5973
+ (path) => elements(path, "[data-composition-src]").filter(
5974
+ (host) => normalizeSource(host.getAttribute("data-composition-src") ?? "") === comp
5975
+ )
5976
+ );
5977
+ if (comp === ROOT_COMPOSITION || hosts.length === 0) return unsized(comp);
5978
+ return hosts.some(
5979
+ (host) => !host.hasAttribute("data-duration") && !host.hasAttribute("data-end")
5980
+ );
5981
+ }
5982
+ var inputSignatures = /* @__PURE__ */ new Map();
5983
+ var rootMemos = /* @__PURE__ */ new Map();
5984
+ var sha1 = (text2) => createHash4("sha1").update(text2).digest("hex");
5985
+ function rootHashes(read) {
5986
+ const source = read(ROOT_COMPOSITION) ?? "";
5987
+ return { root: sha1(source), head: sha1(rootHeadContent(source)) };
5988
+ }
5989
+ function compositionInputSignature(projectDir, compPath, projectSignature) {
5990
+ const key = `${projectDir}\0${compPath}`;
5991
+ const known = inputSignatures.get(key);
5992
+ if (known?.projectSignature === projectSignature) return known.inputSignature;
5993
+ const read = projectReader(projectDir);
5994
+ const comp = normalizeSource(compPath) ?? compPath;
5995
+ const inputs = closureOf(read, comp).add(ROOT_COMPOSITION);
5996
+ const excluded = new Set(
5997
+ [...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path))
5998
+ );
5999
+ let inputSignature;
6000
+ if (comp === ROOT_COMPOSITION) {
6001
+ inputSignature = createProjectSignature(projectDir, excluded);
6002
+ } else {
6003
+ const hashes = rootHashes(read);
6004
+ if (!rootMemos.has(projectDir)) rootMemos.set(projectDir, { ...hashes, affected: null });
6005
+ inputSignature = `${createProjectSignature(projectDir, excluded.add(ROOT_COMPOSITION))}:${hashes.head}`;
6006
+ }
6007
+ inputSignatures.set(key, { projectSignature, inputSignature });
6008
+ return inputSignature;
6009
+ }
6010
+ function compositionsAffectedBy(projectDir, changedPath) {
6011
+ const changed = changedPath.replace(/\\/g, "/");
6012
+ const read = projectReader(projectDir);
6013
+ if (changed === ROOT_COMPOSITION) {
6014
+ const now = rootHashes(read);
6015
+ const memo = rootMemos.get(projectDir);
6016
+ if (memo?.root === now.root) return memo.affected;
6017
+ const affected = memo?.head === now.head ? [ROOT_COMPOSITION] : null;
6018
+ rootMemos.set(projectDir, { ...now, affected });
6019
+ return affected;
6020
+ }
6021
+ const mounted = closureOf(read, ROOT_COMPOSITION);
6022
+ if (!mounted.has(changed)) return null;
6023
+ return [...mounted].filter((path) => closureOf(read, path).has(changed));
6024
+ }
6025
+ var SAMPLE_RATE = 4e3;
6026
+ var PEAK_COUNT = 4e3;
6027
+ var WAVEFORM_CACHE_VERSION = "v2";
6028
+ function buildWaveformCacheKey(assetPath, fingerprint) {
6029
+ const name = assetPath.replace(/[/\\]/g, "_");
6030
+ if (!fingerprint) return `${WAVEFORM_CACHE_VERSION}_${name}.json`;
6031
+ const stamp = `${fingerprint.size}-${Math.round(fingerprint.mtimeMs)}`;
6032
+ return `${WAVEFORM_CACHE_VERSION}_${name}_${stamp}.json`;
6033
+ }
6034
+ function computePeaks(floats, count) {
6035
+ const step = floats.length / count;
6036
+ const peaks = [];
6037
+ for (let i = 0; i < count; i++) {
6038
+ const start = Math.floor(i * step);
6039
+ const end = Math.min(Math.floor((i + 1) * step), floats.length);
6040
+ let max = 0;
6041
+ for (let j = start; j < end; j++) {
6042
+ const abs = Math.abs(floats[j] ?? 0);
6043
+ if (abs > max) max = abs;
6044
+ }
6045
+ peaks.push(max);
6046
+ }
6047
+ const maxPeak = Math.max(...peaks, 1e-3);
6048
+ return peaks.map((p) => p / maxPeak);
6049
+ }
6050
+ function decodeAudioPeaks(audioPath) {
6051
+ return new Promise((resolvePromise, reject) => {
6052
+ const proc = spawn(
6053
+ findFfBinary("ffmpeg") ?? "ffmpeg",
6054
+ [
6055
+ "-i",
6056
+ audioPath,
6057
+ "-af",
6058
+ "atrim=start_sample=1152",
6059
+ "-f",
6060
+ "f32le",
6061
+ "-ac",
6062
+ "1",
6063
+ "-ar",
6064
+ String(SAMPLE_RATE),
6065
+ "-vn",
6066
+ "pipe:1"
6067
+ ],
6068
+ { stdio: ["ignore", "pipe", "ignore"], windowsHide: true }
6069
+ );
6070
+ const chunks = [];
6071
+ proc.stdout?.on("data", (chunk) => chunks.push(chunk));
6072
+ proc.on("close", (code) => {
6073
+ if (code !== 0 && chunks.length === 0) {
6074
+ reject(new Error(`ffmpeg exited with code ${code}`));
6075
+ return;
6076
+ }
6077
+ const buf = Buffer.concat(chunks);
6078
+ const numSamples = Math.floor(buf.length / 4);
6079
+ if (numSamples === 0) {
6080
+ reject(new Error("ffmpeg produced no audio samples"));
6081
+ return;
6082
+ }
6083
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + numSamples * 4);
6084
+ resolvePromise(computePeaks(new Float32Array(ab), PEAK_COUNT));
6085
+ });
6086
+ proc.on("error", reject);
6087
+ });
6088
+ }
6089
+ async function generateWaveformCache(projectDir, assetPath) {
6090
+ const audioPath = join32(projectDir, assetPath);
6091
+ if (!existsSync32(audioPath)) return;
6092
+ const stats = statSync4(audioPath);
6093
+ const cacheDir = join32(projectDir, ".waveform-cache");
6094
+ const cachePath = join32(cacheDir, buildWaveformCacheKey(assetPath, stats));
6095
+ if (isWaveformCacheDirectory(cacheDir) && existsSync32(cachePath)) return;
6096
+ const peaks = await decodeAudioPeaks(audioPath);
6097
+ writeWaveformCache(cachePath, peaks);
6098
+ }
6099
+ function isWaveformCacheDirectory(cacheDir) {
6100
+ return lstatSync3(cacheDir, { throwIfNoEntry: false })?.isDirectory() ?? false;
6101
+ }
6102
+ function writeWaveformCache(cachePath, peaks) {
6103
+ const cacheDir = dirname23(cachePath);
6104
+ try {
6105
+ mkdirSync5(cacheDir, { mode: 448 });
6106
+ } catch (error) {
6107
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
6108
+ }
6109
+ if (!isWaveformCacheDirectory(cacheDir)) {
6110
+ throw new Error("Waveform cache must be a directory, not a symlink");
6111
+ }
6112
+ const stagingDir = mkdtempSync(join32(cacheDir, ".waveform-"));
6113
+ try {
6114
+ const stagingPath = join32(stagingDir, "peaks.json");
6115
+ writeFileSync4(stagingPath, JSON.stringify(peaks), { flag: "wx" });
6116
+ renameSync3(stagingPath, cachePath);
6117
+ } finally {
6118
+ try {
6119
+ rmSync4(stagingDir, { recursive: true, force: true });
6120
+ } catch {
6121
+ }
6122
+ }
6123
+ }
6124
+ var VIDEO_EXT = /\.(mp4|webm|mov|mkv|avi|m4v|mxf|mts|m2ts|ts)$/i;
6125
+ var AUDIO_EXT = /\.(mp3|wav|ogg|m4a|aac|flac)$/i;
6126
+ var defaultRunner = spawnSync;
6127
+ var FFPROBE_MISSING = "not checked: ffprobe was not found. Install FFmpeg or set HYPERFRAMES_FFPROBE_PATH.";
6128
+ function validateUploadedMedia(filePath, runner = defaultRunner) {
6129
+ const isVideo = VIDEO_EXT.test(filePath);
6130
+ const isAudio = AUDIO_EXT.test(filePath);
6131
+ if (!isVideo && !isAudio) {
6132
+ return { ok: true };
6133
+ }
6134
+ const ffprobe = findFfBinary("ffprobe") ?? (runner === defaultRunner ? void 0 : "ffprobe");
6135
+ if (!ffprobe) return { ok: true, unchecked: FFPROBE_MISSING };
6136
+ const result = runner(
6137
+ ffprobe,
6138
+ ["-v", "error", "-show_entries", "stream=codec_type", "-of", "json", "--", filePath],
6139
+ { windowsHide: true }
6140
+ );
6141
+ if (result.error && process.env[FFPROBE_PATH_ENV]?.trim()) {
6142
+ return {
6143
+ ok: false,
6144
+ reason: `${FFPROBE_PATH_ENV} names "${ffprobe}", which cannot run: fix it or unset it`
6145
+ };
6146
+ }
6147
+ if (result.error?.code === "ENOENT") {
6148
+ return { ok: true, unchecked: FFPROBE_MISSING };
6149
+ }
6150
+ if (result.status !== 0) {
6151
+ return { ok: false, reason: "ffprobe failed to read the media file" };
6152
+ }
6153
+ return checkStreams(String(result.stdout || "{}"), isVideo, isAudio);
6154
+ }
6155
+ function checkStreams(stdout, isVideo, isAudio) {
6156
+ try {
6157
+ const parsed = JSON.parse(stdout);
6158
+ const streams = parsed.streams ?? [];
6159
+ const hasVideo = streams.some((stream) => stream.codec_type === "video");
6160
+ const hasAudio = streams.some((stream) => stream.codec_type === "audio");
6161
+ if (isVideo && !hasVideo) {
6162
+ return { ok: false, reason: "no supported video stream found" };
6163
+ }
6164
+ if (isAudio && !hasAudio) {
6165
+ return { ok: false, reason: "no supported audio stream found" };
6166
+ }
6167
+ return { ok: true };
6168
+ } catch {
6169
+ return { ok: false, reason: "ffprobe returned unreadable media metadata" };
6170
+ }
6171
+ }
6172
+ function validateUploadedMediaBuffer(fileName, buffer, runner = defaultRunner) {
6173
+ const tempDir = mkdtempSync2(join42(tmpdir2(), "hyperframes-upload-"));
6174
+ const tempPath = join42(tempDir, basename2(fileName));
6175
+ try {
6176
+ writeFileSync23(tempPath, buffer);
6177
+ return validateUploadedMedia(tempPath, runner);
6178
+ } finally {
6179
+ rmSync22(tempDir, { recursive: true, force: true });
6180
+ }
6181
+ }
6182
+ var DEFAULT_KEEP_PER_FILE = 10;
6183
+ function backupKeyForPath(path) {
6184
+ return Buffer2.from(path, "utf-8").toString("base64url");
6185
+ }
6186
+ function timestampPrefix() {
6187
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6188
+ }
6189
+ function backupPathForResponse(projectDir, backupPath) {
6190
+ if (!backupPath) return null;
6191
+ const rel = relative22(projectDir, backupPath);
6192
+ if (!rel || rel.startsWith("..")) return null;
6193
+ return rel.split("\\").join("/");
6194
+ }
6195
+ function snapshotBeforeWrite(projectDir, absPath, options = {}) {
6196
+ if (!isSafePath(projectDir, absPath)) return { backupPath: null };
6197
+ try {
6198
+ const content = readFileSync32(absPath);
6199
+ const relativePath = relative22(projectDir, absPath);
6200
+ const backupDir = join52(projectDir, ".hyperframes", "backup");
6201
+ mkdirWithinProject(projectDir, backupDir);
6202
+ const backupKey = backupKeyForPath(relativePath);
6203
+ const backupPath = nextBackupPath(backupDir, backupKey);
6204
+ writeFileSync32(backupPath, content);
6205
+ pruneBackups(backupDir, backupKey, options.keepPerFile ?? DEFAULT_KEEP_PER_FILE);
6206
+ return { backupPath };
6207
+ } catch (error) {
6208
+ if (isProjectRootMissing(error)) throw error;
6209
+ if (error && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR")) {
6210
+ return { backupPath: null };
6211
+ }
6212
+ return { backupPath: null, error: error instanceof Error ? error.message : String(error) };
6213
+ }
6214
+ }
6215
+ function nextBackupPath(backupDir, backupKey) {
6216
+ const base = `${timestampPrefix()}-${backupKey}`;
6217
+ let candidate = join52(backupDir, base);
6218
+ let counter = 2;
6219
+ while (true) {
6220
+ try {
6221
+ readFileSync32(candidate);
6222
+ } catch (error) {
6223
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
6224
+ return candidate;
6225
+ }
6226
+ throw error;
6227
+ }
6228
+ candidate = join52(backupDir, `${base}-${counter}`);
6229
+ counter += 1;
6230
+ }
6231
+ }
6232
+ function pruneBackups(backupDir, backupKey, keepPerFile) {
6233
+ const keep = Math.max(1, Math.floor(keepPerFile));
6234
+ const suffix = `-${backupKey}`;
6235
+ const numberedSuffix = new RegExp(`-${backupKey}-\\d+$`);
6236
+ const matches = readdirSync4(backupDir).filter((name) => name.endsWith(suffix) || numberedSuffix.test(name)).map((name) => join52(backupDir, name)).sort((a, b) => {
6237
+ return b.localeCompare(a);
6238
+ });
6239
+ for (const file of matches.slice(keep)) {
6240
+ try {
6241
+ unlinkSync(file);
6242
+ } catch {
6243
+ }
6244
+ }
6245
+ }
6246
+ var PROJECT_DIR_MISSING = { error: "not found", why: "project_dir_missing" };
6247
+ var projectDirMissing = (c) => c.json(PROJECT_DIR_MISSING, 404);
6248
+ function replaceWithProjectDirMissing(c, hostHeaders) {
6249
+ hostHeaders.delete("content-type");
6250
+ c.res = void 0;
6251
+ c.res = Response.json(PROJECT_DIR_MISSING, { status: 404, headers: hostHeaders });
6252
+ }
6253
+ function applyFileMutations(projectDir, mutations, requestToken, writeFile2 = (path, content) => replaceFileAtomically(path, content, statSync22(path).mode)) {
6254
+ const prepared = mutations.map((mutation) => ({
6255
+ ...mutation,
6256
+ before: mutation.before ?? readFileSync42(mutation.absPath, "utf-8")
6257
+ }));
6258
+ const results = [];
6259
+ const attempted = [];
6260
+ for (const mutation of prepared)
6261
+ assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
6262
+ try {
6263
+ for (const mutation of prepared) {
6264
+ results.push(applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted));
6265
+ }
6266
+ return results;
6267
+ } catch (error) {
6268
+ const rollbackErrors = [];
6269
+ for (const mutation of attempted.reverse()) {
6270
+ try {
6271
+ if (!mutation.written || readFileSync42(mutation.absPath, "utf-8") === mutation.after)
6272
+ writeFile2(mutation.absPath, mutation.before, "utf-8");
6273
+ clearFileWriteReceipt(mutation.absPath, mutation.version, mutation.writeToken);
6274
+ } catch (rollbackError) {
6275
+ rollbackErrors.push(rollbackError);
6276
+ }
6277
+ }
6278
+ if (rollbackErrors.length > 0) {
6279
+ throw new AggregateError(
6280
+ [error, ...rollbackErrors],
6281
+ "File mutation failed and rollback did not complete"
6282
+ );
6283
+ }
6284
+ throw error;
6285
+ }
6286
+ }
6287
+ function applyOneMutation(projectDir, mutation, requestToken, writeFile2, attempted) {
6288
+ const current = readFileSync42(mutation.absPath, "utf-8");
6289
+ assertExpectedVersion(mutation, current);
6290
+ if (mutation.after === mutation.before) {
6291
+ return {
6292
+ ...mutation,
6293
+ before: current,
6294
+ changed: false,
6295
+ backupPath: null,
6296
+ version: fileContentVersion(mutation.before),
6297
+ writeToken: null
6298
+ };
6299
+ }
6300
+ const backup = snapshotBeforeWrite(projectDir, mutation.absPath);
6301
+ if (backup.error) throw new Error(`backup failed: ${backup.error}`);
6302
+ assertExpectedVersion(mutation, readFileSync42(mutation.absPath, "utf-8"));
6303
+ const before = current;
6304
+ const version = fileContentVersion(mutation.after);
6305
+ const writeToken = createWriteToken(requestToken);
6306
+ const attempt = { ...mutation, before, version, writeToken, written: false };
6307
+ attempted.push(attempt);
6308
+ writeFile2(mutation.absPath, mutation.after, "utf-8");
6309
+ attempt.written = true;
6310
+ recordFileWriteReceipt(mutation.absPath, {
6311
+ path: mutation.sourceFile,
6312
+ version,
6313
+ writeToken,
6314
+ overwrote: before
6315
+ });
6316
+ return {
6317
+ ...mutation,
6318
+ before,
6319
+ changed: true,
6320
+ backupPath: backupPathForResponse(projectDir, backup.backupPath),
6321
+ version,
6322
+ writeToken
6323
+ };
6324
+ }
6325
+ var FileChangedError = class extends Error {
6326
+ constructor(sourceFile) {
6327
+ super("file changed since the timeline was read");
6328
+ this.sourceFile = sourceFile;
6329
+ }
6330
+ sourceFile;
6331
+ };
6332
+ function assertExpectedVersion(mutation, current) {
6333
+ const stale = mutation.expectedVersion === void 0 ? current !== mutation.before : fileContentVersion(current) !== mutation.expectedVersion;
6334
+ if (stale) throw new FileChangedError(mutation.sourceFile);
6335
+ }
6336
+ var FONT_STYLE_RE = /<style\b[^>]*data-hf-studio-fonts=(["'])true\1[^>]*>([\s\S]*?)<\/style>/i;
6337
+ var ONE_FONT_FACE_RULE = /^@font-face \{(?:[^{}<"'\\]|"(?:[^"\\<\n\r\f]|\\[^<\n\r\f])*"|'(?:[^'\\<\n\r\f]|\\[^<\n\r\f])*')*\}$/;
6338
+ function isStudioFontFaceCss(css) {
6339
+ return typeof css === "string" && ONE_FONT_FACE_RULE.test(css);
6340
+ }
6341
+ function ensureStudioFontFaceCss(html, css) {
6342
+ if (html.includes(css)) return html;
6343
+ const block = FONT_STYLE_RE.exec(html);
6344
+ if (block) {
6345
+ const next = `${(block[2] ?? "").trim()}
6346
+ ${css}`.trim();
6347
+ return html.replace(block[0], () => `<style data-hf-studio-fonts="true">
6348
+ ${next}
6349
+ </style>`);
6350
+ }
6351
+ const styleTag = `<style data-hf-studio-fonts="true">
6352
+ ${css}
6353
+ </style>`;
6354
+ if (/<\/head>/i.test(html)) return html.replace(/<\/head>/i, () => ` ${styleTag}
6355
+ </head>`);
6356
+ return `${styleTag}
6357
+ ${html}`;
6005
6358
  }
6006
6359
  var differential = (family) => ({
6007
6360
  family,
@@ -6070,7 +6423,7 @@ function requestSubPath(url, route) {
6070
6423
  return decodeURIComponent(segments.slice(start + routeSegments.length).join("/"));
6071
6424
  }
6072
6425
  async function loadGsapParser() {
6073
- return import("./gsapParser-DIYYX6JT.js");
6426
+ return import("./gsapParser-GHMVJQLH.js");
6074
6427
  }
6075
6428
  function isDanglingSymlinkInProject(projectDir, lexicalPath) {
6076
6429
  if (!isSafePath(projectDir, dirname32(lexicalPath))) return false;
@@ -6110,7 +6463,7 @@ async function resolveProjectPath(c, adapter, route, opts) {
6110
6463
  }
6111
6464
  return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
6112
6465
  }
6113
- if (opts?.mustExist && !existsSync3(absPath)) {
6466
+ if (opts?.mustExist && !existsSync4(absPath)) {
6114
6467
  return { error: c.json({ error: "not found" }, 404) };
6115
6468
  }
6116
6469
  return { project, filePath, absPath };
@@ -6176,7 +6529,7 @@ function projectHtmlIds(projectDir) {
6176
6529
  if (!rel.endsWith(".html") || isInHiddenOrVendorDir(rel)) continue;
6177
6530
  let html;
6178
6531
  try {
6179
- html = readFileSync32(join52(projectDir, rel), "utf-8");
6532
+ html = readFileSync52(join62(projectDir, rel), "utf-8");
6180
6533
  } catch {
6181
6534
  continue;
6182
6535
  }
@@ -6205,7 +6558,7 @@ function foldAndCommitElementPatchBatches(projectDir, batches, writeFile2, reque
6205
6558
  resolvedPaths.add(absPath);
6206
6559
  let before;
6207
6560
  try {
6208
- before = readFileSync32(absPath, "utf-8");
6561
+ before = readFileSync52(absPath, "utf-8");
6209
6562
  } catch {
6210
6563
  return { error: "not-found", sourceFile: batch.sourceFile };
6211
6564
  }
@@ -6261,7 +6614,7 @@ function commitElementPatchBatchesWithReceipts(c, projectDir, batches) {
6261
6614
  );
6262
6615
  }
6263
6616
  function writeFileWithReceipt(c, filePath, absPath, html) {
6264
- const overwrote = readFileSync32(absPath);
6617
+ const overwrote = readFileSync52(absPath);
6265
6618
  replaceFileAtomically(absPath, html, statSync32(absPath).mode);
6266
6619
  const version = fileContentVersion(html);
6267
6620
  const writeToken = createWriteToken(c.req.header("X-Hyperframes-Write-Token"));
@@ -6271,7 +6624,7 @@ function writeFileWithReceipt(c, filePath, absPath, html) {
6271
6624
  function writeMutationResult(c, projectDir, filePath, absPath, html, original) {
6272
6625
  const backup = snapshotBeforeWrite(projectDir, absPath);
6273
6626
  if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
6274
- if (readFileSync32(absPath, "utf-8") !== original) {
6627
+ if (readFileSync52(absPath, "utf-8") !== original) {
6275
6628
  return c.json({ error: "file changed", conflict: true, path: filePath }, 409);
6276
6629
  }
6277
6630
  const { version } = writeFileWithReceipt(c, filePath, absPath, html);
@@ -6326,7 +6679,7 @@ function generateCopyPath(projectDir, originalPath) {
6326
6679
  const cleanBase = copyMatch ? base.slice(0, -copyMatch[0].length) : base;
6327
6680
  let num = copyMatch ? copyMatch[1] ? parseInt(copyMatch[1]) + 1 : 2 : 1;
6328
6681
  let candidate = num === 1 ? `${cleanBase} (copy)${ext}` : `${cleanBase} (copy ${num})${ext}`;
6329
- while (existsSync3(resolve22(projectDir, candidate))) {
6682
+ while (existsSync4(resolve22(projectDir, candidate))) {
6330
6683
  num++;
6331
6684
  candidate = `${cleanBase} (copy ${num})${ext}`;
6332
6685
  }
@@ -6335,7 +6688,7 @@ function generateCopyPath(projectDir, originalPath) {
6335
6688
  function walkFiles(dir, filter) {
6336
6689
  const results = [];
6337
6690
  for (const entry of readableEntries(dir)) {
6338
- const full2 = join52(dir, entry.name);
6691
+ const full2 = join62(dir, entry.name);
6339
6692
  if (entry.isDirectory()) {
6340
6693
  if (entry.name === "node_modules" || entry.name === ".thumbnails" || entry.name === "renders" || entry.name === ".transcode-cache")
6341
6694
  continue;
@@ -6360,7 +6713,7 @@ function readableEntries(dir) {
6360
6713
  }
6361
6714
  function readableText(file) {
6362
6715
  try {
6363
- return readFileSync32(file, "utf-8");
6716
+ return readFileSync52(file, "utf-8");
6364
6717
  } catch (error) {
6365
6718
  if (skippable(error)) return null;
6366
6719
  throw error;
@@ -6418,7 +6771,7 @@ function occurrences(text2, part) {
6418
6771
  }
6419
6772
  function projectPaths(root, dir = root, prefix = "", ancestors = /* @__PURE__ */ new Set()) {
6420
6773
  return readableEntries(dir).flatMap((entry) => {
6421
- const [path, full2] = [`${prefix}${entry.name}`, join52(dir, entry.name)];
6774
+ const [path, full2] = [`${prefix}${entry.name}`, join62(dir, entry.name)];
6422
6775
  const real = entry.name === "node_modules" ? null : directoryReal(entry, full2);
6423
6776
  if (real === null || ancestors.has(real) || !isSafePath(root, full2)) return [path];
6424
6777
  return [path, ...projectPaths(root, full2, `${path}/`, /* @__PURE__ */ new Set([...ancestors, real]))];
@@ -6726,7 +7079,7 @@ function containsRawGsapExpression(value) {
6726
7079
  return Object.values(value).some(containsRawGsapExpression);
6727
7080
  }
6728
7081
  async function prepareGsapMutationScript(c, res, firstMutation) {
6729
- const beforeHtml = readFileSync32(res.absPath, "utf-8");
7082
+ const beforeHtml = readFileSync52(res.absPath, "utf-8");
6730
7083
  let html = beforeHtml;
6731
7084
  let block = extractGsapScriptBlock(html);
6732
7085
  if (!block && (firstMutation.type === "add" || firstMutation.type === "add-with-keyframes")) {
@@ -6801,6 +7154,7 @@ async function applyGsapMutations(c, res, mutations) {
6801
7154
  const prepared = await prepareGsapMutationScript(c, res, firstMutation);
6802
7155
  if (prepared instanceof Response) return prepared;
6803
7156
  const { html, beforeHtml, block } = prepared;
7157
+ let lengthIsAuthored;
6804
7158
  const initialScript = block.scriptText;
6805
7159
  const skippedSelectors = /* @__PURE__ */ new Set();
6806
7160
  const respond = (data, status) => status ? c.json(data, status) : c.json(data);
@@ -6832,7 +7186,9 @@ async function applyGsapMutations(c, res, mutations) {
6832
7186
  for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
6833
7187
  }
6834
7188
  if (HOLD_SYNC_MUTATION_TYPES.has(mutation.type)) {
6835
- newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript);
7189
+ lengthIsAuthored ??= !lengthIsTimeline(res.project.dir, res.filePath);
7190
+ if (lengthIsAuthored) newScript = trimTrailingKeyframeSpans(previousScript, newScript);
7191
+ newScript = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(newScript, previousScript) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(newScript, previousScript);
6836
7192
  }
6837
7193
  mutationChanges.push(newScript !== previousScript);
6838
7194
  block.scriptText = newScript;
@@ -6840,7 +7196,7 @@ async function applyGsapMutations(c, res, mutations) {
6840
7196
  const changed = block.scriptText !== initialScript;
6841
7197
  const newHtml = changed ? block.replaceScript(block.scriptText) : html;
6842
7198
  let backupPath = null;
6843
- if (readFileSync32(res.absPath, "utf-8") !== beforeHtml) {
7199
+ if (readFileSync52(res.absPath, "utf-8") !== beforeHtml) {
6844
7200
  return c.json({ error: "file changed during GSAP mutation", conflict: true }, 409);
6845
7201
  }
6846
7202
  if (changed) {
@@ -7531,7 +7887,7 @@ async function foldAtomicCutFile(c, file, absPath, before, writer) {
7531
7887
  for (const selector of result.skippedSelectors) skippedSelectors.add(selector);
7532
7888
  }
7533
7889
  if (script !== block.scriptText) {
7534
- script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script);
7890
+ script = writer === "acorn" ? syncPositionHoldsBeforeKeyframes(script, block.scriptText) : (await loadGsapParser()).syncPositionHoldsBeforeKeyframes(script, block.scriptText);
7535
7891
  after = block.replaceScript(script);
7536
7892
  }
7537
7893
  }
@@ -7570,9 +7926,9 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
7570
7926
  const base = dotIdx > 0 ? name.slice(0, dotIdx) : name;
7571
7927
  const MAX_COPY_INDEX = 1e4;
7572
7928
  let n = 1;
7573
- if (existsSync3(finalPath)) {
7929
+ if (existsSync4(finalPath)) {
7574
7930
  n = 2;
7575
- while (n < MAX_COPY_INDEX && existsSync3(resolve22(targetDir, `${base} (${n})${ext}`))) n++;
7931
+ while (n < MAX_COPY_INDEX && existsSync4(resolve22(targetDir, `${base} (${n})${ext}`))) n++;
7576
7932
  if (n >= MAX_COPY_INDEX) {
7577
7933
  skipped.push(name);
7578
7934
  continue;
@@ -7606,7 +7962,7 @@ async function processUploadedFiles(formData, targetDir, projectDir) {
7606
7962
  if (n >= MAX_COPY_INDEX) skipped.push(name);
7607
7963
  continue;
7608
7964
  }
7609
- const relativePath = subDir ? join52(subDir, finalName) : finalName;
7965
+ const relativePath = subDir ? join62(subDir, finalName) : finalName;
7610
7966
  uploaded.push(relativePath);
7611
7967
  if (validation.unchecked) unchecked.push({ name: finalName, reason: validation.unchecked });
7612
7968
  if (isAudioFile(finalName)) {
@@ -7639,7 +7995,7 @@ function registerFileRoutes(api, adapter) {
7639
7995
  if (stat22.size > MAX_TEXT_READ_BYTES) {
7640
7996
  return c.json({ error: "too large to read as text", why: "too_large" }, 413);
7641
7997
  }
7642
- const content = readFileSync32(fd);
7998
+ const content = readFileSync52(fd);
7643
7999
  const version = fileContentVersion(content);
7644
8000
  c.header("ETag", version);
7645
8001
  if (content.subarray(0, GIT_BINARY_SNIFF_BYTES).includes(0)) {
@@ -7664,7 +8020,7 @@ function registerFileRoutes(api, adapter) {
7664
8020
  if (expectedVersion === null && !createOnly) {
7665
8021
  let currentContent = null;
7666
8022
  try {
7667
- currentContent = readFileSync32(res.absPath);
8023
+ currentContent = readFileSync52(res.absPath);
7668
8024
  } catch (error) {
7669
8025
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
7670
8026
  throw error;
@@ -7690,7 +8046,7 @@ function registerFileRoutes(api, adapter) {
7690
8046
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
7691
8047
  throw error;
7692
8048
  }
7693
- const currentContent = readFileSync32(res.absPath);
8049
+ const currentContent = readFileSync52(res.absPath);
7694
8050
  return c.json(
7695
8051
  {
7696
8052
  error: "file conflict",
@@ -7720,7 +8076,7 @@ function registerFileRoutes(api, adapter) {
7720
8076
  );
7721
8077
  }
7722
8078
  try {
7723
- const currentContent = readFileSync32(fd);
8079
+ const currentContent = readFileSync52(fd);
7724
8080
  overwrote = currentContent;
7725
8081
  const currentVersion = fileContentVersion(currentContent);
7726
8082
  if (expectedVersion !== currentVersion) {
@@ -7796,7 +8152,7 @@ function registerFileRoutes(api, adapter) {
7796
8152
  }
7797
8153
  let before;
7798
8154
  try {
7799
- before = readFileSync32(ctx.absPath, "utf-8");
8155
+ before = readFileSync52(ctx.absPath, "utf-8");
7800
8156
  } catch (error) {
7801
8157
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
7802
8158
  throw error;
@@ -7825,7 +8181,7 @@ function registerFileRoutes(api, adapter) {
7825
8181
  }
7826
8182
  const backup = snapshotBeforeWrite(ctx.project.dir, ctx.absPath);
7827
8183
  if (backup.error) return c.json({ error: `backup failed: ${backup.error}` }, 500);
7828
- const current = readFileSync32(ctx.absPath, "utf-8");
8184
+ const current = readFileSync52(ctx.absPath, "utf-8");
7829
8185
  if (current !== before) {
7830
8186
  const currentVersion2 = fileContentVersion(current);
7831
8187
  return c.json({ error: "file conflict", currentVersion: currentVersion2, currentContent: current }, 409);
@@ -7853,12 +8209,12 @@ function registerFileRoutes(api, adapter) {
7853
8209
  api.post("/projects/:id/file-mutations/remove-element/*", async (c) => {
7854
8210
  const ctx = await resolveFileMutationContext(c, adapter, "remove-element");
7855
8211
  if ("error" in ctx) return ctx.error;
7856
- if (!existsSync3(ctx.absPath)) {
8212
+ if (!existsSync4(ctx.absPath)) {
7857
8213
  return c.json({ error: "not found" }, 404);
7858
8214
  }
7859
8215
  const parsed = await parseMutationBody(c);
7860
8216
  if ("error" in parsed) return parsed.error;
7861
- const originalContent = readFileSync32(ctx.absPath, "utf-8");
8217
+ const originalContent = readFileSync52(ctx.absPath, "utf-8");
7862
8218
  return writeIfChanged(
7863
8219
  c,
7864
8220
  ctx.project.dir,
@@ -7871,7 +8227,7 @@ function registerFileRoutes(api, adapter) {
7871
8227
  api.post("/projects/:id/file-mutations/remove-elements/*", async (c) => {
7872
8228
  const ctx = await resolveFileMutationContext(c, adapter, "remove-elements");
7873
8229
  if ("error" in ctx) return ctx.error;
7874
- if (!existsSync3(ctx.absPath)) {
8230
+ if (!existsSync4(ctx.absPath)) {
7875
8231
  return c.json({ error: "not found" }, 404);
7876
8232
  }
7877
8233
  const body = await c.req.json().catch(() => null);
@@ -7879,7 +8235,7 @@ function registerFileRoutes(api, adapter) {
7879
8235
  if (!Array.isArray(targets) || targets.length === 0) {
7880
8236
  return c.json({ error: "targets required" }, 400);
7881
8237
  }
7882
- const originalContent = readFileSync32(ctx.absPath, "utf-8");
8238
+ const originalContent = readFileSync52(ctx.absPath, "utf-8");
7883
8239
  return writeIfChanged(
7884
8240
  c,
7885
8241
  ctx.project.dir,
@@ -7915,7 +8271,7 @@ function registerFileRoutes(api, adapter) {
7915
8271
  seen.add(absPath);
7916
8272
  let before;
7917
8273
  try {
7918
- before = readFileSync32(absPath, "utf-8");
8274
+ before = readFileSync52(absPath, "utf-8");
7919
8275
  } catch {
7920
8276
  return c.json({ error: `not found: ${file.path}` }, 404);
7921
8277
  }
@@ -7942,7 +8298,7 @@ function registerFileRoutes(api, adapter) {
7942
8298
  prepared.push(folded);
7943
8299
  }
7944
8300
  for (const file of prepared) {
7945
- const current = readFileSync32(file.absPath, "utf-8");
8301
+ const current = readFileSync52(file.absPath, "utf-8");
7946
8302
  if (current !== file.before) {
7947
8303
  return c.json(
7948
8304
  {
@@ -7985,7 +8341,7 @@ function registerFileRoutes(api, adapter) {
7985
8341
  const conflicts = [];
7986
8342
  for (const file of written.reverse()) {
7987
8343
  try {
7988
- const current = readFileSync32(file.absPath, "utf-8");
8344
+ const current = readFileSync52(file.absPath, "utf-8");
7989
8345
  if (current !== file.after) {
7990
8346
  conflicts.push(file.path);
7991
8347
  continue;
@@ -8033,7 +8389,7 @@ function registerFileRoutes(api, adapter) {
8033
8389
  const fallbackTiming = typeof parsed.body.elementStart === "number" && typeof parsed.body.elementDuration === "number" ? { start: parsed.body.elementStart, duration: parsed.body.elementDuration } : void 0;
8034
8390
  let originalContent;
8035
8391
  try {
8036
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8392
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8037
8393
  } catch {
8038
8394
  return c.json({ error: "not found" }, 404);
8039
8395
  }
@@ -8095,7 +8451,7 @@ function registerFileRoutes(api, adapter) {
8095
8451
  for (let attempt = 1; ; attempt += 1) {
8096
8452
  let originalContent;
8097
8453
  try {
8098
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8454
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8099
8455
  } catch {
8100
8456
  return c.json({ error: "not found" }, 404);
8101
8457
  }
@@ -8207,7 +8563,7 @@ function registerFileRoutes(api, adapter) {
8207
8563
  }
8208
8564
  let originalContent;
8209
8565
  try {
8210
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8566
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8211
8567
  } catch {
8212
8568
  return c.json({ error: "not found" }, 404);
8213
8569
  }
@@ -8263,7 +8619,7 @@ function registerFileRoutes(api, adapter) {
8263
8619
  ).map((entry) => ({ target: entry.target, track: entry.track }));
8264
8620
  let originalContent;
8265
8621
  try {
8266
- originalContent = readFileSync32(ctx.absPath, "utf-8");
8622
+ originalContent = readFileSync52(ctx.absPath, "utf-8");
8267
8623
  } catch {
8268
8624
  return c.json({ error: "not found" }, 404);
8269
8625
  }
@@ -8292,7 +8648,7 @@ function registerFileRoutes(api, adapter) {
8292
8648
  if ("error" in parsed) return parsed.error;
8293
8649
  let content;
8294
8650
  try {
8295
- content = readFileSync32(ctx.absPath, "utf-8");
8651
+ content = readFileSync52(ctx.absPath, "utf-8");
8296
8652
  } catch {
8297
8653
  return c.json({ exists: false });
8298
8654
  }
@@ -8306,7 +8662,7 @@ function registerFileRoutes(api, adapter) {
8306
8662
  if (!Array.isArray(body?.targets)) return c.json({ error: "targets required" }, 400);
8307
8663
  let content;
8308
8664
  try {
8309
- content = readFileSync32(ctx.absPath, "utf-8");
8665
+ content = readFileSync52(ctx.absPath, "utf-8");
8310
8666
  } catch {
8311
8667
  return c.json({ exists: body.targets.map(() => false) });
8312
8668
  }
@@ -8323,7 +8679,7 @@ function registerFileRoutes(api, adapter) {
8323
8679
  if (!newAbs) {
8324
8680
  return c.json({ error: "forbidden" }, 403);
8325
8681
  }
8326
- if (existsSync3(newAbs)) {
8682
+ if (existsSync4(newAbs)) {
8327
8683
  return c.json({ error: "already exists" }, 409);
8328
8684
  }
8329
8685
  const isDirectory = statSync32(res.absPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
@@ -8340,7 +8696,7 @@ function registerFileRoutes(api, adapter) {
8340
8696
  return c.json({ error: "path required" }, 400);
8341
8697
  }
8342
8698
  const srcAbs = resolveWithinProject(project.dir, body.path);
8343
- if (!srcAbs || !existsSync3(srcAbs)) {
8699
+ if (!srcAbs || !existsSync4(srcAbs)) {
8344
8700
  return c.json({ error: "not found" }, 404);
8345
8701
  }
8346
8702
  const copyPath = generateCopyPath(project.dir, body.path);
@@ -8350,7 +8706,7 @@ function registerFileRoutes(api, adapter) {
8350
8706
  }
8351
8707
  ensureDir(project.dir, destAbs);
8352
8708
  try {
8353
- createFileAtomically(destAbs, readFileSync32(srcAbs));
8709
+ createFileAtomically(destAbs, readFileSync52(srcAbs));
8354
8710
  } catch (error) {
8355
8711
  if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
8356
8712
  throw error;
@@ -8394,7 +8750,7 @@ function registerFileRoutes(api, adapter) {
8394
8750
  mustExist: true
8395
8751
  });
8396
8752
  if ("error" in res) return res.error;
8397
- const html = readFileSync32(res.absPath, "utf-8");
8753
+ const html = readFileSync52(res.absPath, "utf-8");
8398
8754
  const etag = `${fileContentVersion(html).slice(0, -1)}:gsap-animations:v1"`;
8399
8755
  const headers = { ETag: etag, "Cache-Control": "no-cache" };
8400
8756
  if (c.req.header("If-None-Match") === etag) return new Response(null, { status: 304, headers });
@@ -8451,7 +8807,7 @@ function registerFileRoutes(api, adapter) {
8451
8807
  if (!body || typeof body.expected !== "string" || typeof body.restore !== "string") {
8452
8808
  return c.json({ error: "expected and restore contents required" }, 400);
8453
8809
  }
8454
- const current = readFileSync32(res.absPath, "utf-8");
8810
+ const current = readFileSync52(res.absPath, "utf-8");
8455
8811
  if (current !== body.expected) {
8456
8812
  return c.json({ ok: true, restored: false, conflict: true });
8457
8813
  }
@@ -8459,176 +8815,6 @@ function registerFileRoutes(api, adapter) {
8459
8815
  return c.json({ ok: true, restored: true, conflict: false });
8460
8816
  });
8461
8817
  }
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
8818
  var ALWAYS_AFFECTS = ["hyperframes.json", ...STUDIO_SIGNATURE_MANIFEST_PATHS];
8633
8819
  var REFERENCE = /\b(?:src|href|poster|data-composition-src)\s*=\s*(?:"([^"\n]*)"|'([^'\n]*)')|url\(\s*(?:"([^"\n]*)"|'([^'\n]*)'|([^"'\s)]+))/gi;
8634
8820
  var pathKey = process.platform === "darwin" || process.platform === "win32" ? (path) => resolve32(path).toLowerCase() : (path) => resolve32(path);
@@ -8763,7 +8949,7 @@ function readStudioMotionManifestContent(projectDir) {
8763
8949
  const manifestPath = join8(projectDir, STUDIO_MOTION_PATH);
8764
8950
  if (!existsSync5(manifestPath)) return "";
8765
8951
  try {
8766
- return readFileSync52(manifestPath, "utf-8");
8952
+ return readFileSync62(manifestPath, "utf-8");
8767
8953
  } catch {
8768
8954
  return "";
8769
8955
  }
@@ -8905,7 +9091,7 @@ function parsePreviewVariablesParam(raw) {
8905
9091
  }
8906
9092
  function variablesEtagSalt(raw) {
8907
9093
  if (!raw) return "";
8908
- return `:vars:${createHash4("sha1").update(raw).digest("hex").slice(0, 12)}`;
9094
+ return `:vars:${createHash23("sha1").update(raw).digest("hex").slice(0, 12)}`;
8909
9095
  }
8910
9096
  function previewVariablesFromRequest(rawVariables) {
8911
9097
  const parse22 = parsePreviewVariablesParam(rawVariables);
@@ -8945,13 +9131,13 @@ function resolveProjectMainHtml(projectDir, projectId) {
8945
9131
  recordPreviewRead(projectDir, blockHtmlPath);
8946
9132
  if (existsSync5(indexPath)) {
8947
9133
  return {
8948
- html: readFileSync52(indexPath, "utf-8"),
9134
+ html: readFileSync62(indexPath, "utf-8"),
8949
9135
  compositionPath: "index.html"
8950
9136
  };
8951
9137
  }
8952
9138
  if (existsSync5(blockHtmlPath)) {
8953
9139
  return {
8954
- html: readFileSync52(blockHtmlPath, "utf-8"),
9140
+ html: readFileSync62(blockHtmlPath, "utf-8"),
8955
9141
  compositionPath: `${projectId}.html`
8956
9142
  };
8957
9143
  }
@@ -9082,7 +9268,7 @@ ${etag}`;
9082
9268
  function pinSubCompHfIds(compFile, compPath) {
9083
9269
  if (!/\.html?$/i.test(compPath)) return void 0;
9084
9270
  try {
9085
- return ensureHfIds(readFileSync52(compFile, "utf-8"));
9271
+ return ensureHfIds(readFileSync62(compFile, "utf-8"));
9086
9272
  } catch {
9087
9273
  return null;
9088
9274
  }
@@ -9101,7 +9287,7 @@ ${etag}`;
9101
9287
  if (!compFile || !existsSync5(compFile) || !statSync42(compFile).isFile()) {
9102
9288
  return c.text("not found", 404);
9103
9289
  }
9104
- const compPathHash = createHash4("sha1").update(compPath).digest("hex");
9290
+ const compPathHash = createHash23("sha1").update(compPath).digest("hex");
9105
9291
  const capture = c.req.query(PREVIEW_CAPTURE_PARAM) !== void 0;
9106
9292
  const etag = `"comp:v2:${compPathHash}:${signature}${variablesEtagSalt(vars.raw)}${capture ? ":capture" : ""}"`;
9107
9293
  const ifNoneMatch = c.req.header("If-None-Match");
@@ -9193,7 +9379,7 @@ ${etag}`;
9193
9379
  recordProxyRequest();
9194
9380
  servedContentType = PROXY_VARIANT_CONFIG[proxyVariant].contentType;
9195
9381
  }
9196
- const textBuffer = isText ? Buffer.from(readFileSync52(file, "utf-8"), "utf-8") : null;
9382
+ const textBuffer = isText ? Buffer.from(readFileSync62(file, "utf-8"), "utf-8") : null;
9197
9383
  const totalSize = textBuffer ? textBuffer.length : statSync42(servedPath).size;
9198
9384
  const bodyFor = (start, end) => textBuffer ? new Uint8Array(textBuffer.subarray(start, end + 1)) : (
9199
9385
  // Node's web stream type and the DOM one do not overlap for tsc on
@@ -9252,7 +9438,7 @@ async function lintUncoveredHtml(adapter, projectDir, htmlFiles, coveredFiles) {
9252
9438
  const findings = [];
9253
9439
  for (const file of htmlFiles) {
9254
9440
  if (coveredFiles.has(file)) continue;
9255
- const content = readFileSync62(join9(projectDir, file), "utf-8");
9441
+ const content = readFileSync72(join9(projectDir, file), "utf-8");
9256
9442
  const result = await adapter.lint(content, { filePath: file, isSubComposition: true });
9257
9443
  for (const finding of result?.findings ?? []) {
9258
9444
  findings.push({ ...finding, file });
@@ -9418,7 +9604,7 @@ function registerRenderRoutes(api, adapter) {
9418
9604
  }
9419
9605
  const contentType = renderContentType(job.outputPath);
9420
9606
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
9421
- const content = readFileSync72(job.outputPath);
9607
+ const content = readFileSync8(job.outputPath);
9422
9608
  return new Response(content, {
9423
9609
  headers: {
9424
9610
  "Content-Type": contentType,
@@ -9436,7 +9622,7 @@ function registerRenderRoutes(api, adapter) {
9436
9622
  }
9437
9623
  const contentType = renderContentType(job.outputPath);
9438
9624
  const filename = job.outputPath.split("/").pop() ?? `render.mp4`;
9439
- const content = readFileSync72(job.outputPath);
9625
+ const content = readFileSync8(job.outputPath);
9440
9626
  return new Response(content, {
9441
9627
  headers: {
9442
9628
  "Content-Type": contentType,
@@ -9469,7 +9655,7 @@ function registerRenderRoutes(api, adapter) {
9469
9655
  if (!fp) return c.json({ error: "forbidden" }, 403);
9470
9656
  if (!existsSync6(fp)) return c.json({ error: "not found" }, 404);
9471
9657
  const contentType = renderContentType(fp);
9472
- const content = readFileSync72(fp);
9658
+ const content = readFileSync8(fp);
9473
9659
  return new Response(content, {
9474
9660
  headers: {
9475
9661
  "Content-Type": contentType,
@@ -9494,7 +9680,7 @@ function registerRenderRoutes(api, adapter) {
9494
9680
  let audioLoweredDb;
9495
9681
  if (existsSync6(metaPath)) {
9496
9682
  try {
9497
- const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
9683
+ const meta = JSON.parse(readFileSync8(metaPath, "utf-8"));
9498
9684
  if (meta.status === "failed" && !existsSync6(fp)) status = "failed";
9499
9685
  if (meta.durationMs) durationMs = meta.durationMs;
9500
9686
  if (typeof meta.audioLoweredDb === "number") audioLoweredDb = meta.audioLoweredDb;
@@ -9702,7 +9888,7 @@ function registerImageThumbnailRoutes(api, adapter) {
9702
9888
  api.get("/projects/:id/image-thumbnail/*", async (c) => {
9703
9889
  try {
9704
9890
  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");
9891
+ const key = createHash32("sha256").update(JSON.stringify([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs])).digest("hex");
9706
9892
  const etag = `"${key}"`;
9707
9893
  c.header("ETag", etag);
9708
9894
  c.header("Cache-Control", "private, no-cache");
@@ -9727,84 +9913,6 @@ function registerImageThumbnailRoutes(api, adapter) {
9727
9913
  }
9728
9914
  });
9729
9915
  }
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
9916
  var THUMBNAIL_CACHE_VERSION = "v5";
9809
9917
  var THUMBNAIL_MAX_OUTPUT_WIDTH = 240;
9810
9918
  var THUMBNAIL_MAX_OUTPUT_HEIGHT = 135;
@@ -11381,12 +11489,12 @@ export {
11381
11489
  createStudioMotionRenderBodyScript,
11382
11490
  MIME_TYPES,
11383
11491
  getMimeType,
11492
+ buildSubCompositionHtml,
11493
+ compositionsAffectedBy,
11384
11494
  applyFileMutations,
11385
11495
  requestSubPath,
11386
- buildSubCompositionHtml,
11387
11496
  affectsPreview,
11388
11497
  PREVIEW_BUNDLE_OPTIONS,
11389
- compositionsAffectedBy,
11390
11498
  createStudioApi,
11391
11499
  duplicateElementInHtml,
11392
11500
  createPreviewDocumentStore,