@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,347 @@
1
+ // Y1 · Kurzgesagt 式扁平科普:「AI 是怎么学会认猫的」
2
+ // 三镜:①夜晚山坡,机器人扫描一只猫(视差 5 层、慢推) → 转场 A「钻进镜头」:相机指数推进机器人眼睛,眼睛里是下一个世界
3
+ // ②机器人脑中:像素猫 → 神经网络逐层点亮(边缘→形状→猫) → 转场 B「填满再拉出」:推进点亮的输出节点直到满屏黄,切到机器人发黄光的眼睛再指数拉远
4
+ // ③白天草坡:花叔招手,机器人认出一只没见过的黑猫,标签弹出
5
+ // 时间全部用片内时间 ft = t - F0。镜头表在 Y1.SHOTS。
6
+ // 用到的库:CAM(世界相机、视差层、对数缩放、推进锚点)、TOON(双色分面 flat、glow)、TY.charsIn(逐字上浮)、MO(k75 等缓动)。
7
+ (() => {
8
+ const W = 1920, H = 1080;
9
+ const { clamp, lerp, rng } = U;
10
+ const seg = MO.seg;
11
+ const Y1 = window.Y1 = {};
12
+ const F0 = 0; // 本片在时间轴上的起点
13
+ Y1.F0 = F0;
14
+ Y1.SHOTS = { s1: [0, 2.6], s2: [2.6, 5.3], s3: [5.3, 8.0] };
15
+ const T1 = { a: 2.6, d: 1.0 }, T2 = { a: 5.3, d: 0.9 };
16
+ // 调研 §6.2:官方「其他动作用 75% influence」≈ cubic-bezier(.75,0,.25,1);循环用 Easy Ease ≈ (.333,0,.667,1)
17
+ const K75 = MO.k75;
18
+
19
+ // ---------- 配色(夜 / 机内 / 昼 三套,Kurzgesagt 换场景=换整套色板) ----------
20
+ const C = {
21
+ // 色值取自调研 §7.1(2025 深色系实测):深靛紫底+青/橙黄/品红三个强调色
22
+ night: { sky: ['#05020F', '#0E0631', '#2a1a6e'], star: '#F9FCFB', moon: '#FAF3CC', moonSh: '#E2C34E', hillFar: '#1a1260', hillFarHi: '#261c86', hill: '#2b1f86', hillHi: '#3a2c9e', hillSh: '#1f1668', fg: '#0E0631', grass: '#34269a' },
23
+ mind: { bg: ['#010718', '#130C4B'], grid: 'rgba(125,211,220,.07)', line: 'rgba(95,88,219,.35)', node: '#191670', nodeHi: '#3826A3', pulse: '#32BAEE', hot: '#FACC12', label: '#7DD3DC' },
24
+ day: { sky: ['#3fb4f5', '#8fd8ff', '#dcf6ff'], sun: '#fff1a8', cloud: '#ffffff', cloudSh: '#d6efff', hillFar: '#9ad66b', hillMid: '#6cc04d', ground: '#4ea83f', groundHi: '#66c24c', tree: '#2f8f3c', treeHi: '#4fb34f', trunk: '#8a5a3c' },
25
+ cat: { fur: '#F48301', sh: '#BF5E07', hi: '#ffab4a', belly: '#ffe0b5', ear: '#C83C7F', eye: '#ffffff', pupil: '#0E0631' },
26
+ bot: { shell: '#E7DFE1', sh: '#b3a6d6', hi: '#ffffff', face: '#0E0631', eye: '#32BAEE', eyeHot: '#FACC12', ant: '#BE3A95' },
27
+ };
28
+
29
+ // ---------- 扁平+双色阴影(Kurzgesagt 的「无描边、靠色块分面」):TOON.flat;视差层:CAM.layer ----------
30
+ const flat = TOON.flat, glow = TOON.glow, circ = TOON.circle, ell = TOON.ellipse, vgrad = TOON.vgrad, layer = CAM.layer;
31
+ Y1.flat = flat;
32
+ const poly = (pts, smooth = true) => smooth ? RIG.smooth(pts, true) : U.poly(pts);
33
+
34
+ // ---------- 角色:猫 ----------
35
+ // 原点 = 坐姿底部中心;face=-1 朝左
36
+ Y1.cat = (c, x, y, ft, { s = 1, face = -1, pal = C.cat, look = 0 } = {}) => {
37
+ const P = pal; c.save(); c.translate(x, y); c.scale(face * -s, s); // 本地朝左(-x 为脸)
38
+ const br = Math.sin(ft * 2.6) * 0.012; // 呼吸
39
+ c.scale(1 - br, 1 + br);
40
+ // 尾巴:两段贝塞尔,尖端画 8 字摆
41
+ const tw = Math.sin(ft * 2.2) * 0.5, tw2 = Math.sin(ft * 2.2 - 0.9) * 0.6;
42
+ const tail = new Path2D(); const tx = 120 + Math.cos(tw) * 30, ty = -150 + Math.sin(tw) * 40;
43
+ c.lineCap = 'round'; c.strokeStyle = P.sh; c.lineWidth = 30;
44
+ c.beginPath(); c.moveTo(70, -20); c.bezierCurveTo(150, -10, 170, -80, tx, ty); c.quadraticCurveTo(tx + 30 * Math.cos(tw2), ty - 40, tx - 10 + 30 * Math.sin(tw2), ty - 70); c.stroke();
45
+ c.strokeStyle = P.fur; c.lineWidth = 22; c.stroke();
46
+ // 身体:水滴形坐姿
47
+ const body = poly([[-70, 0], [-82, -60], [-64, -140], [-20, -190], [36, -186], [74, -140], [92, -60], [86, 0]]);
48
+ flat(c, body, P.fur, P.sh, P.hi, [-14, 12], [-5, 5]);
49
+ flat(c, poly([[-58, -4], [-62, -70], [-40, -140], [-6, -150], [10, -100], [10, -4]]), P.belly, '#f3c99a', '#fff3e0', [-8, 8], [-3, 3]);
50
+ // 前爪
51
+ flat(c, ell(-38, -6, 26, 14), P.belly, '#efc392', '#fff', [-4, 4], [-2, 2]);
52
+ flat(c, ell(12, -6, 26, 14), P.hi, P.sh, '#fff', [-4, 4], [-2, 2]);
53
+ // 头
54
+ c.save(); c.translate(-16, -210); c.rotate(look * 0.12 + Math.sin(ft * 1.3) * 0.03);
55
+ const earL = poly([[-70, -30], [-82, -118], [-20, -64]], false), earR = poly([[20, -64], [64, -120], [72, -30]], false);
56
+ flat(c, earL, P.fur, P.sh, P.hi, [-6, 6], [-2, 2]); flat(c, earR, P.fur, P.sh, P.hi, [-6, 6], [-2, 2]);
57
+ c.fillStyle = P.ear; c.fill(poly([[-62, -44], [-72, -98], [-32, -62]], false)); c.fill(poly([[30, -62], [58, -100], [62, -44]], false));
58
+ const head = ell(0, 0, 86, 74);
59
+ flat(c, head, P.fur, P.sh, P.hi, [-14, 12], [-5, 5]);
60
+ flat(c, ell(-12, 26, 46, 30), P.belly, '#f3c99a', '#fff', [-6, 6], [-2, 2]);
61
+ // 眼:大白眼+瞳孔看向 look 方向,眨眼
62
+ const bl = (ft % 3.1) > 2.98 ? 0.15 : 1;
63
+ for (const ex of [-44, 16]) {
64
+ c.save(); c.translate(ex, -8); c.scale(1, bl);
65
+ c.fillStyle = P.eye; c.beginPath(); c.ellipse(0, 0, 19, 23, 0, 0, Math.PI * 2); c.fill();
66
+ c.fillStyle = P.pupil; c.beginPath(); c.ellipse(-6 - look * 3, 2, 11, 16, 0, 0, Math.PI * 2); c.fill();
67
+ c.fillStyle = '#fff'; c.beginPath(); c.arc(-10 - look * 3, -5, 4.5, 0, Math.PI * 2); c.fill();
68
+ c.restore();
69
+ }
70
+ c.fillStyle = '#ff6f8a'; c.beginPath(); c.moveTo(-22, 18); c.lineTo(-6, 18); c.lineTo(-14, 28); c.closePath(); c.fill();
71
+ // 胡须
72
+ c.strokeStyle = 'rgba(255,255,255,.75)'; c.lineWidth = 3;
73
+ for (const k of [-1, 1]) { c.beginPath(); c.moveTo(-40, 26 + k * 6); c.lineTo(-104, 18 + k * 16 + Math.sin(ft * 3) * 2); c.stroke(); }
74
+ c.restore();
75
+ c.restore();
76
+ };
77
+
78
+ // ---------- 角色:机器人(AI) ----------
79
+ // 原点 = 头部中心;返回眼睛中心(世界坐标)
80
+ Y1.bot = (c, x, y, ft, { eye = C.bot.eye, eyeR = 34, look = 0, s = 1 } = {}) => {
81
+ const P = C.bot; c.save(); c.translate(x, y); c.scale(s, s); c.rotate(Math.sin(ft * 1.7) * 0.04);
82
+ // 喷射光
83
+ const jf = 1 + Math.sin(ft * 23) * 0.08 + Math.sin(ft * 37) * 0.05;
84
+ glow(c, 0, 128, 70 * jf, '#7df9ff', 0.55);
85
+ c.fillStyle = '#c9fbff'; c.beginPath(); c.moveTo(-22, 96); c.quadraticCurveTo(0, 96 + 70 * jf, 22, 96); c.closePath(); c.fill();
86
+ flat(c, (() => { const p = new Path2D(); p.roundRect(-46, 70, 92, 34, 14); return p; })(), '#d6d3fb', P.sh, P.hi, [6, 8], [2, 3]);
87
+ // 天线
88
+ c.strokeStyle = P.sh; c.lineWidth = 8; c.lineCap = 'round'; c.beginPath(); c.moveTo(0, -84); c.quadraticCurveTo(8, -120, 0 + Math.sin(ft * 3) * 6, -142); c.stroke();
89
+ const ab = 0.6 + 0.4 * Math.max(0, Math.sin(ft * 5));
90
+ glow(c, Math.sin(ft * 3) * 6, -148, 34, P.ant, 0.6 * ab); flat(c, circ(Math.sin(ft * 3) * 6, -148, 13), P.ant, '#d84a7b', '#ffc2d6', [3, 3], [1, 1]);
91
+ // 头壳
92
+ const shell = new Path2D(); shell.roundRect(-118, -88, 236, 176, 64);
93
+ flat(c, shell, P.shell, P.sh, P.hi, [16, 18], [6, 7]);
94
+ // 侧耳
95
+ for (const k of [-1, 1]) flat(c, (() => { const p = new Path2D(); p.roundRect(k * 118 - 14, -30, 28, 60, 12); return p; })(), '#d6d3fb', P.sh, P.hi, [4, 4], [2, 2]);
96
+ // 屏幕脸
97
+ const face = new Path2D(); face.roundRect(-88, -60, 176, 120, 44); c.fillStyle = P.face; c.fill(face);
98
+ c.save(); c.clip(face); c.fillStyle = 'rgba(255,255,255,.07)'; c.beginPath(); c.ellipse(-40, -50, 90, 30, -0.3, 0, Math.PI * 2); c.fill(); c.restore();
99
+ // 眼睛(镜头)
100
+ const ex = look * 14, ey = 0;
101
+ glow(c, ex, ey, eyeR * 2.6, eye, 0.55);
102
+ const g = c.createRadialGradient(ex - eyeR * 0.25, ey - eyeR * 0.25, 0, ex, ey, eyeR); g.addColorStop(0, '#ffffff'); g.addColorStop(0.35, eye); g.addColorStop(1, eye);
103
+ c.fillStyle = g; c.beginPath(); c.arc(ex, ey, eyeR, 0, Math.PI * 2); c.fill();
104
+ c.strokeStyle = 'rgba(255,255,255,.55)'; c.lineWidth = 3; c.beginPath(); c.arc(ex, ey, eyeR * 0.7, -2.4, -1.2); c.stroke();
105
+ c.restore();
106
+ const cs = Math.cos(Math.sin(ft * 1.7) * 0.04), sn = Math.sin(Math.sin(ft * 1.7) * 0.04);
107
+ return [x + (ex * cs - ey * sn) * s, y + (ex * sn + ey * cs) * s, eyeR * s];
108
+ };
109
+
110
+ // ---------- 小鸟(Kurzgesagt 的签名角色:圆身、大眼、三角喙) ----------
111
+ Y1.bird = (c, x, y, ft, { s = 1, col = '#ff5d8f', sh = '#d63c70', face = 1, ph = 0 } = {}) => {
112
+ c.save(); c.translate(x, y); c.scale(face * s, s);
113
+ const fl = Math.sin((ft + ph) * 2 * Math.PI / 0.36);
114
+ const wing = poly([[-6, -6], [-30, -6 - 34 * fl], [-44, -2 - 30 * fl], [-14, 8]], false);
115
+ c.fillStyle = sh; c.fill(wing);
116
+ flat(c, ell(0, 0, 30, 24), col, sh, '#ffa3c2', [5, 5], [2, 2]);
117
+ c.fillStyle = '#fff1f5'; c.beginPath(); c.ellipse(6, 8, 18, 12, 0, 0, Math.PI * 2); c.fill();
118
+ c.fillStyle = '#ffb02e'; c.beginPath(); c.moveTo(26, -4); c.lineTo(44, 2); c.lineTo(26, 8); c.closePath(); c.fill();
119
+ c.fillStyle = '#fff'; c.beginPath(); c.arc(12, -6, 9, 0, Math.PI * 2); c.fill(); c.fillStyle = '#1d1640'; c.beginPath(); c.arc(15, -5, 5, 0, Math.PI * 2); c.fill();
120
+ c.fillStyle = col; c.beginPath(); c.moveTo(-26, -4); c.lineTo(-44, -12); c.lineTo(-40, 6); c.closePath(); c.fill();
121
+ c.restore();
122
+ };
123
+
124
+ // ---------- 镜 1:夜晚山坡 ----------
125
+ const BOT1 = [700, 470];
126
+ const s1cam = (ft) => {
127
+ // 慢推:0→2.6s 从 1.00 推到 1.10(sine 进出),中心略偏向机器人
128
+ const k = MO.sineInOut(seg(ft, 0, 2.6));
129
+ const cam = { x: lerp(960, 900, k), y: lerp(540, 520, k), z: lerp(1.0, 1.1, k) };
130
+ if (ft <= T1.a) return cam;
131
+ // 钻进镜头:0.9s 对数插值推进 1.1 → 60,眼睛中心平滑移到画面中心(缓动用 sineIn 偏 InOut:起步不拖,中段最快)
132
+ const e = K75(Math.pow(seg(ft, T1.a, T1.a + T1.d), 0.85));
133
+ const eye = Y1.eyeWorld(ft);
134
+ const k2 = MO.cubicOut(seg(ft, T1.a, T1.a + T1.d * 0.7)); // 眼睛移到画面中心比推进先完成
135
+ return { ...CAM.pushTo(cam, eye[0], eye[1], 60, e, k2), e };
136
+ };
137
+ Y1.botPos = (ft) => [BOT1[0] + Math.sin(ft * 1.9) * 6, BOT1[1] + Math.sin(ft * 2.4) * 12];
138
+ Y1.eyeWorld = (ft) => { const [bx, by] = Y1.botPos(ft); const a = Math.sin(ft * 1.7) * 0.04; const ex = 0.6 * 14; return [bx + ex * Math.cos(a), by + ex * Math.sin(a), 34]; };
139
+ // 转场 A 要的「镜头圆」:屏幕上的圆心与半径
140
+ Y1.lensAt = (t) => { const ft = t - F0, cam = s1cam(ft), e = Y1.eyeWorld(ft), s = CAM.toScreen(cam, e[0], e[1]); return { x: s[0], y: s[1], r: e[2] * cam.z, e: cam.e || 0 }; };
141
+
142
+ const STARS = (() => { const r = rng(11), o = []; for (let i = 0; i < 170; i++) o.push({ x: r() * 2400 - 240, y: r() * 760 - 80, r: 0.8 + r() * r() * 3.2, ph: r() * 6.28, sp: 1 + r() * 2.5 }); return o; })();
143
+ const GRASS = (() => { const r = rng(12), o = []; for (let i = 0; i < 46; i++) o.push({ x: 820 + r() * 900, h: 18 + r() * 26, ph: r() * 6.28 }); return o; })();
144
+ const MOTES = (() => { const r = rng(13), o = []; for (let i = 0; i < 26; i++) o.push({ x: r() * W, y: 300 + r() * 600, ph: r() * 6.28, d: 0.8 + r() * 0.5, s: 2 + r() * 3 }); return o; })();
145
+
146
+ Y1.shot1 = (c, lt, t) => {
147
+ const ft = t - F0, P = C.night, cam = s1cam(ft);
148
+ c.fillStyle = vgrad(c, 0, H, P.sky); c.fillRect(0, 0, W, H);
149
+ // d=0.05 星空:闪烁
150
+ layer(c, cam, 0.05, g => { for (const s of STARS) { const a = 0.45 + 0.55 * (0.5 + 0.5 * Math.sin(ft * s.sp + s.ph)); g.globalAlpha = a; g.fillStyle = P.star; g.beginPath(); g.arc(s.x, s.y, s.r, 0, Math.PI * 2); g.fill(); } g.globalAlpha = 1; });
151
+ // d=0.12 月亮:光晕呼吸
152
+ layer(c, cam, 0.12, g => { glow(g, 1500, 230, 260 + Math.sin(ft * 1.2) * 12, '#ffd9a0', 0.22); flat(g, circ(1500, 230, 92), P.moon, P.moonSh, '#fff8ea', [-18, 14], [-6, 5]);
153
+ g.fillStyle = 'rgba(214,180,130,.45)'; for (const [x, y, r] of [[1470, 205, 16], [1528, 262, 11], [1520, 196, 7]]) { g.beginPath(); g.arc(x, y, r, 0, Math.PI * 2); g.fill(); } });
154
+ // d=0.3 远山
155
+ layer(c, cam, 0.3, g => { const p = poly([[-300, 760], [-80, 640], [200, 700], [520, 610], [860, 690], [1180, 600], [1520, 680], [1840, 620], [2200, 700], [2200, 1300], [-300, 1300]]); flat(g, p, P.hillFar, P.hillFar, P.hillFarHi, [0, -10], [0, 4]); });
156
+ // d=0.65 近坡
157
+ layer(c, cam, 0.65, g => { const p = poly([[-300, 900], [200, 840], [700, 820], [1100, 740], [1500, 760], [1900, 840], [2300, 900], [2300, 1400], [-300, 1400]]); flat(g, p, P.hill, P.hillSh, P.hillHi, [0, -14], [0, 5]); });
158
+ // d=1 主体:山顶小丘、草、猫、机器人与扫描光
159
+ layer(c, cam, 1, g => {
160
+ flat(g, poly([[860, 1200], [960, 860], [1180, 790], [1400, 810], [1620, 900], [1760, 1200]]), P.hill, P.hillSh, P.hillHi, [0, -12], [0, 5]);
161
+ // 草叶:填色的细三角(不用线条——线条会读成描边)
162
+ g.fillStyle = P.grass;
163
+ for (const q of GRASS) { const by = 806 + Math.pow((q.x - 1240) / 420, 2) * 70; const sw = Math.sin(ft * 2.1 + q.ph) * 6; g.beginPath(); g.moveTo(q.x - 5, by + 10); g.quadraticCurveTo(q.x + sw * 0.4, by - q.h * 0.5, q.x + sw, by - q.h); g.quadraticCurveTo(q.x + 2 + sw * 0.3, by - q.h * 0.4, q.x + 5, by + 10); g.closePath(); g.fill(); }
164
+ // 扫描光束:从眼睛扇形照到猫,扫描线上下扫
165
+ const [ex, ey] = Y1.eyeWorld(ft), cx = 1240, sy = 640 + Math.sin(ft * 3.2) * 110;
166
+ const beam = new Path2D(); beam.moveTo(ex, ey); beam.lineTo(cx + 40, 420); beam.lineTo(cx + 60, 830); beam.closePath();
167
+ const bg = g.createLinearGradient(ex, ey, cx, ey); bg.addColorStop(0, 'rgba(50,186,238,.45)'); bg.addColorStop(1, 'rgba(50,186,238,.06)');
168
+ g.fillStyle = bg; g.fill(beam);
169
+ Y1.cat(g, 1240, 810, ft, { s: 1.05, face: -1, look: 0.4 });
170
+ g.save(); g.clip(beam); g.strokeStyle = 'rgba(150,245,255,.9)'; g.lineWidth = 5; g.beginPath(); g.moveTo(1080, sy); g.lineTo(1420, sy); g.stroke(); glow(g, 1240, sy, 160, '#46e6ff', 0.18); g.restore();
171
+ const [bx, by] = Y1.botPos(ft); Y1.bot(g, bx, by, ft, { look: 0.6 });
172
+ });
173
+ // d=0.9–1.3 萤火微粒
174
+ for (const m of MOTES) layer(c, cam, m.d, g => { const x = m.x + Math.sin(ft * 0.8 + m.ph) * 30, y = m.y - ((ft * 22 + m.ph * 40) % 120); g.globalAlpha = 0.5 + 0.5 * Math.sin(ft * 2 + m.ph); glow(g, x, y, m.s * 5, '#ffe9a8', 0.5); g.fillStyle = '#fff6d0'; g.beginPath(); g.arc(x, y, m.s, 0, Math.PI * 2); g.fill(); g.globalAlpha = 1; });
175
+ // d=1.5 前景叶片剪影(近景最快,景深靠它)
176
+ // 前景叶片:虚焦(景深),实测 2025 片大量用前景虚化
177
+ layer(c, cam, 1.5, g => { const sw = Math.sin(ft * 1.4) * 0.05; g.filter = 'blur(7px)';
178
+ const leaf = (x, y, a, L, w) => { g.save(); g.translate(x, y); g.rotate(a); const p = poly([[0, 0], [w, -L * 0.35], [w * 0.7, -L * 0.75], [0, -L], [-w * 0.7, -L * 0.75], [-w, -L * 0.35]]);
179
+ flat(g, p, P.fg, '#0a0824', '#241e5a', [8, 0], [3, 0]); g.strokeStyle = '#241e5a'; g.lineWidth = 4; g.beginPath(); g.moveTo(0, -10); g.lineTo(0, -L * 0.9); g.stroke(); g.restore(); };
180
+ leaf(-30, 1130, -0.75 + sw, 380, 90); leaf(60, 1150, -0.25 + sw * 1.3, 300, 70); leaf(1960, 1150, 0.6 - sw, 400, 95); g.filter = 'none'; });
181
+ // 标题:逐字上浮(0.25s 起,每字错开 45ms,单字 0.5s cubicOut),钻镜头前淡出
182
+ const out = 1 - MO.sineInOut(seg(ft, 1.95, 2.5)); // 0.55s 淡出,别一帧消失
183
+ if (out > 0) { c.save(); c.globalAlpha = out;
184
+ TY.charsIn(c, 'AI 是怎么学会认猫的?', W / 2, 150, ft - 0.25, { font: '76px "PuHui-Heavy"', color: '#ffffff', dur: 0.5, stagger: 0.045, dy: 30, align: 'center', shadow: { color: 'rgba(10,8,40,.45)', y: 6, blur: 0 } });
185
+ c.restore(); }
186
+ };
187
+
188
+ // ---------- 镜 2:机器脑内 ----------
189
+ const PIX = [
190
+ '..O......O..', '.OOO....OOO.', '.OPOOOOOOPO.', '.OOOOOOOOOO.', 'OOWWOOOOWWOO', 'OOWKOOOOWKOO',
191
+ 'OOOOOOOOOOOO', 'OOOOCPPCOOOO', 'OOOCCCCCCOOO', '.OOOCCCCOOO.', '..OOOOOOOO..', '....OOOO....'];
192
+ const PCOL = { O: '#F48301', P: '#C83C7F', W: '#ffffff', K: '#0E0631', C: '#ffe0b5' };
193
+ const LAYERS = [{ x: 860, n: 4, label: '边缘' }, { x: 1120, n: 5, label: '形状' }, { x: 1370, n: 3, label: '部件' }, { x: 1600, n: 1, label: '猫', out: true }];
194
+ LAYERS.forEach((L, i) => { L.ys = []; for (let k = 0; k < L.n; k++) L.ys.push(L.n === 1 ? 520 : 520 + (k - (L.n - 1) / 2) * (L.n > 4 ? 118 : 140)); L.i = i; });
195
+ const OUT = [1600, 520, 50];
196
+ const pulseT = 1.0; // 信号从像素出发的时刻(镜内时间)
197
+ const layerHit = i => pulseT + 0.32 * (i + 1); // 第 i 层被点亮的时刻
198
+ Y1.outNode = OUT;
199
+ const s2cam = (ft) => {
200
+ const lt = ft - Y1.SHOTS.s2[0];
201
+ // 从镜头里浮出:0→0.9s z 0.42→1.0(expoOut),之后极慢漂移推进
202
+ const k = MO.expoOut(seg(lt, 0, 1.2));
203
+ let cam = { x: lerp(1020, 1000, k) + lt * 12, y: 540, z: CAM.zlerp(0.42, 1.0, k) * (1 + 0.012 * lt) };
204
+ if (ft <= T2.a) return cam;
205
+ // 推进输出节点直到满屏(0.45s,cubicIn)
206
+ const e = MO.cubicIn(seg(ft, T2.a, T2.a + T2.d * 0.5));
207
+ return CAM.pushTo(cam, OUT[0], OUT[1], 26, e);
208
+ };
209
+ const DUST = (() => { const r = rng(21), o = []; for (let i = 0; i < 90; i++) o.push({ x: r() * 2600 - 340, y: r() * 1500 - 210, r: 1 + r() * 2.5, d: 0.3 + r() * 0.5, ph: r() * 6.28 }); return o; })();
210
+ const nodeIcon = (g, li, k, x, y, a) => {
211
+ g.save(); g.globalAlpha = a; g.strokeStyle = '#e8f6ff'; g.fillStyle = '#e8f6ff'; g.lineWidth = 5; g.lineCap = 'round'; g.lineJoin = 'round';
212
+ if (li === 0) { const ang = [0, Math.PI / 2, Math.PI / 4, -Math.PI / 4][k]; g.beginPath(); g.moveTo(x - Math.cos(ang) * 16, y - Math.sin(ang) * 16); g.lineTo(x + Math.cos(ang) * 16, y + Math.sin(ang) * 16); g.stroke(); }
213
+ if (li === 1) { g.beginPath(); if (k === 0 || k === 4) { g.moveTo(x - 14, y + 10); g.lineTo(x, y - 14); g.lineTo(x + 14, y + 10); g.closePath(); } else if (k === 1) { g.ellipse(x, y, 10, 14, 0, 0, Math.PI * 2); } else if (k === 2) { g.moveTo(x - 16, y - 6); g.lineTo(x + 16, y - 10); g.moveTo(x - 16, y + 6); g.lineTo(x + 16, y + 8); } else { g.arc(x, y, 13, 0.2, Math.PI - 0.2); } g.stroke(); }
214
+ if (li === 2) { g.beginPath(); if (k === 0) { g.moveTo(x - 18, y + 8); g.lineTo(x - 10, y - 14); g.lineTo(x - 2, y + 2); g.moveTo(x + 2, y + 2); g.lineTo(x + 10, y - 14); g.lineTo(x + 18, y + 8); } else if (k === 1) { g.ellipse(x - 9, y, 6, 9, 0, 0, Math.PI * 2); g.moveTo(x + 15, y); g.ellipse(x + 9, y, 6, 9, 0, 0, Math.PI * 2); } else { g.moveTo(x - 6, y - 4); g.lineTo(x + 6, y - 4); g.lineTo(x, y + 4); g.closePath(); g.moveTo(x - 20, y + 2); g.lineTo(x - 8, y + 4); g.moveTo(x + 8, y + 4); g.lineTo(x + 20, y + 2); } g.stroke(); }
215
+ g.restore();
216
+ };
217
+ Y1.shot2 = (c, lt, t) => {
218
+ const ft = t - F0, P = C.mind, cam = s2cam(ft), l = ft - Y1.SHOTS.s2[0];
219
+ c.fillStyle = vgrad(c, 0, H, P.bg); c.fillRect(0, 0, W, H);
220
+ // 远景:点阵网格+漂浮尘埃(视差)
221
+ layer(c, cam, 0.35, g => { g.fillStyle = P.grid; for (let y = -400; y < 1500; y += 60) for (let x = -600; x < 2600; x += 60) { g.beginPath(); g.arc(x, y, 2.4, 0, Math.PI * 2); g.fill(); } });
222
+ for (const d of DUST) layer(c, cam, d.d, g => { g.globalAlpha = 0.35 + 0.35 * Math.sin(ft * 1.5 + d.ph); g.fillStyle = '#8fb0ff'; g.beginPath(); g.arc(d.x + Math.sin(ft * 0.6 + d.ph) * 20, d.y + Math.cos(ft * 0.5 + d.ph) * 14, d.r, 0, Math.PI * 2); g.fill(); g.globalAlpha = 1; });
223
+ layer(c, cam, 1, g => {
224
+ // 像素猫:144 块从四周飞入就位(每块 0.5s backOut,按到中心的距离错开),之后整体轻微呼吸
225
+ const cell = 30, gx = 400 - 6 * cell, gy = 520 - 6 * cell, r = rng(31);
226
+ for (let j = 0; j < 12; j++) for (let i = 0; i < 12; i++) {
227
+ const ch = PIX[j][i], fx = r() * 2 - 1, fy = r() * 2 - 1; if (ch === '.') continue;
228
+ const dl = 0.05 + Math.hypot(i - 5.5, j - 5.5) * 0.035, q = clamp((l - dl) / 0.5), e = MO.backOut(q, 1.4);
229
+ const x = gx + i * cell + (1 - e) * fx * 260, y = gy + j * cell + (1 - e) * fy * 260, s = (cell - 4) * (0.3 + 0.7 * e) * (1 + 0.04 * Math.sin(ft * 3 + i * 0.5 + j * 0.3));
230
+ g.globalAlpha = clamp(q * 3); g.fillStyle = PCOL[ch]; g.beginPath(); g.roundRect(x + (cell - s) / 2, y + (cell - s) / 2, s, s, 5); g.fill();
231
+ }
232
+ g.globalAlpha = 1;
233
+ // 连线:像素 → 第一层 → … 全连接
234
+ const cols = [{ x: 600, ys: [380, 470, 570, 660] }, ...LAYERS];
235
+ g.lineWidth = 2.5;
236
+ for (let a = 0; a < cols.length - 1; a++) for (const y0 of cols[a].ys) for (const y1 of cols[a + 1].ys) {
237
+ const lit = clamp((l - layerHit(a) + 0.1) / 0.3);
238
+ g.strokeStyle = lit > 0 ? `rgba(${lerp(110, 120, lit) | 0},${lerp(140, 230, lit) | 0},255,${0.22 + 0.25 * lit})` : P.line;
239
+ g.beginPath(); g.moveTo(cols[a].x, y0); g.lineTo(cols[a + 1].x, y1); g.stroke();
240
+ }
241
+ // 信号脉冲:沿每条连线跑,0.32s 跑完一层;之后循环流动(持续的「思考」)
242
+ for (let a = 0; a < cols.length - 1; a++) {
243
+ const rr = rng(40 + a);
244
+ for (const y0 of cols[a].ys) for (const y1 of cols[a + 1].ys) {
245
+ if (rr() < 0.45) continue;
246
+ const start = pulseT + 0.32 * a, ph = rr() * 0.3;
247
+ let q = (l - start - ph * 0.3) / 0.32; if (q < 0) continue; if (q > 1) q = ((l - start) * 1.6 + ph) % 1;
248
+ const x = lerp(cols[a].x, cols[a + 1].x, q), y = lerp(y0, y1, q);
249
+ glow(g, x, y, 22, P.pulse, 0.6); g.fillStyle = '#e9fdff'; g.beginPath(); g.arc(x, y, 5, 0, Math.PI * 2); g.fill();
250
+ }
251
+ }
252
+ // 节点:被信号点亮时弹一下(backOut 0.35s)+显示该层学到的特征图标
253
+ for (const L of LAYERS) L.ys.forEach((y, k) => {
254
+ const hit = layerHit(L.i) + k * 0.04, q = clamp((l - hit) / 0.35), pop = q > 0 ? 1 + 0.25 * Math.sin(Math.PI * clamp(q * 1.2)) : 1;
255
+ const r = (L.out ? OUT[2] : 34) * pop * (1 + 0.03 * Math.sin(ft * 4 + k));
256
+ if (L.out) {
257
+ if (q > 0) glow(g, L.x, y, r * (3.2 + 0.4 * Math.sin(ft * 5)), P.hot, 0.55 * q);
258
+ const gg = g.createRadialGradient(L.x - r * 0.3, y - r * 0.3, 0, L.x, y, r); gg.addColorStop(0, q > 0 ? '#fff6c8' : P.nodeHi); gg.addColorStop(1, q > 0 ? P.hot : P.node);
259
+ g.fillStyle = gg; g.beginPath(); g.arc(L.x, y, r, 0, Math.PI * 2); g.fill();
260
+ } else {
261
+ if (q > 0) glow(g, L.x, y, r * 2.4, P.pulse, 0.32 * q);
262
+ flat(g, circ(L.x, y, r), q > 0 ? '#3e6fd8' : P.node, q > 0 ? '#2b4fb0' : '#1c275a', q > 0 ? '#7fb2ff' : P.nodeHi, [-6, -6], [-2, -2]);
263
+ if (q > 0) nodeIcon(g, L.i, k, L.x, y, q);
264
+ }
265
+ });
266
+ // 层标签:逐个出现(文字是从下往上浮 + 短下划线伸出)
267
+ const labs = [{ x: 400, s: '像素' }, ...LAYERS.map(L => ({ x: L.x, s: L.label }))];
268
+ labs.forEach((L, i) => { const t0 = 0.55 + (i ? layerHit(i - 1) - 0.55 + 0.1 : 0); const q = MO.cubicOut(seg(l, t0, t0 + 0.45));
269
+ if (q <= 0) return; TY.charsIn(g, L.s, L.x, 880, l - t0, { font: '40px "PuHui-Medium"', color: i === 4 ? P.hot : P.label, dur: 0.4, stagger: 0.06, dy: 18, align: 'center' });
270
+ g.strokeStyle = i === 4 ? P.hot : P.label; g.lineWidth = 4; g.lineCap = 'round'; g.beginPath(); g.moveTo(L.x - 34 * q, 904); g.lineTo(L.x + 34 * q, 904); g.stroke(); });
271
+ // 箭头:标签之间
272
+ g.fillStyle = 'rgba(169,188,255,.55)'; for (let i = 0; i < 4; i++) { const xa = [400, ...LAYERS.map(L => L.x)]; const q = seg(l, 0.6 + (i ? layerHit(i - 1) - 0.5 : 0.2), 1.0 + (i ? layerHit(i - 1) - 0.5 : 0.2)); if (q <= 0) continue; const mx = (xa[i] + xa[i + 1]) / 2; g.globalAlpha = q; g.beginPath(); g.moveTo(mx - 10, 868); g.lineTo(mx + 10, 878); g.lineTo(mx - 10, 888); g.closePath(); g.fill(); }
273
+ g.globalAlpha = 1;
274
+ });
275
+ };
276
+
277
+ // ---------- 镜 3:白天草坡 ----------
278
+ const BOT3 = [1010, 400];
279
+ Y1.bot3Pos = (ft) => [BOT3[0] + Math.sin(ft * 1.9) * 6, BOT3[1] + Math.sin(ft * 2.4) * 10];
280
+ const s3cam = (ft) => {
281
+ const l = ft - Y1.SHOTS.s3[0];
282
+ const [bx, by] = Y1.bot3Pos(ft); const eye = [bx, by]; // look=0 时眼睛在头中心
283
+ // 0–0.45s 停在满屏黄色的眼睛里;0.45→1.9s 对数插值拉远 42→1.0。
284
+ // 用 cubicInOut 而不是 expoOut:expoOut 起步斜率太大,第一帧就从 42 倍跳到 15 倍(qa 量到 9 帧跳变);实测 Kurzgesagt 穿越是先加速后减速、峰值在中段
285
+ const e = MO.cubicInOut(seg(l, 0.45, 1.9));
286
+ const z = CAM.zlerp(42, 1.0, e) * (1 + 0.04 * Math.max(0, l - 1.9)); // 落定后 4%/s 慢推(实测待机推 3–4.5%/s)
287
+ const final = { x: 960, y: 540 };
288
+ const s0 = [W / 2, H / 2], s1 = CAM.toScreen({ ...final, z: 1 }, eye[0], eye[1]);
289
+ return CAM.anchor(eye[0], eye[1], z, lerp(s0[0], s1[0], e), lerp(s0[1], s1[1], e));
290
+ };
291
+ const CLOUDS = [[300, 230, 1.0], [880, 150, 0.7], [1480, 260, 1.15], [2100, 180, 0.8]];
292
+ const TREES = [[180, 760, 1], [330, 790, 0.7], [1580, 770, 0.9], [1760, 790, 1.2]];
293
+ const cloud = (g, x, y, s) => { const p = new Path2D(); for (const [dx, dy, r] of [[-70, 10, 50], [-20, -18, 66], [50, -4, 56], [100, 16, 40], [0, 22, 46]]) p.arc(x + dx * s, y + dy * s, r * s, 0, Math.PI * 2); flat(g, p, C.day.cloud, C.day.cloudSh, '#ffffff', [0, -10 * s], [0, 4]); };
294
+ Y1.shot3 = (c, lt, t) => {
295
+ const ft = t - F0, P = C.day, cam = s3cam(ft), l = ft - Y1.SHOTS.s3[0];
296
+ c.fillStyle = vgrad(c, 0, H, P.sky); c.fillRect(0, 0, W, H);
297
+ layer(c, cam, 0.08, g => { glow(g, 1640, 170, 300, '#fff6c0', 0.55); g.fillStyle = P.sun; g.beginPath(); g.arc(1640, 170, 80 + Math.sin(ft * 2) * 3, 0, Math.PI * 2); g.fill(); });
298
+ layer(c, cam, 0.2, g => { for (const [x, y, s] of CLOUDS) cloud(g, ((x + ft * 26) % 2600) - 340, y, s); });
299
+ layer(c, cam, 0.35, g => flat(g, poly([[-300, 760], [100, 650], [520, 700], [900, 620], [1300, 690], [1700, 610], [2200, 700], [2200, 1300], [-300, 1300]]), P.hillFar, '#86c45c', '#b4e889', [0, -10], [0, 4]));
300
+ layer(c, cam, 0.6, g => { flat(g, poly([[-300, 820], [300, 760], [900, 800], [1500, 740], [2200, 820], [2200, 1300], [-300, 1300]]), P.hillMid, '#58a63e', '#8fd468', [0, -12], [0, 4]);
301
+ for (const [x, y, s] of TREES) { g.fillStyle = P.trunk; g.fillRect(x - 8 * s, y - 60 * s, 16 * s, 70 * s); const sw = Math.sin(ft * 1.6 + x) * 4; flat(g, circ(x + sw, y - 100 * s, 62 * s), P.tree, '#23753a', P.treeHi, [-12 * s, -10 * s], [-4, -4]); } });
302
+ // 鸟:两只从左飞到右(Kurzgesagt 的签名彩蛋)
303
+ layer(c, cam, 0.75, g => { Y1.bird(g, -200 + ft * 150, 175 + Math.sin(ft * 3) * 14, ft, { s: 0.9, col: '#ff5d8f', sh: '#d63c70' }); Y1.bird(g, -420 + ft * 165, 225 + Math.sin(ft * 3 + 1) * 12, ft, { s: 0.7, col: '#ffb02e', sh: '#e08a10', ph: 0.15 }); });
304
+ layer(c, cam, 1, g => {
305
+ flat(g, poly([[-300, 900], [400, 860], [1000, 880], [1600, 850], [2300, 900], [2300, 1400], [-300, 1400]]), P.ground, '#3d8f33', P.groundHi, [0, -14], [0, 5]);
306
+ // 花叔:指向黑猫(point 姿势+手臂与头的小幅持续摆动,嘴在说话)
307
+ if (HERO.y1(g, ft) === false) { // 形象方向见 lib/hero.js
308
+ const HSb = HUASHU.build({ x: 470, y: 900 + Math.sin(ft * 3.2) * 2, s: 0.66, face: 1, pose: HUASHU.pose('point', 0, { ua1: 1.38 + 0.07 * Math.sin(ft * 3.4), fa1: 0.05 + 0.05 * Math.sin(ft * 3.4 + 1), head: -0.04 + 0.03 * Math.sin(ft * 2.1), mouth: 0.45 + 0.4 * Math.max(0, Math.sin(ft * 9)) }) });
309
+ const pal = { skin: '#ffd3b4', hair: '#1f1a3a', shirt: '#ffffff', shorts: '#d9c9a8', hat: '#ffffff', hatBand: '#d8d4f2', shoe: '#f4f2ff', watch: '#2a2550' };
310
+ const shade = { skin: '#f0a98a', hair: '#120f26', shirt: '#cfcbee', shorts: '#b39f7c', hat: '#d3cff0', hatBand: '#b6b0e0', shoe: '#c9c5ea', watch: '#14112b' };
311
+ for (const p of HSb.parts) flat(g, p.path, pal[p.role] || pal.skin, shade[p.role] || shade.skin, '#ffffff', [-10, -8], [-4, -3]);
312
+ HUASHU.details(g, HSb, { line: '#1f1a3a', lw: 3.5, glass: '#1f1a3a', cheek: 'rgba(255,120,120,.35)', earLine: 'rgba(200,110,90,.7)', shoeHole: '#8a86b8' });
313
+ }
314
+ // 认识的那只橙猫蹲在花叔脚边;镜头要认的是一只「没见过的」黑猫
315
+ Y1.cat(g, 790, 905, ft + 0.3, { s: 0.55, face: -1, look: 0.5 });
316
+ Y1.cat(g, 1420, 880, ft + 0.7, { s: 0.85, face: -1, pal: { fur: '#3a3550', sh: '#24203a', hi: '#5a5478', belly: '#4a4566', ear: '#ff8fa3', eye: '#d9ff6b', pupil: '#16132a' }, look: 0.2 });
317
+ // 扫描光与机器人
318
+ const [bx, by] = Y1.bot3Pos(ft);
319
+ const bq = MO.cubicOut(seg(l, 1.5, 1.85));
320
+ if (bq > 0) { const beam = new Path2D(); beam.moveTo(bx, by); beam.lineTo(1300, 660); beam.lineTo(1540, 660); beam.closePath(); g.save(); g.globalAlpha = bq; const bg = g.createLinearGradient(bx, by, 1420, 760); bg.addColorStop(0, 'rgba(255,210,63,.55)'); bg.addColorStop(1, 'rgba(255,210,63,.08)'); g.fillStyle = bg; g.fill(beam); g.restore(); }
321
+ Y1.bot(g, bx, by, ft, { eye: C.bot.eyeHot, eyeR: 30, look: 0 });
322
+ // 小字:一只没见过的猫(引线先画出,再逐字打出,实测标注≈40 字/秒)
323
+ const cq = seg(l, 1.0, 1.25), cout = 1 - seg(l, 1.65, 1.8);
324
+ if (cq > 0 && cout > 0) { g.save(); g.globalAlpha = cout; g.strokeStyle = '#ffffff'; g.lineWidth = 3; g.beginPath(); g.moveTo(1500, 700); g.lineTo(1500 + 80 * cq, 640 - 40 * cq); g.lineTo(1500 + 80 * cq + 120 * clamp(cq * 2 - 1), 600); g.stroke();
325
+ const str = '没见过的猫', n = Math.floor((l - 1.2) * 25); // 中文按 25 字/秒打出
326
+ g.fillStyle = '#ffffff'; g.font = '34px "PuHui-Medium"'; g.textBaseline = 'alphabetic'; g.fillText([...str].slice(0, Math.min(5, Math.max(0, n))).join(''), 1590, 590); g.restore(); }
327
+ // 识别标签:从猫头上方弹出,之后轻轻上下浮
328
+ const q = seg(l, 1.75, 2.25); if (q > 0) {
329
+ // 实测卡片弹出 ≈0.5s、过冲到 108% 回 100%
330
+ const e = MO.backOut(q, 1.1), y = 560 + Math.sin(ft * 2.5) * 6;
331
+ g.save(); g.translate(1420, y); g.scale(e, e);
332
+ g.fillStyle = 'rgba(20,40,20,.18)'; g.beginPath(); g.roundRect(-122, -40, 252, 92, 46); g.fill();
333
+ g.fillStyle = '#ffffff'; g.beginPath(); g.roundRect(-130, -50, 252, 92, 46); g.fill();
334
+ g.beginPath(); g.moveTo(-14, 40); g.lineTo(0, 64); g.lineTo(14, 40); g.fill();
335
+ g.fillStyle = '#2fbf71'; g.beginPath(); g.arc(-80, -4, 30, 0, Math.PI * 2); g.fill();
336
+ g.strokeStyle = '#fff'; g.lineWidth = 8; g.lineCap = 'round'; g.lineJoin = 'round'; g.beginPath(); g.moveTo(-94, -4); g.lineTo(-84, 8); g.lineTo(-64, -16); g.stroke();
337
+ g.fillStyle = '#1f1a3a'; g.font = '52px "PuHui-Heavy"'; g.textBaseline = 'middle'; g.fillText('猫', -36, -2);
338
+ g.font = '34px "PuHui-Medium"'; g.fillStyle = '#2fbf71'; g.fillText('98%', 26, 0);
339
+ g.restore();
340
+ }
341
+ });
342
+ };
343
+ })();
344
+
345
+ SCENES['y1_s1'] = { draw: (c, lt, t) => Y1.shot1(c, lt, t) };
346
+ SCENES['y1_s2'] = { draw: (c, lt, t) => Y1.shot2(c, lt, t) };
347
+ SCENES['y1_s3'] = { draw: (c, lt, t) => Y1.shot3(c, lt, t) };
@@ -0,0 +1,10 @@
1
+ // 示范 · Vox 式拼贴解说「AI 是怎么学会认猫的」(8 秒三镜)。语法卡:references/动画语法/y2_vox.md
2
+ // 预览 index.html?film=demos/y2_vox 渲染 render.py --film demos/y2_vox --out y2.mp4
3
+ // 一张大桌面=世界;相机 60fps 平滑推移,桌上的纸片/荧光笔/红笔/打字按 12fps 步进。
4
+ window.PUNCH = 0;
5
+ window.SCENE_LIBS = ['demos/_shared/hero.js', 'demos/y2_vox/vox.js'];
6
+ window.ERAS = [
7
+ { id: 'y2_s1', dur: 2.6 }, // 剪报+网点照片:荧光笔扫行、红笔圈词、相机慢推
8
+ { id: 'y2_s2', dur: 3.3, transition: { type: 'same', dur: 1.1 } }, // 沿红线平移到调查墙(同一桌面,相机连续走)
9
+ { id: 'y2_s3', dur: 2.1, transition: { type: 'cut', dur: 1 / 60 } }, // 推满小照片 → 硬切到同一张图的大幅扫描,再拉出揭示黑底拼贴
10
+ ];
@@ -0,0 +1,240 @@
1
+ // Y2 · Vox 式拼贴解说(Missing Chapter / Borders 一路):「AI 是怎么学会认猫的」
2
+ // 一张大桌面(世界坐标),相机 60fps 平滑地推、移;桌上的元素(荧光笔、红笔、打字、红线、贴纸)按 12fps「一拍二」步进。
3
+ // 依据:调研/y2_vox/调研.md(13 段相机运动实测:中位 1.29s,对称 easeInOutSine 或长尾 bezier(.33,0,.2,1),没有线性;元素层 12fps)
4
+ // ①剪报+网点照片已在桌上(随切点直接出现),荧光笔按行扫、红笔圈词、相机慢推向那一行
5
+ // → 转场 A「沿红线平移」:一条红线从剪报连到调查墙,相机沿线 1.1s 追过去(长尾 bezier)
6
+ // ②调查墙:1000 万张截图里的猫被红线连到索引卡,打字机 13 字/秒,计数滚到 10,000,000,一张小照片带倾斜滑入放平
7
+ // → 转场 B「推进照片→硬切」:相机推满那张小照片,硬切成同一张图的大幅扫描,再「拉出揭示」整张黑底拼贴
8
+ // ③黑底:它脑中的「猫」、纸条标题打字+荧光笔、花叔剪纸贴纸滑入、红笔圈住
9
+ // 用到的库:CL(纸纹、撕边、网点照片、胶带、图钉、红线、毛边荧光笔、倾斜滑入、贴纸白边)、CAM(关键帧相机、推进锚点、运动模糊)、
10
+ // DG(手画圈、逐段描出)、TY.typedCount(打字机)、MO(longTail、12fps 步进)。
11
+ (() => {
12
+ const W = 1920, H = 1080;
13
+ const { clamp, lerp, rng } = U;
14
+ const seg = MO.seg, step = MO.step;
15
+ const Y2 = window.Y2 = {};
16
+ const F0 = 0; Y2.F0 = F0;
17
+ Y2.SHOTS = { s1: [0, 2.6], s2: [2.6, 5.9], s3: [5.9, 8.0] };
18
+ const PAN = { a: 2.6, d: 1.1 }, PUSH = { a: 5.3, b: 5.9 };
19
+ const longTail = MO.longTail; // 「沿线追过去」「拉出揭示」用:起步快、长尾落定
20
+
21
+ // 调研实测色(MC/BD/PSY 取色)
22
+ const C = { desk: '#e4ddcf', news: '#eeedeb', ink: '#171716', red: '#b8433f', hl: '#dacf08', black: '#171716', paper: '#eeedeb', tape: 'rgba(226,220,200,.85)', card: '#f6f4ee' };
23
+ const SERIF = '"Songti SC", "STSong", serif';
24
+
25
+ // ---------- 素材(一次画好缓存) ----------
26
+ const sprite = (key, w, h, fn) => PAINT.cached('y2_' + key, w, h, fn);
27
+ const tornPoly = CL.tornRect;
28
+ const catPhoto = (g, w, h, { dark = false, pose = 0 } = {}) => {
29
+ const bg = g.createLinearGradient(0, 0, w, h); bg.addColorStop(0, dark ? '#555' : '#bdbdbd'); bg.addColorStop(1, dark ? '#2a2a2a' : '#8a8a8a'); g.fillStyle = bg; g.fillRect(0, 0, w, h);
30
+ const cx = w * (0.5 + pose * 0.08), cy = h * 0.56, s = Math.min(w, h) / 300;
31
+ g.save(); g.translate(cx, cy); g.scale(s, s);
32
+ g.fillStyle = dark ? '#d9d9d9' : '#3a3a3a';
33
+ g.beginPath(); g.ellipse(0, 120, 120, 90, 0, 0, Math.PI * 2); g.fill();
34
+ g.beginPath(); g.ellipse(0, 0, 92, 78, 0, 0, Math.PI * 2); g.fill();
35
+ g.beginPath(); g.moveTo(-80, -30); g.lineTo(-70, -118); g.lineTo(-20, -66); g.fill(); g.beginPath(); g.moveTo(80, -30); g.lineTo(70, -118); g.lineTo(20, -66); g.fill();
36
+ const fg = g.createRadialGradient(0, 20, 5, 0, 20, 70); fg.addColorStop(0, dark ? '#fff' : '#9a9a9a'); fg.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = fg; g.beginPath(); g.ellipse(0, 22, 60, 46, 0, 0, Math.PI * 2); g.fill();
37
+ g.fillStyle = '#f2f2f2'; for (const ex of [-34, 34]) { g.beginPath(); g.ellipse(ex, -6, 17, 13, 0, 0, Math.PI * 2); g.fill(); }
38
+ g.fillStyle = '#111'; for (const ex of [-34, 34]) { g.beginPath(); g.ellipse(ex, -6, 5, 12, 0, 0, Math.PI * 2); g.fill(); }
39
+ g.fillStyle = '#222'; g.beginPath(); g.moveTo(-9, 22); g.lineTo(9, 22); g.lineTo(0, 32); g.fill();
40
+ g.strokeStyle = 'rgba(240,240,240,.7)'; g.lineWidth = 2.5; for (const k of [-1, 1]) for (const d of [-6, 6]) { g.beginPath(); g.moveTo(k * 30, 34 + d); g.lineTo(k * 110, 26 + d * 2.4); g.stroke(); }
41
+ g.restore();
42
+ };
43
+ const thumbScene = (g, w, h, kind, seed) => {
44
+ const r = rng(seed); const bg = g.createLinearGradient(0, 0, 0, h); const L = 120 + r() * 80; bg.addColorStop(0, `rgb(${L},${L},${L})`); bg.addColorStop(1, `rgb(${L - 50},${L - 50},${L - 50})`); g.fillStyle = bg; g.fillRect(0, 0, w, h);
45
+ g.fillStyle = r() < 0.5 ? '#2c2c2c' : '#e8e8e8';
46
+ if (kind === 'cat') return catPhoto(g, w, h, { dark: r() < 0.4, pose: r() - 0.5 });
47
+ g.save(); g.translate(w / 2, h * 0.6); const s = h / 90; g.scale(s, s);
48
+ if (kind === 'dog') { g.beginPath(); g.ellipse(0, 6, 34, 18, 0, 0, Math.PI * 2); g.fill(); g.beginPath(); g.ellipse(-30, -14, 14, 12, 0, 0, Math.PI * 2); g.fill(); g.beginPath(); g.ellipse(-40, -8, 6, 12, 0.3, 0, Math.PI * 2); g.fill(); g.fillRect(-26, 14, 6, 16); g.fillRect(18, 14, 6, 16); }
49
+ if (kind === 'car') { g.beginPath(); g.roundRect(-44, -4, 88, 22, 6); g.fill(); g.beginPath(); g.roundRect(-24, -22, 46, 22, 8); g.fill(); g.fillStyle = '#111'; g.beginPath(); g.arc(-26, 20, 9, 0, 7); g.arc(26, 20, 9, 0, 7); g.fill(); }
50
+ if (kind === 'man') { g.beginPath(); g.arc(0, -26, 13, 0, 7); g.fill(); g.beginPath(); g.roundRect(-20, -10, 40, 44, 10); g.fill(); }
51
+ if (kind === 'tree') { g.fillRect(-5, -4, 10, 34); g.beginPath(); g.arc(0, -20, 26, 0, 7); g.fill(); }
52
+ if (kind === 'house') { g.fillRect(-28, -8, 56, 36); g.beginPath(); g.moveTo(-36, -8); g.lineTo(0, -38); g.lineTo(36, -8); g.fill(); }
53
+ if (kind === 'game') { g.beginPath(); g.roundRect(-36, -14, 72, 34, 16); g.fill(); }
54
+ g.restore();
55
+ g.fillStyle = 'rgba(0,0,0,.45)'; g.fillRect(0, h - 8, w, 8); g.fillStyle = '#b8433f'; g.fillRect(0, h - 8, w * (0.2 + r() * 0.6), 8); // 播放条(只用形状,不放台标)
56
+ };
57
+ const photoSprite = CL.photo; // 网点照片+纸边(halftone 6px、对比 1.3)
58
+ // 「猫神经元」的最优刺激图(论文 Fig.6 那种模糊猫脸),用大块柔光画,过网点
59
+ const neuronDraw = (g, w, h) => {
60
+ g.fillStyle = '#7b7b7b'; g.fillRect(0, 0, w, h); const k = w / 560;
61
+ const blob = (x, y, rx, ry, col) => { g.save(); g.filter = `blur(${26 * k}px)`; g.fillStyle = col; g.beginPath(); g.ellipse(x * w, y * h, rx * k, ry * k, 0, 0, Math.PI * 2); g.fill(); g.restore(); };
62
+ blob(0.5, 0.56, 190, 160, '#3b3b3b'); blob(0.28, 0.25, 60, 90, '#3a3a3a'); blob(0.72, 0.25, 60, 90, '#3a3a3a');
63
+ blob(0.5, 0.68, 110, 70, '#c9c9c9'); blob(0.36, 0.5, 34, 26, '#e6e6e6'); blob(0.64, 0.5, 34, 26, '#e6e6e6');
64
+ blob(0.36, 0.5, 12, 18, '#111'); blob(0.64, 0.5, 12, 18, '#111'); blob(0.5, 0.64, 16, 10, '#222');
65
+ };
66
+ // 剪报(数字出自 Le et al. 2012 论文摘要与 Google 官方博客:1000 台机器、1.6 万个 CPU 核、1000 万张 YouTube 截图、不打标签)
67
+ const NEWS = { w: 860, h: 600, hl: { x: 52, y: 352, w: 590, h: 48 }, circ: { x: 450, y: 226, rx: 96, ry: 50 } };
68
+ const newsSprite = () => sprite('news', NEWS.w, NEWS.h, (g) => {
69
+ const tile = CL.paperTile('news', C.news, { amt: 9, fibers: 160 });
70
+ g.save(); tornPoly(g, NEWS.w, NEWS.h, 21, 4); g.clip(); g.fillStyle = g.createPattern(tile, 'repeat'); g.fillRect(0, 0, NEWS.w, NEWS.h);
71
+ const yel = g.createRadialGradient(NEWS.w * 0.4, NEWS.h * 0.4, 50, NEWS.w * 0.5, NEWS.h * 0.5, NEWS.w * 0.75); yel.addColorStop(0, 'rgba(255,255,255,0)'); yel.addColorStop(1, 'rgba(170,150,110,.2)'); g.fillStyle = yel; g.fillRect(0, 0, NEWS.w, NEWS.h);
72
+ g.fillStyle = C.ink; g.font = `600 22px ${SERIF}`; g.fillText('科技 · 2012 年 6 月', 52, 64);
73
+ g.fillRect(52, 80, NEWS.w - 104, 3); g.fillRect(52, 86, NEWS.w - 104, 1);
74
+ g.font = `900 66px ${SERIF}`; g.fillText('谷歌的电脑', 52, 170); g.fillText('自己学会了认猫', 52, 250);
75
+ g.font = `400 32px ${SERIF}`; g.fillStyle = '#2a2723';
76
+ g.fillText('1000 台机器、1.6 万个处理器核心,', 52, 330); g.fillText('看了 1000 万张 YouTube 视频截图,', 52, 386); g.fillText('没有人告诉过它,哪一张是猫。', 52, 442);
77
+ g.fillStyle = 'rgba(40,36,30,.3)'; for (let i = 0; i < 3; i++) g.fillRect(52, 482 + i * 34, [700, 640, 420][i], 14);
78
+ g.restore();
79
+ });
80
+ const CARD = { w: 520, h: 330 };
81
+ const cardSprite = () => sprite('card', CARD.w, CARD.h, (g) => {
82
+ g.fillStyle = C.card; g.fillRect(0, 0, CARD.w, CARD.h);
83
+ g.strokeStyle = 'rgba(80,140,200,.3)'; g.lineWidth = 2; for (let y = 96; y < CARD.h; y += 52) { g.beginPath(); g.moveTo(0, y); g.lineTo(CARD.w, y); g.stroke(); }
84
+ g.strokeStyle = 'rgba(184,67,63,.5)'; g.beginPath(); g.moveTo(0, 60); g.lineTo(CARD.w, 60); g.stroke();
85
+ });
86
+
87
+ // ---------- 桌面世界 ----------
88
+ const R1 = [960, 540], R2 = [3100, 720];
89
+ const NEWSPOS = { x: 620, y: 520, r: -0.035 }, PHOTO = { x: 1440, y: 470, r: 0.06, w: 380, h: 430 };
90
+ const THUMBS = (() => { const r = rng(55), o = [], kinds = ['dog', 'car', 'man', 'tree', 'house', 'game']; const cats = new Set([3, 8, 13, 16, 22]);
91
+ for (let j = 0; j < 4; j++) for (let i = 0; i < 6; i++) { const k = j * 6 + i; o.push({ x: 2250 + i * 205 + (r() - .5) * 26, y: 420 + j * 150 + (r() - .5) * 20, r: (r() - .5) * 0.09, kind: cats.has(k) ? 'cat' : kinds[(r() * kinds.length) | 0], seed: 100 + k, cat: cats.has(k) }); }
92
+ return o; })();
93
+ const CARDPOS = { x: 3660, y: 560, r: 0.025 };
94
+ const PRINT = { x: 3640, y: 900, r: -0.04, w: 210, h: 210 }; // 调查墙上那张小照片:推进它、硬切到镜 3
95
+ const deskCam = (ft) => {
96
+ const base = CAM.at([
97
+ { t: 0, x: R1[0] + 20, y: R1[1] + 20, z: 1.14 },
98
+ { t: 0.9, x: R1[0] + 10, y: R1[1] + 30, z: 1.165, ease: MO.sineInOut }, // 开场慢推(停留段 0.5–1%/s 的 Ken Burns 加一点)
99
+ // 荧光笔扫完后推向那一行(BD 实测约 +11%/s),1.5s easeInOutSine
100
+ { t: 2.4, x: R1[0] - 60, y: R1[1] + 60, z: 1.28, ease: MO.sineInOut },
101
+ { t: PAN.a, x: R1[0] - 56, y: R1[1] + 60, z: 1.29, ease: MO.sineInOut },
102
+ // 沿红线追过去:1.1s 长尾 bezier(.33,0,.2,1)
103
+ { t: PAN.a + PAN.d, x: R2[0], y: R2[1], z: 1.0, ease: longTail },
104
+ { t: PUSH.a, x: R2[0] + 30, y: R2[1] + 10, z: 1.04, ease: MO.sineInOut },
105
+ ], ft);
106
+ if (ft <= PUSH.a) return base;
107
+ // 推进照片:0.6s easeInOutSine,对数插值推到照片铺满画面(z≈9.6)
108
+ return CAM.pushTo(base, PRINT.x, PRINT.y, 9.6, MO.sineInOut(seg(ft, PUSH.a, PUSH.b)));
109
+ };
110
+ Y2.deskCam = deskCam;
111
+ // 投影很弱(MC 实测黑底卡片几乎无投影;桌面上留一点就够);纸片滑入带倾斜、到位放平(12fps 步进)
112
+ const shadowed = CL.shadowed, slideIn = CL.slideIn, placed = CL.placed, tape = CL.tape, stringPts = CL.stringPts, highlight = CL.highlight;
113
+ const pin = (g, x, y, on) => { if (on) CL.pin(g, x, y, C.red); };
114
+ const NEWS_CIRCLE = DG.hand(DG.ellipsePts(0, 0, NEWS.circ.rx, NEWS.circ.ry, -2.6, 1.1, 40), { amp: 2.2, seed: 9 }), NEWS_CIRCLE_L = DG.cum(NEWS_CIRCLE);
115
+ // 线索红线:从剪报高亮那一行的末端,一路连到调查墙的计数标签(相机沿它追)
116
+ const CLUE = (() => { const a = [NEWSPOS.x + 250, NEWSPOS.y + 70], b = [2400, 1100]; return { a, b, pts: stringPts(a, b, 0.05) }; })();
117
+ CLUE.cum = DG.cum(CLUE.pts);
118
+ const drawDesk = (g, cam, ft) => {
119
+ g.save(); CAM.apply(g, cam);
120
+ g.fillStyle = g.createPattern(CL.paperTile('desk', C.desk, { amt: 12, fibers: 320, fiberCol: [110, 95, 70] }), 'repeat'); g.fillRect(-1200, -1200, 6800, 3600);
121
+ const st = step(ft);
122
+ // —— 区域 1:剪报+照片(随切点直接在桌上) ——
123
+ placed(g, NEWSPOS.x, NEWSPOS.y, NEWSPOS.r, (g) => {
124
+ const sp = newsSprite(); shadowed(g, () => g.drawImage(sp, -NEWS.w / 2, -NEWS.h / 2));
125
+ const b = NEWS.hl; highlight(g, -NEWS.w / 2 + b.x - 8, -NEWS.h / 2 + b.y - 6, b.w + 16, b.h + 12, MO.sineInOut(seg(st, 0.35, 1.25)));
126
+ const cq = MO.cubicOut(seg(st, 1.35, 2.0)); // 红笔圈:0.65s,首尾不闭合
127
+ if (cq > 0) { g.save(); g.translate(-NEWS.w / 2 + NEWS.circ.x, -NEWS.h / 2 + NEWS.circ.y); g.strokeStyle = C.red; g.lineWidth = 7; g.lineCap = 'round'; g.lineJoin = 'round'; DG.drawPartial(g, NEWS_CIRCLE, NEWS_CIRCLE_L, NEWS_CIRCLE_L[NEWS_CIRCLE_L.length - 1] * cq); g.restore(); }
128
+ });
129
+ placed(g, PHOTO.x, PHOTO.y, PHOTO.r, (g) => {
130
+ const sp = photoSprite('catphoto', PHOTO.w, PHOTO.h, (gg, w, h) => catPhoto(gg, w, h), { border: 18, cell: 7 });
131
+ shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); tape(g, -sp.width / 2 + 30, -sp.height / 2 + 10, -0.6); tape(g, sp.width / 2 - 30, -sp.height / 2 + 10, 0.5);
132
+ });
133
+ // Borders 式黑底白字标签条
134
+ placed(g, 1260, 830, -0.02, (g) => { g.fillStyle = C.black; g.fillRect(-150, -40, 300, 80); g.fillStyle = C.paper; g.font = '46px "PuHui-Heavy"'; g.textAlign = 'center'; g.textBaseline = 'middle'; g.fillText('2012 · 谷歌', 0, 3); });
135
+ // —— 线索红线(一直在桌上;相机沿它追) ——
136
+ CL.string(g, CLUE.pts, CLUE.cum, 1e9, { col: C.red, lw: 5 });
137
+ pin(g, CLUE.a[0], CLUE.a[1], true); pin(g, CLUE.b[0], CLUE.b[1], true);
138
+ // —— 区域 2:调查墙(截图早就钉在墙上) ——
139
+ for (const [i, th] of THUMBS.entries()) placed(g, th.x, th.y, th.r, (g) => { const sp = photoSprite('th' + i, 168, 94, (gg, w, h) => thumbScene(gg, w, h, th.kind, th.seed), { border: 7, cell: 4 }); shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); });
140
+ placed(g, CARDPOS.x, CARDPOS.y, CARDPOS.r, (g) => {
141
+ shadowed(g, () => g.drawImage(cardSprite(), -CARD.w / 2, -CARD.h / 2));
142
+ // 打字机:13 字/秒(MC 实测),按一拍二出字
143
+ const lines = ['没有人告诉它', '哪一张是猫'], t0 = PAN.a + PAN.d + 0.1;
144
+ g.fillStyle = C.ink; g.font = `600 52px ${SERIF}`; g.textBaseline = 'alphabetic';
145
+ const typed = TY.typedCount(st, t0, 13);
146
+ let k = 0; lines.forEach((ln, li) => { let x = -CARD.w / 2 + 40; for (const ch of ln) { if (k < typed) g.fillText(ch, x, -CARD.h / 2 + 140 + li * 104); x += g.measureText(ch).width; k++; } });
147
+ });
148
+ // 红线:每根 0.5s 描出(12fps),错开 0.15s;图钉随线头一起出现
149
+ const cats = THUMBS.filter(t => t.cat), cardPin = [CARDPOS.x - 40, CARDPOS.y - CARD.h / 2 + 22];
150
+ cats.forEach((th, i) => {
151
+ const t0 = PAN.a + PAN.d + 0.15 + i * 0.15, q = MO.sineInOut(seg(st, t0, t0 + 0.5)); if (q <= 0) return;
152
+ const a = [th.x + 40, th.y - 28], pts = stringPts(a, cardPin), L = DG.cum(pts);
153
+ CL.string(g, pts, L, L[L.length - 1] * q, { col: C.red, lw: 4 });
154
+ pin(g, a[0], a[1], true);
155
+ });
156
+ pin(g, cardPin[0], cardPin[1], st >= PAN.a + PAN.d + 0.15);
157
+ // 计数标签:黑胶带,数字 12fps 滚到 10,000,000(cubicOut)
158
+ placed(g, 2770, 1110, -0.03, (g) => {
159
+ const nq = MO.cubicOut(seg(st, PAN.a + 0.6, PAN.a + 2.0));
160
+ shadowed(g, () => { g.fillStyle = C.black; g.save(); g.translate(-380, -52); tornPoly(g, 760, 104, 77, 3); g.fill(); g.restore(); });
161
+ g.fillStyle = C.paper; g.textBaseline = 'middle'; g.font = `600 40px ${SERIF}`; g.fillText('视频截图', -350, 2);
162
+ g.fillStyle = C.hl; g.font = '74px "Anton-400"'; g.textAlign = 'right'; g.fillText('× ' + Math.round(10000000 * nq).toLocaleString('en-US'), 350, 6);
163
+ });
164
+ // 小照片:它脑中的「猫」,带倾斜滑入放平
165
+ slideIn(g, ft, PAN.a + PAN.d + 0.95, PRINT.x, PRINT.y, PRINT.r, (g) => {
166
+ const sp = photoSprite('neuron_s', PRINT.w, PRINT.h, neuronDraw, { border: 10, cell: 4 });
167
+ shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); tape(g, 0, -sp.height / 2 + 2, 0.05, 90);
168
+ }, { from: [520, 120], tilt: 0.22, dur: 0.55 });
169
+ g.restore();
170
+ };
171
+
172
+ // 屏幕空间:暗角+静态颗粒(Vox 的纸纹是贴死的,不逐帧抖;调研 §9 实测)
173
+ const finish = (c) => {
174
+ const v = PAINT.cached('y2_vig', W, H, (g) => { const r = g.createRadialGradient(W / 2, H / 2, H * 0.45, W / 2, H / 2, H * 1.05); r.addColorStop(0, 'rgba(40,30,20,0)'); r.addColorStop(1, 'rgba(40,30,20,.34)'); g.fillStyle = r; g.fillRect(0, 0, W, H); });
175
+ c.drawImage(v, 0, 0); c.drawImage(PAINT.grain('y2grain', 0.05, [40, 30, 20], 0.14), 0, 0);
176
+ };
177
+ const deskShot = (c, lt, t) => {
178
+ const ft = t - F0, cam = deskCam(ft);
179
+ const buf = PAINT.scratch('yt_y2desk'), g = buf.getContext('2d'); g.reset();
180
+ drawDesk(g, cam, ft);
181
+ c.fillStyle = C.desk; c.fillRect(0, 0, W, H);
182
+ // 快摇时沿速度方向的短模糊(PremiumBeat:模糊比相机运动更短)
183
+ const [vx, vy] = CAM.velocity(deskCam, ft);
184
+ CAM.motionBlur(c, buf, vx * 0.6, vy * 0.6, 7);
185
+ finish(c);
186
+ };
187
+ Y2.shot1 = deskShot; Y2.shot2 = deskShot;
188
+
189
+ // ---------- 镜 3:黑底拼贴(MC 的「黑、纸白、一种红」) ----------
190
+ const BIG = { x: 650, y: 610, w: 560, h: 560 };
191
+ const s3cam = (ft) => {
192
+ const l = ft - Y2.SHOTS.s3[0];
193
+ // 拉出揭示:硬切后从同一张图铺满(z=3.68,和上一镜推满时照片的屏幕宽度一致)0.85s 长尾拉到 1.0;之后慢推 1%/s
194
+ const e = longTail(seg(l, 0, 0.85)), z = CAM.zlerp(3.68, 1.0, e) * (1 + 0.01 * Math.max(0, l - 0.85));
195
+ return CAM.anchor(BIG.x, BIG.y, z, lerp(W / 2, BIG.x, e), lerp(H / 2, BIG.y, e));
196
+ };
197
+ const NCIRC = DG.hand(DG.ellipsePts(0, 0, 340, 330, -2.2, 1.08, 48), { amp: 3, seed: 4 }), NCIRC_L = DG.cum(NCIRC);
198
+ Y2.shot3 = (c, lt, t) => {
199
+ const ft = t - F0, l = ft - Y2.SHOTS.s3[0], cam = s3cam(ft), st = step(l);
200
+ c.save(); CAM.apply(c, cam);
201
+ // 结论还在同一张桌面上:一张大黑卡纸铺在桌上(MC 的黑底卡片),四边露出桌面纸纹
202
+ c.fillStyle = c.createPattern(CL.paperTile('desk', C.desk, { amt: 12, fibers: 320, fiberCol: [110, 95, 70] }), 'repeat'); c.fillRect(-800, -800, W + 1600, H + 1600);
203
+ placed(c, 960, 545, -0.012, (g) => { shadowed(g, () => { g.fillStyle = '#1b1b1a'; g.save(); g.translate(-890, -490); tornPoly(g, 1780, 980, 91, 3); g.fill(); g.restore(); });
204
+ g.save(); g.translate(-890, -490); tornPoly(g, 1780, 980, 91, 3); g.clip(); g.fillStyle = g.createPattern(CL.paperTile('black', '#1b1b1a', { amt: 5, fibers: 90, fiberCol: [90, 90, 85] }), 'repeat'); g.fillRect(0, 0, 1780, 980); g.restore(); });
205
+ placed(c, BIG.x, BIG.y, 0, (g) => { const sp = photoSprite('neuron', BIG.w, BIG.h, neuronDraw, { border: 20, cell: 9 }); g.drawImage(sp, -sp.width / 2, -sp.height / 2);
206
+ tape(g, 0, -sp.height / 2 + 4, 0.05, 220);
207
+ g.fillStyle = C.paper; g.font = `600 40px ${SERIF}`; g.textAlign = 'left'; g.fillText('它脑中的「猫」', -sp.width / 2, sp.height / 2 + 54); });
208
+ // 纸条标题:打字 13 字/秒(一拍二),随后荧光笔扫「猫神经元」
209
+ const T1 = '它自己长出了一个', T2 = '「猫神经元」', t0 = 0.4;
210
+ slideIn(c, l, 0.15, 1490, 330, -0.02, (g) => {
211
+ g.fillStyle = C.paper; g.save(); g.translate(-470, -150); tornPoly(g, 900, 290, 61, 4); g.fill(); g.restore();
212
+ g.fillStyle = C.ink; g.font = `900 70px ${SERIF}`; g.textBaseline = 'alphabetic';
213
+ const typed = TY.typedCount(st, t0, 13);
214
+ let x = -420; [...T1].forEach((ch, i) => { if (i < typed) g.fillText(ch, x, -40); x += g.measureText(ch).width; });
215
+ const w2 = g.measureText(T2).width; highlight(g, -428, 22, w2 + 20, 82, MO.sineInOut(seg(st, 1.5, 1.8)));
216
+ g.fillStyle = C.ink; x = -420; [...T2].forEach((ch, i) => { if (T1.length + i < typed) g.fillText(ch, x, 88); x += g.measureText(ch).width; });
217
+ }, { from: [700, -200], tilt: 0.18, dur: 0.4 });
218
+ // 红笔圈住(0.6s,12fps,首尾略交叉)
219
+ const cq = MO.cubicOut(seg(st, 0.95, 1.5));
220
+ if (cq > 0) { c.save(); c.translate(BIG.x, BIG.y); c.strokeStyle = C.red; c.lineWidth = 9; c.lineCap = 'round'; c.lineJoin = 'round'; DG.drawPartial(c, NCIRC, NCIRC_L, NCIRC_L[NCIRC_L.length - 1] * cq); c.restore(); }
221
+ // 花叔剪纸贴纸:带 12° 倾斜滑入放平;姿势按 12fps 步进(剪纸动画的一顿一顿)
222
+ const sq = clamp((st - 0.6) / 0.6);
223
+ if (sq > 0) {
224
+ const e = MO.quartOut(sq);
225
+ const pose = HUASHU.pose('point', 0, { ua1: 1.35 + 0.12 * Math.sin(st * 5), head: -0.05 + 0.04 * Math.sin(st * 3), mouth: 0.5 + 0.4 * Math.sin(st * 9) });
226
+ const HS = HUASHU.build({ x: 0, y: 0, s: 0.82, face: -1, pose });
227
+ const pal = { skin: '#f3c9a8', hair: '#171716', shirt: '#eeedeb', shorts: '#cdbf9f', hat: '#eeedeb', hatBand: '#b8433f', shoe: '#e8e4da', watch: '#171716', line: '#171716', lw: 5 };
228
+ const sp = PAINT.scratch('y2hs'), g = sp.getContext('2d'); g.reset(); if (!HERO.y2(g, st)) { g.translate(1500, 1040); HUASHU.draw(g, HS, pal); g.setTransform(1, 0, 0, 1, 0, 0); }
229
+ const sil = CL.stickerEdge(sp, 'y2sil', 12, '#f4f2ec'); // 剪纸贴纸:外扩 12px 的纸白边
230
+ c.save(); c.translate(1500 + 1100 * (1 - e), 1040 + 120 * (1 - e)); c.rotate(0.02 + 0.21 * (1 - Math.min(1, e * 1.15))); c.translate(-1500, -1040);
231
+ c.drawImage(sil, 0, 0); c.drawImage(sp, 0, 0); c.restore();
232
+ }
233
+ c.restore();
234
+ finish(c);
235
+ };
236
+ })();
237
+
238
+ SCENES['y2_s1'] = { draw: (c, lt, t) => Y2.shot1(c, lt, t) };
239
+ SCENES['y2_s2'] = { draw: (c, lt, t) => Y2.shot2(c, lt, t) };
240
+ SCENES['y2_s3'] = { draw: (c, lt, t) => Y2.shot3(c, lt, t) };
@@ -0,0 +1,10 @@
1
+ // 示范 · 白板手绘解说(RSA Animate 型)「AI 是怎么学会认猫的」(8 秒三镜)。语法卡:references/动画语法/y3_whiteboard.md
2
+ // 预览 index.html?film=demos/y3_whiteboard 渲染 render.py --film demos/y3_whiteboard --out y3.mp4
3
+ // 一整块大白板=世界,相机在板上移动;三段是同一个画面函数(DG.board 的笔画时间线),段只是为了 qa 分镜。
4
+ window.PUNCH = 0;
5
+ window.SCENE_LIBS = ['demos/_shared/hero.js', 'demos/y3_whiteboard/whiteboard.js'];
6
+ window.ERAS = [
7
+ { id: 'y3_s1', dur: 2.5 }, // 画猫、写「猫?」、写问题
8
+ { id: 'y3_s2', dur: 2.25, transition: { type: 'same', dur: 0.8 } }, // 甩镜(0.8s 带运动模糊,笔不离板)
9
+ { id: 'y3_s3', dur: 3.25, transition: { type: 'same', dur: 0.85 } }, // 板上平滑移动 → 画花叔、「是猫!」→ 拉远看全图
10
+ ];