@1agh/maude 0.42.0 → 0.44.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 (332) hide show
  1. package/apps/studio/.ai/cache/_stats.json +7 -0
  2. package/apps/studio/acp/bootstrap-brief.ts +12 -2
  3. package/apps/studio/acp/bridge.ts +44 -10
  4. package/apps/studio/acp/env.ts +23 -0
  5. package/apps/studio/acp/login-state.ts +263 -0
  6. package/apps/studio/acp/plugin-bootstrap.ts +31 -25
  7. package/apps/studio/acp/probe.ts +152 -1
  8. package/apps/studio/annotations-layer.tsx +1451 -324
  9. package/apps/studio/annotations-model.ts +58 -0
  10. package/apps/studio/api.ts +568 -24
  11. package/apps/studio/bin/_audio-search.mjs +140 -0
  12. package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
  13. package/apps/studio/bin/_import-asset.mjs +815 -0
  14. package/apps/studio/bin/_import-brand.mjs +635 -0
  15. package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
  16. package/apps/studio/bin/_import-tokens.mjs +1201 -0
  17. package/apps/studio/bin/_ingest-footage.mjs +386 -0
  18. package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
  19. package/apps/studio/bin/_transcribe.mjs +419 -0
  20. package/apps/studio/bin/_transcribe.test.mjs +80 -0
  21. package/apps/studio/bin/_video-playwright.mjs +141 -71
  22. package/apps/studio/bin/audio-search.sh +28 -0
  23. package/apps/studio/bin/generate.sh +154 -0
  24. package/apps/studio/bin/import-asset.sh +34 -0
  25. package/apps/studio/bin/import-brand.sh +29 -0
  26. package/apps/studio/bin/import-tokens.sh +33 -0
  27. package/apps/studio/bin/ingest-footage.sh +27 -0
  28. package/apps/studio/bin/photo-adjust.sh +163 -0
  29. package/apps/studio/bin/photo-bg-remove.sh +258 -0
  30. package/apps/studio/bin/probe-footage.sh +28 -0
  31. package/apps/studio/bin/read-annotations.mjs +125 -2
  32. package/apps/studio/bin/transcribe.sh +39 -0
  33. package/apps/studio/canvas-edit.ts +656 -12
  34. package/apps/studio/canvas-lib.tsx +637 -10
  35. package/apps/studio/canvas-pipeline.ts +65 -7
  36. package/apps/studio/canvas-shell.tsx +156 -16
  37. package/apps/studio/client/app.jsx +2385 -699
  38. package/apps/studio/client/export-center.jsx +35 -1
  39. package/apps/studio/client/generate-dialog.jsx +352 -0
  40. package/apps/studio/client/github.js +18 -0
  41. package/apps/studio/client/inspector-controls.jsx +781 -0
  42. package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
  43. package/apps/studio/client/panels/ChatPanel.jsx +15 -33
  44. package/apps/studio/client/panels/GitPanel.jsx +8 -0
  45. package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
  46. package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
  47. package/apps/studio/client/panels/ReadinessList.jsx +227 -7
  48. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
  49. package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
  50. package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
  51. package/apps/studio/client/panels/StickerPicker.jsx +160 -0
  52. package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
  53. package/apps/studio/client/photo-knobs.jsx +432 -0
  54. package/apps/studio/client/styles/3-shell-maude.css +376 -28
  55. package/apps/studio/client/styles/4-components.css +223 -0
  56. package/apps/studio/client/styles/6-acp-chat.css +86 -0
  57. package/apps/studio/client/tour/quick-setup-tour.js +39 -0
  58. package/apps/studio/commands/annotation-strokes-command.ts +13 -3
  59. package/apps/studio/commands/edit-source-command.ts +31 -4
  60. package/apps/studio/config.schema.json +70 -0
  61. package/apps/studio/context-menu.tsx +42 -8
  62. package/apps/studio/context.ts +16 -0
  63. package/apps/studio/design-setup-readiness.ts +115 -0
  64. package/apps/studio/dist/client.bundle.js +5572 -21
  65. package/apps/studio/dist/comment-mount.js +2 -2
  66. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  67. package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
  68. package/apps/studio/dist/runtime/pixi-js.js +519 -519
  69. package/apps/studio/dist/styles.css +1 -1
  70. package/apps/studio/dom-selection.ts +25 -0
  71. package/apps/studio/draw/palette.ts +34 -0
  72. package/apps/studio/exporters/jobs.ts +47 -5
  73. package/apps/studio/exporters/video.ts +24 -4
  74. package/apps/studio/footage/schema.test.ts +287 -0
  75. package/apps/studio/footage/schema.ts +662 -0
  76. package/apps/studio/footage-store.ts +235 -0
  77. package/apps/studio/fs-watch.ts +10 -0
  78. package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
  79. package/apps/studio/generation/adapters/gemini.ts +542 -0
  80. package/apps/studio/generation/adapters/groq.test.ts +80 -0
  81. package/apps/studio/generation/adapters/groq.ts +247 -0
  82. package/apps/studio/generation/audio-library.test.ts +95 -0
  83. package/apps/studio/generation/audio-library.ts +156 -0
  84. package/apps/studio/generation/captions.test.ts +78 -0
  85. package/apps/studio/generation/captions.ts +132 -0
  86. package/apps/studio/generation/download.ts +76 -0
  87. package/apps/studio/generation/elevenlabs.test.ts +223 -0
  88. package/apps/studio/generation/gemini.test.ts +350 -0
  89. package/apps/studio/generation/jobs.test.ts +128 -0
  90. package/apps/studio/generation/jobs.ts +243 -0
  91. package/apps/studio/generation/keys.test.ts +75 -0
  92. package/apps/studio/generation/keys.ts +162 -0
  93. package/apps/studio/generation/prefs.test.ts +95 -0
  94. package/apps/studio/generation/prefs.ts +82 -0
  95. package/apps/studio/generation/registry.ts +88 -0
  96. package/apps/studio/generation/types.test.ts +123 -0
  97. package/apps/studio/generation/types.ts +266 -0
  98. package/apps/studio/generation/whisper-models.test.ts +152 -0
  99. package/apps/studio/generation/whisper-models.ts +257 -0
  100. package/apps/studio/generation/whisper-spike-results.md +61 -0
  101. package/apps/studio/git/endpoints.ts +68 -7
  102. package/apps/studio/git/service.ts +199 -21
  103. package/apps/studio/github/service.ts +67 -0
  104. package/apps/studio/handoff.ts +3 -2
  105. package/apps/studio/http.ts +1039 -22
  106. package/apps/studio/input-router.tsx +10 -0
  107. package/apps/studio/inspect.ts +53 -1
  108. package/apps/studio/media/intro.mp4 +0 -0
  109. package/apps/studio/media-commit-chain.ts +119 -0
  110. package/apps/studio/paths.ts +20 -0
  111. package/apps/studio/photo/filters.ts +285 -0
  112. package/apps/studio/photo/pipeline.ts +607 -0
  113. package/apps/studio/photo/schema.ts +367 -0
  114. package/apps/studio/photo-store.ts +154 -0
  115. package/apps/studio/readiness.ts +108 -20
  116. package/apps/studio/runtime-bundle.ts +29 -6
  117. package/apps/studio/scaffold-design.ts +1145 -0
  118. package/apps/studio/server.ts +18 -2
  119. package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
  120. package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
  121. package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
  122. package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
  123. package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
  124. package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
  125. package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
  126. package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
  127. package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
  128. package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
  129. package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
  130. package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
  131. package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
  132. package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
  133. package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
  134. package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
  135. package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
  136. package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
  137. package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
  138. package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
  139. package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
  140. package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
  141. package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
  142. package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
  143. package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
  144. package/apps/studio/stickers/life-style/best.png +0 -0
  145. package/apps/studio/stickers/life-style/come-on.png +0 -0
  146. package/apps/studio/stickers/life-style/cut.png +0 -0
  147. package/apps/studio/stickers/life-style/detox.png +0 -0
  148. package/apps/studio/stickers/life-style/just.png +0 -0
  149. package/apps/studio/stickers/life-style/manifest.json +68 -0
  150. package/apps/studio/stickers/life-style/mirror.png +0 -0
  151. package/apps/studio/stickers/life-style/nice.png +0 -0
  152. package/apps/studio/stickers/life-style/objects.png +0 -0
  153. package/apps/studio/stickers/life-style/ok.png +0 -0
  154. package/apps/studio/stickers/life-style/queen.png +0 -0
  155. package/apps/studio/stickers/life-style/star.png +0 -0
  156. package/apps/studio/stickers/life-style/sun.png +0 -0
  157. package/apps/studio/stickers/life-style/water.png +0 -0
  158. package/apps/studio/stickers/life-style/yeah.png +0 -0
  159. package/apps/studio/stickers/life-style/you-can.png +0 -0
  160. package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
  161. package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
  162. package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
  163. package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
  164. package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
  165. package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
  166. package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
  167. package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
  168. package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
  169. package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
  170. package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
  171. package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
  172. package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
  173. package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
  174. package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
  175. package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
  176. package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
  177. package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
  178. package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
  179. package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
  180. package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
  181. package/apps/studio/stickers/project-status/group-100.png +0 -0
  182. package/apps/studio/stickers/project-status/group-101.png +0 -0
  183. package/apps/studio/stickers/project-status/group-102.png +0 -0
  184. package/apps/studio/stickers/project-status/group-103.png +0 -0
  185. package/apps/studio/stickers/project-status/group-104.png +0 -0
  186. package/apps/studio/stickers/project-status/group-105.png +0 -0
  187. package/apps/studio/stickers/project-status/group-106.png +0 -0
  188. package/apps/studio/stickers/project-status/group-107.png +0 -0
  189. package/apps/studio/stickers/project-status/group-108.png +0 -0
  190. package/apps/studio/stickers/project-status/group-109.png +0 -0
  191. package/apps/studio/stickers/project-status/group-110.png +0 -0
  192. package/apps/studio/stickers/project-status/group-111.png +0 -0
  193. package/apps/studio/stickers/project-status/group-112.png +0 -0
  194. package/apps/studio/stickers/project-status/group-113.png +0 -0
  195. package/apps/studio/stickers/project-status/group-114.png +0 -0
  196. package/apps/studio/stickers/project-status/group-115.png +0 -0
  197. package/apps/studio/stickers/project-status/group-117.png +0 -0
  198. package/apps/studio/stickers/project-status/group-118.png +0 -0
  199. package/apps/studio/stickers/project-status/group-119.png +0 -0
  200. package/apps/studio/stickers/project-status/group-120.png +0 -0
  201. package/apps/studio/stickers/project-status/group-121.png +0 -0
  202. package/apps/studio/stickers/project-status/group-122.png +0 -0
  203. package/apps/studio/stickers/project-status/group-41.png +0 -0
  204. package/apps/studio/stickers/project-status/group-42.png +0 -0
  205. package/apps/studio/stickers/project-status/group-43.png +0 -0
  206. package/apps/studio/stickers/project-status/group-44.png +0 -0
  207. package/apps/studio/stickers/project-status/group-45.png +0 -0
  208. package/apps/studio/stickers/project-status/group-46.png +0 -0
  209. package/apps/studio/stickers/project-status/group-47.png +0 -0
  210. package/apps/studio/stickers/project-status/group-48.png +0 -0
  211. package/apps/studio/stickers/project-status/group-49.png +0 -0
  212. package/apps/studio/stickers/project-status/group-50.png +0 -0
  213. package/apps/studio/stickers/project-status/group-51.png +0 -0
  214. package/apps/studio/stickers/project-status/group-52.png +0 -0
  215. package/apps/studio/stickers/project-status/group-53.png +0 -0
  216. package/apps/studio/stickers/project-status/group-54.png +0 -0
  217. package/apps/studio/stickers/project-status/group-55.png +0 -0
  218. package/apps/studio/stickers/project-status/group-56.png +0 -0
  219. package/apps/studio/stickers/project-status/group-57.png +0 -0
  220. package/apps/studio/stickers/project-status/group-58.png +0 -0
  221. package/apps/studio/stickers/project-status/group-59.png +0 -0
  222. package/apps/studio/stickers/project-status/group-60.png +0 -0
  223. package/apps/studio/stickers/project-status/group-61.png +0 -0
  224. package/apps/studio/stickers/project-status/group-62.png +0 -0
  225. package/apps/studio/stickers/project-status/group-63.png +0 -0
  226. package/apps/studio/stickers/project-status/group-64.png +0 -0
  227. package/apps/studio/stickers/project-status/group-65.png +0 -0
  228. package/apps/studio/stickers/project-status/group-66.png +0 -0
  229. package/apps/studio/stickers/project-status/group-67.png +0 -0
  230. package/apps/studio/stickers/project-status/group-68.png +0 -0
  231. package/apps/studio/stickers/project-status/group-69.png +0 -0
  232. package/apps/studio/stickers/project-status/group-70.png +0 -0
  233. package/apps/studio/stickers/project-status/group-71.png +0 -0
  234. package/apps/studio/stickers/project-status/group-72.png +0 -0
  235. package/apps/studio/stickers/project-status/group-73.png +0 -0
  236. package/apps/studio/stickers/project-status/group-74.png +0 -0
  237. package/apps/studio/stickers/project-status/group-75.png +0 -0
  238. package/apps/studio/stickers/project-status/group-76.png +0 -0
  239. package/apps/studio/stickers/project-status/group-77.png +0 -0
  240. package/apps/studio/stickers/project-status/group-78.png +0 -0
  241. package/apps/studio/stickers/project-status/group-79.png +0 -0
  242. package/apps/studio/stickers/project-status/group-80.png +0 -0
  243. package/apps/studio/stickers/project-status/group-81.png +0 -0
  244. package/apps/studio/stickers/project-status/group-82.png +0 -0
  245. package/apps/studio/stickers/project-status/group-83.png +0 -0
  246. package/apps/studio/stickers/project-status/group-84.png +0 -0
  247. package/apps/studio/stickers/project-status/group-85.png +0 -0
  248. package/apps/studio/stickers/project-status/group-86.png +0 -0
  249. package/apps/studio/stickers/project-status/group-87.png +0 -0
  250. package/apps/studio/stickers/project-status/group-88.png +0 -0
  251. package/apps/studio/stickers/project-status/group-89.png +0 -0
  252. package/apps/studio/stickers/project-status/group-90.png +0 -0
  253. package/apps/studio/stickers/project-status/group-91.png +0 -0
  254. package/apps/studio/stickers/project-status/group-92.png +0 -0
  255. package/apps/studio/stickers/project-status/group-93.png +0 -0
  256. package/apps/studio/stickers/project-status/group-94.png +0 -0
  257. package/apps/studio/stickers/project-status/group-96.png +0 -0
  258. package/apps/studio/stickers/project-status/group-97.png +0 -0
  259. package/apps/studio/stickers/project-status/group-98.png +0 -0
  260. package/apps/studio/stickers/project-status/group-99.png +0 -0
  261. package/apps/studio/stickers/project-status/manifest.json +328 -0
  262. package/apps/studio/test/_helpers.ts +15 -2
  263. package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
  264. package/apps/studio/test/acp-bridge.test.ts +34 -1
  265. package/apps/studio/test/acp-commands.test.ts +2 -1
  266. package/apps/studio/test/acp-env.test.ts +30 -0
  267. package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
  268. package/apps/studio/test/acp-session-plugins.test.ts +72 -1
  269. package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
  270. package/apps/studio/test/annotations-layer.test.ts +174 -0
  271. package/apps/studio/test/asset-api.test.ts +3 -3
  272. package/apps/studio/test/canvas-edit.test.ts +17 -6
  273. package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
  274. package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
  275. package/apps/studio/test/csrf-write-guard.test.ts +23 -1
  276. package/apps/studio/test/design-setup-readiness.test.ts +155 -0
  277. package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
  278. package/apps/studio/test/edit-source-command.test.ts +23 -0
  279. package/apps/studio/test/element-structural-edit.test.ts +154 -0
  280. package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
  281. package/apps/studio/test/footage-store.test.ts +100 -0
  282. package/apps/studio/test/generate-route.test.ts +106 -0
  283. package/apps/studio/test/git-branches.test.ts +97 -0
  284. package/apps/studio/test/github-api.test.ts +56 -1
  285. package/apps/studio/test/import-asset-browser.test.ts +64 -0
  286. package/apps/studio/test/import-asset-route.test.ts +71 -0
  287. package/apps/studio/test/import-asset.test.ts +399 -0
  288. package/apps/studio/test/import-brand-browser.test.ts +88 -0
  289. package/apps/studio/test/import-brand-route.test.ts +120 -0
  290. package/apps/studio/test/import-brand.test.ts +206 -0
  291. package/apps/studio/test/import-tokens.test.ts +722 -0
  292. package/apps/studio/test/input-router.test.ts +33 -0
  293. package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
  294. package/apps/studio/test/media-commit-chain.test.ts +210 -0
  295. package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
  296. package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
  297. package/apps/studio/test/photo-edit-api.test.ts +201 -0
  298. package/apps/studio/test/photo-filters.test.ts +149 -0
  299. package/apps/studio/test/photo-pipeline.test.ts +106 -0
  300. package/apps/studio/test/photo-taxonomy.test.ts +96 -0
  301. package/apps/studio/test/read-annotations.test.ts +164 -0
  302. package/apps/studio/test/readiness.test.ts +13 -11
  303. package/apps/studio/test/scaffold-design.test.ts +122 -0
  304. package/apps/studio/test/shell-importmap.test.ts +49 -0
  305. package/apps/studio/test/text-editability-stamp.test.ts +131 -0
  306. package/apps/studio/test/timeline-parse.test.ts +57 -0
  307. package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
  308. package/apps/studio/test/tour-overlay.test.tsx +16 -0
  309. package/apps/studio/test/undo-stack.test.ts +33 -0
  310. package/apps/studio/test/video-asset.test.ts +5 -5
  311. package/apps/studio/test/video-render-bridge.test.ts +23 -1
  312. package/apps/studio/text-caret.ts +239 -0
  313. package/apps/studio/tool-palette.tsx +49 -21
  314. package/apps/studio/ui-prefs.ts +130 -0
  315. package/apps/studio/undo-stack.ts +94 -2
  316. package/apps/studio/use-annotation-resize.tsx +4 -4
  317. package/apps/studio/use-canvas-media-drop.tsx +40 -1
  318. package/apps/studio/use-chrome-visibility.tsx +8 -2
  319. package/apps/studio/use-selection-set.tsx +21 -0
  320. package/apps/studio/video-comp.tsx +48 -7
  321. package/apps/studio/whats-new.json +149 -0
  322. package/apps/studio/ws.ts +5 -0
  323. package/cli/bin/maude.mjs +6 -6
  324. package/cli/commands/design.mjs +67 -3
  325. package/cli/lib/gitignore-block.mjs +2 -0
  326. package/cli/lib/pkg-root.mjs +107 -0
  327. package/cli/lib/pkg-root.test.mjs +79 -0
  328. package/cli/lib/reconstruct-toolset.test.mjs +194 -0
  329. package/cli/lib/update-check.mjs +8 -0
  330. package/package.json +13 -11
  331. package/plugins/design/dependencies.json +18 -0
  332. package/plugins/design/templates/_shell.html +1 -0
