@biunow/agent-biuart 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/bin/server.mjs +575 -0
  2. package/data/styles.json +533 -0
  3. package/engine/audit_clip.py +46 -0
  4. package/engine/clip.html +24 -0
  5. package/engine/clip.js +104 -0
  6. package/engine/clip_schema.json +19 -0
  7. package/engine/clips/t1_3b1b.js +203 -0
  8. package/engine/clips/t2_keynote_ui.js +347 -0
  9. package/engine/clips/t3_finance_chart.js +206 -0
  10. package/engine/clips/y1_kurzgesagt.js +183 -0
  11. package/engine/clips/y2_vox.js +150 -0
  12. package/engine/clips/y3_whiteboard.js +209 -0
  13. package/engine/clips/y4_storytime.js +194 -0
  14. package/engine/clips/y5_kinetic_type.js +134 -0
  15. package/engine/compare.py +19 -0
  16. package/engine/demos/_shared/hero/c1_0.png +0 -0
  17. package/engine/demos/_shared/hero/c1_1.png +0 -0
  18. package/engine/demos/_shared/hero/c1_2.png +0 -0
  19. package/engine/demos/_shared/hero/c3_line.png +0 -0
  20. package/engine/demos/_shared/hero.js +96 -0
  21. package/engine/demos/long_scroll/eras.js +23 -0
  22. package/engine/demos/long_scroll/frames/8bit_act/01.png +0 -0
  23. package/engine/demos/long_scroll/frames/8bit_act/02.png +0 -0
  24. package/engine/demos/long_scroll/frames/8bit_act/03.png +0 -0
  25. package/engine/demos/long_scroll/frames/8bit_act/04.png +0 -0
  26. package/engine/demos/long_scroll/frames/8bit_act/05.png +0 -0
  27. package/engine/demos/long_scroll/frames/8bit_act/06.png +0 -0
  28. package/engine/demos/long_scroll/frames/8bit_act/07.png +0 -0
  29. package/engine/demos/long_scroll/frames/8bit_act/08.png +0 -0
  30. package/engine/demos/long_scroll/frames/8bit_act/meta.json +69 -0
  31. package/engine/demos/long_scroll/frames/8bit_walk/01.png +0 -0
  32. package/engine/demos/long_scroll/frames/8bit_walk/02.png +0 -0
  33. package/engine/demos/long_scroll/frames/8bit_walk/03.png +0 -0
  34. package/engine/demos/long_scroll/frames/8bit_walk/04.png +0 -0
  35. package/engine/demos/long_scroll/frames/8bit_walk/05.png +0 -0
  36. package/engine/demos/long_scroll/frames/8bit_walk/06.png +0 -0
  37. package/engine/demos/long_scroll/frames/8bit_walk/07.png +0 -0
  38. package/engine/demos/long_scroll/frames/8bit_walk/08.png +0 -0
  39. package/engine/demos/long_scroll/frames/8bit_walk/meta.json +69 -0
  40. package/engine/demos/long_scroll/frames/egypt_dodge/01.png +0 -0
  41. package/engine/demos/long_scroll/frames/egypt_dodge/02.png +0 -0
  42. package/engine/demos/long_scroll/frames/egypt_dodge/03.png +0 -0
  43. package/engine/demos/long_scroll/frames/egypt_dodge/04.png +0 -0
  44. package/engine/demos/long_scroll/frames/egypt_dodge/05.png +0 -0
  45. package/engine/demos/long_scroll/frames/egypt_dodge/06.png +0 -0
  46. package/engine/demos/long_scroll/frames/egypt_dodge/07.png +0 -0
  47. package/engine/demos/long_scroll/frames/egypt_dodge/08.png +0 -0
  48. package/engine/demos/long_scroll/frames/egypt_dodge/meta.json +69 -0
  49. package/engine/demos/long_scroll/frames/egypt_walk8/01.png +0 -0
  50. package/engine/demos/long_scroll/frames/egypt_walk8/02.png +0 -0
  51. package/engine/demos/long_scroll/frames/egypt_walk8/03.png +0 -0
  52. package/engine/demos/long_scroll/frames/egypt_walk8/04.png +0 -0
  53. package/engine/demos/long_scroll/frames/egypt_walk8/05.png +0 -0
  54. package/engine/demos/long_scroll/frames/egypt_walk8/06.png +0 -0
  55. package/engine/demos/long_scroll/frames/egypt_walk8/07.png +0 -0
  56. package/engine/demos/long_scroll/frames/egypt_walk8/08.png +0 -0
  57. package/engine/demos/long_scroll/frames/egypt_walk8/meta.json +69 -0
  58. package/engine/demos/long_scroll/frames/monet_throw/01.png +0 -0
  59. package/engine/demos/long_scroll/frames/monet_throw/02.png +0 -0
  60. package/engine/demos/long_scroll/frames/monet_throw/03.png +0 -0
  61. package/engine/demos/long_scroll/frames/monet_throw/04.png +0 -0
  62. package/engine/demos/long_scroll/frames/monet_throw/05.png +0 -0
  63. package/engine/demos/long_scroll/frames/monet_throw/06.png +0 -0
  64. package/engine/demos/long_scroll/frames/monet_throw/07.png +0 -0
  65. package/engine/demos/long_scroll/frames/monet_throw/08.png +0 -0
  66. package/engine/demos/long_scroll/frames/monet_throw/meta.json +71 -0
  67. package/engine/demos/long_scroll/frames/monet_walk/01.png +0 -0
  68. package/engine/demos/long_scroll/frames/monet_walk/02.png +0 -0
  69. package/engine/demos/long_scroll/frames/monet_walk/03.png +0 -0
  70. package/engine/demos/long_scroll/frames/monet_walk/04.png +0 -0
  71. package/engine/demos/long_scroll/frames/monet_walk/05.png +0 -0
  72. package/engine/demos/long_scroll/frames/monet_walk/06.png +0 -0
  73. package/engine/demos/long_scroll/frames/monet_walk/07.png +0 -0
  74. package/engine/demos/long_scroll/frames/monet_walk/08.png +0 -0
  75. package/engine/demos/long_scroll/frames/monet_walk/meta.json +71 -0
  76. package/engine/demos/long_scroll/lib/acts.js +87 -0
  77. package/engine/demos/long_scroll/lib/crossing.js +463 -0
  78. package/engine/demos/long_scroll/lib/world.js +143 -0
  79. package/engine/demos/long_scroll/lib/xing2_ding.js +42 -0
  80. package/engine/demos/long_scroll/lib/xing2_jia.js +114 -0
  81. package/engine/demos/long_scroll/lib/xing2_yi.js +133 -0
  82. package/engine/demos/long_scroll/segments/02_egypt.js +234 -0
  83. package/engine/demos/long_scroll/segments/14_8bit.js +118 -0
  84. package/engine/demos/long_scroll/segments/28_monet.js +86 -0
  85. package/engine/demos/t1_3b1b/eras.js +11 -0
  86. package/engine/demos/t1_3b1b/t1_s1.js +101 -0
  87. package/engine/demos/t1_3b1b/t1_s2.js +74 -0
  88. package/engine/demos/t1_3b1b/t1_s3.js +65 -0
  89. package/engine/demos/t2_keynote_ui/eras.js +10 -0
  90. package/engine/demos/t2_keynote_ui/t2_s1.js +57 -0
  91. package/engine/demos/t2_keynote_ui/t2_s2.js +70 -0
  92. package/engine/demos/t2_keynote_ui/t2_s3.js +61 -0
  93. package/engine/demos/t3_finance_chart/eras.js +11 -0
  94. package/engine/demos/t3_finance_chart/t3_s1.js +48 -0
  95. package/engine/demos/t3_finance_chart/t3_s2.js +42 -0
  96. package/engine/demos/t3_finance_chart/t3_s3.js +51 -0
  97. package/engine/demos/y1_kurzgesagt/eras.js +10 -0
  98. package/engine/demos/y1_kurzgesagt/kurzgesagt.js +347 -0
  99. package/engine/demos/y2_vox/eras.js +10 -0
  100. package/engine/demos/y2_vox/vox.js +240 -0
  101. package/engine/demos/y3_whiteboard/eras.js +10 -0
  102. package/engine/demos/y3_whiteboard/whiteboard.js +162 -0
  103. package/engine/demos/y4_storytime/eras.js +14 -0
  104. package/engine/demos/y4_storytime/s1_desk.js +18 -0
  105. package/engine/demos/y4_storytime/s2_screen.js +82 -0
  106. package/engine/demos/y4_storytime/s3_react.js +39 -0
  107. package/engine/demos/y4_storytime/s4_wide.js +16 -0
  108. package/engine/demos/y4_storytime/s5_close.js +18 -0
  109. package/engine/demos/y4_storytime/story.js +25 -0
  110. package/engine/demos/y4_storytime/vo_env.js +2 -0
  111. package/engine/demos/y4_storytime/world.js +73 -0
  112. package/engine/demos/y5_kinetic_type/eras.js +16 -0
  113. package/engine/demos/y5_kinetic_type/k1_title.js +38 -0
  114. package/engine/demos/y5_kinetic_type/k2_complete.js +55 -0
  115. package/engine/demos/y5_kinetic_type/k3_chat.js +47 -0
  116. package/engine/demos/y5_kinetic_type/k4_agent.js +49 -0
  117. package/engine/demos/y5_kinetic_type/kinetic.js +52 -0
  118. package/engine/engine.js +277 -0
  119. package/engine/eras.js +60 -0
  120. package/engine/eras_gallery.js +115 -0
  121. package/engine/eras_style_studies.js +20 -0
  122. package/engine/examples/assets/K/347/272/277/345/233/276_/346/250/252/345/261/217.png +0 -0
  123. package/engine/examples/assets//347/273/210/347/253/257_for/345/276/252/347/216/257/346/261/202/345/222/214.png +0 -0
  124. package/engine/examples/assets//347/273/210/347/253/257_sum/346/261/202/345/222/214.png +0 -0
  125. package/engine/examples/assets//350/220/245/346/224/266/345/233/276_/347/253/226/345/261/217.png +0 -0
  126. package/engine/examples/assets//350/264/242/347/273/217/345/233/276/350/241/250_/346/250/252/345/261/217.png +0 -0
  127. package/engine/examples/t1_3b1b.json +19 -0
  128. package/engine/examples/t2_fullbleed_pulse.json +15 -0
  129. package/engine/examples/t2_keynote_ui.json +15 -0
  130. package/engine/examples/t3_finance_chart.json +29 -0
  131. package/engine/examples/y1_kurzgesagt.json +16 -0
  132. package/engine/examples/y2_vox.json +14 -0
  133. package/engine/examples/y3_whiteboard.json +17 -0
  134. package/engine/examples/y4_storytime.json +52 -0
  135. package/engine/examples/y5_kinetic_type.json +14 -0
  136. package/engine/index.html +42 -0
  137. package/engine/lib/THREE_LICENSE.txt +21 -0
  138. package/engine/lib/brush.js +149 -0
  139. package/engine/lib/camera.js +99 -0
  140. package/engine/lib/chart.js +152 -0
  141. package/engine/lib/collage.js +97 -0
  142. package/engine/lib/diagram.js +264 -0
  143. package/engine/lib/finish.js +70 -0
  144. package/engine/lib/fonts/AlfaSlabOne-400.woff +0 -0
  145. package/engine/lib/fonts/Anton-400.woff +0 -0
  146. package/engine/lib/fonts/ArchivoBlack-400.woff +0 -0
  147. package/engine/lib/fonts/Bangers-400.woff +0 -0
  148. package/engine/lib/fonts/Caveat-600.woff +0 -0
  149. package/engine/lib/fonts/Cinzel-400.woff +0 -0
  150. package/engine/lib/fonts/Cinzel-700.woff +0 -0
  151. package/engine/lib/fonts/CormorantGaramond-500.woff +0 -0
  152. package/engine/lib/fonts/CormorantGaramond-500i.woff +0 -0
  153. package/engine/lib/fonts/CormorantGaramond-700.woff +0 -0
  154. package/engine/lib/fonts/EBGaramond-400.woff +0 -0
  155. package/engine/lib/fonts/EBGaramond-400i.woff +0 -0
  156. package/engine/lib/fonts/EBGaramond-600.woff +0 -0
  157. package/engine/lib/fonts/Exo2-800i.woff +0 -0
  158. package/engine/lib/fonts/GochiHand-400.woff +0 -0
  159. package/engine/lib/fonts/IMFellEnglish-400.woff +0 -0
  160. package/engine/lib/fonts/IMFellEnglish-400i.woff +0 -0
  161. package/engine/lib/fonts/Inter-var.woff +0 -0
  162. package/engine/lib/fonts/JosefinSans-700.woff +0 -0
  163. package/engine/lib/fonts/Kalam-700.woff +0 -0
  164. package/engine/lib/fonts/LICENSES.md +40 -0
  165. package/engine/lib/fonts/LXGWWenKai-500.woff +0 -0
  166. package/engine/lib/fonts/LibreBaskerville-400.woff +0 -0
  167. package/engine/lib/fonts/LibreBaskerville-700.woff +0 -0
  168. package/engine/lib/fonts/LilitaOne-400.woff +0 -0
  169. package/engine/lib/fonts/Marcellus-400.woff +0 -0
  170. package/engine/lib/fonts/NotoSansSC-500.woff +0 -0
  171. package/engine/lib/fonts/NotoSansSC-700.woff +0 -0
  172. package/engine/lib/fonts/NotoSansSC-800.woff +0 -0
  173. package/engine/lib/fonts/NotoSansSC-900.woff +0 -0
  174. package/engine/lib/fonts/NotoSansSC-sub.woff +0 -0
  175. package/engine/lib/fonts/NotoSerifJP-600.woff +0 -0
  176. package/engine/lib/fonts/NotoSerifSC-sub.woff +0 -0
  177. package/engine/lib/fonts/OFL.txt +94 -0
  178. package/engine/lib/fonts/PlayfairDisplay-400i.woff +0 -0
  179. package/engine/lib/fonts/PlayfairDisplay-700.woff +0 -0
  180. package/engine/lib/fonts/PlayfairDisplay-700i.woff +0 -0
  181. package/engine/lib/fonts/Poppins-600.woff +0 -0
  182. package/engine/lib/fonts/Poppins-700.woff +0 -0
  183. package/engine/lib/fonts/Poppins-800.woff +0 -0
  184. package/engine/lib/fonts/PressStart2P-400.woff +0 -0
  185. package/engine/lib/fonts/Righteous-400.woff +0 -0
  186. package/engine/lib/fonts/RobotoCondensed-var.woff +0 -0
  187. package/engine/lib/fonts/Rye-400.woff +0 -0
  188. package/engine/lib/fonts/UnifrakturMaguntia-400.woff +0 -0
  189. package/engine/lib/fonts/cmunrm.woff +0 -0
  190. package/engine/lib/fonts/cmunti.woff +0 -0
  191. package/engine/lib/fonts/index.json +1 -0
  192. package/engine/lib/fonts.js +56 -0
  193. package/engine/lib/kit.js +91 -0
  194. package/engine/lib/media.js +118 -0
  195. package/engine/lib/medium.js +258 -0
  196. package/engine/lib/motion.js +111 -0
  197. package/engine/lib/paint.js +200 -0
  198. package/engine/lib/palettes.js +57 -0
  199. package/engine/lib/post.js +108 -0
  200. package/engine/lib/render.js +189 -0
  201. package/engine/lib/rig.js +390 -0
  202. package/engine/lib/rig_huashu.js +171 -0
  203. package/engine/lib/three.min.js +7 -0
  204. package/engine/lib/toon.js +186 -0
  205. package/engine/lib/typo.js +136 -0
  206. package/engine/lib/ui.js +141 -0
  207. package/engine/lib/util.js +92 -0
  208. package/engine/render.py +157 -0
  209. package/engine/scenes/01_cave.js +364 -0
  210. package/engine/scenes/02_egypt.js +524 -0
  211. package/engine/scenes/03_greek.js +448 -0
  212. package/engine/scenes/04_roman.js +555 -0
  213. package/engine/scenes/05_gothic.js +522 -0
  214. package/engine/scenes/06_renaissance.js +338 -0
  215. package/engine/scenes/08_impressionism.js +206 -0
  216. package/engine/scenes/09_postimp.js +183 -0
  217. package/engine/scenes/10_nouveau.js +334 -0
  218. package/engine/scenes/11_cubism.js +265 -0
  219. package/engine/scenes/12_bauhaus.js +214 -0
  220. package/engine/scenes/13_pop.js +269 -0
  221. package/engine/scenes/14_8bit.js +238 -0
  222. package/engine/scenes/15_raytrace.js +300 -0
  223. package/engine/scenes/16_2026.js +565 -0
  224. package/engine/scenes/17_ink.js +273 -0
  225. package/engine/scenes/18_klimt.js +227 -0
  226. package/engine/scenes/19_munch.js +153 -0
  227. package/engine/scenes/20_dunhuang.js +215 -0
  228. package/engine/scenes/21_kusama.js +124 -0
  229. package/engine/scenes/22_constructivism.js +205 -0
  230. package/engine/scenes/23_dali.js +221 -0
  231. package/engine/scenes/24_hopper.js +150 -0
  232. package/engine/scenes/25_ghibli.js +251 -0
  233. package/engine/scenes/26_vaporwave.js +191 -0
  234. package/engine/scenes/27_kirby.js +255 -0
  235. package/engine/scenes/28_monet.js +176 -0
  236. package/engine/scenes/29_seurat.js +104 -0
  237. package/engine/scenes/30_matisse.js +151 -0
  238. package/engine/scenes/31_haring.js +109 -0
  239. package/engine/scenes/32_rembrandt.js +217 -0
  240. package/engine/scenes/33_rubberhose.js +219 -0
  241. package/engine/scenes/34_shadowpuppet.js +195 -0
  242. package/engine/scenes/35_shinkai.js +197 -0
  243. package/engine/scenes/36_picasso_blue.js +123 -0
  244. package/engine/scenes/38_giotto.js +133 -0
  245. package/engine/scenes/39_piero.js +53 -0
  246. package/engine/scenes/40_raphael.js +96 -0
  247. package/engine/scenes/41_michelangelo.js +112 -0
  248. package/engine/scenes/42_guardi.js +70 -0
  249. package/engine/scenes/43_lorenzetti.js +145 -0
  250. package/engine/scenes/44_cortona.js +86 -0
  251. package/engine/scenes/45_renoir.js +86 -0
  252. package/engine/scenes/46_pissarro.js +82 -0
  253. package/engine/scenes/47_sisley.js +94 -0
  254. package/engine/scenes/48_gauguin.js +107 -0
  255. package/engine/scenes/49_chagall.js +60 -0
  256. package/engine/scenes/50_ma_yuan.js +131 -0
  257. package/engine/scenes/51_persian_miniature.js +149 -0
  258. package/engine/scenes/52_south_park.js +105 -0
  259. package/engine/scenes/53_pixar.js +60 -0
  260. package/engine/scenes/54_ghibli_lab.js +68 -0
  261. package/engine/scenes/55_dunhuang.js +161 -0
  262. package/engine/scenes/index.js +28 -0
  263. package/engine/spec_assets.py +76 -0
  264. package/engine/transitions.js +929 -0
  265. package/engine//346/227/266/344/273/243/344/275/234/350/200/205/350/247/204/350/214/203.md +49 -0
  266. package/lib/host.mjs +754 -0
  267. package/lib/render-bridge.mjs +543 -0
  268. package/lib/render-cli.mjs +98 -0
  269. package/lib/storyboard-lint.mjs +54 -0
  270. package/manifest.json +52 -0
  271. package/package.json +8 -4
  272. package/runtime/package.json +8 -0
  273. package/skills/biuart/SKILL.md +154 -0
  274. package/skills/biuart/references/01-/346/213/206/350/247/243.md +55 -0
  275. package/skills/biuart/references/02-/346/234/272/345/210/266.md +41 -0
  276. package/skills/biuart/references/03-/344/270/200/345/270/247/345/205/210/350/241/214/345/233/233/346/235/241/350/267/257/347/272/277.md +39 -0
  277. package/skills/biuart/references/04-/347/272/257/344/273/243/347/240/201/347/273/230/345/210/266.md +92 -0
  278. package/skills/biuart/references/05-/350/212/202/345/245/217/344/270/216/351/205/215/344/271/220.md +19 -0
  279. package/skills/biuart/references/06-/345/217/243/346/222/255/351/251/261/345/212/250/347/232/204/350/211/272/346/234/257/347/237/255/347/211/207.md +31 -0
  280. package/skills/biuart/references/07-/346/255/243/351/235/242/347/273/217/351/252/214.md +196 -0
  281. package/skills/biuart/references/08-/351/243/216/346/240/274/344/275/234/350/200/205/350/247/204/350/214/203.md +61 -0
  282. package/skills/biuart/references/09-/350/247/206/351/242/221/345/212/250/347/224/273/350/257/255/346/263/225.md +382 -0
  283. package/skills/biuart/references/10-/350/247/222/350/211/262.md +34 -0
  284. package/skills/biuart/references/11-/351/225/277/345/215/267/347/251/277/350/266/212/347/211/207.md +43 -0
  285. package/skills/biuart/references/12-/345/217/243/346/222/255/346/225/264/347/211/207/344/270/216/347/273/217/351/252/214/345/233/236/346/265/201.md +25 -0
  286. package/skills/biuart/references/13-/345/217/243/346/222/255/344/270/216/350/257/255/351/237/263/345/244/215/345/210/273.md +7 -0
  287. package/skills/biuart/references/15-/350/267/250/345/234/272/346/231/257/351/243/216/346/240/274/346/217/220/347/202/274.md +48 -0
  288. package/skills/biuart/references/16-/351/243/216/346/240/274/346/274/224/347/244/27295/345/210/206/345/215/217/350/256/256.md +103 -0
  289. package/skills/biuart/references/about-author.md +99 -0
  290. package/skills/biuart/references/capabilities.md +91 -0
  291. package/skills/biuart/references/compatibility.md +34 -0
  292. package/skills/biuart/references/images.md +97 -0
  293. package/skills/biuart/references/voice-model-review.md +18 -0
  294. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t1_3b1b.md +122 -0
  295. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t2_keynote_ui.md +117 -0
  296. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t3_finance_chart.md +116 -0
  297. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y1_kurzgesagt.md +193 -0
  298. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y2_vox.md +195 -0
  299. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y3_whiteboard.md +190 -0
  300. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y4_storytime.md +166 -0
  301. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y5_kinetic_type.md +158 -0
  302. package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y6_presenter_explainer.md +24 -0
  303. package/skills/biuart/references//345/217/243/346/222/255API.md +125 -0
  304. package/skills/biuart/references//350/211/262/346/235/277.md +38 -0
  305. package/skills/biuart/references//350/247/222/350/211/262/347/262/276/347/273/206/345/272/246.md +61 -0
  306. package/skills/biuart/references//351/225/234/345/244/264/350/241/250/346/250/241/346/235/277.md +80 -0
  307. package/skills/biuart/references//351/225/234/345/244/264/350/257/255/350/250/200.md +73 -0
  308. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/01_cave.md +46 -0
  309. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/02_egypt.md +64 -0
  310. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/03_greek.md +50 -0
  311. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/04_roman.md +99 -0
  312. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/05_gothic.md +32 -0
  313. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/06_renaissance.md +41 -0
  314. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/08_impressionism.md +34 -0
  315. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/09_postimp.md +17 -0
  316. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/10_nouveau.md +31 -0
  317. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/11_cubism.md +30 -0
  318. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/12_bauhaus.md +30 -0
  319. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/13_pop.md +29 -0
  320. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/14_8bit.md +27 -0
  321. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/15_raytrace.md +32 -0
  322. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/16_2026.md +55 -0
  323. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/17_ink.md +50 -0
  324. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/18_klimt.md +37 -0
  325. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/19_munch.md +42 -0
  326. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/20_dunhuang.md +49 -0
  327. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/21_kusama.md +32 -0
  328. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/22_constructivism.md +35 -0
  329. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/23_dali.md +29 -0
  330. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/24_hopper.md +30 -0
  331. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/25_ghibli.md +55 -0
  332. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/26_vaporwave.md +27 -0
  333. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/27_kirby.md +30 -0
  334. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/28_monet.md +39 -0
  335. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/29_seurat.md +31 -0
  336. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/30_matisse.md +26 -0
  337. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/31_haring.md +22 -0
  338. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/32_rembrandt.md +29 -0
  339. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/33_rubberhose.md +31 -0
  340. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/34_shadowpuppet.md +40 -0
  341. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/35_shinkai.md +34 -0
  342. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/36_picasso_blue.md +25 -0
  343. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/37_rick_morty.md +48 -0
  344. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/38_giotto.md +67 -0
  345. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/39_piero.md +59 -0
  346. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/40_raphael.md +65 -0
  347. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/41_michelangelo.md +65 -0
  348. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/42_guardi.md +65 -0
  349. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/43_lorenzetti.md +69 -0
  350. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/44_cortona.md +66 -0
  351. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/45_renoir.md +66 -0
  352. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/46_pissarro.md +66 -0
  353. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/47_sisley.md +67 -0
  354. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/48_gauguin.md +66 -0
  355. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/49_chagall.md +59 -0
  356. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/50_ma_yuan.md +68 -0
  357. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/51_persian_miniature.md +66 -0
  358. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/52_south_park.md +65 -0
  359. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/53_pixar.md +61 -0
  360. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/55_dunhuang.md +41 -0
  361. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/INDEX.md +89 -0
  362. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/350/275/254/345/234/272_/350/277/201/347/247/273/346/265/213/350/257/225.md +53 -0
  363. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/207/215/345/201/232/350/277/233/345/272/246.md +37 -0
  364. package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/237/263/350/275/250_/350/211/272/346/234/257/345/217/262/351/200/237/351/200/232.md +135 -0
  365. package/studio/app.js +380 -0
  366. package/studio/assert-panel-permit.mjs +67 -0
  367. package/studio/composer.css +465 -0
  368. package/studio/composer.js +480 -0
  369. package/studio/index.html +97 -0
  370. package/studio/optimize-prompt.mjs +84 -0
  371. package/studio/optimize-prompt.test.mjs +47 -0
  372. package/studio/recipes.mjs +87 -0
  373. package/studio/select-menu.js +450 -0
  374. package/studio/styles.css +71 -0
  375. package/studio/to-english-prompt.mjs +149 -0
  376. package/studio/to-english-prompt.test.mjs +72 -0
  377. package/README.md +0 -3
