@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,57 @@
1
+ // 全片主题色板:spec.theme 写名字(或 {name, 覆盖的键} / 完整 6 键对象),clip.js 解析成 ctx.pal,8 个语法都按它上色——
2
+ // 一支片子由几种语法拼起来时,底色、字色、强调色是同一套,不再各带各的写死配色。
3
+ // 每套 6 个键:bg 底色 · surface 卡片/纸/面板 · ink 主文字 · sub 次要文字与线 · accent 强调 · accent2 第二强调。
4
+ // 其余辅助色一律从这 6 个派生(PAL.mix 调明暗、PAL.alpha 调透明度),不引入色板外的色相。
5
+ (() => {
6
+ window.PALETTES = {
7
+ paper: { bg: '#F3EDE2', surface: '#FBF8F2', ink: '#1B1A17', sub: '#6B655B', accent: '#C8402F', accent2: '#2F5D62' }, // 纸本
8
+ poster: { bg: '#FFD23F', surface: '#FFF6D6', ink: '#111111', sub: '#4A4433', accent: '#FF5A36', accent2: '#14213D' }, // 海报黄
9
+ ink: { bg: '#121417', surface: '#1E2226', ink: '#F2EFE8', sub: '#A39E93', accent: '#F2B33D', accent2: '#5FB3A1' }, // 深墨
10
+ navy: { bg: '#0D1B2A', surface: '#16293D', ink: '#EEF3F6', sub: '#8FA3B3', accent: '#5CC8E0', accent2: '#F2A93B' }, // 海军(平涂,不渐变)
11
+ bauhaus: { bg: '#EFE9DD', surface: '#FFFFFF', ink: '#111111', sub: '#555555', accent: '#D62828', accent2: '#1D4E89' }, // 包豪斯
12
+ snow: { bg: '#E9EEF0', surface: '#FFFFFF', ink: '#1F2A30', sub: '#5E6B72', accent: '#D9482B', accent2: '#3E7C8C' }, // 雪原
13
+ wood: { bg: '#EDE3D1', surface: '#F7F1E6', ink: '#2B2622', sub: '#7A6E62', accent: '#C4552D', accent2: '#3F5B4B' }, // 暖木
14
+ chalk: { bg: '#23302B', surface: '#2E3D37', ink: '#F1EFE6', sub: '#A9B3AA', accent: '#F4D35E', accent2: '#E87D5A' }, // 黑板
15
+ };
16
+ const KEYS = ['bg', 'surface', 'ink', 'sub', 'accent', 'accent2'];
17
+ const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
18
+ const rgb = h => { h = String(h).trim().replace('#', ''); if (h.length === 3) h = [...h].map(x => x + x).join(''); const n = parseInt(h, 16); return [n >> 16 & 255, n >> 8 & 255, n & 255]; };
19
+ const hex = v => '#' + v.map(x => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, '0')).join('');
20
+ const lin = x => { x /= 255; return x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); };
21
+ const PAL = window.PAL = {
22
+ KEYS,
23
+ names: () => Object.keys(window.PALETTES),
24
+ rgb, hex,
25
+ mix: (a, b, t) => { const x = rgb(a), y = rgb(b); return hex(x.map((v, i) => v + (y[i] - v) * t)); }, // a→b 走 t(0..1),返回 hex
26
+ alpha: (c, a) => { const v = rgb(c); return `rgba(${v[0]},${v[1]},${v[2]},${a})`; },
27
+ lum: c => { const [r, g, b] = rgb(c).map(lin); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }, // WCAG 相对亮度
28
+ contrast: (a, b) => { const x = PAL.lum(a), y = PAL.lum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); },
29
+ dark: p => PAL.lum(p.bg) < 0.2, // 深底色板(ink / navy / chalk)
30
+ on: (bg, cands) => cands.reduce((best, c) => PAL.contrast(bg, c) > PAL.contrast(bg, best) ? c : best), // 压在 bg 上最清楚的那个字色
31
+ // spec.theme → 完整 6 键(多一个 name)。null / {} → null(各语法用自己的默认)。写错 → 抛错,clip.js 拒绝启动
32
+ resolve(theme) {
33
+ if (theme == null || (typeof theme === 'object' && !Array.isArray(theme) && !Object.keys(theme).length)) return null;
34
+ const avail = `可用:${PAL.names().join(' / ')}`;
35
+ let out;
36
+ if (typeof theme === 'string') {
37
+ if (!window.PALETTES[theme]) throw new Error(`spec.theme「${theme}」不是色板名。${avail}`);
38
+ out = { name: theme, ...window.PALETTES[theme] };
39
+ } else if (typeof theme === 'object' && !Array.isArray(theme)) {
40
+ const unknown = Object.keys(theme).filter(k => k !== 'name' && !KEYS.includes(k));
41
+ if (unknown.length) throw new Error(`spec.theme 里有不认识的键:${unknown.join(' ')}(只认 name 和 ${KEYS.join(' ')})`);
42
+ if (theme.name != null) {
43
+ if (!window.PALETTES[theme.name]) throw new Error(`spec.theme.name「${theme.name}」不是色板名。${avail}`);
44
+ out = { name: theme.name, ...window.PALETTES[theme.name], ...theme };
45
+ } else {
46
+ const miss = KEYS.filter(k => theme[k] == null);
47
+ if (miss.length) throw new Error(`spec.theme 对象缺 ${miss.join(' ')}:写全 6 个键(${KEYS.join(' ')}),或加 name 用那套色板补缺的键。${avail}`);
48
+ out = { name: 'custom', ...theme };
49
+ }
50
+ } else throw new Error(`spec.theme 要写色板名(字符串)或对象,收到的是 ${JSON.stringify(theme)}。${avail}`);
51
+ const bad = KEYS.filter(k => !HEX.test(String(out[k])));
52
+ if (bad.length) throw new Error(`spec.theme 的 ${bad.map(k => `${k}=${JSON.stringify(out[k])}`).join(' ')} 不是 #rgb / #rrggbb 色值`);
53
+ for (const k of KEYS) out[k] = hex(rgb(out[k])).toUpperCase();
54
+ return out;
55
+ },
56
+ };
57
+ })();
@@ -0,0 +1,108 @@
1
+ // 后期:整幅叠在画面上的「媒介层」。全部挂在 PAINT(P)上。
2
+ // 来源:迁移测试 B(蒸汽波 VHS、吉卜力角色水彩肌理)、D(橡皮管胶片、新海诚泛光/光晕)、A(敦煌褪色)。
3
+ // P.rgbSplit(c, S, opt) RGB 三通道分离错位:multiply 取单通道 → lighter 合成到黑底(VHS、故障、3D 眼镜)
4
+ // P.vhs(c, S, t, lt, opt) VHS 全套:通道分离+跟踪噪声带下滚撕裂+扫描线+OSD+暗角(先把整幅画进离屏 S)
5
+ // P.scanlines(c, a1, a2) 4px 扫描线 pattern
6
+ // P.film(c, t, opt) 老胶片层:24fps 闪烁、跨格存活的竖划痕、灰尘毛发、颗粒抖动、圆角片门(1930 卡通、默片)
7
+ // P.gateWeave(c, t, fn, amp) 抖片:fn 里画的内容整体按 24fps 微抖
8
+ // P.bloom(c, opt) 泛光:整帧缩小糊化后 screen 回叠(新海诚、CG)
9
+ // P.lensFlare(c, sun, t, opt) 镜头光晕:太阳核+慢转星芒+横向拉丝+沿 太阳→画心 的六边形光斑
10
+ // P.textureInside(c, shapeLayer, drawTex, opt) 纹理只「乘」在角色上:纹理层 destination-in 角色剪影 → multiply 叠回
11
+ // P.fade(c, opt) 褪色/单色化:saturation 混合去色+multiply 染色(壁画褪色、胶片单色)
12
+ // P.vignette(c, opt) 径向暗角
13
+ (() => {
14
+ let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
15
+ const P = window.PAINT;
16
+ const { rng } = U;
17
+
18
+ // ---------- 通道分离 / VHS ----------
19
+ P.rgbSplit = (c, S, { r = [-3, 0], g = [0, 0], b = [3, 1], key = 'rgbSplit' } = {}) => {
20
+ const T = P.scratch(key), tg = T.getContext('2d');
21
+ c.save(); c.fillStyle = '#000'; c.fillRect(0, 0, W, H); c.globalCompositeOperation = 'lighter';
22
+ [['#ff0000', r], ['#00ff00', g], ['#0000ff', b]].forEach(([col, [dx, dy]]) => {
23
+ tg.globalCompositeOperation = 'source-over'; tg.clearRect(0, 0, W, H); tg.drawImage(S, 0, 0);
24
+ tg.globalCompositeOperation = 'multiply'; tg.fillStyle = col; tg.fillRect(0, 0, W, H);
25
+ c.drawImage(T, dx, dy);
26
+ });
27
+ c.restore();
28
+ };
29
+ P.scanlines = (c, a1 = 0.28, a2 = 0.12) => {
30
+ const tile = P.cached(`scan_${a1}_${a2}`, 4, 4, g => { g.fillStyle = `rgba(0,0,0,${a1})`; g.fillRect(0, 0, 4, 1); g.fillStyle = `rgba(0,0,0,${a2})`; g.fillRect(0, 2, 4, 1); });
31
+ c.save(); c.fillStyle = c.createPattern(tile, 'repeat'); c.fillRect(0, 0, W, H); c.restore();
32
+ };
33
+ // osd: false 关掉;或 {label:'PLAY', date:'SEP. 04 2011', clock:'23:59', font:'PressStart2P-400'}(▶ 自己画:像素字体里没有这个字形)
34
+ P.vhs = (c, S, t, lt, { osd = {}, band = 70, bandSpeed = 520, vignette = 'rgba(10,0,25,.55)' } = {}) => {
35
+ const f = Math.floor(t * 60), jit = U.hash(f >> 2, 9) < 0.12 ? 1 : 0;
36
+ const ca = 3 + jit * 6 + Math.sin(t * 7) * 1.5; // 色差错位量(偶发加大)
37
+ P.rgbSplit(c, S, { r: [-ca, 0], g: [0, 0], b: [ca, 1], key: 'vhsSplit' });
38
+ // 跟踪噪声带:从上往下滚,带内每 6px 横向撕裂+白噪声线
39
+ const by = ((lt * bandSpeed + 200) % (H + 160)) - 80, bh = band, r = rng(f * 7 + 1);
40
+ for (let y = by; y < by + bh; y += 6) { const dx = (r() - 0.5) * 60 * Math.sin((y - by) / bh * Math.PI); c.drawImage(S, 0, y, W, 6, dx, y, W, 6); }
41
+ c.save(); c.globalAlpha = 0.5; for (let i = 0; i < 90; i++) { c.fillStyle = r() < 0.5 ? '#ffffff' : '#bbbbbb'; c.fillRect(r() * W, by + r() * bh, 20 + r() * 120, 1.5); } c.restore();
42
+ P.scanlines(c);
43
+ if (osd) {
44
+ const font = osd.font || 'PressStart2P-400';
45
+ c.save(); c.font = `34px "${font}"`; c.fillStyle = '#ffffff'; c.shadowColor = 'rgba(0,0,0,.8)'; c.shadowOffsetX = 3; c.shadowOffsetY = 3;
46
+ if ((f >> 4) % 2 === 0 || lt < 0.2) { c.fillText(osd.label || 'PLAY', 60, 92); c.beginPath(); c.moveTo(232, 60); c.lineTo(262, 77); c.lineTo(232, 94); c.closePath(); c.fill(); }
47
+ c.font = `26px "${font}"`; c.fillText(osd.date || 'SEP. 04 2011', 60, 1020); c.fillText((osd.clock || '23:59') + ':' + String(30 + Math.floor(lt)).padStart(2, '0') + ':' + String(Math.floor(lt * 30) % 30).padStart(2, '0'), 60, 1056);
48
+ c.restore();
49
+ }
50
+ if (vignette) P.vignette(c, { inner: 500, outer: 1150, col: vignette });
51
+ };
52
+
53
+ // ---------- 胶片 ----------
54
+ P.film = (c, t, { fps = 24, flicker = [0.02, 0.07, 0.06], scratches = 3, dust = 14, grain = 'film', grainDensity = 0.18, gate = true, ink = '20,16,10', light = '255,252,240' } = {}) => {
55
+ const fq = Math.floor(t * fps), r = rng(fq * 7919 + 11);
56
+ c.fillStyle = `rgba(255,250,235,${flicker[0] + r() * flicker[1]})`; c.fillRect(0, 0, W, H);
57
+ c.fillStyle = `rgba(${ink},${r() * flicker[2]})`; c.fillRect(0, 0, W, H);
58
+ // 竖划痕:用 floor(t*6) 当寿命,一条划痕能活好几格
59
+ const r6 = rng(Math.floor(t * 6) * 131 + 5);
60
+ for (let k = 0; k < scratches; k++) { const x = r6() * W + (r() - 0.5) * 6; c.strokeStyle = r6() < 0.5 ? `rgba(${light},.55)` : `rgba(${ink},.5)`; c.lineWidth = 1 + r6() * 2; c.beginPath(); c.moveTo(x, 0); c.lineTo(x + (r() - 0.5) * 12, H); c.stroke(); }
61
+ for (let k = 0; k < dust; k++) { c.fillStyle = r() < 0.6 ? `rgba(${ink},.7)` : `rgba(${light},.8)`; c.beginPath(); c.arc(r() * W, r() * H, 1 + r() * 3.5, 0, 7); c.fill(); }
62
+ if (r() < 0.5) { c.strokeStyle = `rgba(${ink},.6)`; c.lineWidth = 1.6; const x = r() * W, y = r() * H; c.beginPath(); c.moveTo(x, y); c.bezierCurveTo(x + 30, y - 20, x + 10, y + 40, x + 50, y + 30); c.stroke(); }
63
+ if (grain) c.drawImage(P.grain(grain, grainDensity, ink.split(',').map(Number), 0.25), (r() - 0.5) * 40, (r() - 0.5) * 40);
64
+ if (gate) c.drawImage(P.cached('film_gate', W, H, (g) => {
65
+ const vg = g.createRadialGradient(960, 540, 420, 960, 540, 1150); vg.addColorStop(0, 'rgba(10,8,5,0)'); vg.addColorStop(0.7, 'rgba(10,8,5,.18)'); vg.addColorStop(1, 'rgba(10,8,5,.85)'); g.fillStyle = vg; g.fillRect(0, 0, W, H);
66
+ g.fillStyle = '#0c0a07'; g.beginPath(); g.rect(0, 0, W, H); g.roundRect(18, 14, W - 36, H - 28, 60); g.fill('evenodd');
67
+ }), 0, 0);
68
+ };
69
+ P.gateWeave = (c, t, fn, amp = [3, 4], fps = 24) => { const r = rng(Math.floor(t * fps) * 31 + 7); c.save(); c.translate((r() - 0.5) * amp[0], (r() - 0.5) * amp[1]); fn(); c.restore(); };
70
+
71
+ // ---------- 光 ----------
72
+ P.bloom = (c, { scale = 4, blur = 6, brightness = 1.1, alpha = 0.28, key = 'bloom' } = {}) => {
73
+ const w = W / scale, h = H / scale, bl = P.scratch(key), g = bl.getContext('2d'); g.reset();
74
+ g.filter = `blur(${blur}px) brightness(${brightness})`; g.drawImage(c.canvas, 0, 0, w, h);
75
+ c.save(); c.globalCompositeOperation = 'screen'; c.globalAlpha = alpha; c.drawImage(bl, 0, 0, w, h, 0, 0, W, H); c.restore();
76
+ };
77
+ // 坑:太阳要放在玻璃里面、核半径 ≤130,否则光晕溢到墙上像墙上挂了盏灯;横丝别超过 ~840px,否则像 bug 线。
78
+ P.lensFlare = (c, sun, t, { core = 130, rays = 10, streak = 840, ghosts = [[0.45, 34, '120,255,200', 0.09], [0.75, 18, '255,180,120', 0.12], [1.25, 56, '140,170,255', 0.06], [1.6, 24, '255,140,220', 0.09], [2.0, 90, '120,220,255', 0.04]] } = {}) => {
79
+ c.save(); c.globalCompositeOperation = 'lighter';
80
+ const sg = c.createRadialGradient(...sun, 0, ...sun, core); sg.addColorStop(0, 'rgba(255,255,250,.95)'); sg.addColorStop(0.18, 'rgba(255,248,220,.55)'); sg.addColorStop(1, 'rgba(255,230,180,0)'); c.fillStyle = sg; c.beginPath(); c.arc(...sun, core, 0, 7); c.fill();
81
+ for (let k = 0; k < rays; k++) { const a = k / rays * Math.PI * 2 + t * 0.25, L = (k % 2 ? 90 : 170) * (0.9 + 0.1 * Math.sin(t * 5 + k)); c.strokeStyle = 'rgba(255,250,235,.28)'; c.lineWidth = 2.5; c.beginPath(); c.moveTo(...sun); c.lineTo(sun[0] + Math.cos(a) * L, sun[1] + Math.sin(a) * L); c.stroke(); }
82
+ const hz = c.createLinearGradient(sun[0] - streak / 2, 0, sun[0] + streak / 2, 0); hz.addColorStop(0, 'rgba(120,200,255,0)'); hz.addColorStop(0.5, 'rgba(200,235,255,.35)'); hz.addColorStop(1, 'rgba(120,200,255,0)'); c.fillStyle = hz; c.fillRect(sun[0] - streak / 2, sun[1] - 2, streak, 4);
83
+ const cxp = 960 + Math.sin(t * 0.7) * 20, cyp = 540;
84
+ ghosts.forEach(([k, r, col, a]) => {
85
+ const x = sun[0] + (cxp - sun[0]) * k, y = sun[1] + (cyp - sun[1]) * k; c.fillStyle = `rgba(${col},${a})`; c.beginPath(); for (let i = 0; i < 6; i++) { const aa = i / 6 * Math.PI * 2 + 0.3; i ? c.lineTo(x + Math.cos(aa) * r, y + Math.sin(aa) * r) : c.moveTo(x + Math.cos(aa) * r, y + Math.sin(aa) * r); } c.closePath(); c.fill(); });
86
+ c.restore();
87
+ };
88
+
89
+ // ---------- 纹理叠在角色上 ----------
90
+ // shapeLayer:只画了角色的离屏(透明底);drawTex(g) 往纹理层上画纸纹/颗粒/色晕。
91
+ // 坑:直接 source-atop 叠纸纹会把纸的颜色盖上去,人物整体发白;正确做法是纹理层 destination-in 角色剪影,再 multiply 叠回(迁移测试 B)。
92
+ P.textureInside = (c, shapeLayer, drawTex, { op = 'multiply', alpha = 1, key = 'texInside' } = {}) => {
93
+ const M = P.scratch(key), mg = M.getContext('2d'); mg.reset(); mg.clearRect(0, 0, W, H);
94
+ drawTex(mg);
95
+ mg.globalCompositeOperation = 'destination-in'; mg.globalAlpha = 1; mg.drawImage(shapeLayer, 0, 0);
96
+ c.save(); c.globalCompositeOperation = op; c.globalAlpha = alpha; c.drawImage(M, 0, 0); c.restore();
97
+ };
98
+
99
+ // ---------- 色调 ----------
100
+ // sat: saturation 混合灰的 α(1 = 完全去色);tint: multiply 一层颜色(暖灰 '#f2e8d2' = 老胶片;土色纹理 = 壁画)
101
+ P.fade = (c, { sat = 1, tint } = {}) => {
102
+ c.save(); c.globalCompositeOperation = 'saturation'; c.fillStyle = `rgba(128,128,128,${sat})`; c.fillRect(0, 0, W, H); c.restore();
103
+ if (tint) { c.save(); c.globalCompositeOperation = 'multiply'; c.fillStyle = tint; c.fillRect(0, 0, W, H); c.restore(); }
104
+ };
105
+ P.vignette = (c, { cx = 960, cy = 540, inner = 520, outer = 1150, col = 'rgba(0,0,0,.4)' } = {}) => {
106
+ const vg = c.createRadialGradient(cx, cy, inner, cx, cy, outer); vg.addColorStop(0, col.replace(/[\d.]+\)$/, '0)')); vg.addColorStop(1, col); c.fillStyle = vg; c.fillRect(0, 0, W, H);
107
+ };
108
+ })();
@@ -0,0 +1,189 @@
1
+ // 渲染器与光影:网点、点彩、赛璐珞、漫画阴影、体积、长影子、双版本光影、光照图、皮影皮件、手剪位移。全部挂在 PAINT(P)上。
2
+ // 来源:迁移测试 B(构成主义/达利/霍珀/吉卜力)、C(Kirby/修拉/马蒂斯)、D(伦勃朗/皮影/新海诚)。
3
+ // P.halftoneGray(c, src, bx,by,bw,bh, cell, col, gain, ang) 灰度照片网点:45° 网格、点径 ∝ √暗度、全部点并成一条 path 一次填(照片拼贴)
4
+ // P.shadeDots(c, path, bx,by,bw,bh, cell, col, f) 路径内按函数 f(x,y)∈0..1 落网点(网点阴影)
5
+ // P.dotPattern(c, key, pitch, r, col, bg, angleDeg) 本戴点 createPattern,带网屏角(四色印刷 C15° M75° Y0° K45°)
6
+ // P.pointillism(c, src, opt) 修拉点彩=视觉混色:等大点、纯色板两两并置逼近目标色(P.dabs 是印象派,不是点彩)
7
+ // P.clipBeside(c, path, dx, dy) clip 到「path 减去平移后的自身」——赛璐珞阴影带、Kirby 黑块、亮边的公共底
8
+ // P.cel(c, p, base, shade, rim, opt) 赛璐珞两调+边缘光(新海诚、吉卜力角色)
9
+ // P.kirbyShade(g, path, opt) Kirby 黑块阴影(spotted blacks)+羽化排线(feathering)
10
+ // P.vol(c, path, x0,y0,x1,y1, a, b, occl, blur) 体积填色:线性渐变+路径内模糊暗边(学院派,无勾线)
11
+ // P.castShadow(c, layers, opt) 剪切仿射长影子:剪影投到地面(达利的长影子、黄昏)
12
+ // P.litClip(c, polys, drawLit) 双版本光影裁切:背光版先画好,光斑多边形里画受光版(霍珀的硬光块)
13
+ // P.pool / P.lightMap / P.applyLight 光照图整幅相乘:环境光打底+lighter 叠光池+multiply(伦勃朗、烛光、夜景)
14
+ // P.carve / P.piece / P.stamp 皮影:皮件填色→镂空刻纹→补皮边→描边;印到幕布 = 软影+multiply(剪影/镂空类风格)
15
+ // P.roughen(layer, box, opt) 整层低频像素位移:RIG 角色的边也像手剪的(马蒂斯)
16
+ // P.glowStroke(c, col, w, blur) 霓虹灯管:对当前路径用 shadowBlur 描辉光(再描一道近白细芯线就是灯管)
17
+ (() => {
18
+ const TAU = Math.PI * 2;
19
+ let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
20
+ const P = window.PAINT;
21
+ const { clamp, lerp, rng } = U;
22
+
23
+ // ---------- 网点 ----------
24
+ P.halftoneGray = (c, src, bx, by, bw, bh, cell, col, gain = 0.78, ang = Math.PI / 4) => {
25
+ bx |= 0; by |= 0; bw |= 0; bh |= 0;
26
+ const d = src.getContext('2d', { willReadFrequently: true }).getImageData(bx, by, bw, bh).data;
27
+ const ca = Math.cos(ang), sa = Math.sin(ang), cx = bx + bw / 2, cy = by + bh / 2, R = Math.hypot(bw, bh) / 2;
28
+ c.beginPath();
29
+ for (let v = -R; v <= R; v += cell) for (let u = -R; u <= R; u += cell) {
30
+ const x = cx + u * ca - v * sa, y = cy + u * sa + v * ca, ix = (x - bx) | 0, iy = (y - by) | 0;
31
+ if (ix < 0 || iy < 0 || ix >= bw || iy >= bh) continue;
32
+ const i = (iy * bw + ix) * 4; if (d[i + 3] < 128) continue;
33
+ const L = (d[i] * .3 + d[i + 1] * .59 + d[i + 2] * .11) / 255, r = cell * gain * Math.sqrt(Math.max(0, 1 - L));
34
+ if (r < 0.45) continue; c.moveTo(x + r, y); c.arc(x, y, r, 0, TAU);
35
+ }
36
+ c.fillStyle = col; c.fill();
37
+ };
38
+ P.shadeDots = (c, path, bx, by, bw, bh, cell, col, f) => {
39
+ c.save(); c.clip(path); c.beginPath();
40
+ for (let y = by; y < by + bh; y += cell) for (let x = bx + ((((y - by) / cell) | 0) % 2) * cell / 2; x < bx + bw; x += cell) {
41
+ const r = cell * 0.62 * f(x, y); if (r < 0.5) continue; c.moveTo(x + r, y); c.arc(x, y, r, 0, TAU);
42
+ }
43
+ c.fillStyle = col; c.fill(); c.restore();
44
+ };
45
+ P.dotPattern = (c, key, pitch, r, col, bg, angleDeg = 45) => {
46
+ const tile = P.cached('dotpat_' + key, pitch, pitch, g => { if (bg) { g.fillStyle = bg; g.fillRect(0, 0, pitch, pitch); } g.fillStyle = col;
47
+ [[0, 0], [pitch, 0], [0, pitch], [pitch, pitch]].forEach(([x, y]) => { g.beginPath(); g.arc(x, y, r, 0, TAU); g.fill(); }); });
48
+ const p = c.createPattern(tile, 'repeat');
49
+ p.setTransform(new DOMMatrix().rotate(angleDeg));
50
+ return p;
51
+ };
52
+
53
+ // ---------- 点彩(修拉:视觉混色) ----------
54
+ // 固定六角网格、点等大(pitch 10、rad 4.5、位置 ±1.2px 静态抖);每个点从纯色板 pal 里挑一对 (i,j):
55
+ // 目标色投影到 Pj→Pi 上得连续比例 w(clamp .2–.8),距离加 0.004·|Pi−Pj|² 惩罚(别用黑白点混出灰),格子哈希 < w 选 i 否则 j。
56
+ // 取色前给目标色加 ±colorJitter 哈希抖动——配对色分界变成交错的点,不出竖向色带(两个坑都踩过:偏好纯色→色带;连续比例→整列切换)。
57
+ // compRate 的点换成补色 comp[idx];光渗:和 (+14,+6) 处亮度差 >45 时,亮侧放 white 点、暗侧放 dark 点。
58
+ // shimmer(x,y) 为真的区域按 fps 重掷哈希(水面波光);flicker 比例的点按 fps 换成配对色(空气在颤,太低会被判「基本定格」)。
59
+ // 色板要带淡色(和白混过的色),否则亮墙被拆成白+肤色+零星蓝点,像雪花噪声。约 2.4 万点、每色一条 Path2D 一次 fill,每帧约 100ms。
60
+ const pointCache = new WeakMap();
61
+ P.pointillism = (c, src, { pal, comp = {}, pitch = 10, rad = 4.5, posJitter = 2.4, colorJitter = 40, compRate = 0.08, flicker = 0.07, fps = 6, t = 0, shimmer, white = 12, dark = 0, bleed = 45 } = {}) => {
62
+ const PAL = pal.map(v => typeof v === 'string' ? P.hex(v) : v);
63
+ let cache = pointCache.get(pal); if (!cache) { cache = new Map(); pointCache.set(pal, cache); }
64
+ const pick = (r, g, b) => {
65
+ const key = (r >> 3) << 10 | (g >> 3) << 5 | (b >> 3);
66
+ let v = cache.get(key); if (v) return v;
67
+ let best = 1e18, bi = 0, bj = 0, bw = 1;
68
+ for (let i = 0; i < PAL.length; i++) for (let j = i + 1; j < PAL.length; j++) {
69
+ const pi = PAL[i], pj = PAL[j], ex = pi[0] - pj[0], ey = pi[1] - pj[1], ez = pi[2] - pj[2];
70
+ const w = clamp(((r - pj[0]) * ex + (g - pj[1]) * ey + (b - pj[2]) * ez) / (ex * ex + ey * ey + ez * ez + 1e-6), 0.2, 0.8);
71
+ const dr = pj[0] + w * ex - r, dg = pj[1] + w * ey - g, db = pj[2] + w * ez - b;
72
+ const d = dr * dr * 0.9 + dg * dg * 1.2 + db * db * 0.8 + ex * ex * 0.004 + ey * ey * 0.004 + ez * ez * 0.004;
73
+ if (d < best) { best = d; bi = i; bj = j; bw = w; }
74
+ }
75
+ v = [bi, bj, bw]; cache.set(key, v); return v;
76
+ };
77
+ const sd = src.getContext('2d', { willReadFrequently: true }).getImageData(0, 0, W, H).data;
78
+ const L = (x, y) => { const i = (clamp(y | 0, 0, H - 1) * W + clamp(x | 0, 0, W - 1)) * 4; return sd[i] * 0.3 + sd[i + 1] * 0.59 + sd[i + 2] * 0.11; };
79
+ const paths = PAL.map(() => new Path2D()), st = Math.floor(t * fps), ROWH = pitch * 0.866, hj = posJitter / 2;
80
+ for (let j = 0, y = 0; y < H + ROWH; j++, y += ROWH) for (let i = 0, x = (j % 2) * pitch / 2; x < W + pitch; i++, x += pitch) {
81
+ const jx = (U.hash(i, j * 7 + 1) - 0.5) * 2 * hj, jy = (U.hash(i * 3, j + 5) - 0.5) * 2 * hj, px = x + jx, py = y + jy;
82
+ const k = (clamp(py | 0, 0, H - 1) * W + clamp(px | 0, 0, W - 1)) * 4;
83
+ const dj = (U.hash(i * 7 + 3, j * 11) - 0.5) * colorJitter;
84
+ const [a, bb, w] = pick(clamp(sd[k] + dj, 0, 255), clamp(sd[k + 1] + dj * 0.8, 0, 255), clamp(sd[k + 2] - dj * 0.6, 0, 255));
85
+ const hseed = shimmer && shimmer(px, py) ? U.hash(i + st * 977, j) : U.hash(i, j);
86
+ let idx = hseed < w ? a : bb;
87
+ const h2 = U.hash(i * 5 + 2, j * 3 + 9);
88
+ if (h2 < compRate && comp[idx] != null) idx = comp[idx];
89
+ else if (U.hash(i * 13 + st * 31, j * 17) < flicker) idx = (U.hash(i, j + st) < w) ? bb : a;
90
+ else { const dl = L(px, py) - L(px + 14, py + 6); if (Math.abs(dl) > bleed && h2 < 0.45) idx = dl > 0 ? white : dark; }
91
+ paths[idx].moveTo(px + rad, py); paths[idx].arc(px, py, rad, 0, TAU);
92
+ }
93
+ paths.forEach((p, k) => { c.fillStyle = P.rgb(PAL[k]); c.fill(p); });
94
+ };
95
+
96
+ // ---------- 赛璐珞 / 漫画阴影 ----------
97
+ let BIG; U.onStage((w, h) => { BIG = new Path2D(); BIG.rect(-60, -60, w + 120, h + 120); }); // 比画布四边各大 60px 的矩形,跟画布尺寸走
98
+ // clip 到 path ∩ ¬translate(path, dx, dy):沿 (dx,dy) 方向的那一侧之外留下一条带(光从 -(dx,dy) 来时就是背光侧的阴影带)
99
+ P.clipBeside = (c, path, dx, dy) => { const m = new Path2D(); m.addPath(BIG); m.addPath(path, new DOMMatrix().translate(dx, dy)); c.clip(path); c.clip(m, 'evenodd'); };
100
+ // 底色 → 背光侧硬阴影带(宽 sd,光方向 (lx,ly) 指向光源的反方向:lx=-1 光从左来、阴影在右)→ 受光侧亮边(宽 rw)→ 细描线。
101
+ // 按部件顺序逐个 cel,遮挡天然正确,不需要 visibleLines。逆光(窗下的猫):lx/ly 取反并另加 lighter 暖色亮边。
102
+ P.cel = (c, p, base, shade, rim, { sd = 22, rw = 7, line = '#6a4632', lw = 2.2, lx = -1, ly = -0.55 } = {}) => {
103
+ if (!p) return;
104
+ c.fillStyle = base; c.fill(p);
105
+ if (shade) { c.save(); P.clipBeside(c, p, lx * sd, ly * sd); c.fillStyle = shade; c.fill(p); c.restore(); }
106
+ if (rim) { c.save(); P.clipBeside(c, p, -lx * rw, -ly * rw); c.fillStyle = rim; c.fill(p); c.restore(); }
107
+ if (lw) { c.strokeStyle = line; c.lineWidth = lw; c.lineJoin = 'round'; c.stroke(p); }
108
+ };
109
+ // Kirby:light 指向光源(默认左上),黑块 = path ∩ ¬shift(d1),羽化带 = path ∩ ¬shift(d2) 里沿光方向的平行线(间距 gap)
110
+ P.kirbyShade = (g, path, { d1 = 14, d2 = 30, gap = 9, light = [-0.86, -0.5], ink = '#141212', lw = 2.6 } = {}) => {
111
+ g.save(); P.clipBeside(g, path, light[0] * d2, light[1] * d2);
112
+ g.strokeStyle = ink; g.lineWidth = lw; g.lineCap = 'butt'; g.beginPath();
113
+ const a = Math.atan2(light[1], light[0]), nx = -Math.sin(a), ny = Math.cos(a);
114
+ for (let k = -260; k < 260; k++) { const ox = 960 + nx * k * gap, oy = 540 + ny * k * gap; g.moveTo(ox - light[0] * 1400, oy - light[1] * 1400); g.lineTo(ox + light[0] * 1400, oy + light[1] * 1400); }
115
+ g.stroke(); g.restore();
116
+ g.save(); P.clipBeside(g, path, light[0] * d1, light[1] * d1); g.fillStyle = ink; g.fillRect(0, 0, W, H); g.restore();
117
+ };
118
+
119
+ // ---------- 体积 / 影子 / 光 ----------
120
+ P.vol = (c, path, x0, y0, x1, y1, a, b, occl = 'rgba(40,20,10,.45)', blur = 14) => {
121
+ const g = c.createLinearGradient(x0, y0, x1, y1); g.addColorStop(0, a); g.addColorStop(1, b);
122
+ c.fillStyle = g; c.fill(path);
123
+ if (occl) { c.save(); c.clip(path); c.shadowColor = occl; c.shadowBlur = blur; c.strokeStyle = occl; c.lineWidth = blur * 0.6; c.stroke(path); c.restore(); }
124
+ };
125
+ // layers: [[yg, g => { 在 g 上用黑色画这一组物体的剪影 }], ...],yg 是这组物体的着地线。
126
+ // 剪切仿射 y' = yg − ky·(yg − y)、x' = x − kx·(y' 方向的高度),影子往右后方躺(太阳在左前方低空);kx 变大 = 影子变长(黄昏)。
127
+ // 所有剪影先画进一张离屏(纯黑)再整体以 alpha 叠,重叠处不会加深;clipY 以下才是地面。坑:ky>0 往观众方向投会被画框底边吃掉。
128
+ P.castShadow = (c, layers, { kx = 1, ky = 0.22, clipY = 0, alpha = 0.55, blur = 1.5, key = 'castShadow' } = {}) => {
129
+ const S = P.scratch(key), g = S.getContext('2d'); g.reset(); g.clearRect(0, 0, W, H); g.fillStyle = '#000'; g.strokeStyle = '#000';
130
+ for (const [yg, fn] of layers) { g.setTransform(1, 0, -kx, ky, kx * yg, yg * (1 - ky)); fn(g); }
131
+ g.setTransform(1, 0, 0, 1, 0, 0);
132
+ c.save(); c.beginPath(); c.rect(0, clipY, W, H); c.clip(); c.globalAlpha = alpha; if (blur) c.filter = `blur(${blur}px)`; c.drawImage(S, 0, 0); c.restore();
133
+ };
134
+ // 双版本光影裁切:先把整幅画成背光版,再 clip 到光斑多边形(可多个、可每帧平移)画受光版 → 光影边缘是硬的、能整体移动。
135
+ // 人物同理:drawLit 里用受光色再画一遍。人在墙上的投影=剪影平移后 source-in 背光版,再画回光斑里。
136
+ P.litClip = (c, polys, drawLit) => {
137
+ c.save(); c.beginPath(); polys.forEach(pp => { pp.forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.closePath(); }); c.clip(); drawLit(c); c.restore();
138
+ };
139
+ // 光池:径向渐变的一团光(col 为 'r,g,b' 字符串)
140
+ P.pool = (g, x, y, r, col, a) => { const rg = g.createRadialGradient(x, y, 0, x, y, r); rg.addColorStop(0, `rgba(${col},${a})`); rg.addColorStop(0.45, `rgba(${col},${a * 0.55})`); rg.addColorStop(1, `rgba(${col},0)`); g.fillStyle = rg; g.fillRect(x - r, y - r, 2 * r, 2 * r); };
141
+ // 光照图:ambient 打底(环境光,伦勃朗约 rgb(30,21,13) = 0.13),fn(g) 里用 P.pool 叠光池(已是 lighter),返回画布
142
+ P.lightMap = (key, ambient, fn) => {
143
+ const S = P.scratch('light_' + key), g = S.getContext('2d'); g.reset();
144
+ g.fillStyle = ambient; g.fillRect(0, 0, W, H); g.globalCompositeOperation = 'lighter'; fn(g); g.globalCompositeOperation = 'source-over';
145
+ return S;
146
+ };
147
+ // 整幅乘光:亮态底稿 × 光照图。所有明暗法、烛光、夜景都这么做——比逐个物体画明暗统一得多。
148
+ P.applyLight = (c, map) => { c.save(); c.globalCompositeOperation = 'multiply'; c.drawImage(map, 0, 0); c.restore(); };
149
+
150
+ // ---------- 皮影 / 镂空 ----------
151
+ // 刻纹库(在 clip 内用 destination-out 画):dots 鱼子纹、flowers 团花、clouds 云纹、lattice 方格、lines 刻线
152
+ P.carve = {
153
+ dots(g, x0, y0, x1, y1, s = 18, r = 3.2) { for (let y = y0; y < y1; y += s) for (let x = x0 + ((y / s | 0) % 2) * s / 2; x < x1; x += s) { g.beginPath(); g.arc(x, y, r, 0, 7); g.fill(); } },
154
+ flowers(g, x0, y0, x1, y1, s = 46, r = 5) { for (let y = y0; y < y1; y += s) for (let x = x0 + ((y / s | 0) % 2) * s / 2; x < x1; x += s) { for (let k = 0; k < 5; k++) { const a = k / 5 * Math.PI * 2; g.beginPath(); g.ellipse(x + Math.cos(a) * r * 1.5, y + Math.sin(a) * r * 1.5, r, r * 0.6, a, 0, 7); g.fill(); } } },
155
+ clouds(g, x0, y0, x1, y1, s = 40) { g.lineWidth = 3.4; g.lineCap = 'round'; for (let y = y0; y < y1; y += s) for (let x = x0 + ((y / s | 0) % 2) * s / 2; x < x1; x += s) { g.beginPath(); for (let a = 0; a < 7; a += 0.3) { const rr = 2 + a * 1.9; const px = x + Math.cos(a) * rr, py = y + Math.sin(a) * rr; a ? g.lineTo(px, py) : g.moveTo(px, py); } g.stroke(); } },
156
+ lattice(g, x0, y0, x1, y1, s = 26, w = 12) { for (let y = y0; y < y1; y += s) for (let x = x0; x < x1; x += s) g.fillRect(x + (s - w) / 2, y + (s - w) / 2, w, w); },
157
+ lines(g, segs, w = 3.2) { g.lineWidth = w; g.lineCap = 'round'; segs.forEach(sg => { g.beginPath(); sg.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.stroke(); }); },
158
+ };
159
+ // 一块皮件:填色 → carve(g) 在 clip 内镂空 → 补 edge 宽的同色皮边(刻纹不刻穿外轮廓)→ 深色描边。
160
+ // 白色部位(猫的白胸白爪)直接整块镂空只留皮边——所有剪影/镂空类风格都适用。
161
+ P.piece = (g, path, col, carve, { edge = 5, line = '#3a160a', lw = 2.6 } = {}) => {
162
+ g.save(); g.fillStyle = col; g.fill(path);
163
+ if (carve) { g.save(); g.clip(path); g.globalCompositeOperation = 'destination-out'; g.fillStyle = '#000'; g.strokeStyle = '#000'; carve(g); g.restore(); }
164
+ g.strokeStyle = col; g.lineWidth = edge * 2; g.lineJoin = 'round'; g.save(); g.clip(path); g.stroke(path); g.restore();
165
+ g.strokeStyle = line; g.lineWidth = lw; g.stroke(path); g.restore();
166
+ };
167
+ // 把一层皮件印到幕布上:先 multiply 一层偏移软影(皮件没贴紧幕布的虚影),再 multiply 本体(透光 = 幕布色 × 皮色,重叠处自然变深)
168
+ P.stamp = (c, layer, { dx = 7, dy = 5, shadow = 0.28, blur = 6 } = {}) => {
169
+ c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = shadow; c.filter = `blur(${blur}px)`; c.drawImage(layer, dx, dy); c.restore();
170
+ c.save(); c.globalCompositeOperation = 'multiply'; c.drawImage(layer, 0, 0); c.restore();
171
+ };
172
+
173
+ // ---------- 霓虹 ----------
174
+ P.glowStroke = (c, col, w, blur) => { c.shadowColor = col; c.shadowBlur = blur; c.strokeStyle = col; c.lineWidth = w; c.stroke(); c.shadowBlur = 0; };
175
+
176
+ // ---------- 手剪位移 ----------
177
+ // 对 layer 的 box=[x0,y0,x1,y1] 区域做静态低频位移(fbm 频率 freq、幅度 amp px):RIG 角色不给剪刀折线时,整层边缘也像手剪的。约 15ms。
178
+ const fields = {};
179
+ P.roughen = (layer, box, { freq = 0.028, amp = 9, key = 'rough' } = {}) => {
180
+ const [X0, Y0, X1, Y1] = box, w = X1 - X0, h = Y1 - Y0, fk = `${key}|${box}|${freq}|${amp}`;
181
+ const f = fields[fk] || (fields[fk] = (() => { const o = { dx: new Int8Array(w * h), dy: new Int8Array(w * h) };
182
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { const gx = (x + X0) * freq, gy = (y + Y0) * freq; o.dx[y * w + x] = Math.round(P.fbm(gx, gy, 2) * amp); o.dy[y * w + x] = Math.round(P.fbm(gx + 40, gy + 17, 2) * amp); } return o; })());
183
+ const g = layer.getContext('2d', { willReadFrequently: true });
184
+ const src = g.getImageData(X0, Y0, w, h), out = g.createImageData(w, h), s = src.data, o = out.data;
185
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { const k = y * w + x, sx = clamp(x + f.dx[k], 0, w - 1), sy = clamp(y + f.dy[k], 0, h - 1), j = (sy * w + sx) * 4, i = k * 4;
186
+ o[i] = s[j]; o[i + 1] = s[j + 1]; o[i + 2] = s[j + 2]; o[i + 3] = s[j + 3]; }
187
+ g.putImageData(out, X0, Y0);
188
+ };
189
+ })();