@hyperframes/producer 0.2.2-alpha.4 → 0.2.3-alpha.1

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.
@@ -108452,6 +108452,34 @@ function getSingleClassSelector(selector) {
108452
108452
  const match2 = selector.trim().match(/^\.(?<name>[A-Za-z0-9_-]+)$/);
108453
108453
  return match2?.groups?.name || null;
108454
108454
  }
108455
+ function cssTransformToGsapProps(cssTransform) {
108456
+ const parts = [];
108457
+ const translateMatch = cssTransform.match(
108458
+ /translate\(\s*(-?[\d.]+)(%|px)?\s*,\s*(-?[\d.]+)(%|px)?\s*\)/
108459
+ );
108460
+ if (translateMatch) {
108461
+ const [, xVal, xUnit, yVal, yUnit] = translateMatch;
108462
+ if (xUnit === "%") parts.push(`xPercent: ${xVal}`);
108463
+ else parts.push(`x: ${xVal}`);
108464
+ if (yUnit === "%") parts.push(`yPercent: ${yVal}`);
108465
+ else parts.push(`y: ${yVal}`);
108466
+ }
108467
+ const txMatch = cssTransform.match(/translateX\(\s*(-?[\d.]+)(%|px)?\s*\)/);
108468
+ if (txMatch) {
108469
+ const [, val, unit] = txMatch;
108470
+ parts.push(unit === "%" ? `xPercent: ${val}` : `x: ${val}`);
108471
+ }
108472
+ const tyMatch = cssTransform.match(/translateY\(\s*(-?[\d.]+)(%|px)?\s*\)/);
108473
+ if (tyMatch) {
108474
+ const [, val, unit] = tyMatch;
108475
+ parts.push(unit === "%" ? `yPercent: ${val}` : `y: ${val}`);
108476
+ }
108477
+ const scaleMatch = cssTransform.match(/scale\(\s*([\d.]+)\s*\)/);
108478
+ if (scaleMatch) {
108479
+ parts.push(`scale: ${scaleMatch[1]}`);
108480
+ }
108481
+ return parts.length > 0 ? parts.join(", ") : null;
108482
+ }
108455
108483
  var gsapRules = [
108456
108484
  // overlapping_gsap_tweens + gsap_animates_clip_element + unscoped_gsap_selector
108457
108485
  ({ tags, scripts, rootCompositionId }) => {
@@ -108509,14 +108537,18 @@ ${right2.raw}`)
108509
108537
  const sel = win.targetSelector;
108510
108538
  const clipInfo = clipIds.get(sel) || clipClasses.get(sel);
108511
108539
  if (!clipInfo) continue;
108540
+ const conflictingProps = win.properties.filter(
108541
+ (p) => p === "visibility" || p === "display"
108542
+ );
108543
+ if (conflictingProps.length === 0) continue;
108512
108544
  const elDesc = `<${clipInfo.tag}${clipInfo.id ? ` id="${clipInfo.id}"` : ""} class="${clipInfo.classes}">`;
108513
108545
  findings.push({
108514
108546
  code: "gsap_animates_clip_element",
108515
108547
  severity: "error",
108516
- message: `GSAP animation targets a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility \u2014 animate an inner wrapper instead.`,
108548
+ message: `GSAP animation sets ${conflictingProps.join(", ")} on a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility via ${conflictingProps.join("/")} \u2014 do not animate these properties on clip elements.`,
108517
108549
  selector: sel,
108518
108550
  elementId: clipInfo.id || void 0,
108519
- fixHint: "Wrap content in a child <div> and target that with GSAP.",
108551
+ fixHint: "Remove the visibility/display tween, or move the content into a child <div> and target that instead.",
108520
108552
  snippet: truncateSnippet(win.raw)
108521
108553
  });
108522
108554
  }
@@ -108581,12 +108613,14 @@ ${right2.raw}`)
108581
108613
  }
