@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/CHANGELOG.md CHANGED
@@ -4,6 +4,45 @@ VanillaSky follows semantic versioning. This changelog begins with the 0.1 beta.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.3.2
8
+
9
+ - Rebuilds text legibility over photo and video backdrops. Scrims now ramp off
10
+ an eased curve instead of a two-stop linear fade, so they no longer leave a
11
+ visible band across the frame, and they are shaped to where each template's
12
+ copy actually sits rather than washing the whole picture. Type over media
13
+ carries its own halo, which lets the scrims stay lighter: the footage keeps
14
+ its contrast and highlights while the headline stays readable.
15
+ - Stops darkening the brand gradient for media that never arrives. A dead,
16
+ blocked, or unresolved `mediaUrl` used to leave the full scrim stack over
17
+ the gradient fallback, so the scene rendered as a muddy, vignetted version
18
+ of the gradient scenes beside it. The fallback is now the clean gradient it
19
+ was always documented to be.
20
+ - Holds a media scene's scrim back until the backdrop actually paints, so a
21
+ photo or video that is still loading shows the clean brand gradient instead
22
+ of a gradient wearing an overlay meant for footage. The scrim and the
23
+ picture now arrive on the same frame.
24
+ - Preloads scene backdrops as soon as their URL is known — including the
25
+ `asset.patch` that carries a resolved stock lookup — so the loading window
26
+ is usually gone before the scene plays. Video posters are warmed; video
27
+ streams deliberately are not.
28
+ - Hides playback controls while a video is playing until the viewer hovers,
29
+ focuses, or taps the player, and keeps paused and completed controls visible.
30
+ - Makes fullscreen usable on mobile browsers through prefixed fullscreen APIs
31
+ and a viewport-filling fallback when native fullscreen is unavailable.
32
+ - Restores configured soundtrack volume and end-of-video fades on iPhone Safari
33
+ by using a Web Audio gain stage for same-origin audio when element volume is
34
+ device-controlled, while preserving direct playback for cross-origin tracks.
35
+
36
+ ## 0.3.1
37
+
38
+ - Redesigns player controls for each playback state with responsive circular
39
+ icon controls, a sound-first start action, and a dimmed replay treatment that
40
+ leaves the completed poster frame visible.
41
+ - Lets applications pass `opening: false` to omit the deterministic opening
42
+ scene and render their own transient loading UI while the first generated
43
+ scene is planned. This keeps loading state out of completed video JSON and
44
+ does not force `media` into the planner's template capabilities.
45
+
7
46
  ## 0.3.0
8
47
 
9
48
  - Documents closer eligibility: a template may close a video only when its
package/PUBLIC-API.md CHANGED
@@ -183,6 +183,10 @@ mounted player autoplay with sound after the first successful viewer start.
183
183
  `manual`, `muted-autoplay`, and `autoplay-with-sound` cover the other browser
184
184
  startup policies.
185
185
 
186
+ `VideoInput.opening` accepts custom copy, uses the deterministic fallback when
187
+ omitted, and accepts `false` when the application owns transient loading UI and
188
+ wants the completed video to begin with the first generated scene.
189
+
186
190
  Saved-video playback performs no generation request. `VideoPlayerBinding` and
187
191
  the internal reducer state are not public types.
188
192
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Give your AI a video output
2
2
 
