@scenar/cli 0.9.1 → 0.11.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/example-bundle/assets/index-DxmqjG-8.js +388 -0
  2. package/example-bundle/assets/style-B2sxnVVZ.css +1 -0
  3. package/example-bundle/embed.js +1 -1
  4. package/example-bundle/index.html +2 -2
  5. package/example-bundle/logo.png +0 -0
  6. package/example-bundle/pack-manifest.json +33 -9
  7. package/example-bundle/soundtrack/music.mp3 +0 -0
  8. package/example-bundle/soundtrack/sfx/click.mp3 +0 -0
  9. package/example-bundle/soundtrack/sfx/keystroke.mp3 +0 -0
  10. package/package.json +4 -4
  11. package/src/__tests__/collect-pack-shots.test.ts +27 -1
  12. package/src/__tests__/elevenlabs-provider.test.ts +187 -0
  13. package/src/__tests__/embed-snippet.test.ts +5 -0
  14. package/src/__tests__/find-authored-soundtrack.test.ts +54 -0
  15. package/src/__tests__/find-authored-title-cards.test.ts +65 -0
  16. package/src/__tests__/find-authored-viewport.test.ts +58 -0
  17. package/src/__tests__/load-bundle.test.ts +67 -0
  18. package/src/__tests__/narrate-command.test.ts +65 -1
  19. package/src/__tests__/narration-cache.test.ts +134 -0
  20. package/src/__tests__/pack-bundle-contract.test.ts +2 -0
  21. package/src/__tests__/pack-generate-embed-entry.test.ts +99 -16
  22. package/src/__tests__/presenter-command.test.ts +366 -0
  23. package/src/__tests__/render-command.test.ts +11 -0
  24. package/src/__tests__/render-generate-entry.test.ts +102 -0
  25. package/src/__tests__/resolve-pack-viewport.test.ts +46 -0
  26. package/src/__tests__/resolve-provider.test.ts +32 -3
  27. package/src/__tests__/scenario-validator.test.ts +174 -0
  28. package/src/__tests__/stage-assets.test.ts +269 -0
  29. package/src/commands/narrate.js +1 -1
  30. package/src/commands/narrate.js.map +1 -1
  31. package/src/commands/narrate.ts +1 -1
  32. package/src/commands/pack.d.ts.map +1 -1
  33. package/src/commands/pack.js +23 -4
  34. package/src/commands/pack.js.map +1 -1
  35. package/src/commands/pack.ts +26 -5
  36. package/src/commands/presenter.d.ts +3 -0
  37. package/src/commands/presenter.d.ts.map +1 -0
  38. package/src/commands/presenter.js +89 -0
  39. package/src/commands/presenter.js.map +1 -0
  40. package/src/commands/presenter.ts +107 -0
  41. package/src/commands/render.d.ts.map +1 -1
  42. package/src/commands/render.js +2 -0
  43. package/src/commands/render.js.map +1 -1
  44. package/src/commands/render.ts +6 -0
  45. package/src/embed/embed-snippet.d.ts +5 -0
  46. package/src/embed/embed-snippet.d.ts.map +1 -1
  47. package/src/embed/embed-snippet.js +6 -1
  48. package/src/embed/embed-snippet.js.map +1 -1
  49. package/src/embed/embed-snippet.ts +6 -1
  50. package/src/index.d.ts.map +1 -1
  51. package/src/index.js +2 -0
  52. package/src/index.js.map +1 -1
  53. package/src/index.ts +2 -0
  54. package/src/narrate/run-narrate.d.ts +15 -1
  55. package/src/narrate/run-narrate.d.ts.map +1 -1
  56. package/src/narrate/run-narrate.js +11 -4
  57. package/src/narrate/run-narrate.js.map +1 -1
  58. package/src/narrate/run-narrate.ts +15 -6
  59. package/src/pack/bundle-contract.d.ts +1 -0
  60. package/src/pack/bundle-contract.d.ts.map +1 -1
  61. package/src/pack/bundle-contract.js +3 -0
  62. package/src/pack/bundle-contract.js.map +1 -1
  63. package/src/pack/bundle-contract.ts +3 -0
  64. package/src/pack/collect-pack-shots.d.ts +16 -5
  65. package/src/pack/collect-pack-shots.d.ts.map +1 -1
  66. package/src/pack/collect-pack-shots.js +13 -4
  67. package/src/pack/collect-pack-shots.js.map +1 -1
  68. package/src/pack/collect-pack-shots.ts +35 -8
  69. package/src/pack/generate-embed-entry.d.ts +7 -0
  70. package/src/pack/generate-embed-entry.d.ts.map +1 -1
  71. package/src/pack/generate-embed-entry.js +105 -7
  72. package/src/pack/generate-embed-entry.js.map +1 -1
  73. package/src/pack/generate-embed-entry.ts +110 -7
  74. package/src/pack/run-pack.d.ts +8 -2
  75. package/src/pack/run-pack.d.ts.map +1 -1
  76. package/src/pack/run-pack.js +120 -17
  77. package/src/pack/run-pack.js.map +1 -1
  78. package/src/pack/run-pack.ts +164 -19
  79. package/src/pack/viewport.d.ts +17 -0
  80. package/src/pack/viewport.d.ts.map +1 -1
  81. package/src/pack/viewport.js +19 -0
  82. package/src/pack/viewport.js.map +1 -1
  83. package/src/pack/viewport.ts +29 -0
  84. package/src/presenter/heygen-client.d.ts +69 -0
  85. package/src/presenter/heygen-client.d.ts.map +1 -0
  86. package/src/presenter/heygen-client.js +154 -0
  87. package/src/presenter/heygen-client.js.map +1 -0
  88. package/src/presenter/heygen-client.ts +224 -0
  89. package/src/presenter/run-presenter.d.ts +67 -0
  90. package/src/presenter/run-presenter.d.ts.map +1 -0
  91. package/src/presenter/run-presenter.js +348 -0
  92. package/src/presenter/run-presenter.js.map +1 -0
  93. package/src/presenter/run-presenter.ts +536 -0
  94. package/src/render/generate-entry.d.ts +4 -0
  95. package/src/render/generate-entry.d.ts.map +1 -1
  96. package/src/render/generate-entry.js +75 -7
  97. package/src/render/generate-entry.js.map +1 -1
  98. package/src/render/generate-entry.ts +84 -7
  99. package/src/render/run-render.d.ts +6 -0
  100. package/src/render/run-render.d.ts.map +1 -1
  101. package/src/render/run-render.js +58 -0
  102. package/src/render/run-render.js.map +1 -1
  103. package/src/render/run-render.ts +72 -0
  104. package/src/render/stage-assets.d.ts +44 -0
  105. package/src/render/stage-assets.d.ts.map +1 -0
  106. package/src/render/stage-assets.js +91 -0
  107. package/src/render/stage-assets.js.map +1 -0
  108. package/src/render/stage-assets.ts +130 -0
  109. package/src/tts/echogarden.d.ts.map +1 -1
  110. package/src/tts/echogarden.js +4 -2
  111. package/src/tts/echogarden.js.map +1 -1
  112. package/src/tts/echogarden.ts +5 -2
  113. package/src/tts/edge-tts.d.ts.map +1 -1
  114. package/src/tts/edge-tts.js +2 -1
  115. package/src/tts/edge-tts.js.map +1 -1
  116. package/src/tts/edge-tts.ts +2 -1
  117. package/src/tts/elevenlabs.d.ts +15 -0
  118. package/src/tts/elevenlabs.d.ts.map +1 -0
  119. package/src/tts/elevenlabs.js +73 -0
  120. package/src/tts/elevenlabs.js.map +1 -0
  121. package/src/tts/elevenlabs.ts +105 -0
  122. package/src/tts/openai.d.ts.map +1 -1
  123. package/src/tts/openai.js +2 -1
  124. package/src/tts/openai.js.map +1 -1
  125. package/src/tts/openai.ts +2 -1
  126. package/src/tts/resolve-provider.d.ts +1 -1
  127. package/src/tts/resolve-provider.d.ts.map +1 -1
  128. package/src/tts/resolve-provider.js +10 -3
  129. package/src/tts/resolve-provider.js.map +1 -1
  130. package/src/tts/resolve-provider.ts +12 -4
  131. package/src/tts/types.d.ts +9 -0
  132. package/src/tts/types.d.ts.map +1 -1
  133. package/src/tts/types.ts +10 -0
  134. package/src/util/confirm.d.ts +11 -0
  135. package/src/util/confirm.d.ts.map +1 -0
  136. package/src/util/confirm.js +24 -0
  137. package/src/util/confirm.js.map +1 -0
  138. package/src/util/confirm.ts +23 -0
  139. package/src/util/load-bundle.d.ts +17 -1
  140. package/src/util/load-bundle.d.ts.map +1 -1
  141. package/src/util/load-bundle.js +19 -11
  142. package/src/util/load-bundle.js.map +1 -1
  143. package/src/util/load-bundle.ts +38 -12
  144. package/src/util/load-ts.d.ts +100 -0
  145. package/src/util/load-ts.d.ts.map +1 -1
  146. package/src/util/load-ts.js +176 -0
  147. package/src/util/load-ts.js.map +1 -1
  148. package/src/util/load-ts.ts +227 -0
  149. package/src/util/narration-cache.d.ts +13 -5
  150. package/src/util/narration-cache.d.ts.map +1 -1
  151. package/src/util/narration-cache.js +10 -8
  152. package/src/util/narration-cache.js.map +1 -1
  153. package/src/util/narration-cache.ts +22 -6
  154. package/src/util/presenter-cache.d.ts +44 -0
  155. package/src/util/presenter-cache.d.ts.map +1 -0
  156. package/src/util/presenter-cache.js +43 -0
  157. package/src/util/presenter-cache.js.map +1 -0
  158. package/src/util/presenter-cache.ts +101 -0
  159. package/src/util/scenario-assets.d.ts +47 -0
  160. package/src/util/scenario-assets.d.ts.map +1 -0
  161. package/src/util/scenario-assets.js +71 -0
  162. package/src/util/scenario-assets.js.map +1 -0
  163. package/src/util/scenario-assets.ts +111 -0
  164. package/src/util/soundtrack-assets.d.ts +34 -0
  165. package/src/util/soundtrack-assets.d.ts.map +1 -0
  166. package/src/util/soundtrack-assets.js +48 -0
  167. package/src/util/soundtrack-assets.js.map +1 -0
  168. package/src/util/soundtrack-assets.ts +69 -0
  169. package/src/validate/scenario-validator.d.ts.map +1 -1
  170. package/src/validate/scenario-validator.js +82 -0
  171. package/src/validate/scenario-validator.js.map +1 -1
  172. package/src/validate/scenario-validator.ts +110 -0
  173. package/tsconfig.tsbuildinfo +1 -1
  174. package/example-bundle/assets/index-DlmG1Uh7.js +0 -388
  175. package/example-bundle/assets/style-BjquDnTE.css +0 -1
