// ==UserScript== // @name WorldGuessr GeoAssist // @namespace https://greasyfork.org/ // @version 1.1 // @description Advanced WorldGuessr overlay toolkit - lat/long bands, offset radius circle, country outlines, and exact pin marker. Left Shift to open. // @author WhosGravy // @match *://www.worldguessr.com/* // @match *://worldguessr.com/* // @icon data:image/svg+xml;utf8, // @grant none // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/576534/WorldGuessr%20GeoAssist.user.js // @updateURL https://update.greasyfork.icu/scripts/576534/WorldGuessr%20GeoAssist.meta.js // ==/UserScript== (function () { 'use strict'; const _Proxy = window.Proxy; const _Reflect = window.Reflect; const SCRIPT_VERSION = '1.1'; const PIN_GLYPH_SVG = ''; const MAP_PIN_IMAGE_URL = 'https://i.imgur.com/taGq4EF.png'; const MAP_PIN_SIZE = [28, 34]; const SV_HEAT_TILE_URL = 'https://mt1.google.com/vt?lyrs=svv|cb_client:apiv3&style=40,18&x={x}&y={y}&z={z}'; // Unlock iframes const _origSetAttr = Element.prototype.setAttribute; Element.prototype.setAttribute = new _Proxy(_origSetAttr, { apply(target, thisArg, args) { if (thisArg.tagName === 'IFRAME' && args[0].toLowerCase() === 'sandbox') return; return _Reflect.apply(target, thisArg, args); } }); // Country ISO-2 pool for decoys const ALL_COUNTRIES = [ 'AF', 'AL', 'DZ', 'AR', 'AM', 'AU', 'AT', 'AZ', 'BD', 'BE', 'BO', 'BA', 'BR', 'BG', 'KH', 'CM', 'CA', 'CL', 'CN', 'CO', 'CR', 'CU', 'CZ', 'DK', 'DO', 'EC', 'EG', 'SV', 'ET', 'FI', 'FR', 'GE', 'DE', 'GH', 'GR', 'GT', 'HN', 'HU', 'IN', 'ID', 'IR', 'IQ', 'IE', 'IL', 'IT', 'JP', 'JO', 'KZ', 'KE', 'KR', 'KW', 'KG', 'LA', 'LB', 'LY', 'MY', 'MX', 'MD', 'MN', 'MA', 'MZ', 'MM', 'NP', 'NL', 'NZ', 'NI', 'NE', 'NG', 'NO', 'PK', 'PA', 'PY', 'PE', 'PH', 'PL', 'PT', 'RO', 'RU', 'SA', 'SN', 'RS', 'ZA', 'ES', 'LK', 'SD', 'SE', 'CH', 'SY', 'TZ', 'TH', 'TN', 'TR', 'UA', 'AE', 'GB', 'US', 'UY', 'UZ', 'VE', 'VN', 'YE', 'ZM', 'ZW' ]; // Persistence const STORAGE_KEY = 'wg_geoassist_v3'; const DEFAULTS = { bandsEnabled: true, mode: 'both', latHeightKm: 200, lngWidthKm: 300, latColor: '#ff5a5a', lngColor: '#468cff', circleEnabled: false, circleRadiusKm: 500, circleColor: '#22c55e', countryEnabled: false, countryDecoys: 2, countryColor: '#fbbf24', pinEnabled: false, svHeatEnabled: false, svHeatOpacity: 45, toggleHotkeyCode: '', toggleHotkeyLabel: 'None', mapsHotkeyCode: '', mapsHotkeyLabel: 'None' }; function loadConfig() { try { return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') }; } catch { return { ...DEFAULTS }; } } function saveConfig() { localStorage.setItem(STORAGE_KEY, JSON.stringify(cfg)); } let cfg = loadConfig(); // State let gameMap = null; let latBand = null; let lngBand = null; let latRandFrac = Math.random(); let lngRandFrac = Math.random(); let circleLayer = null; let circleRandBearing = Math.random() * Math.PI * 2; let circleRandFrac = 0.15 + Math.sqrt(Math.random()) * 0.75; let exactPinMarker = null; let svHeatLayer = null; let countryLayers = []; let countryFeatureIndex = null; let currentIso = null; let lastCoords = null; let panel = null; let activeTab = 'bands'; let coordPollId = null; let countryBusy = false; let countryRetryTimer = null; let countryRetryAttempt = 0; let countryRunId = 0; let lastActivatedCheatKeys = []; let awaitingHotkeyCapture = false; let awaitingMapsHotkeyCapture = false; // Coordinate extraction function getCoordinates() { try { const iframe = document.querySelector('#PanoramaIframe') || document.querySelector('iframe[src*="location"]') || document.querySelector('.iframeWithStreetView'); if (iframe?.src) { const loc = new URL(iframe.src).searchParams.get('location'); if (loc) { const [lat, lng] = loc.split(',').map(Number); if (isFinite(lat) && isFinite(lng)) return { lat, lng }; } } } catch {} return null; } // Unit helpers const EARTH_RADIUS_KM = 6371; const COUNTRY_FETCH_DELAY_MS = 60; const COUNTRY_FETCH_RETRIES = 2; const COUNTRY_RETRY_DELAY_MS = 1400; const COUNTRY_AUTO_RETRY_DELAY_MS = 2200; const COUNTRY_AUTO_RETRY_MAX = 8; const COUNTRY_DATA_URL = 'https://cdn.jsdelivr.net/gh/datasets/geo-countries@master/data/countries.geojson'; const kmToLatDeg = km => km / 111.32; const kmToLngDeg = (km, lat) => km / (111.32 * Math.max(Math.cos(lat * Math.PI / 180), 0.001)); const sleep = ms => new Promise(r => setTimeout(r, ms)); async function fetchJsonWithRetry(url, options = {}, retries = COUNTRY_FETCH_RETRIES) { let lastErr; for (let i = 0; i <= retries; i++) { try { const res = await fetch(url, options); if (!res.ok) throw new Error('HTTP ' + res.status); return await res.json(); } catch (err) { lastErr = err; if (i < retries) await sleep(COUNTRY_RETRY_DELAY_MS + i * 500); } } throw lastErr; } function offsetCoords(lat, lng, distanceKm, bearingRad) { const angDist = distanceKm / EARTH_RADIUS_KM; const lat1 = lat * Math.PI / 180; const lng1 = lng * Math.PI / 180; const sinLat1 = Math.sin(lat1); const cosLat1 = Math.cos(lat1); const sinAng = Math.sin(angDist); const cosAng = Math.cos(angDist); const lat2 = Math.asin(sinLat1 * cosAng + cosLat1 * sinAng * Math.cos(bearingRad)); const lng2 = lng1 + Math.atan2( Math.sin(bearingRad) * sinAng * cosLat1, cosAng - sinLat1 * Math.sin(lat2) ); return { lat: lat2 * 180 / Math.PI, lng: ((lng2 * 180 / Math.PI + 540) % 360) - 180 }; } function hexToRgba(hex, alpha) { const raw = String(hex || '').trim().replace('#', ''); const full = raw.length === 3 ? raw.split('').map(c => c + c).join('') : raw; if (!/^[0-9a-fA-F]{6}$/.test(full)) return `rgba(255,255,255,${alpha})`; const int = parseInt(full, 16); const r = (int >> 16) & 255; const g = (int >> 8) & 255; const b = int & 255; return `rgba(${r},${g},${b},${alpha})`; } // Bands function clearBands() { if (!gameMap) return; if (latBand) { gameMap.removeLayer(latBand); latBand = null; } if (lngBand) { gameMap.removeLayer(lngBand); lngBand = null; } } function drawBands() { if (!gameMap || !lastCoords) return; clearBands(); const { lat, lng } = lastCoords; if (cfg.mode === 'lat' || cfg.mode === 'both') { const h = kmToLatDeg(cfg.latHeightKm); const mn = lat - latRandFrac * h; latBand = L.rectangle([[mn, -540], [mn + h, 540]], { color: hexToRgba(cfg.latColor, 0.85), weight: 1.5, fillColor: cfg.latColor, fillOpacity: 0.11, interactive: false, noClip: true }).addTo(gameMap); } if (cfg.mode === 'lng' || cfg.mode === 'both') { const w = kmToLngDeg(cfg.lngWidthKm, lat); const mn = lng - lngRandFrac * w; lngBand = L.rectangle([[-90, mn], [90, mn + w]], { color: hexToRgba(cfg.lngColor, 0.85), weight: 1.5, fillColor: cfg.lngColor, fillOpacity: 0.11, interactive: false, noClip: true }).addTo(gameMap); } } // Circle function clearCircle() { if (circleLayer && gameMap) { gameMap.removeLayer(circleLayer); circleLayer = null; } } function drawCircle() { if (!gameMap || !lastCoords) return; clearCircle(); const offsetKm = cfg.circleRadiusKm * circleRandFrac; const circleCenter = offsetCoords(lastCoords.lat, lastCoords.lng, offsetKm, circleRandBearing); circleLayer = L.circle([circleCenter.lat, circleCenter.lng], { radius: cfg.circleRadiusKm * 1000, color: hexToRgba(cfg.circleColor, 0.95), weight: 2, fillColor: cfg.circleColor, fillOpacity: 0.14, interactive: false }).addTo(gameMap); } // Exact pin function clearExactPin() { if (exactPinMarker && gameMap) { gameMap.removeLayer(exactPinMarker); exactPinMarker = null; } } function drawExactPin() { if (!gameMap || !lastCoords) return; clearExactPin(); const icon = L.icon({ iconUrl: MAP_PIN_IMAGE_URL, iconSize: MAP_PIN_SIZE, iconAnchor: [Math.round(MAP_PIN_SIZE[0] / 2), MAP_PIN_SIZE[1]] }); exactPinMarker = L.marker([lastCoords.lat, lastCoords.lng], { icon, interactive: false }).addTo(gameMap); } // Street View heat overlay function clearSvHeat() { if (svHeatLayer && gameMap) { gameMap.removeLayer(svHeatLayer); svHeatLayer = null; } } function drawSvHeat() { if (!gameMap) return; clearSvHeat(); svHeatLayer = L.tileLayer(SV_HEAT_TILE_URL, { maxZoom: 20, opacity: Math.max(0, Math.min(1, (cfg.svHeatOpacity || 45) / 100)), noWrap: true, pane: 'overlayPane', className: 'cga-svheat-layer' }).addTo(gameMap); } function openCurrentLocationInMaps() { const c = lastCoords || getCoordinates(); if (!c) return; const url = `https://www.google.com/maps?q=${c.lat},${c.lng}&ll=${c.lat},${c.lng}&z=18&t=k`; window.open(url, '_blank', 'noopener,noreferrer'); } // Country outlines function clearCountryLayers() { if (!gameMap) return; countryLayers.forEach(l => { try { gameMap.removeLayer(l); } catch {} }); countryLayers = []; } const getCountryStyle = () => ({ color: cfg.countryColor, weight: 2.5, fillColor: cfg.countryColor, fillOpacity: 0.07, interactive: false }); function addGeoLayer(data) { if (!gameMap || !data) return; try { const layer = L.geoJSON(data, { style: () => getCountryStyle() }).addTo(gameMap); countryLayers.push(layer); } catch (e) { console.warn('[GeoAssist] GeoJSON draw error', e); } } function setCountryStatus(msg, color) { const el = document.getElementById('cga-country-status'); if (el) { el.textContent = msg; el.style.color = color || cfg.countryColor; } } function clearCountryRetry() { if (countryRetryTimer) { clearTimeout(countryRetryTimer); countryRetryTimer = null; } } function isCountryRunActive(runId) { return runId === countryRunId && cfg.countryEnabled && !!gameMap; } function scheduleCountryRetry(lat, lng) { if (!cfg.countryEnabled || !gameMap) return; if (countryRetryAttempt >= COUNTRY_AUTO_RETRY_MAX) { setCountryStatus('Failed (retry limit)', '#f87171'); return; } clearCountryRetry(); countryRetryAttempt++; setCountryStatus(`Retrying... ${countryRetryAttempt}/${COUNTRY_AUTO_RETRY_MAX}`, '#fbbf24'); countryRetryTimer = setTimeout(() => { countryRetryTimer = null; drawCountryOutlines(lat, lng); }, COUNTRY_AUTO_RETRY_DELAY_MS); } function getFeatureIso2(props) { if (!props) return null; const raw = props['ISO3166-1-Alpha-2'] || props.ISO_A2 || props.iso_a2 || props.ISO2 || props.iso2; if (!raw || raw === '-99') return null; return String(raw).toUpperCase(); } async function ensureCountryIndex() { if (countryFeatureIndex) return countryFeatureIndex; const data = await fetchJsonWithRetry(COUNTRY_DATA_URL); const index = new Map(); for (const feature of (data?.features || [])) { const iso2 = getFeatureIso2(feature?.properties); if (!iso2) continue; if (!index.has(iso2)) index.set(iso2, []); index.get(iso2).push(feature); } countryFeatureIndex = index; return index; } function addCountryByIso2(iso2) { if (!countryFeatureIndex || !iso2) return false; const features = countryFeatureIndex.get(iso2); if (!features?.length) return false; addGeoLayer({ type: 'FeatureCollection', features }); return true; } async function resolveCountryIso2(lat, lng) { try { const revData = await fetchJsonWithRetry( `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=jsonv2&zoom=3&addressdetails=1`, { headers: { 'Accept-Language': 'en' } } ); const iso = revData?.address?.country_code?.toUpperCase(); if (iso) return iso; } catch {} try { const bdcData = await fetchJsonWithRetry( `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${lat}&longitude=${lng}&localityLanguage=en` ); const iso = bdcData?.countryCode?.toUpperCase(); if (iso) return iso; } catch {} return null; } async function drawCountryOutlines(snapLat, snapLng) { const runId = ++countryRunId; countryBusy = true; clearCountryRetry(); clearCountryLayers(); setCountryStatus('Loading boundaries...', '#94a3b8'); try { await ensureCountryIndex(); currentIso = await resolveCountryIso2(snapLat, snapLng); if (!isCountryRunActive(runId)) return; if (currentIso) addCountryByIso2(currentIso); const pool = ALL_COUNTRIES.filter(c => c !== currentIso).sort(() => Math.random() - 0.5); let addedDecoys = 0; for (const code of pool) { if (addedDecoys >= cfg.countryDecoys) break; await sleep(COUNTRY_FETCH_DELAY_MS); if (!isCountryRunActive(runId)) break; if (addCountryByIso2(code)) addedDecoys++; } if (!isCountryRunActive(runId)) return; countryRetryAttempt = 0; setCountryStatus(currentIso ? 'Loaded' : 'Loaded (no exact country)', currentIso ? '#4ade80' : '#fbbf24'); } catch (e) { console.error('[GeoAssist] Country fetch failed', e); if (isCountryRunActive(runId)) { setCountryStatus('Failed, retrying...', '#f87171'); scheduleCountryRetry(snapLat, snapLng); } } if (runId === countryRunId) countryBusy = false; } function isCheatEnabled(key) { if (key === 'bands') return !!cfg.bandsEnabled; if (key === 'circle') return !!cfg.circleEnabled; if (key === 'country') return !!cfg.countryEnabled; if (key === 'pin') return !!cfg.pinEnabled; if (key === 'svheat') return !!cfg.svHeatEnabled; return false; } function getEnabledCheatKeys() { return ['bands', 'circle', 'country', 'pin', 'svheat'].filter(isCheatEnabled); } function updateLastUsedCheatSet() { lastActivatedCheatKeys = getEnabledCheatKeys(); } function syncCheatUi(key) { if (!panel) return; if (key === 'bands') { const tog = panel.querySelector('#cga-bands-tog'); const ctrl = panel.querySelector('#cga-bands-ctrl'); if (tog) tog.checked = !!cfg.bandsEnabled; if (ctrl) { ctrl.style.opacity = cfg.bandsEnabled ? '1' : '.35'; ctrl.style.pointerEvents = cfg.bandsEnabled ? '' : 'none'; } return; } if (key === 'circle') { const tog = panel.querySelector('#cga-circle-tog'); const ctrl = panel.querySelector('#cga-circle-ctrl'); if (tog) tog.checked = !!cfg.circleEnabled; if (ctrl) { ctrl.style.opacity = cfg.circleEnabled ? '1' : '.35'; ctrl.style.pointerEvents = cfg.circleEnabled ? '' : 'none'; } return; } if (key === 'country') { const tog = panel.querySelector('#cga-country-tog'); const ctrl = panel.querySelector('#cga-country-ctrl'); if (tog) tog.checked = !!cfg.countryEnabled; if (ctrl) { ctrl.style.opacity = cfg.countryEnabled ? '1' : '.35'; ctrl.style.pointerEvents = cfg.countryEnabled ? '' : 'none'; } return; } if (key === 'pin') { const tog = panel.querySelector('#cga-pin-tog'); if (tog) tog.checked = !!cfg.pinEnabled; return; } if (key === 'svheat') { const tog = panel.querySelector('#cga-svheat-tog'); if (tog) tog.checked = !!cfg.svHeatEnabled; const val = panel.querySelector('#cga-svheat-op-val'); const sl = panel.querySelector('#cga-svheat-op'); const ctrl = panel.querySelector('#cga-svheat-ctrl'); if (val) val.textContent = `${cfg.svHeatOpacity}%`; if (sl) { sl.value = cfg.svHeatOpacity; sl.style.setProperty('--pct', `${cfg.svHeatOpacity}%`); } if (ctrl) { ctrl.style.opacity = cfg.svHeatEnabled ? '1' : '.35'; ctrl.style.pointerEvents = cfg.svHeatEnabled ? '' : 'none'; } } } function setCheatEnabled(key, enabled, markAsActivated = true) { if (key === 'bands') { cfg.bandsEnabled = !!enabled; if (cfg.bandsEnabled) drawBands(); else clearBands(); } if (key === 'circle') { cfg.circleEnabled = !!enabled; if (cfg.circleEnabled) drawCircle(); else clearCircle(); } if (key === 'country') { cfg.countryEnabled = !!enabled; if (cfg.countryEnabled && lastCoords) { countryRetryAttempt = 0; drawCountryOutlines(lastCoords.lat, lastCoords.lng); } else { clearCountryRetry(); countryRetryAttempt = 0; countryRunId++; countryBusy = false; clearCountryLayers(); setCountryStatus('', ''); } } if (key === 'pin') { cfg.pinEnabled = !!enabled; if (cfg.pinEnabled) drawExactPin(); else clearExactPin(); } if (key === 'svheat') { cfg.svHeatEnabled = !!enabled; if (cfg.svHeatEnabled) drawSvHeat(); else clearSvHeat(); } if (markAsActivated) updateLastUsedCheatSet(); saveConfig(); syncCheatUi(key); } function formatHotkeyFromEvent(e) { if (e.code.startsWith('Key')) return e.code.slice(3).toUpperCase(); if (e.code.startsWith('Digit')) return e.code.slice(5); if (e.code === 'Space') return 'Space'; if (e.code.startsWith('Arrow')) return e.code.replace('Arrow', ''); return e.key.length === 1 ? e.key.toUpperCase() : e.key; } function syncHotkeyUi() { if (!panel) return; const valEl = panel.querySelector('#cga-hotkey-val'); const setBtn = panel.querySelector('#cga-hotkey-set'); const mapValEl = panel.querySelector('#cga-maps-hotkey-val'); const mapSetBtn = panel.querySelector('#cga-maps-hotkey-set'); if (valEl) valEl.textContent = awaitingHotkeyCapture ? 'Press key...' : (cfg.toggleHotkeyLabel || 'None'); if (setBtn) setBtn.textContent = awaitingHotkeyCapture ? 'Cancel capture' : 'Set key'; if (mapValEl) mapValEl.textContent = awaitingMapsHotkeyCapture ? 'Press key...' : (cfg.mapsHotkeyLabel || 'None'); if (mapSetBtn) mapSetBtn.textContent = awaitingMapsHotkeyCapture ? 'Cancel capture' : 'Set key'; } function syncOverlayColorUi() { if (!panel) return; panel.style.setProperty('--col-lat', cfg.latColor); panel.style.setProperty('--col-lng', cfg.lngColor); panel.style.setProperty('--col-circle', cfg.circleColor); panel.style.setProperty('--col-country', cfg.countryColor); const latLabel = panel.querySelector('#cga-lat-label'); const lngLabel = panel.querySelector('#cga-lng-label'); if (latLabel) latLabel.style.color = cfg.latColor; if (lngLabel) lngLabel.style.color = cfg.lngColor; const bandsTog = panel.querySelector('#cga-tog-bands-wrap'); const circleTog = panel.querySelector('#cga-tog-circle-wrap'); const countryTog = panel.querySelector('#cga-tog-country-wrap'); if (bandsTog) bandsTog.style.setProperty('--acc', cfg.latColor); if (circleTog) circleTog.style.setProperty('--acc', cfg.circleColor); if (countryTog) countryTog.style.setProperty('--acc', cfg.countryColor); const latSl = panel.querySelector('#cga-lat-sl'); const lngSl = panel.querySelector('#cga-lng-sl'); const circleSl = panel.querySelector('#cga-radius-sl'); if (latSl) latSl.style.setProperty('--acc', cfg.latColor); if (lngSl) lngSl.style.setProperty('--acc', cfg.lngColor); if (circleSl) circleSl.style.setProperty('--acc', cfg.circleColor); } function toggleMostRecentCheat() { const targets = lastActivatedCheatKeys.length ? [...lastActivatedCheatKeys] : getEnabledCheatKeys(); if (!targets.length) return; const allEnabled = targets.every(isCheatEnabled); for (const key of targets) setCheatEnabled(key, !allEnabled, false); } // Master update on new location function onNewCoords(coords) { lastCoords = coords; latRandFrac = Math.random(); lngRandFrac = Math.random(); circleRandBearing = Math.random() * Math.PI * 2; circleRandFrac = 0.15 + Math.sqrt(Math.random()) * 0.75; if (cfg.bandsEnabled) drawBands(); else clearBands(); if (cfg.circleEnabled) drawCircle(); else clearCircle(); if (cfg.pinEnabled) drawExactPin(); else clearExactPin(); if (cfg.svHeatEnabled) drawSvHeat(); else clearSvHeat(); if (cfg.countryEnabled) { countryRetryAttempt = 0; drawCountryOutlines(coords.lat, coords.lng); } else { clearCountryRetry(); countryRetryAttempt = 0; countryRunId++; countryBusy = false; clearCountryLayers(); } } // Leaflet map interception const mapObserver = new MutationObserver(() => { try { if (typeof L !== 'undefined' && L.Map?.prototype.setView) { const origSetView = L.Map.prototype.setView; L.Map.prototype.setView = new _Proxy(origSetView, { apply(target, thisArg, args) { if (!gameMap) { gameMap = thisArg; if (lastCoords) { if (cfg.bandsEnabled) drawBands(); if (cfg.circleEnabled) drawCircle(); if (cfg.pinEnabled) drawExactPin(); if (cfg.svHeatEnabled) drawSvHeat(); } } return _Reflect.apply(target, thisArg, args); } }); mapObserver.disconnect(); } } catch {} }); mapObserver.observe(document.body, { childList: true, subtree: true }); // Coordinate polling const iframeObserver = new MutationObserver(() => { const iframe = document.querySelector('#PanoramaIframe') || document.querySelector('iframe[src*="location"]') || document.querySelector('.iframeWithStreetView'); if (iframe) { iframeObserver.disconnect(); if (coordPollId) clearInterval(coordPollId); coordPollId = setInterval(() => { const c = getCoordinates(); if (!c) return; if (!lastCoords || lastCoords.lat !== c.lat || lastCoords.lng !== c.lng) onNewCoords(c); }, 500); } }); iframeObserver.observe(document.body, { childList: true, subtree: true }); // Panel styles const PANEL_CSS = ` #cga-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); transform-origin: top left; scale: var(--cga-scale, 1); zoom: var(--cga-zoom, 1); z-index: 2147483647; width: 392px; max-height: none; display: flex; flex-direction: column; background: #090c10; border: 1px solid rgba(255,255,255,0.07); border-radius: 18px; color: #94a3b8; font-family: 'DM Sans', 'Segoe UI', system-ui, sans-serif; font-size: 14px; box-shadow: 0 40px 100px rgba(0,0,0,0.85), 0 0 0 1px rgba(255,255,255,0.035) inset, 0 1px 0 rgba(255,255,255,0.06) inset; overflow: hidden; transition: left 0.22s ease, top 0.22s ease, opacity 0.18s ease, height 0.2s ease; } #cga-panel.cga-dragging { transition: none; } #cga-panel.cga-enter { animation: cga-fade-in 0.18s ease-out; } #cga-panel.cga-exit { animation: cga-fade-out 0.16s ease-in forwards; } @keyframes cga-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes cga-fade-out { from { opacity: 1; } to { opacity: 0; } } #cga-panel * { box-sizing: border-box; margin: 0; padding: 0; } #cga-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px 14px; border-bottom: 1px solid rgba(255,255,255,0.05); cursor: grab; flex-shrink: 0; background: linear-gradient(to bottom, rgba(255,255,255,0.025), transparent); } #cga-header:active { cursor: grabbing; } #cga-logo { display: flex; align-items: center; gap: 9px; } #cga-logo-mark { width: 30px; height: 30px; background: linear-gradient(135deg,#22d3ee 0%,#818cf8 100%); border-radius: 9px; display: flex; align-items: center; justify-content: center; font-size: 15px; box-shadow: 0 0 16px rgba(34,211,238,0.25); } #cga-logo-mark svg { width: 18px; height: 18px; display: block; } .cga-map-pin-icon { background: transparent; border: 0; } .cga-map-pin-wrap { width: 30px; height: 30px; background: linear-gradient(135deg,#22d3ee 0%,#818cf8 100%); border-radius: 9px; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 12px rgba(34,211,238,0.45); } .cga-map-pin-wrap svg { width: 18px; height: 18px; display: block; } #cga-logo-name { font-size: 15px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: #e2e8f0; } #cga-logo-ver { font-size: 10px; color: #334155; letter-spacing: 0.06em; margin-left: 5px; font-weight: 600; } #cga-close { width: 28px; height: 28px; border-radius: 7px; display: flex; align-items: center; justify-content: center; font-size: 15px; color: #334155; cursor: pointer; transition: all 0.15s; border: 1px solid transparent; } #cga-close:hover { color: #f87171; border-color: rgba(248,113,113,0.25); background: rgba(248,113,113,0.07); } #cga-tabs { display: grid; grid-template-columns: repeat(4,1fr); border-bottom: 1px solid rgba(255,255,255,0.05); flex-shrink: 0; } .cga-tab { padding: 11px 0 10px; text-align: center; font-size: 11.5px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: #2d3748; cursor: pointer; transition: all 0.15s; border-bottom: 2px solid transparent; position: relative; top: 1px; } .cga-tab::after { content: ''; position: absolute; left: 0; right: 0; bottom: -2px; height: 2px; opacity: 0; } .cga-tab:hover { color: #4a5568; } .cga-tab[data-tab="bands"].active { color: transparent; border-color: transparent; background: linear-gradient(90deg, var(--col-lat,#ff7070) 0%, var(--col-lng,#60a5fa) 100%); -webkit-background-clip: text; background-clip: text; } .cga-tab[data-tab="bands"].active::after { opacity: 1; background: linear-gradient(90deg, var(--col-lat,#ff7070) 0%, var(--col-lng,#60a5fa) 100%); } .cga-tab[data-tab="circle"].active { color: var(--col-circle,#22c55e); border-color: var(--col-circle,#22c55e); } .cga-tab[data-tab="country"].active { color: var(--col-country,#fbbf24); border-color: var(--col-country,#fbbf24); } .cga-tab[data-tab="pin"].active { color: #60a5fa; border-color: #60a5fa; } #cga-body { flex: 1; overflow: hidden; } .cga-tab-body { display: none; padding: 18px 20px 14px; } .cga-tab-body.active { display: block; animation: cga-tab-in 0.18s cubic-bezier(0.16,1,0.3,1); } @keyframes cga-tab-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } .cga-feat-head { display: flex; align-items: center; justify-content: flex-end; min-height: 28px; margin: 12px 0 14px; } .cga-feat-label { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #e2e8f0; } .cga-feat-icon { font-size: 16px; line-height: 1; } .cga-tog { position: relative; width: 46px; height: 25px; cursor: pointer; flex-shrink: 0; } .cga-tog input { opacity: 0; width: 0; height: 0; position: absolute; } .cga-tog-track { position: absolute; inset: 0; border-radius: 12px; background: #1a2035; border: 1px solid rgba(255,255,255,0.07); transition: all 0.22s; } .cga-tog input:checked ~ .cga-tog-track { background: var(--acc,#22d3ee); border-color: var(--acc,#22d3ee); box-shadow: 0 0 12px color-mix(in srgb,var(--acc,#22d3ee) 40%,transparent); } .cga-tog-knob { position: absolute; width: 18px; height: 18px; border-radius: 50%; background: #3a4560; top: 3px; left: 3px; transition: all 0.22s; box-shadow: 0 1px 4px rgba(0,0,0,0.5); } .cga-tog input:checked ~ .cga-tog-track .cga-tog-knob { left: 25px; background: #fff; } .cga-hr { border: none; border-top: 1px solid rgba(255,255,255,0.04); margin: 14px 0; } .cga-stack { display: flex; flex-direction: column; gap: 15px; } .cga-srow-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; } .cga-slabel { font-size: 11.5px; font-weight: 600; color: #4a5568; letter-spacing: 0.04em; } .cga-sval { font-size: 12px; font-weight: 700; color: #e2e8f0; font-family: 'JetBrains Mono','Courier New',monospace; } .cga-range { -webkit-appearance: none; appearance: none; width: 100%; height: 18px; border-radius: 3px; background: linear-gradient(to right, var(--acc,#22d3ee) var(--pct,50%), #1a2035 var(--pct,50%)); background-size: 100% 3px; background-repeat: no-repeat; background-position: center; outline: none; cursor: pointer; } .cga-range::-webkit-slider-thumb { -webkit-appearance: none; width: 15px; height: 15px; border-radius: 50%; background: var(--acc,#22d3ee); cursor: pointer; box-shadow: 0 0 0 3px color-mix(in srgb,var(--acc,#22d3ee) 20%,transparent), 0 2px 6px rgba(0,0,0,0.5); transition: transform 0.1s; } .cga-range:active::-webkit-slider-thumb { transform: scale(1.2); } .cga-range::-moz-range-thumb { width: 15px; height: 15px; border: 0; border-radius: 50%; background: var(--acc,#22d3ee); cursor: pointer; box-shadow: 0 0 0 3px color-mix(in srgb,var(--acc,#22d3ee) 20%,transparent), 0 2px 6px rgba(0,0,0,0.5); } .cga-range::-moz-range-track { height: 3px; background: #1a2035; border: 0; border-radius: 3px; } .cga-mode-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 5px; } .cga-mbtn { padding: 7px 0; border-radius: 7px; border: 1px solid #1a2035; background: transparent; color: #2d3748; font-size: 11px; font-weight: 700; cursor: pointer; text-align: center; letter-spacing: 0.05em; text-transform: uppercase; transition: all 0.15s; font-family: inherit; } .cga-mbtn[data-mode="lat"] { color: var(--col-lat,#ff7070); border-color: color-mix(in srgb, var(--col-lat,#ff7070) 35%, transparent); background: color-mix(in srgb, var(--col-lat,#ff7070) 10%, transparent); } .cga-mbtn[data-mode="lng"] { color: var(--col-lng,#60a5fa); border-color: color-mix(in srgb, var(--col-lng,#60a5fa) 35%, transparent); background: color-mix(in srgb, var(--col-lng,#60a5fa) 10%, transparent); } .cga-mbtn[data-mode="both"] { color: #c4b5fd; border-color: rgba(196,181,253,0.35); background: rgba(196,181,253,0.06); } .cga-mbtn[data-mode="lat"]:hover { border-color: color-mix(in srgb, var(--col-lat,#ff7070) 60%, transparent); background: color-mix(in srgb, var(--col-lat,#ff7070) 18%, transparent); } .cga-mbtn[data-mode="lng"]:hover { border-color: color-mix(in srgb, var(--col-lng,#60a5fa) 60%, transparent); background: color-mix(in srgb, var(--col-lng,#60a5fa) 18%, transparent); } .cga-mbtn[data-mode="both"]:hover { border-color: rgba(196,181,253,0.6); background: rgba(196,181,253,0.12); } .cga-mbtn[data-mode="lat"].on { background: color-mix(in srgb, var(--col-lat,#ff7070) 25%, transparent); border-color: color-mix(in srgb, var(--col-lat,#ff7070) 75%, transparent); color: var(--col-lat,#ff7070); } .cga-mbtn[data-mode="lng"].on { background: color-mix(in srgb, var(--col-lng,#60a5fa) 25%, transparent); border-color: color-mix(in srgb, var(--col-lng,#60a5fa) 75%, transparent); color: var(--col-lng,#60a5fa); } .cga-mbtn[data-mode="both"].on { color: #e2e8f0; border-color: rgba(167,139,250,0.75); background: linear-gradient(90deg, color-mix(in srgb, var(--col-lat,#ff7070) 25%, transparent), color-mix(in srgb, var(--col-lng,#60a5fa) 25%, transparent)); } .cga-stepper { display: flex; align-items: center; gap: 8px; background: #0d1117; border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; padding: 8px 12px; } .cga-sbtn { width: 24px; height: 24px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.07); background: rgba(255,255,255,0.04); color: #6b7280; font-size: 17px; font-weight: 700; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.12s; line-height: 1; user-select: none; } .cga-sbtn:hover { background: rgba(255,255,255,0.09); color: #e2e8f0; } .cga-snum { flex: 1; text-align: center; font-weight: 700; color: #e2e8f0; font-size: 16px; font-family: 'JetBrains Mono',monospace; } .cga-sunit { font-size: 11px; color: #2d3748; letter-spacing: 0.04em; } .cga-status-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } #cga-country-status { font-size: 11px; font-weight: 700; letter-spacing: 0.04em; color: var(--col-country,#fbbf24); min-height: 14px; } .cga-btn { width: 100%; padding: 9px 12px; border-radius: 9px; border: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.025); color: #4a5568; font-size: 11.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer; transition: all 0.15s; font-family: inherit; } .cga-btn:hover { background: rgba(255,255,255,0.06); color: #94a3b8; border-color: rgba(255,255,255,0.1); } .cga-color-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; } .cga-color-input { width: 34px; height: 24px; border: 1px solid rgba(255,255,255,0.16); border-radius: 6px; background: transparent; cursor: pointer; padding: 0; } .cga-color-input::-webkit-color-swatch-wrapper { padding: 0; } .cga-color-input::-webkit-color-swatch { border: 0; border-radius: 5px; } .cga-svheat-layer { filter: saturate(1.3) contrast(1.15); } #cga-hint { padding: 9px 18px 13px; text-align: center; font-size: 9.5px; letter-spacing: 0.12em; color: #161e2a; text-transform: uppercase; border-top: 1px solid rgba(255,255,255,0.03); flex-shrink: 0; } `; // Panel builder function buildPanel() { if (!document.getElementById('cga-fonts')) { const lnk = document.createElement('link'); lnk.id = 'cga-fonts'; lnk.rel = 'stylesheet'; lnk.href = 'https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap'; document.head.appendChild(lnk); } const styleEl = document.createElement('style'); styleEl.id = 'cga-styles'; styleEl.textContent = PANEL_CSS; document.head.appendChild(styleEl); const el = document.createElement('div'); el.id = 'cga-panel'; el.innerHTML = `
X
Lat/Long
Circle
Country
Misc
Display mode
Lat
Both
Lng

