Gartic.io Custom Background

This userscript let you customize your own gartic.io background

// ==UserScript==
// @name         Gartic.io Custom Background
// @namespace    http://tampermonkey.net/
// @version      1.25
// @description  This userscript let you customize your own gartic.io background
// @author       arcticrevurne
// @icon         https://em-content.zobj.net/source/twitter/408/fox_1f98a.png
// @match        *://*.gartic.io/*
// @license      MIT
// @noframes
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const defaultBackgroundColor = '#241500';
    const oldDefaultOrange = '#FF9800';

    function injectCustomCss() {
        const style = document.createElement('style');
        style.id = 'gartic-custom-styles';
        style.textContent = `
            #background::before {
                content: none !important;
                background-image: none !important;
                display: none !important;
                opacity: 0 !important;
            }
            body {
                background-color: ${defaultBackgroundColor} !important;
            }
        `;
        document.head.appendChild(style);
    }

    function applyCustomBackground(value) {
        const body = document.querySelector('body');
        if (body) {
            if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('data:')) {
                body.style.backgroundImage = `url('${value}')`;
                body.style.backgroundSize = 'cover';
                body.style.backgroundRepeat = 'no-repeat';
                body.style.backgroundAttachment = 'fixed';
                body.style.backgroundPosition = 'center center';
                body.style.removeProperty('background-color');
            } else {
                body.style.backgroundImage = 'none';
                body.style.setProperty('background-color', value, 'important');
            }
        }
    }

    function resetBackground() {
        const body = document.querySelector('body');
        if (body) {
            body.style.backgroundImage = '';
            body.style.removeProperty('background-color');
        }
        localStorage.removeItem('gartic_custom_background');
    }

    function showNotification(message, isError = false) {
        const notificationArea = document.getElementById('gartic-bg-notification');
        if (!notificationArea) return;

        notificationArea.textContent = message;
        notificationArea.style.opacity = '1';
        notificationArea.style.backgroundColor = isError ? '#FF5722' : '#4CAF50';
        notificationArea.style.color = '#FFFFFF';
        notificationArea.style.padding = '8px';
        notificationArea.style.borderRadius = '4px';
        notificationArea.style.marginBottom = '10px';
        notificationArea.style.textAlign = 'center';

        setTimeout(() => {
            notificationArea.style.opacity = '0';
            notificationArea.style.padding = '0';
            notificationArea.style.marginBottom = '0';
        }, 3000);
    }

    function createSettingsMenu() {
        if (document.getElementById('gartic-custom-bg-menu')) {
            return;
        }

        const menuContainer = document.createElement('div');
        menuContainer.id = 'gartic-custom-bg-menu';
        menuContainer.style.position = 'fixed';
        menuContainer.style.backgroundColor = '#FFFFFF';
        menuContainer.style.border = '1px solid #FF9800';
        menuContainer.style.borderRadius = '6px';
        menuContainer.style.padding = '18px';
        menuContainer.style.zIndex = '99999';
        menuContainer.style.display = 'none';
        menuContainer.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.15)';
        menuContainer.style.color = '#444444';
        menuContainer.style.fontFamily = 'Arial, sans-serif';
        menuContainer.style.maxWidth = '300px';
        menuContainer.style.width = '90%'; // Tetap pakai ini agar responsif di layar kecil
        menuContainer.style.boxSizing = 'border-box'; // Penting untuk padding dan border tidak menambah lebar

        const menuTitle = document.createElement('h3');
        menuTitle.textContent = 'Customise Background';
        menuTitle.style.marginBottom = '18px';
        menuTitle.style.color = '#FB8C00';
        menuTitle.style.textAlign = 'center';
        menuTitle.style.fontSize = '1.1em';
        menuTitle.style.fontWeight = '600';
        menuContainer.appendChild(menuTitle);

        const notificationArea = document.createElement('div');
        notificationArea.id = 'gartic-bg-notification';
        notificationArea.style.opacity = '0';
        notificationArea.style.transition = 'opacity 0.3s ease-in-out, padding 0.3s ease-in-out, margin-bottom 0.3s ease-in-out';
        notificationArea.style.overflow = 'hidden';
        notificationArea.style.maxHeight = '50px';
        menuContainer.appendChild(notificationArea);

        const fileInputLabel = document.createElement('label');
        fileInputLabel.textContent = 'Select Image File:';
        fileInputLabel.style.display = 'block';
        fileInputLabel.style.marginBottom = '6px';
        fileInputLabel.style.fontWeight = 'bold';
        fileInputLabel.style.fontSize = '0.9em';
        menuContainer.appendChild(fileInputLabel);

        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'image/jpeg, image/png, image/gif';
        fileInput.style.width = '100%'; // Atur lebar ke 100% dari parent (menuContainer)
        fileInput.style.padding = '8px 0';
        fileInput.style.marginBottom = '15px';
        fileInput.style.backgroundColor = 'transparent';
        fileInput.style.border = 'none';
        fileInput.style.color = '#444444';
        fileInput.style.cursor = 'pointer';
        fileInput.style.fontSize = '0.9em';
        fileInput.style.boxSizing = 'border-box'; // Pastikan padding tidak membuat lebar melebihi 100%
        menuContainer.appendChild(fileInput);

        const divider = document.createElement('hr');
        divider.style.borderColor = '#FFCC80';
        divider.style.margin = '18px 0';
        menuContainer.appendChild(divider);

        const inputUrlLabel = document.createElement('label');
        inputUrlLabel.textContent = 'Or Enter Image URL or Hex Colour:';
        inputUrlLabel.style.display = 'block';
        inputUrlLabel.style.marginBottom = '6px';
        inputUrlLabel.style.fontWeight = 'bold';
        inputUrlLabel.style.fontSize = '0.9em';
        menuContainer.appendChild(inputUrlLabel);

        const imageUrlInput = document.createElement('input');
        imageUrlInput.type = 'text';
        imageUrlInput.placeholder = 'e.g., #FF9800 or https://example.com/image.jpg';
        imageUrlInput.style.width = '100%'; // Atur lebar ke 100% dari parent (menuContainer)
        imageUrlInput.style.padding = '9px';
        imageUrlInput.style.marginBottom = '20px';
        imageUrlInput.style.backgroundColor = '#FFF3E0';
        imageUrlInput.style.border = '1px solid #FFB74D';
        imageUrlInput.style.borderRadius = '4px';
        imageUrlInput.style.color = '#444444';
        imageUrlInput.style.fontSize = '0.9em';
        imageUrlInput.style.boxSizing = 'border-box'; // Pastikan padding dan border tidak membuat lebar melebihi 100%
        menuContainer.appendChild(imageUrlInput);

        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.gap = '10px';
        menuContainer.appendChild(buttonContainer);

        const applyButton = document.createElement('button');
        applyButton.textContent = 'Apply';
        applyButton.style.flexGrow = '1';
        applyButton.style.backgroundColor = '#FF9800';
        applyButton.style.color = '#FFFFFF';
        applyButton.style.border = 'none';
        applyButton.style.padding = '10px 15px';
        applyButton.style.borderRadius = '5px';
        applyButton.style.cursor = 'pointer';
        applyButton.style.fontWeight = 'bold';
        applyButton.style.fontSize = '0.9em';
        applyButton.style.transition = 'background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease';
        applyButton.onmouseover = () => { applyButton.style.backgroundColor = '#F57C00'; applyButton.style.boxShadow = '0 2px 6px rgba(0,0,0,0.2)'; };
        applyButton.onmouseout = () => { applyButton.style.backgroundColor = '#FF9800'; applyButton.style.boxShadow = 'none'; };
        applyButton.onmousedown = () => applyButton.style.transform = 'scale(0.98)';
        applyButton.onmouseup = () => applyButton.style.transform = 'scale(1)';

        applyButton.onclick = () => {
            if (fileInput.files.length > 0) {
                const file = fileInput.files[0];
                const reader = new FileReader();
                reader.onloadend = function() {
                    const dataUrl = reader.result;
                    applyCustomBackground(dataUrl);
                    localStorage.setItem('gartic_custom_background', dataUrl);
                    showNotification('Background from file applied successfully!');
                };
                reader.onerror = function() {
                    showNotification('Failed to read file.', true);
                };
                reader.readAsDataURL(file);
            } else {
                const url = imageUrlInput.value.trim();
                if (url) {
                    applyCustomBackground(url);
                    localStorage.setItem('gartic_custom_background', url);
                    showNotification('Background from URL or colour applied successfully!');
                } else {
                    showNotification('Please select an image file or enter an image URL or hex colour.', true);
                }
            }
            fileInput.value = '';
            imageUrlInput.value = '';
        };
        buttonContainer.appendChild(applyButton);

        const resetButton = document.createElement('button');
        resetButton.textContent = 'Reset';
        resetButton.style.flexGrow = '1';
        resetButton.style.backgroundColor = '#FFB74D';
        resetButton.style.color = '#444444';
        resetButton.style.border = 'none';
        resetButton.style.padding = '10px 15px';
        resetButton.style.borderRadius = '5px';
        resetButton.style.cursor = 'pointer';
        resetButton.style.fontWeight = 'bold';
        resetButton.style.fontSize = '0.9em';
        resetButton.style.transition = 'background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease';
        resetButton.onmouseover = () => { resetButton.style.backgroundColor = '#FFA726'; resetButton.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)'; };
        resetButton.onmouseout = () => { resetButton.style.backgroundColor = '#FFB74D'; resetButton.style.boxShadow = 'none'; };
        resetButton.onmousedown = () => resetButton.style.transform = 'scale(0.98)';
        resetButton.onmouseup = () => resetButton.style.transform = 'scale(1)';

        resetButton.onclick = () => {
            resetBackground();
            imageUrlInput.value = '';
            fileInput.value = '';
            showNotification('Background has been reset to default.');
        };
        buttonContainer.appendChild(resetButton);

        document.body.appendChild(menuContainer);

        const menuToggleButton = document.createElement('button');
        menuToggleButton.textContent = '🖼️';
        menuToggleButton.id = 'gartic-custom-bg-toggle';
        menuToggleButton.classList.add('gartic-userscript-button-left');

        menuToggleButton.style.position = 'fixed';
        menuToggleButton.style.left = '15px';
        menuToggleButton.style.top = '15px';
        menuToggleButton.style.backgroundColor = '#FF9800';
        menuToggleButton.style.color = '#FFFFFF';
        menuToggleButton.style.border = 'none';
        menuToggleButton.style.padding = '8px 14px';
        menuToggleButton.style.borderRadius = '5px';
        menuToggleButton.style.cursor = 'pointer';
        menuToggleButton.style.zIndex = '100000';
        menuToggleButton.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.15)';
        menuToggleButton.style.fontWeight = 'bold';
        menuToggleButton.style.fontSize = '0.9em';
        menuToggleButton.style.transition = 'background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease';
        menuToggleButton.onmouseover = () => { menuToggleButton.style.backgroundColor = '#F57C00'; menuToggleButton.style.boxShadow = '0 3px 7px rgba(0,0,0,0.2)'; };
        menuToggleButton.onmouseout = () => { menuToggleButton.style.backgroundColor = '#FF9800'; menuToggleButton.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.15)'; };
        menuToggleButton.onmousedown = () => menuToggleButton.style.transform = 'scale(0.98)';
        menuToggleButton.onmouseup = () => menuToggleButton.style.transform = 'scale(1)';

        menuToggleButton.onclick = (event) => {
            event.stopPropagation();
            if (menuContainer.style.display === 'none') {
                menuContainer.style.display = 'block';
                const toggleRect = menuToggleButton.getBoundingClientRect();
                menuContainer.style.left = `${toggleRect.right + 15}px`;
                menuContainer.style.top = `${toggleRect.top}px`;

                if (parseFloat(menuContainer.style.top) < 5) {
                    menuContainer.style.top = '5px';
                }
                if (parseFloat(menuContainer.style.top) + menuContainer.offsetHeight > window.innerHeight - 5) {
                    menuContainer.style.top = `${window.innerHeight - menuContainer.offsetHeight - 5}px`;
                }

                imageUrlInput.value = '';
                fileInput.value = '';
                notificationArea.style.opacity = '0';
                notificationArea.style.padding = '0';
                notificationArea.style.marginBottom = '0';
            } else {
                menuContainer.style.display = 'none';
            }
        };
        document.body.appendChild(menuToggleButton);

        document.addEventListener('click', (event) => {
            if (menuContainer.style.display === 'block' &&
                !menuContainer.contains(event.target) &&
                !menuToggleButton.contains(event.target)) {
                menuContainer.style.display = 'none';
            }
        });
    }

    // Fungsi ini akan menerapkan gaya awal dan menangani background yang tersimpan.
    // CSS bawaan untuk menu userscript kita akan diatur secara inline di createSettingsMenu
    // untuk memastikan tidak ada konflik dengan theme selector.
    injectCustomCss();

    let savedBackground = localStorage.getItem('gartic_custom_background');
    if (!savedBackground || savedBackground === oldDefaultOrange) {
        applyCustomBackground(defaultBackgroundColor);
        localStorage.setItem('gartic_custom_background', defaultBackgroundColor);
    } else {
        applyCustomBackground(savedBackground);
    }

    const observer = new MutationObserver((mutationsList, observer) => {
        const garticCoreElement = document.querySelector('.main-menu') || document.querySelector('.game-container') || document.body;

        if (garticCoreElement) {
            createSettingsMenu();
            observer.disconnect();
        } else if (document.body && !document.getElementById('gartic-custom-bg-menu')) {
            setTimeout(() => {
                if (!document.getElementById('gartic-custom-bg-menu')) {
                    createSettingsMenu();
                }
            }, 1000);
            observer.disconnect();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    document.addEventListener('DOMContentLoaded', () => {
        if (!document.getElementById('gartic-custom-bg-menu')) {
            setTimeout(createSettingsMenu, 1500);
        }
    });

})();