@vanillaskyai/video 0.3.0 → 0.3.2

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 (88) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/PUBLIC-API.md +4 -0
  3. package/README.md +3 -3
  4. package/dist/{bg-confetti-WNQXS7ZS.js → bg-confetti-KTHLUJPO.js} +3 -3
  5. package/dist/{bg-emoji-ETI47GLS.js → bg-emoji-AW7374DZ.js} +3 -3
  6. package/dist/{bg-media-SVUZAMGX.js → bg-media-KGYE6UVS.js} +9 -4
  7. package/dist/{brand-message-F3RD4P3P.js → brand-message-AY43ZQR2.js} +4 -3
  8. package/dist/{chart-bar-CNSC7BNK.js → chart-bar-Z42RDRQT.js} +8 -4
  9. package/dist/{chart-counter-MCRHKN77.js → chart-counter-X7NAOQ2C.js} +10 -5
  10. package/dist/{chart-progress-ring-RMI4A7ZK.js → chart-progress-ring-7NSNNHL4.js} +8 -4
  11. package/dist/check-runtime.js +2 -2
  12. package/dist/chunk-35LIYILY.js +25 -0
  13. package/dist/{chunk-IFOW65Z6.js → chunk-4AODC6MS.js} +76 -36
  14. package/dist/{chunk-FSG2PYWG.js → chunk-G5JPHBX7.js} +1 -1
  15. package/dist/{chunk-QTMHS7JD.js → chunk-HXSV6RHF.js} +1 -1
  16. package/dist/{chunk-ABZACD4G.js → chunk-IJM4B2ZN.js} +1 -1
  17. package/dist/{chunk-XYZOJ5NZ.js → chunk-LHFADNWJ.js} +9 -8
  18. package/dist/{chunk-BCRUUJ6A.js → chunk-TCEHK2BW.js} +6 -2
  19. package/dist/{chunk-FNAEQ7QO.js → chunk-XWWLKRNU.js} +3 -1
  20. package/dist/{compose-video-TQOHDXNW.js → compose-video-6UQ33DOO.js} +1 -1
  21. package/dist/control-visibility-NQOWTKOQ.js +258 -0
  22. package/dist/{cta-logo-SVA5GP2A.js → cta-logo-KNBLXUJU.js} +4 -3
  23. package/dist/{cta-media-E7VLLK42.js → cta-media-NY4ASBGG.js} +8 -4
  24. package/dist/{events-CTIsANzz.d.ts → events-tQ0x-VaL.d.ts} +1 -1
  25. package/dist/{incoming-call-62MFZL3Z.js → incoming-call-KWOXLGJN.js} +4 -3
  26. package/dist/index.d.ts +2 -2
  27. package/dist/{infographic-before-after-AXCDAWND.js → infographic-before-after-OEM4DSTZ.js} +3 -3
  28. package/dist/{infographic-feature-list-GW5GUE6O.js → infographic-feature-list-ZZ25JRYQ.js} +8 -4
  29. package/dist/{infographic-problem-solution-7NURJANA.js → infographic-problem-solution-L3RLFDPE.js} +5 -4
  30. package/dist/{infographic-stat-row-J7IZR6ZN.js → infographic-stat-row-3OL3TNSH.js} +8 -4
  31. package/dist/{infographic-steps-VSZM6F5M.js → infographic-steps-LMBSW22P.js} +8 -4
  32. package/dist/{kit-DA2cfJ96.d.ts → kit-DlUSg8lA.d.ts} +1 -1
  33. package/dist/preload-media-KIYBFYRH.js +30 -0
  34. package/dist/{prompt-input-LOUCRXCY.js → prompt-input-XCQ6TS2L.js} +4 -3
  35. package/dist/react.d.ts +3 -3
  36. package/dist/react.js +215 -104
  37. package/dist/{reaction-QG4CYZLQ.js → reaction-YL6YPGPB.js} +5 -4
  38. package/dist/server.d.ts +3 -3
  39. package/dist/server.js +2 -2
  40. package/dist/{showcase-code-RUTFZR3H.js → showcase-code-EFVHRJUM.js} +8 -4
  41. package/dist/{showcase-phone-6CP6XEU5.js → showcase-phone-CTHXHEBT.js} +9 -5
  42. package/dist/{showcase-terminal-MGAEQWPR.js → showcase-terminal-XAMPADHN.js} +8 -4
  43. package/dist/{showcase-web-Y4W2HVRJ.js → showcase-web-LAKHYXSD.js} +9 -5
  44. package/dist/{social-milestone-XI2QE5BO.js → social-milestone-EOBEN7CX.js} +6 -4
  45. package/dist/{social-notification-2UOHQCUY.js → social-notification-MELRWIB6.js} +4 -3
  46. package/dist/{social-review-stack-FDYZGZTJ.js → social-review-stack-H3U437VT.js} +4 -3
  47. package/dist/{social-testimonial-OQ7N52GJ.js → social-testimonial-GNNMKMDD.js} +4 -3
  48. package/dist/{social-tweet-5OLCNPPC.js → social-tweet-PBUKKPT3.js} +4 -3
  49. package/dist/templates.d.ts +3 -3
  50. package/dist/test.d.ts +2 -2
  51. package/dist/test.js +1 -1
  52. package/dist/{types-CkO2EYr4.d.ts → types-CVMb6QEq.d.ts} +2 -2
  53. package/docs/concepts.md +2 -1
  54. package/docs/customization.md +6 -3
  55. package/docs/getting-started.md +2 -2
  56. package/docs/input-and-first-scene.md +16 -4
  57. package/docs/integrate-nextjs.md +2 -2
  58. package/docs/media-and-audio.md +5 -1
  59. package/docs/prompt-and-input.md +3 -2
  60. package/examples/nextjs-quickstart/package.json +1 -1
  61. package/package.json +1 -1
  62. package/registry/items/barChart.json +9 -3
  63. package/registry/items/beforeAfter.json +1 -1
  64. package/registry/items/bigNumber.json +10 -4
  65. package/registry/items/brandMessage.json +7 -1
  66. package/registry/items/cardList.json +9 -3
  67. package/registry/items/codeEditor.json +9 -3
  68. package/registry/items/confetti.json +1 -1
  69. package/registry/items/ctaLogo.json +7 -1
  70. package/registry/items/ctaMedia.json +9 -3
  71. package/registry/items/emojiBurst.json +1 -1
  72. package/registry/items/incomingCall.json +7 -1
  73. package/registry/items/media.json +9 -3
  74. package/registry/items/milestone.json +8 -2
  75. package/registry/items/notification.json +7 -1
  76. package/registry/items/phoneMockup.json +9 -3
  77. package/registry/items/problemSolution.json +8 -2
  78. package/registry/items/progressRing.json +9 -3
  79. package/registry/items/promptInput.json +7 -1
  80. package/registry/items/reaction.json +8 -2
  81. package/registry/items/reviewStack.json +7 -1
  82. package/registry/items/steps.json +9 -3
  83. package/registry/items/terminal.json +9 -3
  84. package/registry/items/testimonial.json +7 -1
  85. package/registry/items/theme.json +1 -1
  86. package/registry/items/tripleStats.json +9 -3
  87. package/registry/items/tweet.json +7 -1
  88. package/registry/items/webMockup.json +9 -3
