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}