geoDK's Random Zoom Mode

Adds random zooms and screen blackouts in Geoguessr for challenge play. Special thanks to Chhote for the idea!!

目前為 2025-06-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name         geoDK's Random Zoom Mode
// @namespace    https://geodk.dev/
// @version      1.0.0
// @description  Adds random zooms and screen blackouts in Geoguessr for challenge play. Special thanks to Chhote for the idea!!
// @author       Dorukhan Bozkurt (geoDK)
// @match        https://www.geoguessr.com/*
// @icon         https://www.svgrepo.com/show/40039/eye.svg
// @grant        none
// @license      MIT
// ==/UserScript==


(function () {
    'use strict';

    let zoomTime = parseFloat(localStorage.getItem('zoomTime')) || 1.0;
    let zoomNumber = parseInt(localStorage.getItem('zoomNumber')) || 2;
    let zoomEnabled = localStorage.getItem('zoomEnabled') === 'enabled';

    const blinkTime = 500; // ms
    const minZoom = 3.0;
    const maxZoom = 5.0;

    let wasBackdropThereOrLoading = false;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function getPanoramaEl() {
        return document.querySelector("[data-qa=panorama]");
    }

    function applySimpleRandomZoom(pano) {
        if (!pano) return;

        pano.style.transition = 'none';

        const zoomAmount = minZoom + Math.random() * (maxZoom - minZoom);
        const randPercent = () => `${Math.random() * 100}%`;

        pano.style.transformOrigin = `${randPercent()} ${randPercent()}`;
        pano.style.transform = `scale(${zoomAmount})`;
    }

    function resetZoom(pano) {
        if (!pano) return;
        pano.style.transform = 'scale(1)';
        pano.style.transformOrigin = 'center center';
    }

    function hidePanorama() {
        const pano = getPanoramaEl();
        if (pano) pano.style.filter = 'brightness(0%)';
    }

    function showPanorama() {
        const pano = getPanoramaEl();
        if (pano) pano.style.filter = 'brightness(100%)';
    }

    function isBackdropThereOrLoading() {
        return document.querySelector('[class*=overlay_backdrop__]') ||
               document.querySelector('[class*=round-starting_wrapper__]') ||
               document.querySelector('[class*=fullscreen-spinner_root__]') ||
               document.querySelector('[class*=game-starting_container__]');
    }

    async function handleRoundStart() {
        if (!zoomEnabled) return;

        const pano = getPanoramaEl();
        if (!pano) return;

        resetZoom(pano);
        showPanorama();

        for (let i = 0; i < zoomNumber; i++) {
            applySimpleRandomZoom(pano);
            await sleep(zoomTime * 1000);

            if (i < zoomNumber - 1) {
                hidePanorama();
                await sleep(blinkTime);
                showPanorama();
                resetZoom(pano);
            }
        }

        hidePanorama();
        resetZoom(pano);
    }

    const observer = new MutationObserver(() => {
        addSettingsUI();
        if (isBackdropThereOrLoading()) {
            wasBackdropThereOrLoading = true;
        } else if (wasBackdropThereOrLoading) {
            wasBackdropThereOrLoading = false;
            handleRoundStart();
        }
    });

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

    function addSettingsUI() {
        const container = document.querySelector('[class*=map-block_mapStatsContainer__]');
        if (!container || document.getElementById('zoomModeSettings')) return;

        const html = `
<div id="zoomModeSettings" style="
    position: absolute;
    bottom: 20px;
    left: 20px;
    background: rgba(255, 255, 255, 0.05);
    padding: 12px;
    border-radius: 12px;
    z-index: 999;
    backdrop-filter: blur(4px);
    color: white;
    width: 220px;
    font-family: inherit;
    box-shadow: 0 0 8px rgba(0,0,0,0.2);
">
    <h3 style="margin-bottom: 10px; font-size: 16px; text-align: center;">🎥 geoDK's Random Zoom Mode</h3>
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
        <label style="margin-right: 10px;">Enabled</label>
        <input type="checkbox" id="zoomEnabled">
    </div>
    <div style="margin-bottom: 10px;">
        <label style="display: block; margin-bottom: 4px;">Zoom Time (s)</label>
        <input type="number" id="zoomTime" value="${zoomTime}" min="0.1" step="0.1" style="width: 100%;">
    </div>
    <div>
        <label style="display: block; margin-bottom: 4px;">Zoom Number</label>
        <input type="number" id="zoomNumber" value="${zoomNumber}" min="1" step="1" style="width: 100%;">
    </div>
</div>`;


        container.insertAdjacentHTML('afterend', html);

        document.getElementById('zoomEnabled').checked = zoomEnabled;
        document.getElementById('zoomEnabled').addEventListener('change', e => {
            zoomEnabled = e.target.checked;
            localStorage.setItem('zoomEnabled', zoomEnabled ? 'enabled' : 'disabled');
        });

        document.getElementById('zoomTime').addEventListener('change', e => {
            zoomTime = parseFloat(e.target.value);
            localStorage.setItem('zoomTime', zoomTime);
        });

        document.getElementById('zoomNumber').addEventListener('change', e => {
            zoomNumber = parseInt(e.target.value);
            localStorage.setItem('zoomNumber', zoomNumber);
        });
    }
})();

/*
 * ----------------------------------------------------------------------------
 * MIT License
 * ----------------------------------------------------------------------------
 *
 * Copyright (c) 2025 Dorukhan Bozkurt (geoDK)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * ----------------------------------------------------------------------------
 * TL;DR (just for human understanding, not legally binding):
 * - ✅ You can use, copy, change, and share this script freely
 * - 🧠 You must keep the credit to geoDK (Dorukhan Bozkurt)
 * - 🚫 No warranty — if something breaks, it's not my responsibility
 * ----------------------------------------------------------------------------
 */

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址