@vanillaskyai/video 0.8.7 → 0.9.0

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 (175) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/PUBLIC-API.md +5 -8
  3. package/dist/{builtin-server-CUYPIDE7.js → builtin-server-HW6HGXCZ.js} +2 -2
  4. package/dist/{catalog-types-BIhSpOWK.d.ts → catalog-types-WTbLP6Jh.d.ts} +5 -1
  5. package/dist/chapter-title-YRLIQY3V.js +7 -0
  6. package/dist/check-runtime.js +6 -15
  7. package/dist/{chunk-2USVB3CY.js → chunk-3ZTEFJIR.js} +285 -178
  8. package/dist/chunk-4YM2M62S.js +13 -0
  9. package/dist/chunk-77VY4O7A.js +390 -0
  10. package/dist/{chunk-MMUXVA47.js → chunk-7M56IUUX.js} +9 -8
  11. package/dist/chunk-7NZOMTAL.js +63 -0
  12. package/dist/{chunk-2DOLF4QN.js → chunk-AUPC6MDK.js} +12 -14
  13. package/dist/{chunk-352ZZCET.js → chunk-EGVQODKU.js} +2 -2
  14. package/dist/chunk-EWWCQFTI.js +18 -0
  15. package/dist/{chunk-MXZSDGZQ.js → chunk-G7IZBBWF.js} +14 -9
  16. package/dist/{chunk-KT3M7KDL.js → chunk-HKUI5BTB.js} +3 -3
  17. package/dist/{chunk-LMG3GHLL.js → chunk-IQMYK5DX.js} +29 -3
  18. package/dist/chunk-LH4BPALA.js +564 -0
  19. package/dist/{chunk-OWBT45EN.js → chunk-MQILEEXC.js} +1 -1
  20. package/dist/chunk-MXEYPK7M.js +552 -0
  21. package/dist/{chunk-AITKH6QT.js → chunk-OR52UMSF.js} +13 -39
  22. package/dist/{chunk-5SLENAJW.js → chunk-PAJ44TFW.js} +1 -2
  23. package/dist/{chunk-L7QGGKJ2.js → chunk-PDQFQIQW.js} +1 -1
  24. package/dist/{chunk-WJ6FLKNC.js → chunk-R3XAOMKP.js} +14 -0
  25. package/dist/{chunk-2OKB27TG.js → chunk-RXTN2CW6.js} +2 -2
  26. package/dist/{chunk-XWWLKRNU.js → chunk-TVIU23OM.js} +38 -49
  27. package/dist/{chunk-224QNWRA.js → chunk-UDIODEDK.js} +1 -5
  28. package/dist/{chunk-3VRMKFM7.js → chunk-ZDJNFA52.js} +11 -24
  29. package/dist/cinema-media-YQHSIFDA.js +11 -0
  30. package/dist/cli.js +18 -9
  31. package/dist/comparison-RZMMHZR2.js +27 -0
  32. package/dist/compose-video-TBFTONUC.js +12 -0
  33. package/dist/editorial-timeline-OZX7OS6G.js +36 -0
  34. package/dist/{events-BQ6z264n.d.ts → events-B4YCc4vc.d.ts} +1 -1
  35. package/dist/focus-cards-TJ72BT7U.js +20 -0
  36. package/dist/index.d.ts +4 -6
  37. package/dist/index.js +3 -7
  38. package/dist/key-figure-GYENXKH3.js +20 -0
  39. package/dist/{kit-tQnCPMOM.d.ts → kit-DrRpdn0p.d.ts} +2 -2
  40. package/dist/mobile-message-UZUPIKHD.js +49 -0
  41. package/dist/{preload-media-LJXWKTWG.js → preload-media-CFTZIMZL.js} +1 -1
  42. package/dist/quote-EW5HQA6H.js +23 -0
  43. package/dist/react.d.ts +21 -11
  44. package/dist/react.js +608 -851
  45. package/dist/scene-video-backdrop-PCTARAAJ.js +7 -0
  46. package/dist/server.d.ts +7 -8
  47. package/dist/server.js +91 -74
  48. package/dist/{system-prompt-RRXIWDDD.js → system-prompt-SYRT5DWM.js} +1 -1
  49. package/dist/template-catalog.d.ts +257 -16
  50. package/dist/template-catalog.js +1 -1
  51. package/dist/templates.d.ts +5 -5
  52. package/dist/test.d.ts +3 -3
  53. package/dist/test.js +18 -18
  54. package/dist/text-stream-LD364KBI.js +9 -0
  55. package/dist/{types-DWFO6qca.d.ts → types-CdBmNTGD.d.ts} +1 -1
  56. package/dist/{types-CEh9eUFu.d.ts → types-DdZw4GRQ.d.ts} +17 -48
  57. package/dist/validate-4OUD2CLX.js +10 -0
  58. package/docs/architecture.md +1 -1
  59. package/docs/concepts.md +1 -1
  60. package/docs/custom-templates.md +13 -28
  61. package/docs/customization.md +11 -59
  62. package/docs/getting-started.md +2 -2
  63. package/docs/immersive-interface.md +3 -4
  64. package/docs/maintainers/cinematic-migration.md +34 -0
  65. package/docs/media-and-audio.md +14 -14
  66. package/docs/motion-and-effects.md +6 -14
  67. package/docs/persistence.md +3 -3
  68. package/docs/production.md +11 -9
  69. package/docs/provider-integration.md +9 -16
  70. package/docs/reference/design-system.html +3 -3
  71. package/docs/reference/protocol.md +14 -4
  72. package/docs/testing.md +1 -1
  73. package/package.json +6 -3
  74. package/registry/items/backgrounds.json +1 -1
  75. package/registry/items/chapterTitle.json +80 -0
  76. package/registry/items/cinemaMedia.json +136 -0
  77. package/registry/items/comparison.json +101 -0
  78. package/registry/items/editorialTimeline.json +105 -0
  79. package/registry/items/focusCards.json +90 -0
  80. package/registry/items/keyFigure.json +97 -0
  81. package/registry/items/mobileMessage.json +153 -0
  82. package/registry/items/quote.json +94 -0
  83. package/registry/items/template-context.json +1 -1
  84. package/registry/items/theme.json +1 -1
  85. package/starters/video-chat/README.md +24 -26
  86. package/starters/video-chat/package.json +1 -1
  87. package/starters/video-chat/providers/video.ts +6 -5
  88. package/starters/video-chat/stock.ts +35 -151
  89. package/styles/fonts/OFL.txt +93 -0
  90. package/styles/fonts/manifest.json +66 -0
  91. package/styles/fonts/roboto-v51-cyrillic-ext.woff2 +0 -0
  92. package/styles/fonts/roboto-v51-cyrillic.woff2 +0 -0
  93. package/styles/fonts/roboto-v51-greek-ext.woff2 +0 -0
  94. package/styles/fonts/roboto-v51-greek.woff2 +0 -0
  95. package/styles/fonts/roboto-v51-latin-ext.woff2 +0 -0
  96. package/styles/fonts/roboto-v51-latin.woff2 +0 -0
  97. package/styles/fonts/roboto-v51-math.woff2 +0 -0
  98. package/styles/fonts/roboto-v51-symbols.woff2 +0 -0
  99. package/styles/fonts/roboto-v51-vietnamese.woff2 +0 -0
  100. package/styles/fonts/roboto.css +91 -0
  101. package/styles/video-chat.css +30 -23
  102. package/dist/bg-confetti-HITCGLGD.js +0 -89
  103. package/dist/bg-emoji-VU4UZYZN.js +0 -150
  104. package/dist/bg-media-L34PDQXJ.js +0 -106
  105. package/dist/brand-message-FUJ4STFO.js +0 -256
  106. package/dist/chart-bar-EGPHJATL.js +0 -310
  107. package/dist/chart-counter-5FFWQNZO.js +0 -306
  108. package/dist/chart-progress-ring-JMXQLMLC.js +0 -302
  109. package/dist/chunk-4UNKZEAT.js +0 -3575
  110. package/dist/chunk-5CDAM24P.js +0 -31
  111. package/dist/chunk-AOWSXU2K.js +0 -18
  112. package/dist/chunk-CJ7F2CE6.js +0 -104
  113. package/dist/chunk-FRN6WKHA.js +0 -185
  114. package/dist/chunk-G5JPHBX7.js +0 -336
  115. package/dist/chunk-L5BTTCZV.js +0 -97
  116. package/dist/chunk-P22HHFDK.js +0 -43
  117. package/dist/chunk-PP3MCPN4.js +0 -1998
  118. package/dist/chunk-QAAO7OAY.js +0 -19
  119. package/dist/chunk-S7HFLYNN.js +0 -35
  120. package/dist/chunk-W3VDIBGR.js +0 -217
  121. package/dist/chunk-XGRA2MUV.js +0 -242
  122. package/dist/chunk-XTOXFMLM.js +0 -105
  123. package/dist/chunk-YJJC4N4D.js +0 -761
  124. package/dist/compose-video-2NFH4DZY.js +0 -13
  125. package/dist/cta-logo-YR7LYUR5.js +0 -212
  126. package/dist/cta-media-DQJBKUO2.js +0 -221
  127. package/dist/incoming-call-54KQ7O5A.js +0 -323
  128. package/dist/infographic-before-after-ROS52GOW.js +0 -460
  129. package/dist/infographic-feature-list-E7MGUDAN.js +0 -272
  130. package/dist/infographic-problem-solution-3S6NGO5N.js +0 -343
  131. package/dist/infographic-stat-row-SYB7AB7H.js +0 -247
  132. package/dist/infographic-steps-KCE6JWE2.js +0 -406
  133. package/dist/prompt-input-MHX4O42G.js +0 -239
  134. package/dist/reaction-SLEGR3BE.js +0 -122
  135. package/dist/scene-video-backdrop-XFPX4O3I.js +0 -10
  136. package/dist/showcase-code-2HLAVKBM.js +0 -357
  137. package/dist/showcase-phone-Q4HEO4LH.js +0 -278
  138. package/dist/showcase-terminal-DL45LUFG.js +0 -312
  139. package/dist/showcase-web-2QZO73FZ.js +0 -396
  140. package/dist/social-conversation-75SD2637.js +0 -695
  141. package/dist/social-milestone-GEFUQSBF.js +0 -296
  142. package/dist/social-notification-FJ5VHVLK.js +0 -247
  143. package/dist/social-review-stack-KXQCKFRP.js +0 -242
  144. package/dist/social-testimonial-JZ7CHC7S.js +0 -287
  145. package/dist/social-tweet-UCDXM25F.js +0 -298
  146. package/dist/text-stream-54LXABJW.js +0 -10
  147. package/dist/validate-T7GBU2YF.js +0 -8
  148. package/registry/items/barChart.json +0 -245
  149. package/registry/items/beforeAfter.json +0 -180
  150. package/registry/items/bigNumber.json +0 -232
  151. package/registry/items/brandMessage.json +0 -164
  152. package/registry/items/cardList.json +0 -214
  153. package/registry/items/chatMessenger.json +0 -151
  154. package/registry/items/chatWhatsapp.json +0 -151
  155. package/registry/items/codeEditor.json +0 -191
  156. package/registry/items/confetti.json +0 -107
  157. package/registry/items/ctaLogo.json +0 -167
  158. package/registry/items/ctaMedia.json +0 -200
  159. package/registry/items/emojiBurst.json +0 -101
  160. package/registry/items/incomingCall.json +0 -177
  161. package/registry/items/media.json +0 -187
  162. package/registry/items/milestone.json +0 -212
  163. package/registry/items/notification.json +0 -189
  164. package/registry/items/phoneMockup.json +0 -261
  165. package/registry/items/problemSolution.json +0 -193
  166. package/registry/items/progressRing.json +0 -206
  167. package/registry/items/promptInput.json +0 -155
  168. package/registry/items/reaction.json +0 -176
  169. package/registry/items/reviewStack.json +0 -231
  170. package/registry/items/steps.json +0 -215
  171. package/registry/items/terminal.json +0 -219
  172. package/registry/items/testimonial.json +0 -171
  173. package/registry/items/tripleStats.json +0 -235
  174. package/registry/items/tweet.json +0 -210
  175. package/registry/items/webMockup.json +0 -280
