GeoGuessr Focus Mode

Activates a Focus Mode in GeoGuessr Duels which hides opponent names and Rank/Elo automatically. You can turn the mode on and off in the Duels lobby.

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         GeoGuessr Focus Mode
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Activates a Focus Mode in GeoGuessr Duels which hides opponent names and Rank/Elo automatically. You can turn the mode on and off in the Duels lobby.
// @author       AaronThug
// @license      MIT
// @icon         https://www.geoguessr.com/favicon.ico
// @match        https://www.geoguessr.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let focusModeEnabled = false;
    let MY_USERNAME = '';

    function createFocusModeButton() {
        if (document.querySelector('#focus-mode-container')) return;
        if (window.location.pathname !== '/multiplayer') return;
        const gameModeContainer = document.querySelector('.player-section_gameModeContainer__KMM5F');
        if (!gameModeContainer) return;

        const outerContainer = document.createElement('div');
        outerContainer.id = 'focus-mode-container';
        outerContainer.style.cssText = `
            margin-bottom: 20px;
            display: flex;
            justify-content: center;
        `;

        const panel = document.createElement('div');
        panel.style.cssText = `
            background: linear-gradient(145deg, #2a2550 0%, #1f1a42 100%);
            border: 2px solid #3d3764;
            border-radius: 20px;
            padding: 16px 24px;
            box-shadow: 
                0 8px 32px rgba(0, 0, 0, 0.4),
                inset 0 1px 0 rgba(255, 255, 255, 0.1);
            display: flex;
            align-items: center;
            gap: 16px;
            backdrop-filter: blur(10px);
        `;

        const label = document.createElement('span');
        label.textContent = 'Focus Mode';
        label.style.cssText = `
            font-family: 'neo-sans', 'Helvetica Neue', Arial, sans-serif;
            font-size: 16px;
            font-weight: 600;
            color: #ffffff;
            user-select: none;
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
        `;

        const toggleContainer = document.createElement('div');
        toggleContainer.style.cssText = `
            width: 52px;
            height: 28px;
            background: linear-gradient(145deg, #1a1538 0%, #0f0f2a 100%);
            border: 1px solid #4a4a6a;
            border-radius: 14px;
            position: relative;
            cursor: pointer;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 
                inset 0 2px 4px rgba(0, 0, 0, 0.5),
                0 1px 2px rgba(255, 255, 255, 0.1);
        `;

        const toggleKnob = document.createElement('div');
        toggleKnob.style.cssText = `
            width: 24px;
            height: 24px;
            background: linear-gradient(145deg, #f0f0f0 0%, #e0e0e0 100%);
            border: 1px solid #d0d0d0;
            border-radius: 50%;
            position: absolute;
            top: 2px;
            left: 2px;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 
                0 2px 6px rgba(0, 0, 0, 0.3),
                0 1px 2px rgba(0, 0, 0, 0.2);
        `;

        function updateToggleAppearance() {
            if (focusModeEnabled) {
                toggleContainer.style.background = 'linear-gradient(145deg, #4CAF50 0%, #45a049 100%)';
                toggleContainer.style.borderColor = '#388e3c';
                toggleContainer.style.boxShadow = `
                    inset 0 1px 2px rgba(255, 255, 255, 0.2),
                    0 2px 8px rgba(76, 175, 80, 0.4)`;
                toggleKnob.style.transform = 'translateX(24px)';
                toggleKnob.style.background = 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)';
                toggleKnob.style.boxShadow = `
                    0 3px 8px rgba(0, 0, 0, 0.4),
                    0 1px 3px rgba(0, 0, 0, 0.2)`;
            } else {
                toggleContainer.style.background = 'linear-gradient(145deg, #1a1538 0%, #0f0f2a 100%)';
                toggleContainer.style.borderColor = '#4a4a6a';
                toggleContainer.style.boxShadow = `
                    inset 0 2px 4px rgba(0, 0, 0, 0.5),
                    0 1px 2px rgba(255, 255, 255, 0.1)`;
                toggleKnob.style.transform = 'translateX(0)';
                toggleKnob.style.background = 'linear-gradient(145deg, #f0f0f0 0%, #e0e0e0 100%)';
                toggleKnob.style.boxShadow = `
                    0 2px 6px rgba(0, 0, 0, 0.3),
                    0 1px 2px rgba(0, 0, 0, 0.2)`;
            }
        }

        toggleContainer.addEventListener('click', () => {
            focusModeEnabled = !focusModeEnabled;
            updateToggleAppearance();
            localStorage.setItem('geoguessr-focus-mode', focusModeEnabled);
            if (focusModeEnabled) {
                setTimeout(() => {
                    if (!MY_USERNAME) detectMyUsername();
                    censorOpponentNames();
                }, 100);
            }
        });

        updateToggleAppearance();
        toggleContainer.appendChild(toggleKnob);
        panel.appendChild(label);
        panel.appendChild(toggleContainer);
        outerContainer.appendChild(panel);
        gameModeContainer.parentNode.insertBefore(outerContainer, gameModeContainer);
    }

    function detectMyUsername() {
        const duelsNickElement = document.querySelector('.user-nick_nick__sRjZ2.user-nick_visibleOverflow__oR46v');
        if (duelsNickElement && duelsNickElement.textContent) {
            const username = duelsNickElement.textContent.replace(/\s+/g, ' ').trim();
            if (username.length > 0 && username.length < 30) {
                MY_USERNAME = username;
                return true;
            }
        }
        try {
            const userData = localStorage.getItem('gg-user-data');
            if (userData) {
                const user = JSON.parse(userData);
                if (user.nick || user.username || user.name) {
                    MY_USERNAME = user.nick || user.username || user.name;
                    return true;
                }
            }
        } catch (e) {}
        const profileSelectors = [
            '[data-qa="navbar-profile-button"]',
            '.navbar-profile-button',
            '.profile-button'
        ];
        for (const selector of profileSelectors) {
            const element = document.querySelector(selector);
            if (element && element.textContent.trim()) {
                const username = element.textContent.trim();
                if (username.length > 0 && username.length < 30 && !username.includes('Profile')) {
                    MY_USERNAME = username;
                    return true;
                }
            }
        }
        return false;
    }

    function censorName(element) {
        if (!element || !element.textContent) return;
        const text = element.textContent.trim();
        if (text === MY_USERNAME) return;
        if (text.length < 2 || /^\d+$/.test(text)) return;
        element.textContent = '?';
    }

    function censorChatMessage(message) {
        if (window.location.pathname !== '/multiplayer') return;
        if (!message || !message.textContent || message.dataset.censored) return;
        const text = message.textContent.trim();
        if (text.includes('has guessed') || text.includes('guessed')) {
            const username = text.split(/has guessed|guessed/)[0].trim();
            if (username !== MY_USERNAME) {
                message.textContent = message.textContent.replace(username, '?');
                message.dataset.censored = 'true';
            }
        }
    }

    function censorOpponentNames() {
        if (window.location.pathname !== '/multiplayer') return;
        if (!focusModeEnabled) return;

        const matchmakingNames = document.querySelectorAll('.summon-glow-text_root__iYz_y');
        matchmakingNames.forEach(element => {
            if (element.closest('.ranked-leaderboard_root__kpVHS')) return;
            const textNodes = Array.from(element.childNodes).filter(node =>
                node.nodeType === Node.TEXT_NODE && node.textContent.trim()
            );
            textNodes.forEach(textNode => {
                const text = textNode.textContent.trim();
                if (text !== MY_USERNAME && text.length > 1) {
                    textNode.textContent = '?';
                    const shadowElement = element.querySelector('.summon-glow-text_shadowPlaceholder___MC0p');
                    if (shadowElement) shadowElement.textContent = '?';
                }
            });
        });

        const chatContainer = document.querySelector('.chat-log_scrollContainer__0fy56');
        if (chatContainer) {
            const messages = Array.from(chatContainer.children);
            messages.forEach(message => {
                if (!message.dataset.censored) {
                    censorChatMessage(message);
                }
            });
        }

        const healthBarNames = document.querySelectorAll('.health-bar-2_nick__dWcMx');
        healthBarNames.forEach(element => {
            if (element.closest('.ranked-leaderboard_root__kpVHS')) return;
            censorName(element);
        });

        const additionalSelectors = [
            '[class*="nick"]:not(.user-nick_visibleOverflow__oR46v)',
            '[class*="player-name"]',
            '[class*="opponent"]'
        ];
        additionalSelectors.forEach(selector => {
            const elements = document.querySelectorAll(selector);
            elements.forEach(element => {
                if (element.closest('.side-tray_body__30bbe')) return;
                if (element.closest('.ranked-leaderboard_root__kpVHS')) return;
                if (!element.classList.contains('summon-glow-text_root__iYz_y') &&
                    !element.classList.contains('health-bar-2_nick__dWcMx') &&
                    !element.classList.contains('user-nick_visibleOverflow__oR46v')) {
                    if (element.textContent && element.textContent.trim() && element.textContent.trim().length > 1) {
                        censorName(element);
                    }
                }
            });
        });
    }

    const observer = new MutationObserver(function(mutations) {
        let shouldUpdate = false;
        for (const mutation of mutations) {
            if (
                window.location.pathname === '/multiplayer' &&
                mutation.target.classList?.contains('chat-log_scrollContainer__0fy56')
            ) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === 1 && !node.dataset.censored) {
                        censorChatMessage(node);
                    }
                }
                continue;
            }
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                shouldUpdate = true;
                break;
            }
        }
        if (shouldUpdate && focusModeEnabled && window.location.pathname === '/multiplayer') {
            setTimeout(() => {
                if (window.location.pathname === '/multiplayer') {
                    createFocusModeButton();
                    if (!MY_USERNAME) detectMyUsername();
                    censorOpponentNames();
                }
            }, 200);
        }
    });

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

    const savedStatus = localStorage.getItem('geoguessr-focus-mode');
    if (savedStatus === 'true') {
        focusModeEnabled = true;
    }

    setTimeout(() => {
        detectMyUsername();
        if (window.location.pathname === '/multiplayer') {
            createFocusModeButton();
        }
        censorOpponentNames();
    }, 1000);

    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(() => {
                if (!MY_USERNAME) detectMyUsername();
                if (window.location.pathname === '/multiplayer') {
                    createFocusModeButton();
                }
                censorOpponentNames();
            }, 500);
        }
    }).observe(document, { subtree: true, childList: true });
})();