package/dist/react.js CHANGED
@@ -29,10 +29,10 @@ import "./chunk-2E6T633S.js";
29
29
  import {
30
30
  VideoFrame,
31
31
  getDimensions
32
- } from "./chunk-QTMHS7JD.js";
32
+ } from "./chunk-HXSV6RHF.js";
33
33
  import {
34
34
  fontStack
35
- } from "./chunk-FNAEQ7QO.js";
35
+ } from "./chunk-XWWLKRNU.js";
36
36
  import {
37
37
  getVideoDuration,
38
38
  resolveVideoTimeline
@@ -53,34 +53,34 @@ import { createElement } from "react";
53
53
 
54
54
  // src/visual-system/catalog/builtin-loaders.generated.ts
55
55
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
56
- "media": () => import("./bg-media-SVUZAMGX.js").then((module) => ({ default: module.BgMediaTemplate })),
57
- "reaction": () => import("./reaction-QG4CYZLQ.js").then((module) => ({ default: module.ReactionTemplate })),
58
- "confetti": () => import("./bg-confetti-WNQXS7ZS.js").then((module) => ({ default: module.BgConfettiTemplate })),
59
- "emojiBurst": () => import("./bg-emoji-ETI47GLS.js").then((module) => ({ default: module.BgEmojiTemplate })),
60
- "bigNumber": () => import("./chart-counter-MCRHKN77.js").then((module) => ({ default: module.ChartCounterTemplate })),
61
- "barChart": () => import("./chart-bar-CNSC7BNK.js").then((module) => ({ default: module.ChartBarTemplate })),
62
- "progressRing": () => import("./chart-progress-ring-RMI4A7ZK.js").then((module) => ({ default: module.ChartProgressRingTemplate })),
63
- "phoneMockup": () => import("./showcase-phone-6CP6XEU5.js").then((module) => ({ default: module.ShowcasePhoneTemplate })),
64
- "webMockup": () => import("./showcase-web-Y4W2HVRJ.js").then((module) => ({ default: module.ShowcaseWebTemplate })),
65
- "codeEditor": () => import("./showcase-code-RUTFZR3H.js").then((module) => ({ default: module.ShowcaseCodeTemplate })),
66
- "terminal": () => import("./showcase-terminal-MGAEQWPR.js").then((module) => ({ default: module.ShowcaseTerminalTemplate })),
67
- "tweet": () => import("./social-tweet-5OLCNPPC.js").then((module) => ({ default: module.SocialTweetTemplate })),
68
- "notification": () => import("./social-notification-2UOHQCUY.js").then((module) => ({ default: module.SocialNotificationTemplate })),
56
+ "media": () => import("./bg-media-KGYE6UVS.js").then((module) => ({ default: module.BgMediaTemplate })),
57
+ "reaction": () => import("./reaction-YL6YPGPB.js").then((module) => ({ default: module.ReactionTemplate })),
58
+ "confetti": () => import("./bg-confetti-KTHLUJPO.js").then((module) => ({ default: module.BgConfettiTemplate })),
59
+ "emojiBurst": () => import("./bg-emoji-AW7374DZ.js").then((module) => ({ default: module.BgEmojiTemplate })),
60
+ "bigNumber": () => import("./chart-counter-X7NAOQ2C.js").then((module) => ({ default: module.ChartCounterTemplate })),
61
+ "barChart": () => import("./chart-bar-Z42RDRQT.js").then((module) => ({ default: module.ChartBarTemplate })),
62
+ "progressRing": () => import("./chart-progress-ring-7NSNNHL4.js").then((module) => ({ default: module.ChartProgressRingTemplate })),
63
+ "phoneMockup": () => import("./showcase-phone-CTHXHEBT.js").then((module) => ({ default: module.ShowcasePhoneTemplate })),
64
+ "webMockup": () => import("./showcase-web-LAKHYXSD.js").then((module) => ({ default: module.ShowcaseWebTemplate })),
65
+ "codeEditor": () => import("./showcase-code-EFVHRJUM.js").then((module) => ({ default: module.ShowcaseCodeTemplate })),
66
+ "terminal": () => import("./showcase-terminal-XAMPADHN.js").then((module) => ({ default: module.ShowcaseTerminalTemplate })),
67
+ "tweet": () => import("./social-tweet-PBUKKPT3.js").then((module) => ({ default: module.SocialTweetTemplate })),
68
+ "notification": () => import("./social-notification-MELRWIB6.js").then((module) => ({ default: module.SocialNotificationTemplate })),
69
69
  "chatMessenger": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
70
70
  "chatWhatsapp": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
71
- "milestone": () => import("./social-milestone-XI2QE5BO.js").then((module) => ({ default: module.SocialMilestoneTemplate })),
72
- "reviewStack": () => import("./social-review-stack-FDYZGZTJ.js").then((module) => ({ default: module.SocialReviewStackTemplate })),
73
- "testimonial": () => import("./social-testimonial-OQ7N52GJ.js").then((module) => ({ default: module.SocialTestimonialTemplate })),
74
- "incomingCall": () => import("./incoming-call-62MFZL3Z.js").then((module) => ({ default: module.IncomingCallTemplate })),
75
- "brandMessage": () => import("./brand-message-F3RD4P3P.js").then((module) => ({ default: module.BrandMessageTemplate })),
76
- "promptInput": () => import("./prompt-input-LOUCRXCY.js").then((module) => ({ default: module.PromptInputTemplate })),
77
- "beforeAfter": () => import("./infographic-before-after-AXCDAWND.js").then((module) => ({ default: module.InfographicBeforeAfterTemplate })),
78
- "tripleStats": () => import("./infographic-stat-row-J7IZR6ZN.js").then((module) => ({ default: module.InfographicStatRowTemplate })),
79
- "problemSolution": () => import("./infographic-problem-solution-7NURJANA.js").then((module) => ({ default: module.InfographicProblemSolutionTemplate })),
80
- "cardList": () => import("./infographic-feature-list-GW5GUE6O.js").then((module) => ({ default: module.InfographicFeatureListTemplate })),
81
- "steps": () => import("./infographic-steps-VSZM6F5M.js").then((module) => ({ default: module.InfographicStepsTemplate })),
82
- "ctaLogo": () => import("./cta-logo-SVA5GP2A.js").then((module) => ({ default: module.CtaLogoTemplate })),
83
- "ctaMedia": () => import("./cta-media-E7VLLK42.js").then((module) => ({ default: module.CtaMediaTemplate }))
71
+ "milestone": () => import("./social-milestone-EOBEN7CX.js").then((module) => ({ default: module.SocialMilestoneTemplate })),
72
+ "reviewStack": () => import("./social-review-stack-H3U437VT.js").then((module) => ({ default: module.SocialReviewStackTemplate })),
73
+ "testimonial": () => import("./social-testimonial-GNNMKMDD.js").then((module) => ({ default: module.SocialTestimonialTemplate })),
74
+ "incomingCall": () => import("./incoming-call-KWOXLGJN.js").then((module) => ({ default: module.IncomingCallTemplate })),
75
+ "brandMessage": () => import("./brand-message-AY43ZQR2.js").then((module) => ({ default: module.BrandMessageTemplate })),
76
+ "promptInput": () => import("./prompt-input-XCQ6TS2L.js").then((module) => ({ default: module.PromptInputTemplate })),
77
+ "beforeAfter": () => import("./infographic-before-after-OEM4DSTZ.js").then((module) => ({ default: module.InfographicBeforeAfterTemplate })),
78
+ "tripleStats": () => import("./infographic-stat-row-3OL3TNSH.js").then((module) => ({ default: module.InfographicStatRowTemplate })),
79
+ "problemSolution": () => import("./infographic-problem-solution-L3RLFDPE.js").then((module) => ({ default: module.InfographicProblemSolutionTemplate })),
80
+ "cardList": () => import("./infographic-feature-list-ZZ25JRYQ.js").then((module) => ({ default: module.InfographicFeatureListTemplate })),
81
+ "steps": () => import("./infographic-steps-LMBSW22P.js").then((module) => ({ default: module.InfographicStepsTemplate })),
82
+ "ctaLogo": () => import("./cta-logo-KNBLXUJU.js").then((module) => ({ default: module.CtaLogoTemplate })),
83
+ "ctaMedia": () => import("./cta-media-NY4ASBGG.js").then((module) => ({ default: module.CtaMediaTemplate }))
84
84
  };
