mirror of
https://github.com/therootcompany/golib.git
synced 2026-11-04 06:55:00 +00:00
rewrite UI: static-first with hidden attr, no classitus
This commit is contained in:
parent
98938936fc
commit
ede27c582c
@ -1,221 +1,192 @@
|
|||||||
(function () {
|
const App = {
|
||||||
"use strict";
|
myIpMode: false,
|
||||||
|
|
||||||
function $(sel, $parent) {
|
submit(event) {
|
||||||
return ($parent || document).querySelector(sel);
|
event.preventDefault();
|
||||||
}
|
const input = event.target.querySelector('[data-input]');
|
||||||
|
const host = input.value.trim();
|
||||||
|
this.check(host);
|
||||||
|
},
|
||||||
|
|
||||||
function $$(sel, $parent) {
|
check(host) {
|
||||||
return Array.from(($parent || document).querySelectorAll(sel));
|
const btn = document.querySelector('[data-submit-btn]');
|
||||||
}
|
|
||||||
|
|
||||||
const form = $(".search-form");
|
|
||||||
const input = $("input[type=\"text\"]", form);
|
|
||||||
const btn = $("button", form);
|
|
||||||
const myIpLink = $("#my-ip-link");
|
|
||||||
const loading = $(".loading");
|
|
||||||
const errorEl = $(".error-msg");
|
|
||||||
const resultEl = $(".result");
|
|
||||||
let myIpVisible = false;
|
|
||||||
|
|
||||||
function show(el) { el.classList.add("visible"); }
|
|
||||||
function hide(el) { el.classList.remove("visible"); }
|
|
||||||
|
|
||||||
function render(result, isMyIp) {
|
|
||||||
hide(errorEl);
|
|
||||||
hide(resultEl);
|
|
||||||
if (isMyIp) myIpVisible = true;
|
|
||||||
|
|
||||||
const host = result.ip;
|
|
||||||
const resolvedFrom = result.resolved_from || "";
|
|
||||||
const blocked = result.blocked;
|
|
||||||
const blockedIn = result.blocked_inbound;
|
|
||||||
const blockedOut = result.blocked_outbound;
|
|
||||||
const whitelisted = result.allowlisted;
|
|
||||||
const geo = result.geo || {};
|
|
||||||
|
|
||||||
// Verdict
|
|
||||||
let verdictClass = "unknown";
|
|
||||||
let verdictText = "UNKNOWN";
|
|
||||||
if (whitelisted) {
|
|
||||||
verdictClass = "whitelist";
|
|
||||||
verdictText = "ALLOWED (whitelist)";
|
|
||||||
} else if (blocked) {
|
|
||||||
verdictClass = "blocked";
|
|
||||||
verdictText = "BLOCKED";
|
|
||||||
} else {
|
|
||||||
verdictClass = "allowed";
|
|
||||||
verdictText = "ALLOWED";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Geo info
|
|
||||||
let geoParts = [];
|
|
||||||
if (geo.city) geoParts.push(geo.city);
|
|
||||||
if (geo.region) {
|
|
||||||
geoParts.push(geo.region + (geo.region_iso ? " (" + geo.region_iso + ")" : ""));
|
|
||||||
} else if (geo.region_iso) {
|
|
||||||
geoParts.push(geo.region_iso);
|
|
||||||
}
|
|
||||||
if (geo.country) {
|
|
||||||
geoParts.push(geo.country + (geo.country_iso ? " (" + geo.country_iso + ")" : ""));
|
|
||||||
}
|
|
||||||
if (geo.continent) {
|
|
||||||
geoParts.push(geo.continent + (geo.continent_code ? " (" + geo.continent_code + ")" : ""));
|
|
||||||
}
|
|
||||||
let geoStr = geoParts.length ? geoParts.join(", ") : "—";
|
|
||||||
let latLonStr = "";
|
|
||||||
if (geo.latitude || geo.longitude) {
|
|
||||||
latLonStr = geo.latitude.toFixed(4) + ", " + geo.longitude.toFixed(4);
|
|
||||||
}
|
|
||||||
let asnStr = geo.asn ? "AS" + geo.asn + " " + (geo.asn_org || "") : "—";
|
|
||||||
let postalStr = geo.postal_code || "";
|
|
||||||
let tzStr = geo.timezone || "";
|
|
||||||
let metroStr = geo.metro_code ? String(geo.metro_code) : "";
|
|
||||||
let accuracyStr = geo.accuracy_radius ? String(geo.accuracy_radius) : "";
|
|
||||||
let registeredStr = geo.registered_country ? (geo.registered_country + (geo.registered_country_iso ? " (" + geo.registered_country_iso + ")" : "")) : "";
|
|
||||||
|
|
||||||
// Trait badges
|
|
||||||
let traitBadges = "";
|
|
||||||
if (geo.anycast) {
|
|
||||||
traitBadges += '<span class="trait-badge anycast">anycast</span>';
|
|
||||||
}
|
|
||||||
if (geo.anonymous_proxy) {
|
|
||||||
traitBadges += '<span class="trait-badge anonymous">anonymous proxy</span>';
|
|
||||||
}
|
|
||||||
if (geo.satellite) {
|
|
||||||
traitBadges += '<span class="trait-badge satellite">satellite</span>';
|
|
||||||
}
|
|
||||||
if (geo.country_in_eu) {
|
|
||||||
traitBadges += '<span class="trait-badge eu">EU</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block details
|
|
||||||
let blockHtml = "";
|
|
||||||
if (blockedIn || blockedOut) {
|
|
||||||
const parts = [];
|
|
||||||
if (blockedIn) parts.push("inbound");
|
|
||||||
if (blockedOut) parts.push("outbound");
|
|
||||||
blockHtml = '<span class="block-badge">BLOCKED: ' + parts.join(" + ") + '</span>';
|
|
||||||
} else {
|
|
||||||
blockHtml = '<span class="block-badge clean">clean</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
resultEl.innerHTML =
|
|
||||||
'<div class="result-header">' +
|
|
||||||
'<span class="verdict ' + verdictClass + '">' + verdictText + '</span>' +
|
|
||||||
'<span class="host">' + escapeHtml(host) + '</span>' +
|
|
||||||
(resolvedFrom ? '<span class="resolved">→ ' + escapeHtml(resolvedFrom) + '</span>' : '') +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="result-body">' +
|
|
||||||
(geoStr !== "—" ?
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Location</h3>' +
|
|
||||||
'<p class="mono">' + escapeHtml(geoStr) + '</p>' +
|
|
||||||
'</div>' : '') +
|
|
||||||
(latLonStr ?
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Coordinates</h3>' +
|
|
||||||
'<p class="mono">' + escapeHtml(latLonStr) + '</p>' +
|
|
||||||
'</div>' : '') +
|
|
||||||
(postalStr || tzStr || metroStr || accuracyStr ?
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Details</h3>' +
|
|
||||||
'<p class="mono">' +
|
|
||||||
(postalStr ? escapeHtml(postalStr) + " " : "") +
|
|
||||||
(tzStr ? escapeHtml(tzStr) + " " : "") +
|
|
||||||
(metroStr ? "Metro " + escapeHtml(metroStr) + " " : "") +
|
|
||||||
(accuracyStr ? "±" + escapeHtml(accuracyStr) + "km" : "") +
|
|
||||||
'</p>' +
|
|
||||||
'</div>' : '') +
|
|
||||||
(traitBadges ?
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Traits</h3>' +
|
|
||||||
'<div class="block-list">' + traitBadges + '</div>' +
|
|
||||||
'</div>' : '') +
|
|
||||||
(registeredStr ?
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Registered Country</h3>' +
|
|
||||||
'<p class="mono">' + escapeHtml(registeredStr) + '</p>' +
|
|
||||||
'</div>' : '') +
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>ASN</h3>' +
|
|
||||||
'<p class="mono">' + escapeHtml(asnStr) + '</p>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="result-section">' +
|
|
||||||
'<h3>Blocklist</h3>' +
|
|
||||||
'<div class="block-list">' + blockHtml + '</div>' +
|
|
||||||
'</div>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
show(resultEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(s) {
|
|
||||||
const d = document.createElement("div");
|
|
||||||
d.textContent = s;
|
|
||||||
return d.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitHost(host) {
|
|
||||||
hide(errorEl);
|
|
||||||
hide(resultEl);
|
|
||||||
show(loading);
|
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
var isMyIp = host === "";
|
this.clearState();
|
||||||
|
document.querySelector('[data-state="loading"]').hidden = false;
|
||||||
|
this.myIpMode = host === '';
|
||||||
|
|
||||||
fetch("/check?host=" + encodeURIComponent(host) + "&format=json")
|
fetch('/check?host=' + encodeURIComponent(host) + '&format=json')
|
||||||
.then(function (r) {
|
.then(function(r) {
|
||||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||||
return r.json();
|
return r.json();
|
||||||
})
|
})
|
||||||
.then(function (data) {
|
.then(function(data) {
|
||||||
hide(loading);
|
document.querySelector('[data-state="loading"]').hidden = true;
|
||||||
render(data, isMyIp);
|
document.querySelector('[data-state="result"]').hidden = false;
|
||||||
|
App.render(data);
|
||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function(err) {
|
||||||
hide(loading);
|
document.querySelector('[data-state="loading"]').hidden = true;
|
||||||
errorEl.textContent = "Error: " + err.message;
|
document.querySelector('[data-state="error"]').hidden = false;
|
||||||
show(errorEl);
|
document.querySelector('[data-error-text]').textContent = err.message;
|
||||||
})
|
})
|
||||||
.finally(function () {
|
.finally(function() {
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
form.addEventListener("submit", function (e) {
|
clearState() {
|
||||||
e.preventDefault();
|
document.querySelector('[data-state="result"]').hidden = true;
|
||||||
submitHost(input.value.trim());
|
document.querySelector('[data-state="error"]').hidden = true;
|
||||||
});
|
document.querySelectorAll('[data-verdict]').forEach(function(el) { el.hidden = true; });
|
||||||
|
document.querySelectorAll('[data-section]').forEach(function(el) { el.hidden = true; });
|
||||||
|
document.querySelectorAll('[data-trait]').forEach(function(el) { el.hidden = true; });
|
||||||
|
document.querySelectorAll('[data-block]').forEach(function(el) { el.hidden = true; });
|
||||||
|
document.querySelector('[data-resolved-from]').hidden = true;
|
||||||
|
},
|
||||||
|
|
||||||
myIpLink.addEventListener("click", function (e) {
|
render(data) {
|
||||||
e.preventDefault();
|
/* ── Verdict ──────────────────────────────────────────── */
|
||||||
if (myIpVisible) {
|
document.querySelectorAll('[data-verdict]').forEach(function(el) { el.hidden = true; });
|
||||||
hide(resultEl);
|
var verdictKey = 'allowed';
|
||||||
myIpLink.textContent = "Show my IP Info";
|
if (data.allowlisted) {
|
||||||
myIpVisible = false;
|
verdictKey = 'whitelist';
|
||||||
} else {
|
} else if (data.blocked_inbound && data.blocked_outbound) {
|
||||||
myIpLink.textContent = "Hide my IP Info";
|
verdictKey = 'blocked-inbound-outbound';
|
||||||
submitHost("");
|
} else if (data.blocked_inbound) {
|
||||||
|
verdictKey = 'blocked-inbound';
|
||||||
|
} else if (data.blocked_outbound) {
|
||||||
|
verdictKey = 'blocked-outbound';
|
||||||
}
|
}
|
||||||
});
|
document.querySelector('[data-verdict="' + verdictKey + '"]').hidden = false;
|
||||||
|
|
||||||
// Pre-fill from URL query param or hash fragment and auto-submit
|
/* ── IP address ───────────────────────────────────────── */
|
||||||
(function init() {
|
document.querySelector('[data-value="ip"]').textContent = data.ip;
|
||||||
let host = new URLSearchParams(window.location.search).get("host");
|
|
||||||
// Also check hash fragment (e.g. /#/?host=google.com)
|
/* ── Resolved from ────────────────────────────────────── */
|
||||||
|
if (data.resolved_from) {
|
||||||
|
document.querySelector('[data-value="resolved-from"]').textContent = data.resolved_from;
|
||||||
|
document.querySelector('[data-resolved-from]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Geo ──────────────────────────────────────────────── */
|
||||||
|
var geo = data.geo || {};
|
||||||
|
|
||||||
|
/* Location */
|
||||||
|
var locParts = [];
|
||||||
|
if (geo.city) locParts.push(geo.city);
|
||||||
|
if (geo.region) {
|
||||||
|
locParts.push(geo.region + (geo.region_iso ? ' (' + geo.region_iso + ')' : ''));
|
||||||
|
} else if (geo.region_iso) {
|
||||||
|
locParts.push(geo.region_iso);
|
||||||
|
}
|
||||||
|
if (geo.country) {
|
||||||
|
locParts.push(geo.country + (geo.country_iso ? ' (' + geo.country_iso + ')' : ''));
|
||||||
|
}
|
||||||
|
if (geo.continent) {
|
||||||
|
locParts.push(geo.continent + (geo.continent_code ? ' (' + geo.continent_code + ')' : ''));
|
||||||
|
}
|
||||||
|
if (locParts.length) {
|
||||||
|
document.querySelector('[data-value="location"]').textContent = locParts.join(', ');
|
||||||
|
document.querySelector('[data-section="location"]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Coordinates */
|
||||||
|
if (geo.latitude || geo.longitude) {
|
||||||
|
document.querySelector('[data-value="coordinates"]').textContent =
|
||||||
|
geo.latitude.toFixed(4) + ', ' + geo.longitude.toFixed(4);
|
||||||
|
document.querySelector('[data-section="coordinates"]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Details */
|
||||||
|
var detailParts = [];
|
||||||
|
if (geo.postal_code) detailParts.push(geo.postal_code);
|
||||||
|
if (geo.timezone) detailParts.push(geo.timezone);
|
||||||
|
if (geo.metro_code) detailParts.push('Metro ' + String(geo.metro_code));
|
||||||
|
if (geo.accuracy_radius) detailParts.push('±' + String(geo.accuracy_radius) + 'km');
|
||||||
|
if (detailParts.length) {
|
||||||
|
document.querySelector('[data-value="details"]').textContent = detailParts.join(' ');
|
||||||
|
document.querySelector('[data-section="details"]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Traits */
|
||||||
|
var traitMap = {
|
||||||
|
anycast: 'anycast',
|
||||||
|
anonymous: 'anonymous_proxy',
|
||||||
|
satellite: 'satellite',
|
||||||
|
eu: 'country_in_eu'
|
||||||
|
};
|
||||||
|
var hasTraits = false;
|
||||||
|
var traitKeys = Object.keys(traitMap);
|
||||||
|
for (var i = 0; i < traitKeys.length; i++) {
|
||||||
|
if (geo[traitMap[traitKeys[i]]]) {
|
||||||
|
document.querySelector('[data-trait="' + traitKeys[i] + '"]').hidden = false;
|
||||||
|
hasTraits = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasTraits) {
|
||||||
|
document.querySelector('[data-section="traits"]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Registered country */
|
||||||
|
if (geo.registered_country) {
|
||||||
|
var rcParts = geo.registered_country +
|
||||||
|
(geo.registered_country_iso ? ' (' + geo.registered_country_iso + ')' : '');
|
||||||
|
document.querySelector('[data-value="registered-country"]').textContent = rcParts;
|
||||||
|
document.querySelector('[data-section="registered-country"]').hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ASN */
|
||||||
|
if (geo.asn) {
|
||||||
|
document.querySelector('[data-value="asn"]').textContent =
|
||||||
|
'AS' + geo.asn + ' ' + (geo.asn_org || '');
|
||||||
|
} else {
|
||||||
|
document.querySelector('[data-value="asn"]').textContent = '—';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blocklist */
|
||||||
|
var hasBlock = false;
|
||||||
|
if (data.blocked_inbound) {
|
||||||
|
document.querySelector('[data-block="inbound"]').hidden = false;
|
||||||
|
hasBlock = true;
|
||||||
|
}
|
||||||
|
if (data.blocked_outbound) {
|
||||||
|
document.querySelector('[data-block="outbound"]').hidden = false;
|
||||||
|
hasBlock = true;
|
||||||
|
}
|
||||||
|
if (!hasBlock) {
|
||||||
|
document.querySelector('[data-block="clean"]').hidden = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleIpInfo(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
var link = document.querySelector('[data-my-ip-link]');
|
||||||
|
if (this.myIpMode) {
|
||||||
|
document.querySelector('[data-state="result"]').hidden = true;
|
||||||
|
link.textContent = 'Show my IP Info';
|
||||||
|
this.myIpMode = false;
|
||||||
|
} else {
|
||||||
|
link.textContent = 'Hide my IP Info';
|
||||||
|
this.check('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
var host = new URLSearchParams(window.location.search).get('host');
|
||||||
if (!host) {
|
if (!host) {
|
||||||
const hashQuery = window.location.hash.split("?")[1];
|
var hashQuery = window.location.hash.split('?')[1];
|
||||||
if (hashQuery) {
|
if (hashQuery) {
|
||||||
host = new URLSearchParams(hashQuery).get("host");
|
host = new URLSearchParams(hashQuery).get('host');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (host) {
|
if (host) {
|
||||||
input.value = host;
|
document.querySelector('[data-input]').value = host;
|
||||||
submitHost(host);
|
this.check(host);
|
||||||
|
} else {
|
||||||
|
document.querySelector('[data-input]').focus();
|
||||||
}
|
}
|
||||||
// Focus input on load (only if no auto-submit triggered)
|
}
|
||||||
if (!host) {
|
};
|
||||||
input.focus();
|
|
||||||
}
|
App.init();
|
||||||
})();
|
|
||||||
})();
|
|
||||||
|
|||||||
@ -3,33 +3,88 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>IP Location & Status</title>
|
<title>IP Location & Status</title>
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<main class="container" data-app>
|
||||||
<h1>IP Location & Status</h1>
|
<h1>IP Location & Status</h1>
|
||||||
<p class="subtitle">Look up an IP address, domain, or your own IP against blocklists and GeoIP databases.</p>
|
<p class="subtitle">Look up an IP address, domain, or your own IP against blocklists and GeoIP databases.</p>
|
||||||
|
|
||||||
<form class="search-form" autocomplete="off">
|
<form class="search-form" data-form autocomplete="off" onsubmit="App.submit(window.event)">
|
||||||
<input type="text" name="host" placeholder="Enter IP or domain…">
|
<input type="text" name="host" placeholder="Enter IP or domain…" data-input>
|
||||||
<button type="submit">Check</button>
|
<button type="submit" data-submit-btn>Check</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<a href="#" class="my-ip-link" id="my-ip-link">Show my IP Info</a>
|
<a href="#" class="my-ip-link" data-my-ip-link onclick="App.toggleIpInfo(window.event)">Show my IP Info</a>
|
||||||
|
|
||||||
<div class="loading">
|
<div class="loading" data-state="loading" hidden>
|
||||||
<span class="spinner"></span> Resolving and checking…
|
<span class="spinner"></span>
|
||||||
|
Resolving and checking…
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="error-msg"></div>
|
<div class="error-msg" data-state="error" hidden>
|
||||||
|
<span>Error: </span>
|
||||||
|
<span data-error-text></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="result"></div>
|
<div class="result" data-state="result" hidden>
|
||||||
|
<div class="result-header">
|
||||||
|
<span data-verdict-badge>
|
||||||
|
<span data-verdict="whitelist" hidden class="verdict verdict-whitelist">ALLOWED (whitelist)</span>
|
||||||
|
<span data-verdict="blocked-inbound-outbound" hidden class="verdict verdict-blocked">BLOCKED (inbound + outbound)</span>
|
||||||
|
<span data-verdict="blocked-inbound" hidden class="verdict verdict-blocked">BLOCKED (inbound)</span>
|
||||||
|
<span data-verdict="blocked-outbound" hidden class="verdict verdict-blocked">BLOCKED (outbound)</span>
|
||||||
|
<span data-verdict="allowed" hidden class="verdict verdict-allowed">ALLOWED</span>
|
||||||
|
</span>
|
||||||
|
<span class="host" data-value="ip"></span>
|
||||||
|
<span data-resolved-from hidden class="resolved">→ <span data-value="resolved-from"></span></span>
|
||||||
|
</div>
|
||||||
|
<div class="result-body">
|
||||||
|
<div class="result-section" data-section="location" hidden>
|
||||||
|
<h3>Location</h3>
|
||||||
|
<p class="mono" data-value="location"></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="coordinates" hidden>
|
||||||
|
<h3>Coordinates</h3>
|
||||||
|
<p class="mono" data-value="coordinates"></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="details" hidden>
|
||||||
|
<h3>Details</h3>
|
||||||
|
<p class="mono" data-value="details"></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="traits" hidden>
|
||||||
|
<h3>Traits</h3>
|
||||||
|
<div class="block-list">
|
||||||
|
<span data-trait="anycast" hidden class="trait-badge anycast">anycast</span>
|
||||||
|
<span data-trait="anonymous" hidden class="trait-badge anonymous">anonymous proxy</span>
|
||||||
|
<span data-trait="satellite" hidden class="trait-badge satellite">satellite</span>
|
||||||
|
<span data-trait="eu" hidden class="trait-badge eu">EU</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="registered-country" hidden>
|
||||||
|
<h3>Registered Country</h3>
|
||||||
|
<p class="mono" data-value="registered-country"></p>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="asn">
|
||||||
|
<h3>ASN</h3>
|
||||||
|
<p class="mono" data-value="asn">—</p>
|
||||||
|
</div>
|
||||||
|
<div class="result-section" data-section="blocklist">
|
||||||
|
<h3>Blocklist</h3>
|
||||||
|
<div class="block-list">
|
||||||
|
<span data-block="inbound" hidden class="block-badge">inbound</span>
|
||||||
|
<span data-block="outbound" hidden class="block-badge">outbound</span>
|
||||||
|
<span data-block="clean" hidden class="block-badge clean">clean</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<a href="/healthz">/healthz</a>
|
<a href="/healthz">/healthz</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
/* ── Reset & base ─────────────────────────────────────────── */
|
/* ── Reset & base ─────────────────────────────────────────── */
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #fafafa;
|
--bg: #fafafa;
|
||||||
@ -60,7 +61,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100dvh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -126,6 +127,7 @@ button {
|
|||||||
|
|
||||||
button:hover { background: var(--primary-hover); }
|
button:hover { background: var(--primary-hover); }
|
||||||
button:active { transform: scale(.98); }
|
button:active { transform: scale(.98); }
|
||||||
|
button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
|
||||||
button:disabled {
|
button:disabled {
|
||||||
opacity: .6;
|
opacity: .6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
@ -133,7 +135,6 @@ button:disabled {
|
|||||||
|
|
||||||
/* ── Result card ──────────────────────────────────────────── */
|
/* ── Result card ──────────────────────────────────────────── */
|
||||||
.result {
|
.result {
|
||||||
display: none;
|
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
@ -141,8 +142,6 @@ button:disabled {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result.visible { display: block; }
|
|
||||||
|
|
||||||
.result-header {
|
.result-header {
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
@ -165,6 +164,7 @@ button:disabled {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Verdict badge ────────────────────────────────────────── */
|
||||||
.verdict {
|
.verdict {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -178,30 +178,25 @@ button:disabled {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.verdict.allowed {
|
.verdict.verdict-allowed {
|
||||||
background: var(--ok-bg);
|
background: var(--ok-bg);
|
||||||
color: var(--ok);
|
color: var(--ok);
|
||||||
border: 1px solid var(--ok-border);
|
border: 1px solid var(--ok-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.verdict.blocked {
|
.verdict.verdict-blocked {
|
||||||
background: var(--bad-bg);
|
background: var(--bad-bg);
|
||||||
color: var(--bad);
|
color: var(--bad);
|
||||||
border: 1px solid var(--bad-border);
|
border: 1px solid var(--bad-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.verdict.whitelist {
|
.verdict.verdict-whitelist {
|
||||||
background: var(--warn-bg);
|
background: var(--warn-bg);
|
||||||
color: var(--warn);
|
color: var(--warn);
|
||||||
border: 1px solid var(--warn-border);
|
border: 1px solid var(--warn-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.verdict.unknown {
|
/* ── Result body ──────────────────────────────────────────── */
|
||||||
background: var(--bg);
|
|
||||||
color: var(--muted);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-body {
|
.result-body {
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
}
|
}
|
||||||
@ -237,6 +232,7 @@ button:disabled {
|
|||||||
gap: .375rem;
|
gap: .375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Block badge ──────────────────────────────────────────── */
|
||||||
.block-badge {
|
.block-badge {
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -313,15 +309,12 @@ button:disabled {
|
|||||||
|
|
||||||
/* ── Loading spinner ──────────────────────────────────────── */
|
/* ── Loading spinner ──────────────────────────────────────── */
|
||||||
.loading {
|
.loading {
|
||||||
display: none;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading.visible { display: block; }
|
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
@ -338,7 +331,6 @@ button:disabled {
|
|||||||
|
|
||||||
/* ── Error ────────────────────────────────────────────────── */
|
/* ── Error ────────────────────────────────────────────────── */
|
||||||
.error-msg {
|
.error-msg {
|
||||||
display: none;
|
|
||||||
background: var(--bad-bg);
|
background: var(--bad-bg);
|
||||||
color: var(--bad);
|
color: var(--bad);
|
||||||
border: 1px solid var(--bad-border);
|
border: 1px solid var(--bad-border);
|
||||||
@ -348,8 +340,6 @@ button:disabled {
|
|||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-msg.visible { display: block; }
|
|
||||||
|
|
||||||
/* ── My IP link ───────────────────────────────────────────── */
|
/* ── My IP link ───────────────────────────────────────────── */
|
||||||
.my-ip-link {
|
.my-ip-link {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -364,6 +354,7 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.my-ip-link:hover { text-decoration-color: var(--primary); }
|
.my-ip-link:hover { text-decoration-color: var(--primary); }
|
||||||
|
.my-ip-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
|
||||||
|
|
||||||
/* ── Footer ───────────────────────────────────────────────── */
|
/* ── Footer ───────────────────────────────────────────────── */
|
||||||
footer {
|
footer {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user