Clean cheat overlay for WorldGuessr — reads coords from Street View iframe, pins the location on the guess map
// ==UserScript==
// @name WG Cheat Panel
// @namespace wg-cheat-panel
// @version 1.0.0
// @description Clean cheat overlay for WorldGuessr — reads coords from Street View iframe, pins the location on the guess map
// @author RandomAccount
// @match https://www.worldguessr.com/*
// @license MIT
// @grant none
// @run-at document-idle
// ==/UserScript==
(() => {
"use strict";
// ─────────────────────────────────────────────
// State
// ─────────────────────────────────────────────
let lat = null;
let lng = null;
let panelOpen = false;
let isDragging = false;
let dragOffX = 0;
let dragOffY = 0;
let lastSrc = "";
let leafletMarker = null;
let pinActive = false;
// ─────────────────────────────────────────────
// Coord extraction — parses Google Maps iframe src
// ─────────────────────────────────────────────
function parseCoords(src) {
if (!src) return null;
let m;
m = src.match(/[?&]location=(-?\d+\.\d+),(-?\d+\.\d+)/);
if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) };
m = src.match(/[?&]q=(-?\d+\.\d+),(-?\d+\.\d+)/);
if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) };
m = src.match(/@(-?\d+\.\d+),(-?\d+\.\d+)/);
if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) };
m = src.match(/!2d(-?\d+\.\d+)!3d(-?\d+\.\d+)/);
if (m) return { lat: parseFloat(m[2]), lng: parseFloat(m[1]) };
m = src.match(/cbll=(-?\d+\.\d+),(-?\d+\.\d+)/);
if (m) return { lat: parseFloat(m[1]), lng: parseFloat(m[2]) };
return null;
}
function findCoords() {
for (const iframe of document.querySelectorAll("iframe")) {
const src = iframe.src || iframe.getAttribute("src") || "";
if (!src.includes("google.com/maps")) continue;
const c = parseCoords(src);
if (c) return c;
}
return null;
}
// ─────────────────────────────────────────────
// Leaflet map finder
// Uses the same deep recursive scan as the
// original working script: walks object properties
// up to depth 4 looking for a Leaflet map instance
// whose _container matches the leaflet-container el.
// ─────────────────────────────────────────────
function isLeafletCandidate(obj, container) {
return Boolean(
obj &&
typeof obj === "object" &&
typeof obj.latLngToContainerPoint === "function" &&
typeof obj.containerPointToLatLng === "function" &&
typeof obj.getZoom === "function" &&
obj._container === container
);
}
function findLeafletMapForContainer(container) {
if (!container) return null;
const seen = new WeakSet();
function scan(obj, depth) {
if (!obj || typeof obj !== "object" || seen.has(obj) || depth < 0) return null;
seen.add(obj);
if (isLeafletCandidate(obj, container)) return obj;
let keys = [];
try { keys = Object.getOwnPropertyNames(obj); } catch { return null; }
for (const key of keys) {
if (key === "parentNode" || key === "children" || key === "childNodes") continue;
let val;
try { val = obj[key]; } catch { continue; }
if (!val || typeof val !== "object") continue;
if (isLeafletCandidate(val, container)) return val;
const found = scan(val, depth - 1);
if (found) return found;
}
return null;
}
// Search roots: the container itself, its parent, any #miniMapArea ancestor, then window
const roots = [
container,
container.parentElement,
container.closest("#miniMapArea"),
window,
];
for (const root of roots) {
if (!root) continue;
const found = scan(root, root === window ? 2 : 4);
if (found) return found;
}
return null;
}
function getLeafletMap() {
const containers = document.querySelectorAll(".leaflet-container");
for (const el of containers) {
const map = findLeafletMapForContainer(el);
if (map) return map;
}
return null;
}
// ─────────────────────────────────────────────
// Pin management
// ─────────────────────────────────────────────
// Extract the Leaflet constructor (L) from the map instance itself.
// WorldGuessr bundles Leaflet as a module so window.L is often undefined.
// But the map object's prototype chain leads back to L.Map, and L.Map
// has .addInitHook on it — we can walk up to find the L namespace by
// looking for divIcon / marker on the map's constructor exports,
// or we can use the map's own addLayer to place a raw marker directly.
function getLFromMap(map) {
if (window.L) return window.L;
// Try to get L from the map's options or internal refs
try {
// Leaflet attaches _leaflet_id to instances; the constructor is L.Map
// Walk the prototype to find the namespace
let proto = Object.getPrototypeOf(map);
while (proto) {
const ctor = proto.constructor;
if (ctor && ctor.version && typeof ctor.marker === "function") return ctor;
proto = Object.getPrototypeOf(proto);
}
} catch {}
return null;
}
function placePin(coordLat, coordLng) {
const map = getLeafletMap();
if (!map) return false;
// Remove existing marker
if (leafletMarker) {
try { map.removeLayer(leafletMarker); } catch {}
leafletMarker = null;
}
// Build the pin as a raw positioned div injected into the map's pane,
// positioned via Leaflet's own latLngToLayerPoint — no L namespace needed.
try {
const L = getLFromMap(map);
if (L) {
// Full Leaflet path — proper marker with custom icon
const icon = L.divIcon({
className: "",
html: `<div style="width:28px;height:36px;filter:drop-shadow(0 3px 8px rgba(99,102,241,0.5))">
<svg viewBox="0 0 28 36" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%">
<path d="M14 0C6.27 0 0 6.27 0 14c0 9.63 14 22 14 22S28 23.63 28 14C28 6.27 21.73 0 14 0z" fill="#6366f1"/>
<circle cx="14" cy="14" r="5.5" fill="white"/>
<circle cx="14" cy="14" r="3" fill="#6366f1"/>
</svg>
</div>`,
iconSize: [28, 36],
iconAnchor: [14, 36],
});
leafletMarker = L.marker([coordLat, coordLng], { icon, zIndexOffset: 9999 }).addTo(map);
} else {
// Fallback: inject a DOM pin directly into the marker pane and
// reposition it whenever the map moves, using the map's own projection.
const markerPane = map.getPane ? map.getPane("markerPane") : map._panes && map._panes.markerPane;
if (!markerPane) return false;
const pin = document.createElement("div");
pin.id = "wgcp-leaflet-pin";
pin.style.cssText = `
position: absolute;
width: 28px;
height: 36px;
pointer-events: none;
z-index: 9999;
filter: drop-shadow(0 3px 8px rgba(99,102,241,0.5));
transform: translate(-14px, -36px);
`;
pin.innerHTML = `
<svg viewBox="0 0 28 36" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%">
<path d="M14 0C6.27 0 0 6.27 0 14c0 9.63 14 22 14 22S28 23.63 28 14C28 6.27 21.73 0 14 0z" fill="#6366f1"/>
<circle cx="14" cy="14" r="5.5" fill="white"/>
<circle cx="14" cy="14" r="3" fill="#6366f1"/>
</svg>
`;
markerPane.appendChild(pin);
function positionPin() {
try {
const pt = map.latLngToLayerPoint([coordLat, coordLng]);
pin.style.left = pt.x + "px";
pin.style.top = pt.y + "px";
} catch {}
}
positionPin();
map.on("move zoom viewreset zoomend moveend", positionPin);
// Store as a fake marker object so removePin can clean it up
leafletMarker = {
_wgcpDom: pin,
_wgcpMap: map,
_wgcpHandler: positionPin,
};
}
pinActive = true;
return true;
} catch {
return false;
}
}
function removePin() {
if (leafletMarker) {
// Real Leaflet marker
if (typeof leafletMarker.remove === "function" || typeof leafletMarker.removeFrom === "function") {
const map = getLeafletMap();
try { if (map) map.removeLayer(leafletMarker); } catch {}
try { leafletMarker.remove(); } catch {}
}
// DOM fallback marker
if (leafletMarker._wgcpDom) {
try {
leafletMarker._wgcpMap.off("move zoom viewreset zoomend moveend", leafletMarker._wgcpHandler);
leafletMarker._wgcpDom.remove();
} catch {}
}
leafletMarker = null;
}
// Also clean up any orphaned pin divs
document.getElementById("wgcp-leaflet-pin")?.remove();
pinActive = false;
}
// ─────────────────────────────────────────────
// Styles
// ─────────────────────────────────────────────
const style = document.createElement("style");
style.textContent = `
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&family=DM+Mono:wght@400;500&display=swap');
#wgcp-root {
position: fixed;
bottom: 24px;
left: 24px;
z-index: 999999;
font-family: 'DM Sans', system-ui, sans-serif;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
#wgcp-panel {
width: 300px;
background: #ffffff;
border-radius: 20px;
box-shadow:
0 0 0 1px rgba(0,0,0,0.06),
0 4px 6px -1px rgba(0,0,0,0.06),
0 24px 48px -8px rgba(0,0,0,0.18);
overflow: hidden;
display: none;
flex-direction: column;
transform-origin: bottom left;
}
#wgcp-panel.open {
display: flex;
animation: wgcp-pop 0.2s cubic-bezier(0.34,1.56,0.64,1);
}
@keyframes wgcp-pop {
from { opacity:0; transform:scale(0.88) translateY(12px); }
to { opacity:1; transform:scale(1) translateY(0); }
}
/* Header */
#wgcp-header {
padding: 14px 14px 0;
display: flex;
align-items: center;
justify-content: space-between;
cursor: move;
user-select: none;
gap: 10px;
}
#wgcp-logo {
width: 32px;
height: 32px;
border-radius: 10px;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
#wgcp-logo svg {
width: 16px;
height: 16px;
fill: none;
stroke: white;
stroke-width: 2;
stroke-linecap: round;
}
#wgcp-title-group { flex: 1; min-width: 0; }
#wgcp-title {
font-size: 14px;
font-weight: 700;
color: #0f172a;
line-height: 1.2;
}
#wgcp-subtitle {
font-size: 11px;
color: #94a3b8;
font-weight: 500;
}
#wgcp-close {
width: 26px;
height: 26px;
border-radius: 8px;
border: none;
background: #f1f5f9;
color: #94a3b8;
cursor: pointer;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
flex-shrink: 0;
}
#wgcp-close:hover { background: #fee2e2; color: #ef4444; }
/* Status */
#wgcp-status-wrap { padding: 10px 14px 0; }
#wgcp-status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
background: #f8fafc;
border: 1px solid #e2e8f0;
font-size: 11px;
font-weight: 600;
color: #94a3b8;
transition: all 0.3s;
}
#wgcp-status.found {
background: #f0fdf4;
border-color: #86efac;
color: #16a34a;
}
#wgcp-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #cbd5e1;
flex-shrink: 0;
transition: background 0.3s, box-shadow 0.3s;
}
#wgcp-status.found #wgcp-dot {
background: #22c55e;
box-shadow: 0 0 0 3px rgba(34,197,94,0.2);
animation: wgcp-blink 2s infinite;
}
@keyframes wgcp-blink {
0%,100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Coord cards */
#wgcp-coords {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
padding: 10px 14px 0;
}
.wgcp-card {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 10px 11px;
transition: all 0.25s;
}
.wgcp-card.lit {
background: #eef2ff;
border-color: #c7d2fe;
}
.wgcp-card-label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.07em;
text-transform: uppercase;
color: #cbd5e1;
margin-bottom: 2px;
transition: color 0.25s;
}
.wgcp-card.lit .wgcp-card-label { color: #a5b4fc; }
.wgcp-card-value {
font-family: 'DM Mono', monospace;
font-size: 14px;
font-weight: 500;
color: #dde0ff;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.25s;
}
.wgcp-card.lit .wgcp-card-value { color: #4338ca; }
/* Map */
#wgcp-map-wrap {
margin: 10px 14px 0;
border-radius: 12px;
overflow: hidden;
height: 152px;
background: #f8fafc;
border: 1px solid #e2e8f0;
position: relative;
flex-shrink: 0;
}
#wgcp-map-empty {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
}
#wgcp-map-empty svg {
width: 26px;
height: 26px;
stroke: #e2e8f0;
fill: none;
stroke-width: 1.5;
}
#wgcp-map-empty span {
font-size: 12px;
color: #cbd5e1;
font-weight: 500;
}
#wgcp-map-iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
display: none;
}
/* Actions */
#wgcp-actions {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 6px;
padding: 10px 14px 14px;
}
.wgcp-btn {
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 9px 6px 8px;
font-family: 'DM Sans', system-ui, sans-serif;
font-size: 11px;
font-weight: 600;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
transition: all 0.15s;
background: #f8fafc;
color: #475569;
line-height: 1.1;
text-align: center;
}
.wgcp-btn svg {
width: 15px;
height: 15px;
fill: none;
stroke: #6366f1;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
}
.wgcp-btn:hover {
background: #f1f5f9;
border-color: #c7d2fe;
color: #4338ca;
transform: translateY(-1px);
}
.wgcp-btn:active { transform: translateY(0); }
.wgcp-btn.primary {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border-color: transparent;
color: white;
box-shadow: 0 2px 8px rgba(99,102,241,0.3);
}
.wgcp-btn.primary svg { stroke: white; }
.wgcp-btn.primary:hover { opacity: 0.9; color: white; border-color: transparent; }
.wgcp-btn.danger {
background: #fef2f2;
border-color: #fecaca;
color: #dc2626;
}
.wgcp-btn.danger svg { stroke: #dc2626; }
.wgcp-btn.danger:hover { background: #fee2e2; color: #dc2626; border-color: #fca5a5; }
.wgcp-btn.success {
background: #f0fdf4;
border-color: #86efac;
color: #16a34a;
}
.wgcp-btn.success svg { stroke: #16a34a; }
/* FAB */
#wgcp-fab-wrap { position: relative; }
#wgcp-fab {
width: 46px;
height: 46px;
border-radius: 14px;
border: none;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(99,102,241,0.45);
transition: all 0.2s cubic-bezier(0.34,1.56,0.64,1);
}
#wgcp-fab:hover {
transform: scale(1.08);
box-shadow: 0 6px 20px rgba(99,102,241,0.55);
}
#wgcp-fab:active { transform: scale(0.96); }
#wgcp-fab svg {
width: 20px;
height: 20px;
fill: none;
stroke: white;
stroke-width: 2.5;
stroke-linecap: round;
transition: transform 0.25s;
}
#wgcp-fab.open svg { transform: rotate(45deg); }
#wgcp-pin-badge {
position: absolute;
top: -3px;
right: -3px;
width: 11px;
height: 11px;
border-radius: 50%;
background: #22c55e;
border: 2px solid white;
display: none;
}
`;
document.head.appendChild(style);
// ─────────────────────────────────────────────
// Build DOM
// ─────────────────────────────────────────────
const root = document.createElement("div");
root.id = "wgcp-root";
document.body.appendChild(root);
const panel = document.createElement("div");
panel.id = "wgcp-panel";
root.appendChild(panel);
panel.innerHTML = `
<div id="wgcp-header">
<div id="wgcp-logo">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="3"/>
<circle cx="12" cy="12" r="7"/>
<line x1="12" y1="2" x2="12" y2="5"/>
<line x1="12" y1="19" x2="12" y2="22"/>
<line x1="2" y1="12" x2="5" y2="12"/>
<line x1="19" y1="12" x2="22" y2="12"/>
</svg>
</div>
<div id="wgcp-title-group">
<div id="wgcp-title">WG Cheat Panel</div>
<div id="wgcp-subtitle">Location assistant</div>
</div>
<button id="wgcp-close">✕</button>
</div>
<div id="wgcp-status-wrap">
<div id="wgcp-status">
<div id="wgcp-dot"></div>
<span id="wgcp-status-text">Waiting for location…</span>
</div>
</div>
<div id="wgcp-coords">
<div class="wgcp-card" id="wgcp-lat-card">
<div class="wgcp-card-label">Latitude</div>
<div class="wgcp-card-value" id="wgcp-lat">—</div>
</div>
<div class="wgcp-card" id="wgcp-lng-card">
<div class="wgcp-card-label">Longitude</div>
<div class="wgcp-card-value" id="wgcp-lng">—</div>
</div>
</div>
<div id="wgcp-map-wrap">
<div id="wgcp-map-empty">
<svg viewBox="0 0 24 24"><path d="M3 7l6-3 6 3 6-3v13l-6 3-6-3-6 3V7z"/><line x1="9" y1="4" x2="9" y2="17"/><line x1="15" y1="7" x2="15" y2="20"/></svg>
<span>No location yet</span>
</div>
<iframe id="wgcp-map-iframe" loading="lazy" referrerpolicy="no-referrer-when-downgrade" allowfullscreen></iframe>
</div>
<div id="wgcp-actions">
<button class="wgcp-btn primary" id="wgcp-pin-btn">
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
Pin map
</button>
<button class="wgcp-btn" id="wgcp-copy-btn">
<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
Copy
</button>
<button class="wgcp-btn" id="wgcp-maps-btn">
<svg viewBox="0 0 24 24"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
Maps
</button>
</div>
`;
// FAB
const fabWrap = document.createElement("div");
fabWrap.id = "wgcp-fab-wrap";
const fab = document.createElement("button");
fab.id = "wgcp-fab";
fab.innerHTML = `<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>`;
const pinBadge = document.createElement("div");
pinBadge.id = "wgcp-pin-badge";
fabWrap.appendChild(fab);
fabWrap.appendChild(pinBadge);
root.appendChild(fabWrap);
// ─────────────────────────────────────────────
// Refs
// ─────────────────────────────────────────────
const statusEl = panel.querySelector("#wgcp-status");
const statusText = panel.querySelector("#wgcp-status-text");
const latEl = panel.querySelector("#wgcp-lat");
const lngEl = panel.querySelector("#wgcp-lng");
const latCard = panel.querySelector("#wgcp-lat-card");
const lngCard = panel.querySelector("#wgcp-lng-card");
const mapIframe = panel.querySelector("#wgcp-map-iframe");
const mapEmpty = panel.querySelector("#wgcp-map-empty");
const pinBtn = panel.querySelector("#wgcp-pin-btn");
const copyBtn = panel.querySelector("#wgcp-copy-btn");
const mapsBtn = panel.querySelector("#wgcp-maps-btn");
const closeBtn = panel.querySelector("#wgcp-close");
const header = panel.querySelector("#wgcp-header");
// ─────────────────────────────────────────────
// Drag
// ─────────────────────────────────────────────
header.addEventListener("mousedown", (e) => {
if (e.target === closeBtn) return;
isDragging = true;
const rect = root.getBoundingClientRect();
dragOffX = e.clientX - rect.left;
dragOffY = e.clientY - rect.bottom;
e.preventDefault();
});
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
root.style.left = Math.max(8, e.clientX - dragOffX) + "px";
root.style.bottom = Math.max(8, window.innerHeight - (e.clientY - dragOffY)) + "px";
});
document.addEventListener("mouseup", () => { isDragging = false; });
// ─────────────────────────────────────────────
// FAB toggle
// ─────────────────────────────────────────────
fab.addEventListener("click", () => {
panelOpen = !panelOpen;
panel.classList.toggle("open", panelOpen);
fab.classList.toggle("open", panelOpen);
});
closeBtn.addEventListener("click", () => {
panelOpen = false;
panel.classList.remove("open");
fab.classList.remove("open");
});
// ─────────────────────────────────────────────
// Pin button
// ─────────────────────────────────────────────
function setPinBtnState(active) {
if (active) {
pinBtn.className = "wgcp-btn danger";
pinBtn.innerHTML = `
<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
Remove
`;
pinBadge.style.display = "block";
} else {
pinBtn.className = "wgcp-btn primary";
pinBtn.innerHTML = `
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
Pin map
`;
pinBadge.style.display = "none";
}
}
pinBtn.addEventListener("click", () => {
if (lat === null) return;
if (pinActive) {
removePin();
setPinBtnState(false);
} else {
const ok = placePin(lat, lng);
if (ok) {
setPinBtnState(true);
} else {
const prev = pinBtn.innerHTML;
pinBtn.className = "wgcp-btn";
pinBtn.textContent = "Not ready";
setTimeout(() => {
pinBtn.className = "wgcp-btn primary";
pinBtn.innerHTML = prev;
}, 1500);
}
}
});
// ─────────────────────────────────────────────
// Copy
// ─────────────────────────────────────────────
copyBtn.addEventListener("click", () => {
if (lat === null) return;
navigator.clipboard.writeText(`${lat.toFixed(6)}, ${lng.toFixed(6)}`).then(() => {
copyBtn.classList.add("success");
copyBtn.innerHTML = `<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>Copied!`;
setTimeout(() => {
copyBtn.classList.remove("success");
copyBtn.innerHTML = `<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>Copy`;
}, 1800);
});
});
// ─────────────────────────────────────────────
// Open Google Maps
// ─────────────────────────────────────────────
mapsBtn.addEventListener("click", () => {
if (lat === null) return;
window.open(`https://www.google.com/maps?q=${lat},${lng}&z=10`, "_blank");
});
// ─────────────────────────────────────────────
// UI update
// ─────────────────────────────────────────────
function updateUI(coords) {
if (!coords) return;
if (coords.lat === lat && coords.lng === lng) return;
lat = coords.lat;
lng = coords.lng;
statusEl.classList.add("found");
statusText.textContent = "Location acquired";
latEl.textContent = lat.toFixed(5);
lngEl.textContent = lng.toFixed(5);
latCard.classList.add("lit");
lngCard.classList.add("lit");
const url = `https://www.google.com/maps?q=${lat},${lng}&z=7&output=embed`;
if (mapIframe.src !== url) mapIframe.src = url;
mapIframe.style.display = "block";
mapEmpty.style.display = "none";
// Move existing pin to new coords on round change
if (pinActive) placePin(lat, lng);
}
// ─────────────────────────────────────────────
// Polling
// ─────────────────────────────────────────────
function poll() {
const iframe = document.querySelector("iframe[src*='google.com/maps']");
const src = iframe ? (iframe.src || "") : "";
if (src === lastSrc) return;
lastSrc = src;
updateUI(findCoords());
}
new MutationObserver(() => {
updateUI(findCoords());
}).observe(document.body, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["src"],
});
setInterval(poll, 800);
poll();
})();