A browser source overlay for winter vibes for your Live Streams or Videos
1import Config from "./config.js"; 2import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake.js"; 3import { 4 accumulateSnow, 5 snowAccumulatorCollisionY, 6 createSnowAccumulator, 7 drawSnowAccumulators, 8 resetSnowAccumulator, 9} from "./snow_accumulator.js"; 10import { createPlow, drawPlow, movePlow, plowDone } from "./plow.js"; 11import { toggleModal } from "./contextMenu"; 12 13// Modals & buttons 14const openSettings = document.getElementById('openSettings'); 15const openHelp = document.getElementById('openHelp'); 16const closeSettings = document.getElementById('closeSettings'); 17const closeHelp = document.getElementById('closeHelp'); 18 19openSettings.addEventListener('click', () => toggleModal('modalSettings', true)); 20openHelp.addEventListener('click', () => toggleModal('modalHelp', true)); 21 22// Closing doesn't call the function idk why its stupid... assuming its something to do with DOM 23closeSettings.addEventListener('click', () => toggleModal('modalSettings', false)); 24closeSettings.addEventListener('click', () => console.log('close settings')); 25closeHelp.addEventListener('click', () => toggleModal('modalHelp', false)); 26 27/** 28 * @type{Array.<Snowflake>} 29 */ 30const snowFlakes = []; 31 32/** 33 * @type SnowAccumulator[] 34 */ 35const floor = []; 36 37const sectionSize = Config.canvas.width / Config.groundAccumulator.slices; 38/** 39 * @type {HTMLCanvasElement} 40 */ 41let canvas; 42 43/** 44 * @type {CanvasRenderingContext2D} 45 */ 46let ctx; 47 48/** 49 * @type {Plow} 50 */ 51let plow; 52 53let lastSpawn = 0; 54let lastFrame = 0; 55 56let isPlowing = false; 57 58window.onload = init; 59 60function init() { 61 // @ts-ignore -- I dunno how to get ts / jsdoc to be ok with this 62 canvas = document.getElementById("main"); 63 64 ctx = canvas.getContext("2d"); 65 canvas.width = Config.canvas.width; 66 canvas.height = Config.canvas.height; 67 ctx.imageSmoothingEnabled = false; 68 69 for (let i = 0; i < Config.groundAccumulator.slices; i++) { 70 floor.push( 71 createSnowAccumulator(i * sectionSize, Config.canvas.height, sectionSize), 72 ); 73 } 74 75 window.requestAnimationFrame(gameLoop); 76} 77 78/** 79 * @param time {DOMHighResTimeStamp} 80 */ 81function gameLoop(time) { 82 const delta = time - lastFrame; 83 84 ctx.clearRect(0, 0, Config.canvas.width, Config.canvas.height); 85 if (time - lastSpawn > 500 && snowFlakes.length < Config.snow.max) { 86 for (let i = 0; i < Math.floor(Math.random() * 2); i++) { 87 snowFlakes.push(createSnowflake()); 88 } 89 } 90 91 snowFlakes.forEach((flake, idx) => { 92 moveSnowflake(flake, delta); 93 const floorIdx = Math.floor(flake.x / sectionSize); 94 95 if (snowAccumulatorCollisionY(floor[floorIdx], flake)) { 96 if (!isPlowing) { 97 accumulateSnow(floor[floorIdx], flake); 98 } 99 snowFlakes[idx] = createSnowflake(); 100 } 101 drawSnowflake(flake, ctx); 102 }); 103 104 const avgHeight = 105 floor.reduce((acc, sa) => acc + sa.height, 0) / floor.length; 106 107 if (avgHeight > Config.groundAccumulator.max) { 108 isPlowing = true; 109 } 110 111 drawSnowAccumulators(ctx, floor, plow); 112 113 if (isPlowing) { 114 plow ??= createPlow(); 115 movePlow(plow, delta); 116 drawPlow(plow, ctx); 117 if (plowDone(plow)) { 118 isPlowing = false; 119 plow = null; 120 floor.forEach((sa) => resetSnowAccumulator(sa)); 121 } 122 } 123 124 lastFrame = time; 125 window.requestAnimationFrame(gameLoop); 126}