@@ -0,0 +1 @@
1
+ @source "./**/*.js";@layer scenar{.scenar{--scenar-surface: #ffffff;--scenar-border: #e5e7eb;--scenar-foreground: #0f172a;--scenar-muted-foreground: #64748b;--scenar-card: #ffffff;--scenar-accent: #f1f5f9;--scenar-primary: #0f172a;--scenar-ring: #0f172a;--scenar-chrome: #202124;--scenar-chrome-raised: #35363a;--scenar-chrome-foreground: #bdc1c6;--scenar-cursor-stroke: #ffffff;--scenar-caption-surface: rgb(0 0 0 / .5);--scenar-caption-foreground: rgb(255 255 255 / .95);--scenar-presenter-width: 24%;--scenar-presenter-radius: 10px;--scenar-presenter-margin: 12px;--scenar-presenter-shadow: 0 12px 32px -8px rgb(15 23 42 / .4), 0 2px 8px -2px rgb(15 23 42 / .25);--scenar-backdrop-glow-1: #b7d4f2;--scenar-backdrop-glow-2: #e3d0ec;--scenar-backdrop-glow-3: #b4c4ea;--scenar-backdrop-base-1: #e4ecf7;--scenar-backdrop-base-2: #e6e1f0;--scenar-backdrop: radial-gradient(120% 100% at 12% 8%, var(--scenar-backdrop-glow-1) 0%, transparent 55%), radial-gradient(110% 95% at 88% 12%, var(--scenar-backdrop-glow-2) 0%, transparent 55%), radial-gradient(130% 110% at 70% 100%, var(--scenar-backdrop-glow-3) 0%, transparent 60%), linear-gradient(135deg, var(--scenar-backdrop-base-1) 0%, var(--scenar-backdrop-base-2) 100%);--scenar-shadow-window: 0 24px 64px -16px rgb(15 23 42 / .35), 0 4px 16px -4px rgb(15 23 42 / .2);--scenar-radius-window: 10px}.scenar.dark{--scenar-surface: #0f172a;--scenar-border: #1e293b;--scenar-foreground: #f8fafc;--scenar-muted-foreground: #94a3b8;--scenar-card: #1e293b;--scenar-accent: #1e293b;--scenar-primary: #f8fafc;--scenar-ring: #f8fafc;--scenar-chrome: #202124;--scenar-chrome-raised: #35363a;--scenar-chrome-foreground: #bdc1c6;--scenar-cursor-stroke: #ffffff;--scenar-presenter-shadow: 0 12px 32px -8px rgb(0 0 0 / .65), 0 2px 8px -2px rgb(0 0 0 / .4);--scenar-backdrop-glow-1: #1c3a63;--scenar-backdrop-glow-2: #372a5e;--scenar-backdrop-glow-3: #142647;--scenar-backdrop-base-1: #101828;--scenar-backdrop-base-2: #14122a;--scenar-shadow-window: 0 24px 64px -16px rgb(0 0 0 / .65), 0 4px 16px -4px rgb(0 0 0 / .4);--scenar-radius-window: 10px}}/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--drop-shadow-sm:0 1px 2px #00000026;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-foreground:var(--scenar-foreground);--color-border:var(--scenar-border)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.top-1{top:calc(var(--spacing) * 1)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing) * 3)}.-right-px{right:-1px}.right-0{right:calc(var(--spacing) * 0)}.-bottom-px{bottom:-1px}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-1{bottom:calc(var(--spacing) * 1)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-16{bottom:calc(var(--spacing) * 16)}.bottom-28{bottom:calc(var(--spacing) * 28)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing) * 0)}.left-1\/2{left:50%}.left-4{left:calc(var(--spacing) * 4)}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[15\]{z-index:15}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-0\.5{margin-right:calc(var(--spacing) * .5)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0\.5{height:calc(var(--spacing) * .5)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-16{height:calc(var(--spacing) * 16)}.h-\[5px\]{height:5px}.h-\[7px\]{height:7px}.h-\[14px\]{height:14px}.h-\[15px\]{height:15px}.h-full{height:100%}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-0\.5{width:calc(var(--spacing) * .5)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-1\/3{width:33.3333%}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-16{width:calc(var(--spacing) * 16)}.w-\[3px\]{width:3px}.w-\[7px\]{width:7px}.w-\[14px\]{width:14px}.w-\[15px\]{width:15px}.w-\[42px\]{width:42px}.w-\[160px\]{width:160px}.w-full{width:100%}.w-px{width:1px}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[80\%\]{max-width:80%}.max-w-\[85\%\]{max-width:85%}.max-w-\[180px\]{max-width:180px}.max-w-\[200px\]{max-width:200px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[2\.5rem\]{min-width:2.5rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-2{--tw-translate-x:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-px{--tw-translate-x:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-2{--tw-translate-y:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-px{--tw-translate-y:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-move{cursor:move}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-\[5px\]{gap:5px}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-\[\#1a1a1a\]{border-color:#1a1a1a}.border-\[\#1e1e1e\]{border-color:#1e1e1e}.border-\[\#2a2a2e\]{border-color:#2a2a2e}.border-\[\#2d2d2d\]{border-color:#2d2d2d}.border-\[\#3a3a3a\]{border-color:#3a3a3a}.border-\[\#3a3f47\]{border-color:#3a3f47}.border-\[\#3c4043\]{border-color:#3c4043}.border-\[\#007acc\]{border-color:#007acc}.border-\[\#ff6c37\]{border-color:#ff6c37}.border-\[var\(--scenar-border\)\],.border-border{border-color:var(--scenar-border)}.border-transparent{border-color:#0000}.bg-\[\#0b93f6\]{background-color:#0b93f6}.bg-\[\#1a1a1a\]{background-color:#1a1a1a}.bg-\[\#1a73e8\]{background-color:#1a73e8}.bg-\[\#1c1c1c\]{background-color:#1c1c1c}.bg-\[\#1e1e1e\]{background-color:#1e1e1e}.bg-\[\#2a2a2e\]{background-color:#2a2a2e}.bg-\[\#2c2c2c\]{background-color:#2c2c2c}.bg-\[\#2d2d2d\]{background-color:#2d2d2d}.bg-\[\#3b82f6\]{background-color:#3b82f6}.bg-\[\#3b82f6\]\/10{background-color:#3b82f61a}.bg-\[\#3b82f6\]\/15{background-color:#3b82f626}.bg-\[\#5f6368\]{background-color:#5f6368}.bg-\[\#007acc\]{background-color:#007acc}.bg-\[\#007acc\]\/10{background-color:#007acc1a}.bg-\[\#22c55e\]{background-color:#22c55e}.bg-\[\#22c55e\]\/15{background-color:#22c55e26}.bg-\[\#28c840\]{background-color:#28c840}.bg-\[\#94a3b8\]\/15{background-color:#94a3b826}.bg-\[\#181b1f\]{background-color:#181b1f}.bg-\[\#999\]{background-color:#999}.bg-\[\#3274d9\]{background-color:#3274d9}.bg-\[\#04395e\]{background-color:#04395e}.bg-\[\#6366f1\]{background-color:#6366f1}.bg-\[\#111217\]{background-color:#111217}.bg-\[\#202124\]{background-color:#202124}.bg-\[\#252526\]{background-color:#252526}.bg-\[\#323232\]{background-color:#323232}.bg-\[\#323233\]{background-color:#323233}.bg-\[\#333333\]{background-color:#333}.bg-\[\#404040\]{background-color:#404040}.bg-\[\#eab308\]\/15{background-color:#eab30826}.bg-\[\#ef4444\]\/15{background-color:#ef444426}.bg-\[\#febc2e\]{background-color:#febc2e}.bg-\[\#ff5f57\]{background-color:#ff5f57}.bg-\[var\(--scenar-accent\)\]{background-color:var(--scenar-accent)}.bg-\[var\(--scenar-caption-surface\)\]{background-color:var(--scenar-caption-surface)}.bg-\[var\(--scenar-card\)\]{background-color:var(--scenar-card)}.bg-\[var\(--scenar-chrome\)\]{background-color:var(--scenar-chrome)}.bg-\[var\(--scenar-chrome-raised\)\]{background-color:var(--scenar-chrome-raised)}.bg-\[var\(--scenar-surface\)\]{background-color:var(--scenar-surface)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-black\/70{background-color:#000000b3}@supports (color:color-mix(in lab,red,red)){.bg-black\/70{background-color:color-mix(in oklab,var(--color-black) 70%,transparent)}}.bg-card{background-color:var(--scenar-card)}.bg-primary\/25{background-color:var(--scenar-primary)}@supports (color:color-mix(in lab,red,red)){.bg-primary\/25{background-color:color-mix(in oklab,var(--scenar-primary) 25%,transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/30{background-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.bg-white\/30{background-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.bg-white\/80{background-color:#fffc}@supports (color:color-mix(in lab,red,red)){.bg-white\/80{background-color:color-mix(in oklab,var(--color-white) 80%,transparent)}}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#6366f1\]{--tw-gradient-from:#6366f1;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-\[\#ff6600\]{--tw-gradient-from:#f60;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-black\/70{--tw-gradient-from:#000000b3}@supports (color:color-mix(in lab,red,red)){.from-black\/70{--tw-gradient-from:color-mix(in oklab, var(--color-black) 70%, transparent)}}.from-black\/70{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-black\/30{--tw-gradient-via:#0000004d}@supports (color:color-mix(in lab,red,red)){.via-black\/30{--tw-gradient-via:color-mix(in oklab, var(--color-black) 30%, transparent)}}.via-black\/30{--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-\[\#8b5cf6\]{--tw-gradient-to:#8b5cf6;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-\[\#f5a623\]{--tw-gradient-to:#f5a623;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-\[2px\]{padding-block:2px}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-\[10px\]{padding-top:10px}.pt-\[14px\]{padding-top:14px}.pr-1{padding-right:calc(var(--spacing) * 1)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-1{padding-left:calc(var(--spacing) * 1)}.pl-1\.5{padding-left:calc(var(--spacing) * 1.5)}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[16px\]{font-size:16px}.text-\[24px\]{font-size:24px}.leading-\[1\.6\]{--tw-leading:1.6;line-height:1.6}.leading-\[18px\]{--tw-leading:18px;line-height:18px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#3b82f6\]{color:#3b82f6}.text-\[\#5f6368\]{color:#5f6368}.text-\[\#6e7681\]{color:#6e7681}.text-\[\#9aa0a6\]{color:#9aa0a6}.text-\[\#9d9d9d\]{color:#9d9d9d}.text-\[\#22c55e\]{color:#22c55e}.text-\[\#50fa7b\]{color:#50fa7b}.text-\[\#94a3b8\]{color:#94a3b8}.text-\[\#519aba\]{color:#519aba}.text-\[\#666\]{color:#666}.text-\[\#888\]{color:#888}.text-\[\#64748b\]{color:#64748b}.text-\[\#666666\]{color:#666}.text-\[\#858585\]{color:#858585}.text-\[\#a0a0a0\]{color:#a0a0a0}.text-\[\#bd93f9\]{color:#bd93f9}.text-\[\#bdc1c6\]{color:#bdc1c6}.text-\[\#c6c6c6\]{color:#c6c6c6}.text-\[\#cccccc\]{color:#ccc}.text-\[\#d4d4d4\]{color:#d4d4d4}.text-\[\#dcb67a\]{color:#dcb67a}.text-\[\#e8e8e8\]{color:#e8e8e8}.text-\[\#e8eaed\]{color:#e8eaed}.text-\[\#e6995b\]{color:#e6995b}.text-\[\#eab308\]{color:#eab308}.text-\[\#ef4444\]{color:#ef4444}.text-\[\#f8f8f2\]{color:#f8f8f2}.text-\[\#f8f8f2\]\/70{color:#f8f8f2b3}.text-\[\#ff5555\]{color:#f55}.text-\[\#ffffff\]{color:#fff}.text-\[var\(--scenar-caption-foreground\)\]{color:var(--scenar-caption-foreground)}.text-\[var\(--scenar-chrome-foreground\)\]{color:var(--scenar-chrome-foreground)}.text-\[var\(--scenar-foreground\)\]{color:var(--scenar-foreground)}.text-\[var\(--scenar-muted-foreground\)\]{color:var(--scenar-muted-foreground)}.text-foreground{color:var(--scenar-foreground)}.text-muted-foreground{color:var(--scenar-muted-foreground)}.text-primary{color:var(--scenar-primary)}.text-white{color:var(--color-white)}.text-white\/40{color:#fff6}@supports (color:color-mix(in lab,red,red)){.text-white\/40{color:color-mix(in oklab,var(--color-white) 40%,transparent)}}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.text-white\/70{color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.text-white\/90{color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.text-white\/90{color:color-mix(in oklab,var(--color-white) 90%,transparent)}}.text-white\/95{color:#fffffff2}@supports (color:color-mix(in lab,red,red)){.text-white\/95{color:color-mix(in oklab,var(--color-white) 95%,transparent)}}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.drop-shadow-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[bottom\]{transition-property:bottom;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[height\]{transition-property:height;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:h-2:hover{height:calc(var(--spacing) * 2)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:bg-accent:hover{background-color:var(--scenar-accent)}.hover\:text-white:hover{color:var(--color-white)}}}@layer scenar{.scenar{--scenar-surface:#fff;--scenar-border:#e5e7eb;--scenar-foreground:#0f172a;--scenar-muted-foreground:#64748b;--scenar-card:#fff;--scenar-accent:#f1f5f9;--scenar-primary:#0f172a;--scenar-ring:#0f172a;--scenar-chrome:#202124;--scenar-chrome-raised:#35363a;--scenar-chrome-foreground:#bdc1c6;--scenar-cursor-stroke:#fff;--scenar-caption-surface:#00000080;--scenar-caption-foreground:#fffffff2;--scenar-presenter-width:24%;--scenar-presenter-radius:10px;--scenar-presenter-margin:12px;--scenar-presenter-shadow:0 12px 32px -8px #0f172a66, 0 2px 8px -2px #0f172a40;--scenar-backdrop-glow-1:#b7d4f2;--scenar-backdrop-glow-2:#e3d0ec;--scenar-backdrop-glow-3:#b4c4ea;--scenar-backdrop-base-1:#e4ecf7;--scenar-backdrop-base-2:#e6e1f0;--scenar-backdrop:radial-gradient(120% 100% at 12% 8%, var(--scenar-backdrop-glow-1) 0%, transparent 55%), radial-gradient(110% 95% at 88% 12%, var(--scenar-backdrop-glow-2) 0%, transparent 55%), radial-gradient(130% 110% at 70% 100%, var(--scenar-backdrop-glow-3) 0%, transparent 60%), linear-gradient(135deg, var(--scenar-backdrop-base-1) 0%, var(--scenar-backdrop-base-2) 100%);--scenar-shadow-window:0 24px 64px -16px #0f172a59, 0 4px 16px -4px #0f172a33;--scenar-radius-window:10px}.scenar.dark{--scenar-surface:#0f172a;--scenar-border:#1e293b;--scenar-foreground:#f8fafc;--scenar-muted-foreground:#94a3b8;--scenar-card:#1e293b;--scenar-accent:#1e293b;--scenar-primary:#f8fafc;--scenar-ring:#f8fafc;--scenar-chrome:#202124;--scenar-chrome-raised:#35363a;--scenar-chrome-foreground:#bdc1c6;--scenar-cursor-stroke:#fff;--scenar-presenter-shadow:0 12px 32px -8px #000000a6, 0 2px 8px -2px #0006;--scenar-backdrop-glow-1:#1c3a63;--scenar-backdrop-glow-2:#372a5e;--scenar-backdrop-glow-3:#142647;--scenar-backdrop-base-1:#101828;--scenar-backdrop-base-2:#14122a;--scenar-shadow-window:0 24px 64px -16px #000000a6, 0 4px 16px -4px #0006;--scenar-radius-window:10px}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}
@@ -1 +1 @@
1
- var ScenarEmbed=function(o){"use strict";var I=Object.defineProperty;var N=(o,u,d)=>u in o?I(o,u,{enumerable:!0,configurable:!0,writable:!0,value:d}):o[u]=d;var E=(o,u,d)=>N(o,typeof u!="symbol"?u+"":u,d);const u="scenar-embed";function g(e){return{source:u,v:1,...e}}function S(e){if(typeof e!="object"||e===null)return!1;const s=e;return s.source===u&&s.v===1}function l(e){return typeof e=="number"&&Number.isFinite(e)}function A(e){if(!S(e))return null;const s=e.type;switch(s){case"started":case"paused":case"completed":case"audioBlocked":return{type:s};case"ready":return l(e.totalSteps)&&typeof e.hasNarration=="boolean"?{type:s,totalSteps:e.totalSteps,hasNarration:e.hasNarration}:null;case"resize":return l(e.widthPx)&&l(e.heightPx)?{type:s,widthPx:e.widthPx,heightPx:e.heightPx}:null;case"stepchange":return l(e.stepIndex)&&l(e.totalSteps)?{type:s,stepIndex:e.stepIndex,totalSteps:e.totalSteps}:null;case"progress":return l(e.stepIndex)&&l(e.totalSteps)&&l(e.fraction)?{type:s,stepIndex:e.stepIndex,totalSteps:e.totalSteps,fraction:e.fraction}:null;case"error":return typeof e.message=="string"?{type:s,message:e.message}:null;default:return null}}function w(e,s={}){const{iframe:t,origin:n}=e,{onEvent:r}=s,a=typeof window<"u",c=i=>{var h;(h=t.contentWindow)==null||h.postMessage(g(i),n)},f=i=>{if(i.source!==t.contentWindow||i.origin!==n)return;const h=A(i.data);h&&(r==null||r(h))};return a&&window.addEventListener("message",f),{play:()=>c({type:"play"}),pause:()=>c({type:"pause"}),seek:i=>c({type:"seek",timeMs:i}),setMuted:i=>c({type:"setMuted",muted:i}),setVolume:i=>c({type:"setVolume",volume:i}),prefetch:()=>c({type:"prefetch"}),destroy:()=>{c({type:"destroy"}),a&&window.removeEventListener("message",f)}}}const T=896,C=480;function M(e,s){return e==="light"?"light":e==="dark"||s?"dark":"light"}function _(e,s){const t=new URL(e);return t.searchParams.set("theme",s),t.toString()}function P(e){return new URL(e).origin}function R(){return typeof document<"u"&&document.documentElement.classList.contains("dark")}function O(e,s){const{src:t,theme:n="auto",onAspectRatio:r,onEvent:a}=s,c=P(t);let f=null;const i=()=>{const m=M(n,R());m!==f&&(f=m,e.src=_(t,m))},h=w({iframe:e,origin:c},{onEvent:m=>{m.type==="resize"&&(r==null||r({widthPx:m.widthPx,heightPx:m.heightPx})),a==null||a(m)}});let p;return n==="auto"&&typeof MutationObserver<"u"&&typeof document<"u"&&(p=new MutationObserver(i),p.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]})),i(),{controller:h,destroy(){p==null||p.disconnect(),h.destroy()}}}const k="scenar-embed",x="Interactive product tour",L=typeof HTMLElement<"u"?HTMLElement:class{};class y extends L{constructor(){super(...arguments);E(this,"iframe",null);E(this,"mount",null)}static get observedAttributes(){return["src","title","theme"]}connectedCallback(){this.iframe||this.render(),this.attach()}disconnectedCallback(){var t;(t=this.mount)==null||t.destroy(),this.mount=null}attributeChangedCallback(t,n,r){if(n!==r){if(t==="title"){this.iframe&&this.iframe.setAttribute("title",this.titleAttr());return}this.isConnected&&this.attach()}}play(){var t;(t=this.mount)==null||t.controller.play()}pause(){var t;(t=this.mount)==null||t.controller.pause()}seek(t){var n;(n=this.mount)==null||n.controller.seek(t)}setMuted(t){var n;(n=this.mount)==null||n.controller.setMuted(t)}setVolume(t){var n;(n=this.mount)==null||n.controller.setVolume(t)}titleAttr(){return this.getAttribute("title")??x}themeAttr(){const t=this.getAttribute("theme");return t==="light"||t==="dark"?t:"auto"}setAspectRatio(t,n){this.style.aspectRatio=`${t} / ${n}`}render(){this.style.display="block",this.style.position="relative",this.style.width="100%",this.setAspectRatio(T,C);const t=document.createElement("iframe");t.setAttribute("title",this.titleAttr()),t.setAttribute("loading","lazy"),t.setAttribute("allow","autoplay; fullscreen"),t.setAttribute("allowfullscreen",""),t.style.cssText="position:absolute;inset:0;width:100%;height:100%;border:0;border-radius:inherit",this.appendChild(t),this.iframe=t}attach(){var n;const t=this.getAttribute("src");!t||!this.iframe||((n=this.mount)==null||n.destroy(),this.mount=O(this.iframe,{src:t,theme:this.themeAttr(),onAspectRatio:({widthPx:r,heightPx:a})=>this.setAspectRatio(r,a),onEvent:r=>this.dispatchEvent(new CustomEvent(`scenar:${r.type}`,{detail:r}))}))}}function b(e=k){typeof customElements>"u"||customElements.get(e)||customElements.define(e,y)}return b(),o.ScenarEmbedElement=y,o.defineScenarEmbed=b,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}),o}({});
1
+ var ScenarEmbed=function(l){"use strict";var W=Object.defineProperty;var G=(l,c,m)=>c in l?W(l,c,{enumerable:!0,configurable:!0,writable:!0,value:m}):l[c]=m;var S=(l,c,m)=>G(l,typeof c!="symbol"?c+"":c,m);const c="scenar-embed";function M(t){return{source:c,v:1,...t}}function T(t){if(typeof t!="object"||t===null)return!1;const n=t;return n.source===c&&n.v===1}function u(t){return typeof t=="number"&&Number.isFinite(t)}function _(t){if(t===void 0)return{};if(typeof t!="object"||t===null)return null;const n=t;return u(n.widthPx)&&n.widthPx>0&&u(n.heightPx)&&n.heightPx>0?{viewport:{widthPx:n.widthPx,heightPx:n.heightPx}}:null}function O(t){if(!T(t))return null;const n=t.type;switch(n){case"started":case"paused":case"completed":case"audioBlocked":return{type:n};case"ready":{if(!u(t.totalSteps)||typeof t.hasNarration!="boolean")return null;const e=_(t.viewport);return e===null?null:{type:n,totalSteps:t.totalSteps,hasNarration:t.hasNarration,...e}}case"resize":return u(t.widthPx)&&u(t.heightPx)?{type:n,widthPx:t.widthPx,heightPx:t.heightPx}:null;case"stepchange":return u(t.stepIndex)&&u(t.totalSteps)?{type:n,stepIndex:t.stepIndex,totalSteps:t.totalSteps}:null;case"progress":return u(t.stepIndex)&&u(t.totalSteps)&&u(t.fraction)?{type:n,stepIndex:t.stepIndex,totalSteps:t.totalSteps,fraction:t.fraction}:null;case"error":return typeof t.message=="string"?{type:n,message:t.message}:null;default:return null}}function k(t,n={}){const{iframe:e,origin:s}=t,{onEvent:o}=n,d=typeof window<"u",h=r=>{var a;(a=e.contentWindow)==null||a.postMessage(M(r),s)},E=r=>{if(r.source!==e.contentWindow||r.origin!==s)return;const a=O(r.data);a&&(o==null||o(a))};return d&&window.addEventListener("message",E),{play:()=>h({type:"play"}),pause:()=>h({type:"pause"}),seek:r=>h({type:"seek",timeMs:r}),setMuted:r=>h({type:"setMuted",muted:r}),setVolume:r=>h({type:"setVolume",volume:r}),setHostScale:r=>h({type:"setHostScale",scale:r}),prefetch:()=>h({type:"prefetch"}),destroy:()=>{h({type:"destroy"}),d&&window.removeEventListener("message",E)}}}const L=896,I=480;function v(t,n){return t==="light"?"light":t==="dark"||n?"dark":"light"}function B(t,n){const e=new URL(t);return e.searchParams.set("theme",n),e.toString()}function N(t){return new URL(t).origin}function V(){return typeof document<"u"&&document.documentElement.classList.contains("dark")}function D(t,n){const{src:e,theme:s="auto",onAspectRatio:o,onEvent:d}=n,h=N(e);let E=null;const r=()=>{const i=v(s,V());i!==E&&(E=i,t.src=B(e,i))};let a=null,p,A=null;const R=()=>{const i=t.parentElement;if(!a||!i)return;const{widthPx:w,heightPx:C}=a,g=i.getBoundingClientRect();if(g.width<=0)return;const f=Math.min(g.width/w,1),U=(g.width-w*f)/2,j=(g.height-C*f)/2;t.style.width=`${w}px`,t.style.height=`${C}px`,t.style.transformOrigin="0 0",t.style.transform=`translate(${U}px, ${j}px) scale(${f})`,f!==A&&(A=f,b.setHostScale(f))},F=i=>{a=i,R(),!p&&typeof ResizeObserver<"u"&&t.parentElement&&(p=new ResizeObserver(R),p.observe(t.parentElement))},b=k({iframe:t,origin:h},{onEvent:i=>{i.type==="resize"&&(o==null||o({widthPx:i.widthPx,heightPx:i.heightPx})),i.type==="ready"&&i.viewport&&F(i.viewport),d==null||d(i)}});let y;return s==="auto"&&typeof MutationObserver<"u"&&typeof document<"u"&&(y=new MutationObserver(r),y.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]})),r(),{controller:b,destroy(){y==null||y.disconnect(),p==null||p.disconnect(),b.destroy()}}}const H="scenar-embed",$="Interactive product tour",z=typeof HTMLElement<"u"?HTMLElement:class{};class x extends z{constructor(){super(...arguments);S(this,"iframe",null);S(this,"mount",null)}static get observedAttributes(){return["src","title","theme"]}connectedCallback(){this.iframe||this.render(),this.attach()}disconnectedCallback(){var e;(e=this.mount)==null||e.destroy(),this.mount=null}attributeChangedCallback(e,s,o){if(s!==o){if(e==="title"){this.iframe&&this.iframe.setAttribute("title",this.titleAttr());return}this.isConnected&&this.attach()}}play(){var e;(e=this.mount)==null||e.controller.play()}pause(){var e;(e=this.mount)==null||e.controller.pause()}seek(e){var s;(s=this.mount)==null||s.controller.seek(e)}setMuted(e){var s;(s=this.mount)==null||s.controller.setMuted(e)}setVolume(e){var s;(s=this.mount)==null||s.controller.setVolume(e)}titleAttr(){return this.getAttribute("title")??$}themeAttr(){const e=this.getAttribute("theme");return e==="light"||e==="dark"?e:"auto"}setAspectRatio(e,s){this.style.aspectRatio=`${e} / ${s}`}render(){this.style.display="block",this.style.position="relative",this.style.width="100%",this.style.overflow="hidden",this.setAspectRatio(L,I);const e=document.createElement("iframe");e.setAttribute("title",this.titleAttr()),e.setAttribute("loading","lazy"),e.setAttribute("allow","autoplay; fullscreen"),e.setAttribute("allowfullscreen",""),e.style.cssText="position:absolute;inset:0;width:100%;height:100%;border:0",this.appendChild(e),this.iframe=e}attach(){var s;const e=this.getAttribute("src");!e||!this.iframe||((s=this.mount)==null||s.destroy(),this.mount=D(this.iframe,{src:e,theme:this.themeAttr(),onAspectRatio:({widthPx:o,heightPx:d})=>this.setAspectRatio(o,d),onEvent:o=>this.dispatchEvent(new CustomEvent(`scenar:${o.type}`,{detail:o}))}))}}function P(t=H){typeof customElements>"u"||customElements.get(t)||customElements.define(t,x)}return P(),l.ScenarEmbedElement=x,l.defineScenarEmbed=P,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"}),l}({});
@@ -8,8 +8,8 @@
8
8
  <style>