@@ -100,6 +100,1135 @@ const STARTER_CANVAS_META = {
100
100
  layout: { artboards: [{ id: 'welcome', x: 0, y: 0 }] },
101
101
  };
102
102
 
103
+ // ── "How to use Maude" — a 10-artboard reference canvas, seeded alongside
104
+ // Welcome.tsx (Phase 1, DDR-166's onboarding plan — replaces the earlier
105
+ // explainer-video plan item). Genuinely self-contained: no `system/maude/`
106
+ // import (doesn't exist yet on a fresh project), tokens inlined as custom
107
+ // properties. Source of truth for edits: .design/ui/How to use Maude.tsx in
108
+ // this repo — re-sync this constant by hand after editing there.
109
+ const HOW_TO_USE_MAUDE_TSX = `/**
110
+ * @canvas How to use Maude · seeded into every new project (scaffold-design.ts) · 10 artboards, one per capability
111
+ * @ds maude
112
+ * @platform desktop
113
+ * @opt_out palette
114
+ * @artboards start-here | design-system | edit-canvas | ai-assistant | point-comment-draw | generate-images | photo-editing | media-and-templates | save-share-collab | draw-as-code
115
+ * @brief A "how to use Maude" reference canvas seeded alongside Welcome.tsx on every new project. One artboard per capability cluster (start here, design system, editing, AI assistant, annotation-driven iteration, AI image generation, photo editing, media/templates, git+collab, draw-as-code). Real domain nouns, no Lorem, honest mini-mockups instead of live screenshots (the seeded copy has to work with zero server state).
116
+ * @stack React 19 · TSX · Bun.build · css_mode=inline (matches Welcome.tsx's seeded-canvas precedent — no sibling CSS to keep in sync across the scaffold template)
117
+ * @history .design/_history/how-to-use-maude/
118
+ *
119
+ * Genuinely self-contained (no DS import): this canvas is SEEDED into a
120
+ * brand-new project by scaffoldDesign() before any design system exists, so
121
+ * unlike Studio Docs.tsx it can't rely on \`system/maude/\` being present. The
122
+ * token VALUES below are copied from system/maude/colors_and_type.css's dark
123
+ * theme (kept in sync by eye — this is a fixed onboarding surface, not a
124
+ * living DS specimen) and applied as inline custom properties on \`Board\`, so
125
+ * every \`var(--token)\` below still resolves without an external stylesheet.
126
+ */
127
+
128
+ import { DCArtboard, DCSection, DesignCanvas } from "@maude/canvas-lib";
129
+
130
+ // Copied from system/maude/colors_and_type.css (dark theme) — see the note above.
131
+ const TOKENS: React.CSSProperties = {
132
+ "--bg-0": "oklch(0.165 0.012 255)",
133
+ "--bg-1": "oklch(0.198 0.012 255)",
134
+ "--bg-2": "oklch(0.232 0.013 255)",
135
+ "--bg-3": "oklch(0.270 0.013 252)",
136
+ "--border-subtle": "oklch(0.290 0.012 255)",
137
+ "--border-default": "oklch(0.360 0.013 252)",
138
+ "--fg-0": "oklch(0.955 0.005 250)",
139
+ "--fg-1": "oklch(0.790 0.008 250)",
140
+ "--fg-2": "oklch(0.660 0.010 250)",
141
+ "--fg-3": "oklch(0.500 0.010 250)",
142
+ "--accent": "oklch(0.680 0.180 268)",
143
+ "--accent-muted": "oklch(0.460 0.110 268)",
144
+ "--accent-tint": "color-mix(in oklab, oklch(0.680 0.180 268) 16%, transparent)",
145
+ "--status-success": "oklch(0.760 0.150 162)",
146
+ "--presence-agent": "oklch(0.700 0.190 322)",
147
+ "--radius-sm": "5px",
148
+ "--radius-md": "7px",
149
+ "--radius-lg": "10px",
150
+ "--radius-pill": "999px",
151
+ "--space-2": "4px",
152
+ "--space-3": "8px",
153
+ "--space-4": "12px",
154
+ "--space-5": "16px",
155
+ "--space-6": "24px",
156
+ "--space-7": "32px",
157
+ "--font-display": '"Inter Tight", "Inter", system-ui, -apple-system, sans-serif',
158
+ "--font-body": '"Inter", system-ui, -apple-system, "Segoe UI", sans-serif',
159
+ "--font-mono": '"JetBrains Mono", "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace',
160
+ "--type-xs": "11px", "--lh-xs": "16px",
161
+ "--type-sm": "12px", "--lh-sm": "18px",
162
+ "--type-base": "14px",
163
+ "--type-md": "16px", "--lh-md": "24px",
164
+ "--type-2xl": "28px",
165
+ "--tracking-tight": "-0.014em",
166
+ "--tracking-wide": "0.04em",
167
+ } as React.CSSProperties;
168
+
169
+ /* ── Icon set — thin-stroke (1.4) geometric glyphs, no emoji (DS rule) ── */
170
+ const ICONS: Record<string, React.ReactNode> = {
171
+ cursor: <path d="M3 2l9 4.4-4 1.1-1.1 4z" />,
172
+ folder: <path d="M2 4.5h4l1.3 1.5H14V13H2z" />,
173
+ palette: (<><circle cx="8" cy="8" r="5.5" /><circle cx="6" cy="6" r="0.9" fill="currentColor" stroke="none" /><circle cx="10" cy="6.2" r="0.9" fill="currentColor" stroke="none" /><circle cx="10.6" cy="9.4" r="0.9" fill="currentColor" stroke="none" /></>),
174
+ layers: (<><polygon points="8 2.2 13.8 5.5 8 8.8 2.2 5.5" /><polyline points="2.2 9 8 12.3 13.8 9" /></>),
175
+ sliders: (<><line x1="3" y1="5" x2="13" y2="5" /><circle cx="6" cy="5" r="1.7" fill="currentColor" /><line x1="3" y1="11" x2="13" y2="11" /><circle cx="10" cy="11" r="1.7" fill="currentColor" /></>),
176
+ message: <path d="M2.5 4.2A1.7 1.7 0 0 1 4.2 2.5h7.6A1.7 1.7 0 0 1 13.5 4.2v4.6a1.7 1.7 0 0 1-1.7 1.7H6.2L3 13V4.2z" />,
177
+ sparkle: <path d="M8 1.8l1.4 4.8L14 8l-4.6 1.4L8 14.2l-1.4-4.8L2 8l4.6-1.4z" fill="currentColor" stroke="none" />,
178
+ pen: (<><path d="M10.5 2.8l2.7 2.7L6 12.7 3 13.5l.8-3z" /><line x1="9.2" y1="4.1" x2="11.9" y2="6.8" /></>),
179
+ pin: (<><path d="M8 1.8c-2.2 0-4 1.7-4 4 0 2.9 4 8.4 4 8.4s4-5.5 4-8.4c0-2.3-1.8-4-4-4z" /><circle cx="8" cy="5.8" r="1.4" /></>),
180
+ highlighter: <path d="M3 13h4L13.5 6.5a1.6 1.6 0 0 0 0-2.3l-1.7-1.7a1.6 1.6 0 0 0-2.3 0L3 9v4z" />,
181
+ image: (<><rect x="2.5" y="3" width="11" height="10" rx="1.4" /><circle cx="6" cy="6.5" r="1.2" /><path d="M3 11.5l3.2-3.2a1 1 0 0 1 1.4 0L13 13" /></>),
182
+ wand: (<><path d="M2.5 13.5 9 7" /><path d="M10 4.5l.6 1.4 1.4.6-1.4.6-.6 1.4-.6-1.4-1.4-.6 1.4-.6z" fill="currentColor" stroke="none" /><path d="M13 2l.3.8.8.3-.8.3-.3.8-.3-.8-.8-.3.8-.3z" fill="currentColor" stroke="none" /></>),
183
+ scissors: (<><circle cx="4.2" cy="4.2" r="1.7" /><circle cx="4.2" cy="11.8" r="1.7" /><line x1="5.5" y1="5.3" x2="13" y2="13" /><line x1="5.5" y1="10.7" x2="13" y2="3" /></>),
184
+ drop: <path d="M8 2c2.4 3 4.2 5.7 4.2 8a4.2 4.2 0 1 1-8.4 0C3.8 7.7 5.6 5 8 2z" />,
185
+ grid: (<><rect x="2.5" y="2.5" width="4.5" height="4.5" rx="0.8" /><rect x="9" y="2.5" width="4.5" height="4.5" rx="0.8" /><rect x="2.5" y="9" width="4.5" height="4.5" rx="0.8" /><rect x="9" y="9" width="4.5" height="4.5" rx="0.8" /></>),
186
+ git: (<><circle cx="4" cy="4" r="1.6" /><circle cx="4" cy="12" r="1.6" /><circle cx="12" cy="6" r="1.6" /><path d="M4 5.6v4.8M5.6 4H9a1.4 1.4 0 0 1 1.4 1.4v.6" /></>),
187
+ users: (<><circle cx="6" cy="6" r="2.3" /><path d="M2 13.5c0-2.2 1.8-3.8 4-3.8s4 1.6 4 3.8" /><circle cx="12.2" cy="5.5" r="1.9" /><path d="M10.8 9.8c1.7.3 2.9 1.7 2.9 3.5" /></>),
188
+ play: <polygon points="5 3.5 12.5 8 5 12.5" fill="currentColor" stroke="none" />,
189
+ code: (<><polyline points="6 5 3 8 6 11" /><polyline points="10 5 13 8 10 11" /></>),
190
+ check: <polyline points="3 8.2 6.4 11.5 13 4.2" />,
191
+ "arrow-right": (<><line x1="3" y1="8" x2="12.5" y2="8" /><polyline points="9 4.5 12.5 8 9 11.5" /></>),
192
+ "chevron-right": <polyline points="6 3.5 10.5 8 6 12.5" />,
193
+ terminal: (<><polyline points="3.5 5.5 6 8 3.5 10.5" /><line x1="8" y1="11" x2="12" y2="11" /></>),
194
+ };
195
+ function Icon({ name, size = 16, style }: { name: string; size?: number; style?: React.CSSProperties }) {
196
+ return (
197
+ <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"
198
+ strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={style}>
199
+ {ICONS[name]}
200
+ </svg>
201
+ );
202
+ }
203
+ function Kbd({ children }: { children: React.ReactNode }) {
204
+ return (
205
+ <kbd style={{ display: "inline-block", padding: "1px 6px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-default)", background: "var(--bg-2)", fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--fg-0)" }}>
206
+ {children}
207
+ </kbd>
208
+ );
209
+ }
210
+
211
+ /* ── Shared frame chrome ── */
212
+ function Board({ children }: { children: React.ReactNode }) {
213
+ return (
214
+ <div style={{ ...TOKENS, position: "absolute", inset: 0, overflow: "hidden", background: "var(--bg-0)", color: "var(--fg-0)", fontFamily: "var(--font-body)" }}>
215
+ {children}
216
+ </div>
217
+ );
218
+ }
219
+ function ArtHeader({ n, total, eyebrow, title }: { n: number; total: number; eyebrow: string; title: string }) {
220
+ return (
221
+ <header style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", padding: "var(--space-6) var(--space-7) var(--space-5)", borderBottom: "1px solid var(--border-subtle)" }}>
222
+ <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
223
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--accent)" }}>{eyebrow}</span>
224
+ <h1 style={{ margin: 0, fontFamily: "var(--font-display)", fontSize: "var(--type-2xl)", fontWeight: 650, letterSpacing: "var(--tracking-tight)" }}>{title}</h1>
225
+ </div>
226
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--fg-3)" }}>{String(n).padStart(2, "0")} / {String(total).padStart(2, "0")}</span>
227
+ </header>
228
+ );
229
+ }
230
+ function Body({ children }: { children: React.ReactNode }) {
231
+ return <div style={{ padding: "var(--space-6) var(--space-7)", display: "flex", flexDirection: "column", gap: "var(--space-6)", height: "calc(100% - 84px)", boxSizing: "border-box", overflow: "hidden" }}>{children}</div>;
232
+ }
233
+ function Lede({ children }: { children: React.ReactNode }) {
234
+ return <p style={{ margin: 0, maxWidth: 640, fontSize: "var(--type-md)", lineHeight: "var(--lh-md)", color: "var(--fg-1)" }}>{children}</p>;
235
+ }
236
+ function FeatureCard({ icon, title, body }: { icon: string; title: string; body: string }) {
237
+ return (
238
+ <div style={{ flex: 1, minWidth: 220, display: "flex", flexDirection: "column", gap: "var(--space-3)", padding: "var(--space-5)", background: "var(--bg-1)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)" }}>
239
+ <span style={{ width: 30, height: 30, display: "grid", placeItems: "center", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", color: "var(--accent)" }}><Icon name={icon} size={15} /></span>
240
+ <div style={{ fontSize: "var(--type-base)", fontWeight: 600 }}>{title}</div>
241
+ <div style={{ fontSize: "var(--type-sm)", lineHeight: "var(--lh-sm)", color: "var(--fg-2)" }}>{body}</div>
242
+ </div>
243
+ );
244
+ }
245
+ function CardRow({ children }: { children: React.ReactNode }) {
246
+ return <div style={{ display: "flex", gap: "var(--space-4)", flexWrap: "wrap" }}>{children}</div>;
247
+ }
248
+ // A real, hands-on instruction — every one of these describes something you
249
+ // can genuinely do RIGHT NOW on this artboard or in this app (verified
250
+ // against the actual keyboard-shortcut table in client/app.jsx, not guessed).
251
+ function TryIt({ children }: { children: React.ReactNode }) {
252
+ return (
253
+ <div style={{ display: "flex", gap: "var(--space-3)", alignItems: "flex-start", padding: "var(--space-4)", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", border: "1px solid var(--accent-muted)" }}>
254
+ <span style={{ flexShrink: 0, fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--accent)" }}>Try it</span>
255
+ <span style={{ fontSize: "var(--type-sm)", lineHeight: "var(--lh-sm)", color: "var(--fg-0)" }}>{children}</span>
256
+ </div>
257
+ );
258
+ }
259
+ // A big hand-drawn-feel curved arrow + numbered callout, pointing at a real
260
+ // interactive target elsewhere in the artboard (the same "point at it"
261
+ // vocabulary the product itself uses for annotations).
262
+ function PointAt({ x, y, w = 150, h = 70, flip = false, children }: { x: number; y: number; w?: number; h?: number; flip?: boolean; children: React.ReactNode }) {
263
+ // Starts below the label (clears the text instead of striking through it)
264
+ // and ends short of the box's bottom edge (clears whatever sits just below).
265
+ const path = flip ? \`M\${w - 10} 20 C \${w * 0.4} 18, \${w * 0.15} \${h * 0.45}, 6 \${h - 28}\` : \`M6 20 C \${w * 0.6} 18, \${w * 0.85} \${h * 0.45}, \${w - 6} \${h - 28}\`;
266
+ return (
267
+ <div style={{ position: "absolute", left: x, top: y, width: w, height: h, pointerEvents: "none" }}>
268
+ <svg width={w} height={h} viewBox={\`0 0 \${w} \${h}\`} style={{ position: "absolute", inset: 0, overflow: "visible" }} aria-hidden>
269
+ <path d={path} stroke="var(--presence-agent)" strokeWidth="2" fill="none" strokeLinecap="round" markerEnd="url(#pointAtArrow)" />
270
+ <defs>
271
+ <marker id="pointAtArrow" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
272
+ <path d="M0 0 L8 4 L0 8 Z" fill="var(--presence-agent)" />
273
+ </marker>
274
+ </defs>
275
+ </svg>
276
+ <div style={{ position: "absolute", top: 0, [flip ? "right" : "left"]: 0, maxWidth: w - 10, fontSize: "var(--type-xs)", fontWeight: 700, color: "var(--presence-agent)", textAlign: flip ? "right" : "left" }}>{children}</div>
277
+ </div>
278
+ );
279
+ }
280
+ function StepRow({ n, title, body }: { n: number; title: string; body: string }) {
281
+ return (
282
+ <div style={{ display: "flex", gap: "var(--space-4)", alignItems: "flex-start" }}>
283
+ <span style={{ flexShrink: 0, width: 24, height: 24, display: "grid", placeItems: "center", borderRadius: "var(--radius-pill)", background: "var(--bg-2)", border: "1px solid var(--border-default)", fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--accent)" }}>{n}</span>
284
+ <div>
285
+ <div style={{ fontSize: "var(--type-base)", fontWeight: 600, marginBottom: 2 }}>{title}</div>
286
+ <div style={{ fontSize: "var(--type-sm)", lineHeight: "var(--lh-sm)", color: "var(--fg-2)" }}>{body}</div>
287
+ </div>
288
+ </div>
289
+ );
290
+ }
291
+ function MiniPanelHd({ label, right }: { label: string; right?: React.ReactNode }) {
292
+ return (
293
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "var(--space-2) var(--space-4)", borderBottom: "1px solid var(--border-subtle)", background: "var(--bg-2)", fontSize: "var(--type-xs)", color: "var(--fg-2)", fontFamily: "var(--font-mono)" }}>
294
+ <span>{label}</span>
295
+ {right}
296
+ </div>
297
+ );
298
+ }
299
+ function Panel({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
300
+ return <div style={{ background: "var(--bg-1)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)", overflow: "hidden", ...style }}>{children}</div>;
301
+ }
302
+
303
+ /* ─────────────────────────── 01 · Start here ─────────────────────────── */
304
+ function ArtStartHere() {
305
+ return (
306
+ <Board>
307
+ <ArtHeader n={1} total={10} eyebrow="Orientation" title="Start here" />
308
+ <Body>
309
+ <Lede>Everything you design lives on a canvas. Canvases are grouped in the file tree on the left. A fresh project starts with two groups: <b>Design system</b> (your tokens and components) and <b>UI kit</b> (the screens you build).</Lede>
310
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
311
+ <Panel style={{ width: 230, flexShrink: 0 }}>
312
+ <MiniPanelHd label="FILES" right={<Icon name="grid" size={13} />} />
313
+ <div style={{ padding: "var(--space-3)", display: "flex", flexDirection: "column", gap: "var(--space-2)", fontSize: "var(--type-sm)" }}>
314
+ <div style={{ color: "var(--fg-3)", fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", marginTop: 2 }}>Design system</div>
315
+ <div style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--fg-2)" }}><Icon name="folder" size={13} /> (empty until set up)</div>
316
+ <div style={{ color: "var(--fg-3)", fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", marginTop: 8 }}>UI kit</div>
317
+ <div style={{ display: "flex", alignItems: "center", gap: 6, padding: "3px 6px", borderRadius: "var(--radius-sm)", background: "var(--accent-tint)", color: "var(--accent)" }}>Welcome.tsx</div>
318
+ <div style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--fg-1)" }}>How to use Maude.tsx</div>
319
+ <div style={{ display: "flex", alignItems: "center", gap: 6, color: "var(--fg-1)" }}>How to make video.tsx</div>
320
+ </div>
321
+ </Panel>
322
+ <CardRow>
323
+ <FeatureCard icon="cursor" title="Click to open" body="Opening a file replaces the active canvas. Nothing is destructive: your work saves on its own as you go." />
324
+ <FeatureCard icon="grid" title="One infinite canvas" body="Every artboard on a canvas lives on the same pannable, zoomable surface. Pan and zoom like a whiteboard." />
325
+ <FeatureCard icon="terminal" title="No terminal needed" body="Everything here (browsing, editing, saving, sharing) works from the app. The Assistant is the only thing that pairs with a Claude Code you install once." />
326
+ </CardRow>
327
+ </div>
328
+ <TryIt>Click "How to make video.tsx" in the file list on the left, right now. That's the real file tree, not a picture of it.</TryIt>
329
+ </Body>
330
+ </Board>
331
+ );
332
+ }
333
+
334
+ /* ────────────────────── 02 · Build a design system ───────────────────── */
335
+ function ArtDesignSystem() {
336
+ const swatches = ["oklch(0.68 0.18 268)", "oklch(0.76 0.15 162)", "oklch(0.80 0.13 78)", "oklch(0.66 0.19 25)"];
337
+ return (
338
+ <Board>
339
+ <ArtHeader n={2} total={10} eyebrow="Foundations" title="Build a design system" />
340
+ <Body>
341
+ <Lede>Ask the Assistant to run <b>/design:setup-ds</b> and it interviews you: a handful of sharp questions about your product, mood, and brand. Then it generates a real, tokenized system: color, type, space, motion, and components. No blank Figma file, no picking a font at random.</Lede>
342
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
343
+ <Panel style={{ flex: 1, padding: "var(--space-5)" }}>
344
+ <div style={{ fontSize: "var(--type-xs)", fontFamily: "var(--font-mono)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: "var(--space-3)" }}>Colors</div>
345
+ <div style={{ display: "flex", gap: "var(--space-3)", marginBottom: "var(--space-5)" }}>
346
+ {swatches.map((c) => <span key={c} style={{ width: 40, height: 40, borderRadius: "var(--radius-md)", background: c, border: "1px solid var(--border-subtle)" }} />)}
347
+ </div>
348
+ <div style={{ fontSize: "var(--type-xs)", fontFamily: "var(--font-mono)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--fg-3)", marginBottom: "var(--space-3)" }}>Type</div>
349
+ <div style={{ fontFamily: "var(--font-display)", fontSize: 26, fontWeight: 700, marginBottom: 4 }}>Display heading</div>
350
+ <div style={{ fontSize: "var(--type-sm)", color: "var(--fg-2)" }}>Body copy at a readable measure.</div>
351
+ </Panel>
352
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: "var(--space-4)" }}>
353
+ <StepRow n={1} title="A dozen sharp questions" body="Product, audience, mood references, a hard-NO list: real research grounds every option, not vibes." />
354
+ <StepRow n={2} title="Pick a direction" body="A small set of moodboard variants, seeded from your answers. Pick one and refine it." />
355
+ <StepRow n={3} title="A complete, tokenized system" body="Colors, type ladder, spacing, motion, and a component library: every value a token, nothing hardcoded." />
356
+ </div>
357
+ </div>
358
+ <TryIt>Open the Assistant (<Kbd>⌘⇧A</Kbd>) and type <code>/design:setup-ds</code>. It starts the real interview.</TryIt>
359
+ </Body>
360
+ </Board>
361
+ );
362
+ }
363
+
364
+ /* ────────────────────────── 03 · Edit a canvas ────────────────────────── */
365
+ function ArtEditCanvas() {
366
+ return (
367
+ <Board>
368
+ <ArtHeader n={3} total={10} eyebrow="Editing" title="Edit a canvas" />
369
+ <Body>
370
+ <Lede>This isn't a mockup. The button below is a real element on this real artboard. Try the two moves that cover most editing:</Lede>
371
+ <div style={{ position: "relative", display: "flex", alignItems: "center", gap: "var(--space-7)", padding: "var(--space-4) 0" }}>
372
+ <div
373
+ data-dc-element="try-it-button"
374
+ style={{ padding: "16px 30px", borderRadius: "var(--radius-md)", background: "var(--accent)", color: "var(--bg-0)", fontFamily: "var(--font-display)", fontSize: "var(--type-md)", fontWeight: 700, cursor: "pointer" }}
375
+ >
376
+ Get started
377
+ </div>
378
+ <div style={{ position: "relative", width: 260, height: 90 }}>
379
+ <PointAt x={0} y={0} w={130} h={60} flip>① ⌘-click this button</PointAt>
380
+ <div style={{ position: "absolute", left: 0, bottom: 0, maxWidth: 250, fontSize: "var(--type-sm)", color: "var(--fg-2)" }}>The Inspector opens (or press <Kbd>⌘⇧I</Kbd>). Try changing its Fill or Radius, or double-click "Get started" to retype it.</div>
381
+ </div>
382
+ </div>
383
+ <TryIt><Kbd>⌘</Kbd>-click the button above right now. Then in the Inspector's CSS tab, change its background. This artboard updates live, the same way any canvas does.</TryIt>
384
+ <CardRow>
385
+ <FeatureCard icon="pen" title="Inline text edit" body="Double-click any text on the canvas and type. No dialog, no round-trip." />
386
+ <FeatureCard icon="sliders" title="Bind to a token" body="Drag a color or spacing value onto a design token to bind it. Change the token, every use updates." />
387
+ <FeatureCard icon="layers" title="Drag to reorder" body="Reorder elements right on the canvas, or open Layers (⌘⇧I) to drag them there instead." />
388
+ </CardRow>
389
+ </Body>
390
+ </Board>
391
+ );
392
+ }
393
+
394
+ /* ───────────────────────── 04 · The AI Assistant ──────────────────────── */
395
+ function ArtAiAssistant() {
396
+ return (
397
+ <Board>
398
+ <ArtHeader n={4} total={10} eyebrow="Assistant" title="The AI Assistant" />
399
+ <Body>
400
+ <Lede>The chat panel runs on your own Claude subscription: never a separate key, never metered billing. It sees whatever's open and selected, so short requests work: "make this bigger," not a paragraph of context.</Lede>
401
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
402
+ <Panel style={{ width: 320, flexShrink: 0 }}>
403
+ <MiniPanelHd label="ASSISTANT" right={<span style={{ display: "flex", alignItems: "center", gap: 4, color: "var(--status-success)" }}><span style={{ width: 6, height: 6, borderRadius: "50%", background: "var(--status-success)" }} />Ready</span>} />
404
+ <div style={{ padding: "var(--space-4)", display: "flex", flexDirection: "column", gap: "var(--space-3)" }}>
405
+ <div style={{ alignSelf: "flex-end", maxWidth: "80%", padding: "var(--space-3)", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", color: "var(--fg-0)", fontSize: "var(--type-sm)" }}>Make the hero title bigger and tighten the tracking</div>
406
+ <div style={{ maxWidth: "85%", padding: "var(--space-3)", borderRadius: "var(--radius-md)", background: "var(--bg-2)", fontSize: "var(--type-sm)", color: "var(--fg-1)" }}>Bumped it to <code>var(--type-3xl)</code> and tightened tracking. Want it bolder too?</div>
407
+ </div>
408
+ </Panel>
409
+ <CardRow>
410
+ <FeatureCard icon="terminal" title="Slash commands" body="Type / for autocomplete: /design:new, /design:edit, /design:critic and more, right in the composer." />
411
+ <FeatureCard icon="message" title="It remembers" body="Conversations survive a restart. Close the laptop, come back tomorrow, keep going mid-thought." />
412
+ <FeatureCard icon="sparkle" title="Runs on your subscription" body="Install Claude Code once, sign in, and the Assistant just works. No separate API key, ever." />
413
+ </CardRow>
414
+ </div>
415
+ <TryIt>Press <Kbd>⌘⇧A</Kbd> right now to open the Assistant. It's the sparkle icon, top-right of the window.</TryIt>
416
+ </Body>
417
+ </Board>
418
+ );
419
+ }
420
+
421
+ /* ──────────────────── 05 · Point, comment, draw → it acts ────────────── */
422
+ function ArtPointCommentDraw() {
423
+ return (
424
+ <Board>
425
+ <ArtHeader n={5} total={10} eyebrow="✦ The signature move" title="Point, comment, draw → it acts" />
426
+ <Body>
427
+ <Lede>You don't have to describe a change in words. The box below is real. Try it:</Lede>
428
+ <div style={{ position: "relative", display: "flex", alignItems: "center", gap: "var(--space-7)" }}>
429
+ <div data-dc-element="try-it-annotate-target" style={{ width: 160, height: 90, borderRadius: "var(--radius-md)", background: "var(--bg-2)", border: "1px solid var(--accent)" }} />
430
+ <div style={{ position: "relative", width: 260, height: 90 }}>
431
+ <PointAt x={0} y={0} w={130} h={60} flip>① ⌘-click this box</PointAt>
432
+ <div style={{ position: "absolute", left: 0, bottom: 0, maxWidth: 250, fontSize: "var(--type-sm)", color: "var(--fg-2)" }}>② right-click it for the annotation menu, or press <Kbd>⌘⇧M</Kbd> to open Comments and drop a pin on it.</div>
433
+ </div>
434
+ </div>
435
+ <TryIt>Right-click the box above right now. The context menu has highlighter, sticky notes, and freehand drawing. Anything you mark stays attached to that exact spot as context for the Assistant.</TryIt>
436
+ <CardRow>
437
+ <FeatureCard icon="cursor" title="⌘-click to select" body="Hover for a preview, click to select. ⌘⇧+click adds more than one to the selection." />
438
+ <FeatureCard icon="pin" title="Comment on a pixel" body="Pin a note to an exact spot, not a vague description. The Assistant reads it as scoped context." />
439
+ <FeatureCard icon="highlighter" title="Draw & annotate" body="Highlighter, sticky notes, shapes and freehand marks: a real whiteboard layer over the canvas." />
440
+ </CardRow>
441
+ </Body>
442
+ </Board>
443
+ );
444
+ }
445
+
446
+ /* ──────────────── 06 · Generate images with your own AI key ───────────── */
447
+ function ArtGenerateImages() {
448
+ return (
449
+ <Board>
450
+ <ArtHeader n={6} total={10} eyebrow="AI media" title="Generate images with your own AI key" />
451
+ <Body>
452
+ <Lede>Bring your own Google (Nano Banana) key and generate an image right onto the canvas, via <b>⌘K → "Generate with AI"</b>, or just ask in the chat ("generate a hero image of a mountain lake"). The result lands as a normal asset you can place, edit, and re-generate.</Lede>
453
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
454
+ <Panel style={{ width: 260, height: 150, flexShrink: 0, display: "flex", flexDirection: "column" }}>
455
+ <MiniPanelHd label="GENERATE" right={<Icon name="sparkle" size={13} style={{ color: "var(--accent)" }} />} />
456
+ <div style={{ flex: 1, margin: "var(--space-3)", borderRadius: "var(--radius-md)", background: "linear-gradient(135deg, var(--bg-3), var(--bg-2))", display: "grid", placeItems: "center", color: "var(--fg-3)" }}>
457
+ <Icon name="image" size={26} />
458
+ </div>
459
+ </Panel>
460
+ <CardRow>
461
+ <FeatureCard icon="wand" title="Prompt, or seed from a still" body="A plain-text prompt, or start from an image you already generated so a new shot matches its look." />
462
+ <FeatureCard icon="pen" title="Ask to edit it again" body="&ldquo;Make the sky purple&rdquo; produces a fresh AI-edited version. Iterate the same way you'd iterate on any canvas element." />
463
+ <FeatureCard icon="sliders" title="Stays on your machine" body="Add your key under File → Settings → AI generation. It's sent straight to the provider, never touches git history." />
464
+ </CardRow>
465
+ </div>
466
+ <TryIt>Press <Kbd>⌘K</Kbd> right now and pick "Generate with AI…" from the list.</TryIt>
467
+ </Body>
468
+ </Board>
469
+ );
470
+ }
471
+
472
+ /* ─────────────────────────── 07 · Photo editing ───────────────────────── */
473
+ function ArtPhotoEditing() {
474
+ return (
475
+ <Board>
476
+ <ArtHeader n={7} total={10} eyebrow="AI media" title="Photo editing, right on the canvas" />
477
+ <Body>
478
+ <Lede>Select any photo and open the <b>Photo</b> tab in the Inspector: adjustments, duotone, grain, masking, and one-click background removal. Every edit is non-destructive, the original stays untouched.</Lede>
479
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
480
+ <Panel style={{ width: 280, flexShrink: 0 }}>
481
+ <MiniPanelHd label="PHOTO" />
482
+ <div style={{ padding: "var(--space-4)", display: "flex", flexDirection: "column", gap: "var(--space-3)" }}>
483
+ {["Brightness", "Contrast", "Saturation"].map((k) => (
484
+ <div key={k} style={{ display: "flex", alignItems: "center", gap: "var(--space-3)" }}>
485
+ <span style={{ width: 80, fontSize: "var(--type-xs)", color: "var(--fg-2)" }}>{k}</span>
486
+ <div style={{ flex: 1, height: 3, borderRadius: "var(--radius-pill)", background: "var(--bg-3)", position: "relative" }}>
487
+ <div style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: "60%", borderRadius: "var(--radius-pill)", background: "var(--accent)" }} />
488
+ </div>
489
+ </div>
490
+ ))}
491
+ <button type="button" style={{ marginTop: 4, alignSelf: "flex-start", display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 10px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-default)", background: "var(--bg-2)", color: "var(--fg-0)", fontSize: "var(--type-sm)", cursor: "pointer" }}><Icon name="scissors" size={13} /> Remove background</button>
492
+ </div>
493
+ </Panel>
494
+ <CardRow>
495
+ <FeatureCard icon="scissors" title="One-click background removal" body="Runs locally: no upload, no per-image cost." />
496
+ <FeatureCard icon="drop" title="Duotone, grain, masking" body="Real photo-editing tools, not a filter preset list. Every value is a canvas-native property." />
497
+ <FeatureCard icon="check" title="Non-destructive" body="Adjustments live as metadata next to the asset. Revert anytime, the source file never changes." />
498
+ </CardRow>
499
+ </div>
500
+ <TryIt>Drop any photo from Finder onto this canvas, ⌘-click it, then open the Photo tab (<Kbd>⌘⇧I</Kbd>) to try Remove background.</TryIt>
501
+ </Body>
502
+ </Board>
503
+ );
504
+ }
505
+
506
+ /* ────────────────────── 08 · Drop in media & templates ────────────────── */
507
+ function ArtMediaTemplates() {
508
+ return (
509
+ <Board>
510
+ <ArtHeader n={8} total={10} eyebrow="Assets" title="Drop in media & templates" />
511
+ <Body>
512
+ <Lede>Drag images, video, or audio straight from Finder onto the canvas: one file or a whole batch. Paste a link to unfurl it. Create or delete a canvas from the file tree without ever touching the filesystem yourself.</Lede>
513
+ <CardRow>
514
+ <FeatureCard icon="image" title="Drag & drop, one or many" body="Drop a single photo or select a batch. Every file lands, even in a large drop. Video and audio go on the canvas as annotations; photos can drop straight into an artboard." />
515
+ <FeatureCard icon="arrow-right" title="Paste a link" body="Paste a URL onto the canvas and it unfurls. No manual screenshotting." />
516
+ <FeatureCard icon="grid" title="Create & delete canvases" body="Right-click the file tree: new canvas, new folder, rename, delete, all without a terminal." />
517
+ </CardRow>
518
+ <TryIt>Drag any image file from Finder and drop it right on this artboard, now. It lands as a real asset.</TryIt>
519
+ </Body>
520
+ </Board>
521
+ );
522
+ }
523
+
524
+ /* ────────────────── 09 · Save, share, collaborate ──────────────────────── */
525
+ function ArtSaveShareCollab() {
526
+ return (
527
+ <Board>
528
+ <ArtHeader n={9} total={10} eyebrow="Version & team" title="Save, share, collaborate" />
529
+ <Body>
530
+ <Lede>Version control lives in plain words: <b>Save version</b> keeps a checkpoint just for you, <b>Publish</b> shares it with your team, <b>Get latest</b> pulls in everyone else's work. No terminal, no git vocabulary required.</Lede>
531
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
532
+ <CardRow>
533
+ <FeatureCard icon="git" title="No-terminal git layer" body="Save version · Publish · Get latest: the same underlying git history, described in plain words." />
534
+ <FeatureCard icon="users" title="Live multiplayer" body="See your team's cursors moving in real time, on the same branch. Changes sync as they happen." />
535
+ <FeatureCard icon="play" title="Present Mode" body="Strip the chrome down to just your artboards for a clean walkthrough or a client call." />
536
+ </CardRow>
537
+ </div>
538
+ <TryIt>Press <Kbd>⌘⇧G</Kbd> right now to open Changes. It tracks everything since your last save, live.</TryIt>
539
+ </Body>
540
+ </Board>
541
+ );
542
+ }
543
+
544
+ /* ───────────────────────────── 10 · Draw as code ───────────────────────── */
545
+ function ArtDrawAsCode() {
546
+ return (
547
+ <Board>
548
+ <ArtHeader n={10} total={10} eyebrow="Vector & motion" title="Draw as code" />
549
+ <Body>
550
+ <Lede>Ask for a logo, icon, or diagram and it's built by a deterministic geometry engine: real computed vectors, never a guessed path. Animate the result once and ship one file that plays identically on web and native.</Lede>
551
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
552
+ <Panel style={{ width: 180, height: 150, flexShrink: 0, display: "grid", placeItems: "center" }}>
553
+ <svg width={72} height={72} viewBox="0 0 32 32" style={{ color: "var(--accent)" }} aria-hidden>
554
+ <rect width="16" height="16" x="8" y="8" fill="none" stroke="currentColor" strokeWidth="2.4" rx="4" />
555
+ <g fill="currentColor"><rect width="5.6" height="5.6" x="23" y="3" rx="1.6" /><rect width="5.6" height="5.6" x="3" y="23" rx="1.6" /><rect width="5.6" height="5.6" x="23" y="23" rx="1.6" /></g>
556
+ </svg>
557
+ </Panel>
558
+ <CardRow>
559
+ <FeatureCard icon="code" title="Computed, not guessed" body="Splines, connectors, and layout math produce the SVG. Deterministic and consistent, every time." />
560
+ <FeatureCard icon="play" title="Animate once" body="Turn a mark into motion and export a single .lottie. Frame for frame, web and native in sync." />
561
+ <FeatureCard icon="check" title="WCAG + grid-checked" body="Every mark clears a legibility, contrast, and grid-alignment gate before it's considered done." />
562
+ </CardRow>
563
+ </div>
564
+ <TryIt>Open the Assistant (<Kbd>⌘⇧A</Kbd>) and ask it to draw a simple compass icon. Watch it build from geometry, not guess a path.</TryIt>
565
+ </Body>
566
+ </Board>
567
+ );
568
+ }
569
+
570
+ export default function Canvas() {
571
+ return (
572
+ <DesignCanvas>
573
+ <DCSection id="how-to-use-maude" title="How to use Maude" subtitle="REFERENCE · 10 artboards, one per capability">
574
+ <DCArtboard id="start-here" label="01 · Start here" width={1040} height={640}><ArtStartHere /></DCArtboard>
575
+ <DCArtboard id="design-system" label="02 · Design system" width={1040} height={640}><ArtDesignSystem /></DCArtboard>
576
+ <DCArtboard id="edit-canvas" label="03 · Edit a canvas" width={1040} height={640}><ArtEditCanvas /></DCArtboard>
577
+ <DCArtboard id="ai-assistant" label="04 · AI Assistant" width={1040} height={640}><ArtAiAssistant /></DCArtboard>
578
+ <DCArtboard id="point-comment-draw" label="05 · Point, comment, draw" width={1040} height={640}><ArtPointCommentDraw /></DCArtboard>
579
+ <DCArtboard id="generate-images" label="06 · Generate images" width={1040} height={640}><ArtGenerateImages /></DCArtboard>
580
+ <DCArtboard id="photo-editing" label="07 · Photo editing" width={1040} height={640}><ArtPhotoEditing /></DCArtboard>
581
+ <DCArtboard id="media-and-templates" label="08 · Media & templates" width={1040} height={640}><ArtMediaTemplates /></DCArtboard>
582
+ <DCArtboard id="save-share-collab" label="09 · Save, share, collaborate" width={1040} height={640}><ArtSaveShareCollab /></DCArtboard>
583
+ <DCArtboard id="draw-as-code" label="10 · Draw as code" width={1040} height={640}><ArtDrawAsCode /></DCArtboard>
584
+ </DCSection>
585
+ </DesignCanvas>
586
+ );
587
+ }
588
+ `;
589
+
590
+ const HOW_TO_USE_MAUDE_META = {
591
+ title: 'How to use Maude',
592
+ subtitle:
593
+ 'Reference canvas seeded into every new project \u2014 10 artboards, one per capability',
594
+ brief:
595
+ 'Onboarding reference canvas (replaces the Phase 1 explainer-video plan item). Seeded alongside Welcome.tsx by scaffold-design.ts on every new project. One artboard per capability cluster: start here, design system, editing, AI assistant, point/comment/draw iteration, AI image generation (Nano Banana), photo editing, media & templates, save/share/collaborate, draw-as-code. Honest inline mini-mockups (no live screenshots \u2014 has to work with zero server state on a freshly scaffolded project).',
596
+ platform: 'desktop',
597
+ designSystem: 'maude',
598
+ opt_out_scope: 'none',
599
+ sections: [
600
+ {
601
+ id: 'how-to-use-maude',
602
+ title: 'How to use Maude',
603
+ subtitle: 'REFERENCE \u00b7 10 artboards, one per capability',
604
+ },
605
+ ],
606
+ artboards: [
607
+ {
608
+ id: 'start-here',
609
+ label: '01 \u00b7 Start here',
610
+ },
611
+ {
612
+ id: 'design-system',
613
+ label: '02 \u00b7 Design system',
614
+ },
615
+ {
616
+ id: 'edit-canvas',
617
+ label: '03 \u00b7 Edit a canvas',
618
+ },
619
+ {
620
+ id: 'ai-assistant',
621
+ label: '04 \u00b7 AI Assistant',
622
+ },
623
+ {
624
+ id: 'point-comment-draw',
625
+ label: '05 \u00b7 Point, comment, draw',
626
+ },
627
+ {
628
+ id: 'generate-images',
629
+ label: '06 \u00b7 Generate images',
630
+ },
631
+ {
632
+ id: 'photo-editing',
633
+ label: '07 \u00b7 Photo editing',
634
+ },
635
+ {
636
+ id: 'media-and-templates',
637
+ label: '08 \u00b7 Media & templates',
638
+ },
639
+ {
640
+ id: 'save-share-collab',
641
+ label: '09 \u00b7 Save, share, collaborate',
642
+ },
643
+ {
644
+ id: 'draw-as-code',
645
+ label: '10 \u00b7 Draw as code',
646
+ },
647
+ ],
648
+ layout: {
649
+ artboards: [
650
+ {
651
+ id: 'start-here',
652
+ x: 0,
653
+ y: 0,
654
+ },
655
+ {
656
+ id: 'design-system',
657
+ x: 1120,
658
+ y: 0,
659
+ },
660
+ {
661
+ id: 'edit-canvas',
662
+ x: 2240,
663
+ y: 0,
664
+ },
665
+ {
666
+ id: 'ai-assistant',
667
+ x: 3360,
668
+ y: 0,
669
+ },
670
+ {
671
+ id: 'point-comment-draw',
672
+ x: 4480,
673
+ y: 0,
674
+ },
675
+ {
676
+ id: 'generate-images',
677
+ x: 5600,
678
+ y: 0,
679
+ },
680
+ {
681
+ id: 'photo-editing',
682
+ x: 6720,
683
+ y: 0,
684
+ },
685
+ {
686
+ id: 'media-and-templates',
687
+ x: 7840,
688
+ y: 0,
689
+ },
690
+ {
691
+ id: 'save-share-collab',
692
+ x: 8960,
693
+ y: 0,
694
+ },
695
+ {
696
+ id: 'draw-as-code',
697
+ x: 10080,
698
+ y: 0,
699
+ },
700
+ ],
701
+ },
702
+ css_mode: 'inline',
703
+ iteration_count: 1,
704
+ };
705
+
706
+ // ── "How to make video" — the video-comp companion (7 artboards). Same
707
+ // self-contained shape. Source of truth: .design/ui/How to make video.tsx.
708
+ const HOW_TO_MAKE_VIDEO_TSX = `/**
709
+ * @canvas How to make video · seeded into every new project (scaffold-design.ts) · 7 artboards, one per video capability
710
+ * @ds maude
711
+ * @platform desktop
712
+ * @opt_out palette
713
+ * @artboards start-video-comp | timeline | ai-assistant-video | footage-to-cut | generate-video-audio | captions | export
714
+ * @brief A "how to make video" reference canvas seeded alongside How to use Maude.tsx on every new project. One artboard per capability cluster: what a video-comp is, the Timeline, the Assistant for video, turning raw footage into a cut, AI video/audio generation, automatic captions (whisper.cpp / cloud), export. Honest inline mini-mockups, no live screenshots.
715
+ * @stack React 19 · TSX · Bun.build · css_mode=inline (matches Welcome.tsx's seeded-canvas precedent)
716
+ * @history .design/_history/how-to-make-video/
717
+ *
718
+ * Genuinely self-contained (no DS import) — same reasoning as How to use
719
+ * Maude.tsx: this is seeded into a brand-new project before any design
720
+ * system exists, so it can't rely on \`system/maude/\` being present. Token
721
+ * VALUES are copied from system/maude/colors_and_type.css's dark theme and
722
+ * applied as inline custom properties on \`Board\`.
723
+ */
724
+
725
+ import { DCArtboard, DCSection, DesignCanvas, VideoComp } from "@maude/canvas-lib";
726
+ import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from "remotion";
727
+
728
+ // Copied from system/maude/colors_and_type.css (dark theme) — kept in sync
729
+ // by eye with How to use Maude.tsx's identical block (a fixed onboarding
730
+ // surface, not a living DS specimen).
731
+ const TOKENS: React.CSSProperties = {
732
+ "--bg-0": "oklch(0.165 0.012 255)",
733
+ "--bg-1": "oklch(0.198 0.012 255)",
734
+ "--bg-2": "oklch(0.232 0.013 255)",
735
+ "--bg-3": "oklch(0.270 0.013 252)",
736
+ "--border-subtle": "oklch(0.290 0.012 255)",
737
+ "--border-default": "oklch(0.360 0.013 252)",
738
+ "--fg-0": "oklch(0.955 0.005 250)",
739
+ "--fg-1": "oklch(0.790 0.008 250)",
740
+ "--fg-2": "oklch(0.660 0.010 250)",
741
+ "--fg-3": "oklch(0.500 0.010 250)",
742
+ "--accent": "oklch(0.680 0.180 268)",
743
+ "--accent-muted": "oklch(0.460 0.110 268)",
744
+ "--accent-tint": "color-mix(in oklab, oklch(0.680 0.180 268) 16%, transparent)",
745
+ "--radius-sm": "5px",
746
+ "--radius-md": "7px",
747
+ "--radius-lg": "10px",
748
+ "--space-2": "4px",
749
+ "--space-3": "8px",
750
+ "--space-4": "12px",
751
+ "--space-5": "16px",
752
+ "--space-6": "24px",
753
+ "--space-7": "32px",
754
+ "--font-display": '"Inter Tight", "Inter", system-ui, -apple-system, sans-serif',
755
+ "--font-body": '"Inter", system-ui, -apple-system, "Segoe UI", sans-serif',
756
+ "--font-mono": '"JetBrains Mono", "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace',
757
+ "--type-xs": "11px",
758
+ "--type-sm": "12px", "--lh-sm": "18px",
759
+ "--type-base": "14px",
760
+ "--type-md": "16px", "--lh-md": "24px",
761
+ "--type-2xl": "28px",
762
+ "--tracking-tight": "-0.014em",
763
+ "--tracking-wide": "0.04em",
764
+ } as React.CSSProperties;
765
+
766
+ /* ── Icon set ── */
767
+ const ICONS: Record<string, React.ReactNode> = {
768
+ play: <polygon points="5 3.5 12.5 8 5 12.5" fill="currentColor" stroke="none" />,
769
+ pause: (<><rect x="4.5" y="3.5" width="2.2" height="9" rx="0.6" fill="currentColor" stroke="none" /><rect x="9.3" y="3.5" width="2.2" height="9" rx="0.6" fill="currentColor" stroke="none" /></>),
770
+ film: (<><rect x="2.5" y="3" width="11" height="10" rx="1.4" /><line x1="5.5" y1="3" x2="5.5" y2="13" /><line x1="10.5" y1="3" x2="10.5" y2="13" /></>),
771
+ scissors: (<><circle cx="4.2" cy="4.2" r="1.7" /><circle cx="4.2" cy="11.8" r="1.7" /><line x1="5.5" y1="5.3" x2="13" y2="13" /><line x1="5.5" y1="10.7" x2="13" y2="3" /></>),
772
+ message: <path d="M2.5 4.2A1.7 1.7 0 0 1 4.2 2.5h7.6A1.7 1.7 0 0 1 13.5 4.2v4.6a1.7 1.7 0 0 1-1.7 1.7H6.2L3 13V4.2z" />,
773
+ folder: <path d="M2 4.5h4l1.3 1.5H14V13H2z" />,
774
+ eye: (<><path d="M1.5 8s2.3-4.5 6.5-4.5S14.5 8 14.5 8s-2.3 4.5-6.5 4.5S1.5 8 1.5 8z" /><circle cx="8" cy="8" r="2" /></>),
775
+ sparkle: <path d="M8 1.8l1.4 4.8L14 8l-4.6 1.4L8 14.2l-1.4-4.8L2 8l4.6-1.4z" fill="currentColor" stroke="none" />,
776
+ volume: (<><polygon points="3 6 5.5 6 8 3.5 8 12.5 5.5 10 3 10" fill="currentColor" stroke="none" /><path d="M10.5 6a3 3 0 0 1 0 4" /></>),
777
+ waveform: (<><line x1="2" y1="8" x2="2" y2="8" /><line x1="4" y1="5" x2="4" y2="11" /><line x1="6" y1="3" x2="6" y2="13" /><line x1="8" y1="6" x2="8" y2="10" /><line x1="10" y1="2" x2="10" y2="14" /><line x1="12" y1="5" x2="12" y2="11" /><line x1="14" y1="7" x2="14" y2="9" /></>),
778
+ captions: (<><rect x="2" y="4" width="12" height="8" rx="1.4" /><line x1="4.5" y1="7" x2="8" y2="7" /><line x1="4.5" y1="9.3" x2="10.5" y2="9.3" /></>),
779
+ download: (<><line x1="8" y1="2.5" x2="8" y2="10" /><polyline points="4.5 7 8 10.5 11.5 7" /><polyline points="3 12.8 3 13.6 13 13.6 13 12.8" /></>),
780
+ check: <polyline points="3 8.2 6.4 11.5 13 4.2" />,
781
+ wand: (<><path d="M2.5 13.5 9 7" /><path d="M10 4.5l.6 1.4 1.4.6-1.4.6-.6 1.4-.6-1.4-1.4-.6 1.4-.6z" fill="currentColor" stroke="none" /></>),
782
+ drag: (<><circle cx="6" cy="4.5" r="1" fill="currentColor" stroke="none" /><circle cx="10" cy="4.5" r="1" fill="currentColor" stroke="none" /><circle cx="6" cy="8" r="1" fill="currentColor" stroke="none" /><circle cx="10" cy="8" r="1" fill="currentColor" stroke="none" /><circle cx="6" cy="11.5" r="1" fill="currentColor" stroke="none" /><circle cx="10" cy="11.5" r="1" fill="currentColor" stroke="none" /></>),
783
+ };
784
+ function Icon({ name, size = 16, style }: { name: string; size?: number; style?: React.CSSProperties }) {
785
+ return (
786
+ <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"
787
+ strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={style}>
788
+ {ICONS[name]}
789
+ </svg>
790
+ );
791
+ }
792
+ function Kbd({ children }: { children: React.ReactNode }) {
793
+ return (
794
+ <kbd style={{ display: "inline-block", padding: "1px 6px", borderRadius: "var(--radius-sm)", border: "1px solid var(--border-default)", background: "var(--bg-2)", fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--fg-0)" }}>
795
+ {children}
796
+ </kbd>
797
+ );
798
+ }
799
+ // Real, hands-on instructions (same shape as How to use Maude.tsx's TryIt —
800
+ // verified against the actual keyboard-shortcut table in client/app.jsx).
801
+ function TryIt({ children }: { children: React.ReactNode }) {
802
+ return (
803
+ <div style={{ display: "flex", gap: "var(--space-3)", alignItems: "flex-start", padding: "var(--space-4)", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", border: "1px solid var(--accent-muted)" }}>
804
+ <span style={{ flexShrink: 0, fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--accent)" }}>Try it</span>
805
+ <span style={{ fontSize: "var(--type-sm)", lineHeight: "var(--lh-sm)", color: "var(--fg-0)" }}>{children}</span>
806
+ </div>
807
+ );
808
+ }
809
+ function PointAt({ x, y, w = 150, h = 70, flip = false, children }: { x: number; y: number; w?: number; h?: number; flip?: boolean; children: React.ReactNode }) {
810
+ const path = flip ? \`M\${w - 6} 6 C \${w * 0.35} 4, \${w * 0.1} \${h * 0.5}, 6 \${h - 6}\` : \`M6 6 C \${w * 0.65} 4, \${w * 0.9} \${h * 0.5}, \${w - 6} \${h - 6}\`;
811
+ return (
812
+ <div style={{ position: "absolute", left: x, top: y, width: w, height: h, pointerEvents: "none" }}>
813
+ <svg width={w} height={h} viewBox={\`0 0 \${w} \${h}\`} style={{ position: "absolute", inset: 0, overflow: "visible" }} aria-hidden>
814
+ <path d={path} stroke="var(--presence-agent, #c34fd8)" strokeWidth="2" fill="none" strokeLinecap="round" markerEnd="url(#pointAtArrowV)" />
815
+ <defs>
816
+ <marker id="pointAtArrowV" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
817
+ <path d="M0 0 L8 4 L0 8 Z" fill="var(--presence-agent, #c34fd8)" />
818
+ </marker>
819
+ </defs>
820
+ </svg>
821
+ <div style={{ position: "absolute", top: 0, [flip ? "right" : "left"]: 0, maxWidth: w - 10, fontSize: "var(--type-xs)", fontWeight: 700, color: "var(--presence-agent, #c34fd8)", textAlign: flip ? "right" : "left" }}>{children}</div>
822
+ </div>
823
+ );
824
+ }
825
+
826
+ /* ── Shared chrome (same shapes as How to use Maude.tsx) ── */
827
+ function Board({ children }: { children: React.ReactNode }) {
828
+ return (
829
+ <div style={{ ...TOKENS, position: "absolute", inset: 0, overflow: "hidden", background: "var(--bg-0)", color: "var(--fg-0)", fontFamily: "var(--font-body)" }}>
830
+ {children}
831
+ </div>
832
+ );
833
+ }
834
+ function ArtHeader({ n, total, eyebrow, title }: { n: number; total: number; eyebrow: string; title: string }) {
835
+ return (
836
+ <header style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", padding: "var(--space-6) var(--space-7) var(--space-5)", borderBottom: "1px solid var(--border-subtle)" }}>
837
+ <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
838
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--accent)" }}>{eyebrow}</span>
839
+ <h1 style={{ margin: 0, fontFamily: "var(--font-display)", fontSize: "var(--type-2xl)", fontWeight: 650, letterSpacing: "var(--tracking-tight)" }}>{title}</h1>
840
+ </div>
841
+ <span style={{ fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--fg-3)" }}>{String(n).padStart(2, "0")} / {String(total).padStart(2, "0")}</span>
842
+ </header>
843
+ );
844
+ }
845
+ function Body({ children }: { children: React.ReactNode }) {
846
+ return <div style={{ padding: "var(--space-6) var(--space-7)", display: "flex", flexDirection: "column", gap: "var(--space-6)", height: "calc(100% - 84px)", boxSizing: "border-box", overflow: "hidden" }}>{children}</div>;
847
+ }
848
+ function Lede({ children }: { children: React.ReactNode }) {
849
+ return <p style={{ margin: 0, maxWidth: 640, fontSize: "var(--type-md)", lineHeight: "var(--lh-md)", color: "var(--fg-1)" }}>{children}</p>;
850
+ }
851
+ function FeatureCard({ icon, title, body }: { icon: string; title: string; body: string }) {
852
+ return (
853
+ <div style={{ flex: 1, minWidth: 220, display: "flex", flexDirection: "column", gap: "var(--space-3)", padding: "var(--space-5)", background: "var(--bg-1)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)" }}>
854
+ <span style={{ width: 30, height: 30, display: "grid", placeItems: "center", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", color: "var(--accent)" }}><Icon name={icon} size={15} /></span>
855
+ <div style={{ fontSize: "var(--type-base)", fontWeight: 600 }}>{title}</div>
856
+ <div style={{ fontSize: "var(--type-sm)", lineHeight: "var(--lh-sm)", color: "var(--fg-2)" }}>{body}</div>
857
+ </div>
858
+ );
859
+ }
860
+ function CardRow({ children }: { children: React.ReactNode }) {
861
+ return <div style={{ display: "flex", gap: "var(--space-4)", flexWrap: "wrap" }}>{children}</div>;
862
+ }
863
+ function MiniPanelHd({ label, right }: { label: string; right?: React.ReactNode }) {
864
+ return (
865
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "var(--space-2) var(--space-4)", borderBottom: "1px solid var(--border-subtle)", background: "var(--bg-2)", fontSize: "var(--type-xs)", color: "var(--fg-2)", fontFamily: "var(--font-mono)" }}>
866
+ <span>{label}</span>
867
+ {right}
868
+ </div>
869
+ );
870
+ }
871
+ function Panel({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
872
+ return <div style={{ background: "var(--bg-1)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)", overflow: "hidden", ...style }}>{children}</div>;
873
+ }
874
+ function Chip({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
875
+ return <span style={{ display: "inline-flex", padding: "4px 10px", borderRadius: "var(--radius-pill, 999px)", border: "1px solid var(--border-default)", background: "var(--bg-2)", color: "var(--fg-1)", fontSize: "var(--type-xs)", ...style }}>{children}</span>;
876
+ }
877
+ function PrimaryButton({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
878
+ return <button type="button" style={{ display: "inline-flex", alignItems: "center", gap: 6, padding: "7px 12px", borderRadius: "var(--radius-sm)", border: "1px solid transparent", background: "var(--accent)", color: "var(--bg-0)", fontSize: "var(--type-sm)", fontWeight: 600, cursor: "pointer", ...style }}>{children}</button>;
879
+ }
880
+
881
+ /* ─────────────────────── 01 · Start a video comp ──────────────────────── */
882
+ function ArtStartVideoComp() {
883
+ return (
884
+ <Board>
885
+ <ArtHeader n={1} total={7} eyebrow="Video" title="Start a video comp" />
886
+ <Body>
887
+ <Lede>A video comp is a canvas artboard whose body is a real Remotion composition. You author it in TSX like any other canvas, but every animated value is a function of a frame number, so it plays back, scrubs, and exports frame-perfect.</Lede>
888
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
889
+ <Panel style={{ width: 260, height: 150, flexShrink: 0, display: "flex", flexDirection: "column" }}>
890
+ <MiniPanelHd label="VIDEO COMP" right={<span style={{ display: "flex", alignItems: "center", gap: 4 }}><Icon name="film" size={12} /> 0:04</span>} />
891
+ <div style={{ flex: 1, margin: "var(--space-3)", borderRadius: "var(--radius-md)", background: "#000", display: "grid", placeItems: "center" }}>
892
+ <span style={{ width: 40, height: 40, borderRadius: "50%", background: "var(--accent-tint)", display: "grid", placeItems: "center" }}><Icon name="play" size={16} style={{ color: "var(--accent)" }} /></span>
893
+ </div>
894
+ </Panel>
895
+ <CardRow>
896
+ <FeatureCard icon="film" title="Ask for it in words" body="&ldquo;Cut these three clips together with a crossfade and a title card&rdquo;: the Assistant writes the comp." />
897
+ <FeatureCard icon="play" title="Free scrub & preview" body="A real embedded player, right in the canvas. No separate render just to check a frame." />
898
+ <FeatureCard icon="download" title="Export MP4 or GIF" body="⌘⇧E, or /design:export mp4, through Maude's own capture spine. No renderer install, no native binaries." />
899
+ </CardRow>
900
+ </div>
901
+ <TryIt>Ask the Assistant (<Kbd>⌘⇧A</Kbd>) to add a video comp to this canvas. A 2-second title card is a good first try.</TryIt>
902
+ </Body>
903
+ </Board>
904
+ );
905
+ }
906
+
907
+ // A real, tiny frame-driven Remotion comp — not a mockup. 90 frames @ 30fps
908
+ // (3s): a mark springs in, then keeps rotating for the rest of the loop, so
909
+ // scrubbing the REAL Timeline against this REAL comp shows real motion at
910
+ // every point, not a frozen frame (DDR-094 — freeze-frames lie).
911
+ const TIMELINE_DEMO_FRAMES = 90;
912
+ const TimelineDemoAnim = () => {
913
+ const frame = useCurrentFrame();
914
+ const { fps } = useVideoConfig();
915
+ const pop = spring({ frame, fps, config: { damping: 200 } });
916
+ const scale = interpolate(pop, [0, 1], [0.6, 1]);
917
+ const rotate = interpolate(frame, [0, TIMELINE_DEMO_FRAMES], [0, 360]);
918
+ return (
919
+ <AbsoluteFill style={{ background: "#0b0e16", alignItems: "center", justifyContent: "center" }}>
920
+ <div style={{ width: 56, height: 56, borderRadius: 14, background: "#7c8cf8", transform: \`scale(\${scale}) rotate(\${rotate}deg)\` }} />
921
+ </AbsoluteFill>
922
+ );
923
+ };
924
+
925
+ /* ────────────────────────── 02 · Edit on the Timeline ─────────────────── */
926
+ // Accepts the VideoComp as \`children\` (not rendered inline) — canvas-lib.tsx's
927
+ // \`subtreeHasVideoComp()\` walks the STATIC JSX tree via \`props.children\` to
928
+ // decide whether to render the real top-right video badge, and it does NOT
929
+ // reach inside a function component's own return value — only literal JSX
930
+ // nesting from the DCArtboard call site down. So the VideoComp must arrive
931
+ // as a literal child at that call site (see Canvas() below), not be
932
+ // constructed inside this function's own body. Real bug, caught by checking
933
+ // \`document.querySelector('.dc-artboard-video-badge')\` live and finding
934
+ // nothing, not by assuming the mockup-style code would just work.
935
+ function ArtTimeline({ children }: { children: React.ReactNode }) {
936
+ const clips = [
937
+ { w: 90, label: "intro" },
938
+ { w: 140, label: "clip-a" },
939
+ { w: 110, label: "clip-b" },
940
+ { w: 70, label: "outro" },
941
+ ];
942
+ return (
943
+ <Board>
944
+ {/* Points at the REAL video-artboard badge canvas-lib.tsx renders top-right
945
+ (top:4px, right:6px) the instant any real <VideoComp> is in this
946
+ artboard's subtree — not drawn by this canvas, just aimed at. */}
947
+ <svg width={140} height={60} viewBox="0 0 140 60" style={{ position: "absolute", right: 26, top: "75px", overflow: "visible", pointerEvents: "none" }} aria-hidden>
948
+ <path d="M108 56 C 135 20, 150 -15, 151 -60" stroke="var(--presence-agent, #c34fd8)" strokeWidth="2" fill="none" strokeLinecap="round" markerEnd="url(#pointAtBadge)" />
949
+ <defs>
950
+ <marker id="pointAtBadge" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
951
+ <path d="M0 0 L8 4 L0 8 Z" fill="var(--presence-agent, #c34fd8)" />
952
+ </marker>
953
+ </defs>
954
+ </svg>
955
+ <div style={{ position: "absolute", right: 75, top: "98px", width: 130, fontSize: "var(--type-xs)", fontWeight: 700, color: "var(--presence-agent, #c34fd8)", textAlign: "right" }}>
956
+ ① that real icon, or ⌘⇧T
957
+ </div>
958
+ <ArtHeader n={2} total={7} eyebrow="Video" title="Edit on the Timeline" />
959
+ <Body>
960
+ <Lede>The clip on the left is a real, playing Remotion comp, not a screenshot. Click the small video icon in this artboard's own top-right corner (real chrome, not drawn by this canvas), or press <Kbd>⌘⇧T</Kbd>, to open the real Timeline and scrub it.</Lede>
961
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
962
+ <Panel style={{ width: 220, flexShrink: 0, overflow: "hidden" }}>
963
+ {children}
964
+ </Panel>
965
+ <Panel style={{ flex: 1, padding: "var(--space-4)" }}>
966
+ <div style={{ display: "flex", alignItems: "center", gap: 2, marginBottom: "var(--space-3)" }}>
967
+ <Icon name="play" size={13} />
968
+ <span style={{ marginLeft: 8, fontFamily: "var(--font-mono)", fontSize: "var(--type-xs)", color: "var(--fg-3)" }}>00:00 / 00:12</span>
969
+ </div>
970
+ <div style={{ display: "flex", height: 44, borderRadius: "var(--radius-sm)", overflow: "hidden", border: "1px solid var(--border-default)" }}>
971
+ {clips.map((c, i) => (
972
+ <div key={c.label} style={{ width: c.w, display: "flex", alignItems: "center", justifyContent: "center", gap: 4, background: i % 2 === 0 ? "var(--bg-2)" : "var(--bg-3)", borderRight: i < clips.length - 1 ? "1px solid var(--border-subtle)" : "none", fontSize: "var(--type-xs)", fontFamily: "var(--font-mono)", color: "var(--fg-1)" }}>
973
+ <Icon name="drag" size={11} style={{ color: "var(--fg-3)" }} /> {c.label}
974
+ </div>
975
+ ))}
976
+ </div>
977
+ <div style={{ marginTop: "var(--space-2)", fontSize: "var(--type-xs)", color: "var(--fg-3)" }}>(a 4-clip cut, for scale: the real player on the left is the actual demo)</div>
978
+ </Panel>
979
+ </div>
980
+ <TryIt>Click the small video-camera icon in this artboard's own top-right corner right now (or press <Kbd>⌘⇧T</Kbd>). The real Timeline opens, and you can scrub the clip on the left frame by frame.</TryIt>
981
+ <CardRow>
982
+ <FeatureCard icon="drag" title="Drag to retime" body="Move, trim, or resize a clip directly on the track. The underlying frame math updates with it." />
983
+ <FeatureCard icon="scissors" title="Split, reorder, replace" body="Right-click for the full clip menu: the same vocabulary as a real NLE, scoped to what a comp needs." />
984
+ <FeatureCard icon="eye" title="Inspect any clip" body="Select a clip to see its layer decomposition: video track, caption track, effects, each independently editable." />
985
+ </CardRow>
986
+ </Body>
987
+ </Board>
988
+ );
989
+ }
990
+
991
+ /* ───────────────────── 03 · AI Assistant for video ─────────────────────── */
992
+ function ArtAiAssistantVideo() {
993
+ return (
994
+ <Board>
995
+ <ArtHeader n={3} total={7} eyebrow="Video" title="AI Assistant for video" />
996
+ <Body>
997
+ <Lede>Ask for beats, transitions, and motion graphics in plain language. The Assistant writes real Remotion, frame-driven and deterministic, never a CSS animation that would tear on export.</Lede>
998
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
999
+ <Panel style={{ width: 320, flexShrink: 0 }}>
1000
+ <MiniPanelHd label="ASSISTANT" />
1001
+ <div style={{ padding: "var(--space-4)", display: "flex", flexDirection: "column", gap: "var(--space-3)" }}>
1002
+ <div style={{ alignSelf: "flex-end", maxWidth: "85%", padding: "var(--space-3)", borderRadius: "var(--radius-md)", background: "var(--accent-tint)", fontSize: "var(--type-sm)" }}>Add a kinetic-type title card, then crossfade into the first clip</div>
1003
+ <div style={{ maxWidth: "90%", padding: "var(--space-3)", borderRadius: "var(--radius-md)", background: "var(--bg-2)", fontSize: "var(--type-sm)", color: "var(--fg-1)" }}>Added a per-letter spring-in title, 18-frame crossfade into clip-a. Want a chromatic-split flourish on the title too?</div>
1004
+ </div>
1005
+ </Panel>
1006
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
1007
+ <div style={{ fontSize: "var(--type-xs)", fontFamily: "var(--font-mono)", letterSpacing: "var(--tracking-wide)", textTransform: "uppercase", color: "var(--fg-3)" }}>Prompt ideas</div>
1008
+ {["Crossfade these three clips with a music bed", "Add a lower-third caption to every clip", "Cinematic grade, teal/orange, subtle grain", "Ken-Burns push on the hero shot", "Kinetic-type title, per-letter spring-in"].map((p) => (
1009
+ <div key={p} style={{ padding: "var(--space-3)", borderRadius: "var(--radius-md)", background: "var(--bg-1)", border: "1px solid var(--border-subtle)", fontSize: "var(--type-sm)", color: "var(--fg-1)" }}>"{p}"</div>
1010
+ ))}
1011
+ </div>
1012
+ </div>
1013
+ <TryIt>Open the Assistant (<Kbd>⌘⇧A</Kbd>) and paste one of the prompts on the right, against any video comp you've already added.</TryIt>
1014
+ </Body>
1015
+ </Board>
1016
+ );
1017
+ }
1018
+
1019
+ /* ───────────────────── 04 · Turn footage into a cut ────────────────────── */
1020
+ function ArtFootageToCut() {
1021
+ return (
1022
+ <Board>
1023
+ <ArtHeader n={4} total={7} eyebrow="Video" title="Turn a folder of clips into a cut" />
1024
+ <Body>
1025
+ <Lede>Drop a folder of raw footage on the canvas and ask for a reel. An analyst watches every clip first (good moments, subject, motion, quality), then a director assembles an edit decision list before a single frame of comp code is written.</Lede>
1026
+ <div style={{ display: "flex", gap: "var(--space-4)", alignItems: "center" }}>
1027
+ {[["folder", "Raw clips"], ["eye", "Analyst watches"], ["wand", "Director cuts"], ["film", "Comp generated"]].map(([icon, label], i, arr) => (
1028
+ <div key={label as string} style={{ display: "flex", alignItems: "center", gap: "var(--space-4)" }}>
1029
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "var(--space-2)", width: 100 }}>
1030
+ <span style={{ width: 44, height: 44, display: "grid", placeItems: "center", borderRadius: "var(--radius-md)", background: "var(--bg-1)", border: "1px solid var(--border-default)", color: "var(--accent)" }}><Icon name={icon as string} size={18} /></span>
1031
+ <span style={{ fontSize: "var(--type-xs)", textAlign: "center", color: "var(--fg-2)" }}>{label}</span>
1032
+ </div>
1033
+ {i < arr.length - 1 ? <span style={{ color: "var(--fg-3)" }}>→</span> : null}
1034
+ </div>
1035
+ ))}
1036
+ </div>
1037
+ <CardRow>
1038
+ <FeatureCard icon="eye" title="It watches before it cuts" body="Every clip gets characterized (good-moment ranges, mood, quality score) before any editing decision is made." />
1039
+ <FeatureCard icon="wand" title="A real edit decision list" body="Beats, transitions, and an optional music bed: a story, not just clips concatenated in folder order." />
1040
+ <FeatureCard icon="film" title="Then it becomes a comp" body="The EDL turns into real, hand-editable TSX on the Timeline. Pull a different shot, retime a beat, it's all still yours." />
1041
+ </CardRow>
1042
+ <TryIt>Drag a folder of real clips from Finder onto this canvas, then ask the Assistant (<Kbd>⌘⇧A</Kbd>) for "a reel from these clips."</TryIt>
1043
+ </Body>
1044
+ </Board>
1045
+ );
1046
+ }
1047
+
1048
+ /* ─────────────────── 05 · Generate video & audio ────────────────────────── */
1049
+ function ArtGenerateVideoAudio() {
1050
+ return (
1051
+ <Board>
1052
+ <ArtHeader n={5} total={7} eyebrow="AI media" title="Generate video & audio with your own key" />
1053
+ <Body>
1054
+ <Lede>Bring your own key and fill a gap without hand-sourcing it. A generated clip drops into your assets like real footage, and Maude checks what you've already made before spending a new generation.</Lede>
1055
+ <CardRow>
1056
+ <FeatureCard icon="film" title="Video: text or seeded" body="A prompt, or seed a clip from a still you already generated so it matches your hero's look. Runs in the background." />
1057
+ <FeatureCard icon="waveform" title="Music, SFX & voiceover" body="One key covers music beds, sound effects, and text-to-speech. The track lands ready to drop under a reel." />
1058
+ <FeatureCard icon="check" title="Reuse before you pay" body="Before spending credits, Maude searches audio you've already generated (and your provider history) for a match." />
1059
+ </CardRow>
1060
+ <TryIt>Add your key under File → Settings → AI generation, then ask the Assistant for "a 4-second clip of ocean waves at sunset."</TryIt>
1061
+ </Body>
1062
+ </Board>
1063
+ );
1064
+ }
1065
+
1066
+ /* ──────────────────────────── 06 · Captions ──────────────────────────────── */
1067
+ function ArtCaptions() {
1068
+ return (
1069
+ <Board>
1070
+ <ArtHeader n={6} total={7} eyebrow="Video" title="Automatic captions" />
1071
+ <Body>
1072
+ <Lede>Subtitles are free and need no key. whisper.cpp runs locally (one-click model download) and writes word-timed captions on any clip. Prefer a cloud engine? Pick ElevenLabs Scribe or Groq in Settings. Maude never silently switches you to a paid engine.</Lede>
1073
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
1074
+ <Panel style={{ width: 300, flexShrink: 0 }}>
1075
+ <div style={{ height: 90, background: "#000", display: "flex", alignItems: "flex-end", justifyContent: "center", padding: "var(--space-3)" }}>
1076
+ <span style={{ padding: "4px 10px", borderRadius: "var(--radius-sm)", background: "rgba(0,0,0,0.7)", color: "#fff", fontSize: "var(--type-sm)" }}>the canvas fills in <b>live</b></span>
1077
+ </div>
1078
+ <div style={{ padding: "var(--space-3)", display: "flex", gap: "var(--space-2)" }}>
1079
+ <Chip>local · whisper.cpp</Chip>
1080
+ <Chip>cloud · Scribe</Chip>
1081
+ </div>
1082
+ </Panel>
1083
+ <CardRow>
1084
+ <FeatureCard icon="captions" title="Word-timed & editable" body="Captions land as an editable JSON sidecar. whisper mishears jargon sometimes, so patch it by hand and re-render, no re-transcribing." />
1085
+ <FeatureCard icon="check" title="Auto-converts your file" body="Any video container works. Maude handles the audio extraction, no ffmpeg wrangling." />
1086
+ <FeatureCard icon="sparkle" title="You choose the engine" body="Local, Scribe, or Groq: an explicit setting, never a silent default to a paid cloud engine." />
1087
+ </CardRow>
1088
+ </div>
1089
+ <TryIt>Select a clip on this canvas, then ask the Assistant (<Kbd>⌘⇧A</Kbd>) to "add captions to this clip."</TryIt>
1090
+ </Body>
1091
+ </Board>
1092
+ );
1093
+ }
1094
+
1095
+ /* ────────────────────────────── 07 · Export ────────────────────────────── */
1096
+ function ArtExport() {
1097
+ return (
1098
+ <Board>
1099
+ <ArtHeader n={7} total={7} eyebrow="Ship it" title="Export" />
1100
+ <Body>
1101
+ <Lede><Kbd>⌘⇧E</Kbd> opens the export dialog: MP4 (H.264) or palette-quantized GIF, resolved from the comp's own frame rate and duration. Everything renders through Maude's own capture spine: no renderer binaries, no install step for whoever opens the project next.</Lede>
1102
+ <div style={{ display: "flex", gap: "var(--space-5)" }}>
1103
+ <Panel style={{ width: 260, padding: "var(--space-5)", display: "flex", flexDirection: "column", gap: "var(--space-3)" }}>
1104
+ <div style={{ display: "flex", gap: "var(--space-2)" }}>
1105
+ <Chip style={{ background: "var(--accent-tint)", color: "var(--accent)", borderColor: "var(--accent-muted)" }}>MP4</Chip>
1106
+ <Chip>GIF</Chip>
1107
+ </div>
1108
+ <PrimaryButton style={{ alignSelf: "flex-start" }}><Icon name="download" size={13} /> Export ⌘⇧E</PrimaryButton>
1109
+ </Panel>
1110
+ <CardRow>
1111
+ <FeatureCard icon="download" title="No install, ever" body="Rendering happens through the same headless capture Maude already uses for screenshots. Nothing new to set up." />
1112
+ <FeatureCard icon="film" title="Frame-accurate" body="fps and duration come straight from the comp's own meta. What you scrub in the Player is exactly what exports." />
1113
+ <FeatureCard icon="check" title="Same command, from the CLI" body="/design:export mp4 --scope artboard works headlessly too, useful once a cut is part of a repeatable pipeline." />
1114
+ </CardRow>
1115
+ </div>
1116
+ <TryIt>Press <Kbd>⌘⇧E</Kbd> right now. The real export dialog opens for whatever's active.</TryIt>
1117
+ </Body>
1118
+ </Board>
1119
+ );
1120
+ }
1121
+
1122
+ export default function Canvas() {
1123
+ return (
1124
+ <DesignCanvas>
1125
+ <DCSection id="how-to-make-video" title="How to make video" subtitle="REFERENCE · 7 artboards, one per video capability">
1126
+ <DCArtboard id="start-video-comp" label="01 · Start a video comp" width={1040} height={640}><ArtStartVideoComp /></DCArtboard>
1127
+ <DCArtboard id="timeline" label="02 · Edit on the Timeline" width={1040} height={640}>
1128
+ <ArtTimeline>
1129
+ <VideoComp component={TimelineDemoAnim} durationInFrames={TIMELINE_DEMO_FRAMES} fps={30} width={220} height={140} />
1130
+ </ArtTimeline>
1131
+ </DCArtboard>
1132
+ <DCArtboard id="ai-assistant-video" label="03 · AI Assistant for video" width={1040} height={640}><ArtAiAssistantVideo /></DCArtboard>
1133
+ <DCArtboard id="footage-to-cut" label="04 · Footage into a cut" width={1040} height={640}><ArtFootageToCut /></DCArtboard>
1134
+ <DCArtboard id="generate-video-audio" label="05 · Generate video & audio" width={1040} height={640}><ArtGenerateVideoAudio /></DCArtboard>
1135
+ <DCArtboard id="captions" label="06 · Automatic captions" width={1040} height={640}><ArtCaptions /></DCArtboard>
1136
+ <DCArtboard id="export" label="07 · Export" width={1040} height={640}><ArtExport /></DCArtboard>
1137
+ </DCSection>
1138
+ </DesignCanvas>
1139
+ );
1140
+ }
1141
+ `;
1142
+
1143
+ const HOW_TO_MAKE_VIDEO_META = {
1144
+ title: 'How to make video',
1145
+ subtitle:
1146
+ 'Reference canvas seeded into every new project \u2014 7 artboards, one per video capability',
1147
+ brief:
1148
+ 'Onboarding reference canvas (companion to How to use Maude.tsx). Seeded by scaffold-design.ts on every new project. One artboard per video-comp capability: starting a comp, the Timeline, the Assistant for video, turning raw footage into a cut, AI video/audio generation, automatic captions, export.',
1149
+ platform: 'desktop',
1150
+ designSystem: 'maude',
1151
+ opt_out_scope: 'none',
1152
+ sections: [
1153
+ {
1154
+ id: 'how-to-make-video',
1155
+ title: 'How to make video',
1156
+ subtitle: 'REFERENCE \u00b7 7 artboards, one per video capability',
1157
+ },
1158
+ ],
1159
+ artboards: [
1160
+ {
1161
+ id: 'start-video-comp',
1162
+ label: '01 \u00b7 Start a video comp',
1163
+ },
1164
+ {
1165
+ id: 'timeline',
1166
+ label: '02 \u00b7 Edit on the Timeline',
1167
+ },
1168
+ {
1169
+ id: 'ai-assistant-video',
1170
+ label: '03 \u00b7 AI Assistant for video',
1171
+ },
1172
+ {
1173
+ id: 'footage-to-cut',
1174
+ label: '04 \u00b7 Footage into a cut',
1175
+ },
1176
+ {
1177
+ id: 'generate-video-audio',
1178
+ label: '05 \u00b7 Generate video & audio',
1179
+ },
1180
+ {
1181
+ id: 'captions',
1182
+ label: '06 \u00b7 Automatic captions',
1183
+ },
1184
+ {
1185
+ id: 'export',
1186
+ label: '07 \u00b7 Export',
1187
+ },
1188
+ ],
1189
+ layout: {
1190
+ artboards: [
1191
+ {
1192
+ id: 'start-video-comp',
1193
+ x: 0,
1194
+ y: 0,
1195
+ },
1196
+ {
1197
+ id: 'timeline',
1198
+ x: 1120,
1199
+ y: 0,
1200
+ },
1201
+ {
1202
+ id: 'ai-assistant-video',
1203
+ x: 2240,
1204
+ y: 0,
1205
+ },
1206
+ {
1207
+ id: 'footage-to-cut',
1208
+ x: 3360,
1209
+ y: 0,
1210
+ },
1211
+ {
1212
+ id: 'generate-video-audio',
1213
+ x: 4480,
1214
+ y: 0,
1215
+ },
1216
+ {
1217
+ id: 'captions',
1218
+ x: 5600,
1219
+ y: 0,
1220
+ },
1221
+ {
1222
+ id: 'export',
1223
+ x: 6720,
1224
+ y: 0,
1225
+ },
1226
+ ],
1227
+ },
1228
+ css_mode: 'inline',
1229
+ iteration_count: 1,
1230
+ };
1231
+
103
1232
  /** Whether `dir` is already a Maude project (has `.design/config.json`). */
