223 lines
13 KiB
HTML
223 lines
13 KiB
HTML
|
|
<!doctype html>
|
||
|
|
<html>
|
||
|
|
<head><meta charset="utf-8">
|
||
|
|
<style>
|
||
|
|
:root { color-scheme: dark; font-family: system-ui, sans-serif; }
|
||
|
|
body { margin: 0; height: 100vh; background: #0f1420; color: #e7eaf1; user-select: none; overflow: hidden; }
|
||
|
|
/* tab strip */
|
||
|
|
.tabs { display: flex; align-items: flex-end; gap: 4px; padding: 6px 8px 0; height: 34px; }
|
||
|
|
.tab { display: flex; align-items: center; gap: 8px; max-width: 200px; min-width: 90px; padding: 6px 10px;
|
||
|
|
background: #1b2330; border: 1px solid #ffffff14; border-bottom: none; border-radius: 8px 8px 0 0;
|
||
|
|
font-size: 12.5px; cursor: default; color: #b9c2d0; }
|
||
|
|
.tab.active { background: #26304180; color: #fff; }
|
||
|
|
.tab .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||
|
|
.tab .x { opacity: .5; cursor: pointer; padding: 0 2px; border-radius: 4px; }
|
||
|
|
.tab .x:hover { opacity: 1; background: #ffffff1a; }
|
||
|
|
.newtab { padding: 4px 10px; cursor: pointer; color: #8b98a9; border-radius: 6px; font-size: 16px; }
|
||
|
|
.newtab:hover { background: #ffffff12; color: #fff; }
|
||
|
|
/* toolbar */
|
||
|
|
.bar { display: flex; gap: 6px; align-items: center; padding: 6px 10px; }
|
||
|
|
.nav { display: flex; gap: 1px; }
|
||
|
|
/* Firefox-style round-rect icon buttons: 16px stroke glyph in a 34x32 hit target */
|
||
|
|
.ic { width: 34px; height: 32px; display: grid; place-items: center; border-radius: 8px; cursor: pointer;
|
||
|
|
color: #c5cdda; background: transparent; border: none; padding: 0; }
|
||
|
|
.ic svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7;
|
||
|
|
stroke-linecap: round; stroke-linejoin: round; }
|
||
|
|
.ic:hover { background: #ffffff14; color: #fff; }
|
||
|
|
.ic:active { background: #ffffff22; }
|
||
|
|
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
|
||
|
|
/* clickable Theseus logo = site-info button */
|
||
|
|
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
|
||
|
|
padding: 5px 10px; margin: 0 2px; border: 1px solid transparent; border-radius: 8px; cursor: pointer;
|
||
|
|
background: transparent; font-size: 13px; }
|
||
|
|
.logo:hover { background: rgba(214,255,61,.10); border-color: #d6ff3d33; }
|
||
|
|
.logo.open { background: rgba(214,255,61,.14); border-color: #d6ff3d55; }
|
||
|
|
.logo .chev { color: #9fb04e; font-size: 9px; }
|
||
|
|
input { padding: 8px 13px; border-radius: 999px; border: 1px solid #ffffff22; background: #1b2330; color: inherit; font-size: 13.5px; outline: none; }
|
||
|
|
input:focus { border-color: #4b7bec; }
|
||
|
|
.urlwrap { flex: 1; display: flex; align-items: center; gap: 6px; background: #1b2330; border: 1px solid #ffffff22;
|
||
|
|
border-radius: 999px; padding-right: 8px; }
|
||
|
|
.urlwrap:focus-within { border-color: #4b7bec; }
|
||
|
|
#url { flex: 1; border: none; background: transparent; border-radius: 999px; }
|
||
|
|
#url:focus { border: none; }
|
||
|
|
#search { width: 180px; }
|
||
|
|
.tor { padding: 7px 10px; border-radius: 8px; border: 1px solid #ffffff22; background: #2b2f3a; color: #fff; cursor: pointer; white-space: nowrap; font-size: 12.5px; }
|
||
|
|
.tor.connecting { background: #7a5c14; } .tor.on { background: #6b3fa0; }
|
||
|
|
.sep { width: 1px; height: 22px; background: #ffffff1a; margin: 0 4px; }
|
||
|
|
.searchbox { display: flex; align-items: center; gap: 4px; }
|
||
|
|
.searchbox .mag { color: #8b98a9; font-size: 13px; }
|
||
|
|
/* registry/TLD badge — trusted chrome, painted by the browser not the page */
|
||
|
|
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 9px; border-radius: 999px;
|
||
|
|
background: rgba(214,255,61,.13); color: #d6ff3d; border: 1px solid #d6ff3d33; white-space: nowrap; }
|
||
|
|
/* site-info dropdown panel (opened by the logo) */
|
||
|
|
.siteinfo { font-size: 12px; color: #dbe3ef; background: #141b28; border-top: 1px solid #ffffff12;
|
||
|
|
padding: 10px 16px; display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; }
|
||
|
|
.siteinfo .k { color: #7f8aa0; }
|
||
|
|
.siteinfo .v { color: #e7eaf1; }
|
||
|
|
.siteinfo .v code { font-family: ui-monospace, monospace; color: #bfeae4; }
|
||
|
|
.siteinfo .dotv { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: #5e6678; margin-right: 6px; vertical-align: middle; }
|
||
|
|
.siteinfo .dotv.chain { background: #d6ff3d; } .siteinfo .dotv.sia { background: #b39ddb; }
|
||
|
|
.siteinfo .dotv.server { background: #4fd1a5; } .siteinfo .dotv.web { background: #8b93a7; } .siteinfo .dotv.err { background: #f6768a; }
|
||
|
|
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
|
||
|
|
.tordisc a { color: #d6ff3d; }
|
||
|
|
/* passive "also on BCNR" bar — dual-use TLD switch (never hijacks the web) */
|
||
|
|
.bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: #dbead0;
|
||
|
|
background: #1a2417; border-top: 1px solid #d6ff3d33; padding: 6px 14px; }
|
||
|
|
.bcnrbar .breg { font-size: 10px; letter-spacing: .03em; padding: 1px 7px; border-radius: 999px;
|
||
|
|
background: rgba(214,255,61,.14); color: #d6ff3d; border: 1px solid #d6ff3d33; }
|
||
|
|
.bcnrbar b { color: #fff; }
|
||
|
|
.bcnrbar .bopen { margin-left: auto; padding: 4px 11px; border-radius: 7px; border: 1px solid #d6ff3d55;
|
||
|
|
background: rgba(214,255,61,.12); color: #eaffb0; cursor: pointer; font-size: 11.5px; }
|
||
|
|
.bcnrbar .bopen:hover { background: rgba(214,255,61,.22); }
|
||
|
|
.bcnrbar .bdismiss { color: #8b98a9; text-decoration: none; }
|
||
|
|
.bcnrbar .bdismiss:hover { color: #fff; }
|
||
|
|
</style></head>
|
||
|
|
<body>
|
||
|
|
<div class="tabs" id="tabs"></div>
|
||
|
|
<div class="bar">
|
||
|
|
<div class="nav">
|
||
|
|
<button class="ic" id="back" title="Back">
|
||
|
|
<svg viewBox="0 0 16 16"><path d="M10 3 L5 8 L10 13"/></svg></button>
|
||
|
|
<button class="ic" id="fwd" title="Forward">
|
||
|
|
<svg viewBox="0 0 16 16"><path d="M6 3 L11 8 L6 13"/></svg></button>
|
||
|
|
<button class="ic" id="reload" title="Reload">
|
||
|
|
<svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg></button>
|
||
|
|
<button class="ic" id="home" title="Home">
|
||
|
|
<svg viewBox="0 0 16 16"><path d="M2.5 7.5 L8 3 L13.5 7.5"/><path d="M4 6.7 L4 13 L12 13 L12 6.7"/></svg></button>
|
||
|
|
</div>
|
||
|
|
<button class="logo" id="logo" title="Site information">
|
||
|
|
<span>⛓ Theseus</span><span class="chev">▼</span></button>
|
||
|
|
<div class="urlwrap">
|
||
|
|
<input id="url" placeholder="Search or enter a .bch / .p2p name or web address" spellcheck="false">
|
||
|
|
<span class="reg" id="reg" hidden></span>
|
||
|
|
</div>
|
||
|
|
<span class="sep"></span>
|
||
|
|
<div class="searchbox">
|
||
|
|
<span class="mag">🔍</span>
|
||
|
|
<input id="search" placeholder="Search the web" spellcheck="false">
|
||
|
|
</div>
|
||
|
|
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
|
||
|
|
<button class="ic" id="settings" title="Settings">
|
||
|
|
<svg viewBox="0 0 16 16"><circle cx="8" cy="8" r="2.2"/><path d="M8 1.5 L8 3 M8 13 L8 14.5 M1.5 8 L3 8 M13 8 L14.5 8 M3.3 3.3 L4.3 4.3 M11.7 11.7 L12.7 12.7 M12.7 3.3 L11.7 4.3 M4.3 11.7 L3.3 12.7"/></svg></button>
|
||
|
|
</div>
|
||
|
|
<div id="siteinfo" class="siteinfo" hidden></div>
|
||
|
|
<div id="tordisc" class="tordisc" hidden>
|
||
|
|
Onion mode hides your IP from sites and your ISP (including .bch lookups).
|
||
|
|
It is <b>not full anonymity</b> — this browser can still be fingerprinted; for that use the Tor Browser.
|
||
|
|
<a href="#" id="tordismiss">got it</a>
|
||
|
|
</div>
|
||
|
|
<div id="bcnrbar" class="bcnrbar" hidden>
|
||
|
|
<span class="breg" id="bcnrreg">BCNR</span>
|
||
|
|
<span><b id="bcnrhost"></b> also exists on the <b>BCNR</b> decentralized registry.</span>
|
||
|
|
<button id="bcnropen" class="bopen">Open on BCNR</button>
|
||
|
|
<a href="#" id="bcnrdismiss" class="bdismiss">dismiss</a>
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
const $ = (id) => document.getElementById(id);
|
||
|
|
const T = window.theseus;
|
||
|
|
// Keep the chrome view tall enough for whatever bars are visible (site info / Tor / BCNR).
|
||
|
|
function syncHeight() { requestAnimationFrame(() => T.setChromeHeight(document.body.scrollHeight)); }
|
||
|
|
function goURL() { const v = $("url").value.trim(); if (v) T.navigate(v); }
|
||
|
|
$("url").addEventListener("keydown", (e) => { if (e.key === "Enter") goURL(); });
|
||
|
|
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) { T.search(q); $("search").value = ""; } } });
|
||
|
|
$("back").onclick = () => T.back();
|
||
|
|
$("fwd").onclick = () => T.forward();
|
||
|
|
$("reload").onclick = () => T.reload();
|
||
|
|
$("home").onclick = () => T.goHome();
|
||
|
|
$("settings").onclick = () => T.openSettings();
|
||
|
|
|
||
|
|
// ---- site-info panel (toggled by the Theseus logo) ----
|
||
|
|
let lastNav = null, infoOpen = false;
|
||
|
|
function renderInfo() {
|
||
|
|
const d = lastNav, box = $("siteinfo");
|
||
|
|
const row = (k, v) => `<span class="k">${k}</span><span class="v">${v}</span>`;
|
||
|
|
let html = "";
|
||
|
|
if (!d || d.kind === "home") {
|
||
|
|
html = row("Page", "Theseus home") +
|
||
|
|
row("About", "Theseus is a decentralized-web navigator. It resolves BCNR names (.bch, .p2p, .nav…) straight from the Bitcoin Cash chain — no ICANN, no DNS middleman.");
|
||
|
|
} else if (d.kind === "web") {
|
||
|
|
html = row("Site", `<span class="dotv web"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
||
|
|
row("Resolved via", "ICANN DNS (ordinary web)") +
|
||
|
|
row("Registry", "ICANN — not a BCNR name");
|
||
|
|
} else if (d.kind === "resolving") {
|
||
|
|
html = row("Site", `<code>${(d.host||"").replace(/</g,"<")}</code>`) + row("Status", "resolving on the BCH chain…");
|
||
|
|
} else if (d.kind === "nxdomain") {
|
||
|
|
html = row("Name", `<span class="dotv err"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
||
|
|
row("Status", "not registered on the BCNR chain (NXDOMAIN)");
|
||
|
|
} else if (d.kind === "error") {
|
||
|
|
html = row("Status", `<span class="dotv err"></span>${(d.error||"resolution failed").replace(/</g,"<")}`);
|
||
|
|
} else { // ok
|
||
|
|
const cls = (d.source||"").includes("chain") ? "chain" : (d.source||"").includes("Sia") ? "sia" : (d.source||"").includes("server") ? "server" : "web";
|
||
|
|
html = row("Site", `<span class="dotv ${cls}"></span><code>${(d.host||"").replace(/</g,"<")}</code>`) +
|
||
|
|
row("Served from", d.source || "chain") +
|
||
|
|
row("Registry", (d.registry || "BCNR") + (d.tld ? " · ." + d.tld : "")) +
|
||
|
|
(d.records && d.records.length ? row("Records", `<code>${d.records.join(", ")}</code>`) : "") +
|
||
|
|
(d.category ? row("Certificate", `<code>${String(d.category).slice(0,24)}…</code>`) : "");
|
||
|
|
}
|
||
|
|
box.innerHTML = html;
|
||
|
|
}
|
||
|
|
function toggleInfo(force) {
|
||
|
|
infoOpen = force !== undefined ? force : !infoOpen;
|
||
|
|
$("siteinfo").hidden = !infoOpen;
|
||
|
|
$("logo").classList.toggle("open", infoOpen);
|
||
|
|
if (infoOpen) renderInfo();
|
||
|
|
syncHeight();
|
||
|
|
}
|
||
|
|
$("logo").onclick = () => toggleInfo();
|
||
|
|
|
||
|
|
let torShown = false;
|
||
|
|
$("tor").onclick = () => { T.toggleTor(); if (!torShown) { $("tordisc").hidden = false; torShown = true; syncHeight(); } };
|
||
|
|
$("tordismiss").onclick = (e) => { e.preventDefault(); $("tordisc").hidden = true; syncHeight(); };
|
||
|
|
|
||
|
|
// Passive "also on BCNR" bar for dual-use ICANN TLDs (.de/.dev/.ltd).
|
||
|
|
T.onBcnrOffer((d) => {
|
||
|
|
const bar = $("bcnrbar");
|
||
|
|
if (d && d.host) {
|
||
|
|
$("bcnrhost").textContent = d.host;
|
||
|
|
$("bcnrreg").textContent = d.registry || "BCNR";
|
||
|
|
bar.hidden = false;
|
||
|
|
} else { bar.hidden = true; }
|
||
|
|
syncHeight();
|
||
|
|
});
|
||
|
|
$("bcnropen").onclick = () => { $("bcnrbar").hidden = true; syncHeight(); T.switchToBcnr(); };
|
||
|
|
$("bcnrdismiss").onclick = (e) => { e.preventDefault(); $("bcnrbar").hidden = true; syncHeight(); };
|
||
|
|
T.onTor((d) => {
|
||
|
|
const b = $("tor"); b.className = "tor " + (d.state === "on" ? "on" : d.state === "connecting" ? "connecting" : "");
|
||
|
|
b.textContent = d.state === "on" ? "🧅 Tor: On" : d.state === "connecting" ? "🧅 connecting…" : "🧅 Tor: Off";
|
||
|
|
});
|
||
|
|
|
||
|
|
T.onTabs((d) => {
|
||
|
|
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
|
||
|
|
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
||
|
|
const box = $("tabs");
|
||
|
|
box.innerHTML = d.tabs.map((t) =>
|
||
|
|
`<div class="tab ${t.active ? "active" : ""}" data-id="${t.id}">
|
||
|
|
<span class="t">${(t.title || "New Tab").replace(/</g, "<")}</span>
|
||
|
|
<span class="x" data-close="${t.id}">✕</span></div>`).join("") +
|
||
|
|
`<span class="newtab" id="newtab">+</span>`;
|
||
|
|
box.querySelectorAll(".tab").forEach((el) => el.onclick = (e) => {
|
||
|
|
if (e.target.dataset.close) T.closeTab(Number(e.target.dataset.close));
|
||
|
|
else T.switchTab(Number(el.dataset.id));
|
||
|
|
});
|
||
|
|
$("newtab").onclick = () => T.newTab();
|
||
|
|
});
|
||
|
|
|
||
|
|
T.onNav((d) => {
|
||
|
|
lastNav = d;
|
||
|
|
// Registry/TLD badge: shown for any BNS name, painted here (trusted chrome).
|
||
|
|
const reg = $("reg");
|
||
|
|
if (d.tld) { reg.hidden = false; reg.textContent = (d.registry || "BCNR") + " · ." + d.tld; }
|
||
|
|
else reg.hidden = true;
|
||
|
|
// Keep the address bar in sync (unless the user is typing).
|
||
|
|
if (document.activeElement !== $("url")) {
|
||
|
|
if (d.kind === "home") $("url").value = "";
|
||
|
|
else if (d.host && !$("url").value) $("url").value = d.host;
|
||
|
|
}
|
||
|
|
if (infoOpen) renderInfo();
|
||
|
|
});
|
||
|
|
|
||
|
|
syncHeight();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|