@@ -0,0 +1,162 @@
1
+ // Y3 · 白板手绘解说(RSA Animate 型:一整块大白板+相机移动):「AI 是怎么学会认猫的」
2
+ // 依据:语法卡 y3_whiteboard.md(RSA 实测:黑 #0A0503+唯一强调色橙 #EB701F;写字≈16 字/秒、比口播晚 0.5s;
3
+ // 平滑移动 easeInOutSine 中位 1.8s;远跳用 0.8s 带运动模糊的甩镜;手从右下入画、画完出画;结尾拉远看全图)
4
+ // ①画一只猫、写「猫?」、写问题 → 转场 A「甩镜」:笔不离板,相机 0.8s 甩到板上远处,带运动模糊
5
+ // ②画 1000 万张图的一摞(橙色数字直接弹出、不带手)、箭头、神经网络、「自己找规律」
6
+ // → 转场 B「板上平滑移动」:相机 sine 缓入缓出 0.95s 斜移到旁边空白处,手在相机落定前就开始画
7
+ // ③画花叔指着猫、打勾,橙色「是猫!」弹出 → 收尾「拉远看全图」(RSA / VideoScribe 默认的 Zoom at end),整板铺一层灰
8
+ // 用到的库:DG.board(笔画时间线+跟随的笔)、DG.pen、DG.revealMask/zigzag(人物 Reveal)、CAM(关键帧相机、运动模糊)。
9
+ (() => {
10
+ const W = 1920, H = 1080;
11
+ const { clamp, lerp, rng } = U;
12
+ const seg = MO.seg;
13
+ const Y3 = window.Y3 = {};
14
+ const F0 = 0; Y3.F0 = F0;
15
+ Y3.SHOTS = { s1: [0, 2.5], s2: [2.5, 4.75], s3: [4.75, 8.0] };
16
+ const INK = '#0A0503', ORANGE = '#EF7226', WASH = 'rgba(239,114,38,.22)', BOARD = '#FBFBFB';
17
+ const LW = 6.5; // RSA 实测笔画约画高 0.6–0.7%(1080p≈6–7px)
18
+ const R1 = [960, 540], R2 = [3000, 540], R3 = [3700, 1360], K3 = 0.8;
19
+ const L3 = (x, y) => [R3[0] + (x - 960) * K3, R3[1] + (y - 540) * K3];
20
+ const SPEED = 2600; // 线条笔速:世界像素/秒(演示片比 RSA 实拍快约 2 倍,见卡片)
21
+
22
+ // ---------- 笔画时间线:按顺序排,时长 = 长度 / 笔速 ----------
23
+ const B = DG.board({ ink: INK, lw: LW, speed: SPEED, font: '"LXGWWenKai-500"' });
24
+ const tr = DG.xform, arc = DG.arcPts, pathOf = pts => U.poly(pts);
25
+
26
+ // 猫的笔画(本地坐标,原点 = 坐姿底部中心,约 400 高)
27
+ const catStrokes = (ox, oy, s, sp, w, whiskers = true) => {
28
+ const P = (pts) => tr(pts, ox, oy, s);
29
+ B.line(P([[-60, -250], [-92, -330], [-30, -300], [30, -300], [92, -330], [60, -250]]), { w, speed: sp });
30
+ B.line(P(arc(0, -230, 100, 82, -0.55, Math.PI + 0.55, 26).reverse()), { w, speed: sp });
31
+ B.line(P([[-80, -170], [-120, -60], [-110, 0], [110, 0], [120, -60], [80, -170]]), { w, speed: sp });
32
+ B.line(P([[110, -20], [190, -40], [210, -130], [170, -190]]), { w, speed: sp });
33
+ B.line(P(arc(-38, -240, 12, 16, 0, Math.PI * 2, 14)), { w: w * 0.8, speed: sp * 1.6 });
34
+ B.line(P(arc(38, -240, 12, 16, 0, Math.PI * 2, 14)), { w: w * 0.8, speed: sp * 1.6 });
35
+ B.line(P([[-10, -205], [10, -205], [0, -194], [-10, -205]]), { w: w * 0.8, speed: sp * 1.6, smooth: false });
36
+ if (whiskers) for (const k of [-1, 1]) B.line(P([[k * 30, -196], [k * 110, -206]]), { w: w * 0.6, speed: sp * 2 });
37
+ };
38
+ const catBody = (ox, oy, s) => pathOf(tr([[-80, -170], [-120, -60], [-110, 0], [110, 0], [120, -60], [80, -170], [60, -160], [0, -150], [-60, -160]], ox, oy, s));
39
+
40
+ // ===== 第一块 =====
41
+ B.at(0.12);
42
+ catStrokes(760, 820, 1.0, SPEED * 0.75, LW + 1);
43
+ B.fill(catBody(760, 820, 1.0), WASH);
44
+ B.text('猫?', 1060, 560, 150, { col: ORANGE, rate: 7.5 }); // 标题级:7.5 字/秒
45
+ B.line([[1080, 610], [1020, 660], [930, 690]], { col: ORANGE });
46
+ B.line([[960, 668], [928, 690], [966, 704]], { col: ORANGE, smooth: false });
47
+ B.text('AI 怎么认出它?', 960, 960, 84, { align: 'center' }); // 正文:16 字/秒
48
+ B.line([[1290, 590], [1500, 560], [1760, 540]], { col: ORANGE, speed: SPEED * 1.6 }); // 指向下一块的橙箭头:甩镜有了方向
49
+ B.line([[1725, 512], [1764, 540], [1728, 572]], { col: ORANGE, smooth: false });
50
+ const END1 = B.cur;
51
+ // 甩镜时笔不离板:笔尖在屏幕上从第一块的箭头头滑向第二块的起笔点,板在笔下飞过,留下一条橙色连线(笔一直在画,视线一直被它领着)。
52
+ // 轨迹 = 屏幕上的笔位置按当时的相机反算成世界点(第一次画时算好)。
53
+ const trail = { kind: 'custom', from: [1764, 540], to: [2382, 278], t0: B.cur + 0.01, t1: 3.3, col: ORANGE, w: LW, wob: 14,
54
+ draw(g, q) {
55
+ const s = this;
56
+ if (!s.pts) { const N = 72, a = CAM.toScreen(camAt(s.t0), ...s.from), b = CAM.toScreen(camAt(s.t1), ...s.to); s.pts = [];
57
+ for (let i = 0; i <= N; i++) { const u = i / N, cm = camAt(lerp(s.t0, s.t1, u)), e = MO.sineInOut(u), sx = lerp(a[0], b[0], e), sy = lerp(a[1], b[1], e) + Math.sin(u * Math.PI * 3) * s.wob * Math.sin(u * Math.PI);
58
+ s.pts.push(CAM.toWorld(cm, sx, sy)); }
59
+ s.cum = DG.cum(s.pts); }
60
+ const k = q * (s.pts.length - 1), i = Math.floor(k), f = k - i, d = s.cum[i] + (i < s.pts.length - 1 ? (s.cum[i + 1] - s.cum[i]) * f : 0);
61
+ g.strokeStyle = s.col; g.lineWidth = s.w; return DG.drawPartial(g, s.pts, s.cum, d);
62
+ },
63
+ start() { return this.from; }, end() { return this.pts ? this.pts[this.pts.length - 1] : this.to; } };
64
+ B.push(trail);
65
+
66
+ // ===== 第二块(相机 2.5→3.3 甩过来) =====
67
+ B.at(3.31);
68
+ for (let i = 2; i >= 0; i--) { const x = 2330 + i * 26, y = 330 - i * 26; B.fill(pathOf([[x, y], [x + 260, y], [x + 260, y + 190], [x, y + 190]]), BOARD, 0.01); B.line([[x, y], [x + 260, y], [x + 260, y + 190], [x, y + 190], [x, y + 3]], { w: 6, speed: SPEED * 4.5, smooth: false, amp: 0 }); }
69
+ B.line(tr([[-60, -250], [-92, -330], [-30, -300], [30, -300], [92, -330], [60, -250], ...arc(0, -230, 100, 82, -0.55, Math.PI + 0.55, 20).reverse(), [-60, -250]], 2460, 560, 0.4), { w: 5, speed: SPEED * 3, gap: 0 });
70
+ B.line(tr([[-38, -240], [-38, -236], [38, -236], [38, -240]], 2460, 560, 0.4), { w: 6, speed: SPEED, smooth: false, gap: 0.01 });
71
+ B.pop('×1000万张', 2470, 690, 74, { col: ORANGE });
72
+ B.line([[2680, 450], [2800, 452], [2860, 450]]);
73
+ B.line([[2830, 425], [2864, 450], [2830, 476]], { smooth: false });
74
+ const NET = [[2960, [330, 450, 570]], [3090, [300, 450, 600]], [3220, [400, 500]]];
75
+ for (const [x, ys] of NET) for (const y of ys) B.line(arc(x, y, 26, 26, -Math.PI / 2, Math.PI * 1.55, 16), { w: 6, speed: SPEED * 5, amp: 0.8, min: 0.03, gap: 0.01 });
76
+ for (let a = 0; a < 2; a++) for (const y0 of NET[a][1]) for (const y1 of NET[a + 1][1]) B.segment([NET[a][0] + 26, y0], [NET[a + 1][0] - 26, y1], { w: 3 });
77
+ B.at(B.cur + 0.05);
78
+ B.text('自己找规律', 3090, 780, 64, { align: 'center' });
79
+ // 橙色连线:从网络引向下一块(相机开始移动时画,手领着相机走)
80
+ B.at(4.62); B.line([[3250, 470], [3420, 560], [3480, 760], [3420, 900]], { col: ORANGE, speed: SPEED * 1.4 }); B.line([[3392, 868], [3420, 902], [3452, 872]], { col: ORANGE, smooth: false });
81
+ const END2 = B.cur;
82
+
83
+ // ===== 第三块(相机 5.1→6.05 平滑斜移;手在 5.8 就入画开画) =====
84
+ B.at(5.35);
85
+ // VideoScribe 的 Reveal 画法——完整线稿藏在下面,笔尖从上往下走 8 行之字形,笔过之处揭开(笔尖永远在揭开的前沿上)
86
+ const huashu = () => { const [x, y] = L3(500, 1010); return HUASHU.build({ x, y, s: 0.95 * K3, face: 1, pose: HUASHU.pose('point', 0, { ua1: 1.25, mouth: 0.8 }) }); };
87
+ const rigZig = (HSb) => { if (rigZig.c) return rigZig.c; const js = Object.values(HSb.J).filter(v => Array.isArray(v)); const xs = js.map(v => v[0]), ys = js.map(v => v[1]);
88
+ const x0 = Math.min(...xs) - 70, x1 = Math.max(...xs) + 50, y0 = Math.min(...ys) - 20, y1 = Math.max(...ys) + 40, rows = 8;
89
+ rigZig.rowH = (y1 - y0) / rows; const zz = DG.zigzag(x0, y0, x1 - x0, y1 - y0, rows); return (rigZig.c = { zz, cum: DG.cum(zz) }); };
90
+ const RIG3 = { t0: B.cur, t1: B.cur + 0.7 };
91
+ B.push({ kind: 'custom', t0: RIG3.t0, t1: RIG3.t1,
92
+ draw(g, q, ft) {
93
+ const HSb = huashu(), { zz, cum } = rigZig(HSb), d = cum[cum.length - 1] * q;
94
+ g.save(); if (q < 1) g.clip(DG.revealMask(zz, cum, d, rigZig.rowH * 2.2)); g.lineJoin = 'round'; g.lineCap = 'round'; // 揭完就不再裁:之字形拐角处的三角会漏掉
95
+ const [fx, fy] = L3(500, 1010);
96
+ if (!HERO.y3(g, fx, fy, 0.95 * K3 * 740, ft)) {
97
+ for (const p of HSb.parts) { g.fillStyle = p.role === 'hair' ? INK : BOARD; g.fill(p.path); g.strokeStyle = INK; g.lineWidth = LW * 0.9; g.stroke(p.path); }
98
+ HUASHU.details(g, HSb, { line: INK, lw: 4.5, glass: INK, cheek: null, earLine: 'rgba(0,0,0,.5)', shoeHole: INK });
99
+ }
100
+ g.restore();
101
+ return q < 1 ? DG.pointAt(zz, cum, d) : null;
102
+ },
103
+ start() { return rigZig(huashu()).zz[0]; }, end() { const z = rigZig(huashu()).zz; return z[z.length - 1]; } });
104
+ B.at(RIG3.t1 + 0.03);
105
+ catStrokes(...L3(1150, 900), K3 * 0.95, SPEED * 1.6, LW, false);
106
+ B.line([L3(1000, 330), L3(1070, 400), L3(1240, 200)], { col: ORANGE, w: LW + 2, speed: SPEED * 1.5, smooth: false });
107
+ B.pop('是猫!', ...L3(1500, 410), 150 * K3, { col: ORANGE, align: 'left' });
108
+ // 收尾:拉远的同时圈住「是猫!」,全景停住时回到第一块,在问题下面划一道——问题和答案连上,笔停在最后一笔上
109
+ B.at(Math.max(B.cur + 0.02, 6.95));
110
+ B.line(DG.ellipsePts(...L3(1720, 360), 300 * K3, 120 * K3, -2.9, 1.08, 40), { col: ORANGE, w: LW, speed: SPEED * 1.1 });
111
+ B.at(Math.max(B.cur + 0.06, 7.52));
112
+ B.line([[640, 1000], [960, 1006], [1290, 998]], { col: ORANGE, w: LW + 2, speed: SPEED * 0.9 });
113
+ const END3 = B.cur;
114
+ Y3.timeline = { END1, END2, END3, RIG3 };
115
+ Y3.board = B;
116
+
117
+ // ---------- 相机 ----------
118
+ const OVER = { x: 2400, y: 930, z: 0.45 };
119
+ const camAt = ft => CAM.at([
120
+ { t: 0, x: R1[0], y: R1[1], z: 1.0 },
121
+ { t: 2.5, x: R1[0] + 12, y: R1[1] + 6, z: 1.02, ease: MO.sineInOut },
122
+ // 甩镜:0.8s(RSA 实测远跳约 0.8s,带运动模糊)
123
+ { t: 3.3, x: R2[0], y: R2[1], z: 1.0, ease: MO.cubicInOut },
124
+ { t: 4.7, x: R2[0] + 16, y: R2[1] + 6, z: 1.02, ease: MO.sineInOut },
125
+ // 板上平滑移动:easeInOutSine 0.9s(实测中位 1.8s,演示片压缩)
126
+ { t: 5.6, x: R3[0], y: R3[1] - 20, z: 1.12, ease: MO.sineInOut },
127
+ { t: 6.85, x: R3[0] + 10, y: R3[1] - 14, z: 1.14, ease: MO.sineInOut },
128
+ // 拉远看全图,停 0.5s
129
+ { t: 7.5, ...OVER, ease: MO.sineInOut },
130
+ { t: 8.0, ...OVER, z: OVER.z * 0.985, ease: MO.sineInOut },
131
+ ], ft);
132
+ Y3.camAt = camAt;
133
+
134
+ // ---------- 画 ----------
135
+ const drawBoard = (c, cam, ft) => {
136
+ // 结尾整板铺一层灰(RSA 实测),在拉远时 0.5s 淡入
137
+ const gray = MO.sineInOut(seg(ft, 6.9, 7.4));
138
+ c.fillStyle = gray > 0 ? PAINT.rgb(PAINT.mix(PAINT.hex(BOARD), [238, 238, 236], gray)) : BOARD; c.fillRect(0, 0, W, H);
139
+ // 极淡的擦痕:屏幕空间平铺、跟着相机平移走(不随缩放放大)
140
+ const sm = PAINT.cached('y3smudge', 1024, 1024, g => { const r = rng(5); for (let i = 0; i < 22; i++) { g.fillStyle = `rgba(120,120,130,${0.003 + r() * 0.005})`; g.beginPath(); g.ellipse(r() * 1024, r() * 1024, 80 + r() * 200, 14 + r() * 40, r() * 3, 0, 7); g.fill(); } });
141
+ c.save(); const ox = -(((cam.x * cam.z) % 1024) + 1024) % 1024, oy = -(((cam.y * cam.z) % 1024) + 1024) % 1024; c.translate(ox, oy); c.fillStyle = c.createPattern(sm, 'repeat'); c.fillRect(0, 0, W + 1024, H + 1024); c.restore();
142
+ c.save(); CAM.apply(c, cam);
143
+ const r = B.draw(c, ft);
144
+ c.restore();
145
+ return r;
146
+ };
147
+ const shot = (c, lt, t) => {
148
+ const ft = t - F0, cam = camAt(ft);
149
+ const buf = PAINT.scratch('yt_y3board'), g = buf.getContext('2d'); g.reset();
150
+ const { tip, col } = drawBoard(g, cam, ft);
151
+ const [vx, vy] = CAM.velocity(camAt, ft);
152
+ c.fillStyle = BOARD; c.fillRect(0, 0, W, H);
153
+ const whip = ft > 2.5 && ft < 3.3; // 只有甩镜带模糊;板上平滑移动和结尾拉远都是清楚的(RSA 实测)
154
+ CAM.motionBlur(c, buf, whip ? vx * 0.8 : 0, whip ? vy * 0.8 : 0, 9);
155
+ const [hx, hy] = B.penAt(ft, cam, tip);
156
+ DG.pen(c, hx, hy, col || B.penColor(ft), Math.sin(ft * 7) * 0.03); // 只有一支马克笔,笔尾帽 = 墨色
157
+ };
158
+ Y3.shot1 = shot; Y3.shot2 = shot; Y3.shot3 = shot;
159
+ SCENES['y3_s1'] = { draw: shot };
160
+ SCENES['y3_s2'] = { draw: shot };
161
+ SCENES['y3_s3'] = { draw: shot };
162
+ })();
@@ -0,0 +1,14 @@
1
+ // 示范 · 故事型简笔角色(storytime)「我第一次让 AI 帮我写代码」(8.75 秒五镜)。语法卡:references/动画语法/y4_storytime.md
2
+ // 预览 index.html?film=demos/y4_storytime 渲染 render.py --film demos/y4_storytime --out y4.mp4(口播音轨另配;口型读 vo_env.js 的包络)
3
+ // 镜头按口播切(不是按节拍):一句口播 ≈ 一个镜头,反应镜头放在两句之间的「沉默」里。
4
+ window.PUNCH = 0; // storytime 不卡音乐拍
5
+ window.FILM_DURATION = 8.75;
6
+ window.SCENE_LIBS = ['demos/y4_storytime/vo_env.js', 'demos/y4_storytime/story.js', 'demos/y4_storytime/world.js'];
7
+ window.SCENE_DIR = 'demos/y4_storytime';
8
+ window.ERAS = [
9
+ { id: 's1_desk', dur: 2.0 }, // 中景:对镜头讲
10
+ { id: 's2_screen', dur: 2.4, transition: { type: 'whip', dur: 0.26, dir: 1 } }, // 甩镜 → 屏幕(过肩)。甩在两句之间的换气里,别切在句中
11
+ { id: 's3_react', dur: 1.2, transition: { type: 'smash', dur: 0.32 } }, // 砸镜 → 反应大特写(沉默)
12
+ { id: 's4_wide', dur: 1.65, transition: { type: 'cut', dur: 0.02 } }, // 硬切 → 同一房间大全景
13
+ { id: 's5_close', dur: 1.5, transition: { type: 'cut', dur: 0.02 } }, // 跳近 → 近景,笑点+定住
14
+ ];
@@ -0,0 +1,18 @@
1
+ // S1 中景:花叔坐在桌后对镜头讲。口播「我第一次让AI帮我写代码,」
2
+ // 动:口型(口播包络,一拍二)、说话点头、呼吸、换姿势回弹(talk → point)、眼神跟手、眨眼、窗外云、镜头慢推+微漂。
3
+ // 房间、桌子、笔记本都在 world.js(S4/S5 同一个房间换镜头)。
4
+ (() => {
5
+ const S = STORY;
6
+ const KEYS = [[0, 'talk', 'happy'], [1.12, 'point', 'talk']];
7
+ SCENES['s1_desk'] = {
8
+ draw(c, lt, t) {
9
+ const ts = MO.step(t, 24); // 角色按 24fps(Flash 默认帧率)走,镜头按 60fps 平滑
10
+ const zoom = 1.01 + 0.035 * MO.expoOut(Math.min(1, lt / 2.0)) + 0.03 * (1 - MO.expoOut(Math.min(1, lt / 0.5))); // 开场 1.04→1.01 落定,再慢推 3.5%
11
+ const [dx, dy] = CAM.drift(t, 4, 1);
12
+ CAM.with(c, { x: 900 + dx, y: 520 + dy, z: zoom }, c => {
13
+ const st = S.poseAt(KEYS, ts), al = S.alive(ts, 165);
14
+ S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts), look: ts > 1.12 ? [1, 0] : [0, 0], squash: st.squash * al.breathe, bob: al.bob, tilt: ts > 1.12 ? 0.05 : -0.03 });
15
+ });
16
+ },
17
+ };
18
+ })();
@@ -0,0 +1,82 @@
1
+ // S2 过肩看屏幕:他只打了一行「帮我做个网站」,AI 回了一整面墙的代码,越滚越快,计数器在「三千」那个字上砸出「3000 行」。
2
+ // 动:打字、代码瀑布加速滚、行数计数、3000 砸出+屏幕震、前景花叔后脑勺随呼吸晃。
3
+ (() => {
4
+ const S = STORY, C = S.C, W = 1920, H = 1080;
5
+ const { clamp, lerp, rng } = U;
6
+ const T_TYPE = 2.30, T_SEND = 2.78, T_SLAM = 3.70; // 全片时间;3.70 = 口播「三千」
7
+ // 代码行(色块表示,远看就是代码;颜色取常见编辑器主题)
8
+ const COLS = ['#C792EA', '#82AAFF', '#C3E88D', '#F78C6C', '#89DDFF', '#FFCB6B', '#676E95'];
9
+ const LINES = (() => { const r = rng(42), out = []; let ind = 0;
10
+ for (let i = 0; i < 400; i++) { if (r() < 0.18) ind = Math.min(4, ind + 1); else if (r() < 0.2) ind = Math.max(0, ind - 1);
11
+ const toks = []; let x = ind * 34; const n = 1 + (r() * 4 | 0); for (let k = 0; k < n; k++) { const w = 30 + r() * 150; toks.push([x, w, COLS[(r() * COLS.length) | 0]]); x += w + 14; }
12
+ out.push(toks); } return out; })();
13
+ // 滚动位置:发送后 0.25s 开始,加速(三次方),在 SLAM 时达到每秒上千行的视觉速度
14
+ const scrollAt = t => { const q = Math.max(0, t - T_SEND - 0.25); return 46 * (q * 6 + Math.pow(q * 2.6, 3) * 6); };
15
+ const countAt = t => t >= T_SLAM ? 3000 : Math.min(2999, Math.floor(Math.pow(Math.max(0, (t - T_SEND - 0.25)) / (T_SLAM - T_SEND - 0.25), 2.4) * 2999));
16
+ SCENES['s2_screen'] = {
17
+ draw(c, lt, t) {
18
+ const slam = t - T_SLAM;
19
+ const shake = slam > 0 && slam < 0.3 ? MO.settle(slam, 14, 9, 14) : 0; // 震:9Hz、0.3s 内衰减完
20
+ const zoom = 1.0 + 0.02 * Math.min(1, lt / 2.2) + (slam > 0 ? 0.04 * MO.expoOut(clamp(slam / 0.12)) : 0);
21
+ c.fillStyle = '#15161C'; c.fillRect(0, 0, W, H);
22
+ CAM.with(c, { x: 960 + shake, y: 540 - shake * 0.5, z: zoom }, c => {
23
+ // 屏幕边框
24
+ c.fillStyle = '#2B2D36'; c.beginPath(); c.roundRect(150, 40, 1620, 1000, 34); c.fill();
25
+ c.save(); c.beginPath(); c.roundRect(190, 80, 1540, 920, 14); c.clip();
26
+ c.fillStyle = C.screen; c.fillRect(190, 80, 1540, 920);
27
+ // 我的那一行:右侧气泡,打字
28
+ const sent = MO.at(t, T_SEND, 0.18), scr = scrollAt(t);
29
+ const bubY = 200 - Math.min(scr, 400);
30
+ const msg = TY.typed('帮我做个网站', MO.at(t, T_TYPE, 0.4));
31
+ if (bubY > 60) {
32
+ c.fillStyle = sent > 0 ? '#3B82F6' : '#2F3342'; const bw = TY.width(c, '帮我做个网站', 40, 'PuHui-Bold') + 60;
33
+ c.beginPath(); c.roundRect(1680 - bw, bubY - 52, bw, 78, 24); c.fill();
34
+ TY.text(c, msg + (sent <= 0 && Math.floor(t * 4) % 2 ? '|' : ''), 1680 - bw + 30, bubY + 2, { size: 40, fam: 'PuHui-Bold', color: '#fff', align: 'left' });
35
+ }
36
+ // AI 的回复:代码墙,从气泡下方开始往上滚
37
+ if (t > T_SEND + 0.1) {
38
+ const top = 300 - scr, lh = 46;
39
+ const i0 = Math.max(0, Math.floor((150 - top) / lh)), i1 = Math.min(LINES.length * 8, Math.ceil((1000 - top) / lh));
40
+ for (let i = i0; i < i1; i++) { const y = top + i * lh; if (y < 150 || y > 1000) continue;
41
+ const shown = MO.at(t, T_SEND + 0.1 + Math.min(i, 12) * 0.03, 0.05); if (shown <= 0) continue; // 头几行逐行冒出来
42
+ c.fillStyle = '#4A4F66'; c.fillRect(220, y - 12, 44, 16);
43
+ for (const [x, w, col] of LINES[i % LINES.length]) { c.fillStyle = col; c.globalAlpha = 0.9; c.beginPath(); c.roundRect(300 + x, y - 14, w * shown, 20, 10); c.fill(); }
44
+ c.globalAlpha = 1; }
45
+ // 速度线感:滚太快时整屏加一层竖向拖影
46
+ const v = (scrollAt(t + 1 / 60) - scr) * 60; if (v > 1500) { c.fillStyle = `rgba(32,34,45,${clamp((v - 1500) / 6000) * 0.5})`; c.fillRect(190, 150, 1540, 850); }
47
+ }
48
+ // 顶栏(画在内容之后:滚上去的内容钻到它下面)
49
+ c.fillStyle = '#2A2D3A'; c.fillRect(190, 80, 1540, 64);
50
+ ['#FF5F57', '#FEBC2E', '#28C840'].forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(232 + i * 34, 112, 10, 0, Math.PI * 2); c.fill(); });
51
+ TY.text(c, 'AI助手', 960, 124, { size: 30, fam: 'PuHui-Bold', color: '#9AA3B8' });
52
+ const n = countAt(t);
53
+ if (t > T_SEND + 0.2) TY.text(c, `${n}行`, 1700, 124, { size: 32, fam: 'PuHui-Bold', color: n >= 3000 ? '#FFCB6B' : '#9AA3B8', align: 'right' });
54
+ // 3000 行:砸出来(backOut 过冲 23%)+底板压暗
55
+ if (slam > 0) {
56
+ c.fillStyle = `rgba(10,10,16,${0.6 * MO.expoOut(clamp(slam / 0.15))})`; c.fillRect(190, 80, 1540, 920);
57
+ TY.pop(c, '3000行', 960, 600, clamp(slam / 0.24), { size: 230, fam: 'PuHui-Black', color: '#FFCB6B', over: 3.5, stroke: '#15161C', strokeW: 18 });
58
+ }
59
+ c.restore();
60
+ });
61
+ // 前景:花叔的背影(过肩)。不跟镜头推,只随呼吸起伏 —— 前景与屏幕的视差让镜头「有空间」。
62
+ // 返修:第一版是纯黑剪影,审片读成「另一个影子人」。现在画出主角背面的识别点:白帽+帽带、耳朵、眼镜腿、后颈、白 T,屏幕那侧加一圈冷色轮廓光。
63
+ const by = 840 + 6 * Math.sin(2 * Math.PI * t / 1.6) + (slam > 0 ? -MO.settle(slam, 18, 4, 7) : 0), hx = 330, L = C.line;
64
+ c.save(); c.lineWidth = 7; c.strokeStyle = L; c.lineJoin = 'round'; c.lineCap = 'round';
65
+ c.fillStyle = '#FFFFFF'; c.beginPath(); c.ellipse(hx, by + 300, 330, 200, 0, Math.PI, 0); c.fill(); c.stroke(); // 肩(白 T)
66
+ c.fillStyle = '#FFE2CC'; c.beginPath(); c.roundRect(hx - 70, by + 120, 140, 110, 30); c.fill(); c.stroke(); // 后颈
67
+ c.strokeStyle = '#E6E6EA'; c.lineWidth = 16; c.beginPath(); c.moveTo(hx - 95, by + 205); c.quadraticCurveTo(hx, by + 175, hx + 95, by + 205); c.stroke(); c.strokeStyle = L; c.lineWidth = 7; // 领口
68
+ for (const sx of [-1, 1]) { c.fillStyle = '#FFE2CC'; c.beginPath(); c.ellipse(hx + sx * 196, by + 40, 34, 52, sx * 0.2, 0, Math.PI * 2); c.fill(); c.stroke(); } // 耳朵
69
+ c.fillStyle = '#1B1B1F'; c.beginPath(); c.ellipse(hx, by + 40, 190, 175, 0, 0, Math.PI * 2); c.fill(); // 后脑(短黑发)
70
+ c.strokeStyle = '#3A3A44'; c.lineWidth = 4; for (let i = -3; i <= 3; i++) { c.beginPath(); c.moveTo(hx + i * 40, by + 130); c.lineTo(hx + i * 44, by + 175); c.stroke(); } // 发梢
71
+ c.strokeStyle = L; c.lineWidth = 9; for (const sx of [-1, 1]) { c.beginPath(); c.moveTo(hx + sx * 214, by + 10); c.lineTo(hx + sx * 236, by - 40); c.stroke(); } // 眼镜腿(挂在耳朵上,往前伸)
72
+ c.lineWidth = 7;
73
+ c.fillStyle = '#FFFFFF'; c.beginPath(); c.moveTo(hx - 180, by - 20); c.bezierCurveTo(hx - 180, by - 290, hx + 180, by - 290, hx + 180, by - 20); c.closePath(); c.fill(); c.stroke(); // 帽冠
74
+ c.save(); c.beginPath(); c.moveTo(hx - 180, by - 20); c.bezierCurveTo(hx - 180, by - 290, hx + 180, by - 290, hx + 180, by - 20); c.closePath(); c.clip(); c.fillStyle = '#E4E4E8'; c.fillRect(hx - 200, by - 85, 400, 40); c.restore(); // 帽带
75
+ c.fillStyle = '#FFFFFF'; c.beginPath(); c.ellipse(hx, by - 12, 280, 64, 0, 0, Math.PI * 2); c.fill(); c.stroke(); // 帽檐
76
+ // 屏幕光轮廓(右上侧,冷色)
77
+ c.globalCompositeOperation = 'source-atop'; c.strokeStyle = 'rgba(140,200,255,0.55)'; c.lineWidth = 14;
78
+ c.beginPath(); c.ellipse(hx, by - 12, 272, 58, 0, -1.2, 0.1); c.stroke(); c.beginPath(); c.ellipse(hx, by + 300, 322, 192, 0, -1.3, -0.2); c.stroke();
79
+ c.restore();
80
+ },
81
+ };
82
+ })();
@@ -0,0 +1,39 @@
1
+ // S3 反应大特写:砸镜进来,死鱼眼,一言不发。整段是沉默 —— 笑点在「停顿」里。
2
+ // 动:砸镜回弹(转场里)、极慢推进 6%、镜片反光扫过、汗滴滑落、一次慢眨眼、背景暗角呼吸、嘴角抽一下。
3
+ (() => {
4
+ const S = STORY, C = S.C, W = 1920, H = 1080;
5
+ const { clamp } = U;
6
+ SCENES['s3_react'] = {
7
+ draw(c, lt, t) {
8
+ const ts = MO.step(t, 24);
9
+ const lts = ts - 4.4; // 本段局部(步进后)
10
+ // 虚空背景:纯色+暗角(反应镜头把环境拿掉,只留脸)
11
+ c.fillStyle = C.void; c.fillRect(0, 0, W, H);
12
+ const vg = c.createRadialGradient(960, 560, 200, 960, 560, 1100); vg.addColorStop(0, 'rgba(255,255,255,0.10)'); vg.addColorStop(1, 'rgba(0,0,0,0.35)');
13
+ c.fillStyle = vg; c.fillRect(0, 0, W, H);
14
+ // 集中线(很淡、极慢转):漫画「凝固」的符号
15
+ c.save(); c.translate(960, 560); c.rotate(t * 0.04); c.strokeStyle = 'rgba(255,255,255,0.07)'; c.lineWidth = 6;
16
+ for (let i = 0; i < 48; i++) { const a = i / 48 * Math.PI * 2; c.beginPath(); c.moveTo(Math.cos(a) * 640, Math.sin(a) * 640); c.lineTo(Math.cos(a) * 1300, Math.sin(a) * 1300); c.stroke(); }
17
+ c.restore();
18
+ const zoom = 1 + 0.06 * Math.min(1, lt / 1.2);
19
+ CAM.with(c, { x: 960, y: 560, z: zoom }, c => {
20
+ const R = 360;
21
+ const blink = (lts >= 0.62 && lts < 0.62 + 0.125) ? 1 : 0; // 慢眨眼:3 帧@24fps(比平时多 1 帧,读作「无语」)
22
+ const twitch = lts > 0.78 ? 'worry' : 'blank'; // 第二声蟋蟀后 0.15s 眉毛一垮(声画同一件事)
23
+ const info = TOON.bean(c, { char: 'author', x: 960, y: 560 + R * 2.1 + 40, s: R, pose: TOON.POSES.stiff, expr: twitch, mouth: 0, blink, look: [0, 0], lw: 12 });
24
+ // 镜片反光:一条白色斜带从左扫到右(0.15–0.45s)
25
+ const g = MO.at(lts, 0.15, 0.3);
26
+ if (g > 0 && g < 1) {
27
+ const hy = info.head[1];
28
+ for (const sx of [-1, 1]) { const gx = 960 + sx * R * 0.37, gy = hy + R * 0.2, gr = R * 0.27;
29
+ c.save(); c.beginPath(); c.arc(gx, gy, gr - 4, 0, Math.PI * 2); c.clip();
30
+ const bx = gx - gr * 1.6 + g * gr * 3.2; c.fillStyle = 'rgba(255,255,255,0.85)';
31
+ c.beginPath(); c.moveTo(bx - 30, gy - gr); c.lineTo(bx + 20, gy - gr); c.lineTo(bx - 40, gy + gr); c.lineTo(bx - 90, gy + gr); c.closePath(); c.fill(); c.restore(); }
32
+ }
33
+ // 汗滴:太阳穴处,0.35s 起滑落 0.9s
34
+ const sw = MO.at(lt, 0.35, 0.9);
35
+ if (sw > 0) TOON.sweat(c, 960 + R * 0.88, info.head[1] - R * 0.1, 34, MO.expoOut(sw) * 0.6 + sw * 0.4);
36
+ });
37
+ },
38
+ };
39
+ })();
@@ -0,0 +1,16 @@
1
+ // S4 大全景(同一个房间拉远到 0.5 倍):小小的花叔坐在大房间里。口播「我问它:这些……」
2
+ // 全景让人显得渺小无助 —— 远景本身就是笑点的铺垫。返修:两段停顿里加「眼睛瞟一下电脑再看镜头」、说「这些」时指电脑。
3
+ // 动:口型、眼神(6.2 瞟电脑 → 6.6 回到镜头)、换姿势回弹(talk → point)、吊灯摆、云飘、屏幕光闪、镜头极慢拉远。
4
+ (() => {
5
+ const S = STORY;
6
+ SCENES['s4_wide'] = {
7
+ draw(c, lt, t) {
8
+ const ts = MO.step(t, 24);
9
+ const zoom = 0.53 - 0.03 * MO.quintOut(Math.min(1, lt / 1.65)); // 0.53 → 0.50
10
+ CAM.with(c, { x: 900, y: 470, z: zoom }, c => {
11
+ const st = S.poseAt(S.KEYS_END, ts), al = S.alive(ts, 165);
12
+ S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts), look: S.lookEnd(ts), squash: st.squash * al.breathe, bob: al.bob, lw: 10 });
13
+ });
14
+ },
15
+ };
16
+ })();
@@ -0,0 +1,18 @@
1
+ // S5 近景(跳近:同机位直接切到 1.9 倍):「贴哪儿?」——摊手、眉毛内端抬高、头顶「?」弹出,然后定住 0.95s 给笑。
2
+ // 返修:第一版把这句放在全景里,脸只有 110px 高,表情看不清,「?」出来 0.4s 片子就完了。
3
+ (() => {
4
+ const S = STORY;
5
+ SCENES['s5_close'] = {
6
+ draw(c, lt, t) {
7
+ const ts = MO.step(t, 24);
8
+ const zoom = 1.9 + 0.08 * MO.quintOut(Math.min(1, lt / 1.5)); // 跳近后极慢再推 4%
9
+ CAM.with(c, { x: 870, y: 470, z: zoom }, c => {
10
+ const st = S.poseAt(S.KEYS_END, ts), al = S.alive(ts, 165);
11
+ const info = S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts, [8.3]), look: S.lookEnd(ts), squash: st.squash * al.breathe, bob: al.bob, tilt: ts >= 7.45 ? -0.07 : 0, browUp: ts >= 7.45 ? 0.04 : 0 });
12
+ // 「?」:问完那一刻弹出(过冲 30%),之后 ±3° 轻晃
13
+ const q = MO.at(t, 7.80, 0.26);
14
+ if (q > 0) { c.save(); c.translate(info.head[0] + 165 * 1.4, info.head[1] - 165 * 0.5); c.rotate(0.14 + 0.05 * Math.sin(t * 6)); TOON.mark(c, '?', 0, 0, q, 120); c.restore(); }
15
+ });
16
+ },
17
+ };
18
+ })();
@@ -0,0 +1,25 @@
1
+ // y4 本片共用:配色、姿势时间线(pose-to-pose+换姿势回弹)、眨眼、字幕。
2
+ // 用到的库:TOON(豆子花叔 bean、poseAt、blinkAt、alive、口型读 VO_ENV)、TY.text(字幕)、CAM(镜头)、MO(步进、弹簧)。
3
+ (() => {
4
+ const S = window.STORY = {};
5
+ // 背景压低饱和与对比、线更细更灰;角色线最黑最粗 —— 视线永远先落到角色上
6
+ S.C = { wall: '#DCE9EC', wall2: '#CFE0E4', floor: '#E8DCC8', desk: '#E2C29B', deskSide: '#CDA97F', bgLine: '#5B6470', line: '#1B1B1F',
7
+ sky: '#BFE3F5', cloud: '#FFFFFF', laptop: '#C9CDD6', screen: '#20222D', glow: 'rgba(120,200,255,0.35)', void: '#3D405B' };
8
+ S.bgLW = 3;
9
+ // 口播时间(秒,全片时间):与 共享/音频/build_y4.py 的摆放一致
10
+ S.LINES = [[0.12, 2.0, '我第一次让AI帮我写代码,'], [2.13, 4.40, '它一口气,给了我三千行。'], [5.75, 8.75, '我问它:这些……贴哪儿?']]; // 字幕和镜头切点同一帧换
11
+
12
+ // 姿势时间线:keys = [[t, poseName, expr], ...](全片时间)。换姿势 = TOON.poseAt:手臂 0.1s 快切、身体挤压 7% 后弹簧回弹
13
+ S.poseAt = TOON.poseAt;
14
+ // 眨眼:固定时刻表(「随机」间隔 2.4–3.6s 写死),闭眼 2 帧@24fps ≈ 0.083s
15
+ S.blinkAt = (t, extra = []) => TOON.blinkAt(t, [0.9, 3.4, 6.1, ...extra]);
16
+ // 说话时头随音节轻点+呼吸(1.6s 一周期,±1.2%)
17
+ S.alive = TOON.alive;
18
+
19
+ // 字幕(中文平台需要烧录;YouTube 原生 storytime 不烧,靠 CC):白字黑描边,整句切入切出,不做花样
20
+ window.GLOBAL_OVERLAY = (c, t) => {
21
+ for (const [a, b, s] of S.LINES) if (t >= a && t < b) {
22
+ TY.text(c, s, 960, 1022, { size: 50, fam: 'PuHui-Bold', color: '#fff', stroke: '#1B1B1F', strokeW: 10, track: 1 });
23
+ }
24
+ };
25
+ })();
@@ -0,0 +1,2 @@
1
+ // 由 共享/音频/build_y4.py 生成:口播 RMS 包络,60fps,0..1
2
+ window.VO_ENV = {"fps": 60, "a": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558, 0.959, 0.963, 0.984, 0.929, 0.834, 0.614, 0.501, 0.149, 0.385, 0.474, 0.541, 0.516, 0.521, 0.635, 0.651, 0.678, 0.671, 0.608, 0.611, 0.672, 0.455, 0.025, 0.058, 0.088, 0.071, 0.044, 0.639, 1.0, 1.0, 1.0, 0.965, 0.694, 0.498, 0.892, 1.0, 1.0, 0.654, 0.515, 0.5, 0.383, 0.159, 0.013, 0.006, 0.507, 0.883, 0.855, 0.89, 0.803, 0.691, 0.708, 0.498, 0.178, 0.018, 0.002, 0.0, 0.0, 0.0, 0.216, 0.78, 1.0, 1.0, 1.0, 0.99, 0.733, 0.615, 0.538, 0.598, 0.804, 0.838, 0.825, 0.785, 0.631, 0.584, 0.489, 0.348, 0.078, 0.106, 0.096, 0.062, 0.591, 0.877, 0.842, 0.844, 0.843, 0.696, 0.441, 0.207, 0.395, 0.516, 0.565, 0.479, 0.345, 0.422, 0.525, 0.795, 1.0, 1.0, 1.0, 0.917, 0.894, 0.88, 0.876, 0.653, 0.187, 0.05, 0.006, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04, 0.047, 0.054, 0.698, 1.0, 1.0, 1.0, 0.81, 0.704, 0.724, 0.738, 0.694, 0.615, 0.567, 0.569, 0.461, 0.169, 0.072, 0.074, 0.065, 0.076, 0.679, 0.995, 0.908, 0.739, 0.652, 0.524, 0.315, 0.069, 0.089, 0.089, 0.067, 0.277, 0.744, 0.865, 0.818, 0.726, 0.655, 0.414, 0.039, 0.002, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.079, 0.633, 0.799, 0.847, 0.845, 0.785, 0.707, 0.678, 0.692, 0.724, 0.632, 0.626, 0.633, 0.571, 0.692, 0.86, 0.977, 0.923, 1.0, 0.729, 0.681, 0.407, 0.067, 0.061, 0.324, 0.616, 0.7, 0.654, 0.472, 0.321, 0.289, 0.295, 0.207, 0.054, 0.077, 0.081, 0.089, 0.111, 0.443, 0.625, 0.692, 0.771, 0.564, 0.419, 0.35, 0.274, 0.195, 0.065, 0.039, 0.034, 0.909, 1.0, 1.0, 1.0, 0.589, 0.432, 0.415, 0.315, 0.158, 0.011, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.608, 0.957, 0.954, 0.955, 1.0, 0.946, 0.853, 0.768, 0.868, 0.969, 1.0, 1.0, 0.98, 0.714, 0.661, 0.65, 0.456, 0.417, 0.316, 0.051, 0.035, 0.046, 0.037, 0.07, 0.55, 0.946, 0.985, 0.849, 0.779, 0.715, 0.485, 0.151, 0.07, 0.049, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02, 0.068, 0.248, 0.875, 1.0, 1.0, 1.0, 0.98, 0.759, 0.404, 0.081, 0.095, 0.081, 0.387, 0.699, 0.75, 0.71, 0.75, 0.814, 0.513, 0.105, 0.014, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.017, 0.056, 0.059, 0.046, 0.043, 0.371, 0.806, 0.956, 1.0, 0.903, 0.681, 0.602, 0.582, 0.514, 0.437, 0.513, 0.786, 0.765, 0.806, 0.761, 0.711, 0.376, 0.04, 0.006, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]};
@@ -0,0 +1,73 @@
1
+ // y4 的「世界画布」:S1 中景、S4 大全景、S5 近景是同一个房间、同一套世界坐标,只换镜头。
2
+ // (返修:第一版 S4 另画了一个房间,审片指出空间不连贯;中景镜头偏移露出画框外的黑边。
3
+ // 现在世界铺到 x −1000..2900、y −700..1700,最远的全景 zoom 0.5 也盖得满。)
4
+ // S1 镜头看到的范围 ≈ x 0..1920、y 0..1080;全景时才看得到书架、绿植、吊灯、地板。
5
+ (() => {
6
+ const S = STORY, C = S.C;
7
+ const X0 = -1000, Y0 = -700, WW = 3900, WH = 2400;
8
+ const back = () => PAINT.cached('y4_world_back', WW, WH, g => {
9
+ g.translate(-X0, -Y0);
10
+ g.lineWidth = S.bgLW; g.strokeStyle = C.bgLine; g.lineJoin = 'round';
11
+ g.fillStyle = C.wall; g.fillRect(X0, Y0, WW, WH);
12
+ // 天花板线、地板
13
+ g.fillStyle = C.wall2; g.fillRect(X0, Y0, WW, 330); g.beginPath(); g.moveTo(X0, Y0 + 330); g.lineTo(X0 + WW, Y0 + 330); g.stroke();
14
+ g.fillStyle = C.floor; g.fillRect(X0, 1150, WW, 600); g.fillStyle = '#D2C3AA'; g.fillRect(X0, 1132, WW, 18);
15
+ g.beginPath(); g.moveTo(X0, 1132); g.lineTo(X0 + WW, 1132); g.moveTo(X0, 1150); g.lineTo(X0 + WW, 1150); g.stroke();
16
+ // 猫画框(小猫补光灯的草图)
17
+ g.fillStyle = '#F4EEE2'; g.beginPath(); g.roundRect(1380, 210, 230, 170, 10); g.fill(); g.stroke();
18
+ g.strokeStyle = '#B9A88E'; g.lineWidth = 4; g.beginPath(); g.arc(1495, 300, 46, 0, Math.PI * 2); g.stroke();
19
+ g.beginPath(); g.moveTo(1462, 266); g.lineTo(1470, 238); g.lineTo(1484, 258); g.moveTo(1528, 266); g.lineTo(1520, 238); g.lineTo(1506, 258); g.stroke();
20
+ g.strokeStyle = C.bgLine; g.lineWidth = S.bgLW;
21
+ // 书架(S1 画外左侧)
22
+ const r = U.rng(9); g.fillStyle = '#D9C3A0'; g.beginPath(); g.roundRect(-700, 420, 420, 712, 8); g.fill(); g.stroke();
23
+ for (const sy of [420, 600, 780, 960]) { g.beginPath(); g.moveTo(-700, sy + 160); g.lineTo(-280, sy + 160); g.stroke();
24
+ let x = -675; while (x < -310) { const w = 22 + r() * 24, h = 90 + r() * 50; g.fillStyle = ['#8FB3C9', '#E7A37B', '#B9C98F', '#C6A0C9'][(r() * 4) | 0]; g.fillRect(x, sy + 160 - h, w, h); g.strokeRect(x, sy + 160 - h, w, h); x += w + 6; } }
25
+ // 绿植(画外右侧,地上)
26
+ g.fillStyle = '#E7A37B'; g.beginPath(); g.moveTo(2240, 1140); g.lineTo(2270, 990); g.lineTo(2410, 990); g.lineTo(2440, 1140); g.closePath(); g.fill(); g.stroke();
27
+ g.fillStyle = '#8DBF8B'; for (const [x, y, rx, ry, a] of [[2290, 900, 40, 110, -0.5], [2340, 870, 40, 128, 0], [2392, 905, 40, 106, 0.5]]) { g.beginPath(); g.ellipse(x, y, rx, ry, a, 0, Math.PI * 2); g.fill(); g.stroke(); }
28
+ });
29
+ // 桌子:S1 里只看得到桌面和挡板上沿;全景看得到桌腿
30
+ const desk = (g) => {
31
+ g.lineWidth = S.bgLW; g.strokeStyle = C.bgLine; g.lineJoin = 'round';
32
+ g.fillStyle = C.deskSide; for (const x of [-120, 1960]) { g.fillRect(x, 740, 60, 400); g.strokeRect(x, 740, 60, 400); }
33
+ g.fillRect(-60, 740, 2020, 320); g.strokeRect(-60, 740, 2020, 320);
34
+ g.fillStyle = C.desk; g.beginPath(); g.rect(-160, 690, 2240, 50); g.fill(); g.stroke();
35
+ // 马克杯
36
+ g.fillStyle = '#F26B4F'; g.beginPath(); g.roundRect(470, 600, 70, 92, 10); g.fill(); g.stroke();
37
+ g.beginPath(); g.arc(548, 640, 22, -1.2, 1.2); g.lineWidth = 9; g.stroke(); g.lineWidth = S.bgLW;
38
+ };
39
+ const front = () => PAINT.cached('y4_world_front', WW, WH, g => { g.translate(-X0, -Y0); desk(g); });
40
+
41
+ // ch: 角色参数(TOON.bean 的 o,不含 x/y/s);返回 bean 的 info
42
+ S.world = (c, t, ch) => {
43
+ c.drawImage(back(), X0, Y0);
44
+ // 窗:天空+慢飘的云(18px/s、12px/s)
45
+ c.save(); c.beginPath(); c.roundRect(220, 190, 420, 300, 12); c.clip(); c.fillStyle = C.sky; c.fillRect(220, 190, 420, 300);
46
+ for (const [cx, cy, r, sp] of [[300, 300, 46, 18], [560, 400, 34, 12]]) { const x = 180 + ((cx + t * sp) % 520); c.fillStyle = C.cloud;
47
+ c.beginPath(); c.arc(x, cy, r, 0, Math.PI * 2); c.arc(x + r * 0.9, cy + 6, r * 0.75, 0, Math.PI * 2); c.arc(x - r * 0.9, cy + 8, r * 0.65, 0, Math.PI * 2); c.fill(); }
48
+ c.restore();
49
+ c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine; c.beginPath(); c.roundRect(220, 190, 420, 300, 12); c.stroke();
50
+ c.beginPath(); c.moveTo(430, 190); c.lineTo(430, 490); c.moveTo(220, 340); c.lineTo(640, 340); c.stroke();
51
+ // 吊灯(只有全景看得到):钟摆 1.9s、±4°
52
+ c.save(); c.translate(860, -370); c.rotate(0.07 * Math.sin(2 * Math.PI * t / 1.9)); c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine;
53
+ c.beginPath(); c.moveTo(0, 0); c.lineTo(0, 240); c.stroke();
54
+ c.fillStyle = '#F2C86B'; c.beginPath(); c.moveTo(-90, 330); c.lineTo(-42, 240); c.lineTo(42, 240); c.lineTo(90, 330); c.closePath(); c.fill(); c.stroke(); c.restore();
55
+ // 角色
56
+ const info = TOON.bean(c, { x: 860, y: 760, s: 165, char: 'author', ...ch }); // 示范片讲的是作者自己的经历,显式用作者造型(TOON.bean 默认是中性角色)
57
+ c.drawImage(front(), X0, Y0);
58
+ // 笔记本(背面朝镜头、斜对着他),屏幕光打在桌上,光随代码滚动微闪
59
+ c.save(); c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine; c.lineJoin = 'round';
60
+ const glow = c.createRadialGradient(1240, 690, 10, 1240, 690, 260); glow.addColorStop(0, `rgba(140,210,255,${0.42 + 0.06 * Math.sin(t * 9)})`); glow.addColorStop(1, 'rgba(140,210,255,0)');
61
+ c.fillStyle = glow; c.fillRect(980, 560, 520, 180);
62
+ c.fillStyle = C.laptop; c.beginPath(); c.moveTo(1080, 692); c.lineTo(1130, 470); c.lineTo(1420, 470); c.lineTo(1380, 692); c.closePath(); c.fill(); c.stroke();
63
+ c.fillStyle = '#AEB3BE'; c.beginPath(); c.roundRect(1050, 684, 360, 16, 6); c.fill(); c.stroke();
64
+ const k = 1 + 0.06 * Math.sin(t * 5);
65
+ c.translate(1255, 580); c.scale(k, k); c.fillStyle = '#F2A33A'; c.beginPath();
66
+ for (let i = 0; i < 8; i++) { const a = i / 8 * Math.PI * 2, r = i % 2 ? 14 : 36; c.lineTo(Math.cos(a) * r, Math.sin(a) * r); } c.closePath(); c.fill(); c.stroke();
67
+ c.restore();
68
+ return info;
69
+ };
70
+ // S4/S5 共用的姿势时间线(全片时间,跨两个镜头连续)
71
+ S.KEYS_END = [[5.6, 'talk', 'worry'], [6.81, 'point', 'worry'], [7.45, 'shrug', 'worry']];
72
+ S.lookEnd = t => t >= 7.45 ? [0, 0] : t >= 6.81 ? [1, 0.1] : (t >= 6.2 && t < 6.6) ? [1, 0.35] : [0, 0];
73
+ })();
@@ -0,0 +1,16 @@
1
+ // 示范 · 动态文字排版(kinetic typography)「AI 写代码的 3 个阶段」 120 BPM,8 秒 = 4 小节 = 16 拍,再静住 0.5s。
2
+ // 语法卡:references/动画语法/y5_kinetic_type.md
3
+ // 预览 index.html?film=demos/y5_kinetic_type 渲染 render.py --film demos/y5_kinetic_type --out y5.mp4
4
+ // 揭开那一帧落在拍上:段起点 = 小节线 − 揭开点 × 转场时长(MO.onBeat)——
5
+ // fillZoom 揭开点 .55、dur .45 → 2.0−.25 = 1.75;bands 揭开点 ≈.6、dur .5 → 4.0−.3 = 3.7;push 到 90% 位移在 p≈.58、dur .35 → 6.0−.2 ≈ 5.80
6
+ window.PUNCH = 0; // 拍点冲击由 kinetic.js 的 K.bump 自己做(每拍 1.2%)
7
+ window.FILM_DURATION = 8.5;
8
+ window.SCENE_LIBS = ['demos/y5_kinetic_type/kinetic.js'];
9
+ window.SCENE_DIR = 'demos/y5_kinetic_type';
10
+ window.ERAS = [
11
+ { id: 'k1_title', dur: 1.75 },
12
+ { id: 'k2_complete', dur: 1.95, // 冲进点选在圆内、避开「3」字形(冲进白字会一片白)
13
+ transition: { type: 'fillZoom', dur: 0.45, k: 0.55, cx: 1075, cy: 773, maxZoom: 30, fill: '#FF5A36' } },
14
+ { id: 'k3_chat', dur: 2.1, transition: { type: 'bands', dur: 0.5, gap: 0.12, colors: ['#FFD23F', '#FFFFFF', '#14213D'] } },
15
+ { id: 'k4_agent', dur: 2.7, transition: { type: 'push', dur: 0.35 } },
16
+ ];
@@ -0,0 +1,38 @@
1
+ // K1 标题:「AI 写代码」两拍砸进来 →「的 3 个阶段」在第三拍落下 → 第四拍其余字快速退场,只剩「3」,镜头冲进 3 的圆里。
2
+ // 入场慢(0.3–0.4s 弹簧)、退场快(0.18s expoIn)——退场比入场快,观众的注意力已经走了。
3
+ (() => {
4
+ const K = KT, C = K.C, b = K.b;
5
+ const { clamp, lerp } = U;
6
+ // 退场:t0 起 0.18s,缩到 0.6、下落 60px、透明
7
+ const exitK = (t, t0) => MO.expoIn(clamp((t - t0) / 0.18));
8
+ SCENES['k1_title'] = {
9
+ draw(c, lt, t) {
10
+ c.fillStyle = C.yellow; c.fillRect(0, 0, 1920, 1080);
11
+ K.dots(c, t, C.ink, 0.08);
12
+ K.cam(c, t, c => {
13
+ // 第一行:AI | 写代码
14
+ const s1 = 300, wAI = TY.width(c, 'AI', s1), wCode = TY.width(c, '写代码', s1), gap = 50, x0 = 960 - (wAI + gap + wCode) / 2, y1 = 470;
15
+ const ex = [exitK(t, 1.45), exitK(t, 1.48), exitK(t, 1.51), exitK(t, 1.54)];
16
+ // AI:第 0 拍砸进来,带 -8° → 0° 的旋转回正
17
+ if (t >= 0) { const k = MO.springHz(t, 2.2, 10), e = ex[0];
18
+ TY.text(c, 'AI', x0, y1 + 60 * e, { size: s1, align: 'left', color: C.ink, scale: lerp(1.7, 1, k) * (1 - 0.4 * e), rot: -0.14 * (1 - k), alpha: clamp(t / 0.06) * (1 - e), ox: x0 + wAI / 2, oy: y1 - s1 * 0.36 }); }
19
+ // 写代码:第 1 拍从槽里升起(遮罩),0.35s expoOut
20
+ { const e = ex[1]; c.save(); c.globalAlpha = 1 - e; c.translate(0, 60 * e);
21
+ TY.rise(c, '写代码', x0 + wAI + gap, y1, MO.at(t, b(1), 0.35), { size: s1, color: C.ink }); c.restore(); }
22
+ // 第二行:的 | (3) | 个阶段 圆心 (960, 773) —— 转场从这里冲进去
23
+ const cy = 773, r = 165;
24
+ const pc = MO.at(t, b(2), 0.42);
25
+ if (pc > 0) {
26
+ const k = MO.backOut(pc, 3.0) * (t > b(3) ? 1 + 0.12 * MO.expoOut(clamp((t - b(3)) / 0.2)) : 1);
27
+ c.save(); c.translate(960, cy); c.scale(k, k); c.rotate(0.25 * (1 - MO.expoOut(pc)));
28
+ c.fillStyle = C.coral; c.beginPath(); c.arc(0, 0, r, 0, Math.PI * 2); c.fill();
29
+ TY.text(c, '3', 0, 92, { size: 270, color: C.white });
30
+ c.restore();
31
+ }
32
+ { const e = ex[2]; TY.text(c, '的', 960 - r - 40, cy + 40 + 60 * e, { size: 120, fam: 'PuHui-Heavy', align: 'right', color: C.ink, alpha: MO.at(t, b(1.75), 0.08) * (1 - e) }); }
33
+ { const e = ex[3]; c.save(); c.globalAlpha = 1 - e; c.translate(0, 60 * e);
34
+ TY.rise(c, '个阶段', 960 + r + 40, cy + 52, MO.at(t, b(2.25), 0.35), { size: 150, fam: 'PuHui-Heavy', color: C.ink }); c.restore(); }
35
+ });
36
+ },
37
+ };
38
+ })();
@@ -0,0 +1,55 @@
1
+ // K2 阶段一「补全」(一小节 = 第 4–7 拍,2.0–4.0s)
2
+ // 第 4 拍主词砸进+巨型「1」→ 4.5 拍代码卡滑入、开始打字 → 第 5 拍辅句「它补完」升起、5.25 拍「下一行」→
3
+ // 第 6 拍灰色补全闪现+Tab 键弹出 → 6.5 拍按下 Tab、灰字变实、「下一行」放大 1.35 倍+荧光笔(字和动作说同一件事)。
4
+ // 返修:辅句原在第 6 拍出、7 个字,到被色带盖住只剩 0.7s;现在提前一拍、压到 6 个字,可读 1.2s。
5
+ (() => {
6
+ const K = KT, C = K.C, b = K.b;
7
+ const { clamp } = U;
8
+ const MONO = 'Menlo'; // 代码用等宽(返修:第一版用了比例字体,不像代码)
9
+ SCENES['k2_complete'] = {
10
+ draw(c, lt, t) {
11
+ c.fillStyle = C.coral; c.fillRect(0, 0, 1920, 1080);
12
+ K.dots(c, t, C.white, 0.1);
13
+ K.cam(c, t, c => {
14
+ K.bigNum(c, t, '1', b(4), 1880, 1150, 'rgba(255,255,255,0.13)');
15
+ TY.rise(c, '阶段 01', 140, 250, MO.at(t, b(4), 0.3), { size: K.SZ.label, fam: 'PuHui-Bold', color: C.white, track: 6 });
16
+ TY.underline(c, 140, 272, 170, MO.at(t, b(4.25), 0.3), C.white, 6);
17
+ K.slam(c, '补全', 130, 600, t, b(4), { color: C.white });
18
+ const sub = '它补完', key = '下一行', sw = TY.width(c, sub, K.SZ.sub, 'PuHui-Heavy'), kw = TY.width(c, key, K.SZ.sub, 'PuHui-Heavy');
19
+ const ky = 775, kx = 150 + sw;
20
+ TY.rise(c, sub, 140, ky, MO.at(t, b(5), 0.35), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: C.white });
21
+ const ksc = t >= b(6.5) ? 1 + 0.35 * MO.springHz(t - b(6.5), 2.5, 9) : 1;
22
+ K.keyMarker(c, kx, ky, kw, K.SZ.sub, t, b(6.5), ksc, C.yellow);
23
+ K.key(c, key, kx, ky, t, b(5.25), b(6.5), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: t >= b(6.5) ? C.ink : C.white });
24
+ // 代码卡:4.5 拍从右侧滑入(expoOut 0.4s,起点 +700px,带 3.4° 回正)
25
+ const pin = MO.expoOut(MO.at(t, b(4.5), 0.4));
26
+ if (pin > 0) {
27
+ const cx = 1010 + 700 * (1 - pin), cy = 330;
28
+ c.save(); c.translate(cx, cy); c.rotate(0.06 * (1 - pin));
29
+ c.fillStyle = 'rgba(0,0,0,0.18)'; c.beginPath(); c.roundRect(14, 18, 800, 330, 26); c.fill();
30
+ c.fillStyle = C.code; c.beginPath(); c.roundRect(0, 0, 800, 330, 26); c.fill();
31
+ ['#FF5F57', '#FEBC2E', '#28C840'].forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(40 + i * 30, 40, 9, 0, Math.PI * 2); c.fill(); });
32
+ const T = (s, x, y, col, a = 1) => TY.text(c, s, x, y, { size: 40, fam: MONO, color: col, align: 'left', alpha: a });
33
+ T('1', 40, 130, '#555A72'); T('function hello() {', 96, 130, '#82AAFF');
34
+ T('2', 40, 210, '#555A72');
35
+ const typed = TY.typed(' const name =', MO.at(t, b(4.75), 0.5)); T(typed, 96, 210, '#E9ECF5');
36
+ const tw = TY.width(c, typed, 40, MONO), acc = t >= b(6.5);
37
+ // 补全:第 6 拍整段灰字 0 帧闪现(补全本来就是瞬间出现),Tab 按下后变实色
38
+ if (t >= b(6)) T(" 'huashu';", 96 + tw, 210, acc ? '#C3E88D' : 'rgba(233,236,245,0.35)');
39
+ T('3', 40, 290, '#555A72'); if (acc) T('}', 96, 290, '#82AAFF', MO.at(t, b(6.6), 0.1));
40
+ if (Math.floor(t * 4) % 2 === 0) { const cxr = 96 + tw + (acc ? TY.width(c, " 'huashu';", 40, MONO) : 0); c.fillStyle = '#FFD23F'; c.fillRect(cxr + 4, 172, 5, 50); }
41
+ c.restore();
42
+ }
43
+ // Tab 键帽:第 6 拍弹出,6.5 拍按下(压 14%,e^-18t 回弹)
44
+ const kp = MO.at(t, b(6), 0.3); if (kp > 0) {
45
+ const press = t >= b(6.5) ? 1 - 0.14 * Math.exp(-(t - b(6.5)) * 18) : 1, s = MO.backOut(kp, 2.6) * press;
46
+ c.save(); c.translate(1410, 790); c.scale(s, s);
47
+ c.fillStyle = '#C24128'; c.beginPath(); c.roundRect(-130, -62, 260, 136, 22); c.fill();
48
+ c.fillStyle = C.white; c.beginPath(); c.roundRect(-130, -76 + (1 - press) * 70, 260, 128, 22); c.fill();
49
+ TY.text(c, 'TAB', 0, 12 + (1 - press) * 70, { size: 64, fam: 'PuHui-Black', color: C.coral });
50
+ c.restore();
51
+ }
52
+ });
53
+ },
54
+ };
55
+ })();