hyperframes 0.7.26 → 0.7.27

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.
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.7.26" : "0.0.0-dev";
53
+ VERSION = true ? "0.7.27" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -60925,7 +60925,7 @@ function formatHttpErrorDiagnostic(input2) {
60925
60925
  return `[Browser:HTTP${input2.status}] ${input2.method} ${sanitizeDiagnosticUrl(input2.url)} resource=${input2.resourceType}${statusText}`;
60926
60926
  }
60927
60927
  async function driveWarmupTicks(options, state) {
60928
- const sleep3 = options.sleep ?? realSleep;
60928
+ const sleep5 = options.sleep ?? realSleep;
60929
60929
  while (true) {
60930
60930
  if (options.lockWarmupTicks) {
60931
60931
  if (state.ticks >= LOCKED_WARMUP_TICKS) return;
@@ -60937,7 +60937,7 @@ async function driveWarmupTicks(options, state) {
60937
60937
  state.ticks += 1;
60938
60938
  } catch {
60939
60939
  }
60940
- await sleep3(options.intervalMs);
60940
+ await sleep5(options.intervalMs);
60941
60941
  }
60942
60942
  }
60943
60943
  function resolveCaptureSessionOptions(options, browserVersion, platform10 = process.platform) {
@@ -62729,7 +62729,7 @@ import { join as join6, dirname as dirname4, extname as extname2 } from "path";
62729
62729
  function appendEncodeTimeoutMessage(error, timedOut, timeoutMs) {
62730
62730
  if (!timedOut) return error;
62731
62731
  return `${error}
62732
- FFmpeg killed after exceeding ffmpegEncodeTimeout (${timeoutMs} ms)`;
62732
+ FFmpeg killed after exceeding ffmpegEncodeTimeout (${timeoutMs} ms). Long or high-frame-count renders may need more time: set FFMPEG_ENCODE_TIMEOUT_MS to a higher value (ms), or set PRODUCER_ENABLE_CHUNKED_ENCODE=true to encode in smaller chunks instead of one long-running ffmpeg process.`;
62733
62733
  }
62734
62734
  function isAacSidecar(audioPath) {
62735
62735
  return extname2(audioPath).toLowerCase() === ".aac";
@@ -66304,8 +66304,15 @@ function findRootTag(source) {
66304
66304
  const bodyEnd = bodyOpenMatch && bodyCloseMatch && bodyCloseMatch.index > bodyStart ? bodyCloseMatch.index : source.length;
66305
66305
  const bodyContent = bodyOpenMatch ? source.slice(bodyStart, bodyEnd) : source;
66306
66306
  const bodyTags = extractOpenTags(bodyContent);
66307
+ let skipBefore = -1;
66307
66308
  for (const tag of bodyTags) {
66309
+ if (tag.index < skipBefore) continue;
66308
66310
  if (["script", "style", "meta", "link", "title"].includes(tag.name)) continue;
66311
+ if (tag.name === "svg" && !readAttr(tag.raw, "data-composition-id") && !readAttr(tag.raw, "data-width") && !readAttr(tag.raw, "data-height")) {
66312
+ const closeMatch = /<\/svg\s*>/i.exec(bodyContent.slice(tag.index));
66313
+ skipBefore = closeMatch ? tag.index + closeMatch.index + closeMatch[0].length : Infinity;
66314
+ continue;
66315
+ }
66309
66316
  return { ...tag, index: tag.index + bodyStart };
66310
66317
  }
66311
66318
  return null;
@@ -67974,7 +67981,7 @@ var init_dist4 = __esm({
67974
67981
  TIMELINE_REGISTRY_INIT_PATTERN = /window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\{\}|window\.__timelines\s*=\s*\{\}|window\.__timelines\s*\?\?=\s*\{\}/i;
67975
67982
  TIMELINE_REGISTRY_OBJECT_LITERAL_PATTERN = /window\.__timelines\s*=\s*\{\s*(?:["'][^"']+["']|[A-Za-z_$][\w$]*)\s*:/i;
67976
67983
  TIMELINE_REGISTRY_ASSIGN_PATTERN = /window\.__timelines(?:\[[^\]]+\]|\.[A-Za-z_$][\w$]*)\s*=/i;
67977
- WINDOW_TIMELINE_ASSIGN_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=\s*([A-Za-z_$][\w$]*)/i;
67984
+ WINDOW_TIMELINE_ASSIGN_PATTERN = /window\.__timelines(?:\[\s*(?:["']([^"']+)["']|[A-Za-z_$][\w$.]*)\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=\s*([A-Za-z_$][\w$]*)/i;
67978
67985
  INVALID_SCRIPT_CLOSE_PATTERN = /<script[^>]*>[\s\S]*?<\s*\/\s*script(?!>)/i;
67979
67986
  TIMELINE_REGISTRY_KEY_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=/g;
67980
67987
  TIMELINE_REGISTRY_OBJECT_BODY_PATTERN = /window\.__timelines\s*=\s*\{([\s\S]*?)\}/i;
@@ -68708,12 +68715,14 @@ ${right.raw}`)
68708
68715
  (candidate) => isHardKillSet(candidate, win.targetSelector, boundary)
68709
68716
  );
68710
68717
  if (hasHardKill) continue;
68718
+ const exitClipInfo = clipIds.get(win.targetSelector) || clipClasses.get(win.targetSelector);
68719
+ const fixHint = exitClipInfo ? `"${win.targetSelector}" is a clip element \u2014 the framework already manages its visibility. Wrap the scene's content in an inner non-clip <div>, move the exit tween and the hard kill (\`tl.set("<inner-selector>", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\`) onto that wrapper instead.` : `Add \`tl.set("${win.targetSelector}", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\` after the exit tween.`;
68711
68720
  findings.push({
68712
68721
  code: "gsap_exit_missing_hard_kill",
68713
68722
  severity: "error",
68714
68723
  message: `GSAP exit on "${win.targetSelector}" ends at the ${boundary.toFixed(2)}s clip start boundary without a matching tl.set hard kill. Non-linear seeking can land after the fade and leave stale visibility state.`,
68715
68724
  selector: win.targetSelector,
68716
- fixHint: `Add \`tl.set("${win.targetSelector}", ${hiddenStateLiteral(win.propertyValues)}, ${boundary.toFixed(2)})\` after the exit tween.`,
68725
+ fixHint,
68717
68726
  snippet: truncateSnippet(win.raw)
68718
68727
  });
68719
68728
  }
@@ -68983,12 +68992,15 @@ ${right.raw}`)
68983
68992
  const killPattern = new RegExp(`["']#${id}["'][^)]*visibility\\s*:\\s*["']hidden["']`);
68984
68993
  const hasKill = killPattern.test(content);
68985
68994
  if (!hasKill) {
68995
+ const classes = (readAttr(tag.raw, "class") || "").split(/\s+/).filter(Boolean);
68996
+ const isClip = classes.includes("clip");
68997
+ const fixHint = isClip ? `"#${id}" is a clip element \u2014 the framework already manages its visibility. Wrap the scene's content in an inner non-clip <div>, move the exit tween and the hard kill (\`tl.set("<inner-selector>", { visibility: "hidden" }, <exit-end-time>)\`) onto that wrapper instead.` : `Add \`tl.set("#${id}", { visibility: "hidden" }, <exit-end-time>)\` after the scene's exit tweens.`;
68986
68998
  findings.push({
68987
68999
  code: "scene_layer_missing_visibility_kill",
68988
69000
  severity: "error",
68989
69001
  elementId: id,
68990
69002
  message: `Scene layer "#${id}" exits via opacity tween but has no visibility: hidden hard kill. When scrubbing or when tweens conflict, the scene may remain partially visible and overlap the next scene.`,
68991
- fixHint: `Add \`tl.set("#${id}", { visibility: "hidden" }, <exit-end-time>)\` after the scene's exit tweens.`
69003
+ fixHint
68992
69004
  });
68993
69005
  }
68994
69006
  }
