A browser source overlay for winter vibes for your Live Streams or Videos

configuration is now stored in URL params

+60 -25
src/config.js
···
-
// canvas size - I chose these sizes for 16:9 aspect ratio and really pixely
-
export const CANVAS_WIDTH = 320;
-
export const CANVAS_HEIGHT = 180;
+
// default configuration values
+
const PARAM_DEFAULTS = [
+
{ name: "v", value: 1 }, // version
+
{ name: "cw", value: 640 }, // canvas width
+
{ name: "ch", value: 360 }, // canvas height
+
{ name: "smx", value: 40 }, // snow max
+
{ name: "smnsz", value: 1 }, // snow min size
+
{ name: "smxsz", value: 4 }, // snow max size
+
{ name: "smxspd", value: 0.07 }, // snow max speed
+
{ name: "snmnspd", value: 0.04 }, // snow min speed
+
{ name: "pspd", value: 0.1 }, // plow speed
+
{ name: "pd", value: "left" }, // plow direction
+
{ name: "gamax", value: 4 }, // ground accumulator max
+
{ name: "gas", value: 10 }, // ground accumulatior slices
+
];
-
// number of collisions required to shift the snowlevel up
-
export const FLOOR_RAISE_THRESHOLD = 4;
-
-
// how many sections the floor is divided into
-
export const FLOOR_SICE_COUNT = 16;
-
-
// number of snowflakes to render
-
export const MAX_PARTICAL_COUNT = 30;
-
-
export const MAX_FLOOR_HEIGHT = 10;
-
-
// minimum size of snowflake
-
export const MIN_SIZE = 1;
-
-
// maximum size of snowflake
-
export const MAX_SIZE = 4;
+
/**
+
* @returns {Config} configuration object
+
* gets configuration from query parameters or defaults
+
*/
+
function getConfiguration() {
+
const urlParams = new URLSearchParams(window.location.search);
+
if (PARAM_DEFAULTS.some(({ name }) => !urlParams.has(name))) {
+
setParamsAndRefresh(PARAM_DEFAULTS);
+
}
+
return {
+
version: parseInt(urlParams.get("v")),
+
canvas: {
+
width: parseInt(urlParams.get("cw")),
+
height: parseInt(urlParams.get("ch")),
+
},
+
snow: {
+
max: parseInt(urlParams.get("smx")),
+
minSize: parseInt(urlParams.get("smnsz")),
+
maxSize: parseInt(urlParams.get("smxsz")),
+
maxSpeed: parseFloat(urlParams.get("smxspd")),
+
minSpeed: parseFloat(urlParams.get("snmnspd")),
+
},
+
plow: {
+
speed: parseFloat(urlParams.get("pspd")),
+
// @ts-ignore -- I dunno how to get ts / jsdoc to be ok with this
+
direction: urlParams.get("pd"),
+
},
+
groundAccumulator: {
+
max: parseInt(urlParams.get("gamax")),
+
slices: parseInt(urlParams.get("gas")),
+
},
+
};
+
}
-
// maximum speed of a falling snowflake
-
export const MAX_SPEED = 0.07;
+
/**
+
* @param {Array<{name: string, value: any}>} params
+
*/
+
export function setParamsAndRefresh(params) {
+
const urlParams = new URLSearchParams(window.location.search);
+
params.forEach(({ name, value }) => {
+
urlParams.set(name, value);
+
});
+
window.location.search = urlParams.toString();
+
}
-
// minimum speed of a snowflake falling
-
export const MIN_SPEED = 0.04;
+
const config = getConfiguration();
-
// speed of the plow
-
export const PLOW_SPEED = 0.1;
+
export default config;
+9 -15
src/main.js
···
import "./style.css";
-
import {
-
CANVAS_HEIGHT,
-
CANVAS_WIDTH,
-
FLOOR_SICE_COUNT,
-
MAX_FLOOR_HEIGHT,
-
MAX_PARTICAL_COUNT,
-
} from "./config";
+
import Config from "./config";
import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake";
import {
accumulateSnow,
···
*/
const floor = [];
-
const sectionSize = CANVAS_WIDTH / FLOOR_SICE_COUNT;
+
const sectionSize = Config.canvas.width / Config.groundAccumulator.slices;
/**
* @type {HTMLCanvasElement}
*/
···
canvas = document.getElementById("main");
ctx = canvas.getContext("2d");
-
canvas.width = CANVAS_WIDTH;
-
canvas.height = CANVAS_HEIGHT;
+
canvas.width = Config.canvas.width;
+
canvas.height = Config.canvas.height;
-
for (let i = 0; i < FLOOR_SICE_COUNT; i++) {
+
for (let i = 0; i < Config.groundAccumulator.slices; i++) {
floor.push(
-
createSnowAccumulator(i * sectionSize, CANVAS_HEIGHT, sectionSize),
+
createSnowAccumulator(i * sectionSize, Config.canvas.height, sectionSize),
);
}
···
function gameLoop(time) {
const delta = time - lastFrame;
-
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
-
if (time - lastSpawn > 500 && snowFlakes.length < MAX_PARTICAL_COUNT) {
+
ctx.clearRect(0, 0, Config.canvas.width, Config.canvas.height);
+
if (time - lastSpawn > 500 && snowFlakes.length < Config.snow.max) {
for (let i = 0; i < Math.floor(Math.random() * 2); i++) {
snowFlakes.push(createSnowflake());
}
···
const avgHeight =
floor.reduce((acc, sa) => acc + sa.height, 0) / floor.length;
-
if (avgHeight > MAX_FLOOR_HEIGHT) {
+
if (avgHeight > Config.groundAccumulator.max) {
isPlowing = true;
}
+5 -5
src/snow_accumulator.js
···
-
import { CANVAS_HEIGHT, CANVAS_WIDTH, FLOOR_RAISE_THRESHOLD } from "./config";
+
import Config from "./config";
/**
* @param {number} y
···
export function accumulateSnow(sa, { size }) {
sa.accumulator += size;
-
if (sa.accumulator % FLOOR_RAISE_THRESHOLD == 0) {
+
if (sa.accumulator % Config.groundAccumulator.max == 0) {
sa.height += 1;
}
}
···
ctx.beginPath();
noramlized.forEach(([x, y]) => ctx.lineTo(x, y));
-
ctx.lineTo(CANVAS_WIDTH, noramlized[noramlized.length - 1][1]);
-
ctx.lineTo(CANVAS_WIDTH, CANVAS_HEIGHT);
-
ctx.lineTo(0, CANVAS_HEIGHT);
+
ctx.lineTo(Config.canvas.width, noramlized[noramlized.length - 1][1]);
+
ctx.lineTo(Config.canvas.width, Config.canvas.height);
+
ctx.lineTo(0, Config.canvas.height);
ctx.fillStyle = "#fff";
ctx.fill();
}
+9 -10
src/snowflake.js
···
-
import {
-
CANVAS_WIDTH,
-
MAX_SIZE,
-
MAX_SPEED,
-
MIN_SIZE,
-
MIN_SPEED,
-
} from "./config";
+
import Config from "./config";
/**
* @returns {Snowflake}
*/
export function createSnowflake() {
return {
-
x: Math.floor(Math.random() * CANVAS_WIDTH),
+
x: Math.floor(Math.random() * Config.canvas.width),
y: -1,
-
speed: Math.random() * (MIN_SPEED - MAX_SPEED) + MAX_SPEED,
-
size: Math.floor(Math.random() * (MIN_SIZE - MAX_SIZE) + MAX_SIZE),
+
speed:
+
Math.random() * (Config.snow.minSpeed - Config.snow.maxSpeed) +
+
Config.snow.maxSpeed,
+
size: Math.floor(
+
Math.random() * (Config.snow.minSize - Config.snow.maxSize) +
+
Config.snow.maxSize,
+
),
};
}
+23
src/types.d.ts
···
export {};
declare global {
+
type Config = {
+
version: number;
+
canvas: {
+
width: number;
+
height: number;
+
};
+
snow: {
+
max: number;
+
minSize: number;
+
maxSize: number;
+
minSpeed: number;
+
maxSpeed: number;
+
};
+
groundAccumulator: {
+
max: number;
+
slices: number;
+
};
+
plow: {
+
speed: number;
+
direction: "left" | "right";
+
};
+
};
+
type Snowflake = {
x: number;
y: number;