// ==UserScript== // @name World Guessr Cheat Panel // @namespace https://greasyfork.org/users/your-user-id // @version 1.0.0 // @description Adds a draggable panel to World Guessr that reads coordinates already present on the page, shows a live map preview, and allows placing/removing a pin. // @author RandomAccount // @license MIT (Chatgpt) // @match https://www.worldguessr.com/ // @grant none // @run-at document-idle // @downloadURL none // ==/UserScript== (() => { "use strict"; let lastCoords = null; let isOpen = false; let isMinimized = false; let isDragging = false; let isResizing = false; let offsetX = 0; let offsetY = 0; let savedHeight = 720; let savedWidth = 420; let pinnedLatLng = null; let pinEnabled = false; let activeTab = "menu"; document.querySelectorAll(".sgp-root, .sgp-style").forEach((el) => el.remove()); const root = document.createElement("div"); root.className = "sgp-root"; document.body.appendChild(root); const style = document.createElement("style"); style.className = "sgp-style"; style.textContent = ` .sgp-root * { box-sizing: border-box; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .sgp-fab { position: fixed; bottom: 22px; left: 22px; z-index: 100001; display: flex; align-items: center; gap: 10px; padding: 14px 18px; border: 1px solid rgba(255,255,255,0.15); border-radius: 18px; background: linear-gradient(135deg, rgba(17,24,39,0.96), rgba(31,41,55,0.96)); color: #fff; font-size: 14px; font-weight: 700; letter-spacing: 0.2px; cursor: pointer; box-shadow: 0 18px 45px rgba(0,0,0,0.35); transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease; } .sgp-fab:hover { transform: translateY(-2px) scale(1.01); box-shadow: 0 22px 52px rgba(0,0,0,0.42); } .sgp-fab-icon { width: 28px; height: 28px; border-radius: 10px; display: grid; place-items: center; background: linear-gradient(135deg, #34d399, #10b981); color: #06281d; font-size: 15px; font-weight: 900; box-shadow: 0 0 18px rgba(16,185,129,0.4); flex-shrink: 0; } .sgp-overlay { position: fixed; inset: 0; z-index: 100000; background: rgba(2,6,23,0.12); opacity: 0; pointer-events: none; transition: opacity 0.25s ease; } .sgp-overlay.open { opacity: 1; pointer-events: auto; } .sgp-panel { position: absolute; top: 26px; left: 26px; width: 420px; height: 720px; display: flex; flex-direction: column; border-radius: 28px; overflow: hidden; background: #f8fafc; border: 1px solid rgba(226,232,240,0.95); box-shadow: 0 25px 70px rgba(15,23,42,0.25), 0 8px 24px rgba(15,23,42,0.12); transform: translateX(-14px) scale(0.97); opacity: 0; transition: transform 0.28s ease, opacity 0.28s ease; user-select: none; } .sgp-overlay.open .sgp-panel { transform: translateX(0) scale(1); opacity: 1; } .sgp-header { height: 68px; min-height: 68px; display: flex; align-items: center; justify-content: space-between; padding: 0 14px 0 16px; background: linear-gradient(135deg, #0f172a, #1e293b); color: white; border-bottom: 1px solid rgba(255,255,255,0.08); cursor: move; } .sgp-header-left { display: flex; align-items: center; gap: 12px; min-width: 0; } .sgp-logo { width: 40px; height: 40px; border-radius: 14px; display: grid; place-items: center; font-size: 18px; background: linear-gradient(135deg, #34d399, #10b981); color: #06281d; box-shadow: 0 0 22px rgba(52,211,153,0.35); flex-shrink: 0; } .sgp-title-group { min-width: 0; } .sgp-title { font-size: 15px; font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.1; } .sgp-subtitle { font-size: 11px; opacity: 0.72; margin-top: 4px; letter-spacing: 0.2px; } .sgp-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } .sgp-btn { width: 34px; height: 34px; border: none; border-radius: 12px; background: rgba(255,255,255,0.10); color: white; cursor: pointer; font-size: 15px; font-weight: 800; transition: all 0.18s ease; } .sgp-btn:hover { background: rgba(255,255,255,0.20); transform: scale(1.06); } .sgp-content { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 12px; padding: 14px; background: #f8fafc; overflow: hidden; } .sgp-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; background: rgba(255,255,255,0.7); padding: 6px; border-radius: 18px; border: 1px solid rgba(226,232,240,0.95); box-shadow: 0 10px 24px rgba(15,23,42,0.05); flex-shrink: 0; } .sgp-tab-btn { border: none; border-radius: 14px; padding: 11px 12px; font-size: 12px; font-weight: 800; letter-spacing: 0.02em; cursor: pointer; background: transparent; color: #475569; transition: all 0.18s ease; } .sgp-tab-btn:hover { background: rgba(241,245,249,0.95); color: #0f172a; } .sgp-tab-btn.active { background: linear-gradient(135deg, #0f172a, #1e293b); color: white; box-shadow: 0 10px 20px rgba(15,23,42,0.18); } .sgp-pages { flex: 1; min-height: 0; position: relative; overflow: hidden; } .sgp-page { position: absolute; inset: 0; display: none; flex-direction: column; gap: 12px; overflow-y: auto; overflow-x: hidden; padding-right: 4px; min-height: 0; } .sgp-page.active { display: flex; } .sgp-page::-webkit-scrollbar { width: 8px; } .sgp-page::-webkit-scrollbar-thumb { background: rgba(148,163,184,0.4); border-radius: 999px; } .sgp-page::-webkit-scrollbar-track { background: transparent; } .sgp-menu-card, .sgp-map-card, .sgp-info-card, .sgp-credit-page-card { background: #ffffff; border: 1px solid rgba(226,232,240,0.95); border-radius: 22px; box-shadow: 0 10px 30px rgba(15,23,42,0.06); } .sgp-menu-card { padding: 14px; display: flex; flex-direction: column; gap: 14px; background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(248,250,252,0.98)); flex-shrink: 0; } .sgp-menu-section { display: flex; flex-direction: column; gap: 10px; } .sgp-menu-section-title { font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #64748b; padding: 0 2px; } .sgp-menu-labels, .sgp-menu-buttons { display: grid; gap: 10px; } .sgp-menu-labels { grid-template-columns: repeat(3, 1fr); } .sgp-menu-buttons { grid-template-columns: repeat(2, 1fr); } .sgp-menu-label-static { position: relative; padding: 14px 10px 12px; border-radius: 18px; background: linear-gradient(180deg, #ffffff, #f8fafc); border: 1px solid rgba(226,232,240,0.95); display: flex; flex-direction: column; align-items: center; gap: 9px; text-align: center; cursor: default; box-shadow: 0 8px 22px rgba(15,23,42,0.05); } .sgp-menu-label-static::after { content: ""; position: absolute; inset: 0; border-radius: 18px; pointer-events: none; box-shadow: inset 0 1px 0 rgba(255,255,255,0.75); } .sgp-menu-item { position: relative; padding: 14px 10px 12px; border-radius: 18px; background: linear-gradient(180deg, #ffffff, #f8fafc); border: 1px solid rgba(226,232,240,0.95); display: flex; flex-direction: column; align-items: center; gap: 9px; cursor: pointer; text-align: center; box-shadow: 0 10px 24px rgba(15,23,42,0.07); transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease, background 0.18s ease; } .sgp-menu-item:hover { transform: translateY(-2px); box-shadow: 0 14px 28px rgba(15,23,42,0.11); border-color: rgba(148,163,184,0.45); background: linear-gradient(180deg, #ffffff, #f1f5f9); } .sgp-menu-item:active { transform: translateY(0); } .sgp-pin-btn { background: linear-gradient(135deg, #ecfdf5, #d1fae5); border: 1px solid rgba(16,185,129,0.22); outline: none; } .sgp-pin-btn:hover { background: linear-gradient(135deg, #f0fdf4, #bbf7d0); border-color: rgba(16,185,129,0.32); } .sgp-remove-pin-btn { background: linear-gradient(135deg, #fff7ed, #ffedd5); border: 1px solid rgba(249,115,22,0.20); outline: none; } .sgp-remove-pin-btn:hover { background: linear-gradient(135deg, #fff7ed, #fed7aa); border-color: rgba(249,115,22,0.32); } .sgp-menu-icon { width: 42px; height: 42px; border-radius: 15px; display: grid; place-items: center; background: linear-gradient(135deg, rgba(16,185,129,0.16), rgba(59,130,246,0.14)); font-size: 19px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 6px 14px rgba(15,23,42,0.06); } .sgp-pin-btn .sgp-menu-icon { background: linear-gradient(135deg, rgba(16,185,129,0.24), rgba(34,197,94,0.18)); } .sgp-remove-pin-btn .sgp-menu-icon { background: linear-gradient(135deg, rgba(249,115,22,0.20), rgba(251,146,60,0.16)); } .sgp-menu-label { font-size: 12px; font-weight: 800; color: #0f172a; line-height: 1.25; } .sgp-menu-label-static .sgp-menu-label { color: #334155; } .sgp-menu-hint { font-size: 10px; font-weight: 600; color: #94a3b8; line-height: 1.2; } .sgp-credit-page-card { position: relative; overflow: hidden; padding: 22px; min-height: 100%; background: radial-gradient(circle at top right, rgba(52,211,153,0.16), transparent 26%), radial-gradient(circle at bottom left, rgba(59,130,246,0.14), transparent 28%), linear-gradient(135deg, #ffffff, #f8fafc); flex-shrink: 0; } .sgp-credit-page-card::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient( 120deg, rgba(255,255,255,0.22), transparent 35%, transparent 65%, rgba(255,255,255,0.10) ); } .sgp-credit-page-inner { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 18px; } .sgp-credit-hero { text-align: center; padding: 8px 6px 2px; } .sgp-credit-kicker { display: inline-flex; align-items: center; gap: 8px; padding: 7px 12px; border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #0f172a; background: rgba(255,255,255,0.8); border: 1px solid rgba(226,232,240,0.92); box-shadow: 0 8px 18px rgba(15,23,42,0.05); } .sgp-credit-hero-title { margin-top: 14px; font-size: 24px; font-weight: 900; letter-spacing: -0.03em; color: #0f172a; } .sgp-credit-hero-subtitle { margin-top: 8px; font-size: 13px; line-height: 1.6; color: #475569; max-width: 300px; margin-left: auto; margin-right: auto; } .sgp-credit-showcase { display: grid; grid-template-columns: 1fr; gap: 14px; } .sgp-credit-profile { display: flex; align-items: center; gap: 14px; padding: 16px; border-radius: 22px; background: rgba(255,255,255,0.82); border: 1px solid rgba(226,232,240,0.95); box-shadow: 0 14px 30px rgba(15,23,42,0.07); backdrop-filter: blur(8px); } .sgp-credit-profile-avatar { width: 56px; height: 56px; border-radius: 18px; display: grid; place-items: center; font-size: 26px; flex-shrink: 0; background: linear-gradient(135deg, rgba(16,185,129,0.18), rgba(59,130,246,0.16)); box-shadow: inset 0 1px 0 rgba(255,255,255,0.75), 0 10px 18px rgba(15,23,42,0.08); } .sgp-credit-profile-content { min-width: 0; } .sgp-credit-profile-role { font-size: 11px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: #64748b; margin-bottom: 5px; } .sgp-credit-profile-name { font-size: 18px; font-weight: 900; color: #0f172a; line-height: 1.2; } .sgp-credit-profile-desc { margin-top: 6px; font-size: 12px; line-height: 1.5; color: #475569; } .sgp-info-card { padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-shrink: 0; } .sgp-info-left { min-width: 0; } .sgp-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px; background: rgba(15,23,42,0.92); color: white; font-size: 11px; font-weight: 700; margin-bottom: 8px; } .sgp-coords { font-size: 13px; font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; } .sgp-meta { font-size: 11px; color: #475569; margin-top: 4px; } .sgp-copy { padding: 10px 12px; border: none; border-radius: 14px; background: linear-gradient(135deg, #0f172a, #1e293b); color: white; font-size: 12px; font-weight: 700; cursor: pointer; transition: transform 0.15s ease, opacity 0.15s ease; white-space: nowrap; } .sgp-copy:hover { transform: translateY(-1px); opacity: 0.95; } .sgp-map-card { position: relative; min-height: 320px; overflow: hidden; display: flex; flex-direction: column; flex-shrink: 0; } .sgp-map-topbar { height: 42px; min-height: 42px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border-bottom: 1px solid rgba(15,23,42,0.06); background: #ffffff; font-size: 12px; font-weight: 700; color: #0f172a; } .sgp-map-status { color: #334155; font-size: 11px; font-weight: 600; } .sgp-iframe-wrap { position: relative; flex: 1; min-height: 260px; background: white; } .sgp-iframe { width: 100%; height: 100%; border: none; background: white; display: block; } .sgp-floating-status { position: absolute; left: 12px; bottom: 12px; z-index: 2; padding: 8px 10px; border-radius: 14px; background: rgba(15,23,42,0.78); color: white; font-size: 11px; font-weight: 700; box-shadow: 0 10px 20px rgba(0,0,0,0.2); max-width: calc(100% - 24px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; } .sgp-game-pin { position: absolute; width: 26px; height: 26px; transform: translate(-50%, -100%); z-index: 1000; pointer-events: none; filter: drop-shadow(0 4px 6px rgba(0,0,0,0.35)); display: grid; place-items: center; font-size: 26px; line-height: 1; } .sgp-resize { position: absolute; right: 0; bottom: 0; width: 24px; height: 24px; cursor: nwse-resize; background: linear-gradient(135deg, transparent 0%, transparent 46%, rgba(15,23,42,0.28) 47%, rgba(15,23,42,0.28) 54%, transparent 55%), linear-gradient(135deg, transparent 0%, transparent 63%, rgba(15,23,42,0.42) 64%, rgba(15,23,42,0.42) 71%, transparent 72%), linear-gradient(135deg, transparent 0%, transparent 80%, rgba(15,23,42,0.6) 81%, rgba(15,23,42,0.6) 88%, transparent 89%); } .sgp-dock { position: fixed; left: 22px; bottom: 88px; z-index: 100002; display: none; align-items: center; gap: 10px; padding: 10px 14px; border-radius: 16px; background: rgba(15,23,42,0.95); color: white; box-shadow: 0 14px 36px rgba(0,0,0,0.28); cursor: pointer; font-size: 13px; font-weight: 700; } .sgp-dock.show { display: flex; } `; document.head.appendChild(style); const fab = document.createElement("button"); fab.className = "sgp-fab"; fab.innerHTML = ` 🗺 Open Secret Project `; root.appendChild(fab); const dock = document.createElement("div"); dock.className = "sgp-dock"; dock.innerHTML = `🗺Secret Project minimized`; root.appendChild(dock); const overlay = document.createElement("div"); overlay.className = "sgp-overlay"; root.appendChild(overlay); const panel = document.createElement("div"); panel.className = "sgp-panel"; panel.style.width = `${savedWidth}px`; panel.style.height = `${savedHeight}px`; overlay.appendChild(panel); const header = document.createElement("div"); header.className = "sgp-header"; panel.appendChild(header); const headerLeft = document.createElement("div"); headerLeft.className = "sgp-header-left"; header.appendChild(headerLeft); const logo = document.createElement("div"); logo.className = "sgp-logo"; logo.textContent = "⌖"; headerLeft.appendChild(logo); const titleGroup = document.createElement("div"); titleGroup.className = "sgp-title-group"; headerLeft.appendChild(titleGroup); const title = document.createElement("div"); title.className = "sgp-title"; title.textContent = "Secret Project"; titleGroup.appendChild(title); const subtitle = document.createElement("div"); subtitle.className = "sgp-subtitle"; subtitle.textContent = "Location tools • live map • draggable panel"; titleGroup.appendChild(subtitle); const actions = document.createElement("div"); actions.className = "sgp-actions"; header.appendChild(actions); const minimizeBtn = document.createElement("button"); minimizeBtn.className = "sgp-btn"; minimizeBtn.textContent = "–"; actions.appendChild(minimizeBtn); const closeBtn = document.createElement("button"); closeBtn.className = "sgp-btn"; closeBtn.textContent = "✕"; actions.appendChild(closeBtn); const content = document.createElement("div"); content.className = "sgp-content"; panel.appendChild(content); const tabs = document.createElement("div"); tabs.className = "sgp-tabs"; tabs.innerHTML = ` `; content.appendChild(tabs); const pages = document.createElement("div"); pages.className = "sgp-pages"; content.appendChild(pages); const menuPage = document.createElement("div"); menuPage.className = "sgp-page active"; menuPage.style.overflowY = "auto"; menuPage.style.overflowX = "hidden"; pages.appendChild(menuPage); const creditsPage = document.createElement("div"); creditsPage.className = "sgp-page"; creditsPage.style.overflowY = "auto"; creditsPage.style.overflowX = "hidden"; pages.appendChild(creditsPage); const menuCard = document.createElement("div"); menuCard.className = "sgp-menu-card"; menuCard.innerHTML = `
Overview
📍
Live Coordinates
Auto-detected
🛰
Map Viewer
Preview panel
🧭
Quick Position
Current location
Actions
`; menuPage.appendChild(menuCard); const infoCard = document.createElement("div"); infoCard.className = "sgp-info-card"; infoCard.innerHTML = `
● Tracking page data
Waiting for coordinates...
Auto-refresh every 2 seconds
`; menuPage.appendChild(infoCard); const copyBtn = document.createElement("button"); copyBtn.className = "sgp-copy"; copyBtn.textContent = "Copy"; infoCard.appendChild(copyBtn); const mapCard = document.createElement("div"); mapCard.className = "sgp-map-card"; menuPage.appendChild(mapCard); const mapTopbar = document.createElement("div"); mapTopbar.className = "sgp-map-topbar"; mapTopbar.innerHTML = ` Google Maps preview No location detected yet `; mapCard.appendChild(mapTopbar); const iframeWrap = document.createElement("div"); iframeWrap.className = "sgp-iframe-wrap"; mapCard.appendChild(iframeWrap); const iframe = document.createElement("iframe"); iframe.className = "sgp-iframe"; iframe.loading = "lazy"; iframe.referrerPolicy = "no-referrer-when-downgrade"; iframe.allowFullscreen = true; iframe.src = "about:blank"; iframeWrap.appendChild(iframe); const floatingStatus = document.createElement("div"); floatingStatus.className = "sgp-floating-status"; floatingStatus.textContent = "Waiting for coordinates..."; iframeWrap.appendChild(floatingStatus); const creditPageCard = document.createElement("div"); creditPageCard.className = "sgp-credit-page-card"; creditPageCard.innerHTML = `
✨ Secret Project Credits
Built with vision
A dedicated page honoring the minds behind the experience.
👑
Founder
RandomAccount
Visionary behind the concept, direction, and identity of Secret Project.
🤖
Developer
ChatGPT
Designed and engineered the interface, logic, layout, and interactive experience.
`; creditsPage.appendChild(creditPageCard); const coordsEl = infoCard.querySelector(".sgp-coords"); const pinBtn = menuCard.querySelector(".sgp-pin-btn"); const removePinBtn = menuCard.querySelector(".sgp-remove-pin-btn"); const mapStatus = mapTopbar.querySelector(".sgp-map-status"); const tabButtons = tabs.querySelectorAll(".sgp-tab-btn"); function setActiveTab(tabName) { activeTab = tabName; tabButtons.forEach((btn) => { btn.classList.toggle("active", btn.dataset.tab === tabName); }); menuPage.classList.toggle("active", tabName === "menu"); creditsPage.classList.toggle("active", tabName === "credits"); } tabButtons.forEach((btn) => { btn.addEventListener("click", (event) => { event.stopPropagation(); setActiveTab(btn.dataset.tab); }); }); const resizeHandle = document.createElement("div"); resizeHandle.className = "sgp-resize"; panel.appendChild(resizeHandle); function openWindow() { overlay.classList.add("open"); fab.innerHTML = `Close Secret Project`; dock.classList.remove("show"); isOpen = true; } function closeWindow() { overlay.classList.remove("open"); fab.innerHTML = `🗺Open Secret Project`; dock.classList.remove("show"); isOpen = false; isMinimized = false; content.style.display = "flex"; panel.style.height = `${savedHeight}px`; panel.style.width = `${savedWidth}px`; minimizeBtn.textContent = "–"; resizeHandle.style.display = "block"; } function minimizeWindow() { if (!isMinimized) { savedHeight = panel.offsetHeight; savedWidth = panel.offsetWidth; content.style.display = "none"; resizeHandle.style.display = "none"; panel.style.height = "68px"; panel.style.width = "320px"; minimizeBtn.textContent = "▢"; dock.classList.add("show"); } else { content.style.display = "flex"; resizeHandle.style.display = "block"; panel.style.height = `${savedHeight}px`; panel.style.width = `${savedWidth}px`; minimizeBtn.textContent = "–"; dock.classList.remove("show"); } isMinimized = !isMinimized; } fab.addEventListener("click", () => { if (isOpen) { closeWindow(); } else { openWindow(); } }); closeBtn.addEventListener("click", (event) => { event.stopPropagation(); closeWindow(); }); minimizeBtn.addEventListener("click", (event) => { event.stopPropagation(); minimizeWindow(); }); dock.addEventListener("click", () => { if (isOpen && isMinimized) { minimizeWindow(); } }); overlay.addEventListener("mousedown", (event) => { if (event.target === overlay) { closeWindow(); } }); header.addEventListener("mousedown", (event) => { if (event.target === minimizeBtn || event.target === closeBtn) { return; } isDragging = true; const rect = panel.getBoundingClientRect(); offsetX = event.clientX - rect.left; offsetY = event.clientY - rect.top; }); resizeHandle.addEventListener("mousedown", (event) => { event.stopPropagation(); isResizing = true; }); document.addEventListener("mousemove", (event) => { if (isDragging) { const maxLeft = window.innerWidth - panel.offsetWidth - 10; const maxTop = window.innerHeight - panel.offsetHeight - 10; const newLeft = Math.min(Math.max(10, event.clientX - offsetX), Math.max(10, maxLeft)); const newTop = Math.min(Math.max(10, event.clientY - offsetY), Math.max(10, maxTop)); panel.style.left = `${newLeft}px`; panel.style.top = `${newTop}px`; } if (isResizing && !isMinimized) { const rect = panel.getBoundingClientRect(); const newWidth = Math.max(event.clientX - rect.left, 340); const newHeight = Math.max(event.clientY - rect.top, 360); panel.style.width = `${newWidth}px`; panel.style.height = `${newHeight}px`; savedWidth = newWidth; savedHeight = newHeight; } }); document.addEventListener("mouseup", () => { isDragging = false; isResizing = false; }); copyBtn.addEventListener("click", async () => { if (!lastCoords) { return; } try { await navigator.clipboard.writeText(lastCoords); copyBtn.textContent = "Copied"; setTimeout(() => { copyBtn.textContent = "Copy"; }, 1200); } catch (error) { copyBtn.textContent = "Failed"; setTimeout(() => { copyBtn.textContent = "Copy"; }, 1200); } }); pinBtn.addEventListener("click", (event) => { event.stopPropagation(); const latLng = getCurrentLatLng(); if (!latLng) { floatingStatus.textContent = "No coordinates available yet"; mapStatus.textContent = "Pin failed"; return; } pinEnabled = true; pinnedLatLng = latLng; refreshGamePin(); }); removePinBtn.addEventListener("click", (event) => { event.stopPropagation(); pinEnabled = false; pinnedLatLng = null; document.querySelectorAll(".sgp-game-pin").forEach((pin) => pin.remove()); mapStatus.textContent = "Pin removed"; floatingStatus.textContent = lastCoords ? `Lat ${lastCoords.split(",")[0]} • Lng ${lastCoords.split(",")[1]}` : "Waiting for coordinates..."; }); function extractCoords() { const html = document.documentElement.innerHTML; const patterns = [ /(-?\d{1,3}\.\d+)\s*,\s*(-?\d{1,3}\.\d+)/, /"lat"\s*:\s*(-?\d{1,3}\.\d+).*?"lng"\s*:\s*(-?\d{1,3}\.\d+)/i, /"latitude"\s*:\s*(-?\d{1,3}\.\d+).*?"longitude"\s*:\s*(-?\d{1,3}\.\d+)/i ]; for (const pattern of patterns) { const match = html.match(pattern); if (match) { const lat = parseFloat(match[1]); const lng = parseFloat(match[2]); if ( !Number.isNaN(lat) && lat >= -90 && lat <= 90 && !Number.isNaN(lng) && lng >= -180 && lng <= 180 ) { return `${lat},${lng}`; } } } return null; } function getCurrentLatLng() { if (!lastCoords) { return null; } const [latStr, lngStr] = lastCoords.split(","); const lat = parseFloat(latStr); const lng = parseFloat(lngStr); if (Number.isNaN(lat) || Number.isNaN(lng)) { return null; } return { lat, lng }; } function getOrCreateGamePin(container) { let pin = container.querySelector(".sgp-game-pin"); if (!pin) { pin = document.createElement("div"); pin.className = "sgp-game-pin"; pin.textContent = "📍"; container.appendChild(pin); } return pin; } function parseTranslateAndScale(transformValue) { if (!transformValue || transformValue === "none") { return { x: 0, y: 0, scale: 1 }; } const match3d = transformValue.match( /translate3d\(([-\d.]+)px,\s*([-\d.]+)px,\s*[-\d.]+px\)(?:\s*scale\(([-\d.]+)\))?/ ); if (match3d) { return { x: parseFloat(match3d[1]) || 0, y: parseFloat(match3d[2]) || 0, scale: parseFloat(match3d[3]) || 1 }; } const match2d = transformValue.match( /translate\(([-\d.]+)px,\s*([-\d.]+)px\)(?:\s*scale\(([-\d.]+)\))?/ ); if (match2d) { return { x: parseFloat(match2d[1]) || 0, y: parseFloat(match2d[2]) || 0, scale: parseFloat(match2d[3]) || 1 }; } return { x: 0, y: 0, scale: 1 }; } function latLngToWorldPixel(lat, lng, zoom) { const tileSize = 256; const scale = tileSize * Math.pow(2, zoom); const x = ((lng + 180) / 360) * scale; const sinLat = Math.sin((lat * Math.PI) / 180); const clamped = Math.min(Math.max(sinLat, -0.9999), 0.9999); const y = (0.5 - Math.log((1 + clamped) / (1 - clamped)) / (4 * Math.PI)) * scale; return { x, y }; } function isLeafletMapCandidate(obj, container) { return Boolean( obj && typeof obj === "object" && typeof obj.latLngToContainerPoint === "function" && typeof obj.containerPointToLatLng === "function" && typeof obj.getZoom === "function" && obj._container === container ); } function findLeafletMapNearContainer(container) { if (!container) { return null; } const seen = new WeakSet(); const roots = [container, container.parentElement, container.closest("#miniMapArea"), window]; function scan(obj, depth) { if (!obj || typeof obj !== "object" || seen.has(obj) || depth < 0) { return null; } seen.add(obj); if (isLeafletMapCandidate(obj, container)) { return obj; } let keys = []; try { keys = Object.getOwnPropertyNames(obj); } catch (error) { return null; } for (const key of keys) { if (key === "parentNode" || key === "children" || key === "childNodes") { continue; } let value; try { value = obj[key]; } catch (error) { continue; } if (!value || typeof value !== "object") { continue; } if (isLeafletMapCandidate(value, container)) { return value; } const found = scan(value, depth - 1); if (found) { return found; } } return null; } for (const rootNode of roots) { const found = scan(rootNode, rootNode === window ? 2 : 4); if (found) { return found; } } return null; } function getDomProjectionContext() { const mapArea = document.querySelector("#miniMapArea"); const container = mapArea?.querySelector(".leaflet-container"); const mapPane = mapArea?.querySelector(".leaflet-map-pane"); const tileContainer = mapArea?.querySelector(".leaflet-tile-container"); if (!mapArea || !container || !mapPane || !tileContainer) { return null; } const tiles = Array.from(mapArea.querySelectorAll(".leaflet-tile-pane .leaflet-tile-loaded")); if (!tiles.length) { return null; } let bestTile = null; let bestScore = Infinity; for (const tile of tiles) { const src = tile.getAttribute("src") || ""; const xMatch = src.match(/[?&]x=(-?\d+)/); const yMatch = src.match(/[?&]y=(-?\d+)/); const zMatch = src.match(/[?&]z=(\d+)/); if (!xMatch || !yMatch || !zMatch) { continue; } const tileTransform = parseTranslateAndScale(tile.style.transform); const containerTransform = parseTranslateAndScale(tileContainer.style.transform); const score = Math.abs((containerTransform.scale || 1) - 1); if (score < bestScore) { bestScore = score; bestTile = { tileX: parseInt(xMatch[1], 10), tileY: parseInt(yMatch[1], 10), zoom: parseInt(zMatch[1], 10), tileTransform, containerTransform }; } } if (!bestTile) { return null; } const mapPaneTransform = parseTranslateAndScale(mapPane.style.transform); return { container, zoom: bestTile.zoom, tileX: bestTile.tileX, tileY: bestTile.tileY, tileXOffset: bestTile.tileTransform.x, tileYOffset: bestTile.tileTransform.y, tileContainerX: bestTile.containerTransform.x, tileContainerY: bestTile.containerTransform.y, tileScale: bestTile.containerTransform.scale || 1, mapPaneX: mapPaneTransform.x, mapPaneY: mapPaneTransform.y }; } function projectWithDomFallback(latLng) { const ctx = getDomProjectionContext(); if (!ctx) { return null; } const world = latLngToWorldPixel(latLng.lat, latLng.lng, ctx.zoom); const tileWorldX = ctx.tileX * 256; const tileWorldY = ctx.tileY * 256; const pixelX = ctx.mapPaneX + ctx.tileContainerX + ctx.tileXOffset + (world.x - tileWorldX) * ctx.tileScale; const pixelY = ctx.mapPaneY + ctx.tileContainerY + ctx.tileYOffset + (world.y - tileWorldY) * ctx.tileScale; return { container: ctx.container, x: pixelX, y: pixelY }; } function refreshGamePin() { if (!pinEnabled || !pinnedLatLng) { return; } const container = document.querySelector("#miniMapArea .leaflet-container"); if (!container) { mapStatus.textContent = "Pin failed"; floatingStatus.textContent = "Game map not found"; return; } const leafletMap = findLeafletMapNearContainer(container); if (leafletMap) { try { const point = leafletMap.latLngToContainerPoint([pinnedLatLng.lat, pinnedLatLng.lng]); const pin = getOrCreateGamePin(container); pin.style.left = `${point.x}px`; pin.style.top = `${point.y}px`; const inView = point.x >= 0 && point.y >= 0 && point.x <= container.clientWidth && point.y <= container.clientHeight; mapStatus.textContent = inView ? "Pin fixed" : "Pin off-screen"; floatingStatus.textContent = inView ? `Pin fixed on map • ${pinnedLatLng.lat}, ${pinnedLatLng.lng}` : `Pin is off-screen • ${pinnedLatLng.lat}, ${pinnedLatLng.lng}`; return; } catch (error) { // Fall back to DOM projection below. } } const projected = projectWithDomFallback(pinnedLatLng); if (!projected) { mapStatus.textContent = "Pin failed"; floatingStatus.textContent = "Could not project pin"; return; } const pin = getOrCreateGamePin(projected.container); pin.style.left = `${projected.x}px`; pin.style.top = `${projected.y}px`; const inView = projected.x >= 0 && projected.y >= 0 && projected.x <= projected.container.clientWidth && projected.y <= projected.container.clientHeight; mapStatus.textContent = inView ? "Pin tracking" : "Pin off-screen"; floatingStatus.textContent = inView ? `Pin tracking map • ${pinnedLatLng.lat}, ${pinnedLatLng.lng}` : `Pin is off-screen • ${pinnedLatLng.lat}, ${pinnedLatLng.lng}`; } function updatePreviewIframe(lat, lng) { const url = `https://www.google.com/maps?q=${encodeURIComponent(`${lat},${lng}`)}&z=6&output=embed`; if (iframe.src !== url) { iframe.src = url; } } function updateMap() { const coords = extractCoords(); if (!coords) { refreshGamePin(); return; } if (coords !== lastCoords) { const [lat, lng] = coords.split(","); lastCoords = coords; coordsEl.textContent = `${lat}, ${lng}`; floatingStatus.textContent = `Lat ${lat} • Lng ${lng}`; mapStatus.textContent = "Location detected"; updatePreviewIframe(lat, lng); if (pinEnabled) { pinnedLatLng = { lat: parseFloat(lat), lng: parseFloat(lng) }; } } refreshGamePin(); } setInterval(updateMap, 50); })();