@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,565 @@
1
+ // 2026 扁平矢量插画(Dribbble 风)+ 全片笑点收尾——纯代码。
2
+ // 结构:①静态底(米白墙/地、粉圆与淡紫色块的颗粒点彩、窗与城市剪影、桌椅地毯、盆,缓存)
3
+ // → ②会动的环境(云左移、飞机、太阳光芒、装饰符号 4fps 沸腾、吊灯与光锥摆、两盆植物晃)
4
+ // → ③角色(自写 2026 姿势库;少女头部/躯干取自 RIG.girl,手臂用 RIG.ik2;猫按姿势关键点插值)
5
+ // → ④道具表演(杯子三次被拍、坠落、碎裂、咖啡渍与涟漪)→ ⑤签名手写 → ⑥角色颗粒噪点(source-atop)。
6
+ // 所有表演时间都按「全片帧号 F = t*60」写,数字直接抄 ../拆解/分镜表.md §4。
7
+ SCENES['16_2026'] = (() => {
8
+ const W = 1920, H = 1080, P = PAINT, { clamp, lerp, ease } = U;
9
+ const C = {
10
+ wall: '#f6efe2', floor: '#eedfc7', navy: '#262a5c', coral: '#e8655a', coralLt: '#f08b7d', coralDk: '#d1514a',
11
+ pink: '#f5cdc6', pinkDot: '#e8807b', lav: '#e4dbf6', lavDot: '#b9a4ea', red: '#e65a52', yellow: '#f2b33d', sun: '#f3b13c',
12
+ green: '#2f8a5d', greenDk: '#22704a', greenLt: '#57b07a', mint: '#d6ece0', chair: '#7cb791', white: '#fffaf2',
13
+ skin: '#f8d3bd', skinDk: '#eab79f', hair: '#f4c24f', hairDk: '#dca23a', blush: '#f39a8d', pants: '#262a5c', sock: '#b9a4ea',
14
+ orange: '#f0a23c', orangeDk: '#d8832a', catWhite: '#fff8ee', earIn: '#f4aaa0', laptop: '#9a88df', laptopDk: '#7563c4',
15
+ coffee: '#b8782f', coffeeLt: '#d79a4a', cup: '#e35b55', cupDk: '#c4433e',
16
+ };
17
+ const FR = t => t * 60; // 全片帧号
18
+ const seg = (f, a, b) => clamp((f - a) / (b - a));
19
+ // 关键帧插值:[[帧, 值], ...],段内 ease
20
+ const kf = (f, tab, e = ease.inOut) => { if (f <= tab[0][0]) return tab[0][1]; for (let i = 1; i < tab.length; i++) if (f <= tab[i][0]) { const q = (f - tab[i - 1][0]) / (tab[i][0] - tab[i - 1][0]); return lerp(tab[i - 1][1], tab[i][1], e(q)); } return tab[tab.length - 1][1]; };
21
+ // 点表的 Catmull-Rom 插值(分镜表的质心/杯子轨迹都是每 2 帧一个实测点)
22
+ const path = (f, tab) => {
23
+ if (f <= tab[0][0]) return [tab[0][1], tab[0][2]]; const n = tab.length; if (f >= tab[n - 1][0]) return [tab[n - 1][1], tab[n - 1][2]];
24
+ let i = 1; while (tab[i][0] < f) i++;
25
+ const p0 = tab[Math.max(0, i - 2)], p1 = tab[i - 1], p2 = tab[i], p3 = tab[Math.min(n - 1, i + 1)], q = (f - p1[0]) / (p2[0] - p1[0]);
26
+ const cr = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * q + (2 * a - 5 * b + 4 * c - d) * q * q + (-a + 3 * b - 3 * c + d) * q * q * q);
27
+ return [cr(p0[1], p1[1], p2[1], p3[1]), cr(p0[2], p1[2], p2[2], p3[2])];
28
+ };
29
+ // 阻尼摆:从 f0 开始的二次动作
30
+ const damped = (f, f0, amp, period, decay) => f < f0 ? 0 : amp * Math.exp(-(f - f0) / decay) * Math.sin((f - f0) / period * Math.PI * 2);
31
+ const smooth = (pts, closed = true) => RIG.smooth(pts, closed);
32
+
33
+ // ---------- 颗粒点彩:在一个形状里撒点,密度随位置变化 ----------
34
+ function stipple(g, pathOrFn, box, col, density, seed, size = 2.2) {
35
+ const r = U.rng(seed); const [x0, y0, x1, y1] = box; g.save(); if (pathOrFn instanceof Path2D) g.clip(pathOrFn); g.fillStyle = col;
36
+ const n = Math.round((x1 - x0) * (y1 - y0) * 0.02);
37
+ for (let i = 0; i < n; i++) { const x = lerp(x0, x1, r()), y = lerp(y0, y1, r()); if (r() < density(x, y)) { const s = size * (0.6 + r() * 0.8); g.fillRect(x, y, s, s); } }
38
+ g.restore();
39
+ }
40
+
41
+ // ---------- 静态背景 ----------
42
+ const PINK = [560, 485, 392], LAVP = [[1215, 230], [1330, 196], [1460, 230], [1545, 330], [1585, 470], [1560, 610], [1480, 720], [1350, 765], [1215, 740], [1140, 640], [1120, 470], [1150, 320]];
43
+ function background(g) {
44
+ g.fillStyle = C.wall; g.fillRect(0, 0, W, 790);
45
+ const fg = g.createLinearGradient(0, 790, 0, H); fg.addColorStop(0, '#eedec4'); fg.addColorStop(1, '#f2e6d2'); g.fillStyle = fg; g.fillRect(0, 790, W, H - 790);
46
+ // 粉色大圆:淡粉底 + 越往下越密的红点(扁平插画的颗粒渐变)
47
+ const pc = new Path2D(); pc.arc(PINK[0], PINK[1], PINK[2], 0, 7);
48
+ g.save(); g.clip(pc); const pg = g.createLinearGradient(0, PINK[1] - PINK[2], 0, PINK[1] + PINK[2]); pg.addColorStop(0, 'rgba(246,214,206,.55)'); pg.addColorStop(1, 'rgba(244,196,188,.95)'); g.fillStyle = pg; g.fill(pc); g.restore();
49
+ stipple(g, pc, [PINK[0] - PINK[2], PINK[1] - PINK[2], PINK[0] + PINK[2], PINK[1] + PINK[2]], C.pinkDot, (x, y) => 0.12 + 0.75 * clamp((y - PINK[1] + 120) / 420) * (0.6 + 0.4 * clamp((PINK[0] + 100 - x) / 300)), 11);
50
+ // 淡紫色块
51
+ const lp = smooth(LAVP); g.fillStyle = C.lav; g.fill(lp);
52
+ stipple(g, lp, [1110, 190, 1600, 770], C.lavDot, (x, y) => 0.06 + 0.6 * clamp((x - 1250) / 340) * clamp((y - 300) / 400 + 0.3), 12);
53
+ // 地毯
54
+ const rug = new Path2D(); rug.ellipse(1232, 902, 385, 33, 0, 0, 7); g.fillStyle = C.mint; g.fill(rug); stipple(g, rug, [847, 869, 1617, 935], '#9fcfb4', () => 0.35, 13, 2.4);
55
+ // 地面软影
56
+ g.fillStyle = 'rgba(160,140,120,.22)'; [[185, 902, 100, 9], [1010, 897, 95, 8], [1720, 903, 110, 9]].forEach(([x, y, rx, ry]) => { g.beginPath(); g.ellipse(x, y, rx, ry, 0, 0, 7); g.fill(); });
57
+ // 窗:白色圆角框
58
+ g.save(); g.shadowColor = 'rgba(150,120,110,.18)'; g.shadowBlur = 18; g.shadowOffsetY = 8; g.fillStyle = C.white; g.beginPath(); g.roundRect(360, 120, 440, 445, 34); g.fill(); g.restore();
59
+ const panes = new Path2D(); panes.roundRect(380, 140, 400, 400, 22);
60
+ g.save(); g.clip(panes);
61
+ const sg = g.createLinearGradient(0, 140, 0, 540); sg.addColorStop(0, '#a6d4f1'); sg.addColorStop(0.6, '#cbe9f3'); sg.addColorStop(1, '#e0f2ef'); g.fillStyle = sg; g.fillRect(380, 140, 400, 400);
62
+ stipple(g, null, [380, 140, 780, 540], '#7fb8e6', (x, y) => 0.22 * (1 - (y - 140) / 400), 14, 2);
63
+ // 城市剪影
64
+ const bld = (x, y, w, h, col, win, round = 0) => { g.fillStyle = col; g.beginPath(); round ? (g.moveTo(x, y + h), g.lineTo(x, y + round), g.arc(x + w / 2, y + round, w / 2, Math.PI, 0), g.lineTo(x + w, y + h)) : g.rect(x, y, w, h); g.fill();
65
+ if (win) { g.fillStyle = win; for (let yy = y + (round ? round : 0) + 14; yy < y + h - 10; yy += 22) for (let xx = x + 10; xx < x + w - 12; xx += 20) g.fillRect(xx, yy, 9, 10); } };
66
+ bld(380, 420, 74, 120, '#c5b2ec'); bld(505, 372, 68, 168, '#ec9e9a'); bld(398, 452, 88, 88, '#a98fe2', '#fffaf2');
67
+ bld(690, 395, 90, 145, '#c9b6ee', null, 45); bld(597, 430, 78, 110, '#7fb593', '#d6ecd9', 39);
68
+ bld(470, 478, 95, 62, '#e85f57', '#fff3e6'); bld(550, 500, 70, 40, '#f2b33d'); bld(662, 468, 118, 72, C.navy, '#f6c445');
69
+ g.fillStyle = '#4c9a69'; [[405, 540, 28], [768, 540, 22]].forEach(([x, y, r]) => { g.beginPath(); g.arc(x, y, r, Math.PI, 0); g.fill(); });
70
+ g.restore();
71
+ g.fillStyle = C.white; g.fillRect(572, 140, 15, 400); g.fillRect(380, 332, 400, 15);
72
+ // 窗台
73
+ g.save(); g.shadowColor = 'rgba(150,120,110,.22)'; g.shadowBlur = 10; g.shadowOffsetY = 6; g.fillStyle = C.white; g.beginPath(); g.roundRect(338, 545, 484, 24, 12); g.fill(); g.restore();
74
+ // 椅子(在少女后面)
75
+ g.fillStyle = C.chair; g.beginPath(); g.roundRect(1448, 470, 44, 300, 22); g.fill();
76
+ g.fillStyle = '#6aa680'; g.beginPath(); g.roundRect(1300, 738, 190, 24, 12); g.fill();
77
+ g.strokeStyle = C.chair; g.lineWidth = 12; g.lineCap = 'round'; [[1330, 762, 1312, 898], [1462, 762, 1478, 898], [1478, 770, 1500, 893]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
78
+ // 桌子(藏青、圆角)
79
+ g.fillStyle = C.navy; g.beginPath(); g.moveTo(996, 662); g.lineTo(1024, 662); g.lineTo(1030, 882); g.lineTo(990, 882); g.closePath(); g.fill();
80
+ g.beginPath(); g.roundRect(925, 878, 170, 20, 10); g.fill();
81
+ g.beginPath(); g.roundRect(820, 640, 380, 24, 12); g.fill();
82
+ stipple(g, null, [820, 640, 1200, 664], '#3c4178', () => 0.4, 15, 2);
83
+ // 盆
84
+ g.fillStyle = C.red; g.beginPath(); g.moveTo(118, 800); g.lineTo(252, 800); g.lineTo(246, 875); g.quadraticCurveTo(244, 900, 220, 900); g.lineTo(150, 900); g.quadraticCurveTo(126, 900, 124, 875); g.closePath(); g.fill();
85
+ g.fillStyle = C.white; g.fillRect(120, 822, 130, 16);
86
+ stipple(g, null, [118, 800, 252, 900], '#c2403a', (x, y) => y > 838 ? 0.4 : 0.2, 16);
87
+ g.fillStyle = '#9a7ee0'; g.beginPath(); g.roundRect(1628, 735, 176, 24, 8); g.fill();
88
+ g.fillStyle = '#ac94ea'; g.beginPath(); g.moveTo(1640, 758); g.lineTo(1792, 758); g.lineTo(1782, 838); g.quadraticCurveTo(1778, 860, 1755, 860); g.lineTo(1677, 860); g.quadraticCurveTo(1654, 860, 1650, 838); g.closePath(); g.fill();
89
+ stipple(g, null, [1628, 735, 1804, 860], '#8a6cd6', () => 0.4, 17);
90
+ g.strokeStyle = C.navy; g.lineWidth = 7; [[1662, 858, 1648, 902], [1770, 858, 1784, 902]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
91
+ }
92
+ // 整体纸面颗粒(静态,叠在最上面很淡一层)
93
+ const grainTex = () => P.cached('fl_grain', W, H, (g) => {
94
+ const im = g.createImageData(W, H), d = im.data, r = U.rng(2026);
95
+ for (let i = 0; i < W * H; i++) { const v = r(); if (v < 0.05) { d[i * 4] = 60; d[i * 4 + 1] = 40; d[i * 4 + 2] = 70; d[i * 4 + 3] = 70; } else if (v > 0.965) { d[i * 4] = d[i * 4 + 1] = d[i * 4 + 2] = 255; d[i * 4 + 3] = 90; } }
96
+ g.putImageData(im, 0, 0);
97
+ });
98
+
99
+ // ---------- 会动的环境 ----------
100
+ function sky(c, t) {
101
+ c.save(); const panes = new Path2D(); panes.rect(380, 140, 192, 192); panes.rect(587, 140, 193, 192); c.clip(panes);
102
+ // 太阳:光芒慢转+脉动
103
+ const S = [676, 232];
104
+ c.strokeStyle = C.sun; c.lineWidth = 6; c.lineCap = 'round';
105
+ for (let k = 0; k < 12; k++) { const a = k / 12 * Math.PI * 2 + t * 0.35, r0 = 68, r1 = 80 + (k % 2 ? 0 : 6) + Math.sin(t * 5 + k) * 3; c.beginPath(); c.moveTo(S[0] + Math.cos(a) * r0, S[1] + Math.sin(a) * r0); c.lineTo(S[0] + Math.cos(a) * r1, S[1] + Math.sin(a) * r1); c.stroke(); }
106
+ c.drawImage(P.cached('fl_sun', 130, 130, (g) => { g.fillStyle = C.sun; g.beginPath(); g.arc(65, 65, 56, 0, 7); g.fill(); const r = U.rng(5); g.fillStyle = '#e8743a'; for (let i = 0; i < 2600; i++) { const a = r() * 7, d = Math.sqrt(r()) * 55, x = 65 + Math.cos(a) * d, y = 65 + Math.sin(a) * d; if (r() < 0.15 + 0.7 * clamp((x + y - 100) / 110)) g.fillRect(x, y, 2, 2); } }), S[0] - 65, S[1] - 65);
107
+ // 云:往左慢移(约 0.25px/帧)
108
+ const cloud = (x, y, s) => { c.fillStyle = C.white; c.beginPath(); c.arc(x, y, 22 * s, Math.PI, 0); c.arc(x + 30 * s, y - 10 * s, 28 * s, Math.PI, 0); c.arc(x + 62 * s, y, 20 * s, Math.PI, 0); c.closePath(); c.fill(); c.beginPath(); c.roundRect(x - 22 * s, y - 2, 104 * s, 18 * s, 9 * s); c.fill(); };
109
+ const dx = -(t - 10.8) * 15;
110
+ cloud(445 + dx, 192, 0.8); cloud(540 + dx, 270, 1.15); cloud(760 + dx * 1.2, 310, 0.7);
111
+ // 小飞机+虚线尾迹:12.0s 在 x≈440,13.75s 在 x≈750
112
+ const px = 440 + (t - 12) * 177, py = 156 - (t - 12) * 4;
113
+ c.strokeStyle = 'rgba(255,255,255,.9)'; c.lineWidth = 3; c.setLineDash([10, 8]); c.beginPath(); c.moveTo(px - 140, py + 6); c.lineTo(px - 18, py + 2); c.stroke(); c.setLineDash([]);
114
+ c.fillStyle = '#e65a52'; c.beginPath(); c.roundRect(px - 22, py - 5, 40, 11, 6); c.fill(); c.beginPath(); c.moveTo(px - 4, py); c.lineTo(px - 14, py + 14); c.lineTo(px + 2, py + 2); c.fill(); c.beginPath(); c.moveTo(px - 20, py - 2); c.lineTo(px - 26, py - 13); c.lineTo(px - 14, py - 3); c.fill();
115
+ c.restore();
116
+ }
117
+ // 彩色小装饰:4fps 沸腾(每 0.25s 换一次微小的位移/旋转)
118
+ const DECOS = [['plus', 192, 140, C.red, 1.2], ['dots', 258, 248, '#8dc7a2', 1], ['dot', 86, 318, C.yellow, 1], ['squig', 242, 395, C.navy, 1], ['dots', 304, 482, C.navy, 0.9], ['tri', 118, 553, '#b59bec', 1.1], ['dot', 302, 603, C.red, 0.8],
119
+ ['squig', 80, 712, C.yellow, 0.7], ['half', 898, 130, '#8dc7a2', 1], ['tri', 866, 292, C.yellow, 0.9], ['ring', 1172, 99, C.yellow, 1], ['squig', 1268, 113, C.red, 1], ['dot', 1393, 76, C.navy, 0.8], ['plus', 1190, 190, '#b59bec', 0.9],
120
+ ['dot', 1570, 274, C.yellow, 1], ['x', 1786, 258, C.navy, 1], ['half', 1846, 318, C.red, 1]];
121
+ function decos(c, t) {
122
+ const b = Math.floor(t * 4);
123
+ DECOS.forEach(([k, x, y, col, s], i) => {
124
+ const jx = (U.hash(i, b) - 0.5) * 3, jy = (U.hash(i + 50, b) - 0.5) * 3, ja = (U.hash(i + 99, b) - 0.5) * 0.18;
125
+ c.save(); c.translate(x + jx, y + jy); c.rotate(ja); c.scale(s, s); c.fillStyle = col; c.strokeStyle = col; c.lineCap = 'round'; c.lineJoin = 'round';
126
+ if (k === 'plus' || k === 'x') { if (k === 'x') c.rotate(0.78); c.lineWidth = 8; c.beginPath(); c.moveTo(-13, 0); c.lineTo(13, 0); c.moveTo(0, -13); c.lineTo(0, 13); c.stroke(); }
127
+ else if (k === 'dot') { c.beginPath(); c.arc(0, 0, 11, 0, 7); c.fill(); }
128
+ else if (k === 'ring') { c.lineWidth = 6; c.beginPath(); c.arc(0, 0, 13, 0, 7); c.stroke(); }
129
+ else if (k === 'tri') { c.beginPath(); c.moveTo(0, -16); c.lineTo(15, 12); c.lineTo(-15, 12); c.closePath(); c.fill(); }
130
+ else if (k === 'half') { c.beginPath(); c.arc(0, 6, 20, Math.PI, 0); c.closePath(); c.fill(); }
131
+ else if (k === 'squig') { c.lineWidth = 5; c.beginPath(); for (let s2 = 0; s2 <= 24; s2++) { const xx = -32 + s2 * 2.7, yy = Math.sin(s2 / 24 * Math.PI * 3 + i) * 8; s2 ? c.lineTo(xx, yy) : c.moveTo(xx, yy); } c.stroke(); }
132
+ else if (k === 'dots') { for (let a = 0; a < 3; a++) for (let d = 0; d < 4; d++) { c.beginPath(); c.arc(d * 15 - 22, a * 15 - 15, 3.6, 0, 7); c.fill(); } }
133
+ c.restore();
134
+ });
135
+ }
136
+ // 吊灯+光锥:绕天花板 (1010,0) 摆
137
+ function lamp(c, ang) {
138
+ c.save(); c.translate(1010, 0); c.rotate(ang); c.translate(-1010, 0);
139
+ // 光锥:颗粒黄光(缓存贴图)
140
+ c.drawImage(P.cached('fl_cone', 340, 420, (g) => {
141
+ const r = U.rng(31); for (let i = 0; i < 26000; i++) { const y = r() * 400, w = lerp(56, 150, y / 400), x = 170 + (r() * 2 - 1) * w; const edge = 1 - Math.abs(x - 170) / w;
142
+ const a = (1 - y / 430) * (0.35 + 0.65 * Math.min(1, edge * 3)); if (r() < a) { g.fillStyle = r() < 0.7 ? 'rgba(245,205,95,.75)' : 'rgba(255,240,190,.9)'; g.fillRect(x, y, 2.4, 2.4); } }
143
+ }), 840, 240);
144
+ c.strokeStyle = C.navy; c.lineWidth = 4; c.beginPath(); c.moveTo(1010, 0); c.lineTo(1010, 152); c.stroke();
145
+ c.fillStyle = C.navy; c.beginPath(); c.roundRect(999, 146, 22, 22, 5); c.fill();
146
+ c.fillStyle = C.yellow; c.beginPath(); c.arc(1010, 238, 22, 0, Math.PI); c.fill();
147
+ c.fillStyle = C.red; c.beginPath(); c.moveTo(936, 240); c.bezierCurveTo(936, 190, 970, 164, 1010, 164); c.bezierCurveTo(1050, 164, 1084, 190, 1084, 240); c.closePath(); c.fill();
148
+ c.save(); c.clip(); c.drawImage(P.cached('fl_shadeDots', 150, 80, (g) => { const r = U.rng(41); g.fillStyle = '#c4433e'; for (let i = 0; i < 1500; i++) { const x = r() * 150, y = r() * 78; if (r() < (x / 150) * 0.8) g.fillRect(x, y, 2, 2); } }), 935, 162, 150, 78); c.restore();
149
+ c.restore();
150
+ }
151
+ // 植物:叶子绕盆口摆;cached 叶片贴图
152
+ function snakePlant(c, ang) {
153
+ c.save(); c.translate(185, 805); c.rotate(ang); c.translate(-185, -805);
154
+ const leaf = (bx, tipx, tipy, w, col, edge) => { c.fillStyle = edge; c.beginPath(); c.moveTo(bx - w, 805); c.quadraticCurveTo(bx - w * 0.6, (805 + tipy) / 2, tipx, tipy); c.quadraticCurveTo(bx + w * 0.8, (805 + tipy) / 2, bx + w, 805); c.closePath(); c.fill();
155
+ c.fillStyle = col; c.beginPath(); c.moveTo(bx - w + 5, 805); c.quadraticCurveTo(bx - w * 0.5, (805 + tipy) / 2 + 10, tipx, tipy + 14); c.quadraticCurveTo(bx + w * 0.6, (805 + tipy) / 2 + 10, bx + w - 5, 805); c.closePath(); c.fill(); };
156
+ leaf(150, 98, 600, 18, '#3f9a62', C.yellow); leaf(222, 272, 625, 17, '#3f9a62', C.yellow); leaf(170, 160, 505, 20, '#2e7f50', '#2e7f50'); leaf(200, 215, 545, 19, '#4aa86c', C.yellow); leaf(186, 120, 690, 14, '#2e7f50', C.yellow); leaf(196, 252, 700, 14, '#4aa86c', '#4aa86c');
157
+ c.restore();
158
+ }
159
+ const leafTex = () => P.cached('fl_mleaf', 200, 150, (g) => {
160
+ // 龟背竹叶:叶片(上半深一点)→ 从主脉两侧斜向叶缘的浅色长缝 → 主脉
161
+ g.translate(100, 75); const leaf = new Path2D(); leaf.ellipse(0, 0, 94, 66, 0, 0, 7);
162
+ g.fillStyle = '#2f8a5d'; g.fill(leaf); g.save(); g.clip(leaf); g.fillStyle = '#26774f'; g.fillRect(-100, -70, 200, 70);
163
+ g.strokeStyle = '#eef3ea'; g.lineCap = 'round'; g.lineWidth = 8;
164
+ for (const sg of [-1, 1]) for (let k = 0; k < 5; k++) { const x0 = -58 + k * 28, a = sg * (0.95 - k * 0.05); g.beginPath(); g.moveTo(x0 + 6, sg * 13); g.lineTo(x0 + 6 + Math.cos(a) * 70 * 0.45, sg * 13 + Math.sin(a) * 70); g.stroke(); }
165
+ g.fillStyle = '#eef3ea'; [[-30, -34], [26, 36], [-6, 40]].forEach(([x, y]) => { g.beginPath(); g.ellipse(x, y, 6, 4, 0.4, 0, 7); g.fill(); });
166
+ g.restore();
167
+ g.strokeStyle = '#1f6844'; g.lineWidth = 4; g.beginPath(); g.moveTo(-94, 0); g.lineTo(94, 0); g.stroke();
168
+ });
169
+ const MLEAVES = [[1705, 352, 1.0, -1.57], [1590, 468, 0.82, -2.4], [1845, 448, 0.9, -0.7], [1563, 665, 0.62, -2.9], [1883, 640, 0.66, -0.25]];
170
+ function monstera(c, ang, f) {
171
+ c.save(); c.translate(1715, 738); c.rotate(ang); c.translate(-1715, -738);
172
+ MLEAVES.forEach(([x, y, s, a], i) => {
173
+ const w = Math.sin(f * 0.05 + i * 1.7) * 0.02 + ang * (1 + i * 0.4);
174
+ c.strokeStyle = '#2f8a5d'; c.lineWidth = 6; c.beginPath(); c.moveTo(1715, 738); c.quadraticCurveTo(lerp(1715, x, 0.6), lerp(738, y, 0.3), x, y); c.stroke();
175
+ c.save(); c.translate(x, y); c.rotate(a + Math.PI / 2 + w); c.scale(s, s); c.drawImage(leafTex(), -100, -75); c.restore();
176
+ });
177
+ c.restore();
178
+ }
179
+
180
+ // ---------- 少女(头/躯干=RIG.girl,手臂=RIG.ik2,腿自画) ----------
181
+ // 姿势:type(闭眼微笑打字)→ pet(睁眼、小 o 嘴、手伸向猫)→ shock(举双手、大张嘴、紧张线)
182
+ function girl(c, f, t, layer) {
183
+ const G = RIG.girl({ s: 1.12, hair: 'bun' });
184
+ const Hd = RIG.girl({ s: 1.344, dy: 43.7, hair: 'bun' }); // 头部放大 1.2 倍(围绕颈点),扁平插画大头比例
185
+ const A = G.A;
186
+ const pet = seg(f, 688, 704), shock = seg(f, 791, 798), shockE = ease.outBack(shock);
187
+ const jit = f > 830 && f < 888 ? [(U.hash(Math.floor(f / 2), 3) - 0.5) * 4, (U.hash(Math.floor(f / 2), 4) - 0.5) * 3] : [0, 0];
188
+ const breathe = Math.sin(t * 3.2) * 2;
189
+ // 手的目标点
190
+ const typ = (k) => [1128 + k * 30, 612 - Math.max(0, Math.sin(t * 22 + k * 2.4)) * 7];
191
+ const petN = [1222 + Math.sin(t * 6) * 6, 548 + Math.cos(t * 6) * 3], petF = [1238, 590];
192
+ const shN = [1228, 432], shF = [1262, 462];
193
+ const tgt = (k) => { let p = typ(k); const q = ease.inOut(pet); p = [lerp(p[0], (k ? petF : petN)[0], q), lerp(p[1], (k ? petF : petN)[1], q)]; return [lerp(p[0], (k ? shF : shN)[0], shockE), lerp(p[1], (k ? shF : shN)[1], shockE)]; };
194
+ const S = [A.shoulder[0] + 6, A.shoulder[1] + 4 + breathe];
195
+ const arm = (Tg, dark, k) => {
196
+ const Sh = [S[0] + (k ? 28 : 0), S[1] + (k ? 10 : 0)];
197
+ const { E, H: Hn } = RIG.ik2(Sh, Tg, 150, 140, -1);
198
+ const sleeve = dark ? C.coralDk : C.coral;
199
+ c.fillStyle = sleeve; c.fill(RIG.taper(Sh, E, 62, 50, 6)); c.fill(RIG.taper(E, Hn, 50, 44, 2));
200
+ const cuffA = [lerp(E[0], Hn[0], 0.82), lerp(E[1], Hn[1], 0.82)];
201
+ c.fillStyle = dark ? '#b8413b' : C.coralDk; c.fill(RIG.taper(cuffA, Hn, 46, 46));
202
+ // 手:手套形+拇指;举手时手指朝上张开
203
+ const a = Math.atan2(Hn[1] - E[1], Hn[0] - E[0]), hc = [Hn[0] + Math.cos(a) * 18, Hn[1] + Math.sin(a) * 18];
204
+ c.save(); c.translate(...hc); c.rotate(lerp(a, -Math.PI / 2 - 0.25 + k * 0.2, shockE));
205
+ c.fillStyle = dark ? C.skinDk : C.skin; c.beginPath(); c.ellipse(4, 0, 24, 19, 0, 0, 7); c.fill();
206
+ if (shock > 0.3) { for (let i = 0; i < 3; i++) { c.beginPath(); c.roundRect(14, -15 + i * 11, 22 * shockE, 10, 5); c.fill(); } }
207
+ c.beginPath(); c.ellipse(-2, (k ? 1 : -1) * -14, 10, 7, -0.6, 0, 7); c.fill();
208
+ c.restore();
209
+ };
210
+ if (layer === 'back') {
211
+ // 远侧腿+粉色鞋、远侧手臂(在身体后面)
212
+ c.fillStyle = '#1d2050'; c.fill(RIG.taper([1440, 718], [1232, 730], 86, 74)); c.fill(RIG.taper([1238, 728], [1246, 848], 68, 58));
213
+ c.fillStyle = C.sock; c.fillRect(1218, 826, 56, 26); c.fillStyle = '#f2a7a0'; c.beginPath(); c.roundRect(1222, 848, 52, 38, 16); c.fill();
214
+ arm(tgt(1), true, 1);
215
+ return;
216
+ }
217
+ if (layer === 'body') {
218
+ // 腿:藏青长裤+淡紫袜+白球鞋(珊瑚鞋底)
219
+ c.fillStyle = C.pants; c.fill(RIG.taper([1430, 712], [1206, 728], 96, 84)); c.fill(RIG.taper([1206, 724], [1214, 840], 78, 64));
220
+ c.fillStyle = C.sock; c.fillRect(1186, 822, 58, 30);
221
+ c.fillStyle = C.white; c.beginPath(); c.moveTo(1112, 886); c.quadraticCurveTo(1118, 852, 1176, 850); c.lineTo(1236, 850); c.quadraticCurveTo(1244, 870, 1240, 886); c.closePath(); c.fill();
222
+ c.fillStyle = C.red; c.beginPath(); c.roundRect(1106, 884, 136, 12, 6); c.fill();
223
+ // 毛衣:RIG 躯干 + 下摆延到胯部;亮面斜块
224
+ c.save(); c.translate(0, breathe * 0.5);
225
+ const sweater = smooth([[A.neck[0] - 18, A.neck[1] + 2], [A.neck[0] + 40, A.neck[1] - 2], [1418, 446], [1452, 520], [1468, 640], [1462, 740], [1420, 762], [1320, 760], [1296, 690], [1286, 560], [1284, 470]]);
226
+ c.fillStyle = C.coral; c.fill(sweater);
227
+ c.save(); c.clip(sweater); c.fillStyle = C.coralLt; c.beginPath(); c.moveTo(1284, 470); c.lineTo(1340, 440); c.lineTo(1410, 760); c.lineTo(1290, 760); c.closePath(); c.fill();
228
+ c.fillStyle = C.coralDk; c.fillRect(1290, 744, 190, 20); c.restore();
229
+ c.fillStyle = C.coralDk; c.beginPath(); c.roundRect(A.neck[0] - 26, A.neck[1] - 8, 64, 22, 10); c.fill();
230
+ c.restore();
231
+ // 头:颈 → 后发/丸子 → 脸 → 刘海 → 耳机
232
+ c.save(); c.translate(jit[0], jit[1] + breathe * 0.4);
233
+ const nk = [1300, 401]; c.translate(...nk); c.rotate(-0.04 * pet + 0.05 * shockE); c.translate(-nk[0], -nk[1]);
234
+ c.fillStyle = C.skinDk; c.fill(G.neck);
235
+ c.fillStyle = C.hair; c.fill(Hd.hairBack); c.fill(Hd.bun);
236
+ c.fillStyle = C.hairDk; c.save(); c.clip(Hd.bun); c.beginPath(); c.arc(Hd.bunSpiral[0] + 14, Hd.bunSpiral[1] + 12, 40, 0, 7); c.fill(); c.restore();
237
+ c.fillStyle = C.skin; c.fill(Hd.face);
238
+ c.fillStyle = C.hair; c.fill(Hd.bangs);
239
+ c.strokeStyle = C.hairDk; c.lineWidth = 3.5; c.lineCap = 'round'; Hd.hairLines.forEach(h => c.stroke(h));
240
+ // 腮红
241
+ c.fillStyle = C.blush; c.beginPath(); c.arc(Hd.cheek[0] - 2, Hd.cheek[1] + 2, 15, 0, 7); c.fill();
242
+ // 眼睛:打字时闭眼弧线 → 睁眼圆点(反应①)→ 大眼(反应②)→ 838 帧后挤眼
243
+ const ex = Hd.eye.x + 2, ey = Hd.eye.y + 2;
244
+ c.strokeStyle = C.navy; c.fillStyle = C.navy; c.lineWidth = 4;
245
+ if (f < 686) { c.beginPath(); c.arc(ex, ey - 4, 10, 0.25 * Math.PI, 0.8 * Math.PI); c.stroke(); }
246
+ else if (f > 838) { c.beginPath(); c.moveTo(ex + 9, ey - 8); c.lineTo(ex - 4, ey - 1); c.lineTo(ex + 9, ey + 5); c.stroke(); }
247
+ else { const big = 1 + 0.35 * shockE; c.beginPath(); c.ellipse(ex, ey, 6.5 * big, 7.5 * big, 0, 0, 7); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(ex - 2, ey - 3, 2.2, 0, 7); c.fill();
248
+ c.strokeStyle = C.hairDk; c.lineWidth = 4; c.beginPath(); c.arc(ex + 2, ey - 6 - 6 * big, 12, 1.15 * Math.PI, 1.75 * Math.PI); c.stroke(); }
249
+ // 嘴:微笑 → 小 o → 大张嘴
250
+ const m = [Hd.lips[1][0] + 2, Hd.lips[1][1] + 6];
251
+ if (f < 686) { c.strokeStyle = C.navy; c.lineWidth = 3.5; c.beginPath(); c.arc(m[0] + 4, m[1] - 8, 10, 0.55 * Math.PI, 0.95 * Math.PI); c.stroke(); }
252
+ else if (shock < 0.01) { const o = ease.outBack(seg(f, 686, 692)); c.fillStyle = '#7a2a3a'; c.beginPath(); c.ellipse(m[0] + 2, m[1], 5 * o, 7 * o, 0, 0, 7); c.fill(); }
253
+ else { const o = shockE; c.fillStyle = '#7a2a3a'; c.beginPath(); c.ellipse(m[0] + 3, m[1] + 4 * o, 8 + 4 * o, 8 + 9 * o, 0.1, 0, 7); c.fill(); c.fillStyle = '#ec7a86'; c.beginPath(); c.ellipse(m[0] + 4, m[1] + 4 + 11 * o, 6 * o, 4 * o, 0, 0, 7); c.fill(); }
254
+ // 耳机:白色头梁 + 黄色耳罩
255
+ const er = [Hd.ear[0] + 4, Hd.ear[1]];
256
+ c.strokeStyle = C.white; c.lineWidth = 10; c.lineCap = 'round'; c.beginPath(); c.moveTo(er[0] + 2, er[1] - 34); c.quadraticCurveTo(er[0] - 4, Hd.A.headTop[1] - 34, Hd.A.headTop[0] - 40, Hd.A.headTop[1] + 4); c.stroke();
257
+ c.fillStyle = '#dc9a2c'; c.beginPath(); c.roundRect(er[0] - 18, er[1] - 38, 42, 76, 20); c.fill();
258
+ c.fillStyle = C.yellow; c.beginPath(); c.roundRect(er[0] - 24, er[1] - 34, 34, 68, 17); c.fill();
259
+ c.fillStyle = '#f8cf6a'; c.beginPath(); c.roundRect(er[0] - 18, er[1] - 24, 12, 40, 6); c.fill();
260
+ c.restore();
261
+ return { headTop: [Hd.A.headTop[0] + jit[0], Hd.A.headTop[1] + jit[1]], face: Hd.A.forehead };
262
+ }
263
+ if (layer === 'front') { arm(tgt(0), false, 0); }
264
+ }
265
+ // 紧张线:头顶前方 3 道,793 淡粉 → 796 全红;830–888 轻抖
266
+ function tension(c, f, headTop) {
267
+ if (f < 792) return;
268
+ const q = seg(f, 792, 797), col = P.mix(P.hex('#f6c4c0'), P.hex('#e5463e'), seg(f, 793, 796));
269
+ const j = f > 830 && f < 888 ? (U.hash(Math.floor(f / 2), 9) - 0.5) * 5 : 0;
270
+ const o = [headTop[0] - 30, headTop[1] + 95]; // 从头顶前上方放射(分镜表:紧张线在 (1215–1290, 200–295))
271
+ c.save(); c.strokeStyle = P.rgb(col); c.lineWidth = 7; c.lineCap = 'round';
272
+ [[-2.6, 84, 30], [-2.05, 88, 34], [-1.5, 96, 34]].forEach(([a, r0, L], i) => {
273
+ const r1 = r0 + L * ease.outBack(q), aa = a + j * 0.01 * (i - 1);
274
+ c.beginPath(); c.moveTo(o[0] + Math.cos(aa) * r0 + j, o[1] + Math.sin(aa) * r0); c.lineTo(o[0] + Math.cos(aa) * r1 + j, o[1] + Math.sin(aa) * r1); c.stroke();
275
+ });
276
+ c.restore();
277
+ }
278
+
279
+ // ---------- 猫:姿势库(局部坐标,朝右,原点=质心) ----------
280
+ // 每个姿势:body 10 点、chest 6 点、head[x,y,rx,ry]、front 两条前腿[肩x,肩y,爪x,爪y]、hind 后腿、tail 4 点、ear 角度
281
+ const rel = (o, pts) => pts.map(p => [p[0] - o[0], p[1] - o[1]]);
282
+ const mir = (o, pts) => pts.map(p => [o[0] - (p[0] - o[0]), p[1]]);
283
+ const SIT_F = (() => { const o = [555, 795], R = (pts) => rel(o, pts); return {
284
+ body: R([[430, 905], [428, 852], [452, 794], [505, 760], [570, 752], [626, 768], [662, 818], [672, 872], [658, 905], [540, 908]]),
285
+ chest: R([[612, 762], [660, 800], [674, 862], [660, 906], [622, 906], [606, 840]]),
286
+ haunch: [482 - o[0], 862 - o[1], 54, 44], stripes: [R([[505, 760], [512, 784]]).flat(), R([[466, 780], [486, 796]]).flat(), R([[446, 815], [470, 826]]).flat()],
287
+ head: [628 - o[0], 708 - o[1], 64, 56], front: [R([[648, 842], [650, 896]]).flat(), R([[612, 842], [612, 896]]).flat()],
288
+ tipW: 1, hind: R([[478, 870], [506, 900]]).flat(), tail: R([[438, 893], [392, 905], [380, 945], [420, 950]]), ear: 0, gy: 905 - o[1] }; })();
289
+ const SIT_T = (() => { const o = [1118, 497], M = (pts) => rel(o, mir(o, pts)); return { // 桌上坐姿:实测朝左,镜像成朝右存
290
+ body: M([[1040, 628], [1034, 580], [1046, 515], [1072, 470], [1120, 455], [1170, 470], [1215, 520], [1248, 580], [1250, 628], [1140, 632]]),
291
+ chest: M([[1058, 470], [1104, 466], [1114, 545], [1104, 606], [1060, 612], [1042, 540]]),
292
+ haunch: [-(1196 - o[0]), 588 - o[1], 56, 46], stripes: [M([[1172, 468], [1160, 492]]).flat(), M([[1208, 505], [1188, 520]]).flat(), M([[1234, 548], [1210, 556]]).flat()],
293
+ head: [-(1080 - o[0]), 400 - o[1], 68, 58], front: [M([[1062, 545], [1060, 618]]).flat(), M([[1100, 545], [1100, 618]]).flat()],
294
+ tipW: 0, hind: M([[1196, 600], [1162, 622]]).flat(), tail: M([[1240, 620], [1296, 626], [1318, 596], [1300, 562]]), ear: 0, gy: 629 - o[1] }; })();
295
+ const LEAP = { body: [[-125, 12], [-120, -24], [-80, -46], [0, -48], [70, -42], [114, -20], [122, 12], [90, 36], [0, 40], [-90, 36]],
296
+ chest: [[56, 8], [110, -2], [118, 22], [84, 38], [40, 36], [38, 20]], haunch: [-80, 6, 46, 36], stripes: [[-60, -46, -56, -28], [-22, -48, -18, -30], [18, -47, 20, -30]],
297
+ head: [150, -46, 58, 52], front: [[84, 18, 182, 34], [66, 16, 168, 48]],
298
+ tipW: 1, hind: [-92, 16, -190, 40], tail: [[-122, -6], [-172, -22], [-214, -44], [-252, -30]], ear: 0.35, gy: 40 };
299
+ const LAND = { ...LEAP, front: [[84, 18, 118, 74], [66, 16, 98, 76]], hind: [-92, 16, -150, 56], head: [140, -30, 60, 54], body: LEAP.body.map(p => [p[0] * 0.92, p[1] * 1.08]), gy: 76 };
300
+ const mixA = (a, b, q) => Array.isArray(a) ? a.map((v, i) => mixA(v, b[i], q)) : lerp(a, b, q);
301
+ const mixPose = (a, b, q) => { const o = {}; for (const k in a) o[k] = mixA(a[k], b[k], q); return o; };
302
+
303
+ function catState(f, t) {
304
+ // 质心轨迹(分镜表实测 + 起跳前与落桌后的补点)
305
+ const traj = [[657, 555, 795], [660, 555, 800], [662, 560, 752], [664, 573, 691], [666, 606, 639], [668, 660, 599], [670, 719, 563], [672, 784, 536], [674, 849, 518], [676, 916, 508], [678, 979, 505], [680, 1038, 510], [682, 1088, 518], [684, 1126, 524], [686, 1160, 548]];
306
+ const s = { pose: SIT_F, x: 555, y: 795, rot: 0, sx: 1, sy: 1, dir: 1, pivot: 'ground', eyes: 'open', look: 0, paw: null, dust: 0 };
307
+ if (f < 661) { // 静坐 → 下蹲预备(657–661)
308
+ const q = seg(f, 657.5, 661); s.sy = 1 - 0.14 * ease.inOut(q); s.sx = 1 + 0.07 * ease.inOut(q); s.look = 3 * q; s.poseHeadDy = 8 * q;
309
+ } else if (f < 664) { // 蹬地:身体上扬、拉伸
310
+ const q = seg(f, 661, 664); [s.x, s.y] = path(f, traj); s.pivot = 'center'; s.pose = mixPose(SIT_F, LEAP, ease.in(q) * 0.45);
311
+ s.rot = -0.75 * ease.out(q); s.sy = lerp(0.86, 1.12, ease.out(q)); s.sx = lerp(1.07, 0.92, ease.out(q));
312
+ } else if (f < 686) { // 腾空弧线:身体沿切线方向,672–684 水平拉长
313
+ [s.x, s.y] = path(f, traj); s.pivot = 'center';
314
+ const a = path(f + 0.5, traj), b = path(f - 0.5, traj); const tang = Math.atan2(a[1] - b[1], a[0] - b[0]);
315
+ s.pose = mixPose(SIT_F, LEAP, clamp(0.45 + seg(f, 664, 670) * 0.55)); s.rot = tang * 0.8 * (1 - 0.85 * ease.inOut(seg(f, 679, 686))); // 落地前把身体放平,前爪先着
316
+ const st = seg(f, 670, 676); s.sx = lerp(0.95, 1.12, st); s.sy = lerp(1.08, 0.9, st);
317
+ if (f > 682) { const q = seg(f, 682, 686); s.pose = mixPose(s.pose, LAND, q); s.sx = lerp(s.sx, 1.0, q); s.sy = lerp(s.sy, 1.0, q); }
318
+ } else if (f < 698) { // 落桌挤压 → 转身坐起(690 帧压到最扁时翻面)
319
+ s.pivot = 'ground'; const flip = f >= 690; s.dir = flip ? -1 : 1;
320
+ s.x = kf(f, [[686, 1160], [688, 1168], [690, 1150], [694, 1122], [698, 1118]], ease.out);
321
+ s.pose = f < 688 ? LAND : mixPose(LAND, SIT_T, ease.out(seg(f, 688, 696)));
322
+ s.sy = kf(f, [[686, 0.86], [688, 0.72], [690, 0.7], [692, 0.96], [694, 1.07], [696, 0.98], [698, 1]], ease.inOut);
323
+ s.sx = kf(f, [[686, 1.12], [688, 1.18], [690, 1.15], [692, 1.0], [694, 0.95], [698, 1]], ease.inOut);
324
+ s.y = 628 - s.pose.gy; // 爪子踩在笔记本面上
325
+ } else { // 桌上坐
326
+ s.pose = SIT_T; s.x = 1118; s.y = 497; s.dir = -1; s.pivot = 'ground';
327
+ s.sy = 1 + Math.sin(t * 3) * 0.006;
328
+ }
329
+ // 起跳烟尘 664–670
330
+ s.dust = seg(f, 662, 672);
331
+ // 表情
332
+ if (f >= 664 && f < 692) s.eyes = 'wide';
333
+ if (f >= 835) { s.eyes = 'smug'; if ((f >= 856 && f < 876) || (f >= 886 && f < 893)) s.eyes = 'happy'; }
334
+ s.look = f >= 698 && f < 744 ? 0 : f >= 744 && f < 800 ? -4 : s.look;
335
+ return s;
336
+ }
337
+ // 拍杯:每次 f0 = 杯子开始动的帧;爪子预备 6 帧→ 4 帧击出→ 停 4 帧→ 6 帧收回
338
+ const SWIPES = [744, 766, 786];
339
+ function pawReach(f) {
340
+ for (const f0 of SWIPES) {
341
+ if (f >= f0 - 7 && f < f0 + 16) {
342
+ if (f < f0) return { k: 'wind', q: ease.inOut(seg(f, f0 - 7, f0)), f0 };
343
+ if (f < f0 + 4) return { k: 'hit', q: ease.out(seg(f, f0, f0 + 4)), f0 };
344
+ if (f < f0 + 8) return { k: 'hold', q: 1, f0 };
345
+ return { k: 'back', q: ease.inOut(seg(f, f0 + 8, f0 + 16)), f0 };
346
+ }
347
+ }
348
+ return null;
349
+ }
350
+ function drawCat(c, s, f, t, cupX) {
351
+ const p = s.pose;
352
+ c.save(); c.translate(s.x, s.y);
353
+ const gy = p.gy;
354
+ if (s.pivot === 'ground') { c.translate(0, gy); c.rotate(s.rot); c.scale(s.dir * s.sx, s.sy); c.translate(0, -gy); }
355
+ else { c.rotate(s.rot); c.scale(s.dir * s.sx, s.sy); }
356
+ // 拍杯时:身体前倾,前爪伸向杯子(世界坐标 → 局部)
357
+ let front = p.front.map(l => l.slice()), lean = 0;
358
+ const pr = f >= 736 && f < 805 ? pawReach(f) : null;
359
+ const watch = f >= 724 && f < 812 ? Math.min(seg(f, 724, 736), 1 - seg(f, 800, 812)) * 0.08 : 0; // 拍杯段之间盯着杯子、身子微倾
360
+ if (pr) {
361
+ // 先按「肩到杯沿的世界距离」定这一拍要趴多低,再施加前倾,最后用当前变换的逆矩阵把杯沿换回局部坐标——保证爪子真的碰到杯子
362
+ const cxw = (pr.k === 'wind' ? cupX(pr.f0) : cupX(Math.min(f, pr.f0 + 4))) + 34; // 杯子右沿(世界)
363
+ const Lmax = 0.1 + 0.28 * clamp((s.x - 70 - cxw - 40) / 130);
364
+ lean = pr.k === 'wind' ? lerp(watch, Lmax * 0.45, pr.q) : pr.k === 'hit' ? lerp(Lmax * 0.45, Lmax, pr.q) : pr.k === 'hold' ? Lmax : lerp(Lmax, watch, pr.q);
365
+ c.translate(0, gy); c.rotate(-lean); c.scale(1, 1 - lean * 0.35); c.translate(0, -gy);
366
+ const m = c.getTransform().invertSelf(), tw = m.transformPoint(new DOMPoint(cxw, 606)), tgt = [tw.x, tw.y];
367
+ const rest = [p.front[0][2], p.front[0][3]], up = [p.front[0][0] + 34, p.front[0][1] - 30];
368
+ let pt;
369
+ if (pr.k === 'wind') pt = [lerp(rest[0], up[0], pr.q), lerp(rest[1], up[1], pr.q)];
370
+ else if (pr.k === 'hit') pt = [lerp(up[0], tgt[0], pr.q), lerp(up[1], tgt[1], pr.q)];
371
+ else if (pr.k === 'hold') pt = tgt;
372
+ else pt = [lerp(tgt[0], rest[0], pr.q), lerp(tgt[1], rest[1], pr.q)];
373
+ front[0] = [p.front[0][0] + 8, p.front[0][1] - 10, pt[0], pt[1]];
374
+ } else if (watch) { lean = watch; c.translate(0, gy); c.rotate(-lean); c.scale(1, 1 - lean * 0.35); c.translate(0, -gy); }
375
+ // 尾巴:粗线条,尖端白;坐着时慢摆,拍杯时甩
376
+ const tl = p.tail.map(q => q.slice()); const sw = Math.sin(t * 4.4) * 14 + (pr ? Math.sin(t * 14) * 10 : 0);
377
+ tl[3][0] += sw * 0.6; tl[3][1] += sw; tl[2][0] += sw * 0.3;
378
+ c.lineCap = 'round'; c.lineJoin = 'round';
379
+ const tailP = new Path2D(); tailP.moveTo(...tl[0]); tailP.bezierCurveTo(...tl[1], ...tl[2], ...tl[3]);
380
+ c.strokeStyle = C.orange; c.lineWidth = 30; c.stroke(tailP);
381
+ c.save(); c.setLineDash([9, 16]); c.lineDashOffset = 4; c.strokeStyle = C.orangeDk; c.lineWidth = 30; c.lineCap = 'butt'; c.stroke(tailP); c.restore(); // 尾巴环纹
382
+ if (p.tipW > 0.05) { c.save(); c.globalAlpha = p.tipW; c.clip((() => { const q = new Path2D(); q.arc(tl[3][0], tl[3][1], 26, 0, 7); return q; })()); c.strokeStyle = C.catWhite; c.lineWidth = 30; c.stroke(tailP); c.restore(); }
383
+ // 后腿(腾空时可见)
384
+ const hindP = RIG.taper([p.hind[0], p.hind[1]], [p.hind[2], p.hind[3]], 46, 30, 6); c.fillStyle = C.orangeDk; c.fill(hindP);
385
+ // 远侧前腿
386
+ const leg = (l, col) => { c.fillStyle = col; c.fill(RIG.taper([l[0], l[1]], [l[2], l[3]], 32, 28)); c.fillStyle = C.catWhite; c.beginPath(); c.ellipse(l[2], l[3] - 2, 21, 13, 0, 0, 7); c.fill(); c.strokeStyle = '#e6d6c2'; c.lineWidth = 2.5; c.stroke(); };
387
+ leg(front[1], '#f3e9dc');
388
+ // 身体
389
+ const body = smooth(p.body); c.fillStyle = C.orange; c.fill(body);
390
+ c.save(); c.clip(body);
391
+ c.fillStyle = C.catWhite; c.fill(smooth(p.chest));
392
+ // 背部虎斑
393
+ c.strokeStyle = C.orangeDk; c.lineWidth = 10; c.lineCap = 'round';
394
+ p.stripes.forEach(st => { c.beginPath(); c.moveTo(st[0], st[1]); c.lineTo(st[2], st[3]); c.stroke(); });
395
+ // 后腿大腿(haunch):同色块+一道深色弧边,扁平插画靠这一笔分出体块
396
+ const [hX, hY, hrx, hry] = p.haunch;
397
+ c.fillStyle = '#f3ad4c'; c.beginPath(); c.ellipse(hX, hY, hrx, hry, 0, 0, 7); c.fill();
398
+ c.strokeStyle = C.orangeDk; c.lineWidth = 6; c.beginPath(); c.ellipse(hX, hY, hrx, hry, 0, -0.2 * Math.PI, 0.75 * Math.PI, true); c.stroke();
399
+ c.lineWidth = 8; [[-0.5, 0.15], [-0.15, 0.2]].forEach(([a0, w]) => { c.beginPath(); c.moveTo(hX + Math.cos(Math.PI + a0) * hrx * 0.95, hY + Math.sin(Math.PI + a0) * hry * 0.95); c.lineTo(hX + Math.cos(Math.PI + a0) * hrx * 0.55, hY + Math.sin(Math.PI + a0) * hry * 0.55); c.stroke(); });
400
+ c.restore();
401
+ c.fillStyle = C.catWhite; c.beginPath(); c.ellipse(p.hind[2], p.hind[3], 20, 12, 0, 0, 7); c.fill();
402
+ // 近侧前腿(拍杯那只)
403
+ leg(front[0], C.catWhite);
404
+ // 头
405
+ const [hx, hy0, rx, ry] = p.head, hy = hy0 + (s.poseHeadDy || 0);
406
+ c.save(); c.translate(hx, hy); c.rotate(pr ? -0.08 : 0);
407
+ const ea = p.ear;
408
+ const ear = (x, sgn) => { c.fillStyle = C.orange; c.beginPath(); c.moveTo(x - 26, -ry * 0.55); c.lineTo(x + sgn * 4 - ea * 30 * sgn * 0 - ea * 20, -ry - 30 + ea * 22); c.lineTo(x + 26, -ry * 0.62); c.closePath(); c.fill();
409
+ c.fillStyle = C.earIn; c.beginPath(); c.moveTo(x - 13, -ry * 0.62); c.lineTo(x + sgn * 3 - ea * 20, -ry - 16 + ea * 20); c.lineTo(x + 13, -ry * 0.66); c.closePath(); c.fill(); };
410
+ ear(-30, -1); ear(32, 1);
411
+ c.fillStyle = C.orange; c.beginPath(); c.ellipse(0, 0, rx, ry, 0, 0, 7); c.fill();
412
+ c.fillStyle = C.catWhite; c.beginPath(); c.ellipse(10, ry * 0.42, rx * 0.55, ry * 0.42, 0, 0, 7); c.fill();
413
+ c.strokeStyle = C.orangeDk; c.lineWidth = 6; c.lineCap = 'round'; [-10, 4, 18].forEach(x => { c.beginPath(); c.moveTo(x, -ry + 8); c.lineTo(x, -ry + 26); c.stroke(); });
414
+ // 五官
415
+ const E1 = [-14 + s.look, -4], E2 = [32 + s.look, -4];
416
+ c.fillStyle = C.blush; [[-28, 18], [50, 18]].forEach(([x, y]) => { c.globalAlpha = s.eyes === 'smug' || s.eyes === 'happy' ? 0.95 : 0.7; c.beginPath(); c.ellipse(x, y, s.eyes === 'smug' || s.eyes === 'happy' ? 15 : 12, 9, 0, 0, 7); c.fill(); }); c.globalAlpha = 1;
417
+ c.strokeStyle = C.navy; c.fillStyle = C.navy; c.lineWidth = 4.5;
418
+ [E1, E2].forEach(([x, y]) => {
419
+ if (s.eyes === 'smug') { c.beginPath(); c.arc(x, y - 6, 10, 0.2 * Math.PI, 0.8 * Math.PI); c.stroke(); }
420
+ else if (s.eyes === 'happy') { c.beginPath(); c.arc(x, y + 4, 9, 1.1 * Math.PI, 1.9 * Math.PI); c.stroke(); }
421
+ else { const r = s.eyes === 'wide' ? 9 : 8; c.beginPath(); c.ellipse(x, y, r * 0.85, r, 0, 0, 7); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(x - 2, y - 3, 2.6, 0, 7); c.fill(); c.fillStyle = C.navy; }
422
+ });
423
+ c.fillStyle = '#e8706a'; c.beginPath(); c.moveTo(4, 12); c.lineTo(14, 12); c.lineTo(9, 18); c.closePath(); c.fill();
424
+ c.strokeStyle = C.navy; c.lineWidth = 2.6; c.beginPath(); c.moveTo(-1, 19); c.quadraticCurveTo(4, 25, 9, 19); c.quadraticCurveTo(14, 25, 19, 19); c.stroke();
425
+ c.strokeStyle = '#ffffff'; c.lineWidth = 2.2; [[-40, 10, -78, 2], [-40, 18, -80, 22], [58, 10, 98, 2], [58, 18, 100, 22]].forEach(([a, b2, c2, d]) => { c.beginPath(); c.moveTo(a, b2); c.lineTo(c2, d); c.stroke(); });
426
+ c.restore();
427
+ c.restore();
428
+ }
429
+ // 起跳烟尘:几团米色小圆往外扩散、变淡
430
+ function dust(c, q) {
431
+ if (q <= 0 || q >= 1) return; c.save(); c.fillStyle = `rgba(214,196,170,${0.7 * (1 - q)})`;
432
+ [[-1, 0.8], [-0.5, 1.2], [0.4, 1], [1, 0.7]].forEach(([d, s]) => { c.beginPath(); c.arc(560 + d * (30 + 60 * q), 900 - 10 * q * s, (8 + 16 * q) * s, 0, 7); c.fill(); });
433
+ c.restore();
434
+ }
435
+
436
+ // ---------- 杯子 ----------
437
+ const CUPX = [[744, 981], [746, 975], [748, 949], [750, 937], [752, 932], [766, 932], [768, 906], [770, 887], [772, 879], [774, 877], [786, 877], [788, 865], [790, 830], [792, 811], [794, 802], [796, 801]];
438
+ const cupX = (f) => kf(f, CUPX, ease.linear);
439
+ const FALL = [[796, 801, 605], [798, 794, 612], [800, 786, 623], [802, 776, 640], [804, 766, 664], [806, 755, 695], [808, 746, 733], [810, 739, 779], [812, 738, 831], [814, 743, 887], [815, 744, 896]];
440
+ function cupState(f) {
441
+ if (f < 796) return { x: cupX(f), y: 605, rot: f > 790 ? -0.12 * seg(f, 790, 796) : 0, on: true };
442
+ if (f < 815) { const [x, y] = path(f, FALL); return { x, y, rot: -0.12 - 2.3 * Math.pow(seg(f, 796, 815), 1.3), on: false }; }
443
+ return null;
444
+ }
445
+ function drawMug(c, x, y, rot, s = 1) {
446
+ c.save(); c.translate(x, y); c.rotate(rot); c.scale(s, s);
447
+ c.strokeStyle = C.cup; c.lineWidth = 9; c.beginPath(); c.arc(-34, -2, 15, 0, 7); c.stroke();
448
+ c.fillStyle = C.cup; c.beginPath(); c.roundRect(-30, -32, 60, 66, [6, 6, 14, 14]); c.fill();
449
+ c.fillStyle = C.cupDk; c.beginPath(); c.roundRect(12, -32, 18, 66, [0, 6, 14, 0]); c.fill();
450
+ c.fillStyle = '#7a3a1a'; c.beginPath(); c.ellipse(0, -32, 30, 5, 0, 0, 7); c.fill();
451
+ c.strokeStyle = '#fff4ea'; c.lineWidth = 1.6; c.beginPath(); c.moveTo(-4, -33); c.lineTo(-6, -8); c.stroke();
452
+ c.fillStyle = '#fff4ea'; c.beginPath(); c.roundRect(-14, -8, 16, 18, 3); c.fill(); c.fillStyle = '#4aa36a'; c.beginPath(); c.arc(-6, 1, 4, 0, 7); c.fill();
453
+ c.restore();
454
+ }
455
+ // 杯子坠落时身后的咖啡滴:沿杯子过去的轨迹排一串,越早越小
456
+ function drips(c, f) {
457
+ if (f < 799 || f > 816) return;
458
+ for (let k = 1; k <= 6; k++) { const ff = f - k * 1.3; if (ff < 797) continue; const [x, y] = path(ff, FALL); const r = 7 - k * 0.8;
459
+ c.fillStyle = C.coffee; c.beginPath(); c.ellipse(x + 10 + k * 3, y - 30 - k * 2, r * 0.8, r * 1.2, 0, 0, 7); c.fill(); }
460
+ }
461
+ // 拍杯的动线记号:杯子右侧 3 道短线(击中后 10 帧淡出)
462
+ function motionMarks(c, f) {
463
+ for (const f0 of SWIPES) { const q = (f - f0) / 12; if (q < 0 || q > 1) continue; const x = cupX(f) + 44, a = 1 - q;
464
+ c.save(); c.strokeStyle = `rgba(38,42,92,${a})`; c.lineWidth = 4; c.lineCap = 'round';
465
+ [[-0.6, 0], [0, 6], [0.6, 2]].forEach(([ang, o], i) => { c.beginPath(); const r0 = 12 + q * 10, r1 = 30 + q * 14; c.moveTo(x + Math.cos(ang) * r0, 588 + Math.sin(ang) * r0 * 1.5); c.lineTo(x + Math.cos(ang) * r1, 588 + Math.sin(ang) * r1 * 1.5); c.stroke(); });
466
+ c.restore(); }
467
+ }
468
+ // 碎裂:放射线、两块大碎片抛物线(分镜表三点)、小碎屑、咖啡渍扩散、灰色涟漪
469
+ const quad3 = (f, a, b, c3) => { // 三点 (f,x,y) 抛物线插值(拉格朗日)
470
+ const L = (x0, x1, x2, v0, v1, v2) => v0 * ((f - x1) * (f - x2)) / ((x0 - x1) * (x0 - x2)) + v1 * ((f - x0) * (f - x2)) / ((x1 - x0) * (x1 - x2)) + v2 * ((f - x0) * (f - x1)) / ((x2 - x0) * (x2 - x1));
471
+ return [L(a[0], b[0], c3[0], a[1], b[1], c3[1]), L(a[0], b[0], c3[0], a[2], b[2], c3[2])];
472
+ };
473
+ const SHARDS = [{ a: [818, 723, 860], b: [824, 683, 827], c: [834, 673, 897], pts: [[-26, -14], [10, -24], [24, 6], [-6, 22], [-22, 10]], rot: -3 },
474
+ { a: [818, 792, 849], b: [824, 830, 797], c: [834, 830, 891], pts: [[-20, -20], [22, -12], [18, 18], [-14, 16]], rot: 4 }];
475
+ const BITS = Array.from({ length: 9 }, (_, i) => { const r = U.rng(300 + i); return { vx: (r() - 0.5) * 9, vy: -4 - r() * 6, s: 6 + r() * 7, col: [C.cup, C.cup, C.coffee, '#fff4ea', C.cupDk][i % 5], spin: (r() - 0.5) * 0.6, land: 893 + r() * 18 }; });
476
+ function smash(c, f) {
477
+ if (f < 815) return;
478
+ const X = 744, Y = 896;
479
+ // 咖啡渍(818: 宽140 → 822: 220 → 826: 235)
480
+ const sw = kf(f, [[815, 30], [818, 140], [822, 220], [826, 235]], ease.out);
481
+ c.fillStyle = C.coffee; c.beginPath(); c.ellipse(X + 8, Y + 4, sw / 2, sw * 0.085 + 4, 0, 0, 7); c.fill();
482
+ c.fillStyle = C.coffeeLt; c.beginPath(); c.ellipse(X - 10, Y + 1, sw * 0.18, sw * 0.022 + 1, 0, 0, 7); c.fill();
483
+ // 灰色涟漪:两圈,最宽 ~560,838 消失
484
+ [[818, 838], [822, 840]].forEach(([a, b], i) => { const q = seg(f, a, b); if (q <= 0 || q >= 1) return; const w = lerp(160, 560 - i * 100, ease.out(q));
485
+ c.strokeStyle = `rgba(110,105,130,${0.75 * (1 - q)})`; c.lineWidth = 3.5; c.beginPath(); c.ellipse(X + 10, Y + 6, w / 2, w * 0.075, 0, 0, 7); c.stroke(); });
486
+ // 冲击放射线(815–823)
487
+ const iq = seg(f, 815, 823); if (iq < 1) { c.save(); c.strokeStyle = `rgba(38,42,92,${1 - iq})`; c.lineWidth = 5; c.lineCap = 'round';
488
+ for (let k = 0; k < 7; k++) { const a = -Math.PI + (k + 0.5) / 7 * Math.PI, r0 = 30 + iq * 40, r1 = 58 + iq * 52; c.beginPath(); c.moveTo(X + Math.cos(a) * r0, Y - 6 + Math.sin(a) * r0 * 0.75); c.lineTo(X + Math.cos(a) * r1, Y - 6 + Math.sin(a) * r1 * 0.75); c.stroke(); } c.restore(); }
489
+ // 小碎屑:抛出去落地后停住
490
+ BITS.forEach((b, i) => { const tt = Math.max(0, f - 815); let x = X + b.vx * tt, y = Y - 10 + b.vy * tt + 0.9 * tt * tt * 0.5; if (y > b.land) { const tl = (-b.vy + Math.sqrt(b.vy * b.vy + 2 * 0.45 * 2 * (b.land - Y + 10))) / 0.9; x = X + b.vx * Math.max(0, tl); y = b.land; }
491
+ c.save(); c.translate(x, y); c.rotate(b.spin * Math.min(tt, 18)); c.fillStyle = b.col; c.beginPath(); c.moveTo(-b.s / 2, -b.s / 3); c.lineTo(b.s / 2, -b.s / 2); c.lineTo(b.s / 3, b.s / 2); c.lineTo(-b.s / 2, b.s / 3); c.closePath(); c.fill(); c.restore(); });
492
+ // 两块大碎片
493
+ SHARDS.forEach(s => { const ff = Math.min(f, 834); const [x, y] = ff < 818 ? [lerp(X, s.a[1], seg(ff, 815, 818)), lerp(Y - 10, s.a[2], seg(ff, 815, 818))] : quad3(ff, s.a, s.b, s.c);
494
+ c.save(); c.translate(x, y); c.rotate(s.rot * seg(ff, 815, 834) * 0.6); c.fillStyle = C.cup; c.beginPath(); s.pts.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.closePath(); c.fill();
495
+ c.fillStyle = C.cupDk; c.beginPath(); c.moveTo(...s.pts[0]); c.lineTo(...s.pts[1]); c.lineTo(0, 0); c.closePath(); c.fill(); c.restore(); });
496
+ // 把手(半圆)和茶包标签留在地上
497
+ if (f > 817) { c.strokeStyle = C.cup; c.lineWidth = 8; c.beginPath(); c.arc(X + 30, Y - 4, 13, Math.PI, 0); c.stroke(); c.fillStyle = '#fff4ea'; c.save(); c.translate(X - 12, Y - 4); c.rotate(0.5); c.fillRect(-8, -8, 16, 16); c.fillStyle = '#4aa36a'; c.beginPath(); c.arc(0, 0, 4, 0, 7); c.fill(); c.restore(); }
498
+ }
499
+
500
+ // ---------- 笔记本+打字气泡 ----------
501
+ function laptop(c, f, t) {
502
+ const lid = ease.in(seg(f, 685, 689)); // 猫落下来把盖子压上
503
+ c.fillStyle = C.laptop; c.beginPath(); c.roundRect(1062, 626, 130, 14, 4); c.fill();
504
+ c.fillStyle = C.laptopDk; c.fillRect(1066, 636, 122, 4);
505
+ const a = lerp(-1.91, 0.02, lid), L = 116;
506
+ c.save(); c.translate(1066, 628); c.rotate(a); c.fillStyle = '#b6a8ee'; c.beginPath(); c.roundRect(0, -5, L, 9, 4); c.fill(); c.restore();
507
+ // 「•••」正在输入气泡
508
+ const bs = f < 685 ? 1 : 1 - ease.in(seg(f, 685, 689));
509
+ if (bs > 0.01) { c.save(); c.translate(1088, 482); c.scale(bs, bs); c.fillStyle = C.white; c.beginPath(); c.roundRect(-30, -18, 60, 36, 18); c.fill(); c.beginPath(); c.moveTo(-14, 14); c.lineTo(-22, 30); c.lineTo(-2, 16); c.fill();
510
+ c.fillStyle = '#8a7ad8'; for (let k = 0; k < 3; k++) { const j = Math.max(0, Math.sin(t * 10 - k * 0.9)) * 5; c.beginPath(); c.arc(-14 + k * 14, -j, 5, 0, 7); c.fill(); } c.restore(); }
511
+ }
512
+ function steam(c, t, x, y, a) {
513
+ if (a <= 0) return; c.save(); c.globalAlpha = a; c.strokeStyle = '#ffffff'; c.lineWidth = 5; c.lineCap = 'round';
514
+ for (let k = 0; k < 2; k++) { c.beginPath(); for (let s = 0; s <= 16; s++) { const q = s / 16, yy = y - q * 70, xx = x + (k - 0.5) * 18 + Math.sin(t * 6 + k * 2 - q * 6) * 7 * q; s ? c.lineTo(xx, yy) : c.moveTo(xx, yy); } c.stroke(); }
515
+ c.restore();
516
+ }
517
+
518
+ // ---------- 签名:844–870 从左往右写出,下划线跟着画 ----------
519
+ function signature(c, f) {
520
+ if (f < 842) return;
521
+ const front = kf(f, [[842, 1606], [844, 1646], [848, 1682], [852, 1721], [856, 1762], [860, 1801], [864, 1835], [868, 1861], [870, 1868]], ease.linear);
522
+ const txt = window.SIGNATURE || 'Opus 5.5';
523
+ c.save(); c.font = '64px "Caveat-600"'; const w = c.measureText(txt).width, sc = Math.min(1.3, 236 / w);
524
+ c.beginPath(); c.rect(0, 960, front, 120); c.clip();
525
+ c.fillStyle = C.navy; c.translate(1614, 1032); c.rotate(-0.03); c.scale(sc, sc); c.fillText(txt, 0, 0); c.restore();
526
+ // 下划线:一笔略弯的线,尾端渐细
527
+ c.save(); c.beginPath(); c.rect(0, 960, front, 120); c.clip(); c.strokeStyle = C.navy; c.lineCap = 'round';
528
+ for (let k = 0; k < 24; k++) { const q0 = k / 24, q1 = (k + 1) / 24, X = q => lerp(1606, 1866, q), Y = q => 1046 - Math.sin(q * Math.PI) * 4 - q * 7; c.lineWidth = 3.4 * (1 - q0 * 0.6); c.beginPath(); c.moveTo(X(q0), Y(q0)); c.lineTo(X(q1), Y(q1)); c.stroke(); }
529
+ c.restore();
530
+ }
531
+
532
+ return {
533
+ draw(c, lt, t) {
534
+ const f = FR(t);
535
+ c.drawImage(P.cached('fl_bg', W, H, background), 0, 0);
536
+ sky(c, t); decos(c, t);
537
+ // 环境二次动作:吊灯 686–737 / 830–884,植物 697–741 晃 / 821–889 抖
538
+ const lampA = damped(f, 686, 0.055, 26, 22) + damped(f, 830, 0.045, 22, 20) + Math.sin(t * 1.3) * 0.004;
539
+ const plantA = damped(f, 697, 0.035, 22, 18) + damped(f, 821, 0.05, 9, 26) + Math.sin(t * 1.7) * 0.006;
540
+ snakePlant(c, -plantA * 0.8); monstera(c, plantA, f);
541
+ lamp(c, lampA);
542
+ // 地上的碎片在桌子前
543
+ smash(c, f);
544
+ // 角色层(之后整体加颗粒)
545
+ const L = P.scratch('fl_chars'), g = L.getContext('2d'); g.clearRect(0, 0, W, H);
546
+ const cs = cupState(f);
547
+ if (cs && cs.on) { drawMug(g, cs.x, cs.y, cs.rot); }
548
+ girl(g, f, t, 'back');
549
+ laptop(g, f, t);
550
+ const hd = girl(g, f, t, 'body');
551
+ const st = catState(f, t);
552
+ if (f < 690) girl(g, f, t, 'front');
553
+ drawCat(g, st, f, t, cupX);
554
+ if (f >= 690) girl(g, f, t, 'front');
555
+ if (cs && !cs.on) { drips(g, f); drawMug(g, cs.x, cs.y, cs.rot); }
556
+ g.save(); g.globalCompositeOperation = 'source-atop'; g.globalAlpha = 0.9; g.drawImage(grainTex(), 0, 0); g.restore();
557
+ c.drawImage(L, 0, 0);
558
+ dust(c, st.dust);
559
+ if (cs && cs.on) steam(c, t, cs.x - 2, cs.y - 42, 1 - seg(f, 786, 796));
560
+ motionMarks(c, f);
561
+ tension(c, f, hd.headTop);
562
+ signature(c, f);
563
+ },
564
+ };
565
+ })();