3
- ![Version 0.3.0 beta](https://img.shields.io/badge/version-0.3.0_beta-7c3aed)
3
+ ![Version 0.3.2 beta](https://img.shields.io/badge/version-0.3.2_beta-7c3aed)
4
4
 
5
5
  **VanillaSky is the open-source video response layer.** Turn text, structured
6
6
  data, and live application context into personalized video responses that start
@@ -17,7 +17,7 @@ the planning prompt, trusted templates, validation, streaming, and player.
17
17
  For humans:
18
18
 
19
19
  ```bash
20
- npm install @vanillaskyai/video@0.3.0 ai @ai-sdk/openai
20
+ npm install @vanillaskyai/video@0.3.2 ai @ai-sdk/openai
21
21
  ```
22
22
 
23
23
  For coding agents:
@@ -106,7 +106,7 @@ shows each complete, validated scene as soon as it is ready and returns a
106
106
  deterministic `Video` object when generation finishes.
107
107
 
108
108
  A copy-and-run app is in
109
- [`examples/nextjs-quickstart`](https://github.com/VanillaSkyAi/video/tree/v0.3.0/examples/nextjs-quickstart).
109
+ [`examples/nextjs-quickstart`](https://github.com/VanillaSkyAi/video/tree/v0.3.2/examples/nextjs-quickstart).
110
110
 
111
111
  ## Shape the response
112
112
 
@@ -4,15 +4,15 @@ import {
4
4
  import "./chunk-QAAO7OAY.js";
5
5
  import {
6
6
  TemplateText
7
- } from "./chunk-BCRUUJ6A.js";
7
+ } from "./chunk-TCEHK2BW.js";
8
8
  import "./chunk-L5BTTCZV.js";
9
9
  import {
10
10
  BrandGradientOverlay
11
- } from "./chunk-ABZACD4G.js";
11
+ } from "./chunk-IJM4B2ZN.js";
12
12
  import "./chunk-XTOXFMLM.js";
13
13
  import {
14
14
  resolveTokens
15
- } from "./chunk-FNAEQ7QO.js";
15
+ } from "./chunk-XWWLKRNU.js";
16
16
 
17
17
  // src/visual-system/scene-templates/bg-confetti.tsx
18
18
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -3,17 +3,17 @@ import {
3
3
  } from "./chunk-QAAO7OAY.js";
4
4
  import {
5
5
  TemplateText
6
- } from "./chunk-BCRUUJ6A.js";
6
+ } from "./chunk-TCEHK2BW.js";
7
7
  import {
8
8
  Emoji
9
9
  } from "./chunk-L5BTTCZV.js";
10
10
  import {
11
11
  BrandGradientOverlay
12
- } from "./chunk-ABZACD4G.js";
12
+ } from "./chunk-IJM4B2ZN.js";
13
13
  import "./chunk-XTOXFMLM.js";
14
14
  import {
15
15
  resolveTokens
16
- } from "./chunk-FNAEQ7QO.js";
16
+ } from "./chunk-XWWLKRNU.js";
17
17
 
18
18
  // src/visual-system/scene-templates/bg-emoji.tsx
19
19
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,20 +1,23 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-IFOW65Z6.js";
4
+ } from "./chunk-4AODC6MS.js";
5
+ import {
6
+ hasSceneMedia
7
+ } from "./chunk-35LIYILY.js";
5
8
  import {
6
9
  ConfettiLayer
7
10
  } from "./chunk-CJ7F2CE6.js";
8
11
  import "./chunk-QAAO7OAY.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 "./chunk-XTOXFMLM.js";
15
18
  import {
16
19
  resolveTokens
17
- } from "./chunk-FNAEQ7QO.js";
20
+ } from "./chunk-XWWLKRNU.js";
18
21
 
19
22
  // src/visual-system/scene-templates/bg-media.tsx