108582
108614
  for (const [sel, { cssTransform, props, raw: raw2 }] of conflicts) {
108583
108615
  const propList = [...props].join("/");
108616
+ const gsapEquivalent = cssTransformToGsapProps(cssTransform);
108617
+ const fixHint = gsapEquivalent ? `Remove \`transform: ${cssTransform}\` from CSS and replace with GSAP properties: ${gsapEquivalent}. Example: tl.fromTo('${sel}', { ${gsapEquivalent} }, { ${gsapEquivalent}, ...yourAnimation }). tl.fromTo is exempt from this rule.` : `Remove the transform from CSS and use tl.fromTo('${sel}', { xPercent: -50, x: -1000 }, { xPercent: -50, x: 0 }) so GSAP owns the full transform state. tl.fromTo is exempt from this rule.`;
108584
108618
  findings.push({
108585
108619
  code: "gsap_css_transform_conflict",
108586
108620
  severity: "warning",
108587
108621
  message: `"${sel}" has CSS \`transform: ${cssTransform}\` and a GSAP tween animates ${propList}. GSAP will overwrite the full CSS transform, discarding any translateX(-50%) centering or CSS scale value.`,
108588
108622
  selector: sel,
108589
- fixHint: `Remove the transform from CSS and use tl.fromTo('${sel}', { xPercent: -50, x: -1000 }, { xPercent: -50, x: 0 }) so GSAP owns the full transform state. tl.fromTo is exempt from this rule.`,
108623
+ fixHint,
108590
108624
  snippet: truncateSnippet(raw2)
108591
108625
  });
108592
108626
  }
@@ -108613,6 +108647,62 @@ ${right2.raw}`)
108613
108647
  fixHint: 'Add <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script> before your animation script.'
108614
108648
  }
108615
108649
  ];
108650
+ },
108651
+ // audio_reactive_single_tween_per_group
108652
+ ({ scripts, styles }) => {
108653
+ const findings = [];
108654
+ const isCaptionFile = styles.some((s) => /\.caption[-_]?(?:group|word)/i.test(s.content));
108655
+ if (!isCaptionFile) return findings;
108656
+ for (const script of scripts) {
108657
+ const content = script.content;
108658
+ const hasAudioData = /AUDIO|audio[-_]?data|bands\[/.test(content);
108659
+ if (!hasAudioData) continue;
108660
+ const hasCaptionLoop = /forEach/.test(content) && /caption|group|cg-/.test(content);
108661
+ if (!hasCaptionLoop) continue;
108662
+ const hasInnerSamplingLoop = /for\s*\(\s*var\s+\w+\s*=\s*group\.start/.test(content) || /for\s*\(\s*var\s+at\s*=/.test(content) || /while\s*\(\s*\w+\s*<\s*group\.end/.test(content);
108663
+ if (!hasInnerSamplingLoop) {
108664
+ const hasPeakTween = /peak(?:Bass|Treble|Energy)/.test(content) && /group\.start/.test(content);
108665
+ if (hasPeakTween) {
108666
+ findings.push({
108667
+ code: "audio_reactive_single_tween_per_group",
108668
+ severity: "warning",
108669
+ message: "Audio-reactive captions use a single tween per group based on peak values. This sets one static value at group.start \u2014 not perceptible as audio reactivity.",
108670
+ fixHint: "Sample audio data at 100-200ms intervals throughout each group's lifetime (for loop from group.start to group.end) and create a tween at each sample point for visible pulsing."
108671
+ });
108672
+ }
108673
+ }
108674
+ }
108675
+ return findings;
108676
+ },
108677
+ // scene_layer_missing_visibility_kill
108678
+ ({ scripts, tags }) => {
108679
+ const findings = [];
108680
+ const sceneElements = tags.filter((t) => {
108681
+ const id = readAttr(t.raw, "id") || "";
108682
+ return /^scene\d+$/i.test(id);
108683
+ });
108684
+ if (sceneElements.length < 2) return findings;
108685
+ for (const script of scripts) {
108686
+ const content = script.content;
108687
+ for (const tag of sceneElements) {
108688
+ const id = readAttr(tag.raw, "id") || "";
108689
+ const exitPattern = new RegExp(`["']#${id}["'][^)]*opacity\\s*:\\s*0`);
108690
+ const hasExit = exitPattern.test(content);
108691
+ if (!hasExit) continue;
108692
+ const killPattern = new RegExp(`["']#${id}["'][^)]*visibility\\s*:\\s*["']hidden["']`);
108693
+ const hasKill = killPattern.test(content);
108694
+ if (!hasKill) {
108695
+ findings.push({
108696
+ code: "scene_layer_missing_visibility_kill",
108697
+ severity: "warning",
108698
+ elementId: id,
108699
+ message: `Scene layer "#${id}" exits via opacity tween but has no visibility: hidden hard kill. When scrubbing or when tweens conflict, the scene may remain partially visible and overlap the next scene.`,
108700
+ fixHint: `Add \`tl.set("#${id}", { visibility: "hidden" }, <exit-end-time>)\` after the scene's exit tweens.`
108701
+ });
108702
+ }
108703
+ }
108704
+ }
108705
+ return findings;
108616
108706
  }
