{"id":81,"date":"2026-03-18T07:19:32","date_gmt":"2026-03-18T07:19:32","guid":{"rendered":"https:\/\/tools.sanepo.com\/?post_type=tool&#038;p=81"},"modified":"2026-03-18T07:19:33","modified_gmt":"2026-03-18T07:19:33","slug":"ip-lookup-geolocation-tool","status":"publish","type":"tool","link":"https:\/\/tools.sanepo.com\/id\/features\/ip-lookup-geolocation-tool\/","title":{"rendered":"Free IP Lookup &amp; Geolocation Tool"},"content":{"rendered":"\n<!-- \n  Sanepo Tools: Advanced IP Lookup & Geolocation (Clean SaaS Edition)\n  Engineered by: WP-NanoTech\n  Dependencies: Vanilla JS, Dynamically loads Leaflet.js for maps\n  Zero Server Load - 100% Client Side\n-->\n<div id=\"wpnt-ip-lookup\" class=\"wpnt-wrapper\">\n    <!-- Header Section -->\n    <div class=\"wpnt-header\">\n        <h2>Sanepo Tools \u2013 IP Lookup &#038; Geolocation<\/h2>\n        <p>Instantly analyze IP addresses, discover origins, and compare network nodes in milliseconds.<\/p>\n        <div class=\"wpnt-badge-secure\">\n            <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"><\/rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"><\/path><\/svg>\n            100% Client-Side (No Data Uploaded)\n        <\/div>\n    <\/div>\n\n    <!-- Main Content Grid -->\n    <div class=\"wpnt-grid-layout\">\n        \n        <!-- Left Column: Inputs & Results -->\n        <div class=\"wpnt-main-col\">\n            <!-- Inputs -->\n            <div class=\"wpnt-input-card\">\n                <div class=\"wpnt-input-header\">\n                    <span class=\"wpnt-input-title\">Primary Target (IP or Domain)<\/span>\n                <\/div>\n                <input type=\"text\" id=\"ip-input-1\" placeholder=\"e.g. 8.8.8.8 or example.com\" class=\"wpnt-input\" \/>\n            <\/div>\n\n            <div class=\"wpnt-input-card\" id=\"ip-input-2-wrapper\" style=\"display: none;\">\n                <div class=\"wpnt-input-header\">\n                    <span class=\"wpnt-input-title\">Secondary Target (For Comparison)<\/span>\n                <\/div>\n                <input type=\"text\" id=\"ip-input-2\" placeholder=\"e.g. 1.1.1.1 or google.com\" class=\"wpnt-input\" \/>\n            <\/div>\n\n            <!-- Loader & Error -->\n            <div id=\"wpnt-loader\" class=\"wpnt-loader-container\" style=\"display: none;\">\n                <div class=\"wpnt-spinner\"><\/div>\n                <p>Analyzing network topology&#8230;<\/p>\n            <\/div>\n            <div id=\"wpnt-error\" class=\"wpnt-error-box\" style=\"display: none;\"><\/div>\n\n            <!-- Results -->\n            <div id=\"wpnt-results\" style=\"display: none;\">\n                <div class=\"wpnt-result-card\">\n                    <div class=\"wpnt-input-header\">\n                        <span class=\"wpnt-input-title\">Analysis Result<\/span>\n                    <\/div>\n                    <div class=\"wpnt-table-wrapper\">\n                        <table id=\"wpnt-result-table\">\n                            <!-- Populated by JS -->\n                        <\/table>\n                    <\/div>\n                <\/div>\n\n                <!-- Maps Area -->\n                <div class=\"wpnt-maps-grid\" id=\"wpnt-maps-grid\">\n                    <div class=\"wpnt-map-card\" id=\"wpnt-map-1-card\">\n                        <div class=\"wpnt-map-title\">Map: Target 1<\/div>\n                        <div id=\"wpnt-map-1\" class=\"wpnt-map-box\"><\/div>\n                    <\/div>\n                    <div class=\"wpnt-map-card\" id=\"wpnt-map-2-card\" style=\"display: none;\">\n                        <div class=\"wpnt-map-title\">Map: Target 2<\/div>\n                        <div id=\"wpnt-map-2\" class=\"wpnt-map-box\"><\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- Right Column: Settings & Actions -->\n        <div class=\"wpnt-sidebar-col\">\n            <div class=\"wpnt-settings-card\">\n                <div class=\"wpnt-settings-header\">\n                    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"><\/circle><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"><\/path><\/svg>\n                    <h3>Scan Settings<\/h3>\n                <\/div>\n                \n                <div class=\"wpnt-setting-item\">\n                    <label class=\"wpnt-checkbox-label\">\n                        <input type=\"checkbox\" id=\"wpnt-compare-toggle\" class=\"wpnt-checkbox\">\n                        <span class=\"wpnt-checkbox-text\">Compare Mode<\/span>\n                    <\/label>\n                    <p class=\"wpnt-setting-desc\">Enables side-by-side comparison of two distinct IP addresses or domains.<\/p>\n                <\/div>\n\n                <div class=\"wpnt-actions-wrapper\">\n                    <button id=\"btn-analyze\" class=\"wpnt-btn wpnt-btn-primary\">\n                        Analyze Network\n                    <\/button>\n                    <button id=\"btn-copy\" class=\"wpnt-btn wpnt-btn-outline\" style=\"display: none;\">\n                        <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"><\/rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"><\/path><\/svg>\n                        Copy Result\n                    <\/button>\n                    <button id=\"btn-download\" class=\"wpnt-btn wpnt-btn-outline\" style=\"display: none;\">\n                        <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"><\/path><polyline points=\"7 10 12 15 17 10\"><\/polyline><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"><\/line><\/svg>\n                        Export JSON\n                    <\/button>\n                    <button id=\"btn-clear\" class=\"wpnt-btn wpnt-btn-ghost\">\n                        Clear All\n                    <\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Bottom Stats Area -->\n    <div class=\"wpnt-stats-row\" id=\"wpnt-stats-row\">\n        <div class=\"wpnt-stat-box\">\n            <div class=\"wpnt-stat-val\" id=\"stat-val-1\">&#8211;<\/div>\n            <div class=\"wpnt-stat-label\" id=\"stat-label-1\">TARGET 1 ASN<\/div>\n        <\/div>\n        <div class=\"wpnt-stat-box wpnt-stat-center\">\n            <div class=\"wpnt-stat-val\" id=\"stat-val-2\">&#8211;<\/div>\n            <div class=\"wpnt-stat-label\" id=\"stat-label-2\">NETWORK DISTANCE<\/div>\n        <\/div>\n        <div class=\"wpnt-stat-box\">\n            <div class=\"wpnt-stat-val wpnt-val-color\" id=\"stat-val-3\">READY<\/div>\n            <div class=\"wpnt-stat-label\" id=\"stat-label-3\">SYSTEM STATUS<\/div>\n        <\/div>\n    <\/div>\n\n    <style>\n        @import url('https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&display=swap');\n\n        \/* * STRICT VARIABLE ENFORCEMENT \n         * Using !important to lock colors and prevent WP Theme overrides \n         *\/\n        #wpnt-ip-lookup {\n            \/* Default: LIGHT MODE *\/\n            --wpnt-bg-main: #fcfcfc !important;\n            --wpnt-bg-card: #ffffff !important;\n            --wpnt-bg-sidebar: #ffffff !important;\n            --wpnt-bg-input: #ffffff !important;\n            --wpnt-text-main: #1e293b !important;\n            --wpnt-text-muted: #64748b !important;\n            --wpnt-text-light: #94a3b8 !important;\n            --wpnt-border: #e2e8f0 !important;\n            --wpnt-primary: #4361ee !important;\n            --wpnt-primary-hover: #3a56d4 !important;\n            --wpnt-primary-text: #ffffff !important;\n            --wpnt-success: #10b981 !important;\n            --wpnt-success-bg: rgba(16, 185, 129, 0.1) !important;\n            --wpnt-warning: #f59e0b !important;\n            --wpnt-warning-bg: rgba(245, 158, 11, 0.15) !important;\n            --wpnt-shadow-sm: 0 1px 3px rgba(0,0,0,0.05) !important;\n            --wpnt-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03) !important;\n            --wpnt-radius: 8px !important;\n            --wpnt-map-filter: none !important;\n        }\n\n        \/* * STRICT DARK MODE HOOKS\n         * Removed invalid :global selectors that broke the CSS.\n         * Now uses robust standard CSS selectors matching common WP dark mode patterns.\n         *\/\n        .dark #wpnt-ip-lookup,\n        .dark-mode #wpnt-ip-lookup,\n        .night-mode #wpnt-ip-lookup,\n        [data-theme=\"dark\"] #wpnt-ip-lookup,\n        [data-bs-theme=\"dark\"] #wpnt-ip-lookup {\n            --wpnt-bg-main: #0f172a !important;\n            --wpnt-bg-card: #1e293b !important;\n            --wpnt-bg-sidebar: #1e293b !important;\n            --wpnt-bg-input: #0f172a !important;\n            --wpnt-text-main: #f1f5f9 !important;\n            --wpnt-text-muted: #94a3b8 !important;\n            --wpnt-text-light: #64748b !important;\n            --wpnt-border: #334155 !important;\n            --wpnt-primary: #4361ee !important;\n            --wpnt-shadow-sm: 0 1px 3px rgba(0,0,0,0.2) !important;\n            --wpnt-shadow-md: 0 4px 6px -1px rgba(0,0,0,0.3) !important;\n            --wpnt-map-filter: invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%) !important;\n        }\n\n        \/* Base & Text Locking *\/\n        #wpnt-ip-lookup {\n            font-family: 'Inter', sans-serif !important;\n            background-color: var(--wpnt-bg-main) !important;\n            color: var(--wpnt-text-main) !important;\n            padding: 40px 20px !important;\n            border-radius: var(--wpnt-radius) !important;\n            box-sizing: border-box !important;\n            line-height: 1.5 !important;\n            width: 100% !important;\n        }\n        #wpnt-ip-lookup * { box-sizing: border-box !important; transition: all 0.2s ease; }\n        \n        \/* Force Text Colors *\/\n        #wpnt-ip-lookup h2, #wpnt-ip-lookup h3, #wpnt-ip-lookup span.wpnt-input-title, #wpnt-ip-lookup th {\n            color: var(--wpnt-text-main) !important;\n        }\n        #wpnt-ip-lookup p, #wpnt-ip-lookup .wpnt-setting-desc {\n            color: var(--wpnt-text-muted) !important;\n        }\n\n        \/* Header *\/\n        #wpnt-ip-lookup .wpnt-header { text-align: center; margin-bottom: 40px; }\n        #wpnt-ip-lookup h2 { font-size: 1.8rem !important; font-weight: 700 !important; margin: 0 0 12px 0 !important; }\n        #wpnt-ip-lookup .wpnt-header p { font-size: 1rem !important; max-width: 600px; margin: 0 auto 16px auto !important; }\n        #wpnt-ip-lookup .wpnt-badge-secure {\n            display: inline-flex; align-items: center; justify-content: center; gap: 6px;\n            background-color: var(--wpnt-success-bg) !important;\n            color: var(--wpnt-success) !important;\n            padding: 6px 16px !important; border-radius: 20px !important;\n            font-size: 0.85rem !important; font-weight: 600 !important;\n        }\n\n        \/* Grid Layout *\/\n        #wpnt-ip-lookup .wpnt-grid-layout {\n            display: flex; gap: 24px; margin-bottom: 24px; align-items: flex-start;\n        }\n        #wpnt-ip-lookup .wpnt-main-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 16px; }\n        #wpnt-ip-lookup .wpnt-sidebar-col { width: 280px; flex-shrink: 0; }\n\n        \/* Cards *\/\n        #wpnt-ip-lookup .wpnt-input-card, #wpnt-ip-lookup .wpnt-result-card, #wpnt-ip-lookup .wpnt-settings-card {\n            background-color: var(--wpnt-bg-card) !important;\n            border: 1px solid var(--wpnt-border) !important;\n            border-radius: var(--wpnt-radius) !important;\n            box-shadow: var(--wpnt-shadow-sm) !important;\n            overflow: hidden;\n        }\n        #wpnt-ip-lookup .wpnt-input-card { padding: 0 !important; }\n        #wpnt-ip-lookup .wpnt-input-header {\n            background-color: var(--wpnt-bg-main) !important;\n            padding: 10px 16px !important;\n            border-bottom: 1px solid var(--wpnt-border) !important;\n            display: flex; justify-content: space-between; align-items: center;\n        }\n        #wpnt-ip-lookup .wpnt-input-title { font-size: 0.85rem !important; font-weight: 600 !important; }\n        \n        \/* Input Fields *\/\n        #wpnt-ip-lookup .wpnt-input {\n            width: 100% !important; border: none !important;\n            background-color: var(--wpnt-bg-input) !important;\n            color: var(--wpnt-text-main) !important;\n            padding: 16px !important; font-size: 0.95rem !important;\n            font-family: 'Inter', monospace !important; outline: none !important;\n        }\n        #wpnt-ip-lookup .wpnt-input:focus { background-color: var(--wpnt-bg-card) !important; }\n\n        \/* Sidebar Settings *\/\n        #wpnt-ip-lookup .wpnt-settings-card { padding: 20px !important; background-color: var(--wpnt-bg-sidebar) !important; box-shadow: var(--wpnt-shadow-md) !important;}\n        #wpnt-ip-lookup .wpnt-settings-header { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; color: var(--wpnt-text-main) !important;}\n        #wpnt-ip-lookup .wpnt-settings-header h3 { margin: 0 !important; font-size: 1.1rem !important; font-weight: 600 !important; }\n        \n        #wpnt-ip-lookup .wpnt-setting-item { margin-bottom: 24px; padding-bottom: 20px; border-bottom: 1px solid var(--wpnt-border) !important;}\n        #wpnt-ip-lookup .wpnt-checkbox-label { display: flex; align-items: center; gap: 10px; cursor: pointer; margin-bottom: 6px;}\n        #wpnt-ip-lookup .wpnt-checkbox { width: 18px !important; height: 18px !important; accent-color: var(--wpnt-primary) !important; cursor: pointer; }\n        #wpnt-ip-lookup .wpnt-checkbox-text { font-weight: 500 !important; font-size: 0.95rem !important; color: var(--wpnt-text-main) !important;}\n        #wpnt-ip-lookup .wpnt-setting-desc { font-size: 0.8rem !important; margin: 0 0 0 28px !important; line-height: 1.4 !important; }\n\n        \/* Buttons *\/\n        #wpnt-ip-lookup .wpnt-actions-wrapper { display: flex; flex-direction: column; gap: 10px; }\n        #wpnt-ip-lookup .wpnt-btn {\n            width: 100% !important; padding: 12px 16px !important; border-radius: var(--wpnt-radius) !important;\n            font-family: 'Inter', sans-serif !important; font-size: 0.95rem !important; font-weight: 600 !important;\n            cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px;\n            border: none !important; transition: background 0.2s, transform 0.1s !important;\n        }\n        #wpnt-ip-lookup .wpnt-btn-primary { background-color: var(--wpnt-primary) !important; color: var(--wpnt-primary-text) !important; box-shadow: 0 2px 4px rgba(67, 97, 238, 0.2) !important;}\n        #wpnt-ip-lookup .wpnt-btn-primary:hover:not(:disabled) { background-color: var(--wpnt-primary-hover) !important; transform: translateY(-1px); }\n        #wpnt-ip-lookup .wpnt-btn-outline { background-color: transparent !important; border: 1px solid var(--wpnt-primary) !important; color: var(--wpnt-primary) !important; }\n        #wpnt-ip-lookup .wpnt-btn-outline:hover { background-color: rgba(67, 97, 238, 0.05) !important; }\n        #wpnt-ip-lookup .wpnt-btn-ghost { background-color: transparent !important; color: var(--wpnt-text-muted) !important; font-size: 0.85rem !important; font-weight: 500 !important; padding: 8px !important;}\n        #wpnt-ip-lookup .wpnt-btn-ghost:hover { color: var(--wpnt-text-main) !important; }\n        #wpnt-ip-lookup .wpnt-btn:disabled { opacity: 0.6 !important; cursor: not-allowed !important; }\n\n        \/* Results Table *\/\n        #wpnt-ip-lookup .wpnt-table-wrapper { overflow-x: auto; }\n        #wpnt-ip-lookup table { width: 100% !important; border-collapse: collapse !important; text-align: left; }\n        #wpnt-ip-lookup th, #wpnt-ip-lookup td { padding: 12px 16px !important; border-bottom: 1px solid var(--wpnt-border) !important; font-size: 0.9rem !important; color: var(--wpnt-text-main) !important;}\n        #wpnt-ip-lookup th { font-weight: 600 !important; background-color: rgba(0,0,0,0.01) !important;}\n        #wpnt-ip-lookup tr:last-child td { border-bottom: none !important; }\n        #wpnt-ip-lookup td.wpnt-label-col { font-weight: 600 !important; color: var(--wpnt-text-muted) !important; width: 25%; }\n        \n        .wpnt-val-cell { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n        .wpnt-val-content { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }\n        #wpnt-ip-lookup .wpnt-diff-badge { background-color: var(--wpnt-warning-bg) !important; color: var(--wpnt-warning) !important; padding: 2px 8px !important; border-radius: 12px !important; font-size: 0.7rem !important; font-weight: 700 !important; text-transform: uppercase;}\n\n        \/* Loader & Error *\/\n        #wpnt-ip-lookup .wpnt-loader-container { text-align: center; padding: 40px 0; }\n        #wpnt-ip-lookup .wpnt-spinner { width: 32px; height: 32px; border: 3px solid var(--wpnt-border); border-top-color: var(--wpnt-primary); border-radius: 50%; animation: wpnt-spin 1s linear infinite; margin: 0 auto 12px auto; }\n        @keyframes wpnt-spin { to { transform: rotate(360deg); } }\n        #wpnt-ip-lookup .wpnt-error-box { background-color: rgba(239, 68, 68, 0.1) !important; color: #ef4444 !important; border: 1px solid rgba(239, 68, 68, 0.2) !important; padding: 16px !important; border-radius: var(--wpnt-radius) !important; margin-bottom: 16px; font-size: 0.9rem !important; text-align: center; }\n\n        \/* Stats Bottom Row *\/\n        #wpnt-ip-lookup .wpnt-stats-row {\n            display: flex; background-color: var(--wpnt-bg-card) !important; border: 1px solid var(--wpnt-border) !important;\n            border-radius: var(--wpnt-radius) !important; box-shadow: var(--wpnt-shadow-sm) !important; margin-top: 10px;\n        }\n        #wpnt-ip-lookup .wpnt-stat-box { flex: 1; padding: 24px 16px !important; text-align: center; }\n        #wpnt-ip-lookup .wpnt-stat-center { border-left: 1px solid var(--wpnt-border) !important; border-right: 1px solid var(--wpnt-border) !important; }\n        #wpnt-ip-lookup .wpnt-stat-val { font-size: 1.5rem !important; font-weight: 700 !important; color: var(--wpnt-primary) !important; margin-bottom: 4px !important; }\n        #wpnt-ip-lookup .wpnt-stat-val.wpnt-val-color { color: var(--wpnt-warning) !important; }\n        #wpnt-ip-lookup .wpnt-stat-label { font-size: 0.75rem !important; font-weight: 600 !important; color: var(--wpnt-text-light) !important; text-transform: uppercase; letter-spacing: 0.5px;}\n\n        \/* Maps *\/\n        #wpnt-ip-lookup .wpnt-maps-grid { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 16px;}\n        #wpnt-ip-lookup .wpnt-map-card { flex: 1; min-width: 250px; background-color: var(--wpnt-bg-card) !important; border: 1px solid var(--wpnt-border) !important; border-radius: var(--wpnt-radius) !important; overflow: hidden; }\n        #wpnt-ip-lookup .wpnt-map-title { padding: 8px 12px !important; font-weight: 600 !important; font-size: 0.8rem !important; border-bottom: 1px solid var(--wpnt-border) !important; color: var(--wpnt-text-muted) !important; background-color: var(--wpnt-bg-main) !important;}\n        #wpnt-ip-lookup .wpnt-map-box { height: 200px; width: 100%; filter: var(--wpnt-map-filter) !important;}\n\n        \/* Responsive *\/\n        @media (max-width: 800px) {\n            #wpnt-ip-lookup .wpnt-grid-layout { flex-direction: column; }\n            #wpnt-ip-lookup .wpnt-sidebar-col { width: 100%; }\n            #wpnt-ip-lookup .wpnt-stats-row { flex-direction: column; }\n            #wpnt-ip-lookup .wpnt-stat-center { border-left: none !important; border-right: none !important; border-top: 1px solid var(--wpnt-border) !important; border-bottom: 1px solid var(--wpnt-border) !important; }\n        }\n    <\/style>\n\n    <script>\n        (() => {\n            const el = {\n                toggle: document.getElementById('wpnt-compare-toggle'),\n                input1: document.getElementById('ip-input-1'),\n                input2Wrapper: document.getElementById('ip-input-2-wrapper'),\n                input2: document.getElementById('ip-input-2'),\n                btnAnalyze: document.getElementById('btn-analyze'),\n                btnCopy: document.getElementById('btn-copy'),\n                btnDownload: document.getElementById('btn-download'),\n                btnClear: document.getElementById('btn-clear'),\n                loader: document.getElementById('wpnt-loader'),\n                error: document.getElementById('wpnt-error'),\n                results: document.getElementById('wpnt-results'),\n                table: document.getElementById('wpnt-result-table'),\n                map1Card: document.getElementById('wpnt-map-1-card'),\n                map2Card: document.getElementById('wpnt-map-2-card'),\n                statVal1: document.getElementById('stat-val-1'),\n                statVal2: document.getElementById('stat-val-2'),\n                statVal3: document.getElementById('stat-val-3'),\n                statLabel1: document.getElementById('stat-label-1'),\n                statLabel2: document.getElementById('stat-label-2')\n            };\n\n            let currentJsonData = null;\n            let isCompareMode = false;\n            let leafletLoaded = false;\n\n            \/\/ Dynamically load Leaflet.js\n            function loadLeaflet() {\n                return new Promise((resolve) => {\n                    if (window.L) { leafletLoaded = true; return resolve(); }\n                    const link = document.createElement('link');\n                    link.rel = 'stylesheet';\n                    link.href = 'https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css';\n                    document.head.appendChild(link);\n\n                    const script = document.createElement('script');\n                    script.src = 'https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js';\n                    script.onload = () => { leafletLoaded = true; resolve(); };\n                    document.head.appendChild(script);\n                });\n            }\n\n            \/\/ UI Listeners\n            el.toggle.addEventListener('change', (e) => {\n                isCompareMode = e.target.checked;\n                el.input2Wrapper.style.display = isCompareMode ? 'block' : 'none';\n                if (!isCompareMode) el.input2.value = '';\n            });\n\n            \/\/ Core Logic\n            const isIP = (str) => \/^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$\/.test(str) || \/^[a-fA-F0-9:]+$\/.test(str);\n\n            async function resolveDomainToIP(domain) {\n                if (!domain) return '';\n                if (isIP(domain)) return domain; \n                try {\n                    const res = await fetch(`https:\/\/dns.google\/resolve?name=${domain}&type=A`);\n                    const data = await res.json();\n                    if (data.Answer && data.Answer.length > 0) return data.Answer[0].data;\n                    throw new Error(\"DNS resolution failed\");\n                } catch (err) {\n                    throw new Error(`Cannot resolve domain: ${domain}`);\n                }\n            }\n\n            async function fetchIPData(ipQuery) {\n                let fetchUrl = 'https:\/\/get.geojs.io\/v1\/ip\/geo.json'; \n                if (ipQuery) {\n                    const ip = await resolveDomainToIP(ipQuery);\n                    fetchUrl = `https:\/\/get.geojs.io\/v1\/ip\/geo\/${ip}.json`;\n                }\n                const res = await fetch(fetchUrl);\n                if (!res.ok) throw new Error(\"Network\/API error. Node unresponsive.\");\n                const data = await res.json();\n                \n                return {\n                    _query: ipQuery || data.ip,\n                    IP_Address: data.ip,\n                    Type: data.ip.includes(':') ? 'IPv6' : 'IPv4',\n                    Country: data.country || \"Unknown\",\n                    City: data.city || \"Unknown\",\n                    ISP: data.organization_name || (data.organization ? data.organization.split(' ').slice(1).join(' ') : \"Unknown\"),\n                    ASN: data.asn ? `AS${data.asn}` : \"Unknown\",\n                    Timezone: data.timezone || \"Unknown\",\n                    Latitude: data.latitude || \"N\/A\",\n                    Longitude: data.longitude || \"N\/A\"\n                };\n            }\n\n            function calculateDistance(lat1, lon1, lat2, lon2) {\n                const R = 6371; \n                const dLat = (lat2 - lat1) * Math.PI \/ 180;\n                const dLon = (lon2 - lon1) * Math.PI \/ 180;\n                const a = Math.sin(dLat\/2) * Math.sin(dLat\/2) + Math.cos(lat1 * Math.PI \/ 180) * Math.cos(lat2 * Math.PI \/ 180) * Math.sin(dLon\/2) * Math.sin(dLon\/2);\n                const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));\n                return (R * c).toFixed(2);\n            }\n\n            const fieldsToDisplay = [\"IP_Address\", \"Type\", \"Country\", \"City\", \"ISP\", \"ASN\", \"Timezone\"];\n\n            function renderTable(data1, data2 = null) {\n                let thead = `<tr><th>Information<\/th><th>Target 1<\/th>${data2 ? '<th>Target 2<\/th>' : ''}<\/tr>`;\n                let tbody = '';\n\n                fieldsToDisplay.forEach(key => {\n                    let val1 = data1[key];\n                    let val2 = data2 ? data2[key] : null;\n                    let diffBadge = (data2 && val1 !== val2 && val1 !== \"N\/A\" && val2 !== \"N\/A\") ? `<span class=\"wpnt-diff-badge\">Diff<\/span>` : '';\n\n                    let row = `<tr>\n                        <td class=\"wpnt-label-col\">${key.replace('_', ' ')}<\/td>\n                        <td><div class=\"wpnt-val-cell\"><div class=\"wpnt-val-content\"><span>${val1}<\/span><\/div><\/div><\/td>`;\n                    \n                    if (data2) {\n                        row += `<td><div class=\"wpnt-val-cell\"><div class=\"wpnt-val-content\"><span>${val2}<\/span> ${diffBadge}<\/div><\/div><\/td>`;\n                    }\n                    row += `<\/tr>`;\n                    tbody += row;\n                });\n\n                el.table.innerHTML = `<thead>${thead}<\/thead><tbody>${tbody}<\/tbody>`;\n\n                \/\/ Update Stats Row\n                if (data2) {\n                    el.statLabel1.textContent = \"TARGET 1 ASN\";\n                    el.statVal1.textContent = data1.ASN;\n                    \n                    const lat1 = parseFloat(data1.Latitude), lon1 = parseFloat(data1.Longitude);\n                    const lat2 = parseFloat(data2.Latitude), lon2 = parseFloat(data2.Longitude);\n                    \n                    if (!isNaN(lat1) && !isNaN(lon1) && !isNaN(lat2) && !isNaN(lon2)) {\n                        el.statVal2.textContent = `${parseFloat(calculateDistance(lat1, lon1, lat2, lon2)).toLocaleString()} KM`;\n                        el.statLabel2.textContent = \"GEOSPATIAL DISTANCE\";\n                    } else {\n                        el.statVal2.textContent = \"ANYCAST\";\n                        el.statLabel2.textContent = \"DISTANCE OBSCURED\";\n                    }\n                } else {\n                    el.statLabel1.textContent = \"IP PROTOCOL\";\n                    el.statVal1.textContent = data1.Type;\n                    el.statLabel2.textContent = \"ISP \/ NETWORK\";\n                    \/\/ Shorten ISP name if too long\n                    el.statVal2.textContent = data1.ISP.length > 20 ? data1.ISP.substring(0, 20) + '...' : data1.ISP;\n                }\n                \n                el.statVal3.textContent = \"SUCCESS\";\n                el.statVal3.style.color = \"var(--wpnt-success)\";\n            }\n\n            function renderMap(mapId, data, instanceVarName) {\n                const lat = parseFloat(data.Latitude);\n                const lon = parseFloat(data.Longitude);\n                \n                if (isNaN(lat) || isNaN(lon)) {\n                    document.getElementById(mapId).innerHTML = '<div style=\"display:flex;height:100%;align-items:center;justify-content:center;color:var(--wpnt-text-muted);\">Coordinates Unavailable<\/div>';\n                    return null;\n                }\n\n                if (window[instanceVarName]) { window[instanceVarName].remove(); }\n\n                const map = L.map(mapId, { zoomControl: true }).setView([lat, lon], 12);\n                L.tileLayer('https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png', {\n                    attribution: '&copy; OSM'\n                }).addTo(map);\n                L.marker([lat, lon]).addTo(map);\n\n                return map;\n            }\n\n            \/\/ Analyze Action\n            el.btnAnalyze.addEventListener('click', async () => {\n                const val1 = el.input1.value.trim();\n                const val2 = el.input2.value.trim();\n\n                if (!val1 && (!isCompareMode || !val2)) el.input1.value = ''; \n\n                el.error.style.display = 'none';\n                el.results.style.display = 'none';\n                el.btnCopy.style.display = 'none';\n                el.btnDownload.style.display = 'none';\n                el.loader.style.display = 'block';\n                el.btnAnalyze.disabled = true;\n                \n                el.statVal3.textContent = \"SCANNING...\";\n                el.statVal3.style.color = \"var(--wpnt-warning)\";\n\n                try {\n                    await loadLeaflet();\n\n                    if (isCompareMode && val1 && val2) {\n                        const [res1, res2] = await Promise.all([fetchIPData(val1), fetchIPData(val2)]);\n                        currentJsonData = { target1: res1, target2: res2 };\n                        renderTable(res1, res2);\n                        \n                        el.map1Card.style.display = 'block';\n                        el.map2Card.style.display = 'block';\n                        setTimeout(() => {\n                            window.map1Instance = renderMap('wpnt-map-1', res1, 'map1Instance');\n                            window.map2Instance = renderMap('wpnt-map-2', res2, 'map2Instance');\n                        }, 100);\n\n                    } else {\n                        const res = await fetchIPData(val1 || \"\");\n                        if (!val1) el.input1.value = res.IP_Address; \n                        \n                        currentJsonData = res;\n                        renderTable(res);\n                        \n                        el.map1Card.style.display = 'block';\n                        el.map2Card.style.display = 'none';\n                        setTimeout(() => {\n                            window.map1Instance = renderMap('wpnt-map-1', res, 'map1Instance');\n                        }, 100);\n                    }\n                    \n                    el.results.style.display = 'block';\n                    el.btnCopy.style.display = 'flex';\n                    el.btnDownload.style.display = 'flex';\n                } catch (err) {\n                    el.error.textContent = `Error: ${err.message}`;\n                    el.error.style.display = 'block';\n                    el.statVal3.textContent = \"FAILED\";\n                    el.statVal3.style.color = \"#ef4444\";\n                } finally {\n                    el.loader.style.display = 'none';\n                    el.btnAnalyze.disabled = false;\n                }\n            });\n\n            \/\/ Utility Actions\n            el.btnClear.addEventListener('click', () => {\n                el.input1.value = ''; el.input2.value = '';\n                el.results.style.display = 'none';\n                el.btnCopy.style.display = 'none'; el.btnDownload.style.display = 'none';\n                el.error.style.display = 'none';\n                el.statVal1.textContent = '-'; el.statVal2.textContent = '-';\n                el.statVal3.textContent = 'READY'; el.statVal3.style.color = \"var(--wpnt-warning)\";\n            });\n\n            el.btnCopy.addEventListener('click', () => {\n                navigator.clipboard.writeText(JSON.stringify(currentJsonData, null, 2)).then(() => {\n                    const originalHTML = el.btnCopy.innerHTML;\n                    el.btnCopy.innerHTML = \"Copied!\";\n                    setTimeout(() => el.btnCopy.innerHTML = originalHTML, 2000);\n                });\n            });\n\n            el.btnDownload.addEventListener('click', () => {\n                const blob = new Blob([JSON.stringify(currentJsonData, null, 2)], { type: \"application\/json\" });\n                const url = URL.createObjectURL(blob);\n                const a = document.createElement('a');\n                a.href = url; a.download = `sanepo-scan-${Date.now()}.json`;\n                document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);\n            });\n\n        })();\n    <\/script>\n<\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sanepo Tools \u2013 IP Lookup &#038; Geolocation Instantly analyze IP addresses, discover origins, and compare network nodes in milliseconds. 100% Client-Side (No Data Uploaded) Primary Target (IP or Domain) Secondary Target (For Comparison) Analyzing network topology&#8230; Analysis Result Map: Target 1 Map: Target 2 Scan Settings Compare Mode Enables side-by-side comparison of two distinct IP [&hellip;]<\/p>\n","protected":false},"featured_media":0,"template":"","meta":[],"class_list":["post-81","tool","type-tool","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/tools.sanepo.com\/id\/wp-json\/wp\/v2\/tool\/81","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tools.sanepo.com\/id\/wp-json\/wp\/v2\/tool"}],"about":[{"href":"https:\/\/tools.sanepo.com\/id\/wp-json\/wp\/v2\/types\/tool"}],"wp:attachment":[{"href":"https:\/\/tools.sanepo.com\/id\/wp-json\/wp\/v2\/media?parent=81"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}