@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
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// 长卷穿越片 · 共用库 XING(《花叔穿越名画》用的,随 demos/long_scroll 收进 skill)。
|
|
2
|
+
// 角色=AI 生的画风帧(gpt-image 按画风重画→绿幕抠图→切帧,见 references/10-角色.md),代码只管:在哪、多大、何时换帧、加什么材质。
|
|
3
|
+
// 场景、运镜、石子、涟漪、睡莲、青蛙、转场全部代码画。
|
|
4
|
+
// XING.sprites(keys) 同步读 demos/long_scroll/frames/<key>/meta.json,返回资源列表(给 era.assets)
|
|
5
|
+
// XING.actor(c, key, i, o) 画第 i 帧:o={x,y(脚底),h(站立身高),mat:'monet'|'ukiyoe'|'vangogh'|'none',wx,t,flip,alpha}
|
|
6
|
+
// XING.hatTop(key,i,o) 帽顶在屏幕上的位置(青蛙落点)
|
|
7
|
+
// XING.strokes(g, src, opt) 任意尺寸画布的笔触重画,每格独立种子(位置永不重洗)
|
|
8
|
+
// XING.monetWorld / ukiyoeWorld / vgWorld 三个世界:静态部分一次画好缓存(世界坐标),动态部分每帧画
|
|
9
|
+
// XING.stone(t, plan) 打水漂:石子位置+各次触水时刻
|
|
10
|
+
// XING.frog(c, x, y, s, pose, style, o)
|
|
11
|
+
(() => {
|
|
12
|
+
const W = 1920, H = 1080, TAU = Math.PI * 2;
|
|
13
|
+
const { clamp, lerp, ss, rng } = U;
|
|
14
|
+
const P = window.PAINT;
|
|
15
|
+
const X = window.XING = {};
|
|
16
|
+
const hx = P.hex;
|
|
17
|
+
const cellRng = (i, j, seed) => rng((Math.floor(U.hash(i * 131 + seed * 7919, j * 31 + seed) * 4294967296)) >>> 0);
|
|
18
|
+
const lum = c => c[0] * 0.3 + c[1] * 0.59 + c[2] * 0.11;
|
|
19
|
+
const pick = (arr, r) => arr[(r() * arr.length) | 0];
|
|
20
|
+
|
|
21
|
+
// ================= 笔触重画(任意尺寸,每格独立种子) =================
|
|
22
|
+
X.strokes = (g, src, { cell = 10, len = 24, width = 6, angle, palette, seed = 1, mask, alphaMask = false, alphaMin = 180, outline = 0, outlineCol = [20, 25, 60], outlineMix = 0.55, jitterCol = 14, skip, cap = 'round', alpha = 1 } = {}) => {
|
|
23
|
+
const w = src.width, h = src.height;
|
|
24
|
+
const sd = src.getContext('2d', { willReadFrequently: true }).getImageData(0, 0, w, h).data;
|
|
25
|
+
g.save(); g.lineCap = cap;
|
|
26
|
+
for (let j = 0; j * cell < h + cell; j++) for (let i = 0; i * cell < w + cell; i++) {
|
|
27
|
+
const r = cellRng(i, j, seed);
|
|
28
|
+
const px = i * cell + (r() - 0.5) * cell, py = j * cell + (r() - 0.5) * cell;
|
|
29
|
+
const ix = clamp(px | 0, 0, w - 1), iy = clamp(py | 0, 0, h - 1), k = (iy * w + ix) * 4;
|
|
30
|
+
if (alphaMask && sd[k + 3] < alphaMin) continue;
|
|
31
|
+
if (mask && !mask(px, py)) continue;
|
|
32
|
+
let col = [sd[k], sd[k + 1], sd[k + 2]];
|
|
33
|
+
if (skip && skip(col, px, py, r)) continue;
|
|
34
|
+
const pc = palette ? palette(px, py, col, r) : null;
|
|
35
|
+
col = pc || P.jitter(col, r, jitterCol);
|
|
36
|
+
const a = angle ? angle(px, py, r) : 0, l = len * (0.7 + r() * 0.6);
|
|
37
|
+
const dx = Math.cos(a) * l / 2, dy = Math.sin(a) * l / 2;
|
|
38
|
+
if (outline > 0) { g.strokeStyle = P.rgb(P.mix(col, outlineCol, outlineMix), outline * alpha); g.lineWidth = width + 2.5; g.beginPath(); g.moveTo(px - dx, py - dy); g.lineTo(px + dx, py + dy); g.stroke(); }
|
|
39
|
+
g.strokeStyle = P.rgb(col, alpha); g.lineWidth = width * (0.8 + r() * 0.4);
|
|
40
|
+
g.beginPath(); g.moveTo(px - dx, py - dy); g.lineTo(px + dx, py + dy); g.stroke();
|
|
41
|
+
}
|
|
42
|
+
g.restore();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ================= 角色帧 =================
|
|
46
|
+
X.SPR = {};
|
|
47
|
+
const syncJSON = url => { const x = new XMLHttpRequest(); x.open('GET', url, false); x.send(); if (x.status !== 200) throw new Error(url + ' ' + x.status); return JSON.parse(x.responseText); };
|
|
48
|
+
X.sprites = keys => {
|
|
49
|
+
const list = [];
|
|
50
|
+
for (const k of keys) {
|
|
51
|
+
if (!X.SPR[k]) { const meta = syncJSON(`demos/long_scroll/frames/${k}/meta.json`); X.SPR[k] = { key: k, meta, imgs: null, mats: {} }; }
|
|
52
|
+
X.SPR[k].meta.frames.forEach(f => list.push(`demos/long_scroll/frames/${k}/${f.file}`));
|
|
53
|
+
}
|
|
54
|
+
return list;
|
|
55
|
+
};
|
|
56
|
+
function ensure(key) {
|
|
57
|
+
const S = X.SPR[key]; if (S.imgs) return S;
|
|
58
|
+
S.imgs = S.meta.frames.map(f => window.IMG[`demos/long_scroll/frames/${key}/${f.file}`]);
|
|
59
|
+
// 帽顶:最上面一行不透明像素的中心
|
|
60
|
+
S.hat = S.imgs.map(im => { const c = P.canvas(im.width, im.height), g = c.getContext('2d', { willReadFrequently: true }); g.drawImage(im, 0, 0); const d = g.getImageData(0, 0, im.width, im.height).data;
|
|
61
|
+
for (let y = 0; y < im.height; y++) { let x0 = -1, x1 = -1; for (let x = 0; x < im.width; x++) if (d[(y * im.width + x) * 4 + 3] > 140) { if (x0 < 0) x0 = x; x1 = x; } if (x0 >= 0 && x1 - x0 > 6) return [(x0 + x1) / 2, y]; } return [im.width / 2, 0]; });
|
|
62
|
+
return S;
|
|
63
|
+
}
|
|
64
|
+
// 材质:把 AI 帧再过一遍该画风的笔触,让他和场景用同一种「笔」(不碰深色=眼镜、眼睛、头发不被糊掉)
|
|
65
|
+
const MAT = {
|
|
66
|
+
monet: { pad: 8, cell: 6, len: 12, width: 4.2, seed: 11, outline: 0, angle: (x, y, r) => -0.55 + (r() - 0.5) * 0.9,
|
|
67
|
+
skip: (col, x, y, r) => lum(col) < 95 || r() < 0.35,
|
|
68
|
+
palette: (x, y, col, r) => { const L = lum(col);
|
|
69
|
+
if (L > 205) return P.mix(hx(pick(['#fbf6ea', '#efe6f6', '#e0e2f6', '#fff0d4', '#e6eef8', '#f6e4ee'], r)), col, 0.45);
|
|
70
|
+
if (col[0] > 190 && col[0] > col[2] + 40 && col[1] > 120) return P.mix(hx(pick(['#f6c6a6', '#f0b4a0', '#fad6bc', '#e8a890', '#f8d0b0'], r)), col, 0.45);
|
|
71
|
+
if (col[0] > 150 && col[1] > 140 && col[2] < col[1]) return P.mix(hx(pick(['#d8c8a0', '#c8b48a', '#e2d4ae', '#b8aaa8', '#d0c0b8'], r)), col, 0.45);
|
|
72
|
+
return null; } },
|
|
73
|
+
vangogh: { pad: 8, cell: 7, len: 17, width: 5, seed: 13, outline: 0.45, outlineCol: [20, 26, 80],
|
|
74
|
+
angle: (x, y, r) => Math.PI / 2 + Math.sin(y * 0.05 + x * 0.02) * 0.7 + (r() - 0.5) * 0.4,
|
|
75
|
+
skip: (col, x, y, r) => lum(col) < 90 || r() < 0.25,
|
|
76
|
+
palette: (x, y, col, r) => { const L = lum(col);
|
|
77
|
+
if (L > 200) return P.mix(hx(pick(['#f8f0b0', '#e8f0f8', '#c8d8f0', '#fff6c8', '#d8e4f4'], r)), col, 0.35);
|
|
78
|
+
if (col[0] > 190 && col[0] > col[2] + 40) return P.mix(hx(pick(['#f0b060', '#e8a050', '#f8c878', '#e89060'], r)), col, 0.4);
|
|
79
|
+
return null; } },
|
|
80
|
+
};
|
|
81
|
+
function material(S, i, mat) {
|
|
82
|
+
const key = mat; S.mats[key] = S.mats[key] || [];
|
|
83
|
+
if (S.mats[key][i]) return S.mats[key][i];
|
|
84
|
+
const im = S.imgs[i], M = MAT[mat], pad = M ? M.pad : 0;
|
|
85
|
+
const out = P.canvas(im.width + 2 * pad, im.height + 2 * pad), g = out.getContext('2d');
|
|
86
|
+
g.drawImage(im, pad, pad);
|
|
87
|
+
if (M) { const base = P.canvas(out.width, out.height), bgx = base.getContext('2d', { willReadFrequently: true }); bgx.drawImage(im, pad, pad);
|
|
88
|
+
// 五官保护:附近 7px 内有深色像素(眼镜、眼睛、嘴、发际)就不下笔,免得把脸糊掉
|
|
89
|
+
const bw = base.width, bh = base.height, bd = bgx.getImageData(0, 0, bw, bh).data, dk = new Uint8Array(bw * bh);
|
|
90
|
+
for (let k = 0; k < bw * bh; k++) dk[k] = bd[k * 4 + 3] > 150 && (bd[k * 4] * 0.3 + bd[k * 4 + 1] * 0.59 + bd[k * 4 + 2] * 0.11) < 110 ? 1 : 0;
|
|
91
|
+
const nearDark = (x, y) => { for (let dy = -7; dy <= 7; dy += 3.5) for (let dx = -7; dx <= 7; dx += 3.5) { const xx = (x + dx) | 0, yy = (y + dy) | 0; if (xx >= 0 && yy >= 0 && xx < bw && yy < bh && dk[yy * bw + xx]) return true; } return false; };
|
|
92
|
+
X.strokes(g, base, { cell: M.cell, len: M.len, width: M.width, seed: M.seed + i, angle: M.angle, palette: M.palette, skip: (col, x, y, r) => M.skip(col, x, y, r) || nearDark(x, y), alphaMask: true, alphaMin: 170, outline: M.outline, outlineCol: M.outlineCol, jitterCol: 12 }); }
|
|
93
|
+
if (mat === 'ukiyoe') { // 木纹+纸纤维压进色块(multiply,只在人身上)
|
|
94
|
+
{ const d = g.getImageData(0, 0, out.width, out.height), q = d.data; // 生图把白帽白T画成米黄:低饱和的亮色拉回纸白(帽子定规是纯白)
|
|
95
|
+
for (let k = 0; k < q.length; k += 4) { const mx = Math.max(q[k], q[k + 1], q[k + 2]), mn = Math.min(q[k], q[k + 1], q[k + 2]); if (q[k + 3] > 0 && mx > 185 && mx - mn < 48) { const f = (mx - 185) / 70; q[k] = lerp(q[k], 251, f); q[k + 1] = lerp(q[k + 1], 249, f); q[k + 2] = lerp(q[k + 2], 242, f); } }
|
|
96
|
+
g.putImageData(d, 0, 0); }
|
|
97
|
+
const tex = P.canvas(out.width, out.height), tg = tex.getContext('2d'), r = rng(77 + i);
|
|
98
|
+
tg.fillStyle = '#fff'; tg.fillRect(0, 0, tex.width, tex.height);
|
|
99
|
+
for (let k = 0; k < 160; k++) { const y = r() * tex.height; tg.strokeStyle = `rgba(150,110,60,${0.05 + r() * 0.08})`; tg.lineWidth = 1 + r() * 1.5; tg.beginPath(); tg.moveTo(0, y); tg.bezierCurveTo(tex.width * 0.3, y + (r() - 0.5) * 8, tex.width * 0.7, y + (r() - 0.5) * 8, tex.width, y + (r() - 0.5) * 6); tg.stroke(); }
|
|
100
|
+
tg.globalCompositeOperation = 'destination-in'; tg.drawImage(out, 0, 0);
|
|
101
|
+
g.globalCompositeOperation = 'multiply'; g.drawImage(tex, 0, 0); g.globalCompositeOperation = 'source-over';
|
|
102
|
+
}
|
|
103
|
+
out.pad = pad; S.mats[key][i] = out; return out;
|
|
104
|
+
}
|
|
105
|
+
// 画一帧。x,y = 脚底锚点(屏幕),h = 站立身高(按 meta.ref_h 换算,整套帧同一缩放)
|
|
106
|
+
X.actor = (c, key, i, o) => {
|
|
107
|
+
const S = ensure(key), f = S.meta.frames[i], mat = o.mat || 'none';
|
|
108
|
+
const im = material(S, i, mat), pad = im.pad || 0, s = o.h / S.meta.ref_h * (o.scale || 1);
|
|
109
|
+
const fx = o.flip ? -1 : 1;
|
|
110
|
+
const dw = im.width * s, dh = im.height * s, x0 = o.x - (f.ax + pad) * s * fx, y0 = o.y - (f.ay + pad) * s;
|
|
111
|
+
// 光影斑驳(莫奈):暖黄光斑与淡紫阴影按世界坐标落在他身上,他走过时光从身上滑过
|
|
112
|
+
const S2 = P.scratch('actorTmp'), g = S2.getContext('2d');
|
|
113
|
+
const bw = Math.ceil(dw) + 4, bh = Math.ceil(dh) + 4;
|
|
114
|
+
g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, Math.min(W, bw + 4), Math.min(H, bh + 4));
|
|
115
|
+
g.save(); if (o.flip) { g.translate(bw - 2, 2); g.scale(-1, 1); } else g.translate(2, 2); g.drawImage(im, 0, 0, dw, dh); g.restore();
|
|
116
|
+
if (mat === 'monet' && o.dapple !== false) {
|
|
117
|
+
g.save(); g.globalCompositeOperation = 'source-atop';
|
|
118
|
+
const wx0 = (o.flip ? x0 - dw : x0) + ((o.wx ?? o.x) - o.x), t = o.t || 0; // 精灵左上角的世界 x
|
|
119
|
+
const cs = 70;
|
|
120
|
+
for (let j = -1; j * cs < bh + cs; j++) for (let ii = Math.floor(wx0 / cs) - 1; ii * cs < wx0 + bw + cs; ii++) {
|
|
121
|
+
const hh = U.hash(ii, j + 40), hv = U.hash(ii + 7, j); if (hh < 0.45) continue;
|
|
122
|
+
const cx = ii * cs + hv * cs + Math.sin(t * 0.9 + ii) * 10 - wx0, cy = j * cs + U.hash(ii, j + 3) * cs + Math.cos(t * 0.7 + j) * 6, rr = 18 + hh * 26;
|
|
123
|
+
const warm = U.hash(ii + 3, j + 9) > 0.35, rg = g.createRadialGradient(cx, cy, 0, cx, cy, rr);
|
|
124
|
+
rg.addColorStop(0, warm ? 'rgba(255,238,170,0.42)' : 'rgba(110,96,190,0.26)'); rg.addColorStop(1, warm ? 'rgba(255,238,170,0)' : 'rgba(110,96,190,0)');
|
|
125
|
+
g.fillStyle = rg; g.fillRect(cx - rr, cy - rr, rr * 2, rr * 2);
|
|
126
|
+
}
|
|
127
|
+
const sg = g.createLinearGradient(0, bh * 0.45, 0, bh); sg.addColorStop(0, 'rgba(120,110,200,0)'); sg.addColorStop(1, 'rgba(120,110,200,0.22)'); g.fillStyle = sg; g.fillRect(0, 0, bw, bh);
|
|
128
|
+
g.restore();
|
|
129
|
+
}
|
|
130
|
+
c.save(); if (o.alpha != null) c.globalAlpha *= o.alpha;
|
|
131
|
+
if (o.shadow) { c.fillStyle = o.shadow; c.beginPath(); c.ellipse(o.x, o.y + 2, o.h * 0.2, o.h * 0.035, 0, 0, TAU); c.fill(); }
|
|
132
|
+
c.drawImage(S2, 0, 0, bw, bh, (o.flip ? x0 - dw : x0) - 2, y0 - 2, bw, bh);
|
|
133
|
+
c.restore();
|
|
134
|
+
};
|
|
135
|
+
X.hatTop = (key, i, o) => { const S = ensure(key), f = S.meta.frames[i], s = o.h / S.meta.ref_h * (o.scale || 1), [hxp, hyp] = S.hat[i], fx = o.flip ? -1 : 1;
|
|
136
|
+
return [o.x + (hxp - f.ax) * s * fx, o.y + (hyp - f.ay) * s]; };
|
|
137
|
+
X.nFrames = key => X.SPR[key].meta.frames.length;
|
|
138
|
+
|
|
139
|
+
// ================= 莫奈:睡莲池+日本桥 =================
|
|
140
|
+
// o: { key, x0, w, waterTop, bridge:{x0,x1,yEnd,lift}, pond:[px0,px1]|null, groundY, willows:[x...] }
|
|
141
|
+
const SW = {
|
|
142
|
+
water: P.swatch(['#5b6fb5', '#7d8fd0', '#9db5e0', '#6aa0a8', '#b7a6d8', '#d9c8e8', '#8fb8a8', '#a6c4e8', '#c8b0d8'], 0.45),
|
|
143
|
+
deep: P.swatch(['#2e5048', '#3f6a58', '#4a5f8a', '#355a4a', '#5a6a9a', '#2a4a5a', '#6a8a5a'], 0.45),
|
|
144
|
+
fol: P.swatch(['#4f7f3a', '#6f9a40', '#2f5a30', '#9cbf58', '#c0d070', '#3a6a5a', '#7aa86a', '#5a8a9a', '#d8d890', '#4a5a8a'], 0.6),
|
|
145
|
+
grass: P.swatch(['#5f8f4a', '#7fae5a', '#3f6f4a', '#9cc070', '#b4cc78', '#6a9a8a'], 0.35),
|
|
146
|
+
path: P.swatch(['#d8b890', '#c89a78', '#e8c8a0', '#b88a8a', '#a89ab8', '#e0c0a8'], 0.35),
|
|
147
|
+
};
|
|
148
|
+
X.monetWorld = (o) => {
|
|
149
|
+
const key = 'mw_' + o.key; if (X[key]) return X[key];
|
|
150
|
+
const w = o.w, x0 = o.x0, B = o.bridge, mid = (B.x0 + B.x1) / 2, half = (B.x1 - B.x0) / 2;
|
|
151
|
+
const deckY = x => { const q = clamp((x - mid) / half, -1, 1); return B.yEnd - B.lift * (1 - q * q); };
|
|
152
|
+
const inPond = wx => !o.pond || (wx > o.pond[0] && wx < o.pond[1]);
|
|
153
|
+
const pondEdge = (wx, y) => { if (!o.pond) return true; const sl = (y - o.groundY) * 0.25; return wx > o.pond[0] - sl && wx < o.pond[1] + sl; };
|
|
154
|
+
const WT = o.waterTop, G = o.groundY;
|
|
155
|
+
const isWater = (wx, y) => y > WT && (y < G - 10 ? inPond(wx) || true : pondEdge(wx, y)) && !(y < G + 30 && !inPond(wx) && y > G - 40);
|
|
156
|
+
// ---- 底稿 ----
|
|
157
|
+
const base = P.canvas(w, H), g = base.getContext('2d');
|
|
158
|
+
g.fillStyle = '#2f4a36'; g.fillRect(0, 0, w, H);
|
|
159
|
+
const r = rng(301);
|
|
160
|
+
// 树荫:低频噪声分出深浅大块(深蓝绿的柳荫 / 中绿 / 黄绿受光),再撒垂柳条
|
|
161
|
+
for (let i = 0; i < w * 0.12; i++) { const x = r() * w, y = r() * (G + 20), n = P.fbm((x + x0) * 0.0035, y * 0.005);
|
|
162
|
+
const pal = n < -0.12 ? ['#1f3a2e', '#2a4a4a', '#34507a', '#2e5a40'] : n < 0.12 ? ['#3f6a3a', '#5a8a3e', '#4a7a6a', '#6a8a9a', '#5f8a4a'] : ['#9cc060', '#b8cc68', '#d8d890', '#c0d070', '#e8e0a0'];
|
|
163
|
+
g.fillStyle = pick(pal, r); g.globalAlpha = 0.85; g.beginPath(); g.ellipse(x, y, 30 + r() * 80, 24 + r() * 50, r() * 3, 0, TAU); g.fill(); }
|
|
164
|
+
for (let i = 0; i < w * 0.01; i++) { const x = r() * w, y = r() * 200; g.fillStyle = pick(['#cfd8e8', '#e6e0f0', '#d8e8d0', '#f0e8d0'], r); g.globalAlpha = 0.75; g.beginPath(); g.ellipse(x, y, 16 + r() * 34, 12 + r() * 18, 0, 0, TAU); g.fill(); }
|
|
165
|
+
g.globalAlpha = 1;
|
|
166
|
+
for (let i = 0; i < w * 0.04; i++) { const x = r() * w, L = 180 + r() * 360, n = P.fbm((x + x0) * 0.0035, 0.5); g.strokeStyle = pick(n < 0 ? ['#3a6a4a', '#4a7a3a', '#2e5a48'] : ['#8ab050', '#a8c868', '#c8d878'], r); g.lineWidth = 5 + r() * 8; g.beginPath(); g.moveTo(x, -10); g.quadraticCurveTo(x + (r() - 0.5) * 30, L / 2, x + (r() - 0.5) * 40, L); g.stroke(); }
|
|
167
|
+
// 水面
|
|
168
|
+
const wg = g.createLinearGradient(0, WT, 0, H); wg.addColorStop(0, '#34503f'); wg.addColorStop(0.18, '#456a7a'); wg.addColorStop(0.5, '#7590c0'); wg.addColorStop(1, '#a9b6dc');
|
|
169
|
+
g.save(); g.beginPath();
|
|
170
|
+
if (o.pond) { g.moveTo(o.pond[0] - x0, WT); g.lineTo(o.pond[1] - x0, WT); g.lineTo(o.pond[1] + (H - G) * 0.25 - x0, H); g.lineTo(o.pond[0] - (H - G) * 0.25 - x0, H); g.closePath(); }
|
|
171
|
+
else g.rect(0, WT, w, H - WT);
|
|
172
|
+
g.fillStyle = wg; g.fill(); g.clip();
|
|
173
|
+
for (let i = 0; i < w * 0.03; i++) { const x = r() * w, y = WT + r() * 260, L = 60 + r() * 200; g.fillStyle = pick(['#2e5048', '#3f6a58', '#4a6a3a', '#5a7a4a'], r); g.globalAlpha = 0.6; g.fillRect(x, y, 10 + r() * 20, L); }
|
|
174
|
+
for (let i = 0; i < w * 0.012; i++) { const x = r() * w, y = WT + 250 + r() * (H - WT - 250); g.fillStyle = pick(['#e8d6e8', '#f2e2c4', '#d0dcf0'], r); g.globalAlpha = 0.55; g.beginPath(); g.ellipse(x, y, 60 + r() * 120, 12 + r() * 16, 0, 0, TAU); g.fill(); }
|
|
175
|
+
g.restore(); g.globalAlpha = 1;
|
|
176
|
+
// 岸(有池边界时):小路+草+鸢尾
|
|
177
|
+
if (o.pond) {
|
|
178
|
+
g.save(); g.beginPath(); g.rect(0, G - 40, w, H); g.moveTo(o.pond[0] - x0, G - 40); g.lineTo(o.pond[1] - x0, G - 40); g.lineTo(o.pond[1] + (H - G + 40) * 0.25 - x0, H); g.lineTo(o.pond[0] - (H - G + 40) * 0.25 - x0, H); g.closePath(); g.clip('evenodd');
|
|
179
|
+
g.fillStyle = '#5f8f4a'; g.fillRect(0, G - 40, w, H);
|
|
180
|
+
g.fillStyle = '#d8b890'; g.fillRect(0, G - 8, w, 46);
|
|
181
|
+
for (let i = 0; i < w * 0.03; i++) { const x = r() * w, y = G + 60 + r() * (H - G - 60); g.strokeStyle = '#4f7f3a'; g.lineWidth = 5; for (let k = 0; k < 5; k++) { g.beginPath(); g.moveTo(x, y); g.lineTo(x + (k - 2) * 9 + (r() - 0.5) * 8, y - 50 - r() * 40); g.stroke(); } g.fillStyle = pick(['#7a5ab8', '#9a7ad0', '#f0e080', '#e890b0'], r); g.beginPath(); g.ellipse(x + (r() - 0.5) * 20, y - 70 - r() * 20, 12, 15, 0, 0, TAU); g.fill(); }
|
|
182
|
+
g.restore();
|
|
183
|
+
}
|
|
184
|
+
// ---- 笔触 ----
|
|
185
|
+
const bg = P.canvas(w, H), bgc = bg.getContext('2d'); bgc.drawImage(base, 0, 0);
|
|
186
|
+
const regionOf = (lx, y) => { const wx = lx + x0;
|
|
187
|
+
if (y > WT) { if (!o.pond) return 'water'; const sl = (y - G + 40) * 0.25; if (y > G - 40 && (wx < o.pond[0] - Math.max(0, sl) || wx > o.pond[1] + Math.max(0, sl))) return (y < G + 38 && y > G - 8) ? 'path' : 'grass'; return 'water'; }
|
|
188
|
+
if (o.pond && !inPond(wx) && y > G - 40) return (y > G - 8) ? 'path' : 'grass';
|
|
189
|
+
return 'fol'; };
|
|
190
|
+
X.strokes(bgc, base, { cell: 9, len: 30, width: 7, seed: 21, jitterCol: 12,
|
|
191
|
+
angle: (lx, y, rr) => { const reg = regionOf(lx, y); if (reg === 'water') return 0.05 * Math.sin(y * 0.05 + lx * 0.004) + (rr() - 0.5) * 0.08; if (reg === 'path') return (rr() - 0.5) * 0.2; if (reg === 'grass') return -1.35 + (rr() - 0.5) * 0.6; return (y < WT - 120 ? -1.5 + 0.35 * P.noise((lx + x0) * 0.01, y * 0.01) : -0.9 + 0.8 * P.noise((lx + x0) * 0.01, y * 0.01)) + (rr() - 0.5) * 0.4; },
|
|
192
|
+
palette: (lx, y, col, rr) => { const reg = regionOf(lx, y); if (reg === 'water') return y < WT + 120 + rr() * 200 ? SW.deep(col, rr) : SW.water(col, rr); return SW[reg](col, rr); } });
|
|
193
|
+
// ---- 桥(远侧栏杆+桥面),近侧栏杆单独一层(画在人前面) ----
|
|
194
|
+
const mkBridge = (near) => { const c = P.canvas(w, H), q = c.getContext('2d'); q.lineCap = 'round'; q.lineJoin = 'round';
|
|
195
|
+
const line = (dy, lw, col) => { q.strokeStyle = col; q.lineWidth = lw; q.beginPath(); for (let x = B.x0; x <= B.x1; x += 6) { const y = deckY(x) + dy; x === B.x0 ? q.moveTo(x - x0, y) : q.lineTo(x - x0, y); } q.stroke(); };
|
|
196
|
+
if (!near) {
|
|
197
|
+
// 桥身(拱下的厚度)+桥面
|
|
198
|
+
q.fillStyle = '#2f6a4a'; q.beginPath(); for (let x = B.x0; x <= B.x1; x += 6) q.lineTo(x - x0, deckY(x) - 4); for (let x = B.x1; x >= B.x0; x -= 6) q.lineTo(x - x0, deckY(x) + 30); q.closePath(); q.fill();
|
|
199
|
+
line(-2, 10, '#8fdcae');
|
|
200
|
+
line(-16 - 96, 8, '#4f9a72'); line(-16 - 52, 6, '#5fae82');
|
|
201
|
+
q.strokeStyle = '#4f9a72'; q.lineWidth = 6; for (let x = B.x0 + 20; x < B.x1; x += 58) { q.beginPath(); q.moveTo(x - x0, deckY(x) - 10); q.lineTo(x - x0, deckY(x) - 16 - 96); q.stroke(); }
|
|
202
|
+
// 桥墩
|
|
203
|
+
[B.x0 + 30, B.x1 - 30].forEach(x => { q.fillStyle = '#2a5a3e'; q.fillRect(x - x0 - 10, deckY(x) + 20, 20, 120); });
|
|
204
|
+
} else {
|
|
205
|
+
line(-80, 9, '#7fcf9a'); line(-38, 6, '#6fbf8a');
|
|
206
|
+
q.strokeStyle = '#6fbf8a'; q.lineWidth = 6; for (let x = B.x0 + 44; x < B.x1; x += 64) { q.beginPath(); q.moveTo(x - x0, deckY(x) + 22); q.lineTo(x - x0, deckY(x) - 80); q.stroke(); }
|
|
207
|
+
}
|
|
208
|
+
const out = P.canvas(w, H), og = out.getContext('2d'); og.drawImage(c, 0, 0);
|
|
209
|
+
og.globalCompositeOperation = 'source-atop';
|
|
210
|
+
X.strokes(og, c, { cell: 5, len: 11, width: 4, seed: near ? 31 : 32, alphaMask: true, alphaMin: 120, angle: (x, y, rr) => 0.1 + (rr() - 0.5) * 0.5,
|
|
211
|
+
palette: (x, y, col, rr) => P.mix(hx(pick(['#8fdcae', '#a8e8c0', '#6fc49a', '#c0f0d0', '#4f9a8a', '#7fb8d8', '#3f7a5a'], rr)), col, 0.35) });
|
|
212
|
+
return out; };
|
|
213
|
+
const bridge = mkBridge(false), nearRail = mkBridge(true);
|
|
214
|
+
// ---- 睡莲(每片预画成小图,每帧按涟漪起伏) ----
|
|
215
|
+
const pads = [], rp = rng(505);
|
|
216
|
+
const sc = y => 0.22 + 0.78 * clamp((y - WT) / (H - WT));
|
|
217
|
+
for (let y = WT + 26; y < H + 20;) {
|
|
218
|
+
const s = sc(y); let x = x0 + (rp() - 0.5) * 200;
|
|
219
|
+
while (x < x0 + w + 100) {
|
|
220
|
+
const clusterN = 2 + ((rp() * 5) | 0);
|
|
221
|
+
for (let k = 0; k < clusterN; k++) {
|
|
222
|
+
const wx = x + (rp() - 0.3) * 60 * s, wy = y + (rp() - 0.5) * 14 * s;
|
|
223
|
+
if (!pondEdge(wx, wy) || (o.pond && wy > G - 30 && !inPond(wx))) continue;
|
|
224
|
+
const rx = (22 + rp() * 26) * s * 1.7, ry = rx * (0.24 + 0.1 * s), flower = rp() < 0.3 ? (rp() < 0.6 ? 'pink' : 'white') : null;
|
|
225
|
+
pads.push({ x: wx, y: wy, rx, ry, flower, notch: rp() * TAU, ph: rp() * TAU, img: null, s });
|
|
226
|
+
x += rx * 1.6;
|
|
227
|
+
}
|
|
228
|
+
x += (60 + rp() * 260) * s * 1.6;
|
|
229
|
+
}
|
|
230
|
+
y += 18 + 44 * s;
|
|
231
|
+
}
|
|
232
|
+
pads.forEach((p, i) => { const pw = Math.ceil(p.rx * 2 + 16), ph = Math.ceil(p.ry * 2 + 34 * p.s + 16); const cc = P.canvas(pw, ph), q = cc.getContext('2d'), cx = pw / 2, cy = ph - p.ry - 8;
|
|
233
|
+
q.fillStyle = pick(['#5f8f4a', '#7aa85a', '#6a9a4a', '#8ab860'], rng(i + 9)); q.beginPath(); q.ellipse(cx, cy, p.rx, p.ry, 0, p.notch + 0.35, p.notch + TAU - 0.35); q.lineTo(cx, cy); q.closePath(); q.fill();
|
|
234
|
+
q.fillStyle = 'rgba(40,70,60,0.35)'; q.beginPath(); q.ellipse(cx, cy + p.ry * 0.25, p.rx * 0.95, p.ry * 0.7, 0, 0, Math.PI); q.fill();
|
|
235
|
+
if (p.flower) { const fr = 11 * p.s * 1.6 + 3; q.fillStyle = p.flower === 'pink' ? '#f2a6bf' : '#fbf4f0'; q.beginPath(); q.ellipse(cx + p.rx * 0.15, cy - fr * 0.5, fr * 1.4, fr * 0.8, 0, 0, TAU); q.fill(); q.fillStyle = p.flower === 'pink' ? '#fff0f2' : '#f6d86a'; q.beginPath(); q.ellipse(cx + p.rx * 0.15, cy - fr * 0.8, fr * 0.6, fr * 0.45, 0, 0, TAU); q.fill(); }
|
|
236
|
+
const out = P.canvas(pw, ph), og = out.getContext('2d'); og.drawImage(cc, 0, 0); og.globalCompositeOperation = 'source-atop';
|
|
237
|
+
X.strokes(og, cc, { cell: Math.max(3, 5 * p.s + 1), len: 10 * p.s + 5, width: 3 * p.s + 1.6, seed: 600 + i, alphaMask: true, alphaMin: 100, angle: (x, y, rr) => (rr() - 0.5) * 0.4,
|
|
238
|
+
palette: (x, y, col, rr) => col[1] > col[0] + 10 ? P.mix(hx(pick(['#5f8f4a', '#7fae5a', '#9cc070', '#b4cc78', '#4a7a5a', '#c8d880'], rr)), col, 0.3) : (lum(col) > 170 ? P.mix(hx(pick(['#fff2f4', '#f7d0dc', '#fbf4e8', '#f0a0b8'], rr)), col, 0.4) : null) });
|
|
239
|
+
p.img = out; p.ax = cx; p.ay = cy; });
|
|
240
|
+
// 水面光斑(世界坐标)
|
|
241
|
+
const glints = []; const rg = rng(909);
|
|
242
|
+
for (let i = 0; i < w * 0.16; i++) { const wx = x0 + rg() * w, y = WT + 20 + rg() * (H - WT - 20); if (!pondEdge(wx, y)) continue; glints.push({ x: wx, y, f: 5 + rg() * 6, ph: rg() * TAU, l: (14 + rg() * 26) * (0.4 + sc(y)), col: pick(['#fffbe8', '#fdf0d0', '#f8e0ec', '#e8f4ff'], rg), w: (3 + rg() * 3) * (0.5 + sc(y) * 0.7) }); }
|
|
243
|
+
const M = X[key] = { o, base, bg, bridge, nearRail, pads, glints, deckY, sc, x0, w, WT, G, pondEdge, inPond };
|
|
244
|
+
return M;
|
|
245
|
+
};
|
|
246
|
+
// 每帧:底 → 睡莲(按涟漪起伏)→ 光斑 → 涟漪 → 桥(远栏杆) ;人画完后再 X.monetFront(近栏杆+前景柳条)
|
|
247
|
+
X.monetBack = (c, M, camX, t, ev = {}) => {
|
|
248
|
+
c.drawImage(M.bg, M.x0 - camX, 0);
|
|
249
|
+
const hops = ev.hops || [];
|
|
250
|
+
// 睡莲
|
|
251
|
+
for (const p of M.pads) { const sx = p.x - camX; if (sx < -200 || sx > W + 200) continue;
|
|
252
|
+
let dy = Math.sin(t * 1.1 + p.ph) * 1.5 * p.s, rot = 0;
|
|
253
|
+
for (const h of hops) { const a = t - h.t; if (a < 0 || a > 2.2) continue; const d = Math.hypot(p.x - h.x, (p.y - h.y) * 3.2), R = 40 + a * 260 * h.s; const near = Math.exp(-Math.pow((d - R) / (60 * h.s + 20), 2)) * Math.exp(-a * 1.6);
|
|
254
|
+
dy += Math.sin(a * 16) * 7 * h.s * near; rot += Math.sin(a * 13 + 1) * 0.06 * near; }
|
|
255
|
+
if (ev.padBump && ev.padBump.pad === p) { const a = t - ev.padBump.t; if (a > 0 && a < 1.2) dy += Math.sin(a * 18) * 16 * Math.exp(-a * 3) * p.s + (a < 0.12 ? 10 * p.s : 0); }
|
|
256
|
+
c.save(); c.translate(sx, p.y + dy); c.rotate(rot); c.drawImage(p.img, -p.ax, -p.ay); c.restore(); }
|
|
257
|
+
// 光斑颤动(位置固定,亮度不规则明灭,10fps 横向小跳)
|
|
258
|
+
const st = Math.floor(t * 10); c.save(); c.lineCap = 'round';
|
|
259
|
+
for (let i = 0; i < M.glints.length; i++) { const gl = M.glints[i], sx = gl.x - camX; if (sx < -60 || sx > W + 60) continue;
|
|
260
|
+
const a = clamp(Math.sin(t * gl.f + gl.ph) * Math.sin(t * 2.3 + i), 0, 1); if (a < 0.12) continue;
|
|
261
|
+
const jx = (U.hash(i, st) - 0.5) * 8; c.strokeStyle = gl.col; c.globalAlpha = 0.85 * a; c.lineWidth = gl.w; c.beginPath(); c.moveTo(sx + jx, gl.y); c.lineTo(sx + jx + gl.l, gl.y + 1); c.stroke(); }
|
|
262
|
+
c.restore();
|
|
263
|
+
// 涟漪:断续的亮色短笔围成透视椭圆,一圈圈外扩
|
|
264
|
+
for (const h of hops) X.ripple(c, h.x - camX, h.y, t - h.t, h.s, h.big);
|
|
265
|
+
c.drawImage(M.bridge, M.x0 - camX, 0);
|
|
266
|
+
};
|
|
267
|
+
X.ripple = (c, x, y, a, s, big) => {
|
|
268
|
+
if (a < 0 || a > 2.0) return;
|
|
269
|
+
c.save(); c.lineCap = 'round';
|
|
270
|
+
for (let k = 0; k < (big ? 4 : 3); k++) { const ak = a - k * 0.16; if (ak <= 0) continue; const R = (22 + ak * (big ? 380 : 300)) * s, al = clamp(1 - ak / 1.6) * (1 - k * 0.15); if (al <= 0) continue;
|
|
271
|
+
const n = Math.max(10, Math.round(R / 7)); for (let i = 0; i < n; i++) { if (U.hash(i, k * 17 + 3) < 0.3) continue; const th = i / n * TAU + k * 0.4, th2 = th + TAU / n * 0.55;
|
|
272
|
+
c.strokeStyle = ['#ffffff', '#fffbe8', '#eef0ff'][(i + k) % 3]; c.globalAlpha = al; c.lineWidth = (9 - k * 1.4) * (0.5 + s * 0.6);
|
|
273
|
+
c.beginPath(); c.ellipse(x, y, R, R * 0.27, 0, th, th2); c.stroke(); } }
|
|
274
|
+
// 溅起的水花(短竖笔向上甩)
|
|
275
|
+
if (a < 0.1) { c.globalAlpha = 1 - a / 0.1; c.fillStyle = '#ffffff'; c.beginPath(); c.ellipse(x, y, 40 * s, 12 * s, 0, 0, TAU); c.fill(); }
|
|
276
|
+
if (a < 0.4) { const r = rng(Math.round(x * 7 + y)); for (let i = 0; i < (big ? 22 : 14); i++) { const ang = -Math.PI / 2 + (r() - 0.5) * 1.6, sp = (90 + r() * 150) * s * (big ? 1.6 : 1), q = a / 0.4;
|
|
277
|
+
const px = x + Math.cos(ang) * sp * a * 2.2, py = y + Math.sin(ang) * sp * a * 2.2 + 300 * a * a * s; c.globalAlpha = 1 - q; c.strokeStyle = i % 2 ? '#ffffff' : '#dfe8ff'; c.lineWidth = 6 * (0.5 + s * 0.6); c.beginPath(); c.moveTo(px, py); c.lineTo(px - Math.cos(ang) * 8 * s, py - Math.sin(ang) * 8 * s); c.stroke(); } }
|
|
278
|
+
c.restore();
|
|
279
|
+
};
|
|
280
|
+
X.monetFront = (c, M, camX, t, willows = []) => {
|
|
281
|
+
c.drawImage(M.nearRail, M.x0 - camX, 0);
|
|
282
|
+
// 前景柳条:从画面顶部垂下,成片摆动
|
|
283
|
+
c.save(); c.lineCap = 'round';
|
|
284
|
+
willows.forEach((wx, k) => { const sx = wx - camX; if (sx < -200 || sx > W + 200) return; const r = rng(80 + k);
|
|
285
|
+
for (let f = 0; f < 9; f++) { const fx = sx + (r() - 0.5) * 150, L = 260 + r() * 300, ph = r() * TAU;
|
|
286
|
+
for (let i = 0; i < 26; i++) { const q = i / 25, sw = Math.sin(t * 1.6 + ph + q * 2) * 26 * q * q, px = fx + sw + Math.sin(q * 5 + f) * 6, py = -10 + L * q;
|
|
287
|
+
c.strokeStyle = pick(['#6f9a40', '#9cbf58', '#4f7f3a', '#c0d070', '#3a6a5a'], r); c.lineWidth = 5; c.globalAlpha = 0.9; c.beginPath(); c.moveTo(px, py); c.lineTo(px + 4 + sw * 0.05, py + 14); c.stroke(); } } });
|
|
288
|
+
c.restore();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// ================= 打水漂 =================
|
|
292
|
+
// plan: { t0, from:[x,y], hops:[{x,y,t,s}], sinkT, sinkS }
|
|
293
|
+
X.stone = (t, plan) => {
|
|
294
|
+
if (t < plan.t0) return null;
|
|
295
|
+
const pts = [{ x: plan.from[0], y: plan.from[1], t: plan.t0, s: plan.s0 || 0.5 }].concat(plan.hops);
|
|
296
|
+
for (let i = 1; i < pts.length; i++) { const A = pts[i - 1], B = pts[i]; if (t <= B.t) { const q = (t - A.t) / (B.t - A.t);
|
|
297
|
+
const pk = i === 1 ? (plan.firstArc || 80) : (B.peak ?? 60) * (B.s ?? 1);
|
|
298
|
+
return { x: lerp(A.x, B.x, q), y: lerp(A.y, B.y, q) - 4 * pk * q * (1 - q), s: lerp(A.s, B.s, q), spin: t * 30 }; } }
|
|
299
|
+
return null;
|
|
300
|
+
};
|
|
301
|
+
X.drawStone = (c, st, trail) => { if (!st) return; const s = 1.2 + st.s * 1.4;
|
|
302
|
+
if (trail) { c.save(); c.lineCap = 'round'; for (let k = 1; k <= 6; k++) { const p = trail(k * 0.018); if (!p) break; c.strokeStyle = '#fffbea'; c.globalAlpha = 0.5 * (1 - k / 7); c.lineWidth = 9 * s * (1 - k / 8); c.beginPath(); c.moveTo(p.x, p.y); const q = trail((k - 1) * 0.018) || st; c.lineTo(q.x, q.y); c.stroke(); } c.restore(); }
|
|
303
|
+
c.save(); c.translate(st.x, st.y);
|
|
304
|
+
c.fillStyle = 'rgba(40,40,80,.3)'; c.beginPath(); c.ellipse(2, 4, 13 * s, 6 * s, 0, 0, TAU); c.fill();
|
|
305
|
+
c.fillStyle = '#4a4a6a'; c.beginPath(); c.ellipse(0, 0, 11 * s, 11 * s * Math.abs(Math.cos(st.spin)) * 0.5 + 3 * s, 0, 0, TAU); c.fill();
|
|
306
|
+
c.fillStyle = '#d8d8f0'; c.beginPath(); c.ellipse(-2 * s, -1.5 * s, 4.5 * s, 2 * s, 0, 0, TAU); c.fill(); c.restore(); };
|
|
307
|
+
// 带拖尾地画石子:x 方向可传相机偏移
|
|
308
|
+
X.stoneAt = (c, lt, plan, camX = 0) => { const sh = p => p ? { ...p, x: p.x - camX } : null; X.drawStone(c, sh(X.stone(lt, plan)), dt => sh(X.stone(lt - dt, plan))); };
|
|
309
|
+
|
|
310
|
+
// ================= 青蛙(几何小动物,代码画;按画风上材质) =================
|
|
311
|
+
const FROG = {};
|
|
312
|
+
function frogImg(pose, style) {
|
|
313
|
+
const k = pose + style; if (FROG[k]) return FROG[k];
|
|
314
|
+
const cw = 200, ch = 160, cc = P.canvas(cw, ch), q = cc.getContext('2d'); q.translate(100, 110); q.lineJoin = 'round';
|
|
315
|
+
const body = style === 'vangogh' ? '#3f8a3a' : '#6aa84a', dark = style === 'vangogh' ? '#1f4a2a' : '#3f6a3a', belly = '#d8e8a0';
|
|
316
|
+
if (pose === 'leap') {
|
|
317
|
+
q.fillStyle = dark; q.beginPath(); q.ellipse(-62, 20, 38, 9, 0.3, 0, TAU); q.fill(); q.beginPath(); q.ellipse(-60, 34, 36, 8, 0.15, 0, TAU); q.fill();
|
|
318
|
+
q.fillStyle = body; q.beginPath(); q.ellipse(0, 0, 50, 26, -0.15, 0, TAU); q.fill();
|
|
319
|
+
q.fillStyle = belly; q.beginPath(); q.ellipse(4, 10, 32, 12, -0.15, 0, TAU); q.fill();
|
|
320
|
+
q.fillStyle = dark; q.beginPath(); q.ellipse(46, 18, 20, 7, 0.6, 0, TAU); q.fill();
|
|
321
|
+
[[28, -22], [8, -26]].forEach(([x, y]) => { q.fillStyle = body; q.beginPath(); q.arc(x, y, 13, 0, TAU); q.fill(); q.fillStyle = '#fbf6e0'; q.beginPath(); q.arc(x + 2, y - 1, 8, 0, TAU); q.fill(); q.fillStyle = '#1a1a2a'; q.beginPath(); q.arc(x + 4, y - 1, 4.5, 0, TAU); q.fill(); });
|
|
322
|
+
} else {
|
|
323
|
+
q.fillStyle = dark; q.beginPath(); q.ellipse(-30, 18, 34, 18, -0.2, 0, TAU); q.fill(); q.beginPath(); q.ellipse(30, 18, 34, 18, 0.2, 0, TAU); q.fill();
|
|
324
|
+
q.fillStyle = body; q.beginPath(); q.ellipse(0, -2, 46, 34, 0, 0, TAU); q.fill();
|
|
325
|
+
q.fillStyle = belly; q.beginPath(); q.ellipse(0, 12, 28, 16, 0, 0, TAU); q.fill();
|
|
326
|
+
q.fillStyle = dark; [[-18, 30], [18, 30]].forEach(([x, y]) => { q.beginPath(); q.ellipse(x, y, 10, 6, 0, 0, TAU); q.fill(); });
|
|
327
|
+
[[-20, -30], [20, -30]].forEach(([x, y]) => { q.fillStyle = body; q.beginPath(); q.arc(x, y, 15, 0, TAU); q.fill(); q.fillStyle = '#fbf6e0'; q.beginPath(); q.arc(x, y - 2, 9.5, 0, TAU); q.fill(); q.fillStyle = '#1a1a2a'; q.beginPath(); q.arc(x, y - 1, 5, 0, TAU); q.fill(); });
|
|
328
|
+
q.strokeStyle = '#2a3a2a'; q.lineWidth = 3; q.lineCap = 'round'; q.beginPath(); q.arc(0, -4, 16, 0.25, Math.PI - 0.25); q.stroke();
|
|
329
|
+
}
|
|
330
|
+
const out = P.canvas(cw, ch), og = out.getContext('2d'); og.drawImage(cc, 0, 0); og.globalCompositeOperation = 'source-atop';
|
|
331
|
+
const M = MAT[style] || MAT.monet;
|
|
332
|
+
X.strokes(og, cc, { cell: 5, len: style === 'vangogh' ? 12 : 9, width: 3.6, seed: 71, alphaMask: true, alphaMin: 120, outline: style === 'vangogh' ? 0.4 : 0, outlineCol: [20, 30, 60],
|
|
333
|
+
angle: (x, y, r) => (style === 'vangogh' ? Math.atan2(y - 110, x - 100) + Math.PI / 2 : -0.4) + (r() - 0.5) * 0.5, skip: (col) => lum(col) < 60 || lum(col) > 235,
|
|
334
|
+
palette: (x, y, col, r) => col[1] > col[0] + 20 ? P.mix(hx(pick(style === 'vangogh' ? ['#3f8a3a', '#6aaa40', '#2a6a4a', '#c8d040'] : ['#6aa84a', '#8ac060', '#4f8a4a', '#a8d070', '#5a9a8a'], r)), col, 0.35) : null });
|
|
335
|
+
out.ax = 100; out.ay = 128; FROG[k] = out; return out;
|
|
336
|
+
}
|
|
337
|
+
// x,y = 脚底;s = 缩放(1 ≈ 100px 宽);o.rot 旋转,o.croak 0..1 鸣囊鼓起
|
|
338
|
+
X.frog = (c, x, y, s, pose, style, o = {}) => {
|
|
339
|
+
const im = frogImg(pose, style); c.save(); c.translate(x, y); c.rotate(o.rot || 0); c.scale(s * (o.flip ? -1 : 1), s * (o.sy || 1)); c.drawImage(im, -im.ax, -im.ay);
|
|
340
|
+
if (o.croak > 0) { c.fillStyle = 'rgba(240,236,190,0.92)'; c.beginPath(); c.ellipse(0, -14 + 26, 14 + 22 * o.croak, 8 + 15 * o.croak, 0, 0, TAU); c.fill(); c.strokeStyle = 'rgba(120,130,80,.6)'; c.lineWidth = 2; c.stroke(); }
|
|
341
|
+
c.restore();
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// ================= 浮世绘:海岸、富士、巨浪 =================
|
|
345
|
+
const INK = '#1c1a1e';
|
|
346
|
+
const uF = (g, path, fill, lw = 2.6) => { if (fill) { g.save(); g.translate(3, 2); g.fillStyle = fill; g.fill(path); g.restore(); } if (lw) { g.strokeStyle = INK; g.lineWidth = lw; g.stroke(path); } };
|
|
347
|
+
const poly = pts => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); p.closePath(); return p; };
|
|
348
|
+
X.ukiyoeWorld = (o) => {
|
|
349
|
+
const key = 'uw_' + o.key; if (X[key]) return X[key];
|
|
350
|
+
const w = o.w, x0 = o.x0, G = o.groundY, SEA = o.sea || 560;
|
|
351
|
+
const c = P.canvas(w, H), g = c.getContext('2d');
|
|
352
|
+
const sg = g.createLinearGradient(0, 0, 0, SEA); sg.addColorStop(0, '#2c4a80'); sg.addColorStop(0.1, '#4a6a98'); sg.addColorStop(0.3, '#e8d8a0'); sg.addColorStop(0.75, '#f2dc96'); sg.addColorStop(1, '#f3c98a');
|
|
353
|
+
g.fillStyle = sg; g.fillRect(0, 0, w, SEA);
|
|
354
|
+
const r = rng(41); for (let i = 0; i < w * 0.3; i++) { const y = r() * SEA; g.strokeStyle = y < 90 ? `rgba(30,50,110,${0.06 + r() * 0.1})` : `rgba(140,90,40,${0.03 + r() * 0.05})`; g.lineWidth = 1 + r() * 2; g.beginPath(); g.moveTo(r() * w, y); g.lineTo(r() * w, y + (r() - 0.5) * 3); g.stroke(); }
|
|
355
|
+
// 富士
|
|
356
|
+
(o.fuji || []).forEach(fx => { const lx = fx - x0; uF(g, poly([[lx - 230, SEA], [lx - 30, SEA - 230], [lx + 30, SEA - 232], [lx + 240, SEA]]), '#26467e', 2.4);
|
|
357
|
+
uF(g, poly([[lx - 70, SEA - 190], [lx - 30, SEA - 230], [lx + 30, SEA - 232], [lx + 72, SEA - 192], [lx + 50, SEA - 196], [lx + 34, SEA - 176], [lx + 16, SEA - 196], [lx - 4, SEA - 172], [lx - 24, SEA - 196], [lx - 46, SEA - 180]]), '#fbf8ee', 1.8); });
|
|
358
|
+
// 远海
|
|
359
|
+
g.save(); g.translate(3, 2); g.fillStyle = '#18305e'; g.fillRect(0, SEA, w, G - 40 - SEA); g.restore();
|
|
360
|
+
g.strokeStyle = INK; g.lineWidth = 2.4; g.beginPath(); g.moveTo(0, SEA); g.lineTo(w, SEA); g.stroke();
|
|
361
|
+
// 岸
|
|
362
|
+
const shore = new Path2D(); shore.moveTo(0, G - 30); for (let x = 0; x <= w; x += 40) shore.lineTo(x, G - 30 + Math.sin(x * 0.013) * 8); shore.lineTo(w, H); shore.lineTo(0, H); shore.closePath();
|
|
363
|
+
uF(g, shore, '#e8c890', 3);
|
|
364
|
+
g.save(); g.clip(shore); for (let i = 0; i < w * 0.05; i++) { const x = r() * w, y = G + r() * (H - G); g.fillStyle = 'rgba(150,100,50,.25)'; g.beginPath(); g.ellipse(x, y, 3 + r() * 6, 1.5 + r() * 2, 0, 0, TAU); g.fill(); }
|
|
365
|
+
const pg = g.createLinearGradient(0, G + 120, 0, H); pg.addColorStop(0, 'rgba(160,120,70,0)'); pg.addColorStop(1, 'rgba(160,120,70,.35)'); g.fillStyle = pg; g.fillRect(0, G, w, H - G); g.restore();
|
|
366
|
+
g.strokeStyle = INK; g.lineWidth = 2; for (let x = 20; x < w; x += 140) { g.beginPath(); g.moveTo(x, G + 70 + (x % 3) * 30); g.quadraticCurveTo(x + 40, G + 64 + (x % 3) * 30, x + 80, G + 72 + (x % 3) * 30); g.stroke(); }
|
|
367
|
+
// 松
|
|
368
|
+
(o.pines || []).forEach(px => { const lx = px - x0; const trunk = poly([[lx - 14, G - 20], [lx - 30, G - 200], [lx + 10, G - 330], [lx + 60, G - 400], [lx + 40, G - 330], [lx + 12, G - 200], [lx + 14, G - 20]]); uF(g, trunk, '#8a4a2a', 2.6);
|
|
369
|
+
[[lx - 60, G - 260, 90], [lx + 80, G - 400, 110], [lx - 10, G - 360, 80], [lx + 120, G - 300, 70]].forEach(([x, y, rr]) => { const p = new Path2D(); p.ellipse(x, y, rr, rr * 0.32, -0.08, 0, TAU); uF(g, p, '#2f5a3a', 2.4); g.strokeStyle = 'rgba(20,40,30,.6)'; g.lineWidth = 1.4; for (let k = -rr + 10; k < rr - 10; k += 12) { g.beginPath(); g.moveTo(x + k, y - 6); g.lineTo(x + k + 6, y + 8); g.stroke(); } }); });
|
|
370
|
+
// 纸纹罩层
|
|
371
|
+
const ov = P.canvas(w, H), og = ov.getContext('2d'); const ro = rng(5);
|
|
372
|
+
for (let i = 0; i < w * 0.7; i++) { const y = ro() * H, x = ro() * w, L = 200 + ro() * 700; og.strokeStyle = `rgba(120,80,40,${0.025 + ro() * 0.04})`; og.lineWidth = 1 + ro(); og.beginPath(); og.moveTo(x, y); og.lineTo(x + L, y + (ro() - 0.5) * 6); og.stroke(); }
|
|
373
|
+
for (let i = 0; i < w * 1.2; i++) { const x = ro() * w, y = ro() * H; og.strokeStyle = ro() < 0.5 ? 'rgba(255,250,235,.12)' : 'rgba(90,60,30,.07)'; og.lineWidth = 1; og.beginPath(); og.arc(x, y, 3 + ro() * 6, ro() * 6, ro() * 6 + 1); og.stroke(); }
|
|
374
|
+
return X[key] = { o, bg: c, over: ov, x0, w, G, SEA };
|
|
375
|
+
};
|
|
376
|
+
// 巨浪(每帧):浪身+内侧流线(虚线前移)+白沫带+浪爪屈伸+飞沫
|
|
377
|
+
X.greatWave = (c, bx, by, S, t) => {
|
|
378
|
+
c.save(); c.translate(bx, by); c.scale(S, S); c.lineCap = 'round'; c.lineJoin = 'round';
|
|
379
|
+
const k = Math.sin(t * 2.4) * 0.5 + 0.5; // 浪唇前卷程度
|
|
380
|
+
const back = K => KIT.densify(K, 6);
|
|
381
|
+
const O = back([[-420, 0], [-300, -90], [-190, -220], [-80, -350], [40, -430], [150, -432 + k * 8], [240, -380 + k * 14], [282 + k * 30, -300 + k * 34]]);
|
|
382
|
+
const tip = O[O.length - 1];
|
|
383
|
+
const I = back([tip, [220 + k * 12, -290 + k * 20], [150, -300], [100, -250], [96, -160], [130, -70], [220, 0]]);
|
|
384
|
+
const body = new Path2D(); O.forEach((p, i) => i ? body.lineTo(p[0], p[1]) : body.moveTo(p[0], p[1])); I.forEach(p => body.lineTo(p[0], p[1])); body.closePath();
|
|
385
|
+
uF(c, body, '#26467e', 3.2);
|
|
386
|
+
// 浪身流线:沿背面轮廓向内偏移,虚线往浪顶涌
|
|
387
|
+
c.save(); c.clip(body); c.setLineDash([30, 16]); c.lineDashOffset = -t * 140;
|
|
388
|
+
for (let m = 1; m < 9; m++) { c.strokeStyle = m % 2 ? 'rgba(150,182,226,.9)' : 'rgba(236,242,250,.8)'; c.lineWidth = 7; c.beginPath();
|
|
389
|
+
O.forEach((p, i) => { const q = i / (O.length - 1); const x = p[0] + m * 26 * (0.4 + q) , y = p[1] + m * 30 * (1 - q * 0.6); i ? c.lineTo(x, y) : c.moveTo(x, y); }); c.stroke(); }
|
|
390
|
+
c.restore(); c.setLineDash([]);
|
|
391
|
+
// 白沫带(沿浪背上段)
|
|
392
|
+
const top = O.slice(Math.floor(O.length * 0.35));
|
|
393
|
+
c.strokeStyle = INK; c.lineWidth = 40; c.beginPath(); top.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke();
|
|
394
|
+
c.strokeStyle = '#fbf7ec'; c.lineWidth = 35; c.stroke();
|
|
395
|
+
// 浪爪:沿浪唇外缘一簇簇胖手指,指向前下方并向内卷
|
|
396
|
+
const claw = (x, y, ang, L, kk) => { const lens = [1, 0.66, 0.56, 0.42], offs = [0, -0.6, 0.45, -0.25];
|
|
397
|
+
for (let f = 0; f < 4; f++) { let a = ang + offs[f], px = x, py = y; const LL = L * lens[f], pts = [[px, py]], cv = 1.1 + Math.sin(t * 5 + kk * 0.9) * 0.6;
|
|
398
|
+
for (let s2 = 0; s2 < 7; s2++) { a += cv * 0.34 * (0.5 + s2 / 7) / 2.4; px += Math.cos(a) * LL / 7; py += Math.sin(a) * LL / 7; pts.push([px, py]); }
|
|
399
|
+
[[LL * 0.32 + 4, INK], [LL * 0.32, '#fbf7ec'], [LL * 0.1, '#9ab0c8']].forEach(([lw, col]) => { c.strokeStyle = col; c.lineWidth = lw; c.beginPath(); pts.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); }); } };
|
|
400
|
+
const lipStart = Math.floor(O.length * 0.55);
|
|
401
|
+
for (let j = lipStart, kk = 0; j < O.length; j += 3, kk++) { const p = O[j], q = O[Math.max(0, j - 1)]; const nx = p[1] - q[1], ny = -(p[0] - q[0]); const a = Math.atan2(ny, nx) * -1; claw(p[0], p[1], Math.atan2(-(p[0] - q[0]), (p[1] - q[1])) + 0.6, 60 + (kk % 3) * 16, kk); }
|
|
402
|
+
for (let j = 0, kk = 20; j < 4; j++, kk++) { const p = I[1 + j]; claw(p[0], p[1], 1.3, 46, kk); }
|
|
403
|
+
// 飞沫(北斋的「雪」)
|
|
404
|
+
const r = rng(13); c.fillStyle = '#fbf7ec'; c.strokeStyle = INK; c.lineWidth = 1.6;
|
|
405
|
+
for (let i = 0; i < 40; i++) { const ph = (t * 0.7 + r()) % 1, x = tip[0] - 20 + r() * 150 + ph * 90, y = tip[1] - 30 - r() * 90 + ph * ph * 260, rr = 2.5 + r() * 4; c.globalAlpha = 1 - ph * 0.7; c.beginPath(); c.arc(x, y, rr, 0, TAU); c.fill(); c.stroke(); }
|
|
406
|
+
c.restore();
|
|
407
|
+
};
|
|
408
|
+
// 近海浪纹(每帧平移)
|
|
409
|
+
X.seaLines = (c, x0, y0, w, h, t) => { c.save(); c.beginPath(); c.rect(x0, y0, w, h); c.clip(); c.strokeStyle = 'rgba(230,236,245,.85)'; c.lineWidth = 3; const r = rng(3);
|
|
410
|
+
for (let k = 0; k < 9; k++) { const y = y0 + 12 + k * (h / 9), off = (t * 40 * (k % 2 ? 1 : -0.6)) % 120; for (let x = x0 - 120 + off + r() * 60; x < x0 + w; x += 120) { c.beginPath(); c.arc(x, y + 10, 22, Math.PI * 1.1, Math.PI * 1.9); c.stroke(); } } c.restore(); };
|
|
411
|
+
|
|
412
|
+
// ================= 梵高:星月夜 =================
|
|
413
|
+
X.vgWorld = (o) => {
|
|
414
|
+
const key = 'vg_' + o.key; if (X[key]) return X[key];
|
|
415
|
+
const w = o.w, x0 = o.x0, G = o.groundY, HOR = o.horizon || 600;
|
|
416
|
+
const stars = []; const rs = rng(17); for (let x = 120; x < w; x += 200 + rs() * 140) { const sx = x0 + x; if ((o.cypress || []).some(cx => Math.abs(cx - sx) < 160)) continue; stars.push({ x: sx, y: 70 + rs() * 360, r: 16 + rs() * 16 }); }
|
|
417
|
+
const swirls = (o.swirls || []).map(([x, y, R]) => ({ x, y, R }));
|
|
418
|
+
const moon = o.moon;
|
|
419
|
+
const angleSky = (wx, y) => { let a = 0.15 * Math.sin(wx * 0.004 + y * 0.01);
|
|
420
|
+
for (const s of swirls) { const dx = wx - s.x, dy = (y - s.y) * 1.6, d = Math.hypot(dx, dy); const f = Math.exp(-Math.pow(d / s.R, 2) * 0.8); a = lerp(a, Math.atan2(dy, dx) + Math.PI / 2, f); }
|
|
421
|
+
for (const s of stars.concat(moon ? [{ x: moon[0], y: moon[1], r: 60 }] : [])) { const dx = wx - s.x, dy = y - s.y, d = Math.hypot(dx, dy); if (d < s.r * 3.2) a = Math.atan2(dy, dx) + Math.PI / 2; }
|
|
422
|
+
return a; };
|
|
423
|
+
const base = P.canvas(w, H), g = base.getContext('2d');
|
|
424
|
+
const sg = g.createLinearGradient(0, 0, 0, HOR); sg.addColorStop(0, '#13246a'); sg.addColorStop(0.5, '#25479e'); sg.addColorStop(1, '#5a7ec0'); g.fillStyle = sg; g.fillRect(0, 0, w, HOR + 40);
|
|
425
|
+
swirls.forEach(s => { g.strokeStyle = '#9ab8e0'; g.lineWidth = 46; g.beginPath(); for (let a = 0; a < TAU * 1.4; a += 0.1) { const rr = s.R * (0.3 + a / (TAU * 1.4) * 0.7); g.lineTo(s.x - x0 + Math.cos(a) * rr, s.y + Math.sin(a) * rr / 1.6); } g.stroke(); });
|
|
426
|
+
stars.forEach(s => { const rg = g.createRadialGradient(s.x - x0, s.y, 0, s.x - x0, s.y, s.r * 2.6); rg.addColorStop(0, '#fff8c0'); rg.addColorStop(0.35, '#f0e070'); rg.addColorStop(0.6, '#c8d8a0'); rg.addColorStop(1, 'rgba(90,126,192,0)'); g.fillStyle = rg; g.beginPath(); g.arc(s.x - x0, s.y, s.r * 2.6, 0, TAU); g.fill(); });
|
|
427
|
+
if (moon) { const rg = g.createRadialGradient(moon[0] - x0, moon[1], 0, moon[0] - x0, moon[1], 150); rg.addColorStop(0, '#fff0a0'); rg.addColorStop(0.4, '#f0c840'); rg.addColorStop(0.7, '#e8d080'); rg.addColorStop(1, 'rgba(90,126,192,0)'); g.fillStyle = rg; g.beginPath(); g.arc(moon[0] - x0, moon[1], 150, 0, TAU); g.fill(); }
|
|
428
|
+
// 远山、村庄
|
|
429
|
+
g.fillStyle = '#2a3a78'; g.beginPath(); g.moveTo(0, HOR); for (let x = 0; x <= w; x += 30) g.lineTo(x, HOR - 70 - Math.sin(x * 0.004 + 1) * 50 - Math.sin(x * 0.011) * 20); g.lineTo(w, HOR + 60); g.lineTo(0, HOR + 60); g.fill();
|
|
430
|
+
const rv = rng(23); for (let x = 30; x < w; x += 46 + rv() * 30) { const hh = 26 + rv() * 30, y = HOR + 10 + rv() * 30; g.fillStyle = pick(['#3a4a6a', '#4a5a8a', '#5a6a5a'], rv); g.fillRect(x, y - hh, 40, hh); g.fillStyle = '#2a3058'; g.beginPath(); g.moveTo(x - 4, y - hh); g.lineTo(x + 20, y - hh - 18); g.lineTo(x + 44, y - hh); g.fill(); if (rv() < 0.6) { g.fillStyle = '#f8d850'; g.fillRect(x + 12, y - hh + 8, 10, 10); } }
|
|
431
|
+
g.fillStyle = '#3a5a3a'; g.fillRect(0, HOR + 50, w, G - HOR - 50);
|
|
432
|
+
const pg = g.createLinearGradient(0, G - 30, 0, H); pg.addColorStop(0, '#8a8a3a'); pg.addColorStop(0.3, '#c8a040'); pg.addColorStop(1, '#7a6a3a'); g.fillStyle = pg; g.fillRect(0, G - 30, w, H - G + 30);
|
|
433
|
+
(o.cypress || []).forEach(cx => { const lx = cx - x0; g.fillStyle = '#1e2e1e'; g.beginPath(); g.moveTo(lx - 70, G + 10); g.bezierCurveTo(lx - 90, G - 300, lx - 30, G - 500, lx + 6, G - 760); g.bezierCurveTo(lx + 40, G - 520, lx + 90, G - 300, lx + 70, G + 10); g.fill(); });
|
|
434
|
+
const out = P.canvas(w, H), oc = out.getContext('2d'); oc.drawImage(base, 0, 0);
|
|
435
|
+
const isCyp = (lx, y) => (o.cypress || []).some(cx => Math.abs(lx + x0 - cx) < 80 * (0.2 + (y - (G - 760)) / 760) && y > G - 760 && y < G + 10);
|
|
436
|
+
X.strokes(oc, base, { cell: 10, len: 28, width: 7, seed: 51, outline: 0.5, outlineCol: [16, 22, 70], jitterCol: 16,
|
|
437
|
+
angle: (lx, y, r) => isCyp(lx, y) ? -Math.PI / 2 + Math.sin(y * 0.03) * 0.5 : y < HOR + 20 ? angleSky(lx + x0, y) : y < G - 30 ? Math.sin(lx * 0.02) * 0.4 : 0.2 * Math.sin(lx * 0.01 + y * 0.02),
|
|
438
|
+
palette: (lx, y, col, r) => { const L = lum(col);
|
|
439
|
+
if (isCyp(lx, y)) return P.mix(hx(pick(['#1e3a24', '#2a4a2a', '#3a5a2a', '#14241a', '#4a6a3a'], r)), col, 0.3);
|
|
440
|
+
if (y < HOR + 20) { if (L > 180) return P.mix(hx(pick(['#fff8c0', '#f0e070', '#f8d850', '#e8f0d0'], r)), col, 0.4); if (L > 120) return P.mix(hx(pick(['#9ab8e0', '#c8d8f0', '#e8e8c0', '#6a8ad0'], r)), col, 0.4); return P.mix(hx(pick(['#1e3a8a', '#2a52a8', '#3a6ac0', '#5a86d0', '#1a2a6a', '#3a4aa0'], r)), col, 0.4); }
|
|
441
|
+
if (y > G - 30) return P.mix(hx(pick(['#c8a040', '#e0b850', '#8a7a3a', '#a89a4a', '#6a7a3a'], r)), col, 0.4);
|
|
442
|
+
return null; } });
|
|
443
|
+
return X[key] = { o, bg: out, x0, w, G, stars, moon, swirls, angleSky };
|
|
444
|
+
};
|
|
445
|
+
// 星空的动:星晕环旋转、漩涡带上的短笔沿螺线流动
|
|
446
|
+
X.vgSky = (c, V, camX, t) => {
|
|
447
|
+
c.save(); c.lineCap = 'round';
|
|
448
|
+
V.stars.forEach((s, k) => { const sx = s.x - camX; if (sx < -150 || sx > W + 150) return; const pul = 1 + 0.12 * Math.sin(t * 3 + k);
|
|
449
|
+
for (let ring = 0; ring < 2; ring++) { const R = s.r * (1.5 + ring * 0.8) * pul, n = 16 + ring * 6; for (let i = 0; i < n; i++) { const a = i / n * TAU + t * (0.8 - ring * 0.3) * (k % 2 ? 1 : -1);
|
|
450
|
+
c.strokeStyle = ring ? '#c8d8a0' : '#fff2a0'; c.lineWidth = 5; c.globalAlpha = 0.85; c.beginPath(); c.arc(sx, s.y, R, a, a + 0.22); c.stroke(); } } });
|
|
451
|
+
V.swirls.forEach((s, k) => { const sx = s.x - camX; if (sx < -s.R * 1.5 || sx > W + s.R * 1.5) return;
|
|
452
|
+
for (let i = 0; i < 60; i++) { const q = ((i / 60) + t * 0.12) % 1, a = q * TAU * 1.4, rr = s.R * (0.3 + q * 0.7), x = sx + Math.cos(a) * rr, y = s.y + Math.sin(a) * rr / 1.6, ta = a + Math.PI / 2;
|
|
453
|
+
c.strokeStyle = ['#e8f0ff', '#c8d8f0', '#fff8d0'][i % 3]; c.lineWidth = 6; c.globalAlpha = 0.8 * Math.sin(q * Math.PI); c.beginPath(); c.moveTo(x - Math.cos(ta) * 14, y - Math.sin(ta) * 9); c.lineTo(x + Math.cos(ta) * 14, y + Math.sin(ta) * 9); c.stroke(); } });
|
|
454
|
+
if (V.moon) { const sx = V.moon[0] - camX; c.globalAlpha = 1; c.fillStyle = '#ffe680'; c.beginPath(); c.arc(sx, V.moon[1], 52, 0, TAU); c.fill(); c.fillStyle = '#e8a830'; c.beginPath(); c.arc(sx + 22, V.moon[1] - 12, 46, 0, TAU); c.fill();
|
|
455
|
+
for (let i = 0; i < 24; i++) { const a = i / 24 * TAU - t * 0.5, R = 90 + 8 * Math.sin(t * 2 + i); c.strokeStyle = '#f8e070'; c.lineWidth = 6; c.globalAlpha = 0.75; c.beginPath(); c.arc(sx, V.moon[1], R, a, a + 0.18); c.stroke(); } }
|
|
456
|
+
c.restore();
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// ================= 画风颜料迸散(跳进/跳出画框、跨边界时用) =================
|
|
460
|
+
X.burst = (c, x, y, a, R, style, seed = 1) => { if (a < 0 || a > 1) return; const r = rng(seed), cols = style === 'monet' ? ['#f2a6bf', '#9db5e0', '#9cc070', '#fff2d4', '#b7a6d8', '#8fdcae'] : ['#f8d850', '#2a52a8', '#fff8c0', '#5a86d0'];
|
|
461
|
+
c.save(); c.lineCap = 'round'; for (let i = 0; i < 70; i++) { const ang = r() * TAU, sp = R * (0.4 + r() * 0.8), d = sp * U.ease.out(a), x1 = x + Math.cos(ang) * d, y1 = y + Math.sin(ang) * d * 0.8 + 120 * a * a;
|
|
462
|
+
c.strokeStyle = cols[i % cols.length]; c.globalAlpha = 1 - a; c.lineWidth = 7 + r() * 6; c.beginPath(); c.moveTo(x1, y1); c.lineTo(x1 + Math.cos(ang) * 16, y1 + Math.sin(ang) * 10); c.stroke(); } c.restore(); };
|
|
463
|
+
})();
|