9
9
  html, body { margin: 0; background: transparent; }
10
10
  </style>
11
- <script type="module" crossorigin src="./assets/index-DlmG1Uh7.js"></script>
12
- <link rel="stylesheet" crossorigin href="./assets/style-BjquDnTE.css">
11
+ <script type="module" crossorigin src="./assets/index-DxmqjG-8.js"></script>
12
+ <link rel="stylesheet" crossorigin href="./assets/style-B2sxnVVZ.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
Binary file
@@ -3,9 +3,9 @@
3
3
  "scenarioId": "welcome-tour",
4
4
  "files": [
5
5
  {
6
- "path": "assets/index-DlmG1Uh7.js",
7
- "sha256": "15421a104b9def8109220de091a7f4221e414ac833b3cad07971ab0d40b1e916",
8
- "sizeBytes": 596438,
6
+ "path": "assets/index-DxmqjG-8.js",
7
+ "sha256": "943edc809b33d2f888050b19903d1179858fa0ad9ef179f9a697d75cc43d926a",
8
+ "sizeBytes": 616649,
9
9
  "contentType": "text/javascript"
10
10
  },
11
11
  {
@@ -15,28 +15,52 @@
15
15
  "contentType": "image/png"
16
16
  },
17
17
  {
18
- "path": "assets/style-BjquDnTE.css",
19
- "sha256": "f74eb6dd3d21e3e259bc6f219302421239eb94559c3c759439a20f853d1ca036",
20
- "sizeBytes": 33952,
18
+ "path": "assets/style-B2sxnVVZ.css",
19
+ "sha256": "19697fd44dd7f413fa6b0cccd0ee58d2378bff715e12db9d5179e610a1d1141d",
20
+ "sizeBytes": 35482,
21
21
  "contentType": "text/css"
22
22
  },
23
23
  {
24
24
  "path": "embed.js",
25
- "sha256": "87ebc6301237b7b99b1f7eb8ab5e4e89362888b7d03a289af592e0cd38922c99",
26
- "sizeBytes": 4574,
25
+ "sha256": "d396c301fd5be9a4fb84a0467c783f25df7bd26f9692113cb709dca8002a428b",
26
+ "sizeBytes": 5463,
27
27
  "contentType": "text/javascript"
28
28
  },
29
29
  {
30
30
  "path": "index.html",
31
- "sha256": "c071cd3ba669dbea4696065b14bdbd1ef8f91f639b7d253d3e348699f52f6795",
31
+ "sha256": "81c5f00bde5ea175e8d06b92384aaea7708df0ed49704a8d8dcc433c2dd894e8",
32
32
  "sizeBytes": 533,
33
33
  "contentType": "text/html"
34
34
  },
35
+ {
36
+ "path": "logo.png",
37
+ "sha256": "646d467684f319064ba97fa832ef53f641d2872ddc6d0821e267b75f9ac714aa",
38
+ "sizeBytes": 15394,
39
+ "contentType": "image/png"
40
+ },
35
41
  {
36
42
  "path": "scenario.json",
37
43
  "sha256": "b9f3c79b8c3dcbba4a89eba5700fd8fc99e10902642150ea29419438ac2b74a3",
38
44
  "sizeBytes": 208,
39
45
  "contentType": "application/json"
46
+ },
47
+ {
48
+ "path": "soundtrack/music.mp3",
49
+ "sha256": "ff85028df41ce632b47e3bec9ae2ab8c0244658dbdd30eea2577d5da05f24131",
50
+ "sizeBytes": 154226,
51
+ "contentType": "audio/mpeg"
52
+ },
53
+ {
54
+ "path": "soundtrack/sfx/click.mp3",
55
+ "sha256": "406dd44f5714fb56c9094adebb860dec4cc3a3d62d256f181e700871b01ef33c",
56
+ "sizeBytes": 1253,
57
+ "contentType": "audio/mpeg"
58
+ },
59
+ {
60
+ "path": "soundtrack/sfx/keystroke.mp3",
61
+ "sha256": "0169864f105ae3f453737a81a8be65e3a33de60cacd2a8bef1731ba23a346a3d",
62
+ "sizeBytes": 835,
63
+ "contentType": "audio/mpeg"
40
64
  }
41
65
  ]
