@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,96 @@
1
+ // 拉斐尔(梵蒂冈签字厅湿壁画):半圆楣窗里一座中心透视的筒拱大厅,藻井格逐层退向远端透出的一片天;人成组站在棋盘格地面上,袍色饱和(橙/蓝/玫/绿/丁香),姿势互相呼应,手和视线把一群人缝成一个整体。
2
+ // 题材(新构图,不搬名作):图书馆大厅——中央两位学者指着藻井与手卷争论,左组围着台上的摊开书,右组围着旋转的星盘;远处楼梯上有人走动。
3
+ // 原作锚定(fetch_reference.py:《雅典学院》《圣礼辩论》《帕纳塞斯》):象牙白石+蓝灰影 #e6dcc4/#9aa4b4,远端一片天 #9cc0e8,袍橙 #d2833a、蓝 #4f72b8、玫 #cf7480、绿 #5f9468、丁香 #8a78b8,金褐装饰带 #b08a40,棋盘格大理石地。
4
+ // 管线:形层(墙框→透视拱顶藻井→侧墙壁柱壁龛→棋盘格地→台阶)+人(M.figure)→ MEDIUM.fresco → 楣窗金带与底部灰色浮雕带。
5
+ SCENES['40_raphael'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
7
+ const CX = 960, SP = 500, N = 9; // 消失点横坐标、拱起点(视平线)、藻井层数
8
+ const ring = j => { const k = Math.pow(0.17, j / N); return { k, rx: 860 * k, ry: 400 * k, fl: SP + 500 * k }; };
9
+ const ROB = { orange: ['#f4bc7e', '#d2833a', '#8c4a22'], blue: ['#9cb8e6', '#4f72b8', '#2a4278'], rose: ['#f4c0c4', '#cf7480', '#8a3e4c'], green: ['#b0d4a6', '#5f9468', '#2f5a3c'], lilac: ['#d0c6ee', '#8a78b8', '#524478'], ivory: ['#faf3de', '#dccfa8', '#9a8c68'], ochre: ['#ecd592', '#c4a050', '#7c6228'] };
10
+ let plate, built = false, back;
11
+ const P3 = (j, th) => { const r = ring(j); return [CX + r.rx * Math.cos(th), SP - r.ry * Math.sin(th)]; };
12
+ const quad = (g, a, b, c2, d, fill) => { g.fillStyle = fill; g.beginPath(); g.moveTo(...a); g.lineTo(...b); g.lineTo(...c2); g.lineTo(...d); g.closePath(); g.fill(); };
13
+ function build() {
14
+ plate = P.canvas(); const g = plate.getContext('2d'), rn = rng(6);
15
+ // 外墙:象牙白,楣窗之外是壁面(带金褐装饰带沿拱走)
16
+ g.fillStyle = '#e4d8bc'; g.fillRect(0, 0, W, H);
17
+ // 远端:一片天+更远的小拱门
18
+ const r8 = ring(N), sg = g.createLinearGradient(0, SP - r8.ry, 0, r8.fl); sg.addColorStop(0, '#7fa8dc'); sg.addColorStop(1, '#cfe0f0');
19
+ g.fillStyle = sg; g.beginPath(); g.ellipse(CX, SP, r8.rx, r8.ry, 0, Math.PI, TAU); g.lineTo(CX + r8.rx, r8.fl); g.lineTo(CX - r8.rx, r8.fl); g.closePath(); g.fill();
20
+ // 层层退向远端:从远到近画(近的盖住远的);每层之间是侧墙(壁柱/壁龛)、拱顶(藻井)、地面
21
+ for (let j = N - 1; j >= 0; j--) {
22
+ const a = ring(j), b = ring(j + 1), shade = 0.08 * (j % 2);
23
+ // 地面:棋盘
24
+ for (let i = 0; i < 16; i++) { const x0a = CX + a.rx * (i / 8 - 1), x1a = CX + a.rx * ((i + 1) / 8 - 1), x0b = CX + b.rx * (i / 8 - 1), x1b = CX + b.rx * ((i + 1) / 8 - 1); quad(g, [x0a, a.fl], [x1a, a.fl], [x1b, b.fl], [x0b, b.fl], (i + j) % 2 ? '#e8dcc0' : '#bfb08c'); }
25
+ // 侧墙(左右):象牙石+深一档的壁柱
26
+ [-1, 1].forEach(sd => { const xa = CX + sd * a.rx, xb = CX + sd * b.rx;
27
+ quad(g, [xa, SP], [xb, SP], [xb, b.fl], [xa, a.fl], sd < 0 ? `rgba(${196 - j * 3},${186 - j * 3},${164 - j * 3},1)` : `rgba(${206 - j * 3},${196 - j * 3},${176 - j * 3},1)`);
28
+ if (j % 3 === 0) { const wa = a.rx * 0.07, wb = b.rx * 0.07; quad(g, [xa, SP], [xa - sd * wa * 0, SP], [xb, SP], [xb, SP], 'rgba(0,0,0,0)'); quad(g, [xa - sd * wa, SP], [xa, SP], [xa, a.fl], [xa - sd * wa, a.fl], 'rgba(120,108,88,.5)'); }
29
+ else if (j % 3 === 1) { // 壁龛:深洞+象牙雕像
30
+ const m = (u, v) => [lerp(xa, xb, u), lerp(lerp(SP, SP, 0), 0, 0) + lerp(lerp(SP + 40 * a.k, SP + 40 * b.k, u), lerp(a.fl - 80 * a.k, b.fl - 80 * b.k, u), v)];
31
+ const p0 = m(0.18, 0), p1 = m(0.82, 0), p2 = m(0.82, 1), p3 = m(0.18, 1); quad(g, p0, p1, p2, p3, '#6c5f4c');
32
+ const cx2 = lerp(xa, xb, 0.5), top = lerp(SP + 40 * a.k, SP + 40 * b.k, 0.5), bot = lerp(a.fl - 80 * a.k, b.fl - 80 * b.k, 0.5), hh = bot - top, ww = Math.abs(xb - xa) * 0.18;
33
+ g.fillStyle = '#f0e8d4'; g.beginPath(); g.ellipse(cx2, top + hh * 0.16, ww * 0.42, hh * 0.09, 0, 0, TAU); g.fill(); g.beginPath(); g.moveTo(cx2 - ww * 0.55, top + hh * 0.24); g.lineTo(cx2 + ww * 0.55, top + hh * 0.24); g.lineTo(cx2 + ww * 0.7, bot); g.lineTo(cx2 - ww * 0.7, bot); g.closePath(); g.fill();
34
+ } });
35
+ // 拱顶:沿角度切成藻井格,每格一个亮面+内凹的暗框
36
+ const nth = 14;
37
+ for (let i = 0; i < nth; i++) { const t0 = i / nth * Math.PI, t1 = (i + 1) / nth * Math.PI, A0 = P3(j, t0), A1 = P3(j, t1), B0 = P3(j + 1, t0), B1 = P3(j + 1, t1); const lit = 0.5 + 0.5 * Math.cos((t0 + t1) / 2 - Math.PI * 0.35);
38
+ quad(g, A0, A1, B1, B0, `rgb(${Math.round(214 - 34 * (1 - lit) - j * 2)},${Math.round(202 - 32 * (1 - lit) - j * 2)},${Math.round(176 - 30 * (1 - lit) - j * 2)})`);
39
+ const m = (p, q, u, v) => [lerp(p[0], q[0], u), lerp(p[1], q[1], u)]; const i0 = [lerp(A0[0], A1[0], 0.2), lerp(A0[1], A1[1], 0.2)], i1 = [lerp(A0[0], A1[0], 0.8), lerp(A0[1], A1[1], 0.8)], i2 = [lerp(B0[0], B1[0], 0.8), lerp(B0[1], B1[1], 0.8)], i3 = [lerp(B0[0], B1[0], 0.2), lerp(B0[1], B1[1], 0.2)];
40
+ const c0 = [lerp(i0[0], i3[0], 0.2), lerp(i0[1], i3[1], 0.2)], c1 = [lerp(i1[0], i2[0], 0.2), lerp(i1[1], i2[1], 0.2)], c2 = [lerp(i1[0], i2[0], 0.8), lerp(i1[1], i2[1], 0.8)], c3 = [lerp(i0[0], i3[0], 0.8), lerp(i0[1], i3[1], 0.8)];
41
+ quad(g, c0, c1, c2, c3, `rgba(110,98,78,${0.35 + 0.1 * (1 - lit)})`); }
42
+ // 拱环:每层交界处一道亮边
43
+ g.strokeStyle = 'rgba(250,244,226,.7)'; g.lineWidth = Math.max(1.5, 6 * a.k); g.beginPath(); g.ellipse(CX, SP, a.rx, a.ry, 0, Math.PI, TAU); g.stroke();
44
+ }
45
+ // 外框:拱外的墙面——楣窗装饰带(金褐+宝石红+群青)沿拱走
46
+ g.save(); g.beginPath(); g.rect(0, 0, W, H); g.ellipse(CX, SP, 860, 400, 0, 0, TAU); g.clip('evenodd'); g.fillStyle = '#e8dcc2'; g.fillRect(0, 0, W, H);
47
+ for (let i = 0; i < 90; i++) { g.fillStyle = `rgba(${rn() < 0.5 ? '255,248,230' : '120,100,70'},${0.04 + rn() * 0.05})`; g.beginPath(); g.ellipse(rn() * W, rn() * 480, 60 + rn() * 140, 30 + rn() * 60, 0, 0, TAU); g.fill(); }
48
+ g.restore();
49
+ [[900, 430, '#b08a40', 16], [880, 410, '#7a2f3c', 10], [920, 450, '#2f4f8c', 8]].forEach(([rx, ry, col, lw]) => { g.strokeStyle = col; g.lineWidth = lw; g.beginPath(); g.ellipse(CX, SP, rx, ry, 0, Math.PI, TAU); g.stroke(); });
50
+ g.fillStyle = '#d4a85a'; for (let i = 0; i < 38; i++) { const th = Math.PI + (i + 0.5) / 38 * Math.PI, x = CX + 890 * Math.cos(th), y = SP + 420 * Math.sin(th); g.beginPath(); g.arc(x, y, 7, 0, TAU); g.fill(); }
51
+ // 拱下左右的壁柱柱头与墙脚
52
+ [[0, 160], [W - 160, 160]].forEach(([x, w]) => { g.fillStyle = '#d8ccb0'; g.fillRect(x, SP, w, H - SP); g.fillStyle = 'rgba(110,96,72,.35)'; g.fillRect(x + (x ? 0 : w - 14), SP, 14, H - SP); });
53
+ // 前景台阶:两级,延伸全宽
54
+ quad(g, [0, 960], [W, 960], [W, 1000], [0, 1000], '#efe4c8'); quad(g, [0, 1000], [W, 1000], [W, 1030], [0, 1030], '#b8aa86'); quad(g, [0, 940], [W, 940], [W, 960], [0, 960], '#d8cba8');
55
+ // 底部灰色浮雕带(grisaille):四块方框里各一个小浮雕
56
+ g.fillStyle = '#d2c6aa'; g.fillRect(0, 1030, W, 50); g.fillStyle = '#7b6c50'; g.fillRect(0, 1030, W, 5);
57
+ for (let i = 0; i < 6; i++) { const x = 90 + i * 300; g.fillStyle = '#a89c80'; g.fillRect(x, 1040, 200, 34); g.fillStyle = '#e2d8be'; g.beginPath(); g.ellipse(x + 100, 1060, 26, 10, 0, 0, TAU); g.fill(); g.fillRect(x + 60, 1052, 80, 4); }
58
+ built = true;
59
+ }
60
+ // 台上摊开的书、星盘
61
+ function book(g, x, y, t) {
62
+ g.fillStyle = '#c4b48a'; g.fillRect(x - 130, y, 260, 56); g.fillStyle = '#9a8860'; g.fillRect(x - 130, y + 56, 260, 12);
63
+ g.fillStyle = '#f6efd6'; g.beginPath(); g.moveTo(x - 110, y - 12); g.lineTo(x, y - 24); g.lineTo(x + 110, y - 12); g.lineTo(x + 110, y + 6); g.lineTo(x, y - 6); g.lineTo(x - 110, y + 6); g.closePath(); g.fill(); g.strokeStyle = '#6a5a3c'; g.lineWidth = 2; g.stroke();
64
+ g.strokeStyle = 'rgba(80,60,40,.6)'; g.lineWidth = 1.8; for (let k = 0; k < 4; k++) { g.beginPath(); g.moveTo(x - 94, y - 4 + k * 3 - 6 * (1 - k / 4)); g.lineTo(x - 12, y - 14 + k * 3); g.moveTo(x + 12, y - 14 + k * 3); g.lineTo(x + 94, y - 4 + k * 3); g.stroke(); }
65
+ }
66
+ function astrolabe(g, x, y, t) {
67
+ g.fillStyle = '#c4b48a'; g.fillRect(x - 70, y + 150, 140, 18); g.fillRect(x - 12, y + 40, 24, 120);
68
+ g.strokeStyle = '#a6802c'; g.lineWidth = 7; [0, 1, 2].forEach(k => { const a = t * 0.9 + k * 1.0; g.beginPath(); g.ellipse(x, y, 90 - k * 8, 90 * Math.abs(Math.cos(a)) + 6, k * 0.6 - 0.3, 0, TAU); g.stroke(); });
69
+ g.fillStyle = '#4f72b8'; g.beginPath(); g.arc(x, y, 22, 0, TAU); g.fill(); g.strokeStyle = '#2a4278'; g.lineWidth = 3; g.stroke();
70
+ }
71
+ function fig(g, x, y, h, face, rb, mt, extra, t, seed) { M.figure(g, Object.assign({ x, y, h, face, t, seed, halo: false, robe: ROB[rb], mantle: mt ? ROB[mt] : null, under: '#f4ead0', skin: '#e2b48a', hair: '#5b3a22', sway: 0.3 }, extra)); }
72
+ function draw(c, lt) {
73
+ if (!built) build();
74
+ const t = clamp(lt, 0, 6);
75
+ const live = P.scratch('raph_live'), g = live.getContext('2d'); g.reset(); g.drawImage(plate, 0, 0);
76
+ // 远处楼梯上的小人:沿地面走(近大远小的极小人影)
77
+ [[0.1, 0, ROB.orange], [0.5, 1, ROB.blue], [0.8, 0, ROB.rose]].forEach(([u0, dir, col], i) => { const u = ((u0 + t * 0.03 * (dir ? -1 : 1)) % 1 + 1) % 1, y = 640 + (i % 2) * 22, x = CX - 90 + u * 180, h = 74; M.figure(g, { x, y, h, face: dir ? -1 : 1, t, seed: 40 + i, robe: col, under: '#f4ead0', skin: '#e2b48a', hair: '#5b3a22', sway: 0.6, lean: 0.02 }); });
78
+ // 左组:石台上的书,三人围看
79
+ book(g, 560, 772, t);
80
+ fig(g, 410, 945, 330, 1, 'green', 'ochre', { lean: 0.1, bow: 0.8, gaze: [540, 760], hands: [[500, 800], [505, 786]] }, t, 11);
81
+ fig(g, 735, 940, 315, -1, 'lilac', 'rose', { lean: 0.07, bow: 0.6, gaze: [600, 760], hands: [[640, 790 + Math.sin(t * 1.3) * 4], null] }, t, 12);
82
+ fig(g, 250, 950, 300, 1, 'ivory', 'blue', { lean: 0.04, bow: 0.3, gaze: [520, 750], hands: [null, null] }, t, 13);
83
+ // 右组:星盘,两人看,一人伸手拨环
84
+ astrolabe(g, 1400, 660, t);
85
+ fig(g, 1230, 945, 325, -1, 'rose', 'blue', { lean: 0.06, bow: 0.5, gaze: [1380, 660], hands: [[1330, 710 + Math.sin(t * 2) * 6], null] }, t, 21);
86
+ fig(g, 1590, 945, 335, -1, 'orange', 'green', { lean: 0.08, bow: 0.7, gaze: [1440, 670], hands: [[1490, 720], [1480, 690]] }, t, 22);
87
+ fig(g, 1730, 940, 295, -1, 'ochre', null, { lean: 0.03, bow: 0.2, gaze: [1440, 730], hands: [null, null] }, t, 23);
88
+ // 中央两位:左边指向藻井(手臂上下起落),右边展开手卷,视线互相咬合
89
+ const pt = Math.sin(t * 1.4) * 0.5;
90
+ fig(g, 870, 990, 385, 1, 'blue', 'orange', { lean: 0.04, bow: 0, gaze: [1000, 580], hands: [[960, 560 - pt * 20], [930, 760]] }, t, 31);
91
+ fig(g, 1070, 990, 365, -1, 'rose', 'green', { lean: 0.05, bow: 0.2, gaze: [930, 600], hands: [[970, 790], [985, 770]] }, t, 32);
92
+ g.strokeStyle = '#6a5a3c'; g.lineWidth = 3; g.fillStyle = '#f6efd6'; g.beginPath(); g.moveTo(950, 745); g.quadraticCurveTo(972, 735, 994, 745); g.lineTo(994, 792); g.quadraticCurveTo(972, 802, 950, 792); g.closePath(); g.fill(); g.stroke(); // 手卷
93
+ M.fresco(c, live, { ground: M.plaster('raphael', { base: '#e0d4b8', seed: 15 }), chalk: 0.05, soften: 0.5, plasterAlpha: 0.45, sat: 1.1 });
94
+ }
95
+ return { draw };
96
+ })();
@@ -0,0 +1,112 @@
1
+ // 米开朗基罗(红粉笔人体习作+「未完成」):形体是雕出来的——光从左上来,肩胛、脊沟、背阔肌像石头一样有体积;用红粉笔沿形体走向排线,暗处交叉再压一层,边缘有断续的重轮廓;画面下端不收尾,像雕像还陷在粗石里。
2
+ // 题材(新构图,不搬名作):一张旧素描纸,一个双臂举起、背身扭转的男子习作正在被一笔笔排线画出来;右侧是同一人翻转缩小的旧习作,角落有几行不成字的注记。
3
+ // 原作锚定(fetch_reference.py:《阿特拉斯奴隶》《利比亚女先知习作》《创造亚当》):纸 #dfc79e 偏暖米黄带霉斑、红粉笔 #9c3f26(暗处 #6e2a1a)、亮处不画(留纸)、排线以斜 50° 为主+沿形体转向、边缘粗而断续。
4
+ // 管线:形体图(灰度,用椭圆光暗叠出肌肉)→ 由明暗+梯度方向生成确定性排线笔画 → 缓存成一张「已画完」的红粉笔层 → 按 y 从上到下揭示,笔尖(粉笔)跟着扫。
5
+ SCENES['41_michelangelo'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
7
+ const FW = 720, FH = 860; // 形体图局部坐标
8
+ let paper, big, small, built = false;
9
+ const smooth = (pts) => { const p = new Path2D(), n = pts.length, 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(); return p; };
10
+ const capsule = (pts, ws) => { const L = [], R = []; pts.forEach((p, i) => { const a = pts[Math.max(0, i - 1)], b = pts[Math.min(pts.length - 1, i + 1)], d = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1, nx = -(b[1] - a[1]) / d, ny = (b[0] - a[0]) / d; L.push([p[0] + nx * ws[i] / 2, p[1] + ny * ws[i] / 2]); R.push([p[0] - nx * ws[i] / 2, p[1] - ny * ws[i] / 2]); }); return smooth([...L, ...R.reverse()]); };
11
+ // ---- 形体:剪影(各部件)与明暗 ----
12
+ const parts = () => ({
13
+ torso: smooth([[262, 188], [196, 206], [118, 238], [100, 280], [140, 340], [176, 410], [208, 490], [226, 565], [214, 640], [188, 716], [236, 792], [305, 804], [374, 792], [424, 716], [398, 640], [386, 565], [404, 490], [438, 410], [474, 340], [516, 280], [498, 238], [418, 206], [348, 188]]),
14
+ armL: capsule([[132, 266], [34, 128], [28, 40], [112, 8]], [128, 104, 70, 44]), armR: capsule([[486, 262], [572, 124], [570, 40], [484, 14]], [128, 104, 70, 44]),
15
+ head: smooth([[298, 22], [346, 36], [366, 84], [358, 128], [336, 160], [300, 176], [268, 168], [250, 150], [238, 128], [226, 114], [237, 100], [240, 70], [262, 36]]),
16
+ neck: smooth([[250, 150], [352, 150], [392, 214], [222, 214]]),
17
+ });
18
+ const LIGHTS = [ // x, y, rx, ry, 明度, 浓度(光从左上)
19
+ [300, 240, 112, 62, 0.98, 0.6], [212, 330, 66, 84, 1.0, 0.8], [398, 330, 62, 84, 0.86, 0.6], [214, 420, 64, 18, 0.15, 0.8], [398, 420, 60, 18, 0.12, 0.8], [170, 300, 70, 12, 0.18, 0.7], [250, 318, 10, 70, 0.2, 0.55],
20
+ [305, 450, 11, 235, 0.12, 0.85], [266, 535, 30, 115, 1.0, 0.65], [346, 535, 30, 115, 0.8, 0.5], [162, 420, 38, 120, 0.22, 0.7], [452, 420, 40, 120, 0.1, 0.8],
21
+ [205, 600, 28, 74, 0.2, 0.6], [405, 600, 26, 74, 0.1, 0.6], [248, 722, 78, 70, 1.0, 0.75], [364, 722, 78, 70, 0.7, 0.6], [305, 708, 7, 76, 0.1, 0.75],
22
+ [132, 262, 62, 64, 1.0, 0.85], [150, 330, 14, 50, 0.15, 0.7], [488, 258, 60, 64, 0.5, 0.7], [468, 330, 14, 50, 0.1, 0.7],
23
+ [96, 180, 40, 92, 1.0, 0.7], [64, 140, 20, 60, 0.2, 0.6], [532, 170, 40, 92, 0.42, 0.65], [566, 130, 20, 60, 0.15, 0.6],
24
+ [78, 90, 28, 52, 0.95, 0.5], [522, 96, 28, 52, 0.38, 0.55],
25
+ [270, 100, 34, 62, 1.0, 0.65], [340, 100, 30, 70, 0.28, 0.7], [338, 116, 10, 18, 0.1, 0.8], [300, 40, 66, 32, 0.28, 0.7], [270, 52, 18, 14, 0.2, 0.6], [330, 50, 20, 14, 0.15, 0.6],
26
+ [160, 345, 12, 70, 0.15, 0.7], [452, 345, 12, 70, 0.1, 0.7], [180, 470, 8, 38, 0.2, 0.6], [196, 500, 8, 34, 0.2, 0.6], [212, 530, 8, 30, 0.2, 0.6], [430, 470, 8, 38, 0.1, 0.7], [414, 500, 8, 34, 0.1, 0.7], [398, 530, 8, 30, 0.1, 0.7],
27
+ [240, 655, 14, 10, 0.12, 0.8], [370, 655, 14, 10, 0.1, 0.8], [215, 290, 70, 14, 0.2, 0.6], [395, 290, 66, 14, 0.15, 0.6], [150, 215, 70, 24, 0.95, 0.5], [300, 300, 140, 24, 0.95, 0.4],
28
+ [305, 188, 56, 28, 0.3, 0.6], [270, 176, 10, 40, 0.2, 0.6], [345, 176, 10, 40, 0.15, 0.6],
29
+ ];
30
+ const twist = y => 30 * Math.sin((y - 60) / 780 * Math.PI * 1.15 - 0.4) - 6; // 肩胛带与骨盆反向偏移:S 形扭转
31
+ function warp(cv, bg) { const o = P.canvas(FW, FH), g = o.getContext('2d'); if (bg) { g.fillStyle = bg; g.fillRect(0, 0, FW, FH); } for (let y = 0; y < FH; y++) g.drawImage(cv, 0, y, FW, 1, twist(y), y, FW, 1); return o; }
32
+ function formMap() {
33
+ const cv = P.canvas(FW, FH), g = cv.getContext('2d'), pt = parts(); g.fillStyle = '#fff'; g.fillRect(0, 0, FW, FH); g.translate(60, 40);
34
+ const body = new Path2D(); Object.values(pt).forEach(p => body.addPath(p));
35
+ g.save(); g.clip(body); g.fillStyle = '#d4d4d4'; g.fillRect(0, 0, FW, FH);
36
+ LIGHTS.forEach(([x, y, rx, ry, v, a]) => { g.save(); g.translate(x, y); g.scale(rx, ry); const gr = g.createRadialGradient(0, 0, 0, 0, 0, 1), c = Math.round(v * 255); gr.addColorStop(0, `rgba(${c},${c},${c},${a})`); gr.addColorStop(1, `rgba(${c},${c},${c},0)`); g.fillStyle = gr; g.beginPath(); g.arc(0, 0, 1, 0, TAU); g.fill(); g.restore(); });
37
+ g.filter = 'blur(7px)'; g.strokeStyle = 'rgba(40,40,40,.55)'; g.lineWidth = 16; Object.values(pt).forEach(p => g.stroke(p)); g.filter = 'none'; // 边缘遮挡:体积感
38
+ g.restore();
39
+ const wcv = warp(cv, '#fff'), mk = P.canvas(FW, FH), mg = mk.getContext('2d'); mg.translate(60, 40); mg.fillStyle = '#000'; Object.values(pt).forEach(p => mg.fill(p));
40
+ return { cv: wcv, pt, body, mask: warp(mk) };
41
+ }
42
+ // ---- 排线:明暗→笔画;亮处留纸,中调单向斜线,暗处交叉;笔向在斜 50° 与形体等高线之间混合 ----
43
+ function hatch(f, seed) {
44
+ const g = f.cv.getContext('2d'), d = g.getImageData(0, 0, FW, FH).data, r = rng(seed), out = [];
45
+ const v = (x, y) => d[((clamp(y | 0, 0, FH - 1)) * FW + clamp(x | 0, 0, FW - 1)) * 4] / 255;
46
+ for (let y = 4; y < FH - 4; y += 4.4) for (let x = 4; x < FW - 4; x += 4.4) {
47
+ const px = x + (r() - .5) * 4, py = y + (r() - .5) * 4, val = v(px, py); if (val > 0.985) continue;
48
+ const dk = 1 - val, gx = v(px + 3, py) - v(px - 3, py), gy = v(px, py + 3) - v(px, py - 3), cont = Math.atan2(gx, -gy), base = -0.88;
49
+ const fade = 1 - ss(730, 840, py); // 下端不收尾:越往下越淡,像还陷在石里
50
+ let a = base + Math.sin(cont - base) * 0.18; const L = 12 + r() * 12;
51
+ if (r() < dk * 1.55 + 0.05) out.push([px, py, a, L, 0.9 + dk * 0.9, clamp((0.18 + dk * 0.75) * fade, 0, 0.92), dk]);
52
+ if (dk > 0.42 && r() < (dk - 0.3) * 1.5) out.push([px, py, 0.62 + (r() - .5) * 0.2, L * 0.8, 0.8 + dk * 0.7, clamp((0.15 + dk * 0.6) * fade, 0, 0.85), dk]);
53
+ }
54
+ return out;
55
+ }
56
+ function renderChalk(f, strokes, key) {
57
+ const cv = P.canvas(FW, FH), g = cv.getContext('2d'); g.lineCap = 'round';
58
+ strokes.forEach(([x, y, a, L, w, al, dk]) => { g.strokeStyle = dk > 0.6 ? `rgba(104,38,22,${al})` : `rgba(158,66,40,${al})`; g.lineWidth = w; g.beginPath(); g.moveTo(x - Math.cos(a) * L / 2, y - Math.sin(a) * L / 2); g.lineTo(x + Math.cos(a) * L / 2, y + Math.sin(a) * L / 2); g.stroke(); });
59
+ // 重轮廓:取整体剪影的边界像素,按背光侧加粗、间断(粉笔在纸上拖出的颗粒线),部件之间的接缝不画
60
+ const mg = f.mask.getContext('2d');
61
+ const md = mg.getImageData(0, 0, FW, FH).data, al = (x, y) => md[((clamp(y | 0, 0, FH - 1)) * FW + clamp(x | 0, 0, FW - 1)) * 4 + 3] > 128, rr = rng(77);
62
+ for (let y = 1; y < FH - 1; y++) for (let x = 1; x < FW - 1; x++) {
63
+ if (!al(x, y) || (al(x - 1, y) && al(x + 1, y) && al(x, y - 1) && al(x, y + 1))) continue;
64
+ const nx = (al(x - 1, y) ? 1 : 0) - (al(x + 1, y) ? 1 : 0) * -1, ny = 0; // 外法线粗估(见下)
65
+ const gx = (al(x + 2, y) ? 1 : 0) - (al(x - 2, y) ? 1 : 0), gy = (al(x, y + 2) ? 1 : 0) - (al(x, y - 2) ? 1 : 0); // 指向内部
66
+ const shade = clamp(-(gx * 0.6 + gy * 0.8), 0, 1); // 外法线朝右下=背光
67
+ const dash = Math.sin(x * 0.045 + y * 0.037) + Math.sin(x * 0.011 - y * 0.05) * 0.8; if (dash < -0.7 - shade * 0.7) continue;
68
+ const fade = 1 - ss(740, 830, y); g.fillStyle = `rgba(96,34,20,${(0.5 + shade * 0.45) * fade})`; g.beginPath(); g.arc(x + (rr() - .5) * 0.8, y + (rr() - .5) * 0.8, 1.0 + shade * 1.5 + rr() * 0.5, 0, TAU); g.fill();
69
+ }
70
+ return cv;
71
+ }
72
+ function paperPlate() {
73
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(9);
74
+ const bg = g.createLinearGradient(0, 0, 0, H); bg.addColorStop(0, '#2c2218'); bg.addColorStop(1, '#17110c'); g.fillStyle = bg; g.fillRect(0, 0, W, H);
75
+ g.strokeStyle = 'rgba(255,230,190,.035)'; g.lineWidth = 2; for (let i = 0; i < 80; i++) { const y = r() * H; g.beginPath(); g.moveTo(0, y); g.bezierCurveTo(500, y + (r() - .5) * 30, 1300, y + (r() - .5) * 30, W, y + (r() - .5) * 20); g.stroke(); }
76
+ // 纸:不规则撕边+投影
77
+ const edge = []; const x0 = 120, y0 = 56, x1 = 1800, y1 = 1030;
78
+ for (let x = x0; x <= x1; x += 40) edge.push([x, y0 + (r() - .5) * 6]); for (let y = y0; y <= y1; y += 40) edge.push([x1 + (r() - .5) * 6, y]); for (let x = x1; x >= x0; x -= 40) edge.push([x, y1 + (r() - .5) * 6]); for (let y = y1; y >= y0; y -= 40) edge.push([x0 + (r() - .5) * 6, y]);
79
+ const sp = smooth(edge); g.save(); g.shadowColor = 'rgba(0,0,0,.6)'; g.shadowBlur = 40; g.shadowOffsetY = 12; g.fillStyle = '#dcc397'; g.fill(sp); g.restore();
80
+ g.save(); g.clip(sp);
81
+ const pg = g.createRadialGradient(960, 520, 200, 960, 540, 1050); pg.addColorStop(0, 'rgba(240,220,170,.35)'); pg.addColorStop(1, 'rgba(120,80,40,.35)'); g.fillStyle = pg; g.fillRect(0, 0, W, H);
82
+ for (let i = 0; i < 70; i++) { g.fillStyle = `rgba(${r() < 0.5 ? '150,100,50' : '110,80,40'},${0.03 + r() * 0.08})`; g.beginPath(); g.ellipse(x0 + r() * (x1 - x0), y0 + r() * (y1 - y0), 6 + r() * 40, 5 + r() * 30, r() * 3, 0, TAU); g.fill(); } // 霉斑
83
+ g.strokeStyle = 'rgba(120,90,50,.12)'; g.lineWidth = 1; for (let i = 0; i < 260; i++) { const x = x0 + r() * (x1 - x0), y = y0 + r() * (y1 - y0); g.beginPath(); g.moveTo(x, y); g.lineTo(x + (r() - .5) * 34, y + (r() - .5) * 10); g.stroke(); } // 纸纤维
84
+ // 注记:几行不成字的手写笔画(右下)
85
+ g.strokeStyle = 'rgba(90,40,24,.55)'; g.lineWidth = 1.8; g.lineCap = 'round'; for (let ln = 0; ln < 4; ln++) { let x = 1240; const y = 900 + ln * 30; g.beginPath(); g.moveTo(x, y); while (x < 1640 - r() * 100) { x += 6 + r() * 12; g.quadraticCurveTo(x - 4, y - 8 - r() * 8, x, y + (r() - .5) * 3); } g.stroke(); }
86
+ g.restore();
87
+ return cv;
88
+ }
89
+ function build() {
90
+ const f = formMap(), st = hatch(f, 5); big = renderChalk(f, st, 'big'); big._f = f;
91
+ const sm = P.canvas(FW, FH), sg = sm.getContext('2d'); sg.drawImage(big, 0, 0); small = sm; // 旧习作:同一人,翻转缩小,较淡
92
+ paper = paperPlate(); built = true;
93
+ }
94
+ function draw(c, lt) {
95
+ if (!built) build();
96
+ const t = clamp(lt, 0, 6);
97
+ c.drawImage(paper, 0, 0);
98
+ // 右侧旧习作:翻转、0.46 倍、较淡,画满
99
+ c.save(); c.globalAlpha = 0.72; c.translate(1560, 90); c.scale(-0.46, 0.46); c.translate(-FW, 0); c.drawImage(small, 0, 0, FW, FH * 0.9, 0, 0, FW, FH * 0.9); c.restore();
100
+ // 主习作:随笔尖从上往下被画出
101
+ const OX = 500, OY = 110, S = 1.0, p = ss(0.25, 5.2, t), ry = p * (FH + 30);
102
+ c.save(); c.beginPath(); c.rect(OX, OY, FW * S, ry * S); c.clip(); c.drawImage(big, OX, OY, FW * S, FH * S); c.restore();
103
+ c.save(); c.globalAlpha = 1; const edge = c.createLinearGradient(0, OY + ry * S - 26, 0, OY + ry * S); edge.addColorStop(0, 'rgba(220,195,150,0)'); edge.addColorStop(1, 'rgba(220,195,150,.0)'); c.restore();
104
+ if (p < 0.999) { // 粉笔:笔尖在揭示线上左右扫,笔杆朝右上
105
+ const tx = OX + FW * S * (0.5 + 0.46 * Math.sin(t * 7.3) * Math.cos(t * 1.9)), ty = OY + ry * S + Math.sin(t * 13) * 3;
106
+ c.save(); c.translate(tx, ty); c.rotate(-0.95); c.fillStyle = '#7a3a24'; c.fillRect(0, -5, 190, 10); c.fillStyle = '#9c3f26'; c.fillRect(-16, -5, 18, 10); c.fillStyle = '#c9a56a'; c.fillRect(120, -6, 60, 12); c.restore();
107
+ c.fillStyle = 'rgba(150,70,44,.28)'; for (let i = 0; i < 6; i++) { c.beginPath(); c.arc(tx + (Math.sin(t * 20 + i * 7) * 14), ty + 6 + ((t * 40 + i * 9) % 36), 1.3, 0, TAU); c.fill(); } // 落下的粉屑
108
+ }
109
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.18; c.drawImage(P.texture('paper_mich', '#e6d2aa', { scale: 0.006, amt: 12, grain: 8, seed: 3 }), 0, 0); c.restore();
110
+ }
111
+ return { draw };
112
+ })();
@@ -0,0 +1,70 @@
1
+ // 瓜尔迪(威尼斯晚期):低地平线、银灰绿的巨大空气、细长的舢板和小到只剩几点白的人、笔触干而快、边缘松动,像被风吹散。
2
+ // 题材(新构图):清晨的潟湖,两条细长的小船载着货横过水面,远处一座瘦塔和低矮的岛,天占四分之三。
3
+ // 原作锚定(fetch_reference.py:《威尼斯潟湖》《大运河与里亚托桥》;第三条搜索命中的是现代照片,已弃用,见 16 号「已知的坑」):
4
+ // 天水一色的银灰绿 #9db0b0→#c9cdbd、暖云边 #e2cfa8、塔与岛赭褐 #8a7a62、船黑褐 #2e2a26、人物=黑身+一两点白或红、水面细长横笔。
5
+ // 管线:干净的形(天/水/岛塔)→ MEDIUM.broken(细长干笔,低饱和)→ 缓存;动态:云漂、水面逐行位移、船与撑篙人(小画布重绘)、鸟。
6
+ SCENES['42_guardi'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
8
+ const HZ = 700; // 低地平线:天占 65%
9
+ let sky, land, water, built = false;
10
+ function skyPlate() {
11
+ const cv = P.canvas(2500, 720), g = cv.getContext('2d'), r = rng(12);
12
+ const gr = g.createLinearGradient(0, 0, 0, 720); gr.addColorStop(0, '#8fa3a8'); gr.addColorStop(0.55, '#b4c0bb'); gr.addColorStop(1, '#d9d2b4'); g.fillStyle = gr; g.fillRect(0, 0, 2500, 720);
13
+ for (let k = 0; k < 26; k++) { const x = r() * 2500, y = 30 + r() * 520, s = 0.6 + r() * 1.3; g.save(); g.filter = 'blur(12px)'; for (let j = 0; j < 7; j++) { const dx = (r() - .5) * 280 * s, dy = (r() - .5) * 40 * s, rx = (50 + r() * 80) * s, ry = (14 + r() * 18) * s;
14
+ g.fillStyle = `rgba(${r() < 0.5 ? '130,146,152' : '236,222,190'},${0.28 + r() * 0.25})`; g.beginPath(); g.ellipse(x + dx, y + dy, rx, ry, (r() - .5) * 0.1, 0, TAU); g.fill(); } g.restore(); }
15
+ return M.broken(cv, { cell: 10, len: 70, wid: 6, passes: 2, angle: (x, y) => -0.03 + Math.sin(x * 0.003 + y * 0.01) * 0.04, seed: 21, jit: 14, skip: 0.1, sat: 0.9, warm: [238, 214, 160], cool: [120, 138, 160] });
16
+ }
17
+ function landPlate() {
18
+ const cv = P.canvas(), g = cv.getContext('2d');
19
+ // 远岛(长而低)
20
+ g.fillStyle = '#8f8a78'; g.beginPath(); g.moveTo(0, HZ); g.lineTo(0, HZ - 16); g.quadraticCurveTo(200, HZ - 30, 420, HZ - 12); g.quadraticCurveTo(520, HZ - 8, 600, HZ); g.fill();
21
+ g.fillStyle = '#7d786a'; g.beginPath(); g.moveTo(1000, HZ); g.quadraticCurveTo(1120, HZ - 22, 1300, HZ - 14); g.lineTo(1500, HZ - 24); g.quadraticCurveTo(1780, HZ - 18, 1920, HZ - 34); g.lineTo(1920, HZ); g.fill();
22
+ // 瘦塔(右)+低屋+木桩
23
+ g.fillStyle = '#85735c'; g.fillRect(1480, HZ - 230, 70, 230); g.fillStyle = '#6b5a46'; g.fillRect(1480, HZ - 230, 18, 230); g.fillStyle = '#9a8670'; g.fillRect(1470, HZ - 246, 90, 18);
24
+ g.fillStyle = '#4a4036'; [[1492, HZ - 190], [1518, HZ - 150], [1494, HZ - 110]].forEach(([x, y]) => g.fillRect(x, y, 14, 28));
25
+ g.fillStyle = '#9a8670'; g.fillRect(1380, HZ - 66, 120, 66); g.fillStyle = '#7a4a3a'; g.beginPath(); g.moveTo(1370, HZ - 66); g.lineTo(1440, HZ - 100); g.lineTo(1510, HZ - 66); g.fill();
26
+ g.fillStyle = '#3a342e'; for (let i = 0; i < 9; i++) g.fillRect(1560 + i * 34, HZ - 12 - (i % 3) * 8, 5, 24);
27
+ // 左前景一角:渔人的木桩与网杆
28
+ g.strokeStyle = '#2e2a26'; g.lineWidth = 7; g.beginPath(); g.moveTo(150, 960); g.lineTo(190, 740); g.stroke(); g.lineWidth = 4; g.beginPath(); g.moveTo(190, 740); g.lineTo(400, 790); g.stroke(); g.beginPath(); g.moveTo(190, 760); g.quadraticCurveTo(260, 900, 400, 800); g.stroke();
29
+ return M.broken(cv, { cell: 7, len: 26, wid: 6, passes: 2, angle: (x, y) => y > 600 && x > 1400 ? 1.45 : 0.04, seed: 23, jit: 14, skip: 0.04, sat: 0.9 });
30
+ }
31
+ function waterPlate() {
32
+ const cv = P.canvas(), g = cv.getContext('2d'), r = rng(31);
33
+ const gr = g.createLinearGradient(0, HZ, 0, H); gr.addColorStop(0, '#b7c0b0'); gr.addColorStop(0.35, '#97a89e'); gr.addColorStop(1, '#5f7470'); g.fillStyle = gr; g.fillRect(0, HZ, W, H - HZ);
34
+ // 天光倒影:长条银色、暖色横带,越近越宽
35
+ for (let i = 0; i < 120; i++) { const k = Math.pow(r(), 1.4), y = HZ + 6 + k * (H - HZ - 10), w = 60 + r() * 300 * (0.3 + k), x = r() * W; g.fillStyle = `rgba(${r() < 0.55 ? '232,232,214' : '200,170,120'},${0.1 + r() * 0.2})`; g.fillRect(x, y, w, 2 + r() * 5 * (0.4 + k)); }
36
+ // 塔与岛的倒影
37
+ g.save(); g.translate(0, HZ * 2); g.scale(1, -1); g.globalAlpha = 0.35; g.filter = 'blur(2px)'; g.drawImage(landCanvas(), 0, 0); g.restore();
38
+ return M.broken(cv, { cell: 9, len: 80, wid: 5, passes: 2, angle: () => 0, seed: 25, jit: 14, skip: 0.06, sat: 0.9, cool: [100, 126, 140], warm: [230, 212, 168] });
39
+ }
40
+ let _landRaw = null; const landCanvas = () => { if (_landRaw) return _landRaw; const cv = P.canvas(), g = cv.getContext('2d'); g.fillStyle = '#6a604e'; g.fillRect(1480, HZ - 230, 70, 230); g.fillRect(1380, HZ - 66, 120, 66); g.fillRect(0, HZ - 16, 600, 16); g.fillRect(1000, HZ - 20, 920, 20); return (_landRaw = cv); };
41
+ function build() { sky = skyPlate(); land = landPlate(); water = waterPlate(); built = true; }
42
+ // 细长舢板+撑篙人:小画布(600×220)→ broken → 贴回;face 朝向
43
+ function gondola(c, x, y, face, t, ph, scale) {
44
+ const cv = P.scratch('guardi_gon'); if (cv.width !== 640) { cv.width = 640; cv.height = 260; } const g = cv.getContext('2d'); g.reset(); g.clearRect(0, 0, 640, 260);
45
+ g.save(); g.translate(320, 190); g.scale(face * scale, scale); const roll = Math.sin(t * 1.3 + ph) * 0.015; g.rotate(roll);
46
+ g.fillStyle = '#2a2622'; g.beginPath(); g.moveTo(-230, -14); g.quadraticCurveTo(-120, 12, 0, 10); g.quadraticCurveTo(130, 12, 236, -26); g.lineTo(210, -22); g.lineTo(-210, -8); g.closePath(); g.fill(); // 船身(弯月)
47
+ g.fillStyle = '#3a342e'; g.fillRect(-60, -46, 110, 34); g.fillStyle = '#5a2a2a'; g.fillRect(-60, -52, 110, 8); // felze 小舱
48
+ g.fillStyle = '#2a2622'; g.fillRect(-20, -20, 8, 12);
49
+ const pole = Math.sin(t * 2.0 + ph); // 撑篙人
50
+ g.fillStyle = '#2a2622'; g.fillRect(146, -100, 22, 84); g.beginPath(); g.arc(157, -112, 11, 0, TAU); g.fill(); g.fillStyle = '#e8e0cc'; g.fillRect(146, -94, 22, 20); g.fillStyle = '#b8402e'; g.fillRect(142, -126, 30, 8); // 黑身+白衫+红帽(几点颜色)
51
+ g.strokeStyle = '#2e2a26'; g.lineWidth = 4; g.lineCap = 'round'; g.beginPath(); g.moveTo(160, -88 - pole * 6); g.lineTo(120 + pole * 36, 56); g.stroke(); // 篙
52
+ g.restore();
53
+ const rep = M.broken(cv, { cell: 4, len: 13, wid: 3.2, passes: 2, angle: (xx, yy) => (yy > 150 && yy < 215) ? 0.02 : 1.2, seed: 27, jit: 12, skip: 0.02, sat: 0.95 });
54
+ // 倒影:被拉长压暗
55
+ c.save(); c.translate(x - 320, y - 190); c.save(); c.translate(0, 380); c.scale(1, -0.55); c.globalAlpha = 0.28; c.drawImage(rep, 0, 0); c.restore(); c.drawImage(rep, 0, 0); c.restore();
56
+ }
57
+ function draw(c, lt) {
58
+ if (!built) build();
59
+ const t = clamp(lt, 0, 6);
60
+ c.drawImage(sky, t * 14, 0, W, 720, 0, 0, W, 720);
61
+ c.drawImage(land, 0, 0);
62
+ for (let y = HZ; y < H; y += 6) { const k = (y - HZ) / (H - HZ), dx = Math.sin(t * 1.1 + y * 0.07) * (1 + k * 7) + Math.sin(t * 2.4 + y * 0.19) * 1.2 * k; c.drawImage(water, 0, y, W, 6, dx - 24, y, W + 48, 6); }
63
+ gondola(c, lerp(1280, 760, ss(0, 6, t)), 880, -1, t, 0.0, 1.0);
64
+ gondola(c, lerp(260, 680, ss(0, 6, t)), 820, 1, t, 2.1, 0.62);
65
+ // 鸟:几笔
66
+ for (let i = 0; i < 5; i++) { const u = (t * 0.06 + i * 0.19) % 1, x = lerp(-40, 1960, u), y = 220 + Math.sin(u * 9 + i) * 40 + i * 28, f = Math.sin(t * 8 + i) * 7; c.strokeStyle = 'rgba(46,42,38,.85)'; c.lineWidth = 2.6; c.lineCap = 'round'; c.beginPath(); c.moveTo(x - 12, y + f); c.quadraticCurveTo(x - 4, y - 6, x, y); c.quadraticCurveTo(x + 4, y - 6, x + 12, y + f); c.stroke(); }
67
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.24; c.drawImage(P.texture('canvas_guardi', '#e8e0cc', { scale: 0.006, amt: 12, grain: 7, seed: 8 }), 0, 0); c.restore();
68
+ }
69
+ return { draw };
70
+ })();
@@ -0,0 +1,145 @@
1
+ // 洛伦采蒂(锡耶纳市政厅《良政的效果》城市一幅):建筑占上半、一栋叠一栋,粉/灰/赭的墙,成排小窗,垛口塔;底层拱廊像舞台一样剖开露出作坊;街上是一条带状的小人,人只有楼高的十分之一,各干各的活。
2
+ // 题材(新构图,不搬名作):广场一角——圈舞、驮骡过街、脚手架吊石头、拱廊里的鞋匠,台阶往右上走。
3
+ // 原作锚定(fetch_reference.py:《良政的效果·城市》《良政寓言》;第三条搜索与第一条是同一幅,被去重丢弃,所以只有两幅不同作品,见 16 号协议「已知的坑」):
4
+ // 墙 粉红 #c98f86/灰蓝 #8f98a4/赭 #b39f8c,街 沙土 #b9a37c,天只剩顶上一线群青,窗=深褐小拱,人=土红/靛蓝/芥黄/白,边带深褐。
5
+ // 管线:干净的形层(天→后排建筑→主建筑含剖开的作坊→街→台阶)→ MEDIUM.fresco → 小人每帧画在 fresco 之后再轻糊(人太小,不过重绘)。
6
+ SCENES['43_lorenzetti'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
8
+ let plate, built = false;
9
+ const GROUND = () => M.plaster('lorenz', { base: '#dccfb2', seed: 13, seams: [[[0, 420], [500, 430], [1000, 414], [1500, 426], [1920, 418]]] });
10
+ const quad = (g, p, fill) => { g.fillStyle = fill; g.beginPath(); p.forEach((q, i) => i ? g.lineTo(q[0], q[1]) : g.moveTo(q[0], q[1])); g.closePath(); g.fill(); };
11
+ const line = (g, p, col = '#5a463a', a = 0.7) => M.outline(g, p, { w: [1.6, 2.6, 1.6], col, a });
12
+ // 小窗(深褐拱)
13
+ const win = (g, x, y, w = 20, h = 34, col = '#4a3a30') => { g.fillStyle = 'rgba(255,240,215,.45)'; g.beginPath(); g.moveTo(x - 3, y + h); g.lineTo(x - 3, y + w / 2); g.arc(x + w / 2, y + w / 2, w / 2 + 3, Math.PI, 0); g.lineTo(x + w + 3, y + h); g.fill(); g.fillStyle = col; g.beginPath(); g.moveTo(x, y + h); g.lineTo(x, y + w / 2); g.arc(x + w / 2, y + w / 2, w / 2, Math.PI, 0); g.lineTo(x + w, y + h); g.fill(); };
14
+ function block(g, x, y, w, h, c0, c1, rows, cols, o = {}) {
15
+ const gr = g.createLinearGradient(x, y, x + w, y + h); gr.addColorStop(0, c0); gr.addColorStop(1, c1); g.fillStyle = gr; g.fillRect(x, y, w, h);
16
+ { const rr = rng((x + y) | 0); g.save(); g.filter = 'blur(10px)'; for (let i = 0; i < 26; i++) { g.fillStyle = rr() < 0.5 ? 'rgba(255,240,220,.07)' : 'rgba(70,40,40,.06)'; g.beginPath(); g.ellipse(x + rr() * w, y + rr() * h, 50 + rr() * 110, 30 + rr() * 60, (rr() - .5) * 0.5, 0, TAU); g.fill(); } g.restore();
17
+ g.strokeStyle = 'rgba(80,50,44,.16)'; g.lineWidth = 1.4; for (let yy = y + 20; yy < y + h; yy += 36) { g.beginPath(); g.moveTo(x, yy); g.lineTo(x + w, yy + (rr() - .5) * 3); g.stroke(); }
18
+ g.fillStyle = 'rgba(40,24,24,.22)'; g.fillRect(x, y + h - 14, w, 14); g.fillStyle = 'rgba(255,240,210,.35)'; g.fillRect(x, y, w, 5); }
19
+ if (o.side) quad(g, [[x + w, y], [x + w + o.side, y - 26], [x + w + o.side, y + h - 30], [x + w, y + h]], o.sideCol || c1);
20
+ for (let r = 0; r < rows; r++) for (let k = 0; k < cols; k++) win(g, x + 24 + k * ((w - 48) / cols), y + 34 + r * ((h - 90) / rows), 20, 34);
21
+ if (o.cren) for (let k = 0; k < Math.floor(w / 44); k++) { g.fillStyle = c0; g.fillRect(x + 6 + k * 44, y - 26, 28, 28); }
22
+ line(g, [[x, y + h], [x, y], [x + w, y], [x + w, y + h]]);
23
+ }
24
+ function build() {
25
+ plate = P.canvas(); const g = plate.getContext('2d'), r = rng(8);
26
+ // 天:只剩顶上一线群青
27
+ const sg = g.createLinearGradient(0, 0, 0, 440); sg.addColorStop(0, '#34569e'); sg.addColorStop(0.35, '#8aa2cc'); sg.addColorStop(1, '#d6cdb8'); g.fillStyle = sg; g.fillRect(0, 0, W, 440);
28
+ // 后排:远处的塔与屋顶,色彩略淡(空气)
29
+ block(g, 560, 70, 150, 470, '#a9a7ae', '#8a8890', 6, 3, { cren: true });
30
+ block(g, 330, 190, 250, 360, '#cfa59c', '#b88478', 4, 5);
31
+ block(g, 740, 150, 330, 400, '#bdb09a', '#a09078', 5, 6, { cren: true });
32
+ block(g, 1100, 120, 260, 430, '#c99a8f', '#a8766a', 5, 4);
33
+ // 屋顶瓦(后排):赭红斜面
34
+ [[330, 190, 250], [1100, 120, 260]].forEach(([x, y, w]) => { quad(g, [[x - 14, y], [x + 40, y - 46], [x + w - 40, y - 46], [x + w + 14, y]], '#a85a44'); line(g, [[x - 14, y], [x + 40, y - 46], [x + w - 40, y - 46], [x + w + 14, y]]); });
35
+ // 中景:一整排退远的低楼与城墙(填满中间,空气更淡),再往街面收
36
+ const far = g.createLinearGradient(0, 430, 0, 760); far.addColorStop(0, '#d6cdb8'); far.addColorStop(1, '#bfae96'); g.fillStyle = far; g.fillRect(560, 430, 800, 330);
37
+ block(g, 600, 400, 210, 360, '#c8a89a', '#a88a7c', 4, 4);
38
+ block(g, 830, 470, 250, 290, '#c2b6a2', '#a6987f', 3, 5, { cren: true });
39
+ block(g, 1090, 440, 170, 320, '#bd9c90', '#9c7c70', 4, 3);
40
+ quad(g, [[586, 400], [640, 360], [790, 360], [826, 400]], '#a85a44'); quad(g, [[1076, 440], [1110, 404], [1230, 404], [1274, 440]], '#9c503c');
41
+ g.fillStyle = 'rgba(70,50,40,.22)'; g.fillRect(560, 700, 800, 60); // 楼脚的暗带
42
+ // 主建筑左:粉红大楼,底层拱廊剖开(舞台)
43
+ block(g, 0, 120, 560, 640, '#d6a199', '#b87d72', 5, 6, { side: 70, sideCol: '#9c6a60', cren: true });
44
+ // 主建筑右:赭灰带阳台
45
+ block(g, 1340, 90, 580, 680, '#b7b3b6', '#8f8c94', 5, 6, { cren: true });
46
+ g.fillStyle = '#8a4a3c'; for (let k = 0; k < 4; k++) g.fillRect(1380 + k * 130, 330 + (k % 2) * 140, 74, 10); // 阳台
47
+ // 拱廊(剖开的作坊):左楼底层
48
+ const ax = 70, ay = 500, aw = 360, ah = 260;
49
+ g.fillStyle = '#3d2f28'; g.beginPath(); g.moveTo(ax, ay + ah); g.lineTo(ax, ay + 70); g.arc(ax + aw / 2, ay + 70, aw / 2, Math.PI, 0); g.lineTo(ax + aw, ay + ah); g.fill();
50
+ const ig = g.createLinearGradient(0, ay, 0, ay + ah); ig.addColorStop(0, '#9a7d5e'); ig.addColorStop(1, '#c4a97c'); g.fillStyle = ig;
51
+ g.beginPath(); g.moveTo(ax + 14, ay + ah); g.lineTo(ax + 14, ay + 70); g.arc(ax + aw / 2, ay + 70, aw / 2 - 14, Math.PI, 0); g.lineTo(ax + aw - 14, ay + ah); g.fill();
52
+ g.fillStyle = '#6a4a34'; g.fillRect(ax + 40, ay + 170, 150, 14); g.fillRect(ax + 50, ay + 184, 10, 76); g.fillRect(ax + 170, ay + 184, 10, 76); // 工作台
53
+ g.fillStyle = '#b8402e'; [[ax + 230, ay + 120], [ax + 270, ay + 140], [ax + 300, ay + 110]].forEach(([x, y]) => { g.beginPath(); g.ellipse(x, y, 16, 10, 0, 0, TAU); g.fill(); }); // 货架上的鞋
54
+ g.strokeStyle = '#e4cf8f'; g.lineWidth = 7; g.beginPath(); g.arc(ax + aw / 2, ay + 70, aw / 2 + 4, Math.PI, 0); g.stroke();
55
+ // 右楼底层:开放的柱廊+布匹
56
+ for (let k = 0; k < 4; k++) { const x = 1360 + k * 140; g.fillStyle = '#e1d2b4'; g.fillRect(x, 560, 26, 200); g.fillStyle = '#4a3a30'; g.beginPath(); g.moveTo(x + 26, 760); g.lineTo(x + 26, 620); g.arc(x + 83, 620, 57, Math.PI, 0); g.lineTo(x + 140, 760); g.fill(); }
57
+ [['#b8402e', 1400], ['#2f5d94', 1540], ['#d9b04a', 1680]].forEach(([c, x]) => { quad(g, [[x, 640], [x + 60, 640], [x + 66, 740], [x - 6, 740]], c); });
58
+ // 脚手架:右楼左侧一排木桩与横梁
59
+ g.strokeStyle = '#6a4a34'; g.lineWidth = 7; [1250, 1320].forEach(x => { g.beginPath(); g.moveTo(x, 770); g.lineTo(x, 190); g.stroke(); }); [260, 400, 540, 680].forEach(y => { g.beginPath(); g.moveTo(1240, y); g.lineTo(1330, y); g.stroke(); });
60
+ // 街:沙土,斜向透视,前景暗、远处亮
61
+ const gr = g.createLinearGradient(0, 740, 0, H); gr.addColorStop(0, '#c9b58a'); gr.addColorStop(0.6, '#b49c70'); gr.addColorStop(1, '#8c7650'); g.fillStyle = gr; g.fillRect(0, 740, W, H - 740);
62
+ for (let i = 0; i < 380; i++) { g.fillStyle = `rgba(${r() < 0.5 ? '90,70,44' : '224,206,158'},${0.1 + r() * 0.16})`; g.fillRect(r() * W, 760 + r() * 300, 6 + r() * 40, 2 + r() * 2); }
63
+ // 台阶(往右上):红砖楼梯
64
+ for (let k = 0; k < 7; k++) { const x = 1080 + k * 40, y = 790 - k * 18; quad(g, [[x, y], [x + 40, y], [x + 40, 800], [x, 800]], k % 2 ? '#a8645a' : '#b8786a'); line(g, [[x, y], [x + 40, y]], '#5a3a30', 0.5); }
65
+ quad(g, [[0, 760], [560, 760], [420, 1004], [0, 1004]], 'rgba(60,44,60,.18)'); // 左楼的影子落在街上
66
+ // 前景下缘的深褐边带+「题记」(一行假笔画,不写成真字)
67
+ g.fillStyle = '#e8dcc0'; g.fillRect(0, 1010, W, 70); g.fillStyle = '#4a3a30'; g.fillRect(0, 1004, W, 8); g.fillRect(0, 1076, W, 4);
68
+ for (let x = 60; x < W - 60; x += 16 + (r() * 30 | 0)) { g.fillRect(x, 1030 + (r() * 4 | 0), 6 + r() * 8, 20); }
69
+ built = true;
70
+ }
71
+ // 小人(修长的锡耶纳式):头小身长、长袍下摆外撇带褶、肩窄、3/4 侧、袍色左亮右暗;走路时下摆摆动、前脚探出
72
+ function tiny(g, x, y, s, o) {
73
+ const { robe = '#b8402e', hat = null, skin = '#e2b48a', face = 1, arm = 0, walk = 0, hair = '#4a3322', under = null, carry = null } = o;
74
+ s *= 0.95;
75
+ const dk = M.css(M.mix(robe, '#1c1428', 0.38)), lt = M.css(M.mix(robe, '#fff4dc', 0.3)), mid = M.css(robe);
76
+ g.fillStyle = 'rgba(60,40,24,.26)'; g.beginPath(); g.ellipse(x, y + 2, 21 * s, 5 * s, 0, 0, TAU); g.fill();
77
+ g.save(); g.translate(x, y); g.scale(s * face, s);
78
+ const sw = Math.sin(walk), bob = Math.abs(sw) * 1.6, H0 = -104 - bob;
79
+ // 前脚探出下摆
80
+ g.fillStyle = '#3d2a24'; g.beginPath(); g.ellipse(7 + sw * 7, -2, 7, 3.2, 0, 0, TAU); g.ellipse(-6 - sw * 7, -2, 7, 3.2, 0, 0, TAU); g.fill();
81
+ // 袍:窄肩 → 腰 → 外撇下摆,下摆随步子左右摆
82
+ const hem = 18, fl = sw * 4;
83
+ const gr = g.createLinearGradient(-hem, 0, hem, 0); gr.addColorStop(0, lt); gr.addColorStop(0.45, mid); gr.addColorStop(1, dk); g.fillStyle = gr;
84
+ g.beginPath(); g.moveTo(-8, H0 + 22); g.quadraticCurveTo(-10, -62, -13 + fl * 0.3, -34); g.quadraticCurveTo(-hem - 2 + fl, -14, -hem + fl, -4);
85
+ g.quadraticCurveTo(0, 2, hem + fl, -4); g.quadraticCurveTo(hem - 1 + fl, -16, 12 + fl * 0.3, -34); g.quadraticCurveTo(10, -62, 8, H0 + 22); g.closePath(); g.fill();
86
+ // 褶:三条自上而下的弧线,下摆处加深
87
+ g.strokeStyle = dk; g.globalAlpha = 0.5; g.lineWidth = 1.6; g.lineCap = 'round';
88
+ [[-4, -2], [2, 4], [8, 9]].forEach(([u, v]) => { g.beginPath(); g.moveTo(u * 0.5, -66); g.quadraticCurveTo(u + v * 0.3, -36, u * 1.8 + fl, -5); g.stroke(); }); g.globalAlpha = 1;
89
+ // 腰带+下摆镶边
90
+ g.fillStyle = 'rgba(30,20,20,.35)'; g.fillRect(-10, -66, 20, 3.5);
91
+ g.strokeStyle = under || lt; g.lineWidth = 2.2; g.beginPath(); g.moveTo(-hem + fl, -4); g.quadraticCurveTo(0, 2, hem + fl, -4); g.stroke();
92
+ // 袖与手:上臂沿身侧,前臂随 arm 抬起(拉手/持物)
93
+ const ax = 9, ay = H0 + 26, ex = 13 + arm * 7, ey = -64 + arm * 8, hx = 14 + arm * 11, hy = -52 - arm * 22;
94
+ g.strokeStyle = mid; g.lineWidth = 6.5; g.beginPath(); g.moveTo(-ax, ay); g.lineTo(-ex, ey); g.lineTo(-hx, hy); g.moveTo(ax, ay); g.lineTo(ex, ey); g.lineTo(hx, hy); g.stroke();
95
+ g.fillStyle = skin; g.beginPath(); g.arc(-hx, hy, 3, 0, TAU); g.arc(hx, hy, 3, 0, TAU); g.fill();
96
+ // 颈、头(小,3/4:脸偏一侧,耳后一团头发)
97
+ g.fillStyle = skin; g.fillRect(-2.6, H0 + 12, 5.2, 7);
98
+ g.beginPath(); g.ellipse(0.8, H0 + 5, 6.4, 7.6, 0, 0, TAU); g.fill();
99
+ g.fillStyle = 'rgba(120,60,40,.25)'; g.beginPath(); g.ellipse(-2.8, H0 + 5, 3.2, 6.8, 0, 0, TAU); g.fill();
100
+ g.fillStyle = hair; g.beginPath(); g.ellipse(-2.4, H0 + 1.5, 6.2, 6.4, 0, Math.PI * 0.55, Math.PI * 1.9); g.fill();
101
+ g.fillStyle = '#3a2a22'; g.fillRect(3.6, H0 + 4, 1.8, 1.8);
102
+ if (hat) { g.fillStyle = M.css(hat); g.beginPath(); g.ellipse(-0.6, H0 - 1.5, 7.8, 4.2, 0, Math.PI, TAU); g.fill(); g.fillRect(-7.4, H0 - 3, 14.6, 3); g.fillStyle = 'rgba(0,0,0,.2)'; g.fillRect(-7.4, H0 - 0.5, 14.6, 1.6); }
103
+ if (carry) { g.fillStyle = carry; g.beginPath(); g.ellipse(hx + 6, hy - 6, 9, 7, 0, 0, TAU); g.fill(); }
104
+ g.restore();
105
+ }
106
+ const COL = [['#b8402e', null], ['#2f5d94', '#2f5d94'], ['#d9b04a', null], ['#f1ead6', '#b8402e'], ['#6e8a52', null], ['#8c3d5a', '#8c3d5a']];
107
+ function draw(c, lt) {
108
+ if (!built) build();
109
+ const t = clamp(lt, 0, 6);
110
+ const live = P.scratch('lorenz_live'), g = live.getContext('2d'); g.reset(); g.drawImage(plate, 0, 0);
111
+ // 圈舞:六人绕椭圆,彼此牵手,前后按 y 排序
112
+ const cx = 760, cy = 850, rx = 150, ry = 36, rot = t * 0.9, ring = [];
113
+ for (let i = 0; i < 6; i++) { const a = rot + i / 6 * TAU; ring.push({ x: cx + Math.cos(a) * rx, y: cy + Math.sin(a) * ry, i, a }); }
114
+ g.strokeStyle = '#3d2f28'; g.lineWidth = 3; g.beginPath(); ring.slice().sort((a, b) => a.i - b.i).forEach((p, k, arr) => { const q = arr[(k + 1) % 6]; g.moveTo(p.x, p.y - 44); g.lineTo(q.x, q.y - 44); }); g.stroke();
115
+ ring.sort((p, q) => p.y - q.y).forEach(p => { tiny(g, p.x, p.y, 1.15, { robe: COL[p.i][0], hat: COL[p.i][1], face: Math.sin(p.a) > 0 ? 1 : -1, arm: 0.5 + Math.sin(t * 4 + p.i) * 0.12, walk: t * 6 + p.i }); });
116
+ // 满街人流:十几个行人,各自按自己的速度和方向在街上走(循环),按 y 近大远小;两三人一伙并肩,一人带货
117
+ const WALK = [[0,830,.07,'#b8402e','#2f5d94'],[0,842,.07,'#f1ead6',null],[0,990,.05,'#2f5d94',null],[0,1002,.05,'#8c3d5a','#b8402e'],[0,880,.04,'#6e8a52','#d9b04a'],
118
+ [1,900,.045,'#d9b04a',null],[1,914,.045,'#b8402e','#2f5d94'],[1,928,.045,'#6e8a52','#8c3d5a'],[0,950,.06,'#f1ead6','#8c3d5a'],[0,800,.035,'#2f5d94','#b8402e'],[1,812,.035,'#d9b04a',null],[1,972,.055,'#6e8a52',null],[0,860,.03,'#8c3d5a',null],
119
+ [1,820,.04,'#b8402e',null],[1,834,.04,'#f1ead6','#2f5d94'],[0,918,.05,'#2f5d94','#d9b04a'],[0,932,.05,'#d9b04a',null],[1,1000,.03,'#8c3d5a','#2f5d94'],[0,792,.05,'#6e8a52',null],[1,968,.06,'#b8402e','#8c3d5a']];
120
+ const people = WALK.map((w, i) => { const dir = w[0] ? -1 : 1, u = ((i * 0.137 + t * w[2] * dir) % 1 + 1) % 1; return { x: -60 + u * 2040, y: w[1], s: 0.82 + (w[1] - 790) / 400, dir, c: w[3], h: w[4], ph: t * 6 * (0.6 + w[2] * 8) + i * 1.9, i }; });
121
+ people.sort((a, b) => a.y - b.y).forEach(q => { if (q.x > 1040 && q.x < 1400 && q.y < 830) return; if (Math.abs(q.x - cx) < rx + 60 && Math.abs(q.y - cy) < ry + 70) return; tiny(g, q.x, q.y, q.s, { robe: q.c, hat: q.h, face: q.dir, walk: q.ph, arm: 0.1, carry: q.i % 5 === 2 ? '#d6c79a' : null }); });
122
+ // 驮骡过街:左→右,驮两只麻袋,驭者在前
123
+ const mu = lerp(-300, 1900, ss(0, 6, t)), my = 1002;
124
+ g.save(); g.translate(mu, my); g.scale(0.85, 0.85); g.fillStyle = '#7a6a5a'; g.beginPath(); g.ellipse(0, -54, 70, 30, 0, 0, TAU); g.fill(); g.fillRect(48, -86, 30, 40); g.beginPath(); g.ellipse(86, -86, 22, 14, -0.3, 0, TAU); g.fill();
125
+ for (let l = 0; l < 4; l++) { const sw = Math.sin(t * 7 + l * 1.6) * 6; g.fillRect(-50 + l * 32 + sw * (l % 2 ? 1 : -1), -30, 10, 34); }
126
+ g.fillStyle = '#d6c79a'; g.beginPath(); g.ellipse(-20, -90, 30, 24, 0, 0, TAU); g.ellipse(26, -92, 28, 22, 0, 0, TAU); g.fill(); g.restore();
127
+ tiny(g, mu + 150, my + 6, 1.3, { robe: '#d9b04a', hat: '#2f5d94', walk: t * 7 });
128
+ // 作坊里的鞋匠:槌起槌落
129
+ const hm = Math.abs(Math.sin(t * 6));
130
+ tiny(g, 190, 688, 1.7, { robe: '#2f5d94', hat: '#b8402e', arm: 0.2 + hm * 0.8 });
131
+ g.strokeStyle = '#4a3a30'; g.lineWidth = 5; g.beginPath(); g.moveTo(224, 646 - hm * 22); g.lineTo(246, 622 - hm * 30); g.stroke();
132
+ // 脚手架:吊石头(绳从顶梁下来),上面一人拉绳
133
+ const up = ss(0.4, 5.6, t), sy = lerp(740, 330, up);
134
+ g.strokeStyle = '#6a4a34'; g.lineWidth = 3; g.beginPath(); g.moveTo(1285, 260); g.lineTo(1285, sy - 8); g.stroke();
135
+ g.fillStyle = '#9a9488'; g.beginPath(); g.moveTo(1262, sy); g.lineTo(1308, sy); g.lineTo(1304, sy + 36); g.lineTo(1266, sy + 38); g.closePath(); g.fill();
136
+ tiny(g, 1290, 262, 1.3, { robe: '#d9b04a', hat: '#8c3d5a', arm: 0.6 + Math.sin(t * 5) * 0.2, face: -1 });
137
+ tiny(g, 1180, 786, 1.5, { robe: '#b8402e', hat: '#2f5d94', arm: 0.5 + Math.sin(t * 5) * 0.25 }); // 地面拉绳的人
138
+ // 台阶上下几个行人
139
+ [[1130, 744, '#6e8a52'], [1250, 672, '#f1ead6']].forEach(([x, y, col], i) => tiny(g, x + Math.sin(t * 0.8 + i) * 8, y, 1.4, { robe: col, hat: i ? '#b8402e' : null, walk: t * 5 + i, face: -1 }));
140
+ // 塔顶小旗
141
+ g.fillStyle = '#b8402e'; g.beginPath(); g.moveTo(635, 38); g.lineTo(700 + Math.sin(t * 5) * 6, 52); g.lineTo(635, 70); g.fill(); g.strokeStyle = '#4a3a30'; g.lineWidth = 3; g.beginPath(); g.moveTo(635, 30); g.lineTo(635, 80); g.stroke();
142
+ M.fresco(c, live, { ground: GROUND(), chalk: 0.06, soften: 0.6, plasterAlpha: 0.5, sat: 1.1 });
143
+ }
144
+ return { draw };
145
+ })();
@@ -0,0 +1,86 @@
1
+ // 科尔托纳(巴洛克天顶,巴贝里尼宫《神意的寓言》):画出来的灰泥框和镀金圆章把天顶「打开」,中间是一片从中心往外发暖金光的天;云团堆成体积,人群绕着中心旋转,身体、衣带和云都漫出框外。
2
+ // 题材(新构图,不搬名作):一个仰视的天顶——灰泥画框里的天空开口,一圈人在云上绕着光旋转,红蓝衣带随风绕过画框。
3
+ // 原作锚定(fetch_reference.py:《神意的寓言》天顶、《劫掠萨宾妇女》;第三条搜索与第一条同幅被丢弃,只有两幅不同原作):
4
+ // 框=象牙灰泥 #e4d8be+镀金 #c89a3c+深棕阴影、角上金圆章;天=中心 #fff0c0 → #e8c07a → 边缘群青 #3a4a82;云内亮外暗;袍色猩红 #b83a3a、群青 #3a5ea8、赭 #c89a4a;对角线运动。
5
+ // 管线:形层(暗背景→画框灰泥带+角圆章+蛋形纹→开口天空渐变与光芒→云团)→ 人群与衣带(旋转,漫出框)→ MEDIUM.fresco(轻糊、灰泥颗粒)→ 中心光再叠一层加法光。
6
+ SCENES['44_cortona'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
8
+ const CX = 960, CY = 540, RX = 560, RY = 300;
9
+ const ROB = { red: ['#f08a7c', '#b83a3a', '#6a1c26'], blue: ['#9ab4ea', '#3a5ea8', '#1c2f62'], ochre: ['#f2d896', '#c89a4a', '#7a5a22'], white: ['#fffaf0', '#e8dcc0', '#a89868'], green: ['#a8d0a0', '#5a8a64', '#2c4a3a'], rose: ['#f6c0c0', '#d27a86', '#8a3a4a'] };
10
+ let plate, built = false;
11
+ const rr = (g, x, y, w, h, r) => { g.beginPath(); g.moveTo(x + r, y); g.arcTo(x + w, y, x + w, y + h, r); g.arcTo(x + w, y + h, x, y + h, r); g.arcTo(x, y + h, x, y, r); g.arcTo(x, y, x + w, y, r); g.closePath(); };
12
+ function cloud(g, x, y, r, seed) { // 云团:一个整体的凹凸轮廓,沿朝向中心的方向打光(内亮外暗),外侧弧线勾出团块
13
+ const rn = rng(seed), dx = CX - x, dy = CY - y, d = Math.hypot(dx, dy) || 1, ux = dx / d, uy = dy / d, cs = [];
14
+ for (let i = 0; i < 8; i++) cs.push([x + (rn() - .5) * r * 2.0, y + (rn() - .5) * r * 0.9, r * (0.34 + rn() * 0.34)]);
15
+ const path = new Path2D(); cs.forEach(([px, py, rad]) => { path.moveTo(px + rad, py); path.arc(px, py, rad, 0, TAU); });
16
+ g.save(); g.clip(path);
17
+ const gr = g.createLinearGradient(x - ux * r * 1.4, y - uy * r * 1.4, x + ux * r * 1.4, y + uy * r * 1.4); gr.addColorStop(0, '#6e6890'); gr.addColorStop(0.35, '#c3a6a2'); gr.addColorStop(0.7, '#f0cc98'); gr.addColorStop(1, '#fff3d0');
18
+ g.fillStyle = gr; g.fillRect(x - r * 2.4, y - r * 2, r * 4.8, r * 4);
19
+ cs.forEach(([px, py, rad]) => { g.strokeStyle = 'rgba(86,76,120,.3)'; g.lineWidth = Math.max(4, rad * 0.14); g.beginPath(); g.arc(px, py, rad * 0.92, Math.atan2(-uy, -ux) - 1.0, Math.atan2(-uy, -ux) + 1.0); g.stroke();
20
+ g.strokeStyle = 'rgba(255,248,222,.4)'; g.lineWidth = Math.max(3, rad * 0.08); g.beginPath(); g.arc(px, py, rad * 0.9, Math.atan2(uy, ux) - 0.8, Math.atan2(uy, ux) + 0.8); g.stroke(); });
21
+ g.restore();
22
+ }
23
+ function build() {
24
+ plate = P.canvas(); const g = plate.getContext('2d'), rn = rng(4);
25
+ g.fillStyle = '#2a1e1c'; g.fillRect(0, 0, W, H);
26
+ // 开口天空:中心暖金 → 边缘群青,加放射光
27
+ const ix = 190, iy = 118, iw = 1540, ih = 844, ir = 150;
28
+ g.save(); rr(g, ix, iy, iw, ih, ir); g.clip();
29
+ const sg = g.createRadialGradient(CX, CY - 20, 20, CX, CY, 900); sg.addColorStop(0, '#fff6d0'); sg.addColorStop(0.22, '#f4d58e'); sg.addColorStop(0.5, '#c2a0a0'); sg.addColorStop(0.8, '#5a669a'); sg.addColorStop(1, '#2c3a72'); g.fillStyle = sg; g.fillRect(0, 0, W, H);
30
+ g.globalCompositeOperation = 'screen'; for (let i = 0; i < 28; i++) { const a = i / 28 * TAU, w = 0.04 + (i % 3) * 0.02; g.fillStyle = 'rgba(255,238,190,.09)'; g.beginPath(); g.moveTo(CX, CY); g.lineTo(CX + Math.cos(a - w) * 1100, CY + Math.sin(a - w) * 1100); g.lineTo(CX + Math.cos(a + w) * 1100, CY + Math.sin(a + w) * 1100); g.closePath(); g.fill(); } g.globalCompositeOperation = 'source-over';
31
+ // 环绕的云团
32
+ for (let i = 0; i < 16; i++) { const a = i / 16 * TAU + rn() * 0.2, k = 0.9 + rn() * 0.2; cloud(g, CX + Math.cos(a) * RX * k * 1.05, CY + Math.sin(a) * RY * k * 1.12, 90 + rn() * 70, 40 + i); }
33
+ for (let i = 0; i < 4; i++) { const a = i / 4 * TAU + 0.4; cloud(g, CX + Math.cos(a) * 250, CY + Math.sin(a) * 130, 60 + rn() * 30, 90 + i); }
34
+ g.restore();
35
+ // 画框:三层灰泥带,向内一层比一层亮,外层有深色阴影缝;内缘金线;蛋形纹
36
+ const bands = [[0, '#9c8a6e'], [34, '#e4d8be'], [74, '#cbbd9c'], [112, '#f0e6cc'], [140, '#c89a3c']];
37
+ bands.forEach(([off, col], k) => { g.fillStyle = col; rr(g, ix - 150 + off, iy - 100 + off * 0.65, iw + 300 - off * 2, ih + 200 - off * 1.3, ir + 150 - off); g.fill(); });
38
+ g.save(); rr(g, ix, iy, iw, ih, ir); g.clip(); g.fillStyle = 'rgba(0,0,0,.12)'; g.fillRect(0, 0, W, H); g.restore();
39
+ g.globalCompositeOperation = 'destination-over'; g.globalCompositeOperation = 'source-over';
40
+ // 重画天空开口(帧压住了),保证「开口」在帧之上
41
+ g.save(); rr(g, ix, iy, iw, ih, ir); g.clip(); g.drawImage(skyOnly(), 0, 0); g.restore();
42
+ g.strokeStyle = '#c89a3c'; g.lineWidth = 12; rr(g, ix, iy, iw, ih, ir); g.stroke(); g.strokeStyle = 'rgba(60,40,20,.5)'; g.lineWidth = 4; rr(g, ix - 8, iy - 8, iw + 16, ih + 16, ir + 8); g.stroke();
43
+ // 蛋形纹沿框走
44
+ g.fillStyle = '#f6eed8'; for (let i = 0; i < 70; i++) { const q = i / 70, th = q * TAU, x = CX + Math.cos(th) * (iw / 2 + 52), y = CY + Math.sin(th) * (ih / 2 + 52); g.save(); g.translate(x, y); g.rotate(th + Math.PI / 2); g.beginPath(); g.ellipse(0, 0, 6, 11, 0, 0, TAU); g.fill(); g.strokeStyle = 'rgba(120,96,60,.6)'; g.lineWidth = 1.6; g.stroke(); g.restore(); }
45
+ // 四角金圆章
46
+ [[ix - 20, iy - 14], [ix + iw + 20, iy - 14], [ix - 20, iy + ih + 14], [ix + iw + 20, iy + ih + 14]].forEach(([x, y]) => { const gr = g.createRadialGradient(x - 14, y - 14, 4, x, y, 76); gr.addColorStop(0, '#fff0b8'); gr.addColorStop(0.5, '#d0a040'); gr.addColorStop(1, '#7a5a1c'); g.fillStyle = gr; g.beginPath(); g.arc(x, y, 76, 0, TAU); g.fill(); g.strokeStyle = '#f6eed8'; g.lineWidth = 9; g.stroke(); g.strokeStyle = 'rgba(80,50,20,.7)'; g.lineWidth = 3; g.beginPath(); g.arc(x, y, 50, 0, TAU); g.stroke(); });
47
+ // 外缘最深的阴影,把画框「压」进天顶
48
+ const vg = g.createRadialGradient(CX, CY, 600, CX, CY, 1150); vg.addColorStop(0, 'rgba(30,20,20,0)'); vg.addColorStop(1, 'rgba(30,20,20,.55)'); g.fillStyle = vg; g.fillRect(0, 0, W, H);
49
+ built = true;
50
+ }
51
+ let _sky = null; function skyOnly() { if (_sky) return _sky; const cv = P.canvas(), g = cv.getContext('2d'), rn = rng(4);
52
+ const sg = g.createRadialGradient(CX, CY - 20, 20, CX, CY, 900); sg.addColorStop(0, '#fff6d0'); sg.addColorStop(0.22, '#f4d58e'); sg.addColorStop(0.5, '#c2a0a0'); sg.addColorStop(0.8, '#5a669a'); sg.addColorStop(1, '#2c3a72'); g.fillStyle = sg; g.fillRect(0, 0, W, H);
53
+ g.globalCompositeOperation = 'screen'; for (let i = 0; i < 28; i++) { const a = i / 28 * TAU, w = 0.04 + (i % 3) * 0.02; g.fillStyle = 'rgba(255,238,190,.09)'; g.beginPath(); g.moveTo(CX, CY); g.lineTo(CX + Math.cos(a - w) * 1100, CY + Math.sin(a - w) * 1100); g.lineTo(CX + Math.cos(a + w) * 1100, CY + Math.sin(a + w) * 1100); g.closePath(); g.fill(); } g.globalCompositeOperation = 'source-over';
54
+ for (let i = 0; i < 16; i++) { const a = i / 16 * TAU + rn() * 0.2, k = 0.9 + rn() * 0.2; cloud(g, CX + Math.cos(a) * RX * k * 1.05, CY + Math.sin(a) * RY * k * 1.12, 90 + rn() * 70, 40 + i); }
55
+ for (let i = 0; i < 4; i++) { const a = i / 4 * TAU + 0.4; cloud(g, CX + Math.cos(a) * 250, CY + Math.sin(a) * 130, 60 + rn() * 30, 90 + i); }
56
+ return (_sky = cv); }
57
+ // 衣带:沿螺旋线的一条绸,宽度起伏、两面深浅不同
58
+ function ribbon(g, t, ph, col, R0, dir) {
59
+ const N = 60, L = [], Rr = [];
60
+ for (let i = 0; i <= N; i++) { const q = i / N, a = ph + dir * (t * 0.35 + q * 2.4), r = lerp(0.55, 1.05, q + Math.sin(q * 6 + t) * 0.05), x = CX + Math.cos(a) * RX * r * R0, y = CY + Math.sin(a) * RY * r * R0, w = 46 * Math.sin(q * Math.PI) * (0.6 + 0.4 * Math.cos(q * 9 + t * 2));
61
+ const nl = Math.hypot(Math.cos(a) * RY, Math.sin(a) * RX) || 1, nx = Math.cos(a) * RY / nl, ny = Math.sin(a) * RX / nl; L.push([x + nx * w, y + ny * w]); Rr.push([x - nx * w, y - ny * w]); }
62
+ g.fillStyle = col[1]; g.beginPath(); L.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); Rr.reverse().forEach(p => g.lineTo(p[0], p[1])); g.closePath(); g.fill();
63
+ g.strokeStyle = col[0]; g.lineWidth = 3; g.beginPath(); L.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); g.stroke();
64
+ }
65
+ function draw(c, lt) {
66
+ if (!built) build();
67
+ const t = clamp(lt, 0, 6);
68
+ const live = P.scratch('cort_live'), g = live.getContext('2d'); g.reset(); g.drawImage(plate, 0, 0);
69
+ ribbon(g, t, 0.5, ROB.red, 0.95, 1); ribbon(g, t, 3.4, ROB.blue, 0.82, 1);
70
+ // 人群:绕中心旋转,头朝外(徐徐翻转),手臂举向光;半数漫出画框
71
+ const FIG = [['red', 'ochre', 0.0, 260, 1], ['blue', 'rose', 0.7, 240, -1], ['white', 'blue', 1.4, 250, 1], ['ochre', 'red', 2.1, 230, -1], ['green', 'white', 2.8, 255, 1], ['rose', 'blue', 3.5, 235, -1], ['blue', 'ochre', 4.2, 245, 1], ['red', 'green', 4.9, 240, -1], ['white', 'rose', 5.6, 250, 1]];
72
+ FIG.forEach(([rb, mt, a0, h, face], i) => {
73
+ const a = a0 + t * 0.22, k = 0.92 + 0.12 * Math.sin(a0 * 2.1), x = CX + Math.cos(a) * RX * k * 1.04, y = CY + Math.sin(a) * RY * k * 1.1, rot = a + Math.PI / 2 + 0.5 * Math.sin(t * 0.8 + i);
74
+ g.save(); g.translate(x, y); g.rotate(rot + Math.PI); const sz = h * (0.78 + 0.22 * ((Math.sin(a) + 1) / 2));
75
+ M.figure(g, { x: 0, y: sz * 0.5, h: sz, face, lean: 0.12 * Math.sin(t * 1.3 + i), bow: 0.1, t, seed: 60 + i, halo: false, robe: ROB[rb], mantle: ROB[mt], under: '#f6ecd0', skin: '#e6b48a', hair: '#5b3a22', sway: 0.9,
76
+ gaze: [0, -sz * 0.9], hands: [[-sz * 0.34 * face, -sz * 0.34 + Math.sin(t * 2 + i) * 8], [sz * 0.3 * face, -sz * 0.18]] });
77
+ g.restore();
78
+ });
79
+ // 中心:金色圆光里一位白衣者(最亮),光晕呼吸
80
+ const br = 0.5 + 0.5 * Math.sin(t * 1.6), cg = g.createRadialGradient(CX, CY, 10, CX, CY, 220 + br * 30); cg.addColorStop(0, 'rgba(255,248,214,.95)'); cg.addColorStop(1, 'rgba(255,230,160,0)'); g.fillStyle = cg; g.beginPath(); g.arc(CX, CY, 260, 0, TAU); g.fill();
81
+ g.save(); g.translate(CX, CY + 10); g.rotate(Math.sin(t * 0.5) * 0.1); M.figure(g, { x: 0, y: 120, h: 240, face: 1, lean: 0.04, t, seed: 99, halo: true, robe: ROB.white, mantle: ROB.ochre, under: '#fffaf0', skin: '#f0c49a', hair: '#8a5a2a', sway: 0.8, gaze: [0, -150], hands: [[-70, -26], [70, -26]] }); g.restore();
82
+ M.fresco(c, live, { ground: M.plaster('cortona', { base: '#d8c8a8', seed: 17 }), chalk: 0.03, soften: 0.4, plasterAlpha: 0.32, sat: 1.12 });
83
+ c.save(); c.globalCompositeOperation = 'screen'; const hg = c.createRadialGradient(CX, CY, 0, CX, CY, 520); hg.addColorStop(0, `rgba(255,236,180,${0.28 + br * 0.1})`); hg.addColorStop(1, 'rgba(255,220,150,0)'); c.fillStyle = hg; c.fillRect(0, 0, W, H); c.restore();
84
+ }
85
+ return { draw };
86
+ })();