@hyperframes/producer 0.2.2 → 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.
package/dist/index.js CHANGED
@@ -105663,6 +105663,34 @@ function getSingleClassSelector(selector) {
105663
105663
  const match2 = selector.trim().match(/^\.(?<name>[A-Za-z0-9_-]+)$/);
105664
105664
  return match2?.groups?.name || null;
105665
105665
  }
105666
+ function cssTransformToGsapProps(cssTransform) {
105667
+ const parts = [];
105668
+ const translateMatch = cssTransform.match(
105669
+ /translate\(\s*(-?[\d.]+)(%|px)?\s*,\s*(-?[\d.]+)(%|px)?\s*\)/
105670
+ );
105671
+ if (translateMatch) {
105672
+ const [, xVal, xUnit, yVal, yUnit] = translateMatch;
105673
+ if (xUnit === "%") parts.push(`xPercent: ${xVal}`);
105674
+ else parts.push(`x: ${xVal}`);
105675
+ if (yUnit === "%") parts.push(`yPercent: ${yVal}`);
105676
+ else parts.push(`y: ${yVal}`);
105677
+ }
105678
+ const txMatch = cssTransform.match(/translateX\(\s*(-?[\d.]+)(%|px)?\s*\)/);
105679
+ if (txMatch) {
105680
+ const [, val, unit] = txMatch;
105681
+ parts.push(unit === "%" ? `xPercent: ${val}` : `x: ${val}`);
105682
+ }
105683
+ const tyMatch = cssTransform.match(/translateY\(\s*(-?[\d.]+)(%|px)?\s*\)/);
105684
+ if (tyMatch) {
105685
+ const [, val, unit] = tyMatch;
105686
+ parts.push(unit === "%" ? `yPercent: ${val}` : `y: ${val}`);
105687
+ }
105688
+ const scaleMatch = cssTransform.match(/scale\(\s*([\d.]+)\s*\)/);
105689
+ if (scaleMatch) {
105690
+ parts.push(`scale: ${scaleMatch[1]}`);
105691
+ }
105692
+ return parts.length > 0 ? parts.join(", ") : null;
105693
+ }
105666
105694
  var gsapRules = [
105667
105695
  // overlapping_gsap_tweens + gsap_animates_clip_element + unscoped_gsap_selector
105668
105696
  ({ tags, scripts, rootCompositionId }) => {
@@ -105720,14 +105748,18 @@ ${right2.raw}`)
105720
105748
  const sel = win.targetSelector;
105721
105749
  const clipInfo = clipIds.get(sel) || clipClasses.get(sel);
105722
105750
  if (!clipInfo) continue;
105751
+ const conflictingProps = win.properties.filter(
105752
+ (p) => p === "visibility" || p === "display"
105753
+ );
105754
+ if (conflictingProps.length === 0) continue;
105723
105755
  const elDesc = `<${clipInfo.tag}${clipInfo.id ? ` id="${clipInfo.id}"` : ""} class="${clipInfo.classes}">`;
105724
105756
  findings.push({
105725
105757
  code: "gsap_animates_clip_element",
105726
105758
  severity: "error",
105727
- message: `GSAP animation targets a clip element. Selector "${sel}" resolves to element ${elDesc}. The framework manages clip visibility \u2014 animate an inner wrapper instead.`,
105759
+ 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.`,
105728
105760
  selector: sel,
105729
105761
  elementId: clipInfo.id || void 0,
105730
- fixHint: "Wrap content in a child <div> and target that with GSAP.",
105762
+ fixHint: "Remove the visibility/display tween, or move the content into a child <div> and target that instead.",
105731
105763
  snippet: truncateSnippet(win.raw)
105732
105764
  });
105733
105765
  }
@@ -105792,12 +105824,14 @@ ${right2.raw}`)
105792
105824
  }
105793
105825
  for (const [sel, { cssTransform, props, raw: raw2 }] of conflicts) {
105794
105826
  const propList = [...props].join("/");
105827
+ const gsapEquivalent = cssTransformToGsapProps(cssTransform);
105828
+ 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.`;
105795
105829
  findings.push({
105796
105830
  code: "gsap_css_transform_conflict",
105797
105831
  severity: "warning",
105798
105832
  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.`,
105799
105833
  selector: sel,
105800
- 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.`,
105834
+ fixHint,
105801
105835
  snippet: truncateSnippet(raw2)
105802
105836
  });
105803
105837
  }
