@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,219 @@
1
+ // 1930 橡皮管卡通(Fleischer / 早期迪士尼 / Cuphead 式)—— 纯代码。
2
+ // 管线:①纸色单色底(墙纸竖纹、地板、窗外太阳脸)→ ②每个物体以自己的「着地点」为轴随 128BPM 节拍弹跳+挤压拉伸
3
+ // → ③角色:骨架只取锚点,画法整个换成卡通(大头、派切眼、橡皮管手臂、白手套)→ ④胶片层:抖片、闪烁、划痕、灰尘、暗角
4
+ // 时间:角色与母题都按 12fps「一拍二」步进(tq),只有胶片层按 24fps 变——这就是 30 年代赛璐珞的顿挫。
5
+ SCENES['33_rubberhose'] = (() => {
6
+ const W = 1920, H = 1080, { clamp, lerp, rng } = U, P = PAINT;
7
+ const INK = '#141210', PAPER = '#efe7d2', LT = '#d9d0b8', MID = '#a39a84', DK = '#5e574b', BEAT = 60 / 128;
8
+ const bnc = (tq, ph = 0) => Math.abs(Math.sin(Math.PI * (tq / BEAT + ph))); // 0 = 落地,1 = 最高
9
+ // 以着地点 (ax,ay) 为轴:上弹 amp 像素;落地时压扁、腾空时拉长
10
+ function boing(c, ax, ay, b, amp, fn, k = 1) {
11
+ c.save(); c.translate(ax, ay - b * amp); const sq = (1 - b) * 0.09 * k, st = b * 0.05 * k;
12
+ c.scale(1 + sq - st * 0.6, 1 - sq + st); c.translate(-ax, -ay); fn(); c.restore();
13
+ }
14
+ const ink = (c, w = 6) => { c.strokeStyle = INK; c.lineWidth = w; c.lineJoin = 'round'; c.lineCap = 'round'; };
15
+ const F = (c, p, col, w = 6) => { c.fillStyle = col; c.fill(p); ink(c, w); c.stroke(p); };
16
+ const circ = (x, y, r) => { const p = new Path2D(); p.arc(x, y, r, 0, Math.PI * 2); return p; };
17
+ const ell = (x, y, rx, ry, a = 0) => { const p = new Path2D(); p.ellipse(x, y, rx, ry, a, 0, Math.PI * 2); return p; };
18
+ // 派切眼:白眼眶+黑瞳,黑瞳上切掉一块楔形(30 年代的标志)
19
+ function pieEye(c, x, y, rx, ry, look, blink, lash) {
20
+ if (blink) { ink(c, 5); c.beginPath(); c.ellipse(x, y + ry * 0.2, rx, ry * 0.5, 0, 0.15, Math.PI - 0.15); c.stroke(); return; }
21
+ F(c, ell(x, y, rx, ry), '#fbf7ec', 4.5);
22
+ const px = x + look * rx * 0.35, py = y + ry * 0.25;
23
+ c.save(); c.clip(ell(x, y, rx, ry)); c.fillStyle = INK; c.beginPath(); c.ellipse(px, py, rx * 0.62, ry * 0.7, 0, 0, Math.PI * 2); c.fill();
24
+ c.fillStyle = '#fbf7ec'; c.beginPath(); c.moveTo(px + rx * 0.05, py - ry * 0.05); c.arc(px, py, ry, -1.45, -0.85); c.closePath(); c.fill(); c.restore();
25
+ if (lash) { ink(c, 4); for (let k = 0; k < 3; k++) { const a = -2.5 + k * 0.45; c.beginPath(); c.moveTo(x + Math.cos(a) * rx, y + Math.sin(a) * ry); c.lineTo(x + Math.cos(a) * (rx + 14), y + Math.sin(a) * (ry + 14)); c.stroke(); } }
26
+ }
27
+ // 橡皮管:没有肘的一根面条(二次贝塞尔),粗细恒定
28
+ function hose(c, A, B, bend, w, col) {
29
+ const mx = (A[0] + B[0]) / 2, my = (A[1] + B[1]) / 2, dx = B[0] - A[0], dy = B[1] - A[1], L = Math.hypot(dx, dy) || 1;
30
+ const C = [mx - dy / L * bend, my + dx / L * bend];
31
+ c.lineCap = 'round'; c.strokeStyle = INK; c.lineWidth = w + 10; c.beginPath(); c.moveTo(...A); c.quadraticCurveTo(...C, ...B); c.stroke();
32
+ c.strokeStyle = col; c.lineWidth = w; c.stroke();
33
+ }
34
+ // 白手套:四指卡通手,袖口外翻,手背三道线
35
+ function glove(c, x, y, ang, s = 1) {
36
+ c.save(); c.translate(x, y); c.rotate(ang); c.scale(s, s);
37
+ F(c, RIG.smooth([[-30, -14], [-8, -24], [16, -22], [30, -12], [34, 4], [24, 20], [4, 24], [-20, 18], [-32, 4]]), '#fbf7ec', 5);
38
+ [[22, -18], [30, -6], [30, 8]].forEach(([fx, fy]) => F(c, ell(fx + 6, fy, 11, 8, 0.2), '#fbf7ec', 5));
39
+ F(c, RIG.smooth([[-30, -20], [-46, -26], [-50, 0], [-46, 26], [-30, 20], [-26, 0]]), '#fbf7ec', 5);
40
+ ink(c, 3); [-6, 2, 10].forEach(xx => { c.beginPath(); c.moveTo(xx - 10, xx * 0.2 - 2); c.lineTo(xx + 2, xx * 0.2 - 4); c.stroke(); });
41
+ c.restore();
42
+ }
43
+
44
+ // ---------- 静态底(缓存) ----------
45
+ const bg = () => P.cached('rh_bg', W, H, (g) => {
46
+ g.fillStyle = PAPER; g.fillRect(0, 0, W, H);
47
+ // 墙纸:竖条+小菱点
48
+ for (let x = 0; x < W; x += 64) { g.fillStyle = '#e5dcc5'; g.fillRect(x, 0, 30, 700); }
49
+ g.fillStyle = '#cfc5ab'; for (let y = 30; y < 690; y += 70) for (let x = 47; x < W; x += 64) { g.beginPath(); g.moveTo(x, y - 7); g.lineTo(x + 6, y); g.lineTo(x, y + 7); g.lineTo(x - 6, y); g.fill(); }
50
+ // 地板
51
+ g.fillStyle = '#cdc3a8'; g.fillRect(0, 712, W, H - 712);
52
+ ink(g, 3); g.strokeStyle = '#8a816c'; for (let y = 760; y < H; y += 60) { g.beginPath(); g.moveTo(0, y); g.lineTo(W, y); g.stroke(); }
53
+ for (let k = 0; k < 40; k++) { const y = 712 + Math.floor(k / 8) * 60, x = (k % 8) * 260 + (Math.floor(k / 8) % 2) * 130; g.beginPath(); g.moveTo(x, y); g.lineTo(x, y + 60); g.stroke(); }
54
+ g.fillStyle = DK; g.fillRect(0, 690, W, 24); ink(g, 5); g.beginPath(); g.moveTo(0, 690); g.lineTo(W, 690); g.moveTo(0, 714); g.lineTo(W, 714); g.stroke();
55
+ // 水彩式明暗:角落压暗
56
+ const vg = g.createRadialGradient(900, 480, 300, 900, 520, 1200); vg.addColorStop(0, 'rgba(0,0,0,0)'); vg.addColorStop(1, 'rgba(60,50,35,.35)'); g.fillStyle = vg; g.fillRect(0, 0, W, H);
57
+ });
58
+
59
+ function windowAndSky(c, tq) {
60
+ // 窗洞里的天
61
+ c.save(); c.beginPath(); c.rect(360, 130, 440, 420); c.clip();
62
+ c.fillStyle = '#f6f0e0'; c.fillRect(360, 130, 440, 420);
63
+ // 远山与树(树随拍点弹)
64
+ c.fillStyle = LT; c.beginPath(); c.moveTo(360, 480); c.quadraticCurveTo(470, 410, 580, 470); c.quadraticCurveTo(690, 420, 800, 470); c.lineTo(800, 550); c.lineTo(360, 550); c.fill(); ink(c, 4); c.stroke();
65
+ boing(c, 450, 500, bnc(tq, 0.25), 14, () => { F(c, RIG.smooth([[442, 500], [446, 440], [458, 440], [462, 500]]), DK, 4); F(c, RIG.smooth([[400, 440], [420, 380], [452, 360], [490, 380], [505, 430], [470, 455], [430, 455]]), MID, 5); });
66
+ // 太阳脸:光芒旋转、脸跟拍点点头
67
+ const sx = 690, sy = 235, rot = tq * 1.6;
68
+ c.save(); c.translate(sx, sy); c.rotate(rot); c.fillStyle = '#fbf7ec';
69
+ for (let k = 0; k < 12; k++) { c.rotate(Math.PI / 6); c.beginPath(); c.moveTo(-12, -66); c.lineTo(0, -108 - (k % 2) * 16); c.lineTo(12, -66); c.closePath(); c.fill(); ink(c, 4); c.stroke(); }
70
+ c.restore();
71
+ boing(c, sx, sy + 58, bnc(tq), 8, () => {
72
+ F(c, circ(sx, sy, 60), '#fbf7ec', 5);
73
+ pieEye(c, sx - 20, sy - 10, 11, 16, -0.4, 0, false); pieEye(c, sx + 16, sy - 10, 11, 16, -0.4, 0, false);
74
+ ink(c, 5); c.beginPath(); c.arc(sx - 2, sy + 12, 26, 0.35, Math.PI - 0.35); c.stroke();
75
+ c.fillStyle = MID; c.beginPath(); c.arc(sx - 36, sy + 14, 9, 0, 7); c.arc(sx + 32, sy + 14, 9, 0, 7); c.fill();
76
+ });
77
+ // 云:两朵,随拍伸缩并慢慢飘
78
+ [[470 + tq * 30, 200, 0.1], [560 + tq * 22, 300, 0.6]].forEach(([x, y, ph]) => boing(c, x, y + 30, bnc(tq, ph), 10, () => {
79
+ F(c, RIG.smooth([[x - 60, y + 30], [x - 70, y + 6], [x - 40, y - 18], [x - 6, y - 30], [x + 30, y - 22], [x + 62, y - 4], [x + 66, y + 26]]), '#fbf7ec', 5); }));
80
+ c.restore();
81
+ // 窗框(白漆粗框+十字)
82
+ ink(c, 6); c.fillStyle = '#f4eedc';
83
+ [[350, 120, 460, 18], [350, 542, 460, 18], [350, 120, 18, 440], [792, 120, 18, 440], [571, 120, 18, 440], [350, 328, 460, 16]].forEach(r => { c.fillRect(...r); c.strokeRect(...r); });
84
+ c.fillStyle = '#f4eedc'; c.fillRect(320, 556, 520, 26); c.strokeRect(320, 556, 520, 26);
85
+ // 窗帘:圆点布,系起,下摆随拍甩
86
+ [[330, 1], [830, -1]].forEach(([x, sgn], k) => {
87
+ const sw = (bnc(tq, 0.5 + k * 0.5) - 0.5) * 18 * sgn;
88
+ const p = RIG.smooth([[x, 96], [x + sgn * 70, 96], [x + sgn * 54, 260], [x + sgn * 26, 330], [x + sgn * 70 + sw, 520], [x + sgn * 30 + sw, 600], [x - sgn * 4, 600]]);
89
+ c.fillStyle = MID; c.fill(p); c.save(); c.clip(p); c.fillStyle = '#efe7d2'; for (let yy = 100; yy < 620; yy += 34) for (let xx = x - 80; xx < x + 80; xx += 34) { c.beginPath(); c.arc(xx + (yy / 34 % 2) * 17, yy, 6, 0, 7); c.fill(); } c.restore();
90
+ ink(c, 5); c.stroke(p); c.fillStyle = INK; c.fillRect(x + sgn * 10 - 10, 318, 50, 14);
91
+ });
92
+ ink(c, 8); c.beginPath(); c.moveTo(300, 96); c.lineTo(860, 96); c.stroke();
93
+ }
94
+
95
+ function table(c, tq) {
96
+ // 桌:桌腿是弯的橡皮管,落拍压扁
97
+ boing(c, 1005, 905, bnc(tq, 0.5), 16, () => {
98
+ [[850, 1], [1160, -1]].forEach(([x, s]) => { ink(c, 26); c.beginPath(); c.moveTo(x, 640); c.quadraticCurveTo(x + s * 26, 770, x - s * 6, 900); c.stroke(); c.strokeStyle = '#8f8670'; c.lineWidth = 15; c.stroke(); });
99
+ F(c, RIG.smooth([[805, 620], [1000, 608], [1205, 620], [1210, 648], [1000, 660], [800, 648]]), '#bdb399', 6);
100
+ ink(c, 3); c.beginPath(); c.moveTo(830, 640); c.quadraticCurveTo(1000, 650, 1180, 640); c.stroke();
101
+ // 花瓶+跳舞的花(花有脸,叶子当手挥)
102
+ F(c, RIG.smooth([[858, 615], [846, 580], [868, 548], [902, 548], [920, 580], [906, 615]]), DK, 5);
103
+ c.fillStyle = PAPER; [[870, 575], [892, 590], [884, 565]].forEach(([x, y]) => { c.beginPath(); c.arc(x, y, 4, 0, 7); c.fill(); });
104
+ const sway = Math.sin(Math.PI * tq / BEAT) * 0.35, top = [884 + Math.sin(sway) * 120, 548 - Math.cos(sway) * 120];
105
+ ink(c, 12); c.beginPath(); c.moveTo(884, 550); c.quadraticCurveTo(884 - sway * 30, 490, top[0], top[1]); c.stroke(); c.strokeStyle = MID; c.lineWidth = 6; c.stroke();
106
+ const leaf = (s) => { c.save(); c.translate(884 - sway * 20, 500); c.rotate(s * (0.9 + sway * 1.4 * s)); F(c, ell(28 * s, 0, 30, 11), MID, 4.5); c.restore(); }; leaf(1); leaf(-1);
107
+ c.save(); c.translate(top[0], top[1]); c.rotate(sway);
108
+ for (let k = 0; k < 8; k++) { c.save(); c.rotate(k / 8 * Math.PI * 2 + tq * 2); F(c, ell(0, -36, 15, 24), '#fbf7ec', 4.5); c.restore(); }
109
+ F(c, circ(0, 0, 28), LT, 5); pieEye(c, -9, -5, 6, 9, 0.3, 0, false); pieEye(c, 9, -5, 6, 9, 0.3, 0, false);
110
+ ink(c, 4); c.beginPath(); c.arc(0, 6, 11, 0.3, Math.PI - 0.3); c.stroke(); c.restore();
111
+ });
112
+ }
113
+
114
+ function chair(c) {
115
+ ink(c, 6); c.fillStyle = '#8f8670';
116
+ [[1436, 1], [1496, -1]].forEach(([x, s]) => { ink(c, 22); c.beginPath(); c.moveTo(x, 460); c.quadraticCurveTo(x - s * 14, 700, x + s * 4, 905); c.stroke(); c.strokeStyle = '#8f8670'; c.lineWidth = 11; c.stroke(); });
117
+ const back = RIG.smooth([[1416, 470], [1440, 420], [1470, 404], [1500, 420], [1522, 470], [1500, 480], [1470, 466], [1440, 480]]);
118
+ c.fill(back); c.stroke(back); [500, 560].forEach(y => { c.fillStyle = '#8f8670'; c.fillRect(1436, y, 60, 14); c.strokeRect(1436, y, 60, 14); });
119
+ }
120
+
121
+ function girl(c, tq, lt, t) {
122
+ const ch = P.choreo(lt, t);
123
+ const cup = 0.5 - 0.5 * Math.cos(clamp(Math.floor(lt * 12) / 12 / 0.9) * Math.PI); // 端杯也按一拍二步进
124
+ const G = RIG.girl({ cup, sip: 0, dx: 0, dy: 0 });
125
+ const b = bnc(tq, 0.25);
126
+ boing(c, 1240, 930, b, 18, () => {
127
+ // 裙:深色波点长裙
128
+ F(c, G.skirt, DK, 6);
129
+ c.save(); c.clip(G.skirt); c.fillStyle = PAPER; for (let y = 640; y < 940; y += 40) for (let x = 1090; x < 1470; x += 40) { c.beginPath(); c.arc(x + (y / 40 % 2) * 20, y, 7, 0, 7); c.fill(); } c.restore();
130
+ ink(c, 6); c.stroke(G.skirt);
131
+ F(c, ell(1120, 924, 34, 15), INK, 4); F(c, ell(1170, 928, 30, 13), INK, 4); // 玛丽珍鞋
132
+ // 远手:搭桌上的橡皮管+手套
133
+ hose(c, [1340, 480], [1196, 612], -30, 14, '#f4eedc'); glove(c, 1180, 614, Math.PI, 0.9);
134
+ // 身体
135
+ F(c, G.torso, DK, 6);
136
+ c.save(); c.clip(G.torso); c.fillStyle = PAPER; for (let y = 440; y < 630; y += 40) for (let x = 1280; x < 1430; x += 40) { c.beginPath(); c.arc(x + (y / 40 % 2) * 20, y, 7, 0, 7); c.fill(); } c.restore(); ink(c, 6); c.stroke(G.torso);
137
+ // 头:大圆头,略比骨架大,朝左
138
+ const hc = [G.A.headC[0] - 14, G.A.headC[1] - 14], R0 = 82;
139
+ F(c, ell(G.A.neck[0], G.A.neck[1] - 6, 18, 26), '#f4eedc', 5);
140
+ F(c, RIG.smooth([[G.A.neck[0] - 44, G.A.neck[1] + 4], [G.A.neck[0] - 16, G.A.neck[1] + 28], [G.A.neck[0] + 6, G.A.neck[1] + 10], [G.A.neck[0] + 30, G.A.neck[1] + 28], [G.A.neck[0] + 54, G.A.neck[1] + 2], [G.A.neck[0], G.A.neck[1] - 6]]), '#fbf7ec', 5); // 小圆领
141
+ // 后脑卷发
142
+ F(c, RIG.smooth([[hc[0] + 10, hc[1] - 78], [hc[0] + 70, hc[1] - 62], [hc[0] + 100, hc[1] - 10], [hc[0] + 96, hc[1] + 50], [hc[0] + 66, hc[1] + 84], [hc[0] + 30, hc[1] + 60], [hc[0] + 10, hc[1]]]), '#ddd2ae', 6);
143
+ F(c, ell(hc[0], hc[1] + 4, R0 * 0.86, R0 * 0.9), '#f8f3e6', 6); // 脸
144
+ F(c, RIG.smooth([[hc[0] - 70, hc[1] - 30], [hc[0] - 50, hc[1] - 74], [hc[0] + 10, hc[1] - 88], [hc[0] + 70, hc[1] - 64], [hc[0] + 84, hc[1] - 6], [hc[0] + 40, hc[1] - 30], [hc[0] + 6, hc[1] - 46], [hc[0] - 36, hc[1] - 38]]), '#ddd2ae', 6); // 刘海
145
+ ink(c, 4); for (let k = 0; k < 3; k++) { c.beginPath(); c.arc(hc[0] + 70 + k * 6, hc[1] + 10 + k * 22, 10, 0.5, 5.5); c.stroke(); } // 卷
146
+ // 金发要读成头发不是软帽:发丝线+卡通高光(白月牙)
147
+ ink(c, 3.5); [[-40, -60, 20, -40], [-10, -74, 50, -50], [30, -78, 80, -40], [60, -50, 92, 10], [70, -10, 90, 50]].forEach(([a, b, c2, d]) => { c.beginPath(); c.moveTo(hc[0] + a, hc[1] + b); c.quadraticCurveTo(hc[0] + (a + c2) / 2 + 10, hc[1] + (b + d) / 2 - 12, hc[0] + c2, hc[1] + d); c.stroke(); });
148
+ c.fillStyle = '#fbf7ec'; c.beginPath(); c.ellipse(hc[0] + 40, hc[1] - 56, 22, 7, 0.5, 0, 7); c.fill();
149
+ // 头顶大蝴蝶结:比身体多晚半拍弹(跟随)
150
+ const bb = bnc(tq, 0.05) * 10;
151
+ c.save(); c.translate(hc[0] + 40, hc[1] - 84 - bb); c.rotate(0.3);
152
+ F(c, RIG.smooth([[0, 0], [-46, -30], [-52, 18]]), INK, 4); F(c, RIG.smooth([[0, 0], [46, -30], [52, 18]]), INK, 4); F(c, circ(0, 0, 12), INK, 4); c.restore();
153
+ const blink = ch.blink && !(cup > 0.85);
154
+ pieEye(c, hc[0] - 44, hc[1] - 4, 15, 23, -0.7, blink || cup > 0.85, true); pieEye(c, hc[0] - 8, hc[1] - 6, 17, 26, -0.7, blink || cup > 0.85, true);
155
+ c.fillStyle = INK; c.beginPath(); c.arc(hc[0] - 72, hc[1] + 20, 7, 0, 7); c.fill(); // 鼻头
156
+ c.fillStyle = MID; c.beginPath(); c.arc(hc[0] - 20, hc[1] + 34, 13, 0, 7); c.fill();
157
+ ink(c, 5); c.beginPath(); c.moveTo(hc[0] - 60, hc[1] + 46); c.quadraticCurveTo(hc[0] - 46, hc[1] + 56, hc[0] - 34, hc[1] + 44); c.stroke();
158
+ // 近手:橡皮管从肩到手,杯子在手前
159
+ const S = [G.A.shoulder[0] - 6, G.A.shoulder[1] + 4], Hd = G.A.hand;
160
+ F(c, ell(S[0], S[1], 34, 28), '#fbf7ec', 5); // 泡泡袖
161
+ hose(c, S, Hd, 40 - cup * 30, 14, '#f4eedc');
162
+ glove(c, Hd[0], Hd[1], -2.6 + cup * 0.8, 1);
163
+ const cp = G.cup; c.save(); c.translate(cp.x, cp.y); c.rotate(cp.tilt);
164
+ F(c, RIG.smooth([[-30, 0], [-26, 34], [0, 44], [26, 34], [30, 0]], true), '#fbf7ec', 5); ink(c, 5); c.beginPath(); c.arc(34, 18, 12, -1.3, 1.5); c.stroke();
165
+ c.fillStyle = MID; c.beginPath(); c.ellipse(0, 2, 28, 6, 0, 0, 7); c.fill(); c.stroke();
166
+ c.restore();
167
+ // 热气:卡通烟圈,一拍冒一个
168
+ for (let k = 0; k < 3; k++) { const q = ((tq / BEAT) + k / 3) % 1; ink(c, 4); c.beginPath(); c.ellipse(cp.x + Math.sin(q * 6) * 8, cp.y - 20 - q * 90, 10 + q * 10, 6 + q * 6, 0, 0, 7); c.globalAlpha = 1 - q; c.stroke(); c.globalAlpha = 1; }
169
+ }, 1.2);
170
+ }
171
+
172
+ function cat(c, tq, t) {
173
+ const K = RIG.cat({ tail: 0, blink: 0, breathe: 0 });
174
+ boing(c, 540, 905, bnc(tq, 0.75), 30, () => {
175
+ // 尾巴:一根波浪橡皮管
176
+ const base = [460, 880]; ink(c, 1); const pts = [];
177
+ for (let i = 0; i <= 20; i++) { const q = i / 20; pts.push([base[0] - q * 150, base[1] - q * 200 + Math.sin(q * 7 - tq * 9) * 26 * q]); }
178
+ c.lineCap = 'round'; c.strokeStyle = INK; c.lineWidth = 32; c.beginPath(); pts.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.stroke();
179
+ c.strokeStyle = MID; c.lineWidth = 20; c.stroke(); c.fillStyle = '#fbf7ec'; c.beginPath(); c.arc(...pts[20], 12, 0, 7); c.fill();
180
+ const body = RIG.smooth(K.bodyPts); // 骨架的身体是折线,卡通要圆:Catmull-Rom 一下
181
+ F(c, body, MID, 6);
182
+ c.save(); c.clip(body); c.fillStyle = '#fbf7ec'; c.fill(RIG.smooth([[566, 700], [612, 690], [642, 742], [636, 830], [596, 870], [560, 800]])); ink(c, 9); c.strokeStyle = DK; K.stripes.slice(3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
183
+ ink(c, 6); c.stroke(body);
184
+ // 白爪(大圆手套脚)
185
+ F(c, ell(585, 892, 30, 18), '#fbf7ec', 5); F(c, ell(640, 892, 30, 18), '#fbf7ec', 5);
186
+ // 头:橘(中灰)头顶虎斑,白口鼻,派切大眼,咧嘴笑
187
+ const hb = bnc(tq, 0.95) * 8;
188
+ c.save(); c.translate(0, -hb);
189
+ F(c, K.head, MID, 6);
190
+ c.save(); c.clip(K.head); ink(c, 9); c.strokeStyle = DK; K.stripes.slice(0, 3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
191
+ F(c, ell(630, 652, 40, 28), '#fbf7ec', 5);
192
+ pieEye(c, 610, 610, 15, 22, 0.6, 0, false); pieEye(c, 648, 607, 15, 22, 0.6, 0, false);
193
+ c.fillStyle = INK; c.beginPath(); c.ellipse(656, 636, 10, 7, 0, 0, 7); c.fill();
194
+ ink(c, 5); c.beginPath(); c.moveTo(612, 650); c.quadraticCurveTo(640, 676, 670, 646); c.stroke();
195
+ ink(c, 3); [[660, 640, 716, 626], [662, 648, 718, 650], [600, 646, 552, 634]].forEach(([a, b, c2, d]) => { c.beginPath(); c.moveTo(a, b); c.lineTo(c2, d); c.stroke(); });
196
+ c.restore();
197
+ }, 1.4);
198
+ }
199
+
200
+ // ---------- 胶片层:P.film(24fps 闪烁、跨格存活的划痕、灰尘毛发、颗粒抖动、圆角片门) ----------
201
+ const film = (c, t) => P.film(c, t, { grain: 'rhg' });
202
+
203
+ return {
204
+ draw(c, lt, t) {
205
+ const tq = U.stepTime(t, 12); // 一拍二:12 张/秒(步进帧率是这个风格的味道本身)
206
+ P.gateWeave(c, t, () => { // 抖片(gate weave,24fps)
207
+ c.drawImage(bg(), 0, 0);
208
+ windowAndSky(c, tq);
209
+ chair(c);
210
+ table(c, tq);
211
+ girl(c, tq, lt, t);
212
+ cat(c, tq, t);
213
+ });
214
+ // 胶片褪色成单色暖灰(防止任何残余彩色)
215
+ P.fade(c, { sat: 1, tint: '#f2e8d2' });
216
+ film(c, t);
217
+ },
218
+ };
219
+ })();
@@ -0,0 +1,195 @@
1
+ // 中国皮影戏 —— 纯代码。
2
+ // 管线:①影窗(油灯在幕后的暖光斑+布纹,灯焰摇曳→光斑位置/亮度抖)→ ②每个「皮件」在离屏层上:填半透明彩色 → 镂空(destination-out 刻纹)
3
+ // → 补皮边 → 整层 multiply 印到幕布上(皮子透光=幕布色×皮色,重叠处自然变深)+偏移软影(皮件没贴紧幕布时的虚影)
4
+ // → ③铆钉、操纵杆(杆子在幕后,糊一点)→ ④布纹+木框
5
+ // 动作:杆子操纵的僵直关节——每块皮件是刚体,只绕铆钉转;手腕是松的,会甩;整个人随主杆轻摆;节奏按 10fps 步进。
6
+ SCENES['34_shadowpuppet'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng } = U, P = PAINT;
8
+ const RED = '#c0352a', GRN = '#2f7d4c', AMB = '#d99a2e', YEL = '#e2bd4a', EDGE = '#3a160a', BLU = '#2c5a8a', ORG = '#d9782a';
9
+ const step = (t, fps = 10) => U.stepTime(t, fps); // 杆操的「一顿一顿」:10fps 步进
10
+ // ---------- 刻纹(在 clip 内 destination-out) ----------
11
+ // 刻纹库 P.carve(鱼子/团花/云纹/方格/刻线);皮件 P.piece(填→刻→补皮边→描边);印到幕布 P.stamp(软影+multiply)
12
+ const K = P.carve;
13
+ const piece = (g, path, col, carve, edge = 5) => P.piece(g, path, col, carve, { edge, line: EDGE });
14
+ const rivet = (c, x, y, r = 7) => { c.fillStyle = '#2a1006'; c.beginPath(); c.arc(x, y, r, 0, 7); c.fill(); c.fillStyle = '#b07a3a'; c.beginPath(); c.arc(x - 1, y - 1, r * 0.42, 0, 7); c.fill(); };
15
+ const rod = (c, A, B) => { c.save(); c.filter = 'blur(1.2px)'; c.globalCompositeOperation = 'multiply'; c.strokeStyle = 'rgba(60,30,12,.85)'; c.lineWidth = 5; c.beginPath(); c.moveTo(...A); c.lineTo(...B); c.stroke(); c.restore(); };
16
+ const rot = (p, o, a) => { const c = Math.cos(a), s = Math.sin(a), x = p[0] - o[0], y = p[1] - o[1]; return [o[0] + x * c - y * s, o[1] + x * s + y * c]; };
17
+ const tr = (path, o, a) => { const m = new DOMMatrix().translate(o[0], o[1]).rotate(a * 180 / Math.PI).translate(-o[0], -o[1]); const p = new Path2D(); p.addPath(path, m); return p; };
18
+ const stamp = (c, layer, dx = 7, dy = 5) => P.stamp(c, layer, { dx, dy });
19
+
20
+ // ---------- 影窗 ----------
21
+ function screen(c, t) {
22
+ const ft = step(t, 15), fl = 0.5 * P.noise(ft * 3, 1) + 0.3 * P.noise(ft * 9, 4);
23
+ const cx = 960 + fl * 26, cy = 470 + P.noise(ft * 2, 7) * 18;
24
+ const g = c.createRadialGradient(cx, cy, 30, cx, cy + 40, 1150);
25
+ g.addColorStop(0, `rgb(${255},${246 + fl * 8},${214 + fl * 20})`); g.addColorStop(0.35, '#f2d7a0'); g.addColorStop(0.7, '#cf9a58'); g.addColorStop(1, '#7a4a20');
26
+ c.fillStyle = g; c.fillRect(0, 0, W, H);
27
+ // 灯芯热点:一小团更亮的光,随灯焰跳
28
+ c.save(); c.globalCompositeOperation = 'screen'; const hg = c.createRadialGradient(cx, cy - 10, 0, cx, cy - 10, 260 + fl * 40); hg.addColorStop(0, `rgba(255,236,190,${0.55 + fl * 0.25})`); hg.addColorStop(1, 'rgba(255,236,190,0)'); c.fillStyle = hg; c.fillRect(0, 0, W, H); c.restore();
29
+ // 布纹(缓存)
30
+ c.save(); c.globalCompositeOperation = 'multiply'; c.drawImage(P.cached('py_weave', W, H, (w) => {
31
+ w.fillStyle = '#fff'; w.fillRect(0, 0, W, H); w.strokeStyle = 'rgba(120,90,50,.08)'; w.lineWidth = 1;
32
+ for (let x = 0; x < W; x += 3) { w.beginPath(); w.moveTo(x, 0); w.lineTo(x, H); w.stroke(); } for (let y = 0; y < H; y += 3) { w.beginPath(); w.moveTo(0, y); w.lineTo(W, y); w.stroke(); }
33
+ w.drawImage(P.grain('pyg', 0.06, [120, 80, 40], 0.3), 0, 0);
34
+ }), 0, 0); c.restore();
35
+ return fl;
36
+ }
37
+
38
+ // ---------- 道具:窗(窗棂+梅枝)、桌(万字纹桌裙)、瓶花 ----------
39
+ const props = () => P.cached('py_props', W, H, (g) => {
40
+ // 窗:外框+冰裂/方格窗棂(大面积镂空)
41
+ const win = new Path2D(); win.rect(350, 120, 460, 440);
42
+ piece(g, win, '#8a4a22', (q) => {
43
+ q.fillRect(372, 142, 416, 396);
44
+ }, 4);
45
+ g.save(); g.beginPath(); g.rect(372, 142, 416, 396); g.clip();
46
+ g.strokeStyle = '#8a4a22'; g.lineWidth = 9; const r = rng(3);
47
+ for (let x = 372; x <= 788; x += 69) { g.beginPath(); g.moveTo(x, 142); g.lineTo(x, 538); g.stroke(); }
48
+ for (let y = 142; y <= 538; y += 66) { g.beginPath(); g.moveTo(372, y); g.lineTo(788, y); g.stroke(); }
49
+ g.strokeStyle = EDGE; g.lineWidth = 2; for (let x = 372; x <= 788; x += 69) { g.strokeRect(x - 4.5, 142, 9, 396); } for (let y = 142; y <= 538; y += 66) g.strokeRect(372, y - 4.5, 416, 9);
50
+ g.restore();
51
+ // 窗台
52
+ piece(g, (() => { const p = new Path2D(); p.rect(326, 556, 508, 26); return p; })(), '#8a4a22', (q) => K.dots(q, 336, 562, 830, 580, 14, 3), 3);
53
+ // 桌:桌面+桌裙(万字纹格镂空)+两条腿
54
+ const top = new Path2D(); top.rect(806, 612, 410, 30); piece(g, top, '#9a3a1c', (q) => K.dots(q, 816, 620, 1210, 640, 16, 3));
55
+ const apron = new Path2D(); apron.rect(826, 642, 370, 70); piece(g, apron, '#9a3a1c', (q) => K.lattice(q, 834, 648, 1190, 708, 30, 14));
56
+ [[836, 642], [1076, 642]].forEach(([x, y]) => { const l = new Path2D(); l.moveTo(x, y); l.lineTo(x + 28, y); l.lineTo(x + 24 + (x < 900 ? -6 : 6), 905); l.lineTo(x + 4 + (x < 900 ? -6 : 6), 905); l.closePath(); piece(g, l, '#9a3a1c', (q) => K.dots(q, x + 8, y + 90, x + 22, 890, 30, 4)); });
57
+ // 瓶+牡丹
58
+ const vase = RIG.smooth([[860, 612], [846, 578], [866, 548], [900, 548], [922, 578], [908, 612]]); piece(g, vase, BLU, (q) => K.clouds(q, 852, 560, 916, 608, 26));
59
+ g.strokeStyle = GRN; g.lineWidth = 7; [[878, 548, 850, 480], [890, 548, 920, 470]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
60
+ [[850, 474, 30, RED], [922, 462, 34, '#d0506a']].forEach(([x, y, rr, col]) => { const f = new Path2D(); for (let k = 0; k < 10; k++) { const a = k / 10 * Math.PI * 2; f.moveTo(x + Math.cos(a) * rr * 0.5, y + Math.sin(a) * rr * 0.5); f.ellipse(x + Math.cos(a) * rr * 0.6, y + Math.sin(a) * rr * 0.6, rr * 0.45, rr * 0.3, a, 0, 7); }
61
+ piece(g, f, col, (q) => { q.beginPath(); q.arc(x, y, rr * 0.22, 0, 7); q.fill(); K.dots(q, x - rr, y - rr, x + rr, y + rr, 12, 2); }, 3); });
62
+ // 椅:方背椅
63
+ const chair = new Path2D(); chair.rect(1430, 470, 64, 440); piece(g, chair, '#7a3a1a', (q) => K.lattice(q, 1436, 480, 1490, 900, 32, 18), 4);
64
+ });
65
+
66
+ return {
67
+ draw(c, lt, t) {
68
+ const fl = screen(c, t);
69
+ const ts = step(t), ls = step(lt);
70
+ stamp(c, props(), 6, 4);
71
+ // 窗外的皮影蝴蝶:翅膀按 12fps 扇(scaleX 翻面),杆子操纵着沿弧线飞
72
+ { const L = P.scratch('pyBf'), g = L.getContext('2d'); g.reset();
73
+ const q = step(lt, 12), bx = 520 + q * 150, by = 300 - Math.sin(q * 3) * 50, flap = Math.abs(Math.cos(q * 15));
74
+ g.save(); g.translate(bx, by); g.rotate(0.3);
75
+ [-1, 1].forEach(sd => { g.save(); g.scale(sd * (0.25 + flap * 0.75), 1);
76
+ piece(g, RIG.smooth([[0, -4], [40, -46], [74, -30], [62, 4], [30, 10]]), sd > 0 ? RED : GRN, (k) => K.dots(k, 10, -40, 70, 6, 14, 3.5), 3);
77
+ piece(g, RIG.smooth([[0, 4], [34, 14], [46, 44], [16, 40]]), YEL, (k) => K.dots(k, 8, 10, 44, 40, 12, 3), 3); g.restore(); });
78
+ piece(g, (() => { const p = new Path2D(); p.ellipse(0, 6, 7, 22, 0, 0, 7); return p; })(), EDGE, null, 2); g.restore();
79
+ stamp(c, L, 5, 4); rod(c, [bx + 4, by + 24], [780, 1080]); }
80
+ // ---------- 猫 ----------
81
+ const ch = P.choreo(lt, t);
82
+ const KT = RIG.cat({ breathe: 0 });
83
+ { const L = P.scratch('pyCat'), g = L.getContext('2d'); g.reset();
84
+ const sway = 0.02 * Math.sin(ts * 5);
85
+ g.save(); g.translate(540, 905); g.rotate(sway); g.translate(-540, -905);
86
+ // 尾巴:4 节皮件铰链,每节相对上一节转一个相位滞后的角度(鞭梢)
87
+ let o = [452, 884], ang = -3.0;
88
+ const segs = [];
89
+ for (let i = 0; i < 4; i++) { ang += 0.3 * Math.sin(ts * 7 - i * 0.9) + (i ? 0.38 : 0); const L_ = 64 - i * 6, e = [o[0] + Math.cos(ang) * L_, o[1] + Math.sin(ang) * L_];
90
+ const p = RIG.taper(o, e, 30 - i * 4, 26 - i * 4); segs.push([p, o, i]); o = e; }
91
+ segs.forEach(([p, oo, i]) => piece(g, p, i === 3 ? '#efe6cc' : ORG, i === 3 ? null : (k) => K.lines(k, [[[oo[0] - 6, oo[1] - 10], [oo[0] + 8, oo[1] + 10]]], 4), 3));
92
+ // 身体(橘皮,背上刻虎斑;白肚=镂空只留皮边)
93
+ const body = RIG.smooth(KT.bodyPts);
94
+ piece(g, body, ORG, (k) => {
95
+ k.fill(RIG.smooth([[566, 704], [612, 694], [640, 744], [632, 828], [596, 868], [562, 802]])); // 白胸肚:整块镂空
96
+ K.lines(k, [[[448, 742], [492, 752]], [[440, 782], [486, 792]], [[436, 824], [480, 832]], [[500, 692], [524, 718]], [[470, 860], [510, 866]]], 7);
97
+ K.dots(k, 470, 700, 540, 880, 22, 3);
98
+ }, 5);
99
+ g.strokeStyle = EDGE; g.lineWidth = 2.2; g.stroke(RIG.smooth([[566, 704], [612, 694], [640, 744], [632, 828], [596, 868], [562, 802]]));
100
+ // 前腿一件(白爪也是镂空)
101
+ [[588, 1], [636, 1]].forEach(([x]) => { const leg = RIG.taper([x, 750], [x, 892], 30, 34);
102
+ piece(g, leg, '#f0e4c4', (k) => { k.fillRect(x - 10, 770, 20, 104); }, 4); }); // 白腿白爪:几乎全镂空,只留皮边
103
+ g.strokeStyle = EDGE; g.lineWidth = 2; [[588], [636]].forEach(([x]) => { g.beginPath(); g.moveTo(x - 8, 892); g.lineTo(x - 8, 880); g.moveTo(x + 4, 892); g.lineTo(x + 4, 880); g.stroke(); });
104
+ g.restore();
105
+ // 头:单独一件,绕颈部铆钉点头(10fps 一顿一顿)
106
+ const nk = [588, 680], nod = 0.1 * Math.sin(ts * 6) + 0.05;
107
+ g.save(); g.translate(540, 905); g.rotate(sway); g.translate(-540, -905);
108
+ g.save(); g.translate(nk[0], nk[1]); g.rotate(nod); g.translate(-nk[0], -nk[1]);
109
+ piece(g, KT.head, ORG, (k) => {
110
+ k.beginPath(); k.ellipse(632, 650, 36, 24, 0, 0, 7); k.fill(); // 白口鼻镂空
111
+ K.lines(k, [[[585, 572], [590, 598]], [[605, 566], [607, 594]], [[625, 572], [622, 596]]], 6); // 头顶虎斑
112
+ k.beginPath(); k.moveTo(598, 612); k.quadraticCurveTo(612, 600, 626, 612); k.quadraticCurveTo(612, 620, 598, 612); k.fill(); // 眼:柳叶形镂空
113
+ k.beginPath(); k.moveTo(636, 610); k.quadraticCurveTo(650, 598, 664, 608); k.quadraticCurveTo(650, 618, 636, 610); k.fill();
114
+ K.clouds(k, 560, 620, 600, 680, 30);
115
+ }, 4);
116
+ g.strokeStyle = EDGE; g.lineWidth = 2; g.beginPath(); g.ellipse(632, 650, 36, 24, 0, 0, 7); g.stroke();
117
+ g.fillStyle = EDGE; g.beginPath(); g.arc(612, 610, 4, 0, 7); g.arc(650, 607, 4, 0, 7); g.fill(); g.beginPath(); g.arc(656, 640, 5, 0, 7); g.fill();
118
+ g.lineWidth = 1.8; [[660, 645, 712, 632], [660, 650, 715, 655]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
119
+ g.restore(); g.restore();
120
+ stamp(c, L, 7, 5);
121
+ const nk2 = rot(nk, [540, 905], sway); rivet(c, nk2[0], nk2[1]); segs.forEach(([, oo]) => { const q = rot(oo, [540, 905], sway); rivet(c, q[0], q[1], 5); });
122
+ rod(c, rot([520, 760], [540, 905], sway), [470, 1080]); rod(c, rot([600, 600], [540, 905], sway), [610, 1080]);
123
+ }
124
+ // ---------- 少女 ----------
125
+ { const L = P.scratch('pyGirl'), g = L.getContext('2d'); g.reset();
126
+ const cupJ = 0.5 - 0.5 * Math.cos(clamp(ls / 0.85) * Math.PI); // 杆子端杯:10fps 步进
127
+ const G = RIG.girl({ cup: cupJ, hair: 'bun' });
128
+ const pivot = [1260, 930], sway = 0.012 * Math.sin(ts * 4.4);
129
+ g.save(); g.translate(...pivot); g.rotate(sway); g.translate(-pivot[0], -pivot[1]);
130
+ // 远侧袖(绿)+手
131
+ piece(g, G.farSleeve, GRN, (k) => K.clouds(k, 1220, 470, 1360, 630, 34));
132
+ piece(g, G.farHand, '#efe2c4', null, 3);
133
+ // 裙:绿长裙,竖刻褶线+下摆鱼子纹边
134
+ piece(g, G.skirt, GRN, (k) => {
135
+ K.lines(k, [[[1170, 700], [1150, 900]], [[1210, 690], [1196, 910]], [[1250, 680], [1244, 910]], [[1290, 680], [1292, 900]], [[1330, 700], [1340, 790]]], 3.5);
136
+ K.dots(k, 1110, 880, 1360, 920, 14, 3.2); K.flowers(k, 1380, 640, 1450, 760, 40, 4.5);
137
+ });
138
+ // 上衣:红袄,刻团花;领口/襟边一条黄色宽边
139
+ piece(g, G.torso, RED, (k) => K.flowers(k, 1300, 470, 1420, 610, 44, 5));
140
+ piece(g, RIG.smooth([[1290, 436], [1318, 430], [1316, 470], [1306, 540], [1300, 610], [1286, 610], [1284, 520]]), YEL, (k) => K.dots(k, 1286, 446, 1318, 600, 12, 2.5), 3);
141
+ // 头:发(琥珀色皮=金发,刻云纹)→ 空脸(脸整个镂空,只留皮边与五官线)→ 头茬(花钿、簪、步摇珠串)
142
+ const nk = G.A.neck, hTilt = 0.06 * Math.sin(ts * 3); // 头绕颈部铆钉微转
143
+ g.save(); g.translate(...nk); g.rotate(hTilt); g.translate(-nk[0], -nk[1]);
144
+ piece(g, G.hairBack, AMB, (k) => K.clouds(k, 1290, 300, 1420, 440, 30));
145
+ piece(g, G.bun, AMB, (k) => K.clouds(k, 1360, 340, 1420, 404, 22));
146
+ g.save(); g.globalCompositeOperation = 'destination-out'; g.fill(G.face); g.restore();
147
+ g.strokeStyle = EDGE; g.lineWidth = 3; g.stroke(G.face);
148
+ g.strokeStyle = AMB; g.lineWidth = 7; g.save(); g.clip(G.face); g.stroke(G.face); g.restore();
149
+ piece(g, G.bangs, AMB, (k) => K.clouds(k, 1250, 296, 1340, 360, 24));
150
+ // 五官:凤眼(细长挑梢)、柳叶眉、点唇——都是皮上留的线
151
+ const e = G.eye; g.strokeStyle = EDGE; g.lineWidth = 2.6; g.beginPath(); g.moveTo(e.x - 12, e.y + 1); g.quadraticCurveTo(e.x, e.y - 6, e.x + 16, e.y - 4); g.stroke();
152
+ g.lineWidth = 2; g.beginPath(); g.moveTo(e.x - 10, e.y + 2); g.quadraticCurveTo(e.x, e.y + 4, e.x + 12, e.y - 1); g.stroke();
153
+ g.fillStyle = EDGE; g.beginPath(); g.arc(e.x - 2, e.y - 1, 2.6, 0, 7); g.fill();
154
+ g.lineWidth = 2.4; g.stroke(G.brow);
155
+ g.fillStyle = RED; g.beginPath(); g.arc(G.lips[1][0] - 1, G.lips[1][1] + 2, 4, 0, 7); g.fill();
156
+ // 头茬:发顶一朵大花+两根簪+垂下的步摇
157
+ const ht = G.A.headTop; const flw = new Path2D(); for (let k = 0; k < 6; k++) { const a = k / 6 * Math.PI * 2; flw.moveTo(ht[0] + 20 + Math.cos(a) * 26, ht[1] + 6 + Math.sin(a) * 26); flw.arc(ht[0] + 20 + Math.cos(a) * 18, ht[1] + 6 + Math.sin(a) * 18, 12, 0, 7); }
158
+ piece(g, flw, RED, (k) => { k.beginPath(); k.arc(ht[0] + 20, ht[1] + 6, 7, 0, 7); k.fill(); }, 3);
159
+ g.strokeStyle = BLU; g.lineWidth = 5; g.beginPath(); g.moveTo(ht[0] + 40, ht[1] + 30); g.lineTo(ht[0] + 110, ht[1] - 10); g.stroke(); g.strokeStyle = EDGE; g.lineWidth = 1.6; g.stroke();
160
+ const sw = 0.4 * Math.sin(ts * 9); // 步摇:串珠随头动甩
161
+ g.fillStyle = YEL; g.strokeStyle = EDGE; g.lineWidth = 1.5; for (let k = 0; k < 4; k++) { const bx = ht[0] + 108 + Math.sin(sw) * k * 11, by = ht[1] - 4 + Math.cos(sw) * k * 11; g.beginPath(); g.arc(bx, by, 5, 0, 7); g.fill(); g.stroke(); }
162
+ g.restore();
163
+ // 近侧手臂:上臂/前臂/手三件刚体,铆钉相连;手腕松,会甩
164
+ const S = G.A.shoulder, E = G.A.elbow, Hd = G.A.hand;
165
+ piece(g, RIG.taper(S, E, 50, 42), RED, (k) => K.clouds(k, Math.min(S[0], E[0]) - 30, Math.min(S[1], E[1]) - 30, Math.max(S[0], E[0]) + 30, Math.max(S[1], E[1]) + 30, 30));
166
+ const Wr = [lerp(E[0], Hd[0], 0.8), lerp(E[1], Hd[1], 0.8)];
167
+ piece(g, RIG.taper(E, Wr, 44, 50), RED, (k) => K.flowers(k, Math.min(E[0], Wr[0]) - 30, Math.min(E[1], Wr[1]) - 30, Math.max(E[0], Wr[0]) + 30, Math.max(E[1], Wr[1]) + 30, 36, 4));
168
+ piece(g, RIG.taper([lerp(E[0], Hd[0], 0.66), lerp(E[1], Hd[1], 0.66)], Wr, 52, 58), YEL, (k) => K.dots(k, Wr[0] - 40, Wr[1] - 40, Wr[0] + 40, Wr[1] + 40, 10, 2.2), 3);
169
+ const dangle = 0.35 * Math.sin(ls * 11) * (1 - cupJ * 0.6);
170
+ g.save(); g.translate(...Wr); g.rotate(dangle); g.translate(-Wr[0], -Wr[1]);
171
+ const hand = new Path2D(); hand.ellipse(Hd[0], Hd[1], 17, 13, Math.atan2(Hd[1] - E[1], Hd[0] - E[0]), 0, 7);
172
+ g.save(); g.globalCompositeOperation = 'destination-out'; g.fill(hand); g.restore(); g.strokeStyle = EDGE; g.lineWidth = 2.6; g.stroke(hand); // 手也是空的
173
+ const cp = G.cup, cup = RIG.smooth([[cp.x - 24, cp.y], [cp.x - 20, cp.y + 34], [cp.x, cp.y + 42], [cp.x + 20, cp.y + 34], [cp.x + 24, cp.y]]);
174
+ piece(g, tr(cup, [cp.x, cp.y], cp.tilt), BLU, (k) => K.flowers(k, cp.x - 30, cp.y, cp.x + 30, cp.y + 44, 24, 3.5), 3);
175
+ g.restore();
176
+ g.restore();
177
+ stamp(c, L, 8, 5);
178
+ // 铆钉与杆:颈、肩、肘、腕;主杆在颈,手杆在腕
179
+ const R = p => rot(p, pivot, sway);
180
+ [nk, S, E, Wr].forEach(p => { const q = R(p); rivet(c, q[0], q[1]); });
181
+ rod(c, R(nk), [1420, 1080]); rod(c, R(Wr), [1180, 1080]); rod(c, [1170, 615], [1060, 1080]);
182
+ // 热气:皮影里用一缕刻出来的云纹皮条表示,跟着杯子飘
183
+ const sx = G.cup.x, sy = G.cup.y - 20;
184
+ c.save(); c.globalCompositeOperation = 'multiply'; c.strokeStyle = 'rgba(160,110,60,.55)'; c.lineWidth = 5; c.lineCap = 'round';
185
+ for (let k = 0; k < 2; k++) { c.beginPath(); for (let i = 0; i <= 14; i++) { const q = i / 14; const x = sx + k * 14 + Math.sin(q * 6 - ts * 6 + k) * 8, y = sy - q * 60; i ? c.lineTo(x, y) : c.moveTo(x, y); } c.stroke(); } c.restore();
186
+ }
187
+ // 木框(影窗边)
188
+ c.drawImage(P.cached('py_frame', W, H, (g) => {
189
+ g.fillStyle = '#2a140a'; g.beginPath(); g.rect(0, 0, W, H); g.rect(28, 24, W - 56, H - 48); g.fill('evenodd');
190
+ g.strokeStyle = '#a8742e'; g.lineWidth = 3; g.strokeRect(34, 30, W - 68, H - 60);
191
+ const vg = g.createRadialGradient(960, 500, 500, 960, 540, 1150); vg.addColorStop(0, 'rgba(40,15,0,0)'); vg.addColorStop(1, 'rgba(40,15,0,.45)'); g.fillStyle = vg; g.fillRect(28, 24, W - 56, H - 48);
192
+ }), 0, 0);
193
+ },
194
+ };
195
+ })();