// ==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);
}
})();