108617
108707
  ];
108618
108708
 
@@ -108720,6 +108810,80 @@ var captionRules = [
108720
108810
  }
108721
108811
  }
108722
108812
  return findings;
108813
+ },
108814
+ // caption_overflow_clips_scaled_words
108815
+ ({ styles, scripts }) => {
108816
+ const findings = [];
108817
+ const hasScaledWords = scripts.some(
108818
+ (s) => /scale\s*:\s*1\.[2-9]/.test(s.content) && /caption|word|cg-/.test(s.content)
108819
+ );
108820
+ if (!hasScaledWords) return findings;
108821
+ for (const style of styles) {
108822
+ const captionBlocks = style.content.matchAll(
108823
+ /(\.caption[-_]?(?:group|container)|#caption[-_]?(?:layer|container))\s*\{([^}]+)\}/gi
108824
+ );
108825
+ for (const [, selector, body] of captionBlocks) {
108826
+ if (!body) continue;
108827
+ if (/overflow\s*:\s*hidden/i.test(body)) {
108828
+ findings.push({
108829
+ code: "caption_overflow_clips_scaled_words",
108830
+ severity: "warning",
108831
+ selector: (selector ?? "").trim(),
108832
+ message: `"${(selector ?? "").trim()}" has overflow: hidden but GSAP scales caption words above 1.0x. Scaled emphasis words and their glow effects will be clipped.`,
108833
+ fixHint: "Use overflow: visible on caption containers. Rely on fitTextFontSize with reduced maxWidth to prevent overflow instead."
108834
+ });
108835
+ }
108836
+ }
108837
+ }
108838
+ return findings;
108839
+ },
108840
+ // caption_textshadow_on_group_container
108841
+ ({ scripts, styles }) => {
108842
+ const findings = [];
108843
+ const isCaptionFile = styles.some((s) => /\.caption[-_]?(?:group|word)/i.test(s.content));
108844
+ if (!isCaptionFile) return findings;
108845
+ for (const script of scripts) {
108846
+ const groupShadowPattern = /\.to\s*\(\s*(?:div|groupEl|el|captionEl|document\.getElementById\s*\(\s*["']cg-)\s*[^,]*,\s*\{[^}]*textShadow/g;
108847
+ const selectorShadowPattern = /\.to\s*\(\s*["'](?:#cg-\d+|\.caption[-_]?group)["']\s*,\s*\{[^}]*textShadow/g;
108848
+ if (groupShadowPattern.test(script.content) || selectorShadowPattern.test(script.content)) {
108849
+ findings.push({
108850
+ code: "caption_textshadow_on_group_container",
108851
+ severity: "warning",
108852
+ message: "textShadow is tweened on a caption group container. When children have semi-transparent color (e.g., inactive karaoke words at rgba opacity), the glow renders as a visible rectangle behind the entire group.",
108853
+ fixHint: "Apply textShadow to individual active word elements instead of the group container. Use scale on the group for bass-reactive pulsing."
108854
+ });
108855
+ }
108856
+ }
108857
+ return findings;
108858
+ },
108859
+ // caption_fittext_scale_mismatch
108860
+ ({ scripts }) => {
108861
+ const findings = [];
108862
+ for (const script of scripts) {
108863
+ const content = script.content;
108864
+ const fitTextMatch = content.match(/fitTextFontSize\s*\([^)]*maxWidth\s*:\s*(\d+)/);
108865
+ if (!fitTextMatch) continue;
108866
+ const maxWidth = parseInt(fitTextMatch[1] ?? "0", 10);
108867
+ if (!maxWidth) continue;
108868
+ const scaleMatches = [...content.matchAll(/scale\s*:\s*(1\.\d+)/g)];
108869
+ const captionContext = /caption|word|cg-|karaoke/i.test(content);
108870
+ if (!captionContext || scaleMatches.length === 0) continue;
108871
+ let maxScale = 1;
108872
+ for (const m of scaleMatches) {
108873
+ const val = parseFloat(m[1] ?? "1");
108874
+ if (val > maxScale) maxScale = val;
108875
+ }
108876
+ const effectiveWidth = maxWidth * maxScale;
108877
+ if (effectiveWidth > 1760) {
108878
+ findings.push({
108879
+ code: "caption_fittext_scale_mismatch",
108880
+ severity: "warning",
108881
+ message: `fitTextFontSize uses maxWidth: ${maxWidth}px but emphasis words scale up to ${maxScale}x. Effective width ${Math.round(effectiveWidth)}px may overflow the composition (1920px minus margins).`,
108882
+ fixHint: `Reduce maxWidth to ${Math.floor(1700 / maxScale)}px to leave headroom for scaled emphasis words.`
108883
+ });
108884
+ }
108885
+ }
108886
+ return findings;
108723
108887
  }
108724
108888
  ];
108725
108889
 
@@ -109299,7 +109463,8 @@ async function initializeSession(session) {
109299
109463
  const type = msg.type();
109300
109464
  const text = msg.text();
109301
109465
  const isFontLoadError = type === "error" && text.startsWith("Failed to load resource") && /fonts\.googleapis|fonts\.gstatic|\.woff2?(\b|$)/i.test(text);
109302
- const prefix = type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
109466
+ const isResourceLoadError = type === "error" && text.startsWith("Failed to load resource") && !isFontLoadError;
109467
+ const prefix = isResourceLoadError ? "[non-blocking]" : type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
109303
109468
  if (!isFontLoadError) {
109304
109469
  console.log(`${prefix} ${text}`);
109305
109470
  }
@@ -111932,6 +112097,23 @@ function buildFontFaceCss(requestedFamilies) {
111932
112097
  unresolved: unresolved.sort()
111933
112098
  };
111934
112099
  }
112100
+ function warnUnresolvedFonts(unresolved) {
112101
+ const mapped = Object.entries(FONT_ALIASES).reduce((acc, [alias, canonical]) => {
112102
+ const display = alias === canonical ? alias : `${alias} \u2192 ${canonical}`;
112103
+ if (!acc.includes(display)) acc.push(display);
112104
+ return acc;
112105
+ }, []).sort();
112106
+ console.warn(
112107
+ `[Compiler] No deterministic font mapping for: ${unresolved.join(", ")}
112108
+ Mapped fonts: ${mapped.join(", ")}
112109
+ To fix, pick one:
112110
+ 1. Use a mapped font name instead (see list above)
112111
+ 2. Add a @font-face block in your HTML with a local or hosted font file
112112
+ 3. Install the font locally on the render machine (Docker: add to Dockerfile)
112113
+ 4. Add an alias to FONT_ALIASES in deterministicFonts.ts (for contributors)
112114
+ Docs: https://hyperframes.heygen.com/docs/fonts`
112115
+ );
112116
+ }
111935
112117
  function injectDeterministicFontFaces(html) {
111936
112118
  const existingFaces = extractExistingFontFaces(html);
111937
112119
  const requestedFamilies = extractRequestedFontFamilies(html);
@@ -111947,7 +112129,7 @@ function injectDeterministicFontFaces(html) {
111947
112129
  const { css, unresolved } = buildFontFaceCss(pendingFamilies);
111948
112130
  if (!css) {
111949
112131
  if (unresolved.length > 0) {
111950
- console.warn(`[Compiler] No deterministic font mapping for: ${unresolved.join(", ")}`);
112132
+ warnUnresolvedFonts(unresolved);
111951
112133
  }
111952
112134
  return html;
111953
112135
  }
@@ -111964,7 +112146,7 @@ function injectDeterministicFontFaces(html) {
111964
112146
  `[Compiler] Injected deterministic @font-face rules for ${pendingFamilies.size - unresolved.length} requested font families`
111965
112147
  );
111966
112148
  if (unresolved.length > 0) {
111967
- console.warn(`[Compiler] Unresolved font families left dynamic: ${unresolved.join(", ")}`);
112149
+ warnUnresolvedFonts(unresolved);
111968
112150
  }
111969
112151
  return document3.toString();
111970
112152
  }