Deadshot.io Accessible Overlay Menu

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);
    });

})();