@hyperframes/producer 0.4.30 → 0.4.32

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.
@@ -102365,12 +102365,28 @@ var coreRules = [
102365
102365
  function escapeRegExp(value) {
102366
102366
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
102367
102367
  }
102368
- function selectorTargetsManagedMedia(selector, mediaIds) {
102368
+ function hasAttrName(tagSource, attr) {
102369
+ const escaped = escapeRegExp(attr);
102370
+ const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
102371
+ return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
102372
+ }
102373
+ function classNamesFromAttr(classAttr) {
102374
+ if (!classAttr) return [];
102375
+ return classAttr.split(/\s+/).filter(Boolean);
102376
+ }
102377
+ function selectorTargetsManagedMedia(selector, mediaIndex) {
102369
102378
  const normalized = selector.trim();
102370
102379
  if (!normalized) return false;
102371
- if (/\b(video|audio)\b/i.test(normalized)) return true;
102372
- for (const mediaId of mediaIds) {
102373
- if (normalized.includes(`#${mediaId}`) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
102380
+ if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized)) return true;
102381
+ if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized)) return true;
102382
+ for (const mediaId of mediaIndex.ids) {
102383
+ const escapedId = escapeRegExp(mediaId);
102384
+ if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
102385
+ return true;
102386
+ }
102387
+ }
102388
+ for (const className of mediaIndex.classes) {
102389
+ if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized)) {
102374
102390
  return true;
102375
102391
  }
102376
102392
  }
@@ -102378,66 +102394,96 @@ function selectorTargetsManagedMedia(selector, mediaIds) {
102378
102394
  }
102379
102395
  function findImperativeMediaControlFindings(ctx) {
102380
102396
  const findings = [];
102381
- const managedMediaIds = new Set(
102382
- ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio").map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
102383
- );
102384
- if (managedMediaIds.size === 0 || ctx.scripts.length === 0) return findings;
102397
+ const mediaTags = ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio");
102398
+ const mediaIndex = {
102399
+ ids: new Set(
102400
+ mediaTags.map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
102401
+ ),
102402
+ classes: new Set(mediaTags.flatMap((tag) => classNamesFromAttr(readAttr(tag.raw, "class")))),
102403
+ hasVideo: mediaTags.some((tag) => tag.name === "video"),
102404
+ hasAudio: mediaTags.some((tag) => tag.name === "audio")
102405
+ };
102406
+ if (mediaTags.length === 0 || ctx.scripts.length === 0) return findings;
102385
102407
  for (const script of ctx.scripts) {
102386
102408
  const mediaVars = /* @__PURE__ */ new Map();
102387
102409
  const assignmentPatterns = [
102388
- /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
102389
- /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)/g
102410
+ {
102411
+ pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
102412
+ variableIndex: 1,
102413
+ targetIndex: 2
102414
+ },
102415
+ {
102416
+ pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\2\s*\)/g,
102417
+ variableIndex: 1,
102418
+ targetIndex: 3
102419
+ }
102390
102420
  ];
