@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,162 @@
|
|
|
1
|
+
// Y3 · 白板手绘解说(RSA Animate 型:一整块大白板+相机移动):「AI 是怎么学会认猫的」
|
|
2
|
+
// 依据:语法卡 y3_whiteboard.md(RSA 实测:黑 #0A0503+唯一强调色橙 #EB701F;写字≈16 字/秒、比口播晚 0.5s;
|
|
3
|
+
// 平滑移动 easeInOutSine 中位 1.8s;远跳用 0.8s 带运动模糊的甩镜;手从右下入画、画完出画;结尾拉远看全图)
|
|
4
|
+
// ①画一只猫、写「猫?」、写问题 → 转场 A「甩镜」:笔不离板,相机 0.8s 甩到板上远处,带运动模糊
|
|
5
|
+
// ②画 1000 万张图的一摞(橙色数字直接弹出、不带手)、箭头、神经网络、「自己找规律」
|
|
6
|
+
// → 转场 B「板上平滑移动」:相机 sine 缓入缓出 0.95s 斜移到旁边空白处,手在相机落定前就开始画
|
|
7
|
+
// ③画花叔指着猫、打勾,橙色「是猫!」弹出 → 收尾「拉远看全图」(RSA / VideoScribe 默认的 Zoom at end),整板铺一层灰
|
|
8
|
+
// 用到的库:DG.board(笔画时间线+跟随的笔)、DG.pen、DG.revealMask/zigzag(人物 Reveal)、CAM(关键帧相机、运动模糊)。
|
|
9
|
+
(() => {
|
|
10
|
+
const W = 1920, H = 1080;
|
|
11
|
+
const { clamp, lerp, rng } = U;
|
|
12
|
+
const seg = MO.seg;
|
|
13
|
+
const Y3 = window.Y3 = {};
|
|
14
|
+
const F0 = 0; Y3.F0 = F0;
|
|
15
|
+
Y3.SHOTS = { s1: [0, 2.5], s2: [2.5, 4.75], s3: [4.75, 8.0] };
|
|
16
|
+
const INK = '#0A0503', ORANGE = '#EF7226', WASH = 'rgba(239,114,38,.22)', BOARD = '#FBFBFB';
|
|
17
|
+
const LW = 6.5; // RSA 实测笔画约画高 0.6–0.7%(1080p≈6–7px)
|
|
18
|
+
const R1 = [960, 540], R2 = [3000, 540], R3 = [3700, 1360], K3 = 0.8;
|
|
19
|
+
const L3 = (x, y) => [R3[0] + (x - 960) * K3, R3[1] + (y - 540) * K3];
|
|
20
|
+
const SPEED = 2600; // 线条笔速:世界像素/秒(演示片比 RSA 实拍快约 2 倍,见卡片)
|
|
21
|
+
|
|
22
|
+
// ---------- 笔画时间线:按顺序排,时长 = 长度 / 笔速 ----------
|
|
23
|
+
const B = DG.board({ ink: INK, lw: LW, speed: SPEED, font: '"LXGWWenKai-500"' });
|
|
24
|
+
const tr = DG.xform, arc = DG.arcPts, pathOf = pts => U.poly(pts);
|
|
25
|
+
|
|
26
|
+
// 猫的笔画(本地坐标,原点 = 坐姿底部中心,约 400 高)
|
|
27
|
+
const catStrokes = (ox, oy, s, sp, w, whiskers = true) => {
|
|
28
|
+
const P = (pts) => tr(pts, ox, oy, s);
|
|
29
|
+
B.line(P([[-60, -250], [-92, -330], [-30, -300], [30, -300], [92, -330], [60, -250]]), { w, speed: sp });
|
|
30
|
+
B.line(P(arc(0, -230, 100, 82, -0.55, Math.PI + 0.55, 26).reverse()), { w, speed: sp });
|
|
31
|
+
B.line(P([[-80, -170], [-120, -60], [-110, 0], [110, 0], [120, -60], [80, -170]]), { w, speed: sp });
|
|
32
|
+
B.line(P([[110, -20], [190, -40], [210, -130], [170, -190]]), { w, speed: sp });
|
|
33
|
+
B.line(P(arc(-38, -240, 12, 16, 0, Math.PI * 2, 14)), { w: w * 0.8, speed: sp * 1.6 });
|
|
34
|
+
B.line(P(arc(38, -240, 12, 16, 0, Math.PI * 2, 14)), { w: w * 0.8, speed: sp * 1.6 });
|
|
35
|
+
B.line(P([[-10, -205], [10, -205], [0, -194], [-10, -205]]), { w: w * 0.8, speed: sp * 1.6, smooth: false });
|
|
36
|
+
if (whiskers) for (const k of [-1, 1]) B.line(P([[k * 30, -196], [k * 110, -206]]), { w: w * 0.6, speed: sp * 2 });
|
|
37
|
+
};
|
|
38
|
+
const catBody = (ox, oy, s) => pathOf(tr([[-80, -170], [-120, -60], [-110, 0], [110, 0], [120, -60], [80, -170], [60, -160], [0, -150], [-60, -160]], ox, oy, s));
|
|
39
|
+
|
|
40
|
+
// ===== 第一块 =====
|
|
41
|
+
B.at(0.12);
|
|
42
|
+
catStrokes(760, 820, 1.0, SPEED * 0.75, LW + 1);
|
|
43
|
+
B.fill(catBody(760, 820, 1.0), WASH);
|
|
44
|
+
B.text('猫?', 1060, 560, 150, { col: ORANGE, rate: 7.5 }); // 标题级:7.5 字/秒
|
|
45
|
+
B.line([[1080, 610], [1020, 660], [930, 690]], { col: ORANGE });
|
|
46
|
+
B.line([[960, 668], [928, 690], [966, 704]], { col: ORANGE, smooth: false });
|
|
47
|
+
B.text('AI 怎么认出它?', 960, 960, 84, { align: 'center' }); // 正文:16 字/秒
|
|
48
|
+
B.line([[1290, 590], [1500, 560], [1760, 540]], { col: ORANGE, speed: SPEED * 1.6 }); // 指向下一块的橙箭头:甩镜有了方向
|
|
49
|
+
B.line([[1725, 512], [1764, 540], [1728, 572]], { col: ORANGE, smooth: false });
|
|
50
|
+
const END1 = B.cur;
|
|
51
|
+
// 甩镜时笔不离板:笔尖在屏幕上从第一块的箭头头滑向第二块的起笔点,板在笔下飞过,留下一条橙色连线(笔一直在画,视线一直被它领着)。
|
|
52
|
+
// 轨迹 = 屏幕上的笔位置按当时的相机反算成世界点(第一次画时算好)。
|
|
53
|
+
const trail = { kind: 'custom', from: [1764, 540], to: [2382, 278], t0: B.cur + 0.01, t1: 3.3, col: ORANGE, w: LW, wob: 14,
|
|
54
|
+
draw(g, q) {
|
|
55
|
+
const s = this;
|
|
56
|
+
if (!s.pts) { const N = 72, a = CAM.toScreen(camAt(s.t0), ...s.from), b = CAM.toScreen(camAt(s.t1), ...s.to); s.pts = [];
|
|
57
|
+
for (let i = 0; i <= N; i++) { const u = i / N, cm = camAt(lerp(s.t0, s.t1, u)), e = MO.sineInOut(u), sx = lerp(a[0], b[0], e), sy = lerp(a[1], b[1], e) + Math.sin(u * Math.PI * 3) * s.wob * Math.sin(u * Math.PI);
|
|
58
|
+
s.pts.push(CAM.toWorld(cm, sx, sy)); }
|
|
59
|
+
s.cum = DG.cum(s.pts); }
|
|
60
|
+
const k = q * (s.pts.length - 1), i = Math.floor(k), f = k - i, d = s.cum[i] + (i < s.pts.length - 1 ? (s.cum[i + 1] - s.cum[i]) * f : 0);
|
|
61
|
+
g.strokeStyle = s.col; g.lineWidth = s.w; return DG.drawPartial(g, s.pts, s.cum, d);
|
|
62
|
+
},
|
|
63
|
+
start() { return this.from; }, end() { return this.pts ? this.pts[this.pts.length - 1] : this.to; } };
|
|
64
|
+
B.push(trail);
|
|
65
|
+
|
|
66
|
+
// ===== 第二块(相机 2.5→3.3 甩过来) =====
|
|
67
|
+
B.at(3.31);
|
|
68
|
+
for (let i = 2; i >= 0; i--) { const x = 2330 + i * 26, y = 330 - i * 26; B.fill(pathOf([[x, y], [x + 260, y], [x + 260, y + 190], [x, y + 190]]), BOARD, 0.01); B.line([[x, y], [x + 260, y], [x + 260, y + 190], [x, y + 190], [x, y + 3]], { w: 6, speed: SPEED * 4.5, smooth: false, amp: 0 }); }
|
|
69
|
+
B.line(tr([[-60, -250], [-92, -330], [-30, -300], [30, -300], [92, -330], [60, -250], ...arc(0, -230, 100, 82, -0.55, Math.PI + 0.55, 20).reverse(), [-60, -250]], 2460, 560, 0.4), { w: 5, speed: SPEED * 3, gap: 0 });
|
|
70
|
+
B.line(tr([[-38, -240], [-38, -236], [38, -236], [38, -240]], 2460, 560, 0.4), { w: 6, speed: SPEED, smooth: false, gap: 0.01 });
|
|
71
|
+
B.pop('×1000万张', 2470, 690, 74, { col: ORANGE });
|
|
72
|
+
B.line([[2680, 450], [2800, 452], [2860, 450]]);
|
|
73
|
+
B.line([[2830, 425], [2864, 450], [2830, 476]], { smooth: false });
|
|
74
|
+
const NET = [[2960, [330, 450, 570]], [3090, [300, 450, 600]], [3220, [400, 500]]];
|
|
75
|
+
for (const [x, ys] of NET) for (const y of ys) B.line(arc(x, y, 26, 26, -Math.PI / 2, Math.PI * 1.55, 16), { w: 6, speed: SPEED * 5, amp: 0.8, min: 0.03, gap: 0.01 });
|
|
76
|
+
for (let a = 0; a < 2; a++) for (const y0 of NET[a][1]) for (const y1 of NET[a + 1][1]) B.segment([NET[a][0] + 26, y0], [NET[a + 1][0] - 26, y1], { w: 3 });
|
|
77
|
+
B.at(B.cur + 0.05);
|
|
78
|
+
B.text('自己找规律', 3090, 780, 64, { align: 'center' });
|
|
79
|
+
// 橙色连线:从网络引向下一块(相机开始移动时画,手领着相机走)
|
|
80
|
+
B.at(4.62); B.line([[3250, 470], [3420, 560], [3480, 760], [3420, 900]], { col: ORANGE, speed: SPEED * 1.4 }); B.line([[3392, 868], [3420, 902], [3452, 872]], { col: ORANGE, smooth: false });
|
|
81
|
+
const END2 = B.cur;
|
|
82
|
+
|
|
83
|
+
// ===== 第三块(相机 5.1→6.05 平滑斜移;手在 5.8 就入画开画) =====
|
|
84
|
+
B.at(5.35);
|
|
85
|
+
// VideoScribe 的 Reveal 画法——完整线稿藏在下面,笔尖从上往下走 8 行之字形,笔过之处揭开(笔尖永远在揭开的前沿上)
|
|
86
|
+
const huashu = () => { const [x, y] = L3(500, 1010); return HUASHU.build({ x, y, s: 0.95 * K3, face: 1, pose: HUASHU.pose('point', 0, { ua1: 1.25, mouth: 0.8 }) }); };
|
|
87
|
+
const rigZig = (HSb) => { if (rigZig.c) return rigZig.c; const js = Object.values(HSb.J).filter(v => Array.isArray(v)); const xs = js.map(v => v[0]), ys = js.map(v => v[1]);
|
|
88
|
+
const x0 = Math.min(...xs) - 70, x1 = Math.max(...xs) + 50, y0 = Math.min(...ys) - 20, y1 = Math.max(...ys) + 40, rows = 8;
|
|
89
|
+
rigZig.rowH = (y1 - y0) / rows; const zz = DG.zigzag(x0, y0, x1 - x0, y1 - y0, rows); return (rigZig.c = { zz, cum: DG.cum(zz) }); };
|
|
90
|
+
const RIG3 = { t0: B.cur, t1: B.cur + 0.7 };
|
|
91
|
+
B.push({ kind: 'custom', t0: RIG3.t0, t1: RIG3.t1,
|
|
92
|
+
draw(g, q, ft) {
|
|
93
|
+
const HSb = huashu(), { zz, cum } = rigZig(HSb), d = cum[cum.length - 1] * q;
|
|
94
|
+
g.save(); if (q < 1) g.clip(DG.revealMask(zz, cum, d, rigZig.rowH * 2.2)); g.lineJoin = 'round'; g.lineCap = 'round'; // 揭完就不再裁:之字形拐角处的三角会漏掉
|
|
95
|
+
const [fx, fy] = L3(500, 1010);
|
|
96
|
+
if (!HERO.y3(g, fx, fy, 0.95 * K3 * 740, ft)) {
|
|
97
|
+
for (const p of HSb.parts) { g.fillStyle = p.role === 'hair' ? INK : BOARD; g.fill(p.path); g.strokeStyle = INK; g.lineWidth = LW * 0.9; g.stroke(p.path); }
|
|
98
|
+
HUASHU.details(g, HSb, { line: INK, lw: 4.5, glass: INK, cheek: null, earLine: 'rgba(0,0,0,.5)', shoeHole: INK });
|
|
99
|
+
}
|
|
100
|
+
g.restore();
|
|
101
|
+
return q < 1 ? DG.pointAt(zz, cum, d) : null;
|
|
102
|
+
},
|
|
103
|
+
start() { return rigZig(huashu()).zz[0]; }, end() { const z = rigZig(huashu()).zz; return z[z.length - 1]; } });
|
|
104
|
+
B.at(RIG3.t1 + 0.03);
|
|
105
|
+
catStrokes(...L3(1150, 900), K3 * 0.95, SPEED * 1.6, LW, false);
|
|
106
|
+
B.line([L3(1000, 330), L3(1070, 400), L3(1240, 200)], { col: ORANGE, w: LW + 2, speed: SPEED * 1.5, smooth: false });
|
|
107
|
+
B.pop('是猫!', ...L3(1500, 410), 150 * K3, { col: ORANGE, align: 'left' });
|
|
108
|
+
// 收尾:拉远的同时圈住「是猫!」,全景停住时回到第一块,在问题下面划一道——问题和答案连上,笔停在最后一笔上
|
|
109
|
+
B.at(Math.max(B.cur + 0.02, 6.95));
|
|
110
|
+
B.line(DG.ellipsePts(...L3(1720, 360), 300 * K3, 120 * K3, -2.9, 1.08, 40), { col: ORANGE, w: LW, speed: SPEED * 1.1 });
|
|
111
|
+
B.at(Math.max(B.cur + 0.06, 7.52));
|
|
112
|
+
B.line([[640, 1000], [960, 1006], [1290, 998]], { col: ORANGE, w: LW + 2, speed: SPEED * 0.9 });
|
|
113
|
+
const END3 = B.cur;
|
|
114
|
+
Y3.timeline = { END1, END2, END3, RIG3 };
|
|
115
|
+
Y3.board = B;
|
|
116
|
+
|
|
117
|
+
// ---------- 相机 ----------
|
|
118
|
+
const OVER = { x: 2400, y: 930, z: 0.45 };
|
|
119
|
+
const camAt = ft => CAM.at([
|
|
120
|
+
{ t: 0, x: R1[0], y: R1[1], z: 1.0 },
|
|
121
|
+
{ t: 2.5, x: R1[0] + 12, y: R1[1] + 6, z: 1.02, ease: MO.sineInOut },
|
|
122
|
+
// 甩镜:0.8s(RSA 实测远跳约 0.8s,带运动模糊)
|
|
123
|
+
{ t: 3.3, x: R2[0], y: R2[1], z: 1.0, ease: MO.cubicInOut },
|
|
124
|
+
{ t: 4.7, x: R2[0] + 16, y: R2[1] + 6, z: 1.02, ease: MO.sineInOut },
|
|
125
|
+
// 板上平滑移动:easeInOutSine 0.9s(实测中位 1.8s,演示片压缩)
|
|
126
|
+
{ t: 5.6, x: R3[0], y: R3[1] - 20, z: 1.12, ease: MO.sineInOut },
|
|
127
|
+
{ t: 6.85, x: R3[0] + 10, y: R3[1] - 14, z: 1.14, ease: MO.sineInOut },
|
|
128
|
+
// 拉远看全图,停 0.5s
|
|
129
|
+
{ t: 7.5, ...OVER, ease: MO.sineInOut },
|
|
130
|
+
{ t: 8.0, ...OVER, z: OVER.z * 0.985, ease: MO.sineInOut },
|
|
131
|
+
], ft);
|
|
132
|
+
Y3.camAt = camAt;
|
|
133
|
+
|
|
134
|
+
// ---------- 画 ----------
|
|
135
|
+
const drawBoard = (c, cam, ft) => {
|
|
136
|
+
// 结尾整板铺一层灰(RSA 实测),在拉远时 0.5s 淡入
|
|
137
|
+
const gray = MO.sineInOut(seg(ft, 6.9, 7.4));
|
|
138
|
+
c.fillStyle = gray > 0 ? PAINT.rgb(PAINT.mix(PAINT.hex(BOARD), [238, 238, 236], gray)) : BOARD; c.fillRect(0, 0, W, H);
|
|
139
|
+
// 极淡的擦痕:屏幕空间平铺、跟着相机平移走(不随缩放放大)
|
|
140
|
+
const sm = PAINT.cached('y3smudge', 1024, 1024, g => { const r = rng(5); for (let i = 0; i < 22; i++) { g.fillStyle = `rgba(120,120,130,${0.003 + r() * 0.005})`; g.beginPath(); g.ellipse(r() * 1024, r() * 1024, 80 + r() * 200, 14 + r() * 40, r() * 3, 0, 7); g.fill(); } });
|
|
141
|
+
c.save(); const ox = -(((cam.x * cam.z) % 1024) + 1024) % 1024, oy = -(((cam.y * cam.z) % 1024) + 1024) % 1024; c.translate(ox, oy); c.fillStyle = c.createPattern(sm, 'repeat'); c.fillRect(0, 0, W + 1024, H + 1024); c.restore();
|
|
142
|
+
c.save(); CAM.apply(c, cam);
|
|
143
|
+
const r = B.draw(c, ft);
|
|
144
|
+
c.restore();
|
|
145
|
+
return r;
|
|
146
|
+
};
|
|
147
|
+
const shot = (c, lt, t) => {
|
|
148
|
+
const ft = t - F0, cam = camAt(ft);
|
|
149
|
+
const buf = PAINT.scratch('yt_y3board'), g = buf.getContext('2d'); g.reset();
|
|
150
|
+
const { tip, col } = drawBoard(g, cam, ft);
|
|
151
|
+
const [vx, vy] = CAM.velocity(camAt, ft);
|
|
152
|
+
c.fillStyle = BOARD; c.fillRect(0, 0, W, H);
|
|
153
|
+
const whip = ft > 2.5 && ft < 3.3; // 只有甩镜带模糊;板上平滑移动和结尾拉远都是清楚的(RSA 实测)
|
|
154
|
+
CAM.motionBlur(c, buf, whip ? vx * 0.8 : 0, whip ? vy * 0.8 : 0, 9);
|
|
155
|
+
const [hx, hy] = B.penAt(ft, cam, tip);
|
|
156
|
+
DG.pen(c, hx, hy, col || B.penColor(ft), Math.sin(ft * 7) * 0.03); // 只有一支马克笔,笔尾帽 = 墨色
|
|
157
|
+
};
|
|
158
|
+
Y3.shot1 = shot; Y3.shot2 = shot; Y3.shot3 = shot;
|
|
159
|
+
SCENES['y3_s1'] = { draw: shot };
|
|
160
|
+
SCENES['y3_s2'] = { draw: shot };
|
|
161
|
+
SCENES['y3_s3'] = { draw: shot };
|
|
162
|
+
})();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// 示范 · 故事型简笔角色(storytime)「我第一次让 AI 帮我写代码」(8.75 秒五镜)。语法卡:references/动画语法/y4_storytime.md
|
|
2
|
+
// 预览 index.html?film=demos/y4_storytime 渲染 render.py --film demos/y4_storytime --out y4.mp4(口播音轨另配;口型读 vo_env.js 的包络)
|
|
3
|
+
// 镜头按口播切(不是按节拍):一句口播 ≈ 一个镜头,反应镜头放在两句之间的「沉默」里。
|
|
4
|
+
window.PUNCH = 0; // storytime 不卡音乐拍
|
|
5
|
+
window.FILM_DURATION = 8.75;
|
|
6
|
+
window.SCENE_LIBS = ['demos/y4_storytime/vo_env.js', 'demos/y4_storytime/story.js', 'demos/y4_storytime/world.js'];
|
|
7
|
+
window.SCENE_DIR = 'demos/y4_storytime';
|
|
8
|
+
window.ERAS = [
|
|
9
|
+
{ id: 's1_desk', dur: 2.0 }, // 中景:对镜头讲
|
|
10
|
+
{ id: 's2_screen', dur: 2.4, transition: { type: 'whip', dur: 0.26, dir: 1 } }, // 甩镜 → 屏幕(过肩)。甩在两句之间的换气里,别切在句中
|
|
11
|
+
{ id: 's3_react', dur: 1.2, transition: { type: 'smash', dur: 0.32 } }, // 砸镜 → 反应大特写(沉默)
|
|
12
|
+
{ id: 's4_wide', dur: 1.65, transition: { type: 'cut', dur: 0.02 } }, // 硬切 → 同一房间大全景
|
|
13
|
+
{ id: 's5_close', dur: 1.5, transition: { type: 'cut', dur: 0.02 } }, // 跳近 → 近景,笑点+定住
|
|
14
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// S1 中景:花叔坐在桌后对镜头讲。口播「我第一次让AI帮我写代码,」
|
|
2
|
+
// 动:口型(口播包络,一拍二)、说话点头、呼吸、换姿势回弹(talk → point)、眼神跟手、眨眼、窗外云、镜头慢推+微漂。
|
|
3
|
+
// 房间、桌子、笔记本都在 world.js(S4/S5 同一个房间换镜头)。
|
|
4
|
+
(() => {
|
|
5
|
+
const S = STORY;
|
|
6
|
+
const KEYS = [[0, 'talk', 'happy'], [1.12, 'point', 'talk']];
|
|
7
|
+
SCENES['s1_desk'] = {
|
|
8
|
+
draw(c, lt, t) {
|
|
9
|
+
const ts = MO.step(t, 24); // 角色按 24fps(Flash 默认帧率)走,镜头按 60fps 平滑
|
|
10
|
+
const zoom = 1.01 + 0.035 * MO.expoOut(Math.min(1, lt / 2.0)) + 0.03 * (1 - MO.expoOut(Math.min(1, lt / 0.5))); // 开场 1.04→1.01 落定,再慢推 3.5%
|
|
11
|
+
const [dx, dy] = CAM.drift(t, 4, 1);
|
|
12
|
+
CAM.with(c, { x: 900 + dx, y: 520 + dy, z: zoom }, c => {
|
|
13
|
+
const st = S.poseAt(KEYS, ts), al = S.alive(ts, 165);
|
|
14
|
+
S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts), look: ts > 1.12 ? [1, 0] : [0, 0], squash: st.squash * al.breathe, bob: al.bob, tilt: ts > 1.12 ? 0.05 : -0.03 });
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
})();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// S2 过肩看屏幕:他只打了一行「帮我做个网站」,AI 回了一整面墙的代码,越滚越快,计数器在「三千」那个字上砸出「3000 行」。
|
|
2
|
+
// 动:打字、代码瀑布加速滚、行数计数、3000 砸出+屏幕震、前景花叔后脑勺随呼吸晃。
|
|
3
|
+
(() => {
|
|
4
|
+
const S = STORY, C = S.C, W = 1920, H = 1080;
|
|
5
|
+
const { clamp, lerp, rng } = U;
|
|
6
|
+
const T_TYPE = 2.30, T_SEND = 2.78, T_SLAM = 3.70; // 全片时间;3.70 = 口播「三千」
|
|
7
|
+
// 代码行(色块表示,远看就是代码;颜色取常见编辑器主题)
|
|
8
|
+
const COLS = ['#C792EA', '#82AAFF', '#C3E88D', '#F78C6C', '#89DDFF', '#FFCB6B', '#676E95'];
|
|
9
|
+
const LINES = (() => { const r = rng(42), out = []; let ind = 0;
|
|
10
|
+
for (let i = 0; i < 400; i++) { if (r() < 0.18) ind = Math.min(4, ind + 1); else if (r() < 0.2) ind = Math.max(0, ind - 1);
|
|
11
|
+
const toks = []; let x = ind * 34; const n = 1 + (r() * 4 | 0); for (let k = 0; k < n; k++) { const w = 30 + r() * 150; toks.push([x, w, COLS[(r() * COLS.length) | 0]]); x += w + 14; }
|
|
12
|
+
out.push(toks); } return out; })();
|
|
13
|
+
// 滚动位置:发送后 0.25s 开始,加速(三次方),在 SLAM 时达到每秒上千行的视觉速度
|
|
14
|
+
const scrollAt = t => { const q = Math.max(0, t - T_SEND - 0.25); return 46 * (q * 6 + Math.pow(q * 2.6, 3) * 6); };
|
|
15
|
+
const countAt = t => t >= T_SLAM ? 3000 : Math.min(2999, Math.floor(Math.pow(Math.max(0, (t - T_SEND - 0.25)) / (T_SLAM - T_SEND - 0.25), 2.4) * 2999));
|
|
16
|
+
SCENES['s2_screen'] = {
|
|
17
|
+
draw(c, lt, t) {
|
|
18
|
+
const slam = t - T_SLAM;
|
|
19
|
+
const shake = slam > 0 && slam < 0.3 ? MO.settle(slam, 14, 9, 14) : 0; // 震:9Hz、0.3s 内衰减完
|
|
20
|
+
const zoom = 1.0 + 0.02 * Math.min(1, lt / 2.2) + (slam > 0 ? 0.04 * MO.expoOut(clamp(slam / 0.12)) : 0);
|
|
21
|
+
c.fillStyle = '#15161C'; c.fillRect(0, 0, W, H);
|
|
22
|
+
CAM.with(c, { x: 960 + shake, y: 540 - shake * 0.5, z: zoom }, c => {
|
|
23
|
+
// 屏幕边框
|
|
24
|
+
c.fillStyle = '#2B2D36'; c.beginPath(); c.roundRect(150, 40, 1620, 1000, 34); c.fill();
|
|
25
|
+
c.save(); c.beginPath(); c.roundRect(190, 80, 1540, 920, 14); c.clip();
|
|
26
|
+
c.fillStyle = C.screen; c.fillRect(190, 80, 1540, 920);
|
|
27
|
+
// 我的那一行:右侧气泡,打字
|
|
28
|
+
const sent = MO.at(t, T_SEND, 0.18), scr = scrollAt(t);
|
|
29
|
+
const bubY = 200 - Math.min(scr, 400);
|
|
30
|
+
const msg = TY.typed('帮我做个网站', MO.at(t, T_TYPE, 0.4));
|
|
31
|
+
if (bubY > 60) {
|
|
32
|
+
c.fillStyle = sent > 0 ? '#3B82F6' : '#2F3342'; const bw = TY.width(c, '帮我做个网站', 40, 'PuHui-Bold') + 60;
|
|
33
|
+
c.beginPath(); c.roundRect(1680 - bw, bubY - 52, bw, 78, 24); c.fill();
|
|
34
|
+
TY.text(c, msg + (sent <= 0 && Math.floor(t * 4) % 2 ? '|' : ''), 1680 - bw + 30, bubY + 2, { size: 40, fam: 'PuHui-Bold', color: '#fff', align: 'left' });
|
|
35
|
+
}
|
|
36
|
+
// AI 的回复:代码墙,从气泡下方开始往上滚
|
|
37
|
+
if (t > T_SEND + 0.1) {
|
|
38
|
+
const top = 300 - scr, lh = 46;
|
|
39
|
+
const i0 = Math.max(0, Math.floor((150 - top) / lh)), i1 = Math.min(LINES.length * 8, Math.ceil((1000 - top) / lh));
|
|
40
|
+
for (let i = i0; i < i1; i++) { const y = top + i * lh; if (y < 150 || y > 1000) continue;
|
|
41
|
+
const shown = MO.at(t, T_SEND + 0.1 + Math.min(i, 12) * 0.03, 0.05); if (shown <= 0) continue; // 头几行逐行冒出来
|
|
42
|
+
c.fillStyle = '#4A4F66'; c.fillRect(220, y - 12, 44, 16);
|
|
43
|
+
for (const [x, w, col] of LINES[i % LINES.length]) { c.fillStyle = col; c.globalAlpha = 0.9; c.beginPath(); c.roundRect(300 + x, y - 14, w * shown, 20, 10); c.fill(); }
|
|
44
|
+
c.globalAlpha = 1; }
|
|
45
|
+
// 速度线感:滚太快时整屏加一层竖向拖影
|
|
46
|
+
const v = (scrollAt(t + 1 / 60) - scr) * 60; if (v > 1500) { c.fillStyle = `rgba(32,34,45,${clamp((v - 1500) / 6000) * 0.5})`; c.fillRect(190, 150, 1540, 850); }
|
|
47
|
+
}
|
|
48
|
+
// 顶栏(画在内容之后:滚上去的内容钻到它下面)
|
|
49
|
+
c.fillStyle = '#2A2D3A'; c.fillRect(190, 80, 1540, 64);
|
|
50
|
+
['#FF5F57', '#FEBC2E', '#28C840'].forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(232 + i * 34, 112, 10, 0, Math.PI * 2); c.fill(); });
|
|
51
|
+
TY.text(c, 'AI助手', 960, 124, { size: 30, fam: 'PuHui-Bold', color: '#9AA3B8' });
|
|
52
|
+
const n = countAt(t);
|
|
53
|
+
if (t > T_SEND + 0.2) TY.text(c, `${n}行`, 1700, 124, { size: 32, fam: 'PuHui-Bold', color: n >= 3000 ? '#FFCB6B' : '#9AA3B8', align: 'right' });
|
|
54
|
+
// 3000 行:砸出来(backOut 过冲 23%)+底板压暗
|
|
55
|
+
if (slam > 0) {
|
|
56
|
+
c.fillStyle = `rgba(10,10,16,${0.6 * MO.expoOut(clamp(slam / 0.15))})`; c.fillRect(190, 80, 1540, 920);
|
|
57
|
+
TY.pop(c, '3000行', 960, 600, clamp(slam / 0.24), { size: 230, fam: 'PuHui-Black', color: '#FFCB6B', over: 3.5, stroke: '#15161C', strokeW: 18 });
|
|
58
|
+
}
|
|
59
|
+
c.restore();
|
|
60
|
+
});
|
|
61
|
+
// 前景:花叔的背影(过肩)。不跟镜头推,只随呼吸起伏 —— 前景与屏幕的视差让镜头「有空间」。
|
|
62
|
+
// 返修:第一版是纯黑剪影,审片读成「另一个影子人」。现在画出主角背面的识别点:白帽+帽带、耳朵、眼镜腿、后颈、白 T,屏幕那侧加一圈冷色轮廓光。
|
|
63
|
+
const by = 840 + 6 * Math.sin(2 * Math.PI * t / 1.6) + (slam > 0 ? -MO.settle(slam, 18, 4, 7) : 0), hx = 330, L = C.line;
|
|
64
|
+
c.save(); c.lineWidth = 7; c.strokeStyle = L; c.lineJoin = 'round'; c.lineCap = 'round';
|
|
65
|
+
c.fillStyle = '#FFFFFF'; c.beginPath(); c.ellipse(hx, by + 300, 330, 200, 0, Math.PI, 0); c.fill(); c.stroke(); // 肩(白 T)
|
|
66
|
+
c.fillStyle = '#FFE2CC'; c.beginPath(); c.roundRect(hx - 70, by + 120, 140, 110, 30); c.fill(); c.stroke(); // 后颈
|
|
67
|
+
c.strokeStyle = '#E6E6EA'; c.lineWidth = 16; c.beginPath(); c.moveTo(hx - 95, by + 205); c.quadraticCurveTo(hx, by + 175, hx + 95, by + 205); c.stroke(); c.strokeStyle = L; c.lineWidth = 7; // 领口
|
|
68
|
+
for (const sx of [-1, 1]) { c.fillStyle = '#FFE2CC'; c.beginPath(); c.ellipse(hx + sx * 196, by + 40, 34, 52, sx * 0.2, 0, Math.PI * 2); c.fill(); c.stroke(); } // 耳朵
|
|
69
|
+
c.fillStyle = '#1B1B1F'; c.beginPath(); c.ellipse(hx, by + 40, 190, 175, 0, 0, Math.PI * 2); c.fill(); // 后脑(短黑发)
|
|
70
|
+
c.strokeStyle = '#3A3A44'; c.lineWidth = 4; for (let i = -3; i <= 3; i++) { c.beginPath(); c.moveTo(hx + i * 40, by + 130); c.lineTo(hx + i * 44, by + 175); c.stroke(); } // 发梢
|
|
71
|
+
c.strokeStyle = L; c.lineWidth = 9; for (const sx of [-1, 1]) { c.beginPath(); c.moveTo(hx + sx * 214, by + 10); c.lineTo(hx + sx * 236, by - 40); c.stroke(); } // 眼镜腿(挂在耳朵上,往前伸)
|
|
72
|
+
c.lineWidth = 7;
|
|
73
|
+
c.fillStyle = '#FFFFFF'; c.beginPath(); c.moveTo(hx - 180, by - 20); c.bezierCurveTo(hx - 180, by - 290, hx + 180, by - 290, hx + 180, by - 20); c.closePath(); c.fill(); c.stroke(); // 帽冠
|
|
74
|
+
c.save(); c.beginPath(); c.moveTo(hx - 180, by - 20); c.bezierCurveTo(hx - 180, by - 290, hx + 180, by - 290, hx + 180, by - 20); c.closePath(); c.clip(); c.fillStyle = '#E4E4E8'; c.fillRect(hx - 200, by - 85, 400, 40); c.restore(); // 帽带
|
|
75
|
+
c.fillStyle = '#FFFFFF'; c.beginPath(); c.ellipse(hx, by - 12, 280, 64, 0, 0, Math.PI * 2); c.fill(); c.stroke(); // 帽檐
|
|
76
|
+
// 屏幕光轮廓(右上侧,冷色)
|
|
77
|
+
c.globalCompositeOperation = 'source-atop'; c.strokeStyle = 'rgba(140,200,255,0.55)'; c.lineWidth = 14;
|
|
78
|
+
c.beginPath(); c.ellipse(hx, by - 12, 272, 58, 0, -1.2, 0.1); c.stroke(); c.beginPath(); c.ellipse(hx, by + 300, 322, 192, 0, -1.3, -0.2); c.stroke();
|
|
79
|
+
c.restore();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
})();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// S3 反应大特写:砸镜进来,死鱼眼,一言不发。整段是沉默 —— 笑点在「停顿」里。
|
|
2
|
+
// 动:砸镜回弹(转场里)、极慢推进 6%、镜片反光扫过、汗滴滑落、一次慢眨眼、背景暗角呼吸、嘴角抽一下。
|
|
3
|
+
(() => {
|
|
4
|
+
const S = STORY, C = S.C, W = 1920, H = 1080;
|
|
5
|
+
const { clamp } = U;
|
|
6
|
+
SCENES['s3_react'] = {
|
|
7
|
+
draw(c, lt, t) {
|
|
8
|
+
const ts = MO.step(t, 24);
|
|
9
|
+
const lts = ts - 4.4; // 本段局部(步进后)
|
|
10
|
+
// 虚空背景:纯色+暗角(反应镜头把环境拿掉,只留脸)
|
|
11
|
+
c.fillStyle = C.void; c.fillRect(0, 0, W, H);
|
|
12
|
+
const vg = c.createRadialGradient(960, 560, 200, 960, 560, 1100); vg.addColorStop(0, 'rgba(255,255,255,0.10)'); vg.addColorStop(1, 'rgba(0,0,0,0.35)');
|
|
13
|
+
c.fillStyle = vg; c.fillRect(0, 0, W, H);
|
|
14
|
+
// 集中线(很淡、极慢转):漫画「凝固」的符号
|
|
15
|
+
c.save(); c.translate(960, 560); c.rotate(t * 0.04); c.strokeStyle = 'rgba(255,255,255,0.07)'; c.lineWidth = 6;
|
|
16
|
+
for (let i = 0; i < 48; i++) { const a = i / 48 * Math.PI * 2; c.beginPath(); c.moveTo(Math.cos(a) * 640, Math.sin(a) * 640); c.lineTo(Math.cos(a) * 1300, Math.sin(a) * 1300); c.stroke(); }
|
|
17
|
+
c.restore();
|
|
18
|
+
const zoom = 1 + 0.06 * Math.min(1, lt / 1.2);
|
|
19
|
+
CAM.with(c, { x: 960, y: 560, z: zoom }, c => {
|
|
20
|
+
const R = 360;
|
|
21
|
+
const blink = (lts >= 0.62 && lts < 0.62 + 0.125) ? 1 : 0; // 慢眨眼:3 帧@24fps(比平时多 1 帧,读作「无语」)
|
|
22
|
+
const twitch = lts > 0.78 ? 'worry' : 'blank'; // 第二声蟋蟀后 0.15s 眉毛一垮(声画同一件事)
|
|
23
|
+
const info = TOON.bean(c, { char: 'author', x: 960, y: 560 + R * 2.1 + 40, s: R, pose: TOON.POSES.stiff, expr: twitch, mouth: 0, blink, look: [0, 0], lw: 12 });
|
|
24
|
+
// 镜片反光:一条白色斜带从左扫到右(0.15–0.45s)
|
|
25
|
+
const g = MO.at(lts, 0.15, 0.3);
|
|
26
|
+
if (g > 0 && g < 1) {
|
|
27
|
+
const hy = info.head[1];
|
|
28
|
+
for (const sx of [-1, 1]) { const gx = 960 + sx * R * 0.37, gy = hy + R * 0.2, gr = R * 0.27;
|
|
29
|
+
c.save(); c.beginPath(); c.arc(gx, gy, gr - 4, 0, Math.PI * 2); c.clip();
|
|
30
|
+
const bx = gx - gr * 1.6 + g * gr * 3.2; c.fillStyle = 'rgba(255,255,255,0.85)';
|
|
31
|
+
c.beginPath(); c.moveTo(bx - 30, gy - gr); c.lineTo(bx + 20, gy - gr); c.lineTo(bx - 40, gy + gr); c.lineTo(bx - 90, gy + gr); c.closePath(); c.fill(); c.restore(); }
|
|
32
|
+
}
|
|
33
|
+
// 汗滴:太阳穴处,0.35s 起滑落 0.9s
|
|
34
|
+
const sw = MO.at(lt, 0.35, 0.9);
|
|
35
|
+
if (sw > 0) TOON.sweat(c, 960 + R * 0.88, info.head[1] - R * 0.1, 34, MO.expoOut(sw) * 0.6 + sw * 0.4);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// S4 大全景(同一个房间拉远到 0.5 倍):小小的花叔坐在大房间里。口播「我问它:这些……」
|
|
2
|
+
// 全景让人显得渺小无助 —— 远景本身就是笑点的铺垫。返修:两段停顿里加「眼睛瞟一下电脑再看镜头」、说「这些」时指电脑。
|
|
3
|
+
// 动:口型、眼神(6.2 瞟电脑 → 6.6 回到镜头)、换姿势回弹(talk → point)、吊灯摆、云飘、屏幕光闪、镜头极慢拉远。
|
|
4
|
+
(() => {
|
|
5
|
+
const S = STORY;
|
|
6
|
+
SCENES['s4_wide'] = {
|
|
7
|
+
draw(c, lt, t) {
|
|
8
|
+
const ts = MO.step(t, 24);
|
|
9
|
+
const zoom = 0.53 - 0.03 * MO.quintOut(Math.min(1, lt / 1.65)); // 0.53 → 0.50
|
|
10
|
+
CAM.with(c, { x: 900, y: 470, z: zoom }, c => {
|
|
11
|
+
const st = S.poseAt(S.KEYS_END, ts), al = S.alive(ts, 165);
|
|
12
|
+
S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts), look: S.lookEnd(ts), squash: st.squash * al.breathe, bob: al.bob, lw: 10 });
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// S5 近景(跳近:同机位直接切到 1.9 倍):「贴哪儿?」——摊手、眉毛内端抬高、头顶「?」弹出,然后定住 0.95s 给笑。
|
|
2
|
+
// 返修:第一版把这句放在全景里,脸只有 110px 高,表情看不清,「?」出来 0.4s 片子就完了。
|
|
3
|
+
(() => {
|
|
4
|
+
const S = STORY;
|
|
5
|
+
SCENES['s5_close'] = {
|
|
6
|
+
draw(c, lt, t) {
|
|
7
|
+
const ts = MO.step(t, 24);
|
|
8
|
+
const zoom = 1.9 + 0.08 * MO.quintOut(Math.min(1, lt / 1.5)); // 跳近后极慢再推 4%
|
|
9
|
+
CAM.with(c, { x: 870, y: 470, z: zoom }, c => {
|
|
10
|
+
const st = S.poseAt(S.KEYS_END, ts), al = S.alive(ts, 165);
|
|
11
|
+
const info = S.world(c, t, { pose: st.pose, expr: st.expr, mouth: TOON.mouth(t), blink: S.blinkAt(ts, [8.3]), look: S.lookEnd(ts), squash: st.squash * al.breathe, bob: al.bob, tilt: ts >= 7.45 ? -0.07 : 0, browUp: ts >= 7.45 ? 0.04 : 0 });
|
|
12
|
+
// 「?」:问完那一刻弹出(过冲 30%),之后 ±3° 轻晃
|
|
13
|
+
const q = MO.at(t, 7.80, 0.26);
|
|
14
|
+
if (q > 0) { c.save(); c.translate(info.head[0] + 165 * 1.4, info.head[1] - 165 * 0.5); c.rotate(0.14 + 0.05 * Math.sin(t * 6)); TOON.mark(c, '?', 0, 0, q, 120); c.restore(); }
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
})();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// y4 本片共用:配色、姿势时间线(pose-to-pose+换姿势回弹)、眨眼、字幕。
|
|
2
|
+
// 用到的库:TOON(豆子花叔 bean、poseAt、blinkAt、alive、口型读 VO_ENV)、TY.text(字幕)、CAM(镜头)、MO(步进、弹簧)。
|
|
3
|
+
(() => {
|
|
4
|
+
const S = window.STORY = {};
|
|
5
|
+
// 背景压低饱和与对比、线更细更灰;角色线最黑最粗 —— 视线永远先落到角色上
|
|
6
|
+
S.C = { wall: '#DCE9EC', wall2: '#CFE0E4', floor: '#E8DCC8', desk: '#E2C29B', deskSide: '#CDA97F', bgLine: '#5B6470', line: '#1B1B1F',
|
|
7
|
+
sky: '#BFE3F5', cloud: '#FFFFFF', laptop: '#C9CDD6', screen: '#20222D', glow: 'rgba(120,200,255,0.35)', void: '#3D405B' };
|
|
8
|
+
S.bgLW = 3;
|
|
9
|
+
// 口播时间(秒,全片时间):与 共享/音频/build_y4.py 的摆放一致
|
|
10
|
+
S.LINES = [[0.12, 2.0, '我第一次让AI帮我写代码,'], [2.13, 4.40, '它一口气,给了我三千行。'], [5.75, 8.75, '我问它:这些……贴哪儿?']]; // 字幕和镜头切点同一帧换
|
|
11
|
+
|
|
12
|
+
// 姿势时间线:keys = [[t, poseName, expr], ...](全片时间)。换姿势 = TOON.poseAt:手臂 0.1s 快切、身体挤压 7% 后弹簧回弹
|
|
13
|
+
S.poseAt = TOON.poseAt;
|
|
14
|
+
// 眨眼:固定时刻表(「随机」间隔 2.4–3.6s 写死),闭眼 2 帧@24fps ≈ 0.083s
|
|
15
|
+
S.blinkAt = (t, extra = []) => TOON.blinkAt(t, [0.9, 3.4, 6.1, ...extra]);
|
|
16
|
+
// 说话时头随音节轻点+呼吸(1.6s 一周期,±1.2%)
|
|
17
|
+
S.alive = TOON.alive;
|
|
18
|
+
|
|
19
|
+
// 字幕(中文平台需要烧录;YouTube 原生 storytime 不烧,靠 CC):白字黑描边,整句切入切出,不做花样
|
|
20
|
+
window.GLOBAL_OVERLAY = (c, t) => {
|
|
21
|
+
for (const [a, b, s] of S.LINES) if (t >= a && t < b) {
|
|
22
|
+
TY.text(c, s, 960, 1022, { size: 50, fam: 'PuHui-Bold', color: '#fff', stroke: '#1B1B1F', strokeW: 10, track: 1 });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
})();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// 由 共享/音频/build_y4.py 生成:口播 RMS 包络,60fps,0..1
|
|
2
|
+
window.VO_ENV = {"fps": 60, "a": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.558, 0.959, 0.963, 0.984, 0.929, 0.834, 0.614, 0.501, 0.149, 0.385, 0.474, 0.541, 0.516, 0.521, 0.635, 0.651, 0.678, 0.671, 0.608, 0.611, 0.672, 0.455, 0.025, 0.058, 0.088, 0.071, 0.044, 0.639, 1.0, 1.0, 1.0, 0.965, 0.694, 0.498, 0.892, 1.0, 1.0, 0.654, 0.515, 0.5, 0.383, 0.159, 0.013, 0.006, 0.507, 0.883, 0.855, 0.89, 0.803, 0.691, 0.708, 0.498, 0.178, 0.018, 0.002, 0.0, 0.0, 0.0, 0.216, 0.78, 1.0, 1.0, 1.0, 0.99, 0.733, 0.615, 0.538, 0.598, 0.804, 0.838, 0.825, 0.785, 0.631, 0.584, 0.489, 0.348, 0.078, 0.106, 0.096, 0.062, 0.591, 0.877, 0.842, 0.844, 0.843, 0.696, 0.441, 0.207, 0.395, 0.516, 0.565, 0.479, 0.345, 0.422, 0.525, 0.795, 1.0, 1.0, 1.0, 0.917, 0.894, 0.88, 0.876, 0.653, 0.187, 0.05, 0.006, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.04, 0.047, 0.054, 0.698, 1.0, 1.0, 1.0, 0.81, 0.704, 0.724, 0.738, 0.694, 0.615, 0.567, 0.569, 0.461, 0.169, 0.072, 0.074, 0.065, 0.076, 0.679, 0.995, 0.908, 0.739, 0.652, 0.524, 0.315, 0.069, 0.089, 0.089, 0.067, 0.277, 0.744, 0.865, 0.818, 0.726, 0.655, 0.414, 0.039, 0.002, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.079, 0.633, 0.799, 0.847, 0.845, 0.785, 0.707, 0.678, 0.692, 0.724, 0.632, 0.626, 0.633, 0.571, 0.692, 0.86, 0.977, 0.923, 1.0, 0.729, 0.681, 0.407, 0.067, 0.061, 0.324, 0.616, 0.7, 0.654, 0.472, 0.321, 0.289, 0.295, 0.207, 0.054, 0.077, 0.081, 0.089, 0.111, 0.443, 0.625, 0.692, 0.771, 0.564, 0.419, 0.35, 0.274, 0.195, 0.065, 0.039, 0.034, 0.909, 1.0, 1.0, 1.0, 0.589, 0.432, 0.415, 0.315, 0.158, 0.011, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.608, 0.957, 0.954, 0.955, 1.0, 0.946, 0.853, 0.768, 0.868, 0.969, 1.0, 1.0, 0.98, 0.714, 0.661, 0.65, 0.456, 0.417, 0.316, 0.051, 0.035, 0.046, 0.037, 0.07, 0.55, 0.946, 0.985, 0.849, 0.779, 0.715, 0.485, 0.151, 0.07, 0.049, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.02, 0.068, 0.248, 0.875, 1.0, 1.0, 1.0, 0.98, 0.759, 0.404, 0.081, 0.095, 0.081, 0.387, 0.699, 0.75, 0.71, 0.75, 0.814, 0.513, 0.105, 0.014, 0.001, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.017, 0.056, 0.059, 0.046, 0.043, 0.371, 0.806, 0.956, 1.0, 0.903, 0.681, 0.602, 0.582, 0.514, 0.437, 0.513, 0.786, 0.765, 0.806, 0.761, 0.711, 0.376, 0.04, 0.006, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// y4 的「世界画布」:S1 中景、S4 大全景、S5 近景是同一个房间、同一套世界坐标,只换镜头。
|
|
2
|
+
// (返修:第一版 S4 另画了一个房间,审片指出空间不连贯;中景镜头偏移露出画框外的黑边。
|
|
3
|
+
// 现在世界铺到 x −1000..2900、y −700..1700,最远的全景 zoom 0.5 也盖得满。)
|
|
4
|
+
// S1 镜头看到的范围 ≈ x 0..1920、y 0..1080;全景时才看得到书架、绿植、吊灯、地板。
|
|
5
|
+
(() => {
|
|
6
|
+
const S = STORY, C = S.C;
|
|
7
|
+
const X0 = -1000, Y0 = -700, WW = 3900, WH = 2400;
|
|
8
|
+
const back = () => PAINT.cached('y4_world_back', WW, WH, g => {
|
|
9
|
+
g.translate(-X0, -Y0);
|
|
10
|
+
g.lineWidth = S.bgLW; g.strokeStyle = C.bgLine; g.lineJoin = 'round';
|
|
11
|
+
g.fillStyle = C.wall; g.fillRect(X0, Y0, WW, WH);
|
|
12
|
+
// 天花板线、地板
|
|
13
|
+
g.fillStyle = C.wall2; g.fillRect(X0, Y0, WW, 330); g.beginPath(); g.moveTo(X0, Y0 + 330); g.lineTo(X0 + WW, Y0 + 330); g.stroke();
|
|
14
|
+
g.fillStyle = C.floor; g.fillRect(X0, 1150, WW, 600); g.fillStyle = '#D2C3AA'; g.fillRect(X0, 1132, WW, 18);
|
|
15
|
+
g.beginPath(); g.moveTo(X0, 1132); g.lineTo(X0 + WW, 1132); g.moveTo(X0, 1150); g.lineTo(X0 + WW, 1150); g.stroke();
|
|
16
|
+
// 猫画框(小猫补光灯的草图)
|
|
17
|
+
g.fillStyle = '#F4EEE2'; g.beginPath(); g.roundRect(1380, 210, 230, 170, 10); g.fill(); g.stroke();
|
|
18
|
+
g.strokeStyle = '#B9A88E'; g.lineWidth = 4; g.beginPath(); g.arc(1495, 300, 46, 0, Math.PI * 2); g.stroke();
|
|
19
|
+
g.beginPath(); g.moveTo(1462, 266); g.lineTo(1470, 238); g.lineTo(1484, 258); g.moveTo(1528, 266); g.lineTo(1520, 238); g.lineTo(1506, 258); g.stroke();
|
|
20
|
+
g.strokeStyle = C.bgLine; g.lineWidth = S.bgLW;
|
|
21
|
+
// 书架(S1 画外左侧)
|
|
22
|
+
const r = U.rng(9); g.fillStyle = '#D9C3A0'; g.beginPath(); g.roundRect(-700, 420, 420, 712, 8); g.fill(); g.stroke();
|
|
23
|
+
for (const sy of [420, 600, 780, 960]) { g.beginPath(); g.moveTo(-700, sy + 160); g.lineTo(-280, sy + 160); g.stroke();
|
|
24
|
+
let x = -675; while (x < -310) { const w = 22 + r() * 24, h = 90 + r() * 50; g.fillStyle = ['#8FB3C9', '#E7A37B', '#B9C98F', '#C6A0C9'][(r() * 4) | 0]; g.fillRect(x, sy + 160 - h, w, h); g.strokeRect(x, sy + 160 - h, w, h); x += w + 6; } }
|
|
25
|
+
// 绿植(画外右侧,地上)
|
|
26
|
+
g.fillStyle = '#E7A37B'; g.beginPath(); g.moveTo(2240, 1140); g.lineTo(2270, 990); g.lineTo(2410, 990); g.lineTo(2440, 1140); g.closePath(); g.fill(); g.stroke();
|
|
27
|
+
g.fillStyle = '#8DBF8B'; for (const [x, y, rx, ry, a] of [[2290, 900, 40, 110, -0.5], [2340, 870, 40, 128, 0], [2392, 905, 40, 106, 0.5]]) { g.beginPath(); g.ellipse(x, y, rx, ry, a, 0, Math.PI * 2); g.fill(); g.stroke(); }
|
|
28
|
+
});
|
|
29
|
+
// 桌子:S1 里只看得到桌面和挡板上沿;全景看得到桌腿
|
|
30
|
+
const desk = (g) => {
|
|
31
|
+
g.lineWidth = S.bgLW; g.strokeStyle = C.bgLine; g.lineJoin = 'round';
|
|
32
|
+
g.fillStyle = C.deskSide; for (const x of [-120, 1960]) { g.fillRect(x, 740, 60, 400); g.strokeRect(x, 740, 60, 400); }
|
|
33
|
+
g.fillRect(-60, 740, 2020, 320); g.strokeRect(-60, 740, 2020, 320);
|
|
34
|
+
g.fillStyle = C.desk; g.beginPath(); g.rect(-160, 690, 2240, 50); g.fill(); g.stroke();
|
|
35
|
+
// 马克杯
|
|
36
|
+
g.fillStyle = '#F26B4F'; g.beginPath(); g.roundRect(470, 600, 70, 92, 10); g.fill(); g.stroke();
|
|
37
|
+
g.beginPath(); g.arc(548, 640, 22, -1.2, 1.2); g.lineWidth = 9; g.stroke(); g.lineWidth = S.bgLW;
|
|
38
|
+
};
|
|
39
|
+
const front = () => PAINT.cached('y4_world_front', WW, WH, g => { g.translate(-X0, -Y0); desk(g); });
|
|
40
|
+
|
|
41
|
+
// ch: 角色参数(TOON.bean 的 o,不含 x/y/s);返回 bean 的 info
|
|
42
|
+
S.world = (c, t, ch) => {
|
|
43
|
+
c.drawImage(back(), X0, Y0);
|
|
44
|
+
// 窗:天空+慢飘的云(18px/s、12px/s)
|
|
45
|
+
c.save(); c.beginPath(); c.roundRect(220, 190, 420, 300, 12); c.clip(); c.fillStyle = C.sky; c.fillRect(220, 190, 420, 300);
|
|
46
|
+
for (const [cx, cy, r, sp] of [[300, 300, 46, 18], [560, 400, 34, 12]]) { const x = 180 + ((cx + t * sp) % 520); c.fillStyle = C.cloud;
|
|
47
|
+
c.beginPath(); c.arc(x, cy, r, 0, Math.PI * 2); c.arc(x + r * 0.9, cy + 6, r * 0.75, 0, Math.PI * 2); c.arc(x - r * 0.9, cy + 8, r * 0.65, 0, Math.PI * 2); c.fill(); }
|
|
48
|
+
c.restore();
|
|
49
|
+
c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine; c.beginPath(); c.roundRect(220, 190, 420, 300, 12); c.stroke();
|
|
50
|
+
c.beginPath(); c.moveTo(430, 190); c.lineTo(430, 490); c.moveTo(220, 340); c.lineTo(640, 340); c.stroke();
|
|
51
|
+
// 吊灯(只有全景看得到):钟摆 1.9s、±4°
|
|
52
|
+
c.save(); c.translate(860, -370); c.rotate(0.07 * Math.sin(2 * Math.PI * t / 1.9)); c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine;
|
|
53
|
+
c.beginPath(); c.moveTo(0, 0); c.lineTo(0, 240); c.stroke();
|
|
54
|
+
c.fillStyle = '#F2C86B'; c.beginPath(); c.moveTo(-90, 330); c.lineTo(-42, 240); c.lineTo(42, 240); c.lineTo(90, 330); c.closePath(); c.fill(); c.stroke(); c.restore();
|
|
55
|
+
// 角色
|
|
56
|
+
const info = TOON.bean(c, { x: 860, y: 760, s: 165, char: 'author', ...ch }); // 示范片讲的是作者自己的经历,显式用作者造型(TOON.bean 默认是中性角色)
|
|
57
|
+
c.drawImage(front(), X0, Y0);
|
|
58
|
+
// 笔记本(背面朝镜头、斜对着他),屏幕光打在桌上,光随代码滚动微闪
|
|
59
|
+
c.save(); c.lineWidth = S.bgLW; c.strokeStyle = C.bgLine; c.lineJoin = 'round';
|
|
60
|
+
const glow = c.createRadialGradient(1240, 690, 10, 1240, 690, 260); glow.addColorStop(0, `rgba(140,210,255,${0.42 + 0.06 * Math.sin(t * 9)})`); glow.addColorStop(1, 'rgba(140,210,255,0)');
|
|
61
|
+
c.fillStyle = glow; c.fillRect(980, 560, 520, 180);
|
|
62
|
+
c.fillStyle = C.laptop; c.beginPath(); c.moveTo(1080, 692); c.lineTo(1130, 470); c.lineTo(1420, 470); c.lineTo(1380, 692); c.closePath(); c.fill(); c.stroke();
|
|
63
|
+
c.fillStyle = '#AEB3BE'; c.beginPath(); c.roundRect(1050, 684, 360, 16, 6); c.fill(); c.stroke();
|
|
64
|
+
const k = 1 + 0.06 * Math.sin(t * 5);
|
|
65
|
+
c.translate(1255, 580); c.scale(k, k); c.fillStyle = '#F2A33A'; c.beginPath();
|
|
66
|
+
for (let i = 0; i < 8; i++) { const a = i / 8 * Math.PI * 2, r = i % 2 ? 14 : 36; c.lineTo(Math.cos(a) * r, Math.sin(a) * r); } c.closePath(); c.fill(); c.stroke();
|
|
67
|
+
c.restore();
|
|
68
|
+
return info;
|
|
69
|
+
};
|
|
70
|
+
// S4/S5 共用的姿势时间线(全片时间,跨两个镜头连续)
|
|
71
|
+
S.KEYS_END = [[5.6, 'talk', 'worry'], [6.81, 'point', 'worry'], [7.45, 'shrug', 'worry']];
|
|
72
|
+
S.lookEnd = t => t >= 7.45 ? [0, 0] : t >= 6.81 ? [1, 0.1] : (t >= 6.2 && t < 6.6) ? [1, 0.35] : [0, 0];
|
|
73
|
+
})();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// 示范 · 动态文字排版(kinetic typography)「AI 写代码的 3 个阶段」 120 BPM,8 秒 = 4 小节 = 16 拍,再静住 0.5s。
|
|
2
|
+
// 语法卡:references/动画语法/y5_kinetic_type.md
|
|
3
|
+
// 预览 index.html?film=demos/y5_kinetic_type 渲染 render.py --film demos/y5_kinetic_type --out y5.mp4
|
|
4
|
+
// 揭开那一帧落在拍上:段起点 = 小节线 − 揭开点 × 转场时长(MO.onBeat)——
|
|
5
|
+
// fillZoom 揭开点 .55、dur .45 → 2.0−.25 = 1.75;bands 揭开点 ≈.6、dur .5 → 4.0−.3 = 3.7;push 到 90% 位移在 p≈.58、dur .35 → 6.0−.2 ≈ 5.80
|
|
6
|
+
window.PUNCH = 0; // 拍点冲击由 kinetic.js 的 K.bump 自己做(每拍 1.2%)
|
|
7
|
+
window.FILM_DURATION = 8.5;
|
|
8
|
+
window.SCENE_LIBS = ['demos/y5_kinetic_type/kinetic.js'];
|
|
9
|
+
window.SCENE_DIR = 'demos/y5_kinetic_type';
|
|
10
|
+
window.ERAS = [
|
|
11
|
+
{ id: 'k1_title', dur: 1.75 },
|
|
12
|
+
{ id: 'k2_complete', dur: 1.95, // 冲进点选在圆内、避开「3」字形(冲进白字会一片白)
|
|
13
|
+
transition: { type: 'fillZoom', dur: 0.45, k: 0.55, cx: 1075, cy: 773, maxZoom: 30, fill: '#FF5A36' } },
|
|
14
|
+
{ id: 'k3_chat', dur: 2.1, transition: { type: 'bands', dur: 0.5, gap: 0.12, colors: ['#FFD23F', '#FFFFFF', '#14213D'] } },
|
|
15
|
+
{ id: 'k4_agent', dur: 2.7, transition: { type: 'push', dur: 0.35 } },
|
|
16
|
+
];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// K1 标题:「AI 写代码」两拍砸进来 →「的 3 个阶段」在第三拍落下 → 第四拍其余字快速退场,只剩「3」,镜头冲进 3 的圆里。
|
|
2
|
+
// 入场慢(0.3–0.4s 弹簧)、退场快(0.18s expoIn)——退场比入场快,观众的注意力已经走了。
|
|
3
|
+
(() => {
|
|
4
|
+
const K = KT, C = K.C, b = K.b;
|
|
5
|
+
const { clamp, lerp } = U;
|
|
6
|
+
// 退场:t0 起 0.18s,缩到 0.6、下落 60px、透明
|
|
7
|
+
const exitK = (t, t0) => MO.expoIn(clamp((t - t0) / 0.18));
|
|
8
|
+
SCENES['k1_title'] = {
|
|
9
|
+
draw(c, lt, t) {
|
|
10
|
+
c.fillStyle = C.yellow; c.fillRect(0, 0, 1920, 1080);
|
|
11
|
+
K.dots(c, t, C.ink, 0.08);
|
|
12
|
+
K.cam(c, t, c => {
|
|
13
|
+
// 第一行:AI | 写代码
|
|
14
|
+
const s1 = 300, wAI = TY.width(c, 'AI', s1), wCode = TY.width(c, '写代码', s1), gap = 50, x0 = 960 - (wAI + gap + wCode) / 2, y1 = 470;
|
|
15
|
+
const ex = [exitK(t, 1.45), exitK(t, 1.48), exitK(t, 1.51), exitK(t, 1.54)];
|
|
16
|
+
// AI:第 0 拍砸进来,带 -8° → 0° 的旋转回正
|
|
17
|
+
if (t >= 0) { const k = MO.springHz(t, 2.2, 10), e = ex[0];
|
|
18
|
+
TY.text(c, 'AI', x0, y1 + 60 * e, { size: s1, align: 'left', color: C.ink, scale: lerp(1.7, 1, k) * (1 - 0.4 * e), rot: -0.14 * (1 - k), alpha: clamp(t / 0.06) * (1 - e), ox: x0 + wAI / 2, oy: y1 - s1 * 0.36 }); }
|
|
19
|
+
// 写代码:第 1 拍从槽里升起(遮罩),0.35s expoOut
|
|
20
|
+
{ const e = ex[1]; c.save(); c.globalAlpha = 1 - e; c.translate(0, 60 * e);
|
|
21
|
+
TY.rise(c, '写代码', x0 + wAI + gap, y1, MO.at(t, b(1), 0.35), { size: s1, color: C.ink }); c.restore(); }
|
|
22
|
+
// 第二行:的 | (3) | 个阶段 圆心 (960, 773) —— 转场从这里冲进去
|
|
23
|
+
const cy = 773, r = 165;
|
|
24
|
+
const pc = MO.at(t, b(2), 0.42);
|
|
25
|
+
if (pc > 0) {
|
|
26
|
+
const k = MO.backOut(pc, 3.0) * (t > b(3) ? 1 + 0.12 * MO.expoOut(clamp((t - b(3)) / 0.2)) : 1);
|
|
27
|
+
c.save(); c.translate(960, cy); c.scale(k, k); c.rotate(0.25 * (1 - MO.expoOut(pc)));
|
|
28
|
+
c.fillStyle = C.coral; c.beginPath(); c.arc(0, 0, r, 0, Math.PI * 2); c.fill();
|
|
29
|
+
TY.text(c, '3', 0, 92, { size: 270, color: C.white });
|
|
30
|
+
c.restore();
|
|
31
|
+
}
|
|
32
|
+
{ const e = ex[2]; TY.text(c, '的', 960 - r - 40, cy + 40 + 60 * e, { size: 120, fam: 'PuHui-Heavy', align: 'right', color: C.ink, alpha: MO.at(t, b(1.75), 0.08) * (1 - e) }); }
|
|
33
|
+
{ const e = ex[3]; c.save(); c.globalAlpha = 1 - e; c.translate(0, 60 * e);
|
|
34
|
+
TY.rise(c, '个阶段', 960 + r + 40, cy + 52, MO.at(t, b(2.25), 0.35), { size: 150, fam: 'PuHui-Heavy', color: C.ink }); c.restore(); }
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// K2 阶段一「补全」(一小节 = 第 4–7 拍,2.0–4.0s)
|
|
2
|
+
// 第 4 拍主词砸进+巨型「1」→ 4.5 拍代码卡滑入、开始打字 → 第 5 拍辅句「它补完」升起、5.25 拍「下一行」→
|
|
3
|
+
// 第 6 拍灰色补全闪现+Tab 键弹出 → 6.5 拍按下 Tab、灰字变实、「下一行」放大 1.35 倍+荧光笔(字和动作说同一件事)。
|
|
4
|
+
// 返修:辅句原在第 6 拍出、7 个字,到被色带盖住只剩 0.7s;现在提前一拍、压到 6 个字,可读 1.2s。
|
|
5
|
+
(() => {
|
|
6
|
+
const K = KT, C = K.C, b = K.b;
|
|
7
|
+
const { clamp } = U;
|
|
8
|
+
const MONO = 'Menlo'; // 代码用等宽(返修:第一版用了比例字体,不像代码)
|
|
9
|
+
SCENES['k2_complete'] = {
|
|
10
|
+
draw(c, lt, t) {
|
|
11
|
+
c.fillStyle = C.coral; c.fillRect(0, 0, 1920, 1080);
|
|
12
|
+
K.dots(c, t, C.white, 0.1);
|
|
13
|
+
K.cam(c, t, c => {
|
|
14
|
+
K.bigNum(c, t, '1', b(4), 1880, 1150, 'rgba(255,255,255,0.13)');
|
|
15
|
+
TY.rise(c, '阶段 01', 140, 250, MO.at(t, b(4), 0.3), { size: K.SZ.label, fam: 'PuHui-Bold', color: C.white, track: 6 });
|
|
16
|
+
TY.underline(c, 140, 272, 170, MO.at(t, b(4.25), 0.3), C.white, 6);
|
|
17
|
+
K.slam(c, '补全', 130, 600, t, b(4), { color: C.white });
|
|
18
|
+
const sub = '它补完', key = '下一行', sw = TY.width(c, sub, K.SZ.sub, 'PuHui-Heavy'), kw = TY.width(c, key, K.SZ.sub, 'PuHui-Heavy');
|
|
19
|
+
const ky = 775, kx = 150 + sw;
|
|
20
|
+
TY.rise(c, sub, 140, ky, MO.at(t, b(5), 0.35), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: C.white });
|
|
21
|
+
const ksc = t >= b(6.5) ? 1 + 0.35 * MO.springHz(t - b(6.5), 2.5, 9) : 1;
|
|
22
|
+
K.keyMarker(c, kx, ky, kw, K.SZ.sub, t, b(6.5), ksc, C.yellow);
|
|
23
|
+
K.key(c, key, kx, ky, t, b(5.25), b(6.5), { size: K.SZ.sub, fam: 'PuHui-Heavy', color: t >= b(6.5) ? C.ink : C.white });
|
|
24
|
+
// 代码卡:4.5 拍从右侧滑入(expoOut 0.4s,起点 +700px,带 3.4° 回正)
|
|
25
|
+
const pin = MO.expoOut(MO.at(t, b(4.5), 0.4));
|
|
26
|
+
if (pin > 0) {
|
|
27
|
+
const cx = 1010 + 700 * (1 - pin), cy = 330;
|
|
28
|
+
c.save(); c.translate(cx, cy); c.rotate(0.06 * (1 - pin));
|
|
29
|
+
c.fillStyle = 'rgba(0,0,0,0.18)'; c.beginPath(); c.roundRect(14, 18, 800, 330, 26); c.fill();
|
|
30
|
+
c.fillStyle = C.code; c.beginPath(); c.roundRect(0, 0, 800, 330, 26); c.fill();
|
|
31
|
+
['#FF5F57', '#FEBC2E', '#28C840'].forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(40 + i * 30, 40, 9, 0, Math.PI * 2); c.fill(); });
|
|
32
|
+
const T = (s, x, y, col, a = 1) => TY.text(c, s, x, y, { size: 40, fam: MONO, color: col, align: 'left', alpha: a });
|
|
33
|
+
T('1', 40, 130, '#555A72'); T('function hello() {', 96, 130, '#82AAFF');
|
|
34
|
+
T('2', 40, 210, '#555A72');
|
|
35
|
+
const typed = TY.typed(' const name =', MO.at(t, b(4.75), 0.5)); T(typed, 96, 210, '#E9ECF5');
|
|
36
|
+
const tw = TY.width(c, typed, 40, MONO), acc = t >= b(6.5);
|
|
37
|
+
// 补全:第 6 拍整段灰字 0 帧闪现(补全本来就是瞬间出现),Tab 按下后变实色
|
|
38
|
+
if (t >= b(6)) T(" 'huashu';", 96 + tw, 210, acc ? '#C3E88D' : 'rgba(233,236,245,0.35)');
|
|
39
|
+
T('3', 40, 290, '#555A72'); if (acc) T('}', 96, 290, '#82AAFF', MO.at(t, b(6.6), 0.1));
|
|
40
|
+
if (Math.floor(t * 4) % 2 === 0) { const cxr = 96 + tw + (acc ? TY.width(c, " 'huashu';", 40, MONO) : 0); c.fillStyle = '#FFD23F'; c.fillRect(cxr + 4, 172, 5, 50); }
|
|
41
|
+
c.restore();
|
|
42
|
+
}
|
|
43
|
+
// Tab 键帽:第 6 拍弹出,6.5 拍按下(压 14%,e^-18t 回弹)
|
|
44
|
+
const kp = MO.at(t, b(6), 0.3); if (kp > 0) {
|
|
45
|
+
const press = t >= b(6.5) ? 1 - 0.14 * Math.exp(-(t - b(6.5)) * 18) : 1, s = MO.backOut(kp, 2.6) * press;
|
|
46
|
+
c.save(); c.translate(1410, 790); c.scale(s, s);
|
|
47
|
+
c.fillStyle = '#C24128'; c.beginPath(); c.roundRect(-130, -62, 260, 136, 22); c.fill();
|
|
48
|
+
c.fillStyle = C.white; c.beginPath(); c.roundRect(-130, -76 + (1 - press) * 70, 260, 128, 22); c.fill();
|
|
49
|
+
TY.text(c, 'TAB', 0, 12 + (1 - press) * 70, { size: 64, fam: 'PuHui-Black', color: C.coral });
|
|
50
|
+
c.restore();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
})();
|