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,219 @@
1
+ import * as THREE from "three";
2
+ import { clamp, rand, toEase } from "@kasaframe/core";
3
+ import { GL_TRANSITIONS, type GlShader } from "../transitions2d/gl-transitions/shaders.ts";
4
+ import { REVEAL_FRAGMENT, REVEAL_VARIANTS, SDF_FRAGMENT, SDF_VARIANTS } from "../transitions2d/smoothui/shaders.ts";
5
+ import type { Kit, KindRun } from "./world.ts";
6
+
7
+ export type GlValue = number | boolean | readonly number[];
8
+
9
+ const byName = new Map(GL_TRANSITIONS.map((s) => [s.name, s]));
10
+
11
+ /** gl-transitions 이름들 — `gl:<이름>` 으로 부른다 */
12
+ export const GL_NAMES: readonly string[] = GL_TRANSITIONS.map((s) => s.name);
13
+
14
+ /** SmoothUI 덮개 — `smooth:reveal-<갈래>` · `smooth:sdf-<갈래>` */
15
+ export const SMOOTH_NAMES: readonly string[] = [...REVEAL_VARIANTS.map((v) => `reveal-${v}`), ...SDF_VARIANTS.map((v) => `sdf-${v}`)];
16
+
17
+ export const glShader = (name: string): GlShader | undefined => byName.get(name);
18
+
19
+ // 그림 두 장(앞·뒤 화면) 사이를 그리는 GLSL ES 1.00 — 원본이 WebGL1 용이라 three 의 GLSL3 머리를 안 붙이는 Raw 로 그대로 컴파일한다
20
+ const VERT = /* glsl */ `
21
+ attribute vec3 position;
22
+ attribute vec2 uv;
23
+ varying vec2 _uv;
24
+ void main() {
25
+ _uv = uv;
26
+ gl_Position = vec4(position.xy, 0.0, 1.0);
27
+ }`;
28
+
29
+ // gl-transition 래퍼(MIT, gre)와 같은 꼴 — 그림 비율이 화면과 다르면 cover 로 맞춘다(같으면 그대로)
30
+ const HEAD = /* glsl */ `
31
+ precision highp float;
32
+ varying vec2 _uv;
33
+ uniform sampler2D from, to;
34
+ uniform float progress, ratio, _fromR, _toR;
35
+ vec4 getFromColor(vec2 uv) { return texture2D(from, .5 + (uv - .5) * vec2(min(ratio / _fromR, 1.), min(_fromR / ratio, 1.))); }
36
+ vec4 getToColor(vec2 uv) { return texture2D(to, .5 + (uv - .5) * vec2(min(ratio / _toR, 1.), min(_toR / ratio, 1.))); }
37
+ `;
38
+
39
+ const FOOT = /* glsl */ `
40
+ void main() { gl_FragColor = transition(_uv); }`;
41
+
42
+ const COPY = /* glsl */ `
43
+ precision highp float;
44
+ varying vec2 _uv;
45
+ uniform sampler2D map;
46
+ void main() { gl_FragColor = texture2D(map, _uv); }`;
47
+
48
+ const literal = (type: string, v: GlValue): string => {
49
+ if (typeof v === "boolean") return v ? "true" : "false";
50
+ const num = (n: number) => (type.startsWith("i") || type === "int" ? String(Math.round(n)) : Number.isInteger(n) ? `${n}.0` : String(n));
51
+ return Array.isArray(v) ? `${type}(${v.map(num).join(", ")})` : num(v as number);
52
+ };
53
+
54
+ // 원본 기본값이 형과 어긋나게 적힌 것이 있다(angular 의 float 90, luminance_melt 의 bool 1) — GLSL ES 1.00 은 상수에 형 변환을 안 해 준다
55
+ function fixDefault(type: string, v: string): string {
56
+ if (type === "float" && /^-?\d+$/.test(v)) return `${v}.0`;
57
+ if (type === "bool" && /^[01]$/.test(v)) return v === "1" ? "true" : "false";
58
+ if (type === "int" && /^-?\d+\.\d*$/.test(v)) return String(Math.round(Number(v)));
59
+ return v;
60
+ }
61
+
62
+ // 기본값이 붙은 uniform 은 상수로 — 고칠 값만 params 로 덮는다. 기본값 없는 sampler2D(displacement·luma 무늬)만 uniform 으로 남긴다
63
+ function glSource(shader: GlShader, params: Record<string, GlValue> = {}): string {
64
+ const body = shader.glsl
65
+ .split("\n")
66
+ .map((line) => {
67
+ const p = shader.params.find((q) => line.trim().startsWith("uniform") && new RegExp(`\\b${q.name}\\b`).test(line));
68
+ if (!p || p.type === "sampler2D") return line;
69
+ const value = p.name in params ? literal(p.type, params[p.name]) : fixDefault(p.type, p.value ?? "");
70
+ return `const ${p.type} ${p.name} = ${value};`;
71
+ })
72
+ .join("\n");
73
+ return HEAD + body + FOOT;
74
+ }
75
+
76
+ // 무늬 그림 자리에 꽂는 시드 고정 노이즈 — 같은 시드면 같은 그림
77
+ function noiseTexture(seed: string): THREE.DataTexture {
78
+ const n = 256;
79
+ const data = new Uint8Array(n * n * 4);
80
+ for (let i = 0; i < n * n; i++) {
81
+ const v = Math.floor(rand(seed, i) * 256);
82
+ data.set([v, v, v, 255], i * 4);
83
+ }
84
+ const tex = new THREE.DataTexture(data, n, n);
85
+ tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
86
+ tex.magFilter = tex.minFilter = THREE.LinearFilter;
87
+ tex.needsUpdate = true;
88
+ return tex;
89
+ }
90
+
91
+ const ratioOf = (tex: THREE.Texture, fallback: number): number => {
92
+ const img = tex.image as { width?: number; height?: number; videoWidth?: number; videoHeight?: number } | null;
93
+ const w = img?.videoWidth || img?.width;
94
+ const h = img?.videoHeight || img?.height;
95
+ return w && h ? w / h : fallback;
96
+ };
97
+
98
+ function quad(mat: THREE.Material): THREE.Mesh {
99
+ const m = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), mat);
100
+ m.frustumCulled = false;
101
+ return m;
102
+ }
103
+
104
+ export interface GlOpts {
105
+ /** gl-transitions 매개변수 덮기 — 이름은 셰이더의 uniform 이름 */
106
+ params?: Record<string, GlValue>;
107
+ }
108
+
109
+ /** `gl:<이름>` — gl-transitions 한 조각. 진행도의 순수 함수라 같은 t 면 같은 그림 */
110
+ export function glKind(name: string) {
111
+ const shader = byName.get(name);
112
+ if (!shader) throw new Error(`모르는 gl 전환: ${name} — kf transitions --source gl 로 이름을 본다`);
113
+ return (k: Kit, o: GlOpts): KindRun => {
114
+ const samplers = shader.params.filter((p) => p.type === "sampler2D");
115
+ const noise = samplers.length ? noiseTexture(`${k.seed}:${name}`) : null;
116
+ const uniforms: Record<string, THREE.IUniform> = {
117
+ from: { value: k.from.texture },
118
+ to: { value: k.to.texture },
119
+ progress: { value: 0 },
120
+ ratio: { value: k.aspect },
121
+ _fromR: { value: k.aspect },
122
+ _toR: { value: k.aspect },
123
+ };
124
+ for (const p of samplers) uniforms[p.name] = { value: noise };
125
+ const mat = new THREE.RawShaderMaterial({ vertexShader: VERT, fragmentShader: glSource(shader, o.params), uniforms, depthTest: false, depthWrite: false });
126
+ return passes(k, mat, (u) => {
127
+ uniforms.progress.value = u;
128
+ uniforms._fromR.value = ratioOf(k.from.texture, k.aspect);
129
+ uniforms._toR.value = ratioOf(k.to.texture, k.aspect);
130
+ }, () => noise?.dispose());
131
+ };
132
+ }
133
+
134
+ const easeInOutCubic = toEase("inOutCubic");
135
+
136
+ /**
137
+ * `smooth:reveal-<갈래>`·`smooth:sdf-<갈래>` — SmoothUI 덮개가 덮었다 걷히며 가운데에서 앞 화면이 뒤 화면으로 바뀐다.
138
+ * 0.8 까지 덮개 진행, 절반을 넘으면 바탕을 갈고, 끝 0.2 동안 덮개가 사라진다. 원본은 덮개를 outQuart 로 몰아 첫 15% 에 다 지나가는데,
139
+ * 영상에서는 전환 가운데에 덮개가 가장 짙어야 읽혀서 inOutCubic 으로 고르게 편다
140
+ */
141
+ export function smoothKind(name: string) {
142
+ const [family, variant] = name.split(/-(.*)/s) as [string, string];
143
+ const list: readonly string[] = family === "reveal" ? REVEAL_VARIANTS : family === "sdf" ? SDF_VARIANTS : [];
144
+ const index = list.indexOf(variant);
145
+ if (index < 0) throw new Error(`모르는 SmoothUI 덮개: ${name} (있는 것: ${SMOOTH_NAMES.join(" · ")})`);
146
+ return (k: Kit): KindRun => {
147
+ const uniforms: Record<string, THREE.IUniform> = {
148
+ uRes: { value: new THREE.Vector2(k.w, k.h) },
149
+ uTime: { value: 0 },
150
+ uProgress: { value: 0 },
151
+ uAlpha: { value: 0 },
152
+ uVariant: { value: index },
153
+ };
154
+ const overlay = new THREE.RawShaderMaterial({
155
+ vertexShader: VERT,
156
+ fragmentShader: family === "reveal" ? REVEAL_FRAGMENT : SDF_FRAGMENT,
157
+ uniforms,
158
+ depthTest: false,
159
+ depthWrite: false,
160
+ transparent: true,
161
+ blending: THREE.CustomBlending,
162
+ blendSrc: THREE.OneFactor,
163
+ blendDst: THREE.OneMinusSrcAlphaFactor,
164
+ });
165
+ const top = quad(overlay);
166
+ let base: THREE.Texture = k.from.texture;
167
+ const run = passes(k, null, (u, t) => {
168
+ const p = u < 0.8 ? easeInOutCubic(u / 0.8) : 1;
169
+ uniforms.uProgress.value = p;
170
+ uniforms.uAlpha.value = u < 0.8 ? 1 : 1 - easeInOutCubic(clamp((u - 0.8) / 0.2));
171
+ uniforms.uTime.value = t;
172
+ base = p < 0.5 ? k.from.texture : k.to.texture;
173
+ return base;
174
+ }, () => {
175
+ top.geometry.dispose();
176
+ overlay.dispose();
177
+ }, top);
178
+ return run;
179
+ };
180
+ }
181
+
182
+ /**
183
+ * 판 하나(필요하면 덮개 하나 더)를 화면 꽉 차게 그린다. 양 끝(u ≤ 0, u ≥ 1)은 셰이더를 거치지 않고 앞·뒤 화면 텍셀 그대로 —
184
+ * 원본 셰이더가 진행도 0·1 에서 어떻든 경계 프레임이 이어진다
185
+ */
186
+ function passes(
187
+ k: Kit,
188
+ mat: THREE.RawShaderMaterial | null,
189
+ set: (u: number, t: number) => THREE.Texture | void,
190
+ free: () => void,
191
+ overlay?: THREE.Mesh,
192
+ ): KindRun {
193
+ const copyMat = new THREE.RawShaderMaterial({ vertexShader: VERT, fragmentShader: COPY, uniforms: { map: { value: k.from.texture } }, depthTest: false, depthWrite: false });
194
+ const plane = quad(copyMat);
195
+ const scene = new THREE.Scene();
196
+ scene.add(plane);
197
+ if (overlay) scene.add(overlay);
198
+ const cam = new THREE.Camera();
199
+ return {
200
+ frame(u, t) {
201
+ const base = set(u, t);
202
+ const edge = u <= 0 || u >= 1;
203
+ if (mat && !edge) plane.material = mat;
204
+ else {
205
+ plane.material = copyMat;
206
+ copyMat.uniforms.map.value = edge ? (u >= 1 ? k.to.texture : k.from.texture) : (base ?? k.from.texture);
207
+ }
208
+ if (overlay) overlay.visible = !edge;
209
+ k.renderer.clear(true, true, true);
210
+ k.renderer.render(scene, cam);
211
+ },
212
+ dispose() {
213
+ plane.geometry.dispose();
214
+ copyMat.dispose();
215
+ mat?.dispose();
216
+ free();
217
+ },
218
+ };
219
+ }
@@ -0,0 +1,235 @@
1
+ import { onSeek } from "@kasaframe/adapters";
2
+ import { clamp, emit, frameSample, rand, safeTime, type BeatGrid, type KfStyle, type SceneCtx } from "@kasaframe/core";
3
+ import { FIT, TRANSITIONS_3D, arrivalsOn, durOf, hump, rawProgress, resolveTiming, type FoldStyle, type Timing, type TimingOpts, type Transition3dName, type WindowRect } from "./rig.ts";
4
+ import { camera, inPaint, makeScreen, placeholder, sharedRenderer, NO_FX, type CamFx, type Kit, type KindRun, type Palette, type Screen, type Source } from "./world.ts";
5
+ import { liveNeighbors, type LiveProject } from "./live.ts";
6
+ import { cards } from "./cards.ts";
7
+ import { chain } from "./chain.ts";
8
+ import { dive } from "./dive.ts";
9
+ import { fold } from "./fold.ts";
10
+ import { portal } from "./portal.ts";
11
+ import { shatter } from "./shatter.ts";
12
+ import { zoomOut } from "./zoomOut.ts";
13
+ import { whip } from "./whip.ts";
14
+ import { land, rise, type HeroRect } from "./space.ts";
15
+ import { GL_NAMES, SMOOTH_NAMES, glKind, smoothKind, type GlValue } from "./gl.ts";
16
+
17
+ export { TRANSITIONS_3D, DUR_3D, FIT, FOV, CHAIN_GAP, durOf, resolveTiming, rawProgress, arrivalsOn } from "./rig.ts";
18
+ export { GL_NAMES, SMOOTH_NAMES, glShader } from "./gl.ts";
19
+ export type { GlValue } from "./gl.ts";
20
+ export { diveRig, portalRig, cardsRig, cardSlots, shatterRig, tiles, tileAmount, zoomOutRig, foldRig, chainRig, chainZ, whipRig } from "./rig.ts";
21
+ export type { Transition3dName, Timing, TimingOpts, Pose, FoldStyle, WindowRect, ChainOpts } from "./rig.ts";
22
+ export type { Painter, Source, Palette, LiveDom, VideoSource } from "./world.ts";
23
+ export { elementTextures } from "./world.ts";
24
+ export { sceneSource, liveNeighbors } from "./live.ts";
25
+ export type { LiveProject } from "./live.ts";
26
+ export type { HeroRect } from "./space.ts";
27
+
28
+ export interface Transition3dOpts extends TimingOpts {
29
+ /** 기본 dive */
30
+ kind?: Transition3dName;
31
+ /** 앞 장면 화면. ctx 가 있으면 기본은 assets/seam/<장면id>-from.png(앞 장면 마지막 프레임) */
32
+ from?: Source;
33
+ /** 뒤 장면 화면. 기본 assets/seam/<장면id>-to.png(뒤 장면 첫 프레임) */
34
+ to?: Source;
35
+ /** 앞·뒤 장면을 살아 있는 화면으로 — { kf, scenes: import.meta.glob("./*.ts", { eager: true }) }. 앞 장면이 전환 중에도 계속 돈다 */
36
+ live?: LiveProject;
37
+ /** diveChain — 뚫고 들어갈 화면들(기본 [from, to]) */
38
+ screens?: Source[];
39
+ /** diveChain — 화면마다 꽉 차는 시각(장면 안 초), 또는 박자 격자("beat"·"bar"…)에 한 겹씩 */
40
+ arrivals?: number[] | BeatGrid;
41
+ /** diveChain — 0 흘러가기 … 1 화면마다 멈추기. 기본 0.35 */
42
+ settle?: number;
43
+ width?: number;
44
+ height?: number;
45
+ seed?: string | number;
46
+ colors?: Partial<Palette>;
47
+ /** zoomOut — 앞 장면이 뒤 장면의 어느 창이었나 */
48
+ window?: WindowRect;
49
+ /** fold — accordion 접기 · roll 말기 */
50
+ fold?: FoldStyle;
51
+ /** rise·land — 3D 로 서거나 내려앉을 주인공 상자(화면 비율, 왼쪽 위 0). 안 주면 살아 있는 장면의 [data-kf-hero] 를 프레임마다 잰다 */
52
+ hero?: HeroRect;
53
+ /** gl:<이름> — 셰이더 매개변수 덮기(uniform 이름 → 값) */
54
+ params?: Record<string, GlValue>;
55
+ /** 빠르기 곡선 — ramp(기본): 천천히 들어가다 확 빨라진다 · linear: 종류가 정한 그대로 */
56
+ pace?: "ramp" | "linear";
57
+ /** 3D — 끝에서 살짝 넘쳐 들어갔다 선다(기본 켬) */
58
+ land?: boolean;
59
+ /** 3D — 카메라 충격 시각(장면 안 초). 기본은 이 전환 안에 든 드롭. false 면 끈다 */
60
+ hits?: number[] | false;
61
+ /** 3D — 피사계 심도 조리개(화면 높이 1 기준). 렌더의 모션 블러 하위 프레임 평균에서만 보인다. 기본 0.03, 0 이면 끈다 */
62
+ dof?: number;
63
+ scene?: string;
64
+ source?: string;
65
+ emit?: boolean;
66
+ }
67
+
68
+ export type Transition3dDraw = ((t: number) => void) & { timing: Timing; kind: Transition3dName; dispose(): void };
69
+
70
+ type Build = (k: Kit, o: Transition3dOpts & { arrivals: number[] }) => KindRun & { visible?(t: number): boolean[] };
71
+ const KINDS: Record<(typeof TRANSITIONS_3D)[number], Build> = { dive, portal, cards, shatter, zoomOut, fold, diveChain: chain, whip, rise, land };
72
+
73
+ function builderOf(kind: Transition3dName): Build | undefined {
74
+ if (kind.startsWith("gl:")) return glKind(kind.slice(3));
75
+ if (kind.startsWith("smooth:")) return smoothKind(kind.slice(7));
76
+ return (KINDS as Partial<Record<string, Build>>)[kind];
77
+ }
78
+
79
+ /** 이름 하나로 부를 수 있는 전환 전부 — 3D 일곱 · gl-transitions · SmoothUI 덮개 */
80
+ export const KINDS_ALL: readonly Transition3dName[] = [...TRANSITIONS_3D, ...GL_NAMES.map((n) => `gl:${n}` as const), ...SMOOTH_NAMES.map((n) => `smooth:${n}` as const)];
81
+
82
+ const DEFAULT_PALETTE: Palette = { bg: "#13203d", deep: "#070b18", accent: "#4A90E2", glow: "#A9CBF0", ink: "#EAF2FF" };
83
+
84
+ /** 3D 공간은 어두워야 빛이 산다 — 밝은 스타일이면 밤 색을 깊은 바탕으로 */
85
+ export function paletteOf(style: KfStyle | null | undefined, over: Partial<Palette> = {}): Palette {
86
+ if (!style?.colors) return { ...DEFAULT_PALETTE, ...over };
87
+ const c = style.colors;
88
+ const deep = c.nightBg ?? (style.scheme === "dark" ? c.bg : c.ink);
89
+ return { bg: c.bg, deep, accent: c.accent, glow: c.accentSoft ?? c.accent, ink: c.ink, ...over };
90
+ }
91
+
92
+ const isCtx = (x: unknown): x is SceneCtx => !!x && typeof x === "object" && "meta" in x && "asset" in x && "id" in x;
93
+
94
+ /**
95
+ * 장면 전환을 three.js 3D 로 그린다. 장면 파일의 build 에서 부르고 반환값을 그대로 돌려준다 —
96
+ * `build(root, ctx) { return transition3d(root, ctx, { kind: "dive" }); }`. 같은 t 면 같은 그림이고,
97
+ * 구간 앞은 앞 장면 화면, 끝은 뒤 장면 화면 그대로다(경계 프레임이 이어진다).
98
+ */
99
+ export function transition3d(root: HTMLElement, ctxOrOpts?: SceneCtx | Transition3dOpts | null, more: Transition3dOpts = {}): Transition3dDraw {
100
+ const ctx = isCtx(ctxOrOpts) ? ctxOrOpts : null;
101
+ const o: Transition3dOpts = isCtx(ctxOrOpts) ? more : { ...(ctxOrOpts ?? {}), ...more };
102
+ const kind = o.kind ?? "dive";
103
+ const build = builderOf(kind);
104
+ if (!build) throw new Error(`모르는 3D 전환: ${kind} (있는 것: ${TRANSITIONS_3D.join(" · ")} · gl:<이름> · smooth:<이름> — kf transitions)`);
105
+ const w = o.width ?? ctx?.meta.width ?? 1920;
106
+ const h = o.height ?? ctx?.meta.height ?? 1080;
107
+ // ctx 만 주면(이음매 장면) 앞 장면 마지막 프레임(-1 프레임)에서 뒤 장면 첫 프레임(장면 끝)까지 잇는다.
108
+ // 그 두 프레임은 앞·뒤 장면이 이미 찍으므로 같은 그림이 두 번 나오지 않는다
109
+ const fps = ctx?.meta.fps ?? 60;
110
+ let timing =
111
+ ctx && o.at === undefined && o.dur === undefined && o.endAt === undefined && !o.snap
112
+ ? { at: -1 / fps, dur: ctx.dur + 1 / fps }
113
+ : resolveTiming({ ...o, beats: o.beats ?? ctx?.beats }, durOf(kind), ctx?.dur);
114
+ const pal = paletteOf(ctx?.style, o.colors);
115
+ const seed = String(o.seed ?? ctx?.id ?? kind);
116
+ const scene = o.scene ?? ctx?.id;
117
+ const source = o.source ?? `transition3d:${kind}`;
118
+
119
+ const host = document.createElement("div");
120
+ host.style.cssText = `position:absolute;inset:0;overflow:hidden;background:${pal.deep}`;
121
+ root.append(host);
122
+ const renderer = sharedRenderer(w, h);
123
+ const env = { host, w, h, renderer, sceneFrom: ctx?.from ?? 0 };
124
+ const near = o.live && ctx ? liveNeighbors(ctx, o.live) : {};
125
+ const fromSrc = o.from ?? near.from ?? (ctx ? ctx.asset(`seam/${ctx.id}-from.png`) : undefined);
126
+ const toSrc = o.to ?? near.to ?? (ctx ? ctx.asset(`seam/${ctx.id}-to.png`) : undefined);
127
+ const screens: Screen[] = (o.screens ?? [fromSrc, toSrc]).map((src, i, all) => makeScreen(src, placeholder(pal, i === all.length - 1 && i > 0 ? 1 : 0), env));
128
+
129
+ // 화면이 여럿이면(diveChain) 꽉 차는 시각들. 구간 진행도 u 는 첫 도착 → 마지막 도착
130
+ let arrivals: number[];
131
+ if (Array.isArray(o.arrivals)) arrivals = o.arrivals;
132
+ else if (typeof o.arrivals === "string" && (o.beats ?? ctx?.beats)) arrivals = arrivalsOn((o.beats ?? ctx?.beats)!, o.arrivals, o.at ?? Math.max(0, timing.at), screens.length);
133
+ else arrivals = screens.map((_, i) => timing.at + (timing.dur * i) / Math.max(1, screens.length - 1));
134
+ if (o.screens || o.arrivals) timing = { at: arrivals[0], dur: arrivals[arrivals.length - 1] - arrivals[0] };
135
+
136
+ const kit: Kit = { renderer, cam: camera(w / h), aspect: w / h, w, h, seed, pal, from: screens[0], to: screens[screens.length - 1], screens };
137
+ const run = build(kit, { ...o, arrivals });
138
+ const live = screens.some((s) => s.live);
139
+
140
+ // 이 전환이 그리는 동안 다른 전환이 캔버스에 남긴 살아 있는 DOM 은 스냅숏에서 뺀다
141
+ const hideOthers = () => {
142
+ const mine = new Set(screens.map((s) => s.box).filter(Boolean));
143
+ for (const el of Array.from(kit.renderer.domElement.children) as HTMLElement[]) if (!mine.has(el) && el.style.display !== "none") el.style.display = "none";
144
+ };
145
+
146
+ // 카메라 효과 — 3D 종류만. 셰이더 전환(gl·smooth)은 화면 평면이라 빠르기 곡선만 받는다
147
+ const is3d = !kind.startsWith("gl:") && !kind.startsWith("smooth:");
148
+ const ramp = (o.pace ?? "ramp") === "ramp";
149
+ const land = is3d && o.land !== false;
150
+ const aperture = is3d ? (o.dof ?? 0.03) : 0;
151
+ const drops = ctx?.beats?.drops ?? [];
152
+ const hits = !is3d || o.hits === false ? [] : (o.hits ?? drops.filter((d) => d > timing.at && d < timing.at + timing.dur));
153
+ const fx: CamFx = { ...NO_FX, shake: [0, 0], jitter: [0, 0] };
154
+ kit.cam.userData.fx = fx;
155
+ const effects = (u: number, t: number) => {
156
+ const inside = u > 0 && u < 1;
157
+ // 끝 18% — 시선 방향으로 넘쳐 들어갔다가 돌아와 선다. 끝에서 값·빠르기 모두 0
158
+ const s = clamp((u - 0.82) / 0.18);
159
+ fx.dolly = land && inside ? 0.07 * FIT * Math.sin(Math.PI * s) ** 2 * (1 - s) : 0;
160
+ let sx = 0;
161
+ let sy = 0;
162
+ let amp = 0;
163
+ let punch = 0;
164
+ for (const [i, h] of hits.entries()) {
165
+ const dt = t - h;
166
+ if (!inside || dt < 0 || dt > 0.5) continue;
167
+ const env = Math.exp(-dt / 0.11);
168
+ amp += 0.01 * env;
169
+ sx += 0.01 * env * Math.sin(2 * Math.PI * 17 * dt + rand(seed, 40 + i) * 6.283);
170
+ sy += 0.01 * env * Math.sin(2 * Math.PI * 13 * dt + rand(seed, 50 + i) * 6.283);
171
+ punch -= 7 * Math.exp(-dt / 0.16) * (1 - Math.exp(-dt / 0.02));
172
+ }
173
+ fx.shake = [sx, sy];
174
+ fx.fov = punch;
175
+ // 화면 높이 1 = 꽉 찬 화면이라 흔들림 0.01 은 높이의 1% — 검사가 px 로 센다. 흔들림이 0 을 지나는 프레임에도 남겨야
176
+ // 한 번 친 흔들림이 끊긴 여러 번으로 세이지 않는다 — 잦아드는 폭(amp)이 0.5px 넘는 동안 이어서 남긴다
177
+ const px = Math.hypot(sx, sy) * h;
178
+ if (amp * h > 0.5 && o.emit !== false) emit({ kind: "shake", source: `${source}:hit`, scene, value: Math.round(px * 10) / 10 });
179
+ const smp = frameSample();
180
+ if (aperture > 0 && inside && smp.count > 1) {
181
+ // 황금각 나선 — 하위 프레임들이 조리개 원판을 고르게 덮는다
182
+ const r = Math.sqrt((smp.index + 0.5) / smp.count) * aperture * hump(u);
183
+ const a = smp.index * 2.399963 + rand(seed, 60) * 6.283;
184
+ fx.jitter = [r * Math.cos(a), r * Math.sin(a)];
185
+ } else fx.jitter = [0, 0];
186
+ };
187
+
188
+ let pending: number | null = null;
189
+ const paint = (t: number) => {
190
+ const u = rawProgress(t, timing);
191
+ effects(u, t);
192
+ // 천천히 들어가다 확 빨라진다 — 출발 빠르기는 0.3 배로 남겨 멈춰 보이지 않게
193
+ run.frame(ramp ? 0.3 * u + 0.7 * u * u * u : u, t);
194
+ };
195
+ const draw = ((t: number) => {
196
+ t = safeTime(t);
197
+ kit.renderer = sharedRenderer(w, h);
198
+ if (kit.renderer.domElement.parentNode !== host) host.append(kit.renderer.domElement);
199
+ const vis = run.visible?.(t);
200
+ screens.forEach((s, i) => {
201
+ s.show(vis ? vis[i] : true);
202
+ s.update(t);
203
+ });
204
+ if (live) hideOthers();
205
+ if (live || screens.some((s) => s.loaded())) pending = t;
206
+ paint(t);
207
+ const u = rawProgress(t, timing);
208
+ if (o.emit !== false && u > 0 && u < 1) emit({ kind: "move", source, scene, name: `transition3d:${kind}`, value: 1 });
209
+ }) as Transition3dDraw;
210
+ // seek 의 그리기 단계 뒤 — 영상은 엔진이 시각을 맞춘 뒤, DOM 은 이번 DOM 으로 찍힌 스냅숏(paint 이벤트) 안에서 올리고 같은 t 로 다시 그린다.
211
+ // React 장면은 첫 seek 에서야 지어져 페이지 ready 가 그림을 못 기다리므로 여기서 받는다
212
+ const off = onSeek(async () => {
213
+ if (pending === null) return;
214
+ const t = pending;
215
+ pending = null;
216
+ await Promise.all(screens.map((s) => s.loaded()));
217
+ // React 커밋 안에서 불렸으면 살아 있는 React 장면의 flushSync 가 미뤄졌을 수 있다 — 바깥에서 한 번 더(같은 t 라 같은 화면)
218
+ for (const s of screens) s.update(t);
219
+ const again = () => {
220
+ for (const s of screens) s.refresh();
221
+ paint(t);
222
+ };
223
+ if (screens.some((s) => s.live === "dom")) await inPaint(kit.renderer.domElement, again);
224
+ else again();
225
+ });
226
+ draw.timing = timing;
227
+ draw.kind = kind;
228
+ draw.dispose = () => {
229
+ off();
230
+ run.dispose();
231
+ for (const s of screens) s.dispose();
232
+ host.remove();
233
+ };
234
+ return draw;
235
+ }
@@ -0,0 +1,70 @@
1
+ import { createBeats, silentBeats, type SceneCtx } from "@kasaframe/core";
2
+ import type { KfProjectJson, KfSceneEntry, SceneModule } from "@kasaframe/adapters";
3
+ import { reactScene } from "@kasaframe/adapters/react";
4
+ import type { LiveDom, Source } from "./world.ts";
5
+
6
+ /** 장면 파일 모음 — main.ts 처럼 import.meta.glob("./*.ts", { eager: true }) 그대로 */
7
+ export interface LiveProject {
8
+ kf: Pick<KfProjectJson, "scenes">;
9
+ scenes: Record<string, unknown>;
10
+ }
11
+
12
+ function baseName(p: string): string {
13
+ return p.split("/").pop()!.replace(/\.[^.]+$/, "");
14
+ }
15
+
16
+ function moduleOf(scenes: Record<string, unknown>, entry: KfSceneEntry): SceneModule | null {
17
+ const file = entry.file?.replace(/^\.\//, "").replace(/^scenes\//, "");
18
+ for (const [key, mod] of Object.entries(scenes)) {
19
+ const k = key.replace(/^\.\//, "");
20
+ if (baseName(k) === entry.id || (file && (k === file || k.endsWith(`/${file}`)))) {
21
+ const m = mod as { default?: SceneModule } & SceneModule;
22
+ return m.default ?? m;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * 다른 장면 하나를 살아 있는 화면으로 — 그 장면 모듈을 한 벌 더 지어 time(t) 시각으로 그린다.
30
+ * 전환 중에도 앞 장면 타자·카운터가 이어서 움직인다.
31
+ */
32
+ export function sceneSource(mod: SceneModule, ctx: SceneCtx, entry: Pick<KfSceneEntry, "id" | "from" | "dur">, time: (t: number) => number, still?: Source): LiveDom {
33
+ return {
34
+ dom(root) {
35
+ // 장면 뿌리와 같은 꼴. data-kf-scene 은 달지 않는다 — 검사가 진짜 장면 뿌리로 센다
36
+ const box = document.createElement("div");
37
+ box.style.cssText = "position:absolute;inset:0;overflow:hidden;";
38
+ root.append(box);
39
+ const sub: SceneCtx = {
40
+ ...ctx,
41
+ id: entry.id,
42
+ from: entry.from,
43
+ dur: entry.dur,
44
+ beats: ctx.beats?.data ? createBeats(ctx.beats.data, entry.from) : silentBeats(entry.dur),
45
+ };
46
+ if (mod.react) return reactScene(box, mod.react, sub);
47
+ return mod.build?.(box, sub) ?? undefined;
48
+ },
49
+ time,
50
+ still,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * 이음매 장면의 앞·뒤 장면을 살아 있는 화면으로. 앞 장면은 자기 끝을 지나 계속 돌고(앞 장면 시각 = 앞 장면 길이 + t),
56
+ * 뒤 장면은 첫 프레임에 서 있다가 이음매가 끝나는 순간 제 시각을 시작한다. HTML-in-Canvas 가 없으면 assets/seam 정지 그림.
57
+ */
58
+ export function liveNeighbors(ctx: SceneCtx, project: LiveProject): { from?: LiveDom; to?: LiveDom } {
59
+ const list = project.kf.scenes;
60
+ const i = list.findIndex((s) => s.id === ctx.id);
61
+ if (i < 0) return {};
62
+ const prev = list[i - 1];
63
+ const next = list[i + 1];
64
+ const pm = prev && moduleOf(project.scenes, prev);
65
+ const nm = next && moduleOf(project.scenes, next);
66
+ return {
67
+ from: pm ? sceneSource(pm, ctx, prev, (t) => prev.dur + t, ctx.asset(`seam/${ctx.id}-from.png`)) : undefined,
68
+ to: nm ? sceneSource(nm, ctx, next, (t) => Math.max(0, t - ctx.dur), ctx.asset(`seam/${ctx.id}-to.png`)) : undefined,
69
+ };
70
+ }
@@ -0,0 +1,115 @@
1
+ import * as THREE from "three";
2
+ import { FIT, PORTAL_DEPTH, portalRig, rr } from "./rig.ts";
3
+ import { aim, disposeTree, dust, imageMaterial, raw, sky, type Kit } from "./world.ts";
4
+
5
+ // 앞 장면 판에 둥근 구멍. 반지름 0 이면 테두리 빛도 0 이라 텍셀 그대로
6
+ const HOLE_FRAG = /* glsl */ `
7
+ uniform sampler2D map;
8
+ uniform vec2 center;
9
+ uniform float radius;
10
+ uniform float aspect;
11
+ uniform vec3 rimColor;
12
+ uniform vec3 hotColor;
13
+ varying vec2 vUv;
14
+ void main() {
15
+ vec2 p = (vUv - 0.5) * vec2(aspect, 1.0);
16
+ float d = length(p - center);
17
+ if (d < radius) discard;
18
+ vec3 c = texture2D(map, vUv).rgb;
19
+ float rim = min(0.035, radius * 0.4);
20
+ if (rim > 0.0) {
21
+ float k = 1.0 - smoothstep(radius, radius + rim, d);
22
+ float halo = (1.0 - smoothstep(radius, radius + rim * 6.0, d)) * 0.35;
23
+ c = mix(c, rimColor, halo);
24
+ c = mix(c, hotColor, k);
25
+ }
26
+ gl_FragColor = vec4(c, 1.0);
27
+ }`;
28
+
29
+ // 터널 안벽 — 고리와 세로줄, 깊을수록 빛 쪽으로
30
+ const TUNNEL_FRAG = /* glsl */ `
31
+ uniform vec3 base;
32
+ uniform vec3 line;
33
+ uniform vec3 glow;
34
+ uniform float len;
35
+ varying vec2 vUv;
36
+ void main() {
37
+ float z = vUv.y * len;
38
+ vec2 g = vec2(vUv.x * 28.0, z * 1.6);
39
+ vec2 w = fwidth(g);
40
+ vec2 l = abs(fract(g - 0.5) - 0.5) / max(w, 1e-4);
41
+ float ring = 1.0 - min(l.y / 1.2, 1.0);
42
+ float rib = 1.0 - min(l.x / 0.9, 1.0);
43
+ float depth = 1.0 - vUv.y;
44
+ vec3 c = base + line * (ring * 0.9 + rib * 0.35) * (0.4 + 0.6 * depth);
45
+ c = mix(c, glow, smoothstep(0.55, 1.0, depth) * 0.6);
46
+ gl_FragColor = vec4(c, 1.0);
47
+ }`;
48
+
49
+ const VERT = /* glsl */ `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`;
50
+
51
+ export function portal(k: Kit) {
52
+ const { aspect, pal, seed } = k;
53
+ const env = { aspect, seed };
54
+ const scene = new THREE.Scene();
55
+ scene.add(sky({ top: pal.deep, bottom: "#020308", glow: pal.accent, dir: [0, 0, -1], amt: 0.5, pow: 10 }));
56
+
57
+ const holeMat = new THREE.ShaderMaterial({
58
+ vertexShader: VERT,
59
+ fragmentShader: HOLE_FRAG,
60
+ uniforms: {
61
+ map: { value: k.from.texture },
62
+ center: { value: new THREE.Vector2() },
63
+ radius: { value: 0 },
64
+ aspect: { value: aspect },
65
+ rimColor: { value: raw(pal.accent) },
66
+ hotColor: { value: raw(pal.glow) },
67
+ },
68
+ });
69
+ scene.add(new THREE.Mesh(new THREE.PlaneGeometry(aspect, 1), holeMat));
70
+
71
+ const R = 1.45;
72
+ const len = PORTAL_DEPTH + 0.6;
73
+ const tube = new THREE.Mesh(
74
+ new THREE.CylinderGeometry(R, R, len, 96, 1, true),
75
+ new THREE.ShaderMaterial({
76
+ vertexShader: VERT,
77
+ fragmentShader: TUNNEL_FRAG,
78
+ uniforms: { base: { value: raw(pal.deep) }, line: { value: raw(pal.accent) }, glow: { value: raw(pal.glow) }, len: { value: len } },
79
+ side: THREE.BackSide,
80
+ }),
81
+ );
82
+ // 원통 축(y)을 화면 깊이(-z)로 눕힌다. uv.y 1 이 입구 쪽
83
+ tube.rotation.x = -Math.PI / 2;
84
+ tube.position.z = -len / 2 - 0.02;
85
+ scene.add(tube);
86
+
87
+ const n = 700;
88
+ const pts = new Float32Array(n * 3);
89
+ for (let i = 0; i < n; i++) {
90
+ const a = rr(seed, 500 + i, 0, Math.PI * 2);
91
+ const r = rr(seed, 1500 + i, 0.55, R - 0.08);
92
+ pts[i * 3] = Math.cos(a) * r;
93
+ pts[i * 3 + 1] = Math.sin(a) * r;
94
+ pts[i * 3 + 2] = rr(seed, 2500 + i, -PORTAL_DEPTH + FIT + 0.3, -0.2);
95
+ }
96
+ scene.add(dust(pts, pal.glow, 0.012));
97
+
98
+ const toPlane = new THREE.Mesh(new THREE.PlaneGeometry(aspect, 1), imageMaterial(k.to.texture));
99
+ toPlane.position.z = -PORTAL_DEPTH;
100
+ scene.add(toPlane);
101
+
102
+ return {
103
+ frame(u: number) {
104
+ const s = portalRig(u, env);
105
+ aim(k.cam, s.cam);
106
+ holeMat.uniforms.radius.value = s.radius;
107
+ holeMat.uniforms.center.value.set(s.center[0], s.center[1]);
108
+ k.renderer.clear(true, true, true);
109
+ k.renderer.render(scene, k.cam);
110
+ },
111
+ dispose() {
112
+ disposeTree(scene);
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,29 @@
1
+ import { useLayoutEffect, useRef, type CSSProperties } from "react";
2
+ import { useCtx, useSceneId, useT } from "../react/time.tsx";
3
+ import { transition3d, type Transition3dDraw, type Transition3dOpts } from "./index.ts";
4
+
5
+ /**
6
+ * 3D 전환을 React 장면에. KfScene 안이면 시각·ctx(크기·스타일·박자·seam 그림 기본 경로)를 알아서 받는다.
7
+ * `<KfScene t={t} ctx={ctx}><Transition3D kind="portal" /></KfScene>`
8
+ */
9
+ export function Transition3D(props: Transition3dOpts & { t?: number; style?: CSSProperties; className?: string }) {
10
+ const t = useT(props.t);
11
+ const ctx = useCtx();
12
+ const scene = useSceneId();
13
+ const host = useRef<HTMLDivElement>(null);
14
+ const draw = useRef<Transition3dDraw | null>(null);
15
+ const { t: _t, style, className, ...o } = props;
16
+ // 재료가 바뀔 때만 three 장면을 새로 짓는다 — 시각만 바뀌면 다시 그리기만
17
+ useLayoutEffect(() => {
18
+ const d = transition3d(host.current!, ctx, { scene, ...o });
19
+ draw.current = d;
20
+ return () => {
21
+ d.dispose();
22
+ draw.current = null;
23
+ };
24
+ }, [ctx, scene, o.kind, o.from, o.to, o.live, o.screens, o.arrivals, o.settle, o.at, o.dur, o.endAt, o.snap, o.beats, o.seed, o.fold, o.window?.x, o.window?.y, o.window?.h, o.width, o.height, JSON.stringify(o.colors ?? null)]);
25
+ useLayoutEffect(() => {
26
+ draw.current?.(t);
27
+ });
28
+ return <div ref={host} className={className} style={{ position: "absolute", inset: 0, ...style }} />;
29
+ }