@@ -105824,6 +105858,62 @@ ${right2.raw}`)
105824
105858
  fixHint: 'Add <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script> before your animation script.'
105825
105859
  }
105826
105860
  ];
105861
+ },
105862
+ // audio_reactive_single_tween_per_group
105863
+ ({ scripts, styles }) => {
105864
+ const findings = [];
105865
+ const isCaptionFile = styles.some((s) => /\.caption[-_]?(?:group|word)/i.test(s.content));
105866
+ if (!isCaptionFile) return findings;
105867
+ for (const script of scripts) {
105868
+ const content = script.content;
105869
+ const hasAudioData = /AUDIO|audio[-_]?data|bands\[/.test(content);
105870
+ if (!hasAudioData) continue;
105871
+ const hasCaptionLoop = /forEach/.test(content) && /caption|group|cg-/.test(content);
105872
+ if (!hasCaptionLoop) continue;
105873
+ 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);
105874
+ if (!hasInnerSamplingLoop) {
105875
+ const hasPeakTween = /peak(?:Bass|Treble|Energy)/.test(content) && /group\.start/.test(content);
105876
+ if (hasPeakTween) {
105877
+ findings.push({
105878
+ code: "audio_reactive_single_tween_per_group",
105879
+ severity: "warning",
105880
+ 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.",
105881
+ 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."
105882
+ });
105883
+ }
105884
+ }
105885
+ }
105886
+ return findings;
105887
+ },
105888
+ // scene_layer_missing_visibility_kill
105889
+ ({ scripts, tags }) => {
105890
+ const findings = [];
105891
+ const sceneElements = tags.filter((t) => {
105892
+ const id = readAttr(t.raw, "id") || "";
105893
+ return /^scene\d+$/i.test(id);
105894
+ });
105895
+ if (sceneElements.length < 2) return findings;
105896
+ for (const script of scripts) {
105897
+ const content = script.content;
105898
+ for (const tag of sceneElements) {
105899
+ const id = readAttr(tag.raw, "id") || "";
105900
+ const exitPattern = new RegExp(`["']#${id}["'][^)]*opacity\\s*:\\s*0`);
105901
+ const hasExit = exitPattern.test(content);
105902
+ if (!hasExit) continue;
105903
+ const killPattern = new RegExp(`["']#${id}["'][^)]*visibility\\s*:\\s*["']hidden["']`);
105904
+ const hasKill = killPattern.test(content);
105905
+ if (!hasKill) {
105906
+ findings.push({
105907
+ code: "scene_layer_missing_visibility_kill",
105908
+ severity: "warning",
105909
+ elementId: id,
105910
+ 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.`,
105911
+ fixHint: `Add \`tl.set("#${id}", { visibility: "hidden" }, <exit-end-time>)\` after the scene's exit tweens.`
105912
+ });
105913
+ }
105914
+ }
105915
+ }
105916
+ return findings;
105827
105917
  }
105828
105918
  ];
105829
105919
 
@@ -105931,6 +106021,80 @@ var captionRules = [
105931
106021
  }
105932
106022
  }
105933
106023
  return findings;
