hyperframes 0.8.22 → 0.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -50,7 +50,7 @@ var VERSION;
50
50
  var init_version = __esm({
51
51
  "src/version.ts"() {
52
52
  "use strict";
53
- VERSION = true ? "0.8.22" : "0.0.0-dev";
53
+ VERSION = true ? "0.8.23" : "0.0.0-dev";
54
54
  }
55
55
  });
56
56
 
@@ -84035,6 +84035,47 @@ ${other.raw}`)
84035
84035
  }
84036
84036
  return findings;
84037
84037
  },
84038
+ // gsap_timeline_return_used_as_tween — `tl.to()` returns the TIMELINE, not the tween it just
84039
+ // created. `gsap.to()` returns a Tween, so the two read identically and behave nothing alike.
84040
+ // Capturing the timeline's return and later treating it as an individual animation aims a
84041
+ // tween-scoped call at the whole composition: `.kill()` on it interrupts the master timeline
84042
+ // and detaches it from its parent, which stops a parent-driven seek dead while leaving an
84043
+ // explicit `tl.progress()` still working -- so a render of the packaged defaults looks fine
84044
+ // and only live playback breaks. A rebuild that collected these to discard them also leaves
84045
+ // every previous keyframe in place and stacks the new ones on top.
84046
+ ({ scripts }) => {
84047
+ const findings = [];
84048
+ for (const script of scripts) {
84049
+ const source = stripJsComments(script.content);
84050
+ const timelineVars = collectTimelineVarNames(source);
84051
+ if (timelineVars.length === 0) continue;
84052
+ const receivers = timelineVars.map(escapeRegExp22).join("|");
84053
+ const pattern = new RegExp(
84054
+ String.raw`(?:\.push\s*\(\s*|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)(?:${receivers})\s*\.\s*(to|from|fromTo|set|call|add)\s*\(`,
84055
+ "g"
84056
+ );
84057
+ let match;
84058
+ while ((match = pattern.exec(source)) !== null) {
84059
+ const boundName = match[1];
84060
+ const method = match[2];
84061
+ if (boundName) {
84062
+ const treatedAsTween = new RegExp(
84063
+ String.raw`\b${escapeRegExp22(boundName)}\s*\.\s*(?:kill|revert|invalidate|pause|resume|restart|seek|progress|timeScale)\s*\(`
84064
+ );
84065
+ if (!treatedAsTween.test(source)) continue;
84066
+ }
84067
+ const contextStart = Math.max(0, match.index - 40);
84068
+ findings.push({
84069
+ code: "gsap_timeline_return_used_as_tween",
84070
+ severity: "error",
84071
+ message: `\`${match[0].includes(".push") ? "push" : boundName}\` captures the return of \`.${method}()\` on timeline \`${timelineVars[0]}\`, but a timeline's \`.to()\`/\`.from()\`/\`.set()\` returns THE TIMELINE ITSELF, not the tween it created. Every captured value is the same master timeline, so a tween-scoped call on it \u2014 \`.kill()\` above all \u2014 hits the whole composition: it interrupts the timeline and detaches it from its parent, which freezes a parent-driven seek while an explicit \`tl.progress()\` keeps working. Only \`gsap.to()\` returns a tween.`,
84072
+ fixHint: "To build a group of keyframes you can later discard, put them in a nested timeline: `const nested = gsap.timeline(); nested.to(...); tl.add(nested, 0);` \u2014 then `nested.kill()` replaces exactly those keyframes and cannot reach `tl`. Children keep their absolute times when the nest is added at 0. To hold a single real tween, create it with `gsap.to(...)` and place it with `tl.add(tween, at)`.",
84073
+ snippet: truncateSnippet(source.slice(contextStart, match.index + match[0].length + 60))
84074
+ });
84075
+ }
84076
+ }
84077
+ return findings;
84078
+ },
84038
84079
  // gsap_group_selector_keyframes
