hyperframes 0.8.82 → 0.8.83

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 (35) hide show
  1. package/dist/backgroundChecksWorker.js +1 -1
  2. package/dist/cli.js +1921 -1560
  3. package/dist/fontLocalizeCli.js +304 -217
  4. package/dist/hyperframe-runtime.js +81 -56
  5. package/dist/hyperframe.manifest.json +1 -1
  6. package/dist/hyperframe.runtime.iife.js +81 -56
  7. package/dist/hyperframes-player.global.js +1 -1
  8. package/dist/studio/assets/{hyperframes-player-zGIbZhLJ.js → hyperframes-player-BAerMCex.js} +1 -1
  9. package/dist/studio/assets/{index-BiLCTunu.js → index-BUhcVhNy.js} +1 -1
  10. package/dist/studio/assets/{index-DSazIt7R.js → index-BYhK_KMc.js} +193 -193
  11. package/dist/studio/assets/{index-DStmfnpZ.js → index-Iv0HI7eM.js} +1 -1
  12. package/dist/studio/{chunk-I7XNAIOF.js → chunk-3IOCVWB6.js} +6 -97
  13. package/dist/studio/chunk-3IOCVWB6.js.map +1 -0
  14. package/dist/studio/chunk-62DDWOSH.js +189 -0
  15. package/dist/studio/chunk-62DDWOSH.js.map +1 -0
  16. package/dist/studio/chunk-LPZMAGS7.js +1871 -0
  17. package/dist/studio/chunk-LPZMAGS7.js.map +1 -0
  18. package/dist/studio/chunk-QZ6HNNFG.js +934 -0
  19. package/dist/studio/chunk-QZ6HNNFG.js.map +1 -0
  20. package/dist/studio/chunk-S65AQH6M.js +101 -0
  21. package/dist/studio/chunk-S65AQH6M.js.map +1 -0
  22. package/dist/studio/{domEditingLayers-MHFDKAQ7.js → domEditingLayers-ZOOMN7CQ.js} +3 -2
  23. package/dist/studio/index.d.ts +669 -330
  24. package/dist/studio/index.html +1 -1
  25. package/dist/studio/index.js +20501 -23167
  26. package/dist/studio/index.js.map +1 -1
  27. package/dist/studio/player.d.ts +30 -0
  28. package/dist/studio/player.js +10 -0
  29. package/dist/studio/player.js.map +1 -0
  30. package/dist/studio/ui.d.ts +294 -0
  31. package/dist/studio/ui.js +66 -0
  32. package/dist/studio/ui.js.map +1 -0
  33. package/package.json +1 -1
  34. package/dist/studio/chunk-I7XNAIOF.js.map +0 -1
  35. /package/dist/studio/{domEditingLayers-MHFDKAQ7.js.map → domEditingLayers-ZOOMN7CQ.js.map} +0 -0