104
1233
  export function hasDesign(dir: string): boolean {
105
1234
  return existsSync(join(dir, '.design', 'config.json'));
@@ -141,6 +1270,22 @@ export function scaffoldDesign(dir: string, name?: string): ScaffoldResult {
141
1270
  `${JSON.stringify(STARTER_CANVAS_META, null, 2)}\n`,
142
1271
  'utf8'
143
1272
  );
1273
+ // Seed the two onboarding reference canvases (Phase 1, DDR-166) — every
1274
+ // new project gets a live, in-app "how to" alongside the blank Welcome
1275
+ // canvas, covering the full capability surface with honest mini-mockups
1276
+ // (no live screenshots — has to work with zero server state).
1277
+ writeFileSync(join(designDir, 'ui', 'How to use Maude.tsx'), HOW_TO_USE_MAUDE_TSX, 'utf8');
1278
+ writeFileSync(
1279
+ join(designDir, 'ui', 'How to use Maude.meta.json'),
1280
+ `${JSON.stringify({ ...HOW_TO_USE_MAUDE_META, created: new Date().toISOString().slice(0, 10) }, null, 2)}\n`,
1281
+ 'utf8'
1282
+ );
1283
+ writeFileSync(join(designDir, 'ui', 'How to make video.tsx'), HOW_TO_MAKE_VIDEO_TSX, 'utf8');
1284
+ writeFileSync(
1285
+ join(designDir, 'ui', 'How to make video.meta.json'),
1286
+ `${JSON.stringify({ ...HOW_TO_MAKE_VIDEO_META, created: new Date().toISOString().slice(0, 10) }, null, 2)}\n`,
1287
+ 'utf8'
1288
+ );
144
1289
  // `system/` is genuinely empty until /design:setup-ds — keep it in git.
145
1290
  writeFileSync(join(designDir, 'system', '.gitkeep'), '', 'utf8');
146
1291
  return { ok: true };