@biunow/agent-biuart 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/server.mjs +575 -0
- package/data/styles.json +533 -0
- package/engine/audit_clip.py +46 -0
- package/engine/clip.html +24 -0
- package/engine/clip.js +104 -0
- package/engine/clip_schema.json +19 -0
- package/engine/clips/t1_3b1b.js +203 -0
- package/engine/clips/t2_keynote_ui.js +347 -0
- package/engine/clips/t3_finance_chart.js +206 -0
- package/engine/clips/y1_kurzgesagt.js +183 -0
- package/engine/clips/y2_vox.js +150 -0
- package/engine/clips/y3_whiteboard.js +209 -0
- package/engine/clips/y4_storytime.js +194 -0
- package/engine/clips/y5_kinetic_type.js +134 -0
- package/engine/compare.py +19 -0
- package/engine/demos/_shared/hero/c1_0.png +0 -0
- package/engine/demos/_shared/hero/c1_1.png +0 -0
- package/engine/demos/_shared/hero/c1_2.png +0 -0
- package/engine/demos/_shared/hero/c3_line.png +0 -0
- package/engine/demos/_shared/hero.js +96 -0
- package/engine/demos/long_scroll/eras.js +23 -0
- package/engine/demos/long_scroll/frames/8bit_act/01.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/02.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/03.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/04.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/05.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/06.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/07.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/08.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/meta.json +69 -0
- package/engine/demos/long_scroll/frames/8bit_walk/01.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/02.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/03.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/04.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/05.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/06.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/07.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/08.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/meta.json +69 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/01.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/02.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/03.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/04.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/05.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/06.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/07.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/08.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/meta.json +69 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/01.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/02.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/03.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/04.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/05.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/06.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/07.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/08.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/meta.json +69 -0
- package/engine/demos/long_scroll/frames/monet_throw/01.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/02.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/03.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/04.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/05.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/06.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/07.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/08.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/meta.json +71 -0
- package/engine/demos/long_scroll/frames/monet_walk/01.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/02.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/03.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/04.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/05.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/06.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/07.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/08.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/meta.json +71 -0
- package/engine/demos/long_scroll/lib/acts.js +87 -0
- package/engine/demos/long_scroll/lib/crossing.js +463 -0
- package/engine/demos/long_scroll/lib/world.js +143 -0
- package/engine/demos/long_scroll/lib/xing2_ding.js +42 -0
- package/engine/demos/long_scroll/lib/xing2_jia.js +114 -0
- package/engine/demos/long_scroll/lib/xing2_yi.js +133 -0
- package/engine/demos/long_scroll/segments/02_egypt.js +234 -0
- package/engine/demos/long_scroll/segments/14_8bit.js +118 -0
- package/engine/demos/long_scroll/segments/28_monet.js +86 -0
- package/engine/demos/t1_3b1b/eras.js +11 -0
- package/engine/demos/t1_3b1b/t1_s1.js +101 -0
- package/engine/demos/t1_3b1b/t1_s2.js +74 -0
- package/engine/demos/t1_3b1b/t1_s3.js +65 -0
- package/engine/demos/t2_keynote_ui/eras.js +10 -0
- package/engine/demos/t2_keynote_ui/t2_s1.js +57 -0
- package/engine/demos/t2_keynote_ui/t2_s2.js +70 -0
- package/engine/demos/t2_keynote_ui/t2_s3.js +61 -0
- package/engine/demos/t3_finance_chart/eras.js +11 -0
- package/engine/demos/t3_finance_chart/t3_s1.js +48 -0
- package/engine/demos/t3_finance_chart/t3_s2.js +42 -0
- package/engine/demos/t3_finance_chart/t3_s3.js +51 -0
- package/engine/demos/y1_kurzgesagt/eras.js +10 -0
- package/engine/demos/y1_kurzgesagt/kurzgesagt.js +347 -0
- package/engine/demos/y2_vox/eras.js +10 -0
- package/engine/demos/y2_vox/vox.js +240 -0
- package/engine/demos/y3_whiteboard/eras.js +10 -0
- package/engine/demos/y3_whiteboard/whiteboard.js +162 -0
- package/engine/demos/y4_storytime/eras.js +14 -0
- package/engine/demos/y4_storytime/s1_desk.js +18 -0
- package/engine/demos/y4_storytime/s2_screen.js +82 -0
- package/engine/demos/y4_storytime/s3_react.js +39 -0
- package/engine/demos/y4_storytime/s4_wide.js +16 -0
- package/engine/demos/y4_storytime/s5_close.js +18 -0
- package/engine/demos/y4_storytime/story.js +25 -0
- package/engine/demos/y4_storytime/vo_env.js +2 -0
- package/engine/demos/y4_storytime/world.js +73 -0
- package/engine/demos/y5_kinetic_type/eras.js +16 -0
- package/engine/demos/y5_kinetic_type/k1_title.js +38 -0
- package/engine/demos/y5_kinetic_type/k2_complete.js +55 -0
- package/engine/demos/y5_kinetic_type/k3_chat.js +47 -0
- package/engine/demos/y5_kinetic_type/k4_agent.js +49 -0
- package/engine/demos/y5_kinetic_type/kinetic.js +52 -0
- package/engine/engine.js +277 -0
- package/engine/eras.js +60 -0
- package/engine/eras_gallery.js +115 -0
- package/engine/eras_style_studies.js +20 -0
- package/engine/examples/assets/K/347/272/277/345/233/276_/346/250/252/345/261/217.png +0 -0
- package/engine/examples/assets//347/273/210/347/253/257_for/345/276/252/347/216/257/346/261/202/345/222/214.png +0 -0
- package/engine/examples/assets//347/273/210/347/253/257_sum/346/261/202/345/222/214.png +0 -0
- package/engine/examples/assets//350/220/245/346/224/266/345/233/276_/347/253/226/345/261/217.png +0 -0
- package/engine/examples/assets//350/264/242/347/273/217/345/233/276/350/241/250_/346/250/252/345/261/217.png +0 -0
- package/engine/examples/t1_3b1b.json +19 -0
- package/engine/examples/t2_fullbleed_pulse.json +15 -0
- package/engine/examples/t2_keynote_ui.json +15 -0
- package/engine/examples/t3_finance_chart.json +29 -0
- package/engine/examples/y1_kurzgesagt.json +16 -0
- package/engine/examples/y2_vox.json +14 -0
- package/engine/examples/y3_whiteboard.json +17 -0
- package/engine/examples/y4_storytime.json +52 -0
- package/engine/examples/y5_kinetic_type.json +14 -0
- package/engine/index.html +42 -0
- package/engine/lib/THREE_LICENSE.txt +21 -0
- package/engine/lib/brush.js +149 -0
- package/engine/lib/camera.js +99 -0
- package/engine/lib/chart.js +152 -0
- package/engine/lib/collage.js +97 -0
- package/engine/lib/diagram.js +264 -0
- package/engine/lib/finish.js +70 -0
- package/engine/lib/fonts/AlfaSlabOne-400.woff +0 -0
- package/engine/lib/fonts/Anton-400.woff +0 -0
- package/engine/lib/fonts/ArchivoBlack-400.woff +0 -0
- package/engine/lib/fonts/Bangers-400.woff +0 -0
- package/engine/lib/fonts/Caveat-600.woff +0 -0
- package/engine/lib/fonts/Cinzel-400.woff +0 -0
- package/engine/lib/fonts/Cinzel-700.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-500.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-500i.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-700.woff +0 -0
- package/engine/lib/fonts/EBGaramond-400.woff +0 -0
- package/engine/lib/fonts/EBGaramond-400i.woff +0 -0
- package/engine/lib/fonts/EBGaramond-600.woff +0 -0
- package/engine/lib/fonts/Exo2-800i.woff +0 -0
- package/engine/lib/fonts/GochiHand-400.woff +0 -0
- package/engine/lib/fonts/IMFellEnglish-400.woff +0 -0
- package/engine/lib/fonts/IMFellEnglish-400i.woff +0 -0
- package/engine/lib/fonts/Inter-var.woff +0 -0
- package/engine/lib/fonts/JosefinSans-700.woff +0 -0
- package/engine/lib/fonts/Kalam-700.woff +0 -0
- package/engine/lib/fonts/LICENSES.md +40 -0
- package/engine/lib/fonts/LXGWWenKai-500.woff +0 -0
- package/engine/lib/fonts/LibreBaskerville-400.woff +0 -0
- package/engine/lib/fonts/LibreBaskerville-700.woff +0 -0
- package/engine/lib/fonts/LilitaOne-400.woff +0 -0
- package/engine/lib/fonts/Marcellus-400.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-500.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-700.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-800.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-900.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-sub.woff +0 -0
- package/engine/lib/fonts/NotoSerifJP-600.woff +0 -0
- package/engine/lib/fonts/NotoSerifSC-sub.woff +0 -0
- package/engine/lib/fonts/OFL.txt +94 -0
- package/engine/lib/fonts/PlayfairDisplay-400i.woff +0 -0
- package/engine/lib/fonts/PlayfairDisplay-700.woff +0 -0
- package/engine/lib/fonts/PlayfairDisplay-700i.woff +0 -0
- package/engine/lib/fonts/Poppins-600.woff +0 -0
- package/engine/lib/fonts/Poppins-700.woff +0 -0
- package/engine/lib/fonts/Poppins-800.woff +0 -0
- package/engine/lib/fonts/PressStart2P-400.woff +0 -0
- package/engine/lib/fonts/Righteous-400.woff +0 -0
- package/engine/lib/fonts/RobotoCondensed-var.woff +0 -0
- package/engine/lib/fonts/Rye-400.woff +0 -0
- package/engine/lib/fonts/UnifrakturMaguntia-400.woff +0 -0
- package/engine/lib/fonts/cmunrm.woff +0 -0
- package/engine/lib/fonts/cmunti.woff +0 -0
- package/engine/lib/fonts/index.json +1 -0
- package/engine/lib/fonts.js +56 -0
- package/engine/lib/kit.js +91 -0
- package/engine/lib/media.js +118 -0
- package/engine/lib/medium.js +258 -0
- package/engine/lib/motion.js +111 -0
- package/engine/lib/paint.js +200 -0
- package/engine/lib/palettes.js +57 -0
- package/engine/lib/post.js +108 -0
- package/engine/lib/render.js +189 -0
- package/engine/lib/rig.js +390 -0
- package/engine/lib/rig_huashu.js +171 -0
- package/engine/lib/three.min.js +7 -0
- package/engine/lib/toon.js +186 -0
- package/engine/lib/typo.js +136 -0
- package/engine/lib/ui.js +141 -0
- package/engine/lib/util.js +92 -0
- package/engine/render.py +157 -0
- package/engine/scenes/01_cave.js +364 -0
- package/engine/scenes/02_egypt.js +524 -0
- package/engine/scenes/03_greek.js +448 -0
- package/engine/scenes/04_roman.js +555 -0
- package/engine/scenes/05_gothic.js +522 -0
- package/engine/scenes/06_renaissance.js +338 -0
- package/engine/scenes/08_impressionism.js +206 -0
- package/engine/scenes/09_postimp.js +183 -0
- package/engine/scenes/10_nouveau.js +334 -0
- package/engine/scenes/11_cubism.js +265 -0
- package/engine/scenes/12_bauhaus.js +214 -0
- package/engine/scenes/13_pop.js +269 -0
- package/engine/scenes/14_8bit.js +238 -0
- package/engine/scenes/15_raytrace.js +300 -0
- package/engine/scenes/16_2026.js +565 -0
- package/engine/scenes/17_ink.js +273 -0
- package/engine/scenes/18_klimt.js +227 -0
- package/engine/scenes/19_munch.js +153 -0
- package/engine/scenes/20_dunhuang.js +215 -0
- package/engine/scenes/21_kusama.js +124 -0
- package/engine/scenes/22_constructivism.js +205 -0
- package/engine/scenes/23_dali.js +221 -0
- package/engine/scenes/24_hopper.js +150 -0
- package/engine/scenes/25_ghibli.js +251 -0
- package/engine/scenes/26_vaporwave.js +191 -0
- package/engine/scenes/27_kirby.js +255 -0
- package/engine/scenes/28_monet.js +176 -0
- package/engine/scenes/29_seurat.js +104 -0
- package/engine/scenes/30_matisse.js +151 -0
- package/engine/scenes/31_haring.js +109 -0
- package/engine/scenes/32_rembrandt.js +217 -0
- package/engine/scenes/33_rubberhose.js +219 -0
- package/engine/scenes/34_shadowpuppet.js +195 -0
- package/engine/scenes/35_shinkai.js +197 -0
- package/engine/scenes/36_picasso_blue.js +123 -0
- package/engine/scenes/38_giotto.js +133 -0
- package/engine/scenes/39_piero.js +53 -0
- package/engine/scenes/40_raphael.js +96 -0
- package/engine/scenes/41_michelangelo.js +112 -0
- package/engine/scenes/42_guardi.js +70 -0
- package/engine/scenes/43_lorenzetti.js +145 -0
- package/engine/scenes/44_cortona.js +86 -0
- package/engine/scenes/45_renoir.js +86 -0
- package/engine/scenes/46_pissarro.js +82 -0
- package/engine/scenes/47_sisley.js +94 -0
- package/engine/scenes/48_gauguin.js +107 -0
- package/engine/scenes/49_chagall.js +60 -0
- package/engine/scenes/50_ma_yuan.js +131 -0
- package/engine/scenes/51_persian_miniature.js +149 -0
- package/engine/scenes/52_south_park.js +105 -0
- package/engine/scenes/53_pixar.js +60 -0
- package/engine/scenes/54_ghibli_lab.js +68 -0
- package/engine/scenes/55_dunhuang.js +161 -0
- package/engine/scenes/index.js +28 -0
- package/engine/spec_assets.py +76 -0
- package/engine/transitions.js +929 -0
- package/engine//346/227/266/344/273/243/344/275/234/350/200/205/350/247/204/350/214/203.md +49 -0
- package/lib/host.mjs +754 -0
- package/lib/render-bridge.mjs +543 -0
- package/lib/render-cli.mjs +98 -0
- package/lib/storyboard-lint.mjs +54 -0
- package/manifest.json +52 -0
- package/package.json +8 -4
- package/runtime/package.json +8 -0
- package/skills/biuart/SKILL.md +154 -0
- package/skills/biuart/references/01-/346/213/206/350/247/243.md +55 -0
- package/skills/biuart/references/02-/346/234/272/345/210/266.md +41 -0
- package/skills/biuart/references/03-/344/270/200/345/270/247/345/205/210/350/241/214/345/233/233/346/235/241/350/267/257/347/272/277.md +39 -0
- package/skills/biuart/references/04-/347/272/257/344/273/243/347/240/201/347/273/230/345/210/266.md +92 -0
- package/skills/biuart/references/05-/350/212/202/345/245/217/344/270/216/351/205/215/344/271/220.md +19 -0
- package/skills/biuart/references/06-/345/217/243/346/222/255/351/251/261/345/212/250/347/232/204/350/211/272/346/234/257/347/237/255/347/211/207.md +31 -0
- package/skills/biuart/references/07-/346/255/243/351/235/242/347/273/217/351/252/214.md +196 -0
- package/skills/biuart/references/08-/351/243/216/346/240/274/344/275/234/350/200/205/350/247/204/350/214/203.md +61 -0
- package/skills/biuart/references/09-/350/247/206/351/242/221/345/212/250/347/224/273/350/257/255/346/263/225.md +382 -0
- package/skills/biuart/references/10-/350/247/222/350/211/262.md +34 -0
- package/skills/biuart/references/11-/351/225/277/345/215/267/347/251/277/350/266/212/347/211/207.md +43 -0
- package/skills/biuart/references/12-/345/217/243/346/222/255/346/225/264/347/211/207/344/270/216/347/273/217/351/252/214/345/233/236/346/265/201.md +25 -0
- package/skills/biuart/references/13-/345/217/243/346/222/255/344/270/216/350/257/255/351/237/263/345/244/215/345/210/273.md +7 -0
- package/skills/biuart/references/15-/350/267/250/345/234/272/346/231/257/351/243/216/346/240/274/346/217/220/347/202/274.md +48 -0
- package/skills/biuart/references/16-/351/243/216/346/240/274/346/274/224/347/244/27295/345/210/206/345/215/217/350/256/256.md +103 -0
- package/skills/biuart/references/about-author.md +99 -0
- package/skills/biuart/references/capabilities.md +91 -0
- package/skills/biuart/references/compatibility.md +34 -0
- package/skills/biuart/references/images.md +97 -0
- package/skills/biuart/references/voice-model-review.md +18 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t1_3b1b.md +122 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t2_keynote_ui.md +117 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t3_finance_chart.md +116 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y1_kurzgesagt.md +193 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y2_vox.md +195 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y3_whiteboard.md +190 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y4_storytime.md +166 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y5_kinetic_type.md +158 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y6_presenter_explainer.md +24 -0
- package/skills/biuart/references//345/217/243/346/222/255API.md +125 -0
- package/skills/biuart/references//350/211/262/346/235/277.md +38 -0
- package/skills/biuart/references//350/247/222/350/211/262/347/262/276/347/273/206/345/272/246.md +61 -0
- package/skills/biuart/references//351/225/234/345/244/264/350/241/250/346/250/241/346/235/277.md +80 -0
- package/skills/biuart/references//351/225/234/345/244/264/350/257/255/350/250/200.md +73 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/01_cave.md +46 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/02_egypt.md +64 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/03_greek.md +50 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/04_roman.md +99 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/05_gothic.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/06_renaissance.md +41 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/08_impressionism.md +34 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/09_postimp.md +17 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/10_nouveau.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/11_cubism.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/12_bauhaus.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/13_pop.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/14_8bit.md +27 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/15_raytrace.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/16_2026.md +55 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/17_ink.md +50 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/18_klimt.md +37 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/19_munch.md +42 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/20_dunhuang.md +49 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/21_kusama.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/22_constructivism.md +35 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/23_dali.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/24_hopper.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/25_ghibli.md +55 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/26_vaporwave.md +27 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/27_kirby.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/28_monet.md +39 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/29_seurat.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/30_matisse.md +26 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/31_haring.md +22 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/32_rembrandt.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/33_rubberhose.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/34_shadowpuppet.md +40 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/35_shinkai.md +34 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/36_picasso_blue.md +25 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/37_rick_morty.md +48 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/38_giotto.md +67 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/39_piero.md +59 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/40_raphael.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/41_michelangelo.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/42_guardi.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/43_lorenzetti.md +69 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/44_cortona.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/45_renoir.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/46_pissarro.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/47_sisley.md +67 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/48_gauguin.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/49_chagall.md +59 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/50_ma_yuan.md +68 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/51_persian_miniature.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/52_south_park.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/53_pixar.md +61 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/55_dunhuang.md +41 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/INDEX.md +89 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/350/275/254/345/234/272_/350/277/201/347/247/273/346/265/213/350/257/225.md +53 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/207/215/345/201/232/350/277/233/345/272/246.md +37 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/237/263/350/275/250_/350/211/272/346/234/257/345/217/262/351/200/237/351/200/232.md +135 -0
- package/studio/app.js +380 -0
- package/studio/assert-panel-permit.mjs +67 -0
- package/studio/composer.css +465 -0
- package/studio/composer.js +480 -0
- package/studio/index.html +97 -0
- package/studio/optimize-prompt.mjs +84 -0
- package/studio/optimize-prompt.test.mjs +47 -0
- package/studio/recipes.mjs +87 -0
- package/studio/select-menu.js +450 -0
- package/studio/styles.css +71 -0
- package/studio/to-english-prompt.mjs +149 -0
- package/studio/to-english-prompt.test.mjs +72 -0
- package/README.md +0 -3
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// 数据图表动画 CH:比例尺、好看的刻度、网格(先出)、柱(从 0 长出)、折线(按时间画出、头部圆点)、K 线(逐根)、
|
|
2
|
+
// 标注(点→引线→文字)、经济学人式版式(顶红线+红色小旗+标题+单位副标题+来源注+「示意数据」角标)。
|
|
3
|
+
// 顺序铁律(准确性+可读性):坐标系先出 → 数据后出 → 标注最后、一镜只标一件事;柱状图基线必须是 0(纵轴不从 0 开始要在副标题写明);
|
|
4
|
+
// 动画只改「怎么出现」,不改数值;单位写在副标题;虚构/示意数据每一镜角落都标。
|
|
5
|
+
(() => {
|
|
6
|
+
let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
|
|
7
|
+
const { clamp, lerp } = U;
|
|
8
|
+
const CH = window.CH = {};
|
|
9
|
+
const seg = (t, a, b) => clamp((t - a) / (b - a));
|
|
10
|
+
|
|
11
|
+
// 线性比例尺:值 → 屏幕;f.inv 反查
|
|
12
|
+
CH.lin = (d0, d1, r0, r1) => { const f = v => r0 + (v - d0) / (d1 - d0) * (r1 - r0); f.inv = y => d0 + (y - r0) / (r1 - r0) * (d1 - d0); return f; };
|
|
13
|
+
// 1/2/2.5/5×10^k 的好看刻度
|
|
14
|
+
CH.ticks = (min, max, n = 5) => {
|
|
15
|
+
const raw = (max - min) / n, mag = Math.pow(10, Math.floor(Math.log10(raw))), steps = [1, 2, 2.5, 5, 10];
|
|
16
|
+
const step = steps.map(s => s * mag).find(s => s >= raw) || 10 * mag;
|
|
17
|
+
const out = []; for (let v = Math.ceil(min / step) * step; v <= max + 1e-9; v += step) out.push(+v.toFixed(10)); return out;
|
|
18
|
+
};
|
|
19
|
+
CH.mixHex = (a, b, t) => { const A = PAINT.hex(a), B = PAINT.hex(b); return `rgb(${lerp(A[0], B[0], t) | 0},${lerp(A[1], B[1], t) | 0},${lerp(A[2], B[2], t) | 0})`; };
|
|
20
|
+
|
|
21
|
+
// ---------- 网格:水平线从左往右逐条画出(lag 错开),零线更深更粗;刻度值跟着淡入 ----------
|
|
22
|
+
// F = {x,y,w,h} 绘图区;ys = 刻度值;yS = 值→屏幕 y;p = 0..1
|
|
23
|
+
CH.grid = (c, F, ys, yS, p, { col = 'rgba(0,0,0,.14)', zeroCol = '#121212', lw = 1.5, zeroLw = 3, lag = 0.12, font = '28px sans-serif', labelCol = '#5b5b5b', fmt = v => String(v), side = 'right', labelDy = -10 } = {}) => {
|
|
24
|
+
c.save(); c.font = font; c.textBaseline = 'alphabetic';
|
|
25
|
+
ys.forEach((v, i) => {
|
|
26
|
+
const q = MO.lagged(i, ys.length, p, lag), e = MO.quintOut(q); if (q <= 0) return;
|
|
27
|
+
const y = Math.round(yS(v)) + 0.5, isZero = Math.abs(v) < 1e-9;
|
|
28
|
+
c.strokeStyle = isZero ? zeroCol : col; c.lineWidth = isZero ? zeroLw : lw;
|
|
29
|
+
c.beginPath(); c.moveTo(F.x, y); c.lineTo(F.x + F.w * e, y); c.stroke();
|
|
30
|
+
c.globalAlpha = seg(q, 0.3, 1); c.fillStyle = labelCol;
|
|
31
|
+
if (side === 'right') { c.textAlign = 'right'; c.fillText(fmt(v), F.x + F.w, y + labelDy); }
|
|
32
|
+
else { c.textAlign = 'left'; c.fillText(fmt(v), F.x, y + labelDy); }
|
|
33
|
+
c.globalAlpha = 1;
|
|
34
|
+
});
|
|
35
|
+
c.restore();
|
|
36
|
+
};
|
|
37
|
+
// x 轴标签(年份等):逐个淡入上移。items = [{x, label}]
|
|
38
|
+
CH.xLabels = (c, items, y, p, { font = '28px sans-serif', col = '#3a3a3a', lag = 0.1 } = {}) => {
|
|
39
|
+
c.save(); c.font = font; c.textAlign = 'center'; c.fillStyle = col;
|
|
40
|
+
items.forEach((it, i) => { const q = MO.quintOut(MO.lagged(i, items.length, p, lag)); if (q <= 0) return; c.globalAlpha = q; c.fillText(it.label, it.x, y + (1 - q) * 14); });
|
|
41
|
+
c.restore();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------- 柱:从基线 0 长出(quintOut),数值标签与柱顶同步计数(等宽数字) ----------
|
|
45
|
+
// data: [{x, v}];bw 柱宽;p 0..1 总进度;hi 高亮下标(其余按 hiP 过渡到 dimCol)。返回各柱顶 [[x,y]]
|
|
46
|
+
CH.bars = (c, data, yS, bw, p, { lag = 0.18, col = '#1f5c99', hi = -1, hiP = 0, hiCol, dimCol = '#b9c3cc', label = true, fmt = v => v.toFixed(0), font = '30px sans-serif', labelCol = '#121212' } = {}) => {
|
|
47
|
+
const y0 = yS(0), tops = [];
|
|
48
|
+
data.forEach((d, i) => {
|
|
49
|
+
const q = MO.lagged(i, data.length, p, lag), e = MO.quintOut(q);
|
|
50
|
+
const v = d.v * e, y = yS(v);
|
|
51
|
+
let fill = col;
|
|
52
|
+
if (hi >= 0) fill = i === hi ? (hiCol || col) : CH.mixHex(col, dimCol, hiP);
|
|
53
|
+
c.fillStyle = fill; c.fillRect(d.x - bw / 2, y, bw, y0 - y);
|
|
54
|
+
tops.push([d.x, y]);
|
|
55
|
+
if (label && q > 0) {
|
|
56
|
+
c.save(); c.font = font; c.textAlign = 'center'; c.fillStyle = labelCol; c.globalAlpha = seg(q, 0.05, 0.4);
|
|
57
|
+
if (d.v < 0) { c.textBaseline = 'top'; TY.tabular(c, fmt(v), d.x, y + 12, { align: 'center' }); } // 负数柱:数值写在柱底下方,别写进柱子里
|
|
58
|
+
else TY.tabular(c, fmt(v), d.x, y - 14, { align: 'center' });
|
|
59
|
+
c.restore();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return tops;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ---------- 折线:按 x(时间)推进画出;头部圆点;可选面积渐变;返回头部点 ----------
|
|
66
|
+
// pts: 屏幕坐标(x 递增);p: 0..1 = 画到 x 范围的哪里。area = {top, base, c0, c1}
|
|
67
|
+
CH.line = (c, pts, p, { col = '#e3120b', lw = 5, head = true, headR = 9, area, glow } = {}) => {
|
|
68
|
+
if (p <= 0) return null;
|
|
69
|
+
const xEnd = lerp(pts[0][0], pts[pts.length - 1][0], clamp(p));
|
|
70
|
+
const vis = [pts[0]];
|
|
71
|
+
for (let i = 1; i < pts.length; i++) {
|
|
72
|
+
if (pts[i][0] <= xEnd) vis.push(pts[i]);
|
|
73
|
+
else { const a = pts[i - 1], b = pts[i], q = (xEnd - a[0]) / (b[0] - a[0]); vis.push([xEnd, lerp(a[1], b[1], q)]); break; }
|
|
74
|
+
}
|
|
75
|
+
const hp = vis[vis.length - 1];
|
|
76
|
+
if (area) {
|
|
77
|
+
const g = c.createLinearGradient(0, area.top, 0, area.base); g.addColorStop(0, area.c0); g.addColorStop(1, area.c1);
|
|
78
|
+
c.save(); c.beginPath(); c.moveTo(vis[0][0], area.base); vis.forEach(q => c.lineTo(q[0], q[1])); c.lineTo(hp[0], area.base); c.closePath(); c.fillStyle = g; c.fill(); c.restore();
|
|
79
|
+
}
|
|
80
|
+
c.save(); c.strokeStyle = col; c.lineWidth = lw; c.lineJoin = 'round'; c.lineCap = 'round';
|
|
81
|
+
if (glow) { c.shadowColor = col; c.shadowBlur = glow; }
|
|
82
|
+
c.beginPath(); vis.forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.stroke(); c.restore();
|
|
83
|
+
if (head && p < 1.0001) { c.save(); c.fillStyle = col; c.beginPath(); c.arc(hp[0], hp[1], headR, 0, Math.PI * 2); c.fill();
|
|
84
|
+
c.strokeStyle = '#fff'; c.lineWidth = 3; c.stroke(); c.restore(); }
|
|
85
|
+
return hp;
|
|
86
|
+
};
|
|
87
|
+
// 折线在 x 处的 y(标注要钉在线上)
|
|
88
|
+
CH.yAt = (pts, x) => { for (let i = 1; i < pts.length; i++) if (pts[i][0] >= x) { const a = pts[i - 1], b = pts[i]; return lerp(a[1], b[1], (x - a[0]) / (b[0] - a[0])); } return pts[pts.length - 1][1]; };
|
|
89
|
+
|
|
90
|
+
// ---------- K 线:逐根出现,实体从开盘价长到收盘价,影线随后;默认红涨绿跌(A 股惯例,海外片换成绿涨红跌并在副标题写明) ----------
|
|
91
|
+
// rows: [{o,h,l,c}];xs: 每根中心 x;cw 实体宽;p 0..1
|
|
92
|
+
CH.candles = (c, rows, xs, yS, cw, p, { up = '#e0322f', down = '#11a05a', lag = 0.06, hollowUp = false } = {}) => {
|
|
93
|
+
rows.forEach((r, i) => {
|
|
94
|
+
const q = MO.lagged(i, rows.length, p, lag); if (q <= 0) return;
|
|
95
|
+
const e = MO.quintOut(seg(q, 0, 0.7)), w = MO.quintOut(seg(q, 0.35, 1));
|
|
96
|
+
const isUp = r.c >= r.o, col = isUp ? up : down, x = xs[i];
|
|
97
|
+
const yo = yS(r.o), yc = lerp(yo, yS(r.c), e);
|
|
98
|
+
c.save(); c.strokeStyle = col; c.fillStyle = col; c.lineWidth = 2.5;
|
|
99
|
+
if (w > 0) { const ym = (yo + yS(r.c)) / 2; c.beginPath(); c.moveTo(x, lerp(ym, yS(r.h), w)); c.lineTo(x, lerp(ym, yS(r.l), w)); c.stroke(); }
|
|
100
|
+
const top = Math.min(yo, yc), hgt = Math.max(2, Math.abs(yc - yo));
|
|
101
|
+
if (isUp && hollowUp) { c.fillStyle = '#fff'; c.fillRect(x - cw / 2, top, cw, hgt); c.strokeRect(x - cw / 2, top, cw, hgt); }
|
|
102
|
+
else c.fillRect(x - cw / 2, top, cw, hgt);
|
|
103
|
+
c.restore();
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ---------- 标注:圆点弹出(弹簧)→ 脉冲环 → 引线画出 → 文字淡入。时间用秒,便于对口播 ----------
|
|
108
|
+
// lt 秒;t0 开始时刻;(x,y) 数据点;(tx,ty) 文字锚点;lines 第一行用 font、其余用 font2(数字先说,比较后说)
|
|
109
|
+
CH.callout = (c, lt, t0, { x, y, tx, ty, lines = [], col = '#121212', dotCol = '#e3120b', font = '600 30px sans-serif', font2, align = 'left', r = 11 }) => {
|
|
110
|
+
const k = lt - t0; if (k <= 0) return;
|
|
111
|
+
const s = MO.spring(k, { duration: 0.45, bounce: 0.25 });
|
|
112
|
+
c.save();
|
|
113
|
+
c.fillStyle = dotCol; c.beginPath(); c.arc(x, y, r * Math.max(0, s), 0, Math.PI * 2); c.fill();
|
|
114
|
+
const pr = seg(k, 0, 0.6); if (pr > 0 && pr < 1) { c.strokeStyle = dotCol; c.globalAlpha = 1 - pr; c.lineWidth = 3; c.beginPath(); c.arc(x, y, r + pr * 34, 0, Math.PI * 2); c.stroke(); c.globalAlpha = 1; }
|
|
115
|
+
const lp = MO.quintOut(seg(k, 0.12, 0.45));
|
|
116
|
+
if (lp > 0) { c.strokeStyle = col; c.lineWidth = 2; c.beginPath(); c.moveTo(x, y); c.lineTo(lerp(x, tx, lp), lerp(y, ty, lp)); c.stroke(); }
|
|
117
|
+
const fp = MO.quintOut(seg(k, 0.3, 0.7));
|
|
118
|
+
if (fp > 0) {
|
|
119
|
+
c.globalAlpha = fp; c.fillStyle = col; c.textAlign = align; c.textBaseline = 'alphabetic';
|
|
120
|
+
const dx = align === 'left' ? 12 : align === 'right' ? -12 : 0;
|
|
121
|
+
lines.forEach((L, i) => { c.font = i === 0 ? font : (font2 || font); c.fillText(L, tx + dx + (1 - fp) * (align === 'right' ? 16 : -16), ty + 8 + i * 40); });
|
|
122
|
+
}
|
|
123
|
+
c.restore();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ---------- 版式 ----------
|
|
127
|
+
CH.tag = (c, x, y, w = 64, h = 14, col = '#e3120b') => { c.fillStyle = col; c.fillRect(x, y, w, h); };
|
|
128
|
+
// 「示意数据」角标:右下圆角框。虚构数据每一镜都要有
|
|
129
|
+
CH.demoBadge = (c, text = '示意数据', { x = W - 40, y = 1040, font = '600 24px sans-serif', col = '#7a7a7a', box = 'rgba(0,0,0,.06)' } = {}) => {
|
|
130
|
+
c.save(); c.font = font; c.textAlign = 'right'; c.textBaseline = 'middle';
|
|
131
|
+
const w = c.measureText(text).width + 28; c.fillStyle = box; c.beginPath(); c.roundRect(x - w, y - 22, w, 44, 8); c.fill();
|
|
132
|
+
c.strokeStyle = col; c.globalAlpha = 0.5; c.lineWidth = 1.5; c.stroke(); c.globalAlpha = 1;
|
|
133
|
+
c.fillStyle = col; c.fillText(text, x - 14, y + 1); c.restore();
|
|
134
|
+
};
|
|
135
|
+
// 经济学人式整页版式(每镜都画):白底 → 顶部细红线从左画出 → 左上红色小旗 → 标题 → 副标题(单位/口径)→ 左下来源 → 右下「示意数据」。
|
|
136
|
+
// lt = 本镜时间(版式 0.6s 内全部到位,之后才出坐标系)。badge 传 false 不画角标(真实数据时)。
|
|
137
|
+
CH.frame = (c, lt, { title, sub, source = '来源:示意数据', badge = '示意数据', bg = '#FFFFFF', red = '#E3120B', ink = '#0C0C0C', subCol = '#4F5B61', srcCol = '#7c868b',
|
|
138
|
+
font = '"NotoSansSC"', x = 90 } = {}) => {
|
|
139
|
+
c.fillStyle = bg; c.fillRect(0, 0, W, H);
|
|
140
|
+
const p = MO.quintOut(MO.seg(lt, 0, 0.45));
|
|
141
|
+
c.fillStyle = red; c.fillRect(0, 0, W * p, 8);
|
|
142
|
+
CH.tag(c, x, 56, 96 * p, 22, red);
|
|
143
|
+
c.save(); c.textBaseline = 'alphabetic'; c.textAlign = 'left';
|
|
144
|
+
const tp = MO.quintOut(MO.seg(lt, 0.05, 0.5));
|
|
145
|
+
c.globalAlpha = tp; c.font = `700 58px ${font}`; c.fillStyle = ink; c.fillText(title, x, 150 + (1 - tp) * 18);
|
|
146
|
+
const sp = MO.quintOut(MO.seg(lt, 0.15, 0.6));
|
|
147
|
+
c.globalAlpha = sp; c.font = `400 34px ${font}`; c.fillStyle = subCol; c.fillText(sub, x, 205 + (1 - sp) * 14);
|
|
148
|
+
c.globalAlpha = 1; c.font = `400 26px ${font}`; c.fillStyle = srcCol; c.fillText(source, x, 1040);
|
|
149
|
+
c.restore();
|
|
150
|
+
if (badge) CH.demoBadge(c, badge, { x: W - 60, y: 1030, font: `700 28px ${font}`, col: red, box: 'rgba(227,18,11,0.06)' });
|
|
151
|
+
};
|
|
152
|
+
})();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// 拼贴材料 CL(Vox / Missing Chapter / Borders 一路):纸纹、撕纸边、网点照片、胶带、图钉、红线(下垂的线索绳)、
|
|
2
|
+
// 毛边荧光笔(multiply)、轻投影、带倾斜滑入放平、剪纸贴纸白边。
|
|
3
|
+
// 节奏铁律(y2 调研):相机 60fps 平滑;桌上的纸片/荧光笔/红笔/打字按 12fps「一拍二」步进(MO.step)——stop-motion 手感的一半。
|
|
4
|
+
// 纸纹是贴死的(静态颗粒,不逐帧抖)。素材一次画好走 PAINT.cached,确定性。
|
|
5
|
+
(() => {
|
|
6
|
+
let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
|
|
7
|
+
const { clamp, lerp, rng } = U;
|
|
8
|
+
const CL = window.CL = {};
|
|
9
|
+
|
|
10
|
+
// ---------- 纸 ----------
|
|
11
|
+
// 可平铺纸纹 tile:环面取样的 fbm 明暗(无缝)+纤维短线+细颗粒。用 c.createPattern(tile, 'repeat') 铺
|
|
12
|
+
CL.paperTile = (key, base, { size = 512, amt = 10, fibers = 260, grain = 10, fiberCol = [120, 100, 70], seed = 3 } = {}) => PAINT.cached('ytpaper_' + key, size, size, (g) => {
|
|
13
|
+
const img = g.createImageData(size, size), d = img.data, b = PAINT.hex(base), r = rng(seed);
|
|
14
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
15
|
+
const ax = x / size * Math.PI * 2, ay = y / size * Math.PI * 2;
|
|
16
|
+
const n = PAINT.fbm(Math.cos(ax) * 1.6 + 10, Math.sin(ax) * 1.6 + Math.cos(ay) * 1.6, 4) * 0.6 + PAINT.noise(Math.sin(ay) * 3 + 5, Math.cos(ay) * 3) * 0.4;
|
|
17
|
+
const gr = (r() - .5) * grain, i = (y * size + x) * 4;
|
|
18
|
+
d[i] = b[0] + n * amt + gr; d[i + 1] = b[1] + n * amt + gr; d[i + 2] = b[2] + n * amt * 0.9 + gr; d[i + 3] = 255;
|
|
19
|
+
}
|
|
20
|
+
g.putImageData(img, 0, 0);
|
|
21
|
+
g.lineCap = 'round';
|
|
22
|
+
for (let k = 0; k < fibers; k++) { const x = r() * size, y = r() * size, a = r() * Math.PI, l = 4 + r() * 14;
|
|
23
|
+
g.strokeStyle = PAINT.rgb(fiberCol, 0.05 + r() * 0.08); g.lineWidth = 0.6 + r() * 0.8;
|
|
24
|
+
for (const ox of [0, -size, size]) for (const oy of [0, -size, size]) { g.beginPath(); g.moveTo(x + ox, y + oy); g.quadraticCurveTo(x + ox + Math.cos(a + 1) * l * 0.5, y + oy + Math.sin(a + 1) * l * 0.5, x + ox + Math.cos(a) * l, y + oy + Math.sin(a) * l); g.stroke(); } }
|
|
25
|
+
});
|
|
26
|
+
CL.fillPaper = (c, tile, x, y, w, h) => { c.save(); c.fillStyle = c.createPattern(tile, 'repeat'); c.fillRect(x, y, w, h); c.restore(); };
|
|
27
|
+
// 撕纸边:沿一条线生成锯齿+纤维的点列(左→右),amp 起伏、seed 固定
|
|
28
|
+
CL.tornEdge = (x0, y0, x1, y1, { step = 9, amp = 10, seed = 5 } = {}) => {
|
|
29
|
+
const r = rng(seed), L = Math.hypot(x1 - x0, y1 - y0), n = Math.max(2, Math.round(L / step)), nx = -(y1 - y0) / L, ny = (x1 - x0) / L, o = [];
|
|
30
|
+
for (let i = 0; i <= n; i++) { const q = i / n, k = PAINT.noise(i * 0.18, seed) * amp + (r() - .5) * amp * 0.9; o.push([lerp(x0, x1, q) + nx * k, lerp(y0, y1, q) + ny * k]); }
|
|
31
|
+
return o;
|
|
32
|
+
};
|
|
33
|
+
// 撕边矩形 (0,0,w,h):只建路径(beginPath…closePath),调用方 fill / clip
|
|
34
|
+
CL.tornRect = (g, w, h, seed, amp = 5) => {
|
|
35
|
+
const pts = [...CL.tornEdge(0, 0, w, 0, { amp, seed }), ...CL.tornEdge(w, 0, w, h, { amp, seed: seed + 1 }), ...CL.tornEdge(w, h, 0, h, { amp, seed: seed + 2 }), ...CL.tornEdge(0, h, 0, 0, { amp, seed: seed + 3 })];
|
|
36
|
+
g.beginPath(); pts.forEach((p, i) => i ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); g.closePath();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ---------- 网点照片 ----------
|
|
40
|
+
// 灰度网点:源图(canvas)按亮度转成 45° 网点,cell 像素一格;只留在源图不透明处。结果缓存(key 唯一)
|
|
41
|
+
CL.halftone = (key, src, { cell = 7, ink = '#1b1b1b', paper = '#efe9dc', angle = Math.PI / 4, gamma = 1, contrast = 1.15 } = {}) => PAINT.cached('ytht_' + key, src.width, src.height, (g) => {
|
|
42
|
+
const w = src.width, h = src.height, sd = src.getContext('2d').getImageData(0, 0, w, h).data;
|
|
43
|
+
g.fillStyle = paper; g.fillRect(0, 0, w, h); g.fillStyle = ink;
|
|
44
|
+
const ca = Math.cos(angle), sa = Math.sin(angle), R = Math.hypot(w, h);
|
|
45
|
+
g.beginPath();
|
|
46
|
+
for (let v = -R; v < R; v += cell) for (let u = -R; u < R; u += cell) {
|
|
47
|
+
const x = w / 2 + u * ca - v * sa, y = h / 2 + u * sa + v * ca; if (x < -cell || y < -cell || x > w + cell || y > h + cell) continue;
|
|
48
|
+
const ix = clamp(x | 0, 0, w - 1), iy = clamp(y | 0, 0, h - 1), i = (iy * w + ix) * 4;
|
|
49
|
+
if (sd[i + 3] < 10) continue;
|
|
50
|
+
let L = (sd[i] * .3 + sd[i + 1] * .59 + sd[i + 2] * .11) / 255; L = clamp((L - 0.5) * contrast + 0.5); L = Math.pow(L, gamma);
|
|
51
|
+
const rr = cell * 0.62 * Math.sqrt(1 - L); if (rr < 0.4) continue;
|
|
52
|
+
g.moveTo(x + rr, y); g.arc(x, y, rr, 0, Math.PI * 2);
|
|
53
|
+
}
|
|
54
|
+
g.fill();
|
|
55
|
+
g.globalCompositeOperation = 'destination-in'; g.drawImage(src, 0, 0); g.globalCompositeOperation = 'source-over';
|
|
56
|
+
});
|
|
57
|
+
// 网点照片贴纸:draw(g, w, h) 画源图 → 网点 → 加 border 宽的纸边、一层旧纸色(multiply)。返回缓存 canvas(尺寸 w+2b × h+2b)
|
|
58
|
+
CL.photo = (key, w, h, draw, { border = 14, cell = 6, paper = '#f2f0ea', ink = '#1a1a1a', htPaper = '#e8e4da', contrast = 1.3 } = {}) => PAINT.cached('clphoto_' + key, w + border * 2, h + border * 2, (g) => {
|
|
59
|
+
const src = PAINT.canvas(w, h); draw(src.getContext('2d'), w, h);
|
|
60
|
+
const ht = CL.halftone(key + '_ht', src, { cell, ink, paper: htPaper, contrast });
|
|
61
|
+
g.fillStyle = paper; g.fillRect(0, 0, w + border * 2, h + border * 2); g.drawImage(ht, border, border);
|
|
62
|
+
g.globalCompositeOperation = 'multiply'; g.fillStyle = 'rgba(214,205,185,.3)'; g.fillRect(0, 0, w + border * 2, h + border * 2); g.globalCompositeOperation = 'source-over';
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ---------- 摆放 ----------
|
|
66
|
+
CL.placed = (g, x, y, r, fn) => { g.save(); g.translate(x, y); g.rotate(r); fn(g); g.restore(); };
|
|
67
|
+
// 轻投影(Vox 桌面上留一点就够;黑底卡片几乎无投影)
|
|
68
|
+
CL.shadowed = (g, fn, { col = 'rgba(50,40,25,.28)', blur = 10, x = 3, y = 6 } = {}) => { g.save(); g.shadowColor = col; g.shadowBlur = blur; g.shadowOffsetX = x; g.shadowOffsetY = y; fn(); g.restore(); };
|
|
69
|
+
// 带倾斜滑入、到位放平(MC 实测:滑入带 5–15° 倾斜,约 0.6–0.8s),按 12fps 步进。返回进度 q
|
|
70
|
+
CL.slideIn = (g, t, t0, x, y, r, fn, { from = [420, -60], tilt = 0.2, dur = 0.6, fps = 12 } = {}) => {
|
|
71
|
+
const q = clamp((MO.step(t, fps) - t0) / dur); if (q <= 0) return 0;
|
|
72
|
+
const e = MO.quartOut(q);
|
|
73
|
+
g.save(); g.translate(x + from[0] * (1 - e), y + from[1] * (1 - e)); g.rotate(r + tilt * (1 - Math.min(1, e * 1.15))); fn(g); g.restore(); return q;
|
|
74
|
+
};
|
|
75
|
+
// 胶带:一条半透明撕边纸条
|
|
76
|
+
CL.tape = (g, x, y, a, w = 120, col = 'rgba(226,220,200,.85)') => { g.save(); g.translate(x, y); g.rotate(a); g.fillStyle = col; g.translate(-w / 2, -17); CL.tornRect(g, w, 34, (x * 7 + y) | 0, 3); g.fill(); g.restore(); };
|
|
77
|
+
// 图钉
|
|
78
|
+
CL.pin = (g, x, y, col = '#b8433f') => { g.save(); g.translate(x, y); g.fillStyle = 'rgba(0,0,0,.25)'; g.beginPath(); g.arc(3, 5, 12, 0, 7); g.fill(); g.fillStyle = col; g.beginPath(); g.arc(0, 0, 12, 0, 7); g.fill(); g.fillStyle = 'rgba(255,255,255,.55)'; g.beginPath(); g.arc(-4, -4, 4, 0, 7); g.fill(); g.restore(); };
|
|
79
|
+
// 调查墙的线索绳:a→b 之间按长度 sag 下垂的二次曲线点列(配 DG.cum / DG.drawPartial 逐段描出)
|
|
80
|
+
CL.stringPts = (a, b, sag = 0.08, n = 48) => { const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2 + Math.hypot(b[0] - a[0], b[1] - a[1]) * sag; const o = []; for (let i = 0; i <= n; i++) { const t = i / n, u = 1 - t; o.push([u * u * a[0] + 2 * u * t * mx + t * t * b[0], u * u * a[1] + 2 * u * t * my + t * t * b[1]]); } return o; };
|
|
81
|
+
// 红线:带投影描出前 d 长度(d 用弧长;整根画完传 1e9)
|
|
82
|
+
CL.string = (g, pts, cum, d, { col = '#b8433f', lw = 5, shadow = 'rgba(0,0,0,.16)' } = {}) => {
|
|
83
|
+
if (shadow) { g.save(); g.strokeStyle = shadow; g.lineWidth = lw; g.translate(3, 6); DG.drawPartial(g, pts, cum, d); g.restore(); }
|
|
84
|
+
g.save(); g.strokeStyle = col; g.lineWidth = lw; g.lineCap = 'round'; DG.drawPartial(g, pts, cum, d); g.restore();
|
|
85
|
+
};
|
|
86
|
+
// 毛边荧光笔(multiply,压在印刷字上):q 0..1 从左扫到右
|
|
87
|
+
CL.highlight = (g, x0, y0, w, h, q, col = 'rgba(218,207,8,.9)') => { if (q <= 0) return; g.save(); g.globalCompositeOperation = 'multiply'; g.fillStyle = col;
|
|
88
|
+
g.beginPath(); const ww = w * q; g.moveTo(x0, y0 + 4); for (let i = 0; i <= 20; i++) g.lineTo(x0 + ww * i / 20, y0 + Math.sin(i * 1.7) * 2); g.lineTo(x0 + ww + 6, y0 + h / 2); g.lineTo(x0 + ww, y0 + h);
|
|
89
|
+
for (let i = 20; i >= 0; i--) g.lineTo(x0 + ww * i / 20, y0 + h + Math.sin(i * 2.3) * 2); g.closePath(); g.fill(); g.restore(); };
|
|
90
|
+
// 剪纸贴纸白边:把 src(全屏离屏里的角色/物件)向四周膨胀 border 像素铺 col,返回同尺寸离屏;用法:先画返回值、再画 src
|
|
91
|
+
CL.stickerEdge = (src, key, border = 12, col = '#f4f2ec', dirs = 16) => {
|
|
92
|
+
const sil = PAINT.scratch(key), sg = sil.getContext('2d'); sg.reset();
|
|
93
|
+
for (let a = 0; a < dirs; a++) sg.drawImage(src, Math.cos(a / dirs * 6.283) * border, Math.sin(a / dirs * 6.283) * border);
|
|
94
|
+
sg.globalCompositeOperation = 'source-in'; sg.fillStyle = col; sg.fillRect(0, 0, W, H);
|
|
95
|
+
return sil;
|
|
96
|
+
};
|
|
97
|
+
})();
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// 图解与形变 DG:点列、逐笔描出(draw-on)、揭开遮罩、手绘抖动线、manim 式 Transform / Write、白板笔画时间线+跟随的笔、
|
|
2
|
+
// 神经网络(神经元/连线/信号闪过)、Indicate / Circumscribe / 大括号、3b1b 色板。
|
|
3
|
+
// 约定:点列 pts = [[x,y],...];cum = DG.cum(pts) 是累计弧长;「画到 d」都按弧长算。确定性:随机全走种子。
|
|
4
|
+
(() => {
|
|
5
|
+
let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
|
|
6
|
+
const { clamp, lerp, rng } = U;
|
|
7
|
+
const DG = window.DG = {};
|
|
8
|
+
|
|
9
|
+
// ---------- 点列 ----------
|
|
10
|
+
DG.cum = pts => { const L = [0]; for (let i = 1; i < pts.length; i++) L.push(L[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1])); return L; };
|
|
11
|
+
DG.len = pts => { const L = DG.cum(pts); return L[L.length - 1]; };
|
|
12
|
+
// 折线上弧长 d 处的点
|
|
13
|
+
DG.pointAt = (pts, cum, d) => {
|
|
14
|
+
if (d <= 0) return pts[0]; const n = pts.length; if (d >= cum[n - 1]) return pts[n - 1];
|
|
15
|
+
let lo = 0, hi = n - 1; while (hi - lo > 1) { const m = (lo + hi) >> 1; if (cum[m] < d) lo = m; else hi = m; }
|
|
16
|
+
const q = (d - cum[lo]) / (cum[hi] - cum[lo] || 1); return [lerp(pts[lo][0], pts[hi][0], q), lerp(pts[lo][1], pts[hi][1], q)];
|
|
17
|
+
};
|
|
18
|
+
// 逐笔描出:画折线的前 d 长度(用当前 strokeStyle/lineWidth),返回笔尖位置。白板、红笔圈、红线连接都用它。
|
|
19
|
+
DG.drawPartial = (c, pts, cum, d) => {
|
|
20
|
+
const n = pts.length; if (d <= 0 || n < 2) return pts[0];
|
|
21
|
+
c.beginPath(); c.moveTo(pts[0][0], pts[0][1]);
|
|
22
|
+
let i = 1; for (; i < n && cum[i] <= d; i++) c.lineTo(pts[i][0], pts[i][1]);
|
|
23
|
+
let tip = pts[n - 1];
|
|
24
|
+
if (i < n) { tip = DG.pointAt(pts, cum, d); c.lineTo(tip[0], tip[1]); }
|
|
25
|
+
c.stroke(); return tip;
|
|
26
|
+
};
|
|
27
|
+
// 变换点列:平移 (dx,dy)、缩放 s
|
|
28
|
+
DG.xform = (pts, dx, dy, s = 1) => pts.map(([x, y]) => [dx + x * s, dy + y * s]);
|
|
29
|
+
// 椭圆弧点列(a0→a1)
|
|
30
|
+
DG.arcPts = (cx, cy, rx, ry, a0, a1, n = 24) => { const o = []; for (let i = 0; i <= n; i++) { const a = a0 + (a1 - a0) * i / n; o.push([cx + Math.cos(a) * rx, cy + Math.sin(a) * ry]); } return o; };
|
|
31
|
+
// 手画的圈:从 a0 起转 turns 圈(>1 首尾略交叉,像真的用笔圈)
|
|
32
|
+
DG.ellipsePts = (cx, cy, rx, ry, a0 = -Math.PI / 2, turns = 1.08, n = 64) => DG.arcPts(cx, cy, rx, ry, a0, a0 + turns * Math.PI * 2, n);
|
|
33
|
+
// 手绘抖动:Catmull-Rom 加密后按种子加低频抖动(静态,不沸腾——白板上的线画完就不动)
|
|
34
|
+
DG.hand = (pts, { per = 8, amp = 1.6, seed = 1, freq = 0.035 } = {}) => {
|
|
35
|
+
const d = KIT.densify(pts, per), r = rng(seed), ph = r() * 100;
|
|
36
|
+
return KIT.resample(d, 3).map(([x, y], i) => [x + PAINT.noise(i * freq + ph, 3.1) * amp * 2, y + PAINT.noise(i * freq + ph, 7.7) * amp * 2]);
|
|
37
|
+
};
|
|
38
|
+
// 揭开遮罩:沿折线前 d 长度,每段做成同向绕行的方条(方头延长 w/2),合成一条 Path2D 用来 clip。
|
|
39
|
+
// VideoScribe 的「Reveal」画法:之字形笔触从上往下揭开一张画;也用于逐字书写。同向绕行保证 nonzero 下重叠处不抵消。
|
|
40
|
+
DG.revealMask = (pts, cum, d, w) => {
|
|
41
|
+
const p = new Path2D(), h = w / 2;
|
|
42
|
+
for (let i = 1; i < pts.length && cum[i - 1] < d; i++) {
|
|
43
|
+
const a = pts[i - 1], b0 = pts[i], L = cum[i] - cum[i - 1]; if (L <= 0) continue;
|
|
44
|
+
const q = Math.min(1, (d - cum[i - 1]) / L), b = [a[0] + (b0[0] - a[0]) * q, a[1] + (b0[1] - a[1]) * q];
|
|
45
|
+
const ux = (b0[0] - a[0]) / L, uy = (b0[1] - a[1]) / L, nx = -uy * h, ny = ux * h;
|
|
46
|
+
const A = [a[0] - ux * h, a[1] - uy * h], B = [b[0] + ux * h, b[1] + uy * h];
|
|
47
|
+
p.moveTo(A[0] + nx, A[1] + ny); p.lineTo(B[0] + nx, B[1] + ny); p.lineTo(B[0] - nx, B[1] - ny); p.lineTo(A[0] - nx, A[1] - ny); p.closePath();
|
|
48
|
+
}
|
|
49
|
+
return p;
|
|
50
|
+
};
|
|
51
|
+
// 之字形揭开路径:在框 (x,y,w,h) 里从上往下 rows 行来回
|
|
52
|
+
DG.zigzag = (x, y, w, h, rows) => { const o = []; for (let r = 0; r <= rows; r++) { const yy = y + h * r / rows; o.push(r % 2 ? [x + w, yy] : [x, yy]); } return o; };
|
|
53
|
+
|
|
54
|
+
// ---------- manim Transform:两边点数相同、起点对齐(都从左上方向起、顺时针)才能逐点插值 ----------
|
|
55
|
+
DG.rectPts = (cx, cy, w, h, n = 64) => {
|
|
56
|
+
const per = 2 * (w + h), out = [];
|
|
57
|
+
for (let i = 0; i < n; i++) {
|
|
58
|
+
let d = i / n * per; let x, y;
|
|
59
|
+
if (d < w) { x = cx - w / 2 + d; y = cy - h / 2; }
|
|
60
|
+
else if ((d -= w) < h) { x = cx + w / 2; y = cy - h / 2 + d; }
|
|
61
|
+
else if ((d -= h) < w) { x = cx + w / 2 - d; y = cy + h / 2; }
|
|
62
|
+
else { d -= w; x = cx - w / 2; y = cy + h / 2 - d; }
|
|
63
|
+
out.push([x, y]);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
};
|
|
67
|
+
DG.circlePts = (cx, cy, r, n = 64) => { const out = []; for (let i = 0; i < n; i++) { const a = -3 * Math.PI / 4 + i / n * 2 * Math.PI; out.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); } return out; };
|
|
68
|
+
DG.morph = (A, B, p) => A.map((a, i) => [lerp(a[0], B[i][0], p), lerp(a[1], B[i][1], p)]);
|
|
69
|
+
DG.shape = (c, pts, { fill, stroke, lw = 2 } = {}) => {
|
|
70
|
+
c.beginPath(); pts.forEach((q, i) => i ? c.lineTo(q[0], q[1]) : c.moveTo(q[0], q[1])); c.closePath();
|
|
71
|
+
if (fill) { c.fillStyle = fill; c.fill(); }
|
|
72
|
+
if (stroke && lw > 0) { c.strokeStyle = stroke; c.lineWidth = lw; c.lineJoin = 'round'; c.stroke(); }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ---------- 颜色(接受 '#hex' 或 [r,g,b]) ----------
|
|
76
|
+
DG.arr = h => Array.isArray(h) ? h : PAINT.hex(h);
|
|
77
|
+
DG.mixA = (h1, h2, t) => { const a = DG.arr(h1), b = DG.arr(h2); return [lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t)]; };
|
|
78
|
+
DG.rgba = (h, a = 1) => { const v = DG.arr(h); return `rgba(${v[0] | 0},${v[1] | 0},${v[2] | 0},${a})`; };
|
|
79
|
+
DG.mix = (h1, h2, t) => DG.rgba(DG.mixA(h1, h2, t));
|
|
80
|
+
// 3b1b 色板。Grant 的 custom_config 在 ffmpeg 导出时 saturation=1.5,照 manim 原色值直接用会发灰,所以 DG.MANIM 是饱和后的。
|
|
81
|
+
DG.MANIM_RAW = { BLUE_C: '#58C4DD', BLUE_D: '#29ABCA', BLUE_E: '#1C758A', TEAL_C: '#5CD0B3', GREEN_C: '#83C167', YELLOW: '#FFFF00', GOLD_C: '#F0AC5F',
|
|
82
|
+
RED_C: '#FC6255', MAROON_C: '#C55F73', PURPLE_C: '#9A72AC', GREY_A: '#DDDDDD', GREY_B: '#BBBBBB', GREY_C: '#888888', GREY_D: '#444444', GREY_E: '#222222', WHITE: '#FFFFFF', BLACK: '#000000' };
|
|
83
|
+
DG.saturate = (hex, s = 1.5) => { // CSS/SVG saturate 矩阵
|
|
84
|
+
const [r, g, b] = PAINT.hex(hex);
|
|
85
|
+
const R = (0.213 + 0.787 * s) * r + (0.715 - 0.715 * s) * g + (0.072 - 0.072 * s) * b;
|
|
86
|
+
const G = (0.213 - 0.213 * s) * r + (0.715 + 0.285 * s) * g + (0.072 - 0.072 * s) * b;
|
|
87
|
+
const B = (0.213 - 0.213 * s) * r + (0.715 - 0.715 * s) * g + (0.072 + 0.928 * s) * b;
|
|
88
|
+
const f = v => Math.round(clamp(v, 0, 255)).toString(16).padStart(2, '0');
|
|
89
|
+
return '#' + f(R) + f(G) + f(B);
|
|
90
|
+
};
|
|
91
|
+
DG.MANIM = {}; for (const k in DG.MANIM_RAW) DG.MANIM[k] = DG.saturate(DG.MANIM_RAW[k]);
|
|
92
|
+
DG.U = H / 8; // manim:画面高 8 个单位 → 1 单位 = 135px(1080p)
|
|
93
|
+
|
|
94
|
+
// ---------- Write 与公式(CMU = LaTeX Computer Modern;中文用思源宋) ----------
|
|
95
|
+
DG.FONT = { rm: 'CMU-rm', it: 'CMU-it', zh: 'NotoSerifSC' };
|
|
96
|
+
const famOf = tk => tk.zh ? DG.FONT.zh : tk.it ? DG.FONT.it : DG.FONT.rm;
|
|
97
|
+
// tokens: [{s, it, zh, col, sub, sup, size, gap}];返回每个 token 的 {x,w,sz}(相对起点)
|
|
98
|
+
DG.layout = (c, tokens, size) => {
|
|
99
|
+
let x = 0; return tokens.map(tk => {
|
|
100
|
+
const sz = (tk.size || 1) * size; c.font = `${sz}px "${famOf(tk)}"`;
|
|
101
|
+
const w = c.measureText(tk.s).width; let sw = 0;
|
|
102
|
+
if (tk.sub || tk.sup) { c.font = `${sz * 0.62}px "${DG.FONT.rm}"`; sw = c.measureText(tk.sub || tk.sup).width + sz * 0.04; }
|
|
103
|
+
const r = { x, w: w + sw, sz }; x += w + sw + (tk.gap != null ? tk.gap : sz * 0.06); return r;
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
// manim Write:每个 token 先沿轮廓描边(lineDash 推进),后半程填色、描边淡掉;token 间 lag = min(4/(n+1), 0.2)(3b1b creation.py)。
|
|
107
|
+
// 尊重外层 globalAlpha(FadeOut 时整条公式一起淡)。返回每个 token 的屏幕 {x,w}。
|
|
108
|
+
DG.math = (c, tokens, x, y, size, { p = 1, align = 'center', col = '#fff', lag } = {}) => {
|
|
109
|
+
const L = DG.layout(c, tokens, size), total = L.length ? L[L.length - 1].x + L[L.length - 1].w : 0;
|
|
110
|
+
const x0 = align === 'center' ? x - total / 2 : align === 'right' ? x - total : x;
|
|
111
|
+
const n = tokens.length, r = lag != null ? lag : Math.min(4 / (n + 1), 0.2);
|
|
112
|
+
const A0 = c.globalAlpha;
|
|
113
|
+
c.save(); c.textBaseline = 'alphabetic'; c.textAlign = 'left';
|
|
114
|
+
tokens.forEach((tk, i) => {
|
|
115
|
+
const q = MO.lagged(i, n, clamp(p), r); if (q <= 0) return;
|
|
116
|
+
const sz = L[i].sz, fx = x0 + L[i].x, colr = tk.col || col;
|
|
117
|
+
const draw = (txt, xx, yy, f, mode) => { c.font = f; mode === 's' ? c.strokeText(txt, xx, yy) : c.fillText(txt, xx, yy); };
|
|
118
|
+
const main = `${sz}px "${famOf(tk)}"`, small = `${sz * 0.62}px "${DG.FONT.rm}"`;
|
|
119
|
+
c.font = main; const mw = c.measureText(tk.s).width;
|
|
120
|
+
const parts = [[tk.s, fx, y, main]]; if (tk.sub) parts.push([tk.sub, fx + mw + sz * 0.02, y + sz * 0.22, small]); if (tk.sup) parts.push([tk.sup, fx + mw + sz * 0.02, y - sz * 0.42, small]);
|
|
121
|
+
const qs = MO.doubleSmooth(q);
|
|
122
|
+
if (qs < 1) { c.strokeStyle = colr; c.lineWidth = Math.max(1.2, sz * 0.025); c.setLineDash([sz * 5 * clamp(qs * 2), 1e5]); c.globalAlpha = A0 * (1 - clamp((qs - 0.6) / 0.4)); parts.forEach(pt => draw(pt[0], pt[1], pt[2], pt[3], 's')); c.setLineDash([]); }
|
|
123
|
+
const fa = clamp((qs - 0.4) / 0.6); if (fa > 0) { c.globalAlpha = A0 * fa; c.fillStyle = colr; parts.forEach(pt => draw(pt[0], pt[1], pt[2], pt[3], 'f')); }
|
|
124
|
+
c.globalAlpha = A0;
|
|
125
|
+
});
|
|
126
|
+
c.restore();
|
|
127
|
+
return L.map(l => ({ x: x0 + l.x, w: l.w }));
|
|
128
|
+
};
|
|
129
|
+
// 纯文本的 Write(中文标题也走这个:非 ASCII 字自动用 DG.FONT.zh)
|
|
130
|
+
DG.write = (c, text, x, y, size, p, opt = {}) => DG.math(c, [...text].map(ch => ({ s: ch, zh: /[^\x00-\x7f]/.test(ch) || opt.zh, gap: 0 })), x, y, size, { ...opt, p });
|
|
131
|
+
|
|
132
|
+
// ---------- 神经网络 ----------
|
|
133
|
+
// 神经元:描边白 2px,填充不透明度 = 激活值(3b1b nn/part1.py 原做法)
|
|
134
|
+
DG.neuron = (c, x, y, r, a, { stroke = '#fff', fill = '#fff', lw = 2, sa = 1 } = {}) => {
|
|
135
|
+
c.save(); c.beginPath(); c.arc(x, y, r, 0, Math.PI * 2);
|
|
136
|
+
if (a > 0) { c.globalAlpha = clamp(a); c.fillStyle = fill; c.fill(); }
|
|
137
|
+
c.globalAlpha = sa; c.strokeStyle = stroke; c.lineWidth = lw; c.stroke(); c.restore();
|
|
138
|
+
};
|
|
139
|
+
// 圆周到圆周的连线端点
|
|
140
|
+
DG.edgeEnds = (ax, ay, ar, bx, by, br) => { const dx = bx - ax, dy = by - ay, d = Math.hypot(dx, dy) || 1; return [ax + dx / d * ar, ay + dy / d * ar, bx - dx / d * br, by - dy / d * br]; };
|
|
141
|
+
// 连线:正权重蓝、负权重红;线宽 = minW + maxW·|w|³(nn/part2.py:3·(|w|/max)³);p = 画到哪(ShowCreation)
|
|
142
|
+
DG.edge = (c, e, w, p = 1, { maxW = 3.2, minW = 0.6, pos = DG.MANIM.BLUE_C, neg = DG.MANIM.RED_C, alpha = 0.85, from = 0 } = {}) => {
|
|
143
|
+
if (p <= from) return;
|
|
144
|
+
const [x1, y1, x2, y2] = e;
|
|
145
|
+
c.save(); c.strokeStyle = w >= 0 ? pos : neg; c.globalAlpha = alpha; c.lineWidth = minW + maxW * Math.pow(Math.min(1, Math.abs(w)), 3);
|
|
146
|
+
c.beginPath(); c.moveTo(lerp(x1, x2, from), lerp(y1, y2, from)); c.lineTo(lerp(x1, x2, p), lerp(y1, y2, p)); c.stroke(); c.restore();
|
|
147
|
+
};
|
|
148
|
+
// ShowPassingFlash(time_width=2.0):信号沿线闪过,返回可见段 [lo, hi](0..1)
|
|
149
|
+
DG.flash = (alpha, tw = 2) => { const up = lerp(0, 1 + tw, alpha); return [clamp(up - tw), clamp(up)]; };
|
|
150
|
+
|
|
151
|
+
// ---------- 提示 ----------
|
|
152
|
+
DG.indicate = p => ({ s: 1 + 0.2 * MO.thereAndBack(clamp(p)), k: MO.thereAndBack(clamp(p)) }); // 放大 1.2、染黄(k)、回来
|
|
153
|
+
// Circumscribe:矩形框沿周长画出再收走(time_width 0.3 的近似)
|
|
154
|
+
DG.circumscribe = (c, x, y, w, h, p, { col = DG.MANIM.YELLOW, lw = 4, tw = 0.3 } = {}) => {
|
|
155
|
+
if (p <= 0 || p >= 1) return; const pts = DG.rectPts(x + w / 2, y + h / 2, w, h, 160);
|
|
156
|
+
const hi = clamp(lerp(0, 1 + tw, p)), lo = clamp(hi - tw - (1 - p) * 0.7);
|
|
157
|
+
c.save(); c.strokeStyle = col; c.lineWidth = lw; c.beginPath();
|
|
158
|
+
const i0 = Math.floor(lo * 160), i1 = Math.floor(hi * 160); for (let i = i0; i <= Math.min(160, i1); i++) { const q = pts[i % 160]; i === i0 ? c.moveTo(q[0], q[1]) : c.lineTo(q[0], q[1]); } c.stroke(); c.restore();
|
|
159
|
+
};
|
|
160
|
+
// 大括号(竖直,开口朝右),从中间向两端长出
|
|
161
|
+
DG.brace = (c, x, y0, y1, { col = '#fff', lw = 3, depth = 22, p = 1 } = {}) => {
|
|
162
|
+
if (p <= 0) return; const ym = (y0 + y1) / 2;
|
|
163
|
+
c.save(); c.strokeStyle = col; c.lineWidth = lw; c.lineCap = 'round'; c.globalAlpha = clamp(p * 1.5);
|
|
164
|
+
const k = MO.smooth(p), a0 = lerp(ym, y0, k), a1 = lerp(ym, y1, k);
|
|
165
|
+
c.beginPath(); c.moveTo(x, a0); c.quadraticCurveTo(x - depth * 0.5, a0, x - depth * 0.5, a0 + Math.min(40, (ym - a0) * 0.3));
|
|
166
|
+
c.lineTo(x - depth * 0.5, ym - Math.min(40, (ym - a0) * 0.3)); c.quadraticCurveTo(x - depth * 0.5, ym, x - depth, ym);
|
|
167
|
+
c.quadraticCurveTo(x - depth * 0.5, ym, x - depth * 0.5, ym + Math.min(40, (a1 - ym) * 0.3));
|
|
168
|
+
c.lineTo(x - depth * 0.5, a1 - Math.min(40, (a1 - ym) * 0.3)); c.quadraticCurveTo(x - depth * 0.5, a1, x, a1); c.stroke(); c.restore();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// ---------- 白板:笔画时间线 + 一支跟着画的笔 ----------
|
|
172
|
+
// 按顺序往板上加笔画,每笔时长 = 长度 / 笔速,自动排成时间线;画的时候笔尖永远在最新那一笔的前沿,两笔之间沿弧线滑过去,笔从不离开画面。
|
|
173
|
+
// const B = DG.board({ ink, lw, speed, font }); B.at(0.12); B.line(pts); B.text('猫?', x, y, 150, {rate: 7.5}); B.pop('×1000万张', x, y, 74); ...
|
|
174
|
+
// 每帧:const { tip, col } = B.draw(g, ft)(在世界坐标里画,调用方先套相机);const [sx, sy] = B.penAt(ft, cam, tip);DG.pen(c, sx, sy, col || B.penColor(ft))。
|
|
175
|
+
// 自定义笔画 B.push({ kind:'custom', t0, t1, col, draw(g, q, ft) → 笔尖|null, start() → 世界点, end() → 世界点, handed:true })。
|
|
176
|
+
// 速度口径(RSA 实测):正文写字 ≈16 字/秒、标题 ≈7.5 字/秒;强调色关键词不带手、整词弹出(后期合成)。
|
|
177
|
+
DG.board = ({ ink = '#0A0503', lw = 6.5, speed = 2600, font = '"LXGWWenKai-500"' } = {}) => {
|
|
178
|
+
const B = { S: [], cur: 0, ink, lw, speed, font };
|
|
179
|
+
B.at = t => { B.cur = t; return B; };
|
|
180
|
+
B.push = s => { B.S.push(s); return s; };
|
|
181
|
+
B.line = (raw, { w = lw, col = ink, speed: sp = speed, seed = B.S.length + 1, amp = 1.4, smooth = true, min = 0.04, gap = 0.02 } = {}) => {
|
|
182
|
+
const pts = smooth ? DG.hand(raw, { amp, seed, per: 8 }) : raw, cum = DG.cum(pts), len = cum[cum.length - 1];
|
|
183
|
+
const d = Math.max(min, len / sp); B.S.push({ kind: 'line', pts, cum, len, w, col, t0: B.cur, t1: B.cur + d }); B.cur += d + gap;
|
|
184
|
+
};
|
|
185
|
+
// 一笔直线(不抖、固定时长,比如网络连线),不推进太多时间
|
|
186
|
+
B.segment = (a, b, { w = 3, col = ink, dur = 0.06, gap = 0.012 } = {}) => {
|
|
187
|
+
const L = Math.hypot(b[0] - a[0], b[1] - a[1]); B.S.push({ kind: 'line', pts: [a, b], cum: [0, L], len: L, w, col, t0: B.cur, t1: B.cur + dur }); B.cur += gap;
|
|
188
|
+
};
|
|
189
|
+
// 手写字:rate 字/秒
|
|
190
|
+
B.text = (str, x, y, size, { col = ink, rate = 16, align = 'left', dur } = {}) => {
|
|
191
|
+
const d = dur || [...str].length / rate; B.S.push({ kind: 'text', str, x, y, size, col, align, t0: B.cur, t1: B.cur + d }); B.cur += d + 0.03;
|
|
192
|
+
};
|
|
193
|
+
// 强调词:不带手、0.3s 放大回落整词弹出
|
|
194
|
+
B.pop = (str, x, y, size, { col = ink, align = 'center', dur = 0.3 } = {}) => { B.S.push({ kind: 'pop', str, x, y, size, col, align, t0: B.cur, t1: B.cur + dur }); B.cur += 0.12; };
|
|
195
|
+
// 铺色(淡入,不推进时间)
|
|
196
|
+
B.fill = (path, col, dur = 0.18) => { B.S.push({ kind: 'fill', path, col, t0: B.cur, t1: B.cur + dur }); };
|
|
197
|
+
|
|
198
|
+
const drawOne = (g, s, ft) => {
|
|
199
|
+
if (ft < s.t0) return null;
|
|
200
|
+
const q = clamp((ft - s.t0) / (s.t1 - s.t0));
|
|
201
|
+
if (s.kind === 'custom') return s.draw(g, q, ft);
|
|
202
|
+
if (s.kind === 'fill') { g.save(); g.globalAlpha = q; g.fillStyle = s.col; g.fill(s.path); g.restore(); return null; }
|
|
203
|
+
if (s.kind === 'line') { g.strokeStyle = s.col; g.lineWidth = s.w; return DG.drawPartial(g, s.pts, s.cum, s.len * MO.sineInOut(q)); }
|
|
204
|
+
if (s.kind === 'pop') {
|
|
205
|
+
g.save(); g.font = `${s.size}px ${B.font}`; g.fillStyle = s.col; g.textBaseline = 'alphabetic'; g.textAlign = s.align;
|
|
206
|
+
const e = MO.backOut(q, 2.2); g.globalAlpha = clamp(q * 4); g.translate(s.x, s.y - s.size * 0.35); g.scale(e, e); g.fillText(s.str, 0, s.size * 0.35); g.restore(); return null;
|
|
207
|
+
}
|
|
208
|
+
if (s.kind === 'text') {
|
|
209
|
+
// 逐字写:笔尖在字框里 3 行之字形来回,遮罩跟着笔尖揭开这个字(Reveal 画法用在单字上)
|
|
210
|
+
g.save(); g.font = `${s.size}px ${B.font}`; g.fillStyle = s.col; g.textBaseline = 'alphabetic';
|
|
211
|
+
const chars = [...s.str], ws = chars.map(ch => g.measureText(ch).width), w = ws.reduce((a, b) => a + b, 0), x0 = s.align === 'center' ? s.x - w / 2 : s.x;
|
|
212
|
+
const n = chars.length, k = Math.min(n, q * n), done = Math.floor(k);
|
|
213
|
+
let x = x0, tip = null;
|
|
214
|
+
chars.forEach((ch, i) => {
|
|
215
|
+
if (i < done) g.fillText(ch, x, s.y);
|
|
216
|
+
else if (i === done && k < n) {
|
|
217
|
+
const zz = DG.zigzag(x - 2, s.y - s.size * 0.95, ws[i] + 4, s.size * 1.15, 3), cum = DG.cum(zz), d = cum[cum.length - 1] * (k - done);
|
|
218
|
+
g.save(); g.clip(DG.revealMask(zz, cum, d, s.size * 0.62)); g.fillText(ch, x, s.y); g.restore(); tip = DG.pointAt(zz, cum, d);
|
|
219
|
+
}
|
|
220
|
+
x += ws[i];
|
|
221
|
+
});
|
|
222
|
+
g.restore(); return tip || [x0 + w, s.y - s.size * 0.35];
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
// 画全部笔画(世界坐标)。返回正在画的那一笔的笔尖与墨色(没有在画的 → tip=null)
|
|
227
|
+
B.draw = (g, ft) => {
|
|
228
|
+
g.lineJoin = 'round'; g.lineCap = 'round';
|
|
229
|
+
let tip = null, col = null;
|
|
230
|
+
for (const s of B.S) { const p = drawOne(g, s, ft); if (p && ft < s.t1) { tip = p; col = s.col; } }
|
|
231
|
+
return { tip, col };
|
|
232
|
+
};
|
|
233
|
+
const handed = () => B.S.filter(s => s.kind !== 'pop' && s.kind !== 'fill' && (s.kind !== 'custom' || s.handed !== false));
|
|
234
|
+
const tw = s => s.size * [...s.str].length * 0.9;
|
|
235
|
+
const endPt = s => s.kind === 'custom' ? s.end() : s.kind === 'line' ? s.pts[s.pts.length - 1] : [s.align === 'center' ? s.x + tw(s) / 2 : s.x + tw(s), s.y - s.size * 0.3];
|
|
236
|
+
const startPt = s => s.kind === 'custom' ? s.start() : s.kind === 'line' ? s.pts[0] : [s.align === 'center' ? s.x - tw(s) / 2 : s.x, s.y - s.size * 0.3];
|
|
237
|
+
// 笔在屏幕上的位置:在画 → 笔尖;两笔之间 → 从上一笔终点沿弧线滑到下一笔起点;开场 → 从右下入画
|
|
238
|
+
B.penAt = (ft, cam, tip) => {
|
|
239
|
+
if (tip) return CAM.toScreen(cam, tip[0], tip[1]);
|
|
240
|
+
let prev = null, next = null;
|
|
241
|
+
for (const s of handed()) { if (s.t1 <= ft) prev = s; else if (s.t0 > ft && !next) next = s; }
|
|
242
|
+
if (prev && next) {
|
|
243
|
+
const q = MO.sineInOut((ft - prev.t1) / (next.t0 - prev.t1)), a = CAM.toScreen(cam, ...endPt(prev)), b = CAM.toScreen(cam, ...startPt(next));
|
|
244
|
+
return [lerp(a[0], b[0], q), lerp(a[1], b[1], q) - Math.sin(q * Math.PI) * Math.min(80, 20 + Math.hypot(b[0] - a[0], b[1] - a[1]) * 0.15)];
|
|
245
|
+
}
|
|
246
|
+
if (next) { const b = CAM.toScreen(cam, ...startPt(next)), q = MO.cubicOut(clamp(ft / next.t0)); return [lerp(W * 0.62, b[0], q), lerp(H * 0.7, b[1], q)]; }
|
|
247
|
+
return CAM.toScreen(cam, ...endPt(prev));
|
|
248
|
+
};
|
|
249
|
+
// 空档里笔的颜色 = 下一笔的墨色(没有下一笔就用上一笔)
|
|
250
|
+
B.penColor = ft => { const hs = handed(), nx = hs.find(s => s.t0 > ft), pv = [...hs].reverse().find(s => s.t1 <= ft); return (nx || pv || { col: ink }).col; };
|
|
251
|
+
return B;
|
|
252
|
+
};
|
|
253
|
+
// 一支马克笔(屏幕空间,笔尖在 (x,y),笔身朝右上)。笔尾帽 = 墨色;有高光和投影,浮在板上。用笔引导视线。
|
|
254
|
+
DG.pen = (c, x, y, col, tilt = 0) => {
|
|
255
|
+
c.save(); c.translate(x, y); c.rotate(-0.72 + tilt);
|
|
256
|
+
c.fillStyle = 'rgba(0,0,0,.12)'; c.beginPath(); c.roundRect(30, 14, 250, 34, 16); c.fill();
|
|
257
|
+
c.fillStyle = col; c.beginPath(); c.moveTo(0, 0); c.lineTo(26, -8); c.lineTo(26, 8); c.closePath(); c.fill();
|
|
258
|
+
c.fillStyle = '#e8e8e4'; c.beginPath(); c.roundRect(24, -12, 26, 24, 4); c.fill();
|
|
259
|
+
c.fillStyle = '#26262a'; c.beginPath(); c.roundRect(48, -17, 190, 34, 12); c.fill();
|
|
260
|
+
c.fillStyle = col; c.beginPath(); c.roundRect(212, -18, 46, 36, 10); c.fill();
|
|
261
|
+
c.fillStyle = 'rgba(255,255,255,.22)'; c.beginPath(); c.roundRect(60, -12, 140, 7, 4); c.fill();
|
|
262
|
+
c.restore();
|
|
263
|
+
};
|
|
264
|
+
})();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 成片质感层(spec.finish):画完一帧之后、对整幅统一加的后期。所有语法通用;默认不开(clean)。
|
|
2
|
+
// spec.finish = 预设名 | { vignette, bloom, grain, contrast, saturate, warm }(对象里的键覆盖预设)。
|
|
3
|
+
// soft :轻暗角+极淡泛光+细颗粒——任何深色/浅色底都不显脏,大多数口播片的默认推荐
|
|
4
|
+
// film :暗角更重+泛光+可见颗粒+一点暖——叙事、故事型
|
|
5
|
+
// punch :对比+饱和各抬一点+轻暗角——发布会、财经、需要「抓眼」的段落
|
|
6
|
+
// clean :什么都不做(和不写一样)
|
|
7
|
+
// 键含义:vignette 暗角强度 0–0.6;bloom 泛光强度 0–0.4;grain 颗粒强度 0–0.12(按 24fps 换格,确定性);contrast/saturate 1=不变;warm 0–0.12 暖色罩。
|
|
8
|
+
// alpha 模式(透明背景)一律不加:后期会把透明区域染黑。
|
|
9
|
+
// 确定性:颗粒只由帧序号决定,同一时刻渲两次逐像素一致。
|
|
10
|
+
(() => {
|
|
11
|
+
const { clamp } = U;
|
|
12
|
+
const PRESETS = {
|
|
13
|
+
clean: {},
|
|
14
|
+
soft: { vignette: 0.2, bloom: 0.1, grain: 0.035 },
|
|
15
|
+
film: { vignette: 0.34, bloom: 0.12, grain: 0.07, warm: 0.04 },
|
|
16
|
+
punch: { vignette: 0.16, bloom: 0.08, contrast: 1.07, saturate: 1.1 },
|
|
17
|
+
};
|
|
18
|
+
const cache = {};
|
|
19
|
+
const scratch = (k, w, h) => { let c = cache[k]; if (!c || c.width !== w || c.height !== h) { c = cache[k] = document.createElement('canvas'); c.width = w; c.height = h; } return c; };
|
|
20
|
+
// 颗粒 tile:3 张 256×256 灰度噪声,种子固定
|
|
21
|
+
function grainTiles() {
|
|
22
|
+
if (cache.__grain) return cache.__grain;
|
|
23
|
+
const r = U.rng(20261016), tiles = [];
|
|
24
|
+
for (let k = 0; k < 3; k++) { const cv = document.createElement('canvas'); cv.width = cv.height = 256; const g = cv.getContext('2d'), im = g.createImageData(256, 256);
|
|
25
|
+
for (let i = 0; i < 256 * 256; i++) { const v = 128 + (r() + r() + r() - 1.5) * 90; im.data[i * 4] = im.data[i * 4 + 1] = im.data[i * 4 + 2] = v; im.data[i * 4 + 3] = 255; }
|
|
26
|
+
g.putImageData(im, 0, 0); tiles.push(cv); }
|
|
27
|
+
return (cache.__grain = tiles);
|
|
28
|
+
}
|
|
29
|
+
const FINISH = window.FINISH = {
|
|
30
|
+
PRESETS,
|
|
31
|
+
// 解析 spec.finish:返回完整参数对象或 null(不加)。写错预设名 → 抛错(clip.js 转成启动失败)
|
|
32
|
+
resolve(f) {
|
|
33
|
+
if (f == null || f === false || f === 'clean') return null;
|
|
34
|
+
let o = typeof f === 'string' ? PRESETS[f] : { ...(PRESETS[f.preset] || {}), ...f };
|
|
35
|
+
if (!o) throw new Error(`spec.finish「${f}」不是预设:${Object.keys(PRESETS).join(' / ')}(或写对象 {vignette, bloom, grain, contrast, saturate, warm})`);
|
|
36
|
+
const bad = Object.keys(o).filter(k => !['vignette', 'bloom', 'grain', 'contrast', 'saturate', 'warm', 'preset'].includes(k));
|
|
37
|
+
if (bad.length) throw new Error(`spec.finish 里没有这些键:${bad.join('、')}(可用 vignette bloom grain contrast saturate warm)`);
|
|
38
|
+
return Object.keys(o).length ? o : null;
|
|
39
|
+
},
|
|
40
|
+
apply(c, t, o, W, H, fps = 30) {
|
|
41
|
+
if (!o) return;
|
|
42
|
+
const cv = c.canvas;
|
|
43
|
+
// 对比/饱和:整幅过一遍 CSS filter
|
|
44
|
+
if ((o.contrast && o.contrast !== 1) || (o.saturate && o.saturate !== 1)) {
|
|
45
|
+
const s = scratch('fin_cs', W, H), g = s.getContext('2d'); g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, W, H); g.drawImage(cv, 0, 0);
|
|
46
|
+
c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.filter = `contrast(${o.contrast || 1}) saturate(${o.saturate || 1})`; c.drawImage(s, 0, 0); c.restore();
|
|
47
|
+
}
|
|
48
|
+
// 泛光:缩小糊化后 screen 叠回(亮处晕开一点)
|
|
49
|
+
if (o.bloom > 0) {
|
|
50
|
+
const sw = Math.max(64, Math.round(W / 6)), sh = Math.max(64, Math.round(H / 6)), s = scratch('fin_bloom', sw, sh), g = s.getContext('2d');
|
|
51
|
+
g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, sw, sh); g.filter = 'blur(5px) brightness(1.15)'; g.drawImage(cv, 0, 0, sw, sh); g.filter = 'none';
|
|
52
|
+
c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.globalCompositeOperation = 'screen'; c.globalAlpha = clamp(o.bloom, 0, 0.5); c.imageSmoothingQuality = 'high'; c.drawImage(s, 0, 0, W, H); c.restore();
|
|
53
|
+
}
|
|
54
|
+
// 暖色罩
|
|
55
|
+
if (o.warm > 0) { c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.globalCompositeOperation = 'soft-light'; c.fillStyle = `rgba(255,190,120,${clamp(o.warm, 0, 0.2) * 2})`; c.fillRect(0, 0, W, H); c.restore(); }
|
|
56
|
+
// 颗粒:24fps 换格,随机偏移;overlay 叠灰度噪声
|
|
57
|
+
if (o.grain > 0) {
|
|
58
|
+
const tiles = grainTiles(), f = Math.floor(t * 24 + 1e-6), tile = tiles[f % 3], r = U.rng(f * 7919 + 13), ox = Math.floor(r() * 256), oy = Math.floor(r() * 256);
|
|
59
|
+
c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.globalCompositeOperation = 'overlay'; c.globalAlpha = clamp(o.grain, 0, 0.2) * 3.2;
|
|
60
|
+
c.fillStyle = c.createPattern(tile, 'repeat'); c.translate(-ox, -oy); c.fillRect(ox, oy, W, H); c.restore();
|
|
61
|
+
}
|
|
62
|
+
// 暗角
|
|
63
|
+
if (o.vignette > 0) {
|
|
64
|
+
const R = Math.hypot(W, H) / 2, g = c.createRadialGradient(W / 2, H / 2, R * 0.45, W / 2, H / 2, R);
|
|
65
|
+
g.addColorStop(0, 'rgba(0,0,0,0)'); g.addColorStop(1, `rgba(0,0,0,${clamp(o.vignette, 0, 0.7)})`);
|
|
66
|
+
c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.fillStyle = g; c.fillRect(0, 0, W, H); c.restore();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
})();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|