@@ -0,0 +1,1871 @@
1
+ import {
2
+ HyperframesLoader
3
+ } from "./chunk-QZ6HNNFG.js";
4
+ import {
5
+ buildProjectApiPath,
6
+ getSourceScopedSelectorIndex
7
+ } from "./chunk-S65AQH6M.js";
8
+
9
+ // src/player/lib/time.ts
10
+ var STUDIO_PREVIEW_FPS = 30;
11
+ function formatTime(time) {
12
+ if (!Number.isFinite(time) || time < 0) return "00:00";
13
+ const mins = Math.floor(time / 60);
14
+ const secs = Math.floor(time % 60);
15
+ return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
16
+ }
17
+ function secondsToFrame(time, fps = STUDIO_PREVIEW_FPS) {
18
+ if (!Number.isFinite(time) || time <= 0) return 0;
19
+ if (!Number.isFinite(fps) || fps <= 0) return 0;
20
+ return Math.round(time * fps);
21
+ }
22
+ function frameToSeconds(frame, fps = STUDIO_PREVIEW_FPS) {
23
+ if (!Number.isFinite(frame) || frame <= 0) return 0;
24
+ if (!Number.isFinite(fps) || fps <= 0) return 0;
25
+ return frame / fps;
26
+ }
27
+ function stepFrameTime(time, deltaFrames, fps = STUDIO_PREVIEW_FPS) {
28
+ const currentFrame = secondsToFrame(time, fps);
29
+ const nextFrame = Math.max(0, currentFrame + deltaFrames);
30
+ return frameToSeconds(nextFrame, fps);
31
+ }
32
+ function formatFrameTime(time, duration, fps = STUDIO_PREVIEW_FPS) {
33
+ const currentFrame = secondsToFrame(time, fps);
34
+ const totalFrames = secondsToFrame(duration, fps);
35
+ return `${currentFrame}f / ${totalFrames}f`;
36
+ }
37
+
38
+ // src/player/store/liveTime.ts
39
+ var timeListeners = /* @__PURE__ */ new Set();
40
+ var liveTime = {
41
+ notify: (time) => timeListeners.forEach((listener) => listener(time)),
42
+ subscribe: (listener) => {
43
+ timeListeners.add(listener);
44
+ return () => timeListeners.delete(listener);
45
+ }
46
+ };
47
+
48
+ // src/player/store/playerStore.ts
49
+ import { create } from "zustand";
50
+
51
+ // src/player/store/playerStoreDevHandle.ts
52
+ function isDevBuild() {
53
+ try {
54
+ return import.meta.env.DEV === true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ function attachPlayerStoreDevHandle(store) {
60
+ if (!isDevBuild() || typeof window === "undefined") return;
61
+ window.__playerStore = store;
62
+ }
63
+
64
+ // src/player/lib/playbackAdapter.ts
65
+ function isFinitePositive(value) {
66
+ return Number.isFinite(value) && value > 0;
67
+ }
68
+ function clampTime(time, duration) {
69
+ const safeDuration = Math.max(0, Number.isFinite(duration) ? duration : 0);
70
+ const safeTime = Math.max(0, Number.isFinite(time) ? time : 0);
71
+ return safeDuration > 0 ? Math.min(safeTime, safeDuration) : safeTime;
72
+ }
73
+ function getAdapterDuration(adapter) {
74
+ if (!adapter) return 0;
75
+ try {
76
+ const duration = Number(adapter.getDuration());
77
+ return isFinitePositive(duration) ? duration : 0;
78
+ } catch {
79
+ return 0;
80
+ }
81
+ }
82
+ function getDefaultStaticSeekPlaybackClock(win) {
83
+ return {
84
+ now: () => win.performance.now(),
85
+ requestAnimationFrame: (callback) => win.requestAnimationFrame(callback),
86
+ cancelAnimationFrame: (handle) => win.cancelAnimationFrame(handle)
87
+ };
88
+ }
89
+ function createStaticSeekPlaybackAdapter(player, duration, clock, getPlaybackRate = () => 1) {
90
+ const safeDuration = Math.max(0, Number.isFinite(duration) ? duration : 0);
91
+ let currentTime = clampTime(Number(player.getTime?.() ?? 0), safeDuration);
92
+ let playing = false;
93
+ let rafId = 0;
94
+ let playStartTime = currentTime;
95
+ let playStartNow = clock.now();
96
+ const renderSeek = (time) => {
97
+ currentTime = clampTime(time, safeDuration);
98
+ if (typeof player.renderSeek === "function") {
99
+ player.renderSeek(currentTime);
100
+ return;
101
+ }
102
+ player.seek?.(currentTime);
103
+ };
104
+ const stopTicker = () => {
105
+ if (rafId) {
106
+ clock.cancelAnimationFrame(rafId);
107
+ rafId = 0;
108
+ }
109
+ };
110
+ const tick = (now) => {
111
+ if (!playing) return;
112
+ const playbackRate = Math.max(0.1, Number(getPlaybackRate()) || 1);
113
+ const elapsed = (now - playStartNow) / 1e3 * playbackRate;
114
+ renderSeek(playStartTime + elapsed);
115
+ if (currentTime >= safeDuration) {
116
+ playing = false;
117
+ rafId = 0;
118
+ return;
119
+ }
120
+ rafId = clock.requestAnimationFrame(tick);
121
+ };
122
+ return {
123
+ play: () => {
124
+ if (playing || safeDuration <= 0) return;
125
+ if (currentTime >= safeDuration) renderSeek(0);
126
+ playing = true;
127
+ playStartTime = currentTime;
128
+ playStartNow = clock.now();
129
+ stopTicker();
130
+ rafId = clock.requestAnimationFrame(tick);
131
+ },
132
+ pause: () => {
133
+ playing = false;
134
+ stopTicker();
135
+ },
136
+ seek: (time, options) => {
137
+ renderSeek(time);
138
+ if (options?.keepPlaying) {
139
+ if (playing) {
140
+ playStartTime = currentTime;
141
+ playStartNow = clock.now();
142
+ }
143
+ return;
144
+ }
145
+ playing = false;
146
+ stopTicker();
147
+ },
148
+ getTime: () => currentTime,
149
+ getDuration: () => safeDuration,
150
+ isPlaying: () => playing
151
+ };
152
+ }
153
+ function releaseStaticSeekCache(cache, warned) {
154
+ cache.current?.adapter.pause();
155
+ cache.current = null;
156
+ warned.current = false;
157
+ }
158
+ function resolveStaticSeekFallback(opts) {
159
+ const { cache, warned, bestAdapter, effectiveDuration, docDuration } = opts;
160
+ const cached = cache.current;
161
+ if (cached?.player === bestAdapter && cached.duration === effectiveDuration) {
162
+ return cached.adapter;
163
+ }
164
+ cached?.adapter.pause();
165
+ if (!warned.current) {
166
+ warned.current = true;
167
+ console.warn(
168
+ `[useTimelinePlayer] Selected adapter duration (${getAdapterDuration(bestAdapter)}s) does not cover the document duration (${docDuration}s); falling back to seek-driven playback, which never starts media elements or WebAudio. Audio will not play in preview \u2014 extend the GSAP timeline to cover the declared data-duration.`
169
+ );
170
+ }
171
+ const adapter = createStaticSeekPlaybackAdapter(
172
+ bestAdapter,
173
+ effectiveDuration,
174
+ opts.clock,
175
+ opts.getPlaybackRate
176
+ );
177
+ cache.current = { player: bestAdapter, duration: effectiveDuration, adapter };
178
+ return adapter;
179
+ }
180
+ function wrapTimeline(tl) {
181
+ return {
182
+ play: () => tl.play(),
183
+ pause: () => tl.pause(),
184
+ seek: (t, options) => {
185
+ const shouldPause = !options?.keepPlaying;
186
+ if (shouldPause) tl.pause();
187
+ tl.seek(t);
188
+ if (shouldPause) tl.pause();
189
+ },
190
+ getTime: () => tl.time(),
191
+ getDuration: () => tl.duration(),
192
+ isPlaying: () => tl.isActive()
193
+ };
194
+ }
195
+
196
+ // src/player/lib/timelineElementHelpers.ts
197
+ import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
198
+ import { readElementFades } from "@hyperframes/core/audio-fade";
199
+ import { readMediaOffsetSeconds } from "@hyperframes/parsers/media-duration";
200
+ var LAYER_REVEAL_PRIOR_Z_ATTR = "data-hf-reveal-prior-z";
201
+ var LAYER_REVEAL_PRIOR_POSITION_ATTR = "data-hf-reveal-prior-pos";
202
+ function readLayerRevealPriorZ(el) {
203
+ const raw = el.getAttribute(LAYER_REVEAL_PRIOR_Z_ATTR);
204
+ if (raw == null) return null;
205
+ const n = Number.parseInt(raw, 10);
206
+ return Number.isFinite(n) ? n : null;
207
+ }
208
+ function readTimelineElementZIndex(el) {
209
+ const prior = readLayerRevealPriorZ(el);
210
+ if (prior != null) return prior;
211
+ const html = el;
212
+ const parseZ = (value) => {
213
+ if (value == null || value === "" || value === "auto") return null;
214
+ const n = Number.parseInt(value, 10);
215
+ return Number.isFinite(n) ? n : null;
216
+ };
217
+ const fromInline = parseZ(html.style?.zIndex);
218
+ if (fromInline != null) return fromInline;
219
+ const view = el.ownerDocument?.defaultView;
220
+ if (view?.getComputedStyle) {
221
+ const fromComputed = parseZ(view.getComputedStyle(html).zIndex);
222
+ if (fromComputed != null) return fromComputed;
223
+ }
224
+ return 0;
225
+ }
226
+ function readDurationAttribute(el) {
227
+ if (!el) return 0;
228
+ const duration = Number.parseFloat(el.getAttribute("data-duration") ?? "") || Number.parseFloat(el.getAttribute("data-hf-authored-duration") ?? "");
229
+ return isFinitePositive(duration) ? duration : 0;
230
+ }
231
+ function normalizePlaybackRate(raw) {
232
+ return Number.isFinite(raw) && raw > 0 ? Math.max(0.1, Math.min(5, raw)) : 1;
233
+ }
234
+ function isTimelineIgnoredElement(el) {
235
+ if (el.tagName.toLowerCase() === HF_AUDIO_GROUP_TAG) return true;
236
+ return Boolean(
237
+ el.closest(
238
+ [
239
+ "[data-hyperframes-ignore]",
240
+ "[data-hyperframes-picker-ignore]",
241
+ "[data-hf-ignore]",
242
+ "[data-hf-color-grading-canvas]"
243
+ ].join(",")
244
+ )
245
+ );
246
+ }
247
+ function furthestClipEndFromDocument(doc) {
248
+ if (!doc) return 0;
249
+ const root = doc.querySelector("[data-composition-id]");
250
+ let maxEnd = 0;
251
+ for (const node of Array.from(doc.querySelectorAll("[data-start]"))) {
252
+ if (node === root || isTimelineIgnoredElement(node)) continue;
253
+ const start = Number.parseFloat(node.getAttribute("data-start") ?? "");
254
+ const duration = readDurationAttribute(node);
255
+ if (!Number.isFinite(start) || start < 0 || duration <= 0) continue;
256
+ maxEnd = Math.max(maxEnd, start + duration);
257
+ }
258
+ return maxEnd;
259
+ }
260
+ function readTimelineDurationFromDocument(doc) {
261
+ if (!doc) return 0;
262
+ const rootDuration = readDurationAttribute(doc.querySelector("[data-composition-id]"));
263
+ if (rootDuration > 0) return rootDuration;
264
+ return furthestClipEndFromDocument(doc);
265
+ }
266
+ function furthestClipEndFromSource(source) {
267
+ if (!source) return 0;
268
+ return furthestClipEndFromDocument(new DOMParser().parseFromString(source, "text/html"));
269
+ }
270
+ function isHtmlElement(el) {
271
+ const HtmlElementCtor = el.ownerDocument.defaultView?.HTMLElement ?? globalThis.HTMLElement;
272
+ return typeof HtmlElementCtor !== "undefined" && el instanceof HtmlElementCtor;
273
+ }
274
+ function isCompositionHost(el) {
275
+ return el.hasAttribute("data-composition-id") || el.hasAttribute("data-composition-src") || el.hasAttribute("data-composition-file");
276
+ }
277
+ function resolveMediaElement(el) {
278
+ const win = el.ownerDocument.defaultView ?? window;
279
+ const MediaElementCtor = win.HTMLMediaElement ?? globalThis.HTMLMediaElement;
280
+ const ImageElementCtor = win.HTMLImageElement ?? globalThis.HTMLImageElement;
281
+ if (el instanceof MediaElementCtor || el instanceof ImageElementCtor) return el;
282
+ if (isCompositionHost(el)) return null;
283
+ const candidate = el.querySelector("video, audio, img");
284
+ return candidate instanceof MediaElementCtor || candidate instanceof ImageElementCtor ? candidate : null;
285
+ }
286
+ function readPlaybackStartAttributes(getAttr) {
287
+ const playbackStartAttr = getAttr("data-playback-start") != null ? "playback-start" : getAttr("data-media-start") != null ? "media-start" : void 0;
288
+ return playbackStartAttr ? { playbackStart: readMediaOffsetSeconds(getAttr), playbackStartAttr } : {};
289
+ }
290
+ function playbackStartAttributeForElement(element) {
291
+ return element.playbackStartAttr === "playback-start" || element.kind === "composition" ? "data-playback-start" : "data-media-start";
292
+ }
293
+ function applyPlaybackMetadataFromElement(entry, el) {
294
+ Object.assign(
295
+ entry,
296
+ readPlaybackStartAttributes((n) => el.getAttribute(n))
297
+ );
298
+ const authoredPlaybackRate = Number.parseFloat(el.getAttribute("data-playback-rate") ?? "");
299
+ if (Number.isFinite(authoredPlaybackRate) && authoredPlaybackRate > 0) {
300
+ entry.playbackRate = normalizePlaybackRate(authoredPlaybackRate);
301
+ }
302
+ }
303
+ function setOptional(entry, key, value) {
304
+ if (value === void 0) delete entry[key];
305
+ else entry[key] = value;
306
+ }
307
+ function readVolume(el, media) {
308
+ const volume = Number.parseFloat(
309
+ el.getAttribute("data-volume") ?? media.getAttribute("data-volume") ?? ""
310
+ );
311
+ return Number.isFinite(volume) ? volume : void 0;
312
+ }
313
+ function applyAudioMetadataFromElement(entry, el) {
314
+ const media = resolveMediaElement(el) ?? el;
315
+ const muted = el.hasAttribute("muted") || media.hasAttribute("muted");
316
+ const hasAudio = el.getAttribute("data-has-audio");
317
+ const sound = hasAudio === null ? el.tagName === "VIDEO" && !muted : hasAudio === "true";
318
+ setOptional(entry, "hasAudio", sound ? true : void 0);
319
+ setOptional(entry, "muted", muted ? true : void 0);
320
+ setOptional(entry, "volume", readVolume(el, media));
321
+ }
322
+ function applyFadeMetadataFromElement(entry, el) {
323
+ const fades = readElementFades(el);
324
+ setOptional(entry, "fadeIn", fades.fadeIn > 0 ? fades.fadeIn : void 0);
325
+ setOptional(entry, "fadeOut", fades.fadeOut > 0 ? fades.fadeOut : void 0);
326
+ }
327
+ function applyMediaMetadataFromElement(entry, el) {
328
+ applyPlaybackMetadataFromElement(entry, el);
329
+ applyAudioMetadataFromElement(entry, el);
330
+ applyFadeMetadataFromElement(entry, el);
331
+ const mediaEl = resolveMediaElement(el);
332
+ if (!mediaEl) return;
333
+ entry.tag = mediaEl.tagName.toLowerCase();
334
+ const src = mediaEl.getAttribute("src");
335
+ if (src) entry.src = src;
336
+ const win = mediaEl.ownerDocument.defaultView ?? window;
337
+ const MediaElementCtor = win.HTMLMediaElement ?? globalThis.HTMLMediaElement;
338
+ if (typeof MediaElementCtor === "undefined" || !(mediaEl instanceof MediaElementCtor)) return;
339
+ const sourceDurationAttr = el.getAttribute("data-source-duration") ?? mediaEl.getAttribute("data-source-duration");
340
+ const sourceDuration = sourceDurationAttr ? parseFloat(sourceDurationAttr) : mediaEl.duration;
341
+ if (Number.isFinite(sourceDuration) && sourceDuration > 0) {
342
+ entry.sourceDuration = sourceDuration;
343
+ }
344
+ const playbackRate = mediaEl.defaultPlaybackRate;
345
+ if (entry.playbackRate == null && Number.isFinite(playbackRate) && playbackRate > 0) {
346
+ entry.playbackRate = normalizePlaybackRate(playbackRate);
347
+ }
348
+ }
349
+ function getTimelineElementDisplayLabel(input) {
350
+ const label = input.label?.trim();
351
+ if (label) return label;
352
+ const id = input.id?.trim();
353
+ if (id) return id;
354
+ const tag = input.tag?.trim().toLowerCase();
355
+ return tag ? `${tag} clip` : "Timeline clip";
356
+ }
357
+ function getTimelineElementSelector(el) {
358
+ if (isHtmlElement(el) && el.id) return `#${CSS.escape(el.id)}`;
359
+ const compId = el.getAttribute("data-composition-id");
360
+ if (compId) return `[data-composition-id="${CSS.escape(compId)}"]`;
361
+ if (isHtmlElement(el)) {
362
+ const classes = el.className.split(/\s+/).filter(Boolean);
363
+ const firstClass = classes.find((className) => className !== "clip") ?? classes[0];
364
+ if (firstClass) return `.${CSS.escape(firstClass)}`;
365
+ }
366
+ return void 0;
367
+ }
368
+ function getTimelineElementSourceFile(el) {
369
+ const ownerRoot = el.parentElement?.closest("[data-composition-id]");
370
+ return ownerRoot?.getAttribute("data-composition-file") ?? ownerRoot?.getAttribute("data-composition-src") ?? void 0;
371
+ }
372
+ function getTimelineElementSelectorIndex(doc, el, selector) {
373
+ return getSourceScopedSelectorIndex(
374
+ doc,
375
+ el,
376
+ selector,
377
+ getTimelineElementSourceFile(el),
378
+ getTimelineElementSourceFile
379
+ );
380
+ }
381
+ function buildTimelineElementKey(params) {
382
+ const scope = params.sourceFile ?? "index.html";
383
+ if (params.domId) return `${scope}#${params.domId}`;
384
+ if (params.selector) return `${scope}:${params.selector}:${params.selectorIndex ?? 0}`;
385
+ return `${scope}:${params.id}:${params.fallbackIndex}`;
386
+ }
387
+ function splitTimelineElementKey(key) {
388
+ const hashIndex = key.lastIndexOf("#");
389
+ if (hashIndex < 0) return { sourceFile: null, domId: key };
390
+ return { sourceFile: key.slice(0, hashIndex), domId: key.slice(hashIndex + 1) };
391
+ }
392
+ function buildTimelineElementIdentity(params) {
393
+ const id = params.preferredId?.trim() || buildTimelineElementKey({
394
+ id: params.label,
395
+ fallbackIndex: params.fallbackIndex,
396
+ domId: params.domId,
397
+ selector: params.selector,
398
+ selectorIndex: params.selectorIndex,
399
+ sourceFile: params.sourceFile
400
+ });
401
+ const key = buildTimelineElementKey({
402
+ id,
403
+ fallbackIndex: params.fallbackIndex,
404
+ domId: params.domId,
405
+ selector: params.selector,
406
+ selectorIndex: params.selectorIndex,
407
+ sourceFile: params.sourceFile
408
+ });
409
+ return { id, key };
410
+ }
411
+ function getTimelineElementIdentity(element) {
412
+ return element.key ?? element.id;
413
+ }
414
+ function runtimeAudioId(element) {
415
+ return element.domId || null;
416
+ }
417
+ function deriveTimelineStoreKey(params) {
418
+ if (!params.domId && !params.selector) return void 0;
419
+ return buildTimelineElementKey({ id: "", fallbackIndex: 0, ...params });
420
+ }
421
+ function deriveTimelineStoreKeyForDomId(domId, sourceFile) {
422
+ return deriveTimelineStoreKey({ domId, sourceFile });
423
+ }
424
+ function getTimelineDomNodes(doc) {
425
+ const rootComp = doc.querySelector("[data-composition-id]");
426
+ return Array.from(doc.querySelectorAll("[data-start]")).filter(
427
+ (node) => node !== rootComp && !isTimelineIgnoredElement(node)
428
+ );
429
+ }
430
+ function numbersNearlyEqual(a, b) {
431
+ return Math.abs(a - b) < 1e-3;
432
+ }
433
+ var MANIFEST_CLIP_ATTRS = [
434
+ ["data-start", (clip) => clip.start],
435
+ ["data-duration", (clip) => clip.duration],
436
+ ["data-track-index", (clip) => clip.track]
437
+ ];
438
+ function nodeMatchesClipTag(node, clip) {
439
+ return !clip.tagName || node.tagName.toLowerCase() === clip.tagName.toLowerCase();
440
+ }
441
+ function nodeMatchesManifestClip(node, clip) {
442
+ if (!nodeMatchesClipTag(node, clip)) return false;
443
+ return MANIFEST_CLIP_ATTRS.every(([attr, expected]) => {
444
+ const actual = Number.parseFloat(node.getAttribute(attr) ?? "");
445
+ return !Number.isFinite(actual) || numbersNearlyEqual(actual, expected(clip));
446
+ });
447
+ }
448
+ function nodeInClipScope(node, clip) {
449
+ const scope = clip.compositionAncestors;
450
+ if (!scope) return true;
451
+ const ids = [];
452
+ for (let cursor = node.parentElement; cursor; cursor = cursor.parentElement) {
453
+ const id = cursor.getAttribute("data-composition-id");
454
+ if (id) ids.unshift(id);
455
+ }
456
+ return ids.length === scope.length && ids.every((id, index) => id === scope[index]);
457
+ }
458
+ function findInClipScope(doc, clip, selectors) {
459
+ let lone = null;
460
+ for (const selector of selectors) {
461
+ const nodes = Array.from(doc.querySelectorAll(selector));
462
+ const scoped = nodes.find((node) => nodeInClipScope(node, clip));
463
+ if (scoped) return scoped;
464
+ if (nodes.length === 1) lone ??= nodes[0];
465
+ }
466
+ return lone;
467
+ }
468
+ function previewElementFinder(doc, selector = "[data-hf-id], [id]") {
469
+ const byKey = /* @__PURE__ */ new Map();
470
+ const add = (key, node) => {
471
+ const nodes = byKey.get(key);
472
+ if (nodes) nodes.push(node);
473
+ else byKey.set(key, [node]);
474
+ };
475
+ for (const node of doc.querySelectorAll(selector)) {
476
+ const hfId = node.getAttribute("data-hf-id");
477
+ const id = node.getAttribute("id");
478
+ if (hfId) add(`hf:${hfId}`, node);
479
+ if (id) add(`id:${id}`, node);
480
+ }
481
+ return (target) => {
482
+ const matches = [
483
+ ...target.hfId && byKey.get(`hf:${target.hfId}`) || [],
484
+ ...byKey.get(`id:${target.domId ?? target.id}`) ?? []
485
+ ];
486
+ return matches.find((node) => getTimelineElementSourceFile(node) === target.sourceFile) ?? matches[0] ?? null;
487
+ };
488
+ }
489
+ function findClipElementById(doc, clip) {
490
+ if (!clip.id) return null;
491
+ const first = doc.getElementById(clip.id);
492
+ if (!first || nodeInClipScope(first, clip)) return first;
493
+ return findInClipScope(doc, clip, [`[id="${CSS.escape(clip.id)}"]`]);
494
+ }
495
+ function findTimelineDomNode(doc, clip) {
496
+ if (!clip.id) return null;
497
+ const first = doc.getElementById(clip.id);
498
+ if (first && nodeInClipScope(first, clip)) return first;
499
+ const id = CSS.escape(clip.id);
500
+ const byOtherKeys = [`[data-hf-id="${id}"]`, `[data-composition-id="${id}"]`, `.${id}`];
501
+ return findInClipScope(doc, clip, first ? [`[id="${id}"]`, ...byOtherKeys] : byOtherKeys);
502
+ }
503
+ function findTimelineDomNodeForClip(doc, clip, fallbackIndex, usedNodes = /* @__PURE__ */ new Set(), getCandidates = () => getTimelineDomNodes(doc)) {
504
+ const byIdentity = findTimelineDomNode(doc, clip);
505
+ if (byIdentity && !usedNodes.has(byIdentity) && nodeMatchesClipTag(byIdentity, clip))
506
+ return byIdentity;
507
+ const candidates = getCandidates().filter((node) => !usedNodes.has(node));
508
+ const exact = candidates.find((node) => nodeMatchesManifestClip(node, clip));
509
+ if (exact) return exact;
510
+ const positional = candidates[fallbackIndex];
511
+ return positional && nodeMatchesClipTag(positional, clip) ? positional : null;
512
+ }
513
+ function createTimelineDomNodeResolver(doc) {
514
+ let candidates;
515
+ const usedNodes = /* @__PURE__ */ new Set();
516
+ const getCandidates = () => candidates ??= getTimelineDomNodes(doc);
517
+ return (clip, index) => {
518
+ const node = findTimelineDomNodeForClip(doc, clip, index, usedNodes, getCandidates);
519
+ if (node) usedNodes.add(node);
520
+ return node;
521
+ };
522
+ }
523
+
524
+ // src/player/store/playerStoreSelection.ts
525
+ function nextSelectionSet(current, id, preserveSet) {
526
+ if (preserveSet) return id && current.has(id) ? new Set(current) : /* @__PURE__ */ new Set();
527
+ return id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set();
528
+ }
529
+ function revealTargetsSelection(request, id) {
530
+ if (!request || id === null) return false;
531
+ return request.elementKey === splitTimelineElementKey(id).domId;
532
+ }
533
+
534
+ // src/components/dock/panelRegistry.ts
535
+ var PANEL_IDS = [
536
+ "preview",
537
+ "timeline",
538
+ "compositions",
539
+ "assets",
540
+ "code",
541
+ "catalog",
542
+ "design",
543
+ "layers",
544
+ "renders",
545
+ "variables",
546
+ "slideshow"
547
+ ];
548
+ var PANEL_DEFINITIONS = {
549
+ preview: {
550
+ title: "Preview",
551
+ zone: "center",
552
+ reopen: { near: "timeline", direction: "above" },
553
+ keepMounted: true
554
+ },
555
+ timeline: {
556
+ title: "Timeline",
557
+ zone: "center",
558
+ reopen: { near: "preview", direction: "below" },
559
+ keepMounted: true
560
+ },
561
+ compositions: {
562
+ title: "Compositions",
563
+ zone: "left",
564
+ reopen: { near: "preview", direction: "left" }
565
+ },
566
+ assets: { title: "Assets", zone: "left", reopen: { near: "compositions", direction: "within" } },
567
+ code: { title: "Code", zone: "left", reopen: { near: "compositions", direction: "within" } },
568
+ catalog: {
569
+ title: "Catalog",
570
+ zone: "left",
571
+ reopen: { near: "compositions", direction: "within" }
572
+ },
573
+ design: { title: "Design", zone: "right", reopen: { near: "preview", direction: "right" } },
574
+ layers: { title: "Layers", zone: "right", reopen: { near: "design", direction: "within" } },
575
+ renders: { title: "Renders", zone: "right", reopen: { near: "design", direction: "within" } },
576
+ variables: { title: "Variables", zone: "right", reopen: { near: "design", direction: "within" } },
577
+ slideshow: { title: "Slideshow", zone: "right", reopen: { near: "design", direction: "within" } }
578
+ };
579
+ function isPanelId(value) {
580
+ return typeof value === "string" && PANEL_IDS.includes(value);
581
+ }
582
+ function panelsInZone(zone) {
583
+ return PANEL_IDS.filter((id) => PANEL_DEFINITIONS[id].zone === zone);
584
+ }
585
+
586
+ // src/components/dock/dockLayoutSchema.ts
587
+ var DOCK_PANEL_COMPONENT = "panel";
588
+ function isRecord(value) {
589
+ return typeof value === "object" && value !== null && !Array.isArray(value);
590
+ }
591
+ function collectLeafViews(node, into) {
592
+ if (!isRecord(node)) return false;
593
+ if (node.type === "leaf") {
594
+ if (!isRecord(node.data) || !Array.isArray(node.data.views)) return false;
595
+ into.push(...node.data.views);
596
+ return true;
597
+ }
598
+ if (node.type === "branch") {
599
+ return Array.isArray(node.data) && node.data.every((child) => collectLeafViews(child, into));
600
+ }
601
+ return false;
602
+ }
603
+ function hasGridShape(grid) {
604
+ const oriented = grid.orientation === "HORIZONTAL" || grid.orientation === "VERTICAL";
605
+ return oriented && typeof grid.width === "number" && typeof grid.height === "number";
606
+ }
607
+ function panelsAreRegistered(panels) {
608
+ const ids = Object.keys(panels);
609
+ return ids.length > 0 && ids.every((id) => {
610
+ const state = panels[id];
611
+ return isPanelId(id) && isRecord(state) && state.id === id && state.contentComponent === DOCK_PANEL_COMPONENT;
612
+ });
613
+ }
614
+ function collectFloatingViews(floating, into) {
615
+ if (floating === void 0) return true;
616
+ if (!Array.isArray(floating)) return false;
617
+ return floating.every((group) => {
618
+ if (!isRecord(group) || !isRecord(group.data) || !Array.isArray(group.data.views)) return false;
619
+ into.push(...group.data.views);
620
+ return true;
621
+ });
622
+ }
623
+ function parseDockLayout(value) {
624
+ if (!isRecord(value) || !isRecord(value.grid) || !isRecord(value.panels)) return null;
625
+ const { grid, panels } = value;
626
+ if (!hasGridShape(grid) || !panelsAreRegistered(panels)) return null;
627
+ const views = [];
628
+ if (!collectLeafViews(grid.root, views)) return null;
629
+ if (!collectFloatingViews(value.floatingGroups, views)) return null;
630
+ const everyViewIsAPanel = views.every((id) => typeof id === "string" && id in panels);
631
+ const everyPanelIsPlaced = Object.keys(panels).every((id) => views.includes(id));
632
+ return everyViewIsAPanel && everyPanelIsPlaced ? value : null;
633
+ }
634
+
635
+ // src/utils/studioUiPreferences.ts
636
+ var STUDIO_UI_PREFERENCES_KEY = "hf-studio-ui-preferences";
637
+ function isRecord2(value) {
638
+ return typeof value === "object" && value !== null;
639
+ }
640
+ function getBrowserStorage() {
641
+ if (typeof window === "undefined") return null;
642
+ try {
643
+ return window.localStorage;
644
+ } catch {
645
+ return null;
646
+ }
647
+ }
648
+ function storageKeyFor(projectId) {
649
+ return projectId ? `${STUDIO_UI_PREFERENCES_KEY}:${projectId}` : STUDIO_UI_PREFERENCES_KEY;
650
+ }
651
+ function readStorage(storage, key) {
652
+ if (!storage) return {};
653
+ try {
654
+ const raw = storage.getItem(key);
655
+ if (!raw) return {};
656
+ const parsed = JSON.parse(raw);
657
+ if (!isRecord2(parsed)) return {};
658
+ const preferences = {};
659
+ if (typeof parsed.timelineVisible === "boolean") {
660
+ preferences.timelineVisible = parsed.timelineVisible;
661
+ }
662
+ if (typeof parsed.playbackRate === "number" && Number.isFinite(parsed.playbackRate)) {
663
+ preferences.playbackRate = parsed.playbackRate;
664
+ }
665
+ if (typeof parsed.audioMuted === "boolean") {
666
+ preferences.audioMuted = parsed.audioMuted;
667
+ }
668
+ if (typeof parsed.audioVolume === "number" && Number.isFinite(parsed.audioVolume) && parsed.audioVolume >= 0 && parsed.audioVolume <= 1) {
669
+ preferences.audioVolume = parsed.audioVolume;
670
+ }
671
+ if (parsed.thumbnailMode === "adaptive" || parsed.thumbnailMode === "hidden") {
672
+ preferences.thumbnailMode = parsed.thumbnailMode;
673
+ } else if (typeof parsed.thumbnailsEnabled === "boolean") {
674
+ preferences.thumbnailMode = parsed.thumbnailsEnabled ? "adaptive" : "hidden";
675
+ }
676
+ if (Array.isArray(parsed.recentBlocks)) {
677
+ preferences.recentBlocks = parsed.recentBlocks.filter(
678
+ (v) => typeof v === "string"
679
+ );
680
+ }
681
+ if (typeof parsed.snapEnabled === "boolean") {
682
+ preferences.snapEnabled = parsed.snapEnabled;
683
+ }
684
+ if (typeof parsed.gridVisible === "boolean") {
685
+ preferences.gridVisible = parsed.gridVisible;
686
+ }
687
+ if (typeof parsed.rulerVisible === "boolean") {
688
+ preferences.rulerVisible = parsed.rulerVisible;
689
+ }
690
+ if (typeof parsed.safeMarginsVisible === "boolean") {
691
+ preferences.safeMarginsVisible = parsed.safeMarginsVisible;
692
+ }
693
+ if (typeof parsed.gridSpacing === "number" && Number.isFinite(parsed.gridSpacing)) {
694
+ preferences.gridSpacing = parsed.gridSpacing;
695
+ }
696
+ if (typeof parsed.snapToGrid === "boolean") {
697
+ preferences.snapToGrid = parsed.snapToGrid;
698
+ }
699
+ if (typeof parsed.timelineSnapEnabled === "boolean") {
700
+ preferences.timelineSnapEnabled = parsed.timelineSnapEnabled;
701
+ }
702
+ if (typeof parsed.audioMetersVisible === "boolean") {
703
+ preferences.audioMetersVisible = parsed.audioMetersVisible;
704
+ }
705
+ if (typeof parsed.rippleEditEnabled === "boolean") {
706
+ preferences.rippleEditEnabled = parsed.rippleEditEnabled;
707
+ }
708
+ if (parsed.timeDisplayMode === "time" || parsed.timeDisplayMode === "frame") {
709
+ preferences.timeDisplayMode = parsed.timeDisplayMode;
710
+ }
711
+ if (parsed.timelineZoomMode === "fit" || parsed.timelineZoomMode === "manual") {
712
+ preferences.timelineZoomMode = parsed.timelineZoomMode;
713
+ }
714
+ if (typeof parsed.timelineManualZoomPercent === "number" && Number.isFinite(parsed.timelineManualZoomPercent)) {
715
+ preferences.timelineManualZoomPercent = parsed.timelineManualZoomPercent;
716
+ }
717
+ if (typeof parsed.agentToolsEnabled === "boolean") {
718
+ preferences.agentToolsEnabled = parsed.agentToolsEnabled;
719
+ }
720
+ const dockLayout = parseDockLayout(parsed.dockLayout);
721
+ if (dockLayout) preferences.dockLayout = dockLayout;
722
+ return preferences;
723
+ } catch {
724
+ return {};
725
+ }
726
+ }
727
+ function readStudioUiPreferences(storage = getBrowserStorage(), projectId = null) {
728
+ const scoped = readStorage(storage, storageKeyFor(projectId));
729
+ if (!projectId || Object.keys(scoped).length > 0) return scoped;
730
+ return readStorage(storage, STUDIO_UI_PREFERENCES_KEY);
731
+ }
732
+ function writeStudioUiPreferences(patch, storage = getBrowserStorage(), projectId = null) {
733
+ if (!storage) return;
734
+ try {
735
+ const next = {
736
+ ...readStudioUiPreferences(storage, projectId),
737
+ ...patch
738
+ };
739
+ storage.setItem(storageKeyFor(projectId), JSON.stringify(next));
740
+ } catch {
741
+ }
742
+ }
743
+
744
+ // src/player/components/timelineZoom.ts
745
+ var MIN_TIMELINE_ZOOM_PERCENT = 10;
746
+ var MAX_TIMELINE_FRAME_WIDTH_PX = 48;
747
+ var ZOOM_OUT_FACTOR = 0.5;
748
+ var ZOOM_IN_FACTOR = 2;
749
+ var PINCH_ZOOM_SENSITIVITY = 0.014;
750
+ function getMaxTimelineZoomPercent(fitPixelsPerSecond) {
751
+ if (!Number.isFinite(fitPixelsPerSecond) || fitPixelsPerSecond <= 0) return 100;
752
+ const frameLevelPixelsPerSecond = STUDIO_PREVIEW_FPS * MAX_TIMELINE_FRAME_WIDTH_PX;
753
+ return Math.max(100, Math.round(frameLevelPixelsPerSecond / fitPixelsPerSecond * 100));
754
+ }
755
+ function clampTimelineZoomPercent(percent, fitPixelsPerSecond) {
756
+ if (!Number.isFinite(percent)) return 100;
757
+ const maxZoomPercent = getMaxTimelineZoomPercent(fitPixelsPerSecond);
758
+ return Math.max(MIN_TIMELINE_ZOOM_PERCENT, Math.min(maxZoomPercent, Math.round(percent)));
759
+ }
760
+ function getTimelineZoomPercent(zoomMode, manualZoomPercent, fitPixelsPerSecond) {
761
+ return zoomMode === "fit" ? 100 : clampTimelineZoomPercent(manualZoomPercent, fitPixelsPerSecond);
762
+ }
763
+ function computePinnedZoomPercent(currentPixelsPerSecond, fitPixelsPerSecond) {
764
+ if (!Number.isFinite(currentPixelsPerSecond) || currentPixelsPerSecond <= 0 || !Number.isFinite(fitPixelsPerSecond) || fitPixelsPerSecond <= 0) {
765
+ return 100;
766
+ }
767
+ return clampTimelineZoomPercent(
768
+ currentPixelsPerSecond / fitPixelsPerSecond * 100,
769
+ fitPixelsPerSecond
770
+ );
771
+ }
772
+ function getTimelinePixelsPerSecond(fitPixelsPerSecond, zoomMode, manualZoomPercent) {
773
+ if (!Number.isFinite(fitPixelsPerSecond) || fitPixelsPerSecond <= 0) return 100;
774
+ const zoomPercent = getTimelineZoomPercent(zoomMode, manualZoomPercent, fitPixelsPerSecond);
775
+ return zoomMode === "fit" ? fitPixelsPerSecond : fitPixelsPerSecond * (zoomPercent / 100);
776
+ }
777
+ function getNextTimelineZoomPercent(direction, zoomMode, manualZoomPercent, fitPixelsPerSecond) {
778
+ const current = getTimelineZoomPercent(zoomMode, manualZoomPercent, fitPixelsPerSecond);
779
+ const next = direction === "in" ? current * ZOOM_IN_FACTOR : current * ZOOM_OUT_FACTOR;
780
+ return clampTimelineZoomPercent(next, fitPixelsPerSecond);
781
+ }
782
+ function getPinchTimelineZoomPercent(deltaY, zoomMode, manualZoomPercent, fitPixelsPerSecond) {
783
+ const current = getTimelineZoomPercent(zoomMode, manualZoomPercent, fitPixelsPerSecond);
784
+ if (!Number.isFinite(deltaY) || deltaY === 0) return current;
785
+ return clampTimelineZoomPercent(
786
+ current * Math.exp(-deltaY * PINCH_ZOOM_SENSITIVITY),
787
+ fitPixelsPerSecond
788
+ );
789
+ }
790
+ var LOG_MIN = Math.log(MIN_TIMELINE_ZOOM_PERCENT);
791
+ function timelineZoomPercentToSlider(percent, fitPixelsPerSecond) {
792
+ const clamped = clampTimelineZoomPercent(percent, fitPixelsPerSecond);
793
+ const logMax = Math.log(getMaxTimelineZoomPercent(fitPixelsPerSecond));
794
+ return (Math.log(clamped) - LOG_MIN) / (logMax - LOG_MIN) * 100;
795
+ }
796
+ function timelineSliderToZoomPercent(slider, fitPixelsPerSecond) {
797
+ const clampedSlider = Math.max(0, Math.min(100, slider));
798
+ const logMax = Math.log(getMaxTimelineZoomPercent(fitPixelsPerSecond));
799
+ const logValue = LOG_MIN + clampedSlider / 100 * (logMax - LOG_MIN);
800
+ return clampTimelineZoomPercent(Math.exp(logValue), fitPixelsPerSecond);
801
+ }
802
+
803
+ // src/player/store/keyframeSlice.ts
804
+ function isRevealedAudioFxRequestCurrent(request, state) {
805
+ return request.projectId === state.timelineProjectId && request.sessionEpoch === state.timelineSessionEpoch;
806
+ }
807
+ function isFocusedEaseRequestCurrent(request, state) {
808
+ return request.projectId === state.timelineProjectId && request.sessionEpoch === state.timelineSessionEpoch && request.elementId === state.selectedElementId;
809
+ }
810
+ function createKeyframeSlice(set, getTimelineSessionIdentity) {
811
+ return {
812
+ selectedKeyframes: /* @__PURE__ */ new Set(),
813
+ toggleSelectedKeyframe: (key) => set((state) => {
814
+ const next = new Set(state.selectedKeyframes);
815
+ if (next.has(key)) next.delete(key);
816
+ else next.add(key);
817
+ return { selectedKeyframes: next };
818
+ }),
819
+ clearSelectedKeyframes: () => set({ selectedKeyframes: /* @__PURE__ */ new Set() }),
820
+ expandedClipIds: /* @__PURE__ */ new Set(),
821
+ toggleClipExpanded: (id) => set((state) => {
822
+ const next = new Set(state.expandedClipIds);
823
+ if (next.has(id)) next.delete(id);
824
+ else next.add(id);
825
+ return { expandedClipIds: next };
826
+ }),
827
+ setClipExpanded: (id, expanded) => set((state) => {
828
+ if (state.expandedClipIds.has(id) === expanded) return state;
829
+ const next = new Set(state.expandedClipIds);
830
+ if (expanded) next.add(id);
831
+ else next.delete(id);
832
+ return { expandedClipIds: next };
833
+ }),
834
+ expandClips: (ids) => set((state) => {
835
+ if (ids.every((id) => state.expandedClipIds.has(id))) return state;
836
+ const next = new Set(state.expandedClipIds);
837
+ for (const id of ids) next.add(id);
838
+ return { expandedClipIds: next };
839
+ }),
840
+ collapsedGroupIds: /* @__PURE__ */ new Set(),
841
+ toggleGroupExpanded: (id) => set((state) => {
842
+ const next = new Set(state.collapsedGroupIds);
843
+ if (next.has(id)) next.delete(id);
844
+ else next.add(id);
845
+ return { collapsedGroupIds: next };
846
+ }),
847
+ expandedLaneOwnerIds: /* @__PURE__ */ new Set(),
848
+ toggleLaneOwnerExpanded: (id) => set((state) => {
849
+ const next = new Set(state.expandedLaneOwnerIds);
850
+ if (next.has(id)) next.delete(id);
851
+ else next.add(id);
852
+ return { expandedLaneOwnerIds: next };
853
+ }),
854
+ focusedEaseSegment: null,
855
+ focusedEaseRequestNonce: 0,
856
+ setFocusedEaseSegment: (target) => set((state) => {
857
+ const nonce = state.focusedEaseRequestNonce + 1;
858
+ const { timelineProjectId, timelineSessionEpoch } = getTimelineSessionIdentity();
859
+ return {
860
+ focusedEaseRequestNonce: nonce,
861
+ focusedEaseSegment: {
862
+ ...target,
863
+ projectId: timelineProjectId,
864
+ sessionEpoch: timelineSessionEpoch,
865
+ nonce
866
+ }
867
+ };
868
+ }),
869
+ clearFocusedEaseSegment: (nonce) => set(
870
+ (state) => state.focusedEaseSegment?.nonce === nonce ? { focusedEaseSegment: null } : state
871
+ ),
872
+ revealedAudioFxTarget: null,
873
+ revealedAudioFxNonce: 0,
874
+ setRevealedAudioFxTarget: (target) => set((state) => {
875
+ const nonce = state.revealedAudioFxNonce + 1;
876
+ const { timelineProjectId, timelineSessionEpoch } = getTimelineSessionIdentity();
877
+ return {
878
+ revealedAudioFxNonce: nonce,
879
+ revealedAudioFxTarget: {
880
+ ...target,
881
+ projectId: timelineProjectId,
882
+ sessionEpoch: timelineSessionEpoch,
883
+ nonce
884
+ }
885
+ };
886
+ }),
887
+ clearRevealedAudioFxTarget: (nonce) => set(
888
+ (state) => state.revealedAudioFxTarget?.nonce === nonce ? { revealedAudioFxTarget: null } : state
889
+ ),
890
+ keyframeCache: /* @__PURE__ */ new Map(),
891
+ setKeyframeCache: (elementId, data) => set((state) => {
892
+ if (data ? state.keyframeCache.get(elementId) === data : !state.keyframeCache.has(elementId))
893
+ return state;
894
+ const next = new Map(state.keyframeCache);
895
+ if (data) next.set(elementId, data);
896
+ else next.delete(elementId);
897
+ return { keyframeCache: next };
898
+ }),
899
+ gsapAnimations: /* @__PURE__ */ new Map(),
900
+ setGsapAnimations: (elementId, animations) => set((state) => {
901
+ if (animations ? state.gsapAnimations.get(elementId) === animations : !state.gsapAnimations.has(elementId))
902
+ return state;
903
+ const next = new Map(state.gsapAnimations);
904
+ if (animations) next.set(elementId, animations);
905
+ else next.delete(elementId);
906
+ return { gsapAnimations: next };
907
+ })
908
+ };
909
+ }
910
+
911
+ // src/player/store/automationSelectionSlice.ts
912
+ function createAutomationSelectionSlice(set) {
913
+ return {
914
+ automationSelection: null,
915
+ setAutomationSelection: (sel) => set({
916
+ automationSelection: {
917
+ ...sel,
918
+ t0: Math.min(sel.t0, sel.t1),
919
+ t1: Math.max(sel.t0, sel.t1),
920
+ v0: Math.min(sel.v0, sel.v1),
921
+ v1: Math.max(sel.v0, sel.v1)
922
+ }
923
+ }),
924
+ clearAutomationSelection: () => set({ automationSelection: null })
925
+ };
926
+ }
927
+
928
+ // src/player/store/editingModeSlice.ts
929
+ function createEditingModeSlice(set) {
930
+ return {
931
+ motionPathArmed: false,
932
+ setMotionPathArmed: (armed) => set({ motionPathArmed: armed }),
933
+ motionPathCreateAvailable: false,
934
+ setMotionPathCreateAvailable: (available) => set({ motionPathCreateAvailable: available }),
935
+ autoKeyframeEnabled: true,
936
+ setAutoKeyframeEnabled: (enabled) => set({ autoKeyframeEnabled: enabled })
937
+ };
938
+ }
939
+
940
+ // src/player/store/timelineFocusState.ts
941
+ function createTimelineFocusRequest(id, projectId, sessionEpoch, nonce) {
942
+ return { id, projectId, sessionEpoch, nonce };
943
+ }
944
+
945
+ // src/player/lib/thumbnailPolicy.ts
946
+ var rawPolicy = import.meta.env?.VITE_STUDIO_TIMELINE_THUMBNAIL_POLICY;
947
+ var studioThumbnailRuntimePolicy = rawPolicy === "force-hidden" || rawPolicy === "legacy-default" ? rawPolicy : "follow-preference";
948
+ function defaultThumbnailMode(storedMode, policy = studioThumbnailRuntimePolicy) {
949
+ return storedMode ?? (policy === "legacy-default" ? "hidden" : "adaptive");
950
+ }
951
+ function effectiveThumbnailMode(preferredMode, policy = studioThumbnailRuntimePolicy) {
952
+ return policy === "force-hidden" ? "hidden" : preferredMode;
953
+ }
954
+
955
+ // src/player/store/thumbnailSlice.ts
956
+ var EVERY_COMPOSITION = "*";
957
+ function revisionKey(path) {
958
+ return (path.split(/[?#]/)[0] ?? "").replace(/\\/g, "/").replace(/^\.?\//, "");
959
+ }
960
+ function thumbnailRevisionOf(revisions, compositionPath) {
961
+ return (revisions[EVERY_COMPOSITION] ?? 0) + (revisions[revisionKey(compositionPath)] ?? 0);
962
+ }
963
+ function createThumbnailSlice(set) {
964
+ return {
965
+ thumbnailMode: defaultThumbnailMode(readStudioUiPreferences().thumbnailMode),
966
+ thumbnailRevisions: {},
967
+ setThumbnailMode: (mode) => {
968
+ writeStudioUiPreferences({ thumbnailMode: mode });
969
+ set({ thumbnailMode: mode });
970
+ },
971
+ bumpThumbnailRevisions: (compositions) => set(({ thumbnailRevisions }) => {
972
+ const next = { ...thumbnailRevisions };
973
+ for (const path of compositions?.map(revisionKey) ?? [EVERY_COMPOSITION]) {
974
+ next[path] = (next[path] ?? 0) + 1;
975
+ }
976
+ return { thumbnailRevisions: next };
977
+ })
978
+ };
979
+ }
980
+
981
+ // src/player/store/readinessSlice.ts
982
+ import { settleCompositionReadiness } from "@hyperframes/core/composition-readiness";
983
+ var timelineReadyGeneration = 0;
984
+ var loadStepDocs = /* @__PURE__ */ new WeakSet();
985
+ var awaitingLoadStep = null;
986
+ function resetPlaybackReadinessState() {
987
+ timelineReadyGeneration++;
988
+ return { timelineReady: false, previewBooted: false };
989
+ }
990
+ function createPlaybackReadinessSlice(set) {
991
+ const settle = (doc, generation) => settleCompositionReadiness(doc, () => {
992
+ if (generation === timelineReadyGeneration) set({ timelineReady: true });
993
+ });
994
+ return {
995
+ timelineReady: false,
996
+ previewBooted: false,
997
+ markPreviewBooted: () => set({ previewBooted: true }),
998
+ setTimelineReady: (ready) => {
999
+ timelineReadyGeneration++;
1000
+ set({ timelineReady: ready });
1001
+ },
1002
+ requestTimelineReady: (doc) => {
1003
+ const generation = ++timelineReadyGeneration;
1004
+ if (!doc) return set({ timelineReady: true });
1005
+ if (!loadStepDocs.has(doc)) {
1006
+ awaitingLoadStep = { doc, generation };
1007
+ return;
1008
+ }
1009
+ settle(doc, generation);
1010
+ },
1011
+ markPreviewLoadStep: (doc) => {
1012
+ loadStepDocs.add(doc);
1013
+ if (awaitingLoadStep?.doc !== doc) return;
1014
+ const { generation } = awaitingLoadStep;
1015
+ awaitingLoadStep = null;
1016
+ settle(doc, generation);
1017
+ }
1018
+ };
1019
+ }
1020
+
1021
+ // src/player/store/rangeSelectionSlice.ts
1022
+ function createRangeSelectionSlice() {
1023
+ return { rangeSelection: null };
1024
+ }
1025
+
1026
+ // src/player/store/timelineResetState.ts
1027
+ function createTimelineResetState() {
1028
+ return {
1029
+ isPlaying: false,
1030
+ currentTime: 0,
1031
+ duration: 0,
1032
+ ...resetPlaybackReadinessState(),
1033
+ beatDragging: false,
1034
+ elements: [],
1035
+ selectedElementId: null,
1036
+ zEditVersion: 0,
1037
+ inPoint: null,
1038
+ outPoint: null,
1039
+ rangeSelection: null,
1040
+ activeTool: "select",
1041
+ activeKeyframePct: null,
1042
+ motionPathArmed: false,
1043
+ motionPathCreateAvailable: false,
1044
+ selectedKeyframes: /* @__PURE__ */ new Set(),
1045
+ // Ephemeral like every other selection here. A range surviving a project
1046
+ // switch can match a same-keyed clip in the new project and redirect a
1047
+ // paste through `sel.elementKey === paste.elementKey` to a stale t0.
1048
+ automationSelection: null,
1049
+ expandedClipIds: /* @__PURE__ */ new Set(),
1050
+ // Per-composition: ids from comp A match nothing in B, silencing all of it.
1051
+ collapsedGroupIds: /* @__PURE__ */ new Set(),
1052
+ expandedLaneOwnerIds: /* @__PURE__ */ new Set(),
1053
+ focusedEaseSegment: null,
1054
+ revealedAudioFxTarget: null,
1055
+ selectedElementIds: /* @__PURE__ */ new Set(),
1056
+ requestedSeekTime: null,
1057
+ lintFindingsByElement: /* @__PURE__ */ new Map(),
1058
+ timelineFocus: null,
1059
+ keyframeCache: /* @__PURE__ */ new Map(),
1060
+ gsapAnimations: /* @__PURE__ */ new Map(),
1061
+ beatAnalysis: null,
1062
+ beatEdits: null,
1063
+ beatUndo: [],
1064
+ beatRedo: [],
1065
+ beatPersist: null,
1066
+ clipManifest: null,
1067
+ clipParentMap: /* @__PURE__ */ new Map(),
1068
+ domClipChildren: [],
1069
+ subCompositionHostState: /* @__PURE__ */ new Map()
1070
+ };
1071
+ }
1072
+
1073
+ // src/player/store/playerStore.ts
1074
+ function resolveElementSelection(ids, anchor) {
1075
+ const selectedElementIds = new Set(ids);
1076
+ if (selectedElementIds.size === 0) {
1077
+ return { selectedElementIds, selectedElementId: null };
1078
+ }
1079
+ if (anchor && selectedElementIds.has(anchor)) {
1080
+ return { selectedElementIds, selectedElementId: anchor };
1081
+ }
1082
+ return {
1083
+ selectedElementIds,
1084
+ selectedElementId: selectedElementIds.values().next().value ?? null
1085
+ };
1086
+ }
1087
+ var usePlayerStore = create((set, get) => ({
1088
+ isPlaying: false,
1089
+ currentTime: 0,
1090
+ duration: 0,
1091
+ timelineSessionEpoch: 0,
1092
+ timelineProjectId: null,
1093
+ beatDragging: false,
1094
+ elements: [],
1095
+ selectedElementId: null,
1096
+ playbackRate: readStudioUiPreferences().playbackRate ?? 1,
1097
+ audioMuted: readStudioUiPreferences().audioMuted ?? false,
1098
+ audioVolume: readStudioUiPreferences().audioVolume ?? 1,
1099
+ loopEnabled: false,
1100
+ zoomMode: "fit",
1101
+ manualZoomPercent: 100,
1102
+ zEditVersion: 0,
1103
+ timelinePps: 100,
1104
+ timelineFitPps: 100,
1105
+ inPoint: null,
1106
+ outPoint: null,
1107
+ activeTool: "select",
1108
+ setActiveTool: (tool) => set({ activeTool: tool }),
1109
+ ...createKeyframeSlice(set, () => ({
1110
+ timelineProjectId: get().timelineProjectId,
1111
+ timelineSessionEpoch: get().timelineSessionEpoch
1112
+ })),
1113
+ ...createThumbnailSlice(set),
1114
+ ...createAutomationSelectionSlice(set),
1115
+ ...createEditingModeSlice(set),
1116
+ ...createRangeSelectionSlice(),
1117
+ ...createPlaybackReadinessSlice(set),
1118
+ activeKeyframePct: null,
1119
+ setActiveKeyframePct: (pct) => set({ activeKeyframePct: pct }),
1120
+ selectedElementIds: /* @__PURE__ */ new Set(),
1121
+ setSelection: (ids, anchor) => set(resolveElementSelection(ids, anchor)),
1122
+ addSelectedElementId: (id) => set((s) => {
1123
+ const next = new Set(s.selectedElementIds);
1124
+ next.add(id);
1125
+ return resolveElementSelection(next, s.selectedElementId);
1126
+ }),
1127
+ toggleSelectedElementId: (id) => set((s) => {
1128
+ const next = new Set(s.selectedElementIds);
1129
+ if (next.has(id)) next.delete(id);
1130
+ else next.add(id);
1131
+ return resolveElementSelection(next, s.selectedElementId);
1132
+ }),
1133
+ clearSelection: () => set({ selectedElementId: null, selectedElementIds: /* @__PURE__ */ new Set() }),
1134
+ requestedSeekTime: null,
1135
+ requestSeek: (time) => set({ requestedSeekTime: time }),
1136
+ clearSeekRequest: () => set({ requestedSeekTime: null }),
1137
+ playbackRequest: null,
1138
+ requestPlayback: (playing, returnTo = null) => set((s) => ({
1139
+ playbackRequest: { playing, returnTo, nonce: (s.playbackRequest?.nonce ?? 0) + 1 }
1140
+ })),
1141
+ clearPlaybackRequest: () => set({ playbackRequest: null }),
1142
+ clipRevealRequest: null,
1143
+ requestClipReveal: (elementId) => set((s) => ({
1144
+ clipRevealRequest: { elementId, nonce: (s.clipRevealRequest?.nonce ?? 0) + 1 }
1145
+ })),
1146
+ clearClipRevealRequest: () => set({ clipRevealRequest: null }),
1147
+ timelineFocus: null,
1148
+ timelineFocusNonce: 0,
1149
+ requestTimelineFocus: (id) => set((s) => {
1150
+ const nonce = s.timelineFocusNonce + 1;
1151
+ return {
1152
+ timelineFocusNonce: nonce,
1153
+ timelineFocus: createTimelineFocusRequest(
1154
+ id,
1155
+ s.timelineProjectId,
1156
+ s.timelineSessionEpoch,
1157
+ nonce
1158
+ )
1159
+ };
1160
+ }),
1161
+ clearTimelineFocus: (nonce) => set((s) => s.timelineFocus?.nonce === nonce ? { timelineFocus: null } : s),
1162
+ lintFindingsByElement: /* @__PURE__ */ new Map(),
1163
+ setLintFindingsByElement: (map) => set({ lintFindingsByElement: map }),
1164
+ beatAnalysis: null,
1165
+ setBeatAnalysis: (analysis) => set({ beatAnalysis: analysis }),
1166
+ beatEdits: null,
1167
+ setBeatEdits: (edits) => set({ beatEdits: edits }),
1168
+ beatUndo: [],
1169
+ beatRedo: [],
1170
+ beatPersist: null,
1171
+ setBeatPersist: (fn) => set({ beatPersist: fn }),
1172
+ commitBeatEdits: (next, label) => {
1173
+ set((s) => ({
1174
+ beatEdits: next,
1175
+ beatUndo: [...s.beatUndo, { restore: s.beatEdits, at: Date.now(), label }],
1176
+ beatRedo: []
1177
+ }));
1178
+ get().beatPersist?.();
1179
+ },
1180
+ undoBeatEdits: () => {
1181
+ const s = get();
1182
+ const entry = s.beatUndo[s.beatUndo.length - 1];
1183
+ if (!entry) return null;
1184
+ set({
1185
+ beatEdits: entry.restore,
1186
+ beatUndo: s.beatUndo.slice(0, -1),
1187
+ beatRedo: [...s.beatRedo, { restore: s.beatEdits, at: entry.at, label: entry.label }]
1188
+ });
1189
+ get().beatPersist?.();
1190
+ return entry.label;
1191
+ },
1192
+ resetBeatHistory: () => set({ beatUndo: [], beatRedo: [] }),
1193
+ redoBeatEdits: () => {
1194
+ const s = get();
1195
+ const entry = s.beatRedo[s.beatRedo.length - 1];
1196
+ if (!entry) return null;
1197
+ set({
1198
+ beatEdits: entry.restore,
1199
+ beatRedo: s.beatRedo.slice(0, -1),
1200
+ beatUndo: [...s.beatUndo, { restore: s.beatEdits, at: entry.at, label: entry.label }]
1201
+ });
1202
+ get().beatPersist?.();
1203
+ return entry.label;
1204
+ },
1205
+ clipManifest: null,
1206
+ setClipManifest: (clips) => set({ clipManifest: clips }),
1207
+ clipParentMap: /* @__PURE__ */ new Map(),
1208
+ setClipParentMap: (map) => set({ clipParentMap: map }),
1209
+ domClipChildren: [],
1210
+ setDomClipChildren: (children) => set({ domClipChildren: children }),
1211
+ subCompositionHostState: /* @__PURE__ */ new Map(),
1212
+ setSubCompositionHostState: (state) => set({ subCompositionHostState: state }),
1213
+ setIsPlaying: (playing) => {
1214
+ if (get().isPlaying === playing) return;
1215
+ set({ isPlaying: playing });
1216
+ },
1217
+ setPlaybackRate: (rate) => {
1218
+ writeStudioUiPreferences({ playbackRate: rate });
1219
+ set({ playbackRate: rate });
1220
+ },
1221
+ setAudioMuted: (muted) => {
1222
+ writeStudioUiPreferences({ audioMuted: muted });
1223
+ set({ audioMuted: muted });
1224
+ },
1225
+ setAudioVolume: (volume) => {
1226
+ const nextVolume = Number.isFinite(volume) ? Math.max(0, Math.min(1, volume)) : 1;
1227
+ writeStudioUiPreferences({ audioVolume: nextVolume });
1228
+ set({ audioVolume: nextVolume });
1229
+ },
1230
+ setLoopEnabled: (enabled) => set({ loopEnabled: enabled }),
1231
+ setZoomMode: (mode) => set({ zoomMode: mode }),
1232
+ clearSelectedElementIds: () => set({ selectedElementIds: /* @__PURE__ */ new Set() }),
1233
+ setSelectedElementIds: (ids) => set({ selectedElementIds: new Set(ids) }),
1234
+ timelineSnapEnabled: readStudioUiPreferences().timelineSnapEnabled ?? true,
1235
+ setTimelineSnapEnabled: (enabled) => {
1236
+ writeStudioUiPreferences({ timelineSnapEnabled: enabled });
1237
+ set({ timelineSnapEnabled: enabled });
1238
+ },
1239
+ rippleEditEnabled: readStudioUiPreferences().rippleEditEnabled ?? true,
1240
+ // default on
1241
+ setRippleEditEnabled: (enabled) => {
1242
+ writeStudioUiPreferences({ rippleEditEnabled: enabled });
1243
+ set({ rippleEditEnabled: enabled });
1244
+ },
1245
+ timeDisplayMode: readStudioUiPreferences().timeDisplayMode ?? "time",
1246
+ setTimeDisplayMode: (mode) => {
1247
+ writeStudioUiPreferences({ timeDisplayMode: mode });
1248
+ set({ timeDisplayMode: mode });
1249
+ },
1250
+ pinTimelineZoom: (currentPixelsPerSecond, fitPixelsPerSecond) => set((s) => {
1251
+ if (s.zoomMode === "manual") return {};
1252
+ const percent = computePinnedZoomPercent(currentPixelsPerSecond, fitPixelsPerSecond);
1253
+ writeStudioUiPreferences({
1254
+ timelineZoomMode: "manual",
1255
+ timelineManualZoomPercent: percent
1256
+ });
1257
+ return { zoomMode: "manual", manualZoomPercent: percent };
1258
+ }),
1259
+ setTimelineScale: (pps, fitPps) => {
1260
+ const state = get();
1261
+ if (state.timelinePps === pps && state.timelineFitPps === fitPps) return;
1262
+ set({ timelinePps: pps, timelineFitPps: fitPps });
1263
+ },
1264
+ setInPoint: (time) => set((state) => {
1265
+ const t = time !== null && Number.isFinite(time) ? time : null;
1266
+ return {
1267
+ inPoint: t,
1268
+ outPoint: t !== null && state.outPoint !== null && t >= state.outPoint ? null : state.outPoint,
1269
+ // Setting a work-area marker implies the user wants playback bounded by it.
1270
+ // Auto-enable loop so the playhead respects the marker instead of running past.
1271
+ loopEnabled: t !== null ? true : state.loopEnabled
1272
+ };
1273
+ }),
1274
+ setOutPoint: (time) => set((state) => {
1275
+ const t = time !== null && Number.isFinite(time) ? time : null;
1276
+ return {
1277
+ outPoint: t,
1278
+ inPoint: t !== null && state.inPoint !== null && t <= state.inPoint ? null : state.inPoint,
1279
+ loopEnabled: t !== null ? true : state.loopEnabled
1280
+ };
1281
+ }),
1282
+ setManualZoomPercent: (percent) => set((state) => ({
1283
+ manualZoomPercent: clampTimelineZoomPercent(percent, state.timelineFitPps)
1284
+ })),
1285
+ bumpZEditVersion: () => set((state) => ({ zEditVersion: state.zEditVersion + 1 })),
1286
+ setCurrentTime: (time) => set({ currentTime: Number.isFinite(time) ? time : 0 }),
1287
+ setDuration: (duration) => set({ duration: Number.isFinite(duration) ? duration : 0 }),
1288
+ setBeatDragging: (dragging) => set({ beatDragging: dragging }),
1289
+ setElements: (elements) => set({ elements }),
1290
+ // A genuine single selection: always collapse the set to just this element. User
1291
+ // intent (timeline click, preview click via applyDomSelection) flows here; DOM sync
1292
+ // echoes that must preserve a group go through setSelectionAnchor instead.
1293
+ setSelectedElementId: (id, options) => set((s) => {
1294
+ const selectedElementIds = nextSelectionSet(s.selectedElementIds, id, options?.preserveSet);
1295
+ const revealSurvives = revealTargetsSelection(s.revealedAudioFxTarget, id);
1296
+ return id !== s.selectedElementId ? {
1297
+ selectedElementId: id,
1298
+ selectedElementIds,
1299
+ activeKeyframePct: null,
1300
+ motionPathArmed: false,
1301
+ focusedEaseSegment: null,
1302
+ ...revealSurvives ? {} : { revealedAudioFxTarget: null }
1303
+ } : { selectedElementId: id, selectedElementIds };
1304
+ }),
1305
+ // Move the anchor within an active multi-selection WITHOUT collapsing it — used by
1306
+ // DOM->store sync echoes while a group gesture re-patches the preview. A non-member
1307
+ // id is treated as a genuine new single selection.
1308
+ setSelectionAnchor: (id) => set((s) => {
1309
+ if (id != null && s.selectedElementIds.size > 1 && s.selectedElementIds.has(id)) {
1310
+ return {
1311
+ selectedElementId: id,
1312
+ focusedEaseSegment: id === s.selectedElementId ? s.focusedEaseSegment : null
1313
+ };
1314
+ }
1315
+ return {
1316
+ selectedElementId: id,
1317
+ selectedElementIds: id ? /* @__PURE__ */ new Set([id]) : /* @__PURE__ */ new Set(),
1318
+ focusedEaseSegment: id === s.selectedElementId ? s.focusedEaseSegment : null
1319
+ };
1320
+ }),
1321
+ updateElement: (elementId, updates) => set((state) => ({
1322
+ elements: state.elements.map(
1323
+ (el) => (el.key ?? el.id) === elementId ? { ...el, ...updates } : el
1324
+ )
1325
+ })),
1326
+ // UI preferences intentionally survive reset. So do timelineSessionEpoch and
1327
+ // focusedEaseRequestNonce: the epoch advances only when project identity
1328
+ // changes, while a monotonic nonce prevents collisions with stale consumers.
1329
+ beginTimelineSession: (projectId) => set((state) => {
1330
+ if (state.timelineProjectId === projectId) return state;
1331
+ return {
1332
+ ...createTimelineResetState(),
1333
+ timelineSessionEpoch: state.timelineSessionEpoch + 1,
1334
+ timelineProjectId: projectId
1335
+ };
1336
+ }),
1337
+ reset: () => set(createTimelineResetState())
1338
+ }));
1339
+ attachPlayerStoreDevHandle(usePlayerStore);
1340
+ function isPreviewBooted(projectId) {
1341
+ const { previewBooted, timelineProjectId } = usePlayerStore.getState();
1342
+ return previewBooted && timelineProjectId === projectId;
1343
+ }
1344
+ function whenPreviewBooted(projectId) {
1345
+ const openedFrom = usePlayerStore.getState().timelineProjectId;
1346
+ let seen = false;
1347
+ const settle = (state) => {
1348
+ if (state.timelineProjectId === projectId) {
1349
+ seen = true;
1350
+ return state.previewBooted ? true : null;
1351
+ }
1352
+ return seen || state.timelineProjectId !== openedFrom ? false : null;
1353
+ };
1354
+ return new Promise((resolve) => {
1355
+ const now = settle(usePlayerStore.getState());
1356
+ if (now !== null) return resolve(now);
1357
+ const stop = usePlayerStore.subscribe((state) => {
1358
+ const result = settle(state);
1359
+ if (result === null) return;
1360
+ stop();
1361
+ resolve(result);
1362
+ });
1363
+ });
1364
+ }
1365
+
1366
+ // src/player/components/Player.tsx
1367
+ import { forwardRef, useEffect as useEffect2, useRef, useState } from "react";
1368
+ import { isLottieAnimationLoaded } from "@hyperframes/core/runtime/lottie-readiness";
1369
+
1370
+ // src/hooks/useMountEffect.ts
1371
+ import { useEffect } from "react";
1372
+ function useMountEffect(effect) {
1373
+ useEffect(effect, []);
1374
+ }
1375
+
1376
+ // src/hooks/previewVariablesStore.ts
1377
+ import { create as create2 } from "zustand";
1378
+ var usePreviewVariablesStore = create2((set) => ({
1379
+ values: null,
1380
+ setValues: (values) => set({ values: values && Object.keys(values).length > 0 ? values : null })
1381
+ }));
1382
+ function applyPreviewVariablesToUrl(url) {
1383
+ const values = usePreviewVariablesStore.getState().values;
1384
+ if (values) {
1385
+ url.searchParams.set("variables", JSON.stringify(values));
1386
+ } else {
1387
+ url.searchParams.delete("variables");
1388
+ }
1389
+ }
1390
+
1391
+ // src/player/components/Player.tsx
1392
+ import { jsx, jsxs } from "react/jsx-runtime";
1393
+ var playerModule = null;
1394
+ function loadPlayerModule() {
1395
+ playerModule ??= import("@hyperframes/player").catch((err) => {
1396
+ playerModule = null;
1397
+ throw err;
1398
+ });
1399
+ return playerModule;
1400
+ }
1401
+ if (typeof window !== "undefined") void loadPlayerModule().catch(() => {
1402
+ });
1403
+ var MEDIA_HAVE_FUTURE_DATA = 3;
1404
+ var MEDIA_NETWORK_NO_SOURCE = 3;
1405
+ function isRecord3(value) {
1406
+ return typeof value === "object" && value !== null;
1407
+ }
1408
+ function getShaderTransitionLoading(event) {
1409
+ if (!(event instanceof CustomEvent)) return null;
1410
+ const detail = event.detail;
1411
+ if (!isRecord3(detail)) return null;
1412
+ const state = detail.state;
1413
+ if (!isRecord3(state)) return null;
1414
+ return state.loading === true && state.ready !== true;
1415
+ }
1416
+ var COMPOSITION_LOADING_OVERLAY_DELAY_MS = 400;
1417
+ var PREVIEW_BOOT_DEADLINE_MS = 5e3;
1418
+ var DEFAULT_PREVIEW_ERROR = "The composition preview did not become ready.";
1419
+ function shouldShowCompositionLoadingOverlay(compositionLoading) {
1420
+ return compositionLoading;
1421
+ }
1422
+ function readPreviewErrorMessage(event) {
1423
+ if (!(event instanceof CustomEvent) || !isRecord3(event.detail)) return DEFAULT_PREVIEW_ERROR;
1424
+ return typeof event.detail.message === "string" && event.detail.message.trim() ? event.detail.message : DEFAULT_PREVIEW_ERROR;
1425
+ }
1426
+ function enableInteractiveIframe(player) {
1427
+ const root = player.shadowRoot;
1428
+ if (!root) return;
1429
+ const container = root.querySelector(".hfp-container");
1430
+ const iframe = root.querySelector(".hfp-iframe");
1431
+ container?.style.setProperty("pointer-events", "auto");
1432
+ iframe?.style.setProperty("pointer-events", "auto");
1433
+ }
1434
+ function isPreviewMediaElement(el) {
1435
+ const tagName = el.tagName.toLowerCase();
1436
+ return tagName === "video" || tagName === "audio";
1437
+ }
1438
+ function hasUnloadedAssets(iframe, lastResult) {
1439
+ try {
1440
+ const win = iframe.contentWindow;
1441
+ const doc = iframe.contentDocument;
1442
+ if (!win || !doc) return lastResult;
1443
+ for (const el of doc.querySelectorAll("video, audio")) {
1444
+ if (isPreviewMediaElement(el) && !el.error && el.networkState !== MEDIA_NETWORK_NO_SOURCE && el.readyState < MEDIA_HAVE_FUTURE_DATA) {
1445
+ return true;
1446
+ }
1447
+ }
1448
+ const lotties = win.__hfLottie;
1449
+ if (lotties?.length) {
1450
+ for (const anim of lotties) {
1451
+ if (!isLottieAnimationLoaded(anim)) return true;
1452
+ }
1453
+ }
1454
+ return false;
1455
+ } catch {
1456
+ return lastResult;
1457
+ }
1458
+ }
1459
+ var Player = forwardRef(
1460
+ ({
1461
+ projectId,
1462
+ directUrl,
1463
+ onLoad,
1464
+ onReadyToShowChange,
1465
+ onPreviewError,
1466
+ onCompositionLoadingChange,
1467
+ onPainted,
1468
+ portrait,
1469
+ style,
1470
+ suppressLoadingOverlay
1471
+ }, ref) => {
1472
+ const containerRef = useRef(null);
1473
+ const loadCountRef = useRef(0);
1474
+ const assetPollRef = useRef(null);
1475
+ const assetFadeRef = useRef(null);
1476
+ const retryPreviewRef = useRef(null);
1477
+ const loadStartedAtRef = useRef(0);
1478
+ const loadIdRef = useRef(0);
1479
+ const retryCountRef = useRef(0);
1480
+ const onLoadRef = useRef(onLoad);
1481
+ onLoadRef.current = onLoad;
1482
+ const onReadyToShowChangeRef = useRef(onReadyToShowChange);
1483
+ onReadyToShowChangeRef.current = onReadyToShowChange;
1484
+ const onPreviewErrorRef = useRef(onPreviewError);
1485
+ onPreviewErrorRef.current = onPreviewError;
1486
+ const [loaded, setLoaded] = useState(false);
1487
+ const [assetsLoading, setAssetsLoading] = useState(false);
1488
+ const [assetOverlayVisible, setAssetOverlayVisible] = useState(false);
1489
+ const [assetOverlayFading, setAssetOverlayFading] = useState(false);
1490
+ const [assetWaitLong, setAssetWaitLong] = useState(false);
1491
+ const [shaderTransitionLoading, setShaderTransitionLoading] = useState(false);
1492
+ const [compositionLoading, setCompositionLoading] = useState(true);
1493
+ const [painted, setPainted] = useState(false);
1494
+ const [compositionOverlayDeferred, setCompositionOverlayDeferred] = useState(true);
1495
+ const [previewError, setPreviewError] = useState(null);
1496
+ useEffect2(() => {
1497
+ if (!compositionLoading) {
1498
+ setCompositionOverlayDeferred(true);
1499
+ return;
1500
+ }
1501
+ const timer = setTimeout(
1502
+ () => setCompositionOverlayDeferred(false),
1503
+ COMPOSITION_LOADING_OVERLAY_DELAY_MS
1504
+ );
1505
+ return () => clearTimeout(timer);
1506
+ }, [compositionLoading]);
1507
+ useMountEffect(() => {
1508
+ const container = containerRef.current;
1509
+ if (!container) return;
1510
+ const previewSource = directUrl || (projectId ? buildProjectApiPath(projectId, "/preview") : null);
1511
+ if (!previewSource) return;
1512
+ let canceled = false;
1513
+ let cleanup;
1514
+ void loadPlayerModule().then(() => {
1515
+ if (canceled) return;
1516
+ const player = document.createElement("hyperframes-player");
1517
+ const srcUrl = new URL(previewSource, window.location.origin);
1518
+ applyPreviewVariablesToUrl(srcUrl);
1519
+ const src = srcUrl.pathname + srcUrl.search;
1520
+ const retryPreview = () => {
1521
+ retryCountRef.current += 1;
1522
+ const retryUrl = new URL(src, window.location.origin);
1523
+ retryUrl.searchParams.set("_hfStudioRetry", String(retryCountRef.current));
1524
+ setPreviewError(null);
1525
+ setCompositionLoading(true);
1526
+ loadStartedAtRef.current = performance.now();
1527
+ loadIdRef.current += 1;
1528
+ player.setAttribute("src", retryUrl.pathname + retryUrl.search);
1529
+ };
1530
+ retryPreviewRef.current = retryPreview;
1531
+ const iframe = player.iframeElement;
1532
+ const preventToggle = (e) => e.stopImmediatePropagation();
1533
+ const handleShaderTransitionState = (event) => {
1534
+ const loading = getShaderTransitionLoading(event);
1535
+ if (loading !== null) setShaderTransitionLoading(loading);
1536
+ };
1537
+ const handleReady = () => {
1538
+ setPreviewError(null);
1539
+ setCompositionLoading(false);
1540
+ };
1541
+ const handlePainted = () => {
1542
+ setPainted(true);
1543
+ onPainted?.({ iframe, startedAt: loadStartedAtRef.current, loadId: loadIdRef.current });
1544
+ };
1545
+ const handleError = (event) => {
1546
+ const message = readPreviewErrorMessage(event);
1547
+ onPreviewErrorRef.current?.(message);
1548
+ setPreviewError(message);
1549
+ setCompositionLoading(false);
1550
+ };
1551
+ const handleLoad = () => {
1552
+ loadCountRef.current++;
1553
+ setLoaded(true);
1554
+ setPreviewError(null);
1555
+ setShaderTransitionLoading(false);
1556
+ setCompositionLoading(!player.ready);
1557
+ setPainted(Boolean(player.painted));
1558
+ if (loadCountRef.current > 1) {
1559
+ container.classList.remove("preview-revealing");
1560
+ void container.offsetWidth;
1561
+ container.classList.add("preview-revealing");
1562
+ const onEnd = () => container.classList.remove("preview-revealing");
1563
+ container.addEventListener("animationend", onEnd, { once: true });
1564
+ }
1565
+ onLoadRef.current();
1566
+ if (assetPollRef.current) clearInterval(assetPollRef.current);
1567
+ const isContentRefresh = loadCountRef.current > 1;
1568
+ let lastUnloaded = isContentRefresh ? false : hasUnloadedAssets(iframe, false);
1569
+ if (lastUnloaded) {
1570
+ setAssetsLoading(true);
1571
+ let attempts = 0;
1572
+ assetPollRef.current = setInterval(() => {
1573
+ attempts += 1;
1574
+ lastUnloaded = hasUnloadedAssets(iframe, lastUnloaded);
1575
+ if (!lastUnloaded || attempts > 100) {
1576
+ if (lastUnloaded && attempts > 100) {
1577
+ console.debug(
1578
+ "[studio] asset readiness poll hit the 10s cap \u2014 continuing with unloaded assets"
1579
+ );
1580
+ }
1581
+ if (assetPollRef.current) clearInterval(assetPollRef.current);
1582
+ assetPollRef.current = null;
1583
+ setAssetsLoading(false);
1584
+ }
1585
+ }, 100);
1586
+ } else {
1587
+ setAssetsLoading(false);
1588
+ }
1589
+ };
1590
+ iframe.addEventListener("load", handleLoad);
1591
+ player.addEventListener("click", preventToggle, { capture: true });
1592
+ player.addEventListener("shadertransitionstate", handleShaderTransitionState);
1593
+ player.addEventListener("ready", handleReady);
1594
+ player.addEventListener("painted", handlePainted);
1595
+ player.addEventListener("error", handleError);
1596
+ if (typeof ref === "function") {
1597
+ ref(iframe);
1598
+ } else if (ref) {
1599
+ ref.current = iframe;
1600
+ }
1601
+ player.setAttribute("shader-capture-scale", "1");
1602
+ player.setAttribute("shader-loading", "player");
1603
+ player.setAttribute("low-power-idle", "");
1604
+ player.setAttribute("width", String(portrait ? 1080 : 1920));
1605
+ player.setAttribute("height", String(portrait ? 1920 : 1080));
1606
+ player.style.width = "100%";
1607
+ player.style.height = "100%";
1608
+ player.style.display = "block";
1609
+ player.style.background = "transparent";
1610
+ loadStartedAtRef.current = performance.now();
1611
+ loadIdRef.current += 1;
1612
+ player.setAttribute("src", src);
1613
+ container.appendChild(player);
1614
+ if (player.shadowRoot) {
1615
+ const pasteboardStyle = document.createElement("style");
1616
+ pasteboardStyle.textContent = ".hfp-container{overflow:visible}.hfp-iframe{box-shadow:0 0 0 1px rgba(255,255,255,0.08),0 4px 32px rgba(0,0,0,.7)}";
1617
+ player.shadowRoot.appendChild(pasteboardStyle);
1618
+ }
1619
+ enableInteractiveIframe(player);
1620
+ cleanup = () => {
1621
+ iframe.removeEventListener("load", handleLoad);
1622
+ player.removeEventListener("click", preventToggle, { capture: true });
1623
+ player.removeEventListener("shadertransitionstate", handleShaderTransitionState);
1624
+ player.removeEventListener("ready", handleReady);
1625
+ player.removeEventListener("painted", handlePainted);
1626
+ player.removeEventListener("error", handleError);
1627
+ if (assetPollRef.current) clearInterval(assetPollRef.current);
1628
+ assetPollRef.current = null;
1629
+ player.remove();
1630
+ if (retryPreviewRef.current === retryPreview) retryPreviewRef.current = null;
1631
+ if (typeof ref === "function") {
1632
+ } else if (ref) {
1633
+ const mutableRef = ref;
1634
+ if (mutableRef.current === iframe) {
1635
+ mutableRef.current = null;
1636
+ }
1637
+ }
1638
+ };
1639
+ });
1640
+ return () => {
1641
+ canceled = true;
1642
+ cleanup?.();
1643
+ };
1644
+ });
1645
+ useEffect2(() => {
1646
+ if (!assetsLoading) {
1647
+ setAssetWaitLong(false);
1648
+ return;
1649
+ }
1650
+ const timer = setTimeout(() => setAssetWaitLong(true), 3e3);
1651
+ return () => clearTimeout(timer);
1652
+ }, [assetsLoading]);
1653
+ useEffect2(() => {
1654
+ if (assetFadeRef.current) {
1655
+ clearTimeout(assetFadeRef.current);
1656
+ assetFadeRef.current = null;
1657
+ }
1658
+ if (assetsLoading) {
1659
+ setAssetOverlayVisible(true);
1660
+ setAssetOverlayFading(false);
1661
+ return;
1662
+ }
1663
+ setAssetOverlayFading(true);
1664
+ assetFadeRef.current = setTimeout(() => {
1665
+ setAssetOverlayVisible(false);
1666
+ setAssetOverlayFading(false);
1667
+ assetFadeRef.current = null;
1668
+ }, 240);
1669
+ return () => {
1670
+ if (assetFadeRef.current) {
1671
+ clearTimeout(assetFadeRef.current);
1672
+ assetFadeRef.current = null;
1673
+ }
1674
+ };
1675
+ }, [assetsLoading]);
1676
+ const handleContinueAnyway = () => {
1677
+ if (assetPollRef.current) {
1678
+ clearInterval(assetPollRef.current);
1679
+ assetPollRef.current = null;
1680
+ }
1681
+ setAssetsLoading(false);
1682
+ };
1683
+ const firstFrameShown = loaded && painted && !compositionLoading && !shaderTransitionLoading && !previewError;
1684
+ const readyToShow = firstFrameShown && !assetsLoading;
1685
+ useEffect2(() => {
1686
+ if (firstFrameShown) usePlayerStore.getState().markPreviewBooted();
1687
+ }, [firstFrameShown]);
1688
+ useEffect2(() => {
1689
+ if (!readyToShow) {
1690
+ onReadyToShowChangeRef.current?.(false);
1691
+ return;
1692
+ }
1693
+ let second = 0;
1694
+ const first = requestAnimationFrame(() => {
1695
+ second = requestAnimationFrame(() => onReadyToShowChangeRef.current?.(true));
1696
+ });
1697
+ return () => {
1698
+ cancelAnimationFrame(first);
1699
+ cancelAnimationFrame(second);
1700
+ };
1701
+ }, [readyToShow]);
1702
+ useEffect2(() => {
1703
+ if (previewError) usePlayerStore.getState().markPreviewBooted();
1704
+ }, [previewError]);
1705
+ useEffect2(() => {
1706
+ const timer = setTimeout(
1707
+ () => usePlayerStore.getState().markPreviewBooted(),
1708
+ PREVIEW_BOOT_DEADLINE_MS
1709
+ );
1710
+ return () => clearTimeout(timer);
1711
+ }, [projectId]);
1712
+ const showCompositionOverlay = !suppressLoadingOverlay && !compositionOverlayDeferred && shouldShowCompositionLoadingOverlay(compositionLoading);
1713
+ const showAssetOverlay = assetOverlayVisible && !shaderTransitionLoading && !showCompositionOverlay && !previewError;
1714
+ useEffect2(() => {
1715
+ onCompositionLoadingChange?.(showCompositionOverlay || showAssetOverlay);
1716
+ }, [onCompositionLoadingChange, showCompositionOverlay, showAssetOverlay]);
1717
+ return /* @__PURE__ */ jsxs(
1718
+ "div",
1719
+ {
1720
+ className: "relative w-full h-full max-w-full max-h-full overflow-hidden flex items-center justify-center",
1721
+ style,
1722
+ children: [
1723
+ /* @__PURE__ */ jsx("div", { ref: containerRef, className: "w-full h-full" }),
1724
+ showCompositionOverlay && /* @__PURE__ */ jsx(
1725
+ "div",
1726
+ {
1727
+ className: "absolute inset-0 bg-black flex items-center justify-center z-30 select-none",
1728
+ "data-hyperframes-ignore": "",
1729
+ "data-testid": "composition-loading-overlay",
1730
+ draggable: false,
1731
+ onDragStart: (event) => event.preventDefault(),
1732
+ onMouseDown: (event) => event.preventDefault(),
1733
+ onPointerDown: (event) => event.preventDefault(),
1734
+ children: /* @__PURE__ */ jsx(
1735
+ HyperframesLoader,
1736
+ {
1737
+ title: "Loading composition",
1738
+ detail: "Preparing the Studio preview.",
1739
+ size: 56
1740
+ }
1741
+ )
1742
+ }
1743
+ ),
1744
+ showAssetOverlay && /* @__PURE__ */ jsx(
1745
+ "div",
1746
+ {
1747
+ className: "absolute inset-0 bg-black flex items-center justify-center z-20 select-none",
1748
+ "data-hyperframes-ignore": "",
1749
+ draggable: false,
1750
+ style: {
1751
+ opacity: assetOverlayFading ? 0 : 1,
1752
+ pointerEvents: assetOverlayFading ? "none" : "auto",
1753
+ transition: "opacity 180ms ease-in"
1754
+ },
1755
+ onDragStart: (event) => event.preventDefault(),
1756
+ onMouseDown: (event) => event.preventDefault(),
1757
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
1758
+ /* @__PURE__ */ jsx(
1759
+ HyperframesLoader,
1760
+ {
1761
+ title: "Preparing preview assets",
1762
+ detail: "Waiting for media and motion assets before playback starts.",
1763
+ size: 56
1764
+ }
1765
+ ),
1766
+ assetWaitLong && /* @__PURE__ */ jsx(
1767
+ "button",
1768
+ {
1769
+ type: "button",
1770
+ onClick: handleContinueAnyway,
1771
+ className: "px-3 py-1.5 text-[11px] rounded-md border border-neutral-700 text-neutral-300 hover:border-neutral-500 hover:bg-neutral-800 transition-colors",
1772
+ children: "Continue anyway"
1773
+ }
1774
+ )
1775
+ ] })
1776
+ }
1777
+ ),
1778
+ previewError && /* @__PURE__ */ jsx(
1779
+ "div",
1780
+ {
1781
+ className: "absolute inset-0 z-40 flex items-center justify-center bg-black/90 px-6 text-center",
1782
+ "data-hyperframes-ignore": "",
1783
+ "data-testid": "composition-preview-error",
1784
+ children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
1785
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-white", children: "Preview failed to load" }),
1786
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-neutral-400", children: previewError }),
1787
+ /* @__PURE__ */ jsx(
1788
+ "button",
1789
+ {
1790
+ type: "button",
1791
+ className: "mt-4 rounded-md bg-white px-3 py-1.5 text-xs font-semibold text-black transition-colors hover:bg-neutral-200",
1792
+ onClick: () => retryPreviewRef.current?.(),
1793
+ children: "Retry preview"
1794
+ }
1795
+ )
1796
+ ] })
1797
+ }
1798
+ )
1799
+ ]
1800
+ }
1801
+ );
1802
+ }
1803
+ );
1804
+ Player.displayName = "Player";
1805
+
1806
+ export {
1807
+ useMountEffect,
1808
+ usePreviewVariablesStore,
1809
+ applyPreviewVariablesToUrl,
1810
+ getAdapterDuration,
1811
+ getDefaultStaticSeekPlaybackClock,
1812
+ releaseStaticSeekCache,
1813
+ resolveStaticSeekFallback,
1814
+ wrapTimeline,
1815
+ LAYER_REVEAL_PRIOR_Z_ATTR,
1816
+ LAYER_REVEAL_PRIOR_POSITION_ATTR,
1817
+ readLayerRevealPriorZ,
1818
+ readTimelineElementZIndex,
1819
+ isTimelineIgnoredElement,
1820
+ furthestClipEndFromDocument,
1821
+ readTimelineDurationFromDocument,
1822
+ furthestClipEndFromSource,
1823
+ resolveMediaElement,
1824
+ readPlaybackStartAttributes,
1825
+ playbackStartAttributeForElement,
1826
+ applyMediaMetadataFromElement,
1827
+ getTimelineElementDisplayLabel,
1828
+ getTimelineElementSelector,
1829
+ getTimelineElementSourceFile,
1830
+ getTimelineElementSelectorIndex,
1831
+ buildTimelineElementKey,
1832
+ splitTimelineElementKey,
1833
+ buildTimelineElementIdentity,
1834
+ getTimelineElementIdentity,
1835
+ runtimeAudioId,
1836
+ deriveTimelineStoreKey,
1837
+ deriveTimelineStoreKeyForDomId,
1838
+ previewElementFinder,
1839
+ findClipElementById,
1840
+ createTimelineDomNodeResolver,
1841
+ PANEL_IDS,
1842
+ PANEL_DEFINITIONS,
1843
+ isPanelId,
1844
+ panelsInZone,
1845
+ DOCK_PANEL_COMPONENT,
1846
+ readStudioUiPreferences,
1847
+ writeStudioUiPreferences,
1848
+ STUDIO_PREVIEW_FPS,
1849
+ formatTime,
1850
+ secondsToFrame,
1851
+ frameToSeconds,
1852
+ stepFrameTime,
1853
+ formatFrameTime,
1854
+ getTimelineZoomPercent,
1855
+ getTimelinePixelsPerSecond,
1856
+ getNextTimelineZoomPercent,
1857
+ getPinchTimelineZoomPercent,
1858
+ timelineZoomPercentToSlider,
1859
+ timelineSliderToZoomPercent,
1860
+ isRevealedAudioFxRequestCurrent,
1861
+ isFocusedEaseRequestCurrent,
1862
+ effectiveThumbnailMode,
1863
+ thumbnailRevisionOf,
1864
+ createTimelineResetState,
1865
+ liveTime,
1866
+ usePlayerStore,
1867
+ isPreviewBooted,
1868
+ whenPreviewBooted,
1869
+ Player
1870
+ };
1871
+ //# sourceMappingURL=chunk-LPZMAGS7.js.map