@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,183 @@
|
|
|
1
|
+
// 1889 后印象派(梵高《阿尔的卧室》+《星月夜》)——纯代码。质量标杆:其他时代照这个结构写。
|
|
2
|
+
// 管线:①平涂底稿 → ②流场笔触重画(墙竖、地板沿透视、星空沿漩涡、灯晕沿圆周,8fps 沸腾)
|
|
3
|
+
// → ③角色平涂+细笔触 → ④角色轮廓与五官补线 → ⑤会动的高光(星星、灯晕、热气)
|
|
4
|
+
SCENES['09_postimp'] = (() => {
|
|
5
|
+
const W = 1920, H = 1080, { clamp, lerp } = U, P = PAINT;
|
|
6
|
+
const C = {
|
|
7
|
+
wall: '#5b7bd3', wall2: '#4462bd', sky: '#1d2c7c', sky2: '#3a5bc0', swirl: '#8fb3ec', star: '#f6d84a', halo: '#fbeea0',
|
|
8
|
+
frame: '#2f8f58', frameHi: '#6fd08a', floor: '#b5553b', floor2: '#8a3a2c', wood: '#e3ad38', wood2: '#b57d1c',
|
|
9
|
+
bed: '#d9472f', line: '#1c2458', cypress: '#1f4a2c', village: '#1a2160',
|
|
10
|
+
};
|
|
11
|
+
const VP = [960, 330]; // 地板透视消失点
|
|
12
|
+
const swirls = [[520, 300, 90], [660, 240, 60], [450, 420, 55], [700, 400, 45]];
|
|
13
|
+
const stars = [[410, 180, 16], [520, 170, 12], [610, 190, 14], [745, 200, 24], [560, 260, 10], [700, 300, 11], [470, 300, 9]];
|
|
14
|
+
|
|
15
|
+
function base(g, t) {
|
|
16
|
+
// 墙
|
|
17
|
+
const wg = g.createLinearGradient(0, 0, 0, 720); wg.addColorStop(0, C.wall); wg.addColorStop(1, C.wall2);
|
|
18
|
+
g.fillStyle = wg; g.fillRect(0, 0, W, 720);
|
|
19
|
+
// 地板 + 木板缝(汇向消失点)
|
|
20
|
+
g.fillStyle = C.floor; g.fillRect(0, 700, W, H - 700);
|
|
21
|
+
g.strokeStyle = C.floor2; g.lineWidth = 6;
|
|
22
|
+
for (let i = -14; i <= 14; i++) { g.beginPath(); g.moveTo(VP[0] + i * 60, 700); g.lineTo(VP[0] + i * 230, H); g.stroke(); }
|
|
23
|
+
g.fillStyle = '#3a2a6a'; g.fillRect(0, 696, W, 10);
|
|
24
|
+
// 灯晕底色(大面积,交给笔触)
|
|
25
|
+
{ const hg = g.createRadialGradient(1000, 180, 10, 1000, 180, 105); hg.addColorStop(0, '#fff7c8'); hg.addColorStop(0.55, '#f4d65a'); hg.addColorStop(1, 'rgba(120,150,220,0)'); g.fillStyle = hg; g.beginPath(); g.arc(1000, 180, 105, 0, Math.PI * 2); g.fill(); }
|
|
26
|
+
// 窗:星空
|
|
27
|
+
g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
|
|
28
|
+
const sg = g.createLinearGradient(0, 140, 0, 540); sg.addColorStop(0, C.sky); sg.addColorStop(1, C.sky2); g.fillStyle = sg; g.fillRect(370, 140, 420, 400);
|
|
29
|
+
// 漩涡:螺旋色带,随时间旋转
|
|
30
|
+
g.lineCap = 'round';
|
|
31
|
+
swirls.forEach(([x, y, r], k) => { for (let ring = 0; ring < 3; ring++) { g.strokeStyle = ring % 2 ? C.swirl : '#5a82d6'; g.lineWidth = 14 - ring * 3; g.beginPath();
|
|
32
|
+
for (let a = 0; a < Math.PI * 3; a += 0.15) { const rr = r * (0.25 + a / (Math.PI * 3)) * (1 - ring * 0.12), aa = a + t * (0.9 + k * 0.2) * (k % 2 ? -1 : 1); const px = x + Math.cos(aa) * rr, py = y + Math.sin(aa) * rr * 0.8; a ? g.lineTo(px, py) : g.moveTo(px, py); } g.stroke(); } });
|
|
33
|
+
// 月亮
|
|
34
|
+
g.fillStyle = C.star; g.beginPath(); g.arc(745, 200, 30, 0, Math.PI * 2); g.fill(); g.fillStyle = C.sky; g.beginPath(); g.arc(732, 192, 24, 0, Math.PI * 2); g.fill();
|
|
35
|
+
// 柏树(火焰形)与村庄
|
|
36
|
+
g.fillStyle = C.cypress; g.beginPath(); g.moveTo(400, 545); g.bezierCurveTo(372, 430, 420, 330, 425, 200); g.bezierCurveTo(445, 320, 470, 430, 452, 545); g.closePath(); g.fill();
|
|
37
|
+
g.fillStyle = C.village; g.beginPath(); g.moveTo(370, 545); [[420, 500], [470, 505], [500, 480], [540, 500], [590, 470], [610, 440], [625, 470], [680, 495], [730, 485], [790, 500], [790, 545]].forEach(p => g.lineTo(...p)); g.closePath(); g.fill();
|
|
38
|
+
g.fillStyle = '#f2c94c'; [[480, 512], [520, 520], [560, 508], [650, 512], [700, 520], [740, 510]].forEach(([x, y]) => g.fillRect(x, y, 8, 8));
|
|
39
|
+
g.restore();
|
|
40
|
+
// 窗框(绿)+ 中梃 + 窗台
|
|
41
|
+
g.lineWidth = 26; g.strokeStyle = C.frame; g.strokeRect(357, 127, 446, 426);
|
|
42
|
+
g.lineWidth = 6; g.strokeStyle = C.frameHi; g.strokeRect(366, 136, 428, 408);
|
|
43
|
+
g.fillStyle = C.frame; g.fillRect(568, 140, 20, 400);
|
|
44
|
+
g.fillStyle = C.frameHi; g.fillRect(330, 552, 500, 22); g.fillStyle = C.frame; g.fillRect(330, 570, 500, 14);
|
|
45
|
+
// 床(右侧)
|
|
46
|
+
g.fillStyle = C.wood; g.fillRect(1610, 560, 330, 420); g.fillStyle = C.wood2; g.fillRect(1640, 640, 290, 300);
|
|
47
|
+
g.fillStyle = C.bed; g.beginPath(); g.moveTo(1620, 600); g.quadraticCurveTo(1760, 500, 1940, 520); g.lineTo(1940, 640); g.quadraticCurveTo(1780, 620, 1620, 660); g.closePath(); g.fill();
|
|
48
|
+
g.fillStyle = C.wood; g.fillRect(1598, 520, 26, 470); g.beginPath(); g.arc(1611, 515, 18, 0, Math.PI * 2); g.fill();
|
|
49
|
+
}
|
|
50
|
+
function props(g, t) {
|
|
51
|
+
// 墙上两幅小画
|
|
52
|
+
[[1590, 250, 100, 120, '#d9733a', '#66a35a'], [1730, 250, 100, 120, '#e8d36a', '#4f7fc9']].forEach(([x, y, w, h, a, b]) => {
|
|
53
|
+
g.fillStyle = '#d8a83a'; g.fillRect(x - 10, y - 10, w + 20, h + 20); g.fillStyle = b; g.fillRect(x, y, w, h); g.fillStyle = a; g.beginPath(); g.ellipse(x + w / 2, y + h * 0.55, w * 0.28, h * 0.25, 0, 0, Math.PI * 2); g.fill(); });
|
|
54
|
+
// 椅子(梯背,在少女后面)
|
|
55
|
+
g.fillStyle = C.wood; [[1432, 470, 16, 440], [1478, 480, 16, 430]].forEach(r => g.fillRect(...r));
|
|
56
|
+
for (let k = 0; k < 4; k++) g.fillRect(1436, 510 + k * 55, 56, 10);
|
|
57
|
+
g.fillRect(1250, 742, 250, 16); g.fillRect(1262, 750, 14, 160);
|
|
58
|
+
// 桌子
|
|
59
|
+
g.fillStyle = C.wood; g.fillRect(810, 618, 400, 32); g.fillStyle = C.wood2; g.fillRect(830, 650, 360, 52);
|
|
60
|
+
g.fillStyle = '#e9c46a'; g.fillRect(960, 664, 90, 22); g.fillStyle = '#6b4a1a'; g.beginPath(); g.arc(1005, 675, 5, 0, Math.PI * 2); g.fill();
|
|
61
|
+
g.fillStyle = C.wood; g.fillRect(842, 700, 22, 205); g.fillRect(1160, 700, 22, 205); g.fillRect(842, 860, 340, 12);
|
|
62
|
+
// 花瓶+向日葵
|
|
63
|
+
g.fillStyle = '#e8c255'; g.beginPath(); g.moveTo(852, 618); g.quadraticCurveTo(838, 575, 862, 548); g.lineTo(898, 548); g.quadraticCurveTo(920, 575, 905, 618); g.closePath(); g.fill();
|
|
64
|
+
g.strokeStyle = '#4d8a3a'; g.lineWidth = 6; [[870, 548, 842, 490], [880, 548, 890, 462], [890, 548, 930, 502], [875, 548, 868, 528]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.lineTo(c2, d); g.stroke(); });
|
|
65
|
+
[[842, 490, 26], [890, 462, 28], [930, 502, 24], [866, 526, 18]].forEach(([x, y, r], k) => { const sp = Math.sin(t * 3 + k) * 0.08;
|
|
66
|
+
g.fillStyle = '#f2b51c'; for (let i = 0; i < 12; i++) { const a = i / 12 * Math.PI * 2 + sp; g.beginPath(); g.ellipse(x + Math.cos(a) * r * 0.8, y + Math.sin(a) * r * 0.8, r * 0.45, r * 0.18, a, 0, Math.PI * 2); g.fill(); }
|
|
67
|
+
g.fillStyle = '#8a4b14'; g.beginPath(); g.arc(x, y, r * 0.45, 0, Math.PI * 2); g.fill(); });
|
|
68
|
+
// 吊灯
|
|
69
|
+
g.strokeStyle = '#3a2a2a'; g.lineWidth = 4; g.beginPath(); g.moveTo(1000, 0); g.lineTo(1000, 150); g.stroke();
|
|
70
|
+
g.fillStyle = '#f1c232'; g.beginPath(); g.moveTo(968, 176); g.quadraticCurveTo(1000, 132, 1032, 176); g.closePath(); g.fill();
|
|
71
|
+
|
|
72
|
+
g.fillStyle = '#fff6c0'; g.beginPath(); g.arc(1000, 182, 12, 0, Math.PI * 2); g.fill();
|
|
73
|
+
}
|
|
74
|
+
// 笔触方向场
|
|
75
|
+
const angle = (x, y, t) => {
|
|
76
|
+
if (x > 370 && x < 790 && y > 140 && y < 540) { // 星空:沿最近漩涡的切线
|
|
77
|
+
let best = null, bd = 1e9; swirls.forEach(s => { const d = Math.hypot(x - s[0], y - s[1]) / s[2]; if (d < bd) { bd = d; best = s; } });
|
|
78
|
+
if (bd < 1.6) return Math.atan2(y - best[1], x - best[0]) + Math.PI / 2 + t * 0.3;
|
|
79
|
+
return 0.15 * Math.sin(x * 0.02 + t) ;
|
|
80
|
+
}
|
|
81
|
+
const dl = Math.hypot(x - 1000, y - 180); if (dl < 150) return Math.atan2(y - 180, x - 1000) + Math.PI / 2; // 灯晕:圆周
|
|
82
|
+
if (y > 700) return Math.atan2(y - VP[1], x - VP[0]) + 0.06 * Math.sin(t * 2 + x * 0.01); // 地板:透视线
|
|
83
|
+
if (x > 1600 && y > 520) return y < 640 ? -0.12 : -Math.PI / 2;
|
|
84
|
+
if (x > 810 && x < 1210 && y > 610 && y < 705) return 0.02;
|
|
85
|
+
return -Math.PI / 2 + 0.26 * P.noise(x * 0.008 + t * 0.5, y * 0.008 + 3 - t * 0.8); // 墙:竖向,随时间像火苗一样缓慢摇曳
|
|
86
|
+
};
|
|
87
|
+
// 区域色板:每一笔从所在区域的色组里抽色,再混一点底稿色保留明暗(梵高的「同一面墙十几种蓝」)
|
|
88
|
+
const SW = {
|
|
89
|
+
wall: P.swatch(['#7f9be6', '#5f7fd8', '#a7bbf2', '#6c6fd2', '#8ad0e6', '#c3cbf6', '#4f6cc8', '#9a8fe0'], 0.3),
|
|
90
|
+
sky: P.swatch(['#22339a', '#2f4fb8', '#5c86dc', '#8fb6ee', '#1b2a78', '#3c6bd0'], 0.45),
|
|
91
|
+
floor: P.swatch(['#c0573c', '#a8453a', '#d7774a', '#8e3b30', '#5f8f6a', '#c96a52', '#e08a5c'], 0.3),
|
|
92
|
+
wood: P.swatch(['#f2c13a', '#e0a020', '#f8d860', '#d08a18', '#b8c040'], 0.15),
|
|
93
|
+
bed: P.swatch(['#e04a32', '#c63a2a', '#f06b45', '#b83224'], 0.4),
|
|
94
|
+
green: P.swatch(['#2f9a5c', '#4fc27a', '#1f7a48', '#8be09a'], 0.35),
|
|
95
|
+
halo: P.swatch(['#fff2a0', '#f6d84a', '#ffe680', '#f0c040'], 0.3),
|
|
96
|
+
};
|
|
97
|
+
const palette = (x, y, col, r) => {
|
|
98
|
+
if (Math.hypot(x - 1000, y - 180) < 92) return SW.halo(col, r);
|
|
99
|
+
if (x > 370 && x < 790 && y > 140 && y < 540) { const L = col[0] + col[1] + col[2]; return L > 500 ? null : SW.sky(col, r); }
|
|
100
|
+
if (x > 340 && x < 830 && y > 115 && y < 590) return SW.green(col, r);
|
|
101
|
+
if (y > 706) return SW.floor(col, r);
|
|
102
|
+
if (x > 1595 && y > 600) return SW.wood(col, r);
|
|
103
|
+
if (x > 1600 && y > 505 && y <= 640) return SW.bed(col, r);
|
|
104
|
+
return SW.wall(col, r);
|
|
105
|
+
};
|
|
106
|
+
// 环境线稿:桌、椅、床、窗框、画框、花瓶的深蓝轮廓(8fps 轻微抖动)
|
|
107
|
+
function lines(c, t) {
|
|
108
|
+
const sd = P.boilSeed(t, 8); c.save(); c.strokeStyle = C.line; c.lineWidth = 4.5; c.lineJoin = 'round';
|
|
109
|
+
const R_ = (pts, closed = true) => { P.roughPath(c, pts, { amp: 2.2, seed: sd * 31 + pts.length + (pts[0][0] | 0), closed, step: 14 }); c.stroke(); };
|
|
110
|
+
R_([[344, 114], [816, 114], [816, 566], [344, 566]]); R_([[370, 140], [790, 140], [790, 540], [370, 540]]); R_([[568, 140], [588, 140], [588, 540], [568, 540]]);
|
|
111
|
+
R_([[330, 552], [830, 552], [830, 584], [330, 584]]);
|
|
112
|
+
R_([[810, 618], [1210, 618], [1210, 650], [810, 650]]); R_([[830, 650], [1190, 650], [1190, 702], [830, 702]]); R_([[960, 664], [1050, 664], [1050, 686], [960, 686]]);
|
|
113
|
+
R_([[842, 700], [864, 700], [864, 905], [842, 905]]); R_([[1160, 700], [1182, 700], [1182, 905], [1160, 905]]);
|
|
114
|
+
R_([[1432, 470], [1448, 470], [1448, 910], [1432, 910]]); R_([[1478, 480], [1494, 480], [1494, 910], [1478, 910]]);
|
|
115
|
+
for (let k = 0; k < 4; k++) R_([[1436, 510 + k * 55], [1492, 510 + k * 55], [1492, 520 + k * 55], [1436, 520 + k * 55]]);
|
|
116
|
+
R_([[1610, 560], [1940, 560], [1940, 980], [1610, 980]]); R_([[1640, 640], [1930, 640], [1930, 940], [1640, 940]]); R_([[1598, 520], [1624, 520], [1624, 990], [1598, 990]]);
|
|
117
|
+
R_([[1580, 240], [1700, 240], [1700, 380], [1580, 380]]); R_([[1720, 240], [1840, 240], [1840, 380], [1720, 380]]);
|
|
118
|
+
R_([[852, 618], [840, 580], [862, 548], [898, 548], [918, 580], [905, 618]]);
|
|
119
|
+
R_([[0, 700], [1940, 700]], false);
|
|
120
|
+
c.lineWidth = 3.5; for (let i = -12; i <= 12; i++) { if (i % 2) continue; R_([[VP[0] + i * 62 * 1.2, 704], [VP[0] + i * 240, 1080]], false); }
|
|
121
|
+
c.restore();
|
|
122
|
+
}
|
|
123
|
+
const girlPal = { skin: '#f8dcc6', hair: '#f2c552', hairLine: '#c4922c', dress: '#3a5fb4', fold: '#2a4386', apron: '#f6f1e6', hat: '#ecc860', hatBand: '#2f50a8',
|
|
124
|
+
cheek: 'rgba(240,120,110,.45)', lip: '#d0504e', iris: '#2a4d90', line: C.line, lw: 4, cupBody: '#2a50b0', cupRim: '#4a2a12' };
|
|
125
|
+
const catPal = { orange: '#ec9433', white: '#fbf1dd', stripe: '#c0621a', line: C.line, lw: 4, eye: '#7ec850' };
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
draw(c, lt, t) {
|
|
129
|
+
const ch = P.choreo(lt, t);
|
|
130
|
+
const b = P.scratch('vgBase'), bg = b.getContext('2d');
|
|
131
|
+
base(bg, t);
|
|
132
|
+
c.drawImage(b, 0, 0);
|
|
133
|
+
const isWall = (x, y) => y < 700 && !(x > 340 && x < 830 && y > 110 && y < 590) && !(x > 1595 && y > 505) && Math.hypot(x - 1000, y - 180) > 125;
|
|
134
|
+
P.strokes(c, b, { cell: 10, len: 22, width: 6.5, angle, seed: 9, t, boil: 0, outline: 0.9, outlineMix: 0.45, palette, mask: (x, y) => !isWall(x, y) && (y <= 704 || (x > 1595 && y < 995)) });
|
|
135
|
+
P.strokes(c, b, { cell: 14, len: 64, width: 8, angle, seed: 19, t, boil: 0, outline: 0.8, outlineMix: 0.38, palette, mask: isWall });
|
|
136
|
+
P.strokes(c, b, { cell: 12, len: 46, width: 8, angle, seed: 29, t, boil: 0, outline: 0.85, outlineMix: 0.45, palette, mask: (x, y) => y > 704 && !(x > 1595 && y < 995) });
|
|
137
|
+
// 道具层:桌、椅、花瓶向日葵、画框、吊灯——单独画、单独上细肌理,避免被大笔触打碎
|
|
138
|
+
const Pr = P.scratch('vgProps'), pg = Pr.getContext('2d'); pg.clearRect(0, 0, W, H); props(pg, t);
|
|
139
|
+
pg.save(); pg.globalCompositeOperation = 'source-atop';
|
|
140
|
+
P.strokes(pg, Pr, { cell: 7, len: 14, width: 4.5, seed: 7, t, boil: 0, outline: 0.5, outlineMix: 0.35, jitterCol: 34, alphaMask: true,
|
|
141
|
+
angle: (x, y) => (y > 650 && y < 905 && ((x > 836 && x < 870) || (x > 1154 && x < 1188) || x > 1425)) ? -Math.PI / 2 : 0.05 });
|
|
142
|
+
pg.restore(); c.drawImage(Pr, 0, 0);
|
|
143
|
+
lines(c, t);
|
|
144
|
+
// 灯晕:一圈圈向外脉动
|
|
145
|
+
c.save(); c.globalCompositeOperation = 'lighter'; c.lineCap = 'round';
|
|
146
|
+
for (let k = 0; k < 3; k++) { const ph = ((t * 0.9 + k / 3) % 1), r = 40 + ph * 110; c.strokeStyle = `rgba(255,236,140,${0.28 * (1 - ph)})`; c.lineWidth = 18; c.setLineDash([22, 14]); c.lineDashOffset = -t * 60; c.beginPath(); c.arc(1000, 180, r, 0, Math.PI * 2); c.stroke(); }
|
|
147
|
+
c.setLineDash([]);
|
|
148
|
+
c.restore();
|
|
149
|
+
// 星星:闪烁+光圈
|
|
150
|
+
stars.forEach(([x, y, r], k) => { const tw = 0.75 + 0.35 * Math.sin(t * 5 + k * 1.7); c.fillStyle = 'rgba(251,238,160,.55)'; c.beginPath(); c.arc(x, y, r * 1.9 * tw, 0, Math.PI * 2); c.fill(); c.fillStyle = C.star; c.beginPath(); c.arc(x, y, r * 0.75 * tw, 0, Math.PI * 2); c.fill(); });
|
|
151
|
+
// 角色:平涂 → 细笔触 → 补线
|
|
152
|
+
const L = P.scratch('vgChars'), lg = L.getContext('2d'); lg.clearRect(0, 0, W, H);
|
|
153
|
+
const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
|
|
154
|
+
const G = RIG.girl({ cup: ch.cup, sip: ch.sip, hat: 'straw', breathe: ch.breathe });
|
|
155
|
+
RIG.drawCat(lg, K, { ...catPal, lw: 0 }); RIG.drawGirl(lg, G, { ...girlPal, mode: 'fill' });
|
|
156
|
+
lg.save(); lg.globalCompositeOperation = 'source-atop';
|
|
157
|
+
P.strokes(lg, L, { cell: 8, len: 16, width: 5, seed: 4, t, boil: 0, outline: 0, jitterCol: 22, alphaMask: true,
|
|
158
|
+
angle: (x, y) => (x < 760 ? Math.atan2(y - 760, x - 545) + Math.PI / 2 : -Math.PI / 2 + 0.3 * Math.sin(y * 0.02)) }); // 猫身体打旋(梵高式),少女竖向
|
|
159
|
+
lg.restore();
|
|
160
|
+
// 猫身上的梵高漩涡纹(原片那只「漩涡猫」)
|
|
161
|
+
lg.save(); lg.clip(K.body); lg.lineCap = 'round';
|
|
162
|
+
for (let ring = 0; ring < 7; ring++) { lg.strokeStyle = ring % 2 ? '#f7c063' : '#c96a1e'; lg.lineWidth = 7; lg.beginPath();
|
|
163
|
+
for (let a = 0; a < Math.PI * 1.7; a += 0.12) { const rr = 18 + ring * 17, aa = a + ring * 0.7 + t * 0.6; const px = 505 + Math.cos(aa) * rr, py = 805 + Math.sin(aa) * rr * 0.9; a ? lg.lineTo(px, py) : lg.moveTo(px, py); } lg.stroke(); }
|
|
164
|
+
lg.restore();
|
|
165
|
+
c.drawImage(L, 0, 0);
|
|
166
|
+
c.drawImage(RIG.visibleLines('vgCat', (g, lc) => RIG.drawCat(g, K, { ...catPal, line: lc, whisker: lc }), C.line, [340, 500, 740, 940]), 0, 0);
|
|
167
|
+
c.drawImage(RIG.visibleLines('vgGirl', (g, lc) => RIG.drawGirl(g, G, { ...girlPal, line: lc }), C.line, [1060, 200, 1520, 950]), 0, 0);
|
|
168
|
+
// 线稿层会把帽檐后半圈画穿到头发上:线稿之后把帽子整顶重新盖一遍(填色+草编短笔+外轮廓)
|
|
169
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
170
|
+
[G.brim, G.crown].forEach(p => { c.fillStyle = girlPal.hat; c.fill(p); });
|
|
171
|
+
c.save(); c.clip(G.brim); c.clip(G.brim);
|
|
172
|
+
const hr = U.rng(55); c.lineWidth = 3.5;
|
|
173
|
+
for (let i = 0; i < 70; i++) { const x = G.A.headTop[0] - 140 + hr() * 280, y = G.A.headTop[1] - 40 + hr() * 70; c.strokeStyle = hr() < .5 ? '#f6d977' : '#c99a2e'; c.beginPath(); c.moveTo(x, y); c.lineTo(x + 16, y + 2); c.stroke(); }
|
|
174
|
+
c.restore();
|
|
175
|
+
c.save(); c.clip(G.crown); for (let i = 0; i < 30; i++) { const x = G.A.headTop[0] - 60 + hr() * 130, y = G.A.headTop[1] - 50 + hr() * 50; c.strokeStyle = hr() < .5 ? '#f6d977' : '#c99a2e'; c.beginPath(); c.moveTo(x, y); c.lineTo(x + 3, y + 14); c.stroke(); } c.restore();
|
|
176
|
+
c.fillStyle = girlPal.hatBand; c.fill(G.band);
|
|
177
|
+
c.strokeStyle = C.line; c.lineWidth = 4; c.stroke(G.brim); c.stroke(G.crown);
|
|
178
|
+
c.restore();
|
|
179
|
+
// 热气(从杯口卷曲上升)
|
|
180
|
+
P.steam(c, t, G.cup.x, G.cup.y - 6, { h: 70, n: 2, color: 'rgba(240,244,255,.85)', width: 4, spread: 14, wobble: 9 });
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
})();
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// 1896 新艺术运动(慕夏海报)——纯代码。
|
|
2
|
+
// 管线:①缓存底版(灰绿渐变+圆点花纹+金框+马赛克地砖带+绿横幅+标题)
|
|
3
|
+
// → ②会动的母题(彩色玻璃拱窗里太阳升起、铅条光芒转动;罂粟/百合摇摆;光环转动)
|
|
4
|
+
// → ③角色平涂进离屏层 → 整体剪影往右下错位填深棕 = 慕夏式「外轮廓粗、内线细」
|
|
5
|
+
// → ④细内线 + 可变线宽的金色长卷发、花环、S 形装饰热气
|
|
6
|
+
SCENES['10_nouveau'] = (() => {
|
|
7
|
+
const W = 1920, H = 1080, { clamp, lerp } = U, P = PAINT;
|
|
8
|
+
const C = {
|
|
9
|
+
paper: '#efe6cf', gold: '#c9a24a', goldD: '#9c7a2e', line: '#4e3320', green: '#b9cba0', green2: '#cfd3a6', peach: '#ecc9a2',
|
|
10
|
+
dot: 'rgba(120,140,100,.38)', banner: '#8fac80', bannerD: '#6f8f63', cream: '#f6eedb',
|
|
11
|
+
poppy: '#d9706a', poppyD: '#b4504b', stem: '#5f8a58', leaf: '#8fb38a', leafD: '#5f8a58', lily: '#f7f2e6',
|
|
12
|
+
iron: '#3d7763', ironHi: '#5f9a82', wood: '#7a5234',
|
|
13
|
+
};
|
|
14
|
+
const TAU = Math.PI * 2;
|
|
15
|
+
|
|
16
|
+
// ---------- 小工具 ----------
|
|
17
|
+
// Catmull-Rom 加密成点列
|
|
18
|
+
const dense = (pts, n = 10) => {
|
|
19
|
+
const o = [], N = pts.length; const g = i => pts[Math.max(0, Math.min(N - 1, i))];
|
|
20
|
+
for (let i = 0; i < N - 1; i++) { const p0 = g(i - 1), p1 = g(i), p2 = g(i + 1), p3 = g(i + 2);
|
|
21
|
+
for (let k = 0; k < n; k++) { const t = k / n, t2 = t * t, t3 = t2 * t;
|
|
22
|
+
o.push([0.5 * ((2 * p1[0]) + (-p0[0] + p2[0]) * t + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3),
|
|
23
|
+
0.5 * ((2 * p1[1]) + (-p0[1] + p2[1]) * t + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3)]); } }
|
|
24
|
+
o.push(pts[N - 1]); return o;
|
|
25
|
+
};
|
|
26
|
+
// 可变线宽路径:沿点列两侧偏移 w(q)/2,返回闭合 Path2D(慕夏的「粗细变化的勾线」靠它)
|
|
27
|
+
const ribbon = (pts, wf) => {
|
|
28
|
+
const L = [], R = [], n = pts.length;
|
|
29
|
+
for (let i = 0; i < n; i++) {
|
|
30
|
+
const a = pts[Math.max(0, i - 1)], b = pts[Math.min(n - 1, i + 1)];
|
|
31
|
+
let dx = b[0] - a[0], dy = b[1] - a[1]; const d = Math.hypot(dx, dy) || 1; dx /= d; dy /= d;
|
|
32
|
+
const w = wf(i / (n - 1)) / 2; L.push([pts[i][0] - dy * w, pts[i][1] + dx * w]); R.push([pts[i][0] + dy * w, pts[i][1] - dx * w]);
|
|
33
|
+
}
|
|
34
|
+
const p = new Path2D(); L.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); for (let i = n - 1; i >= 0; i--) p.lineTo(R[i][0], R[i][1]); p.closePath(); return p;
|
|
35
|
+
};
|
|
36
|
+
const taperW = (w0, w1, mid = 1) => q => lerp(w0, w1, q) * (0.55 + 0.45 * Math.sin(Math.PI * Math.min(1, q * 1.15)) * mid + (1 - mid) * 0.45);
|
|
37
|
+
const swellW = (wmax, wend = 1.2) => q => wend + (wmax - wend) * Math.sin(Math.PI * q); // 两头尖中间粗
|
|
38
|
+
// 螺旋收尾的点列(新艺术的「鞭线」卷曲)
|
|
39
|
+
const spiral = (cx, cy, r0, a0, turns, dir = 1, n = 28) => { const o = []; for (let i = 0; i <= n; i++) { const q = i / n, a = a0 + dir * q * turns * TAU, r = r0 * (1 - q * 0.85); o.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); } return o; };
|
|
40
|
+
const inkLine = (c, pts, wmax, col = C.line, wend = 1) => { c.fillStyle = col; c.fill(ribbon(dense(pts, 8), swellW(wmax, wend))); };
|
|
41
|
+
|
|
42
|
+
// ---------- ① 底版(缓存) ----------
|
|
43
|
+
const bg = () => P.cached('nv_bg', W, H, (g) => {
|
|
44
|
+
g.fillStyle = C.paper; g.fillRect(0, 0, W, H);
|
|
45
|
+
const gr = g.createLinearGradient(0, 30, 0, 905); gr.addColorStop(0, C.green); gr.addColorStop(0.55, C.green2); gr.addColorStop(1, C.peach);
|
|
46
|
+
g.fillStyle = gr; g.fillRect(34, 32, W - 68, 875);
|
|
47
|
+
// 圆点花纹:环+心点,菱形交错
|
|
48
|
+
g.strokeStyle = C.dot; g.fillStyle = C.dot; g.lineWidth = 1.6;
|
|
49
|
+
for (let j = 0, y = 62; y < 900; y += 54, j++) for (let x = 64 + (j % 2) * 31; x < 1880; x += 62) {
|
|
50
|
+
g.beginPath(); g.arc(x, y, 10, 0, TAU); g.stroke(); g.beginPath(); g.arc(x, y, 2.6, 0, TAU); g.fill(); }
|
|
51
|
+
// 轻微的石版印刷斑驳(小噪声图放大)
|
|
52
|
+
const nz = P.canvas(240, 135), ng = nz.getContext('2d'), im = ng.createImageData(240, 135);
|
|
53
|
+
for (let y = 0; y < 135; y++) for (let x = 0; x < 240; x++) { const v = P.fbm(x * 0.05, y * 0.05, 4), i = (y * 240 + x) * 4; im.data[i] = 90; im.data[i + 1] = 70; im.data[i + 2] = 40; im.data[i + 3] = clamp(v * 1.4 + 0.15) * 60; }
|
|
54
|
+
ng.putImageData(im, 0, 0); g.globalAlpha = 0.5; g.drawImage(nz, 0, 0, W, H); g.globalAlpha = 1;
|
|
55
|
+
// 马赛克地砖带
|
|
56
|
+
const tiles = ['#f1ead6', '#8fb08a', '#c9a24a', '#f1ead6', '#a9c39a', '#d98c8c', '#c9a24a', '#f1ead6', '#6f9a80'];
|
|
57
|
+
g.fillStyle = C.line; g.fillRect(34, 898, W - 68, 34);
|
|
58
|
+
for (let k = 0, x = 38; x < 1880; x += 24, k++) { g.fillStyle = tiles[(k * 7 + (k >> 2)) % tiles.length]; g.fillRect(x, 903, 20, 24); }
|
|
59
|
+
// 绿横幅
|
|
60
|
+
g.fillStyle = C.banner; g.fillRect(34, 934, W - 68, 114);
|
|
61
|
+
g.fillStyle = C.line; g.fillRect(34, 932, W - 68, 3); g.fillRect(34, 1046, W - 68, 3);
|
|
62
|
+
// 横幅两侧的鞭线卷草(左右镜像)
|
|
63
|
+
const whip = (s) => { g.save(); if (s < 0) { g.translate(W, 0); g.scale(-1, 1); }
|
|
64
|
+
g.fillStyle = C.cream; g.fill(ribbon(dense([[100, 960], [112, 948], [128, 958], [200, 985], [300, 990], [400, 984]], 10), swellW(7, 1)));
|
|
65
|
+
g.fillStyle = C.gold; g.fill(ribbon(dense([[130, 1010], [150, 1022], [180, 1004], [260, 985], [330, 975], [380, 975]], 10), swellW(8, 1)));
|
|
66
|
+
g.fillStyle = C.cream; g.fill(ribbon(dense(spiral(112, 956, 16, Math.PI, 0.9, 1), 2), q => 4.5 - q * 3));
|
|
67
|
+
g.fillStyle = C.gold; g.fill(ribbon(dense(spiral(142, 1012, 15, 0, 0.8, -1), 2), q => 5 - q * 3.5));
|
|
68
|
+
g.strokeStyle = C.line; g.lineWidth = 1.2; g.fill(ribbon(dense([[200, 1030], [260, 1010], [340, 1000]], 10), swellW(3, .6)));
|
|
69
|
+
g.fillStyle = '#d07a86'; g.beginPath(); g.arc(420, 993, 8, 0, TAU); g.fill();
|
|
70
|
+
g.restore(); };
|
|
71
|
+
whip(1); whip(-1);
|
|
72
|
+
// 标题
|
|
73
|
+
g.font = '76px "Marcellus-400"'; g.textAlign = 'center'; g.textBaseline = 'middle'; g.letterSpacing = '12px';
|
|
74
|
+
g.lineJoin = 'round'; g.strokeStyle = C.line; g.lineWidth = 7; g.strokeText('THÉ DU CHAT ROUX', 966, 994);
|
|
75
|
+
g.fillStyle = C.cream; g.fillText('THÉ DU CHAT ROUX', 966, 994); g.letterSpacing = '0px';
|
|
76
|
+
// 金框:外粗金线+内细棕线
|
|
77
|
+
g.strokeStyle = C.gold; g.lineWidth = 6; g.strokeRect(22, 20, W - 44, H - 40);
|
|
78
|
+
g.strokeStyle = C.line; g.lineWidth = 2; g.strokeRect(31, 29, W - 62, H - 58); g.strokeRect(16, 14, W - 32, H - 28);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ---------- ② 彩色玻璃拱窗 ----------
|
|
82
|
+
const WX0 = 362, WX1 = 800, WY = 548, ACX = 581, ACY = 250, ARX = 219, ARY = 130;
|
|
83
|
+
const archPath = (ins) => { const p = new Path2D(); p.moveTo(WX0 + ins, WY - ins); p.lineTo(WX0 + ins, ACY); p.ellipse(ACX, ACY, ARX - ins, ARY - ins, 0, Math.PI, TAU); p.lineTo(WX1 - ins, WY - ins); p.closePath(); return p; };
|
|
84
|
+
const archPts = (ins) => { const o = []; for (let y = WY - ins; y > ACY; y -= 4) o.push([WX0 + ins, y]); for (let a = Math.PI; a <= TAU + 1e-6; a += 0.02) o.push([ACX + Math.cos(a) * (ARX - ins), ACY + Math.sin(a) * (ARY - ins)]); for (let y = ACY; y < WY - ins; y += 4) o.push([WX1 - ins, y]); return o; };
|
|
85
|
+
const hill = (y0, amp, ph, f = 0.016) => { const o = []; for (let x = WX0; x <= WX1 + 10; x += 12) o.push([x, y0 + Math.sin(x * f + ph) * amp + Math.sin(x * f * 2.3 + ph * 2) * amp * 0.35]); return o; };
|
|
86
|
+
const bands = [[372, 16, 0.5, '#f2dd9c'], [398, 18, 2.2, '#efc39c'], [424, 16, 4.1, '#e6a7a0'], [452, 14, 1.3, '#a9cfa0'], [490, 10, 3.3, '#7fb5a8']];
|
|
87
|
+
function windowGlass(c, lt, t) {
|
|
88
|
+
const inner = archPath(30);
|
|
89
|
+
c.save(); c.clip(inner);
|
|
90
|
+
const sg = c.createLinearGradient(0, 130, 0, 400); sg.addColorStop(0, '#86bfbd'); sg.addColorStop(0.6, '#b9d8b3'); sg.addColorStop(1, '#e9e2a8');
|
|
91
|
+
c.fillStyle = sg; c.fillRect(WX0, 110, WX1 - WX0, 440);
|
|
92
|
+
// 太阳:从山后升起(原片 ~-5px/帧)
|
|
93
|
+
const k = clamp(lt, 0, 1.4), sx = 583 - (k - 0.267) * 20, sy = 400 - (k - 0.267) * 288;
|
|
94
|
+
// 铅条光芒:从太阳中心放射,随时间转动
|
|
95
|
+
c.strokeStyle = C.line; c.lineWidth = 3.5; c.lineCap = 'round';
|
|
96
|
+
const rot = lt * 0.9;
|
|
97
|
+
for (let i = 0; i < 14; i++) { const a = Math.PI + (i + 0.5) / 14 * Math.PI + Math.sin(rot + i) * 0.04 + rot * 0.25; c.beginPath(); c.moveTo(sx + Math.cos(a) * 92, sy + Math.sin(a) * 92); c.lineTo(sx + Math.cos(a) * 420, sy + Math.sin(a) * 420); c.stroke(); }
|
|
98
|
+
// 天空里两道弧形铅条
|
|
99
|
+
c.lineWidth = 3; [[0.78, 150], [1.15, 200]].forEach(([e, r]) => { c.beginPath(); c.ellipse(sx, sy, r * 1.25, r * e * 0.75, 0, Math.PI, TAU); c.stroke(); });
|
|
100
|
+
// 光晕环+日盘
|
|
101
|
+
c.fillStyle = '#f7e2a0'; c.beginPath(); c.arc(sx, sy, 80, 0, TAU); c.fill(); c.stroke();
|
|
102
|
+
const sun = c.createRadialGradient(sx - 14, sy - 14, 6, sx, sy, 64); sun.addColorStop(0, '#f8c860'); sun.addColorStop(1, '#e8a43c');
|
|
103
|
+
c.fillStyle = sun; c.beginPath(); c.arc(sx, sy, 62, 0, TAU); c.fill(); c.lineWidth = 4; c.stroke();
|
|
104
|
+
// 山丘色带(挡住太阳下半)
|
|
105
|
+
bands.forEach(([y0, amp, ph, col], i) => { const pts = hill(y0, amp, ph + Math.sin(t * 1.4 + i) * 0.12); c.fillStyle = col; c.beginPath(); c.moveTo(WX0 - 10, 560); pts.forEach(p => c.lineTo(p[0], p[1])); c.lineTo(WX1 + 10, 560); c.closePath(); c.fill(); c.lineWidth = 3.5; c.beginPath(); pts.forEach((p, j) => j ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); });
|
|
106
|
+
// 鸢尾:白色旗瓣+紫/粉垂瓣,叶片剑形
|
|
107
|
+
[[456, 452, '#b27ec4', 0], [580, 462, '#e3a0b0', 1.7], [704, 452, '#a777c0', 3.1]].forEach(([x, y, fc, ph]) => {
|
|
108
|
+
const sw = Math.sin(t * 5 + ph) * 4;
|
|
109
|
+
c.fillStyle = '#6aa860'; c.lineWidth = 2.5;
|
|
110
|
+
[[-34, -40], [30, -46], [-14, -60], [46, -20]].forEach(([dx, dy], j) => { const p = ribbon(dense([[x + dx * 0.2, 560], [x + dx * 0.6 + sw * 0.5, y + 50], [x + dx + sw, y + dy + 40]], 8), q => 14 * (1 - q) + 1); c.fill(p); c.stroke(p); });
|
|
111
|
+
c.strokeStyle = C.line; c.lineWidth = 6; c.beginPath(); c.moveTo(x, 560); c.quadraticCurveTo(x + sw * 0.5, y + 50, x + sw, y); c.stroke(); c.strokeStyle = '#6aa860'; c.lineWidth = 3; c.stroke(); c.strokeStyle = C.line;
|
|
112
|
+
const fx = x + sw;
|
|
113
|
+
[-1, 1].forEach(s => { c.fillStyle = fc; c.lineWidth = 3; c.beginPath(); c.ellipse(fx + s * 26, y + 12, 26, 17, s * 0.5, 0, TAU); c.fill(); c.stroke(); });
|
|
114
|
+
c.fillStyle = '#f5f0e4'; c.beginPath(); c.moveTo(fx, y + 6); c.bezierCurveTo(fx - 22, y - 14, fx - 14, y - 50, fx, y - 66); c.bezierCurveTo(fx + 14, y - 50, fx + 22, y - 14, fx, y + 6); c.fill(); c.stroke();
|
|
115
|
+
});
|
|
116
|
+
c.restore();
|
|
117
|
+
// 彩色小玻璃边带(沿拱形内缘,一格一色)
|
|
118
|
+
const pts = archPts(22); let acc = 0, k2 = 0; const cols = ['#3f7fa0', '#e0b040', '#6fb0a0', '#e9e0c4', '#3f7fa0', '#d9a03a'];
|
|
119
|
+
c.lineWidth = 1.5; c.strokeStyle = C.line;
|
|
120
|
+
for (let i = 1; i < pts.length; i++) { acc += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); if (acc >= 17) { acc = 0; const a = Math.atan2(pts[i][1] - pts[i - 1][1], pts[i][0] - pts[i - 1][0]); c.save(); c.translate(pts[i][0], pts[i][1]); c.rotate(a); c.fillStyle = cols[k2++ % cols.length]; c.fillRect(-7, -7, 14, 14); c.strokeRect(-7, -7, 14, 14); c.restore(); } }
|
|
121
|
+
// 木拱框+窗台
|
|
122
|
+
c.lineWidth = 16; c.strokeStyle = '#6b4a2e'; c.stroke(archPath(8)); c.lineWidth = 3; c.strokeStyle = C.line; c.stroke(archPath(0)); c.stroke(archPath(15)); c.stroke(archPath(30));
|
|
123
|
+
c.fillStyle = '#6b4a2e'; c.fillRect(348, 540, 466, 22); c.strokeRect(348, 540, 466, 22);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------- 两侧花:罂粟(左)、百合(右),摇摆 ----------
|
|
127
|
+
const poppyHead = (c, x, y, r, rot) => {
|
|
128
|
+
c.save(); c.translate(x, y); c.rotate(rot);
|
|
129
|
+
for (let i = 0; i < 5; i++) { const a = i / 5 * TAU; c.save(); c.rotate(a); c.fillStyle = i % 2 ? C.poppy : '#df7d74';
|
|
130
|
+
c.beginPath(); c.moveTo(0, 0); c.bezierCurveTo(r * 0.9, -r * 0.55, r * 1.12, r * 0.25, r * 0.4, r * 0.62); c.closePath(); c.fill(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke(); c.restore(); }
|
|
131
|
+
c.strokeStyle = C.poppyD; c.lineWidth = 2; for (let i = 0; i < 10; i++) { const a = i / 10 * TAU + 0.3; c.beginPath(); c.moveTo(Math.cos(a) * r * 0.3, Math.sin(a) * r * 0.3); c.lineTo(Math.cos(a) * r * 0.75, Math.sin(a) * r * 0.75); c.stroke(); }
|
|
132
|
+
c.fillStyle = '#3f6a4a'; c.beginPath(); c.arc(0, 0, r * 0.24, 0, TAU); c.fill(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke();
|
|
133
|
+
c.fillStyle = '#e2b84a'; for (let i = 0; i < 10; i++) { const a = i / 10 * TAU; c.beginPath(); c.arc(Math.cos(a) * r * 0.36, Math.sin(a) * r * 0.36, 3, 0, TAU); c.fill(); }
|
|
134
|
+
c.restore();
|
|
135
|
+
};
|
|
136
|
+
const lilyHead = (c, x, y, r, rot) => {
|
|
137
|
+
c.save(); c.translate(x, y); c.rotate(rot); c.lineWidth = 2.5; c.strokeStyle = C.line;
|
|
138
|
+
for (let i = 0; i < 6; i++) { c.save(); c.rotate(i / 6 * TAU + (i % 2) * 0.1); const rr = i % 2 ? r * 0.85 : r;
|
|
139
|
+
c.fillStyle = C.lily; c.beginPath(); c.moveTo(0, 0); c.quadraticCurveTo(rr * 0.35, -rr * 0.28, rr, 0); c.quadraticCurveTo(rr * 0.35, rr * 0.28, 0, 0); c.fill(); c.stroke();
|
|
140
|
+
c.strokeStyle = '#b9a98a'; c.lineWidth = 1.2; c.beginPath(); c.moveTo(rr * 0.15, 0); c.lineTo(rr * 0.8, 0); c.stroke(); c.strokeStyle = C.line; c.lineWidth = 2.5; c.restore(); }
|
|
141
|
+
c.strokeStyle = '#a07a2a'; c.lineWidth = 1.5; for (let i = 0; i < 5; i++) { const a = -1.2 + i * 0.6; c.beginPath(); c.moveTo(0, 0); c.lineTo(Math.cos(a) * r * 0.42, Math.sin(a) * r * 0.42); c.stroke(); c.fillStyle = '#d99a2a'; c.beginPath(); c.arc(Math.cos(a) * r * 0.42, Math.sin(a) * r * 0.42, 3, 0, TAU); c.fill(); }
|
|
142
|
+
c.restore();
|
|
143
|
+
};
|
|
144
|
+
// 茎:从根部到花头的鞭线,sway 只推动上段
|
|
145
|
+
const stem = (c, base, head, bend, w = 9) => { const pts = dense([base, [lerp(base[0], head[0], 0.35) + bend, lerp(base[1], head[1], 0.35)], [lerp(base[0], head[0], 0.7) - bend * 0.6, lerp(base[1], head[1], 0.7)], head], 8);
|
|
146
|
+
c.fillStyle = C.line; c.fill(ribbon(pts, q => w + 3 - q * 3)); c.fillStyle = C.stem; c.fill(ribbon(pts, q => w - 1 - q * 3)); };
|
|
147
|
+
const blade = (c, base, tip, bend, w = 34) => { const pts = dense([base, [lerp(base[0], tip[0], 0.45) + bend, lerp(base[1], tip[1], 0.45)], tip], 10);
|
|
148
|
+
const p = ribbon(pts, q => w * Math.sin(Math.PI * Math.min(1, 0.12 + q * 0.88)) + 1); c.fillStyle = C.leaf; c.fill(p); c.strokeStyle = C.line; c.lineWidth = 2.5; c.stroke(p);
|
|
149
|
+
c.strokeStyle = C.leafD; c.lineWidth = 2; c.beginPath(); pts.slice(2, -3).forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.stroke(); };
|
|
150
|
+
function flowers(c, t) {
|
|
151
|
+
const sw = (ph, amp = 1) => (Math.sin(t * 7.5 + ph) * 13 + Math.sin(t * 3.1 + ph * 2) * 5) * amp;
|
|
152
|
+
// 左:罂粟
|
|
153
|
+
blade(c, [70, 900], [40, 640], 30 + sw(0.4, 0.3)); blade(c, [120, 900], [190, 690], -20 + sw(1.4, 0.3), 28); blade(c, [200, 900], [300, 640], -30 + sw(2.2, 0.3), 30);
|
|
154
|
+
const ps = [[[100, 900], [110, 215], 66, 0], [[150, 900], [234, 372], 62, 1.3], [[180, 900], [162, 495], 52, 2.6]];
|
|
155
|
+
ps.forEach(([b, h, r, ph]) => { const d = sw(ph); stem(c, b, [h[0] + d, h[1] + Math.abs(d) * 0.2], 40 - ph * 20); });
|
|
156
|
+
// 花苞
|
|
157
|
+
{ const d = sw(3.5); stem(c, [230, 900], [272 + d, 222], -40, 8); c.fillStyle = '#9fc3a0'; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(272 + d, 205, 24, 28, 0.2, 0, TAU); c.fill(); c.stroke();
|
|
158
|
+
c.beginPath(); c.arc(240 + d * 0.8, 222, 12, 0, TAU); c.stroke(); }
|
|
159
|
+
ps.forEach(([b, h, r, ph]) => { const d = sw(ph); poppyHead(c, h[0] + d, h[1] + Math.abs(d) * 0.2, r, d * 0.012 + ph); });
|
|
160
|
+
// 右:百合
|
|
161
|
+
blade(c, [1690, 900], [1600, 640], 40 + sw(5, 0.3), 30); blade(c, [1760, 900], [1830, 600], -20 + sw(6, 0.3), 26); blade(c, [1830, 900], [1880, 720], -10 + sw(4.4, 0.3), 24); blade(c, [1720, 900], [1660, 760], 30, 22);
|
|
162
|
+
const ls = [[[1730, 900], [1712, 300], 82, 4.0], [[1700, 900], [1652, 520], 62, 5.1], [[1800, 900], [1822, 470], 64, 6.3]];
|
|
163
|
+
ls.forEach(([b, h, r, ph]) => { const d = -sw(ph); stem(c, b, [h[0] + d, h[1]], -30 + (ph - 5) * 30, 8); });
|
|
164
|
+
{ const d = -sw(7); stem(c, [1820, 900], [1838 + d, 360], 20, 7); c.fillStyle = C.lily; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(1840 + d, 330, 11, 34, 0.1, 0, TAU); c.fill(); c.stroke(); }
|
|
165
|
+
ls.forEach(([b, h, r, ph]) => { const d = -sw(ph); lilyHead(c, h[0] + d, h[1], r, -0.3 + d * 0.01 + ph); });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------- 光环(缓慢转动) ----------
|
|
169
|
+
function girlHalo(c, cx, cy, t) {
|
|
170
|
+
const R = 172, rot = t * 0.9;
|
|
171
|
+
c.save(); c.lineWidth = 3; c.strokeStyle = C.line;
|
|
172
|
+
c.fillStyle = C.gold; c.beginPath(); c.arc(cx, cy, R, 0, TAU); c.fill(); c.stroke();
|
|
173
|
+
// 金环上的圆钉(转动)
|
|
174
|
+
for (let i = 0; i < 18; i++) { const a = rot + i / 18 * TAU; const x = cx + Math.cos(a) * (R - 13), y = cy + Math.sin(a) * (R - 13);
|
|
175
|
+
c.fillStyle = C.cream; c.beginPath(); c.arc(x, y, 7.5, 0, TAU); c.fill(); c.lineWidth = 2; c.stroke(); c.fillStyle = '#c0606a'; c.beginPath(); c.arc(x, y, 3, 0, TAU); c.fill(); }
|
|
176
|
+
c.lineWidth = 3; c.fillStyle = '#5f9a8a'; c.beginPath(); c.arc(cx, cy, R - 26, 0, TAU); c.fill(); c.stroke();
|
|
177
|
+
c.fillStyle = '#ebb8b2'; c.beginPath(); c.arc(cx, cy, R - 34, 0, TAU); c.fill(); c.stroke();
|
|
178
|
+
// 玫瑰窗花瓣(反向转)
|
|
179
|
+
c.save(); c.translate(cx, cy); c.rotate(-rot * 0.7); c.strokeStyle = '#c98a86'; c.lineWidth = 2;
|
|
180
|
+
for (let ring = 0; ring < 2; ring++) for (let i = 0; i < 16; i++) { c.save(); c.rotate(i / 16 * TAU + ring * TAU / 32); const r0 = ring ? 40 : 70, r1 = ring ? 95 : 132;
|
|
181
|
+
c.fillStyle = ring ? '#e3a5a0' : '#efc4be'; c.beginPath(); c.moveTo(r0, 0); c.quadraticCurveTo((r0 + r1) / 2, -20 + ring * 6, r1, 0); c.quadraticCurveTo((r0 + r1) / 2, 20 - ring * 6, r0, 0); c.fill(); c.stroke(); c.restore(); }
|
|
182
|
+
c.restore(); c.restore();
|
|
183
|
+
}
|
|
184
|
+
function catHalo(c, cx, cy, t) {
|
|
185
|
+
const R = 106, rot = -t * 1.1;
|
|
186
|
+
c.save(); c.strokeStyle = C.line; c.lineWidth = 3;
|
|
187
|
+
c.fillStyle = C.gold; c.beginPath(); c.arc(cx, cy, R, 0, TAU); c.fill(); c.stroke();
|
|
188
|
+
c.setLineDash([10, 8]); c.lineDashOffset = rot * 40; c.strokeStyle = C.goldD; c.lineWidth = 6; c.beginPath(); c.arc(cx, cy, R - 8, 0, TAU); c.stroke(); c.setLineDash([]);
|
|
189
|
+
c.strokeStyle = C.line; c.lineWidth = 3; c.fillStyle = '#b8473a'; c.beginPath(); c.arc(cx, cy, R - 16, 0, TAU); c.fill(); c.stroke();
|
|
190
|
+
c.save(); c.translate(cx, cy); c.rotate(rot);
|
|
191
|
+
for (let i = 0; i < 8; i++) { c.save(); c.rotate(i / 8 * TAU); c.fillStyle = '#e8924a'; c.beginPath(); c.moveTo(14, 0); c.quadraticCurveTo(48, -20, 84, 0); c.quadraticCurveTo(48, 20, 14, 0); c.fill(); c.lineWidth = 2; c.stroke(); c.restore(); }
|
|
192
|
+
c.fillStyle = '#e0b84a'; c.beginPath(); c.arc(0, 0, 18, 0, TAU); c.fill(); c.stroke(); c.restore(); c.restore();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------- 桌(绿铁艺卷脚)、椅、碟 ----------
|
|
196
|
+
function table(c) {
|
|
197
|
+
const iron = (pts, w) => { const d = dense(pts, 10); c.fillStyle = C.line; c.fill(ribbon(d, q => w + 4)); c.fillStyle = C.iron; c.fill(ribbon(d, q => w)); };
|
|
198
|
+
iron([[1004, 660], [1004, 760], [1006, 890]], 16);
|
|
199
|
+
iron([[1000, 672], [930, 682], [870, 690], [848, 712], [862, 736], [886, 730], [884, 712]], 10);
|
|
200
|
+
iron([[1010, 672], [1080, 682], [1140, 690], [1162, 712], [1148, 736], [1124, 730], [1126, 712]], 10);
|
|
201
|
+
iron([[1000, 760], [960, 790], [930, 800], [938, 828], [960, 822]], 9); iron([[1010, 760], [1050, 790], [1080, 800], [1072, 828], [1050, 822]], 9);
|
|
202
|
+
iron([[1004, 860], [940, 872], [860, 880], [842, 868], [852, 852], [866, 860]], 10); iron([[1006, 860], [1070, 872], [1150, 880], [1168, 868], [1158, 852], [1144, 860]], 10);
|
|
203
|
+
c.fillStyle = C.ironHi; c.fillRect(1001, 670, 4, 200);
|
|
204
|
+
// 桌面
|
|
205
|
+
c.fillStyle = '#f3ead8'; c.strokeStyle = C.line; c.lineWidth = 3.5; c.beginPath(); c.moveTo(812, 640); c.lineTo(1208, 640); c.lineTo(1196, 660); c.lineTo(824, 660); c.closePath(); c.fill(); c.stroke();
|
|
206
|
+
c.fillStyle = '#d9cdb2'; c.fillRect(826, 655, 368, 4);
|
|
207
|
+
}
|
|
208
|
+
function chair(c) {
|
|
209
|
+
c.fillStyle = C.line; c.fill(ribbon(dense([[1468, 900], [1478, 760], [1470, 600], [1486, 470], [1500, 450]], 8), q => 22 - q * 6));
|
|
210
|
+
c.fillStyle = C.wood; c.fill(ribbon(dense([[1468, 900], [1478, 760], [1470, 600], [1486, 470], [1500, 450]], 8), q => 15 - q * 6));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------- 少女的长卷发:发团(波浪外缘)+ 一绺绺可变线宽的 S 形卷发,末端螺旋 ----------
|
|
214
|
+
function hairMass(c, G, t) {
|
|
215
|
+
const h = G.A.headC, ox = h[0] - 1318, oy = h[1] - 360;
|
|
216
|
+
const pts = [[1286, 300], [1350, 286], [1404, 318], [1432, 380], [1462, 440], [1496, 510], [1520, 590], [1530, 660], [1512, 730], [1470, 760], [1440, 712], [1420, 640], [1396, 560], [1368, 490], [1340, 440], [1318, 410]]
|
|
217
|
+
.map(([x, y], i) => { const w = i > 2 && i < 10 ? Math.sin(t * 6 + y * 0.035) * 9 * (y - 300) / 400 : 0; return [x + ox * (1 - (y - 300) / 500) + w, y + oy * (1 - (y - 300) / 500)]; });
|
|
218
|
+
const p = RIG.smooth(pts, true); c.fillStyle = '#e2b54c'; c.fill(p);
|
|
219
|
+
}
|
|
220
|
+
const lockDefs = {
|
|
221
|
+
back: [[1392, 360, 330, 120, 18, 0.2, 1], [1420, 400, 300, 150, 16, 1.4, -1], [1440, 450, 290, 105, 17, 2.5, 1], [1404, 430, 330, 60, 18, 3.3, -1], [1460, 520, 220, 120, 15, 4.2, 1], [1376, 470, 300, 30, 16, 5.0, 1], [1470, 600, 160, 70, 14, 0.9, -1]],
|
|
222
|
+
front: [[1316, 420, 170, -24, 15, 2.0, -1], [1334, 430, 140, 4, 13, 3.6, 1]],
|
|
223
|
+
};
|
|
224
|
+
const hairLocks = (G, t, which) => {
|
|
225
|
+
const h = G.A.headC, ox = h[0] - 1318, oy = h[1] - 360;
|
|
226
|
+
return lockDefs[which].map(([sx, sy, len, drift, w, ph, dir]) => {
|
|
227
|
+
const pts = []; for (let i = 0; i <= 12; i++) { const q = i / 12; const wave = Math.sin(q * 7.4 + ph - t * 7) * (6 + 20 * q);
|
|
228
|
+
pts.push([sx + ox * (1 - q) + drift * q + wave, sy + oy * (1 - q) + len * q]); }
|
|
229
|
+
const e = pts[pts.length - 1], a0 = Math.atan2(pts[12][1] - pts[11][1], pts[12][0] - pts[11][0]);
|
|
230
|
+
const r = 13 + w * 0.5, cx = e[0] + Math.cos(a0 + dir * Math.PI / 2) * r, cy = e[1] + Math.sin(a0 + dir * Math.PI / 2) * r;
|
|
231
|
+
const sp = spiral(cx, cy, r, a0 - dir * Math.PI / 2, 1.2, dir, 16);
|
|
232
|
+
return { pts: dense(pts.concat(sp.slice(1)), 4), w };
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
function drawHair(c, locks) {
|
|
236
|
+
locks.forEach(({ pts, w }) => { const rb = ribbon(pts, q => 2.5 + (w - 2.5) * Math.pow(1 - q, 0.6));
|
|
237
|
+
c.fillStyle = '#e8bf55'; c.fill(rb); c.strokeStyle = C.line; c.lineWidth = 2.2; c.stroke(rb);
|
|
238
|
+
const mid = pts.slice(3, -6); c.fillStyle = '#f6dc8a'; c.fill(ribbon(mid, q => 1 + 2.4 * (1 - q)));
|
|
239
|
+
c.strokeStyle = '#b9862a'; c.lineWidth = 1.2; c.beginPath(); mid.forEach((p, i) => i ? c.lineTo(p[0] + 3, p[1] + 2) : c.moveTo(p[0] + 3, p[1] + 2)); c.stroke(); });
|
|
240
|
+
}
|
|
241
|
+
// 头顶外缘的小卷(让头发有体积,像设定稿那样蓬)
|
|
242
|
+
function crownCurls(c, G, t) {
|
|
243
|
+
const h = G.A.headC;
|
|
244
|
+
[[-20, -66, 15, 1], [22, -64, 17, -1], [58, -40, 18, 1], [80, -2, 17, -1], [88, 38, 16, 1], [70, 70, 15, -1]].forEach(([dx, dy, r, dir], k) => {
|
|
245
|
+
const cx = h[0] + dx + Math.sin(t * 6 + k) * 2, cy = h[1] + dy;
|
|
246
|
+
const sp = dense(spiral(cx, cy, r, k * 1.3 + t * 2 * dir, 1.15, dir, 18), 2), rb = ribbon(sp, q => 9 * (1 - q) + 2);
|
|
247
|
+
c.fillStyle = '#e8bf55'; c.fill(rb); c.strokeStyle = C.line; c.lineWidth = 1.8; c.stroke(rb);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// 花环:沿额头—头顶—后脑的弧线排 5 朵粉花
|
|
251
|
+
function wreath(c, G, t) {
|
|
252
|
+
const a = G.A, p0 = a.forehead, p1 = [a.headTop[0] + 6, a.headTop[1] - 14], p2 = [a.nape[0] + 14, a.nape[1] - 70];
|
|
253
|
+
const at = q => [(1 - q) * (1 - q) * p0[0] + 2 * q * (1 - q) * p1[0] + q * q * p2[0], (1 - q) * (1 - q) * p0[1] + 2 * q * (1 - q) * p1[1] + q * q * p2[1]];
|
|
254
|
+
c.strokeStyle = C.gold; c.lineWidth = 5; c.beginPath(); for (let i = 0; i <= 20; i++) { const p = at(i / 20); i ? c.lineTo(p[0], p[1] + 8) : c.moveTo(p[0], p[1] + 8); } c.stroke();
|
|
255
|
+
[0.22, 0.42, 0.6, 0.78, 0.95].forEach((q, k) => { const [x, y] = at(q), r = k % 2 ? 15 : 19, rot = t * 0.8 + k;
|
|
256
|
+
c.fillStyle = '#7fa86a'; c.strokeStyle = C.line; c.lineWidth = 1.8; [-1, 1].forEach(s => { c.beginPath(); c.ellipse(x + s * r * 0.9, y + r * 0.5, r * 0.6, r * 0.28, s * 0.6, 0, TAU); c.fill(); c.stroke(); });
|
|
257
|
+
c.save(); c.translate(x, y); c.rotate(rot); for (let i = 0; i < 5; i++) { c.rotate(TAU / 5); c.fillStyle = k % 2 ? '#e7a0aa' : '#f0b4bb'; c.beginPath(); c.ellipse(r * 0.55, 0, r * 0.55, r * 0.4, 0, 0, TAU); c.fill(); c.stroke(); }
|
|
258
|
+
c.fillStyle = '#e0b04a'; c.beginPath(); c.arc(0, 0, r * 0.3, 0, TAU); c.fill(); c.stroke(); c.restore(); });
|
|
259
|
+
}
|
|
260
|
+
// S 形装饰热气:奶白可变宽带+棕双线,顶端螺旋,整体随时间卷动
|
|
261
|
+
function steam(c, x, y, t, cup) {
|
|
262
|
+
const H0 = 360 * (1 - 0.25 * cup), lean = -90 - 160 * cup;
|
|
263
|
+
const defs = [[-6, 0, 1, 26], [22, 2.3, -1, 20], [-26, 4.2, 1, 14]];
|
|
264
|
+
defs.forEach(([ox, ph, dir, w], k) => {
|
|
265
|
+
const pts = []; for (let i = 0; i <= 10; i++) { const q = i / 10; pts.push([x + ox * (1 + 2.6 * q) + Math.sin(q * 4.0 + ph - t * 6) * (18 + 70 * q) * dir + lean * q * q, y - q * H0 * (1 - k * 0.12)]); }
|
|
266
|
+
const e = pts[pts.length - 1]; const sp = spiral(e[0] + dir * 32, e[1] + 4, 34, dir > 0 ? Math.PI : 0, 1.1, dir > 0 ? 1 : -1, 14).map(([px, py], i) => [px, py]);
|
|
267
|
+
const all = dense(pts.concat(sp.slice(1)), 5), rb = ribbon(all, q => 2 + (w - 2) * Math.sin(Math.PI * Math.min(1, 0.15 + q * 1.1)));
|
|
268
|
+
c.globalAlpha = 0.95; c.fillStyle = '#fbf7ec'; c.fill(rb); c.strokeStyle = '#8a6a4a'; c.lineWidth = 1.8; c.stroke(rb);
|
|
269
|
+
c.strokeStyle = 'rgba(160,130,100,.6)'; c.lineWidth = 1.2; c.beginPath(); all.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); c.stroke(); c.globalAlpha = 1;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const girlPal = { skin: '#f6dcc4', hair: '#e8bf55', hairLine: '#b9862a', dress: '#f4ecda', sleeve: '#f6dcc4', apron: '#e6aaa5', cuff: '#c9a24a', fold: '#c9b89a', shoe: '#c9a24a',
|
|
274
|
+
cheek: 'rgba(235,130,130,.45)', lip: '#c8505a', iris: '#4a6a3a', browC: '#9a6a2a', line: C.line, lw: 2.2, cupBody: '#f8f1e2', cupRim: '#7a4a24' };
|
|
275
|
+
const catPal = { orange: '#e39a4c', white: '#fbf3e3', stripe: '#b8622a', eye: '#e6c040', earInner: '#eaa0a0', line: C.line, lw: 2.2 };
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
draw(c, lt, t) {
|
|
279
|
+
const ch = P.choreo(lt, t);
|
|
280
|
+
c.drawImage(bg(), 0, 0);
|
|
281
|
+
windowGlass(c, lt, t);
|
|
282
|
+
flowers(c, t);
|
|
283
|
+
catHalo(c, 606, 640, t);
|
|
284
|
+
const G = RIG.girl({ cup: ch.cup, sip: ch.sip, hair: 'long', breathe: ch.breathe });
|
|
285
|
+
girlHalo(c, 1342, 332, t);
|
|
286
|
+
chair(c);
|
|
287
|
+
table(c);
|
|
288
|
+
// 碟(杯子举起后仍在桌上)
|
|
289
|
+
c.fillStyle = '#f6efe0'; c.strokeStyle = C.line; c.lineWidth = 2.5; c.beginPath(); c.ellipse(992, 634, 58, 9, 0, 0, TAU); c.fill(); c.stroke();
|
|
290
|
+
// —— 角色层 ——
|
|
291
|
+
const L = P.scratch('nvChars'), lg = L.getContext('2d'); lg.clearRect(0, 0, W, H);
|
|
292
|
+
const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
|
|
293
|
+
RIG.drawCat(lg, K, { ...catPal, lw: 2 });
|
|
294
|
+
hairMass(lg, G, t); drawHair(lg, hairLocks(G, t, 'back'));
|
|
295
|
+
RIG.drawGirl(lg, G, { ...girlPal, lw: 2 });
|
|
296
|
+
// 短袖:上臂靠肩一段盖奶白
|
|
297
|
+
lg.save(); lg.beginPath(); lg.arc(G.A.shoulder[0], G.A.shoulder[1], 62, 0, TAU); lg.clip(); lg.fillStyle = '#f4ecda'; lg.fill(G.upperArm); lg.strokeStyle = C.line; lg.lineWidth = 2; lg.stroke(G.upperArm); lg.restore();
|
|
298
|
+
// 粉色披帛:从肩头沿背垂到腰后
|
|
299
|
+
{ const a = G.A; const d = dense([[a.shoulder[0] + 6, a.shoulder[1] - 18], [a.back[0] - 4, a.back[1] - 30], [a.back[0] + 14, a.back[1] + 50], [a.waist[0] + 80, a.waist[1] + 40], [a.waist[0] + 96, a.waist[1] + 120]], 10);
|
|
300
|
+
const dr = ribbon(d, q => 30 + 26 * q); lg.fillStyle = '#e6aaa5'; lg.fill(dr); lg.strokeStyle = C.line; lg.lineWidth = 2; lg.stroke(dr); lg.fillStyle = '#d68f8c'; lg.fill(ribbon(d.slice(4), q => 4 + 6 * q)); }
|
|
301
|
+
// 粉色下摆的褶(深粉弧带)
|
|
302
|
+
lg.save(); lg.clip(G.apronSkirt); lg.fillStyle = '#d48e8c';
|
|
303
|
+
[[[1290, 640], [1250, 720], [1236, 830]], [[1250, 650], [1214, 740], [1206, 830]], [[1320, 680], [1296, 760], [1290, 830]]].forEach(pts => lg.fill(ribbon(dense(pts, 8), swellW(7, 1))));
|
|
304
|
+
lg.restore();
|
|
305
|
+
// 裙摆金色锯齿饰边
|
|
306
|
+
lg.save(); lg.clip(G.skirt); lg.lineJoin = 'miter';
|
|
307
|
+
{ const zz = (y0, w, col) => { lg.strokeStyle = col; lg.lineWidth = w; lg.beginPath(); for (let k = 0, x = 1080; x <= 1380; x += 28, k++) { const y = y0 + (x - 1080) * 0.02 + (k % 2 ? 30 : 0); k ? lg.lineTo(x, y) : lg.moveTo(x, y); } lg.stroke(); };
|
|
308
|
+
zz(850, 13, C.line); zz(850, 8, C.gold); lg.fillStyle = C.cream; for (let k = 0, x = 1080; x <= 1380; x += 56, k++) { lg.beginPath(); lg.arc(x, 850 + (x - 1080) * 0.02 - 2, 4, 0, TAU); lg.fill(); } }
|
|
309
|
+
lg.restore();
|
|
310
|
+
// 猫的弧形虎斑(可变线宽)
|
|
311
|
+
lg.fillStyle = '#c0672c';
|
|
312
|
+
[[[450, 735], [470, 728], [500, 738]], [[436, 772], [462, 764], [496, 776]], [[430, 812], [458, 806], [490, 818]], [[432, 850], [458, 846], [486, 858]], [[520, 700], [536, 690], [556, 694]], [[580, 572], [586, 586], [590, 600]], [[604, 566], [606, 582], [606, 598]], [[626, 570], [622, 584], [620, 598]]]
|
|
313
|
+
.forEach(pts => { const d = dense(pts, 8); lg.fill(ribbon(d, swellW(9, 1))); });
|
|
314
|
+
// 金腰带+胸针
|
|
315
|
+
lg.fillStyle = C.gold; lg.save(); lg.clip(G.torso); lg.fillRect(G.A.waist[0] - 140, G.A.waist[1] - 22, 200, 16); lg.restore();
|
|
316
|
+
lg.beginPath(); lg.arc(G.A.waist[0] + 50, G.A.waist[1] - 14, 13, 0, TAU); lg.fill();
|
|
317
|
+
// 粗外轮廓:剪影染深棕,往右下错位垫在下面
|
|
318
|
+
const S = P.scratch('nvSil'), sg = S.getContext('2d'); sg.clearRect(0, 0, W, H); sg.drawImage(L, 0, 0); sg.globalCompositeOperation = 'source-in'; sg.fillStyle = C.line; sg.fillRect(0, 0, W, H); sg.globalCompositeOperation = 'source-over';
|
|
319
|
+
[[-1.5, -1.5], [3.5, 4.5], [2, 2.5]].forEach(([dx, dy]) => c.drawImage(S, dx, dy));
|
|
320
|
+
c.drawImage(L, 0, 0);
|
|
321
|
+
// 金臂环、耳坠
|
|
322
|
+
c.strokeStyle = C.gold; c.lineWidth = 6; c.beginPath(); c.arc(G.A.shoulder[0] - 6, G.A.shoulder[1] + 40, 22, 0.6, 2.4); c.stroke();
|
|
323
|
+
c.fillStyle = C.gold; c.strokeStyle = C.line; c.lineWidth = 2; c.beginPath(); c.arc(G.A.ear[0] - 4, G.A.ear[1] + 22, 8, 0, TAU); c.fill(); c.stroke();
|
|
324
|
+
// 肩前的发卷、花环
|
|
325
|
+
drawHair(c, hairLocks(G, t, 'front'));
|
|
326
|
+
crownCurls(c, G, t);
|
|
327
|
+
wreath(c, G, t);
|
|
328
|
+
// 杯上的点饰
|
|
329
|
+
c.fillStyle = '#d07a86'; [-12, 0, 12].forEach((d, i) => { c.fillStyle = i === 1 ? '#6fa08a' : '#d07a86'; c.beginPath(); c.arc(G.cup.x + d, G.cup.y + 20, 3.5, 0, TAU); c.fill(); });
|
|
330
|
+
steam(c, G.cup.x, G.cup.y - 8, t, ch.cup);
|
|
331
|
+
c.drawImage(P.grain('nv', 0.05, [70, 50, 30], 0.14), 0, 0);
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
})();
|