106024
+ },
106025
+ // caption_overflow_clips_scaled_words
106026
+ ({ styles, scripts }) => {
106027
+ const findings = [];
106028
+ const hasScaledWords = scripts.some(
106029
+ (s) => /scale\s*:\s*1\.[2-9]/.test(s.content) && /caption|word|cg-/.test(s.content)
106030
+ );
106031
+ if (!hasScaledWords) return findings;
106032
+ for (const style of styles) {
106033
+ const captionBlocks = style.content.matchAll(
106034
+ /(\.caption[-_]?(?:group|container)|#caption[-_]?(?:layer|container))\s*\{([^}]+)\}/gi
106035
+ );
106036
+ for (const [, selector, body] of captionBlocks) {
106037
+ if (!body) continue;
106038
+ if (/overflow\s*:\s*hidden/i.test(body)) {
106039
+ findings.push({
106040
+ code: "caption_overflow_clips_scaled_words",
106041
+ severity: "warning",
106042
+ selector: (selector ?? "").trim(),
106043
+ message: `"${(selector ?? "").trim()}" has overflow: hidden but GSAP scales caption words above 1.0x. Scaled emphasis words and their glow effects will be clipped.`,
106044
+ fixHint: "Use overflow: visible on caption containers. Rely on fitTextFontSize with reduced maxWidth to prevent overflow instead."
106045
+ });
106046
+ }
106047
+ }
106048
+ }
106049
+ return findings;
106050
+ },
106051
+ // caption_textshadow_on_group_container
106052
+ ({ scripts, styles }) => {
106053
+ const findings = [];
106054
+ const isCaptionFile = styles.some((s) => /\.caption[-_]?(?:group|word)/i.test(s.content));
106055
+ if (!isCaptionFile) return findings;
106056
+ for (const script of scripts) {
106057
+ const groupShadowPattern = /\.to\s*\(\s*(?:div|groupEl|el|captionEl|document\.getElementById\s*\(\s*["']cg-)\s*[^,]*,\s*\{[^}]*textShadow/g;
106058
+ const selectorShadowPattern = /\.to\s*\(\s*["'](?:#cg-\d+|\.caption[-_]?group)["']\s*,\s*\{[^}]*textShadow/g;
106059
+ if (groupShadowPattern.test(script.content) || selectorShadowPattern.test(script.content)) {
106060
+ findings.push({
106061
+ code: "caption_textshadow_on_group_container",
106062
+ severity: "warning",
106063
+ 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.",
106064
+ fixHint: "Apply textShadow to individual active word elements instead of the group container. Use scale on the group for bass-reactive pulsing."
106065
+ });
106066
+ }
106067
+ }
106068
+ return findings;
106069
+ },
106070
+ // caption_fittext_scale_mismatch
106071
+ ({ scripts }) => {
106072
+ const findings = [];
106073
+ for (const script of scripts) {
106074
+ const content = script.content;
106075
+ const fitTextMatch = content.match(/fitTextFontSize\s*\([^)]*maxWidth\s*:\s*(\d+)/);
106076
+ if (!fitTextMatch) continue;
106077
+ const maxWidth = parseInt(fitTextMatch[1] ?? "0", 10);
106078
+ if (!maxWidth) continue;
106079
+ const scaleMatches = [...content.matchAll(/scale\s*:\s*(1\.\d+)/g)];
106080
+ const captionContext = /caption|word|cg-|karaoke/i.test(content);
106081
+ if (!captionContext || scaleMatches.length === 0) continue;
106082
+ let maxScale = 1;
106083
+ for (const m of scaleMatches) {
106084
+ const val = parseFloat(m[1] ?? "1");
106085
+ if (val > maxScale) maxScale = val;
106086
+ }
106087
+ const effectiveWidth = maxWidth * maxScale;
106088
+ if (effectiveWidth > 1760) {
106089
+ findings.push({
106090
+ code: "caption_fittext_scale_mismatch",
106091
+ severity: "warning",
106092
+ 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).`,
106093
+ fixHint: `Reduce maxWidth to ${Math.floor(1700 / maxScale)}px to leave headroom for scaled emphasis words.`
106094
+ });
106095
+ }
106096
+ }
106097
+ return findings;
105934
106098
  }
105935
106099
  ];
105936
106100
 
@@ -106510,7 +106674,8 @@ async function initializeSession(session) {
106510
106674
  const type = msg.type();
106511
106675
  const text = msg.text();
106512
106676
  const isFontLoadError = type === "error" && text.startsWith("Failed to load resource") && /fonts\.googleapis|fonts\.gstatic|\.woff2?(\b|$)/i.test(text);
106513
- const prefix = type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
106677
+ const isResourceLoadError = type === "error" && text.startsWith("Failed to load resource") && !isFontLoadError;
106678
+ const prefix = isResourceLoadError ? "[non-blocking]" : type === "error" ? "[Browser:ERROR]" : type === "warn" ? "[Browser:WARN]" : "[Browser]";
106514
106679
  if (!isFontLoadError) {
106515
106680
  console.log(`${prefix} ${text}`);
106516
106681
  }
@@ -111767,6 +111932,23 @@ function buildFontFaceCss(requestedFamilies) {
111767
111932
  unresolved: unresolved.sort()
111768
111933
  };
111769
111934
  }
111935
+ function warnUnresolvedFonts(unresolved) {
111936
+ const mapped = Object.entries(FONT_ALIASES).reduce((acc, [alias, canonical]) => {
111937
+ const display = alias === canonical ? alias : `${alias} \u2192 ${canonical}`;
111938
+ if (!acc.includes(display)) acc.push(display);
111939
+ return acc;
111940
+ }, []).sort();
111941
+ console.warn(
111942
+ `[Compiler] No deterministic font mapping for: ${unresolved.join(", ")}
111943
+ Mapped fonts: ${mapped.join(", ")}
111944
+ To fix, pick one:
111945
+ 1. Use a mapped font name instead (see list above)
111946
+ 2. Add a @font-face block in your HTML with a local or hosted font file
111947
+ 3. Install the font locally on the render machine (Docker: add to Dockerfile)
111948
+ 4. Add an alias to FONT_ALIASES in deterministicFonts.ts (for contributors)
111949
+ Docs: https://hyperframes.heygen.com/docs/fonts`
111950
+ );
111951
+ }
111770
111952
  function injectDeterministicFontFaces(html) {
111771
111953
  const existingFaces = extractExistingFontFaces(html);
111772
111954
  const requestedFamilies = extractRequestedFontFamilies(html);
@@ -111782,7 +111964,7 @@ function injectDeterministicFontFaces(html) {
111782
111964
  const { css, unresolved } = buildFontFaceCss(pendingFamilies);
111783
111965
  if (!css) {
111784
111966
  if (unresolved.length > 0) {
111785
- console.warn(`[Compiler] No deterministic font mapping for: ${unresolved.join(", ")}`);
111967
+ warnUnresolvedFonts(unresolved);
111786
111968
  }
111787
111969
  return html;
111788
111970
  }
@@ -111799,7 +111981,7 @@ function injectDeterministicFontFaces(html) {
111799
111981
  `[Compiler] Injected deterministic @font-face rules for ${pendingFamilies.size - unresolved.length} requested font families`
111800
111982
  );
111801
111983
  if (unresolved.length > 0) {
111802
- console.warn(`[Compiler] Unresolved font families left dynamic: ${unresolved.join(", ")}`);
111984
+ warnUnresolvedFonts(unresolved);
111803
111985
  }
111804
111986
  return document3.toString();
111805
111987
  }