@vanillaskyai/video 0.8.8 → 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 +30 -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 +590 -833
  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 +1 -1
  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 -22
  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,6 +1,6 @@
1
1
  import {
2
2
  createVideoSystemPrompt
3
- } from "./chunk-5SLENAJW.js";
3
+ } from "./chunk-PAJ44TFW.js";
4
4
  import {
5
5
  PACING_PLANNER_RULES
6
6
  } from "./chunk-3O7OMMMF.js";
@@ -15,7 +15,7 @@ function getStandardMediaResolverContract(schema) {
15
15
  const url = schema.properties.mediaUrl;
16
16
  const type = schema.properties.mediaType;
17
17
  const allowedTypes = new Set(type?.enum ?? []);
18
- if (keyword?.format !== "stock-media-keyword" || url?.format !== "uri" || !["photo", "video", "gradient"].every((value) => allowedTypes.has(value))) {
18
+ if (keyword?.format !== "stock-media-keyword" || url?.format !== "uri" || !["photo", "video"].every((value) => allowedTypes.has(value))) {
19
19
  return void 0;
20
20
  }
21
21
  return {
@@ -26,6 +26,8 @@ function getStandardMediaResolverContract(schema) {
26
26
  // src/visual-system/catalog/prompt.ts
27
27
  function plannerProperty(property) {
28
28
  return {
29
+ ...property.description == null ? {} : { description: property.description },
30
+ ...property.examples == null ? {} : { examples: property.examples.slice(0, 1) },
29
31
  ...property.type == null ? {} : { type: property.type },
30
32
  ...property.format == null ? {} : { format: property.format },
31
33
  ...property.enum == null ? {} : { enum: property.enum },
@@ -60,10 +62,6 @@ function plannerSchema(schema, exposeMediaKeyword) {
60
62
  }
61
63
  };
62
64
  }
63
- function needsPlannerSchema(schema) {
64
- const visit = (property) => property.minimum != null || property.maximum != null || property.type === "object" || property.items != null && visit(property.items);
65
- return Object.values(schema.properties).some(visit);
66
- }
67
65
  function isMediaProperty(property) {
68
66
  return property?.format === "uri" || property?.format === "supplied-image" || property?.format === "stock-media-keyword";
69
67
  }
@@ -71,13 +69,6 @@ function requiresAvailableMedia(template) {
71
69
  const gates = getTemplateSchemaGates(template.schema);
72
70
  return (template.schema.required ?? []).some((name) => isMediaProperty(template.schema.properties[name])) || gates.requiredAnyOf.some((group) => group.every((name) => isMediaProperty(template.schema.properties[name])));
73
71
  }
74
- var COMMON_MEDIA_VARIABLES = {
75
- mediaUrl: "media",
76
- mediaType: "enum(auto|photo|video|gradient)",
77
- mediaPoster: "media",
78
- mediaPosition: "enum(center|top|bottom|left|right)",
79
- mediaTreatment: "enum(none|subtle|cinematic|text-safe)"
80
- };
81
72
  function plannerCatalog(templates, mediaResolverAvailable) {
82
73
  return templates.map((template) => {
83
74
  const gates = getTemplateSchemaGates(template.schema);
@@ -88,23 +79,19 @@ function plannerCatalog(templates, mediaResolverAvailable) {
88
79
  name === "mediaKeyword" ? "string{2..80}" : templateVariableNotation(property, template.schema.required?.includes(name) === true)
89
80
  ])
90
81
  );
91
- const usesCommonMedia = Object.entries(COMMON_MEDIA_VARIABLES).every(([name, notation]) => variables[name] === notation);
92
- if (usesCommonMedia) {
93
- for (const name of Object.keys(COMMON_MEDIA_VARIABLES)) delete variables[name];
94
- }
95
82
  return {
96
83
  id: template.id,
97
84
  jobs: template.jobs,
98
85
  use: template.useWhen ?? template.description,
86
+ ...template.avoidWhen ? { avoid: template.avoidWhen } : {},
99
87
  ...template.minDuration == null && template.preferredDuration == null ? {} : {
100
88
  seconds: [template.minDuration ?? null, template.preferredDuration ?? null]
101
89
  },
102
90
  ...gates.requiresStat ? { requiresStat: true } : {},
103
91
  ...gates.requiresQuote ? { requiresQuote: true } : {},
104
92
  ...gates.requiresScreenshot ? { requiresScreenshot: true } : {},
105
- ...gates.requiredAnyOf.length > 0 ? { requiredAnyOf: gates.requiredAnyOf } : {},
106
- ...needsPlannerSchema(template.schema) ? { schema: plannerSchema(template.schema, exposeMediaKeyword) } : {},
107
- ...usesCommonMedia ? { media: true } : {},
93
+ ...gates.requiredAnyOf.length > 0 ? { requiredAnyOf: gates.requiredAnyOf.map((group) => group.filter((name) => name !== "mediaKeyword" || exposeMediaKeyword)) } : {},
94
+ schema: plannerSchema(template.schema, exposeMediaKeyword),
108
95
  variables
109
96
  };
110
97
  });
@@ -112,24 +99,14 @@ function plannerCatalog(templates, mediaResolverAvailable) {
112
99
  function createTemplateSystemPrompt(options) {
113
100
  const templates = options.kit.listTemplateMetadata().filter((template) => !requiresAvailableMedia(template) || options.suppliedMediaAvailable === true || options.mediaResolverAvailable === true && getStandardMediaResolverContract(template.schema) != null);
114
101
  const resolverMediaAvailable = options.mediaResolverAvailable === true && templates.some(({ schema }) => getStandardMediaResolverContract(schema) != null);
115
- const ids = new Set(templates.map(({ id }) => id));
116
- const fields = new Set(templates.flatMap(({ schema }) => Object.keys(schema.properties)));
117
102
  const basePrompt = createVideoSystemPrompt(options.knowledgeMode, options.narrate === true).split("\n").filter(
118
103
  (line) => !line.includes("Never use media, ctaMedia, or reaction as the first generated body template") && // The rule that keeps the opening beat asset-free belongs to the same
119
104
  // pair as the one below it: both exist so playback can start without
120
105
  // waiting on a provider, and both go when the host owns that wait.
121
106
  !(options.mediaOnFirstScene === true && line.includes("The first generated body scene must be asset-free")) && !(resolverMediaAvailable && line.includes("Never expose a loading placeholder or unresolved media keyword"))
122
107
  ).join("\n");
123
- const factRules = [
124
- ...ids.has("cardList") ? ["cardList needs two or three unused parallel facts"] : [],
125
- ...ids.has("steps") ? ["steps needs two or three unused sequential facts"] : [],
126
- ...ids.has("tripleStats") ? ["tripleStats needs exactly three unused peer values"] : []
127
- ];
128
- const mediaTemplates = ["media", "ctaMedia", "reaction"].filter((id) => ids.has(id));
129
- const mediaTemplateNames = mediaTemplates.length === 3 ? "media, ctaMedia, and reaction" : mediaTemplates.join(", ");
130
- const terminalPayoffs = ["media", "emojiBurst", "confetti"].filter((id) => ids.has(id));
131
- const terminalPayoffNames = terminalPayoffs.length < 2 ? terminalPayoffs.join("") : `${terminalPayoffs.slice(0, -1).join(", ")}, or ${terminalPayoffs.at(-1)}`;
132
- const listFields = ["items", "itemEmojis", "steps", "stepEmojis", "problemEmojis", "solutionEmojis"].filter((name) => fields.has(name));
108
+ const mediaTemplates = templates.filter(({ schema }) => getStandardMediaResolverContract(schema) != null).map(({ id }) => id);
109
+ const terminalPayoffs = templates.filter(({ jobs }) => jobs?.includes("payoff")).map(({ id }) => id);
133
110
  return [
134
111
  basePrompt.trim(),
135
112
  options.basePrompt?.trim() ? `
@@ -139,20 +116,17 @@ ${options.basePrompt.trim()}` : void 0,
139
116
  "TRUSTED TEMPLATE CATALOG",
140
117
  "Only use template IDs from this catalog. Only emit variables declared for the selected template.",
141
118
  "Variable notation is type[count]{characters}(options)! where count is list cardinality, characters is the inclusive character count for a string or each string-array item, and ! means required. Omitted ! means optional.",
142
- `seconds is [minimum, preferred]. media=true adds these optional variables: ${Object.entries(COMMON_MEDIA_VARIABLES).map(([name, notation]) => `${name}:${notation}`).join(", ")}.`,
143
119
  "Choose a template only when the permitted factual basis contains every fact it needs. Never invent peer values to complete a chart, comparison, stat set, timeline, or list.",
144
120
  ...PACING_PLANNER_RULES,
145
- ids.has("cardList") && ids.has("steps") && ids.has("tripleStats") ? "Fact availability carries across templates: cardList and steps each need two or three unused facts (parallel for cardList, sequential for steps) and must carry only the count the evidence supports, and tripleStats needs exactly three unused peer values. Never invent a fact to fill a collection, and do not fill one with facts already shown in another scene." : factRules.length > 0 ? `Fact availability carries across templates: ${factRules.join(", and ")}. Use only the count the evidence supports. Never invent a fact to fill a collection, and do not fill one with facts already shown in another scene.` : void 0,
146
121
  "Do not compress a list, sequence, metric set, or comparison into a general-purpose prose field when a specific catalog template can show that structure.",
147
122
  "If the catalog includes a suitable ask template and the input supplies a grounded CTA or URL, keep that concise action closer as its own final scene instead of folding it into preceding content.",
148
123
  "When a grounded CTA or URL is supplied and the catalog contains jobs:[ask], emit that final closer. A brand name may accompany the action but never qualifies as an ask by itself.",
149
- terminalPayoffs.length > 0 ? `When the input has no clear grounded action, never invent one and do not use jobs:[ask]. End with a grounded payoff using ${terminalPayoffNames}. Its concise 6\u201312 words of declarative copy must answer the story's so-what without repeating hook or setup language. Use media for an honest visual payoff, emojiBurst for playful delight, and confetti only for a real celebration, win, or milestone.` : void 0,
124
+ terminalPayoffs.length > 0 ? `When there is no grounded action, end with a concise supported payoff using a suitable template from ${terminalPayoffs.join(", ")}. Do not repeat the hook or invent a CTA.` : void 0,
150
125
  "requiresQuote is a hard gate: use those templates only for exact quoted words and attribution present in raw input. Never turn a role, relationship, or summary into speech. requiresScreenshot likewise needs an actual supplied screenshot URL.",
151
126
  templates.some(({ schema }) => (schema["x-vanillasky"]?.requiredAnyOf?.length ?? 0) > 0) ? "requiredAnyOf is a hard presence gate: satisfy every listed group with at least one non-empty value." : void 0,
152
- mediaTemplates.length > 0 && options.mediaOnFirstScene !== true ? `For streaming startup, ${mediaTemplateNames} ${mediaTemplates.length === 1 ? "is" : "are"} forbidden as the first generated body template, even when mediaType is gradient. Start with a content-fit non-media template.` : void 0,
153
- listFields.length > 0 ? `For list variables (${listFields.join(", ")}), emit actual JSON arrays. Keep list labels to 1\u20133 words. Keep step labels to 1\u20132 words and at most 18 characters. Do not use pipes or newlines as list delimiters.` : void 0,
154
- fields.has("bars") ? 'For bars, emit an actual JSON array of 2\u20136 grounded objects with "label" and "value" fields. Use comparable units and avoid a largest-to-smallest positive ratio above 20. Do not encode bars as a string.' : void 0,
155
- resolverMediaAvailable ? `Prefer a relevant resolved image or video background on ${options.mediaOnFirstScene === true ? "every media-capable scene, the first included," : "later media-capable scenes"} whenever the permitted factual basis names a concrete person, place, product context, activity, or outcome that can be depicted honestly. Emit mediaKeyword on scene.add as a specific 2\u20138 word semantic query of at most 80 characters. The host removes mediaKeyword before the complete scene reaches the browser and replaces it with an approved asset. Use mediaType=gradient for abstract, sensitive, unsafe, or visually ambiguous material. Never emit or invent mediaUrl or mediaPoster.` : mediaTemplates.length > 0 ? `Use exact grounded values for required fields. mediaUrl must come verbatim from supplied input. Stock queries are not available. Do not select ${mediaTemplateNames} without a supplied mediaUrl; if another template has no mediaUrl, explicitly use mediaType=gradient. mediaType auto detects a URL, while gradient deliberately uses no external asset.` : "Use exact grounded values for required fields.",
127
+ mediaTemplates.length > 0 && options.mediaOnFirstScene !== true ? `The host owns immediate startup; reserve external media templates (${mediaTemplates.join(", ")}) for later beats when an asset-free opening is required.` : void 0,
128
+ "For list and event variables emit actual JSON arrays. Follow each field's content budget; never use pipe-delimited strings or invent entries.",
129
+ resolverMediaAvailable ? "Media-capable scenes may carry mediaKeyword: a literal 2\u20138 word shot description, at most 80 characters. Choose mediaSource=generate for distinctive illustrative shots and mediaSource=stock for common subjects a verified asset can depict. For generated footage, optional shotDirection gives action, framing and continuity separately from the stock query. Keep people, actions and essential details precise. Never broaden an exact identity or action into unrelated atmosphere. Supply a grounded fallbackText where declared. Only the host fills mediaUrl and mediaPoster; never invent them. If no media fits an abstract beat, choose an appropriate graphic template instead." : "Use media only when a supplied asset satisfies the scene. Never invent asset URLs. Otherwise choose a grounded graphic template.",
156
130
  "Catalog guidance describes composition; it is not a factual source and must never replace the permitted factual basis.",
157
131
  JSON.stringify(plannerCatalog(templates, resolverMediaAvailable)),
158
132
  // Last, because the catalogue is thousands of tokens of per-scene field
@@ -42,13 +42,12 @@ Composition rules:
42
42
  - Before emitting, assign each supported fact to at most one scene. The supplied opening counts: once a fact is visible, treat it as unavailable to later scenes. Finish when the supported material is covered instead of padding the response.
43
43
  - Do not infer that something is scheduled, ready, triggered, enabled, automatic, causal, or available unless the permitted factual basis supports it.
44
44
  - The first generated body scene must be asset-free and fully playable before any external media resolves.
45
- - Never use media, ctaMedia, or reaction as the first generated body template, including in gradient mode.
46
45
  - Use only media URLs present in the supplied input or already resolved by the host. Never expose a loading placeholder or unresolved media keyword. Audio is optional and must never delay the first scene.
47
46
  - Audio is selected by the host before generation. Never emit audio.
48
47
 
49
48
  Streaming rules:
50
49
  - Emit every scene once as a complete scene.add.
51
- - Emit exactly one closer immediately after the first playable body scene using placement:"closer". The runtime holds that closer and appends it last while later body scenes continue streaming.
50
+ - Emit exactly one closer using placement:"closer". It is held and appended last; preserve the intended story order.
52
51
  - Prefer resolved media on scene.add. Scenes are immutable after emission.
53
52
  - End explicitly with plan.complete. A truncated stream is never treated as complete.
54
53
  - Return only plan parts accepted by the provided schema.
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-E7CL7UPB.js";
7
7
  import {
8
8
  parseVideoPlanPart
9
- } from "./chunk-MMUXVA47.js";
9
+ } from "./chunk-7M56IUUX.js";
10
10
 
11
11
  // src/server/model/truncated-json.ts
12
12
  function isConfidentlyTruncatedJson(source) {
@@ -1,5 +1,18 @@
1
1
  // src/visual-system/catalog/video-backdrop-capability.ts
2
2
  var EXTERNAL_VIDEO_BACKDROP_CAPABILITY = "__vanillaskyExternalVideoBackdrop";
3
+ function markExternalVideoBackdropTemplate(template) {
4
+ const component = template.component;
5
+ if ((typeof component !== "object" || component === null) && typeof component !== "function") {
6
+ throw new Error("External video backdrop templates require a component");
7
+ }
8
+ Object.defineProperty(component, EXTERNAL_VIDEO_BACKDROP_CAPABILITY, {
9
+ value: true,
10
+ enumerable: false,
11
+ configurable: false,
12
+ writable: false
13
+ });
14
+ return template;
15
+ }
3
16
  function supportsExternalVideoBackdrop(template) {
4
17
  if (typeof template !== "object" || template === null) return false;
5
18
  const component = template.component;
@@ -10,6 +23,7 @@ function externalVideoBackdropCapabilitySource() {
10
23
  }
11
24
 
12
25
  export {
26
+ markExternalVideoBackdropTemplate,
13
27
  supportsExternalVideoBackdrop,
14
28
  externalVideoBackdropCapabilitySource
15
29
  };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  parseVideoEvent
3
- } from "./chunk-MMUXVA47.js";
3
+ } from "./chunk-7M56IUUX.js";
4
4
  import {
5
5
  VIDEO_PROTOCOL_VERSION,
6
6
  VIDEO_SCHEMA_VERSION
7
- } from "./chunk-AOWSXU2K.js";
7
+ } from "./chunk-7NZOMTAL.js";
8
8
 
9
9
  // src/protocol/stable-json.ts
10
10
  function stableJson(value) {
@@ -1,23 +1,30 @@
1
- // src/visual-system/scene-templates/tokens.ts
2
- var TOKEN_DEFAULTS = {
3
- /** Primary brand colour CTA, highlights. */
4
- primary: "#00E5A0",
5
- /** Deepest background surface. */
6
- surface: "#0A0A14",
7
- /** Elevated card / panel surface. */
8
- surfaceElevated: "#14152A",
9
- /** Primary text color. */
10
- foreground: "#FFFFFF",
11
- /** Muted text — labels, footers, supporting copy. */
12
- muted: "#A7A6B0",
13
- /** Primary sans font family (first name of the stack). */
14
- font: "Inter",
15
- /** Script accent font family for handwritten callouts. */
16
- scriptFont: "Caveat"
17
- };
18
- function fontStack(styleFont) {
19
- return `${(styleFont || "").split(",")[0].trim() || TOKEN_DEFAULTS.font}, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif`;
1
+ // src/visual-system/scene-templates/external-video-backdrop.tsx
2
+ import React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var DEFAULT = { mode: false, audioMuted: true, audioVolume: 1 };
5
+ var sharedContext = globalThis;
6
+ var BackdropContext = sharedContext.__vanillaskyVideoBackdropContext ??= React.createContext(DEFAULT);
7
+ function ExternalVideoBackdropProvider({
8
+ mode,
9
+ audioMuted = true,
10
+ audioVolume = 1,
11
+ children
12
+ }) {
13
+ const value = React.useMemo(
14
+ () => ({ mode, audioMuted, audioVolume }),
15
+ [mode, audioMuted, audioVolume]
16
+ );
17
+ return /* @__PURE__ */ jsx(BackdropContext.Provider, { value, children });
18
+ }
19
+ function useExternalVideoBackdrop() {
20
+ return React.useContext(BackdropContext).mode;
20
21
  }
22
+ function useMediaAudio() {
23
+ const { audioMuted, audioVolume } = React.useContext(BackdropContext);
24
+ return { muted: audioMuted, volume: audioVolume };
25
+ }
26
+
27
+ // src/visual-system/scene-templates/tokens.ts
21
28
  var STYLE_PRESETS = {
22
29
  bold: {
23
30
  id: "bold",
@@ -94,7 +101,6 @@ function resolveMotion(id) {
94
101
  return id && MOTION_SCALES[id] || MOTION_SCALES[DEFAULT_MOTION_ID];
95
102
  }
96
103
  function resolveTokens(style) {
97
- const brand = style.brand;
98
104
  const preset = resolvePreset(style.preset);
99
105
  const density = resolveDensity(style.density);
100
106
  const motion = resolveMotion(style.motion);
@@ -107,17 +113,15 @@ function resolveTokens(style) {
107
113
  }
108
114
  };
109
115
  return {
110
- primary: brand.colors.primary,
111
- secondary: brand.colors.secondary,
112
- background: brand.background,
113
- surface: brand.colors.surface,
114
- surfaceElevated: brand.colors.surfaceElevated,
115
- foreground: brand.colors.foreground,
116
- muted: brand.colors.muted,
117
- font: fontStack(brand.font),
118
- scriptFont: brand.scriptFont,
119
- logoUrl: brand.logoUrl,
120
- name: brand.name,
116
+ primary: "#FFFFFF",
117
+ secondary: "#FFFFFF",
118
+ background: { type: "solid", color: "#000000" },
119
+ surface: "#000000",
120
+ surfaceElevated: "#171717",
121
+ foreground: "#FFFFFF",
122
+ muted: "#B7B7BC",
123
+ font: '-apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto, Arial, sans-serif',
124
+ scriptFont: "Georgia",
121
125
  preset: composedPreset,
122
126
  density,
123
127
  motion
@@ -131,27 +135,12 @@ function darken(hex, factor) {
131
135
  const ch = (i) => Math.max(0, Math.round(parseInt(full.slice(i, i + 2), 16) * (1 - factor))).toString(16).padStart(2, "0");
132
136
  return `#${ch(1)}${ch(3)}${ch(5)}`;
133
137
  }
134
- function lighten(hex, factor) {
135
- if (!hex.startsWith("#") || hex.length !== 4 && hex.length !== 7) {
136
- return hex;
137
- }
138
- const full = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
139
- const r = parseInt(full.slice(1, 3), 16);
140
- const g = parseInt(full.slice(3, 5), 16);
141
- const b = parseInt(full.slice(5, 7), 16);
142
- const lr = Math.min(255, Math.round(r + (255 - r) * factor));
143
- const lg = Math.min(255, Math.round(g + (255 - g) * factor));
144
- const lb = Math.min(255, Math.round(b + (255 - b) * factor));
145
- return `#${lr.toString(16).padStart(2, "0")}${lg.toString(16).padStart(2, "0")}${lb.toString(16).padStart(2, "0")}`;
146
- }
147
- var MEDIA_TEXT_SHADOW = "0 2px 8px rgba(0,0,0,0.55), 0 6px 16px rgba(0,0,0,0.35)";
148
138
 
149
139
  export {
150
- TOKEN_DEFAULTS,
151
- fontStack,
152
140
  resolveDensity,
153
141
  resolveTokens,
154
142
  darken,
155
- lighten,
156
- MEDIA_TEXT_SHADOW
143
+ ExternalVideoBackdropProvider,
144
+ useExternalVideoBackdrop,
145
+ useMediaAudio
157
146
  };
@@ -19,12 +19,8 @@ function resolveMediaType(mediaType, mediaUrl) {
19
19
  if (mediaType === "photo") return "photo";
20
20
  return mediaUrl && isVideoUrl(mediaUrl) ? "video" : "photo";
21
21
  }
22
- function hasSceneMedia(variables) {
23
- return String(variables.mediaUrl || "").trim() !== "" && String(variables.mediaType || "auto") !== "gradient";
24
- }
25
22
 
26
23
  export {
27
24
  limitsConcurrentVideoDecoders,
28
- resolveMediaType,
29
- hasSceneMedia
25
+ resolveMediaType
30
26
  };
@@ -6,30 +6,27 @@ import {
6
6
  checksumVideo,
7
7
  createVideoEventFactory,
8
8
  createVideoState
9
- } from "./chunk-2OKB27TG.js";
9
+ } from "./chunk-RXTN2CW6.js";
10
10
  import {
11
11
  parseVideoPlanPart,
12
12
  safePublicDiagnostic
13
- } from "./chunk-MMUXVA47.js";
13
+ } from "./chunk-7M56IUUX.js";
14
14
  import {
15
15
  MAX_RETAINED_INSTRUCTIONS_LENGTH,
16
16
  MAX_RETAINED_MEDIA_URLS,
17
17
  MAX_RETAINED_MEDIA_URL_LENGTH,
18
18
  MAX_RETAINED_SOURCE_LENGTH,
19
19
  parseVideo
20
- } from "./chunk-2DOLF4QN.js";
20
+ } from "./chunk-AUPC6MDK.js";
21
21
  import {
22
22
  VIDEO_SCHEMA_VERSION,
23
23
  createVideoRequest
24
- } from "./chunk-AOWSXU2K.js";
24
+ } from "./chunk-7NZOMTAL.js";
25
25
  import {
26
26
  getCloserReserve,
27
27
  getReadableSceneDuration,
28
28
  paceScene
29
29
  } from "./chunk-3O7OMMMF.js";
30
- import {
31
- resolveVideoBrand
32
- } from "./chunk-XGRA2MUV.js";
33
30
 
34
31
  // src/replayable-stream.ts
35
32
  function createReplayableStream(source, onError) {
@@ -114,7 +111,6 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
114
111
  input.opening === false ? "The host owns the opening wait. Add the first grounded scene as soon as it is complete; it may use host-resolved media." : 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.",
115
112
  ...input.opening === false ? [] : [
116
113
  "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.",
117
- "Never use media, ctaMedia, or reaction as the first generated body template, even with mediaType=gradient. Choose a non-media template first.",
118
114
  "Add that scene before resolving any stock or supplied asset. Resolve media as part of each complete later body scene."
119
115
  ],
120
116
  "Use only claims supported by the factual basis permitted by the trusted system prompt.",
@@ -125,7 +121,7 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
125
121
  "If the creative instructions explicitly require one separate scene per named item, release, section, or list entry, do not merge, group, or omit those required items. Keep related required scenes adjacent in a coherent progression while preserving each item as its own scene.",
126
122
  "Before emitting, verify that the explicitly requested structure can fit readably within the maximum duration. If it cannot, preserve readability and the requested separation for the scenes that fit, then finish with plan.complete using finishReason length rather than silently changing the structure.",
127
123
  "For ordinary multi-fact input, form at least three distinct beats: hook, comprehension, and payoff. Use only one or two beats when the source genuinely contains no more than two independent grounded takeaways. This is a narrative guideline, never permission to repeat facts or add filler.",
128
- "After the first playable body scene, emit exactly one scene.add with placement closer. Generate a short grounded conclusion that answers the story's so-what; it must not repeat hook language or imply another scene follows.",
124
+ "Emit exactly one final scene.add with placement closer. Generate a short grounded conclusion that answers the story's so-what; it must not repeat hook language or imply another scene follows.",
129
125
  "Use a different suitable template for each body scene when the catalog supports it.",
130
126
  "Never add filler to satisfy a count or diversity target.",
131
127
  input.suppliedMedia?.length ? input.opening === false ? "Select zero or more relevant opaque supplied-media references for visible scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "Select zero or more relevant opaque supplied-media references for visible later scenes. Never invent or transform a reference. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster." : input.opening === false ? "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent, which may begin on the first scene. Only emit mediaKeyword when the trusted system catalog explicitly exposes it. Never invent mediaUrl or mediaPoster." : "No supplied media URL is available. Use asset-free templates unless the trusted system catalog explicitly permits host-resolved media intent. Only emit mediaKeyword when the trusted system catalog explicitly exposes it, and only on later scenes. Never invent mediaUrl or mediaPoster.",
@@ -140,13 +136,7 @@ function buildVideoUserPrompt(input, openingDurationSec = 0) {
140
136
  JSON.stringify(input.personalization ?? {}),
141
137
  "",
142
138
  "SUPPLIED MEDIA",
143
- JSON.stringify(suppliedMediaReferences(input)),
144
- "",
145
- "BRAND",
146
- JSON.stringify({
147
- ...input.brand?.name?.trim() ? { name: input.brand.name.trim() } : {},
148
- hasLogo: Boolean(input.brand?.logoUrl?.trim())
149
- })
139
+ JSON.stringify(suppliedMediaReferences(input))
150
140
  ].join("\n");
151
141
  }
152
142
 
@@ -162,7 +152,7 @@ function resolveStreamCapabilities(capabilities, hasRuntimeOpening) {
162
152
  if (!hasRuntimeOpening || capabilities?.templates == null) return capabilities;
163
153
  return {
164
154
  ...capabilities,
165
- templates: [.../* @__PURE__ */ new Set(["media", ...capabilities.templates])]
155
+ templates: [.../* @__PURE__ */ new Set(["chapterTitle", ...capabilities.templates])]
166
156
  };
167
157
  }
168
158
  function validateVideoInput(input) {
@@ -190,16 +180,14 @@ function validateVideoInput(input) {
190
180
  throw new Error(`Video response supplied media ${index} poster URL must be at most ${MAX_RETAINED_MEDIA_URL_LENGTH} characters`);
191
181
  }
192
182
  }
193
- resolveVideoBrand(input.brand);
194
183
  }
195
184
  function buildInitialComposition(input, audio, snapshotRetention, closerReserveSec, getTemplatePacing) {
196
185
  const openingText = typeof input.opening === "string" ? input.opening.trim() : void 0;
197
186
  const openingScenes = openingText ? [{
198
187
  id: "supplied-opening",
199
- templateId: "media",
188
+ templateId: "chapterTitle",
200
189
  variables: {
201
- texts: openingText,
202
- mediaType: "gradient"
190
+ title: openingText
203
191
  },
204
192
  timing: { fixedDuration: 3 }
205
193
  }] : [];
@@ -223,7 +211,7 @@ function buildInitialComposition(input, audio, snapshotRetention, closerReserveS
223
211
  warnings.push(...paced.warnings);
224
212
  }
225
213
  const meta = {
226
- name: input.brand?.name?.trim() || "Video response"
214
+ name: "Video response"
227
215
  };
228
216
  if (snapshotRetention?.source) {
229
217
  meta.source = input.input.trim().slice(0, MAX_RETAINED_SOURCE_LENGTH);
@@ -241,7 +229,6 @@ function buildInitialComposition(input, audio, snapshotRetention, closerReserveS
241
229
  scenes,
242
230
  ...audio ? { audio } : {},
243
231
  style: {
244
- brand: resolveVideoBrand(input.brand),
245
232
  density: input.style?.density ?? "normal",
246
233
  motion: input.style?.motion ?? "normal",
247
234
  defaultBackgroundEffect: input.style?.backgroundEffect ?? "static",
@@ -704,7 +691,7 @@ function createVideo(rawInput, options) {
704
691
  position
705
692
  }));
706
693
  }
707
- const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-RRXIWDDD.js")).createVideoSystemPrompt(input.knowledgeMode);
694
+ const systemPrompt = options.systemPrompt ?? (await import("./system-prompt-SYRT5DWM.js")).createVideoSystemPrompt(input.knowledgeMode);
708
695
  const context = { request, systemPrompt, userPrompt, initialConfig, signal: controller.signal };
709
696
  attachGenerationLifecycleSink(context, lifecycle);
710
697
  for await (const untrustedPart of options.generate(context)) {
@@ -0,0 +1,11 @@
1
+ import {
2
+ MediaScene,
3
+ MediaSceneTemplate
4
+ } from "./chunk-G7IZBBWF.js";
5
+ import "./chunk-77VY4O7A.js";
6
+ import "./chunk-UDIODEDK.js";
7
+ import "./chunk-TVIU23OM.js";
8
+ export {
9
+ MediaScene,
10
+ MediaSceneTemplate
11
+ };
package/dist/cli.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-AITKH6QT.js";
3
- import "./chunk-5SLENAJW.js";
2
+ import "./chunk-OR52UMSF.js";
3
+ import "./chunk-PAJ44TFW.js";
4
4
  import {
5
5
  isTemplatePropertyFormatSupported,
6
6
  validateTemplateSchemaValue
7
- } from "./chunk-352ZZCET.js";
7
+ } from "./chunk-EGVQODKU.js";
8
8
  import {
9
9
  getBuiltinTemplateMetadata,
10
10
  listBuiltinTemplateMetadata
11
- } from "./chunk-4UNKZEAT.js";
11
+ } from "./chunk-LH4BPALA.js";
12
12
  import "./chunk-3O7OMMMF.js";
13
13
  import {
14
14
  assertTemplateTransitionMetadata
15
15
  } from "./chunk-2E6T633S.js";
16
16
  import {
17
17
  BUILTIN_TEMPLATE_MANIFEST
18
- } from "./chunk-PP3MCPN4.js";
18
+ } from "./chunk-MXEYPK7M.js";
19
19
  import {
20
20
  externalVideoBackdropCapabilitySource
21
- } from "./chunk-WJ6FLKNC.js";
21
+ } from "./chunk-R3XAOMKP.js";
22
22
  import {
23
23
  getTemplateDefaults,
24
24
  isMeaningfullyPresentTemplateValue,
@@ -1006,8 +1006,12 @@ function validateDiscoveredTemplate(template, cwd) {
1006
1006
  validateSchema(metadata.schema);
1007
1007
  if (metadata.timing !== void 0) {
1008
1008
  if (!metadata.timing || typeof metadata.timing !== "object" || Array.isArray(metadata.timing)) throw new Error("timing must be an object");
1009
- for (const key of Object.keys(metadata.timing)) if (!(/* @__PURE__ */ new Set(["contentFields", "contentUnit"])).has(key)) throw new Error(`timing.${key} is not supported`);
1010
- if (!Array.isArray(metadata.timing.contentFields) || metadata.timing.contentFields.length === 0) throw new Error("timing.contentFields must contain at least one schema property");
1009
+ for (const key of Object.keys(metadata.timing)) if (!(/* @__PURE__ */ new Set(["contentFields", "contentUnit", "revealSeconds", "holdSeconds", "exitSeconds"])).has(key)) throw new Error(`timing.${key} is not supported`);
1010
+ if (!Array.isArray(metadata.timing.contentFields)) throw new Error("timing.contentFields must be an array of schema properties");
1011
+ for (const key of ["revealSeconds", "holdSeconds", "exitSeconds"]) {
1012
+ const value = metadata.timing[key];
1013
+ if (value !== void 0 && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) throw new Error(`timing.${key} must be a finite non-negative number`);
1014
+ }
1011
1015
  const timingFields = /* @__PURE__ */ new Set();
1012
1016
  for (const field of metadata.timing.contentFields) {
1013
1017
  if (typeof field !== "string" || !Object.hasOwn(metadata.schema.properties, field)) throw new Error(`timing.contentFields references undeclared property ${JSON.stringify(field)}`);
@@ -1017,6 +1021,11 @@ function validateDiscoveredTemplate(template, cwd) {
1017
1021
  if (!CONTENT_UNITS.has(metadata.timing.contentUnit)) throw new Error("timing.contentUnit contains an unsupported value");
1018
1022
  }
1019
1023
  const defaults = getTemplateDefaults(metadata.schema);
1024
+ const smokeVariables = { ...defaults };
1025
+ for (const field of metadata.schema.required ?? []) {
1026
+ const example = metadata.schema.properties[field]?.examples?.[0];
1027
+ if (!(field in smokeVariables) && example !== void 0) smokeVariables[field] = example;
1028
+ }
1020
1029
  if (template.examples !== void 0 && !Array.isArray(template.examples)) throw new Error("examples must be an array");
1021
1030
  const supplied = template.examples ?? [];
1022
1031
  for (const [index, value] of supplied.entries()) {
@@ -1031,7 +1040,7 @@ function validateDiscoveredTemplate(template, cwd) {
1031
1040
  name,
1032
1041
  variables: { ...defaults, ...variables },
1033
1042
  enforceRequiredAnyOf: true
1034
- })) : [{ name: "defaults", variables: defaults, enforceRequiredAnyOf: false }];
1043
+ })) : [{ name: "defaults", variables: smokeVariables, enforceRequiredAnyOf: false }];
1035
1044
  const names = /* @__PURE__ */ new Set();
1036
1045
  for (const example of examples) {
1037
1046
  if (typeof example.name !== "string" || !example.name.trim()) throw new Error("example names must be non-empty strings");
@@ -0,0 +1,27 @@
1
+ import {
2
+ editorialFont,
3
+ editorialLabel,
4
+ fade
5
+ } from "./chunk-4YM2M62S.js";
6
+
7
+ // src/visual-system/scene-templates/comparison.tsx
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ function ComparisonScene({ variables, width, height, progress, motionProgress = progress, safeZone }) {
10
+ const portrait = height > width, u = Math.min(width, height);
11
+ const side = Math.max(width * 0.12, safeZone.left, safeZone.right);
12
+ const top = Math.max(height * 0.2, safeZone.top), bottom = height - Math.max(height * 0.23, safeZone.bottom);
13
+ const area = bottom - top, cy = (top + bottom) / 2, available = width - side * 2;
14
+ const entries = [{ label: String(variables.leftLabel ?? ""), text: String(variables.leftText ?? "") }, { label: String(variables.rightLabel ?? ""), text: String(variables.rightText ?? "") }];
15
+ const textWidth = portrait ? available : available * 0.39;
16
+ return /* @__PURE__ */ jsxs("div", { "data-template": "comparison", style: { position: "absolute", inset: 0, background: "#000", color: "#fff", fontFamily: editorialFont, overflow: "hidden" }, children: [
17
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", left: portrait ? width * 0.32 : width / 2, top: portrait ? cy : top + area * 0.18, width: portrait ? width * 0.36 : u * 1e-3, height: portrait ? u * 1e-3 : area * 0.64, background: "#fff", opacity: 0.22 * fade((motionProgress - 0.16) / 0.18) } }),
18
+ entries.map((entry, i) => /* @__PURE__ */ jsxs("div", { "data-comparison-side": i === 0 ? "left" : "right", style: { position: "absolute", left: portrait ? side : side + available * (i === 0 ? 0.055 : 0.555), top: portrait ? top + area * (i === 0 ? 0.25 : 0.75) : cy, width: textWidth, transform: "translateY(-50%)", textAlign: "center", opacity: fade((motionProgress - 0.04 - i * 0.22) / 0.2) }, children: [
19
+ entry.label && /* @__PURE__ */ jsx("div", { style: { ...editorialLabel(u), marginBottom: u * 0.028 }, children: entry.label }),
20
+ /* @__PURE__ */ jsx("div", { style: { fontSize: u * 0.058, fontWeight: 500, lineHeight: 1.22, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: entry.text })
21
+ ] }, i))
22
+ ] });
23
+ }
24
+ var ComparisonSceneTemplate = ComparisonScene;
25
+ export {
26
+ ComparisonSceneTemplate
27
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createVideo
3
+ } from "./chunk-ZDJNFA52.js";
4
+ import "./chunk-E7CL7UPB.js";
5
+ import "./chunk-RXTN2CW6.js";
6
+ import "./chunk-7M56IUUX.js";
7
+ import "./chunk-AUPC6MDK.js";
8
+ import "./chunk-7NZOMTAL.js";
9
+ import "./chunk-3O7OMMMF.js";
10
+ export {
11
+ createVideo
12
+ };
@@ -0,0 +1,36 @@
1
+ import {
2
+ editorialFont,
3
+ fade
4
+ } from "./chunk-4YM2M62S.js";
5
+
6
+ // src/visual-system/scene-templates/editorial-timeline.tsx
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var timelineTiming = (index) => ({ nodeStart: index === 0 ? 0.06 : 0.23 + (index - 1) * 0.11, lineStart: 0.14 + index * 0.11, lineDuration: 0.09, entranceDuration: 0.16 });
9
+ function TimelineScene({ variables, progress, motionProgress = progress, width, height, safeZone }) {
10
+ const events = (Array.isArray(variables.events) ? variables.events : []).slice(0, 5).map((e) => ({ label: String(e?.label ?? "") }));
11
+ const portrait = height > width, u = Math.min(width, height), n = Math.max(1, events.length);
12
+ const side = Math.max(width * 0.13, safeZone.left, safeZone.right), available = width - side * 2;
13
+ const top = Math.max(height * 0.2, safeZone.top), bottom = height - Math.max(height * 0.24, safeZone.bottom);
14
+ const slot = portrait ? (bottom - top) / n : available / n;
15
+ const points = events.map((_, i) => ({ x: portrait ? side : side + slot * (i + 0.5), y: portrait ? top + slot * (i + 0.5) : height * 0.43 }));
16
+ const radius = u * 45e-4;
17
+ return /* @__PURE__ */ jsxs("div", { "data-template": "timeline", style: { position: "absolute", inset: 0, overflow: "hidden", background: "#000", fontFamily: editorialFont, color: "#fff" }, children: [
18
+ points.slice(0, -1).map((point, i) => {
19
+ const next = points[i + 1], timing = timelineTiming(i), line = fade((motionProgress - timing.lineStart) / timing.lineDuration);
20
+ return /* @__PURE__ */ jsx("div", { "data-step-connector": "true", style: { position: "absolute", left: point.x, top: point.y, width: portrait ? u * 1e-3 : next.x - point.x, height: portrait ? next.y - point.y : u * 1e-3, background: "#fff", opacity: 0.3, transform: portrait ? `scaleY(${line})` : `scaleX(${line})`, transformOrigin: portrait ? "top" : "left" } }, `line-${i}`);
21
+ }),
22
+ points.map((point, i) => {
23
+ const timing = timelineTiming(i), event = events[i], opacity = fade((motionProgress - timing.nodeStart) / timing.entranceDuration);
24
+ const textW = portrait ? available - u * 0.055 : slot * 0.86;
25
+ return /* @__PURE__ */ jsxs("div", { "data-template-item": "steps", style: { opacity }, children: [
26
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: { position: "absolute", left: point.x - radius, top: point.y - radius, width: radius * 2, height: radius * 2, borderRadius: "50%", background: "#fff" } }),
27
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: portrait ? point.x + u * 0.055 : point.x - textW / 2, top: portrait ? point.y : point.y + u * 0.05, width: textW, transform: portrait ? "translateY(-50%)" : void 0, textAlign: portrait ? "left" : "center" }, children: /* @__PURE__ */ jsx("div", { style: { fontSize: u * (portrait ? 0.052 : 0.041), fontWeight: 500, lineHeight: 1.22, letterSpacing: "-.025em", textWrap: "balance", overflowWrap: "anywhere" }, children: event.label }) })
28
+ ] }, i);
29
+ })
30
+ ] });
31
+ }
32
+ var TimelineSceneTemplate = TimelineScene;
33
+ export {
34
+ TimelineSceneTemplate,
35
+ timelineTiming
36
+ };
@@ -1,4 +1,4 @@
1
- import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-CEh9eUFu.js';
1
+ import { b as VideoOrientation, V as VideoStyle, c as Video, a as VideoCapabilities, d as VideoAudio, e as VideoScene, f as VIDEO_PROTOCOL_VERSION } from './types-DdZw4GRQ.js';
2
2
 
3
3
  type VideoWarningCategory = "validation" | "readability" | "grounding" | "provider" | "media" | "protocol";
4
4
  type VideoWarningCode = "scene_duration_adjusted" | "scene_omitted_unreadable" | "scene_omitted_for_closer" | "scene_variable_clipped" | "chart_scale_imbalance" | "plan_incomplete" | "plan_missing_closer" | "provider_warning" | "provider_diagnostics_unavailable" | "media_budget_reached";