kasaframe 0.0.0-stage → 0.1.1

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 (338) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +146 -0
  3. package/README.md +77 -2
  4. package/app/package.json +12 -0
  5. package/app/server/engine.ts +415 -0
  6. package/app/server/main.ts +205 -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 +1013 -0
  11. package/app/web/style.css +430 -0
  12. package/bin/hooks.mjs +51 -0
  13. package/bin/kf.mjs +14 -0
  14. package/docs/AGENTS.md +238 -0
  15. package/music/README.md +13 -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 +273 -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 +42 -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 +135 -0
  43. package/packages/check/src/rules/layout.ts +257 -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 +29 -0
  52. package/packages/cli/src/blur.ts +68 -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/blender.ts +270 -0
  58. package/packages/cli/src/commands/check.ts +25 -0
  59. package/packages/cli/src/commands/doctor.ts +22 -0
  60. package/packages/cli/src/commands/gen.ts +173 -0
  61. package/packages/cli/src/commands/look.ts +39 -0
  62. package/packages/cli/src/commands/lyrics.ts +111 -0
  63. package/packages/cli/src/commands/make.ts +542 -0
  64. package/packages/cli/src/commands/new.ts +176 -0
  65. package/packages/cli/src/commands/preview.ts +43 -0
  66. package/packages/cli/src/commands/render.ts +216 -0
  67. package/packages/cli/src/commands/setup.ts +63 -0
  68. package/packages/cli/src/commands/sheet.ts +114 -0
  69. package/packages/cli/src/commands/skill.ts +43 -0
  70. package/packages/cli/src/commands/still.ts +40 -0
  71. package/packages/cli/src/commands/theme.ts +85 -0
  72. package/packages/cli/src/commands/transitions.ts +40 -0
  73. package/packages/cli/src/deps.ts +122 -0
  74. package/packages/cli/src/ffmpeg.ts +81 -0
  75. package/packages/cli/src/index.ts +8 -0
  76. package/packages/cli/src/kf.ts +53 -0
  77. package/packages/cli/src/lock.ts +72 -0
  78. package/packages/cli/src/out.ts +13 -0
  79. package/packages/cli/src/serve.ts +124 -0
  80. package/packages/clock/package.json +14 -0
  81. package/packages/clock/src/index.ts +25 -0
  82. package/packages/clock/src/install.ts +351 -0
  83. package/packages/clock/src/types.ts +45 -0
  84. package/packages/core/package.json +9 -0
  85. package/packages/core/src/beats.ts +283 -0
  86. package/packages/core/src/ease.ts +87 -0
  87. package/packages/core/src/frame.ts +99 -0
  88. package/packages/core/src/index.ts +27 -0
  89. package/packages/core/src/project.ts +146 -0
  90. package/packages/core/src/rand.ts +56 -0
  91. package/packages/core/src/spring.ts +52 -0
  92. package/packages/core/src/style.ts +100 -0
  93. package/packages/core/src/time.ts +88 -0
  94. package/packages/gen/PROVIDERS.md +55 -0
  95. package/packages/gen/package.json +9 -0
  96. package/packages/gen/src/generate.ts +204 -0
  97. package/packages/gen/src/image/og.ts +133 -0
  98. package/packages/gen/src/index.ts +18 -0
  99. package/packages/gen/src/keys.ts +60 -0
  100. package/packages/gen/src/media.ts +150 -0
  101. package/packages/gen/src/plan.ts +106 -0
  102. package/packages/gen/src/registry.ts +51 -0
  103. package/packages/gen/src/seam.ts +103 -0
  104. package/packages/gen/src/types.ts +86 -0
  105. package/packages/gen/src/video/fal.ts +140 -0
  106. package/packages/gen/src/video/h3.ts +107 -0
  107. package/packages/gen/src/video/http.ts +44 -0
  108. package/packages/gen/src/video/ltx.ts +60 -0
  109. package/packages/gen/src/video/minimax.ts +55 -0
  110. package/packages/gen/src/video/veo.ts +71 -0
  111. package/packages/jizura/package.json +16 -0
  112. package/packages/jizura/src/deny.ts +73 -0
  113. package/packages/jizura/src/index.ts +7 -0
  114. package/packages/jizura/src/install.ts +226 -0
  115. package/packages/jizura/src/layer.ts +282 -0
  116. package/packages/jizura/src/lyrics.ts +81 -0
  117. package/packages/jizura/src/pin.ts +5 -0
  118. package/packages/jizura/src/vite.ts +36 -0
  119. package/packages/motion/README.md +142 -0
  120. package/packages/motion/package.json +29 -0
  121. package/packages/motion/src/camera.ts +97 -0
  122. package/packages/motion/src/captions.ts +479 -0
  123. package/packages/motion/src/enter.ts +128 -0
  124. package/packages/motion/src/hangul.ts +129 -0
  125. package/packages/motion/src/impact.ts +100 -0
  126. package/packages/motion/src/index.ts +17 -0
  127. package/packages/motion/src/moves/brushWipe.ts +90 -0
  128. package/packages/motion/src/moves/cloneArray.ts +111 -0
  129. package/packages/motion/src/moves/curtains.ts +81 -0
  130. package/packages/motion/src/moves/echoStack.ts +70 -0
  131. package/packages/motion/src/moves/fit.ts +34 -0
  132. package/packages/motion/src/moves/flood.ts +61 -0
  133. package/packages/motion/src/moves/ghostStack.ts +79 -0
  134. package/packages/motion/src/moves/gridFloor.ts +100 -0
  135. package/packages/motion/src/moves/imageSwarm.ts +109 -0
  136. package/packages/motion/src/moves/index.ts +19 -0
  137. package/packages/motion/src/moves/irisBlades.ts +113 -0
  138. package/packages/motion/src/moves/karaokeLine.ts +94 -0
  139. package/packages/motion/src/moves/ledBoard.ts +94 -0
  140. package/packages/motion/src/moves/liquidTab.ts +72 -0
  141. package/packages/motion/src/moves/markerWrite.ts +63 -0
  142. package/packages/motion/src/moves/morphBox.ts +96 -0
  143. package/packages/motion/src/moves/neonSign.ts +77 -0
  144. package/packages/motion/src/moves/panelPop.ts +78 -0
  145. package/packages/motion/src/moves/paperTear.ts +75 -0
  146. package/packages/motion/src/moves/path.ts +64 -0
  147. package/packages/motion/src/moves/pathText.ts +107 -0
  148. package/packages/motion/src/moves/pointSphere.ts +109 -0
  149. package/packages/motion/src/moves/ransomLetters.ts +73 -0
  150. package/packages/motion/src/moves/shapeMorph.ts +143 -0
  151. package/packages/motion/src/moves/slamWords.ts +89 -0
  152. package/packages/motion/src/moves/sparkTrail.ts +114 -0
  153. package/packages/motion/src/moves/speedLines.ts +91 -0
  154. package/packages/motion/src/moves/splitFlap.ts +147 -0
  155. package/packages/motion/src/moves/stepWords.ts +117 -0
  156. package/packages/motion/src/moves/strikeOut.ts +98 -0
  157. package/packages/motion/src/moves/sunburst.ts +58 -0
  158. package/packages/motion/src/moves/tickerRows.ts +87 -0
  159. package/packages/motion/src/moves/tokenPicker.ts +114 -0
  160. package/packages/motion/src/moves/truchet.ts +85 -0
  161. package/packages/motion/src/moves/wordWall.ts +76 -0
  162. package/packages/motion/src/moves/worldRoll.ts +31 -0
  163. package/packages/motion/src/particles.ts +102 -0
  164. package/packages/motion/src/react/index.tsx +21 -0
  165. package/packages/motion/src/react/parts.tsx +431 -0
  166. package/packages/motion/src/react/time.tsx +97 -0
  167. package/packages/motion/src/text.ts +103 -0
  168. package/packages/motion/src/transition.ts +82 -0
  169. package/packages/motion/src/transition3d/cards.ts +65 -0
  170. package/packages/motion/src/transition3d/chain.ts +84 -0
  171. package/packages/motion/src/transition3d/dive.ts +181 -0
  172. package/packages/motion/src/transition3d/fold.ts +110 -0
  173. package/packages/motion/src/transition3d/gl.ts +219 -0
  174. package/packages/motion/src/transition3d/index.ts +228 -0
  175. package/packages/motion/src/transition3d/live.ts +70 -0
  176. package/packages/motion/src/transition3d/portal.ts +115 -0
  177. package/packages/motion/src/transition3d/react.tsx +29 -0
  178. package/packages/motion/src/transition3d/rig.ts +502 -0
  179. package/packages/motion/src/transition3d/shatter.ts +101 -0
  180. package/packages/motion/src/transition3d/whip.ts +65 -0
  181. package/packages/motion/src/transition3d/world.ts +683 -0
  182. package/packages/motion/src/transition3d/zoomOut.ts +114 -0
  183. package/packages/motion/src/transitions2d/catalog.ts +127 -0
  184. package/packages/motion/src/transitions2d/css.d.ts +2 -0
  185. package/packages/motion/src/transitions2d/cult-ui/LICENSE.md +9 -0
  186. package/packages/motion/src/transitions2d/cult-ui/RollingNumber.tsx +43 -0
  187. package/packages/motion/src/transitions2d/gl-transitions/LICENSE +25 -0
  188. package/packages/motion/src/transitions2d/gl-transitions/shaders.ts +1931 -0
  189. package/packages/motion/src/transitions2d/index.ts +2 -0
  190. package/packages/motion/src/transitions2d/kokonutui/LICENSE +21 -0
  191. package/packages/motion/src/transitions2d/kokonutui/MatrixText.css +16 -0
  192. package/packages/motion/src/transitions2d/kokonutui/MatrixText.tsx +94 -0
  193. package/packages/motion/src/transitions2d/magicui/HyperText.css +15 -0
  194. package/packages/motion/src/transitions2d/magicui/HyperText.tsx +115 -0
  195. package/packages/motion/src/transitions2d/magicui/LICENSE.md +21 -0
  196. package/packages/motion/src/transitions2d/magicui/MorphingText.css +25 -0
  197. package/packages/motion/src/transitions2d/magicui/MorphingText.tsx +130 -0
  198. package/packages/motion/src/transitions2d/magicui/WordRotate.tsx +42 -0
  199. package/packages/motion/src/transitions2d/react.ts +13 -0
  200. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.css +39 -0
  201. package/packages/motion/src/transitions2d/skiper-ui/ClipDiv.tsx +32 -0
  202. package/packages/motion/src/transitions2d/smoothui/LICENSE +21 -0
  203. package/packages/motion/src/transitions2d/smoothui/SharedAxisY.tsx +50 -0
  204. package/packages/motion/src/transitions2d/smoothui/TextMorph.tsx +90 -0
  205. package/packages/motion/src/transitions2d/smoothui/shaders.ts +223 -0
  206. package/packages/motion/src/transitions2d/unlumen/LICENSE +21 -0
  207. package/packages/motion/src/transitions2d/unlumen/ScrambleText.tsx +114 -0
  208. package/packages/motion/src/transitions2d/vengeance-ui/LICENSE +21 -0
  209. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.css +69 -0
  210. package/packages/motion/src/transitions2d/vengeance-ui/MorphText.tsx +45 -0
  211. package/packages/motion/src/type.ts +131 -0
  212. package/packages/motion/src/ui.ts +172 -0
  213. package/packages/plan/package.json +15 -0
  214. package/packages/plan/src/brief.ts +254 -0
  215. package/packages/plan/src/captions.ts +75 -0
  216. package/packages/plan/src/detect.ts +93 -0
  217. package/packages/plan/src/index.ts +23 -0
  218. package/packages/plan/src/layout.ts +176 -0
  219. package/packages/plan/src/llm.ts +110 -0
  220. package/packages/plan/src/look.ts +220 -0
  221. package/packages/plan/src/plan.ts +125 -0
  222. package/packages/plan/src/request.ts +55 -0
  223. package/packages/plan/src/studio.ts +53 -0
  224. package/packages/plan/src/templates.ts +42 -0
  225. package/packages/plan/src/themes.ts +48 -0
  226. package/packages/plan/src/types.ts +106 -0
  227. package/packages/studio/package.json +12 -0
  228. package/packages/studio/src/index.ts +1 -0
  229. package/packages/studio/src/plugin.ts +236 -0
  230. package/packages/studio/src/ui/main.ts +503 -0
  231. package/packages/studio/src/ui/studio.css +165 -0
  232. package/packages/studio/src/ui/timeline.ts +317 -0
  233. package/packages/themes/kit/THEME-MAKE.md +68 -0
  234. package/packages/themes/kit/THEME.md +30 -0
  235. package/packages/themes/kit/briefs/og-intro-15s/brief.json +16 -0
  236. package/packages/themes/kit/briefs/og-intro-15s/facts.ts +29 -0
  237. package/packages/themes/kit/briefs/og-intro-15s/logo.svg +12 -0
  238. package/packages/themes/kit/effects.ts +5 -0
  239. package/packages/themes/kit/sample/index.html +11 -0
  240. package/packages/themes/kit/sample/main.ts +9 -0
  241. package/packages/themes/kit/sample/scenes/scene.ts +15 -0
  242. package/packages/themes/package.json +16 -0
  243. package/packages/themes/src/factory.ts +296 -0
  244. package/packages/themes/src/fx.ts +14 -0
  245. package/packages/themes/src/index.ts +105 -0
  246. package/packages/themes/src/stage3d.ts +177 -0
  247. package/packages/themes/src/types.ts +58 -0
  248. package/presets/kawaii-pastel.json +94 -0
  249. package/presets/neutral-dark.json +82 -0
  250. package/presets/og.json +98 -0
  251. package/skills/kasaframe/SKILL.md +32 -0
  252. package/templates/_shared/project/index.html +11 -0
  253. package/templates/_shared/project/lib/react.ts +22 -0
  254. package/templates/_shared/project/lib/stage.ts +272 -0
  255. package/templates/_shared/project/main.ts +9 -0
  256. package/templates/_shared/scenes/default.ts +18 -0
  257. package/templates/_shared/scenes/drop.ts +30 -0
  258. package/templates/_shared/scenes/end.ts +23 -0
  259. package/templates/_shared/scenes/hook.ts +16 -0
  260. package/templates/_shared/scenes/seam.ts +41 -0
  261. package/templates/_shared/scenes/step.ts +40 -0
  262. package/templates/ad/preview.png +0 -0
  263. package/templates/ad/template.json +132 -0
  264. package/templates/demo/preview.png +0 -0
  265. package/templates/demo/template.json +130 -0
  266. package/templates/explainer/preview.png +0 -0
  267. package/templates/explainer/template.json +142 -0
  268. package/templates/launch/preview.png +0 -0
  269. package/templates/launch/template.json +130 -0
  270. package/templates/mv/preview.png +0 -0
  271. package/templates/mv/scenes/default.ts +95 -0
  272. package/templates/mv/template.json +176 -0
  273. package/templates/promo/preview.png +0 -0
  274. package/templates/promo/template.json +144 -0
  275. package/templates/shorts/preview.png +0 -0
  276. package/templates/shorts/scenes/hook.ts +34 -0
  277. package/templates/shorts/template.json +127 -0
  278. package/templates/square/preview.png +0 -0
  279. package/templates/square/template.json +113 -0
  280. package/themes/debug-hud/THEME.md +39 -0
  281. package/themes/debug-hud/effects.ts +389 -0
  282. package/themes/debug-hud/sample/assets/og/logo.svg +12 -0
  283. package/themes/debug-hud/sample/beats.json +1 -0
  284. package/themes/debug-hud/sample/index.html +11 -0
  285. package/themes/debug-hud/sample/kf.json +59 -0
  286. package/themes/debug-hud/sample/lib/hudkit.ts +49 -0
  287. package/themes/debug-hud/sample/lib/og.ts +29 -0
  288. package/themes/debug-hud/sample/main.ts +9 -0
  289. package/themes/debug-hud/sample/scenes/end.ts +62 -0
  290. package/themes/debug-hud/sample/scenes/gather.ts +67 -0
  291. package/themes/debug-hud/sample/scenes/models.ts +70 -0
  292. package/themes/debug-hud/sample/scenes/price.ts +79 -0
  293. package/themes/debug-hud/sample/style.json +91 -0
  294. package/themes/debug-hud/still.png +0 -0
  295. package/themes/debug-hud/style.json +91 -0
  296. package/themes/debug-hud/theme.json +68 -0
  297. package/themes/og-measure/THEME.md +47 -0
  298. package/themes/og-measure/effects.ts +432 -0
  299. package/themes/og-measure/sample/assets/brand/og-logo-full.svg +12 -0
  300. package/themes/og-measure/sample/assets/brand/og-logo.svg +12 -0
  301. package/themes/og-measure/sample/assets/data/measure.json +353 -0
  302. package/themes/og-measure/sample/beats.json +1 -0
  303. package/themes/og-measure/sample/index.html +11 -0
  304. package/themes/og-measure/sample/kf.json +66 -0
  305. package/themes/og-measure/sample/lib/data.ts +62 -0
  306. package/themes/og-measure/sample/main.ts +9 -0
  307. package/themes/og-measure/sample/measure.ts +78 -0
  308. package/themes/og-measure/sample/scenes/end.ts +47 -0
  309. package/themes/og-measure/sample/scenes/gather.ts +51 -0
  310. package/themes/og-measure/sample/scenes/models.ts +34 -0
  311. package/themes/og-measure/sample/scenes/one.ts +39 -0
  312. package/themes/og-measure/sample/scenes/price.ts +56 -0
  313. package/themes/og-measure/sample/style.json +97 -0
  314. package/themes/og-measure/still.png +0 -0
  315. package/themes/og-measure/style.json +97 -0
  316. package/themes/og-measure/theme.json +79 -0
  317. package/themes/riso-zine/THEME.md +43 -0
  318. package/themes/riso-zine/effects.ts +321 -0
  319. package/themes/riso-zine/sample/assets/og/logo.svg +12 -0
  320. package/themes/riso-zine/sample/beats.json +1 -0
  321. package/themes/riso-zine/sample/index.html +11 -0
  322. package/themes/riso-zine/sample/kf.json +59 -0
  323. package/themes/riso-zine/sample/lib/og.ts +29 -0
  324. package/themes/riso-zine/sample/lib/shot.ts +86 -0
  325. package/themes/riso-zine/sample/lib/zine.ts +42 -0
  326. package/themes/riso-zine/sample/main.ts +9 -0
  327. package/themes/riso-zine/sample/scenes/end.ts +61 -0
  328. package/themes/riso-zine/sample/scenes/gather.ts +62 -0
  329. package/themes/riso-zine/sample/scenes/models.ts +52 -0
  330. package/themes/riso-zine/sample/scenes/price.ts +82 -0
  331. package/themes/riso-zine/sample/style.json +99 -0
  332. package/themes/riso-zine/still.png +0 -0
  333. package/themes/riso-zine/style.json +99 -0
  334. package/themes/riso-zine/theme.json +75 -0
  335. package/tools/beats/beats.py +372 -0
  336. package/tools/beats/requirements.txt +3 -0
  337. package/tools/lyrics/align.py +164 -0
  338. package/tools/lyrics/requirements.txt +9 -0
