hyperframes 0.8.69 → 0.8.71

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.
Files changed (29) hide show
  1. package/dist/cli.js +280 -29
  2. package/dist/commands/layout-audit.browser.js +36 -4
  3. package/dist/fontLocalizeCli.js +217 -4
  4. package/dist/hyperframe-runtime.js +143 -39
  5. package/dist/hyperframe.manifest.json +1 -1
  6. package/dist/hyperframe.runtime.iife.js +143 -39
  7. package/dist/hyperframes-player.global.js +1 -1
  8. package/dist/skills/media-use/audio/references/tts-to-captions.md +11 -2
  9. package/dist/skills/media-use/audio/references/tts.md +84 -6
  10. package/dist/skills/media-use/audio/scripts/audio.mjs +10 -3
  11. package/dist/skills/media-use/audio/scripts/gemini-pipeline.test.mjs +113 -0
  12. package/dist/skills/media-use/audio/scripts/lib/gemini-auth.mjs +43 -0
  13. package/dist/skills/media-use/audio/scripts/lib/gemini-auth.py +52 -0
  14. package/dist/skills/media-use/audio/scripts/lib/gemini-auth.test.mjs +86 -0
  15. package/dist/skills/media-use/audio/scripts/lib/gemini-auth_test.py +90 -0
  16. package/dist/skills/media-use/audio/scripts/lib/gemini-tts.mjs +122 -0
  17. package/dist/skills/media-use/audio/scripts/lib/gemini-tts.test.mjs +243 -0
  18. package/dist/skills/media-use/audio/scripts/lib/tts.mjs +13 -2
  19. package/dist/skills/media-use/references/audio.md +2 -1
  20. package/dist/studio/assets/{hyperframes-player-B8bDHi2F.js → hyperframes-player-DuN39PN6.js} +1 -1
  21. package/dist/studio/assets/{index-BBOktYKC.css → index-0w6iIlwZ.css} +1 -1
  22. package/dist/studio/assets/{index-784N6Mag.js → index-BmCEvqsr.js} +1 -1
  23. package/dist/studio/assets/{index-D-JPuFj7.js → index-BoQsbDgf.js} +1 -1
  24. package/dist/studio/assets/{index-Bmd-35nQ.js → index-CUcdruHw.js} +108 -108
  25. package/dist/studio/index.html +2 -2
  26. package/dist/studio/index.js +753 -715
  27. package/dist/studio/index.js.map +1 -1
  28. package/dist/studio/styles.css +1 -1
  29. package/package.json +1 -1