@@ -70089,7 +70101,7 @@ ${allInlineStyles}`.replace(/\/\*[\s\S]*?\*\//g, "");
70089
70101
  code: "font_family_without_font_face",
70090
70102
  severity: "error",
70091
70103
  message: `Font ${undeclared.length === 1 ? "family" : "families"} used without @font-face declaration: ${undeclared.join(", ")}. These are not in the auto-resolved font list, so the renderer cannot supply them automatically. Text will fall back to a generic font, producing incorrect typography in the video.`,
70092
- fixHint: "Add @font-face { font-family: '...'; src: url('capture/assets/fonts/...woff2'); } for each font family, pointing to the captured .woff2 files."
70104
+ fixHint: "Add @font-face { font-family: '...'; src: url('capture/assets/fonts/...woff2'); } for each font family, pointing to the captured .woff2 files. For an OS-bundled system font (e.g. Hiragino Sans, Microsoft YaHei) that has no downloadable file, use src: local('Exact Font Name') instead \u2014 the declaration alone satisfies this check without needing a font file."
70093
70105
  });
70094
70106
  return findings;
70095
70107
  }
@@ -95031,10 +95043,11 @@ __export(manager_exports2, {
95031
95043
  clearBrowser: () => clearBrowser,
95032
95044
  ensureBrowser: () => ensureBrowser,
95033
95045
  findBrowser: () => findBrowser,
95034
- isLinuxArm: () => isLinuxArm
95046
+ isLinuxArm: () => isLinuxArm,
95047
+ withInstallLock: () => withInstallLock
95035
95048
  });
95036
95049
  import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
95037
- import { existsSync as existsSync31, readdirSync as readdirSync13, rmSync as rmSync6 } from "fs";
95050
+ import { existsSync as existsSync31, mkdirSync as mkdirSync15, readdirSync as readdirSync13, rmSync as rmSync6, statSync as statSync9 } from "fs";
95038
95051
  import { basename as basename4 } from "path";
95039
95052
  import { homedir as homedir9 } from "os";
95040
95053
  import { join as join27 } from "path";
@@ -95049,6 +95062,59 @@ Fix: run \`npm install\` or \`bun install\` to restore missing packages, then re
95049
95062
  );
95050
95063
  }
95051
95064
  }
95065
+ function isErrno(err, code) {
95066
+ return err.code === code;
95067
+ }
95068
+ function sleep(ms) {
95069
+ return new Promise((resolve62) => setTimeout(resolve62, ms));
95070
+ }
95071
+ function tryAcquireDirLock(lockDir) {
95072
+ try {
95073
+ mkdirSync15(lockDir, { recursive: false });
95074
+ return true;
95075
+ } catch (err) {
95076
+ if (!isErrno(err, "EEXIST")) throw err;
95077
+ return false;
95078
+ }
95079
+ }
95080
+ function reclaimStaleInstallLock(timeoutMs) {
95081
+ if (!tryAcquireDirLock(INSTALL_RECLAIM_LOCK_DIR)) return;
95082
+ try {
95083
+ const mtimeMs = statSync9(INSTALL_LOCK_DIR).mtimeMs;
95084
+ if (Date.now() - mtimeMs > timeoutMs) {
95085
+ rmSync6(INSTALL_LOCK_DIR, { recursive: true, force: true });
95086
+ }
95087
+ } catch (err) {
95088
+ if (!isErrno(err, "ENOENT")) throw err;
95089
+ } finally {
95090
+ rmSync6(INSTALL_RECLAIM_LOCK_DIR, { recursive: true, force: true });
95091
+ }
95092
+ }
95093
+ async function withInstallLock(fn, timeoutMs = INSTALL_LOCK_TIMEOUT_MS, pollMs = INSTALL_LOCK_POLL_MS) {
95094
+ if (!existsSync31(CACHE_DIR2)) mkdirSync15(CACHE_DIR2, { recursive: true });
95095
+ let deadline = Date.now() + timeoutMs;
95096
+ for (; ; ) {
95097
+ if (existsSync31(INSTALL_RECLAIM_LOCK_DIR)) {
95098
+ await sleep(pollMs);
95099
+ continue;
95100
+ }
95101
+ if (tryAcquireDirLock(INSTALL_LOCK_DIR)) {
95102
+ rmSync6(INSTALL_RECLAIM_LOCK_DIR, { recursive: true, force: true });
95103
+ break;
95104
+ }
95105
+ if (Date.now() > deadline) {
95106
+ reclaimStaleInstallLock(timeoutMs);
95107
+ deadline = Date.now() + timeoutMs;
95108
+ continue;
95109
+ }
95110
+ await sleep(pollMs);
95111
+ }
95112
+ try {
95113
+ return await fn();
95114
+ } finally {
95115
+ rmSync6(INSTALL_LOCK_DIR, { recursive: true, force: true });
95116
+ }
95117
+ }
95052
95118
  function whichBinary2(name) {
95053
95119
  try {
95054
95120
  const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
@@ -95199,7 +95265,7 @@ async function findBrowser() {
95199
95265
  `[browser] Cached binary missing at ${fromCache.staleHyperframesCachePath} \u2014 re-downloading...`
95200
95266
  );
95201
95267
  try {
95202
- return await downloadBrowser();
95268
+ return await withInstallLock(() => downloadBrowser());
95203
95269
  } catch (err) {
95204
95270
  const cause = normalizeErrorMessage(err);
95205
95271
  throw new Error(
@@ -95262,14 +95328,18 @@ async function ensureBrowser(options) {
95262
95328
  console.warn(
95263
95329
  `[browser] Cached binary missing at ${fromCache.staleHyperframesCachePath} \u2014 re-downloading...`
95264
95330
  );
95265
- return downloadBrowser(options);
95331
+ return withInstallLock(() => downloadBrowser(options));
95266
95332
  }
95267
95333
  const fromSystem = findFromSystem2();
95268
95334
  if (fromSystem) {
95269
95335
  warnSystemFallbackOnce(fromSystem.executablePath);
95270
95336
  return fromSystem;
95271
95337
  }
95272
- return downloadBrowser(options);
95338
+ return withInstallLock(async () => {
95339
+ const afterLock = await findFromCache();
95340
+ if (afterLock.result) return afterLock.result;
95341
+ return downloadBrowser(options);
95342
+ });
95273
95343
  }
95274
95344
  async function downloadBrowser(options) {
95275
95345
  if (isLinuxArm()) {
@@ -95299,7 +95369,7 @@ function clearBrowser() {
95299
95369
  function isLinuxArm() {
95300
95370
  return process.platform === "linux" && process.arch === "arm64";
95301
95371
  }
95302
- var CHROME_VERSION, CACHE_DIR2, PUPPETEER_CACHE_DIR, SYSTEM_CHROME_PATHS, _warnedSystemFallback;
95372
+ var CHROME_VERSION, CACHE_DIR2, PUPPETEER_CACHE_DIR, INSTALL_LOCK_DIR, INSTALL_RECLAIM_LOCK_DIR, INSTALL_LOCK_TIMEOUT_MS, INSTALL_LOCK_POLL_MS, SYSTEM_CHROME_PATHS, _warnedSystemFallback;
95303
95373
  var init_manager2 = __esm({
95304
95374
  "src/browser/manager.ts"() {
95305
95375
  "use strict";
@@ -95307,6 +95377,10 @@ var init_manager2 = __esm({
95307
95377
  CHROME_VERSION = "131.0.6778.85";
95308
95378
  CACHE_DIR2 = join27(homedir9(), ".cache", "hyperframes", "chrome");
95309
95379
  PUPPETEER_CACHE_DIR = join27(homedir9(), ".cache", "puppeteer", "chrome-headless-shell");
95380
+ INSTALL_LOCK_DIR = join27(CACHE_DIR2, ".install.lock");
95381
+ INSTALL_RECLAIM_LOCK_DIR = join27(CACHE_DIR2, ".install.reclaim.lock");
95382
+ INSTALL_LOCK_TIMEOUT_MS = 12e4;
95383
+ INSTALL_LOCK_POLL_MS = 200;
95310
95384
  SYSTEM_CHROME_PATHS = process.platform === "darwin" ? ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"] : [
95311
95385
  "/usr/bin/google-chrome",
95312
95386
  "/usr/bin/google-chrome-stable",
@@ -98587,7 +98661,7 @@ __export(deterministicFonts_exports, {
98587
98661
  resolveFontFamilyDeclarationFamilies: () => resolveFontFamilyDeclarationFamilies
98588
98662
  });
98589
98663
  import { createHash as createHash6 } from "crypto";
98590
- import { existsSync as existsSync33, mkdirSync as mkdirSync15, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
98664
+ import { existsSync as existsSync33, mkdirSync as mkdirSync16, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
98591
98665
  import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
98592
98666
  import { join as join28 } from "path";
98593
98667
  import postcss4 from "postcss";
@@ -98914,7 +98988,7 @@ function fontSlug(familyName) {
98914
98988
  function fontCacheDir(slug) {
98915
98989
  const dir = join28(GOOGLE_FONTS_CACHE_DIR, slug);
98916
98990
  if (!existsSync33(dir)) {
98917
- mkdirSync15(dir, { recursive: true });
98991
+ mkdirSync16(dir, { recursive: true });
98918
98992
  }
98919
98993
  return dir;
98920
98994
  }
@@ -99201,11 +99275,11 @@ import { spawn as spawn10 } from "child_process";
99201
99275
  import {
99202
99276
  copyFileSync as copyFileSync2,
99203
99277
  existsSync as existsSync34,
99204
- mkdirSync as mkdirSync16,
99278
+ mkdirSync as mkdirSync17,
99205
99279
  readFileSync as readFileSync21,
99206
99280
  renameSync as renameSync4,
99207
99281
  rmSync as rmSync7,
99208
- statSync as statSync9
99282
+ statSync as statSync10
99209
99283
  } from "fs";
99210
99284
  import { dirname as dirname13, isAbsolute as isAbsolute10, join as join29, resolve as resolve20 } from "path";
99211
99285
  function splitUrlSuffix2(src) {
@@ -99251,7 +99325,7 @@ function resolveGifSourcePath(src, options) {
99251
99325
  }
99252
99326
  function isUsableFile(path2) {
99253
99327
  try {
99254
- const stat3 = statSync9(path2);
99328
+ const stat3 = statSync10(path2);
99255
99329
  return stat3.isFile() && stat3.size > 0;
99256
99330
  } catch {
99257
99331
  return false;
@@ -99368,10 +99442,10 @@ async function runAnimatedGifTranscode(request) {
99368
99442
  }
99369
99443
  async function ensurePreparedWebm(input2) {
99370
99444
  if (!existsSync34(dirname13(input2.cachePath))) {
99371
- mkdirSync16(dirname13(input2.cachePath), { recursive: true });
99445
+ mkdirSync17(dirname13(input2.cachePath), { recursive: true });
99372
99446
  }
99373
99447
  if (!existsSync34(dirname13(input2.outputPath))) {
99374
- mkdirSync16(dirname13(input2.outputPath), { recursive: true });
99448
+ mkdirSync17(dirname13(input2.outputPath), { recursive: true });
99375
99449
  }
99376
99450
  if (!isUsableFile(input2.cachePath)) {
99377
99451
  const tmpPath = `${input2.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -99617,7 +99691,7 @@ var init_hf_early_stub_inline = __esm({
99617
99691
  // ../producer/src/services/fileServer.ts
99618
99692
  import { Hono as Hono3 } from "hono";
99619
99693
  import { serve as serve2 } from "@hono/node-server";
99620
- import { existsSync as existsSync36, realpathSync as realpathSync4, statSync as statSync10, createReadStream } from "fs";
99694
+ import { existsSync as existsSync36, realpathSync as realpathSync4, statSync as statSync11, createReadStream } from "fs";
99621
99695
  import { readFile as readFile2 } from "fs/promises";
99622
99696
  import { Readable as Readable2 } from "stream";
99623
99697
  import { join as join30, extname as extname9, resolve as resolve23, sep as sep5 } from "path";
@@ -99965,13 +100039,13 @@ function createFileServer2(options) {
99965
100039
  let filePath = null;
99966
100040
  if (compiledDir) {
99967
100041
  const candidate = join30(compiledDir, relativePath);
99968
- if (existsSync36(candidate) && isPathInside2(candidate, compiledDir) && statSync10(candidate).isFile()) {
100042
+ if (existsSync36(candidate) && isPathInside2(candidate, compiledDir) && statSync11(candidate).isFile()) {
99969
100043
  filePath = candidate;
99970
100044
  }
99971
100045
  }
99972
100046
  if (!filePath) {
99973
100047
  const candidate = join30(projectDir, relativePath);
99974
- if (existsSync36(candidate) && isPathInside2(candidate, projectDir) && statSync10(candidate).isFile()) {
100048
+ if (existsSync36(candidate) && isPathInside2(candidate, projectDir) && statSync11(candidate).isFile()) {
99975
100049
  filePath = candidate;
99976
100050
  }
99977
100051
  }
@@ -99993,7 +100067,7 @@ function createFileServer2(options) {
99993
100067
  html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
99994
100068
  return c3.text(html, 200, { "Content-Type": contentType });
99995
100069
  }
99996
- const stat3 = statSync10(filePath);
100070
+ const stat3 = statSync11(filePath);
99997
100071
  const totalSize2 = stat3.size;
99998
100072
  const rangeHeader = c3.req.header("range");
99999
100073
  const rangeRequest = parseRangeHeader(rangeHeader, totalSize2);
@@ -100245,7 +100319,7 @@ var init_paths = __esm({
100245
100319
  });
100246
100320
 
100247
100321
  // ../producer/src/services/render/shared.ts
100248
- import { copyFileSync as copyFileSync3, cpSync as cpSync2, existsSync as existsSync37, mkdirSync as mkdirSync17, symlinkSync as symlinkSync2, writeFileSync as writeFileSync12 } from "fs";
100322
+ import { copyFileSync as copyFileSync3, cpSync as cpSync2, existsSync as existsSync37, mkdirSync as mkdirSync18, symlinkSync as symlinkSync2, writeFileSync as writeFileSync12 } from "fs";
100249
100323
  import { basename as basename6, dirname as dirname15, isAbsolute as isAbsolute11, join as join33, relative as relative8, resolve as resolve24 } from "path";
100250
100324
  function writeFileExclusiveSync(path2, data2) {
100251
100325
  try {
@@ -100275,11 +100349,11 @@ function resolveDeviceScaleFactor(input2) {
100275
100349
  }
100276
100350
  function writeCompiledArtifacts(compiled, workDir, includeSummary) {
100277
100351
  const compileDir = join33(workDir, "compiled");
100278
- mkdirSync17(compileDir, { recursive: true });
100352
+ mkdirSync18(compileDir, { recursive: true });
100279
100353
  writeFileSync12(join33(compileDir, "index.html"), compiled.html, "utf-8");
100280
100354
  for (const [srcPath, html] of compiled.subCompositions) {
100281
100355
  const outPath = join33(compileDir, srcPath);
100282
- mkdirSync17(dirname15(outPath), { recursive: true });
100356
+ mkdirSync18(dirname15(outPath), { recursive: true });
100283
100357
  writeFileSync12(outPath, html, "utf-8");
100284
100358
  }
100285
100359
  for (const [relativePath, absolutePath] of compiled.externalAssets) {
@@ -100288,7 +100362,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
100288
100362
  console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
100289
100363
  continue;
100290
100364
  }
100291
- mkdirSync17(dirname15(outPath), { recursive: true });
100365
+ mkdirSync18(dirname15(outPath), { recursive: true });
100292
100366
  copyFileSync3(absolutePath, outPath);
100293
100367
  }
100294
100368
  if (includeSummary) {
@@ -100413,7 +100487,7 @@ var init_shared = __esm({
100413
100487
  };
100414
100488
  materializeFileSystem = {
100415
100489
  existsSync: existsSync37,
100416
- mkdirSync: mkdirSync17,
100490
+ mkdirSync: mkdirSync18,
100417
100491
  symlinkSync: symlinkSync2,
100418
100492
  cpSync: cpSync2
100419
100493
  };
@@ -101605,7 +101679,7 @@ var init_manualEditsRenderScript = __esm({
101605
101679
  });
101606
101680
 
101607
101681
  // ../producer/src/services/htmlCompiler.ts
101608
- import { readFileSync as readFileSync24, existsSync as existsSync38, mkdirSync as mkdirSync18 } from "fs";
101682
+ import { readFileSync as readFileSync24, existsSync as existsSync38, mkdirSync as mkdirSync19 } from "fs";
101609
101683
  import { join as join35, dirname as dirname16, resolve as resolve25, basename as basename7 } from "path";
101610
101684
  function parseSubCompHtmlForValidity(html) {
101611
101685
  return parseHTML(html).document;
@@ -101715,7 +101789,7 @@ function detectShaderTransitionUsage(html) {
101715
101789
  async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
101716
101790
  let filePath = src;
101717
101791
  if (isHttpUrl(src)) {
101718
- if (!existsSync38(downloadDir)) mkdirSync18(downloadDir, { recursive: true });
101792
+ if (!existsSync38(downloadDir)) mkdirSync19(downloadDir, { recursive: true });
101719
101793
  try {
101720
101794
  filePath = await downloadToTemp(src, downloadDir);
101721
101795
  } catch {
@@ -102202,7 +102276,7 @@ function collectExternalAssets(html, projectDir) {
102202
102276
  }
102203
102277
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
102204
102278
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
102205
- if (!existsSync38(remoteDir)) mkdirSync18(remoteDir, { recursive: true });
102279
+ if (!existsSync38(remoteDir)) mkdirSync19(remoteDir, { recursive: true });
102206
102280
  const urlToLocal = /* @__PURE__ */ new Map();
102207
102281
  await Promise.all(
102208
102282
  [...urlSet].map(async (url) => {
@@ -103465,6 +103539,7 @@ async function runAudioStage(input2) {
103465
103539
  const stage3Start = Date.now();
103466
103540
  const audioOutputPath = join39(workDir, "audio.aac");
103467
103541
  let hasAudio = false;
103542
+ let audioError;
103468
103543
  if (audios.length > 0) {
103469
103544
  const audioResult = await processCompositionAudio(
103470
103545
  audios,
@@ -103478,9 +103553,10 @@ async function runAudioStage(input2) {
103478
103553
  );
103479
103554
  assertNotAborted();
103480
103555
  hasAudio = audioResult.success;
103556
+ if (!hasAudio) audioError = audioResult.error ?? "audio mix failed for an unknown reason";
103481
103557
  }
103482
103558
  const audioProcessMs = Date.now() - stage3Start;
103483
- return { audioOutputPath, hasAudio, audioProcessMs };
103559
+ return { audioOutputPath, hasAudio, audioProcessMs, audioError };
103484
103560
  }
103485
103561
  var init_audioStage = __esm({
103486
103562
  "../producer/src/services/render/stages/audioStage.ts"() {
@@ -104551,7 +104627,7 @@ import {
104551
104627
  closeSync as closeSync3,
104552
104628
  constants as constants2,
104553
104629
  fstatSync as fstatSync2,
104554
- mkdirSync as mkdirSync19,
104630
+ mkdirSync as mkdirSync20,
104555
104631
  mkdtempSync as mkdtempSync2,
104556
104632
  openSync as openSync2,
104557
104633
  readFileSync as readFileSync25
@@ -104643,7 +104719,7 @@ async function extractHdrVideoFrames(args) {
104643
104719
  for (const [videoId, srcPath] of prep.hdrVideoSrcPaths) {
104644
104720
  const video = composition.videos.find((v2) => v2.id === videoId);
104645
104721
  if (!video) continue;
104646
- mkdirSync19(framesDir, { recursive: true });
104722
+ mkdirSync20(framesDir, { recursive: true });
104647
104723
  const frameDir = mkdtempSync2(join41(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
104648
104724
  const duration = video.end - video.start;
104649
104725
  const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
@@ -105362,7 +105438,7 @@ var init_captureHdrHybridLoop = __esm({
105362
105438
  });
105363
105439
 
105364
105440
  // ../producer/src/services/render/stages/captureHdrStage.ts
105365
- import { existsSync as existsSync41, mkdirSync as mkdirSync20 } from "fs";
105441
+ import { existsSync as existsSync41, mkdirSync as mkdirSync21 } from "fs";
105366
105442
  import { join as join46 } from "path";
105367
105443
  async function runCaptureHdrStage(input2) {
105368
105444
  const {
@@ -105524,7 +105600,7 @@ async function runCaptureHdrStage(input2) {
105524
105600
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
105525
105601
  const debugDumpDir = debugDumpEnabled ? join46(framesDir, "debug-composite") : null;
105526
105602
  if (debugDumpDir && !existsSync41(debugDumpDir)) {
105527
- mkdirSync20(debugDumpDir, { recursive: true });
105603
+ mkdirSync21(debugDumpDir, { recursive: true });
105528
105604
  }
105529
105605
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
105530
105606
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -105725,7 +105801,7 @@ var init_gifEncodeArgs = __esm({
105725
105801
  });
105726
105802
 
105727
105803
  // ../producer/src/services/render/stages/encodeStage.ts
105728
- import { copyFileSync as copyFileSync4, existsSync as existsSync43, mkdirSync as mkdirSync21, readdirSync as readdirSync14, rmSync as rmSync10, statSync as statSync11 } from "fs";
105804
+ import { copyFileSync as copyFileSync4, existsSync as existsSync43, mkdirSync as mkdirSync23, readdirSync as readdirSync14, rmSync as rmSync10, statSync as statSync12 } from "fs";
105729
105805
  import { dirname as dirname18, join as join48 } from "path";
105730
105806
  function resolveGifLoop(loop) {
105731
105807
  const resolved = loop ?? 0;
@@ -105785,7 +105861,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input2) {
105785
105861
  error: formatFfmpegError(gifResult.exitCode, gifResult.stderr)
105786
105862
  };
105787
105863
  }
105788
- const fileSize = existsSync43(outputPath) ? statSync11(outputPath).size : 0;
105864
+ const fileSize = existsSync43(outputPath) ? statSync12(outputPath).size : 0;
105789
105865
  return {
105790
105866
  success: true,
105791
105867
  outputPath,
@@ -105823,7 +105899,7 @@ async function runEncodeStage(input2) {
105823
105899
  const stage5Start = Date.now();
105824
105900
  if (isPngSequence) {
105825
105901
  updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
105826
- if (!existsSync43(outputPath)) mkdirSync21(outputPath, { recursive: true });
105902
+ if (!existsSync43(outputPath)) mkdirSync23(outputPath, { recursive: true });
105827
105903
  const captured = readdirSync14(framesDir).filter((name) => name.endsWith(".png")).sort();
105828
105904
  if (captured.length === 0) {
105829
105905
  throw new Error(
@@ -105967,12 +106043,12 @@ var init_assembleStage = __esm({
105967
106043
  // ../producer/src/services/renderOrchestrator.ts
105968
106044
  import {
105969
106045
  existsSync as existsSync44,
105970
- mkdirSync as mkdirSync23,
106046
+ mkdirSync as mkdirSync24,
105971
106047
  mkdtempSync as mkdtempSync3,
105972
106048
  readFileSync as readFileSync26,
105973
106049
  readdirSync as readdirSync15,
105974
106050
  rmSync as rmSync11,
105975
- statSync as statSync12,
106051
+ statSync as statSync13,
105976
106052
  writeFileSync as writeFileSync13,
105977
106053
  copyFileSync as copyFileSync5,
105978
106054
  appendFileSync
@@ -105995,7 +106071,7 @@ function sampleDirectoryBytes(dir) {
105995
106071
  for (const name of entries2) {
105996
106072
  const full2 = join49(current2, name);
105997
106073
  try {
105998
- const st3 = statSync12(full2);
106074
+ const st3 = statSync13(full2);
105999
106075
  if (st3.isDirectory()) {
106000
106076
  stack.push(full2);
106001
106077
  } else if (st3.isFile()) {
@@ -106350,7 +106426,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
106350
106426
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve26(process.env.PRODUCER_RENDERS_DIR, "..") : resolve26(moduleDir, "../..");
106351
106427
  const debugDir = join49(producerRoot, ".debug");
106352
106428
  const outputDir = dirname19(outputPath);
106353
- if (!existsSync44(outputDir)) mkdirSync23(outputDir, { recursive: true });
106429
+ if (!existsSync44(outputDir)) mkdirSync24(outputDir, { recursive: true });
106354
106430
  const workDir = job.config.debug ? join49(debugDir, job.id) : mkdtempSync3(join49(outputDir, `work-${job.id}-`));
106355
106431
  const pipelineStart = Date.now();
106356
106432
  const log2 = job.config.logger ?? defaultLogger;
@@ -106412,7 +106488,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
106412
106488
  job.startedAt = /* @__PURE__ */ new Date();
106413
106489
  assertNotAborted();
106414
106490
  assertConfiguredFfmpegBinariesExist();
106415
- if (!existsSync44(workDir)) mkdirSync23(workDir, { recursive: true });
106491
+ if (!existsSync44(workDir)) mkdirSync24(workDir, { recursive: true });
106416
106492
  if (job.config.debug) {
106417
106493
  const logPath = join49(workDir, "render.log");
106418
106494
  restoreLogger = installDebugLogger(logPath, log2);
@@ -106648,6 +106724,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
106648
106724
  );
106649
106725
  const { audioOutputPath, hasAudio } = audioResult;
106650
106726
  perfStages.audioProcessMs = audioResult.audioProcessMs;
106727
+ if (audioResult.audioError) {
106728
+ log2.warn(`[Render] Audio mix failed \u2014 output will be video-only: ${audioResult.audioError}`);
106729
+ }
106651
106730
  const stage4Start = Date.now();
106652
106731
  updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress);
106653
106732
  if (!fileServer) {
@@ -106674,7 +106753,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
106674
106753
  throw new Error("File server failed to initialize before frame capture");
106675
106754
  }
106676
106755
  const framesDir = join49(workDir, "captured-frames");
106677
- if (!existsSync44(framesDir)) mkdirSync23(framesDir, { recursive: true });
106756
+ if (!existsSync44(framesDir)) mkdirSync24(framesDir, { recursive: true });
106678
106757
  const resolvedBrowserGpuMode = await resolveBrowserGpuMode(cfg.browserGpuMode, {
106679
106758
  chromePath: resolveHeadlessShellPath(cfg),
106680
106759
  browserTimeout: cfg.browserTimeout
@@ -107264,7 +107343,7 @@ var init_config3 = __esm({
107264
107343
  });
107265
107344
 
107266
107345
  // ../producer/src/services/hyperframeLint.ts
107267
- import { existsSync as existsSync45, readFileSync as readFileSync27, statSync as statSync13 } from "fs";
107346
+ import { existsSync as existsSync45, readFileSync as readFileSync27, statSync as statSync14 } from "fs";
107268
107347
  import { resolve as resolve27, join as join50 } from "path";
107269
107348
  function isStringRecord2(value) {
107270
107349
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -107293,7 +107372,7 @@ function pickEntryFile(files, preferredEntryFile) {
107293
107372
  }
107294
107373
  function readProjectEntryFile(projectDir, preferredEntryFile) {
107295
107374
  const absProjectDir = resolve27(projectDir);
107296
- if (!existsSync45(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
107375
+ if (!existsSync45(absProjectDir) || !statSync14(absProjectDir).isDirectory()) {
107297
107376
  return { error: `Project directory not found: ${absProjectDir}` };
107298
107377
  }
107299
107378
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -107304,7 +107383,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
107304
107383
  if (!absoluteEntryPath.startsWith(absProjectDir)) {
107305
107384
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
107306
107385
  }
107307
- if (existsSync45(absoluteEntryPath) && statSync13(absoluteEntryPath).isFile()) {
107386
+ if (existsSync45(absoluteEntryPath) && statSync14(absoluteEntryPath).isFile()) {
107308
107387
  return {
107309
107388
  entryFile,
107310
107389
  html: readFileSync27(absoluteEntryPath, "utf-8"),
@@ -107495,8 +107574,8 @@ var init_semaphore = __esm({
107495
107574
  // ../producer/src/server.ts
107496
107575
  import {
107497
107576
  existsSync as existsSync47,
107498
- mkdirSync as mkdirSync24,
107499
- statSync as statSync14,
107577
+ mkdirSync as mkdirSync25,
107578
+ statSync as statSync15,
107500
107579
  mkdtempSync as mkdtempSync4,
107501
107580
  writeFileSync as writeFileSync14,
107502
107581
  rmSync as rmSync12,
@@ -107563,7 +107642,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log2) {
107563
107642
  const { input: input2, cleanupProjectDir } = prepared;
107564
107643
  const absoluteOutputPath = resolveOutputPath(input2.projectDir, input2.outputPath, rendersDir, log2);
107565
107644
  const outputDir = dirname21(absoluteOutputPath);
107566
- if (!existsSync47(outputDir)) mkdirSync24(outputDir, { recursive: true });
107645
+ if (!existsSync47(outputDir)) mkdirSync25(outputDir, { recursive: true });
107567
107646
  return { input: input2, cleanupProjectDir, absoluteOutputPath };
107568
107647
  }
107569
107648
  function validateRenderOverrides(body) {
@@ -107593,7 +107672,7 @@ async function prepareRenderBody(body) {
107593
107672
  const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
107594
107673
  if (projectDir) {
107595
107674
  const absProjectDir = resolve28(projectDir);
107596
- if (!existsSync47(absProjectDir) || !statSync14(absProjectDir).isDirectory()) {
107675
+ if (!existsSync47(absProjectDir) || !statSync15(absProjectDir).isDirectory()) {
107597
107676
  return { error: `Project directory not found: ${absProjectDir}` };
107598
107677
  }
107599
107678
  const entry = options.entryFile || "index.html";
@@ -107760,7 +107839,7 @@ function createRenderHandlers(options = {}) {
107760
107839
  log2.info(`render progress ${pct}%`, { requestId, stage: j3.currentStage, message });
107761
107840
  }
107762
107841
  });
107763
- const fileSize = existsSync47(absoluteOutputPath) ? statSync14(absoluteOutputPath).size : 0;
107842
+ const fileSize = existsSync47(absoluteOutputPath) ? statSync15(absoluteOutputPath).size : 0;
107764
107843
  const durationMs = Date.now() - t0;
107765
107844
  const outputToken = store.register(absoluteOutputPath);
107766
107845
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
@@ -107876,7 +107955,7 @@ function createRenderHandlers(options = {}) {
107876
107955
  },
107877
107956
  abortController.signal
107878
107957
  );
107879
- const fileSize = existsSync47(absoluteOutputPath) ? statSync14(absoluteOutputPath).size : 0;
107958
+ const fileSize = existsSync47(absoluteOutputPath) ? statSync15(absoluteOutputPath).size : 0;
107880
107959
  const outputToken = store.register(absoluteOutputPath);
107881
107960
  const outputUrl = `${outputUrlPrefix}/${outputToken}`;
107882
107961
  log2.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
@@ -107939,7 +108018,7 @@ function createRenderHandlers(options = {}) {
107939
108018
  store.delete(token);
107940
108019
  return c3.json({ success: false, error: "Output artifact file missing" }, 404);
107941
108020
  }
107942
- const stats = statSync14(artifact.path);
108021
+ const stats = statSync15(artifact.path);
107943
108022
  return new Response(createReadStream2(artifact.path), {
107944
108023
  headers: {
107945
108024
  "content-type": "video/mp4",
@@ -108103,7 +108182,7 @@ var init_planHash = __esm({
108103
108182
  });
108104
108183
 
108105
108184
  // ../producer/src/services/render/stages/freezePlan.ts
108106
- import { existsSync as existsSync48, mkdirSync as mkdirSync25, readFileSync as readFileSync28, readdirSync as readdirSync16, writeFileSync as writeFileSync15 } from "fs";
108185
+ import { existsSync as existsSync48, mkdirSync as mkdirSync26, readFileSync as readFileSync28, readdirSync as readdirSync16, writeFileSync as writeFileSync15 } from "fs";
108107
108186
  import { join as join54, relative as relative9, resolve as resolve29 } from "path";
108108
108187
  function stripUndefined(value) {
108109
108188
  if (Array.isArray(value)) return value.map(stripUndefined);
@@ -108199,7 +108278,7 @@ async function freezePlan(input2) {
108199
108278
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
108200
108279
  }
108201
108280
  const metaDir = join54(planDir, "meta");
108202
- if (!existsSync48(metaDir)) mkdirSync25(metaDir, { recursive: true });
108281
+ if (!existsSync48(metaDir)) mkdirSync26(metaDir, { recursive: true });
108203
108282
  writeFileSync15(
108204
108283
  join54(metaDir, "composition.json"),
108205
108284
  `${JSON.stringify(composition, null, 2)}
@@ -108429,11 +108508,11 @@ var init_shared2 = __esm({
108429
108508
  import {
108430
108509
  cpSync as cpSync3,
108431
108510
  existsSync as existsSync50,
108432
- mkdirSync as mkdirSync26,
108511
+ mkdirSync as mkdirSync27,
108433
108512
  readdirSync as readdirSync17,
108434
108513
  renameSync as renameSync5,
108435
108514
  rmSync as rmSync13,
108436
- statSync as statSync15,
108515
+ statSync as statSync16,
108437
108516
  writeFileSync as writeFileSync16
108438
108517
  } from "fs";
108439
108518
  import { join as join56, relative as relative10, sep as sep6 } from "path";
@@ -108466,7 +108545,7 @@ function measurePlanDirBytes(planDir) {
108466
108545
  walk(full2);
108467
108546
  } else if (entry.isFile()) {
108468
108547
  try {
108469
- total += statSync15(full2).size;
108548
+ total += statSync16(full2).size;
108470
108549
  } catch {
108471
108550
  }
108472
108551
  }
@@ -108600,7 +108679,7 @@ async function plan(projectDir, config, planDir) {
108600
108679
  useGpu: false,
108601
108680
  browserGpuMode: "software"
108602
108681
  });
108603
- if (!existsSync50(planDir)) mkdirSync26(planDir, { recursive: true });
108682
+ if (!existsSync50(planDir)) mkdirSync27(planDir, { recursive: true });
108604
108683
  const log2 = config.logger ?? defaultLogger;
108605
108684
  const abortSignal = config.abortSignal;
108606
108685
  const assertNotAborted = () => {
@@ -108634,9 +108713,9 @@ async function plan(projectDir, config, planDir) {
108634
108713
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
108635
108714
  }
108636
108715
  const workDir = join56(planDir, ".plan-work");
108637
- if (!existsSync50(workDir)) mkdirSync26(workDir, { recursive: true });
108716
+ if (!existsSync50(workDir)) mkdirSync27(workDir, { recursive: true });
108638
108717
  const compiledDir = join56(workDir, "compiled");
108639
- mkdirSync26(compiledDir, { recursive: true });
108718
+ mkdirSync27(compiledDir, { recursive: true });
108640
108719
  cpSync3(projectDir, compiledDir, {
108641
108720
  recursive: true,
108642
108721
  dereference: true,
@@ -108729,13 +108808,16 @@ async function plan(projectDir, config, planDir) {
108729
108808
  abortSignal,
108730
108809
  assertNotAborted
108731
108810
  });
108811
+ if (audioResult.audioError) {
108812
+ log2.warn(`[Render] Audio mix failed \u2014 output will be video-only: ${audioResult.audioError}`);
108813
+ }
108732
108814
  const stagedVideoFrames = join56(compiledDir, "__hyperframes_video_frames");
108733
108815
  const videoFramesDst = join56(planDir, "video-frames");
108734
108816
  if (existsSync50(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
108735
108817
  if (existsSync50(stagedVideoFrames)) {
108736
108818
  renameSync5(stagedVideoFrames, videoFramesDst);
108737
108819
  } else {
108738
- mkdirSync26(videoFramesDst, { recursive: true });
108820
+ mkdirSync27(videoFramesDst, { recursive: true });
108739
108821
  }
108740
108822
  if (existsSync50(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
108741
108823
  renameSync5(compiledDir, finalCompiledDir);
@@ -108750,7 +108832,7 @@ async function plan(projectDir, config, planDir) {
108750
108832
  metadata: ext.metadata
108751
108833
  }))
108752
108834
  };
108753
- mkdirSync26(join56(planDir, "meta"), { recursive: true });
108835
+ mkdirSync27(join56(planDir, "meta"), { recursive: true });
108754
108836
  writeFileSync16(
108755
108837
  join56(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
108756
108838
  JSON.stringify(planVideosJson, null, 2),
@@ -108900,7 +108982,7 @@ var init_plan = __esm({
108900
108982
 
108901
108983
  // ../producer/src/services/distributed/renderChunk.ts
108902
108984
  import { randomBytes as randomBytes2 } from "crypto";
108903
- import { existsSync as existsSync51, mkdirSync as mkdirSync27, readFileSync as readFileSync30, readdirSync as readdirSync18, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
108985
+ import { existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync30, readdirSync as readdirSync18, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
108904
108986
  import { extname as extname10, join as join57 } from "path";
108905
108987
  function rebuildExtractedFramesFromPlanDir(planDir, videos) {
108906
108988
  const result = [];
@@ -109066,9 +109148,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
109066
109148
  "software"
109067
109149
  );
109068
109150
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes2(4).toString("hex")}`;
109069
- mkdirSync27(workDir, { recursive: true });
109151
+ mkdirSync28(workDir, { recursive: true });
109070
109152
  const framesDir = join57(workDir, "captured-frames");
109071
- mkdirSync27(framesDir, { recursive: true });
109153
+ mkdirSync28(framesDir, { recursive: true });
109072
109154
  const fileServer = await createFileServer2({
109073
109155
  projectDir: compiledDir,
109074
109156
  compiledDir,
@@ -109147,10 +109229,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath) {
109147
109229
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
109148
109230
  const videoOnlyPath = outputChunkPath;
109149
109231
  if (isPngSequence) {
109150
- if (!existsSync51(outputChunkPath)) mkdirSync27(outputChunkPath, { recursive: true });
109232
+ if (!existsSync51(outputChunkPath)) mkdirSync28(outputChunkPath, { recursive: true });
109151
109233
  } else {
109152
109234
  const outDir = join57(outputChunkPath, "..");
109153
- if (!existsSync51(outDir)) mkdirSync27(outDir, { recursive: true });
109235
+ if (!existsSync51(outDir)) mkdirSync28(outDir, { recursive: true });
109154
109236
  }
109155
109237
  const encodeStarted = Date.now();
109156
109238
  await runEncodeStage({
@@ -109589,11 +109671,11 @@ var init_audioPadTrim = __esm({
109589
109671
  import {
109590
109672
  cpSync as cpSync4,
109591
109673
  existsSync as existsSync53,
109592
- mkdirSync as mkdirSync28,
109674
+ mkdirSync as mkdirSync29,
109593
109675
  readFileSync as readFileSync31,
109594
109676
  readdirSync as readdirSync19,
109595
109677
  rmSync as rmSync16,
109596
- statSync as statSync16,
109678
+ statSync as statSync17,
109597
109679
  writeFileSync as writeFileSync18
109598
109680
  } from "fs";
109599
109681
  import { dirname as dirname23, join as join58 } from "path";
@@ -109626,11 +109708,11 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
109626
109708
  return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
109627
109709
  }
109628
109710
  if (!existsSync53(dirname23(outputPath))) {
109629
- mkdirSync28(dirname23(outputPath), { recursive: true });
109711
+ mkdirSync29(dirname23(outputPath), { recursive: true });
109630
109712
  }
109631
109713
  const workDir = `${outputPath}.assemble-work`;
109632
109714
  if (existsSync53(workDir)) rmSync16(workDir, { recursive: true, force: true });
109633
- mkdirSync28(workDir, { recursive: true });
109715
+ mkdirSync29(workDir, { recursive: true });
109634
109716
  try {
109635
109717
  const concatOutputPath = join58(workDir, `concat.${plan2.dimensions.format}`);
109636
109718
  const fpsArg = fpsToFfmpegArg({
@@ -109775,7 +109857,7 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
109775
109857
  });
109776
109858
  }
109777
109859
  }
109778
- const fileSize = existsSync53(outputPath) ? statSync16(outputPath).size : 0;
109860
+ const fileSize = existsSync53(outputPath) ? statSync17(outputPath).size : 0;
109779
109861
  return {
109780
109862
  outputPath,
109781
109863
  durationMs: Date.now() - start,
@@ -109785,10 +109867,10 @@ async function assemble(planDir, chunkPaths, audioPath, outputPath, options) {
109785
109867
  }
109786
109868
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
109787
109869
  if (existsSync53(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
109788
- mkdirSync28(outputPath, { recursive: true });
109870
+ mkdirSync29(outputPath, { recursive: true });
109789
109871
  let globalIdx = 0;
109790
109872
  for (const chunkDir of chunkPaths) {
109791
- if (!statSync16(chunkDir).isDirectory()) {
109873
+ if (!statSync17(chunkDir).isDirectory()) {
109792
109874
  throw new Error(
109793
109875
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
109794
109876
  );
@@ -109815,7 +109897,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
109815
109897
  let fileSize = 0;
109816
109898
  for (const name of readdirSync19(outputPath)) {
109817
109899
  try {
109818
- fileSize += statSync16(join58(outputPath, name)).size;
109900
+ fileSize += statSync17(join58(outputPath, name)).size;
109819
109901
  } catch {
109820
109902
  }
109821
109903
  }
@@ -109929,7 +110011,7 @@ __export(studioServer_exports, {
109929
110011
  });
109930
110012
  import { Hono as Hono5 } from "hono";
109931
110013
  import { streamSSE as streamSSE3 } from "hono/streaming";
109932
- import { existsSync as existsSync54, readFileSync as readFileSync34, writeFileSync as writeFileSync19, statSync as statSync17 } from "fs";
110014
+ import { existsSync as existsSync54, readFileSync as readFileSync34, writeFileSync as writeFileSync19, statSync as statSync18 } from "fs";
109933
110015
  import { resolve as resolve30, join as join60, basename as basename8 } from "path";
109934
110016
  function resolveDistDir() {
109935
110017
  return resolveStudioBundle().dir;
@@ -110385,7 +110467,7 @@ function createStudioServer(options) {
110385
110467
  });
110386
110468
  const serveStudioStaticFile = (c3) => {
110387
110469
  const filePath = resolve30(studioDir, c3.req.path.slice(1));
110388
- if (!existsSync54(filePath) || !statSync17(filePath).isFile()) return c3.text("not found", 404);
110470
+ if (!existsSync54(filePath) || !statSync18(filePath).isFile()) return c3.text("not found", 404);
110389
110471
  const content = readFileSync34(filePath);
110390
110472
  return new Response(content, {
110391
110473
  headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
@@ -110496,7 +110578,7 @@ __export(preview_exports, {
110496
110578
  examples: () => examples2
110497
110579
  });
110498
110580
  import { spawn as spawn12 } from "child_process";
110499
- import { existsSync as existsSync55, lstatSync as lstatSync3, symlinkSync as symlinkSync3, unlinkSync as unlinkSync4, readlinkSync, mkdirSync as mkdirSync29 } from "fs";
110581
+ import { existsSync as existsSync55, lstatSync as lstatSync3, symlinkSync as symlinkSync3, unlinkSync as unlinkSync4, readlinkSync, mkdirSync as mkdirSync30 } from "fs";
110500
110582
  import { resolve as resolve31, dirname as dirname24, basename as basename9, join as join61 } from "path";
110501
110583
  import { fileURLToPath as fileURLToPath8 } from "url";
110502
110584
  import { createRequire as createRequire2 } from "module";
@@ -110804,7 +110886,7 @@ function printStudioSummary(projectName, url, opts = {}) {
110804
110886
  }
110805
110887
  function linkProjectIntoStudioData(dir, projectsDir, projectName) {
110806
110888
  const symlinkPath = join61(projectsDir, projectName);
110807
- mkdirSync29(projectsDir, { recursive: true });
110889
+ mkdirSync30(projectsDir, { recursive: true });
110808
110890
  let createdSymlink = false;
110809
110891
  if (dir !== symlinkPath) {
110810
110892
  if (existsSync55(symlinkPath)) {
@@ -111251,7 +111333,7 @@ __export(init_exports, {
111251
111333
  });
111252
111334
  import {
111253
111335
  existsSync as existsSync56,
111254
- mkdirSync as mkdirSync30,
111336
+ mkdirSync as mkdirSync31,
111255
111337
  copyFileSync as copyFileSync6,
111256
111338
  cpSync as cpSync5,
111257
111339
  writeFileSync as writeFileSync20,
@@ -111590,7 +111672,7 @@ function applyResolutionPreset(destDir, resolution) {
111590
111672
  }
111591
111673
  }
111592
111674
  async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds, tailwind = false, resolution) {
111593
- mkdirSync30(destDir, { recursive: true });
111675
+ mkdirSync31(destDir, { recursive: true });
111594
111676
  const templateDir = getStaticTemplateDir(templateId);
111595
111677
  if (existsSync56(join63(templateDir, "index.html"))) {
111596
111678
  cpSync5(templateDir, destDir, { recursive: true });
@@ -111832,7 +111914,7 @@ var init_init = __esm({
111832
111914
  console.error(c.error(`Audio file not found: ${audioFlag}`));
111833
111915
  process.exit(1);
111834
111916
  }
111835
- mkdirSync30(destDir2, { recursive: true });
111917
+ mkdirSync31(destDir2, { recursive: true });
111836
111918
  let localVideoName2;
111837
111919
  let videoDuration2;
111838
111920
  let sourceFilePath2;
@@ -111974,7 +112056,7 @@ var init_init = __esm({
111974
112056
  me("Setup cancelled.");
111975
112057
  process.exit(1);
111976
112058
  }
111977
- mkdirSync30(destDir, { recursive: true });
112059
+ mkdirSync31(destDir, { recursive: true });
111978
112060
  sourceFilePath = videoPath;
111979
112061
  const result = await handleVideoFile(videoPath, destDir, true);
111980
112062
  localVideoName = result.localVideoName;
@@ -111986,7 +112068,7 @@ var init_init = __esm({
111986
112068
  me("Setup cancelled.");
111987
112069
  process.exit(1);
111988
112070
  }
111989
- mkdirSync30(destDir, { recursive: true });
112071
+ mkdirSync31(destDir, { recursive: true });
111990
112072
  sourceFilePath = audioPath;
111991
112073
  copyFileSync6(audioPath, resolve33(destDir, basename10(audioPath)));
111992
112074
  R2.info(`Audio copied to ${c.accent(basename10(audioPath))}`);
@@ -112856,18 +112938,18 @@ function buildPresentPage(projectName, islandJson) {
112856
112938
  <meta charset="UTF-8" />
112857
112939
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
112858
112940
  <title>${escHtml(projectName)} \u2014 Presenter</title>
112941
+ <script>
112942
+ // Retitle the audience tab so the two tabs are distinguishable in the
112943
+ // browser tab strip and in Meet's "Share a tab" picker.
112944
+ if (new URLSearchParams(location.search).get("mode") === "audience") {
112945
+ document.title = ${JSON.stringify(projectName).replace(/</g, "\\u003c")} + " \u2014 Audience";
112946
+ }
112947
+ </script>
112859
112948
  <style>
112860
112949
  * { margin: 0; padding: 0; box-sizing: border-box; }
112861
112950
  html, body { height: 100%; background: #0a0a0a; overflow: hidden; }
112862
112951
  hyperframes-slideshow { display: block; position: relative; width: 100vw; height: 100vh; }
112863
112952
  hyperframes-player { position: absolute; inset: 0; }
112864
- #present-btn {
112865
- position: fixed; top: 18px; right: 18px; z-index: 99999;
112866
- font: 600 14px/1 system-ui, sans-serif; color: #0d1321;
112867
- background: #f4b740; border: none; border-radius: 999px;
112868
- padding: 11px 18px; cursor: pointer; box-shadow: 0 6px 20px rgba(0,0,0,.45);
112869
- }
112870
- #present-btn:hover { background: #ffcb5c; }
112871
112953
  </style>
112872
112954
  <script src="/player.js"></script>
112873
112955
  <script src="/slideshow.js"></script>
@@ -112879,28 +112961,8 @@ function buildPresentPage(projectName, islandJson) {
112879
112961
  ${islandJson}
112880
112962
  </script>
112881
112963
  </hyperframes-slideshow>
112882
- <button id="present-btn" type="button">\u25B6&nbsp; Present</button>
112883
- <script>
112884
- (function () {
112885
- // The audience window loads this same page with ?mode=audience; it must not
112886
- // show the Present button or it would recurse opening windows.
112887
- var isAudience = new URLSearchParams(location.search).get("mode") === "audience";
112888
- var btn = document.getElementById("present-btn");
112889
- if (isAudience) { if (btn) btn.remove(); return; }
112890
- function present() {
112891
- var ss = document.querySelector("hyperframes-slideshow");
112892
- if (ss && typeof ss.present === "function") {
112893
- ss.present();
112894
- if (btn) btn.style.display = "none";
112895
- }
112896
- }
112897
- if (btn) btn.addEventListener("click", present);
112898
- // 'P' opens presenter mode (window.open needs a user gesture, so a key/click).
112899
- window.addEventListener("keydown", function (e) {
112900
- if ((e.key === "p" || e.key === "P") && !e.metaKey && !e.ctrlKey) present();
112901
- });
112902
- })();
112903
- </script>
112964
+ <!-- Present CTA lives in the component's nav cluster (data-hf-present, plus
112965
+ the P key) \u2014 no page-level button, or we'd render two Present CTAs. -->
112904
112966
  <script>
112905
112967
  // Sound effects play HERE, in the parent document. The composition runs in the
112906
112968
  // player's iframe, which is autoplay-blocked without its own user gesture; the
@@ -113069,6 +113131,15 @@ var init_present = __esm({
113069
113131
  return;
113070
113132
  }
113071
113133
  const islandJson = islandMatch[1].trim();
113134
+ try {
113135
+ JSON.parse(islandJson);
113136
+ } catch (err) {
113137
+ R2.error(
113138
+ `Slideshow island in ${project.indexPath} is not valid JSON: ${err.message}`
113139
+ );
113140
+ process.exitCode = 1;
113141
+ return;
113142
+ }
113072
113143
  const { Hono: Hono6 } = await import("hono");
113073
113144
  const { createAdaptorServer } = await import("@hono/node-server");
113074
113145
  const { isSafePath: isSafePath2 } = await Promise.resolve().then(() => (init_studio_api(), studio_api_exports));
@@ -113110,7 +113181,15 @@ var init_present = __esm({
113110
113181
  console.log(` ${c.dim("Deck")} ${c.accent(project.name)}`);
113111
113182
  console.log(` ${c.dim("Present")} ${c.accent(url)}`);
113112
113183
  console.log();
113113
- console.log(` ${c.dim("Click \u25B6 Present (or press P) to open the audience display.")}`);
113184
+ console.log(
113185
+ ` ${c.dim("Click the Present control (or press P) to open the audience display.")}`
113186
+ );
113187
+ console.log(
113188
+ ` ${c.dim('Google Meet: share the AUDIENCE tab ("Share screen \u2192 A tab"), not a window or your whole screen.')}`
113189
+ );
113190
+ console.log(
113191
+ ` ${c.dim("Zoom desktop: drag the audience tab into its own window and share that window (keep it at least partly visible).")}`
113192
+ );
113114
113193
  console.log(` ${c.dim("Press Ctrl+C to stop")}`);
113115
113194
  console.log();
113116
113195
  if (args.open) {
@@ -113129,7 +113208,7 @@ var init_present = __esm({
113129
113208
 
113130
113209
  // src/utils/publishProject.ts
113131
113210
  import { basename as basename11, dirname as dirname27, join as join64, posix as posix4, relative as relative13, resolve as resolve39 } from "path";
113132
- import { existsSync as existsSync61, readdirSync as readdirSync23, readFileSync as readFileSync38, statSync as statSync18 } from "fs";
113211
+ import { existsSync as existsSync61, readdirSync as readdirSync23, readFileSync as readFileSync38, statSync as statSync19 } from "fs";
113133
113212
  import AdmZip from "adm-zip";
113134
113213
  function isRecord3(value) {
113135
113214
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -113236,7 +113315,7 @@ function collectProjectFiles(rootDir, currentDir, paths) {
113236
113315
  collectProjectFiles(rootDir, absolutePath, paths);
113237
113316
  continue;
113238
113317
  }
113239
- if (!statSync18(absolutePath).isFile()) continue;
113318
+ if (!statSync19(absolutePath).isFile()) continue;
113240
113319
  paths.push(relativePath);
113241
113320
  }
113242
113321
  }
@@ -113263,7 +113342,7 @@ function tryResolveExternal(ctx, rawPath, referrerAbsDir) {
113263
113342
  const absPath = resolve39(referrerAbsDir, rawPath);
113264
113343
  if (isPathInside(absPath, ctx.absProjectDir)) return null;
113265
113344
  try {
113266
- if (!existsSync61(absPath) || !statSync18(absPath).isFile()) return null;
113345
+ if (!existsSync61(absPath) || !statSync19(absPath).isFile()) return null;
113267
113346
  } catch {
113268
113347
  return null;
113269
113348
  }
@@ -114292,7 +114371,7 @@ __export(batchRender_exports, {
114292
114371
  resolveOutputTemplate: () => resolveOutputTemplate,
114293
114372
  runBatchRender: () => runBatchRender
114294
114373
  });
114295
- import { mkdirSync as mkdirSync31, readFileSync as readFileSync40, writeFileSync as writeFileSync21 } from "fs";
114374
+ import { mkdirSync as mkdirSync33, readFileSync as readFileSync40, writeFileSync as writeFileSync21 } from "fs";
114296
114375
  import { dirname as dirname28, join as join66, resolve as resolve43, sep as sep8 } from "path";
114297
114376
  function isRecord4(value) {
114298
114377
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -114477,7 +114556,7 @@ function summarizeManifest(manifest) {
114477
114556
  }
114478
114557
  function writeManifest(manifest) {
114479
114558
  summarizeManifest(manifest);
114480
- mkdirSync31(dirname28(manifest.manifestPath), { recursive: true });
114559
+ mkdirSync33(dirname28(manifest.manifestPath), { recursive: true });
114481
114560
  writeFileSync21(manifest.manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
114482
114561
  }
114483
114562
  function emitJsonEvent(event, json) {
@@ -114499,7 +114578,7 @@ async function renderBatchRow(row, manifest, options) {
114499
114578
  console.log(c.dim(`Batch row ${row.index}: ${row.outputPath}`));
114500
114579
  }
114501
114580
  try {
114502
- mkdirSync31(dirname28(row.outputPath), { recursive: true });
114581
+ mkdirSync33(dirname28(row.outputPath), { recursive: true });
114503
114582
  const result = await options.renderOne(row);
114504
114583
  manifestRow.status = "completed";
114505
114584
  manifestRow.durationMs = result.durationMs ?? null;
@@ -114640,7 +114719,7 @@ __export(render_exports, {
114640
114719
  renderLocal: () => renderLocal,
114641
114720
  resolveBrowserGpuForCli: () => resolveBrowserGpuForCli
114642
114721
  });
114643
- import { mkdirSync as mkdirSync33, readdirSync as readdirSync24, readFileSync as readFileSync41, statSync as statSync19, writeFileSync as writeFileSync23, rmSync as rmSync17 } from "fs";
114722
+ import { mkdirSync as mkdirSync34, readdirSync as readdirSync24, readFileSync as readFileSync41, statSync as statSync20, writeFileSync as writeFileSync23, rmSync as rmSync17 } from "fs";
114644
114723
  import { cpus as cpus4, freemem as freemem5, tmpdir as tmpdir5 } from "os";
114645
114724
  import { resolve as resolve44, dirname as dirname29, join as join67, basename as basename12 } from "path";
114646
114725
  import { execFileSync as execFileSync9, spawn as spawn14 } from "child_process";
@@ -114706,7 +114785,7 @@ function resolveDockerfilePath() {
114706
114785
  const devPath = resolve44(__dirname, "..", "src", "docker", "Dockerfile.render");
114707
114786
  for (const p2 of [builtPath, devPath]) {
114708
114787
  try {
114709
- statSync19(p2);
114788
+ statSync20(p2);
114710
114789
  return p2;
114711
114790
  } catch {
114712
114791
  continue;
@@ -114735,7 +114814,7 @@ function ensureDockerImage(version2, platform10, quiet) {
114735
114814
  if (!quiet) console.log(c.dim(` Building Docker image: ${tag} (${platform10})...`));
114736
114815
  const dockerfilePath = resolveDockerfilePath();
114737
114816
  const tmpDir = join67(tmpdir5(), `hyperframes-docker-${Date.now()}`);
114738
- mkdirSync33(tmpDir, { recursive: true });
114817
+ mkdirSync34(tmpDir, { recursive: true });
114739
114818
  writeFileSync23(join67(tmpDir, "Dockerfile"), readFileSync41(dockerfilePath));
114740
114819
  const targetArch = platform10 === "linux/arm64" ? "arm64" : "amd64";
114741
114820
  try {
@@ -115133,14 +115212,14 @@ function printRenderComplete(outputPath, elapsedMs, quiet, outputDurationSeconds
115133
115212
  let fileSize = "unknown";
115134
115213
  let isDirectory = false;
115135
115214
  try {
115136
- const stat3 = statSync19(outputPath);
115215
+ const stat3 = statSync20(outputPath);
115137
115216
  isDirectory = stat3.isDirectory();
115138
115217
  if (stat3.isDirectory()) {
115139
115218
  let total = 0;
115140
115219
  for (const entry of readdirSync24(outputPath, { withFileTypes: true })) {
115141
115220
  if (!entry.isFile()) continue;
115142
115221
  try {
115143
- total += statSync19(join67(outputPath, entry.name)).size;
115222
+ total += statSync20(join67(outputPath, entry.name)).size;
115144
115223
  } catch {
115145
115224
  }
115146
115225
  }
@@ -115560,7 +115639,7 @@ var init_render = __esm({
115560
115639
  const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
115561
115640
  const batchOutputTemplate = args.output ? args.output : join67(rendersDir, `${project.name}_${datePart}_${timePart}_{index}${ext}`);
115562
115641
  const outputPath = args.output ? resolve44(args.output) : join67(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
115563
- if (!batchPath) mkdirSync33(dirname29(outputPath), { recursive: true });
115642
+ if (!batchPath) mkdirSync34(dirname29(outputPath), { recursive: true });
115564
115643
  const useDocker = args.docker ?? false;
115565
115644
  const useGpu = args.gpu ?? false;
115566
115645
  const browserGpuArg = args["browser-gpu"];
@@ -115618,7 +115697,7 @@ var init_render = __esm({
115618
115697
  console.log(c.warn(" GIF output is capped at 30fps. Use --fps 15 for smaller files."));
115619
115698
  }
115620
115699
  const pageNavigationTimeoutMs = resolveBrowserTimeoutMsArg(args["browser-timeout"]);
115621
- const entryFile = resolveCompositionEntryArg(args.composition, project.dir, statSync19);
115700
+ const entryFile = resolveCompositionEntryArg(args.composition, project.dir, statSync20);
115622
115701
  let batchModule;
115623
115702
  let preparedBatch;
115624
115703
  if (batchPath) {
@@ -116171,7 +116250,7 @@ __export(beats_exports, {
116171
116250
  default: () => beats_default,
116172
116251
  examples: () => examples11
116173
116252
  });
116174
- import { existsSync as existsSync66, readFileSync as readFileSync44, mkdirSync as mkdirSync34, writeFileSync as writeFileSync24 } from "fs";
116253
+ import { existsSync as existsSync66, readFileSync as readFileSync44, mkdirSync as mkdirSync35, writeFileSync as writeFileSync24 } from "fs";
116175
116254
  import { resolve as resolve45, join as join69, dirname as dirname31 } from "path";
116176
116255
  function fail(message) {
116177
116256
  console.error(c.error(message));
@@ -116243,7 +116322,7 @@ var init_beats2 = __esm({
116243
116322
  fail(`No beats detected in ${rel} \u2014 nothing written. (Track may be silent/ambient.)`);
116244
116323
  }
116245
116324
  const outPath = join69(project.dir, "beats", `${rel}.json`);
116246
- mkdirSync34(dirname31(outPath), { recursive: true });
116325
+ mkdirSync35(dirname31(outPath), { recursive: true });
116247
116326
  writeFileSync24(outPath, serializeBeats(result.beatTimes, result.beatStrengths, rel));
116248
116327
  report(`beats/${rel}.json`, result, Boolean(args.json));
116249
116328
  }
@@ -116257,10 +116336,10 @@ __export(staticProjectServer_exports, {
116257
116336
  serveStaticProjectHtml: () => serveStaticProjectHtml
116258
116337
  });
116259
116338
  import { createServer } from "http";
116260
- import { createReadStream as createReadStream3, existsSync as existsSync67, statSync as statSync20 } from "fs";
116339
+ import { createReadStream as createReadStream3, existsSync as existsSync67, statSync as statSync21 } from "fs";
116261
116340
  import { isAbsolute as isAbsolute13, relative as relative14, resolve as resolve46 } from "path";
116262
116341
  function serveFileWithRange(filePath, rangeHeader, res) {
116263
- const size = statSync20(filePath).size;
116342
+ const size = statSync21(filePath).size;
116264
116343
  const headers = {
116265
116344
  "Content-Type": getMimeType(filePath),
116266
116345
  "Accept-Ranges": "bytes"
@@ -119459,7 +119538,7 @@ __export(keyframes_exports, {
119459
119538
  examples: () => examples14,
119460
119539
  surfaceComposition: () => surfaceComposition
119461
119540
  });
119462
- import { existsSync as existsSync70, readFileSync as readFileSync47, statSync as statSync21 } from "fs";
119541
+ import { existsSync as existsSync70, readFileSync as readFileSync47, statSync as statSync23 } from "fs";
119463
119542
  import { resolve as resolve47, dirname as dirname33, basename as basename14 } from "path";
119464
119543
  function inlineScriptText(html) {
119465
119544
  const doc = new DOMParser().parseFromString(html, "text/html");
@@ -119941,7 +120020,7 @@ function resolveScope(args) {
119941
120020
  let comps;
119942
120021
  let projectName;
119943
120022
  let projectDir;
119944
- if (raw && raw.endsWith(".html") && existsSync70(raw) && statSync21(raw).isFile()) {
120023
+ if (raw && raw.endsWith(".html") && existsSync70(raw) && statSync23(raw).isFile()) {
119945
120024
  comps = [surfaceComposition(readFileSync47(raw, "utf-8"), basename14(raw), raw)];
119946
120025
  projectName = basename14(raw);
119947
120026
  projectDir = dirname33(raw);
@@ -120151,7 +120230,7 @@ __export(info_exports, {
120151
120230
  examples: () => examples15,
120152
120231
  orientation: () => orientation
120153
120232
  });
120154
- import { readFileSync as readFileSync48, readdirSync as readdirSync26, statSync as statSync23 } from "fs";
120233
+ import { readFileSync as readFileSync48, readdirSync as readdirSync26, statSync as statSync24 } from "fs";
120155
120234
  import { join as join73 } from "path";
120156
120235
  function orientation(width, height) {
120157
120236
  if (width > height) return "landscape";
@@ -120170,7 +120249,7 @@ function totalSize(dir) {
120170
120249
  if (entry.isDirectory()) {
120171
120250
  total += totalSize(path2);
120172
120251
  } else {
120173
- total += statSync23(path2).size;
120252
+ total += statSync24(path2).size;
120174
120253
  }
120175
120254
  }
120176
120255
  return total;
@@ -120433,7 +120512,7 @@ __export(benchmark_exports, {
120433
120512
  default: () => benchmark_default,
120434
120513
  examples: () => examples17
120435
120514
  });
120436
- import { existsSync as existsSync73, statSync as statSync24 } from "fs";
120515
+ import { existsSync as existsSync73, statSync as statSync25 } from "fs";
120437
120516
  import { resolve as resolve49, join as join74 } from "path";
120438
120517
  var examples17, FPS_30, FPS_60, DEFAULT_CONFIGS, benchmark_default;
120439
120518
  var init_benchmark = __esm({
@@ -120527,7 +120606,7 @@ var init_benchmark = __esm({
120527
120606
  const elapsedMs = Date.now() - startTime;
120528
120607
  let fileSize = null;
120529
120608
  if (existsSync73(outputPath)) {
120530
- const stat3 = statSync24(outputPath);
120609
+ const stat3 = statSync25(outputPath);
120531
120610
  fileSize = stat3.size;
120532
120611
  }
120533
120612
  runs.push({ elapsedMs, fileSize });
@@ -120784,7 +120863,7 @@ __export(manager_exports3, {
120784
120863
  modelPath: () => modelPath,
120785
120864
  selectProviders: () => selectProviders
120786
120865
  });
120787
- import { existsSync as existsSync74, mkdirSync as mkdirSync35 } from "fs";
120866
+ import { existsSync as existsSync74, mkdirSync as mkdirSync36 } from "fs";
120788
120867
  import { homedir as homedir11, platform as platform8, arch } from "os";
120789
120868
  import { join as join75 } from "path";
120790
120869
  function isDevice(value) {
@@ -120831,7 +120910,7 @@ function modelPath(model = DEFAULT_MODEL2) {
120831
120910
  async function ensureModel2(model = DEFAULT_MODEL2, options) {
120832
120911
  const dest = modelPath(model);
120833
120912
  if (existsSync74(dest)) return dest;
120834
- mkdirSync35(MODELS_DIR2, { recursive: true });
120913
+ mkdirSync36(MODELS_DIR2, { recursive: true });
120835
120914
  options?.onProgress?.(`Downloading ${model} weights (~168 MB)...`);
120836
120915
  await downloadFile(MODEL_URLS[model], dest);
120837
120916
  if (!existsSync74(dest)) {
@@ -121783,7 +121862,7 @@ var init_transcribe2 = __esm({
121783
121862
  });
121784
121863
 
121785
121864
  // src/tts/manager.ts
121786
- import { existsSync as existsSync77, mkdirSync as mkdirSync36 } from "fs";
121865
+ import { existsSync as existsSync77, mkdirSync as mkdirSync37 } from "fs";
121787
121866
  import { homedir as homedir12 } from "os";
121788
121867
  import { join as join77 } from "path";
121789
121868
  function inferLangFromVoiceId(voiceId) {
@@ -121802,7 +121881,7 @@ async function ensureModel3(model = DEFAULT_MODEL3, options) {
121802
121881
  `Unknown TTS model: ${model}. Available: ${Object.keys(MODEL_URLS2).join(", ")}`
121803
121882
  );
121804
121883
  }
121805
- mkdirSync36(MODELS_DIR3, { recursive: true });
121884
+ mkdirSync37(MODELS_DIR3, { recursive: true });
121806
121885
  options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
121807
121886
  await downloadFile(url, modelPath2);
121808
121887
  if (!existsSync77(modelPath2)) {
@@ -121813,7 +121892,7 @@ async function ensureModel3(model = DEFAULT_MODEL3, options) {
121813
121892
  async function ensureVoices(options) {
121814
121893
  const voicesPath = join77(VOICES_DIR, "voices-v1.0.bin");
121815
121894
  if (existsSync77(voicesPath)) return voicesPath;
121816
- mkdirSync36(VOICES_DIR, { recursive: true });
121895
+ mkdirSync37(VOICES_DIR, { recursive: true });
121817
121896
  options?.onProgress?.("Downloading voice data (~27 MB)...");
121818
121897
  await downloadFile(VOICES_URL, voicesPath);
121819
121898
  if (!existsSync77(voicesPath)) {
@@ -121945,12 +122024,12 @@ __export(synthesize_exports, {
121945
122024
  synthesize: () => synthesize
121946
122025
  });
121947
122026
  import { execFileSync as execFileSync11 } from "child_process";
121948
- import { existsSync as existsSync78, writeFileSync as writeFileSync27, mkdirSync as mkdirSync37, readdirSync as readdirSync27, unlinkSync as unlinkSync5 } from "fs";
122027
+ import { existsSync as existsSync78, writeFileSync as writeFileSync27, mkdirSync as mkdirSync38, readdirSync as readdirSync27, unlinkSync as unlinkSync5 } from "fs";
121949
122028
  import { join as join78, dirname as dirname36, basename as basename15 } from "path";
121950
122029
  import { homedir as homedir13 } from "os";
121951
122030
  function ensureSynthScript() {
121952
122031
  if (!existsSync78(SCRIPT_PATH)) {
121953
- mkdirSync37(SCRIPT_DIR, { recursive: true });
122032
+ mkdirSync38(SCRIPT_DIR, { recursive: true });
121954
122033
  writeFileSync27(SCRIPT_PATH, SYNTH_SCRIPT);
121955
122034
  const currentName = basename15(SCRIPT_PATH);
121956
122035
  try {
@@ -121991,7 +122070,7 @@ async function synthesize(text2, outputPath, options) {
121991
122070
  ensureVoices({ onProgress: options?.onProgress })
121992
122071
  ]);
121993
122072
  const scriptPath = ensureSynthScript();
121994
- mkdirSync37(dirname36(outputPath), { recursive: true });
122073
+ mkdirSync38(dirname36(outputPath), { recursive: true });
121995
122074
  options?.onProgress?.(`Generating speech with voice ${voice} (${lang})...`);
121996
122075
  try {
121997
122076
  const stdout2 = execFileSync11(
@@ -123211,6 +123290,7 @@ var validate_exports = {};
123211
123290
  __export(validate_exports, {
123212
123291
  default: () => validate_default,
123213
123292
  extractCompositionErrorsFromLint: () => extractCompositionErrorsFromLint,
123293
+ raceMediaReady: () => raceMediaReady,
123214
123294
  shouldIgnoreRequestFailure: () => shouldIgnoreRequestFailure
123215
123295
  });
123216
123296
  import { existsSync as existsSync81, readFileSync as readFileSync53 } from "fs";
@@ -123247,13 +123327,49 @@ async function seekTo2(page, time) {
123247
123327
  }, time);
123248
123328
  await new Promise((r2) => setTimeout(r2, SEEK_SETTLE_MS2));
123249
123329
  }
123250
- async function auditClipDurations(page, analyzeClipMediaFit2) {
123251
- const clips = await page.evaluate(() => {
123330
+ function raceMediaReady(el, deadlineMs) {
123331
+ if (Number.isFinite(el.duration) && el.duration > 0) return Promise.resolve();
123332
+ return new Promise((resolve62) => {
123333
+ const onReady = () => {
123334
+ el.removeEventListener("loadedmetadata", onReady);
123335
+ el.removeEventListener("error", onReady);
123336
+ clearTimeout(timer);
123337
+ resolve62();
123338
+ };
123339
+ el.addEventListener("loadedmetadata", onReady, { once: true });
123340
+ el.addEventListener("error", onReady, { once: true });
123341
+ const timer = setTimeout(onReady, Math.max(0, deadlineMs - Date.now()));
123342
+ });
123343
+ }
123344
+ async function auditClipDurations(page, analyzeClipMediaFit2, extraWaitMs) {
123345
+ const clips = await page.evaluate(async (maxWaitMs) => {
123346
+ const nodes = Array.from(
123347
+ document.querySelectorAll("video[data-duration], audio[data-duration]")
123348
+ );
123349
+ const deadline = Date.now() + maxWaitMs;
123350
+ await Promise.all(
123351
+ nodes.map((el) => {
123352
+ if (Number.isFinite(el.duration) && el.duration > 0) return Promise.resolve();
123353
+ return new Promise((resolve62) => {
123354
+ const cleanup = () => {
123355
+ el.removeEventListener("loadedmetadata", onReady);
123356
+ el.removeEventListener("error", onReady);
123357
+ clearTimeout(timer);
123358
+ };
123359
+ const onReady = () => {
123360
+ cleanup();
123361
+ resolve62();
123362
+ };
123363
+ el.addEventListener("loadedmetadata", onReady, { once: true });
123364
+ el.addEventListener("error", onReady, { once: true });
123365
+ const timer = setTimeout(onReady, Math.max(0, deadline - Date.now()));
123366
+ });
123367
+ })
123368
+ );
123252
123369
  const rows = [];
123253
- document.querySelectorAll("video[data-duration], audio[data-duration]").forEach((node) => {
123254
- const el = node;
123370
+ for (const el of nodes) {
123255
123371
  const slot = parseFloat(el.getAttribute("data-duration") ?? "");
123256
- if (!(slot > 0)) return;
123372
+ if (!(slot > 0)) continue;
123257
123373
  rows.push({
123258
123374
  id: el.id || el.getAttribute("src") || `(${el.tagName.toLowerCase()})`,
123259
123375
  kind: el.tagName === "AUDIO" ? "Audio" : "Video",
@@ -123262,9 +123378,9 @@ async function auditClipDurations(page, analyzeClipMediaFit2) {
123262
123378
  duration: el.duration,
123263
123379
  loop: el.loop || el.getAttribute("data-loop") === "true"
123264
123380
  });
123265
- });
123381
+ }
123266
123382
  return rows;
123267
- });
123383
+ }, extraWaitMs);
123268
123384
  const warnings = [];
123269
123385
  const unreadable = [];
123270
123386
  for (const clip of clips) {
@@ -123383,7 +123499,7 @@ async function validateInBrowser(project, opts) {
123383
123499
  });
123384
123500
  await page.goto(server.url, { waitUntil: "domcontentloaded", timeout: 1e4 });
123385
123501
  await new Promise((r2) => setTimeout(r2, opts.timeout ?? 3e3));
123386
- for (const w3 of await auditClipDurations(page, analyzeClipMediaFit2)) {
123502
+ for (const w3 of await auditClipDurations(page, analyzeClipMediaFit2, opts.timeout ?? 3e3)) {
123387
123503
  warnings.push(w3);
123388
123504
  }
123389
123505
  if (opts.contrast) {
@@ -129885,7 +130001,7 @@ var require_node_domexception = __commonJS({
129885
130001
  });
129886
130002
 
129887
130003
  // ../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js
129888
- import { statSync as statSync25, createReadStream as createReadStream4, promises as fs2 } from "fs";
130004
+ import { statSync as statSync26, createReadStream as createReadStream4, promises as fs2 } from "fs";
129889
130005
  import { basename as basename17 } from "path";
129890
130006
  var import_node_domexception, stat, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
129891
130007
  var init_from = __esm({
@@ -129895,10 +130011,10 @@ var init_from = __esm({
129895
130011
  init_file();
129896
130012
  init_fetch_blob();
129897
130013
  ({ stat } = fs2);
129898
- blobFromSync = (path2, type) => fromBlob(statSync25(path2), path2, type);
130014
+ blobFromSync = (path2, type) => fromBlob(statSync26(path2), path2, type);
129899
130015
  blobFrom = (path2, type) => stat(path2).then((stat3) => fromBlob(stat3, path2, type));
129900
130016
  fileFrom = (path2, type) => stat(path2).then((stat3) => fromFile(stat3, path2, type));
129901
- fileFromSync = (path2, type) => fromFile(statSync25(path2), path2, type);
130017
+ fileFromSync = (path2, type) => fromFile(statSync26(path2), path2, type);
129902
130018
  fromBlob = (stat3, path2, type = "") => new fetch_blob_default([new BlobDataItem({
129903
130019
  path: path2,
129904
130020
  size: stat3.size,
@@ -143689,7 +143805,7 @@ var require_websocket = __commonJS({
143689
143805
  var http4 = __require("http");
143690
143806
  var net2 = __require("net");
143691
143807
  var tls = __require("tls");
143692
- var { randomBytes: randomBytes4, createHash: createHash14 } = __require("crypto");
143808
+ var { randomBytes: randomBytes4, createHash: createHash15 } = __require("crypto");
143693
143809
  var { Duplex, Readable: Readable4 } = __require("stream");
143694
143810
  var { URL: URL2 } = __require("url");
143695
143811
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -144349,7 +144465,7 @@ var require_websocket = __commonJS({
144349
144465
  abortHandshake(websocket, socket, "Invalid Upgrade header");
144350
144466
  return;
144351
144467
  }
144352
- const digest = createHash14("sha1").update(key2 + GUID).digest("base64");
144468
+ const digest = createHash15("sha1").update(key2 + GUID).digest("base64");
144353
144469
  if (res.headers["sec-websocket-accept"] !== digest) {
144354
144470
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
144355
144471
  return;
@@ -144716,7 +144832,7 @@ var require_websocket_server = __commonJS({
144716
144832
  var EventEmitter = __require("events");
144717
144833
  var http4 = __require("http");
144718
144834
  var { Duplex } = __require("stream");
144719
- var { createHash: createHash14 } = __require("crypto");
144835
+ var { createHash: createHash15 } = __require("crypto");
144720
144836
  var extension2 = require_extension();
144721
144837
  var PerMessageDeflate2 = require_permessage_deflate();
144722
144838
  var subprotocol2 = require_subprotocol();
@@ -145017,7 +145133,7 @@ var require_websocket_server = __commonJS({
145017
145133
  );
145018
145134
  }
145019
145135
  if (this._state > RUNNING) return abortHandshake(socket, 503);
145020
- const digest = createHash14("sha1").update(key2 + GUID).digest("base64");
145136
+ const digest = createHash15("sha1").update(key2 + GUID).digest("base64");
145021
145137
  const headers = [
145022
145138
  "HTTP/1.1 101 Switching Protocols",
145023
145139
  "Upgrade: websocket",
@@ -156052,7 +156168,7 @@ async function uploadBlobInternal(file, uploadUrl, apiClient, httpOptions) {
156052
156168
  break;
156053
156169
  }
156054
156170
  retryCount++;
156055
- await sleep(currentDelayMs);
156171
+ await sleep2(currentDelayMs);
156056
156172
  currentDelayMs = currentDelayMs * DELAY_MULTIPLIER;
156057
156173
  }
156058
156174
  offset2 += chunkSize;
@@ -156069,7 +156185,7 @@ async function getBlobStat(file) {
156069
156185
  const fileStat = { size: file.size, type: file.type };
156070
156186
  return fileStat;
156071
156187
  }
156072
- function sleep(ms) {
156188
+ function sleep2(ms) {
156073
156189
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
156074
156190
  }
156075
156191
  function resolveCloudFlag(options) {
@@ -163294,7 +163410,7 @@ ${underline2}`);
163294
163410
  break;
163295
163411
  }
163296
163412
  retryCount++;
163297
- await sleep(currentDelayMs);
163413
+ await sleep2(currentDelayMs);
163298
163414
  currentDelayMs = currentDelayMs * DELAY_MULTIPLIER;
163299
163415
  }
163300
163416
  offset2 += bytesRead;
@@ -163465,7 +163581,7 @@ __export(snapshot_exports, {
163465
163581
  tailFrameTime: () => tailFrameTime
163466
163582
  });
163467
163583
  import { spawn as spawn16 } from "child_process";
163468
- import { existsSync as existsSync84, mkdtempSync as mkdtempSync5, readFileSync as readFileSync55, mkdirSync as mkdirSync38, rmSync as rmSync18, writeFileSync as writeFileSync29 } from "fs";
163584
+ import { existsSync as existsSync84, mkdtempSync as mkdtempSync5, readFileSync as readFileSync55, mkdirSync as mkdirSync39, rmSync as rmSync18, writeFileSync as writeFileSync29 } from "fs";
163469
163585
  import { tmpdir as tmpdir6 } from "os";
163470
163586
  import { resolve as resolve55, join as join83, relative as relative15, isAbsolute as isAbsolute14, basename as basename19 } from "path";
163471
163587
  function orbitStageSource() {
@@ -163667,7 +163783,7 @@ async function captureSnapshots(projectDir, opts) {
163667
163783
  }
163668
163784
  const cameraExpr = opts.angle && (opts.angle.yaw !== 0 || opts.angle.pitch !== 0) ? `(${orbitStageSource()})(${JSON.stringify(opts.angle)})` : null;
163669
163785
  const snapshotDir = opts.outputDir ?? join83(projectDir, "snapshots");
163670
- mkdirSync38(snapshotDir, { recursive: true });
163786
+ mkdirSync39(snapshotDir, { recursive: true });
163671
163787
  try {
163672
163788
  const { readdirSync: readdirSync35 } = await import("fs");
163673
163789
  for (const file of readdirSync35(snapshotDir)) {
@@ -163997,7 +164113,7 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
163997
164113
  });
163998
164114
 
163999
164115
  // src/capture/assetDownloader.ts
164000
- import { writeFileSync as writeFileSync30, mkdirSync as mkdirSync39 } from "fs";
164116
+ import { writeFileSync as writeFileSync30, mkdirSync as mkdirSync40 } from "fs";
164001
164117
  import { join as join84, extname as extname15 } from "path";
164002
164118
  import { createHash as createHash12 } from "crypto";
164003
164119
  function svgContentHashSlug(svgSource, isLogo) {
@@ -164006,10 +164122,10 @@ function svgContentHashSlug(svgSource, isLogo) {
164006
164122
  }
164007
164123
  async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks) {
164008
164124
  const assetsDir = join84(outputDir, "assets");
164009
- mkdirSync39(assetsDir, { recursive: true });
164125
+ mkdirSync40(assetsDir, { recursive: true });
164010
164126
  const assets = [];
164011
164127
  const downloadedUrls = /* @__PURE__ */ new Set();
164012
- mkdirSync39(join84(outputDir, "assets", "svgs"), { recursive: true });
164128
+ mkdirSync40(join84(outputDir, "assets", "svgs"), { recursive: true });
164013
164129
  const usedSvgNames = /* @__PURE__ */ new Set();
164014
164130
  for (let i2 = 0; i2 < tokens.svgs.length && i2 < 30; i2++) {
164015
164131
  const svg = tokens.svgs[i2];
@@ -164140,7 +164256,7 @@ function normalizeUrl(u) {
164140
164256
  }
164141
164257
  async function downloadAndRewriteFonts(css, outputDir) {
164142
164258
  const assetsDir = join84(outputDir, "assets", "fonts");
164143
- mkdirSync39(assetsDir, { recursive: true });
164259
+ mkdirSync40(assetsDir, { recursive: true });
164144
164260
  const fontUrlRegex = /url\(['"]?(https?:\/\/[^'")\s]+\.(?:woff2?|ttf|otf)[^'")\s]*?)['"]?\)/g;
164145
164261
  const fontUrls = /* @__PURE__ */ new Set();
164146
164262
  let match;
@@ -164333,7 +164449,7 @@ __export(video_exports, {
164333
164449
  runVideoMode: () => runVideoMode,
164334
164450
  safeFilename: () => safeFilename
164335
164451
  });
164336
- import { createWriteStream as createWriteStream4, existsSync as existsSync85, mkdirSync as mkdirSync40, readFileSync as readFileSync56, unlinkSync as unlinkSync7 } from "fs";
164452
+ import { createWriteStream as createWriteStream4, existsSync as existsSync85, mkdirSync as mkdirSync41, readFileSync as readFileSync56, unlinkSync as unlinkSync7 } from "fs";
164337
164453
  import { resolve as resolve56, join as join85, basename as basename20 } from "path";
164338
164454
  async function streamToFile(url, destPath) {
164339
164455
  const r2 = await safeFetch(url, {
@@ -164509,7 +164625,7 @@ async function runVideoMode(args) {
164509
164625
  return;
164510
164626
  }
164511
164627
  const outDir = isW2hLayout ? join85(projectDir, "capture", "assets", "videos") : join85(projectDir, "assets", "videos");
164512
- mkdirSync40(outDir, { recursive: true });
164628
+ mkdirSync41(outDir, { recursive: true });
164513
164629
  const fname = safeFilename(entry.filename || basename20(entry.url));
164514
164630
  const outPath = join85(outDir, fname);
164515
164631
  const relPath = isW2hLayout ? `capture/assets/videos/${fname}` : `assets/videos/${fname}`;
@@ -165280,7 +165396,8 @@ var init_designStyleExtractor = __esm({
165280
165396
  function rgbToHex(color) {
165281
165397
  if (!color) return "";
165282
165398
  if (color.startsWith('#')) return color.toUpperCase();
165283
- var m = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([\\d.]+))?/);
165399
+ // capture optional alpha (group 4), allowing both comma and modern slash (rgb r g b / a) syntax.
165400
+ var m = color.match(/rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)(?:\\s*[,/]\\s*([\\d.]+))?/);
165284
165401
  if (!m) return color;
165285
165402
  // fully-transparent fill (rgba(...,0)) \u2192 sentinel, NOT #000000 \u2014 otherwise a transparent
165286
165403
  // chip/tab/stat ground reads as solid black on a light-ground site.
@@ -165292,8 +165409,15 @@ var init_designStyleExtractor = __esm({
165292
165409
  return f.split(",")[0].replace(/['"]/g, "").trim();
165293
165410
  }
165294
165411
 
165412
+ // keep only gradient background-images (drop url() sprites + "none"); gradients are a core
165413
+ // brand signal (Stripe/ElevenLabs/Snowflake mesh washes) that a flat background-color misses.
165414
+ function gradientOf(v) {
165415
+ return v && v.indexOf("gradient") >= 0 ? v.trim() : "";
165416
+ }
165417
+
165295
165418
  function getStyles(el) {
165296
165419
  var s = getComputedStyle(el);
165420
+ var bf = s.backdropFilter || s.webkitBackdropFilter || "";
165297
165421
  return {
165298
165422
  fontFamily: cleanFont(s.fontFamily),
165299
165423
  fontSize: s.fontSize,
@@ -165302,6 +165426,8 @@ var init_designStyleExtractor = __esm({
165302
165426
  letterSpacing: s.letterSpacing,
165303
165427
  color: rgbToHex(s.color),
165304
165428
  background: rgbToHex(s.backgroundColor),
165429
+ backgroundImage: gradientOf(s.backgroundImage),
165430
+ backdropFilter: bf === "none" ? "" : bf,
165305
165431
  padding: s.padding,
165306
165432
  borderRadius: s.borderRadius,
165307
165433
  border: s.border,
@@ -165370,23 +165496,37 @@ var init_designStyleExtractor = __esm({
165370
165496
  }
165371
165497
 
165372
165498
  // \u2500\u2500 2. Buttons \u2500\u2500
165499
+ // a page's primary CTA is very often a FILLED pill in the nav/header ("Sign up", "Start for free").
165500
+ // Old code dropped everything under <nav>, losing that CTA; keep nav elements that carry a solid
165501
+ // fill (a real button), still dropping plain nav text links.
165502
+ var isFilledEl = (el) => {
165503
+ var cs = getComputedStyle(el);
165504
+ var bg = cs.backgroundColor;
165505
+ var solid = !!bg && bg !== "transparent" && !/rgba?\\([^)]*,\\s*0\\s*\\)/.test(bg);
165506
+ // a gradient-filled CTA (e.g. Snowflake's blue pill = background: var(--ui-background-03)) has a
165507
+ // transparent background-COLOR but a gradient background-IMAGE \u2014 count it as filled too.
165508
+ return solid || (cs.backgroundImage || "").indexOf("gradient") >= 0;
165509
+ };
165373
165510
  var buttonEls = Array.from(document.querySelectorAll(
165374
165511
  'button, a[class*="btn"], a[class*="button"], a[role="button"], ' +
165375
165512
  '[class*="btn-"], [class*="button-"], [class*="cta"]'
165376
165513
  )).filter(function(el) {
165377
- return isVisible(el) && !el.closest('nav, [role="navigation"]');
165378
- }).slice(0, 10);
165514
+ if (!isVisible(el)) return false;
165515
+ return el.closest('nav, [role="navigation"]') ? isFilledEl(el) : true;
165516
+ }).slice(0, 16);
165379
165517
 
165380
165518
  var buttonMap = {};
165381
165519
  for (var bi = 0; bi < buttonEls.length; bi++) {
165382
165520
  var bs = getStyles(buttonEls[bi]);
165383
- // Deduplicate by visual appearance
165384
- var bKey = bs.background + "|" + bs.borderRadius + "|" + bs.border;
165521
+ // Deduplicate by visual appearance (gradient fill kept distinct so a gradient CTA survives)
165522
+ var bKey = bs.background + "|" + bs.backgroundImage + "|" + bs.borderRadius + "|" + bs.border;
165385
165523
  if (!buttonMap[bKey]) {
165386
165524
  var btnText = (buttonEls[bi].textContent || "").trim().slice(0, 40);
165387
165525
  buttonMap[bKey] = {
165388
165526
  label: btnText || "button",
165389
165527
  background: bs.background,
165528
+ backgroundImage: bs.backgroundImage,
165529
+ backdropFilter: bs.backdropFilter,
165390
165530
  color: bs.color,
165391
165531
  padding: bs.padding,
165392
165532
  borderRadius: bs.borderRadius,
@@ -165398,7 +165538,7 @@ var init_designStyleExtractor = __esm({
165398
165538
  };
165399
165539
  }
165400
165540
  }
165401
- var buttons = Object.values(buttonMap).slice(0, 4);
165541
+ var buttons = Object.values(buttonMap).slice(0, 6);
165402
165542
 
165403
165543
  // \u2500\u2500 3. Cards / containers \u2500\u2500
165404
165544
  var cardEls = Array.from(document.querySelectorAll(
@@ -165413,11 +165553,14 @@ var init_designStyleExtractor = __esm({
165413
165553
  var cardMap = {};
165414
165554
  for (var ci = 0; ci < cardEls.length; ci++) {
165415
165555
  var cs = getStyles(cardEls[ci]);
165416
- var cKey = cs.background + "|" + cs.borderRadius + "|" + cs.border;
165556
+ // gradient fill + glass blur kept in the key so a gradient/frosted card is a distinct variant
165557
+ var cKey = cs.background + "|" + cs.backgroundImage + "|" + cs.backdropFilter + "|" + cs.borderRadius + "|" + cs.border;
165417
165558
  if (!cardMap[cKey]) {
165418
165559
  cardMap[cKey] = {
165419
165560
  label: "card",
165420
165561
  background: cs.background,
165562
+ backgroundImage: cs.backgroundImage,
165563
+ backdropFilter: cs.backdropFilter,
165421
165564
  color: cs.color,
165422
165565
  padding: cs.padding,
165423
165566
  borderRadius: cs.borderRadius,
@@ -165439,6 +165582,8 @@ var init_designStyleExtractor = __esm({
165439
165582
  nav = {
165440
165583
  label: "navigation",
165441
165584
  background: ns.background,
165585
+ backgroundImage: ns.backgroundImage,
165586
+ backdropFilter: ns.backdropFilter,
165442
165587
  color: ns.color,
165443
165588
  padding: ns.padding,
165444
165589
  borderRadius: ns.borderRadius,
@@ -165622,6 +165767,90 @@ var init_designStyleExtractor = __esm({
165622
165767
  .slice(0, 5)
165623
165768
  .map(function(e) { return { value: e[0], count: e[1] }; });
165624
165769
 
165770
+ // \u2500\u2500 8. Dominant gradient / mesh backgrounds \u2500\u2500
165771
+ // A site's signature color wash (Stripe/ElevenLabs/Snowflake) lives in gradient background-images
165772
+ // on large blocks \u2014 often on a pseudo-element (::before glow orbs) rather than the block itself.
165773
+ // Weight each distinct gradient by the total on-screen area it covers; return the top few.
165774
+ var gradientArea = {};
165775
+ var gradSamples = Array.from(document.querySelectorAll(
165776
+ "body, main, section, header, div, [class*='hero'], [class*='gradient'], [class*='bg'], [class*='background']"
165777
+ )).slice(0, 400);
165778
+
165779
+ // max stop chroma (max\u2212min RGB) of a gradient: a vivid brand wash scores high, a neutral
165780
+ // white/cream scrim ~0. Used to rank washes so a small vivid gradient beats a big grey scrim.
165781
+ function gradientChroma(g) {
165782
+ var max = 0, re = /rgba?\\(\\s*(\\d+)[,\\s]+(\\d+)[,\\s]+(\\d+)/g, m;
165783
+ while ((m = re.exec(g))) {
165784
+ var r = +m[1], gr = +m[2], b = +m[3];
165785
+ var c = Math.max(r, gr, b) - Math.min(r, gr, b);
165786
+ if (c > max) max = c;
165787
+ }
165788
+ return max;
165789
+ }
165790
+
165791
+ function addGradient(val, area) {
165792
+ var g = gradientOf(val);
165793
+ if (!g || area < 20000) return; // ignore tiny decorative gradients
165794
+ var norm = g.replace(/\\s+/g, " ");
165795
+ gradientArea[norm] = (gradientArea[norm] || 0) + area;
165796
+ }
165797
+
165798
+ for (var gi = 0; gi < gradSamples.length; gi++) {
165799
+ var gel = gradSamples[gi];
165800
+ if (!isVisible(gel)) continue;
165801
+ var grect = gel.getBoundingClientRect();
165802
+ var garea = grect.width * grect.height;
165803
+ if (garea < 20000) continue;
165804
+ addGradient(getComputedStyle(gel).backgroundImage, garea);
165805
+ addGradient(getComputedStyle(gel, "::before").backgroundImage, garea);
165806
+ addGradient(getComputedStyle(gel, "::after").backgroundImage, garea);
165807
+ }
165808
+
165809
+ // rank by chroma-weighted area so the brand's signature color wash outranks a larger neutral scrim
165810
+ var backgrounds = Object.entries(gradientArea)
165811
+ .map(function(e) { return { value: e[0], area: Math.round(e[1]), score: e[1] * (1 + 2 * gradientChroma(e[0]) / 255) }; })
165812
+ .sort(function(a, b) { return b.score - a.score; })
165813
+ .slice(0, 6)
165814
+ .map(function(e) { return { value: e.value, area: e.area }; });
165815
+
165816
+ // \u2500\u2500 9. Frosted-glass panels (backdrop-filter) \u2500\u2500
165817
+ // A defining material on modern hero UIs (HeyGen's prompt box, Stripe's floating chrome): a
165818
+ // translucent surface with a backdrop blur. Capture the RAW fill (rgba/gradient \u2014 alpha preserved,
165819
+ // unlike rgbToHex) + the blur, ranked by area. This is what lets a frame render a real frosted card.
165820
+ var glassSamples = Array.from(document.querySelectorAll(
165821
+ "div, section, header, nav, aside, [class*='card'], [class*='panel'], [class*='glass'], [class*='blur'], [class*='modal'], [class*='overlay'], [class*='input']"
165822
+ )).slice(0, 400);
165823
+ var glassByKey = {};
165824
+ for (var qi = 0; qi < glassSamples.length; qi++) {
165825
+ var qel = glassSamples[qi];
165826
+ if (!isVisible(qel)) continue;
165827
+ var qs = getComputedStyle(qel);
165828
+ var bf = qs.backdropFilter || qs.webkitBackdropFilter || "";
165829
+ if (!bf || bf === "none" || bf.indexOf("blur") < 0) continue;
165830
+ var qrect = qel.getBoundingClientRect();
165831
+ var qarea = qrect.width * qrect.height;
165832
+ if (qarea < 8000) continue; // ignore tiny blurred chips
165833
+ // raw fill with alpha intact: prefer a translucent gradient, else the rgba background-color
165834
+ var gi2 = qs.backgroundImage;
165835
+ var rawFill = gi2 && gi2.indexOf("gradient") >= 0 ? gi2.replace(/\\s+/g, " ").trim() : qs.backgroundColor;
165836
+ var key = bf + "|" + rawFill + "|" + qs.borderRadius;
165837
+ if (!glassByKey[key]) {
165838
+ glassByKey[key] = {
165839
+ backdropFilter: bf,
165840
+ background: rawFill,
165841
+ border: qs.border,
165842
+ borderRadius: qs.borderRadius,
165843
+ boxShadow: qs.boxShadow === "none" ? "" : qs.boxShadow,
165844
+ area: 0
165845
+ };
165846
+ }
165847
+ glassByKey[key].area += qarea;
165848
+ }
165849
+ var glass = Object.values(glassByKey)
165850
+ .sort(function(a, b) { return b.area - a.area; })
165851
+ .slice(0, 3)
165852
+ .map(function(g) { return { backdropFilter: g.backdropFilter, background: g.background, border: g.border, borderRadius: g.borderRadius, boxShadow: g.boxShadow, area: Math.round(g.area) }; });
165853
+
165625
165854
  return {
165626
165855
  typography: uniqueTypo,
165627
165856
  spacing: { observed: observedSpacing.slice(0, 15), baseUnit: baseUnit },
@@ -165632,7 +165861,9 @@ var init_designStyleExtractor = __esm({
165632
165861
  nav: nav,
165633
165862
  chips: chips,
165634
165863
  statCells: statCells,
165635
- tabs: tabs
165864
+ tabs: tabs,
165865
+ backgrounds: backgrounds,
165866
+ glass: glass
165636
165867
  };
165637
165868
  })()`;
165638
165869
  }
@@ -165962,7 +166193,7 @@ var init_animationCataloger = __esm({
165962
166193
  });
165963
166194
 
165964
166195
  // src/capture/mediaCapture.ts
165965
- import { mkdirSync as mkdirSync41, writeFileSync as writeFileSync33, readdirSync as readdirSync30, readFileSync as readFileSync58, statSync as statSync26 } from "fs";
166196
+ import { mkdirSync as mkdirSync43, writeFileSync as writeFileSync33, readdirSync as readdirSync30, readFileSync as readFileSync58, statSync as statSync27 } from "fs";
165966
166197
  import { join as join87, extname as extname16 } from "path";
165967
166198
  async function saveLottieAnimations(discoveredLotties, lottieDir) {
165968
166199
  let savedCount = 0;
@@ -166024,7 +166255,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
166024
166255
  async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
166025
166256
  const manifest = [];
166026
166257
  const previewDir = join87(lottieDir, "previews");
166027
- mkdirSync41(previewDir, { recursive: true });
166258
+ mkdirSync43(previewDir, { recursive: true });
166028
166259
  for (const file of readdirSync30(lottieDir)) {
166029
166260
  if (!file.endsWith(".json")) continue;
166030
166261
  try {
@@ -166032,7 +166263,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
166032
166263
  const fr = raw.fr || 30;
166033
166264
  const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
166034
166265
  const previewName = file.replace(".json", "-preview.png");
166035
- const fileSize = statSync26(join87(lottieDir, file)).size;
166266
+ const fileSize = statSync27(join87(lottieDir, file)).size;
166036
166267
  if (fileSize > 2e6) continue;
166037
166268
  let previewPage;
166038
166269
  try {
@@ -166190,9 +166421,9 @@ async function captureVideoManifest(page, outputDir, progress, opts) {
166190
166421
  const merged = [...byKey.values()];
166191
166422
  if (merged.length === 0) return;
166192
166423
  const videoManifestDir = join87(outputDir, "assets", "videos");
166193
- mkdirSync41(videoManifestDir, { recursive: true });
166424
+ mkdirSync43(videoManifestDir, { recursive: true });
166194
166425
  const previewDir = join87(videoManifestDir, "previews");
166195
- mkdirSync41(previewDir, { recursive: true });
166426
+ mkdirSync43(previewDir, { recursive: true });
166196
166427
  const videoManifest = [];
166197
166428
  const dlStart = Date.now();
166198
166429
  for (let vi = 0; vi < merged.length && vi < 20; vi++) {
@@ -166314,7 +166545,7 @@ var init_mediaCapture = __esm({
166314
166545
  });
166315
166546
 
166316
166547
  // src/capture/contentExtractor.ts
166317
- import { existsSync as existsSync87, readdirSync as readdirSync31, statSync as statSync27, readFileSync as readFileSync59 } from "fs";
166548
+ import { existsSync as existsSync87, readdirSync as readdirSync31, statSync as statSync28, readFileSync as readFileSync59 } from "fs";
166318
166549
  import { basename as basename21, join as join88 } from "path";
166319
166550
  async function detectLibraries(page, capturedShaders) {
166320
166551
  let detectedLibraries = [];
@@ -166491,7 +166722,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
166491
166722
  const results = await Promise.allSettled(
166492
166723
  batch.map(async (file) => {
166493
166724
  const filePath = join88(outputDir, "assets", file);
166494
- const stat3 = statSync27(filePath);
166725
+ const stat3 = statSync28(filePath);
166495
166726
  if (stat3.size > 4e6) return { file, caption: "" };
166496
166727
  const buffer = readFileSync59(filePath);
166497
166728
  const base64 = buffer.toString("base64");
@@ -166615,7 +166846,7 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
166615
166846
  for (const file of readdirSync31(assetsPath)) {
166616
166847
  if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
166617
166848
  const filePath = join88(assetsPath, file);
166618
- const stat3 = statSync27(filePath);
166849
+ const stat3 = statSync28(filePath);
166619
166850
  if (!stat3.isFile()) continue;
166620
166851
  const sizeKb = Math.round(stat3.size / 1024);
166621
166852
  const catalogMatch = catalogedAssets.find(
@@ -166898,11 +167129,11 @@ var screenshotCapture_exports = {};
166898
167129
  __export(screenshotCapture_exports, {
166899
167130
  captureScrollScreenshots: () => captureScrollScreenshots
166900
167131
  });
166901
- import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync43 } from "fs";
167132
+ import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync44 } from "fs";
166902
167133
  import { join as join91 } from "path";
166903
167134
  async function captureScrollScreenshots(page, outputDir) {
166904
167135
  const screenshotsDir = join91(outputDir, "screenshots");
166905
- mkdirSync43(screenshotsDir, { recursive: true });
167136
+ mkdirSync44(screenshotsDir, { recursive: true });
166906
167137
  const MAX_SCREENSHOTS = 20;
166907
167138
  const filePaths = [];
166908
167139
  try {
@@ -167342,7 +167573,7 @@ var capture_exports = {};
167342
167573
  __export(capture_exports, {
167343
167574
  captureWebsite: () => captureWebsite
167344
167575
  });
167345
- import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync37, existsSync as existsSync90 } from "fs";
167576
+ import { mkdirSync as mkdirSync45, writeFileSync as writeFileSync37, existsSync as existsSync90 } from "fs";
167346
167577
  import { join as join93 } from "path";
167347
167578
  async function captureWebsite(opts, onProgress) {
167348
167579
  const {
@@ -167360,9 +167591,9 @@ async function captureWebsite(opts, onProgress) {
167360
167591
  onProgress?.(stage, detail);
167361
167592
  };
167362
167593
  loadEnvFile(outputDir);
167363
- mkdirSync44(join93(outputDir, "extracted"), { recursive: true });
167364
- mkdirSync44(join93(outputDir, "screenshots"), { recursive: true });
167365
- mkdirSync44(join93(outputDir, "assets"), { recursive: true });
167594
+ mkdirSync45(join93(outputDir, "extracted"), { recursive: true });
167595
+ mkdirSync45(join93(outputDir, "screenshots"), { recursive: true });
167596
+ mkdirSync45(join93(outputDir, "assets"), { recursive: true });
167366
167597
  progress("browser", "Launching headless Chrome...");
167367
167598
  const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
167368
167599
  const browser = await ensureBrowser2();
@@ -167523,7 +167754,7 @@ async function captureWebsite(opts, onProgress) {
167523
167754
  }
167524
167755
  if (discoveredLotties.length > 0) {
167525
167756
  const lottieDir = join93(outputDir, "assets", "lottie");
167526
- mkdirSync44(lottieDir, { recursive: true });
167757
+ mkdirSync45(lottieDir, { recursive: true });
167527
167758
  const savedCount = await saveLottieAnimations(discoveredLotties, lottieDir);
167528
167759
  if (savedCount > 0) {
167529
167760
  await renderLottiePreviews(chromeBrowser, lottieDir, outputDir);
@@ -168025,8 +168256,8 @@ var init_capture2 = __esm({
168025
168256
  } catch (err) {
168026
168257
  const errMsg = normalizeErrorMessage(err);
168027
168258
  try {
168028
- const { mkdirSync: mkdirSync49, writeFileSync: writeFileSync40 } = await import("fs");
168029
- mkdirSync49(outputDir, { recursive: true });
168259
+ const { mkdirSync: mkdirSync50, writeFileSync: writeFileSync40 } = await import("fs");
168260
+ mkdirSync50(outputDir, { recursive: true });
168030
168261
  const isTimeout = /timeout|timed out/i.test(errMsg);
168031
168262
  const reason = isTimeout ? "Page navigation timed out \u2014 the site may be blocking headless browsers or requires authentication." : `Capture failed: ${errMsg}`;
168032
168263
  writeFileSync40(
@@ -168072,14 +168303,14 @@ __export(state_exports, {
168072
168303
  stateFilePath: () => stateFilePath,
168073
168304
  writeStackOutputs: () => writeStackOutputs
168074
168305
  });
168075
- import { existsSync as existsSync91, mkdirSync as mkdirSync45, readdirSync as readdirSync34, readFileSync as readFileSync61, rmSync as rmSync19, writeFileSync as writeFileSync38 } from "fs";
168306
+ import { existsSync as existsSync91, mkdirSync as mkdirSync46, readdirSync as readdirSync34, readFileSync as readFileSync61, rmSync as rmSync19, writeFileSync as writeFileSync38 } from "fs";
168076
168307
  import { dirname as dirname40, join as join94 } from "path";
168077
168308
  function stateFilePath(stackName = DEFAULT_STACK_NAME, cwd = process.cwd()) {
168078
168309
  return join94(cwd, STATE_DIR_NAME, `${STATE_FILE_PREFIX}${stackName}.json`);
168079
168310
  }
168080
168311
  function writeStackOutputs(outputs, cwd = process.cwd()) {
168081
168312
  const path2 = stateFilePath(outputs.stackName, cwd);
168082
- mkdirSync45(dirname40(path2), { recursive: true });
168313
+ mkdirSync46(dirname40(path2), { recursive: true });
168083
168314
  writeFileSync38(path2, JSON.stringify(outputs, null, 2) + "\n");
168084
168315
  return path2;
168085
168316
  }
@@ -168563,10 +168794,10 @@ async function waitForCompletion(executionArn, stack, intervalMs, json) {
168563
168794
  }
168564
168795
  return;
168565
168796
  }
168566
- await sleep2(intervalMs);
168797
+ await sleep3(intervalMs);
168567
168798
  }
168568
168799
  }
168569
- function sleep2(ms) {
168800
+ function sleep3(ms) {
168570
168801
  return new Promise((res) => setTimeout(res, ms));
168571
168802
  }
168572
168803
  var init_render2 = __esm({
@@ -169632,7 +169863,7 @@ __export(cloudrun_exports, {
169632
169863
  });
169633
169864
  import { spawnSync as spawnSync6 } from "child_process";
169634
169865
  import { createRequire as createRequire4 } from "module";
169635
- import { existsSync as existsSync97, mkdirSync as mkdirSync46, readFileSync as readFileSync66, writeFileSync as writeFileSync39 } from "fs";
169866
+ import { existsSync as existsSync97, mkdirSync as mkdirSync47, readFileSync as readFileSync66, writeFileSync as writeFileSync39 } from "fs";
169636
169867
  import { homedir as homedir14 } from "os";
169637
169868
  import { dirname as dirname42, join as join100, resolve as resolve61 } from "path";
169638
169869
  function stateDir() {
@@ -169642,7 +169873,7 @@ function statePath() {
169642
169873
  return join100(stateDir(), "cloudrun-state.json");
169643
169874
  }
169644
169875
  function writeState(state) {
169645
- mkdirSync46(stateDir(), { recursive: true });
169876
+ mkdirSync47(stateDir(), { recursive: true });
169646
169877
  writeFileSync39(statePath(), JSON.stringify(state, null, 2));
169647
169878
  }
169648
169879
  function readState(args) {
@@ -169805,7 +170036,7 @@ function findRepoRoot(tfDir) {
169805
170036
  }
169806
170037
  function writeCloudBuildConfig(image) {
169807
170038
  const cfgPath = join100(stateDir(), "cloudrun-cloudbuild.yaml");
169808
- mkdirSync46(stateDir(), { recursive: true });
170039
+ mkdirSync47(stateDir(), { recursive: true });
169809
170040
  writeFileSync39(
169810
170041
  cfgPath,
169811
170042
  [
@@ -170380,7 +170611,7 @@ async function pollUntilTerminal(client, renderId, options = {}) {
170380
170611
  const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
170381
170612
  const maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
170382
170613
  const now = options.now ?? (() => Date.now());
170383
- const sleep3 = options.sleep ?? defaultAbortableSleep(options.signal);
170614
+ const sleep5 = options.sleep ?? defaultAbortableSleep(options.signal);
170384
170615
  const started = now();
170385
170616
  while (true) {
170386
170617
  if (options.signal?.aborted) {
@@ -170395,7 +170626,7 @@ async function pollUntilTerminal(client, renderId, options = {}) {
170395
170626
  if (elapsed >= maxWaitMs) {
170396
170627
  throw new PollTimeoutError(detail, elapsed);
170397
170628
  }
170398
- await sleep3(intervalMs);
170629
+ await sleep5(intervalMs);
170399
170630
  }
170400
170631
  }
170401
170632
  function signalAbortError(signal) {
@@ -170436,7 +170667,7 @@ var init_poll = __esm({
170436
170667
  });
170437
170668
 
170438
170669
  // src/cloud/download.ts
170439
- import { createWriteStream as createWriteStream5, mkdirSync as mkdirSync47, unlinkSync as unlinkSync8 } from "fs";
170670
+ import { createWriteStream as createWriteStream5, mkdirSync as mkdirSync48, unlinkSync as unlinkSync8 } from "fs";
170440
170671
  import { dirname as dirname43 } from "path";
170441
170672
  async function downloadToFile(url, destPath, options = {}) {
170442
170673
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -170447,7 +170678,7 @@ async function downloadToFile(url, destPath, options = {}) {
170447
170678
  if (!res.body) {
170448
170679
  throw new Error(`Failed to download ${url}: empty response body`);
170449
170680
  }
170450
- mkdirSync47(dirname43(destPath), { recursive: true });
170681
+ mkdirSync48(dirname43(destPath), { recursive: true });
170451
170682
  const totalHeader = res.headers.get("content-length");
170452
170683
  const total = totalHeader ? Number.parseInt(totalHeader, 10) : void 0;
170453
170684
  const totalOpt = total !== void 0 && Number.isFinite(total) ? total : void 0;
@@ -170653,6 +170884,33 @@ var init_client2 = __esm({
170653
170884
  signal: args.signal
170654
170885
  });
170655
170886
  }
170887
+ /**
170888
+ * Create Asset Upload
170889
+ *
170890
+ * Begin a direct-to-S3 upload. Returns an asset_id and a presigned upload_url; PUT the file bytes to upload_url, then call POST /v3/assets/{asset_id}/complete. Unlike POST /v3/assets (which proxies the bytes), this never sends the file through the API.
170891
+ */
170892
+ async createAssetUpload(args) {
170893
+ return await this.request({
170894
+ method: "POST",
170895
+ path: "/v3/assets/direct-uploads",
170896
+ body: args.body,
170897
+ idempotencyKey: args.idempotencyKey,
170898
+ signal: args.signal
170899
+ });
170900
+ }
170901
+ /**
170902
+ * Complete Asset Upload
170903
+ *
170904
+ * Finalize a direct-to-S3 upload into a reusable asset. Call after the upload PUT returns 200. Idempotent: repeated calls return the same finalized asset.
170905
+ */
170906
+ async completeAssetUpload(args) {
170907
+ return await this.request({
170908
+ method: "POST",
170909
+ path: `/v3/assets/${encodeURIComponent(args.asset_id)}/complete`,
170910
+ body: args.body,
170911
+ signal: args.signal
170912
+ });
170913
+ }
170656
170914
  /**
170657
170915
  * Create HyperFrames Render
170658
170916
  *
@@ -171782,7 +172040,7 @@ var init_errors2 = __esm({
171782
172040
  init_format();
171783
172041
  init_client2();
171784
172042
  ERROR_CODE_HINTS = {
171785
- hyperframes_project_too_large: "The zip exceeded the 32 MB limit. Trim large media (or pre-host them and reference by URL), then try again.",
172043
+ hyperframes_project_too_large: "The zip exceeded the 200 MB limit. Trim large media (or pre-host them and reference by URL), then try again.",
171786
172044
  hyperframes_render_not_found: "The render_id no longer exists \u2014 either soft-deleted or never created.",
171787
172045
  invalid_parameter: "Check the listed parameter against `hyperframes cloud render --help` for the accepted values.",
171788
172046
  authentication_failed: "Run `hyperframes auth status` to confirm your credential; `hyperframes auth login` to re-auth.",
@@ -171848,6 +172106,91 @@ var init_parsing = __esm({
171848
172106
  }
171849
172107
  });
171850
172108
 
172109
+ // src/cloud/upload.ts
172110
+ import { createHash as createHash14 } from "crypto";
172111
+ function sha256Hex2(bytes) {
172112
+ return createHash14("sha256").update(bytes).digest("hex");
172113
+ }
172114
+ function sleep4(ms) {
172115
+ return new Promise((r2) => setTimeout(r2, ms));
172116
+ }
172117
+ function normalizeUploadHeaders(raw) {
172118
+ const out = {};
172119
+ for (const [k2, v2] of Object.entries(raw ?? {})) {
172120
+ if (typeof v2 === "string") out[k2] = v2;
172121
+ else if (typeof v2 === "number" || typeof v2 === "boolean") out[k2] = String(v2);
172122
+ }
172123
+ return out;
172124
+ }
172125
+ async function putBytesToPresignedUrl(fetchImpl, uploadUrl, uploadHeaders, bytes) {
172126
+ const headers = {
172127
+ "content-type": CONTENT_TYPE_ZIP,
172128
+ ...normalizeUploadHeaders(uploadHeaders)
172129
+ };
172130
+ const res = await fetchImpl(uploadUrl, {
172131
+ method: "PUT",
172132
+ headers,
172133
+ body: bytes
172134
+ });
172135
+ if (!res.ok) {
172136
+ const detail = await res.text().catch(() => "");
172137
+ throw new Error(
172138
+ `Direct upload PUT failed: ${res.status} ${res.statusText}${detail ? ` \u2014 ${detail.slice(0, 300)}` : ""}`
172139
+ );
172140
+ }
172141
+ }
172142
+ async function completeWithRetry(client, asset_id, checksum_sha256) {
172143
+ let lastErr;
172144
+ for (let attempt = 0; attempt < COMPLETE_MAX_RETRIES; attempt++) {
172145
+ try {
172146
+ return await client.completeAssetUpload({
172147
+ asset_id,
172148
+ body: { checksum_sha256 }
172149
+ });
172150
+ } catch (err) {
172151
+ const retryable = err instanceof HyperframesApiError && err.status === 409;
172152
+ if (!retryable || attempt === COMPLETE_MAX_RETRIES - 1) {
172153
+ throw err;
172154
+ }
172155
+ lastErr = err;
172156
+ await sleep4(COMPLETE_RETRY_BASE_MS * (attempt + 1));
172157
+ }
172158
+ }
172159
+ throw lastErr ?? new Error("completeAssetUpload retries exhausted");
172160
+ }
172161
+ async function uploadZipViaDirectUpload(opts) {
172162
+ const start = Date.now();
172163
+ const { bytes, filename, idempotencyKey } = opts;
172164
+ const size_bytes = bytes.byteLength;
172165
+ const checksum_sha256 = sha256Hex2(bytes);
172166
+ const fetchImpl = opts.fetchImpl ?? fetch;
172167
+ opts.onProgress?.({ phase: "initialize" });
172168
+ const initialize = await opts.client.createAssetUpload({
172169
+ body: { filename, content_type: CONTENT_TYPE_ZIP, size_bytes, checksum_sha256 },
172170
+ idempotencyKey
172171
+ });
172172
+ opts.onProgress?.({ phase: "upload", percent: 0 });
172173
+ await putBytesToPresignedUrl(fetchImpl, initialize.upload_url, initialize.upload_headers, bytes);
172174
+ opts.onProgress?.({ phase: "upload", percent: 100 });
172175
+ opts.onProgress?.({ phase: "complete" });
172176
+ const completed = await completeWithRetry(opts.client, initialize.asset_id, checksum_sha256);
172177
+ return {
172178
+ asset_id: completed.asset_id,
172179
+ size_bytes,
172180
+ duration_ms: Date.now() - start
172181
+ };
172182
+ }
172183
+ var COMPLETE_MAX_RETRIES, COMPLETE_RETRY_BASE_MS, CONTENT_TYPE_ZIP;
172184
+ var init_upload = __esm({
172185
+ "src/cloud/upload.ts"() {
172186
+ "use strict";
172187
+ init_client2();
172188
+ COMPLETE_MAX_RETRIES = 5;
172189
+ COMPLETE_RETRY_BASE_MS = 500;
172190
+ CONTENT_TYPE_ZIP = "application/zip";
172191
+ }
172192
+ });
172193
+
171851
172194
  // src/cloud/statusColor.ts
171852
172195
  function colorStatus(status) {
171853
172196
  switch (status) {
@@ -172008,22 +172351,25 @@ async function maybeUploadProject(client, source, asJson, idempotencyKey) {
172008
172351
  }
172009
172352
  if (!asJson) {
172010
172353
  console.log("");
172011
- console.log(`${c.accent("\u25C6")} Uploading to /v3/assets`);
172354
+ console.log(`${c.accent("\u25C6")} Uploading (direct-to-S3)`);
172012
172355
  }
172013
172356
  const uploadStart = Date.now();
172014
172357
  let uploaded;
172015
172358
  try {
172016
- uploaded = await client.uploadAsset({
172017
- file: archive.buffer,
172359
+ uploaded = await uploadZipViaDirectUpload({
172360
+ client,
172361
+ bytes: archive.buffer,
172018
172362
  filename: `${project.name}.zip`,
172019
- // Tag the multipart part with application/zip so downstream
172020
- // proxies / WAFs / any server-side path that keys off the
172021
- // part Content-Type see the intended type. The asset
172022
- // controller currently sniffs magic bytes from the file
172023
- // bytes, so this is belt-and-suspenders today; without it,
172024
- // FormData defaults to application/octet-stream.
172025
- mimeType: "application/zip",
172026
- idempotencyKey
172363
+ idempotencyKey,
172364
+ onProgress: !asJson ? (ev) => {
172365
+ if (ev.phase === "initialize") {
172366
+ console.log(c.dim(` initializing\u2026`));
172367
+ } else if (ev.phase === "upload" && ev.percent === 0) {
172368
+ console.log(c.dim(` uploading to S3\u2026`));
172369
+ } else if (ev.phase === "complete") {
172370
+ console.log(c.dim(` finalizing\u2026`));
172371
+ }
172372
+ } : void 0
172027
172373
  });
172028
172374
  } catch (err) {
172029
172375
  reportApiError("Upload failed", err);
@@ -172160,6 +172506,7 @@ var init_render3 = __esm({
172160
172506
  init_cloud();
172161
172507
  init_errors2();
172162
172508
  init_parsing();
172509
+ init_upload();
172163
172510
  init_statusColor();
172164
172511
  VALID_QUALITY2 = ["draft", "standard", "high"];
172165
172512
  VALID_FORMAT2 = ["mp4", "webm", "mov"];
@@ -173600,7 +173947,7 @@ __export(autoUpdate_exports, {
173600
173947
  scheduleBackgroundInstall: () => scheduleBackgroundInstall
173601
173948
  });
173602
173949
  import { spawn as spawn17 } from "child_process";
173603
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync48, openSync as openSync3 } from "fs";
173950
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync49, openSync as openSync3 } from "fs";
173604
173951
  import { homedir as homedir16 } from "os";
173605
173952
  import { join as join103 } from "path";
173606
173953
  import { compareVersions as compareVersions3 } from "compare-versions";
@@ -173617,14 +173964,14 @@ function majorOf(version2) {
173617
173964
  }
173618
173965
  function log(line2) {
173619
173966
  try {
173620
- mkdirSync48(CONFIG_DIR2, { recursive: true, mode: 448 });
173967
+ mkdirSync49(CONFIG_DIR2, { recursive: true, mode: 448 });
173621
173968
  appendFileSync2(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
173622
173969
  `, { mode: 384 });
173623
173970
  } catch {
173624
173971
  }
173625
173972
  }
173626
173973
  function launchDetachedInstall(installCommand, version2) {
173627
- mkdirSync48(CONFIG_DIR2, { recursive: true, mode: 448 });
173974
+ mkdirSync49(CONFIG_DIR2, { recursive: true, mode: 448 });
173628
173975
  const configFile = join103(CONFIG_DIR2, "config.json");
173629
173976
  const nodeScript = `
173630
173977
  const { exec } = require("node:child_process");