@biunow/agent-biuart 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,131 @@
|
|
|
1
|
+
// 马远(南宋院体,「一角」构图):绢底泛金、重心压在左下一角、斧劈皴+大片留白、垂柳长枝、渺小的人看向空处。
|
|
2
|
+
// 题材(新构图):雨后,书生立在岸角老柳下,风把柳枝吹向水面,一只小船在远处雾里。
|
|
3
|
+
// 原作锚定(fetch_reference.py 实拉并核对为三幅不同的马远作品:《山径春行》《月下赏梅》《华灯侍宴图》):绢 #c9a65a 偏金褐、墨分五色(焦/浓/重/淡/清)、
|
|
4
|
+
// 岩石=短促侧锋斧劈(宽扁笔,边硬面湿)、枝干=顿挫的线、垂条=细而长的淡墨、远山=清墨渲而无轮廓、人=极小、一笔衣纹。
|
|
5
|
+
// 管线:绢(缓存)→ 远山渲染(缓存)→ 岩石斧劈(缓存)→ 老柳干(缓存)→ 动态:柳条摆(逐条毛笔)、书生、舟、鸟、水纹 → 绢纹相乘罩一遍。
|
|
6
|
+
SCENES['50_ma_yuan'] = (() => {
|
|
7
|
+
const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
|
|
8
|
+
const INK = [22, 24, 22];
|
|
9
|
+
let layer;
|
|
10
|
+
const silk = () => M.silk('mayuan', { base: '#bfa46c', seed: 4, warp: 0.55, vig: 0.62, fleck: 2400 });
|
|
11
|
+
|
|
12
|
+
function wash(g, poly, a0, a1, y0, y1, blur) { // 清墨渲:上浓下淡的渐变面,无轮廓,边缘糊
|
|
13
|
+
g.save(); g.filter = `blur(${blur}px)`; const gr = g.createLinearGradient(0, y0, 0, y1); gr.addColorStop(0, `rgba(60,62,56,${a0})`); gr.addColorStop(1, `rgba(60,62,56,${a1})`);
|
|
14
|
+
g.fillStyle = gr; g.beginPath(); poly.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); g.closePath(); g.fill(); g.restore();
|
|
15
|
+
}
|
|
16
|
+
function mountains(g) {
|
|
17
|
+
wash(g, [[1020, 560], [1130, 440], [1210, 480], [1330, 360], [1440, 470], [1560, 410], [1700, 520], [1920, 470], [1920, 640], [1020, 640]], 0.26, 0.0, 360, 620, 9);
|
|
18
|
+
wash(g, [[1250, 600], [1380, 520], [1470, 560], [1600, 500], [1760, 580], [1920, 560], [1920, 680], [1250, 680]], 0.16, 0.0, 500, 680, 7);
|
|
19
|
+
g.save(); g.translate(1420, 610); g.scale(1, 0.16); const mg = g.createRadialGradient(0, 0, 0, 0, 0, 760); mg.addColorStop(0, 'rgba(226,210,160,.55)'); mg.addColorStop(1, 'rgba(226,210,160,0)'); g.fillStyle = mg; g.beginPath(); g.arc(0, 0, 760, 0, TAU); g.fill(); g.restore(); // 雾带压住山脚
|
|
20
|
+
}
|
|
21
|
+
// 斧劈岩:多边形轮廓作剪裁,里面按岩势方向排宽扁侧锋;先渲后皴,最后硬边
|
|
22
|
+
function rock(g, poly, { seed = 1, ang = 0.95, n = 90, wmin = 12, wmax = 30, wash0 = 0.34 } = {}) {
|
|
23
|
+
const r = rng(seed), xs = poly.map(p => p[0]), ys = poly.map(p => p[1]), bx = Math.min(...xs), by = Math.min(...ys), bw = Math.max(...xs) - bx, bh = Math.max(...ys) - by;
|
|
24
|
+
const path = new Path2D(); poly.forEach((p, i) => i ? path.lineTo(p[0], p[1]) : path.moveTo(p[0], p[1])); path.closePath();
|
|
25
|
+
g.save(); g.clip(path);
|
|
26
|
+
const gr = g.createLinearGradient(0, by, 0, by + bh); gr.addColorStop(0, `rgba(70,66,52,${wash0})`); gr.addColorStop(0.6, `rgba(60,58,46,${wash0 * 0.7})`); gr.addColorStop(1, `rgba(36,36,30,${wash0 * 1.5})`); g.fillStyle = gr; g.fillRect(bx, by, bw, bh);
|
|
27
|
+
for (let i = 0; i < n; i++) { // 斧劈:短、宽、起笔重收笔飞白,沿 ang 方向(用毛笔库;楔形多边形读成箭头,试过,弃用)
|
|
28
|
+
const x = bx + r() * bw, y = by + r() * bh, L = 40 + r() * 90, a = ang + (r() - .5) * 0.35, w = lerp(wmin, wmax, r()) * 0.55;
|
|
29
|
+
const q = (y - by) / bh;
|
|
30
|
+
P.brush(g, [[x, y], [x + Math.cos(a) * L * 0.5, y + Math.sin(a) * L * 0.5 + (r() - .5) * 8], [x + Math.cos(a) * L, y + Math.sin(a) * L]], { w, tone: 0.1 + q * 0.26 + r() * 0.08, dry: 0.85, seed: seed * 100 + i, bristles: 12, head: 0.05, tail: 0.5, col: INK });
|
|
31
|
+
}
|
|
32
|
+
for (let i = 0; i < n * 0.8; i++) { // 硬皴:细、直、顿挫,勾出斧劈的棱
|
|
33
|
+
const x = bx + r() * bw, y = by + r() * bh, L = 26 + r() * 60, a = ang + (r() - .5) * 0.25;
|
|
34
|
+
P.brush(g, [[x, y], [x + Math.cos(a) * L, y + Math.sin(a) * L]], { w: 2.6 + r() * 3, tone: 0.5 + r() * 0.3, dry: 0.5, seed: seed * 77 + i, bristles: 3, head: 0.05, tail: 0.5, col: INK });
|
|
35
|
+
}
|
|
36
|
+
g.restore();
|
|
37
|
+
// 轮廓:只描受光棱和水边,断续,一笔一顿
|
|
38
|
+
for (let i = 0; i < poly.length; i++) { const A = poly[i], B = poly[(i + 1) % poly.length]; if (r() < 0.25) continue;
|
|
39
|
+
P.brush(g, [A, [lerp(A[0], B[0], .5) + (r() - .5) * 8, lerp(A[1], B[1], .5) + (r() - .5) * 8], B], { w: 6 + r() * 6, tone: 0.62, dry: 0.5, seed: seed * 31 + i, bristles: 6, head: 0.1, tail: 0.3, col: INK }); }
|
|
40
|
+
}
|
|
41
|
+
// 干:从岸角向上斜出,两折,分叉
|
|
42
|
+
const TRUNK = [[250, 1000], [210, 820], [260, 640], [350, 500], [470, 410], [610, 340]];
|
|
43
|
+
const LIMBS = [ // 长枝的锚点(沿干的第 i 点)与走势:向右、向下垂
|
|
44
|
+
{ a: [350, 500], p: [[350, 500], [520, 440], [720, 470], [890, 560]], tw: 12, ph: 0.0 },
|
|
45
|
+
{ a: [470, 410], p: [[470, 410], [650, 350], [880, 380], [1080, 480]], tw: 15, ph: 1.4 },
|
|
46
|
+
{ a: [610, 340], p: [[610, 340], [790, 300], [1010, 330], [1200, 420]], tw: 14, ph: 2.6 },
|
|
47
|
+
{ a: [260, 640], p: [[260, 640], [400, 620], [560, 670], [690, 760]], tw: 9, ph: 3.7 },
|
|
48
|
+
];
|
|
49
|
+
function build() {
|
|
50
|
+
layer = P.canvas(); const g = layer.getContext('2d');
|
|
51
|
+
g.drawImage(silk(), 0, 0); mountains(g);
|
|
52
|
+
// 近岸岩:左下一角,前高后低两三块,错落
|
|
53
|
+
rock(g, [[0, 1080], [0, 700], [90, 640], [210, 690], [330, 800], [470, 860], [620, 940], [800, 1010], [900, 1080]], { seed: 3, ang: 1.0, n: 130, wmin: 14, wmax: 34 });
|
|
54
|
+
rock(g, [[420, 880], [520, 820], [640, 850], [760, 930], [980, 1000], [1120, 1080], [700, 1080]], { seed: 8, ang: 0.8, n: 70, wmin: 10, wmax: 24, wash0: 0.28 });
|
|
55
|
+
rock(g, [[1480, 1080], [1560, 1010], [1700, 990], [1860, 1040], [1920, 1080]], { seed: 15, ang: 2.2, n: 40, wmin: 8, wmax: 18, wash0: 0.22 }); // 对岸一撮(右下,平衡)
|
|
56
|
+
// 老干:一气呵成的粗线(分段画读成竹节,试过,弃用),再补树皮的顿挫短笔
|
|
57
|
+
P.brush(g, TRUNK, { w: 54, tone: 0.84, dry: 0.5, seed: 21, bristles: 16, head: 0.04, tail: 0.5, col: INK });
|
|
58
|
+
P.brush(g, TRUNK.map(p => [p[0] + 14, p[1] + 4]), { w: 20, tone: 0.55, dry: 0.8, seed: 22, bristles: 8, col: INK });
|
|
59
|
+
for (let i = 0; i < 40; i++) { const r = rng(40 + i), q = r(), y = lerp(990, 420, q), x = lerp(236, 480, 1 - q) + (r() - .5) * 46, h = 20 + r() * 40;
|
|
60
|
+
P.brush(g, [[x - 5, y], [x + 2, y + h * 0.5], [x + 6, y + h]], { w: 5 + r() * 5, tone: 0.3 + r() * 0.3, dry: 0.8, seed: 50 + i, bristles: 4, col: INK }); }
|
|
61
|
+
// 钤印
|
|
62
|
+
g.fillStyle = 'rgba(176,40,34,.88)'; g.fillRect(1760, 880, 46, 46); g.fillStyle = 'rgba(201,168,94,.9)'; g.fillRect(1768, 888, 14, 14); g.fillRect(1786, 906, 14, 14); g.fillRect(1768, 910, 8, 10);
|
|
63
|
+
}
|
|
64
|
+
function willow(c, t) {
|
|
65
|
+
LIMBS.forEach((L, li) => {
|
|
66
|
+
const sw = (k) => Math.sin(t * 1.5 + L.ph + k * 1.1) * (10 + k * 14) + Math.sin(t * 3.1 + L.ph * 2) * 3 * k;
|
|
67
|
+
const pts = L.p.map((p, k) => [p[0] + (k ? sw(k) * 0.6 : 0), p[1] + (k ? Math.sin(t * 1.7 + L.ph + k) * 6 * k : 0)]);
|
|
68
|
+
P.brush(c, pts, { w: L.tw, tone: 0.8, dry: 0.5, seed: 90 + li, bristles: 8, head: 0.04, tail: 0.5, col: INK });
|
|
69
|
+
// 垂条:沿枝取点,每条下垂,末端随风向右
|
|
70
|
+
const D = KIT.resample(KIT.densify(pts, 5), 34), n = D.length;
|
|
71
|
+
for (let k = 2; k < n; k++) {
|
|
72
|
+
const [x, y] = D[k], r = rng(li * 500 + k * 13);
|
|
73
|
+
if (r() < 0.3) continue; // 成簇:随机缺条,留出透气的缝
|
|
74
|
+
const cnt = 1 + (r() * 3 | 0);
|
|
75
|
+
for (let m = 0; m < cnt; m++) {
|
|
76
|
+
const x0 = x + (r() - .5) * 22, len = 40 + Math.pow(r(), 1.6) * 250, ph = r() * 6, wind = (Math.sin(t * 1.7 + ph + k * 0.35) * 0.5 + 0.5) * 38 + 8;
|
|
77
|
+
const bend = (r() - 0.3) * 40, tw = [[x0, y], [x0 + wind * 0.15 + bend * 0.3, y + len * 0.35], [x0 + wind * 0.5 + bend, y + len * 0.7], [x0 + wind + bend * 0.8, y + len]];
|
|
78
|
+
P.brush(c, tw, { w: 1.8 + r() * 1.8, tone: 0.18 + r() * 0.34, dry: 0.5, seed: li * 700 + k * 5 + m, bristles: 3, head: 0.05, tail: 0.65, col: INK });
|
|
79
|
+
if (r() < 0.35) P.leaf(c, tw[3][0], tw[3][1], 1.35 + Math.sin(t * 2.2 + ph) * 0.2, 14 + r() * 10, 4, 0.45, INK);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function scholar(c, t) { // 立在岸角看向空处:背影,宽袖,高冠;一笔衣纹,袍不填满(留绢)
|
|
85
|
+
const x = 760, y = 972, bob = Math.sin(t * 1.3) * 1.0, fl = Math.sin(t * 2.1) * 5, look = ss(0.5, 3.5, t);
|
|
86
|
+
c.save(); c.translate(x, y + bob);
|
|
87
|
+
c.fillStyle = 'rgba(236,224,184,.5)';
|
|
88
|
+
c.beginPath(); c.moveTo(-14, -128); c.quadraticCurveTo(-34, -60, -30, 0); c.lineTo(26, 0); c.quadraticCurveTo(28 + fl * 0.4, -64, 14, -128); c.closePath(); c.fill();
|
|
89
|
+
P.brush(c, [[-16, -126], [-34, -56], [-30, 0]], { w: 4, tone: 0.85, dry: 0.2, seed: 201, bristles: 4, col: INK });
|
|
90
|
+
P.brush(c, [[14, -126], [28, -60], [26 + fl * 0.5, 0]], { w: 3.4, tone: 0.75, dry: 0.3, seed: 202, bristles: 4, col: INK });
|
|
91
|
+
P.brush(c, [[-30, 2], [-2, 8], [26, 2]], { w: 3.6, tone: 0.6, dry: 0.3, seed: 203, bristles: 3, col: INK });
|
|
92
|
+
P.brush(c, [[-4, -120], [-8, -70], [0, -20]], { w: 2.6, tone: 0.5, dry: 0.5, seed: 204, bristles: 3, col: INK });
|
|
93
|
+
// 右袖抬起指向远处(随 look 抬起)
|
|
94
|
+
const ah = lerp(-30, -96, look);
|
|
95
|
+
c.fillStyle = 'rgba(236,224,184,.5)'; c.beginPath(); c.moveTo(14, -118); c.quadraticCurveTo(52, ah + 10, 72 + fl * 0.4, ah); c.quadraticCurveTo(54, ah + 30, 20, -88); c.closePath(); c.fill();
|
|
96
|
+
P.brush(c, [[14, -118], [52, ah + 8], [72 + fl * 0.4, ah]], { w: 3.4, tone: 0.8, dry: 0.3, seed: 205, bristles: 4, col: INK });
|
|
97
|
+
P.brush(c, [[20, -88], [54, ah + 30], [72 + fl * 0.4, ah]], { w: 2.6, tone: 0.6, dry: 0.3, seed: 206, bristles: 3, col: INK });
|
|
98
|
+
c.fillStyle = 'rgba(40,38,34,.95)'; c.beginPath(); c.ellipse(-2, -142, 14, 15, 0, 0, TAU); c.fill(); // 头
|
|
99
|
+
c.beginPath(); c.moveTo(-14, -150); c.lineTo(-10, -178); c.lineTo(10, -178); c.lineTo(14, -150); c.closePath(); c.fill(); // 高冠
|
|
100
|
+
P.brush(c, [[8, -168], [34 + fl, -164], [52 + fl * 1.4, -140]], { w: 3.6, tone: 0.8, dry: 0.3, seed: 207, bristles: 3, col: INK }); // 冠带随风
|
|
101
|
+
c.restore();
|
|
102
|
+
}
|
|
103
|
+
function boat(c, t) { // 远处小舟:一笔船身+一笔蓑笠人,随水轻荡
|
|
104
|
+
const x = lerp(1620, 1420, ss(0, 6, t)), y = 790 + Math.sin(t * 1.1) * 4, r2 = Math.sin(t * 1.1) * 0.02;
|
|
105
|
+
c.save(); c.translate(x, y); c.rotate(r2); c.globalAlpha = 0.9;
|
|
106
|
+
P.brush(c, [[-60, 0], [-20, 12], [30, 10], [74, -6]], { w: 11, tone: 0.78, dry: 0.45, seed: 301, bristles: 6, head: 0.1, tail: 0.3, col: INK });
|
|
107
|
+
c.fillStyle = 'rgba(40,40,34,.85)'; c.beginPath(); c.moveTo(-6, -4); c.quadraticCurveTo(-6, -40, 4, -48); c.quadraticCurveTo(14, -40, 12, -4); c.fill();
|
|
108
|
+
P.brush(c, [[-18, -44], [4, -56], [26, -44]], { w: 6, tone: 0.8, dry: 0.3, seed: 302, bristles: 4, col: INK });
|
|
109
|
+
P.brush(c, [[14, -30], [40, -10], [66, 22]], { w: 2.4, tone: 0.7, dry: 0.2, seed: 303, bristles: 2, col: INK }); // 篙
|
|
110
|
+
c.restore();
|
|
111
|
+
c.save(); c.globalAlpha = 0.28; P.brush(c, [[x - 70, y + 22], [x - 10, y + 26], [x + 80, y + 22]], { w: 4, tone: 0.5, dry: 0.8, seed: 304, bristles: 3, col: INK }); c.restore();
|
|
112
|
+
}
|
|
113
|
+
function bird(c, t) {
|
|
114
|
+
const u = ss(0.3, 5.7, t), x = lerp(1860, 1180, u), y = 250 + Math.sin(u * 7) * 26 - u * 40, f = Math.sin(t * 9) * 12;
|
|
115
|
+
c.save(); c.translate(x, y); P.brush(c, [[-22, -f * 0.4], [-6, -8 - f], [0, 0]], { w: 5, tone: 0.8, dry: 0.2, seed: 401, bristles: 3, col: INK }); P.brush(c, [[22, -f * 0.4], [6, -8 - f], [0, 0]], { w: 5, tone: 0.8, dry: 0.2, seed: 402, bristles: 3, col: INK });
|
|
116
|
+
c.fillStyle = 'rgba(30,30,28,.85)'; c.beginPath(); c.ellipse(0, 2, 7, 4, 0, 0, TAU); c.fill(); c.restore();
|
|
117
|
+
}
|
|
118
|
+
function ripples(c, t) { // 水面只有几道极淡的横线,加岸边涟漪
|
|
119
|
+
c.save(); for (let i = 0; i < 9; i++) { const y = 700 + i * 36, x0 = 980 + (i % 3) * 170 + Math.sin(t * 0.6 + i) * 18; c.strokeStyle = `rgba(58,60,52,${0.05 + i * 0.008})`; c.lineWidth = 1.4; c.beginPath(); c.moveTo(x0, y); c.bezierCurveTo(x0 + 60, y - 2, x0 + 150, y + 2, x0 + 230, y); c.stroke(); }
|
|
120
|
+
for (let k = 0; k < 3; k++) { const q = ((t * 0.28 + k / 3) % 1); c.strokeStyle = `rgba(58,60,52,${(1 - q) * 0.2})`; c.lineWidth = 1.4; c.beginPath(); c.ellipse(1010, 905, 24 + q * 150, 3 + q * 15, 0, 0, TAU); c.stroke(); }
|
|
121
|
+
c.restore();
|
|
122
|
+
}
|
|
123
|
+
function draw(c, lt) {
|
|
124
|
+
if (!layer) build();
|
|
125
|
+
const t = clamp(lt, 0, 6);
|
|
126
|
+
c.drawImage(layer, 0, 0);
|
|
127
|
+
ripples(c, t); boat(c, t); bird(c, t); willow(c, t); scholar(c, t);
|
|
128
|
+
c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.35; c.drawImage(silk(), 0, 0); c.restore(); // 绢纹罩在墨上:墨是渗进绢里的
|
|
129
|
+
}
|
|
130
|
+
return { draw };
|
|
131
|
+
})();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// 波斯细密画(赫拉特/萨法维):没有透视收束,地面像一张竖起的挂毯;建筑被切开让人看见里面;每一寸都有纹样;金、青金石蓝、朱、孔雀绿;文字块进画。
|
|
2
|
+
// 题材(新构图):庭院里的汲水轮——一位园丁摇轮,水顺渠流进八角水池,旁边两人在亭下读书;上方书页式文字块。
|
|
3
|
+
// 原作锚定(fetch_reference.py:赫拉特 Bustan《尤素福与祖莱哈》、花园浴女页、沙赫塔赫马斯普《列王纪》页):
|
|
4
|
+
// 纸/羊皮 #d9bf88、青金石 #1f4f8f、朱 #b7372b、孔雀绿 #2a8a7a、金 #d6a93c、玫粉 #d98a7a;砖红/青瓷砖满铺几何;
|
|
5
|
+
// 树=平涂树干+点叶团+花;人=小、侧身、大头巾、袍上暗纹;地面一律「俯视平铺」,远处靠叠高不靠缩小。
|
|
6
|
+
// 管线:纸底(缓存)→ 外框金线+蓝带 → 文字块 → 建筑切面(砖+瓷砖满铺)→ 庭院平铺地面(草+花点)→ 水池(八角,几何瓷砖边)
|
|
7
|
+
// → 树 → 动态:汲水轮转、水沿渠流、人物动作、花瓣飘、水纹 → 极薄的纸纤维相乘。
|
|
8
|
+
SCENES['51_persian_miniature'] = (() => {
|
|
9
|
+
const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, M = MEDIUM, TAU = Math.PI * 2;
|
|
10
|
+
const C = { paper: '#dcc38c', lapis: '#1f4f8f', lapis2: '#2f6fb0', red: '#b7372b', teal: '#2a8a7a', gold: '#d6a93c', rose: '#d98a7a', brick: '#c0643f', cream: '#efe0b4', green: '#6f9a4a', dgreen: '#3f6e3a', ink: '#2b2118' };
|
|
11
|
+
let layer;
|
|
12
|
+
const poly = (pts, close = true) => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(q[0], q[1]) : p.moveTo(q[0], q[1])); if (close) p.closePath(); return p; };
|
|
13
|
+
const fillPoly = (g, pts, col, o = {}) => { g.fillStyle = col; g.fill(poly(pts)); if (o.line !== false) { g.strokeStyle = o.line || C.ink; g.lineWidth = o.lw || 2.4; g.stroke(poly(pts)); } };
|
|
14
|
+
// 满铺几何:星/十字瓷砖,周期取模,两色交替
|
|
15
|
+
function tiles(g, x, y, w, h, size, a, b, c2, seed = 1) {
|
|
16
|
+
g.save(); g.beginPath(); g.rect(x, y, w, h); g.clip(); g.fillStyle = a; g.fillRect(x, y, w, h);
|
|
17
|
+
const r = rng(seed);
|
|
18
|
+
for (let j = 0; y + j * size < y + h + size; j++) for (let i = 0; x + i * size < x + w + size; i++) {
|
|
19
|
+
const cx = x + i * size + size / 2, cy = y + j * size + size / 2, k = (i + j) % 2;
|
|
20
|
+
g.fillStyle = k ? b : c2; g.beginPath(); for (let q = 0; q < 8; q++) { const ang = q / 8 * TAU + Math.PI / 8, rr = q % 2 ? size * 0.2 : size * 0.38; g.lineTo(cx + Math.cos(ang) * rr, cy + Math.sin(ang) * rr); } g.closePath(); g.fill();
|
|
21
|
+
g.fillStyle = k ? c2 : b; g.beginPath(); g.arc(cx, cy, size * 0.1, 0, TAU); g.fill();
|
|
22
|
+
}
|
|
23
|
+
g.strokeStyle = 'rgba(30,24,16,.5)'; g.lineWidth = 1.4; for (let i = 0; i <= w / size + 1; i++) { g.beginPath(); g.moveTo(x + i * size, y); g.lineTo(x + i * size, y + h); g.stroke(); } for (let j = 0; j <= h / size + 1; j++) { g.beginPath(); g.moveTo(x, y + j * size); g.lineTo(x + w, y + j * size); g.stroke(); }
|
|
24
|
+
g.restore();
|
|
25
|
+
}
|
|
26
|
+
function border(g) { // 金线+青金石蓝带+金色细花
|
|
27
|
+
g.fillStyle = C.lapis; g.fillRect(0, 0, W, H); g.fillStyle = C.gold; g.fillRect(22, 22, W - 44, H - 44); g.fillStyle = C.paper; g.fillRect(30, 30, W - 60, H - 60);
|
|
28
|
+
g.fillStyle = C.lapis; g.fillRect(44, 44, W - 88, H - 88); g.fillStyle = C.gold; g.fillRect(52, 52, W - 104, H - 104); g.fillStyle = C.paper; g.fillRect(58, 58, W - 116, H - 116);
|
|
29
|
+
g.fillStyle = 'rgba(214,169,60,.9)'; for (let x = 70; x < W - 70; x += 38) { [34, H - 34].forEach(y => { g.beginPath(); g.arc(x, y, 6, 0, TAU); g.fill(); }); } for (let y = 70; y < H - 70; y += 38) { [34, W - 34].forEach(x => { g.beginPath(); g.arc(x, y, 6, 0, TAU); g.fill(); }); }
|
|
30
|
+
}
|
|
31
|
+
function textBlock(g, x, y, w, h) { // 书法文字块:金边朱框内一行行「连笔」——用折线笔画模拟,不写具体字
|
|
32
|
+
fillPoly(g, [[x, y], [x + w, y], [x + w, y + h], [x, y + h]], '#efe3bd', { lw: 3, line: C.gold });
|
|
33
|
+
const r = rng(7); g.strokeStyle = C.ink; g.lineCap = 'round'; g.lineWidth = 2.4;
|
|
34
|
+
for (let row = 0; row < Math.floor(h / 34); row++) { const yy = y + 28 + row * 34; let xx = x + w - 20; g.beginPath(); g.moveTo(xx, yy); while (xx > x + 24) { const st = 8 + r() * 18; xx -= st; g.quadraticCurveTo(xx + st / 2, yy - 8 - r() * 10, xx, yy + (r() - .5) * 6); if (r() < 0.12) { g.moveTo(xx - 10, yy); xx -= 10; } } g.stroke();
|
|
35
|
+
g.fillStyle = C.red; if (row % 3 === 0) { g.beginPath(); g.arc(x + w - 14, yy - 6, 4, 0, TAU); g.fill(); } }
|
|
36
|
+
}
|
|
37
|
+
function build() {
|
|
38
|
+
layer = P.canvas(); const g = layer.getContext('2d');
|
|
39
|
+
g.fillStyle = C.paper; g.fillRect(0, 0, W, H); border(g);
|
|
40
|
+
// 顶部:建筑切面(被切开:里面看得到人)
|
|
41
|
+
g.save(); g.beginPath(); g.rect(60, 60, W - 120, H - 120); g.clip();
|
|
42
|
+
// 外墙大面:砖红+瓷砖腰线;门楣蓝釉
|
|
43
|
+
fillPoly(g, [[900, 60], [1860, 60], [1860, 520], [900, 520]], C.brick, { lw: 3 });
|
|
44
|
+
for (let y = 80; y < 500; y += 26) { g.strokeStyle = 'rgba(80,30,20,.35)'; g.lineWidth = 1.5; g.beginPath(); g.moveTo(900, y); g.lineTo(1860, y); g.stroke(); for (let x = 900 + ((y / 26) % 2) * 20; x < 1860; x += 40) { g.beginPath(); g.moveTo(x, y); g.lineTo(x, y + 26); g.stroke(); } }
|
|
45
|
+
tiles(g, 900, 60, 960, 90, 30, C.lapis, C.cream, C.teal, 3);
|
|
46
|
+
// 切开的房间:亭(拱形开口),里面有地毯、两位读书人
|
|
47
|
+
const arch = new Path2D(); arch.moveTo(1040, 520); arch.lineTo(1040, 250); arch.quadraticCurveTo(1040, 170, 1260, 170); arch.quadraticCurveTo(1480, 170, 1480, 250); arch.lineTo(1480, 520); arch.closePath();
|
|
48
|
+
g.fillStyle = C.gold; g.fill(arch); g.save(); g.translate(0, 0); g.scale(1, 1); g.restore();
|
|
49
|
+
const inner = new Path2D(); inner.moveTo(1060, 520); inner.lineTo(1060, 256); inner.quadraticCurveTo(1060, 192, 1260, 192); inner.quadraticCurveTo(1460, 192, 1460, 256); inner.lineTo(1460, 520); inner.closePath();
|
|
50
|
+
g.save(); g.clip(inner); tiles(g, 1060, 180, 400, 340, 36, '#e7d6a6', C.lapis2, C.red, 5);
|
|
51
|
+
// 地毯:红地,边带,中央大八角菱形
|
|
52
|
+
fillPoly(g, [[1060, 420], [1460, 420], [1460, 520], [1060, 520]], C.red, { lw: 2.4 });
|
|
53
|
+
for (let x = 1076; x < 1460; x += 34) { g.fillStyle = C.gold; g.beginPath(); g.moveTo(x, 470); g.lineTo(x + 12, 456); g.lineTo(x + 24, 470); g.lineTo(x + 12, 484); g.fill(); }
|
|
54
|
+
g.restore(); g.strokeStyle = C.ink; g.lineWidth = 3; g.stroke(arch);
|
|
55
|
+
// 左侧塔楼+窗;右侧小窗格
|
|
56
|
+
fillPoly(g, [[1520, 150], [1840, 150], [1840, 500], [1520, 500]], C.cream, { lw: 3 }); tiles(g, 1520, 150, 320, 350, 32, C.cream, C.teal, C.lapis, 9);
|
|
57
|
+
[[1560, 200], [1700, 200], [1560, 340], [1700, 340]].forEach(([x, y]) => { fillPoly(g, [[x, y + 100], [x, y + 40], [x + 20, y], [x + 60, y], [x + 80, y + 40], [x + 80, y + 100]], C.lapis, { lw: 3, line: C.gold }); });
|
|
58
|
+
g.restore();
|
|
59
|
+
textBlock(g, 120, 120, 640, 200); textBlock(g, 120, 340, 640, 120);
|
|
60
|
+
// 下半:庭院平铺地面(竖起的挂毯)
|
|
61
|
+
g.save(); g.beginPath(); g.rect(60, 520, W - 120, H - 580); g.clip();
|
|
62
|
+
fillPoly(g, [[60, 520], [W - 60, 520], [W - 60, H - 60], [60, H - 60]], C.green, { line: false });
|
|
63
|
+
const r = rng(11); for (let i = 0; i < 1400; i++) { const x = 70 + r() * (W - 140), y = 530 + r() * (H - 600), k = r(); g.fillStyle = k < 0.5 ? '#86b05a' : k < 0.8 ? '#4d7c3a' : (r() < 0.5 ? '#f0e6c8' : C.rose); g.beginPath(); g.ellipse(x, y, 3 + r() * 5, 2 + r() * 3, r(), 0, TAU); g.fill(); }
|
|
64
|
+
// 水渠:从左上角的汲水轮底座流向八角水池,铺青瓷砖,几何边
|
|
65
|
+
g.lineCap = 'butt'; g.lineJoin = 'round'; [['#2b2118', 78], [C.gold, 72], [C.teal, 58]].forEach(([col, w]) => { g.strokeStyle = col; g.lineWidth = w; g.beginPath(); g.moveTo(260, 670); g.lineTo(790, 670); g.lineTo(930, 800); g.stroke(); });
|
|
66
|
+
// 八角水池:中央偏右
|
|
67
|
+
const cx = 1160, cy = 790, R = 230, oct = Array.from({ length: 8 }, (_, k) => [cx + Math.cos(k / 8 * TAU + Math.PI / 8) * R * 1.08, cy + Math.sin(k / 8 * TAU + Math.PI / 8) * R * 0.74]);
|
|
68
|
+
fillPoly(g, oct.map(p => [cx + (p[0] - cx) * 1.14, cy + (p[1] - cy) * 1.2]), C.gold, { lw: 3 });
|
|
69
|
+
fillPoly(g, oct.map(p => [cx + (p[0] - cx) * 1.06, cy + (p[1] - cy) * 1.1]), C.lapis, { lw: 3, line: C.gold });
|
|
70
|
+
fillPoly(g, oct, '#2c7e8a', { lw: 3, line: C.gold });
|
|
71
|
+
// 周围铺小道:奶油色菱格
|
|
72
|
+
for (let i = 0; i < 16; i++) { const x = 300 + i * 36, y = 760 + (i % 2) * 14; g.fillStyle = C.cream; g.beginPath(); g.moveTo(x, y); g.lineTo(x + 14, y - 10); g.lineTo(x + 28, y); g.lineTo(x + 14, y + 10); g.fill(); }
|
|
73
|
+
g.restore();
|
|
74
|
+
// 树:左右各一,直干+圆叶团+花点+果(树冠超出画面边缘上探,细密画常见)
|
|
75
|
+
function tree(x, y, hgt, seed, bloom) {
|
|
76
|
+
g.fillStyle = '#7a4a2a'; g.fillRect(x - 11, y - hgt, 22, hgt); g.strokeStyle = C.ink; g.lineWidth = 2.4; g.strokeRect(x - 11, y - hgt, 22, hgt);
|
|
77
|
+
const rr = rng(seed); for (let k = 0; k < 9; k++) { const a = rr() * TAU, d = rr() * 70, cx2 = x + Math.cos(a) * d, cy2 = y - hgt - 30 + Math.sin(a) * d * 0.7; g.fillStyle = k % 2 ? C.dgreen : '#4f8a3f'; g.beginPath(); g.ellipse(cx2, cy2, 58, 46, 0, 0, TAU); g.fill(); g.strokeStyle = C.ink; g.lineWidth = 1.8; g.stroke(); }
|
|
78
|
+
for (let k = 0; k < 40; k++) { const a = rr() * TAU, d = rr() * 100; g.fillStyle = bloom; g.beginPath(); g.arc(x + Math.cos(a) * d, y - hgt - 30 + Math.sin(a) * d * 0.6, 5, 0, TAU); g.fill(); }
|
|
79
|
+
}
|
|
80
|
+
tree(140, 720, 220, 31, '#f4d3d0'); tree(1790, 760, 240, 32, '#f4e6a0'); tree(700, 560, 120, 33, '#f4d3d0');
|
|
81
|
+
}
|
|
82
|
+
// 汲水轮:转;水斗按周期取模;水沿渠流
|
|
83
|
+
function wheel(c, t) {
|
|
84
|
+
const cx = 340, cy = 580, R = 120, a0 = t * 0.8;
|
|
85
|
+
c.save(); c.translate(cx, cy);
|
|
86
|
+
c.strokeStyle = C.ink; c.lineWidth = 3; c.fillStyle = '#9a6a3a'; c.beginPath(); c.arc(0, 0, R, 0, TAU); c.arc(0, 0, R - 14, 0, TAU, true); c.fill('evenodd'); c.stroke(); c.beginPath(); c.arc(0, 0, R - 14, 0, TAU); c.stroke();
|
|
87
|
+
for (let k = 0; k < 12; k++) { const a = a0 + k / 12 * TAU; c.save(); c.rotate(a); c.fillStyle = '#7a4a2a'; c.fillRect(-4, 0, 8, R - 14); c.strokeRect(-4, 0, 8, R - 14); c.fillStyle = C.gold; c.fillRect(-14, R - 6, 28, 16); c.strokeRect(-14, R - 6, 28, 16); c.restore(); }
|
|
88
|
+
c.fillStyle = C.red; c.beginPath(); c.arc(0, 0, 18, 0, TAU); c.fill(); c.stroke();
|
|
89
|
+
c.restore();
|
|
90
|
+
}
|
|
91
|
+
function water(c, t) {
|
|
92
|
+
// 渠水:沿折线 (260,670)→(790,670)→(930,800) 的金色小波,向右下流
|
|
93
|
+
const seg = [[260, 670, 790, 670], [790, 670, 930, 800]], Ls = seg.map(q => Math.hypot(q[2] - q[0], q[3] - q[1])), tot = Ls[0] + Ls[1];
|
|
94
|
+
for (let i = 0; i < 16; i++) { let d = ((i * tot / 16 + t * 90) % tot), k = d < Ls[0] ? 0 : 1; if (k) d -= Ls[0]; const q = seg[k], u = d / Ls[k], x = lerp(q[0], q[2], u), y = lerp(q[1], q[3], u) + ((i % 3) - 1) * 12;
|
|
95
|
+
c.strokeStyle = 'rgba(244,224,160,.85)'; c.lineWidth = 2.4; c.beginPath(); c.moveTo(x - 12, y); c.quadraticCurveTo(x, y - 7, x + 12, y); c.stroke(); }
|
|
96
|
+
// 水池:同心波+金鱼
|
|
97
|
+
const cx = 1160, cy = 790;
|
|
98
|
+
c.save(); c.beginPath(); for (let k = 0; k < 8; k++) { const ang = k / 8 * TAU + Math.PI / 8; c.lineTo(cx + Math.cos(ang) * 230 * 1.08, cy + Math.sin(ang) * 230 * 0.74); } c.closePath(); c.clip();
|
|
99
|
+
for (let i = 0; i < 6; i++) { const q = ((t * 0.35 + i / 6) % 1); c.strokeStyle = `rgba(244,230,180,${(1 - q) * 0.8})`; c.lineWidth = 2.6; c.beginPath(); c.ellipse(cx - 130 + Math.sin(i) * 40, cy - 20, 20 + q * 150, 8 + q * 50, 0, 0, TAU); c.stroke(); }
|
|
100
|
+
[[0, 1.0, C.red], [2.2, -0.8, C.gold], [4.1, 0.7, C.cream]].forEach(([ph, dir, col], i) => { const a = t * 0.6 * dir + ph, x = cx + Math.cos(a) * 140, y = cy + Math.sin(a) * 70 + 10, ang = Math.atan2(Math.cos(a) * 70 * dir, -Math.sin(a) * 140 * dir);
|
|
101
|
+
c.save(); c.translate(x, y); c.rotate(ang); c.fillStyle = col; c.strokeStyle = C.ink; c.lineWidth = 2; c.beginPath(); c.ellipse(0, 0, 30, 12, 0, 0, TAU); c.fill(); c.stroke(); c.beginPath(); c.moveTo(-28, 0); c.lineTo(-48, -12 + Math.sin(t * 8 + i) * 4); c.lineTo(-48, 12 + Math.sin(t * 8 + i) * 4); c.closePath(); c.fill(); c.stroke(); c.fillStyle = C.ink; c.beginPath(); c.arc(16, -3, 2.4, 0, TAU); c.fill(); c.restore(); });
|
|
102
|
+
c.restore();
|
|
103
|
+
}
|
|
104
|
+
// 人(萨法维式):修长,头小,高头巾+红色冠杆,束腰长袍下摆外展,腰带垂一端,鞋尖朝前;扁平填色+粗线描边,袍上小花散点。
|
|
105
|
+
function person(c, x, y, s, face, col, turban, t, o = {}) {
|
|
106
|
+
c.save(); c.translate(x, y); c.scale(s * face, s); c.lineJoin = 'round'; c.lineCap = 'round'; c.strokeStyle = C.ink; c.lineWidth = 2.6 / s;
|
|
107
|
+
const bob = o.bob || 0, sw = Math.sin(t * 1.7 + (o.ph || 0)) * 3;
|
|
108
|
+
// 鞋:尖头
|
|
109
|
+
c.fillStyle = C.red; [[-14, 0], [16, 0]].forEach(([dx]) => { c.beginPath(); c.moveTo(dx - 9, -4); c.quadraticCurveTo(dx + 10, -10, dx + 24, -2); c.quadraticCurveTo(dx + 12, 5, dx - 9, 4); c.closePath(); c.fill(); c.stroke(); });
|
|
110
|
+
// 长袍:窄肩 → 束腰 → 下摆外展,前襟交叠一道线
|
|
111
|
+
c.fillStyle = col; c.beginPath(); c.moveTo(-20, -152 + bob); c.quadraticCurveTo(-26, -118, -18, -96 + bob); c.quadraticCurveTo(-34, -52, -52 + sw, -6); c.quadraticCurveTo(0, 3, 52 + sw, -6); c.quadraticCurveTo(34, -52, 18, -96 + bob); c.quadraticCurveTo(26, -118, 20, -152 + bob); c.closePath(); c.fill(); c.stroke();
|
|
112
|
+
c.save(); c.clip(); c.fillStyle = 'rgba(244,224,160,.8)'; for (let k = 0; k < 24; k++) { const px = -44 + (k % 6) * 17 + (Math.floor(k / 6) % 2) * 8, py = -140 + Math.floor(k / 6) * 34; c.beginPath(); for (let q = 0; q < 5; q++) { const aa = q / 5 * TAU; c.moveTo(px, py); c.arc(px + Math.cos(aa) * 4.4, py + Math.sin(aa) * 4.4, 2.6, 0, TAU); } c.fill(); }
|
|
113
|
+
c.strokeStyle = 'rgba(43,33,24,.55)'; c.lineWidth = 1.6; c.beginPath(); c.moveTo(4, -148 + bob); c.quadraticCurveTo(10, -80, 30 + sw, -6); c.stroke(); c.restore();
|
|
114
|
+
c.strokeStyle = C.ink; c.lineWidth = 2.6 / s; c.strokeStyle = C.gold; c.lineWidth = 4; c.beginPath(); c.moveTo(-52 + sw, -6); c.quadraticCurveTo(0, 3, 52 + sw, -6); c.stroke(); c.strokeStyle = C.ink; c.lineWidth = 2.6 / s;
|
|
115
|
+
// 腰带+垂端
|
|
116
|
+
c.fillStyle = C.gold; c.fillRect(-19, -100 + bob * 0.5, 38, 9); c.strokeRect(-19, -100 + bob * 0.5, 38, 9);
|
|
117
|
+
c.fillStyle = C.cream; c.beginPath(); c.moveTo(10, -92 + bob * 0.5); c.lineTo(22, -60 + sw * 0.4); c.lineTo(10, -64 + sw * 0.4); c.closePath(); c.fill(); c.stroke();
|
|
118
|
+
// 手臂:默认一只垂袖,另一只抬到胸前持物
|
|
119
|
+
if (o.arm) o.arm(c, bob); else { c.strokeStyle = col; c.lineWidth = 11; c.beginPath(); c.moveTo(16, -144 + bob); c.lineTo(34, -108 + bob); c.lineTo(22, -90 + bob); c.stroke(); c.strokeStyle = C.ink; c.lineWidth = 2.6 / s; c.fillStyle = '#e8c298'; c.beginPath(); c.arc(22, -88 + bob, 5, 0, TAU); c.fill(); c.stroke(); }
|
|
120
|
+
// 颈与头:小,3/4,弯眉连成一道,杏仁眼,胡须一线
|
|
121
|
+
c.fillStyle = '#e8c298'; c.fillRect(-4, -160 + bob, 9, 12);
|
|
122
|
+
c.beginPath(); c.ellipse(3, -172 + bob, 15, 18, 0, 0, TAU); c.fill(); c.stroke();
|
|
123
|
+
c.fillStyle = C.ink; c.beginPath(); c.ellipse(10, -174 + bob, 3.8, 2.4, 0, 0, TAU); c.fill();
|
|
124
|
+
c.beginPath(); c.moveTo(-1, -181 + bob); c.quadraticCurveTo(7, -186 + bob, 17, -180 + bob); c.stroke();
|
|
125
|
+
c.beginPath(); c.moveTo(6, -161 + bob); c.quadraticCurveTo(13, -159 + bob, 17, -163 + bob); c.stroke();
|
|
126
|
+
// 头巾:白裹布+高耸的红冠杆(向前微倾),顶端一小簇羽
|
|
127
|
+
c.fillStyle = turban; c.beginPath(); c.ellipse(1, -188 + bob, 22, 11, 0, 0, TAU); c.fill(); c.stroke();
|
|
128
|
+
c.fillStyle = C.red; c.beginPath(); c.moveTo(-9, -192 + bob); c.quadraticCurveTo(-4, -226 + bob, 6 + sw * 0.3, -246 + bob); c.lineTo(12 + sw * 0.3, -244 + bob); c.quadraticCurveTo(12, -216 + bob, 14, -192 + bob); c.closePath(); c.fill(); c.stroke();
|
|
129
|
+
c.strokeStyle = C.gold; c.lineWidth = 3; c.beginPath(); c.moveTo(-8, -202 + bob); c.lineTo(13, -202 + bob); c.stroke(); c.strokeStyle = C.ink; c.lineWidth = 2.6 / s;
|
|
130
|
+
c.fillStyle = C.cream; c.beginPath(); c.ellipse(9 + sw * 0.3, -250 + bob, 4, 9, 0.4, 0, TAU); c.fill(); c.stroke();
|
|
131
|
+
c.restore();
|
|
132
|
+
}
|
|
133
|
+
function draw(c, lt) {
|
|
134
|
+
if (!layer) build();
|
|
135
|
+
const t = clamp(lt, 0, 6);
|
|
136
|
+
c.drawImage(layer, 0, 0);
|
|
137
|
+
wheel(c, t); water(c, t);
|
|
138
|
+
// 园丁:站在汲水轮旁摇杆
|
|
139
|
+
const crank = Math.sin(t * 0.8 * 3.0);
|
|
140
|
+
person(c, 530, 706, 1.05, 1, C.lapis2, C.cream, t, { bob: Math.sin(t * 2.4) * 2, arm: (cc, bob) => { const hx = -70 - crank * 20, hy = -104 - crank * 14; cc.strokeStyle = C.ink; cc.lineWidth = 14; cc.beginPath(); cc.moveTo(14, -144 + bob); cc.lineTo(-24 - crank * 8, -118 + bob); cc.lineTo(hx, hy); cc.stroke(); cc.strokeStyle = C.lapis2; cc.lineWidth = 10; cc.beginPath(); cc.moveTo(14, -144 + bob); cc.lineTo(-24 - crank * 8, -118 + bob); cc.lineTo(hx, hy); cc.stroke(); cc.strokeStyle = C.ink; cc.lineWidth = 2.6; cc.fillStyle = '#e8c298'; cc.beginPath(); cc.arc(hx, hy, 5.5, 0, TAU); cc.fill(); cc.stroke(); } });
|
|
141
|
+
// 亭下读书人(在建筑切面里):两人对坐,一个翻页
|
|
142
|
+
person(c, 1180, 520, 0.92, 1, C.red, C.cream, t, { ph: 0, bob: Math.sin(t * 1.4) * 1.5 });
|
|
143
|
+
person(c, 1370, 520, 0.92, -1, C.teal, C.cream, t, { ph: 2, bob: Math.sin(t * 1.4 + 2) * 1.5 });
|
|
144
|
+
// 花瓣与落叶:从树冠飘到池面
|
|
145
|
+
const r = rng(3); for (let i = 0; i < 22; i++) { const ph = r() * 6, sp = 0.4 + r() * 0.4, x0 = 160 + r() * 1600, q = ((t * sp * 0.2 + ph) % 1), x = x0 + Math.sin(t * 1.4 + i) * 24 + q * 80, y = 560 + q * 480; c.fillStyle = i % 2 ? '#f4d3d0' : '#f4e6a0'; c.strokeStyle = 'rgba(43,33,24,.5)'; c.lineWidth = 1.2; c.beginPath(); c.ellipse(x, y, 7, 4, t + i, 0, TAU); c.fill(); c.stroke(); }
|
|
146
|
+
c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.28; c.drawImage(P.texture('paper_pm', '#d6bf8a', { scale: 0.004, amt: 14, grain: 6, seed: 9 }), 0, 0); c.restore();
|
|
147
|
+
}
|
|
148
|
+
return { draw };
|
|
149
|
+
})();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// 南方公园式数字剪纸:一切都是被剪刀剪出来的色纸——圆头、梯形身体、直边、没有曲线细节;眼睛是两个白椭圆加黑点;
|
|
2
|
+
// 动画是「纸片绕枢轴转、整块平移」,嘴是一个黑色半椭圆开合;背景是几块粗糙的平涂纸。
|
|
3
|
+
// 题材(新构图):小镇雪地里的公交站,四个小孩缩着脖子等车,校车慢吞吞滑进来、车门啪地打开,孩子们一块一块地跳上去。
|
|
4
|
+
// 原作锚定(风格特征,非角色):①纸片感——每块形状边缘有轻微的剪刀抖动,表面有纸纤维 ②眼睛贴脸、黑点在白椭圆里
|
|
5
|
+
// ③身体是梯形/方块,手是单纯的圆头短棒,腿是两根黑短柱 ④色块平涂、只有一道细黑轮廓 ⑤镜头永远正对,层叠为主,不透视
|
|
6
|
+
// 管线:纸块(每块 P.cut 出抖动边缘)→ 纸纹相乘 → 枢轴动画。不使用任何具名角色的服装或配色组合。
|
|
7
|
+
SCENES['52_south_park'] = (() => {
|
|
8
|
+
const W = 1920, H = 1080, { clamp, lerp, rng, ss } = U, P = PAINT, TAU = Math.PI * 2;
|
|
9
|
+
const OUT = '#14120f';
|
|
10
|
+
const E = { lin: x => x, io: x => { x = clamp(x); return x * x * (3 - 2 * x); } };
|
|
11
|
+
// 剪纸:多边形(顶点)→ 抖动边缘,填色,细黑线。seed 固定所以确定性
|
|
12
|
+
function paper(c, pts, fill, { seed = 1, lw = 3, amp = 2.4, step = 18, shade = null } = {}) {
|
|
13
|
+
const p = P.cut(pts, seed, step, amp);
|
|
14
|
+
c.fillStyle = fill; c.fill(p);
|
|
15
|
+
if (shade) { c.save(); c.clip(p); c.fillStyle = shade; c.globalAlpha = 0.18; c.fillRect(-9999, -9999, 99999, 99999); c.restore(); c.globalAlpha = 1; }
|
|
16
|
+
if (lw) { c.strokeStyle = OUT; c.lineWidth = lw; c.lineJoin = 'round'; c.stroke(p); }
|
|
17
|
+
return p;
|
|
18
|
+
}
|
|
19
|
+
function circle(c, x, y, r, fill, seed, lw = 3) { const pts = Array.from({ length: 28 }, (_, i) => [x + Math.cos(i / 28 * TAU) * r, y + Math.sin(i / 28 * TAU) * r]); return paper(c, pts, fill, { seed, lw, amp: 1.2, step: 12 }); }
|
|
20
|
+
function ell(c, x, y, rx, ry, fill, seed, lw = 3) { const pts = Array.from({ length: 24 }, (_, i) => [x + Math.cos(i / 24 * TAU) * rx, y + Math.sin(i / 24 * TAU) * ry]); return paper(c, pts, fill, { seed, lw, amp: 0.8, step: 10 }); }
|
|
21
|
+
const tex = () => P.cached('sp_paper', W, H, g => {
|
|
22
|
+
g.fillStyle = '#fff'; g.fillRect(0, 0, W, H); const r = rng(5);
|
|
23
|
+
for (let i = 0; i < 9000; i++) { const x = r() * W, y = r() * H, a = r() * TAU, l = 3 + r() * 12; g.strokeStyle = r() < 0.5 ? 'rgba(140,130,115,.16)' : 'rgba(255,255,255,.5)'; g.lineWidth = 0.8; g.beginPath(); g.moveTo(x, y); g.lineTo(x + Math.cos(a) * l, y + Math.sin(a) * l); g.stroke(); }
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 小孩:head 圆、眼、嘴;parka 梯形;帽子;手;腿。o: x,y(脚底) s(缩放) skin parka hat tilt(头倾) talk(0..1 嘴开) armL armR(弧度,0=下垂) hop face
|
|
27
|
+
function kid(c, o) {
|
|
28
|
+
const { x, y, s = 1, skin = '#f5cfa8', parka, hat, band, tilt = 0, talk = 0, armL = 0, armR = 0, hop = 0, seed = 1, lookx = 0.5 } = o;
|
|
29
|
+
c.save(); c.translate(x, y - hop); c.scale(s, s);
|
|
30
|
+
// 腿
|
|
31
|
+
paper(c, [[-34, -52], [-10, -52], [-10, 0], [-34, 0]], '#1c1a18', { seed: seed + 1, lw: 0, amp: 1 }); paper(c, [[10, -52], [34, -52], [34, 0], [10, 0]], '#1c1a18', { seed: seed + 2, lw: 0, amp: 1 });
|
|
32
|
+
// 手臂(绕肩枢轴)
|
|
33
|
+
const arm = (sx, ang, k) => { c.save(); c.translate(sx, -150); c.rotate(ang); paper(c, [[-12, 0], [12, 0], [11, 84], [-11, 84]], parka, { seed: seed + 3 + k, lw: 3, amp: 1.2 }); circle(c, 0, 90, 17, '#f2ede0', seed + 5 + k, 3); c.restore(); };
|
|
34
|
+
arm(-70, armL, 0); arm(70, armR, 1);
|
|
35
|
+
// 身体:梯形,小孩缩着脖子
|
|
36
|
+
paper(c, [[-72, -170], [72, -170], [92, -52], [-92, -52]], parka, { seed: seed + 6, lw: 3.4, amp: 1.6 });
|
|
37
|
+
paper(c, [[-10, -170], [10, -170], [10, -52], [-10, -52]], 'rgba(0,0,0,.18)', { seed: seed + 7, lw: 0 }); // 拉链缝
|
|
38
|
+
// 头:绕颈枢轴倾斜
|
|
39
|
+
c.save(); c.translate(0, -168); c.rotate(tilt);
|
|
40
|
+
circle(c, 0, -62, 66, skin, seed + 8, 3.4);
|
|
41
|
+
// 帽:半个纸球+边带+绒球
|
|
42
|
+
paper(c, [[-66, -68], [-56, -112], [-30, -134], [0, -142], [30, -134], [56, -112], [66, -68]], hat, { seed: seed + 9, lw: 3.4, amp: 1.6 });
|
|
43
|
+
paper(c, [[-68, -66], [68, -66], [68, -86], [-68, -86]], band, { seed: seed + 10, lw: 3.4, amp: 1.2 });
|
|
44
|
+
circle(c, 0, -150, 18, band, seed + 11, 3);
|
|
45
|
+
// 眼:两个白椭圆紧贴,黑点在里面朝向 lookx
|
|
46
|
+
const ex = 15, ey = -62; ell(c, -ex, ey, 17, 21, '#fff', seed + 12, 3); ell(c, ex, ey, 17, 21, '#fff', seed + 13, 3);
|
|
47
|
+
const px = (lookx - 0.5) * 12; c.fillStyle = OUT; [-ex, ex].forEach(xx => { c.beginPath(); c.arc(xx + px, ey + 2, 3.8, 0, TAU); c.fill(); });
|
|
48
|
+
// 嘴:黑色半椭圆,开合
|
|
49
|
+
const m = 4 + talk * 20; c.fillStyle = OUT; c.beginPath(); c.ellipse(0, -22, 14 + talk * 6, m, 0, 0, Math.PI); c.fill(); if (talk > 0.4) { c.fillStyle = '#c4412f'; c.beginPath(); c.ellipse(0, -22 + m * 0.5, 9, m * 0.4, 0, 0, Math.PI); c.fill(); }
|
|
50
|
+
c.restore(); c.restore();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function draw(c, lt, t) {
|
|
54
|
+
const T = clamp(lt, 0, 6), r = rng(2);
|
|
55
|
+
// 天与远山:平涂纸块
|
|
56
|
+
c.fillStyle = '#c9dce6'; c.fillRect(0, 0, W, H);
|
|
57
|
+
paper(c, [[0, 0], [W, 0], [W, 330], [0, 330]], '#bcd3e0', { seed: 21, lw: 0, amp: 3 });
|
|
58
|
+
paper(c, [[-20, 520], [260, 300], [520, 440], [860, 250], [1260, 460], [1560, 330], [1960, 520]], '#e9eef0', { seed: 22, lw: 3.4, amp: 4, step: 30 });
|
|
59
|
+
paper(c, [[260, 300], [330, 360], [300, 410], [380, 400], [330, 460], [420, 460], [520, 440]], '#a9b9c6', { seed: 23, lw: 0, amp: 3 });
|
|
60
|
+
paper(c, [[860, 250], [920, 330], [880, 350], [960, 380], [900, 440], [1000, 450], [1260, 460]], '#a9b9c6', { seed: 24, lw: 0, amp: 3 });
|
|
61
|
+
// 松树:三角叠三层
|
|
62
|
+
[[1440, 560, 1.1], [1560, 580, 0.9], [200, 560, 1.0], [320, 580, 0.8]].forEach(([x, y, s], i) => { paper(c, [[x - 8 * s, y], [x + 8 * s, y], [x + 8 * s, y + 60 * s], [x - 8 * s, y + 60 * s]], '#6b4a30', { seed: 30 + i, lw: 3 }); [[0, 70], [-26, 120], [-52, 170]].forEach(([dy, hw], k) => paper(c, [[x, y - 40 * s - dy * s * 0.2 + k * -20 * s], [x - (50 + k * 18) * s, y + 20 * s - k * 0 - (2 - k) * -20 * s + 0], [x + (50 + k * 18) * s, y + 20 * s + (2 - k) * 20 * s * 0]], '#2f5d3c', { seed: 40 + i * 3 + k, lw: 3.4, amp: 2 })); });
|
|
63
|
+
// 雪地:两层白纸,前面一层带灰蓝阴影块
|
|
64
|
+
paper(c, [[-20, 600], [W + 20, 600], [W + 20, H + 20], [-20, H + 20]], '#f4f7f8', { seed: 50, lw: 3.4, amp: 3, step: 40 });
|
|
65
|
+
paper(c, [[-20, 840], [600, 800], [1300, 830], [W + 20, 810], [W + 20, H + 20], [-20, H + 20]], '#e6edf0', { seed: 51, lw: 0, amp: 4, step: 30 });
|
|
66
|
+
// 公路(灰纸)+黄虚线
|
|
67
|
+
paper(c, [[-20, 700], [W + 20, 700], [W + 20, 800], [-20, 800]], '#6c7378', { seed: 52, lw: 3.4, amp: 2.4, step: 30 });
|
|
68
|
+
for (let i = 0; i < 12; i++) paper(c, [[i * 170 + 20, 744], [i * 170 + 110, 744], [i * 170 + 110, 758], [i * 170 + 20, 758]], '#e8c13a', { seed: 60 + i, lw: 0, amp: 1.2 });
|
|
69
|
+
// 站牌:灰杆+红八角形牌(不写字,画一个 ■ 示意)
|
|
70
|
+
paper(c, [[470, 520], [490, 520], [490, 840], [470, 840]], '#8c9296', { seed: 70, lw: 3.4 });
|
|
71
|
+
paper(c, [[420, 400], [470, 372], [540, 372], [590, 400], [590, 468], [540, 496], [470, 496], [420, 468]], '#c8402e', { seed: 71, lw: 3.6, amp: 1.6 });
|
|
72
|
+
paper(c, [[470, 420], [540, 420], [540, 450], [470, 450]], '#f6efe0', { seed: 72, lw: 0 });
|
|
73
|
+
|
|
74
|
+
// 校车:从右滑入,停在 x≈1210,车门在 3.7 秒啪地打开;车轮不转(剪纸整块平移)
|
|
75
|
+
const bx = lerp(2300, 1230, E.io(ss(0.2, 3.2, T))), by = 760;
|
|
76
|
+
c.save(); c.translate(bx, by);
|
|
77
|
+
paper(c, [[-420, -250], [330, -250], [350, -210], [350, 60], [-420, 60]], '#f0b92a', { seed: 80, lw: 4, amp: 2 });
|
|
78
|
+
paper(c, [[-420, -250], [330, -250], [350, -210], [350, -190], [-420, -190]], '#f6cc55', { seed: 81, lw: 0 });
|
|
79
|
+
for (let i = 0; i < 5; i++) paper(c, [[-380 + i * 110, -170], [-300 + i * 110, -170], [-300 + i * 110, -90], [-380 + i * 110, -90]], '#bcd3e0', { seed: 90 + i, lw: 3.4, amp: 1.2 });
|
|
80
|
+
paper(c, [[-420, -20], [350, -20], [350, 0], [-420, 0]], '#14120f', { seed: 100, lw: 0 }); // 黑杠
|
|
81
|
+
const door = E.io(ss(3.6, 3.9, T)); paper(c, [[130, -190], [130 + 76 * (1 - door), -190], [130 + 76 * (1 - door), 40], [130, 40]], '#bcd3e0', { seed: 101, lw: 3.4, amp: 1 }); paper(c, [[130 + 76 * (1 - door), -190], [206, -190], [206, 40], [130 + 76 * (1 - door), 40]], '#2a2622', { seed: 102, lw: 3.4, amp: 1 });
|
|
82
|
+
[-250, 200].forEach((wx, i) => { circle(c, wx, 62, 62, '#14120f', 110 + i, 0); circle(c, wx, 62, 30, '#8c9296', 112 + i, 3); });
|
|
83
|
+
c.restore();
|
|
84
|
+
|
|
85
|
+
// 四个孩子(原创配色,不借具名角色的服装组合):站在站牌右侧等车;车停后单列依次走到车门、进门(进门=消失在车厢里)
|
|
86
|
+
const talkPhase = (k) => (Math.sin(T * 14 + k * 1.7) > 0.1 && T < 3.4 && (Math.floor(T * 1.3 + k) % 4 === k % 4)) ? 1 : 0;
|
|
87
|
+
const DOOR = 1400, STARTX = [720, 860, 1000, 1140];
|
|
88
|
+
const board = (k) => { const t0 = 3.9 + k * 0.5, q = clamp((T - t0) / 0.8); return { q, x: lerp(0, DOOR - STARTX[k], E.io(q)), hop: Math.abs(Math.sin(q * Math.PI * 3)) * 22 * (q > 0 && q < 1 ? 1 : 0) }; };
|
|
89
|
+
[
|
|
90
|
+
{ parka: '#d65a7a', hat: '#4a4a52', band: '#f0d34a', seed: 200, s: 1.12 },
|
|
91
|
+
{ parka: '#5a78c8', hat: '#d6803a', band: '#f2ece0', seed: 300, s: 1.04 },
|
|
92
|
+
{ parka: '#8a8f3a', hat: '#8a3a5a', band: '#e8d9a0', seed: 400, s: 1.16 },
|
|
93
|
+
{ parka: '#d9d2c0', hat: '#2f6a58', band: '#3a3a3a', seed: 500, s: 1.0 },
|
|
94
|
+
].forEach((k, i) => {
|
|
95
|
+
const j = board(i); if (j.q >= 1) return; // 进了车厢
|
|
96
|
+
const shiver = (T < 3.6) ? Math.sin(T * 30 + i) * 1.4 : 0, boarding = j.q > 0;
|
|
97
|
+
kid(c, { ...k, x: STARTX[i] + j.x + shiver, y: 842 + i % 2 * 10, hop: j.hop, tilt: Math.sin(T * 1.8 + i) * 0.07 + (T > 3.4 ? -0.1 : 0), talk: talkPhase(i), armL: Math.sin(T * 6 + i) * 0.05 + (boarding ? 0.5 : 0), armR: -0.05 + Math.sin(T * 5 + i) * 0.05 + (boarding ? -0.5 : 0), lookx: T > 1 ? 0.9 : 0.4 });
|
|
98
|
+
});
|
|
99
|
+
// 落雪:纸圆片,12fps 一格格往下掉(剪纸的步进感)
|
|
100
|
+
const st = Math.floor(T * 12) / 12; c.fillStyle = '#fff'; c.strokeStyle = 'rgba(100,110,120,.5)'; c.lineWidth = 1.4;
|
|
101
|
+
for (let i = 0; i < 130; i++) { const rr = rng(i * 7 + 1), x = (rr() * W + st * 40 * (0.5 + rr() * 0.6)) % W, y = ((rr() * H) + st * (90 + rr() * 90)) % H, s = 3 + rr() * 5; c.beginPath(); c.arc(x, y, s, 0, TAU); c.fill(); c.stroke(); }
|
|
102
|
+
c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.55; c.drawImage(tex(), 0, 0); c.restore();
|
|
103
|
+
}
|
|
104
|
+
return { draw };
|
|
105
|
+
})();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* Original un-faced laundry appliance. Programmed 3D geometry and acting; no film assets. */
|
|
2
|
+
SCENES['53_pixar']=(()=>{
|
|
3
|
+
let R,S,C,washer,drum,door,loose,clothGeo,clothes=[],bubbles=[],towelMeshes=[];
|
|
4
|
+
const T=()=>window.THREE, clamp=x=>Math.max(0,Math.min(1,x)),smooth=x=>{x=clamp(x);return x*x*(3-2*x)},mix=(a,b,u)=>a+(b-a)*u;
|
|
5
|
+
function mat(color,rough=.65,metal=0){return new THREE.MeshStandardMaterial({color,roughness:rough,metalness:metal});}
|
|
6
|
+
function box(x,y,z,w,h,d,m,parent=S){let o=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),m);o.position.set(x,y,z);o.castShadow=o.receiveShadow=true;parent.add(o);return o;}
|
|
7
|
+
function sphere(x,y,z,rx,ry,rz,m,parent=S){let o=new THREE.Mesh(new THREE.SphereGeometry(1,24,16),m);o.position.set(x,y,z);o.scale.set(rx,ry,rz);o.castShadow=true;parent.add(o);return o;}
|
|
8
|
+
function cylinder(x,y,z,r,h,m,parent=S){let o=new THREE.Mesh(new THREE.CylinderGeometry(r,r,h,48),m);o.position.set(x,y,z);o.castShadow=o.receiveShadow=true;parent.add(o);return o;}
|
|
9
|
+
function tube(points,r,m,parent=S){let curve=new THREE.CatmullRomCurve3(points.map(p=>new THREE.Vector3(...p)));let o=new THREE.Mesh(new THREE.TubeGeometry(curve,48,r,8,false),m);parent.add(o);return o;}
|
|
10
|
+
function rounded(w,h,d,r,m,parent){let s=new THREE.Shape(),x=-w/2,y=-h/2;s.moveTo(x+r,y);s.lineTo(x+w-r,y);s.quadraticCurveTo(x+w,y,x+w,y+r);s.lineTo(x+w,y+h-r);s.quadraticCurveTo(x+w,y+h,x+w-r,y+h);s.lineTo(x+r,y+h);s.quadraticCurveTo(x,y+h,x,y+h-r);s.lineTo(x,y+r);s.quadraticCurveTo(x,y,x+r,y);let g=new THREE.ExtrudeGeometry(s,{depth:d,bevelEnabled:true,bevelThickness:r/3,bevelSize:r/3,bevelSegments:3,steps:1,curveSegments:12});g.translate(0,0,-d/2);let o=new THREE.Mesh(g,m);o.castShadow=o.receiveShadow=true;parent.add(o);return o;}
|
|
11
|
+
function fabricMat(color){const cv=document.createElement('canvas');cv.width=cv.height=256;let g=cv.getContext('2d');g.fillStyle=color;g.fillRect(0,0,256,256);for(let i=0;i<256;i+=2){g.fillStyle=i%4?'rgba(255,255,255,.08)':'rgba(40,25,10,.10)';g.fillRect(i,0,1,256);g.fillRect(0,i,256,1)}let tx=new THREE.CanvasTexture(cv);tx.wrapS=tx.wrapT=THREE.RepeatWrapping;tx.repeat.set(3,3);tx.colorSpace=THREE.SRGBColorSpace;return new THREE.MeshStandardMaterial({map:tx,roughness:.95,side:THREE.DoubleSide,bumpMap:tx,bumpScale:.012})}
|
|
12
|
+
function fabricGeometry(){const nx=24,ny=16,g=new THREE.BufferGeometry(),a=[],idx=[],uv=[];for(let j=0;j<=ny;j++)for(let i=0;i<=nx;i++){a.push(i/nx,j/ny,0);uv.push(i/nx,j/ny)}for(let j=0;j<ny;j++)for(let i=0;i<nx;i++){let k=j*(nx+1)+i;idx.push(k,k+1,k+nx+1,k+1,k+nx+2,k+nx+1)}g.setAttribute('position',new THREE.Float32BufferAttribute(a,3));g.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));g.setIndex(idx);g.computeVertexNormals();return g;}
|
|
13
|
+
function init(){if(R)return;const THREE=T();if(!THREE)throw Error('53_pixar requires lib/three.min.js');
|
|
14
|
+
R=new THREE.WebGLRenderer({alpha:false,antialias:true,preserveDrawingBuffer:true});R.setSize(1280,720);R.setPixelRatio(1);R.shadowMap.enabled=true;R.shadowMap.type=THREE.PCFSoftShadowMap;R.outputColorSpace=THREE.SRGBColorSpace;R.toneMapping=THREE.ACESFilmicToneMapping;R.toneMappingExposure=1.05;
|
|
15
|
+
S=new THREE.Scene();S.background=new THREE.Color('#bed1bd');C=new THREE.PerspectiveCamera(37,16/9,.1,100);C.position.set(7.3,5.1,10.4);C.lookAt(.25,1.75,0);
|
|
16
|
+
S.add(new THREE.HemisphereLight('#fff1d5','#527674',1.15));let key=new THREE.DirectionalLight('#ffedcc',3.1);key.position.set(-4,8,6);key.castShadow=true;key.shadow.mapSize.set(2048,2048);key.shadow.camera.left=-9;key.shadow.camera.right=9;key.shadow.camera.top=9;key.shadow.camera.bottom=-9;key.shadow.normalBias=.035;S.add(key);let fill=new THREE.DirectionalLight('#bde4dc',.55);fill.position.set(6,4,-1);S.add(fill);
|
|
17
|
+
const tile=mat('#94b7ab'),grout=mat('#d5ddce'),cream=mat('#efdfbd'),wood=mat('#ae6a3d'),orange=mat('#c76e42'),steel=mat('#b2bdb9',.27,.7),dark=mat('#243e3d',.45,.25),rubber=mat('#4d5953',.9),linen=mat('#eadfca');
|
|
18
|
+
box(0,-.16,0,24,.25,18,mat('#d2c8ad'));for(let i=-8;i<=8;i++)for(let j=-5;j<=4;j++){let m=((i+j)%2)?mat('#d3cbb7'):mat('#c9bda3');box(i*.85,-.025,j*.85,.823,.025,.823,m)}
|
|
19
|
+
box(0,3.5,-3.1,18,7,.25,grout);box(-5.6,3.5,1.2,.2,7,8.4,mat('#b8c5ae'));
|
|
20
|
+
for(let i=-9;i<10;i++)for(let j=0;j<7;j++)box(i*.66,.37+j*.64,-2.945,.64,.615,.035,tile);
|
|
21
|
+
// sunlit window and sill
|
|
22
|
+
box(3.4,4.5,-2.8,3.5,2.65,.12,cream);box(3.4,4.5,-2.68,3.15,2.32,.12,mat('#d7eddd'));box(3.4,4.5,-2.57,.09,2.38,.1,wood);box(3.4,4.5,-2.57,3.2,.09,.1,wood);box(3.4,3.22,-2.45,3.8,.18,.72,cream);
|
|
23
|
+
for(let k=0;k<4;k++){let pot=cylinder(2.3+k*.65,3.48,-2.25,.19,.38,orange);for(let v=0;v<5;v++){let leaf=sphere(2.3+k*.65+(v-2)*.1,3.9+.1*Math.sin(v),-2.25,.08,.32,.045,mat('#517c52'));leaf.rotation.z=(v-2)*.35}}
|
|
24
|
+
// washing-machine body with rounded enamel, separate rubber and chrome
|
|
25
|
+
washer=new THREE.Group();washer.position.set(-.7,1.95,.45);S.add(washer);rounded(3.25,3.65,2.55,.18,mat('#e8dfc8',.36),washer);box(0,1.33,1.35,2.94,.62,.13,cream,washer);
|
|
26
|
+
for(let x of [-1.18,1.18])for(let z of [-.88,.88])cylinder(x,-1.84,z,.17,.24,rubber,washer);
|
|
27
|
+
let dial=cylinder(.96,1.34,1.47,.19,.19,steel,washer);dial.rotation.x=Math.PI/2;box(-.7,1.35,1.46,.96,.25,.065,dark,washer);box(-.75,1.35,1.504,.5,.035,.01,mat('#d7c36a'),washer);
|
|
28
|
+
let rim=new THREE.Mesh(new THREE.TorusGeometry(1.06,.12,20,72),steel);rim.position.set(0,-.12,1.4);washer.add(rim);let seal=new THREE.Mesh(new THREE.TorusGeometry(.91,.08,16,72),rubber);seal.position.set(0,-.12,1.43);washer.add(seal);
|
|
29
|
+
let cavity=cylinder(0,-.12,1.32,.93,.18,dark,washer);cavity.rotation.x=Math.PI/2;
|
|
30
|
+
drum=new THREE.Group();drum.position.set(0,-.12,1.47);washer.add(drum);for(let i=0;i<28;i++){let a=i*Math.PI*2/28;let hole=sphere(Math.cos(a)*.78,Math.sin(a)*.78,0,.035,.035,.025,steel,drum)}
|
|
31
|
+
for(let i=0;i<8;i++){const col=['#cb6b47','#b9d4c1','#cfaf65','#e3d4b9'][i%4];let m=sphere(Math.cos(i*1.7)*.48,Math.sin(i*1.7)*.48,.035,.33,.20,.09,fabricMat(col),drum);m.rotation.z=i*.8;clothes.push(m)}
|
|
32
|
+
door=new THREE.Group();door.position.set(-1.02,-.12,1.49);washer.add(door);let ring=new THREE.Mesh(new THREE.TorusGeometry(.99,.075,16,80),mat('#e5eee5',.2,.35));ring.position.set(1.02,0,0);door.add(ring);let glass=cylinder(1.02,0,-.015,.92,.045,new THREE.MeshPhysicalMaterial({color:'#d1e6de',transparent:true,opacity:.14,roughness:.08,metalness:.1,side:THREE.DoubleSide}),door);glass.rotation.x=Math.PI/2;
|
|
33
|
+
box(1.85,.08,.09,.1,.4,.14,steel,door);
|
|
34
|
+
// a trapped orange sleeve; cloth topology deforms continuously around the clamped edge
|
|
35
|
+
clothGeo=fabricGeometry();loose=new THREE.Mesh(clothGeo,fabricMat('#c86239'));loose.castShadow=true;washer.add(loose);
|
|
36
|
+
// Basket and lived-in room detail
|
|
37
|
+
cylinder(2.35,.54,1.25,.82,1.05,mat('#9e7450'));for(let k=0;k<10;k++){let rr=new THREE.Mesh(new THREE.TorusGeometry(.83,.018,5,64),cream);rr.rotation.x=Math.PI/2;rr.position.set(2.35,.08+k*.1,1.25);S.add(rr)}
|
|
38
|
+
for(let k=0;k<12;k++)sphere(2.35+Math.sin(k*4)*.51,1.03+Math.sin(k*1.2)*.09,1.25+Math.cos(k*4)*.4,.33,.19,.28,fabricMat(k%2?'#e5c67b':'#b8c6b5'));
|
|
39
|
+
box(-3.7,1.13,-1.3,2.2,.14,2,wood);for(let x of [-4.6,-2.8])box(x,.55,-1.3,.12,1.1,1.7,cream);for(let k=0;k<4;k++)box(-3.7,1.3+k*.13,-1.3,1.5,.12,1.2,mat(['#adbdad','#e1cfb2','#be7957','#d8bea0'][k]));
|
|
40
|
+
tube([[-4.8,4,-2],[-2.8,3.91,-2],[0,3.92,-2],[2,4,-2]],.025,wood);
|
|
41
|
+
for(let k=0;k<3;k++){const g=fabricGeometry(),m=new THREE.Mesh(g,new THREE.MeshStandardMaterial({color:['#dcbd88','#b7ccc1','#cf8465'][k],roughness:1,side:THREE.DoubleSide}));S.add(m);towelMeshes.push({m,g,x:-4.5+k*1.03})}
|
|
42
|
+
// Feet shadow and restrained machine details.
|
|
43
|
+
let cv=document.createElement('canvas');cv.width=cv.height=128;let cg=cv.getContext('2d');let sg=cg.createRadialGradient(64,64,5,64,64,62);sg.addColorStop(0,'rgba(23,39,32,.48)');sg.addColorStop(1,'rgba(23,39,32,0)');cg.fillStyle=sg;cg.fillRect(0,0,128,128);let sh=new THREE.Mesh(new THREE.PlaneGeometry(5,4),new THREE.MeshBasicMaterial({map:new THREE.CanvasTexture(cv),transparent:true,depthWrite:false}));sh.rotation.x=-Math.PI/2;sh.position.set(-.7,.015,.4);S.add(sh);
|
|
44
|
+
for(let k=0;k<6;k++)box(-.95+k*.15,-1.4,1.35,.085,.018,.018,rubber,washer);
|
|
45
|
+
let tc=document.createElement('canvas');tc.width=512;tc.height=128;let tg=tc.getContext('2d');tg.fillStyle='#eee4cc';tg.fillRect(0,0,512,128);tg.fillStyle='#52675e';tg.font='bold 38px sans-serif';tg.fillText('MORNING / 06',28,80);let tm=new THREE.Mesh(new THREE.PlaneGeometry(1.18,.29),new THREE.MeshBasicMaterial({map:new THREE.CanvasTexture(tc)}));tm.position.set(-.55,.91,1.45);washer.add(tm);
|
|
46
|
+
// Small scuffs, seams, baseboards avoid an empty toy stage.
|
|
47
|
+
box(0,.17,-2.78,18,.18,.12,wood);for(let k=0;k<9;k++)box(-5+1.1*k,.45,-2.73,.22,.025,.015,mat('#638e82'));
|
|
48
|
+
}
|
|
49
|
+
function draw(c,lt){init();const t=Math.max(0,Math.min(6,lt));
|
|
50
|
+
let jam=smooth((t-.9)/.5)*(1-smooth((t-3.5)/.5));let release=smooth((t-3.7)/.7);
|
|
51
|
+
washer.rotation.z=(Math.sin(t*31)*.028+Math.sin(t*17)*.016)*jam;washer.position.y=1.95+Math.abs(Math.sin(t*31))*.035*jam;washer.position.x=-.7+Math.sin(t*26)*.035*jam;
|
|
52
|
+
drum.rotation.z=t<1.4?t*4.5:6.3+Math.sin((t-1.4)*18)*.07*jam-(smooth((t-3.2)/1.2))*2.5+(t>4.5?(t-4.5)*1.4:0);
|
|
53
|
+
door.rotation.y=-smooth((t-3.25)/.65)*.68;
|
|
54
|
+
let a=clothGeo.attributes.position,nx=24,ny=16;for(let j=0;j<=ny;j++)for(let i=0;i<=nx;i++){let u=i/nx,v=j/ny,idx=j*(nx+1)+i;let tug=Math.sin(t*24-v*3)*.07*jam*(v+.1);let xx=.45+(u-.5)*.48+Math.sin(v*5+t*3)*.06*v;let yy=Math.max(-1.928+.006*Math.sin(u*24+v*8),-.99-v*1.12-release*2.05);let zz=1.59+v*.7+Math.sin(u*16+v*9)*.045+Math.sin(v*7-t*4)*v*.075+tug; a.setXYZ(idx,xx,yy,zz)}a.needsUpdate=true;clothGeo.computeVertexNormals();
|
|
55
|
+
for(const {g,x} of towelMeshes){let a=g.attributes.position;for(let j=0;j<=16;j++)for(let i=0;i<=24;i++){let u=i/24,v=j/16,idx=j*25+i;a.setXYZ(idx,x+u*.75,3.9-v*1.35,-2+Math.sin(u*15)*.045+Math.sin(t*1.9+v*3+x)*v*.08)}a.needsUpdate=true;g.computeVertexNormals()}
|
|
56
|
+
// Deliberately held camera: movement is in the machine, drum and cloth.
|
|
57
|
+
R.render(S,C);c.drawImage(R.domElement,0,0,1920,1080);
|
|
58
|
+
}
|
|
59
|
+
return {draw};
|
|
60
|
+
})();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* Original urban bicycle-repair kiosk. Hand-painted environment + mechanical contact. */
|
|
2
|
+
SCENES['54_ghibli_lab']=(()=>{
|
|
3
|
+
const T=Math.PI*2;let back,front;
|
|
4
|
+
const rng=n=>()=>((n=(n*1664525+1013904223)>>>0)/4294967296);
|
|
5
|
+
function P(g,p,col,stroke='#4e6158',w=2){g.beginPath();p.forEach((v,i)=>i?g.lineTo(...v):g.moveTo(...v));g.closePath();if(col){g.fillStyle=col;g.fill();}if(stroke){g.strokeStyle=stroke;g.lineWidth=w;g.stroke();}}
|
|
6
|
+
function L(g,p,col='#4e6158',w=2){g.beginPath();p.forEach((v,i)=>i?g.lineTo(...v):g.moveTo(...v));g.strokeStyle=col;g.lineWidth=w;g.lineCap='round';g.stroke();}
|
|
7
|
+
function R(g,x,y,w,h,col,stroke='#607363'){g.fillStyle=col;g.fillRect(x,y,w,h);if(stroke){g.strokeStyle=stroke;g.lineWidth=2;g.strokeRect(x,y,w,h);}}
|
|
8
|
+
function E(g,x,y,rx,ry,col){g.fillStyle=col;g.beginPath();g.ellipse(x,y,rx,ry,0,0,T);g.fill();}
|
|
9
|
+
function speck(g,x,y,w,h,n,seed,cols){const r=rng(seed);g.save();for(let i=0;i<n;i++){g.fillStyle=cols[i%cols.length];g.globalAlpha=.04+r()*.12;let xx=x+r()*w,yy=y+r()*h;g.beginPath();g.ellipse(xx,yy,2+r()*13,1+r()*4,r()*.4,0,T);g.fill();}g.restore();}
|
|
10
|
+
function pigment(canvas,seed){const g=canvas.getContext('2d'),r=rng(seed),src=g.getImageData(0,0,1920,1080),d=src.data;for(let i=0;i<72000;i++){let x=r()*1920,y=r()*1080,k=(Math.floor(y)*1920+Math.floor(x))*4;if(d[k+3]<180)continue;let delta=(r()-.5)*35;g.strokeStyle=`rgba(${d[k]+delta},${d[k+1]+delta},${d[k+2]+delta*.8},${.06+r()*.10})`;g.lineWidth=.6+r()*2.5;g.beginPath();g.moveTo(x,y);g.lineTo(x+3+r()*14,y+(r()-.5)*4);g.stroke();}const p=g.getImageData(0,0,1920,1080),a=p.data;for(let i=0;i<a.length;i+=4){if(a[i+3]<180)continue;let n=(r()-.5)*7;a[i]+=n;a[i+1]+=n;a[i+2]+=n;}g.putImageData(p,0,0);}
|
|
11
|
+
function window(g,x,y,w,h){R(g,x-5,y-5,w+10,h+10,'#c3ba8c');R(g,x,y,w,h,'#657f80');R(g,x+5,y+4,w-10,h*.44,'#94b2ac');L(g,[[x+w*.5,y],[x+w*.5,y+h]],'#e8ddb1',5);L(g,[[x,y+h*.5],[x+w,y+h*.5]],'#e8ddb1',5);L(g,[[x+4,y+h-4],[x+w-5,y+6]],'#d8dfbd55',4);}
|
|
12
|
+
function plant(g,x,y,s,seed){const r=rng(seed);P(g,[[x-30*s,y],[x+30*s,y],[x+23*s,y+48*s],[x-21*s,y+48*s]],'#a86f51');E(g,x,y,32*s,9*s,'#694e37');for(let i=0;i<20;i++){let a=r()*T,len=(30+r()*65)*s,xx=x+Math.cos(a)*len,yy=y-40*s-r()*60*s;L(g,[[x,y],[xx,yy]],'#4e7957',2*s);g.save();g.translate(xx,yy);g.rotate(a);E(g,0,0,17*s,6*s,i%3?'#60875a':'#9aaa62');L(g,[[-13*s,0],[14*s,0]],'#c2c77d',.7*s);g.restore();}}
|
|
13
|
+
function make(){if(back)return;back=document.createElement('canvas');back.width=1920;back.height=1080;let g=back.getContext('2d');let sky=g.createLinearGradient(0,0,0,620);sky.addColorStop(0,'#9dbeb8');sky.addColorStop(.7,'#d6d6ac');sky.addColorStop(1,'#efe2af');g.fillStyle=sky;g.fillRect(0,0,1920,1080);
|
|
14
|
+
// Hills and connected city blocks, observed roofs, wires and masonry.
|
|
15
|
+
P(g,[[0,270],[150,170],[300,208],[450,123],[620,210],[770,140],[950,258],[1250,177],[1510,242],[1920,120],[1920,650],[0,650]],'#95aa93',null);
|
|
16
|
+
for(let i=0;i<9;i++){let x=i*171-25,y=190+(i%3)*55,w=155;R(g,x,y,w,340,['#c9c8a4','#d0bba0','#adbcb0'][i%3]);P(g,[[x-8,y],[x+70,y-62],[x+w+10,y],[x+w,y+14],[x,y+14]],['#6c8e80','#ad7b64','#7f9a8b'][i%3]);P(g,[[x,y+14],[x+w,y+14],[x+w,y+38],[x,y+30]],'#516d6033',null);for(let row=0;row<2;row++)for(let j=0;j<2;j++){let wx=x+20+j*75,wy=y+45+row*113;window(g,wx,wy,42,70);if(i%2){R(g,wx-13,wy,9,70,'#788b71');for(let k=0;k<8;k++)L(g,[[wx-12,wy+k*9],[wx-5,wy+k*9]],'#566b58',1);}R(g,wx-7,wy+72,56,8,'#c1b78c');if(row===1&&i%3===0){R(g,wx-5,wy+65,51,15,'#a77b5a');for(let k=0;k<9;k++){E(g,wx+k*5,wy+60-(k%3)*5,6,7,'#6c8c5e');E(g,wx+k*5,wy+56-(k%3)*5,3,3,'#d69b7f');}}}L(g,[[x+w-8,y+20],[x+w-8,y+330],[x+w-21,y+337]],'#7d8e78',4);R(g,x+65,y-62,21,35,'#a39275');R(g,x+60,y-64,30,7,'#657c6c');if(i%3===1){R(g,x+18,y+277,120,55,'#5d8279');for(let k=0;k<6;k++)P(g,[[x+10+k*23,y+265],[x+33+k*23,y+265],[x+40+k*23,y+292],[x+17+k*23,y+292]],k%2?'#d3ba8b':'#b87859');}speck(g,x,y,w,330,130,i+54,['#7e9b85','#fff2b3','#9e8e73']);}
|
|
17
|
+
R(g,0,547,1920,183,'#b2b4a0');L(g,[[0,643],[1920,643]],'#e0d6ac',5);L(g,[[0,678],[1920,678]],'#6f857a',4);L(g,[[0,695],[1920,695]],'#e0d6ac',3);
|
|
18
|
+
for(let i=0;i<50;i++){let x=(i*127)%1920,y=562+(i*43)%140;L(g,[[x,y],[x+42,y-2]],'#859b8b44',2);}
|
|
19
|
+
// A plane tree's irregular leaf masses interrupt the repetitive street rhythm.
|
|
20
|
+
L(g,[[1258,572],[1255,437],[1215,339]],'#6d8066',18);L(g,[[1251,451],[1320,355]],'#6d8066',10);const treeR=rng(558);for(let i=0;i<620;i++){let a=treeR()*T,rad=Math.sqrt(treeR())*127,xx=1250+Math.cos(a)*rad,yy=329+Math.sin(a)*rad*.7;E(g,xx,yy,8+treeR()*17,5+treeR()*10,['#678b6b','#7e9b70','#a4b77d','#88a575','#bbbf89'][i%5]);}
|
|
21
|
+
// Overhead cable follows the rail direction and powers the tram's pantograph.
|
|
22
|
+
L(g,[[-20,158],[490,186],[980,179],[1510,164]],'#677e72',3);L(g,[[74,0],[74,562]],'#76846e',9);L(g,[[60,154],[380,154]],'#76846e',5);
|
|
23
|
+
speck(g,0,0,1920,1080,2900,545,['#fff0bd','#6c927f','#b5b28b']);
|
|
24
|
+
pigment(back,555);
|
|
25
|
+
front=document.createElement('canvas');front.width=1920;front.height=1080;g=front.getContext('2d');
|
|
26
|
+
// Open kiosk structure leaves most of the street unobstructed.
|
|
27
|
+
P(g,[[1415,0],[1920,0],[1920,1080],[1550,1080],[1435,716]],'#d8ccaa');speck(g,1415,0,505,1080,1700,549,['#a3ac91','#fff0bd','#b29976']);
|
|
28
|
+
R(g,1432,0,42,754,'#526e65');R(g,1474,0,14,754,'#d4d0a2');R(g,0,0,1440,64,'#54796b');R(g,0,64,1440,12,'#a9b28b');
|
|
29
|
+
P(g,[[0,720],[1448,702],[1920,1080],[0,1080]],'#b9b99b');speck(g,0,720,1920,360,1300,550,['#718d7c','#e9dcb0','#a89d78']);
|
|
30
|
+
for(let i=-4;i<11;i++)L(g,[[100+i*170,720],[50+i*256,1080]],'#83948155',2);for(let y=778;y<1080;y+=70)L(g,[[0,y],[1920,y]],'#81968044',2);
|
|
31
|
+
// Strong, coherent morning light, tied to the aperture and objects.
|
|
32
|
+
P(g,[[118,720],[846,713],[1290,1080],[367,1080]],'#f5e8af66',null);P(g,[[1435,716],[1484,716],[1747,1080],[1670,1080]],'#5a75642d',null);
|
|
33
|
+
// Wall tools with handles, hooks and tiny marks instead of ornamental symbols.
|
|
34
|
+
R(g,1557,129,313,344,'#a88f68');R(g,1567,139,293,324,'#c6b68b');for(let y=160;y<455;y+=25)for(let x=1582;x<1850;x+=25)E(g,x,y,1.4,1.4,'#957d5b');
|
|
35
|
+
for(let i=0;i<4;i++){let x=1610+i*65;L(g,[[x,179],[x,191]],'#71684c',3);if(i%2){L(g,[[x,195],[x,330]],'#737f70',11);E(g,x,208,17,22,'#7c8b7b');E(g,x,207,8,14,'#c6b68b');R(g,x-9,307,18,66,'#a05c45');}else{L(g,[[x,195],[x,336]],'#6e7f71',10);L(g,[[x-19,195],[x-14,221],[x+14,221],[x+19,195]],'#667c70',7);R(g,x-8,319,16,47,'#637d69');}}
|
|
36
|
+
// Long well-used repair bench, a perspective volume with imperfect wood grain.
|
|
37
|
+
P(g,[[1025,688],[1687,674],[1830,747],[1128,779]],'#b39061');P(g,[[1128,779],[1830,747],[1830,785],[1128,815]],'#8c704e');P(g,[[1025,688],[1128,779],[1128,815],[1025,726]],'#857556');for(let i=0;i<14;i++)L(g,[[1061+i*22,704+i*3],[1720+i*3,701+i*3]],'#dec18c66',1.4);
|
|
38
|
+
R(g,1150,809,24,271,'#7a7451');R(g,1763,783,30,297,'#7a7451');R(g,1180,947,580,18,'#8c805a');R(g,1224,831,277,67,'#ad9065');R(g,1235,839,254,48,'#bd9f70');L(g,[[1338,858],[1380,858]],'#6f795b',6);P(g,[[1450,927],[1651,927],[1674,947],[1475,947]],'#c6ab78');R(g,1475,897,199,50,'#ad9164');for(let j=0;j<5;j++)L(g,[[1490+j*38,900],[1490+j*38,942]],'#927e59',2);
|
|
39
|
+
// Two tins, packed parts tray, oil can, rag and bicycle pump.
|
|
40
|
+
E(g,1632,711,61,15,'#536655');R(g,1573,650,118,60,'#9cae91');E(g,1632,650,59,14,'#c9ccb0');E(g,1632,648,47,9,'#697d68');for(let i=0;i<8;i++){let x=1594+i*11;L(g,[[x,646],[x+6,604-i%3*13]],'#566f66',3);}
|
|
41
|
+
P(g,[[1395,727],[1528,724],[1570,743],[1433,753]],'#687c6e');for(let i=0;i<12;i++)E(g,1417+(i*37)%123,733+(i%3)*4,8,3,'#c1c8a6');
|
|
42
|
+
P(g,[[1691,671],[1738,665],[1750,708],[1698,712]],'#b4774d');L(g,[[1717,669],[1725,635],[1776,621]],'#b4774d',7);R(g,1705,656,22,13,'#647667');
|
|
43
|
+
// A bicycle at the open edge; tubes, spokes, chain and cast shadow agree.
|
|
44
|
+
E(g,586,1025,381,26,'#5a79602b');for(const x of [358,815]){g.beginPath();g.arc(x,908,123,0,T);g.strokeStyle='#455a52';g.lineWidth=13;g.stroke();g.strokeStyle='#bec7a5';g.lineWidth=3;g.beginPath();g.arc(x,908,111,0,T);g.stroke();for(let i=0;i<32;i++){let a=i*T/32;L(g,[[x,908],[x+Math.cos(a)*111,908+Math.sin(a)*111]],'#8a9f8d',1.2);}E(g,x,908,9,9,'#516d61');}
|
|
45
|
+
const red='#9c6254';L(g,[[358,908],[493,733],[574,908],[358,908]],red,10);L(g,[[493,733],[735,744],[574,908],[735,744],[815,908]],red,10);L(g,[[493,733],[478,691]],'#576f62',9);L(g,[[438,686],[507,686]],'#4e6052',16);L(g,[[735,744],[719,666],[773,643],[795,652]],'#536e63',9);E(g,574,908,31,31,'#66816c');E(g,574,908,23,23,'#9cae8c');L(g,[[574,908],[604,931],[633,930]],'#4c675c',6);L(g,[[360,908],[574,934],[574,882],[360,908]],'#718773',2);R(g,292,765,138,14,'#7b8e74');L(g,[[315,778],[336,888]],'#788974',3);L(g,[[420,778],[373,888]],'#788974',3);
|
|
46
|
+
plant(g,102,892,1.35,547);plant(g,945,787,.85,548);plant(g,1832,662,.8,551);
|
|
47
|
+
// Blue enamel kettle on an actual burner, cup and detached bicycle lamp.
|
|
48
|
+
E(g,1285,750,82,19,'#53695b44');R(g,1214,712,137,34,'#75816b');E(g,1284,714,50,13,'#435b50');
|
|
49
|
+
E(g,1283,672,59,58,'#4d8587');g.save();g.beginPath();g.ellipse(1283,672,59,58,0,0,T);g.clip();E(g,1308,696,52,51,'#396f78');E(g,1268,652,35,42,'#6caaa3');g.restore();P(g,[[1238,683],[1192,643],[1198,633],[1252,654]],'#6e9e99');E(g,1284,629,42,10,'#89b1a3');E(g,1284,621,13,8,'#4e6861');g.beginPath();g.arc(1288,649,51,Math.PI,0);g.strokeStyle='#566b58';g.lineWidth=10;g.stroke();L(g,[[1247,661],[1243,685]],'#adc6ac',5);
|
|
50
|
+
E(g,1396,711,35,9,'#526d5933');R(g,1371,667,48,44,'#e3d0a0');E(g,1395,667,24,7,'#eae0b8');E(g,1395,668,18,4,'#856c4c');g.beginPath();g.arc(1422,686,14,-1.6,1.6);g.strokeStyle='#c9b68b';g.lineWidth=6;g.stroke();
|
|
51
|
+
pigment(front,556);
|
|
52
|
+
}
|
|
53
|
+
function tram(g,x,t){g.save();g.translate(x,0);E(g,475,691,534,23,'#506d6830');for(let xx of [140,785]){E(g,xx,657,47,47,'#3e5b56');E(g,xx,657,31,31,'#86978a');for(let i=0;i<8;i++){let a=i*T/8-t*4;L(g,[[xx,657],[xx+Math.cos(a)*27,657+Math.sin(a)*27]],'#485f56',3);}}
|
|
54
|
+
P(g,[[-40,389],[-17,343],[49,325],[905,325],[959,354],[978,624],[947,664],[-13,664]],'#b76543','#556b5d',3);R(g,-20,459,979,104,'#e0bd7c');P(g,[[-25,350],[944,350],[936,392],[-28,392]],'#d7c8a0');
|
|
55
|
+
for(let i=0;i<7;i++){let xx=24+i*125;R(g,xx,376,103,145,'#465e5a');R(g,xx+5,381,93,105,'#8fb5ad');P(g,[[xx+5,381],[xx+35,381],[xx+93,461],[xx+93,486],[xx+70,486]],'#d6dfbe55',null);R(g,xx+5,491,93,25,'#5d7770');L(g,[[xx+52,381],[xx+52,486]],'#d6c89f',3);}
|
|
56
|
+
R(g,850,365,98,263,'#b7754d');R(g,860,380,76,134,'#7ea099');L(g,[[899,380],[899,626]],'#e6bf7f',3);L(g,[[859,592],[939,592]],'#9f6341',3);E(g,944,586,12,17,'#f1d393');L(g,[[2,628],[946,628]],'#e3b777',4);
|
|
57
|
+
// Roof pantograph stays in contact with the overhead wire.
|
|
58
|
+
L(g,[[450,323],[410,260],[486,194],[570,260],[523,323]],'#5d7568',5);L(g,[[460,193],[520,193]],'#526e62',6);R(g,353,313,280,16,'#688276');
|
|
59
|
+
speck(g,-10,535,952,112,180,554,['#f4c58a','#8b5b41']);g.restore();}
|
|
60
|
+
return {draw(c,lt){let t=Math.max(0,lt);make();c.drawImage(back,0,0);c.save();c.beginPath();c.rect(0,75,1435,645);c.clip();tram(c,1270-t*390,t);c.restore();c.drawImage(front,0,0);
|
|
61
|
+
// Steam rises continuously from the spout; translucent curls are attached to it.
|
|
62
|
+
for(let i=0;i<5;i++){let q=(t*.35+i*.2)%1,x=1193-24*q+Math.sin(t*1.8+i)*10,y=636-q*150;c.beginPath();c.moveTo(x,y);c.bezierCurveTo(x-23,y-22,x+31,y-34,x+5,y-57);c.strokeStyle=`rgba(240,237,207,${(1-q)*.47})`;c.lineWidth=4+q*9;c.stroke();}
|
|
63
|
+
// A hanging shop cloth responds to the tram's wake, with a fixed top seam.
|
|
64
|
+
let wind=Math.sin(t*2)*10+18*Math.exp(-Math.pow((t-3)/1.5,2));c.beginPath();c.moveTo(57,84);c.lineTo(256,84);c.bezierCurveTo(244,161,270+wind,235,248+wind,312);c.quadraticCurveTo(153+wind,333,65+wind*.7,310);c.bezierCurveTo(74+wind,217,54,144,57,84);c.closePath();c.fillStyle='#839c8fe8';c.fill();c.strokeStyle='#5a7a6d';c.lineWidth=2;c.stroke();for(let i=0;i<6;i++){c.beginPath();c.moveTo(78+i*30,85);c.quadraticCurveTo(65+i*30+wind*.5,196,78+i*30+wind,315);c.strokeStyle='#b8c4a078';c.lineWidth=2;c.stroke();}
|
|
65
|
+
// Dust only where morning light catches it, not a whole-frame particle layer.
|
|
66
|
+
for(let i=0;i<23;i++){let x=525+(i*47)%510+Math.sin(t*.4+i)*8,y=703+(i*73)%280-Math.sin(t*.3+i)*9;E(c,x,y,1.2,1.2,'#fff0c0aa');}
|
|
67
|
+
}};
|
|
68
|
+
})();
|