20
23
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -55,6 +58,7 @@ var BgMediaTemplate = ({
55
58
  width,
56
59
  height,
57
60
  ...getMediaBackgroundProps(variables),
61
+ textAnchor: "center",
58
62
  backgroundEffect,
59
63
  seed: String(variables.texts || ""),
60
64
  isPlaying,
@@ -74,6 +78,7 @@ var BgMediaTemplate = ({
74
78
  /* @__PURE__ */ jsx(
75
79
  TemplateText,
76
80
  {
81
+ overMedia: hasSceneMedia(variables),
77
82
  motionProgress,
78
83
  typeTreatment: resolveTokens(style).preset.type,
79
84
  archetype: textArchetype ?? "subtle",
@@ -4,15 +4,16 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-IFOW65Z6.js";
7
+ } from "./chunk-4AODC6MS.js";
8
+ import "./chunk-35LIYILY.js";
8
9
  import {
9
10
  renderWithEmoji
10
11
  } from "./chunk-L5BTTCZV.js";
11
- import "./chunk-ABZACD4G.js";
12
+ import "./chunk-IJM4B2ZN.js";
12
13
  import {
13
14
  interpolate
14
15
  } from "./chunk-XTOXFMLM.js";
15
- import "./chunk-FNAEQ7QO.js";
16
+ import "./chunk-XWWLKRNU.js";
16
17
 
17
18
  // src/visual-system/primitives/typography/BrandMessageBubble.tsx
18
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  SceneBackground,
3
3
  getMediaBackgroundProps
4
- } from "./chunk-IFOW65Z6.js";
4
+ } from "./chunk-4AODC6MS.js";
5
+ import {
6
+ hasSceneMedia
7
+ } from "./chunk-35LIYILY.js";
5
8
  import {
6
9
  TemplateText
7
- } from "./chunk-BCRUUJ6A.js";
10
+ } from "./chunk-TCEHK2BW.js";
8
11
  import "./chunk-L5BTTCZV.js";
9
12
  import {
10
13
  TOP_TEXT_AREA_RATIO,
11
14
  withOpacity
12
- } from "./chunk-ABZACD4G.js";
15
+ } from "./chunk-IJM4B2ZN.js";
13
16
  import {
14
17
  SPRING_SNAPPY,
15
18
  interpolate,
@@ -17,7 +20,7 @@ import {
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/charts/BarChart.tsx
23
26
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -279,6 +282,7 @@ var ChartBarTemplate = ({
279
282
  /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: 0, left: 0, right: 0, height: height * TOP_TEXT_AREA_RATIO, overflow: "visible" }, children: /* @__PURE__ */ jsx2(
280
283
  TemplateText,
281
284
  {
285
+ overMedia: hasSceneMedia(variables),
282
286
  motionProgress,
283
287
  typeTreatment: resolveTokens(style).preset.type,
284
288
  archetype: "subtle",
@@ -4,27 +4,31 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-IFOW65Z6.js";
7
+ } from "./chunk-4AODC6MS.js";
8
+ import {
9
+ hasSceneMedia
10
+ } from "./chunk-35LIYILY.js";
8
11
  import {
9
12
  ConfettiLayer
10
13
  } from "./chunk-CJ7F2CE6.js";
11
14
  import "./chunk-QAAO7OAY.js";
12
15
  import {
13
16
  TemplateText
14
- } from "./chunk-BCRUUJ6A.js";
17
+ } from "./chunk-TCEHK2BW.js";
15
18
  import "./chunk-L5BTTCZV.js";
16
19
  import {
17
20
  TOP_TEXT_AREA_RATIO,
18
21
  withOpacity
19
- } from "./chunk-ABZACD4G.js";
22
+ } from "./chunk-IJM4B2ZN.js";
20
23
  import {
21
24
  interpolate,
22
25
  spring
23
26
  } from "./chunk-XTOXFMLM.js";
24
27
  import {
28
+ MEDIA_TEXT_SHADOW,
25
29
  TOKEN_DEFAULTS,
26
30
  resolveTokens
27
- } from "./chunk-FNAEQ7QO.js";
31
+ } from "./chunk-XWWLKRNU.js";
28
32
 
29
33
  // src/visual-system/primitives/typography/CountUpNumber.tsx
30
34
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -93,7 +97,7 @@ var CountUpNumber = ({
93
97
  [0, 1],
94
98
  { extrapolateLeft: "clamp", extrapolateRight: "clamp" }
95
99
  );
96
- const mediaTextShadow = hasMediaShadow ? "0 2px 8px rgba(0,0,0,0.55), 0 6px 16px rgba(0,0,0,0.35)" : void 0;
100
+ const mediaTextShadow = hasMediaShadow ? MEDIA_TEXT_SHADOW : void 0;
97
101
  const finalLabelFontSize = labelFontSize ?? 36 * s * 1.3;
98
102
  return /* @__PURE__ */ jsxs(
99
103
  "div",
@@ -253,6 +257,7 @@ var ChartCounterTemplate = ({
253
257
  /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: 0, left: 0, right: 0, height: height * TOP_TEXT_AREA_RATIO, overflow: "visible" }, children: /* @__PURE__ */ jsx2(
254
258
  TemplateText,
255
259
  {
260
+ overMedia: hasSceneMedia(variables),
256
261
  motionProgress,
257
262
  typeTreatment: resolveTokens(style).preset.type,
258
263
  archetype: "subtle",
@@ -5,22 +5,25 @@ 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
16
  import {
14
17
  TOP_TEXT_AREA_RATIO,
15
18
  withOpacity
16
- } from "./chunk-ABZACD4G.js";
19
+ } from "./chunk-IJM4B2ZN.js";
17
20
  import {
18
21
  interpolate
19
22
  } from "./chunk-XTOXFMLM.js";
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/charts/ProgressRing.tsx
26
29
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -254,6 +257,7 @@ var ChartProgressRingTemplate = ({
254
257
  /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: 0, left: 0, right: 0, height: height * TOP_TEXT_AREA_RATIO, overflow: "visible" }, children: /* @__PURE__ */ jsx2(
255
258
  TemplateText,
256
259
  {
260
+ overMedia: hasSceneMedia(variables),
257
261
  motionProgress,
258
262
  typeTreatment: resolveTokens(style).preset.type,
259
263
  archetype: "subtle",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VideoFrame
3
- } from "./chunk-QTMHS7JD.js";
4
- import "./chunk-FNAEQ7QO.js";
3
+ } from "./chunk-HXSV6RHF.js";
4
+ import "./chunk-XWWLKRNU.js";
5
5
  import "./chunk-SPVTJH3F.js";
6
6
  import {
7
7
  resolveVideoBrand
@@ -0,0 +1,25 @@
1
+ // src/visual-system/scene-templates/media-source.ts
2
+ var VIDEO_EXTENSIONS = [".mp4", ".webm", ".mov", ".m4v", ".avi"];
3
+ function isVideoUrl(url) {
4
+ try {
5
+ const pathname = new URL(url).pathname.toLowerCase();
6
+ return VIDEO_EXTENSIONS.some((ext) => pathname.endsWith(ext));
7
+ } catch {
8
+ const lower = url.toLowerCase();
9
+ return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
10
+ }
11
+ }
12
+ function resolveMediaType(mediaType, mediaUrl) {
13
+ if (mediaType === "gradient") return "gradient";
14
+ if (mediaType === "video") return "video";
15
+ if (mediaType === "photo") return "photo";
16
+ return mediaUrl && isVideoUrl(mediaUrl) ? "video" : "photo";
17
+ }
18
+ function hasSceneMedia(variables) {
19
+ return String(variables.mediaUrl || "").trim() !== "" && String(variables.mediaType || "auto") !== "gradient";
20
+ }
21
+
22
+ export {
23
+ resolveMediaType,
24
+ hasSceneMedia
25
+ };
@@ -1,21 +1,14 @@
1
+ import {
2
+ resolveMediaType
3
+ } from "./chunk-35LIYILY.js";
1
4
  import {
2
5
  BrandGradientOverlay,
3
6
  getBackgroundTransform
4
- } from "./chunk-ABZACD4G.js";
7
+ } from "./chunk-IJM4B2ZN.js";
5
8
 
6
9
  // src/visual-system/scene-templates/scene-background.tsx
7
- import { useEffect, useRef } from "react";
10
+ import { useEffect, useRef, useState } from "react";
8
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
- var VIDEO_EXTENSIONS = [".mp4", ".webm", ".mov", ".m4v", ".avi"];
10
- function isVideoUrl(url) {
11
- try {
12
- const pathname = new URL(url).pathname.toLowerCase();
13
- return VIDEO_EXTENSIONS.some((ext) => pathname.endsWith(ext));
14
- } catch {
15
- const lower = url.toLowerCase();
16
- return VIDEO_EXTENSIONS.some((ext) => lower.endsWith(ext));
17
- }
18
- }
19
12
  var MEDIA_POSITIONS = {
20
13
  center: "center center",
21
14
  top: "center top",
@@ -29,38 +22,57 @@ function resolveMediaPosition(value) {
29
22
  function resolveMediaTreatment(value) {
30
23
  return value === "subtle" || value === "text-safe" ? value : "cinematic";
31
24
  }
32
- function getMediaTreatmentLayers(value) {
25
+ var SCRIM_STOP_COUNT = 7;
26
+ function smoothstep(t) {
27
+ return t * t * (3 - 2 * t);
28
+ }
29
+ function easedStops(peakAlpha, start, end, direction) {
30
+ const alphaAt = (t) => {
31
+ const eased = direction === "fade-out" ? 1 - smoothstep(t) : smoothstep(t);
32
+ return `rgba(0,0,0,${Number((peakAlpha * eased).toFixed(3))})`;
33
+ };
34
+ const stops = [];
35
+ if (start > 0) stops.push(`${alphaAt(0)} 0%`);
36
+ for (let i = 0; i < SCRIM_STOP_COUNT; i += 1) {
37
+ const t = i / (SCRIM_STOP_COUNT - 1);
38
+ const position = Number((start + (end - start) * t).toFixed(2));
39
+ stops.push(`${alphaAt(t)} ${position}%`);
40
+ }
41
+ if (end < 100) stops.push(`${alphaAt(1)} 100%`);
42
+ return stops.join(", ");
43
+ }
44
+ function getMediaTreatmentLayers(value, anchor = "full") {
33
45
  const treatment = resolveMediaTreatment(value);
34
46
  const vignette = {
35
47
  id: "vignette",
36
- background: treatment === "subtle" ? "radial-gradient(ellipse at center, transparent 45%, rgba(0,0,0,0.28) 100%)" : "radial-gradient(ellipse at center, transparent 30%, rgba(0,0,0,0.55) 80%, rgba(0,0,0,0.75) 100%)"
48
+ background: treatment === "subtle" ? `radial-gradient(ellipse at center, ${easedStops(0.28, 45, 100, "fade-in")})` : `radial-gradient(ellipse at center, ${easedStops(0.72, 32, 100, "fade-in")})`
37
49
  };
38
50
  if (treatment === "subtle") return [vignette];
39
- const cinematic = [
40
- vignette,
41
- {
51
+ const textSafe = treatment === "text-safe";
52
+ const layers = [vignette];
53
+ if (anchor !== "bottom") {
54
+ layers.push({
42
55
  id: "center-scrim",
43
- background: treatment === "text-safe" ? "radial-gradient(ellipse 90% 56% at 50% 50%, rgba(0,0,0,0.36) 0%, rgba(0,0,0,0.18) 55%, transparent 84%)" : "radial-gradient(ellipse 85% 50% at 50% 50%, rgba(0,0,0,0.22) 0%, rgba(0,0,0,0.10) 50%, transparent 80%)"
44
- },
45
- {
56
+ background: textSafe ? `radial-gradient(ellipse 92% 58% at 50% 50%, ${easedStops(0.46, 34, 90, "fade-out")})` : `radial-gradient(ellipse 88% 52% at 50% 50%, ${easedStops(0.26, 30, 88, "fade-out")})`
57
+ });
58
+ }
59
+ if (anchor !== "center") {
60
+ layers.push({
46
61
  id: "bottom-scrim",
47
- background: treatment === "text-safe" ? "linear-gradient(to top, rgba(0,0,0,0.68) 0%, transparent 100%)" : "linear-gradient(to top, rgba(0,0,0,0.5) 0%, transparent 100%)",
62
+ background: `linear-gradient(to top, ${easedStops(textSafe ? 0.64 : 0.5, 8, 100, "fade-out")})`,
48
63
  style: { top: "55%" }
49
- }
50
- ];
51
- if (treatment === "text-safe") {
52
- cinematic.splice(1, 0, {
53
- id: "full-wash",
54
- background: "rgba(0,0,0,0.30)"
55
64
  });
56
65
  }
57
- return cinematic;
66
+ return layers;
58
67
  }
59
- function resolveMediaType(mediaType, mediaUrl) {
60
- if (mediaType === "gradient") return "gradient";
61
- if (mediaType === "video") return "video";
62
- if (mediaType === "photo") return "photo";
63
- return mediaUrl && isVideoUrl(mediaUrl) ? "video" : "photo";
68
+ function initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster) {
69
+ if (typeof window === "undefined") return "ready";
70
+ if (!wantsMedia) return "ready";
71
+ if (resolved === "video") return mediaPoster ? "ready" : "pending";
72
+ if (typeof Image === "undefined") return "ready";
73
+ const cached = new Image();
74
+ cached.src = mediaUrl;
75
+ return cached.complete && cached.naturalWidth > 0 ? "ready" : "pending";
64
76
  }
65
77
  function getMediaBackgroundProps(variables) {
66
78
  return {
@@ -83,6 +95,7 @@ var SceneBackground = ({
83
95
  mediaPoster,
84
96
  mediaPosition = "center",
85
97
  mediaTreatment = "cinematic",
98
+ textAnchor = "full",
86
99
  backgroundEffect,
87
100
  seed,
88
101
  isPlaying = true,
@@ -91,10 +104,35 @@ var SceneBackground = ({
91
104
  void _width;
92
105
  void _height;
93
106
  const resolved = resolveMediaType(mediaType, mediaUrl);
94
- const showMedia = resolved !== "gradient" && !!mediaUrl;
107
+ const wantsMedia = resolved !== "gradient" && !!mediaUrl;
108
+ const [mediaPaint, setMediaPaint] = useState(
109
+ () => initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster)
110
+ );
111
+ useEffect(() => {
112
+ setMediaPaint(initialMediaPaint(wantsMedia, resolved, mediaUrl, mediaPoster));
113
+ if (!wantsMedia || resolved !== "photo") return;
114
+ if (typeof Image === "undefined") return;
115
+ let cancelled = false;
116
+ const probe = new Image();
117
+ probe.onload = () => {
118
+ if (!cancelled) setMediaPaint("ready");
119
+ };
120
+ probe.onerror = () => {
121
+ if (!cancelled) setMediaPaint("failed");
122
+ };
123
+ probe.src = mediaUrl;
124
+ if (probe.complete) setMediaPaint(probe.naturalWidth > 0 ? "ready" : "failed");
125
+ return () => {
126
+ cancelled = true;
127
+ probe.onload = null;
128
+ probe.onerror = null;
129
+ };
130
+ }, [mediaUrl, mediaPoster, resolved, wantsMedia]);
131
+ const showMedia = wantsMedia && mediaPaint !== "failed";
132
+ const showTreatment = wantsMedia && mediaPaint === "ready";
95
133
  const resolvedPosition = resolveMediaPosition(mediaPosition);
96
134
  const resolvedTreatment = resolveMediaTreatment(mediaTreatment);
97
- const treatmentLayers = getMediaTreatmentLayers(resolvedTreatment);
135
+ const treatmentLayers = getMediaTreatmentLayers(resolvedTreatment, textAnchor);
98
136
  const gradSeed = typeof seed === "number" ? seed : typeof seed === "string" ? seed.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) : 0;
99
137
  const bgTransform = getBackgroundTransform(
100
138
  backgroundEffect,
@@ -154,6 +192,8 @@ var SceneBackground = ({
154
192
  loop: true,
155
193
  playsInline: true,
156
194
  preload: "auto",
195
+ onLoadedData: () => setMediaPaint("ready"),
196
+ onError: () => setMediaPaint("failed"),
157
197
  "data-media-position": mediaPosition,
158
198
  style: {
159
199
  position: "absolute",
@@ -181,7 +221,7 @@ var SceneBackground = ({
181
221
  }
182
222
  }
183
223
  )),
184
- showMedia && treatmentLayers.map((layer) => /* @__PURE__ */ jsx(
224
+ showTreatment && treatmentLayers.map((layer) => /* @__PURE__ */ jsx(
185
225
  "div",
186
226
  {
187
227
  "data-media-treatment": resolvedTreatment,
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-XTOXFMLM.js";
12
12
  import {
13
13
  TOKEN_DEFAULTS
14
- } from "./chunk-FNAEQ7QO.js";
14
+ } from "./chunk-XWWLKRNU.js";
15
15
 
16
16
  // src/visual-system/primitives/devices/product-surface-config.ts
17
17
  function resolveProductSurfaceMotion(value) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveDensity
3
- } from "./chunk-FNAEQ7QO.js";
3
+ } from "./chunk-XWWLKRNU.js";
4
4
  import {
5
5
  resolveVideoTimeline
6
6
  } from "./chunk-SPVTJH3F.js";
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  darken,
6
6
  resolveTokens
7
- } from "./chunk-FNAEQ7QO.js";
7
+ } from "./chunk-XWWLKRNU.js";
8
8
 
9
9
  // src/visual-system/theme/colors.ts
10
10
  function withOpacity(hex, opacity) {
@@ -97,7 +97,7 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
97
97
  "Compose a video response from the structured customer input below.",
98
98
  `Knowledge mode: ${input.knowledgeMode ?? "input-only"}.`,
99
99
  `Maximum duration: ${input.maxDurationSec ?? 30} seconds, including the supplied opening.`,
100
- input.opening?.trim() ? `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Continue after it and do not repeat or rewrite it.` : "Add the first grounded scene as soon as it is complete.",
100
+ typeof input.opening === "string" && input.opening.trim() ? `The host has already added the opening scene, which consumes ${openingDurationSec} seconds. Continue after it and do not repeat or rewrite it.` : "Add the first grounded scene as soon as it is complete.",
101
101
  "The first generated body scene must be fully playable without external media. Use a content-fit text, data, comparison, list, or device-free template with no media URL or keyword.",
102
102
  "Never use media, ctaMedia, or reaction as the first generated body template, even with mediaType=gradient. Choose a non-media template first.",
103
103
  "Add that scene before resolving any stock or supplied asset. Media belongs on later body scenes and must arrive without blocking scene additions.",
@@ -139,10 +139,11 @@ var DEFAULT_OPENING_TEXT = "Creating your video...";
139
139
  function resolveVideoInput(input) {
140
140
  return {
141
141
  ...input,
142
- opening: input.opening?.trim() || DEFAULT_OPENING_TEXT
142
+ opening: input.opening === false ? false : input.opening?.trim() || DEFAULT_OPENING_TEXT
143
143
  };
144
144
  }
145
- function resolveStreamCapabilities(capabilities) {
145
+ function resolveStreamCapabilities(capabilities, hasRuntimeOpening) {
146
+ if (!hasRuntimeOpening) return capabilities;
146
147
  if (capabilities?.templates == null) return capabilities;
147
148
  return {
148
149
  ...capabilities,
@@ -174,7 +175,7 @@ function safeAbortReason(value) {
174
175
  return safePublicDiagnostic(value instanceof Error ? value.message : value, "Request aborted");
175
176
  }
176
177
  function buildInitialConfig(input, audio, snapshotRetention, closerReserveSec, getTemplatePacing) {
177
- const openingText = input.opening?.trim();
178
+ const openingText = typeof input.opening === "string" ? input.opening.trim() : void 0;
178
179
  const rawScenes = openingText ? [{
179
180
  id: "supplied-opening",
180
181
  templateId: "media",
@@ -238,7 +239,7 @@ function validateInput(input) {
238
239
  if (input.maxDurationSec != null && (!Number.isFinite(input.maxDurationSec) || input.maxDurationSec < 5 || input.maxDurationSec > 120)) {
239
240
  throw new Error("Video response maximum duration must be between 5 and 120 seconds");
240
241
  }
241
- if (input.opening != null && !input.opening.trim()) {
242
+ if (input.opening !== false && input.opening != null && !input.opening.trim()) {
242
243
  throw new Error("Video response opening must be a non-empty string");
243
244
  }
244
245
  if (input.audio && !input.audio.src.trim()) {
@@ -331,7 +332,7 @@ function createSceneQualityWarnings(scene) {
331
332
  }
332
333
  function createVideo(rawInput, options) {
333
334
  validateInput(rawInput);
334
- const usesDefaultOpening = rawInput.opening == null;
335
+ const requiresGeneratedScene = rawInput.opening == null || rawInput.opening === false;
335
336
  const input = resolveVideoInput(rawInput);
336
337
  const requestId = options.requestId ?? createId("request");
337
338
  const runId = options.runId ?? createId("run");
@@ -446,7 +447,7 @@ function createVideo(rawInput, options) {
446
447
  format: { orientation: initialConfig.orientation ?? "portrait" },
447
448
  style: initialConfig.style,
448
449
  meta: initialConfig.meta,
449
- capabilities: resolveStreamCapabilities(options.capabilities)
450
+ capabilities: resolveStreamCapabilities(options.capabilities, initialConfig.scenes.length > 0)
450
451
  }));
451
452
  if (initialConfig.audio) yield emit(events.create("audio.set", { audio: initialConfig.audio }));
452
453
  for (const warning of initial.warnings) {
@@ -784,7 +785,7 @@ function createVideo(rawInput, options) {
784
785
  warning: createIncompletePlanWarning()
785
786
  }));
786
787
  }
787
- if (generatedSceneCount === 0 && usesDefaultOpening) {
788
+ if (generatedSceneCount === 0 && requiresGeneratedScene) {
788
789
  throw new Error("The planner completed without adding a scene");
789
790
  }
790
791
  const snapshot = parseVideo(state.config);
@@ -9,6 +9,9 @@ import {
9
9
  interpolate,
10
10
  spring
11
11
  } from "./chunk-XTOXFMLM.js";
12
+ import {
13
+ MEDIA_TEXT_SHADOW
14
+ } from "./chunk-XWWLKRNU.js";
12
15
 
13
16
  // src/visual-system/scene-templates/text-archetypes.ts
14
17
  var TEXT_ARCHETYPES = [
@@ -526,7 +529,8 @@ var TemplateText = ({
526
529
  safeZone = DEFAULT_SAFE_ZONE,
527
530
  font = DEFAULT_FONT,
528
531
  color = "#FFFFFF",
529
- beatIntensity = 0
532
+ beatIntensity = 0,
533
+ overMedia = false
530
534
  }) => {
531
535
  const textSafe = typeof textRaw === "string" ? textRaw : "";
532
536
  const isDevelopment = import.meta.env?.DEV;
@@ -568,7 +572,7 @@ var TemplateText = ({
568
572
  } : baseTypo;
569
573
  const presetWeight = tt ? Math.min(900, Math.max(100, fontWeight + tt.weightDelta)) : fontWeight;
570
574
  const presetSize = tt ? fontSize * tt.sizeScale : fontSize;
571
- const textShadow = dropShadowFor(color);
575
+ const textShadow = overMedia ? MEDIA_TEXT_SHADOW : dropShadowFor(color);
572
576
  const containerStyle = {
573
577
  position: "absolute",
574
578
  left: 0,