@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,205 @@
1
+ // 1924 俄罗斯构成主义海报(罗德琴科 / 李西茨基)——纯代码。
2
+ // 管线:①缓存底版(米白旧纸+斜红地面+黑圆+窗内网点照片与舒霍夫塔+排版小构件)
3
+ // → ②会动的母题(红楔刺入黑圆、塔顶无线电波外扩、黑圆外虚线环旋转、标语字逐个盖章后波浪跳)
4
+ // → ③角色:平涂剪纸(红头巾共青团少女)+皮肤是 45° 网点照片(照片拼贴)+白色剪切边
5
+ SCENES['22_constructivism'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
7
+ const C = { paper: '#e9dfc4', red: '#c8231c', redD: '#8f1711', ink: '#151311', cream: '#f4ecd6', gray: '#4a4640', ochre: '#e3b03e', ochreD: '#a87a1c', orange: '#e2741c' };
8
+ const poly = U.poly;
9
+
10
+ // 网点:P.halftoneGray(灰度照片网点,照片拼贴的核心)、P.shadeDots(路径内按函数落点的网点阴影)
11
+ const halftone = P.halftoneGray, shadeDots = P.shadeDots;
12
+
13
+ // ---------- 几何西里尔字(5×7 模块,罗德琴科式手绘方块字) ----------
14
+ const GL = {
15
+ 'П': [[0, 0, 5, 1.3], [0, 0, 1.3, 7], [3.7, 0, 1.3, 7]],
16
+ 'Е': [[0, 0, 1.3, 7], [0, 0, 5, 1.3], [0, 2.9, 4, 1.2], [0, 5.7, 5, 1.3]],
17
+ 'Т': [[0, 0, 5, 1.3], [1.85, 0, 1.3, 7]],
18
+ 'Ч': [[0, 0, 1.3, 4], [3.7, 0, 1.3, 7], [0, 2.9, 5, 1.2]],
19
+ 'А': [{ p: [[0, 7], [1.8, 0], [3.2, 0], [1.4, 7]] }, { p: [[5, 7], [3.2, 0], [1.8, 0], [3.6, 7]] }, [1.0, 4.3, 3.0, 1.15]],
20
+ 'Й': [[0, 1.7, 1.3, 5.3], [3.7, 1.7, 1.3, 5.3], { p: [[1.3, 5.5], [3.7, 2.0], [3.7, 3.7], [1.3, 7]] }, [1.2, 0, 2.6, 1.0]], // 短音符收在字格内:伸出格外会被相邻字母/人物盖掉(第一版踩到)
21
+ '!': [[1.85, 0, 1.3, 4.8], [1.85, 5.7, 1.3, 1.3]],
22
+ };
23
+ function glyph(c, ch, x, y, u) {
24
+ const g = GL[ch]; if (!g) return;
25
+ // 逐部件各自 fill:同一 path 里反向绕行的两条斜腿会按 nonzero 规则互相抵消出洞(А 第一版踩到)
26
+ g.forEach(r => { c.beginPath(); if (r.p) { r.p.forEach((q, i) => i ? c.lineTo(x + q[0] * u, y + q[1] * u) : c.moveTo(x + q[0] * u, y + q[1] * u)); c.closePath(); } else c.rect(x + r[0] * u, y + r[1] * u, r[2] * u, r[3] * u); c.fill(); });
27
+ }
28
+
29
+ // ---------- ① 底版 ----------
30
+ const TOWER = [[540, 170, 430, 108], [430, 100, 342, 70], [342, 66, 268, 47], [268, 44, 208, 32], [208, 30, 168, 22]];
31
+ const TX = 600;
32
+ const bg = () => P.cached('cs_bg', W, H, (g) => {
33
+ g.drawImage(P.texture('cs_paper', C.paper, { scale: 0.0035, amt: 16, grain: 18, seed: 5, dark: '#d9c9a4' }), 0, 0);
34
+ // 左侧排版构件:斜黑条、红方块、几行「正文」黑条
35
+ g.save(); g.translate(170, 610); g.rotate(-0.5); g.fillStyle = C.ink; g.fillRect(-260, -30, 560, 58); g.restore();
36
+ g.save(); g.translate(150, 200); g.rotate(0.26); g.fillStyle = C.red; g.fillRect(-70, -70, 140, 140); g.restore();
37
+ g.save(); g.translate(60, 330); g.rotate(-0.08); g.fillStyle = C.ink; [180, 150, 200, 120, 170].forEach((w, k) => g.fillRect(0, k * 26, w, 10)); g.restore();
38
+ // 右侧:细斜线+红圆点+竖排「ЧАЙ」
39
+ g.strokeStyle = C.ink; g.lineWidth = 6; g.beginPath(); g.moveTo(1520, 250); g.lineTo(1920, 760); g.stroke();
40
+ g.fillStyle = C.red; g.beginPath(); g.arc(1790, 340, 46, 0, TAU); g.fill();
41
+ g.fillStyle = C.ink; g.fillRect(1660, 430, 120, 400);
42
+ g.fillStyle = C.cream; ['Ч', 'А', 'Й'].forEach((ch, k) => glyph(g, ch, 1682, 470 + k * 120, 15.5));
43
+ // 大黑圆(人物头后)
44
+ g.fillStyle = C.ink; g.beginPath(); g.arc(1330, 395, 215, 0, TAU); g.fill();
45
+ // 红色斜地面
46
+ g.fillStyle = C.red; g.beginPath(); g.moveTo(0, 905); g.lineTo(W, 856); g.lineTo(W, H); g.lineTo(0, H); g.closePath(); g.fill();
47
+ g.fillStyle = C.ink; g.beginPath(); g.moveTo(0, 899); g.lineTo(W, 850); g.lineTo(W, 862); g.lineTo(0, 911); g.closePath(); g.fill();
48
+ // 窗:网点照片(天空+云)
49
+ const ph = P.canvas(), pg = ph.getContext('2d');
50
+ const sg = pg.createLinearGradient(0, 140, 0, 540); sg.addColorStop(0, '#5e5e5e'); sg.addColorStop(0.75, '#cfcfcf'); sg.addColorStop(1, '#e8e8e8');
51
+ pg.fillStyle = sg; pg.fillRect(370, 140, 420, 400);
52
+ [[460, 240, 90, 34], [700, 300, 110, 30], [520, 400, 140, 26]].forEach(([x, y, rx, ry]) => { const rg = pg.createRadialGradient(x, y, 4, x, y, rx); rg.addColorStop(0, 'rgba(250,250,250,.95)'); rg.addColorStop(1, 'rgba(250,250,250,0)'); pg.fillStyle = rg; pg.beginPath(); pg.ellipse(x, y, rx, ry * 2, 0, 0, TAU); pg.fill(); });
53
+ pg.fillStyle = '#2a2a2a'; pg.fillRect(370, 505, 420, 35); // 远处厂房屋脊(暗)
54
+ [[390, 470, 50], [470, 485, 36], [690, 460, 70], [760, 480, 30]].forEach(([x, y, w]) => pg.fillRect(x, y, w, 60));
55
+ pg.fillRect(712, 360, 18, 120); // 烟囱
56
+ g.fillStyle = C.cream; g.fillRect(370, 140, 420, 400);
57
+ halftone(g, ph, 370, 140, 420, 400, 7, C.ink, 0.8);
58
+ // 舒霍夫塔:五节双曲面,两组直母线
59
+ g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
60
+ g.strokeStyle = C.ink; g.lineCap = 'round';
61
+ TOWER.forEach(([y0, w0, y1, w1], k) => {
62
+ g.lineWidth = 2.6 - k * 0.25;
63
+ for (const dir of [1, -1]) for (let i = 0; i < 18; i++) {
64
+ const a = i / 18 * TAU, b = a + dir * 1.1;
65
+ const x0 = TX + Math.cos(a) * w0 / 2, x1 = TX + Math.cos(b) * w1 / 2;
66
+ if (Math.sin(a) < -0.2 && Math.sin(b) < -0.2) continue; // 背面的线省掉,读起来更清楚
67
+ g.beginPath(); g.moveTo(x0, y0 + Math.sin(a) * w0 * 0.08); g.lineTo(x1, y1 + Math.sin(b) * w1 * 0.08); g.stroke();
68
+ }
69
+ g.lineWidth = 4; g.beginPath(); g.ellipse(TX, y1, w1 / 2, w1 * 0.08, 0, 0, TAU); g.stroke();
70
+ });
71
+ g.lineWidth = 4; g.beginPath(); g.moveTo(TX, 168); g.lineTo(TX, 140); g.stroke();
72
+ g.restore();
73
+ // 窗框(粗黑)+红窗台
74
+ g.strokeStyle = C.ink; g.lineWidth = 26; g.strokeRect(357, 127, 446, 426);
75
+ g.fillStyle = C.red; g.fillRect(330, 554, 500, 30); g.fillStyle = C.ink; g.fillRect(330, 584, 500, 8);
76
+ // 桌:黑板面+红边+斜撑
77
+ g.fillStyle = C.ink; g.fillRect(810, 618, 400, 34); g.fillStyle = C.red; g.fillRect(810, 652, 400, 12);
78
+ g.fillStyle = C.ink; g.fillRect(842, 664, 24, 220); g.fillRect(1158, 664, 24, 220);
79
+ g.lineWidth = 14; g.strokeStyle = C.ink; g.beginPath(); g.moveTo(854, 870); g.lineTo(1170, 680); g.stroke();
80
+ // 凳:黑块
81
+ g.fillStyle = C.ink; g.fillRect(1440, 470, 34, 420); g.fillRect(1290, 740, 190, 26); g.fillRect(1300, 760, 22, 120);
82
+ });
83
+
84
+ // ---------- 少女 ----------
85
+ const girlPal = { skin: C.cream, hair: C.ochre, dress: '#2f2c29', sleeve: C.cream, cuff: C.red, line: C.ink, lw: 3 };
86
+ function girl(c, G, ch, lt) {
87
+ const tilt = G.tilt, nk = [1300, 425];
88
+ const HP = (x, y) => { const X = x - nk[0], Y = y - nk[1], cs = Math.cos(tilt), sn = Math.sin(tilt); return [nk[0] + X * cs - Y * sn, nk[1] + X * sn + Y * cs]; };
89
+ const L = C.ink;
90
+ const F = (p, col, lw = 3) => { if (!p) return; c.fillStyle = col; c.fill(p); if (lw) { c.strokeStyle = L; c.lineWidth = lw; c.stroke(p); } };
91
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
92
+ // 远侧袖(白衬衫)+手
93
+ F(G.farSleeve, C.cream); F(G.farCuff, C.red, 2);
94
+ // 裙:深灰平涂+网点阴影
95
+ F(G.skirt, '#34302c', 0);
96
+ shadeDots(c, G.skirt, 1090, 600, 380, 340, 9, '#0d0c0b', (x, y) => clamp((x - 1180) / 260) * 0.9);
97
+ c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.skirt);
98
+ F(G.shoe, L, 0);
99
+ // 上身:白衬衫+红领巾三角
100
+ F(G.torso, C.cream);
101
+ shadeDots(c, G.torso, 1270, 420, 170, 210, 8, '#7d766a', (x, y) => clamp((x - 1350) / 80));
102
+ const nkA = G.A.neck;
103
+ c.fillStyle = C.red; c.beginPath(); c.moveTo(nkA[0] - 18, nkA[1] - 2); c.lineTo(nkA[0] + 26, nkA[1] + 2); c.lineTo(nkA[0] - 2, nkA[1] + 62); c.closePath(); c.fill();
104
+ // 头发(金)+红头巾
105
+ F(G.hairBack, C.ochre, 0); F(G.bangs, C.ochre, 0);
106
+ c.strokeStyle = C.ochreD; c.lineWidth = 3; G.hairLines.forEach(h => c.stroke(h));
107
+ // 皮肤:先画灰度「照片」到离屏,再网点化 → 照片拼贴
108
+ const S = P.scratch('csSkin'), sg = S.getContext('2d', { willReadFrequently: true });
109
+ sg.reset(); sg.clearRect(1100, 270, 300, 400);
110
+ const shade = (p, x0, x1, a, b) => { const gr = sg.createLinearGradient(x0, 0, x1, 0); gr.addColorStop(0, a); gr.addColorStop(1, b); sg.fillStyle = gr; sg.fill(p); };
111
+ shade(G.neck, 1285, 1325, '#b4b4b4', '#767676');
112
+ shade(G.face, 1250, 1330, '#fafafa', '#9c9c9c');
113
+ sg.fillStyle = 'rgba(90,90,90,.28)'; sg.beginPath(); sg.ellipse(G.cheek[0] + 16, G.cheek[1] + 4, 16, 12, 0, 0, TAU); sg.fill(); // 颧下阴影(轻)
114
+ shade(G.hand, G.A.hand[0] - 20, G.A.hand[0] + 20, '#d8d8d8', '#8a8a8a'); shade(G.farHand, 1146, 1218, '#f4f4f4', '#b0b0b0');
115
+ // 白色剪切边 + 网点
116
+ c.save(); c.strokeStyle = '#fbf7ea'; c.lineWidth = 7; [G.neck, G.face, G.farHand].forEach(p => c.stroke(p)); c.restore();
117
+ [G.neck, G.face, G.farHand].forEach(p => { c.fillStyle = C.cream; c.fill(p); });
118
+ halftone(c, S, 1135, 280, 220, 360, 4.6, C.ink, 0.74);
119
+ // 五官(照片上手工补的清晰笔画)+红唇
120
+ if (ch.blink) { c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.lid); }
121
+ else { c.fillStyle = L; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1, 5, 6.5, 0, 0, TAU); c.fill(); c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.lid); G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.stroke(); }); }
122
+ c.lineWidth = 3.4; c.stroke(G.brow);
123
+ c.fillStyle = C.red; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
124
+ // 头巾:盖住头顶与后脑,额前露金发;颈后打结两条尾
125
+ const kf = poly([[1280, 296], [1300, 285], [1330, 280], [1368, 291], [1402, 321], [1415, 368], [1405, 414], [1382, 440], [1356, 442], [1350, 410], [1342, 372], [1326, 334], [1304, 309]].map(p => HP(...p)));
126
+ const lock = poly([[1352, 425], [1372, 432], [1380, 470], [1372, 505], [1360, 470]].map(p => HP(...p)));
127
+ F(lock, C.ochre, 0); c.strokeStyle = C.ochreD; c.lineWidth = 2.5; c.stroke(lock);
128
+ F(kf, C.red, 3);
129
+ c.strokeStyle = C.redD; c.lineWidth = 3; c.beginPath(); [[1300, 300, 1390, 360], [1330, 292, 1400, 330]].forEach(([a, b, x, y]) => { const p0 = HP(a, b), p1 = HP(x, y); c.moveTo(...p0); c.lineTo(...p1); }); c.stroke();
130
+ const sw = Math.sin(lt * 9) * 6; // 头巾尾随动作轻摆
131
+ [[[1384, 432], [1446, 470 + sw], [1416, 478 + sw]], [[1378, 438], [1428, 500 - sw], [1400, 498 - sw]]].forEach(tri => F(poly(tri.map(p => HP(...p))), C.red, 3));
132
+ // 近侧手臂(白袖+红袖口)、杯、手
133
+ F(G.upperArm, C.cream); F(G.foreArm, C.cream); F(G.cuff, C.red, 2);
134
+ sg.reset(); sg.clearRect(1100, 270, 300, 400); shade(G.hand, G.A.hand[0] - 18, G.A.hand[0] + 18, '#dcdcdc', '#7a7a7a');
135
+ c.save(); c.strokeStyle = '#fbf7ea'; c.lineWidth = 7; c.stroke(G.hand); c.restore(); c.fillStyle = C.cream; c.fill(G.hand);
136
+ halftone(c, S, G.A.hand[0] - 26, G.A.hand[1] - 26, 52, 52, 4.6, C.ink, 0.74);
137
+ RIG.drawCup(c, G.cup, { body: C.cream, rim: C.ink, line: L, lw: 3, hw: 7 });
138
+ c.save(); c.translate(G.cup.x, G.cup.y); c.rotate(G.cup.tilt); c.fillStyle = C.red; c.fillRect(-G.cup.w * 0.46, G.cup.h * 0.38, G.cup.w * 0.92, 9); c.restore();
139
+ c.fillStyle = C.cream; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill(); c.strokeStyle = L; c.lineWidth = 2.4; c.stroke();
140
+ c.restore();
141
+ }
142
+
143
+ // ---------- 猫:剪纸平涂+网点阴影+粗黑条纹 ----------
144
+ function cat(c, K) {
145
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'butt';
146
+ c.strokeStyle = C.ink; c.lineWidth = K.tailW + 8; c.stroke(K.tail); c.strokeStyle = C.orange; c.lineWidth = K.tailW; c.stroke(K.tail);
147
+ c.setLineDash([12, 14]); c.strokeStyle = C.ink; c.lineWidth = K.tailW * 0.9; c.stroke(K.tail); c.setLineDash([]);
148
+ const tip = K.tailPts[K.tailPts.length - 1]; c.fillStyle = C.cream; c.beginPath(); c.arc(tip[0], tip[1], K.tailW / 2, 0, TAU); c.fill();
149
+ c.fillStyle = C.orange; c.fill(K.body); shadeDots(c, K.body, 420, 660, 250, 250, 9, '#7a2f0c', (x, y) => clamp((x - 520) / 140) * 0.8 + clamp((y - 820) / 90) * 0.3);
150
+ c.strokeStyle = C.ink; c.lineWidth = 4; c.stroke(K.body);
151
+ K.legs.forEach(l => { c.fillStyle = C.cream; c.fill(l); c.stroke(l); });
152
+ c.fillStyle = C.cream; c.fill(K.white);
153
+ c.fillStyle = C.orange; c.fill(K.head); c.stroke(K.head);
154
+ c.save(); c.clip(K.head); c.fillStyle = C.cream; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
155
+ c.fillStyle = C.red; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); });
156
+ c.strokeStyle = C.ink; c.lineWidth = 10; K.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); });
157
+ K.eyes.forEach(e => { if (K.blink) { c.lineWidth = 4; c.beginPath(); c.moveTo(e.x - 9, e.y); c.lineTo(e.x + 9, e.y); c.stroke(); }
158
+ else { c.fillStyle = C.cream; c.beginPath(); c.arc(e.x, e.y, 10, 0, TAU); c.fill(); c.lineWidth = 3; c.stroke(); c.fillStyle = C.ink; c.fillRect(e.x - 1, e.y - 8, 5, 16); } });
159
+ c.fillStyle = C.red; c.beginPath(); c.moveTo(K.nose[0] - 7, K.nose[1] - 4); c.lineTo(K.nose[0] + 7, K.nose[1] - 4); c.lineTo(K.nose[0], K.nose[1] + 5); c.fill();
160
+ c.lineWidth = 2; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
161
+ c.restore();
162
+ }
163
+
164
+ const SLOGAN = 'ПЕЙТЕ ЧАЙ!';
165
+ return {
166
+ draw(c, lt, t) {
167
+ const ch = P.choreo(lt, t);
168
+ c.drawImage(bg(), 0, 0);
169
+ // 黑圆外的红虚线环(旋转)
170
+ c.save(); c.strokeStyle = C.red; c.lineWidth = 12; c.setLineDash([46, 22]); c.lineDashOffset = -lt * 260;
171
+ c.beginPath(); c.arc(1330, 395, 242, 0, TAU); c.stroke(); c.restore();
172
+ // 李西茨基红楔:沿轴线刺入黑圆(1.6Hz 进退)
173
+ const thrust = 70 * (0.5 - 0.5 * Math.cos(lt * TAU * 1.6)), ax = Math.cos(0.34), ay = Math.sin(0.34);
174
+ const tipX = 1150 + ax * thrust, tipY = 360 + ay * thrust;
175
+ c.fillStyle = C.red; c.beginPath(); c.moveTo(tipX, tipY); c.lineTo(tipX - ax * 330 - ay * 95, tipY - ay * 330 + ax * 95); c.lineTo(tipX - ax * 330 + ay * 95, tipY - ay * 330 - ax * 95); c.closePath(); c.fill();
176
+ // 窗内:塔顶无线电波(红弧外扩)
177
+ c.save(); c.beginPath(); c.rect(370, 140, 420, 400); c.clip(); c.strokeStyle = C.red; c.lineCap = 'butt';
178
+ for (let k = 0; k < 4; k++) { const ph = (lt * 1.4 + k / 4) % 1, r = 18 + ph * 230; c.globalAlpha = 1 - ph; c.lineWidth = 10 - ph * 6;
179
+ c.beginPath(); c.arc(TX, 150, r, Math.PI * 0.05, Math.PI * 0.95, true); c.stroke(); }
180
+ c.restore();
181
+ // 角色
182
+ const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
183
+ cat(c, K);
184
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe });
185
+ girl(c, G, ch, lt);
186
+ // 地面标语(画在角色之上:海报字压人物):逐字盖章(0–0.5s)→ 之后波浪跳
187
+ c.save(); c.translate(0, 0); const ang = Math.atan2(-49, W); c.rotate(ang);
188
+ const u = 19, adv = 6.3 * u; let x = 350;
189
+ [...SLOGAN].forEach((chh, k) => {
190
+ if (chh === ' ') { x += 3.5 * u; return; }
191
+ const q = clamp((lt - k * 0.045) / 0.12); if (q <= 0) { x += adv; return; }
192
+ const s = 1 + 0.5 * (1 - ease.out(q)), hop = -10 * Math.max(0, Math.sin(lt * 11 - k * 0.8));
193
+ c.save(); c.translate(x + 2.5 * u, 990 + hop); c.scale(s, s); c.globalAlpha = q;
194
+ c.fillStyle = k % 4 === 3 ? C.ink : C.cream; glyph(c, chh, -2.5 * u, -3.5 * u, u); c.restore();
195
+ x += adv;
196
+ });
197
+ c.restore();
198
+ // 杯上热气:三条黑折线上行
199
+ c.save(); c.strokeStyle = C.ink; c.lineWidth = 4; c.lineJoin = 'miter';
200
+ for (let k = 0; k < 3; k++) { const ph = (lt * 2 + k / 3) % 1, x0 = G.cup.x - 14 + k * 14, y0 = G.cup.y - 12 - ph * 60; c.globalAlpha = 1 - ph;
201
+ c.beginPath(); c.moveTo(x0, y0); c.lineTo(x0 + 7, y0 - 9); c.lineTo(x0, y0 - 18); c.lineTo(x0 + 7, y0 - 27); c.stroke(); }
202
+ c.restore();
203
+ },
204
+ };
205
+ })();
@@ -0,0 +1,221 @@
1
+ // 1931 达利超现实主义(《记忆的永恒》)——纯代码。
2
+ // 管线:①缓存底版(群青→淡黄天空、玻璃般静海、克雷乌斯角金色礁岩、赭色荒原、开窗的断墙残片、石块桌)
3
+ // → ②长影子:每个物体的剪影用一个剪切仿射投到地面(kx 随时间增大=影子缓慢变长),先进离屏再整体半透明叠,避免重叠加深
4
+ // → ③软钟:圆盘 (u,v) → 屏幕的折叠映射,越过边缘的部分垂下并随时间继续下垂;刻度与指针走同一映射
5
+ // → ④会动的母题(软钟下垂+指针转、高跷象在地平线上迈步、蚂蚁绕怀表爬、影子变长)
6
+ // → ⑤角色:无勾线学院派体积(线性渐变+clip 内模糊描边做内阴影)+细五官
7
+ SCENES['23_dali'] = (() => {
8
+ const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
9
+ const HZ = 560, GROUND = 592; // 海平线、岸线
10
+ const C = { sky0: '#22407c', sky1: '#5f88bd', sky2: '#e9dcae', sea: '#6f8fa6', ground0: '#d2ad70', ground1: '#7a5129', shadow: '#2a1a10',
11
+ wall: '#e7dcc3', wallD: '#a99a7c', wood: '#7a4a24', woodL: '#a8703a', block: '#8a5a32', blockL: '#b98754', gold: '#d9b25a', dial: '#efe6c8', orange: '#d8621f' };
12
+
13
+ const vol = P.vol; // 体积填色:线性渐变+路径内模糊暗边(学院派,无勾线)
14
+
15
+ // ---------- ① 底版 ----------
16
+ const bg = () => P.cached('dali_bg', W, H, (g) => {
17
+ const sg = g.createLinearGradient(0, 0, 0, HZ); sg.addColorStop(0, C.sky0); sg.addColorStop(0.55, C.sky1); sg.addColorStop(1, C.sky2);
18
+ g.fillStyle = sg; g.fillRect(0, 0, W, HZ + 2);
19
+ // 细长粉云
20
+ [[200, 300, 260, 7], [700, 250, 340, 6], [1280, 330, 300, 8], [900, 420, 420, 5]].forEach(([x, y, w, h]) => { const rg = g.createLinearGradient(x - w, 0, x + w, 0); rg.addColorStop(0, 'rgba(250,215,200,0)'); rg.addColorStop(0.5, 'rgba(250,215,200,.55)'); rg.addColorStop(1, 'rgba(250,215,200,0)'); g.fillStyle = rg; g.beginPath(); g.ellipse(x, y, w, h, 0, 0, TAU); g.fill(); });
21
+ // 静海(玻璃般,几道高光线)
22
+ const mg = g.createLinearGradient(0, HZ, 0, GROUND); mg.addColorStop(0, '#8aa6b8'); mg.addColorStop(1, '#4f6f86'); g.fillStyle = mg; g.fillRect(0, HZ, W, GROUND - HZ);
23
+ g.strokeStyle = 'rgba(240,235,210,.6)'; g.lineWidth = 1.5; [568, 574, 583].forEach((y, k) => { g.beginPath(); g.moveTo(80 + k * 140, y); g.lineTo(700 + k * 200, y); g.stroke(); });
24
+ // 克雷乌斯角礁岩(右侧,金色,光滑高光)
25
+ const cliff = new Path2D(); cliff.moveTo(1380, GROUND); [[1420, 540], [1470, 500], [1520, 470], [1590, 460], [1640, 430], [1700, 440], [1760, 410], [1830, 430], [1920, 420], [1920, GROUND]].forEach(p => cliff.lineTo(...p)); cliff.closePath();
26
+ vol(g, cliff, 1400, 420, 1500, 600, '#e8c27a', '#9a6a34', 'rgba(70,40,15,.5)', 18);
27
+ g.strokeStyle = 'rgba(255,240,200,.7)'; g.lineWidth = 3; g.beginPath(); g.moveTo(1470, 505); g.quadraticCurveTo(1530, 480, 1590, 470); g.moveTo(1650, 440); g.quadraticCurveTo(1700, 445, 1760, 418); g.stroke();
28
+ // 荒原:近暗远亮,几道细地层线
29
+ const gg = g.createLinearGradient(0, GROUND, 0, H); gg.addColorStop(0, C.ground0); gg.addColorStop(0.45, '#a87a44'); gg.addColorStop(1, C.ground1); g.fillStyle = gg; g.fillRect(0, GROUND, W, H - GROUND);
30
+ g.strokeStyle = 'rgba(90,55,25,.25)'; g.lineWidth = 2; for (let k = 0; k < 9; k++) { const y = GROUND + Math.pow(k / 9, 1.6) * 480 + 10; g.beginPath(); g.moveTo(0, y); g.bezierCurveTo(600, y - 6, 1300, y + 8, W, y - 2); g.stroke(); }
31
+ // 画布纹+暖色上光油
32
+ g.fillStyle = 'rgba(255,200,120,.06)'; g.fillRect(0, 0, W, H);
33
+ g.drawImage(P.grain('dali', 0.05, [70, 45, 20], 0.10), 0, 0);
34
+ });
35
+
36
+ const wallLayer = () => P.cached('dali_wall', W, H, (g) => {
37
+ // 断墙残片(开窗,立在荒原上,基线 y=700)
38
+ const wall = new Path2D(); wall.moveTo(300, 700); [[300, 120], [330, 96], [372, 108], [410, 78], [470, 92], [540, 70], [610, 88], [680, 64], [760, 90], [820, 80], [858, 104], [860, 700]].forEach(p => wall.lineTo(...p)); wall.closePath();
39
+ wall.rect(370, 140, 420, 400); // 窗洞(evenodd 挖空)
40
+ g.save(); const wg = g.createLinearGradient(300, 0, 860, 0); wg.addColorStop(0, '#f4ead2'); wg.addColorStop(1, '#cdbf9f'); g.fillStyle = wg; g.fill(wall, 'evenodd'); g.restore();
41
+ g.fillStyle = '#9d8c6c'; g.beginPath(); g.moveTo(860, 104); g.lineTo(892, 120); g.lineTo(892, 712); g.lineTo(860, 700); g.closePath(); g.fill(); // 墙侧面
42
+ g.strokeStyle = 'rgba(110,90,60,.55)'; g.lineWidth = 1.6; [[[330, 300], [352, 340], [344, 390], [360, 430]], [[820, 600], [800, 640], [812, 690]], [[640, 92], [652, 118], [646, 136]]].forEach(cr => { g.beginPath(); cr.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.stroke(); });
43
+ // 窗内:同一片风景(更亮更远)+窗框
44
+ g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
45
+ const ig = g.createLinearGradient(0, 140, 0, 540); ig.addColorStop(0, '#3d64a2'); ig.addColorStop(0.85, '#f0e1b4'); ig.addColorStop(1, '#f6ecc8'); g.fillStyle = ig; g.fillRect(370, 140, 420, 400);
46
+ g.fillStyle = '#7f9db3'; g.fillRect(370, 505, 420, 35); g.strokeStyle = 'rgba(250,245,220,.7)'; g.lineWidth = 1.4; g.beginPath(); g.moveTo(400, 515); g.lineTo(600, 515); g.moveTo(520, 528); g.lineTo(760, 528); g.stroke();
47
+ // 窗里漂浮的一只蛋(达利母题)
48
+ const eg = g.createRadialGradient(560, 300, 6, 580, 330, 70); eg.addColorStop(0, '#fffaf0'); eg.addColorStop(1, '#bfae8e'); g.fillStyle = eg; g.beginPath(); g.ellipse(580, 330, 46, 60, 0, 0, TAU); g.fill();
49
+ g.restore();
50
+ g.lineWidth = 18; g.strokeStyle = C.wood; g.strokeRect(361, 131, 438, 418); g.lineWidth = 4; g.strokeStyle = C.woodL; g.strokeRect(356, 126, 448, 428);
51
+ g.fillStyle = C.woodL; g.fillRect(340, 548, 480, 18); g.fillStyle = C.wood; g.fillRect(340, 566, 480, 12);
52
+ // 枯橄榄枝:从墙头右上角伸出(挂软钟用)
53
+ g.strokeStyle = '#4a3020'; g.lineCap = 'round'; g.lineWidth = 12; g.beginPath(); g.moveTo(850, 112); g.bezierCurveTo(900, 90, 960, 104, 1040, 96); g.stroke();
54
+ g.lineWidth = 6; g.beginPath(); g.moveTo(960, 100); g.quadraticCurveTo(990, 70, 1000, 46); g.moveTo(1010, 98); g.lineTo(1070, 112); g.stroke();
55
+ });
56
+ const blockLayer = () => P.cached('dali_block', W, H, (g) => {
57
+ // 石块桌:顶面+正面(光从左来)
58
+ g.fillStyle = C.blockL; g.beginPath(); g.moveTo(810, 620); g.lineTo(1205, 620); g.lineTo(1190, 600); g.lineTo(826, 600); g.closePath(); g.fill();
59
+ const bk = new Path2D(); bk.rect(810, 620, 395, 285); vol(g, bk, 810, 0, 1205, 0, '#b07a46', '#6e4422', 'rgba(40,20,8,.5)', 22);
60
+ // 少女的凳子(深色木块)
61
+ const st = new Path2D(); st.rect(1300, 742, 190, 163); vol(g, st, 1300, 0, 1490, 0, '#8a5a32', '#4a2c14', 'rgba(30,15,5,.5)', 16);
62
+ });
63
+
64
+ // ---------- ③ 软钟:圆盘局部坐标 (u,v)∈半径 R → 屏幕;v 越过折线 fold 的部分垂下 ----------
65
+ function softMap(o, sag) {
66
+ const { cx, cy, R, fold, top = 0.32, flare = -0.12, drip = 0.18 } = o; // flare<0:垂下部分像软布一样往外摊开,下缘圆而不是尖
67
+ return (u, v) => {
68
+ if (v <= fold) return [cx + u, cy + v * top + Math.sin(u * 0.05) * 2];
69
+ const d = v - fold, q = d / R;
70
+ return [cx + u * (1 - flare * q) + Math.sin(q * 3) * 5 * sag, cy + fold * top + d * (0.62 + 0.3 * sag) + sag * R * drip * Math.exp(-(((u + R * 0.2) / (R * 0.6)) ** 2)) * q * q + Math.sin(u * 0.06 + 1) * 6 * q];
71
+ };
72
+ }
73
+ function softClock(c, o, sag, t, handSpeed) {
74
+ const m = softMap(o, sag), R = o.R;
75
+ const ring = (r) => { const pts = []; for (let i = 0; i <= 120; i++) { const a = i / 120 * TAU; pts.push(m(Math.cos(a) * r, Math.sin(a) * r)); } return pts; };
76
+ const path = pts => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); p.closePath(); return p; };
77
+ const outer = path(ring(R)), inner = path(ring(R * 0.86));
78
+ // 投影(贴着表面的软影)
79
+ c.save(); c.translate(-8, 10); c.fillStyle = 'rgba(30,15,5,.35)'; c.filter = 'blur(6px)'; c.fill(outer); c.restore();
80
+ vol(c, outer, o.cx - R, 0, o.cx + R, 0, '#f0d27c', '#9a7426', 'rgba(70,45,10,.5)', 8);
81
+ const fg = c.createLinearGradient(0, o.cy - R * 0.3, 0, o.cy + R * 1.4); fg.addColorStop(0, '#f6efd6'); fg.addColorStop(0.45, '#e8dcb6'); fg.addColorStop(1, '#bfae84');
82
+ c.fillStyle = fg; c.fill(inner);
83
+ // 刻度与罗马数字的「点」:走同一映射
84
+ c.fillStyle = '#3a2a18';
85
+ for (let k = 0; k < 12; k++) { const a = k / 12 * TAU - Math.PI / 2, p0 = m(Math.cos(a) * R * 0.74, Math.sin(a) * R * 0.74), p1 = m(Math.cos(a) * R * 0.8, Math.sin(a) * R * 0.8);
86
+ c.strokeStyle = '#3a2a18'; c.lineWidth = k % 3 ? 2 : 4; c.beginPath(); c.moveTo(...p0); c.lineTo(...p1); c.stroke(); }
87
+ // 指针(沿映射采样成软线)
88
+ const hand = (ang, len, w) => { c.lineWidth = w; c.lineCap = 'round'; c.beginPath(); for (let i = 0; i <= 12; i++) { const r = len * i / 12, q = m(Math.cos(ang) * r, Math.sin(ang) * r); i ? c.lineTo(...q) : c.moveTo(...q); } c.stroke(); };
89
+ c.strokeStyle = '#2a1c10'; hand(t * handSpeed - 1.2, R * 0.66, 4); hand(t * handSpeed / 12 + 0.6, R * 0.46, 6);
90
+ const cc = m(0, 0); c.fillStyle = '#2a1c10'; c.beginPath(); c.arc(cc[0], cc[1], 4, 0, TAU); c.fill();
91
+ // 折线上的高光(柔软的「棱」)
92
+ c.strokeStyle = 'rgba(255,250,225,.55)'; c.lineWidth = 3; c.beginPath(); for (let i = 0; i <= 30; i++) { const u = -R * 0.9 + i / 30 * R * 1.8, q = m(u, o.fold + 2); i ? c.lineTo(...q) : c.moveTo(...q); } c.stroke();
93
+ return outer;
94
+ }
95
+
96
+ // ---------- 高跷象 ----------
97
+ function elephant(c, x, lt) {
98
+ const y = 400, legH = GROUND - 2 - y;
99
+ c.save(); c.fillStyle = '#5a4a46'; c.strokeStyle = '#5a4a46'; c.lineCap = 'round';
100
+ // 四条细长腿:交替迈步(关节处微弯)
101
+ [[-26, 0], [-12, Math.PI], [18, Math.PI * 0.5], [30, Math.PI * 1.5]].forEach(([ox, ph]) => {
102
+ const sw = Math.sin(lt * 7 + ph), fx = x + ox + sw * 10, kx = x + ox + sw * 4 + 3, ky = y + legH * 0.52 - Math.max(0, sw) * 6;
103
+ c.lineWidth = 2.6; c.beginPath(); c.moveTo(x + ox, y + 8); c.lineTo(kx, ky); c.lineTo(fx, GROUND - 2 - Math.max(0, sw) * 5); c.stroke();
104
+ });
105
+ const bob = Math.sin(lt * 14) * 1.5;
106
+ c.beginPath(); c.ellipse(x, y + bob, 54, 28, 0, 0, TAU); c.fill();
107
+ c.beginPath(); c.ellipse(x - 44, y - 8 + bob, 18, 16, 0, 0, TAU); c.fill(); // 头(朝左)
108
+ c.lineWidth = 6; c.beginPath(); c.moveTo(x - 56, y - 2 + bob); c.quadraticCurveTo(x - 72, y + 30, x - 64 + Math.sin(lt * 5) * 6, y + 52); c.stroke(); // 象鼻
109
+ // 背上的方尖碑与鞍布
110
+ c.fillStyle = '#c8402c'; c.fillRect(x - 22, y - 26 + bob, 40, 8);
111
+ c.fillStyle = '#e6d6b0'; c.beginPath(); c.moveTo(x - 10, y - 26 + bob); c.lineTo(x + 6, y - 26 + bob); c.lineTo(x - 2, y - 96 + bob); c.closePath(); c.fill();
112
+ c.fillStyle = 'rgba(255,235,200,.35)'; c.beginPath(); c.ellipse(x - 14, y - 10 + bob, 26, 6, -0.2, 0, TAU); c.fill(); // 左上受光
113
+ c.restore();
114
+ }
115
+
116
+ // ---------- 蚂蚁怀表 ----------
117
+ function antWatch(c, lt) {
118
+ const x = 1030, y = 606;
119
+ const wg = c.createRadialGradient(x - 10, y - 8, 3, x, y, 40); wg.addColorStop(0, '#f39a4a'); wg.addColorStop(1, '#9a3a10');
120
+ c.fillStyle = 'rgba(30,15,5,.35)'; c.beginPath(); c.ellipse(x + 10, y + 8, 40, 11, 0, 0, TAU); c.fill();
121
+ c.fillStyle = wg; c.beginPath(); c.ellipse(x, y, 38, 13, 0, 0, TAU); c.fill();
122
+ c.fillStyle = '#c9a24a'; c.beginPath(); c.ellipse(x + 36, y - 4, 7, 5, 0, 0, TAU); c.fill();
123
+ c.fillStyle = '#120a06';
124
+ for (let k = 0; k < 9; k++) { const a = lt * (1.6 + (k % 3) * 0.5) * (k % 2 ? 1 : -1) + k * 0.7, r = 0.35 + (k % 4) * 0.14;
125
+ const ax = x + Math.cos(a) * 32 * r, ay = y + Math.sin(a) * 10 * r, d = a + (k % 2 ? Math.PI / 2 : -Math.PI / 2);
126
+ for (let s = -1; s <= 1; s++) { c.beginPath(); c.ellipse(ax + Math.cos(d) * s * 3.2, ay + Math.sin(d) * s * 1.2, 2, 1.4, d, 0, TAU); c.fill(); } }
127
+ }
128
+
129
+ // ---------- 角色 ----------
130
+ function girl(c, G, ch) {
131
+ c.save();
132
+ const sk = ['#f6dcc6', '#d9a888'], dress = ['#c9d9e6', '#6e8aa6'], hair = ['#f4d98a', '#b98a34'];
133
+ vol(c, G.farSleeve, 1200, 560, 1340, 600, dress[0], dress[1]); vol(c, G.farHand, 1150, 0, 1218, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
134
+ vol(c, G.skirt, 1100, 650, 1450, 900, '#dce8f0', dress[1], 'rgba(40,50,70,.45)', 26);
135
+ c.save(); c.clip(G.skirt); c.strokeStyle = 'rgba(60,80,110,.35)'; c.lineWidth = 10; c.filter = 'blur(5px)'; G.folds.forEach(f => c.stroke(f)); c.restore();
136
+ vol(c, G.shoe, 1100, 0, 1150, 0, '#6a3a22', '#2a140a', null);
137
+ vol(c, G.torso, 1290, 440, 1420, 600, dress[0], dress[1], 'rgba(40,50,70,.45)', 18);
138
+ // 白色小翻领
139
+ c.fillStyle = '#fbf6ea'; c.beginPath(); const nk = G.A.neck; c.ellipse(nk[0] + 4, nk[1] + 6, 30, 12, 0.1, 0, TAU); c.fill();
140
+ vol(c, G.neck, 1286, 0, 1324, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
141
+ vol(c, G.hairBack, 1270, 300, 1400, 440, hair[0], hair[1], 'rgba(110,70,20,.45)', 12); vol(c, G.bun, 1360, 340, 1420, 400, hair[0], hair[1], 'rgba(110,70,20,.45)', 8);
142
+ // 卷发细线(波浪)
143
+ c.save(); c.clip(G.hairBack); c.strokeStyle = 'rgba(150,105,40,.6)'; c.lineWidth = 2; G.hairLines.forEach(h => c.stroke(h)); c.restore();
144
+ vol(c, G.face, 1245, 0, 1330, 0, sk[0], sk[1], 'rgba(150,80,60,.3)', 10);
145
+ c.save(); c.clip(G.face); c.fillStyle = 'rgba(235,130,120,.3)'; c.filter = 'blur(6px)'; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 12, 0, TAU); c.fill(); c.restore();
146
+ vol(c, G.bangs, 1250, 295, 1330, 360, hair[0], hair[1], 'rgba(110,70,20,.4)', 8);
147
+ c.strokeStyle = 'rgba(150,105,40,.7)'; c.lineWidth = 2; G.locks.forEach(h => c.stroke(h));
148
+ // 五官(细、暗褐、无黑线)
149
+ c.lineCap = 'round';
150
+ if (ch.blink) { c.strokeStyle = '#5a3420'; c.lineWidth = 2.2; c.stroke(G.lid); }
151
+ else { c.fillStyle = '#fbf3ea'; c.beginPath(); c.ellipse(G.eye.x, G.eye.y + 1, 6, 4, 0, 0, TAU); c.fill(); c.fillStyle = '#4a6a8a'; c.beginPath(); c.arc(G.eye.x - 1.5, G.eye.y + 1, 3.6, 0, TAU); c.fill(); c.fillStyle = '#1a1008'; c.beginPath(); c.arc(G.eye.x - 1.5, G.eye.y + 1, 1.6, 0, TAU); c.fill(); c.strokeStyle = '#4a2a18'; c.lineWidth = 2; c.stroke(G.lid); }
152
+ c.strokeStyle = '#a0763a'; c.lineWidth = 2; c.stroke(G.brow);
153
+ c.fillStyle = '#c8645e'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
154
+ c.fillStyle = 'rgba(120,60,40,.5)'; c.beginPath(); c.arc(G.nostril[0] + 2, G.nostril[1], 1.8, 0, TAU); c.fill();
155
+ vol(c, G.upperArm, 1300, 0, 1380, 0, dress[0], dress[1], 'rgba(40,50,70,.4)', 10); vol(c, G.foreArm, 1200, 0, 1320, 0, dress[0], dress[1], 'rgba(40,50,70,.4)', 10);
156
+ vol(c, G.cuff, 1200, 0, 1300, 0, '#fbf6ea', '#c8bea8', null);
157
+ vol(c, G.hand, G.A.hand[0] - 15, 0, G.A.hand[0] + 15, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
158
+ RIG.drawCup(c, G.cup, { body: '#f3efe4', rim: '#7a4a2a', hw: 6 });
159
+ c.save(); c.translate(G.cup.x, G.cup.y); c.rotate(G.cup.tilt); const cg = c.createLinearGradient(-G.cup.w / 2, 0, G.cup.w / 2, 0); cg.addColorStop(0, 'rgba(255,255,255,0)'); cg.addColorStop(1, 'rgba(90,70,50,.45)'); c.fillStyle = cg; c.beginPath(); c.moveTo(-G.cup.w / 2, 0); c.lineTo(-G.cup.w * 0.42, G.cup.h); c.lineTo(G.cup.w * 0.42, G.cup.h); c.lineTo(G.cup.w / 2, 0); c.fill(); c.restore();
160
+ c.fillStyle = sk[0]; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill();
161
+ c.restore();
162
+ }
163
+ function cat(c, K, lt) {
164
+ c.save(); c.lineCap = 'round';
165
+ // 尾巴:软化下垂(达利式融化)
166
+ const pts = K.tailPts.map((p, i) => { const q = i / (K.tailPts.length - 1); return [p[0], p[1] + q * q * 34 * (0.6 + 0.4 * Math.sin(lt * 2))]; });
167
+ const tail = new Path2D(); pts.forEach((p, i) => i ? tail.lineTo(...p) : tail.moveTo(...p));
168
+ c.strokeStyle = '#8a3c10'; c.lineWidth = K.tailW + 4; c.stroke(tail); c.strokeStyle = '#e48a3a'; c.lineWidth = K.tailW; c.stroke(tail);
169
+ const tip = pts[pts.length - 1]; c.fillStyle = '#fbf2e2'; c.beginPath(); c.ellipse(tip[0], tip[1] + 6, K.tailW / 2, K.tailW * 0.75, 0, 0, TAU); c.fill();
170
+ vol(c, K.body, 430, 0, 660, 0, '#f2a24c', '#a24a14', 'rgba(70,25,5,.5)', 22);
171
+ c.save(); c.clip(K.body); c.strokeStyle = 'rgba(150,60,15,.55)'; c.lineWidth = 9; c.filter = 'blur(2px)'; K.stripes.slice(3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
172
+ K.legs.forEach(l => vol(c, l, 560, 0, 650, 0, '#fffaf0', '#cbbca0', 'rgba(90,70,40,.35)', 6));
173
+ c.save(); c.clip(K.body); const wch = new Path2D(); wch.ellipse(606, 768, 40, 92, -0.05, 0, TAU); vol(c, wch, 560, 0, 650, 0, '#fffaf0', '#d6c8ac', 'rgba(90,70,40,.3)', 10); c.restore();
174
+ vol(c, K.head, 540, 560, 660, 690, '#f6ac58', '#b25618', 'rgba(70,25,5,.45)', 14);
175
+ c.save(); c.clip(K.head); c.fillStyle = '#fbf3e4'; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
176
+ c.fillStyle = '#e8a0a0'; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); });
177
+ c.save(); c.clip(K.head); c.strokeStyle = 'rgba(150,60,15,.6)'; c.lineWidth = 7; c.filter = 'blur(1.5px)'; K.stripes.slice(0, 3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
178
+ K.eyes.forEach(e => { if (K.blink) { c.strokeStyle = '#3a200c'; c.lineWidth = 2.5; c.beginPath(); c.arc(e.x, e.y, 8, 0.2, Math.PI - 0.2); c.stroke(); }
179
+ else { const eg = c.createRadialGradient(e.x - 2, e.y - 2, 1, e.x, e.y, 9); eg.addColorStop(0, '#e8f0a0'); eg.addColorStop(1, '#7a9a30'); c.fillStyle = eg; c.beginPath(); c.arc(e.x, e.y, 9, 0, TAU); c.fill(); c.fillStyle = '#120a04'; c.beginPath(); c.ellipse(e.x + 2, e.y, 2.6, 7, 0, 0, TAU); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(e.x - 2, e.y - 3, 1.8, 0, TAU); c.fill(); } });
180
+ c.fillStyle = '#c86a64'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 5, 0, TAU); c.fill();
181
+ c.strokeStyle = 'rgba(60,30,10,.8)'; c.lineWidth = 2; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke();
182
+ c.strokeStyle = 'rgba(255,250,235,.85)'; c.lineWidth = 1.2; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
183
+ c.restore();
184
+ }
185
+
186
+ // ---------- ② 长影子:剪影 → 剪切仿射投到地面 ----------
187
+ function shadows(c, lt, K, G) { // 剪切仿射长影子:P.castShadow
188
+ const kx = 0.85 + 0.6 * ease.inOut(clamp(lt / 1.2));
189
+ P.castShadow(c, [
190
+ [700, g => { g.beginPath(); g.moveTo(300, 700); [[300, 120], [410, 78], [540, 70], [680, 64], [858, 104], [892, 120], [892, 700]].forEach(p => g.lineTo(...p)); g.closePath(); g.rect(370, 140, 420, 400); g.fill('evenodd'); }],
191
+ [905, g => { g.fillRect(810, 600, 395, 305); g.fill(K.body); g.fill(K.head); g.lineWidth = K.tailW; g.stroke(K.tail); }],
192
+ [930, g => { [G.skirt, G.torso, G.hairBack, G.bun, G.face, G.upperArm, G.foreArm, G.hand].forEach(p => p && g.fill(p)); g.fillRect(1300, 742, 190, 163); }],
193
+ ], { kx, ky: 0.22, clipY: GROUND + 2, alpha: 0.55, blur: 1.5, key: 'daliSh' });
194
+ }
195
+
196
+ return {
197
+ draw(c, lt, t) {
198
+ const ch = P.choreo(lt, t);
199
+ c.drawImage(bg(), 0, 0);
200
+ elephant(c, 1060 - lt * 70, lt);
201
+ const K = RIG.cat({ tail: ch.tail * 0.5, blink: ch.blink, breathe: ch.breathe });
202
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe, hair: 'bun' });
203
+ shadows(c, lt, K, G);
204
+ c.drawImage(wallLayer(), 0, 0); c.drawImage(blockLayer(), 0, 0);
205
+ // 枯枝上的软钟(大部分垂下)
206
+ softClock(c, { cx: 975, cy: 104, R: 64, fold: -34, top: 0.25, drip: 0.25 }, 0.1 + 1.2 * ease.inOut(clamp(lt / 1.2)), t + 7, 4.5);
207
+ // 窗台上的软钟(垂在墙面上,慢慢往下流)
208
+ const sag = 0.1 + 1.3 * ease.inOut(clamp(lt / 1.2)); // 钟在 1.2s 里明显往下流
209
+ softClock(c, { cx: 740, cy: 548, R: 72, fold: 4, top: 0.3, drip: 0.4 }, sag, t, 6.5);
210
+ // 石块桌前沿的软钟
211
+ softClock(c, { cx: 912, cy: 610, R: 88, fold: 12, top: 0.28, drip: 0.3 }, sag * 0.9, t + 3, -2.4);
212
+ antWatch(c, lt);
213
+ cat(c, K, lt);
214
+ girl(c, G, ch);
215
+ // 热气:很淡的两缕
216
+ P.steam(c, t, G.cup.x, G.cup.y - 8, { h: 60, n: 2, color: 'rgba(255,250,240,.55)', width: 3, spread: 12, wobble: 7 });
217
+ // 暗角(博物馆灯下的画)
218
+ const vg = c.createRadialGradient(960, 520, 520, 960, 540, 1150); vg.addColorStop(0, 'rgba(0,0,0,0)'); vg.addColorStop(1, 'rgba(25,12,4,.38)'); c.fillStyle = vg; c.fillRect(0, 0, W, H);
219
+ },
220
+ };
221
+ })();
@@ -0,0 +1,150 @@
1
+ // 1942 霍珀美国现实主义(《晨光》《夜鹰》的硬光块面)——纯代码。
2
+ // 核心手法「双版本裁切」:整幅底版画两张缓存——背光版(冷绿墙、暗地板)和受光版(暖黄墙、亮地板);
3
+ // 每帧用会移动的光斑多边形把受光版裁进来 → 光影边缘是硬的、而且能整体平移。人物也画两遍(背光色/受光色),
4
+ // 第二遍裁进斜向光带。少女在墙上的投影 = 在光斑里把背光版透出来(剪影平移后 ∩ 光斑)。
5
+ // 母题动画:光斑缓慢右→左移动(光边扫过少女的脸)、窗外理发店旋转柱、卷帘拉绳轻摆、光里浮尘、热气。
6
+ SCENES['24_hopper'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
8
+ const FLOOR = 730;
9
+ const SH = { wall: '#5d8679', dado: '#4b6d63', floor: '#5b3d2b', board: '#4a3122', trim: '#a7b3a5', trimD: '#7f8d80', blind: '#cfc3a0' };
10
+ const LT = { wall: '#efcf8f', dado: '#d8ad68', floor: '#c98b52', board: '#b0743e', trim: '#f7eed2', trimD: '#d9c9a2', blind: '#f4e8c4' };
11
+ const poly = (c, pts) => { c.beginPath(); pts.forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.closePath(); };
12
+
13
+ // 窗外:对街的红砖立面(《星期天的清晨》),阳光从左来,每个窗洞右侧有硬投影
14
+ function street(g) {
15
+ g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
16
+ const sk = g.createLinearGradient(0, 140, 0, 220); sk.addColorStop(0, '#5f97c9'); sk.addColorStop(1, '#a9c8dc'); g.fillStyle = sk; g.fillRect(370, 140, 420, 80);
17
+ g.fillStyle = '#b4553a'; g.fillRect(370, 205, 420, 335); // 砖立面
18
+ g.fillStyle = '#d27a52'; g.fillRect(370, 196, 420, 14); g.fillStyle = '#5a2418'; g.fillRect(370, 210, 420, 10); // 檐口+檐下硬影
19
+ for (let row = 0; row < 2; row++) for (let k = 0; k < 4; k++) {
20
+ const x = 392 + k * 102, y = 240 + row * 92;
21
+ g.fillStyle = '#e8dcc0'; g.fillRect(x - 6, y - 6, 58, 76); // 窗套
22
+ g.fillStyle = '#1f2a24'; g.fillRect(x, y, 46, 64);
23
+ const sh = [0.55, 0.3, 0.7, 0.42, 0.25, 0.6, 0.5, 0.35][row * 4 + k]; // 半拉的深绿卷帘,高低不一
24
+ g.fillStyle = '#3d6a4e'; g.fillRect(x, y, 46, 64 * sh);
25
+ g.fillStyle = 'rgba(40,10,5,.55)'; g.fillRect(x + 46, y - 6, 10, 76); // 窗洞右侧硬影
26
+ }
27
+ g.fillStyle = '#2d5a44'; g.fillRect(370, 432, 420, 108); // 店面
28
+ g.fillStyle = '#c9d6c8'; [[386, 448, 150], [560, 448, 150]].forEach(([x, y, w]) => { g.fillRect(x, y, w, 70); });
29
+ g.fillStyle = 'rgba(30,50,40,.75)'; [[386, 448, 150], [560, 448, 150]].forEach(([x, y, w]) => { g.fillRect(x, y + 30, w, 40); });
30
+ g.fillStyle = '#e9e0c8'; g.fillRect(370, 424, 420, 10);
31
+ g.fillStyle = '#d8c9a8'; g.fillRect(370, 526, 420, 14); // 人行道
32
+ g.restore();
33
+ }
34
+ function room(g, K) {
35
+ g.fillStyle = K.wall; g.fillRect(0, 0, W, FLOOR);
36
+ g.fillStyle = K.dado; g.fillRect(0, 640, W, FLOOR - 640); g.fillStyle = K.trim; g.fillRect(0, 636, W, 8); g.fillRect(0, FLOOR - 14, W, 14);
37
+ g.fillStyle = K.floor; g.fillRect(0, FLOOR, W, H - FLOOR);
38
+ g.strokeStyle = K.board; g.lineWidth = 3; for (let k = 1; k < 8; k++) { const y = FLOOR + Math.pow(k / 8, 1.5) * (H - FLOOR); g.beginPath(); g.moveTo(0, y); g.lineTo(W, y); g.stroke(); }
39
+ street(g);
40
+ // 窗套(白漆)+窗台+中梃
41
+ g.fillStyle = K.trim; g.fillRect(346, 116, 468, 24); g.fillRect(346, 116, 24, 446); g.fillRect(790, 116, 24, 446); g.fillRect(330, 540, 500, 26);
42
+ g.fillStyle = K.trimD; g.fillRect(330, 566, 500, 10); g.fillRect(370, 330, 420, 10); g.fillRect(575, 140, 10, 400);
43
+ // 卷帘(拉下来一截)
44
+ g.fillStyle = K.blind; g.fillRect(366, 140, 428, 92); g.fillStyle = K.trimD; g.fillRect(366, 228, 428, 6);
45
+ // 墙上一幅小画框(霍珀房间里常有的空白小画)
46
+ g.fillStyle = K.trimD; g.fillRect(1560, 300, 160, 120); g.fillStyle = K.dado; g.fillRect(1572, 312, 136, 96);
47
+ }
48
+ const bgShade = () => P.cached('hop_sh', W, H, g => room(g, SH));
49
+ const bgLit = () => P.cached('hop_lt', W, H, g => room(g, LT));
50
+ // 油画刷痕:竖向拉长的 fbm 为主、斜向为辅,soft-light 叠 α0.2
51
+ const brush = () => P.cached('hop_brush', W, H, (g) => {
52
+ const s = 4, w = W / s, h = H / s, sm = P.canvas(w, h), sg = sm.getContext('2d'), im = sg.createImageData(w, h), d = im.data;
53
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { const n = P.fbm(x * 0.06, y * 0.006, 4) * 0.7 + P.fbm(x * 0.02 + y * 0.02, 3.3, 3) * 0.3, v = 128 + n * 110, i = (y * w + x) * 4; d[i] = d[i + 1] = d[i + 2] = v; d[i + 3] = 255; }
54
+ sg.putImageData(im, 0, 0); g.imageSmoothingQuality = 'high'; g.drawImage(sm, 0, 0, W, H);
55
+ });
56
+
57
+ // ---------- 家具与角色:同一个函数,传背光/受光两套色 ----------
58
+ const TS = { wood: '#4f3322', woodD: '#3a2418', skin: '#c89a84', skinD: '#a87866', hair: '#a8893f', hairD: '#7f6428', dress: '#7a2c2a', dressD: '#5e1f1e', collar: '#b9b09c',
59
+ orange: '#a6602c', orangeD: '#80461c', white: '#dcd6c8', stripe: '#6e3a18', cup: '#bfb8a8', eye: '#6a8a3a', band: '#3a5a8a' };
60
+ const TL = { wood: '#a86c3c', woodD: '#7a4a26', skin: '#f7d3b4', skinD: '#e0ad8c', hair: '#f3d67e', hairD: '#c9a24a', dress: '#cc4a3c', dressD: '#9e3328', collar: '#f6efdc',
61
+ orange: '#f0a04a', orangeD: '#c8722a', white: '#fbf5e6', stripe: '#c0642a', cup: '#fbf8f0', eye: '#a8d050', band: '#4a78c0' };
62
+ function furniture(c, K) {
63
+ c.fillStyle = K.wood; c.fillRect(810, 618, 400, 26); c.fillStyle = K.woodD; c.fillRect(826, 644, 368, 22);
64
+ c.fillStyle = K.wood; c.fillRect(840, 644, 20, 261); c.fillRect(1160, 644, 20, 261);
65
+ c.fillStyle = K.woodD; c.fillRect(1436, 470, 18, 440); c.fillRect(1474, 478, 18, 430); c.fillStyle = K.wood; c.fillRect(1290, 744, 210, 18); c.fillRect(1300, 760, 16, 150);
66
+ }
67
+ function cat(c, K, KK) {
68
+ c.save(); c.lineCap = 'round';
69
+ c.strokeStyle = K.orange; c.lineWidth = KK.tailW; c.stroke(KK.tail);
70
+ const tip = KK.tailPts[KK.tailPts.length - 1]; c.fillStyle = K.white; c.beginPath(); c.arc(tip[0], tip[1], KK.tailW / 2, 0, TAU); c.fill();
71
+ c.fillStyle = K.orange; c.fill(KK.body);
72
+ c.save(); c.clip(KK.body); c.fillStyle = K.orangeD; c.beginPath(); c.ellipse(450, 830, 70, 120, 0.1, 0, TAU); c.fill(); c.restore(); // 背侧暗面(块面)
73
+ KK.legs.forEach(l => { c.fillStyle = K.white; c.fill(l); });
74
+ c.save(); c.clip(KK.body); c.fillStyle = K.white; c.beginPath(); c.ellipse(606, 768, 40, 92, -0.05, 0, TAU); c.fill(); c.restore(); // 白胸:平滑椭圆(RIG 的 white 是直线多边形,不描线时像贴片)
75
+ c.fillStyle = K.orange; c.fill(KK.head);
76
+ c.save(); c.clip(KK.head); c.fillStyle = K.white; c.beginPath(); c.ellipse(KK.nose[0] - 22, KK.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
77
+ c.strokeStyle = K.stripe; c.lineWidth = 7; KK.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); });
78
+ KK.eyes.forEach(e => { if (KK.blink) { c.strokeStyle = '#2a1a0e'; c.lineWidth = 3; c.beginPath(); c.moveTo(e.x - 8, e.y + 1); c.lineTo(e.x + 8, e.y + 1); c.stroke(); }
79
+ else { c.fillStyle = K.eye; c.beginPath(); c.arc(e.x, e.y, 8, 0, TAU); c.fill(); c.fillStyle = '#1a120a'; c.beginPath(); c.ellipse(e.x + 2, e.y, 2.4, 6.5, 0, 0, TAU); c.fill(); } });
80
+ c.fillStyle = '#b8605a'; c.beginPath(); c.arc(KK.nose[0], KK.nose[1], 5, 0, TAU); c.fill();
81
+ c.strokeStyle = '#3a2416'; c.lineWidth = 2; c.beginPath(); c.moveTo(...KK.mouth[0]); c.quadraticCurveTo(...KK.mouth[1], ...KK.mouth[2]); c.stroke();
82
+ c.restore();
83
+ }
84
+ function girl(c, K, G, ch) {
85
+ c.save(); c.lineCap = 'round'; c.lineJoin = 'round';
86
+ const f = (p, col) => { if (p) { c.fillStyle = col; c.fill(p); } };
87
+ f(G.farSleeve, K.dressD); f(G.farHand, K.skinD); f(G.farCuff, K.collar);
88
+ f(G.skirt, K.dress);
89
+ c.save(); c.clip(G.skirt); c.fillStyle = K.dressD; c.beginPath(); c.moveTo(1330, 640); c.lineTo(1460, 640); c.lineTo(1460, 940); c.lineTo(1300, 940); c.lineTo(1352, 780); c.closePath(); c.fill(); c.restore(); // 膝后暗块
90
+ c.strokeStyle = K.dressD; c.lineWidth = 4; G.folds.forEach(p => c.stroke(p));
91
+ f(G.shoe, '#2a1810');
92
+ f(G.torso, K.dress);
93
+ c.save(); c.clip(G.torso); c.fillStyle = K.dressD; c.fillRect(1375, 420, 80, 220); c.restore();
94
+ const nk = G.A.neck; c.fillStyle = K.collar; c.beginPath(); c.moveTo(nk[0] - 20, nk[1] - 2); c.lineTo(nk[0] + 24, nk[1]); c.lineTo(nk[0] + 6, nk[1] + 30); c.closePath(); c.fill(); // 白领尖
95
+ f(G.hairDown, K.hair); f(G.neck, K.skinD); f(G.hairBack, K.hair);
96
+ c.save(); c.clip(G.hairBack); c.fillStyle = K.hairD; c.beginPath(); c.ellipse(G.A.nape[0] + 20, G.A.nape[1] - 10, 50, 40, 0, 0, TAU); c.fill(); c.restore();
97
+ f(G.face, K.skin);
98
+ c.save(); c.clip(G.face); c.fillStyle = K.skinD; c.beginPath(); c.ellipse(G.A.ear[0] + 6, G.A.ear[1] + 10, 26, 46, 0, 0, TAU); c.fill(); c.restore(); // 颊后暗面
99
+ f(G.bangs, K.hair);
100
+ { const a = G.A.headTop, nk = G.A.nape; c.strokeStyle = K.band; c.lineWidth = 9; c.beginPath(); c.moveTo(a[0] - 52, a[1] + 26); c.quadraticCurveTo(a[0] + 10, a[1] - 6, nk[0] + 30, nk[1] - 62); c.stroke(); } // 发带(少女感)
101
+ c.strokeStyle = K.hairD; c.lineWidth = 3; G.hairLines.forEach(p => c.stroke(p));
102
+ if (ch.blink) { c.strokeStyle = '#3a2418'; c.lineWidth = 2.6; c.stroke(G.lid); }
103
+ else { c.fillStyle = '#2e2018'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1, 3.6, 4.6, 0, 0, TAU); c.fill(); c.strokeStyle = '#3a2418'; c.lineWidth = 2.4; c.stroke(G.lid); }
104
+ c.strokeStyle = K.hairD; c.lineWidth = 2.4; c.stroke(G.brow);
105
+ c.fillStyle = '#a83a36'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
106
+ f(G.upperArm, K.dress); f(G.foreArm, K.dress); f(G.cuff, K.collar); f(G.hand, K.skin);
107
+ RIG.drawCup(c, G.cup, { body: K.cup, rim: '#5a3a22', hw: 6 });
108
+ c.fillStyle = K.skin; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill();
109
+ c.restore();
110
+ }
111
+
112
+ return {
113
+ draw(c, lt, t) {
114
+ const ch = P.choreo(lt, t);
115
+ const dx = 30 - 95 * ease.inOut(clamp(lt / 1.25)); // 光斑 1.2s 里向左移 ~95px(太阳在走)
116
+ const KK = RIG.cat({ tail: ch.tail * 0.6, blink: ch.blink, breathe: ch.breathe });
117
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe, hair: 'long' });
118
+ // ① 背光底版 → 墙/地光斑里透出受光底版
119
+ c.drawImage(bgShade(), 0, 0);
120
+ const WP = [[1420 + dx, 250], [1920, 470], [1920, FLOOR], [1420 + dx, FLOOR]];
121
+ const FP = [[1420 + dx, FLOOR], [1920, FLOOR], [1920, H], [1700 + dx, H]];
122
+ P.litClip(c, [WP, FP], c => { c.drawImage(bgLit(), 0, 0);
123
+ // 光斑里的窗棂影(竖+斜横)与少女投在墙上的影:都把背光版透出来
124
+ const S = P.scratch('hopSh'), sg = S.getContext('2d'); sg.reset(); sg.clearRect(0, 0, W, H); sg.fillStyle = '#000';
125
+ sg.fillRect(1650 + dx, 200, 24, FLOOR - 200); poly(sg, [[1650 + dx, FLOOR], [1674 + dx, FLOOR], [1820 + dx, H], [1796 + dx, H]]); sg.fill();
126
+ poly(sg, [[1420 + dx, 470], [1920, 680], [1920, 702], [1420 + dx, 492]]); sg.fill();
127
+ sg.save(); sg.translate(150, 18); [G.torso, G.hairBack, G.bun, G.face, G.bangs, G.upperArm, G.foreArm, G.hand, G.skirt].forEach(p => p && sg.fill(p)); sg.restore();
128
+ sg.globalCompositeOperation = 'source-in'; sg.drawImage(bgShade(), 0, 0); sg.globalCompositeOperation = 'source-over';
129
+ c.drawImage(S, 0, 0); });
130
+ // ② 窗外理发店旋转柱(红白蓝斜纹上行)
131
+ c.save(); const bx = 742, by = 446, bw = 18, bh = 64; c.beginPath(); c.rect(bx, by, bw, bh); c.clip(); c.fillStyle = '#f4efe2'; c.fillRect(bx, by, bw, bh);
132
+ const off = (lt * 60) % 24; c.lineWidth = 6; for (let k = -4; k < 6; k++) { const y = by + k * 24 - off; c.strokeStyle = k % 2 ? '#c8302a' : '#2a4a9a'; c.beginPath(); c.moveTo(bx - 4, y + 14); c.lineTo(bx + bw + 4, y); c.stroke(); }
133
+ c.restore(); c.fillStyle = '#d9d2bf'; c.fillRect(bx - 3, by - 8, bw + 6, 8); c.fillRect(bx - 3, by + bh, bw + 6, 8);
134
+ // 卷帘拉绳轻摆
135
+ const sw = Math.sin(lt * 4.2) * 0.07; c.save(); c.translate(580, 232); c.rotate(sw); c.strokeStyle = '#7a6a4a'; c.lineWidth = 2; c.beginPath(); c.moveTo(0, 0); c.lineTo(0, 70); c.stroke();
136
+ c.strokeStyle = '#c9b88a'; c.lineWidth = 4; c.beginPath(); c.arc(0, 78, 8, 0, TAU); c.stroke(); c.restore();
137
+ // ③ 家具与角色:先背光色,再裁进斜光带画受光色
138
+ furniture(c, TS); cat(c, TS, KK); girl(c, TS, G, ch);
139
+ const OP = [[1345 + dx, 0], [W, 0], [W, H], [1185 + dx, H]];
140
+ P.litClip(c, [OP], c => { furniture(c, TL); girl(c, TL, G, ch); });
141
+ // 光带里的浮尘
142
+ c.save(); c.globalCompositeOperation = 'lighter'; P.particles(26, 42, t, { x0: 1250, x1: 1900, y0: 760, y1: 120, speed: 40, drift: 18, life: 9 }).forEach(p => {
143
+ const ex = 1345 + dx - (p.y / H) * 160; if (p.x < ex) return; c.fillStyle = `rgba(255,235,180,${0.35 + 0.3 * Math.sin(t * 6 + p.i)})`; c.beginPath(); c.arc(p.x, p.y, 2.2 * p.s, 0, TAU); c.fill(); });
144
+ c.restore();
145
+ P.steam(c, t, G.cup.x, G.cup.y - 8, { h: 55, n: 2, color: 'rgba(255,248,230,.5)', width: 3, spread: 12, wobble: 6 });
146
+ // 画布刷痕
147
+ c.save(); c.globalCompositeOperation = 'soft-light'; c.globalAlpha = 0.2; c.drawImage(brush(), 0, 0); c.restore();
148
+ },
149
+ };
150
+ })();