@@ -0,0 +1,122 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { geminiAuth } from "./gemini-auth.mjs";
4
+
5
+ export const GEMINI_TTS_MODEL = "gemini-3.8-flash-tts";
6
+
7
+ export const GEMINI_TTS_MODELS = [
8
+ GEMINI_TTS_MODEL,
9
+ "gemini-3.8-flash-lite-tts",
10
+ "gemini-3.1-flash-tts-preview",
11
+ "gemini-2.5-pro-preview-tts",
12
+ "gemini-2.5-flash-preview-tts",
13
+ ];
14
+
15
+ // 3.8 returns WAV; older models return PCM that we wrap without resampling.
16
+ // The shared engine transcribes the saved audio for word timings.
17
+ export async function synthesizeGemini(
18
+ { text, voiceId = "Kore", model = GEMINI_TTS_MODEL, style, speed = 1, wavAbs },
19
+ { fetchImpl = fetch, authenticate = geminiAuth } = {},
20
+ ) {
21
+ let secret;
22
+ try {
23
+ if (!GEMINI_TTS_MODELS.includes(model)) {
24
+ throw new Error(`Unsupported Gemini TTS model: ${model}`);
25
+ }
26
+ if (speed !== 1) throw new Error("Gemini TTS uses style for pacing; omit speed or use 1");
27
+ const modern = model.startsWith("gemini-3.8-");
28
+ if (!modern && /^(voice_|voicekey_)/.test(voiceId)) {
29
+ throw new Error(
30
+ "Custom Gemini voices require a 3.8 TTS model; use a prebuilt voice with older models",
31
+ );
32
+ }
33
+ const auth = authenticate();
34
+ secret = auth.secret;
35
+ const content = {
36
+ type: "text",
37
+ text:
38
+ !modern && style ? `Read the following text with this delivery: ${style}\n\n${text}` : text,
39
+ };
40
+ if (modern && style) content.annotations = [{ type: "speech_metadata", style }];
41
+ const response = await fetchImpl(
42
+ "https://generativelanguage.googleapis.com/v1beta/interactions",
43
+ {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json", ...auth.headers },
46
+ signal: AbortSignal.timeout(120_000),
47
+ body: JSON.stringify({
48
+ model,
49
+ input: [{ type: "user_input", content: [content] }],
50
+ response_format: modern ? { type: "audio", mime_type: "audio/wav" } : { type: "audio" },
51
+ generation_config: { speech_config: [{ voice: voiceId }] },
52
+ store: false,
53
+ }),
54
+ },
55
+ );
56
+ if (!response.ok) {
57
+ const detail = await response.text();
58
+ throw new Error(`Gemini TTS HTTP ${response.status}: ${detail}`);
59
+ }
60
+ const payload = await response.json();
61
+ if (payload.status !== "completed") {
62
+ throw new Error(`Gemini TTS did not complete (${payload.status ?? "missing status"})`);
63
+ }
64
+ const audio = (payload.steps ?? [])
65
+ .filter((step) => step.type === "model_output")
66
+ .flatMap((step) => step.content ?? [])
67
+ .filter((part) => part.type === "audio");
68
+ if (audio.length !== 1 || !audio[0].data) {
69
+ throw new Error("Gemini TTS returned no single audio block");
70
+ }
71
+ let bytes = Buffer.from(audio[0].data, "base64");
72
+ if (!modern && /^audio\/l16(?:;|$)/i.test(audio[0].mime_type ?? "")) {
73
+ bytes = pcmToWav(bytes, audio[0].mime_type);
74
+ } else if (audio[0].mime_type !== "audio/wav") {
75
+ throw new Error("Gemini TTS returned an unsupported audio format");
76
+ }
77
+ if (
78
+ bytes.length <= 44 ||
79
+ bytes.toString("ascii", 0, 4) !== "RIFF" ||
80
+ bytes.toString("ascii", 8, 12) !== "WAVE"
81
+ ) {
82
+ throw new Error("Gemini TTS returned invalid WAV audio");
83
+ }
84
+ mkdirSync(dirname(wavAbs), { recursive: true });
85
+ writeFileSync(wavAbs, bytes);
86
+ return { ok: true, words: null };
87
+ } catch (error) {
88
+ // Error responses must never echo the credential into logs or metadata.
89
+ const message = String(error?.message ?? error);
90
+ return {
91
+ ok: false,
92
+ words: null,
93
+ error: secret ? message.split(secret).join("[redacted]") : message,
94
+ };
95
+ }
96
+ }
97
+
98
+ function pcmToWav(pcm, mime) {
99
+ const rate = Number(/(?:^|;)\s*rate=(\d+)(?:;|$)/i.exec(mime)?.[1]);
100
+ const channels = /(?:^|;)\s*channels=([^;]+)/i.exec(mime)?.[1];
101
+ const codec = /(?:^|;)\s*codec=([^;]+)/i.exec(mime)?.[1];
102
+ if ((channels && channels.trim() !== "1") || (codec && codec.trim() !== "pcm")) {
103
+ throw new Error("Gemini TTS returned unsupported PCM channels or codec");
104
+ }
105
+ if (!Number.isInteger(rate) || rate < 8000 || rate > 96000 || !pcm.length || pcm.length % 2) {
106
+ throw new Error("Gemini TTS returned invalid PCM audio or sample rate");
107
+ }
108
+ const header = Buffer.alloc(44);
109
+ header.write("RIFF");
110
+ header.writeUInt32LE(36 + pcm.length, 4);
111
+ header.write("WAVEfmt ", 8);
112
+ header.writeUInt32LE(16, 16);
113
+ header.writeUInt16LE(1, 20);
114
+ header.writeUInt16LE(1, 22);
115
+ header.writeUInt32LE(rate, 24);
116
+ header.writeUInt32LE(rate * 2, 28);
117
+ header.writeUInt16LE(2, 32);
118
+ header.writeUInt16LE(16, 34);
119
+ header.write("data", 36);
120
+ header.writeUInt32LE(pcm.length, 40);
121
+ return Buffer.concat([header, pcm]);
122
+ }
@@ -0,0 +1,243 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { synthesizeGemini, GEMINI_TTS_MODEL } from "./gemini-tts.mjs";
7
+ import { pickProvider, resolveVoiceId, synthesizeOne } from "./tts.mjs";
8
+
9
+ function fixture(t) {
10
+ const dir = mkdtempSync(join(tmpdir(), "gemini-tts-"));
11
+ const saved = { ...process.env };
12
+ process.env.GEMINI_API_KEY = "test-gemini-key";
13
+ delete process.env.GOOGLE_API_KEY;
14
+ delete process.env.GOOGLE_APPLICATION_CREDENTIALS;
15
+ delete process.env.GCS_CREDS;
16
+ t.after(() => {
17
+ process.env = saved;
18
+ rmSync(dir, { recursive: true, force: true });
19
+ });
20
+ const wav = Buffer.alloc(48);
21
+ wav.write("RIFF");
22
+ wav.writeUInt32LE(40, 4);
23
+ wav.write("WAVEfmt ", 8);
24
+ wav.writeUInt32LE(16, 16);
25
+ wav.writeUInt16LE(1, 20);
26
+ wav.writeUInt16LE(1, 22);
27
+ wav.writeUInt32LE(24000, 24);
28
+ wav.writeUInt32LE(48000, 28);
29
+ wav.writeUInt16LE(2, 32);
30
+ wav.writeUInt16LE(16, 34);
31
+ wav.write("data", 36);
32
+ wav.writeUInt32LE(4, 40);
33
+ const audio = { type: "audio", mime_type: "audio/wav", data: wav.toString("base64") };
34
+ const payload = { status: "completed", steps: [{ type: "model_output", content: [audio] }] };
35
+ return {
36
+ wav,
37
+ audio,
38
+ payload,
39
+ args: { text: "Hello there.", voiceId: "Kore", wavAbs: join(dir, "voice", "one.wav") },
40
+ };
41
+ }
42
+
43
+ test("Gemini preserves verbatim text, directs style separately, saves WAV and requests transcription", async (t) => {
44
+ const { wav, payload, args } = fixture(t);
45
+ t.mock.method(globalThis, "fetch", async (url, options) => {
46
+ assert.equal(url, "https://generativelanguage.googleapis.com/v1beta/interactions");
47
+ assert.equal(options.headers["x-goog-api-key"], "test-gemini-key");
48
+ const body = JSON.parse(options.body);
49
+ assert.equal(body.model, GEMINI_TTS_MODEL);
50
+ assert.equal(body.store, false);
51
+ assert.deepEqual(body.input[0].content, [
52
+ {
53
+ type: "text",
54
+ text: args.text,
55
+ annotations: [{ type: "speech_metadata", style: "Warm and calm" }],
56
+ },
57
+ ]);
58
+ assert.deepEqual(body.generation_config.speech_config, [{ voice: "Kore" }]);
59
+ assert.equal(body.response_format.mime_type, "audio/wav");
60
+ return Response.json(payload);
61
+ });
62
+ const result = await synthesizeOne({ ...args, provider: "gemini", style: "Warm and calm" });
63
+ assert.deepEqual(result, { ok: true, words: null });
64
+ assert.deepEqual(readFileSync(args.wavAbs), wav);
65
+ });
66
+
67
+ test("Flash-Lite and GOOGLE_API_KEY work without a Gemini key", async (t) => {
68
+ const { payload, args } = fixture(t);
69
+ delete process.env.GEMINI_API_KEY;
70
+ process.env.GOOGLE_API_KEY = "test-google-key";
71
+ assert.equal(pickProvider("gemini"), "gemini");
72
+ assert.equal(await resolveVoiceId({ provider: "gemini" }), "Kore");
73
+ const result = await synthesizeGemini(
74
+ { ...args, model: "gemini-3.8-flash-lite-tts" },
75
+ {
76
+ fetchImpl: async (_, options) => {
77
+ assert.equal(options.headers["x-goog-api-key"], "test-google-key");
78
+ const body = JSON.parse(options.body);
79
+ assert.equal(body.model, "gemini-3.8-flash-lite-tts");
80
+ assert.equal(body.input[0].content[0].annotations, undefined);
81
+ return Response.json(payload);
82
+ },
83
+ },
84
+ );
85
+ assert.equal(result.ok, true);
86
+ });
87
+
88
+ test("Gemini remains opt-in even with a key and preserves explicitly chosen voices", async (t) => {
89
+ fixture(t);
90
+ assert.notEqual(pickProvider(), "gemini");
91
+ assert.equal(
92
+ await resolveVoiceId({ provider: "gemini", userVoice: "voice_custom" }),
93
+ "voice_custom",
94
+ );
95
+ delete process.env.GEMINI_API_KEY;
96
+ assert.throws(() => pickProvider("gemini"), /GEMINI_API_KEY or GOOGLE_API_KEY/);
97
+ });
98
+
99
+ test("invalid config fails before spending a generation request", async (t) => {
100
+ const { args } = fixture(t);
101
+ const deps = { fetchImpl: () => assert.fail("must not call the API") };
102
+ for (const change of [{ model: "gemini-2.5-flash" }, { speed: 1.2 }]) {
103
+ const result = await synthesizeGemini({ ...args, ...change }, deps);
104
+ assert.equal(result.ok, false);
105
+ assert.ok(result.error);
106
+ }
107
+ delete process.env.GEMINI_API_KEY;
108
+ assert.match((await synthesizeGemini(args, deps)).error, /needs GEMINI_API_KEY/);
109
+ assert.equal(existsSync(args.wavAbs), false);
110
+ });
111
+
112
+ test("HTTP and network failures stay actionable without leaking the key", async (t) => {
113
+ const { args } = fixture(t);
114
+ const result = await synthesizeGemini(args, {
115
+ fetchImpl: async () => new Response("quota exceeded test-gemini-key", { status: 429 }),
116
+ });
117
+ assert.equal(result.ok, false);
118
+ assert.match(result.error, /HTTP 429: quota exceeded/);
119
+ assert.ok(!result.error.includes("test-gemini-key"));
120
+ const timeout = await synthesizeGemini(args, {
121
+ fetchImpl: async () => {
122
+ throw new Error("timed out");
123
+ },
124
+ });
125
+ assert.match(timeout.error, /timed out/);
126
+ assert.equal(existsSync(args.wavAbs), false);
127
+ });
128
+
129
+ test("incomplete, missing, raw PCM and malformed audio cannot become a successful WAV", async (t) => {
130
+ const { args, payload, audio } = fixture(t);
131
+ const cases = [
132
+ { ...payload, status: "incomplete" },
133
+ { status: "completed", steps: [] },
134
+ ...[{ ...audio, mime_type: "audio/l16" }, { ...audio, data: "not audio" }, audio].map(
135
+ (part, i) => ({
136
+ status: "completed",
137
+ steps: [{ type: "model_output", content: i === 2 ? [part, part] : [part] }],
138
+ }),
139
+ ),
140
+ ];
141
+ for (const body of cases) {
142
+ const result = await synthesizeGemini(args, { fetchImpl: async () => Response.json(body) });
143
+ assert.equal(result.ok, false);
144
+ assert.equal(existsSync(args.wavAbs), false);
145
+ }
146
+ });
147
+
148
+ for (const model of [
149
+ "gemini-3.1-flash-tts-preview",
150
+ "gemini-2.5-pro-preview-tts",
151
+ "gemini-2.5-flash-preview-tts",
152
+ ]) {
153
+ test(`${model} uses legacy delivery prompts and wraps PCM as mono WAV`, async (t) => {
154
+ const { args, wav } = fixture(t);
155
+ const pcm = wav.subarray(44);
156
+ const result = await synthesizeGemini(
157
+ { ...args, model, style: "Warm and clear" },
158
+ {
159
+ fetchImpl: async (_, options) => {
160
+ const body = JSON.parse(options.body);
161
+ assert.deepEqual(body.response_format, { type: "audio" });
162
+ assert.equal(body.input[0].content[0].annotations, undefined);
163
+ assert.ok(body.input[0].content[0].text.endsWith(args.text));
164
+ assert.ok(body.input[0].content[0].text.includes("Warm and clear"));
165
+ return Response.json({
166
+ status: "completed",
167
+ steps: [
168
+ {
169
+ type: "model_output",
170
+ content: [
171
+ {
172
+ type: "audio",
173
+ mime_type: "audio/L16;codec=pcm;rate=24000",
174
+ data: pcm.toString("base64"),
175
+ },
176
+ ],
177
+ },
178
+ ],
179
+ });
180
+ },
181
+ },
182
+ );
183
+ assert.equal(result.ok, true, result.error);
184
+ assert.deepEqual(readFileSync(args.wavAbs), wav);
185
+ });
186
+ }
187
+
188
+ test("service-account bearer and quota project reach synthesis, and token errors are redacted", async (t) => {
189
+ const { args, payload } = fixture(t);
190
+ const authenticate = () => ({
191
+ headers: { Authorization: "Bearer secret-token", "x-goog-user-project": "test-project" },
192
+ secret: "secret-token",
193
+ });
194
+ const ok = await synthesizeGemini(args, {
195
+ authenticate,
196
+ fetchImpl: async (_, options) => {
197
+ assert.equal(options.headers.Authorization, "Bearer secret-token");
198
+ assert.equal(options.headers["x-goog-user-project"], "test-project");
199
+ assert.equal(options.headers["x-goog-api-key"], undefined);
200
+ return Response.json(payload);
201
+ },
202
+ });
203
+ assert.equal(ok.ok, true);
204
+ const failed = await synthesizeGemini(args, {
205
+ authenticate,
206
+ fetchImpl: async () => new Response("denied secret-token", { status: 403 }),
207
+ });
208
+ assert.match(failed.error, /HTTP 403/);
209
+ assert.ok(!failed.error.includes("secret-token"));
210
+ });
211
+
212
+ test("older models reject unsupported PCM and custom voices", async (t) => {
213
+ const { args } = fixture(t);
214
+ for (const mime of [
215
+ "audio/l16",
216
+ "audio/l16;rate=0",
217
+ "audio/l16;rate=24000;channels=2",
218
+ "audio/l16;rate=24000;codec=other",
219
+ ]) {
220
+ const result = await synthesizeGemini(
221
+ { ...args, model: "gemini-3.1-flash-tts-preview" },
222
+ {
223
+ fetchImpl: async () =>
224
+ Response.json({
225
+ status: "completed",
226
+ steps: [
227
+ {
228
+ type: "model_output",
229
+ content: [{ type: "audio", mime_type: mime, data: "AAAAAA==" }],
230
+ },
231
+ ],
232
+ }),
233
+ },
234
+ );
235
+ assert.equal(result.ok, false);
236
+ assert.equal(existsSync(args.wavAbs), false);
237
+ }
238
+ const result = await synthesizeGemini(
239
+ { ...args, model: "gemini-2.5-pro-preview-tts", voiceId: "voice_custom" },
240
+ { fetchImpl: () => assert.fail("must not generate") },
241
+ );
242
+ assert.match(result.error, /Custom Gemini voices require/);
243
+ });
@@ -20,6 +20,8 @@ import { tmpdir } from "node:os";
20
20
  import { dirname, join } from "node:path";
