mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
feat(core): add fitTextFontSize utility for pixel-accurate text measurement (#152)
Add @chenglou/pretext dependency and fitTextFontSize() utility that uses canvas measureText to compute the largest font size that fits text within a given width. Replaces character-count heuristics with actual font-aware measurement. - New fitTextFontSize() in @hyperframes/core/text, exposed on window.__hyperframes - Generalized for all text elements (captions, titles, etc.), not just captions - Unit tests (mocked pretext) + browser integration test (real Chromium canvas) - Updated captions skill docs with usage, exit guarantee, and self-lint patterns Co-authored-by: James <james.russo@heygen.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
James
Claude Opus 4.6
parent
dac304ed9f
commit
ef6225da1d
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@hyperframes/cli",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.13",
|
||||
"bin": {
|
||||
"hyperframes": "./dist/cli.js",
|
||||
},
|
||||
@@ -53,11 +53,15 @@
|
||||
"tsup": "^8.0.0",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^3.2.4",
|
||||
},
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@hyperframes/core",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.13",
|
||||
"dependencies": {
|
||||
"@chenglou/pretext": "^0.0.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^28.0.0",
|
||||
"@types/node": "^24.10.13",
|
||||
@@ -80,7 +84,7 @@
|
||||
},
|
||||
"packages/engine": {
|
||||
"name": "@hyperframes/engine",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.13",
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.13.0",
|
||||
"@hyperframes/core": "workspace:^",
|
||||
@@ -97,7 +101,7 @@
|
||||
},
|
||||
"packages/producer": {
|
||||
"name": "@hyperframes/producer",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.13",
|
||||
"dependencies": {
|
||||
"@fontsource/archivo-black": "^5.2.8",
|
||||
"@fontsource/eb-garamond": "^5.2.7",
|
||||
@@ -127,7 +131,7 @@
|
||||
},
|
||||
"packages/studio": {
|
||||
"name": "@hyperframes/studio",
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.13",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.1",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
@@ -219,6 +223,8 @@
|
||||
|
||||
"@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "3.2.1" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
|
||||
|
||||
"@chenglou/pretext": ["@chenglou/pretext@0.0.3", "", {}, "sha512-RQmqMqUAPRCyv4R3LlRi/ao6KbNWYclqLA+V1HS7sWgyUUbjn3JmmlfXZSY/BjM4rbmIaMSyIVisYocYGYftiQ=="],
|
||||
|
||||
"@clack/core": ["@clack/core@1.1.0", "", { "dependencies": { "sisteransi": "1.0.5" } }, "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA=="],
|
||||
|
||||
"@clack/prompts": ["@clack/prompts@1.1.0", "", { "dependencies": { "@clack/core": "1.1.0", "sisteransi": "1.0.5" } }, "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g=="],
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
"./studio-api": {
|
||||
"import": "./src/studio-api/index.ts",
|
||||
"types": "./src/studio-api/index.ts"
|
||||
},
|
||||
"./text": {
|
||||
"import": "./src/text/index.ts",
|
||||
"types": "./src/text/index.ts"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
@@ -53,6 +57,10 @@
|
||||
"./studio-api": {
|
||||
"import": "./dist/studio-api/index.js",
|
||||
"types": "./dist/studio-api/index.d.ts"
|
||||
},
|
||||
"./text": {
|
||||
"import": "./dist/text/index.js",
|
||||
"types": "./dist/text/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
@@ -80,6 +88,9 @@
|
||||
"debug:timeline": "tsx scripts/debug-timeline.ts",
|
||||
"prepublishOnly": "echo skip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chenglou/pretext": "^0.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsdom": "^28.0.0",
|
||||
"@types/node": "^24.10.13",
|
||||
|
||||
@@ -18,6 +18,8 @@ const requiredSnippets = [
|
||||
"hf-preview",
|
||||
"hf-parent",
|
||||
"renderSeek",
|
||||
"__hyperframes",
|
||||
"fitTextFontSize",
|
||||
];
|
||||
|
||||
for (const snippet of requiredSnippets) {
|
||||
|
||||
@@ -154,3 +154,7 @@ export type {
|
||||
export type { FrameAdapter, FrameAdapterContext } from "./adapters/types";
|
||||
export type { GSAPTimelineLike, CreateGSAPFrameAdapterOptions } from "./adapters/gsap";
|
||||
export { createGSAPFrameAdapter } from "./adapters/gsap";
|
||||
|
||||
// Text measurement
|
||||
export { fitTextFontSize } from "./text/index.js";
|
||||
export type { FitTextOptions, FitTextResult } from "./text/index.js";
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
import { initSandboxRuntimeModular } from "./init";
|
||||
import { fitTextFontSize } from "../text/fitTextFontSize";
|
||||
|
||||
type HyperframeWindow = Window & {
|
||||
__hyperframeRuntimeBootstrapped?: boolean;
|
||||
__hyperframes?: {
|
||||
fitTextFontSize: typeof fitTextFontSize;
|
||||
};
|
||||
};
|
||||
|
||||
// Inline composition scripts can run before DOMContentLoaded.
|
||||
// Ensure timeline registry exists at script evaluation time.
|
||||
(window as HyperframeWindow).__timelines = (window as HyperframeWindow).__timelines || {};
|
||||
|
||||
// Expose text utilities immediately so composition scripts can use them
|
||||
// before DOMContentLoaded (font sizing runs during script evaluation).
|
||||
(window as HyperframeWindow).__hyperframes = {
|
||||
fitTextFontSize,
|
||||
};
|
||||
|
||||
function bootstrapHyperframeRuntime(): void {
|
||||
const win = window as HyperframeWindow;
|
||||
if (win.__hyperframeRuntimeBootstrapped) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
// Mock @chenglou/pretext since jsdom lacks real canvas measureText accuracy.
|
||||
vi.mock("@chenglou/pretext", () => ({
|
||||
prepare: vi.fn((_text: string, _font: string) => ({ __mock: true, font: _font })),
|
||||
layout: vi.fn(),
|
||||
}));
|
||||
|
||||
import { fitTextFontSize } from "./fitTextFontSize.js";
|
||||
import { prepare, layout } from "@chenglou/pretext";
|
||||
|
||||
const mockLayout = vi.mocked(layout);
|
||||
const mockPrepare = vi.mocked(prepare);
|
||||
|
||||
describe("fitTextFontSize", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("returns base font size when text fits at base size", () => {
|
||||
mockLayout.mockReturnValue({ height: 90, lineCount: 1 });
|
||||
const result = fitTextFontSize("short text");
|
||||
expect(result).toEqual({ fontSize: 78, fits: true });
|
||||
expect(mockPrepare).toHaveBeenCalledTimes(1);
|
||||
expect(mockLayout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("shrinks font size when text wraps at base size", () => {
|
||||
mockLayout
|
||||
.mockReturnValueOnce({ height: 180, lineCount: 2 })
|
||||
.mockReturnValueOnce({ height: 180, lineCount: 2 })
|
||||
.mockReturnValueOnce({ height: 90, lineCount: 1 });
|
||||
const result = fitTextFontSize("this is a much wider piece of text");
|
||||
expect(result).toEqual({ fontSize: 74, fits: true });
|
||||
expect(mockPrepare).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("returns minFontSize with fits: false when text never fits", () => {
|
||||
mockLayout.mockReturnValue({ height: 180, lineCount: 2 });
|
||||
const result = fitTextFontSize("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW");
|
||||
expect(result).toEqual({ fontSize: 42, fits: false });
|
||||
expect(mockPrepare).toHaveBeenCalledTimes(19); // (78 - 42) / 2 + 1
|
||||
});
|
||||
|
||||
it("respects custom options", () => {
|
||||
mockLayout.mockReturnValue({ height: 60, lineCount: 1 });
|
||||
const result = fitTextFontSize("hello", {
|
||||
baseFontSize: 60,
|
||||
minFontSize: 30,
|
||||
fontWeight: 700,
|
||||
fontFamily: "Inter",
|
||||
maxWidth: 800,
|
||||
step: 4,
|
||||
});
|
||||
expect(result).toEqual({ fontSize: 60, fits: true });
|
||||
expect(mockPrepare).toHaveBeenCalledWith("hello", "700 60px Inter");
|
||||
expect(mockLayout).toHaveBeenCalledWith(expect.anything(), 800, 72);
|
||||
});
|
||||
|
||||
it("passes correct font string to prepare for each size step", () => {
|
||||
mockLayout
|
||||
.mockReturnValueOnce({ height: 180, lineCount: 2 })
|
||||
.mockReturnValueOnce({ height: 90, lineCount: 1 });
|
||||
fitTextFontSize("test", {
|
||||
baseFontSize: 80,
|
||||
step: 10,
|
||||
fontWeight: 900,
|
||||
fontFamily: "Outfit",
|
||||
});
|
||||
expect(mockPrepare).toHaveBeenNthCalledWith(1, "test", "900 80px Outfit");
|
||||
expect(mockPrepare).toHaveBeenNthCalledWith(2, "test", "900 70px Outfit");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { prepare, layout } from "@chenglou/pretext";
|
||||
|
||||
export type FitTextOptions = {
|
||||
/** Container width in px */
|
||||
maxWidth: number;
|
||||
/** Starting font size in px */
|
||||
baseFontSize: number;
|
||||
/** Floor font size in px */
|
||||
minFontSize: number;
|
||||
/** CSS font-weight */
|
||||
fontWeight: number;
|
||||
/** CSS font-family */
|
||||
fontFamily: string;
|
||||
/** Decrement step in px */
|
||||
step: number;
|
||||
};
|
||||
|
||||
export type FitTextResult = {
|
||||
/** The computed font size that fits */
|
||||
fontSize: number;
|
||||
/** True if text fits at >= minFontSize */
|
||||
fits: boolean;
|
||||
};
|
||||
|
||||
const DEFAULTS: FitTextOptions = {
|
||||
maxWidth: 1600,
|
||||
baseFontSize: 78,
|
||||
minFontSize: 42,
|
||||
fontWeight: 900,
|
||||
fontFamily: "Outfit",
|
||||
step: 2,
|
||||
};
|
||||
|
||||
export function fitTextFontSize(text: string, options?: Partial<FitTextOptions>): FitTextResult {
|
||||
const opts = { ...DEFAULTS, ...options };
|
||||
const lineHeightRatio = 1.2;
|
||||
|
||||
for (let size = opts.baseFontSize; size >= opts.minFontSize; size -= opts.step) {
|
||||
const font = `${opts.fontWeight} ${size}px ${opts.fontFamily}`;
|
||||
const prepared = prepare(text, font);
|
||||
const { lineCount } = layout(prepared, opts.maxWidth, size * lineHeightRatio);
|
||||
if (lineCount <= 1) {
|
||||
return { fontSize: size, fits: true };
|
||||
}
|
||||
}
|
||||
|
||||
return { fontSize: opts.minFontSize, fits: false };
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { fitTextFontSize } from "./fitTextFontSize.js";
|
||||
export type { FitTextOptions, FitTextResult } from "./fitTextFontSize.js";
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Browser integration test for fitTextFontSize.
|
||||
*
|
||||
* Launches headless Chrome, loads the runtime IIFE into a page,
|
||||
* and verifies that window.__hyperframes.fitTextFontSize produces
|
||||
* correct results with real canvas measureText.
|
||||
*
|
||||
* Requires: puppeteer (dep of @hyperframes/engine)
|
||||
* Run: cd packages/engine && npx tsx scripts/test-fitTextFontSize-browser.ts
|
||||
*/
|
||||
|
||||
import { buildHyperframesRuntimeScript } from "../../core/src/inline-scripts/hyperframesRuntime.engine";
|
||||
|
||||
function assert(condition: unknown, message: string): void {
|
||||
if (!condition) {
|
||||
throw new Error(`FAIL: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Dynamic import — puppeteer is a monorepo dep, not a core dep
|
||||
let puppeteer;
|
||||
try {
|
||||
puppeteer = (await import("puppeteer")).default;
|
||||
} catch {
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
event: "fitTextFontSize_browser_test_skipped",
|
||||
reason: "puppeteer not available",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const runtimeSource = buildHyperframesRuntimeScript({ minify: false });
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html><head>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@900&display=block');
|
||||
</style>
|
||||
</head><body>
|
||||
<script>${runtimeSource}</script>
|
||||
<script>
|
||||
window.__testResults = {};
|
||||
|
||||
// Test 1: Short text should fit at base size
|
||||
var r1 = window.__hyperframes.fitTextFontSize("HI");
|
||||
window.__testResults.shortText = r1;
|
||||
|
||||
// Test 2: Wide text should shrink below base size but still fit at 1600px
|
||||
var r2 = window.__hyperframes.fitTextFontSize(
|
||||
"CONGRATULATIONS TO EVERYBODY IN THE WORLD",
|
||||
{ fontFamily: "sans-serif", fontWeight: 900, maxWidth: 1600 }
|
||||
);
|
||||
window.__testResults.wideText = r2;
|
||||
|
||||
// Test 3: Extremely wide text that can't fit should return minFontSize
|
||||
var r3 = window.__hyperframes.fitTextFontSize(
|
||||
"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW",
|
||||
{ fontFamily: "sans-serif", fontWeight: 900, maxWidth: 400 }
|
||||
);
|
||||
window.__testResults.extremeText = r3;
|
||||
|
||||
// Test 4: Function exists and is callable
|
||||
window.__testResults.exists = typeof window.__hyperframes.fitTextFontSize === "function";
|
||||
</script>
|
||||
</body></html>`;
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
await page.setContent(html, { waitUntil: "networkidle0", timeout: 10000 });
|
||||
|
||||
// Wait for font to potentially load (best effort — sans-serif fallback is fine for testing)
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
|
||||
const results = await page.evaluate(() => (window as any).__testResults);
|
||||
|
||||
// Test 1: Short text fits at base size (78px default)
|
||||
assert(results.exists === true, "fitTextFontSize should exist on window.__hyperframes");
|
||||
assert(
|
||||
results.shortText.fits === true,
|
||||
`Short text should fit, got fits=${results.shortText.fits}`,
|
||||
);
|
||||
assert(
|
||||
results.shortText.fontSize === 78,
|
||||
`Short text should use base size 78, got ${results.shortText.fontSize}`,
|
||||
);
|
||||
|
||||
// Test 2: Wide text should shrink below base size
|
||||
assert(
|
||||
results.wideText.fits === true,
|
||||
`Wide text should still fit, got fits=${results.wideText.fits}`,
|
||||
);
|
||||
assert(
|
||||
results.wideText.fontSize < 78,
|
||||
`Wide text should shrink below 78, got ${results.wideText.fontSize}`,
|
||||
);
|
||||
|
||||
// Test 3: Extreme text should hit floor
|
||||
assert(
|
||||
results.extremeText.fontSize === 42,
|
||||
`Extreme text should hit minFontSize 42, got ${results.extremeText.fontSize}`,
|
||||
);
|
||||
assert(
|
||||
results.extremeText.fits === false,
|
||||
`Extreme text should not fit, got fits=${results.extremeText.fits}`,
|
||||
);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
event: "fitTextFontSize_browser_test_passed",
|
||||
shortText: results.shortText,
|
||||
wideText: results.wideText,
|
||||
extremeText: results.extremeText,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Generated
+422
-17
@@ -40,7 +40,41 @@ importers:
|
||||
version: 5.9.3
|
||||
|
||||
packages/cli:
|
||||
dependencies:
|
||||
'@hono/node-server':
|
||||
specifier: ^1.8.0
|
||||
version: 1.19.11(hono@4.12.9)
|
||||
'@puppeteer/browsers':
|
||||
specifier: ^2.13.0
|
||||
version: 2.13.0
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
cheerio:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
citty:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1
|
||||
esbuild:
|
||||
specifier: ^0.25.0
|
||||
version: 0.25.12
|
||||
hono:
|
||||
specifier: ^4.0.0
|
||||
version: 4.12.9
|
||||
mime-types:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
open:
|
||||
specifier: ^10.0.0
|
||||
version: 10.2.0
|
||||
puppeteer-core:
|
||||
specifier: ^24.39.1
|
||||
version: 24.40.0
|
||||
devDependencies:
|
||||
'@clack/prompts':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
'@hyperframes/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
@@ -50,14 +84,39 @@ importers:
|
||||
'@hyperframes/producer':
|
||||
specifier: workspace:*
|
||||
version: link:../producer
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.8
|
||||
'@types/mime-types':
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 25.5.0
|
||||
version: 22.19.15
|
||||
linkedom:
|
||||
specifier: ^0.18.12
|
||||
version: 0.18.12
|
||||
picocolors:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
tsup:
|
||||
specifier: ^8.0.0
|
||||
version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
tsx:
|
||||
specifier: ^4.0.0
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@types/node@25.5.0)(jsdom@29.0.1)
|
||||
version: 3.2.4(@types/node@22.19.15)(jsdom@29.0.1)
|
||||
|
||||
packages/core:
|
||||
dependencies:
|
||||
'@chenglou/pretext':
|
||||
specifier: ^0.0.3
|
||||
version: 0.0.3
|
||||
devDependencies:
|
||||
'@types/jsdom':
|
||||
specifier: ^28.0.0
|
||||
@@ -230,15 +289,15 @@ importers:
|
||||
codemirror:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.2
|
||||
motion:
|
||||
specifier: ^12.38.0
|
||||
version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react:
|
||||
specifier: ^18.0.0 || ^19.0.0
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^18.0.0 || ^19.0.0
|
||||
version: 19.2.4(react@19.2.4)
|
||||
zustand:
|
||||
specifier: ^4.0.0 || ^5.0.0
|
||||
version: 5.0.12(@types/react@19.2.14)(react@19.2.4)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.0.0
|
||||
@@ -255,6 +314,9 @@ importers:
|
||||
postcss:
|
||||
specifier: ^8.4.0
|
||||
version: 8.5.8
|
||||
puppeteer-core:
|
||||
specifier: ^24.40.0
|
||||
version: 24.40.0
|
||||
tailwindcss:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
@@ -264,6 +326,12 @@ importers:
|
||||
vite:
|
||||
specifier: ^5.0.0
|
||||
version: 5.4.21(@types/node@25.5.0)
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@types/node@25.5.0)(jsdom@29.0.1)
|
||||
zustand:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.12(@types/react@19.2.14)(react@19.2.4)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -381,6 +449,15 @@ packages:
|
||||
resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
|
||||
hasBin: true
|
||||
|
||||
'@chenglou/pretext@0.0.3':
|
||||
resolution: {integrity: sha512-RQmqMqUAPRCyv4R3LlRi/ao6KbNWYclqLA+V1HS7sWgyUUbjn3JmmlfXZSY/BjM4rbmIaMSyIVisYocYGYftiQ==}
|
||||
|
||||
'@clack/core@1.1.0':
|
||||
resolution: {integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==}
|
||||
|
||||
'@clack/prompts@1.1.0':
|
||||
resolution: {integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==}
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
|
||||
|
||||
@@ -1706,6 +1783,9 @@ packages:
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/adm-zip@0.5.8':
|
||||
resolution: {integrity: sha512-RVVH7QvZYbN+ihqZ4kX/dMiowf6o+Jk1fNwiSdx0NahBJLU787zkULhGhJM8mf/obmLGmgdMM0bXsQTmyfbR7Q==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -1730,6 +1810,9 @@ packages:
|
||||
'@types/jsdom@28.0.1':
|
||||
resolution: {integrity: sha512-GJq2QE4TAZ5ajSoCasn5DOFm8u1mI3tIFvM5tIq3W5U/RTB6gsHwc6Yhpl91X9VSDOUVblgXmG+2+sSvFQrdlw==}
|
||||
|
||||
'@types/mime-types@3.0.1':
|
||||
resolution: {integrity: sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==}
|
||||
|
||||
'@types/node@22.19.15':
|
||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||
|
||||
@@ -1797,6 +1880,15 @@ packages:
|
||||
'@vitest/utils@3.2.4':
|
||||
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adm-zip@0.5.16:
|
||||
resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -1948,6 +2040,16 @@ packages:
|
||||
buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
esbuild: '>=0.18'
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1986,11 +2088,18 @@ packages:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
chromium-bidi@14.0.0:
|
||||
resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==}
|
||||
peerDependencies:
|
||||
devtools-protocol: '*'
|
||||
|
||||
citty@0.2.1:
|
||||
resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2017,6 +2126,13 @@ packages:
|
||||
engines: {node: ^14.13.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
consola@3.4.2:
|
||||
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
|
||||
conventional-changelog-angular@8.3.0:
|
||||
resolution: {integrity: sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2107,6 +2223,18 @@ packages:
|
||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
default-browser-id@5.0.1:
|
||||
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
default-browser@5.5.0:
|
||||
resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
define-lazy-prop@3.0.0:
|
||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
degenerator@5.0.1:
|
||||
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -2264,6 +2392,9 @@ packages:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2276,6 +2407,20 @@ packages:
|
||||
fraction.js@5.3.4:
|
||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||
|
||||
framer-motion@12.38.0:
|
||||
resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -2388,6 +2533,11 @@ packages:
|
||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2400,6 +2550,11 @@ packages:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||
engines: {node: '>=14.16'}
|
||||
hasBin: true
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
@@ -2415,6 +2570,10 @@ packages:
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
is-wsl@3.1.1:
|
||||
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
@@ -2445,6 +2604,10 @@ packages:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
@@ -2558,6 +2721,10 @@ packages:
|
||||
canvas:
|
||||
optional: true
|
||||
|
||||
load-tsconfig@0.2.5:
|
||||
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
lodash.camelcase@4.3.0:
|
||||
resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
|
||||
|
||||
@@ -2621,6 +2788,14 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mime-db@1.54.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
minimatch@10.2.4:
|
||||
resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -2639,6 +2814,29 @@ packages:
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
mlly@1.8.2:
|
||||
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||
|
||||
motion-dom@12.38.0:
|
||||
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
|
||||
|
||||
motion-utils@12.36.0:
|
||||
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
|
||||
|
||||
motion@12.38.0:
|
||||
resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -2675,6 +2873,10 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
open@10.2.0:
|
||||
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
oxc-parser@0.120.0:
|
||||
resolution: {integrity: sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -2768,6 +2970,9 @@ packages:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
postcss-import@15.1.0:
|
||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2865,6 +3070,10 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2898,6 +3107,10 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
run-applescript@7.1.0:
|
||||
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
@@ -2942,6 +3155,9 @@ packages:
|
||||
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
@@ -2966,6 +3182,10 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
spawn-command@0.0.2:
|
||||
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
|
||||
|
||||
@@ -3111,6 +3331,25 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsup@8.5.1:
|
||||
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7.36.0
|
||||
'@swc/core': ^1
|
||||
postcss: ^8.4.12
|
||||
typescript: '>=4.5.0'
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
'@swc/core':
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
tsx@4.21.0:
|
||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -3124,6 +3363,9 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
uhyphen@0.2.0:
|
||||
resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==}
|
||||
|
||||
@@ -3288,6 +3530,10 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
wsl-utils@0.1.0:
|
||||
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
xml-name-validator@5.0.0:
|
||||
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3489,6 +3735,17 @@ snapshots:
|
||||
dependencies:
|
||||
css-tree: 3.2.1
|
||||
|
||||
'@chenglou/pretext@0.0.3': {}
|
||||
|
||||
'@clack/core@1.1.0':
|
||||
dependencies:
|
||||
sisteransi: 1.0.5
|
||||
|
||||
'@clack/prompts@1.1.0':
|
||||
dependencies:
|
||||
'@clack/core': 1.1.0
|
||||
sisteransi: 1.0.5
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
@@ -4419,6 +4676,10 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/adm-zip@0.5.8':
|
||||
dependencies:
|
||||
'@types/node': 22.19.15
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.2
|
||||
@@ -4456,6 +4717,8 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
undici-types: 7.24.6
|
||||
|
||||
'@types/mime-types@3.0.1': {}
|
||||
|
||||
'@types/node@22.19.15':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -4572,6 +4835,10 @@ snapshots:
|
||||
loupe: 3.2.1
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
adm-zip@0.5.16: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv@8.18.0:
|
||||
@@ -4697,6 +4964,15 @@ snapshots:
|
||||
|
||||
buffer-crc32@0.2.13: {}
|
||||
|
||||
bundle-name@4.1.0:
|
||||
dependencies:
|
||||
run-applescript: 7.1.0
|
||||
|
||||
bundle-require@5.1.0(esbuild@0.27.4):
|
||||
dependencies:
|
||||
esbuild: 0.27.4
|
||||
load-tsconfig: 0.2.5
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
callsites@3.1.0: {}
|
||||
@@ -4728,7 +5004,6 @@ snapshots:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
optional: true
|
||||
|
||||
cheerio@1.2.0:
|
||||
dependencies:
|
||||
@@ -4743,7 +5018,6 @@ snapshots:
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 7.24.6
|
||||
whatwg-mimetype: 4.0.0
|
||||
optional: true
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
@@ -4757,12 +5031,18 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
chromium-bidi@14.0.0(devtools-protocol@0.0.1581282):
|
||||
dependencies:
|
||||
devtools-protocol: 0.0.1581282
|
||||
mitt: 3.0.1
|
||||
zod: 3.25.76
|
||||
|
||||
citty@0.2.1: {}
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@@ -4804,6 +5084,10 @@ snapshots:
|
||||
tree-kill: 1.2.2
|
||||
yargs: 17.7.2
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
conventional-changelog-angular@8.3.0:
|
||||
dependencies:
|
||||
compare-func: 2.0.0
|
||||
@@ -4885,6 +5169,15 @@ snapshots:
|
||||
|
||||
deep-eql@5.0.2: {}
|
||||
|
||||
default-browser-id@5.0.1: {}
|
||||
|
||||
default-browser@5.5.0:
|
||||
dependencies:
|
||||
bundle-name: 4.1.0
|
||||
default-browser-id: 5.0.1
|
||||
|
||||
define-lazy-prop@3.0.0: {}
|
||||
|
||||
degenerator@5.0.1:
|
||||
dependencies:
|
||||
ast-types: 0.13.4
|
||||
@@ -4931,7 +5224,6 @@ snapshots:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
whatwg-encoding: 3.1.1
|
||||
optional: true
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
dependencies:
|
||||
@@ -5005,7 +5297,6 @@ snapshots:
|
||||
'@esbuild/win32-arm64': 0.25.12
|
||||
'@esbuild/win32-ia32': 0.25.12
|
||||
'@esbuild/win32-x64': 0.25.12
|
||||
optional: true
|
||||
|
||||
esbuild@0.27.4:
|
||||
optionalDependencies:
|
||||
@@ -5108,6 +5399,12 @@ snapshots:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
mlly: 1.8.2
|
||||
rollup: 4.60.0
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
@@ -5119,6 +5416,15 @@ snapshots:
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
|
||||
framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
motion-dom: 12.38.0
|
||||
motion-utils: 12.36.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -5215,7 +5521,6 @@ snapshots:
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
optional: true
|
||||
|
||||
import-fresh@3.3.1:
|
||||
dependencies:
|
||||
@@ -5238,6 +5543,8 @@ snapshots:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
@@ -5246,6 +5553,10 @@ snapshots:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-obj@2.0.0: {}
|
||||
@@ -5254,6 +5565,10 @@ snapshots:
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
is-wsl@3.1.1:
|
||||
dependencies:
|
||||
is-inside-container: 1.0.0
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
@@ -5287,6 +5602,8 @@ snapshots:
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
@@ -5404,6 +5721,8 @@ snapshots:
|
||||
htmlparser2: 10.1.0
|
||||
uhyphen: 0.2.0
|
||||
|
||||
load-tsconfig@0.2.5: {}
|
||||
|
||||
lodash.camelcase@4.3.0: {}
|
||||
|
||||
lodash.kebabcase@4.1.1: {}
|
||||
@@ -5455,6 +5774,12 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.2
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
minimatch@10.2.4:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
@@ -5469,6 +5794,27 @@ snapshots:
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
mlly@1.8.2:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
pathe: 2.0.3
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.6.3
|
||||
|
||||
motion-dom@12.38.0:
|
||||
dependencies:
|
||||
motion-utils: 12.36.0
|
||||
|
||||
motion-utils@12.36.0: {}
|
||||
|
||||
motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
framer-motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mz@2.7.0:
|
||||
@@ -5497,6 +5843,13 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
open@10.2.0:
|
||||
dependencies:
|
||||
default-browser: 5.5.0
|
||||
define-lazy-prop: 3.0.0
|
||||
is-inside-container: 1.0.0
|
||||
wsl-utils: 0.1.0
|
||||
|
||||
oxc-parser@0.120.0:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.120.0
|
||||
@@ -5626,12 +5979,10 @@ snapshots:
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
parse5: 7.3.0
|
||||
optional: true
|
||||
|
||||
parse5-parser-stream@7.1.2:
|
||||
dependencies:
|
||||
parse5: 7.3.0
|
||||
optional: true
|
||||
|
||||
parse5@7.3.0:
|
||||
dependencies:
|
||||
@@ -5666,6 +6017,12 @@ snapshots:
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
dependencies:
|
||||
confbox: 0.1.8
|
||||
mlly: 1.8.2
|
||||
pathe: 2.0.3
|
||||
|
||||
postcss-import@15.1.0(postcss@8.5.8):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
@@ -5687,6 +6044,15 @@ snapshots:
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
optionalDependencies:
|
||||
jiti: 2.6.1
|
||||
postcss: 8.5.8
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
postcss-nested@6.2.0(postcss@8.5.8):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
@@ -5782,6 +6148,8 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.2
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
@@ -5831,6 +6199,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.60.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
run-applescript@7.1.0: {}
|
||||
|
||||
run-parallel@1.2.0:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
@@ -5839,8 +6209,7 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
optional: true
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
saxes@6.0.0:
|
||||
dependencies:
|
||||
@@ -5864,6 +6233,8 @@ snapshots:
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
smol-toml@1.6.1: {}
|
||||
@@ -5886,6 +6257,8 @@ snapshots:
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
spawn-command@0.0.2: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
@@ -6072,6 +6445,34 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.27.4)
|
||||
cac: 6.7.14
|
||||
chokidar: 4.0.3
|
||||
consola: 3.4.2
|
||||
debug: 4.4.3
|
||||
esbuild: 0.27.4
|
||||
fix-dts-default-cjs-exports: 1.0.1
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.3)
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.60.0
|
||||
source-map: 0.7.6
|
||||
sucrase: 3.35.1
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
postcss: 8.5.8
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- supports-color
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
tsx@4.21.0:
|
||||
dependencies:
|
||||
esbuild: 0.27.4
|
||||
@@ -6083,6 +6484,8 @@ snapshots:
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
uhyphen@0.2.0: {}
|
||||
|
||||
unbash@2.2.0: {}
|
||||
@@ -6318,10 +6721,8 @@ snapshots:
|
||||
whatwg-encoding@3.1.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
optional: true
|
||||
|
||||
whatwg-mimetype@4.0.0:
|
||||
optional: true
|
||||
whatwg-mimetype@4.0.0: {}
|
||||
|
||||
whatwg-mimetype@5.0.0: {}
|
||||
|
||||
@@ -6358,6 +6759,10 @@ snapshots:
|
||||
|
||||
ws@8.20.0: {}
|
||||
|
||||
wsl-utils@0.1.0:
|
||||
dependencies:
|
||||
is-wsl: 3.1.1
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
||||
xmlchars@2.2.0: {}
|
||||
|
||||
@@ -205,17 +205,47 @@ Break groups on sentence boundaries (`.` `?` `!`), pauses (>150ms gap), or max w
|
||||
|
||||
## Text Overflow Prevention
|
||||
|
||||
Captions must never clip off-screen. Apply these rules:
|
||||
Use `window.__hyperframes.fitTextFontSize()` to measure actual rendered text width and compute the correct font size. This replaces character-count heuristics with pixel-accurate measurement powered by [pretext](https://github.com/chenglou/pretext).
|
||||
|
||||
- Set `max-width: 1600px` (landscape) or `max-width: 900px` (portrait) on caption container
|
||||
- Add `overflow: hidden` as a safety net
|
||||
- **Auto-scale font size** based on character count:
|
||||
- ≤18 chars → full size (e.g., 78px)
|
||||
- 19–25 chars → reduce ~15% (e.g., 68px)
|
||||
- 26+ chars → reduce ~25% (e.g., 58px)
|
||||
- Reduce `letter-spacing` for long text (switch from `-0.02em` to `-0.04em`)
|
||||
- Give the caption container an explicit `height` (e.g., `200px`) — don't rely on content sizing with absolute children
|
||||
- Use `position: absolute` on all caption elements — `position: relative` causes overflow
|
||||
**Usage in composition scripts:**
|
||||
|
||||
```js
|
||||
GROUPS.forEach(function (group, gi) {
|
||||
// Measure with text-transform applied (captions typically uppercase)
|
||||
var result = window.__hyperframes.fitTextFontSize(group.text.toUpperCase(), {
|
||||
fontFamily: "Outfit",
|
||||
fontWeight: 900,
|
||||
maxWidth: 1600,
|
||||
});
|
||||
|
||||
// Apply computed font size to all word spans
|
||||
wordEls.forEach(function (el) {
|
||||
el.style.fontSize = result.fontSize + "px";
|
||||
});
|
||||
|
||||
// If result.fits is false, text exceeds minFontSize — overflow: hidden catches it
|
||||
});
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Default | Description |
|
||||
| -------------- | ---------- | ---------------------------------------------------- |
|
||||
| `maxWidth` | `1600` | Container width in px (1600 landscape, 900 portrait) |
|
||||
| `baseFontSize` | `78` | Starting font size — used when text fits |
|
||||
| `minFontSize` | `42` | Floor — never shrink below this |
|
||||
| `fontWeight` | `900` | Must match the CSS font-weight |
|
||||
| `fontFamily` | `"Outfit"` | Must match the CSS font-family |
|
||||
| `step` | `2` | Decrement step in px per iteration |
|
||||
|
||||
**Important:** The `fontWeight` and `fontFamily` options must match the CSS applied to the text elements exactly, or measurements will be inaccurate.
|
||||
|
||||
**Safety nets (still required in CSS):**
|
||||
|
||||
- `max-width: 1600px` (landscape) or `max-width: 900px` (portrait) on caption container
|
||||
- `overflow: hidden` as a fallback for `fits: false` edge cases
|
||||
- `position: absolute` on all caption elements
|
||||
- Explicit `height` on caption container (e.g., `200px`)
|
||||
|
||||
## Caption Exit Guarantee
|
||||
|
||||
@@ -239,6 +269,32 @@ tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end);
|
||||
|
||||
The `tl.set` at `group.end` is a deterministic kill — it fires at an exact time, doesn't animate, and can't be overridden by other tweens at different times.
|
||||
|
||||
**Self-lint rule:** After building the timeline, verify every caption group has a hard kill. Run this check before registering the timeline:
|
||||
|
||||
```js
|
||||
// Caption lint: verify every group has a hard kill
|
||||
GROUPS.forEach(function (group, gi) {
|
||||
var el = document.getElementById("cg-" + gi);
|
||||
if (!el) return;
|
||||
tl.seek(group.end + 0.01);
|
||||
var computed = window.getComputedStyle(el);
|
||||
if (computed.opacity !== "0" && computed.visibility !== "hidden") {
|
||||
console.warn(
|
||||
"[caption-lint] group " +
|
||||
gi +
|
||||
" ('" +
|
||||
group.text +
|
||||
"') still visible at t=" +
|
||||
(group.end + 0.01).toFixed(2) +
|
||||
"s",
|
||||
);
|
||||
}
|
||||
});
|
||||
tl.seek(0); // reset after lint
|
||||
```
|
||||
|
||||
Place this **before** `window.__timelines[id] = tl` so it runs at composition init. Warnings appear in the browser console during `hyperframes dev`.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Deterministic.** No `Math.random()`, no `Date.now()`.
|
||||
|
||||
@@ -133,6 +133,7 @@ Video must be `muted playsinline`. Audio is always a separate `<audio>` element:
|
||||
- Use `font-display: block` for local fonts — renderer needs fonts loaded before capturing
|
||||
- Add `crossorigin="anonymous"` to media loaded from external URLs
|
||||
- Minimum readable text: 20px landscape, 18px portrait
|
||||
- For dynamic text that may overflow its container, use `window.__hyperframes.fitTextFontSize(text, { maxWidth, fontFamily, fontWeight })` to compute the largest font size that fits on one line. Returns `{ fontSize, fits }`. Options: `baseFontSize` (default 78), `minFontSize` (default 42), `step` (default 2). `fontFamily` and `fontWeight` must match the CSS applied to the element.
|
||||
- All files (video, audio, fonts, images) live at the project root alongside `index.html`
|
||||
- From sub-compositions, use `../` to reference root files
|
||||
|
||||
|
||||
Reference in New Issue
Block a user