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,68 @@
1
+ {
2
+ "id": "debug-hud",
3
+ "name": "디버그 HUD",
4
+ "metaphor": "화면이 계측되며 드러난다 — 재는 순간 상자가 서고 값이 찍힌다",
5
+ "status": "approved",
6
+ "approvedAt": "2026-10-08",
7
+ "purposes": [
8
+ "explainer",
9
+ "demo",
10
+ "promo",
11
+ "shorts",
12
+ "launch"
13
+ ],
14
+ "aspect": [
15
+ "16:9",
16
+ "9:16"
17
+ ],
18
+ "effects": [
19
+ {
20
+ "name": "readout",
21
+ "kind": "value",
22
+ "does": "고정 폭 숫자 — 앞을 0 으로 채워 굴러도 폭이 안 흔들리고 NaN 은 「--」",
23
+ "use": "el.textContent = readout(t, { from: 0, to: 170, at: 0.4, dur: 0.6, width: 5, decimals: 1 })"
24
+ },
25
+ {
26
+ "name": "wireBox",
27
+ "kind": "layer",
28
+ "does": "와이어 상자 — 모서리 꺾쇠가 서고 변이 뻗은 뒤 이름이 찍힌다, 크기 표를 단다",
29
+ "use": "const box = wireBox(el, { ctx, boxes: [{ x, y, w, h, at: 0.3, label: \"log\", dims: true }] })"
30
+ },
31
+ {
32
+ "name": "scanLine",
33
+ "kind": "layer",
34
+ "does": "스캔선 — 지나간 자리만 보이게(reveal) 또는 지우게(hide) 하는 clip 을 돌려준다",
35
+ "use": "const scan = scanLine(el, { ctx, at: 0 }); applyStyle(content, scan(t))"
36
+ },
37
+ {
38
+ "name": "logTail",
39
+ "kind": "layer",
40
+ "does": "로그 꼬리 — 줄이 정한 시각에 빠르게 찍히고 칸이 차면 한 줄씩 밀린다",
41
+ "use": "const log = logTail(el, { ctx, x, y, w, h, lines: [{ t: 0.2, text: \"$ kf beats song.mp3\" }] })"
42
+ },
43
+ {
44
+ "name": "plotTrace",
45
+ "kind": "layer",
46
+ "does": "오실로스코프 — 지난 몇 초를 흐르는 선으로, 박 눈금·DROP 표시가 같이 흐른다",
47
+ "use": "const plot = plotTrace(el, { ctx, x, y, w, h, series: [{ label: \"LOW\", value: (t) => ctx.beats.energy(t, \"low\") }] })"
48
+ },
49
+ {
50
+ "name": "crosshair",
51
+ "kind": "layer",
52
+ "does": "조준선 — 가로·세로 가는 선과 과녁, 키 사이를 잡고 멈추듯 옮긴다",
53
+ "use": "const aim = crosshair(el, { ctx, keys: beats.map((b) => ({ t: b, x, y: yOf(b) })), area: [x, y, w, h] })"
54
+ },
55
+ {
56
+ "name": "hudFrame",
57
+ "kind": "layer",
58
+ "does": "HUD 틀 — 안전 영역 꺾쇠, 곡 시각·프레임 번호·마디·구간(그 프레임에 잰 값), grid 면 계측판 눈금이 늘 흘러 화면이 서지 않는다",
59
+ "use": "const frame = hudFrame(el, { ctx, title: \"kf beats · song.mp3\", songFrom: 39.58, barFrom: 28, grid: {} })"
60
+ },
61
+ {
62
+ "name": "dropTrigger",
63
+ "kind": "layer",
64
+ "does": "드롭 — 화면이 형광으로 뒤집히고 가로 띠가 어긋났다 돌아온다, 흔들림을 돌려준다",
65
+ "use": "const s = trig(t); applyStyle(content, s.shake) // trig = dropTrigger(el, { ctx, drop: 0, label: \"DROP 3/6\" })"
66
+ }
67
+ ]
68
+ }
@@ -0,0 +1,47 @@
1
+ # OG 측정 화면 — 재 온 숫자가 쌓이는 계측기
2
+
3
+ **비유 한 줄**: 화면은 계측기다 — 실제 호출이 한 줄 나가고, 원자료 로그가 흐르고, 거기서 숫자가 뽑혀 점·막대·큰 숫자로 쌓인다. 얼굴도 캐릭터도 없다.
4
+
5
+ ## 맞는 내용·용도
6
+
7
+ - 요금·토큰·속도·적중률처럼 **직접 재 본 값**이 주인공인 것 — OG 「재 봤습니다」 연재, 모델 비교, 벤치 결과, 설정 하나 바꾼 전후.
8
+ - 용도: 쇼츠(9:16) · 설명 · 시연 · 홍보의 한 장면. 목소리·자막이 말을 맡고 화면엔 용어와 숫자만.
9
+ - 원자료가 있어야 쓴다 — `measure.json` 같은 호출별 기록(usage 원문·시각·가격). 끝 장면의 612.5ms 는 `sample/measure.ts` 가 OG 를 실제로 18번 불러 만든 값이다.
10
+ - 견본은 공통 주제 「OpenGateway 소개 15초」(`docs/briefs/og-intro-15s.md`). 숫자는 브리프의 사실과 실측만 쓰고, 모델 ID 가 바뀌는 요청 줄은 실제로 부르지 않았으니 결과(상태·지연) 없이 보낸 모양만 보인다.
11
+
12
+ ## 금지
13
+
14
+ - 숫자를 지어내지 않는다. 재 오지 못한 값은 화면에 「시뮬레이션」·「미확인」을 붙이거나 쓰지 않는다. 비용은 반올림해 0 으로 만들지 않는다(첫 유효 자리까지).
15
+ - 문구·광고 카피·가입 유도 금지. OG 는 실험 도구 한 줄(호출 주소)과 끝 로고만.
16
+ - 파랑은 가장 좋은 값 하나에만. 나머지는 회색 막대. 빨강은 오류에만. 그림자·테두리·그라데이션·네온 금지.
17
+ - 차트 선을 매번 처음부터 다시 그리지 않는다 — 점은 들어온 시각에 한 번 찍히고 남는다.
18
+ - 화면을 흔들지 않는다(맞는 용도가 흔들림을 끈다). 드롭의 힘은 `trackCam` 툭(kick)과 `meter` 로.
19
+
20
+ ## 효과 쓰는 법
21
+
22
+ ```ts
23
+ import { applyStyle, type SceneCtx } from "@kasaframe/core";
24
+ import { fx } from "@kasaframe/themes/fx";
25
+ import data from "../assets/data/measure.json";
26
+ const { graphPaper, callLine, rawLog, samplePlot, meter, trackCam } = fx("og-measure");
27
+
28
+ export default {
29
+ build(root: HTMLElement, ctx: SceneCtx) {
30
+ const bg = graphPaper(root, { phase: ctx.from });
31
+ const stage = document.createElement("div");
32
+ stage.style.cssText = "position:absolute;left:0;top:0;width:1920px;height:1080px;transform-origin:0 0";
33
+ root.append(stage);
34
+ const b = ctx.beats.beats.filter((x) => x > -0.01);
35
+ const c = data.calls[0];
36
+ const call = callLine(stage, { x: 90, y: 140, w: 1740, at: 0, url: ["apis.opengateway.ai", "/v1", "/chat", "/completions"], urlAt: b.slice(0, 4), model: c.model, modelAt: b[4], sendAt: b[6], status: c.status, ms: c.ttft_ms, answer: c.text });
37
+ return (t: number) => { bg(t); call(t); applyStyle(stage, trackCam(t, { shots: [{ at: 0 }, { at: b[3], zoom: 1.05, kick: 0.04 }], scene: ctx.id })); };
38
+ },
39
+ };
40
+ ```
41
+
42
+ - 모든 효과는 시각 t 의 순수 함수다. layer 효과는 `build` 에서 한 번 부르고 돌려받은 `(t) => void` 를 장면 함수에서 부른다.
43
+ - 흐름은 「호출 한 줄 → 원자료 로그 → 점·가운데값 → 큰 숫자 하나 → 끝 도장」. 한 장면엔 큰 숫자 하나(display 200 이상)와 굵은 막대 둘·셋.
44
+ - 로그 줄이 들어오는 시각과 점이 찍히는 시각을 같게 둔다 — 「거기서 뽑혔다」가 보인다. 들어오는 박자는 마디·박에.
45
+ - 원자료 로그·축 눈금은 코드 화면이라 글 덩어리가 많다 — 견본은 `kf.json` 의 `text.maxOnScreen` 을 로그 줄·눈금·값을 센 실제 수(30)로 두었다. 로그·차트 밖 글은 셋을 넘기지 않는다.
46
+ - 결과가 서는 순간은 드롭에(`meter` + `trackCam` kick), 끝 도장은 마지막 마디에.
47
+ - **쉬지 않는다** — 화면을 꽉 채우고(글 56–64px, 화면 전체), 박마다 주소 조각·카운터·로그 한 줄·날아가는 숫자(`pickFly`)·막대·칩 중 하나가 크게 움직여 같은 구도가 1.5초를 넘지 않게. 기다리는 동안도 카운터가 실제 속도로 구른다. `trackCam` 샷은 드러냄·결과가 서는 순간에만 — 구도를 메우려고 샷을 박지 않는다(샷 사이 카메라는 선다).
@@ -0,0 +1,432 @@
1
+ import { applyStyle, clamp, emit, ease, progress, safeTime, type Style } from "@kasaframe/core";
2
+ import { typeText } from "@kasaframe/motion";
3
+
4
+ // 비유: 계측기 화면. 실제 호출이 한 줄 나가고, 원자료 로그가 흐르고, 거기서 숫자가 뽑혀 점·막대·큰 숫자로 쌓인다.
5
+ // 화면의 숫자는 늘 재 온 값(measure.json 같은 원자료)에서 온다 — 효과는 숫자를 지어내지 않고, 들어온 시각에 보여 줄 뿐이다.
6
+ // 화면은 0.1초도 쉬지 않는다 — 카메라는 늘 조금씩 다가가며 흐르고, 박마다 글자·줄·점·막대 중 하나가 크게 움직인다.
7
+
8
+ const MONO = "var(--kf-font-mono)";
9
+ const BAR = "rgba(100, 116, 139, 0.28)";
10
+ const snappy = ease.snappy;
11
+
12
+ // OG 움직임 결: 220ms 감속 + 진입 거리
13
+ function rise(t: number, at: number, dur = 0.22, px = 14): Style {
14
+ const p = snappy(progress(t, at, dur));
15
+ return { opacity: p, y: px * (1 - p) };
16
+ }
17
+
18
+ function box(doc: Document, css: string, text = ""): HTMLElement {
19
+ const d = doc.createElement("div");
20
+ d.style.cssText = css;
21
+ if (text) d.textContent = text;
22
+ return d;
23
+ }
24
+
25
+ /**
26
+ * 측정 바탕 — 흰 모눈이 천천히 흐르고 옅은 파란 주사선이 반 주기 간격으로 둘 지나간다. 주사선은 카드 위로도 지나간다 —
27
+ * 카메라가 서 있는 사이 화면을 꽉 채운 카드 밑에 숨으면 박 사이가 멈춘 프레임이 된다. 구도는 안 바꾼다
28
+ */
29
+ export function graphPaper(el: HTMLElement, o: { cell?: number; period?: number; phase?: number } = {}): (t: number) => void {
30
+ const doc = el.ownerDocument;
31
+ const cell = o.cell ?? 56;
32
+ const bg = box(
33
+ doc,
34
+ `position:absolute;inset:0;background:var(--kf-bg);overflow:hidden;` +
35
+ `background-image:linear-gradient(rgba(203,213,225,.55) 1px,transparent 1px),linear-gradient(90deg,rgba(203,213,225,.55) 1px,transparent 1px);` +
36
+ `background-size:${cell}px ${cell}px`,
37
+ );
38
+ const scans = [0, 0.5].map((k) => ({ k, el: box(doc, "position:absolute;top:0;bottom:0;width:120px;z-index:5;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(0,111,214,.07),transparent)") }));
39
+ for (const s of scans) bg.append(s.el);
40
+ el.append(bg);
41
+ const period = o.period ?? 4;
42
+ return (t: number) => {
43
+ const u = safeTime(t) + (o.phase ?? 0);
44
+ bg.style.backgroundPosition = `${(u * 18) % cell}px ${(u * 9) % cell}px`;
45
+ for (const s of scans) s.el.style.transform = `translateX(${((((u / period + s.k) % 1) + 1) % 1) * ((el.clientWidth || 1920) + 240) - 120}px)`;
46
+ };
47
+ }
48
+
49
+ export interface CamShot {
50
+ at: number;
51
+ /** 화면 가운데에 둘 무대 좌표 */
52
+ x?: number;
53
+ y?: number;
54
+ zoom?: number;
55
+ /** 샷이 바뀌는 순간 배율을 이만큼 더했다 걷는다(툭) */
56
+ kick?: number;
57
+ }
58
+
59
+ /**
60
+ * 계측 카메라 — 샷이 바뀌면 220ms 감속으로 옮기며 툭(kick) 치고, 샷 사이에는 선다. 샷은 드러냄·결과처럼 이유가 있을 때만 —
61
+ * 쉬지 않는 몫은 카운터·로그 한 줄·막대가 진다(늘 흐르는 카메라는 kf check camera.wander). drift·pan 을 주면 샷 사이에도 흐르지만 이벤트가 안 남는다.
62
+ * 긴 로그 줄이 반쯤 잘리지 않게 크게 들어가지 않는다(배율 1–1.1). 무대 상자(transform-origin 0 0)에 붙인다.
63
+ */
64
+ export function trackCam(t: number, o: { shots: CamShot[]; drift?: number; pan?: number; w?: number; h?: number; scene?: string }): Style {
65
+ t = safeTime(t);
66
+ const W = o.w ?? 1920;
67
+ const H = o.h ?? 1080;
68
+ const drift = o.drift ?? 0;
69
+ const pan = o.pan ?? 0;
70
+ const shots = o.shots.length ? o.shots : [{ at: 0 }];
71
+ const frame = (i: number, u: number) => {
72
+ const s = shots[i];
73
+ const e = Math.max(0, u - s.at);
74
+ const dir = i % 2 ? -1 : 1;
75
+ return { x: (s.x ?? W / 2) + dir * pan * e, y: (s.y ?? H / 2) + pan * 0.4 * e, z: (s.zoom ?? 1) * (1 + drift * e) };
76
+ };
77
+ let i = 0;
78
+ for (let k = 0; k < shots.length; k++) if (shots[k].at <= t) i = k;
79
+ let f = frame(i, t);
80
+ const since = t - shots[i].at;
81
+ if (i > 0 && since < 0.22) {
82
+ const p = snappy(since / 0.22);
83
+ const a = frame(i - 1, shots[i].at);
84
+ f = { x: a.x + (f.x - a.x) * p, y: a.y + (f.y - a.y) * p, z: a.z + (f.z - a.z) * p };
85
+ emit({ kind: "camera", source: "trackCam", scene: o.scene, value: Math.round((1 - p) * 1000) });
86
+ }
87
+ const kick = since >= 0 ? (shots[i].kick ?? 0) * Math.exp(-since / 0.12) : 0;
88
+ if (since >= 0.22 && kick > 0.0005) emit({ kind: "camera", source: "trackCam", scene: o.scene, value: Math.round(kick * 1000) });
89
+ const z = f.z * (1 + kick);
90
+ return { x: W / 2 - z * f.x, y: H / 2 - z * f.y, scale: z };
91
+ }
92
+
93
+ /**
94
+ * 실제 호출 한 줄 — 주소가 박마다 한 조각씩 붙고, 모델·보낼 글이 찍히고, sendAt 부터 경과 시간이 실제 속도로 굴러
95
+ * 잰 값(ms)에 멈추며 상태·첫 토큰·받은 글이 붙는다. 카운터는 지어낸 값이 아니라 원자료의 ms 까지만 센다.
96
+ */
97
+ export function callLine(
98
+ el: HTMLElement,
99
+ o: {
100
+ x: number;
101
+ y: number;
102
+ w: number;
103
+ at: number;
104
+ url: string[];
105
+ urlAt: number[];
106
+ model?: string;
107
+ modelAt?: number;
108
+ prompt?: string;
109
+ promptAt?: number;
110
+ /** 안 주면 주소 줄만 — 보내고 재는 줄(경과 시간·상태)은 실제로 잰 호출에만 */
111
+ sendAt?: number;
112
+ status?: number;
113
+ ms?: number;
114
+ answer?: string;
115
+ usage?: string;
116
+ usageAt?: number;
117
+ method?: string;
118
+ size?: number;
119
+ },
120
+ ): (t: number) => void {
121
+ const doc = el.ownerDocument;
122
+ const size = o.size ?? 56;
123
+ const card = box(doc, `position:absolute;left:${o.x}px;top:${o.y}px;width:${o.w}px;box-sizing:border-box;padding:${size * 0.7}px ${size * 0.85}px;border-radius:12px;background:var(--kf-surface);font-family:${MONO};font-size:${size}px;line-height:1.45;color:var(--kf-ink);font-variant-numeric:tabular-nums`);
124
+ const row = (css = "") => box(doc, `display:flex;align-items:center;gap:${size * 0.35}px;white-space:nowrap;${css}`);
125
+ const r1 = row();
126
+ const chip = box(doc, `padding:0 ${size * 0.3}px;border-radius:999px;background:var(--kf-accent);color:var(--kf-on-accent);font-size:${size * 0.8}px;font-weight:700`, o.method ?? "POST");
127
+ const urlBox = box(doc, "display:flex");
128
+ const parts = o.url.map((u) => {
129
+ const s = doc.createElement("span");
130
+ s.textContent = u;
131
+ s.style.display = "inline-block";
132
+ urlBox.append(s);
133
+ return s;
134
+ });
135
+ r1.append(chip, urlBox);
136
+ const r2 = row("color:var(--kf-dim)");
137
+ const r3 = row("color:var(--kf-dim)");
138
+ const r4 = row(`margin-top:${size * 0.4}px`);
139
+ const timer = box(doc, `font-family:var(--kf-font-display);font-weight:800;font-size:${size * 1.9}px;letter-spacing:-0.03em;line-height:1;transform-origin:0 50%`);
140
+ const ok = box(doc, `padding:0 ${size * 0.3}px;border-radius:999px;background:var(--kf-success);color:var(--kf-ink);font-size:${size * 0.8}px;font-weight:700`, String(o.status ?? ""));
141
+ const what = box(doc, "color:var(--kf-accent-text);font-weight:700", "첫 토큰");
142
+ const ans = box(doc, "color:var(--kf-ink)");
143
+ r4.append(timer, ok, what, ans);
144
+ const r5 = row(`color:var(--kf-dim);font-size:${size * 0.7}px`);
145
+ card.append(r1, r2, r3, r4, r5);
146
+ el.append(card);
147
+ const ms = o.ms ?? 0;
148
+ const doneAt = o.sendAt === undefined ? Infinity : o.sendAt + ms / 1000;
149
+ return (t: number) => {
150
+ applyStyle(card, rise(t, o.at, 0.22, 40));
151
+ parts.forEach((s, i) => {
152
+ const at = o.urlAt[i] ?? o.at;
153
+ s.style.visibility = t >= at ? "visible" : "hidden";
154
+ applyStyle(s, t >= at ? { ...rise(t, at, 0.18, 0), scale: 1 + 0.25 * (1 - snappy(progress(t, at, 0.18))) } : {});
155
+ });
156
+ r2.textContent = o.model ? typeText(`model: ${o.model}`, t, { at: o.modelAt ?? o.at, cps: 90, jitter: 0, ime: false }).text : "";
157
+ r2.style.display = o.model ? "" : "none";
158
+ r3.textContent = o.prompt && o.promptAt !== undefined && t >= o.promptAt ? `content: "${o.prompt}"` : "";
159
+ applyStyle(r3, o.promptAt !== undefined ? rise(t, o.promptAt) : {});
160
+ const done = t >= doneAt;
161
+ r4.style.display = o.sendAt === undefined ? "none" : "";
162
+ r4.style.visibility = o.sendAt !== undefined && t >= o.sendAt ? "visible" : "hidden";
163
+ timer.textContent = o.sendAt === undefined ? "" : `${Math.min(ms, Math.max(0, Math.floor((t - o.sendAt) * 1000)))}ms`;
164
+ timer.style.color = done ? "var(--kf-accent-text)" : "var(--kf-ink)";
165
+ for (const e of [ok, what, ans]) {
166
+ e.style.display = done ? "" : "none";
167
+ applyStyle(e, rise(t, doneAt));
168
+ }
169
+ applyStyle(timer, done ? { scale: 1 + 0.12 * Math.exp(-(t - doneAt) / 0.1) } : {});
170
+ ans.textContent = done ? typeText(o.answer ?? "", t, { at: doneAt + 0.08, cps: 22, jitter: 0, ime: false }).text : "";
171
+ r5.textContent = o.usage && o.usageAt !== undefined && t >= o.usageAt ? o.usage : "";
172
+ r5.style.display = o.usage ? "" : "none";
173
+ applyStyle(r5, o.usageAt !== undefined ? rise(t, o.usageAt) : {});
174
+ };
175
+ }
176
+
177
+ /**
178
+ * 원자료 로그 — 줄이 들어온 시각에 맨 아래 붙고 한 줄씩 위로 밀려 오른다. pick 정규식이 잡은 숫자는 그 시각부터 파란 칸.
179
+ * 줄 자리는 size·lineHeight·pad 로 정해지니 pickFly 출발점을 장면이 같은 값으로 셀 수 있다(새 줄 = 맨 아래 칸).
180
+ */
181
+ export function rawLog(
182
+ el: HTMLElement,
183
+ o: { x: number; y: number; w: number; rows?: number; at?: number; lines: string[]; times: number[]; pick?: RegExp; size?: number; lineHeight?: number; pad?: number },
184
+ ): (t: number) => void {
185
+ const doc = el.ownerDocument;
186
+ const size = o.size ?? 40;
187
+ const lh = o.lineHeight ?? Math.round(size * 1.5);
188
+ const rows = o.rows ?? 9;
189
+ const pad = o.pad ?? 28;
190
+ const panel = box(doc, `position:absolute;left:${o.x}px;top:${o.y}px;width:${o.w}px;box-sizing:border-box;padding:${pad}px;border-radius:12px;background:var(--kf-surface);font-family:${MONO};font-size:${size}px;line-height:${lh}px;color:var(--kf-ink-soft);font-variant-numeric:tabular-nums`);
191
+ // 밀려 오르는 줄이 안쪽 여백에 반쯤 걸쳐 보이지 않게 줄 칸만큼만 보이는 창을 따로 둔다
192
+ const view = box(doc, `position:relative;height:${rows * lh}px;overflow:hidden`);
193
+ const strip = box(doc, "position:relative");
194
+ view.append(strip);
195
+ panel.append(view);
196
+ el.append(panel);
197
+ const items = o.lines.map((line) => {
198
+ const row = box(doc, `white-space:pre;height:${lh}px`);
199
+ const m = o.pick ? o.pick.exec(line) : null;
200
+ let num: HTMLElement | null = null;
201
+ if (m && m.index !== undefined) {
202
+ const g = m[1] ?? m[0];
203
+ const start = m.index + m[0].indexOf(g);
204
+ num = doc.createElement("span");
205
+ num.textContent = g;
206
+ num.style.cssText = "border-radius:6px;padding:0 4px;margin:0 -4px";
207
+ row.append(doc.createTextNode(line.slice(0, start)), num, doc.createTextNode(line.slice(start + g.length)));
208
+ } else row.textContent = line;
209
+ strip.append(row);
210
+ return { row, num };
211
+ });
212
+ return (t: number) => {
213
+ applyStyle(panel, rise(t, o.at ?? 0, 0.22, 40));
214
+ let shown = 0;
215
+ let slide = 1;
216
+ o.times.forEach((at, i) => {
217
+ if (t >= at) {
218
+ shown = i + 1;
219
+ slide = snappy(progress(t, at, 0.2));
220
+ }
221
+ applyStyle(items[i].row, t >= at ? rise(t, at, 0.2, 0) : { opacity: 0 });
222
+ const n = items[i].num;
223
+ if (n) {
224
+ const on = t >= at;
225
+ n.style.background = on ? "rgba(0,111,214,.16)" : "transparent";
226
+ n.style.color = on ? "var(--kf-accent-text)" : "";
227
+ n.style.fontWeight = on ? "700" : "";
228
+ }
229
+ });
230
+ const over = Math.max(0, shown - rows);
231
+ strip.style.transform = `translateY(${over > 0 ? -(over - 1 + slide) * lh : 0}px)`;
232
+ };
233
+ }
234
+
235
+ /** 뽑힌 숫자 — 로그 줄의 숫자가 그 자리에서 떠올라 호를 그리며 점 그림 자리로 날아가 꽂힌다(박마다 큰 움직임) */
236
+ export function pickFly(
237
+ el: HTMLElement,
238
+ o: { items: { at: number; text: string; from: { x: number; y: number }; to: { x: number; y: number } }[]; dur?: number; size?: number },
239
+ ): (t: number) => void {
240
+ const doc = el.ownerDocument;
241
+ const dur = o.dur ?? 0.3;
242
+ const size = o.size ?? 44;
243
+ const spans = o.items.map((it) => {
244
+ const s = box(doc, `position:absolute;left:0;top:0;font-family:${MONO};font-size:${size}px;font-weight:800;color:var(--kf-accent-text);background:rgba(0,111,214,.14);border-radius:8px;padding:0 8px;white-space:nowrap;opacity:0;z-index:7;font-variant-numeric:tabular-nums`, it.text);
245
+ el.append(s);
246
+ return s;
247
+ });
248
+ return (t: number) => {
249
+ o.items.forEach((it, i) => {
250
+ const s = spans[i];
251
+ const u = (t - it.at) / dur;
252
+ if (u < 0 || u > 1) {
253
+ s.style.opacity = "0";
254
+ return;
255
+ }
256
+ const p = ease.inOutCubic(u);
257
+ const x = it.from.x + (it.to.x - it.from.x) * p;
258
+ const y = it.from.y + (it.to.y - it.from.y) * p - Math.sin(Math.PI * p) * 180;
259
+ s.style.opacity = String(1 - clamp((u - 0.85) / 0.15));
260
+ s.style.transform = `translate(${x}px, ${y}px) translate(-50%, -50%) scale(${1 + 0.5 * Math.sin(Math.PI * p)})`;
261
+ });
262
+ };
263
+ }
264
+
265
+ /** 샘플 점 그림 — 모델마다 재 온 값이 들어온 시각에 점으로 꽂히고, 지금까지의 가운데값 눈금·숫자가 따라 미끄러진다 */
266
+ export function samplePlot(
267
+ el: HTMLElement,
268
+ o: { x: number; y: number; w: number; rows: { label: string; samples: { t: number; v: number }[] }[]; min: number; max: number; unit?: string; decimals?: number; rowH?: number; at?: number; ticks?: number[]; best?: number; size?: number },
269
+ ): (t: number) => void {
270
+ const doc = el.ownerDocument;
271
+ const rowH = o.rowH ?? 230;
272
+ const size = o.size ?? 40;
273
+ const wrap = box(doc, `position:absolute;left:${o.x}px;top:${o.y}px;width:${o.w}px;height:${rowH * o.rows.length + 70}px;font-family:${MONO};font-variant-numeric:tabular-nums`);
274
+ el.append(wrap);
275
+ const X = (v: number) => ((v - o.min) / (o.max - o.min)) * o.w;
276
+ for (const tk of o.ticks ?? []) {
277
+ const line = box(doc, `position:absolute;left:${X(tk)}px;top:0;bottom:54px;width:2px;background:var(--kf-line)`);
278
+ const lab = box(doc, `position:absolute;left:${X(tk)}px;bottom:0;transform:translateX(-50%);font-size:${size * 0.75}px;color:var(--kf-dim)`, `${tk}${o.unit ?? ""}`);
279
+ wrap.append(line, lab);
280
+ }
281
+ const trackY = Math.round(size * 2.6);
282
+ const rows = o.rows.map((r, i) => {
283
+ const top = i * rowH;
284
+ const label = box(doc, `position:absolute;left:0;top:${top}px;font-size:${size}px;color:var(--kf-ink);white-space:nowrap`, r.label);
285
+ const track = box(doc, `position:absolute;left:0;right:0;top:${top + trackY}px;height:10px;border-radius:5px;background:${BAR}`);
286
+ const dots = r.samples.map(() => box(doc, `position:absolute;top:${top + trackY - 19}px;width:48px;height:48px;margin-left:-24px;border-radius:50%;background:var(--kf-accent);opacity:0`));
287
+ const mark = box(doc, `position:absolute;top:${top + trackY - 40}px;width:8px;height:90px;margin-left:-4px;border-radius:4px;background:var(--kf-ink);opacity:0`);
288
+ const val = box(doc, `position:absolute;top:${top + trackY + 30}px;right:0;font-size:${size * 1.35}px;font-weight:700;color:var(--kf-ink);opacity:0;transform-origin:100% 50%`);
289
+ wrap.append(label, track, ...dots, mark, val);
290
+ return { r, dots, mark, val };
291
+ });
292
+ const median = (xs: number[]) => {
293
+ const s = [...xs].sort((a, b) => a - b);
294
+ const m = s.length >> 1;
295
+ return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
296
+ };
297
+ return (t: number) => {
298
+ applyStyle(wrap, rise(t, o.at ?? 0, 0.22, 40));
299
+ rows.forEach(({ r, dots, mark, val }, i) => {
300
+ const got = r.samples.filter((s) => t >= s.t);
301
+ r.samples.forEach((s, k) => {
302
+ const p = snappy(progress(t, s.t, 0.2));
303
+ dots[k].style.left = `${X(s.v)}px`;
304
+ dots[k].style.opacity = String(0.6 * p);
305
+ dots[k].style.transform = `scale(${t >= s.t ? 1.8 - 0.8 * p : 0})`;
306
+ });
307
+ if (!got.length) {
308
+ mark.style.opacity = "0";
309
+ val.style.opacity = "0";
310
+ return;
311
+ }
312
+ // 가운데값은 새 점이 올 때마다 그 자리로 미끄러진다 — 앞 값과 새 값을 220ms 로 잇는다
313
+ const last = got[got.length - 1];
314
+ const prev = got.length > 1 ? median(got.slice(0, -1).map((s) => s.v)) : last.v;
315
+ const now = median(got.map((s) => s.v));
316
+ const k = snappy(progress(t, last.t, 0.22));
317
+ const m = prev + (now - prev) * k;
318
+ mark.style.left = `${X(m)}px`;
319
+ mark.style.opacity = "1";
320
+ mark.style.background = o.best === i ? "var(--kf-accent)" : "var(--kf-ink)";
321
+ val.style.opacity = "1";
322
+ val.style.color = o.best === i ? "var(--kf-accent-text)" : "var(--kf-ink)";
323
+ val.style.transform = `scale(${1 + 0.15 * (1 - k)})`;
324
+ val.textContent = `${m.toFixed(o.decimals ?? 1)}${o.unit ?? ""}`;
325
+ });
326
+ };
327
+ }
328
+
329
+ /** 자리 고정 숫자 — 계기판처럼 앞자리부터 하나씩 멈춰 선다. 멈추기 전 자리는 돌고 있다. 글자 수는 처음부터 끝까지 같다 */
330
+ export function meter(t: number, o: { to: number; at: number; dur?: number; decimals?: number; unit?: string }): string {
331
+ t = safeTime(t);
332
+ const target = o.to.toFixed(o.decimals ?? 0);
333
+ const n = target.replace(/\D/g, "").length || 1;
334
+ const dur = o.dur ?? 0.9;
335
+ let k = 0;
336
+ let out = "";
337
+ for (const ch of target) {
338
+ if (!/\d/.test(ch)) {
339
+ out += ch;
340
+ continue;
341
+ }
342
+ const settle = o.at + (dur * (k + 1)) / n;
343
+ out += t >= settle ? ch : String(Math.floor((t * 26 + k * 3.7) % 10 + 10) % 10);
344
+ k++;
345
+ }
346
+ return `${out}${o.unit ?? ""}`;
347
+ }
348
+
349
+ /** 막대 — 굵은 막대가 박마다 하나씩 값만큼 자란다. best 하나만 OG 파랑, 나머지는 회색. 값은 막대 끝, delta 를 주면 그 박에 차이 칩 */
350
+ export function rankBars(
351
+ el: HTMLElement,
352
+ o: { x: number; y: number; w: number; items: { label: string; value: number; at: number; delta?: string; deltaAt?: number }[]; max: number; best?: number | number[]; prefix?: string; unit?: string; decimals?: number; barH?: number; gap?: number; size?: number },
353
+ ): (t: number) => void {
354
+ const doc = el.ownerDocument;
355
+ const barH = o.barH ?? 76;
356
+ const size = o.size ?? 38;
357
+ const gap = o.gap ?? 40;
358
+ const step = barH + gap + size * 1.4;
359
+ const bests = [o.best ?? []].flat();
360
+ const wrap = box(doc, `position:absolute;left:${o.x}px;top:${o.y}px;width:${o.w}px;font-family:${MONO};font-variant-numeric:tabular-nums`);
361
+ el.append(wrap);
362
+ const rows = o.items.map((it, i) => {
363
+ const top = i * step;
364
+ const label = box(doc, `position:absolute;left:0;top:${top}px;font-size:${size}px;color:var(--kf-ink-soft);white-space:nowrap`, it.label);
365
+ const bar = box(doc, `position:absolute;left:0;top:${top + size * 1.4}px;height:${barH}px;background:${bests.includes(i) ? "var(--kf-accent)" : BAR};transform-origin:0 50%`);
366
+ const val = box(doc, `position:absolute;top:${top + size * 1.4}px;line-height:${barH}px;font-size:${size * 1.2}px;font-weight:700;white-space:nowrap;color:${bests.includes(i) ? "var(--kf-accent-text)" : "var(--kf-ink)"}`);
367
+ const delta = it.delta
368
+ ? box(doc, `position:absolute;top:${top + size * 1.4 + barH / 2}px;padding:4px 16px;border-radius:999px;background:var(--kf-raised);color:var(--kf-ink);font-size:${size}px;font-weight:700;white-space:nowrap;transform-origin:0 50%`, it.delta)
369
+ : null;
370
+ wrap.append(label, bar, val, ...(delta ? [delta] : []));
371
+ return { it, label, bar, val, delta };
372
+ });
373
+ return (t: number) => {
374
+ rows.forEach(({ it, label, bar, val, delta }) => {
375
+ const p = snappy(progress(t, it.at, 0.3));
376
+ const w = (it.value / o.max) * (o.w - 440);
377
+ applyStyle(label, rise(t, it.at));
378
+ bar.style.width = `${w}px`;
379
+ bar.style.transform = `scaleX(${p})`;
380
+ val.style.left = `${w * p + 24}px`;
381
+ val.style.opacity = String(p);
382
+ val.textContent = `${o.prefix ?? ""}${it.value.toFixed(o.decimals ?? 1)}${o.unit ?? ""}`;
383
+ if (delta) {
384
+ const at = it.deltaAt ?? it.at + 0.35;
385
+ const d = snappy(progress(t, at, 0.22));
386
+ delta.style.left = `${w + 24 + size * 6}px`;
387
+ delta.style.opacity = String(d);
388
+ delta.style.transform = `translateY(-50%) scale(${t >= at ? 1.3 - 0.3 * d : 0})`;
389
+ }
390
+ });
391
+ };
392
+ }
393
+
394
+ /** 끝 도장 — 「측정 날짜 · 호출 N번 · $비용 · 키 하나」 한 줄과 OG 로고가 쾅 찍힌다. 비용은 반올림해 0 으로 만들지 않고 첫 유효 자리까지 */
395
+ export function stamp(el: HTMLElement, o: { x: number; y: number; at: number; date: string; calls: number; cost: number; logo?: string; size?: number }): (t: number) => void {
396
+ const doc = el.ownerDocument;
397
+ const size = o.size ?? 36;
398
+ const card = box(doc, `position:absolute;left:${o.x}px;top:${o.y}px;display:flex;align-items:center;gap:${size}px;padding:${size * 0.6}px ${size}px;border-radius:12px;background:var(--kf-raised);font-family:${MONO};font-size:${size}px;color:var(--kf-ink);white-space:nowrap;font-variant-numeric:tabular-nums`);
399
+ if (o.logo) {
400
+ const img = doc.createElement("img");
401
+ img.src = o.logo;
402
+ img.style.cssText = `height:${size * 1.25}px;width:auto;display:block`;
403
+ card.append(img);
404
+ }
405
+ const digits = o.cost > 0 ? Math.max(2, -Math.floor(Math.log10(o.cost))) : 2;
406
+ card.append(box(doc, "", `측정 ${o.date} · 호출 ${o.calls}번 · $${o.cost.toFixed(digits)} · 키 하나`));
407
+ el.append(card);
408
+ return (t: number) => {
409
+ const p = snappy(progress(t, o.at, 0.22));
410
+ card.style.opacity = String(p);
411
+ card.style.transform = `translateX(-50%) translateY(${20 * (1 - p)}px) scale(${1.25 - 0.25 * p})`;
412
+ };
413
+ }
414
+
415
+ /** 칩 — 짧은 낱말·모델 ID·잰 값 하나(「실측」·「openai/ 41」). 220ms 로 크게 들어오고 until 에 오그라들며 빠진다 */
416
+ export function flag(el: HTMLElement, o: { x: number; y: number; text: string; at: number; until?: number; tone?: "accent" | "soft" | "plain"; size?: number; mono?: boolean }): (t: number) => void {
417
+ const doc = el.ownerDocument;
418
+ const soft = o.tone === "soft";
419
+ const chip = box(
420
+ doc,
421
+ `position:absolute;left:${o.x}px;top:${o.y}px;padding:6px 20px;border-radius:999px;font-family:${o.mono ? MONO : "var(--kf-font-sans)"};font-size:${o.size ?? 38}px;font-weight:700;white-space:nowrap;transform-origin:0 50%;font-variant-numeric:tabular-nums;` +
422
+ (o.tone === "plain" ? "background:var(--kf-surface);color:var(--kf-ink)" : soft ? "background:var(--kf-tint);color:var(--kf-accent-text)" : "background:var(--kf-accent);color:var(--kf-on-accent)"),
423
+ o.text,
424
+ );
425
+ el.append(chip);
426
+ return (t: number) => {
427
+ const p = snappy(progress(t, o.at, 0.22));
428
+ const gone = o.until === undefined ? 0 : snappy(progress(t, o.until, 0.12));
429
+ chip.style.opacity = String(p * (1 - gone));
430
+ chip.style.transform = `scale(${t >= o.at ? (1.4 - 0.4 * p) * (1 - 0.6 * gone) : 0.6})`;
431
+ };
432
+ }
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1140 650" width="1140" height="650" fill="none" role="img" aria-label="OpenGateway">
2
+ <title>OpenGateway</title>
3
+ <g id="logo" stroke="currentColor" fill="currentColor">
4
+ <rect id="node-in" x="36" y="36" width="420" height="578" rx="89" fill="none" stroke-width="72"/>
5
+ <rect id="bar-in" x="72" y="272" width="277" height="106" stroke="none"/>
6
+ <path id="node-out" d="M684 213 L684 125 A89 89 0 0 1 773 36 L1015 36 A89 89 0 0 1 1104 125 L1104 525 A89 89 0 0 1 1015 614 L773 614 A89 89 0 0 1 684 525 L684 436" fill="none" stroke-width="72" stroke-linecap="butt"/>
7
+ <g id="arrow">
8
+ <rect id="arrow-shaft" x="420" y="272" width="394" height="106" stroke="none"/>
9
+ <path id="arrow-head" d="M833 220 L970 325 L833 430 Z" stroke-width="38" stroke-linejoin="round"/>
10
+ </g>
11
+ </g>
12
+ </svg>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="_레이어_1" data-name="레이어_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1140 650">
3
+ <!-- Generator: Adobe Illustrator 30.6.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 109) -->
4
+ <g id="node-in">
5
+ <path d="M367,650H125c-68.9,0-125-56.1-125-125V125C0,56.1,56.1,0,125,0h242c68.9,0,125,56.1,125,125v400c0,68.9-56.1,125-125,125ZM125,72c-29.2,0-53,23.8-53,53v400c0,29.2,23.8,53,53,53h242c29.2,0,53-23.8,53-53V125c0-29.2-23.8-53-53-53H125Z"/>
6
+ </g>
7
+ <rect id="bar-in" x="72" y="272" width="277" height="106"/>
8
+ <g id="node-out">
9
+ <path d="M1015,650h-242c-68.9,0-125-56.1-125-125v-89h72v89c0,29.2,23.8,53,53,53h242c29.2,0,53-23.8,53-53V125c0-29.2-23.8-53-53-53h-242c-29.2,0-53,23.8-53,53v88h-72v-88c0-68.9,56.1-125,125-125h242c68.9,0,125,56.1,125,125v400c0,68.9-56.1,125-125,125Z"/>
10
+ </g>
11
+ <path d="M981.6,309.9l-137-105c-5.7-4.4-13.5-5.2-20-2-6.5,3.2-10.6,9.8-10.6,17v52h-394v106h394v52c0,7.2,4.1,13.8,10.6,17,2.7,1.3,5.5,2,8.4,2,4.1,0,8.2-1.3,11.6-3.9l137-105c4.7-3.6,7.4-9.2,7.4-15.1s-2.8-11.5-7.4-15.1Z"/>
12
+ </svg>