42
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenar/cli",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "Scenar CLI — validate scenarios, generate narration, and render videos.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -39,9 +39,9 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@scenar/core": "0.9.1",
43
- "@scenar/embed": "0.9.1",
44
- "@scenar/sdk": "0.9.1",
42
+ "@scenar/core": "0.11.0",
43
+ "@scenar/embed": "0.11.0",
44
+ "@scenar/sdk": "0.11.0",
45
45
  "commander": "^13.0.0",
46
46
  "yaml": "^2.7.0"
47
47
  },
@@ -42,6 +42,9 @@ describe("collectPackShots", () => {
42
42
  expect(await collectPackShots(scenarioDir)).toEqual({
43
43
  recorded: true,
44
44
  shots: ["opening", "detail-open"],
45
+ authoredViewport: null,
46
+ authoredSoundtrack: null,
47
+ authoredTitleCards: null,
45
48
  });
46
49
  });
47
50
 
@@ -53,12 +56,35 @@ describe("collectPackShots", () => {
53
56
  expect(await collectPackShots(scenarioDir)).toEqual({
54
57
  recorded: true,
55
58
  shots: ["detail-open"],
59
+ authoredViewport: null,
60
+ authoredSoundtrack: null,
61
+ authoredTitleCards: null,
56
62
  });
57
63
  });
