@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.
package/dist/index.js CHANGED
@@ -99576,12 +99576,28 @@ var coreRules = [
99576
99576
  function escapeRegExp(value) {
99577
99577
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
99578
99578
  }
99579
- function selectorTargetsManagedMedia(selector, mediaIds) {
99579
+ function hasAttrName(tagSource, attr) {
99580
+ const escaped = escapeRegExp(attr);
99581
+ const attrs = tagSource.replace(/^<\s*[a-z][\w:-]*/i, "");
99582
+ return new RegExp(`(?:^|\\s)${escaped}(?:\\s*=|\\s|/?>)`, "i").test(attrs);
99583
+ }
99584
+ function classNamesFromAttr(classAttr) {
99585
+ if (!classAttr) return [];
99586
+ return classAttr.split(/\s+/).filter(Boolean);
99587
+ }
99588
+ function selectorTargetsManagedMedia(selector, mediaIndex) {
99580
99589
  const normalized = selector.trim();
99581
99590
  if (!normalized) return false;
99582
- if (/\b(video|audio)\b/i.test(normalized)) return true;
99583
- for (const mediaId of mediaIds) {
99584
- if (normalized.includes(`#${mediaId}`) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
99591
+ if (mediaIndex.hasVideo && /\bvideo\b/i.test(normalized)) return true;
99592
+ if (mediaIndex.hasAudio && /\baudio\b/i.test(normalized)) return true;
99593
+ for (const mediaId of mediaIndex.ids) {
99594
+ const escapedId = escapeRegExp(mediaId);
99595
+ if (new RegExp(`#${escapedId}(?![\\w-])`).test(normalized) || normalized.includes(`[id="${mediaId}"]`) || normalized.includes(`[id='${mediaId}']`)) {
99596
+ return true;
99597
+ }
99598
+ }
99599
+ for (const className of mediaIndex.classes) {
99600
+ if (new RegExp(`\\.${escapeRegExp(className)}(?![\\w-])`).test(normalized)) {
99585
99601
  return true;
99586
99602
  }
99587
99603
  }
@@ -99589,66 +99605,96 @@ function selectorTargetsManagedMedia(selector, mediaIds) {
99589
99605
  }
99590
99606
  function findImperativeMediaControlFindings(ctx) {
99591
99607
  const findings = [];
99592
- const managedMediaIds = new Set(
99593
- ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio").map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
99594
- );
99595
- if (managedMediaIds.size === 0 || ctx.scripts.length === 0) return findings;
99608
+ const mediaTags = ctx.tags.filter((tag) => tag.name === "video" || tag.name === "audio");
99609
+ const mediaIndex = {
99610
+ ids: new Set(
99611
+ mediaTags.map((tag) => readAttr(tag.raw, "id")).filter((id) => Boolean(id))
99612
+ ),
99613
+ classes: new Set(mediaTags.flatMap((tag) => classNamesFromAttr(readAttr(tag.raw, "class")))),
99614
+ hasVideo: mediaTags.some((tag) => tag.name === "video"),
99615
+ hasAudio: mediaTags.some((tag) => tag.name === "audio")
99616
+ };
99617
+ if (mediaTags.length === 0 || ctx.scripts.length === 0) return findings;
99596
99618
  for (const script of ctx.scripts) {
99597
99619
  const mediaVars = /* @__PURE__ */ new Map();
99598
99620
  const assignmentPatterns = [
99599
- /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
99600
- /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)/g
99621
+ {
99622
+ pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)/g,
99623
+ variableIndex: 1,
99624
+ targetIndex: 2
99625
+ },
99626
+ {
99627
+ pattern: /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\2\s*\)/g,
99628
+ variableIndex: 1,
99629
+ targetIndex: 3
99630
+ }
99601
99631
  ];
99602
- for (const pattern of assignmentPatterns) {
99632
+ for (const { pattern, variableIndex, targetIndex } of assignmentPatterns) {
99603
99633
  let match2;
99604
99634
  while ((match2 = pattern.exec(script.content)) !== null) {
99605
- const variableName = match2[1];
99606
- const target = match2[2];
99635
+ const variableName = match2[variableIndex];
99636
+ const target = match2[targetIndex];
99607
99637
  if (!variableName || !target) continue;
99608
- if (managedMediaIds.has(target) || selectorTargetsManagedMedia(target, managedMediaIds)) {
99609
- mediaVars.set(variableName, managedMediaIds.has(target) ? target : void 0);
99638
+ if (mediaIndex.ids.has(target) || selectorTargetsManagedMedia(target, mediaIndex)) {
99639
+ mediaVars.set(variableName, mediaIndex.ids.has(target) ? target : void 0);
99610
99640
  }
99611
99641
  }
99612
99642
  }
99613
99643
  const directIdPatterns = [
99614
99644
  {
99615
99645
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
99616
- kind: "play()"
99646
+ kind: "play()",
99647
+ targetIndex: 1
99617
99648
  },
99618
99649
  {
99619
99650
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
99620
- kind: "pause()"
99651
+ kind: "pause()",
99652
+ targetIndex: 1
99621
99653
  },
99622
99654
  {
99623
99655
  pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
99624
- kind: "currentTime"
99656
+ kind: "currentTime",
99657
+ targetIndex: 1
99658
+ },
99659
+ {
99660
+ pattern: /\b(?:document|window\.document)\.getElementById\(\s*["']([^"']+)["']\s*\)\.muted\s*=/g,
99661
+ kind: "muted assignment",
99662
+ targetIndex: 1
99625
99663
  },
99626
99664
  {
99627
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.play\s*\(/g,
99628
- kind: "play()"
99665
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.play\s*\(/g,
99666
+ kind: "play()",
99667
+ targetIndex: 2
99629
99668
  },
99630
99669
  {
99631
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.pause\s*\(/g,
99632
- kind: "pause()"
99670
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.pause\s*\(/g,
99671
+ kind: "pause()",
99672
+ targetIndex: 2
99633
99673
  },
99634
99674
  {
99635
- pattern: /\b(?:document|window\.document)\.querySelector\(\s*["']([^"']+)["']\s*\)\.currentTime\s*=/g,
99636
- kind: "currentTime"
99675
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.currentTime\s*=/g,
99676
+ kind: "currentTime",
99677
+ targetIndex: 2
99678
+ },
99679
+ {
99680
+ pattern: /\b(?:document|window\.document)\.querySelector\(\s*(["'])([\s\S]*?)\1\s*\)\.muted\s*=/g,
99681
+ kind: "muted assignment",
99682
+ targetIndex: 2
99637
99683
  }
99638
99684
  ];
99639
- for (const { pattern, kind } of directIdPatterns) {
99685
+ for (const { pattern, kind, targetIndex } of directIdPatterns) {
99640
99686
  let match2;
99641
99687
  while ((match2 = pattern.exec(script.content)) !== null) {
99642
- const target = match2[1];
99688
+ const target = match2[targetIndex];
99643
99689
  if (!target) continue;
99644
- const elementId = managedMediaIds.has(target) ? target : selectorTargetsManagedMedia(target, managedMediaIds) ? void 0 : null;
99690
+ const elementId = mediaIndex.ids.has(target) ? target : selectorTargetsManagedMedia(target, mediaIndex) ? void 0 : null;
99645
99691
  if (elementId === null) continue;
99646
99692
  findings.push({
99647
99693
  code: "imperative_media_control",
99648
99694
  severity: "error",
99649
99695
  message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
99650
99696
  elementId: elementId || void 0,
99651
- 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.",
99697
+ 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.",
99652
99698
  snippet: truncateSnippet(match2[0])
99653
99699
  });
99654
99700
  }
@@ -99658,7 +99704,11 @@ function findImperativeMediaControlFindings(ctx) {
99658
99704
  const variablePatterns = [
99659
99705
  { pattern: new RegExp(`\\b${escapedVar}\\.play\\s*\\(`, "g"), kind: "play()" },
99660
99706
  { pattern: new RegExp(`\\b${escapedVar}\\.pause\\s*\\(`, "g"), kind: "pause()" },
99661
- { pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" }
99707
+ { pattern: new RegExp(`\\b${escapedVar}\\.currentTime\\s*=`, "g"), kind: "currentTime" },
99708
+ {
99709
+ pattern: new RegExp(`\\b${escapedVar}\\.muted\\s*=`, "g"),
99710
+ kind: "muted assignment"
99711
+ }
99662
99712
  ];
99663
99713
  for (const { pattern, kind } of variablePatterns) {
99664
99714
  let match2;
@@ -99668,7 +99718,7 @@ function findImperativeMediaControlFindings(ctx) {
99668
99718
  severity: "error",
99669
99719
  message: `Inline <script> imperatively controls managed media via ${kind}. HyperFrames must own media play/pause/seek to keep preview, timeline, and renders deterministic.`,
99670
99720
  elementId,
99671
- 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.",
99721
+ 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.",
99672
99722
  snippet: truncateSnippet(match2[0])
99673
99723
  });
99674
99724
  }
@@ -99730,15 +99780,27 @@ var mediaRules = [
99730
99780
  const findings = [];
99731
99781
  for (const tag of tags) {
99732
99782
  if (tag.name !== "video") continue;
99733
- const hasMuted = /\bmuted\b/i.test(tag.raw);
99734
- if (!hasMuted && readAttr(tag.raw, "data-start")) {
99783
+ const hasMuted = hasAttrName(tag.raw, "muted");
99784
+ const hasDeclaredAudio = readAttr(tag.raw, "data-has-audio") === "true";
99785
+ if (!hasMuted && !hasDeclaredAudio && readAttr(tag.raw, "data-start")) {
99735
99786
  const elementId = readAttr(tag.raw, "id") || void 0;
99736
99787
  findings.push({
99737
99788
  code: "video_missing_muted",
99738
99789
  severity: "error",
99739
- 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.`,
99790
+ 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.`,
99791
+ elementId,
99792
+ fixHint: 'Add the `muted` attribute for silent video, or add data-has-audio="true" when the video track should contribute audio.',
99793
+ snippet: truncateSnippet(tag.raw)
99794
+ });
99795
+ }
99796
+ if (hasMuted && hasDeclaredAudio) {
99797
+ const elementId = readAttr(tag.raw, "id") || void 0;
99798
+ findings.push({
99799
+ code: "video_muted_with_declared_audio",
99800
+ severity: "error",
99801
+ message: `<video${elementId ? ` id="${elementId}"` : ""}> declares data-has-audio="true" but also has muted. Studio preview will silence the video audio.`,
99740
99802
  elementId,
99741
- fixHint: "Add the `muted` attribute to the <video> tag and use a separate <audio> element with the same src for audio playback.",
99803
+ 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.',
99742
99804
  snippet: truncateSnippet(tag.raw)
99743
99805
  });
99744
99806
  }
@@ -99860,7 +99922,7 @@ var mediaRules = [
99860
99922
  }
99861
99923
  return findings;
99862
99924
  },
99863
- // media_missing_id + media_missing_src + media_preload_none
99925
+ // media_missing_data_start + media_missing_id + media_missing_src + media_preload_none
99864
99926
  ({ tags }) => {
99865
99927
  const findings = [];
99866
99928
  for (const tag of tags) {
@@ -99868,6 +99930,16 @@ var mediaRules = [
99868
99930
  const hasDataStart = readAttr(tag.raw, "data-start");
99869
99931
  const hasId = readAttr(tag.raw, "id");
99870
99932
  const hasSrc = readAttr(tag.raw, "src");
99933
+ if (hasSrc && !hasDataStart) {
99934
+ findings.push({
99935
+ code: "media_missing_data_start",
99936
+ severity: "error",
99937
+ 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.`,
99938
+ elementId: hasId || void 0,
99939
+ fixHint: `Add data-start="0" (or the intended start time) and data-duration if the clip should stop before the source ends.`,
99940
+ snippet: truncateSnippet(tag.raw)
99941
+ });
99942
+ }
99871
99943
  if (hasDataStart && !hasId) {
99872
99944
  findings.push({
99873
99945
  code: "media_missing_id",
@@ -100724,6 +100796,31 @@ var compositionRules = [
100724
100796
  }
100725
100797
  return findings;
100726
100798
  },
100799
+ // split_data_attribute_selector
100800
+ ({ scripts, styles }) => {
100801
+ const findings = [];
100802
+ const splitDataAttrSelectorPattern = /\[data-composition-id=(["'])([^"'\]]+)\1\s+(data-[\w:-]+)=(["'])([^"'\]]*)\4\]/g;
100803
+ const scan = (content) => {
100804
+ splitDataAttrSelectorPattern.lastIndex = 0;
100805
+ let match2;
100806
+ while ((match2 = splitDataAttrSelectorPattern.exec(content)) !== null) {
100807
+ const compId = match2[2] ?? "";
100808
+ const attrName = match2[3] ?? "";
100809
+ const attrValue = match2[5] ?? "";
100810
+ findings.push({
100811
+ code: "split_data_attribute_selector",
100812
+ severity: "error",
100813
+ 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.`,
100814
+ selector: match2[0],
100815
+ fixHint: `Use separate attribute selectors: [data-composition-id="${compId}"][${attrName}="${attrValue}"].`,
100816
+ snippet: truncateSnippet(match2[0])
100817
+ });
100818
+ }
100819
+ };
100820
+ for (const style of styles) scan(style.content);
100821
+ for (const script of scripts) scan(script.content);
100822
+ return findings;
100823
+ },
100727
100824
  // template_literal_selector
100728
100825
  ({ scripts }) => {
100729
100826
  const findings = [];
@@ -101137,13 +101234,13 @@ async function beginFrameCapture(page, options, frameTimeTicks, interval) {
101137
101234
  }
101138
101235
  async function pageScreenshotCapture(page, options) {
101139
101236
  const client = await getCdpSession(page);
101140
- const format3 = options.format === "png" ? "png" : "jpeg";
101237
+ const isPng = options.format === "png";
101141
101238
  const result = await client.send("Page.captureScreenshot", {
101142
- format: format3,
101143
- quality: format3 === "jpeg" ? options.quality ?? 80 : void 0,
101239
+ format: isPng ? "png" : "jpeg",
101240
+ quality: isPng ? void 0 : options.quality ?? 80,
101144
101241
  fromSurface: true,
101145
101242
  captureBeyondViewport: false,
101146
- optimizeForSpeed: true
101243
+ optimizeForSpeed: !isPng
101147
101244
  });
101148
101245
  return Buffer.from(result.data, "base64");
101149
101246
  }
@@ -101356,12 +101453,6 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
101356
101453
  deviceScaleFactor: options.deviceScaleFactor || 1
101357
101454
  };
101358
101455
  await page.setViewport(viewport);
101359
- if (options.format === "png") {
101360
- const cdp = await getCdpSession(page);
101361
- await cdp.send("Emulation.setDefaultBackgroundColorOverride", {
101362
- color: { r: 0, g: 0, b: 0, a: 0 }
101363
- });
101364
- }
101365
101456
  return {
101366
101457
  browser,
101367
101458
  page,
@@ -101457,6 +101548,9 @@ async function initializeSession(session) {
101457
101548
  );
101458
101549
  }
101459
101550
  await page.evaluate(`document.fonts?.ready`);
101551
+ if (session.options.format === "png") {
101552
+ await initTransparentBackground(session.page);
101553
+ }
101460
101554
  session.isInitialized = true;
101461
101555
  return;
101462
101556
  }
@@ -101520,6 +101614,9 @@ async function initializeSession(session) {
101520
101614
  await page.evaluate(`document.fonts?.ready`);
101521
101615
  warmupRunning = false;
101522
101616
  session.beginFrameTimeTicks = (warmupTicks + 10) * session.beginFrameIntervalMs;
101617
+ if (session.options.format === "png") {
101618
+ await initTransparentBackground(session.page);
101619
+ }
101523
101620
  session.isInitialized = true;
101524
101621
  }
101525
101622
  async function captureFrameErrorDiagnostics(session, frameIndex, time, error) {
@@ -108658,7 +108755,217 @@ function createFileServer2(options) {
108658
108755
  // src/services/htmlCompiler.ts
108659
108756
  import { readFileSync as readFileSync9, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
108660
108757
  import { join as join15, dirname as dirname9, resolve as resolve10 } from "path";
108758
+
108759
+ // ../core/src/compiler/htmlBundler.ts
108760
+ import { transformSync } from "esbuild";
108761
+
108762
+ // ../core/src/compiler/compositionScoping.ts
108661
108763
  import postcss from "postcss";
108764
+ function escapeRegExp2(value) {
108765
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108766
+ }
108767
+ function escapeCssAttributeValue(value) {
108768
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
108769
+ }
108770
+ function scopeSelector(selector, scope, compositionId) {
108771
+ const selectorWithoutRootTiming = normalizeCompositionRootSelector(
108772
+ selector,
108773
+ scope,
108774
+ compositionId
108775
+ );
108776
+ const trimmed = selectorWithoutRootTiming.trim();
108777
+ if (!trimmed) return selector;
108778
+ if (/^(html|body|:root|\*)$/i.test(trimmed)) return selector;
108779
+ const compositionIdPattern = new RegExp(
108780
+ `data-composition-id\\s*=\\s*(["'])${escapeRegExp2(compositionId)}\\1`
108781
+ );
108782
+ if (compositionIdPattern.test(trimmed)) return selectorWithoutRootTiming;
108783
+ const leading = selectorWithoutRootTiming.match(/^\s*/)?.[0] ?? "";
108784
+ const trailing = selectorWithoutRootTiming.match(/\s*$/)?.[0] ?? "";
108785
+ return `${leading}${scope} ${trimmed}${trailing}`;
108786
+ }
108787
+ function normalizeCompositionRootSelector(selector, scope, compositionId) {
108788
+ const quotedCompId = escapeRegExp2(compositionId);
108789
+ const compAttr = String.raw`\[\s*data-composition-id\s*=\s*(?:"${quotedCompId}"|'${quotedCompId}')\s*\]`;
108790
+ const timingAttr = String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`;
108791
+ return selector.replace(new RegExp(`${compAttr}(?:${timingAttr})+`, "g"), scope).replace(new RegExp(`(?:${timingAttr})+${compAttr}`, "g"), scope);
108792
+ }
108793
+ var GLOBAL_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
108794
+ function isAtRuleNode(node) {
108795
+ return node?.type === "atrule";
108796
+ }
108797
+ function isInsideGlobalAtRule(rule) {
108798
+ let current = rule.parent;
108799
+ while (current) {
108800
+ if (isAtRuleNode(current) && GLOBAL_AT_RULES.has(current.name.toLowerCase())) {
108801
+ return true;
108802
+ }
108803
+ current = current.parent;
108804
+ }
108805
+ return false;
108806
+ }
108807
+ function scopeCssToComposition(css, compositionId) {
108808
+ const trimmedCompositionId = compositionId.trim();
108809
+ if (!css || !trimmedCompositionId) return css;
108810
+ const scope = `[data-composition-id="${escapeCssAttributeValue(trimmedCompositionId)}"]`;
108811
+ const root = postcss.parse(css);
108812
+ root.walkRules((rule) => {
108813
+ if (isInsideGlobalAtRule(rule)) return;
108814
+ rule.selectors = rule.selectors.map(
108815
+ (selector) => scopeSelector(selector, scope, trimmedCompositionId)
108816
+ );
108817
+ });
108818
+ return root.toResult({ map: false }).css;
108819
+ }
108820
+ function wrapScopedCompositionScript(source2, compositionId, errorLabel = "[HyperFrames] composition script error:") {
108821
+ const compositionIdLiteral = JSON.stringify(compositionId);
108822
+ const errorLabelLiteral = JSON.stringify(errorLabel);
108823
+ const escapedCompositionId = escapeRegExp2(compositionId);
108824
+ const rootSelectorPatternLiteral = JSON.stringify(
108825
+ String.raw`\[\s*data-composition-id\s*=\s*(?:"${escapedCompositionId}"|'${escapedCompositionId}')\s*\]`
108826
+ );
108827
+ const timingSelectorPatternLiteral = JSON.stringify(
108828
+ String.raw`\s*\[\s*data-(?:start|duration)\s*=\s*(?:"[^"]*"|'[^']*')\s*\]`
108829
+ );
108830
+ return `(function(){
108831
+ var __hfCompId = ${compositionIdLiteral};
108832
+ var __hfErrorLabel = ${errorLabelLiteral};
108833
+ var __hfEscapeAttr = function(value) {
108834
+ return (value + "").replace(/\\\\/g, "\\\\\\\\").replace(/"/g, "\\\\\\"");
108835
+ };
108836
+ var __hfRootSelector = __hfCompId
108837
+ ? '[data-composition-id="' + __hfEscapeAttr(__hfCompId) + '"]'
108838
+ : "";
108839
+ var __hfRoot = null;
108840
+ var __hfRootSelectorPattern = ${rootSelectorPatternLiteral};
108841
+ var __hfTimingSelectorPattern = ${timingSelectorPatternLiteral};
108842
+ var __hfNormalizeSelector = function(selector) {
108843
+ if (!__hfCompId || typeof selector !== "string") return selector;
108844
+ return selector
108845
+ .replace(new RegExp(__hfRootSelectorPattern + '(?:' + __hfTimingSelectorPattern + ')+', 'g'), __hfRootSelector)
108846
+ .replace(new RegExp('(?:' + __hfTimingSelectorPattern + ')+' + __hfRootSelectorPattern, 'g'), __hfRootSelector);
108847
+ };
108848
+ var __hfFindRoot = function() {
108849
+ if (!__hfRoot && __hfRootSelector) {
108850
+ __hfRoot = window.document.querySelector(__hfRootSelector);
108851
+ }
108852
+ return __hfRoot;
108853
+ };
108854
+ var __hfContains = function(node) {
108855
+ var root = __hfFindRoot();
108856
+ return !root || node === root || root.contains(node);
108857
+ };
108858
+ var __hfQueryAll = function(selector) {
108859
+ var root = __hfFindRoot();
108860
+ if (!root || typeof selector !== "string") {
108861
+ return window.document.querySelectorAll(selector);
108862
+ }
108863
+ return Array.prototype.filter.call(window.document.querySelectorAll(__hfNormalizeSelector(selector)), function(node) {
108864
+ return __hfContains(node);
108865
+ });
108866
+ };
108867
+ var __hfQueryOne = function(selector) {
108868
+ var matches = __hfQueryAll(selector);
108869
+ return matches[0] || null;
108870
+ };
108871
+ var __hfScopedDocument = typeof Proxy === "function"
108872
+ ? new Proxy(window.document, {
108873
+ get: function(target, prop, receiver) {
108874
+ if (prop === "querySelector") return __hfQueryOne;
108875
+ if (prop === "querySelectorAll") return __hfQueryAll;
108876
+ if (prop === "getElementById") {
108877
+ return function(id) {
108878
+ var found = target.getElementById(id);
108879
+ return found && __hfContains(found) ? found : null;
108880
+ };
108881
+ }
108882
+ var value = Reflect.get(target, prop, receiver);
108883
+ return typeof value === "function" ? value.bind(target) : value;
108884
+ },
108885
+ })
108886
+ : window.document;
108887
+ var __hfResolveGsapTarget = function(target) {
108888
+ if (typeof target !== "string") return target;
108889
+ return __hfQueryAll(target);
108890
+ };
108891
+ var __hfScopeTimeline = function(timeline) {
108892
+ if (!timeline || timeline.__hfScopedCompositionRoot === __hfFindRoot()) return timeline;
108893
+ ["to", "from", "fromTo", "set"].forEach(function(method) {
108894
+ var original = timeline[method];
108895
+ if (typeof original !== "function") return;
108896
+ timeline[method] = function(target) {
108897
+ var args = Array.prototype.slice.call(arguments);
108898
+ args[0] = __hfResolveGsapTarget(target);
108899
+ return original.apply(timeline, args);
108900
+ };
108901
+ });
108902
+ try {
108903
+ Object.defineProperty(timeline, "__hfScopedCompositionRoot", {
108904
+ value: __hfFindRoot(),
108905
+ configurable: true,
108906
+ });
108907
+ } catch (_err) {}
108908
+ return timeline;
108909
+ };
108910
+ var __hfBaseGsap = typeof gsap === "undefined" ? window.gsap : gsap;
108911
+ var __hfScopedGsap = !__hfBaseGsap || typeof Proxy !== "function"
108912
+ ? __hfBaseGsap
108913
+ : new Proxy(__hfBaseGsap, {
108914
+ get: function(target, prop, receiver) {
108915
+ if (prop === "timeline") {
108916
+ return function() {
108917
+ return __hfScopeTimeline(target.timeline.apply(target, arguments));
108918
+ };
108919
+ }
108920
+ if (prop === "to" || prop === "from" || prop === "fromTo" || prop === "set") {
108921
+ return function(firstArg) {
108922
+ var args = Array.prototype.slice.call(arguments);
108923
+ args[0] = __hfResolveGsapTarget(firstArg);
108924
+ return target[prop].apply(target, args);
108925
+ };
108926
+ }
108927
+ if (prop === "utils" && target.utils && typeof Proxy === "function") {
108928
+ return new Proxy(target.utils, {
108929
+ get: function(utilsTarget, utilsProp, utilsReceiver) {
108930
+ if (utilsProp === "toArray") {
108931
+ return function(firstArg) {
108932
+ var args = Array.prototype.slice.call(arguments);
108933
+ args[0] = __hfResolveGsapTarget(firstArg);
108934
+ return utilsTarget.toArray.apply(utilsTarget, args);
108935
+ };
108936
+ }
108937
+ if (utilsProp === "selector") {
108938
+ return function(base) {
108939
+ var baseEl = typeof base === "string" ? __hfQueryOne(base) : base;
108940
+ var root = baseEl || __hfFindRoot();
108941
+ return function(selector) {
108942
+ if (!root || typeof selector !== "string") return [];
108943
+ return Array.prototype.slice.call(root.querySelectorAll(selector));
108944
+ };
108945
+ };
108946
+ }
108947
+ var value = Reflect.get(utilsTarget, utilsProp, utilsReceiver);
108948
+ return typeof value === "function" ? value.bind(utilsTarget) : value;
108949
+ },
108950
+ });
108951
+ }
108952
+ var value = Reflect.get(target, prop, receiver);
108953
+ return typeof value === "function" ? value.bind(target) : value;
108954
+ },
108955
+ });
108956
+ var __hfRun = function() {
108957
+ try {
108958
+ (function(document, gsap) {
108959
+ ${source2}
108960
+ }).call(window, __hfScopedDocument, __hfScopedGsap);
108961
+ } catch (_err) {
108962
+ console.error(__hfErrorLabel, __hfCompId, _err);
108963
+ }
108964
+ };
108965
+ __hfFindRoot();
108966
+ __hfRun();
108967
+ })()`;
108968
+ }
108662
108969
 
108663
108970
  // src/utils/paths.ts
108664
108971
  import { resolve as resolve9, basename as basename2, join as join13, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
@@ -109315,27 +109622,6 @@ function promoteCssImportsToLinkTags(html) {
109315
109622
  }
109316
109623
  return document2.toString();
109317
109624
  }
109318
- function scopeCssToComposition(css, compositionId) {
109319
- const scope = `[data-composition-id="${compositionId}"]`;
109320
- const globalAtRules = /* @__PURE__ */ new Set(["keyframes", "-webkit-keyframes", "font-face"]);
109321
- const root = postcss.parse(css);
109322
- root.walkRules((rule) => {
109323
- let node = rule.parent;
109324
- while (node) {
109325
- if (node.type === "atrule" && globalAtRules.has(node.name.toLowerCase())) {
109326
- return;
109327
- }
109328
- node = node.parent;
109329
- }
109330
- rule.selectors = rule.selectors.map((sel) => {
109331
- if (!sel.trim()) return sel;
109332
- if (/^(html|body|:root|\*)$/i.test(sel.trim())) return sel;
109333
- if (sel.includes(`data-composition-id="${compositionId}"`)) return sel;
109334
- return `${scope} ${sel}`;
109335
- });
109336
- });
109337
- return root.toResult().css;
109338
- }
109339
109625
  function coalesceHeadStylesAndBodyScripts(html) {
109340
109626
  const { document: document2 } = parseHTML(html);
109341
109627
  const head = document2.querySelector("head");
@@ -109464,28 +109750,13 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
109464
109750
  const content = (scriptEl.textContent || "").trim();
109465
109751
  if (content) {
109466
109752
  const scriptMountCompId = compId || inferredCompId || "";
109467
- const compIdLiteral = JSON.stringify(scriptMountCompId);
109468
- collectedScripts.push(`(function(){
109469
- var __compId = ${compIdLiteral};
109470
- var __run = function() {
109471
- try {
109472
- ${content}
109473
- } catch (_err) {
109474
- console.error("[Compiler] Composition script failed", __compId, _err);
109475
- }
109476
- };
109477
- if (!__compId) { __run(); return; }
109478
- ${COMPILER_MOUNT_BLOCK_START}
109479
- var __selector = '[data-composition-id="' + (__compId + '').replace(/"/g, '\\\\"') + '"]';
109480
- var __attempt = 0;
109481
- var __tryRun = function() {
109482
- if (document.querySelector(__selector)) { __run(); return; }
109483
- if (++__attempt >= 8) { __run(); return; }
109484
- requestAnimationFrame(__tryRun);
109485
- };
109486
- __tryRun();
109487
- ${COMPILER_MOUNT_BLOCK_END}
109488
- })()`);
109753
+ collectedScripts.push(
109754
+ scriptMountCompId ? wrapScopedCompositionScript(
109755
+ content,
109756
+ scriptMountCompId,
109757
+ "[Compiler] Composition script failed"
109758
+ ) : `(function(){ try { ${content} } catch (_err) { console.error("[Compiler] Composition script failed", _err); } })()`
109759
+ );
109489
109760
  }
109490
109761
  scriptEl.remove();
109491
109762
  }
@@ -109502,23 +109773,12 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
109502
109773
  if (innerW && !host.getAttribute("data-width")) host.setAttribute("data-width", innerW);
109503
109774
  if (innerH && !host.getAttribute("data-height")) host.setAttribute("data-height", innerH);
109504
109775
  innerRoot.querySelectorAll("style, script").forEach((el) => el.remove());
109505
- if (!compId && inferredCompId) {
109506
- host.innerHTML = innerRoot.outerHTML || "";
109507
- } else {
109508
- host.innerHTML = innerRoot.innerHTML || "";
109509
- }
109776
+ host.innerHTML = compId ? innerRoot.innerHTML || "" : innerRoot.outerHTML || "";
109510
109777
  } else {
109511
109778
  contentDoc.querySelectorAll("style, script").forEach((el) => el.remove());
109512
109779
  host.innerHTML = contentDoc.toString();
109513
109780
  }
109514
109781
  host.removeAttribute("data-composition-src");
109515
- const hostDataStart = host.getAttribute("data-start");
109516
- if (hostDataStart != null) {
109517
- const innerComp = host.querySelector("[data-composition-id]");
109518
- if (innerComp && !innerComp.getAttribute("data-start")) {
109519
- innerComp.setAttribute("data-start", hostDataStart);
109520
- }
109521
- }
109522
109782
  const hostW = host.getAttribute("data-width");
109523
109783
  const hostH = host.getAttribute("data-height");
109524
109784
  if (hostW && hostH) {
@@ -110463,13 +110723,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110463
110723
  const outputFormat = job.config.format ?? "mp4";
110464
110724
  const isWebm = outputFormat === "webm";
110465
110725
  const isMov = outputFormat === "mov";
110466
- const needsAlpha = isWebm || isMov;
110726
+ const isPngSequence = outputFormat === "png-sequence";
110727
+ const needsAlpha = isWebm || isMov || isPngSequence;
110467
110728
  if (needsAlpha) {
110468
110729
  cfg.forceScreenshot = true;
110469
110730
  }
110470
110731
  const enableChunkedEncode = cfg.enableChunkedEncode;
110471
110732
  const chunkedEncodeSize = cfg.chunkSizeFrames;
110472
- const enableStreamingEncode = cfg.enableStreamingEncode;
110733
+ const enableStreamingEncode = cfg.enableStreamingEncode && !isPngSequence;
110473
110734
  let peakRssBytes = 0;
110474
110735
  let peakHeapUsedBytes = 0;
110475
110736
  const sampleMemory = () => {
@@ -110857,7 +111118,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110857
111118
  }
110858
111119
  if (effectiveHdr && outputFormat !== "mp4") {
110859
111120
  log.warn(
110860
- `[Render] HDR source detected but format is ${outputFormat} \u2014 falling back to SDR. Use --format mp4 for HDR10 output.`
111121
+ `[Render] HDR source detected but format is "${outputFormat}" \u2014 falling back to SDR. HDR + alpha is not supported. Use --format mp4 for HDR10 output.`
110861
111122
  );
110862
111123
  effectiveHdr = void 0;
110863
111124
  }
@@ -110912,13 +111173,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110912
111173
  skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
110913
111174
  });
110914
111175
  const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
110915
- const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
111176
+ const FORMAT_EXT = {
111177
+ mp4: ".mp4",
111178
+ webm: ".webm",
111179
+ mov: ".mov",
111180
+ "png-sequence": ""
111181
+ };
110916
111182
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
110917
111183
  const videoOnlyPath = join16(workDir, `video-only${videoExt}`);
110918
111184
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
110919
111185
  const hasHdrContent = effectiveHdr && nativeHdrIds.size > 0;
110920
111186
  const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
110921
- const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
111187
+ const presetFormat = isPngSequence ? "mp4" : outputFormat;
111188
+ const preset = getEncoderPreset(job.config.quality, presetFormat, encoderHdr);
110922
111189
  if (job.config.crf != null && job.config.videoBitrate) {
110923
111190
  log.warn(
110924
111191
  `[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.`
@@ -111568,41 +111835,64 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111568
111835
  }
111569
111836
  }
111570
111837
  perfStages.captureMs = Date.now() - stage4Start;
111571
- const stage5Start = Date.now();
111572
- updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
111573
- const frameExt = needsAlpha ? "png" : "jpg";
111574
- const framePattern = `frame_%06d.${frameExt}`;
111575
- const encoderOpts = {
111576
- fps: job.config.fps,
111577
- width,
111578
- height,
111579
- codec: preset.codec,
111580
- preset: preset.preset,
111581
- quality: effectiveQuality,
111582
- bitrate: effectiveBitrate,
111583
- pixelFormat: preset.pixelFormat,
111584
- useGpu: job.config.useGpu,
111585
- hdr: preset.hdr
111586
- };
111587
- const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
111588
- framesDir,
111589
- framePattern,
111590
- videoOnlyPath,
111591
- encoderOpts,
111592
- chunkedEncodeSize,
111593
- abortSignal
111594
- ) : await encodeFramesFromDir(
111595
- framesDir,
111596
- framePattern,
111597
- videoOnlyPath,
111598
- encoderOpts,
111599
- abortSignal
111600
- );
111601
- assertNotAborted();
111602
- if (!encodeResult.success) {
111603
- throw new Error(`Encoding failed: ${encodeResult.error}`);
111838
+ if (isPngSequence) {
111839
+ const stage5Start = Date.now();
111840
+ updateJobStatus(job, "encoding", "Writing PNG sequence", 75, onProgress);
111841
+ if (!existsSync16(outputPath)) mkdirSync11(outputPath, { recursive: true });
111842
+ const captured = readdirSync8(framesDir).filter((name) => name.endsWith(".png")).sort();
111843
+ if (captured.length === 0) {
111844
+ throw new Error(
111845
+ `[Render] png-sequence output requested but no PNGs were captured to ${framesDir}`
111846
+ );
111847
+ }
111848
+ captured.forEach((name, i) => {
111849
+ const dst = join16(outputPath, `frame_${String(i + 1).padStart(6, "0")}.png`);
111850
+ copyFileSync2(join16(framesDir, name), dst);
111851
+ });
111852
+ if (hasAudio && existsSync16(audioOutputPath)) {
111853
+ copyFileSync2(audioOutputPath, join16(outputPath, "audio.aac"));
111854
+ log.info(
111855
+ `[Render] png-sequence: audio.aac sidecar written to ${outputPath}/audio.aac`
111856
+ );
111857
+ }
111858
+ perfStages.encodeMs = Date.now() - stage5Start;
111859
+ } else {
111860
+ const stage5Start = Date.now();
111861
+ updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
111862
+ const frameExt = needsAlpha ? "png" : "jpg";
111863
+ const framePattern = `frame_%06d.${frameExt}`;
111864
+ const encoderOpts = {
111865
+ fps: job.config.fps,
111866
+ width,
111867
+ height,
111868
+ codec: preset.codec,
111869
+ preset: preset.preset,
111870
+ quality: effectiveQuality,
111871
+ bitrate: effectiveBitrate,
111872
+ pixelFormat: preset.pixelFormat,
111873
+ useGpu: job.config.useGpu,
111874
+ hdr: preset.hdr
111875
+ };
111876
+ const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
111877
+ framesDir,
111878
+ framePattern,
111879
+ videoOnlyPath,
111880
+ encoderOpts,
111881
+ chunkedEncodeSize,
111882
+ abortSignal
111883
+ ) : await encodeFramesFromDir(
111884
+ framesDir,
111885
+ framePattern,
111886
+ videoOnlyPath,
111887
+ encoderOpts,
111888
+ abortSignal
111889
+ );
111890
+ assertNotAborted();
111891
+ if (!encodeResult.success) {
111892
+ throw new Error(`Encoding failed: ${encodeResult.error}`);
111893
+ }
111894
+ perfStages.encodeMs = Date.now() - stage5Start;
111604
111895
  }
111605
- perfStages.encodeMs = Date.now() - stage5Start;
111606
111896
  }
111607
111897
  } finally {
111608
111898
  if (streamingEncoder && !streamingEncoderClosed) {
@@ -111625,27 +111915,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111625
111915
  if (frameLookup) frameLookup.cleanup();
111626
111916
  fileServer.close();
111627
111917
  fileServer = null;
111628
- const stage6Start = Date.now();
111629
- updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
111630
- if (hasAudio) {
111631
- const muxResult = await muxVideoWithAudio(
111632
- videoOnlyPath,
111633
- audioOutputPath,
111634
- outputPath,
111635
- abortSignal
111636
- );
111637
- assertNotAborted();
111638
- if (!muxResult.success) {
111639
- throw new Error(`Audio muxing failed: ${muxResult.error}`);
111640
- }
111641
- } else {
111642
- const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
111643
- assertNotAborted();
111644
- if (!faststartResult.success) {
111645
- throw new Error(`Faststart failed: ${faststartResult.error}`);
111918
+ if (!isPngSequence) {
111919
+ const stage6Start = Date.now();
111920
+ updateJobStatus(job, "assembling", "Assembling final video", 90, onProgress);
111921
+ if (hasAudio) {
111922
+ const muxResult = await muxVideoWithAudio(
111923
+ videoOnlyPath,
111924
+ audioOutputPath,
111925
+ outputPath,
111926
+ abortSignal
111927
+ );
111928
+ assertNotAborted();
111929
+ if (!muxResult.success) {
111930
+ throw new Error(`Audio muxing failed: ${muxResult.error}`);
111931
+ }
111932
+ } else {
111933
+ const faststartResult = await applyFaststart(videoOnlyPath, outputPath, abortSignal);
111934
+ assertNotAborted();
111935
+ if (!faststartResult.success) {
111936
+ throw new Error(`Faststart failed: ${faststartResult.error}`);
111937
+ }
111646
111938
  }
111939
+ perfStages.assembleMs = Date.now() - stage6Start;
111647
111940
  }
111648
- perfStages.assembleMs = Date.now() - stage6Start;
111649
111941
  job.outputPath = outputPath;
111650
111942
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
111651
111943
  const totalElapsed = Date.now() - pipelineStart;
@@ -111684,7 +111976,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111684
111976
  }
111685
111977
  }
111686
111978
  if (job.config.debug) {
111687
- if (existsSync16(outputPath)) {
111979
+ if (!isPngSequence && existsSync16(outputPath)) {
111688
111980
  const debugOutput = join16(workDir, `output${videoExt}`);
111689
111981
  copyFileSync2(outputPath, debugOutput);
111690
111982
  }