84039
84080
  ({ scripts }) => {
84040
84081
  const findings = [];
@@ -149583,18 +149624,22 @@ function geometryIssueAnchor(candidate, time) {
149583
149624
  rect: candidate.rect
149584
149625
  };
149585
149626
  }
149586
- function captionCenterInZone(rect, zone, canvas) {
149587
- const cx = rect.left + rect.width / 2;
149588
- const cy = rect.top + rect.height / 2;
149589
- const inside = cx >= zone.x0 * canvas.width && cx <= zone.x1 * canvas.width && cy >= zone.y0 * canvas.height && cy <= zone.y1 * canvas.height;
149590
- return { inside, cy };
149627
+ function captionBoxOverlapsZone(box, zone, canvas) {
149628
+ const zx0 = zone.x0 * canvas.width;
149629
+ const zy0 = zone.y0 * canvas.height;
149630
+ const zx1 = zone.x1 * canvas.width;
149631
+ const zy1 = zone.y1 * canvas.height;
149632
+ const bx1 = box.left + box.width;
149633
+ const by1 = box.top + box.height;
149634
+ const overlaps = box.left < zx1 && bx1 > zx0 && box.top < zy1 && by1 > zy0;
149635
+ return { overlaps, cy: box.top + box.height / 2 };
149591
149636
  }
149592
149637
  function captionFinding(candidate, options, canvas, time) {
149593
149638
  const zone = options.captionZone;
149594
149639
  if (!zone || candidate.kind !== "text" || !candidateIsSized(candidate, canvas)) return null;
149595
149640
  if ("data-layout-allow-caption-zone" in candidate.dataAttributes) return null;
149596
- const { inside, cy } = captionCenterInZone(candidate.rect, zone, canvas);
149597
- if (!inside) return null;
149641
+ const { overlaps, cy } = captionBoxOverlapsZone(candidate.elementRect, zone, canvas);
149642
+ if (!overlaps) return null;
149598
149643
  const text2 = candidate.text.slice(0, 48);
149599
149644
  const pctFromBottom = Math.round((canvas.height - cy) / canvas.height * 100);
149600
149645
  return {
@@ -149604,7 +149649,7 @@ function captionFinding(candidate, options, canvas, time) {
149604
149649
  code: "caption_zone_collision",
149605
149650
  severity: zone.severity === "error" ? "error" : "warning",
149606
149651
  text: text2,
149607
- message: `<${candidate.tag}> "${text2}" is centred in the reserved caption band (~${pctFromBottom}% up from the bottom).`,
149652
+ message: `<${candidate.tag}> "${text2}" overlaps the reserved caption band (~${pctFromBottom}% up from the bottom).`,
149608
149653
  fixHint: "Keep main content outside the configured caption band, or mark intentional lower-third copy with data-layout-allow-caption-zone."
149609
149654
  }
149610
149655
  };
@@ -203174,18 +203219,25 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203174
203219
  }
203175
203220
  const openRouterKey = process.env.OPENROUTER_API_KEY;
203176
203221
  const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
203177
- if (!openRouterKey && !geminiKey) {
203222
+ const vertexProject = process.env.HYPERFRAMES_VERTEX_PROJECT_ID;
203223
+ const vertexServiceAccount = process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT;
203224
+ const useVertex = Boolean(vertexProject && vertexServiceAccount);
203225
+ if (!openRouterKey && !useVertex && !geminiKey) {
203178
203226
  reportOutcome();
203179
203227
  return geminiCaptions;
203180
203228
  }
203181
- const useOpenRouter = Boolean(openRouterKey);
203182
- const providerName = useOpenRouter ? "OpenRouter" : "Gemini";
203183
- const model = useOpenRouter ? process.env.HYPERFRAMES_OPENROUTER_MODEL || "google/gemini-3.1-flash-lite" : process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
203229
+ const provider = openRouterKey ? "openrouter" : useVertex ? "vertex" : "gemini";
203230
+ const providerName = { openrouter: "OpenRouter", vertex: "Vertex AI", gemini: "Gemini" }[provider];
203231
+ const model = {
203232
+ openrouter: process.env.HYPERFRAMES_OPENROUTER_MODEL || "google/gemini-3.1-flash-lite",
203233
+ vertex: process.env.HYPERFRAMES_VERTEX_MODEL || "gemini-2.5-flash",
203234
+ gemini: process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview"
203235
+ }[provider];
203184
203236
  const requestTimeoutMs = resolveVisionRequestTimeoutMs();
203185
203237
  progress("design", `Captioning images with ${providerName} vision...`);
203186
203238
  try {
203187
203239
  let captionOne;
203188
- if (openRouterKey) {
203240
+ if (provider === "openrouter") {
203189
203241
  captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
203190
203242
  return runBoundedVisionRequest(async (signal) => {
203191
203243
  const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
@@ -203221,9 +203273,31 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203221
203273
  }, timeoutMs);
203222
203274
  };
203223
203275
  } else {
203224
- if (!geminiKey) return geminiCaptions;
203225
203276
  const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
203226
- const ai = new GoogleGenAI2({ apiKey: geminiKey });
203277
+ let ai;
203278
+ if (provider === "vertex") {
203279
+ if (!vertexProject || !vertexServiceAccount) return geminiCaptions;
203280
+ let credentials;
203281
+ try {
203282
+ credentials = JSON.parse(vertexServiceAccount);
203283
+ } catch {
203284
+ warnings.push(
203285
+ "HYPERFRAMES_VERTEX_SERVICE_ACCOUNT is not valid JSON; skipped vision captioning."
203286
+ );
203287
+ internalError = true;
203288
+ reportOutcome();
203289
+ return geminiCaptions;
203290
+ }
203291
+ ai = new GoogleGenAI2({
203292
+ vertexai: true,
203293
+ project: vertexProject,
203294
+ location: process.env.HYPERFRAMES_VERTEX_LOCATION || "us-central1",
203295
+ googleAuthOptions: { credentials }
203296
+ });
203297
+ } else {
203298
+ if (!geminiKey) return geminiCaptions;
203299
+ ai = new GoogleGenAI2({ apiKey: geminiKey });
203300
+ }
203227
203301
  captionOne = async ({ mimeType, base64, prompt, maxTokens, timeoutMs }) => {
203228
203302
  const response = await runBoundedVisionRequest(
203229
203303
  (signal) => ai.models.generateContent({
@@ -203236,6 +203310,12 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203236
203310
  ],
203237
203311
  config: {
203238
203312
  maxOutputTokens: maxTokens,
203313
+ // A one-line factual caption needs no reasoning, and leaving thinking on is
203314
+ // not merely wasteful: thinking tokens are drawn from maxOutputTokens, so the
203315
+ // model can spend the whole budget and return empty text. That surfaces as a
203316
+ // successful request with no caption — the capture then logs "Captioned N/N
203317
+ // images" followed by "0 images captioned", which is what production shows.
203318
+ thinkingConfig: { thinkingBudget: 0 },
203239
203319
  abortSignal: signal,
203240
203320
  httpOptions: { timeout: timeoutMs }
203241
203321
  }
@@ -203327,6 +203407,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203327
203407
  reportOutcome();
203328
203408
  return geminiCaptions;
203329
203409
  }
203410
+ const hostConcurrency = sharp3.concurrency();
203330
203411
  progress("design", `Rasterizing + captioning ${svgFiles.length} SVGs via vision API...`);
203331
203412
  const SVG_BATCH = 20;
203332
203413
  const SVG_RENDER_SIZE = 256;
@@ -203339,10 +203420,11 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203339
203420
  }
203340
203421
  const timeoutMs = Math.max(1, Math.min(requestTimeoutMs, remainingMs));
203341
203422
  const batch = svgFiles.slice(i2, i2 + SVG_BATCH);
203342
- const results = await Promise.allSettled(
203343
- batch.map(async ({ relPath }) => {
203423
+ const rasterized = [];
203424
+ sharp3.concurrency(1);
203425
+ try {
203426
+ for (const { relPath } of batch) {
203344
203427
  const filePath = join121(assetsDir, relPath);
203345
- let pngBase64;
203346
203428
  try {
203347
203429
  const svgSource = readFileSync84(filePath, "utf-8");
203348
203430
  const lightFillHits = (svgSource.match(/fill\s*=\s*["'](#fff(fff)?|white|#[ef][ef][ef]|#[ef]{6})["']/gi) || []).length;
@@ -203354,11 +203436,18 @@ async function captionImagesWithGemini(outputDir, progress, warnings, options =
203354
203436
  fit: "inside",
203355
203437
  withoutEnlargement: false
203356
203438
  }).flatten({ background: bg }).png().toBuffer();
203357
- pngBase64 = pngBuffer.toString("base64");
203439
+ rasterized.push({ relPath, pngBase64: pngBuffer.toString("base64") });
203358
203440
  } catch {
203359
203441
  svgsSkipped++;
203360
- return { file: relPath, caption: "" };
203442
+ rasterized.push({ relPath, pngBase64: null });
203361
203443
  }
203444
+ }
203445
+ } finally {
203446
+ sharp3.concurrency(hostConcurrency);
203447
+ }
203448
+ const results = await Promise.allSettled(
203449
+ rasterized.map(async ({ relPath, pngBase64 }) => {
203450
+ if (pngBase64 === null) return { file: relPath, caption: "" };
203362
203451
  const caption = await captionOne({
203363
203452
  mimeType: "image/png",
203364
203453
  base64: pngBase64,
@@ -204810,8 +204899,8 @@ ${extracted.bodyHtml}
204810
204899
  try {
204811
204900
  const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
204812
204901
  if (lines.length > 0) {
204813
- const hasVisionKey = !!(!skipVision && (process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY));
204814
- const header = hasVisionKey ? "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\nTo find a specific brand or icon, **grep this file for the brand name in the description text** (e.g. `grep -i 'autodesk' asset-descriptions.md`). The Gemini Vision captions identify what's actually in each file \u2014 that's the agent's selector.\n\nThe `logo-<hash>.svg` filename prefix is a cheap structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). It is NOT a content claim \u2014 many `logo-*` files are nav icons or decorative shapes. Trust the captions, not the filename prefix.\n\n" : "# Asset Descriptions\n\n\u26A0\uFE0F GEMINI_API_KEY not set \u2014 descriptions below are catalog-derived (alt text, headings, section context, filename) instead of Vision-generated. To get richer Vision descriptions on the next capture, set GEMINI_API_KEY (or GOOGLE_API_KEY) and re-run.\n\nThe `logo-<hash>.svg` filename prefix is a structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). To pick the actual brand logo without Vision, open the `logo-*` candidates in a previewer or rasterize them with `sharp` before referencing \u2014 composing a fake logo ships off-brand in the final video.\n\n";
204902
+ const hasVisionKey = !!(!skipVision && (process.env.OPENROUTER_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || process.env.HYPERFRAMES_VERTEX_PROJECT_ID && process.env.HYPERFRAMES_VERTEX_SERVICE_ACCOUNT));
204903
+ const header = hasVisionKey ? "# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\nTo find a specific brand or icon, **grep this file for the brand name in the description text** (e.g. `grep -i 'autodesk' asset-descriptions.md`). The Gemini Vision captions identify what's actually in each file \u2014 that's the agent's selector.\n\nThe `logo-<hash>.svg` filename prefix is a cheap structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). It is NOT a content claim \u2014 many `logo-*` files are nav icons or decorative shapes. Trust the captions, not the filename prefix.\n\n" : "# Asset Descriptions\n\n\u26A0\uFE0F No vision credentials \u2014 descriptions below are catalog-derived (alt text, headings, section context, filename) instead of Vision-generated. To get richer Vision descriptions on the next capture, set GEMINI_API_KEY (or GOOGLE_API_KEY), or HYPERFRAMES_VERTEX_PROJECT_ID plus HYPERFRAMES_VERTEX_SERVICE_ACCOUNT for Vertex service-account auth, and re-run.\n\nThe `logo-<hash>.svg` filename prefix is a structural hint (DOM said this SVG was inside a `<header>`, home-link `<a>`, or had an aria-label matching the page brand). To pick the actual brand logo without Vision, open the `logo-*` candidates in a previewer or rasterize them with `sharp` before referencing \u2014 composing a fake logo ships off-brand in the final video.\n\n";
204815
204904
  writeFileSync56(
204816
204905
  join126(outputDir, "extracted", "asset-descriptions.md"),
204817
204906
  header + lines.map((l) => "- " + l).join("\n") + "\n",
@@ -72753,6 +72753,47 @@ ${other.raw}`)
72753
72753
  }
72754
72754
  return findings;
72755
72755
  },
72756
+ // gsap_timeline_return_used_as_tween — `tl.to()` returns the TIMELINE, not the tween it just
72757
+ // created. `gsap.to()` returns a Tween, so the two read identically and behave nothing alike.
72758
+ // Capturing the timeline's return and later treating it as an individual animation aims a
72759
+ // tween-scoped call at the whole composition: `.kill()` on it interrupts the master timeline
72760
+ // and detaches it from its parent, which stops a parent-driven seek dead while leaving an
72761
+ // explicit `tl.progress()` still working -- so a render of the packaged defaults looks fine
72762
+ // and only live playback breaks. A rebuild that collected these to discard them also leaves
72763
+ // every previous keyframe in place and stacks the new ones on top.
72764
+ ({ scripts }) => {
72765
+ const findings = [];
72766
+ for (const script of scripts) {
72767
+ const source = stripJsComments(script.content);
72768
+ const timelineVars = collectTimelineVarNames(source);
72769
+ if (timelineVars.length === 0) continue;
72770
+ const receivers = timelineVars.map(escapeRegExp22).join("|");
72771
+ const pattern = new RegExp(
72772
+ String.raw`(?:\.push\s*\(\s*|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)(?:${receivers})\s*\.\s*(to|from|fromTo|set|call|add)\s*\(`,
72773
+ "g"
72774
+ );
72775
+ let match;
72776
+ while ((match = pattern.exec(source)) !== null) {
72777
+ const boundName = match[1];
72778
+ const method = match[2];
72779
+ if (boundName) {
72780
+ const treatedAsTween = new RegExp(
72781
+ String.raw`\b${escapeRegExp22(boundName)}\s*\.\s*(?:kill|revert|invalidate|pause|resume|restart|seek|progress|timeScale)\s*\(`
72782
+ );
72783
+ if (!treatedAsTween.test(source)) continue;
72784
+ }
72785
+ const contextStart = Math.max(0, match.index - 40);
72786
+ findings.push({
72787
+ code: "gsap_timeline_return_used_as_tween",
72788
+ severity: "error",
72789
+ message: `\`${match[0].includes(".push") ? "push" : boundName}\` captures the return of \`.${method}()\` on timeline \`${timelineVars[0]}\`, but a timeline's \`.to()\`/\`.from()\`/\`.set()\` returns THE TIMELINE ITSELF, not the tween it created. Every captured value is the same master timeline, so a tween-scoped call on it \u2014 \`.kill()\` above all \u2014 hits the whole composition: it interrupts the timeline and detaches it from its parent, which freezes a parent-driven seek while an explicit \`tl.progress()\` keeps working. Only \`gsap.to()\` returns a tween.`,
72790
+ fixHint: "To build a group of keyframes you can later discard, put them in a nested timeline: `const nested = gsap.timeline(); nested.to(...); tl.add(nested, 0);` \u2014 then `nested.kill()` replaces exactly those keyframes and cannot reach `tl`. Children keep their absolute times when the nest is added at 0. To hold a single real tween, create it with `gsap.to(...)` and place it with `tl.add(tween, at)`.",
72791
+ snippet: truncateSnippet(source.slice(contextStart, match.index + match[0].length + 60))
72792
+ });
72793
+ }
72794
+ }
72795
+ return findings;
72796
+ },
72756
72797
  // gsap_group_selector_keyframes
72757
72798
  ({ scripts }) => {
72758
72799
  const findings = [];
@@ -97707,8 +97748,8 @@ async function runFontLocalize(io, localize) {
97707
97748
  }
97708
97749
 
97709
97750
  // src/version.ts
97710
- var VERSION = true ? "0.8.22" : "0.0.0-dev";
97711
- var PRODUCER_VERSION = true ? "0.8.22" : "0.0.0-dev";
97751
+ var VERSION = true ? "0.8.23" : "0.0.0-dev";
97752
+ var PRODUCER_VERSION = true ? "0.8.23" : "0.0.0-dev";
97712
97753
 
97713
97754
  // src/fontLocalizeCli.ts
97714
97755
  async function readStdin() {
@@ -1,4 +1,4 @@
1
- "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.22/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
1
+ "use strict";var HyperframesPlayer=(()=>{var ee=Object.defineProperty;var et=Object.getOwnPropertyDescriptor;var tt=Object.getOwnPropertyNames;var it=Object.prototype.hasOwnProperty;var rt=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},nt=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of tt(e))!it.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(i=et(e,n))||i.enumerable});return r};var ot=r=>nt(ee({},"__esModule",{value:!0}),r);var Ht={};rt(Ht,{HyperframesPlayer:()=>K,SPEED_PRESETS:()=>re,formatSpeed:()=>O,formatTime:()=>q});function we(r){return r.hasRuntime||r.runtimeInjected?!1:!!(r.hasNestedCompositions||r.hasTimelines&&r.attempts>=5)}function P(r){return typeof r=="object"&&r!==null}function Ae(r){return P(r)&&typeof r.getDuration=="function"}function Ce(r){return P(r)&&typeof r.duration=="function"&&typeof r.time=="function"&&typeof r.seek=="function"&&typeof r.play=="function"&&typeof r.pause=="function"}var k="https://cdn.jsdelivr.net/npm/@hyperframes/core@0.8.23/dist/hyperframe.runtime.iife.js";function N(r){if(r===null)return null;let e=Number.parseInt(r,10);return Number.isFinite(e)&&e>0?e:null}function at(r){let e=r?.querySelector("[data-composition-id][data-width][data-height]")??r?.querySelector("[data-width][data-height]");if(!e)return null;let t=N(e.getAttribute("data-width")),i=N(e.getAttribute("data-height"));return t!==null&&i!==null?{width:t,height:i}:null}var W=class{constructor(e,t){this._iframe=e;this._callbacks=t}_iframe;_callbacks;_interval=null;_runtimeInjected=!1;get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let e=0;this._interval=setInterval(()=>{e++;try{let t=this._iframe.contentWindow;if(!t)return;let i=!!(t.__hf||t.__player),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!this._iframe.contentDocument?.querySelector("[data-composition-src]");if(we({hasRuntime:i,hasTimelines:n,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:e})){this._injectRuntime();return}if(this._runtimeInjected&&!i)return;let d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();let s=at(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:s});return}}catch{}e>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{let e=this._iframe.contentWindow;return e?this._resolveDirectTimelineAdapterFromWindow(e):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(e){return this._resolveDirectTimelineAdapterFromWindow(e)}hasRuntimeBridge(e){return Reflect.get(e,"__hf")!==void 0||P(Reflect.get(e,"__player"))}_injectRuntime(){this._runtimeInjected=!0;try{let e=this._iframe.contentDocument;if(!e)return;let t=e.createElement("script");t.src=k,(e.head||e.documentElement).appendChild(t),this._callbacks.onRuntimeInjected?.()}catch{}}_resolveDirectTimelineAdapterFromWindow(e){if(this.hasRuntimeBridge(e))return null;let t=Reflect.get(e,"__timelines");if(!P(t))return null;let i=Object.keys(t);if(i.length===0)return null;let n=this._iframe.contentDocument?.querySelector("[data-composition-id]")?.getAttribute("data-composition-id"),o=n&&n in t?n:i[i.length-1],d=t[o];return Ce(d)?d:null}_resolvePlaybackDurationAdapter(e){let t=Reflect.get(e,"__player");if(Ae(t))return{kind:"runtime",getDuration:()=>t.getDuration()};let i=this._resolveDirectTimelineAdapterFromWindow(e);return i?{kind:"direct-timeline",timeline:i,getDuration:()=>i.duration()}:null}};var Re=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -67,7 +67,7 @@ npx hyperframes check --caption-zone "x0=0;y0=.82;x1=1;y1=1;severity=error;seek=
67
67
  npx hyperframes check --frame-check # media (img/svg/video/canvas) out-of-frame detection
68
68
  ```
69
69
 
70
- `--caption-zone` takes fractional band geometry (`x0/y0/x1/y1` required, 0-1 fractions of the composition's own canvas, portrait included) with optional `severity` and comma-separated `seek` fractions; it flags content whose center sits inside the band. Waive intentional lower-third copy with `data-layout-allow-caption-zone` on the element or its nearest wrapper (see Escape hatches). `--frame-check` reports media elements breaching the canvas beyond `max(120px, 6% of the min canvas dimension)`.
70
+ `--caption-zone` takes fractional band geometry (`x0/y0/x1/y1` required, 0-1 fractions of the composition's own canvas, portrait included) with optional `severity` and comma-separated `seek` fractions; it flags a text element's DOM box (`getBoundingClientRect`) that overlaps the band. Waive intentional lower-third copy with `data-layout-allow-caption-zone` on the element or its nearest wrapper (see Escape hatches). `--frame-check` reports media elements breaching the canvas beyond `max(120px, 6% of the min canvas dimension)`.
71
71
 
72
72
  **Fixing contrast errors** — thresholds are 4.5:1 for normal text, 3:1 for large text (24px+, or 19px+ bold). The finding's `suggestedColor` already picks the nearest compliant color in the right direction (brighten on dark backgrounds, darken on light); apply it or adjust within the palette family, then re-run `check`.
73
73
 
@@ -1,4 +1,4 @@
1
- var ve=Object.defineProperty;var be=(n,i,e)=>i in n?ve(n,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[i]=e;var u=(n,i,e)=>be(n,typeof i!="symbol"?i+"":i,e);import{r as le,i as we,a as Ae}from"./index-BIf0zQNt.js";function Ee(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function V(n){return typeof n=="object"&&n!==null}function Ce(n){return V(n)&&typeof n.getDuration=="function"}function Te(n){return V(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}function Se(n){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(n))throw new Error(`Invalid HyperFrames runtime version: ${n}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${n}/dist/hyperframe.runtime.iife.js`}const H=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:Se("0.0.0-dev");function $(n){if(n===null)return null;const i=Number.parseInt(n,10);return Number.isFinite(i)&&i>0?i:null}function Re(n){const i=(n==null?void 0:n.querySelector("[data-composition-id][data-width][data-height]"))??(n==null?void 0:n.querySelector("[data-width][data-height]"));if(!i)return null;const e=$(i.getAttribute("data-width")),t=$(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class xe{constructor(i,e){u(this,"_interval",null);u(this,"_runtimeInjected",!1);this._iframe=i,this._callbacks=e}get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let i=0;this._interval=setInterval(()=>{var e;i++;try{const t=this._iframe.contentWindow;if(!t)return;const r=!!(t.__hf||t.__player),s=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(Ee({hasRuntime:r,hasTimelines:s,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:i})){this._injectRuntime();return}if(this._runtimeInjected&&!r)return;const d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();const p=Re(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:p});return}}catch{}i>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{const i=this._iframe.contentWindow;return i?this._resolveDirectTimelineAdapterFromWindow(i):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(i){return this._resolveDirectTimelineAdapterFromWindow(i)}hasRuntimeBridge(i){return Reflect.get(i,"__hf")!==void 0||V(Reflect.get(i,"__player"))}_injectRuntime(){var i,e;this._runtimeInjected=!0;try{const t=this._iframe.contentDocument;if(!t)return;const r=t.createElement("script");r.src=H,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,p;if(this.hasRuntimeBridge(i))return null;const e=Reflect.get(i,"__timelines");if(!V(e))return null;const t=Object.keys(e);if(t.length===0)return null;const r=(p=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:p.getAttribute("data-composition-id"),s=r&&r in e?r:t[t.length-1],o=e[s];return Te(o)?o:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(Ce(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Me=`
1
+ var ve=Object.defineProperty;var be=(n,i,e)=>i in n?ve(n,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[i]=e;var u=(n,i,e)=>be(n,typeof i!="symbol"?i+"":i,e);import{r as le,i as we,a as Ae}from"./index-Tiu74FPg.js";function Ee(n){return n.hasRuntime||n.runtimeInjected?!1:!!(n.hasNestedCompositions||n.hasTimelines&&n.attempts>=5)}function V(n){return typeof n=="object"&&n!==null}function Ce(n){return V(n)&&typeof n.getDuration=="function"}function Te(n){return V(n)&&typeof n.duration=="function"&&typeof n.time=="function"&&typeof n.seek=="function"&&typeof n.play=="function"&&typeof n.pause=="function"}function Se(n){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(n))throw new Error(`Invalid HyperFrames runtime version: ${n}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${n}/dist/hyperframe.runtime.iife.js`}const H=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:Se("0.0.0-dev");function $(n){if(n===null)return null;const i=Number.parseInt(n,10);return Number.isFinite(i)&&i>0?i:null}function Re(n){const i=(n==null?void 0:n.querySelector("[data-composition-id][data-width][data-height]"))??(n==null?void 0:n.querySelector("[data-width][data-height]"));if(!i)return null;const e=$(i.getAttribute("data-width")),t=$(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class xe{constructor(i,e){u(this,"_interval",null);u(this,"_runtimeInjected",!1);this._iframe=i,this._callbacks=e}get runtimeInjected(){return this._runtimeInjected}start(){this.stop(),this._runtimeInjected=!1;let i=0;this._interval=setInterval(()=>{var e;i++;try{const t=this._iframe.contentWindow;if(!t)return;const r=!!(t.__hf||t.__player),s=!!(t.__timelines&&Object.keys(t.__timelines).length>0),o=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(Ee({hasRuntime:r,hasTimelines:s,hasNestedCompositions:o,runtimeInjected:this._runtimeInjected,attempts:i})){this._injectRuntime();return}if(this._runtimeInjected&&!r)return;const d=this._resolvePlaybackDurationAdapter(t);if(d&&d.getDuration()>0){this.stop();const p=Re(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:p});return}}catch{}i>=40&&(this.stop(),this._callbacks.onError("Composition timeline not found after 8s"))},200)}stop(){this._interval!==null&&(clearInterval(this._interval),this._interval=null)}resolveDirectTimelineAdapter(){try{const i=this._iframe.contentWindow;return i?this._resolveDirectTimelineAdapterFromWindow(i):null}catch{return null}}resolveDirectTimelineAdapterFromWindow(i){return this._resolveDirectTimelineAdapterFromWindow(i)}hasRuntimeBridge(i){return Reflect.get(i,"__hf")!==void 0||V(Reflect.get(i,"__player"))}_injectRuntime(){var i,e;this._runtimeInjected=!0;try{const t=this._iframe.contentDocument;if(!t)return;const r=t.createElement("script");r.src=H,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,p;if(this.hasRuntimeBridge(i))return null;const e=Reflect.get(i,"__timelines");if(!V(e))return null;const t=Object.keys(e);if(t.length===0)return null;const r=(p=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:p.getAttribute("data-composition-id"),s=r&&r in e?r:t[t.length-1],o=e[s];return Te(o)?o:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(Ce(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Me=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;
@@ -1 +1 @@
1
- import{g as P}from"./index-BIf0zQNt.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
1
+ import{g as P}from"./index-Tiu74FPg.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};