@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,347 @@
|
|
|
1
|
+
// Y1 · Kurzgesagt 式扁平科普:「AI 是怎么学会认猫的」
|
|
2
|
+
// 三镜:①夜晚山坡,机器人扫描一只猫(视差 5 层、慢推) → 转场 A「钻进镜头」:相机指数推进机器人眼睛,眼睛里是下一个世界
|
|
3
|
+
// ②机器人脑中:像素猫 → 神经网络逐层点亮(边缘→形状→猫) → 转场 B「填满再拉出」:推进点亮的输出节点直到满屏黄,切到机器人发黄光的眼睛再指数拉远
|
|
4
|
+
// ③白天草坡:花叔招手,机器人认出一只没见过的黑猫,标签弹出
|
|
5
|
+
// 时间全部用片内时间 ft = t - F0。镜头表在 Y1.SHOTS。
|
|
6
|
+
// 用到的库:CAM(世界相机、视差层、对数缩放、推进锚点)、TOON(双色分面 flat、glow)、TY.charsIn(逐字上浮)、MO(k75 等缓动)。
|
|
7
|
+
(() => {
|
|
8
|
+
const W = 1920, H = 1080;
|
|
9
|
+
const { clamp, lerp, rng } = U;
|
|
10
|
+
const seg = MO.seg;
|
|
11
|
+
const Y1 = window.Y1 = {};
|
|
12
|
+
const F0 = 0; // 本片在时间轴上的起点
|
|
13
|
+
Y1.F0 = F0;
|
|
14
|
+
Y1.SHOTS = { s1: [0, 2.6], s2: [2.6, 5.3], s3: [5.3, 8.0] };
|
|
15
|
+
const T1 = { a: 2.6, d: 1.0 }, T2 = { a: 5.3, d: 0.9 };
|
|
16
|
+
// 调研 §6.2:官方「其他动作用 75% influence」≈ cubic-bezier(.75,0,.25,1);循环用 Easy Ease ≈ (.333,0,.667,1)
|
|
17
|
+
const K75 = MO.k75;
|
|
18
|
+
|
|
19
|
+
// ---------- 配色(夜 / 机内 / 昼 三套,Kurzgesagt 换场景=换整套色板) ----------
|
|
20
|
+
const C = {
|
|
21
|
+
// 色值取自调研 §7.1(2025 深色系实测):深靛紫底+青/橙黄/品红三个强调色
|
|
22
|
+
night: { sky: ['#05020F', '#0E0631', '#2a1a6e'], star: '#F9FCFB', moon: '#FAF3CC', moonSh: '#E2C34E', hillFar: '#1a1260', hillFarHi: '#261c86', hill: '#2b1f86', hillHi: '#3a2c9e', hillSh: '#1f1668', fg: '#0E0631', grass: '#34269a' },
|
|
23
|
+
mind: { bg: ['#010718', '#130C4B'], grid: 'rgba(125,211,220,.07)', line: 'rgba(95,88,219,.35)', node: '#191670', nodeHi: '#3826A3', pulse: '#32BAEE', hot: '#FACC12', label: '#7DD3DC' },
|
|
24
|
+
day: { sky: ['#3fb4f5', '#8fd8ff', '#dcf6ff'], sun: '#fff1a8', cloud: '#ffffff', cloudSh: '#d6efff', hillFar: '#9ad66b', hillMid: '#6cc04d', ground: '#4ea83f', groundHi: '#66c24c', tree: '#2f8f3c', treeHi: '#4fb34f', trunk: '#8a5a3c' },
|
|
25
|
+
cat: { fur: '#F48301', sh: '#BF5E07', hi: '#ffab4a', belly: '#ffe0b5', ear: '#C83C7F', eye: '#ffffff', pupil: '#0E0631' },
|
|
26
|
+
bot: { shell: '#E7DFE1', sh: '#b3a6d6', hi: '#ffffff', face: '#0E0631', eye: '#32BAEE', eyeHot: '#FACC12', ant: '#BE3A95' },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ---------- 扁平+双色阴影(Kurzgesagt 的「无描边、靠色块分面」):TOON.flat;视差层:CAM.layer ----------
|
|
30
|
+
const flat = TOON.flat, glow = TOON.glow, circ = TOON.circle, ell = TOON.ellipse, vgrad = TOON.vgrad, layer = CAM.layer;
|
|
31
|
+
Y1.flat = flat;
|
|
32
|
+
const poly = (pts, smooth = true) => smooth ? RIG.smooth(pts, true) : U.poly(pts);
|
|
33
|
+
|
|
34
|
+
// ---------- 角色:猫 ----------
|
|
35
|
+
// 原点 = 坐姿底部中心;face=-1 朝左
|
|
36
|
+
Y1.cat = (c, x, y, ft, { s = 1, face = -1, pal = C.cat, look = 0 } = {}) => {
|
|
37
|
+
const P = pal; c.save(); c.translate(x, y); c.scale(face * -s, s); // 本地朝左(-x 为脸)
|
|
38
|
+
const br = Math.sin(ft * 2.6) * 0.012; // 呼吸
|
|
39
|
+
c.scale(1 - br, 1 + br);
|
|
40
|
+
// 尾巴:两段贝塞尔,尖端画 8 字摆
|
|
41
|
+
const tw = Math.sin(ft * 2.2) * 0.5, tw2 = Math.sin(ft * 2.2 - 0.9) * 0.6;
|
|
42
|
+
const tail = new Path2D(); const tx = 120 + Math.cos(tw) * 30, ty = -150 + Math.sin(tw) * 40;
|
|
43
|
+
c.lineCap = 'round'; c.strokeStyle = P.sh; c.lineWidth = 30;
|
|
44
|
+
c.beginPath(); c.moveTo(70, -20); c.bezierCurveTo(150, -10, 170, -80, tx, ty); c.quadraticCurveTo(tx + 30 * Math.cos(tw2), ty - 40, tx - 10 + 30 * Math.sin(tw2), ty - 70); c.stroke();
|
|
45
|
+
c.strokeStyle = P.fur; c.lineWidth = 22; c.stroke();
|
|
46
|
+
// 身体:水滴形坐姿
|
|
47
|
+
const body = poly([[-70, 0], [-82, -60], [-64, -140], [-20, -190], [36, -186], [74, -140], [92, -60], [86, 0]]);
|
|
48
|
+
flat(c, body, P.fur, P.sh, P.hi, [-14, 12], [-5, 5]);
|
|
49
|
+
flat(c, poly([[-58, -4], [-62, -70], [-40, -140], [-6, -150], [10, -100], [10, -4]]), P.belly, '#f3c99a', '#fff3e0', [-8, 8], [-3, 3]);
|
|
50
|
+
// 前爪
|
|
51
|
+
flat(c, ell(-38, -6, 26, 14), P.belly, '#efc392', '#fff', [-4, 4], [-2, 2]);
|
|
52
|
+
flat(c, ell(12, -6, 26, 14), P.hi, P.sh, '#fff', [-4, 4], [-2, 2]);
|
|
53
|
+
// 头
|
|
54
|
+
c.save(); c.translate(-16, -210); c.rotate(look * 0.12 + Math.sin(ft * 1.3) * 0.03);
|
|
55
|
+
const earL = poly([[-70, -30], [-82, -118], [-20, -64]], false), earR = poly([[20, -64], [64, -120], [72, -30]], false);
|
|
56
|
+
flat(c, earL, P.fur, P.sh, P.hi, [-6, 6], [-2, 2]); flat(c, earR, P.fur, P.sh, P.hi, [-6, 6], [-2, 2]);
|
|
57
|
+
c.fillStyle = P.ear; c.fill(poly([[-62, -44], [-72, -98], [-32, -62]], false)); c.fill(poly([[30, -62], [58, -100], [62, -44]], false));
|
|
58
|
+
const head = ell(0, 0, 86, 74);
|
|
59
|
+
flat(c, head, P.fur, P.sh, P.hi, [-14, 12], [-5, 5]);
|
|
60
|
+
flat(c, ell(-12, 26, 46, 30), P.belly, '#f3c99a', '#fff', [-6, 6], [-2, 2]);
|
|
61
|
+
// 眼:大白眼+瞳孔看向 look 方向,眨眼
|
|
62
|
+
const bl = (ft % 3.1) > 2.98 ? 0.15 : 1;
|
|
63
|
+
for (const ex of [-44, 16]) {
|
|
64
|
+
c.save(); c.translate(ex, -8); c.scale(1, bl);
|
|
65
|
+
c.fillStyle = P.eye; c.beginPath(); c.ellipse(0, 0, 19, 23, 0, 0, Math.PI * 2); c.fill();
|
|
66
|
+
c.fillStyle = P.pupil; c.beginPath(); c.ellipse(-6 - look * 3, 2, 11, 16, 0, 0, Math.PI * 2); c.fill();
|
|
67
|
+
c.fillStyle = '#fff'; c.beginPath(); c.arc(-10 - look * 3, -5, 4.5, 0, Math.PI * 2); c.fill();
|
|
68
|
+
c.restore();
|
|
69
|
+
}
|
|
70
|
+
c.fillStyle = '#ff6f8a'; c.beginPath(); c.moveTo(-22, 18); c.lineTo(-6, 18); c.lineTo(-14, 28); c.closePath(); c.fill();
|
|
71
|
+
// 胡须
|
|
72
|
+
c.strokeStyle = 'rgba(255,255,255,.75)'; c.lineWidth = 3;
|
|
73
|
+
for (const k of [-1, 1]) { c.beginPath(); c.moveTo(-40, 26 + k * 6); c.lineTo(-104, 18 + k * 16 + Math.sin(ft * 3) * 2); c.stroke(); }
|
|
74
|
+
c.restore();
|
|
75
|
+
c.restore();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ---------- 角色:机器人(AI) ----------
|
|
79
|
+
// 原点 = 头部中心;返回眼睛中心(世界坐标)
|
|
80
|
+
Y1.bot = (c, x, y, ft, { eye = C.bot.eye, eyeR = 34, look = 0, s = 1 } = {}) => {
|
|
81
|
+
const P = C.bot; c.save(); c.translate(x, y); c.scale(s, s); c.rotate(Math.sin(ft * 1.7) * 0.04);
|
|
82
|
+
// 喷射光
|
|
83
|
+
const jf = 1 + Math.sin(ft * 23) * 0.08 + Math.sin(ft * 37) * 0.05;
|
|
84
|
+
glow(c, 0, 128, 70 * jf, '#7df9ff', 0.55);
|
|
85
|
+
c.fillStyle = '#c9fbff'; c.beginPath(); c.moveTo(-22, 96); c.quadraticCurveTo(0, 96 + 70 * jf, 22, 96); c.closePath(); c.fill();
|
|
86
|
+
flat(c, (() => { const p = new Path2D(); p.roundRect(-46, 70, 92, 34, 14); return p; })(), '#d6d3fb', P.sh, P.hi, [6, 8], [2, 3]);
|
|
87
|
+
// 天线
|
|
88
|
+
c.strokeStyle = P.sh; c.lineWidth = 8; c.lineCap = 'round'; c.beginPath(); c.moveTo(0, -84); c.quadraticCurveTo(8, -120, 0 + Math.sin(ft * 3) * 6, -142); c.stroke();
|
|
89
|
+
const ab = 0.6 + 0.4 * Math.max(0, Math.sin(ft * 5));
|
|
90
|
+
glow(c, Math.sin(ft * 3) * 6, -148, 34, P.ant, 0.6 * ab); flat(c, circ(Math.sin(ft * 3) * 6, -148, 13), P.ant, '#d84a7b', '#ffc2d6', [3, 3], [1, 1]);
|
|
91
|
+
// 头壳
|
|
92
|
+
const shell = new Path2D(); shell.roundRect(-118, -88, 236, 176, 64);
|
|
93
|
+
flat(c, shell, P.shell, P.sh, P.hi, [16, 18], [6, 7]);
|
|
94
|
+
// 侧耳
|
|
95
|
+
for (const k of [-1, 1]) flat(c, (() => { const p = new Path2D(); p.roundRect(k * 118 - 14, -30, 28, 60, 12); return p; })(), '#d6d3fb', P.sh, P.hi, [4, 4], [2, 2]);
|
|
96
|
+
// 屏幕脸
|
|
97
|
+
const face = new Path2D(); face.roundRect(-88, -60, 176, 120, 44); c.fillStyle = P.face; c.fill(face);
|
|
98
|
+
c.save(); c.clip(face); c.fillStyle = 'rgba(255,255,255,.07)'; c.beginPath(); c.ellipse(-40, -50, 90, 30, -0.3, 0, Math.PI * 2); c.fill(); c.restore();
|
|
99
|
+
// 眼睛(镜头)
|
|
100
|
+
const ex = look * 14, ey = 0;
|
|
101
|
+
glow(c, ex, ey, eyeR * 2.6, eye, 0.55);
|
|
102
|
+
const g = c.createRadialGradient(ex - eyeR * 0.25, ey - eyeR * 0.25, 0, ex, ey, eyeR); g.addColorStop(0, '#ffffff'); g.addColorStop(0.35, eye); g.addColorStop(1, eye);
|
|
103
|
+
c.fillStyle = g; c.beginPath(); c.arc(ex, ey, eyeR, 0, Math.PI * 2); c.fill();
|
|
104
|
+
c.strokeStyle = 'rgba(255,255,255,.55)'; c.lineWidth = 3; c.beginPath(); c.arc(ex, ey, eyeR * 0.7, -2.4, -1.2); c.stroke();
|
|
105
|
+
c.restore();
|
|
106
|
+
const cs = Math.cos(Math.sin(ft * 1.7) * 0.04), sn = Math.sin(Math.sin(ft * 1.7) * 0.04);
|
|
107
|
+
return [x + (ex * cs - ey * sn) * s, y + (ex * sn + ey * cs) * s, eyeR * s];
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ---------- 小鸟(Kurzgesagt 的签名角色:圆身、大眼、三角喙) ----------
|
|
111
|
+
Y1.bird = (c, x, y, ft, { s = 1, col = '#ff5d8f', sh = '#d63c70', face = 1, ph = 0 } = {}) => {
|
|
112
|
+
c.save(); c.translate(x, y); c.scale(face * s, s);
|
|
113
|
+
const fl = Math.sin((ft + ph) * 2 * Math.PI / 0.36);
|
|
114
|
+
const wing = poly([[-6, -6], [-30, -6 - 34 * fl], [-44, -2 - 30 * fl], [-14, 8]], false);
|
|
115
|
+
c.fillStyle = sh; c.fill(wing);
|
|
116
|
+
flat(c, ell(0, 0, 30, 24), col, sh, '#ffa3c2', [5, 5], [2, 2]);
|
|
117
|
+
c.fillStyle = '#fff1f5'; c.beginPath(); c.ellipse(6, 8, 18, 12, 0, 0, Math.PI * 2); c.fill();
|
|
118
|
+
c.fillStyle = '#ffb02e'; c.beginPath(); c.moveTo(26, -4); c.lineTo(44, 2); c.lineTo(26, 8); c.closePath(); c.fill();
|
|
119
|
+
c.fillStyle = '#fff'; c.beginPath(); c.arc(12, -6, 9, 0, Math.PI * 2); c.fill(); c.fillStyle = '#1d1640'; c.beginPath(); c.arc(15, -5, 5, 0, Math.PI * 2); c.fill();
|
|
120
|
+
c.fillStyle = col; c.beginPath(); c.moveTo(-26, -4); c.lineTo(-44, -12); c.lineTo(-40, 6); c.closePath(); c.fill();
|
|
121
|
+
c.restore();
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// ---------- 镜 1:夜晚山坡 ----------
|
|
125
|
+
const BOT1 = [700, 470];
|
|
126
|
+
const s1cam = (ft) => {
|
|
127
|
+
// 慢推:0→2.6s 从 1.00 推到 1.10(sine 进出),中心略偏向机器人
|
|
128
|
+
const k = MO.sineInOut(seg(ft, 0, 2.6));
|
|
129
|
+
const cam = { x: lerp(960, 900, k), y: lerp(540, 520, k), z: lerp(1.0, 1.1, k) };
|
|
130
|
+
if (ft <= T1.a) return cam;
|
|
131
|
+
// 钻进镜头:0.9s 对数插值推进 1.1 → 60,眼睛中心平滑移到画面中心(缓动用 sineIn 偏 InOut:起步不拖,中段最快)
|
|
132
|
+
const e = K75(Math.pow(seg(ft, T1.a, T1.a + T1.d), 0.85));
|
|
133
|
+
const eye = Y1.eyeWorld(ft);
|
|
134
|
+
const k2 = MO.cubicOut(seg(ft, T1.a, T1.a + T1.d * 0.7)); // 眼睛移到画面中心比推进先完成
|
|
135
|
+
return { ...CAM.pushTo(cam, eye[0], eye[1], 60, e, k2), e };
|
|
136
|
+
};
|
|
137
|
+
Y1.botPos = (ft) => [BOT1[0] + Math.sin(ft * 1.9) * 6, BOT1[1] + Math.sin(ft * 2.4) * 12];
|
|
138
|
+
Y1.eyeWorld = (ft) => { const [bx, by] = Y1.botPos(ft); const a = Math.sin(ft * 1.7) * 0.04; const ex = 0.6 * 14; return [bx + ex * Math.cos(a), by + ex * Math.sin(a), 34]; };
|
|
139
|
+
// 转场 A 要的「镜头圆」:屏幕上的圆心与半径
|
|
140
|
+
Y1.lensAt = (t) => { const ft = t - F0, cam = s1cam(ft), e = Y1.eyeWorld(ft), s = CAM.toScreen(cam, e[0], e[1]); return { x: s[0], y: s[1], r: e[2] * cam.z, e: cam.e || 0 }; };
|
|
141
|
+
|
|
142
|
+
const STARS = (() => { const r = rng(11), o = []; for (let i = 0; i < 170; i++) o.push({ x: r() * 2400 - 240, y: r() * 760 - 80, r: 0.8 + r() * r() * 3.2, ph: r() * 6.28, sp: 1 + r() * 2.5 }); return o; })();
|
|
143
|
+
const GRASS = (() => { const r = rng(12), o = []; for (let i = 0; i < 46; i++) o.push({ x: 820 + r() * 900, h: 18 + r() * 26, ph: r() * 6.28 }); return o; })();
|
|
144
|
+
const MOTES = (() => { const r = rng(13), o = []; for (let i = 0; i < 26; i++) o.push({ x: r() * W, y: 300 + r() * 600, ph: r() * 6.28, d: 0.8 + r() * 0.5, s: 2 + r() * 3 }); return o; })();
|
|
145
|
+
|
|
146
|
+
Y1.shot1 = (c, lt, t) => {
|
|
147
|
+
const ft = t - F0, P = C.night, cam = s1cam(ft);
|
|
148
|
+
c.fillStyle = vgrad(c, 0, H, P.sky); c.fillRect(0, 0, W, H);
|
|
149
|
+
// d=0.05 星空:闪烁
|
|
150
|
+
layer(c, cam, 0.05, g => { for (const s of STARS) { const a = 0.45 + 0.55 * (0.5 + 0.5 * Math.sin(ft * s.sp + s.ph)); g.globalAlpha = a; g.fillStyle = P.star; g.beginPath(); g.arc(s.x, s.y, s.r, 0, Math.PI * 2); g.fill(); } g.globalAlpha = 1; });
|
|
151
|
+
// d=0.12 月亮:光晕呼吸
|
|
152
|
+
layer(c, cam, 0.12, g => { glow(g, 1500, 230, 260 + Math.sin(ft * 1.2) * 12, '#ffd9a0', 0.22); flat(g, circ(1500, 230, 92), P.moon, P.moonSh, '#fff8ea', [-18, 14], [-6, 5]);
|
|
153
|
+
g.fillStyle = 'rgba(214,180,130,.45)'; for (const [x, y, r] of [[1470, 205, 16], [1528, 262, 11], [1520, 196, 7]]) { g.beginPath(); g.arc(x, y, r, 0, Math.PI * 2); g.fill(); } });
|
|
154
|
+
// d=0.3 远山
|
|
155
|
+
layer(c, cam, 0.3, g => { const p = poly([[-300, 760], [-80, 640], [200, 700], [520, 610], [860, 690], [1180, 600], [1520, 680], [1840, 620], [2200, 700], [2200, 1300], [-300, 1300]]); flat(g, p, P.hillFar, P.hillFar, P.hillFarHi, [0, -10], [0, 4]); });
|
|
156
|
+
// d=0.65 近坡
|
|
157
|
+
layer(c, cam, 0.65, g => { const p = poly([[-300, 900], [200, 840], [700, 820], [1100, 740], [1500, 760], [1900, 840], [2300, 900], [2300, 1400], [-300, 1400]]); flat(g, p, P.hill, P.hillSh, P.hillHi, [0, -14], [0, 5]); });
|
|
158
|
+
// d=1 主体:山顶小丘、草、猫、机器人与扫描光
|
|
159
|
+
layer(c, cam, 1, g => {
|
|
160
|
+
flat(g, poly([[860, 1200], [960, 860], [1180, 790], [1400, 810], [1620, 900], [1760, 1200]]), P.hill, P.hillSh, P.hillHi, [0, -12], [0, 5]);
|
|
161
|
+
// 草叶:填色的细三角(不用线条——线条会读成描边)
|
|
162
|
+
g.fillStyle = P.grass;
|
|
163
|
+
for (const q of GRASS) { const by = 806 + Math.pow((q.x - 1240) / 420, 2) * 70; const sw = Math.sin(ft * 2.1 + q.ph) * 6; g.beginPath(); g.moveTo(q.x - 5, by + 10); g.quadraticCurveTo(q.x + sw * 0.4, by - q.h * 0.5, q.x + sw, by - q.h); g.quadraticCurveTo(q.x + 2 + sw * 0.3, by - q.h * 0.4, q.x + 5, by + 10); g.closePath(); g.fill(); }
|
|
164
|
+
// 扫描光束:从眼睛扇形照到猫,扫描线上下扫
|
|
165
|
+
const [ex, ey] = Y1.eyeWorld(ft), cx = 1240, sy = 640 + Math.sin(ft * 3.2) * 110;
|
|
166
|
+
const beam = new Path2D(); beam.moveTo(ex, ey); beam.lineTo(cx + 40, 420); beam.lineTo(cx + 60, 830); beam.closePath();
|
|
167
|
+
const bg = g.createLinearGradient(ex, ey, cx, ey); bg.addColorStop(0, 'rgba(50,186,238,.45)'); bg.addColorStop(1, 'rgba(50,186,238,.06)');
|
|
168
|
+
g.fillStyle = bg; g.fill(beam);
|
|
169
|
+
Y1.cat(g, 1240, 810, ft, { s: 1.05, face: -1, look: 0.4 });
|
|
170
|
+
g.save(); g.clip(beam); g.strokeStyle = 'rgba(150,245,255,.9)'; g.lineWidth = 5; g.beginPath(); g.moveTo(1080, sy); g.lineTo(1420, sy); g.stroke(); glow(g, 1240, sy, 160, '#46e6ff', 0.18); g.restore();
|
|
171
|
+
const [bx, by] = Y1.botPos(ft); Y1.bot(g, bx, by, ft, { look: 0.6 });
|
|
172
|
+
});
|
|
173
|
+
// d=0.9–1.3 萤火微粒
|
|
174
|
+
for (const m of MOTES) layer(c, cam, m.d, g => { const x = m.x + Math.sin(ft * 0.8 + m.ph) * 30, y = m.y - ((ft * 22 + m.ph * 40) % 120); g.globalAlpha = 0.5 + 0.5 * Math.sin(ft * 2 + m.ph); glow(g, x, y, m.s * 5, '#ffe9a8', 0.5); g.fillStyle = '#fff6d0'; g.beginPath(); g.arc(x, y, m.s, 0, Math.PI * 2); g.fill(); g.globalAlpha = 1; });
|
|
175
|
+
// d=1.5 前景叶片剪影(近景最快,景深靠它)
|
|
176
|
+
// 前景叶片:虚焦(景深),实测 2025 片大量用前景虚化
|
|
177
|
+
layer(c, cam, 1.5, g => { const sw = Math.sin(ft * 1.4) * 0.05; g.filter = 'blur(7px)';
|
|
178
|
+
const leaf = (x, y, a, L, w) => { g.save(); g.translate(x, y); g.rotate(a); const p = poly([[0, 0], [w, -L * 0.35], [w * 0.7, -L * 0.75], [0, -L], [-w * 0.7, -L * 0.75], [-w, -L * 0.35]]);
|
|
179
|
+
flat(g, p, P.fg, '#0a0824', '#241e5a', [8, 0], [3, 0]); g.strokeStyle = '#241e5a'; g.lineWidth = 4; g.beginPath(); g.moveTo(0, -10); g.lineTo(0, -L * 0.9); g.stroke(); g.restore(); };
|
|
180
|
+
leaf(-30, 1130, -0.75 + sw, 380, 90); leaf(60, 1150, -0.25 + sw * 1.3, 300, 70); leaf(1960, 1150, 0.6 - sw, 400, 95); g.filter = 'none'; });
|
|
181
|
+
// 标题:逐字上浮(0.25s 起,每字错开 45ms,单字 0.5s cubicOut),钻镜头前淡出
|
|
182
|
+
const out = 1 - MO.sineInOut(seg(ft, 1.95, 2.5)); // 0.55s 淡出,别一帧消失
|
|
183
|
+
if (out > 0) { c.save(); c.globalAlpha = out;
|
|
184
|
+
TY.charsIn(c, 'AI 是怎么学会认猫的?', W / 2, 150, ft - 0.25, { font: '76px "PuHui-Heavy"', color: '#ffffff', dur: 0.5, stagger: 0.045, dy: 30, align: 'center', shadow: { color: 'rgba(10,8,40,.45)', y: 6, blur: 0 } });
|
|
185
|
+
c.restore(); }
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// ---------- 镜 2:机器脑内 ----------
|
|
189
|
+
const PIX = [
|
|
190
|
+
'..O......O..', '.OOO....OOO.', '.OPOOOOOOPO.', '.OOOOOOOOOO.', 'OOWWOOOOWWOO', 'OOWKOOOOWKOO',
|
|
191
|
+
'OOOOOOOOOOOO', 'OOOOCPPCOOOO', 'OOOCCCCCCOOO', '.OOOCCCCOOO.', '..OOOOOOOO..', '....OOOO....'];
|
|
192
|
+
const PCOL = { O: '#F48301', P: '#C83C7F', W: '#ffffff', K: '#0E0631', C: '#ffe0b5' };
|
|
193
|
+
const LAYERS = [{ x: 860, n: 4, label: '边缘' }, { x: 1120, n: 5, label: '形状' }, { x: 1370, n: 3, label: '部件' }, { x: 1600, n: 1, label: '猫', out: true }];
|
|
194
|
+
LAYERS.forEach((L, i) => { L.ys = []; for (let k = 0; k < L.n; k++) L.ys.push(L.n === 1 ? 520 : 520 + (k - (L.n - 1) / 2) * (L.n > 4 ? 118 : 140)); L.i = i; });
|
|
195
|
+
const OUT = [1600, 520, 50];
|
|
196
|
+
const pulseT = 1.0; // 信号从像素出发的时刻(镜内时间)
|
|
197
|
+
const layerHit = i => pulseT + 0.32 * (i + 1); // 第 i 层被点亮的时刻
|
|
198
|
+
Y1.outNode = OUT;
|
|
199
|
+
const s2cam = (ft) => {
|
|
200
|
+
const lt = ft - Y1.SHOTS.s2[0];
|
|
201
|
+
// 从镜头里浮出:0→0.9s z 0.42→1.0(expoOut),之后极慢漂移推进
|
|
202
|
+
const k = MO.expoOut(seg(lt, 0, 1.2));
|
|
203
|
+
let cam = { x: lerp(1020, 1000, k) + lt * 12, y: 540, z: CAM.zlerp(0.42, 1.0, k) * (1 + 0.012 * lt) };
|
|
204
|
+
if (ft <= T2.a) return cam;
|
|
205
|
+
// 推进输出节点直到满屏(0.45s,cubicIn)
|
|
206
|
+
const e = MO.cubicIn(seg(ft, T2.a, T2.a + T2.d * 0.5));
|
|
207
|
+
return CAM.pushTo(cam, OUT[0], OUT[1], 26, e);
|
|
208
|
+
};
|
|
209
|
+
const DUST = (() => { const r = rng(21), o = []; for (let i = 0; i < 90; i++) o.push({ x: r() * 2600 - 340, y: r() * 1500 - 210, r: 1 + r() * 2.5, d: 0.3 + r() * 0.5, ph: r() * 6.28 }); return o; })();
|
|
210
|
+
const nodeIcon = (g, li, k, x, y, a) => {
|
|
211
|
+
g.save(); g.globalAlpha = a; g.strokeStyle = '#e8f6ff'; g.fillStyle = '#e8f6ff'; g.lineWidth = 5; g.lineCap = 'round'; g.lineJoin = 'round';
|
|
212
|
+
if (li === 0) { const ang = [0, Math.PI / 2, Math.PI / 4, -Math.PI / 4][k]; g.beginPath(); g.moveTo(x - Math.cos(ang) * 16, y - Math.sin(ang) * 16); g.lineTo(x + Math.cos(ang) * 16, y + Math.sin(ang) * 16); g.stroke(); }
|
|
213
|
+
if (li === 1) { g.beginPath(); if (k === 0 || k === 4) { g.moveTo(x - 14, y + 10); g.lineTo(x, y - 14); g.lineTo(x + 14, y + 10); g.closePath(); } else if (k === 1) { g.ellipse(x, y, 10, 14, 0, 0, Math.PI * 2); } else if (k === 2) { g.moveTo(x - 16, y - 6); g.lineTo(x + 16, y - 10); g.moveTo(x - 16, y + 6); g.lineTo(x + 16, y + 8); } else { g.arc(x, y, 13, 0.2, Math.PI - 0.2); } g.stroke(); }
|
|
214
|
+
if (li === 2) { g.beginPath(); if (k === 0) { g.moveTo(x - 18, y + 8); g.lineTo(x - 10, y - 14); g.lineTo(x - 2, y + 2); g.moveTo(x + 2, y + 2); g.lineTo(x + 10, y - 14); g.lineTo(x + 18, y + 8); } else if (k === 1) { g.ellipse(x - 9, y, 6, 9, 0, 0, Math.PI * 2); g.moveTo(x + 15, y); g.ellipse(x + 9, y, 6, 9, 0, 0, Math.PI * 2); } else { g.moveTo(x - 6, y - 4); g.lineTo(x + 6, y - 4); g.lineTo(x, y + 4); g.closePath(); g.moveTo(x - 20, y + 2); g.lineTo(x - 8, y + 4); g.moveTo(x + 8, y + 4); g.lineTo(x + 20, y + 2); } g.stroke(); }
|
|
215
|
+
g.restore();
|
|
216
|
+
};
|
|
217
|
+
Y1.shot2 = (c, lt, t) => {
|
|
218
|
+
const ft = t - F0, P = C.mind, cam = s2cam(ft), l = ft - Y1.SHOTS.s2[0];
|
|
219
|
+
c.fillStyle = vgrad(c, 0, H, P.bg); c.fillRect(0, 0, W, H);
|
|
220
|
+
// 远景:点阵网格+漂浮尘埃(视差)
|
|
221
|
+
layer(c, cam, 0.35, g => { g.fillStyle = P.grid; for (let y = -400; y < 1500; y += 60) for (let x = -600; x < 2600; x += 60) { g.beginPath(); g.arc(x, y, 2.4, 0, Math.PI * 2); g.fill(); } });
|
|
222
|
+
for (const d of DUST) layer(c, cam, d.d, g => { g.globalAlpha = 0.35 + 0.35 * Math.sin(ft * 1.5 + d.ph); g.fillStyle = '#8fb0ff'; g.beginPath(); g.arc(d.x + Math.sin(ft * 0.6 + d.ph) * 20, d.y + Math.cos(ft * 0.5 + d.ph) * 14, d.r, 0, Math.PI * 2); g.fill(); g.globalAlpha = 1; });
|
|
223
|
+
layer(c, cam, 1, g => {
|
|
224
|
+
// 像素猫:144 块从四周飞入就位(每块 0.5s backOut,按到中心的距离错开),之后整体轻微呼吸
|
|
225
|
+
const cell = 30, gx = 400 - 6 * cell, gy = 520 - 6 * cell, r = rng(31);
|
|
226
|
+
for (let j = 0; j < 12; j++) for (let i = 0; i < 12; i++) {
|
|
227
|
+
const ch = PIX[j][i], fx = r() * 2 - 1, fy = r() * 2 - 1; if (ch === '.') continue;
|
|
228
|
+
const dl = 0.05 + Math.hypot(i - 5.5, j - 5.5) * 0.035, q = clamp((l - dl) / 0.5), e = MO.backOut(q, 1.4);
|
|
229
|
+
const x = gx + i * cell + (1 - e) * fx * 260, y = gy + j * cell + (1 - e) * fy * 260, s = (cell - 4) * (0.3 + 0.7 * e) * (1 + 0.04 * Math.sin(ft * 3 + i * 0.5 + j * 0.3));
|
|
230
|
+
g.globalAlpha = clamp(q * 3); g.fillStyle = PCOL[ch]; g.beginPath(); g.roundRect(x + (cell - s) / 2, y + (cell - s) / 2, s, s, 5); g.fill();
|
|
231
|
+
}
|
|
232
|
+
g.globalAlpha = 1;
|
|
233
|
+
// 连线:像素 → 第一层 → … 全连接
|
|
234
|
+
const cols = [{ x: 600, ys: [380, 470, 570, 660] }, ...LAYERS];
|
|
235
|
+
g.lineWidth = 2.5;
|
|
236
|
+
for (let a = 0; a < cols.length - 1; a++) for (const y0 of cols[a].ys) for (const y1 of cols[a + 1].ys) {
|
|
237
|
+
const lit = clamp((l - layerHit(a) + 0.1) / 0.3);
|
|
238
|
+
g.strokeStyle = lit > 0 ? `rgba(${lerp(110, 120, lit) | 0},${lerp(140, 230, lit) | 0},255,${0.22 + 0.25 * lit})` : P.line;
|
|
239
|
+
g.beginPath(); g.moveTo(cols[a].x, y0); g.lineTo(cols[a + 1].x, y1); g.stroke();
|
|
240
|
+
}
|
|
241
|
+
// 信号脉冲:沿每条连线跑,0.32s 跑完一层;之后循环流动(持续的「思考」)
|
|
242
|
+
for (let a = 0; a < cols.length - 1; a++) {
|
|
243
|
+
const rr = rng(40 + a);
|
|
244
|
+
for (const y0 of cols[a].ys) for (const y1 of cols[a + 1].ys) {
|
|
245
|
+
if (rr() < 0.45) continue;
|
|
246
|
+
const start = pulseT + 0.32 * a, ph = rr() * 0.3;
|
|
247
|
+
let q = (l - start - ph * 0.3) / 0.32; if (q < 0) continue; if (q > 1) q = ((l - start) * 1.6 + ph) % 1;
|
|
248
|
+
const x = lerp(cols[a].x, cols[a + 1].x, q), y = lerp(y0, y1, q);
|
|
249
|
+
glow(g, x, y, 22, P.pulse, 0.6); g.fillStyle = '#e9fdff'; g.beginPath(); g.arc(x, y, 5, 0, Math.PI * 2); g.fill();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// 节点:被信号点亮时弹一下(backOut 0.35s)+显示该层学到的特征图标
|
|
253
|
+
for (const L of LAYERS) L.ys.forEach((y, k) => {
|
|
254
|
+
const hit = layerHit(L.i) + k * 0.04, q = clamp((l - hit) / 0.35), pop = q > 0 ? 1 + 0.25 * Math.sin(Math.PI * clamp(q * 1.2)) : 1;
|
|
255
|
+
const r = (L.out ? OUT[2] : 34) * pop * (1 + 0.03 * Math.sin(ft * 4 + k));
|
|
256
|
+
if (L.out) {
|
|
257
|
+
if (q > 0) glow(g, L.x, y, r * (3.2 + 0.4 * Math.sin(ft * 5)), P.hot, 0.55 * q);
|
|
258
|
+
const gg = g.createRadialGradient(L.x - r * 0.3, y - r * 0.3, 0, L.x, y, r); gg.addColorStop(0, q > 0 ? '#fff6c8' : P.nodeHi); gg.addColorStop(1, q > 0 ? P.hot : P.node);
|
|
259
|
+
g.fillStyle = gg; g.beginPath(); g.arc(L.x, y, r, 0, Math.PI * 2); g.fill();
|
|
260
|
+
} else {
|
|
261
|
+
if (q > 0) glow(g, L.x, y, r * 2.4, P.pulse, 0.32 * q);
|
|
262
|
+
flat(g, circ(L.x, y, r), q > 0 ? '#3e6fd8' : P.node, q > 0 ? '#2b4fb0' : '#1c275a', q > 0 ? '#7fb2ff' : P.nodeHi, [-6, -6], [-2, -2]);
|
|
263
|
+
if (q > 0) nodeIcon(g, L.i, k, L.x, y, q);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
// 层标签:逐个出现(文字是从下往上浮 + 短下划线伸出)
|
|
267
|
+
const labs = [{ x: 400, s: '像素' }, ...LAYERS.map(L => ({ x: L.x, s: L.label }))];
|
|
268
|
+
labs.forEach((L, i) => { const t0 = 0.55 + (i ? layerHit(i - 1) - 0.55 + 0.1 : 0); const q = MO.cubicOut(seg(l, t0, t0 + 0.45));
|
|
269
|
+
if (q <= 0) return; TY.charsIn(g, L.s, L.x, 880, l - t0, { font: '40px "PuHui-Medium"', color: i === 4 ? P.hot : P.label, dur: 0.4, stagger: 0.06, dy: 18, align: 'center' });
|
|
270
|
+
g.strokeStyle = i === 4 ? P.hot : P.label; g.lineWidth = 4; g.lineCap = 'round'; g.beginPath(); g.moveTo(L.x - 34 * q, 904); g.lineTo(L.x + 34 * q, 904); g.stroke(); });
|
|
271
|
+
// 箭头:标签之间
|
|
272
|
+
g.fillStyle = 'rgba(169,188,255,.55)'; for (let i = 0; i < 4; i++) { const xa = [400, ...LAYERS.map(L => L.x)]; const q = seg(l, 0.6 + (i ? layerHit(i - 1) - 0.5 : 0.2), 1.0 + (i ? layerHit(i - 1) - 0.5 : 0.2)); if (q <= 0) continue; const mx = (xa[i] + xa[i + 1]) / 2; g.globalAlpha = q; g.beginPath(); g.moveTo(mx - 10, 868); g.lineTo(mx + 10, 878); g.lineTo(mx - 10, 888); g.closePath(); g.fill(); }
|
|
273
|
+
g.globalAlpha = 1;
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ---------- 镜 3:白天草坡 ----------
|
|
278
|
+
const BOT3 = [1010, 400];
|
|
279
|
+
Y1.bot3Pos = (ft) => [BOT3[0] + Math.sin(ft * 1.9) * 6, BOT3[1] + Math.sin(ft * 2.4) * 10];
|
|
280
|
+
const s3cam = (ft) => {
|
|
281
|
+
const l = ft - Y1.SHOTS.s3[0];
|
|
282
|
+
const [bx, by] = Y1.bot3Pos(ft); const eye = [bx, by]; // look=0 时眼睛在头中心
|
|
283
|
+
// 0–0.45s 停在满屏黄色的眼睛里;0.45→1.9s 对数插值拉远 42→1.0。
|
|
284
|
+
// 用 cubicInOut 而不是 expoOut:expoOut 起步斜率太大,第一帧就从 42 倍跳到 15 倍(qa 量到 9 帧跳变);实测 Kurzgesagt 穿越是先加速后减速、峰值在中段
|
|
285
|
+
const e = MO.cubicInOut(seg(l, 0.45, 1.9));
|
|
286
|
+
const z = CAM.zlerp(42, 1.0, e) * (1 + 0.04 * Math.max(0, l - 1.9)); // 落定后 4%/s 慢推(实测待机推 3–4.5%/s)
|
|
287
|
+
const final = { x: 960, y: 540 };
|
|
288
|
+
const s0 = [W / 2, H / 2], s1 = CAM.toScreen({ ...final, z: 1 }, eye[0], eye[1]);
|
|
289
|
+
return CAM.anchor(eye[0], eye[1], z, lerp(s0[0], s1[0], e), lerp(s0[1], s1[1], e));
|
|
290
|
+
};
|
|
291
|
+
const CLOUDS = [[300, 230, 1.0], [880, 150, 0.7], [1480, 260, 1.15], [2100, 180, 0.8]];
|
|
292
|
+
const TREES = [[180, 760, 1], [330, 790, 0.7], [1580, 770, 0.9], [1760, 790, 1.2]];
|
|
293
|
+
const cloud = (g, x, y, s) => { const p = new Path2D(); for (const [dx, dy, r] of [[-70, 10, 50], [-20, -18, 66], [50, -4, 56], [100, 16, 40], [0, 22, 46]]) p.arc(x + dx * s, y + dy * s, r * s, 0, Math.PI * 2); flat(g, p, C.day.cloud, C.day.cloudSh, '#ffffff', [0, -10 * s], [0, 4]); };
|
|
294
|
+
Y1.shot3 = (c, lt, t) => {
|
|
295
|
+
const ft = t - F0, P = C.day, cam = s3cam(ft), l = ft - Y1.SHOTS.s3[0];
|
|
296
|
+
c.fillStyle = vgrad(c, 0, H, P.sky); c.fillRect(0, 0, W, H);
|
|
297
|
+
layer(c, cam, 0.08, g => { glow(g, 1640, 170, 300, '#fff6c0', 0.55); g.fillStyle = P.sun; g.beginPath(); g.arc(1640, 170, 80 + Math.sin(ft * 2) * 3, 0, Math.PI * 2); g.fill(); });
|
|
298
|
+
layer(c, cam, 0.2, g => { for (const [x, y, s] of CLOUDS) cloud(g, ((x + ft * 26) % 2600) - 340, y, s); });
|
|
299
|
+
layer(c, cam, 0.35, g => flat(g, poly([[-300, 760], [100, 650], [520, 700], [900, 620], [1300, 690], [1700, 610], [2200, 700], [2200, 1300], [-300, 1300]]), P.hillFar, '#86c45c', '#b4e889', [0, -10], [0, 4]));
|
|
300
|
+
layer(c, cam, 0.6, g => { flat(g, poly([[-300, 820], [300, 760], [900, 800], [1500, 740], [2200, 820], [2200, 1300], [-300, 1300]]), P.hillMid, '#58a63e', '#8fd468', [0, -12], [0, 4]);
|
|
301
|
+
for (const [x, y, s] of TREES) { g.fillStyle = P.trunk; g.fillRect(x - 8 * s, y - 60 * s, 16 * s, 70 * s); const sw = Math.sin(ft * 1.6 + x) * 4; flat(g, circ(x + sw, y - 100 * s, 62 * s), P.tree, '#23753a', P.treeHi, [-12 * s, -10 * s], [-4, -4]); } });
|
|
302
|
+
// 鸟:两只从左飞到右(Kurzgesagt 的签名彩蛋)
|
|
303
|
+
layer(c, cam, 0.75, g => { Y1.bird(g, -200 + ft * 150, 175 + Math.sin(ft * 3) * 14, ft, { s: 0.9, col: '#ff5d8f', sh: '#d63c70' }); Y1.bird(g, -420 + ft * 165, 225 + Math.sin(ft * 3 + 1) * 12, ft, { s: 0.7, col: '#ffb02e', sh: '#e08a10', ph: 0.15 }); });
|
|
304
|
+
layer(c, cam, 1, g => {
|
|
305
|
+
flat(g, poly([[-300, 900], [400, 860], [1000, 880], [1600, 850], [2300, 900], [2300, 1400], [-300, 1400]]), P.ground, '#3d8f33', P.groundHi, [0, -14], [0, 5]);
|
|
306
|
+
// 花叔:指向黑猫(point 姿势+手臂与头的小幅持续摆动,嘴在说话)
|
|
307
|
+
if (HERO.y1(g, ft) === false) { // 形象方向见 lib/hero.js
|
|
308
|
+
const HSb = HUASHU.build({ x: 470, y: 900 + Math.sin(ft * 3.2) * 2, s: 0.66, face: 1, pose: HUASHU.pose('point', 0, { ua1: 1.38 + 0.07 * Math.sin(ft * 3.4), fa1: 0.05 + 0.05 * Math.sin(ft * 3.4 + 1), head: -0.04 + 0.03 * Math.sin(ft * 2.1), mouth: 0.45 + 0.4 * Math.max(0, Math.sin(ft * 9)) }) });
|
|
309
|
+
const pal = { skin: '#ffd3b4', hair: '#1f1a3a', shirt: '#ffffff', shorts: '#d9c9a8', hat: '#ffffff', hatBand: '#d8d4f2', shoe: '#f4f2ff', watch: '#2a2550' };
|
|
310
|
+
const shade = { skin: '#f0a98a', hair: '#120f26', shirt: '#cfcbee', shorts: '#b39f7c', hat: '#d3cff0', hatBand: '#b6b0e0', shoe: '#c9c5ea', watch: '#14112b' };
|
|
311
|
+
for (const p of HSb.parts) flat(g, p.path, pal[p.role] || pal.skin, shade[p.role] || shade.skin, '#ffffff', [-10, -8], [-4, -3]);
|
|
312
|
+
HUASHU.details(g, HSb, { line: '#1f1a3a', lw: 3.5, glass: '#1f1a3a', cheek: 'rgba(255,120,120,.35)', earLine: 'rgba(200,110,90,.7)', shoeHole: '#8a86b8' });
|
|
313
|
+
}
|
|
314
|
+
// 认识的那只橙猫蹲在花叔脚边;镜头要认的是一只「没见过的」黑猫
|
|
315
|
+
Y1.cat(g, 790, 905, ft + 0.3, { s: 0.55, face: -1, look: 0.5 });
|
|
316
|
+
Y1.cat(g, 1420, 880, ft + 0.7, { s: 0.85, face: -1, pal: { fur: '#3a3550', sh: '#24203a', hi: '#5a5478', belly: '#4a4566', ear: '#ff8fa3', eye: '#d9ff6b', pupil: '#16132a' }, look: 0.2 });
|
|
317
|
+
// 扫描光与机器人
|
|
318
|
+
const [bx, by] = Y1.bot3Pos(ft);
|
|
319
|
+
const bq = MO.cubicOut(seg(l, 1.5, 1.85));
|
|
320
|
+
if (bq > 0) { const beam = new Path2D(); beam.moveTo(bx, by); beam.lineTo(1300, 660); beam.lineTo(1540, 660); beam.closePath(); g.save(); g.globalAlpha = bq; const bg = g.createLinearGradient(bx, by, 1420, 760); bg.addColorStop(0, 'rgba(255,210,63,.55)'); bg.addColorStop(1, 'rgba(255,210,63,.08)'); g.fillStyle = bg; g.fill(beam); g.restore(); }
|
|
321
|
+
Y1.bot(g, bx, by, ft, { eye: C.bot.eyeHot, eyeR: 30, look: 0 });
|
|
322
|
+
// 小字:一只没见过的猫(引线先画出,再逐字打出,实测标注≈40 字/秒)
|
|
323
|
+
const cq = seg(l, 1.0, 1.25), cout = 1 - seg(l, 1.65, 1.8);
|
|
324
|
+
if (cq > 0 && cout > 0) { g.save(); g.globalAlpha = cout; g.strokeStyle = '#ffffff'; g.lineWidth = 3; g.beginPath(); g.moveTo(1500, 700); g.lineTo(1500 + 80 * cq, 640 - 40 * cq); g.lineTo(1500 + 80 * cq + 120 * clamp(cq * 2 - 1), 600); g.stroke();
|
|
325
|
+
const str = '没见过的猫', n = Math.floor((l - 1.2) * 25); // 中文按 25 字/秒打出
|
|
326
|
+
g.fillStyle = '#ffffff'; g.font = '34px "PuHui-Medium"'; g.textBaseline = 'alphabetic'; g.fillText([...str].slice(0, Math.min(5, Math.max(0, n))).join(''), 1590, 590); g.restore(); }
|
|
327
|
+
// 识别标签:从猫头上方弹出,之后轻轻上下浮
|
|
328
|
+
const q = seg(l, 1.75, 2.25); if (q > 0) {
|
|
329
|
+
// 实测卡片弹出 ≈0.5s、过冲到 108% 回 100%
|
|
330
|
+
const e = MO.backOut(q, 1.1), y = 560 + Math.sin(ft * 2.5) * 6;
|
|
331
|
+
g.save(); g.translate(1420, y); g.scale(e, e);
|
|
332
|
+
g.fillStyle = 'rgba(20,40,20,.18)'; g.beginPath(); g.roundRect(-122, -40, 252, 92, 46); g.fill();
|
|
333
|
+
g.fillStyle = '#ffffff'; g.beginPath(); g.roundRect(-130, -50, 252, 92, 46); g.fill();
|
|
334
|
+
g.beginPath(); g.moveTo(-14, 40); g.lineTo(0, 64); g.lineTo(14, 40); g.fill();
|
|
335
|
+
g.fillStyle = '#2fbf71'; g.beginPath(); g.arc(-80, -4, 30, 0, Math.PI * 2); g.fill();
|
|
336
|
+
g.strokeStyle = '#fff'; g.lineWidth = 8; g.lineCap = 'round'; g.lineJoin = 'round'; g.beginPath(); g.moveTo(-94, -4); g.lineTo(-84, 8); g.lineTo(-64, -16); g.stroke();
|
|
337
|
+
g.fillStyle = '#1f1a3a'; g.font = '52px "PuHui-Heavy"'; g.textBaseline = 'middle'; g.fillText('猫', -36, -2);
|
|
338
|
+
g.font = '34px "PuHui-Medium"'; g.fillStyle = '#2fbf71'; g.fillText('98%', 26, 0);
|
|
339
|
+
g.restore();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
})();
|
|
344
|
+
|
|
345
|
+
SCENES['y1_s1'] = { draw: (c, lt, t) => Y1.shot1(c, lt, t) };
|
|
346
|
+
SCENES['y1_s2'] = { draw: (c, lt, t) => Y1.shot2(c, lt, t) };
|
|
347
|
+
SCENES['y1_s3'] = { draw: (c, lt, t) => Y1.shot3(c, lt, t) };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// 示范 · Vox 式拼贴解说「AI 是怎么学会认猫的」(8 秒三镜)。语法卡:references/动画语法/y2_vox.md
|
|
2
|
+
// 预览 index.html?film=demos/y2_vox 渲染 render.py --film demos/y2_vox --out y2.mp4
|
|
3
|
+
// 一张大桌面=世界;相机 60fps 平滑推移,桌上的纸片/荧光笔/红笔/打字按 12fps 步进。
|
|
4
|
+
window.PUNCH = 0;
|
|
5
|
+
window.SCENE_LIBS = ['demos/_shared/hero.js', 'demos/y2_vox/vox.js'];
|
|
6
|
+
window.ERAS = [
|
|
7
|
+
{ id: 'y2_s1', dur: 2.6 }, // 剪报+网点照片:荧光笔扫行、红笔圈词、相机慢推
|
|
8
|
+
{ id: 'y2_s2', dur: 3.3, transition: { type: 'same', dur: 1.1 } }, // 沿红线平移到调查墙(同一桌面,相机连续走)
|
|
9
|
+
{ id: 'y2_s3', dur: 2.1, transition: { type: 'cut', dur: 1 / 60 } }, // 推满小照片 → 硬切到同一张图的大幅扫描,再拉出揭示黑底拼贴
|
|
10
|
+
];
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Y2 · Vox 式拼贴解说(Missing Chapter / Borders 一路):「AI 是怎么学会认猫的」
|
|
2
|
+
// 一张大桌面(世界坐标),相机 60fps 平滑地推、移;桌上的元素(荧光笔、红笔、打字、红线、贴纸)按 12fps「一拍二」步进。
|
|
3
|
+
// 依据:调研/y2_vox/调研.md(13 段相机运动实测:中位 1.29s,对称 easeInOutSine 或长尾 bezier(.33,0,.2,1),没有线性;元素层 12fps)
|
|
4
|
+
// ①剪报+网点照片已在桌上(随切点直接出现),荧光笔按行扫、红笔圈词、相机慢推向那一行
|
|
5
|
+
// → 转场 A「沿红线平移」:一条红线从剪报连到调查墙,相机沿线 1.1s 追过去(长尾 bezier)
|
|
6
|
+
// ②调查墙:1000 万张截图里的猫被红线连到索引卡,打字机 13 字/秒,计数滚到 10,000,000,一张小照片带倾斜滑入放平
|
|
7
|
+
// → 转场 B「推进照片→硬切」:相机推满那张小照片,硬切成同一张图的大幅扫描,再「拉出揭示」整张黑底拼贴
|
|
8
|
+
// ③黑底:它脑中的「猫」、纸条标题打字+荧光笔、花叔剪纸贴纸滑入、红笔圈住
|
|
9
|
+
// 用到的库:CL(纸纹、撕边、网点照片、胶带、图钉、红线、毛边荧光笔、倾斜滑入、贴纸白边)、CAM(关键帧相机、推进锚点、运动模糊)、
|
|
10
|
+
// DG(手画圈、逐段描出)、TY.typedCount(打字机)、MO(longTail、12fps 步进)。
|
|
11
|
+
(() => {
|
|
12
|
+
const W = 1920, H = 1080;
|
|
13
|
+
const { clamp, lerp, rng } = U;
|
|
14
|
+
const seg = MO.seg, step = MO.step;
|
|
15
|
+
const Y2 = window.Y2 = {};
|
|
16
|
+
const F0 = 0; Y2.F0 = F0;
|
|
17
|
+
Y2.SHOTS = { s1: [0, 2.6], s2: [2.6, 5.9], s3: [5.9, 8.0] };
|
|
18
|
+
const PAN = { a: 2.6, d: 1.1 }, PUSH = { a: 5.3, b: 5.9 };
|
|
19
|
+
const longTail = MO.longTail; // 「沿线追过去」「拉出揭示」用:起步快、长尾落定
|
|
20
|
+
|
|
21
|
+
// 调研实测色(MC/BD/PSY 取色)
|
|
22
|
+
const C = { desk: '#e4ddcf', news: '#eeedeb', ink: '#171716', red: '#b8433f', hl: '#dacf08', black: '#171716', paper: '#eeedeb', tape: 'rgba(226,220,200,.85)', card: '#f6f4ee' };
|
|
23
|
+
const SERIF = '"Songti SC", "STSong", serif';
|
|
24
|
+
|
|
25
|
+
// ---------- 素材(一次画好缓存) ----------
|
|
26
|
+
const sprite = (key, w, h, fn) => PAINT.cached('y2_' + key, w, h, fn);
|
|
27
|
+
const tornPoly = CL.tornRect;
|
|
28
|
+
const catPhoto = (g, w, h, { dark = false, pose = 0 } = {}) => {
|
|
29
|
+
const bg = g.createLinearGradient(0, 0, w, h); bg.addColorStop(0, dark ? '#555' : '#bdbdbd'); bg.addColorStop(1, dark ? '#2a2a2a' : '#8a8a8a'); g.fillStyle = bg; g.fillRect(0, 0, w, h);
|
|
30
|
+
const cx = w * (0.5 + pose * 0.08), cy = h * 0.56, s = Math.min(w, h) / 300;
|
|
31
|
+
g.save(); g.translate(cx, cy); g.scale(s, s);
|
|
32
|
+
g.fillStyle = dark ? '#d9d9d9' : '#3a3a3a';
|
|
33
|
+
g.beginPath(); g.ellipse(0, 120, 120, 90, 0, 0, Math.PI * 2); g.fill();
|
|
34
|
+
g.beginPath(); g.ellipse(0, 0, 92, 78, 0, 0, Math.PI * 2); g.fill();
|
|
35
|
+
g.beginPath(); g.moveTo(-80, -30); g.lineTo(-70, -118); g.lineTo(-20, -66); g.fill(); g.beginPath(); g.moveTo(80, -30); g.lineTo(70, -118); g.lineTo(20, -66); g.fill();
|
|
36
|
+
const fg = g.createRadialGradient(0, 20, 5, 0, 20, 70); fg.addColorStop(0, dark ? '#fff' : '#9a9a9a'); fg.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = fg; g.beginPath(); g.ellipse(0, 22, 60, 46, 0, 0, Math.PI * 2); g.fill();
|
|
37
|
+
g.fillStyle = '#f2f2f2'; for (const ex of [-34, 34]) { g.beginPath(); g.ellipse(ex, -6, 17, 13, 0, 0, Math.PI * 2); g.fill(); }
|
|
38
|
+
g.fillStyle = '#111'; for (const ex of [-34, 34]) { g.beginPath(); g.ellipse(ex, -6, 5, 12, 0, 0, Math.PI * 2); g.fill(); }
|
|
39
|
+
g.fillStyle = '#222'; g.beginPath(); g.moveTo(-9, 22); g.lineTo(9, 22); g.lineTo(0, 32); g.fill();
|
|
40
|
+
g.strokeStyle = 'rgba(240,240,240,.7)'; g.lineWidth = 2.5; for (const k of [-1, 1]) for (const d of [-6, 6]) { g.beginPath(); g.moveTo(k * 30, 34 + d); g.lineTo(k * 110, 26 + d * 2.4); g.stroke(); }
|
|
41
|
+
g.restore();
|
|
42
|
+
};
|
|
43
|
+
const thumbScene = (g, w, h, kind, seed) => {
|
|
44
|
+
const r = rng(seed); const bg = g.createLinearGradient(0, 0, 0, h); const L = 120 + r() * 80; bg.addColorStop(0, `rgb(${L},${L},${L})`); bg.addColorStop(1, `rgb(${L - 50},${L - 50},${L - 50})`); g.fillStyle = bg; g.fillRect(0, 0, w, h);
|
|
45
|
+
g.fillStyle = r() < 0.5 ? '#2c2c2c' : '#e8e8e8';
|
|
46
|
+
if (kind === 'cat') return catPhoto(g, w, h, { dark: r() < 0.4, pose: r() - 0.5 });
|
|
47
|
+
g.save(); g.translate(w / 2, h * 0.6); const s = h / 90; g.scale(s, s);
|
|
48
|
+
if (kind === 'dog') { g.beginPath(); g.ellipse(0, 6, 34, 18, 0, 0, Math.PI * 2); g.fill(); g.beginPath(); g.ellipse(-30, -14, 14, 12, 0, 0, Math.PI * 2); g.fill(); g.beginPath(); g.ellipse(-40, -8, 6, 12, 0.3, 0, Math.PI * 2); g.fill(); g.fillRect(-26, 14, 6, 16); g.fillRect(18, 14, 6, 16); }
|
|
49
|
+
if (kind === 'car') { g.beginPath(); g.roundRect(-44, -4, 88, 22, 6); g.fill(); g.beginPath(); g.roundRect(-24, -22, 46, 22, 8); g.fill(); g.fillStyle = '#111'; g.beginPath(); g.arc(-26, 20, 9, 0, 7); g.arc(26, 20, 9, 0, 7); g.fill(); }
|
|
50
|
+
if (kind === 'man') { g.beginPath(); g.arc(0, -26, 13, 0, 7); g.fill(); g.beginPath(); g.roundRect(-20, -10, 40, 44, 10); g.fill(); }
|
|
51
|
+
if (kind === 'tree') { g.fillRect(-5, -4, 10, 34); g.beginPath(); g.arc(0, -20, 26, 0, 7); g.fill(); }
|
|
52
|
+
if (kind === 'house') { g.fillRect(-28, -8, 56, 36); g.beginPath(); g.moveTo(-36, -8); g.lineTo(0, -38); g.lineTo(36, -8); g.fill(); }
|
|
53
|
+
if (kind === 'game') { g.beginPath(); g.roundRect(-36, -14, 72, 34, 16); g.fill(); }
|
|
54
|
+
g.restore();
|
|
55
|
+
g.fillStyle = 'rgba(0,0,0,.45)'; g.fillRect(0, h - 8, w, 8); g.fillStyle = '#b8433f'; g.fillRect(0, h - 8, w * (0.2 + r() * 0.6), 8); // 播放条(只用形状,不放台标)
|
|
56
|
+
};
|
|
57
|
+
const photoSprite = CL.photo; // 网点照片+纸边(halftone 6px、对比 1.3)
|
|
58
|
+
// 「猫神经元」的最优刺激图(论文 Fig.6 那种模糊猫脸),用大块柔光画,过网点
|
|
59
|
+
const neuronDraw = (g, w, h) => {
|
|
60
|
+
g.fillStyle = '#7b7b7b'; g.fillRect(0, 0, w, h); const k = w / 560;
|
|
61
|
+
const blob = (x, y, rx, ry, col) => { g.save(); g.filter = `blur(${26 * k}px)`; g.fillStyle = col; g.beginPath(); g.ellipse(x * w, y * h, rx * k, ry * k, 0, 0, Math.PI * 2); g.fill(); g.restore(); };
|
|
62
|
+
blob(0.5, 0.56, 190, 160, '#3b3b3b'); blob(0.28, 0.25, 60, 90, '#3a3a3a'); blob(0.72, 0.25, 60, 90, '#3a3a3a');
|
|
63
|
+
blob(0.5, 0.68, 110, 70, '#c9c9c9'); blob(0.36, 0.5, 34, 26, '#e6e6e6'); blob(0.64, 0.5, 34, 26, '#e6e6e6');
|
|
64
|
+
blob(0.36, 0.5, 12, 18, '#111'); blob(0.64, 0.5, 12, 18, '#111'); blob(0.5, 0.64, 16, 10, '#222');
|
|
65
|
+
};
|
|
66
|
+
// 剪报(数字出自 Le et al. 2012 论文摘要与 Google 官方博客:1000 台机器、1.6 万个 CPU 核、1000 万张 YouTube 截图、不打标签)
|
|
67
|
+
const NEWS = { w: 860, h: 600, hl: { x: 52, y: 352, w: 590, h: 48 }, circ: { x: 450, y: 226, rx: 96, ry: 50 } };
|
|
68
|
+
const newsSprite = () => sprite('news', NEWS.w, NEWS.h, (g) => {
|
|
69
|
+
const tile = CL.paperTile('news', C.news, { amt: 9, fibers: 160 });
|
|
70
|
+
g.save(); tornPoly(g, NEWS.w, NEWS.h, 21, 4); g.clip(); g.fillStyle = g.createPattern(tile, 'repeat'); g.fillRect(0, 0, NEWS.w, NEWS.h);
|
|
71
|
+
const yel = g.createRadialGradient(NEWS.w * 0.4, NEWS.h * 0.4, 50, NEWS.w * 0.5, NEWS.h * 0.5, NEWS.w * 0.75); yel.addColorStop(0, 'rgba(255,255,255,0)'); yel.addColorStop(1, 'rgba(170,150,110,.2)'); g.fillStyle = yel; g.fillRect(0, 0, NEWS.w, NEWS.h);
|
|
72
|
+
g.fillStyle = C.ink; g.font = `600 22px ${SERIF}`; g.fillText('科技 · 2012 年 6 月', 52, 64);
|
|
73
|
+
g.fillRect(52, 80, NEWS.w - 104, 3); g.fillRect(52, 86, NEWS.w - 104, 1);
|
|
74
|
+
g.font = `900 66px ${SERIF}`; g.fillText('谷歌的电脑', 52, 170); g.fillText('自己学会了认猫', 52, 250);
|
|
75
|
+
g.font = `400 32px ${SERIF}`; g.fillStyle = '#2a2723';
|
|
76
|
+
g.fillText('1000 台机器、1.6 万个处理器核心,', 52, 330); g.fillText('看了 1000 万张 YouTube 视频截图,', 52, 386); g.fillText('没有人告诉过它,哪一张是猫。', 52, 442);
|
|
77
|
+
g.fillStyle = 'rgba(40,36,30,.3)'; for (let i = 0; i < 3; i++) g.fillRect(52, 482 + i * 34, [700, 640, 420][i], 14);
|
|
78
|
+
g.restore();
|
|
79
|
+
});
|
|
80
|
+
const CARD = { w: 520, h: 330 };
|
|
81
|
+
const cardSprite = () => sprite('card', CARD.w, CARD.h, (g) => {
|
|
82
|
+
g.fillStyle = C.card; g.fillRect(0, 0, CARD.w, CARD.h);
|
|
83
|
+
g.strokeStyle = 'rgba(80,140,200,.3)'; g.lineWidth = 2; for (let y = 96; y < CARD.h; y += 52) { g.beginPath(); g.moveTo(0, y); g.lineTo(CARD.w, y); g.stroke(); }
|
|
84
|
+
g.strokeStyle = 'rgba(184,67,63,.5)'; g.beginPath(); g.moveTo(0, 60); g.lineTo(CARD.w, 60); g.stroke();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ---------- 桌面世界 ----------
|
|
88
|
+
const R1 = [960, 540], R2 = [3100, 720];
|
|
89
|
+
const NEWSPOS = { x: 620, y: 520, r: -0.035 }, PHOTO = { x: 1440, y: 470, r: 0.06, w: 380, h: 430 };
|
|
90
|
+
const THUMBS = (() => { const r = rng(55), o = [], kinds = ['dog', 'car', 'man', 'tree', 'house', 'game']; const cats = new Set([3, 8, 13, 16, 22]);
|
|
91
|
+
for (let j = 0; j < 4; j++) for (let i = 0; i < 6; i++) { const k = j * 6 + i; o.push({ x: 2250 + i * 205 + (r() - .5) * 26, y: 420 + j * 150 + (r() - .5) * 20, r: (r() - .5) * 0.09, kind: cats.has(k) ? 'cat' : kinds[(r() * kinds.length) | 0], seed: 100 + k, cat: cats.has(k) }); }
|
|
92
|
+
return o; })();
|
|
93
|
+
const CARDPOS = { x: 3660, y: 560, r: 0.025 };
|
|
94
|
+
const PRINT = { x: 3640, y: 900, r: -0.04, w: 210, h: 210 }; // 调查墙上那张小照片:推进它、硬切到镜 3
|
|
95
|
+
const deskCam = (ft) => {
|
|
96
|
+
const base = CAM.at([
|
|
97
|
+
{ t: 0, x: R1[0] + 20, y: R1[1] + 20, z: 1.14 },
|
|
98
|
+
{ t: 0.9, x: R1[0] + 10, y: R1[1] + 30, z: 1.165, ease: MO.sineInOut }, // 开场慢推(停留段 0.5–1%/s 的 Ken Burns 加一点)
|
|
99
|
+
// 荧光笔扫完后推向那一行(BD 实测约 +11%/s),1.5s easeInOutSine
|
|
100
|
+
{ t: 2.4, x: R1[0] - 60, y: R1[1] + 60, z: 1.28, ease: MO.sineInOut },
|
|
101
|
+
{ t: PAN.a, x: R1[0] - 56, y: R1[1] + 60, z: 1.29, ease: MO.sineInOut },
|
|
102
|
+
// 沿红线追过去:1.1s 长尾 bezier(.33,0,.2,1)
|
|
103
|
+
{ t: PAN.a + PAN.d, x: R2[0], y: R2[1], z: 1.0, ease: longTail },
|
|
104
|
+
{ t: PUSH.a, x: R2[0] + 30, y: R2[1] + 10, z: 1.04, ease: MO.sineInOut },
|
|
105
|
+
], ft);
|
|
106
|
+
if (ft <= PUSH.a) return base;
|
|
107
|
+
// 推进照片:0.6s easeInOutSine,对数插值推到照片铺满画面(z≈9.6)
|
|
108
|
+
return CAM.pushTo(base, PRINT.x, PRINT.y, 9.6, MO.sineInOut(seg(ft, PUSH.a, PUSH.b)));
|
|
109
|
+
};
|
|
110
|
+
Y2.deskCam = deskCam;
|
|
111
|
+
// 投影很弱(MC 实测黑底卡片几乎无投影;桌面上留一点就够);纸片滑入带倾斜、到位放平(12fps 步进)
|
|
112
|
+
const shadowed = CL.shadowed, slideIn = CL.slideIn, placed = CL.placed, tape = CL.tape, stringPts = CL.stringPts, highlight = CL.highlight;
|
|
113
|
+
const pin = (g, x, y, on) => { if (on) CL.pin(g, x, y, C.red); };
|
|
114
|
+
const NEWS_CIRCLE = DG.hand(DG.ellipsePts(0, 0, NEWS.circ.rx, NEWS.circ.ry, -2.6, 1.1, 40), { amp: 2.2, seed: 9 }), NEWS_CIRCLE_L = DG.cum(NEWS_CIRCLE);
|
|
115
|
+
// 线索红线:从剪报高亮那一行的末端,一路连到调查墙的计数标签(相机沿它追)
|
|
116
|
+
const CLUE = (() => { const a = [NEWSPOS.x + 250, NEWSPOS.y + 70], b = [2400, 1100]; return { a, b, pts: stringPts(a, b, 0.05) }; })();
|
|
117
|
+
CLUE.cum = DG.cum(CLUE.pts);
|
|
118
|
+
const drawDesk = (g, cam, ft) => {
|
|
119
|
+
g.save(); CAM.apply(g, cam);
|
|
120
|
+
g.fillStyle = g.createPattern(CL.paperTile('desk', C.desk, { amt: 12, fibers: 320, fiberCol: [110, 95, 70] }), 'repeat'); g.fillRect(-1200, -1200, 6800, 3600);
|
|
121
|
+
const st = step(ft);
|
|
122
|
+
// —— 区域 1:剪报+照片(随切点直接在桌上) ——
|
|
123
|
+
placed(g, NEWSPOS.x, NEWSPOS.y, NEWSPOS.r, (g) => {
|
|
124
|
+
const sp = newsSprite(); shadowed(g, () => g.drawImage(sp, -NEWS.w / 2, -NEWS.h / 2));
|
|
125
|
+
const b = NEWS.hl; highlight(g, -NEWS.w / 2 + b.x - 8, -NEWS.h / 2 + b.y - 6, b.w + 16, b.h + 12, MO.sineInOut(seg(st, 0.35, 1.25)));
|
|
126
|
+
const cq = MO.cubicOut(seg(st, 1.35, 2.0)); // 红笔圈:0.65s,首尾不闭合
|
|
127
|
+
if (cq > 0) { g.save(); g.translate(-NEWS.w / 2 + NEWS.circ.x, -NEWS.h / 2 + NEWS.circ.y); g.strokeStyle = C.red; g.lineWidth = 7; g.lineCap = 'round'; g.lineJoin = 'round'; DG.drawPartial(g, NEWS_CIRCLE, NEWS_CIRCLE_L, NEWS_CIRCLE_L[NEWS_CIRCLE_L.length - 1] * cq); g.restore(); }
|
|
128
|
+
});
|
|
129
|
+
placed(g, PHOTO.x, PHOTO.y, PHOTO.r, (g) => {
|
|
130
|
+
const sp = photoSprite('catphoto', PHOTO.w, PHOTO.h, (gg, w, h) => catPhoto(gg, w, h), { border: 18, cell: 7 });
|
|
131
|
+
shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); tape(g, -sp.width / 2 + 30, -sp.height / 2 + 10, -0.6); tape(g, sp.width / 2 - 30, -sp.height / 2 + 10, 0.5);
|
|
132
|
+
});
|
|
133
|
+
// Borders 式黑底白字标签条
|
|
134
|
+
placed(g, 1260, 830, -0.02, (g) => { g.fillStyle = C.black; g.fillRect(-150, -40, 300, 80); g.fillStyle = C.paper; g.font = '46px "PuHui-Heavy"'; g.textAlign = 'center'; g.textBaseline = 'middle'; g.fillText('2012 · 谷歌', 0, 3); });
|
|
135
|
+
// —— 线索红线(一直在桌上;相机沿它追) ——
|
|
136
|
+
CL.string(g, CLUE.pts, CLUE.cum, 1e9, { col: C.red, lw: 5 });
|
|
137
|
+
pin(g, CLUE.a[0], CLUE.a[1], true); pin(g, CLUE.b[0], CLUE.b[1], true);
|
|
138
|
+
// —— 区域 2:调查墙(截图早就钉在墙上) ——
|
|
139
|
+
for (const [i, th] of THUMBS.entries()) placed(g, th.x, th.y, th.r, (g) => { const sp = photoSprite('th' + i, 168, 94, (gg, w, h) => thumbScene(gg, w, h, th.kind, th.seed), { border: 7, cell: 4 }); shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); });
|
|
140
|
+
placed(g, CARDPOS.x, CARDPOS.y, CARDPOS.r, (g) => {
|
|
141
|
+
shadowed(g, () => g.drawImage(cardSprite(), -CARD.w / 2, -CARD.h / 2));
|
|
142
|
+
// 打字机:13 字/秒(MC 实测),按一拍二出字
|
|
143
|
+
const lines = ['没有人告诉它', '哪一张是猫'], t0 = PAN.a + PAN.d + 0.1;
|
|
144
|
+
g.fillStyle = C.ink; g.font = `600 52px ${SERIF}`; g.textBaseline = 'alphabetic';
|
|
145
|
+
const typed = TY.typedCount(st, t0, 13);
|
|
146
|
+
let k = 0; lines.forEach((ln, li) => { let x = -CARD.w / 2 + 40; for (const ch of ln) { if (k < typed) g.fillText(ch, x, -CARD.h / 2 + 140 + li * 104); x += g.measureText(ch).width; k++; } });
|
|
147
|
+
});
|
|
148
|
+
// 红线:每根 0.5s 描出(12fps),错开 0.15s;图钉随线头一起出现
|
|
149
|
+
const cats = THUMBS.filter(t => t.cat), cardPin = [CARDPOS.x - 40, CARDPOS.y - CARD.h / 2 + 22];
|
|
150
|
+
cats.forEach((th, i) => {
|
|
151
|
+
const t0 = PAN.a + PAN.d + 0.15 + i * 0.15, q = MO.sineInOut(seg(st, t0, t0 + 0.5)); if (q <= 0) return;
|
|
152
|
+
const a = [th.x + 40, th.y - 28], pts = stringPts(a, cardPin), L = DG.cum(pts);
|
|
153
|
+
CL.string(g, pts, L, L[L.length - 1] * q, { col: C.red, lw: 4 });
|
|
154
|
+
pin(g, a[0], a[1], true);
|
|
155
|
+
});
|
|
156
|
+
pin(g, cardPin[0], cardPin[1], st >= PAN.a + PAN.d + 0.15);
|
|
157
|
+
// 计数标签:黑胶带,数字 12fps 滚到 10,000,000(cubicOut)
|
|
158
|
+
placed(g, 2770, 1110, -0.03, (g) => {
|
|
159
|
+
const nq = MO.cubicOut(seg(st, PAN.a + 0.6, PAN.a + 2.0));
|
|
160
|
+
shadowed(g, () => { g.fillStyle = C.black; g.save(); g.translate(-380, -52); tornPoly(g, 760, 104, 77, 3); g.fill(); g.restore(); });
|
|
161
|
+
g.fillStyle = C.paper; g.textBaseline = 'middle'; g.font = `600 40px ${SERIF}`; g.fillText('视频截图', -350, 2);
|
|
162
|
+
g.fillStyle = C.hl; g.font = '74px "Anton-400"'; g.textAlign = 'right'; g.fillText('× ' + Math.round(10000000 * nq).toLocaleString('en-US'), 350, 6);
|
|
163
|
+
});
|
|
164
|
+
// 小照片:它脑中的「猫」,带倾斜滑入放平
|
|
165
|
+
slideIn(g, ft, PAN.a + PAN.d + 0.95, PRINT.x, PRINT.y, PRINT.r, (g) => {
|
|
166
|
+
const sp = photoSprite('neuron_s', PRINT.w, PRINT.h, neuronDraw, { border: 10, cell: 4 });
|
|
167
|
+
shadowed(g, () => g.drawImage(sp, -sp.width / 2, -sp.height / 2)); tape(g, 0, -sp.height / 2 + 2, 0.05, 90);
|
|
168
|
+
}, { from: [520, 120], tilt: 0.22, dur: 0.55 });
|
|
169
|
+
g.restore();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// 屏幕空间:暗角+静态颗粒(Vox 的纸纹是贴死的,不逐帧抖;调研 §9 实测)
|
|
173
|
+
const finish = (c) => {
|
|
174
|
+
const v = PAINT.cached('y2_vig', W, H, (g) => { const r = g.createRadialGradient(W / 2, H / 2, H * 0.45, W / 2, H / 2, H * 1.05); r.addColorStop(0, 'rgba(40,30,20,0)'); r.addColorStop(1, 'rgba(40,30,20,.34)'); g.fillStyle = r; g.fillRect(0, 0, W, H); });
|
|
175
|
+
c.drawImage(v, 0, 0); c.drawImage(PAINT.grain('y2grain', 0.05, [40, 30, 20], 0.14), 0, 0);
|
|
176
|
+
};
|
|
177
|
+
const deskShot = (c, lt, t) => {
|
|
178
|
+
const ft = t - F0, cam = deskCam(ft);
|
|
179
|
+
const buf = PAINT.scratch('yt_y2desk'), g = buf.getContext('2d'); g.reset();
|
|
180
|
+
drawDesk(g, cam, ft);
|
|
181
|
+
c.fillStyle = C.desk; c.fillRect(0, 0, W, H);
|
|
182
|
+
// 快摇时沿速度方向的短模糊(PremiumBeat:模糊比相机运动更短)
|
|
183
|
+
const [vx, vy] = CAM.velocity(deskCam, ft);
|
|
184
|
+
CAM.motionBlur(c, buf, vx * 0.6, vy * 0.6, 7);
|
|
185
|
+
finish(c);
|
|
186
|
+
};
|
|
187
|
+
Y2.shot1 = deskShot; Y2.shot2 = deskShot;
|
|
188
|
+
|
|
189
|
+
// ---------- 镜 3:黑底拼贴(MC 的「黑、纸白、一种红」) ----------
|
|
190
|
+
const BIG = { x: 650, y: 610, w: 560, h: 560 };
|
|
191
|
+
const s3cam = (ft) => {
|
|
192
|
+
const l = ft - Y2.SHOTS.s3[0];
|
|
193
|
+
// 拉出揭示:硬切后从同一张图铺满(z=3.68,和上一镜推满时照片的屏幕宽度一致)0.85s 长尾拉到 1.0;之后慢推 1%/s
|
|
194
|
+
const e = longTail(seg(l, 0, 0.85)), z = CAM.zlerp(3.68, 1.0, e) * (1 + 0.01 * Math.max(0, l - 0.85));
|
|
195
|
+
return CAM.anchor(BIG.x, BIG.y, z, lerp(W / 2, BIG.x, e), lerp(H / 2, BIG.y, e));
|
|
196
|
+
};
|
|
197
|
+
const NCIRC = DG.hand(DG.ellipsePts(0, 0, 340, 330, -2.2, 1.08, 48), { amp: 3, seed: 4 }), NCIRC_L = DG.cum(NCIRC);
|
|
198
|
+
Y2.shot3 = (c, lt, t) => {
|
|
199
|
+
const ft = t - F0, l = ft - Y2.SHOTS.s3[0], cam = s3cam(ft), st = step(l);
|
|
200
|
+
c.save(); CAM.apply(c, cam);
|
|
201
|
+
// 结论还在同一张桌面上:一张大黑卡纸铺在桌上(MC 的黑底卡片),四边露出桌面纸纹
|
|
202
|
+
c.fillStyle = c.createPattern(CL.paperTile('desk', C.desk, { amt: 12, fibers: 320, fiberCol: [110, 95, 70] }), 'repeat'); c.fillRect(-800, -800, W + 1600, H + 1600);
|
|
203
|
+
placed(c, 960, 545, -0.012, (g) => { shadowed(g, () => { g.fillStyle = '#1b1b1a'; g.save(); g.translate(-890, -490); tornPoly(g, 1780, 980, 91, 3); g.fill(); g.restore(); });
|
|
204
|
+
g.save(); g.translate(-890, -490); tornPoly(g, 1780, 980, 91, 3); g.clip(); g.fillStyle = g.createPattern(CL.paperTile('black', '#1b1b1a', { amt: 5, fibers: 90, fiberCol: [90, 90, 85] }), 'repeat'); g.fillRect(0, 0, 1780, 980); g.restore(); });
|
|
205
|
+
placed(c, BIG.x, BIG.y, 0, (g) => { const sp = photoSprite('neuron', BIG.w, BIG.h, neuronDraw, { border: 20, cell: 9 }); g.drawImage(sp, -sp.width / 2, -sp.height / 2);
|
|
206
|
+
tape(g, 0, -sp.height / 2 + 4, 0.05, 220);
|
|
207
|
+
g.fillStyle = C.paper; g.font = `600 40px ${SERIF}`; g.textAlign = 'left'; g.fillText('它脑中的「猫」', -sp.width / 2, sp.height / 2 + 54); });
|
|
208
|
+
// 纸条标题:打字 13 字/秒(一拍二),随后荧光笔扫「猫神经元」
|
|
209
|
+
const T1 = '它自己长出了一个', T2 = '「猫神经元」', t0 = 0.4;
|
|
210
|
+
slideIn(c, l, 0.15, 1490, 330, -0.02, (g) => {
|
|
211
|
+
g.fillStyle = C.paper; g.save(); g.translate(-470, -150); tornPoly(g, 900, 290, 61, 4); g.fill(); g.restore();
|
|
212
|
+
g.fillStyle = C.ink; g.font = `900 70px ${SERIF}`; g.textBaseline = 'alphabetic';
|
|
213
|
+
const typed = TY.typedCount(st, t0, 13);
|
|
214
|
+
let x = -420; [...T1].forEach((ch, i) => { if (i < typed) g.fillText(ch, x, -40); x += g.measureText(ch).width; });
|
|
215
|
+
const w2 = g.measureText(T2).width; highlight(g, -428, 22, w2 + 20, 82, MO.sineInOut(seg(st, 1.5, 1.8)));
|
|
216
|
+
g.fillStyle = C.ink; x = -420; [...T2].forEach((ch, i) => { if (T1.length + i < typed) g.fillText(ch, x, 88); x += g.measureText(ch).width; });
|
|
217
|
+
}, { from: [700, -200], tilt: 0.18, dur: 0.4 });
|
|
218
|
+
// 红笔圈住(0.6s,12fps,首尾略交叉)
|
|
219
|
+
const cq = MO.cubicOut(seg(st, 0.95, 1.5));
|
|
220
|
+
if (cq > 0) { c.save(); c.translate(BIG.x, BIG.y); c.strokeStyle = C.red; c.lineWidth = 9; c.lineCap = 'round'; c.lineJoin = 'round'; DG.drawPartial(c, NCIRC, NCIRC_L, NCIRC_L[NCIRC_L.length - 1] * cq); c.restore(); }
|
|
221
|
+
// 花叔剪纸贴纸:带 12° 倾斜滑入放平;姿势按 12fps 步进(剪纸动画的一顿一顿)
|
|
222
|
+
const sq = clamp((st - 0.6) / 0.6);
|
|
223
|
+
if (sq > 0) {
|
|
224
|
+
const e = MO.quartOut(sq);
|
|
225
|
+
const pose = HUASHU.pose('point', 0, { ua1: 1.35 + 0.12 * Math.sin(st * 5), head: -0.05 + 0.04 * Math.sin(st * 3), mouth: 0.5 + 0.4 * Math.sin(st * 9) });
|
|
226
|
+
const HS = HUASHU.build({ x: 0, y: 0, s: 0.82, face: -1, pose });
|
|
227
|
+
const pal = { skin: '#f3c9a8', hair: '#171716', shirt: '#eeedeb', shorts: '#cdbf9f', hat: '#eeedeb', hatBand: '#b8433f', shoe: '#e8e4da', watch: '#171716', line: '#171716', lw: 5 };
|
|
228
|
+
const sp = PAINT.scratch('y2hs'), g = sp.getContext('2d'); g.reset(); if (!HERO.y2(g, st)) { g.translate(1500, 1040); HUASHU.draw(g, HS, pal); g.setTransform(1, 0, 0, 1, 0, 0); }
|
|
229
|
+
const sil = CL.stickerEdge(sp, 'y2sil', 12, '#f4f2ec'); // 剪纸贴纸:外扩 12px 的纸白边
|
|
230
|
+
c.save(); c.translate(1500 + 1100 * (1 - e), 1040 + 120 * (1 - e)); c.rotate(0.02 + 0.21 * (1 - Math.min(1, e * 1.15))); c.translate(-1500, -1040);
|
|
231
|
+
c.drawImage(sil, 0, 0); c.drawImage(sp, 0, 0); c.restore();
|
|
232
|
+
}
|
|
233
|
+
c.restore();
|
|
234
|
+
finish(c);
|
|
235
|
+
};
|
|
236
|
+
})();
|
|
237
|
+
|
|
238
|
+
SCENES['y2_s1'] = { draw: (c, lt, t) => Y2.shot1(c, lt, t) };
|
|
239
|
+
SCENES['y2_s2'] = { draw: (c, lt, t) => Y2.shot2(c, lt, t) };
|
|
240
|
+
SCENES['y2_s3'] = { draw: (c, lt, t) => Y2.shot3(c, lt, t) };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// 示范 · 白板手绘解说(RSA Animate 型)「AI 是怎么学会认猫的」(8 秒三镜)。语法卡:references/动画语法/y3_whiteboard.md
|
|
2
|
+
// 预览 index.html?film=demos/y3_whiteboard 渲染 render.py --film demos/y3_whiteboard --out y3.mp4
|
|
3
|
+
// 一整块大白板=世界,相机在板上移动;三段是同一个画面函数(DG.board 的笔画时间线),段只是为了 qa 分镜。
|
|
4
|
+
window.PUNCH = 0;
|
|
5
|
+
window.SCENE_LIBS = ['demos/_shared/hero.js', 'demos/y3_whiteboard/whiteboard.js'];
|
|
6
|
+
window.ERAS = [
|
|
7
|
+
{ id: 'y3_s1', dur: 2.5 }, // 画猫、写「猫?」、写问题
|
|
8
|
+
{ id: 'y3_s2', dur: 2.25, transition: { type: 'same', dur: 0.8 } }, // 甩镜(0.8s 带运动模糊,笔不离板)
|
|
9
|
+
{ id: 'y3_s3', dur: 3.25, transition: { type: 'same', dur: 0.85 } }, // 板上平滑移动 → 画花叔、「是猫!」→ 拉远看全图
|
|
10
|
+
];
|