@biunow/agent-biuart 0.0.0-stage → 0.1.0

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,522 @@
1
+ // 1290 哥特泥金手抄本——纯代码。
2
+ // 管线:①静态层(羊皮纸+逐像素金箔+菱格压花+哥特尖拱/小尖塔/彩窗/桌/宝座/拉丁文栏)缓存
3
+ // → ②金箔掩膜(从静态层按「金色度」逐像素分类得到)→ ③每帧:彩窗闪烁、藤蔓花饰摇摆、斜向高光扫过(只落在金上)
4
+ // → ④角色:平涂+粗墨线(光环、金发、红袍金细条、蓝斗篷、金杯),稚拙人脸猫拎着老鼠 → ⑤蜗牛爬、老鼠跑、金杯闪光
5
+ SCENES['05_gothic'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp } = U, P = PAINT;
7
+ const INK = '#2b1a12';
8
+ const C = {
9
+ parch: '#ecdfc0', parchDk: '#c9ae80', gold: '#d9a63a', goldHi: '#f8de86', goldLo: '#9c6a1a',
10
+ red: '#c3262e', redDk: '#7e1318', blue: '#24449e', blueDk: '#142a6a', pink: '#e7a3aa', green: '#55a03c', greenDk: '#2f6e26',
11
+ white: '#f8f2e2', wood: '#8a5a2c', woodDk: '#5a3818', skin: '#f5dcc2', hair: '#e9b746', hairDk: '#9a6618',
12
+ };
13
+ const ease = U.ease.inOut;
14
+ const R_ = (seed) => U.rng(seed);
15
+
16
+ // ---------- 小工具 ----------
17
+ const F = (c, p, fill, line = INK, lw = 3) => { if (!p) return; if (fill) { c.fillStyle = fill; c.fill(p); } if (line && lw) { c.strokeStyle = line; c.lineWidth = lw; c.stroke(p); } };
18
+ const poly = (pts, closed = true) => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); if (closed) p.closePath(); return p; };
19
+ // 尖拱:xl..xr,起拱线 ys,底 yb,半径 r(>宽/2 才是尖的)
20
+ const arch = (xl, xr, ys, yb, r) => {
21
+ const p = new Path2D(), w = xr - xl, mid = (xl + xr) / 2, h = Math.sqrt(r * r - (r - w / 2) ** 2);
22
+ const a1 = Math.atan2(-h, mid - (xl + r)), a2 = Math.atan2(-h, mid - (xr - r));
23
+ p.moveTo(xl, yb); p.lineTo(xl, ys); p.arc(xl + r, ys, r, Math.PI, a1 + Math.PI * 2); p.arc(xr - r, ys, r, a2, 0); p.lineTo(xr, yb); p.closePath(); return p;
24
+ };
25
+ const dot = (c, x, y, r, fill, line = INK, lw = 1.5) => { c.beginPath(); c.arc(x, y, r, 0, Math.PI * 2); if (fill) { c.fillStyle = fill; c.fill(); } if (line) { c.strokeStyle = line; c.lineWidth = lw; c.stroke(); } };
26
+
27
+ // ---------- 金箔:逐像素(大尺度斑驳+横向打磨细纹+颗粒+左上受光) ----------
28
+ const goldTex = () => P.cached('goth_goldTex', W, H, (g) => {
29
+ const x0 = 290, x1 = 1480, y0 = 20, y1 = 960, img = g.createImageData(W, H), d = img.data, r = R_(5);
30
+ const lo = P.hex(C.goldLo), mid = P.hex(C.gold), hi = P.hex(C.goldHi);
31
+ for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) {
32
+ const big = P.fbm(x * 0.0035, y * 0.0035 + 7, 3);
33
+ const brush = P.noise(x * 0.004, y * 0.09) * 0.5 + P.noise(x * 0.012, y * 0.25) * 0.3; // 横向打磨纹
34
+ const light = 0.18 - (x - x0) / (x1 - x0) * 0.12 - (y - y0) / (y1 - y0) * 0.16;
35
+ let f = 0.52 + big * 0.42 + brush * 0.22 + light + (r() - .5) * 0.10;
36
+ f = clamp(f, 0, 1);
37
+ const c0 = f < 0.5 ? P.mix(lo, mid, f * 2) : P.mix(mid, hi, (f - 0.5) * 2);
38
+ const i = (y * W + x) * 4; d[i] = c0[0]; d[i + 1] = c0[1]; d[i + 2] = c0[2]; d[i + 3] = 255;
39
+ }
40
+ g.putImageData(img, 0, 0);
41
+ });
42
+
43
+ // ---------- 几何 ----------
44
+ const PANEL = poly([[328, 255], [328, 205], [580, 66], [830, 205], [985, 96], [1135, 205], [1285, 96], [1440, 205], [1440, 255], [1440, 895], [328, 895]]);
45
+ const WIN = arch(362, 798, 360, 540, 240);
46
+ const LANCETS = [arch(392, 565, 414, 532, 131), arch(595, 768, 414, 532, 131)];
47
+
48
+ // 羊皮纸+边缘做旧
49
+ function parchment(g) {
50
+ g.drawImage(P.texture('goth_parch', C.parch, { scale: 0.0028, amt: 20, grain: 9, dark: C.parchDk, seed: 3 }), 0, 0);
51
+ const v = g.createRadialGradient(W / 2, H / 2, 380, W / 2, H / 2, 1150);
52
+ v.addColorStop(0, 'rgba(120,80,30,0)'); v.addColorStop(1, 'rgba(110,70,25,.5)');
53
+ g.fillStyle = v; g.fillRect(0, 0, W, H);
54
+ }
55
+
56
+ // 菱格压花:深线+错 1px 亮线 = 刻进金箔的感觉;菱心四点压印(上亮下暗)
57
+ function lattice(g) {
58
+ const s = 46;
59
+ g.save(); g.clip(PANEL);
60
+ g.drawImage(goldTex(), 0, 0);
61
+ g.lineWidth = 1.6;
62
+ for (const [col, off] of [['rgba(255,240,180,.55)', 1.2], ['rgba(110,66,10,.55)', 0]]) {
63
+ g.strokeStyle = col; g.beginPath();
64
+ for (let k = -40; k < 80; k++) { const u = k * s; g.moveTo(u - 1200 + off, 1200 + off); g.lineTo(u + 1200 + off, -1200 + off); g.moveTo(u - 1200 + off, -1200 + off); g.lineTo(u + 1200 + off, 1200 + off); }
65
+ g.stroke();
66
+ }
67
+ for (let a = -10; a < 70; a++) for (let b = -40; b < 40; b++) {
68
+ const u = (a + 0.5) * s, v = (b + 0.5) * s, x = (u + v) / 2, y = (u - v) / 2;
69
+ if (x < 320 || x > 1450 || y < 60 || y > 900) continue;
70
+ for (const [dx, dy] of [[-5, 0], [5, 0], [0, -5], [0, 5]]) {
71
+ g.fillStyle = 'rgba(255,245,200,.7)'; g.beginPath(); g.arc(x + dx - .6, y + dy - .6, 1.9, 0, 7); g.fill();
72
+ g.fillStyle = 'rgba(120,70,10,.55)'; g.beginPath(); g.arc(x + dx + .7, y + dy + .7, 1.5, 0, 7); g.fill();
73
+ }
74
+ }
75
+ // 金箔接缝:几条极淡的方块拼接线(金箔是一片片贴上去的)
76
+ g.strokeStyle = 'rgba(120,75,15,.18)'; g.lineWidth = 1;
77
+ for (let x = 328 + 118; x < 1440; x += 118) { g.beginPath(); g.moveTo(x, 60); g.lineTo(x + 3, 895); g.stroke(); }
78
+ for (let y = 140; y < 895; y += 125) { g.beginPath(); g.moveTo(328, y); g.lineTo(1440, y + 2); g.stroke(); }
79
+ g.restore();
80
+ g.strokeStyle = INK; g.lineWidth = 3; g.stroke(PANEL);
81
+ }
82
+
83
+ // 山墙、小尖塔、侧柱、下边框、草地
84
+ function architecture(g) {
85
+ const band = (pts, col, w) => {
86
+ g.lineJoin = 'miter'; g.lineCap = 'butt';
87
+ g.strokeStyle = INK; g.lineWidth = w + 6; g.beginPath(); pts.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.stroke();
88
+ g.strokeStyle = col; g.lineWidth = w; g.stroke();
89
+ // 白点
90
+ for (let i = 0; i < pts.length - 1; i++) { const [a, b] = [pts[i], pts[i + 1]], L = Math.hypot(b[0] - a[0], b[1] - a[1]), n = Math.floor(L / 22);
91
+ for (let k = 1; k < n; k++) { const q = k / n; dot(g, lerp(a[0], b[0], q), lerp(a[1], b[1], q), 2.6, '#fff', null); } }
92
+ // 卷叶饰金球(沿外沿)
93
+ for (let i = 0; i < pts.length - 1; i++) { const [a, b] = [pts[i], pts[i + 1]], L = Math.hypot(b[0] - a[0], b[1] - a[1]), n = Math.floor(L / 34);
94
+ const nx = (b[1] - a[1]) / L, ny = -(b[0] - a[0]) / L, sg = ny < 0 ? 1 : -1;
95
+ for (let k = 1; k < n; k++) { const q = k / n; dot(g, lerp(a[0], b[0], q) + nx * sg * (w / 2 + 9), lerp(a[1], b[1], q) + ny * sg * (w / 2 + 9), 6, C.gold, INK, 2); } }
96
+ };
97
+ band([[322, 196], [580, 52], [838, 196]], C.pink, 30);
98
+ band([[836, 196], [985, 82], [1135, 196]], C.blue, 26);
99
+ band([[1135, 196], [1285, 82], [1446, 196]], C.blue, 26);
100
+ // 三叶拱(中、右山墙之下):深金描线
101
+ g.strokeStyle = 'rgba(110,66,10,.8)'; g.lineWidth = 3;
102
+ for (const cx of [985, 1285]) { g.beginPath(); g.arc(cx - 88, 262, 62, Math.PI * 1.05, Math.PI * 1.85); g.arc(cx, 205, 70, Math.PI * 1.12, Math.PI * 1.88); g.arc(cx + 88, 262, 62, Math.PI * 1.15, Math.PI * 1.95); g.stroke(); }
103
+ // 吊坠尖
104
+ for (const x of [835, 1135]) { const p = poly([[x - 16, 238], [x + 16, 238], [x, 282]]); F(g, p, C.white, INK, 2.5); dot(g, x, 252, 4.5, C.red, null); }
105
+ // 十字顶饰
106
+ for (const [x, y] of [[580, 30], [985, 60], [1285, 60]]) { g.fillStyle = C.gold; g.strokeStyle = INK; g.lineWidth = 2; g.fillRect(x - 4, y - 22, 8, 30); g.strokeRect(x - 4, y - 22, 8, 30); g.fillRect(x - 12, y - 14, 24, 7); g.strokeRect(x - 12, y - 14, 24, 7); }
107
+ // 小尖塔
108
+ for (const [x, top, bot] of [[312, 68, 248], [835, 92, 236], [1135, 92, 236], [1455, 68, 248]]) {
109
+ const sw = 22, shaftTop = top + 62;
110
+ g.fillStyle = C.white; g.fillRect(x - sw / 2, shaftTop, sw, bot - shaftTop);
111
+ g.fillStyle = C.blue; for (const dx of [-5, 3]) g.fillRect(x + dx, shaftTop + 8, 3, bot - shaftTop - 16);
112
+ g.strokeStyle = INK; g.lineWidth = 2.5; g.strokeRect(x - sw / 2, shaftTop, sw, bot - shaftTop);
113
+ F(g, poly([[x - 15, shaftTop], [x, top], [x + 15, shaftTop]]), C.red, INK, 2.5);
114
+ g.fillStyle = C.gold; g.fillRect(x - 15, shaftTop - 2, 30, 7); g.strokeRect(x - 15, shaftTop - 2, 30, 7);
115
+ dot(g, x, top - 4, 5, C.gold, INK, 2);
116
+ }
117
+ // 侧柱:红/蓝/粉方块+白点
118
+ const cols = [C.red, C.blue, C.pink, C.blue];
119
+ for (const x0 of [298, 1440]) for (let y = 250, k = 0; y < 945; y += 31, k++) {
120
+ g.fillStyle = cols[k % 4]; g.fillRect(x0, y, 30, 31); g.strokeStyle = INK; g.lineWidth = 2; g.strokeRect(x0, y, 30, 31);
121
+ dot(g, x0 + 15, y + 15.5, 4, '#fff', null);
122
+ }
123
+ g.fillStyle = C.gold; g.fillRect(296, 240, 34, 12); g.fillRect(1438, 240, 34, 12); g.strokeStyle = INK; g.strokeRect(296, 240, 34, 12); g.strokeRect(1438, 240, 34, 12);
124
+ // 草地
125
+ g.fillStyle = C.green; g.fillRect(328, 892, 1112, 53);
126
+ g.fillStyle = C.greenDk; g.fillRect(328, 892, 1112, 5);
127
+ const r = R_(11); g.strokeStyle = C.greenDk; g.lineWidth = 2;
128
+ for (let i = 0; i < 90; i++) { const x = 340 + r() * 1090, y = 905 + r() * 34; g.beginPath(); g.moveTo(x - 4, y - 6); g.lineTo(x, y + 2); g.lineTo(x + 5, y - 7); g.stroke(); }
129
+ for (const x of [1000, 1040, 1052, 1085, 1118, 1180, 760, 690]) { const y = 900 + (x % 7); for (let k = 0; k < 5; k++) dot(g, x + Math.cos(k * 1.26) * 4, y + Math.sin(k * 1.26) * 4, 2.4, '#fff', null); dot(g, x, y, 2, '#f2c230', null); }
130
+ // 下边框:红蓝相间+白波浪
131
+ for (let x = 322, k = 0; x < 1440; x += 52, k++) {
132
+ const w = Math.min(52, 1440 - x); g.fillStyle = k % 2 ? C.blue : C.red; g.fillRect(x, 945, w, 26);
133
+ g.strokeStyle = '#fff'; g.lineWidth = 2; g.beginPath(); for (let s = 0; s <= w - 10; s += 2) { const yy = 958 + Math.sin(s / (w - 10) * Math.PI * 2) * 4; s ? g.lineTo(x + 5 + s, yy) : g.moveTo(x + 5, yy); } g.stroke();
134
+ g.strokeStyle = INK; g.lineWidth = 2; g.strokeRect(x, 945, w, 26);
135
+ }
136
+ g.lineWidth = 3; g.strokeRect(298, 943, 1172, 30);
137
+ for (const [x, y] of [[310, 958], [1458, 958]]) { g.fillStyle = C.gold; g.fillRect(x - 13, y - 13, 26, 26); g.strokeStyle = INK; g.strokeRect(x - 13, y - 13, 26, 26); }
138
+ dot(g, 1468, 968, 8, C.gold, INK, 2);
139
+ }
140
+
141
+ // 尖拱窗:四叶玫瑰窗+两扇柳叶窗的红蓝菱格彩玻璃
142
+ function windowStatic(g) {
143
+ g.save(); g.shadowColor = 'rgba(80,40,0,.35)'; g.shadowOffsetX = 5; g.shadowOffsetY = 5; F(g, WIN, '#f6eedb', INK, 4); g.restore();
144
+ g.strokeStyle = INK; g.lineWidth = 3; g.stroke(WIN);
145
+ // 内圈细线
146
+ const inner = arch(374, 786, 362, 540, 228); g.strokeStyle = 'rgba(140,110,70,.5)'; g.lineWidth = 2; g.stroke(inner);
147
+ // 四叶玫瑰窗
148
+ const rc = [580, 222];
149
+ const q = new Path2D(); for (let k = 0; k < 4; k++) { const a = k * Math.PI / 2 - Math.PI / 2; q.moveTo(rc[0] + Math.cos(a) * 33 + 30, rc[1] + Math.sin(a) * 33); q.arc(rc[0] + Math.cos(a) * 33, rc[1] + Math.sin(a) * 33, 30, 0, Math.PI * 2); }
150
+ F(g, q, C.red, INK, 3);
151
+ for (let k = 0; k < 4; k++) { const a = k * Math.PI / 2 - Math.PI / 2; dot(g, rc[0] + Math.cos(a) * 33, rc[1] + Math.sin(a) * 33, 20, C.blue, INK, 2.5); dot(g, rc[0] + Math.cos(a) * 33, rc[1] + Math.sin(a) * 33, 6, C.gold, INK, 1.5); }
152
+ dot(g, rc[0], rc[1], 13, C.gold, INK, 2.5);
153
+ for (const [dx, dy] of [[0, -9], [-9, 6], [9, 6]]) dot(g, 578 + dx, 316 + dy, 8, C.blue, INK, 2);
154
+ // 柳叶窗:红蓝菱格+金铅条+白点
155
+ LANCETS.forEach((L, li) => {
156
+ g.save(); g.clip(L);
157
+ const s = 38;
158
+ for (let a = -30; a < 60; a++) for (let b = -30; b < 30; b++) {
159
+ const u = a * s, v = b * s, x = (u + v) / 2, y = (u - v) / 2;
160
+ if (x < 370 || x > 790 || y < 260 || y > 560) continue;
161
+ const dm = poly([[x, y - s / 2], [x + s / 2, y], [x, y + s / 2], [x - s / 2, y]]);
162
+ g.fillStyle = (a + b) % 2 ? C.red : C.blue; g.fill(dm);
163
+ if ((a + b) % 2 === 0) for (const [dx, dy] of [[-4, 0], [4, 0], [0, -4], [0, 4], [0, 0]]) dot(g, x + dx, y + dy, 1.8, '#fff', null);
164
+ else dot(g, x, y, 4, C.gold, null);
165
+ }
166
+ // 铅条:沿菱形边(x+y、x−y 为常数的两族斜线)
167
+ g.strokeStyle = C.gold; g.lineWidth = 3; g.beginPath();
168
+ for (let k = -60; k < 120; k++) { const c0 = (k + 0.5) * s; g.moveTo(c0 - 1000, 1000); g.lineTo(c0 + 1000, -1000); g.moveTo(c0 - 1000, -1000); g.lineTo(c0 + 1000, 1000); }
169
+ g.stroke(); g.restore();
170
+ F(g, L, null, INK, 4);
171
+ });
172
+ // 窗台
173
+ const sill = new Path2D(); sill.rect(345, 538, 470, 24); F(g, sill, C.pink, INK, 3);
174
+ g.fillStyle = 'rgba(255,255,255,.5)'; g.fillRect(350, 542, 460, 4);
175
+ }
176
+
177
+ // 桌(白桌布+蓝竖条+扇贝边)、宝座、面包、刀
178
+ function furniture(g) {
179
+ // 支架腿(A 字)
180
+ g.lineCap = 'round';
181
+ for (const x of [872, 1142]) for (const [a, b] of [[-26, 18], [26, -18]]) { g.strokeStyle = INK; g.lineWidth = 15; g.beginPath(); g.moveTo(x + b, 715); g.lineTo(x + a, 898); g.stroke(); g.strokeStyle = C.wood; g.lineWidth = 10; g.stroke(); }
182
+ for (const x of [872, 1142]) { g.fillStyle = C.wood; g.fillRect(x - 22, 830, 44, 8); g.strokeStyle = INK; g.lineWidth = 2; g.strokeRect(x - 22, 830, 44, 8); }
183
+ // 桌面
184
+ const top = poly([[805, 578], [1215, 578], [1210, 645], [810, 645]]); F(g, top, C.white, INK, 3);
185
+ g.fillStyle = 'rgba(150,140,120,.18)'; g.fillRect(810, 630, 400, 14);
186
+ g.strokeStyle = C.blue; g.lineWidth = 2.5; for (const x of [828, 836, 1184, 1192]) { g.beginPath(); g.moveTo(x, 580); g.lineTo(x + (x < 1000 ? 2 : -2), 644); g.stroke(); }
187
+ // 垂布:扇贝下摆
188
+ const cl = new Path2D(); cl.moveTo(812, 645); cl.lineTo(1208, 645); cl.lineTo(1205, 712);
189
+ for (let x = 1205; x > 815; x -= 26) cl.quadraticCurveTo(x - 13, 742, x - 26, 712);
190
+ cl.closePath(); F(g, cl, C.white, INK, 3);
191
+ g.save(); g.clip(cl);
192
+ g.strokeStyle = 'rgba(36,68,158,.8)'; g.lineWidth = 2; for (let x = 822; x < 1205; x += 26) { g.beginPath(); g.moveTo(x, 648); g.quadraticCurveTo(x + 3, 690, x + 1, 735); g.stroke(); }
193
+ g.strokeStyle = 'rgba(150,140,120,.45)'; g.lineWidth = 1.5; for (let x = 834; x < 1205; x += 26) { g.beginPath(); g.moveTo(x, 650); g.lineTo(x + 2, 725); g.stroke(); }
194
+ g.restore();
195
+ for (let x = 826; x < 1200; x += 13) dot(g, x, 700, 2.3, C.red, null);
196
+ // 面包+刀
197
+ const br = new Path2D(); br.ellipse(880, 616, 34, 19, 0, 0, Math.PI * 2); F(g, br, '#d9a058', INK, 2.5);
198
+ g.strokeStyle = '#8a5524'; g.lineWidth = 2.5; g.beginPath(); g.moveTo(880, 600); g.lineTo(880, 632); g.moveTo(862, 616); g.quadraticCurveTo(880, 606, 898, 616); g.stroke();
199
+ g.strokeStyle = INK; g.lineWidth = 6; g.beginPath(); g.moveTo(1106, 636); g.lineTo(1140, 632); g.stroke(); g.strokeStyle = '#7a4a20'; g.lineWidth = 4; g.stroke();
200
+ g.strokeStyle = INK; g.lineWidth = 4; g.beginPath(); g.moveTo(1140, 632); g.lineTo(1186, 627); g.stroke(); g.strokeStyle = '#d8d8d8'; g.lineWidth = 2.5; g.stroke();
201
+ // 宝座:绿靠背+金顶球、红坐垫、粉色基座+蓝尖拱
202
+ const back = poly([[1438, 430], [1478, 430], [1480, 748], [1438, 748]]); F(g, back, '#3c8a3a', INK, 3);
203
+ g.strokeStyle = 'rgba(255,255,255,.3)'; g.lineWidth = 3; g.beginPath(); g.moveTo(1446, 440); g.lineTo(1446, 740); g.stroke();
204
+ dot(g, 1458, 422, 9, C.gold, INK, 2); dot(g, 1478, 460, 7, C.gold, INK, 2);
205
+ const base = poly([[1302, 748], [1482, 748], [1482, 882], [1302, 882]]); F(g, base, C.pink, INK, 3);
206
+ for (let k = 0; k < 4; k++) { const x = 1318 + k * 40; F(g, arch(x, x + 26, 790, 872, 17), C.blue, INK, 2.5); }
207
+ F(g, poly([[1296, 880], [1488, 880], [1488, 898], [1296, 898]]), C.gold, INK, 2.5);
208
+ F(g, poly([[1300, 726], [1484, 726], [1486, 750], [1300, 750]]), C.red, INK, 3);
209
+ dot(g, 1484, 735, 7, C.gold, INK, 2);
210
+ }
211
+
212
+ // 正文栏:格线、红蓝笔花、蓝底金字首字母 H、哥特体拉丁文(句首红字=朱批)
213
+ const LINES = [['ic sedet homo', 1640], ['cum poculo suo', 1640], ['et bibit. Cattus', 1515], ['eum spectat.', 1515], ['Cattus semper', 1515], ['spectat. Nemo', 1515], ['scit quare. Mus', 1515], ['autem scit et', 1515], ['tacet in eternum.', 1515]];
214
+ function textColumn(g) {
215
+ g.strokeStyle = 'rgba(150,110,70,.28)'; g.lineWidth = 1.2;
216
+ for (let k = 0; k < 10; k++) { const y = 300 + k * 62; g.beginPath(); g.moveTo(1495, y); g.lineTo(1895, y); g.stroke(); }
217
+ g.beginPath(); g.moveTo(1505, 240); g.lineTo(1505, 920); g.stroke(); g.beginPath(); g.moveTo(1888, 240); g.lineTo(1888, 920); g.stroke();
218
+ // 笔花
219
+ g.lineWidth = 2; g.strokeStyle = C.red; g.beginPath(); for (let y = 235; y < 500; y += 2) { const x = 1497 + Math.sin(y * 0.09) * 6; y === 235 ? g.moveTo(x, y) : g.lineTo(x, y); } g.stroke();
220
+ g.strokeStyle = C.blue; g.beginPath(); for (let y = 260; y < 330; y += 2) { const x = 1506 + Math.sin(y * 0.11 + 1) * 5; y === 260 ? g.moveTo(x, y) : g.lineTo(x, y); } g.stroke();
221
+ // 首字母 H
222
+ g.save(); g.shadowColor = 'rgba(60,30,0,.3)'; g.shadowOffsetX = 3; g.shadowOffsetY = 3; g.fillStyle = C.gold; g.fillRect(1508, 304, 120, 118); g.restore();
223
+ g.fillStyle = C.blue; g.fillRect(1517, 313, 102, 100); g.strokeStyle = INK; g.lineWidth = 2.5; g.strokeRect(1508, 304, 120, 118); g.strokeRect(1517, 313, 102, 100);
224
+ g.strokeStyle = 'rgba(255,255,255,.85)'; g.lineWidth = 1.6;
225
+ for (const [x, y, s] of [[1600, 330, 1], [1532, 395, -1], [1600, 396, 1]]) { g.beginPath(); for (let a = 0; a < 9; a += 0.25) { const rr = 1 + a * 1.4; const px = x + Math.cos(a * s) * rr, py = y + Math.sin(a * s) * rr; a ? g.lineTo(px, py) : g.moveTo(px, py); } g.stroke(); }
226
+ for (let k = 0; k < 7; k++) dot(g, 1528 + k * 13, 320, 1.6, '#fff', null);
227
+ g.font = '118px "UnifrakturMaguntia-400"'; g.textAlign = 'center'; g.textBaseline = 'alphabetic';
228
+ g.lineWidth = 6; g.strokeStyle = INK; g.strokeText('H', 1568, 404); g.fillStyle = C.gold; g.fillText('H', 1568, 404);
229
+ g.fillStyle = 'rgba(255,245,200,.6)'; g.font = '118px "UnifrakturMaguntia-400"'; g.save(); g.beginPath(); g.rect(1517, 313, 102, 46); g.clip(); g.fillText('H', 1567, 403); g.restore();
230
+ // 正文
231
+ g.textAlign = 'left'; g.font = '44px "UnifrakturMaguntia-400"';
232
+ LINES.forEach(([s, x], k) => {
233
+ const y = 350 + k * 62; let cx = x;
234
+ for (const word of s.split(' ')) {
235
+ const red = /^(Cattus|Nemo|Mus)/.test(word);
236
+ g.fillStyle = red ? C.red : '#2a1c12';
237
+ g.fillText(word, cx, y); cx += g.measureText(word + ' ').width;
238
+ }
239
+ if (k === 3) { // 行尾填充波折线
240
+ g.lineWidth = 2.4; g.strokeStyle = C.red; g.beginPath(); for (let i = 0; i <= 10; i++) g.lineTo(cx + 6 + i * 11, y - 14 + (i % 2) * 12); g.stroke();
241
+ g.strokeStyle = C.blue; g.beginPath(); for (let i = 0; i <= 10; i++) g.lineTo(cx + 12 + i * 11, y - 14 + (i % 2) * 12); g.stroke();
242
+ }
243
+ });
244
+ }
245
+
246
+ // 小骑士(页边滑稽画)
247
+ function knight(g) {
248
+ g.save(); g.translate(760, 1010);
249
+ F(g, poly([[-10, -8], [10, -8], [14, 26], [-14, 26]]), C.red, INK, 2.5);
250
+ g.strokeStyle = INK; g.lineWidth = 6; g.lineCap = 'round'; g.beginPath(); g.moveTo(-6, 24); g.lineTo(-14, 48); g.moveTo(6, 24); g.lineTo(18, 44); g.stroke();
251
+ g.strokeStyle = '#d0d0d8'; g.lineWidth = 3.5; g.stroke();
252
+ const helm = new Path2D(); helm.rect(-12, -34, 24, 26); F(g, helm, '#c8ccd6', INK, 2.5);
253
+ g.fillStyle = INK; g.fillRect(-8, -24, 16, 3);
254
+ F(g, poly([[6, -6], [30, -6], [30, 14], [18, 26], [6, 14]]), C.blue, INK, 2.5);
255
+ g.strokeStyle = '#fff'; g.lineWidth = 3; g.beginPath(); g.moveTo(18, -2); g.lineTo(18, 20); g.moveTo(10, 6); g.lineTo(26, 6); g.stroke();
256
+ g.restore();
257
+ }
258
+
259
+ const STATIC = () => P.cached('goth_static', W, H, (g) => { parchment(g); lattice(g); architecture(g); windowStatic(g); furniture(g); textColumn(g); knight(g); });
260
+ // 金色度掩膜(只让高光落在金上)
261
+ const MASK = () => P.cached('goth_mask', W, H, (g) => {
262
+ const s = STATIC().getContext('2d').getImageData(0, 0, W, H).data, img = g.createImageData(W, H), d = img.data;
263
+ for (let i = 0; i < W * H * 4; i += 4) {
264
+ const r = s[i], gg = s[i + 1], b = s[i + 2];
265
+ const k = clamp((r - b - 70) / 50) * clamp((gg - b - 30) / 40) * clamp((r - 140) / 40);
266
+ d[i] = d[i + 1] = d[i + 2] = 255; d[i + 3] = k * 255;
267
+ }
268
+ g.putImageData(img, 0, 0);
269
+ });
270
+
271
+ // ---------- 藤蔓花饰(每帧画,枝条与叶子各自轻摇) ----------
272
+ const VINES = (() => {
273
+ const L = [], B = [];
274
+ for (let y = 30; y <= 990; y += 8) L.push([172 + 48 * Math.sin(y * 0.0125) + 18 * Math.sin(y * 0.033 + 1), y]);
275
+ for (let x = 230; x <= 1900; x += 8) B.push([x, 1012 + 26 * Math.sin(x * 0.011) + 10 * Math.sin(x * 0.035 + 2) - (x > 1500 ? (x - 1500) * 0.06 : 0)]);
276
+ const joinL = [[L[L.length - 1][0], L[L.length - 1][1]], [200, 1005], [230, B[0][1]]];
277
+ return [L.concat(joinL.slice(1)), B];
278
+ })();
279
+ const LEAFCOL = [C.blue, C.red, C.gold, C.blue, C.red];
280
+ function ivy(c, x, y, a, s, col) { // 三瓣常春藤叶/喇叭花:哥特页边最常见的叶饰
281
+ c.save(); c.translate(x, y); c.rotate(a); c.scale(s, s);
282
+ const p = new Path2D(); p.moveTo(0, 0); p.quadraticCurveTo(-10, -6, -14, -18); p.quadraticCurveTo(-6, -14, -2, -22); p.quadraticCurveTo(0, -30, 2, -22);
283
+ p.quadraticCurveTo(6, -14, 14, -18); p.quadraticCurveTo(10, -6, 0, 0); p.closePath();
284
+ c.fillStyle = col; c.fill(p); c.strokeStyle = INK; c.lineWidth = 1.6 / s; c.stroke(p);
285
+ c.restore();
286
+ }
287
+ function vines(c, t) {
288
+ c.save(); c.lineCap = 'round'; c.lineJoin = 'round';
289
+ VINES.forEach((pts, vi) => {
290
+ const sway = (i) => [Math.sin(t * 2.6 + i * 0.09 + vi) * 5, Math.cos(t * 2.1 + i * 0.07) * 4];
291
+ const pp = pts.map((p, i) => { const s = sway(i); return [p[0] + s[0], p[1] + s[1]]; });
292
+ c.strokeStyle = '#3a2416'; c.lineWidth = 2.6; c.beginPath(); pp.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.stroke();
293
+ const r = R_(31 + vi * 7);
294
+ for (let i = 3, k = 0; i < pp.length - 3; i += 3 + ((r() * 3) | 0), k++) {
295
+ const p = pp[i], q = pp[i + 1], ta = Math.atan2(q[1] - p[1], q[0] - p[0]), side = k % 2 ? 1 : -1;
296
+ const na = ta + side * (Math.PI / 2 - 0.5);
297
+ const L = 18 + r() * 16, sw = Math.sin(t * 3.6 + k * 1.7 + vi) * 0.42;
298
+ const ex = p[0] + Math.cos(na + sw * 0.5) * L, ey = p[1] + Math.sin(na + sw * 0.5) * L;
299
+ c.strokeStyle = '#3a2416'; c.lineWidth = 1.6; c.beginPath(); c.moveTo(p[0], p[1]); c.quadraticCurveTo(p[0] + Math.cos(na) * L * 0.6 + side * 4, p[1] + Math.sin(na) * L * 0.6, ex, ey); c.stroke();
300
+ if (r() < 0.62) ivy(c, ex, ey, na + Math.PI / 2 + sw, 1.05 + r() * 0.4, LEAFCOL[(k + vi) % 5]);
301
+ else { dot(c, ex, ey, 5.5, C.gold, INK, 1.4); c.strokeStyle = '#3a2416'; c.lineWidth = 1; for (let h = -1; h <= 1; h++) { const a2 = na + h * 0.6 + sw; c.beginPath(); c.moveTo(ex + Math.cos(a2) * 6, ey + Math.sin(a2) * 6); c.lineTo(ex + Math.cos(a2) * 13, ey + Math.sin(a2) * 13); c.stroke(); } }
302
+ }
303
+ });
304
+ c.restore();
305
+ }
306
+
307
+ // ---------- 动态金:斜向高光 ----------
308
+ const K_SLANT = 0.45;
309
+ const sweepX = (lt) => lerp(150, 1650, clamp((lt - 0.08) / 1.15)); // 匀速:0.3s 就扫到窗边,0.9s 扫过少女
310
+ function goldSweep(c, lt) {
311
+ const xc = sweepX(lt), bx = Math.round(xc - 460), bw = 920, s = P.scratch('gothSweep'), g = s.getContext('2d');
312
+ g.clearRect(0, 0, W, H); g.globalCompositeOperation = 'source-over';
313
+ const gr = g.createLinearGradient(xc - 170, 540 - 170 * K_SLANT, xc + 170, 540 + 170 * K_SLANT);
314
+ gr.addColorStop(0, 'rgba(255,240,190,0)'); gr.addColorStop(0.25, 'rgba(255,236,170,.32)'); gr.addColorStop(0.47, 'rgba(255,252,230,.85)');
315
+ gr.addColorStop(0.53, 'rgba(255,252,230,.85)'); gr.addColorStop(0.75, 'rgba(255,236,170,.32)'); gr.addColorStop(1, 'rgba(255,240,190,0)');
316
+ g.fillStyle = gr; g.fillRect(bx, 0, bw, H);
317
+ g.globalCompositeOperation = 'destination-in'; g.drawImage(MASK(), bx, 0, bw, H, bx, 0, bw, H); g.globalCompositeOperation = 'source-over';
318
+ c.save(); c.globalCompositeOperation = 'lighter'; c.globalAlpha = 0.85; c.drawImage(s, bx, 0, bw, H, bx, 0, bw, H); c.restore();
319
+ }
320
+ const glint = (c, x, y, s, a = 1) => { // 四角星闪光
321
+ if (s <= 0.02) return; c.save(); c.globalCompositeOperation = 'lighter'; c.globalAlpha = a; c.fillStyle = '#fff6d0';
322
+ c.beginPath(); c.moveTo(x, y - 18 * s); c.quadraticCurveTo(x, y, x + 18 * s, y); c.quadraticCurveTo(x, y, x, y + 18 * s); c.quadraticCurveTo(x, y, x - 18 * s, y); c.quadraticCurveTo(x, y, x, y - 18 * s); c.fill();
323
+ c.globalAlpha = a * 0.5; c.beginPath(); c.arc(x, y, 6 * s, 0, 7); c.fill(); c.restore();
324
+ };
325
+ const SPARKS = [[452, 640], [760, 330], [930, 470], [1090, 300], [1250, 520], [700, 780], [1010, 820], [1380, 330], [560, 115], [1300, 150], [1568, 360]];
326
+ const nearSweep = (x, y, lt) => { const u = x + K_SLANT * (y - 540); return clamp(1 - Math.abs(u - sweepX(lt)) / 70); };
327
+
328
+ // 彩窗闪烁:几块菱格轮流亮起
329
+ function glassTwinkle(c, t) {
330
+ c.save(); c.globalCompositeOperation = 'lighter';
331
+ LANCETS.forEach((L, li) => { c.save(); c.clip(L);
332
+ for (let k = 0; k < 40; k++) { const h = U.hash(k, li + 3), x = (li ? 600 : 396) + U.hash(k, 9 + li) * 170, y = 300 + h * 230;
333
+ const a = Math.max(0, Math.sin(t * 5 + h * 40)); if (a < 0.6) continue;
334
+ const gr = c.createRadialGradient(x, y, 0, x, y, 22); gr.addColorStop(0, `rgba(255,255,255,${(a - 0.6) * 1.1})`); gr.addColorStop(1, 'rgba(255,255,255,0)'); c.fillStyle = gr; c.fillRect(x - 22, y - 22, 44, 44); }
335
+ c.restore(); });
336
+ c.restore();
337
+ }
338
+
339
+ // ---------- 少女:哥特圣像画法 ----------
340
+ function girl(c, G, lt, t, ch) {
341
+ const A = G.A;
342
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
343
+ // 光环(头后)
344
+ const hc = [A.headC[0] + 16, A.headC[1] - 22], hr = 114;
345
+ const hg = c.createRadialGradient(hc[0] - 30, hc[1] - 30, 10, hc[0], hc[1], hr);
346
+ hg.addColorStop(0, '#fbe597'); hg.addColorStop(0.6, '#e2b445'); hg.addColorStop(1, '#b98222');
347
+ c.fillStyle = hg; c.beginPath(); c.arc(hc[0], hc[1], hr, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 3; c.stroke();
348
+ c.strokeStyle = C.red; c.lineWidth = 3; c.beginPath(); c.arc(hc[0], hc[1], hr - 9, 0, 7); c.stroke();
349
+ for (let k = 0; k < 36; k++) { const a = k / 36 * Math.PI * 2; dot(c, hc[0] + Math.cos(a) * (hr - 18), hc[1] + Math.sin(a) * (hr - 18), 2.3, '#fff3c0', 'rgba(120,70,10,.6)', 1); }
350
+ const hs = nearSweep(hc[0], hc[1], lt); if (hs > 0) { c.save(); c.globalCompositeOperation = 'lighter'; c.globalAlpha = hs * 0.5; c.fillStyle = '#fff4c8'; c.beginPath(); c.arc(hc[0], hc[1], hr, 0, 7); c.fill(); c.restore(); }
351
+ // 长金发(背后,波浪)
352
+ const nape = A.nape, sw = Math.sin(t * 2.4) * 3;
353
+ const hair = RIG.smooth([[nape[0] + 30, nape[1] - 100], [nape[0] + 58, nape[1] - 40], [nape[0] + 72, nape[1] + 30], [nape[0] + 84 + sw, nape[1] + 110], [nape[0] + 78 + sw, nape[1] + 190], [nape[0] + 92 + sw, nape[1] + 250],
354
+ [nape[0] + 62 + sw, nape[1] + 262], [nape[0] + 50, nape[1] + 200], [nape[0] + 34, nape[1] + 120], [nape[0] + 10, nape[1] + 50], [nape[0] - 20, nape[1] + 10]]);
355
+ F(c, hair, C.hair, INK, 3);
356
+ c.strokeStyle = C.hairDk; c.lineWidth = 2;
357
+ for (let k = 0; k < 4; k++) { c.beginPath(); for (let s = 0; s <= 20; s++) { const q = s / 20, x = nape[0] + 30 + k * 12 + Math.sin(q * 9 + k) * 6 + q * 30, y = nape[1] - 60 + q * 300; s ? c.lineTo(x, y) : c.moveTo(x, y); } c.stroke(); }
358
+ // 远侧袖、手
359
+ F(c, G.farSleeve, C.red); F(c, G.farCuff, C.gold); F(c, G.farHand, C.skin);
360
+ // 裙:红+金细竖条+金下摆
361
+ F(c, G.skirt, C.red, INK, 3.5);
362
+ c.save(); c.clip(G.skirt); c.strokeStyle = 'rgba(240,190,80,.75)'; c.lineWidth = 1.6;
363
+ for (let x = 1080; x < 1470; x += 20) { c.beginPath(); c.moveTo(x, 600); c.quadraticCurveTo(x - 14, 780, x - 22, 940); c.stroke(); }
364
+ c.strokeStyle = C.redDk; c.lineWidth = 3; G.folds.forEach(f => c.stroke(f));
365
+ c.strokeStyle = C.gold; c.lineWidth = 12; c.beginPath(); c.moveTo(1090, 924); c.quadraticCurveTo(1230, 940, 1360, 924); c.stroke();
366
+ c.restore();
367
+ F(c, G.shoe, '#3a2418', INK, 2.5);
368
+ // 躯干
369
+ F(c, G.torso, C.red, INK, 3.5);
370
+ c.save(); c.clip(G.torso); c.strokeStyle = 'rgba(240,190,80,.75)'; c.lineWidth = 1.6; for (let x = 1280; x < 1440; x += 20) { c.beginPath(); c.moveTo(x, 420); c.lineTo(x + 6, 630); c.stroke(); } c.restore();
371
+ // 蓝斗篷:从后肩垂到座位,金边
372
+ const b = A.back, cloak = RIG.smooth([[A.nape[0] - 6, A.nape[1] + 12], [A.nape[0] + 40, A.nape[1] + 22], [b[0] + 30, b[1] + 10], [b[0] + 44, b[1] + 140], [b[0] + 46, b[1] + 230], [b[0] + 10, b[1] + 236], [b[0] + 4, b[1] + 120], [b[0] - 18, b[1] + 10], [A.nape[0] - 10, A.nape[1] + 40]]);
373
+ F(c, cloak, C.blue, INK, 3.5);
374
+ c.save(); c.clip(cloak); c.strokeStyle = C.blueDk; c.lineWidth = 3; for (let k = 0; k < 3; k++) { c.beginPath(); c.moveTo(b[0] + 6 + k * 12, b[1] - 20); c.quadraticCurveTo(b[0] + 18 + k * 12, b[1] + 100, b[0] + 14 + k * 12, b[1] + 230); c.stroke(); } c.restore();
375
+ c.strokeStyle = C.gold; c.lineWidth = 4; c.beginPath(); c.moveTo(A.nape[0] - 8, A.nape[1] + 30); c.quadraticCurveTo(b[0] - 14, b[1] + 20, b[0] + 6, b[1] + 232); c.stroke();
376
+ // 领口金边
377
+ F(c, G.neck, C.skin, INK, 2.5);
378
+ c.strokeStyle = C.gold; c.lineWidth = 5; c.beginPath(); c.moveTo(A.neck[0] - 18, A.neck[1] + 2); c.quadraticCurveTo(A.neck[0] + 6, A.neck[1] + 18, A.neck[0] + 30, A.neck[1] - 2); c.stroke();
379
+ // 头
380
+ F(c, G.hairBack, C.hair, INK, 3);
381
+ F(c, G.face, C.skin, INK, 3);
382
+ c.save(); c.clip(G.face); c.fillStyle = 'rgba(232,110,110,.55)'; c.beginPath(); c.arc(G.cheek[0] - 4, G.cheek[1], 12, 0, 7); c.fill(); c.restore();
383
+ c.fillStyle = C.skin; c.beginPath(); c.ellipse(G.ear[0], G.ear[1], 8, 12, 0.2, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 2; c.stroke();
384
+ F(c, G.bangs, C.hair, INK, 3);
385
+ c.strokeStyle = C.hairDk; c.lineWidth = 2; G.hairLines.forEach(h => c.stroke(h));
386
+ G.locks.forEach(h => { c.strokeStyle = INK; c.lineWidth = 9; c.stroke(h); c.strokeStyle = C.hair; c.lineWidth = 6; c.stroke(h); });
387
+ // 五官:哥特圣像的杏眼、细眉、小红唇
388
+ const closed = ch.blink || ch.cup > 0.86;
389
+ if (closed) { c.strokeStyle = INK; c.lineWidth = 2.6; c.beginPath(); c.arc(G.eye.x, G.eye.y - 2, 7, 0.3, Math.PI - 0.3); c.stroke(); }
390
+ else {
391
+ c.fillStyle = '#fff'; c.beginPath(); c.ellipse(G.eye.x, G.eye.y, 9, 5, -0.1, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 2.2; c.stroke();
392
+ c.fillStyle = '#3a2a1c'; c.beginPath(); c.arc(G.eye.x - 3, G.eye.y, 3.6, 0, 7); c.fill();
393
+ c.strokeStyle = INK; c.lineWidth = 2.6; c.stroke(G.lid);
394
+ }
395
+ c.strokeStyle = '#8a5a20'; c.lineWidth = 2.2; c.stroke(G.brow);
396
+ c.fillStyle = '#c0303a'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
397
+ // 近侧手臂:红袖+金袖口
398
+ F(c, G.upperArm, C.red, INK, 3.5); F(c, G.foreArm, C.red, INK, 3.5); F(c, G.cuff, C.gold, INK, 2.5);
399
+ F(c, G.hand, C.skin, INK, 2.5);
400
+ chalice(c, G.cup, lt, t);
401
+ c.fillStyle = C.skin; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 2; c.stroke();
402
+ c.restore();
403
+ }
404
+ // 金圣杯:杯口在 cup.x,cup.y;杯身半椭圆+节+杯脚;红酒
405
+ function chalice(c, cup, lt, t) {
406
+ c.save(); c.translate(cup.x, cup.y); c.rotate(cup.tilt || 0);
407
+ const w = 52, gg = c.createLinearGradient(-w / 2, 0, w / 2, 0); gg.addColorStop(0, '#a87020'); gg.addColorStop(0.35, '#fbe08a'); gg.addColorStop(0.6, '#e0ad40'); gg.addColorStop(1, '#8a5a14');
408
+ const bowl = new Path2D(); bowl.moveTo(-w / 2, 0); bowl.bezierCurveTo(-w / 2, 26, -10, 36, 0, 36); bowl.bezierCurveTo(10, 36, w / 2, 26, w / 2, 0); bowl.closePath();
409
+ F(c, bowl, gg, INK, 2.5);
410
+ const stem = poly([[-5, 34], [5, 34], [4, 58], [-4, 58]]); F(c, stem, gg, INK, 2);
411
+ const knot = new Path2D(); knot.ellipse(0, 46, 9, 5, 0, 0, 7); F(c, knot, '#f0c860', INK, 2);
412
+ const foot = new Path2D(); foot.moveTo(-20, 66); foot.quadraticCurveTo(0, 50, 20, 66); foot.closePath(); F(c, foot, gg, INK, 2);
413
+ const rim = new Path2D(); rim.ellipse(0, 0, w / 2, 7, 0, 0, 7); F(c, rim, '#7a1018', INK, 2.2);
414
+ c.fillStyle = 'rgba(255,255,255,.45)'; c.beginPath(); c.ellipse(-8, -1.5, 9, 2, 0, 0, 7); c.fill();
415
+ c.restore();
416
+ // 闪光:杯沿一颗四角星
417
+ const tw = Math.max(0, Math.sin(t * 7 + 1)); glint(c, cup.x + 18, cup.y - 4, 0.5 + tw * 0.5, 0.5 + tw * 0.5);
418
+ // 热气(细墨线弯曲上升)
419
+ c.save(); c.globalAlpha = 0.7; P.steam(c, t, cup.x, cup.y - 12, { h: 64, n: 3, color: 'rgba(120,110,100,.9)', width: 2.2, spread: 10, wobble: 7 }); c.restore();
420
+ }
421
+
422
+ // ---------- 猫:手抄本里的稚拙人脸猫,拎着老鼠 ----------
423
+ function cat(c, K, t, ch) {
424
+ const O = '#e3913e', ODk = '#b45a18', Wt = C.white;
425
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
426
+ // 尾巴(环纹)
427
+ c.strokeStyle = INK; c.lineWidth = K.tailW + 6; c.stroke(K.tail); c.strokeStyle = O; c.lineWidth = K.tailW; c.stroke(K.tail);
428
+ const tp = K.tailPts; c.strokeStyle = ODk; c.lineWidth = 6;
429
+ for (let i = 3; i < tp.length - 1; i += 3) { const a = Math.atan2(tp[i + 1][1] - tp[i][1], tp[i + 1][0] - tp[i][0]) + Math.PI / 2; c.beginPath(); c.moveTo(tp[i][0] - Math.cos(a) * 11, tp[i][1] - Math.sin(a) * 11); c.lineTo(tp[i][0] + Math.cos(a) * 11, tp[i][1] + Math.sin(a) * 11); c.stroke(); }
430
+ // 身体
431
+ const body = RIG.smooth(K.bodyPts); F(c, body, O, INK, 3.5);
432
+ c.save(); c.clip(body);
433
+ c.strokeStyle = ODk; c.lineWidth = 7;
434
+ for (let k = 0; k < 7; k++) { const y = 700 + k * 28; c.beginPath(); c.moveTo(418, y + 8); c.quadraticCurveTo(470, y - 10, 520, y + 14); c.stroke(); }
435
+ c.fillStyle = Wt; c.fill(K.white);
436
+ c.restore();
437
+ // 后腿团(坐姿大腿)
438
+ c.strokeStyle = INK; c.lineWidth = 2.5; c.beginPath(); c.arc(505, 840, 52, Math.PI * 1.05, Math.PI * 1.9); c.stroke();
439
+ // 前腿:一条着地,一条抬起拎老鼠
440
+ c.fillStyle = Wt; c.fill(K.legs[0]); c.strokeStyle = INK; c.lineWidth = 3; c.stroke(K.legs[0]);
441
+ for (let k = -1; k <= 1; k++) { c.beginPath(); c.moveTo(584 + k * 7, 880); c.lineTo(584 + k * 7, 892); c.lineWidth = 1.6; c.stroke(); }
442
+ const sh = [632, 744], pw = [664, 778];
443
+ const arm = RIG.limb(sh, pw, 32, 26); F(c, arm, Wt, INK, 3);
444
+ // 老鼠:尾巴挂在爪子上,钟摆摆动
445
+ const sw = Math.sin(t * 5.5) * 0.32, ml = 56, mx = pw[0] + Math.sin(sw) * ml, my = pw[1] + Math.cos(sw) * ml;
446
+ c.strokeStyle = '#6a4a40'; c.lineWidth = 2; c.beginPath(); c.moveTo(pw[0], pw[1] + 6); c.quadraticCurveTo(pw[0] + Math.sin(sw) * 30 + 6, pw[1] + 30, mx, my - 12); c.stroke();
447
+ c.save(); c.translate(mx, my); c.rotate(-sw);
448
+ const mb = new Path2D(); mb.ellipse(0, 6, 10, 17, 0, 0, 7); F(c, mb, '#4c3e38', INK, 2);
449
+ dot(c, -8, 20, 5, '#4c3e38', INK, 1.5); dot(c, 8, 20, 5, '#4c3e38', INK, 1.5); dot(c, 0, 24, 2, '#e8a0a0', null);
450
+ c.strokeStyle = '#4c3e38'; c.lineWidth = 3; c.beginPath(); c.moveTo(-6, 4); c.lineTo(-12, 12 + Math.sin(t * 9) * 3); c.moveTo(6, 4); c.lineTo(12, 12 - Math.sin(t * 9) * 3); c.stroke();
451
+ c.restore();
452
+ c.fillStyle = Wt; c.beginPath(); c.ellipse(pw[0], pw[1] + 2, 15, 12, 0.3, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 2.5; c.stroke();
453
+ // 头:橘色,稚拙的人脸
454
+ F(c, K.head, O, INK, 3.5);
455
+ c.fillStyle = '#f2a8a0'; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); });
456
+ c.save(); c.clip(K.head);
457
+ c.strokeStyle = ODk; c.lineWidth = 6; K.stripes.slice(0, 3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); });
458
+ const hc = K.headC, fx = hc[0] + 10, fy = hc[1] + 6;
459
+ c.fillStyle = '#f7e6cc'; c.beginPath(); c.ellipse(fx, fy + 6, 42, 34, 0, 0, 7); c.fill();
460
+ c.restore();
461
+ // 脸颊红晕
462
+ c.fillStyle = 'rgba(232,110,110,.45)'; dot(c, fx - 26, fy + 14, 9, 'rgba(232,110,110,.45)', null); dot(c, fx + 28, fy + 12, 9, 'rgba(232,110,110,.45)', null);
463
+ // 眼:杏眼斜睨(瞳孔挤到右边看少女),厚上眼皮
464
+ const eyes = [[fx - 18, fy - 6], [fx + 18, fy - 8]];
465
+ eyes.forEach(([x, y], k) => {
466
+ if (ch.blink) { c.strokeStyle = INK; c.lineWidth = 2.5; c.beginPath(); c.moveTo(x - 10, y); c.lineTo(x + 10, y); c.stroke(); return; }
467
+ c.fillStyle = '#fff'; c.beginPath(); c.ellipse(x, y, 11, 7, 0, 0, 7); c.fill(); c.strokeStyle = INK; c.lineWidth = 2; c.stroke();
468
+ dot(c, x + 5, y + 1, 4.2, '#2a1c12', null);
469
+ c.lineWidth = 3; c.beginPath(); c.moveTo(x - 12, y - 2); c.quadraticCurveTo(x, y - 9, x + 12, y - 3); c.stroke();
470
+ c.lineWidth = 2; c.beginPath(); c.moveTo(x - 10, y - 15 - k * 2); c.quadraticCurveTo(x, y - 20 - k * 3, x + 11, y - 15 + k * 2); c.stroke();
471
+ });
472
+ // 鼻、撇嘴、胡须
473
+ c.strokeStyle = INK; c.lineWidth = 2; c.beginPath(); c.moveTo(fx + 1, fy + 2); c.quadraticCurveTo(fx + 5, fy + 12, fx - 1, fy + 14); c.stroke();
474
+ c.lineWidth = 2.4; c.beginPath(); c.moveTo(fx - 12, fy + 28); c.quadraticCurveTo(fx, fy + 20, fx + 12, fy + 27); c.stroke();
475
+ c.lineWidth = 1.4; for (const [a, b] of [[[fx + 34, fy + 14], [fx + 80, fy + 6]], [[fx + 34, fy + 20], [fx + 78, fy + 26]], [[fx - 34, fy + 16], [fx - 74, fy + 10]], [[fx - 34, fy + 22], [fx - 72, fy + 28]]]) { c.beginPath(); c.moveTo(...a); c.lineTo(...b); c.stroke(); }
476
+ c.restore();
477
+ }
478
+
479
+ // ---------- 蜗牛、老鼠 ----------
480
+ function snail(c, lt, t) {
481
+ const x = 912 - 62 * lt, y = 1040, st = Math.sin(t * 6) * 4;
482
+ c.save(); c.lineJoin = 'round';
483
+ const body = new Path2D(); body.moveTo(x + 52, y + 26); body.lineTo(x - 54 - st, y + 26); body.quadraticCurveTo(x - 70 - st, y + 22, x - 62 - st, y + 6); body.quadraticCurveTo(x - 40, y + 4, x - 10, y + 12); body.lineTo(x + 52, y + 18); body.closePath();
484
+ F(c, body, '#aab6cc', INK, 2.5);
485
+ c.strokeStyle = INK; c.lineWidth = 2.4; const ex = x - 58 - st;
486
+ for (const [dx, dy] of [[-12, -30], [2, -34]]) { const wob = Math.sin(t * 8 + dx) * 3; c.beginPath(); c.moveTo(ex + 4, y + 8); c.quadraticCurveTo(ex + dx * 0.5, y - 10, ex + dx + wob, y + dy); c.stroke(); dot(c, ex + dx + wob, y + dy, 3.5, INK, null); }
487
+ const shell = new Path2D(); shell.arc(x + 4, y - 10, 34, 0, 7); F(c, shell, '#e3a447', INK, 3);
488
+ c.strokeStyle = '#9a5a1a'; c.lineWidth = 3; c.beginPath(); for (let a = 0; a < 15; a += 0.2) { const r = 2 + a * 2.05; const px = x + 4 + Math.cos(a) * r, py = y - 10 + Math.sin(a) * r; a ? c.lineTo(px, py) : c.moveTo(px, py); } c.stroke();
489
+ c.restore();
490
+ }
491
+ function runMouse(c, lt, t) {
492
+ const q = clamp((lt - 0.12) / 1.05), x = lerp(1890, 1530, q), y = 898, leg = Math.sin(t * 40);
493
+ c.save(); c.lineCap = 'round';
494
+ c.fillStyle = 'rgba(80,50,20,.18)'; c.beginPath(); c.ellipse(x, y + 12, 30, 5, 0, 0, 7); c.fill();
495
+ c.strokeStyle = '#3a2a24'; c.lineWidth = 2; c.beginPath(); c.moveTo(x + 22, y); c.bezierCurveTo(x + 50, y - 12 + Math.sin(t * 14) * 6, x + 60, y + 10, x + 82, y - 4 + Math.sin(t * 14 + 1) * 6); c.stroke();
496
+ c.lineWidth = 3; c.beginPath(); c.moveTo(x - 10, y + 4); c.lineTo(x - 16 - leg * 7, y + 12); c.moveTo(x + 12, y + 4); c.lineTo(x + 16 + leg * 7, y + 12); c.stroke();
497
+ const b = new Path2D(); b.ellipse(x, y - 2, 25, 12, 0, 0, 7); F(c, b, '#3e302c', INK, 1.5);
498
+ const hd = new Path2D(); hd.moveTo(x - 14, y - 10); hd.quadraticCurveTo(x - 30, y - 8, x - 38, y + 2); hd.quadraticCurveTo(x - 24, y + 6, x - 12, y + 6); hd.closePath(); F(c, hd, '#3e302c', INK, 1.5);
499
+ dot(c, x - 16, y - 13, 6, '#5a4640', INK, 1.2); dot(c, x - 30, y - 2, 1.6, '#fff', null);
500
+ c.restore();
501
+ }
502
+
503
+ return {
504
+ init() { STATIC(); MASK(); },
505
+ draw(c, lt, t) {
506
+ const ch = P.choreo(lt, t);
507
+ c.drawImage(STATIC(), 0, 0);
508
+ glassTwinkle(c, t);
509
+ goldSweep(c, lt);
510
+ SPARKS.forEach(([x, y], k) => { const n = nearSweep(x, y, lt); if (n > 0) glint(c, x, y, 0.4 + n * 0.8, n); });
511
+ vines(c, t);
512
+ snail(c, lt, t);
513
+ runMouse(c, lt, t);
514
+ const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
515
+ cat(c, K, t, ch);
516
+ const G = RIG.girl({ cup: ch.cup, sip: ch.sip, hair: 'long', breathe: ch.breathe });
517
+ girl(c, G, lt, t, ch);
518
+ // 做旧:极淡的颗粒
519
+ c.drawImage(P.grain('goth', 0.05, [90, 60, 20], 0.16), 0, 0);
520
+ },
521
+ };
522
+ })();