58
64
 
59
65
  it("records an authoritatively empty list for a shot-less scenario", async () => {
60
66
  await writeSteps(`export const steps = [{ delayMs: 0 }, { delayMs: 900 }];`);
61
- expect(await collectPackShots(scenarioDir)).toEqual({ recorded: true, shots: [] });
67
+ expect(await collectPackShots(scenarioDir)).toEqual({
68
+ recorded: true,
69
+ shots: [],
70
+ authoredViewport: null,
71
+ authoredSoundtrack: null,
72
+ authoredTitleCards: null,
73
+ });
74
+ });
75
+
76
+ it("discovers the authored viewport alongside the shots (one SSR load)", async () => {
77
+ await writeSteps(
78
+ `export const viewport = { width: 1440, height: 900 };
79
+ export const steps = [{ delayMs: 0, shot: "opening" }];`,
80
+ );
81
+ expect(await collectPackShots(scenarioDir)).toEqual({
82
+ recorded: true,
83
+ shots: ["opening"],
84
+ authoredViewport: { width: 1440, height: 900 },
85
+ authoredSoundtrack: null,
86
+ authoredTitleCards: null,
87
+ });
62
88
  });
63
89
 
64
90
  it("degrades to recorded:false with the reason when the module cannot be imported", async () => {
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { createElevenLabsProvider } from "../tts/elevenlabs.js";
3
+
4
+ const DEFAULT_VOICE_ID = "21m00Tcm4TlvDq8ikWAM";
5
+
6
+ /** Build a successful with-timestamps API payload around the given audio bytes. */
7
+ function timestampsPayload(
8
+ audio: Buffer,
9
+ endTimesSeconds: number[] | null,
10
+ ): Record<string, unknown> {
11
+ return {
12
+ audio_base64: audio.toString("base64"),
13
+ alignment:
14
+ endTimesSeconds === null
15
+ ? null
16
+ : {
17
+ characters: endTimesSeconds.map(() => "x"),
18
+ character_start_times_seconds: endTimesSeconds.map((t) => t - 0.1),
19
+ character_end_times_seconds: endTimesSeconds,
20
+ },
21
+ };
22
+ }
23
+
24
+ function mockFetchOk(payload: Record<string, unknown>) {
25
+ const mockFetch = vi.fn().mockResolvedValue({
26
+ ok: true,
27
+ json: () => Promise.resolve(payload),
28
+ });
29
+ vi.stubGlobal("fetch", mockFetch);
30
+ return mockFetch;
31
+ }
32
+
33
+ describe("ElevenLabs TTS provider", () => {
34
+ const originalApiKey = process.env["ELEVENLABS_API_KEY"];
35
+ const originalModelId = process.env["ELEVENLABS_MODEL_ID"];
36
+
37
+ beforeEach(() => {
38
+ delete process.env["ELEVENLABS_API_KEY"];
39
+ delete process.env["ELEVENLABS_MODEL_ID"];
40
+ });
41
+
42
+ afterEach(() => {
43
+ if (originalApiKey !== undefined) {
44
+ process.env["ELEVENLABS_API_KEY"] = originalApiKey;
45
+ } else {
46
+ delete process.env["ELEVENLABS_API_KEY"];
47
+ }
48
+ if (originalModelId !== undefined) {
49
+ process.env["ELEVENLABS_MODEL_ID"] = originalModelId;
50
+ } else {
51
+ delete process.env["ELEVENLABS_MODEL_ID"];
52
+ }
53
+ vi.restoreAllMocks();
54
+ vi.unstubAllGlobals();
55
+ });
56
+
57
+ it("throws when ELEVENLABS_API_KEY is not set", async () => {
58
+ const provider = createElevenLabsProvider();
59
+ await expect(provider.synthesize("hello", {})).rejects.toThrow(/ELEVENLABS_API_KEY/);
60
+ });
61
+
62
+ it("calls the with-timestamps endpoint with correct parameters", async () => {
63
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
64
+
65
+ const audio = Buffer.from("fake-mp3-bytes");
66
+ const mockFetch = mockFetchOk(timestampsPayload(audio, [0.5, 1.2, 2.345]));
67
+
68
+ const provider = createElevenLabsProvider();
69
+ const result = await provider.synthesize("hello world", { voice: "custom-voice-id" });
70
+
71
+ expect(mockFetch).toHaveBeenCalledOnce();
72
+ const [url, init] = mockFetch.mock.calls[0]!;
73
+ expect(url).toBe(
74
+ "https://api.elevenlabs.io/v1/text-to-speech/custom-voice-id/with-timestamps" +
75
+ "?output_format=mp3_44100_128",
76
+ );
77
+ expect(init.headers["xi-api-key"]).toBe("el-test-key");
78
+
79
+ const body = JSON.parse(init.body as string);
80
+ expect(body.text).toBe("hello world");
81
+ expect(body.model_id).toBe("eleven_multilingual_v2");
82
+
83
+ expect(result.audio).toBeInstanceOf(Buffer);
84
+ expect(result.audio.equals(audio)).toBe(true);
85
+ });
86
+
87
+ it("uses the default stock voice when no voice is given", async () => {
88
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
89
+
90
+ const mockFetch = mockFetchOk(timestampsPayload(Buffer.from("audio"), [1.0]));
91
+
92
+ const provider = createElevenLabsProvider();
93
+ await provider.synthesize("hello", {});
94
+
95
+ const [url] = mockFetch.mock.calls[0]!;
96
+ expect(url).toContain(`/text-to-speech/${DEFAULT_VOICE_ID}/`);
97
+ });
98
+
99
+ it("derives exact duration from the last alignment end time", async () => {
100
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
101
+
102
+ mockFetchOk(timestampsPayload(Buffer.from("audio"), [0.5, 1.2, 2.3456]));
103
+
104
+ const provider = createElevenLabsProvider();
105
+ const result = await provider.synthesize("hello", {});
106
+
107
+ // ceil(2.3456s * 1000) — never truncate narration shorter than it is.
108
+ expect(result.durationMs).toBe(2346);
109
+ });
110
+
111
+ it("falls back to a bitrate estimate when alignment is null", async () => {
112
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
113
+
114
+ // 16000 bytes at 128 kbps: (16000 * 8) / 128 = 1000ms.
115
+ const audio = Buffer.alloc(16000);
116
+ mockFetchOk(timestampsPayload(audio, null));
117
+
118
+ const provider = createElevenLabsProvider();
119
+ const result = await provider.synthesize("hello", {});
120
+
121
+ expect(result.durationMs).toBe(1000);
122
+ });
123
+
124
+ it("falls back to a bitrate estimate when alignment arrays are empty", async () => {
125
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
126
+
127
+ const audio = Buffer.alloc(3200);
128
+ mockFetchOk(timestampsPayload(audio, []));
129
+
130
+ const provider = createElevenLabsProvider();
131
+ const result = await provider.synthesize("hello", {});
132
+
133
+ expect(result.durationMs).toBe(200);
134
+ });
135
+
136
+ it("honors the ELEVENLABS_MODEL_ID override in request and fingerprint", async () => {
137
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
138
+ process.env["ELEVENLABS_MODEL_ID"] = "eleven_v3";
139
+
140
+ const mockFetch = mockFetchOk(timestampsPayload(Buffer.from("audio"), [1.0]));
141
+
142
+ const provider = createElevenLabsProvider();
143
+ expect(provider.fingerprint).toBe(`elevenlabs/eleven_v3/${DEFAULT_VOICE_ID}`);
144
+
145
+ await provider.synthesize("hello", {});
146
+ const [, init] = mockFetch.mock.calls[0]!;
147
+ expect(JSON.parse(init.body as string).model_id).toBe("eleven_v3");
148
+ });
149
+
150
+ it("passes unicode narration text through unchanged", async () => {
151
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
152
+
153
+ const mockFetch = mockFetchOk(timestampsPayload(Buffer.from("audio"), [1.0]));
154
+
155
+ const provider = createElevenLabsProvider();
156
+ const text = "Grüße — 你好, नमस्ते! 🚀";
157
+ await provider.synthesize(text, {});
158
+
159
+ const [, init] = mockFetch.mock.calls[0]!;
160
+ expect(JSON.parse(init.body as string).text).toBe(text);
161
+ });
162
+
163
+ it("throws on non-OK response with status and body", async () => {
164
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
165
+
166
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
167
+ ok: false,
168
+ status: 401,
169
+ text: () => Promise.resolve("invalid api key"),
170
+ }));
171
+
172
+ const provider = createElevenLabsProvider();
173
+ await expect(provider.synthesize("hello", {})).rejects.toThrow(/401.*invalid api key/);
174
+ });
175
+
176
+ it("returns an empty buffer without crashing when the API returns empty audio", async () => {
177
+ process.env["ELEVENLABS_API_KEY"] = "el-test-key";
178
+
179
+ mockFetchOk(timestampsPayload(Buffer.alloc(0), [1.5]));
180
+
181
+ const provider = createElevenLabsProvider();
182
+ const result = await provider.synthesize("hello", {});
183
+
184
+ expect(result.audio.length).toBe(0);
185
+ expect(result.durationMs).toBe(1500);
186
+ });
187
+ });
@@ -11,6 +11,11 @@ describe("buildEmbedSnippet", () => {
11
11
  expect(snippet).toContain("width:100%");
12
12
  });
