videowright 0.1.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 (306) hide show
  1. package/README.md +91 -0
  2. package/dist/cli/argv.d.ts +28 -0
  3. package/dist/cli/argv.d.ts.map +1 -0
  4. package/dist/cli/argv.js +115 -0
  5. package/dist/cli/argv.js.map +1 -0
  6. package/dist/cli/bin.d.ts +7 -0
  7. package/dist/cli/bin.d.ts.map +1 -0
  8. package/dist/cli/bin.js +10 -0
  9. package/dist/cli/bin.js.map +1 -0
  10. package/dist/cli/dev.d.ts +19 -0
  11. package/dist/cli/dev.d.ts.map +1 -0
  12. package/dist/cli/dev.js +104 -0
  13. package/dist/cli/dev.js.map +1 -0
  14. package/dist/cli/discover.d.ts +29 -0
  15. package/dist/cli/discover.d.ts.map +1 -0
  16. package/dist/cli/discover.js +104 -0
  17. package/dist/cli/discover.js.map +1 -0
  18. package/dist/cli/discover_project.d.ts +29 -0
  19. package/dist/cli/discover_project.d.ts.map +1 -0
  20. package/dist/cli/discover_project.js +108 -0
  21. package/dist/cli/discover_project.js.map +1 -0
  22. package/dist/cli/errors.d.ts +10 -0
  23. package/dist/cli/errors.d.ts.map +1 -0
  24. package/dist/cli/errors.js +13 -0
  25. package/dist/cli/errors.js.map +1 -0
  26. package/dist/cli/ffmpeg.d.ts +57 -0
  27. package/dist/cli/ffmpeg.d.ts.map +1 -0
  28. package/dist/cli/ffmpeg.js +122 -0
  29. package/dist/cli/ffmpeg.js.map +1 -0
  30. package/dist/cli/index.d.ts +7 -0
  31. package/dist/cli/index.d.ts.map +1 -0
  32. package/dist/cli/index.js +152 -0
  33. package/dist/cli/index.js.map +1 -0
  34. package/dist/cli/playwright_check.d.ts +44 -0
  35. package/dist/cli/playwright_check.d.ts.map +1 -0
  36. package/dist/cli/playwright_check.js +20 -0
  37. package/dist/cli/playwright_check.js.map +1 -0
  38. package/dist/cli/prompt.d.ts +13 -0
  39. package/dist/cli/prompt.d.ts.map +1 -0
  40. package/dist/cli/prompt.js +47 -0
  41. package/dist/cli/prompt.js.map +1 -0
  42. package/dist/cli/render.d.ts +60 -0
  43. package/dist/cli/render.d.ts.map +1 -0
  44. package/dist/cli/render.js +471 -0
  45. package/dist/cli/render.js.map +1 -0
  46. package/dist/cli/script_cmd.d.ts +26 -0
  47. package/dist/cli/script_cmd.d.ts.map +1 -0
  48. package/dist/cli/script_cmd.js +88 -0
  49. package/dist/cli/script_cmd.js.map +1 -0
  50. package/dist/cli/time_shim.d.ts +44 -0
  51. package/dist/cli/time_shim.d.ts.map +1 -0
  52. package/dist/cli/time_shim.js +390 -0
  53. package/dist/cli/time_shim.js.map +1 -0
  54. package/dist/cli/ts_loader.d.ts +28 -0
  55. package/dist/cli/ts_loader.d.ts.map +1 -0
  56. package/dist/cli/ts_loader.js +95 -0
  57. package/dist/cli/ts_loader.js.map +1 -0
  58. package/dist/cli/vite_helpers.d.ts +62 -0
  59. package/dist/cli/vite_helpers.d.ts.map +1 -0
  60. package/dist/cli/vite_helpers.js +273 -0
  61. package/dist/cli/vite_helpers.js.map +1 -0
  62. package/dist/index.d.ts +11 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +14 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/player/hash_router.d.ts +23 -0
  67. package/dist/player/hash_router.d.ts.map +1 -0
  68. package/dist/player/hash_router.js +49 -0
  69. package/dist/player/hash_router.js.map +1 -0
  70. package/dist/player/hud.d.ts +33 -0
  71. package/dist/player/hud.d.ts.map +1 -0
  72. package/dist/player/hud.js +357 -0
  73. package/dist/player/hud.js.map +1 -0
  74. package/dist/player/index.d.ts +123 -0
  75. package/dist/player/index.d.ts.map +1 -0
  76. package/dist/player/index.js +848 -0
  77. package/dist/player/index.js.map +1 -0
  78. package/dist/player/input.d.ts +14 -0
  79. package/dist/player/input.d.ts.map +1 -0
  80. package/dist/player/input.js +90 -0
  81. package/dist/player/input.js.map +1 -0
  82. package/dist/player/slot.d.ts +22 -0
  83. package/dist/player/slot.d.ts.map +1 -0
  84. package/dist/player/slot.js +43 -0
  85. package/dist/player/slot.js.map +1 -0
  86. package/dist/player/transitions/cut.d.ts +7 -0
  87. package/dist/player/transitions/cut.d.ts.map +1 -0
  88. package/dist/player/transitions/cut.js +9 -0
  89. package/dist/player/transitions/cut.js.map +1 -0
  90. package/dist/player/transitions/fade.d.ts +7 -0
  91. package/dist/player/transitions/fade.d.ts.map +1 -0
  92. package/dist/player/transitions/fade.js +18 -0
  93. package/dist/player/transitions/fade.js.map +1 -0
  94. package/dist/player/transitions/index.d.ts +4 -0
  95. package/dist/player/transitions/index.d.ts.map +1 -0
  96. package/dist/player/transitions/index.js +4 -0
  97. package/dist/player/transitions/index.js.map +1 -0
  98. package/dist/player/transitions/slide.d.ts +6 -0
  99. package/dist/player/transitions/slide.d.ts.map +1 -0
  100. package/dist/player/transitions/slide.js +35 -0
  101. package/dist/player/transitions/slide.js.map +1 -0
  102. package/dist/script/index.d.ts +2 -0
  103. package/dist/script/index.d.ts.map +1 -0
  104. package/dist/script/index.js +2 -0
  105. package/dist/script/index.js.map +1 -0
  106. package/dist/script/script.d.ts +10 -0
  107. package/dist/script/script.d.ts.map +1 -0
  108. package/dist/script/script.js +41 -0
  109. package/dist/script/script.js.map +1 -0
  110. package/dist/segment/SegmentRunner.d.ts +52 -0
  111. package/dist/segment/SegmentRunner.d.ts.map +1 -0
  112. package/dist/segment/SegmentRunner.js +187 -0
  113. package/dist/segment/SegmentRunner.js.map +1 -0
  114. package/dist/segment/defineConfig.d.ts +6 -0
  115. package/dist/segment/defineConfig.d.ts.map +1 -0
  116. package/dist/segment/defineConfig.js +7 -0
  117. package/dist/segment/defineConfig.js.map +1 -0
  118. package/dist/segment/defineSegment.d.ts +7 -0
  119. package/dist/segment/defineSegment.d.ts.map +1 -0
  120. package/dist/segment/defineSegment.js +25 -0
  121. package/dist/segment/defineSegment.js.map +1 -0
  122. package/dist/segment/index.d.ts +5 -0
  123. package/dist/segment/index.d.ts.map +1 -0
  124. package/dist/segment/index.js +4 -0
  125. package/dist/segment/index.js.map +1 -0
  126. package/dist/timeline/index.d.ts +73 -0
  127. package/dist/timeline/index.d.ts.map +1 -0
  128. package/dist/timeline/index.js +142 -0
  129. package/dist/timeline/index.js.map +1 -0
  130. package/dist/timeline/loadAudioTrack.d.ts +18 -0
  131. package/dist/timeline/loadAudioTrack.d.ts.map +1 -0
  132. package/dist/timeline/loadAudioTrack.js +44 -0
  133. package/dist/timeline/loadAudioTrack.js.map +1 -0
  134. package/dist/timeline/loadVoiceover.d.ts +18 -0
  135. package/dist/timeline/loadVoiceover.d.ts.map +1 -0
  136. package/dist/timeline/loadVoiceover.js +38 -0
  137. package/dist/timeline/loadVoiceover.js.map +1 -0
  138. package/dist/timeline/resolveTiming.d.ts +28 -0
  139. package/dist/timeline/resolveTiming.d.ts.map +1 -0
  140. package/dist/timeline/resolveTiming.js +63 -0
  141. package/dist/timeline/resolveTiming.js.map +1 -0
  142. package/dist/timeline/validateTiming.d.ts +29 -0
  143. package/dist/timeline/validateTiming.d.ts.map +1 -0
  144. package/dist/timeline/validateTiming.js +62 -0
  145. package/dist/timeline/validateTiming.js.map +1 -0
  146. package/dist/types.d.ts +216 -0
  147. package/dist/types.d.ts.map +1 -0
  148. package/dist/types.js +6 -0
  149. package/dist/types.js.map +1 -0
  150. package/package.json +47 -0
  151. package/skill/SKILL.md +64 -0
  152. package/skill/assets/hello_world/PLAN.md +31 -0
  153. package/skill/assets/hello_world/README.md +27 -0
  154. package/skill/assets/hello_world/audio/audio_plan.md +14 -0
  155. package/skill/assets/hello_world/segments/hello_intro.ts +69 -0
  156. package/skill/assets/hello_world/segments/hello_outro.ts +71 -0
  157. package/skill/assets/hello_world/timeline.ts +15 -0
  158. package/skill/assets/hello_world/voiceover_script/script.md +10 -0
  159. package/skill/assets/install/package.json +10 -0
  160. package/skill/assets/install/tsconfig.json +23 -0
  161. package/skill/assets/styles/editorial-mono/STYLE.md +124 -0
  162. package/skill/assets/styles/editorial-mono/brand.md +85 -0
  163. package/skill/assets/styles/editorial-mono/reference/animations.jsx +752 -0
  164. package/skill/assets/styles/editorial-mono/reference/scenes.html +563 -0
  165. package/skill/assets/styles/editorial-mono/sample/bullet.ts +101 -0
  166. package/skill/assets/styles/editorial-mono/sample/content.ts +104 -0
  167. package/skill/assets/styles/editorial-mono/sample/cta.ts +113 -0
  168. package/skill/assets/styles/editorial-mono/sample/feature.ts +111 -0
  169. package/skill/assets/styles/editorial-mono/sample/grid.ts +97 -0
  170. package/skill/assets/styles/editorial-mono/sample/kinetic.ts +96 -0
  171. package/skill/assets/styles/editorial-mono/sample/section.ts +101 -0
  172. package/skill/assets/styles/editorial-mono/sample/stat.ts +128 -0
  173. package/skill/assets/styles/editorial-mono/sample/title.ts +97 -0
  174. package/skill/assets/styles/editorial-mono/sample/ui-showcase.ts +159 -0
  175. package/skill/assets/styles/editorial-mono/tokens.css +44 -0
  176. package/skill/assets/styles/iso-diagram/STYLE.md +109 -0
  177. package/skill/assets/styles/iso-diagram/brand.md +32 -0
  178. package/skill/assets/styles/iso-diagram/reference/animations.jsx +673 -0
  179. package/skill/assets/styles/iso-diagram/reference/scenes.html +427 -0
  180. package/skill/assets/styles/iso-diagram/sample/bullet.ts +144 -0
  181. package/skill/assets/styles/iso-diagram/sample/content.ts +192 -0
  182. package/skill/assets/styles/iso-diagram/sample/cta.ts +162 -0
  183. package/skill/assets/styles/iso-diagram/sample/feature.ts +205 -0
  184. package/skill/assets/styles/iso-diagram/sample/grid.ts +181 -0
  185. package/skill/assets/styles/iso-diagram/sample/kinetic.ts +102 -0
  186. package/skill/assets/styles/iso-diagram/sample/section.ts +149 -0
  187. package/skill/assets/styles/iso-diagram/sample/stat.ts +164 -0
  188. package/skill/assets/styles/iso-diagram/sample/title.ts +173 -0
  189. package/skill/assets/styles/iso-diagram/sample/ui-showcase.ts +162 -0
  190. package/skill/assets/styles/iso-diagram/tokens.css +40 -0
  191. package/skill/assets/styles/motion-engineering/STYLE.md +106 -0
  192. package/skill/assets/styles/motion-engineering/brand.md +29 -0
  193. package/skill/assets/styles/motion-engineering/reference/animations.jsx +673 -0
  194. package/skill/assets/styles/motion-engineering/reference/scenes.html +513 -0
  195. package/skill/assets/styles/motion-engineering/sample/bullet.ts +176 -0
  196. package/skill/assets/styles/motion-engineering/sample/content.ts +228 -0
  197. package/skill/assets/styles/motion-engineering/sample/cta.ts +209 -0
  198. package/skill/assets/styles/motion-engineering/sample/feature.ts +299 -0
  199. package/skill/assets/styles/motion-engineering/sample/grid.ts +190 -0
  200. package/skill/assets/styles/motion-engineering/sample/kinetic.ts +159 -0
  201. package/skill/assets/styles/motion-engineering/sample/section.ts +196 -0
  202. package/skill/assets/styles/motion-engineering/sample/stat.ts +230 -0
  203. package/skill/assets/styles/motion-engineering/sample/title.ts +219 -0
  204. package/skill/assets/styles/motion-engineering/sample/ui-showcase.ts +267 -0
  205. package/skill/assets/styles/motion-engineering/tokens.css +40 -0
  206. package/skill/assets/styles/neon-terminal/STYLE.md +105 -0
  207. package/skill/assets/styles/neon-terminal/brand.md +27 -0
  208. package/skill/assets/styles/neon-terminal/reference/animations.jsx +673 -0
  209. package/skill/assets/styles/neon-terminal/reference/scenes.html +387 -0
  210. package/skill/assets/styles/neon-terminal/sample/bullet.ts +113 -0
  211. package/skill/assets/styles/neon-terminal/sample/content.ts +117 -0
  212. package/skill/assets/styles/neon-terminal/sample/cta.ts +131 -0
  213. package/skill/assets/styles/neon-terminal/sample/feature.ts +112 -0
  214. package/skill/assets/styles/neon-terminal/sample/grid.ts +128 -0
  215. package/skill/assets/styles/neon-terminal/sample/kinetic.ts +105 -0
  216. package/skill/assets/styles/neon-terminal/sample/section.ts +96 -0
  217. package/skill/assets/styles/neon-terminal/sample/stat.ts +123 -0
  218. package/skill/assets/styles/neon-terminal/sample/title.ts +122 -0
  219. package/skill/assets/styles/neon-terminal/sample/ui-showcase.ts +127 -0
  220. package/skill/assets/styles/neon-terminal/tokens.css +39 -0
  221. package/skill/assets/styles/risograph/STYLE.md +110 -0
  222. package/skill/assets/styles/risograph/brand.md +26 -0
  223. package/skill/assets/styles/risograph/reference/animations.jsx +673 -0
  224. package/skill/assets/styles/risograph/reference/scenes.html +403 -0
  225. package/skill/assets/styles/risograph/sample/bullet.ts +124 -0
  226. package/skill/assets/styles/risograph/sample/content.ts +135 -0
  227. package/skill/assets/styles/risograph/sample/cta.ts +149 -0
  228. package/skill/assets/styles/risograph/sample/feature.ts +152 -0
  229. package/skill/assets/styles/risograph/sample/grid.ts +123 -0
  230. package/skill/assets/styles/risograph/sample/kinetic.ts +125 -0
  231. package/skill/assets/styles/risograph/sample/section.ts +130 -0
  232. package/skill/assets/styles/risograph/sample/stat.ts +145 -0
  233. package/skill/assets/styles/risograph/sample/title.ts +132 -0
  234. package/skill/assets/styles/risograph/sample/ui-showcase.ts +147 -0
  235. package/skill/assets/styles/risograph/tokens.css +39 -0
  236. package/skill/assets/styles/swiss-console/STYLE.md +107 -0
  237. package/skill/assets/styles/swiss-console/brand.md +37 -0
  238. package/skill/assets/styles/swiss-console/reference/animations.jsx +673 -0
  239. package/skill/assets/styles/swiss-console/reference/scenes.html +420 -0
  240. package/skill/assets/styles/swiss-console/sample/bullet.ts +122 -0
  241. package/skill/assets/styles/swiss-console/sample/content.ts +137 -0
  242. package/skill/assets/styles/swiss-console/sample/cta.ts +109 -0
  243. package/skill/assets/styles/swiss-console/sample/feature.ts +163 -0
  244. package/skill/assets/styles/swiss-console/sample/grid.ts +145 -0
  245. package/skill/assets/styles/swiss-console/sample/kinetic.ts +117 -0
  246. package/skill/assets/styles/swiss-console/sample/section.ts +127 -0
  247. package/skill/assets/styles/swiss-console/sample/stat.ts +148 -0
  248. package/skill/assets/styles/swiss-console/sample/title.ts +148 -0
  249. package/skill/assets/styles/swiss-console/sample/ui-showcase.ts +198 -0
  250. package/skill/assets/styles/swiss-console/tokens.css +39 -0
  251. package/skill/install/INSTALL.md +400 -0
  252. package/skill/references/audio/audio_plan.md +199 -0
  253. package/skill/references/audio/build.md +208 -0
  254. package/skill/references/audio/cue_template.md +219 -0
  255. package/skill/references/audio/ffmpeg_cookbook.md +267 -0
  256. package/skill/references/audio/music/music.md +171 -0
  257. package/skill/references/audio/music/providers/elevenlabs.md +170 -0
  258. package/skill/references/audio/music/providers/manual.md +140 -0
  259. package/skill/references/audio/music/providers/openverse.md +265 -0
  260. package/skill/references/audio/sfx/providers/elevenlabs.md +152 -0
  261. package/skill/references/audio/sfx/providers/manual.md +117 -0
  262. package/skill/references/audio/sfx/providers/openverse.md +243 -0
  263. package/skill/references/audio/sfx/sfx.md +149 -0
  264. package/skill/references/audio/styles.md +102 -0
  265. package/skill/references/audio/sync.md +237 -0
  266. package/skill/references/audio/voiceover/animation_sync.md +142 -0
  267. package/skill/references/audio/voiceover/provider_script.md +153 -0
  268. package/skill/references/audio/voiceover/providers/elevenlabs.md +288 -0
  269. package/skill/references/audio/voiceover/providers/manual.md +100 -0
  270. package/skill/references/audio/voiceover/script_writing.md +100 -0
  271. package/skill/references/audio/voiceover/style_intake.md +56 -0
  272. package/skill/references/audio/voiceover/sync_algorithm.md +167 -0
  273. package/skill/references/audio/voiceover.md +296 -0
  274. package/skill/references/audio.md +135 -0
  275. package/skill/references/authoring_segment.md +446 -0
  276. package/skill/references/create_or_edit_video.md +232 -0
  277. package/skill/references/dev_server.md +157 -0
  278. package/skill/references/export.md +145 -0
  279. package/skill/references/new_video.md +117 -0
  280. package/skill/references/project_structure.md +144 -0
  281. package/skill/references/setup.md +109 -0
  282. package/skill/references/setup_new_style.md +158 -0
  283. package/skill/references/styles.md +154 -0
  284. package/skill/references/testing.md +115 -0
  285. package/skill/references/types.md +240 -0
  286. package/src/cli/entry/components/copy_button.ts +42 -0
  287. package/src/cli/entry/components/download_modal.ts +204 -0
  288. package/src/cli/entry/components/empty_state.ts +55 -0
  289. package/src/cli/entry/components/hide_hud_tab.ts +37 -0
  290. package/src/cli/entry/components/icons.ts +31 -0
  291. package/src/cli/entry/components/top_bar.ts +69 -0
  292. package/src/cli/entry/components/video_card.ts +57 -0
  293. package/src/cli/entry/dev_frame.ts +189 -0
  294. package/src/cli/entry/entry_index.ts +16 -0
  295. package/src/cli/entry/entry_video.ts +24 -0
  296. package/src/cli/entry/index.html +12 -0
  297. package/src/cli/entry/parse_slug.ts +14 -0
  298. package/src/cli/entry/render.html +17 -0
  299. package/src/cli/entry/render_entry.ts +121 -0
  300. package/src/cli/entry/styles/base.css +45 -0
  301. package/src/cli/entry/styles/components.css +605 -0
  302. package/src/cli/entry/styles/tokens.css +44 -0
  303. package/src/cli/entry/video.html +22 -0
  304. package/src/cli/entry/views/homepage.ts +66 -0
  305. package/src/cli/entry/views/video_view.ts +286 -0
  306. package/src/cli/entry/virtual.d.ts +8 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Inline SVG icons (Lucide-style).
