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