21
21
  import { heygenAuthHeaders, heygenCredential, heygenJSON } from "./heygen.mjs";
22
22
  import { pythonInvocation } from "./python.mjs";
23
+ import { synthesizeGemini } from "./gemini-tts.mjs";
24
+ import { geminiConfigured } from "./gemini-auth.mjs";
23
25
 
24
26
  // ── provider detection ────────────────────────────────────────────────────────
25
27
  export function heygenAvailable() {
@@ -37,8 +39,12 @@ export function elevenlabsAvailable() {
37
39
  // First available provider wins; an explicit choice is honored (and validated).
38
40
  export function pickProvider(userProvider) {
39
41
  if (userProvider) {
40
- if (!["heygen", "elevenlabs", "kokoro"].includes(userProvider))
41
- throw new Error(`invalid provider "${userProvider}" (heygen | elevenlabs | kokoro)`);
42
+ if (!["heygen", "elevenlabs", "kokoro", "gemini"].includes(userProvider))
43
+ throw new Error(`invalid provider "${userProvider}" (heygen | elevenlabs | kokoro | gemini)`);
44
+ if (userProvider === "gemini" && !geminiConfigured())
45
+ throw new Error(
46
+ "provider=gemini needs GEMINI_API_KEY or GOOGLE_API_KEY, or service-account credentials (GOOGLE_APPLICATION_CREDENTIALS or GCS_CREDS)",
47
+ );
42
48
  if (userProvider === "heygen" && !heygenAvailable())
43
49
  throw new Error(
44
50
  "provider=heygen but no HeyGen credentials (set $HEYGEN_API_KEY or run `npx hyperframes auth login`)",
@@ -56,6 +62,7 @@ export function pickProvider(userProvider) {
56
62
  // their own defaults.
57
63
  export async function resolveVoiceId({ provider, userVoice, lang = "en" }) {
58
64
  if (userVoice) return userVoice;
65
+ if (provider === "gemini") return "Kore";
59
66
  if (provider === "elevenlabs") return "21m00Tcm4TlvDq8ikWAM"; // Rachel
60
67
  if (provider === "kokoro") {
61
68
  if (lang === "en") return "am_michael";
@@ -248,10 +255,14 @@ export async function synthesizeOne({
248
255
  voiceId,
249
256
  lang = "en",
250
257
  speed = 1.0,
258
+ model,
259
+ style,
251
260
  wavAbs,
252
261
  hyperframesDir,
253
262
  }) {
254
263
  if (provider === "heygen") return synthesizeHeygen({ text, voiceId, lang, speed, wavAbs });
264
+ if (provider === "gemini")
265
+ return synthesizeGemini({ text, voiceId, model, style, speed, wavAbs });
255
266
  if (provider === "elevenlabs") {
256
267
  // The Python helper writes straight to wavAbs; unlike heygen (transcodeToWav)
257
268
  // and kokoro (the `hyperframes tts` CLI), it does NOT create the parent dir,
@@ -9,7 +9,8 @@ shot), use the shared engine at `audio/scripts/audio.mjs`. It takes a neutral
9
9
  node <SKILL_DIR>/audio/scripts/audio.mjs --request ./audio_request.json --out ./audio_meta.json
10
10
  ```
11
11
 
12
- - **Request** `{ provider?, lang?, speed?, lines: [{ id, text, sfx?: [names] }], bgm: { mode?, query?, prompt? } }`: `id` joins each line back to your model; `bgm.mode` = `retrieve | generate | none` (omit for auto). `--only tts,bgm,sfx` runs a subset and merges into an existing `--out`.
12
+ - **Request** `{ provider?, voice?, lang?, speed?, tts_model?, style?, lines: [{ id, text, style?, sfx?: [names] }], bgm: { mode?, query?, prompt? } }`: `id` joins each line back to your model; `bgm.mode` = `retrieve | generate | none` (omit for auto). `--only tts,bgm,sfx` runs a subset and merges into an existing `--out`.
13
+ - **Gemini narration** is opt-in with `provider: "gemini"` and an API key or service-account credentials. `tts_model` defaults to `gemini-3.8-flash-tts`; 3.8 Flash-Lite, 3.1 Flash Preview, and 2.5 Pro/Flash Preview TTS are also supported. `style` directs delivery; a line's style overrides the request's style. CLI overrides: `--tts-model`, `--style`. See `audio/references/tts.md` for a complete request. The existing automatic provider order is unchanged.
13
14
  - **Output** `audio_meta.json` (id-keyed): `voices[].{path,duration_s,words[]}` (word timestamps for captions), `sfx[]`, `bgm`, `total_duration_s`.
14
15
  - **HeyGen free-usage path**: HeyGen CLI auth unlocks TTS plus music/SFX retrieval. Local/provider-specific generators are explicit alternatives where installed; run `npx hyperframes media-use resolve --doctor` before assuming retrieval or TTS will work.
15
16
  - If BGM took the generate path (`bgm_pending: true`), run `audio/scripts/wait-bgm.mjs` before final render.
@@ -1,4 +1,4 @@
1
- var Ae=Object.defineProperty;var Ee=(s,i,e)=>i in s?Ae(s,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[i]=e;var h=(s,i,e)=>Ee(s,typeof i!="symbol"?i+"":i,e);import{r as ue,i as Ce,a as w,b as ce,c as Te,d as Re,s as Se,e as Le,F as Me}from"./index-Bmd-35nQ.js";function xe(s){return s.hasRuntime||s.runtimeInjected?!1:!!(s.hasNestedCompositions||s.hasTimelines&&s.attempts>=5)}function V(s){return typeof s=="object"&&s!==null}function De(s){return V(s)&&typeof s.getDuration=="function"}function Pe(s){return V(s)&&typeof s.duration=="function"&&typeof s.time=="function"&&typeof s.seek=="function"&&typeof s.play=="function"&&typeof s.pause=="function"}function ke(s){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(s))throw new Error(`Invalid HyperFrames runtime version: ${s}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${s}/dist/hyperframe.runtime.iife.js`}const U=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:ke("0.0.0-dev");function W(s){if(s===null)return null;const i=Number.parseInt(s,10);return Number.isFinite(i)&&i>0?i:null}function Ie(s){const i=(s==null?void 0:s.querySelector("[data-composition-id][data-width][data-height]"))??(s==null?void 0:s.querySelector("[data-width][data-height]"));if(!i)return null;const e=W(i.getAttribute("data-width")),t=W(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class Oe{constructor(i,e){h(this,"_interval",null);h(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),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),a=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(xe({hasRuntime:r,hasTimelines:n,hasNestedCompositions:a,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 c=Ie(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:c});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=U,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,c;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=(c=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:c.getAttribute("data-composition-id"),n=r&&r in e?r:t[t.length-1],a=e[n];return Pe(a)?a:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(De(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Ne=`
1
+ var Ae=Object.defineProperty;var Ee=(s,i,e)=>i in s?Ae(s,i,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[i]=e;var h=(s,i,e)=>Ee(s,typeof i!="symbol"?i+"":i,e);import{r as ue,i as Ce,a as w,b as ce,c as Te,d as Re,s as Se,e as Le,F as Me}from"./index-CUcdruHw.js";function xe(s){return s.hasRuntime||s.runtimeInjected?!1:!!(s.hasNestedCompositions||s.hasTimelines&&s.attempts>=5)}function V(s){return typeof s=="object"&&s!==null}function De(s){return V(s)&&typeof s.getDuration=="function"}function Pe(s){return V(s)&&typeof s.duration=="function"&&typeof s.time=="function"&&typeof s.seek=="function"&&typeof s.play=="function"&&typeof s.pause=="function"}function ke(s){if(!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(s))throw new Error(`Invalid HyperFrames runtime version: ${s}`);return`https://cdn.jsdelivr.net/npm/@hyperframes/core@${s}/dist/hyperframe.runtime.iife.js`}const U=typeof __HYPERFRAMES_RUNTIME_CDN_URL__=="string"?__HYPERFRAMES_RUNTIME_CDN_URL__:ke("0.0.0-dev");function W(s){if(s===null)return null;const i=Number.parseInt(s,10);return Number.isFinite(i)&&i>0?i:null}function Ie(s){const i=(s==null?void 0:s.querySelector("[data-composition-id][data-width][data-height]"))??(s==null?void 0:s.querySelector("[data-width][data-height]"));if(!i)return null;const e=W(i.getAttribute("data-width")),t=W(i.getAttribute("data-height"));return e!==null&&t!==null?{width:e,height:t}:null}class Oe{constructor(i,e){h(this,"_interval",null);h(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),n=!!(t.__timelines&&Object.keys(t.__timelines).length>0),a=!!((e=this._iframe.contentDocument)!=null&&e.querySelector("[data-composition-src]"));if(xe({hasRuntime:r,hasTimelines:n,hasNestedCompositions:a,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 c=Ie(this._iframe.contentDocument);this._callbacks.onReady({duration:d.getDuration(),adapter:d,compositionSize:c});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=U,(t.head||t.documentElement).appendChild(r),(e=(i=this._callbacks).onRuntimeInjected)==null||e.call(i)}catch{}}_resolveDirectTimelineAdapterFromWindow(i){var d,c;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=(c=(d=this._iframe.contentDocument)==null?void 0:d.querySelector("[data-composition-id]"))==null?void 0:c.getAttribute("data-composition-id"),n=r&&r in e?r:t[t.length-1],a=e[n];return Pe(a)?a:null}_resolvePlaybackDurationAdapter(i){const e=Reflect.get(i,"__player");if(De(e))return{kind:"runtime",getDuration:()=>e.getDuration()};const t=this._resolveDirectTimelineAdapterFromWindow(i);return t?{kind:"direct-timeline",timeline:t,getDuration:()=>t.duration()}:null}}const Ne=`
2
2
  :host {
3
3
  display: block;
4
4
  position: relative;