// ==UserScript== // @name WorldGuessr GeoAssist // @namespace https://greasyfork.org/ // @version 1.0 // @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 none // ==/UserScript== (function () { 'use strict'; const _Proxy = window.Proxy; const _Reflect = window.Reflect; const SCRIPT_VERSION = '1.0'; const PIN_GLYPH_SVG = ''; const MAP_PIN_IMAGE_URL = 'https://cdn.discordapp.com/attachments/1470671042975502502/1500377533265219765/bluepin.png?ex=69f836f7&is=69f6e577&hm=02350a611af2193ec848c20fa19e69b5ea8d3f1d9bc9bf392493a3913f7e6601&'; 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%); z-index: 2147483647; width: 392px; max-height: 82vh; 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 = `