@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,194 @@
|
|
|
1
|
+
// 片段 · 故事型角色(y4,storytime):一个角色(AI 生成的帧库)在小房间里对镜头讲,姿势快切(0.1s)后定住,笑点靠反应特写和插入镜头。
|
|
2
|
+
// 镜头不慢推、不手持漂、角色不呼吸:定住就是定住,变化靠快切、跳近(反应特写砸进来)和插入镜头。
|
|
3
|
+
// data.character(必填):帧库——render.py 里写一个目录(文件名去扩展名 = 姿势名),或 {frames: {rest, talk, point, shrug, type, stiff, react, back, <姿势>_open: 路径}}。
|
|
4
|
+
// 代码画的小人('neutral' / 'bun' / 'author' / 造型对象)已停用:不给帧库就启动失败。
|
|
5
|
+
// 至少要有 rest 或 talk;缺的姿势退到 talk → rest;<姿势>_open 是张嘴帧(说话时一拍二换);react 是反应特写,back 是插入镜头前景的后脑勺。
|
|
6
|
+
// 帧图要透明底、角色脚底在图片底边中间,画出来高 ≈ 3.15 个头半径。没有 back 帧时插入镜头不画前景后脑勺。
|
|
7
|
+
// cues:point | title(角色说一句:at 这一帧气泡弹出、开始张嘴;嘴按 text 的字数说 ≈6.5 字/秒;data.pose 指定姿势 talk/point/shrug/type)
|
|
8
|
+
// 气泡里写什么:有 sub 就写 sub(≤12 字的短句最好),没有才写 text——口播原文交给管线的字幕,气泡别和字幕重复一遍
|
|
9
|
+
// highlight(「?」「!」弹出+摊手担心:data.mark 默认「?」)
|
|
10
|
+
// react(反应镜头:at 这一帧跳到大特写,贴 react 帧(没有就退到 talk / rest)+汗滴,一言不发;dur 默认到下一个 cue)
|
|
11
|
+
// insert(插入镜头:at 这一帧切到桌上那台电脑的屏幕特写——sub = 角色打的那一行(打字、发送),之后 AI 的回复像代码墙一样越滚越快,
|
|
12
|
+
// text = 砸出来的大字(如「3000行」),在 data.hit(绝对秒,默认 at+1.3,没有 sub 时 at+0.4)砸出、屏幕一震;
|
|
13
|
+
// data.count + data.unit 让右上角计数器在砸出那一刻停在这个数;dur 默认到下一个 cue)
|
|
14
|
+
// 房间里有桌上的电脑(插入镜头切进去的就是它的屏幕)、马克杯、墙上的书架和绿植,右边不再是空墙。
|
|
15
|
+
// 没有口播包络时,口型由字数和时间合成(一拍二,0/1/2 三张嘴);有包络就把 window.VO_ENV 交给管线(TOON.mouth 自动读)。
|
|
16
|
+
// safe:气泡、插入镜头的大字排在 top..H−bottom 之间。alpha:不画房间,角色和气泡照画(气泡本来就是白底黑字);插入镜头只画显示器和后脑勺,四周透明。
|
|
17
|
+
// spec.theme(全片色板 ctx.pal):墙 bg、地面和桌子是 bg 往 ink / sub 调的明暗,书、杯子、绿植 accent / accent2,气泡 surface 底 ink 字,
|
|
18
|
+
// 屏幕是深色(ink 或 bg 压暗),代码色块、计数器、砸出的大字只用 accent / accent2 和它们的明暗。
|
|
19
|
+
CLIPS.y4_storytime = (() => {
|
|
20
|
+
const { clamp, lerp, rng } = U;
|
|
21
|
+
// 代码墙色块(屏幕上的语法高亮):不放紫、紫蓝
|
|
22
|
+
const COLS = ['#E06C75', '#5FB3A1', '#C3E88D', '#F78C6C', '#89DDFF', '#FFCB6B', '#8A9199'];
|
|
23
|
+
const C0 = { wall: '#DCE9EC', floor: '#E8DCC8', desk: '#E2C29B', deskSide: '#CDA97F', line: '#5B6470', sky: '#BFE3F5', screen: '#20222D', cloud: '#fff', shelf: '#D9C3A0',
|
|
24
|
+
books: ['#8FB3C9', '#E7A37B', '#B9C98F', '#C9B28F'], pot: '#E7A37B', leaf: '#8DBF8B', stand: '#9AA0AC', bezel: '#2B2D36', mug: '#F26B4F', codes: COLS,
|
|
25
|
+
insertBg: '#15161C', gutter: '#4A4F66', bubble: '#2F3342', sent: '#3B82F6', sentInk: '#fff', bar: '#2A2D3A', dots: ['#FF5F57', '#FEBC2E', '#28C840'], appText: '#9AA3B8', hot: '#FFCB6B',
|
|
26
|
+
slamDim: '10,10,16', fast: '32,34,45', sayBg: '#fff', sayInk: '#1B1B1F', type: '#fff' };
|
|
27
|
+
// 全片色板 → 房间和屏幕的一整套色(只从 6 个键派生)
|
|
28
|
+
const fromPal = p => { const dk = PAL.dark(p), light = dk ? p.ink : p.surface, screen = dk ? PAL.mix(p.bg, '#000000', 0.35) : PAL.mix(p.ink, p.bg, 0.08);
|
|
29
|
+
return { wall: p.bg, floor: PAL.mix(p.bg, p.ink, 0.07), desk: PAL.mix(p.bg, p.sub, 0.25), deskSide: PAL.mix(p.bg, p.sub, 0.4), line: dk ? PAL.mix(p.sub, p.bg, 0.2) : p.sub,
|
|
30
|
+
sky: PAL.mix(p.bg, p.accent2, 0.3), screen, cloud: PAL.mix(light, PAL.mix(p.bg, p.accent2, 0.3), 0.3), shelf: PAL.mix(p.bg, p.sub, 0.3),
|
|
31
|
+
books: [p.accent, p.accent2, PAL.mix(p.accent, p.bg, 0.45), PAL.mix(p.accent2, p.bg, 0.45)], pot: p.accent, leaf: p.accent2, stand: PAL.mix(p.sub, p.bg, 0.3), bezel: PAL.mix(screen, light, 0.12), mug: p.accent,
|
|
32
|
+
codes: [p.accent, PAL.mix(p.accent2, light, 0.4), PAL.mix(light, p.accent, 0.3), PAL.mix(p.accent2, light, 0.65), PAL.mix(light, screen, 0.45), PAL.mix(p.accent, light, 0.5), PAL.mix(light, screen, 0.65)], // 深屏上看得见:accent2 往亮色提
|
|
33
|
+
insertBg: dk ? PAL.mix(p.bg, '#000000', 0.55) : PAL.mix(p.ink, '#000000', 0.25), gutter: PAL.mix(screen, light, 0.25), bubble: PAL.mix(screen, light, 0.15), sent: p.accent2,
|
|
34
|
+
sentInk: PAL.on(p.accent2, [p.surface, p.ink, p.bg]), bar: PAL.mix(screen, light, 0.08), dots: [p.accent, PAL.mix(light, screen, 0.5), p.accent2], appText: PAL.mix(light, screen, 0.4), hot: p.accent,
|
|
35
|
+
slamDim: PAL.rgb(screen).join(','), fast: PAL.rgb(screen).join(','), sayBg: p.surface, sayInk: p.ink, type: light }; };
|
|
36
|
+
const LINES = (() => { const r = rng(42), out = []; let ind = 0; // 色块存色号(第几个),画的时候按 C.codes 取色
|
|
37
|
+
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);
|
|
38
|
+
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, (r() * COLS.length) | 0]); x += w + 14; } out.push(toks); } return out; })();
|
|
39
|
+
const NEED_FRAMES = 'y4 需要角色帧库 data.character.frames(AI 生帧,见 references/10-角色.md);代码画的小人已停用。没有生图能力就别用 y4:用 y2/y5 或 scenes 里用手、物件、背影讲故事。';
|
|
40
|
+
let keys, says, L, shots, FR, C = C0;
|
|
41
|
+
return {
|
|
42
|
+
fonts: ['PuHui-Bold', 'PuHui-Black'],
|
|
43
|
+
safe: true,
|
|
44
|
+
init(ctx) {
|
|
45
|
+
const { W, H, u } = ctx, ch = ctx.data.character;
|
|
46
|
+
C = ctx.pal ? fromPal(ctx.pal) : C0;
|
|
47
|
+
// 角色只认帧库(对象带 frames;帧图已由 clip.js 加载,缺图在启动时就失败)。没有帧库 / 写预设名 → 启动失败
|
|
48
|
+
if (!ch || typeof ch !== 'object' || !ch.frames || typeof ch.frames !== 'object') throw new Error(NEED_FRAMES + (ch != null ? `(收到的 data.character = ${JSON.stringify(ch)})` : '(spec 里没写 data.character)'));
|
|
49
|
+
FR = Object.fromEntries(Object.entries(ch.frames).map(([k, v]) => [k, ctx.IMG[v]]));
|
|
50
|
+
if (!FR.rest && !FR.talk) throw new Error('y4 data.character.frames 至少要有 rest 或 talk 一张(现有:' + Object.keys(FR).join(' ') + ')');
|
|
51
|
+
L = { cx: ctx.portrait ? W * 0.42 : W * 0.3, fy: ctx.portrait ? H * 0.8 : H * 1.02, R: (ctx.portrait ? 230 : 215) * u };
|
|
52
|
+
L.dy = L.fy - L.R * 0.55; // 桌面高度
|
|
53
|
+
L.mon = ctx.portrait ? { x: W * 0.66, w: 330 * u, h: 240 * u } : { x: W * 0.6, w: 520 * u, h: 340 * u }; // 桌上的电脑(屏幕左上角 x、宽高)
|
|
54
|
+
says = ctx.of('point', 'title').map(q => ({ q, end: q.at + Math.max(0.6, [...(q.text || '')].length / 6.5) }));
|
|
55
|
+
keys = [[0, 'rest', 'happy']]; let n = 0;
|
|
56
|
+
for (const q of ctx.cues) {
|
|
57
|
+
if (q.kind === 'point' || q.kind === 'title') keys.push([q.at, (q.data && q.data.pose) || (n++ % 2 ? 'point' : 'talk'), 'talk']);
|
|
58
|
+
if (q.kind === 'highlight') keys.push([q.at, 'shrug', 'worry']);
|
|
59
|
+
if (q.kind === 'react' || q.kind === 'insert') keys.push([q.at, 'stiff', 'blank']);
|
|
60
|
+
}
|
|
61
|
+
// 特写镜头(react / insert)占的时间段
|
|
62
|
+
shots = ctx.of('react', 'insert').map(q => { const nx = ctx.cues.find(z => z.at > q.at + 1e-6); const end = q.dur ? q.at + q.dur : nx ? nx.at : ctx.dur + 1;
|
|
63
|
+
const hit = q.kind === 'insert' ? (q.data && q.data.hit != null ? +q.data.hit : q.at + (q.sub ? 1.3 : 0.4)) : 0; return { q, end, hit }; });
|
|
64
|
+
},
|
|
65
|
+
draw(c, t, ctx) {
|
|
66
|
+
const { W, H, u } = ctx, ts = MO.step(t, 24); // 角色 24fps,镜头 60fps
|
|
67
|
+
const shot = shots.find(s => t >= s.q.at - 1e-6 && t < s.end);
|
|
68
|
+
if (shot && shot.q.kind === 'insert') return insert(c, t, ctx, shot);
|
|
69
|
+
const inReact = !!shot;
|
|
70
|
+
// 镜头:中景定住;反应=同机位跳到 1.9 倍大特写(砸镜:前 0.3s 从 2.2 弹回,之后定住)
|
|
71
|
+
let z = 1, cy = L.fy - L.R * 1.6;
|
|
72
|
+
if (inReact) { const l = t - shot.q.at; z = 1.9 * (1 + 0.15 * Math.max(0, 1 - MO.springHz(l, 3, 11))); cy = L.fy - L.R * 2.2; }
|
|
73
|
+
if (!ctx.alpha) { c.fillStyle = C.wall; c.fillRect(0, 0, W, H); }
|
|
74
|
+
CAM.with(c, { x: inReact ? L.cx : W / 2, y: inReact ? cy : H / 2, z }, g => {
|
|
75
|
+
if (!ctx.alpha) room(g, t, ctx);
|
|
76
|
+
const st = TOON.poseAt(keys, ts), say = says.find(s => t >= s.q.at - 1e-6 && t < s.end);
|
|
77
|
+
const mouth = window.VO_ENV ? TOON.mouth(t) : say ? [1, 2, 1, 0, 2, 1][Math.floor(MO.step(t - say.q.at + 1e-6, 12) * 12) % 6] : 0;
|
|
78
|
+
const info = frameChar(g, ts, st, inReact, mouth);
|
|
79
|
+
if (!ctx.alpha) desk(g, t, ctx);
|
|
80
|
+
if (inReact) { const sw = clamp((t - shot.q.at - 0.3) / 0.9); if (sw > 0) TOON.sweat(g, L.cx + L.R * 0.88, info.head[1] - L.R * 0.1, 30 * u, MO.expoOut(sw) * 0.6 + sw * 0.4); }
|
|
81
|
+
for (const h of ctx.of('highlight')) { const l = ctx.lt(t, h.at); if (l <= 0) continue;
|
|
82
|
+
g.save(); g.translate(info.head[0] + L.R * 1.35, info.head[1] - L.R * 0.6); g.rotate(0.14 + MO.settle(l, 0.12, 3, 6)); TOON.mark(g, (h.data && h.data.mark) || '?', 0, 0, clamp(l / 0.26), 130 * u, C === C0 ? undefined : ctx.pal.accent); g.restore(); }
|
|
83
|
+
});
|
|
84
|
+
// 对话气泡(屏幕空间,不跟镜头):at 这一帧弹出,说完 0.6s 后收起;特写切回来后,特写之前的那句不再出现
|
|
85
|
+
if (!inReact) for (const s of says) {
|
|
86
|
+
const l = ctx.lt(t, s.q.at), out = clamp((t - s.end - 0.6) / 0.2); if (l <= 0 || out >= 1) continue;
|
|
87
|
+
const nx = says[says.indexOf(s) + 1]; if (nx && t >= nx.q.at - 1e-6) continue;
|
|
88
|
+
if (shots.some(sh => sh.q.at > s.q.at && sh.q.at <= t)) continue;
|
|
89
|
+
const txt = s.q.sub || s.q.text || '';
|
|
90
|
+
const bw0 = ctx.portrait ? W * 0.84 : W * 0.42, f = TY.fit(c, txt, bw0 - 80 * u, (ctx.portrait ? 64 : 62) * u, 'PuHui-Bold', { maxLines: 2, min: 0.72, balance: true });
|
|
91
|
+
c.font = `${f.size}px "PuHui-Bold"`; const bw = Math.min(bw0, Math.max(...f.lines.map(x => c.measureText(x).width)) + 80 * u);
|
|
92
|
+
const bh = f.lines.length * f.size * 1.3 + 56 * u, bx = ctx.portrait ? (W - bw) / 2 : W * 0.5, by = ctx.safe.top + (ctx.portrait ? H * 0.06 : H * 0.09);
|
|
93
|
+
const sc = MO.backOut(clamp(l / 0.25), 2.2) * (1 - out);
|
|
94
|
+
c.save(); c.translate(bx + 60 * u, by + bh); c.scale(sc, sc); c.translate(-(bx + 60 * u), -(by + bh));
|
|
95
|
+
c.fillStyle = C.sayBg; c.strokeStyle = C.sayInk; c.lineWidth = 6 * u; c.lineJoin = 'round';
|
|
96
|
+
c.beginPath(); c.roundRect(bx, by, bw, bh, 40 * u); c.moveTo(bx + 70 * u, by + bh); c.lineTo(bx + (ctx.portrait ? 40 : 10) * u, by + bh + 60 * u); c.lineTo(bx + 130 * u, by + bh); c.fill(); c.stroke();
|
|
97
|
+
c.fillStyle = C.sayBg; c.fillRect(bx + 76 * u, by + bh - 8 * u, 50 * u, 12 * u);
|
|
98
|
+
c.fillStyle = C.sayInk; c.textBaseline = 'alphabetic';
|
|
99
|
+
f.lines.forEach((ln, i) => c.fillText(ln, bx + 40 * u, by + 26 * u + (i + 1) * f.size * 1.2));
|
|
100
|
+
c.restore();
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
// 帧库角色:按当前姿势名取帧(缺的退到 talk → rest),说话时一拍二换 <姿势>_open;换姿势那一下沿用 poseAt 的挤压回弹
|
|
105
|
+
function frameChar(g, ts, st, inReact, mouth) {
|
|
106
|
+
let name = 'rest'; for (const k of keys) if (ts >= k[0]) name = k[1];
|
|
107
|
+
if (inReact) name = 'react';
|
|
108
|
+
const pick = n => FR[n] || FR.talk || FR.rest;
|
|
109
|
+
const im = (!inReact && mouth && FR[name + '_open']) || pick(name), [iw, ih] = MD.size(im), h = L.R * 3.15, w = iw * h / ih;
|
|
110
|
+
g.save(); g.translate(L.cx, L.fy); g.scale(1 / Math.sqrt(st.squash), st.squash); g.drawImage(im, -w / 2, -h, w, h); g.restore();
|
|
111
|
+
return { head: [L.cx, L.fy - h * 0.78], hl: [L.cx - w * 0.4, L.fy - h * 0.45], hr: [L.cx + w * 0.4, L.fy - h * 0.45] };
|
|
112
|
+
}
|
|
113
|
+
// 房间:墙、地、窗(慢飘的云)、书架、画框。只画一次的东西不缓存——片段短,画得起
|
|
114
|
+
function room(g, t, ctx) {
|
|
115
|
+
const { W, H, u } = ctx, X0 = -W, Y0 = -H, fy = L.fy;
|
|
116
|
+
g.fillStyle = C.wall; g.fillRect(X0, Y0, W * 3, H * 3);
|
|
117
|
+
g.fillStyle = C.floor; g.fillRect(X0, fy + 10 * u, W * 3, H * 2);
|
|
118
|
+
g.lineWidth = 3 * u; g.strokeStyle = C.line; g.lineJoin = 'round'; g.beginPath(); g.moveTo(X0, fy + 10 * u); g.lineTo(X0 + W * 3, fy + 10 * u); g.stroke();
|
|
119
|
+
// 窗:天空+慢飘的云
|
|
120
|
+
const wx = ctx.portrait ? W * 0.08 : W * 0.05, wy = ctx.portrait ? H * 0.2 : H * 0.12, ww = ctx.portrait ? W * 0.3 : W * 0.17, wh = ww * 0.7;
|
|
121
|
+
g.save(); g.beginPath(); g.roundRect(wx, wy, ww, wh, 12 * u); g.clip(); g.fillStyle = C.sky; g.fillRect(wx, wy, ww, wh);
|
|
122
|
+
for (const [k, r, sp] of [[0.2, 46, 18], [0.7, 34, 12]]) { const x = wx - 60 * u + ((k * ww + t * sp * u) % (ww + 120 * u)), y = wy + wh * (0.35 + k * 0.3); g.fillStyle = C.cloud; g.beginPath(); g.arc(x, y, r * u, 0, 7); g.arc(x + r * 0.9 * u, y + 6 * u, r * 0.75 * u, 0, 7); g.arc(x - r * 0.9 * u, y + 8 * u, r * 0.65 * u, 0, 7); g.fill(); }
|
|
123
|
+
g.restore(); g.strokeStyle = C.line; g.beginPath(); g.roundRect(wx, wy, ww, wh, 12 * u); g.moveTo(wx + ww / 2, wy); g.lineTo(wx + ww / 2, wy + wh); g.moveTo(wx, wy + wh / 2); g.lineTo(wx + ww, wy + wh / 2); g.stroke();
|
|
124
|
+
// 墙上:横屏右边一块书架;竖屏书架在窗下方右侧
|
|
125
|
+
const r = rng(9), sx = ctx.portrait ? W * 0.62 : W * 0.68, sy = ctx.portrait ? H * 0.3 : H * 0.36, sw = ctx.portrait ? W * 0.3 : W * 0.22;
|
|
126
|
+
g.fillStyle = C.shelf; g.fillRect(sx, sy, sw, 16 * u); g.strokeRect(sx, sy, sw, 16 * u);
|
|
127
|
+
let x = sx + 16 * u; while (x < sx + sw * 0.7) { const w = (22 + r() * 22) * u, h = (70 + r() * 40) * u; g.fillStyle = C.books[(r() * 4) | 0]; g.fillRect(x, sy - h, w, h); g.strokeRect(x, sy - h, w, h); x += w + 5 * u; }
|
|
128
|
+
g.fillStyle = C.pot; const px = sx + sw * 0.83; g.beginPath(); g.moveTo(px - 30 * u, sy); g.lineTo(px - 38 * u, sy - 56 * u); g.lineTo(px + 38 * u, sy - 56 * u); g.lineTo(px + 30 * u, sy); g.closePath(); g.fill(); g.stroke();
|
|
129
|
+
g.fillStyle = C.leaf; for (const [dx, a] of [[-18, -0.5], [0, 0], [18, 0.5]]) { g.beginPath(); g.ellipse(px + dx * u, sy - 100 * u, 16 * u, 46 * u, a, 0, 7); g.fill(); g.stroke(); }
|
|
130
|
+
}
|
|
131
|
+
// 桌子挡在腰前(角色线最黑最粗,背景线细而灰);桌上的电脑、马克杯
|
|
132
|
+
function desk(g, t, ctx) {
|
|
133
|
+
const { W, H, u } = ctx, X0 = -W, dy = L.dy; g.lineWidth = 3 * u; g.strokeStyle = C.line;
|
|
134
|
+
// 电脑:屏幕微微发光,上面是滚动的代码色块(插入镜头切进去就是它)
|
|
135
|
+
const M = L.mon, mx = M.x, my = dy - M.h - 60 * u;
|
|
136
|
+
g.fillStyle = C.stand; g.fillRect(mx + M.w / 2 - 18 * u, dy - 62 * u, 36 * u, 62 * u); g.strokeRect(mx + M.w / 2 - 18 * u, dy - 62 * u, 36 * u, 62 * u);
|
|
137
|
+
g.beginPath(); g.ellipse(mx + M.w / 2, dy, 90 * u, 10 * u, 0, 0, 7); g.fill(); g.stroke();
|
|
138
|
+
g.fillStyle = C.bezel; g.beginPath(); g.roundRect(mx, my, M.w, M.h, 16 * u); g.fill(); g.stroke();
|
|
139
|
+
g.save(); g.beginPath(); g.roundRect(mx + 14 * u, my + 14 * u, M.w - 28 * u, M.h - 28 * u, 6 * u); g.clip(); g.fillStyle = C.screen; g.fillRect(mx, my, M.w, M.h);
|
|
140
|
+
const lh = 22 * u, off = (t * 30 * u) % lh; for (let i = 0; i < 20; i++) { const y = my + 30 * u + i * lh - off; for (const [x, w, col] of LINES[i % LINES.length]) { g.fillStyle = C.codes[col % C.codes.length]; g.globalAlpha = 0.75; g.fillRect(mx + 26 * u + x * 0.3 * u, y, w * 0.3 * u, 8 * u); } }
|
|
141
|
+
g.restore(); g.globalAlpha = 1;
|
|
142
|
+
g.fillStyle = C.desk; g.fillRect(X0, dy, W * 3, 40 * u); g.strokeRect(X0, dy, W * 3, 40 * u);
|
|
143
|
+
g.fillStyle = C.deskSide; g.fillRect(X0, dy + 40 * u, W * 3, H); g.strokeRect(X0, dy + 40 * u, W * 3, H);
|
|
144
|
+
// 马克杯
|
|
145
|
+
const cx = ctx.portrait ? W * 0.1 : L.cx - L.R * 1.9; g.fillStyle = C.mug; g.beginPath(); g.roundRect(cx, dy - 92 * u, 70 * u, 92 * u, 10 * u); g.fill(); g.stroke();
|
|
146
|
+
g.beginPath(); g.arc(cx + 78 * u, dy - 52 * u, 22 * u, -1.2, 1.2); g.lineWidth = 9 * u; g.stroke(); g.lineWidth = 3 * u;
|
|
147
|
+
}
|
|
148
|
+
// 插入镜头:电脑屏幕特写(过肩),打字→发送→代码墙加速滚→大字砸出+屏幕一震(和示范片 s2_screen 同一套)
|
|
149
|
+
function insert(c, t, ctx, shot) {
|
|
150
|
+
const { W, H, u, safe } = ctx, q = shot.q, d = q.data || {}, t0 = q.at, msg = q.sub || '', T_SEND = t0 + (msg ? 0.1 + Math.min(0.9, [...msg].length / 14) + 0.1 : 0.05), T_HIT = Math.max(shot.hit, T_SEND + 0.2);
|
|
151
|
+
const lt = t - t0, slam = t - T_HIT;
|
|
152
|
+
const scrollAt = tt => { const k = Math.max(0, tt - T_SEND - 0.15), span = Math.max(0.3, T_HIT - T_SEND - 0.15); return 46 * u * (k * 6 + Math.pow(k / span * 1.8, 3) * 6); };
|
|
153
|
+
const shake = slam > 0 && slam < 0.3 ? MO.settle(slam, 14 * u, 9, 14) : 0;
|
|
154
|
+
const zoom = 1.06 - 0.06 * MO.expoOut(clamp(lt / 0.25)) + (slam > 0 ? 0.04 * MO.expoOut(clamp(slam / 0.12)) : 0); // 切进来:1.06 落到 1(像推过去的一下);大字砸出时再推一档,之后定住
|
|
155
|
+
if (!ctx.alpha) { c.fillStyle = C.insertBg; c.fillRect(0, 0, W, H); } // 透明模式:只留显示器和后脑勺,口播画面从四周透出来
|
|
156
|
+
const bx = W * 0.06, by = safe.top + H * 0.05, bw = W * 0.88, bh = H - safe.bottom - by - H * (ctx.portrait ? 0.2 : 0.06);
|
|
157
|
+
CAM.with(c, { x: W / 2 + shake, y: H / 2 - shake * 0.5, z: zoom }, c => {
|
|
158
|
+
c.fillStyle = C.bezel; c.beginPath(); c.roundRect(bx, by, bw, bh, 34 * u); c.fill();
|
|
159
|
+
const sx = bx + 40 * u, sy = by + 40 * u, sw = bw - 80 * u, sh = bh - 80 * u;
|
|
160
|
+
c.save(); c.beginPath(); c.roundRect(sx, sy, sw, sh, 14 * u); c.clip(); c.fillStyle = C.screen; c.fillRect(sx, sy, sw, sh);
|
|
161
|
+
const scr = scrollAt(t), top = sy + 64 * u;
|
|
162
|
+
// 我那一行:右侧气泡,打字(光标闪),发送后变蓝
|
|
163
|
+
if (msg) { const bubY = top + 110 * u - Math.min(scr, 600 * u), p = clamp((t - t0 - 0.1) / Math.max(0.2, T_SEND - t0 - 0.2));
|
|
164
|
+
const mf = TY.fit(c, msg, sw * 0.7 - 60 * u, (ctx.portrait ? 44 : 40) * u, 'PuHui-Bold', { maxLines: 3, min: 0.75, balance: true }), fs = mf.size, lh = fs * 1.3; // 长句折行(最多 3 行),气泡往下长
|
|
165
|
+
const bwid = Math.max(...mf.lines.map(l => TY.width(c, l, fs, 'PuHui-Bold'))) + 60 * u, bht = 78 * u + (mf.lines.length - 1) * lh;
|
|
166
|
+
if (bubY > sy) { c.fillStyle = t >= T_SEND ? C.sent : C.bubble; c.beginPath(); c.roundRect(sx + sw - 30 * u - bwid, bubY - 52 * u, bwid, bht, 24 * u); c.fill();
|
|
167
|
+
let left = Math.round(p * mf.lines.reduce((a, l) => a + [...l].length, 0)); // 逐行打出来,光标跟在最后打到的那一行
|
|
168
|
+
const ns = mf.lines.map(ln => { const n = Math.min(left, [...ln].length); left -= n; return n; }), curK = Math.max(0, ns.findLastIndex(n => n > 0)), blink = t < T_SEND && Math.floor(t * 4) % 2;
|
|
169
|
+
mf.lines.forEach((ln, k) => { if (k > curK) return;
|
|
170
|
+
TY.text(c, [...ln].slice(0, ns[k]).join('') + (blink && k === curK ? '|' : ''), sx + sw - 30 * u - bwid + 30 * u, bubY + 2 * u + k * lh, { size: fs, fam: 'PuHui-Bold', color: t >= T_SEND ? C.sentInk : C.type, align: 'left' }); }); } }
|
|
171
|
+
// AI 的回复:代码墙,越滚越快
|
|
172
|
+
if (t > T_SEND + 0.05) { const lh = 46 * u, y0 = top + (msg ? 220 : 40) * u - scr;
|
|
173
|
+
for (let i = Math.max(0, Math.floor((top - y0) / lh)); ; i++) { const y = y0 + i * lh; if (y > sy + sh) break; if (y < top) continue;
|
|
174
|
+
const shown = clamp((t - T_SEND - 0.05 - Math.min(i, 12) * 0.03) / 0.05); if (shown <= 0) continue;
|
|
175
|
+
c.fillStyle = C.gutter; c.fillRect(sx + 30 * u, y - 12 * u, 44 * u, 16 * u);
|
|
176
|
+
for (const [x, w, col] of LINES[i % LINES.length]) { c.fillStyle = C.codes[col % C.codes.length]; c.globalAlpha = 0.9; c.beginPath(); c.roundRect(sx + 110 * u + x * u, y - 14 * u, w * u * shown, 20 * u, 10 * u); c.fill(); } c.globalAlpha = 1; }
|
|
177
|
+
const v = (scrollAt(t + 1 / 60) - scr) * 60; if (v > 1500 * u) { c.fillStyle = `rgba(${C.fast},${clamp((v - 1500 * u) / (6000 * u)) * 0.5})`; c.fillRect(sx, top, sw, sh); } }
|
|
178
|
+
// 顶栏:三个圆点、标题、计数器(砸出那一刻停在 data.count)
|
|
179
|
+
c.fillStyle = C.bar; c.fillRect(sx, sy, sw, 64 * u);
|
|
180
|
+
C.dots.forEach((col, i) => { c.fillStyle = col; c.beginPath(); c.arc(sx + 42 * u + i * 34 * u, sy + 32 * u, 10 * u, 0, 7); c.fill(); });
|
|
181
|
+
TY.text(c, d.app || 'AI助手', sx + sw / 2, sy + 44 * u, { size: 30 * u, fam: 'PuHui-Bold', color: C.appText });
|
|
182
|
+
if (d.count != null && t > T_SEND + 0.1) { const N = +d.count, k = slam >= 0 ? N : Math.min(N - 1, Math.floor(Math.pow(clamp((t - T_SEND - 0.15) / Math.max(0.2, T_HIT - T_SEND - 0.15)), 2.4) * (N - 1)));
|
|
183
|
+
TY.text(c, `${k}${d.unit || ''}`, sx + sw - 30 * u, sy + 44 * u, { size: 32 * u, fam: 'PuHui-Bold', color: slam >= 0 ? C.hot : C.appText, align: 'right' }); }
|
|
184
|
+
// 大字砸出(backOut 过冲)+压暗
|
|
185
|
+
if (slam > 0 && q.text) { c.fillStyle = `rgba(${C.slamDim},${0.6 * MO.expoOut(clamp(slam / 0.15))})`; c.fillRect(sx, sy, sw, sh);
|
|
186
|
+
const fs = TY.fit(c, q.text, sw * 0.86, (ctx.portrait ? 200 : 230) * u, 'PuHui-Black', { maxLines: 2, min: 0.4, balance: true });
|
|
187
|
+
fs.lines.forEach((ln, i) => TY.pop(c, ln, sx + sw / 2, sy + sh / 2 + fs.size * 0.35 + (i - (fs.lines.length - 1) / 2) * fs.size * 1.1, clamp(slam / 0.24), { size: fs.size, fam: 'PuHui-Black', color: C.hot, over: 3.5, stroke: C.insertBg, strokeW: 18 * u })); }
|
|
188
|
+
c.restore();
|
|
189
|
+
});
|
|
190
|
+
// 前景:帧库给了 back 就贴角色的后脑勺(过肩),定住;砸出时往后一缩。没有 back 帧就不画(不再用代码画后脑勺)
|
|
191
|
+
const hx = W * (ctx.portrait ? 0.2 : 0.17), hy = by + bh - 60 * u + (slam > 0 ? -MO.settle(slam, 18 * u, 4, 7) : 0), s = (ctx.portrait ? 0.8 : 0.75) * u;
|
|
192
|
+
if (FR.back) { const [iw, ih] = MD.size(FR.back), h = 560 * s, w = iw * h / ih; c.drawImage(FR.back, hx - w / 2, hy + 500 * s - h, w, h); }
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// 片段 · 动态文字(y5):字就是演员。一个 cue 一页:主词在 at 这一帧砸进来(弹簧过冲 ≈8%),辅句从槽里升起;
|
|
2
|
+
// 换页是色带擦除,新页面露出来的那一帧=at(擦除在 at 之前 0.3s 开始)。
|
|
3
|
+
// cues:title | point(一页:text 主词(太长自动缩字号、最多两行),sub 辅句,data.label 左上小标签如「01」)
|
|
4
|
+
// number(一页大数字:data {value, prefix, suffix, decimals},at = 数字落定的时刻,页面提前 0.7s 出现开始滚)
|
|
5
|
+
// highlight(当前页里 data.word 这个词刷荧光笔+放大一下;不给 word 就刷整个主词)
|
|
6
|
+
// sub 里的关键词:data.key 写辅句里的一个词,那个词用强调色(示范片「下一行」那样)
|
|
7
|
+
// number 的说明写 text 或 sub 都行(clip.js 已统一);data.label 照样有
|
|
8
|
+
// 两个以上的 point 页 = 一份清单:画进度轨(01/02/03,告诉观众第几个、一共几个;横屏右上、竖屏在下方)和背景巨型序号(低对比、斜向慢漂,data.num 可指定)。
|
|
9
|
+
// data.palette = [[底色, 字色], ...] 换配色(写了就优先,盖过 spec.theme)。alpha 模式不画底,字白色带深色描边。safe:主词块在 top..H−bottom 之间居中,进度轨也在这一带里。
|
|
10
|
+
// spec.theme(全片色板 ctx.pal):页面轮换 [bg 底 ink 字] → [accent 底] → [ink 底 bg 字] → [accent2 底](强调色页的字取 surface/ink/bg 里最清楚的那个);
|
|
11
|
+
// 荧光笔、辅句关键词、换页色带都只从 bg / ink / accent / accent2 里挑和当前页底色不同的。
|
|
12
|
+
CLIPS.y5_kinetic_type = (() => {
|
|
13
|
+
const { clamp, lerp } = U;
|
|
14
|
+
const PAGES0 = [['#FFD23F', '#111111'], ['#FF5A36', '#FFFFFF'], ['#14213D', '#FFD23F'], ['#F4EFE6', '#111111']];
|
|
15
|
+
let pages, LIST, TP = null; // TP:全片色板(没写 data.palette 时才用)
|
|
16
|
+
const same = (a, b) => String(a).toLowerCase() === String(b).toLowerCase();
|
|
17
|
+
// 在候选色里挑一个和页底、字色都不同、压在页底上最清楚的
|
|
18
|
+
const pick = (bg, fg, cands) => { const ok = cands.filter(x => !same(x, bg) && !same(x, fg)); return ok.length ? PAL.on(bg, ok) : cands[0]; };
|
|
19
|
+
const accentOf = col => TP ? pick(col[0], col[1], [TP.accent, TP.accent2]) : col[0] === '#FFD23F' || col[0] === '#F4EFE6' || col[1] === '#FFD23F' ? '#FF5A36' : '#FFD23F'; // 字本身是黄的(深蓝页)就用橙,不然着色看不出来
|
|
20
|
+
const fromPal = p => { const txt = bg => PAL.on(bg, [p.surface, p.ink, p.bg]); return [[p.bg, p.ink], [p.accent, txt(p.accent)], [p.ink, p.bg], [p.accent2, txt(p.accent2)]]; };
|
|
21
|
+
return {
|
|
22
|
+
fonts: ['PuHui-Black', 'PuHui-Heavy', 'PuHui-Bold'],
|
|
23
|
+
safe: true,
|
|
24
|
+
init(ctx) {
|
|
25
|
+
TP = ctx.data.palette ? null : ctx.pal;
|
|
26
|
+
const { W, u } = ctx, pal = ctx.data.palette || (TP ? fromPal(TP) : PAGES0), g = document.createElement('canvas').getContext('2d');
|
|
27
|
+
pages = ctx.of('title', 'point', 'number').map((q, i) => {
|
|
28
|
+
const num = q.kind === 'number', nd = q.data || {};
|
|
29
|
+
const text = num ? (nd.prefix || '') + TY.fmt(nd.value || 0, nd.decimals ?? 0) + (nd.suffix || '') : (q.text || '');
|
|
30
|
+
const S0 = (ctx.portrait ? 380 : 300) * u;
|
|
31
|
+
let f = TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 1, min: 0.55, soft: true }); // 先缩字号放一行,再不行折两行;单个长英文词再缩也要放得下
|
|
32
|
+
if (!f.ok) f = num ? TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 1 }) // 数字不折行(滚动时逐字变宽,折开会跳):只缩字号
|
|
33
|
+
: TY.fit(g, text, W * 0.86, S0, 'PuHui-Black', { maxLines: 2, min: 0.3, balance: true });
|
|
34
|
+
const sf = q.sub ? TY.fit(g, q.sub, W * 0.86, (ctx.portrait ? 64 : 80) * u, 'PuHui-Heavy', { maxLines: 2, min: 0.6, balance: true }) : null; // 辅句:放不下先缩、再折两行
|
|
35
|
+
const show = num ? q.at - 0.7 : q.at;
|
|
36
|
+
return { q, i, num, nd, text, f, sf, show, col: pal[i % pal.length] };
|
|
37
|
+
});
|
|
38
|
+
const pts = pages.filter(p => p.q.kind === 'point'); pts.forEach((p, k) => { p.ord = k + 1; });
|
|
39
|
+
LIST = pts.length >= 2 ? pts : null;
|
|
40
|
+
// 高亮的词不在页上:只告警(以前什么都不发生)
|
|
41
|
+
for (const h of ctx.of('highlight')) { const w = h.data && h.data.word; if (!w) continue; const pg = [...pages].reverse().find(p => p.show <= h.at + 1e-6);
|
|
42
|
+
if (pg && !pg.f.lines.some(l => l.includes(w))) console.warn(`y5 highlight at=${h.at}:主词里没有「${w}」(主词「${pg.text}」;荧光笔只刷主词,辅句里的词用 data.key 着色),这一下什么都不做`); }
|
|
43
|
+
},
|
|
44
|
+
draw(c, t, ctx) {
|
|
45
|
+
const { W, H, u } = ctx;
|
|
46
|
+
let k = -1; pages.forEach((p, i) => { if (t >= p.show - 1e-6) k = i; });
|
|
47
|
+
const P = pages[Math.max(0, k)]; if (!P) return;
|
|
48
|
+
const page = (p, c2) => drawPage(c2, p, t, ctx);
|
|
49
|
+
// 底+点阵
|
|
50
|
+
if (!ctx.alpha) { c.fillStyle = P.col[0]; c.fillRect(0, 0, W, H); dots(c, t, P.col[1], 0.07, u, W, H); }
|
|
51
|
+
if (k >= 0) {
|
|
52
|
+
// 拍点微冲:每个 cue 落下时整屏放大 1.5%,0.15s 衰减
|
|
53
|
+
let bump = 1; for (const q of ctx.cues) { const l = t - q.at; if (l >= 0 && l < 0.4) bump += 0.015 * Math.exp(-l * 22); }
|
|
54
|
+
// 镜头不漂不晃:落定就停,运动只在拍点上(上面的微冲、主词砸入、换页色带)
|
|
55
|
+
CAM.with(c, { x: W / 2, y: H / 2, z: bump }, cc => { ghost(cc, P, t, ctx); page(P, cc); });
|
|
56
|
+
track(c, P, t, ctx);
|
|
57
|
+
}
|
|
58
|
+
// 换页:两条色带从左扫到右,最后一条后面露出新页(露出那一帧=新页的 show 时刻)
|
|
59
|
+
const N = pages[k + 1];
|
|
60
|
+
if (N && !ctx.alpha) {
|
|
61
|
+
const p = (t - (N.show - 0.3)) / 0.36; if (p > 0 && p < 1) {
|
|
62
|
+
const mid = (TP ? [TP.accent, TP.accent2, TP.ink, TP.bg] : ['#FFD23F', '#FF5A36', '#14213D', '#3A86FF']).find(x => !same(x, P.col[0]) && !same(x, N.col[0])), cols = [mid, N.col[0]], sk = H * 0.35; // 两条:一条和前后底色都不同的强调色+新底色(以前第一条是字色,白字页前会整屏闪一帧白)
|
|
63
|
+
const edge = q => lerp(-sk - 40, W + sk + 40, MO.expoInOut(clamp(q)));
|
|
64
|
+
cols.forEach((col, i) => { const x = edge((p - i * 0.12) / 0.76); c.save(); c.beginPath(); c.moveTo(-sk - 60, 0); c.lineTo(x + sk, 0); c.lineTo(x, H); c.lineTo(-sk - 60, H); c.closePath(); c.clip(); c.fillStyle = col; c.fillRect(0, 0, W, H); c.restore(); });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
function dots(c, t, col, a, u, W, H) { const g = 64 * u, ox = (t * 36 * u) % g, oy = (t * 18 * u) % g; c.save(); c.globalAlpha = a; c.fillStyle = col; for (let y = -g; y < H + g; y += g) for (let x = -g; x < W + g; x += g) { c.beginPath(); c.arc(x + ox, y + oy, 4 * u, 0, Math.PI * 2); c.fill(); } c.restore(); }
|
|
70
|
+
function drawPage(c, P, t, ctx) {
|
|
71
|
+
const { W, H, u } = ctx, q = P.q, fg = ctx.alpha ? '#FFFFFF' : P.col[1], lt = ctx.lt(t, P.num ? P.show : q.at);
|
|
72
|
+
const stroke = ctx.alpha ? { stroke: '#111', strokeW: P.f.size * 0.06 } : {};
|
|
73
|
+
const lines = P.num ? [P.text] : P.f.lines, size = P.f.size, lh = size * 1.08;
|
|
74
|
+
const sub = q.sub, subSz = P.sf ? P.sf.size : 0, subL = P.sf ? P.sf.lines : [];
|
|
75
|
+
const by0 = ctx.safe.top, by1 = H - ctx.safe.bottom - (ctx.portrait && LIST ? 220 * u : 0), cyB = ctx.portrait ? by0 + (by1 - by0) * 0.44 : (by0 + by1) / 2;
|
|
76
|
+
const blockH = lines.length * lh + (sub ? subSz * (0.6 + subL.length * 1.15) : 0), y0 = cyB - blockH / 2 + size * 0.82;
|
|
77
|
+
// 标签(左上)
|
|
78
|
+
let label = q.data && q.data.label;
|
|
79
|
+
if (label) { const lf = TY.fitFloor(c, label, W * (ctx.portrait ? 0.84 : 0.5) - (ctx.portrait ? 80 : 140) * u, (ctx.portrait ? 56 : 52) * u, 'PuHui-Bold', { maxLines: 1, floor: 34 * u }); label = lf.lines[0] || ''; }
|
|
80
|
+
if (label) TY.rise(c, label, (ctx.portrait ? 80 : 140) * u, ctx.safe.top + (ctx.portrait ? 260 : 210) * u, MO.at(lt, 0, 0.3), { size: (ctx.portrait ? 56 : 52) * u, fam: 'PuHui-Bold', color: fg, track: 6 * u, ...(ctx.alpha ? { stroke: '#111', strokeW: 6 * u } : {}) });
|
|
81
|
+
// 高亮(画在字后面)
|
|
82
|
+
const hq = ctx.of('highlight').filter(h => h.at >= (P.num ? P.show : q.at) - 1e-6).find(h => { const nx = ctx.of('title', 'point', 'number').find(z => z.at > q.at); return !nx || h.at < nx.at; });
|
|
83
|
+
let hiScale = 1, knock = null;
|
|
84
|
+
if (hq) {
|
|
85
|
+
const hl = ctx.lt(t, hq.at), wd = hq.data && hq.data.word, found = !wd || lines.some(l => l.includes(wd));
|
|
86
|
+
hiScale = hl > 0 && found ? 1 + 0.06 * MO.settle(hl, 1, 2.5, 6) : 1; // 词不在主词里:不刷也不放大(init 里已告警)
|
|
87
|
+
if (hl > 0) lines.forEach((ln, i) => {
|
|
88
|
+
const word = wd || ln, at = ln.indexOf(word); if (at < 0) return;
|
|
89
|
+
const wPre = TY.width(c, ln.slice(0, at), size), wW = TY.width(c, word, size), wl = TY.width(c, ln, size), x = W / 2 - wl / 2 + wPre, y = y0 + i * lh;
|
|
90
|
+
const mc = ctx.alpha ? (TP ? TP.accent : '#FF5A36') : TP ? (same(P.col[0], TP.accent) ? TP.accent2 : TP.accent) : (P.col[0] === '#FFD23F' ? '#FF5A36' : '#FFD23F'), mr = [x - 12 * u, y - size * 0.86, wW + 24 * u, size * 1.0, MO.at(hl, 0, 0.3)];
|
|
91
|
+
TY.marker(c, ...mr, mc);
|
|
92
|
+
if (mc.toLowerCase() === fg.toLowerCase()) (knock = knock || []).push({ i, mr }); // 荧光笔和字同色(深蓝页黄字):笔下面的字反白成底色,不然整个词被涂没
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// 主词:每行在 at(+0.08s/行)砸进来;数字页从 0 滚到落定
|
|
96
|
+
lines.forEach((ln, i) => {
|
|
97
|
+
let s = ln; if (P.num) { const k = MO.expoOut(clamp(lt / 0.7)); s = (P.nd.prefix || '') + TY.fmt((P.nd.value || 0) * k, P.nd.decimals ?? 0) + (P.nd.suffix || ''); }
|
|
98
|
+
const w = TY.width(c, s, size), x = W / 2 - w / 2, y = y0 + i * lh;
|
|
99
|
+
TY.slam(c, s, x, y, lt - i * 0.08, { size, color: fg, scale: hiScale, ox: W / 2, oy: y - size * 0.36, ...stroke });
|
|
100
|
+
for (const k of (knock || []).filter(k => k.i === i)) { // 只在荧光笔那块平行四边形里,用底色再砸一遍同一行(和 TY.marker 同一形状)
|
|
101
|
+
const [mx, my, mw, mh, mp] = k.mr, ww = mw * MO.expoOut(clamp(mp)), sk = mh * 0.18;
|
|
102
|
+
c.save(); c.beginPath(); c.moveTo(mx + sk, my); c.lineTo(mx + ww + sk, my); c.lineTo(mx + ww, my + mh); c.lineTo(mx, my + mh); c.closePath(); c.clip();
|
|
103
|
+
TY.slam(c, s, x, y, lt - i * 0.08, { size, color: P.col[0], scale: hiScale, ox: W / 2, oy: y - size * 0.36 }); c.restore();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (sub) subL.forEach((ln, k) => { // 辅句(可两行);data.key 那个词用强调色
|
|
107
|
+
const sw = TY.width(c, ln, subSz, 'PuHui-Heavy'), sy = y0 + (lines.length - 1) * lh + subSz * (1.7 + k * 1.15), sp = MO.at(lt, 0.35 + k * 0.08, 0.35), so = { size: subSz, fam: 'PuHui-Heavy', color: fg, ...(ctx.alpha ? { stroke: '#111', strokeW: subSz * 0.08 } : {}) };
|
|
108
|
+
const key = q.data && q.data.key, ki = key ? ln.indexOf(key) : -1, x0 = W / 2 - sw / 2;
|
|
109
|
+
if (ki < 0) return TY.rise(c, ln, x0, sy, sp, so);
|
|
110
|
+
const pre = ln.slice(0, ki), post = ln.slice(ki + key.length), wp = TY.width(c, pre, subSz, 'PuHui-Heavy'), wk = TY.width(c, key, subSz, 'PuHui-Heavy');
|
|
111
|
+
if (pre) TY.rise(c, pre, x0, sy, sp, so); TY.rise(c, key, x0 + wp, sy, sp, { ...so, color: ctx.alpha ? (TP ? TP.accent : '#FFD23F') : accentOf(P.col) }); if (post) TY.rise(c, post, x0 + wp + wk, sy, sp, so);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// 背景巨型序号:清单页右下角(竖屏落在下半屏),低对比、弹簧落位后斜向慢漂;不和主词抢(字色 12% 透明度)
|
|
115
|
+
function ghost(c, P, t, ctx) {
|
|
116
|
+
if (!LIST || !P.ord || ctx.alpha) return;
|
|
117
|
+
const { W, H, u } = ctx, lt = t - P.show, k = MO.springHz(lt, 2.0, 8), sc = lerp(1.35, 1, k), s = String((P.q.data && P.q.data.num) ?? P.ord);
|
|
118
|
+
const size = (ctx.portrait ? 1000 : 900) * u, x = W - (ctx.portrait ? 40 : 60) * u + lt * 14 * u, y = H - ctx.safe.bottom + (ctx.portrait ? -120 : 70) * u - lt * 10 * u;
|
|
119
|
+
TY.text(c, s, x, y, { size, fam: 'PuHui-Black', align: 'right', color: P.col[1], alpha: 0.12 * clamp(lt / 0.08), scale: sc, ox: x - size * 0.28, oy: y - size * 0.36 });
|
|
120
|
+
}
|
|
121
|
+
// 进度轨:01/02/03 三格(几个 point 页就几格),当前页那一格在换页露出那一帧点亮;画在相机外,不跟页面晃
|
|
122
|
+
function track(c, P, t, ctx) {
|
|
123
|
+
if (!LIST) return;
|
|
124
|
+
const { W, H, u } = ctx, n = LIST.length, k = LIST.filter(p => p.show <= t + 1e-6).length; if (k === 0) return;
|
|
125
|
+
const fg = ctx.alpha ? '#FFFFFF' : P.col[1], gap = 18 * u, seg = Math.min((ctx.portrait ? 150 : 110) * u, ((ctx.portrait ? W * 0.86 : W * 0.4) - (n - 1) * gap) / n), tot = n * seg + (n - 1) * gap; // 要点多时每格变窄,不出画
|
|
126
|
+
const x0 = ctx.portrait ? (W - tot) / 2 : W - (ctx.portrait ? 80 : 140) * u - tot, y = ctx.portrait ? H - ctx.safe.bottom - 130 * u : ctx.safe.top + 120 * u;
|
|
127
|
+
const a = MO.expoOut(clamp((t - LIST[0].show) / 0.3));
|
|
128
|
+
c.save(); c.globalAlpha = a;
|
|
129
|
+
for (let i = 0; i < n; i++) { const x = x0 + i * (seg + gap), on = i < k, fill = on ? MO.expoOut(clamp((t - LIST[i].show) / 0.3)) : 0;
|
|
130
|
+
c.fillStyle = fg; c.globalAlpha = a * 0.25; c.fillRect(x, y, seg, 10 * u); c.globalAlpha = a; c.fillRect(x, y, seg * fill, 10 * u);
|
|
131
|
+
TY.text(c, String(i + 1).padStart(2, '0'), x, y - 20 * u, { size: (ctx.portrait ? 40 : 32) * u, fam: 'PuHui-Bold', color: fg, align: 'left', alpha: on ? 1 : 0.4, track: 2 * u, ...(ctx.alpha ? { stroke: '#111', strokeW: 5 * u } : {}) }); }
|
|
132
|
+
c.restore();
|
|
133
|
+
}
|
|
134
|
+
})();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""原片 vs 复刻 逐帧并排:给一组时刻,各抽一帧,左原片右复刻,拼成一张图。
|
|
3
|
+
python3 compare.py --a 原片.mp4 --b 复刻.mp4 --times 1.5,1.6 --out 对比.jpg [--w 640]"""
|
|
4
|
+
import argparse, subprocess, io
|
|
5
|
+
from PIL import Image, ImageDraw
|
|
6
|
+
ap = argparse.ArgumentParser(); ap.add_argument('--a'); ap.add_argument('--b'); ap.add_argument('--times'); ap.add_argument('--out'); ap.add_argument('--w', type=int, default=640); ap.add_argument('--cols', type=int, default=2)
|
|
7
|
+
a = ap.parse_args()
|
|
8
|
+
def grab(v, t):
|
|
9
|
+
png = subprocess.run(['ffmpeg', '-v', 'error', '-ss', str(t), '-i', v, '-frames:v', '1', '-f', 'image2pipe', '-c:v', 'png', '-'], capture_output=True).stdout
|
|
10
|
+
return Image.open(io.BytesIO(png)).convert('RGB').resize((a.w, a.w * 9 // 16))
|
|
11
|
+
ts = [float(x) for x in a.times.split(',')]
|
|
12
|
+
w, h = a.w, a.w * 9 // 16
|
|
13
|
+
cols = a.cols; rows = (len(ts) + cols - 1) // cols
|
|
14
|
+
S = Image.new('RGB', (cols * 2 * w + (cols - 1) * 12, rows * (h + 18)), 'white'); d = ImageDraw.Draw(S)
|
|
15
|
+
for i, t in enumerate(ts):
|
|
16
|
+
x = (i % cols) * (2 * w + 12); y = (i // cols) * (h + 18)
|
|
17
|
+
S.paste(grab(a.a, t), (x, y + 18)); S.paste(grab(a.b, t), (x + w, y + 18))
|
|
18
|
+
d.text((x + 3, y + 3), f'{t:.3f}s 原片 | 复刻', fill='black')
|
|
19
|
+
S.save(a.out, quality=85); print(a.out)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// y1/y2/y3 三支示范片里的花叔形象。window.HERO_MODE = 'C'(默认)| 'A' | 'rig'。
|
|
2
|
+
// C 各片原生(示范片采用):每种风格单独用 gpt-image 生一版设定图(Kurzgesagt 扁平无描边 / Vox 复用 Kurzgesagt 那张转灰度当剪报贴纸 /
|
|
3
|
+
// 白板马克笔线稿),放在 demos/_shared/hero/。这是「纯代码绘制」主线的明确例外——角色是贴图,画面其余全是代码。
|
|
4
|
+
// A 极简几何:纯代码画的 Q 版(大圆头+白渔夫帽+圆眼镜),每片按该片画法上色/描线。不想用贴图时切到它。
|
|
5
|
+
// rig 旧骨架:lib/rig_huashu.js 的 FK 角色(各片里的回退分支)。
|
|
6
|
+
// (实验时还有方向 B「G2 绘本帧库」,需要 24 张帧图,没收进 skill。)
|
|
7
|
+
(() => {
|
|
8
|
+
const { clamp, lerp } = U;
|
|
9
|
+
const H = window.HERO = {};
|
|
10
|
+
const MODE = () => window.HERO_MODE || 'C';
|
|
11
|
+
H.mode = MODE;
|
|
12
|
+
const PNG = n => `demos/_shared/hero/${n}.png`;
|
|
13
|
+
window.EXTRA_ASSETS = (window.EXTRA_ASSETS || []).concat(['c1_0', 'c1_1', 'c1_2', 'c3_line'].map(PNG));
|
|
14
|
+
const img = p => window.IMG[p];
|
|
15
|
+
|
|
16
|
+
// 把一张图按「脚底中心」放到 (x, y),高 h;face=-1 水平翻转
|
|
17
|
+
const place = (c, im, x, y, h, face = 1, anchorY = 0.97) => {
|
|
18
|
+
const w = im.width * h / im.height; c.save(); c.translate(x, y); if (face < 0) c.scale(-1, 1); c.drawImage(im, -w / 2, -h * anchorY, w, h); c.restore();
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ---------- A:极简几何 Q 版 ----------
|
|
22
|
+
// 原点 = 两脚之间地面;单位高 1 ≈ 全身高;opt.style: 'flat'(无描边,同色深一档做阴影)| 'line'(白底黑线)| 'print'(平涂+细黑线)
|
|
23
|
+
H.geo = (c, x, y, h, { face = 1, t = 0, style = 'flat', point = true, ink = '#1d1b19' } = {}) => {
|
|
24
|
+
const s = h / 100; c.save(); c.translate(x, y); c.scale(face * s, s);
|
|
25
|
+
const bob = Math.sin(t * 6.5) * 0.8;
|
|
26
|
+
const line = style !== 'flat', lw = style === 'line' ? 2.2 : 1.4;
|
|
27
|
+
const P = { skin: '#ffd8bd', skinSh: '#f2b493', hat: '#ffffff', hatSh: '#dfe3ef', tee: '#ffffff', teeSh: '#e3e6f0', shorts: '#d8c7a6', shortsSh: '#bfa982', shoe: '#ffffff', hair: '#1f1b2e', glass: '#1f1b2e', cheek: 'rgba(255,128,120,.45)' };
|
|
28
|
+
if (style === 'line') Object.assign(P, { skin: '#fbfbf8', skinSh: '#fbfbf8', hatSh: '#fbfbf8', tee: '#fbfbf8', teeSh: '#fbfbf8', shorts: '#fbfbf8', shortsSh: '#fbfbf8', cheek: 'rgba(239,114,38,.35)' });
|
|
29
|
+
// 扁平分面:先铺深一档,再把本色往左上挪 2.5 单位盖回去,右下留一条阴影带(和 Y1 的 flat 同一招)
|
|
30
|
+
const shape = (path, fill, sh) => { if (sh && sh !== fill) { c.save(); c.clip(path); c.fillStyle = sh; c.fill(path); c.translate(-2.5, -2); c.fillStyle = fill; c.fill(path); c.restore(); } else { c.fillStyle = fill; c.fill(path); } if (line) { c.strokeStyle = ink; c.lineWidth = lw; c.stroke(path); } };
|
|
31
|
+
const rr = (x0, y0, w, hh, r) => { const p = new Path2D(); p.roundRect(x0, y0, w, hh, r); return p; };
|
|
32
|
+
const el = (cx, cy, rx, ry) => { const p = new Path2D(); p.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2); return p; };
|
|
33
|
+
c.lineJoin = 'round'; c.lineCap = 'round';
|
|
34
|
+
// 腿+洞洞鞋
|
|
35
|
+
for (const lx of [-7, 7]) { shape(rr(lx - 3, -14, 6, 12, 3), P.skin, P.skinSh); shape(el(lx + 2.5, -2, 7.5, 4), P.shoe, P.hatSh); }
|
|
36
|
+
c.fillStyle = ink; for (const lx of [-7, 7]) for (const [dx, dy] of [[-1, -3], [2, -3.5], [5, -2.5]]) { c.beginPath(); c.arc(lx + 2.5 + dx, -2 + dy + 1, 0.8, 0, 7); c.fill(); }
|
|
37
|
+
c.translate(0, bob);
|
|
38
|
+
shape(rr(-12, -26, 24, 13, 4), P.shorts, P.shortsSh); // 短裤
|
|
39
|
+
// 远侧手臂(自然下垂,手表)
|
|
40
|
+
shape(rr(-15, -42, 6, 16, 3), P.skin, P.skinSh); c.fillStyle = ink; c.fillRect(-15.5, -30, 7, 3);
|
|
41
|
+
shape(rr(-14, -46, 28, 22, 7), P.tee, P.teeSh); // T 恤
|
|
42
|
+
// 近侧手臂:指向前方(point)或下垂
|
|
43
|
+
if (point) { c.save(); c.translate(10, -42); c.rotate(-1.35 + Math.sin(t * 3.4) * 0.06); shape(rr(-3.2, 0, 6.4, 20, 3.2), P.skin, P.skinSh); shape(el(0, 21, 3.6, 3.6), P.skin); c.restore(); }
|
|
44
|
+
else shape(rr(9, -42, 6, 16, 3), P.skin, P.skinSh);
|
|
45
|
+
// 头:正面大圆脸(头身比约 1:1.2)。正面比侧面好画、也更好认:帽檐下一圈刘海、两只耳朵、圆眼镜居中
|
|
46
|
+
c.translate(0, -66); c.rotate(Math.sin(t * 2.1) * 0.03);
|
|
47
|
+
for (const ex of [-19, 19]) shape(el(ex, 4, 3.6, 4.6), P.skin, P.skinSh); // 耳
|
|
48
|
+
shape(el(0, 2, 19, 18.5), P.skin, P.skinSh); // 脸
|
|
49
|
+
const crown = new Path2D(); crown.moveTo(-17, -9); crown.bezierCurveTo(-17, -33, 17, -33, 17, -9); crown.closePath();
|
|
50
|
+
const brim = new Path2D(); brim.ellipse(0, -9, 29, 7, 0, 0, Math.PI * 2);
|
|
51
|
+
shape(brim, P.hat, P.hatSh); shape(crown, P.hat, P.hatSh);
|
|
52
|
+
// 刘海+鬓角:画在帽檐之后,从帽檐下沿露出一排黑发(光头感就是少了这一排)
|
|
53
|
+
c.fillStyle = P.hair; c.beginPath(); c.moveTo(-19.5, -4); c.lineTo(19.5, -4); c.lineTo(19, 5); c.lineTo(16.5, 1); c.lineTo(13, -0.5); c.lineTo(9, 1.5); c.lineTo(5, -1); c.lineTo(0, 1); c.lineTo(-5, -1.5); c.lineTo(-9, 1); c.lineTo(-13, -1); c.lineTo(-16.5, 1.5); c.lineTo(-19, 5); c.closePath(); c.fill();
|
|
54
|
+
shape(brim, P.hat, P.hatSh);
|
|
55
|
+
if (!line) { c.fillStyle = P.hatSh; c.beginPath(); c.ellipse(0, -9.5, 17, 2.2, 0, 0, Math.PI * 2); c.fill(); } // 帽身与帽檐交界的一道暗线(色块,不是描边)
|
|
56
|
+
// 圆眼镜+眼睛(眨眼 0.12s/3.2s)
|
|
57
|
+
const blink = (t % 3.2) > 3.08;
|
|
58
|
+
c.strokeStyle = P.glass; c.lineWidth = 1.8; for (const ex of [-7.5, 7.5]) { c.beginPath(); c.arc(ex, 3, 6, 0, 7); c.stroke(); }
|
|
59
|
+
c.beginPath(); c.moveTo(-1.5, 3); c.lineTo(1.5, 3); c.stroke();
|
|
60
|
+
c.fillStyle = P.glass; for (const ex of [-7.5, 7.5]) { if (blink) c.fillRect(ex - 2.2, 3, 4.4, 1.1); else { c.beginPath(); c.ellipse(ex, 3.4, 1.8, 2.3, 0, 0, 7); c.fill(); } }
|
|
61
|
+
c.fillStyle = P.cheek; for (const ex of [-11, 11]) { c.beginPath(); c.ellipse(ex, 10.5, 3.2, 1.9, 0, 0, 7); c.fill(); }
|
|
62
|
+
const m = 0.4 + 0.6 * Math.max(0, Math.sin(t * 9)); // 嘴:说话时开合
|
|
63
|
+
c.fillStyle = '#c4473f'; c.beginPath(); c.ellipse(0, 12.5, 2.8, 0.9 + 1.7 * m, 0, 0, 7); c.fill();
|
|
64
|
+
c.restore();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ---------- 每片的接入点 ----------
|
|
68
|
+
// Y1 镜 3:花叔站在草坡左侧指向黑猫(脚底 (470,900),全身约 440px)
|
|
69
|
+
H.y1 = (g, ft, flatFn) => {
|
|
70
|
+
const m = MODE();
|
|
71
|
+
if (m === 'A') return H.geo(g, 470, 900, 440, { face: 1, t: ft, style: 'flat' });
|
|
72
|
+
if (m === 'C') {
|
|
73
|
+
// 一张设定稿里的三个姿势当三个关键帧:拉出时挥手 → 扫描时指向黑猫 → 标签弹出后举手欢呼。
|
|
74
|
+
// 换姿势那一下给 0.12s 的挤压回弹(Kurzgesagt 弹出的 108% 过冲),站着时以脚底为轴呼吸。
|
|
75
|
+
const l = ft - Y1.SHOTS.s3[0], k = l < 1.25 ? 1 : l < 2.3 ? 0 : 2, t0 = [0, 1.25, 2.3][[1, 0, 2].indexOf(k)];
|
|
76
|
+
const pop = l - t0 < 0.14 && t0 > 0 ? 1 + 0.06 * Math.sin((l - t0) / 0.14 * Math.PI) : 1;
|
|
77
|
+
const br = 1 + 0.012 * Math.sin(ft * 5.2);
|
|
78
|
+
g.save(); g.translate(470, 905); g.scale(1 / Math.sqrt(br * pop), br * pop); place(g, img(PNG('c1_' + k)), 0, 0, 450, 1); g.restore(); return;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
// Y2 镜 3:剪纸贴纸,画进 sp(之后统一垫白边、阴影),脚底 (1500,1040),面朝左
|
|
83
|
+
H.y2 = (g, st) => {
|
|
84
|
+
const m = MODE();
|
|
85
|
+
if (m === 'A') return H.geo(g, 1500, 1040, 560, { face: -1, t: st, style: 'print', ink: '#171716' }), true;
|
|
86
|
+
if (m === 'C') { const k = Math.floor(st * 12); g.save(); g.translate(1500, 1015); g.rotate(((k * 7919) % 13 - 6) * 0.0012); g.filter = 'grayscale(1) contrast(1.25) brightness(1.04)'; place(g, img(PNG('c1_0')), 20, 8, 520, -1); g.restore(); return true; } // 卡通帧转灰度当剪报贴纸:按 12fps 微抖(定格感)
|
|
87
|
+
return false;
|
|
88
|
+
};
|
|
89
|
+
// Y3 镜 3:被之字形笔触揭开的那张画(世界坐标,脚底 (fx, fy),高 hh)
|
|
90
|
+
H.y3 = (g, fx, fy, hh, ft) => {
|
|
91
|
+
const m = MODE();
|
|
92
|
+
if (m === 'A') return H.geo(g, fx, fy, hh, { face: 1, t: ft, style: 'line', ink: '#0A0503' }), true;
|
|
93
|
+
if (m === 'C') { g.save(); g.globalCompositeOperation = 'multiply'; place(g, img(PNG('c3_line')), fx, fy + 4, hh * 1.08, 1); g.restore(); return true; }
|
|
94
|
+
return false;
|
|
95
|
+
};
|
|
96
|
+
})();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 示范 · 长卷穿越片:一条横向长画卷,几个「世界段」首尾相接,主角从左走到右,跨过边界时画风跟着换。
|
|
2
|
+
// 这里只收了 3 段(埃及壁画 → 莫奈《日本桥》→ 8-bit 像素),完整片子是 24 段,做法见 references/11-长卷穿越片.md。
|
|
3
|
+
// 预览 index.html?film=demos/long_scroll 渲染 render.py --film demos/long_scroll --out long_scroll.mp4
|
|
4
|
+
// 骨架 lib/world.js 管时间轴、主角行进、只进不退的相机、按边界裁开的主角、角标;每段一个文件 segments/<id>.js,调用 WORLD.add({...})。
|
|
5
|
+
(() => {
|
|
6
|
+
window.__bootErrors = window.__bootErrors || [];
|
|
7
|
+
const base = 'demos/long_scroll/';
|
|
8
|
+
const load = url => { const x = new XMLHttpRequest(); x.open('GET', url, false); x.send(); if (x.status !== 200) { __bootErrors.push(`${url} 不存在 (HTTP ${x.status})`); return; }
|
|
9
|
+
try { (0, eval)(x.responseText + `\n//# sourceURL=${url}`); } catch (e) { __bootErrors.push(`${url} 执行出错 ${e && e.stack || e}`); } };
|
|
10
|
+
load(base + 'lib/crossing.js'); // XING:角色帧库、笔触材质、莫奈世界
|
|
11
|
+
load(base + 'lib/acts.js'); // ACT:打水漂的石子轨迹等小剧本工具
|
|
12
|
+
load(base + 'lib/world.js'); // WORLD:长卷骨架
|
|
13
|
+
// 片子顺序:改顺序、加段只改这里(加段 = 往 segments/ 放一个文件,再把 id 写进来)
|
|
14
|
+
window.FILM_ORDER = ['02_egypt', '28_monet', '14_8bit'];
|
|
15
|
+
for (const id of window.FILM_ORDER) { const n = WORLD.segs.length; load(`${base}segments/${id}.js`);
|
|
16
|
+
if (WORLD.segs.length === n || WORLD.segs[WORLD.segs.length - 1].id !== id) __bootErrors.push(`segments/${id}.js 没有 WORLD.add({ id: '${id}', ... })`); }
|
|
17
|
+
// 每段用到的帧库(hero.walk、acts[].key、sprites)一次性登记成资源,引擎启动前全部加载
|
|
18
|
+
const sprites = new Set(); WORLD.segs.forEach(s => { if (s.hero && s.hero.walk) sprites.add(s.hero.walk); (s.acts || []).forEach(a => a.key && sprites.add(a.key)); (s.sprites || []).forEach(k => sprites.add(k)); });
|
|
19
|
+
try { window.EXTRA_ASSETS = (window.EXTRA_ASSETS || []).concat(XING.sprites([...sprites])); } catch (e) { __bootErrors.push('帧库缺失:' + e.message); }
|
|
20
|
+
WORLD.layout();
|
|
21
|
+
window.PUNCH = 0;
|
|
22
|
+
window.ERAS = [{ id: 'film', dur: WORLD.total, draw: (c, lt, t) => WORLD.draw(c, t) }];
|
|
23
|
+
})();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"frames": [
|
|
3
|
+
{
|
|
4
|
+
"file": "01.png",
|
|
5
|
+
"ax": 120.0,
|
|
6
|
+
"ay": 374.0,
|
|
7
|
+
"w": 239,
|
|
8
|
+
"h": 375,
|
|
9
|
+
"air": 0
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"file": "02.png",
|
|
13
|
+
"ax": 118.5,
|
|
14
|
+
"ay": 323.0,
|
|
15
|
+
"w": 239,
|
|
16
|
+
"h": 324,
|
|
17
|
+
"air": 0
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"file": "03.png",
|
|
21
|
+
"ax": 116.5,
|
|
22
|
+
"ay": 396.0,
|
|
23
|
+
"w": 255,
|
|
24
|
+
"h": 367,
|
|
25
|
+
"air": 30
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"file": "04.png",
|
|
29
|
+
"ax": 127.0,
|
|
30
|
+
"ay": 420.0,
|
|
31
|
+
"w": 246,
|
|
32
|
+
"h": 372,
|
|
33
|
+
"air": 49
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"file": "05.png",
|
|
37
|
+
"ax": 136.0,
|
|
38
|
+
"ay": 395.0,
|
|
39
|
+
"w": 257,
|
|
40
|
+
"h": 381,
|
|
41
|
+
"air": 15
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"file": "06.png",
|
|
45
|
+
"ax": 125.0,
|
|
46
|
+
"ay": 316.0,
|
|
47
|
+
"w": 247,
|
|
48
|
+
"h": 317,
|
|
49
|
+
"air": 0
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"file": "07.png",
|
|
53
|
+
"ax": 106.5,
|
|
54
|
+
"ay": 369.0,
|
|
55
|
+
"w": 214,
|
|
56
|
+
"h": 370,
|
|
57
|
+
"air": 0
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"file": "08.png",
|
|
61
|
+
"ax": 128.5,
|
|
62
|
+
"ay": 397.0,
|
|
63
|
+
"w": 267,
|
|
64
|
+
"h": 373,
|
|
65
|
+
"air": 25
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"ref_h": 371.0
|
|
69
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|