@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,183 @@
1
+ // 1889 后印象派(梵高《阿尔的卧室》+《星月夜》)——纯代码。质量标杆:其他时代照这个结构写。
2
+ // 管线:①平涂底稿 → ②流场笔触重画(墙竖、地板沿透视、星空沿漩涡、灯晕沿圆周,8fps 沸腾)
3
+ // → ③角色平涂+细笔触 → ④角色轮廓与五官补线 → ⑤会动的高光(星星、灯晕、热气)
4
+ SCENES['09_postimp'] = (() => {
5
+ const W = 1920, H = 1080, { clamp, lerp } = U, P = PAINT;
6
+ const C = {
7
+ wall: '#5b7bd3', wall2: '#4462bd', sky: '#1d2c7c', sky2: '#3a5bc0', swirl: '#8fb3ec', star: '#f6d84a', halo: '#fbeea0',
8
+ frame: '#2f8f58', frameHi: '#6fd08a', floor: '#b5553b', floor2: '#8a3a2c', wood: '#e3ad38', wood2: '#b57d1c',
9
+ bed: '#d9472f', line: '#1c2458', cypress: '#1f4a2c', village: '#1a2160',
10
+ };
11
+ const VP = [960, 330]; // 地板透视消失点
12
+ const swirls = [[520, 300, 90], [660, 240, 60], [450, 420, 55], [700, 400, 45]];
13
+ const stars = [[410, 180, 16], [520, 170, 12], [610, 190, 14], [745, 200, 24], [560, 260, 10], [700, 300, 11], [470, 300, 9]];
14
+
15
+ function base(g, t) {
16
+ // 墙
17
+ const wg = g.createLinearGradient(0, 0, 0, 720); wg.addColorStop(0, C.wall); wg.addColorStop(1, C.wall2);
18
+ g.fillStyle = wg; g.fillRect(0, 0, W, 720);
19
+ // 地板 + 木板缝(汇向消失点)
20
+ g.fillStyle = C.floor; g.fillRect(0, 700, W, H - 700);
21
+ g.strokeStyle = C.floor2; g.lineWidth = 6;
22
+ for (let i = -14; i <= 14; i++) { g.beginPath(); g.moveTo(VP[0] + i * 60, 700); g.lineTo(VP[0] + i * 230, H); g.stroke(); }
23
+ g.fillStyle = '#3a2a6a'; g.fillRect(0, 696, W, 10);
24
+ // 灯晕底色(大面积,交给笔触)
25
+ { const hg = g.createRadialGradient(1000, 180, 10, 1000, 180, 105); hg.addColorStop(0, '#fff7c8'); hg.addColorStop(0.55, '#f4d65a'); hg.addColorStop(1, 'rgba(120,150,220,0)'); g.fillStyle = hg; g.beginPath(); g.arc(1000, 180, 105, 0, Math.PI * 2); g.fill(); }
26
+ // 窗:星空
27
+ g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
28
+ const sg = g.createLinearGradient(0, 140, 0, 540); sg.addColorStop(0, C.sky); sg.addColorStop(1, C.sky2); g.fillStyle = sg; g.fillRect(370, 140, 420, 400);
29
+ // 漩涡:螺旋色带,随时间旋转
30
+ g.lineCap = 'round';
31
+ swirls.forEach(([x, y, r], k) => { for (let ring = 0; ring < 3; ring++) { g.strokeStyle = ring % 2 ? C.swirl : '#5a82d6'; g.lineWidth = 14 - ring * 3; g.beginPath();
32
+ for (let a = 0; a < Math.PI * 3; a += 0.15) { const rr = r * (0.25 + a / (Math.PI * 3)) * (1 - ring * 0.12), aa = a + t * (0.9 + k * 0.2) * (k % 2 ? -1 : 1); const px = x + Math.cos(aa) * rr, py = y + Math.sin(aa) * rr * 0.8; a ? g.lineTo(px, py) : g.moveTo(px, py); } g.stroke(); } });
33
+ // 月亮
34
+ g.fillStyle = C.star; g.beginPath(); g.arc(745, 200, 30, 0, Math.PI * 2); g.fill(); g.fillStyle = C.sky; g.beginPath(); g.arc(732, 192, 24, 0, Math.PI * 2); g.fill();
35
+ // 柏树(火焰形)与村庄
36
+ g.fillStyle = C.cypress; g.beginPath(); g.moveTo(400, 545); g.bezierCurveTo(372, 430, 420, 330, 425, 200); g.bezierCurveTo(445, 320, 470, 430, 452, 545); g.closePath(); g.fill();
37
+ g.fillStyle = C.village; g.beginPath(); g.moveTo(370, 545); [[420, 500], [470, 505], [500, 480], [540, 500], [590, 470], [610, 440], [625, 470], [680, 495], [730, 485], [790, 500], [790, 545]].forEach(p => g.lineTo(...p)); g.closePath(); g.fill();
38
+ g.fillStyle = '#f2c94c'; [[480, 512], [520, 520], [560, 508], [650, 512], [700, 520], [740, 510]].forEach(([x, y]) => g.fillRect(x, y, 8, 8));
39
+ g.restore();
40
+ // 窗框(绿)+ 中梃 + 窗台
41
+ g.lineWidth = 26; g.strokeStyle = C.frame; g.strokeRect(357, 127, 446, 426);
42
+ g.lineWidth = 6; g.strokeStyle = C.frameHi; g.strokeRect(366, 136, 428, 408);
43
+ g.fillStyle = C.frame; g.fillRect(568, 140, 20, 400);
44
+ g.fillStyle = C.frameHi; g.fillRect(330, 552, 500, 22); g.fillStyle = C.frame; g.fillRect(330, 570, 500, 14);
45
+ // 床(右侧)
46
+ g.fillStyle = C.wood; g.fillRect(1610, 560, 330, 420); g.fillStyle = C.wood2; g.fillRect(1640, 640, 290, 300);
47
+ g.fillStyle = C.bed; g.beginPath(); g.moveTo(1620, 600); g.quadraticCurveTo(1760, 500, 1940, 520); g.lineTo(1940, 640); g.quadraticCurveTo(1780, 620, 1620, 660); g.closePath(); g.fill();
48
+ g.fillStyle = C.wood; g.fillRect(1598, 520, 26, 470); g.beginPath(); g.arc(1611, 515, 18, 0, Math.PI * 2); g.fill();
49
+ }
50
+ function props(g, t) {
51
+ // 墙上两幅小画
52
+ [[1590, 250, 100, 120, '#d9733a', '#66a35a'], [1730, 250, 100, 120, '#e8d36a', '#4f7fc9']].forEach(([x, y, w, h, a, b]) => {
53
+ g.fillStyle = '#d8a83a'; g.fillRect(x - 10, y - 10, w + 20, h + 20); g.fillStyle = b; g.fillRect(x, y, w, h); g.fillStyle = a; g.beginPath(); g.ellipse(x + w / 2, y + h * 0.55, w * 0.28, h * 0.25, 0, 0, Math.PI * 2); g.fill(); });
54
+ // 椅子(梯背,在少女后面)
55
+ g.fillStyle = C.wood; [[1432, 470, 16, 440], [1478, 480, 16, 430]].forEach(r => g.fillRect(...r));
56
+ for (let k = 0; k < 4; k++) g.fillRect(1436, 510 + k * 55, 56, 10);
57
+ g.fillRect(1250, 742, 250, 16); g.fillRect(1262, 750, 14, 160);
58
+ // 桌子
59
+ g.fillStyle = C.wood; g.fillRect(810, 618, 400, 32); g.fillStyle = C.wood2; g.fillRect(830, 650, 360, 52);
60
+ g.fillStyle = '#e9c46a'; g.fillRect(960, 664, 90, 22); g.fillStyle = '#6b4a1a'; g.beginPath(); g.arc(1005, 675, 5, 0, Math.PI * 2); g.fill();
61
+ g.fillStyle = C.wood; g.fillRect(842, 700, 22, 205); g.fillRect(1160, 700, 22, 205); g.fillRect(842, 860, 340, 12);
62
+ // 花瓶+向日葵
63
+ g.fillStyle = '#e8c255'; g.beginPath(); g.moveTo(852, 618); g.quadraticCurveTo(838, 575, 862, 548); g.lineTo(898, 548); g.quadraticCurveTo(920, 575, 905, 618); g.closePath(); g.fill();
64
+ g.strokeStyle = '#4d8a3a'; g.lineWidth = 6; [[870, 548, 842, 490], [880, 548, 890, 462], [890, 548, 930, 502], [875, 548, 868, 528]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
65
+ [[842, 490, 26], [890, 462, 28], [930, 502, 24], [866, 526, 18]].forEach(([x, y, r], k) => { const sp = Math.sin(t * 3 + k) * 0.08;
66
+ g.fillStyle = '#f2b51c'; for (let i = 0; i < 12; i++) { const a = i / 12 * Math.PI * 2 + sp; g.beginPath(); g.ellipse(x + Math.cos(a) * r * 0.8, y + Math.sin(a) * r * 0.8, r * 0.45, r * 0.18, a, 0, Math.PI * 2); g.fill(); }
67
+ g.fillStyle = '#8a4b14'; g.beginPath(); g.arc(x, y, r * 0.45, 0, Math.PI * 2); g.fill(); });
68
+ // 吊灯
69
+ g.strokeStyle = '#3a2a2a'; g.lineWidth = 4; g.beginPath(); g.moveTo(1000, 0); g.lineTo(1000, 150); g.stroke();
70
+ g.fillStyle = '#f1c232'; g.beginPath(); g.moveTo(968, 176); g.quadraticCurveTo(1000, 132, 1032, 176); g.closePath(); g.fill();
71
+
72
+ g.fillStyle = '#fff6c0'; g.beginPath(); g.arc(1000, 182, 12, 0, Math.PI * 2); g.fill();
73
+ }
74
+ // 笔触方向场
75
+ const angle = (x, y, t) => {
76
+ if (x > 370 && x < 790 && y > 140 && y < 540) { // 星空:沿最近漩涡的切线
77
+ let best = null, bd = 1e9; swirls.forEach(s => { const d = Math.hypot(x - s[0], y - s[1]) / s[2]; if (d < bd) { bd = d; best = s; } });
78
+ if (bd < 1.6) return Math.atan2(y - best[1], x - best[0]) + Math.PI / 2 + t * 0.3;
79
+ return 0.15 * Math.sin(x * 0.02 + t) ;
80
+ }
81
+ const dl = Math.hypot(x - 1000, y - 180); if (dl < 150) return Math.atan2(y - 180, x - 1000) + Math.PI / 2; // 灯晕:圆周
82
+ if (y > 700) return Math.atan2(y - VP[1], x - VP[0]) + 0.06 * Math.sin(t * 2 + x * 0.01); // 地板:透视线
83
+ if (x > 1600 && y > 520) return y < 640 ? -0.12 : -Math.PI / 2;
84
+ if (x > 810 && x < 1210 && y > 610 && y < 705) return 0.02;
85
+ return -Math.PI / 2 + 0.26 * P.noise(x * 0.008 + t * 0.5, y * 0.008 + 3 - t * 0.8); // 墙:竖向,随时间像火苗一样缓慢摇曳
86
+ };
87
+ // 区域色板:每一笔从所在区域的色组里抽色,再混一点底稿色保留明暗(梵高的「同一面墙十几种蓝」)
88
+ const SW = {
89
+ wall: P.swatch(['#7f9be6', '#5f7fd8', '#a7bbf2', '#6c6fd2', '#8ad0e6', '#c3cbf6', '#4f6cc8', '#9a8fe0'], 0.3),
90
+ sky: P.swatch(['#22339a', '#2f4fb8', '#5c86dc', '#8fb6ee', '#1b2a78', '#3c6bd0'], 0.45),
91
+ floor: P.swatch(['#c0573c', '#a8453a', '#d7774a', '#8e3b30', '#5f8f6a', '#c96a52', '#e08a5c'], 0.3),
92
+ wood: P.swatch(['#f2c13a', '#e0a020', '#f8d860', '#d08a18', '#b8c040'], 0.15),
93
+ bed: P.swatch(['#e04a32', '#c63a2a', '#f06b45', '#b83224'], 0.4),
94
+ green: P.swatch(['#2f9a5c', '#4fc27a', '#1f7a48', '#8be09a'], 0.35),
95
+ halo: P.swatch(['#fff2a0', '#f6d84a', '#ffe680', '#f0c040'], 0.3),
96
+ };
97
+ const palette = (x, y, col, r) => {
98
+ if (Math.hypot(x - 1000, y - 180) < 92) return SW.halo(col, r);
99
+ if (x > 370 && x < 790 && y > 140 && y < 540) { const L = col[0] + col[1] + col[2]; return L > 500 ? null : SW.sky(col, r); }
100
+ if (x > 340 && x < 830 && y > 115 && y < 590) return SW.green(col, r);
101
+ if (y > 706) return SW.floor(col, r);
102
+ if (x > 1595 && y > 600) return SW.wood(col, r);
103
+ if (x > 1600 && y > 505 && y <= 640) return SW.bed(col, r);
104
+ return SW.wall(col, r);
105
+ };
106
+ // 环境线稿:桌、椅、床、窗框、画框、花瓶的深蓝轮廓(8fps 轻微抖动)
107
+ function lines(c, t) {
108
+ const sd = P.boilSeed(t, 8); c.save(); c.strokeStyle = C.line; c.lineWidth = 4.5; c.lineJoin = 'round';
109
+ const R_ = (pts, closed = true) => { P.roughPath(c, pts, { amp: 2.2, seed: sd * 31 + pts.length + (pts[0][0] | 0), closed, step: 14 }); c.stroke(); };
110
+ R_([[344, 114], [816, 114], [816, 566], [344, 566]]); R_([[370, 140], [790, 140], [790, 540], [370, 540]]); R_([[568, 140], [588, 140], [588, 540], [568, 540]]);
111
+ R_([[330, 552], [830, 552], [830, 584], [330, 584]]);
112
+ R_([[810, 618], [1210, 618], [1210, 650], [810, 650]]); R_([[830, 650], [1190, 650], [1190, 702], [830, 702]]); R_([[960, 664], [1050, 664], [1050, 686], [960, 686]]);
113
+ R_([[842, 700], [864, 700], [864, 905], [842, 905]]); R_([[1160, 700], [1182, 700], [1182, 905], [1160, 905]]);
114
+ R_([[1432, 470], [1448, 470], [1448, 910], [1432, 910]]); R_([[1478, 480], [1494, 480], [1494, 910], [1478, 910]]);
115
+ for (let k = 0; k < 4; k++) R_([[1436, 510 + k * 55], [1492, 510 + k * 55], [1492, 520 + k * 55], [1436, 520 + k * 55]]);
116
+ R_([[1610, 560], [1940, 560], [1940, 980], [1610, 980]]); R_([[1640, 640], [1930, 640], [1930, 940], [1640, 940]]); R_([[1598, 520], [1624, 520], [1624, 990], [1598, 990]]);
117
+ R_([[1580, 240], [1700, 240], [1700, 380], [1580, 380]]); R_([[1720, 240], [1840, 240], [1840, 380], [1720, 380]]);
118
+ R_([[852, 618], [840, 580], [862, 548], [898, 548], [918, 580], [905, 618]]);
119
+ R_([[0, 700], [1940, 700]], false);
120
+ c.lineWidth = 3.5; for (let i = -12; i <= 12; i++) { if (i % 2) continue; R_([[VP[0] + i * 62 * 1.2, 704], [VP[0] + i * 240, 1080]], false); }
121
+ c.restore();
122
+ }
123
+ const girlPal = { skin: '#f8dcc6', hair: '#f2c552', hairLine: '#c4922c', dress: '#3a5fb4', fold: '#2a4386', apron: '#f6f1e6', hat: '#ecc860', hatBand: '#2f50a8',
124
+ cheek: 'rgba(240,120,110,.45)', lip: '#d0504e', iris: '#2a4d90', line: C.line, lw: 4, cupBody: '#2a50b0', cupRim: '#4a2a12' };
125
+ const catPal = { orange: '#ec9433', white: '#fbf1dd', stripe: '#c0621a', line: C.line, lw: 4, eye: '#7ec850' };
126
+
127
+ return {
128
+ draw(c, lt, t) {
129
+ const ch = P.choreo(lt, t);
130
+ const b = P.scratch('vgBase'), bg = b.getContext('2d');
131
+ base(bg, t);
132
+ c.drawImage(b, 0, 0);
133
+ const isWall = (x, y) => y < 700 && !(x > 340 && x < 830 && y > 110 && y < 590) && !(x > 1595 && y > 505) && Math.hypot(x - 1000, y - 180) > 125;
134
+ P.strokes(c, b, { cell: 10, len: 22, width: 6.5, angle, seed: 9, t, boil: 0, outline: 0.9, outlineMix: 0.45, palette, mask: (x, y) => !isWall(x, y) && (y <= 704 || (x > 1595 && y < 995)) });
135
+ P.strokes(c, b, { cell: 14, len: 64, width: 8, angle, seed: 19, t, boil: 0, outline: 0.8, outlineMix: 0.38, palette, mask: isWall });
136
+ P.strokes(c, b, { cell: 12, len: 46, width: 8, angle, seed: 29, t, boil: 0, outline: 0.85, outlineMix: 0.45, palette, mask: (x, y) => y > 704 && !(x > 1595 && y < 995) });
137
+ // 道具层:桌、椅、花瓶向日葵、画框、吊灯——单独画、单独上细肌理,避免被大笔触打碎
138
+ const Pr = P.scratch('vgProps'), pg = Pr.getContext('2d'); pg.clearRect(0, 0, W, H); props(pg, t);
139
+ pg.save(); pg.globalCompositeOperation = 'source-atop';
140
+ P.strokes(pg, Pr, { cell: 7, len: 14, width: 4.5, seed: 7, t, boil: 0, outline: 0.5, outlineMix: 0.35, jitterCol: 34, alphaMask: true,
141
+ angle: (x, y) => (y > 650 && y < 905 && ((x > 836 && x < 870) || (x > 1154 && x < 1188) || x > 1425)) ? -Math.PI / 2 : 0.05 });
142
+ pg.restore(); c.drawImage(Pr, 0, 0);
143
+ lines(c, t);
144
+ // 灯晕:一圈圈向外脉动
145
+ c.save(); c.globalCompositeOperation = 'lighter'; c.lineCap = 'round';
146
+ for (let k = 0; k < 3; k++) { const ph = ((t * 0.9 + k / 3) % 1), r = 40 + ph * 110; c.strokeStyle = `rgba(255,236,140,${0.28 * (1 - ph)})`; c.lineWidth = 18; c.setLineDash([22, 14]); c.lineDashOffset = -t * 60; c.beginPath(); c.arc(1000, 180, r, 0, Math.PI * 2); c.stroke(); }
147
+ c.setLineDash([]);
148
+ c.restore();
149
+ // 星星:闪烁+光圈
150
+ stars.forEach(([x, y, r], k) => { const tw = 0.75 + 0.35 * Math.sin(t * 5 + k * 1.7); c.fillStyle = 'rgba(251,238,160,.55)'; c.beginPath(); c.arc(x, y, r * 1.9 * tw, 0, Math.PI * 2); c.fill(); c.fillStyle = C.star; c.beginPath(); c.arc(x, y, r * 0.75 * tw, 0, Math.PI * 2); c.fill(); });
151
+ // 角色:平涂 → 细笔触 → 补线
152
+ const L = P.scratch('vgChars'), lg = L.getContext('2d'); lg.clearRect(0, 0, W, H);
153
+ const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
154
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, hat: 'straw', breathe: ch.breathe });
155
+ RIG.drawCat(lg, K, { ...catPal, lw: 0 }); RIG.drawGirl(lg, G, { ...girlPal, mode: 'fill' });
156
+ lg.save(); lg.globalCompositeOperation = 'source-atop';
157
+ P.strokes(lg, L, { cell: 8, len: 16, width: 5, seed: 4, t, boil: 0, outline: 0, jitterCol: 22, alphaMask: true,
158
+ angle: (x, y) => (x < 760 ? Math.atan2(y - 760, x - 545) + Math.PI / 2 : -Math.PI / 2 + 0.3 * Math.sin(y * 0.02)) }); // 猫身体打旋(梵高式),少女竖向
159
+ lg.restore();
160
+ // 猫身上的梵高漩涡纹(原片那只「漩涡猫」)
161
+ lg.save(); lg.clip(K.body); lg.lineCap = 'round';
162
+ for (let ring = 0; ring < 7; ring++) { lg.strokeStyle = ring % 2 ? '#f7c063' : '#c96a1e'; lg.lineWidth = 7; lg.beginPath();
163
+ for (let a = 0; a < Math.PI * 1.7; a += 0.12) { const rr = 18 + ring * 17, aa = a + ring * 0.7 + t * 0.6; const px = 505 + Math.cos(aa) * rr, py = 805 + Math.sin(aa) * rr * 0.9; a ? lg.lineTo(px, py) : lg.moveTo(px, py); } lg.stroke(); }
164
+ lg.restore();
165
+ c.drawImage(L, 0, 0);
166
+ c.drawImage(RIG.visibleLines('vgCat', (g, lc) => RIG.drawCat(g, K, { ...catPal, line: lc, whisker: lc }), C.line, [340, 500, 740, 940]), 0, 0);
167
+ c.drawImage(RIG.visibleLines('vgGirl', (g, lc) => RIG.drawGirl(g, G, { ...girlPal, line: lc }), C.line, [1060, 200, 1520, 950]), 0, 0);
168
+ // 线稿层会把帽檐后半圈画穿到头发上:线稿之后把帽子整顶重新盖一遍(填色+草编短笔+外轮廓)
169
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
170
+ [G.brim, G.crown].forEach(p => { c.fillStyle = girlPal.hat; c.fill(p); });
171
+ c.save(); c.clip(G.brim); c.clip(G.brim);
172
+ const hr = U.rng(55); c.lineWidth = 3.5;
173
+ for (let i = 0; i < 70; i++) { const x = G.A.headTop[0] - 140 + hr() * 280, y = G.A.headTop[1] - 40 + hr() * 70; c.strokeStyle = hr() < .5 ? '#f6d977' : '#c99a2e'; c.beginPath(); c.moveTo(x, y); c.lineTo(x + 16, y + 2); c.stroke(); }
174
+ c.restore();
175
+ c.save(); c.clip(G.crown); for (let i = 0; i < 30; i++) { const x = G.A.headTop[0] - 60 + hr() * 130, y = G.A.headTop[1] - 50 + hr() * 50; c.strokeStyle = hr() < .5 ? '#f6d977' : '#c99a2e'; c.beginPath(); c.moveTo(x, y); c.lineTo(x + 3, y + 14); c.stroke(); } c.restore();
176
+ c.fillStyle = girlPal.hatBand; c.fill(G.band);
177
+ c.strokeStyle = C.line; c.lineWidth = 4; c.stroke(G.brim); c.stroke(G.crown);
178
+ c.restore();
179
+ // 热气(从杯口卷曲上升)
180
+ P.steam(c, t, G.cup.x, G.cup.y - 6, { h: 70, n: 2, color: 'rgba(240,244,255,.85)', width: 4, spread: 14, wobble: 9 });
181
+ },
182
+ };
183
+ })();
@@ -0,0 +1,334 @@
1
+ // 1896 新艺术运动(慕夏海报)——纯代码。
2
+ // 管线:①缓存底版(灰绿渐变+圆点花纹+金框+马赛克地砖带+绿横幅+标题)
3
+ // → ②会动的母题(彩色玻璃拱窗里太阳升起、铅条光芒转动;罂粟/百合摇摆;光环转动)
4
+ // → ③角色平涂进离屏层 → 整体剪影往右下错位填深棕 = 慕夏式「外轮廓粗、内线细」
5
+ // → ④细内线 + 可变线宽的金色长卷发、花环、S 形装饰热气
6
+ SCENES['10_nouveau'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp } = U, P = PAINT;
8
+ const C = {
9
+ paper: '#efe6cf', gold: '#c9a24a', goldD: '#9c7a2e', line: '#4e3320', green: '#b9cba0', green2: '#cfd3a6', peach: '#ecc9a2',
10
+ dot: 'rgba(120,140,100,.38)', banner: '#8fac80', bannerD: '#6f8f63', cream: '#f6eedb',
11
+ poppy: '#d9706a', poppyD: '#b4504b', stem: '#5f8a58', leaf: '#8fb38a', leafD: '#5f8a58', lily: '#f7f2e6',
12
+ iron: '#3d7763', ironHi: '#5f9a82', wood: '#7a5234',
13
+ };
14
+ const TAU = Math.PI * 2;
15
+
16
+ // ---------- 小工具 ----------
17
+ // Catmull-Rom 加密成点列
18
+ const dense = (pts, n = 10) => {
19
+ const o = [], N = pts.length; const g = i => pts[Math.max(0, Math.min(N - 1, i))];
20
+ for (let i = 0; i < N - 1; i++) { const p0 = g(i - 1), p1 = g(i), p2 = g(i + 1), p3 = g(i + 2);
21
+ for (let k = 0; k < n; k++) { const t = k / n, t2 = t * t, t3 = t2 * t;
22
+ o.push([0.5 * ((2 * p1[0]) + (-p0[0] + p2[0]) * t + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3),
23
+ 0.5 * ((2 * p1[1]) + (-p0[1] + p2[1]) * t + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3)]); } }
24
+ o.push(pts[N - 1]); return o;
25
+ };
26
+ // 可变线宽路径:沿点列两侧偏移 w(q)/2,返回闭合 Path2D(慕夏的「粗细变化的勾线」靠它)
27
+ const ribbon = (pts, wf) => {
28
+ const L = [], R = [], n = pts.length;
29
+ for (let i = 0; i < n; i++) {
30
+ const a = pts[Math.max(0, i - 1)], b = pts[Math.min(n - 1, i + 1)];
31
+ let dx = b[0] - a[0], dy = b[1] - a[1]; const d = Math.hypot(dx, dy) || 1; dx /= d; dy /= d;
32
+ const w = wf(i / (n - 1)) / 2; L.push([pts[i][0] - dy * w, pts[i][1] + dx * w]); R.push([pts[i][0] + dy * w, pts[i][1] - dx * w]);
33
+ }
34
+ const p = new Path2D(); L.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); for (let i = n - 1; i >= 0; i--) p.lineTo(R[i][0], R[i][1]); p.closePath(); return p;
35
+ };
36
+ const taperW = (w0, w1, mid = 1) => q => lerp(w0, w1, q) * (0.55 + 0.45 * Math.sin(Math.PI * Math.min(1, q * 1.15)) * mid + (1 - mid) * 0.45);
37
+ const swellW = (wmax, wend = 1.2) => q => wend + (wmax - wend) * Math.sin(Math.PI * q); // 两头尖中间粗
38
+ // 螺旋收尾的点列(新艺术的「鞭线」卷曲)
39
+ const spiral = (cx, cy, r0, a0, turns, dir = 1, n = 28) => { const o = []; for (let i = 0; i <= n; i++) { const q = i / n, a = a0 + dir * q * turns * TAU, r = r0 * (1 - q * 0.85); o.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); } return o; };
40
+ const inkLine = (c, pts, wmax, col = C.line, wend = 1) => { c.fillStyle = col; c.fill(ribbon(dense(pts, 8), swellW(wmax, wend))); };
41
+
42
+ // ---------- ① 底版(缓存) ----------
43
+ const bg = () => P.cached('nv_bg', W, H, (g) => {
44
+ g.fillStyle = C.paper; g.fillRect(0, 0, W, H);
45
+ const gr = g.createLinearGradient(0, 30, 0, 905); gr.addColorStop(0, C.green); gr.addColorStop(0.55, C.green2); gr.addColorStop(1, C.peach);
46
+ g.fillStyle = gr; g.fillRect(34, 32, W - 68, 875);
47
+ // 圆点花纹:环+心点,菱形交错
48
+ g.strokeStyle = C.dot; g.fillStyle = C.dot; g.lineWidth = 1.6;
49
+ for (let j = 0, y = 62; y < 900; y += 54, j++) for (let x = 64 + (j % 2) * 31; x < 1880; x += 62) {
50
+ g.beginPath(); g.arc(x, y, 10, 0, TAU); g.stroke(); g.beginPath(); g.arc(x, y, 2.6, 0, TAU); g.fill(); }
51
+ // 轻微的石版印刷斑驳(小噪声图放大)
52
+ const nz = P.canvas(240, 135), ng = nz.getContext('2d'), im = ng.createImageData(240, 135);
53
+ for (let y = 0; y < 135; y++) for (let x = 0; x < 240; x++) { const v = P.fbm(x * 0.05, y * 0.05, 4), i = (y * 240 + x) * 4; im.data[i] = 90; im.data[i + 1] = 70; im.data[i + 2] = 40; im.data[i + 3] = clamp(v * 1.4 + 0.15) * 60; }
54
+ ng.putImageData(im, 0, 0); g.globalAlpha = 0.5; g.drawImage(nz, 0, 0, W, H); g.globalAlpha = 1;
55
+ // 马赛克地砖带
56
+ const tiles = ['#f1ead6', '#8fb08a', '#c9a24a', '#f1ead6', '#a9c39a', '#d98c8c', '#c9a24a', '#f1ead6', '#6f9a80'];
57
+ g.fillStyle = C.line; g.fillRect(34, 898, W - 68, 34);
58
+ for (let k = 0, x = 38; x < 1880; x += 24, k++) { g.fillStyle = tiles[(k * 7 + (k >> 2)) % tiles.length]; g.fillRect(x, 903, 20, 24); }
59
+ // 绿横幅
60
+ g.fillStyle = C.banner; g.fillRect(34, 934, W - 68, 114);
61
+ g.fillStyle = C.line; g.fillRect(34, 932, W - 68, 3); g.fillRect(34, 1046, W - 68, 3);
62
+ // 横幅两侧的鞭线卷草(左右镜像)
63
+ const whip = (s) => { g.save(); if (s < 0) { g.translate(W, 0); g.scale(-1, 1); }
64
+ g.fillStyle = C.cream; g.fill(ribbon(dense([[100, 960], [112, 948], [128, 958], [200, 985], [300, 990], [400, 984]], 10), swellW(7, 1)));
65
+ g.fillStyle = C.gold; g.fill(ribbon(dense([[130, 1010], [150, 1022], [180, 1004], [260, 985], [330, 975], [380, 975]], 10), swellW(8, 1)));
66
+ g.fillStyle = C.cream; g.fill(ribbon(dense(spiral(112, 956, 16, Math.PI, 0.9, 1), 2), q => 4.5 - q * 3));
67
+ g.fillStyle = C.gold; g.fill(ribbon(dense(spiral(142, 1012, 15, 0, 0.8, -1), 2), q => 5 - q * 3.5));
68
+ g.strokeStyle = C.line; g.lineWidth = 1.2; g.fill(ribbon(dense([[200, 1030], [260, 1010], [340, 1000]], 10), swellW(3, .6)));
69
+ g.fillStyle = '#d07a86'; g.beginPath(); g.arc(420, 993, 8, 0, TAU); g.fill();
70
+ g.restore(); };
71
+ whip(1); whip(-1);
72
+ // 标题
73
+ g.font = '76px "Marcellus-400"'; g.textAlign = 'center'; g.textBaseline = 'middle'; g.letterSpacing = '12px';
74
+ g.lineJoin = 'round'; g.strokeStyle = C.line; g.lineWidth = 7; g.strokeText('THÉ DU CHAT ROUX', 966, 994);
75
+ g.fillStyle = C.cream; g.fillText('THÉ DU CHAT ROUX', 966, 994); g.letterSpacing = '0px';
76
+ // 金框:外粗金线+内细棕线
77
+ g.strokeStyle = C.gold; g.lineWidth = 6; g.strokeRect(22, 20, W - 44, H - 40);
78
+ g.strokeStyle = C.line; g.lineWidth = 2; g.strokeRect(31, 29, W - 62, H - 58); g.strokeRect(16, 14, W - 32, H - 28);
79
+ });
80
+
81
+ // ---------- ② 彩色玻璃拱窗 ----------
82
+ const WX0 = 362, WX1 = 800, WY = 548, ACX = 581, ACY = 250, ARX = 219, ARY = 130;
83
+ const archPath = (ins) => { const p = new Path2D(); p.moveTo(WX0 + ins, WY - ins); p.lineTo(WX0 + ins, ACY); p.ellipse(ACX, ACY, ARX - ins, ARY - ins, 0, Math.PI, TAU); p.lineTo(WX1 - ins, WY - ins); p.closePath(); return p; };
84
+ const archPts = (ins) => { const o = []; for (let y = WY - ins; y > ACY; y -= 4) o.push([WX0 + ins, y]); for (let a = Math.PI; a <= TAU + 1e-6; a += 0.02) o.push([ACX + Math.cos(a) * (ARX - ins), ACY + Math.sin(a) * (ARY - ins)]); for (let y = ACY; y < WY - ins; y += 4) o.push([WX1 - ins, y]); return o; };
85
+ const hill = (y0, amp, ph, f = 0.016) => { const o = []; for (let x = WX0; x <= WX1 + 10; x += 12) o.push([x, y0 + Math.sin(x * f + ph) * amp + Math.sin(x * f * 2.3 + ph * 2) * amp * 0.35]); return o; };
86
+ const bands = [[372, 16, 0.5, '#f2dd9c'], [398, 18, 2.2, '#efc39c'], [424, 16, 4.1, '#e6a7a0'], [452, 14, 1.3, '#a9cfa0'], [490, 10, 3.3, '#7fb5a8']];
87
+ function windowGlass(c, lt, t) {
88
+ const inner = archPath(30);
89
+ c.save(); c.clip(inner);
90
+ const sg = c.createLinearGradient(0, 130, 0, 400); sg.addColorStop(0, '#86bfbd'); sg.addColorStop(0.6, '#b9d8b3'); sg.addColorStop(1, '#e9e2a8');
91
+ c.fillStyle = sg; c.fillRect(WX0, 110, WX1 - WX0, 440);
92
+ // 太阳:从山后升起(原片 ~-5px/帧)
93
+ const k = clamp(lt, 0, 1.4), sx = 583 - (k - 0.267) * 20, sy = 400 - (k - 0.267) * 288;
94
+ // 铅条光芒:从太阳中心放射,随时间转动
95
+ c.strokeStyle = C.line; c.lineWidth = 3.5; c.lineCap = 'round';
96
+ const rot = lt * 0.9;
97
+ for (let i = 0; i < 14; i++) { const a = Math.PI + (i + 0.5) / 14 * Math.PI + Math.sin(rot + i) * 0.04 + rot * 0.25; c.beginPath(); c.moveTo(sx + Math.cos(a) * 92, sy + Math.sin(a) * 92); c.lineTo(sx + Math.cos(a) * 420, sy + Math.sin(a) * 420); c.stroke(); }
98
+ // 天空里两道弧形铅条
99
+ c.lineWidth = 3; [[0.78, 150], [1.15, 200]].forEach(([e, r]) => { c.beginPath(); c.ellipse(sx, sy, r * 1.25, r * e * 0.75, 0, Math.PI, TAU); c.stroke(); });
100
+ // 光晕环+日盘
101
+ c.fillStyle = '#f7e2a0'; c.beginPath(); c.arc(sx, sy, 80, 0, TAU); c.fill(); c.stroke();
102
+ const sun = c.createRadialGradient(sx - 14, sy - 14, 6, sx, sy, 64); sun.addColorStop(0, '#f8c860'); sun.addColorStop(1, '#e8a43c');
103
+ c.fillStyle = sun; c.beginPath(); c.arc(sx, sy, 62, 0, TAU); c.fill(); c.lineWidth = 4; c.stroke();
104
+ // 山丘色带(挡住太阳下半)
105
+ bands.forEach(([y0, amp, ph, col], i) => { const pts = hill(y0, amp, ph + Math.sin(t * 1.4 + i) * 0.12); c.fillStyle = col; c.beginPath(); c.moveTo(WX0 - 10, 560); pts.forEach(p => c.lineTo(p[0], p[1])); c.lineTo(WX1 + 10, 560); c.closePath(); c.fill(); c.lineWidth = 3.5; c.beginPath(); pts.forEach((p, j) => j ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); });
106
+ // 鸢尾:白色旗瓣+紫/粉垂瓣,叶片剑形
107
+ [[456, 452, '#b27ec4', 0], [580, 462, '#e3a0b0', 1.7], [704, 452, '#a777c0', 3.1]].forEach(([x, y, fc, ph]) => {
108
+ const sw = Math.sin(t * 5 + ph) * 4;
109
+ c.fillStyle = '#6aa860'; c.lineWidth = 2.5;
110
+ [[-34, -40], [30, -46], [-14, -60], [46, -20]].forEach(([dx, dy], j) => { const p = ribbon(dense([[x + dx * 0.2, 560], [x + dx * 0.6 + sw * 0.5, y + 50], [x + dx + sw, y + dy + 40]], 8), q => 14 * (1 - q) + 1); c.fill(p); c.stroke(p); });
111
+ c.strokeStyle = C.line; c.lineWidth = 6; c.beginPath(); c.moveTo(x, 560); c.quadraticCurveTo(x + sw * 0.5, y + 50, x + sw, y); c.stroke(); c.strokeStyle = '#6aa860'; c.lineWidth = 3; c.stroke(); c.strokeStyle = C.line;
112
+ const fx = x + sw;
113
+ [-1, 1].forEach(s => { c.fillStyle = fc; c.lineWidth = 3; c.beginPath(); c.ellipse(fx + s * 26, y + 12, 26, 17, s * 0.5, 0, TAU); c.fill(); c.stroke(); });
114
+ c.fillStyle = '#f5f0e4'; c.beginPath(); c.moveTo(fx, y + 6); c.bezierCurveTo(fx - 22, y - 14, fx - 14, y - 50, fx, y - 66); c.bezierCurveTo(fx + 14, y - 50, fx + 22, y - 14, fx, y + 6); c.fill(); c.stroke();
115
+ });
116
+ c.restore();
117
+ // 彩色小玻璃边带(沿拱形内缘,一格一色)
118
+ const pts = archPts(22); let acc = 0, k2 = 0; const cols = ['#3f7fa0', '#e0b040', '#6fb0a0', '#e9e0c4', '#3f7fa0', '#d9a03a'];
119
+ c.lineWidth = 1.5; c.strokeStyle = C.line;
120
+ for (let i = 1; i < pts.length; i++) { acc += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); if (acc >= 17) { acc = 0; const a = Math.atan2(pts[i][1] - pts[i - 1][1], pts[i][0] - pts[i - 1][0]); c.save(); c.translate(pts[i][0], pts[i][1]); c.rotate(a); c.fillStyle = cols[k2++ % cols.length]; c.fillRect(-7, -7, 14, 14); c.strokeRect(-7, -7, 14, 14); c.restore(); } }
121
+ // 木拱框+窗台
122
+ c.lineWidth = 16; c.strokeStyle = '#6b4a2e'; c.stroke(archPath(8)); c.lineWidth = 3; c.strokeStyle = C.line; c.stroke(archPath(0)); c.stroke(archPath(15)); c.stroke(archPath(30));
123
+ c.fillStyle = '#6b4a2e'; c.fillRect(348, 540, 466, 22); c.strokeRect(348, 540, 466, 22);
124
+ }
125
+
126
+ // ---------- 两侧花:罂粟(左)、百合(右),摇摆 ----------
127
+ const poppyHead = (c, x, y, r, rot) => {
128
+ c.save(); c.translate(x, y); c.rotate(rot);
129
+ for (let i = 0; i < 5; i++) { const a = i / 5 * TAU; c.save(); c.rotate(a); c.fillStyle = i % 2 ? C.poppy : '#df7d74';
130
+ c.beginPath(); c.moveTo(0, 0); c.bezierCurveTo(r * 0.9, -r * 0.55, r * 1.12, r * 0.25, r * 0.4, r * 0.62); c.closePath(); c.fill(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke(); c.restore(); }
131
+ c.strokeStyle = C.poppyD; c.lineWidth = 2; for (let i = 0; i < 10; i++) { const a = i / 10 * TAU + 0.3; c.beginPath(); c.moveTo(Math.cos(a) * r * 0.3, Math.sin(a) * r * 0.3); c.lineTo(Math.cos(a) * r * 0.75, Math.sin(a) * r * 0.75); c.stroke(); }
132
+ c.fillStyle = '#3f6a4a'; c.beginPath(); c.arc(0, 0, r * 0.24, 0, TAU); c.fill(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke();
133
+ c.fillStyle = '#e2b84a'; for (let i = 0; i < 10; i++) { const a = i / 10 * TAU; c.beginPath(); c.arc(Math.cos(a) * r * 0.36, Math.sin(a) * r * 0.36, 3, 0, TAU); c.fill(); }
134
+ c.restore();
135
+ };
136
+ const lilyHead = (c, x, y, r, rot) => {
137
+ c.save(); c.translate(x, y); c.rotate(rot); c.lineWidth = 2.5; c.strokeStyle = C.line;
138
+ for (let i = 0; i < 6; i++) { c.save(); c.rotate(i / 6 * TAU + (i % 2) * 0.1); const rr = i % 2 ? r * 0.85 : r;
139
+ c.fillStyle = C.lily; c.beginPath(); c.moveTo(0, 0); c.quadraticCurveTo(rr * 0.35, -rr * 0.28, rr, 0); c.quadraticCurveTo(rr * 0.35, rr * 0.28, 0, 0); c.fill(); c.stroke();
140
+ c.strokeStyle = '#b9a98a'; c.lineWidth = 1.2; c.beginPath(); c.moveTo(rr * 0.15, 0); c.lineTo(rr * 0.8, 0); c.stroke(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.restore(); }
141
+ c.strokeStyle = '#a07a2a'; c.lineWidth = 1.5; for (let i = 0; i < 5; i++) { const a = -1.2 + i * 0.6; c.beginPath(); c.moveTo(0, 0); c.lineTo(Math.cos(a) * r * 0.42, Math.sin(a) * r * 0.42); c.stroke(); c.fillStyle = '#d99a2a'; c.beginPath(); c.arc(Math.cos(a) * r * 0.42, Math.sin(a) * r * 0.42, 3, 0, TAU); c.fill(); }
142
+ c.restore();
143
+ };
144
+ // 茎:从根部到花头的鞭线,sway 只推动上段
145
+ const stem = (c, base, head, bend, w = 9) => { const pts = dense([base, [lerp(base[0], head[0], 0.35) + bend, lerp(base[1], head[1], 0.35)], [lerp(base[0], head[0], 0.7) - bend * 0.6, lerp(base[1], head[1], 0.7)], head], 8);
146
+ c.fillStyle = C.line; c.fill(ribbon(pts, q => w + 3 - q * 3)); c.fillStyle = C.stem; c.fill(ribbon(pts, q => w - 1 - q * 3)); };
147
+ const blade = (c, base, tip, bend, w = 34) => { const pts = dense([base, [lerp(base[0], tip[0], 0.45) + bend, lerp(base[1], tip[1], 0.45)], tip], 10);
148
+ const p = ribbon(pts, q => w * Math.sin(Math.PI * Math.min(1, 0.12 + q * 0.88)) + 1); c.fillStyle = C.leaf; c.fill(p); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke(p);
149
+ c.strokeStyle = C.leafD; c.lineWidth = 2; c.beginPath(); pts.slice(2, -3).forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.stroke(); };
150
+ function flowers(c, t) {
151
+ const sw = (ph, amp = 1) => (Math.sin(t * 7.5 + ph) * 13 + Math.sin(t * 3.1 + ph * 2) * 5) * amp;
152
+ // 左:罂粟
153
+ blade(c, [70, 900], [40, 640], 30 + sw(0.4, 0.3)); blade(c, [120, 900], [190, 690], -20 + sw(1.4, 0.3), 28); blade(c, [200, 900], [300, 640], -30 + sw(2.2, 0.3), 30);
154
+ const ps = [[[100, 900], [110, 215], 66, 0], [[150, 900], [234, 372], 62, 1.3], [[180, 900], [162, 495], 52, 2.6]];
155
+ ps.forEach(([b, h, r, ph]) => { const d = sw(ph); stem(c, b, [h[0] + d, h[1] + Math.abs(d) * 0.2], 40 - ph * 20); });
156
+ // 花苞
157
+ { const d = sw(3.5); stem(c, [230, 900], [272 + d, 222], -40, 8); c.fillStyle = '#9fc3a0'; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(272 + d, 205, 24, 28, 0.2, 0, TAU); c.fill(); c.stroke();
158
+ c.beginPath(); c.arc(240 + d * 0.8, 222, 12, 0, TAU); c.stroke(); }
159
+ ps.forEach(([b, h, r, ph]) => { const d = sw(ph); poppyHead(c, h[0] + d, h[1] + Math.abs(d) * 0.2, r, d * 0.012 + ph); });
160
+ // 右:百合
161
+ blade(c, [1690, 900], [1600, 640], 40 + sw(5, 0.3), 30); blade(c, [1760, 900], [1830, 600], -20 + sw(6, 0.3), 26); blade(c, [1830, 900], [1880, 720], -10 + sw(4.4, 0.3), 24); blade(c, [1720, 900], [1660, 760], 30, 22);
162
+ const ls = [[[1730, 900], [1712, 300], 82, 4.0], [[1700, 900], [1652, 520], 62, 5.1], [[1800, 900], [1822, 470], 64, 6.3]];
163
+ ls.forEach(([b, h, r, ph]) => { const d = -sw(ph); stem(c, b, [h[0] + d, h[1]], -30 + (ph - 5) * 30, 8); });
164
+ { const d = -sw(7); stem(c, [1820, 900], [1838 + d, 360], 20, 7); c.fillStyle = C.lily; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(1840 + d, 330, 11, 34, 0.1, 0, TAU); c.fill(); c.stroke(); }
165
+ ls.forEach(([b, h, r, ph]) => { const d = -sw(ph); lilyHead(c, h[0] + d, h[1], r, -0.3 + d * 0.01 + ph); });
166
+ }
167
+
168
+ // ---------- 光环(缓慢转动) ----------
169
+ function girlHalo(c, cx, cy, t) {
170
+ const R = 172, rot = t * 0.9;
171
+ c.save(); c.lineWidth = 3; c.strokeStyle = C.line;
172
+ c.fillStyle = C.gold; c.beginPath(); c.arc(cx, cy, R, 0, TAU); c.fill(); c.stroke();
173
+ // 金环上的圆钉(转动)
174
+ for (let i = 0; i < 18; i++) { const a = rot + i / 18 * TAU; const x = cx + Math.cos(a) * (R - 13), y = cy + Math.sin(a) * (R - 13);
175
+ c.fillStyle = C.cream; c.beginPath(); c.arc(x, y, 7.5, 0, TAU); c.fill(); c.lineWidth = 2; c.stroke(); c.fillStyle = '#c0606a'; c.beginPath(); c.arc(x, y, 3, 0, TAU); c.fill(); }
176
+ c.lineWidth = 3; c.fillStyle = '#5f9a8a'; c.beginPath(); c.arc(cx, cy, R - 26, 0, TAU); c.fill(); c.stroke();
177
+ c.fillStyle = '#ebb8b2'; c.beginPath(); c.arc(cx, cy, R - 34, 0, TAU); c.fill(); c.stroke();
178
+ // 玫瑰窗花瓣(反向转)
179
+ c.save(); c.translate(cx, cy); c.rotate(-rot * 0.7); c.strokeStyle = '#c98a86'; c.lineWidth = 2;
180
+ for (let ring = 0; ring < 2; ring++) for (let i = 0; i < 16; i++) { c.save(); c.rotate(i / 16 * TAU + ring * TAU / 32); const r0 = ring ? 40 : 70, r1 = ring ? 95 : 132;
181
+ c.fillStyle = ring ? '#e3a5a0' : '#efc4be'; c.beginPath(); c.moveTo(r0, 0); c.quadraticCurveTo((r0 + r1) / 2, -20 + ring * 6, r1, 0); c.quadraticCurveTo((r0 + r1) / 2, 20 - ring * 6, r0, 0); c.fill(); c.stroke(); c.restore(); }
182
+ c.restore(); c.restore();
183
+ }
184
+ function catHalo(c, cx, cy, t) {
185
+ const R = 106, rot = -t * 1.1;
186
+ c.save(); c.strokeStyle = C.line; c.lineWidth = 3;
187
+ c.fillStyle = C.gold; c.beginPath(); c.arc(cx, cy, R, 0, TAU); c.fill(); c.stroke();
188
+ c.setLineDash([10, 8]); c.lineDashOffset = rot * 40; c.strokeStyle = C.goldD; c.lineWidth = 6; c.beginPath(); c.arc(cx, cy, R - 8, 0, TAU); c.stroke(); c.setLineDash([]);
189
+ c.strokeStyle = C.line; c.lineWidth = 3; c.fillStyle = '#b8473a'; c.beginPath(); c.arc(cx, cy, R - 16, 0, TAU); c.fill(); c.stroke();
190
+ c.save(); c.translate(cx, cy); c.rotate(rot);
191
+ for (let i = 0; i < 8; i++) { c.save(); c.rotate(i / 8 * TAU); c.fillStyle = '#e8924a'; c.beginPath(); c.moveTo(14, 0); c.quadraticCurveTo(48, -20, 84, 0); c.quadraticCurveTo(48, 20, 14, 0); c.fill(); c.lineWidth = 2; c.stroke(); c.restore(); }
192
+ c.fillStyle = '#e0b84a'; c.beginPath(); c.arc(0, 0, 18, 0, TAU); c.fill(); c.stroke(); c.restore(); c.restore();
193
+ }
194
+
195
+ // ---------- 桌(绿铁艺卷脚)、椅、碟 ----------
196
+ function table(c) {
197
+ const iron = (pts, w) => { const d = dense(pts, 10); c.fillStyle = C.line; c.fill(ribbon(d, q => w + 4)); c.fillStyle = C.iron; c.fill(ribbon(d, q => w)); };
198
+ iron([[1004, 660], [1004, 760], [1006, 890]], 16);
199
+ iron([[1000, 672], [930, 682], [870, 690], [848, 712], [862, 736], [886, 730], [884, 712]], 10);
200
+ iron([[1010, 672], [1080, 682], [1140, 690], [1162, 712], [1148, 736], [1124, 730], [1126, 712]], 10);
201
+ iron([[1000, 760], [960, 790], [930, 800], [938, 828], [960, 822]], 9); iron([[1010, 760], [1050, 790], [1080, 800], [1072, 828], [1050, 822]], 9);
202
+ iron([[1004, 860], [940, 872], [860, 880], [842, 868], [852, 852], [866, 860]], 10); iron([[1006, 860], [1070, 872], [1150, 880], [1168, 868], [1158, 852], [1144, 860]], 10);
203
+ c.fillStyle = C.ironHi; c.fillRect(1001, 670, 4, 200);
204
+ // 桌面
205
+ c.fillStyle = '#f3ead8'; c.strokeStyle = C.line; c.lineWidth = 3.5; c.beginPath(); c.moveTo(812, 640); c.lineTo(1208, 640); c.lineTo(1196, 660); c.lineTo(824, 660); c.closePath(); c.fill(); c.stroke();
206
+ c.fillStyle = '#d9cdb2'; c.fillRect(826, 655, 368, 4);
207
+ }
208
+ function chair(c) {
209
+ c.fillStyle = C.line; c.fill(ribbon(dense([[1468, 900], [1478, 760], [1470, 600], [1486, 470], [1500, 450]], 8), q => 22 - q * 6));
210
+ c.fillStyle = C.wood; c.fill(ribbon(dense([[1468, 900], [1478, 760], [1470, 600], [1486, 470], [1500, 450]], 8), q => 15 - q * 6));
211
+ }
212
+
213
+ // ---------- 少女的长卷发:发团(波浪外缘)+ 一绺绺可变线宽的 S 形卷发,末端螺旋 ----------
214
+ function hairMass(c, G, t) {
215
+ const h = G.A.headC, ox = h[0] - 1318, oy = h[1] - 360;
216
+ const pts = [[1286, 300], [1350, 286], [1404, 318], [1432, 380], [1462, 440], [1496, 510], [1520, 590], [1530, 660], [1512, 730], [1470, 760], [1440, 712], [1420, 640], [1396, 560], [1368, 490], [1340, 440], [1318, 410]]
217
+ .map(([x, y], i) => { const w = i > 2 && i < 10 ? Math.sin(t * 6 + y * 0.035) * 9 * (y - 300) / 400 : 0; return [x + ox * (1 - (y - 300) / 500) + w, y + oy * (1 - (y - 300) / 500)]; });
218
+ const p = RIG.smooth(pts, true); c.fillStyle = '#e2b54c'; c.fill(p);
219
+ }
220
+ const lockDefs = {
221
+ back: [[1392, 360, 330, 120, 18, 0.2, 1], [1420, 400, 300, 150, 16, 1.4, -1], [1440, 450, 290, 105, 17, 2.5, 1], [1404, 430, 330, 60, 18, 3.3, -1], [1460, 520, 220, 120, 15, 4.2, 1], [1376, 470, 300, 30, 16, 5.0, 1], [1470, 600, 160, 70, 14, 0.9, -1]],
222
+ front: [[1316, 420, 170, -24, 15, 2.0, -1], [1334, 430, 140, 4, 13, 3.6, 1]],
223
+ };
224
+ const hairLocks = (G, t, which) => {
225
+ const h = G.A.headC, ox = h[0] - 1318, oy = h[1] - 360;
226
+ return lockDefs[which].map(([sx, sy, len, drift, w, ph, dir]) => {
227
+ const pts = []; for (let i = 0; i <= 12; i++) { const q = i / 12; const wave = Math.sin(q * 7.4 + ph - t * 7) * (6 + 20 * q);
228
+ pts.push([sx + ox * (1 - q) + drift * q + wave, sy + oy * (1 - q) + len * q]); }
229
+ const e = pts[pts.length - 1], a0 = Math.atan2(pts[12][1] - pts[11][1], pts[12][0] - pts[11][0]);
230
+ const r = 13 + w * 0.5, cx = e[0] + Math.cos(a0 + dir * Math.PI / 2) * r, cy = e[1] + Math.sin(a0 + dir * Math.PI / 2) * r;
231
+ const sp = spiral(cx, cy, r, a0 - dir * Math.PI / 2, 1.2, dir, 16);
232
+ return { pts: dense(pts.concat(sp.slice(1)), 4), w };
233
+ });
234
+ };
235
+ function drawHair(c, locks) {
236
+ locks.forEach(({ pts, w }) => { const rb = ribbon(pts, q => 2.5 + (w - 2.5) * Math.pow(1 - q, 0.6));
237
+ c.fillStyle = '#e8bf55'; c.fill(rb); c.strokeStyle = C.line; c.lineWidth = 2.2; c.stroke(rb);
238
+ const mid = pts.slice(3, -6); c.fillStyle = '#f6dc8a'; c.fill(ribbon(mid, q => 1 + 2.4 * (1 - q)));
239
+ c.strokeStyle = '#b9862a'; c.lineWidth = 1.2; c.beginPath(); mid.forEach((p, i) => i ? c.lineTo(p[0] + 3, p[1] + 2) : c.moveTo(p[0] + 3, p[1] + 2)); c.stroke(); });
240
+ }
241
+ // 头顶外缘的小卷(让头发有体积,像设定稿那样蓬)
242
+ function crownCurls(c, G, t) {
243
+ const h = G.A.headC;
244
+ [[-20, -66, 15, 1], [22, -64, 17, -1], [58, -40, 18, 1], [80, -2, 17, -1], [88, 38, 16, 1], [70, 70, 15, -1]].forEach(([dx, dy, r, dir], k) => {
245
+ const cx = h[0] + dx + Math.sin(t * 6 + k) * 2, cy = h[1] + dy;
246
+ const sp = dense(spiral(cx, cy, r, k * 1.3 + t * 2 * dir, 1.15, dir, 18), 2), rb = ribbon(sp, q => 9 * (1 - q) + 2);
247
+ c.fillStyle = '#e8bf55'; c.fill(rb); c.strokeStyle = C.line; c.lineWidth = 1.8; c.stroke(rb);
248
+ });
249
+ }
250
+ // 花环:沿额头—头顶—后脑的弧线排 5 朵粉花
251
+ function wreath(c, G, t) {
252
+ const a = G.A, p0 = a.forehead, p1 = [a.headTop[0] + 6, a.headTop[1] - 14], p2 = [a.nape[0] + 14, a.nape[1] - 70];
253
+ const at = q => [(1 - q) * (1 - q) * p0[0] + 2 * q * (1 - q) * p1[0] + q * q * p2[0], (1 - q) * (1 - q) * p0[1] + 2 * q * (1 - q) * p1[1] + q * q * p2[1]];
254
+ c.strokeStyle = C.gold; c.lineWidth = 5; c.beginPath(); for (let i = 0; i <= 20; i++) { const p = at(i / 20); i ? c.lineTo(p[0], p[1] + 8) : c.moveTo(p[0], p[1] + 8); } c.stroke();
255
+ [0.22, 0.42, 0.6, 0.78, 0.95].forEach((q, k) => { const [x, y] = at(q), r = k % 2 ? 15 : 19, rot = t * 0.8 + k;
256
+ c.fillStyle = '#7fa86a'; c.strokeStyle = C.line; c.lineWidth = 1.8; [-1, 1].forEach(s => { c.beginPath(); c.ellipse(x + s * r * 0.9, y + r * 0.5, r * 0.6, r * 0.28, s * 0.6, 0, TAU); c.fill(); c.stroke(); });
257
+ c.save(); c.translate(x, y); c.rotate(rot); for (let i = 0; i < 5; i++) { c.rotate(TAU / 5); c.fillStyle = k % 2 ? '#e7a0aa' : '#f0b4bb'; c.beginPath(); c.ellipse(r * 0.55, 0, r * 0.55, r * 0.4, 0, 0, TAU); c.fill(); c.stroke(); }
258
+ c.fillStyle = '#e0b04a'; c.beginPath(); c.arc(0, 0, r * 0.3, 0, TAU); c.fill(); c.stroke(); c.restore(); });
259
+ }
260
+ // S 形装饰热气:奶白可变宽带+棕双线,顶端螺旋,整体随时间卷动
261
+ function steam(c, x, y, t, cup) {
262
+ const H0 = 360 * (1 - 0.25 * cup), lean = -90 - 160 * cup;
263
+ const defs = [[-6, 0, 1, 26], [22, 2.3, -1, 20], [-26, 4.2, 1, 14]];
264
+ defs.forEach(([ox, ph, dir, w], k) => {
265
+ const pts = []; for (let i = 0; i <= 10; i++) { const q = i / 10; pts.push([x + ox * (1 + 2.6 * q) + Math.sin(q * 4.0 + ph - t * 6) * (18 + 70 * q) * dir + lean * q * q, y - q * H0 * (1 - k * 0.12)]); }
266
+ const e = pts[pts.length - 1]; const sp = spiral(e[0] + dir * 32, e[1] + 4, 34, dir > 0 ? Math.PI : 0, 1.1, dir > 0 ? 1 : -1, 14).map(([px, py], i) => [px, py]);
267
+ const all = dense(pts.concat(sp.slice(1)), 5), rb = ribbon(all, q => 2 + (w - 2) * Math.sin(Math.PI * Math.min(1, 0.15 + q * 1.1)));
268
+ c.globalAlpha = 0.95; c.fillStyle = '#fbf7ec'; c.fill(rb); c.strokeStyle = '#8a6a4a'; c.lineWidth = 1.8; c.stroke(rb);
269
+ c.strokeStyle = 'rgba(160,130,100,.6)'; c.lineWidth = 1.2; c.beginPath(); all.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); c.globalAlpha = 1;
270
+ });
271
+ }
272
+
273
+ const girlPal = { skin: '#f6dcc4', hair: '#e8bf55', hairLine: '#b9862a', dress: '#f4ecda', sleeve: '#f6dcc4', apron: '#e6aaa5', cuff: '#c9a24a', fold: '#c9b89a', shoe: '#c9a24a',
274
+ cheek: 'rgba(235,130,130,.45)', lip: '#c8505a', iris: '#4a6a3a', browC: '#9a6a2a', line: C.line, lw: 2.2, cupBody: '#f8f1e2', cupRim: '#7a4a24' };
275
+ const catPal = { orange: '#e39a4c', white: '#fbf3e3', stripe: '#b8622a', eye: '#e6c040', earInner: '#eaa0a0', line: C.line, lw: 2.2 };
276
+
277
+ return {
278
+ draw(c, lt, t) {
279
+ const ch = P.choreo(lt, t);
280
+ c.drawImage(bg(), 0, 0);
281
+ windowGlass(c, lt, t);
282
+ flowers(c, t);
283
+ catHalo(c, 606, 640, t);
284
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, hair: 'long', breathe: ch.breathe });
285
+ girlHalo(c, 1342, 332, t);
286
+ chair(c);
287
+ table(c);
288
+ // 碟(杯子举起后仍在桌上)
289
+ c.fillStyle = '#f6efe0'; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(992, 634, 58, 9, 0, 0, TAU); c.fill(); c.stroke();
290
+ // —— 角色层 ——
291
+ const L = P.scratch('nvChars'), lg = L.getContext('2d'); lg.clearRect(0, 0, W, H);
292
+ const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
293
+ RIG.drawCat(lg, K, { ...catPal, lw: 2 });
294
+ hairMass(lg, G, t); drawHair(lg, hairLocks(G, t, 'back'));
295
+ RIG.drawGirl(lg, G, { ...girlPal, lw: 2 });
296
+ // 短袖:上臂靠肩一段盖奶白
297
+ lg.save(); lg.beginPath(); lg.arc(G.A.shoulder[0], G.A.shoulder[1], 62, 0, TAU); lg.clip(); lg.fillStyle = '#f4ecda'; lg.fill(G.upperArm); lg.strokeStyle = C.line; lg.lineWidth = 2; lg.stroke(G.upperArm); lg.restore();
298
+ // 粉色披帛:从肩头沿背垂到腰后
299
+ { const a = G.A; const d = dense([[a.shoulder[0] + 6, a.shoulder[1] - 18], [a.back[0] - 4, a.back[1] - 30], [a.back[0] + 14, a.back[1] + 50], [a.waist[0] + 80, a.waist[1] + 40], [a.waist[0] + 96, a.waist[1] + 120]], 10);
300
+ const dr = ribbon(d, q => 30 + 26 * q); lg.fillStyle = '#e6aaa5'; lg.fill(dr); lg.strokeStyle = C.line; lg.lineWidth = 2; lg.stroke(dr); lg.fillStyle = '#d68f8c'; lg.fill(ribbon(d.slice(4), q => 4 + 6 * q)); }
301
+ // 粉色下摆的褶(深粉弧带)
302
+ lg.save(); lg.clip(G.apronSkirt); lg.fillStyle = '#d48e8c';
303
+ [[[1290, 640], [1250, 720], [1236, 830]], [[1250, 650], [1214, 740], [1206, 830]], [[1320, 680], [1296, 760], [1290, 830]]].forEach(pts => lg.fill(ribbon(dense(pts, 8), swellW(7, 1))));
304
+ lg.restore();
305
+ // 裙摆金色锯齿饰边
306
+ lg.save(); lg.clip(G.skirt); lg.lineJoin = 'miter';
307
+ { const zz = (y0, w, col) => { lg.strokeStyle = col; lg.lineWidth = w; lg.beginPath(); for (let k = 0, x = 1080; x <= 1380; x += 28, k++) { const y = y0 + (x - 1080) * 0.02 + (k % 2 ? 30 : 0); k ? lg.lineTo(x, y) : lg.moveTo(x, y); } lg.stroke(); };
308
+ zz(850, 13, C.line); zz(850, 8, C.gold); lg.fillStyle = C.cream; for (let k = 0, x = 1080; x <= 1380; x += 56, k++) { lg.beginPath(); lg.arc(x, 850 + (x - 1080) * 0.02 - 2, 4, 0, TAU); lg.fill(); } }
309
+ lg.restore();
310
+ // 猫的弧形虎斑(可变线宽)
311
+ lg.fillStyle = '#c0672c';
312
+ [[[450, 735], [470, 728], [500, 738]], [[436, 772], [462, 764], [496, 776]], [[430, 812], [458, 806], [490, 818]], [[432, 850], [458, 846], [486, 858]], [[520, 700], [536, 690], [556, 694]], [[580, 572], [586, 586], [590, 600]], [[604, 566], [606, 582], [606, 598]], [[626, 570], [622, 584], [620, 598]]]
313
+ .forEach(pts => { const d = dense(pts, 8); lg.fill(ribbon(d, swellW(9, 1))); });
314
+ // 金腰带+胸针
315
+ lg.fillStyle = C.gold; lg.save(); lg.clip(G.torso); lg.fillRect(G.A.waist[0] - 140, G.A.waist[1] - 22, 200, 16); lg.restore();
316
+ lg.beginPath(); lg.arc(G.A.waist[0] + 50, G.A.waist[1] - 14, 13, 0, TAU); lg.fill();
317
+ // 粗外轮廓:剪影染深棕,往右下错位垫在下面
318
+ const S = P.scratch('nvSil'), sg = S.getContext('2d'); sg.clearRect(0, 0, W, H); sg.drawImage(L, 0, 0); sg.globalCompositeOperation = 'source-in'; sg.fillStyle = C.line; sg.fillRect(0, 0, W, H); sg.globalCompositeOperation = 'source-over';
319
+ [[-1.5, -1.5], [3.5, 4.5], [2, 2.5]].forEach(([dx, dy]) => c.drawImage(S, dx, dy));
320
+ c.drawImage(L, 0, 0);
321
+ // 金臂环、耳坠
322
+ c.strokeStyle = C.gold; c.lineWidth = 6; c.beginPath(); c.arc(G.A.shoulder[0] - 6, G.A.shoulder[1] + 40, 22, 0.6, 2.4); c.stroke();
323
+ c.fillStyle = C.gold; c.strokeStyle = C.line; c.lineWidth = 2; c.beginPath(); c.arc(G.A.ear[0] - 4, G.A.ear[1] + 22, 8, 0, TAU); c.fill(); c.stroke();
324
+ // 肩前的发卷、花环
325
+ drawHair(c, hairLocks(G, t, 'front'));
326
+ crownCurls(c, G, t);
327
+ wreath(c, G, t);
328
+ // 杯上的点饰
329
+ c.fillStyle = '#d07a86'; [-12, 0, 12].forEach((d, i) => { c.fillStyle = i === 1 ? '#6fa08a' : '#d07a86'; c.beginPath(); c.arc(G.cup.x + d, G.cup.y + 20, 3.5, 0, TAU); c.fill(); });
330
+ steam(c, G.cup.x, G.cup.y - 8, t, ch.cup);
331
+ c.drawImage(P.grain('nv', 0.05, [70, 50, 30], 0.14), 0, 0);
332
+ },
333
+ };
334
+ })();