13
13
 
14
+ it("clips the wrapper so a host-applied border-radius rounds the embed", () => {
15
+ const snippet = buildEmbedSnippet({ embedUrl, viewport: { width: 896, height: 480 } });
16
+ expect(snippet).toContain("overflow:hidden");
17
+ });
18
+
14
19
  it("embeds the URL as the iframe src and lazy-loads", () => {
15
20
  const snippet = buildEmbedSnippet({ embedUrl, viewport: { width: 896, height: 480 } });
16
21
  expect(snippet).toContain(`src="${embedUrl}"`);
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { findAuthoredSoundtrack } from "../util/load-ts.js";
3
+
4
+ const STEPS = [{ delayMs: 0 }, { delayMs: 1500 }];
5
+ const SOUNDTRACK = { musicSrc: "./soundtrack/music.mp3", musicVolume: 0.3, sfx: true };
6
+
7
+ describe("findAuthoredSoundtrack", () => {
8
+ it("finds the soundtrack riding a scenario-shaped export (createScenario)", () => {
9
+ const exports = {
10
+ default: { soundtrack: SOUNDTRACK, views: {}, steps: STEPS },
11
+ };
12
+ expect(findAuthoredSoundtrack(exports)).toEqual(SOUNDTRACK);
13
+ });
14
+
15
+ it("finds a bare `soundtrack` named export (directory-form authoring)", () => {
16
+ const exports = { steps: STEPS, soundtrack: SOUNDTRACK };
17
+ expect(findAuthoredSoundtrack(exports)).toEqual(SOUNDTRACK);
18
+ });
19
+
20
+ it("prefers the scenario-shaped export over a bare named export", () => {
21
+ const exports = {
22
+ default: { soundtrack: SOUNDTRACK, steps: STEPS },
23
+ soundtrack: { musicSrc: "./other.mp3" },
24
+ };
25
+ expect(findAuthoredSoundtrack(exports)).toEqual(SOUNDTRACK);
26
+ });
27
+
28
+ it("returns null when the module authors no soundtrack", () => {
29
+ expect(findAuthoredSoundtrack({ steps: STEPS })).toBeNull();
30
+ expect(findAuthoredSoundtrack({})).toBeNull();
31
+ });
32
+
33
+ it("ignores a soundtrack on an object that carries no steps", () => {
34
+ const exports = {
35
+ config: { soundtrack: SOUNDTRACK },
36
+ steps: STEPS,
37
+ };
38
+ expect(findAuthoredSoundtrack(exports)).toBeNull();
39
+ });
40
+
41
+ it("copies only the known soundtrack fields", () => {
42
+ const exports = {
43
+ steps: STEPS,
44
+ soundtrack: { ...SOUNDTRACK, stray: "ignored" },
45
+ };
46
+ expect(findAuthoredSoundtrack(exports)).toEqual(SOUNDTRACK);
47
+ });
48
+
49
+ it("rejects non-object soundtrack exports", () => {
50
+ expect(findAuthoredSoundtrack({ steps: STEPS, soundtrack: "music.mp3" })).toBeNull();
51
+ expect(findAuthoredSoundtrack({ steps: STEPS, soundtrack: ["music.mp3"] })).toBeNull();
52
+ expect(findAuthoredSoundtrack({ steps: STEPS, soundtrack: null })).toBeNull();
53
+ });
54
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { findAuthoredTitleCards } from "../util/load-ts.js";
3
+
4
+ const STEPS = [{ delayMs: 0 }, { delayMs: 1500 }];
5
+ const TITLE_CARDS = {
6
+ intro: { title: "Acme Deploy", subtitle: "Ship in seconds", logoSrc: "./logo.png" },
7
+ outro: { title: "Try it today", ctaText: "acme.dev/start", durationMs: 4000 },
8
+ };
9
+
10
+ describe("findAuthoredTitleCards", () => {
11
+ it("finds title cards riding a scenario-shaped export (createScenario)", () => {
12
+ const exports = {
13
+ default: { titleCards: TITLE_CARDS, views: {}, steps: STEPS },
14
+ };
15
+ expect(findAuthoredTitleCards(exports)).toEqual(TITLE_CARDS);
16
+ });
17
+
18
+ it("finds a bare `titleCards` named export (directory-form authoring)", () => {
19
+ const exports = { steps: STEPS, titleCards: TITLE_CARDS };
20
+ expect(findAuthoredTitleCards(exports)).toEqual(TITLE_CARDS);
21
+ });
22
+
23
+ it("prefers the scenario-shaped export over a bare named export", () => {
24
+ const exports = {
25
+ default: { titleCards: TITLE_CARDS, steps: STEPS },
26
+ titleCards: { intro: { title: "Other" } },
27
+ };
28
+ expect(findAuthoredTitleCards(exports)).toEqual(TITLE_CARDS);
29
+ });
30
+
31
+ it("returns null when the module authors no title cards", () => {
32
+ expect(findAuthoredTitleCards({ steps: STEPS })).toBeNull();
33
+ expect(findAuthoredTitleCards({})).toBeNull();
34
+ });
35
+
36
+ it("ignores title cards on an object that carries no steps", () => {
37
+ const exports = {
38
+ config: { titleCards: TITLE_CARDS },
39
+ steps: STEPS,
40
+ };
41
+ expect(findAuthoredTitleCards(exports)).toBeNull();
42
+ });
43
+
44
+ it("copies only the known card fields on each side", () => {
45
+ const exports = {
46
+ steps: STEPS,
47
+ titleCards: {
48
+ intro: { ...TITLE_CARDS.intro, stray: "ignored" },
49
+ outro: { ...TITLE_CARDS.outro, onClick: () => {} },
50
+ },
51
+ };
52
+ expect(findAuthoredTitleCards(exports)).toEqual(TITLE_CARDS);
53
+ });
54
+
55
+ it("keeps one-sided configs one-sided", () => {
56
+ const exports = { steps: STEPS, titleCards: { outro: TITLE_CARDS.outro } };
57
+ expect(findAuthoredTitleCards(exports)).toEqual({ outro: TITLE_CARDS.outro });
58
+ });
59
+
60
+ it("rejects non-object titleCards exports", () => {
61
+ expect(findAuthoredTitleCards({ steps: STEPS, titleCards: "cards" })).toBeNull();
62
+ expect(findAuthoredTitleCards({ steps: STEPS, titleCards: ["intro"] })).toBeNull();
63
+ expect(findAuthoredTitleCards({ steps: STEPS, titleCards: null })).toBeNull();
64
+ });
65
+ });