@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,390 @@
1
+ // 角色骨架:17 岁欧洲金发少女(侧身朝左坐)+ 橘白猫(朝右坐)。
2
+ // 每个部位输出 Path2D,时代代码自己决定填色/描边/剪影/渲染器——同一副骨架、同一套动作,只换画法。
3
+ // 坐标基于标准构图(分镜表 §2.5):人 (1120,200)-(1500,910),猫 (390,610)-(690,905),桌 (810,620)-(1200,905)。
4
+ (() => {
5
+ const { clamp, lerp } = U;
6
+ const R = window.RIG = {};
7
+
8
+ // 二骨 IK:肩 S,目标 T,上臂 a,前臂 b,bendSign 决定肘向
9
+ R.ik2 = (S, T, a, b, bendSign = 1) => {
10
+ let dx = T[0] - S[0], dy = T[1] - S[1], d = Math.hypot(dx, dy);
11
+ d = Math.min(d, a + b - 0.01);
12
+ const ang = Math.atan2(dy, dx), cosA = clamp((a * a + d * d - b * b) / (2 * a * d), -1, 1);
13
+ const e = ang + bendSign * Math.acos(cosA);
14
+ const E = [S[0] + Math.cos(e) * a, S[1] + Math.sin(e) * a];
15
+ const H = [S[0] + Math.cos(ang) * d, S[1] + Math.sin(ang) * d];
16
+ return { E, H };
17
+ };
18
+ // 粗细变化的肢体:沿 A→B 画一个圆头胶囊。两端半圆帽朝外(逆时针从 n 走到 n-π)。
19
+ // 旧版 arc 方向反了,帽子凹进胶囊、路径自交,粗描边/平涂时关节处露洞(迁移测试 C:哈林小人被画成奶牛)。
20
+ R.limb = (A, B, wA, wB) => {
21
+ const p = new Path2D(), a = Math.atan2(B[1] - A[1], B[0] - A[0]), n = a + Math.PI / 2;
22
+ p.moveTo(A[0] + Math.cos(n) * wA / 2, A[1] + Math.sin(n) * wA / 2);
23
+ p.lineTo(B[0] + Math.cos(n) * wB / 2, B[1] + Math.sin(n) * wB / 2);
24
+ p.arc(B[0], B[1], wB / 2, n, n - Math.PI, true);
25
+ p.lineTo(A[0] - Math.cos(n) * wA / 2, A[1] - Math.sin(n) * wA / 2);
26
+ p.arc(A[0], A[1], wA / 2, n - Math.PI, n - 2 * Math.PI, true);
27
+ p.closePath(); return p;
28
+ };
29
+ // 非等比拉长(毕加索蓝色时期、El Greco 式的瘦长人物):以 anchor 为轴把整副骨架按 (sx, sy) 缩放。
30
+ // 所有 Path2D(含 .pts 点列)、锚点、杯子、眼睛、HP/T 坐标函数一起变换;线宽不变(不像 setTransform 那样把描边也压扁)。
31
+ R.stretch = (O, sx = 1, sy = 1, anchor = [0, 0]) => {
32
+ if (sx === 1 && sy === 1) return O;
33
+ const m = new DOMMatrix().translate(anchor[0], anchor[1]).scale(sx, sy).translate(-anchor[0], -anchor[1]);
34
+ const mp = q => { const r = m.transformPoint(new DOMPoint(q[0], q[1])); return [r.x, r.y]; };
35
+ const isPt = v => Array.isArray(v) && v.length === 2 && typeof v[0] === 'number' && typeof v[1] === 'number';
36
+ const map = v => {
37
+ if (v instanceof Path2D) { const p = new Path2D(); p.addPath(v, m); if (v.pts) { p.pts = v.pts.map(mp); p.closed = v.closed; } return p; }
38
+ if (isPt(v)) return mp(v);
39
+ if (Array.isArray(v)) return v.map(map);
40
+ if (v && typeof v === 'object' && 'x' in v && 'y' in v) { const q = mp([v.x, v.y]), o = { ...v, x: q[0], y: q[1] }; if ('w' in v) { o.w = v.w * sx; o.h = v.h * sy; } if ('r' in v) o.r = v.r * Math.sqrt(sx * sy); if ('rx' in v) { o.rx = v.rx * sx; o.ry = v.ry * sy; } return o; }
41
+ if (v && typeof v === 'object' && v.constructor === Object) { const o = {}; for (const k in v) o[k] = map(v[k]); return o; }
42
+ return v;
43
+ };
44
+ const out = {};
45
+ for (const k in O) {
46
+ if (typeof O[k] === 'function') { const f = O[k]; out[k] = (...a) => mp(f(...a)); }
47
+ else if (k === 'rigid' || k === 'tilt' || k === 'blink' || k === 'tailW' || k === 'eyeR' || k === 'headR') out[k] = O[k];
48
+ else out[k] = map(O[k]);
49
+ }
50
+ out.stretch = { sx, sy, anchor, matrix: m };
51
+ return out;
52
+ };
53
+
54
+ // ---------------- 少女(v2:按设定稿逐部位量点,Catmull-Rom 平滑) ----------------
55
+ // 关键点取自 生图/plates/09_postimp.png(AI 设定稿)叠网格读数;参考姿势 = 端杯在嘴边。
56
+ R.smooth = (pts, closed = true, k = 0.5) => { // Catmull-Rom → 三次贝塞尔
57
+ const p = new Path2D(), n = pts.length; if (n < 2) return p;
58
+ const P_ = i => pts[closed ? (i + n) % n : Math.max(0, Math.min(n - 1, i))];
59
+ p.moveTo(...pts[0]);
60
+ for (let i = 0; i < (closed ? n : n - 1); i++) {
61
+ const p0 = P_(i - 1), p1 = P_(i), p2 = P_(i + 1), p3 = P_(i + 2);
62
+ p.bezierCurveTo(p1[0] + (p2[0] - p0[0]) * k / 3, p1[1] + (p2[1] - p0[1]) * k / 3, p2[0] - (p3[0] - p1[0]) * k / 3, p2[1] - (p3[1] - p1[1]) * k / 3, p2[0], p2[1]);
63
+ }
64
+ if (closed) p.closePath(); return p;
65
+ };
66
+ R.taper = (A, B, wA, wB, bulge = 0) => { // 锥形肢体(袖子/手臂),bulge 让中段鼓起
67
+ const a = Math.atan2(B[1] - A[1], B[0] - A[0]), nx = Math.cos(a + Math.PI / 2), ny = Math.sin(a + Math.PI / 2);
68
+ const M = [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2], wM = (wA + wB) / 2 + bulge;
69
+ return R.smooth([[A[0] + nx * wA / 2, A[1] + ny * wA / 2], [M[0] + nx * wM / 2, M[1] + ny * wM / 2], [B[0] + nx * wB / 2, B[1] + ny * wB / 2],
70
+ [B[0] + Math.cos(a) * wB * 0.35, B[1] + Math.sin(a) * wB * 0.35], [B[0] - nx * wB / 2, B[1] - ny * wB / 2], [M[0] - nx * wM / 2, M[1] - ny * wM / 2], [A[0] - nx * wA / 2, A[1] - ny * wA / 2], [A[0] - Math.cos(a) * wA * 0.3, A[1] - Math.sin(a) * wA * 0.3]]);
71
+ };
72
+ // opt: { cup:0..1, sip, dx, dy, s, hat:'straw'|null, hair:'bun'|'long'|'braid'|'down', breathe,
73
+ // sx, sy, anchor:[x,y](非等比拉长,默认以脚底 (1240,932) 为轴,见 R.stretch) }
74
+ // 返回 G:每个部件一个 Path2D(RIG.smooth 生成的带 .pts 点列,可沿轮廓逐点改线宽/重剪);G.A 锚点;
75
+ // G.pivots 关节枢轴+G.rigid 刚体分组(皮影/剪纸偶:每组只绕自己的枢轴转,见文件末 R.rigidPose)。
76
+ R.girl = (opt = {}) => {
77
+ const { cup = 0, sip = 0, dx = 0, dy = 0, s = 1, breathe = 0 } = opt;
78
+ const T = (x, y) => [dx + 1300 + (x - 1300) * s, dy + 620 + (y - 620) * s];
79
+ const neck = T(1300, 425), tilt = -0.07 * cup - sip * 0.6;
80
+ const HP = (x, y) => { const p = T(x, y), c = Math.cos(tilt), si = Math.sin(tilt), X = p[0] - neck[0], Y = p[1] - neck[1]; return [neck[0] + X * c - Y * si, neck[1] + X * si + Y * c]; };
81
+ const TB = (x, y) => T(x, y + (y < 600 ? breathe * 150 * (600 - y) / 170 : 0)); // 呼吸只动上身
82
+ const sm = (f, pts, closed = true) => { const q = pts.map(p => f(...p)); return Object.assign(R.smooth(q, closed), { pts: q, closed }); }; // Path2D 带点列 .pts
83
+ const G = {};
84
+ // —— 头部组 ——
85
+ G.hairBack = sm(HP, [[1262, 312], [1300, 290], [1350, 293], [1392, 322], [1408, 368], [1398, 420], [1372, 448], [1346, 442], [1332, 412], [1318, 380], [1292, 340]]);
86
+ const style = opt.hair || 'bun';
87
+ G.bun = style === 'bun' ? (() => { const c0 = HP(1390, 372), p = new Path2D(); p.arc(c0[0], c0[1], 31 * s, 0, Math.PI * 2); return p; })() : null;
88
+ G.bunSpiral = HP(1390, 372);
89
+ G.hairDown = (style === 'long' || style === 'down') ? sm(HP, [[1340, 420], [1385, 430], [1418, 480], [1425, 560], [1405, 600], [1385, 540], [1360, 470]]) : null;
90
+ G.braid = style === 'braid' ? [0, 1, 2, 3, 4].map(k => HP(1392 - k * 2, 440 + k * 30)) : null;
91
+ G.face = sm(HP, [[1258, 318], [1252, 335], [1250, 352], [1242, 366], [1249, 373], [1256, 378], [1253, 386], [1259, 391], [1255, 398], [1263, 406], [1276, 413], [1296, 412], [1316, 398], [1326, 370], [1320, 335], [1296, 314]]);
92
+ G.neck = sm(T, [[1286, 400], [1314, 400], [1324, 445], [1290, 448]]);
93
+ G.bangs = sm(HP, [[1248, 322], [1260, 300], [1290, 293], [1322, 304], [1338, 330], [1332, 364], [1320, 346], [1302, 328], [1282, 322], [1266, 330], [1257, 346]]);
94
+ G.locks = [sm(HP, [[1318, 345], [1308, 372], [1314, 398], [1306, 420], [1314, 440]], false), sm(HP, [[1326, 352], [1322, 380], [1330, 405], [1324, 430]], false)];
95
+ G.hairLines = [sm(HP, [[1262, 312], [1300, 300], [1345, 312], [1380, 340]], false), sm(HP, [[1275, 322], [1315, 318], [1350, 338], [1372, 372]], false), sm(HP, [[1300, 330], [1340, 352], [1360, 395]], false)];
96
+ const ec = HP(1268, 350); G.eye = { x: ec[0], y: ec[1], r: 5.5 * s };
97
+ G.lid = sm(HP, [[1259, 348], [1268, 343], [1279, 348]], false);
98
+ G.lashes = [[HP(1259, 348), HP(1254, 343)], [HP(1262, 345), HP(1258, 339)]];
99
+ G.brow = sm(HP, [[1257, 335], [1268, 330], [1281, 334]], false);
100
+ G.nostril = HP(1250, 382);
101
+ G.lips = [HP(1252, 389), HP(1259, 390), HP(1254, 397)];
102
+ G.cheek = HP(1283, 380); G.ear = HP(1318, 372);
103
+ if (opt.hat === 'straw' || opt.hat === 'bonnet') {
104
+ G.brim = (() => { const c0 = HP(1326, 300), p = new Path2D(); p.ellipse(c0[0], c0[1], 130 * s, 25 * s, tilt + 0.19, 0, Math.PI * 2); return p; })();
105
+ G.crown = (() => { const c0 = HP(1336, 290), p = new Path2D(); p.ellipse(c0[0], c0[1], 62 * s, 44 * s, tilt + 0.14, Math.PI, 0); p.closePath(); return p; })();
106
+ G.band = sm(HP, [[1276, 283], [1336, 296], [1398, 306], [1396, 318], [1336, 309], [1276, 296]]);
107
+ G.bow = HP(1404, 318);
108
+ }
109
+ // —— 身体 ——
110
+ G.torso = sm(TB, [[1290, 432], [1318, 428], [1350, 434], [1380, 446], [1404, 482], [1416, 542], [1424, 600], [1412, 622], [1300, 618], [1285, 560], [1282, 500], [1286, 460]]);
111
+ G.bib = sm(TB, [[1291, 438], [1313, 436], [1307, 500], [1303, 560], [1305, 612], [1286, 612], [1283, 520]]);
112
+ G.skirt = sm(T, [[1300, 605], [1420, 612], [1446, 650], [1452, 722], [1440, 762], [1362, 772], [1342, 802], [1352, 926], [1220, 932], [1104, 926], [1118, 820], [1150, 722], [1190, 656], [1242, 630]]);
113
+ G.apronSkirt = sm(T, [[1300, 612], [1352, 642], [1332, 702], [1316, 822], [1200, 824], [1192, 702], [1200, 656], [1252, 630]]);
114
+ G.folds = [sm(T, [[1230, 670], [1222, 760], [1226, 815]], false), sm(T, [[1268, 668], [1262, 760], [1268, 818]], false), sm(T, [[1160, 760], [1150, 860], [1146, 920]], false), sm(T, [[1300, 820], [1306, 880], [1300, 925]], false)];
115
+ G.apronBow = [T(1428, 616), T(1450, 600), T(1452, 634), T(1440, 690)];
116
+ G.shoe = sm(T, [[1098, 918], [1120, 910], [1146, 914], [1150, 928], [1100, 930]]);
117
+ // 远侧手臂:搭在桌上(在躯干后面画)
118
+ G.farSleeve = sm(TB, [[1332, 468], [1364, 520], [1346, 600], [1250, 632], [1212, 626], [1220, 604], [1300, 584], [1318, 520]]);
119
+ G.farCuff = sm(T, [[1222, 604], [1236, 604], [1238, 630], [1220, 632]]);
120
+ G.farHand = sm(T, [[1218, 606], [1180, 604], [1150, 609], [1146, 618], [1180, 626], [1218, 630]]);
121
+ // 近侧手臂:IK,杯子从桌面(1196,598)举到嘴边(1226,380)
122
+ const S = TB(1352, 456);
123
+ const cupTable = T(1190, 596), cupMouth = HP(1226, 376);
124
+ const cp = [lerp(cupTable[0], cupMouth[0], cup), lerp(cupTable[1], cupMouth[1], cup)];
125
+ const handT = [cp[0] + 28 * s, cp[1] + 16 * s];
126
+ const { E, H } = R.ik2(S, handT, 165 * s, 122 * s, -1);
127
+ G.upperArm = R.taper(S, E, 54 * s, 40 * s, 10 * s);
128
+ const W_ = [lerp(E[0], H[0], 0.82), lerp(E[1], H[1], 0.82)];
129
+ G.foreArm = R.taper(E, W_, 40 * s, 30 * s, 2 * s);
130
+ G.cuff = R.taper([lerp(E[0], H[0], 0.76), lerp(E[1], H[1], 0.76)], W_, 32 * s, 31 * s);
131
+ const fa = Math.atan2(H[1] - E[1], H[0] - E[0]);
132
+ G.hand = (() => { const p = new Path2D(), r = 15 * s; p.ellipse(H[0], H[1], r * 1.15, r, fa, 0, Math.PI * 2); return p; })();
133
+ G.thumb = [H[0] + Math.cos(fa - 1.2) * 14 * s, H[1] + Math.sin(fa - 1.2) * 14 * s];
134
+ G.cup = { x: cp[0], y: cp[1] - 18 * s, w: 44 * s, h: 38 * s, tilt: -0.45 * cup };
135
+ G.hipSeat = T(1400, 700);
136
+ // 锚点:给时代代码加服饰/头饰/光环用(都已跟随缩放、头部点跟随头部倾斜)
137
+ G.A = { headTop: HP(1320, 288), headC: HP(1318, 360), forehead: HP(1262, 318), nape: HP(1360, 420), ear: HP(1318, 372), chin: HP(1270, 410),
138
+ neck: T(1305, 435), chest: TB(1300, 500), shoulder: S, back: TB(1410, 520), waist: T(1350, 612), lap: T(1250, 640), knee: T(1190, 660), hemL: T(1104, 926), hemR: T(1352, 926), elbow: E, hand: H };
139
+ G.tilt = tilt;
140
+ G.HP = HP; G.T = T; // 头部局部坐标(随仰头旋转)与身体坐标,加头饰/服饰用
141
+ G.A.wrist = W_; G.A.cupBottom = [G.cup.x, G.cup.y + G.cup.h];
142
+ G.box = [T(1100, 240), T(1460, 935)];
143
+ // 刚体部件+枢轴:head 组绕 neck 转(仰头就是这么转的),upperArm 绕 shoulder,foreArm/cuff 绕 elbow,hand 绕 wrist
144
+ G.pivots = { neck, shoulder: S, elbow: E, wrist: W_, hip: T(1400, 700), farShoulder: TB(1332, 468), knee: G.A.knee };
145
+ G.rigid = { head: ['hairBack', 'bun', 'hairDown', 'braid', 'face', 'bangs', 'locks', 'hairLines', 'lid', 'brow', 'brim', 'crown', 'band'], neck: ['neck'],
146
+ torso: ['torso', 'bib'], skirt: ['skirt', 'apronSkirt', 'folds', 'shoe'], farArm: ['farSleeve', 'farCuff', 'farHand'],
147
+ upperArm: ['upperArm'], foreArm: ['foreArm', 'cuff'], hand: ['hand'] };
148
+ G.rigidPivot = { head: 'neck', neck: 'neck', torso: 'hip', skirt: 'hip', farArm: 'farShoulder', upperArm: 'shoulder', foreArm: 'elbow', hand: 'wrist' };
149
+ if (opt.sx || opt.sy) return R.stretch(G, opt.sx || 1, opt.sy || 1, opt.anchor || [1240, 932]);
150
+ return G;
151
+ };
152
+ // ---------- 分层绘制的钩子(迁移测试 C 的 X.girl/X.cat 思路,正式并入) ----------
153
+ // pal.before = {部件名: (c, G, pal) => {}}、pal.after = {部件名: fn}:在任意部件前/后插画(领子、裙撑、刺绣、发饰、项圈……)。
154
+ // pal.hooks = {别名: fn}:常用插槽的简写,别名表见 GIRL_SLOTS / CAT_SLOTS。
155
+ // 钩子画的东西和部件一样参与遮挡:在 mode:'line'(visibleLines)下也只露出看得见的线——钩子里描线请用 pal.line,别写死颜色。
156
+ const GIRL_SLOTS = { back: ['before', 'farSleeve'], skirt: ['after', 'skirt'], torso: ['after', 'torso'], head: ['after', 'bangs'], face: ['after', 'features'], arm: ['after', 'cuff'], end: ['after', 'thumb'] };
157
+ const CAT_SLOTS = { back: ['before', 'tail'], body: ['after', 'white'], head: ['after', 'stripes'], end: ['after', 'whiskers'] };
158
+ const hooker = (c, O, pal, slots) => {
159
+ const B = { ...(pal.before || {}) }, A = { ...(pal.after || {}) };
160
+ if (pal.hooks) for (const k in pal.hooks) { const sl = slots[k]; if (!sl) throw new Error(`RIG 钩子别名不存在:${k}(可用 ${Object.keys(slots).join('/')},或用 before/after:{部件名})`); (sl[0] === 'before' ? B : A)[sl[1]] = pal.hooks[k]; }
161
+ return (name, fn) => { if (B[name]) B[name](c, O, pal); fn(); if (A[name]) A[name](c, O, pal); };
162
+ };
163
+ // 少女部件画序(远→近)。自己逐部件上色时照这个顺序画,遮挡就是对的。
164
+ R.GIRL_ORDER = ['farSleeve', 'farHand', 'farCuff', 'hairDown', 'skirt', 'apronSkirt', 'folds', 'shoe', 'torso', 'bib', 'apronBow', 'neck', 'hairBack', 'bun', 'bunSpiral', 'face', 'cheek', 'ear',
165
+ 'bangs', 'hairLines', 'locks', 'features', 'hat', 'upperArm', 'foreArm', 'cuff', 'hand', 'cup', 'thumb'];
166
+ // 端杯手与杯子的前后:杯子在手前面(侧身朝左时杯子靠观众这侧)
167
+ // pal 颜色:dress(skirt/torso/farSleeve 可单独覆盖)sleeve skin hair hairLine cuff apron fold shoe iris lip cheek browC hat hatBand cupBody cupRim
168
+ // pal 线:line lw lwOf:{部件名:线宽} mode:'both'|'fill'|'line'(line 内部走 visibleLines)lineKey
169
+ // pal 分层:before/after/hooks(见上);paint(c, path, color, name) 接管每个部件的「填+描」(赛璐珞、网点、剪纸边……);
170
+ // features(c, G, pal) 接管五官;cup(c, G, pal) 接管杯子;blink 闭眼;locks:false 不画鬓角发绺。
171
+ R.drawGirl = (c, G, pal) => {
172
+ if (pal.mode === 'line') { // 线稿模式:内部走 visibleLines,被遮住的轮廓(远侧袖子、帽檐后半圈、被手臂挡住的躯干线)不会画穿
173
+ const [a, b] = G.box, box = [a[0] - 60, a[1] - 90, b[0] + 60, b[1] + 30];
174
+ c.drawImage(R.visibleLines(pal.lineKey || 'girl', (g, lc) => R.drawGirl(g, G, { ...pal, mode: 'both', line: lc }), pal.line || '#1b1d3a', box), 0, 0); return;
175
+ }
176
+ const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3, mode = pal.mode || 'both';
177
+ const step = hooker(c, G, pal, GIRL_SLOTS);
178
+ const LWof = (name, d = lw) => pal.lwOf && pal.lwOf[name] != null ? pal.lwOf[name] : d;
179
+ const F = (name, p, col, line = true) => step(name, () => {
180
+ if (!p || !col) return;
181
+ if (pal.paint) { pal.paint(c, p, col, name); return; }
182
+ if (mode !== 'line') { c.fillStyle = col; c.fill(p); }
183
+ const w = LWof(name); if (w && line && mode !== 'fill') { c.strokeStyle = L; c.lineWidth = w; c.stroke(p); }
184
+ });
185
+ const S_ = (p, col, w) => { if (!p) return; c.strokeStyle = col; c.lineWidth = w; c.stroke(p); };
186
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
187
+ F('farSleeve', G.farSleeve, pal.farSleeve || pal.dress); F('farHand', G.farHand, pal.skin); F('farCuff', G.farCuff, pal.cuff || pal.apron || '#f3eee2');
188
+ F('hairDown', G.hairDown, pal.hair);
189
+ F('skirt', G.skirt, pal.skirt || pal.dress); if (pal.apron) F('apronSkirt', G.apronSkirt, pal.apron);
190
+ step('folds', () => { if (pal.fold) G.folds.forEach(f => S_(f, pal.fold, 2.5)); });
191
+ F('shoe', G.shoe, pal.shoe || '#4a2c1a');
192
+ F('torso', G.torso, pal.torso || pal.dress); if (pal.apron) F('bib', G.bib, pal.apron);
193
+ step('apronBow', () => { if (!pal.apron) return; c.strokeStyle = L; c.lineWidth = lw; c.fillStyle = pal.apron; const b = G.apronBow; c.beginPath(); c.ellipse(b[0][0] + 10, b[0][1] - 6, 16, 10, -0.5, 0, Math.PI * 2); c.fill(); c.stroke(); c.beginPath(); c.moveTo(...b[0]); c.lineTo(...b[3]); c.lineWidth = 9; c.strokeStyle = pal.apron; c.stroke(); });
194
+ F('neck', G.neck, pal.skin);
195
+ F('hairBack', G.hairBack, pal.hair); F('bun', G.bun, pal.hair);
196
+ step('bunSpiral', () => { if (G.bun && pal.hairLine) { c.strokeStyle = pal.hairLine; c.lineWidth = 2.5; c.beginPath(); for (let a = 0; a < 9; a += 0.2) { const r = 4 + a * 2.6; const x = G.bunSpiral[0] + Math.cos(a) * r, y = G.bunSpiral[1] + Math.sin(a) * r; a ? c.lineTo(x, y) : c.moveTo(x, y); } c.stroke(); } });
197
+ F('face', G.face, pal.skin);
198
+ step('cheek', () => { c.save(); c.clip(G.face); if (pal.cheek) { c.fillStyle = pal.cheek; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 13, 0, Math.PI * 2); c.fill(); } c.restore(); });
199
+ step('ear', () => { if (G.ear && pal.skin) { c.fillStyle = pal.skin; c.beginPath(); c.ellipse(G.ear[0], G.ear[1], 8, 12, 0.2, 0, Math.PI * 2); c.fill(); const w = LWof('ear', lw * 0.7); if (w) { c.strokeStyle = L; c.lineWidth = w; c.stroke(); } } });
200
+ F('bangs', G.bangs, pal.hair);
201
+ step('hairLines', () => { if (pal.hairLine) { G.hairLines.forEach(h => S_(h, pal.hairLine, 2.2)); } });
202
+ step('locks', () => { if (pal.locks !== false) G.locks.forEach(h => { S_(h, L, 9); S_(h, pal.hair, 6); }); });
203
+ // 五官
204
+ step('features', () => {
205
+ if (pal.features) { pal.features(c, G, pal); return; }
206
+ if (pal.blink) { c.strokeStyle = L; c.lineWidth = 2.8; c.beginPath(); c.arc(G.eye.x, G.eye.y - 1, 7, 0.25, Math.PI - 0.25); c.stroke(); }
207
+ else {
208
+ S_(G.lid, L, 2.6); G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.strokeStyle = L; c.lineWidth = 2; c.stroke(); });
209
+ c.fillStyle = pal.iris || '#2b4d8a'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1.5, G.eye.r * 0.8, G.eye.r, 0, 0, Math.PI * 2); c.fill();
210
+ c.fillStyle = '#fff'; c.beginPath(); c.arc(G.eye.x - 2.5, G.eye.y - 0.5, 1.6, 0, Math.PI * 2); c.fill();
211
+ }
212
+ S_(G.brow, pal.browC || pal.hairLine || L, 2.4);
213
+ c.fillStyle = pal.lip || '#cf5a58'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
214
+ });
215
+ // 帽
216
+ step('hat', () => { if (G.brim && pal.hat) { F('brim', G.brim, pal.hat); F('crown', G.crown, pal.hat); F('band', G.band, pal.hatBand || '#2d4fa0', false); c.fillStyle = pal.hatBand || '#2d4fa0'; c.beginPath(); c.ellipse(G.bow[0], G.bow[1], 14, 9, 0.6, 0, Math.PI * 2); c.fill(); } });
217
+ // 近侧手臂、杯、手
218
+ F('upperArm', G.upperArm, pal.sleeve || pal.dress); F('foreArm', G.foreArm, pal.sleeve || pal.dress); F('cuff', G.cuff, pal.cuff || pal.apron || '#f3eee2');
219
+ F('hand', G.hand, pal.skin);
220
+ step('cup', () => { if (pal.cup) pal.cup(c, G, pal); else R.drawCup(c, G.cup, { body: pal.cupBody || '#2a4fa8', rim: pal.cupRim || '#5a3216', line: L, lw: lw * 0.8, hw: 7 }); });
221
+ step('thumb', () => { c.fillStyle = pal.skin; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, Math.PI * 2); c.fill(); const w = LWof('thumb', lw * 0.7); if (w) { c.strokeStyle = L; c.lineWidth = w; c.stroke(); } });
222
+ c.restore();
223
+ };
224
+
225
+ // 只画五官(迁移测试 C 的 X.features):「先渲染器、后补五官」的画法用——平涂进笔触/点彩后五官会糊,最后单独补一遍。
226
+ // s: { line, featureW(线宽倍数), iris, brow, lip, cheek, blink }
227
+ R.drawFeatures = (c, G, s = {}) => {
228
+ const L = s.line || '#111', k = s.featureW || 1;
229
+ c.save(); c.lineCap = 'round'; c.lineJoin = 'round';
230
+ if (s.blink) { c.strokeStyle = L; c.lineWidth = 3 * k; c.beginPath(); c.arc(G.eye.x, G.eye.y - 1, 7, 0.25, Math.PI - 0.25); c.stroke(); }
231
+ else {
232
+ c.strokeStyle = L; c.lineWidth = 2.8 * k; c.stroke(G.lid);
233
+ G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.lineWidth = 2 * k; c.stroke(); });
234
+ c.fillStyle = s.iris || '#2b4d8a'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1.5, G.eye.r * 0.8, G.eye.r, 0, 0, Math.PI * 2); c.fill();
235
+ c.fillStyle = '#fff'; c.beginPath(); c.arc(G.eye.x - 2.5, G.eye.y - 0.5, 1.6, 0, Math.PI * 2); c.fill();
236
+ }
237
+ c.strokeStyle = s.brow || L; c.lineWidth = 2.6 * k; c.stroke(G.brow);
238
+ c.fillStyle = s.lip || '#cf5a58'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
239
+ if (s.cheek) { c.save(); c.clip(G.face); c.fillStyle = s.cheek; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 13, 0, Math.PI * 2); c.fill(); c.restore(); }
240
+ c.restore();
241
+ };
242
+
243
+ // 画杯子(通用):c.translate 到杯口中心再画,style: {body, rim, line, handle:true}
244
+ R.drawCup = (c, cup, st = {}) => {
245
+ c.save(); c.translate(cup.x, cup.y); c.rotate(cup.tilt || 0);
246
+ const w = cup.w, h = cup.h;
247
+ c.beginPath(); c.moveTo(-w / 2, 0); c.lineTo(-w * 0.42, h); c.quadraticCurveTo(0, h * 1.12, w * 0.42, h); c.lineTo(w / 2, 0); c.closePath();
248
+ c.fillStyle = st.body || '#2a4fa8'; c.fill();
249
+ if (st.line) { c.strokeStyle = st.line; c.lineWidth = st.lw || 3; c.stroke(); }
250
+ if (st.handle !== false) { c.beginPath(); c.arc(w * 0.55, h * 0.4, h * 0.26, -1.2, 1.4); c.strokeStyle = st.body || '#2a4fa8'; c.lineWidth = st.hw || 7; c.stroke(); }
251
+ c.beginPath(); c.ellipse(0, 0, w / 2, h * 0.14, 0, 0, Math.PI * 2); c.fillStyle = st.rim || '#6b3f1f'; c.fill();
252
+ if (st.line) { c.strokeStyle = st.line; c.lineWidth = st.lw || 3; c.stroke(); }
253
+ c.restore();
254
+ };
255
+
256
+ // ---------------- 猫(橘白,朝右坐) ----------------
257
+ // opt: { tail(弧度), blink, ear, breathe, dx, dy, s, look, sx, sy, anchor }
258
+ // 返回 K:K.body 已平滑(Catmull-Rom,带 .pts;旧折线在 K.bodyPoly);白区拆成 K.chest / K.muzzle / K.paws,
259
+ // K.white = 三者合在一条 Path2D(兼容旧代码),K.whitePts 给点列/椭圆参数;K.pivots / K.rigid 同少女。
260
+ R.cat = (opt = {}) => {
261
+ const { tail = 0, blink = 0, breathe = 0, dx = 0, dy = 0, s = 1, look = 0 } = opt;
262
+ const T = (x, y) => [dx + 540 + (x - 540) * s, dy + 905 + (y - 905) * s];
263
+ const P = {};
264
+ // 身体:梨形
265
+ const body = new Path2D();
266
+ P.bodyPts = [[470, 900], [430, 860], [425, 790], [450, 720], [500, 680], [570, 668], [625, 690], [650, 750], [660, 830], [650, 900]].map(p => T(p[0], p[1] - (p[1] < 800 ? breathe * 300 : 0)));
267
+ P.bodyPts.forEach((p, i) => i ? body.lineTo(...p) : body.moveTo(...p)); body.closePath(); P.bodyPoly = body;
268
+ P.body = Object.assign(R.smooth(P.bodyPts), { pts: P.bodyPts, closed: true }); // 卡通/赛璐珞/不描线的画法里折线身体会露直边(迁移测试 B、D)
269
+ // 头
270
+ const hc = T(600 + look * 6, 625); P.headC = hc; P.headR = 64 * s;
271
+ const head = new Path2D(); head.ellipse(hc[0], hc[1], 66 * s, 58 * s, 0, 0, Math.PI * 2);
272
+ // 耳朵
273
+ const ear = (bx, by, tx, ty, ex, ey) => { head.moveTo(...T(bx, by)); head.lineTo(...T(tx, ty)); head.lineTo(...T(ex, ey)); head.closePath(); };
274
+ ear(555, 590, 560, 535, 595, 575); ear(615, 575, 652, 540, 650, 600);
275
+ P.head = head;
276
+ P.earInner = [[T(563, 585), T(565, 550), T(588, 578)], [T(622, 578), T(646, 553), T(644, 596)]];
277
+ // 白色部分:口鼻、胸口、爪子(橘白猫的标志)。胸口用平滑闭合曲线:旧版直线多边形在不描线的画法里像一块「盾牌」(迁移测试 B)
278
+ const mz = T(628, 650), chestPts = [[560, 700], [610, 690], [640, 740], [630, 820], [590, 860], [560, 800]].map(p => T(...p));
279
+ P.chest = Object.assign(R.smooth(chestPts), { pts: chestPts, closed: true });
280
+ P.muzzle = new Path2D(); P.muzzle.ellipse(mz[0], mz[1], 34 * s, 24 * s, 0, 0, Math.PI * 2);
281
+ P.paws = new Path2D(); const pawC = [585, 635].map(x => T(x, 893));
282
+ pawC.forEach(c0 => { P.paws.moveTo(c0[0] + 22 * s, c0[1]); P.paws.ellipse(c0[0], c0[1], 22 * s, 13 * s, 0, 0, Math.PI * 2); });
283
+ const white = new Path2D(); white.addPath(P.muzzle); white.addPath(P.chest); white.addPath(P.paws);
284
+ P.white = white;
285
+ P.whitePts = { chest: chestPts, muzzle: { x: mz[0], y: mz[1], rx: 34 * s, ry: 24 * s }, paws: pawC.map(c0 => ({ x: c0[0], y: c0[1], rx: 22 * s, ry: 13 * s })) };
286
+ // 前腿
287
+ P.legs = [R.limb(T(588, 740), T(584, 888), 34 * s, 28 * s), R.limb(T(630, 742), T(634, 888), 34 * s, 28 * s)];
288
+ // 眼睛(朝右看)、鼻、嘴、胡须
289
+ P.eyes = [{ x: T(612, 615)[0], y: T(612, 615)[1] }, { x: T(648, 612)[0], y: T(648, 612)[1] }]; P.eyeR = 9 * s; P.blink = blink;
290
+ P.nose = T(655, 640); P.mouth = [T(645, 655), T(655, 650), T(664, 655)];
291
+ P.whiskers = [[T(660, 645), T(712, 632)], [T(660, 650), T(715, 655)], [T(598, 648), T(552, 640)]];
292
+ // 虎斑条纹(头顶、背)
293
+ P.stripes = [[T(585, 572), T(590, 595)], [T(605, 568), T(607, 592)], [T(625, 572), T(622, 594)],
294
+ [T(452, 740), T(490, 750)], [T(440, 780), T(482, 790)], [T(436, 822), T(478, 830)], [T(500, 690), T(520, 715)]];
295
+ // 尾巴:从臀部绕到身前,末端随 tail 摆
296
+ const t0 = T(455, 885), tip = T(380 + Math.sin(tail) * 40, 830 - Math.cos(tail) * 30);
297
+ P.tailPts = P_bez(t0, T(360, 900), T(330, 860 + tail * 40), tip);
298
+ P.tail = (() => { const p = new Path2D(); const pts = P.tailPts; pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); return p; })();
299
+ P.tailW = 26 * s;
300
+ P.box = [T(390, 540), T(700, 905)];
301
+ P.pivots = { neck: T(588 + look * 6, 680), tailBase: t0, shoulder: T(600, 735), hip: T(470, 870) };
302
+ P.rigid = { head: ['head', 'earInner', 'eyes', 'nose', 'mouth', 'whiskers', 'muzzle'], body: ['body', 'chest', 'stripes'], legs: ['legs', 'paws'], tail: ['tail'] };
303
+ P.rigidPivot = { head: 'neck', body: 'hip', legs: 'shoulder', tail: 'tailBase' };
304
+ if (opt.sx || opt.sy) return R.stretch(P, opt.sx || 1, opt.sy || 1, opt.anchor || [540, 905]);
305
+ return P;
306
+ };
307
+ function P_bez(p0, p1, p2, p3, n = 20) { const o = []; for (let i = 0; i <= n; i++) { const t = i / n, u = 1 - t; o.push([u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0], u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]]); } return o; }
308
+
309
+ // 猫只描线(给「先渲染器、后补线」的时代用)
310
+ R.drawCatLines = (c, K, pal) => {
311
+ const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3; c.save(); c.lineJoin = 'round'; c.lineCap = 'round'; c.strokeStyle = L; c.lineWidth = lw;
312
+ c.stroke(K.body); c.stroke(K.head); K.legs.forEach(l => c.stroke(l));
313
+ K.eyes.forEach(e => { if (K.blink) { c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0.2, Math.PI - 0.2); c.stroke(); } else { c.fillStyle = pal.eye || '#6aa84f'; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0, Math.PI * 2); c.fill(); c.stroke(); c.fillStyle = L; c.beginPath(); c.ellipse(e.x + 2, e.y, K.eyeR * 0.35, K.eyeR * 0.8, 0, 0, Math.PI * 2); c.fill(); } });
314
+ c.fillStyle = pal.noseC || '#d8746a'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 6, 0, Math.PI * 2); c.fill();
315
+ c.lineWidth = 2.5; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke();
316
+ c.lineWidth = 1.5; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
317
+ c.restore();
318
+ };
319
+ // 只取「看得见的线」:同一个完整画法(填色+描线,按部件顺序所以遮挡正确)画两遍,一遍用真线色、一遍用白色当哨兵;
320
+ // 两遍不同的像素 = 没被遮住的线(含用线色画的眼睑、眉、胡须、嘴)。取第一遍的颜色输出。
321
+ // 用于「平涂→风格肌理→补线」:直接 mode:'line' 叠最上层会把被遮住的轮廓(远侧袖子、帽檐后半圈)画穿。
322
+ // drawWith(g, lineColor) 由调用方提供,box=[x0,y0,x1,y1] 限定计算范围。
323
+ R._lc = {};
324
+ R.visibleLines = (key, drawWith, lineColor, box, thresh = 90) => {
325
+ const [x0, y0, x1, y1] = box.map(Math.round), w = x1 - x0, h = y1 - y0;
326
+ const mk = n => R._lc[key + n] || (R._lc[key + n] = (() => { const c = document.createElement('canvas'); c.width = window.STAGE.W; c.height = window.STAGE.H; return c; })());
327
+ const A = mk('a'), B = mk('b'), O = mk('o');
328
+ const ga = A.getContext('2d', { willReadFrequently: true }), gb = B.getContext('2d', { willReadFrequently: true }), go = O.getContext('2d');
329
+ ga.clearRect(0, 0, ga.canvas.width, ga.canvas.height); gb.clearRect(0, 0, gb.canvas.width, gb.canvas.height); go.clearRect(0, 0, go.canvas.width, go.canvas.height);
330
+ drawWith(ga, lineColor); drawWith(gb, '#ffffff');
331
+ const da = ga.getImageData(x0, y0, w, h), db = gb.getImageData(x0, y0, w, h).data, d = da.data;
332
+ for (let i = 0; i < d.length; i += 4) {
333
+ const diff = Math.abs(d[i] - db[i]) + Math.abs(d[i + 1] - db[i + 1]) + Math.abs(d[i + 2] - db[i + 2]);
334
+ if (diff < thresh) d[i + 3] = 0; else d[i + 3] = Math.min(255, d[i + 3] * Math.min(1, diff / 300));
335
+ }
336
+ go.putImageData(da, x0, y0); return O;
337
+ };
338
+ // 一站式默认画法见上方 R.drawGirl;猫:
339
+ // pal:orange white stripe stripeW eye earInner noseC(=nose) whisker line lw lwOf mode('line' 走 visibleLines) lineKey
340
+ // 分层:before/after/hooks(别名 back/body/head/end);paint(c, path, color, name, stroke) 接管部件填描;
341
+ // face(c, K, pal) 接管眼鼻嘴须;tail(c, K, pal) 接管尾巴;noStripes 不画条纹。
342
+ // 部件画序:tail body legs white head muzzleWhite earInner stripes eyes nose mouth whiskers
343
+ R.drawCat = (c, K, pal) => {
344
+ const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3;
345
+ if (pal.mode === 'line') { const [a, b] = K.box, box = [a[0] - 60, a[1] - 40, b[0] + 70, b[1] + 30];
346
+ c.drawImage(R.visibleLines(pal.lineKey || 'cat', (g, lc) => R.drawCat(g, K, { ...pal, mode: 'both', line: lc, whisker: lc }), pal.line || '#1b1d3a', box), 0, 0); return; }
347
+ const step = hooker(c, K, pal, CAT_SLOTS), O = pal.orange || '#e8913a', Wt = pal.white || '#fff6ea';
348
+ const LWof = name => pal.lwOf && pal.lwOf[name] != null ? pal.lwOf[name] : lw;
349
+ const F = (name, p, col, stroke = true) => { if (pal.paint) { pal.paint(c, p, col, name, stroke); return; } c.fillStyle = col; c.fill(p); const w = LWof(name); if (w && stroke) { c.strokeStyle = L; c.lineWidth = w; c.stroke(p); } };
350
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
351
+ // 尾巴
352
+ step('tail', () => { if (pal.tail) { pal.tail(c, K, pal); return; }
353
+ c.strokeStyle = L; c.lineWidth = K.tailW + lw * 2; c.stroke(K.tail); c.strokeStyle = O; c.lineWidth = K.tailW; c.stroke(K.tail);
354
+ const tip = K.tailPts[K.tailPts.length - 1]; c.fillStyle = Wt; c.beginPath(); c.arc(tip[0], tip[1], K.tailW / 2, 0, Math.PI * 2); c.fill(); });
355
+ // 身体、腿、头
356
+ step('body', () => F('body', K.body, O));
357
+ step('legs', () => K.legs.forEach(l => F('leg', l, Wt)));
358
+ step('white', () => F('white', K.white, Wt, false));
359
+ step('head', () => F('head', K.head, O));
360
+ step('muzzleWhite', () => { c.save(); c.clip(K.head); c.fillStyle = Wt; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, Math.PI * 2); c.fill(); c.restore(); });
361
+ step('earInner', () => { c.fillStyle = pal.earInner || '#f2a7a0'; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); }); });
362
+ // 条纹
363
+ step('stripes', () => { if (pal.noStripes) return; c.strokeStyle = pal.stripe || '#c0621a'; c.lineWidth = pal.stripeW || 7; K.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); });
364
+ if (pal.face) step('face', () => pal.face(c, K, pal));
365
+ else {
366
+ // 眼
367
+ step('eyes', () => K.eyes.forEach(e => { if (K.blink) { c.strokeStyle = L; c.lineWidth = 3; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0.2, Math.PI - 0.2); c.stroke(); }
368
+ else { c.fillStyle = pal.eye || '#6aa84f'; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0, Math.PI * 2); c.fill(); c.fillStyle = L; c.beginPath(); c.ellipse(e.x + 2, e.y, K.eyeR * 0.35, K.eyeR * 0.8, 0, 0, Math.PI * 2); c.fill(); } }));
369
+ step('nose', () => { c.fillStyle = pal.noseC || pal.nose || '#d8746a'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 6, 0, Math.PI * 2); c.fill(); });
370
+ step('mouth', () => { c.strokeStyle = L; c.lineWidth = 2.5; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke(); });
371
+ step('whiskers', () => { c.lineWidth = 1.5; c.strokeStyle = pal.whisker || L; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); }); });
372
+ }
373
+ c.restore();
374
+ };
375
+ // 刚体姿态(皮影、剪纸偶、提线木偶):把 rigid 分组里的部件绕各自枢轴旋转。angles = {组名: 弧度},
376
+ // 返回新对象(Path2D 与点都转好),子组不自动跟随父组——需要链式时自己把父组的角度加到子组上(如 foreArm += upperArm)。
377
+ R.rigidPose = (O, angles) => {
378
+ const out = { ...O };
379
+ for (const grp in angles) {
380
+ const a = angles[grp], piv = O.pivots[O.rigidPivot[grp]]; if (!a || !piv) continue;
381
+ const m = new DOMMatrix().translate(piv[0], piv[1]).rotate(a * 180 / Math.PI).translate(-piv[0], -piv[1]);
382
+ const mp = q => { const r = m.transformPoint(new DOMPoint(q[0], q[1])); return [r.x, r.y]; };
383
+ const map = v => { if (v instanceof Path2D) { const p = new Path2D(); p.addPath(v, m); if (v.pts) { p.pts = v.pts.map(mp); p.closed = v.closed; } return p; }
384
+ if (Array.isArray(v) && v.length === 2 && typeof v[0] === 'number') return mp(v); if (Array.isArray(v)) return v.map(map);
385
+ if (v && typeof v === 'object' && 'x' in v) { const q = mp([v.x, v.y]); return { ...v, x: q[0], y: q[1] }; } return v; };
386
+ O.rigid[grp].forEach(name => { if (out[name] != null) out[name] = map(out[name]); });
387
+ }
388
+ return out;
389
+ };
390
+ })();
@@ -0,0 +1,171 @@
1
+ // 花叔卡通形象骨架(侧面,FK 关节)。来源:花叔形象的 superflat 三视图设定稿(未随仓库附带),侧视图叠网格读点。
2
+ // 角色定规:纯白渔夫帽、圆框黑眼镜、白 T、卡其短裤、白洞洞鞋、近侧手腕黑手表、大头小身体(头≈全身 38%)。
3
+ // 坐标:本地单位 1 = 设定图侧视 1px;原点 = 双脚之间的地面;y 向上为负;原生朝左(-x 为前)。全身高 740 单位。
4
+ // 用法:
5
+ // const H = HUASHU.build({ x, y, s: 0.6, face: 1 /*1朝右 -1朝左*/, pose: HUASHU.pose('walk', phase) });
6
+ // HUASHU.draw(c, H, pal) // 默认扁平画法;pal.mode: 'fill' | 'line' | 'both'
7
+ // H.parts = [{ name, role, path, z }] // 按画序;role ∈ skin/shirt/shorts/hat/hair/shoe/watch/glass/line
8
+ // H.J = { hip, knee1, ankle1, toe1, knee2, ..., shoulder1, elbow1, wrist1, ..., neck, head, hatTop, mouth, eye }
9
+ // 角度约定:0 = 自然下垂;正 = 往脸朝的方向(前)转;大头小身体手够不到头顶:举手=近侧手臂向前上(~1.9)、远侧向后上(~-1.9),侧视里成 V 字,不要直上。1 = 近侧肢体(画在最前),2 = 远侧。
10
+ (() => {
11
+ const HS = window.HUASHU = {};
12
+ const { lerp, clamp } = U;
13
+ const D = (a, b, c, d, e, f) => new DOMMatrix([a, b, c, d, e, f]);
14
+ const P2 = (pts, closed = true) => (RIG && RIG.smooth) ? RIG.smooth(pts, closed) : (() => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); if (closed) p.closePath(); return p; })();
15
+ const add = (dst, path, m) => { dst.addPath(path, m); return dst; };
16
+ const tf = (m, x, y) => { const p = m.transformPoint(new DOMPoint(x, y)); return [p.x, p.y]; };
17
+
18
+ // ---------- 局部形状(各自骨骼坐标系,原点 = 该骨骼起点关节,沿 +y 向下) ----------
19
+ const SHAPE = {
20
+ // 头:原点 = 颈点;脸朝 -x
21
+ face: P2([[-30, -158], [-78, -150], [-108, -128], [-122, -98], [-124, -66], [-118, -38], [-102, -14], [-80, 2], [-50, 8], [-18, 2], [8, -14], [22, -40], [24, -78], [10, -120]]),
22
+ hairBack: P2([[-70, -168], [-10, -176], [56, -168], [102, -140], [118, -96], [112, -58], [86, -30], [52, -18], [26, -28], [20, -62], [8, -110], [-30, -140], [-62, -140]]),
23
+ bangs: P2([[-122, -120], [-110, -146], [-76, -164], [-34, -166], [-10, -150], [-28, -138], [-52, -132], [-70, -118], [-86, -132], [-102, -116]]),
24
+ ear: (() => { const p = new Path2D(); p.ellipse(16, -66, 17, 22, 0.15, 0, Math.PI * 2); return p; })(),
25
+ crown: P2([[-100, -170], [-94, -232], [-64, -262], [-4, -274], [62, -266], [100, -236], [116, -180], [124, -110], [64, -118], [-10, -150], [-60, -166]]),
26
+ brim: P2([[-160, -166], [-112, -186], [-30, -190], [52, -164], [116, -112], [134, -62], [116, -52], [64, -86], [-16, -128], [-100, -150], [-156, -150]]),
27
+ band: P2([[-100, -186], [-30, -196], [60, -176], [118, -128], [120, -112], [60, -158], [-30, -180], [-100, -172]]),
28
+ lens: (() => { const p = new Path2D(); p.roundRect(-128, -118, 44, 62, 16); return p; })(),
29
+ // 躯干(T 恤):原点 = 胯;肩约 (0,-250)
30
+ shirt: P2([[-18, -272], [-40, -258], [-60, -200], [-72, -120], [-82, -42], [-84, -32], [74, -34], [72, -40], [70, -140], [62, -230], [44, -262], [14, -274]]),
31
+ collar: P2([[-18, -272], [0, -262], [14, -274]], false),
32
+ shorts: P2([[-74, -44], [76, -42], [80, 18], [70, 44], [-70, 46], [-78, 16]]),
33
+ // 大腿裤管:原点 = 胯侧关节;长 62
34
+ thigh: P2([[-40, -6], [38, -6], [42, 58], [-40, 62]]),
35
+ // 小腿:原点 = 膝;长 66
36
+ shin: P2([[-24, 0], [22, 0], [21, 66], [-21, 66]]),
37
+ // 洞洞鞋:原点 = 踝;鞋尖朝 -x
38
+ shoe: P2([[-104, 26], [-100, 6], [-80, -4], [-50, -8], [-20, -12], [18, -10], [46, -2], [58, 18], [56, 46], [40, 60], [-60, 62], [-96, 54], [-106, 40]]),
39
+ // 袖子:原点 = 肩;长 112
40
+ sleeve: P2([[-40, -10], [36, -14], [44, 100], [-34, 112]]),
41
+ upper: P2([[-19, 0], [19, 0], [18, 126], [-18, 126]]),
42
+ fore: P2([[-18, 0], [18, 0], [16, 74], [-16, 74]]),
43
+ hand: (() => { const p = new Path2D(); p.ellipse(0, 18, 22, 25, 0, 0, Math.PI * 2); return p; })(),
44
+ watch: (() => { const p = new Path2D(); p.roundRect(-24, 50, 48, 22, 6); return p; })(),
45
+ watchFace: (() => { const p = new Path2D(); p.roundRect(-16, 46, 32, 30, 7); return p; })(),
46
+ };
47
+ const SHOE_HOLES = [[-80, 18], [-58, 12], [-34, 10], [-10, 12], [-70, 32], [-46, 30], [-22, 30]];
48
+
49
+ // ---------- 姿势库 ----------
50
+ // 每个姿势返回关节角(弧度)与根偏移。phase 用于循环动作(0..1)。
51
+ HS.pose = (name, ph = 0, k = {}) => {
52
+ const s = Math.sin(2 * Math.PI * ph), c = Math.cos(2 * Math.PI * ph);
53
+ const P = { lean: 0, head: 0, bob: 0, dx: 0, th1: 0, sh1: 0, ft1: 0, th2: 0, sh2: 0, ft2: 0, ua1: 0, fa1: 0, ua2: 0, fa2: 0, pocket1: 0, pocket2: 0, mouth: 0.3, blink: 0 };
54
+ switch (name) {
55
+ case 'idle': Object.assign(P, { pocket1: 1, pocket2: 1, ua1: -0.05, fa1: 0.25, ua2: -0.05, fa2: 0.25, bob: 2 * s, head: 0.03 * s }); break;
56
+ case 'walk': Object.assign(P, { lean: 0.05, bob: -6 * Math.abs(c), th1: 0.42 * s, sh1: -0.55 * Math.max(0, -s) - 0.15, ft1: 0.1 * s, th2: -0.42 * s, sh2: -0.55 * Math.max(0, s) - 0.15, ft2: -0.1 * s,
57
+ ua1: -0.45 * s, fa1: 0.35, ua2: 0.45 * s, fa2: 0.35, head: 0.03 * c }); break;
58
+ case 'run': Object.assign(P, { lean: 0.22, bob: -14 * Math.abs(c), th1: 0.8 * s + 0.1, sh1: -1.1 * Math.max(0, -s) - 0.3, th2: -0.8 * s + 0.1, sh2: -1.1 * Math.max(0, s) - 0.3,
59
+ ua1: -0.9 * s, fa1: 1.4, ua2: 0.9 * s, fa2: 1.4, head: -0.06, mouth: 0.6 }); break;
60
+ case 'crouch': Object.assign(P, { lean: 0.35, bob: 52, th1: 1.25, sh1: -1.9, ft1: 0.5, th2: 1.05, sh2: -1.8, ft2: 0.6, ua1: 0.5, fa1: 0.4, ua2: 0.3, fa2: 0.5, head: -0.2 }); break;
61
+ case 'jump': Object.assign(P, { lean: 0.05, th1: 0.7, sh1: -1.2, th2: 0.2, sh2: -0.6, ua1: 1.95, fa1: 0.7, ua2: -1.95, fa2: -0.7, head: -0.15, mouth: 1 }); break;
62
+ case 'wave': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 2.15, fa1: 0.15 + 0.5 * (0.5 + 0.5 * s), head: 0.08, mouth: 0.8, bob: 2 * s }); break;
63
+ case 'sit': Object.assign(P, { bob: 62, th1: 1.55, sh1: -1.55, th2: 1.5, sh2: -1.5, ua1: 0.5, fa1: 0.9, ua2: 0.4, fa2: 0.9, lean: -0.05 }); break;
64
+ case 'point': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 1.45, fa1: 0.05, head: -0.05, mouth: 0.7 }); break;
65
+ case 'think': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 0.6, fa1: 2.3, head: -0.1, mouth: 0.1 }); break;
66
+ case 'cheer': Object.assign(P, { ua1: 2.05, fa1: 0.55, ua2: -2.05, fa2: -0.55, bob: -10 * Math.abs(s), head: -0.12, mouth: 1 }); break;
67
+ // 侧身甩臂扔(打水漂):ph 0→1 = 蓄力 → 出手 → 随挥
68
+ case 'throw': { const w = ph < 0.45 ? ph / 0.45 : 1, r = ph < 0.45 ? 0 : clamp((ph - 0.45) / 0.25), f = clamp((ph - 0.7) / 0.3);
69
+ Object.assign(P, { lean: lerp(lerp(0, -0.12, w), 0.32, r), bob: lerp(0, 34, w) - 10 * r, th1: lerp(0, 0.65, w) - 0.3 * r, sh1: lerp(0, -0.9, w) + 0.4 * r, th2: lerp(0, -0.35, w) + 0.25 * r, sh2: lerp(-0.1, -0.5, w),
70
+ ua1: lerp(lerp(0, -1.3, w), 1.1, r) + 0.3 * f, fa1: lerp(lerp(0.3, 0.9, w), 0.15, r), ua2: lerp(0, 0.9, w) - 0.5 * r, fa2: 0.6, head: lerp(0, -0.05, w), mouth: 0.5 + 0.5 * r }); break; }
71
+ }
72
+ return Object.assign(P, k);
73
+ };
74
+
75
+ // ---------- 组装 ----------
76
+ // o: { x, y, s, face, pose, squash } squash = 竖向挤压(落地 0.85、起跳 1.1)
77
+ HS.build = (o = {}) => {
78
+ const { x = 960, y = 900, s = 0.6, face = 1, pose = HS.pose('idle'), squash = 1, rot = 0 } = o;
79
+ const P = pose;
80
+ // 根:先挪到世界坐标,按朝向镜像(原生朝左,face=1 朝右要翻),挤压以脚底为轴
81
+ const root = new DOMMatrix().translate(x, y).rotate(rot * 180 / Math.PI).scale(-face * s, s * squash);
82
+ const hip = root.translate(P.dx, -185 + P.bob).rotate(-P.lean * 180 / Math.PI); // lean>0 往前倾;本地前=-x,rotate 负角让上身朝 -x 倒
83
+ const deg = a => a * 180 / Math.PI;
84
+ const parts = [], J = {};
85
+ const push = (name, role, shape, m, z) => parts.push({ name, role, path: add(new Path2D(), shape, m), z, m });
86
+ const leg = (i, th, sh, ft, zBase) => {
87
+ const hipJ = hip.translate(i === 1 ? -8 : 10, 0);
88
+ const mT = hipJ.rotate(deg(th)); // 大腿往前(-x):正角 rotate → (0,1) 转向 -x ✓
89
+ const knee = mT.translate(0, 60), mS = knee.rotate(deg(sh)), ankle = mS.translate(0, 64), mF = ankle.rotate(deg(ft));
90
+ push('thigh' + i, 'shorts', SHAPE.thigh, mT, zBase + 2);
91
+ push('shin' + i, 'skin', SHAPE.shin, mS, zBase);
92
+ push('shoe' + i, 'shoe', SHAPE.shoe, mF, zBase + 1);
93
+ J['hip' + i] = tf(hipJ, 0, 0); J['knee' + i] = tf(knee, 0, 0); J['ankle' + i] = tf(ankle, 0, 0); J['toe' + i] = tf(mF, -104, 40); J['heel' + i] = tf(mF, 56, 46);
94
+ J['shoeM' + i] = mF;
95
+ };
96
+ const arm = (i, ua, fa, pocket, zBase) => {
97
+ const sh = hip.translate(i === 1 ? -4 : 14, -250);
98
+ const mU = sh.rotate(deg(ua)), elbow = mU.translate(0, 124), mFo = elbow.rotate(deg(fa)), wrist = mFo.translate(0, 70);
99
+ push('sleeve' + i, 'shirt', SHAPE.sleeve, mU, zBase + 3);
100
+ push('upper' + i, 'skin', SHAPE.upper, mU, zBase);
101
+ if (!pocket) { push('fore' + i, 'skin', SHAPE.fore, mFo, zBase + 1); push('hand' + i, 'skin', SHAPE.hand, wrist, zBase + 2); }
102
+ else push('fore' + i, 'skin', SHAPE.fore, mFo, zBase - 30); // 插兜:前臂藏到裤子后面
103
+ if (i === 1) { push('watch', 'watch', SHAPE.watch, mFo, zBase + (pocket ? -29 : 2)); push('watchFace', 'watch', SHAPE.watchFace, mFo, zBase + (pocket ? -28 : 3)); }
104
+ J['shoulder' + i] = tf(sh, 0, 0); J['elbow' + i] = tf(elbow, 0, 0); J['wrist' + i] = tf(wrist, 0, 0); J['hand' + i] = tf(wrist, 0, 18); J['handM' + i] = wrist;
105
+ };
106
+ // 远侧在后
107
+ leg(2, P.th2, P.sh2, P.ft2, 0);
108
+ arm(2, P.ua2, P.fa2, P.pocket2, 10);
109
+ push('shorts', 'shorts', SHAPE.shorts, hip, 40);
110
+ push('shirt', 'shirt', SHAPE.shirt, hip, 50);
111
+ leg(1, P.th1, P.sh1, P.ft1, 60);
112
+ push('shortsFront', 'shorts', SHAPE.shorts, hip, 64);
113
+ push('shirtFront', 'shirt', SHAPE.shirt, hip, 66);
114
+ // 头
115
+ const neck = hip.translate(6, -272), mH = neck.rotate(deg(-P.head));
116
+ push('hairBack', 'hair', SHAPE.hairBack, mH, 80);
117
+ push('face', 'skin', SHAPE.face, mH, 81);
118
+ push('ear', 'skin', SHAPE.ear, mH, 82);
119
+ push('bangs', 'hair', SHAPE.bangs, mH, 83);
120
+ push('crown', 'hat', SHAPE.crown, mH, 85);
121
+ push('band', 'hatBand', SHAPE.band, mH, 86);
122
+ push('brim', 'hat', SHAPE.brim, mH, 87);
123
+ arm(1, P.ua1, P.fa1, P.pocket1, 70);
124
+ parts.sort((a, b) => a.z - b.z);
125
+ J.hip = tf(hip, 0, 0); J.neck = tf(neck, 0, 0); J.head = tf(mH, -40, -80); J.hatTop = tf(mH, 0, -275); J.mouth = tf(mH, -104, -34); J.eye = tf(mH, -104, -88);
126
+ J.chest = tf(hip, -40, -180); J.face = face; J.s = s;
127
+ return { parts, J, mH, root, hip, P, s, face };
128
+ };
129
+
130
+ // 五官与细节线(在头部坐标里画)
131
+ HS.details = (c, H, pal) => {
132
+ const L = pal.line || '#151515', lw = (pal.lw ?? 5);
133
+ c.save(); c.setTransform(c.getTransform().multiply(H.mH)); c.lineCap = 'round'; c.lineJoin = 'round';
134
+ // 眼镜:前镜片+镜腿;镜片里一只黑眼睛
135
+ c.fillStyle = pal.lensFill || 'rgba(255,255,255,0.0)'; c.fill(SHAPE.lens);
136
+ if (!H.P.blink) { c.fillStyle = pal.eye || L; c.beginPath(); c.ellipse(-104, -86, 9, 13, 0, 0, Math.PI * 2); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(-107, -91, 3, 0, Math.PI * 2); c.fill(); }
137
+ else { c.strokeStyle = pal.eye || L; c.lineWidth = lw * 0.8; c.beginPath(); c.arc(-104, -90, 9, 0.2, Math.PI - 0.2); c.stroke(); }
138
+ c.strokeStyle = pal.glass || L; c.lineWidth = lw * 1.6; c.stroke(SHAPE.lens);
139
+ c.lineWidth = lw * 1.3; c.beginPath(); c.moveTo(-84, -96); c.lineTo(8, -84); c.stroke();
140
+ // 嘴:mouth 0 闭嘴微笑 → 1 张嘴
141
+ const m = H.P.mouth;
142
+ c.strokeStyle = L; c.lineWidth = lw * 0.8;
143
+ if (m < 0.5) { c.beginPath(); c.moveTo(-112, -40); c.quadraticCurveTo(-100, -28 - 8 * m, -88, -38); c.stroke(); }
144
+ else { c.fillStyle = pal.mouthFill || '#7a2e2e'; c.beginPath(); c.ellipse(-102, -34, 9, 6 + 6 * m, 0, 0, Math.PI * 2); c.fill(); c.stroke(); }
145
+ // 腮红、耳廓
146
+ if (pal.cheek) { c.fillStyle = pal.cheek; c.beginPath(); c.ellipse(-70, -48, 14, 9, 0, 0, Math.PI * 2); c.fill(); }
147
+ c.strokeStyle = pal.earLine || 'rgba(190,110,90,.8)'; c.lineWidth = lw * 0.6; c.beginPath(); c.arc(16, -66, 8, -1.2, 1.6); c.stroke();
148
+ c.restore();
149
+ // 鞋洞
150
+ for (const i of [1, 2]) { const m2 = H.J['shoeM' + i]; c.save(); c.setTransform(c.getTransform().multiply(m2)); c.fillStyle = pal.shoeHole || '#2a2a2a'; SHOE_HOLES.forEach(([x, y]) => { c.beginPath(); c.ellipse(x, y, 4.5, 3.5, 0, 0, Math.PI * 2); c.fill(); }); c.strokeStyle = pal.shoeHole || '#2a2a2a'; c.lineWidth = 3; c.beginPath(); c.arc(40, 24, 9, 0, Math.PI * 2); c.stroke(); c.restore(); }
151
+ };
152
+
153
+ HS.PAL = { skin: '#fbd9bf', hair: '#151515', shirt: '#ffffff', shorts: '#d8cdb8', hat: '#ffffff', hatBand: '#e9e9e9', shoe: '#fbfaf6', watch: '#1d1f22', line: '#151515', lw: 5, cheek: 'rgba(245,140,120,.35)' };
154
+
155
+ // 默认画法:逐部件「填→描」,天然只剩看得见的线(遮挡正确)
156
+ HS.draw = (c, H, pal = HS.PAL) => {
157
+ pal = { ...HS.PAL, ...pal };
158
+ const mode = pal.mode || 'both', L = pal.line, lw = pal.lw / H.s * H.s; // 线宽按世界像素
159
+ c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
160
+ for (const p of H.parts) {
161
+ const col = pal[p.role] ?? pal.skin;
162
+ if (mode !== 'line') { c.fillStyle = col; c.fill(p.path); }
163
+ if (mode !== 'fill' && lw) {
164
+ if (mode === 'line') { c.save(); c.globalCompositeOperation = 'destination-out'; c.fill(p.path); c.restore(); } // 线稿模式:后画的部件先挖掉前面的线
165
+ c.strokeStyle = L; c.lineWidth = lw; c.stroke(p.path);
166
+ }
167
+ }
168
+ HS.details(c, H, pal);
169
+ c.restore();
170
+ };
171
+ })();