102391
- for (const pattern of assignmentPatterns) {
102421
+ for (const { pattern, variableIndex, targetIndex } of assignmentPatterns) {
102392
102422
  let match2;
102393
102423
  while ((match2 = pattern.exec(script.content)) !== null) {
102394
- const variableName = match2[1];
102395
- const target = match2[2];
102424
+ const variableName = match2[variableIndex];
102425
+ const target = match2[targetIndex];
102396
102426
  if (!variableName || !target) continue;
102397
- if (managedMediaIds.has(target) || selectorTargetsManagedMedia(target, managedMediaIds)) {
102398
- mediaVars.set(variableName, managedMediaIds.has(target) ? target : void 0);
102427
+ if (mediaIndex.ids.has(target) || selectorTargetsManagedMedia(target, mediaIndex)) {
102428
+ mediaVars.set(variableName, mediaIndex.ids.has(target) ? target : void 0);
102399
102429
  }
102400
102430
  }
102401
102431
  }
102402
102432
  const directIdPatterns = [
102403
102433
  {
102404
102434
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
102405
- kind: "play()"
102435
+ kind: "play()",
102436
+ targetIndex: 1
102406
102437
  },
102407
102438
  {
102408
102439
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
102409
- kind: "pause()"
102440
+ kind: "pause()",
102441
+ targetIndex: 1
102410
102442
  },
102411
102443
  {
102412
102444
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
102413
- kind: "currentTime"
102445
+ kind: "currentTime",
102446
+ targetIndex: 1
102447
+ },
102448
+ {
102449
+ pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.muted\s*=/g,
102450
+ kind: "muted assignment",
102451
+ targetIndex: 1
102414
102452
  },
102415
102453
  {
102416
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
102417
- kind: "play()"
102454
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.play\s*\(/g,
102455
+ kind: "play()",
102456
+ targetIndex: 2
102418
102457
  },
102419
102458
  {
102420
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
102421
- kind: "pause()"
102459
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.pause\s*\(/g,
102460
+ kind: "pause()",
102461
+ targetIndex: 2
102422
102462
  },
102423
102463
  {
102424
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
102425
- kind: "currentTime"
102464
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.currentTime\s*=/g,
102465
+ kind: "currentTime",
102466
+ targetIndex: 2
102467
+ },
102468
+ {
102469
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.muted\s*=/g,
102470
+ kind: "muted assignment",
102471
+ targetIndex: 2
102426
102472
  }
102427
102473
  ];
102428
- for (const { pattern, kind } of directIdPatterns) {
102474
+ for (const { pattern, kind, targetIndex } of directIdPatterns) {
102429
102475
  let match2;
102430
102476
  while ((match2 = pattern.exec(script.content)) !== null) {
102431
- const target = match2[1];
102477
+ const target = match2[targetIndex];
102432
102478
  if (!target) continue;
102433
- const elementId = managedMediaIds.has(target) ? target : selectorTargetsManagedMedia(target, managedMediaIds) ? void 0 : null;
102479
+ const elementId = mediaIndex.ids.has(target) ? target : selectorTargetsManagedMedia(target, mediaIndex) ? void 0 : null;
102434
102480
  if (elementId === null) continue;
102435
102481
  findings.push({
102436
102482
  code: "imperative_media_control",
102437
102483
  severity: "error",
102438
102484
  message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
102439
102485
  elementId: elementId || void 0,
102440
- fixHint: "Remove imperative media play/pause/currentTime control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
102486
+ fixHint: "Remove imperative media play/pause/currentTime/muted control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
102441
102487
  snippet: truncateSnippet(match2[0])
102442
102488
  });
102443
102489
  }
@@ -102447,7 +102493,11 @@ function findImperativeMediaControlFindings(ctx) {
102447
102493
  const variablePatterns = [
102448
102494
  { pattern: new RegExp(`\\b${escapedVar}\\.play\\s*\\(`, "g"), kind: "play()" },
102449
102495
  { pattern: new RegExp(`\\b${escapedVar}\\.pause\\s*\\(`, "g"), kind: "pause()" },
102450
- { pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" }
102496
+ { pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" },
102497
+ {
102498
+ pattern: new RegExp(`\\b${escapedVar}\\.muted\\s*=`, "g"),
102499
+ kind: "muted assignment"
102500
+ }
102451
102501
  ];
102452
102502
  for (const { pattern, kind } of variablePatterns) {
102453
102503
  let match2;
@@ -102457,7 +102507,7 @@ function findImperativeMediaControlFindings(ctx) {
102457
102507
  severity: "error",
102458
102508
  message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
102459
102509
  elementId,
102460
- fixHint: "Remove imperative media play/pause/currentTime control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
102510
+ fixHint: "Remove imperative media play/pause/currentTime/muted control. Express timing with data-start/data-duration and media offsets like data-media-start or data-playback-start instead.",
102461
102511
  snippet: truncateSnippet(match2[0])
102462
102512
  });
102463
102513
  }
@@ -102519,15 +102569,27 @@ var mediaRules = [
102519
102569
  const findings = [];
102520
102570
  for (const tag of tags) {
102521
102571
  if (tag.name !== "video") continue;
102522
- const hasMuted = /\bmuted\b/i.test(tag.raw);
102523
- if (!hasMuted && readAttr(tag.raw, "data-start")) {
102572
+ const hasMuted = hasAttrName(tag.raw, "muted");
102573
+ const hasDeclaredAudio = readAttr(tag.raw, "data-has-audio") === "true";
102574
+ if (!hasMuted && !hasDeclaredAudio && readAttr(tag.raw, "data-start")) {
102524
102575
  const elementId = readAttr(tag.raw, "id") || void 0;
102525
102576
  findings.push({
102526
102577
  code: "video_missing_muted",
102527
102578
  severity: "error",
102528
- message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but is not muted. The framework expects video to be muted with a separate <audio> element for sound.`,
102579
+ message: `<video${elementId ? ` id="${elementId}"` : ""}> has data-start but is not muted. Mark audible videos with data-has-audio="true"; otherwise keep video muted and use a separate <audio> element for sound.`,
102580
+ elementId,
102581
+ fixHint: 'Add the `muted` attribute for silent video, or add data-has-audio="true" when the video track should contribute audio.',
102582
+ snippet: truncateSnippet(tag.raw)
102583
+ });
102584
+ }
102585
+ if (hasMuted && hasDeclaredAudio) {
102586
+ const elementId = readAttr(tag.raw, "id") || void 0;
102587
+ findings.push({
102588
+ code: "video_muted_with_declared_audio",
102589
+ severity: "error",
102590
+ message: `<video${elementId ? ` id="${elementId}"` : ""}> declares data-has-audio="true" but also has muted. Studio preview will silence the video audio.`,
102529
102591
  elementId,
102530
- fixHint: "Add the `muted` attribute to the <video> tag and use a separate <audio> element with the same src for audio playback.",
102592
+ fixHint: 'Remove the `muted` attribute if this video should be audible, or remove data-has-audio="true" and use data-volume="0" for silent visual video.',
102531
102593
  snippet: truncateSnippet(tag.raw)
102532
102594
  });
102533
102595
  }
@@ -102649,7 +102711,7 @@ var mediaRules = [
102649
102711
  }
102650
102712
  return findings;
102651
102713
  },
102652
- // media_missing_id + media_missing_src + media_preload_none
102714
+ // media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
102653
102715
  ({ tags }) => {
102654
102716
  const findings = [];
102655
102717
  for (const tag of tags) {
@@ -102657,6 +102719,16 @@ var mediaRules = [
102657
102719
  const hasDataStart = readAttr(tag.raw, "data-start");
102658
102720
  const hasId = readAttr(tag.raw, "id");
102659
102721
  const hasSrc = readAttr(tag.raw, "src");
102722
+ if (hasSrc && !hasDataStart) {
102723
+ findings.push({
102724
+ code: "media_missing_data_start",
102725
+ severity: "error",
102726
+ message: `<${tag.name}${hasId ? ` id="${hasId}"` : ""}> has src but no data-start. HyperFrames cannot own playback for untimed media, so preview and render behavior can diverge.`,
102727
+ elementId: hasId || void 0,
102728
+ fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
102729
+ snippet: truncateSnippet(tag.raw)
102730
+ });
102731
+ }
102660
102732
  if (hasDataStart && !hasId) {
102661
102733
  findings.push({
102662
102734
  code: "media_missing_id",
@@ -103513,6 +103585,31 @@ var compositionRules = [
103513
103585
  }
103514
103586
  return findings;
103515
103587
  },
103588
+ // split_data_attribute_selector
103589
+ ({ scripts, styles }) => {
103590
+ const findings = [];
103591
+ const splitDataAttrSelectorPattern = /\[data-composition-id=(["'])([^"'\]]+)\1\s+(data-[\w:-]+)=(["'])([^"'\]]*)\4\]/g;
103592
+ const scan = (content) => {
103593
+ splitDataAttrSelectorPattern.lastIndex = 0;
103594
+ let match2;
103595
+ while ((match2 = splitDataAttrSelectorPattern.exec(content)) !== null) {
103596
+ const compId = match2[2] ?? "";
103597
+ const attrName = match2[3] ?? "";
103598
+ const attrValue = match2[5] ?? "";
103599
+ findings.push({
103600
+ code: "split_data_attribute_selector",
103601
+ severity: "error",
103602
+ message: `Selector "${match2[0]}" combines two attributes inside one CSS attribute selector. Browsers reject it, so GSAP timelines or querySelector calls will fail before registering.`,
103603
+ selector: match2[0],
103604
+ fixHint: `Use separate attribute selectors: [data-composition-id="${compId}"][${attrName}="${attrValue}"].`,
103605
+ snippet: truncateSnippet(match2[0])
103606
+ });
103607
+ }
103608
+ };
103609
+ for (const style of styles) scan(style.content);
103610
+ for (const script of scripts) scan(script.content);
103611
+ return findings;
103612
+ },
103516
103613
  // template_literal_selector
103517
103614
  ({ scripts }) => {
103518
103615
  const findings = [];
@@ -103926,13 +104023,13 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
103926
104023
  }
103927
104024
  async function pageScreenshotCapture(page, options) {
103928
104025
  const client = await getCdpSession(page);
103929
- const format3 = options.format === "png" ? "png" : "jpeg";
104026
+ const isPng = options.format === "png";
103930
104027
  const result = await client.send("Page.captureScreenshot", {
103931
- format: format3,
103932
- quality: format3 === "jpeg" ? options.quality ?? 80 : void 0,
104028
+ format: isPng ? "png" : "jpeg",
104029
+ quality: isPng ? void 0 : options.quality ?? 80,
103933
104030
  fromSurface: true,
103934
104031
  captureBeyondViewport: false,
103935
- optimizeForSpeed: true
104032
+ optimizeForSpeed: !isPng
103936
104033
  });
103937
104034
  return Buffer.from(result.data, "base64");
103938
104035
  }
@@ -104145,12 +104242,6 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
104145
104242
  deviceScaleFactor: options.deviceScaleFactor || 1
104146
104243
  };
104147
104244
  await page.setViewport(viewport);
104148
- if (options.format === "png") {
104149
- const cdp = await getCdpSession(page);
104150
- await cdp.send("Emulation.setDefaultBackgroundColorOverride", {
104151
- color: { r: 0, g: 0, b: 0, a: 0 }
104152
- });
104153
- }
104154
104245
  return {
104155
104246
  browser,
104156
104247
  page,
@@ -104246,6 +104337,9 @@ async function initializeSession(session) {
104246
104337
  );
104247
104338
  }
104248
104339
  await page.evaluate(`document.fonts?.ready`);
104340
+ if (session.options.format === "png") {
104341
+ await initTransparentBackground(session.page);
104342
+ }
104249
104343
  session.isInitialized = true;
104250
104344
  return;
104251
104345
  }
@@ -104309,6 +104403,9 @@ async function initializeSession(session) {
104309
104403
  await page.evaluate(`document.fonts?.ready`);
104310
104404
  warmupRunning = false;
104311
104405
  session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
104406
+ if (session.options.format === "png") {
104407
+ await initTransparentBackground(session.page);
104408
+ }
104312
104409
  session.isInitialized = true;
104313
104410
  }
104314
104411
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
@@ -108823,7 +108920,217 @@ function createFileServer2(options) {
108823
108920
  // src/services/htmlCompiler.ts
108824
108921
  import { readFileSync as readFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
108825
108922
  import { join as join15, dirname as dirname9, resolve as resolve10 } from "path";
108923
+
108924
+ // ../core/src/compiler/htmlBundler.ts
108925
+ import { transformSync } from "esbuild";
108926
+
108927
+ // ../core/src/compiler/compositionScoping.ts
108826
108928
  import postcss from "postcss";
108929
+ function escapeRegExp2(value) {
108930
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108931
+ }
108932
+ function escapeCssAttributeValue(value) {
108933
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
108934
+ }
108935
+ function scopeSelector(selector, scope, compositionId) {
108936
+ const selectorWithoutRootTiming = normalizeCompositionRootSelector(
108937
+ selector,
108938
+ scope,
108939
+ compositionId
108940
+ );
108941
+ const trimmed = selectorWithoutRootTiming.trim();
108942
+ if (!trimmed) return selector;
108943
+ if (/^(html|body|:root|\*)$/i.test(trimmed)) return selector;
108944
+ const compositionIdPattern = new RegExp(
108945
+ `data-composition-id\\s*=\\s*(["'])${escapeRegExp2(compositionId)}\\1`
108946
+ );
108947
+ if (compositionIdPattern.test(trimmed)) return selectorWithoutRootTiming;
108948
+ const leading = selectorWithoutRootTiming.match(/^\s*/)?.[0] ?? "";
108949
+ const trailing = selectorWithoutRootTiming.match(/\s*$/)?.[0] ?? "";
108950
+ return `${leading}${scope} ${trimmed}${trailing}`;
108951
+ }
108952
+ function normalizeCompositionRootSelector(selector, scope, compositionId) {
108953
+ const quotedCompId = escapeRegExp2(compositionId);
108954
+ const compAttr = String.raw`\[\s*data-composition-id\s*=\s*(?:"${quotedCompId}"|'${quotedCompId}')\s*\]`;
108955
+ const timingAttr = String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`;
108956
+ return selector.replace(new RegExp(`${compAttr}(?:${timingAttr})+`, "g"), scope).replace(new RegExp(`(?:${timingAttr})+${compAttr}`, "g"), scope);
108957
+ }
108958
+ var GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
108959
+ function isAtRuleNode(node) {
108960
+ return node?.type === "atrule";
108961
+ }
108962
+ function isInsideGlobalAtRule(rule) {
108963
+ let current = rule.parent;
108964
+ while (current) {
108965
+ if (isAtRuleNode(current) && GLOBAL_AT_RULES.has(current.name.toLowerCase())) {
108966
+ return true;
108967
+ }
108968
+ current = current.parent;
108969
+ }
108970
+ return false;
108971
+ }
108972
+ function scopeCssToComposition(css, compositionId) {
108973
+ const trimmedCompositionId = compositionId.trim();
108974
+ if (!css || !trimmedCompositionId) return css;
108975
+ const scope = `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`;
108976
+ const root = postcss.parse(css);
108977
+ root.walkRules((rule) => {
108978
+ if (isInsideGlobalAtRule(rule)) return;
108979
+ rule.selectors = rule.selectors.map(
108980
+ (selector) => scopeSelector(selector, scope, trimmedCompositionId)
108981
+ );
108982
+ });
108983
+ return root.toResult({ map: false }).css;
108984
+ }
108985
+ function wrapScopedCompositionScript(source2, compositionId, errorLabel = "[HyperFrames] composition script error:") {
108986
+ const compositionIdLiteral = JSON.stringify(compositionId);
108987
+ const errorLabelLiteral = JSON.stringify(errorLabel);
108988
+ const escapedCompositionId = escapeRegExp2(compositionId);
108989
+ const rootSelectorPatternLiteral = JSON.stringify(
108990
+ String.raw`\[\s*data-composition-id\s*=\s*(?:"${escapedCompositionId}"|'${escapedCompositionId}')\s*\]`
108991
+ );
108992
+ const timingSelectorPatternLiteral = JSON.stringify(
108993
+ String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`
108994
+ );
108995
+ return `(function(){
108996
+ var __hfCompId = ${compositionIdLiteral};
108997
+ var __hfErrorLabel = ${errorLabelLiteral};
108998
+ var __hfEscapeAttr = function(value) {
108999
+ return (value + "").replace(/\\\\/g, "\\\\\\\\").replace(/"/g, "\\\\\\"");
109000
+ };
109001
+ var __hfRootSelector = __hfCompId
109002
+ ? '[data-composition-id="' + __hfEscapeAttr(__hfCompId) + '"]'
109003
+ : "";
109004
+ var __hfRoot = null;
109005
+ var __hfRootSelectorPattern = ${rootSelectorPatternLiteral};
109006
+ var __hfTimingSelectorPattern = ${timingSelectorPatternLiteral};
109007
+ var __hfNormalizeSelector = function(selector) {
109008
+ if (!__hfCompId || typeof selector !== "string") return selector;
109009
+ return selector
109010
+ .replace(new RegExp(__hfRootSelectorPattern + '(?:' + __hfTimingSelectorPattern + ')+', 'g'), __hfRootSelector)
109011
+ .replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector);
109012
+ };
109013
+ var __hfFindRoot = function() {
109014
+ if (!__hfRoot && __hfRootSelector) {
109015
+ __hfRoot = window.document.querySelector(__hfRootSelector);
109016
+ }
109017
+ return __hfRoot;
109018
+ };
109019
+ var __hfContains = function(node) {
109020
+ var root = __hfFindRoot();
109021
+ return !root || node === root || root.contains(node);
109022
+ };
109023
+ var __hfQueryAll = function(selector) {
109024
+ var root = __hfFindRoot();
109025
+ if (!root || typeof selector !== "string") {
109026
+ return window.document.querySelectorAll(selector);
109027
+ }
109028
+ return Array.prototype.filter.call(window.document.querySelectorAll(__hfNormalizeSelector(selector)), function(node) {
109029
+ return __hfContains(node);
109030
+ });
109031
+ };
109032
+ var __hfQueryOne = function(selector) {
109033
+ var matches = __hfQueryAll(selector);
109034
+ return matches[0] || null;
109035
+ };
109036
+ var __hfScopedDocument = typeof Proxy === "function"
109037
+ ? new Proxy(window.document, {
109038
+ get: function(target, prop, receiver) {
109039
+ if (prop === "querySelector") return __hfQueryOne;
109040
+ if (prop === "querySelectorAll") return __hfQueryAll;
109041
+ if (prop === "getElementById") {
109042
+ return function(id) {
109043
+ var found = target.getElementById(id);
109044
+ return found && __hfContains(found) ? found : null;
109045
+ };
109046
+ }
109047
+ var value = Reflect.get(target, prop, receiver);
109048
+ return typeof value === "function" ? value.bind(target) : value;
109049
+ },
109050
+ })
109051
+ : window.document;
109052
+ var __hfResolveGsapTarget = function(target) {
109053
+ if (typeof target !== "string") return target;
109054
+ return __hfQueryAll(target);
109055
+ };
109056
+ var __hfScopeTimeline = function(timeline) {
109057
+ if (!timeline || timeline.__hfScopedCompositionRoot === __hfFindRoot()) return timeline;
109058
+ ["to", "from", "fromTo", "set"].forEach(function(method) {
109059
+ var original = timeline[method];
109060
+ if (typeof original !== "function") return;
109061
+ timeline[method] = function(target) {
109062
+ var args = Array.prototype.slice.call(arguments);
109063
+ args[0] = __hfResolveGsapTarget(target);
109064
+ return original.apply(timeline, args);
109065
+ };
109066
+ });
109067
+ try {
109068
+ Object.defineProperty(timeline, "__hfScopedCompositionRoot", {
109069
+ value: __hfFindRoot(),
109070
+ configurable: true,
109071
+ });
109072
+ } catch (_err) {}
109073
+ return timeline;
109074
+ };
109075
+ var __hfBaseGsap = typeof gsap === "undefined" ? window.gsap : gsap;
109076
+ var __hfScopedGsap = !__hfBaseGsap || typeof Proxy !== "function"
109077
+ ? __hfBaseGsap
109078
+ : new Proxy(__hfBaseGsap, {
109079
+ get: function(target, prop, receiver) {
109080
+ if (prop === "timeline") {
109081
+ return function() {
109082
+ return __hfScopeTimeline(target.timeline.apply(target, arguments));
109083
+ };
109084
+ }
109085
+ if (prop === "to" || prop === "from" || prop === "fromTo" || prop === "set") {
109086
+ return function(firstArg) {
109087
+ var args = Array.prototype.slice.call(arguments);
109088
+ args[0] = __hfResolveGsapTarget(firstArg);
109089
+ return target[prop].apply(target, args);
109090
+ };
109091
+ }
109092
+ if (prop === "utils" && target.utils && typeof Proxy === "function") {
109093
+ return new Proxy(target.utils, {
109094
+ get: function(utilsTarget, utilsProp, utilsReceiver) {
109095
+ if (utilsProp === "toArray") {
109096
+ return function(firstArg) {
109097
+ var args = Array.prototype.slice.call(arguments);
109098
+ args[0] = __hfResolveGsapTarget(firstArg);
109099
+ return utilsTarget.toArray.apply(utilsTarget, args);
109100
+ };
109101
+ }
109102
+ if (utilsProp === "selector") {
109103
+ return function(base) {
109104
+ var baseEl = typeof base === "string" ? __hfQueryOne(base) : base;
109105
+ var root = baseEl || __hfFindRoot();
109106
+ return function(selector) {
109107
+ if (!root || typeof selector !== "string") return [];
109108
+ return Array.prototype.slice.call(root.querySelectorAll(selector));
109109
+ };
109110
+ };
109111
+ }
109112
+ var value = Reflect.get(utilsTarget, utilsProp, utilsReceiver);
109113
+ return typeof value === "function" ? value.bind(utilsTarget) : value;
109114
+ },
109115
+ });
109116
+ }
109117
+ var value = Reflect.get(target, prop, receiver);
109118
+ return typeof value === "function" ? value.bind(target) : value;
109119
+ },
109120
+ });
109121
+ var __hfRun = function() {
109122
+ try {
109123
+ (function(document, gsap) {
109124
+ ${source2}
109125
+ }).call(window, __hfScopedDocument, __hfScopedGsap);
109126
+ } catch (_err) {
109127
+ console.error(__hfErrorLabel, __hfCompId, _err);
109128
+ }
109129
+ };
109130
+ __hfFindRoot();
109131
+ __hfRun();
109132
+ })()`;
109133
+ }
108827
109134
 
108828
109135
  // src/utils/paths.ts
108829
109136
  import { resolve as resolve9, basename as basename2, join as join13, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
@@ -109480,27 +109787,6 @@ function promoteCssImportsToLinkTags(html) {
109480
109787
  }
109481
109788
  return document2.toString();
109482
109789
  }
109483
- function scopeCssToComposition(css, compositionId) {
109484
- const scope = `[data-composition-id="${compositionId}"]`;
109485
- const globalAtRules = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
109486
- const root = postcss.parse(css);
109487
- root.walkRules((rule) => {
109488
- let node = rule.parent;
109489
- while (node) {
109490
- if (node.type === "atrule" && globalAtRules.has(node.name.toLowerCase())) {
109491
- return;
109492
- }
109493
- node = node.parent;
109494
- }
109495
- rule.selectors = rule.selectors.map((sel) => {
109496
- if (!sel.trim()) return sel;
109497
- if (/^(html|body|:root|\*)$/i.test(sel.trim())) return sel;
109498
- if (sel.includes(`data-composition-id="${compositionId}"`)) return sel;
109499
- return `${scope} ${sel}`;
109500
- });
109501
- });
109502
- return root.toResult().css;
109503
- }
109504
109790
  function coalesceHeadStylesAndBodyScripts(html) {
109505
109791
  const { document: document2 } = parseHTML(html);
109506
109792
  const head = document2.querySelector("head");
@@ -109629,28 +109915,13 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
109629
109915
  const content = (scriptEl.textContent || "").trim();
109630
109916
  if (content) {
109631
109917
  const scriptMountCompId = compId || inferredCompId || "";
109632
- const compIdLiteral = JSON.stringify(scriptMountCompId);
109633
- collectedScripts.push(`(function(){
109634
- var __compId = ${compIdLiteral};
109635
- var __run = function() {
109636
- try {
109637
- ${content}
109638
- } catch (_err) {
109639
- console.error("[Compiler] Composition script failed", __compId, _err);
109640
- }
109641
- };
109642
- if (!__compId) { __run(); return; }
109643
- ${COMPILER_MOUNT_BLOCK_START}
109644
- var __selector = '[data-composition-id="' + (__compId + '').replace(/"/g, '\\\\"') + '"]';
109645
- var __attempt = 0;
109646
- var __tryRun = function() {
109647
- if (document.querySelector(__selector)) { __run(); return; }
109648
- if (++__attempt >= 8) { __run(); return; }
109649
- requestAnimationFrame(__tryRun);
109650
- };
109651
- __tryRun();
109652
- ${COMPILER_MOUNT_BLOCK_END}
109653
- })()`);
109918
+ collectedScripts.push(
109919
+ scriptMountCompId ? wrapScopedCompositionScript(
109920
+ content,
109921
+ scriptMountCompId,
109922
+ "[Compiler] Composition script failed"
109923
+ ) : `(function(){ try { ${content} } catch (_err) { console.error("[Compiler] Composition script failed", _err); } })()`
109924
+ );
109654
109925
  }
109655
109926
  scriptEl.remove();
109656
109927
  }
@@ -109667,23 +109938,12 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
109667
109938
  if (innerW && !host.getAttribute("data-width")) host.setAttribute("data-width", innerW);
109668
109939
  if (innerH && !host.getAttribute("data-height")) host.setAttribute("data-height", innerH);
109669
109940
  innerRoot.querySelectorAll("style, script").forEach((el) => el.remove());
109670
- if (!compId && inferredCompId) {
109671
- host.innerHTML = innerRoot.outerHTML || "";
109672
- } else {
109673
- host.innerHTML = innerRoot.innerHTML || "";
109674
- }
109941
+ host.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || "";
109675
109942
  } else {
109676
109943
  contentDoc.querySelectorAll("style, script").forEach((el) => el.remove());
109677
109944
  host.innerHTML = contentDoc.toString();
109678
109945
  }
109679
109946
  host.removeAttribute("data-composition-src");
109680
- const hostDataStart = host.getAttribute("data-start");
109681
- if (hostDataStart != null) {
109682
- const innerComp = host.querySelector("[data-composition-id]");
109683
- if (innerComp && !innerComp.getAttribute("data-start")) {
109684
- innerComp.setAttribute("data-start", hostDataStart);
109685
- }
109686
- }
109687
109947
  const hostW = host.getAttribute("data-width");
109688
109948
  const hostH = host.getAttribute("data-height");
109689
109949
  if (hostW && hostH) {
@@ -110628,13 +110888,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110628
110888
  const outputFormat = job.config.format ?? "mp4";
110629
110889
  const isWebm = outputFormat === "webm";
110630
110890
  const isMov = outputFormat === "mov";
110631
- const needsAlpha = isWebm || isMov;
110891
+ const isPngSequence = outputFormat === "png-sequence";
110892
+ const needsAlpha = isWebm || isMov || isPngSequence;
110632
110893
  if (needsAlpha) {
110633
110894
  cfg.forceScreenshot = true;
110634
110895
  }
110635
110896
  const enableChunkedEncode = cfg.enableChunkedEncode;
110636
110897
  const chunkedEncodeSize = cfg.chunkSizeFrames;
110637
- const enableStreamingEncode = cfg.enableStreamingEncode;
110898
+ const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
110638
110899
  let peakRssBytes = 0;
110639
110900
  let peakHeapUsedBytes = 0;
110640
110901
  const sampleMemory = () => {
@@ -111022,7 +111283,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111022
111283
  }
111023
111284
  if (effectiveHdr && outputFormat !== "mp4") {
111024
111285
  log.warn(
111025
- `[Render] HDR source detected but format is ${outputFormat} \u2014 falling back to SDR. Use --format mp4 for HDR10 output.`
111286
+ `[Render] HDR source detected but format is "${outputFormat}" \u2014 falling back to SDR. HDR + alpha is not supported. Use --format mp4 for HDR10 output.`
111026
111287
  );
111027
111288
  effectiveHdr = void 0;
111028
111289
  }
@@ -111077,13 +111338,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111077
111338
  skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
111078
111339
  });
111079
111340
  const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
111080
- const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
111341
+ const FORMAT_EXT = {
111342
+ mp4: ".mp4",
111343
+ webm: ".webm",
111344
+ mov: ".mov",
111345
+ "png-sequence": ""
111346
+ };
111081
111347
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
111082
111348
  const videoOnlyPath = join16(workDir, `video-only${videoExt}`);
111083
111349
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
111084
111350
  const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
111085
111351
  const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
111086
- const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
111352
+ const presetFormat = isPngSequence ? "mp4" : outputFormat;
111353
+ const preset = getEncoderPreset(job.config.quality, presetFormat, encoderHdr);
111087
111354
  if (job.config.crf != null && job.config.videoBitrate) {
111088
111355
  log.warn(
111089
111356
  `[Render] Both crf=${job.config.crf} and videoBitrate=${job.config.videoBitrate} were set. These are mutually exclusive; honoring crf and ignoring videoBitrate. Set only one to silence this warning.`
@@ -111733,41 +112000,64 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111733
112000
  }
111734
112001
  }
111735
112002
  perfStages.captureMs = Date.now() - stage4Start;
111736
- const stage5Start = Date.now();
111737
- updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
111738
- const frameExt = needsAlpha ? "png" : "jpg";
111739
- const framePattern = `frame_%06d.${frameExt}`;
111740
- const encoderOpts = {
111741
- fps: job.config.fps,
111742
- width,
111743
- height,
111744
- codec: preset.codec,
111745
- preset: preset.preset,
111746
- quality: effectiveQuality,
111747
- bitrate: effectiveBitrate,
111748
- pixelFormat: preset.pixelFormat,
111749
- useGpu: job.config.useGpu,
111750
- hdr: preset.hdr
111751
- };
111752
- const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
111753
- framesDir,
111754
- framePattern,
111755
- videoOnlyPath,
111756
- encoderOpts,
111757
- chunkedEncodeSize,
111758
- abortSignal
111759
- ) : await encodeFramesFromDir(
111760
- framesDir,
111761
- framePattern,
111762
- videoOnlyPath,
111763
- encoderOpts,
111764
- abortSignal
111765
- );
111766
- assertNotAborted();
111767
- if (!encodeResult.success) {
111768
- throw new Error(`Encoding failed: ${encodeResult.error}`);
112003
+ if (isPngSequence) {
112004
+ const stage5Start = Date.now();
112005
+ updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
112006
+ if (!existsSync16(outputPath)) mkdirSync11(outputPath, { recursive: true });
112007
+ const captured = readdirSync8(framesDir).filter((name) => name.endsWith(".png")).sort();
112008
+ if (captured.length === 0) {
112009
+ throw new Error(
112010
+ `[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
112011
+ );
112012
+ }
112013
+ captured.forEach((name, i) => {
112014
+ const dst = join16(outputPath, `frame_${String(i + 1).padStart(6, "0")}.png`);
112015
+ copyFileSync2(join16(framesDir, name), dst);
112016
+ });
112017
+ if (hasAudio && existsSync16(audioOutputPath)) {
112018
+ copyFileSync2(audioOutputPath, join16(outputPath, "audio.aac"));
112019
+ log.info(
112020
+ `[Render] png-sequence: audio.aac sidecar written to ${outputPath}/audio.aac`
112021
+ );
112022
+ }
112023
+ perfStages.encodeMs = Date.now() - stage5Start;
112024
+ } else {
112025
+ const stage5Start = Date.now();
112026
+ updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
112027
+ const frameExt = needsAlpha ? "png" : "jpg";
112028
+ const framePattern = `frame_%06d.${frameExt}`;
112029
+ const encoderOpts = {
112030
+ fps: job.config.fps,
112031
+ width,
112032
+ height,
112033
+ codec: preset.codec,
112034
+ preset: preset.preset,
112035
+ quality: effectiveQuality,
112036
+ bitrate: effectiveBitrate,
112037
+ pixelFormat: preset.pixelFormat,
112038
+ useGpu: job.config.useGpu,
112039
+ hdr: preset.hdr
112040
+ };
112041
+ const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
112042
+ framesDir,
112043
+ framePattern,
112044
+ videoOnlyPath,
112045
+ encoderOpts,
112046
+ chunkedEncodeSize,
112047
+ abortSignal
112048
+ ) : await encodeFramesFromDir(
112049
+ framesDir,
112050
+ framePattern,
112051
+ videoOnlyPath,
112052
+ encoderOpts,
112053
+ abortSignal
112054
+ );
112055
+ assertNotAborted();
112056
+ if (!encodeResult.success) {
112057
+ throw new Error(`Encoding failed: ${encodeResult.error}`);
112058
+ }
112059
+ perfStages.encodeMs = Date.now() - stage5Start;
111769
112060
  }
111770
- perfStages.encodeMs = Date.now() - stage5Start;
111771
112061
  }
111772
112062
  } finally {
111773
112063
  if (streamingEncoder && !streamingEncoderClosed) {
@@ -111790,27 +112080,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111790
112080
  if (frameLookup) frameLookup.cleanup();
111791
112081
  fileServer.close();
111792
112082
  fileServer = null;
111793
- const stage6Start = Date.now();
111794
- updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
111795
- if (hasAudio) {
111796
- const muxResult = await muxVideoWithAudio(
111797
- videoOnlyPath,
111798
- audioOutputPath,
111799
- outputPath,
111800
- abortSignal
111801
- );
111802
- assertNotAborted();
111803
- if (!muxResult.success) {
111804
- throw new Error(`Audio muxing failed: ${muxResult.error}`);
111805
- }
111806
- } else {
111807
- const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
111808
- assertNotAborted();
111809
- if (!faststartResult.success) {
111810
- throw new Error(`Faststart failed: ${faststartResult.error}`);
112083
+ if (!isPngSequence) {
112084
+ const stage6Start = Date.now();
112085
+ updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
112086
+ if (hasAudio) {
112087
+ const muxResult = await muxVideoWithAudio(
112088
+ videoOnlyPath,
112089
+ audioOutputPath,
112090
+ outputPath,
112091
+ abortSignal
112092
+ );
112093
+ assertNotAborted();
112094
+ if (!muxResult.success) {
112095
+ throw new Error(`Audio muxing failed: ${muxResult.error}`);
112096
+ }
112097
+ } else {
112098
+ const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
112099
+ assertNotAborted();
112100
+ if (!faststartResult.success) {
112101
+ throw new Error(`Faststart failed: ${faststartResult.error}`);
112102
+ }
111811
112103
  }
112104
+ perfStages.assembleMs = Date.now() - stage6Start;
111812
112105
  }
111813
- perfStages.assembleMs = Date.now() - stage6Start;
111814
112106
  job.outputPath = outputPath;
111815
112107
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
111816
112108
  const totalElapsed = Date.now() - pipelineStart;
@@ -111849,7 +112141,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111849
112141
  }
111850
112142
  }
111851
112143
  if (job.config.debug) {
111852
- if (existsSync16(outputPath)) {
112144
+ if (!isPngSequence && existsSync16(outputPath)) {
111853
112145
  const debugOutput = join16(workDir, `output${videoExt}`);
111854
112146
  copyFileSync2(outputPath, debugOutput);
111855
112147
  }