kasaframe 0.0.0-stage → 0.1.3

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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +148 -0
  3. package/README.md +92 -2
  4. package/app/package.json +12 -0
  5. package/app/server/engine.ts +533 -0
  6. package/app/server/main.ts +209 -0
  7. package/app/web/env.d.ts +2 -0
  8. package/app/web/icons.ts +31 -0
  9. package/app/web/index.html +21 -0
  10. package/app/web/main.ts +1195 -0
  11. package/app/web/style.css +463 -0
  12. package/bin/hooks.mjs +64 -0
  13. package/bin/kf.mjs +32 -0
  14. package/docs/AGENTS.md +286 -0
  15. package/music/README.md +37 -0
  16. package/package.json +44 -4
  17. package/packages/adapters/package.json +36 -0
  18. package/packages/adapters/src/css.ts +11 -0
  19. package/packages/adapters/src/gsap.ts +26 -0
  20. package/packages/adapters/src/index.ts +9 -0
  21. package/packages/adapters/src/lottie.ts +22 -0
  22. package/packages/adapters/src/media.ts +59 -0
  23. package/packages/adapters/src/page.ts +229 -0
  24. package/packages/adapters/src/project.ts +214 -0
  25. package/packages/adapters/src/react.ts +36 -0
  26. package/packages/adapters/src/svelte.ts +22 -0
  27. package/packages/adapters/src/three.ts +18 -0
  28. package/packages/adapters/src/vue.ts +27 -0
  29. package/packages/check/package.json +19 -0
  30. package/packages/check/src/check.ts +280 -0
  31. package/packages/check/src/cli.ts +70 -0
  32. package/packages/check/src/image.ts +146 -0
  33. package/packages/check/src/index.ts +9 -0
  34. package/packages/check/src/ocr.swift +37 -0
  35. package/packages/check/src/probe.ts +328 -0
  36. package/packages/check/src/report.ts +44 -0
  37. package/packages/check/src/rules/assets.ts +45 -0
  38. package/packages/check/src/rules/beats.ts +212 -0
  39. package/packages/check/src/rules/camera.ts +217 -0
  40. package/packages/check/src/rules/captions.ts +49 -0
  41. package/packages/check/src/rules/flash.ts +235 -0
  42. package/packages/check/src/rules/frames.ts +136 -0
  43. package/packages/check/src/rules/layout.ts +281 -0
  44. package/packages/check/src/rules/page.ts +87 -0
  45. package/packages/check/src/rules/runs.ts +20 -0
  46. package/packages/check/src/rules/text.ts +326 -0
  47. package/packages/check/src/scan.ts +240 -0
  48. package/packages/check/src/serve.ts +67 -0
  49. package/packages/check/src/types.ts +80 -0
  50. package/packages/check/src/video.ts +137 -0
  51. package/packages/cli/package.json +30 -0
  52. package/packages/cli/src/blur.ts +80 -0
  53. package/packages/cli/src/browser.ts +203 -0
  54. package/packages/cli/src/command.ts +44 -0
  55. package/packages/cli/src/commands/app.ts +25 -0
  56. package/packages/cli/src/commands/beats.ts +36 -0
  57. package/packages/cli/src/commands/bench.ts +123 -0
  58. package/packages/cli/src/commands/blender.ts +293 -0
  59. package/packages/cli/src/commands/check.ts +25 -0
  60. package/packages/cli/src/commands/direct.ts +210 -0
  61. package/packages/cli/src/commands/doctor.ts +30 -0
  62. package/packages/cli/src/commands/gen.ts +173 -0
  63. package/packages/cli/src/commands/look.ts +46 -0
  64. package/packages/cli/src/commands/lyrics.ts +157 -0
  65. package/packages/cli/src/commands/make.ts +1121 -0
  66. package/packages/cli/src/commands/music.ts +78 -0
  67. package/packages/cli/src/commands/narrate.ts +238 -0
  68. package/packages/cli/src/commands/new.ts +185 -0
  69. package/packages/cli/src/commands/preview.ts +43 -0
  70. package/packages/cli/src/commands/render.ts +250 -0
  71. package/packages/cli/src/commands/series.ts +110 -0
  72. package/packages/cli/src/commands/setup.ts +102 -0
  73. package/packages/cli/src/commands/sheet.ts +114 -0
  74. package/packages/cli/src/commands/skill.ts +43 -0
  75. package/packages/cli/src/commands/still.ts +40 -0
  76. package/packages/cli/src/commands/theme.ts +172 -0
  77. package/packages/cli/src/commands/transitions.ts +40 -0
  78. package/packages/cli/src/deps.ts +122 -0
  79. package/packages/cli/src/ffmpeg.ts +81 -0
  80. package/packages/cli/src/index.ts +8 -0
  81. package/packages/cli/src/kf.ts +64 -0
  82. package/packages/cli/src/lock.ts +72 -0
  83. package/packages/cli/src/mix.ts +131 -0
  84. package/packages/cli/src/out.ts +13 -0
  85. package/packages/cli/src/pool.ts +34 -0
  86. package/packages/cli/src/segments.ts +402 -0
  87. package/packages/cli/src/serve.ts +124 -0
  88. package/packages/clock/package.json +14 -0
  89. package/packages/clock/src/index.ts +25 -0
  90. package/packages/clock/src/install.ts +351 -0
  91. package/packages/clock/src/types.ts +45 -0
  92. package/packages/core/package.json +9 -0
  93. package/packages/core/src/beats.ts +283 -0
  94. package/packages/core/src/ease.ts +87 -0
  95. package/packages/core/src/frame.ts +99 -0
  96. package/packages/core/src/index.ts +27 -0
  97. package/packages/core/src/project.ts +157 -0
  98. package/packages/core/src/rand.ts +56 -0
  99. package/packages/core/src/spring.ts +52 -0
  100. package/packages/core/src/style.ts +100 -0
  101. package/packages/core/src/time.ts +88 -0
  102. package/packages/director/package.json +23 -0
  103. package/packages/director/src/assemble.ts +361 -0
  104. package/packages/director/src/brief.ts +122 -0
  105. package/packages/director/src/catalog.ts +49 -0
  106. package/packages/director/src/content.ts +343 -0
  107. package/packages/director/src/fallback.ts +58 -0
  108. package/packages/director/src/index.ts +13 -0
  109. package/packages/director/src/pattern.ts +90 -0
  110. package/packages/director/src/patterns/fill/cells.ts +115 -0
  111. package/packages/director/src/patterns/formula/stack.ts +93 -0
  112. package/packages/director/src/patterns/util.ts +58 -0
  113. package/packages/director/src/scene.ts +62 -0
  114. package/packages/director/src/select.ts +163 -0
  115. package/packages/director/src/transitions.ts +115 -0
  116. package/packages/gen/PROVIDERS.md +90 -0
  117. package/packages/gen/package.json +9 -0
  118. package/packages/gen/src/ask.ts +132 -0
  119. package/packages/gen/src/audio.ts +91 -0
  120. package/packages/gen/src/blender.ts +54 -0
  121. package/packages/gen/src/capabilities.ts +151 -0
  122. package/packages/gen/src/generate.ts +206 -0
  123. package/packages/gen/src/image/codex.ts +117 -0
  124. package/packages/gen/src/image/og.ts +133 -0
  125. package/packages/gen/src/index.ts +26 -0
  126. package/packages/gen/src/keys.ts +86 -0
  127. package/packages/gen/src/media.ts +150 -0
  128. package/packages/gen/src/music/elevenlabs.ts +41 -0
  129. package/packages/gen/src/music/index.ts +120 -0
  130. package/packages/gen/src/music/library.ts +73 -0
  131. package/packages/gen/src/music/lyria.ts +52 -0
  132. package/packages/gen/src/music/types.ts +28 -0
  133. package/packages/gen/src/plan.ts +106 -0
  134. package/packages/gen/src/registry.ts +54 -0
  135. package/packages/gen/src/seam.ts +103 -0
  136. package/packages/gen/src/tts/dashscope.ts +64 -0
  137. package/packages/gen/src/tts/elevenlabs.ts +84 -0
  138. package/packages/gen/src/tts/gemini.ts +73 -0
  139. package/packages/gen/src/tts/index.ts +241 -0
  140. package/packages/gen/src/tts/local.ts +42 -0
  141. package/packages/gen/src/tts/types.ts +44 -0
  142. package/packages/gen/src/types.ts +90 -0
  143. package/packages/gen/src/video/fal.ts +140 -0
  144. package/packages/gen/src/video/h3.ts +112 -0
  145. package/packages/gen/src/video/http.ts +44 -0
  146. package/packages/gen/src/video/ltx.ts +60 -0
  147. package/packages/gen/src/video/minimax.ts +55 -0
  148. package/packages/gen/src/video/veo.ts +71 -0
  149. package/packages/jizura/package.json +16 -0
  150. package/packages/jizura/src/deny.ts +73 -0
  151. package/packages/jizura/src/index.ts +7 -0
  152. package/packages/jizura/src/install.ts +269 -0
  153. package/packages/jizura/src/layer.ts +325 -0
  154. package/packages/jizura/src/lyrics.ts +81 -0
  155. package/packages/jizura/src/pin.ts +8 -0
  156. package/packages/jizura/src/vendor.ts +140 -0
  157. package/packages/jizura/src/vite.ts +63 -0
  158. package/packages/jizura/vendor/JIZURA_LICENSE.txt +21 -0
  159. package/packages/jizura/vendor/NOTICE +43 -0
  160. package/packages/jizura/vendor/engine.json +47 -0
  161. package/packages/jizura/vendor/fonts/BlackHanSans-Regular.woff2 +0 -0
  162. package/packages/jizura/vendor/fonts/DotGothic16-Regular.woff2 +0 -0
  163. package/packages/jizura/vendor/fonts/GowunBatang-Bold.woff2 +0 -0
  164. package/packages/jizura/vendor/fonts/IBMPlexMono-Medium.ttf +0 -0
  165. package/packages/jizura/vendor/fonts/IBMPlexMono-SemiBold.ttf +0 -0
  166. package/packages/jizura/vendor/fonts/NotoSansKR-VF.woff2 +0 -0
  167. package/packages/jizura/vendor/fonts/NotoSerifKR-VF.woff2 +0 -0
  168. package/packages/jizura/vendor/fonts/OFL-blackhansans.txt +93 -0
  169. package/packages/jizura/vendor/fonts/OFL-dotgothic16.txt +93 -0
  170. package/packages/jizura/vendor/fonts/OFL-gowunbatang.txt +94 -0
  171. package/packages/jizura/vendor/fonts/OFL-ibmplexmono.txt +93 -0
  172. package/packages/jizura/vendor/fonts/OFL-notosanskr.txt +93 -0
  173. package/packages/jizura/vendor/fonts/OFL-notoserifkr.txt +94 -0
  174. package/packages/jizura/vendor/fonts/fonts.json +52 -0
  175. package/packages/jizura/vendor/jizura_engine.js +35024 -0
  176. package/packages/motion/README.md +153 -0
  177. package/packages/motion/package.json +30 -0
  178. package/packages/motion/src/camera.ts +97 -0
  179. package/packages/motion/src/captions.ts +487 -0
  180. package/packages/motion/src/enter.ts +128 -0
  181. package/packages/motion/src/hangul.ts +129 -0
  182. package/packages/motion/src/impact.ts +100 -0
  183. package/packages/motion/src/index.ts +17 -0
  184. package/packages/motion/src/moves/brushWipe.ts +103 -0
  185. package/packages/motion/src/moves/cloneArray.ts +111 -0
  186. package/packages/motion/src/moves/curtains.ts +81 -0
  187. package/packages/motion/src/moves/dress.ts +191 -0
  188. package/packages/motion/src/moves/echoStack.ts +70 -0
  189. package/packages/motion/src/moves/fit.ts +70 -0
  190. package/packages/motion/src/moves/flood.ts +61 -0
  191. package/packages/motion/src/moves/ghostStack.ts +88 -0
  192. package/packages/motion/src/moves/gridFloor.ts +100 -0
  193. package/packages/motion/src/moves/imageSwarm.ts +113 -0
  194. package/packages/motion/src/moves/index.ts +38 -0
  195. package/packages/motion/src/moves/irisBlades.ts +113 -0
  196. package/packages/motion/src/moves/karaokeLine.ts +108 -0
  197. package/packages/motion/src/moves/ledBoard.ts +94 -0
  198. package/packages/motion/src/moves/liquidTab.ts +72 -0
  199. package/packages/motion/src/moves/markerWrite.ts +63 -0
  200. package/packages/motion/src/moves/menu.ts +138 -0
  201. package/packages/motion/src/moves/morphBox.ts +96 -0
  202. package/packages/motion/src/moves/neonSign.ts +78 -0
  203. package/packages/motion/src/moves/panelPop.ts +78 -0
  204. package/packages/motion/src/moves/paperTear.ts +75 -0
  205. package/packages/motion/src/moves/path.ts +64 -0
  206. package/packages/motion/src/moves/pathText.ts +107 -0
  207. package/packages/motion/src/moves/pointSphere.ts +109 -0
  208. package/packages/motion/src/moves/ransomLetters.ts +73 -0
  209. package/packages/motion/src/moves/shapeMorph.ts +143 -0
  210. package/packages/motion/src/moves/slamWords.ts +96 -0
  211. package/packages/motion/src/moves/sparkTrail.ts +130 -0
  212. package/packages/motion/src/moves/speedLines.ts +91 -0
  213. package/packages/motion/src/moves/splitFlap.ts +149 -0
  214. package/packages/motion/src/moves/stepWords.ts +128 -0
  215. package/packages/motion/src/moves/strikeOut.ts +98 -0
  216. package/packages/motion/src/moves/sunburst.ts +58 -0
  217. package/packages/motion/src/moves/tickerRows.ts +91 -0
  218. package/packages/motion/src/moves/tokenPicker.ts +115 -0
  219. package/packages/motion/src/moves/truchet.ts +85 -0
  220. package/packages/motion/src/moves/wordWall.ts +78 -0
  221. package/packages/motion/src/moves/worldRoll.ts +32 -0
  222. package/packages/motion/src/particles.ts +102 -0
  223. package/packages/motion/src/react/index.tsx +21 -0
  224. package/packages/motion/src/react/parts.tsx +431 -0
  225. package/packages/motion/src/react/time.tsx +97 -0
  226. package/packages/motion/src/text.ts +103 -0
  227. package/packages/motion/src/transition.ts +82 -0
  228. package/packages/motion/src/transition3d/cards.ts +65 -0
  229. package/packages/motion/src/transition3d/chain.ts +84 -0
  230. package/packages/motion/src/transition3d/dive.ts +181 -0
  231. package/packages/motion/src/transition3d/fold.ts +110 -0
  232. package/packages/motion/src/transition3d/gl.ts +219 -0
  233. package/packages/motion/src/transition3d/index.ts +235 -0
  234. package/packages/motion/src/transition3d/live.ts +70 -0
  235. package/packages/motion/src/transition3d/portal.ts +115 -0
  236. package/packages/motion/src/transition3d/react.tsx +29 -0
  237. package/packages/motion/src/transition3d/rig.ts +600 -0
  238. package/packages/motion/src/transition3d/shatter.ts +104 -0
  239. package/packages/motion/src/transition3d/space.ts +173 -0
  240. package/packages/motion/src/transition3d/whip.ts +78 -0
  241. package/packages/motion/src/transition3d/world.ts +737 -0
  242. package/packages/motion/src/transition3d/zoomOut.ts +114 -0
  243. package/packages/motion/src/transitions2d/catalog.ts +127 -0
  244. package/packages/motion/src/transitions2d/css.d.ts +2 -0
  245. package/packages/motion/src/transitions2d/cult-ui/LICENSE.md +9 -0
  246. package/packages/motion/src/transitions2d/cult-ui/RollingNumber.tsx +43 -0
  247. package/packages/motion/src/transitions2d/gl-transitions/LICENSE +25 -0
  248. package/packages/motion/src/transitions2d/gl-transitions/shaders.ts +1931 -0
  249. package/packages/motion/src/transitions2d/index.ts +2 -0
  250. package/packages/motion/src/transitions2d/kokonutui/LICENSE +21 -0
  251. package/packages/motion/src/transitions2d/kokonutui/MatrixText.css +16 -0
  252. package/packages/motion/src/transitions2d/kokonutui/MatrixText.tsx +94 -0
  253. package/packages/motion/src/transitions2d/magicui/HyperText.css +15 -0
  254. package/packages/motion/src/transitions2d/magicui/HyperText.tsx +115 -0
  255. package/packages/motion/src/transitions2d/magicui/LICENSE.md +21 -0
  256. package/packages/motion/src/transitions2d/magicui/MorphingText.css +25 -0
  257. package/packages/motion/src/transitions2d/magicui/MorphingText.tsx +130 -0
  258. package/packages/motion/src/transitions2d/magicui/WordRotate.tsx +42 -0
  259. package/packages/motion/src/transitions2d/react.ts +13 -0
  260. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.css +39 -0
  261. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.tsx +32 -0
  262. package/packages/motion/src/transitions2d/smoothui/LICENSE +21 -0
  263. package/packages/motion/src/transitions2d/smoothui/SharedAxisY.tsx +50 -0
  264. package/packages/motion/src/transitions2d/smoothui/TextMorph.tsx +90 -0
  265. package/packages/motion/src/transitions2d/smoothui/shaders.ts +223 -0
  266. package/packages/motion/src/transitions2d/unlumen/LICENSE +21 -0
  267. package/packages/motion/src/transitions2d/unlumen/ScrambleText.tsx +114 -0
  268. package/packages/motion/src/transitions2d/vengeance-ui/LICENSE +21 -0
  269. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.css +69 -0
  270. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.tsx +45 -0
  271. package/packages/motion/src/type.ts +131 -0
  272. package/packages/motion/src/ui.ts +172 -0
  273. package/packages/patterns/package.json +15 -0
  274. package/packages/patterns/src/bars/rank.ts +119 -0
  275. package/packages/patterns/src/before-after/shrink.ts +127 -0
  276. package/packages/patterns/src/before-after/strike.ts +125 -0
  277. package/packages/patterns/src/code/terminal.ts +133 -0
  278. package/packages/patterns/src/compare/bars.ts +142 -0
  279. package/packages/patterns/src/compare/duel.ts +103 -0
  280. package/packages/patterns/src/end/lockup.ts +85 -0
  281. package/packages/patterns/src/end/slam.ts +91 -0
  282. package/packages/patterns/src/flow/graph.ts +250 -0
  283. package/packages/patterns/src/hook-number/counter.ts +101 -0
  284. package/packages/patterns/src/hook-number/roll.ts +179 -0
  285. package/packages/patterns/src/hook-number/slam.ts +91 -0
  286. package/packages/patterns/src/index.ts +38 -0
  287. package/packages/patterns/src/kit.ts +449 -0
  288. package/packages/patterns/src/limit/gauge.ts +117 -0
  289. package/packages/patterns/src/list/cards.ts +131 -0
  290. package/packages/patterns/src/list/cycle.ts +22 -0
  291. package/packages/patterns/src/media/panels.ts +100 -0
  292. package/packages/patterns/src/objects.ts +234 -0
  293. package/packages/patterns/src/quote/karaoke.ts +126 -0
  294. package/packages/patterns/src/quote/slam.ts +22 -0
  295. package/packages/patterns/src/screen/focus.ts +85 -0
  296. package/packages/patterns/src/steps/path.ts +137 -0
  297. package/packages/patterns/src/steps/stack.ts +111 -0
  298. package/packages/patterns/src/term-reveal/chips.ts +92 -0
  299. package/packages/patterns/src/term-reveal/expand.ts +127 -0
  300. package/packages/patterns/src/timeline/axis.ts +144 -0
  301. package/packages/patterns/src/title/mask.ts +85 -0
  302. package/packages/patterns/src/title/slam.ts +23 -0
  303. package/packages/patterns/src/toggle-sim/switch.ts +92 -0
  304. package/packages/patterns/src/trend/columns.ts +108 -0
  305. package/packages/patterns/src/trend/draw.ts +140 -0
  306. package/packages/patterns/src/types.ts +67 -0
  307. package/packages/patterns/src/words/moves.ts +42 -0
  308. package/packages/patterns/src/words/slam.ts +4 -0
  309. package/packages/plan/package.json +15 -0
  310. package/packages/plan/src/asks.ts +256 -0
  311. package/packages/plan/src/brief.ts +383 -0
  312. package/packages/plan/src/captions.ts +78 -0
  313. package/packages/plan/src/detect.ts +93 -0
  314. package/packages/plan/src/index.ts +27 -0
  315. package/packages/plan/src/layout.ts +176 -0
  316. package/packages/plan/src/llm.ts +172 -0
  317. package/packages/plan/src/look.ts +222 -0
  318. package/packages/plan/src/plan.ts +179 -0
  319. package/packages/plan/src/request.ts +55 -0
  320. package/packages/plan/src/series.ts +76 -0
  321. package/packages/plan/src/studio.ts +53 -0
  322. package/packages/plan/src/templates.ts +42 -0
  323. package/packages/plan/src/themes.ts +48 -0
  324. package/packages/plan/src/types.ts +157 -0
  325. package/packages/studio/package.json +12 -0
  326. package/packages/studio/src/index.ts +1 -0
  327. package/packages/studio/src/plugin.ts +236 -0
  328. package/packages/studio/src/ui/main.ts +503 -0
  329. package/packages/studio/src/ui/studio.css +165 -0
  330. package/packages/studio/src/ui/timeline.ts +317 -0
  331. package/packages/themes/kit/THEME-MAKE.md +68 -0
  332. package/packages/themes/kit/THEME.md +30 -0
  333. package/packages/themes/kit/briefs/og-intro-15s/brief.json +16 -0
  334. package/packages/themes/kit/briefs/og-intro-15s/facts.ts +29 -0
  335. package/packages/themes/kit/briefs/og-intro-15s/logo.svg +12 -0
  336. package/packages/themes/kit/effects.ts +5 -0
  337. package/packages/themes/kit/sample/index.html +11 -0
  338. package/packages/themes/kit/sample/main.ts +9 -0
  339. package/packages/themes/kit/sample/scenes/scene.ts +15 -0
  340. package/packages/themes/package.json +19 -0
  341. package/packages/themes/src/factory.ts +296 -0
  342. package/packages/themes/src/fx.ts +14 -0
  343. package/packages/themes/src/index.ts +114 -0
  344. package/packages/themes/src/slotkit.ts +79 -0
  345. package/packages/themes/src/slots.ts +46 -0
  346. package/packages/themes/src/stage3d.ts +197 -0
  347. package/packages/themes/src/types.ts +58 -0
  348. package/presets/kawaii-pastel.json +94 -0
  349. package/presets/neutral-dark.json +82 -0
  350. package/presets/og.json +98 -0
  351. package/series/og-shorts.json +14 -0
  352. package/skills/kasaframe/SKILL.md +33 -0
  353. package/templates/_shared/project/index.html +11 -0
  354. package/templates/_shared/project/lib/react.ts +22 -0
  355. package/templates/_shared/project/lib/stage.ts +292 -0
  356. package/templates/_shared/project/main.ts +9 -0
  357. package/templates/_shared/scenes/default.ts +21 -0
  358. package/templates/_shared/scenes/drop.ts +33 -0
  359. package/templates/_shared/scenes/end.ts +26 -0
  360. package/templates/_shared/scenes/hook.ts +18 -0
  361. package/templates/_shared/scenes/seam.ts +41 -0
  362. package/templates/_shared/scenes/step.ts +49 -0
  363. package/templates/ad/preview.png +0 -0
  364. package/templates/ad/template.json +151 -0
  365. package/templates/demo/preview.png +0 -0
  366. package/templates/demo/template.json +141 -0
  367. package/templates/explainer/preview.png +0 -0
  368. package/templates/explainer/template.json +193 -0
  369. package/templates/launch/preview.png +0 -0
  370. package/templates/launch/template.json +163 -0
  371. package/templates/mv/preview.png +0 -0
  372. package/templates/mv/scenes/default.ts +98 -0
  373. package/templates/mv/template.json +178 -0
  374. package/templates/promo/preview.png +0 -0
  375. package/templates/promo/template.json +185 -0
  376. package/templates/shorts/preview.png +0 -0
  377. package/templates/shorts/scenes/hook.ts +37 -0
  378. package/templates/shorts/template.json +196 -0
  379. package/templates/square/preview.png +0 -0
  380. package/templates/square/template.json +133 -0
  381. package/themes/debug-hud/THEME.md +39 -0
  382. package/themes/debug-hud/effects.ts +389 -0
  383. package/themes/debug-hud/sample/assets/og/logo.svg +12 -0
  384. package/themes/debug-hud/sample/beats.json +1 -0
  385. package/themes/debug-hud/sample/index.html +11 -0
  386. package/themes/debug-hud/sample/kf.json +59 -0
  387. package/themes/debug-hud/sample/lib/hudkit.ts +49 -0
  388. package/themes/debug-hud/sample/lib/og.ts +29 -0
  389. package/themes/debug-hud/sample/main.ts +9 -0
  390. package/themes/debug-hud/sample/scenes/end.ts +62 -0
  391. package/themes/debug-hud/sample/scenes/gather.ts +67 -0
  392. package/themes/debug-hud/sample/scenes/models.ts +70 -0
  393. package/themes/debug-hud/sample/scenes/price.ts +79 -0
  394. package/themes/debug-hud/sample/style.json +91 -0
  395. package/themes/debug-hud/slots.ts +99 -0
  396. package/themes/debug-hud/still.png +0 -0
  397. package/themes/debug-hud/style.json +91 -0
  398. package/themes/debug-hud/theme.json +68 -0
  399. package/themes/og-measure/THEME.md +47 -0
  400. package/themes/og-measure/effects.ts +432 -0
  401. package/themes/og-measure/sample/assets/brand/og-logo-full.svg +12 -0
  402. package/themes/og-measure/sample/assets/brand/og-logo.svg +12 -0
  403. package/themes/og-measure/sample/assets/data/measure.json +353 -0
  404. package/themes/og-measure/sample/beats.json +1 -0
  405. package/themes/og-measure/sample/index.html +11 -0
  406. package/themes/og-measure/sample/kf.json +66 -0
  407. package/themes/og-measure/sample/lib/data.ts +62 -0
  408. package/themes/og-measure/sample/main.ts +9 -0
  409. package/themes/og-measure/sample/measure.ts +78 -0
  410. package/themes/og-measure/sample/scenes/end.ts +47 -0
  411. package/themes/og-measure/sample/scenes/gather.ts +51 -0
  412. package/themes/og-measure/sample/scenes/models.ts +34 -0
  413. package/themes/og-measure/sample/scenes/one.ts +39 -0
  414. package/themes/og-measure/sample/scenes/price.ts +56 -0
  415. package/themes/og-measure/sample/style.json +97 -0
  416. package/themes/og-measure/slots.ts +88 -0
  417. package/themes/og-measure/still.png +0 -0
  418. package/themes/og-measure/style.json +97 -0
  419. package/themes/og-measure/theme.json +79 -0
  420. package/themes/riso-zine/THEME.md +43 -0
  421. package/themes/riso-zine/effects.ts +321 -0
  422. package/themes/riso-zine/sample/assets/og/logo.svg +12 -0
  423. package/themes/riso-zine/sample/beats.json +1 -0
  424. package/themes/riso-zine/sample/index.html +11 -0
  425. package/themes/riso-zine/sample/kf.json +59 -0
  426. package/themes/riso-zine/sample/lib/og.ts +29 -0
  427. package/themes/riso-zine/sample/lib/shot.ts +86 -0
  428. package/themes/riso-zine/sample/lib/zine.ts +42 -0
  429. package/themes/riso-zine/sample/main.ts +9 -0
  430. package/themes/riso-zine/sample/scenes/end.ts +61 -0
  431. package/themes/riso-zine/sample/scenes/gather.ts +62 -0
  432. package/themes/riso-zine/sample/scenes/models.ts +52 -0
  433. package/themes/riso-zine/sample/scenes/price.ts +82 -0
  434. package/themes/riso-zine/sample/style.json +99 -0
  435. package/themes/riso-zine/slots.ts +81 -0
  436. package/themes/riso-zine/still.png +0 -0
  437. package/themes/riso-zine/style.json +99 -0
  438. package/themes/riso-zine/theme.json +75 -0
  439. package/tools/beats/beats.py +372 -0
  440. package/tools/beats/requirements.txt +3 -0
  441. package/tools/lyrics/align.py +164 -0
  442. package/tools/lyrics/requirements.txt +9 -0
