@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,157 @@
1
+ #!/usr/bin/env python3
2
+ """逐帧渲染:起本地 http 服务 → 无头 Chromium 打开 index.html → 对每帧调用 window.renderFrame(t)
3
+ → 读 canvas 像素 → 管道送 ffmpeg 出 60fps mp4,最后混入音轨。
4
+
5
+ uv run --with playwright python render.py --out ../成片/复刻_v1.mp4 [--audio ../音频/复刻音轨.wav] [--from 0 --to 片长] [--fps 60]
6
+ [--film gallery](读 eras_gallery.js)[--solo <id> --stills 0.2,0.6 --no-counter] [--width 1080 --height 1920](竖屏整片:传给 index.html?w=&h=,
7
+ 引擎、转场、相机和各库按这个画布算;不写就用 eras.js 的 window.FILM_SIZE,再不写 1920×1080)
8
+ uv run --with playwright python render.py --spec clip.json --out 片段.mp4 [--alpha] [--stills 0.5,2]
9
+ 参数化片段(口播管线的「动画段」,契约见 references/09):时长严格 = spec.duration(帧数 round(duration×fps)),
10
+ 宽高/fps 从 spec 读;默认无声 H.264 yuv420p、每秒一个关键帧(GOP=fps,管线渲染器按秒 seek),--alpha 出 ProRes 4444 带透明(.mov)。spec 里图片路径相对 spec 文件。
11
+ 页面里有 pageerror 或 console.error(场景报错、缺字形)→ 渲完后非零退出。
12
+ """
13
+ import argparse, base64, http.server, json, socketserver, subprocess, sys, threading, functools, time, urllib.parse
14
+ from pathlib import Path
15
+ sys.path.insert(0, str(Path(__file__).parent))
16
+ import spec_assets
17
+ from playwright.sync_api import sync_playwright
18
+
19
+ ap = argparse.ArgumentParser()
20
+ ap.add_argument('--out', help='输出文件(--audit 时可不写)')
21
+ ap.add_argument('--audio')
22
+ ap.add_argument('--fps', type=int, default=60)
23
+ ap.add_argument('--from', dest='t0', type=float, default=0)
24
+ ap.add_argument('--to', dest='t1', type=float, default=None, help='默认 = 段落表算出的片长 window.__total')
25
+ ap.add_argument('--stills', help='逗号分隔秒数:只导出这些时刻的 png 到 --out 目录')
26
+ ap.add_argument('--solo', help='只渲某个时代(时间为本段局部时间),配合 --stills 或 --from/--to')
27
+ ap.add_argument('--film', default='', help='片子名:读 eras_<名>.js(默认 eras.js)')
28
+ ap.add_argument('--no-counter', action='store_true', help='--solo 时不画角标')
29
+ ap.add_argument('--crf', type=int, default=14)
30
+ ap.add_argument('--spec', help='参数化片段 spec.json(走 clip.html,不读段落表)')
31
+ ap.add_argument('--alpha', action='store_true', help='--spec 时出 ProRes 4444 带透明(背景不画)')
32
+ ap.add_argument('--width', type=int, help='整片模式的画布宽(竖屏 1080);--spec 时宽高从 spec 读,写了这个会覆盖 spec')
33
+ ap.add_argument('--height', type=int, help='整片模式的画布高(竖屏 1920)')
34
+ ap.add_argument('--audit', nargs='?', const='-', help='版面审计(只配 --spec):不出视频,每 0.25 秒采一帧,记录主画布上每次 fillText 的位置/字号/透明度,查出界、字太小、文字互相压;可跟一个 JSON 输出路径')
35
+ a = ap.parse_args()
36
+ if not a.out and not a.audit: ap.error('--out 必填(除非 --audit)')
37
+
38
+ root = Path(__file__).parent
39
+ spec, ALLOWED = None, set()
40
+ if a.spec:
41
+ sp = Path(a.spec).resolve(); spec = json.loads(sp.read_text())
42
+ try: spec_assets.localize(spec, sp.parent, ALLOWED) # image / frames / data.character 里的本地路径 → /__file__/<绝对路径>(只服务 spec 里点名的文件)
43
+ except FileNotFoundError as e: raise SystemExit(str(e))
44
+ if a.alpha: spec['alpha'] = True
45
+ if a.width or a.height: spec['width'], spec['height'] = a.width or spec.get('width', 1920), a.height or spec.get('height', 1080)
46
+ # 单个参数化片段撑太久会读成一页 PPT(一个版式到底、要点越堆越长)。只警告不报错:管线里有合法的长片段。
47
+ LONG_CLIP = None
48
+ if spec and float(spec.get('duration', 0)) > 8:
49
+ LONG_CLIP = ('\n' + '!' * 72 + f'\n!! 警告:这个片段 duration = {spec["duration"]} 秒,超过 8 秒。\n'
50
+ '!! 单个片段撑太久会读成一页 PPT:一个版式到底,要点越堆越长,背景从头死到尾。\n'
51
+ '!! 拆成多镜:每镜 2–6 秒、换语法或换做法(scenes、真实素材),再拼接(SKILL.md 轨道档第 3–4 步)。\n'
52
+ '!! 口播管线里确实要一段长片段的,可以忽略这条。\n' + '!' * 72 + '\n')
53
+ print(LONG_CLIP, file=sys.stderr, flush=True)
54
+
55
+ AUDIT_JS = r"""
56
+ (() => {
57
+ window.__AUDIT = []; window.__auditT = 0;
58
+ const P = CanvasRenderingContext2D.prototype;
59
+ for (const fn of ['fillText', 'strokeText']) {
60
+ const orig = P[fn];
61
+ P[fn] = function (text, x, y) {
62
+ try {
63
+ if (fn === 'fillText' && this.canvas === window.__canvas && String(text).trim()) {
64
+ const m = this.measureText(String(text)), tf = this.getTransform(), l = m.actualBoundingBoxLeft, r = m.actualBoundingBoxRight, as = m.actualBoundingBoxAscent, ds = m.actualBoundingBoxDescent;
65
+ const pts = [[x - l, y - as], [x + r, y - as], [x + r, y + ds], [x - l, y + ds]].map(q => [tf.a * q[0] + tf.c * q[1] + tf.e, tf.b * q[0] + tf.d * q[1] + tf.f]);
66
+ const xs = pts.map(q => q[0]), ys = pts.map(q => q[1]), px = parseFloat(this.font) * Math.hypot(tf.a, tf.b);
67
+ window.__AUDIT.push({ t: window.__auditT, text: String(text), x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys), px, a: this.globalAlpha });
68
+ }
69
+ } catch (e) {}
70
+ return orig.apply(this, arguments);
71
+ };
72
+ }
73
+ })();
74
+ """
75
+
76
+ class Q(http.server.SimpleHTTPRequestHandler):
77
+ def log_message(self, *x): pass
78
+ def translate_path(self, path):
79
+ if path.startswith('/__file__/'):
80
+ f = urllib.parse.unquote(path[len('/__file__/'):].split('?')[0])
81
+ return f if spec and f in ALLOWED else '/nonexistent'
82
+ return super().translate_path(path)
83
+ # Chromium may open idle connections while loading scripts in parallel.
84
+ class LocalServer(socketserver.ThreadingTCPServer):
85
+ request_queue_size = 128
86
+ daemon_threads = True
87
+
88
+ srv = LocalServer(('127.0.0.1', 0), functools.partial(Q, directory=str(root)))
89
+ port = srv.server_address[1]
90
+ threading.Thread(target=srv.serve_forever, daemon=True).start()
91
+
92
+ with sync_playwright() as p:
93
+ b = p.chromium.launch(args=['--disable-web-security', '--enable-gpu-rasterization', '--ignore-gpu-blocklist'])
94
+ pg = b.new_page(viewport={'width': 1920, 'height': 1080}, device_scale_factor=1)
95
+ errors = []
96
+ def on_console(m):
97
+ if m.type in ('error', 'warning'): print('[page]', m.text)
98
+ if m.type == 'error': errors.append(m.text)
99
+ pg.on('console', on_console)
100
+ pg.on('pageerror', lambda e: (print('[pageerror]', e), errors.append(str(e))))
101
+ if spec:
102
+ pg.set_viewport_size({'width': spec.get('width', 1920), 'height': spec.get('height', 1080)})
103
+ pg.add_init_script('window.CLIP_SPEC = ' + json.dumps(spec, ensure_ascii=False) + ';')
104
+ if a.audit: pg.add_init_script(AUDIT_JS)
105
+ pg.goto(f'http://127.0.0.1:{port}/clip.html?render=1')
106
+ else:
107
+ if a.width or a.height:
108
+ if not (a.width and a.height): raise SystemExit('--width 和 --height 要一起写')
109
+ pg.set_viewport_size({'width': a.width, 'height': a.height})
110
+ pg.goto(f'http://127.0.0.1:{port}/index.html?render=1' + (f'&film={a.film}' if a.film else '') + (f'&w={a.width}&h={a.height}' if a.width else ''))
111
+ pg.wait_for_function('window.__ready === true || !!window.__bootFailed', timeout=120000)
112
+ bf = pg.evaluate('window.__bootFailed || null')
113
+ if bf: raise SystemExit('场景加载失败,拒绝渲染:\n' + bf)
114
+ if a.t1 is None: a.t1 = pg.evaluate('window.__total')
115
+ if spec: a.fps = spec.get('fps', 30); a.t0 = 0; a.t1 = spec['duration']
116
+ grab = '''async (t)=>{ await window.prepare(t); window.renderFrame(t); return window.__canvas.toDataURL('image/png').split(',')[1]; }'''
117
+
118
+ if a.audit:
119
+ if not spec: raise SystemExit('--audit 只配 --spec')
120
+ import audit_clip
121
+ rep = audit_clip.run(pg, spec, grab)
122
+ if a.audit != '-': Path(a.audit).write_text(json.dumps(rep, ensure_ascii=False, indent=1))
123
+ b.close(); srv.shutdown(); srv.server_close()
124
+ sys.exit(1 if rep['red'] else 0)
125
+ if a.solo:
126
+ grab = '''async (t)=>{ window.renderSolo(%s, t, {counter: %s}); return window.__canvas.toDataURL('image/png').split(',')[1]; }''' % (repr(a.solo), 'false' if a.no_counter else 'true')
127
+ if a.stills:
128
+ out = Path(a.out); out.mkdir(parents=True, exist_ok=True)
129
+ for s in a.stills.split(','):
130
+ (out / f't{float(s):06.2f}.png').write_bytes(base64.b64decode(pg.evaluate(grab, float(s))))
131
+ print('stills ->', out)
132
+ else:
133
+ Path(a.out).parent.mkdir(parents=True, exist_ok=True)
134
+ vid = a.out if not a.audio else a.out + '.noaudio.mp4'
135
+ enc = ['-c:v', 'prores_ks', '-profile:v', '4444', '-pix_fmt', 'yuva444p10le', '-alpha_bits', '16'] if (spec and a.alpha) else \
136
+ ['-c:v', 'libx264', '-preset', 'slow', '-crf', str(a.crf), '-pix_fmt', 'yuv420p', '-movflags', '+faststart'] + \
137
+ (['-g', str(a.fps), '-keyint_min', str(a.fps), '-sc_threshold', '0'] if spec else []) # 片段要被管线的渲染器按秒 seek:每秒一个关键帧,否则冻帧
138
+ if spec and a.alpha and not vid.endswith('.mov'): print('提示:ProRes 4444 一般用 .mov 容器')
139
+ ff = subprocess.Popen(['ffmpeg', '-y', '-v', 'error', '-f', 'image2pipe', '-framerate', str(a.fps), '-c:v', 'png', '-i', '-'] + enc + [vid],
140
+ stdin=subprocess.PIPE)
141
+ n0, n1 = round(a.t0 * a.fps), round(a.t1 * a.fps)
142
+ st = time.time()
143
+ for i in range(n0, n1):
144
+ ff.stdin.write(base64.b64decode(pg.evaluate(grab, i / a.fps)))
145
+ if (i - n0) % 60 == 0:
146
+ print(f'{i}/{n1} {time.time()-st:.0f}s', flush=True)
147
+ ff.stdin.close(); ff.wait()
148
+ if a.audio:
149
+ subprocess.run(['ffmpeg', '-y', '-v', 'error', '-i', vid, '-ss', str(a.t0), '-t', str(a.t1 - a.t0), '-i', a.audio,
150
+ '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-b:a', '256k', '-shortest', a.out], check=True)
151
+ Path(vid).unlink()
152
+ print('done ->', a.out)
153
+ b.close()
154
+ srv.shutdown()
155
+ srv.server_close()
156
+ if LONG_CLIP: print(LONG_CLIP, file=sys.stderr) # 渲完再说一遍,免得被进度行冲掉
157
+ if errors: raise SystemExit(f'❌ 页面报错 {len(errors)} 条(见上方 [page]/[pageerror])')
@@ -0,0 +1,364 @@
1
+ // 40,000 BC 洞穴岩画——纯代码。
2
+ // 管线:①岩壁(半分辨率 fbm 高度场 → 法线 × 左上光 → 浮雕明暗;底色噪声;颗粒;裂缝;手印/爪印喷绘;暗角)全部缓存
3
+ // ②「颜料层」:所有图形先描炭黑粗边(带孔洞的炭笔图案描边)再平涂赭石,painter 顺序保证遮挡;
4
+ // 整层再 source-atop 叠一张「岩面明暗+颗粒」贴图 → 颜料吃进凹凸岩石
5
+ // ③会动的:野牛/马两张姿态每 7 帧交替、举杯、热气、猫尾/眨眼、火光摇曳、金色余烬、标题淡出(全片 t 0.933–1.133)
6
+ SCENES['01_cave'] = (() => {
7
+ const W = 1920, H = 1080, { clamp, lerp, rng } = U, P = PAINT;
8
+ const C = {
9
+ wallL: [206, 146, 90], wallD: [110, 68, 38], wallR: [182, 100, 58],
10
+ ink: '#21150d', red: '#9c3520', redD: '#74261a', ochre: '#c9772f', ochreL: '#d49a5a', hair: '#cf8f2c', hairD: '#6e3c0e', coat: '#b06a2c',
11
+ chalk: '#f0dfbf', bison: '#8c3d22', horse: '#cf8236', stone: '#c98c50', stoneD: '#c08048', spot: '#4e2610',
12
+ };
13
+
14
+ // ---------- 炭笔图案(描边用):炭黑 + 随机孔洞 → 颗粒边 ----------
15
+ const inkTile = () => P.cached('cave_inkpat', 256, 256, (g) => {
16
+ const img = g.createImageData(256, 256), d = img.data, r = rng(91);
17
+ for (let i = 0; i < 256 * 256; i++) {
18
+ const x = i % 256, y = (i / 256) | 0, n = P.noise(x * 0.09, y * 0.09) * 0.5 + P.noise(x * 0.3 + 9, y * 0.3) * 0.5;
19
+ const hole = r() < 0.07 || n > 0.58;
20
+ d[i * 4] = 33; d[i * 4 + 1] = 21; d[i * 4 + 2] = 13; d[i * 4 + 3] = hole ? 255 * r() * 0.35 : 225 + r() * 30;
21
+ }
22
+ g.putImageData(img, 0, 0);
23
+ });
24
+ const fuzzTile = () => P.cached('cave_fuzzpat', 256, 256, (g) => {
25
+ const r = rng(57); g.fillStyle = '#21150d';
26
+ for (let i = 0; i < 2600; i++) { g.globalAlpha = 0.35 + r() * 0.6; g.beginPath(); g.arc(r() * 256, r() * 256, 0.7 + r() * 1.5, 0, Math.PI * 2); g.fill(); }
27
+ });
28
+ const FPATS = new WeakMap();
29
+ const fuzzPat = (g, t) => { let p = FPATS.get(g); if (!p) { p = g.createPattern(fuzzTile(), 'repeat'); FPATS.set(g, p); } const r = rng(P.boilSeed(t, 9) * 13 + 5); p.setTransform(new DOMMatrix().translate((r() * 256) | 0, (r() * 256) | 0)); return p; };
30
+ const PATS = new WeakMap();
31
+ const inkPat = (g, t) => {
32
+ let p = PATS.get(g); if (!p) { p = g.createPattern(inkTile(), 'repeat'); PATS.set(g, p); }
33
+ const b = P.boilSeed(t, 9), r = rng(b * 7 + 3); // 9fps:图案整体错位 → 炭笔颗粒「沸腾」
34
+ p.setTransform(new DOMMatrix().translate((r() * 256) | 0, (r() * 256) | 0)); return p;
35
+ };
36
+ // 先描粗边(2×lw,外半圈露出)再填色 —— 颜料图形的统一画法
37
+ function shape(g, path, fill, lw, t, alpha = 1) {
38
+ if (lw) { g.lineJoin = 'round'; g.lineCap = 'round'; g.strokeStyle = fuzzPat(g, t); g.lineWidth = lw * 2 + 7; g.stroke(path); g.strokeStyle = inkPat(g, t); g.lineWidth = lw * 2; g.stroke(path); }
39
+ if (fill) { g.globalAlpha = alpha; g.fillStyle = fill; g.fill(path); g.globalAlpha = 1; }
40
+ }
41
+ function inkLine(g, path, lw, t) { if (lw > 3.5) { g.strokeStyle = fuzzPat(g, t); g.lineWidth = lw + 5; g.lineCap = 'round'; g.stroke(path); } g.strokeStyle = inkPat(g, t); g.lineWidth = lw; g.lineCap = 'round'; g.lineJoin = 'round'; g.stroke(path); }
42
+ const poly = (pts, closed = true) => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); if (closed) p.closePath(); return p; };
43
+ const sm = (pts, closed = true) => RIG.smooth(pts, closed);
44
+
45
+ // ---------- 岩壁(缓存) ----------
46
+ const HW = 960, HH = 540;
47
+ const relief = () => P.cached('cave_relief', HW, HH, (g) => {
48
+ // 高度场(全分辨率坐标):大起伏 + 中频鼓包
49
+ const hgt = new Float32Array((HW + 1) * (HH + 1));
50
+ for (let y = 0; y <= HH; y++) for (let x = 0; x <= HW; x++) { const X = x * 2, Y = y * 2;
51
+ hgt[y * (HW + 1) + x] = P.fbm(X * 0.0022 + 3, Y * 0.0022, 4) * 1.0 + P.fbm(X * 0.011, Y * 0.011 + 7, 3) * 0.16 + P.noise(X * 0.05, Y * 0.05) * 0.03; }
52
+ const img = g.createImageData(HW, HH), d = img.data, L = [-0.55, -0.62, 0.56];
53
+ const shade = new Float32Array(HW * HH);
54
+ for (let y = 0; y < HH; y++) for (let x = 0; x < HW; x++) {
55
+ const i = y * (HW + 1) + x, dx = (hgt[i + 1] - hgt[i]) * 52, dy = (hgt[i + HW + 1] - hgt[i]) * 52;
56
+ const nl = Math.hypot(dx, dy, 1), dot = (-dx * L[0] - dy * L[1] + L[2]) / nl;
57
+ const s = clamp(0.5 + (dot - 0.56) * 0.9, 0, 1); shade[y * HW + x] = s;
58
+ const X = x * 2, Y = y * 2, n2 = P.fbm(X * 0.0016 + 11, Y * 0.0016 + 2, 3), n3 = P.fbm(X * 0.004 + 40, Y * 0.004, 2);
59
+ let col = P.mix(C.wallL, C.wallD, clamp(0.35 - n2 * 1.3, 0, 1)); col = P.mix(col, C.wallR, clamp(n3 * 1.4, 0, 0.45));
60
+ const k = 0.72 + s * 0.55, j = (y * HW + x) * 4;
61
+ d[j] = col[0] * k; d[j + 1] = col[1] * k; d[j + 2] = col[2] * k; d[j + 3] = 255;
62
+ }
63
+ g.putImageData(img, 0, 0); g.__shade = shade;
64
+ });
65
+ // 给颜料层用的「岩面贴图」:阴影处压暗、亮处提亮 + 颗粒(source-atop 用)
66
+ const figTex = () => P.cached('cave_figtex', W, H, (g) => {
67
+ const rl = relief(), sh = rl.getContext('2d').__shade || relief().getContext('2d').__shade;
68
+ const s = P.canvas(HW, HH), sg = s.getContext('2d'), img = sg.createImageData(HW, HH), d = img.data;
69
+ for (let i = 0; i < HW * HH; i++) { const v = sh ? sh[i] : 0.5; const dk = v < 0.5; d[i * 4] = d[i * 4 + 1] = d[i * 4 + 2] = dk ? 20 : 255; if (!dk) { d[i * 4 + 1] = 236; d[i * 4 + 2] = 200; } d[i * 4 + 3] = (dk ? (0.5 - v) * 150 : (v - 0.5) * 50); }
70
+ sg.putImageData(img, 0, 0); g.drawImage(s, 0, 0, W, H);
71
+ g.drawImage(P.grain('cave_fdk', 0.14, [30, 18, 8], 0.32), 0, 0);
72
+ g.drawImage(P.grain('cave_flt', 0.05, [255, 236, 200], 0.22), 0, 0);
73
+ // 颜料干湿不均的大斑(低频)
74
+ const m = P.canvas(240, 135), mg = m.getContext('2d'), mi = mg.createImageData(240, 135);
75
+ for (let i = 0; i < 240 * 135; i++) { const x = i % 240, y = (i / 240) | 0, n = P.fbm(x * 0.08 + 21, y * 0.08, 3); mi.data[i * 4] = mi.data[i * 4 + 1] = mi.data[i * 4 + 2] = n > 0 ? 255 : 0; mi.data[i * 4 + 3] = Math.abs(n) * 70; }
76
+ mg.putImageData(mi, 0, 0); g.drawImage(m, 0, 0, W, H);
77
+ });
78
+
79
+ // 手形(负形手印用),中心在原点、指尖朝上
80
+ function handPath(s = 1) { // 返回若干块(逐块填)
81
+ const out = [], pal = new Path2D(); pal.ellipse(0, 8 * s, 26 * s, 31 * s, 0, 0, Math.PI * 2); out.push(pal);
82
+ const cap = (A, B, w) => { const p = new Path2D(); const a = Math.atan2(B[1] - A[1], B[0] - A[0]); p.moveTo(A[0], A[1]); p.lineTo(B[0], B[1]); return { p, w }; };
83
+ [[-0.5, 54, -17], [-0.17, 64, -6], [0.14, 62, 6], [0.46, 50, 17], [-1.25, 46, -20]].forEach(([a, l, ox], k) => {
84
+ const A = k < 4 ? [ox * s, -8 * s] : [-18 * s, 18 * s], B = [A[0] + Math.sin(a) * l * s, A[1] - Math.cos(a) * l * s];
85
+ const p = new Path2D(); const n = Math.atan2(B[1] - A[1], B[0] - A[0]) + Math.PI / 2, w = (k < 4 ? 7 : 8) * s;
86
+ p.moveTo(A[0] + Math.cos(n) * w, A[1] + Math.sin(n) * w); p.lineTo(B[0] + Math.cos(n) * w * 0.85, B[1] + Math.sin(n) * w * 0.85);
87
+ p.arc(B[0], B[1], w * 0.85, n, n + Math.PI, true); p.lineTo(A[0] - Math.cos(n) * w, A[1] - Math.sin(n) * w); p.closePath(); out.push(p); void cap; });
88
+ const wr = new Path2D(); wr.rect(-16 * s, 26 * s, 32 * s, 46 * s); out.push(wr); return out;
89
+ }
90
+ function spray(g, cx, cy, sigma, n, col, seed, rmin = 0.8, rmax = 3.2, amin = 0.25, amax = 0.75) {
91
+ const r = rng(seed); g.fillStyle = col;
92
+ for (let i = 0; i < n; i++) { const a = r() * Math.PI * 2, rr = Math.sqrt(-2 * Math.log(1 - r() * 0.999)) * sigma; g.globalAlpha = amin + r() * (amax - amin); g.beginPath(); g.arc(cx + Math.cos(a) * rr, cy + Math.sin(a) * rr, rmin + r() * (rmax - rmin), 0, Math.PI * 2); g.fill(); }
93
+ g.globalAlpha = 1;
94
+ }
95
+ function stencil(g, x, y, rot, s, col, seed, shapeFn) {
96
+ const tmp = P.canvas(320, 320), tg = tmp.getContext('2d');
97
+ spray(tg, 160, 160, 46 * s, 3200, col, seed, 0.8, 3.2, 0.15, 0.6);
98
+ spray(tg, 160, 160, 28 * s, 1400, col, seed + 1, 1, 3.5, 0.3, 0.75);
99
+ // 手形先画成实心蒙版(逐块 fill,避免 Path2D 子路径绕向相反造成的「缺口」)
100
+ const mk = P.canvas(320, 320), mg = mk.getContext('2d'); mg.translate(160, 160); mg.rotate(rot); mg.fillStyle = '#f6e2bc'; shapeFn(s).forEach(p => mg.fill(p));
101
+ tg.globalCompositeOperation = 'destination-out'; tg.globalAlpha = 0.95; tg.drawImage(mk, 0, 0);
102
+ tg.globalCompositeOperation = 'source-over'; tg.globalAlpha = 0.3; tg.drawImage(mk, 0, 0);
103
+ g.drawImage(tmp, x - 160, y - 160);
104
+ }
105
+ function pawPath(s = 1) { const out = []; const p = new Path2D(); p.ellipse(0, 8 * s, 15 * s, 12 * s, 0, 0, Math.PI * 2); out.push(p); [[-16, -10], [-6, -19], [6, -19], [16, -10]].forEach(([x, y]) => { const q = new Path2D(); q.ellipse(x * s, y * s, 6 * s, 7.5 * s, 0, 0, Math.PI * 2); out.push(q); }); return out; }
106
+
107
+ function cracks(g) {
108
+ const r = rng(404);
109
+ const starts = [[860, 0, 1.5], [930, 0, 1.75], [1540, 0, 1.7], [0, 600, -0.15], [0, 760, 0.05], [1920, 520, 3.0], [1920, 860, 3.25], [1250, 1080, -1.4], [230, 1080, -1.6], [0, 160, 0.4]];
110
+ const walk = (x, y, a, n, w, depth) => {
111
+ const pts = [[x, y]];
112
+ for (let s = 0; s < n; s++) { a += (r() - .5) * 0.9; const l = 14 + r() * 26; x += Math.cos(a) * l; y += Math.sin(a) * l; pts.push([x, y]);
113
+ if (depth < 2 && r() < 0.12) walk(x, y, a + (r() < .5 ? 1 : -1) * (0.6 + r() * 0.6), (n - s) * 0.5 | 0, w * 0.7, depth + 1); }
114
+ g.lineJoin = 'round';
115
+ g.strokeStyle = 'rgba(255,226,180,.22)'; g.lineWidth = w + 1; g.beginPath(); pts.forEach((p, i) => i ? g.lineTo(p[0] + 1.6, p[1] + 1.6) : g.moveTo(p[0] + 1.6, p[1] + 1.6)); g.stroke();
116
+ g.strokeStyle = 'rgba(40,22,10,.62)'; g.lineWidth = w; g.beginPath(); pts.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); g.stroke();
117
+ };
118
+ starts.forEach(([x, y, a]) => walk(x, y, a, 12 + (r() * 14 | 0), 2.2, 0));
119
+ }
120
+
121
+ const HANDS = [[150, 232, -0.35, 0.95], [238, 442, 0.25, 0.9], [1098, 138, 0.05, 0.95], [1742, 440, -0.2, 1.0], [1800, 662, 0.3, 0.92]];
122
+ // 静态画面(岩壁 + 所有不动的颜料图形)
123
+ const bg = () => P.cached('cave_bg', W, H, (g) => {
124
+ g.imageSmoothingQuality = 'high'; g.drawImage(relief(), 0, 0, W, H);
125
+ g.drawImage(P.grain('cave_wdk', 0.22, [60, 36, 18], 0.32), 0, 0);
126
+ g.drawImage(P.grain('cave_wlt', 0.10, [255, 238, 205], 0.28), 0, 0);
127
+ cracks(g);
128
+ // 手印(红赭石喷绘负形)+ 爪印(炭黑)+ 红点虚线
129
+ HANDS.forEach(([x, y, a, s], k) => { if (s) stencil(g, x, y, a, s, '#8e2a1a', 31 + k * 17, handPath); });
130
+ stencil(g, 262, 762, 0, 0.5, '#1a0f08', 77, pawPath);
131
+ g.fillStyle = '#9a2e1c'; for (let k = 0; k < 9; k++) { const x = 100 + k * 28, y = 972 - k * 4.5; spray(g, x, y, 3.5, 40, '#9a2e1c', 200 + k, 0.8, 2, 0.4, 0.8); g.globalAlpha = 0.75; g.beginPath(); g.arc(x, y, 5.5, 0, Math.PI * 2); g.fill(); g.globalAlpha = 1; }
132
+ // 静态颜料图形画到一张临时层,再叠岩面贴图
133
+ const L = P.canvas(), lg = L.getContext('2d');
134
+ // 壁画框
135
+ const fr = new Path2D(); P.roughPath({ moveTo: (...a) => fr.moveTo(...a), lineTo: (...a) => fr.lineTo(...a), closePath: () => fr.closePath(), beginPath() {} }, [[367, 126], [793, 124], [796, 552], [366, 550]], { amp: 3, seed: 5, closed: true, step: 18 });
136
+ inkLine(lg, fr, 8, 0);
137
+ const dot = (x, y) => { spray(lg, x, y, 5, 26, '#a8331f', (x * 7 + y) | 0, 0.8, 2.2, 0.3, 0.8); lg.fillStyle = '#a8331f'; lg.globalAlpha = 0.85; lg.beginPath(); lg.ellipse(x, y, 8.5, 8, (x + y) * 0.1, 0, Math.PI * 2); lg.fill(); lg.globalAlpha = 1; };
138
+ for (let x = 398; x <= 768; x += 37) { dot(x, 152); dot(x, 524); }
139
+ for (let y = 190; y <= 490; y += 37) { dot(392, y); dot(770, y); }
140
+ // 太阳+射线
141
+ spray(lg, 470, 228, 14, 200, '#a8301c', 9, 1, 3, 0.3, 0.8); lg.fillStyle = '#a8301c'; lg.globalAlpha = 0.9; lg.beginPath(); lg.arc(470, 228, 28, 0, Math.PI * 2); lg.fill(); lg.globalAlpha = 1;
142
+ lg.strokeStyle = '#a8301c'; lg.lineWidth = 4.5; lg.lineCap = 'round';
143
+ for (let k = 0; k < 9; k++) { const a = Math.PI * (0.95 + k * 0.13); lg.beginPath(); lg.moveTo(470 + Math.cos(a) * 38, 228 + Math.sin(a) * 38); lg.lineTo(470 + Math.cos(a) * 56, 228 + Math.sin(a) * 56); lg.stroke(); }
144
+ // 桌子上方的淡炭笔涂鸦(之前画过的人形)
145
+ lg.strokeStyle = 'rgba(40,26,16,.5)'; lg.lineWidth = 3;
146
+ [[[858, 342], [872, 300], [905, 262], [945, 250], [972, 268], [1000, 258], [1040, 250], [1080, 272], [1098, 318], [1100, 365]], [[880, 330], [885, 380], [890, 395]], [[928, 300], [924, 360], [926, 412]], [[955, 352], [990, 342], [1008, 352], [1012, 384]], [[1040, 360], [1048, 400]], [[1060, 330], [1078, 352], [1088, 392]]]
147
+ .forEach((pts, k) => { P.roughPath(lg, pts, { amp: 1.6, seed: 50 + k, step: 10 }); lg.stroke(); });
148
+ // 石凳(少女坐)、石板桌、长矛
149
+ shape(lg, sm([[1288, 768], [1325, 738], [1420, 730], [1492, 748], [1518, 806], [1514, 882], [1484, 908], [1300, 910], [1276, 872], [1272, 806]]), C.stone, 7, 0, 0.92);
150
+ inkLine(lg, sm([[1460, 790], [1478, 840], [1470, 880]], false), 3, 0); inkLine(lg, sm([[1320, 860], [1340, 890]], false), 3, 0);
151
+ shape(lg, sm([[958, 668], [1078, 668], [1086, 760], [1100, 870], [1094, 906], [936, 906], [926, 872], [944, 760]]), C.stoneD, 7, 0, 0.92);
152
+ inkLine(lg, sm([[985, 720], [978, 790], [984, 830]], false), 3, 0); inkLine(lg, sm([[1048, 790], [1060, 850]], false), 3, 0);
153
+ shape(lg, sm([[818, 650], [842, 636], [1000, 632], [1182, 636], [1210, 650], [1198, 668], [1000, 674], [830, 670]]), C.ochreL, 7, 0, 0.92);
154
+ const sp = new Path2D(); sp.moveTo(1562, 902); sp.lineTo(1512, 340); inkLine(lg, sp, 7, 0);
155
+ shape(lg, sm([[1508, 296], [1522, 322], [1520, 350], [1510, 362], [1500, 350], [1497, 322]]), C.red, 4, 0);
156
+ lg.globalCompositeOperation = 'source-atop'; lg.drawImage(figTex(), 0, 0); lg.globalCompositeOperation = 'source-over';
157
+ g.drawImage(L, 0, 0);
158
+ // 洞穴暗角(四角压暗)+ 中心暖光(静态部分)
159
+ const v = g.createRadialGradient(990, 560, 260, 990, 560, 1180); v.addColorStop(0, 'rgba(30,14,4,0)'); v.addColorStop(0.5, 'rgba(30,14,4,.16)'); v.addColorStop(1, 'rgba(18,8,2,.8)');
160
+ g.fillStyle = v; g.fillRect(0, 0, W, H);
161
+ const fl = g.createRadialGradient(1000, 520, 20, 1000, 520, 620); fl.addColorStop(0, 'rgba(255,190,110,.36)'); fl.addColorStop(0.5, 'rgba(255,150,70,.14)'); fl.addColorStop(1, 'rgba(255,150,70,0)');
162
+ g.globalCompositeOperation = 'lighter'; g.fillStyle = fl; g.fillRect(0, 0, W, H); g.globalCompositeOperation = 'source-over';
163
+ });
164
+
165
+ // ---------- 壁画动物:两张姿态(每 7 帧换一张) ----------
166
+ const BISON = sm([[470, 300], [495, 280], [545, 268], [595, 250], [632, 240], [660, 246], [682, 268], [692, 300], [686, 330], [668, 350], [640, 356], [590, 352], [530, 348], [488, 342], [468, 324]]);
167
+ const BHEAD = sm([[672, 270], [698, 280], [716, 302], [724, 326], [714, 340], [696, 338], [682, 326], [668, 300]]);
168
+ const HORSE = sm([[452, 474], [470, 458], [510, 456], [548, 452], [566, 432], [582, 414], [598, 410], [614, 432], [612, 444], [598, 444], [586, 438], [578, 458], [574, 478], [540, 488], [486, 488], [458, 486]]);
169
+ function animals(g, t) {
170
+ const ph = Math.floor(t * 60 / 7) % 2, rb = P.boilSeed(t, 9);
171
+ // 野牛
172
+ g.save(); g.translate(ph ? 3 : -3, ph ? -7 : 2);
173
+ const bl = ph ? [[652, 340, 698, 384], [632, 346, 672, 394], [500, 340, 458, 378], [520, 344, 482, 390]]
174
+ : [[652, 340, 648, 392], [632, 346, 618, 396], [500, 340, 504, 394], [520, 344, 530, 394]];
175
+ bl.forEach(([x0, y0, x1, y1], k) => { const pth = sm([[x0, y0], [(x0 + x1) / 2 + (ph ? 0 : 8 * (k < 2 ? -1 : 1)), (y0 + y1) / 2 + 2], [x1, y1]], false); inkLine(g, pth, 6, t); const hf = new Path2D(); hf.moveTo(x1 - 6, y1); hf.lineTo(x1 + 7, y1 + 2); inkLine(g, hf, 6, t); });
176
+ g.save(); g.translate(698, 300); g.rotate(ph ? 0.12 : -0.09); g.translate(-698, -300);
177
+ shape(g, BISON, C.bison, 7, t, 0.95);
178
+ g.save(); g.clip(BISON); g.strokeStyle = 'rgba(70,24,10,.55)'; g.lineWidth = 22; g.stroke(sm([[480, 292], [545, 276], [600, 258], [640, 252], [672, 268]], false)); g.restore();
179
+ shape(g, BHEAD, '#76301a', 6, t);
180
+ inkLine(g, sm([[700, 284], [708, 262], [722, 248]], false), 5, t); inkLine(g, sm([[688, 280], [688, 258], [698, 242]], false), 4.5, t);
181
+ inkLine(g, sm([[690, 340], [688, 352]], false), 4, t); inkLine(g, sm([[702, 342], [702, 354]], false), 4, t);
182
+ g.fillStyle = C.ink; g.beginPath(); g.arc(704, 304, 3.5, 0, Math.PI * 2); g.fill();
183
+ g.restore();
184
+ g.fillStyle = C.ink; for (let k = 0; k < 6; k++) { g.beginPath(); g.arc(538 + k * 13, 301, 3.2, 0, Math.PI * 2); g.fill(); }
185
+ inkLine(g, sm([[468, 296], [452, 314], [446, 336]], false), 4, t);
186
+ g.restore();
187
+ // 马(与野牛反相)
188
+ g.save(); g.translate(ph ? -3 : 3, ph ? 2 : -7);
189
+ const hl = ph ? [[566, 486, 562, 528], [552, 488, 544, 530], [484, 488, 488, 528], [470, 486, 466, 526]]
190
+ : [[566, 486, 596, 520], [552, 488, 580, 528], [484, 488, 456, 520], [470, 486, 442, 516]];
191
+ hl.forEach(([x0, y0, x1, y1]) => { inkLine(g, sm([[x0, y0], [(x0 + x1) / 2, (y0 + y1) / 2 + 2], [x1, y1]], false), 5.5, t); });
192
+ g.save(); g.translate(590, 440); g.rotate(ph ? -0.08 : 0.1); g.translate(-590, -440);
193
+ shape(g, HORSE, C.horse, 6, t, 0.95);
194
+ shape(g, poly([[592, 414], [598, 396], [606, 414]]), C.horse, 3.5, t);
195
+ for (let k = 0; k < 6; k++) { const p = new Path2D(); p.moveTo(566 + k * 4.5, 446 - k * 6); p.lineTo(558 + k * 4.5, 438 - k * 6); inkLine(g, p, 3.2, t); }
196
+ g.fillStyle = C.ink; g.beginPath(); g.arc(600, 424, 3, 0, Math.PI * 2); g.fill();
197
+ g.restore();
198
+ inkLine(g, sm([[454, 468], [440, 480], [432, 500]], false), 4.5, t);
199
+ g.restore();
200
+ void rb;
201
+ }
202
+
203
+ // ---------- 猫(岩画画法,橘白) ----------
204
+ function cat(g, ch, t, lt) {
205
+ const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe, look: hq(lt) * 1.5 });
206
+ const ha = -0.16 * hq(lt), ear = hq(lt) > 0.2 ? Math.sin(lt * 60) * 0.06 : 0;
207
+ const br = (x, y) => [x, y - (y < 800 ? ch.breathe * 300 : 0)];
208
+ // 尾巴(在身后)
209
+ g.lineCap = 'round'; g.lineJoin = 'round';
210
+ g.strokeStyle = fuzzPat(g, t); g.lineWidth = K.tailW + 18; g.stroke(K.tail); g.strokeStyle = inkPat(g, t); g.lineWidth = K.tailW + 12; g.stroke(K.tail);
211
+ g.strokeStyle = C.ochre; g.lineWidth = K.tailW; g.stroke(K.tail);
212
+ const tp = K.tailPts; g.strokeStyle = '#55210b'; g.lineWidth = 6;
213
+ for (let k = 5; k < tp.length - 1; k += 4) { const a = Math.atan2(tp[k + 1][1] - tp[k][1], tp[k + 1][0] - tp[k][0]) + Math.PI / 2; g.beginPath(); g.moveTo(tp[k][0] + Math.cos(a) * 10, tp[k][1] + Math.sin(a) * 10); g.lineTo(tp[k][0] - Math.cos(a) * 10, tp[k][1] - Math.sin(a) * 10); g.stroke(); }
214
+ // 身体
215
+ const body = sm([[560, 684], [505, 702], [464, 756], [446, 826], [450, 884], [488, 906], [600, 906], [654, 902], [662, 858], [654, 790], [642, 734], [616, 698]].map(p => br(...p)));
216
+ shape(g, body, C.ochre, 7, t);
217
+ g.save(); g.clip(body); g.strokeStyle = '#55210b'; g.lineCap = 'round';
218
+ [[716, 12], [748, 11], [780, 11], [812, 10], [846, 9]].forEach(([y, w], k) => { const yy = br(0, y)[1]; g.lineWidth = w; g.beginPath(); g.moveTo(430, yy - 6); g.quadraticCurveTo(470 + k * 2, yy + 2, 512 - k * 3, yy + 16); g.stroke(); });
219
+ g.lineWidth = 9; g.beginPath(); g.moveTo(520, 690); g.lineTo(540, 716); g.stroke(); g.beginPath(); g.moveTo(548, 686); g.lineTo(562, 708); g.stroke();
220
+ g.restore();
221
+ // 白胸肚
222
+ g.fillStyle = C.chalk; g.fill(sm([[612, 700], [640, 736], [652, 792], [656, 860], [644, 898], [592, 902], [584, 830], [592, 748]].map(p => br(...p))));
223
+ // 大腿弧线
224
+ inkLine(g, sm([[478, 842], [508, 826], [546, 838], [566, 872], [566, 904]], false), 4, t);
225
+ // 前腿+爪(白)
226
+ [[592, 600], [634, 640]].forEach(([x0, x1]) => { const leg = sm([[x0 - 4, 790], [x0 + 22, 792], [x0 + 24, 880], [x0 + 32, 892], [x0 + 26, 906], [x0 - 12, 906], [x0 - 10, 884]]); shape(g, leg, C.chalk, 6, t); });
227
+ // 头(原片 65–81 帧猫头有小动作:绕颈点抬头转向,耳朵抖)
228
+ g.save(); g.translate(596, 680); g.rotate(ha + ear); g.translate(-596, -680);
229
+ shape(g, K.head, C.ochre, 7, t);
230
+ g.save(); g.clip(K.head);
231
+ g.fillStyle = C.chalk; g.beginPath(); g.ellipse(K.nose[0] - 22, K.nose[1] + 16, 38, 28, 0, 0, Math.PI * 2); g.fill();
232
+ g.strokeStyle = '#55210b'; g.lineWidth = 7; g.lineCap = 'round'; K.stripes.slice(0, 3).forEach(s => { g.beginPath(); g.moveTo(...s[0]); g.lineTo(...s[1]); g.stroke(); });
233
+ g.restore();
234
+ g.fillStyle = '#c0644a'; K.earInner.forEach(e => g.fill(poly(e)));
235
+ // 眼、鼻、嘴、须
236
+ K.eyes.forEach(e => { if (K.blink) { const p = new Path2D(); p.arc(e.x, e.y, 8, 0.2, Math.PI - 0.2); inkLine(g, p, 3.5, t); }
237
+ else { const p = new Path2D(); p.ellipse(e.x, e.y, 9, 7.5, 0, 0, Math.PI * 2); g.fillStyle = '#e8b54a'; g.fill(p); inkLine(g, p, 3, t); g.fillStyle = C.ink; g.beginPath(); g.ellipse(e.x + 2, e.y, 2.6, 6, 0, 0, Math.PI * 2); g.fill(); } });
238
+ g.fillStyle = '#8a2a1a'; g.fill(poly([[K.nose[0] - 6, K.nose[1] - 4], [K.nose[0] + 6, K.nose[1] - 4], [K.nose[0], K.nose[1] + 4]]));
239
+ const m = new Path2D(); m.moveTo(...K.mouth[0]); m.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); inkLine(g, m, 3, t);
240
+ K.whiskers.forEach(w => { const p = new Path2D(); p.moveTo(...w[0]); p.lineTo(...w[1]); inkLine(g, p, 2.2, t); });
241
+ g.restore();
242
+ }
243
+ const hq = (lt) => { const q = clamp((lt - 1.08) / 0.27); return Math.sin(q * Math.PI); };
244
+
245
+ // ---------- 少女(岩画人形,金发长卷发,及膝豹纹兽皮外衣) ----------
246
+ function girl(g, lt, t, ch) {
247
+ const k = P.choreo(Math.max(0, lt - 0.95), t).cup; // 0.95s 起举杯(choreo 曲线平移)
248
+ const G = RIG.girl({ cup: k, sip: ch.sip * k, breathe: ch.breathe, hair: 'long' });
249
+ const nk = [1300, 425], ct = Math.cos(G.tilt), st = Math.sin(G.tilt);
250
+ const HP = (x, y) => { const X = x - nk[0], Y = y - nk[1]; return [nk[0] + X * ct - Y * st, nk[1] + X * st + Y * ct]; };
251
+ const HB = (x, y) => (y < 440 ? HP(x, y) : [x + (y < 520 ? (HP(x, y)[0] - x) * (520 - y) / 80 : 0), y]); // 头发:越往下越不跟头转
252
+ // 远侧小腿(在后)
253
+ const leg = (ox, oy) => sm([[1140, 770], [1128, 830], [1120, 878], [1094, 896], [1084, 910], [1172, 912], [1178, 890], [1192, 830], [1198, 778]].map(([x, y]) => [x + ox, y + oy]));
254
+ shape(g, leg(40, -6), C.redD, 6, t);
255
+ // 背后长发(黄赭石,波浪边)
256
+ const back = [], front = [];
257
+ for (let i = 0; i <= 10; i++) { const q = i / 10, y = 330 + q * 300; back.push(HB(1398 + q * 52 + Math.sin(q * 13) * 13 * q, y)); front.push(HB(1332 + q * 68 + Math.sin(q * 12 + 1) * 11 * q, 345 + q * 270)); }
258
+ const tips = [[1440, 640], [1428, 622], [1418, 646], [1406, 626]];
259
+ const hairPts = [HP(1262, 312), HP(1300, 288), HP(1352, 290), HP(1392, 314), ...back, ...tips, ...front.reverse(), HP(1320, 330), HP(1290, 318)];
260
+ const hairP = sm(hairPts);
261
+ // 兽皮外衣(躯干 + 及膝下摆,毛边)
262
+ const fr = [], rr = rng(17);
263
+ for (let x = 1448, i = 0; x >= 1126; x -= 15, i++) fr.push([x, 782 + (i % 2 ? 16 : 0) + (rr() - .5) * 6]);
264
+ const coatLow = sm([[1300, 600], [1420, 608], [1446, 650], [1452, 722], [1450, 770], ...fr, [1124, 764], [1148, 722], [1190, 656], [1242, 628]]);
265
+ // 近侧小腿
266
+ // 外衣描边 → 填色(两块一起,接缝被盖住)
267
+ g.lineJoin = 'round'; g.strokeStyle = fuzzPat(g, t); g.lineWidth = 19; g.stroke(G.torso); g.stroke(coatLow); g.strokeStyle = inkPat(g, t); g.lineWidth = 13; g.stroke(G.torso); g.stroke(coatLow);
268
+ shape(g, leg(0, 0), C.red, 6, t);
269
+ g.fillStyle = C.coat; g.fill(G.torso); g.fill(coatLow);
270
+ // 豹纹斑点
271
+ g.save(); const clipP = new Path2D(); clipP.addPath(G.torso); clipP.addPath(coatLow); g.clip(clipP);
272
+ const r = rng(23);
273
+ for (let i = 0; i < 26; i++) { const x = 1150 + r() * 300, y = 450 + r() * 330, s = 7 + r() * 6; g.fillStyle = C.spot; g.beginPath(); g.arc(x, y, s, 0, Math.PI * 2); g.fill();
274
+ for (let j = 0; j < 7; j++) { const a = j / 7 * Math.PI * 2 + r(); g.beginPath(); g.arc(x + Math.cos(a) * s * 1.6, y + Math.sin(a) * s * 1.6, 2.4, 0, Math.PI * 2); g.fill(); } }
275
+ g.strokeStyle = 'rgba(78,38,16,.55)'; g.lineWidth = 2.5; for (let i = 0; i < 18; i++) { const x = 1170 + r() * 270, y = 470 + r() * 300; g.beginPath(); g.moveTo(x, y); g.lineTo(x - 6, y + 12); g.stroke(); }
276
+ g.restore();
277
+ // 腰带(皮绳)
278
+ inkLine(g, sm([[1292, 606], [1360, 612], [1424, 614]], false), 6, t);
279
+ // 头发、脖子、脸
280
+ shape(g, G.neck, C.red, 6, t);
281
+ shape(g, hairP, C.hair, 7, t);
282
+ g.save(); g.clip(hairP); g.strokeStyle = C.hairD; g.lineWidth = 3; g.lineCap = 'round';
283
+ for (let j = 0; j < 6; j++) { const pts = []; for (let i = 0; i <= 12; i++) { const q = i / 12; pts.push(HB(1338 + j * 17 + q * (56 - j * 2) + Math.sin(q * 13 + j * 0.7) * 12 * Math.min(1, q * 2.5), 300 + q * 330)); } g.stroke(sm(pts, false)); }
284
+ g.restore();
285
+ shape(g, G.face, C.red, 7, t);
286
+ shape(g, G.bangs, C.hair, 6, t);
287
+ g.save(); g.clip(G.bangs); g.strokeStyle = C.hairD; g.lineWidth = 2.5; G.hairLines.forEach(h => g.stroke(h)); g.restore();
288
+ // 眼(侧脸一笔:杏仁+睫毛;眨眼时只剩一条线)
289
+ if (ch.blink) { const p = new Path2D(); p.moveTo(G.eye.x - 8, G.eye.y + 1); p.quadraticCurveTo(G.eye.x, G.eye.y + 5, G.eye.x + 8, G.eye.y + 1); inkLine(g, p, 3, t); }
290
+ else { const p = new Path2D(); p.ellipse(G.eye.x, G.eye.y, 7.5, 4.2, -0.1, 0, Math.PI * 2); g.fillStyle = '#f0dcb8'; g.fill(p); inkLine(g, p, 2.6, t); g.fillStyle = C.ink; g.beginPath(); g.arc(G.eye.x - 2, G.eye.y, 2.8, 0, Math.PI * 2); g.fill();
291
+ const l = new Path2D(); l.moveTo(G.eye.x - 7, G.eye.y - 2); l.lineTo(G.eye.x - 12, G.eye.y - 6); inkLine(g, l, 2.4, t); }
292
+ inkLine(g, G.brow, 2.4, t);
293
+ // 端杯手臂:自定 IK(伸臂端在半空 → 举到嘴边)
294
+ const hold = [1084, 506], mouth = HP(1224, 380), cp = [lerp(hold[0], mouth[0], k), lerp(hold[1], mouth[1], k)];
295
+ const S = G.A.shoulder, hand = [cp[0] + 34, cp[1] + 10];
296
+ const { E, H: Hd } = RIG.ik2(S, hand, 170, 128, -1);
297
+ const ua = RIG.taper(S, E, 50, 36, 6), fa = RIG.taper(E, Hd, 36, 26, 2);
298
+ g.strokeStyle = fuzzPat(g, t); g.lineWidth = 18; g.stroke(ua); g.stroke(fa); g.strokeStyle = inkPat(g, t); g.lineWidth = 12; g.stroke(ua); g.stroke(fa);
299
+ g.fillStyle = C.red; g.fill(ua); g.fill(fa);
300
+ // 杯(红赭小碗)
301
+ g.save(); g.translate(cp[0], cp[1]); g.rotate(-0.45 * k);
302
+ const cup = sm([[-34, -2], [-28, 22], [-12, 40], [12, 40], [28, 22], [34, -2]]);
303
+ shape(g, cup, C.red, 6, t);
304
+ const rim = new Path2D(); rim.ellipse(0, -2, 34, 7, 0, 0, Math.PI * 2); g.fillStyle = C.redD; g.fill(rim); inkLine(g, rim, 3.5, t);
305
+ g.restore();
306
+ // 手+手指(岩画式几道短线)
307
+ const hp = new Path2D(); hp.ellipse(Hd[0], Hd[1], 15, 12, Math.atan2(Hd[1] - E[1], Hd[0] - E[0]), 0, Math.PI * 2); shape(g, hp, C.red, 5, t);
308
+ const fa_ = Math.atan2(Hd[1] - E[1], Hd[0] - E[0]);
309
+ for (let j = -1; j <= 1; j++) { const p = new Path2D(), ox = Math.cos(fa_ + Math.PI / 2) * j * 7, oy = Math.sin(fa_ + Math.PI / 2) * j * 7; p.moveTo(Hd[0] + ox + Math.cos(fa_) * 12, Hd[1] + oy + Math.sin(fa_) * 12); p.lineTo(Hd[0] + ox + Math.cos(fa_) * 30, Hd[1] + oy + Math.sin(fa_) * 30); inkLine(g, p, 3, t); }
310
+ // 热气:三道波浪炭线,向上蠕动
311
+ for (let j = -1; j <= 1; j++) { const p = new Path2D(), x0 = cp[0] + j * 16, y0 = cp[1] - 22;
312
+ for (let s = 0; s <= 18; s++) { const q = s / 18, y = y0 - q * 58, x = x0 + Math.sin(q * 13 - t * 11 + j * 1.3) * 6; s ? p.lineTo(x, y) : p.moveTo(x, y); }
313
+ inkLine(g, p, 3.4, t); }
314
+ return G;
315
+ }
316
+
317
+ // ---------- 标题(全片 0.933–1.133s 淡出) ----------
318
+ function title(g, t) {
319
+ const a = t < 0.933 ? 1 : 1 - clamp((t - 0.933) / 0.2); if (a <= 0) return;
320
+ g.save(); g.globalAlpha = a; g.font = '78px "Kalam-700"'; g.letterSpacing = '7px'; g.textBaseline = 'alphabetic';
321
+ const txt = 'TEA ACROSS THE AGES', w = g.measureText(txt).width, sx = 930 / w;
322
+ g.translate(955, 1024); g.scale(sx, 1); g.fillStyle = '#9b2a18'; g.fillText(txt, -w / 2, 0);
323
+ g.restore();
324
+ }
325
+
326
+ return {
327
+ draw(c, lt, t) {
328
+ const ch = P.choreo(lt, t);
329
+ c.drawImage(bg(), 0, 0);
330
+ // 颜料层:动物 → 猫 → 少女 → 标题,整层叠岩面贴图
331
+ const L = P.scratch('cave_fig'), g = L.getContext('2d'); g.clearRect(0, 0, W, H);
332
+ animals(g, t);
333
+ // 猫整体以脚底 (540,905) 为锚缩 0.86:耳尖从 y≈535 降到 ≈587,和壁画框底边(550)留出 ≥35px
334
+ g.save(); g.translate(540, 905); g.scale(0.86, 0.86); g.translate(-540, -905); cat(g, ch, t, lt); g.restore();
335
+ girl(g, lt, t, ch); title(g, t);
336
+ g.globalCompositeOperation = 'source-atop'; g.drawImage(figTex(), 0, 0); g.globalCompositeOperation = 'source-over';
337
+ c.drawImage(L, 0, 0);
338
+ // 火光摇曳:中心与强度随噪声抖动
339
+ const n1 = P.noise(t * 3.1, 1.7), n2 = P.noise(t * 2.3, 8.1), fl = 0.14 + 0.11 * P.noise(t * 9.5, 3.3) + 0.04 * Math.sin(t * 23) + 0.03 * Math.sin(t * 37);
340
+ const fx = 1000 + n1 * 60, fy = 540 + n2 * 36;
341
+ // overlay 合成:提亮暖化但不洗白颜料的饱和度(lighter 会把人物洗成粉黄)
342
+ const gr = c.createRadialGradient(fx, fy, 10, fx, fy, 700 + n2 * 60); gr.addColorStop(0, `rgba(255,196,110,${fl * 2.6})`); gr.addColorStop(0.5, `rgba(255,160,80,${fl * 1.3})`); gr.addColorStop(1, 'rgba(255,140,60,0)');
343
+ c.save(); c.globalCompositeOperation = 'overlay'; c.fillStyle = gr; c.fillRect(0, 0, W, H);
344
+ // 暗处随火光一起「呼吸」
345
+ c.globalCompositeOperation = 'source-over';
346
+ const dk = c.createRadialGradient(fx, fy, 380, fx, fy, 1150); dk.addColorStop(0, 'rgba(20,8,2,0)'); dk.addColorStop(1, `rgba(20,8,2,${0.12 - fl * 0.4 + 0.08})`);
347
+ c.fillStyle = dk; c.fillRect(0, 0, W, H);
348
+ // 手印忽明忽暗:每个手印上叠一块独立噪声驱动的暖光
349
+ c.globalCompositeOperation = 'lighter';
350
+ HANDS.forEach(([x, y], k) => { const v = clamp(0.5 + 0.9 * P.noise(t * 7 + k * 3.7, k)); if (v < 0.05) return;
351
+ const hg = c.createRadialGradient(x, y, 5, x, y, 105); hg.addColorStop(0, `rgba(255,150,80,${0.2 * v})`); hg.addColorStop(1, 'rgba(255,120,60,0)'); c.fillStyle = hg; c.fillRect(x - 105, y - 105, 210, 210); });
352
+ // 金色余烬:左右两侧缓慢上漂、闪烁
353
+ c.globalCompositeOperation = 'lighter';
354
+ [[0, 380, 20], [1520, 1920, 20], [380, 1520, 9]].forEach(([x0, x1, n], s) => {
355
+ P.particles(n, 300 + s * 31, t, { x0, x1, y0: H + 30, y1: 120, drift: 22, life: 5.5 }).forEach(p => {
356
+ const tw = 0.3 + 0.7 * Math.abs(Math.sin(t * 7 + p.i * 1.9)), fade = Math.sin(p.q * Math.PI), r0 = 2.6 + p.s * 2.8;
357
+ c.fillStyle = `rgba(255,170,60,${0.32 * tw * fade})`; c.beginPath(); c.arc(p.x, p.y, r0 * 3.6, 0, Math.PI * 2); c.fill();
358
+ c.fillStyle = `rgba(255,224,130,${0.95 * tw * fade})`; c.beginPath(); c.arc(p.x, p.y, r0, 0, Math.PI * 2); c.fill();
359
+ });
360
+ });
361
+ c.restore();
362
+ },
363
+ };
364
+ })();