hyperframes 0.6.63 → 0.6.65

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.6.63" : "0.0.0-dev";
53
+ VERSION = true ? "0.6.65" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -36438,6 +36438,19 @@ function extractCompositionIdsFromCss(css) {
36438
36438
  }
36439
36439
  return [...ids];
36440
36440
  }
36441
+ function extractTimelineRegistryKeys(source) {
36442
+ const keys2 = /* @__PURE__ */ new Set();
36443
+ let match;
36444
+ const pattern = new RegExp(
36445
+ TIMELINE_REGISTRY_KEY_PATTERN.source,
36446
+ TIMELINE_REGISTRY_KEY_PATTERN.flags
36447
+ );
36448
+ while ((match = pattern.exec(source)) !== null) {
36449
+ const key2 = match[1] ?? match[2];
36450
+ if (key2) keys2.add(key2);
36451
+ }
36452
+ return [...keys2];
36453
+ }
36441
36454
  function getInlineScriptSyntaxError(source) {
36442
36455
  if (!source.trim()) return null;
36443
36456
  try {
@@ -36457,7 +36470,7 @@ function truncateSnippet(value, maxLength = 220) {
36457
36470
  if (normalized.length <= maxLength) return normalized;
36458
36471
  return `${normalized.slice(0, maxLength - 3)}...`;
36459
36472
  }
36460
- var TAG_PATTERN, STYLE_BLOCK_PATTERN, SCRIPT_BLOCK_PATTERN, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN;
36473
+ var TAG_PATTERN, STYLE_BLOCK_PATTERN, SCRIPT_BLOCK_PATTERN, COMPOSITION_ID_IN_CSS_PATTERN, TIMELINE_REGISTRY_INIT_PATTERN, TIMELINE_REGISTRY_ASSIGN_PATTERN, WINDOW_TIMELINE_ASSIGN_PATTERN, INVALID_SCRIPT_CLOSE_PATTERN, TIMELINE_REGISTRY_KEY_PATTERN;
36461
36474
  var init_utils2 = __esm({
36462
36475
  "../core/src/lint/utils.ts"() {
36463
36476
  "use strict";
@@ -36466,9 +36479,10 @@ var init_utils2 = __esm({
36466
36479
  SCRIPT_BLOCK_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
36467
36480
  COMPOSITION_ID_IN_CSS_PATTERN = /\[data-composition-id=["']([^"']+)["']\]/g;
36468
36481
  TIMELINE_REGISTRY_INIT_PATTERN = /window\.__timelines\s*=\s*window\.__timelines\s*\|\|\s*\{\}|window\.__timelines\s*=\s*\{\}|window\.__timelines\s*\?\?=\s*\{\}/i;
36469
- TIMELINE_REGISTRY_ASSIGN_PATTERN = /window\.__timelines\[[^\]]+\]\s*=/i;
36470
- WINDOW_TIMELINE_ASSIGN_PATTERN = /window\.__timelines\[\s*["']([^"']+)["']\s*\]\s*=\s*([A-Za-z_$][\w$]*)/i;
36482
+ TIMELINE_REGISTRY_ASSIGN_PATTERN = /window\.__timelines(?:\[[^\]]+\]|\.[A-Za-z_$][\w$]*)\s*=/i;
36483
+ WINDOW_TIMELINE_ASSIGN_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=\s*([A-Za-z_$][\w$]*)/i;
36471
36484
  INVALID_SCRIPT_CLOSE_PATTERN = /<script[^>]*>[\s\S]*?<\s*\/\s*script(?!>)/i;
36485
+ TIMELINE_REGISTRY_KEY_PATTERN = /window\.__timelines(?:\[\s*["']([^"']+)["']\s*\]|\.\s*([A-Za-z_$][\w$]*))\s*=/g;
36472
36486
  }
36473
36487
  });
36474
36488
 
@@ -36604,13 +36618,12 @@ var init_core = __esm({
36604
36618
  const htmlCompIds = /* @__PURE__ */ new Set();
36605
36619
  const timelineRegKeys = /* @__PURE__ */ new Set();
36606
36620
  const compIdRe = /data-composition-id\s*=\s*["']([^"']+)["']/gi;
36607
- const tlKeyRe = /window\.__timelines\[\s*["']([^"']+)["']\s*\]/g;
36608
36621
  let m2;
36609
36622
  while ((m2 = compIdRe.exec(source)) !== null) {
36610
36623
  if (m2[1]) htmlCompIds.add(m2[1]);
36611
36624
  }
36612
- while ((m2 = tlKeyRe.exec(source)) !== null) {
36613
- if (m2[1]) timelineRegKeys.add(m2[1]);
36625
+ for (const key2 of extractTimelineRegistryKeys(source)) {
36626
+ timelineRegKeys.add(key2);
36614
36627
  }
36615
36628
  for (const key2 of timelineRegKeys) {
36616
36629
  if (!htmlCompIds.has(key2)) {
@@ -37358,7 +37371,7 @@ function countClassUsage(tags) {
37358
37371
  }
37359
37372
  function readRegisteredTimelineCompositionId(script) {
37360
37373
  const match = script.match(WINDOW_TIMELINE_ASSIGN_PATTERN);
37361
- return match?.[1] || null;
37374
+ return match?.[1] || match?.[2] || null;
37362
37375
  }
37363
37376
  function unwrapRaw(value) {
37364
37377
  if (typeof value === "number") return value;
@@ -67373,9 +67386,9 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
67373
67386
  );
67374
67387
  });
67375
67388
  const mixInputs = tracks.map((_, i2) => `[a${i2}]`).join("");
67376
- const weights = tracks.map(() => "1").join(" ");
67377
- const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0:normalize=0:weights='${weights}'[mixed]`;
67378
- const postMixGainFilter = `[mixed]volume=${masterOutputGain}[out]`;
67389
+ const mixFilter = `${mixInputs}amix=inputs=${tracks.length}:duration=longest:dropout_transition=0[mixed]`;
67390
+ const compensatedGain = masterOutputGain * tracks.length;
67391
+ const postMixGainFilter = `[mixed]volume=${formatFilterNumber(compensatedGain)}[out]`;
67379
67392
  const fullFilter = [...filterParts, mixFilter, postMixGainFilter].join(";");
67380
67393
  return [
67381
67394
  ...inputs,
@@ -71420,7 +71433,7 @@ var init_urlDownloader2 = __esm({
71420
71433
 
71421
71434
  // ../producer/src/services/htmlCompiler.ts
71422
71435
  import { readFileSync as readFileSync27, existsSync as existsSync35, mkdirSync as mkdirSync20 } from "fs";
71423
- import { join as join39, dirname as dirname13, resolve as resolve19 } from "path";
71436
+ import { join as join39, dirname as dirname13, resolve as resolve19, basename as basename5 } from "path";
71424
71437
  function dedupeElementsById(elements) {
71425
71438
  const deduped = /* @__PURE__ */ new Map();
71426
71439
  for (const element of elements) {
@@ -71552,6 +71565,8 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
71552
71565
  compiledHtml = clampDurations(compiledHtml, clampList);
71553
71566
  }
71554
71567
  compiledHtml = compiledHtml.replace(/(<video\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71568
+ compiledHtml = compiledHtml.replace(/(<img\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71569
+ compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
71555
71570
  return { html: compiledHtml, unresolvedCompositions };
71556
71571
  }
71557
71572
  async function parseSubCompositions(html, projectDir, downloadDir, parentOffset = 0, parentEnd = Infinity, visited = /* @__PURE__ */ new Set()) {
@@ -71964,6 +71979,46 @@ function collectExternalAssets(html, projectDir) {
71964
71979
  externalAssets
71965
71980
  };
71966
71981
  }
71982
+ async function localizeRemoteMediaSources(html, downloadDir) {
71983
+ const remoteDir = join39(downloadDir, REMOTE_MEDIA_SUBDIR);
71984
+ const urlSet = /* @__PURE__ */ new Set();
71985
+ const re2 = new RegExp(REMOTE_MEDIA_TAG_RE.source, REMOTE_MEDIA_TAG_RE.flags);
71986
+ let m2;
71987
+ while ((m2 = re2.exec(html)) !== null) {
71988
+ if (m2[1]) urlSet.add(m2[1]);
71989
+ }
71990
+ if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
71991
+ if (!existsSync35(remoteDir)) mkdirSync20(remoteDir, { recursive: true });
71992
+ const urlToLocal = /* @__PURE__ */ new Map();
71993
+ await Promise.all(
71994
+ [...urlSet].map(async (url) => {
71995
+ try {
71996
+ const localPath = await downloadToTemp(url, remoteDir);
71997
+ urlToLocal.set(url, localPath);
71998
+ } catch (err) {
71999
+ console.warn(
72000
+ `[Compiler] Remote media download failed for ${url} \u2014 using original URL as fallback. ${err instanceof Error ? err.message : String(err)}`
72001
+ );
72002
+ }
72003
+ })
72004
+ );
72005
+ if (urlToLocal.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
72006
+ const remoteMediaAssets = /* @__PURE__ */ new Map();
72007
+ const urlToRelPath = /* @__PURE__ */ new Map();
72008
+ for (const [url, absPath] of urlToLocal) {
72009
+ const relPath = `${REMOTE_MEDIA_SUBDIR}/${basename5(absPath)}`;
72010
+ remoteMediaAssets.set(relPath, absPath);
72011
+ urlToRelPath.set(url, relPath);
72012
+ }
72013
+ let result = html;
72014
+ for (const [url, relPath] of urlToRelPath) {
72015
+ result = result.replaceAll(`"${url}"`, `"${relPath}"`).replaceAll(`'${url}'`, `'${relPath}'`);
72016
+ }
72017
+ console.log(
72018
+ `[Compiler] Localized ${urlToLocal.size} remote media source(s) to ${REMOTE_MEDIA_SUBDIR}/`
72019
+ );
72020
+ return { html: result, remoteMediaAssets };
72021
+ }
71967
72022
  function rewriteUnresolvableGsapToCdn(html, projectDir) {
71968
72023
  return html.replace(
71969
72024
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
@@ -72014,10 +72069,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
72014
72069
  'data-hf-studio-motion="'
72015
72070
  ];
72016
72071
  const hasPositionEdits = HF_POSITION_ATTRS.some((attr) => htmlWithAssets.includes(attr));
72017
- const html = hasPositionEdits ? htmlWithAssets.replace(
72072
+ const htmlWithPositionScript = hasPositionEdits ? htmlWithAssets.replace(
72018
72073
  /<\/body>/i,
72019
72074
  `<script>${createStudioPositionSeekReapplyScript()}</script></body>`
72020
72075
  ) : htmlWithAssets;
72076
+ const { html, remoteMediaAssets } = await localizeRemoteMediaSources(
72077
+ htmlWithPositionScript,
72078
+ downloadDir
72079
+ );
72080
+ for (const [relPath, absPath] of remoteMediaAssets) {
72081
+ externalAssets.set(relPath, absPath);
72082
+ }
72021
72083
  const mainVideos = parseVideoElements(html);
72022
72084
  const mainAudios = parseAudioElements(html);
72023
72085
  const mainImages = parseImageElements(html);
@@ -72298,7 +72360,7 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
72298
72360
  hasShaderTransitions: compiled.hasShaderTransitions
72299
72361
  };
72300
72362
  }
72301
- var INLINE_SCRIPT_PATTERN, COMPILER_MOUNT_BLOCK_START, COMPILER_MOUNT_BLOCK_END, SHADER_TRANSITION_USAGE_PATTERN, GSAP_CDN_BASE;
72363
+ var INLINE_SCRIPT_PATTERN, COMPILER_MOUNT_BLOCK_START, COMPILER_MOUNT_BLOCK_END, SHADER_TRANSITION_USAGE_PATTERN, REMOTE_MEDIA_SUBDIR, REMOTE_MEDIA_TAG_RE, GSAP_CDN_BASE;
72302
72364
  var init_htmlCompiler2 = __esm({
72303
72365
  "../producer/src/services/htmlCompiler.ts"() {
72304
72366
  "use strict";
@@ -72315,6 +72377,8 @@ var init_htmlCompiler2 = __esm({
72315
72377
  COMPILER_MOUNT_BLOCK_START = "/* __HF_COMPILER_MOUNT_START__ */";
72316
72378
  COMPILER_MOUNT_BLOCK_END = "/* __HF_COMPILER_MOUNT_END__ */";
72317
72379
  SHADER_TRANSITION_USAGE_PATTERN = /\b(?:(?:window|globalThis)\s*\.\s*)?HyperShader\s*\.\s*init\s*\(|\b__hf\s*\.\s*transitions\s*=/;
72380
+ REMOTE_MEDIA_SUBDIR = "_remote_media";
72381
+ REMOTE_MEDIA_TAG_RE = /<(?:video|audio)\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
72318
72382
  GSAP_CDN_BASE = "https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/";
72319
72383
  }
72320
72384
  });
@@ -78329,7 +78393,7 @@ __export(studioServer_exports, {
78329
78393
  import { Hono as Hono5 } from "hono";
78330
78394
  import { streamSSE as streamSSE3 } from "hono/streaming";
78331
78395
  import { existsSync as existsSync48, readFileSync as readFileSync35, writeFileSync as writeFileSync25, statSync as statSync17 } from "fs";
78332
- import { resolve as resolve24, join as join58, basename as basename5 } from "path";
78396
+ import { resolve as resolve24, join as join58, basename as basename6 } from "path";
78333
78397
  function resolveDistDir() {
78334
78398
  return resolveStudioBundle().dir;
78335
78399
  }
@@ -78456,7 +78520,7 @@ async function loadPreviewServerBuildSignature() {
78456
78520
  }
78457
78521
  function createStudioServer(options) {
78458
78522
  const { projectDir, projectName } = options;
78459
- const projectId = projectName || basename5(projectDir);
78523
+ const projectId = projectName || basename6(projectDir);
78460
78524
  const studioDir = resolveDistDir();
78461
78525
  const runtimePath = resolveRuntimePath();
78462
78526
  const watcher = createProjectWatcher(projectDir);
@@ -78632,19 +78696,19 @@ function createStudioServer(options) {
78632
78696
  async installRegistryBlock(opts) {
78633
78697
  const { resolveItem: resolveItem2 } = await Promise.resolve().then(() => (init_resolver(), resolver_exports));
78634
78698
  const { installItem: installItem2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
78635
- const { readFileSync: readFileSync58, writeFileSync: writeFileSync40, existsSync: existsSync81 } = await import("fs");
78699
+ const { readFileSync: readFileSync59, writeFileSync: writeFileSync40, existsSync: existsSync81 } = await import("fs");
78636
78700
  const { join: join92 } = await import("path");
78637
78701
  const item = await resolveItem2(opts.blockName);
78638
78702
  const { written } = await installItem2(item, { destDir: opts.project.dir });
78639
78703
  const indexPath = join92(opts.project.dir, "index.html");
78640
78704
  if (existsSync81(indexPath)) {
78641
- const indexHtml = readFileSync58(indexPath, "utf-8");
78705
+ const indexHtml = readFileSync59(indexPath, "utf-8");
78642
78706
  const hostW = indexHtml.match(/data-width="(\d+)"/)?.[1];
78643
78707
  const hostH = indexHtml.match(/data-height="(\d+)"/)?.[1];
78644
78708
  if (hostW && hostH) {
78645
78709
  for (const absPath of written) {
78646
78710
  if (!absPath.endsWith(".html")) continue;
78647
- let content = readFileSync58(absPath, "utf-8");
78711
+ let content = readFileSync59(absPath, "utf-8");
78648
78712
  content = content.replace(
78649
78713
  /(<meta\s+name="viewport"\s+content="width=)\d+(,\s*height=)\d+/i,
78650
78714
  `$1${hostW}$2${hostH}`
@@ -78842,14 +78906,14 @@ __export(preview_exports, {
78842
78906
  });
78843
78907
  import { spawn as spawn11 } from "child_process";
78844
78908
  import { existsSync as existsSync49, lstatSync as lstatSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync30 } from "fs";
78845
- import { resolve as resolve25, dirname as dirname19, basename as basename6, join as join59 } from "path";
78909
+ import { resolve as resolve25, dirname as dirname19, basename as basename7, join as join59 } from "path";
78846
78910
  import { fileURLToPath as fileURLToPath6 } from "url";
78847
78911
  import { createRequire as createRequire2 } from "module";
78848
78912
  async function runDevMode(dir, options) {
78849
78913
  const thisFile = fileURLToPath6(import.meta.url);
78850
78914
  const repoRoot2 = resolve25(dirname19(thisFile), "..", "..", "..", "..");
78851
78915
  const projectsDir = join59(repoRoot2, "packages", "studio", "data", "projects");
78852
- const pName = options?.projectName ?? basename6(dir);
78916
+ const pName = options?.projectName ?? basename7(dir);
78853
78917
  const symlinkPath = join59(projectsDir, pName);
78854
78918
  mkdirSync30(projectsDir, { recursive: true });
78855
78919
  let createdSymlink = false;
@@ -78939,7 +79003,7 @@ function hasLocalStudio(dir) {
78939
79003
  async function runLocalStudioMode(dir, options) {
78940
79004
  const req = createRequire2(join59(dir, "package.json"));
78941
79005
  const studioPkgPath = dirname19(req.resolve("@hyperframes/studio/package.json"));
78942
- const pName = options?.projectName ?? basename6(dir);
79006
+ const pName = options?.projectName ?? basename7(dir);
78943
79007
  const projectsDir = join59(studioPkgPath, "data", "projects");
78944
79008
  const symlinkPath = join59(projectsDir, pName);
78945
79009
  mkdirSync30(projectsDir, { recursive: true });
@@ -79010,7 +79074,7 @@ async function runLocalStudioMode(dir, options) {
79010
79074
  }
79011
79075
  async function runEmbeddedMode(dir, startPort, options) {
79012
79076
  const { createStudioServer: createStudioServer2, loadPreviewServerBuildSignature: loadPreviewServerBuildSignature2, resolveStudioBundle: resolveStudioBundle2 } = await Promise.resolve().then(() => (init_studioServer(), studioServer_exports));
79013
- const pName = options?.projectName ?? basename6(dir);
79077
+ const pName = options?.projectName ?? basename7(dir);
79014
79078
  const studioBundle = resolveStudioBundle2();
79015
79079
  ge(c2.bold("hyperframes preview"));
79016
79080
  const s2 = ft();
@@ -79240,7 +79304,7 @@ var init_preview2 = __esm({
79240
79304
  const rawArg = args.dir;
79241
79305
  const dir = resolve25(rawArg ?? ".");
79242
79306
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
79243
- const projectName = isImplicitCwd ? basename6(process.env.PWD ?? dir) : basename6(dir);
79307
+ const projectName = isImplicitCwd ? basename7(process.env.PWD ?? dir) : basename7(dir);
79244
79308
  const indexPath = join59(dir, "index.html");
79245
79309
  if (existsSync49(indexPath)) {
79246
79310
  const project = { dir, name: projectName, indexPath };
@@ -79328,7 +79392,7 @@ import {
79328
79392
  readFileSync as readFileSync36,
79329
79393
  readdirSync as readdirSync21
79330
79394
  } from "fs";
79331
- import { resolve as resolve26, basename as basename7, join as join60, dirname as dirname20 } from "path";
79395
+ import { resolve as resolve26, basename as basename8, join as join60, dirname as dirname20 } from "path";
79332
79396
  import { fileURLToPath as fileURLToPath7 } from "url";
79333
79397
  import { execFileSync as execFileSync5, spawn as spawn12 } from "child_process";
79334
79398
  function probeVideo(filePath) {
@@ -79409,7 +79473,7 @@ function getSharedTemplateDir() {
79409
79473
  return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
79410
79474
  }
79411
79475
  function toPackageName(projectName) {
79412
- const normalized = basename7(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
79476
+ const normalized = basename8(projectName).trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9._~-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
79413
79477
  return normalized || "hyperframes-project";
79414
79478
  }
79415
79479
  function getHyperframesPackageSpecifier() {
@@ -79520,7 +79584,7 @@ async function patchTranscript(dir, transcriptPath) {
79520
79584
  async function handleVideoFile(videoPath, destDir, interactive) {
79521
79585
  const probed = probeVideo(videoPath);
79522
79586
  let meta = { ...DEFAULT_META };
79523
- let localVideoName = basename7(videoPath);
79587
+ let localVideoName = basename8(videoPath);
79524
79588
  if (probed) {
79525
79589
  meta = probed;
79526
79590
  if (interactive) {
@@ -79870,8 +79934,8 @@ var init_init = __esm({
79870
79934
  process.exit(1);
79871
79935
  }
79872
79936
  sourceFilePath2 = audioPath;
79873
- copyFileSync5(audioPath, resolve26(destDir2, basename7(audioPath)));
79874
- console.log(`Audio: ${basename7(audioPath)}`);
79937
+ copyFileSync5(audioPath, resolve26(destDir2, basename8(audioPath)));
79938
+ console.log(`Audio: ${basename8(audioPath)}`);
79875
79939
  }
79876
79940
  if (sourceFilePath2 && !skipTranscribe) {
79877
79941
  try {
@@ -79895,7 +79959,7 @@ var init_init = __esm({
79895
79959
  try {
79896
79960
  await scaffoldProject(
79897
79961
  destDir2,
79898
- basename7(destDir2),
79962
+ basename8(destDir2),
79899
79963
  templateId2,
79900
79964
  localVideoName2,
79901
79965
  videoDuration2,
@@ -80002,8 +80066,8 @@ var init_init = __esm({
80002
80066
  }
80003
80067
  mkdirSync31(destDir, { recursive: true });
80004
80068
  sourceFilePath = audioPath;
80005
- copyFileSync5(audioPath, resolve26(destDir, basename7(audioPath)));
80006
- R2.info(`Audio copied to ${c2.accent(basename7(audioPath))}`);
80069
+ copyFileSync5(audioPath, resolve26(destDir, basename8(audioPath)));
80070
+ R2.info(`Audio copied to ${c2.accent(basename8(audioPath))}`);
80007
80071
  }
80008
80072
  if (sourceFilePath) {
80009
80073
  const transcribeChoice = await ue({
@@ -80565,10 +80629,10 @@ var init_format = __esm({
80565
80629
 
80566
80630
  // src/utils/project.ts
80567
80631
  import { existsSync as existsSync52, statSync as statSync18 } from "fs";
80568
- import { resolve as resolve29, basename as basename8 } from "path";
80632
+ import { resolve as resolve29, basename as basename9 } from "path";
80569
80633
  function resolveProject(dirArg) {
80570
80634
  const dir = resolve29(dirArg ?? ".");
80571
- const name = basename8(dir);
80635
+ const name = basename9(dir);
80572
80636
  const indexPath = resolve29(dir, "index.html");
80573
80637
  if (!existsSync52(dir) || !statSync18(dir).isDirectory()) {
80574
80638
  errorBox("Not a directory: " + dir);
@@ -80860,7 +80924,7 @@ var init_play = __esm({
80860
80924
  });
80861
80925
 
80862
80926
  // src/utils/publishProject.ts
80863
- import { basename as basename9, join as join61, relative as relative9 } from "path";
80927
+ import { basename as basename10, join as join61, relative as relative9 } from "path";
80864
80928
  import { readdirSync as readdirSync22, readFileSync as readFileSync38, statSync as statSync19 } from "fs";
80865
80929
  import AdmZip from "adm-zip";
80866
80930
  function isRecord2(value) {
@@ -81074,7 +81138,7 @@ async function publishProjectArchiveStaged(apiBaseUrl2, title, archive) {
81074
81138
  return publishedProject;
81075
81139
  }
81076
81140
  async function publishProjectArchive(projectDir) {
81077
- const title = basename9(projectDir);
81141
+ const title = basename10(projectDir);
81078
81142
  const archive = createPublishArchive(projectDir);
81079
81143
  const apiBaseUrl2 = getPublishApiBaseUrl();
81080
81144
  const stagedResult = await publishProjectArchiveStaged(apiBaseUrl2, title, archive);
@@ -81100,7 +81164,7 @@ __export(publish_exports, {
81100
81164
  default: () => publish_default,
81101
81165
  examples: () => examples6
81102
81166
  });
81103
- import { basename as basename10, resolve as resolve31 } from "path";
81167
+ import { basename as basename11, resolve as resolve31 } from "path";
81104
81168
  import { existsSync as existsSync54 } from "fs";
81105
81169
  import { join as join62 } from "path";
81106
81170
  var examples6, publish_default;
@@ -81136,7 +81200,7 @@ var init_publish = __esm({
81136
81200
  const rawArg = args.dir;
81137
81201
  const dir = resolve31(rawArg ?? ".");
81138
81202
  const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
81139
- const projectName = isImplicitCwd ? basename10(process.env["PWD"] ?? dir) : basename10(dir);
81203
+ const projectName = isImplicitCwd ? basename11(process.env["PWD"] ?? dir) : basename11(dir);
81140
81204
  const indexPath = join62(dir, "index.html");
81141
81205
  if (existsSync54(indexPath)) {
81142
81206
  const lintResult = await lintProject({ dir, name: projectName, indexPath });
@@ -81559,7 +81623,7 @@ __export(render_exports, {
81559
81623
  });
81560
81624
  import { mkdirSync as mkdirSync32, readdirSync as readdirSync23, readFileSync as readFileSync40, statSync as statSync20, writeFileSync as writeFileSync27, rmSync as rmSync14 } from "fs";
81561
81625
  import { cpus as cpus4, freemem as freemem4, tmpdir as tmpdir5 } from "os";
81562
- import { resolve as resolve33, dirname as dirname22, join as join63, basename as basename11 } from "path";
81626
+ import { resolve as resolve33, dirname as dirname22, join as join63, basename as basename12 } from "path";
81563
81627
  import { execFileSync as execFileSync6, spawn as spawn13 } from "child_process";
81564
81628
  function formatFpsParseError(input2, reason) {
81565
81629
  switch (reason) {
@@ -81663,7 +81727,7 @@ async function renderDocker(projectDir, outputPath, options) {
81663
81727
  process.exit(1);
81664
81728
  }
81665
81729
  const outputDir = dirname22(outputPath);
81666
- const outputFilename = basename11(outputPath);
81730
+ const outputFilename = basename12(outputPath);
81667
81731
  const dockerArgs = buildDockerRunArgs({
81668
81732
  imageTag,
81669
81733
  projectDir: resolve33(projectDir),
@@ -84652,7 +84716,7 @@ __export(synthesize_exports, {
84652
84716
  });
84653
84717
  import { execFileSync as execFileSync7 } from "child_process";
84654
84718
  import { existsSync as existsSync63, writeFileSync as writeFileSync29, mkdirSync as mkdirSync35, readdirSync as readdirSync25, unlinkSync as unlinkSync6 } from "fs";
84655
- import { join as join70, dirname as dirname26, basename as basename12 } from "path";
84719
+ import { join as join70, dirname as dirname26, basename as basename13 } from "path";
84656
84720
  import { homedir as homedir11 } from "os";
84657
84721
  function findPython() {
84658
84722
  for (const name of ["python3", "python"]) {
@@ -84691,7 +84755,7 @@ function ensureSynthScript() {
84691
84755
  if (!existsSync63(SCRIPT_PATH)) {
84692
84756
  mkdirSync35(SCRIPT_DIR, { recursive: true });
84693
84757
  writeFileSync29(SCRIPT_PATH, SYNTH_SCRIPT);
84694
- const currentName = basename12(SCRIPT_PATH);
84758
+ const currentName = basename13(SCRIPT_PATH);
84695
84759
  try {
84696
84760
  for (const entry of readdirSync25(SCRIPT_DIR)) {
84697
84761
  if (entry !== currentName && /^synth(-v\d+)?\.py$/.test(entry)) {
@@ -85963,7 +86027,7 @@ __export(contactSheet_exports, {
85963
86027
  });
85964
86028
  import sharp from "sharp";
85965
86029
  import { readdirSync as readdirSync26, readFileSync as readFileSync48, writeFileSync as writeFileSync30, unlinkSync as unlinkSync7, existsSync as existsSync67 } from "fs";
85966
- import { join as join73, extname as extname13, basename as basename13, dirname as dirname29 } from "path";
86030
+ import { join as join73, extname as extname13, basename as basename14, dirname as dirname29 } from "path";
85967
86031
  async function createContactSheet(imagePaths, outputPath, opts = {}) {
85968
86032
  const {
85969
86033
  cols = 3,
@@ -85996,7 +86060,7 @@ async function createContactSheet(imagePaths, outputPath, opts = {}) {
85996
86060
  overlays.push({ input: resized, left: x3, top: y + labelH });
85997
86061
  let labelText = `${i2 + 1}`;
85998
86062
  if (labelMode === "filename") {
85999
- labelText = `${i2 + 1}. ${basename13(files[i2]).replace(extname13(files[i2]), "")}`;
86063
+ labelText = `${i2 + 1}. ${basename14(files[i2]).replace(extname13(files[i2]), "")}`;
86000
86064
  } else if (labelMode === "custom" && labels?.[i2]) {
86001
86065
  labelText = `${i2 + 1}. ${labels[i2]}`;
86002
86066
  }
@@ -93094,7 +93158,7 @@ var require_node_domexception = __commonJS({
93094
93158
 
93095
93159
  // ../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js
93096
93160
  import { statSync as statSync23, createReadStream as createReadStream2, promises as fs2 } from "fs";
93097
- import { basename as basename14 } from "path";
93161
+ import { basename as basename15 } from "path";
93098
93162
  var import_node_domexception, stat, blobFromSync, blobFrom, fileFrom, fileFromSync, fromBlob, fromFile, BlobDataItem;
93099
93163
  var init_from = __esm({
93100
93164
  "../../node_modules/.bun/fetch-blob@3.2.0/node_modules/fetch-blob/from.js"() {
@@ -93118,7 +93182,7 @@ var init_from = __esm({
93118
93182
  size: stat3.size,
93119
93183
  lastModified: stat3.mtimeMs,
93120
93184
  start: 0
93121
- })], basename14(path2), { type, lastModified: stat3.mtimeMs });
93185
+ })], basename15(path2), { type, lastModified: stat3.mtimeMs });
93122
93186
  BlobDataItem = class _BlobDataItem {
93123
93187
  #path;
93124
93188
  #start;
@@ -131852,6 +131916,57 @@ ${HELP}`);
131852
131916
  }
131853
131917
  });
131854
131918
 
131919
+ // src/cloud/detectAspectRatio.ts
131920
+ import { readFileSync as readFileSync58 } from "fs";
131921
+ function extractAttributeNumber(tag, re2) {
131922
+ const match = tag.match(re2);
131923
+ if (!match) return null;
131924
+ const raw = match[1] ?? match[2] ?? match[3];
131925
+ if (raw === void 0) return null;
131926
+ const value = Number(raw);
131927
+ return Number.isFinite(value) ? value : null;
131928
+ }
131929
+ function detectAspectRatioFromHtml(entryHtmlPath) {
131930
+ let html;
131931
+ try {
131932
+ html = readFileSync58(entryHtmlPath, "utf-8");
131933
+ } catch (err) {
131934
+ return { kind: "read-error", error: err instanceof Error ? err.message : String(err) };
131935
+ }
131936
+ return detectAspectRatioFromHtmlString(html);
131937
+ }
131938
+ function detectAspectRatioFromHtmlString(html) {
131939
+ const tagMatch = html.match(ROOT_COMPOSITION_DIV_RE);
131940
+ if (!tagMatch) return { kind: "no-root-div" };
131941
+ const openTag = tagMatch[0];
131942
+ const width = extractAttributeNumber(openTag, DATA_WIDTH_RE);
131943
+ const height = extractAttributeNumber(openTag, DATA_HEIGHT_RE);
131944
+ if (width === null || height === null) return { kind: "no-dims" };
131945
+ if (width <= 0 || height <= 0) return { kind: "invalid-dims", width, height };
131946
+ const ratio = width / height;
131947
+ for (const candidate of SUPPORTED_RATIOS) {
131948
+ if (Math.abs(ratio - candidate.ratio) <= RATIO_TOLERANCE) {
131949
+ return { kind: "matched", aspectRatio: candidate.value, width, height };
131950
+ }
131951
+ }
131952
+ return { kind: "no-match", width, height, ratio };
131953
+ }
131954
+ var RATIO_TOLERANCE, SUPPORTED_RATIOS, ROOT_COMPOSITION_DIV_RE, DATA_WIDTH_RE, DATA_HEIGHT_RE;
131955
+ var init_detectAspectRatio = __esm({
131956
+ "src/cloud/detectAspectRatio.ts"() {
131957
+ "use strict";
131958
+ RATIO_TOLERANCE = 0.05;
131959
+ SUPPORTED_RATIOS = [
131960
+ { value: "16:9", ratio: 16 / 9 },
131961
+ { value: "9:16", ratio: 9 / 16 },
131962
+ { value: "1:1", ratio: 1 }
131963
+ ];
131964
+ ROOT_COMPOSITION_DIV_RE = /<div\b[^>]*?\bdata-composition-id\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)[^>]*>/i;
131965
+ DATA_WIDTH_RE = /\bdata-width\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
131966
+ DATA_HEIGHT_RE = /\bdata-height\s*=\s*(?:"(\d+(?:\.\d+)?)"|'(\d+(?:\.\d+)?)'|(\d+(?:\.\d+)?))(?=\s|>|\/)/i;
131967
+ }
131968
+ });
131969
+
131855
131970
  // src/cloud/poll.ts
