Keystroke Overlay (Customizable)

WASD HUD for SmashKarts.io

// ==UserScript==
// @name         Keystroke Overlay (Customizable)
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  WASD HUD for SmashKarts.io
// @author       Raiyan
// @match        https://smashkarts.io/*
// @icon         https://raw.githubusercontent.com/elitegamersk/elitegamersk/main/exe.png
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // default configuration
    const defaultConfig = {
        hud: {
            backgroundColor: 'rgba(0,0,0,0.6)', // HUD panel background color & opacity
            textColor: 'white', // default key text color
            borderColor: 'white', // key box border color
            borderRadius: '5px', // key box corner roundness
            padding: '10px 15px', // key box padding (size)
            spacebarWidth: '120px', // width of the Spacebar key box
            scale: 1 // default HUD scale
        },
        keyPress: {
            background: 'rgba(255,255,255,0.9)', // background when key is pressed
            textColor: 'black', // text color when key is pressed
            glow: '0 0 8px rgba(255,255,255,0.8)' // glow effect when pressed
        },
        keys: { // default labels
            w: 'W', // labels w
            a: 'A', // labels a
            s: 'S', // labels s
            d: 'D', // labels d
            space: 'Space' // labels Spacebar
        },
        position: { // saved position and scale
            x: 20,
            y: 140,
            scale: 1
        }
    };

    // load saved settings from localStorage
    const savedConfig = JSON.parse(localStorage.getItem("keystrokeOverlayConfig") || "{}");
    const config = mergeDeep(defaultConfig, savedConfig);

    // HUD setup
    const overlay = document.createElement('div');
    overlay.style.position = 'fixed';
    overlay.style.top = config.position.y + 'px';
    overlay.style.left = config.position.x + 'px';
    overlay.style.background = config.hud.backgroundColor;
    overlay.style.color = config.hud.textColor;
    overlay.style.padding = config.hud.padding;
    overlay.style.borderRadius = config.hud.borderRadius;
    overlay.style.zIndex = 999999;
    overlay.style.userSelect = 'none';
    overlay.style.cursor = 'grab';
    overlay.style.fontFamily = 'Arial, sans-serif';
    overlay.style.textAlign = 'center';
    overlay.style.transform = `scale(${config.position.scale})`;

    const row1 = document.createElement('div'); // W row
    const row2 = document.createElement('div'); // A S D row
    const row3 = document.createElement('div'); // Space row
    overlay.appendChild(row1);
    overlay.appendChild(row2);
    overlay.appendChild(row3);

    const keys = {
        w: {label: config.keys.w, row: row1, linked:['w','arrowup']},
        a: {label: config.keys.a, row: row2, linked:['a','arrowleft']},
        s: {label: config.keys.s, row: row2, linked:['s','arrowdown']},
        d: {label: config.keys.d, row: row2, linked:['d','arrowright']},
        space: {label: config.keys.space, row: row3, linked:[' ']}
    };

    const keyElems = {};
    Object.keys(keys).forEach(k => {
        const el = document.createElement('div');
        el.innerText = keys[k].label;
        el.style.display = 'inline-block';
        el.style.margin = '3px';
        el.style.padding = config.hud.padding;
        el.style.border = `2px solid ${config.hud.borderColor}`;
        el.style.borderRadius = config.hud.borderRadius;
        el.style.transition = 'all 0.1s ease';

        if (k === 'space') {
            el.style.minWidth = config.hud.spacebarWidth;
            el.style.textAlign = 'center';
        }

        keys[k].row.appendChild(el);
        keyElems[k] = el;
    });

    document.body.appendChild(overlay);

    // key tracking and glow
    const pressed = new Set();
    document.addEventListener('keydown', e => pressed.add(e.key.toLowerCase()));
    document.addEventListener('keyup', e => pressed.delete(e.key.toLowerCase()));

    function updateKeys() {
        Object.keys(keys).forEach(k => {
            const active = keys[k].linked.some(code => pressed.has(code));
            if (active) {
                keyElems[k].style.background = config.keyPress.background;
                keyElems[k].style.color = config.keyPress.textColor;
                keyElems[k].style.boxShadow = config.keyPress.glow;
            } else {
                keyElems[k].style.background = 'transparent';
                keyElems[k].style.color = config.hud.textColor;
                keyElems[k].style.boxShadow = 'none';
            }
        });
        requestAnimationFrame(updateKeys);
    }
    updateKeys();

    // dragging and saving position
    let isDragging = false, offsetX = 0, offsetY = 0;
    overlay.addEventListener('mousedown', e => {
        isDragging = true;
        overlay.style.cursor = 'grabbing';
        offsetX = e.clientX - overlay.getBoundingClientRect().left;
        offsetY = e.clientY - overlay.getBoundingClientRect().top;
    });
    document.addEventListener('mousemove', e => {
        if(isDragging){
            const newX = e.clientX - offsetX;
            const newY = e.clientY - offsetY;
            overlay.style.left = newX + 'px';
            overlay.style.top = newY + 'px';
            saveSettings(newX, newY, getScale()); // save on drag
        }
    });
    document.addEventListener('mouseup', () => {
        isDragging = false;
        overlay.style.cursor = 'grab';
    });

    // resize with wheel and save
    overlay.addEventListener('wheel', e => {
        e.preventDefault();
        let newScale = getScale() + (e.deltaY < 0 ? 0.1 : -0.1);
        newScale = Math.min(Math.max(newScale, 0.5), 3);
        overlay.style.transform = `scale(${newScale})`;
        const rect = overlay.getBoundingClientRect();
        saveSettings(rect.left, rect.top, newScale);
    });

    // helpers
    function getScale() {
        return parseFloat(overlay.style.transform.replace(/scale\((.*)\)/,'$1')) || 1;
    }

    function saveSettings(x, y, scale) {
        const saved = JSON.parse(localStorage.getItem("keystrokeOverlayConfig") || "{}");
        saved.position = {x, y, scale};
        localStorage.setItem("keystrokeOverlayConfig", JSON.stringify(saved));
    }

    // deep merge for defaults and saved settings
    function mergeDeep(target, source) {
        const output = {...target};
        if (isObject(target) && isObject(source)) {
            Object.keys(source).forEach(key => {
                if (isObject(source[key])) {
                    if (!(key in target)) Object.assign(output, {[key]: source[key]});
                    else output[key] = mergeDeep(target[key], source[key]);
                } else {
                    output[key] = source[key];
                }
            });
        }
        return output;
    }

    function isObject(item) {
        return (item && typeof item === 'object' && !Array.isArray(item));
    }

})();