Confetti Changer

Hijack submitty's confetti with our own colors (and more features soon™)

// ==UserScript==
// @name         Confetti Changer
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Hijack submitty's confetti with our own colors (and more features soon™)
// @author       Colin SF
// @match        https://submit.scss.tcd.ie/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tcd.ie
// @grant        none
// @license      MIT
// ==/UserScript==

/* eslint-disable no-multi-spaces */ // so the linter doesn't complain about my formatting :)

// to test or preview these hex colors you can use https://www.w3schools.com/colors/colors_hexadecimal.asp or https://www.color-hex.com/
// color themes can be added just by adding a new const list on a new line

const JANUARY_COLORS   = ['#406BC9','#FFFFFF','#809BCE','#9AC8DE','#B6C7BE'];
const FEBRUARY_COLORS  = ['#DF3B57','#EE4B6A','#7D2335','#86CEC5','#B2E6F1'];
const MARCH_COLORS     = ['#8DB62F','#7B9233','#034121','#022607','#FFCC00'];
const APRIL_COLORS     = ['#EED149','#3BCA8B','#9EE0E7','#EBB8AA','#FFFFFF'];
const MAY_COLORS       = ['#F9EAE5','#F16878','#C1DBB3','#7EBC89','#FF8154'];
const JUNE_COLORS      = ['#EC4067','#F4D35E','#F78764','#00889F','#083D77'];
const JULY_COLORS      = ['#FFFFFF','#DE1A1A','#090C9B'];
const AUGUST_COLORS    = ['#F0A202','#FF4040','#F2C940','#AB2321'];
const SEPTEMBER_COLORS = ['#8FD7FF','#316498','#34CA34','#FFFF40','#FF2929','#9C84A4'];
const OCTOBER_COLORS   = ['#000000','#FF6700','#291528'];
const NOVEMBER_COLORS  = ['#5A351E','#522B47','#912F09','#F0A202','#FBF5F3'];
const DECEMBER_COLORS  = ['#D7CDCC','#F7B11D','#1F5E00','#DE1A1A','#FFFFFF'];
const DEFAULT_COLORS   = ['#8FD7FF','#316498','#34CA34','#FFFF40','#FF2929','#9C84A4'];
const MARTINI          = ['#BB0059','#FF1493','#00BFFF','#FFF0F5','#FF69B4','#ADD8E6'];          // sweet girl
const USER_COLORS      = ['#FFFFFF','#808080','#000000'];                                        // YOUR COLORS HERE!

// CONFETTI PARAMETERS
var CONFETTI_COLORS = MARTINI;       // Set color theme here, can change to any const above or "PER_MONTH" if you want submitty's naturally updating confetti
const CONFETTI_DENSITY = 0.75;       // must be a float between [0,inf), 1.0 is submitty's original behavior (but large values will probably tank performance)

// handle per-month confetti color choice
if ( CONFETTI_COLORS == "PER_MONTH" ) {
    let month = new Date().getMonth();
    CONFETTI_COLORS = [
        JANUARY_COLORS,
        FEBRUARY_COLORS,
        MARCH_COLORS,
        APRIL_COLORS,
        MAY_COLORS,
        JUNE_COLORS,
        JULY_COLORS,
        AUGUST_COLORS,
        SEPTEMBER_COLORS,
        OCTOBER_COLORS,
        NOVEMBER_COLORS,
        DECEMBER_COLORS
    ][month];
}

// copy the page's addConfetti() func, alter it so it always uses whatever colors we want
// and then substitute it for the window's original function to customize our confetti
// ( NB: you should never have to go in here unless you're curious :) )
function addConfetti() {
    const canvas = document.getElementById('confetti_canvas');
    if (!canvas) {
        return;
    }
    let times_ran = 0;
    let frame = 0;

    //destroy the canvas animation on click or on enter
    canvas.addEventListener('click', () => {
        if (canvas.style.display !== 'none') {
            canvas.style.display = 'none';
            return;
        }
    });

    window.addEventListener('keypress', (e) => {
        if (e.code === 'Enter' && canvas.style.display !== 'none') {
            canvas.style.display = 'none';
            return;
        }
        if (e.key === 'p' || e.key === 'P'){
            confettiPaused = !confettiPaused;
        }
    });

    // Resume confetti animation when window is visible again
    window.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'visible' && is_drawing) {
            lastUpdateTime = Date.now();
            draw();
        }
    });

    canvas.width = window.innerWidth;
    const body = document.body;
    const html = document.documentElement;
    canvas.height = Math.max( body.scrollHeight, body.offsetHeight,
                             html.clientHeight, html.scrollHeight, html.offsetHeight );

    canvas.style.display = 'block';

    const ctx = canvas.getContext('2d');
    const pieces = [];
    const numberOfPieces = 2500 * CONFETTI_DENSITY;
    let lastUpdateTime = Date.now();
    const x_const = 0.25;
    const max_times = 250;
    const size_const = 10;
    const gravity_const = 0.25;

    let is_drawing = false;

    function randomColor () {
        return CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)];
    }

    function update () {
        const now = Date.now(),
              dt = now - lastUpdateTime;

        for (let i = pieces.length - 1; i >= 0; i--) {
            const p = pieces[i];

            if (p.y > canvas.height) {
                pieces.splice(i, 1);
                continue;
            }

            p.y += p.gravity * dt;
            p.rotation += p.rotationSpeed * dt;
            p.x += p.x_vel;
        }

        while (pieces.length < numberOfPieces && times_ran < max_times) {
            pieces.push(new Piece(Math.random() * canvas.width, -20));
        }

        lastUpdateTime = now;

        times_ran ++;

        if (times_ran >= max_times * 10) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            canvas.style.display = '';
            canvas.width = 0;
            canvas.height = 0;
        }

    }

    function draw () {

        if (canvas.style.display === 'none') {
            cancelAnimationFrame(frame);
            is_drawing = false;
            return;
        }

        is_drawing = true;

        // Stop the confetti animation if the page is not visible
        if (document.visibilityState === 'hidden') {
            cancelAnimationFrame(frame);
            return;
        }

        update();
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        pieces.forEach((p) => {
            ctx.save();

            ctx.fillStyle = p.color;

            ctx.translate(p.x + p.size / 25, p.y + p.size / 2);
            ctx.rotate(p.rotation);

            ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);

            ctx.restore();
        });

        frame = requestAnimationFrame(draw);
    }

    function Piece (x, y) {
        this.x = x;
        this.y = y;
        this.x_vel = (Math.random() - 0.5) * x_const;
        this.size = (Math.random() * 0.5 + 0.75) * size_const;
        this.gravity = (Math.random() * 0.5 + 0.75) * gravity_const;
        this.rotation = (Math.PI * 2) * Math.random();
        this.rotationSpeed = (Math.PI * 2) * (Math.random() - 0.5) * 0.0015;
        this.color = randomColor();
    }

    while (pieces.length < numberOfPieces) {
        pieces.push(new Piece(Math.random() * canvas.width, Math.random() * canvas.height));
    }

    draw();
}

(function() {
    'use strict';
    window.addConfetti = addConfetti;
})();