Deadshot.io Accessible Overlay Menu

Adds an accessible in-game overlay menu with feature toggles, settings controls, and help information for Deadshot.io.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Deadshot.io Accessible Overlay Menu
// @namespace    https://deadshot.io/
// @version      1.0.0
// @description  Adds an accessible in-game overlay menu with feature toggles, settings controls, and help information for Deadshot.io.
// @author       OpenAI
// @match        *://deadshot.io/*
// @icon         https://deadshot.io/favicon.ico
// @grant        none
// @run-at       document-idle
// ==/UserScript==

/*
==========================================================
 Deadshot.io Accessible Overlay Menu
==========================================================

 PURPOSE
 -------
 This userscript injects a lightweight, keyboard-accessible
 overlay menu into Deadshot.io without modifying gameplay logic.

 FEATURES
 --------
 - Floating menu button
 - Overlay panel with:
    1. Feature Toggles
    2. Settings
    3. Help / FAQ
 - Session-based state persistence
 - Accessible controls
 - Keyboard navigation
 - Minimal performance impact
 - Safe DOM isolation
 - No external libraries

 IMPORTANT NOTES
 ----------------
 - This script intentionally avoids altering game mechanics.
 - All example "features" are UI/session-side placeholders.
 - Uses sessionStorage so settings reset when the tab/browser closes.
 - Uses defensive coding to avoid interfering with the game.

 BEST PRACTICES USED
 -------------------
 - Wrapped in an IIFE to avoid global namespace pollution
 - Uses strict mode
 - Uses requestAnimationFrame-safe UI logic
 - Uses passive-safe event handling where applicable
 - Uses semantic buttons and ARIA labels
 - Uses event delegation minimally
 - Avoids excessive DOM mutation
==========================================================
*/