85
85
 
86
86
  // src/visual-system/catalog/builtin.ts
@@ -142,6 +142,12 @@ function preloadBuiltinTemplate(id) {
142
142
  return preloadableById.get(id)?.preload();
143
143
  }
144
144
 
145
+ // src/player/warm-scene-media.ts
146
+ function warmSceneMedia(variables) {
147
+ void import("./preload-media-KIYBFYRH.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
148
+ });
149
+ }
150
+
145
151
  // src/player/video-player.tsx
146
152
  import { jsx, jsxs } from "react/jsx-runtime";
147
153
  var MINIMUM_GENERATION_INTRO_MS = 3e3;
@@ -152,6 +158,46 @@ function savedVideoState(video) {
152
158
  config: video
153
159
  };
154
160
  }
161
+ function PlayerIcon({ name, size = 22 }) {
162
+ const shared = {
163
+ width: size,
164
+ height: size,
165
+ viewBox: "0 0 24 24",
166
+ fill: "none",
167
+ stroke: "currentColor",
168
+ strokeWidth: 2,
169
+ strokeLinecap: "round",
170
+ strokeLinejoin: "round"
171
+ };
172
+ if (name === "play") {
173
+ return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M8 5v14l11-7z", fill: "currentColor", stroke: "none" }) });
174
+ }
175
+ if (name === "pause") {
176
+ return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M7 5h4v14H7zm6 0h4v14h-4z", fill: "currentColor", stroke: "none" }) });
177
+ }
178
+ if (name === "replay") {
179
+ return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
180
+ /* @__PURE__ */ jsx("path", { d: "M4.5 9A8 8 0 1 1 5 16" }),
181
+ /* @__PURE__ */ jsx("path", { d: "M4.5 4.5V9H9" })
182
+ ] });
183
+ }
184
+ if (name === "volume-off") {
185
+ return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
186
+ /* @__PURE__ */ jsx("path", { d: "M11 5 6.5 9H3v6h3.5L11 19z" }),
187
+ /* @__PURE__ */ jsx("path", { d: "m16 9 5 5M21 9l-5 5" })
188
+ ] });
189
+ }
190
+ if (name === "volume") {
191
+ return /* @__PURE__ */ jsxs("svg", { ...shared, children: [
192
+ /* @__PURE__ */ jsx("path", { d: "M11 5 6.5 9H3v6h3.5L11 19z" }),
193
+ /* @__PURE__ */ jsx("path", { d: "M15 9.5a4 4 0 0 1 0 5M17.8 7a7.5 7.5 0 0 1 0 10" })
194
+ ] });
195
+ }
196
+ if (name === "exit-fullscreen") {
197
+ return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" }) });
198
+ }
199
+ return /* @__PURE__ */ jsx("svg", { ...shared, children: /* @__PURE__ */ jsx("path", { d: "M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5" }) });
200
+ }
155
201
  function VideoPlayerRuntime({
156
202
  kit,
157
203
  stream,
@@ -178,7 +224,7 @@ function VideoPlayerRuntime({
178
224
  const autoStartGeneration = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
179
225
  const [isPlaying, setIsPlaying] = useState(() => shouldAutoPlay && !reducedMotion && !autoStartGeneration);
180
226
  const [isMuted, setIsMuted] = useState(resolvedStartMuted);
181
- const [isFullscreen, setIsFullscreen] = useState(false);
227
+ const [fullscreenMode, setFullscreenMode] = useState("none");
182
228
  const [state, setState] = useState(() => video ? savedVideoState(video) : createVideoState());
183
229
  const [currentTime, setCurrentTime] = useState(0);
184
230
  const [activeStream, setActiveStream] = useState(stream);
@@ -196,6 +242,15 @@ function VideoPlayerRuntime({
196
242
  stateRef.current = state;
197
243
  timeRef.current = currentTime;
198
244
  callbacksRef.current = { onComplete, onError, onStateChange };
245
+ const primeSoundtrack = () => {
246
+ const audio = audioRef.current;
247
+ if (!audio || audio.dataset.audioOutput) return;
248
+ const Context = window.AudioContext ?? window.webkitAudioContext;
249
+ if (!Context) return;
250
+ const context = new Context();
251
+ context.resume().catch(Boolean);
252
+ import("./control-visibility-NQOWTKOQ.js").then((output) => output.default(audio, context)).catch(() => context.close());
253
+ };
199
254
  if (stream !== activeStream) {
200
255
  const autoStartReplacement = Boolean(playbackMode && stream && shouldAutoPlay && !reducedMotion);
201
256
  setActiveStream(stream);
@@ -224,12 +279,10 @@ function VideoPlayerRuntime({
224
279
  preference.addEventListener?.("change", handleChange);
225
280
  return () => preference.removeEventListener?.("change", handleChange);
226
281
  }, []);
227
- useEffect(() => setIsMuted(resolvedStartMuted), [resolvedStartMuted]);
228
282
  useEffect(() => {
229
- const updateFullscreen = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
230
- document.addEventListener("fullscreenchange", updateFullscreen);
231
- return () => document.removeEventListener("fullscreenchange", updateFullscreen);
283
+ import("./control-visibility-NQOWTKOQ.js");
232
284
  }, []);
285
+ useEffect(() => setIsMuted(resolvedStartMuted), [resolvedStartMuted]);
233
286
  useEffect(() => {
234
287
  let cancelled = false;
235
288
  let started = false;
@@ -239,7 +292,10 @@ function VideoPlayerRuntime({
239
292
  setState(reset);
240
293
  setCurrentTime(0);
241
294
  if (video) {
242
- for (const scene of video.scenes) preloadBuiltinTemplate(scene.templateId);
295
+ for (const scene of video.scenes) {
296
+ preloadBuiltinTemplate(scene.templateId);
297
+ warmSceneMedia(scene.variables);
298
+ }
243
299
  return;
244
300
  }
245
301
  if (!stream) return;
@@ -286,7 +342,9 @@ function VideoPlayerRuntime({
286
342
  if (width != null) return;
287
343
  const container = containerRef.current;
288
344
  if (!container) return;
289
- const update = () => setObservedWidth(container.clientWidth);
345
+ const update = () => {
346
+ setObservedWidth(container.clientWidth);
347
+ };
290
348
  update();
291
349
  if (typeof ResizeObserver === "undefined") return;
292
350
  const observer = new ResizeObserver(update);
@@ -394,7 +452,8 @@ function VideoPlayerRuntime({
394
452
  return () => clearTimeout(timer);
395
453
  }, [startRequested, state.config?.audio?.volume, state.config?.scenes.length, state.status]);
396
454
  const streamOrientation = state.config?.orientation ?? "portrait";
397
- const responsiveWidth = width ?? observedWidth;
455
+ const isFullscreen = fullscreenMode !== "none";
456
+ const responsiveWidth = isFullscreen ? window.innerWidth : width ?? observedWidth;
398
457
  const orientation = orientationOverride === "auto" ? responsiveWidth > 0 ? responsiveWidth <= responsiveBreakpoint ? "portrait" : "landscape" : streamOrientation : orientationOverride ?? streamOrientation;
399
458
  const dimensions = getDimensions(orientation);
400
459
  const displayWidth = (width ?? observedWidth) || dimensions.width;
@@ -413,6 +472,7 @@ function VideoPlayerRuntime({
413
472
  const posterTime = firstSceneRange ? firstSceneRange.start + Math.max(0, firstSceneRange.end - firstSceneRange.start) * firstSceneHoldProgress : currentTime;
414
473
  const startPlayback = () => {
415
474
  setGenerationIntroComplete(true);
475
+ containerRef.current?.setAttribute("data-touch-controls", "false");
416
476
  const audio = audioRef.current;
417
477
  if (audio) {
418
478
  void audio.play().then(() => {
@@ -463,39 +523,42 @@ function VideoPlayerRuntime({
463
523
  if (isPlaying) setIsPlaying(false);
464
524
  else startPlayback();
465
525
  };
466
- const toggleFullscreen = async () => {
526
+ const toggleFullscreen = () => {
467
527
  const container = containerRef.current;
468
528
  if (!container) return;
469
- if (document.fullscreenElement === container) await document.exitFullscreen?.();
470
- else await container.requestFullscreen?.();
529
+ void import("./control-visibility-NQOWTKOQ.js").then((output) => output.togglePlayerFullscreen(container, setFullscreenMode));
471
530
  };
531
+ const controlSize = Math.max(40, Math.min(52, Math.round(displayWidth * 0.15)));
532
+ const controlInset = Math.max(10, Math.min(20, Math.round(displayWidth * 0.056)));
472
533
  const controlButtonStyle = {
473
534
  display: "inline-grid",
474
535
  placeItems: "center",
475
- flex: "0 0 auto",
476
- minWidth: 44,
477
- minHeight: 44,
478
- padding: "6px 9px",
479
- border: 0,
480
- borderRadius: 8,
481
- backgroundColor: "rgba(9, 7, 18, 0.88)",
536
+ width: controlSize,
537
+ height: controlSize,
538
+ minWidth: controlSize,
539
+ minHeight: controlSize,
540
+ padding: 0,
541
+ border: "1px solid rgba(255, 255, 255, 0.08)",
542
+ borderRadius: 999,
543
+ backgroundColor: "rgba(255, 255, 255, 0.16)",
482
544
  color: "#ffffff",
483
- font: "600 13px/1 system-ui, sans-serif",
545
+ boxShadow: "0 5px 18px rgba(0, 0, 0, 0.16)",
546
+ backdropFilter: "blur(12px)",
484
547
  cursor: "pointer"
485
548
  };
486
549
  const startButtonStyle = {
487
550
  display: "inline-flex",
488
551
  alignItems: "center",
489
552
  gap: 10,
490
- minHeight: 54,
491
- padding: "12px 18px",
492
- border: "1px solid rgba(255, 255, 255, 0.3)",
553
+ minHeight: 60,
554
+ padding: "14px 22px",
555
+ border: "1px solid rgba(9, 7, 18, 0.08)",
493
556
  borderRadius: 999,
494
- backgroundColor: "rgba(9, 7, 18, 0.88)",
495
- color: "#ffffff",
496
- boxShadow: "0 8px 28px rgba(0, 0, 0, 0.4)",
497
- backdropFilter: "blur(12px)",
498
- font: "700 15px/1 system-ui, sans-serif",
557
+ backgroundColor: "#ffffff",
558
+ color: "#090712",
559
+ boxShadow: "0 8px 28px rgba(0, 0, 0, 0.24)",
560
+ font: "700 16px/1 system-ui, sans-serif",
561
+ whiteSpace: "nowrap",
499
562
  cursor: "pointer"
500
563
  };
501
564
  const startButtonPositionStyle = {
@@ -518,6 +581,8 @@ function VideoPlayerRuntime({
518
581
  role: "region",
519
582
  "aria-label": ariaLabel,
520
583
  tabIndex: 0,
584
+ onClickCapture: primeSoundtrack,
585
+ onKeyDownCapture: primeSoundtrack,
521
586
  onKeyDown: (event) => {
522
587
  if (event.target === event.currentTarget && (event.key === " " || event.key === "Enter")) {
523
588
  event.preventDefault();
@@ -538,6 +603,7 @@ function VideoPlayerRuntime({
538
603
  "data-generation-intro-complete": generationIntroComplete,
539
604
  "data-audio-unlocked": audioUnlocked,
540
605
  "data-playback-mode": playbackMode ?? "custom",
606
+ "data-fullscreen": fullscreenMode,
541
607
  className,
542
608
  style: {
543
609
  width: width ?? "100%",
@@ -579,7 +645,7 @@ function VideoPlayerRuntime({
579
645
  onClick: armPlayback,
580
646
  style: { ...startButtonStyle, ...startButtonPositionStyle },
581
647
  children: [
582
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B6" }),
648
+ /* @__PURE__ */ jsx(PlayerIcon, { name: "play", size: 20 }),
583
649
  config?.audio && !isMuted ? "Play with sound" : "Play video"
584
650
  ]
585
651
  }
@@ -597,7 +663,8 @@ function VideoPlayerRuntime({
597
663
  playing: isPlaying,
598
664
  style: {
599
665
  position: "absolute",
600
- inset: 0,
666
+ left: 0,
667
+ top: 0,
601
668
  transform: `scale(${scale})`,
602
669
  transformOrigin: "top left"
603
670
  }
@@ -615,7 +682,7 @@ function VideoPlayerRuntime({
615
682
  ...startButtonPositionStyle
616
683
  },
617
684
  children: [
618
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u25B6" }),
685
+ /* @__PURE__ */ jsx(PlayerIcon, { name: "play", size: 20 }),
619
686
  config?.audio && !isMuted ? "Play with sound" : "Play video"
620
687
  ]
621
688
  }
@@ -625,66 +692,106 @@ function VideoPlayerRuntime({
625
692
  {
626
693
  ref: audioRef,
627
694
  src: config.audio.audioUrl,
695
+ "data-v": config.audio.volume,
628
696
  autoPlay: isPlaying,
629
697
  muted: isMuted,
630
698
  preload: "auto",
631
699
  loop: state.status === "streaming" || introPlaying
700
+ },
701
+ config.audio.audioUrl
702
+ ) : null,
703
+ ended ? /* @__PURE__ */ jsx(
704
+ "div",
705
+ {
706
+ "data-testid": "video-ended-scrim",
707
+ "aria-hidden": "true",
708
+ style: {
709
+ position: "absolute",
710
+ inset: 0,
711
+ zIndex: 1,
712
+ pointerEvents: "none",
713
+ background: "rgba(4, 3, 18, 0.52)",
714
+ backdropFilter: "blur(4px)"
715
+ }
632
716
  }
633
717
  ) : null,
634
- !generationCoverVisible ? /* @__PURE__ */ jsxs(
718
+ ended ? /* @__PURE__ */ jsxs(
719
+ "button",
720
+ {
721
+ type: "button",
722
+ "data-testid": "video-replay-button",
723
+ "aria-label": "Replay video response",
724
+ onClick: togglePlayback,
725
+ style: {
726
+ ...startButtonStyle,
727
+ ...startButtonPositionStyle,
728
+ zIndex: 3
729
+ },
730
+ children: [
731
+ /* @__PURE__ */ jsx(PlayerIcon, { name: "replay", size: 21 }),
732
+ "Replay"
733
+ ]
734
+ }
735
+ ) : null,
736
+ !generationCoverVisible && !showStartPoster && config?.scenes.length ? /* @__PURE__ */ jsxs(
635
737
  "div",
636
738
  {
637
739
  "data-testid": "video-controls",
638
740
  style: {
639
741
  position: "absolute",
640
- right: 10,
641
- bottom: 10,
642
- zIndex: 2,
643
- display: "flex",
644
- alignItems: "center",
645
- gap: 6,
646
- minHeight: 48,
647
- padding: "6px 8px",
648
- border: "1px solid rgba(255, 255, 255, 0.2)",
649
- borderRadius: 12,
650
- backgroundColor: "rgba(9, 7, 18, 0.82)",
651
- backdropFilter: "blur(12px)",
652
- color: "#ffffff",
653
- boxShadow: "0 4px 20px rgba(0, 0, 0, 0.35)"
742
+ inset: 0,
743
+ zIndex: 4,
744
+ pointerEvents: "none"
654
745
  },
655
746
  children: [
656
- !showStartPoster ? /* @__PURE__ */ jsx(
657
- "button",
747
+ /* @__PURE__ */ jsx(
748
+ "div",
658
749
  {
659
- type: "button",
660
- "aria-label": ended ? "Replay video response" : isPlaying ? "Pause video response" : "Play video response",
661
- onClick: togglePlayback,
662
- style: controlButtonStyle,
663
- children: ended ? "\u21BB Replay" : isPlaying ? "\u2161" : "\u25B6"
750
+ "data-testid": "video-primary-controls",
751
+ style: { position: "absolute", left: controlInset, bottom: controlInset, display: "flex", pointerEvents: "auto" },
752
+ children: /* @__PURE__ */ jsx(
753
+ "button",
754
+ {
755
+ type: "button",
756
+ "aria-label": ended ? "Play video response from beginning" : isPlaying ? "Pause video response" : "Play video response",
757
+ onClick: togglePlayback,
758
+ style: controlButtonStyle,
759
+ children: /* @__PURE__ */ jsx(PlayerIcon, { name: isPlaying ? "pause" : "play" })
760
+ }
761
+ )
664
762
  }
665
- ) : null,
666
- config?.audio ? /* @__PURE__ */ jsx(
667
- "button",
763
+ ),
764
+ /* @__PURE__ */ jsxs(
765
+ "div",
668
766
  {
669
- type: "button",
670
- "aria-label": isMuted ? "Unmute video response" : "Mute video response",
671
- "aria-pressed": !isMuted,
672
- onClick: () => setIsMuted((muted) => {
673
- if (muted) setAudioUnlocked(true);
674
- return !muted;
675
- }),
676
- style: controlButtonStyle,
677
- children: isMuted ? "\u{1F507}" : "\u{1F50A}"
678
- }
679
- ) : null,
680
- /* @__PURE__ */ jsx(
681
- "button",
682
- {
683
- type: "button",
684
- "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
685
- onClick: () => void toggleFullscreen(),
686
- style: controlButtonStyle,
687
- children: isFullscreen ? "\u2199" : "\u26F6"
767
+ "data-testid": "video-secondary-controls",
768
+ style: { position: "absolute", right: controlInset, bottom: controlInset, display: "flex", gap: 10, pointerEvents: "auto" },
769
+ children: [
770
+ config?.audio ? /* @__PURE__ */ jsx(
771
+ "button",
772
+ {
773
+ type: "button",
774
+ "aria-label": isMuted ? "Unmute video response" : "Mute video response",
775
+ "aria-pressed": !isMuted,
776
+ onClick: () => setIsMuted((muted) => {
777
+ if (muted) setAudioUnlocked(true);
778
+ return !muted;
779
+ }),
780
+ style: controlButtonStyle,
781
+ children: /* @__PURE__ */ jsx(PlayerIcon, { name: isMuted ? "volume-off" : "volume" })
782
+ }
783
+ ) : null,
784
+ /* @__PURE__ */ jsx(
785
+ "button",
786
+ {
787
+ type: "button",
788
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
789
+ onClick: () => void toggleFullscreen(),
790
+ style: controlButtonStyle,
791
+ children: /* @__PURE__ */ jsx(PlayerIcon, { name: isFullscreen ? "exit-fullscreen" : "enter-fullscreen" })
792
+ }
793
+ )
794
+ ]
688
795
  }
689
796
  )
690
797
  ]
@@ -976,9 +1083,13 @@ function useVideo(options = {}) {
976
1083
  credentials: currentOptions.credentials,
977
1084
  fetcher: currentOptions.fetcher,
978
1085
  onEvent: (_event, nextState) => {
979
- if (_event.type === "scene.add" && !currentOptions.templates?.getTemplate(_event.data.scene.templateId)) {
980
- preloadBuiltinTemplate(_event.data.scene.templateId);
1086
+ if (_event.type === "scene.add") {
1087
+ if (!currentOptions.templates?.getTemplate(_event.data.scene.templateId)) {
1088
+ preloadBuiltinTemplate(_event.data.scene.templateId);
1089
+ }
981
1090
  }
1091
+ const backdrop = _event.type === "scene.add" ? _event.data.scene.variables : _event.type === "asset.patch" ? _event.data.variables : _event.type === "scene.patch" ? _event.data.patch.variables : void 0;
1092
+ if (backdrop) warmSceneMedia(backdrop);
982
1093
  if (mounted.current && generation.current === currentGeneration) setState(nextState);
983
1094
  }
984
1095
  });
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  SceneBackground
3
- } from "./chunk-IFOW65Z6.js";
3
+ } from "./chunk-4AODC6MS.js";
4
+ import "./chunk-35LIYILY.js";
4
5
  import {
5
6
  TemplateText
6
- } from "./chunk-BCRUUJ6A.js";
7
+ } from "./chunk-TCEHK2BW.js";
7
8
  import "./chunk-L5BTTCZV.js";
8
- import "./chunk-ABZACD4G.js";
9
+ import "./chunk-IJM4B2ZN.js";
9
10
  import "./chunk-XTOXFMLM.js";
10
11
  import {
11
12
  resolveTokens
12
- } from "./chunk-FNAEQ7QO.js";
13
+ } from "./chunk-XWWLKRNU.js";
13
14
 
14
15
  // src/visual-system/scene-templates/reaction.tsx
15
16
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, k as VideoCapabilities, d as VideoInput, a as VideoAudio, p as VideoSceneValidator, q as VideoTemplatePacing, r as VideoSnapshotRetention, s as VideoResumeCursor, t as VideoSceneValidationContext, g as VideoScene } from './types-CkO2EYr4.js';
2
- import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-CTIsANzz.js';
3
- export { c as VideoWarningCategory } from './events-CTIsANzz.js';
1
+ import { m as VideoGenerationContext, n as VideoPlanner, o as VideoRequest, k as VideoCapabilities, d as VideoInput, a as VideoAudio, p as VideoSceneValidator, q as VideoTemplatePacing, r as VideoSnapshotRetention, s as VideoResumeCursor, t as VideoSceneValidationContext, g as VideoScene } from './types-CVMb6QEq.js';
2
+ import { b as VideoFinishReason, a as VideoWarning, V as VideoEvent } from './events-tQ0x-VaL.js';
3
+ export { c as VideoWarningCategory } from './events-tQ0x-VaL.js';
4
4
  import { S as SceneTemplateMetadata } from './catalog-types-BIhSpOWK.js';
5
5
 
6
6
  interface VideoProviderUsage {
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-BKF3A357.js";
4
4
  import {
5
5
  createVideo
6
- } from "./chunk-XYZOJ5NZ.js";
6
+ } from "./chunk-LHFADNWJ.js";
7
7
  import "./chunk-E7CL7UPB.js";
8
8
  import {
9
9
  BUILTIN_SERVER_TEMPLATE_KIT,
@@ -161,7 +161,7 @@ function parseVideoRequest(value) {
161
161
  "request.input.orientation"
162
162
  );
163
163
  }
164
- if (input.opening != null) string(input.opening, "request.input.opening");
164
+ if (input.opening != null && input.opening !== false) string(input.opening, "request.input.opening");
165
165
  if (input.audio != null && input.audio !== false) {
166
166
  const soundtrack = record(input.audio, "request.input.audio");
167
167
  allowedKeys(soundtrack, ["src"], "request.input.audio");
@@ -5,19 +5,22 @@ import {
5
5
  import {
6
6
  SceneBackground,
7
7
  getMediaBackgroundProps
8
- } from "./chunk-IFOW65Z6.js";
8
+ } from "./chunk-4AODC6MS.js";
9
+ import {
10
+ hasSceneMedia
11
+ } from "./chunk-35LIYILY.js";
9
12
  import {
10
13
  TemplateText
11
- } from "./chunk-BCRUUJ6A.js";
14
+ } from "./chunk-TCEHK2BW.js";
12
15
  import "./chunk-L5BTTCZV.js";
13
- import "./chunk-ABZACD4G.js";
16
+ import "./chunk-IJM4B2ZN.js";
14
17
  import {
15
18
  interpolate,
16
19
  spring
17
20
  } from "./chunk-XTOXFMLM.js";
18
21
  import {
19
22
  resolveTokens
20
- } from "./chunk-FNAEQ7QO.js";
23
+ } from "./chunk-XWWLKRNU.js";
21
24
 
22
25
  // src/visual-system/primitives/devices/CodeEditor.tsx
23
26
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -314,6 +317,7 @@ var ShowcaseCodeTemplate = ({
314
317
  /* @__PURE__ */ jsx2(
315
318
  TemplateText,
316
319
  {
320
+ overMedia: hasSceneMedia(variables),
317
321
  motionProgress,
318
322
  typeTreatment: resolveTokens(style).preset.type,
319
323
  archetype: "subtle",
@@ -2,17 +2,20 @@ import {
2
2
  PhoneScreenFill,
3
3
  ProductSurface,
4
4
  resolveProductSurfaceMotion
5
- } from "./chunk-FSG2PYWG.js";
5
+ } from "./chunk-G5JPHBX7.js";
6
6
  import "./chunk-S7HFLYNN.js";
7
7
  import {
8
8
  SceneBackground,
9
9
  getMediaBackgroundProps
10
- } from "./chunk-IFOW65Z6.js";
10
+ } from "./chunk-4AODC6MS.js";
11
+ import {
12
+ hasSceneMedia
13
+ } from "./chunk-35LIYILY.js";
11
14
  import {
12
15
  TemplateText
13
- } from "./chunk-BCRUUJ6A.js";
16
+ } from "./chunk-TCEHK2BW.js";
14
17
  import "./chunk-L5BTTCZV.js";
15
- import "./chunk-ABZACD4G.js";
18
+ import "./chunk-IJM4B2ZN.js";
16
19
  import {
17
20
  SPRING_SMOOTH,
18
21
  interpolate,
@@ -21,7 +24,7 @@ import {
21
24
  import {
22
25
  TOKEN_DEFAULTS,
23
26
  resolveTokens
24
- } from "./chunk-FNAEQ7QO.js";
27
+ } from "./chunk-XWWLKRNU.js";
25
28
 
26
29
  // src/visual-system/primitives/devices/PhoneFrame.tsx
27
30
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -224,6 +227,7 @@ var ShowcasePhoneTemplate = ({
224
227
  /* @__PURE__ */ jsx2(
225
228
  TemplateText,
226
229
  {
230
+ overMedia: hasSceneMedia(variables),
227
231
  motionProgress,
228
232
  typeTreatment: tokens.preset.type,
229
233
  archetype: textArchetype ?? "subtle",
@@ -7,12 +7,15 @@ import {
7
7
  import {
8
8
  SceneBackground,
9
9
  getMediaBackgroundProps
10
- } from "./chunk-IFOW65Z6.js";
10
+ } from "./chunk-4AODC6MS.js";
11
+ import {
12
+ hasSceneMedia
13
+ } from "./chunk-35LIYILY.js";
11
14
  import {
12
15
  TemplateText
13
- } from "./chunk-BCRUUJ6A.js";
16
+ } from "./chunk-TCEHK2BW.js";
14
17
  import "./chunk-L5BTTCZV.js";
15
- import "./chunk-ABZACD4G.js";
18
+ import "./chunk-IJM4B2ZN.js";
16
19
  import {
17
20
  interpolate,
18
21
  spring
@@ -20,7 +23,7 @@ import {
20
23
  import {
21
24
  TOKEN_DEFAULTS,
22
25
  resolveTokens
23
- } from "./chunk-FNAEQ7QO.js";
26
+ } from "./chunk-XWWLKRNU.js";
24
27
 
25
28
  // src/visual-system/primitives/devices/TerminalOutput.tsx
26
29
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -268,6 +271,7 @@ var ShowcaseTerminalTemplate = ({
268
271
  /* @__PURE__ */ jsx2(
269
272
  TemplateText,
270
273
  {
274
+ overMedia: hasSceneMedia(variables),
271
275
  motionProgress,
272
276
  typeTreatment: resolveTokens(style).preset.type,
273
277
  archetype: "subtle",