3
+ * Each function returns an SVG string. 16px default, stroke-width 1.5, currentColor.
4
+ */
5
+
6
+ const ICON_ATTRS =
7
+ 'xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"';
8
+
9
+ export function iconDownload(): string {
10
+ return `<svg ${ICON_ATTRS}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`;
11
+ }
12
+
13
+ export function iconCopy(): string {
14
+ return `<svg ${ICON_ATTRS}><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
15
+ }
16
+
17
+ export function iconCheck(): string {
18
+ return `<svg ${ICON_ATTRS}><polyline points="20 6 9 17 4 12"/></svg>`;
19
+ }
20
+
21
+ export function iconX(): string {
22
+ return `<svg ${ICON_ATTRS}><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
23
+ }
24
+
25
+ export function iconChevronDown(): string {
26
+ return `<svg ${ICON_ATTRS}><polyline points="6 9 12 15 18 9"/></svg>`;
27
+ }
28
+
29
+ export function iconChevronUp(): string {
30
+ return `<svg ${ICON_ATTRS}><polyline points="18 15 12 9 6 15"/></svg>`;
31
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Top bar component shared across all pages.
3
+ * 56px tall, dark surface background, bottom border.
4
+ */
5
+
6
+ import { iconDownload } from "./icons.js";
7
+
8
+ export interface TopBarProps {
9
+ projectName: string;
10
+ breadcrumbTitle?: string;
11
+ showDownload?: boolean;
12
+ onDownload?: () => void;
13
+ }
14
+
15
+ export function renderTopBar(props: TopBarProps): HTMLElement {
16
+ const bar = document.createElement("nav");
17
+ bar.className = "vw-top-bar";
18
+ bar.setAttribute("aria-label", "Top navigation");
19
+
20
+ const left = document.createElement("div");
21
+ left.className = "vw-top-bar__left";
22
+
23
+ // Wordmark -- plain link home
24
+ const wordmark = document.createElement("a");
25
+ wordmark.className = "vw-top-bar__wordmark";
26
+ wordmark.href = "/";
27
+ wordmark.textContent = "videowright";
28
+ left.appendChild(wordmark);
29
+
30
+ // Breadcrumb separator + title (video view)
31
+ if (props.breadcrumbTitle) {
32
+ const sep = document.createElement("span");
33
+ sep.className = "vw-top-bar__sep";
34
+ sep.textContent = "/";
35
+ left.appendChild(sep);
36
+
37
+ const title = document.createElement("span");
38
+ title.className = "vw-top-bar__title";
39
+ title.textContent = props.breadcrumbTitle;
40
+ left.appendChild(title);
41
+ }
42
+
43
+ bar.appendChild(left);
44
+
45
+ const right = document.createElement("div");
46
+ right.className = "vw-top-bar__right";
47
+
48
+ // Download button (when enabled)
49
+ if (props.showDownload && props.onDownload) {
50
+ const dlBtn = document.createElement("button");
51
+ dlBtn.className = "vw-top-bar__icon-btn";
52
+ dlBtn.type = "button";
53
+ dlBtn.setAttribute("aria-label", "Download video");
54
+ dlBtn.innerHTML = iconDownload();
55
+ const handler = props.onDownload;
56
+ dlBtn.addEventListener("click", () => handler());
57
+ right.appendChild(dlBtn);
58
+ }
59
+
60
+ // Project name
61
+ const projectName = document.createElement("span");
62
+ projectName.className = "vw-top-bar__project";
63
+ projectName.textContent = props.projectName;
64
+ right.appendChild(projectName);
65
+
66
+ bar.appendChild(right);
67
+
68
+ return bar;
69
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Video card for the homepage grid.
3
+ * An anchor element that navigates to the video page on click.
4
+ */
5
+
6
+ import { iconDownload } from "./icons.js";
7
+
8
+ export interface VideoCardProps {
9
+ slug: string;
10
+ title: string;
11
+ style: string;
12
+ onDownload: () => void;
13
+ }
14
+
15
+ export function renderVideoCard(props: VideoCardProps): HTMLElement {
16
+ const card = document.createElement("a") as HTMLAnchorElement;
17
+ card.className = "vw-card";
18
+ card.href = `/video/${props.slug}`;
19
+ card.setAttribute("aria-label", `Open video: ${props.title}`);
20
+
21
+ // Header row: title + download icon
22
+ const header = document.createElement("div");
23
+ header.className = "vw-card__header";
24
+
25
+ const title = document.createElement("div");
26
+ title.className = "vw-card__title";
27
+ title.textContent = props.title;
28
+ header.appendChild(title);
29
+
30
+ const dlBtn = document.createElement("button");
31
+ dlBtn.className = "vw-card__download";
32
+ dlBtn.type = "button";
33
+ dlBtn.setAttribute("aria-label", `Download ${props.title}`);
34
+ dlBtn.innerHTML = iconDownload();
35
+ dlBtn.addEventListener("click", (e) => {
36
+ e.stopPropagation();
37
+ e.preventDefault();
38
+ props.onDownload();
39
+ });
40
+ header.appendChild(dlBtn);
41
+
42
+ card.appendChild(header);
43
+
44
+ // Slug
45
+ const slug = document.createElement("div");
46
+ slug.className = "vw-card__slug";
47
+ slug.textContent = props.slug;
48
+ card.appendChild(slug);
49
+
50
+ // Style badge
51
+ const badge = document.createElement("div");
52
+ badge.className = "vw-card__badge";
53
+ badge.textContent = props.style;
54
+ card.appendChild(badge);
55
+
56
+ return card;
57
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Dev preview frame sizing and scale-to-fit logic.
3
+ *
4
+ * Sets #player-host to the native target resolution and applies a CSS
5
+ * transform to scale the frame down to fit the video area above the HUD.
6
+ * The inner DOM renders at full native resolution so the preview matches
7
+ * render output pixel-for-pixel.
8
+ *
9
+ * NOTE: This module is dev-entry-only. It is imported by the video page
10
+ * entry (entry_video.ts) and should NOT be imported in non-dev contexts
11
+ * (SSR, production builds, etc.).
12
+ */
13
+
14
+ /** Padding inside the video area (each side). Kept small so the video fills more space. */
15
+ export const VIDEO_AREA_PADDING = 12;
16
+
17
+ /** Fixed height of the HUD strip at the bottom of the viewport.
18
+ * Must match the CSS `height` on `#dev-hud-container` in index.html. */
19
+ export const HUD_HEIGHT = 80;
20
+
21
+ /** Whether the HUD strip is currently visible. */
22
+ let hudVisible = true;
23
+
24
+ /**
25
+ * Returns HUD_HEIGHT when the HUD is visible, 0 when hidden.
26
+ * Used by the scale computation so the video area grows when the HUD is toggled off.
27
+ */
28
+ export function getCurrentHudHeight(): number {
29
+ return hudVisible ? HUD_HEIGHT : 0;
30
+ }
31
+
32
+ /**
33
+ * Compute the scale factor to fit `width x height` into available space.
34
+ * Never upscales (clamped to 1). Returns 1 if available space is non-positive.
35
+ */
36
+ export function computeScale(
37
+ targetWidth: number,
38
+ targetHeight: number,
39
+ availableWidth: number,
40
+ availableHeight: number,
41
+ ): number {
42
+ if (availableWidth <= 0 || availableHeight <= 0) return 1;
43
+ return Math.min(availableWidth / targetWidth, availableHeight / targetHeight, 1);
44
+ }
45
+
46
+ /** Stored reference to the scale-update function, set by applyDevFrameSize.
47
+ * Also used by toggleDevHud to recompute scale when the HUD is toggled. */
48
+ let updateScaleFn: (() => void) | null = null;
49
+
50
+ /**
51
+ * Toggle the HUD strip visibility. When hidden, the video area grows to fill
52
+ * the freed space and the scale factor is recomputed. The grid template
53
+ * switches between `auto 1fr 80px` (visible) and `0 1fr 0px` (hidden),
54
+ * collapsing both the nav row and HUD row so the video uses full height.
55
+ * The top bar (.vw-top-bar) is also hidden/shown in sync.
56
+ *
57
+ * Returns the new visibility state.
58
+ */
59
+ export function toggleDevHud(): boolean {
60
+ hudVisible = !hudVisible;
61
+
62
+ const layout = document.getElementById("dev-layout");
63
+ const hudContainer = document.getElementById("dev-hud-container");
64
+
65
+ // Toggle the top bar visibility.
66
+ // IMPORTANT: Do NOT use display:none — that removes the element from the
67
+ // grid flow, causing subsequent grid items to shift into the wrong tracks.
68
+ // Instead use visibility:hidden + overflow:hidden so it stays in its grid
69
+ // track (which is collapsed to height 0 via grid-template-rows).
70
+ const topBar = layout?.querySelector(".vw-top-bar") as HTMLElement | null;
71
+ if (topBar) {
72
+ if (hudVisible) {
73
+ topBar.style.visibility = "";
74
+ topBar.style.overflow = "";
75
+ } else {
76
+ topBar.style.visibility = "hidden";
77
+ topBar.style.overflow = "hidden";
78
+ }
79
+ }
80
+
81
+ if (layout) {
82
+ layout.style.gridTemplateRows = hudVisible ? "auto 1fr 80px" : "0 1fr 0px";
83
+ }
84
+ if (hudContainer) {
85
+ // Hide only the inner HUD content (.vw-hud), NOT the container itself.
86
+ // The container must stay visible (with overflow:visible) so the
87
+ // hide-HUD tab — positioned at top:-8px — remains accessible when
88
+ // the HUD strip is collapsed.
89
+ const hudContent = hudContainer.querySelector(".vw-hud") as HTMLElement | null;
90
+ if (hudContent) {
91
+ hudContent.style.display = hudVisible ? "" : "none";
92
+ }
93
+ }
94
+
95
+ // Recompute scale with the new available height
96
+ if (updateScaleFn) updateScaleFn();
97
+
98
+ return hudVisible;
99
+ }
100
+
101
+ /**
102
+ * Reset internal HUD visibility state. Intended for tests only.
103
+ */
104
+ export function _resetHudVisible(): void {
105
+ hudVisible = true;
106
+ updateScaleFn = null;
107
+ }
108
+
109
+ /**
110
+ * Measure the current height of the nav bar inside #dev-layout, if present.
111
+ * Returns 0 when there is no nav element (e.g., non-video views).
112
+ */
113
+ function getNavHeight(): number {
114
+ const nav = document.querySelector("#dev-layout > nav");
115
+ if (!nav) return 0;
116
+ return (nav as HTMLElement).offsetHeight || 0;
117
+ }
118
+
119
+ /**
120
+ * Apply native resolution to #player-host and scale-to-fit the video area.
121
+ * Attaches a resize listener to keep the scale updated.
122
+ *
123
+ * The available space for the frame is:
124
+ * width: viewport width - 2 * VIDEO_AREA_PADDING
125
+ * height: viewport height - getNavHeight() - getCurrentHudHeight() - 2 * VIDEO_AREA_PADDING
126
+ *
127
+ * getCurrentHudHeight() returns HUD_HEIGHT when the HUD is visible, 0 when
128
+ * hidden, so the scale recomputes correctly when the HUD is toggled.
129
+ * getNavHeight() measures the nav bar introduced by the multi-video layout.
130
+ *
131
+ * Expects the DOM to contain:
132
+ * - `#player-host` — the rendering surface
133
+ * - `#dev-scale-container` — wrapper that receives scaled dimensions
134
+ */
135
+ export function applyDevFrameSize(resolution: [number, number]): void {
136
+ const [width, height] = resolution;
137
+ const host = document.getElementById("player-host");
138
+ const scaleContainer = document.getElementById("dev-scale-container");
139
+ if (!host || !scaleContainer) return;
140
+
141
+ host.style.width = `${width}px`;
142
+ host.style.height = `${height}px`;
143
+
144
+ const updateScale = () => {
145
+ const availableW = window.innerWidth - VIDEO_AREA_PADDING * 2;
146
+ const availableH =
147
+ window.innerHeight - getNavHeight() - getCurrentHudHeight() - VIDEO_AREA_PADDING * 2;
148
+
149
+ const scale = computeScale(width, height, availableW, availableH);
150
+ host.style.transform = `scale(${scale})`;
151
+ scaleContainer.style.width = `${width * scale}px`;
152
+ scaleContainer.style.height = `${height * scale}px`;
153
+ };
154
+
155
+ updateScaleFn = updateScale;
156
+ updateScale();
157
+ // Called once per page load in MPA mode; no listener cleanup needed since
158
+ // the page is torn down on navigation.
159
+ window.addEventListener("resize", updateScale);
160
+ }
161
+
162
+ /**
163
+ * Install the "H" key listener for toggling HUD visibility.
164
+ * Ignores key events when the target is an input, textarea, or
165
+ * contenteditable element. Called once during dev boot (MPA page load).
166
+ *
167
+ * Returns a cleanup function that removes the listener.
168
+ */
169
+ export function installHudKeyListener(): () => void {
170
+ const handler = (e: KeyboardEvent) => {
171
+ if (e.key !== "h" && e.key !== "H") return;
172
+ // Ignore when typing in form fields or contenteditable
173
+ const target = e.target as HTMLElement | null;
174
+ if (target) {
175
+ const tag = target.tagName;
176
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
177
+ if (target.isContentEditable || target.contentEditable === "true") return;
178
+ }
179
+ toggleDevHud();
180
+ };
181
+
182
+ // Called once per page load in MPA mode; no dedup guard needed since
183
+ // the page is torn down on navigation.
184
+ document.addEventListener("keydown", handler);
185
+
186
+ return () => {
187
+ document.removeEventListener("keydown", handler);
188
+ };
189
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Entry point for the index page (video list).
3
+ * Imports global styles and mounts the homepage view.
4
+ */
5
+
6
+ import "./styles/tokens.css";
7
+ import "./styles/base.css";
8
+ import "./styles/components.css";
9
+
10
+ import projectInfo from "virtual:videowright/project";
11
+ import { renderHomepage } from "./views/homepage.js";
12
+
13
+ const app = document.getElementById("app");
14
+ if (!app) throw new Error("No #app element found");
15
+
16
+ app.appendChild(renderHomepage(projectInfo));
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Entry point for the video page.
3
+ * Parses the video slug from the URL pathname and boots the video player.
4
+ */
5
+
6
+ import "./styles/tokens.css";
7
+ import "./styles/base.css";
8
+ import "./styles/components.css";
9
+
10
+ import projectInfo from "virtual:videowright/project";
11
+ import { parseSlugFromPath } from "./parse_slug.js";
12
+ import { renderVideoView } from "./views/video_view.js";
13
+
14
+ const app = document.getElementById("app");
15
+ if (!app) throw new Error("No #app element found");
16
+
17
+ const slug = parseSlugFromPath(location.pathname);
18
+ const video = slug ? projectInfo.videos.find((v) => v.slug === slug) : null;
19
+
20
+ if (!video) {
21
+ app.innerHTML = `<p>Unknown video. <a href="/">Back to videos</a></p>`;
22
+ } else {
23
+ app.appendChild(renderVideoView(projectInfo, video.slug));
24
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Videowright Dev</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/entry_index.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Utility to extract a video slug from a URL pathname.
3
+ * Separated from entry_video.ts so it can be unit-tested without
4
+ * pulling in virtual modules or the full entry bootstrap.
5
+ */
6
+
7
+ /**
8
+ * Extract the video slug from a pathname like `/video/<slug>` or `/video/<slug>/`.
9
+ * Returns null if the pathname does not match the expected pattern.
10
+ */
11
+ export function parseSlugFromPath(pathname: string): string | null {
12
+ const match = pathname.match(/^\/video\/([^/]+)\/?$/);
13
+ return match ? decodeURIComponent(match[1]) : null;
14
+ }
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Videowright Render</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }
10
+ #player-host { width: 100vw; height: 100vh; }
11
+ </style>
12
+ </head>
13
+ <body>
14
+ <div id="player-host"></div>
15
+ <script type="module" src="./render_entry.ts"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Render-mode entry client for `videowright render`.
3
+ * Similar to entry_video.ts but boots the player in render mode
4
+ * and exposes page-evaluate-callable globals for frame-by-frame control.
5
+ */
6
+
7
+ // Virtual module provided by globalsVirtualModulePlugin -- exports concrete
8
+ // values that were previously injected via Vite's define: config.
9
+ import { consumerRoot, renderFps, timelinePath } from "virtual:vw-globals";
10
+
11
+ // Virtual module provided by segmentDiscoveryPlugin
12
+ import segmentGlobRaw from "virtual:vw-segments";
13
+
14
+ import {
15
+ Player,
16
+ applyMetaDefaults,
17
+ buildSegmentLoaderMap,
18
+ buildTransitionLoaderMap,
19
+ validateTimeline,
20
+ } from "../../index.js";
21
+ import type { Config, Timeline } from "../../index.js";
22
+
23
+ // Extend window for render-mode globals
24
+ declare global {
25
+ interface Window {
26
+ __VW_RENDER__: boolean;
27
+ __VW_RENDER_READY__: boolean;
28
+ __VW_RENDER_ADVANCE__: (isLast: boolean) => Promise<boolean>;
29
+ __VW_RENDER_ERROR__: string | null;
30
+ __VW_SEGMENT_ADVANCES__: Record<string, number[]>;
31
+ __VW_SEGMENTS_LOADED__: boolean;
32
+ __VW_ENGAGE_VIRTUAL_TIME__: () => void;
33
+ __VW_ADVANCE_CLOCK__: (deltaMs: number) => void | Promise<void>;
34
+ }
35
+ }
36
+
37
+ window.__VW_RENDER__ = true;
38
+ window.__VW_RENDER_READY__ = false;
39
+ window.__VW_RENDER_ERROR__ = null;
40
+ window.__VW_SEGMENTS_LOADED__ = false;
41
+
42
+ async function boot() {
43
+ const segmentGlob = segmentGlobRaw as Record<string, () => Promise<unknown>>;
44
+ const segmentLoaders = buildSegmentLoaderMap(segmentGlob);
45
+
46
+ // Load timeline
47
+ const timelineMod = (await import(/* @vite-ignore */ timelinePath)) as {
48
+ default: Timeline;
49
+ };
50
+ const timeline = timelineMod.default;
51
+
52
+ // Load config
53
+ const configMod = (await import(/* @vite-ignore */ `${consumerRoot}/videowright.config.ts`)) as {
54
+ default: Config;
55
+ };
56
+ const config = configMod.default;
57
+
58
+ const transitionLoaders = buildTransitionLoaderMap(config);
59
+ const finalTimeline = applyMetaDefaults(timeline, config);
60
+
61
+ // Validate
62
+ const result = validateTimeline(finalTimeline, segmentLoaders, transitionLoaders);
63
+ if (!result.ok) {
64
+ const messages = result.errors.map((e) => {
65
+ switch (e.kind) {
66
+ case "missing-segment":
67
+ return `Missing segment "${e.segmentId}" in timeline "${e.timelineTitle}"`;
68
+ case "missing-transition":
69
+ return `Missing transition "${e.transitionName}" on segment "${e.segmentId}"`;
70
+ case "missing-title":
71
+ return "Timeline is missing a title";
72
+ case "malformed-segment-path":
73
+ return `Malformed segment path: ${e.path}`;
74
+ }
75
+ });
76
+ throw new Error(`Timeline validation failed:\n${messages.join("\n")}`);
77
+ }
78
+
79
+ // Extract advances from each segment so drivers can read them from the browser
80
+ const advancesMap: Record<string, number[]> = {};
81
+ for (const entry of finalTimeline.segments) {
82
+ const loader = segmentLoaders.get(entry.id);
83
+ if (loader) {
84
+ const mod = await loader();
85
+ const seg = mod.default;
86
+ if (seg && Array.isArray(seg.advances)) {
87
+ advancesMap[entry.id] = seg.advances;
88
+ }
89
+ }
90
+ }
91
+ window.__VW_SEGMENT_ADVANCES__ = advancesMap;
92
+ window.__VW_SEGMENTS_LOADED__ = true;
93
+
94
+ // Mount player in render mode
95
+ const host = document.getElementById("player-host");
96
+ if (!host) throw new Error("No #player-host element found");
97
+
98
+ const fps = typeof renderFps === "number" ? renderFps : 60;
99
+ const player = new Player(host, { renderMode: true, hud: false, fps });
100
+ await player.load(finalTimeline, segmentLoaders, transitionLoaders);
101
+ await player.start();
102
+
103
+ // Expose render control globals for page.evaluate
104
+ window.__VW_RENDER_ADVANCE__ = async (isLast: boolean) => {
105
+ try {
106
+ return await player.renderAdvance(isLast);
107
+ } catch (e) {
108
+ window.__VW_RENDER_ERROR__ = e instanceof Error ? e.message : String(e);
109
+ return false;
110
+ }
111
+ };
112
+
113
+ window.__VW_RENDER_READY__ = true;
114
+ }
115
+
116
+ boot().catch((e) => {
117
+ console.error("Videowright render boot error:", e);
118
+ window.__VW_RENDER_ERROR__ = e instanceof Error ? e.message : String(e);
119
+ // Still set ready so the driver can read the error
120
+ window.__VW_RENDER_READY__ = true;
121
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Base styles: reset, typography, body.
3
+ */
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ margin: 0;
9
+ padding: 0;
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ html,
14
+ body {
15
+ width: 100%;
16
+ height: 100%;
17
+ background: var(--bg-base);
18
+ color: var(--text-primary);
19
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
20
+ font-size: 14px;
21
+ line-height: 1.5;
22
+ -webkit-font-smoothing: antialiased;
23
+ -moz-osx-font-smoothing: grayscale;
24
+ }
25
+
26
+ #app {
27
+ width: 100%;
28
+ height: 100%;
29
+ }
30
+
31
+ /* Focus styles */
32
+ :focus-visible {
33
+ outline: 2px solid var(--accent);
34
+ outline-offset: 2px;
35
+ }
36
+
37
+ /* Reduced motion */
38
+ @media (prefers-reduced-motion: reduce) {
39
+ *,
40
+ *::before,
41
+ *::after {
42
+ transition-duration: 0.01ms !important;
43
+ animation-duration: 0.01ms !important;
44
+ }
45
+ }