// ==UserScript==
// @name GeoGuessr 5K Counter
// @namespace https://greasyfork.org/en/users/1501889
// @version 1.3
// @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,
detectedElements: new Set()
};
function saveStats() {
GM_setValue('geo5k_total', stats.total);
GM_setValue('geo5k_current', stats.currentGame);
}
GM_addStyle(`
#geo5k-counter {
position: fixed !important;
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: 99999 !important; /* Higher z-index */
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() {
// Only create if doesn't exist or was removed
let display = document.getElementById('geo5k-counter');
if (!display) {
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();
}
});
setupDragging(display);
}
return display;
}
function setupDragging(display) {
let isDragging = false;
let startX, startY, initialX, initialY;
display.addEventListener('mousedown', (e) => {
if (e.button !== 0 || e.target.id === 'geo5k-reset-btn') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialX = display.offsetLeft;
initialY = display.offsetTop;
display.classList.add('dragging');
display.style.transition = 'none';
e.preventDefault();
});
const moveHandler = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newX = Math.max(0, Math.min(initialX + dx, window.innerWidth - display.offsetWidth));
const newY = Math.max(0, Math.min(initialY + dy, window.innerHeight - display.offsetHeight));
display.style.left = `${newX}px`;
display.style.top = `${newY}px`;
};
const upHandler = () => {
if (!isDragging) return;
isDragging = false;
display.classList.remove('dragging');
display.style.transition = '';
};
document.addEventListener('mousemove', moveHandler, { passive: true });
document.addEventListener('mouseup', upHandler);
}
function updateDisplay() {
const display = document.getElementById('geo5k-counter');
if (!display) {
createDisplay();
return;
}
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() {
// Check all potential score-containing elements
const elements = document.querySelectorAll('span, div, p, h1, h2, h3, h4, h5, h6, button');
for (const element of elements) {
if (stats.detectedElements.has(element)) continue;
const text = element.textContent.trim();
if (text === '5,000' || text === '5000' || text.replace(/\s/g,'') === '5000') {
stats.detectedElements.add(element);
handle5KDetection(element);
setTimeout(() => stats.detectedElements.delete(element), 3000);
break; // Only process one at a time
}
}
}
function handle5KDetection(element) {
const now = Date.now();
if (now - stats.lastDetection < 1000) return; // 1 second cooldown
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 indicators = ['Round 1/5', '1 / 5', 'round 1 of 5'];
const bodyText = document.body.textContent;
if (indicators.some(indicator => bodyText.includes(indicator))) {
stats.currentGame = 0;
stats.detectedElements.clear();
saveStats();
updateDisplay();
}
}
function init() {
// Create display first
createDisplay();
updateDisplay();
setInterval(() => {
checkFor5K();
checkGameState();
}, 200); // Check every 200ms
document.addEventListener('keyup', (e) => {
if (e.code === 'Space') setTimeout(checkFor5K, 100);
});
document.addEventListener('click', () => setTimeout(checkFor5K, 100));
setInterval(() => {
if (!document.getElementById('geo5k-counter')) {
createDisplay();
updateDisplay();
}
}, 1000);
}
if (document.readyState === 'complete') {
init();
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();