Latitude height ${cfg.latHeightKm} km
Longitude width ${cfg.lngWidthKm} km
Radius ${cfg.circleRadiusKm} km
Decoy countries
${cfg.countryDecoys} decoys
Pin exact location on the map

Street View heat overlay
Opacity ${cfg.svHeatOpacity}%

Last-cheat toggle key ${cfg.toggleHotkeyLabel || 'None'}
Open location in maps ${cfg.mapsHotkeyLabel || 'None'}

Latitude overlay
Longitude overlay
Circle overlay
Country overlay
Left Shift ยท toggle panel
`; document.body.appendChild(el); el.classList.add('cga-enter'); el.querySelector('#cga-close').onclick = () => hidePanel(el); el.querySelectorAll('.cga-tab').forEach(tab => { tab.onclick = () => { switchTabWithResize(el, tab.dataset.tab); }; }); const bandsTogEl = el.querySelector('#cga-bands-tog'); bandsTogEl.onchange = () => setCheatEnabled('bands', bandsTogEl.checked); el.querySelectorAll('.cga-mbtn').forEach(btn => { btn.onclick = () => { cfg.mode = btn.dataset.mode; saveConfig(); el.querySelectorAll('.cga-mbtn').forEach(b => b.classList.remove('on')); btn.classList.add('on'); drawBands(); }; }); bindSlider(el, '#cga-lat-sl', '#cga-lat-val', 'latHeightKm', 50, 5000, drawBands); bindSlider(el, '#cga-lng-sl', '#cga-lng-val', 'lngWidthKm', 50, 5000, drawBands); const circleTogEl = el.querySelector('#cga-circle-tog'); circleTogEl.onchange = () => setCheatEnabled('circle', circleTogEl.checked); bindSlider(el, '#cga-radius-sl', '#cga-radius-val', 'circleRadiusKm', 50, 5000, drawCircle); const countryTogEl = el.querySelector('#cga-country-tog'); countryTogEl.onchange = () => setCheatEnabled('country', countryTogEl.checked); el.querySelector('#cga-dec-minus').onclick = () => { if (cfg.countryDecoys > 0) { cfg.countryDecoys--; el.querySelector('#cga-dec-val').textContent = cfg.countryDecoys; saveConfig(); } }; el.querySelector('#cga-dec-plus').onclick = () => { if (cfg.countryDecoys < 25) { cfg.countryDecoys++; el.querySelector('#cga-dec-val').textContent = cfg.countryDecoys; saveConfig(); } }; el.querySelector('#cga-country-refetch').onclick = () => { if (!lastCoords) return; countryRetryAttempt = 0; clearCountryRetry(); clearCountryLayers(); drawCountryOutlines(lastCoords.lat, lastCoords.lng); }; const pinTogEl = el.querySelector('#cga-pin-tog'); pinTogEl.onchange = () => setCheatEnabled('pin', pinTogEl.checked); const svHeatTogEl = el.querySelector('#cga-svheat-tog'); const svHeatOpEl = el.querySelector('#cga-svheat-op'); const svHeatOpValEl = el.querySelector('#cga-svheat-op-val'); svHeatTogEl.onchange = () => setCheatEnabled('svheat', svHeatTogEl.checked); svHeatOpEl.oninput = () => { cfg.svHeatOpacity = parseInt(svHeatOpEl.value, 10); svHeatOpValEl.textContent = `${cfg.svHeatOpacity}%`; svHeatOpEl.style.setProperty('--pct', `${cfg.svHeatOpacity}%`); saveConfig(); if (cfg.svHeatEnabled) drawSvHeat(); }; const hotkeySetBtn = el.querySelector('#cga-hotkey-set'); const hotkeyClearBtn = el.querySelector('#cga-hotkey-clear'); const mapsHotkeySetBtn = el.querySelector('#cga-maps-hotkey-set'); const mapsHotkeyClearBtn = el.querySelector('#cga-maps-hotkey-clear'); hotkeySetBtn.onclick = () => { awaitingMapsHotkeyCapture = false; awaitingHotkeyCapture = !awaitingHotkeyCapture; syncHotkeyUi(); }; hotkeyClearBtn.onclick = () => { awaitingHotkeyCapture = false; cfg.toggleHotkeyCode = ''; cfg.toggleHotkeyLabel = 'None'; saveConfig(); syncHotkeyUi(); }; mapsHotkeySetBtn.onclick = () => { awaitingHotkeyCapture = false; awaitingMapsHotkeyCapture = !awaitingMapsHotkeyCapture; syncHotkeyUi(); }; mapsHotkeyClearBtn.onclick = () => { awaitingMapsHotkeyCapture = false; cfg.mapsHotkeyCode = ''; cfg.mapsHotkeyLabel = 'None'; saveConfig(); syncHotkeyUi(); }; const applyOverlayColor = (key, val) => { cfg[key] = val; saveConfig(); syncOverlayColorUi(); if (cfg.bandsEnabled) drawBands(); if (cfg.circleEnabled) drawCircle(); if (cfg.countryEnabled && lastCoords) { clearCountryLayers(); drawCountryOutlines(lastCoords.lat, lastCoords.lng); } }; el.querySelector('#cga-col-lat').oninput = e => applyOverlayColor('latColor', e.target.value); el.querySelector('#cga-col-lng').oninput = e => applyOverlayColor('lngColor', e.target.value); el.querySelector('#cga-col-circle').oninput = e => applyOverlayColor('circleColor', e.target.value); el.querySelector('#cga-col-country').oninput = e => applyOverlayColor('countryColor', e.target.value); el.querySelector('#cga-colors-reset').onclick = () => { cfg.latColor = '#ff5a5a'; cfg.lngColor = '#468cff'; cfg.circleColor = '#22c55e'; cfg.countryColor = '#fbbf24'; saveConfig(); syncOverlayColorUi(); el.querySelector('#cga-col-lat').value = cfg.latColor; el.querySelector('#cga-col-lng').value = cfg.lngColor; el.querySelector('#cga-col-circle').value = cfg.circleColor; el.querySelector('#cga-col-country').value = cfg.countryColor; if (cfg.bandsEnabled) drawBands(); if (cfg.circleEnabled) drawCircle(); if (cfg.countryEnabled && lastCoords) { clearCountryLayers(); drawCountryOutlines(lastCoords.lat, lastCoords.lng); } }; syncHotkeyUi(); syncOverlayColorUi(); syncCheatUi('svheat'); keepPanelInViewport(el); makeDraggable(el, el.querySelector('#cga-header')); el.addEventListener('keydown', e => e.stopImmediatePropagation(), true); el.addEventListener('keyup', e => e.stopImmediatePropagation(), true); panel = el; return el; } function bindSlider(panelEl, sliderId, valId, key, min, max, redrawFn) { const slider = panelEl.querySelector(sliderId); const valEl = panelEl.querySelector(valId); slider.addEventListener('input', () => { cfg[key] = parseInt(slider.value, 10); valEl.textContent = cfg[key] + ' km'; slider.style.setProperty('--pct', ((cfg[key] - min) / (max - min) * 100).toFixed(1) + '%'); saveConfig(); redrawFn(); }); } function makeDraggable(el, handle) { let startX, startY, origLeft, origTop; handle.addEventListener('mousedown', e => { if (e.target.closest('#cga-close')) return; e.preventDefault(); el.classList.add('cga-dragging'); const rect = el.getBoundingClientRect(); el.style.transform = 'none'; el.style.left = rect.left + 'px'; el.style.top = rect.top + 'px'; startX = e.clientX; startY = e.clientY; origLeft = rect.left; origTop = rect.top; const onMove = e2 => { el.style.left = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, origLeft + e2.clientX - startX)) + 'px'; el.style.top = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, origTop + e2.clientY - startY)) + 'px'; }; const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); el.classList.remove('cga-dragging'); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }); } function applyPanelAutoScale(el) { if (!el) return; const safePad = 12; const availW = Math.max(220, window.innerWidth - safePad * 2); const availH = Math.max(220, window.innerHeight - safePad * 2); const currentZoom = Math.max(0.01, parseFloat(el.style.getPropertyValue('--cga-zoom')) || 1); const rect = el.getBoundingClientRect(); // Convert current on-screen size back to unscaled size, then apply one uniform scale. const naturalW = Math.max(1, rect.width / currentZoom); const naturalH = Math.max(1, rect.height / currentZoom); const fitScale = Math.min(1, availW / naturalW, availH / naturalH); // Use zoom for consistent shrinking in Chromium-based browsers. el.style.setProperty('--cga-scale', '1'); el.style.setProperty('--cga-zoom', fitScale.toFixed(4)); } function keepPanelInViewport(el) { if (!el || el.style.display === 'none') return; requestAnimationFrame(() => { applyPanelAutoScale(el); const rect = el.getBoundingClientRect(); let nextTop = rect.top; let nextLeft = rect.left; let changed = false; if (rect.bottom > window.innerHeight) { nextTop = Math.max(0, rect.top - (rect.bottom - window.innerHeight)); changed = true; } if (rect.top < 0) { nextTop = 0; changed = true; } if (rect.right > window.innerWidth) { nextLeft = Math.max(0, rect.left - (rect.right - window.innerWidth)); changed = true; } if (rect.left < 0) { nextLeft = 0; changed = true; } if (!changed) return; if (el.style.transform !== 'none') { el.style.transform = 'none'; el.style.left = rect.left + 'px'; el.style.top = rect.top + 'px'; } el.style.left = nextLeft + 'px'; el.style.top = nextTop + 'px'; }); } function showPanel(el) { if (!el) return; el.classList.remove('cga-exit'); el.style.display = 'flex'; keepPanelInViewport(el); // Restart entry animation cleanly. el.classList.remove('cga-enter'); void el.offsetWidth; el.classList.add('cga-enter'); } function switchTabWithResize(el, nextTab) { if (!el) return; const startHeight = el.offsetHeight; activeTab = nextTab; el.querySelectorAll('.cga-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === nextTab)); el.querySelectorAll('.cga-tab-body').forEach(b => b.classList.toggle('active', b.dataset.body === nextTab)); applyPanelAutoScale(el); const endHeight = el.offsetHeight; if (startHeight !== endHeight) { const rect = el.getBoundingClientRect(); let targetTop = rect.top; if (rect.top + endHeight > window.innerHeight) { targetTop = Math.max(0, window.innerHeight - endHeight); } el.style.height = startHeight + 'px'; void el.offsetHeight; el.style.height = endHeight + 'px'; if (targetTop !== rect.top) { if (el.style.transform !== 'none') { el.style.transform = 'none'; el.style.left = rect.left + 'px'; el.style.top = rect.top + 'px'; } el.style.top = targetTop + 'px'; } const onEnd = () => { el.style.height = ''; keepPanelInViewport(el); el.removeEventListener('transitionend', onEnd); }; el.addEventListener('transitionend', onEnd); } keepPanelInViewport(el); } function hidePanel(el) { if (!el || el.style.display === 'none') return; el.classList.remove('cga-enter'); el.classList.add('cga-exit'); setTimeout(() => { if (el.classList.contains('cga-exit')) { el.style.display = 'none'; el.classList.remove('cga-exit'); } }, 170); } document.addEventListener('keydown', e => { if (awaitingHotkeyCapture || awaitingMapsHotkeyCapture) { e.preventDefault(); e.stopImmediatePropagation(); if (e.key === 'Escape') { awaitingHotkeyCapture = false; awaitingMapsHotkeyCapture = false; syncHotkeyUi(); return; } if (e.key === 'Shift' && e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) { return; } if (awaitingHotkeyCapture) { cfg.toggleHotkeyCode = e.code; cfg.toggleHotkeyLabel = formatHotkeyFromEvent(e); } if (awaitingMapsHotkeyCapture) { cfg.mapsHotkeyCode = e.code; cfg.mapsHotkeyLabel = formatHotkeyFromEvent(e); } awaitingHotkeyCapture = false; awaitingMapsHotkeyCapture = false; saveConfig(); syncHotkeyUi(); return; } if (e.key === 'Shift' && e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) { e.preventDefault(); e.stopImmediatePropagation(); if (!panel) buildPanel(); else { const shouldShow = panel.style.display === 'none'; if (shouldShow) showPanel(panel); else hidePanel(panel); } return; } if (cfg.toggleHotkeyCode && e.code === cfg.toggleHotkeyCode) { e.preventDefault(); e.stopImmediatePropagation(); toggleMostRecentCheat(); return; } if (cfg.mapsHotkeyCode && e.code === cfg.mapsHotkeyCode) { e.preventDefault(); e.stopImmediatePropagation(); openCurrentLocationInMaps(); } }, true); window.addEventListener('resize', () => keepPanelInViewport(panel)); let lastUrl = location.href; setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(() => { gameMap = null; lastCoords = null; countryBusy = false; countryRunId++; clearCountryRetry(); countryRetryAttempt = 0; exactPinMarker = null; if (coordPollId) { clearInterval(coordPollId); coordPollId = null; } iframeObserver.observe(document.body, { childList: true, subtree: true }); mapObserver.observe(document.body, { childList: true, subtree: true }); }, 900); } }, 500); const domWatcher = new MutationObserver(() => { if (!document.querySelector('.leaflet-container') && gameMap) gameMap = null; }); domWatcher.observe(document.body, { childList: true, subtree: true }); })();