@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,194 @@
1
+ // 片段 · 故事型角色(y4,storytime):一个角色(AI 生成的帧库)在小房间里对镜头讲,姿势快切(0.1s)后定住,笑点靠反应特写和插入镜头。
2
+ // 镜头不慢推、不手持漂、角色不呼吸:定住就是定住,变化靠快切、跳近(反应特写砸进来)和插入镜头。
3
+ // data.character(必填):帧库——render.py 里写一个目录(文件名去扩展名 = 姿势名),或 {frames: {rest, talk, point, shrug, type, stiff, react, back, <姿势>_open: 路径}}。
4
+ // 代码画的小人('neutral' / 'bun' / 'author' / 造型对象)已停用:不给帧库就启动失败。
5
+ // 至少要有 rest 或 talk;缺的姿势退到 talk → rest;<姿势>_open 是张嘴帧(说话时一拍二换);react 是反应特写,back 是插入镜头前景的后脑勺。
6
+ // 帧图要透明底、角色脚底在图片底边中间,画出来高 ≈ 3.15 个头半径。没有 back 帧时插入镜头不画前景后脑勺。
7
+ // cues:point | title(角色说一句:at 这一帧气泡弹出、开始张嘴;嘴按 text 的字数说 ≈6.5 字/秒;data.pose 指定姿势 talk/point/shrug/type)
8
+ // 气泡里写什么:有 sub 就写 sub(≤12 字的短句最好),没有才写 text——口播原文交给管线的字幕,气泡别和字幕重复一遍
9
+ // highlight(「?」「!」弹出+摊手担心:data.mark 默认「?」)
10
+ // react(反应镜头:at 这一帧跳到大特写,贴 react 帧(没有就退到 talk / rest)+汗滴,一言不发;dur 默认到下一个 cue)
11
+ // insert(插入镜头:at 这一帧切到桌上那台电脑的屏幕特写——sub = 角色打的那一行(打字、发送),之后 AI 的回复像代码墙一样越滚越快,
12
+ // text = 砸出来的大字(如「3000行」),在 data.hit(绝对秒,默认 at+1.3,没有 sub 时 at+0.4)砸出、屏幕一震;
13
+ // data.count + data.unit 让右上角计数器在砸出那一刻停在这个数;dur 默认到下一个 cue)
14
+ // 房间里有桌上的电脑(插入镜头切进去的就是它的屏幕)、马克杯、墙上的书架和绿植,右边不再是空墙。
15
+ // 没有口播包络时,口型由字数和时间合成(一拍二,0/1/2 三张嘴);有包络就把 window.VO_ENV 交给管线(TOON.mouth 自动读)。
16
+ // safe:气泡、插入镜头的大字排在 top..H−bottom 之间。alpha:不画房间,角色和气泡照画(气泡本来就是白底黑字);插入镜头只画显示器和后脑勺,四周透明。
17
+ // spec.theme(全片色板 ctx.pal):墙 bg、地面和桌子是 bg 往 ink / sub 调的明暗,书、杯子、绿植 accent / accent2,气泡 surface 底 ink 字,
18
+ // 屏幕是深色(ink 或 bg 压暗),代码色块、计数器、砸出的大字只用 accent / accent2 和它们的明暗。
19
+ CLIPS.y4_storytime = (() => {
20
+ const { clamp, lerp, rng } = U;
21
+ // 代码墙色块(屏幕上的语法高亮):不放紫、紫蓝
22
+ const COLS = ['#E06C75', '#5FB3A1', '#C3E88D', '#F78C6C', '#89DDFF', '#FFCB6B', '#8A9199'];
23
+ const C0 = { wall: '#DCE9EC', floor: '#E8DCC8', desk: '#E2C29B', deskSide: '#CDA97F', line: '#5B6470', sky: '#BFE3F5', screen: '#20222D', cloud: '#fff', shelf: '#D9C3A0',
24
+ books: ['#8FB3C9', '#E7A37B', '#B9C98F', '#C9B28F'], pot: '#E7A37B', leaf: '#8DBF8B', stand: '#9AA0AC', bezel: '#2B2D36', mug: '#F26B4F', codes: COLS,
25
+ insertBg: '#15161C', gutter: '#4A4F66', bubble: '#2F3342', sent: '#3B82F6', sentInk: '#fff', bar: '#2A2D3A', dots: ['#FF5F57', '#FEBC2E', '#28C840'], appText: '#9AA3B8', hot: '#FFCB6B',
26
+ slamDim: '10,10,16', fast: '32,34,45', sayBg: '#fff', sayInk: '#1B1B1F', type: '#fff' };
27
+ // 全片色板 → 房间和屏幕的一整套色(只从 6 个键派生)
28
+ const fromPal = p => { const dk = PAL.dark(p), light = dk ? p.ink : p.surface, screen = dk ? PAL.mix(p.bg, '#000000', 0.35) : PAL.mix(p.ink, p.bg, 0.08);
29
+ return { wall: p.bg, floor: PAL.mix(p.bg, p.ink, 0.07), desk: PAL.mix(p.bg, p.sub, 0.25), deskSide: PAL.mix(p.bg, p.sub, 0.4), line: dk ? PAL.mix(p.sub, p.bg, 0.2) : p.sub,
30
+ sky: PAL.mix(p.bg, p.accent2, 0.3), screen, cloud: PAL.mix(light, PAL.mix(p.bg, p.accent2, 0.3), 0.3), shelf: PAL.mix(p.bg, p.sub, 0.3),
31
+ books: [p.accent, p.accent2, PAL.mix(p.accent, p.bg, 0.45), PAL.mix(p.accent2, p.bg, 0.45)], pot: p.accent, leaf: p.accent2, stand: PAL.mix(p.sub, p.bg, 0.3), bezel: PAL.mix(screen, light, 0.12), mug: p.accent,
32
+ codes: [p.accent, PAL.mix(p.accent2, light, 0.4), PAL.mix(light, p.accent, 0.3), PAL.mix(p.accent2, light, 0.65), PAL.mix(light, screen, 0.45), PAL.mix(p.accent, light, 0.5), PAL.mix(light, screen, 0.65)], // 深屏上看得见:accent2 往亮色提
33
+ insertBg: dk ? PAL.mix(p.bg, '#000000', 0.55) : PAL.mix(p.ink, '#000000', 0.25), gutter: PAL.mix(screen, light, 0.25), bubble: PAL.mix(screen, light, 0.15), sent: p.accent2,
34
+ sentInk: PAL.on(p.accent2, [p.surface, p.ink, p.bg]), bar: PAL.mix(screen, light, 0.08), dots: [p.accent, PAL.mix(light, screen, 0.5), p.accent2], appText: PAL.mix(light, screen, 0.4), hot: p.accent,
35
+ slamDim: PAL.rgb(screen).join(','), fast: PAL.rgb(screen).join(','), sayBg: p.surface, sayInk: p.ink, type: light }; };
36
+ const LINES = (() => { const r = rng(42), out = []; let ind = 0; // 色块存色号(第几个),画的时候按 C.codes 取色
37
+ for (let i = 0; i < 400; i++) { if (r() < 0.18) ind = Math.min(4, ind + 1); else if (r() < 0.2) ind = Math.max(0, ind - 1);
38
+ const toks = []; let x = ind * 34; const n = 1 + (r() * 4 | 0); for (let k = 0; k < n; k++) { const w = 30 + r() * 150; toks.push([x, w, (r() * COLS.length) | 0]); x += w + 14; } out.push(toks); } return out; })();
39
+ const NEED_FRAMES = 'y4 需要角色帧库 data.character.frames(AI 生帧,见 references/10-角色.md);代码画的小人已停用。没有生图能力就别用 y4:用 y2/y5 或 scenes 里用手、物件、背影讲故事。';
40
+ let keys, says, L, shots, FR, C = C0;
41
+ return {
42
+ fonts: ['PuHui-Bold', 'PuHui-Black'],
43
+ safe: true,
44
+ init(ctx) {
45
+ const { W, H, u } = ctx, ch = ctx.data.character;
46
+ C = ctx.pal ? fromPal(ctx.pal) : C0;
47
+ // 角色只认帧库(对象带 frames;帧图已由 clip.js 加载,缺图在启动时就失败)。没有帧库 / 写预设名 → 启动失败
48
+ if (!ch || typeof ch !== 'object' || !ch.frames || typeof ch.frames !== 'object') throw new Error(NEED_FRAMES + (ch != null ? `(收到的 data.character = ${JSON.stringify(ch)})` : '(spec 里没写 data.character)'));
49
+ FR = Object.fromEntries(Object.entries(ch.frames).map(([k, v]) => [k, ctx.IMG[v]]));
50
+ if (!FR.rest && !FR.talk) throw new Error('y4 data.character.frames 至少要有 rest 或 talk 一张(现有:' + Object.keys(FR).join(' ') + ')');
51
+ L = { cx: ctx.portrait ? W * 0.42 : W * 0.3, fy: ctx.portrait ? H * 0.8 : H * 1.02, R: (ctx.portrait ? 230 : 215) * u };
52
+ L.dy = L.fy - L.R * 0.55; // 桌面高度
53
+ L.mon = ctx.portrait ? { x: W * 0.66, w: 330 * u, h: 240 * u } : { x: W * 0.6, w: 520 * u, h: 340 * u }; // 桌上的电脑(屏幕左上角 x、宽高)
54
+ says = ctx.of('point', 'title').map(q => ({ q, end: q.at + Math.max(0.6, [...(q.text || '')].length / 6.5) }));
55
+ keys = [[0, 'rest', 'happy']]; let n = 0;
56
+ for (const q of ctx.cues) {
57
+ if (q.kind === 'point' || q.kind === 'title') keys.push([q.at, (q.data && q.data.pose) || (n++ % 2 ? 'point' : 'talk'), 'talk']);
58
+ if (q.kind === 'highlight') keys.push([q.at, 'shrug', 'worry']);
59
+ if (q.kind === 'react' || q.kind === 'insert') keys.push([q.at, 'stiff', 'blank']);
60
+ }
61
+ // 特写镜头(react / insert)占的时间段
62
+ shots = ctx.of('react', 'insert').map(q => { const nx = ctx.cues.find(z => z.at > q.at + 1e-6); const end = q.dur ? q.at + q.dur : nx ? nx.at : ctx.dur + 1;
63
+ const hit = q.kind === 'insert' ? (q.data && q.data.hit != null ? +q.data.hit : q.at + (q.sub ? 1.3 : 0.4)) : 0; return { q, end, hit }; });
64
+ },
65
+ draw(c, t, ctx) {
66
+ const { W, H, u } = ctx, ts = MO.step(t, 24); // 角色 24fps,镜头 60fps
67
+ const shot = shots.find(s => t >= s.q.at - 1e-6 && t < s.end);
68
+ if (shot && shot.q.kind === 'insert') return insert(c, t, ctx, shot);
69
+ const inReact = !!shot;
70
+ // 镜头:中景定住;反应=同机位跳到 1.9 倍大特写(砸镜:前 0.3s 从 2.2 弹回,之后定住)
71
+ let z = 1, cy = L.fy - L.R * 1.6;
72
+ if (inReact) { const l = t - shot.q.at; z = 1.9 * (1 + 0.15 * Math.max(0, 1 - MO.springHz(l, 3, 11))); cy = L.fy - L.R * 2.2; }
73
+ if (!ctx.alpha) { c.fillStyle = C.wall; c.fillRect(0, 0, W, H); }
74
+ CAM.with(c, { x: inReact ? L.cx : W / 2, y: inReact ? cy : H / 2, z }, g => {
75
+ if (!ctx.alpha) room(g, t, ctx);
76
+ const st = TOON.poseAt(keys, ts), say = says.find(s => t >= s.q.at - 1e-6 && t < s.end);
77
+ const mouth = window.VO_ENV ? TOON.mouth(t) : say ? [1, 2, 1, 0, 2, 1][Math.floor(MO.step(t - say.q.at + 1e-6, 12) * 12) % 6] : 0;
78
+ const info = frameChar(g, ts, st, inReact, mouth);
79
+ if (!ctx.alpha) desk(g, t, ctx);
80
+ if (inReact) { const sw = clamp((t - shot.q.at - 0.3) / 0.9); if (sw > 0) TOON.sweat(g, L.cx + L.R * 0.88, info.head[1] - L.R * 0.1, 30 * u, MO.expoOut(sw) * 0.6 + sw * 0.4); }
81
+ for (const h of ctx.of('highlight')) { const l = ctx.lt(t, h.at); if (l <= 0) continue;
82
+ g.save(); g.translate(info.head[0] + L.R * 1.35, info.head[1] - L.R * 0.6); g.rotate(0.14 + MO.settle(l, 0.12, 3, 6)); TOON.mark(g, (h.data && h.data.mark) || '?', 0, 0, clamp(l / 0.26), 130 * u, C === C0 ? undefined : ctx.pal.accent); g.restore(); }
83
+ });
84
+ // 对话气泡(屏幕空间,不跟镜头):at 这一帧弹出,说完 0.6s 后收起;特写切回来后,特写之前的那句不再出现
85
+ if (!inReact) for (const s of says) {
86
+ const l = ctx.lt(t, s.q.at), out = clamp((t - s.end - 0.6) / 0.2); if (l <= 0 || out >= 1) continue;
87
+ const nx = says[says.indexOf(s) + 1]; if (nx && t >= nx.q.at - 1e-6) continue;
88
+ if (shots.some(sh => sh.q.at > s.q.at && sh.q.at <= t)) continue;
89
+ const txt = s.q.sub || s.q.text || '';
90
+ const bw0 = ctx.portrait ? W * 0.84 : W * 0.42, f = TY.fit(c, txt, bw0 - 80 * u, (ctx.portrait ? 64 : 62) * u, 'PuHui-Bold', { maxLines: 2, min: 0.72, balance: true });
91
+ c.font = `${f.size}px "PuHui-Bold"`; const bw = Math.min(bw0, Math.max(...f.lines.map(x => c.measureText(x).width)) + 80 * u);
92
+ const bh = f.lines.length * f.size * 1.3 + 56 * u, bx = ctx.portrait ? (W - bw) / 2 : W * 0.5, by = ctx.safe.top + (ctx.portrait ? H * 0.06 : H * 0.09);
93
+ const sc = MO.backOut(clamp(l / 0.25), 2.2) * (1 - out);
94
+ c.save(); c.translate(bx + 60 * u, by + bh); c.scale(sc, sc); c.translate(-(bx + 60 * u), -(by + bh));
95
+ c.fillStyle = C.sayBg; c.strokeStyle = C.sayInk; c.lineWidth = 6 * u; c.lineJoin = 'round';
96
+ c.beginPath(); c.roundRect(bx, by, bw, bh, 40 * u); c.moveTo(bx + 70 * u, by + bh); c.lineTo(bx + (ctx.portrait ? 40 : 10) * u, by + bh + 60 * u); c.lineTo(bx + 130 * u, by + bh); c.fill(); c.stroke();
97
+ c.fillStyle = C.sayBg; c.fillRect(bx + 76 * u, by + bh - 8 * u, 50 * u, 12 * u);
98
+ c.fillStyle = C.sayInk; c.textBaseline = 'alphabetic';
99
+ f.lines.forEach((ln, i) => c.fillText(ln, bx + 40 * u, by + 26 * u + (i + 1) * f.size * 1.2));
100
+ c.restore();
101
+ }
102
+ },
103
+ };
104
+ // 帧库角色:按当前姿势名取帧(缺的退到 talk → rest),说话时一拍二换 <姿势>_open;换姿势那一下沿用 poseAt 的挤压回弹
105
+ function frameChar(g, ts, st, inReact, mouth) {
106
+ let name = 'rest'; for (const k of keys) if (ts >= k[0]) name = k[1];
107
+ if (inReact) name = 'react';
108
+ const pick = n => FR[n] || FR.talk || FR.rest;
109
+ const im = (!inReact && mouth && FR[name + '_open']) || pick(name), [iw, ih] = MD.size(im), h = L.R * 3.15, w = iw * h / ih;
110
+ g.save(); g.translate(L.cx, L.fy); g.scale(1 / Math.sqrt(st.squash), st.squash); g.drawImage(im, -w / 2, -h, w, h); g.restore();
111
+ return { head: [L.cx, L.fy - h * 0.78], hl: [L.cx - w * 0.4, L.fy - h * 0.45], hr: [L.cx + w * 0.4, L.fy - h * 0.45] };
112
+ }
113
+ // 房间:墙、地、窗(慢飘的云)、书架、画框。只画一次的东西不缓存——片段短,画得起
114
+ function room(g, t, ctx) {
115
+ const { W, H, u } = ctx, X0 = -W, Y0 = -H, fy = L.fy;
116
+ g.fillStyle = C.wall; g.fillRect(X0, Y0, W * 3, H * 3);
117
+ g.fillStyle = C.floor; g.fillRect(X0, fy + 10 * u, W * 3, H * 2);
118
+ g.lineWidth = 3 * u; g.strokeStyle = C.line; g.lineJoin = 'round'; g.beginPath(); g.moveTo(X0, fy + 10 * u); g.lineTo(X0 + W * 3, fy + 10 * u); g.stroke();
119
+ // 窗:天空+慢飘的云
120
+ const wx = ctx.portrait ? W * 0.08 : W * 0.05, wy = ctx.portrait ? H * 0.2 : H * 0.12, ww = ctx.portrait ? W * 0.3 : W * 0.17, wh = ww * 0.7;
121
+ g.save(); g.beginPath(); g.roundRect(wx, wy, ww, wh, 12 * u); g.clip(); g.fillStyle = C.sky; g.fillRect(wx, wy, ww, wh);
122
+ for (const [k, r, sp] of [[0.2, 46, 18], [0.7, 34, 12]]) { const x = wx - 60 * u + ((k * ww + t * sp * u) % (ww + 120 * u)), y = wy + wh * (0.35 + k * 0.3); g.fillStyle = C.cloud; g.beginPath(); g.arc(x, y, r * u, 0, 7); g.arc(x + r * 0.9 * u, y + 6 * u, r * 0.75 * u, 0, 7); g.arc(x - r * 0.9 * u, y + 8 * u, r * 0.65 * u, 0, 7); g.fill(); }
123
+ g.restore(); g.strokeStyle = C.line; g.beginPath(); g.roundRect(wx, wy, ww, wh, 12 * u); g.moveTo(wx + ww / 2, wy); g.lineTo(wx + ww / 2, wy + wh); g.moveTo(wx, wy + wh / 2); g.lineTo(wx + ww, wy + wh / 2); g.stroke();
124
+ // 墙上:横屏右边一块书架;竖屏书架在窗下方右侧
125
+ const r = rng(9), sx = ctx.portrait ? W * 0.62 : W * 0.68, sy = ctx.portrait ? H * 0.3 : H * 0.36, sw = ctx.portrait ? W * 0.3 : W * 0.22;
126
+ g.fillStyle = C.shelf; g.fillRect(sx, sy, sw, 16 * u); g.strokeRect(sx, sy, sw, 16 * u);
127
+ let x = sx + 16 * u; while (x < sx + sw * 0.7) { const w = (22 + r() * 22) * u, h = (70 + r() * 40) * u; g.fillStyle = C.books[(r() * 4) | 0]; g.fillRect(x, sy - h, w, h); g.strokeRect(x, sy - h, w, h); x += w + 5 * u; }
128
+ g.fillStyle = C.pot; const px = sx + sw * 0.83; g.beginPath(); g.moveTo(px - 30 * u, sy); g.lineTo(px - 38 * u, sy - 56 * u); g.lineTo(px + 38 * u, sy - 56 * u); g.lineTo(px + 30 * u, sy); g.closePath(); g.fill(); g.stroke();
129
+ g.fillStyle = C.leaf; for (const [dx, a] of [[-18, -0.5], [0, 0], [18, 0.5]]) { g.beginPath(); g.ellipse(px + dx * u, sy - 100 * u, 16 * u, 46 * u, a, 0, 7); g.fill(); g.stroke(); }
130
+ }
131
+ // 桌子挡在腰前(角色线最黑最粗,背景线细而灰);桌上的电脑、马克杯
132
+ function desk(g, t, ctx) {
133
+ const { W, H, u } = ctx, X0 = -W, dy = L.dy; g.lineWidth = 3 * u; g.strokeStyle = C.line;
134
+ // 电脑:屏幕微微发光,上面是滚动的代码色块(插入镜头切进去就是它)
135
+ const M = L.mon, mx = M.x, my = dy - M.h - 60 * u;
136
+ g.fillStyle = C.stand; g.fillRect(mx + M.w / 2 - 18 * u, dy - 62 * u, 36 * u, 62 * u); g.strokeRect(mx + M.w / 2 - 18 * u, dy - 62 * u, 36 * u, 62 * u);
137
+ g.beginPath(); g.ellipse(mx + M.w / 2, dy, 90 * u, 10 * u, 0, 0, 7); g.fill(); g.stroke();
138
+ g.fillStyle = C.bezel; g.beginPath(); g.roundRect(mx, my, M.w, M.h, 16 * u); g.fill(); g.stroke();
139
+ g.save(); g.beginPath(); g.roundRect(mx + 14 * u, my + 14 * u, M.w - 28 * u, M.h - 28 * u, 6 * u); g.clip(); g.fillStyle = C.screen; g.fillRect(mx, my, M.w, M.h);
140
+ const lh = 22 * u, off = (t * 30 * u) % lh; for (let i = 0; i < 20; i++) { const y = my + 30 * u + i * lh - off; for (const [x, w, col] of LINES[i % LINES.length]) { g.fillStyle = C.codes[col % C.codes.length]; g.globalAlpha = 0.75; g.fillRect(mx + 26 * u + x * 0.3 * u, y, w * 0.3 * u, 8 * u); } }
141
+ g.restore(); g.globalAlpha = 1;
142
+ g.fillStyle = C.desk; g.fillRect(X0, dy, W * 3, 40 * u); g.strokeRect(X0, dy, W * 3, 40 * u);
143
+ g.fillStyle = C.deskSide; g.fillRect(X0, dy + 40 * u, W * 3, H); g.strokeRect(X0, dy + 40 * u, W * 3, H);
144
+ // 马克杯
145
+ const cx = ctx.portrait ? W * 0.1 : L.cx - L.R * 1.9; g.fillStyle = C.mug; g.beginPath(); g.roundRect(cx, dy - 92 * u, 70 * u, 92 * u, 10 * u); g.fill(); g.stroke();
146
+ g.beginPath(); g.arc(cx + 78 * u, dy - 52 * u, 22 * u, -1.2, 1.2); g.lineWidth = 9 * u; g.stroke(); g.lineWidth = 3 * u;
147
+ }
148
+ // 插入镜头:电脑屏幕特写(过肩),打字→发送→代码墙加速滚→大字砸出+屏幕一震(和示范片 s2_screen 同一套)
149
+ function insert(c, t, ctx, shot) {
150
+ const { W, H, u, safe } = ctx, q = shot.q, d = q.data || {}, t0 = q.at, msg = q.sub || '', T_SEND = t0 + (msg ? 0.1 + Math.min(0.9, [...msg].length / 14) + 0.1 : 0.05), T_HIT = Math.max(shot.hit, T_SEND + 0.2);
151
+ const lt = t - t0, slam = t - T_HIT;
152
+ const scrollAt = tt => { const k = Math.max(0, tt - T_SEND - 0.15), span = Math.max(0.3, T_HIT - T_SEND - 0.15); return 46 * u * (k * 6 + Math.pow(k / span * 1.8, 3) * 6); };
153
+ const shake = slam > 0 && slam < 0.3 ? MO.settle(slam, 14 * u, 9, 14) : 0;
154
+ const zoom = 1.06 - 0.06 * MO.expoOut(clamp(lt / 0.25)) + (slam > 0 ? 0.04 * MO.expoOut(clamp(slam / 0.12)) : 0); // 切进来:1.06 落到 1(像推过去的一下);大字砸出时再推一档,之后定住
155
+ if (!ctx.alpha) { c.fillStyle = C.insertBg; c.fillRect(0, 0, W, H); } // 透明模式:只留显示器和后脑勺,口播画面从四周透出来
156
+ const bx = W * 0.06, by = safe.top + H * 0.05, bw = W * 0.88, bh = H - safe.bottom - by - H * (ctx.portrait ? 0.2 : 0.06);
157
+ CAM.with(c, { x: W / 2 + shake, y: H / 2 - shake * 0.5, z: zoom }, c => {
158
+ c.fillStyle = C.bezel; c.beginPath(); c.roundRect(bx, by, bw, bh, 34 * u); c.fill();
159
+ const sx = bx + 40 * u, sy = by + 40 * u, sw = bw - 80 * u, sh = bh - 80 * u;
160
+ c.save(); c.beginPath(); c.roundRect(sx, sy, sw, sh, 14 * u); c.clip(); c.fillStyle = C.screen; c.fillRect(sx, sy, sw, sh);
161
+ const scr = scrollAt(t), top = sy + 64 * u;
162
+ // 我那一行:右侧气泡,打字(光标闪),发送后变蓝
163
+ if (msg) { const bubY = top + 110 * u - Math.min(scr, 600 * u), p = clamp((t - t0 - 0.1) / Math.max(0.2, T_SEND - t0 - 0.2));
164
+ const mf = TY.fit(c, msg, sw * 0.7 - 60 * u, (ctx.portrait ? 44 : 40) * u, 'PuHui-Bold', { maxLines: 3, min: 0.75, balance: true }), fs = mf.size, lh = fs * 1.3; // 长句折行(最多 3 行),气泡往下长
165
+ const bwid = Math.max(...mf.lines.map(l => TY.width(c, l, fs, 'PuHui-Bold'))) + 60 * u, bht = 78 * u + (mf.lines.length - 1) * lh;
166
+ if (bubY > sy) { c.fillStyle = t >= T_SEND ? C.sent : C.bubble; c.beginPath(); c.roundRect(sx + sw - 30 * u - bwid, bubY - 52 * u, bwid, bht, 24 * u); c.fill();
167
+ let left = Math.round(p * mf.lines.reduce((a, l) => a + [...l].length, 0)); // 逐行打出来,光标跟在最后打到的那一行
168
+ const ns = mf.lines.map(ln => { const n = Math.min(left, [...ln].length); left -= n; return n; }), curK = Math.max(0, ns.findLastIndex(n => n > 0)), blink = t < T_SEND && Math.floor(t * 4) % 2;
169
+ mf.lines.forEach((ln, k) => { if (k > curK) return;
170
+ TY.text(c, [...ln].slice(0, ns[k]).join('') + (blink && k === curK ? '|' : ''), sx + sw - 30 * u - bwid + 30 * u, bubY + 2 * u + k * lh, { size: fs, fam: 'PuHui-Bold', color: t >= T_SEND ? C.sentInk : C.type, align: 'left' }); }); } }
171
+ // AI 的回复:代码墙,越滚越快
172
+ if (t > T_SEND + 0.05) { const lh = 46 * u, y0 = top + (msg ? 220 : 40) * u - scr;
173
+ for (let i = Math.max(0, Math.floor((top - y0) / lh)); ; i++) { const y = y0 + i * lh; if (y > sy + sh) break; if (y < top) continue;
174
+ const shown = clamp((t - T_SEND - 0.05 - Math.min(i, 12) * 0.03) / 0.05); if (shown <= 0) continue;
175
+ c.fillStyle = C.gutter; c.fillRect(sx + 30 * u, y - 12 * u, 44 * u, 16 * u);
176
+ for (const [x, w, col] of LINES[i % LINES.length]) { c.fillStyle = C.codes[col % C.codes.length]; c.globalAlpha = 0.9; c.beginPath(); c.roundRect(sx + 110 * u + x * u, y - 14 * u, w * u * shown, 20 * u, 10 * u); c.fill(); } c.globalAlpha = 1; }
177
+ const v = (scrollAt(t + 1 / 60) - scr) * 60; if (v > 1500 * u) { c.fillStyle = `rgba(${C.fast},${clamp((v - 1500 * u) / (6000 * u)) * 0.5})`; c.fillRect(sx, top, sw, sh); } }
178
+ // 顶栏:三个圆点、标题、计数器(砸出那一刻停在 data.count)
179
+ c.fillStyle = C.bar; c.fillRect(sx, sy, sw, 64 * u);
180
+ C.dots.forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(sx + 42 * u + i * 34 * u, sy + 32 * u, 10 * u, 0, 7); c.fill(); });
181
+ TY.text(c, d.app || 'AI助手', sx + sw / 2, sy + 44 * u, { size: 30 * u, fam: 'PuHui-Bold', color: C.appText });
182
+ if (d.count != null && t > T_SEND + 0.1) { const N = +d.count, k = slam >= 0 ? N : Math.min(N - 1, Math.floor(Math.pow(clamp((t - T_SEND - 0.15) / Math.max(0.2, T_HIT - T_SEND - 0.15)), 2.4) * (N - 1)));
183
+ TY.text(c, `${k}${d.unit || ''}`, sx + sw - 30 * u, sy + 44 * u, { size: 32 * u, fam: 'PuHui-Bold', color: slam >= 0 ? C.hot : C.appText, align: 'right' }); }
184
+ // 大字砸出(backOut 过冲)+压暗
185
+ if (slam > 0 && q.text) { c.fillStyle = `rgba(${C.slamDim},${0.6 * MO.expoOut(clamp(slam / 0.15))})`; c.fillRect(sx, sy, sw, sh);
186
+ const fs = TY.fit(c, q.text, sw * 0.86, (ctx.portrait ? 200 : 230) * u, 'PuHui-Black', { maxLines: 2, min: 0.4, balance: true });
187
+ fs.lines.forEach((ln, i) => TY.pop(c, ln, sx + sw / 2, sy + sh / 2 + fs.size * 0.35 + (i - (fs.lines.length - 1) / 2) * fs.size * 1.1, clamp(slam / 0.24), { size: fs.size, fam: 'PuHui-Black', color: C.hot, over: 3.5, stroke: C.insertBg, strokeW: 18 * u })); }
188
+ c.restore();
189
+ });
190
+ // 前景:帧库给了 back 就贴角色的后脑勺(过肩),定住;砸出时往后一缩。没有 back 帧就不画(不再用代码画后脑勺)
191
+ const hx = W * (ctx.portrait ? 0.2 : 0.17), hy = by + bh - 60 * u + (slam > 0 ? -MO.settle(slam, 18 * u, 4, 7) : 0), s = (ctx.portrait ? 0.8 : 0.75) * u;
192
+ if (FR.back) { const [iw, ih] = MD.size(FR.back), h = 560 * s, w = iw * h / ih; c.drawImage(FR.back, hx - w / 2, hy + 500 * s - h, w, h); }
193
+ }
194
+ })();
@@ -0,0 +1,134 @@
1
+ // 片段 · 动态文字(y5):字就是演员。一个 cue 一页:主词在 at 这一帧砸进来(弹簧过冲 ≈8%),辅句从槽里升起;
2
+ // 换页是色带擦除,新页面露出来的那一帧=at(擦除在 at 之前 0.3s 开始)。
3
+ // cues:title | point(一页:text 主词(太长自动缩字号、最多两行),sub 辅句,data.label 左上小标签如「01」)
4
+ // number(一页大数字:data {value, prefix, suffix, decimals},at = 数字落定的时刻,页面提前 0.7s 出现开始滚)
5
+ // highlight(当前页里 data.word 这个词刷荧光笔+放大一下;不给 word 就刷整个主词)
6
+ // sub 里的关键词:data.key 写辅句里的一个词,那个词用强调色(示范片「下一行」那样)
7
+ // number 的说明写 text 或 sub 都行(clip.js 已统一);data.label 照样有
8
+ // 两个以上的 point 页 = 一份清单:画进度轨(01/02/03,告诉观众第几个、一共几个;横屏右上、竖屏在下方)和背景巨型序号(低对比、斜向慢漂,data.num 可指定)。
9
+ // data.palette = [[底色, 字色], ...] 换配色(写了就优先,盖过 spec.theme)。alpha 模式不画底,字白色带深色描边。safe:主词块在 top..H−bottom 之间居中,进度轨也在这一带里。
10
+ // spec.theme(全片色板 ctx.pal):页面轮换 [bg 底 ink 字] → [accent 底] → [ink 底 bg 字] → [accent2 底](强调色页的字取 surface/ink/bg 里最清楚的那个);
11
+ // 荧光笔、辅句关键词、换页色带都只从 bg / ink / accent / accent2 里挑和当前页底色不同的。
12
+ CLIPS.y5_kinetic_type = (() => {
13
+ const { clamp, lerp } = U;
14
+ const PAGES0 = [['#FFD23F', '#111111'], ['#FF5A36', '#FFFFFF'], ['#14213D', '#FFD23F'], ['#F4EFE6', '#111111']];
15
+ let pages, LIST, TP = null; // TP:全片色板(没写 data.palette 时才用)
16
+ const same = (a, b) => String(a).toLowerCase() === String(b).toLowerCase();
17
+ // 在候选色里挑一个和页底、字色都不同、压在页底上最清楚的
18
+ const pick = (bg, fg, cands) => { const ok = cands.filter(x => !same(x, bg) && !same(x, fg)); return ok.length ? PAL.on(bg, ok) : cands[0]; };
19
+ const accentOf = col => TP ? pick(col[0], col[1], [TP.accent, TP.accent2]) : col[0] === '#FFD23F' || col[0] === '#F4EFE6' || col[1] === '#FFD23F' ? '#FF5A36' : '#FFD23F'; // 字本身是黄的(深蓝页)就用橙,不然着色看不出来
20
+ const fromPal = p => { const txt = bg => PAL.on(bg, [p.surface, p.ink, p.bg]); return [[p.bg, p.ink], [p.accent, txt(p.accent)], [p.ink, p.bg], [p.accent2, txt(p.accent2)]]; };
21
+ return {
22
+ fonts: ['PuHui-Black', 'PuHui-Heavy', 'PuHui-Bold'],
23
+ safe: true,
24
+ init(ctx) {
25
+ TP = ctx.data.palette ? null : ctx.pal;
26
+ const { W, u } = ctx, pal = ctx.data.palette || (TP ? fromPal(TP) : PAGES0), g = document.createElement('canvas').getContext('2d');
27
+ pages = ctx.of('title', 'point', 'number').map((q, i) => {
28
+ const num = q.kind === 'number', nd = q.data || {};
29
+ const text = num ? (nd.prefix || '') + TY.fmt(nd.value || 0, nd.decimals ?? 0) + (nd.suffix || '') : (q.text || '');
30
+ const S0 = (ctx.portrait ? 380 : 300) * u;
31
+ let f = TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 1, min: 0.55, soft: true }); // 先缩字号放一行,再不行折两行;单个长英文词再缩也要放得下
32
+ if (!f.ok) f = num ? TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 1 }) // 数字不折行(滚动时逐字变宽,折开会跳):只缩字号
33
+ : TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 2, min: 0.3, balance: true });
34
+ const sf = q.sub ? TY.fit(g, q.sub, W * 0.86, (ctx.portrait ? 64 : 80) * u, 'PuHui-Heavy', { maxLines: 2, min: 0.6, balance: true }) : null; // 辅句:放不下先缩、再折两行
35
+ const show = num ? q.at - 0.7 : q.at;
36
+ return { q, i, num, nd, text, f, sf, show, col: pal[i % pal.length] };
37
+ });
38
+ const pts = pages.filter(p => p.q.kind === 'point'); pts.forEach((p, k) => { p.ord = k + 1; });
39
+ LIST = pts.length >= 2 ? pts : null;
40
+ // 高亮的词不在页上:只告警(以前什么都不发生)
41
+ for (const h of ctx.of('highlight')) { const w = h.data && h.data.word; if (!w) continue; const pg = [...pages].reverse().find(p => p.show <= h.at + 1e-6);
42
+ if (pg && !pg.f.lines.some(l => l.includes(w))) console.warn(`y5 highlight at=${h.at}:主词里没有「${w}」(主词「${pg.text}」;荧光笔只刷主词,辅句里的词用 data.key 着色),这一下什么都不做`); }
43
+ },
44
+ draw(c, t, ctx) {
45
+ const { W, H, u } = ctx;
46
+ let k = -1; pages.forEach((p, i) => { if (t >= p.show - 1e-6) k = i; });
47
+ const P = pages[Math.max(0, k)]; if (!P) return;
48
+ const page = (p, c2) => drawPage(c2, p, t, ctx);
49
+ // 底+点阵
50
+ if (!ctx.alpha) { c.fillStyle = P.col[0]; c.fillRect(0, 0, W, H); dots(c, t, P.col[1], 0.07, u, W, H); }
51
+ if (k >= 0) {
52
+ // 拍点微冲:每个 cue 落下时整屏放大 1.5%,0.15s 衰减
53
+ let bump = 1; for (const q of ctx.cues) { const l = t - q.at; if (l >= 0 && l < 0.4) bump += 0.015 * Math.exp(-l * 22); }
54
+ // 镜头不漂不晃:落定就停,运动只在拍点上(上面的微冲、主词砸入、换页色带)
55
+ CAM.with(c, { x: W / 2, y: H / 2, z: bump }, cc => { ghost(cc, P, t, ctx); page(P, cc); });
56
+ track(c, P, t, ctx);
57
+ }
58
+ // 换页:两条色带从左扫到右,最后一条后面露出新页(露出那一帧=新页的 show 时刻)
59
+ const N = pages[k + 1];
60
+ if (N && !ctx.alpha) {
61
+ const p = (t - (N.show - 0.3)) / 0.36; if (p > 0 && p < 1) {
62
+ const mid = (TP ? [TP.accent, TP.accent2, TP.ink, TP.bg] : ['#FFD23F', '#FF5A36', '#14213D', '#3A86FF']).find(x => !same(x, P.col[0]) && !same(x, N.col[0])), cols = [mid, N.col[0]], sk = H * 0.35; // 两条:一条和前后底色都不同的强调色+新底色(以前第一条是字色,白字页前会整屏闪一帧白)
63
+ const edge = q => lerp(-sk - 40, W + sk + 40, MO.expoInOut(clamp(q)));
64
+ cols.forEach((col, i) => { const x = edge((p - i * 0.12) / 0.76); c.save(); c.beginPath(); c.moveTo(-sk - 60, 0); c.lineTo(x + sk, 0); c.lineTo(x, H); c.lineTo(-sk - 60, H); c.closePath(); c.clip(); c.fillStyle = col; c.fillRect(0, 0, W, H); c.restore(); });
65
+ }
66
+ }
67
+ },
68
+ };
69
+ function dots(c, t, col, a, u, W, H) { const g = 64 * u, ox = (t * 36 * u) % g, oy = (t * 18 * u) % g; c.save(); c.globalAlpha = a; c.fillStyle = col; for (let y = -g; y < H + g; y += g) for (let x = -g; x < W + g; x += g) { c.beginPath(); c.arc(x + ox, y + oy, 4 * u, 0, Math.PI * 2); c.fill(); } c.restore(); }
70
+ function drawPage(c, P, t, ctx) {
71
+ const { W, H, u } = ctx, q = P.q, fg = ctx.alpha ? '#FFFFFF' : P.col[1], lt = ctx.lt(t, P.num ? P.show : q.at);
72
+ const stroke = ctx.alpha ? { stroke: '#111', strokeW: P.f.size * 0.06 } : {};
73
+ const lines = P.num ? [P.text] : P.f.lines, size = P.f.size, lh = size * 1.08;
74
+ const sub = q.sub, subSz = P.sf ? P.sf.size : 0, subL = P.sf ? P.sf.lines : [];
75
+ const by0 = ctx.safe.top, by1 = H - ctx.safe.bottom - (ctx.portrait && LIST ? 220 * u : 0), cyB = ctx.portrait ? by0 + (by1 - by0) * 0.44 : (by0 + by1) / 2;
76
+ const blockH = lines.length * lh + (sub ? subSz * (0.6 + subL.length * 1.15) : 0), y0 = cyB - blockH / 2 + size * 0.82;
77
+ // 标签(左上)
78
+ let label = q.data && q.data.label;
79
+ if (label) { const lf = TY.fitFloor(c, label, W * (ctx.portrait ? 0.84 : 0.5) - (ctx.portrait ? 80 : 140) * u, (ctx.portrait ? 56 : 52) * u, 'PuHui-Bold', { maxLines: 1, floor: 34 * u }); label = lf.lines[0] || ''; }
80
+ if (label) TY.rise(c, label, (ctx.portrait ? 80 : 140) * u, ctx.safe.top + (ctx.portrait ? 260 : 210) * u, MO.at(lt, 0, 0.3), { size: (ctx.portrait ? 56 : 52) * u, fam: 'PuHui-Bold', color: fg, track: 6 * u, ...(ctx.alpha ? { stroke: '#111', strokeW: 6 * u } : {}) });
81
+ // 高亮(画在字后面)
82
+ const hq = ctx.of('highlight').filter(h => h.at >= (P.num ? P.show : q.at) - 1e-6).find(h => { const nx = ctx.of('title', 'point', 'number').find(z => z.at > q.at); return !nx || h.at < nx.at; });
83
+ let hiScale = 1, knock = null;
84
+ if (hq) {
85
+ const hl = ctx.lt(t, hq.at), wd = hq.data && hq.data.word, found = !wd || lines.some(l => l.includes(wd));
86
+ hiScale = hl > 0 && found ? 1 + 0.06 * MO.settle(hl, 1, 2.5, 6) : 1; // 词不在主词里:不刷也不放大(init 里已告警)
87
+ if (hl > 0) lines.forEach((ln, i) => {
88
+ const word = wd || ln, at = ln.indexOf(word); if (at < 0) return;
89
+ const wPre = TY.width(c, ln.slice(0, at), size), wW = TY.width(c, word, size), wl = TY.width(c, ln, size), x = W / 2 - wl / 2 + wPre, y = y0 + i * lh;
90
+ const mc = ctx.alpha ? (TP ? TP.accent : '#FF5A36') : TP ? (same(P.col[0], TP.accent) ? TP.accent2 : TP.accent) : (P.col[0] === '#FFD23F' ? '#FF5A36' : '#FFD23F'), mr = [x - 12 * u, y - size * 0.86, wW + 24 * u, size * 1.0, MO.at(hl, 0, 0.3)];
91
+ TY.marker(c, ...mr, mc);
92
+ if (mc.toLowerCase() === fg.toLowerCase()) (knock = knock || []).push({ i, mr }); // 荧光笔和字同色(深蓝页黄字):笔下面的字反白成底色,不然整个词被涂没
93
+ });
94
+ }
95
+ // 主词:每行在 at(+0.08s/行)砸进来;数字页从 0 滚到落定
96
+ lines.forEach((ln, i) => {
97
+ let s = ln; if (P.num) { const k = MO.expoOut(clamp(lt / 0.7)); s = (P.nd.prefix || '') + TY.fmt((P.nd.value || 0) * k, P.nd.decimals ?? 0) + (P.nd.suffix || ''); }
98
+ const w = TY.width(c, s, size), x = W / 2 - w / 2, y = y0 + i * lh;
99
+ TY.slam(c, s, x, y, lt - i * 0.08, { size, color: fg, scale: hiScale, ox: W / 2, oy: y - size * 0.36, ...stroke });
100
+ for (const k of (knock || []).filter(k => k.i === i)) { // 只在荧光笔那块平行四边形里,用底色再砸一遍同一行(和 TY.marker 同一形状)
101
+ const [mx, my, mw, mh, mp] = k.mr, ww = mw * MO.expoOut(clamp(mp)), sk = mh * 0.18;
102
+ c.save(); c.beginPath(); c.moveTo(mx + sk, my); c.lineTo(mx + ww + sk, my); c.lineTo(mx + ww, my + mh); c.lineTo(mx, my + mh); c.closePath(); c.clip();
103
+ TY.slam(c, s, x, y, lt - i * 0.08, { size, color: P.col[0], scale: hiScale, ox: W / 2, oy: y - size * 0.36 }); c.restore();
104
+ }
105
+ });
106
+ if (sub) subL.forEach((ln, k) => { // 辅句(可两行);data.key 那个词用强调色
107
+ const sw = TY.width(c, ln, subSz, 'PuHui-Heavy'), sy = y0 + (lines.length - 1) * lh + subSz * (1.7 + k * 1.15), sp = MO.at(lt, 0.35 + k * 0.08, 0.35), so = { size: subSz, fam: 'PuHui-Heavy', color: fg, ...(ctx.alpha ? { stroke: '#111', strokeW: subSz * 0.08 } : {}) };
108
+ const key = q.data && q.data.key, ki = key ? ln.indexOf(key) : -1, x0 = W / 2 - sw / 2;
109
+ if (ki < 0) return TY.rise(c, ln, x0, sy, sp, so);
110
+ const pre = ln.slice(0, ki), post = ln.slice(ki + key.length), wp = TY.width(c, pre, subSz, 'PuHui-Heavy'), wk = TY.width(c, key, subSz, 'PuHui-Heavy');
111
+ if (pre) TY.rise(c, pre, x0, sy, sp, so); TY.rise(c, key, x0 + wp, sy, sp, { ...so, color: ctx.alpha ? (TP ? TP.accent : '#FFD23F') : accentOf(P.col) }); if (post) TY.rise(c, post, x0 + wp + wk, sy, sp, so);
112
+ });
113
+ }
114
+ // 背景巨型序号:清单页右下角(竖屏落在下半屏),低对比、弹簧落位后斜向慢漂;不和主词抢(字色 12% 透明度)
115
+ function ghost(c, P, t, ctx) {
116
+ if (!LIST || !P.ord || ctx.alpha) return;
117
+ const { W, H, u } = ctx, lt = t - P.show, k = MO.springHz(lt, 2.0, 8), sc = lerp(1.35, 1, k), s = String((P.q.data && P.q.data.num) ?? P.ord);
118
+ const size = (ctx.portrait ? 1000 : 900) * u, x = W - (ctx.portrait ? 40 : 60) * u + lt * 14 * u, y = H - ctx.safe.bottom + (ctx.portrait ? -120 : 70) * u - lt * 10 * u;
119
+ TY.text(c, s, x, y, { size, fam: 'PuHui-Black', align: 'right', color: P.col[1], alpha: 0.12 * clamp(lt / 0.08), scale: sc, ox: x - size * 0.28, oy: y - size * 0.36 });
120
+ }
121
+ // 进度轨:01/02/03 三格(几个 point 页就几格),当前页那一格在换页露出那一帧点亮;画在相机外,不跟页面晃
122
+ function track(c, P, t, ctx) {
123
+ if (!LIST) return;
124
+ const { W, H, u } = ctx, n = LIST.length, k = LIST.filter(p => p.show <= t + 1e-6).length; if (k === 0) return;
125
+ const fg = ctx.alpha ? '#FFFFFF' : P.col[1], gap = 18 * u, seg = Math.min((ctx.portrait ? 150 : 110) * u, ((ctx.portrait ? W * 0.86 : W * 0.4) - (n - 1) * gap) / n), tot = n * seg + (n - 1) * gap; // 要点多时每格变窄,不出画
126
+ const x0 = ctx.portrait ? (W - tot) / 2 : W - (ctx.portrait ? 80 : 140) * u - tot, y = ctx.portrait ? H - ctx.safe.bottom - 130 * u : ctx.safe.top + 120 * u;
127
+ const a = MO.expoOut(clamp((t - LIST[0].show) / 0.3));
128
+ c.save(); c.globalAlpha = a;
129
+ for (let i = 0; i < n; i++) { const x = x0 + i * (seg + gap), on = i < k, fill = on ? MO.expoOut(clamp((t - LIST[i].show) / 0.3)) : 0;
130
+ c.fillStyle = fg; c.globalAlpha = a * 0.25; c.fillRect(x, y, seg, 10 * u); c.globalAlpha = a; c.fillRect(x, y, seg * fill, 10 * u);
131
+ TY.text(c, String(i + 1).padStart(2, '0'), x, y - 20 * u, { size: (ctx.portrait ? 40 : 32) * u, fam: 'PuHui-Bold', color: fg, align: 'left', alpha: on ? 1 : 0.4, track: 2 * u, ...(ctx.alpha ? { stroke: '#111', strokeW: 5 * u } : {}) }); }
132
+ c.restore();
133
+ }
134
+ })();
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env python3
2
+ """原片 vs 复刻 逐帧并排:给一组时刻,各抽一帧,左原片右复刻,拼成一张图。
3
+ python3 compare.py --a 原片.mp4 --b 复刻.mp4 --times 1.5,1.6 --out 对比.jpg [--w 640]"""
4
+ import argparse, subprocess, io
5
+ from PIL import Image, ImageDraw
6
+ ap = argparse.ArgumentParser(); ap.add_argument('--a'); ap.add_argument('--b'); ap.add_argument('--times'); ap.add_argument('--out'); ap.add_argument('--w', type=int, default=640); ap.add_argument('--cols', type=int, default=2)
7
+ a = ap.parse_args()
8
+ def grab(v, t):
9
+ png = subprocess.run(['ffmpeg', '-v', 'error', '-ss', str(t), '-i', v, '-frames:v', '1', '-f', 'image2pipe', '-c:v', 'png', '-'], capture_output=True).stdout
10
+ return Image.open(io.BytesIO(png)).convert('RGB').resize((a.w, a.w * 9 // 16))
11
+ ts = [float(x) for x in a.times.split(',')]
12
+ w, h = a.w, a.w * 9 // 16
13
+ cols = a.cols; rows = (len(ts) + cols - 1) // cols
14
+ S = Image.new('RGB', (cols * 2 * w + (cols - 1) * 12, rows * (h + 18)), 'white'); d = ImageDraw.Draw(S)
15
+ for i, t in enumerate(ts):
16
+ x = (i % cols) * (2 * w + 12); y = (i // cols) * (h + 18)
17
+ S.paste(grab(a.a, t), (x, y + 18)); S.paste(grab(a.b, t), (x + w, y + 18))
18
+ d.text((x + 3, y + 3), f'{t:.3f}s 原片 | 复刻', fill='black')
19
+ S.save(a.out, quality=85); print(a.out)
@@ -0,0 +1,96 @@
1
+ // y1/y2/y3 三支示范片里的花叔形象。window.HERO_MODE = 'C'(默认)| 'A' | 'rig'。
2
+ // C 各片原生(示范片采用):每种风格单独用 gpt-image 生一版设定图(Kurzgesagt 扁平无描边 / Vox 复用 Kurzgesagt 那张转灰度当剪报贴纸 /
3
+ // 白板马克笔线稿),放在 demos/_shared/hero/。这是「纯代码绘制」主线的明确例外——角色是贴图,画面其余全是代码。
4
+ // A 极简几何:纯代码画的 Q 版(大圆头+白渔夫帽+圆眼镜),每片按该片画法上色/描线。不想用贴图时切到它。
5
+ // rig 旧骨架:lib/rig_huashu.js 的 FK 角色(各片里的回退分支)。
6
+ // (实验时还有方向 B「G2 绘本帧库」,需要 24 张帧图,没收进 skill。)
7
+ (() => {
8
+ const { clamp, lerp } = U;
9
+ const H = window.HERO = {};
10
+ const MODE = () => window.HERO_MODE || 'C';
11
+ H.mode = MODE;
12
+ const PNG = n => `demos/_shared/hero/${n}.png`;
13
+ window.EXTRA_ASSETS = (window.EXTRA_ASSETS || []).concat(['c1_0', 'c1_1', 'c1_2', 'c3_line'].map(PNG));
14
+ const img = p => window.IMG[p];
15
+
16
+ // 把一张图按「脚底中心」放到 (x, y),高 h;face=-1 水平翻转
17
+ const place = (c, im, x, y, h, face = 1, anchorY = 0.97) => {
18
+ const w = im.width * h / im.height; c.save(); c.translate(x, y); if (face < 0) c.scale(-1, 1); c.drawImage(im, -w / 2, -h * anchorY, w, h); c.restore();
19
+ };
20
+
21
+ // ---------- A:极简几何 Q 版 ----------
22
+ // 原点 = 两脚之间地面;单位高 1 ≈ 全身高;opt.style: 'flat'(无描边,同色深一档做阴影)| 'line'(白底黑线)| 'print'(平涂+细黑线)
23
+ H.geo = (c, x, y, h, { face = 1, t = 0, style = 'flat', point = true, ink = '#1d1b19' } = {}) => {
24
+ const s = h / 100; c.save(); c.translate(x, y); c.scale(face * s, s);
25
+ const bob = Math.sin(t * 6.5) * 0.8;
26
+ const line = style !== 'flat', lw = style === 'line' ? 2.2 : 1.4;
27
+ const P = { skin: '#ffd8bd', skinSh: '#f2b493', hat: '#ffffff', hatSh: '#dfe3ef', tee: '#ffffff', teeSh: '#e3e6f0', shorts: '#d8c7a6', shortsSh: '#bfa982', shoe: '#ffffff', hair: '#1f1b2e', glass: '#1f1b2e', cheek: 'rgba(255,128,120,.45)' };
28
+ if (style === 'line') Object.assign(P, { skin: '#fbfbf8', skinSh: '#fbfbf8', hatSh: '#fbfbf8', tee: '#fbfbf8', teeSh: '#fbfbf8', shorts: '#fbfbf8', shortsSh: '#fbfbf8', cheek: 'rgba(239,114,38,.35)' });
29
+ // 扁平分面:先铺深一档,再把本色往左上挪 2.5 单位盖回去,右下留一条阴影带(和 Y1 的 flat 同一招)
30
+ const shape = (path, fill, sh) => { if (sh && sh !== fill) { c.save(); c.clip(path); c.fillStyle = sh; c.fill(path); c.translate(-2.5, -2); c.fillStyle = fill; c.fill(path); c.restore(); } else { c.fillStyle = fill; c.fill(path); } if (line) { c.strokeStyle = ink; c.lineWidth = lw; c.stroke(path); } };
31
+ const rr = (x0, y0, w, hh, r) => { const p = new Path2D(); p.roundRect(x0, y0, w, hh, r); return p; };
32
+ const el = (cx, cy, rx, ry) => { const p = new Path2D(); p.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); return p; };
33
+ c.lineJoin = 'round'; c.lineCap = 'round';
34
+ // 腿+洞洞鞋
35
+ for (const lx of [-7, 7]) { shape(rr(lx - 3, -14, 6, 12, 3), P.skin, P.skinSh); shape(el(lx + 2.5, -2, 7.5, 4), P.shoe, P.hatSh); }
36
+ c.fillStyle = ink; for (const lx of [-7, 7]) for (const [dx, dy] of [[-1, -3], [2, -3.5], [5, -2.5]]) { c.beginPath(); c.arc(lx + 2.5 + dx, -2 + dy + 1, 0.8, 0, 7); c.fill(); }
37
+ c.translate(0, bob);
38
+ shape(rr(-12, -26, 24, 13, 4), P.shorts, P.shortsSh); // 短裤
39
+ // 远侧手臂(自然下垂,手表)
40
+ shape(rr(-15, -42, 6, 16, 3), P.skin, P.skinSh); c.fillStyle = ink; c.fillRect(-15.5, -30, 7, 3);
41
+ shape(rr(-14, -46, 28, 22, 7), P.tee, P.teeSh); // T 恤
42
+ // 近侧手臂:指向前方(point)或下垂
43
+ if (point) { c.save(); c.translate(10, -42); c.rotate(-1.35 + Math.sin(t * 3.4) * 0.06); shape(rr(-3.2, 0, 6.4, 20, 3.2), P.skin, P.skinSh); shape(el(0, 21, 3.6, 3.6), P.skin); c.restore(); }
44
+ else shape(rr(9, -42, 6, 16, 3), P.skin, P.skinSh);
45
+ // 头:正面大圆脸(头身比约 1:1.2)。正面比侧面好画、也更好认:帽檐下一圈刘海、两只耳朵、圆眼镜居中
46
+ c.translate(0, -66); c.rotate(Math.sin(t * 2.1) * 0.03);
47
+ for (const ex of [-19, 19]) shape(el(ex, 4, 3.6, 4.6), P.skin, P.skinSh); // 耳
48
+ shape(el(0, 2, 19, 18.5), P.skin, P.skinSh); // 脸
49
+ const crown = new Path2D(); crown.moveTo(-17, -9); crown.bezierCurveTo(-17, -33, 17, -33, 17, -9); crown.closePath();
50
+ const brim = new Path2D(); brim.ellipse(0, -9, 29, 7, 0, 0, Math.PI * 2);
51
+ shape(brim, P.hat, P.hatSh); shape(crown, P.hat, P.hatSh);
52
+ // 刘海+鬓角:画在帽檐之后,从帽檐下沿露出一排黑发(光头感就是少了这一排)
53
+ c.fillStyle = P.hair; c.beginPath(); c.moveTo(-19.5, -4); c.lineTo(19.5, -4); c.lineTo(19, 5); c.lineTo(16.5, 1); c.lineTo(13, -0.5); c.lineTo(9, 1.5); c.lineTo(5, -1); c.lineTo(0, 1); c.lineTo(-5, -1.5); c.lineTo(-9, 1); c.lineTo(-13, -1); c.lineTo(-16.5, 1.5); c.lineTo(-19, 5); c.closePath(); c.fill();
54
+ shape(brim, P.hat, P.hatSh);
55
+ if (!line) { c.fillStyle = P.hatSh; c.beginPath(); c.ellipse(0, -9.5, 17, 2.2, 0, 0, Math.PI * 2); c.fill(); } // 帽身与帽檐交界的一道暗线(色块,不是描边)
56
+ // 圆眼镜+眼睛(眨眼 0.12s/3.2s)
57
+ const blink = (t % 3.2) > 3.08;
58
+ c.strokeStyle = P.glass; c.lineWidth = 1.8; for (const ex of [-7.5, 7.5]) { c.beginPath(); c.arc(ex, 3, 6, 0, 7); c.stroke(); }
59
+ c.beginPath(); c.moveTo(-1.5, 3); c.lineTo(1.5, 3); c.stroke();
60
+ c.fillStyle = P.glass; for (const ex of [-7.5, 7.5]) { if (blink) c.fillRect(ex - 2.2, 3, 4.4, 1.1); else { c.beginPath(); c.ellipse(ex, 3.4, 1.8, 2.3, 0, 0, 7); c.fill(); } }
61
+ c.fillStyle = P.cheek; for (const ex of [-11, 11]) { c.beginPath(); c.ellipse(ex, 10.5, 3.2, 1.9, 0, 0, 7); c.fill(); }
62
+ const m = 0.4 + 0.6 * Math.max(0, Math.sin(t * 9)); // 嘴:说话时开合
63
+ c.fillStyle = '#c4473f'; c.beginPath(); c.ellipse(0, 12.5, 2.8, 0.9 + 1.7 * m, 0, 0, 7); c.fill();
64
+ c.restore();
65
+ };
66
+
67
+ // ---------- 每片的接入点 ----------
68
+ // Y1 镜 3:花叔站在草坡左侧指向黑猫(脚底 (470,900),全身约 440px)
69
+ H.y1 = (g, ft, flatFn) => {
70
+ const m = MODE();
71
+ if (m === 'A') return H.geo(g, 470, 900, 440, { face: 1, t: ft, style: 'flat' });
72
+ if (m === 'C') {
73
+ // 一张设定稿里的三个姿势当三个关键帧:拉出时挥手 → 扫描时指向黑猫 → 标签弹出后举手欢呼。
74
+ // 换姿势那一下给 0.12s 的挤压回弹(Kurzgesagt 弹出的 108% 过冲),站着时以脚底为轴呼吸。
75
+ const l = ft - Y1.SHOTS.s3[0], k = l < 1.25 ? 1 : l < 2.3 ? 0 : 2, t0 = [0, 1.25, 2.3][[1, 0, 2].indexOf(k)];
76
+ const pop = l - t0 < 0.14 && t0 > 0 ? 1 + 0.06 * Math.sin((l - t0) / 0.14 * Math.PI) : 1;
77
+ const br = 1 + 0.012 * Math.sin(ft * 5.2);
78
+ g.save(); g.translate(470, 905); g.scale(1 / Math.sqrt(br * pop), br * pop); place(g, img(PNG('c1_' + k)), 0, 0, 450, 1); g.restore(); return;
79
+ }
80
+ return false;
81
+ };
82
+ // Y2 镜 3:剪纸贴纸,画进 sp(之后统一垫白边、阴影),脚底 (1500,1040),面朝左
83
+ H.y2 = (g, st) => {
84
+ const m = MODE();
85
+ if (m === 'A') return H.geo(g, 1500, 1040, 560, { face: -1, t: st, style: 'print', ink: '#171716' }), true;
86
+ if (m === 'C') { const k = Math.floor(st * 12); g.save(); g.translate(1500, 1015); g.rotate(((k * 7919) % 13 - 6) * 0.0012); g.filter = 'grayscale(1) contrast(1.25) brightness(1.04)'; place(g, img(PNG('c1_0')), 20, 8, 520, -1); g.restore(); return true; } // 卡通帧转灰度当剪报贴纸:按 12fps 微抖(定格感)
87
+ return false;
88
+ };
89
+ // Y3 镜 3:被之字形笔触揭开的那张画(世界坐标,脚底 (fx, fy),高 hh)
90
+ H.y3 = (g, fx, fy, hh, ft) => {
91
+ const m = MODE();
92
+ if (m === 'A') return H.geo(g, fx, fy, hh, { face: 1, t: ft, style: 'line', ink: '#0A0503' }), true;
93
+ if (m === 'C') { g.save(); g.globalCompositeOperation = 'multiply'; place(g, img(PNG('c3_line')), fx, fy + 4, hh * 1.08, 1); g.restore(); return true; }
94
+ return false;
95
+ };
96
+ })();
@@ -0,0 +1,23 @@
1
+ // 示范 · 长卷穿越片:一条横向长画卷,几个「世界段」首尾相接,主角从左走到右,跨过边界时画风跟着换。
2
+ // 这里只收了 3 段(埃及壁画 → 莫奈《日本桥》→ 8-bit 像素),完整片子是 24 段,做法见 references/11-长卷穿越片.md。
3
+ // 预览 index.html?film=demos/long_scroll 渲染 render.py --film demos/long_scroll --out long_scroll.mp4
4
+ // 骨架 lib/world.js 管时间轴、主角行进、只进不退的相机、按边界裁开的主角、角标;每段一个文件 segments/<id>.js,调用 WORLD.add({...})。
5
+ (() => {
6
+ window.__bootErrors = window.__bootErrors || [];
7
+ const base = 'demos/long_scroll/';
8
+ const load = url => { const x = new XMLHttpRequest(); x.open('GET', url, false); x.send(); if (x.status !== 200) { __bootErrors.push(`${url} 不存在 (HTTP ${x.status})`); return; }
9
+ try { (0, eval)(x.responseText + `\n//# sourceURL=${url}`); } catch (e) { __bootErrors.push(`${url} 执行出错 ${e && e.stack || e}`); } };
10
+ load(base + 'lib/crossing.js'); // XING:角色帧库、笔触材质、莫奈世界
11
+ load(base + 'lib/acts.js'); // ACT:打水漂的石子轨迹等小剧本工具
12
+ load(base + 'lib/world.js'); // WORLD:长卷骨架
13
+ // 片子顺序:改顺序、加段只改这里(加段 = 往 segments/ 放一个文件,再把 id 写进来)
14
+ window.FILM_ORDER = ['02_egypt', '28_monet', '14_8bit'];
15
+ for (const id of window.FILM_ORDER) { const n = WORLD.segs.length; load(`${base}segments/${id}.js`);
16
+ if (WORLD.segs.length === n || WORLD.segs[WORLD.segs.length - 1].id !== id) __bootErrors.push(`segments/${id}.js 没有 WORLD.add({ id: '${id}', ... })`); }
17
+ // 每段用到的帧库(hero.walk、acts[].key、sprites)一次性登记成资源,引擎启动前全部加载
18
+ const sprites = new Set(); WORLD.segs.forEach(s => { if (s.hero && s.hero.walk) sprites.add(s.hero.walk); (s.acts || []).forEach(a => a.key && sprites.add(a.key)); (s.sprites || []).forEach(k => sprites.add(k)); });
19
+ try { window.EXTRA_ASSETS = (window.EXTRA_ASSETS || []).concat(XING.sprites([...sprites])); } catch (e) { __bootErrors.push('帧库缺失:' + e.message); }
20
+ WORLD.layout();
21
+ window.PUNCH = 0;
22
+ window.ERAS = [{ id: 'film', dur: WORLD.total, draw: (c, lt, t) => WORLD.draw(c, t) }];
23
+ })();
@@ -0,0 +1,69 @@
1
+ {
2
+ "frames": [
3
+ {
4
+ "file": "01.png",
5
+ "ax": 120.0,
6
+ "ay": 374.0,
7
+ "w": 239,
8
+ "h": 375,
9
+ "air": 0
10
+ },
11
+ {
12
+ "file": "02.png",
13
+ "ax": 118.5,
14
+ "ay": 323.0,
15
+ "w": 239,
16
+ "h": 324,
17
+ "air": 0
18
+ },
19
+ {
20
+ "file": "03.png",
21
+ "ax": 116.5,
22
+ "ay": 396.0,
23
+ "w": 255,
24
+ "h": 367,
25
+ "air": 30
26
+ },
27
+ {
28
+ "file": "04.png",
29
+ "ax": 127.0,
30
+ "ay": 420.0,
31
+ "w": 246,
32
+ "h": 372,
33
+ "air": 49
34
+ },
35
+ {
36
+ "file": "05.png",
37
+ "ax": 136.0,
38
+ "ay": 395.0,
39
+ "w": 257,
40
+ "h": 381,
41
+ "air": 15
42
+ },
43
+ {
44
+ "file": "06.png",
45
+ "ax": 125.0,
46
+ "ay": 316.0,
47
+ "w": 247,
48
+ "h": 317,
49
+ "air": 0
50
+ },
51
+ {
52
+ "file": "07.png",
53
+ "ax": 106.5,
54
+ "ay": 369.0,
55
+ "w": 214,
56
+ "h": 370,
57
+ "air": 0
58
+ },
59
+ {
60
+ "file": "08.png",
61
+ "ax": 128.5,
62
+ "ay": 397.0,
63
+ "w": 267,
64
+ "h": 373,
65
+ "air": 25
66
+ }
67
+ ],
68
+ "ref_h": 371.0
69
+ }