@@ -1,23 +1,130 @@
1
1
  import {
2
2
  limitsConcurrentVideoDecoders,
3
3
  resolveMediaType
4
- } from "./chunk-224QNWRA.js";
5
- import {
6
- ExternalVideoBackdropProvider
7
- } from "./chunk-5CDAM24P.js";
4
+ } from "./chunk-UDIODEDK.js";
8
5
  import {
6
+ ExternalVideoBackdropProvider,
9
7
  resolveDensity
10
- } from "./chunk-XWWLKRNU.js";
8
+ } from "./chunk-TVIU23OM.js";
11
9
  import {
12
10
  resolveVideoTimeline
13
11
  } from "./chunk-SPVTJH3F.js";
14
12
  import {
15
13
  supportsExternalVideoBackdrop
16
- } from "./chunk-WJ6FLKNC.js";
14
+ } from "./chunk-R3XAOMKP.js";
17
15
  import {
18
16
  getTemplateDefaults
19
17
  } from "./chunk-73NTSFFI.js";
20
18
 
19
+ // src/player/mounted-scene-readiness.tsx
20
+ import { createContext, useContext, useEffect, useRef } from "react";
21
+ import { jsx } from "react/jsx-runtime";
22
+ var MountedReadinessContext = createContext(void 0);
23
+ var sceneReadinessKey = (scene) => `${scene.id}\0${String(scene.variables.mediaUrl || "")}`;
24
+ function MountedSceneReadiness({ scene, playing, fallback = false }) {
25
+ const marker = useRef(null);
26
+ const report = useContext(MountedReadinessContext);
27
+ const key = sceneReadinessKey(scene);
28
+ useEffect(() => {
29
+ if (!report || !playing) return;
30
+ let stopped = false;
31
+ let frame = 0;
32
+ let callback;
33
+ let observed;
34
+ const start = performance.now();
35
+ const finish = (error, actualVideoFrame = false) => {
36
+ if (!stopped) {
37
+ stopped = true;
38
+ report(key, error, actualVideoFrame);
39
+ }
40
+ };
41
+ const check = () => {
42
+ if (stopped) return;
43
+ const root = marker.current?.closest("[data-video-frame]");
44
+ if (fallback && root?.querySelector("[data-scene-fallback]")) {
45
+ finish();
46
+ return;
47
+ }
48
+ const layer = root?.querySelector('[data-scene-layer="active"]');
49
+ const loading = layer?.querySelector("[data-template-loading]");
50
+ const mediaUrl = String(scene.variables.mediaUrl || "");
51
+ const isVideo = scene.variables.mediaType === "video" || /\.(mp4|webm|mov)(?:[?#]|$)/i.test(mediaUrl);
52
+ if (root && layer && !loading && document.fonts?.status !== "loading") {
53
+ if (!mediaUrl || scene.variables.mediaType === "gradient") {
54
+ finish();
55
+ return;
56
+ }
57
+ if (isVideo) {
58
+ const persistent = root.querySelector("[data-persistent-video-scene-id]");
59
+ const video = (persistent?.getAttribute("data-persistent-video-scene-id") === scene.id ? persistent : layer)?.querySelector("video");
60
+ if (video && video.getAttribute("src") === mediaUrl && video.readyState >= 2) {
61
+ observed = video;
62
+ if (video.requestVideoFrameCallback) {
63
+ callback = video.requestVideoFrameCallback(() => finish(void 0, true));
64
+ return;
65
+ }
66
+ finish(void 0, true);
67
+ return;
68
+ }
69
+ } else {
70
+ const image = [...layer.querySelectorAll("img") ?? []].find((element) => element.getAttribute("src") === mediaUrl);
71
+ if (image?.complete && image.naturalWidth > 0) {
72
+ finish();
73
+ return;
74
+ }
75
+ }
76
+ }
77
+ if (performance.now() - start >= 8e3) {
78
+ finish(new Error("Scene media did not become ready"));
79
+ return;
80
+ }
81
+ frame = requestAnimationFrame(check);
82
+ };
83
+ check();
84
+ const timeout = setTimeout(() => finish(new Error("Scene media did not become ready")), 8e3);
85
+ return () => {
86
+ stopped = true;
87
+ clearTimeout(timeout);
88
+ cancelAnimationFrame(frame);
89
+ if (callback !== void 0) observed?.cancelVideoFrameCallback?.(callback);
90
+ };
91
+ }, [key, report, scene, playing, fallback]);
92
+ return /* @__PURE__ */ jsx("span", { ref: marker, hidden: true });
93
+ }
94
+ function PreparedSceneReadiness({ scene }) {
95
+ const marker = useRef(null);
96
+ const report = useContext(MountedReadinessContext);
97
+ useEffect(() => {
98
+ if (!report) return;
99
+ let frame = 0;
100
+ let stopped = false;
101
+ const check = () => {
102
+ if (stopped) return;
103
+ const root = marker.current?.closest("[data-video-frame]");
104
+ const incoming = root?.querySelector("[data-scene-layer='incoming']");
105
+ const video = incoming?.getAttribute("data-layer-scene-id") === scene.id ? incoming.querySelector("video") : void 0;
106
+ if (video && video.getAttribute("src") === scene.variables.mediaUrl && video.readyState >= 2) {
107
+ report(sceneReadinessKey(scene), void 0, false, true);
108
+ return;
109
+ }
110
+ const image = [...root?.querySelectorAll("img[data-video-poster-plane='prepared']") ?? []].find((image2) => image2.getAttribute("src") === scene.variables.mediaPoster);
111
+ if (image?.complete && image.naturalWidth > 0) {
112
+ void image.decode().then(() => {
113
+ if (!stopped && image.isConnected) report(sceneReadinessKey(scene), void 0, false, true);
114
+ }).catch(() => void 0);
115
+ return;
116
+ }
117
+ frame = requestAnimationFrame(check);
118
+ };
119
+ check();
120
+ return () => {
121
+ stopped = true;
122
+ cancelAnimationFrame(frame);
123
+ };
124
+ }, [scene, report]);
125
+ return /* @__PURE__ */ jsx("span", { ref: marker, hidden: true });
126
+ }
127
+
21
128
  // src/player/video-frame.tsx
22
129
  import {
23
130
  createElement,
@@ -25,7 +132,7 @@ import {
25
132
  lazy,
26
133
  Suspense,
27
134
  useState,
28
- useEffect,
135
+ useEffect as useEffect2,
29
136
  useSyncExternalStore
30
137
  } from "react";
31
138
 
@@ -47,9 +154,9 @@ function scaleSafeZone(zone, factor) {
47
154
  }
48
155
 
49
156
  // src/player/video-frame.tsx
50
- import { jsx, jsxs } from "react/jsx-runtime";
157
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
51
158
  function PresentedScene({ notify }) {
52
- useEffect(() => {
159
+ useEffect2(() => {
53
160
  if (!notify) return;
54
161
  const frame = requestAnimationFrame(() => {
55
162
  try {
@@ -80,9 +187,10 @@ function SafeScene({ scene, onFramePresented }) {
80
187
  overflow: "hidden"
81
188
  },
82
189
  children: [
83
- /* @__PURE__ */ jsx(PresentedScene, { notify: onFramePresented }),
84
- /* @__PURE__ */ jsx("p", { children: copy.slice(0, 600) || "Your response continues." }),
85
- /* @__PURE__ */ jsx("small", { style: { fontSize: "0.3em" }, role: "status", children: "This scene uses a simpler layout." })
190
+ /* @__PURE__ */ jsx2(MountedSceneReadiness, { scene, playing: true, fallback: true }),
191
+ /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
192
+ /* @__PURE__ */ jsx2("p", { children: copy.slice(0, 600) || "Your response continues." }),
193
+ /* @__PURE__ */ jsx2("small", { style: { fontSize: "0.3em" }, role: "status", children: "This scene uses a simpler layout." })
86
194
  ]
87
195
  }
88
196
  );
@@ -93,11 +201,11 @@ var SceneBoundary = class extends Component {
93
201
  return { failed: true };
94
202
  }
95
203
  render() {
96
- return this.state.failed ? /* @__PURE__ */ jsx(SafeScene, { scene: this.props.scene, onFramePresented: this.props.onFramePresented }) : this.props.children;
204
+ return this.state.failed ? /* @__PURE__ */ jsx2(SafeScene, { scene: this.props.scene, onFramePresented: this.props.onFramePresented }) : this.props.children;
97
205
  }
98
206
  };
99
207
  var SCENE_TRANSITION_SECONDS = 0.3;
100
- var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-XFPX4O3I.js").then((module) => ({ default: module.SceneVideoBackdrop })));
208
+ var SceneVideoBackdrop = lazy(() => import("./scene-video-backdrop-PCTARAAJ.js").then((module) => ({ default: module.SceneVideoBackdrop })));
101
209
  var MEDIA_PREROLL_SECONDS = 1.2;
102
210
  var CONTIGUITY_ULP_FACTOR = 4;
103
211
  var subscribeToDecoderPolicy = () => () => {
@@ -120,14 +228,6 @@ function rangesAreContiguous(left, right) {
120
228
  const ulpTolerance = Number.EPSILON * scale * CONTIGUITY_ULP_FACTOR;
121
229
  return Math.abs(left.end - right.start) <= ulpTolerance;
122
230
  }
123
- function brandBackground(config) {
124
- const background = config.style.brand.background;
125
- return background.type === "solid" ? background.color : `linear-gradient(135deg, ${background.colors[0]}, ${background.colors[1]})`;
126
- }
127
- function brandBackgroundFallback(config) {
128
- const background = config.style.brand.background;
129
- return background.type === "solid" ? background.color : background.colors[0];
130
- }
131
231
  function sceneHasBackdrop(range) {
132
232
  return String(range.scene.variables.mediaType || "auto") !== "gradient" && String(range.scene.variables.mediaUrl || "").trim() !== "";
133
233
  }
@@ -171,13 +271,13 @@ function SceneLayer({
171
271
  }) {
172
272
  const template = kit.getTemplate(range.scene.templateId);
173
273
  const duration = range.end - range.start;
174
- return /* @__PURE__ */ jsx(
274
+ return /* @__PURE__ */ jsx2(
175
275
  ExternalVideoBackdropProvider,
176
276
  {
177
277
  mode: externalVideoBackdrop,
178
278
  audioMuted: mediaAudioMuted || !playing,
179
279
  audioVolume: mediaAudioVolume,
180
- children: /* @__PURE__ */ jsx(
280
+ children: /* @__PURE__ */ jsx2(
181
281
  "div",
182
282
  {
183
283
  "data-scene-layer": layer,
@@ -197,14 +297,14 @@ function SceneLayer({
197
297
  "--vanillasky-transition-semantic-visibility": layer === "incoming" ? "hidden" : "visible",
198
298
  "--vanillasky-template-surface": externalVideoBackdrop !== false ? "transparent" : void 0
199
299
  },
200
- children: template ? /* @__PURE__ */ jsx(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx(
300
+ children: template ? /* @__PURE__ */ jsx2(SceneBoundary, { scene: range.scene, onFramePresented, children: /* @__PURE__ */ jsxs(Suspense, { fallback: /* @__PURE__ */ jsx2(
201
301
  "div",
202
302
  {
203
303
  "data-template-loading": range.scene.templateId,
204
304
  style: { position: "absolute", inset: 0 }
205
305
  }
206
306
  ), children: [
207
- /* @__PURE__ */ jsx(PresentedScene, { notify: onFramePresented }),
307
+ /* @__PURE__ */ jsx2(PresentedScene, { notify: onFramePresented }),
208
308
  createElement(template.component, {
209
309
  variables: {
210
310
  ...template.defaults ?? getTemplateDefaults(template.schema),
@@ -225,7 +325,7 @@ function SceneLayer({
225
325
  sceneDuration: duration,
226
326
  isPlaying: playing
227
327
  })
228
- ] }) }, range.scene.id) : /* @__PURE__ */ jsx(SafeScene, { scene: range.scene, onFramePresented })
328
+ ] }) }, range.scene.id) : /* @__PURE__ */ jsx2(SafeScene, { scene: range.scene, onFramePresented })
229
329
  }
230
330
  )
231
331
  }
@@ -254,24 +354,24 @@ function VideoFrame({
254
354
  const activeIndex = foundIndex >= 0 ? foundIndex : afterEnd ? timeline.length - 1 : -1;
255
355
  const active = activeIndex >= 0 ? timeline[activeIndex] : void 0;
256
356
  if (!active) {
257
- return /* @__PURE__ */ jsx(
357
+ return /* @__PURE__ */ jsx2(
258
358
  "div",
259
359
  {
260
360
  "data-video-frame": timeline.length === 0 ? "empty" : "gap",
261
361
  className,
262
- style: { width, height, background: brandBackground(config), ...style }
362
+ style: { width, height, background: "#000", ...style }
263
363
  }
264
364
  );
265
365
  }
266
366
  if (!kit.getTemplate(active.scene.templateId)) {
267
- return /* @__PURE__ */ jsx(
367
+ return /* @__PURE__ */ jsx2(
268
368
  "div",
269
369
  {
270
370
  "data-video-frame": "unsupported",
271
371
  "data-template-id": active.scene.templateId,
272
372
  className,
273
373
  style: { width, height, background: "#090712", color: "#fff", ...style },
274
- children: /* @__PURE__ */ jsx(SafeScene, { scene: active.scene, onFramePresented })
374
+ children: /* @__PURE__ */ jsx2(SafeScene, { scene: active.scene, onFramePresented })
275
375
  }
276
376
  );
277
377
  }
@@ -311,6 +411,7 @@ function VideoFrame({
311
411
  const persistentVideoKey = persistentVideoRange ? `${persistentVideoRange.scene.id}\0${String(persistentVideoRange.scene.variables.mediaUrl || "")}` : void 0;
312
412
  const firstVideoRange = timeline.find(sceneHasVideoBackdrop);
313
413
  const posterPreparationRange = activeUsesPersistentVideo ? contiguousNext && sceneHasVideoBackdrop(contiguousNext) ? contiguousNext : activeIndex === timeline.length - 1 && firstVideoRange?.scene.id !== active.scene.id ? firstVideoRange : void 0 : void 0;
414
+ const preparedReadinessRange = posterPreparationRange ?? (contiguousNext && sceneHasVideoBackdrop(contiguousNext) ? contiguousNext : void 0);
314
415
  const preparedPoster = posterPreparationRange && String(
315
416
  posterPreparationRange.scene.variables.mediaPoster || ""
316
417
  ) ? {
@@ -338,7 +439,7 @@ function VideoFrame({
338
439
  const scale = Math.min(width / canvas.width, height / canvas.height);
339
440
  const canvasLeft = (width - canvas.width * scale) / 2;
340
441
  const canvasTop = (height - canvas.height * scale) / 2;
341
- return /* @__PURE__ */ jsx(
442
+ return /* @__PURE__ */ jsxs(
342
443
  "div",
343
444
  {
344
445
  "data-video-frame": "ready",
@@ -350,163 +451,169 @@ function VideoFrame({
350
451
  height,
351
452
  position: "relative",
352
453
  overflow: "hidden",
353
- background: brandBackground(config),
454
+ background: "#000",
354
455
  ...style
355
456
  },
356
- children: /* @__PURE__ */ jsxs(
357
- "div",
358
- {
359
- "data-video-canvas": "true",
360
- style: {
361
- position: "absolute",
362
- left: canvasLeft,
363
- top: canvasTop,
364
- width: canvas.width,
365
- height: canvas.height,
366
- transform: `scale(${scale})`,
367
- transformOrigin: "top left"
368
- },
369
- children: [
370
- /* @__PURE__ */ jsx(
371
- "div",
372
- {
373
- "data-player-background": "brand",
374
- "aria-hidden": "true",
375
- style: {
376
- position: "absolute",
377
- inset: 0,
378
- background: brandBackground(config),
379
- backgroundColor: brandBackgroundFallback(config),
380
- pointerEvents: "none"
381
- }
382
- }
383
- ),
384
- persistentVideoRange && !persistentVideoFailed && /* @__PURE__ */ jsx(
385
- "div",
386
- {
387
- "data-persistent-video-scene-id": persistentVideoRange.scene.id,
388
- "aria-hidden": "true",
389
- style: { position: "absolute", inset: 0, zIndex: 0 },
390
- children: /* @__PURE__ */ jsx(Suspense, { fallback: String(persistentVideoRange.scene.variables.mediaPoster || "") ? /* @__PURE__ */ jsx(
391
- "img",
392
- {
393
- src: String(persistentVideoRange.scene.variables.mediaPoster),
394
- alt: "",
395
- "data-video-poster-plane": "loading",
396
- "data-video-poster-visible": "true",
397
- style: {
398
- position: "absolute",
399
- inset: 0,
400
- width: "100%",
401
- height: "100%",
402
- objectFit: "cover",
403
- objectPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center")
404
- }
405
- }
406
- ) : null, children: /* @__PURE__ */ jsx(
407
- SceneVideoBackdrop,
408
- {
409
- mediaUrl: String(persistentVideoRange.scene.variables.mediaUrl || ""),
410
- mediaPoster: String(persistentVideoRange.scene.variables.mediaPoster || "") || void 0,
411
- mediaPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center"),
412
- backgroundEffect: persistentVideoRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
413
- progress: persistentVideoRange.scene.id === active.scene.id ? rawProgress : 0,
414
- isPlaying: persistentVideoRange.scene.id === active.scene.id && playing,
415
- muted: mediaAudioMuted || persistentVideoRange.scene.id !== active.scene.id || !playing,
416
- volume: mediaAudioVolume,
417
- playbackId: persistentVideoRange.scene.id,
418
- retainPoster: true,
419
- persistent: true,
420
- preparedPoster: preparedPoster ? {
421
- ...preparedPoster,
422
- // The image is already decoded and costs no second video
423
- // decoder. Fade it above the outgoing video using the same
424
- // authored transition clock, then reuse that exact DOM image
425
- // as the new source's underlay at the cut.
426
- opacity: decoderConstrainedTransition ? blendProgress : 0
427
- } : void 0,
428
- onReady: () => setReadyPersistentVideo(persistentVideoKey),
429
- onError: () => setFailedPersistentVideo(persistentVideoKey)
430
- }
431
- ) })
432
- }
433
- ),
434
- mountingNext && contiguousNext ? [
435
- /* @__PURE__ */ jsx(
436
- SceneLayer,
457
+ children: [
458
+ /* @__PURE__ */ jsx2(MountedSceneReadiness, { scene: active.scene, playing }),
459
+ preparedReadinessRange && /* @__PURE__ */ jsx2(PreparedSceneReadiness, { scene: preparedReadinessRange.scene }),
460
+ /* @__PURE__ */ jsxs(
461
+ "div",
462
+ {
463
+ "data-video-canvas": "true",
464
+ style: {
465
+ position: "absolute",
466
+ left: canvasLeft,
467
+ top: canvasTop,
468
+ width: canvas.width,
469
+ height: canvas.height,
470
+ transform: `scale(${scale})`,
471
+ transformOrigin: "top left"
472
+ },
473
+ children: [
474
+ /* @__PURE__ */ jsx2(
475
+ "div",
437
476
  {
438
- onFramePresented,
439
- kit,
440
- config,
441
- range: active,
442
- progress,
443
- motionProgress,
444
- width: canvas.width,
445
- height: canvas.height,
446
- playing,
447
- mediaAudioMuted,
448
- mediaAudioVolume,
449
- layer: previewingNext ? "outgoing" : "active",
450
- opacity: 1 - blendProgress,
451
- interactive: true,
452
- zIndex: 1,
453
- externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
454
- },
455
- active.scene.id
477
+ "data-player-background": "black",
478
+ "aria-hidden": "true",
479
+ style: {
480
+ position: "absolute",
481
+ inset: 0,
482
+ background: "#000",
483
+ backgroundColor: "#000",
484
+ pointerEvents: "none"
485
+ }
486
+ }
456
487
  ),
457
- /* @__PURE__ */ jsx(
458
- SceneLayer,
488
+ persistentVideoRange && !persistentVideoFailed && /* @__PURE__ */ jsx2(
489
+ "div",
459
490
  {
460
- kit,
461
- config,
462
- range: contiguousNext,
463
- progress: 0,
464
- motionProgress: 0,
465
- width: canvas.width,
466
- height: canvas.height,
467
- playing: false,
468
- mediaAudioMuted,
469
- mediaAudioVolume,
470
- layer: "incoming",
471
- opacity: blendProgress,
472
- interactive: false,
473
- zIndex: 2,
474
- externalVideoBackdrop: persistentVideoRange?.scene.id === contiguousNext.scene.id ? persistentVideoMode : false
475
- },
476
- contiguousNext.scene.id
477
- )
478
- ] : [
479
- /* @__PURE__ */ jsx(
480
- SceneLayer,
481
- {
482
- onFramePresented,
483
- kit,
484
- config,
485
- range: active,
486
- progress,
487
- motionProgress,
488
- width: canvas.width,
489
- height: canvas.height,
490
- playing,
491
- mediaAudioMuted,
492
- mediaAudioVolume,
493
- layer: "active",
494
- opacity: 1,
495
- interactive: true,
496
- zIndex: 1,
497
- externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
498
- },
499
- active.scene.id
500
- )
491
+ "data-persistent-video-scene-id": persistentVideoRange.scene.id,
492
+ "aria-hidden": "true",
493
+ style: { position: "absolute", inset: 0, zIndex: 0 },
494
+ children: /* @__PURE__ */ jsx2(Suspense, { fallback: String(persistentVideoRange.scene.variables.mediaPoster || "") ? /* @__PURE__ */ jsx2(
495
+ "img",
496
+ {
497
+ src: String(persistentVideoRange.scene.variables.mediaPoster),
498
+ alt: "",
499
+ "data-video-poster-plane": "loading",
500
+ "data-video-poster-visible": "true",
501
+ style: {
502
+ position: "absolute",
503
+ inset: 0,
504
+ width: "100%",
505
+ height: "100%",
506
+ objectFit: "cover",
507
+ objectPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center")
508
+ }
509
+ }
510
+ ) : null, children: /* @__PURE__ */ jsx2(
511
+ SceneVideoBackdrop,
512
+ {
513
+ mediaUrl: String(persistentVideoRange.scene.variables.mediaUrl || ""),
514
+ mediaPoster: String(persistentVideoRange.scene.variables.mediaPoster || "") || void 0,
515
+ mediaPosition: String(persistentVideoRange.scene.variables.mediaPosition || "center"),
516
+ backgroundEffect: persistentVideoRange.scene.backgroundEffect ?? config.style.defaultBackgroundEffect,
517
+ progress: persistentVideoRange.scene.id === active.scene.id ? rawProgress : 0,
518
+ isPlaying: persistentVideoRange.scene.id === active.scene.id && playing,
519
+ muted: mediaAudioMuted || persistentVideoRange.scene.id !== active.scene.id || !playing,
520
+ volume: mediaAudioVolume,
521
+ playbackId: persistentVideoRange.scene.id,
522
+ retainPoster: true,
523
+ persistent: true,
524
+ preparedPoster: preparedPoster ? {
525
+ ...preparedPoster,
526
+ // The image is already decoded and costs no second video
527
+ // decoder. Fade it above the outgoing video using the same
528
+ // authored transition clock, then reuse that exact DOM image
529
+ // as the new source's underlay at the cut.
530
+ opacity: decoderConstrainedTransition ? blendProgress : 0
531
+ } : void 0,
532
+ onReady: () => setReadyPersistentVideo(persistentVideoKey),
533
+ onError: () => setFailedPersistentVideo(persistentVideoKey)
534
+ }
535
+ ) })
536
+ }
537
+ ),
538
+ mountingNext && contiguousNext ? [
539
+ /* @__PURE__ */ jsx2(
540
+ SceneLayer,
541
+ {
542
+ onFramePresented,
543
+ kit,
544
+ config,
545
+ range: active,
546
+ progress,
547
+ motionProgress,
548
+ width: canvas.width,
549
+ height: canvas.height,
550
+ playing,
551
+ mediaAudioMuted,
552
+ mediaAudioVolume,
553
+ layer: previewingNext ? "outgoing" : "active",
554
+ opacity: 1 - blendProgress,
555
+ interactive: true,
556
+ zIndex: 1,
557
+ externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
558
+ },
559
+ active.scene.id
560
+ ),
561
+ /* @__PURE__ */ jsx2(
562
+ SceneLayer,
563
+ {
564
+ kit,
565
+ config,
566
+ range: contiguousNext,
567
+ progress: 0,
568
+ motionProgress: 0,
569
+ width: canvas.width,
570
+ height: canvas.height,
571
+ playing: false,
572
+ mediaAudioMuted,
573
+ mediaAudioVolume,
574
+ layer: "incoming",
575
+ opacity: blendProgress,
576
+ interactive: false,
577
+ zIndex: 2,
578
+ externalVideoBackdrop: persistentVideoRange?.scene.id === contiguousNext.scene.id ? persistentVideoMode : false
579
+ },
580
+ contiguousNext.scene.id
581
+ )
582
+ ] : [
583
+ /* @__PURE__ */ jsx2(
584
+ SceneLayer,
585
+ {
586
+ onFramePresented,
587
+ kit,
588
+ config,
589
+ range: active,
590
+ progress,
591
+ motionProgress,
592
+ width: canvas.width,
593
+ height: canvas.height,
594
+ playing,
595
+ mediaAudioMuted,
596
+ mediaAudioVolume,
597
+ layer: "active",
598
+ opacity: 1,
599
+ interactive: true,
600
+ zIndex: 1,
601
+ externalVideoBackdrop: persistentVideoRange?.scene.id === active.scene.id ? persistentVideoMode : false
602
+ },
603
+ active.scene.id
604
+ )
605
+ ]
501
606
  ]
502
- ]
503
- }
504
- )
607
+ }
608
+ )
609
+ ]
505
610
  }
506
611
  );
507
612
  }
508
613
 
509
614
  export {
615
+ MountedReadinessContext,
616
+ sceneReadinessKey,
510
617
  getDimensions,
511
618
  VideoFrame
512
619
  };
@@ -0,0 +1,13 @@
1
+ // src/visual-system/scene-templates/editorial-typography.ts
2
+ var editorialFont = '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif';
3
+ var fade = (value) => {
4
+ const t = Math.max(0, Math.min(1, value));
5
+ return t * t * (3 - 2 * t);
6
+ };
7
+ var editorialLabel = (unit) => ({ fontSize: unit * 0.042, fontWeight: 400, color: "#b7b7bc", lineHeight: 1.25 });
8
+
9
+ export {
10
+ editorialFont,
11
+ fade,
12
+ editorialLabel
13
+ };