hyperframes 0.8.81 → 0.8.83

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 (61) hide show
  1. package/dist/backgroundChecksWorker.js +890 -0
  2. package/dist/cli.js +4272 -2792
  3. package/dist/commands/layout-audit.browser.js +55 -25
  4. package/dist/fontLocalizeCli.js +714 -344
  5. package/dist/hyperframe-runtime.js +83 -58
  6. package/dist/hyperframe.manifest.json +1 -1
  7. package/dist/hyperframe.runtime.iife.js +83 -58
  8. package/dist/hyperframes-player.global.js +3 -3
  9. package/dist/registry/localSemantic.js +7 -3
  10. package/dist/renderSetupWorker.js +62 -32
  11. package/dist/sherpaWorker.js +179 -0
  12. package/dist/skills/hyperframes/SKILL.md +2 -0
  13. package/dist/skills/hyperframes/references/plugin-installation.md +43 -0
  14. package/dist/skills/hyperframes/references/skill-lifecycle.md +2 -0
  15. package/dist/skills/hyperframes/scripts/plugin-cli.mjs +88 -0
  16. package/dist/skills/hyperframes/scripts/plugin-cli.test.mjs +117 -0
  17. package/dist/skills/hyperframes-cli/SKILL.md +2 -0
  18. package/dist/skills/hyperframes-cli/references/upgrade-info-misc.md +1 -0
  19. package/dist/skills/media-use/SKILL.md +2 -0
  20. package/dist/skills/media-use/references/operations.md +7 -5
  21. package/dist/skills/media-use/references/setup-providers.md +9 -9
  22. package/dist/skills/media-use/registry/localSemantic.js +7 -3
  23. package/dist/skills/media-use/scripts/lib/ff-binaries.mjs +37 -0
  24. package/dist/skills/media-use/scripts/lib/probe.mjs +3 -1
  25. package/dist/skills/media-use/scripts/lib/probe.test.mjs +118 -1
  26. package/dist/skills/media-use/scripts/lib/tts-local-provider.mjs +5 -3
  27. package/dist/skills/media-use/scripts/resolve-plugin.test.mjs +62 -0
  28. package/dist/skills/media-use/scripts/resolve.mjs +45 -19
  29. package/dist/skills/media-use/scripts/transcribe.mjs +39 -25
  30. package/dist/skills/media-use/scripts/transcribe.test.mjs +112 -0
  31. package/dist/skills/registry/localSemantic.js +7 -3
  32. package/dist/studio/assets/{hyperframes-player-C68UawR3.js → hyperframes-player-BAerMCex.js} +1 -1
  33. package/dist/studio/assets/{index-CMzPGv9P.js → index-BUhcVhNy.js} +1 -1
  34. package/dist/studio/assets/{index-hNVM9Pd2.js → index-BYhK_KMc.js} +187 -187
  35. package/dist/studio/assets/{index-CS5SImRT.js → index-Iv0HI7eM.js} +1 -1
  36. package/dist/studio/{chunk-I7XNAIOF.js → chunk-3IOCVWB6.js} +6 -97
  37. package/dist/studio/chunk-3IOCVWB6.js.map +1 -0
  38. package/dist/studio/chunk-62DDWOSH.js +189 -0
  39. package/dist/studio/chunk-62DDWOSH.js.map +1 -0
  40. package/dist/studio/chunk-LPZMAGS7.js +1871 -0
  41. package/dist/studio/chunk-LPZMAGS7.js.map +1 -0
  42. package/dist/studio/chunk-QZ6HNNFG.js +934 -0
  43. package/dist/studio/chunk-QZ6HNNFG.js.map +1 -0
  44. package/dist/studio/chunk-S65AQH6M.js +101 -0
  45. package/dist/studio/chunk-S65AQH6M.js.map +1 -0
  46. package/dist/studio/{domEditingLayers-MHFDKAQ7.js → domEditingLayers-ZOOMN7CQ.js} +3 -2
  47. package/dist/studio/index.d.ts +834 -330
  48. package/dist/studio/index.html +1 -1
  49. package/dist/studio/index.js +26378 -28802
  50. package/dist/studio/index.js.map +1 -1
  51. package/dist/studio/player.d.ts +30 -0
  52. package/dist/studio/player.js +10 -0
  53. package/dist/studio/player.js.map +1 -0
  54. package/dist/studio/ui.d.ts +294 -0
  55. package/dist/studio/ui.js +66 -0
  56. package/dist/studio/ui.js.map +1 -0
  57. package/dist/templates/_shared/AGENTS.md +5 -1
  58. package/dist/templates/_shared/CLAUDE.md +5 -1
  59. package/package.json +1 -1
  60. package/dist/studio/chunk-I7XNAIOF.js.map +0 -1
  61. /package/dist/studio/{domEditingLayers-MHFDKAQ7.js.map → domEditingLayers-ZOOMN7CQ.js.map} +0 -0