package/docs/AGENTS.md ADDED
@@ -0,0 +1,238 @@
1
+ # kasaframe 사용서 — 영상을 만드는 모델용
2
+
3
+ 웹 화면을 시각 t 의 함수로 짜면 kasaframe 이 1/60초마다 찍어 MP4 로 붙인다. 연출·박자·검사는 라이브러리가 쥔다. 너는 **장면 파일만 채운다**. 명령은 `kf <명령>` — `npm i -g kasaframe` 로 깔았으면 어느 폴더에서나, 레포에서는 뿌리에서 `npx kf <명령>`(또는 `node packages/cli/src/kf.ts <명령>`). 처음이면 `kf doctor`(ffmpeg·크롬·librosa·OG 키 점검)·`kf setup`(크롬·librosa 깔기).
4
+
5
+ ## 0. 한 줄로 끝까지 가려면
6
+
7
+ ```
8
+ kf make "카사텀 홍보 영상 1분, 카와이 퓨처베이스" [--music 곡.mp3] [--purpose promo] [--theme auto]
9
+ ```
10
+
11
+ - **Claude Code 안**(기본 `--model claude-code`): 뼈대·곡 분석·장면 지시서 `MAKE.md` 까지 만들고 멈춘다. 다른 모델을 부르지 않고, **돈이 드는 그림도 묻지 않고 만들지 않는다** — 지시서의 「만들 그림」을 보고 필요할 때만 네가 구체적인 프롬프트로 `kf gen img` 를 부른다. 장면을 다 채우고 `kf look <폴더>` 가 찍는 장면 모음·확인표를 직접 본 뒤 `kf make --finish <폴더>`(렌더·검사·표지·자막. 그림 보는 모델도 안 부르고 끝에 장면 모음·확인표 경로를 다시 찍는다).
12
+ - **OG 모드**(`--model deepseek/deepseek-v4-pro` 같은 이름): 장면 작성·고치기까지 OG `chat/completions` 로 끝까지. 그림 검사는 `--vision-model <모델>` 을 줄 때만.
13
+ - **그림 보기는 세션이 직접**: `kf look <폴더|mp4>` 가 장면 모음(`out/look-sheet.png`)과 장면별 확인표(`out/look.md` — 빈 화면·글 깨짐·대비·잘림·주인공 자리·할 일·테마)를 만들고 경로만 찍는다. 외부 모델을 부르지 않는다. **눈 없는 모델**(Claude Code 를 OG 로 딥시크 같은 모델에 붙였을 때 — OG `/v1/messages` 는 그림을 버려서 Read 해도 안 보인다)만 `kf look <폴더|mp4> --vision-model google/gemini-3.5-flash [--ask "<볼 것>"]` 로 그림 보는 모델이 장면마다 글로 보고한다(한 번 1센트 안팎, 묻지 않고 써도 된다). `kf make --finish` 도 기본은 그림 보는 모델 없이 끝에 장면 모음·확인표 경로를 찍는다.
14
+ - **곡**: 홍보·공개·뮤비는 곡이 끌고 간다. `--music` 을 안 주면 곡 모음(`KF_MUSIC_DIR`, 없으면 레포 `music/` — 곡마다 원본·라이선스는 `music/README.md`. npm 패키지에는 곡이 없다)에서 요청 낱말과 맞는 곡을 고른다. 곡 모음이 비었으면 `--music` 으로 준다.
15
+ - **용도·테마**: 말로만 해도 알아서 고른다 — 테마는 기본 `auto`: 요청이 「리소 느낌으로」처럼 짚은 테마(이름·비유에서 찾는다), 아니면 내용에 맞는 approved 후보 셋 중 첫째(고른 이유·다른 후보 둘이 지시서에, 첫 장면을 셋 다 찍어 `out/theme-<id>.png`). 못 박으려면 `--purpose <용도>`·`--theme <id>`, 테마 없이 프리셋만이면 `--theme none`. 묻는 것은 용도 확신이 낮을 때뿐(종료 코드 3).
16
+ - **작업실**: `kf app` 이 떠 있으면 `kf make` 가 거기에 등록하고 주소를 찍는다 — 사람은 페이지에서 실시간 미리보기·지난 작업을 본다.
17
+ - **Claude Code 스킬**: `kf skill install` 하면 「영상 만들어」 같은 말에 kasaframe 으로 오는 스킬이 깔린다(`skills/kasaframe/SKILL.md`).
18
+ - 영상 생성 모델은 요청 글에 「영상 생성」을 적었을 때만 쓴다(이음매 기본은 3D 모션그래픽 전환). 영상은 `--yes` 가 있어야 돈을 쓴다.
19
+
20
+ **규칙 — 단 0.1초도 쉬지 않는다**(검사가 오류로 잡는다. 용도마다 틀이 기준을 정한다 — 아래 표, 이 영상의 값은 MAKE.md 첫머리): ① 0.1초(60fps 6프레임) 넘게 화면이 안 바뀌면 오류 — 끝 카드·큰 숫자·로고도 예외 없음 ② 같은 구도가 표의 초를 넘으면 경고·오류 — 새 요소·칸 채우기·숫자 키우기·컷·전환 ③ 미세한 흔들림 말고 눈에 보이는 큰 움직임 — 표의 자리에서 박마다 하나 이상 ④ 주 요소가 화면을 채운다(세로는 폭 70% 넘게). 쉬지 않는 몫은 **화면 안 움직임**이 진다 — 카메라는 이유가 있을 때만 움직인다(3장 「카메라」). `kf new` 가 넣어 주는 `lib/stage.ts`(`makeStage`·`makeCamera`·`bigWord`·`cutTimes`·`lastT`)가 이걸 처음부터 넘게 짜 둔 무대다 — 장면을 하나하나 고치기 전에 겉에 씌워라.
21
+
22
+ | 용도 | 0.1초 정지 | 같은 구도 경고·오류 | 박마다 큰 움직임 |
23
+ |---|---|---|---|
24
+ | `shorts` `promo` `ad` `launch` `square` | 금지 | 1.5초 · 3초 | 어디서나 |
25
+ | `mv` `explainer` `demo` | 금지 | 5초 · 8초 | 후렴·드롭 구간(beats.json sections 의 drop)만 — 벌스는 화면 안 움직임(가사·숫자·바탕 흐름)으로 충분 |
26
+
27
+ ## 1. 용도 고르기
28
+
29
+ 요청 글과 받은 자산을 보고 하나 고른다. 애매할 때만 사람에게 묻는다.
30
+
31
+ | 용도 | 화면 · 길이 | 글 |
32
+ |---|---|---|
33
+ | `promo` 홍보 | 16:9 · 30–60초 | 문구 없이 기능 낱말·끝 카드만. 곡이 끌고 감, 드롭에서 가장 크게 |
34
+ | `shorts` 쇼츠 | 9:16 · 15–60초 | 전문 낱말만, 자막은 따로(srt·vtt). 첫 1.5초 훅, 끝이 처음으로 이어지게 |
35
+ | `explainer` 설명 | 16:9 · 60–120초 | 도식·3D, 전문 낱말만, 자막 트랙 |
36
+ | `launch` 공개 | 16:9 · 30–90초 | 감추고 → 드러내고 → 기능 셋 → 이름 |
37
+ | `demo` 시연 | 16:9 · 30–180초 | 화면 녹화 + 확대·커서·단계 표시 |
38
+ | `ad` 광고 | 6·15·30초 | 브랜드 첫 2초 안, 자막 굽기 |
39
+ | `square` 피드 | 1:1 · 15–60초 | 소리 없이도 읽히게, 자막 굽기 |
40
+ | `mv` 뮤비 | 16:9·9:16 · 곡 전체 | 곡 구간마다 장면, 가사 키네틱 타이포(`lyrics.json` 낱말 시각), 컷은 마디·박마다 |
41
+
42
+ ## 2. 뼈대 — `kf new`
43
+
44
+ ```
45
+ kf new <폴더> --template promo --music 곡.mp3 --style og
46
+ ```
47
+
48
+ 곡을 재고(`beats.json`) 장면 경계를 박·드롭에 붙인 시간표(`kf.json`)와 장면 파일을 만든다. 스타일: `presets/` 의 `og` `neutral-dark` `kawaii-pastel`(틀이 고른 프리셋이 없으면 `neutral-dark`).
49
+
50
+ 곡만 다시 재려면 `kf beats 곡.mp3 --out <폴더>/beats.json [--from 초 --duration 초]` — 요약(bpm·드롭·구간)은 stderr. 드롭·구간은 **잰 시각**이다. BPM 을 곱해 시각을 만들지 않는다(뒤로 갈수록 밀린다).
51
+
52
+ ```
53
+ <폴더>/kf.json 장면 시간표 — 시각은 여기 한 곳. 장면마다 할 일이 idea 에 적혀 있다
54
+ <폴더>/scenes/<id>.ts 네가 채울 곳 — 장면 하나 = 파일 하나
55
+ <폴더>/style.json 색·글꼴·움직임 규칙 <폴더>/beats.json 박·드롭·프레이즈
56
+ <폴더>/assets/ 받은 자산 · assets/gen/(생성한 것) <폴더>/out/ 결과
57
+ ```
58
+
59
+ `main.ts`·`index.html` 은 고치지 않는다. 장면 경계를 옮기려면 `kf.json` 의 숫자만.
60
+
61
+ ## 3. 장면 채우기 — `scenes/<id>.ts`
62
+
63
+ ```ts
64
+ import { applyStyle, type SceneCtx } from "@kasaframe/core";
65
+ import { enter, typeText } from "@kasaframe/motion";
66
+
67
+ export default {
68
+ build(root: HTMLElement, ctx: SceneCtx) {
69
+ const title = document.createElement("h1");
70
+ root.append(title);
71
+ return (t: number) => {
72
+ title.textContent = typeText("선택한 줄을 보냅니다", t, { at: 0.3 }).text;
73
+ applyStyle(title, enter("rise", t, { at: 0.2, scene: ctx.id }));
74
+ };
75
+ },
76
+ };
77
+ ```
78
+
79
+ - `t` 는 **장면 안 초**(장면 시작 = 0). 반환 함수는 **같은 t 면 같은 화면** — 앞 프레임 상태를 쌓지 않는다.
80
+ - DOM 은 `build` 에서 한 번 짓고, `(t) => …` 에서는 글·스타일만 바꾼다.
81
+ - 박자는 `ctx.beats`(`drop(0)`·`nearestBeat`·`phrase`·`inDrop`), 그림은 `ctx.asset("x.png")`, 글은 `ctx.safe` 안에.
82
+ - 움직임은 `@kasaframe/motion` 부품을 쓴다 — 손으로 짠 흔들림·펄스는 검사가 셀 수 없다. 부품 표와 Remotion 대응표: [`packages/motion/README.md`](../packages/motion/README.md).
83
+ - React 장면: `export default { react: Component }`, `Component({ t, ctx })`.
84
+
85
+ 하지 말 것:
86
+
87
+ - `Math.random` · `Date.now` · `setInterval` 로 상태 쌓기 → `rand(seed, i)` · `t`
88
+ - 배열·객체를 그대로 글에 넣기(`undefined`·`[object Object]` 가 화면에 찍힌다) → 글은 늘 문자열로 만들고 길이를 맞춘다
89
+ - 박마다 화면 흔들기 · 이유 없이 카메라 떠다니기(사인 드리프트·박마다 자세·늘 다가가기) · 같은 등장 움직임을 이어서 두 장면에 · 드롭 시각을 BPM 으로 곱해 만들기
90
+ - 한글이 없는 글꼴로 한글 쓰기 → 스타일 프리셋 글꼴(전부 한글이 선다)
91
+
92
+ ### 카메라 — 이유가 있을 때만
93
+
94
+ 기본은 **정지**, 주인공은 안전 영역 가운데. 화면 전체를 사인으로 흔들거나 박마다 자세를 바꾸면 격자가 기울고 주인공이 가장자리로 밀린다 — `kf check` 가 `camera.wander`(이유 없는 카메라 이동)·`layout.edge`(주인공이 안전 영역 가장자리 4% 띠에) 로 경고한다. 카메라는 장면이 이유를 댈 때만:
95
+
96
+ ```ts
97
+ const stage = makeStage(root, ctx); // 흐르는 바탕 + 컷마다 모양을 바꾸는 바탕 판 + 정지 카메라
98
+ stage.camera
99
+ .pushIn(2.0, { to: answer }) // 드러냄에 다가가기 — 요소 상자를 가운데로, 안전 영역 86% 안에 들게 배율을 누른다
100
+ .pullOut(5.5) // 비교 전체를 보이게 물러나기 — 기본은 처음 구도. 더 넓게는 { scale: 0.9 }
101
+ .follow(ball, 6.5, 9.0) // 움직이는 주인공 따라가기 — 끝나면 앞 구도로 돌아와 선다
102
+ .impact(ctx.beats.drop(0)); // 드롭·결과가 서는 순간에 한 번 툭
103
+ return (t: number) => {
104
+ /* 내용을 칠한다 */
105
+ stage.tick(t); // 요소를 겨누면(to·follow) 맨 끝에 — 그 프레임의 자리를 잰다
106
+ };
107
+ ```
108
+
109
+ - 부드럽게(inOutCubic) 가고 끝나면 선다. 움직이는 동안만 `__kf.events` 에 카메라로 남아 이유 있는 이동으로 친다 — 프레이즈당 한 번 규칙(`motion.camera`)도 이걸 센다.
110
+ - 자기 카메라 층을 짠 장면은 `makeCamera(layer, ctx, { frame })` 로 같은 넷을 건다. `frame` 은 층 안 좌표로 본 안전 영역, 다른 움직임과 섞으려면 `camera.pose(t)` 를 `combine` 한다.
111
+ - 회전·원근 기울기는 0 — 3D 전환(이음매)·입체 테마가 원할 때만.
112
+ - 「쉬지 않기」는 화면 안 움직임이 맡는다: 숫자가 구르고, 칸이 차고, 반짝이고, 바탕이 흐른다. 바탕 흐름은 바탕 층(`stage.box`)에만, 주인공 층(`stage.cam`)은 흔들지 않는다.
113
+ - `stage.hero(el)` 로 주인공을 짚으면 `layout.edge` 가 그 상자를 늘 본다(`bigWord` 는 스스로 짚는다). 짚지 않으면 그 프레임의 가장 큰 글을 주인공으로 보되, 카메라가 처음 자리를 벗어난 동안만 센다(일부러 구석에 둔 배치는 안 잡는다).
114
+
115
+ ### 이미 있는 컴포넌트·영상·3D 를 넣을 때
116
+
117
+ - 자기 rAF·타이머·물리로 도는 컴포넌트를 **고치지 않고** 넣으려면 `kf.json` 에 `"mode": "step"` — 0 초부터 차례로 돌린다. 기본 `seek` 는 화면이 t 의 순수 함수일 때(탭 병렬로 빠르다).
118
+ - 렌더 중에는 `requestAnimationFrame`·`performance.now`·`Date`(0 초 = 2026-01-01T00:00Z)·`setTimeout`·`setInterval` 이 영상 시각을 따르고 `Math.random` 은 프레임마다 그 번호로 다시 씨를 뿌린다(같은 프레임은 어느 탭에서 찍어도 같은 값 — 남이 만든 라이브러리의 난수도 이음매가 맞는다). 캔버스에 그리는 그림은 처음 그리기 전에 엔진이 한 번 데워 둔다.
119
+ - seek 모드의 CSS 애니메이션·트랜지션은 0 초에 시작한 것으로 본다 — 장면 가운데서 시작할 것은 `animation-delay` 로 박거나 움직임 부품으로.
120
+ - 영상: `<video src data-kf-from="<영상 전체 기준 초>" muted>` — seek 마다 맞춘다. 소리는 `kf.json` 의 `music` 만 얹힌다.
121
+ - Lottie·GSAP·three.js: `@kasaframe/adapters` 의 `bindLottie(anim, { from })` · `bindGsap(gsap)` · `bindThree({ renderer, scene, camera, update(t) })`.
122
+ - **Blender 툰 3D**(카메라가 공간을 지나가는 샷·박에 맞춰 움직이는 물체): `kf blender <샷.py> --project <폴더> --scene <id>` 가 헤드리스 Blender 로 굽어 `assets/blender/<id>.mp4` 를 두고 `<video data-kf-from>` 값을 찍어 준다. 샷은 `tools/blender/kfb.py` 헬퍼로 — `update(t)` 가 t 만 보고 자세를 정하고, 카메라 열쇠는 `S.downbeats`·`S.drops` 에(`Track(..., move=…)` 로 도착하면 선다). 굽기 전에 `--still 1.2,4.5` 로 몇 장 본다. 예시·쓰는 법: `tools/blender/README.md`(Blender 4.3 이상, GPL-3.0 폴더).
123
+ - 글꼴·`<img>`·CSS `background-image`·영상은 받을 때까지 기다린다. 더 기다릴 것이 있으면 `definePage({ ready: async () => … })`.
124
+ - **이미 있는 React 컴포넌트**는 `lib/react.ts` 의 `bit(host, Comp)` 로 — 장면이 처음 그려질 때 붙인다(`build` 에서 바로 붙이면 장면이 숨은 동안 등장 애니메이션이 끝나 버린다). 컴포넌트가 쓰는 npm 패키지는 프로젝트 폴더에 `package.json` 을 두고 `npm i <패키지> --legacy-peer-deps`. **react·react-dom 은 프로젝트에 깔지 않는다**(레포 것 한 벌로 묶인다 — 두 벌이면 훅이 깨진다). 새 의존을 넣은 첫 실행은 Vite 가 다시 묶으며 한 번 새로 고칠 수 있다 — 두 번째부터 본다. 자기 시계로 도는 컴포넌트면 `"mode": "step"`(대신 「같은 프레임 두 번 찍기」 검사는 못 한다).
125
+ - **이음매 장면**(`role: "seam"`)은 고치지 않는다 — 틀의 3D 전환(`transition3d`)이 앞 장면 마지막 화면에서 뒤 장면 첫 화면으로 잇는다. 앞뒤 화면 그림은 `kf make --finish` 가 장면을 다 쓴 뒤 찍는다.
126
+ - **되돌이**(쇼츠·피드처럼 끝이 처음으로 이어질 때): 렌더가 마지막으로 찍는 장면 안 시각은 `ctx.dur` 이 아니라 `ctx.dur - 1/fps`(`lastT(ctx)`) — 움직임을 거기서 끝내야 끝 화면과 첫 화면이 픽셀까지 같다.
127
+
128
+ ### 뮤비 가사 — `kf lyrics align` · JIZURA 가사 층
129
+
130
+ ```
131
+ npx kf setup jizura lyrics # 한 번만 — 가사 층 엔진·한글 글꼴(60MB), 정렬 꾸러미(2GB)
132
+ npx kf lyrics align 곡.mp3 --text 공식가사.txt --lang ko --out <폴더>/lyrics.json [--from 초 --duration 초]
133
+ ```
134
+
135
+ - 글자는 공식 가사(한 줄 = 가사 한 줄)가 정본이고 정렬은 시각만 붙인다 — 받아쓰기 글자를 화면에 쓰지 않는다. 곡 일부만 쓰면 `kf beats` 와 같은 `--from` 을 준다(시각의 0 이 같아야 한다). 첫 실행은 whisper 모델(1.6GB)을 받는다.
136
+ - `kf new --template mv` 의 장면은 프로젝트 뿌리의 `lyrics.json` 을 JIZURA 가사 층으로 그린다 — 벌스는 차분한 플랜, 드롭(`ctx.beats.inDrop`)부터 큰 플랜. 엔진이 없거나 `lyrics.json` 이 없으면 부른 낱말을 DOM 으로 띄운다.
137
+ - 직접 쓸 때: `jizuraPlans({ day: { style: "ocean", schemes: "light" } })` 로 플랜을 이름 짓고 `const paint = jizuraLayer(root, { ctx, plan: "day" })` → `(t) => paint(t)`. 같은 설정의 플랜은 장면끼리 나눠 쓴다. 밝은 바탕엔 `schemes: "light"`(어두운 글), 밤 장면엔 `"dark"`, 주인공 얼굴이 요점이면 `centerFree: true`(가운데를 비운다). 분위기는 `moods: ["calm", "emotional", "editorial"]` 처럼 부품 태그로 거른다.
138
+ - 화면을 덮는 부품(불투명 판·카드 카메라·화면 전체 섬광)은 기본 거부 목록(`DENY`)이 막는다. 더 막을 것은 `deny: { layout: ["…"] }`. **거부 목록·`moods`·`seed` 를 바꾸면 그 플랜의 컷이 전부 다시 굴려진다** — 스틸을 다시 본다. 어느 부품인지는 `jizuraCuts({ ctx, plan })` 로 그 시각의 컷(layout·enter·hold·exit·decor·cam)을 본다.
139
+ - 가사 층 글은 검사에서 장면 글과 따로 묶인다. 띠·배경 큰 글자처럼 일부러 화면 끝을 넘기는 장식은 `text.offscreen` 경고로만 나온다 — 읽혀야 할 가사 줄이 잘리면 그 부품을 막는다.
140
+ - 가사 층으로 낸 영상에는 「JIZURA 字面 (c) 2026 hakoniwa (github.com/852wa), MIT」를 적는다.
141
+
142
+ ### 자주 밟는 곳
143
+
144
+ - **`visibility` 로 고르면 숨긴 컷에서도 비친다** — 부모 `visibility:hidden` 을 자식 `visibility:visible` 이 뒤집는다. 여러 장 중 하나만 보일 땐 `opacity` 나 `display`.
145
+ - **CSS `zoom` 으로 키우지 않는다** — `left/top` 까지 같이 커져 화면 밖으로 나가고, 검사의 글 상자 자리도 어긋난다. 부품의 크기 인자나 `transform: scale` 로.
146
+ - **이름 겹침** — 장면 안 지역 변수·함수가 가져온 이름을 덮으면(겉층 `live` 와 컷 판정 `live(t, i)` 처럼) 던지지 않고 늘 거짓이 되어 화면이 통째로 빈다. 검사는 `frame.blank`·`layout.fill` 로만 잡고 `page.error` 는 안 뜬다 — 빈 화면이 나오면 이름부터 본다.
147
+ - **크기를 className CSS 로 받는 컴포넌트**(캔버스 등)는 그대로 넣으면 300×150 — 장면에 `<style>` 로 그 클래스 크기를 준다.
148
+ - **스크린리더 전용 글**(1px·clip 으로 숨긴 span)은 검사가 대비·화면 밖 판정에서 뺀다. 그래도 걸리면 그 클래스를 `display:none`.
149
+ - **`build` 에서 장면이 숨어 있다**(앞 장면 차례엔 `display:none`) — 그때 잰 크기는 0 이다. 크기는 글자 수로 어림하거나(`fitPx`) 처음 그려질 때 잰다.
150
+ - **0.1초 규칙** — 끝 카드·큰 숫자 화면이 제일 먼저 걸린다. 구도는 칸 평균 밝기와 화면 안 내용(글·칸의 센 가장자리 지도) 둘로 본다 — 숫자가 구르고 칸이 차고 줄이 붙으면 바뀐 것으로 치지만, 빛 방울·모눈 같은 부드러운 바탕 흐름만으로는 안 바뀐다. 컷(`cutTimes`)마다 화면 안 내용을 바꾼다(새 줄·칸 채우기·숫자 키우기·바탕 판).
151
+
152
+ ## 4. 보기 — `kf preview` · `kf still` · `kf sheet`
153
+
154
+ ```
155
+ kf preview <폴더> [--t 12.3] # 주소 한 줄(stdout). / 가 미리보기, /index.html 이 프로젝트 페이지, ?t=12.3
156
+ kf still <폴더> --t 12.3 # 한 장 → out/still-12.3.png
157
+ kf sheet <폴더> # 장면마다 한 장씩 모은 한 장
158
+ kf look <폴더> # 장면 모음 + 장면별 확인표(out/look.md) 경로 — 직접 Read 로 본다(외부 모델 0)
159
+ kf look <폴더> --vision-model google/gemini-3.5-flash # 그림을 못 보는 세션만 — 다른 모델이 장면마다 글로 보고(out/look.json)
160
+ ```
161
+
162
+ 미리보기: Space 재생 · ←→ 한 프레임 · Shift+←→ 1초 · Alt+←→ 박 · ↑↓ 장면·구간·드롭 경계 · 타임라인 Shift 끌기는 박에 붙인다. 장면을 저장하면 보던 시각 그대로 새로 그리고, 깨진 장면은 붉은 줄로 알린다.
163
+
164
+ 그림을 못 보면 미리보기 대신 `kf check`(숫자·글로 다 나온다)와 `kf still`.
165
+
166
+ ## 5. 굽기 — `kf render`
167
+
168
+ ```
169
+ kf render <폴더> [--tabs 2] # → out/render.mp4 + out/render.json(markers·움직임 기록)
170
+ kf render <폴더> --from 10 --to 14 --no-lock # 고친 구간만 빨리
171
+ ```
172
+
173
+ ### 자막 켜고 끄기
174
+
175
+ 들릴 말은 장면 파일이 아니라 `captions.json` 에 쓴다 — `[{"scene": "<id>", "text": "짧은 마디를 쉼표로"}]`(`from`·`to` 초나 `words` 낱말 시각도 된다). `kf.json` 의 `captions` 가 `off`(쇼츠·설명 기본 — srt·vtt 만) · `on`(화면에 얹고 srt·vtt 도) · `none`. 화면 자막은 엔진이 장면 위에 판을 얹는다(안전 영역 아래쪽 가운데, 테마 글꼴, 어절 단위 두 줄까지, 세로 화면은 폭의 8% 크기) — 장면에 문장을 쓰지 않는다. 켜면 `ctx.safe` 아래쪽에서 자막 띠(판 두 줄 높이)를 뺀다 — 무대(`makeStage`)처럼 `ctx.safe` 로 배치하는 장면은 제 크기 그대로 띠 위로 올라간다. 픽셀을 박아 둔 장면은 안 움직여 `kf check` 가 `caption.overlap` 으로 잡는다 — 글을 `ctx.safe` 안으로 옮기거나, 장면을 못 고치면 `--captions fit`(장면 전체를 띠 위로 줄여 올린다 — 안 겹치지만 내용이 작아지고 움직이는 면적도 준다).
176
+
177
+ ```
178
+ kf render <폴더> --captions on --suffix # 다시 만들지 않고 자막 판만 → out/render-자막.mp4 (원본은 그대로). fit 이면 장면을 줄여 올린다
179
+ kf make --finish <폴더> --captions on --suffix # out/video-자막.mp4 + srt·vtt
180
+ kf check <폴더> --captions on # 자막 켠 화면으로 훑는다
181
+ kf preview <폴더> --captions on # 미리보기 단추(C)로도 켜고 끈다
182
+ ```
183
+
184
+ ## 6. 검사 — `kf check`
185
+
186
+ ```
187
+ kf check <폴더> # 가장 정확하다 — 페이지를 열어 프레임마다 훑는다
188
+ kf check <폴더>/out/render.mp4 # 영상만 있을 때 — 프레임 차 + OCR
189
+ ```
190
+
191
+ 렌더·검사는 한 기계에서 하나씩 돈다(`.render-lock`, npm 으로 깐 판은 임시 폴더의 `kasaframe-render-lock` — 다른 렌더가 쥐고 있으면 기다린다, 탭은 둘까지). 렌더를 되풀이하기 전에 고칠 것을 모아 한 번에. 종료 코드 `0` 통과 · `1` 오류 · `2` 못 돌림. 보고서는 글로 나오고 JSON 은 `out/check.json`. **오류가 0 이 될 때까지 7 로 간다.** 경고는 읽고 판단한다(`--strict` 면 경고도 실패).
192
+
193
+ ## 7. 고치기 — 보고서 규칙별로
194
+
195
+ | 규칙 | 뜻 | 고치는 법 |
196
+ |---|---|---|
197
+ | `text.bad` | 화면에 `undefined`·`NaN`·`[object Object]`·`null` | `자리:` 의 파일·줄로 간다. 배열 길이·빠진 칸·숫자 포맷을 고친다. 코드 화면에서 일부러 보이면 `kf.json` 에 `"check": { "allowText": ["null"] }` |
198
+ | `text.tofu` | 어떤 글꼴에도 없는 글자(빈 네모) | 그 글자를 빼거나 SVG 로. 이모지 금지 |
199
+ | `text.fallback` | 첫 글꼴이 안 불러와졌거나 그 글자가 없다 | 글꼴 이름·`@font-face` 를 확인, 한글이면 프리셋 글꼴로 |
200
+ | `text.offscreen` | 글이 화면 밖으로 잘린 채 0.5초 넘게(가사 층 `data-kf-lyrics` 의 띠·배경 글자는 경고) | 자리·크기를 줄여 화면 안으로. 가사 층 경고는 장식 띠면 두고, 읽혀야 할 가사 줄이면 화면 안으로 |
201
+ | `text.safe-area` | 플랫폼 UI 가 가리는 자리에 글 | `ctx.safe` 안으로 |
202
+ | `text.contrast` | 바탕과 대비가 낮다 | 글 색이나 바탕을 3:1 이상 벌린다. 덮개 아래 글이면 덮개를 걷는다 |
203
+ | `text.small` · `text.crowded` | 틀이 정한 글 크기·한 화면 글 수를 넘었다 | 낱말을 줄이고 키운다(터미널·코드 화면이면 둬도 된다) |
204
+ | `frame.freeze` | 0.1초 넘게 거의 안 바뀐다(30fps 로 훑는다) | 늘 도는 화면 안 움직임 — 숫자 굴러가기·반짝임·`makeStage` 의 흐르는 바탕을 겉에, 끝 카드도 |
205
+ | `layout.static` | 장면 안에서 같은 구도가 표의 초(기본 1.5초 경고·3초 오류, 뮤비·설명·시연 5·8초) | 새 요소·칸 채우기·숫자 키우기·컷 — `cutTimes(ctx)` 박마다 내용을 바꾼다(카메라로 메우지 않는다) |
206
+ | `camera.wander` | 글을 품은 큰 층(카메라 층)이 카메라 이벤트 없이 움직였다 — 장면 안 합 0.8초 넘게 | 사인 드리프트·박마다 자세·늘 다가가는 줌을 걷는다. 이유가 있으면 `stage.camera`(pushIn·pullOut·follow·impact)로 — 그쪽은 이벤트가 남아 안 센다 |
207
+ | `layout.edge` | 주인공 상자(`data-kf-hero`, 없으면 카메라가 움직여 있는 동안의 가장 큰 글)가 안전 영역 가장자리 4% 띠에 0.5초 넘게 | 주인공을 안전 영역 가운데로. 카메라가 밀었으면 그 이동을 걷고, 왼쪽에 붙인 배치면 가운데로 옮긴다 |
208
+ | `layout.fill` | 장면 내용이 안전 영역의 12% 아래(오류) · 세로 화면 폭 70% 아래(경고) | 주 요소를 키운다 — `bigWord`·`fitPx`, 도식 확대, 화면을 채우는 면 |
209
+ | `caption.overlap` | 화면 자막 판이 장면 글을 덮는다(경고) | 그 글을 `ctx.safe` 안으로 — 자막을 켜면 안전 영역 아래쪽 띠 자리가 빠진다. 장면을 못 고치면 `--captions fit` |
210
+ | `frame.spike` | 한 프레임만 앞뒤와 크게 다르다(경고) — 깜빡임·한 프레임 오류 | 그 시각 장면의 조건 분기·켜고 끄기 경계를 본다. 일부러 한 섬광·컷이면 드롭·장면 경계에 붙인다(거기선 안 센다) |
211
+ | `frame.flash` | 화면 25% 넘게 번쩍이는 섬광이 1초에 3번 넘게(빛 민감 발작 — `style.json` `motion.maxFlashPerSec`) | 박마다 섬광이면 두 박·한 마디에 한 번으로, 섬광 연타는 2마디 안. 빠른 곡(150BPM 넘게)은 반 박이 아니라 2박에 한 번 |
212
+ | `frame.flash-red` · `frame.flash-pattern` · `frame.flash-cut` | 빨강 전체 깜빡임(오류) · 대비 큰 줄무늬·나선이 빠르게 화면 25% 넘게(경고) · 컷 앞뒤 밝기 50%p 넘게 뜀(경고) | 빨강은 화면 10% 아래 작은 요소로 · 무늬를 줄이거나 느리게 · 흰 화면 섬광 대신 색 섬광 |
213
+ | `frame.blank` | 한 색 화면이 0.5초 넘게 | 장면이 비었거나 그리기가 실패했다. `page.error` 부터 본다 |
214
+ | `frame.nondeterministic` | 같은 t 를 두 번 그렸더니 달랐다 | `Math.random`·`Date.now`·쌓는 상태를 `t` 의 함수로 |
215
+ | `page.error` | 장면이 던졌다 | 메시지의 장면·시각으로 간다 |
216
+ | `beat.boundary` · `beat.boundary-drop` | 장면 경계가 박·드롭에서 어긋났다 | `kf.json` 의 `from` 을 알려 준 박·드롭 시각으로 |
217
+ | `motion.beat-pulse` | 규칙 밖에서 박 펄스 | `beatPulse` 를 드롭 구간에서만(부품 기본값) |
218
+ | `motion.shake-*` | 드롭 밖 흔들림 · 프레이즈당 횟수 · 상한 px 넘음 | `dropBurst` 로 드롭에서만, 한 프레이즈에 한 번 |
219
+ | `motion.camera` | 한 프레이즈에 카메라 두 번 넘게 | 프레이즈당 한 번 |
220
+ | `motion.repeat` | 이어지는 두 장면이 같은 등장 움직임 | 한쪽을 다른 `enter` 로 |
221
+ | `asset.*` | 생성 자산의 라이선스·지역 제한·투명 실패 | 밖에 내기 전에 사람에게 알린다 |
222
+
223
+ 고친 뒤: 그 구간만 `kf check <폴더> --from 10 --to 14` → 다 고쳤으면 `kf render` → `kf check <영상>`.
224
+
225
+ ## 8. 없는 그림·영상 — `kf gen`
226
+
227
+ ```
228
+ kf gen models # 키·단가 표
229
+ kf gen img "<구체적 프롬프트>" --id hook-bg --project <폴더> [--size 1080x1920 --transparent --ref a.png] # 그림 한 장 — 돈이 든다(한 장 $0.25 상한)
230
+ kf gen plan <폴더> [--yes] # plan.json 의 gen:img·gen:vid 를 다 만든다
231
+ kf gen seam <폴더> <장면id> "<프롬프트>" --yes # 앞 장면 끝 → 다음 장면 첫 프레임을 잇는 전환 클립
232
+ ```
233
+
234
+ 영상은 `--yes` 가 없으면 견적만 내고 종료 코드 3 — 돈이 드는 호출은 사람 승인 뒤에. 단가·라이선스: `packages/gen/PROVIDERS.md`.
235
+
236
+ ## 예제
237
+
238
+ [`examples/`](../examples/) — 채운 프로젝트와 검사가 잡는 버그 표본.
@@ -0,0 +1,13 @@
1
+ # 곡 모음 — kf make 가 곡을 안 받았을 때 고르는 곳
2
+
3
+ `kf make` 는 곡이 필요한 용도(홍보·공개·뮤비)인데 `--music` 이 없으면 여기서 요청 낱말과 파일 이름이 맞는 곡을 고른다(「카와이·퓨처베이스·칠·로파이·애니…」 같은 한국어 장르 낱말은 영어로도 찾는다). 다른 폴더를 쓰려면 `KF_MUSIC_DIR=<폴더>`.
4
+
5
+ 곡 파일은 깃에 올리지 않는다(`.gitignore`) — 아래 표의 원본에서 받아 이 폴더에 둔다. 파일 이름에 장르·분위기 낱말을 넣어 두면 잘 골린다.
6
+
7
+ | 파일 | 곡 | 장르 · 길이 | 원본 · 라이선스 |
8
+ |---|---|---|---|
9
+ | `kawaii-friends.mp3` | Kawaii Friends — ckotty3 | Future Bass · 2:31.8 · 170 BPM, 드롭 11.34·22.63·45.22초 | https://pixabay.com/music/future-bass-kawaii-friends-110954/ · Pixabay Content License(상업 이용 가능, 출처 표기 의무 없음) |
10
+ | `anime-star-ckotty3.mp3` | Anime Star — ckotty3 | Anime · 1:49 · 107 BPM | Pixabay · 같은 라이선스 |
11
+ | `gummy-drive-mvncm.mp3` | Gummy Drive — mvncm | 2:00 안팎 | Pixabay · 같은 라이선스 |
12
+
13
+ 새 곡을 넣을 땐 이 표에 한 줄(원본 주소·라이선스)을 더한다. 출처를 모르는 곡은 넣지 않는다.
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "kasaframe",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "말로 시키는 코드 영상 엔진 — 쇼츠·홍보·설명·뮤비를 장면 파일로 짜고 박에 맞춰 MP4 로 굽고 검사한다",
5
+ "keywords": [
6
+ "video",
7
+ "motion-graphics",
8
+ "shorts",
9
+ "music-video",
10
+ "render",
11
+ "playwright",
12
+ "vite",
13
+ "claude-code"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "2rami",
17
+ "type": "module",
18
+ "bin": {
19
+ "kf": "bin/kf.mjs"
20
+ },
21
+ "engines": {
22
+ "node": ">=22.18"
23
+ },
24
+ "dependencies": {
25
+ "esbuild": "^0.28.2",
26
+ "motion": "^12.43.0",
27
+ "playwright": "^1.64.0",
28
+ "pngjs": "^7.0.0",
29
+ "react": "^19.3.0",
30
+ "react-dom": "^19.3.0",
31
+ "three": "^0.186.1",
32
+ "vite": "^8.3.3"
33
+ },
34
+ "peerDependencies": {
35
+ "vue": ">=3.3",
36
+ "svelte": ">=5"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "vue": {
40
+ "optional": true
41
+ },
42
+ "svelte": {
43
+ "optional": true
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@kasaframe/adapters",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts",
8
+ "./react": "./src/react.ts",
9
+ "./vue": "./src/vue.ts",
10
+ "./svelte": "./src/svelte.ts"
11
+ },
12
+ "dependencies": {
13
+ "@kasaframe/clock": "^0.0.0",
14
+ "@kasaframe/core": "^0.0.0",
15
+ "react": "^19.3.0",
16
+ "react-dom": "^19.3.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.3.0",
20
+ "@types/react-dom": "^19.3.0",
21
+ "svelte": "^5.57.2",
22
+ "vue": "^3.5.43"
23
+ },
24
+ "peerDependencies": {
25
+ "vue": ">=3.3",
26
+ "svelte": ">=5"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "vue": {
30
+ "optional": true
31
+ },
32
+ "svelte": {
33
+ "optional": true
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,11 @@
1
+ // 가상 시계가 없을 때(미리보기) seek 모드 페이지의 CSS·WAAPI 애니메이션을 t 에 세운다.
2
+ // 렌더 때는 시계가 애니메이션마다 태어난 시각을 기억해 맞추므로 이걸 쓰지 않는다.
3
+ export function syncCss(t: number, root: Document | Element = document): void {
4
+ const ms = t * 1000;
5
+ const list = root instanceof Document ? root.getAnimations() : root.getAnimations({ subtree: true });
6
+ for (const a of list) {
7
+ if (a.timeline !== document.timeline && a.timeline !== null) continue;
8
+ if (a.playState !== "paused") a.pause();
9
+ a.currentTime = ms * (a.playbackRate || 1);
10
+ }
11
+ }
@@ -0,0 +1,26 @@
1
+ import { onSeek } from "./page.ts";
2
+
3
+ interface TimelineLike {
4
+ pause(): unknown;
5
+ totalTime(t: number, suppressEvents?: boolean): unknown;
6
+ autoRemoveChildren?: boolean;
7
+ }
8
+ interface GsapLike {
9
+ ticker: { lagSmoothing(threshold: number, adjustedLag?: number): void };
10
+ globalTimeline: TimelineLike;
11
+ }
12
+
13
+ // step 모드에서는 가상 rAF·performance.now 로 GSAP 이 스스로 돈다 — 렉 보정만 끈다.
14
+ // seek 모드에서는 타임라인(없으면 globalTimeline)을 멈추고 seek 마다 그 시각으로 옮긴다.
15
+ export function bindGsap(gsap: GsapLike, opts: { timeline?: TimelineLike; from?: number } = {}): () => void {
16
+ gsap.ticker.lagSmoothing(0);
17
+ if (window.__kf?.mode === "step") return () => {};
18
+ const tl = opts.timeline ?? gsap.globalTimeline;
19
+ const from = opts.from ?? 0;
20
+ // globalTimeline 은 끝난 트윈을 빼 버려서, 끝을 지나 찾아갔다가 뒤로 오면 트윈이 사라진다.
21
+ tl.autoRemoveChildren = false;
22
+ tl.pause();
23
+ return onSeek((t) => {
24
+ tl.totalTime(Math.max(0, t - from), true);
25
+ });
26
+ }
@@ -0,0 +1,9 @@
1
+ export { definePage, onSeek, clock } from "./page.ts";
2
+ export type { KfPage, KfPageMeta, KfMarker, KfMode, PageOptions } from "./page.ts";
3
+ export { mountProject, projectMarkers } from "./project.ts";
4
+ export type { KfProjectJson, KfSceneEntry, MountOptions, SceneModule, SceneDraw } from "./project.ts";
5
+ export { syncCss } from "./css.ts";
6
+ export { syncMedia, mediaTime } from "./media.ts";
7
+ export { bindLottie } from "./lottie.ts";
8
+ export { bindGsap } from "./gsap.ts";
9
+ export { bindThree } from "./three.ts";
@@ -0,0 +1,22 @@
1
+ import { onSeek } from "./page.ts";
2
+
3
+ interface LottieLike {
4
+ pause(): void;
5
+ goToAndStop(value: number, isFrame?: boolean): void;
6
+ totalFrames?: number;
7
+ frameRate?: number;
8
+ }
9
+
10
+ // lottie-web 애니메이션을 시각에 묶는다. 만들 때 autoplay:false 를 권한다.
11
+ export function bindLottie(anim: LottieLike, opts: { from?: number; rate?: number; loop?: boolean } = {}): () => void {
12
+ const from = opts.from ?? 0;
13
+ const rate = opts.rate ?? 1;
14
+ anim.pause();
15
+ return onSeek((t) => {
16
+ let ms = Math.max(0, (t - from) * rate * 1000);
17
+ if (opts.loop && anim.totalFrames && anim.frameRate) {
18
+ ms %= (anim.totalFrames / anim.frameRate) * 1000;
19
+ }
20
+ anim.goToAndStop(ms, false);
21
+ });
22
+ }
@@ -0,0 +1,59 @@
1
+ // <video data-kf-from="2.5" data-kf-rate="1"> — 영상 위 시각 from 에 0 초부터 튼다. loop 속성이면 되감는다.
2
+ // 소리는 렌더가 곡을 따로 얹으므로 여기서는 늘 끈다.
3
+ function waitSeeked(v: HTMLVideoElement): Promise<void> {
4
+ const realTimeout = window.__kfClock?.real.setTimeout ?? setTimeout;
5
+ return new Promise((resolve) => {
6
+ const done = () => resolve();
7
+ v.addEventListener("seeked", done, { once: true });
8
+ realTimeout(done, 3000);
9
+ });
10
+ }
11
+
12
+ // seeked 는 디코더 쪽 준비일 뿐, 합성기가 새 프레임을 받기 전에 찍으면 직전 프레임이 나온다.
13
+ // requestVideoFrameCallback 은 멈춘 영상에서 헤드리스가 거의 부르지 않아, 진짜 렌더링 기회를 두 번 넘긴다.
14
+ function waitPresented(): Promise<void> {
15
+ const raf = window.__kfClock?.real.requestAnimationFrame ?? requestAnimationFrame;
16
+ return new Promise((resolve) => raf(() => raf(() => resolve())));
17
+ }
18
+
19
+ export function mediaTime(v: HTMLMediaElement, t: number): number {
20
+ const from = Number(v.dataset.kfFrom ?? 0);
21
+ const rate = Number(v.dataset.kfRate ?? 1);
22
+ let local = Math.max(0, (t - from) * rate);
23
+ const d = v.duration;
24
+ if (Number.isFinite(d) && d > 0) local = v.loop ? local % d : Math.min(local, Math.max(0, d - 0.001));
25
+ return local;
26
+ }
27
+
28
+ export async function syncMedia(t: number, root: ParentNode = document): Promise<void> {
29
+ const rendering = !!window.__kfClock;
30
+ const jobs: Promise<void>[] = [];
31
+ for (const a of Array.from(root.querySelectorAll("audio"))) {
32
+ if (rendering) {
33
+ a.muted = true;
34
+ if (!a.paused) a.pause();
35
+ }
36
+ }
37
+ for (const v of Array.from(root.querySelectorAll("video"))) {
38
+ v.muted = true;
39
+ if (!v.paused) v.pause();
40
+ // 받지 못한 영상은 seeked 가 오지 않는다 — 기다리지 않고 넘긴다(ready 가 이미 경고했다).
41
+ if (v.readyState === HTMLMediaElement.HAVE_NOTHING || v.error) continue;
42
+ const local = mediaTime(v, t);
43
+ if (Math.abs(v.currentTime - local) < 1e-4) continue;
44
+ v.currentTime = local;
45
+ jobs.push(waitSeeked(v));
46
+ }
47
+ await Promise.all(jobs);
48
+ if (jobs.length) {
49
+ await waitPresented();
50
+ // 부하가 걸리면 합성기가 더 늦는다. 렌더러가 이 표시를 보고 그림이 멈출 때까지 다시 찍는다.
51
+ window.__kfMediaMoved = true;
52
+ }
53
+ }
54
+
55
+ declare global {
56
+ interface Window {
57
+ __kfMediaMoved?: boolean;
58
+ }
59
+ }