@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,86 @@
1
+ // 雷诺阿(印象派,人间的光):树叶筛下的斑驳光——暖黄的亮斑、蓝紫的影;人都是一团团粉彩的色点,脸颊泛红,边缘融进周围;没有黑线,黑色衣服也是深蓝紫。
2
+ // 题材(新构图):午后树下的露天舞场,近处一对舞者旋转,中景桌边人群说笑,光斑缓缓挪动。
3
+ // 原作锚定(风格,非原画):亮斑 #f6e4a0/影 #6f78b8、肤色暖粉 #eab090 带红晕、衣料粉蓝白 #e8c8d0 #a8b8dc #f4f0e4、黑衣=#2c3050、草帽黄 #e6c070、地面碎笔多向;所有边缘被光吃掉。
4
+ // 管线:静底(树冠+地+远景人群+桌)→ MEDIUM.broken → 缓存;光斑层(暖亮/冷影软笔)单独 broken 缓存、两份错位叠加做摇曳;近景舞者每帧小画布重绘;中景人群每个轻微摇摆(小画布)。
5
+ SCENES['45_renoir'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
7
+ let base, crowdLayer, lightA, lightB, built = false;
8
+ // 一个人(色点化):x,y 脚底;s 比例。女:窄肩、收腰、钟形长裙、小头戴帽;男:深蓝紫礼服、礼帽。reach=[dx,dy] 局部坐标,指定一只手去哪(相拥舞)
9
+ function person(g, x, y, s, o) {
10
+ const { dress = '#e8c8d0', trim = '#f4f0e4', hat = null, skin = '#eab090', lean = 0, arms = 0, dark = false, hair = '#8a5a3a', reach = null } = o;
11
+ g.save(); g.translate(x, y); g.scale(s, s); g.rotate(lean);
12
+ const body = dark ? '#2c3050' : dress, lit = dark ? '#4a4f82' : trim, sh = dark ? '#1c1f38' : 'rgba(110,100,150,.45)';
13
+ // 裙/下装:女=钟形长裙(左亮右影),男=直筒裤袍
14
+ if (dark) { g.fillStyle = body; g.beginPath(); g.moveTo(-15, -72); g.lineTo(-17, -4); g.lineTo(-3, -4); g.lineTo(0, -34); g.lineTo(3, -4); g.lineTo(17, -4); g.lineTo(15, -72); g.closePath(); g.fill();
15
+ g.fillStyle = body; g.beginPath(); g.moveTo(-15, -108); g.quadraticCurveTo(-21, -96, -17, -52); g.lineTo(17, -52); g.quadraticCurveTo(21, -96, 15, -108); g.closePath(); g.fill(); g.fillStyle = lit; g.globalAlpha = 0.5; g.fillRect(-15, -108, 6, 54); g.globalAlpha = 1;
16
+ g.fillStyle = '#e8e0ea'; g.beginPath(); g.moveTo(-4, -108); g.lineTo(4, -108); g.lineTo(1, -80); g.closePath(); g.fill(); }
17
+ else { const gr = g.createLinearGradient(-40, 0, 40, 0); gr.addColorStop(0, trim); gr.addColorStop(0.4, body); gr.addColorStop(1, sh);
18
+ g.fillStyle = gr; g.beginPath(); g.moveTo(-10, -104); g.quadraticCurveTo(-13, -84, -9, -68); g.quadraticCurveTo(-34, -34, -44, -3); g.quadraticCurveTo(0, 6, 44, -3); g.quadraticCurveTo(34, -34, 9, -68); g.quadraticCurveTo(13, -84, 10, -104); g.closePath(); g.fill();
19
+ g.strokeStyle = sh; g.lineWidth = 3; g.globalAlpha = 0.5; [[-10, -22], [6, -20], [20, -18]].forEach(([u, v]) => { g.beginPath(); g.moveTo(u * 0.3, -66); g.quadraticCurveTo(u + 4, -36, u * 1.7 + 4, v); g.stroke(); }); g.globalAlpha = 1;
20
+ g.fillStyle = trim; g.fillRect(-10, -72, 20, 5); g.fillStyle = '#c85a6a'; g.fillRect(-10, -70, 20, 2); }
21
+ // 手臂:上臂下垂,前臂抬到 arms 指定高度;reach 时一只手伸向目标
22
+ const sl = dark ? body : (dress), hx = 20 + arms * 12, hy = -70 - arms * 18;
23
+ g.strokeStyle = sl; g.lineCap = 'round'; g.lineWidth = 8; g.beginPath(); g.moveTo(-10, -100); g.lineTo(-20 - arms * 4, -80); g.lineTo(-hx, hy);
24
+ if (reach) { g.moveTo(10, -100); g.lineTo(22, -84); g.lineTo(reach[0], reach[1]); } else { g.moveTo(10, -100); g.lineTo(20 + arms * 4, -80); g.lineTo(hx, hy); } g.stroke();
25
+ g.fillStyle = skin; g.beginPath(); g.arc(-hx, hy, 4.2, 0, TAU); g.arc(reach ? reach[0] : hx, reach ? reach[1] : hy, 4.2, 0, TAU); g.fill();
26
+ // 颈、头(小)、脸颊红晕、发
27
+ g.fillStyle = skin; g.fillRect(-3.4, -117, 6.8, 11); g.beginPath(); g.ellipse(0, -126, 9.4, 11, 0, 0, TAU); g.fill();
28
+ g.fillStyle = 'rgba(214,90,84,.38)'; g.beginPath(); g.arc(4.5, -122, 4.4, 0, TAU); g.fill();
29
+ g.fillStyle = hair; g.beginPath(); g.ellipse(-1.5, -130, 9.6, 8, 0, Math.PI * 0.95, Math.PI * 2.05); g.fill(); if (!dark) { g.beginPath(); g.arc(-8, -122, 5, 0, TAU); g.fill(); }
30
+ g.fillStyle = '#3a2a30'; g.fillRect(4, -127, 1.8, 1.8); g.fillStyle = '#b05a54'; g.fillRect(3, -120, 4.2, 1.4);
31
+ if (hat === 'straw') { g.fillStyle = '#e6c070'; g.beginPath(); g.ellipse(0, -136, 21, 5.6, 0, 0, TAU); g.fill(); g.beginPath(); g.ellipse(0, -140, 11, 8, 0, Math.PI, TAU); g.fill(); g.fillStyle = '#c85a6a'; g.fillRect(-11, -140, 22, 3.4); }
32
+ else if (hat === 'top') { g.fillStyle = '#2c3050'; g.fillRect(-9, -153, 18, 19); g.fillRect(-14, -136, 28, 3.6); }
33
+ g.restore();
34
+ }
35
+ function basePlate() {
36
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(5);
37
+ // 天光漏口+树冠:大片深绿蓝影+暖绿
38
+ g.fillStyle = '#9db8a0'; g.fillRect(0, 0, W, H);
39
+ const gr = g.createLinearGradient(0, 0, 0, 460); gr.addColorStop(0, '#5f7f6a'); gr.addColorStop(1, '#a8b890'); g.fillStyle = gr; g.fillRect(0, 0, W, 460);
40
+ for (let i = 0; i < 160; i++) { const x = r() * W, y = r() * 420, rx = 70 + r() * 150, ry = 40 + r() * 80; g.fillStyle = r() < 0.5 ? 'rgba(54,86,70,.55)' : r() < 0.7 ? 'rgba(170,196,120,.5)' : 'rgba(236,224,150,.4)'; g.beginPath(); g.ellipse(x, y, rx, ry, r(), 0, TAU); g.fill(); }
41
+ for (let i = 0; i < 40; i++) { const x = r() * W, y = r() * 330, rx = 12 + r() * 34, ry = 8 + r() * 20; g.fillStyle = 'rgba(190,214,236,.8)'; g.beginPath(); g.ellipse(x, y, rx, ry, r(), 0, TAU); g.fill(); } // 叶隙里透出的天
42
+ // 树干
43
+ [[260, 470, 70], [1060, 450, 60], [1700, 470, 80]].forEach(([x, y, w]) => { const tg = g.createLinearGradient(x - w, 0, x + w, 0); tg.addColorStop(0, '#6f6a8a'); tg.addColorStop(0.5, '#a89a8a'); tg.addColorStop(1, '#5c5878'); g.fillStyle = tg; g.fillRect(x - w / 2, 0, w, y + 80); });
44
+ // 地:沙土色,明暗斑
45
+ const ggr = g.createLinearGradient(0, 470, 0, H); ggr.addColorStop(0, '#c8b890'); ggr.addColorStop(1, '#b0a07c'); g.fillStyle = ggr; g.fillRect(0, 470, W, H - 470);
46
+ // 桌与远景人群(色点人)
47
+ const T = (x, y, w) => { g.fillStyle = '#e8e0d0'; g.beginPath(); g.ellipse(x, y, w, w * 0.22, 0, 0, TAU); g.fill(); g.fillStyle = '#a89a8a'; g.fillRect(x - 3, y, 6, w * 0.5); };
48
+ [[560, 640, 120], [1180, 620, 110], [1480, 700, 130], [820, 560, 90]].forEach(([x, y, w]) => T(x, y, w));
49
+ const cv2 = P.canvas(), g2 = cv2.getContext('2d');
50
+ const crowd = [[430, 640, 0.9, '#a8b8dc', 'straw'], [520, 676, 0.95, '#e8c8d0', null], [620, 650, 0.9, '#f4f0e4', 'straw'], [700, 662, 0.9, '#2c3050', 'top'], [1090, 640, 0.85, '#e8c8d0', 'straw'], [1190, 650, 0.88, '#a8b8dc', null], [1280, 636, 0.85, '#2c3050', 'top'],
51
+ [1400, 720, 1.0, '#f0d6dc', 'straw'], [1500, 730, 1.02, '#2c3050', 'top'], [1590, 716, 1.0, '#a8c8dc', null], [780, 590, 0.7, '#e8c8d0', 'straw'], [880, 596, 0.7, '#a8b8dc', null], [330, 600, 0.75, '#f4f0e4', null]];
52
+ crowd.forEach(([x, y, s, c, h], i) => person(g2, x, y, s, { dress: c, hat: h, dark: c === '#2c3050', lean: Math.sin(i * 1.7) * 0.08, arms: (i % 3) * 0.25 }));
53
+ crowdLayer = M.broken(cv2, { cell: 4, len: 12, wid: 4.2, passes: 2, angle: (x, y) => 1.25 + Math.sin(x * 0.05) * 0.3, seed: 19, jit: 26, skip: 0.02, sat: 1.12, warm: [255, 226, 170], cool: [130, 130, 200] });
54
+ return M.broken(cv, { cell: 7, len: 22, wid: 7, passes: 2, angle: (x, y) => y < 460 ? 0.7 + Math.sin(x * 0.01 + y * 0.013) * 0.35 : 0.08 + Math.sin(x * 0.012 + y * 0.01) * 0.3, seed: 11, jit: 30, skip: 0.04, warm: [255, 226, 160], cool: [120, 126, 196], sat: 1.12 });
55
+ }
56
+ function lightPlate(seed) {
57
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(seed); g.clearRect(0, 0, W, H);
58
+ for (let i = 0; i < 90; i++) { const x = r() * W, y = 440 + r() * 600, rx = 26 + r() * 80, ry = 12 + r() * 30; g.fillStyle = r() < 0.62 ? 'rgba(252,232,150,.85)' : 'rgba(96,106,176,.6)'; g.beginPath(); g.ellipse(x, y, rx, ry, (r() - .5) * 0.6, 0, TAU); g.fill(); }
59
+ return M.broken(cv, { cell: 8, len: 24, wid: 8, passes: 1, angle: () => -0.3, seed: seed + 3, jit: 24, skip: 0.1, warm: [255, 238, 170], cool: [96, 110, 190], base: false });
60
+ }
61
+ function build() { base = basePlate(); lightA = lightPlate(21); lightB = lightPlate(47); built = true; }
62
+ // 近景舞者:小画布整体重绘(两人,随旋转交换位置)
63
+ function dancers(c, t) {
64
+ const cv = P.scratch('renoir_dance'); if (cv.width !== 760) { cv.width = 760; cv.height = 680; } const g = cv.getContext('2d'); g.reset(); g.clearRect(0, 0, 760, 680);
65
+ const a = t * 1.6, cx = 380, cy = 660, sway = Math.sin(a) * 0.05, S2 = 2.5;
66
+ const A = [cx + 18 + Math.sin(a) * 14, cy + Math.cos(a) * 6], B = [cx - 52 + Math.sin(a) * 6, cy - 10]; // 女在前、男在后左:圆舞里她旋出,他半步退后
67
+ const hold = [(A[0] + B[0]) / 2 + 40, A[1] - 205]; // 两人相握的手
68
+ const loc = (p, d) => [(hold[0] - p[0]) / S2 + d[0], (hold[1] - p[1]) / S2 + d[1]];
69
+ const pa = { dress: '#f4d4dc', trim: '#fff8ee', hat: 'straw', lean: 0.12 + sway, arms: 0.1, reach: loc(A, [0, 0]) }, pb = { dress: '#2c3050', dark: true, hat: 'top', lean: -0.06 + sway, arms: 0.1, skin: '#e2a888', hair: '#4a3a2a', reach: loc(B, [-10, 18]) };
70
+ person(g, B[0], B[1], S2, pb); person(g, A[0], A[1], S2, pa);
71
+ const rep = M.broken(cv, { cell: 5, len: 15, wid: 5, passes: 2, angle: (x, y) => 1.3 + Math.sin(x * 0.04) * 0.3, seed: 31, jit: 24, skip: 0.02, sat: 1.1, warm: [255, 226, 170], cool: [130, 130, 200] });
72
+ c.drawImage(rep, 420, 340);
73
+ }
74
+ function draw(c, lt) {
75
+ if (!built) build();
76
+ const t = clamp(lt, 0, 6);
77
+ c.drawImage(base, 0, 0);
78
+ const q = 0.5 + 0.5 * Math.sin(t * 1.3), lights = (k) => { c.save(); c.globalAlpha = k * (0.55 * (1 - q) + 0.1); c.drawImage(lightA, Math.sin(t * 0.7) * 10, Math.cos(t * 0.6) * 6); c.globalAlpha = k * (0.55 * q + 0.1); c.drawImage(lightB, -Math.sin(t * 0.6) * 10, Math.cos(t * 0.8) * 6); c.restore(); };
79
+ lights(1); // 光斑落在地上
80
+ c.drawImage(crowdLayer, 0, 0); // 人站在光里
81
+ lights(0.28); // 少量光斑再落到人身上:衣服上有碎光,但人不被盖没
82
+ dancers(c, t);
83
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.2; c.drawImage(P.texture('canvas_renoir', '#ece2cc', { scale: 0.006, amt: 12, grain: 7, seed: 12 }), 0, 0); c.restore();
84
+ }
85
+ return { draw };
86
+ })();
@@ -0,0 +1,82 @@
1
+ // 毕沙罗(印象派田园):人是环境的一部分——人物融进树影、田垄、小路;笔触比西斯莱更短、更多向,绿色里有大量橙黄与粉的互补跳动;小路把视线带进深处。
2
+ // 题材(新构图):集市日的乡间小路,两位挑着篮子的农妇和一个赶着小推车的人沿路走来,路两边是开着花的果树,远处一座小教堂。
3
+ // 原作锚定(风格,非原画):天占三分之一、云小而碎;树冠=一团团短笔的绿、黄绿、橙、粉、淡紫;地面笔触顺着地势(田垄、小路的方向);人物只有几笔,但有重量与动势。
4
+ // 管线:干净的形(天/远景/树/地)→ MEDIUM.broken(短笔、多向)→ 缓存;动态:行人沿路(小画布→broken→贴回)、树冠风动(顶层细碎小笔漂)、云慢漂。
5
+ SCENES['46_pissarro'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
7
+ let sky, ground, trees, built = false;
8
+ const PATH = [[250, 1100], [520, 880], [820, 700], [1080, 600], [1250, 548]]; // 小路中线(从近到远)
9
+ const bez = (u) => { const a = PATH, n = a.length - 1, f = u * n, i = Math.min(n - 1, Math.floor(f)), q = f - i; return [lerp(a[i][0], a[i + 1][0], q), lerp(a[i][1], a[i + 1][1], q)]; };
10
+ const widthAt = (y) => 40 + (y - 540) * 0.55; // 路在近处更宽
11
+ function skyPlate() {
12
+ const cv = P.canvas(2400, 560), g = cv.getContext('2d'), r = rng(4);
13
+ const gr = g.createLinearGradient(0, 0, 0, 560); gr.addColorStop(0, '#7fa6d2'); gr.addColorStop(0.6, '#b4cadc'); gr.addColorStop(1, '#efe3c8'); g.fillStyle = gr; g.fillRect(0, 0, 2400, 560);
14
+ for (let k = 0; k < 18; k++) { const x = r() * 2400, y = 40 + r() * 330, s = 0.5 + r() * 0.9; g.save(); g.filter = 'blur(7px)'; for (let j = 0; j < 6; j++) { const dx = (r() - .5) * 150 * s, dy = (r() - .5) * 26 * s, rx = (34 + r() * 50) * s, ry = (16 + r() * 18) * s;
15
+ g.fillStyle = 'rgba(176,176,204,.7)'; g.beginPath(); g.ellipse(x + dx + 6, y + dy + ry * 0.5, rx, ry * 0.9, 0, 0, TAU); g.fill(); g.fillStyle = 'rgba(246,240,226,.95)'; g.beginPath(); g.ellipse(x + dx, y + dy, rx, ry, 0, 0, TAU); g.fill(); g.fillStyle = 'rgba(252,230,184,.5)'; g.beginPath(); g.ellipse(x + dx - rx * 0.2, y + dy - ry * 0.4, rx * 0.55, ry * 0.4, 0, 0, TAU); g.fill(); } g.restore(); }
16
+ return M.broken(cv, { cell: 9, len: 34, wid: 7, passes: 2, angle: () => -0.12, seed: 8, jit: 20, skip: 0.06 });
17
+ }
18
+ function groundPlate() {
19
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(9);
20
+ // 远景:丘陵、田野带、小教堂与农舍
21
+ const hg = g.createLinearGradient(0, 430, 0, 560); hg.addColorStop(0, '#9fb4a8'); hg.addColorStop(1, '#8aa27a'); g.fillStyle = hg; g.beginPath(); g.moveTo(0, 520); g.quadraticCurveTo(300, 420, 640, 480); g.quadraticCurveTo(1000, 440, 1300, 470); g.quadraticCurveTo(1640, 420, 1920, 490); g.lineTo(1920, 600); g.lineTo(0, 600); g.fill();
22
+ g.fillStyle = '#b8a890'; g.fillRect(1250, 440, 70, 80); g.fillStyle = '#c9b8a0'; g.fillRect(1320, 458, 50, 62); g.fillStyle = '#8d5a4a'; g.beginPath(); g.moveTo(1240, 440); g.lineTo(1285, 400); g.lineTo(1330, 440); g.fill(); g.beginPath(); g.moveTo(1316, 458); g.lineTo(1345, 430); g.lineTo(1376, 458); g.fill();
23
+ g.fillStyle = '#a89a84'; g.fillRect(1280, 380, 26, 62); g.fillStyle = '#6f6a78'; g.beginPath(); g.moveTo(1274, 382); g.lineTo(1293, 346); g.lineTo(1312, 382); g.fill();
24
+ // 田:两种绿的垄(沿地势:近处宽斜,远处窄横)
25
+ const gg = g.createLinearGradient(0, 540, 0, H); gg.addColorStop(0, '#9bb87a'); gg.addColorStop(1, '#5f8a42'); g.fillStyle = gg; g.fillRect(0, 540, W, H - 540);
26
+ const FP = ['#7fa05a', '#b8c070', '#5f8a42', '#d2c47a', '#8aa86a', '#9a8a5a', '#6f9a56'];
27
+ for (let i = 0; i < 160; i++) { const k = Math.pow(r(), 1.6), y = 556 + k * 540, sc = 0.25 + k * 1.1, x = r() * W, wd = (60 + r() * 220) * sc, ht = (10 + r() * 30) * sc;
28
+ g.fillStyle = FP[(r() * FP.length) | 0]; g.globalAlpha = 0.55; g.beginPath(); g.ellipse(x, y, wd, ht, (r() - .5) * 0.12, 0, TAU); g.fill(); }
29
+ g.globalAlpha = 1;
30
+ // 树影:横过小路与田的淡紫蓝软影
31
+ [[470, 840, 240, 40], [1500, 930, 300, 50], [900, 690, 150, 22]].forEach(([x, y, rx, ry]) => { g.save(); g.filter = 'blur(10px)'; g.fillStyle = 'rgba(86,86,150,.38)'; g.beginPath(); g.ellipse(x, y, rx, ry, -0.06, 0, TAU); g.fill(); g.restore(); });
32
+ // 小路:沿 PATH 画变宽的赭色带,路边有草,路面有车辙
33
+ g.fillStyle = '#c9a778'; g.beginPath(); const L = [], R = [];
34
+ for (let k = 0; k <= 40; k++) { const [x, y] = bez(k / 40), w = widthAt(y); L.push([x - w, y]); R.push([x + w, y]); } L.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); R.reverse().forEach(p => g.lineTo(p[0], p[1])); g.closePath(); g.fill();
35
+ g.strokeStyle = 'rgba(120,86,56,.5)'; g.lineWidth = 6; for (const o of [-0.35, 0.35]) { g.beginPath(); for (let k = 0; k <= 40; k++) { const [x, y] = bez(k / 40); const px = x + o * widthAt(y); k ? g.lineTo(px, y) : g.moveTo(px, y); } g.stroke(); }
36
+ return M.broken(cv, { cell: 9, len: 30, wid: 7, passes: 2, angle: (x, y) => y < 560 ? 0.02 : (Math.abs(x - bez(clamp((1100 - y) / 560))[0]) < widthAt(y) ? -0.75 : 0.1 + Math.sin(x * 0.01) * 0.2), seed: 11, jit: 24, skip: 0.05, warm: [255, 214, 140], cool: [130, 120, 190] });
37
+ }
38
+ function treesPlate() {
39
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(31);
40
+ const tree = (x, y, h, kind, seed) => {
41
+ const rr = rng(seed); g.fillStyle = '#6b5a4a'; g.beginPath(); g.moveTo(x - h * 0.03, y); g.lineTo(x - h * 0.012, y - h * 0.55); g.lineTo(x + h * 0.012, y - h * 0.55); g.lineTo(x + h * 0.035, y); g.fill();
42
+ const pal = kind === 'bloom' ? ['#e6b4b0', '#f0d0c0', '#a8c070', '#8aa850', '#f4e4d4', '#c8a8b8'] : ['#86a850', '#b8c060', '#d6a84a', '#5f8a42', '#e0c070', '#4d6b3a'];
43
+ for (let i = 0; i < 70; i++) { const a = rr() * TAU, d = Math.pow(rr(), 0.7) * h * 0.36, cx = x + Math.cos(a) * d * 1.2, cy = y - h * 0.66 + Math.sin(a) * d * 0.88, rx = h * (0.035 + rr() * 0.05), ry = rx * (0.7 + rr() * 0.3);
44
+ const low = Math.sin(a) > 0.2, hi = Math.sin(a) < -0.35; // 下沿阴、上沿亮
45
+ g.fillStyle = low && rr() < 0.7 ? '#4d6b3a' : hi && rr() < 0.6 ? '#f0d878' : pal[(rr() * pal.length) | 0]; g.beginPath(); g.ellipse(cx, cy, rx, ry, rr() * 3, 0, TAU); g.fill(); }
46
+ };
47
+ tree(260, 900, 640, 'bloom', 1); tree(560, 760, 380, 'leaf', 2); tree(1560, 880, 620, 'leaf', 3); tree(1780, 780, 400, 'bloom', 4); tree(1040, 580, 200, 'leaf', 5);
48
+ return M.broken(cv, { cell: 8, len: 22, wid: 7, passes: 2, angle: (x, y) => 0.9 + Math.sin(x * 0.02 + y * 0.017) * 1.1, seed: 13, jit: 34, skip: 0.04, warm: [255, 216, 150], cool: [110, 130, 190], sat: 1.12 });
49
+ }
50
+ function build() { sky = skyPlate(); ground = groundPlate(); trees = treesPlate(); built = true; }
51
+ // 行人(小画布→broken→贴回):u 沿路位置,face 朝向,basket 篮子,kind 类型
52
+ function walker(c, u, kind, t, ph) {
53
+ const [x, y] = bez(u), s = lerp(0.34, 1.0, (y - 560) / 540), cv = P.scratch('pis_walker'); if (cv.width !== 360) { cv.width = 360; cv.height = 420; } const g = cv.getContext('2d'); g.reset(); g.clearRect(0, 0, 360, 420);
54
+ g.save(); g.translate(180, 380); g.scale(s * 1.6, s * 1.6); const step = Math.sin(t * 5 + ph), bob = Math.abs(step) * 3;
55
+ g.translate(0, -bob);
56
+ g.fillStyle = '#4a4a5a'; g.fillRect(-14, -38, 10, 38 + step * 4); g.fillRect(4, -38, 10, 38 - step * 4); // 腿
57
+ const robe = kind === 'a' ? '#5a78a8' : kind === 'b' ? '#a8584a' : '#6f7f4a';
58
+ g.fillStyle = robe; g.beginPath(); g.moveTo(-26, -40); g.quadraticCurveTo(-30, -96, -14, -128); g.lineTo(14, -128); g.quadraticCurveTo(30, -96, 28, -40); g.closePath(); g.fill(); // 衣
59
+ g.fillStyle = '#e8d8c0'; g.beginPath(); g.moveTo(-12, -128); g.lineTo(12, -128); g.lineTo(6, -100); g.lineTo(-6, -100); g.fill(); // 围裙/领
60
+ g.fillStyle = '#d8b090'; g.beginPath(); g.arc(0, -142, 12, 0, TAU); g.fill(); g.fillStyle = kind === 'c' ? '#3a3a3a' : '#efe6d4'; g.beginPath(); g.ellipse(0, -152, 14, 5, 0, 0, TAU); g.fill(); g.beginPath(); g.arc(0, -154, 8, Math.PI, 0); g.fill(); // 头与帽
61
+ if (kind !== 'c') { g.strokeStyle = '#4a3a2a'; g.lineWidth = 4; g.beginPath(); g.moveTo(-26, -118); g.quadraticCurveTo(-48, -96, -42, -70); g.stroke(); g.fillStyle = '#b88a4a'; g.beginPath(); g.ellipse(-42, -66, 20, 12, 0, 0, TAU); g.fill(); g.fillStyle = '#d6a84a'; for (let k = 0; k < 4; k++) { g.beginPath(); g.arc(-50 + k * 8, -76, 5, 0, TAU); g.fill(); } }
62
+ else { g.strokeStyle = '#4a3a2a'; g.lineWidth = 5; g.beginPath(); g.moveTo(14, -110); g.lineTo(80, -70); g.stroke(); g.fillStyle = '#8a6a3a'; g.fillRect(60, -64, 70, 36); g.fillStyle = '#6b8a3a'; for (let k = 0; k < 4; k++) { g.beginPath(); g.arc(70 + k * 15, -70, 9, 0, TAU); g.fill(); } g.fillStyle = '#14120f'; g.beginPath(); g.arc(78, -22, 10, 0, TAU); g.arc(120, -22, 10, 0, TAU); g.fill(); }
63
+ g.restore();
64
+ const rep = M.broken(cv, { cell: 5, len: 12, wid: 4.5, passes: 2, angle: (xx, yy) => (yy > 330 ? 0.4 : 1.3), seed: 15, jit: 20, skip: 0.02, sat: 1.08 });
65
+ c.drawImage(rep, x - 180, y - 380);
66
+ }
67
+ function draw(c, lt) {
68
+ if (!built) build();
69
+ const t = clamp(lt, 0, 6);
70
+ c.drawImage(sky, t * 12, 0, W, 560, 0, 0, W, 560);
71
+ c.drawImage(ground, 0, 0);
72
+ // 远景的行人先画(小),再画树,再画近的人,保证层次
73
+ walker(c, lerp(0.66, 0.34, ss(0, 6, t)), 'c', t, 2.0);
74
+ c.drawImage(trees, 0, 0);
75
+ // 树冠风动:顶层再撒一小把随风漂的叶笔
76
+ const r = rng(77); c.save(); c.lineCap = 'round'; for (let i = 0; i < 260; i++) { const tx = [260, 560, 1560, 1780][i % 4], ty = [330, 480, 380, 460][i % 4], a = r() * TAU, d = Math.sqrt(r()) * [230, 140, 220, 150][i % 4], ph = r() * 6;
77
+ const x = tx + Math.cos(a) * d + Math.sin(t * 1.6 + ph) * 5, y = ty + Math.sin(a) * d * 0.8 + Math.cos(t * 1.9 + ph) * 3, col = ['#e9b6b8', '#b8c060', '#f2d2c4', '#86a850', '#d6a84a'][(r() * 5) | 0]; c.strokeStyle = col; c.globalAlpha = 0.85; c.lineWidth = 6; c.beginPath(); c.moveTo(x - 8, y - 3); c.lineTo(x + 8, y + 3); c.stroke(); } c.restore();
78
+ walker(c, lerp(0.5, 0.06, ss(0, 6, t)), 'a', t, 0.0); walker(c, lerp(0.58, 0.16, ss(0, 6, t)), 'b', t, 1.3);
79
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.2; c.drawImage(P.texture('canvas_pis', '#e8e0cc', { scale: 0.006, amt: 12, grain: 7, seed: 5 }), 0, 0); c.restore();
80
+ }
81
+ return { draw };
82
+ })();
@@ -0,0 +1,94 @@
1
+ // 西斯莱(印象派风景):天占大半、积云有暖白与淡紫灰的阴面、水面把房子拖成碎笔倒影、整幅是一笔一笔短弯笔,没有轮廓线。
2
+ // 题材(新构图):涨水后的村口,一位摆渡人撑船载着一只木箱穿过水面,左边是半淹的客栈,右边是光秃的白杨与电线杆。
3
+ // 原作锚定(fetch_reference.py:《路维希安的雪》《马尔利港的洪水》《塞纳河上的船》):天 #8fb0d0→#e9dcc0、云白 #f2ecde/阴 #aab0c4/暖 #f8e4b8、
4
+ // 水 #9aa89c+倒影、屋墙赭黄 #d6bd86、屋顶石板 #6d7684、近景笔触宽而横、远处窄而碎;暖亮处偏黄粉,阴暗处偏冷紫。
5
+ // 管线:干净的形(天/水/景)→ MEDIUM.broken 各自重画成笔触(缓存)→ 动态:云慢漂、水面逐行位移+涟漪、摆渡船与撑篙。
6
+ SCENES['47_sisley'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
8
+ const WL = 540, BASE = 722; // 水面起点(远岸脚)与房脚吃水线
9
+ let sky, water, scene, built = false;
10
+ function skyPlate() {
11
+ const cv = P.canvas(2600, 600), g = cv.getContext('2d');
12
+ const gr = g.createLinearGradient(0, 0, 0, 600); gr.addColorStop(0, '#6a98c8'); gr.addColorStop(0.55, '#a8c0d8'); gr.addColorStop(1, '#ebdfc2'); g.fillStyle = gr; g.fillRect(0, 0, 2600, 600);
13
+ const r = rng(17);
14
+ const cloud = (x, y, s) => {
15
+ const blobs = Array.from({ length: 9 }, () => [x + (r() - .5) * 300 * s, y + (r() - .5) * 60 * s, (50 + r() * 70) * s, (30 + r() * 36) * s]);
16
+ g.save(); g.filter = 'blur(10px)';
17
+ blobs.forEach(([bx, by, rx, ry]) => { g.fillStyle = 'rgba(166,170,196,.75)'; g.beginPath(); g.ellipse(bx + 8 * s, by + ry * 0.55, rx * 1.02, ry * 0.9, 0, 0, TAU); g.fill(); }); // 阴面:淡紫灰,压在下
18
+ blobs.forEach(([bx, by, rx, ry]) => { g.fillStyle = 'rgba(244,238,224,.95)'; g.beginPath(); g.ellipse(bx, by, rx, ry, 0, 0, TAU); g.fill(); }); // 本体:暖白
19
+ blobs.forEach(([bx, by, rx, ry]) => { g.fillStyle = 'rgba(252,232,186,.55)'; g.beginPath(); g.ellipse(bx - rx * 0.2, by - ry * 0.45, rx * 0.6, ry * 0.4, 0, 0, TAU); g.fill(); }); // 受光:暖黄
20
+ g.restore();
21
+ };
22
+ [[260, 160, 1.5], [820, 110, 1.2], [1320, 190, 1.7], [1900, 120, 1.3], [2380, 210, 1.5], [620, 330, 0.9], [1560, 380, 0.8], [2100, 400, 0.9], [60, 380, 0.8]].forEach(([x, y, s]) => cloud(x, y, s));
23
+ return M.broken(cv, { cell: 10, len: 54, wid: 10, passes: 2, angle: (x, y) => -0.06 + Math.sin(x * 0.004) * 0.05, seed: 3, jit: 18, skip: 0.06 });
24
+ }
25
+ function above(g) { // 水面之上的东西:远岸、客栈、电线杆、白杨
26
+ const r = rng(23);
27
+ // 远岸一带:低矮房屋+灌木,偏雾蓝灰(空气透视)
28
+ g.fillStyle = '#9fa9a8'; g.fillRect(0, 486, W, 60);
29
+ [[700, 470, 80, 56, '#b9a58a'], [790, 480, 60, 44, '#a99a86'], [880, 462, 100, 66, '#b7a690'], [1010, 476, 70, 50, '#a99a86'], [1120, 470, 120, 58, '#b9a58a']].forEach(([x, y, w, h, col], i) => { g.fillStyle = col; g.fillRect(x, y, w, h); g.fillStyle = '#7d7f8a'; g.beginPath(); g.moveTo(x - 6, y); g.lineTo(x + w / 2, y - 24); g.lineTo(x + w + 6, y); g.fill(); });
30
+ g.fillStyle = '#7f8e72'; for (let i = 0; i < 40; i++) { g.beginPath(); g.ellipse(560 + r() * 1300, 520 + r() * 20, 30 + r() * 30, 16 + r() * 14, 0, 0, TAU); g.fill(); }
31
+ // 客栈(左):两个立面——正面赭黄、侧面阴面更暗;陡屋顶+老虎窗;一层是浅色招牌带(不写字)
32
+ const fx = 40, fw = 470, top = 250, sideW = 150;
33
+ g.fillStyle = '#d8bf88'; g.fillRect(fx, top, fw, BASE - top);
34
+ g.fillStyle = '#b8a070'; g.beginPath(); g.moveTo(fx + fw, top); g.lineTo(fx + fw + sideW, top + 40); g.lineTo(fx + fw + sideW, BASE - 6); g.lineTo(fx + fw, BASE); g.fill();
35
+ g.fillStyle = '#6a7482'; g.beginPath(); g.moveTo(fx - 20, top + 6); g.lineTo(fx + fw * 0.45, top - 130); g.lineTo(fx + fw + sideW + 20, top + 36); g.lineTo(fx + fw + 8, top + 18); g.lineTo(fx + fw * 0.45, top - 96); g.closePath(); g.fill();
36
+ g.fillStyle = '#4e5766'; g.beginPath(); g.moveTo(fx + fw * 0.45, top - 130); g.lineTo(fx + fw + sideW + 20, top + 36); g.lineTo(fx + fw + sideW, top + 50); g.lineTo(fx + fw * 0.45, top - 100); g.fill();
37
+ g.fillStyle = '#6a7482'; g.fillRect(fx + 270, top - 40, 70, 62); g.fillStyle = '#4e5766'; g.beginPath(); g.moveTo(fx + 262, top - 40); g.lineTo(fx + 305, top - 76); g.lineTo(fx + 348, top - 40); g.fill(); g.fillStyle = '#3a4150'; g.fillRect(fx + 286, top - 26, 38, 40);
38
+ [[fx + 40, top + 60], [fx + 150, top + 60], [fx + 260, top + 60], [fx + 370, top + 60]].forEach(([x, y]) => { g.fillStyle = '#7a8a86'; g.fillRect(x - 10, y, 18, 96); g.fillRect(x + 62, y, 18, 96); g.fillStyle = '#3d4348'; g.fillRect(x + 8, y + 4, 54, 90); g.fillStyle = 'rgba(210,225,235,.55)'; g.fillRect(x + 12, y + 8, 46, 36); });
39
+ g.fillStyle = '#efe3bd'; g.fillRect(fx, top + 200, fw, 56); g.fillStyle = '#c8b27a'; g.fillRect(fx, top + 256, fw, 6); // 招牌带
40
+ [[fx + 60, top + 275], [fx + 220, top + 275]].forEach(([x, y]) => { g.fillStyle = '#7a8a86'; g.fillRect(x - 8, y, 14, 150); g.fillRect(x + 58, y, 14, 150); g.fillStyle = '#31373c'; g.fillRect(x + 6, y + 4, 52, 140); }); // 一层的窗
41
+ g.fillStyle = '#6b4d34'; g.fillRect(fx + 360, top + 290, 70, 132); g.fillStyle = '#2a2420'; g.fillRect(fx + 368, top + 300, 54, 120); // 门(半淹)
42
+ // 白杨:细长的光秃树,枝丫上挑
43
+ const poplar = (x, base, hgt, seed) => { const rr = rng(seed); g.strokeStyle = '#7d7566'; g.lineCap = 'round'; g.lineWidth = 11; g.beginPath(); g.moveTo(x, base); g.lineTo(x + 6, base - hgt); g.stroke();
44
+ for (let k = 0; k < 22; k++) { const y = base - hgt * (0.18 + rr() * 0.8), side = rr() < 0.5 ? -1 : 1, L = 60 + rr() * 110 * (1 - (base - y) / hgt * 0.6); g.lineWidth = 3 + rr() * 3; g.beginPath(); g.moveTo(x + 3, y); g.quadraticCurveTo(x + side * L * 0.4, y - L * 0.5, x + side * L * 0.55, y - L); g.stroke(); } };
45
+ poplar(1600, BASE + 20, 560, 5); poplar(1740, BASE + 60, 500, 6); poplar(1830, BASE + 30, 430, 7);
46
+ // 电线杆
47
+ g.fillStyle = '#6a5d4e'; g.fillRect(1292, 160, 18, BASE - 150); g.fillRect(1250, 190, 102, 10); g.strokeStyle = '#6a5d4e'; g.lineWidth = 3; g.beginPath(); g.moveTo(1250, 194); g.quadraticCurveTo(900, 250, 600, 214); g.moveTo(1352, 194); g.quadraticCurveTo(1650, 240, 1920, 200); g.stroke();
48
+ }
49
+ function build() {
50
+ sky = skyPlate();
51
+ // 干净的景(透明底)
52
+ const sp = P.canvas(), sg = sp.getContext('2d'); above(sg);
53
+ // 水:底色+倒影(景的竖向镜像,压暗压冷)+天光碎片
54
+ const wp = P.canvas(), wg = wp.getContext('2d'); const gr = wg.createLinearGradient(0, WL, 0, H); gr.addColorStop(0, '#c4cbc0'); gr.addColorStop(0.4, '#9fb0aa'); gr.addColorStop(1, '#788a82'); wg.fillStyle = gr; wg.fillRect(0, WL, W, H - WL);
55
+ wg.save(); wg.beginPath(); wg.rect(0, WL, W, H - WL); wg.clip(); wg.translate(0, BASE * 2 - 2); wg.scale(1, -1); wg.globalAlpha = 0.62; wg.filter = 'blur(3px) brightness(.82) saturate(.9)'; wg.drawImage(sp, 0, 0); wg.restore();
56
+ const r = rng(41); wg.globalAlpha = 1; for (let i = 0; i < 90; i++) { const x = r() * W, y = WL + 30 + r() * (H - WL), w = 40 + r() * 190; wg.fillStyle = `rgba(${r() < 0.6 ? '236,236,224' : '150,170,176'},${0.12 + r() * 0.2})`; wg.fillRect(x, y, w, 3 + r() * 5); }
57
+ water = M.broken(wp, { cell: 11, len: 70, wid: 8, passes: 2, angle: () => 0, seed: 5, jit: 16, skip: 0.04, cool: [96, 120, 156], warm: [236, 222, 176] });
58
+ scene = M.broken(sp, { cell: 8, len: 26, wid: 8, passes: 2, angle: (x, y) => x < 700 && y < 330 ? (x < 280 ? -0.5 : 0.5) : (x > 1500 ? 1.45 : (x > 1270 && x < 1330 && y < 740 ? 1.57 : (y < 520 ? 0.02 : 1.5))), seed: 9, jit: 18, skip: 0.04 });
59
+ built = true;
60
+ }
61
+ function boat(c, t) {
62
+ const u = ss(0, 6, t), X = lerp(1380, 840, u), Y = 842 + Math.sin(t * 1.5) * 3, tilt = Math.sin(t * 1.5 + 1) * 0.025, pole = Math.sin(t * 2.2) * 0.5 + 0.5;
63
+ const cvs = P.scratch('sisley_boat'); if (cvs.width !== 520) { cvs.width = 520; cvs.height = 360; } const cg = cvs.getContext('2d'); cg.reset(); cg.clearRect(0, 0, 520, 360);
64
+ const x = 260, y = 230, cc = c; c = cg;
65
+ // 倒影(先画,暗、窄、被水纹打碎)
66
+ c.save(); c.translate(x, y + 40); c.scale(1, -0.5); c.globalAlpha = 0.28; c.fillStyle = '#2d2a28'; c.beginPath(); c.moveTo(-130, 0); c.quadraticCurveTo(0, 30, 130, 0); c.lineTo(100, -36); c.lineTo(-100, -36); c.fill(); c.fillRect(-8, -150, 16, 114); c.restore();
67
+ c.save(); c.translate(x, y); c.rotate(tilt);
68
+ c.fillStyle = '#3a2f2a'; c.beginPath(); c.moveTo(-150, -8); c.quadraticCurveTo(-110, 34, 0, 36); c.quadraticCurveTo(110, 34, 150, -14); c.lineTo(120, -30); c.lineTo(-120, -28); c.closePath(); c.fill();
69
+ c.fillStyle = '#6d5a42'; c.beginPath(); c.moveTo(-150, -8); c.lineTo(-120, -28); c.lineTo(120, -30); c.lineTo(150, -14); c.lineTo(110, -8); c.lineTo(-110, -4); c.closePath(); c.fill();
70
+ c.fillStyle = '#c9b88a'; c.beginPath(); c.moveTo(26, -26); c.quadraticCurveTo(20, -88, 70, -96); c.quadraticCurveTo(122, -88, 116, -26); c.closePath(); c.fill(); c.strokeStyle = '#6b5a3a'; c.lineWidth = 4; c.beginPath(); c.moveTo(48, -92); c.quadraticCurveTo(70, -80, 94, -92); c.stroke(); // 麻袋
71
+ // 摆渡人:深色身形,白帽,竿从右手斜入水
72
+ c.fillStyle = '#2a3340'; c.beginPath(); c.moveTo(-52, -26); c.quadraticCurveTo(-60, -90, -46, -126); c.lineTo(-18, -126); c.quadraticCurveTo(-8, -90, -14, -26); c.fill();
73
+ c.fillStyle = '#d9b48c'; c.beginPath(); c.arc(-32, -142, 13, 0, TAU); c.fill(); c.fillStyle = '#e8e0cc'; c.beginPath(); c.ellipse(-32, -152, 20, 7, 0, 0, TAU); c.fill();
74
+ const px = -14 + pole * 8, ang = -0.5 - pole * 0.15; c.strokeStyle = '#4a3a2a'; c.lineWidth = 6; c.lineCap = 'round'; c.beginPath(); c.moveTo(-18, -104 - pole * 6); c.lineTo(px + Math.cos(ang) * -210 * -1, 30 + pole * 14 + 0); c.stroke();
75
+ c.restore();
76
+ const rep = M.broken(cvs, { cell: 7, len: 18, wid: 6, passes: 2, angle: (xx, yy) => (yy > 250 ? 0 : 1.2), seed: 12, jit: 14, skip: 0.02, base: true });
77
+ cc.drawImage(rep, X - 260, Y - 230);
78
+ c = cc; const x2 = X, y2 = Y;
79
+ // 船行水纹:船尾拖出两道渐开的 V
80
+ c.save(); c.strokeStyle = 'rgba(244,240,226,.55)'; c.lineWidth = 3; c.lineCap = 'round'; for (let k = 0; k < 3; k++) { const q = ((t * 0.5 + k / 3) % 1); c.globalAlpha = (1 - q) * 0.8; c.beginPath(); c.ellipse(x2 + 90 + q * 220, y2 + 34, 40 + q * 110, 6 + q * 14, 0, Math.PI * 0.05, Math.PI * 0.95); c.stroke(); } c.restore();
81
+ }
82
+ function draw(c, lt) {
83
+ if (!built) build();
84
+ const t = clamp(lt, 0, 6);
85
+ c.drawImage(sky, t * 18, 0, W, 600, 0, 0, W, 600); // 云慢漂
86
+ c.fillStyle = '#ebdfc2'; c.fillRect(0, 560, W, WL - 540 + 40);
87
+ // 水:逐行位移,越近越大(倒影被水纹拉碎)
88
+ for (let y = WL; y < H; y += 6) { const k = (y - WL) / (H - WL), dx = Math.sin(t * 1.3 + y * 0.09) * (1.5 + k * 9) + Math.sin(t * 2.7 + y * 0.21) * 1.5 * k; c.drawImage(water, 0, y, W, 6, dx - 24, y, W + 48, 6); }
89
+ c.drawImage(scene, 0, 0);
90
+ boat(c, t);
91
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.22; c.drawImage(P.texture('canvas_sisley', '#e8e0cc', { scale: 0.006, amt: 12, grain: 7, seed: 3 }), 0, 0); c.restore(); // 画布纹
92
+ }
93
+ return { draw };
94
+ })();
@@ -0,0 +1,107 @@
1
+ // 高更(综合主义/掐丝珐琅):不写实的饱和色场、粗而不均的深群青轮廓、斜线分割画面的树干、被画框裁掉的前景人物、没有投影。
2
+ // 题材(新构图):红色草场上,三位戴白帽的妇人(前景,裁切)看着一个男孩牵山羊横穿画面;一棵斜树把画面劈开。
3
+ // 原作锚定(fetch_reference.py:《布道后的幻象》《黄色基督》《我们从哪里来》):朱红 #d63a1c 色场、黄赭 #e0a526 地、
4
+ // 紫褐树干 #5b2c48、白帽 #efece2+蓝灰影 #a9b4cf、黑裙 #1b1a22、轮廓深群青 #1d2a52;没有透视收束,远近靠重叠和裁切。
5
+ // 管线:色场(缓存,顺笔向短条)→ 斜树(缓存)→ 黄地 → 动态:山羊与男孩(走)、前景妇人(风吹帽翼)、叶团摇 → 无投影,不加光效。
6
+ SCENES['48_gauguin'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
8
+ const LINE = '#1d2a52';
9
+ let layer;
10
+ const poly = (pts, close = true) => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); if (close) p.closePath(); return p; };
11
+ // Catmull 式平滑闭合/开放曲线:高更什么都是圆的,不要折线
12
+ const smooth = (pts, close = true) => { const p = new Path2D(), n = pts.length; if (close) { const m = i => [(pts[i % n][0] + pts[(i + 1) % n][0]) / 2, (pts[i % n][1] + pts[(i + 1) % n][1]) / 2]; p.moveTo(...m(n - 1)); for (let i = 0; i < n; i++) p.quadraticCurveTo(pts[i][0], pts[i][1], ...m(i)); p.closePath(); }
13
+ else { p.moveTo(...pts[0]); for (let i = 1; i < n - 1; i++) p.quadraticCurveTo(pts[i][0], pts[i][1], (pts[i][0] + pts[i + 1][0]) / 2, (pts[i][1] + pts[i + 1][1]) / 2); p.lineTo(...pts[n - 1]); } return p; };
14
+ const blob = (cx, cy, rx, ry, rot = 0) => { const p = new Path2D(); p.ellipse(cx, cy, rx, ry, rot, 0, TAU); return p; };
15
+ function build() {
16
+ layer = P.canvas(); const g = layer.getContext('2d');
17
+ // 朱红色场:整幅铺满,笔向斜 30°,同色里有橙红/深红的条
18
+ M.flat(g, poly([[0, 0], [W, 0], [W, H], [0, H]]), '#d63a1c', { streak: 0.5, vari: 34, lw: 0, seed: 1, n: 520 });
19
+ // 黄赭地:右下一块大而圆的坡,波浪形上缘
20
+ const ground = new Path2D(); ground.moveTo(520, H); ground.bezierCurveTo(700, 820, 980, 700, 1280, 730); ground.bezierCurveTo(1560, 760, 1740, 700, W, 640); ground.lineTo(W, H); ground.closePath();
21
+ M.flat(g, ground, '#e0a526', { streak: -0.15, vari: 26, lw: 7, seed: 2, n: 300, line: LINE });
22
+ // 紫褐小径:沿地面斜向的一条
23
+ M.flat(g, poly([[900, 1010], [1260, 840], [1420, 860], [1060, 1060]]), '#7a3a5a', { streak: -0.45, vari: 20, lw: 5, seed: 3, n: 80, line: LINE });
24
+ // 斜树干:从左上劈向右下,上粗下细再略弯,把画面分成两半
25
+ const trunk = smooth([[620, -40], [830, -40], [970, 150], [1100, 330], [1300, 490], [1480, 590], [1580, 730], [1450, 760], [1350, 690], [1170, 620], [1090, 520], [930, 340], [770, 200], [670, 90]]);
26
+ M.flat(g, trunk, '#5b2c48', { streak: 0.8, vari: 26, lw: 8, seed: 4, n: 180, line: LINE, bounds: [500, 0, 1120, 780] });
27
+ M.flat(g, smooth([[640, -30], [760, -30], [940, 170], [1120, 360], [1300, 520], [1420, 600], [1330, 600], [1160, 480], [940, 300], [720, 120]]), '#8d4a66', { streak: 0.8, vari: 14, lw: 0, seed: 5, n: 50, bounds: [500, 0, 1100, 760] });
28
+ // (树干上原有三个暗斑,读成笛孔,已去掉)
29
+ // 枝:树干中段向右上分出两枝,叶团另画(动态)
30
+ M.flat(g, smooth([[1200, 400], [1380, 250], [1560, 140], [1630, 190], [1500, 330], [1310, 480]]), '#5b2c48', { streak: -0.7, vari: 20, lw: 7, seed: 6, n: 70, line: LINE, bounds: [1200, 140, 460, 340] });
31
+ M.flat(g, smooth([[980, 280], [1090, 150], [1190, 30], [1250, 70], [1170, 220], [1090, 340]]), '#5b2c48', { streak: -1.0, vari: 20, lw: 7, seed: 7, n: 40, line: LINE, bounds: [980, 30, 280, 300] });
32
+ }
33
+ function leaves(c, t) { // 叶团:深绿大圆块,叠几个黄点(果),整团微摇
34
+ [[1590, 150, 190, 120, 0.2], [1250, 70, 150, 100, -0.3], [1460, 300, 120, 80, 0.1]].forEach(([x, y, rx, ry, rot], i) => {
35
+ const sw = Math.sin(t * 1.6 + i * 1.7) * 7, ox = x + sw, oy = y + Math.sin(t * 2.1 + i) * 3, rr = rng(70 + i), p = smooth(Array.from({ length: 11 }, (_, k) => { const a = k / 11 * TAU, j = 0.82 + rr() * 0.3; return [ox + Math.cos(a) * rx * j, oy + Math.sin(a) * ry * j]; }));
36
+ M.flat(c, p, '#2f5a3a', { streak: 0.2, vari: 22, lw: 7, seed: 10 + i, n: 90, line: LINE, bounds: [x - rx, y - ry, rx * 2, ry * 2] });
37
+ const r = rng(30 + i); for (let k = 0; k < 6; k++) { const a = r() * TAU, d = r() * 0.7; M.flat(c, blob(x + sw + Math.cos(a) * rx * d, y + Math.sin(a) * ry * d, 15, 13), '#f2c335', { vari: 8, lw: 3.5, seed: 40 + k, n: 6, line: LINE, bounds: [x - rx, y - ry, rx * 2, ry * 2] }); }
38
+ });
39
+ }
40
+ // 戴白帽的妇人(布列塔尼):黑裙大圆块+肩上白披领;侧脸朝右(前额—鼻—唇—下巴一条干净的曲线);高白帽冠立在头顶、帽翼垂在脑后随风;back=true 背对
41
+ function woman(c, x, y, s, t, seed, bow, back) {
42
+ const sw = Math.sin(t * 2.2 + seed) * 8 * s;
43
+ c.save(); c.translate(x, y); c.scale(s, s);
44
+ M.flat(c, smooth([[-170, 0], [-190, -200], [-150, -360], [-60, -430], [60, -430], [150, -340], [190, -190], [175, 0]]), '#1b1a22', { streak: 1.2, vari: 12, lw: 8, seed: seed, n: 60, line: LINE, bounds: [-190, -440, 380, 440] });
45
+ // 白披领:肩上一圈,前襟 V 形,蓝灰影在一侧
46
+ M.flat(c, smooth([[-120, -420], [-30, -470], [80, -462], [150, -400], [90, -350], [10, -330], [-70, -350]]), '#efece2', { streak: 0.3, vari: 8, lw: 7, seed: seed + 1, n: 16, line: LINE, bounds: [-130, -475, 290, 150] });
47
+ M.flat(c, smooth([[-40, -352], [10, -332], [80, -352], [40, -380]]), '#a9b4cf', { vari: 6, lw: 0, seed: seed + 8, n: 6, bounds: [-40, -380, 130, 50] });
48
+ const hy = -560 + bow * 14;
49
+ // 帽翼(脑后垂下,随风)
50
+ M.flat(c, smooth([[-10, hy + 30], [-70 - sw, hy + 20], [-150 - sw * 1.6, hy + 90], [-120 - sw, hy + 170], [-50, hy + 140], [-10, hy + 90]]), '#efece2', { streak: 0.5, vari: 8, lw: 7, seed: seed + 2, n: 16, line: LINE, bounds: [-160, hy + 10, 160, 170] });
51
+ M.flat(c, smooth([[-100 - sw, hy + 110], [-60, hy + 130], [-40, hy + 100]]), '#a9b4cf', { vari: 6, lw: 0, seed: seed + 5, n: 4, bounds: [-110, hy + 90, 80, 50] });
52
+ if (!back) {
53
+ // 脸:侧向右,一条连续的曲线;颈接到披领
54
+ M.flat(c, smooth([[-10, hy + 0], [52, hy - 6], [92, hy + 22], [96, hy + 56], [126, hy + 90], [100, hy + 98], [104, hy + 118], [94, hy + 134], [98, hy + 160], [60, hy + 188], [10, hy + 176], [-20, hy + 140], [-24, hy + 70]]), '#e8b078', { streak: 1.2, vari: 10, lw: 7, seed: seed + 6, n: 14, line: LINE, bounds: [-30, hy - 10, 160, 200] });
55
+ c.strokeStyle = LINE; c.lineWidth = 5.5; c.lineCap = 'round';
56
+ c.beginPath(); c.moveTo(56, hy + 52); c.quadraticCurveTo(76, hy + 64, 96, hy + 52); c.stroke(); // 闭眼
57
+ c.beginPath(); c.moveTo(50, hy + 34); c.quadraticCurveTo(76, hy + 24, 98, hy + 34); c.stroke(); // 眉
58
+ c.strokeStyle = '#8a3a30'; c.lineWidth = 6; c.beginPath(); c.moveTo(74, hy + 138); c.quadraticCurveTo(90, hy + 144, 102, hy + 134); c.stroke(); // 唇:平静
59
+ M.flat(c, smooth([[8, hy + 90], [40, hy + 120], [30, hy + 160], [-6, hy + 120]]), '#d49a66', { vari: 6, lw: 0, seed: seed + 9, n: 4, bounds: [-6, hy + 90, 50, 70] }); // 颊下影
60
+ }
61
+ // 帽冠:高、圆顶、正面略宽,压在额头之上(不盖脸)
62
+ M.flat(c, smooth([[-26, hy + 70], [-34, hy - 40], [-4, hy - 120], [58, hy - 150], [108, hy - 100], [112, hy - 20], [86, hy + 6], [20, hy + 12]]), '#efece2', { streak: 1.3, vari: 8, lw: 8, seed: seed + 4, n: 26, line: LINE, bounds: [-40, hy - 160, 160, 180] });
63
+ M.flat(c, smooth([[-26, hy - 30], [-4, hy - 110], [20, hy - 100], [0, hy + 20]]), '#a9b4cf', { vari: 6, lw: 0, seed: seed + 3, n: 6, bounds: [-30, hy - 120, 60, 150] });
64
+ c.restore();
65
+ }
66
+ // 男孩和山羊:向左走。羊在后,绳牵在男孩手里;步态用腿相位摆
67
+ function boyGoat(c, t) {
68
+ const u = ss(0.0, 6, t), x = lerp(1830, 1420, u), step = Math.sin(t * 7), bob = Math.abs(step) * 4, y = 808 + lerp(0, 22, u);
69
+ c.save(); c.translate(x, y - bob); c.scale(0.58, 0.58);
70
+ // 羊(朝左):长躯干+粗颈+楔形头+后弯角+细腿带膝;远侧两腿先画,近侧后画;肚下蓝灰影
71
+ const leg = (dx, ph, near) => { const sw = Math.sin(t * 7 + ph) * 26, kx = dx + sw * 0.4, fx = dx + sw; return smooth([[dx - 10, -70], [dx + 10, -70], [kx + 8, -36], [fx + 7, 6], [fx + 10, 20], [fx - 14, 20], [fx - 8, 4], [kx - 8, -34]]); };
72
+ [[-62, 3.1, 0], [58, 0, 0]].forEach(([dx, ph], i) => M.flat(c, leg(dx, ph), '#c9c8c4', { vari: 6, lw: 6, seed: 60 + i, n: 4, line: LINE, bounds: [dx - 30, -74, 70, 110] }));
73
+ M.flat(c, smooth([[-96, -126], [-40, -156], [50, -158], [120, -138], [134, -100], [104, -66], [30, -58], [-50, -60], [-98, -82]]), '#efece2', { streak: 0.05, vari: 10, lw: 8, seed: 66, n: 40, line: LINE, bounds: [-100, -160, 240, 100] });
74
+ M.flat(c, smooth([[-100, -86], [-30, -62], [60, -64], [118, -86], [100, -66], [30, -58], [-50, -60]]), '#a9b4cf', { vari: 6, lw: 0, seed: 65, n: 8, bounds: [-110, -90, 240, 40] });
75
+ [[-30, -110, 34, 22], [40, -130, 30, 20], [70, -90, 24, 16]].forEach(([x, y, rx, ry], i) => M.flat(c, blob(x, y, rx, ry, 0.3), '#6a3a2a', { vari: 8, lw: 5, seed: 80 + i, n: 6, line: LINE, bounds: [x - rx, y - ry, rx * 2, ry * 2] }));
76
+ M.flat(c, smooth([[-96, -128], [-100, -198], [-140, -242], [-196, -236], [-224, -200], [-216, -152], [-170, -148], [-130, -110]]), '#efece2', { streak: 0.6, vari: 8, lw: 8, seed: 67, n: 22, line: LINE, bounds: [-250, -232, 160, 130] }); // 颈+头
77
+ M.flat(c, smooth([[-176, -232], [-150, -262], [-130, -226], [-150, -216]]), '#efece2', { vari: 4, lw: 6, seed: 68, n: 2, line: LINE }); // 耳
78
+ c.strokeStyle = LINE; c.lineWidth = 9; c.lineCap = 'round'; c.beginPath(); c.moveTo(-196, -232); c.quadraticCurveTo(-190, -290, -128, -296); c.stroke(); // 角(向后弯)
79
+ c.lineWidth = 6; c.beginPath(); c.moveTo(-244, -166); c.quadraticCurveTo(-254, -134, -240, -110); c.stroke(); // 胡须
80
+ M.flat(c, blob(-208, -172, 22, 17, 0.2), '#c7a591', { vari: 6, lw: 5, seed: 90, n: 4, line: LINE, bounds: [-258, -186, 44, 32] });
81
+ c.fillStyle = LINE; c.beginPath(); c.arc(-184, -208, 6.5, 0, TAU); c.fill();
82
+ [[-34, 0, 0], [86, 3.1, 0]].forEach(([dx, ph], i) => M.flat(c, leg(dx, ph), '#efece2', { vari: 6, lw: 7, seed: 62 + i, n: 4, line: LINE, bounds: [dx - 30, -74, 70, 110] }));
83
+ // 男孩:侧身朝左,蓝衣、黄裤、赭色头发小帽,拉绳
84
+ const bx = -380, sw = Math.sin(t * 7) * 20;
85
+ [0, 3.1].forEach((ph, i) => { const s2 = Math.sin(t * 7 + ph) * 22; M.flat(c, smooth([[bx - 16, -104], [bx + 18, -104], [bx + 14 + s2 * 0.5, -40], [bx + 14 + s2, 22], [bx - 20 + s2, 22], [bx - 12 + s2 * 0.5, -40]]), i ? '#1d2a52' : '#2f3f7a', { lw: 6, seed: 70 + i, n: 8, line: LINE, bounds: [bx - 30, -104, 60, 130] }); });
86
+ M.flat(c, smooth([[bx - 46, -270], [bx + 4, -290], [bx + 48, -250], [bx + 56, -96], [bx - 54, -96]]), '#2f5f9a', { streak: 1.3, vari: 16, lw: 7, seed: 71, n: 30, line: LINE, bounds: [bx - 58, -290, 118, 200] });
87
+ M.flat(c, smooth([[bx - 30, -330], [bx + 10, -352], [bx + 46, -320], [bx + 40, -276], [bx - 8, -262], [bx - 36, -290]]), '#e8b078', { vari: 10, lw: 6, seed: 72, n: 10, line: LINE, bounds: [bx - 40, -352, 90, 90] });
88
+ M.flat(c, smooth([[bx - 40, -318], [bx - 8, -366], [bx + 40, -352], [bx + 52, -322], [bx + 4, -334]]), '#8a2f24', { vari: 6, lw: 6, seed: 73, n: 8, line: LINE, bounds: [bx - 44, -368, 100, 50] }); // 红帽
89
+ c.strokeStyle = LINE; c.lineWidth = 4.5; c.beginPath(); c.moveTo(bx - 22, -312); c.quadraticCurveTo(bx - 14, -306, bx - 6, -312); c.stroke(); // 眼
90
+ c.strokeStyle = LINE; c.lineWidth = 6; c.beginPath(); c.moveTo(bx - 44, -230); c.quadraticCurveTo(bx - 120, -170, bx - 90, -150 + sw * 0.3); c.stroke(); // 左臂向前
91
+ c.lineWidth = 4; c.beginPath(); c.moveTo(bx + 40, -214 + sw * 0.2); c.quadraticCurveTo(-60, -100, -186, -190); c.stroke(); // 绳从男孩右手到羊颈
92
+ c.restore();
93
+ }
94
+ function draw(c, lt) {
95
+ if (!layer) build();
96
+ const t = clamp(lt, 0, 6);
97
+ c.save(); c.translate(W, 0); c.scale(-1, 1); // 水平镜像:不复刻名作的左右布局
98
+ c.drawImage(layer, 0, 0);
99
+ leaves(c, t); boyGoat(c, t);
100
+ // 前景妇人:被画框裁掉(脚在画外),两个并排朝右,一个在最左背对
101
+ woman(c, 230, 1190 + Math.sin(t * 1.4) * 2, 1.0, t, 100, 0.2, true);
102
+ woman(c, 560, 1250, 0.86, t, 200, 0.5, false);
103
+ woman(c, 820, 1310, 0.72, t, 300, 0.3, false);
104
+ c.restore();
105
+ }
106
+ return { draw };
107
+ })();
@@ -0,0 +1,60 @@
1
+ // Original scene; fixed pigment seed, continuous object motion, arbitrary-seek safe.
2
+ SCENES['49_chagall'] = (() => {
3
+ const W=1920,H=1080,TAU=Math.PI*2;
4
+ const rng=(seed)=>()=>{seed=(Math.imul(seed,1664525)+1013904223)>>>0;return seed/4294967296;};
5
+ const canvas=(w=W,h=H)=>{const a=document.createElement('canvas');a.width=w;a.height=h;return a;};
6
+ const poly=(g,pts,col)=>{g.fillStyle=col;g.beginPath();pts.forEach((p,i)=>i?g.lineTo(...p):g.moveTo(...p));g.closePath();g.fill();};
7
+ const shape=(g,d,col,stroke=null,width=2)=>{const p=new Path2D(d);g.fillStyle=col;g.fill(p);if(stroke){g.strokeStyle=stroke;g.lineWidth=width;g.stroke(p);}return p;};
8
+ const oval=(g,x,y,rx,ry,col,a=0)=>{g.fillStyle=col;g.beginPath();g.ellipse(x,y,rx,ry,a,0,TAU);g.fill();};
9
+ const line=(g,pts,col,w=2)=>{g.strokeStyle=col;g.lineWidth=w;g.lineCap='round';g.beginPath();pts.forEach((p,i)=>i?g.lineTo(...p):g.moveTo(...p));g.stroke();};
10
+ function paint(src,seed,cell,len,angle,amount=17){
11
+ const out=canvas(src.width,src.height),g=out.getContext('2d'),s=src.getContext('2d',{willReadFrequently:true}),ww=src.width,hh=src.height,data=s.getImageData(0,0,ww,hh).data,r=rng(seed);
12
+ g.drawImage(src,0,0);g.lineCap='butt';
13
+ for(let yy=0;yy<hh+cell;yy+=cell)for(let xx=0;xx<ww+cell;xx+=cell){
14
+ const x=xx+(r()-.5)*cell*1.8,y=yy+(r()-.5)*cell*1.8,ix=Math.min(ww-1,Math.max(0,x|0)),iy=Math.min(hh-1,Math.max(0,y|0)),i=(iy*ww+ix)*4;if(data[i+3]<200)continue;
15
+ const a=angle(x,y)+(r()-.5)*.45,l=len*(.5+r()),q=(r()-.5)*amount,rr=data[i]+q,gg=data[i+1]+q*.8,bb=data[i+2]+q*.65;
16
+ g.strokeStyle=`rgba(${rr|0},${gg|0},${bb|0},.68)`;g.lineWidth=cell*(.55+r()*.65);g.beginPath();g.moveTo(x-Math.cos(a)*l/2,y-Math.sin(a)*l/2);g.quadraticCurveTo(x,y+(r()-.5)*3,x+Math.cos(a)*l/2,y+Math.sin(a)*l/2);g.stroke();
17
+ if(r()<.22){g.strokeStyle=`rgba(${(rr+17)|0},${(gg+15)|0},${(bb+10)|0},.3)`;g.lineWidth=.7;g.stroke();}
18
+ }
19
+ // Fine weave remains stationary in the object coordinate system.
20
+ g.globalAlpha=.035;g.strokeStyle='#fff4d6';g.lineWidth=.6;for(let y=1;y<hh;y+=3){g.beginPath();g.moveTo(0,y);g.lineTo(ww,y);g.stroke();}g.globalAlpha=1;return out;
21
+ }
22
+ let bg,bouquet,caseBase,lid,letter;
23
+ function house(g,x,y,w,h,a,wall,roof){g.save();g.translate(x,y);g.rotate(a);shape(g,`M${-w/2} 0 L${-w/2} ${-h} L${w/2} ${-h-8} L${w/2} 0Z`,wall,'#3c426c',3);shape(g,`M${-w/2-15} ${-h} L-4 ${-h-w*.55} L${w/2+18} ${-h-7}Z`,roof,'#54405d',3);const n=w>140?3:2;for(let j=0;j<2;j++)for(let i=0;i<n;i++){const xx=-w/2+18+i*(w-32)/n,yy=-h+24+j*(h-36)/2;g.fillStyle=j%2?'#e2cb8a':'#a1c8bf';g.fillRect(xx,yy,w/n*.38,h*.19);line(g,[[xx+w/n*.18,yy],[xx+w/n*.18,yy+h*.19]],'#78616d',2);}line(g,[[-w/2+3,-h+5],[-w/2+3,-5]],'#b8c4b4',2);g.restore();}
24
+ function init(){if(bg)return;const a=canvas(),g=a.getContext('2d',{willReadFrequently:true}),r=rng(491);
25
+ let gr=g.createLinearGradient(0,0,1700,1000);gr.addColorStop(0,'#153f81');gr.addColorStop(.45,'#2a4a93');gr.addColorStop(1,'#66517e');g.fillStyle=gr;g.fillRect(0,0,W,H);
26
+ // Translucent emotional colour zones, full of stationary dry pigment.
27
+ shape(g,'M0 0 L770 0 Q650 230 791 482 Q387 463 0 870Z','#224f8b');shape(g,'M1323 0 L1920 0 L1920 666 Q1590 587 1438 358 Q1263 230 1323 0Z','#4d7d8555');shape(g,'M0 857 Q380 719 751 848 Q1098 995 1455 769 Q1700 608 1920 764 L1920 1080 L0 1080Z','#625480');
28
+ for(let i=0;i<18000;i++){const x=r()*W,y=r()*H,aa=r()*TAU;line(g,[[x,y],[x+Math.cos(aa)*(3+r()*23),y+Math.sin(aa)*(3+r()*23)]],['#7d93b31b','#142d681a','#596eae22','#b381a419'][i%4],1+r()*5);}
29
+ oval(g,1547,185,80,87,'#c9d6ac',-.15);shape(g,'M1536 99 Q1460 159 1524 242 Q1465 220 1465 165 Q1472 115 1536 99Z','#8fbdab');
30
+ for(let i=0;i<120;i++){const x=60+r()*1800,y=35+r()*580;oval(g,x,y,1+r()*2,1+r()*2,['#c7d3bb88','#d4b78b77','#8cc2c0aa'][i%3]);}
31
+ // A street bends upward into remembered architecture; each roof has a different axis.
32
+ for(let i=0;i<9;i++){const x=890+i*122,y=1000-Math.sin(i*.35)*170;house(g,x,y,116+r()*62,140+r()*90,-.12+i*.07,['#73918d','#b9a1a0','#8b95af','#b29a82'][i%4],['#9a5365','#c66e6d','#597d91'][i%3]);}
33
+ house(g,1535,561,268,229,-.34,'#a2bdaf','#b95369');house(g,1840,409,179,170,.28,'#96b6aa','#9d5d77');
34
+ // A door hangs in memory above its own pavement; no borrowed character symbols.
35
+ shape(g,'M1140 719 L1165 477 L1297 485 L1283 735Z','#517993','#283e6b',4);shape(g,'M1160 704 L1178 491 L1282 498 L1272 716Z','#87a2a3');shape(g,'M1193 689 L1195 511 L1267 510 L1257 702Z','#d5b998');oval(g,1244,613,4,5,'#755e72');
36
+ // Fine chalk/pigment traces do not move independently of the houses.
37
+ line(g,[[1010,1040],[1168,930],[1430,928],[1628,879],[1920,972]],'#a69ea155',4);
38
+ for(let i=0;i<60;i++){let x=850+r()*1060,y=880+r()*200;line(g,[[x,y],[x+8+r()*26,y-8]],['#9e8b9f77','#b4a8a966','#574b7a66'][i%3],2);}
39
+ bg=paint(a,4901,6,19,(x,y)=>y>800?-.2:Math.sin(x*.003+y*.004)*.8,16);
40
+ const b=canvas(700,900),q=b.getContext('2d',{willReadFrequently:true}),rr=rng(494);
41
+ // An oversized bouquet is the memory of a garden, distinct from literal village scale.
42
+ const flowers=[[195,240,67,'#d7b7c3'],[382,127,92,'#b85e86'],[501,334,74,'#e8cfb5'],[268,440,74,'#bd6387'],[419,464,63,'#b4ccd0'],[125,416,61,'#e1c9bf']];
43
+ for(const [x,y,sz,col] of flowers){line(q,[[341,885],[329+(x-330)*.3,610],[x,y]],'#668b8a',7);line(q,[[342,881],[332+(x-330)*.3,610],[x+4,y]],'#96a497',2);for(let j=0;j<11;j++){const aa=j*TAU/11+rr()*.25,rx=x+Math.cos(aa)*sz*.48,ry=y+Math.sin(aa)*sz*.43;oval(q,rx,ry,sz*(.3+rr()*.2),sz*(.18+rr()*.17),col,aa);line(q,[[x,y],[rx+Math.cos(aa)*sz*.2,ry+Math.sin(aa)*sz*.2]],'#f0d7c23c',2);}oval(q,x,y,sz*.2,sz*.17,'#a78568');for(let k=0;k<30;k++){const aa=rr()*TAU,ra=rr()*sz*.2;oval(q,x+Math.cos(aa)*ra,y+Math.sin(aa)*ra,2,2,k%2?'#dec89d':'#706b76');}}
44
+ for(let i=0;i<18;i++){const y=520+i*18,x=330+Math.sin(i*1.4)*60,dir=i%2?-1:1;shape(q,`M${x} ${y} Q${x+dir*105} ${y-83} ${x+dir*104} ${y-26} Q${x+dir*72} ${y+29} ${x} ${y}Z`,['#567d80','#7c9d92','#3e7079'][i%3]);line(q,[[x,y],[x+dir*87,y-34]],'#99b09b',2);}
45
+ bouquet=paint(b,4904,4,12,(x,y)=>Math.atan2(y-450,x-335)+.6,17);
46
+ const cb=canvas(640,260),v=cb.getContext('2d',{willReadFrequently:true});shape(v,'M16 43 Q310 20 610 54 L626 194 Q327 246 30 197Z','#7b4f62','#463e63',5);shape(v,'M25 49 Q316 27 600 59 L586 106 Q311 156 40 105Z','#b88f85');shape(v,'M36 57 Q316 40 590 68 L577 94 Q310 134 49 98Z','#524763');for(const x of [116,486]){shape(v,`M${x} 104 L${x+29} 105 L${x+37} 209 L${x+8} 213Z`,'#bba18d');g.fillStyle='#fff';}shape(v,'M266 174 Q272 143 355 154 L362 183 L347 185 L344 168 L283 167 L281 184Z','#b6a28a');for(let i=0;i<100;i++){const xx=45+rr()*540,yy=123+rr()*68;line(v,[[xx,yy],[xx+4+rr()*12,yy]],'#bd8f801f',2);}caseBase=paint(cb,4905,4,11,()=>.05,12);
47
+ const ld=canvas(640,330),z=ld.getContext('2d',{willReadFrequently:true});shape(z,'M25 300 L42 38 Q318 0 590 40 L612 303Z','#976c79','#453f66',5);shape(z,'M45 286 L58 55 Q317 20 575 56 L593 287Z','#b89a98');shape(z,'M65 272 L74 70 Q317 41 560 70 L576 271Z','#b4a2a5');for(let j=0;j<12;j++)line(z,[[75,78+j*16],[562,77+j*16]],'#897b911f',3);shape(z,'M159 104 L482 109 L478 228 L165 233Z','#a58297','#c6b0a9',3);shape(z,'M215 118 L428 127 L420 211 L211 202Z','#c2c5b2');shape(z,'M213 179 L259 151 L301 168 L337 146 L423 181 L420 211 L211 202Z','#788f9c');shape(z,'M213 190 Q292 169 420 192 L420 211 L211 202Z','#94ac92');for(let j=0;j<30;j++){line(z,[[165+j*10,110],[168+j*10,114]],'#d8c0b0',1);line(z,[[165+j*10,226],[168+j*10,230]],'#d8c0b0',1);}lid=paint(ld,4906,4,12,()=>0,12);
48
+ const e=canvas(118,86),u=e.getContext('2d',{willReadFrequently:true});shape(u,'M5 9 L111 3 L116 76 L9 82Z','#dfc9ad','#ab8996',1.5);line(u,[[7,10],[61,46],[109,5]],'#9c8c98',2);line(u,[[10,80],[47,39]],'#baa8a3',1.5);line(u,[[113,76],[74,39]],'#baa8a3',1.5);letter=paint(e,4907,2.8,7,()=>.1,9);
49
+ }
50
+ function pos(p){const u=1-p;return [u*u*u*530+3*u*u*p*880+3*u*p*p*900+p*p*p*1255,u*u*u*890+3*u*u*p*200+3*u*p*p*170+p*p*p*575];}
51
+ return {draw(c,lt){init();const t=Math.max(0,lt);c.drawImage(bg,0,0);
52
+ // The bouquet grows out of the open case as its lid lifts, then breathes in the wind.
53
+ const open=.56+.32*(.5-.5*Math.cos(Math.min(t/2.2,1)*Math.PI));c.save();c.translate(255,920);c.transform(1,-.08,-.13,open,0,0);c.drawImage(lid,0,-300);c.restore();
54
+ c.save();c.translate(625,1000);c.rotate(-.14+Math.sin(t*.75)*.016);c.scale(.94,.94);c.drawImage(bouquet,-350,-890);c.restore();
55
+ for(let i=0;i<7;i++){const p=(t*.135+i/7)%1,[x,y]=pos(p),sc=.48+Math.sin(p*Math.PI)*.32;c.save();c.translate(x,y);c.rotate(-.35+Math.sin(p*9+i)*.5);c.scale(sc,sc);c.drawImage(letter,-59,-43);c.restore();}
56
+ c.save();c.translate(258,877);c.rotate(-.07);c.drawImage(caseBase,0,0);c.restore();
57
+ // Petals are independent light fragments; their motion never re-seeds the canvas.
58
+ for(let i=0;i<11;i++){const p=(t*.1+i*.091)%1,xx=590+p*570+Math.sin(p*7+i)*28,yy=370+p*540;oval(c,xx,yy,5+i%3*2,2.5,['#d9b1c6','#bca5c2','#e5ccbd'][i%3],p*5+i);}
59
+ }};
60
+ })();