@@ -3433,10 +3433,10 @@ function compareDocumentPosition(nodeA, nodeB) {
3433
3433
  function uniqueSort(nodes) {
3434
3434
  nodes = nodes.filter((node, i, arr) => !arr.includes(node, i + 1));
3435
3435
  nodes.sort((a, b) => {
3436
- const relative11 = compareDocumentPosition(a, b);
3437
- if (relative11 & DocumentPosition.PRECEDING) {
3436
+ const relative12 = compareDocumentPosition(a, b);
3437
+ if (relative12 & DocumentPosition.PRECEDING) {
3438
3438
  return -1;
3439
- } else if (relative11 & DocumentPosition.FOLLOWING) {
3439
+ } else if (relative12 & DocumentPosition.FOLLOWING) {
3440
3440
  return 1;
3441
3441
  }
3442
3442
  return 0;
@@ -16993,7 +16993,7 @@ var require_util = __commonJS({
16993
16993
  }
16994
16994
  path = url.path;
16995
16995
  }
16996
- var isAbsolute7 = exports.isAbsolute(path);
16996
+ var isAbsolute8 = exports.isAbsolute(path);
16997
16997
  var parts = path.split(/\/+/);
16998
16998
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
16999
16999
  part = parts[i];
@@ -17013,7 +17013,7 @@ var require_util = __commonJS({
17013
17013
  }
17014
17014
  path = parts.join("/");
17015
17015
  if (path === "") {
17016
- path = isAbsolute7 ? "/" : ".";
17016
+ path = isAbsolute8 ? "/" : ".";
17017
17017
  }
17018
17018
  if (url) {
17019
17019
  url.path = path;
@@ -17058,7 +17058,7 @@ var require_util = __commonJS({
17058
17058
  exports.isAbsolute = function(aPath) {
17059
17059
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
17060
17060
  };
17061
- function relative11(aRoot, aPath) {
17061
+ function relative12(aRoot, aPath) {
17062
17062
  if (aRoot === "") {
17063
17063
  aRoot = ".";
17064
17064
  }
@@ -17077,7 +17077,7 @@ var require_util = __commonJS({
17077
17077
  }
17078
17078
  return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
17079
17079
  }
17080
- exports.relative = relative11;
17080
+ exports.relative = relative12;
17081
17081
  var supportsNullProto = (function() {
17082
17082
  var obj = /* @__PURE__ */ Object.create(null);
17083
17083
  return !("__proto__" in obj);
@@ -48198,10 +48198,10 @@ var init_acorn = __esm({
48198
48198
  state.raise("Invalid group");
48199
48199
  }
48200
48200
  var trackDisjunction = this.options.ecmaVersion >= 16;
48201
- var known = state.groupNames[state.lastStringValue];
48202
- if (known) {
48201
+ var known2 = state.groupNames[state.lastStringValue];
48202
+ if (known2) {
48203
48203
  if (trackDisjunction) {
48204
- for (var i = 0, list = known; i < list.length; i += 1) {
48204
+ for (var i = 0, list = known2; i < list.length; i += 1) {
48205
48205
  var altID = list[i];
48206
48206
  if (!altID.separatedFrom(state.branchID)) {
48207
48207
  state.raise("Duplicate capture group name");
@@ -48212,7 +48212,7 @@ var init_acorn = __esm({
48212
48212
  }
48213
48213
  }
48214
48214
  if (trackDisjunction) {
48215
- (known || (state.groupNames[state.lastStringValue] = [])).push(state.branchID);
48215
+ (known2 || (state.groupNames[state.lastStringValue] = [])).push(state.branchID);
48216
48216
  } else {
48217
48217
  state.groupNames[state.lastStringValue] = true;
48218
48218
  }
@@ -52868,17 +52868,22 @@ function escapeCaseInsensitiveToken(source, token, replacement) {
52868
52868
  function inlineScriptTags(scripts) {
52869
52869
  return scripts.map((source) => `<script>${escapeInlineScriptSource(source)}</script>`).join("\n");
52870
52870
  }
52871
- function findDocumentTag(html, tag) {
52872
- const lowered = lowerAscii(html);
52871
+ function* markupStarts(lowered) {
52873
52872
  const unclosedRawText = /* @__PURE__ */ new Set();
52874
52873
  let cursor = 0;
52875
52874
  while (cursor !== -1) {
52876
52875
  const open = lowered.indexOf("<", cursor);
52877
52876
  if (open === -1)
52878
- return -1;
52877
+ return;
52878
+ yield open;
52879
+ cursor = skipMarkup(lowered, open, unclosedRawText);
52880
+ }
52881
+ }
52882
+ function findDocumentTag(html, tag) {
52883
+ const lowered = lowerAscii(html);
52884
+ for (const open of markupStarts(lowered)) {
52879
52885
  if (isTagAt(lowered, open, tag))
52880
52886
  return open;
52881
- cursor = skipMarkup(lowered, open, unclosedRawText);
52882
52887
  }
52883
52888
  return -1;
52884
52889
  }
@@ -53069,7 +53074,7 @@ function parseApplicationExtension(bytes, offset3) {
53069
53074
  }
53070
53075
  return null;
53071
53076
  }
53072
- function parseAnimatedGifMetadata(bytes) {
53077
+ function parseAnimatedGifMetadata(bytes, onFrame) {
53073
53078
  if (bytes.length < 13)
53074
53079
  return null;
53075
53080
  const signature = readAscii(bytes, 0, 6);
@@ -53085,6 +53090,7 @@ function parseAnimatedGifMetadata(bytes) {
53085
53090
  pos += colorTableByteLength(packed);
53086
53091
  }
53087
53092
  let frameCount = 0;
53093
+ let controlPackedOffset = null;
53088
53094
  const delaysCentiseconds = [];
53089
53095
  let loopCount = null;
53090
53096
  while (pos < bytes.length) {
@@ -53107,6 +53113,7 @@ function parseAnimatedGifMetadata(bytes) {
53107
53113
  if (delay == null)
53108
53114
  return null;
53109
53115
  delaysCentiseconds.push(normalizeDelayCentiseconds(delay));
53116
+ controlPackedOffset = pos + 1;
53110
53117
  pos += 1 + blockSize;
53111
53118
  if (bytes[pos] !== 0)
53112
53119
  return null;
@@ -53122,6 +53129,8 @@ function parseAnimatedGifMetadata(bytes) {
53122
53129
  pos = parsed.nextOffset;
53123
53130
  continue;
53124
53131
  }
53132
+ if (label === PLAIN_TEXT_LABEL)
53133
+ controlPackedOffset = null;
53125
53134
  const next = skipSubBlocks(bytes, pos);
53126
53135
  if (next == null)
53127
53136
  return null;
@@ -53132,8 +53141,11 @@ function parseAnimatedGifMetadata(bytes) {
53132
53141
  if (pos + 9 > bytes.length)
53133
53142
  return null;
53134
53143
  const imagePacked = bytes[pos + 8] ?? 0;
53144
+ const localColorTable = (imagePacked & 128) !== 0;
53145
+ onFrame?.({ controlPackedOffset, localColorTable });
53146
+ controlPackedOffset = null;
53135
53147
  pos += 9;
53136
- if ((imagePacked & 128) !== 0) {
53148
+ if (localColorTable) {
53137
53149
  pos += colorTableByteLength(imagePacked);
53138
53150
  }
53139
53151
  if (pos >= bytes.length)
@@ -53159,11 +53171,59 @@ function parseAnimatedGifMetadata(bytes) {
53159
53171
  animated: frameCount > 1
53160
53172
  };
53161
53173
  }
53162
- var BROWSER_MIN_DELAY_CENTISECONDS;
53174
+ function frameLayouts(bytes) {
53175
+ const frames = [];
53176
+ return parseAnimatedGifMetadata(bytes, (frame) => frames.push(frame)) === null ? null : frames;
53177
+ }
53178
+ function disposalOf(bytes, frame) {
53179
+ return frame.controlPackedOffset === null ? null : (bytes[frame.controlPackedOffset] ?? 0) & DISPOSAL_BITS;
53180
+ }
53181
+ function gifClearsAfterLeavingFrameInPlace(bytes) {
53182
+ const frames = frameLayouts(bytes);
53183
+ if (frames === null)
53184
+ return null;
53185
+ let sawOpaque = false;
53186
+ for (const frame of frames) {
53187
+ const disposal = disposalOf(bytes, frame);
53188
+ if (disposal === LEAVE_IN_PLACE)
53189
+ sawOpaque = true;
53190
+ else if (disposal === DISPOSE_TO_BACKGROUND && sawOpaque)
53191
+ return true;
53192
+ }
53193
+ return false;
53194
+ }
53195
+ function clearGifFramesBeforeNext(bytes) {
53196
+ const frames = frameLayouts(bytes);
53197
+ if (frames === null)
53198
+ return false;
53199
+ const controls = frames.flatMap((f) => f.controlPackedOffset === null ? [] : [f.controlPackedOffset]);
53200
+ const shared = frames.flatMap((f) => f.controlPackedOffset === null || f.localColorTable ? [] : [f.controlPackedOffset]);
53201
+ const hasIndex = (at2) => ((bytes[at2] ?? 0) & HAS_TRANSPARENT_INDEX) !== 0;
53202
+ const source = shared.find(hasIndex);
53203
+ for (const at2 of controls) {
53204
+ bytes[at2] = (bytes[at2] ?? 0) & ~DISPOSAL_BITS | DISPOSE_TO_BACKGROUND;
53205
+ }
53206
+ if (source === void 0)
53207
+ return true;
53208
+ for (const at2 of shared) {
53209
+ if (hasIndex(at2))
53210
+ continue;
53211
+ bytes[at2] = (bytes[at2] ?? 0) | HAS_TRANSPARENT_INDEX;
53212
+ bytes[at2 + TRANSPARENT_INDEX_AFTER_PACKED] = bytes[source + TRANSPARENT_INDEX_AFTER_PACKED] ?? 0;
53213
+ }
53214
+ return true;
53215
+ }
53216
+ var BROWSER_MIN_DELAY_CENTISECONDS, PLAIN_TEXT_LABEL, DISPOSAL_BITS, LEAVE_IN_PLACE, DISPOSE_TO_BACKGROUND, HAS_TRANSPARENT_INDEX, TRANSPARENT_INDEX_AFTER_PACKED;
53163
53217
  var init_gif = __esm({
53164
53218
  "../core/dist/media/gif.js"() {
53165
53219
  "use strict";
53166
53220
  BROWSER_MIN_DELAY_CENTISECONDS = 10;
53221
+ PLAIN_TEXT_LABEL = 1;
53222
+ DISPOSAL_BITS = 28;
53223
+ LEAVE_IN_PLACE = 1 << 2;
53224
+ DISPOSE_TO_BACKGROUND = 2 << 2;
53225
+ HAS_TRANSPARENT_INDEX = 1;
53226
+ TRANSPARENT_INDEX_AFTER_PACKED = 3;
53167
53227
  }
53168
53228
  });
53169
53229
 
@@ -54046,6 +54106,36 @@ var init_colorGrading = __esm({
54046
54106
  }
54047
54107
  });
54048
54108
 
54109
+ // ../core/dist/hdrToneMap.js
54110
+ function known(value) {
54111
+ return value && value !== "unknown" && value !== "reserved" ? value : void 0;
54112
+ }
54113
+ function hdrToSdrToneMapFilter(probed, firstFrame) {
54114
+ const fill = (key2, fallback) => known(firstFrame[key2]) ? void 0 : known(probed[key2]) ?? fallback;
54115
+ const tags = [
54116
+ ["colorspace", fill("colorSpace", "bt2020nc")],
54117
+ ["color_primaries", fill("colorPrimaries", "bt2020")],
54118
+ ["color_trc", fill("colorTransfer")]
54119
+ ].filter(([, value]) => value);
54120
+ const setTags = tags.length ? `setparams=${tags.map((tag) => tag.join("=")).join(":")},` : "";
54121
+ return `${setTags}zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:r=tv`;
54122
+ }
54123
+ function firstFrameColourArgs(videoPath) {
54124
+ return [
54125
+ ...["-hide_banner", "-nostats", "-i", videoPath, "-map", "0:v:0", "-frames:v", "1"],
54126
+ ...["-vf", "showinfo", "-f", "null", "-"]
54127
+ ];
54128
+ }
54129
+ function parseFirstFrameColour(stderr) {
54130
+ const tags = / color_space:(\S+) color_primaries:(\S+) color_trc:(\S+)/.exec(stderr);
54131
+ return tags ? { colorSpace: tags[1], colorPrimaries: tags[2], colorTransfer: tags[3] } : {};
54132
+ }
54133
+ var init_hdrToneMap = __esm({
54134
+ "../core/dist/hdrToneMap.js"() {
54135
+ "use strict";
54136
+ }
54137
+ });
54138
+
54049
54139
  // ../core/dist/inline-scripts/hyperframesRuntime.engine.js
54050
54140
  import { buildSync } from "esbuild";
54051
54141
  import { existsSync } from "fs";
@@ -54204,14 +54294,21 @@ var init_telemetryRedaction = __esm({
54204
54294
  });
54205
54295
 
54206
54296
  // ../core/dist/safePath.js
54207
- import { resolve as resolve3, sep, join as join2, dirname as dirname3, basename } from "path";
54208
- import { lstatSync, realpathSync } from "fs";
54297
+ import { resolve as resolve3, sep, join as join2, dirname as dirname3, basename, relative, isAbsolute } from "path";
54298
+ import { lstatSync, mkdirSync, realpathSync, statSync } from "fs";
54209
54299
  var init_safePath = __esm({
54210
54300
  "../core/dist/safePath.js"() {
54211
54301
  "use strict";
54212
54302
  }
54213
54303
  });
54214
54304
 
54305
+ // ../core/dist/projectRule.js
54306
+ var init_projectRule = __esm({
54307
+ "../core/dist/projectRule.js"() {
54308
+ "use strict";
54309
+ }
54310
+ });
54311
+
54215
54312
  // ../core/dist/adapters/gsap.js
54216
54313
  var init_gsap = __esm({
54217
54314
  "../core/dist/adapters/gsap.js"() {
@@ -55327,6 +55424,7 @@ var init_dist2 = __esm({
55327
55424
  init_gif();
55328
55425
  init_colorGrading();
55329
55426
  init_colorLuts();
55427
+ init_hdrToneMap();
55330
55428
  init_hyperframe();
55331
55429
  init_runtimeContract();
55332
55430
  init_runtime_inline();
@@ -55334,6 +55432,7 @@ var init_dist2 = __esm({
55334
55432
  init_parityContract();
55335
55433
  init_telemetryRedaction();
55336
55434
  init_safePath();
55435
+ init_projectRule();
55337
55436
  init_gsap();
55338
55437
  init_text2();
55339
55438
  init_renderOutputTimestamp();
@@ -56604,16 +56703,16 @@ async function launchBrowser(fingerprint) {
56604
56703
  `[BrowserManager] Browser launched (${browserVersion}, ${captureMode}, gl=${gpuFlags.join(" ") || "default"}, headlessShell=${!!fingerprint.executablePath}, platform=${process.platform})`
56605
56704
  );
56606
56705
  if (captureMode === "beginframe") {
56607
- const probe2 = await probeBeginFrameSupport(browser).catch((error) => ({
56706
+ const probe = await probeBeginFrameSupport(browser).catch((error) => ({
56608
56707
  supported: true,
56609
56708
  detail: `probe harness error ignored: ${error instanceof Error ? error.message : String(error)}`,
56610
56709
  durationMs: 0
56611
56710
  }));
56612
- if (!probe2.supported) {
56711
+ if (!probe.supported) {
56613
56712
  await closeBrowserAfterFailedProbe(browser);
56614
56713
  browser = void 0;
56615
56714
  console.warn(
56616
- `[BrowserManager] HeadlessExperimental.beginFrame probe failed after ${probe2.durationMs}ms: ${probe2.detail}; falling back to screenshot mode.`
56715
+ `[BrowserManager] HeadlessExperimental.beginFrame probe failed after ${probe.durationMs}ms: ${probe.detail}; falling back to screenshot mode.`
56617
56716
  );
56618
56717
  captureMode = "screenshot";
56619
56718
  browser = await ppt.launch({
@@ -57849,9 +57948,9 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
57849
57948
  buffer = Buffer.from(result.screenshotData, "base64");
57850
57949
  lastFrameCache.set(page, buffer);
57851
57950
  } else {
57852
- const cached2 = lastFrameCache.get(page);
57853
- if (cached2) {
57854
- buffer = cached2;
57951
+ const cached = lastFrameCache.get(page);
57952
+ if (cached) {
57953
+ buffer = cached;
57855
57954
  } else {
57856
57955
  const fallback = await sendBeginFrame(client, {
57857
57956
  frameTimeTicks: frameTimeTicks + 1e-3,
@@ -59800,27 +59899,31 @@ var init_ffmpegBinaries = __esm({
59800
59899
  import { execFile } from "child_process";
59801
59900
  import { promisify } from "util";
59802
59901
  function isPsnrFilterAvailable() {
59803
- if (cached === null) cached = probe();
59804
- return cached;
59902
+ return isFfmpegFilterAvailable("psnr");
59805
59903
  }
59806
- async function probe() {
59904
+ async function isFfmpegFilterAvailable(name) {
59905
+ if (cachedFilterList === null) cachedFilterList = listFilters();
59906
+ const stdout = await cachedFilterList;
59907
+ return stdout !== null && new RegExp(`(^|\\s)${name}(\\s|$)`, "m").test(stdout);
59908
+ }
59909
+ async function listFilters() {
59807
59910
  const execFileP = promisify(execFile);
59808
59911
  try {
59809
59912
  const { stdout } = await execFileP(getFfmpegBinary(), ["-hide_banner", "-filters"], {
59810
59913
  maxBuffer: 4 * 1024 * 1024,
59811
59914
  timeout: 5e3
59812
59915
  });
59813
- return /(^|\s)psnr(\s|$)/m.test(stdout);
59916
+ return stdout;
59814
59917
  } catch {
59815
- return false;
59918
+ return null;
59816
59919
  }
59817
59920
  }
59818
- var cached;
59921
+ var cachedFilterList;
59819
59922
  var init_psnrFilterAvailability = __esm({
59820
59923
  "../engine/src/utils/psnrFilterAvailability.ts"() {
59821
59924
  "use strict";
59822
59925
  init_ffmpegBinaries();
59823
- cached = null;
59926
+ cachedFilterList = null;
59824
59927
  }
59825
59928
  });
59826
59929
 
@@ -60159,7 +60262,7 @@ var init_captureFailure = __esm({
60159
60262
  });
60160
60263
 
60161
60264
  // ../engine/src/services/frameCapture.ts
60162
- import { existsSync as existsSync5, mkdirSync, writeFileSync } from "fs";
60265
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync } from "fs";
60163
60266
  import { join as join6 } from "path";
60164
60267
  function isDrawElementVerificationError(err) {
60165
60268
  let e = err;
@@ -60600,7 +60703,7 @@ async function completeDeferredDrawElementInit(session) {
60600
60703
  session.deInitDeferred = false;
60601
60704
  }
60602
60705
  async function createCaptureSession(serverUrl, outputDir, options, onBeforeCapture = null, config) {
60603
- if (!existsSync5(outputDir)) mkdirSync(outputDir, { recursive: true });
60706
+ if (!existsSync5(outputDir)) mkdirSync2(outputDir, { recursive: true });
60604
60707
  const headlessShell = resolveHeadlessShellPath(config);
60605
60708
  const isLinux = process.platform === "linux";
60606
60709
  const forceScreenshot = config?.forceScreenshot ?? DEFAULT_CONFIG.forceScreenshot;
@@ -61411,7 +61514,7 @@ async function initializeSession(session) {
61411
61514
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
61412
61515
  try {
61413
61516
  const diagnosticsDir = join6(session.outputDir, "diagnostics");
61414
- if (!existsSync5(diagnosticsDir)) mkdirSync(diagnosticsDir, { recursive: true });
61517
+ if (!existsSync5(diagnosticsDir)) mkdirSync2(diagnosticsDir, { recursive: true });
61415
61518
  const base2 = join6(diagnosticsDir, `frame-error-${frameIndex}`);
61416
61519
  await session.page.screenshot({ path: `${base2}.png`, type: "png", fullPage: true });
61417
61520
  const html = await session.page.content();
@@ -62518,7 +62621,7 @@ async function closeCaptureSession(session) {
62518
62621
  }
62519
62622
  function prepareCaptureSessionForReuse(session, outputDir, onBeforeCapture) {
62520
62623
  if (!existsSync5(outputDir)) {
62521
- mkdirSync(outputDir, { recursive: true });
62624
+ mkdirSync2(outputDir, { recursive: true });
62522
62625
  }
62523
62626
  session.outputDir = outputDir;
62524
62627
  session.onBeforeCapture = onBeforeCapture;
@@ -63352,7 +63455,7 @@ var init_runFfmpeg = __esm({
63352
63455
 
63353
63456
  // ../engine/src/utils/ffprobe.ts
63354
63457
  import { spawn as spawn3 } from "child_process";
63355
- import { createReadStream, readFileSync as readFileSync2, statSync } from "fs";
63458
+ import { createReadStream, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
63356
63459
  import { StringDecoder } from "string_decoder";
63357
63460
  import { basename as basename2 } from "path";
63358
63461
  function redactFfprobeInput(stderr, filePath) {
@@ -63477,7 +63580,7 @@ function isPngImageProbe(output) {
63477
63580
  }
63478
63581
  function mediaFileIdentity(filePath) {
63479
63582
  try {
63480
- const stat = statSync(filePath, { bigint: true });
63583
+ const stat = statSync2(filePath, { bigint: true });
63481
63584
  return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
63482
63585
  } catch {
63483
63586
  return null;
@@ -63490,13 +63593,13 @@ async function probeMediaOutput(filePath, signal) {
63490
63593
  mediaProbeOutputSignalCaches.set(signal, cache2);
63491
63594
  }
63492
63595
  const identity = mediaFileIdentity(filePath);
63493
- const cached2 = cache2.get(filePath);
63494
- if (identity !== null && cached2?.identity === identity) {
63596
+ const cached = cache2.get(filePath);
63597
+ if (identity !== null && cached?.identity === identity) {
63495
63598
  if (!signal) {
63496
63599
  cache2.delete(filePath);
63497
- cache2.set(filePath, cached2);
63600
+ cache2.set(filePath, cached);
63498
63601
  }
63499
- return cached2.promise;
63602
+ return cached.promise;
63500
63603
  }
63501
63604
  const promise = runFfprobe(
63502
63605
  filePath,
@@ -63532,7 +63635,7 @@ async function readFileRange(filePath, start, length, signal) {
63532
63635
  async function hasCompletePngStructure(filePath, signal) {
63533
63636
  try {
63534
63637
  signal?.throwIfAborted();
63535
- const fileSize = statSync(filePath).size;
63638
+ const fileSize = statSync2(filePath).size;
63536
63639
  const signature = await readFileRange(filePath, 0, 8, signal);
63537
63640
  signal?.throwIfAborted();
63538
63641
  if (signature.length !== 8 || !signature.equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
@@ -63663,8 +63766,8 @@ function parseFrameRate(frameRateStr) {
63663
63766
  return rounded > 0 ? rounded : 0.01;
63664
63767
  }
63665
63768
  async function extractMediaMetadata(filePath) {
63666
- const cached2 = videoMetadataCache.get(filePath);
63667
- if (cached2) return cached2;
63769
+ const cached = videoMetadataCache.get(filePath);
63770
+ if (cached) return cached;
63668
63771
  const probePromise = (async () => {
63669
63772
  let stillImageMetaMemo;
63670
63773
  const stillImage = () => {
@@ -63773,8 +63876,8 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
63773
63876
  probeCache = finalVideoFrameTimestampSignalCaches.get(signal) ?? /* @__PURE__ */ new Map();
63774
63877
  finalVideoFrameTimestampSignalCaches.set(signal, probeCache);
63775
63878
  }
63776
- const cached2 = probeCache.get(cacheKey);
63777
- if (cached2) return cached2;
63879
+ const cached = probeCache.get(cacheKey);
63880
+ if (cached) return cached;
63778
63881
  const probePromise = (async () => {
63779
63882
  if (!(videoDurationSeconds > 0) || !Number.isFinite(videoDurationSeconds)) {
63780
63883
  throw new Error(
@@ -63784,7 +63887,7 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
63784
63887
  const streamEnd = videoStreamStartSeconds + videoDurationSeconds;
63785
63888
  const intervalStart = Math.max(videoStreamStartSeconds, streamEnd - 1);
63786
63889
  const parseFinalTimestamp = (stdout) => stdout.split("\n").map((line) => line.trim().split(",")[0]?.trim() ?? "").filter((value) => value.length > 0).map((value) => Number(value)).filter((timestamp2) => Number.isFinite(timestamp2)).at(-1);
63787
- const probe2 = async (readInterval) => {
63890
+ const probe = async (readInterval) => {
63788
63891
  const args = [
63789
63892
  "-select_streams",
63790
63893
  "v:0",
@@ -63800,7 +63903,7 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
63800
63903
  });
63801
63904
  return parseFinalTimestamp(stdout);
63802
63905
  };
63803
- const timestamp = await probe2(`${intervalStart}%${streamEnd}`) ?? await probe2(
63906
+ const timestamp = await probe(`${intervalStart}%${streamEnd}`) ?? await probe(
63804
63907
  /* full scan */
63805
63908
  );
63806
63909
  if (timestamp === void 0) {
@@ -63817,8 +63920,8 @@ async function extractFinalVideoFrameTimestamp(filePath, metadata, signal) {
63817
63920
  return probePromise;
63818
63921
  }
63819
63922
  async function extractAudioMetadata(filePath, options) {
63820
- const cached2 = options?.signal ? void 0 : audioMetadataCache.get(filePath);
63821
- if (cached2) return cached2;
63923
+ const cached = options?.signal ? void 0 : audioMetadataCache.get(filePath);
63924
+ if (cached) return cached;
63822
63925
  const probePromise = (async () => {
63823
63926
  const output = await probeMediaOutput(filePath, options?.signal);
63824
63927
  const audioStream = output.streams.find((s) => s.codec_type === "audio");
@@ -63875,8 +63978,8 @@ async function analyzeKeyframeIntervals(filePath, metadata) {
63875
63978
  filePath,
63876
63979
  metadata
63877
63980
  );
63878
- const cached2 = keyframeCache.get(cacheKey);
63879
- if (cached2) return cached2;
63981
+ const cached = keyframeCache.get(cacheKey);
63982
+ if (cached) return cached;
63880
63983
  const promise = analyzeKeyframeIntervalsUncached(
63881
63984
  filePath,
63882
63985
  videoStreamStartSeconds,
@@ -64068,12 +64171,12 @@ import {
64068
64171
  copyFileSync,
64069
64172
  existsSync as existsSync6,
64070
64173
  ftruncateSync,
64071
- mkdirSync as mkdirSync2,
64174
+ mkdirSync as mkdirSync3,
64072
64175
  mkdtempSync,
64073
64176
  openSync,
64074
64177
  readFileSync as readFileSync4,
64075
64178
  readdirSync as readdirSync2,
64076
- statSync as statSync2,
64179
+ statSync as statSync3,
64077
64180
  writeFileSync as writeFileSync2,
64078
64181
  writeSync
64079
64182
  } from "fs";
@@ -64316,7 +64419,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
64316
64419
  async function encodeFramesFromDir(framesDir, framePattern, outputPath, options, signal, config) {
64317
64420
  const startTime = Date.now();
64318
64421
  const outputDir = dirname5(outputPath);
64319
- if (!existsSync6(outputDir)) mkdirSync2(outputDir, { recursive: true });
64422
+ if (!existsSync6(outputDir)) mkdirSync3(outputDir, { recursive: true });
64320
64423
  const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
64321
64424
  const frameCount = files.length;
64322
64425
  if (frameCount === 0) {
@@ -64363,7 +64466,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
64363
64466
  failureReason: isExternalFfmpegInterruption(result) ? "external_interruption" : void 0
64364
64467
  };
64365
64468
  }
64366
- const fileSize = existsSync6(outputPath) ? statSync2(outputPath).size : 0;
64469
+ const fileSize = existsSync6(outputPath) ? statSync3(outputPath).size : 0;
64367
64470
  return {
64368
64471
  success: true,
64369
64472
  outputPath,
@@ -64390,7 +64493,7 @@ async function concatVideoFiles(inputPaths, outputPath, signal, config) {
64390
64493
  if (firstInput === void 0) {
64391
64494
  return { success: false, error: "concatVideoFiles: no inputs", externalInterruption: false };
64392
64495
  }
64393
- mkdirSync2(dirname5(outputPath), { recursive: true });
64496
+ mkdirSync3(dirname5(outputPath), { recursive: true });
64394
64497
  const listPath = writeConcatList(dirname5(firstInput), inputPaths);
64395
64498
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG.ffmpegEncodeTimeout;
64396
64499
  const result = await runFfmpeg(buildConcatArgs(listPath, outputPath), {
@@ -64423,7 +64526,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
64423
64526
  }
64424
64527
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
64425
64528
  const chunkCount = Math.ceil(files.length / chunkSize);
64426
- mkdirSync2(dirname5(outputPath), { recursive: true });
64529
+ mkdirSync3(dirname5(outputPath), { recursive: true });
64427
64530
  const chunkDir = mkdtempSync(join8(dirname5(outputPath), "chunk-encode-"));
64428
64531
  const chunkPaths = [];
64429
64532
  for (let i = 0; i < chunkCount; i++) {
@@ -64490,7 +64593,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
64490
64593
  failureReason: concatResult.externalInterruption ? "external_interruption" : void 0
64491
64594
  };
64492
64595
  }
64493
- const fileSize = existsSync6(outputPath) ? statSync2(outputPath).size : 0;
64596
+ const fileSize = existsSync6(outputPath) ? statSync3(outputPath).size : 0;
64494
64597
  return {
64495
64598
  success: true,
64496
64599
  outputPath,
@@ -64501,7 +64604,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
64501
64604
  }
64502
64605
  async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, config, fps) {
64503
64606
  const outputDir = dirname5(outputPath);
64504
- if (!existsSync6(outputDir)) mkdirSync2(outputDir, { recursive: true });
64607
+ if (!existsSync6(outputDir)) mkdirSync3(outputDir, { recursive: true });
64505
64608
  const isWebm = outputPath.endsWith(".webm");
64506
64609
  const isMov = outputPath.endsWith(".mov");
64507
64610
  const shouldCopyAudio = isWebm ? false : await shouldCopyAacSidecar(audioPath, config);
@@ -64568,7 +64671,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
64568
64671
  if (outputDir.includes("%")) {
64569
64672
  throw new Error(`[chunkEncoder] packageHls outputDir must not contain "%": ${outputDir}`);
64570
64673
  }
64571
- if (!existsSync6(outputDir)) mkdirSync2(outputDir, { recursive: true });
64674
+ if (!existsSync6(outputDir)) mkdirSync3(outputDir, { recursive: true });
64572
64675
  const hasAudio = audioPath !== null;
64573
64676
  const args = hasAudio ? ["-i", videoPath, "-i", audioPath, "-map", "0:v:0", "-map", "1:a:0"] : ["-i", videoPath, "-map", "0:v:0"];
64574
64677
  args.push(
@@ -64751,7 +64854,7 @@ var init_jpegInput = __esm({
64751
64854
  // ../engine/src/services/streamingEncoder.ts
64752
64855
  import { spawn as spawn4 } from "child_process";
64753
64856
  import { once } from "events";
64754
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, statSync as statSync3 } from "fs";
64857
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync4 } from "fs";
64755
64858
  import { dirname as dirname6 } from "path";
64756
64859
  function createFrameReorderBuffer(startFrame, endFrame) {
64757
64860
  let cursor = startFrame;
@@ -64985,7 +65088,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
64985
65088
  }
64986
65089
  async function spawnStreamingEncoder(outputPath, options, signal, config) {
64987
65090
  const outputDir = dirname6(outputPath);
64988
- if (!existsSync7(outputDir)) mkdirSync3(outputDir, { recursive: true });
65091
+ if (!existsSync7(outputDir)) mkdirSync4(outputDir, { recursive: true });
64989
65092
  let gpuEncoder = null;
64990
65093
  if (options.useGpu) {
64991
65094
  gpuEncoder = await getCachedGpuEncoder();
@@ -65105,7 +65208,7 @@ FFmpeg stopped after ${streamingTimeout} ms without consuming a frame.` : "";
65105
65208
  }) ? "external_interruption" : void 0
65106
65209
  };
65107
65210
  }
65108
- const fileSize = existsSync7(outputPath) ? statSync3(outputPath).size : 0;
65211
+ const fileSize = existsSync7(outputPath) ? statSync4(outputPath).size : 0;
65109
65212
  return { success: true, durationMs, fileSize };
65110
65213
  },
65111
65214
  getExitStatus: () => exitStatus,
@@ -65147,7 +65250,7 @@ var init_streamingEncoder = __esm({
65147
65250
 
65148
65251
  // ../parsers/dist/assetResolution.js
65149
65252
  import { existsSync as existsSync8 } from "fs";
65150
- import { isAbsolute, join as join9, posix as posix2, relative, resolve as resolve5, sep as sep2 } from "path";
65253
+ import { isAbsolute as isAbsolute2, join as join9, posix as posix2, relative as relative2, resolve as resolve5, sep as sep2 } from "path";
65151
65254
  function decodeUrlPathVariants2(path) {
65152
65255
  const variants = [path];
65153
65256
  try {
@@ -65166,7 +65269,7 @@ function isUnresolvedAssetPlaceholder(rawSrc) {
65166
65269
  function cleanAssetUrl(url) {
65167
65270
  return url.trim().split(/[?#]/, 1)[0] ?? "";
65168
65271
  }
65169
- function isWithinProjectRoot(projectDir, candidate, pathModule = { resolve: resolve5, relative, isAbsolute }) {
65272
+ function isWithinProjectRoot(projectDir, candidate, pathModule = { resolve: resolve5, relative: relative2, isAbsolute: isAbsolute2 }) {
65170
65273
  const projectRoot = pathModule.resolve(projectDir);
65171
65274
  const relativePath = pathModule.relative(projectRoot, candidate);
65172
65275
  return relativePath === "" || !relativePath.startsWith("..") && !pathModule.isAbsolute(relativePath);
@@ -65181,7 +65284,7 @@ function reanchoredCandidates(variant, baseDir, compiledDir) {
65181
65284
  }
65182
65285
  function resolveProjectRelativeSrc(src, baseDir, compiledDir) {
65183
65286
  const cleanSrc = cleanAssetUrl(src);
65184
- if (isAbsolute(cleanSrc) && existsSync8(cleanSrc)) return cleanSrc;
65287
+ if (isAbsolute2(cleanSrc) && existsSync8(cleanSrc)) return cleanSrc;
65185
65288
  const candidates = /* @__PURE__ */ new Set();
65186
65289
  for (const variant of decodeUrlPathVariants2(cleanSrc)) {
65187
65290
  for (const candidate of reanchoredCandidates(variant, baseDir, compiledDir)) {
@@ -65203,8 +65306,8 @@ function findReferenceTargetEl(doc, refId) {
65203
65306
  return doc.getElementById(refId) ?? doc.querySelector(`[data-composition-id="${refId}"]`);
65204
65307
  }
65205
65308
  function resolveReferencedStart(doc, el, startCache, visiting) {
65206
- const cached2 = startCache.get(el);
65207
- if (cached2 !== void 0) return cached2;
65309
+ const cached = startCache.get(el);
65310
+ if (cached !== void 0) return cached;
65208
65311
  if (visiting.has(el)) return 0;
65209
65312
  visiting.add(el);
65210
65313
  try {
@@ -65273,13 +65376,13 @@ import {
65273
65376
  fsyncSync,
65274
65377
  linkSync,
65275
65378
  mkdtempSync as mkdtempSync2,
65276
- mkdirSync as mkdirSync4,
65379
+ mkdirSync as mkdirSync5,
65277
65380
  lstatSync as lstatSync2,
65278
65381
  openSync as openSync2,
65279
65382
  readdirSync as readdirSync3,
65280
65383
  rmdirSync,
65281
65384
  rmSync,
65282
- statSync as statSync4,
65385
+ statSync as statSync5,
65283
65386
  unlinkSync
65284
65387
  } from "fs";
65285
65388
  import { createHash, randomUUID } from "crypto";
@@ -65452,7 +65555,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
65452
65555
  }
65453
65556
  let createdLock = false;
65454
65557
  try {
65455
- mkdirSync4(lockPath);
65558
+ mkdirSync5(lockPath);
65456
65559
  createdLock = true;
65457
65560
  } catch (error) {
65458
65561
  if (error.code !== "EEXIST") throw error;
@@ -65460,7 +65563,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
65460
65563
  if (createdLock) {
65461
65564
  const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID()}`;
65462
65565
  try {
65463
- mkdirSync4(join10(lockPath, owner));
65566
+ mkdirSync5(join10(lockPath, owner));
65464
65567
  const entries2 = readdirSync3(lockPath);
65465
65568
  if (entries2.length === 1 && entries2[0] === owner) {
65466
65569
  return () => releaseOwnedCachePathLock(lockPath, owner);
@@ -65493,7 +65596,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
65493
65596
  }
65494
65597
  const reclaimPath = join10(lockPath, CACHE_LOCK_RECLAIM_NAME);
65495
65598
  try {
65496
- mkdirSync4(reclaimPath);
65599
+ mkdirSync5(reclaimPath);
65497
65600
  } catch (error) {
65498
65601
  const code = error.code;
65499
65602
  if (code === "EEXIST" || code === "ENOENT") continue;
@@ -65830,7 +65933,7 @@ async function fetchToPartial(url, partialPath, controller, options) {
65830
65933
  const readableStream = Readable.fromWeb(response.body);
65831
65934
  const fileStream = createWriteStream(partialPath, { flags: "wx" });
65832
65935
  await pipeline(readableStream, inspector, fileStream, { signal: controller.signal });
65833
- const localSize = statSync4(partialPath).size;
65936
+ const localSize = statSync5(partialPath).size;
65834
65937
  const sha256Bytes = sha256.digest();
65835
65938
  const localSha256 = sha256Bytes.toString("hex");
65836
65939
  const md5Base64 = md5.digest("base64");
@@ -66163,7 +66266,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5, signal, onTransient
66163
66266
  return inFlight;
66164
66267
  }
66165
66268
  if (!existsSync9(destDir)) {
66166
- mkdirSync4(destDir, { recursive: true });
66269
+ mkdirSync5(destDir, { recursive: true });
66167
66270
  }
66168
66271
  const filename = getFilenameFromUrl(url, validationScope);
66169
66272
  const localPath = join10(destDir, filename);
@@ -66309,11 +66412,12 @@ var init_scriptRuns = __esm({
66309
66412
  });
66310
66413
 
66311
66414
  // ../core/dist/sceneParts.js
66312
- var SCENE_PART_ATTR, SCENE_NO_SWAP_ATTR;
66415
+ var SCENE_PART_ATTR, SCENE_PARTS_META, SCENE_NO_SWAP_ATTR;
66313
66416
  var init_sceneParts = __esm({
66314
66417
  "../core/dist/sceneParts.js"() {
66315
66418
  "use strict";
66316
66419
  SCENE_PART_ATTR = "data-hf-scene";
66420
+ SCENE_PARTS_META = "hf-scene-parts";
66317
66421
  SCENE_NO_SWAP_ATTR = "data-hf-scene-no-swap";
66318
66422
  }
66319
66423
  });
@@ -67224,8 +67328,33 @@ ${source.replace(/<\/(script)/gi, "<\\/$1")}
67224
67328
  console.error(__hfErrorLabel, __hfCompId, _err);
67225
67329
  }
67226
67330
  };
67331
+ // What the script started on the global gsap timeline, by any route, for a scene swap to revert.
67332
+ // Only a page with a scene manifest can swap; elsewhere the first script stores null and none records.
67333
+ var __hfRecordAnimations = function(run) {
67334
+ if (window.__hfSceneAnimations === undefined) {
67335
+ window.__hfSceneAnimations = window.document.querySelector(${jsonScriptLiteral(`meta[name="${SCENE_PARTS_META}"]`)})
67336
+ ? {}
67337
+ : null;
67338
+ }
67339
+ var byComp = window.__hfSceneAnimations;
67340
+ var globalTimeline = __hfBaseGsap && __hfBaseGsap.globalTimeline;
67341
+ if (!byComp || !globalTimeline || !__hfTimelineCompId) return run();
67342
+ var before = globalTimeline.getChildren(false);
67343
+ // A set completes as it is made and would leave the timeline before the diff below.
67344
+ var autoRemove = globalTimeline.autoRemoveChildren;
67345
+ globalTimeline.autoRemoveChildren = false;
67346
+ run();
67347
+ globalTimeline.autoRemoveChildren = autoRemove;
67348
+ var recorded = (byComp[__hfTimelineCompId] = byComp[__hfTimelineCompId] || []);
67349
+ globalTimeline.getChildren(false).forEach(function(animation) {
67350
+ if (before.indexOf(animation) >= 0) return;
67351
+ recorded.push(animation);
67352
+ // Dropped as the timeline drops a finished tween (it keeps a paused one); moved back, a tween re-adds itself.
67353
+ if (autoRemove && !animation.getChildren && animation.totalProgress() === 1) globalTimeline.remove(animation);
67354
+ });
67355
+ };
67227
67356
  __hfFindRoot();
67228
- __hfRun();
67357
+ __hfRecordAnimations(__hfRun);
67229
67358
  })();`;
67230
67359
  }
67231
67360
  function wrapInlineScriptWithErrorBoundary(source, errorLabel) {
@@ -67241,6 +67370,7 @@ var AUTHORED_ROOT_ID_ATTR, INNER_ROOT_ATTR, GLOBAL_AT_RULES, SCOPED_HYPERFRAMES_
67241
67370
  var init_compositionScoping = __esm({
67242
67371
  "../core/dist/compiler/compositionScoping.js"() {
67243
67372
  "use strict";
67373
+ init_sceneParts();
67244
67374
  AUTHORED_ROOT_ID_ATTR = "data-hf-authored-id";
67245
67375
  INNER_ROOT_ATTR = "data-hf-inner-root";
67246
67376
  GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
@@ -70903,9 +71033,9 @@ function resolveCompositionDuration(input) {
70903
71033
  if (isPositiveFinite(input.authoredDurationSeconds)) {
70904
71034
  return { seconds: input.authoredDurationSeconds, source: "authored", pendingClips };
70905
71035
  }
70906
- const known = input.clipEndsSeconds.filter(isPositiveFinite);
70907
- if (known.length > 0) {
70908
- return { seconds: Math.max(...known), source: "derived", pendingClips };
71036
+ const known2 = input.clipEndsSeconds.filter(isPositiveFinite);
71037
+ if (known2.length > 0) {
71038
+ return { seconds: Math.max(...known2), source: "derived", pendingClips };
70909
71039
  }
70910
71040
  return {
70911
71041
  seconds: null,
@@ -70914,10 +71044,11 @@ function resolveCompositionDuration(input) {
70914
71044
  reason: pendingClips > 0 ? "every clip's length is still pending" : "no authored duration and no clip with a length"
70915
71045
  };
70916
71046
  }
70917
- var isPositiveFinite;
71047
+ var TIMING_TOLERANCE_SECONDS, isPositiveFinite;
70918
71048
  var init_compositionDuration = __esm({
70919
71049
  "../parsers/dist/compositionDuration.js"() {
70920
71050
  "use strict";
71051
+ TIMING_TOLERANCE_SECONDS = 0.05;
70921
71052
  isPositiveFinite = (value) => value !== null && Number.isFinite(value) && value > 0;
70922
71053
  }
70923
71054
  });
@@ -74251,8 +74382,8 @@ function findAudioGroupCarveAttrFindings(ctx) {
74251
74382
  return findings;
74252
74383
  }
74253
74384
  function callsFor(script) {
74254
- const cached2 = callIndexes.get(script);
74255
- if (cached2) return cached2;
74385
+ const cached = callIndexes.get(script);
74386
+ if (cached) return cached;
74256
74387
  const calls = [];
74257
74388
  try {
74258
74389
  const ast = parse7(script.content, {
@@ -74652,8 +74783,8 @@ function synthesizeWindowRaw(timelineVar, anim) {
74652
74783
  return `${timelineVar}.${anim.method}("${anim.targetSelector}", { ${entries2.join(", ")} }, ${pos})`;
74653
74784
  }
74654
74785
  async function cachedExtractGsapWindows(scriptContent) {
74655
- const cached2 = gsapWindowsCache.get(scriptContent);
74656
- if (cached2) return cached2;
74786
+ const cached = gsapWindowsCache.get(scriptContent);
74787
+ if (cached) return cached;
74657
74788
  const windows = await extractGsapWindows(scriptContent);
74658
74789
  gsapWindowsCache.set(scriptContent, windows);
74659
74790
  return windows;
@@ -75597,6 +75728,26 @@ function captionFindings(rows, severity) {
75597
75728
  }
75598
75729
  return findings;
75599
75730
  }
75731
+ function clipsPastRootFindings(root, rows) {
75732
+ if (root.attrs["data-composition-id"] === void 0) return [];
75733
+ const rootDuration = readDataDurationSeconds((name) => root.attrs[name]);
75734
+ if (rootDuration === null) return [];
75735
+ const limit = hundredths(rootDuration);
75736
+ return rows.flatMap((row) => {
75737
+ const { start, end } = readClipTiming({ getAttribute: (name) => row.attrs[name] ?? null });
75738
+ if (start === null || end === null || end <= rootDuration + TIMING_TOLERANCE_SECONDS) return [];
75739
+ return [
75740
+ {
75741
+ code: "clip_ends_past_root_duration",
75742
+ severity: "warning",
75743
+ message: `${describe(row)} runs from ${hundredths(start)}s to ${hundredths(end)}s, past the root composition's data-duration of ${limit}s, so it is cut off in previews, posters and renders.`,
75744
+ elementId: row.attrs.id,
75745
+ fixHint: `Extend the root data-duration to ${hundredths(end)}, or make ${describe(row)} end at or before ${limit}s.`,
75746
+ snippet: truncateSnippet(row.open.raw)
75747
+ }
75748
+ ];
75749
+ });
75750
+ }
75600
75751
  function dedupeKeyFor(finding) {
75601
75752
  return [
75602
75753
  finding.code,
@@ -75665,7 +75816,7 @@ async function lintHyperframeHtml(html, options = {}) {
75665
75816
  timings: { totalMs: performance.now() - startedAt, ...timings }
75666
75817
  };
75667
75818
  }
75668
- var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, REGEX_ALLOWED_BEFORE, REGEX_ALLOWED_KEYWORDS, WORD_CHAR, CodeContext, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, IMAGE_SRC_EXT, VIDEO_SRC_EXT, AUDIO_SRC_EXT, SRC_KIND_NOUN, mediaRules, AUDIO_GROUP_TIMING_ATTRS, callIndexes, INSPECTION_VIEWPORT_CAP, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, ZOOM_DECLARATION, NEGATIVE_Z_INDEX, compositionRules, CLIP_MEDIA_TAGS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, INLINE_TEXT_TAGS, OPAQUE_TAGS, NON_LAYOUT_TAGS, MEDIA_TAGS2, NODE_ATTRS, describe, structureRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
75819
+ var import_postcss_selector_parser, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN, TIMELINE_REGISTRY_OBJECT_OPEN_PATTERN, TIMELINE_REGISTRY_OBJECT_ENTRY_PATTERN, REGEX_ALLOWED_BEFORE, REGEX_ALLOWED_KEYWORDS, WORD_CHAR, CodeContext, VISIBLE_MARKUP_COMMENT_PATTERN, VISIBLE_MARKUP_COMMENT_PROTECTED_BLOCK_PATTERN, coreRules, IMAGE_SRC_EXT, VIDEO_SRC_EXT, AUDIO_SRC_EXT, SRC_KIND_NOUN, mediaRules, AUDIO_GROUP_TIMING_ATTRS, callIndexes, INSPECTION_VIEWPORT_CAP, MAX_COMPOSITION_LINES, MAX_TIMED_ELEMENTS_PER_TRACK, TRACK_DENSITY_EXEMPT_TAGS, CAPTION_CUE_TOKEN, HEAVY_OVERLAY_ELEMENT_COUNT_WARN, HEAVY_OVERLAY_EXEMPT_TAGS, HEAVY_OVERLAY_CSS_PATTERN, INLINE_STYLE_DISPLAY_NONE_PATTERN, ZOOM_DECLARATION, NEGATIVE_Z_INDEX, compositionRules, CLIP_MEDIA_TAGS, SCENE_BOUNDARY_EPSILON_SECONDS, UNRESOLVED_TARGET, gsapWindowsCache, CONFLICTING_TRANSLATE_PROPS, CONFLICTING_SCALE_PROPS, RELATIVE_TWEEN_VALUE, TRANSFORM_SENSITIVE_READ, TRANSFORM_INVARIANT_READ, CALLBACK_MEASUREMENT_PATTERN, NUMBER_METHODS, gsapRules, captionRules, adapterRules, TEXTURE_BASE_CLASS, TEXTURE_CLASS_PREFIX, textureRules, GENERIC_FAMILIES, fontRules, slideshowRules, INLINE_TEXT_TAGS, OPAQUE_TAGS, NON_LAYOUT_TAGS, MEDIA_TAGS2, NODE_ATTRS, describe, hundredths, structureRules, RULE_GROUPS, LINT_RULE_COUNT, LINT_RULE_GROUP_COUNTS;
75669
75820
  var init_dist3 = __esm({
75670
75821
  "../lint/dist/index.js"() {
75671
75822
  "use strict";
@@ -75686,6 +75837,9 @@ var init_dist3 = __esm({
75686
75837
  init_composition();
75687
75838
  init_slideshow();
75688
75839
  init_topLevelElements();
75840
+ init_compositionContract();
75841
+ init_mediaDuration();
75842
+ init_compositionDuration();
75689
75843
  init_esm10();
75690
75844
  init_esm10();
75691
75845
  COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
@@ -78902,6 +79056,7 @@ ${other.raw}`)
78902
79056
  "data-composition-src"
78903
79057
  ];
78904
79058
  describe = (node) => `<${node.tag}${node.attrs.id ? ` id="${node.attrs.id}"` : ""}>`;
79059
+ hundredths = (seconds) => Math.round(seconds * 100) / 100;
78905
79060
  structureRules = [
78906
79061
  (ctx) => {
78907
79062
  if (ctx.options.isSubComposition) return [];
@@ -78912,7 +79067,8 @@ ${other.raw}`)
78912
79067
  return [
78913
79068
  ...nestedStructureFindings(rows, severity),
78914
79069
  ...missingDurationFindings(rows, severity),
78915
- ...captionFindings(rows, severity)
79070
+ ...captionFindings(rows, severity),
79071
+ ...clipsPastRootFindings(root, rows)
78916
79072
  ];
78917
79073
  }
78918
79074
  ];
@@ -79596,18 +79752,18 @@ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
79596
79752
  import {
79597
79753
  existsSync as existsSync10,
79598
79754
  lstatSync as lstatSync3,
79599
- mkdirSync as mkdirSync5,
79755
+ mkdirSync as mkdirSync6,
79600
79756
  readdirSync as readdirSync5,
79601
79757
  renameSync,
79602
79758
  rmSync as rmSync2,
79603
- statSync as statSync5,
79759
+ statSync as statSync6,
79604
79760
  utimesSync,
79605
79761
  writeFileSync as writeFileSync3
79606
79762
  } from "fs";
79607
79763
  import { join as join12 } from "path";
79608
79764
  function readKeyStat(videoPath) {
79609
79765
  try {
79610
- const stat = statSync5(videoPath);
79766
+ const stat = statSync6(videoPath);
79611
79767
  return { mtimeMs: Math.floor(stat.mtimeMs), size: stat.size };
79612
79768
  } catch {
79613
79769
  return null;
@@ -79740,7 +79896,7 @@ function removeDir(path) {
79740
79896
  }
79741
79897
  function collectGcEntry(dir, name, now, minAgeMs, stats) {
79742
79898
  try {
79743
- const dirStat = statSync5(dir);
79899
+ const dirStat = statSync6(dir);
79744
79900
  if (isPartialChild(name) && now - dirStat.mtimeMs >= minAgeMs) {
79745
79901
  removeDir(dir);
79746
79902
  stats.agedPartialsRemoved += 1;
@@ -79748,7 +79904,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats) {
79748
79904
  }
79749
79905
  let lastUseMs = dirStat.mtimeMs;
79750
79906
  try {
79751
- lastUseMs = statSync5(join12(dir, COMPLETE_SENTINEL)).mtimeMs;
79907
+ lastUseMs = statSync6(join12(dir, COMPLETE_SENTINEL)).mtimeMs;
79752
79908
  } catch {
79753
79909
  }
79754
79910
  return { dir, size: directorySizeBytes(dir), lastUseMs, ageMs: now - lastUseMs };
@@ -79758,7 +79914,7 @@ function collectGcEntry(dir, name, now, minAgeMs, stats) {
79758
79914
  }
79759
79915
  function gcSweepDue(rootDir, maxAgeMs) {
79760
79916
  try {
79761
- return Date.now() - statSync5(join12(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
79917
+ return Date.now() - statSync6(join12(rootDir, GC_MARKER)).mtimeMs > maxAgeMs;
79762
79918
  } catch {
79763
79919
  return true;
79764
79920
  }
@@ -79820,14 +79976,14 @@ var init_extractionCache = __esm({
79820
79976
  init_extractedFrameIndex();
79821
79977
  COMPLETE_SENTINEL = ".hf-complete";
79822
79978
  GC_MARKER = ".hf-last-gc";
79823
- SCHEMA_PREFIX = "hfcache-v5-";
79979
+ SCHEMA_PREFIX = "hfcache-v6-";
79824
79980
  KEY_HEX_CHARS = 16;
79825
79981
  CACHE_GENERATION_PREFIX = "hfcache-v";
79826
79982
  }
79827
79983
  });
79828
79984
 
79829
79985
  // ../engine/src/services/videoFrameExtractor.ts
79830
- import { copyFileSync as copyFileSync2, existsSync as existsSync11, linkSync as linkSync2, mkdirSync as mkdirSync6, rmSync as rmSync3 } from "fs";
79986
+ import { copyFileSync as copyFileSync2, existsSync as existsSync11, linkSync as linkSync2, mkdirSync as mkdirSync7, rmSync as rmSync3 } from "fs";
79831
79987
  import { join as join13 } from "path";
79832
79988
  function isVideoFrameFormat(value) {
79833
79989
  return typeof value === "string" && VIDEO_FRAME_FORMATS.includes(value);
@@ -79870,6 +80026,19 @@ function extractionFrameCountForDuration(durationSeconds, fps, isVFR) {
79870
80026
  const frames = Number(frameCount);
79871
80027
  return Math.max(1, Number.isSafeInteger(frames) ? frames : Number.MAX_SAFE_INTEGER);
79872
80028
  }
80029
+ async function toneMapsWithZscale() {
80030
+ return process.platform !== "darwin" || isFfmpegFilterAvailable("zscale");
80031
+ }
80032
+ async function hdrToSdrTransformKey() {
80033
+ if (await toneMapsWithZscale()) return HDR_TO_SDR_ZSCALE_TRANSFORM_KEY;
80034
+ if (!warnedMissingZscale) {
80035
+ warnedMissingZscale = true;
80036
+ process.stderr.write(
80037
+ "[hyperframes:render] WARNING: this ffmpeg has no zscale filter, so forced-SDR HDR footage is tone-mapped by Chrome instead of the hable curve used on Linux and in Studio, and renders darker. Install an ffmpeg built with libzimg to match.\n"
80038
+ );
80039
+ }
80040
+ return HDR_TO_SDR_VIDEOTOOLBOX_TRANSFORM_KEY;
80041
+ }
79873
80042
  function sdrToHdrTransformKey(transfer) {
79874
80043
  return `sdr2hdr-${transfer}`;
79875
80044
  }
@@ -80168,6 +80337,10 @@ function parseImageElements(html) {
80168
80337
  }
80169
80338
  return images;
80170
80339
  }
80340
+ async function readFirstFrameColour(videoPath, signal) {
80341
+ const result = await runFfmpeg(firstFrameColourArgs(videoPath), { signal });
80342
+ return parseFirstFrameColour(result.stderr);
80343
+ }
80171
80344
  function chromeGuessForUntaggedMatrix(metadata) {
80172
80345
  const matrix = metadata.colorSpace?.colorSpace;
80173
80346
  if (matrix && matrix !== "unknown") return [];
@@ -80190,7 +80363,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
80190
80363
  const fps = fpsToNumber(normalizedFps);
80191
80364
  const ffmpegFps = fpsToFfmpegArg(normalizedFps);
80192
80365
  const videoOutputDir = outputDirOverride ?? join13(outputDir, videoId);
80193
- if (!existsSync11(videoOutputDir)) mkdirSync6(videoOutputDir, { recursive: true });
80366
+ if (!existsSync11(videoOutputDir)) mkdirSync7(videoOutputDir, { recursive: true });
80194
80367
  let metadata;
80195
80368
  try {
80196
80369
  metadata = await extractMediaMetadata(videoPath);
@@ -80219,8 +80392,11 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
80219
80392
  const outputPattern = join13(videoOutputDir, framePattern);
80220
80393
  const isHdr = isHdrColorSpace(metadata.colorSpace);
80221
80394
  const isMacOS = process.platform === "darwin";
80395
+ const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
80396
+ const decodeWithVideoToolbox = isHdr && isMacOS && !toneMappedToSdr;
80397
+ const sampleCfrAtOutputFps = !options.finalFrameOnly && !metadata.isVFR;
80222
80398
  const args = [];
80223
- if (isHdr && isMacOS) {
80399
+ if (decodeWithVideoToolbox) {
80224
80400
  args.push("-hwaccel", "videotoolbox");
80225
80401
  }
80226
80402
  if (codecMayHaveAlpha(metadata.videoCodec)) {
@@ -80229,23 +80405,32 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
80229
80405
  if (options.finalFrameOnly) {
80230
80406
  args.push("-i", videoPath, "-ss", String(startTime), "-frames:v", "1");
80231
80407
  } else {
80408
+ if (sampleCfrAtOutputFps) args.push("-noaccurate_seek");
80232
80409
  args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
80233
80410
  }
80234
80411
  const vfFilters = [];
80235
- if (isHdr && isMacOS) {
80412
+ if (decodeWithVideoToolbox) {
80236
80413
  vfFilters.push("format=nv12");
80237
80414
  }
80238
- if (!options.finalFrameOnly && !metadata.isVFR) {
80239
- vfFilters.push(`fps=${ffmpegFps}`);
80415
+ if (sampleCfrAtOutputFps) {
80416
+ vfFilters.push(
80417
+ "settb=intb/2",
80418
+ "setpts=PTS-lte(TB\\,0.001)",
80419
+ `fps=${ffmpegFps}:start_time=0:round=up`
80420
+ );
80240
80421
  }
80241
80422
  if (options.sdrToHdrTransfer) {
80242
80423
  vfFilters.push(SDR_TO_HDR_COLORSPACE_FILTER);
80243
80424
  }
80244
- if (options.toneMapHdrToSdr && isHdr && !isMacOS) {
80245
- vfFilters.push(HDR_TO_SDR_TONEMAP_FILTER);
80425
+ if (toneMappedToSdr) {
80426
+ const frameColour = await readFirstFrameColour(videoPath, signal);
80427
+ vfFilters.push(hdrToSdrToneMapFilter(metadata.colorSpace ?? {}, frameColour));
80246
80428
  }
80247
- if (!isHdr && !options.sdrToHdrTransfer) {
80248
- vfFilters.push(...chromeGuessForUntaggedMatrix(metadata), SDR_CANVAS_PASSTHROUGH_FILTER);
80429
+ if (toneMappedToSdr || !isHdr && !options.sdrToHdrTransfer) {
80430
+ vfFilters.push(
80431
+ ...isHdr ? [] : chromeGuessForUntaggedMatrix(metadata),
80432
+ SDR_CANVAS_PASSTHROUGH_FILTER
80433
+ );
80249
80434
  if (format === "jpg") vfFilters.push(SDR_JPEG_AS_BT601_FULL_RANGE_FILTER);
80250
80435
  }
80251
80436
  const encodeArgs = ["-q:v", format === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0"];
@@ -80579,7 +80764,7 @@ function supersetGroupingKey(work, fps) {
80579
80764
  String(fps),
80580
80765
  work.format,
80581
80766
  work.sdrToHdrTransfer ?? "",
80582
- work.toneMapHdrToSdr ? HDR_TO_SDR_TRANSFORM_KEY : "",
80767
+ work.hdrToSdrTransformKey ?? "",
80583
80768
  work.finalFrameOnly ? "final" : "range"
80584
80769
  ].join("\0");
80585
80770
  }
@@ -80661,7 +80846,7 @@ function planSupersetGroups(misses, fps) {
80661
80846
  function sliceSupersetMember(member, superset, outputDir, fps, configuredFps) {
80662
80847
  const { work } = member.miss;
80663
80848
  rmSync3(outputDir, { recursive: true, force: true });
80664
- mkdirSync6(outputDir, { recursive: true });
80849
+ mkdirSync7(outputDir, { recursive: true });
80665
80850
  const requestedFrames = extractionFrameCountForDuration(
80666
80851
  work.videoDuration,
80667
80852
  configuredFps,
@@ -80722,7 +80907,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80722
80907
  }
80723
80908
  if (isHttpUrl(videoPath)) {
80724
80909
  const downloadDir = join13(options.outputDir, "_downloads");
80725
- mkdirSync6(downloadDir, { recursive: true });
80910
+ mkdirSync7(downloadDir, { recursive: true });
80726
80911
  videoPath = await downloadToTemp(
80727
80912
  videoPath,
80728
80913
  downloadDir,
@@ -80862,7 +81047,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80862
81047
  let cacheRootDir;
80863
81048
  if (configuredCacheRootDir) {
80864
81049
  try {
80865
- mkdirSync6(configuredCacheRootDir, { recursive: true });
81050
+ mkdirSync7(configuredCacheRootDir, { recursive: true });
80866
81051
  cacheRootDir = configuredCacheRootDir;
80867
81052
  } catch {
80868
81053
  process.stderr.write(
@@ -80885,7 +81070,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80885
81070
  ...options,
80886
81071
  format: work.format,
80887
81072
  sdrToHdrTransfer: work.sdrToHdrTransfer,
80888
- toneMapHdrToSdr: work.toneMapHdrToSdr,
81073
+ toneMapHdrToSdr: work.hdrToSdrTransformKey !== void 0,
80889
81074
  finalFrameOnly: work.finalFrameOnly
80890
81075
  };
80891
81076
  }
@@ -80905,7 +81090,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80905
81090
  if (!keyInput) return { work };
80906
81091
  const transformParts = [
80907
81092
  work.sdrToHdrTransfer ? sdrToHdrTransformKey(work.sdrToHdrTransfer) : void 0,
80908
- work.toneMapHdrToSdr ? HDR_TO_SDR_TRANSFORM_KEY : void 0,
81093
+ work.hdrToSdrTransformKey,
80909
81094
  work.finalFrameOnly ? "final-frame" : void 0
80910
81095
  ].filter((part) => part !== void 0);
80911
81096
  const transform = transformParts.length > 0 ? transformParts.join("+") : void 0;
@@ -80956,7 +81141,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80956
81141
  }
80957
81142
  const partialDir = partialCacheEntryDir(cacheTarget.entry);
80958
81143
  rmSync3(partialDir, { recursive: true, force: true });
80959
- mkdirSync6(partialDir, { recursive: true });
81144
+ mkdirSync7(partialDir, { recursive: true });
80960
81145
  const attempted = await runVideoExtractionWithRetry(
80961
81146
  () => extractVideoFramesRange(
80962
81147
  work.videoPath,
@@ -80974,7 +81159,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
80974
81159
  onRetry: () => {
80975
81160
  recordTransientRetries(1);
80976
81161
  rmSync3(partialDir, { recursive: true, force: true });
80977
- mkdirSync6(partialDir, { recursive: true });
81162
+ mkdirSync7(partialDir, { recursive: true });
80978
81163
  }
80979
81164
  }
80980
81165
  );
@@ -81080,9 +81265,9 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
81080
81265
  if (keyInput) keyInput.mediaStart = extractionMediaStart;
81081
81266
  const format = resolveFrameFormat(metadata, options.format);
81082
81267
  const sdrToHdrTransfer = sdrToHdrTransfers[index];
81083
- const toneMapHdrToSdr = options.toneMapHdrToSdr === true && isHdrColorSpace(metadata.colorSpace);
81268
+ const hdrToSdrTransform = options.toneMapHdrToSdr === true && isHdrColorSpace(metadata.colorSpace) ? await hdrToSdrTransformKey() : void 0;
81084
81269
  const finalFrameOnly = window3.finalFrameOnly === true;
81085
- const dedupeKey = `${videoPath}\0${extractionMediaStart}\0${videoDuration}\0${fpsKey}\0${format}\0${sdrToHdrTransfer ?? ""}\0${toneMapHdrToSdr ? HDR_TO_SDR_TRANSFORM_KEY : ""}\0${finalFrameOnly ? "final" : "range"}`;
81270
+ const dedupeKey = `${videoPath}\0${extractionMediaStart}\0${videoDuration}\0${fpsKey}\0${format}\0${sdrToHdrTransfer ?? ""}\0${hdrToSdrTransform ?? ""}\0${finalFrameOnly ? "final" : "range"}`;
81086
81271
  return {
81087
81272
  work: {
81088
81273
  video,
@@ -81094,7 +81279,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
81094
81279
  finalFrameOnly,
81095
81280
  format,
81096
81281
  sdrToHdrTransfer,
81097
- toneMapHdrToSdr,
81282
+ hdrToSdrTransformKey: hdrToSdrTransform,
81098
81283
  dedupeKey
81099
81284
  }
81100
81285
  };
@@ -81237,7 +81422,7 @@ function createFrameLookupTable(videos, extracted, fps) {
81237
81422
  }
81238
81423
  return table;
81239
81424
  }
81240
- var VIDEO_FRAME_FORMATS, EXTRACT_CACHE_MIN_AGE_MS, GC_STALENESS_MS, SDR_TO_HDR_COLORSPACE_FILTER, HDR_TO_SDR_TONEMAP_FILTER, HDR_TO_SDR_TRANSFORM_KEY, SDR_CANVAS_PASSTHROUGH_FILTER, SDR_JPEG_AS_BT601_FULL_RANGE_FILTER, VideoSourceExtractionError, CHROME_BT601_UNTAGGED_CODECS, NUT_RAW_PIXEL_FORMATS, TRANSIENT_FFMPEG_SPAWN_CODES, FINAL_FRAME_LOGICAL_DURATION_SECONDS, ALPHA_CAPABLE_CODECS, FrameLookupTable;
81425
+ var VIDEO_FRAME_FORMATS, EXTRACT_CACHE_MIN_AGE_MS, GC_STALENESS_MS, SDR_TO_HDR_COLORSPACE_FILTER, HDR_TO_SDR_ZSCALE_TRANSFORM_KEY, HDR_TO_SDR_VIDEOTOOLBOX_TRANSFORM_KEY, SDR_CANVAS_PASSTHROUGH_FILTER, SDR_JPEG_AS_BT601_FULL_RANGE_FILTER, warnedMissingZscale, VideoSourceExtractionError, CHROME_BT601_UNTAGGED_CODECS, NUT_RAW_PIXEL_FORMATS, TRANSIENT_FFMPEG_SPAWN_CODES, FINAL_FRAME_LOGICAL_DURATION_SECONDS, ALPHA_CAPABLE_CODECS, FrameLookupTable;
81241
81426
  var init_videoFrameExtractor = __esm({
81242
81427
  "../engine/src/services/videoFrameExtractor.ts"() {
81243
81428
  "use strict";
@@ -81250,6 +81435,7 @@ var init_videoFrameExtractor = __esm({
81250
81435
  init_hdr();
81251
81436
  init_urlDownloader();
81252
81437
  init_runFfmpeg();
81438
+ init_psnrFilterAvailability();
81253
81439
  init_config();
81254
81440
  init_htmlTemplate();
81255
81441
  init_extractionCache();
@@ -81258,10 +81444,11 @@ var init_videoFrameExtractor = __esm({
81258
81444
  EXTRACT_CACHE_MIN_AGE_MS = 60 * 60 * 1e3;
81259
81445
  GC_STALENESS_MS = 24 * 60 * 60 * 1e3;
81260
81446
  SDR_TO_HDR_COLORSPACE_FILTER = "colorspace=all=bt2020:iall=bt709:range=tv";
81261
- HDR_TO_SDR_TONEMAP_FILTER = "zscale=t=linear:npl=100,tonemap=hable:desat=0,zscale=p=bt709:t=bt709:m=bt709:r=tv";
81262
- HDR_TO_SDR_TRANSFORM_KEY = "hdr2sdr-hable-bt709";
81447
+ HDR_TO_SDR_ZSCALE_TRANSFORM_KEY = "hdr2sdr-hable-srgb";
81448
+ HDR_TO_SDR_VIDEOTOOLBOX_TRANSFORM_KEY = "hdr2sdr-videotoolbox-hdr";
81263
81449
  SDR_CANVAS_PASSTHROUGH_FILTER = "setparams=color_primaries=bt709:color_trc=iec61966-2-1";
81264
81450
  SDR_JPEG_AS_BT601_FULL_RANGE_FILTER = "scale=flags=neighbor,format=gbrp,scale=out_color_matrix=bt601:out_range=pc:flags=neighbor,format=yuv420p";
81451
+ warnedMissingZscale = false;
81265
81452
  VideoSourceExtractionError = class extends Error {
81266
81453
  constructor(kind, retryable, message, diagnostic = message) {
81267
81454
  super(message);
@@ -81480,10 +81667,10 @@ function createFrameSourceCache(entryLimit, bytesLimit, frameSrcResolver) {
81480
81667
  async function get(framePath) {
81481
81668
  const servedSrc = frameSrcResolver?.(framePath);
81482
81669
  if (servedSrc) return servedSrc;
81483
- const cached2 = cache2.get(framePath);
81484
- if (cached2) {
81485
- remember(framePath, cached2);
81486
- return cached2;
81670
+ const cached = cache2.get(framePath);
81671
+ if (cached) {
81672
+ remember(framePath, cached);
81673
+ return cached;
81487
81674
  }
81488
81675
  const existing = inFlight.get(framePath);
81489
81676
  if (existing) {
@@ -82504,8 +82691,8 @@ var init_audioFxRender = __esm({
82504
82691
  });
82505
82692
 
82506
82693
  // ../engine/src/services/audioMixer.ts
82507
- import { closeSync as closeSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync5, openSync as openSync3, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
82508
- import { join as join16, dirname as dirname10, isAbsolute as isAbsolute2, relative as relative2 } from "path";
82694
+ import { closeSync as closeSync3, existsSync as existsSync13, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync5, openSync as openSync3, rmSync as rmSync6, writeFileSync as writeFileSync6 } from "fs";
82695
+ import { join as join16, dirname as dirname10, isAbsolute as isAbsolute3, relative as relative3 } from "path";
82509
82696
  function memberGroupKey(el) {
82510
82697
  return el.getAttribute(AUDIO_GROUP_RENDER_ID_ATTR) ?? el.getAttribute(HF_AUDIO_GROUP_ATTR);
82511
82698
  }
@@ -82687,8 +82874,8 @@ function buildTrackInputFilter(track, index, volumeFilter, totalDuration) {
82687
82874
  return `[${index}:a]atrim=0:${formatFilterNumber(trimDuration)},${volumeFilter}${buildFadeFilters(track)},adelay=${delayMs}|${delayMs},apad,asetpts=N/SR/TB,atrim=0:${formatFilterNumber(totalDuration)}[a${index}]`;
82688
82875
  }
82689
82876
  function missingSourceMessage(elementId, src, baseDir, resolvedPath) {
82690
- const relativePath = relative2(baseDir, resolvedPath).replace(/\\/g, "/");
82691
- const insideProject = !relativePath.startsWith("..") && !isAbsolute2(relativePath);
82877
+ const relativePath = relative3(baseDir, resolvedPath).replace(/\\/g, "/");
82878
+ const insideProject = !relativePath.startsWith("..") && !isAbsolute3(relativePath);
82692
82879
  const authored = /^([\\/]|[A-Za-z]:)/.test(src) ? "" : `src="${src}" `;
82693
82880
  return `Source not found for audio element ${elementId}: ${authored}resolved to ${insideProject ? relativePath : "a path outside the project"}`;
82694
82881
  }
@@ -82863,7 +83050,7 @@ function parseAudioElements(html) {
82863
83050
  async function extractAudioFromVideo(videoPath, outputPath, options, signal, config) {
82864
83051
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
82865
83052
  const outputDir = dirname10(outputPath);
82866
- if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
83053
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
82867
83054
  const playbackRate = normalizeRateSpec(options?.playbackRate);
82868
83055
  const args = [];
82869
83056
  if (options?.startTime !== void 0) args.push("-ss", String(options.startTime));
@@ -82909,7 +83096,7 @@ async function extractAudioFromVideo(videoPath, outputPath, options, signal, con
82909
83096
  async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, playbackRate = 1, signal, config) {
82910
83097
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
82911
83098
  const outputDir = dirname10(outputPath);
82912
- if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
83099
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
82913
83100
  const normalizedPlaybackRate = normalizeRateSpec(playbackRate);
82914
83101
  const outputArgs = await preparedAudioOutputArgs(srcPath, normalizedPlaybackRate, duration);
82915
83102
  const args = [
@@ -82956,7 +83143,7 @@ async function prepareAudioTrack(srcPath, outputPath, mediaStart, duration, play
82956
83143
  async function generateSilence(outputPath, duration, signal, config) {
82957
83144
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
82958
83145
  const outputDir = dirname10(outputPath);
82959
- if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
83146
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
82960
83147
  const args = [
82961
83148
  "-f",
82962
83149
  "lavfi",
@@ -83010,7 +83197,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
83010
83197
  };
83011
83198
  }
83012
83199
  const outputDir = dirname10(outputPath);
83013
- if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
83200
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
83014
83201
  const buildFilterComplex = (ignoreAutomation) => {
83015
83202
  const filterParts = [];
83016
83203
  tracks.forEach((track, i) => {
@@ -83114,7 +83301,7 @@ function groupNormalizeOptionUnsupported(stderr) {
83114
83301
  async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal, config) {
83115
83302
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
83116
83303
  const outputDir = dirname10(outputPath);
83117
- if (!existsSync13(outputDir)) mkdirSync7(outputDir, { recursive: true });
83304
+ if (!existsSync13(outputDir)) mkdirSync8(outputDir, { recursive: true });
83118
83305
  const buildInputFilters = (ignoreKeyframes) => memberTracks.map((track, i) => {
83119
83306
  const volumeFilter = buildVolumeExpression(track, ignoreKeyframes);
83120
83307
  return buildTrackInputFilter(track, i, volumeFilter, totalDuration);
@@ -83201,7 +83388,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
83201
83388
  const failures = [];
83202
83389
  const groupTracks = /* @__PURE__ */ new Map();
83203
83390
  const groupMeta = /* @__PURE__ */ new Map();
83204
- if (!existsSync13(workDir)) mkdirSync7(workDir, { recursive: true });
83391
+ if (!existsSync13(workDir)) mkdirSync8(workDir, { recursive: true });
83205
83392
  const internalController = new AbortController();
83206
83393
  const effectiveSignal = internalController.signal;
83207
83394
  if (signal) {
@@ -83702,7 +83889,7 @@ var init_readWebGlVendorInfoFromCanvas = __esm({
83702
83889
 
83703
83890
  // ../engine/src/services/parallelCoordinator.ts
83704
83891
  import { cpus, freemem } from "os";
83705
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync6 } from "fs";
83892
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync6 } from "fs";
83706
83893
  import { copyFile, readFile as readFile2, rename } from "fs/promises";
83707
83894
  import { join as join18 } from "path";
83708
83895
  import { getHeapStatistics } from "v8";
@@ -84055,7 +84242,7 @@ async function verifyDiskDrawElementSamples(session, task, streaming) {
84055
84242
  async function executeWorkerTask(task, serverUrl, captureOptions, createBeforeCaptureHook, signal, onFrameCaptured, onFrameBuffer, config, parallel, onFailure, onWorkerPhase) {
84056
84243
  const startTime = Date.now();
84057
84244
  let framesCaptured = 0;
84058
- if (!existsSync14(task.outputDir)) mkdirSync8(task.outputDir, { recursive: true });
84245
+ if (!existsSync14(task.outputDir)) mkdirSync9(task.outputDir, { recursive: true });
84059
84246
  let session = null;
84060
84247
  let perf;
84061
84248
  const needsSeparateBrowsers = shouldDisableBrowserPoolForParallelWorker({
@@ -84274,7 +84461,7 @@ async function executeParallelCapture(serverUrl, workDir, tasks, captureOptions,
84274
84461
  return results;
84275
84462
  }
84276
84463
  async function mergeWorkerFrames(workDir, tasks, outputDir) {
84277
- if (!existsSync14(outputDir)) mkdirSync8(outputDir, { recursive: true });
84464
+ if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
84278
84465
  let totalFrames = 0;
84279
84466
  const sortedTasks = [...tasks].sort((a, b) => a.startFrame - b.startFrame);
84280
84467
  for (const task of sortedTasks) {
@@ -84329,7 +84516,7 @@ var init_parallelCoordinator = __esm({
84329
84516
  // ../engine/src/services/fileServer.ts
84330
84517
  import { Hono } from "hono";
84331
84518
  import { serve } from "@hono/node-server";
84332
- import { readFileSync as readFileSync7, openSync as openSync4, fstatSync, closeSync as closeSync4, statSync as statSync6, constants as constants2 } from "fs";
84519
+ import { readFileSync as readFileSync7, openSync as openSync4, fstatSync, closeSync as closeSync4, statSync as statSync7, constants as constants2 } from "fs";
84333
84520
  import { join as join19, extname as extname3 } from "path";
84334
84521
  function readRegularFile(filePath) {
84335
84522
  let fd;
@@ -84338,7 +84525,7 @@ function readRegularFile(filePath) {
84338
84525
  } catch (error) {
84339
84526
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
84340
84527
  return null;
84341
- if (!statSync6(filePath, { throwIfNoEntry: false })?.isFile()) return null;
84528
+ if (!statSync7(filePath, { throwIfNoEntry: false })?.isFile()) return null;
84342
84529
  throw error;
84343
84530
  }
84344
84531
  try {
@@ -88911,7 +89098,7 @@ __export(fontCompression_exports, {
88911
89098
  import { createHash as createHash8 } from "crypto";
88912
89099
  import {
88913
89100
  existsSync as existsSync23,
88914
- mkdirSync as mkdirSync13,
89101
+ mkdirSync as mkdirSync14,
88915
89102
  mkdtempSync as mkdtempSync8,
88916
89103
  readFileSync as readFileSync11,
88917
89104
  renameSync as renameSync5,
@@ -88937,8 +89124,8 @@ function cachedCompressionPath(input, originalFormat, cacheDir) {
88937
89124
  function readCachedCompression(path) {
88938
89125
  try {
88939
89126
  if (!existsSync23(path)) return null;
88940
- const cached2 = readFileSync11(path);
88941
- return cached2.length > 0 ? cached2 : null;
89127
+ const cached = readFileSync11(path);
89128
+ return cached.length > 0 ? cached : null;
88942
89129
  } catch {
88943
89130
  return null;
88944
89131
  }
@@ -88946,7 +89133,7 @@ function readCachedCompression(path) {
88946
89133
  function cacheCompression(path, compressed) {
88947
89134
  let tempDir;
88948
89135
  try {
88949
- mkdirSync13(dirname14(path), { recursive: true });
89136
+ mkdirSync14(dirname14(path), { recursive: true });
88950
89137
  tempDir = mkdtempSync8(join32(dirname14(path), ".compression-"));
88951
89138
  const tmpPath = join32(tempDir, "font.woff2");
88952
89139
  writeFileSync9(tmpPath, compressed, { flag: "wx", mode: 420 });
@@ -88972,8 +89159,8 @@ async function fontToDataUri(input, originalFormat, options = {}) {
88972
89159
  originalFormat,
88973
89160
  options.cacheDir ?? defaultCacheDir()
88974
89161
  );
88975
- const cached2 = readCachedCompression(cachePath);
88976
- if (cached2) return `data:font/woff2;base64,${cached2.toString("base64")}`;
89162
+ const cached = readCachedCompression(cachePath);
89163
+ if (cached) return `data:font/woff2;base64,${cached.toString("base64")}`;
88977
89164
  const compressed = await (options.compressImpl ?? compressToWoff2)(input);
88978
89165
  cacheCompression(cachePath, compressed);
88979
89166
  return `data:font/woff2;base64,${compressed.toString("base64")}`;
@@ -89005,13 +89192,13 @@ init_dist();
89005
89192
  init_src();
89006
89193
  import {
89007
89194
  existsSync as existsSync33,
89008
- mkdirSync as mkdirSync19,
89195
+ mkdirSync as mkdirSync20,
89009
89196
  mkdtempSync as mkdtempSync11,
89010
- readFileSync as readFileSync15,
89197
+ readFileSync as readFileSync16,
89011
89198
  readdirSync as readdirSync12,
89012
89199
  rmSync as rmSync15,
89013
- statSync as statSync12,
89014
- writeFileSync as writeFileSync11,
89200
+ statSync as statSync13,
89201
+ writeFileSync as writeFileSync12,
89015
89202
  copyFileSync as copyFileSync6,
89016
89203
  appendFileSync
89017
89204
  } from "fs";
@@ -89029,7 +89216,7 @@ import { serve as serve2 } from "@hono/node-server";
89029
89216
  import {
89030
89217
  existsSync as existsSync17,
89031
89218
  realpathSync as realpathSync2,
89032
- statSync as statSync7,
89219
+ statSync as statSync8,
89033
89220
  createReadStream as createReadStream3,
89034
89221
  openSync as openSync5,
89035
89222
  fstatSync as fstatSync2,
@@ -89620,7 +89807,7 @@ function openRegularFile(filePath) {
89620
89807
  } catch (error) {
89621
89808
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
89622
89809
  return null;
89623
- if (!statSync7(filePath, { throwIfNoEntry: false })?.isFile()) return null;
89810
+ if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
89624
89811
  throw error;
89625
89812
  }
89626
89813
  let accepted = false;
@@ -89797,12 +89984,12 @@ import {
89797
89984
  copyFileSync as copyFileSync3,
89798
89985
  cpSync,
89799
89986
  existsSync as existsSync18,
89800
- mkdirSync as mkdirSync9,
89987
+ mkdirSync as mkdirSync10,
89801
89988
  rmSync as rmSync7,
89802
89989
  symlinkSync,
89803
89990
  writeFileSync as writeFileSync7
89804
89991
  } from "fs";
89805
- import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute3, join as join23, relative as relative3, resolve as resolve8 } from "path";
89992
+ import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute4, join as join23, relative as relative4, resolve as resolve8 } from "path";
89806
89993
 
89807
89994
  // ../producer/src/utils/paths.ts
89808
89995
  import {
@@ -89883,11 +90070,11 @@ function resolveDeviceScaleFactor(input) {
89883
90070
  }
89884
90071
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
89885
90072
  const compileDir = join23(workDir, "compiled");
89886
- mkdirSync9(compileDir, { recursive: true });
90073
+ mkdirSync10(compileDir, { recursive: true });
89887
90074
  writeFileSync7(join23(compileDir, "index.html"), compiled.html, "utf-8");
89888
90075
  for (const [srcPath, html] of compiled.subCompositions) {
89889
90076
  const outPath = join23(compileDir, srcPath);
89890
- mkdirSync9(dirname12(outPath), { recursive: true });
90077
+ mkdirSync10(dirname12(outPath), { recursive: true });
89891
90078
  writeFileSync7(outPath, html, "utf-8");
89892
90079
  }
89893
90080
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
@@ -89896,7 +90083,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
89896
90083
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
89897
90084
  continue;
89898
90085
  }
89899
- mkdirSync9(dirname12(outPath), { recursive: true });
90086
+ mkdirSync10(dirname12(outPath), { recursive: true });
89900
90087
  copyFileSync3(absolutePath, outPath);
89901
90088
  }
89902
90089
  if (includeSummary) {
@@ -89984,12 +90171,12 @@ var materializePathModule = {
89984
90171
  join: join23,
89985
90172
  dirname: dirname12,
89986
90173
  basename: basename4,
89987
- relative: relative3,
89988
- isAbsolute: isAbsolute3
90174
+ relative: relative4,
90175
+ isAbsolute: isAbsolute4
89989
90176
  };
89990
90177
  var materializeFileSystem = {
89991
90178
  existsSync: existsSync18,
89992
- mkdirSync: mkdirSync9,
90179
+ mkdirSync: mkdirSync10,
89993
90180
  symlinkSync,
89994
90181
  cpSync,
89995
90182
  rmSync: rmSync7
@@ -90645,7 +90832,7 @@ function replanAfterFailure(plan2, failure) {
90645
90832
 
90646
90833
  // ../producer/src/services/render/stages/captureSegmentedStage.ts
90647
90834
  init_src();
90648
- import { existsSync as existsSync20, mkdirSync as mkdirSync11, rmSync as rmSync10, statSync as statSync8 } from "fs";
90835
+ import { existsSync as existsSync20, mkdirSync as mkdirSync12, rmSync as rmSync10, statSync as statSync9 } from "fs";
90649
90836
  import { join as join28 } from "path";
90650
90837
 
90651
90838
  // ../producer/src/services/render/captureStageError.ts
@@ -91235,7 +91422,7 @@ import {
91235
91422
  closeSync as closeSync8,
91236
91423
  constants as constants4,
91237
91424
  fstatSync as fstatSync4,
91238
- mkdirSync as mkdirSync10,
91425
+ mkdirSync as mkdirSync11,
91239
91426
  mkdtempSync as mkdtempSync7,
91240
91427
  openSync as openSync7,
91241
91428
  readFileSync as readFileSync9,
@@ -91447,7 +91634,7 @@ async function extractHdrVideoFrames(args) {
91447
91634
  const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata;
91448
91635
  const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
91449
91636
  const out = /* @__PURE__ */ new Map();
91450
- mkdirSync10(framesDir, { recursive: true });
91637
+ mkdirSync11(framesDir, { recursive: true });
91451
91638
  const plannedVideos = [];
91452
91639
  const extractionWindows = /* @__PURE__ */ new Map();
91453
91640
  for (const [videoId, srcPath] of prep.hdrVideoSrcPaths) {
@@ -91485,7 +91672,7 @@ async function extractHdrVideoFrames(args) {
91485
91672
  const video = composition.videos.find((v) => v.id === videoId);
91486
91673
  const window3 = extractionWindows.get(videoId);
91487
91674
  if (!video || !window3) continue;
91488
- mkdirSync10(framesDir, { recursive: true });
91675
+ mkdirSync11(framesDir, { recursive: true });
91489
91676
  const frameDir = mkdtempSync7(join26(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
91490
91677
  createdFrameDirs.add(frameDir);
91491
91678
  const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
@@ -92769,7 +92956,7 @@ async function captureSegment(ctx, segment) {
92769
92956
  path: segmentPath,
92770
92957
  // Recorded so resume can prove the file on disk is the one that
92771
92958
  // finished; absent only in unit tests, whose fake encoder writes none.
92772
- bytes: existsSync20(segmentPath) ? statSync8(segmentPath).size : 0
92959
+ bytes: existsSync20(segmentPath) ? statSync9(segmentPath).size : 0
92773
92960
  });
92774
92961
  return encodeResult.durationMs;
92775
92962
  } finally {
@@ -92950,7 +93137,7 @@ async function runCaptureSegmentedStage(input) {
92950
93137
  const deps = { ...DEFAULT_DEPS, ...input.deps };
92951
93138
  const segments = planSegments(input.totalFrames, input.segmentFrames);
92952
93139
  const segmentDir = input.segmentDir ?? join28(input.workDir, "segments");
92953
- mkdirSync11(segmentDir, { recursive: true });
93140
+ mkdirSync12(segmentDir, { recursive: true });
92954
93141
  const skip = input.completedSegments ?? /* @__PURE__ */ new Set();
92955
93142
  const pending = segments.filter((s) => !skip.has(s.index));
92956
93143
  for (const index of skip) log.info("[Render] segment skipped (resume)", { index });
@@ -93022,7 +93209,7 @@ async function runCaptureSegmentedStage(input) {
93022
93209
 
93023
93210
  // ../producer/src/services/render/segmentManifest.ts
93024
93211
  import { createHash as createHash6 } from "crypto";
93025
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync10, renameSync as renameSync4, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
93212
+ import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync10, renameSync as renameSync4, statSync as statSync10, writeFileSync as writeFileSync8 } from "fs";
93026
93213
  import { join as join29 } from "path";
93027
93214
  var MANIFEST_FILENAME = "segments.json";
93028
93215
  function computeSegmentPlanHash(input) {
@@ -93074,14 +93261,14 @@ function readSegmentManifest(segmentDir) {
93074
93261
  }
93075
93262
  }
93076
93263
  function writeSegmentManifest(segmentDir, manifest) {
93077
- mkdirSync12(segmentDir, { recursive: true });
93264
+ mkdirSync13(segmentDir, { recursive: true });
93078
93265
  const tmp = join29(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
93079
93266
  writeFileSync8(tmp, JSON.stringify(manifest, null, 2), "utf-8");
93080
93267
  renameSync4(tmp, join29(segmentDir, MANIFEST_FILENAME));
93081
93268
  }
93082
- async function probeSegmentFrameCount(path, probe2) {
93269
+ async function probeSegmentFrameCount(path, probe) {
93083
93270
  try {
93084
- const meta = await probe2(path);
93271
+ const meta = await probe(path);
93085
93272
  if (!Number.isFinite(meta.videoStreamDurationSeconds) || !Number.isFinite(meta.fps))
93086
93273
  return null;
93087
93274
  if (meta.fps <= 0) return null;
@@ -93095,7 +93282,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
93095
93282
  if (manifest.planHash !== expectedHash) return ok;
93096
93283
  for (const entry of manifest.completed) {
93097
93284
  if (!existsSync21(entry.path)) continue;
93098
- const size = statSync9(entry.path).size;
93285
+ const size = statSync10(entry.path).size;
93099
93286
  if (size <= 0 || size !== entry.bytes) continue;
93100
93287
  const frames = await probeFrames(entry.path);
93101
93288
  if (frames !== entry.endFrame - entry.startFrame) continue;
@@ -93889,8 +94076,8 @@ init_compiler();
93889
94076
  init_subCompositionValidity();
93890
94077
  init_assetResolution();
93891
94078
  import { createHash as createHash11 } from "crypto";
93892
- import { createReadStream as createReadStream4, existsSync as existsSync27, mkdirSync as mkdirSync16, readFileSync as readFileSync14 } from "fs";
93893
- import { join as join35, dirname as dirname16, resolve as resolve12, basename as basename6, relative as relative4 } from "path";
94079
+ import { createReadStream as createReadStream4, existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync14 } from "fs";
94080
+ import { join as join35, dirname as dirname16, resolve as resolve12, basename as basename6, relative as relative5 } from "path";
93894
94081
 
93895
94082
  // ../producer/src/services/renderMediaCollector.ts
93896
94083
  init_esm10();
@@ -93993,7 +94180,7 @@ init_src();
93993
94180
 
93994
94181
  // ../producer/src/services/deterministicFonts.ts
93995
94182
  import { createHash as createHash9 } from "crypto";
93996
- import { existsSync as existsSync24, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
94183
+ import { existsSync as existsSync24, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
93997
94184
  import { homedir as homedir5, tmpdir as tmpdir6 } from "os";
93998
94185
  import { join as join33 } from "path";
93999
94186
 
@@ -94269,7 +94456,80 @@ function dedupeVariants(variants) {
94269
94456
  init_esm10();
94270
94457
  init_fontData_generated();
94271
94458
  init_fontCompression();
94459
+ import postcss5 from "postcss";
94460
+
94461
+ // ../producer/src/services/authoredGoogleFonts.ts
94462
+ init_esm10();
94272
94463
  import postcss4 from "postcss";
94464
+ function screenStylesheet(media) {
94465
+ return !media?.trim() || media.split(",").some((query2) => /^(all|screen)$/i.test(query2.trim()));
94466
+ }
94467
+ function googleStylesheetFamilies(urlText) {
94468
+ let parsed;
94469
+ try {
94470
+ parsed = new URL(urlText);
94471
+ } catch {
94472
+ return [];
94473
+ }
94474
+ if (parsed.protocol !== "https:" || parsed.hostname !== "fonts.googleapis.com") return [];
94475
+ return parsed.searchParams.getAll("family").flatMap((family) => family.split("|")).map((family) => family.split(":")[0]?.trim().toLowerCase() ?? "").filter(Boolean);
94476
+ }
94477
+ function importUrls(css) {
94478
+ let root;
94479
+ try {
94480
+ root = postcss4.parse(css);
94481
+ } catch {
94482
+ return [];
94483
+ }
94484
+ const urls = [];
94485
+ for (const node of root.nodes) {
94486
+ if (node.type !== "atrule" || node.name.toLowerCase() !== "import") continue;
94487
+ const url = unconditionalImportUrl(node.params);
94488
+ if (url) urls.push(url);
94489
+ }
94490
+ return urls;
94491
+ }
94492
+ function unconditionalImportUrl(params) {
94493
+ const match = /^(?:url\(\s*['"]?([^'")\s]+)['"]?\s*\)|['"]([^'"]+)['"])\s*(.*)$/i.exec(params);
94494
+ if (!match || !screenStylesheet(match[3] ?? null)) return void 0;
94495
+ return match[1] ?? match[2];
94496
+ }
94497
+ function activeStylesheet(element) {
94498
+ if (element.hasAttribute("disabled") || !screenStylesheet(element.getAttribute("media")))
94499
+ return false;
94500
+ const type = element.getAttribute("type")?.trim().toLowerCase();
94501
+ return !type || type === "text/css";
94502
+ }
94503
+ function stylesheetUrls(element) {
94504
+ if (!activeStylesheet(element)) return [];
94505
+ if (element.localName !== "link") return importUrls(element.textContent ?? "");
94506
+ const rel = (element.getAttribute("rel") ?? "").toLowerCase().split(/\s+/);
94507
+ if (!rel.includes("stylesheet") || rel.includes("alternate")) return [];
94508
+ const href = element.getAttribute("href");
94509
+ return href ? [href] : [];
94510
+ }
94511
+ function authoredGoogleFontStylesheets(html) {
94512
+ const byFamily = /* @__PURE__ */ new Map();
94513
+ const { document: document2 } = parseHTML(html);
94514
+ for (const element of document2.querySelectorAll("link, style")) {
94515
+ for (const url of stylesheetUrls(element)) {
94516
+ for (const family of googleStylesheetFamilies(url)) {
94517
+ const previous = byFamily.get(family) ?? [];
94518
+ byFamily.set(family, [...previous.filter((existing) => existing !== url), url]);
94519
+ }
94520
+ }
94521
+ }
94522
+ return byFamily;
94523
+ }
94524
+ function withPageText(url, fontText) {
94525
+ const parsed = new URL(url);
94526
+ if (!fontText || parsed.searchParams.has("text")) return url;
94527
+ const withoutFragment = url.split("#")[0] ?? url;
94528
+ const joiner = parsed.search ? "&" : "?";
94529
+ return `${withoutFragment}${joiner}text=${encodeURIComponent(fontText)}${parsed.hash}`;
94530
+ }
94531
+
94532
+ // ../producer/src/services/deterministicFonts.ts
94273
94533
  var GENERIC_FAMILIES2 = /* @__PURE__ */ new Set([
94274
94534
  "sans-serif",
94275
94535
  "serif",
@@ -94286,6 +94546,13 @@ var GENERIC_FAMILIES2 = /* @__PURE__ */ new Set([
94286
94546
  "-apple-system",
94287
94547
  "blinkmacsystemfont"
94288
94548
  ]);
94549
+ var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set([
94550
+ "inherit",
94551
+ "initial",
94552
+ "unset",
94553
+ "revert",
94554
+ "revert-layer"
94555
+ ]);
94289
94556
  function parseFontFamilyValue(value) {
94290
94557
  const pieces = [];
94291
94558
  let start = 0;
@@ -94320,7 +94587,17 @@ function parseFontFamilyValue(value) {
94320
94587
  pieces.push(value.slice(start));
94321
94588
  return pieces.map((piece) => piece.trim().replace(/^['"]/, "").replace(/['"]$/, "").trim()).filter((piece) => piece.length > 0);
94322
94589
  }
94323
- function systemPrimaryReplacement(value, deterministicPrimary) {
94590
+ function systemPrimaryReplacement(value, deterministicPrimary, customProperties) {
94591
+ const variable = primaryCssVariable(value);
94592
+ if (variable) {
94593
+ if (customProperties.get(variable.name) || variable.fallback === null) return null;
94594
+ const fallback = systemPrimaryReplacement(
94595
+ variable.fallback,
94596
+ deterministicPrimary,
94597
+ customProperties
94598
+ );
94599
+ return fallback ? `var(${variable.name}, ${fallback})${variable.rest}` : null;
94600
+ }
94324
94601
  const families = parseFontFamilyValue(value);
94325
94602
  if (families.length === 0) return null;
94326
94603
  if (!GENERIC_FAMILIES2.has(normalizeFamilyName(families[0]))) return null;
@@ -94328,7 +94605,7 @@ function systemPrimaryReplacement(value, deterministicPrimary) {
94328
94605
  }
94329
94606
  function parseCssRoot(css) {
94330
94607
  try {
94331
- return postcss4.parse(css);
94608
+ return postcss5.parse(css);
94332
94609
  } catch {
94333
94610
  return null;
94334
94611
  }
@@ -94337,11 +94614,11 @@ function isFontFaceDeclaration(decl) {
94337
94614
  const parent = decl.parent;
94338
94615
  return parent?.type === "atrule" && parent.name.toLowerCase() === "font-face";
94339
94616
  }
94340
- function normalizeCssDeclarations(root, deterministicPrimary) {
94617
+ function normalizeCssDeclarations(root, replacePrimary) {
94341
94618
  let changed = false;
94342
94619
  root.walkDecls((decl) => {
94343
94620
  if (decl.prop.startsWith("--")) {
94344
- const replacement2 = systemPrimaryReplacement(decl.value, deterministicPrimary);
94621
+ const replacement2 = replacePrimary(decl.value);
94345
94622
  if (!replacement2) return;
94346
94623
  decl.value = replacement2;
94347
94624
  changed = true;
@@ -94351,50 +94628,52 @@ function normalizeCssDeclarations(root, deterministicPrimary) {
94351
94628
  if (isFontFaceDeclaration(decl)) {
94352
94629
  return;
94353
94630
  }
94354
- const replacement = systemPrimaryReplacement(decl.value, deterministicPrimary);
94631
+ const replacement = replacePrimary(decl.value);
94355
94632
  if (!replacement) return;
94356
94633
  decl.value = replacement;
94357
94634
  changed = true;
94358
94635
  });
94359
94636
  return changed;
94360
94637
  }
94361
- function normalizeCssFontFamilyDeclarations(css, deterministicPrimary) {
94638
+ function normalizeCssFontFamilyDeclarations(css, replacePrimary) {
94362
94639
  const root = parseCssRoot(css);
94363
94640
  if (!root) return css;
94364
- const changed = normalizeCssDeclarations(root, deterministicPrimary);
94641
+ const changed = normalizeCssDeclarations(root, replacePrimary);
94365
94642
  return changed ? root.toString() : css;
94366
94643
  }
94367
- function normalizeInlineStyleAttribute(style, deterministicPrimary) {
94644
+ function normalizeInlineStyleAttribute(style, replacePrimary) {
94368
94645
  const root = parseCssRoot(`*{${style}}`);
94369
94646
  if (!root) return style;
94370
94647
  const rule = root.first;
94371
94648
  if (rule?.type !== "rule") return style;
94372
94649
  const before2 = rule.toString();
94373
- normalizeCssDeclarations(root, deterministicPrimary);
94650
+ normalizeCssDeclarations(root, replacePrimary);
94374
94651
  if (rule.toString() === before2) return style;
94375
94652
  const serialized = (rule.nodes ?? []).map((node) => node.toString()).join("; ");
94376
94653
  return serialized.endsWith(";") ? serialized : `${serialized};`;
94377
94654
  }
94378
94655
  function normalizeSystemFontPrimaryFamilies(html, deterministicPrimary = "Inter") {
94379
94656
  const { document: document2 } = parseHTML(html);
94657
+ const customProperties = collectFontFamilyCustomProperties(html);
94658
+ const replacePrimary = (value) => systemPrimaryReplacement(value, deterministicPrimary, customProperties);
94380
94659
  let changed = false;
94381
94660
  for (const styleEl of Array.from(document2.querySelectorAll("style"))) {
94382
94661
  const current2 = styleEl.textContent ?? "";
94383
- const next = normalizeCssFontFamilyDeclarations(current2, deterministicPrimary);
94662
+ const next = normalizeCssFontFamilyDeclarations(current2, replacePrimary);
94384
94663
  if (next === current2) continue;
94385
94664
  styleEl.textContent = next;
94386
94665
  changed = true;
94387
94666
  }
94388
94667
  for (const el of Array.from(document2.querySelectorAll("[style]"))) {
94389
94668
  const current2 = el.getAttribute("style") ?? "";
94390
- const next = normalizeInlineStyleAttribute(current2, deterministicPrimary);
94669
+ const next = normalizeInlineStyleAttribute(current2, replacePrimary);
94391
94670
  if (next === current2) continue;
94392
94671
  el.setAttribute("style", next);
94393
94672
  changed = true;
94394
94673
  }
94395
94674
  for (const el of Array.from(document2.querySelectorAll("[data-font-family]"))) {
94396
94675
  const current2 = el.getAttribute("data-font-family") ?? "";
94397
- const next = systemPrimaryReplacement(current2, deterministicPrimary);
94676
+ const next = replacePrimary(current2);
94398
94677
  if (!next) continue;
94399
94678
  el.setAttribute("data-font-family", next);
94400
94679
  changed = true;
@@ -94444,7 +94723,7 @@ function collectFontFamilyCustomProperties(html) {
94444
94723
  }
94445
94724
  return customProperties;
94446
94725
  }
94447
- function primaryCssVariableName(value) {
94726
+ function primaryCssVariable(value) {
94448
94727
  const trimmed = value.trim();
94449
94728
  if (!trimmed.toLowerCase().startsWith("var(")) return null;
94450
94729
  let depth = 0;
@@ -94457,21 +94736,35 @@ function primaryCssVariableName(value) {
94457
94736
  if (char !== ")") continue;
94458
94737
  depth -= 1;
94459
94738
  if (depth !== 0) continue;
94460
- const varExpression = trimmed.slice(0, index + 1);
94461
- const inner = varExpression.slice(4, -1).trim();
94739
+ const inner = trimmed.slice(4, index).trim();
94462
94740
  const commaIndex = inner.indexOf(",");
94463
- const variableName = (commaIndex === -1 ? inner : inner.slice(0, commaIndex)).trim();
94464
- return /^--[A-Za-z0-9_-]+$/.test(variableName) ? variableName : null;
94741
+ const name = (commaIndex === -1 ? inner : inner.slice(0, commaIndex)).trim();
94742
+ if (!/^--[A-Za-z0-9_-]+$/.test(name)) return null;
94743
+ return {
94744
+ name,
94745
+ fallback: commaIndex === -1 ? null : inner.slice(commaIndex + 1),
94746
+ rest: trimmed.slice(index + 1)
94747
+ };
94465
94748
  }
94466
94749
  return null;
94467
94750
  }
94468
- function resolveFontFamilyDeclarationFamilies(declaration, customProperties) {
94751
+ var MAX_CSS_VARIABLE_DEPTH = 8;
94752
+ function resolveDeclaredFamilies(declaration, customProperties, optional = false, depth = 0) {
94753
+ const tag = (families2) => families2.map((family) => ({ family, optional }));
94469
94754
  const families = parseFontFamilyValue(declaration);
94470
- const variableName = primaryCssVariableName(declaration);
94471
- if (!variableName) return families;
94472
- const resolved = customProperties.get(variableName);
94473
- if (!resolved) return families;
94474
- return [...parseFontFamilyValue(resolved), ...families.slice(1)];
94755
+ const variable = depth < MAX_CSS_VARIABLE_DEPTH ? primaryCssVariable(declaration) : null;
94756
+ if (!variable) return tag(families);
94757
+ const resolved = customProperties.get(variable.name);
94758
+ const source = resolved || variable.fallback;
94759
+ if (source === null) return tag(families);
94760
+ const primary = resolveDeclaredFamilies(
94761
+ source,
94762
+ customProperties,
94763
+ !resolved || depth > 0,
94764
+ depth + 1
94765
+ );
94766
+ if (primary.length === 0) return [];
94767
+ return [...primary, ...tag(families.slice(1))];
94475
94768
  }
94476
94769
  function* iterateFontFamilyDeclarations(html) {
94477
94770
  const { document: document2 } = parseHTML(html);
@@ -94596,18 +94889,22 @@ function extractExistingFontFaces(html) {
94596
94889
  }
94597
94890
  return families;
94598
94891
  }
94892
+ function isFetchableFamilyName(normalized2) {
94893
+ if (!normalized2 || normalized2.startsWith("var(")) return false;
94894
+ return !GENERIC_FAMILIES2.has(normalized2) && !CSS_WIDE_KEYWORDS.has(normalized2);
94895
+ }
94599
94896
  function extractRequestedFontFamilies(html) {
94600
94897
  const requested = /* @__PURE__ */ new Map();
94601
94898
  const customProperties = collectFontFamilyCustomProperties(html);
94602
94899
  for (const { declaration } of iterateFontFamilyDeclarations(html)) {
94603
- for (const originalCase of resolveFontFamilyDeclarationFamilies(
94604
- declaration,
94605
- customProperties
94606
- )) {
94607
- const normalized2 = originalCase.toLowerCase();
94608
- if (!normalized2 || GENERIC_FAMILIES2.has(normalized2)) continue;
94609
- if (normalized2.startsWith("var(")) continue;
94610
- if (!requested.has(normalized2)) requested.set(normalized2, originalCase);
94900
+ for (const { family, optional } of resolveDeclaredFamilies(declaration, customProperties)) {
94901
+ const normalized2 = family.toLowerCase();
94902
+ if (!isFetchableFamilyName(normalized2)) continue;
94903
+ const seen = requested.get(normalized2);
94904
+ requested.set(normalized2, {
94905
+ family: seen?.family ?? family,
94906
+ optional: (seen?.optional ?? true) && optional
94907
+ });
94611
94908
  }
94612
94909
  }
94613
94910
  return requested;
@@ -94685,7 +94982,10 @@ function partitionWeightRuns(faces, coveredWeights) {
94685
94982
  async function buildFontFaceCss(requestedFamilies, options, fontText) {
94686
94983
  const rules = [];
94687
94984
  const unresolved = [];
94688
- for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) {
94985
+ const requiredFirst = [...requestedFamilies].sort(
94986
+ ([, a], [, b]) => Number(a.optional) - Number(b.optional)
94987
+ );
94988
+ for (const [normalizedFamily, { family: originalCaseFamily, optional }] of requiredFirst) {
94689
94989
  const canonicalKey = FONT_ALIASES[normalizedFamily];
94690
94990
  if (canonicalKey) {
94691
94991
  const canonical = CANONICAL_FONTS[canonicalKey];
@@ -94707,7 +95007,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94707
95007
  coveredWeights.add(coverageKey(face.weight, style));
94708
95008
  }
94709
95009
  const canonicalFamily = resolveAliasDisplayName(normalizedFamily);
94710
- const googleFaces2 = canonicalFamily ? await fetchGoogleFont(canonicalFamily, options, fontText) : [];
95010
+ const googleFaces2 = canonicalFamily ? await fetchFamilyFaces(canonicalFamily, optional, options, fontText) : [];
94711
95011
  const supplementary = googleFaces2.filter(
94712
95012
  (face) => !coveredWeights.has(coverageKey(face.weight, face.style)) || !isBundledSubsetRange(face.unicodeRange)
94713
95013
  );
@@ -94733,7 +95033,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94733
95033
  rules.push(...bundledRules);
94734
95034
  continue;
94735
95035
  }
94736
- const googleFaces = await fetchGoogleFont(originalCaseFamily, options, fontText);
95036
+ const googleFaces = await fetchFamilyFaces(originalCaseFamily, optional, options, fontText);
94737
95037
  if (googleFaces.length > 0) {
94738
95038
  for (const face of googleFaces) {
94739
95039
  rules.push(
@@ -94748,7 +95048,7 @@ async function buildFontFaceCss(requestedFamilies, options, fontText) {
94748
95048
  }
94749
95049
  continue;
94750
95050
  }
94751
- if (options.allowSystemFontCapture) {
95051
+ if (options.allowSystemFontCapture && !pageNamedThisFile(originalCaseFamily, options)) {
94752
95052
  const variants = locateSystemFontVariants(originalCaseFamily);
94753
95053
  if (variants.length > 0) {
94754
95054
  let totalBytes = 0;
@@ -94813,12 +95113,12 @@ function fontCacheDir(slug) {
94813
95113
  const dir = join33(resolveFontCacheRoot(), slug);
94814
95114
  if (!existsSync24(dir)) {
94815
95115
  try {
94816
- mkdirSync14(dir, { recursive: true });
95116
+ mkdirSync15(dir, { recursive: true });
94817
95117
  } catch {
94818
95118
  const firstFallback = ephemeralFontCacheRoot === void 0;
94819
95119
  ephemeralFontCacheRoot ??= mkdtempSync9(join33(tmpdir6(), "hyperframes-fonts-"));
94820
95120
  const fallback = join33(ephemeralFontCacheRoot, slug);
94821
- mkdirSync14(fallback, { recursive: true });
95121
+ mkdirSync15(fallback, { recursive: true });
94822
95122
  if (firstFallback) {
94823
95123
  defaultLogger.warn(
94824
95124
  `Font cache directory is unwritable (${dir}). Using temporary fallback \u2014 fonts will re-download each run. Fix with: chmod 755 ${resolveFontCacheRoot()}`
@@ -94998,7 +95298,8 @@ function raceAgainstAbort(promise, signal) {
94998
95298
  });
94999
95299
  }
95000
95300
  function fetchGoogleFontCss(url, familyName, options) {
95001
- const key2 = `${options.failClosedFontFetch ? "closed" : "open"}:${url}`;
95301
+ const mode = options.failClosedFontFetch ? `closed${options.retryPolicy.maxAttempts}` : "open";
95302
+ const key2 = `${mode}:${url}`;
95002
95303
  let shared = googleFontCssCache.get(key2);
95003
95304
  if (!shared) {
95004
95305
  shared = fetchFontResource(
@@ -95018,35 +95319,66 @@ function fetchGoogleFontCss(url, familyName, options) {
95018
95319
  }
95019
95320
  return raceAgainstAbort(shared, options.abortSignal);
95020
95321
  }
95322
+ function pageNamedThisFile(familyName, options) {
95323
+ return options.authoredStylesheets.has(normalizeFamilyName(familyName.replace(/\+/g, " ")));
95324
+ }
95325
+ function declaredFaceFamily(block) {
95326
+ const found = /font-family:\s*['"]([^'"]+)['"]/i.exec(block);
95327
+ return found?.[1];
95328
+ }
95021
95329
  async function fetchGoogleFont(familyName, options, fontText) {
95022
- const slug = fontSlug(familyName);
95023
95330
  const googleFamilyName = familyName.replace(/\+/g, " ");
95024
95331
  const encodedFamily = encodeURIComponent(googleFamilyName);
95025
95332
  const textParam = fontText ? `&text=${encodeURIComponent(fontText)}` : "";
95026
- const url = `https://fonts.googleapis.com/css2?family=${encodedFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700${textParam}`;
95027
- let cssText;
95333
+ const normalizedFamily = normalizeFamilyName(googleFamilyName);
95334
+ const authoredStylesheet = FONT_ALIASES[normalizedFamily] ? void 0 : options.authoredStylesheets.get(normalizedFamily);
95335
+ const urls = authoredStylesheet ? authoredStylesheet.map((url) => withPageText(url, fontText)) : [
95336
+ `https://fonts.googleapis.com/css2?family=${encodedFamily}:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,400;1,700${textParam}`
95337
+ ];
95338
+ const faces = [];
95339
+ for (const url of urls) {
95340
+ faces.push(...await fetchGoogleFontStylesheet(googleFamilyName, url, options));
95341
+ }
95342
+ return faces;
95343
+ }
95344
+ async function readGoogleFontStylesheet(familyName, url, options) {
95028
95345
  try {
95029
95346
  const cssResult = await fetchGoogleFontCss(url, familyName, options);
95030
95347
  if (!cssResult.ok) {
95031
- return [];
95348
+ return null;
95032
95349
  }
95033
- cssText = cssResult.body;
95350
+ return cssResult.body;
95034
95351
  } catch (err) {
95035
95352
  throwIfCallerAborted(options.abortSignal);
95036
95353
  if (err instanceof FontFetchError) throw err;
95037
95354
  if (options.failClosedFontFetch) {
95038
95355
  throw fontFetchError(familyName, url, "Google Fonts CSS", { error: err });
95039
95356
  }
95040
- return [];
95357
+ return null;
95041
95358
  }
95042
- const faceRegex = /@font-face\s*\{[^}]*font-style:\s*(normal|italic)[^}]*font-weight:\s*(\d+)[^}]*src:\s*url\(([^)]+)\)\s*format\(['"]woff2['"]\)(?:[^}]*?unicode-range:\s*([^;}]+))?[^}]*\}/gi;
95359
+ }
95360
+ function parseGoogleFontSource(match, familyName) {
95361
+ const declared = declaredFaceFamily(match[0]);
95362
+ if (declared && normalizeFamilyName(declared) !== normalizeFamilyName(familyName)) return null;
95363
+ const url = match[3];
95364
+ if (!url) return null;
95365
+ return {
95366
+ style: match[1] || "normal",
95367
+ weight: (match[2] || "400").replace(/\s+/g, " "),
95368
+ url,
95369
+ unicodeRange: match[4]?.trim() || void 0
95370
+ };
95371
+ }
95372
+ async function fetchGoogleFontStylesheet(familyName, url, options) {
95373
+ const slug = fontSlug(familyName);
95374
+ const cssText = await readGoogleFontStylesheet(familyName, url, options);
95375
+ if (!cssText) return [];
95376
+ const faceRegex = /@font-face\s*\{[^}]*font-style:\s*(normal|italic|oblique(?:\s+-?\d+(?:\.\d+)?deg){0,2})\s*;[^}]*font-weight:\s*(\d+(?:\s+\d+)?)[^}]*src:\s*url\(([^)]+)\)\s*format\(['"]woff2['"]\)(?:[^}]*?unicode-range:\s*([^;}]+))?[^}]*\}/gi;
95043
95377
  const faces = [];
95044
95378
  for (const match of cssText.matchAll(faceRegex)) {
95045
- const style = match[1] || "normal";
95046
- const weight = match[2] || "400";
95047
- const woff2Url = match[3] || "";
95048
- const unicodeRange = match[4]?.trim() || void 0;
95049
- if (!woff2Url) continue;
95379
+ const source = parseGoogleFontSource(match, familyName);
95380
+ if (!source) continue;
95381
+ const { weight, style, unicodeRange, url: woff2Url } = source;
95050
95382
  const cachePath = cachedWoff2Path(slug, weight, style, subsetToken(woff2Url));
95051
95383
  const dataUri = await ensureWoff2DataUri(
95052
95384
  cachePath,
@@ -95065,6 +95397,23 @@ async function fetchGoogleFont(familyName, options, fontText) {
95065
95397
  }
95066
95398
  return faces;
95067
95399
  }
95400
+ async function fetchFamilyFaces(familyName, optional, options, fontText) {
95401
+ if (!optional) return fetchGoogleFont(familyName, options, fontText);
95402
+ const maxAttempts = Math.min(options.retryPolicy.maxAttempts, 2);
95403
+ try {
95404
+ return await fetchGoogleFont(
95405
+ familyName,
95406
+ { ...options, retryPolicy: { ...options.retryPolicy, maxAttempts } },
95407
+ fontText
95408
+ );
95409
+ } catch (err) {
95410
+ if (!(err instanceof FontFetchUnavailableError)) throw err;
95411
+ defaultLogger.warn(
95412
+ `[Compiler] Optional font "${familyName}" is unavailable, rendering its fallback: ${err.message}`
95413
+ );
95414
+ return [];
95415
+ }
95416
+ }
95068
95417
  var GOOGLE_FONTS_TEXT_MAX_ENCODED_LENGTH = 1700;
95069
95418
  var SMALL_TO_FULL_KANA = /* @__PURE__ */ new Map([
95070
95419
  ["\u3041", "\u3042"],
@@ -95208,14 +95557,15 @@ async function injectDeterministicFontFaces(html, options = {}) {
95208
95557
  allowSystemFontCapture,
95209
95558
  abortSignal: options.abortSignal,
95210
95559
  retryPolicy,
95211
- retryDeadlineMs: Date.now() + retryPolicy.maxElapsedMs
95560
+ retryDeadlineMs: Date.now() + retryPolicy.maxElapsedMs,
95561
+ authoredStylesheets: authoredGoogleFontStylesheets(html)
95212
95562
  };
95213
95563
  const existingFaces = extractExistingFontFaces(html);
95214
95564
  const requestedFamilies = extractRequestedFontFamilies(html);
95215
95565
  const pendingFamilies = /* @__PURE__ */ new Map();
95216
- for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) {
95566
+ for (const [normalizedFamily, requested] of requestedFamilies) {
95217
95567
  if (!existingFaces.has(normalizedFamily)) {
95218
- pendingFamilies.set(normalizedFamily, originalCaseFamily);
95568
+ pendingFamilies.set(normalizedFamily, requested);
95219
95569
  }
95220
95570
  }
95221
95571
  if (pendingFamilies.size === 0) {
@@ -95226,11 +95576,14 @@ async function injectDeterministicFontFaces(html, options = {}) {
95226
95576
  fetchOptions,
95227
95577
  extractGoogleFontsText(html)
95228
95578
  );
95229
- if (unresolved.length > 0 && options.failClosedFontFetch) {
95579
+ const required = unresolved.filter(
95580
+ (family) => !pendingFamilies.get(family.toLowerCase())?.optional
95581
+ );
95582
+ if (required.length > 0 && options.failClosedFontFetch) {
95230
95583
  throw new FontFetchError(
95231
- unresolved.join(", "),
95584
+ required.join(", "),
95232
95585
  "",
95233
- `[Compiler] Unresolved fonts in fail-closed mode: ${unresolved.join(", ")}. Distributed renders require all fonts to be resolvable.`
95586
+ `[Compiler] Unresolved fonts in fail-closed mode: ${required.join(", ")}. Distributed renders require all fonts to be resolvable.`
95234
95587
  );
95235
95588
  }
95236
95589
  if (!css) {
@@ -95265,13 +95618,13 @@ import { createHash as createHash10 } from "crypto";
95265
95618
  import {
95266
95619
  copyFileSync as copyFileSync4,
95267
95620
  existsSync as existsSync25,
95268
- mkdirSync as mkdirSync15,
95621
+ mkdirSync as mkdirSync16,
95269
95622
  readFileSync as readFileSync13,
95270
95623
  renameSync as renameSync6,
95271
95624
  rmSync as rmSync12,
95272
- statSync as statSync10
95625
+ statSync as statSync11
95273
95626
  } from "fs";
95274
- import { dirname as dirname15, isAbsolute as isAbsolute4, join as join34, resolve as resolve11 } from "path";
95627
+ import { dirname as dirname15, isAbsolute as isAbsolute5, join as join34, resolve as resolve11 } from "path";
95275
95628
  var PREPARED_GIF_SUBDIR = "_animated_gif";
95276
95629
  var CACHE_SCHEMA = "hfgif-v1";
95277
95630
  function splitUrlSuffix(src) {
@@ -95310,14 +95663,14 @@ function resolveGifSourcePath(src, options) {
95310
95663
  if (isHttpUrl(trimmed)) return null;
95311
95664
  const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
95312
95665
  const candidates = [
95313
- isAbsolute4(basePath) ? basePath : resolve11(options.projectDir, projectRelative),
95666
+ isAbsolute5(basePath) ? basePath : resolve11(options.projectDir, projectRelative),
95314
95667
  resolve11(options.downloadDir, normalizedBase)
95315
95668
  ];
95316
95669
  return candidates.find((candidate) => existsSync25(candidate)) ?? null;
95317
95670
  }
95318
95671
  function isUsableFile(path) {
95319
95672
  try {
95320
- const stat = statSync10(path);
95673
+ const stat = statSync11(path);
95321
95674
  return stat.isFile() && stat.size > 0;
95322
95675
  } catch {
95323
95676
  return false;
@@ -95425,10 +95778,10 @@ async function runAnimatedGifTranscode(request) {
95425
95778
  }
95426
95779
  async function ensurePreparedWebm(input) {
95427
95780
  if (!existsSync25(dirname15(input.cachePath))) {
95428
- mkdirSync15(dirname15(input.cachePath), { recursive: true });
95781
+ mkdirSync16(dirname15(input.cachePath), { recursive: true });
95429
95782
  }
95430
95783
  if (!existsSync25(dirname15(input.outputPath))) {
95431
- mkdirSync15(dirname15(input.outputPath), { recursive: true });
95784
+ mkdirSync16(dirname15(input.outputPath), { recursive: true });
95432
95785
  }
95433
95786
  if (!isUsableFile(input.cachePath)) {
95434
95787
  const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -95858,7 +96211,7 @@ function studioPositionSeekReapplyRuntime() {
95858
96211
  }
95859
96212
 
95860
96213
  // ../core/dist/generated/position-edits-render-inline.js
95861
- var POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function T(){return globalThis}function P(e,t){if(typeof window>"u")return;let o=T(),r=o.__hf?.onSwallowed;if(r)try{r({label:e,error:t})}catch(s){}(o.__hfDebug||o.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${e} swallowed:`,t)}var x=null;function R(e,t){if(x)try{x({source:"hf-preview",type:"analytics",event:e,properties:t??{}})}catch(o){P("runtime.analytics.site1",o)}}var W="http://www.w3.org/1999/xhtml";function N(e){return e!=null&&e.nodeType===1}function _(e){return N(e)&&(e.namespaceURI===W||e.namespaceURI==="http://www.w3.org/2000/svg")}var w="data-hf-edit-base-x",y="data-hf-edit-base-y",m="data-hf-edit-original-translate",k=e=>{let t=parseFloat(e??"");return Number.isFinite(t)?t:0},O=e=>{let t=[],o=0,r="";for(let s of e.trim())s==="("&&(o+=1),s===")"&&(o=Math.max(0,o-1)),/\\s/.test(s)&&o===0?(r&&t.push(r),r=""):r+=s;return r&&t.push(r),t},L=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,b=(e,t)=>L.test(e)&&L.test(t)?`${parseFloat(e)+parseFloat(t)}px`:`calc(${e} + ${t})`,V=(e,t,o)=>{if(!e||e==="none")return`${t} ${o}`;let[r,s,a]=O(e);if(r===void 0)return`${t} ${o}`;if(s===void 0)return`${b(r,t)} ${o}`;let i=a===void 0?"":` ${a}`;return`${b(r,t)} ${b(s,o)}${i}`},j=e=>{try{e.ownerDocument.defaultView?.gsap?.getProperty?.(e,"x")}catch{}},G=e=>{let t=e.style.getPropertyValue("translate").trim();if(t)return t==="none"?"":t;try{let o=e.ownerDocument.defaultView,r=o?o.getComputedStyle(e).getPropertyValue("translate").trim():"";return r==="none"?"":r}catch{return""}},E=new WeakMap;function B(e,t){let o=E.get(e);if(!t?.force&&o!==void 0&&e.style.getPropertyValue("translate")!==o){R("position_edit_fold_skipped",{hfId:e.getAttribute("data-hf-id")});return}let r=k(e.getAttribute("data-x"))-k(e.getAttribute(w)),s=k(e.getAttribute("data-y"))-k(e.getAttribute(y));e.hasAttribute(m)||e.setAttribute(m,G(e)),o===void 0&&j(e);let a=e.getAttribute(m)??"",i=V(a,`${r}px`,`${s}px`);e.style.setProperty("translate",i),E.set(e,e.style.getPropertyValue("translate"))}function S(e,t){let o=e.querySelectorAll(`[${m}]:not([${w}]):not([${y}])`);for(let a=0;a<o.length;a++){let i=o[a];if(i===void 0||!_(i))continue;let g=i.getAttribute(m)??"";g===""?i.style.removeProperty("translate"):i.style.setProperty("translate",g),i.removeAttribute(m),E.delete(i)}let r=e.querySelectorAll(`[${w}], [${y}]`),s=0;for(let a=0;a<r.length;a++){let i=r[a];i===void 0||!_(i)||(B(i,t),s+=1)}return s}var $="__hfPositionEditsSeekReapplyWrapped",D=new WeakSet,M=new WeakMap,H=new WeakMap;function F(e){let t=e,o=()=>{try{S(t.document)}catch{}},r=n=>typeof n=="function"&&(D.has(n)||!!n[$]),s=n=>{D.add(n);try{Object.defineProperty(n,$,{value:!0})}catch{}},a=n=>{if(typeof n!="function"||r(n))return n;let l=function(...u){let d=n.apply(this,u);return o(),d};return s(l),l},i=(n,l)=>{let u=M.get(n);if(u?.has(l))return!0;let d=Object.getOwnPropertyDescriptor(n,l);if(d?.configurable===!1){let c=n[l];return typeof c=="function"&&(n[l]=a(c),o()),!1}let p=n[l],f=d?.set;return Object.defineProperty(n,l,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>p,set:c=>{p=a(c),f?.call(n,c)}}),p=a(p),u??(u=new Set),u.add(l),M.set(n,u),o(),!0},g=(n,l)=>{let u=H.get(t),d=Object.getOwnPropertyDescriptor(t,n);if(!u?.has(n)){if(d?.configurable===!1){let c=t[n];return c?i(c,l):!1}let f=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>f,set:c=>{f=c,f&&i(f,l)}}),u??(u=new Set),u.add(n),H.set(t,u)}let p=t[n];return p?i(p,l):!1},v=()=>{let n=g("__hf","seek"),l=g("__player","renderSeek");return n&&l};if(v())return;let h=120,A=t.setInterval(()=>{if(v()){t.clearInterval(A);return}h-=1,h<=0&&t.clearInterval(A)},50)}function I(){document.querySelector(`[${w}], [${y}]`)&&(S(document),F(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",I,{once:!0}):I();})();\n';
96214
+ var POSITION_EDITS_RENDER_IIFE = '"use strict";(()=>{function T(){return globalThis}function P(e,t){if(typeof window>"u")return;let o=T(),r=o.__hf?.onSwallowed;if(r)try{r({label:e,error:t})}catch(s){}(o.__hfDebug||o.__HYPERFRAMES_DEBUG)&&console.debug(`[hyperframes] ${e} swallowed:`,t)}var x=null;function R(e,t){if(x)try{x({source:"hf-preview",type:"analytics",event:e,properties:t??{}})}catch(o){P("runtime.analytics.site1",o)}}var W="http://www.w3.org/1999/xhtml";function N(e){return e!=null&&e.nodeType===1}function _(e){return N(e)&&(e.namespaceURI===W||e.namespaceURI==="http://www.w3.org/2000/svg")}var w="data-hf-edit-base-x",y="data-hf-edit-base-y",m="data-hf-edit-original-translate",k=e=>{let t=parseFloat(e??"");return Number.isFinite(t)?t:0},O=e=>{let t=[],o=0,r="";for(let s of e.trim())s==="("&&(o+=1),s===")"&&(o=Math.max(0,o-1)),/\\s/.test(s)&&o===0?(r&&t.push(r),r=""):r+=s;return r&&t.push(r),t},L=/^-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)px$/,E=(e,t)=>L.test(e)&&L.test(t)?`${parseFloat(e)+parseFloat(t)}px`:`calc(${e} + ${t})`,V=(e,t,o)=>{if(!e||e==="none")return`${t} ${o}`;let[r,s,a]=O(e);if(r===void 0)return`${t} ${o}`;if(s===void 0)return`${E(r,t)} ${o}`;let i=a===void 0?"":` ${a}`;return`${E(r,t)} ${E(s,o)}${i}`},j=e=>{try{e.ownerDocument.defaultView?.gsap?.getProperty?.(e,"x")}catch{}},G=e=>{let t=e.style.getPropertyValue("translate").trim();if(t)return t==="none"?"":t;try{let o=e.ownerDocument.defaultView,r=o?o.getComputedStyle(e).getPropertyValue("translate").trim():"";return r==="none"?"":r}catch{return""}},b=new WeakMap;function B(e,t){let o=b.get(e);if(!t?.force&&o!==void 0&&e.style.getPropertyValue("translate")!==o){R("position_edit_fold_skipped",{hfId:e.getAttribute("data-hf-id")});return}let r=k(e.getAttribute("data-x"))-k(e.getAttribute(w)),s=k(e.getAttribute("data-y"))-k(e.getAttribute(y));e.hasAttribute(m)||e.setAttribute(m,G(e)),o===void 0&&j(e);let a=e.getAttribute(m)??"",i=V(a,`${r}px`,`${s}px`);e.style.setProperty("translate",i),b.set(e,e.style.getPropertyValue("translate"))}function S(e,t){let o=e.querySelectorAll(`[${m}]:not([${w}]):not([${y}])`);for(let a=0;a<o.length;a++){let i=o[a];if(i===void 0||!_(i))continue;let g=i.getAttribute(m)??"";g===""?i.style.removeProperty("translate"):i.style.setProperty("translate",g),i.removeAttribute(m),b.delete(i)}let r=e.querySelectorAll(`[${w}], [${y}]`),s=0;for(let a=0;a<r.length;a++){let i=r[a];i===void 0||!_(i)||(B(i,t),s+=1)}return s}var $="__hfPositionEditsSeekReapplyWrapped",D=new WeakSet,M=new WeakMap,H=new WeakMap;function F(e){let t=e,o=()=>{try{S(t.document)}catch{}},r=n=>typeof n=="function"&&(D.has(n)||!!n[$]),s=n=>{D.add(n);try{Object.defineProperty(n,$,{value:!0})}catch{}},a=n=>{if(typeof n!="function"||r(n))return n;let l=function(...u){let d=n.apply(this,u);return o(),d};return s(l),l},i=(n,l)=>{let u=M.get(n);if(u?.has(l))return!0;let d=Object.getOwnPropertyDescriptor(n,l);if(d?.configurable===!1){let c=n[l];return typeof c=="function"&&(n[l]=a(c),o()),!1}let p=n[l],f=d?.set;return Object.defineProperty(n,l,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>p,set:c=>{p=a(c),f?.call(n,c)}}),p=a(p),u??(u=new Set),u.add(l),M.set(n,u),o(),!0},g=(n,l)=>{let u=H.get(t),d=Object.getOwnPropertyDescriptor(t,n);if(!u?.has(n)){if(d?.configurable===!1){let c=t[n];return c?i(c,l):!1}let f=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:d?.enumerable??!0,get:()=>f,set:c=>{f=c,f&&i(f,l)}}),u??(u=new Set),u.add(n),H.set(t,u)}let p=t[n];return p?i(p,l):!1},v=()=>{let n=g("__hf","seek"),l=g("__player","renderSeek");return n&&l};if(v())return;let h=120,A=t.setInterval(()=>{if(v()){t.clearInterval(A);return}h-=1,h<=0&&t.clearInterval(A)},50)}function I(){document.querySelector(`[${w}], [${y}]`)&&(S(document),F(window))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",I,{once:!0}):I();})();\n';
95862
96215
  function getPositionEditsRenderScript() {
95863
96216
  return POSITION_EDITS_RENDER_IIFE;
95864
96217
  }
@@ -96193,7 +96546,7 @@ function detectShaderTransitionUsage(html) {
96193
96546
  async function resolveMediaDuration2(src, mediaStart, playbackRate, baseDir, downloadDir, tagName19, elementIdentity, log) {
96194
96547
  let filePath = src;
96195
96548
  if (isHttpUrl(src)) {
96196
- if (!existsSync27(downloadDir)) mkdirSync16(downloadDir, { recursive: true });
96549
+ if (!existsSync27(downloadDir)) mkdirSync17(downloadDir, { recursive: true });
96197
96550
  try {
96198
96551
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
96199
96552
  onTelemetry: logRemoteDownloadTelemetry
@@ -96780,7 +97133,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
96780
97133
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
96781
97134
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
96782
97135
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
96783
- if (!existsSync27(remoteDir)) mkdirSync16(remoteDir, { recursive: true });
97136
+ if (!existsSync27(remoteDir)) mkdirSync17(remoteDir, { recursive: true });
96784
97137
  const urlToLocal = /* @__PURE__ */ new Map();
96785
97138
  await Promise.all(
96786
97139
  [...urlSet].map(async (url) => {
@@ -97092,7 +97445,7 @@ function rewriteUnresolvableGsapToCdn(html, projectDir) {
97092
97445
  }
97093
97446
  function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
97094
97447
  if (!isPathInside3(htmlPath, projectDir)) return html;
97095
- const entryPath = relative4(projectDir, htmlPath).replace(/\\/g, "/");
97448
+ const entryPath = relative5(projectDir, htmlPath).replace(/\\/g, "/");
97096
97449
  if (!entryPath.includes("/")) return html;
97097
97450
  const { document: document2 } = parseHTML(html);
97098
97451
  const assetExists = (path) => existsSync27(resolve12(projectDir, path));
@@ -97353,8 +97706,8 @@ async function discoverAudioVolumeAutomationFromTimeline(page, audioIds, composi
97353
97706
  const volumeDescriptor = typeof HTMLMediaElement === "undefined" ? void 0 : Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, "volume");
97354
97707
  const nativeVolumeGet = volumeDescriptor?.get;
97355
97708
  const nativeVolumeSet = volumeDescriptor?.set;
97356
- const withUnclampedVolume2 = (el, probe2) => {
97357
- if (!nativeVolumeGet || !nativeVolumeSet) return probe2();
97709
+ const withUnclampedVolume2 = (el, probe) => {
97710
+ if (!nativeVolumeGet || !nativeVolumeSet) return probe();
97358
97711
  let authored = Number(nativeVolumeGet.call(el));
97359
97712
  Object.defineProperty(el, "volume", {
97360
97713
  configurable: true,
@@ -97365,7 +97718,7 @@ async function discoverAudioVolumeAutomationFromTimeline(page, audioIds, composi
97365
97718
  }
97366
97719
  });
97367
97720
  try {
97368
- return probe2();
97721
+ return probe();
97369
97722
  } finally {
97370
97723
  delete el.volume;
97371
97724
  nativeVolumeSet.call(el, Math.max(0, Math.min(1, authored)));
@@ -99110,7 +99463,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
99110
99463
  // ../producer/src/services/render/stages/captureHdrStage.ts
99111
99464
  init_src();
99112
99465
  init_dist2();
99113
- import { existsSync as existsSync31, mkdirSync as mkdirSync17 } from "fs";
99466
+ import { existsSync as existsSync31, mkdirSync as mkdirSync18 } from "fs";
99114
99467
  import { join as join44 } from "path";
99115
99468
 
99116
99469
  // ../producer/src/services/hdrImageTransferCache.ts
@@ -99904,7 +100257,7 @@ async function runCaptureHdrStage(input) {
99904
100257
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
99905
100258
  const debugDumpDir = debugDumpEnabled ? join44(framesDir, "debug-composite") : null;
99906
100259
  if (debugDumpDir && !existsSync31(debugDumpDir)) {
99907
- mkdirSync17(debugDumpDir, { recursive: true });
100260
+ mkdirSync18(debugDumpDir, { recursive: true });
99908
100261
  }
99909
100262
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
99910
100263
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -100053,7 +100406,17 @@ async function runCaptureHdrStage(input) {
100053
100406
 
100054
100407
  // ../producer/src/services/render/stages/encodeStage.ts
100055
100408
  init_src();
100056
- import { copyFileSync as copyFileSync5, existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync11, rmSync as rmSync13, statSync as statSync11 } from "fs";
100409
+ init_dist2();
100410
+ import {
100411
+ copyFileSync as copyFileSync5,
100412
+ existsSync as existsSync32,
100413
+ mkdirSync as mkdirSync19,
100414
+ readdirSync as readdirSync11,
100415
+ readFileSync as readFileSync15,
100416
+ rmSync as rmSync13,
100417
+ statSync as statSync12,
100418
+ writeFileSync as writeFileSync11
100419
+ } from "fs";
100057
100420
  import { dirname as dirname18, join as join46 } from "path";
100058
100421
 
100059
100422
  // ../producer/src/services/render/stages/gifEncodeArgs.ts
@@ -100061,15 +100424,23 @@ import { join as join45 } from "path";
100061
100424
  function fpsToFfmpegArg2(fps) {
100062
100425
  return fps.den === 1 ? String(fps.num) : `${fps.num}/${fps.den}`;
100063
100426
  }
100427
+ var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
100428
+ var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
100429
+ function framesInput(input, fpsArg) {
100430
+ return [
100431
+ "-framerate",
100432
+ fpsArg,
100433
+ ...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
100434
+ "-i",
100435
+ join45(input.framesDir, input.framePattern)
100436
+ ];
100437
+ }
100064
100438
  function buildGifPalettegenArgs(input) {
100065
100439
  const fpsArg = fpsToFfmpegArg2(input.fps);
100066
100440
  const transparency = input.preserveAlpha ? ":reserve_transparent=1" : "";
100067
100441
  return [
100068
100442
  "-y",
100069
- "-framerate",
100070
- fpsArg,
100071
- "-i",
100072
- join45(input.framesDir, input.framePattern),
100443
+ ...framesInput(input, fpsArg),
100073
100444
  "-vf",
100074
100445
  `fps=${fpsArg},palettegen=stats_mode=diff${transparency}`,
100075
100446
  input.palettePath
@@ -100080,16 +100451,14 @@ function buildGifPaletteuseArgs(input) {
100080
100451
  const transparency = input.preserveAlpha ? ":alpha_threshold=128" : "";
100081
100452
  return [
100082
100453
  "-y",
100083
- "-framerate",
100084
- fpsArg,
100085
- "-i",
100086
- join45(input.framesDir, input.framePattern),
100454
+ ...framesInput(input, fpsArg),
100087
100455
  "-i",
100088
100456
  input.palettePath,
100089
100457
  "-lavfi",
100090
100458
  `fps=${fpsArg} [x]; [x][1:v] paletteuse=dither=sierra2_4a${transparency}`,
100091
100459
  "-loop",
100092
100460
  String(input.loop),
100461
+ ...input.wholeFrames ? WRITE_EVERY_FRAME_WHOLE : [],
100093
100462
  input.outputPath
100094
100463
  ];
100095
100464
  }
@@ -100103,19 +100472,38 @@ function resolveGifLoop(loop) {
100103
100472
  }
100104
100473
  return resolved;
100105
100474
  }
100475
+ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
100476
+ return {
100477
+ success: false,
100478
+ outputPath,
100479
+ durationMs: Date.now() - startTime,
100480
+ framesEncoded: 0,
100481
+ fileSize: 0,
100482
+ error,
100483
+ failureReason
100484
+ };
100485
+ }
100486
+ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
100487
+ const { outputPath } = argsInput;
100488
+ const result = await runFfmpeg(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
100489
+ if (!result.success) {
100490
+ const error = formatFfmpegError(result.exitCode, result.stderr);
100491
+ return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
100492
+ }
100493
+ const gif = readFileSync15(outputPath);
100494
+ if (!clearGifFramesBeforeNext(gif)) {
100495
+ const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
100496
+ return gifEncodeFailure(startTime, outputPath, error);
100497
+ }
100498
+ writeFileSync11(outputPath, gif);
100499
+ return null;
100500
+ }
100106
100501
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
100107
100502
  const startTime = Date.now();
100108
100503
  const files = readdirSync11(framesDir).filter((file) => file.match(/\.(jpg|jpeg|png)$/i));
100109
100504
  const frameCount = files.length;
100110
100505
  if (frameCount === 0) {
100111
- return {
100112
- success: false,
100113
- outputPath,
100114
- durationMs: Date.now() - startTime,
100115
- framesEncoded: 0,
100116
- fileSize: 0,
100117
- error: "[FFmpeg] No frame files found in directory"
100118
- };
100506
+ return gifEncodeFailure(startTime, outputPath, "[FFmpeg] No frame files found in directory");
100119
100507
  }
100120
100508
  const argsInput = {
100121
100509
  framesDir,
@@ -100127,37 +100515,19 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
100127
100515
  preserveAlpha: input.preserveAlpha
100128
100516
  };
100129
100517
  try {
100130
- const paletteResult = await runFfmpeg(buildGifPalettegenArgs(argsInput), {
100131
- signal: input.signal,
100132
- timeout: input.timeout
100133
- });
100134
- if (!paletteResult.success) {
100135
- return {
100136
- success: false,
100137
- outputPath,
100138
- durationMs: Date.now() - startTime,
100139
- framesEncoded: 0,
100140
- fileSize: 0,
100141
- error: formatFfmpegError(paletteResult.exitCode, paletteResult.stderr),
100142
- failureReason: paletteResult.failureReason
100143
- };
100518
+ const run = { signal: input.signal, timeout: input.timeout };
100519
+ for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
100520
+ const result = await runFfmpeg(args, run);
100521
+ if (!result.success) {
100522
+ const error = formatFfmpegError(result.exitCode, result.stderr);
100523
+ return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
100524
+ }
100144
100525
  }
100145
- const gifResult = await runFfmpeg(buildGifPaletteuseArgs(argsInput), {
100146
- signal: input.signal,
100147
- timeout: input.timeout
100148
- });
100149
- if (!gifResult.success) {
100150
- return {
100151
- success: false,
100152
- outputPath,
100153
- durationMs: Date.now() - startTime,
100154
- framesEncoded: 0,
100155
- fileSize: 0,
100156
- error: formatFfmpegError(gifResult.exitCode, gifResult.stderr),
100157
- failureReason: gifResult.failureReason
100158
- };
100526
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync15(outputPath)) !== false) {
100527
+ const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
100528
+ if (failure) return failure;
100159
100529
  }
100160
- const fileSize = existsSync32(outputPath) ? statSync11(outputPath).size : 0;
100530
+ const fileSize = existsSync32(outputPath) ? statSync12(outputPath).size : 0;
100161
100531
  return {
100162
100532
  success: true,
100163
100533
  outputPath,
@@ -100195,7 +100565,7 @@ async function runEncodeStage(input) {
100195
100565
  const stage5Start = Date.now();
100196
100566
  if (isPngSequence) {
100197
100567
  updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
100198
- if (!existsSync32(outputPath)) mkdirSync18(outputPath, { recursive: true });
100568
+ if (!existsSync32(outputPath)) mkdirSync19(outputPath, { recursive: true });
100199
100569
  const captured = readdirSync11(framesDir).filter((name) => name.endsWith(".png")).sort();
100200
100570
  if (captured.length === 0) {
100201
100571
  throw new Error(
@@ -100800,7 +101170,7 @@ function sampleDirectoryBytes(dir) {
100800
101170
  for (const name of entries2) {
100801
101171
  const full2 = join48(current2, name);
100802
101172
  try {
100803
- const st = statSync12(full2);
101173
+ const st = statSync13(full2);
100804
101174
  if (st.isDirectory()) {
100805
101175
  stack.push(full2);
100806
101176
  } else if (st.isFile()) {
@@ -100948,7 +101318,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
100948
101318
  let rangeStart = null;
100949
101319
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
100950
101320
  const framePath = join48(framesDir, formatCaptureFrameName(frameIndex, frameExt));
100951
- const missing = !existsSync33(framePath) || statSync12(framePath).size <= 8;
101321
+ const missing = !existsSync33(framePath) || statSync13(framePath).size <= 8;
100952
101322
  if (missing && rangeStart === null) {
100953
101323
  rangeStart = frameIndex;
100954
101324
  } else if (!missing && rangeStart !== null) {
@@ -101592,9 +101962,9 @@ function isRetryableEncoderDeath(err) {
101592
101962
  const match = ENCODER_DEATH_FRAME_RE.exec(err.message);
101593
101963
  return match !== null && Number(match[1]) >= 2;
101594
101964
  }
101595
- async function closeOrphanedProbeForRetry(probe2, closer, log, retryContext) {
101965
+ async function closeOrphanedProbeForRetry(probe, closer, log, retryContext) {
101596
101966
  try {
101597
- await closer(probe2);
101967
+ await closer(probe);
101598
101968
  } catch (closeErr) {
101599
101969
  log.warn(`[Render] probe close before ${retryContext} retry failed; continuing with retry`, {
101600
101970
  error: closeErr instanceof Error ? closeErr.message : String(closeErr)
@@ -101675,7 +102045,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
101675
102045
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve13(process.env.PRODUCER_RENDERS_DIR, "..") : resolve13(moduleDir, "../..");
101676
102046
  const debugDir = join48(producerRoot, ".debug");
101677
102047
  const outputDir = dirname20(outputPath);
101678
- if (!existsSync33(outputDir)) mkdirSync19(outputDir, { recursive: true });
102048
+ if (!existsSync33(outputDir)) mkdirSync20(outputDir, { recursive: true });
101679
102049
  const workDir = job.config.debug ? join48(debugDir, job.id) : mkdtempSync11(resolveRenderWorkDirPrefix(outputPath, job.id));
101680
102050
  const pipelineStart = Date.now();
101681
102051
  const baseLog = job.config.logger ?? defaultLogger;
@@ -101807,7 +102177,7 @@ async function executeRenderPipeline(input) {
101807
102177
  job.startedAt = /* @__PURE__ */ new Date();
101808
102178
  assertNotAborted();
101809
102179
  assertConfiguredFfmpegBinariesExist();
101810
- if (!existsSync33(workDir)) mkdirSync19(workDir, { recursive: true });
102180
+ if (!existsSync33(workDir)) mkdirSync20(workDir, { recursive: true });
101811
102181
  if (job.config.debug) {
101812
102182
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
101813
102183
  }
@@ -101841,7 +102211,7 @@ async function executeRenderPipeline(input) {
101841
102211
  throw new Error(`Entry file not found: ${htmlPath}`);
101842
102212
  }
101843
102213
  assertNotAborted();
101844
- const rawEntry = readFileSync15(htmlPath, "utf-8");
102214
+ const rawEntry = readFileSync16(htmlPath, "utf-8");
101845
102215
  if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
101846
102216
  const wrapperPath = join48(workDir, "standalone-entry.html");
101847
102217
  const projectIndexPath = join48(projectDir, "index.html");
@@ -101851,7 +102221,7 @@ async function executeRenderPipeline(input) {
101851
102221
  );
101852
102222
  }
101853
102223
  const standaloneHtml = extractStandaloneEntryFromIndex(
101854
- readFileSync15(projectIndexPath, "utf-8"),
102224
+ readFileSync16(projectIndexPath, "utf-8"),
101855
102225
  entryFile,
101856
102226
  rawEntry
101857
102227
  );
@@ -101860,7 +102230,7 @@ async function executeRenderPipeline(input) {
101860
102230
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
101861
102231
  );
101862
102232
  }
101863
- writeFileSync11(wrapperPath, standaloneHtml, "utf-8");
102233
+ writeFileSync12(wrapperPath, standaloneHtml, "utf-8");
101864
102234
  htmlPath = wrapperPath;
101865
102235
  log.info("Extracted standalone entry from index.html host context", {
101866
102236
  entryFile
@@ -102145,7 +102515,7 @@ async function executeRenderPipeline(input) {
102145
102515
  throw new Error("File server failed to initialize before frame capture");
102146
102516
  }
102147
102517
  const framesDir = join48(workDir, "captured-frames");
102148
- if (!existsSync33(framesDir)) mkdirSync19(framesDir, { recursive: true });
102518
+ if (!existsSync33(framesDir)) mkdirSync20(framesDir, { recursive: true });
102149
102519
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress);
102150
102520
  const resolvedBrowserGpuMode = await resolveBrowserGpuMode(cfg.browserGpuMode, {
102151
102521
  chromePath: resolveHeadlessShellPath(cfg),
@@ -103136,7 +103506,7 @@ async function executeRenderPipeline(input) {
103136
103506
  "drawElement capture failed; retrying with a fresh screenshot session"
103137
103507
  );
103138
103508
  rmSync15(framesDir, { recursive: true, force: true });
103139
- mkdirSync19(framesDir, { recursive: true });
103509
+ mkdirSync20(framesDir, { recursive: true });
103140
103510
  resetCaptureAttemptProgress(job);
103141
103511
  dedupPerfs.length = 0;
103142
103512
  cfg.useDrawElement = false;
@@ -103334,7 +103704,7 @@ async function executeRenderPipeline(input) {
103334
103704
  job.perfSummary = perfSummary;
103335
103705
  if (job.config.debug) {
103336
103706
  try {
103337
- writeFileSync11(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
103707
+ writeFileSync12(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
103338
103708
  } catch (err) {
103339
103709
  log.debug("Failed to write perf summary", {
103340
103710
  perfOutputPath,
@@ -103684,10 +104054,10 @@ init_src();
103684
104054
  // ../producer/src/server.ts
103685
104055
  import {
103686
104056
  existsSync as existsSync36,
103687
- mkdirSync as mkdirSync20,
103688
- statSync as statSync14,
104057
+ mkdirSync as mkdirSync21,
104058
+ statSync as statSync15,
103689
104059
  mkdtempSync as mkdtempSync12,
103690
- writeFileSync as writeFileSync12,
104060
+ writeFileSync as writeFileSync13,
103691
104061
  rmSync as rmSync16,
103692
104062
  createReadStream as createReadStream5
103693
104063
  } from "fs";
@@ -103703,14 +104073,14 @@ import { serve as serve3 } from "@hono/node-server";
103703
104073
  init_dist3();
103704
104074
  import {
103705
104075
  existsSync as existsSync34,
103706
- readFileSync as readFileSync16,
103707
- statSync as statSync13,
104076
+ readFileSync as readFileSync17,
104077
+ statSync as statSync14,
103708
104078
  openSync as openSync8,
103709
104079
  fstatSync as fstatSync5,
103710
104080
  closeSync as closeSync9,
103711
104081
  constants as constants5
103712
104082
  } from "fs";
103713
- import { resolve as resolve14, join as join49, relative as relative5, isAbsolute as isAbsolute5, sep as sep4 } from "path";
104083
+ import { resolve as resolve14, join as join49, relative as relative6, isAbsolute as isAbsolute6, sep as sep4 } from "path";
103714
104084
  function isStringRecord(value) {
103715
104085
  if (!value || typeof value !== "object" || Array.isArray(value)) {
103716
104086
  return false;
@@ -103743,19 +104113,19 @@ function readEntryHtml(filePath) {
103743
104113
  } catch (error) {
103744
104114
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
103745
104115
  return null;
103746
- if (!statSync13(filePath, { throwIfNoEntry: false })?.isFile()) return null;
104116
+ if (!statSync14(filePath, { throwIfNoEntry: false })?.isFile()) return null;
103747
104117
  throw error;
103748
104118
  }
103749
104119
  try {
103750
104120
  if (!fstatSync5(fd).isFile()) return null;
103751
- return readFileSync16(fd, "utf-8");
104121
+ return readFileSync17(fd, "utf-8");
103752
104122
  } finally {
103753
104123
  closeSync9(fd);
103754
104124
  }
103755
104125
  }
103756
104126
  function readProjectEntryFile(projectDir, preferredEntryFile) {
103757
104127
  const absProjectDir = resolve14(projectDir);
103758
- if (!existsSync34(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
104128
+ if (!existsSync34(absProjectDir) || !statSync14(absProjectDir).isDirectory()) {
103759
104129
  return { error: `Project directory not found: ${absProjectDir}` };
103760
104130
  }
103761
104131
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -103763,8 +104133,8 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
103763
104133
  );
103764
104134
  for (const entryFile of entryCandidates) {
103765
104135
  const absoluteEntryPath = resolve14(absProjectDir, entryFile);
103766
- const entryRelativePath = relative5(absProjectDir, absoluteEntryPath);
103767
- if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep4}`) || isAbsolute5(entryRelativePath)) {
104136
+ const entryRelativePath = relative6(absProjectDir, absoluteEntryPath);
104137
+ if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep4}`) || isAbsolute6(entryRelativePath)) {
103768
104138
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
103769
104139
  }
103770
104140
  const html = readEntryHtml(absoluteEntryPath);
@@ -104034,7 +104404,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
104034
104404
  const { input, cleanupProjectDir } = prepared;
104035
104405
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
104036
104406
  const outputDir = dirname22(absoluteOutputPath);
104037
- if (!existsSync36(outputDir)) mkdirSync20(outputDir, { recursive: true });
104407
+ if (!existsSync36(outputDir)) mkdirSync21(outputDir, { recursive: true });
104038
104408
  return { input, cleanupProjectDir, absoluteOutputPath };
104039
104409
  }
104040
104410
  function validateRenderOverrides(body) {
@@ -104072,7 +104442,7 @@ function prepareProjectDirectory(projectDir, options) {
104072
104442
  const candidate = nonEmptyString2(projectDir);
104073
104443
  if (!candidate) return null;
104074
104444
  const absProjectDir = resolve15(candidate);
104075
- if (!existsSync36(absProjectDir) || !statSync14(absProjectDir).isDirectory()) {
104445
+ if (!existsSync36(absProjectDir) || !statSync15(absProjectDir).isDirectory()) {
104076
104446
  return { error: `Project directory not found: ${absProjectDir}` };
104077
104447
  }
104078
104448
  const entry = options.entryFile || "index.html";
@@ -104102,7 +104472,7 @@ async function resolveInlineRenderHtml(body) {
104102
104472
  function materializeInlineProject(html, options) {
104103
104473
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir8();
104104
104474
  const tempProjectDir = mkdtempSync12(join51(tempRoot, "producer-project-"));
104105
- writeFileSync12(join51(tempProjectDir, "index.html"), html, "utf-8");
104475
+ writeFileSync13(join51(tempProjectDir, "index.html"), html, "utf-8");
104106
104476
  return {
104107
104477
  prepared: {
104108
104478
  input: { projectDir: tempProjectDir, ...options },
@@ -104168,7 +104538,7 @@ function cleanupTempDir(dir, log) {
104168
104538
  }
104169
104539
  }
104170
104540
  function outputFileSize(path) {
104171
- return existsSync36(path) ? statSync14(path).size : 0;
104541
+ return existsSync36(path) ? statSync15(path).size : 0;
104172
104542
  }
104173
104543
  function createBlockingProgressReporter(log, requestId) {
104174
104544
  let lastLoggedPct = -10;
@@ -104468,7 +104838,7 @@ function createRenderHandlers(options = {}) {
104468
104838
  store.delete(token);
104469
104839
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
104470
104840
  }
104471
- const stats = statSync14(artifact.path);
104841
+ const stats = statSync15(artifact.path);
104472
104842
  return new Response(createReadStream5(artifact.path), {
104473
104843
  headers: {
104474
104844
  "content-type": "video/mp4",
@@ -104552,12 +104922,12 @@ init_src();
104552
104922
  // ../producer/src/services/distributed/plan.ts
104553
104923
  init_dist2();
104554
104924
  init_src();
104555
- import { cpSync as cpSync2, existsSync as existsSync39, mkdirSync as mkdirSync22, renameSync as renameSync8, rmSync as rmSync17, writeFileSync as writeFileSync14 } from "fs";
104556
- import { join as join55, relative as relative8, sep as sep5 } from "path";
104925
+ import { cpSync as cpSync2, existsSync as existsSync39, mkdirSync as mkdirSync23, renameSync as renameSync8, rmSync as rmSync17, writeFileSync as writeFileSync15 } from "fs";
104926
+ import { join as join55, relative as relative9, sep as sep5 } from "path";
104557
104927
 
104558
104928
  // ../producer/src/services/render/stages/freezePlan.ts
104559
- import { existsSync as existsSync37, mkdirSync as mkdirSync21, readFileSync as readFileSync17, readdirSync as readdirSync13, writeFileSync as writeFileSync13 } from "fs";
104560
- import { join as join52, relative as relative6, resolve as resolve16 } from "path";
104929
+ import { existsSync as existsSync37, mkdirSync as mkdirSync22, readFileSync as readFileSync18, readdirSync as readdirSync13, writeFileSync as writeFileSync14 } from "fs";
104930
+ import { join as join52, relative as relative7, resolve as resolve16 } from "path";
104561
104931
 
104562
104932
  // ../producer/src/services/distributed/planProtocol.ts
104563
104933
  var PLAN_SCHEMA_VERSION = 1;
@@ -104600,7 +104970,7 @@ var FIELD_DELIMITER = Buffer.from([0]);
104600
104970
  init_src();
104601
104971
  import { execFile as execFileCallback } from "child_process";
104602
104972
  import { dirname as dirname23, join as join53 } from "path";
104603
- import { existsSync as existsSync38, readFileSync as readFileSync18 } from "fs";
104973
+ import { existsSync as existsSync38, readFileSync as readFileSync19 } from "fs";
104604
104974
  import { fileURLToPath as fileURLToPath8 } from "url";
104605
104975
  import { promisify as promisify5 } from "util";
104606
104976
  var PLAN_AUDIO_RELATIVE_PATH = MIXED_AUDIO_FILENAME;
@@ -104609,7 +104979,7 @@ var execFile4 = promisify5(execFileCallback);
104609
104979
  // ../producer/src/services/distributed/planSize.ts
104610
104980
  import { createHash as createHash13 } from "crypto";
104611
104981
  import { lstatSync as lstatSync5, readdirSync as readdirSync14 } from "fs";
104612
- import { extname as extname7, join as join54, relative as relative7 } from "path";
104982
+ import { extname as extname7, join as join54, relative as relative8 } from "path";
104613
104983
  var PLAN_ROOT_CATEGORY = {
104614
104984
  compiled: "compiled",
104615
104985
  [PLAN_AUDIO_RELATIVE_PATH]: "audio",
@@ -104633,32 +105003,32 @@ import {
104633
105003
  copyFileSync as copyFileSync8,
104634
105004
  existsSync as existsSync41,
104635
105005
  lstatSync as lstatSync6,
104636
- mkdirSync as mkdirSync24,
105006
+ mkdirSync as mkdirSync25,
104637
105007
  mkdtempSync as mkdtempSync14,
104638
105008
  openSync as openSync9,
104639
- readFileSync as readFileSync19,
105009
+ readFileSync as readFileSync20,
104640
105010
  readSync as readSync3,
104641
105011
  readdirSync as readdirSync15,
104642
105012
  renameSync as renameSync10,
104643
105013
  rmSync as rmSync19,
104644
- statSync as statSync16,
104645
- writeFileSync as writeFileSync16
105014
+ statSync as statSync17,
105015
+ writeFileSync as writeFileSync17
104646
105016
  } from "fs";
104647
105017
  import { createHash as createHash14 } from "crypto";
104648
105018
  import { tmpdir as tmpdir9 } from "os";
104649
- import { dirname as dirname25, isAbsolute as isAbsolute6, join as join58, relative as relative9, resolve as resolve17, sep as sep6 } from "path";
105019
+ import { dirname as dirname25, isAbsolute as isAbsolute7, join as join58, relative as relative10, resolve as resolve17, sep as sep6 } from "path";
104650
105020
 
104651
105021
  // ../producer/src/services/distributed/planV2Publisher.ts
104652
105022
  import {
104653
105023
  copyFileSync as copyFileSync7,
104654
105024
  existsSync as existsSync40,
104655
105025
  linkSync as linkSync3,
104656
- mkdirSync as mkdirSync23,
105026
+ mkdirSync as mkdirSync24,
104657
105027
  mkdtempSync as mkdtempSync13,
104658
105028
  renameSync as renameSync9,
104659
105029
  rmSync as rmSync18,
104660
- statSync as statSync15,
104661
- writeFileSync as writeFileSync15
105030
+ statSync as statSync16,
105031
+ writeFileSync as writeFileSync16
104662
105032
  } from "fs";
104663
105033
  import { dirname as dirname24, join as join57 } from "path";
104664
105034
 
@@ -104676,27 +105046,27 @@ init_dist2();
104676
105046
  import {
104677
105047
  cpSync as cpSync3,
104678
105048
  existsSync as existsSync42,
104679
- mkdirSync as mkdirSync25,
105049
+ mkdirSync as mkdirSync26,
104680
105050
  mkdtempSync as mkdtempSync15,
104681
- readFileSync as readFileSync20,
105051
+ readFileSync as readFileSync21,
104682
105052
  readdirSync as readdirSync16,
104683
105053
  rmSync as rmSync20,
104684
- statSync as statSync17,
104685
- writeFileSync as writeFileSync17
105054
+ statSync as statSync18,
105055
+ writeFileSync as writeFileSync18
104686
105056
  } from "fs";
104687
105057
  import { dirname as dirname26, join as join59 } from "path";
104688
105058
 
104689
105059
  // ../producer/src/services/distributed/renderChunk.ts
104690
105060
  init_src();
104691
105061
  import { randomBytes } from "crypto";
104692
- import { existsSync as existsSync43, mkdirSync as mkdirSync26, readFileSync as readFileSync21, readdirSync as readdirSync17, rmSync as rmSync21, writeFileSync as writeFileSync18 } from "fs";
105062
+ import { existsSync as existsSync43, mkdirSync as mkdirSync27, readFileSync as readFileSync22, readdirSync as readdirSync17, rmSync as rmSync21, writeFileSync as writeFileSync19 } from "fs";
104693
105063
  import { extname as extname8, join as join60 } from "path";
104694
105064
  init_src();
104695
105065
 
104696
105066
  // ../producer/src/services/distributed/projectHash.ts
104697
- import { readdirSync as readdirSync18, readFileSync as readFileSync22 } from "fs";
105067
+ import { readdirSync as readdirSync18, readFileSync as readFileSync23 } from "fs";
104698
105068
  import { createHash as createHash15 } from "crypto";
104699
- import { join as join62, relative as relative10 } from "path";
105069
+ import { join as join62, relative as relative11 } from "path";
104700
105070
 
104701
105071
  // src/fontLocalize.ts
104702
105072
  function safeVersion(version2) {
@@ -104737,8 +105107,8 @@ async function runFontLocalize(io, localize) {
104737
105107
  }
104738
105108
 
104739
105109
  // src/version.ts
104740
- var VERSION = true ? "0.8.81" : "0.0.0-dev";
104741
- var PRODUCER_VERSION = true ? "0.8.81" : "0.0.0-dev";
105110
+ var VERSION = true ? "0.8.83" : "0.0.0-dev";
105111
+ var PRODUCER_VERSION = true ? "0.8.83" : "0.0.0-dev";
104742
105112
 
104743
105113
  // src/fontLocalizeCli.ts
104744
105114
  async function readStdin() {