@biunow/agent-biuart 0.0.0-stage → 0.1.1

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,463 @@
1
+ // 长卷穿越片 · 共用库 XING(《花叔穿越名画》用的,随 demos/long_scroll 收进 skill)。
2
+ // 角色=AI 生的画风帧(gpt-image 按画风重画→绿幕抠图→切帧,见 references/10-角色.md),代码只管:在哪、多大、何时换帧、加什么材质。
3
+ // 场景、运镜、石子、涟漪、睡莲、青蛙、转场全部代码画。
4
+ // XING.sprites(keys) 同步读 demos/long_scroll/frames/<key>/meta.json,返回资源列表(给 era.assets)
5
+ // XING.actor(c, key, i, o) 画第 i 帧:o={x,y(脚底),h(站立身高),mat:'monet'|'ukiyoe'|'vangogh'|'none',wx,t,flip,alpha}
6
+ // XING.hatTop(key,i,o) 帽顶在屏幕上的位置(青蛙落点)
7
+ // XING.strokes(g, src, opt) 任意尺寸画布的笔触重画,每格独立种子(位置永不重洗)
8
+ // XING.monetWorld / ukiyoeWorld / vgWorld 三个世界:静态部分一次画好缓存(世界坐标),动态部分每帧画
9
+ // XING.stone(t, plan) 打水漂:石子位置+各次触水时刻
10
+ // XING.frog(c, x, y, s, pose, style, o)
11
+ (() => {
12
+ const W = 1920, H = 1080, TAU = Math.PI * 2;
13
+ const { clamp, lerp, ss, rng } = U;
14
+ const P = window.PAINT;
15
+ const X = window.XING = {};
16
+ const hx = P.hex;
17
+ const cellRng = (i, j, seed) => rng((Math.floor(U.hash(i * 131 + seed * 7919, j * 31 + seed) * 4294967296)) >>> 0);
18
+ const lum = c => c[0] * 0.3 + c[1] * 0.59 + c[2] * 0.11;
19
+ const pick = (arr, r) => arr[(r() * arr.length) | 0];
20
+
21
+ // ================= 笔触重画(任意尺寸,每格独立种子) =================
22
+ X.strokes = (g, src, { cell = 10, len = 24, width = 6, angle, palette, seed = 1, mask, alphaMask = false, alphaMin = 180, outline = 0, outlineCol = [20, 25, 60], outlineMix = 0.55, jitterCol = 14, skip, cap = 'round', alpha = 1 } = {}) => {
23
+ const w = src.width, h = src.height;
24
+ const sd = src.getContext('2d', { willReadFrequently: true }).getImageData(0, 0, w, h).data;
25
+ g.save(); g.lineCap = cap;
26
+ for (let j = 0; j * cell < h + cell; j++) for (let i = 0; i * cell < w + cell; i++) {
27
+ const r = cellRng(i, j, seed);
28
+ const px = i * cell + (r() - 0.5) * cell, py = j * cell + (r() - 0.5) * cell;
29
+ const ix = clamp(px | 0, 0, w - 1), iy = clamp(py | 0, 0, h - 1), k = (iy * w + ix) * 4;
30
+ if (alphaMask && sd[k + 3] < alphaMin) continue;
31
+ if (mask && !mask(px, py)) continue;
32
+ let col = [sd[k], sd[k + 1], sd[k + 2]];
33
+ if (skip && skip(col, px, py, r)) continue;
34
+ const pc = palette ? palette(px, py, col, r) : null;
35
+ col = pc || P.jitter(col, r, jitterCol);
36
+ const a = angle ? angle(px, py, r) : 0, l = len * (0.7 + r() * 0.6);
37
+ const dx = Math.cos(a) * l / 2, dy = Math.sin(a) * l / 2;
38
+ if (outline > 0) { g.strokeStyle = P.rgb(P.mix(col, outlineCol, outlineMix), outline * alpha); g.lineWidth = width + 2.5; g.beginPath(); g.moveTo(px - dx, py - dy); g.lineTo(px + dx, py + dy); g.stroke(); }
39
+ g.strokeStyle = P.rgb(col, alpha); g.lineWidth = width * (0.8 + r() * 0.4);
40
+ g.beginPath(); g.moveTo(px - dx, py - dy); g.lineTo(px + dx, py + dy); g.stroke();
41
+ }
42
+ g.restore();
43
+ };
44
+
45
+ // ================= 角色帧 =================
46
+ X.SPR = {};
47
+ const syncJSON = url => { const x = new XMLHttpRequest(); x.open('GET', url, false); x.send(); if (x.status !== 200) throw new Error(url + ' ' + x.status); return JSON.parse(x.responseText); };
48
+ X.sprites = keys => {
49
+ const list = [];
50
+ for (const k of keys) {
51
+ if (!X.SPR[k]) { const meta = syncJSON(`demos/long_scroll/frames/${k}/meta.json`); X.SPR[k] = { key: k, meta, imgs: null, mats: {} }; }
52
+ X.SPR[k].meta.frames.forEach(f => list.push(`demos/long_scroll/frames/${k}/${f.file}`));
53
+ }
54
+ return list;
55
+ };
56
+ function ensure(key) {
57
+ const S = X.SPR[key]; if (S.imgs) return S;
58
+ S.imgs = S.meta.frames.map(f => window.IMG[`demos/long_scroll/frames/${key}/${f.file}`]);
59
+ // 帽顶:最上面一行不透明像素的中心
60
+ S.hat = S.imgs.map(im => { const c = P.canvas(im.width, im.height), g = c.getContext('2d', { willReadFrequently: true }); g.drawImage(im, 0, 0); const d = g.getImageData(0, 0, im.width, im.height).data;
61
+ for (let y = 0; y < im.height; y++) { let x0 = -1, x1 = -1; for (let x = 0; x < im.width; x++) if (d[(y * im.width + x) * 4 + 3] > 140) { if (x0 < 0) x0 = x; x1 = x; } if (x0 >= 0 && x1 - x0 > 6) return [(x0 + x1) / 2, y]; } return [im.width / 2, 0]; });
62
+ return S;
63
+ }
64
+ // 材质:把 AI 帧再过一遍该画风的笔触,让他和场景用同一种「笔」(不碰深色=眼镜、眼睛、头发不被糊掉)
65
+ const MAT = {
66
+ monet: { pad: 8, cell: 6, len: 12, width: 4.2, seed: 11, outline: 0, angle: (x, y, r) => -0.55 + (r() - 0.5) * 0.9,
67
+ skip: (col, x, y, r) => lum(col) < 95 || r() < 0.35,
68
+ palette: (x, y, col, r) => { const L = lum(col);
69
+ if (L > 205) return P.mix(hx(pick(['#fbf6ea', '#efe6f6', '#e0e2f6', '#fff0d4', '#e6eef8', '#f6e4ee'], r)), col, 0.45);
70
+ if (col[0] > 190 && col[0] > col[2] + 40 && col[1] > 120) return P.mix(hx(pick(['#f6c6a6', '#f0b4a0', '#fad6bc', '#e8a890', '#f8d0b0'], r)), col, 0.45);
71
+ if (col[0] > 150 && col[1] > 140 && col[2] < col[1]) return P.mix(hx(pick(['#d8c8a0', '#c8b48a', '#e2d4ae', '#b8aaa8', '#d0c0b8'], r)), col, 0.45);
72
+ return null; } },
73
+ vangogh: { pad: 8, cell: 7, len: 17, width: 5, seed: 13, outline: 0.45, outlineCol: [20, 26, 80],
74
+ angle: (x, y, r) => Math.PI / 2 + Math.sin(y * 0.05 + x * 0.02) * 0.7 + (r() - 0.5) * 0.4,
75
+ skip: (col, x, y, r) => lum(col) < 90 || r() < 0.25,
76
+ palette: (x, y, col, r) => { const L = lum(col);
77
+ if (L > 200) return P.mix(hx(pick(['#f8f0b0', '#e8f0f8', '#c8d8f0', '#fff6c8', '#d8e4f4'], r)), col, 0.35);
78
+ if (col[0] > 190 && col[0] > col[2] + 40) return P.mix(hx(pick(['#f0b060', '#e8a050', '#f8c878', '#e89060'], r)), col, 0.4);
79
+ return null; } },
80
+ };
81
+ function material(S, i, mat) {
82
+ const key = mat; S.mats[key] = S.mats[key] || [];
83
+ if (S.mats[key][i]) return S.mats[key][i];
84
+ const im = S.imgs[i], M = MAT[mat], pad = M ? M.pad : 0;
85
+ const out = P.canvas(im.width + 2 * pad, im.height + 2 * pad), g = out.getContext('2d');
86
+ g.drawImage(im, pad, pad);
87
+ if (M) { const base = P.canvas(out.width, out.height), bgx = base.getContext('2d', { willReadFrequently: true }); bgx.drawImage(im, pad, pad);
88
+ // 五官保护:附近 7px 内有深色像素(眼镜、眼睛、嘴、发际)就不下笔,免得把脸糊掉
89
+ const bw = base.width, bh = base.height, bd = bgx.getImageData(0, 0, bw, bh).data, dk = new Uint8Array(bw * bh);
90
+ for (let k = 0; k < bw * bh; k++) dk[k] = bd[k * 4 + 3] > 150 && (bd[k * 4] * 0.3 + bd[k * 4 + 1] * 0.59 + bd[k * 4 + 2] * 0.11) < 110 ? 1 : 0;
91
+ const nearDark = (x, y) => { for (let dy = -7; dy <= 7; dy += 3.5) for (let dx = -7; dx <= 7; dx += 3.5) { const xx = (x + dx) | 0, yy = (y + dy) | 0; if (xx >= 0 && yy >= 0 && xx < bw && yy < bh && dk[yy * bw + xx]) return true; } return false; };
92
+ X.strokes(g, base, { cell: M.cell, len: M.len, width: M.width, seed: M.seed + i, angle: M.angle, palette: M.palette, skip: (col, x, y, r) => M.skip(col, x, y, r) || nearDark(x, y), alphaMask: true, alphaMin: 170, outline: M.outline, outlineCol: M.outlineCol, jitterCol: 12 }); }
93
+ if (mat === 'ukiyoe') { // 木纹+纸纤维压进色块(multiply,只在人身上)
94
+ { const d = g.getImageData(0, 0, out.width, out.height), q = d.data; // 生图把白帽白T画成米黄:低饱和的亮色拉回纸白(帽子定规是纯白)
95
+ for (let k = 0; k < q.length; k += 4) { const mx = Math.max(q[k], q[k + 1], q[k + 2]), mn = Math.min(q[k], q[k + 1], q[k + 2]); if (q[k + 3] > 0 && mx > 185 && mx - mn < 48) { const f = (mx - 185) / 70; q[k] = lerp(q[k], 251, f); q[k + 1] = lerp(q[k + 1], 249, f); q[k + 2] = lerp(q[k + 2], 242, f); } }
96
+ g.putImageData(d, 0, 0); }
97
+ const tex = P.canvas(out.width, out.height), tg = tex.getContext('2d'), r = rng(77 + i);
98
+ tg.fillStyle = '#fff'; tg.fillRect(0, 0, tex.width, tex.height);
99
+ for (let k = 0; k < 160; k++) { const y = r() * tex.height; tg.strokeStyle = `rgba(150,110,60,${0.05 + r() * 0.08})`; tg.lineWidth = 1 + r() * 1.5; tg.beginPath(); tg.moveTo(0, y); tg.bezierCurveTo(tex.width * 0.3, y + (r() - 0.5) * 8, tex.width * 0.7, y + (r() - 0.5) * 8, tex.width, y + (r() - 0.5) * 6); tg.stroke(); }
100
+ tg.globalCompositeOperation = 'destination-in'; tg.drawImage(out, 0, 0);
101
+ g.globalCompositeOperation = 'multiply'; g.drawImage(tex, 0, 0); g.globalCompositeOperation = 'source-over';
102
+ }
103
+ out.pad = pad; S.mats[key][i] = out; return out;
104
+ }
105
+ // 画一帧。x,y = 脚底锚点(屏幕),h = 站立身高(按 meta.ref_h 换算,整套帧同一缩放)
106
+ X.actor = (c, key, i, o) => {
107
+ const S = ensure(key), f = S.meta.frames[i], mat = o.mat || 'none';
108
+ const im = material(S, i, mat), pad = im.pad || 0, s = o.h / S.meta.ref_h * (o.scale || 1);
109
+ const fx = o.flip ? -1 : 1;
110
+ const dw = im.width * s, dh = im.height * s, x0 = o.x - (f.ax + pad) * s * fx, y0 = o.y - (f.ay + pad) * s;
111
+ // 光影斑驳(莫奈):暖黄光斑与淡紫阴影按世界坐标落在他身上,他走过时光从身上滑过
112
+ const S2 = P.scratch('actorTmp'), g = S2.getContext('2d');
113
+ const bw = Math.ceil(dw) + 4, bh = Math.ceil(dh) + 4;
114
+ g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, Math.min(W, bw + 4), Math.min(H, bh + 4));
115
+ g.save(); if (o.flip) { g.translate(bw - 2, 2); g.scale(-1, 1); } else g.translate(2, 2); g.drawImage(im, 0, 0, dw, dh); g.restore();
116
+ if (mat === 'monet' && o.dapple !== false) {
117
+ g.save(); g.globalCompositeOperation = 'source-atop';
118
+ const wx0 = (o.flip ? x0 - dw : x0) + ((o.wx ?? o.x) - o.x), t = o.t || 0; // 精灵左上角的世界 x
119
+ const cs = 70;
120
+ for (let j = -1; j * cs < bh + cs; j++) for (let ii = Math.floor(wx0 / cs) - 1; ii * cs < wx0 + bw + cs; ii++) {
121
+ const hh = U.hash(ii, j + 40), hv = U.hash(ii + 7, j); if (hh < 0.45) continue;
122
+ const cx = ii * cs + hv * cs + Math.sin(t * 0.9 + ii) * 10 - wx0, cy = j * cs + U.hash(ii, j + 3) * cs + Math.cos(t * 0.7 + j) * 6, rr = 18 + hh * 26;
123
+ const warm = U.hash(ii + 3, j + 9) > 0.35, rg = g.createRadialGradient(cx, cy, 0, cx, cy, rr);
124
+ rg.addColorStop(0, warm ? 'rgba(255,238,170,0.42)' : 'rgba(110,96,190,0.26)'); rg.addColorStop(1, warm ? 'rgba(255,238,170,0)' : 'rgba(110,96,190,0)');
125
+ g.fillStyle = rg; g.fillRect(cx - rr, cy - rr, rr * 2, rr * 2);
126
+ }
127
+ const sg = g.createLinearGradient(0, bh * 0.45, 0, bh); sg.addColorStop(0, 'rgba(120,110,200,0)'); sg.addColorStop(1, 'rgba(120,110,200,0.22)'); g.fillStyle = sg; g.fillRect(0, 0, bw, bh);
128
+ g.restore();
129
+ }
130
+ c.save(); if (o.alpha != null) c.globalAlpha *= o.alpha;
131
+ if (o.shadow) { c.fillStyle = o.shadow; c.beginPath(); c.ellipse(o.x, o.y + 2, o.h * 0.2, o.h * 0.035, 0, 0, TAU); c.fill(); }
132
+ c.drawImage(S2, 0, 0, bw, bh, (o.flip ? x0 - dw : x0) - 2, y0 - 2, bw, bh);
133
+ c.restore();
134
+ };
135
+ X.hatTop = (key, i, o) => { const S = ensure(key), f = S.meta.frames[i], s = o.h / S.meta.ref_h * (o.scale || 1), [hxp, hyp] = S.hat[i], fx = o.flip ? -1 : 1;
136
+ return [o.x + (hxp - f.ax) * s * fx, o.y + (hyp - f.ay) * s]; };
137
+ X.nFrames = key => X.SPR[key].meta.frames.length;
138
+
139
+ // ================= 莫奈:睡莲池+日本桥 =================
140
+ // o: { key, x0, w, waterTop, bridge:{x0,x1,yEnd,lift}, pond:[px0,px1]|null, groundY, willows:[x...] }
141
+ const SW = {
142
+ water: P.swatch(['#5b6fb5', '#7d8fd0', '#9db5e0', '#6aa0a8', '#b7a6d8', '#d9c8e8', '#8fb8a8', '#a6c4e8', '#c8b0d8'], 0.45),
143
+ deep: P.swatch(['#2e5048', '#3f6a58', '#4a5f8a', '#355a4a', '#5a6a9a', '#2a4a5a', '#6a8a5a'], 0.45),
144
+ fol: P.swatch(['#4f7f3a', '#6f9a40', '#2f5a30', '#9cbf58', '#c0d070', '#3a6a5a', '#7aa86a', '#5a8a9a', '#d8d890', '#4a5a8a'], 0.6),
145
+ grass: P.swatch(['#5f8f4a', '#7fae5a', '#3f6f4a', '#9cc070', '#b4cc78', '#6a9a8a'], 0.35),
146
+ path: P.swatch(['#d8b890', '#c89a78', '#e8c8a0', '#b88a8a', '#a89ab8', '#e0c0a8'], 0.35),
147
+ };
148
+ X.monetWorld = (o) => {
149
+ const key = 'mw_' + o.key; if (X[key]) return X[key];
150
+ const w = o.w, x0 = o.x0, B = o.bridge, mid = (B.x0 + B.x1) / 2, half = (B.x1 - B.x0) / 2;
151
+ const deckY = x => { const q = clamp((x - mid) / half, -1, 1); return B.yEnd - B.lift * (1 - q * q); };
152
+ const inPond = wx => !o.pond || (wx > o.pond[0] && wx < o.pond[1]);
153
+ const pondEdge = (wx, y) => { if (!o.pond) return true; const sl = (y - o.groundY) * 0.25; return wx > o.pond[0] - sl && wx < o.pond[1] + sl; };
154
+ const WT = o.waterTop, G = o.groundY;
155
+ const isWater = (wx, y) => y > WT && (y < G - 10 ? inPond(wx) || true : pondEdge(wx, y)) && !(y < G + 30 && !inPond(wx) && y > G - 40);
156
+ // ---- 底稿 ----
157
+ const base = P.canvas(w, H), g = base.getContext('2d');
158
+ g.fillStyle = '#2f4a36'; g.fillRect(0, 0, w, H);
159
+ const r = rng(301);
160
+ // 树荫:低频噪声分出深浅大块(深蓝绿的柳荫 / 中绿 / 黄绿受光),再撒垂柳条
161
+ for (let i = 0; i < w * 0.12; i++) { const x = r() * w, y = r() * (G + 20), n = P.fbm((x + x0) * 0.0035, y * 0.005);
162
+ const pal = n < -0.12 ? ['#1f3a2e', '#2a4a4a', '#34507a', '#2e5a40'] : n < 0.12 ? ['#3f6a3a', '#5a8a3e', '#4a7a6a', '#6a8a9a', '#5f8a4a'] : ['#9cc060', '#b8cc68', '#d8d890', '#c0d070', '#e8e0a0'];
163
+ g.fillStyle = pick(pal, r); g.globalAlpha = 0.85; g.beginPath(); g.ellipse(x, y, 30 + r() * 80, 24 + r() * 50, r() * 3, 0, TAU); g.fill(); }
164
+ for (let i = 0; i < w * 0.01; i++) { const x = r() * w, y = r() * 200; g.fillStyle = pick(['#cfd8e8', '#e6e0f0', '#d8e8d0', '#f0e8d0'], r); g.globalAlpha = 0.75; g.beginPath(); g.ellipse(x, y, 16 + r() * 34, 12 + r() * 18, 0, 0, TAU); g.fill(); }
165
+ g.globalAlpha = 1;
166
+ for (let i = 0; i < w * 0.04; i++) { const x = r() * w, L = 180 + r() * 360, n = P.fbm((x + x0) * 0.0035, 0.5); g.strokeStyle = pick(n < 0 ? ['#3a6a4a', '#4a7a3a', '#2e5a48'] : ['#8ab050', '#a8c868', '#c8d878'], r); g.lineWidth = 5 + r() * 8; g.beginPath(); g.moveTo(x, -10); g.quadraticCurveTo(x + (r() - 0.5) * 30, L / 2, x + (r() - 0.5) * 40, L); g.stroke(); }
167
+ // 水面
168
+ const wg = g.createLinearGradient(0, WT, 0, H); wg.addColorStop(0, '#34503f'); wg.addColorStop(0.18, '#456a7a'); wg.addColorStop(0.5, '#7590c0'); wg.addColorStop(1, '#a9b6dc');
169
+ g.save(); g.beginPath();
170
+ if (o.pond) { g.moveTo(o.pond[0] - x0, WT); g.lineTo(o.pond[1] - x0, WT); g.lineTo(o.pond[1] + (H - G) * 0.25 - x0, H); g.lineTo(o.pond[0] - (H - G) * 0.25 - x0, H); g.closePath(); }
171
+ else g.rect(0, WT, w, H - WT);
172
+ g.fillStyle = wg; g.fill(); g.clip();
173
+ for (let i = 0; i < w * 0.03; i++) { const x = r() * w, y = WT + r() * 260, L = 60 + r() * 200; g.fillStyle = pick(['#2e5048', '#3f6a58', '#4a6a3a', '#5a7a4a'], r); g.globalAlpha = 0.6; g.fillRect(x, y, 10 + r() * 20, L); }
174
+ for (let i = 0; i < w * 0.012; i++) { const x = r() * w, y = WT + 250 + r() * (H - WT - 250); g.fillStyle = pick(['#e8d6e8', '#f2e2c4', '#d0dcf0'], r); g.globalAlpha = 0.55; g.beginPath(); g.ellipse(x, y, 60 + r() * 120, 12 + r() * 16, 0, 0, TAU); g.fill(); }
175
+ g.restore(); g.globalAlpha = 1;
176
+ // 岸(有池边界时):小路+草+鸢尾
177
+ if (o.pond) {
178
+ g.save(); g.beginPath(); g.rect(0, G - 40, w, H); g.moveTo(o.pond[0] - x0, G - 40); g.lineTo(o.pond[1] - x0, G - 40); g.lineTo(o.pond[1] + (H - G + 40) * 0.25 - x0, H); g.lineTo(o.pond[0] - (H - G + 40) * 0.25 - x0, H); g.closePath(); g.clip('evenodd');
179
+ g.fillStyle = '#5f8f4a'; g.fillRect(0, G - 40, w, H);
180
+ g.fillStyle = '#d8b890'; g.fillRect(0, G - 8, w, 46);
181
+ for (let i = 0; i < w * 0.03; i++) { const x = r() * w, y = G + 60 + r() * (H - G - 60); g.strokeStyle = '#4f7f3a'; g.lineWidth = 5; for (let k = 0; k < 5; k++) { g.beginPath(); g.moveTo(x, y); g.lineTo(x + (k - 2) * 9 + (r() - 0.5) * 8, y - 50 - r() * 40); g.stroke(); } g.fillStyle = pick(['#7a5ab8', '#9a7ad0', '#f0e080', '#e890b0'], r); g.beginPath(); g.ellipse(x + (r() - 0.5) * 20, y - 70 - r() * 20, 12, 15, 0, 0, TAU); g.fill(); }
182
+ g.restore();
183
+ }
184
+ // ---- 笔触 ----
185
+ const bg = P.canvas(w, H), bgc = bg.getContext('2d'); bgc.drawImage(base, 0, 0);
186
+ const regionOf = (lx, y) => { const wx = lx + x0;
187
+ if (y > WT) { if (!o.pond) return 'water'; const sl = (y - G + 40) * 0.25; if (y > G - 40 && (wx < o.pond[0] - Math.max(0, sl) || wx > o.pond[1] + Math.max(0, sl))) return (y < G + 38 && y > G - 8) ? 'path' : 'grass'; return 'water'; }
188
+ if (o.pond && !inPond(wx) && y > G - 40) return (y > G - 8) ? 'path' : 'grass';
189
+ return 'fol'; };
190
+ X.strokes(bgc, base, { cell: 9, len: 30, width: 7, seed: 21, jitterCol: 12,
191
+ angle: (lx, y, rr) => { const reg = regionOf(lx, y); if (reg === 'water') return 0.05 * Math.sin(y * 0.05 + lx * 0.004) + (rr() - 0.5) * 0.08; if (reg === 'path') return (rr() - 0.5) * 0.2; if (reg === 'grass') return -1.35 + (rr() - 0.5) * 0.6; return (y < WT - 120 ? -1.5 + 0.35 * P.noise((lx + x0) * 0.01, y * 0.01) : -0.9 + 0.8 * P.noise((lx + x0) * 0.01, y * 0.01)) + (rr() - 0.5) * 0.4; },
192
+ palette: (lx, y, col, rr) => { const reg = regionOf(lx, y); if (reg === 'water') return y < WT + 120 + rr() * 200 ? SW.deep(col, rr) : SW.water(col, rr); return SW[reg](col, rr); } });
193
+ // ---- 桥(远侧栏杆+桥面),近侧栏杆单独一层(画在人前面) ----
194
+ const mkBridge = (near) => { const c = P.canvas(w, H), q = c.getContext('2d'); q.lineCap = 'round'; q.lineJoin = 'round';
195
+ const line = (dy, lw, col) => { q.strokeStyle = col; q.lineWidth = lw; q.beginPath(); for (let x = B.x0; x <= B.x1; x += 6) { const y = deckY(x) + dy; x === B.x0 ? q.moveTo(x - x0, y) : q.lineTo(x - x0, y); } q.stroke(); };
196
+ if (!near) {
197
+ // 桥身(拱下的厚度)+桥面
198
+ q.fillStyle = '#2f6a4a'; q.beginPath(); for (let x = B.x0; x <= B.x1; x += 6) q.lineTo(x - x0, deckY(x) - 4); for (let x = B.x1; x >= B.x0; x -= 6) q.lineTo(x - x0, deckY(x) + 30); q.closePath(); q.fill();
199
+ line(-2, 10, '#8fdcae');
200
+ line(-16 - 96, 8, '#4f9a72'); line(-16 - 52, 6, '#5fae82');
201
+ q.strokeStyle = '#4f9a72'; q.lineWidth = 6; for (let x = B.x0 + 20; x < B.x1; x += 58) { q.beginPath(); q.moveTo(x - x0, deckY(x) - 10); q.lineTo(x - x0, deckY(x) - 16 - 96); q.stroke(); }
202
+ // 桥墩
203
+ [B.x0 + 30, B.x1 - 30].forEach(x => { q.fillStyle = '#2a5a3e'; q.fillRect(x - x0 - 10, deckY(x) + 20, 20, 120); });
204
+ } else {
205
+ line(-80, 9, '#7fcf9a'); line(-38, 6, '#6fbf8a');
206
+ q.strokeStyle = '#6fbf8a'; q.lineWidth = 6; for (let x = B.x0 + 44; x < B.x1; x += 64) { q.beginPath(); q.moveTo(x - x0, deckY(x) + 22); q.lineTo(x - x0, deckY(x) - 80); q.stroke(); }
207
+ }
208
+ const out = P.canvas(w, H), og = out.getContext('2d'); og.drawImage(c, 0, 0);
209
+ og.globalCompositeOperation = 'source-atop';
210
+ X.strokes(og, c, { cell: 5, len: 11, width: 4, seed: near ? 31 : 32, alphaMask: true, alphaMin: 120, angle: (x, y, rr) => 0.1 + (rr() - 0.5) * 0.5,
211
+ palette: (x, y, col, rr) => P.mix(hx(pick(['#8fdcae', '#a8e8c0', '#6fc49a', '#c0f0d0', '#4f9a8a', '#7fb8d8', '#3f7a5a'], rr)), col, 0.35) });
212
+ return out; };
213
+ const bridge = mkBridge(false), nearRail = mkBridge(true);
214
+ // ---- 睡莲(每片预画成小图,每帧按涟漪起伏) ----
215
+ const pads = [], rp = rng(505);
216
+ const sc = y => 0.22 + 0.78 * clamp((y - WT) / (H - WT));
217
+ for (let y = WT + 26; y < H + 20;) {
218
+ const s = sc(y); let x = x0 + (rp() - 0.5) * 200;
219
+ while (x < x0 + w + 100) {
220
+ const clusterN = 2 + ((rp() * 5) | 0);
221
+ for (let k = 0; k < clusterN; k++) {
222
+ const wx = x + (rp() - 0.3) * 60 * s, wy = y + (rp() - 0.5) * 14 * s;
223
+ if (!pondEdge(wx, wy) || (o.pond && wy > G - 30 && !inPond(wx))) continue;
224
+ const rx = (22 + rp() * 26) * s * 1.7, ry = rx * (0.24 + 0.1 * s), flower = rp() < 0.3 ? (rp() < 0.6 ? 'pink' : 'white') : null;
225
+ pads.push({ x: wx, y: wy, rx, ry, flower, notch: rp() * TAU, ph: rp() * TAU, img: null, s });
226
+ x += rx * 1.6;
227
+ }
228
+ x += (60 + rp() * 260) * s * 1.6;
229
+ }
230
+ y += 18 + 44 * s;
231
+ }
232
+ pads.forEach((p, i) => { const pw = Math.ceil(p.rx * 2 + 16), ph = Math.ceil(p.ry * 2 + 34 * p.s + 16); const cc = P.canvas(pw, ph), q = cc.getContext('2d'), cx = pw / 2, cy = ph - p.ry - 8;
233
+ q.fillStyle = pick(['#5f8f4a', '#7aa85a', '#6a9a4a', '#8ab860'], rng(i + 9)); q.beginPath(); q.ellipse(cx, cy, p.rx, p.ry, 0, p.notch + 0.35, p.notch + TAU - 0.35); q.lineTo(cx, cy); q.closePath(); q.fill();
234
+ q.fillStyle = 'rgba(40,70,60,0.35)'; q.beginPath(); q.ellipse(cx, cy + p.ry * 0.25, p.rx * 0.95, p.ry * 0.7, 0, 0, Math.PI); q.fill();
235
+ if (p.flower) { const fr = 11 * p.s * 1.6 + 3; q.fillStyle = p.flower === 'pink' ? '#f2a6bf' : '#fbf4f0'; q.beginPath(); q.ellipse(cx + p.rx * 0.15, cy - fr * 0.5, fr * 1.4, fr * 0.8, 0, 0, TAU); q.fill(); q.fillStyle = p.flower === 'pink' ? '#fff0f2' : '#f6d86a'; q.beginPath(); q.ellipse(cx + p.rx * 0.15, cy - fr * 0.8, fr * 0.6, fr * 0.45, 0, 0, TAU); q.fill(); }
236
+ const out = P.canvas(pw, ph), og = out.getContext('2d'); og.drawImage(cc, 0, 0); og.globalCompositeOperation = 'source-atop';
237
+ X.strokes(og, cc, { cell: Math.max(3, 5 * p.s + 1), len: 10 * p.s + 5, width: 3 * p.s + 1.6, seed: 600 + i, alphaMask: true, alphaMin: 100, angle: (x, y, rr) => (rr() - 0.5) * 0.4,
238
+ palette: (x, y, col, rr) => col[1] > col[0] + 10 ? P.mix(hx(pick(['#5f8f4a', '#7fae5a', '#9cc070', '#b4cc78', '#4a7a5a', '#c8d880'], rr)), col, 0.3) : (lum(col) > 170 ? P.mix(hx(pick(['#fff2f4', '#f7d0dc', '#fbf4e8', '#f0a0b8'], rr)), col, 0.4) : null) });
239
+ p.img = out; p.ax = cx; p.ay = cy; });
240
+ // 水面光斑(世界坐标)
241
+ const glints = []; const rg = rng(909);
242
+ for (let i = 0; i < w * 0.16; i++) { const wx = x0 + rg() * w, y = WT + 20 + rg() * (H - WT - 20); if (!pondEdge(wx, y)) continue; glints.push({ x: wx, y, f: 5 + rg() * 6, ph: rg() * TAU, l: (14 + rg() * 26) * (0.4 + sc(y)), col: pick(['#fffbe8', '#fdf0d0', '#f8e0ec', '#e8f4ff'], rg), w: (3 + rg() * 3) * (0.5 + sc(y) * 0.7) }); }
243
+ const M = X[key] = { o, base, bg, bridge, nearRail, pads, glints, deckY, sc, x0, w, WT, G, pondEdge, inPond };
244
+ return M;
245
+ };
246
+ // 每帧:底 → 睡莲(按涟漪起伏)→ 光斑 → 涟漪 → 桥(远栏杆) ;人画完后再 X.monetFront(近栏杆+前景柳条)
247
+ X.monetBack = (c, M, camX, t, ev = {}) => {
248
+ c.drawImage(M.bg, M.x0 - camX, 0);
249
+ const hops = ev.hops || [];
250
+ // 睡莲
251
+ for (const p of M.pads) { const sx = p.x - camX; if (sx < -200 || sx > W + 200) continue;
252
+ let dy = Math.sin(t * 1.1 + p.ph) * 1.5 * p.s, rot = 0;
253
+ for (const h of hops) { const a = t - h.t; if (a < 0 || a > 2.2) continue; const d = Math.hypot(p.x - h.x, (p.y - h.y) * 3.2), R = 40 + a * 260 * h.s; const near = Math.exp(-Math.pow((d - R) / (60 * h.s + 20), 2)) * Math.exp(-a * 1.6);
254
+ dy += Math.sin(a * 16) * 7 * h.s * near; rot += Math.sin(a * 13 + 1) * 0.06 * near; }
255
+ if (ev.padBump && ev.padBump.pad === p) { const a = t - ev.padBump.t; if (a > 0 && a < 1.2) dy += Math.sin(a * 18) * 16 * Math.exp(-a * 3) * p.s + (a < 0.12 ? 10 * p.s : 0); }
256
+ c.save(); c.translate(sx, p.y + dy); c.rotate(rot); c.drawImage(p.img, -p.ax, -p.ay); c.restore(); }
257
+ // 光斑颤动(位置固定,亮度不规则明灭,10fps 横向小跳)
258
+ const st = Math.floor(t * 10); c.save(); c.lineCap = 'round';
259
+ for (let i = 0; i < M.glints.length; i++) { const gl = M.glints[i], sx = gl.x - camX; if (sx < -60 || sx > W + 60) continue;
260
+ const a = clamp(Math.sin(t * gl.f + gl.ph) * Math.sin(t * 2.3 + i), 0, 1); if (a < 0.12) continue;
261
+ const jx = (U.hash(i, st) - 0.5) * 8; c.strokeStyle = gl.col; c.globalAlpha = 0.85 * a; c.lineWidth = gl.w; c.beginPath(); c.moveTo(sx + jx, gl.y); c.lineTo(sx + jx + gl.l, gl.y + 1); c.stroke(); }
262
+ c.restore();
263
+ // 涟漪:断续的亮色短笔围成透视椭圆,一圈圈外扩
264
+ for (const h of hops) X.ripple(c, h.x - camX, h.y, t - h.t, h.s, h.big);
265
+ c.drawImage(M.bridge, M.x0 - camX, 0);
266
+ };
267
+ X.ripple = (c, x, y, a, s, big) => {
268
+ if (a < 0 || a > 2.0) return;
269
+ c.save(); c.lineCap = 'round';
270
+ for (let k = 0; k < (big ? 4 : 3); k++) { const ak = a - k * 0.16; if (ak <= 0) continue; const R = (22 + ak * (big ? 380 : 300)) * s, al = clamp(1 - ak / 1.6) * (1 - k * 0.15); if (al <= 0) continue;
271
+ const n = Math.max(10, Math.round(R / 7)); for (let i = 0; i < n; i++) { if (U.hash(i, k * 17 + 3) < 0.3) continue; const th = i / n * TAU + k * 0.4, th2 = th + TAU / n * 0.55;
272
+ c.strokeStyle = ['#ffffff', '#fffbe8', '#eef0ff'][(i + k) % 3]; c.globalAlpha = al; c.lineWidth = (9 - k * 1.4) * (0.5 + s * 0.6);
273
+ c.beginPath(); c.ellipse(x, y, R, R * 0.27, 0, th, th2); c.stroke(); } }
274
+ // 溅起的水花(短竖笔向上甩)
275
+ if (a < 0.1) { c.globalAlpha = 1 - a / 0.1; c.fillStyle = '#ffffff'; c.beginPath(); c.ellipse(x, y, 40 * s, 12 * s, 0, 0, TAU); c.fill(); }
276
+ if (a < 0.4) { const r = rng(Math.round(x * 7 + y)); for (let i = 0; i < (big ? 22 : 14); i++) { const ang = -Math.PI / 2 + (r() - 0.5) * 1.6, sp = (90 + r() * 150) * s * (big ? 1.6 : 1), q = a / 0.4;
277
+ const px = x + Math.cos(ang) * sp * a * 2.2, py = y + Math.sin(ang) * sp * a * 2.2 + 300 * a * a * s; c.globalAlpha = 1 - q; c.strokeStyle = i % 2 ? '#ffffff' : '#dfe8ff'; c.lineWidth = 6 * (0.5 + s * 0.6); c.beginPath(); c.moveTo(px, py); c.lineTo(px - Math.cos(ang) * 8 * s, py - Math.sin(ang) * 8 * s); c.stroke(); } }
278
+ c.restore();
279
+ };
280
+ X.monetFront = (c, M, camX, t, willows = []) => {
281
+ c.drawImage(M.nearRail, M.x0 - camX, 0);
282
+ // 前景柳条:从画面顶部垂下,成片摆动
283
+ c.save(); c.lineCap = 'round';
284
+ willows.forEach((wx, k) => { const sx = wx - camX; if (sx < -200 || sx > W + 200) return; const r = rng(80 + k);
285
+ for (let f = 0; f < 9; f++) { const fx = sx + (r() - 0.5) * 150, L = 260 + r() * 300, ph = r() * TAU;
286
+ for (let i = 0; i < 26; i++) { const q = i / 25, sw = Math.sin(t * 1.6 + ph + q * 2) * 26 * q * q, px = fx + sw + Math.sin(q * 5 + f) * 6, py = -10 + L * q;
287
+ c.strokeStyle = pick(['#6f9a40', '#9cbf58', '#4f7f3a', '#c0d070', '#3a6a5a'], r); c.lineWidth = 5; c.globalAlpha = 0.9; c.beginPath(); c.moveTo(px, py); c.lineTo(px + 4 + sw * 0.05, py + 14); c.stroke(); } } });
288
+ c.restore();
289
+ };
290
+
291
+ // ================= 打水漂 =================
292
+ // plan: { t0, from:[x,y], hops:[{x,y,t,s}], sinkT, sinkS }
293
+ X.stone = (t, plan) => {
294
+ if (t < plan.t0) return null;
295
+ const pts = [{ x: plan.from[0], y: plan.from[1], t: plan.t0, s: plan.s0 || 0.5 }].concat(plan.hops);
296
+ for (let i = 1; i < pts.length; i++) { const A = pts[i - 1], B = pts[i]; if (t <= B.t) { const q = (t - A.t) / (B.t - A.t);
297
+ const pk = i === 1 ? (plan.firstArc || 80) : (B.peak ?? 60) * (B.s ?? 1);
298
+ return { x: lerp(A.x, B.x, q), y: lerp(A.y, B.y, q) - 4 * pk * q * (1 - q), s: lerp(A.s, B.s, q), spin: t * 30 }; } }
299
+ return null;
300
+ };
301
+ X.drawStone = (c, st, trail) => { if (!st) return; const s = 1.2 + st.s * 1.4;
302
+ if (trail) { c.save(); c.lineCap = 'round'; for (let k = 1; k <= 6; k++) { const p = trail(k * 0.018); if (!p) break; c.strokeStyle = '#fffbea'; c.globalAlpha = 0.5 * (1 - k / 7); c.lineWidth = 9 * s * (1 - k / 8); c.beginPath(); c.moveTo(p.x, p.y); const q = trail((k - 1) * 0.018) || st; c.lineTo(q.x, q.y); c.stroke(); } c.restore(); }
303
+ c.save(); c.translate(st.x, st.y);
304
+ c.fillStyle = 'rgba(40,40,80,.3)'; c.beginPath(); c.ellipse(2, 4, 13 * s, 6 * s, 0, 0, TAU); c.fill();
305
+ c.fillStyle = '#4a4a6a'; c.beginPath(); c.ellipse(0, 0, 11 * s, 11 * s * Math.abs(Math.cos(st.spin)) * 0.5 + 3 * s, 0, 0, TAU); c.fill();
306
+ c.fillStyle = '#d8d8f0'; c.beginPath(); c.ellipse(-2 * s, -1.5 * s, 4.5 * s, 2 * s, 0, 0, TAU); c.fill(); c.restore(); };
307
+ // 带拖尾地画石子:x 方向可传相机偏移
308
+ X.stoneAt = (c, lt, plan, camX = 0) => { const sh = p => p ? { ...p, x: p.x - camX } : null; X.drawStone(c, sh(X.stone(lt, plan)), dt => sh(X.stone(lt - dt, plan))); };
309
+
310
+ // ================= 青蛙(几何小动物,代码画;按画风上材质) =================
311
+ const FROG = {};
312
+ function frogImg(pose, style) {
313
+ const k = pose + style; if (FROG[k]) return FROG[k];
314
+ const cw = 200, ch = 160, cc = P.canvas(cw, ch), q = cc.getContext('2d'); q.translate(100, 110); q.lineJoin = 'round';
315
+ const body = style === 'vangogh' ? '#3f8a3a' : '#6aa84a', dark = style === 'vangogh' ? '#1f4a2a' : '#3f6a3a', belly = '#d8e8a0';
316
+ if (pose === 'leap') {
317
+ q.fillStyle = dark; q.beginPath(); q.ellipse(-62, 20, 38, 9, 0.3, 0, TAU); q.fill(); q.beginPath(); q.ellipse(-60, 34, 36, 8, 0.15, 0, TAU); q.fill();
318
+ q.fillStyle = body; q.beginPath(); q.ellipse(0, 0, 50, 26, -0.15, 0, TAU); q.fill();
319
+ q.fillStyle = belly; q.beginPath(); q.ellipse(4, 10, 32, 12, -0.15, 0, TAU); q.fill();
320
+ q.fillStyle = dark; q.beginPath(); q.ellipse(46, 18, 20, 7, 0.6, 0, TAU); q.fill();
321
+ [[28, -22], [8, -26]].forEach(([x, y]) => { q.fillStyle = body; q.beginPath(); q.arc(x, y, 13, 0, TAU); q.fill(); q.fillStyle = '#fbf6e0'; q.beginPath(); q.arc(x + 2, y - 1, 8, 0, TAU); q.fill(); q.fillStyle = '#1a1a2a'; q.beginPath(); q.arc(x + 4, y - 1, 4.5, 0, TAU); q.fill(); });
322
+ } else {
323
+ q.fillStyle = dark; q.beginPath(); q.ellipse(-30, 18, 34, 18, -0.2, 0, TAU); q.fill(); q.beginPath(); q.ellipse(30, 18, 34, 18, 0.2, 0, TAU); q.fill();
324
+ q.fillStyle = body; q.beginPath(); q.ellipse(0, -2, 46, 34, 0, 0, TAU); q.fill();
325
+ q.fillStyle = belly; q.beginPath(); q.ellipse(0, 12, 28, 16, 0, 0, TAU); q.fill();
326
+ q.fillStyle = dark; [[-18, 30], [18, 30]].forEach(([x, y]) => { q.beginPath(); q.ellipse(x, y, 10, 6, 0, 0, TAU); q.fill(); });
327
+ [[-20, -30], [20, -30]].forEach(([x, y]) => { q.fillStyle = body; q.beginPath(); q.arc(x, y, 15, 0, TAU); q.fill(); q.fillStyle = '#fbf6e0'; q.beginPath(); q.arc(x, y - 2, 9.5, 0, TAU); q.fill(); q.fillStyle = '#1a1a2a'; q.beginPath(); q.arc(x, y - 1, 5, 0, TAU); q.fill(); });
328
+ q.strokeStyle = '#2a3a2a'; q.lineWidth = 3; q.lineCap = 'round'; q.beginPath(); q.arc(0, -4, 16, 0.25, Math.PI - 0.25); q.stroke();
329
+ }
330
+ const out = P.canvas(cw, ch), og = out.getContext('2d'); og.drawImage(cc, 0, 0); og.globalCompositeOperation = 'source-atop';
331
+ const M = MAT[style] || MAT.monet;
332
+ X.strokes(og, cc, { cell: 5, len: style === 'vangogh' ? 12 : 9, width: 3.6, seed: 71, alphaMask: true, alphaMin: 120, outline: style === 'vangogh' ? 0.4 : 0, outlineCol: [20, 30, 60],
333
+ angle: (x, y, r) => (style === 'vangogh' ? Math.atan2(y - 110, x - 100) + Math.PI / 2 : -0.4) + (r() - 0.5) * 0.5, skip: (col) => lum(col) < 60 || lum(col) > 235,
334
+ palette: (x, y, col, r) => col[1] > col[0] + 20 ? P.mix(hx(pick(style === 'vangogh' ? ['#3f8a3a', '#6aaa40', '#2a6a4a', '#c8d040'] : ['#6aa84a', '#8ac060', '#4f8a4a', '#a8d070', '#5a9a8a'], r)), col, 0.35) : null });
335
+ out.ax = 100; out.ay = 128; FROG[k] = out; return out;
336
+ }
337
+ // x,y = 脚底;s = 缩放(1 ≈ 100px 宽);o.rot 旋转,o.croak 0..1 鸣囊鼓起
338
+ X.frog = (c, x, y, s, pose, style, o = {}) => {
339
+ const im = frogImg(pose, style); c.save(); c.translate(x, y); c.rotate(o.rot || 0); c.scale(s * (o.flip ? -1 : 1), s * (o.sy || 1)); c.drawImage(im, -im.ax, -im.ay);
340
+ if (o.croak > 0) { c.fillStyle = 'rgba(240,236,190,0.92)'; c.beginPath(); c.ellipse(0, -14 + 26, 14 + 22 * o.croak, 8 + 15 * o.croak, 0, 0, TAU); c.fill(); c.strokeStyle = 'rgba(120,130,80,.6)'; c.lineWidth = 2; c.stroke(); }
341
+ c.restore();
342
+ };
343
+
344
+ // ================= 浮世绘:海岸、富士、巨浪 =================
345
+ const INK = '#1c1a1e';
346
+ const uF = (g, path, fill, lw = 2.6) => { if (fill) { g.save(); g.translate(3, 2); g.fillStyle = fill; g.fill(path); g.restore(); } if (lw) { g.strokeStyle = INK; g.lineWidth = lw; g.stroke(path); } };
347
+ const poly = pts => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); p.closePath(); return p; };
348
+ X.ukiyoeWorld = (o) => {
349
+ const key = 'uw_' + o.key; if (X[key]) return X[key];
350
+ const w = o.w, x0 = o.x0, G = o.groundY, SEA = o.sea || 560;
351
+ const c = P.canvas(w, H), g = c.getContext('2d');
352
+ const sg = g.createLinearGradient(0, 0, 0, SEA); sg.addColorStop(0, '#2c4a80'); sg.addColorStop(0.1, '#4a6a98'); sg.addColorStop(0.3, '#e8d8a0'); sg.addColorStop(0.75, '#f2dc96'); sg.addColorStop(1, '#f3c98a');
353
+ g.fillStyle = sg; g.fillRect(0, 0, w, SEA);
354
+ const r = rng(41); for (let i = 0; i < w * 0.3; i++) { const y = r() * SEA; g.strokeStyle = y < 90 ? `rgba(30,50,110,${0.06 + r() * 0.1})` : `rgba(140,90,40,${0.03 + r() * 0.05})`; g.lineWidth = 1 + r() * 2; g.beginPath(); g.moveTo(r() * w, y); g.lineTo(r() * w, y + (r() - 0.5) * 3); g.stroke(); }
355
+ // 富士
356
+ (o.fuji || []).forEach(fx => { const lx = fx - x0; uF(g, poly([[lx - 230, SEA], [lx - 30, SEA - 230], [lx + 30, SEA - 232], [lx + 240, SEA]]), '#26467e', 2.4);
357
+ uF(g, poly([[lx - 70, SEA - 190], [lx - 30, SEA - 230], [lx + 30, SEA - 232], [lx + 72, SEA - 192], [lx + 50, SEA - 196], [lx + 34, SEA - 176], [lx + 16, SEA - 196], [lx - 4, SEA - 172], [lx - 24, SEA - 196], [lx - 46, SEA - 180]]), '#fbf8ee', 1.8); });
358
+ // 远海
359
+ g.save(); g.translate(3, 2); g.fillStyle = '#18305e'; g.fillRect(0, SEA, w, G - 40 - SEA); g.restore();
360
+ g.strokeStyle = INK; g.lineWidth = 2.4; g.beginPath(); g.moveTo(0, SEA); g.lineTo(w, SEA); g.stroke();
361
+ // 岸
362
+ const shore = new Path2D(); shore.moveTo(0, G - 30); for (let x = 0; x <= w; x += 40) shore.lineTo(x, G - 30 + Math.sin(x * 0.013) * 8); shore.lineTo(w, H); shore.lineTo(0, H); shore.closePath();
363
+ uF(g, shore, '#e8c890', 3);
364
+ g.save(); g.clip(shore); for (let i = 0; i < w * 0.05; i++) { const x = r() * w, y = G + r() * (H - G); g.fillStyle = 'rgba(150,100,50,.25)'; g.beginPath(); g.ellipse(x, y, 3 + r() * 6, 1.5 + r() * 2, 0, 0, TAU); g.fill(); }
365
+ const pg = g.createLinearGradient(0, G + 120, 0, H); pg.addColorStop(0, 'rgba(160,120,70,0)'); pg.addColorStop(1, 'rgba(160,120,70,.35)'); g.fillStyle = pg; g.fillRect(0, G, w, H - G); g.restore();
366
+ g.strokeStyle = INK; g.lineWidth = 2; for (let x = 20; x < w; x += 140) { g.beginPath(); g.moveTo(x, G + 70 + (x % 3) * 30); g.quadraticCurveTo(x + 40, G + 64 + (x % 3) * 30, x + 80, G + 72 + (x % 3) * 30); g.stroke(); }
367
+ // 松
368
+ (o.pines || []).forEach(px => { const lx = px - x0; const trunk = poly([[lx - 14, G - 20], [lx - 30, G - 200], [lx + 10, G - 330], [lx + 60, G - 400], [lx + 40, G - 330], [lx + 12, G - 200], [lx + 14, G - 20]]); uF(g, trunk, '#8a4a2a', 2.6);
369
+ [[lx - 60, G - 260, 90], [lx + 80, G - 400, 110], [lx - 10, G - 360, 80], [lx + 120, G - 300, 70]].forEach(([x, y, rr]) => { const p = new Path2D(); p.ellipse(x, y, rr, rr * 0.32, -0.08, 0, TAU); uF(g, p, '#2f5a3a', 2.4); g.strokeStyle = 'rgba(20,40,30,.6)'; g.lineWidth = 1.4; for (let k = -rr + 10; k < rr - 10; k += 12) { g.beginPath(); g.moveTo(x + k, y - 6); g.lineTo(x + k + 6, y + 8); g.stroke(); } }); });
370
+ // 纸纹罩层
371
+ const ov = P.canvas(w, H), og = ov.getContext('2d'); const ro = rng(5);
372
+ for (let i = 0; i < w * 0.7; i++) { const y = ro() * H, x = ro() * w, L = 200 + ro() * 700; og.strokeStyle = `rgba(120,80,40,${0.025 + ro() * 0.04})`; og.lineWidth = 1 + ro(); og.beginPath(); og.moveTo(x, y); og.lineTo(x + L, y + (ro() - 0.5) * 6); og.stroke(); }
373
+ for (let i = 0; i < w * 1.2; i++) { const x = ro() * w, y = ro() * H; og.strokeStyle = ro() < 0.5 ? 'rgba(255,250,235,.12)' : 'rgba(90,60,30,.07)'; og.lineWidth = 1; og.beginPath(); og.arc(x, y, 3 + ro() * 6, ro() * 6, ro() * 6 + 1); og.stroke(); }
374
+ return X[key] = { o, bg: c, over: ov, x0, w, G, SEA };
375
+ };
376
+ // 巨浪(每帧):浪身+内侧流线(虚线前移)+白沫带+浪爪屈伸+飞沫
377
+ X.greatWave = (c, bx, by, S, t) => {
378
+ c.save(); c.translate(bx, by); c.scale(S, S); c.lineCap = 'round'; c.lineJoin = 'round';
379
+ const k = Math.sin(t * 2.4) * 0.5 + 0.5; // 浪唇前卷程度
380
+ const back = K => KIT.densify(K, 6);
381
+ const O = back([[-420, 0], [-300, -90], [-190, -220], [-80, -350], [40, -430], [150, -432 + k * 8], [240, -380 + k * 14], [282 + k * 30, -300 + k * 34]]);
382
+ const tip = O[O.length - 1];
383
+ const I = back([tip, [220 + k * 12, -290 + k * 20], [150, -300], [100, -250], [96, -160], [130, -70], [220, 0]]);
384
+ const body = new Path2D(); O.forEach((p, i) => i ? body.lineTo(p[0], p[1]) : body.moveTo(p[0], p[1])); I.forEach(p => body.lineTo(p[0], p[1])); body.closePath();
385
+ uF(c, body, '#26467e', 3.2);
386
+ // 浪身流线:沿背面轮廓向内偏移,虚线往浪顶涌
387
+ c.save(); c.clip(body); c.setLineDash([30, 16]); c.lineDashOffset = -t * 140;
388
+ for (let m = 1; m < 9; m++) { c.strokeStyle = m % 2 ? 'rgba(150,182,226,.9)' : 'rgba(236,242,250,.8)'; c.lineWidth = 7; c.beginPath();
389
+ O.forEach((p, i) => { const q = i / (O.length - 1); const x = p[0] + m * 26 * (0.4 + q) , y = p[1] + m * 30 * (1 - q * 0.6); i ? c.lineTo(x, y) : c.moveTo(x, y); }); c.stroke(); }
390
+ c.restore(); c.setLineDash([]);
391
+ // 白沫带(沿浪背上段)
392
+ const top = O.slice(Math.floor(O.length * 0.35));
393
+ c.strokeStyle = INK; c.lineWidth = 40; c.beginPath(); top.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke();
394
+ c.strokeStyle = '#fbf7ec'; c.lineWidth = 35; c.stroke();
395
+ // 浪爪:沿浪唇外缘一簇簇胖手指,指向前下方并向内卷
396
+ const claw = (x, y, ang, L, kk) => { const lens = [1, 0.66, 0.56, 0.42], offs = [0, -0.6, 0.45, -0.25];
397
+ for (let f = 0; f < 4; f++) { let a = ang + offs[f], px = x, py = y; const LL = L * lens[f], pts = [[px, py]], cv = 1.1 + Math.sin(t * 5 + kk * 0.9) * 0.6;
398
+ for (let s2 = 0; s2 < 7; s2++) { a += cv * 0.34 * (0.5 + s2 / 7) / 2.4; px += Math.cos(a) * LL / 7; py += Math.sin(a) * LL / 7; pts.push([px, py]); }
399
+ [[LL * 0.32 + 4, INK], [LL * 0.32, '#fbf7ec'], [LL * 0.1, '#9ab0c8']].forEach(([lw, col]) => { c.strokeStyle = col; c.lineWidth = lw; c.beginPath(); pts.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); }); } };
400
+ const lipStart = Math.floor(O.length * 0.55);
401
+ for (let j = lipStart, kk = 0; j < O.length; j += 3, kk++) { const p = O[j], q = O[Math.max(0, j - 1)]; const nx = p[1] - q[1], ny = -(p[0] - q[0]); const a = Math.atan2(ny, nx) * -1; claw(p[0], p[1], Math.atan2(-(p[0] - q[0]), (p[1] - q[1])) + 0.6, 60 + (kk % 3) * 16, kk); }
402
+ for (let j = 0, kk = 20; j < 4; j++, kk++) { const p = I[1 + j]; claw(p[0], p[1], 1.3, 46, kk); }
403
+ // 飞沫(北斋的「雪」)
404
+ const r = rng(13); c.fillStyle = '#fbf7ec'; c.strokeStyle = INK; c.lineWidth = 1.6;
405
+ for (let i = 0; i < 40; i++) { const ph = (t * 0.7 + r()) % 1, x = tip[0] - 20 + r() * 150 + ph * 90, y = tip[1] - 30 - r() * 90 + ph * ph * 260, rr = 2.5 + r() * 4; c.globalAlpha = 1 - ph * 0.7; c.beginPath(); c.arc(x, y, rr, 0, TAU); c.fill(); c.stroke(); }
406
+ c.restore();
407
+ };
408
+ // 近海浪纹(每帧平移)
409
+ X.seaLines = (c, x0, y0, w, h, t) => { c.save(); c.beginPath(); c.rect(x0, y0, w, h); c.clip(); c.strokeStyle = 'rgba(230,236,245,.85)'; c.lineWidth = 3; const r = rng(3);
410
+ for (let k = 0; k < 9; k++) { const y = y0 + 12 + k * (h / 9), off = (t * 40 * (k % 2 ? 1 : -0.6)) % 120; for (let x = x0 - 120 + off + r() * 60; x < x0 + w; x += 120) { c.beginPath(); c.arc(x, y + 10, 22, Math.PI * 1.1, Math.PI * 1.9); c.stroke(); } } c.restore(); };
411
+
412
+ // ================= 梵高:星月夜 =================
413
+ X.vgWorld = (o) => {
414
+ const key = 'vg_' + o.key; if (X[key]) return X[key];
415
+ const w = o.w, x0 = o.x0, G = o.groundY, HOR = o.horizon || 600;
416
+ const stars = []; const rs = rng(17); for (let x = 120; x < w; x += 200 + rs() * 140) { const sx = x0 + x; if ((o.cypress || []).some(cx => Math.abs(cx - sx) < 160)) continue; stars.push({ x: sx, y: 70 + rs() * 360, r: 16 + rs() * 16 }); }
417
+ const swirls = (o.swirls || []).map(([x, y, R]) => ({ x, y, R }));
418
+ const moon = o.moon;
419
+ const angleSky = (wx, y) => { let a = 0.15 * Math.sin(wx * 0.004 + y * 0.01);
420
+ for (const s of swirls) { const dx = wx - s.x, dy = (y - s.y) * 1.6, d = Math.hypot(dx, dy); const f = Math.exp(-Math.pow(d / s.R, 2) * 0.8); a = lerp(a, Math.atan2(dy, dx) + Math.PI / 2, f); }
421
+ for (const s of stars.concat(moon ? [{ x: moon[0], y: moon[1], r: 60 }] : [])) { const dx = wx - s.x, dy = y - s.y, d = Math.hypot(dx, dy); if (d < s.r * 3.2) a = Math.atan2(dy, dx) + Math.PI / 2; }
422
+ return a; };
423
+ const base = P.canvas(w, H), g = base.getContext('2d');
424
+ const sg = g.createLinearGradient(0, 0, 0, HOR); sg.addColorStop(0, '#13246a'); sg.addColorStop(0.5, '#25479e'); sg.addColorStop(1, '#5a7ec0'); g.fillStyle = sg; g.fillRect(0, 0, w, HOR + 40);
425
+ swirls.forEach(s => { g.strokeStyle = '#9ab8e0'; g.lineWidth = 46; g.beginPath(); for (let a = 0; a < TAU * 1.4; a += 0.1) { const rr = s.R * (0.3 + a / (TAU * 1.4) * 0.7); g.lineTo(s.x - x0 + Math.cos(a) * rr, s.y + Math.sin(a) * rr / 1.6); } g.stroke(); });
426
+ stars.forEach(s => { const rg = g.createRadialGradient(s.x - x0, s.y, 0, s.x - x0, s.y, s.r * 2.6); rg.addColorStop(0, '#fff8c0'); rg.addColorStop(0.35, '#f0e070'); rg.addColorStop(0.6, '#c8d8a0'); rg.addColorStop(1, 'rgba(90,126,192,0)'); g.fillStyle = rg; g.beginPath(); g.arc(s.x - x0, s.y, s.r * 2.6, 0, TAU); g.fill(); });
427
+ if (moon) { const rg = g.createRadialGradient(moon[0] - x0, moon[1], 0, moon[0] - x0, moon[1], 150); rg.addColorStop(0, '#fff0a0'); rg.addColorStop(0.4, '#f0c840'); rg.addColorStop(0.7, '#e8d080'); rg.addColorStop(1, 'rgba(90,126,192,0)'); g.fillStyle = rg; g.beginPath(); g.arc(moon[0] - x0, moon[1], 150, 0, TAU); g.fill(); }
428
+ // 远山、村庄
429
+ g.fillStyle = '#2a3a78'; g.beginPath(); g.moveTo(0, HOR); for (let x = 0; x <= w; x += 30) g.lineTo(x, HOR - 70 - Math.sin(x * 0.004 + 1) * 50 - Math.sin(x * 0.011) * 20); g.lineTo(w, HOR + 60); g.lineTo(0, HOR + 60); g.fill();
430
+ const rv = rng(23); for (let x = 30; x < w; x += 46 + rv() * 30) { const hh = 26 + rv() * 30, y = HOR + 10 + rv() * 30; g.fillStyle = pick(['#3a4a6a', '#4a5a8a', '#5a6a5a'], rv); g.fillRect(x, y - hh, 40, hh); g.fillStyle = '#2a3058'; g.beginPath(); g.moveTo(x - 4, y - hh); g.lineTo(x + 20, y - hh - 18); g.lineTo(x + 44, y - hh); g.fill(); if (rv() < 0.6) { g.fillStyle = '#f8d850'; g.fillRect(x + 12, y - hh + 8, 10, 10); } }
431
+ g.fillStyle = '#3a5a3a'; g.fillRect(0, HOR + 50, w, G - HOR - 50);
432
+ const pg = g.createLinearGradient(0, G - 30, 0, H); pg.addColorStop(0, '#8a8a3a'); pg.addColorStop(0.3, '#c8a040'); pg.addColorStop(1, '#7a6a3a'); g.fillStyle = pg; g.fillRect(0, G - 30, w, H - G + 30);
433
+ (o.cypress || []).forEach(cx => { const lx = cx - x0; g.fillStyle = '#1e2e1e'; g.beginPath(); g.moveTo(lx - 70, G + 10); g.bezierCurveTo(lx - 90, G - 300, lx - 30, G - 500, lx + 6, G - 760); g.bezierCurveTo(lx + 40, G - 520, lx + 90, G - 300, lx + 70, G + 10); g.fill(); });
434
+ const out = P.canvas(w, H), oc = out.getContext('2d'); oc.drawImage(base, 0, 0);
435
+ const isCyp = (lx, y) => (o.cypress || []).some(cx => Math.abs(lx + x0 - cx) < 80 * (0.2 + (y - (G - 760)) / 760) && y > G - 760 && y < G + 10);
436
+ X.strokes(oc, base, { cell: 10, len: 28, width: 7, seed: 51, outline: 0.5, outlineCol: [16, 22, 70], jitterCol: 16,
437
+ angle: (lx, y, r) => isCyp(lx, y) ? -Math.PI / 2 + Math.sin(y * 0.03) * 0.5 : y < HOR + 20 ? angleSky(lx + x0, y) : y < G - 30 ? Math.sin(lx * 0.02) * 0.4 : 0.2 * Math.sin(lx * 0.01 + y * 0.02),
438
+ palette: (lx, y, col, r) => { const L = lum(col);
439
+ if (isCyp(lx, y)) return P.mix(hx(pick(['#1e3a24', '#2a4a2a', '#3a5a2a', '#14241a', '#4a6a3a'], r)), col, 0.3);
440
+ if (y < HOR + 20) { if (L > 180) return P.mix(hx(pick(['#fff8c0', '#f0e070', '#f8d850', '#e8f0d0'], r)), col, 0.4); if (L > 120) return P.mix(hx(pick(['#9ab8e0', '#c8d8f0', '#e8e8c0', '#6a8ad0'], r)), col, 0.4); return P.mix(hx(pick(['#1e3a8a', '#2a52a8', '#3a6ac0', '#5a86d0', '#1a2a6a', '#3a4aa0'], r)), col, 0.4); }
441
+ if (y > G - 30) return P.mix(hx(pick(['#c8a040', '#e0b850', '#8a7a3a', '#a89a4a', '#6a7a3a'], r)), col, 0.4);
442
+ return null; } });
443
+ return X[key] = { o, bg: out, x0, w, G, stars, moon, swirls, angleSky };
444
+ };
445
+ // 星空的动:星晕环旋转、漩涡带上的短笔沿螺线流动
446
+ X.vgSky = (c, V, camX, t) => {
447
+ c.save(); c.lineCap = 'round';
448
+ V.stars.forEach((s, k) => { const sx = s.x - camX; if (sx < -150 || sx > W + 150) return; const pul = 1 + 0.12 * Math.sin(t * 3 + k);
449
+ for (let ring = 0; ring < 2; ring++) { const R = s.r * (1.5 + ring * 0.8) * pul, n = 16 + ring * 6; for (let i = 0; i < n; i++) { const a = i / n * TAU + t * (0.8 - ring * 0.3) * (k % 2 ? 1 : -1);
450
+ c.strokeStyle = ring ? '#c8d8a0' : '#fff2a0'; c.lineWidth = 5; c.globalAlpha = 0.85; c.beginPath(); c.arc(sx, s.y, R, a, a + 0.22); c.stroke(); } } });
451
+ V.swirls.forEach((s, k) => { const sx = s.x - camX; if (sx < -s.R * 1.5 || sx > W + s.R * 1.5) return;
452
+ for (let i = 0; i < 60; i++) { const q = ((i / 60) + t * 0.12) % 1, a = q * TAU * 1.4, rr = s.R * (0.3 + q * 0.7), x = sx + Math.cos(a) * rr, y = s.y + Math.sin(a) * rr / 1.6, ta = a + Math.PI / 2;
453
+ c.strokeStyle = ['#e8f0ff', '#c8d8f0', '#fff8d0'][i % 3]; c.lineWidth = 6; c.globalAlpha = 0.8 * Math.sin(q * Math.PI); c.beginPath(); c.moveTo(x - Math.cos(ta) * 14, y - Math.sin(ta) * 9); c.lineTo(x + Math.cos(ta) * 14, y + Math.sin(ta) * 9); c.stroke(); } });
454
+ if (V.moon) { const sx = V.moon[0] - camX; c.globalAlpha = 1; c.fillStyle = '#ffe680'; c.beginPath(); c.arc(sx, V.moon[1], 52, 0, TAU); c.fill(); c.fillStyle = '#e8a830'; c.beginPath(); c.arc(sx + 22, V.moon[1] - 12, 46, 0, TAU); c.fill();
455
+ for (let i = 0; i < 24; i++) { const a = i / 24 * TAU - t * 0.5, R = 90 + 8 * Math.sin(t * 2 + i); c.strokeStyle = '#f8e070'; c.lineWidth = 6; c.globalAlpha = 0.75; c.beginPath(); c.arc(sx, V.moon[1], R, a, a + 0.18); c.stroke(); } }
456
+ c.restore();
457
+ };
458
+
459
+ // ================= 画风颜料迸散(跳进/跳出画框、跨边界时用) =================
460
+ X.burst = (c, x, y, a, R, style, seed = 1) => { if (a < 0 || a > 1) return; const r = rng(seed), cols = style === 'monet' ? ['#f2a6bf', '#9db5e0', '#9cc070', '#fff2d4', '#b7a6d8', '#8fdcae'] : ['#f8d850', '#2a52a8', '#fff8c0', '#5a86d0'];
461
+ c.save(); c.lineCap = 'round'; for (let i = 0; i < 70; i++) { const ang = r() * TAU, sp = R * (0.4 + r() * 0.8), d = sp * U.ease.out(a), x1 = x + Math.cos(ang) * d, y1 = y + Math.sin(ang) * d * 0.8 + 120 * a * a;
462
+ c.strokeStyle = cols[i % cols.length]; c.globalAlpha = 1 - a; c.lineWidth = 7 + r() * 6; c.beginPath(); c.moveTo(x1, y1); c.lineTo(x1 + Math.cos(ang) * 16, y1 + Math.sin(ang) * 10); c.stroke(); } c.restore(); };
463
+ })();