131856
131971
  function isTerminal(status) {
131857
131972
  return TERMINAL_STATUSES.has(status);
@@ -133307,6 +133422,40 @@ function resolveProjectInput(opts) {
133307
133422
  if (explicit.url) return { kind: "url", url: opts.url };
133308
133423
  return { kind: "dir", dir: opts.dir ?? "." };
133309
133424
  }
133425
+ function maybeAutoDetectAspectRatio(project, compositionArg, asJson) {
133426
+ if (project.kind !== "dir") {
133427
+ const reason = project.kind === "asset_id" ? "--asset-id" : "--url";
133428
+ logDetection(asJson, `Auto-detect skipped (project is ${reason})`);
133429
+ return void 0;
133430
+ }
133431
+ const dir = project.dir ?? ".";
133432
+ const entryRelative = compositionArg ?? "index.html";
133433
+ const entryPath = resolvePath4(dir, entryRelative);
133434
+ const detection = detectAspectRatioFromHtml(entryPath);
133435
+ logDetection(asJson, summarizeDetection(detection, entryRelative));
133436
+ return detection.kind === "matched" ? detection.aspectRatio : void 0;
133437
+ }
133438
+ function logDetection(asJson, message) {
133439
+ if (asJson) return;
133440
+ const suffix = message.startsWith("Detected aspect ratio") ? "" : `; ${ASPECT_FALLBACK_HINT}`;
133441
+ console.log(c2.dim(` ${message}${suffix}`));
133442
+ }
133443
+ function summarizeDetection(detection, entryRelative) {
133444
+ switch (detection.kind) {
133445
+ case "matched":
133446
+ return `Detected aspect ratio: ${detection.aspectRatio} (from ${entryRelative} dims ${detection.width}\xD7${detection.height})`;
133447
+ case "no-root-div":
133448
+ return `No <div data-composition-id> found in ${entryRelative}`;
133449
+ case "no-dims":
133450
+ return `${entryRelative} root composition has no data-width / data-height`;
133451
+ case "invalid-dims":
133452
+ return `${entryRelative} root has invalid dims (${detection.width}\xD7${detection.height})`;
133453
+ case "no-match":
133454
+ return `${entryRelative} dims ${detection.width}\xD7${detection.height} (ratio ${detection.ratio.toFixed(2)}) don't match 16:9, 9:16, or 1:1`;
133455
+ case "read-error":
133456
+ return `Couldn't read ${entryRelative} for aspect-ratio detection (${detection.error})`;
133457
+ }
133458
+ }
133310
133459
  function resolveVariablesAndValidateIfLocal(inline, filePath, strict, source) {
133311
133460
  const variables = resolveVariablesArg(inline, filePath);
133312
133461
  if (!variables || Object.keys(variables).length === 0) return variables;
@@ -133384,6 +133533,7 @@ function buildRenderBody(opts) {
133384
133533
  if (opts.quality !== void 0) body.quality = opts.quality;
133385
133534
  if (opts.format !== void 0) body.format = opts.format;
133386
133535
  if (opts.resolution !== void 0) body.resolution = opts.resolution;
133536
+ if (opts.aspectRatio !== void 0) body.aspect_ratio = opts.aspectRatio;
133387
133537
  if (opts.composition !== void 0) body.composition = opts.composition;
133388
133538
  if (opts.variables !== void 0) body.variables = opts.variables;
133389
133539
  if (opts.title !== void 0) body.title = opts.title;
@@ -133476,11 +133626,12 @@ async function streamVideo(url, destPath, asJson) {
133476
133626
  process.exit(1);
133477
133627
  }
133478
133628
  }
133479
- var VALID_QUALITY2, VALID_FORMAT2, VALID_RESOLUTION, FORMAT_EXT2, examples26, render_default2, IDEMPOTENCY_KEY_RE;
133629
+ var VALID_QUALITY2, VALID_FORMAT2, VALID_RESOLUTION, VALID_ASPECT_RATIO, FORMAT_EXT2, examples26, render_default2, IDEMPOTENCY_KEY_RE, ASPECT_FALLBACK_HINT;
133480
133630
  var init_render4 = __esm({
133481
133631
  "src/commands/cloud/render.ts"() {
133482
133632
  "use strict";
133483
133633
  init_dist();
133634
+ init_detectAspectRatio();
133484
133635
  init_colors();
133485
133636
  init_format();
133486
133637
  init_project();
@@ -133493,14 +133644,8 @@ var init_render4 = __esm({
133493
133644
  init_statusColor();
133494
133645
  VALID_QUALITY2 = ["draft", "standard", "high"];
133495
133646
  VALID_FORMAT2 = ["mp4", "webm", "mov"];
133496
- VALID_RESOLUTION = [
133497
- "landscape",
133498
- "portrait",
133499
- "landscape-4k",
133500
- "portrait-4k",
133501
- "square",
133502
- "square-4k"
133503
- ];
133647
+ VALID_RESOLUTION = ["1080p", "4k"];
133648
+ VALID_ASPECT_RATIO = ["16:9", "9:16", "1:1"];
133504
133649
  FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
133505
133650
  examples26 = [
133506
133651
  ["Render the current directory in the cloud", "hyperframes cloud render"],
@@ -133528,7 +133673,11 @@ var init_render4 = __esm({
133528
133673
  format: { type: "string", description: "mp4 | webm | mov (default: mp4)" },
133529
133674
  resolution: {
133530
133675
  type: "string",
133531
- description: "Resolution preset: landscape | portrait | landscape-4k | portrait-4k | square | square-4k"
133676
+ description: "Resolution tier: 1080p | 4k (default: 1080p; 4k is billed at 1.5x)"
133677
+ },
133678
+ "aspect-ratio": {
133679
+ type: "string",
133680
+ description: "Aspect ratio: 16:9 | 9:16 | 1:1 (default: 16:9)"
133532
133681
  },
133533
133682
  composition: {
133534
133683
  type: "string",
@@ -133611,6 +133760,9 @@ var init_render4 = __esm({
133611
133760
  const resolution = parseEnumFlag(args.resolution, VALID_RESOLUTION, {
133612
133761
  flag: "--resolution"
133613
133762
  });
133763
+ const explicitAspectRatio = parseEnumFlag(args["aspect-ratio"], VALID_ASPECT_RATIO, {
133764
+ flag: "--aspect-ratio"
133765
+ });
133614
133766
  const pollIntervalMs = parsePollIntervalMs(args["poll-interval"]);
133615
133767
  const maxWaitMs = parseMaxWaitMs(args["max-wait"]);
133616
133768
  validateIdempotencyKey(args["idempotency-key"]);
@@ -133619,6 +133771,7 @@ var init_render4 = __esm({
133619
133771
  assetId: args["asset-id"],
133620
133772
  url: args.url
133621
133773
  });
133774
+ const aspectRatio = explicitAspectRatio ?? maybeAutoDetectAspectRatio(project, args.composition, asJson);
133622
133775
  const variables = resolveVariablesAndValidateIfLocal(
133623
133776
  args.variables,
133624
133777
  args["variables-file"],
@@ -133633,6 +133786,7 @@ var init_render4 = __esm({
133633
133786
  quality,
133634
133787
  format,
133635
133788
  resolution,
133789
+ aspectRatio,
133636
133790
  composition: args.composition,
133637
133791
  variables,
133638
133792
  title: args.title,
@@ -133693,6 +133847,7 @@ var init_render4 = __esm({
133693
133847
  }
133694
133848
  });
133695
133849
  IDEMPOTENCY_KEY_RE = /^[A-Za-z0-9_:.-]{1,255}$/;
133850
+ ASPECT_FALLBACK_HINT = "server will default aspect_ratio to 16:9. Pass --aspect-ratio to override.";
133696
133851
  }
133697
133852
  });
133698
133853
 
@@ -135077,10 +135232,10 @@ if (rootVersionRequested) {
135077
135232
  process.exit(0);
135078
135233
  }
135079
135234
  try {
135080
- const { readFileSync: readFileSync58 } = await import("fs");
135235
+ const { readFileSync: readFileSync59 } = await import("fs");
135081
135236
  const { resolve: resolve46 } = await import("path");
135082
135237
  const envPath = resolve46(process.cwd(), ".env");
135083
- const envContent = readFileSync58(envPath, "utf-8");
135238
+ const envContent = readFileSync59(envPath, "utf-8");
135084
135239
  for (const rawLine of envContent.split("\n")) {
135085
135240
  let line = rawLine.trim();
135086
135241
  if (!line || line.startsWith("#")) continue;
@@ -181,38 +181,23 @@ The slide distance DECAYS per word (80→12px) — mimics a camera settling.
181
181
  Vector animations that play inside a composition. Use for logos, character animations, icons.
182
182
 
183
183
  ```html
184
- <!-- The web-component package is `@lottiefiles/dotlottie-wc` (the SDK
185
- `@lottiefiles/dotlottie-web` does NOT expose a custom element).
186
- The element tag is <dotlottie-wc>. -->
187
- <script
188
- src="https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc/dist/dotlottie-wc.js"
189
- type="module"
190
- ></script>
191
- <dotlottie-wc
192
- class="lottie"
193
- src="capture/assets/lottie/animation-0.json"
194
- autoplay
195
- loop
196
- speed="1.5"
197
- style="width:500px;height:500px;"
198
- >
199
- </dotlottie-wc>
184
+ <div id="anim" class="lottie"></div>
185
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.12.2/lottie.min.js"></script>
200
186
  <script>
201
- gsap.set(".lottie", { scale: 0.3, opacity: 0 });
202
- tl.to(".lottie", { scale: 1, opacity: 1, duration: 0.35, ease: "back.out(1.6)" }, 0.2);
203
- </script>
204
- ```
205
-
206
- Or use lottie-web for more control:
187
+ window.__hfLottie = window.__hfLottie || [];
188
+
189
+ const anim = lottie.loadAnimation({
190
+ container: document.getElementById("anim"),
191
+ renderer: "svg",
192
+ loop: false,
193
+ autoplay: false,
194
+ path: "capture/assets/lottie/animation-0.json",
195
+ });
196
+ window.__hfLottie.push(anim);
207
197
 
208
- ```javascript
209
- var anim = lottie.loadAnimation({
210
- container: document.getElementById("anim"),
211
- renderer: "svg",
212
- loop: false,
213
- autoplay: false,
214
- path: "capture/assets/lottie/animation-0.json",
215
- });
198
+ gsap.set("#anim", { scale: 0.3, opacity: 0 });
199
+ tl.to("#anim", { scale: 1, opacity: 1, duration: 0.35, ease: "back.out(1.6)" }, 0.2);
200
+ </script>
216
201
  ```
217
202
 
218
203
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperframes",
3
- "version": "0.6.63",
3
+ "version": "0.6.65",
4
4
  "description": "HyperFrames CLI — create, preview, and render HTML video compositions",
5
5
  "repository": {
6
6
  "type": "git",