@@ -0,0 +1,383 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { captionMode } from "@kasaframe/motion/captions";
3
+ import { bundleFor, MENU, type Dress } from "@kasaframe/motion/menu";
4
+ import { questionsMarkdown, type Question } from "@kasaframe/gen";
5
+ import { loadTheme } from "@kasaframe/themes";
6
+ import type { GenSpec, ShotSpec } from "./types.ts";
7
+ import { join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const repo = fileURLToPath(new URL("../../../", import.meta.url));
11
+
12
+ export interface ProjectScene {
13
+ id: string;
14
+ from: number;
15
+ dur: number;
16
+ file: string;
17
+ role: string;
18
+ idea: string;
19
+ assets?: string[];
20
+ // kf new 가 고른 연출 수 — 뼈대 장면 파일의 DRESS 와 같다
21
+ dress?: Dress;
22
+ }
23
+
24
+ export interface ProjectFile {
25
+ template: string;
26
+ theme?: string;
27
+ title: string;
28
+ fps: number;
29
+ width: number;
30
+ height: number;
31
+ duration: number;
32
+ music: string | null;
33
+ beats: string | null;
34
+ captions: string;
35
+ render?: { motionBlur?: number; shutter?: number };
36
+ // kf narrate 가 적는다 — voice 가 있으면 내레이션 단계를 지났다
37
+ voice?: string;
38
+ narration?: string | null;
39
+ safeArea: { top: number; bottom: number; left: number; right: number };
40
+ text: { policy: string; maxOnScreen: number; minPx: number };
41
+ scenes: ProjectScene[];
42
+ }
43
+
44
+ export function readProject(dir: string): ProjectFile {
45
+ return JSON.parse(readFileSync(join(dir, "kf.json"), "utf8"));
46
+ }
47
+
48
+ // 모델이 부품 이름을 지어내지 않게 실제로 내보낸 함수의 첫 줄(인자·반환 타입)만 모은다
49
+ function signatures(pkg: string): string[] {
50
+ const src = join(repo, "packages", pkg, "src");
51
+ if (!existsSync(src)) return [];
52
+ const out: string[] = [];
53
+ for (const f of readdirSync(src).filter((x) => x.endsWith(".ts") && !x.endsWith(".test.ts") && x !== "index.ts" && x !== "captions.ts")) {
54
+ const text = readFileSync(join(src, f), "utf8");
55
+ for (const m of text.matchAll(/^export (?:function|const) (\w+)[^\n]*/gm)) {
56
+ const line = m[0].replace(/\s*\{\s*$/, "").replace(/^export (function|const) /, "");
57
+ if (line.length < 220) out.push(line);
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ const TEXT_POLICY: Record<string, string> = {
64
+ none: "화면에 문장을 쓰지 않는다. 기능 낱말 하나·제품 이름·끝 카드만.",
65
+ "terms-only": "화면 글은 전문 낱말과 숫자만(한 번에 몇 개까지). 말은 자막 트랙으로 따로 간다.",
66
+ captions: "소리 없이도 읽히게 — 들릴 말은 captions.json 에 쓰면 엔진이 화면 아래 자막 판에 얹는다. 장면 글은 핵심 낱말만.",
67
+ lyrics: "가사는 키네틱 타이포로 — 부른 낱말이 부른 시각에 뜨고, 뜻을 그림·움직임으로 받친다. 슬로건 금지는 가사에 걸지 않는다.",
68
+ };
69
+
70
+ function sceneBeats(dir: string, kf: ProjectFile, s: ProjectScene): string {
71
+ if (!kf.beats || !existsSync(join(dir, kf.beats))) return "곡 없음";
72
+ const b = JSON.parse(readFileSync(join(dir, kf.beats), "utf8"));
73
+ const inside = (xs: number[] = []) => xs.filter((t) => t >= s.from && t < s.from + s.dur).map((t) => +(t - s.from).toFixed(2));
74
+ const drops = inside(b.drops);
75
+ const bars = inside(b.bars ?? b.downbeats);
76
+ return `${b.bpm?.toFixed?.(1) ?? "?"} BPM · 장면 안 드롭 ${drops.length ? drops.join(", ") + "초" : "없음"} · 마디 ${bars.slice(0, 12).join(", ")}${bars.length > 12 ? " …" : ""}초`;
77
+ }
78
+
79
+ interface PurposeRules {
80
+ staticSec: { warn: number; error: number };
81
+ beatMove: "every-beat" | "drop-sections";
82
+ maxFreezeSec: number;
83
+ }
84
+
85
+ // 틀(templates/<용도>/template.json motion)이 기본값을 덮는다 — kf check 도 같은 값을 읽는다
86
+ function purposeRules(id: string): PurposeRules {
87
+ const file = join(repo, "templates", id, "template.json");
88
+ const m = existsSync(file) ? (JSON.parse(readFileSync(file, "utf8")).motion ?? {}) : {};
89
+ return { staticSec: m.staticSec ?? { warn: 1.5, error: 3 }, beatMove: m.beatMove ?? "every-beat", maxFreezeSec: m.maxFreezeSec ?? 0.1 };
90
+ }
91
+
92
+ // 용도별 표 — 같은 값을 쓰는 틀끼리 한 줄로
93
+ export function purposeTable(): string[] {
94
+ const dir = join(repo, "templates");
95
+ const ids = existsSync(dir) ? readdirSync(dir).filter((d) => existsSync(join(dir, d, "template.json"))) : [];
96
+ const groups = new Map<string, string[]>();
97
+ for (const id of ids) {
98
+ const r = purposeRules(id);
99
+ const key = `같은 구도 ${r.staticSec.warn}초 경고·${r.staticSec.error}초 오류 · 큰 움직임 ${r.beatMove === "every-beat" ? "박마다" : "후렴·드롭 구간만(벌스는 화면 안 움직임으로 충분)"} · 정지 ${r.maxFreezeSec}초 금지`;
100
+ groups.set(key, [...(groups.get(key) ?? []), id]);
101
+ }
102
+ return [...groups].map(([k, v]) => `- ${v.join("·")}: ${k}`);
103
+ }
104
+
105
+ function dropSections(dir: string, kf: ProjectFile): string {
106
+ if (!kf.beats || !existsSync(join(dir, kf.beats))) return "곡이 없어 드롭 구간도 없다 — 화면 안 움직임으로 간다";
107
+ const b = JSON.parse(readFileSync(join(dir, kf.beats), "utf8"));
108
+ const secs: { t: number; kind: string }[] = b.sections ?? [];
109
+ const spans: { t: number; to: number }[] = [];
110
+ secs.forEach((x, i) => {
111
+ if (x.kind !== "drop") return;
112
+ const to = secs[i + 1]?.t ?? kf.duration;
113
+ const last = spans[spans.length - 1];
114
+ if (last && Math.abs(last.to - x.t) < 0.05) last.to = to;
115
+ else spans.push({ t: x.t, to });
116
+ });
117
+ return spans.length ? spans.map((x) => `${x.t.toFixed(2)}–${x.to.toFixed(2)}초`).join(", ") : "드롭 구간이 잡히지 않았다";
118
+ }
119
+
120
+ // 장면을 채우는 모델(Claude Code 든 OG 의 아무 모델이든)이 읽는 한 장. 사용서(docs/AGENTS.md)가 있으면 뒤에 붙인다
121
+ export function projectBrief(dir: string): string {
122
+ const kf = readProject(dir);
123
+ const style = JSON.parse(readFileSync(join(dir, "style.json"), "utf8"));
124
+ const plan = existsSync(join(dir, "plan.json")) ? JSON.parse(readFileSync(join(dir, "plan.json"), "utf8")) : {};
125
+ const rules = purposeRules(kf.template);
126
+ const tplFile = join(repo, "templates", kf.template, "template.json");
127
+ const tplMotion = existsSync(tplFile) ? (JSON.parse(readFileSync(tplFile, "utf8")).motion ?? {}) : {};
128
+ const colors = Object.keys(style.colors ?? {}).map((k: string) => `--kf-${k.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`);
129
+ const core = signatures("core");
130
+ const motion = signatures("motion");
131
+ const agents = join(repo, "docs", "AGENTS.md");
132
+ const lines = [
133
+ `# ${kf.title || plan.request || "kasaframe 프로젝트"} — 장면 작성 지시서`,
134
+ "",
135
+ ...questionsMarkdown(plan.asks ?? [], (flags) => `npx kf make --answer ${dir} ${flags}`),
136
+ ...narrationSection(dir, kf, plan),
137
+ `**이 영상의 규칙 — 용도 ${kf.template}(틀이 기본값을 덮는다, kf check 가 같은 값으로 잡는다)**`,
138
+ `1. 화면은 ${rules.maxFreezeSec}초(60fps 6프레임) 넘게 멈추지 않는다 — 끝 카드·큰 숫자·로고도 예외 없이 화면 안 움직임(숫자 굴러가기·칸 채우기·반짝임·글자 흐름·바탕 흐름)으로 계속 살아 있게.`,
139
+ `2. 같은 구도는 ${rules.staticSec.warn}초 넘기지 않는다(${rules.staticSec.error}초 넘으면 오류) — 새 요소·칸 채우기·숫자 키우기·컷·전환으로 바꾼다.`,
140
+ rules.beatMove === "every-beat"
141
+ ? "3. 미세하게 흔들리는 것만으로 때우지 않는다 — 눈에 보이는 큰 움직임이 박마다 하나 이상."
142
+ : `3. 눈에 보이는 큰 움직임을 박마다 요구하는 건 후렴·드롭 구간(beats.json sections 의 drop — ${dropSections(dir, kf)})뿐이다. 벌스·브레이크는 화면 안 움직임(가사·숫자·바탕 흐름)으로 충분하다 — 절제했다가 드롭에서 터뜨린다.`,
143
+ "4. **카메라는 이유가 있을 때만** 움직인다 — 기본은 정지, 주인공은 안전 영역 가운데. 드러냄에 다가가기(`stage.camera.pushIn`)·비교에 물러나기(`pullOut`)·주인공 따라가기(`follow`)·드롭에 한 번 툭(`impact`), 끝나면 선다. 화면 전체를 사인으로 흔들거나 박마다 자세를 바꾸지 않는다(kf check `camera.wander`·`layout.edge`). 회전·원근 기울기는 0.",
144
+ "5. **섬광은 1초에 3번까지**(kf check frame.flash — 빛 민감 발작). 화면 25% 넘게 번쩍이는 건 두 박·한 마디에 한 번, 빨강 전체 깜빡임·대비 큰 줄무늬 연타·어둠↔흰 화면 컷 이어 붙이기는 쓰지 않는다.",
145
+ "",
146
+ "용도별 기본값(templates/<용도>/template.json motion):",
147
+ ...purposeTable(),
148
+ "",
149
+ "그림 보기: `npx kf look <폴더>` 가 찍는 장면 모음(out/look-sheet.png)을 Read 로 직접 보고 장면별 확인표(out/look.md)대로 고친다. **Read 로 그림이 안 보이면(눈 없는 모델)만** `npx kf look <폴더> --vision-model google/gemini-3.5-flash [--ask \"<볼 것>\"]` — 그림 보는 모델이 장면마다 글로 알려 준다.",
150
+ "",
151
+ `요청: ${plan.request ?? "(없음)"}`,
152
+ `용도 ${kf.template} · ${kf.width}×${kf.height} · ${kf.fps}fps · ${kf.duration}초 · 스타일 ${style.name}(${style.scheme}) · 곡 ${kf.music ?? "없음"} · 자막 ${kf.captions}`,
153
+ `안전 영역(글은 이 안에): 위 ${kf.safeArea.top}px · 아래 ${kf.safeArea.bottom}px · 왼쪽 ${kf.safeArea.left}px · 오른쪽 ${kf.safeArea.right}px`,
154
+ `화면 글: ${TEXT_POLICY[kf.text?.policy] ?? kf.text?.policy} 글자 크기 최소 ${kf.text?.minPx}px.`,
155
+ `움직임 규칙(style.json motion 위에 틀 motion): ${JSON.stringify({ ...style.motion, ...tplMotion })}`,
156
+ ...(style.notes?.length ? ["디자인 메모: " + style.notes.join(" / ")] : []),
157
+ "",
158
+ "## 장면 파일 꼴 — 이 꼴만 쓴다",
159
+ "",
160
+ "```ts",
161
+ 'import { applyStyle, clip, ease, type SceneCtx } from "@kasaframe/core";',
162
+ "export default {",
163
+ " build(root: HTMLElement, ctx: SceneCtx) {",
164
+ " const box = document.createElement(\"div\"); // root.style.display 는 엔진이 켜고 끈다 — 배치는 box 에",
165
+ " box.style.cssText = \"position:absolute;inset:0;overflow:hidden;background:var(--kf-bg)\";",
166
+ " root.append(box);",
167
+ " // DOM 은 여기서 한 번만 짓는다",
168
+ " return (t: number) => { /* t = 장면 안 초(0..ctx.dur). 같은 t 면 같은 화면 */ };",
169
+ " },",
170
+ "};",
171
+ "```",
172
+ "",
173
+ "- 시각 함수 밖에서 상태를 쌓지 않는다(앞 프레임 값을 기억하지 않는다). 탭 여러 개가 아무 시각이나 찍는다.",
174
+ "- `Math.random`·`Date.now`·`performance.now`·`setTimeout` 금지 — 난수는 `rand(seed, i)`, 시간은 `t` 만.",
175
+ "- 글자에 `undefined`·`NaN`·`null` 이 찍히지 않게 값은 미리 문자열로 만든다. 이모지 금지(아이콘은 인라인 SVG).",
176
+ "- 장면이 1초 넘게 멈추지 않게 한다(아주 느린 다가가기라도). 빈 화면으로 시작·끝내지 않는다.",
177
+ "- **화면을 채운다**(kf check layout.fill): 주 요소가 안전 영역을 크게 차지한다 — 세로 화면은 폭의 70% 넘게, 글은 큰 글자 단계(hero·title)로. 가운데 작은 그림 하나에 나머지가 흰 여백이면 실패다.",
178
+ "- 구도 판정(kf check layout.static)은 칸 평균 밝기와 화면 안 내용(글·칸의 센 가장자리)으로 본다 — 숫자가 구르고 칸이 차면 바뀐 것이다. 카메라 샷이나 떠다니는 장식으로 메우지 않는다.",
179
+ "- `../lib/stage.ts` 의 `makeStage(root, ctx, { dress })`(정지 바탕·연출 수·정지 카메라 `stage.camera`)·`stage.say(낱말)`(글 수 + 박마다 차는 강조 띠)·`makeCamera(layer, ctx, { frame })`(자기 카메라 층에)·`bigWord`·`fitPx`·`cutTimes`·`lastT` 를 써도 된다 — 위 규칙을 처음부터 넘게 짜 둔 도구다. 카메라가 요소를 겨누면 `stage.tick(t)` 은 내용을 칠한 뒤 맨 끝에.",
180
+ "- 박에 붙이는 움직임은 `ctx.beats`(장면 시작 0 기준: `ctx.beats.drops`, `ctx.beats.nearestBeat(t)`, `ctx.beats.pulse(t, {...})`). 시각을 BPM 으로 곱해 만들지 않는다.",
181
+ "- 자산은 `ctx.asset(\"gen/<id>.png\")` 처럼 assets/ 기준 경로로. 없는 파일을 가리키지 않는다.",
182
+ "- 색·글꼴·크기는 CSS 변수로: " + [...colors, "--kf-font-sans", "--kf-font-display", "--kf-font-mono", "--kf-size-hero", "--kf-size-title", "--kf-size-body", "--kf-size-caption", "--kf-radius-md", "--kf-shadow-soft"].join(" "),
183
+ "",
184
+ "## 쓸 수 있는 함수",
185
+ "",
186
+ "`@kasaframe/core`:",
187
+ ...core.map((s) => `- ${s}`),
188
+ "",
189
+ ...(motion.length ? ["`@kasaframe/motion`(움직임 부품 — 숫자만 넣으면 Style 이나 상태를 돌려준다. applyStyle 로 붙인다):", ...motion.map((s) => `- ${s}`), ""] : []),
190
+ ...dressSection(kf),
191
+ "## 장면",
192
+ "",
193
+ ...kf.scenes.flatMap((s) => [
194
+ `### ${s.id} — ${s.file} (${s.from.toFixed(2)}–${(s.from + s.dur).toFixed(2)}초, ${s.dur.toFixed(2)}초, 역할 ${s.role})`,
195
+ `- 할 일: ${s.idea}`,
196
+ ...(s.role === "seam" ? ["- 이 장면 파일은 고치지 않는다 — 3D 전환(transition3d)이 앞뒤 화면을 이어 준다"] : []),
197
+ ...(s.dress ? [`- 연출 수(뼈대에 걸려 있다): ${dressLine(s.dress)}`] : []),
198
+ `- 박: ${sceneBeats(dir, kf, s)}`,
199
+ ...(s.assets?.length ? [`- 자산: ${s.assets.map((a) => a.replace(/^gen:(img|vid):(.+)$/, (_, k, id) => `assets/gen/${id}.${k === "img" ? "png" : "mp4"}`)).join(", ")}`] : []),
200
+ "",
201
+ ]),
202
+ ...genSection(dir, kf, plan),
203
+ ...videoSection(dir, kf, plan),
204
+ ...shotSection(dir, kf, plan),
205
+ ...themeSection(kf.theme, plan),
206
+ ...(existsSync(join(dir, "lyrics.json"))
207
+ ? ["## 가사(lyrics.json — 낱말 시각까지)", "", '`import.meta.glob("../lyrics.json", { eager: true, import: "default" })` 로 읽는다(꼴 `{ lines: [{ text, start, end, words?: [{ w, start, end }] }] }`, 시각은 영상 전체 기준 초 — 장면 안 시각은 `start - ctx.from`). 낱말이 없으면 줄을 글자 수 비율로 나눈다.', ""]
208
+ : []),
209
+ ...(existsSync(join(dir, "assets", "in"))
210
+ ? ["## 받은 자산(사람이 준 것 — 쓸 수 있으면 쓴다)", "", ...readdirSync(join(dir, "assets", "in")).map((f) => `- \`ctx.asset("in/${f}")\``), ""]
211
+ : []),
212
+ ...(captionMode(kf.captions) !== "none"
213
+ ? [
214
+ "## 자막",
215
+ "",
216
+ '들릴 말은 프로젝트 `captions.json` 에 `[{"scene": "장면 id", "text": "짧은 마디를 쉼표로"}]` 로 쓴다 — `kf make --finish` 가 out/captions.ko.srt·vtt 로 만든다. 장면 파일에는 문장을 쓰지 않는다.',
217
+ captionMode(kf.captions) === "on"
218
+ ? `이 영상은 자막을 화면에도 얹는다 — 엔진이 장면 위에 판을 깐다(안전 영역 아래쪽 가운데, 두 줄까지). 그 자리(아래 가장자리에서 ${kf.safeArea.bottom}px 위 언저리)에 장면의 큰 글을 두지 않는다.`
219
+ : "화면에는 얹지 않는다(kf render --captions on 이면 그 판만 얹는다).",
220
+ "",
221
+ ]
222
+ : []),
223
+ ...(existsSync(agents) ? ["## 사용서(docs/AGENTS.md)", "", readFileSync(agents, "utf8")] : []),
224
+ ];
225
+ return lines.join("\n");
226
+ }
227
+
228
+ // 내레이션을 켠 틀(쇼츠·설명) — 장면보다 말이 먼저다. 목소리 길이가 장면 길이를 정하니 장면은 kf narrate 뒤에 쓴다
229
+ function narrationSection(dir: string, kf: ProjectFile, plan: { voice?: string; answers?: Record<string, string>; asks?: { id: string }[] }): string[] {
230
+ const tplFile = join(repo, "templates", kf.template, "template.json");
231
+ const tpl = existsSync(tplFile) ? JSON.parse(readFileSync(tplFile, "utf8")) : {};
232
+ if (!tpl.narration?.on) return [];
233
+ const asked = (id: string) => plan.asks?.some((q) => q.id === id);
234
+ const voice = kf.voice ?? plan.voice ?? plan.answers?.voice;
235
+ const music = kf.music ?? plan.answers?.music;
236
+ const done = kf.voice !== undefined;
237
+ const mix = tpl.audio ? `말할 때 곡은 목소리보다 ${-tpl.audio.musicDb}dB 안팎 아래(덕킹), 끝 ${tpl.audio.fadeOut}초 페이드, ${tpl.audio.lufs} LUFS — kf render 가 섞는다` : "";
238
+ return [
239
+ `## 내레이션 먼저 — 장면보다 앞${done ? "(됐다)" : ""}`,
240
+ "",
241
+ `목소리 ${asked("voice") ? "아직 — 맨 위 질문의 답" : (voice ?? "틀 기본")} · 곡 ${asked("music") ? "아직 — 맨 위 질문의 답" : (music ?? "없음")}${tpl.mood ? `(분위기 ${tpl.mood})` : ""}${mix ? ` · ${mix}` : ""}`,
242
+ "",
243
+ ...(done
244
+ ? [
245
+ `kf narrate 를 지났다 — 장면 시각(kf.json)이 목소리 길이에 맞춰졌고 captions.json 에 낱말 시각(\`words\`, 영상 기준 초)이 있다. 문장을 고치면 \`npx kf narrate ${dir}\` 를 다시 부른다(같은 문장은 캐시라 0원).`,
246
+ ]
247
+ : [
248
+ `1. \`captions.json\` 에 장면마다 들릴 말을 쓴다 — \`[{"scene": "hook", "text": "..."}]\`. 차분한 설명체, 짧은 마디는 쉼표로, 문장은 마침표로. 한국어 차분한 낭독은 1초에 4–5글자(띄어쓰기·문장부호 빼고) — 지금 장면 길이에 가깝게, 훅은 한 마디(2초 안). 끝 장면·이음매는 비워도 된다. 말은 소리·자막으로 가고 화면 글 정책은 그대로다.`,
249
+ `2. \`npx kf narrate ${dir}\` — 장면마다 읽혀 장면 길이 = 앞 숨 ${tpl.narration.lead}초 + 말 + 뒤 숨 ${tpl.narration.tail}초 로 맞추고(곡이 있으면 다음 박에 붙인다), 자막 시각을 목소리 시각으로 적는다. 요청에 길이가 있으면 빠르기를 0.9–1.1 안에서만 맞춘다.`,
250
+ "3. 그다음 장면을 쓴다 — kf.json 장면 시각이 바뀌었으니 다시 읽는다(장면은 `ctx.dur` 로 짠다).",
251
+ ]),
252
+ "- 말과 화면을 맞출 때: captions.json 의 `words` 가 영상 기준 초다 — 그 낱말이 들리는 순간에 같은 낱말·도식이 서게(`w.from - ctx.from` 이 장면 안 시각). `import.meta.glob(\"../captions.json\", { eager: true, import: \"default\" })` 로 읽는다.",
253
+ "",
254
+ ];
255
+ }
256
+
257
+ function dressLine(d: Dress): string {
258
+ const parts = [`바탕 \`${d.bg}\``, `글 \`${d.text}\``];
259
+ if (d.wipeIn !== "none") parts.push(`들어올 때 \`${d.wipeIn}\`가 걷힌다`);
260
+ if (d.wipeOut !== "none") parts.push(`나갈 때 \`${d.wipeOut}\`로 덮는다`);
261
+ return parts.join(" · ");
262
+ }
263
+
264
+ // 대충 한 요청에서도 연출이 밋밋하지 않게 — 뼈대가 이미 건 수를 알리고, 바꿀 때 고를 메뉴(이 용도·테마 묶음 먼저)를 준다
265
+ function dressSection(kf: ProjectFile): string[] {
266
+ const tplFile = join(repo, "templates", kf.template, "template.json");
267
+ const tpl = existsSync(tplFile) ? JSON.parse(readFileSync(tplFile, "utf8")) : {};
268
+ if (!tpl.moves) return [];
269
+ const b = bundleFor(tpl.moves, kf.theme);
270
+ const blur = kf.render?.motionBlur ?? 0;
271
+ const kinds = [
272
+ ["bg", "바탕 — 정지만(뜻 없이 떠도는 장식은 쓰지 않는다)", b.bg],
273
+ ["text", "글 — 안전 영역 안, 박마다 내용이 바뀐다(낱말을 WORDS 에)", b.text],
274
+ ["wipe", "경계 덮기 — 앞 장면 끝을 덮고 뒤 장면 처음에 걷힌다. 두 장면 파일의 wipeOut·wipeIn 을 같은 이름으로", b.wipe],
275
+ ] as const;
276
+ return [
277
+ "## 연출 수 — 뼈대에 이미 걸려 있다",
278
+ "",
279
+ `\`kf new\` 가 장면마다 바탕·글·경계 덮기를 이 용도${kf.theme ? `·테마(${kf.theme})` : ""} 묶음에서 골라 장면 파일 \`DRESS\` 에 넣었다(요청 글이 씨앗 — 같은 요청이면 같은 수). 장면을 채울 때 **수는 두고 낱말(\`WORDS\`)과 내용을 바꾼다** — 수가 쉬지 않기·같은 구도·화면 채움 검사를 이미 넘는다. **화면에서 움직이는 것은 모두 내용 하나를 뜻한다** — 떠다니는 방울·판·큐브·흩뿌린 입자 같은 뜻 없는 장식은 넣지 않고, 쉬지 않기는 숫자 굴러가기·칸 채우기·연결선·강조로 채운다. 바꾸려면 아래 이름으로, 이음매(seam)는 3D 전환(dive 2판 — 화면 속으로 빠르게 다이브)이 맡는다.`,
280
+ ...(blur > 1 ? [`굽기: 모션 블러 ${blur}장·셔터 ${kf.render?.shutter ?? 180}°(kf.json \`render\`) — 빠르게 움직이는 것만 번지고 서 있는 글은 그대로다. 손으로 끌 땐 \`kf render --motion-blur 0\`.`] : []),
281
+ "",
282
+ ...kinds.flatMap(([kind, head, mine]) => [
283
+ `- ${head}: ${MENU.filter((m) => m.kind === kind)
284
+ .sort((x, y) => Number(!mine.includes(x.name)) - Number(!mine.includes(y.name)))
285
+ .map((m) => `${mine.includes(m.name) ? "**" : ""}\`${m.name}\`${mine.includes(m.name) ? "**" : ""} ${m.does}`)
286
+ .join(" · ")}`,
287
+ ]),
288
+ "",
289
+ "굵은 이름이 이 영상의 묶음이다. 수만 따로 보려면 `examples/moves/<이름>/`, 메뉴 밖 수(곡선 글·도형 변신·만화 칸·찢기…)까지 전체는 `examples/moves/README.md`. 장면을 직접 짤 때도 `import { slamWords, sunburst, … } from \"@kasaframe/motion/moves\"` 로 같은 수를 쓴다.",
290
+ "",
291
+ ];
292
+ }
293
+
294
+ // 테마를 골랐으면 그 비유·금지·효과 쓰는 꼴을 그대로 싣는다(SPEC 9). auto 인데 맞는 테마가 없으면 강한 모델이 새 테마를 draft 로 써 넣을 수 있다
295
+ function themeSection(id: string | undefined, plan: { themeCandidates?: { id: string; why: string }[]; request?: string; asks?: { id: string }[] }): string[] {
296
+ if (id) {
297
+ const t = loadTheme(id);
298
+ const pick = plan.themeCandidates?.find((c) => c.id === id);
299
+ return [
300
+ `## 테마 ${t.name}(${t.id}) — ${t.metaphor}`,
301
+ "",
302
+ ...(plan.asks?.some((q) => q.id === "theme") ? ["지금은 auto 로 깔아 둔 테마다 — 맨 위 질문의 답을 넣으면(`kf make --answer … --theme <id>`) 테마와 이 지시서가 바뀐다. 장면은 그다음에 쓴다.", ""] : []),
303
+ ...(pick ? [`고른 이유: ${pick.why}`, ""] : []),
304
+ `효과는 복사하지 말고 가져다 쓴다: \`import { fx } from "@kasaframe/themes/fx"; const { ${t.effects.map((e) => e.name).join(", ")} } = fx("${t.id}");\``,
305
+ "",
306
+ ...t.effects.map((e) => `- \`${e.name}\`(${e.kind}) — ${e.does} · \`${e.use}\``),
307
+ "",
308
+ ...(plan.themeCandidates && plan.themeCandidates.length > 1 ? [`다른 후보: ${plan.themeCandidates.slice(1).map((c) => c.id).join(" · ")} — 첫 장면을 각 테마로 찍은 그림은 out/theme-<id>.png`, ""] : []),
309
+ t.guide,
310
+ "",
311
+ ];
312
+ }
313
+ if (plan.themeCandidates && !plan.themeCandidates.length) {
314
+ return [
315
+ "## 테마 — 맞는 approved 테마가 없다",
316
+ "",
317
+ "프리셋 style 로 간다. 이 내용에 맞는 비유가 뚜렷하면 SPEC 계약 9 대로 `themes/<새 id>/` 에 테마 팩(theme.json status draft · THEME.md · style.json · effects.ts 5–10개)을 써 넣어도 된다 — 사람이 견본을 보고 OK 하기 전까지는 draft 라 이 프로젝트에는 쓰지 않는다.",
318
+ "",
319
+ ];
320
+ }
321
+ return [];
322
+ }
323
+
324
+ type BriefPlan = { gen?: GenSpec[]; shots?: ShotSpec[]; asks?: Question[]; answers?: Record<string, string>; aspect?: string };
325
+
326
+ const pending = (plan: BriefPlan, id: string) => (plan.asks ?? []).some((q) => q.id === id);
327
+
328
+ // 막연한 자동 프롬프트는 엉뚱한 그림을 냈다 — 아직 없는 그림은 목록으로 내고 세션이 구체적인 프롬프트를 써서 고른 공급자로 만든다
329
+ function genSection(dir: string, kf: ProjectFile, plan: BriefPlan): string[] {
330
+ const todo = (plan.gen ?? []).filter((g) => g.kind === "img" && !existsSync(join(dir, "assets", "gen", `${g.id}.png`)));
331
+ if (!todo.length) return [];
332
+ const provider = plan.answers?.image;
333
+ return [
334
+ pending(plan, "image") ? "## 만들 그림 — 사람이 그림 생성을 고른 뒤에(맨 위 「사용자에게 먼저 물을 것」)" : `## 만들 그림 — ${provider ?? "기본 공급자"}로(고른 답)`,
335
+ "",
336
+ "받은 자산·실제 화면 오리기·도형으로 되면 만들지 않는다. 만들 땐 요청 내용으로 **구체적인** 프롬프트를 써서(무엇이·어떤 구도로·어떤 색·글자 없음):",
337
+ "",
338
+ ...todo.map((g) => `- \`${g.id}\`(장면 ${g.scene ?? "-"}) — 틀이 제안한 뜻: ${g.prompt}\n \`npx kf gen img "<구체적 프롬프트>" --id ${g.id} --size ${g.size ?? `${kf.width}x${kf.height}`}${provider ? ` --provider ${provider}` : ""} --project ${dir}\` → \`ctx.asset("gen/${g.id}.png")\``),
339
+ "",
340
+ ];
341
+ }
342
+
343
+ // 캐릭터가 실제로 움직여야 하는 컷 — 영상 모델은 돈이 들어 고른 답이 있을 때만 부른다. 이음매 영상은 kf make --finish 가 앞뒤 화면으로 만든다
344
+ function videoSection(dir: string, kf: ProjectFile, plan: BriefPlan): string[] {
345
+ const cuts = (plan.gen ?? []).filter((g) => g.kind === "vid" && !g.seam && !existsSync(join(dir, "assets", "gen", `${g.id}.mp4`)));
346
+ if (!cuts.length) return [];
347
+ const provider = plan.answers?.video;
348
+ const aspect = plan.aspect ?? (kf.width > kf.height ? "16:9" : kf.width < kf.height ? "9:16" : "1:1");
349
+ return [
350
+ pending(plan, "video") ? "## 영상 모델 컷 — 사람이 영상 모델을 고른 뒤에(맨 위 「사용자에게 먼저 물을 것」)" : `## 영상 모델 컷 — ${provider}로(고른 답, 돈이 든다)`,
351
+ "",
352
+ "캐릭터가 실제로 움직여야 하는 컷이다. 그림을 먼저 만들었으면 그걸 첫 프레임으로 준다. 받은 클립은 장면에 `<video data-kf-from=\"<장면 시작 초>\" muted playsinline>` 로 둔다(장면이 영상보다 길면 다른 화면 안 움직임으로 잇는다).",
353
+ "",
354
+ ...cuts.map((g) => `- \`${g.id}\`(장면 ${g.scene}) — ${g.prompt}\n \`npx kf gen vid "<구체적 프롬프트>" --id ${g.id} --dur ${g.dur ?? 5} --aspect ${aspect}${g.first ? ` --first ${dir}/${g.first}` : ""}${provider ? ` --provider ${provider}` : ""} --project ${dir} --yes\` → \`ctx.asset("gen/${g.id}.mp4")\``),
355
+ "",
356
+ ];
357
+ }
358
+
359
+ // 주인공 3D 장면 — Blender 가 있으면 툰 샷을 굽고, 없어서 three 로 대신하기로 했으면 장면 안에서 stage3d 로 짓는다
360
+ function shotSection(dir: string, kf: ProjectFile, plan: BriefPlan): string[] {
361
+ const shots = plan.shots ?? [];
362
+ if (!shots.length) return [];
363
+ const out = ["## 주인공 3D 장면", ""];
364
+ for (const s of shots) {
365
+ const sc = kf.scenes.find((x) => x.id === s.scene);
366
+ const span = sc ? `${sc.from.toFixed(2)}–${(sc.from + sc.dur).toFixed(2)}초` : "";
367
+ const meta = join(dir, "assets", "blender", `${s.id}.json`);
368
+ if (s.tool === "blender" && existsSync(meta)) {
369
+ const m = JSON.parse(readFileSync(meta, "utf8"));
370
+ out.push(`- \`${s.scene}\`(${span}) — 구워 둠: \`<video src="\${ctx.asset("blender/${s.id}.mp4")}" data-kf-from="${m.dataKfFrom}" muted playsinline>\` 를 장면 바탕(또는 주인공 판)으로 두고 그 위에 낱말·숫자를 얹는다`);
371
+ } else if (s.tool === "blender") {
372
+ out.push(
373
+ `- \`${s.scene}\`(${span}) — ${s.idea}`,
374
+ ` 1. \`${dir}/${s.file}\` 를 \`tools/blender/kfb.py\` 헬퍼로 짠다 — 쓰는 법 \`tools/blender/README.md\`, 예 \`tools/blender/shots/lighthouse.py\`. 물체는 한 번 짓고 \`update(t)\` 는 t 만 본다, 카메라 열쇠는 \`S.downbeats\`·\`S.drops\` 에(도착하면 선다), 쉬지 않는 몫은 박에 뛰는 물체가 진다. 색은 style.json 잉크.`,
375
+ ` 2. \`npx kf blender ${dir}/${s.file} --project ${dir} --scene ${s.scene} --id ${s.id} --still <장면 안 두세 시각>\` 로 몇 장 보고 고친다`,
376
+ ` 3. \`npx kf blender ${dir}/${s.file} --project ${dir} --scene ${s.scene} --id ${s.id} --res 50 --step 2\` 로 굽는다(무겁다 — 한 번에) → 명령이 찍어 준 \`<video data-kf-from>\` 값으로 장면에 건다`,
377
+ );
378
+ } else {
379
+ out.push(`- \`${s.scene}\`(${span}) — ${s.idea}. Blender 가 없어 three.js 로 — \`import { stage3d, THREE } from "@kasaframe/themes/stage3d"\`: \`const st = stage3d(root, { ctx, shot: (t, cam) => { /* 카메라는 이유 있을 때만 */ } }); st.add((t) => { /* 물체 자세 */ });\` → \`(t) => st.paint(t)\``);
380
+ }
381
+ }
382
+ return [...out, ""];
383
+ }
@@ -0,0 +1,78 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { captionMetrics, captionMode, toCues, type CaptionLine, type CaptionMode, type Cue } from "@kasaframe/motion/captions";
4
+ import { readProject } from "./brief.ts";
5
+
6
+ // 자막 트랙 — srt(유튜브)·vtt(사이트). OG 쇼츠 scripts/srt3d.py 의 관례를 따른다.
7
+ // 마디 나누기는 화면 자막 판(@kasaframe/motion/captions)과 같은 toCues 라 트랙과 화면이 한 마디도 어긋나지 않는다.
8
+
9
+ export { toCues };
10
+ export type { CaptionLine, Cue };
11
+
12
+ function ts(s: number, sep: string): string {
13
+ const ms = Math.round(s * 1000);
14
+ const p = (n: number, w = 2) => String(n).padStart(w, "0");
15
+ return `${p(Math.floor(ms / 3600000))}:${p(Math.floor(ms / 60000) % 60)}:${p(Math.floor(ms / 1000) % 60)}${sep}${p(ms % 1000, 3)}`;
16
+ }
17
+
18
+ export function srt(cues: Cue[]): string {
19
+ return cues.map((c, i) => `${i + 1}\n${ts(c.from, ",")} --> ${ts(c.to, ",")}\n${c.text}\n`).join("\n");
20
+ }
21
+
22
+ export function vtt(cues: Cue[]): string {
23
+ return `WEBVTT\n\n${cues.map((c) => `${ts(c.from, ".")} --> ${ts(c.to, ".")}\n${c.text}\n`).join("\n")}`;
24
+ }
25
+
26
+ // 뮤비 가사. 시각이 붙은 lyrics.json(natsu_mv align_lyrics.py 꼴: {lines:[{text,start,end}]})이 있으면 그대로,
27
+ // 시각 없는 lyrics.txt 뿐이면 인트로·아웃트로를 뺀 구간에 줄 길이 비율로 대충 편다 — 정확한 시각은 보컬 분리(demucs) + 강제 정렬로(큰 모델이라 GPU 있는 기계에서)
28
+ function lyricLines(dir: string, scenes: { id: string; role: string; from: number; dur: number }[]): CaptionLine[] | null {
29
+ const find = (f: string) => [join(dir, f), join(dir, "assets", "in", f)].find((p) => existsSync(p));
30
+ const timed = find("lyrics.json");
31
+ if (timed) {
32
+ const j = JSON.parse(readFileSync(timed, "utf8")) as { lines?: { text: string; start: number; end: number; words?: { w: string; start: number; end: number }[] }[] };
33
+ return (j.lines ?? []).map((l) => ({ from: l.start, to: l.end, text: l.text, words: l.words?.map((w) => ({ text: w.w, from: w.start, to: w.end })) }));
34
+ }
35
+ const plain = find("lyrics.txt");
36
+ if (!plain) return null;
37
+ const lines = readFileSync(plain, "utf8").split("\n").map((l) => l.trim()).filter(Boolean);
38
+ const sung = scenes.filter((s) => !/intro|outro/.test(s.role));
39
+ const total = sung.reduce((a, s) => a + s.dur, 0);
40
+ const chars = lines.reduce((a, l) => a + l.length, 0);
41
+ const out: CaptionLine[] = [];
42
+ let k = 0;
43
+ let acc = 0;
44
+ for (const l of lines) {
45
+ let at = (acc / chars) * total;
46
+ while (k < sung.length - 1 && at > sung[k].dur) {
47
+ at -= sung[k].dur;
48
+ acc -= (sung[k].dur / total) * chars;
49
+ k++;
50
+ }
51
+ const from = sung[k].from + at;
52
+ out.push({ from, to: from + (l.length / chars) * total, text: l });
53
+ acc += l.length;
54
+ }
55
+ return out;
56
+ }
57
+
58
+ // 프로젝트의 captions.json(또는 가사) → out/captions.ko.srt·vtt. 자막 none 이거나 들릴 말이 없으면 빈 배열. mode 는 kf.json 을 그 판만 덮는다
59
+ export function writeCaptions(dir: string, mode?: CaptionMode): string[] {
60
+ const kf = readProject(dir);
61
+ // captions 칸이 없는 옛 프로젝트는 들릴 말이 있으면 트랙만 낸다
62
+ if ((mode ?? (kf.captions === undefined ? "off" : captionMode(kf.captions))) === "none") return [];
63
+ const scenes = kf.scenes;
64
+ const file = join(dir, "captions.json");
65
+ const lines = existsSync(file) ? (JSON.parse(readFileSync(file, "utf8")) as CaptionLine[]) : lyricLines(dir, scenes);
66
+ if (!lines) return [];
67
+ // 화면 판과 같은 마디로 — style.json captions(글자 크기·줄 수)가 있으면 그 값으로 나눈다
68
+ const stylePath = join(dir, (kf as { style?: string }).style ?? "style.json");
69
+ const look = existsSync(stylePath) ? JSON.parse(readFileSync(stylePath, "utf8")).captions : undefined;
70
+ const m = kf.width && kf.height ? captionMetrics({ width: kf.width, height: kf.height, safe: kf.safeArea, minPx: kf.text?.minPx, look }) : undefined;
71
+ const cues = toCues(lines, scenes, { maxEm: m?.maxEm, duration: kf.duration, maxLines: m?.maxLines });
72
+ if (!cues.length) return [];
73
+ mkdirSync(join(dir, "out"), { recursive: true });
74
+ const out = [join(dir, "out", "captions.ko.srt"), join(dir, "out", "captions.ko.vtt")];
75
+ writeFileSync(out[0], srt(cues));
76
+ writeFileSync(out[1], vtt(cues));
77
+ return out;
78
+ }
@@ -0,0 +1,93 @@
1
+ import { mentions, parseAspect, parseDuration } from "./request.ts";
2
+ import type { Purpose, Template } from "./types.ts";
3
+
4
+ export interface DetectInput {
5
+ request: string;
6
+ assets?: string[]; // 받은 파일 경로들
7
+ music?: string | null;
8
+ }
9
+
10
+ export interface Ranked {
11
+ purpose: Purpose;
12
+ score: number;
13
+ p: number;
14
+ reasons: string[];
15
+ }
16
+
17
+ export interface Detection {
18
+ purpose: Purpose;
19
+ confidence: number;
20
+ ranked: Ranked[];
21
+ // 낱말이 하나도 안 걸렸거나 1·2위가 붙어 있으면 사람에게 묻는다
22
+ ambiguous: boolean;
23
+ }
24
+
25
+ const VIDEO = /\.(mp4|mov|webm|mkv|m4v)$/i;
26
+ const TEMPERATURE = 1.5;
27
+ export const ASK_BELOW = 0.5;
28
+
29
+ export function detect(input: DetectInput, templates: Template[]): Detection {
30
+ const { request } = input;
31
+ const dur = parseDuration(request);
32
+ const aspect = parseAspect(request);
33
+ const hasVideo = (input.assets ?? []).some((a) => VIDEO.test(a));
34
+ let anyWord = false;
35
+
36
+ const scored = templates.map((tpl) => {
37
+ let score = 0;
38
+ const reasons: string[] = [];
39
+ for (const w of tpl.detect.words) {
40
+ if (mentions(request, w)) {
41
+ score += 3;
42
+ anyWord = true;
43
+ reasons.push(`"${w}"`);
44
+ }
45
+ }
46
+ for (const w of tpl.detect.hints) {
47
+ if (mentions(request, w)) {
48
+ score += 1;
49
+ reasons.push(`"${w}"(힌트)`);
50
+ }
51
+ }
52
+ if (aspect) {
53
+ if (aspect === tpl.aspect) {
54
+ score += 2;
55
+ reasons.push(`화면 ${aspect}`);
56
+ } else if (tpl.variants?.[aspect]) score += 0.5;
57
+ else score -= 1.5;
58
+ }
59
+ if (dur !== undefined) {
60
+ const { min, max, allowed } = tpl.duration;
61
+ if (allowed?.includes(dur)) {
62
+ score += 2.5;
63
+ reasons.push(`${dur}초 = 고정 길이`);
64
+ } else if (dur >= min && dur <= max) {
65
+ score += 1.5;
66
+ reasons.push(`${dur}초가 ${min}–${max}초 안`);
67
+ } else score -= 2;
68
+ }
69
+ if (hasVideo && tpl.detect.assetKinds?.includes("video")) {
70
+ score += 2;
71
+ reasons.push("받은 자산에 영상");
72
+ }
73
+ if (input.music && tpl.music === "required") score += 0.5;
74
+ return { purpose: tpl.id, score, reasons };
75
+ });
76
+
77
+ const z = scored.map((s) => Math.exp(s.score / TEMPERATURE));
78
+ const sum = z.reduce((a, b) => a + b, 0);
79
+ const ranked: Ranked[] = scored
80
+ .map((s, i) => ({ ...s, p: round(z[i] / sum) }))
81
+ .sort((a, b) => b.score - a.score);
82
+ const top = ranked[0];
83
+ return {
84
+ purpose: top.purpose,
85
+ confidence: top.p,
86
+ ranked,
87
+ ambiguous: !anyWord || top.p < ASK_BELOW,
88
+ };
89
+ }
90
+
91
+ function round(x: number): number {
92
+ return Math.round(x * 100) / 100;
93
+ }
@@ -0,0 +1,27 @@
1
+ export type * from "./types.ts";
2
+ export { PURPOSES } from "./types.ts";
3
+ export { templatesDir, listTemplates, loadTemplate, frameFor, sceneSkeleton } from "./templates.ts";
4
+ export { parseAspect, parseDuration, parseStyle, parseTitle, mentions } from "./request.ts";
5
+ export { detect, ASK_BELOW } from "./detect.ts";
6
+ export type { Detection, Ranked, DetectInput } from "./detect.ts";
7
+ export { pickDuration, layoutScenes } from "./layout.ts";
8
+ export type { Layout, LaidScene } from "./layout.ts";
9
+ export { makePlan, refuses3d, wantsMotionCut, wantsVideo } from "./plan.ts";
10
+ export { applyAnswers, planQuestions, WAIT } from "./asks.ts";
11
+ export type { PlanQuestions } from "./asks.ts";
12
+ export type { PlanInput } from "./plan.ts";
13
+ export { chat, codeBlock, jsonBlock, ledger, premiumBlock, spend, spentLine, writeStats } from "./llm.ts";
14
+ export type { Message, Part, ChatResult, Spent } from "./llm.ts";
15
+ export { projectBrief, readProject } from "./brief.ts";
16
+ export type { ProjectFile, ProjectScene } from "./brief.ts";
17
+ export { toCues, srt, vtt, writeCaptions } from "./captions.ts";
18
+ export type { CaptionLine, Cue } from "./captions.ts";
19
+ export { suggestThemes, pickTheme, themeFromRequest } from "./themes.ts";
20
+ export type { ThemePick } from "./themes.ts";
21
+ export { look, lookSheet, formatLook, DEFAULT_EYES } from "./look.ts";
22
+ export type { LookSheet } from "./look.ts";
23
+ export type { LookReport, LookScene } from "./look.ts";
24
+ export { attachStudio } from "./studio.ts";
25
+ export type { StudioLink } from "./studio.ts";
26
+ export { checkSeries, listSeries, loadSeries, seriesDir, seriesFromRequest, writeSeries } from "./series.ts";
27
+ export type { Series } from "./series.ts";