(function () {
    'use strict';

    /*
    ==========================================================
    CONFIGURATION
    ==========================================================
    */

    const STORAGE_KEY = 'deadshotio_overlay_menu_state';

    /*
    ==========================================================
    DEFAULT SESSION STATE
    ==========================================================
    */

    const defaultState = {
        features: {
            showFPS: false,
            compactMenu: false,
            soundAlerts: false
        },
        settings: {
            uiScale: 100,
            opacity: 95,
            customNickname: ''
        }
    };

    /*
    ==========================================================
    UTILITY FUNCTIONS
    ==========================================================
    */

    /**
     * Safely loads session state.
     * Falls back to defaults if parsing fails.
     */
    function loadState() {
        try {
            const stored = sessionStorage.getItem(STORAGE_KEY);

            if (!stored) {
                return structuredClone(defaultState);
            }

            return {
                ...structuredClone(defaultState),
                ...JSON.parse(stored)
            };
        } catch (error) {
            console.warn('[Deadshot Menu] Failed to load state:', error);
            return structuredClone(defaultState);
        }
    }

    /**
     * Saves current state into sessionStorage.
     */
    function saveState() {
        try {
            sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
        } catch (error) {
            console.warn('[Deadshot Menu] Failed to save state:', error);
        }
    }

    /**
     * Creates an HTML element with optional properties.
     */
    function createElement(tag, options = {}) {
        const element = document.createElement(tag);

        if (options.className) {
            element.className = options.className;
        }

        if (options.text) {
            element.textContent = options.text;
        }

        if (options.html) {
            element.innerHTML = options.html;
        }

        if (options.attributes) {
            Object.entries(options.attributes).forEach(([key, value]) => {
                element.setAttribute(key, value);
            });
        }

        return element;
    }

    /*
    ==========================================================
    SESSION STATE
    ==========================================================
    */

    const state = loadState();

    /*
    ==========================================================
    UI CREATION
    ==========================================================
    */

    /**
     * Injects minimal scoped CSS.
     * Uses high z-index to stay visible over the game canvas.
     */
    function injectStyles() {
        const style = createElement('style');

        style.textContent = `
            #deadshot-overlay-root {
                position: fixed;
                top: 16px;
                right: 16px;
                z-index: 999999;
                font-family: Arial, sans-serif;
                color: #ffffff;
            }

            #deadshot-menu-button {
                background: rgba(20, 20, 20, 0.9);
                color: #fff;
                border: 1px solid rgba(255,255,255,0.2);
                padding: 10px 14px;
                border-radius: 8px;
                cursor: pointer;
                font-size: 14px;
                transition: background 0.2s ease;
            }

            #deadshot-menu-button:hover,
            #deadshot-menu-button:focus {
                background: rgba(50, 50, 50, 0.95);
                outline: 2px solid #66b3ff;
            }

            #deadshot-overlay-panel {
                margin-top: 10px;
                width: 320px;
                max-width: 90vw;
                background: rgba(15, 15, 15, 0.96);
                border: 1px solid rgba(255,255,255,0.15);
                border-radius: 12px;
                padding: 16px;
                display: none;
                backdrop-filter: blur(8px);
                box-shadow: 0 8px 24px rgba(0,0,0,0.35);
            }

            #deadshot-overlay-panel.visible {
                display: block;
            }

            .deadshot-section {
                margin-bottom: 18px;
            }

            .deadshot-section-title {
                font-size: 15px;
                font-weight: bold;
                margin-bottom: 10px;
                border-bottom: 1px solid rgba(255,255,255,0.1);
                padding-bottom: 4px;
            }

            .deadshot-toggle-row,
            .deadshot-setting-row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 10px;
                gap: 10px;
            }

            .deadshot-toggle-button,
            .deadshot-action-button {
                background: rgba(255,255,255,0.08);
                color: white;
                border: 1px solid rgba(255,255,255,0.15);
                border-radius: 6px;
                padding: 6px 10px;
                cursor: pointer;
                transition: background 0.2s ease;
            }

            .deadshot-toggle-button:hover,
            .deadshot-toggle-button:focus,
            .deadshot-action-button:hover,
            .deadshot-action-button:focus {
                background: rgba(255,255,255,0.16);
                outline: 2px solid #66b3ff;
            }

            .deadshot-toggle-active {
                background: rgba(0, 160, 80, 0.7);
            }

            .deadshot-help-text {
                font-size: 13px;
                line-height: 1.5;
                color: rgba(255,255,255,0.88);
            }

            .deadshot-setting-value {
                opacity: 0.85;
                font-size: 12px;
            }

            @media (max-width: 600px) {
                #deadshot-overlay-root {
                    top: 8px;
                    right: 8px;
                }

                #deadshot-overlay-panel {
                    width: 92vw;
                }
            }
        `;

        document.head.appendChild(style);
    }

    /**
     * Creates the overlay UI.
     */
    function createOverlayMenu() {
        const root = createElement('div', {
            attributes: {
                id: 'deadshot-overlay-root'
            }
        });

        /*
        ======================================================
        MENU TOGGLE BUTTON
        ======================================================
        */

        const menuButton = createElement('button', {
            text: 'Menu',
            attributes: {
                id: 'deadshot-menu-button',
                'aria-label': 'Open Deadshot overlay menu',
                'aria-expanded': 'false',
                type: 'button'
            }
        });

        /*
        ======================================================
        OVERLAY PANEL
        ======================================================
        */

        const panel = createElement('div', {
            attributes: {
                id: 'deadshot-overlay-panel',
                role: 'dialog',
                'aria-label': 'Deadshot settings menu',
                tabindex: '-1'
            }
        });

        /*
        ======================================================
        FEATURE TOGGLES SECTION
        ======================================================
        */

        const featureSection = createElement('div', {
            className: 'deadshot-section'
        });

        const featureTitle = createElement('div', {
            className: 'deadshot-section-title',
            text: 'Feature Toggles'
        });

        featureSection.appendChild(featureTitle);

        const featureDefinitions = [
            {
                key: 'showFPS',
                label: 'Show FPS Indicator'
            },
            {
                key: 'compactMenu',
                label: 'Compact Menu Layout'
            },
            {
                key: 'soundAlerts',
                label: 'Enable Sound Alerts'
            }
        ];

        featureDefinitions.forEach((feature) => {
            const row = createElement('div', {
                className: 'deadshot-toggle-row'
            });

            const label = createElement('span', {
                text: feature.label
            });

            const button = createElement('button', {
                className: 'deadshot-toggle-button',
                text: state.features[feature.key] ? 'Enabled' : 'Disabled',
                attributes: {
                    type: 'button',
                    'aria-pressed': String(state.features[feature.key])
                }
            });

            updateToggleButtonStyle(button, state.features[feature.key]);

            button.addEventListener('click', () => {
                state.features[feature.key] = !state.features[feature.key];

                button.textContent = state.features[feature.key]
                    ? 'Enabled'
                    : 'Disabled';

                button.setAttribute(
                    'aria-pressed',
                    String(state.features[feature.key])
                );

                updateToggleButtonStyle(
                    button,
                    state.features[feature.key]
                );

                saveState();

                console.info(
                    `[Deadshot Menu] ${feature.key}:`,
                    state.features[feature.key]
                );
            });

            row.appendChild(label);
            row.appendChild(button);

            featureSection.appendChild(row);
        });

        /*
        ======================================================
        SETTINGS SECTION
        ======================================================
        */

        const settingsSection = createElement('div', {
            className: 'deadshot-section'
        });

        const settingsTitle = createElement('div', {
            className: 'deadshot-section-title',
            text: 'Settings'
        });

        settingsSection.appendChild(settingsTitle);

        const settingsDefinitions = [
            {
                key: 'uiScale',
                label: 'UI Scale (%)',
                validator: (value) => {
                    const number = Number(value);
                    return number >= 50 && number <= 200;
                },
                parser: Number,
                description: 'Enter a number between 50 and 200.'
            },
            {
                key: 'opacity',
                label: 'Menu Opacity (%)',
                validator: (value) => {
                    const number = Number(value);
                    return number >= 30 && number <= 100;
                },
                parser: Number,
                description: 'Enter a number between 30 and 100.'
            },
            {
                key: 'customNickname',
                label: 'Custom Nickname',
                validator: (value) => value.trim().length <= 20,
                parser: String,
                description: 'Maximum length: 20 characters.'
            }
        ];

        settingsDefinitions.forEach((setting) => {
            const row = createElement('div', {
                className: 'deadshot-setting-row'
            });

            const labelContainer = createElement('div');

            const label = createElement('div', {
                text: setting.label
            });

            const valueDisplay = createElement('div', {
                className: 'deadshot-setting-value',
                text: `Current: ${state.settings[setting.key]}`
            });

            labelContainer.appendChild(label);
            labelContainer.appendChild(valueDisplay);

            const editButton = createElement('button', {
                className: 'deadshot-action-button',
                text: 'Edit',
                attributes: {
                    type: 'button'
                }
            });

            editButton.addEventListener('click', () => {
                const userInput = prompt(
                    `${setting.description}\n\nCurrent value: ${state.settings[setting.key]}`,
                    state.settings[setting.key]
                );

                // User cancelled prompt
                if (userInput === null) {
                    return;
                }

                const parsedValue = setting.parser(userInput);

                if (!setting.validator(parsedValue)) {
                    alert('Invalid value entered.');
                    return;
                }

                /*
                ==============================================
                CONFIRMATION FOR SENSITIVE SETTINGS
                ==============================================
                */

                const confirmed = confirm(
                    `Apply new value "${parsedValue}" for "${setting.label}"?`
                );

                if (!confirmed) {
                    return;
                }

                state.settings[setting.key] = parsedValue;

                valueDisplay.textContent =
                    `Current: ${state.settings[setting.key]}`;

                saveState();

                applyRuntimeSettings();
            });

            row.appendChild(labelContainer);
            row.appendChild(editButton);

            settingsSection.appendChild(row);
        });

        /*
        ======================================================
        HELP SECTION
        ======================================================
        */

        const helpSection = createElement('div', {
            className: 'deadshot-section'
        });

        const helpTitle = createElement('div', {
            className: 'deadshot-section-title',
            text: 'Help'
        });

        const helpText = createElement('div', {
            className: 'deadshot-help-text',
            html: `
                <strong>How to use:</strong><br>
                • Click the Menu button to open or close the overlay.<br>
                • Toggle switches enable or disable UI-side features.<br>
                • Settings can be adjusted using prompts.<br>
                • Values are stored only for the current session.<br><br>

                <strong>Keyboard:</strong><br>
                • Press ESC to close the menu.<br>
                • Use TAB to navigate buttons.<br><br>

                <strong>Safety:</strong><br>
                • This menu avoids modifying game mechanics.<br>
                • UI is isolated to minimize conflicts.
            `
        });

        helpSection.appendChild(helpTitle);
        helpSection.appendChild(helpText);

        /*
        ======================================================
        APPEND ALL SECTIONS
        ======================================================
        */

        panel.appendChild(featureSection);
        panel.appendChild(settingsSection);
        panel.appendChild(helpSection);

        root.appendChild(menuButton);
        root.appendChild(panel);

        document.body.appendChild(root);

        /*
        ======================================================
        MENU OPEN/CLOSE LOGIC
        ======================================================
        */

        function toggleMenu(forceState = null) {
            const shouldOpen =
                forceState !== null
                    ? forceState
                    : !panel.classList.contains('visible');

            panel.classList.toggle('visible', shouldOpen);

            menuButton.setAttribute(
                'aria-expanded',
                String(shouldOpen)
            );

            if (shouldOpen) {
                panel.focus();
            }
        }

        menuButton.addEventListener('click', () => {
            toggleMenu();
        });

        /*
        ======================================================
        KEYBOARD ACCESSIBILITY
        ======================================================
        */

        window.addEventListener('keydown', (event) => {
            // Escape closes menu
            if (event.key === 'Escape') {
                toggleMenu(false);
            }

            // Optional shortcut: Shift + M
            if (event.shiftKey && event.key.toLowerCase() === 'm') {
                toggleMenu();
            }
        });

        /*
        ======================================================
        CLICK OUTSIDE TO CLOSE
        ======================================================
        */

        document.addEventListener('click', (event) => {
            const clickedInside = root.contains(event.target);

            if (!clickedInside) {
                toggleMenu(false);
            }
        });

        /*
        ======================================================
        PREVENT MENU CLICKS FROM PROPAGATING
        ======================================================
        */

        panel.addEventListener('click', (event) => {
            event.stopPropagation();
        });

        menuButton.addEventListener('click', (event) => {
            event.stopPropagation();
        });
    }

    /*
    ==========================================================
    TOGGLE BUTTON STYLE HELPER
    ==========================================================
    */

    function updateToggleButtonStyle(button, enabled) {
        button.classList.toggle('deadshot-toggle-active', enabled);
    }

    /*
    ==========================================================
    APPLY RUNTIME SETTINGS
    ==========================================================
    */

    /**
     * Applies purely visual runtime settings.
     * Avoids touching gameplay systems.
     */
    function applyRuntimeSettings() {
        const panel = document.getElementById('deadshot-overlay-panel');

        if (!panel) {
            return;
        }

        panel.style.opacity =
            String(state.settings.opacity / 100);

        panel.style.transform =
            `scale(${state.settings.uiScale / 100})`;
    }

    /*
    ==========================================================
    INITIALIZATION
    ==========================================================
    */

    function initializeMenu() {
        // Prevent duplicate injection
        if (document.getElementById('deadshot-overlay-root')) {
            return;
        }

        injectStyles();
        createOverlayMenu();
        applyRuntimeSettings();

        console.info('[Deadshot Menu] Overlay initialized.');
    }

    /*
    ==========================================================
    SAFE PAGE LOAD HANDLING
    ==========================================================
    */

    window.addEventListener('load', () => {
        // Delay slightly to avoid conflicting with
        // early game initialization routines.
        setTimeout(() => {
            initializeMenu();
        }, 1200);
    });

})();