osu! BackgroundGrabber

Seamlessly adds a stylish background download button to osu! beatmap pages - grab those beautiful covers with one click!

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey to install this script.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name          osu! BackgroundGrabber
// @namespace     http://tampermonkey.net/
// @version       1.4
// @description   Seamlessly adds a stylish background download button to osu! beatmap pages - grab those beautiful covers with one click!
// @author        Noxie
// @match         https://osu.ppy.sh/*
// @icon          https://raw.githubusercontent.com/Noxie0/osu-background-grabber/refs/heads/main/icon.png
// @license       MIT
// @grant         none
// ==/UserScript==

(function () {
    'use strict';

    // Settings management
    const SETTINGS_KEY = 'osu_backgroundgrabber_settings';
    const DEFAULT_COLOR = '#3986ac';
    const defaultSettings = {
        buttonEnabled: true,
        textEnabled: true,
        iconEnabled: true,
        accentColor: '#ff6bb3',
        useCustomColor: true
    };

    function getSettings() {
        try {
            const saved = localStorage.getItem(SETTINGS_KEY);
            return saved ? { ...defaultSettings, ...JSON.parse(saved) } : defaultSettings;
        } catch (e) {
            console.warn('[BackgroundGrabber] Failed to load settings, using defaults:', e);
            return defaultSettings;
        }
    }

    function saveSettings(settings) {
        try {
            localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
        } catch (e) {
            console.warn('[BackgroundGrabber] Failed to save settings:', e);
        }
    }

    let currentSettings = getSettings();

    function hexToRgba(hex, alpha = 1) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }

    function getEffectiveColor() {
        return currentSettings.useCustomColor ? currentSettings.accentColor : DEFAULT_COLOR;
    }

    function updateColorVariables() {
        const root = document.documentElement;
        const effectiveColor = getEffectiveColor();
        root.style.setProperty('--bg-grabber-accent', effectiveColor);
        root.style.setProperty('--bg-grabber-accent-90', hexToRgba(effectiveColor, 0.9));
        root.style.setProperty('--bg-grabber-accent-100', hexToRgba(effectiveColor, 1));
        const colorPreview = document.getElementById('bg-color-preview');
        if (colorPreview) {
            colorPreview.style.background = effectiveColor;
        }
    }

    const style = document.createElement('style');
    style.textContent = `
        :root {
            --bg-grabber-accent: ${getEffectiveColor()};
            --bg-grabber-accent-90: ${hexToRgba(getEffectiveColor(), 0.9)};
            --bg-grabber-accent-100: ${hexToRgba(getEffectiveColor(), 1)};
        }

        .background-btn {
            min-width: 120px !important;
            white-space: nowrap !important;
            padding: 0 20px !important;
            height: auto !important;
            display: inline-flex !important;
            align-items: center !important;
            justify-content: center !important;
            transition: all 0.2s ease !important;
            background-color: var(--bg-grabber-accent) !important;
            border-color: var(--bg-grabber-accent) !important;
        }
        .background-btn:hover {
            background-color: var(--bg-grabber-accent-100) !important;
            border-color: var(--bg-grabber-accent-100) !important;
        }
        .background-btn .fa-image {
            font-size: 16px !important;
            margin-right: 8px !important;
            line-height: 1 !important;
        }
        .background-btn.icon-only .fa-image {
            font-size: 20px !important;
            margin-right: 0 !important;
        }
        .background-btn span {
            font-size: 14px !important;
            font-weight: 600 !important;
            line-height: 1 !important;
        }

        .bg-grabber-settings {
            position: fixed;
            top: 70px;
            right: 20px;
            background: #2a2a2a;
            color: white;
            padding: 20px;
            border-radius: 10px;
            border: 2px solid var(--bg-grabber-accent);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
            z-index: 10000;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 14px;
            min-width: 300px;

            opacity: 0;
            visibility: hidden;
            transform: translateY(10px);
            transition: opacity 0.3s ease-out, visibility 0.3s ease-out, transform 0.3s ease-out;
            pointer-events: none;
        }

        .bg-grabber-settings.show {
            opacity: 1;
            visibility: visible;
            transform: translateY(0);
            pointer-events: auto;
        }

        .bg-grabber-settings h3 {
            margin: 0 0 20px 0;
            font-size: 18px;
            color: var(--bg-grabber-accent);
            border-bottom: 2px solid var(--bg-grabber-accent);
            padding-bottom: 8px;
            text-align: center;
        }

        .bg-grabber-setting-item {
            display: flex;
            align-items: center;
            margin-bottom: 15px;
            padding: 8px 0;
            border-bottom: 1px solid #444;
        }

        .bg-grabber-setting-item:last-of-type {
            border-bottom: none;
            margin-bottom: 0;
        }

        .bg-grabber-setting-item label {
            flex: 1;
            cursor: pointer;
            font-weight: 500;
        }

        .bg-grabber-setting-item input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
            margin-right: 12px;
            accent-color: var(--bg-grabber-accent);
        }

        .bg-grabber-setting-item input[type="checkbox"]:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .bg-grabber-setting-item label.disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .bg-grabber-color-section {
            display: flex;
            flex-direction: column;
            gap: 8px;
            margin-bottom: 10px;
        }

        .bg-grabber-color-controls {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .bg-grabber-color-picker {
            width: 40px;
            height: 30px;
            border: 2px solid var(--bg-grabber-accent);
            border-radius: 6px;
            cursor: pointer;
            background: var(--bg-grabber-accent);
            transition: all 0.2s ease;
        }

        .bg-grabber-color-picker:hover {
            transform: scale(1.05);
        }

        .bg-grabber-color-picker:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }

        .bg-grabber-hex-input {
            flex: 1;
            background: #1a1a1a;
            border: 1px solid #555;
            border-radius: 4px;
            padding: 6px 10px;
            color: white;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            transition: border-color 0.2s ease;
        }

        .bg-grabber-hex-input:focus {
            outline: none;
            border-color: var(--bg-grabber-accent);
        }

        .bg-grabber-hex-input:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .bg-grabber-hex-input.invalid {
            border-color: #ff4444;
        }

        .bg-grabber-color-preview {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            border: 2px solid #555;
            background: var(--bg-grabber-accent);
            transition: all 0.2s ease;
        }

        .bg-grabber-reset-btn {
            background: #666;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 6px 12px;
            cursor: pointer;
            font-size: 12px;
            font-weight: 500;
            transition: all 0.2s ease;
            white-space: nowrap;
        }

        .bg-grabber-reset-btn:hover {
            background: #777;
        }

        .bg-grabber-reset-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            background: #666;
        }

        .bg-grabber-settings-footer {
            margin-top: 20px;
            padding-top: 15px;
            border-top: 1px solid #444;
            display: flex;
            justify-content: space-around;
            gap: 10px;
        }

        .bg-grabber-settings-footer button,
        .bg-grabber-settings-footer a {
            flex: 1;
            padding: 8px 12px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 13px;
            font-weight: 600;
            text-align: center;
            text-decoration: none;
            transition: background-color 0.2s ease, transform 0.1s ease;
            color: white;
        }

        .bg-grabber-settings-footer .github-btn {
            background-color: #333;
        }

        .bg-grabber-settings-footer .github-btn:hover {
            background-color: #555;
            transform: translateY(-1px);
        }

        .bg-grabber-settings-footer .bug-report-btn {
            background-color: #d9534f;
        }

        .bg-grabber-settings-footer .bug-report-btn:hover {
            background-color: #c9302c;
            transform: translateY(-1px);
        }

        .bg-grabber-settings-icon {
            position: fixed;
            top: 20px;
            right: 20px;
            background: var(--bg-grabber-accent-90);
            color: white;
            border: none;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            cursor: pointer;
            font-size: 18px;
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 9999;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
            transition: all 0.2s ease;
            opacity: 0;
            visibility: hidden;
            pointer-events: none;
        }

        .bg-grabber-settings-icon.visible {
            opacity: 1;
            visibility: visible;
            pointer-events: auto;
        }

        .bg-grabber-settings-icon:hover {
            background: var(--bg-grabber-accent-100);
            transform: scale(1.1);
        }
    `;
    document.head.appendChild(style);

    updateColorVariables();

    function isValidHex(hex) {
        return /^#[0-9A-F]{6}$/i.test(hex);
    }

    function createSettingsPanel() {
        const panel = document.createElement('div');
        panel.className = 'bg-grabber-settings';
        panel.innerHTML = `
            <h3>Background Grabber Settings</h3>
            <div class="bg-grabber-setting-item">
                <input type="checkbox" id="bg-button-toggle" ${currentSettings.buttonEnabled ? 'checked' : ''}>
                <label for="bg-button-toggle">Enable Button</label>
            </div>
            <div class="bg-grabber-setting-item">
                <input type="checkbox" id="bg-text-toggle" ${currentSettings.textEnabled ? 'checked' : ''}>
                <label for="bg-text-toggle">Show Text</label>
            </div>
            <div class="bg-grabber-setting-item">
                <input type="checkbox" id="bg-icon-toggle" ${currentSettings.iconEnabled ? 'checked' : ''}>
                <label for="bg-icon-toggle">Show Icon</label>
            </div>
            <div class="bg-grabber-setting-item">
                <input type="checkbox" id="bg-custom-color-toggle" ${currentSettings.useCustomColor ? 'checked' : ''}>
                <label for="bg-custom-color-toggle">Use Custom Color</label>
            </div>
            <div class="bg-grabber-setting-item">
                <label>Accent Color</label>
            </div>
            <div class="bg-grabber-color-section">
                <div class="bg-grabber-color-controls">
                    <input type="color" class="bg-grabber-color-picker" value="${currentSettings.accentColor}" id="bg-color-picker" ${!currentSettings.useCustomColor ? 'disabled' : ''}>
                    <input type="text" class="bg-grabber-hex-input" value="${currentSettings.accentColor}" id="bg-hex-input" placeholder="#ff6bb3" ${!currentSettings.useCustomColor ? 'disabled' : ''}>
                    <button class="bg-grabber-reset-btn" id="bg-reset-btn" ${!currentSettings.useCustomColor ? 'disabled' : ''}>Reset</button>
                    <div class="bg-grabber-color-preview" id="bg-color-preview" style="background: ${getEffectiveColor()};"></div>
                </div>
            </div>
            <div class="bg-grabber-settings-footer">
                <a href="https://github.com/Noxie0/osu-BackgroundGrabber" target="_blank" rel="noopener noreferrer" class="github-btn">
                    GitHub
                </a>
                <a href="https://github.com/Noxie0/osu-BackgroundGrabber/issues" target="_blank" rel="noopener noreferrer" class="bug-report-btn">
                    Report a Bug
                </a>
            </div>
        `;

        const buttonToggle = panel.querySelector('#bg-button-toggle');
        const textToggle = panel.querySelector('#bg-text-toggle');
        const iconToggle = panel.querySelector('#bg-icon-toggle');
        const customColorToggle = panel.querySelector('#bg-custom-color-toggle');
        const colorPicker = panel.querySelector('#bg-color-picker');
        const hexInput = panel.querySelector('#bg-hex-input');
        const colorPreview = panel.querySelector('#bg-color-preview');
        const resetBtn = panel.querySelector('#bg-reset-btn');

        resetBtn.addEventListener('click', () => {
            if (!currentSettings.useCustomColor) return;
            currentSettings.accentColor = DEFAULT_COLOR;
            colorPicker.value = DEFAULT_COLOR;
            hexInput.value = DEFAULT_COLOR;
            hexInput.classList.remove('invalid');
            updateColorVariables();
            saveSettings(currentSettings);
            updateButtonContent();
        });

        customColorToggle.addEventListener('change', (e) => {
            currentSettings.useCustomColor = e.target.checked;
            colorPicker.disabled = !currentSettings.useCustomColor;
            hexInput.disabled = !currentSettings.useCustomColor;
            resetBtn.disabled = !currentSettings.useCustomColor;
            updateColorVariables();
            saveSettings(currentSettings);
            updateButtonContent();
        });

        colorPicker.addEventListener('input', (e) => {
            if (!currentSettings.useCustomColor) return;
            const newColor = e.target.value;
            currentSettings.accentColor = newColor;
            hexInput.value = newColor;
            hexInput.classList.remove('invalid');
            updateColorVariables();
            saveSettings(currentSettings);
            updateButtonContent();
        });

        hexInput.addEventListener('input', (e) => {
            if (!currentSettings.useCustomColor) return;
            const newColor = e.target.value;
            if (isValidHex(newColor)) {
                currentSettings.accentColor = newColor;
                colorPicker.value = newColor;
                hexInput.classList.remove('invalid');
                updateColorVariables();
                saveSettings(currentSettings);
                updateButtonContent();
            } else {
                hexInput.classList.add('invalid');
            }
        });

        buttonToggle.addEventListener('change', (e) => {
            currentSettings.buttonEnabled = e.target.checked;
            if (currentSettings.buttonEnabled && !currentSettings.textEnabled && !currentSettings.iconEnabled) {
                currentSettings.textEnabled = true;
                textToggle.checked = true;
            }
            saveSettings(currentSettings);
            updateButtonVisibility();
            updateSettingsState();
            tryInjectButton(); // Always try to re-inject/update button
        });

        textToggle.addEventListener('change', (e) => {
            currentSettings.textEnabled = e.target.checked;
            if (!currentSettings.textEnabled && !currentSettings.iconEnabled) {
                currentSettings.buttonEnabled = false;
                buttonToggle.checked = false;
                updateSettingsState();
            }
            saveSettings(currentSettings);
            updateButtonContent();
            updateButtonVisibility();
            tryInjectButton(); // Always try to re-inject/update button
        });

        iconToggle.addEventListener('change', (e) => {
            currentSettings.iconEnabled = e.target.checked;
            if (!currentSettings.iconEnabled && !currentSettings.textEnabled) {
                currentSettings.buttonEnabled = false;
                buttonToggle.checked = false;
                updateSettingsState();
            }
            saveSettings(currentSettings);
            updateButtonContent();
            updateButtonVisibility();
            tryInjectButton(); // Always try to re-inject/update button
        });

        updateSettingsState();
        return panel;
    }

    function createSettingsIcon() {
        const icon = document.createElement('button');
        icon.className = 'bg-grabber-settings-icon';
        icon.innerHTML = '⚙️';
        icon.title = 'Background Grabber Settings';
        icon.addEventListener('click', (event) => {
            event.stopPropagation();
            const panel = document.querySelector('.bg-grabber-settings');
            if (panel) {
                panel.classList.toggle('show');
            }
        });
        return icon;
    }

    function updateButtonVisibility() {
        const button = document.querySelector('.background-btn');
        if (button) {
            if (currentSettings.buttonEnabled && (currentSettings.textEnabled || currentSettings.iconEnabled)) {
                button.style.display = 'inline-flex';
                button.style.visibility = 'visible';
            } else {
                button.style.display = 'none';
                button.style.visibility = 'hidden';
            }
        }
    }

    function updateSettingsIconVisibility() {
        const icon = document.querySelector('.bg-grabber-settings-icon');
        if (icon) {
            const isOnBeatmapPage = window.location.pathname.includes('/beatmapsets/');
            if (isOnBeatmapPage) {
                icon.classList.add('visible');
                ensureSettingsPanel();
            } else {
                icon.classList.remove('visible');
                const panel = document.querySelector('.bg-grabber-settings');
                if (panel) {
                    panel.classList.remove('show');
                }
            }
        }
    }

    function updateSettingsState() {
        const textToggle = document.querySelector('#bg-text-toggle');
        const iconToggle = document.querySelector('#bg-icon-toggle');
        const textLabel = document.querySelector('label[for="bg-text-toggle"]');
        const iconLabel = document.querySelector('label[for="bg-icon-toggle"]');
        const buttonToggle = document.querySelector('#bg-button-toggle');

        if (textToggle && iconToggle && textLabel && iconLabel && buttonToggle) {
            const isDisabled = !currentSettings.buttonEnabled;
            textToggle.disabled = isDisabled;
            iconToggle.disabled = isDisabled;

            if (isDisabled) {
                textLabel.classList.add('disabled');
                iconLabel.classList.add('disabled');
            } else {
                textLabel.classList.remove('disabled');
                iconLabel.classList.remove('disabled');
                if (!textToggle.checked && !iconToggle.checked) {
                    currentSettings.textEnabled = true;
                    textToggle.checked = true;
                    saveSettings(currentSettings);
                    updateButtonContent();
                }
            }
        }

        const customColorToggle = document.querySelector('#bg-custom-color-toggle');
        const colorPicker = document.querySelector('#bg-color-picker');
        const hexInput = document.querySelector('#bg-hex-input');
        const resetBtn = document.querySelector('#bg-reset-btn');

        if (customColorToggle && colorPicker && hexInput && resetBtn) {
            const colorControlsDisabled = !currentSettings.useCustomColor;
            colorPicker.disabled = colorControlsDisabled;
            hexInput.disabled = colorControlsDisabled;
            resetBtn.disabled = colorControlsDisabled;
        }
    }

    function updateButtonContent() {
        const button = document.querySelector('.background-btn');
        if (!button) return;

        const iconHtml = currentSettings.iconEnabled ? '<i class="fas fa-image"></i>' : '';
        const textHtml = currentSettings.textEnabled ? '<span>Background</span>' : '';
        button.innerHTML = iconHtml + textHtml;

        if (!currentSettings.iconEnabled && !currentSettings.textEnabled) {
            button.style.display = 'none';
            button.style.visibility = 'hidden';
            button.classList.remove('icon-only');
        } else if (currentSettings.iconEnabled && !currentSettings.textEnabled) {
            button.style.setProperty('padding', '0', 'important');
            button.style.setProperty('min-width', '45px', 'important');
            button.style.setProperty('width', '45px', 'important');
            button.style.setProperty('height', '45px', 'important');
            button.classList.add('icon-only');
        } else if (!currentSettings.iconEnabled && currentSettings.textEnabled) {
            button.style.setProperty('padding', '0 16px', 'important');
            button.style.setProperty('min-width', '80px', 'important');
            button.style.setProperty('width', 'auto', 'important');
            button.style.setProperty('height', 'auto', 'important');
            button.classList.remove('icon-only');
        } else {
            button.style.setProperty('padding', '0 20px', 'important');
            button.style.setProperty('min-width', '120px', 'important');
            button.style.setProperty('width', 'auto', 'important');
            button.style.setProperty('height', 'auto', 'important');
            button.classList.remove('icon-only');
        }
        updateButtonVisibility();
    }

    function createButton(beatmapSetId, container) {
        const rawUrl = `https://assets.ppy.sh/beatmaps/${beatmapSetId}/covers/raw.jpg`;
        const fallbackUrl = `https://assets.ppy.sh/beatmaps/${beatmapSetId}/covers/cover.jpg`;

        const existingButtonReference = container.querySelector('a[class*="btn"], button[class*="btn"]');
        const bgBtn = document.createElement('a');

        bgBtn.className = existingButtonReference ? existingButtonReference.className : 'btn-osu-big btn-osu-big--beatmapset-header';
        bgBtn.classList.add('background-btn');
        bgBtn.href = '#';
        bgBtn.target = '_blank';
        bgBtn.rel = 'noopener noreferrer';

        bgBtn.addEventListener('click', (e) => {
            e.preventDefault();
            const testImg = new Image();
            testImg.onload = () => window.open(rawUrl, '_blank');
            testImg.onerror = () => window.open(fallbackUrl, '_blank');
            testImg.src = rawUrl;
        });

        container.appendChild(bgBtn);
        updateButtonContent();
        updateButtonVisibility();
    }

    // --- NEW: MutationObserver for the button container ---
    let buttonContainerObserver = null;
    let lastBeatmapSetId = null; // To track if we are on the same beatmapset page

    function tryInjectButton() {
        const match = window.location.pathname.match(/\/beatmapsets\/(\d+)/);
        const container = document.querySelector('.beatmapset-header__buttons');
        const existingButton = document.querySelector('.background-btn');

        if (!match) { // Not on a beatmap page
            if (existingButton) existingButton.remove();
            if (buttonContainerObserver) {
                buttonContainerObserver.disconnect();
                buttonContainerObserver = null;
            }
            lastBeatmapSetId = null;
            return;
        }

        const currentBeatmapSetId = match[1];

        // If we are on a new beatmapset page, or button is missing, re-create
        if (currentBeatmapSetId !== lastBeatmapSetId || !existingButton || !container || !container.contains(existingButton)) {
            if (existingButton) existingButton.remove(); // Clean up old button
            if (container) {
                createButton(currentBeatmapSetId, container);
                lastBeatmapSetId = currentBeatmapSetId; // Update last known ID

                // Set up observer if not already active for this container
                if (!buttonContainerObserver) {
                    buttonContainerObserver = new MutationObserver((mutationsList) => {
                        for (const mutation of mutationsList) {
                            if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
                                // Check if our button was removed
                                const ourButtonRemoved = Array.from(mutation.removedNodes).some(node => node.classList && node.classList.contains('background-btn'));
                                if (ourButtonRemoved) {
                                    // Button was removed, re-inject immediately
                                    // Use a small timeout to allow React to finish its immediate DOM operations
                                    setTimeout(() => tryInjectButton(), 50);
                                    break; // Only need to react once
                                }
                            }
                        }
                    });
                    buttonContainerObserver.observe(container, { childList: true });
                }
            }
        } else if (existingButton) {
            // Button exists and is in place, just update its content/visibility
            updateButtonContent();
            updateButtonVisibility();
        }
    }

    function ensureSettingsPanel() {
        let existingPanel = document.querySelector('.bg-grabber-settings');
        if (!existingPanel) {
            const settingsPanel = createSettingsPanel();
            document.body.appendChild(settingsPanel);
            updateSettingsState();
        } else {
            updateSettingsState();
        }
    }

    // --- NEW: MutationObserver for the settings icon ---
    let settingsIconObserver = null;

    function tryInjectSettingsIcon() {
        currentSettings = getSettings();
        updateColorVariables();

        let existingIcon = document.querySelector('.bg-grabber-settings-icon');

        if (!window.location.pathname.includes('/beatmapsets/')) { // Not on beatmap page
            if (existingIcon) existingIcon.remove();
            if (settingsIconObserver) {
                settingsIconObserver.disconnect();
                settingsIconObserver = null;
            }
            return;
        }

        if (!existingIcon) {
            const settingsIcon = createSettingsIcon();
            document.body.appendChild(settingsIcon);
            existingIcon = settingsIcon;

            // Set up observer for the icon if not already active
            if (!settingsIconObserver) {
                settingsIconObserver = new MutationObserver((mutationsList) => {
                    for (const mutation of mutationsList) {
                        if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
                            const ourIconRemoved = Array.from(mutation.removedNodes).some(node => node.classList && node.classList.contains('bg-grabber-settings-icon'));
                            if (ourIconRemoved) {
                                setTimeout(() => tryInjectSettingsIcon(), 50);
                                break;
                            }
                        }
                    }
                });
                // Observe the body, as the icon is directly appended to it
                settingsIconObserver.observe(document.body, { childList: true });
            }
        }
        updateSettingsIconVisibility();
    }

    function waitForContainer(callback, attempts = 0) {
        if (attempts >= 50) return;
        const container = document.querySelector('.beatmapset-header__buttons');
        if (container) {
            callback();
        } else {
            setTimeout(() => waitForContainer(callback, attempts + 1), 100); // Shorter interval
        }
    }

    function waitForBody(callback, attempts = 0) {
        if (attempts >= 50) return;
        if (document.body) {
            callback();
        } else {
            setTimeout(() => waitForBody(callback, attempts + 1), 100); // Shorter interval
        }
    }

    function setupObservers() {
        let lastPath = location.pathname;

        const reactRouteObserver = new MutationObserver(() => {
            if (location.pathname !== lastPath) {
                lastPath = location.pathname;
                // Still use a generous delay for full route changes
                setTimeout(() => {
                    tryInjectButton();
                    tryInjectSettingsIcon();
                }, 600); // Slightly reduced from 800ms
            }
        });

        waitForBody(() => {
            reactRouteObserver.observe(document.body, { childList: true, subtree: true });
        });

        // General DOM content observer - now less critical due to specific observers
        const domContentObserver = new MutationObserver((mutations) => {
            let shouldCheck = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    if (mutation.target.matches('.beatmapset-header__buttons') ||
                        mutation.target.closest('.beatmapset-header__buttons') ||
                        mutation.target.id === 'app' ||
                        mutation.target.tagName === 'MAIN' ||
                        mutation.target.matches('body')) {
                        shouldCheck = true;
                        break;
                    }
                }
            }

            if (shouldCheck) {
                setTimeout(() => {
                    tryInjectButton();
                    tryInjectSettingsIcon();
                }, 100); // Shorter delay
            }
        });

        waitForBody(() => {
            const reactAppRoot = document.getElementById('app') || document.getElementById('root') || document.body;
            domContentObserver.observe(reactAppRoot, { childList: true, subtree: true });
        });

        window.addEventListener('popstate', () => {
            setTimeout(() => {
                tryInjectButton();
                tryInjectSettingsIcon();
            }, 600);
        });

        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function(...args) {
            originalPushState.apply(history, args);
            setTimeout(() => {
                tryInjectButton();
                tryInjectSettingsIcon();
            }, 600);
        };

        history.replaceState = function(...args) {
            originalReplaceState.apply(history, args);
            setTimeout(() => {
                tryInjectButton();
                tryInjectSettingsIcon();
            }, 600);
        };
    }

    // Initial setup when the script loads
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setupObservers();
            setTimeout(() => {
                tryInjectButton();
                tryInjectSettingsIcon();
                ensureSettingsPanel();
            }, 50); // Very short initial delay
        });
    } else {
        setupObservers();
        setTimeout(() => {
            tryInjectButton();
            tryInjectSettingsIcon();
            ensureSettingsPanel();
        }, 50); // Very short initial delay
    }

    waitForBody(ensureSettingsPanel);

})();