Theseus Navigator: - Firefox-style toolbar: SVG back/fwd/reload/home buttons - security padlock badge in the address bar opens a floating site-info overlay VIEW (on top of the page, never pushes content down) - Edge-style favorites bar (shown only on the new-tab/home page) - search-engine picker (DuckDuckGo/Google/Bing/Brave/Startpage) - Theseus logo moved right, opens Settings; native menu removed - address bar doubles as search; bottom resolver status line Argus: - BNS-only indexer: an electrum endpoint over a bare BCHN node (watches just the beacon, caches txs so no txindex needed) + test - registry lifecycle / portfolio / name-index modules Site restructure (nav/apps/store/hermes/choose) + runbook notes.
253 lines
14 KiB
HTML
253 lines
14 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head><meta charset="utf-8">
|
|
<style>
|
|
:root { color-scheme: dark; font-family: system-ui, sans-serif; }
|
|
/* `display:flex/grid` otherwise overrides the HTML hidden attribute — force it. */
|
|
[hidden] { display: none !important; }
|
|
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; }
|
|
.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; }
|
|
/* address bar with a security badge on the left + bookmark star on the right */
|
|
.urlwrap { flex: 1; display: flex; align-items: center; gap: 4px; background: #1b2330; border: 1px solid #ffffff22;
|
|
border-radius: 999px; padding: 0 6px 0 4px; }
|
|
.urlwrap:focus-within { border-color: #4b7bec; }
|
|
/* Firefox-style padlock badge */
|
|
.secbadge { display: grid; place-items: center; width: 28px; height: 26px; border-radius: 6px; border: none;
|
|
background: transparent; cursor: pointer; padding: 0; }
|
|
.secbadge:hover { background: #ffffff14; }
|
|
.secbadge svg { width: 14px; height: 14px; }
|
|
.secbadge svg .shk { fill: none; stroke: #a9b3c2; stroke-width: 1.3; }
|
|
.secbadge svg .bdy { fill: #a9b3c2; }
|
|
.secbadge.warn svg .shk { stroke: #f6ad55; } .secbadge.warn svg .bdy { fill: #f6ad55; }
|
|
.star { border: none; background: transparent; cursor: pointer; color: #8b98a9; font-size: 15px; padding: 2px 4px; border-radius: 6px; }
|
|
.star:hover { background: #ffffff14; color: #fff; } .star.on { color: #ffd23d; }
|
|
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
|
|
#url { flex: 1; }
|
|
/* search box + engine picker */
|
|
.searchbox { display: flex; align-items: center; gap: 2px; background: #1b2330; border: 1px solid #ffffff22;
|
|
border-radius: 999px; padding-left: 8px; }
|
|
.searchbox:focus-within { border-color: #4b7bec; }
|
|
.searchbox .mag { color: #8b98a9; font-size: 12px; }
|
|
#search { width: 150px; }
|
|
.engine { background: #10151f; color: #b9c2d0; border: none; border-left: 1px solid #ffffff1a; border-radius: 0 999px 999px 0;
|
|
font-size: 11.5px; padding: 6px 6px; outline: none; cursor: pointer; }
|
|
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 8px; border-radius: 999px; white-space: nowrap;
|
|
background: rgba(214,255,61,.13); color: #d6ff3d; border: 1px solid #d6ff3d33; }
|
|
.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; }
|
|
/* Theseus logo — moved to the right, opens Settings */
|
|
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
|
|
padding: 6px 12px; border: 1px solid #d6ff3d33; border-radius: 8px; cursor: pointer;
|
|
background: rgba(214,255,61,.08); font-size: 13px; }
|
|
.logo:hover { background: rgba(214,255,61,.16); }
|
|
.logo .gear { font-size: 12px; opacity: .8; }
|
|
/* bookmarks / saved-pages bar (Edge-style) */
|
|
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; overflow: hidden; }
|
|
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
|
|
background: #171e2a; border: 1px solid #ffffff10; color: #c5cdda; font-size: 12px; cursor: pointer; }
|
|
.bm:hover { background: #202a3a; color: #fff; }
|
|
.bm .bt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.bm .bx { opacity: 0; cursor: pointer; font-size: 11px; } .bm:hover .bx { opacity: .55; }
|
|
.bm .bx:hover { opacity: 1; color: #f6768a; }
|
|
.bm-empty { color: #5e6678; font-size: 11.5px; padding: 3px 4px; }
|
|
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
|
|
.tordisc a { color: #d6ff3d; }
|
|
.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>
|
|
<div class="urlwrap">
|
|
<button class="secbadge" id="secbadge" title="Site information" hidden>
|
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path class="shk" d="M5 7 V5.3 a3 3 0 0 1 6 0 V7"/><rect class="bdy" x="3.6" y="6.9" width="8.8" height="6" rx="1.2"/></svg>
|
|
</button>
|
|
<input id="url" placeholder="Search or enter a .bch / .p2p name or web address" spellcheck="false">
|
|
<span class="reg" id="reg" hidden></span>
|
|
<button class="star" id="star" title="Save this page">☆</button>
|
|
</div>
|
|
<div class="searchbox">
|
|
<span class="mag">🔍</span>
|
|
<input id="search" placeholder="Search" spellcheck="false">
|
|
<select id="engine" class="engine" title="Search engine"></select>
|
|
</div>
|
|
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
|
|
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear">⚙</span></button>
|
|
</div>
|
|
<div class="bookmarks" id="bookmarks" 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;
|
|
function syncHeight() { requestAnimationFrame(() => T.setChromeHeight(document.body.scrollHeight)); }
|
|
|
|
// ---- navigation ----
|
|
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();
|
|
$("logo").onclick = () => T.openSettings(); // logo (right) opens Settings
|
|
|
|
// ---- search engine picker ----
|
|
T.getSearchEngines().then((d) => {
|
|
const sel = $("engine");
|
|
sel.innerHTML = d.engines.map((e) => `<option value="${e.id}">${e.name}</option>`).join("");
|
|
sel.value = d.current;
|
|
sel.onchange = () => T.setSearchEngine(sel.value);
|
|
});
|
|
|
|
// ---- bookmarks bar ----
|
|
let current = { url: "", title: "" };
|
|
let bookmarks = [];
|
|
function renderBookmarks() {
|
|
const box = $("bookmarks");
|
|
if (!bookmarks.length) { box.innerHTML = `<span class="bm-empty">No saved pages yet — click ☆ to save the current page</span>`; syncHeight(); return; }
|
|
box.innerHTML = bookmarks.map((b, i) =>
|
|
`<div class="bm" data-i="${i}" title="${(b.url || "").replace(/"/g, """)}">
|
|
<span class="bt">${(b.title || b.url).replace(/</g, "<")}</span>
|
|
<span class="bx" data-x="${i}">✕</span></div>`).join("");
|
|
box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => {
|
|
const i = Number(el.dataset.i);
|
|
if (e.target.dataset.x !== undefined) { T.removeBookmark(bookmarks[i].url); }
|
|
else T.navigate(bookmarks[i].url);
|
|
});
|
|
syncHeight();
|
|
updateStar();
|
|
}
|
|
function updateStar() {
|
|
const saved = bookmarks.some((b) => b.url === current.url && current.url);
|
|
const s = $("star"); s.textContent = saved ? "★" : "☆"; s.classList.toggle("on", saved);
|
|
s.title = saved ? "Remove from saved pages" : "Save this page";
|
|
}
|
|
$("star").onclick = () => {
|
|
if (!current.url) return;
|
|
if (bookmarks.some((b) => b.url === current.url)) T.removeBookmark(current.url);
|
|
else T.addBookmark({ title: current.title || current.url, url: current.url });
|
|
};
|
|
T.getBookmarks().then((b) => { bookmarks = b || []; renderBookmarks(); });
|
|
T.onBookmarks((b) => { bookmarks = b || []; renderBookmarks(); });
|
|
|
|
// ---- security badge (Firefox-style padlock); opens the floating overlay ----
|
|
function setBadge(d) {
|
|
const b = $("secbadge");
|
|
if (!d || d.kind === "home") { b.hidden = true; return; }
|
|
b.hidden = false;
|
|
let cls = "secbadge", title = "Site information — click for details";
|
|
if (d.kind === "ok") title = "Secure · " + (d.source || "BCNR") + " — click for details";
|
|
else if (d.kind === "web") title = "Connection — click for details";
|
|
else if (d.kind === "resolving") title = "Resolving…";
|
|
else if (d.kind === "nxdomain") { cls = "secbadge warn"; title = "Not registered on BCNR"; }
|
|
else if (d.kind === "error") { cls = "secbadge warn"; title = "Resolution error"; }
|
|
b.className = cls; b.title = title;
|
|
}
|
|
// The popover is a separate overlay view; hand main the badge's screen position.
|
|
$("secbadge").onclick = () => {
|
|
const r = $("secbadge").getBoundingClientRect();
|
|
T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) });
|
|
};
|
|
|
|
// ---- Tor ----
|
|
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 BCNR offer ----
|
|
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";
|
|
});
|
|
|
|
// ---- tabs ----
|
|
T.onTabs((d) => {
|
|
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
|
|
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
|
current.url = d.url || "";
|
|
const active = d.tabs.find((t) => t.active);
|
|
current.title = active ? active.title : "";
|
|
updateStar();
|
|
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();
|
|
});
|
|
|
|
// ---- provenance ----
|
|
T.onNav((d) => {
|
|
setBadge(d);
|
|
const reg = $("reg");
|
|
if (d.tld) { reg.hidden = false; reg.textContent = (d.registry || "BCNR") + " · ." + d.tld; } else reg.hidden = true;
|
|
if (document.activeElement !== $("url")) {
|
|
if (d.kind === "home") $("url").value = "";
|
|
else if (d.host && !$("url").value) $("url").value = d.host;
|
|
}
|
|
// Favorites bar shows only on the new-tab / home page (Edge-style); it hides
|
|
// once a real page loads so it never eats space while browsing.
|
|
$("bookmarks").hidden = !(!d || d.kind === "home");
|
|
syncHeight();
|
|
});
|
|
|
|
syncHeight();
|
|
</script>
|
|
</body>
|
|
</html>
|