@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,47 @@
1
+ // K3 阶段二「对话」(第 8–11 拍,4.0–6.0s;推页 5.8s 起、6.0s 到位)
2
+ // 第 8 拍主词砸在画右+巨型「2」→ 8.5 拍你的气泡弹出 → 第 9 拍 AI 气泡长出、代码一行行流进来 → 9.5 拍辅句 → 10.5 拍「一整段」放大+荧光笔。
3
+ // 版式和 K2 镜像(主词左↔右),同一套拍位换位置,观众不会觉得重复。
4
+ // 返修:辅句原在第 10 拍、10 个字只停 0.55s,左端离 AI 面板只剩 14px;现在提前、压到 7 个字、右对齐留 250px 以上。
5
+ (() => {
6
+ const K = KT, C = K.C, b = K.b;
7
+ const COLS = ['#C792EA', '#82AAFF', '#C3E88D', '#F78C6C', '#89DDFF', '#FFCB6B'];
8
+ SCENES['k3_chat'] = {
9
+ draw(c, lt, t) {
10
+ c.fillStyle = C.navy; c.fillRect(0, 0, 1920, 1080);
11
+ K.dots(c, t, C.white, 0.07);
12
+ K.cam(c, t, c => {
13
+ K.bigNum(c, t, '2', b(8), 1880, 1150, 'rgba(255,255,255,0.07)');
14
+ TY.rise(c, '阶段 02', 1790, 250, MO.at(t, b(8), 0.3), { size: K.SZ.label, fam: 'PuHui-Bold', color: C.yellow, track: 6, align: 'right' });
15
+ TY.underline(c, 1620, 272, 170, MO.at(t, b(8.25), 0.3), C.yellow, 6);
16
+ const hw = TY.width(c, '对话', K.SZ.hero);
17
+ K.slam(c, '对话', 1790 - hw, 600, t, b(8), { color: C.yellow });
18
+ const s1 = '一句话换', key = '一整段', w1 = TY.width(c, s1, K.SZ.sub, 'PuHui-Heavy'), wk = TY.width(c, key, K.SZ.sub, 'PuHui-Heavy');
19
+ const ky = 775, x1 = 1790 - w1 - wk * 1.35 - 12, kx = x1 + w1 + 12;
20
+ TY.rise(c, s1, x1, ky, MO.at(t, b(9.5), 0.35), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: C.white });
21
+ const ksc = t >= b(10.5) ? 1 + 0.35 * MO.springHz(t - b(10.5), 2.5, 9) : 1;
22
+ K.keyMarker(c, kx, ky, wk, K.SZ.sub, t, b(10.5), ksc, C.yellow);
23
+ K.key(c, key, kx, ky, t, b(9.75), b(10.5), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: t >= b(10.5) ? C.navy : C.white });
24
+ // 你的气泡(右对齐到 x=820),8.5 拍弹出
25
+ const p1 = MO.at(t, b(8.5), 0.3);
26
+ if (p1 > 0) { const s = MO.backOut(p1, 2.6); c.save(); c.translate(820, 250); c.scale(s, s);
27
+ const w = TY.width(c, '帮我做个登录页', 48, 'PuHui-Bold') + 70;
28
+ c.fillStyle = C.blue; c.beginPath(); c.roundRect(-w, -50, w, 96, 30); c.fill();
29
+ c.beginPath(); c.moveTo(-26, 40); c.lineTo(10, 58); c.lineTo(-6, 26); c.fill();
30
+ TY.text(c, '帮我做个登录页', -w / 2, 16, { size: 48, fam: 'PuHui-Bold', color: C.white }); c.restore(); }
31
+ // AI 气泡:第 9 拍长出(高度 expoOut 0.35s),之后代码每 0.045s 流入一行
32
+ const p2 = MO.at(t, b(9), 0.35);
33
+ if (p2 > 0) {
34
+ const e = MO.expoOut(p2), x = 130, y = 340, w = 690, h = 620 * e;
35
+ c.fillStyle = C.white; c.beginPath(); c.roundRect(x, y, w, Math.max(40, h), 30); c.fill();
36
+ c.save(); c.beginPath(); c.roundRect(x, y, w, Math.max(40, h), 30); c.clip();
37
+ const n = Math.floor(Math.max(0, t - b(9) - 0.1) / 0.045), r = U.rng(5);
38
+ for (let i = 0; i < 22; i++) { const toks = 1 + (r() * 3 | 0), ind = (r() * 3 | 0) * 28; let xx = x + 40 + ind;
39
+ const yy = y + 40 + i * 26, vis = i < n;
40
+ for (let k = 0; k < toks; k++) { const ww = 40 + r() * 140; if (vis) { c.fillStyle = COLS[(r() * 6) | 0]; c.beginPath(); c.roundRect(xx, yy, ww, 14, 7); c.fill(); } else r(); xx += ww + 12; } }
41
+ c.restore();
42
+ c.fillStyle = C.white; c.beginPath(); c.moveTo(x + 30, y + 4); c.lineTo(x - 10, y - 26); c.lineTo(x + 70, y + 4); c.fill();
43
+ }
44
+ });
45
+ },
46
+ };
47
+ })();
@@ -0,0 +1,49 @@
1
+ // K4 阶段三「代理」(第 12 拍起,6.0–8.5s)
2
+ // 第 12 拍主词砸进+巨型「3」→ 12.5 拍「Agent」+任务卡升起 → 第 13 拍辅句「你只说」、13.25 拍「要什么」→
3
+ // 13.5 / 14 / 14.5 拍三条任务自己打勾 → 第 15 拍「要什么」放大+下划线 → 第 16 拍收尾重拍:镜头拉远 6%、整屏一冲,留 0.5s 静住。
4
+ // 返修:第一版高潮最弱——收尾无一击、打完勾全变灰、底部 28% 空着。现在内容整体下移 70px、勾完不变灰、加第 16 拍收尾。
5
+ (() => {
6
+ const K = KT, C = K.C, b = K.b;
7
+ const { clamp } = U;
8
+ const TASKS = [['写代码', 13.5], ['跑测试', 14], ['上线', 14.5]];
9
+ SCENES['k4_agent'] = {
10
+ draw(c, lt, t) {
11
+ c.fillStyle = C.paper; c.fillRect(0, 0, 1920, 1080);
12
+ K.dots(c, t, C.ink, 0.06);
13
+ const pull = MO.expoOut(MO.at(t, b(16), 0.45)), hit = t >= b(16) ? 0.035 * Math.exp(-(t - b(16)) * 9) : 0;
14
+ K.cam(c, t, c => {
15
+ K.bigNum(c, t, '3', b(12), 1880, 1150, 'rgba(17,17,17,0.05)');
16
+ TY.rise(c, '阶段 03', 140, 300, MO.at(t, b(12), 0.3), { size: K.SZ.label, fam: 'PuHui-Bold', color: C.coral, track: 6 });
17
+ TY.underline(c, 140, 322, 170, MO.at(t, b(12.25), 0.3), C.coral, 6);
18
+ K.slam(c, '代理', 130, 650, t, b(12), { color: C.ink });
19
+ const hw = TY.width(c, '代理', K.SZ.hero);
20
+ TY.rise(c, 'Agent', 150 + hw, 650, MO.at(t, b(12.5), 0.35), { size: 96, fam: 'PuHui-Black', color: C.coral });
21
+ const s1 = '你只说', key = '要什么', w1 = TY.width(c, s1, K.SZ.sub, 'PuHui-Heavy'), wk = TY.width(c, key, K.SZ.sub, 'PuHui-Heavy');
22
+ const ky = 825, kx = 152 + w1;
23
+ TY.rise(c, s1, 140, ky, MO.at(t, b(13), 0.35), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: C.ink });
24
+ const ksc = K.key(c, key, kx, ky, t, b(13.25), b(15), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: C.coral });
25
+ TY.underline(c, kx, ky + 30 * ksc, wk * ksc, MO.at(t, b(15), 0.3), C.coral, 12);
26
+ // 任务卡:12.5 拍从下方升起
27
+ const pc = MO.expoOut(MO.at(t, b(12.5), 0.4)); if (pc <= 0) return;
28
+ c.save(); c.translate(1220, 360 + 260 * (1 - pc)); c.globalAlpha = clamp(pc * 2);
29
+ c.fillStyle = 'rgba(0,0,0,0.08)'; c.beginPath(); c.roundRect(12, 16, 600, 470, 28); c.fill();
30
+ c.fillStyle = C.white; c.beginPath(); c.roundRect(0, 0, 600, 470, 28); c.fill();
31
+ TY.text(c, 'TODO', 50, 80, { size: 40, fam: 'PuHui-Black', color: '#B5AFA4', align: 'left', track: 4 });
32
+ TASKS.forEach(([s, beat], i) => {
33
+ const y = 170 + i * 115, ck = MO.at(t, b(beat), 0.22), done = t >= b(beat);
34
+ // 打勾那一拍框先放大 1.25 再回,勾按弧长画出
35
+ const bs = done ? 1 + 0.25 * Math.exp(-(t - b(beat)) * 10) * Math.sin(Math.min(Math.PI, (t - b(beat)) * 20)) : 1;
36
+ c.save(); c.translate(80, y); c.scale(bs, bs);
37
+ c.fillStyle = done ? C.green : C.paper; c.strokeStyle = done ? C.green : '#CFC7BA'; c.lineWidth = 6;
38
+ c.beginPath(); c.roundRect(-30, -30, 60, 60, 14); c.fill(); c.stroke();
39
+ if (ck > 0) { c.strokeStyle = C.white; c.lineWidth = 10; c.lineCap = 'round'; c.lineJoin = 'round';
40
+ const L1 = Math.hypot(11, 12), L2 = Math.hypot(22, 26), d = MO.expoOut(ck) * (L1 + L2);
41
+ c.beginPath(); c.moveTo(-15, 0); if (d <= L1) c.lineTo(-15 + 11 * d / L1, 12 * d / L1); else { c.lineTo(-4, 12); const q = (d - L1) / L2; c.lineTo(-4 + 22 * q, 12 - 26 * q); } c.stroke(); }
42
+ c.restore();
43
+ TY.text(c, s, 140, y + 24, { size: 66, fam: 'PuHui-Heavy', color: done ? C.green : C.ink, align: 'left' });
44
+ });
45
+ c.restore();
46
+ }, { zoom: (1 - 0.06 * pull) * (1 + hit) });
47
+ },
48
+ };
49
+ })();
@@ -0,0 +1,52 @@
1
+ // y5 本片共用:节拍网格、配色、字号阶梯、拍点微冲、进度轨(全片唯一连续的叙事锚)、背景点阵。
2
+ // 一切「落点」都写成拍号:b(4) = 第 4 拍 = 2.0s。120 BPM:一拍 0.5s,八分 0.25s。
3
+ // 用到的库:TY(slam 砸入、rise 遮罩升起、keyword 关键词强调、marker 荧光笔、underline)、CAM(with、drift)、MO(springHz、拍号)。
4
+ (() => {
5
+ const { clamp, lerp } = U;
6
+ window.BPM = 120;
7
+ const K = window.KT = {};
8
+ K.b = n => MO.beat(n, 120); // 拍 → 秒
9
+ const C0 = K.C = { ink: '#111111', paper: '#F4EFE6', yellow: '#FFD23F', coral: '#FF5A36', navy: '#14213D', blue: '#3A86FF', green: '#2BB673', white: '#FFFFFF', code: '#1B1D29' };
10
+ // 字号阶梯:主词 : 辅句 : 标签 ≈ 3.4 : 1 : 0.4(主词必须一眼读完,辅句给耳朵对位)
11
+ K.SZ = { hero: 340, sub: 96, label: 40 };
12
+ // 拍点微冲:每个四分拍让画面放大 1.2%,0.12s 衰减(e^-25t)——画面跟着鼓「呼吸」
13
+ K.bump = t => { const ph = t % 0.5; return 1 + 0.012 * Math.exp(-ph * 25); };
14
+ // 背景点阵:斜向缓慢平移 36px/s(持续运动,低对比 8%,不和字抢)
15
+ K.dots = (c, t, col, alpha = 0.1) => {
16
+ const g = 64, ox = (t * 36) % g, oy = (t * 18) % g;
17
+ c.save(); c.globalAlpha = alpha; c.fillStyle = col;
18
+ for (let y = -g; y < 1080 + g; y += g) for (let x = -g; x < 1920 + g; x += g) { c.beginPath(); c.arc(x + ox, y + oy, 4, 0, Math.PI * 2); c.fill(); }
19
+ c.restore();
20
+ };
21
+ // 进度轨:右上三格,画在固定层(GLOBAL_OVERLAY),不跟页面一起被推走(返修:第一版画在各段里,推页时跟着跳)。
22
+ // 颜色在转场「揭开」那一帧换(2.0 / 4.0 / 6.0s),亮格同一帧点亮。
23
+ K.STAGES = [2.0, 4.0, 6.0];
24
+ K.trackCol = t => t < 4.0 ? [C0.white, 'rgba(255,255,255,0.35)'] : t < 6.0 ? [C0.yellow, 'rgba(255,255,255,0.25)'] : [C0.coral, 'rgba(17,17,17,0.18)'];
25
+ window.GLOBAL_OVERLAY = (c, t) => {
26
+ if (t < 2.0) return;
27
+ const [col, dim] = K.trackCol(t), a = MO.expoOut(clamp((t - 2.0) / 0.3));
28
+ c.save(); c.globalAlpha = a;
29
+ for (let i = 0; i < 3; i++) {
30
+ const x = 1520 + i * 120, y = 92, on = t >= K.STAGES[i], fill = on ? MO.expoOut(clamp((t - K.STAGES[i]) / 0.3)) : 0;
31
+ c.fillStyle = dim; c.fillRect(x, y, 100, 10); c.fillStyle = col; c.fillRect(x, y, 100 * fill, 10);
32
+ TY.text(c, '0' + (i + 1), x, y - 18, { size: 30, fam: 'PuHui-Bold', color: on ? col : dim, align: 'left', track: 2 });
33
+ }
34
+ c.restore();
35
+ };
36
+ // 背景巨型阶段数字(900px、低对比、斜向慢漂):数字是章节的主角之一,但不和主词抢(返修:审片说 01/02/03 太小,退成了装饰)
37
+ K.bigNum = (c, t, s, t0, x, y, col) => {
38
+ const lt = t - t0; if (lt < 0) return;
39
+ const k = MO.springHz(lt, 2.0, 8), sc = lerp(1.35, 1, k);
40
+ TY.text(c, s, x + lt * 14, y - lt * 10, { size: 900, fam: 'PuHui-Black', align: 'right', color: col, scale: sc, alpha: clamp(lt / 0.08), ox: x - 250, oy: y - 320 });
41
+ };
42
+ // 关键词:先从槽里升起,到「强调拍」再放大 1.35 倍并钉住,荧光笔跟着放大(TY.keyword / TY.keywordMarker)
43
+ K.key = TY.keyword;
44
+ K.keyMarker = TY.keywordMarker;
45
+ // 主词「砸」进来:从 1.7 倍缩到 1(弹簧 2.2Hz/10,过冲 ≈8%),前 0.06s 透明→不透明(TY.slam)
46
+ K.slam = (c, s, x, y, t, t0, o = {}) => TY.slam(c, s, x, y, t - t0, { size: K.SZ.hero, ...o });
47
+ // 镜头:微漂+极慢旋转(±0.5°)+拍点微冲
48
+ K.cam = (c, t, fn, extra = {}) => {
49
+ const [dx, dy] = CAM.drift(t, 5, 2);
50
+ CAM.with(c, { x: 960 + dx + (extra.x || 0), y: 540 + dy + (extra.y || 0), z: K.bump(t) * (extra.zoom || 1), r: 0.009 * Math.sin(t * 0.8) + (extra.rot || 0) }, fn);
51
+ };
52
+ })();
@@ -0,0 +1,277 @@
1
+ // 引擎:时间 t(秒)→ 一帧画面。确定性:同一个 t 永远画出同一帧(随机数全部用种子)。
2
+ // 时间轴由节拍网格驱动:128 BPM,每段占若干八分音符(见 eras.js 的 eighths)。
3
+ (() => {
4
+ // 画布尺寸:?w=1080&h=1920(render.py --width/--height 传)> eras.js 里的 window.FILM_SIZE = [w, h] > 1920×1080。
5
+ // 一次性 U.setStage:转场、相机和各库都跟着换;竖屏整片的场景代码要按 window.STAGE(或 CAM/UI 等库)排版,35 种艺术风格场景只按横屏画过。
6
+ const qs = new URLSearchParams(location.search), qw = +qs.get('w'), qh = +qs.get('h');
7
+ const [W, H] = qw > 0 && qh > 0 ? [qw, qh] : Array.isArray(window.FILM_SIZE) ? window.FILM_SIZE.map(Number) : [1920, 1080];
8
+ window.U.setStage(W, H);
9
+ const cv = document.getElementById('c');
10
+ cv.width = W; cv.height = H; window.__size = [W, H];
11
+ const ctx = cv.getContext('2d');
12
+ window.__canvas = cv;
13
+
14
+ const EIGHTH = 60 / (window.BPM || 128) / 2; // 0.234375s @128;片子可设 window.BPM
15
+ // 段长:eighths(八分音符数,卡节拍)或 dur(秒,按口播切的解说片)。写了 dur 就以 dur 为准。
16
+ let acc = 0;
17
+ for (const e of ERAS) { e.t0 = acc; acc += e.dur != null ? e.dur : e.eighths * EIGHTH; e.t1 = acc; }
18
+ // 片长 = 最后一段的 t1;window.FILM_DURATION 优先。render.py / 预览都读 window.__total。
19
+ window.__total = window.FILM_DURATION || ERAS[ERAS.length - 1].t1;
20
+ ERAS[ERAS.length - 1].t1 = 1e9;
21
+
22
+ // 离屏缓冲:A=上一段,B=当前段
23
+ const mk = () => { const c = document.createElement('canvas'); c.width = W; c.height = H; return c; };
24
+ const bufA = mk(), bufB = mk(), bufT = mk();
25
+ window.__mk = mk;
26
+
27
+ // ---------- 资源 ----------
28
+ const IMG = {};
29
+ function load(src) {
30
+ return new Promise((res, rej) => { const im = new Image(); im.onload = () => res(im); im.onerror = () => rej(src); im.src = src; });
31
+ }
32
+ async function boot() {
33
+ if (window.__bootErrors && __bootErrors.length) { window.__bootFailed = __bootErrors.join('\n'); console.error('BOOT FAILED\n' + window.__bootFailed); return; }
34
+ const list = new Set();
35
+ for (const e of ERAS) (e.assets || []).concat(e.plate ? [e.plate] : []).forEach(s => list.add(s));
36
+ (window.EXTRA_ASSETS || []).forEach(s => list.add(s));
37
+ try { await Promise.all([...list].map(async s => { IMG[s] = await load(s); })); }
38
+ catch (src) { window.__bootFailed = `图片加载失败:${src}(段的 assets / plate / window.EXTRA_ASSETS 里点名的文件不存在或不是图片)`; console.error('BOOT FAILED\n' + window.__bootFailed); return; }
39
+ for (const f of (window.FONT_FACES || [])) { const ff = new FontFace(f.family, `url(${f.url})`, f.desc || {}); await ff.load(); document.fonts.add(ff); }
40
+ await document.fonts.ready;
41
+ await U.loadCmaps(); // 字形检查用(U.assertGlyphs)
42
+ for (const e of ERAS) if (e.init) e.init(IMG);
43
+ checkCounterGlyphs();
44
+ await window.prepare(0); // 第 0 帧的视频帧先加载(缺帧在这里就报错)
45
+ window.__ready = true;
46
+ renderFrame(0);
47
+ }
48
+ window.IMG = IMG;
49
+
50
+ const U = window.U;
51
+
52
+ // ---------- 角标(右上角):可插拔 ----------
53
+ // 每段 era.counter 三种写法:
54
+ // 1) 默认:年份计数器 U.yearCounter——数值只由全局时间决定(U.counterAt),跨段连续滚动;label 写风格名。
55
+ // 2) counter.draw(c, lt, t)(别名 counter.custom(c, lt)):自绘。年代不单调、要印章/榜题/漫画旁白框时用。
56
+ // 3) counter.mode = 'tag':U.styleTag 画「年份+风格名」两行静态牌(年代乱序时不滚动)。
57
+ // 转场期间:默认(window.COUNTER_IN_TRANSITION = 'cut')新旧两层都不画角标,转场画完后在最上层按 p<0.5 硬切旧/新角标——
58
+ // 各段角标版式不同时,局部揭示的转场(点灯、扫光、水波)会让两套字叠成一团(迁移测试 C、D 的独立审片都抓到)。
59
+ // 'layers':角标留在各自层里随转场一起变(原片复刻:年份牌位置固定、数字连续滚动,混在一起看不出来)。单个转场可用 transition.counter 覆盖。
60
+ function drawCounter(c, e, t) {
61
+ const ct = e.counter; if (!ct) return;
62
+ const lt = t - e.t0;
63
+ if (ct.draw) ct.draw(c, lt, t);
64
+ else if (ct.custom) ct.custom(c, lt);
65
+ else if (ct.mode === 'tag') U.styleTag(c, ct, lt);
66
+ else { const cv = U.counterAt(t); U.yearCounter(c, ct, cv.v, cv.rolling, lt); }
67
+ }
68
+ // 启动时检查角标用到的字形(缺字静默回退系统字体是迁移测试 B、D 都踩过的坑)。自绘角标的字请在 scene.init 里自己 U.assertGlyphs。
69
+ function checkCounterGlyphs() {
70
+ for (const e of ERAS) { const ct = e.counter; if (!ct || ct.draw || ct.custom) continue;
71
+ if (ct.mode === 'tag') { if (ct.name) U.assertGlyphs(ct.nameFont || ct.font, ct.name, e.id + ' 角标'); if (ct.sub) U.assertGlyphs(ct.subFont || ct.nameFont || ct.font, ct.sub, e.id + ' 角标'); continue; }
72
+ U.assertGlyphs(ct.font, '0123456789' + (Math.abs(ct.year) >= 10000 ? ',' : ''), e.id + ' 年份');
73
+ if (ct.label) U.assertGlyphs(ct.label.font, ct.label.text, e.id + ' 角标');
74
+ if (ct.suffix) U.assertGlyphs(ct.suffix.font || ct.font, 'BCAD', e.id + ' 角标后缀');
75
+ }
76
+ }
77
+ // 「年份+风格名」静态牌:{ year, name, sub?, x, y, font, size, color, nameFont, nameColor, subFont, subColor, box(c,lt), shadow, align }
78
+ U.styleTag = function (c, ct, lt) {
79
+ if (ct.box) ct.box(c, lt);
80
+ c.save(); c.textAlign = ct.align || 'right'; c.textBaseline = 'alphabetic';
81
+ if (ct.shadow) { c.shadowColor = ct.shadow.color; c.shadowBlur = ct.shadow.blur || 0; c.shadowOffsetX = ct.shadow.x || 0; c.shadowOffsetY = ct.shadow.y || 0; }
82
+ const x = ct.x ?? 1866, y = ct.y ?? 128;
83
+ if (ct.year != null) { c.font = `${ct.size || 96}px "${U.fontFamily(ct.font)}"`; c.fillStyle = ct.color || '#fff'; c.fillText(ct.yearText || (ct.year < 0 ? U.fmtYear(ct.year) + ' BC' : String(ct.year)), x, y); }
84
+ if (ct.name) { c.font = ct.nameFont || `34px "${U.fontFamily(ct.font)}"`; c.fillStyle = ct.nameColor || ct.color || '#fff'; c.fillText(ct.name, x, ct.ny ?? y + 52); }
85
+ if (ct.sub) { c.font = ct.subFont || ct.nameFont; c.fillStyle = ct.subColor || ct.nameColor || ct.color; c.fillText(ct.sub, x, ct.sy ?? y + 92); }
86
+ c.restore();
87
+ };
88
+
89
+ // ---------- 年份计数器(里程表逐位滚动) ----------
90
+ // from/to 是数值;fmt 把数值格式化成字符串;滚动时每一位独立从旧字符滚到新字符,低位先动、错开 stagger。
91
+ U.odometer = function (c, opt, p) {
92
+ const { x, y, size, font, color, fromStr, toStr, align = 'right', stagger = 0.12, spacing = 0, shadow } = opt;
93
+ c.save();
94
+ c.font = `${size}px ${font}`;
95
+ c.textBaseline = 'alphabetic';
96
+ c.fillStyle = color;
97
+ const n = Math.max(fromStr.length, toStr.length);
98
+ const a = fromStr.padStart(n, ' '), b = toStr.padStart(n, ' ');
99
+ const widths = [...b].map(ch => c.measureText(ch === ' ' ? '0' : ch).width + spacing);
100
+ const total = widths.reduce((s, w) => s + w, 0) - spacing;
101
+ let cx = align === 'right' ? x - total : align === 'center' ? x - total / 2 : x;
102
+ for (let i = 0; i < n; i++) {
103
+ const order = n - 1 - i; // 低位先滚
104
+ const pi = U.clamp((p - order * stagger) / Math.max(0.0001, 1 - (n - 1) * stagger));
105
+ const e = U.ease.inOut(pi);
106
+ const w = widths[i];
107
+ c.save();
108
+ c.beginPath(); c.rect(cx - 4, y - size * 1.05, w + 8, size * 1.35); c.clip();
109
+ if (shadow) { c.shadowColor = shadow.color; c.shadowBlur = shadow.blur || 0; c.shadowOffsetX = shadow.x || 0; c.shadowOffsetY = shadow.y || 0; }
110
+ if (a[i] === b[i]) c.fillText(b[i], cx, y);
111
+ else {
112
+ const dy = size * 1.1 * e;
113
+ c.globalAlpha = 1 - e * 0.6; if (a[i] !== ' ') c.fillText(a[i], cx, y - dy);
114
+ c.globalAlpha = 0.4 + e * 0.6; if (b[i] !== ' ') c.fillText(b[i], cx, y + size * 1.1 - dy);
115
+ }
116
+ c.restore();
117
+ cx += w;
118
+ }
119
+ c.restore();
120
+ };
121
+
122
+ // ---------- 年份计数器:数值插值(ease-out)+个位数滚动残影 ----------
123
+ // ct: {year, x, y, size, font, color, suffix:{font,size,color,dy,gap}, label:{text,font,color,x,y,spacing}, box(c), shadow, spacing}
124
+ U.fmtYear = (v) => { const a = Math.abs(Math.round(v)); return a >= 10000 ? a.toLocaleString('en-US') : String(a); };
125
+ // 计数器的值只由全局时间决定:转场中新旧两层显示同一个数字
126
+ U.counterAt = function (t) {
127
+ let k = 0; for (let i = 0; i < ERAS.length; i++) if (t >= ERAS[i].t0) k = i;
128
+ const cur = ERAS[k].counter, prv = k ? ERAS[k - 1].counter : cur;
129
+ const p = cur.roll ? U.clamp((t - ERAS[k].t0 - 0.03) / cur.roll) : 1;
130
+ return { v: prv.year + (cur.year - prv.year) * U.ease.inOut(p), rolling: p > 0 && p < 1 };
131
+ };
132
+ U.yearCounter = function (c, ct, v, rolling, lt) {
133
+ if (ct.box) ct.box(c, lt);
134
+ const vi = Math.round(v);
135
+ const str = U.fmtYear(vi);
136
+ const era = v < 0 ? 'BC' : 'AD';
137
+ c.save();
138
+ c.textBaseline = 'alphabetic';
139
+ if (ct.shadow) { c.shadowColor = ct.shadow.color; c.shadowBlur = ct.shadow.blur || 0; c.shadowOffsetX = ct.shadow.x || 0; c.shadowOffsetY = ct.shadow.y || 0; }
140
+ let x = ct.x;
141
+ // 后缀(BC/AD)
142
+ const sfx = ct.suffix && (ct.suffix.show === 'always' || (ct.suffix.show !== 'never')) ? ((ct.suffix.text === 'BC' || ct.suffix.text === 'AD') ? era : (ct.suffix.text || era)) : '';
143
+ if (sfx) {
144
+ c.font = `${ct.suffix.size}px "${ct.suffix.font || ct.font}"`; c.fillStyle = ct.suffix.color || ct.color; c.textAlign = 'right';
145
+ c.fillText(sfx, x, ct.y + (ct.suffix.dy || 0));
146
+ x -= c.measureText(sfx).width + (ct.suffix.gap || 12);
147
+ }
148
+ c.font = `${ct.size}px "${ct.font}"`; c.fillStyle = ct.color; c.textAlign = 'left';
149
+ if (ct.spacing) c.letterSpacing = ct.spacing + 'px';
150
+ const w = c.measureText(str).width - (ct.spacing || 0);
151
+ const x0 = x - w;
152
+ // 滚动中:除个位外照常画,个位画成上下滚动
153
+ if (rolling) {
154
+ const head = str.slice(0, -1), last = str.slice(-1);
155
+ c.fillText(head, x0, ct.y);
156
+ const hw = head ? c.measureText(head).width : 0;
157
+ const frac = (Math.abs(v) % 1);
158
+ const lw = c.measureText(last).width;
159
+ c.save(); c.beginPath(); c.rect(x0 + hw - 2, ct.y - ct.size * 1.0, lw + 30, ct.size * 1.25); c.clip();
160
+ c.fillText(last, x0 + hw, ct.y - frac * ct.size * 0.3);
161
+ c.globalAlpha = 0.35; c.fillText(String((+last + 1) % 10), x0 + hw, ct.y - ct.size * 0.85 - frac * ct.size * 0.3);
162
+ c.restore();
163
+ } else c.fillText(str, x0, ct.y);
164
+ c.restore();
165
+ if (ct.label) {
166
+ const L = ct.label; c.save(); c.font = L.font; c.fillStyle = L.color; c.textAlign = L.align || 'right'; c.textBaseline = 'alphabetic';
167
+ if (L.spacing) c.letterSpacing = L.spacing + 'px';
168
+ if (L.shadow) { c.shadowColor = L.shadow.color; c.shadowBlur = L.shadow.blur || 0; c.shadowOffsetX = L.shadow.x || 0; c.shadowOffsetY = L.shadow.y || 0; }
169
+ if (L.gradient) { const g = c.createLinearGradient(0, L.y - 30, 0, L.y); L.gradient.forEach((col, i) => g.addColorStop(i / (L.gradient.length - 1), col)); c.fillStyle = g; }
170
+ c.fillText(L.text, (L.x || ct.x) + (L.spacing && (L.align || 'right') === 'right' ? L.spacing : 0), L.y); c.restore();
171
+ }
172
+ };
173
+
174
+ // ---------- 热气(通用:多条正弦卷曲的半透明笔触) ----------
175
+ U.steam = function (c, t, { x, y, h = 70, n = 3, w = 3, color = 'rgba(255,255,255,.8)', spread = 14, speed = 1, seed = 1, wobble = 8 }) {
176
+ c.save(); c.strokeStyle = color; c.lineCap = 'round'; c.lineWidth = w;
177
+ for (let k = 0; k < n; k++) {
178
+ const ox = (k - (n - 1) / 2) * spread;
179
+ const ph = t * 2.2 * speed + k * 1.7 + seed;
180
+ c.beginPath();
181
+ for (let s = 0; s <= 24; s++) {
182
+ const q = s / 24, yy = y - q * h, xx = x + ox + Math.sin(ph + q * 5) * wobble * q;
183
+ c.globalAlpha = 1;
184
+ s ? c.lineTo(xx, yy) : c.moveTo(xx, yy);
185
+ }
186
+ const g = c.createLinearGradient(0, y, 0, y - h); g.addColorStop(0, color); g.addColorStop(1, 'rgba(255,255,255,0)');
187
+ c.strokeStyle = g; c.stroke();
188
+ }
189
+ c.restore();
190
+ };
191
+
192
+ // ---------- 视频层:每个时代可挂一段帧序列(clips/<id>/%04d.jpg),按需异步加载 ----------
193
+ // clip: {dir, fps, n, in, speed, loop} 本段局部时间 lt → 片内时间 in + lt*speed → 帧号
194
+ const FRAMES = new Map(); // url -> Image(已 decode)
195
+ function clipFrameUrl(e, lt) {
196
+ const c = e.clip; let ct = (c.in || 0) + Math.max(0, lt) * (c.speed || 1);
197
+ let i = Math.round(ct * c.fps);
198
+ if (c.loop === 'pingpong') { const m = 2 * (c.n - 1); i = i % m; if (i >= c.n) i = m - i; } else i = Math.min(c.n - 1, i);
199
+ return `${c.dir}/${String(i + 1).padStart(4, '0')}.jpg`;
200
+ }
201
+ async function need(url) {
202
+ if (FRAMES.has(url)) return;
203
+ const im = new Image(); im.src = url;
204
+ try { await im.decode(); } catch (e) { const msg = `视频帧缺失或解不开:${url}(clip.dir / n / in 对不上帧序列)`; console.error(msg); throw new Error(msg); }
205
+ FRAMES.set(url, im);
206
+ if (FRAMES.size > 240) { const k = FRAMES.keys().next().value; FRAMES.delete(k); }
207
+ }
208
+ function layersAt(t) {
209
+ let k = ERAS.findIndex(e => t >= e.t0 && t < e.t1); if (k < 0) k = ERAS.length - 1;
210
+ const out = [k]; const e = ERAS[k];
211
+ if (k > 0 && e.transition && t - e.t0 < e.transition.dur + (e.transition.delay || 0)) out.push(k - 1);
212
+ return out;
213
+ }
214
+ window.prepare = async function (t) {
215
+ await Promise.all(layersAt(t).map(k => ERAS[k].clip ? need(clipFrameUrl(ERAS[k], t - ERAS[k].t0)) : null));
216
+ };
217
+
218
+ // ---------- 画一个时代(本段局部时间 lt) ----------
219
+ function drawEra(c, e, t, prev, punchFrom, noCounter) {
220
+ const lt = t - e.t0;
221
+ c.save();
222
+ c.clearRect(0, 0, W, H);
223
+ // 拍点镜头冲击(只作用于场景层,年份牌不缩放):s(k)=1+0.03·(1-k/20)^1.5,k=转场开始后的帧数
224
+ // 冲击跟「正在进来的那一段」的转场起点走:转场期间旧段也一起放大(原片拍点那一帧整屏都在冲)
225
+ const pe = punchFrom || e;
226
+ // 幅度:transition.punch(数字;false/0 关掉)> 片级 window.PUNCH > 0.03。解说/发布会/数据片不要这一下,写 window.PUNCH = 0。
227
+ const pa = pe.transition ? (pe.transition.punch ?? window.PUNCH ?? 0.03) : 0, amp = pa === true ? 0.03 : +pa || 0;
228
+ const k60 = amp ? (t - pe.t0) * 60 : 99;
229
+ const punch = k60 >= 0 && k60 < 20 ? 1 + amp * Math.pow(1 - k60 / 20, 1.5) : 1;
230
+ c.save(); if (punch !== 1) { c.translate(W / 2, H / 2); c.scale(punch, punch); c.translate(-W / 2, -H / 2); }
231
+ // 视频帧:等比铺满(MD.cover,不拉伸);段里写 clipDraw(c, frame, lt, t) 自己摆(推近、焦点、冲印件)。帧没准备好不许静默空白——直接报错
232
+ if (e.clip) { const url = clipFrameUrl(e, lt), im = FRAMES.get(url);
233
+ if (!im) throw new Error(`视频帧没加载:${url}(渲染前要先 await window.prepare(t);render.py 已经这么做,自己写的脚本也要)`);
234
+ if (e.clipDraw) e.clipDraw(c, im, lt, t); else MD.cover(c, im); }
235
+ else if (e.plate && !e.draw) c.drawImage(IMG[e.plate], 0, 0, W, H);
236
+ if (e.draw) e.draw(c, lt, t, IMG, prev);
237
+ c.restore();
238
+ if (!noCounter) drawCounter(c, e, t);
239
+ c.restore();
240
+ }
241
+
242
+ // ---------- 主渲染 ----------
243
+ function renderFrame(t) {
244
+ let k = ERAS.findIndex(e => t >= e.t0 && t < e.t1); if (k < 0) k = ERAS.length - 1;
245
+ const e = ERAS[k], prev = ERAS[k - 1];
246
+ const tr = e.transition;
247
+ const trDur = tr ? tr.dur + (tr.delay || 0) : 0;
248
+ const lt = t - e.t0;
249
+ if (prev && tr && lt < trDur) {
250
+ const cut = (tr.counter || window.COUNTER_IN_TRANSITION || 'cut') === 'cut';
251
+ drawEra(bufA.getContext('2d'), prev, t, ERAS[k - 2], e, cut);
252
+ drawEra(bufB.getContext('2d'), e, t, prev, undefined, cut);
253
+ const p = U.clamp((lt - (tr.delay || 0)) / tr.dur);
254
+ ctx.save(); ctx.clearRect(0, 0, W, H);
255
+ // o.id / o.from = 进入段 / 离开段的 id:依赖画面内容的转场缓存要带上它(transitions.js 的 once(k, f, o))
256
+ TRANSITIONS[tr.type](ctx, bufA, bufB, p, { ...tr, id: e.id, from: prev.id, lt, t, W, H, tmp: bufT, IMG });
257
+ ctx.restore();
258
+ if (cut) drawCounter(ctx, p < 0.5 ? prev : e, t);
259
+ } else {
260
+ drawEra(ctx, e, t, prev);
261
+ }
262
+ if (window.GLOBAL_OVERLAY) window.GLOBAL_OVERLAY(ctx, t);
263
+ return k;
264
+ }
265
+ window.renderFrame = renderFrame;
266
+ // 单段预览:opt.counter = false 不画角标(自检量帧差时计数器滚动会抬高数字——迁移测试 B)
267
+ window.renderSolo = function (id, lt, opt = {}) { const e = ERAS.find(x => x.id === id); if (!e) throw new Error('renderSolo: 没有这一段 ' + id); const k = ERAS.indexOf(e); drawEra(ctx, e, e.t0 + lt, ERAS[k - 1], undefined, opt.counter === false); };
268
+
269
+ // ---------- 预览 UI ----------
270
+ if (location.search.includes('render=1')) document.body.classList.add('render');
271
+ const scrub = document.getElementById('scrub'), tt = document.getElementById('tt'); scrub.max = window.__total;
272
+ let playing = false, t0 = 0, base = 0;
273
+ scrub.oninput = () => { playing = false; renderFrame(+scrub.value); tt.textContent = (+scrub.value).toFixed(3); };
274
+ document.getElementById('play').onclick = () => { playing = !playing; t0 = performance.now(); base = +scrub.value; if (playing) loop(); };
275
+ function loop() { if (!playing) return; let t = base + (performance.now() - t0) / 1000; if (t > window.__total) { t = 0; base = 0; t0 = performance.now(); } scrub.value = t; tt.textContent = t.toFixed(3); renderFrame(t); requestAnimationFrame(loop); }
276
+ boot().catch(err => { window.__bootFailed = String(err && err.stack || err); console.error('BOOT FAILED\n' + window.__bootFailed); });
277
+ })();
package/engine/eras.js ADDED
@@ -0,0 +1,60 @@
1
+ // 纯代码版:每个时代的画面由 scenes/<id>.js 的 draw(c, lt, t) 程序化绘制。
2
+ // 15 个时代 + 2026 结尾。eighths = 本段占几个八分音符(128 BPM,见 references/风格配方/_音轨_艺术史速通.md)。
3
+ const R = 1866; // 右对齐基准
4
+ const box = (x0, y0, x1, y1, fill, stroke, lw = 4, inset = 0) => c => {
5
+ c.save(); c.fillStyle = fill; c.fillRect(x0, y0, x1 - x0, y1 - y0);
6
+ if (stroke) { c.strokeStyle = stroke; c.lineWidth = lw; c.strokeRect(x0 + inset, y0 + inset, x1 - x0 - 2 * inset, y1 - y0 - 2 * inset); }
7
+ c.restore();
8
+ };
9
+ // 原片复刻:年份牌位置固定、数字连续滚动,转场时角标留在新旧两层里一起变(engine 默认是转场中点硬切,见 drawCounter)
10
+ window.COUNTER_IN_TRANSITION = 'layers';
11
+ window.ERAS = [
12
+ { id: '01_cave', eighths: 6,
13
+ counter: { year: -40000, roll: 0, x: R, y: 114, size: 84, font: 'GochiHand-400', color: '#f3ead8', spacing: 14,
14
+ suffix: { text: 'BC', size: 44, gap: 18, dy: 0 }, label: { text: 'Cave painting', font: '38px "Kalam-700"', color: '#efe4cf', y: 178 } } },
15
+ { id: '02_egypt', eighths: 4, transition: { type: 'tiles', dur: 0.42, delay: 0 },
16
+ counter: { year: -1350, roll: 0.45, x: R, y: 129, size: 100, font: 'IMFellEnglish-400', color: '#6b2318', spacing: 6,
17
+ suffix: { text: 'BC', size: 50, gap: 14 }, label: { text: 'Egyptian wall painting', font: '34px "EBGaramond-400"', color: '#4a3424', y: 186 } } },
18
+ { id: '03_greek', eighths: 4, transition: { type: 'cube', dur: 0.32, delay: 0 },
19
+ counter: { year: -530, roll: 0.47, x: 1855, y: 136, size: 98, font: 'Cinzel-700', color: '#e3a066',
20
+ box: box(1544, 42, 1888, 208, '#1b1310', '#c8743c', 3, 4),
21
+ suffix: { text: 'BC', size: 48, gap: 14 }, label: { text: 'ATTIC BLACK-FIGURE', font: '29px "Cinzel-700"', color: '#e8b27c', x: 1855, y: 184, spacing: 2 } } },
22
+ { id: '04_roman', eighths: 4, transition: { type: 'mosaic', dur: 0.38, delay: 0 },
23
+ counter: { year: 79, roll: 0.43, x: 1852, y: 132, size: 96, font: 'Cinzel-400', color: '#1d1a17',
24
+ box: box(1607, 40, 1888, 208, '#f4ede0', '#2b2724', 4, 0),
25
+ suffix: { text: 'AD', size: 46, gap: 18 }, label: { text: 'ROMAN MOSAIC', font: '26px "Cinzel-400"', color: '#2b2724', x: 1860, y: 182, spacing: 2 } } },
26
+ { id: '05_gothic', eighths: 4, transition: { type: 'pageTurn', dur: 0.32, delay: 0 },
27
+ counter: { year: 1290, roll: 0.45, x: R, y: 134, size: 100, font: 'LibreBaskerville-700', color: '#b3222b',
28
+ label: { text: 'Gothic illumination', font: '36px "IMFellEnglish-400"', color: '#2b211a', y: 184 } } },
29
+ { id: '06_renaissance', eighths: 4, transition: { type: 'godRays', dur: 0.4, delay: 0, cx: 560, cy: 290 },
30
+ counter: { year: 1503, roll: 0.42, x: R, y: 142, size: 104, font: 'CormorantGaramond-500', color: '#ece0bf', spacing: 22,
31
+ label: { text: 'HIGH RENAISSANCE', font: '30px "Cinzel-700"', color: '#e7d9b4', y: 184, spacing: 2 } } },
32
+ { id: '08_impressionism', eighths: 3, transition: { type: 'dabs', dur: 0.28, delay: 0 },
33
+ counter: { year: 1874, roll: 0.37, x: R, y: 139, size: 104, font: 'PlayfairDisplay-400i', color: '#5a4f9a',
34
+ label: { text: 'Impressionism', font: '30px "EBGaramond-600"', color: '#6a5a9a', y: 182 } } },
35
+ { id: '09_postimp', eighths: 3, transition: { type: 'swirl', dur: 0.28, delay: 0, cx: 560, cy: 340 },
36
+ counter: { year: 1889, roll: 0.34, x: R, y: 134, size: 104, font: 'LilitaOne-400', color: '#f5d03b', shadow: { color: '#2a2a6a', x: 3, y: 4 },
37
+ label: { text: 'Post-Impressionism', font: '34px "LilitaOne-400"', color: '#f5d03b', y: 182, shadow: { color: '#2a2a6a', x: 2, y: 3 } } } },
38
+ { id: '10_nouveau', eighths: 3, transition: { type: 'organic', dur: 0.24, delay: 0, cx: 1330, cy: 320 },
39
+ counter: { year: 1896, roll: 0.37, x: R, y: 134, size: 96, font: 'LibreBaskerville-700', color: '#3a2a1c',
40
+ label: { text: 'ART NOUVEAU POSTER', font: '29px "EBGaramond-600"', color: '#3a2a1c', y: 182, spacing: 3 } } },
41
+ { id: '11_cubism', eighths: 2, transition: { type: 'shards', dur: 0.24, delay: 0 },
42
+ counter: { year: 1912, roll: 0.23, x: R, y: 134, size: 98, font: 'AlfaSlabOne-400', color: '#1e1b18', spacing: 4,
43
+ label: { text: 'CUBISM', font: '27px "AlfaSlabOne-400"', color: '#1e1b18', y: 182, spacing: 3 } } },
44
+ { id: '12_bauhaus', eighths: 2, transition: { type: 'bauhaus', dur: 0.2, delay: 0, cx: 1330, cy: 590 },
45
+ counter: { year: 1923, roll: 0.2, x: R, y: 134, size: 104, font: 'ArchivoBlack-400', color: '#151515',
46
+ label: { text: 'bauhaus', font: '36px "Poppins-800"', color: '#c4202b', y: 184 } } },
47
+ { id: '13_pop', eighths: 2, transition: { type: 'zigzag', dur: 0.16, delay: 0 },
48
+ counter: { year: 1962, roll: 0.22, x: 1852, y: 144, size: 104, font: 'Anton-400', color: '#111',
49
+ box: c => { c.save(); c.fillStyle = '#111'; c.fillRect(1637, 40, 253, 170); c.fillStyle = '#f7e23a'; c.fillRect(1643, 46, 241, 158); c.restore(); },
50
+ label: { text: 'POP ART', font: '30px "Anton-400"', color: '#111', x: 1852, y: 192, spacing: 1 } } },
51
+ { id: '14_8bit', eighths: 1, transition: { type: 'pixelate', dur: 0.12, delay: 0 },
52
+ counter: { year: 1985, roll: 0.07, x: R + 4, y: 86, size: 60, font: 'PressStart2P-400', color: '#ffffff', shadow: { color: '#0b1d24', x: 4, y: 4 },
53
+ label: { text: '8-BIT', font: '30px "PressStart2P-400"', color: '#ffffff', y: 138, shadow: { color: '#0b1d24', x: 3, y: 3 } } } },
54
+ { id: '15_raytrace', eighths: 1, transition: { type: 'wireframe', dur: 0.14, delay: 0 },
55
+ counter: { year: 1993, roll: 0.07, x: R, y: 129, size: 104, font: 'Exo2-800i', color: '#ffffff', shadow: { color: '#d23aa6', x: 5, y: 5 },
56
+ label: { text: 'RAY-TRACED CGI', font: '30px "Exo2-800i"', gradient: ['#ffd84a', '#f08a2a'], y: 182, shadow: { color: '#7a2a7a', x: 2, y: 2 } } } },
57
+ { id: '16_2026', eighths: 1 + 9 + 8, dur: 4.28, finale: true, transition: { type: 'iris', dur: 0.183, delay: 0 },
58
+ counter: { year: 2026, roll: 0.4, x: R, y: 132, size: 100, font: 'Poppins-800', color: '#232a5c',
59
+ label: { text: 'Flat vector illustration', font: '30px "Poppins-600"', color: '#e2554e', y: 182 } } },
60
+ ];
@@ -0,0 +1,115 @@
1
+ // 全风格样片:skill 里 35 种风格(15 原段+迁移测试 A–D 的 20 段)各一段,按年代排,用作回归测试与风格目录。
2
+ // 打开:index.html?film=gallery 渲染:render.py --film gallery 验收:qa.py --project scripts/engine --film gallery
3
+ // 每段 5 个八分音符(128 BPM ≈ 1.17s),最后一段 6 个。相邻段用「进入段」的签名转场(原 16 段沿用原片转场,A–D 组用各自配方卡里的转场;
4
+ // 每组第一段原来在片头没有进入转场,样片补了 inkBloom / redWedge / comicPanels / chiaroscuro)。
5
+ // 角标:年份严格单调,所以全片一个年份计数器连续滚动;各段沿用自己风格的字体与版式,label 写风格名。转场中点硬切新旧角标(引擎默认)。
6
+ // name 字段只给总览图/文档用(「年份+风格名」),引擎不读。
7
+ const R = 1866;
8
+ const box = (x0, y0, x1, y1, fill, stroke, lw = 4, inset = 0) => c => {
9
+ c.save(); c.fillStyle = fill; c.fillRect(x0, y0, x1 - x0, y1 - y0);
10
+ if (stroke) { c.strokeStyle = stroke; c.lineWidth = lw; c.strokeRect(x0 + inset, y0 + inset, x1 - x0 - 2 * inset, y1 - y0 - 2 * inset); }
11
+ c.restore();
12
+ };
13
+ window.ERAS = [
14
+ { id: '01_cave', name: '洞穴壁画', eighths: 5,
15
+ counter: { year: -40000, roll: 0, x: R, y: 114, size: 84, font: 'GochiHand-400', color: '#f3ead8', spacing: 14,
16
+ suffix: { text: 'BC', size: 44, gap: 18, dy: 0 }, label: { text: 'Cave painting', font: '38px "Kalam-700"', color: '#efe4cf', y: 178 } } },
17
+ { id: '02_egypt', name: '古埃及壁画', eighths: 5, transition: { type: 'tiles', dur: 0.42 },
18
+ counter: { year: -1350, roll: 0.4, x: R, y: 129, size: 100, font: 'IMFellEnglish-400', color: '#6b2318', spacing: 6,
19
+ suffix: { text: 'BC', size: 50, gap: 14 }, label: { text: 'Egyptian wall painting', font: '34px "EBGaramond-400"', color: '#4a3424', y: 186 } } },
20
+ { id: '03_greek', name: '古希腊黑绘陶', eighths: 5, transition: { type: 'cube', dur: 0.32 },
21
+ counter: { year: -530, roll: 0.4, x: 1855, y: 136, size: 98, font: 'Cinzel-700', color: '#e3a066',
22
+ box: box(1544, 42, 1888, 208, '#1b1310', '#c8743c', 3, 4),
23
+ suffix: { text: 'BC', size: 48, gap: 14 }, label: { text: 'ATTIC BLACK-FIGURE', font: '29px "Cinzel-700"', color: '#e8b27c', x: 1855, y: 184, spacing: 2 } } },
24
+ { id: '04_roman', name: '罗马马赛克', eighths: 5, transition: { type: 'mosaic', dur: 0.38 },
25
+ counter: { year: 79, roll: 0.4, x: 1852, y: 132, size: 96, font: 'Cinzel-400', color: '#1d1a17',
26
+ box: box(1607, 40, 1888, 208, '#f4ede0', '#2b2724', 4, 0),
27
+ suffix: { text: 'AD', size: 46, gap: 18 }, label: { text: 'ROMAN MOSAIC', font: '26px "Cinzel-400"', color: '#2b2724', x: 1860, y: 182, spacing: 2 } } },
28
+ { id: '20_dunhuang', name: '敦煌壁画', eighths: 5, transition: { type: 'apsara', dur: 0.5 },
29
+ counter: { year: 720, roll: 0.4, x: 1852, y: 132, size: 92, font: 'LXGWWenKai-500', color: '#2a1a14', box: box(1500, 44, 1880, 204, '#e6d6b2', '#5a2414', 4, 6), suffix: { text: 'AD', size: 40, gap: 14, font: 'Cinzel-700' }, label: { text: '敦煌壁画 · 盛唐', font: '34px "LXGWWenKai-500"', color: '#8a2a1c', x: 1852, y: 184, spacing: 4 } } },
30
+ { id: '34_shadowpuppet', name: '皮影戏', eighths: 5, transition: { type: 'lampGlow', dur: 0.4, cx: 960, cy: 470 },
31
+ counter: { year: 1050, roll: 0.4, x: R, y: 132, size: 96, font: 'LXGWWenKai-500', color: '#7a1d12', spacing: 4, suffix: { text: 'AD', size: 40, gap: 14, font: 'Cinzel-700' }, label: { text: '皮影戏 · 北宋', font: '36px "LXGWWenKai-500"', color: '#5a2a14', y: 184, spacing: 4 } } },
32
+ { id: '05_gothic', name: '哥特手抄本', eighths: 5, transition: { type: 'pageTurn', dur: 0.32 },
33
+ counter: { year: 1290, roll: 0.4, x: R, y: 134, size: 100, font: 'LibreBaskerville-700', color: '#b3222b',
34
+ label: { text: 'Gothic illumination', font: '36px "IMFellEnglish-400"', color: '#2b211a', y: 184 } } },
35
+ { id: '06_renaissance', name: '文艺复兴', eighths: 5, transition: { type: 'godRays', dur: 0.4, cx: 560, cy: 290 },
36
+ counter: { year: 1503, roll: 0.4, x: R, y: 142, size: 104, font: 'CormorantGaramond-500', color: '#ece0bf', spacing: 22,
37
+ label: { text: 'HIGH RENAISSANCE', font: '30px "Cinzel-700"', color: '#e7d9b4', y: 184, spacing: 2 } } },
38
+ { id: '32_rembrandt', name: '伦勃朗明暗法', eighths: 5, transition: { type: 'chiaroscuro', dur: 0.45, cx: 580, cy: 300 },
39
+ counter: { year: 1642, roll: 0.4, x: R, y: 134, size: 104, font: 'CormorantGaramond-500', color: '#e9c98a', spacing: 10, label: { text: 'Rembrandt · chiaroscuro', font: '34px "IMFellEnglish-400i"', color: '#c9a467', y: 184 } } },
40
+ { id: '17_ink', name: '水墨写意', eighths: 5, transition: { type: 'inkBloom', dur: 0.45, cx: 560, cy: 740 },
41
+ counter: { year: 1690, roll: 0.4, x: R, y: 132, size: 100, font: 'LXGWWenKai-500', color: '#1a1612', spacing: 4, label: { text: '水墨写意 · 八大山人', font: '38px "LXGWWenKai-500"', color: '#3a2c22', y: 186, spacing: 4 } } },
42
+ { id: '08_impressionism', name: '印象派', eighths: 5, transition: { type: 'dabs', dur: 0.28 },
43
+ counter: { year: 1874, roll: 0.4, x: R, y: 139, size: 104, font: 'PlayfairDisplay-400i', color: '#5a4f9a',
44
+ label: { text: 'Impressionism', font: '30px "EBGaramond-600"', color: '#6a5a9a', y: 182 } } },
45
+ { id: '29_seurat', name: '修拉点彩', eighths: 5, transition: { type: 'pointill', dur: 0.34 },
46
+ counter: { year: 1884, roll: 0.4, x: R, y: 134, size: 100, font: 'Marcellus-400', color: '#20306a', spacing: 6,
47
+ label: { text: 'SEURAT · LA GRANDE JATTE', font: '27px "Marcellus-400"', color: '#7a3a2a', y: 182, spacing: 2 } } },
48
+ { id: '09_postimp', name: '后印象派(梵高)', eighths: 5, transition: { type: 'swirl', dur: 0.28, cx: 560, cy: 340 },
49
+ counter: { year: 1889, roll: 0.4, x: R, y: 134, size: 104, font: 'LilitaOne-400', color: '#f5d03b', shadow: { color: '#2a2a6a', x: 3, y: 4 },
50
+ label: { text: 'Post-Impressionism', font: '34px "LilitaOne-400"', color: '#f5d03b', y: 182, shadow: { color: '#2a2a6a', x: 2, y: 3 } } } },
51
+ { id: '19_munch', name: '蒙克表现主义', eighths: 5, transition: { type: 'screamWarp', dur: 0.5 },
52
+ counter: { year: 1893, roll: 0.4, x: R, y: 134, size: 100, font: 'IMFellEnglish-400', color: '#f6c040', shadow: { color: 'rgba(40,10,20,.6)', x: 4, y: 4 }, label: { text: 'Munch · The Scream', font: '38px "IMFellEnglish-400i"', color: '#f8d88a', y: 184, shadow: { color: 'rgba(40,10,20,.6)', x: 2, y: 3 } } } },
53
+ { id: '10_nouveau', name: '新艺术运动海报', eighths: 5, transition: { type: 'organic', dur: 0.24, cx: 1330, cy: 320 },
54
+ counter: { year: 1896, roll: 0.4, x: R, y: 134, size: 96, font: 'LibreBaskerville-700', color: '#3a2a1c',
55
+ label: { text: 'ART NOUVEAU POSTER', font: '29px "EBGaramond-600"', color: '#3a2a1c', y: 182, spacing: 3 } } },
56
+ { id: '28_monet', name: '莫奈睡莲', eighths: 5, transition: { type: 'ripple', dur: 0.34, cx: 1000, cy: 600 },
57
+ counter: { year: 1899, roll: 0.4, x: R, y: 136, size: 104, font: 'PlayfairDisplay-400i', color: '#3e5a86', spacing: 4, shadow: { color: 'rgba(252,248,238,1)', blur: 18, x: 0, y: 0 },
58
+ label: { text: 'Monet · Nymphéas', font: '36px "PlayfairDisplay-400i"', color: '#4a5f8e', y: 186, shadow: { color: 'rgba(252,248,238,1)', blur: 14, x: 0, y: 0 } } } },
59
+ { id: '36_picasso_blue', name: '毕加索蓝色时期', eighths: 5, transition: { type: 'blueBrush', dur: 0.42 },
60
+ counter: { year: 1903, roll: 0.4, x: R, y: 136, size: 108, font: 'Caveat-600', color: '#c9d6e4', label: { text: 'Picasso · période bleue', font: '38px "Caveat-600"', color: '#9fb3cc', y: 188 } } },
61
+ { id: '18_klimt', name: '克里姆特金色时期', eighths: 5, transition: { type: 'goldLeaf', dur: 0.5, cx: 1300, cy: 380 },
62
+ counter: { year: 1907, roll: 0.4, x: R, y: 134, size: 100, font: 'Marcellus-400', color: '#f6dc8a', shadow: { color: 'rgba(40,24,6,.7)', x: 3, y: 4, blur: 6 }, label: { text: '克里姆特 · 金色时期', font: '36px "LXGWWenKai-500"', color: '#f3d27a', y: 186, shadow: { color: 'rgba(40,24,6,.7)', x: 2, y: 3, blur: 4 } } } },
63
+ { id: '11_cubism', name: '立体主义', eighths: 5, transition: { type: 'shards', dur: 0.24 },
64
+ counter: { year: 1912, roll: 0.4, x: R, y: 134, size: 98, font: 'AlfaSlabOne-400', color: '#1e1b18', spacing: 4,
65
+ label: { text: 'CUBISM', font: '27px "AlfaSlabOne-400"', color: '#1e1b18', y: 182, spacing: 3 } } },
66
+ { id: '12_bauhaus', name: '包豪斯', eighths: 5, transition: { type: 'bauhaus', dur: 0.2, cx: 1330, cy: 590 },
67
+ counter: { year: 1923, roll: 0.4, x: R, y: 134, size: 104, font: 'ArchivoBlack-400', color: '#151515',
68
+ label: { text: 'bauhaus', font: '36px "Poppins-800"', color: '#c4202b', y: 184 } } },
69
+ { id: '22_constructivism', name: '构成主义海报', eighths: 5, transition: { type: 'redWedge', dur: 0.4 },
70
+ counter: { year: 1924, roll: 0.4, x: R, y: 132, size: 104, font: 'Anton-400', color: '#151311',
71
+ box: c => { c.save(); c.fillStyle = '#c8231c'; c.beginPath(); c.moveTo(1560, 46); c.lineTo(1892, 30); c.lineTo(1892, 152); c.lineTo(1560, 160); c.closePath(); c.fill(); c.restore(); },
72
+ label: { text: 'CONSTRUCTIVISM', font: '30px "ArchivoBlack-400"', color: '#151311', y: 196, spacing: 4 } } },
73
+ { id: '33_rubberhose', name: '橡皮管卡通', eighths: 5, transition: { type: 'irisFilm', dur: 0.4, cx: 1236, cy: 420 },
74
+ counter: { year: 1930, roll: 0.4, x: 1858, y: 134, size: 100, font: 'Rye-400', color: '#141210', spacing: 4, box: c => { c.save(); c.fillStyle = '#efe7d2'; c.strokeStyle = '#141210'; c.lineWidth = 6; c.beginPath(); c.roundRect(1560, 44, 330, 166, 26); c.fill(); c.stroke(); c.restore(); }, label: { text: 'RUBBER HOSE', font: '34px "LilitaOne-400"', color: '#141210', x: 1858, y: 186, spacing: 2 } } },
75
+ { id: '23_dali', name: '达利超现实', eighths: 5, transition: { type: 'melt', dur: 0.36 },
76
+ counter: { year: 1931, roll: 0.4, x: R, y: 136, size: 104, font: 'PlayfairDisplay-700i', color: '#f6e8c8', shadow: { color: 'rgba(10,20,50,.55)', x: 5, y: 5, blur: 4 },
77
+ label: { text: 'Surrealism', font: '34px "PlayfairDisplay-400i"', color: '#f0dcae', y: 184 } } },
78
+ { id: '24_hopper', name: '霍珀美国现实主义', eighths: 5, transition: { type: 'sunShaft', dur: 0.34 },
79
+ counter: { year: 1942, roll: 0.4, x: R, y: 134, size: 100, font: 'LibreBaskerville-700', color: '#f3e3b5', shadow: { color: 'rgba(20,30,25,.55)', x: 4, y: 4 },
80
+ label: { text: 'AMERICAN REALISM', font: '27px "LibreBaskerville-400"', color: '#f0dca8', y: 182, spacing: 3 } } },
81
+ { id: '30_matisse', name: '马蒂斯剪纸', eighths: 5, transition: { type: 'cutouts', dur: 0.36 },
82
+ counter: { year: 1947, roll: 0.4, x: R, y: 136, size: 108, font: 'Caveat-600', color: '#111', spacing: 2,
83
+ label: { text: 'Matisse · Jazz', font: '46px "Caveat-600"', color: '#c8242a', y: 188 } } },
84
+ { id: '13_pop', name: '波普艺术', eighths: 5, transition: { type: 'zigzag', dur: 0.16 },
85
+ counter: { year: 1962, roll: 0.4, x: 1852, y: 144, size: 104, font: 'Anton-400', color: '#111',
86
+ box: c => { c.save(); c.fillStyle = '#111'; c.fillRect(1637, 40, 253, 170); c.fillStyle = '#f7e23a'; c.fillRect(1643, 46, 241, 158); c.restore(); },
87
+ label: { text: 'POP ART', font: '30px "Anton-400"', color: '#111', x: 1852, y: 192, spacing: 1 } } },
88
+ { id: '21_kusama', name: '草间弥生波点', eighths: 5, transition: { type: 'dotBloom', dur: 0.48, cx: 540, cy: 760 },
89
+ counter: { year: 1965, roll: 0.4, x: R, y: 134, size: 104, font: 'Poppins-800', color: '#ffffff', shadow: { color: '#141414', x: 5, y: 5 }, label: { text: '草间弥生 · 无限波点', font: '36px "LXGWWenKai-500"', color: '#ffffff', y: 186, shadow: { color: '#141414', x: 3, y: 3 } } } },
90
+ { id: '27_kirby', name: 'Kirby 漫画', eighths: 5, transition: { type: 'comicPanels', dur: 0.42 },
91
+ counter: { year: 1966, roll: 0.4, x: 1858, y: 132, size: 100, font: 'Bangers-400', color: '#151313', spacing: 4,
92
+ box: c => { c.save(); c.translate(6, 7); c.fillStyle = '#151313'; c.fillRect(1560, 38, 330, 176); c.restore();
93
+ c.save(); c.fillStyle = '#ffe23a'; c.fillRect(1560, 38, 330, 176); c.strokeStyle = '#151313'; c.lineWidth = 6; c.strokeRect(1560, 38, 330, 176); c.restore(); },
94
+ label: { text: 'JACK KIRBY · MARVEL', font: '34px "Bangers-400"', color: '#d8202a', x: 1858, y: 190, spacing: 2 } } },
95
+ { id: '31_haring', name: '哈林涂鸦', eighths: 5, transition: { type: 'radiant', dur: 0.32, cx: 1000, cy: 600 },
96
+ counter: { year: 1982, roll: 0.4, x: R, y: 136, size: 108, font: 'Kalam-700', color: '#111', spacing: 2,
97
+ label: { text: 'KEITH HARING', font: '40px "Kalam-700"', color: '#111', y: 188, spacing: 2 } } },
98
+ { id: '14_8bit', name: '8-bit 像素', eighths: 5, transition: { type: 'pixelate', dur: 0.12 },
99
+ counter: { year: 1985, roll: 0.4, x: R + 4, y: 86, size: 60, font: 'PressStart2P-400', color: '#ffffff', shadow: { color: '#0b1d24', x: 4, y: 4 },
100
+ label: { text: '8-BIT', font: '30px "PressStart2P-400"', color: '#ffffff', y: 138, shadow: { color: '#0b1d24', x: 3, y: 3 } } } },
101
+ { id: '25_ghibli', name: '吉卜力水彩', eighths: 5, transition: { type: 'bleed', dur: 0.38, cx: 580, cy: 330 },
102
+ counter: { year: 1988, roll: 0.4, x: R, y: 132, size: 104, font: 'Kalam-700', color: '#3f6a4a',
103
+ label: { text: 'Ghibli-style watercolor', font: '34px "Caveat-600"', color: '#4d7a58', y: 182 } } },
104
+ { id: '15_raytrace', name: '光追 CGI', eighths: 5, transition: { type: 'wireframe', dur: 0.14 },
105
+ counter: { year: 1993, roll: 0.4, x: R, y: 129, size: 104, font: 'Exo2-800i', color: '#ffffff', shadow: { color: '#d23aa6', x: 5, y: 5 },
106
+ label: { text: 'RAY-TRACED CGI', font: '30px "Exo2-800i"', gradient: ['#ffd84a', '#f08a2a'], y: 182, shadow: { color: '#7a2a7a', x: 2, y: 2 } } } },
107
+ { id: '26_vaporwave', name: '蒸汽波', eighths: 5, transition: { type: 'vhs', dur: 0.3 },
108
+ counter: { year: 2011, roll: 0.4, x: R, y: 130, size: 104, font: 'Exo2-800i', color: '#ffffff', shadow: { color: '#ff2fb4', x: 6, y: 5 },
109
+ label: { text: 'VAPORWAVE', font: '32px "Righteous-400"', gradient: ['#7ff6ff', '#ff71ce'], y: 182, spacing: 7, shadow: { color: '#3a0a6a', x: 2, y: 2 } } } },
110
+ { id: '35_shinkai', name: '新海诚光影', eighths: 5, transition: { type: 'flareSweep', dur: 0.38 },
111
+ counter: { year: 2016, roll: 0.4, x: R, y: 134, size: 100, font: 'Poppins-600', color: '#ffffff', shadow: { color: 'rgba(40,90,150,.5)', x: 3, y: 4, blur: 4 }, label: { text: 'Makoto Shinkai light', font: '30px "Poppins-600"', color: '#eaf6ff', y: 182, shadow: { color: 'rgba(40,90,150,.5)', x: 2, y: 3 } } } },
112
+ { id: '16_2026', name: '2026 扁平插画', eighths: 6, transition: { type: 'iris', dur: 0.183 },
113
+ counter: { year: 2026, roll: 0.4, x: R, y: 132, size: 100, font: 'Poppins-800', color: '#232a5c',
114
+ label: { text: 'Flat vector illustration', font: '30px "Poppins-600"', color: '#e2554e', y: 182 } } },
115
+ ];