@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,205 @@
|
|
|
1
|
+
// 1924 俄罗斯构成主义海报(罗德琴科 / 李西茨基)——纯代码。
|
|
2
|
+
// 管线:①缓存底版(米白旧纸+斜红地面+黑圆+窗内网点照片与舒霍夫塔+排版小构件)
|
|
3
|
+
// → ②会动的母题(红楔刺入黑圆、塔顶无线电波外扩、黑圆外虚线环旋转、标语字逐个盖章后波浪跳)
|
|
4
|
+
// → ③角色:平涂剪纸(红头巾共青团少女)+皮肤是 45° 网点照片(照片拼贴)+白色剪切边
|
|
5
|
+
SCENES['22_constructivism'] = (() => {
|
|
6
|
+
const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
|
|
7
|
+
const C = { paper: '#e9dfc4', red: '#c8231c', redD: '#8f1711', ink: '#151311', cream: '#f4ecd6', gray: '#4a4640', ochre: '#e3b03e', ochreD: '#a87a1c', orange: '#e2741c' };
|
|
8
|
+
const poly = U.poly;
|
|
9
|
+
|
|
10
|
+
// 网点:P.halftoneGray(灰度照片网点,照片拼贴的核心)、P.shadeDots(路径内按函数落点的网点阴影)
|
|
11
|
+
const halftone = P.halftoneGray, shadeDots = P.shadeDots;
|
|
12
|
+
|
|
13
|
+
// ---------- 几何西里尔字(5×7 模块,罗德琴科式手绘方块字) ----------
|
|
14
|
+
const GL = {
|
|
15
|
+
'П': [[0, 0, 5, 1.3], [0, 0, 1.3, 7], [3.7, 0, 1.3, 7]],
|
|
16
|
+
'Е': [[0, 0, 1.3, 7], [0, 0, 5, 1.3], [0, 2.9, 4, 1.2], [0, 5.7, 5, 1.3]],
|
|
17
|
+
'Т': [[0, 0, 5, 1.3], [1.85, 0, 1.3, 7]],
|
|
18
|
+
'Ч': [[0, 0, 1.3, 4], [3.7, 0, 1.3, 7], [0, 2.9, 5, 1.2]],
|
|
19
|
+
'А': [{ p: [[0, 7], [1.8, 0], [3.2, 0], [1.4, 7]] }, { p: [[5, 7], [3.2, 0], [1.8, 0], [3.6, 7]] }, [1.0, 4.3, 3.0, 1.15]],
|
|
20
|
+
'Й': [[0, 1.7, 1.3, 5.3], [3.7, 1.7, 1.3, 5.3], { p: [[1.3, 5.5], [3.7, 2.0], [3.7, 3.7], [1.3, 7]] }, [1.2, 0, 2.6, 1.0]], // 短音符收在字格内:伸出格外会被相邻字母/人物盖掉(第一版踩到)
|
|
21
|
+
'!': [[1.85, 0, 1.3, 4.8], [1.85, 5.7, 1.3, 1.3]],
|
|
22
|
+
};
|
|
23
|
+
function glyph(c, ch, x, y, u) {
|
|
24
|
+
const g = GL[ch]; if (!g) return;
|
|
25
|
+
// 逐部件各自 fill:同一 path 里反向绕行的两条斜腿会按 nonzero 规则互相抵消出洞(А 第一版踩到)
|
|
26
|
+
g.forEach(r => { c.beginPath(); if (r.p) { r.p.forEach((q, i) => i ? c.lineTo(x + q[0] * u, y + q[1] * u) : c.moveTo(x + q[0] * u, y + q[1] * u)); c.closePath(); } else c.rect(x + r[0] * u, y + r[1] * u, r[2] * u, r[3] * u); c.fill(); });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------- ① 底版 ----------
|
|
30
|
+
const TOWER = [[540, 170, 430, 108], [430, 100, 342, 70], [342, 66, 268, 47], [268, 44, 208, 32], [208, 30, 168, 22]];
|
|
31
|
+
const TX = 600;
|
|
32
|
+
const bg = () => P.cached('cs_bg', W, H, (g) => {
|
|
33
|
+
g.drawImage(P.texture('cs_paper', C.paper, { scale: 0.0035, amt: 16, grain: 18, seed: 5, dark: '#d9c9a4' }), 0, 0);
|
|
34
|
+
// 左侧排版构件:斜黑条、红方块、几行「正文」黑条
|
|
35
|
+
g.save(); g.translate(170, 610); g.rotate(-0.5); g.fillStyle = C.ink; g.fillRect(-260, -30, 560, 58); g.restore();
|
|
36
|
+
g.save(); g.translate(150, 200); g.rotate(0.26); g.fillStyle = C.red; g.fillRect(-70, -70, 140, 140); g.restore();
|
|
37
|
+
g.save(); g.translate(60, 330); g.rotate(-0.08); g.fillStyle = C.ink; [180, 150, 200, 120, 170].forEach((w, k) => g.fillRect(0, k * 26, w, 10)); g.restore();
|
|
38
|
+
// 右侧:细斜线+红圆点+竖排「ЧАЙ」
|
|
39
|
+
g.strokeStyle = C.ink; g.lineWidth = 6; g.beginPath(); g.moveTo(1520, 250); g.lineTo(1920, 760); g.stroke();
|
|
40
|
+
g.fillStyle = C.red; g.beginPath(); g.arc(1790, 340, 46, 0, TAU); g.fill();
|
|
41
|
+
g.fillStyle = C.ink; g.fillRect(1660, 430, 120, 400);
|
|
42
|
+
g.fillStyle = C.cream; ['Ч', 'А', 'Й'].forEach((ch, k) => glyph(g, ch, 1682, 470 + k * 120, 15.5));
|
|
43
|
+
// 大黑圆(人物头后)
|
|
44
|
+
g.fillStyle = C.ink; g.beginPath(); g.arc(1330, 395, 215, 0, TAU); g.fill();
|
|
45
|
+
// 红色斜地面
|
|
46
|
+
g.fillStyle = C.red; g.beginPath(); g.moveTo(0, 905); g.lineTo(W, 856); g.lineTo(W, H); g.lineTo(0, H); g.closePath(); g.fill();
|
|
47
|
+
g.fillStyle = C.ink; g.beginPath(); g.moveTo(0, 899); g.lineTo(W, 850); g.lineTo(W, 862); g.lineTo(0, 911); g.closePath(); g.fill();
|
|
48
|
+
// 窗:网点照片(天空+云)
|
|
49
|
+
const ph = P.canvas(), pg = ph.getContext('2d');
|
|
50
|
+
const sg = pg.createLinearGradient(0, 140, 0, 540); sg.addColorStop(0, '#5e5e5e'); sg.addColorStop(0.75, '#cfcfcf'); sg.addColorStop(1, '#e8e8e8');
|
|
51
|
+
pg.fillStyle = sg; pg.fillRect(370, 140, 420, 400);
|
|
52
|
+
[[460, 240, 90, 34], [700, 300, 110, 30], [520, 400, 140, 26]].forEach(([x, y, rx, ry]) => { const rg = pg.createRadialGradient(x, y, 4, x, y, rx); rg.addColorStop(0, 'rgba(250,250,250,.95)'); rg.addColorStop(1, 'rgba(250,250,250,0)'); pg.fillStyle = rg; pg.beginPath(); pg.ellipse(x, y, rx, ry * 2, 0, 0, TAU); pg.fill(); });
|
|
53
|
+
pg.fillStyle = '#2a2a2a'; pg.fillRect(370, 505, 420, 35); // 远处厂房屋脊(暗)
|
|
54
|
+
[[390, 470, 50], [470, 485, 36], [690, 460, 70], [760, 480, 30]].forEach(([x, y, w]) => pg.fillRect(x, y, w, 60));
|
|
55
|
+
pg.fillRect(712, 360, 18, 120); // 烟囱
|
|
56
|
+
g.fillStyle = C.cream; g.fillRect(370, 140, 420, 400);
|
|
57
|
+
halftone(g, ph, 370, 140, 420, 400, 7, C.ink, 0.8);
|
|
58
|
+
// 舒霍夫塔:五节双曲面,两组直母线
|
|
59
|
+
g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
|
|
60
|
+
g.strokeStyle = C.ink; g.lineCap = 'round';
|
|
61
|
+
TOWER.forEach(([y0, w0, y1, w1], k) => {
|
|
62
|
+
g.lineWidth = 2.6 - k * 0.25;
|
|
63
|
+
for (const dir of [1, -1]) for (let i = 0; i < 18; i++) {
|
|
64
|
+
const a = i / 18 * TAU, b = a + dir * 1.1;
|
|
65
|
+
const x0 = TX + Math.cos(a) * w0 / 2, x1 = TX + Math.cos(b) * w1 / 2;
|
|
66
|
+
if (Math.sin(a) < -0.2 && Math.sin(b) < -0.2) continue; // 背面的线省掉,读起来更清楚
|
|
67
|
+
g.beginPath(); g.moveTo(x0, y0 + Math.sin(a) * w0 * 0.08); g.lineTo(x1, y1 + Math.sin(b) * w1 * 0.08); g.stroke();
|
|
68
|
+
}
|
|
69
|
+
g.lineWidth = 4; g.beginPath(); g.ellipse(TX, y1, w1 / 2, w1 * 0.08, 0, 0, TAU); g.stroke();
|
|
70
|
+
});
|
|
71
|
+
g.lineWidth = 4; g.beginPath(); g.moveTo(TX, 168); g.lineTo(TX, 140); g.stroke();
|
|
72
|
+
g.restore();
|
|
73
|
+
// 窗框(粗黑)+红窗台
|
|
74
|
+
g.strokeStyle = C.ink; g.lineWidth = 26; g.strokeRect(357, 127, 446, 426);
|
|
75
|
+
g.fillStyle = C.red; g.fillRect(330, 554, 500, 30); g.fillStyle = C.ink; g.fillRect(330, 584, 500, 8);
|
|
76
|
+
// 桌:黑板面+红边+斜撑
|
|
77
|
+
g.fillStyle = C.ink; g.fillRect(810, 618, 400, 34); g.fillStyle = C.red; g.fillRect(810, 652, 400, 12);
|
|
78
|
+
g.fillStyle = C.ink; g.fillRect(842, 664, 24, 220); g.fillRect(1158, 664, 24, 220);
|
|
79
|
+
g.lineWidth = 14; g.strokeStyle = C.ink; g.beginPath(); g.moveTo(854, 870); g.lineTo(1170, 680); g.stroke();
|
|
80
|
+
// 凳:黑块
|
|
81
|
+
g.fillStyle = C.ink; g.fillRect(1440, 470, 34, 420); g.fillRect(1290, 740, 190, 26); g.fillRect(1300, 760, 22, 120);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---------- 少女 ----------
|
|
85
|
+
const girlPal = { skin: C.cream, hair: C.ochre, dress: '#2f2c29', sleeve: C.cream, cuff: C.red, line: C.ink, lw: 3 };
|
|
86
|
+
function girl(c, G, ch, lt) {
|
|
87
|
+
const tilt = G.tilt, nk = [1300, 425];
|
|
88
|
+
const HP = (x, y) => { const X = x - nk[0], Y = y - nk[1], cs = Math.cos(tilt), sn = Math.sin(tilt); return [nk[0] + X * cs - Y * sn, nk[1] + X * sn + Y * cs]; };
|
|
89
|
+
const L = C.ink;
|
|
90
|
+
const F = (p, col, lw = 3) => { if (!p) return; c.fillStyle = col; c.fill(p); if (lw) { c.strokeStyle = L; c.lineWidth = lw; c.stroke(p); } };
|
|
91
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
92
|
+
// 远侧袖(白衬衫)+手
|
|
93
|
+
F(G.farSleeve, C.cream); F(G.farCuff, C.red, 2);
|
|
94
|
+
// 裙:深灰平涂+网点阴影
|
|
95
|
+
F(G.skirt, '#34302c', 0);
|
|
96
|
+
shadeDots(c, G.skirt, 1090, 600, 380, 340, 9, '#0d0c0b', (x, y) => clamp((x - 1180) / 260) * 0.9);
|
|
97
|
+
c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.skirt);
|
|
98
|
+
F(G.shoe, L, 0);
|
|
99
|
+
// 上身:白衬衫+红领巾三角
|
|
100
|
+
F(G.torso, C.cream);
|
|
101
|
+
shadeDots(c, G.torso, 1270, 420, 170, 210, 8, '#7d766a', (x, y) => clamp((x - 1350) / 80));
|
|
102
|
+
const nkA = G.A.neck;
|
|
103
|
+
c.fillStyle = C.red; c.beginPath(); c.moveTo(nkA[0] - 18, nkA[1] - 2); c.lineTo(nkA[0] + 26, nkA[1] + 2); c.lineTo(nkA[0] - 2, nkA[1] + 62); c.closePath(); c.fill();
|
|
104
|
+
// 头发(金)+红头巾
|
|
105
|
+
F(G.hairBack, C.ochre, 0); F(G.bangs, C.ochre, 0);
|
|
106
|
+
c.strokeStyle = C.ochreD; c.lineWidth = 3; G.hairLines.forEach(h => c.stroke(h));
|
|
107
|
+
// 皮肤:先画灰度「照片」到离屏,再网点化 → 照片拼贴
|
|
108
|
+
const S = P.scratch('csSkin'), sg = S.getContext('2d', { willReadFrequently: true });
|
|
109
|
+
sg.reset(); sg.clearRect(1100, 270, 300, 400);
|
|
110
|
+
const shade = (p, x0, x1, a, b) => { const gr = sg.createLinearGradient(x0, 0, x1, 0); gr.addColorStop(0, a); gr.addColorStop(1, b); sg.fillStyle = gr; sg.fill(p); };
|
|
111
|
+
shade(G.neck, 1285, 1325, '#b4b4b4', '#767676');
|
|
112
|
+
shade(G.face, 1250, 1330, '#fafafa', '#9c9c9c');
|
|
113
|
+
sg.fillStyle = 'rgba(90,90,90,.28)'; sg.beginPath(); sg.ellipse(G.cheek[0] + 16, G.cheek[1] + 4, 16, 12, 0, 0, TAU); sg.fill(); // 颧下阴影(轻)
|
|
114
|
+
shade(G.hand, G.A.hand[0] - 20, G.A.hand[0] + 20, '#d8d8d8', '#8a8a8a'); shade(G.farHand, 1146, 1218, '#f4f4f4', '#b0b0b0');
|
|
115
|
+
// 白色剪切边 + 网点
|
|
116
|
+
c.save(); c.strokeStyle = '#fbf7ea'; c.lineWidth = 7; [G.neck, G.face, G.farHand].forEach(p => c.stroke(p)); c.restore();
|
|
117
|
+
[G.neck, G.face, G.farHand].forEach(p => { c.fillStyle = C.cream; c.fill(p); });
|
|
118
|
+
halftone(c, S, 1135, 280, 220, 360, 4.6, C.ink, 0.74);
|
|
119
|
+
// 五官(照片上手工补的清晰笔画)+红唇
|
|
120
|
+
if (ch.blink) { c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.lid); }
|
|
121
|
+
else { c.fillStyle = L; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1, 5, 6.5, 0, 0, TAU); c.fill(); c.strokeStyle = L; c.lineWidth = 3; c.stroke(G.lid); G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.stroke(); }); }
|
|
122
|
+
c.lineWidth = 3.4; c.stroke(G.brow);
|
|
123
|
+
c.fillStyle = C.red; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
|
|
124
|
+
// 头巾:盖住头顶与后脑,额前露金发;颈后打结两条尾
|
|
125
|
+
const kf = poly([[1280, 296], [1300, 285], [1330, 280], [1368, 291], [1402, 321], [1415, 368], [1405, 414], [1382, 440], [1356, 442], [1350, 410], [1342, 372], [1326, 334], [1304, 309]].map(p => HP(...p)));
|
|
126
|
+
const lock = poly([[1352, 425], [1372, 432], [1380, 470], [1372, 505], [1360, 470]].map(p => HP(...p)));
|
|
127
|
+
F(lock, C.ochre, 0); c.strokeStyle = C.ochreD; c.lineWidth = 2.5; c.stroke(lock);
|
|
128
|
+
F(kf, C.red, 3);
|
|
129
|
+
c.strokeStyle = C.redD; c.lineWidth = 3; c.beginPath(); [[1300, 300, 1390, 360], [1330, 292, 1400, 330]].forEach(([a, b, x, y]) => { const p0 = HP(a, b), p1 = HP(x, y); c.moveTo(...p0); c.lineTo(...p1); }); c.stroke();
|
|
130
|
+
const sw = Math.sin(lt * 9) * 6; // 头巾尾随动作轻摆
|
|
131
|
+
[[[1384, 432], [1446, 470 + sw], [1416, 478 + sw]], [[1378, 438], [1428, 500 - sw], [1400, 498 - sw]]].forEach(tri => F(poly(tri.map(p => HP(...p))), C.red, 3));
|
|
132
|
+
// 近侧手臂(白袖+红袖口)、杯、手
|
|
133
|
+
F(G.upperArm, C.cream); F(G.foreArm, C.cream); F(G.cuff, C.red, 2);
|
|
134
|
+
sg.reset(); sg.clearRect(1100, 270, 300, 400); shade(G.hand, G.A.hand[0] - 18, G.A.hand[0] + 18, '#dcdcdc', '#7a7a7a');
|
|
135
|
+
c.save(); c.strokeStyle = '#fbf7ea'; c.lineWidth = 7; c.stroke(G.hand); c.restore(); c.fillStyle = C.cream; c.fill(G.hand);
|
|
136
|
+
halftone(c, S, G.A.hand[0] - 26, G.A.hand[1] - 26, 52, 52, 4.6, C.ink, 0.74);
|
|
137
|
+
RIG.drawCup(c, G.cup, { body: C.cream, rim: C.ink, line: L, lw: 3, hw: 7 });
|
|
138
|
+
c.save(); c.translate(G.cup.x, G.cup.y); c.rotate(G.cup.tilt); c.fillStyle = C.red; c.fillRect(-G.cup.w * 0.46, G.cup.h * 0.38, G.cup.w * 0.92, 9); c.restore();
|
|
139
|
+
c.fillStyle = C.cream; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill(); c.strokeStyle = L; c.lineWidth = 2.4; c.stroke();
|
|
140
|
+
c.restore();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------- 猫:剪纸平涂+网点阴影+粗黑条纹 ----------
|
|
144
|
+
function cat(c, K) {
|
|
145
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'butt';
|
|
146
|
+
c.strokeStyle = C.ink; c.lineWidth = K.tailW + 8; c.stroke(K.tail); c.strokeStyle = C.orange; c.lineWidth = K.tailW; c.stroke(K.tail);
|
|
147
|
+
c.setLineDash([12, 14]); c.strokeStyle = C.ink; c.lineWidth = K.tailW * 0.9; c.stroke(K.tail); c.setLineDash([]);
|
|
148
|
+
const tip = K.tailPts[K.tailPts.length - 1]; c.fillStyle = C.cream; c.beginPath(); c.arc(tip[0], tip[1], K.tailW / 2, 0, TAU); c.fill();
|
|
149
|
+
c.fillStyle = C.orange; c.fill(K.body); shadeDots(c, K.body, 420, 660, 250, 250, 9, '#7a2f0c', (x, y) => clamp((x - 520) / 140) * 0.8 + clamp((y - 820) / 90) * 0.3);
|
|
150
|
+
c.strokeStyle = C.ink; c.lineWidth = 4; c.stroke(K.body);
|
|
151
|
+
K.legs.forEach(l => { c.fillStyle = C.cream; c.fill(l); c.stroke(l); });
|
|
152
|
+
c.fillStyle = C.cream; c.fill(K.white);
|
|
153
|
+
c.fillStyle = C.orange; c.fill(K.head); c.stroke(K.head);
|
|
154
|
+
c.save(); c.clip(K.head); c.fillStyle = C.cream; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
|
|
155
|
+
c.fillStyle = C.red; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); });
|
|
156
|
+
c.strokeStyle = C.ink; c.lineWidth = 10; K.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); });
|
|
157
|
+
K.eyes.forEach(e => { if (K.blink) { c.lineWidth = 4; c.beginPath(); c.moveTo(e.x - 9, e.y); c.lineTo(e.x + 9, e.y); c.stroke(); }
|
|
158
|
+
else { c.fillStyle = C.cream; c.beginPath(); c.arc(e.x, e.y, 10, 0, TAU); c.fill(); c.lineWidth = 3; c.stroke(); c.fillStyle = C.ink; c.fillRect(e.x - 1, e.y - 8, 5, 16); } });
|
|
159
|
+
c.fillStyle = C.red; c.beginPath(); c.moveTo(K.nose[0] - 7, K.nose[1] - 4); c.lineTo(K.nose[0] + 7, K.nose[1] - 4); c.lineTo(K.nose[0], K.nose[1] + 5); c.fill();
|
|
160
|
+
c.lineWidth = 2; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
|
|
161
|
+
c.restore();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const SLOGAN = 'ПЕЙТЕ ЧАЙ!';
|
|
165
|
+
return {
|
|
166
|
+
draw(c, lt, t) {
|
|
167
|
+
const ch = P.choreo(lt, t);
|
|
168
|
+
c.drawImage(bg(), 0, 0);
|
|
169
|
+
// 黑圆外的红虚线环(旋转)
|
|
170
|
+
c.save(); c.strokeStyle = C.red; c.lineWidth = 12; c.setLineDash([46, 22]); c.lineDashOffset = -lt * 260;
|
|
171
|
+
c.beginPath(); c.arc(1330, 395, 242, 0, TAU); c.stroke(); c.restore();
|
|
172
|
+
// 李西茨基红楔:沿轴线刺入黑圆(1.6Hz 进退)
|
|
173
|
+
const thrust = 70 * (0.5 - 0.5 * Math.cos(lt * TAU * 1.6)), ax = Math.cos(0.34), ay = Math.sin(0.34);
|
|
174
|
+
const tipX = 1150 + ax * thrust, tipY = 360 + ay * thrust;
|
|
175
|
+
c.fillStyle = C.red; c.beginPath(); c.moveTo(tipX, tipY); c.lineTo(tipX - ax * 330 - ay * 95, tipY - ay * 330 + ax * 95); c.lineTo(tipX - ax * 330 + ay * 95, tipY - ay * 330 - ax * 95); c.closePath(); c.fill();
|
|
176
|
+
// 窗内:塔顶无线电波(红弧外扩)
|
|
177
|
+
c.save(); c.beginPath(); c.rect(370, 140, 420, 400); c.clip(); c.strokeStyle = C.red; c.lineCap = 'butt';
|
|
178
|
+
for (let k = 0; k < 4; k++) { const ph = (lt * 1.4 + k / 4) % 1, r = 18 + ph * 230; c.globalAlpha = 1 - ph; c.lineWidth = 10 - ph * 6;
|
|
179
|
+
c.beginPath(); c.arc(TX, 150, r, Math.PI * 0.05, Math.PI * 0.95, true); c.stroke(); }
|
|
180
|
+
c.restore();
|
|
181
|
+
// 角色
|
|
182
|
+
const K = RIG.cat({ tail: ch.tail, blink: ch.blink, breathe: ch.breathe });
|
|
183
|
+
cat(c, K);
|
|
184
|
+
const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe });
|
|
185
|
+
girl(c, G, ch, lt);
|
|
186
|
+
// 地面标语(画在角色之上:海报字压人物):逐字盖章(0–0.5s)→ 之后波浪跳
|
|
187
|
+
c.save(); c.translate(0, 0); const ang = Math.atan2(-49, W); c.rotate(ang);
|
|
188
|
+
const u = 19, adv = 6.3 * u; let x = 350;
|
|
189
|
+
[...SLOGAN].forEach((chh, k) => {
|
|
190
|
+
if (chh === ' ') { x += 3.5 * u; return; }
|
|
191
|
+
const q = clamp((lt - k * 0.045) / 0.12); if (q <= 0) { x += adv; return; }
|
|
192
|
+
const s = 1 + 0.5 * (1 - ease.out(q)), hop = -10 * Math.max(0, Math.sin(lt * 11 - k * 0.8));
|
|
193
|
+
c.save(); c.translate(x + 2.5 * u, 990 + hop); c.scale(s, s); c.globalAlpha = q;
|
|
194
|
+
c.fillStyle = k % 4 === 3 ? C.ink : C.cream; glyph(c, chh, -2.5 * u, -3.5 * u, u); c.restore();
|
|
195
|
+
x += adv;
|
|
196
|
+
});
|
|
197
|
+
c.restore();
|
|
198
|
+
// 杯上热气:三条黑折线上行
|
|
199
|
+
c.save(); c.strokeStyle = C.ink; c.lineWidth = 4; c.lineJoin = 'miter';
|
|
200
|
+
for (let k = 0; k < 3; k++) { const ph = (lt * 2 + k / 3) % 1, x0 = G.cup.x - 14 + k * 14, y0 = G.cup.y - 12 - ph * 60; c.globalAlpha = 1 - ph;
|
|
201
|
+
c.beginPath(); c.moveTo(x0, y0); c.lineTo(x0 + 7, y0 - 9); c.lineTo(x0, y0 - 18); c.lineTo(x0 + 7, y0 - 27); c.stroke(); }
|
|
202
|
+
c.restore();
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
})();
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// 1931 达利超现实主义(《记忆的永恒》)——纯代码。
|
|
2
|
+
// 管线:①缓存底版(群青→淡黄天空、玻璃般静海、克雷乌斯角金色礁岩、赭色荒原、开窗的断墙残片、石块桌)
|
|
3
|
+
// → ②长影子:每个物体的剪影用一个剪切仿射投到地面(kx 随时间增大=影子缓慢变长),先进离屏再整体半透明叠,避免重叠加深
|
|
4
|
+
// → ③软钟:圆盘 (u,v) → 屏幕的折叠映射,越过边缘的部分垂下并随时间继续下垂;刻度与指针走同一映射
|
|
5
|
+
// → ④会动的母题(软钟下垂+指针转、高跷象在地平线上迈步、蚂蚁绕怀表爬、影子变长)
|
|
6
|
+
// → ⑤角色:无勾线学院派体积(线性渐变+clip 内模糊描边做内阴影)+细五官
|
|
7
|
+
SCENES['23_dali'] = (() => {
|
|
8
|
+
const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
|
|
9
|
+
const HZ = 560, GROUND = 592; // 海平线、岸线
|
|
10
|
+
const C = { sky0: '#22407c', sky1: '#5f88bd', sky2: '#e9dcae', sea: '#6f8fa6', ground0: '#d2ad70', ground1: '#7a5129', shadow: '#2a1a10',
|
|
11
|
+
wall: '#e7dcc3', wallD: '#a99a7c', wood: '#7a4a24', woodL: '#a8703a', block: '#8a5a32', blockL: '#b98754', gold: '#d9b25a', dial: '#efe6c8', orange: '#d8621f' };
|
|
12
|
+
|
|
13
|
+
const vol = P.vol; // 体积填色:线性渐变+路径内模糊暗边(学院派,无勾线)
|
|
14
|
+
|
|
15
|
+
// ---------- ① 底版 ----------
|
|
16
|
+
const bg = () => P.cached('dali_bg', W, H, (g) => {
|
|
17
|
+
const sg = g.createLinearGradient(0, 0, 0, HZ); sg.addColorStop(0, C.sky0); sg.addColorStop(0.55, C.sky1); sg.addColorStop(1, C.sky2);
|
|
18
|
+
g.fillStyle = sg; g.fillRect(0, 0, W, HZ + 2);
|
|
19
|
+
// 细长粉云
|
|
20
|
+
[[200, 300, 260, 7], [700, 250, 340, 6], [1280, 330, 300, 8], [900, 420, 420, 5]].forEach(([x, y, w, h]) => { const rg = g.createLinearGradient(x - w, 0, x + w, 0); rg.addColorStop(0, 'rgba(250,215,200,0)'); rg.addColorStop(0.5, 'rgba(250,215,200,.55)'); rg.addColorStop(1, 'rgba(250,215,200,0)'); g.fillStyle = rg; g.beginPath(); g.ellipse(x, y, w, h, 0, 0, TAU); g.fill(); });
|
|
21
|
+
// 静海(玻璃般,几道高光线)
|
|
22
|
+
const mg = g.createLinearGradient(0, HZ, 0, GROUND); mg.addColorStop(0, '#8aa6b8'); mg.addColorStop(1, '#4f6f86'); g.fillStyle = mg; g.fillRect(0, HZ, W, GROUND - HZ);
|
|
23
|
+
g.strokeStyle = 'rgba(240,235,210,.6)'; g.lineWidth = 1.5; [568, 574, 583].forEach((y, k) => { g.beginPath(); g.moveTo(80 + k * 140, y); g.lineTo(700 + k * 200, y); g.stroke(); });
|
|
24
|
+
// 克雷乌斯角礁岩(右侧,金色,光滑高光)
|
|
25
|
+
const cliff = new Path2D(); cliff.moveTo(1380, GROUND); [[1420, 540], [1470, 500], [1520, 470], [1590, 460], [1640, 430], [1700, 440], [1760, 410], [1830, 430], [1920, 420], [1920, GROUND]].forEach(p => cliff.lineTo(...p)); cliff.closePath();
|
|
26
|
+
vol(g, cliff, 1400, 420, 1500, 600, '#e8c27a', '#9a6a34', 'rgba(70,40,15,.5)', 18);
|
|
27
|
+
g.strokeStyle = 'rgba(255,240,200,.7)'; g.lineWidth = 3; g.beginPath(); g.moveTo(1470, 505); g.quadraticCurveTo(1530, 480, 1590, 470); g.moveTo(1650, 440); g.quadraticCurveTo(1700, 445, 1760, 418); g.stroke();
|
|
28
|
+
// 荒原:近暗远亮,几道细地层线
|
|
29
|
+
const gg = g.createLinearGradient(0, GROUND, 0, H); gg.addColorStop(0, C.ground0); gg.addColorStop(0.45, '#a87a44'); gg.addColorStop(1, C.ground1); g.fillStyle = gg; g.fillRect(0, GROUND, W, H - GROUND);
|
|
30
|
+
g.strokeStyle = 'rgba(90,55,25,.25)'; g.lineWidth = 2; for (let k = 0; k < 9; k++) { const y = GROUND + Math.pow(k / 9, 1.6) * 480 + 10; g.beginPath(); g.moveTo(0, y); g.bezierCurveTo(600, y - 6, 1300, y + 8, W, y - 2); g.stroke(); }
|
|
31
|
+
// 画布纹+暖色上光油
|
|
32
|
+
g.fillStyle = 'rgba(255,200,120,.06)'; g.fillRect(0, 0, W, H);
|
|
33
|
+
g.drawImage(P.grain('dali', 0.05, [70, 45, 20], 0.10), 0, 0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const wallLayer = () => P.cached('dali_wall', W, H, (g) => {
|
|
37
|
+
// 断墙残片(开窗,立在荒原上,基线 y=700)
|
|
38
|
+
const wall = new Path2D(); wall.moveTo(300, 700); [[300, 120], [330, 96], [372, 108], [410, 78], [470, 92], [540, 70], [610, 88], [680, 64], [760, 90], [820, 80], [858, 104], [860, 700]].forEach(p => wall.lineTo(...p)); wall.closePath();
|
|
39
|
+
wall.rect(370, 140, 420, 400); // 窗洞(evenodd 挖空)
|
|
40
|
+
g.save(); const wg = g.createLinearGradient(300, 0, 860, 0); wg.addColorStop(0, '#f4ead2'); wg.addColorStop(1, '#cdbf9f'); g.fillStyle = wg; g.fill(wall, 'evenodd'); g.restore();
|
|
41
|
+
g.fillStyle = '#9d8c6c'; g.beginPath(); g.moveTo(860, 104); g.lineTo(892, 120); g.lineTo(892, 712); g.lineTo(860, 700); g.closePath(); g.fill(); // 墙侧面
|
|
42
|
+
g.strokeStyle = 'rgba(110,90,60,.55)'; g.lineWidth = 1.6; [[[330, 300], [352, 340], [344, 390], [360, 430]], [[820, 600], [800, 640], [812, 690]], [[640, 92], [652, 118], [646, 136]]].forEach(cr => { g.beginPath(); cr.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.stroke(); });
|
|
43
|
+
// 窗内:同一片风景(更亮更远)+窗框
|
|
44
|
+
g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
|
|
45
|
+
const ig = g.createLinearGradient(0, 140, 0, 540); ig.addColorStop(0, '#3d64a2'); ig.addColorStop(0.85, '#f0e1b4'); ig.addColorStop(1, '#f6ecc8'); g.fillStyle = ig; g.fillRect(370, 140, 420, 400);
|
|
46
|
+
g.fillStyle = '#7f9db3'; g.fillRect(370, 505, 420, 35); g.strokeStyle = 'rgba(250,245,220,.7)'; g.lineWidth = 1.4; g.beginPath(); g.moveTo(400, 515); g.lineTo(600, 515); g.moveTo(520, 528); g.lineTo(760, 528); g.stroke();
|
|
47
|
+
// 窗里漂浮的一只蛋(达利母题)
|
|
48
|
+
const eg = g.createRadialGradient(560, 300, 6, 580, 330, 70); eg.addColorStop(0, '#fffaf0'); eg.addColorStop(1, '#bfae8e'); g.fillStyle = eg; g.beginPath(); g.ellipse(580, 330, 46, 60, 0, 0, TAU); g.fill();
|
|
49
|
+
g.restore();
|
|
50
|
+
g.lineWidth = 18; g.strokeStyle = C.wood; g.strokeRect(361, 131, 438, 418); g.lineWidth = 4; g.strokeStyle = C.woodL; g.strokeRect(356, 126, 448, 428);
|
|
51
|
+
g.fillStyle = C.woodL; g.fillRect(340, 548, 480, 18); g.fillStyle = C.wood; g.fillRect(340, 566, 480, 12);
|
|
52
|
+
// 枯橄榄枝:从墙头右上角伸出(挂软钟用)
|
|
53
|
+
g.strokeStyle = '#4a3020'; g.lineCap = 'round'; g.lineWidth = 12; g.beginPath(); g.moveTo(850, 112); g.bezierCurveTo(900, 90, 960, 104, 1040, 96); g.stroke();
|
|
54
|
+
g.lineWidth = 6; g.beginPath(); g.moveTo(960, 100); g.quadraticCurveTo(990, 70, 1000, 46); g.moveTo(1010, 98); g.lineTo(1070, 112); g.stroke();
|
|
55
|
+
});
|
|
56
|
+
const blockLayer = () => P.cached('dali_block', W, H, (g) => {
|
|
57
|
+
// 石块桌:顶面+正面(光从左来)
|
|
58
|
+
g.fillStyle = C.blockL; g.beginPath(); g.moveTo(810, 620); g.lineTo(1205, 620); g.lineTo(1190, 600); g.lineTo(826, 600); g.closePath(); g.fill();
|
|
59
|
+
const bk = new Path2D(); bk.rect(810, 620, 395, 285); vol(g, bk, 810, 0, 1205, 0, '#b07a46', '#6e4422', 'rgba(40,20,8,.5)', 22);
|
|
60
|
+
// 少女的凳子(深色木块)
|
|
61
|
+
const st = new Path2D(); st.rect(1300, 742, 190, 163); vol(g, st, 1300, 0, 1490, 0, '#8a5a32', '#4a2c14', 'rgba(30,15,5,.5)', 16);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ---------- ③ 软钟:圆盘局部坐标 (u,v)∈半径 R → 屏幕;v 越过折线 fold 的部分垂下 ----------
|
|
65
|
+
function softMap(o, sag) {
|
|
66
|
+
const { cx, cy, R, fold, top = 0.32, flare = -0.12, drip = 0.18 } = o; // flare<0:垂下部分像软布一样往外摊开,下缘圆而不是尖
|
|
67
|
+
return (u, v) => {
|
|
68
|
+
if (v <= fold) return [cx + u, cy + v * top + Math.sin(u * 0.05) * 2];
|
|
69
|
+
const d = v - fold, q = d / R;
|
|
70
|
+
return [cx + u * (1 - flare * q) + Math.sin(q * 3) * 5 * sag, cy + fold * top + d * (0.62 + 0.3 * sag) + sag * R * drip * Math.exp(-(((u + R * 0.2) / (R * 0.6)) ** 2)) * q * q + Math.sin(u * 0.06 + 1) * 6 * q];
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function softClock(c, o, sag, t, handSpeed) {
|
|
74
|
+
const m = softMap(o, sag), R = o.R;
|
|
75
|
+
const ring = (r) => { const pts = []; for (let i = 0; i <= 120; i++) { const a = i / 120 * TAU; pts.push(m(Math.cos(a) * r, Math.sin(a) * r)); } return pts; };
|
|
76
|
+
const path = pts => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); p.closePath(); return p; };
|
|
77
|
+
const outer = path(ring(R)), inner = path(ring(R * 0.86));
|
|
78
|
+
// 投影(贴着表面的软影)
|
|
79
|
+
c.save(); c.translate(-8, 10); c.fillStyle = 'rgba(30,15,5,.35)'; c.filter = 'blur(6px)'; c.fill(outer); c.restore();
|
|
80
|
+
vol(c, outer, o.cx - R, 0, o.cx + R, 0, '#f0d27c', '#9a7426', 'rgba(70,45,10,.5)', 8);
|
|
81
|
+
const fg = c.createLinearGradient(0, o.cy - R * 0.3, 0, o.cy + R * 1.4); fg.addColorStop(0, '#f6efd6'); fg.addColorStop(0.45, '#e8dcb6'); fg.addColorStop(1, '#bfae84');
|
|
82
|
+
c.fillStyle = fg; c.fill(inner);
|
|
83
|
+
// 刻度与罗马数字的「点」:走同一映射
|
|
84
|
+
c.fillStyle = '#3a2a18';
|
|
85
|
+
for (let k = 0; k < 12; k++) { const a = k / 12 * TAU - Math.PI / 2, p0 = m(Math.cos(a) * R * 0.74, Math.sin(a) * R * 0.74), p1 = m(Math.cos(a) * R * 0.8, Math.sin(a) * R * 0.8);
|
|
86
|
+
c.strokeStyle = '#3a2a18'; c.lineWidth = k % 3 ? 2 : 4; c.beginPath(); c.moveTo(...p0); c.lineTo(...p1); c.stroke(); }
|
|
87
|
+
// 指针(沿映射采样成软线)
|
|
88
|
+
const hand = (ang, len, w) => { c.lineWidth = w; c.lineCap = 'round'; c.beginPath(); for (let i = 0; i <= 12; i++) { const r = len * i / 12, q = m(Math.cos(ang) * r, Math.sin(ang) * r); i ? c.lineTo(...q) : c.moveTo(...q); } c.stroke(); };
|
|
89
|
+
c.strokeStyle = '#2a1c10'; hand(t * handSpeed - 1.2, R * 0.66, 4); hand(t * handSpeed / 12 + 0.6, R * 0.46, 6);
|
|
90
|
+
const cc = m(0, 0); c.fillStyle = '#2a1c10'; c.beginPath(); c.arc(cc[0], cc[1], 4, 0, TAU); c.fill();
|
|
91
|
+
// 折线上的高光(柔软的「棱」)
|
|
92
|
+
c.strokeStyle = 'rgba(255,250,225,.55)'; c.lineWidth = 3; c.beginPath(); for (let i = 0; i <= 30; i++) { const u = -R * 0.9 + i / 30 * R * 1.8, q = m(u, o.fold + 2); i ? c.lineTo(...q) : c.moveTo(...q); } c.stroke();
|
|
93
|
+
return outer;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------- 高跷象 ----------
|
|
97
|
+
function elephant(c, x, lt) {
|
|
98
|
+
const y = 400, legH = GROUND - 2 - y;
|
|
99
|
+
c.save(); c.fillStyle = '#5a4a46'; c.strokeStyle = '#5a4a46'; c.lineCap = 'round';
|
|
100
|
+
// 四条细长腿:交替迈步(关节处微弯)
|
|
101
|
+
[[-26, 0], [-12, Math.PI], [18, Math.PI * 0.5], [30, Math.PI * 1.5]].forEach(([ox, ph]) => {
|
|
102
|
+
const sw = Math.sin(lt * 7 + ph), fx = x + ox + sw * 10, kx = x + ox + sw * 4 + 3, ky = y + legH * 0.52 - Math.max(0, sw) * 6;
|
|
103
|
+
c.lineWidth = 2.6; c.beginPath(); c.moveTo(x + ox, y + 8); c.lineTo(kx, ky); c.lineTo(fx, GROUND - 2 - Math.max(0, sw) * 5); c.stroke();
|
|
104
|
+
});
|
|
105
|
+
const bob = Math.sin(lt * 14) * 1.5;
|
|
106
|
+
c.beginPath(); c.ellipse(x, y + bob, 54, 28, 0, 0, TAU); c.fill();
|
|
107
|
+
c.beginPath(); c.ellipse(x - 44, y - 8 + bob, 18, 16, 0, 0, TAU); c.fill(); // 头(朝左)
|
|
108
|
+
c.lineWidth = 6; c.beginPath(); c.moveTo(x - 56, y - 2 + bob); c.quadraticCurveTo(x - 72, y + 30, x - 64 + Math.sin(lt * 5) * 6, y + 52); c.stroke(); // 象鼻
|
|
109
|
+
// 背上的方尖碑与鞍布
|
|
110
|
+
c.fillStyle = '#c8402c'; c.fillRect(x - 22, y - 26 + bob, 40, 8);
|
|
111
|
+
c.fillStyle = '#e6d6b0'; c.beginPath(); c.moveTo(x - 10, y - 26 + bob); c.lineTo(x + 6, y - 26 + bob); c.lineTo(x - 2, y - 96 + bob); c.closePath(); c.fill();
|
|
112
|
+
c.fillStyle = 'rgba(255,235,200,.35)'; c.beginPath(); c.ellipse(x - 14, y - 10 + bob, 26, 6, -0.2, 0, TAU); c.fill(); // 左上受光
|
|
113
|
+
c.restore();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------- 蚂蚁怀表 ----------
|
|
117
|
+
function antWatch(c, lt) {
|
|
118
|
+
const x = 1030, y = 606;
|
|
119
|
+
const wg = c.createRadialGradient(x - 10, y - 8, 3, x, y, 40); wg.addColorStop(0, '#f39a4a'); wg.addColorStop(1, '#9a3a10');
|
|
120
|
+
c.fillStyle = 'rgba(30,15,5,.35)'; c.beginPath(); c.ellipse(x + 10, y + 8, 40, 11, 0, 0, TAU); c.fill();
|
|
121
|
+
c.fillStyle = wg; c.beginPath(); c.ellipse(x, y, 38, 13, 0, 0, TAU); c.fill();
|
|
122
|
+
c.fillStyle = '#c9a24a'; c.beginPath(); c.ellipse(x + 36, y - 4, 7, 5, 0, 0, TAU); c.fill();
|
|
123
|
+
c.fillStyle = '#120a06';
|
|
124
|
+
for (let k = 0; k < 9; k++) { const a = lt * (1.6 + (k % 3) * 0.5) * (k % 2 ? 1 : -1) + k * 0.7, r = 0.35 + (k % 4) * 0.14;
|
|
125
|
+
const ax = x + Math.cos(a) * 32 * r, ay = y + Math.sin(a) * 10 * r, d = a + (k % 2 ? Math.PI / 2 : -Math.PI / 2);
|
|
126
|
+
for (let s = -1; s <= 1; s++) { c.beginPath(); c.ellipse(ax + Math.cos(d) * s * 3.2, ay + Math.sin(d) * s * 1.2, 2, 1.4, d, 0, TAU); c.fill(); } }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------- 角色 ----------
|
|
130
|
+
function girl(c, G, ch) {
|
|
131
|
+
c.save();
|
|
132
|
+
const sk = ['#f6dcc6', '#d9a888'], dress = ['#c9d9e6', '#6e8aa6'], hair = ['#f4d98a', '#b98a34'];
|
|
133
|
+
vol(c, G.farSleeve, 1200, 560, 1340, 600, dress[0], dress[1]); vol(c, G.farHand, 1150, 0, 1218, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
|
|
134
|
+
vol(c, G.skirt, 1100, 650, 1450, 900, '#dce8f0', dress[1], 'rgba(40,50,70,.45)', 26);
|
|
135
|
+
c.save(); c.clip(G.skirt); c.strokeStyle = 'rgba(60,80,110,.35)'; c.lineWidth = 10; c.filter = 'blur(5px)'; G.folds.forEach(f => c.stroke(f)); c.restore();
|
|
136
|
+
vol(c, G.shoe, 1100, 0, 1150, 0, '#6a3a22', '#2a140a', null);
|
|
137
|
+
vol(c, G.torso, 1290, 440, 1420, 600, dress[0], dress[1], 'rgba(40,50,70,.45)', 18);
|
|
138
|
+
// 白色小翻领
|
|
139
|
+
c.fillStyle = '#fbf6ea'; c.beginPath(); const nk = G.A.neck; c.ellipse(nk[0] + 4, nk[1] + 6, 30, 12, 0.1, 0, TAU); c.fill();
|
|
140
|
+
vol(c, G.neck, 1286, 0, 1324, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
|
|
141
|
+
vol(c, G.hairBack, 1270, 300, 1400, 440, hair[0], hair[1], 'rgba(110,70,20,.45)', 12); vol(c, G.bun, 1360, 340, 1420, 400, hair[0], hair[1], 'rgba(110,70,20,.45)', 8);
|
|
142
|
+
// 卷发细线(波浪)
|
|
143
|
+
c.save(); c.clip(G.hairBack); c.strokeStyle = 'rgba(150,105,40,.6)'; c.lineWidth = 2; G.hairLines.forEach(h => c.stroke(h)); c.restore();
|
|
144
|
+
vol(c, G.face, 1245, 0, 1330, 0, sk[0], sk[1], 'rgba(150,80,60,.3)', 10);
|
|
145
|
+
c.save(); c.clip(G.face); c.fillStyle = 'rgba(235,130,120,.3)'; c.filter = 'blur(6px)'; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 12, 0, TAU); c.fill(); c.restore();
|
|
146
|
+
vol(c, G.bangs, 1250, 295, 1330, 360, hair[0], hair[1], 'rgba(110,70,20,.4)', 8);
|
|
147
|
+
c.strokeStyle = 'rgba(150,105,40,.7)'; c.lineWidth = 2; G.locks.forEach(h => c.stroke(h));
|
|
148
|
+
// 五官(细、暗褐、无黑线)
|
|
149
|
+
c.lineCap = 'round';
|
|
150
|
+
if (ch.blink) { c.strokeStyle = '#5a3420'; c.lineWidth = 2.2; c.stroke(G.lid); }
|
|
151
|
+
else { c.fillStyle = '#fbf3ea'; c.beginPath(); c.ellipse(G.eye.x, G.eye.y + 1, 6, 4, 0, 0, TAU); c.fill(); c.fillStyle = '#4a6a8a'; c.beginPath(); c.arc(G.eye.x - 1.5, G.eye.y + 1, 3.6, 0, TAU); c.fill(); c.fillStyle = '#1a1008'; c.beginPath(); c.arc(G.eye.x - 1.5, G.eye.y + 1, 1.6, 0, TAU); c.fill(); c.strokeStyle = '#4a2a18'; c.lineWidth = 2; c.stroke(G.lid); }
|
|
152
|
+
c.strokeStyle = '#a0763a'; c.lineWidth = 2; c.stroke(G.brow);
|
|
153
|
+
c.fillStyle = '#c8645e'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
|
|
154
|
+
c.fillStyle = 'rgba(120,60,40,.5)'; c.beginPath(); c.arc(G.nostril[0] + 2, G.nostril[1], 1.8, 0, TAU); c.fill();
|
|
155
|
+
vol(c, G.upperArm, 1300, 0, 1380, 0, dress[0], dress[1], 'rgba(40,50,70,.4)', 10); vol(c, G.foreArm, 1200, 0, 1320, 0, dress[0], dress[1], 'rgba(40,50,70,.4)', 10);
|
|
156
|
+
vol(c, G.cuff, 1200, 0, 1300, 0, '#fbf6ea', '#c8bea8', null);
|
|
157
|
+
vol(c, G.hand, G.A.hand[0] - 15, 0, G.A.hand[0] + 15, 0, sk[0], sk[1], 'rgba(120,60,40,.35)', 6);
|
|
158
|
+
RIG.drawCup(c, G.cup, { body: '#f3efe4', rim: '#7a4a2a', hw: 6 });
|
|
159
|
+
c.save(); c.translate(G.cup.x, G.cup.y); c.rotate(G.cup.tilt); const cg = c.createLinearGradient(-G.cup.w / 2, 0, G.cup.w / 2, 0); cg.addColorStop(0, 'rgba(255,255,255,0)'); cg.addColorStop(1, 'rgba(90,70,50,.45)'); c.fillStyle = cg; c.beginPath(); c.moveTo(-G.cup.w / 2, 0); c.lineTo(-G.cup.w * 0.42, G.cup.h); c.lineTo(G.cup.w * 0.42, G.cup.h); c.lineTo(G.cup.w / 2, 0); c.fill(); c.restore();
|
|
160
|
+
c.fillStyle = sk[0]; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill();
|
|
161
|
+
c.restore();
|
|
162
|
+
}
|
|
163
|
+
function cat(c, K, lt) {
|
|
164
|
+
c.save(); c.lineCap = 'round';
|
|
165
|
+
// 尾巴:软化下垂(达利式融化)
|
|
166
|
+
const pts = K.tailPts.map((p, i) => { const q = i / (K.tailPts.length - 1); return [p[0], p[1] + q * q * 34 * (0.6 + 0.4 * Math.sin(lt * 2))]; });
|
|
167
|
+
const tail = new Path2D(); pts.forEach((p, i) => i ? tail.lineTo(...p) : tail.moveTo(...p));
|
|
168
|
+
c.strokeStyle = '#8a3c10'; c.lineWidth = K.tailW + 4; c.stroke(tail); c.strokeStyle = '#e48a3a'; c.lineWidth = K.tailW; c.stroke(tail);
|
|
169
|
+
const tip = pts[pts.length - 1]; c.fillStyle = '#fbf2e2'; c.beginPath(); c.ellipse(tip[0], tip[1] + 6, K.tailW / 2, K.tailW * 0.75, 0, 0, TAU); c.fill();
|
|
170
|
+
vol(c, K.body, 430, 0, 660, 0, '#f2a24c', '#a24a14', 'rgba(70,25,5,.5)', 22);
|
|
171
|
+
c.save(); c.clip(K.body); c.strokeStyle = 'rgba(150,60,15,.55)'; c.lineWidth = 9; c.filter = 'blur(2px)'; K.stripes.slice(3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
|
|
172
|
+
K.legs.forEach(l => vol(c, l, 560, 0, 650, 0, '#fffaf0', '#cbbca0', 'rgba(90,70,40,.35)', 6));
|
|
173
|
+
c.save(); c.clip(K.body); const wch = new Path2D(); wch.ellipse(606, 768, 40, 92, -0.05, 0, TAU); vol(c, wch, 560, 0, 650, 0, '#fffaf0', '#d6c8ac', 'rgba(90,70,40,.3)', 10); c.restore();
|
|
174
|
+
vol(c, K.head, 540, 560, 660, 690, '#f6ac58', '#b25618', 'rgba(70,25,5,.45)', 14);
|
|
175
|
+
c.save(); c.clip(K.head); c.fillStyle = '#fbf3e4'; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
|
|
176
|
+
c.fillStyle = '#e8a0a0'; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); });
|
|
177
|
+
c.save(); c.clip(K.head); c.strokeStyle = 'rgba(150,60,15,.6)'; c.lineWidth = 7; c.filter = 'blur(1.5px)'; K.stripes.slice(0, 3).forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); c.restore();
|
|
178
|
+
K.eyes.forEach(e => { if (K.blink) { c.strokeStyle = '#3a200c'; c.lineWidth = 2.5; c.beginPath(); c.arc(e.x, e.y, 8, 0.2, Math.PI - 0.2); c.stroke(); }
|
|
179
|
+
else { const eg = c.createRadialGradient(e.x - 2, e.y - 2, 1, e.x, e.y, 9); eg.addColorStop(0, '#e8f0a0'); eg.addColorStop(1, '#7a9a30'); c.fillStyle = eg; c.beginPath(); c.arc(e.x, e.y, 9, 0, TAU); c.fill(); c.fillStyle = '#120a04'; c.beginPath(); c.ellipse(e.x + 2, e.y, 2.6, 7, 0, 0, TAU); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(e.x - 2, e.y - 3, 1.8, 0, TAU); c.fill(); } });
|
|
180
|
+
c.fillStyle = '#c86a64'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 5, 0, TAU); c.fill();
|
|
181
|
+
c.strokeStyle = 'rgba(60,30,10,.8)'; c.lineWidth = 2; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke();
|
|
182
|
+
c.strokeStyle = 'rgba(255,250,235,.85)'; c.lineWidth = 1.2; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
|
|
183
|
+
c.restore();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------- ② 长影子:剪影 → 剪切仿射投到地面 ----------
|
|
187
|
+
function shadows(c, lt, K, G) { // 剪切仿射长影子:P.castShadow
|
|
188
|
+
const kx = 0.85 + 0.6 * ease.inOut(clamp(lt / 1.2));
|
|
189
|
+
P.castShadow(c, [
|
|
190
|
+
[700, g => { g.beginPath(); g.moveTo(300, 700); [[300, 120], [410, 78], [540, 70], [680, 64], [858, 104], [892, 120], [892, 700]].forEach(p => g.lineTo(...p)); g.closePath(); g.rect(370, 140, 420, 400); g.fill('evenodd'); }],
|
|
191
|
+
[905, g => { g.fillRect(810, 600, 395, 305); g.fill(K.body); g.fill(K.head); g.lineWidth = K.tailW; g.stroke(K.tail); }],
|
|
192
|
+
[930, g => { [G.skirt, G.torso, G.hairBack, G.bun, G.face, G.upperArm, G.foreArm, G.hand].forEach(p => p && g.fill(p)); g.fillRect(1300, 742, 190, 163); }],
|
|
193
|
+
], { kx, ky: 0.22, clipY: GROUND + 2, alpha: 0.55, blur: 1.5, key: 'daliSh' });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
draw(c, lt, t) {
|
|
198
|
+
const ch = P.choreo(lt, t);
|
|
199
|
+
c.drawImage(bg(), 0, 0);
|
|
200
|
+
elephant(c, 1060 - lt * 70, lt);
|
|
201
|
+
const K = RIG.cat({ tail: ch.tail * 0.5, blink: ch.blink, breathe: ch.breathe });
|
|
202
|
+
const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe, hair: 'bun' });
|
|
203
|
+
shadows(c, lt, K, G);
|
|
204
|
+
c.drawImage(wallLayer(), 0, 0); c.drawImage(blockLayer(), 0, 0);
|
|
205
|
+
// 枯枝上的软钟(大部分垂下)
|
|
206
|
+
softClock(c, { cx: 975, cy: 104, R: 64, fold: -34, top: 0.25, drip: 0.25 }, 0.1 + 1.2 * ease.inOut(clamp(lt / 1.2)), t + 7, 4.5);
|
|
207
|
+
// 窗台上的软钟(垂在墙面上,慢慢往下流)
|
|
208
|
+
const sag = 0.1 + 1.3 * ease.inOut(clamp(lt / 1.2)); // 钟在 1.2s 里明显往下流
|
|
209
|
+
softClock(c, { cx: 740, cy: 548, R: 72, fold: 4, top: 0.3, drip: 0.4 }, sag, t, 6.5);
|
|
210
|
+
// 石块桌前沿的软钟
|
|
211
|
+
softClock(c, { cx: 912, cy: 610, R: 88, fold: 12, top: 0.28, drip: 0.3 }, sag * 0.9, t + 3, -2.4);
|
|
212
|
+
antWatch(c, lt);
|
|
213
|
+
cat(c, K, lt);
|
|
214
|
+
girl(c, G, ch);
|
|
215
|
+
// 热气:很淡的两缕
|
|
216
|
+
P.steam(c, t, G.cup.x, G.cup.y - 8, { h: 60, n: 2, color: 'rgba(255,250,240,.55)', width: 3, spread: 12, wobble: 7 });
|
|
217
|
+
// 暗角(博物馆灯下的画)
|
|
218
|
+
const vg = c.createRadialGradient(960, 520, 520, 960, 540, 1150); vg.addColorStop(0, 'rgba(0,0,0,0)'); vg.addColorStop(1, 'rgba(25,12,4,.38)'); c.fillStyle = vg; c.fillRect(0, 0, W, H);
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
})();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// 1942 霍珀美国现实主义(《晨光》《夜鹰》的硬光块面)——纯代码。
|
|
2
|
+
// 核心手法「双版本裁切」:整幅底版画两张缓存——背光版(冷绿墙、暗地板)和受光版(暖黄墙、亮地板);
|
|
3
|
+
// 每帧用会移动的光斑多边形把受光版裁进来 → 光影边缘是硬的、而且能整体平移。人物也画两遍(背光色/受光色),
|
|
4
|
+
// 第二遍裁进斜向光带。少女在墙上的投影 = 在光斑里把背光版透出来(剪影平移后 ∩ 光斑)。
|
|
5
|
+
// 母题动画:光斑缓慢右→左移动(光边扫过少女的脸)、窗外理发店旋转柱、卷帘拉绳轻摆、光里浮尘、热气。
|
|
6
|
+
SCENES['24_hopper'] = (() => {
|
|
7
|
+
const W = 1920, H = 1080, { clamp, lerp, ease } = U, P = PAINT, TAU = Math.PI * 2;
|
|
8
|
+
const FLOOR = 730;
|
|
9
|
+
const SH = { wall: '#5d8679', dado: '#4b6d63', floor: '#5b3d2b', board: '#4a3122', trim: '#a7b3a5', trimD: '#7f8d80', blind: '#cfc3a0' };
|
|
10
|
+
const LT = { wall: '#efcf8f', dado: '#d8ad68', floor: '#c98b52', board: '#b0743e', trim: '#f7eed2', trimD: '#d9c9a2', blind: '#f4e8c4' };
|
|
11
|
+
const poly = (c, pts) => { c.beginPath(); pts.forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.closePath(); };
|
|
12
|
+
|
|
13
|
+
// 窗外:对街的红砖立面(《星期天的清晨》),阳光从左来,每个窗洞右侧有硬投影
|
|
14
|
+
function street(g) {
|
|
15
|
+
g.save(); g.beginPath(); g.rect(370, 140, 420, 400); g.clip();
|
|
16
|
+
const sk = g.createLinearGradient(0, 140, 0, 220); sk.addColorStop(0, '#5f97c9'); sk.addColorStop(1, '#a9c8dc'); g.fillStyle = sk; g.fillRect(370, 140, 420, 80);
|
|
17
|
+
g.fillStyle = '#b4553a'; g.fillRect(370, 205, 420, 335); // 砖立面
|
|
18
|
+
g.fillStyle = '#d27a52'; g.fillRect(370, 196, 420, 14); g.fillStyle = '#5a2418'; g.fillRect(370, 210, 420, 10); // 檐口+檐下硬影
|
|
19
|
+
for (let row = 0; row < 2; row++) for (let k = 0; k < 4; k++) {
|
|
20
|
+
const x = 392 + k * 102, y = 240 + row * 92;
|
|
21
|
+
g.fillStyle = '#e8dcc0'; g.fillRect(x - 6, y - 6, 58, 76); // 窗套
|
|
22
|
+
g.fillStyle = '#1f2a24'; g.fillRect(x, y, 46, 64);
|
|
23
|
+
const sh = [0.55, 0.3, 0.7, 0.42, 0.25, 0.6, 0.5, 0.35][row * 4 + k]; // 半拉的深绿卷帘,高低不一
|
|
24
|
+
g.fillStyle = '#3d6a4e'; g.fillRect(x, y, 46, 64 * sh);
|
|
25
|
+
g.fillStyle = 'rgba(40,10,5,.55)'; g.fillRect(x + 46, y - 6, 10, 76); // 窗洞右侧硬影
|
|
26
|
+
}
|
|
27
|
+
g.fillStyle = '#2d5a44'; g.fillRect(370, 432, 420, 108); // 店面
|
|
28
|
+
g.fillStyle = '#c9d6c8'; [[386, 448, 150], [560, 448, 150]].forEach(([x, y, w]) => { g.fillRect(x, y, w, 70); });
|
|
29
|
+
g.fillStyle = 'rgba(30,50,40,.75)'; [[386, 448, 150], [560, 448, 150]].forEach(([x, y, w]) => { g.fillRect(x, y + 30, w, 40); });
|
|
30
|
+
g.fillStyle = '#e9e0c8'; g.fillRect(370, 424, 420, 10);
|
|
31
|
+
g.fillStyle = '#d8c9a8'; g.fillRect(370, 526, 420, 14); // 人行道
|
|
32
|
+
g.restore();
|
|
33
|
+
}
|
|
34
|
+
function room(g, K) {
|
|
35
|
+
g.fillStyle = K.wall; g.fillRect(0, 0, W, FLOOR);
|
|
36
|
+
g.fillStyle = K.dado; g.fillRect(0, 640, W, FLOOR - 640); g.fillStyle = K.trim; g.fillRect(0, 636, W, 8); g.fillRect(0, FLOOR - 14, W, 14);
|
|
37
|
+
g.fillStyle = K.floor; g.fillRect(0, FLOOR, W, H - FLOOR);
|
|
38
|
+
g.strokeStyle = K.board; g.lineWidth = 3; for (let k = 1; k < 8; k++) { const y = FLOOR + Math.pow(k / 8, 1.5) * (H - FLOOR); g.beginPath(); g.moveTo(0, y); g.lineTo(W, y); g.stroke(); }
|
|
39
|
+
street(g);
|
|
40
|
+
// 窗套(白漆)+窗台+中梃
|
|
41
|
+
g.fillStyle = K.trim; g.fillRect(346, 116, 468, 24); g.fillRect(346, 116, 24, 446); g.fillRect(790, 116, 24, 446); g.fillRect(330, 540, 500, 26);
|
|
42
|
+
g.fillStyle = K.trimD; g.fillRect(330, 566, 500, 10); g.fillRect(370, 330, 420, 10); g.fillRect(575, 140, 10, 400);
|
|
43
|
+
// 卷帘(拉下来一截)
|
|
44
|
+
g.fillStyle = K.blind; g.fillRect(366, 140, 428, 92); g.fillStyle = K.trimD; g.fillRect(366, 228, 428, 6);
|
|
45
|
+
// 墙上一幅小画框(霍珀房间里常有的空白小画)
|
|
46
|
+
g.fillStyle = K.trimD; g.fillRect(1560, 300, 160, 120); g.fillStyle = K.dado; g.fillRect(1572, 312, 136, 96);
|
|
47
|
+
}
|
|
48
|
+
const bgShade = () => P.cached('hop_sh', W, H, g => room(g, SH));
|
|
49
|
+
const bgLit = () => P.cached('hop_lt', W, H, g => room(g, LT));
|
|
50
|
+
// 油画刷痕:竖向拉长的 fbm 为主、斜向为辅,soft-light 叠 α0.2
|
|
51
|
+
const brush = () => P.cached('hop_brush', W, H, (g) => {
|
|
52
|
+
const s = 4, w = W / s, h = H / s, sm = P.canvas(w, h), sg = sm.getContext('2d'), im = sg.createImageData(w, h), d = im.data;
|
|
53
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) { const n = P.fbm(x * 0.06, y * 0.006, 4) * 0.7 + P.fbm(x * 0.02 + y * 0.02, 3.3, 3) * 0.3, v = 128 + n * 110, i = (y * w + x) * 4; d[i] = d[i + 1] = d[i + 2] = v; d[i + 3] = 255; }
|
|
54
|
+
sg.putImageData(im, 0, 0); g.imageSmoothingQuality = 'high'; g.drawImage(sm, 0, 0, W, H);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---------- 家具与角色:同一个函数,传背光/受光两套色 ----------
|
|
58
|
+
const TS = { wood: '#4f3322', woodD: '#3a2418', skin: '#c89a84', skinD: '#a87866', hair: '#a8893f', hairD: '#7f6428', dress: '#7a2c2a', dressD: '#5e1f1e', collar: '#b9b09c',
|
|
59
|
+
orange: '#a6602c', orangeD: '#80461c', white: '#dcd6c8', stripe: '#6e3a18', cup: '#bfb8a8', eye: '#6a8a3a', band: '#3a5a8a' };
|
|
60
|
+
const TL = { wood: '#a86c3c', woodD: '#7a4a26', skin: '#f7d3b4', skinD: '#e0ad8c', hair: '#f3d67e', hairD: '#c9a24a', dress: '#cc4a3c', dressD: '#9e3328', collar: '#f6efdc',
|
|
61
|
+
orange: '#f0a04a', orangeD: '#c8722a', white: '#fbf5e6', stripe: '#c0642a', cup: '#fbf8f0', eye: '#a8d050', band: '#4a78c0' };
|
|
62
|
+
function furniture(c, K) {
|
|
63
|
+
c.fillStyle = K.wood; c.fillRect(810, 618, 400, 26); c.fillStyle = K.woodD; c.fillRect(826, 644, 368, 22);
|
|
64
|
+
c.fillStyle = K.wood; c.fillRect(840, 644, 20, 261); c.fillRect(1160, 644, 20, 261);
|
|
65
|
+
c.fillStyle = K.woodD; c.fillRect(1436, 470, 18, 440); c.fillRect(1474, 478, 18, 430); c.fillStyle = K.wood; c.fillRect(1290, 744, 210, 18); c.fillRect(1300, 760, 16, 150);
|
|
66
|
+
}
|
|
67
|
+
function cat(c, K, KK) {
|
|
68
|
+
c.save(); c.lineCap = 'round';
|
|
69
|
+
c.strokeStyle = K.orange; c.lineWidth = KK.tailW; c.stroke(KK.tail);
|
|
70
|
+
const tip = KK.tailPts[KK.tailPts.length - 1]; c.fillStyle = K.white; c.beginPath(); c.arc(tip[0], tip[1], KK.tailW / 2, 0, TAU); c.fill();
|
|
71
|
+
c.fillStyle = K.orange; c.fill(KK.body);
|
|
72
|
+
c.save(); c.clip(KK.body); c.fillStyle = K.orangeD; c.beginPath(); c.ellipse(450, 830, 70, 120, 0.1, 0, TAU); c.fill(); c.restore(); // 背侧暗面(块面)
|
|
73
|
+
KK.legs.forEach(l => { c.fillStyle = K.white; c.fill(l); });
|
|
74
|
+
c.save(); c.clip(KK.body); c.fillStyle = K.white; c.beginPath(); c.ellipse(606, 768, 40, 92, -0.05, 0, TAU); c.fill(); c.restore(); // 白胸:平滑椭圆(RIG 的 white 是直线多边形,不描线时像贴片)
|
|
75
|
+
c.fillStyle = K.orange; c.fill(KK.head);
|
|
76
|
+
c.save(); c.clip(KK.head); c.fillStyle = K.white; c.beginPath(); c.ellipse(KK.nose[0] - 22, KK.nose[1] + 12, 36, 26, 0, 0, TAU); c.fill(); c.restore();
|
|
77
|
+
c.strokeStyle = K.stripe; c.lineWidth = 7; KK.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); });
|
|
78
|
+
KK.eyes.forEach(e => { if (KK.blink) { c.strokeStyle = '#2a1a0e'; c.lineWidth = 3; c.beginPath(); c.moveTo(e.x - 8, e.y + 1); c.lineTo(e.x + 8, e.y + 1); c.stroke(); }
|
|
79
|
+
else { c.fillStyle = K.eye; c.beginPath(); c.arc(e.x, e.y, 8, 0, TAU); c.fill(); c.fillStyle = '#1a120a'; c.beginPath(); c.ellipse(e.x + 2, e.y, 2.4, 6.5, 0, 0, TAU); c.fill(); } });
|
|
80
|
+
c.fillStyle = '#b8605a'; c.beginPath(); c.arc(KK.nose[0], KK.nose[1], 5, 0, TAU); c.fill();
|
|
81
|
+
c.strokeStyle = '#3a2416'; c.lineWidth = 2; c.beginPath(); c.moveTo(...KK.mouth[0]); c.quadraticCurveTo(...KK.mouth[1], ...KK.mouth[2]); c.stroke();
|
|
82
|
+
c.restore();
|
|
83
|
+
}
|
|
84
|
+
function girl(c, K, G, ch) {
|
|
85
|
+
c.save(); c.lineCap = 'round'; c.lineJoin = 'round';
|
|
86
|
+
const f = (p, col) => { if (p) { c.fillStyle = col; c.fill(p); } };
|
|
87
|
+
f(G.farSleeve, K.dressD); f(G.farHand, K.skinD); f(G.farCuff, K.collar);
|
|
88
|
+
f(G.skirt, K.dress);
|
|
89
|
+
c.save(); c.clip(G.skirt); c.fillStyle = K.dressD; c.beginPath(); c.moveTo(1330, 640); c.lineTo(1460, 640); c.lineTo(1460, 940); c.lineTo(1300, 940); c.lineTo(1352, 780); c.closePath(); c.fill(); c.restore(); // 膝后暗块
|
|
90
|
+
c.strokeStyle = K.dressD; c.lineWidth = 4; G.folds.forEach(p => c.stroke(p));
|
|
91
|
+
f(G.shoe, '#2a1810');
|
|
92
|
+
f(G.torso, K.dress);
|
|
93
|
+
c.save(); c.clip(G.torso); c.fillStyle = K.dressD; c.fillRect(1375, 420, 80, 220); c.restore();
|
|
94
|
+
const nk = G.A.neck; c.fillStyle = K.collar; c.beginPath(); c.moveTo(nk[0] - 20, nk[1] - 2); c.lineTo(nk[0] + 24, nk[1]); c.lineTo(nk[0] + 6, nk[1] + 30); c.closePath(); c.fill(); // 白领尖
|
|
95
|
+
f(G.hairDown, K.hair); f(G.neck, K.skinD); f(G.hairBack, K.hair);
|
|
96
|
+
c.save(); c.clip(G.hairBack); c.fillStyle = K.hairD; c.beginPath(); c.ellipse(G.A.nape[0] + 20, G.A.nape[1] - 10, 50, 40, 0, 0, TAU); c.fill(); c.restore();
|
|
97
|
+
f(G.face, K.skin);
|
|
98
|
+
c.save(); c.clip(G.face); c.fillStyle = K.skinD; c.beginPath(); c.ellipse(G.A.ear[0] + 6, G.A.ear[1] + 10, 26, 46, 0, 0, TAU); c.fill(); c.restore(); // 颊后暗面
|
|
99
|
+
f(G.bangs, K.hair);
|
|
100
|
+
{ const a = G.A.headTop, nk = G.A.nape; c.strokeStyle = K.band; c.lineWidth = 9; c.beginPath(); c.moveTo(a[0] - 52, a[1] + 26); c.quadraticCurveTo(a[0] + 10, a[1] - 6, nk[0] + 30, nk[1] - 62); c.stroke(); } // 发带(少女感)
|
|
101
|
+
c.strokeStyle = K.hairD; c.lineWidth = 3; G.hairLines.forEach(p => c.stroke(p));
|
|
102
|
+
if (ch.blink) { c.strokeStyle = '#3a2418'; c.lineWidth = 2.6; c.stroke(G.lid); }
|
|
103
|
+
else { c.fillStyle = '#2e2018'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1, 3.6, 4.6, 0, 0, TAU); c.fill(); c.strokeStyle = '#3a2418'; c.lineWidth = 2.4; c.stroke(G.lid); }
|
|
104
|
+
c.strokeStyle = K.hairD; c.lineWidth = 2.4; c.stroke(G.brow);
|
|
105
|
+
c.fillStyle = '#a83a36'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
|
|
106
|
+
f(G.upperArm, K.dress); f(G.foreArm, K.dress); f(G.cuff, K.collar); f(G.hand, K.skin);
|
|
107
|
+
RIG.drawCup(c, G.cup, { body: K.cup, rim: '#5a3a22', hw: 6 });
|
|
108
|
+
c.fillStyle = K.skin; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, TAU); c.fill();
|
|
109
|
+
c.restore();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
draw(c, lt, t) {
|
|
114
|
+
const ch = P.choreo(lt, t);
|
|
115
|
+
const dx = 30 - 95 * ease.inOut(clamp(lt / 1.25)); // 光斑 1.2s 里向左移 ~95px(太阳在走)
|
|
116
|
+
const KK = RIG.cat({ tail: ch.tail * 0.6, blink: ch.blink, breathe: ch.breathe });
|
|
117
|
+
const G = RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe, hair: 'long' });
|
|
118
|
+
// ① 背光底版 → 墙/地光斑里透出受光底版
|
|
119
|
+
c.drawImage(bgShade(), 0, 0);
|
|
120
|
+
const WP = [[1420 + dx, 250], [1920, 470], [1920, FLOOR], [1420 + dx, FLOOR]];
|
|
121
|
+
const FP = [[1420 + dx, FLOOR], [1920, FLOOR], [1920, H], [1700 + dx, H]];
|
|
122
|
+
P.litClip(c, [WP, FP], c => { c.drawImage(bgLit(), 0, 0);
|
|
123
|
+
// 光斑里的窗棂影(竖+斜横)与少女投在墙上的影:都把背光版透出来
|
|
124
|
+
const S = P.scratch('hopSh'), sg = S.getContext('2d'); sg.reset(); sg.clearRect(0, 0, W, H); sg.fillStyle = '#000';
|
|
125
|
+
sg.fillRect(1650 + dx, 200, 24, FLOOR - 200); poly(sg, [[1650 + dx, FLOOR], [1674 + dx, FLOOR], [1820 + dx, H], [1796 + dx, H]]); sg.fill();
|
|
126
|
+
poly(sg, [[1420 + dx, 470], [1920, 680], [1920, 702], [1420 + dx, 492]]); sg.fill();
|
|
127
|
+
sg.save(); sg.translate(150, 18); [G.torso, G.hairBack, G.bun, G.face, G.bangs, G.upperArm, G.foreArm, G.hand, G.skirt].forEach(p => p && sg.fill(p)); sg.restore();
|
|
128
|
+
sg.globalCompositeOperation = 'source-in'; sg.drawImage(bgShade(), 0, 0); sg.globalCompositeOperation = 'source-over';
|
|
129
|
+
c.drawImage(S, 0, 0); });
|
|
130
|
+
// ② 窗外理发店旋转柱(红白蓝斜纹上行)
|
|
131
|
+
c.save(); const bx = 742, by = 446, bw = 18, bh = 64; c.beginPath(); c.rect(bx, by, bw, bh); c.clip(); c.fillStyle = '#f4efe2'; c.fillRect(bx, by, bw, bh);
|
|
132
|
+
const off = (lt * 60) % 24; c.lineWidth = 6; for (let k = -4; k < 6; k++) { const y = by + k * 24 - off; c.strokeStyle = k % 2 ? '#c8302a' : '#2a4a9a'; c.beginPath(); c.moveTo(bx - 4, y + 14); c.lineTo(bx + bw + 4, y); c.stroke(); }
|
|
133
|
+
c.restore(); c.fillStyle = '#d9d2bf'; c.fillRect(bx - 3, by - 8, bw + 6, 8); c.fillRect(bx - 3, by + bh, bw + 6, 8);
|
|
134
|
+
// 卷帘拉绳轻摆
|
|
135
|
+
const sw = Math.sin(lt * 4.2) * 0.07; c.save(); c.translate(580, 232); c.rotate(sw); c.strokeStyle = '#7a6a4a'; c.lineWidth = 2; c.beginPath(); c.moveTo(0, 0); c.lineTo(0, 70); c.stroke();
|
|
136
|
+
c.strokeStyle = '#c9b88a'; c.lineWidth = 4; c.beginPath(); c.arc(0, 78, 8, 0, TAU); c.stroke(); c.restore();
|
|
137
|
+
// ③ 家具与角色:先背光色,再裁进斜光带画受光色
|
|
138
|
+
furniture(c, TS); cat(c, TS, KK); girl(c, TS, G, ch);
|
|
139
|
+
const OP = [[1345 + dx, 0], [W, 0], [W, H], [1185 + dx, H]];
|
|
140
|
+
P.litClip(c, [OP], c => { furniture(c, TL); girl(c, TL, G, ch); });
|
|
141
|
+
// 光带里的浮尘
|
|
142
|
+
c.save(); c.globalCompositeOperation = 'lighter'; P.particles(26, 42, t, { x0: 1250, x1: 1900, y0: 760, y1: 120, speed: 40, drift: 18, life: 9 }).forEach(p => {
|
|
143
|
+
const ex = 1345 + dx - (p.y / H) * 160; if (p.x < ex) return; c.fillStyle = `rgba(255,235,180,${0.35 + 0.3 * Math.sin(t * 6 + p.i)})`; c.beginPath(); c.arc(p.x, p.y, 2.2 * p.s, 0, TAU); c.fill(); });
|
|
144
|
+
c.restore();
|
|
145
|
+
P.steam(c, t, G.cup.x, G.cup.y - 8, { h: 55, n: 2, color: 'rgba(255,248,230,.5)', width: 3, spread: 12, wobble: 6 });
|
|
146
|
+
// 画布刷痕
|
|
147
|
+
c.save(); c.globalCompositeOperation = 'soft-light'; c.globalAlpha = 0.2; c.drawImage(brush(), 0, 0); c.restore();
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
})();
|