GeoGuessr 5K Counter

singleplayer 5k counter, counts per game and total, resettable

当前为 2025-08-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         GeoGuessr 5K Counter
// @namespace    https://greasyfork.org/en/users/1501889
// @version      1.2
// @description  singleplayer 5k counter, counts per game and total, resettable
// @author       Clemens
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?domain=geoguessr.com
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const stats = {
        total: GM_getValue('geo5k_total', 0),
        currentGame: GM_getValue('geo5k_current', 0),
        lastDetection: 0
    };

    function saveStats() {
        GM_setValue('geo5k_total', stats.total);
        GM_setValue('geo5k_current', stats.currentGame);
    }

    GM_addStyle(`
        #geo5k-counter {
            position: fixed;
            left: 20px;
            top: 75vh;
            background: rgba(30, 30, 30, 0.9);
            color: #f0f0f0;
            padding: 12px 16px;
            border-radius: 8px;
            font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
            font-size: 14px;
            z-index: 9999;
            border: 1px solid rgba(255, 255, 255, 0.1);
            min-width: 180px;
            cursor: move;
            user-select: none;
            backdrop-filter: blur(4px);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }
        #geo5k-counter:hover {
            box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
        }
        #geo5k-counter.dragging {
            cursor: grabbing;
            transform: scale(1.02);
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
            transition: transform 0.05s ease, box-shadow 0.05s ease;
        }
        #geo5k-counter-header {
            font-weight: 600;
            margin-bottom: 8px;
            font-size: 15px;
            color: #58a6ff;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        #geo5k-counter-header::before {
            content: "✓";
            color: #58a6ff;
            font-weight: bold;
            margin-right: 6px;
        }
        .geo5k-counter-row {
            margin: 6px 0;
            display: flex;
            justify-content: space-between;
        }
        .geo5k-counter-value {
            font-weight: 500;
            color: #ffffff;
        }
        #geo5k-reset-btn {
            background: rgba(255, 255, 255, 0.1);
            color: #ff6b6b;
            border: none;
            border-radius: 4px;
            padding: 2px 8px;
            font-size: 12px;
            cursor: pointer;
            transition: all 0.2s ease;
            margin-left: 8px;
        }
        #geo5k-reset-btn:hover {
            background: rgba(255, 107, 107, 0.2);
            color: #ff4d4d;
        }
        .geo5k-highlight {
            animation: geo5k-pulse 0.5s;
        }
        @keyframes geo5k-pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.1); }
            100% { transform: scale(1); }
        }
    `);

    function createDisplay() {
        if (document.getElementById('geo5k-counter')) return;

        const display = document.createElement('div');
        display.id = 'geo5k-counter';
        display.innerHTML = `
            <div id="geo5k-counter-header">
                <span>5K COUNTER</span>
                <button id="geo5k-reset-btn" title="Reset Total Count">Reset</button>
            </div>
            <div class="geo5k-counter-row">
                <span>This Game:</span>
                <span id="geo5k-current" class="geo5k-counter-value">${stats.currentGame}/5</span>
            </div>
            <div class="geo5k-counter-row">
                <span>Total:</span>
                <span id="geo5k-total" class="geo5k-counter-value">${stats.total}</span>
            </div>
        `;
        document.body.appendChild(display);

        document.getElementById('geo5k-reset-btn').addEventListener('click', function(e) {
            e.stopPropagation(); 
            if (confirm('Are you sure you want to reset your total 5K count?')) {
                stats.total = 0;
                saveStats();
                updateDisplay();
            }
        });

        let isDragging = false;
        let offsetX, offsetY;

        display.addEventListener('mousedown', startDrag);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', endDrag);

        function startDrag(e) {
            if (e.button !== 0 || e.target.id === 'geo5k-reset-btn') return; 
            isDragging = true;

            const rect = display.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;

            display.classList.add('dragging');
            e.preventDefault();
        }

        function drag(e) {
            if (!isDragging) return;

            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;

            const maxX = window.innerWidth - display.offsetWidth;
            const maxY = window.innerHeight - display.offsetHeight;

            newX = Math.max(0, Math.min(newX, maxX));
            newY = Math.max(0, Math.min(newY, maxY));

            display.style.left = `${newX}px`;
            display.style.top = `${newY}px`;
        }

        function endDrag() {
            if (!isDragging) return;
            isDragging = false;
            display.classList.remove('dragging');
        }
    }

    function updateDisplay() {
        const currentEl = document.getElementById('geo5k-current');
        const totalEl = document.getElementById('geo5k-total');

        if (currentEl) currentEl.textContent = `${stats.currentGame}/5`;
        if (totalEl) totalEl.textContent = stats.total;
    }

    function checkFor5K() {
        const elements = document.body.getElementsByTagName('*');
        for (let i = 0; i < elements.length; i++) {
            const text = elements[i].textContent.trim();
            if (text === '5,000' || text === '5000' || text.replace(/\s/g,'') === '5000') {
                handle5KDetection(elements[i]);
                return;
            }
        }
    }

    function handle5KDetection(element) {
        const now = Date.now();
        if (now - stats.lastDetection < 3000) return;
        stats.lastDetection = now;

        stats.total++;
        stats.currentGame++;

        saveStats();
        updateDisplay();

        element.classList.add('geo5k-highlight');
        setTimeout(() => {
            element.classList.remove('geo5k-highlight');
        }, 500);

        if (stats.currentGame >= 5) {
            setTimeout(() => {
                stats.currentGame = 0;
                saveStats();
                updateDisplay();
            }, 3000);
        }
    }

    function checkGameState() {
        const roundText = document.body.textContent;
        if (roundText.includes('Round 1/5') || roundText.includes('1 / 5')) {
            stats.currentGame = 0;
            saveStats();
            updateDisplay();
        }
    }

    function init() {
        createDisplay();
        updateDisplay();

        setInterval(() => {
            checkFor5K();
            checkGameState();
        }, 1000);
    }

    if (document.readyState === 'complete') {
        setTimeout(init, 1000);
    } else {
        window.addEventListener('load', init);
    }
})();