2026-07-29 13:54:34 +02:00
|
|
|
<!doctype html>
|
|
|
|
|
<html>
|
|
|
|
|
<head><meta charset="utf-8">
|
|
|
|
|
<style>
|
2026-07-30 22:00:01 +02:00
|
|
|
:root {
|
|
|
|
|
color-scheme: light dark; font-family: system-ui, sans-serif;
|
|
|
|
|
--bg:#0f1420; --surface:#1b2330; --surface2:#171e2a; --active:#263041cc;
|
|
|
|
|
--ink:#e7eaf1; --mut:#b9c2d0; --dim:#8b98a9; --faint:#5e6678;
|
|
|
|
|
--line:rgba(255,255,255,.20); --line2:rgba(255,255,255,.13); --hover:rgba(255,255,255,.08);
|
|
|
|
|
}
|
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
|
|
|
:root {
|
|
|
|
|
--bg:#e9ecf1; --surface:#ffffff; --surface2:#f1f3f7; --active:#dbe1ec;
|
|
|
|
|
--ink:#1a1f28; --mut:#3c4453; --dim:#697280; --faint:#98a1b0;
|
|
|
|
|
--line:rgba(0,0,0,.18); --line2:rgba(0,0,0,.11); --hover:rgba(0,0,0,.06);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-30 08:16:55 +02:00
|
|
|
[hidden] { display: none !important; }
|
2026-07-30 22:00:01 +02:00
|
|
|
body { margin: 0; height: 100vh; background: var(--bg); color: var(--ink); user-select: none; overflow: hidden; }
|
2026-07-29 13:54:34 +02:00
|
|
|
.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;
|
2026-07-30 22:00:01 +02:00
|
|
|
background: var(--surface); border: 1px solid var(--line2); border-bottom: none; border-radius: 8px 8px 0 0;
|
|
|
|
|
font-size: 12.5px; cursor: default; color: var(--mut); }
|
|
|
|
|
.tab.active { background: var(--active); color: var(--ink); }
|
2026-07-29 13:54:34 +02:00
|
|
|
.tab .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
2026-07-30 19:29:32 +02:00
|
|
|
.tab .spin { width: 10px; height: 10px; flex: none; border: 1.5px solid #ffffff2e; border-top-color: #d6ff3d; border-radius: 50%; animation: spin .7s linear infinite; }
|
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
2026-07-29 13:54:34 +02:00
|
|
|
.tab .x { opacity: .5; cursor: pointer; padding: 0 2px; border-radius: 4px; }
|
2026-07-30 22:00:01 +02:00
|
|
|
.tab .x:hover { opacity: 1; background: var(--line2); }
|
|
|
|
|
.newtab { padding: 4px 10px; cursor: pointer; color: var(--dim); border-radius: 6px; font-size: 16px; }
|
|
|
|
|
.newtab:hover { background: var(--hover); color: var(--ink); }
|
2026-07-29 13:54:34 +02:00
|
|
|
.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;
|
2026-07-30 22:00:01 +02:00
|
|
|
color: var(--mut); background: transparent; border: none; padding: 0; }
|
2026-07-30 19:29:32 +02:00
|
|
|
.ic svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
|
2026-07-30 22:00:01 +02:00
|
|
|
.ic:hover { background: var(--line2); color: var(--ink); }
|
|
|
|
|
.ic:active { background: var(--line); }
|
2026-07-29 13:54:34 +02:00
|
|
|
.ic:disabled { opacity: .28; cursor: default; background: transparent; }
|
2026-08-02 13:02:47 +02:00
|
|
|
/* downloads button — a small badge sits over the icon when there's activity */
|
|
|
|
|
.dlbtn { position: relative; }
|
|
|
|
|
.dlbtn.spin::before { content: ""; position: absolute; inset: 4px; border-radius: 999px;
|
|
|
|
|
border: 1.5px solid transparent; border-top-color: #4b7bec; animation: dlspin .9s infinite linear; }
|
|
|
|
|
@keyframes dlspin { to { transform: rotate(360deg); } }
|
|
|
|
|
.dlbadge { position: absolute; top: 2px; right: 2px; min-width: 14px; height: 14px; padding: 0 3px;
|
|
|
|
|
background: #4b7bec; color: #fff; border-radius: 7px; font-size: 9.5px; font-weight: 700;
|
|
|
|
|
display: grid; place-items: center; line-height: 1; box-shadow: 0 0 0 2px var(--bg); }
|
|
|
|
|
.dlbtn.done .dlbadge { background: #4fd1a5; }
|
|
|
|
|
.dlbtn.err .dlbadge { background: #f6768a; }
|
|
|
|
|
/* indeterminate loading bar under the toolbar — collapses when idle so it
|
|
|
|
|
doesn't take a permanent 2px strip below the address bar */
|
|
|
|
|
.loadbar { height: 0; overflow: hidden; background: transparent; transition: height .12s linear; }
|
|
|
|
|
.loadbar.on { height: 2px; }
|
2026-07-30 19:29:32 +02:00
|
|
|
.loadbar.on::after { content: ""; display: block; height: 100%; width: 35%; border-radius: 2px;
|
|
|
|
|
background: linear-gradient(90deg, transparent, #4b7bec, #d6ff3d, transparent); animation: loadslide 1.1s infinite linear; }
|
|
|
|
|
@keyframes loadslide { 0% { transform: translateX(-110%); } 100% { transform: translateX(400%); } }
|
|
|
|
|
/* address bar */
|
2026-07-30 22:00:01 +02:00
|
|
|
.urlwrap { flex: 1; display: flex; align-items: center; gap: 4px; background: var(--surface); border: 1px solid var(--line);
|
2026-07-30 08:16:55 +02:00
|
|
|
border-radius: 999px; padding: 0 6px 0 4px; }
|
2026-07-29 13:54:34 +02:00
|
|
|
.urlwrap:focus-within { border-color: #4b7bec; }
|
2026-08-02 17:46:03 +02:00
|
|
|
/* Site security shield — always present. Colour communicates state:
|
|
|
|
|
neutral (default) for home / resolving, green when the connection is
|
|
|
|
|
secure (BCNR chain-verified OR https:// clearnet), red when it isn't
|
|
|
|
|
(nxdomain / resolver error / plain http). Shape stays constant so the
|
|
|
|
|
button doesn't jump around when the state changes. */
|
2026-07-30 08:16:55 +02:00
|
|
|
.secbadge { display: grid; place-items: center; width: 28px; height: 26px; border-radius: 6px; border: none;
|
2026-08-02 17:46:03 +02:00
|
|
|
background: transparent; cursor: pointer; padding: 0; color: var(--dim); }
|
2026-07-30 22:00:01 +02:00
|
|
|
.secbadge:hover { background: var(--line2); }
|
2026-08-02 17:46:03 +02:00
|
|
|
.secbadge svg { width: 16px; height: 16px; fill: currentColor; }
|
2026-08-06 01:41:31 +02:00
|
|
|
.secbadge.secure { color: #3fb950; } /* GitHub-style saturated green */
|
2026-08-02 17:46:03 +02:00
|
|
|
.secbadge.insecure { color: #f6768a; }
|
|
|
|
|
.secbadge.warn { color: #f6ad55; } /* kept for legacy call sites */
|
2026-07-30 08:16:55 +02:00
|
|
|
input { padding: 7px 6px; border-radius: 999px; border: none; background: transparent; color: inherit; font-size: 13.5px; outline: none; }
|
|
|
|
|
#url { flex: 1; }
|
2026-08-02 19:26:47 +02:00
|
|
|
/* Registry indicator at the END of the address bar. Two shapes:
|
|
|
|
|
- Toggle (segmented control) when this host could be served by EITHER
|
|
|
|
|
registry — a single pill split in half, active side filled, inactive
|
|
|
|
|
side transparent and clickable to switch.
|
|
|
|
|
- Single pill when there's no alternative (pure ICANN or a BCNR-unique TLD).*/
|
|
|
|
|
#reg { display: inline-flex; align-items: center; }
|
|
|
|
|
.reg { font-size: 10.5px; letter-spacing: .03em; padding: 2px 10px; white-space: nowrap;
|
|
|
|
|
cursor: default; user-select: none; transition: background .12s, color .12s, border-color .12s; }
|
|
|
|
|
/* Single-chip form (non-collision hosts) */
|
|
|
|
|
.reg.single { border-radius: 999px; border: 1px solid; }
|
|
|
|
|
.reg.single.bcdn.active { background: rgba(214,255,61,.15); color: #d6ff3d; border-color: #d6ff3d55; }
|
|
|
|
|
.reg.single.icann.active { background: rgba(76,158,255,.15); color: #4c9eff; border-color: #4c9eff55; }
|
|
|
|
|
/* Toggle (segmented control) form for collision candidates */
|
|
|
|
|
.tog { display: inline-flex; border-radius: 999px; overflow: hidden; border: 1px solid #ffffff26; }
|
|
|
|
|
.tog .reg { border: 0; }
|
|
|
|
|
.tog .reg + .reg { border-left: 1px solid #ffffff26; }
|
|
|
|
|
.tog .reg.active.bcdn { background: rgba(214,255,61,.18); color: #d6ff3d; }
|
|
|
|
|
.tog .reg.active.icann { background: rgba(76,158,255,.18); color: #4c9eff; }
|
|
|
|
|
.tog .reg.switch { background: transparent; color: #7b8494; cursor: pointer; }
|
|
|
|
|
.tog .reg.switch:hover { color: #e5e7eb; background: rgba(255,255,255,.05); }
|
2026-07-30 22:00:01 +02:00
|
|
|
.star { border: none; background: transparent; cursor: pointer; color: var(--dim); font-size: 15px; padding: 2px 4px; border-radius: 6px; }
|
|
|
|
|
.star:hover { background: var(--line2); color: var(--ink); } .star.on { color: #ffd23d; }
|
|
|
|
|
/* search box: engine icon (click = pick engine) + a wide typing area */
|
|
|
|
|
.searchbox { display: flex; align-items: center; width: 300px; background: var(--surface); border: 1px solid var(--line);
|
|
|
|
|
border-radius: 999px; padding: 0 12px 0 3px; }
|
|
|
|
|
.searchbox:focus-within { border-color: #4b7bec; }
|
2026-08-01 00:13:14 +02:00
|
|
|
/* dropdown button shows the SELECTED engine's favicon (emoji fallback) + a caret */
|
|
|
|
|
.engine-btn { display: flex; align-items: center; gap: 3px; height: 30px; border: none; cursor: pointer;
|
|
|
|
|
border-radius: 999px 0 0 999px; padding: 0 4px 0 8px; background: transparent; }
|
2026-07-30 22:55:52 +02:00
|
|
|
.engine-btn:hover { background-color: var(--hover); }
|
2026-08-01 00:13:14 +02:00
|
|
|
.engine-btn #engineIcon { display: grid; place-items: center; width: 16px; height: 16px; }
|
|
|
|
|
.engine-btn .eimg { width: 16px; height: 16px; border-radius: 3px; }
|
|
|
|
|
.engine-btn .esym { font-size: 14px; line-height: 1; }
|
|
|
|
|
.engine-btn .cv { font-size: 8px; color: var(--dim); }
|
2026-07-30 22:00:01 +02:00
|
|
|
#search { flex: 1; padding: 7px 4px; }
|
|
|
|
|
.tor { padding: 7px 10px; border-radius: 8px; border: 1px solid var(--line); background: #2b2f3a; color: var(--ink); cursor: pointer; white-space: nowrap; font-size: 12.5px; }
|
2026-07-29 13:54:34 +02:00
|
|
|
.tor.connecting { background: #7a5c14; } .tor.on { background: #6b3fa0; }
|
2026-07-30 08:16:55 +02:00
|
|
|
.logo { display: flex; align-items: center; gap: 6px; font-weight: 700; color: #d6ff3d; white-space: nowrap;
|
2026-07-30 19:29:32 +02:00
|
|
|
padding: 6px 12px; border: 1px solid #d6ff3d33; border-radius: 8px; cursor: pointer; background: rgba(214,255,61,.08); font-size: 13px; }
|
2026-07-30 08:16:55 +02:00
|
|
|
.logo:hover { background: rgba(214,255,61,.16); }
|
|
|
|
|
.logo .gear { font-size: 12px; opacity: .8; }
|
2026-07-30 19:29:32 +02:00
|
|
|
/* favorites bar — new-tab page only, single compact row */
|
|
|
|
|
.bookmarks { display: flex; align-items: center; gap: 4px; padding: 3px 10px 5px; height: 26px; overflow: hidden; }
|
2026-07-30 08:16:55 +02:00
|
|
|
.bm { display: flex; align-items: center; gap: 6px; max-width: 180px; padding: 3px 9px; border-radius: 6px;
|
2026-07-30 22:00:01 +02:00
|
|
|
background: var(--surface2); border: 1px solid var(--hover); color: var(--mut); font-size: 12px; cursor: pointer; white-space: nowrap; }
|
|
|
|
|
.bm:hover { background: var(--active); color: var(--ink); }
|
2026-07-30 19:29:32 +02:00
|
|
|
.bm .bt { overflow: hidden; text-overflow: ellipsis; }
|
2026-07-30 08:16:55 +02:00
|
|
|
.bm .bx { opacity: 0; cursor: pointer; font-size: 11px; } .bm:hover .bx { opacity: .55; }
|
|
|
|
|
.bm .bx:hover { opacity: 1; color: #f6768a; }
|
2026-07-30 22:00:01 +02:00
|
|
|
.bm-empty { color: var(--faint); font-size: 11.5px; white-space: nowrap; }
|
2026-07-29 13:54:34 +02:00
|
|
|
.tordisc { font-size: 11.5px; color: #d9c7f2; background: #2a1c40; border-top: 1px solid #6b3fa055; padding: 5px 14px; }
|
|
|
|
|
.tordisc a { color: #d6ff3d; }
|
2026-07-30 22:00:01 +02:00
|
|
|
.bcnrbar { display: flex; align-items: center; gap: 10px; font-size: 11.5px; color: var(--mut);
|
2026-07-29 13:54:34 +02:00
|
|
|
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; }
|
2026-07-30 22:00:01 +02:00
|
|
|
.bcnrbar b { color: var(--ink); }
|
2026-07-29 13:54:34 +02:00
|
|
|
.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); }
|
2026-07-30 22:00:01 +02:00
|
|
|
.bcnrbar .bdismiss { color: var(--dim); text-decoration: none; } .bcnrbar .bdismiss:hover { color: var(--ink); }
|
2026-07-29 13:54:34 +02:00
|
|
|
</style></head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="tabs" id="tabs"></div>
|
|
|
|
|
<div class="bar">
|
|
|
|
|
<div class="nav">
|
2026-07-30 08:16:55 +02:00
|
|
|
<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>
|
2026-07-29 13:54:34 +02:00
|
|
|
</div>
|
|
|
|
|
<div class="urlwrap">
|
2026-07-30 19:29:32 +02:00
|
|
|
<button class="secbadge" id="secbadge" title="Site information">
|
2026-08-02 17:46:03 +02:00
|
|
|
<!-- Heraldic shield outline matching the reference image: flared top
|
|
|
|
|
shoulders, concave sides tapering to a sharp bottom point. Single
|
|
|
|
|
filled path, `currentColor` so state class toggles the tint. -->
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true">
|
|
|
|
|
<path d="M8 1.2 C6 1.2 3.5 1.7 2.5 2.3 Q2 2.6 2 3.5 L2 7 C2 11.2 4.2 13.7 8 15 C11.8 13.7 14 11.2 14 7 L14 3.5 Q14 2.6 13.5 2.3 C12.5 1.7 10 1.2 8 1.2 Z"/>
|
|
|
|
|
</svg>
|
2026-07-30 08:16:55 +02:00
|
|
|
</button>
|
2026-07-30 20:58:45 +02:00
|
|
|
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
|
2026-07-29 13:54:34 +02:00
|
|
|
<span class="reg" id="reg" hidden></span>
|
2026-07-30 08:16:55 +02:00
|
|
|
<button class="star" id="star" title="Save this page">☆</button>
|
2026-07-29 13:54:34 +02:00
|
|
|
</div>
|
2026-07-30 22:00:01 +02:00
|
|
|
<div class="searchbox">
|
2026-08-01 00:13:14 +02:00
|
|
|
<button id="engineBtn" class="engine-btn" title="Choose search engine"><span id="engineIcon"></span><span class="cv">▾</span></button>
|
2026-07-30 22:00:01 +02:00
|
|
|
<input id="search" placeholder="Search" spellcheck="false">
|
|
|
|
|
</div>
|
2026-08-02 13:02:47 +02:00
|
|
|
<button class="ic dlbtn" id="downloads" title="Downloads">
|
|
|
|
|
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 2 V10 M4.5 6.5 L8 10 L11.5 6.5"/><path d="M3 12.5 L3 13.5 L13 13.5 L13 12.5"/></svg>
|
|
|
|
|
<span class="dlbadge" id="dlbadge" hidden>0</span>
|
|
|
|
|
</button>
|
2026-07-29 13:54:34 +02:00
|
|
|
<button class="tor" id="tor" title="Route traffic through Tor">🧅 Tor: Off</button>
|
2026-07-30 08:16:55 +02:00
|
|
|
<button class="logo" id="logo" title="Settings"><span>⛓ Theseus</span><span class="gear">⚙</span></button>
|
2026-07-29 13:54:34 +02:00
|
|
|
</div>
|
2026-07-30 19:29:32 +02:00
|
|
|
<div class="loadbar" id="loadbar"></div>
|
2026-07-30 08:16:55 +02:00
|
|
|
<div class="bookmarks" id="bookmarks" hidden></div>
|
2026-07-29 13:54:34 +02:00
|
|
|
<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>
|
2026-08-02 13:02:47 +02:00
|
|
|
<span class="breg" id="bcnrreg">BCDN</span>
|
|
|
|
|
<span><b id="bcnrhost"></b> also exists on <b>BCDN</b> — the decentralized registry.</span>
|
|
|
|
|
<button id="bcnropen" class="bopen">Open on BCDN</button>
|
2026-07-29 13:54:34 +02:00
|
|
|
<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)); }
|
2026-07-30 19:29:32 +02:00
|
|
|
const RELOAD_SVG = `<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>`;
|
|
|
|
|
const STOP_SVG = `<svg viewBox="0 0 16 16"><path d="M4.5 4.5 L11.5 11.5 M11.5 4.5 L4.5 11.5"/></svg>`;
|
2026-07-30 08:16:55 +02:00
|
|
|
|
2026-08-02 17:46:03 +02:00
|
|
|
function goURL() {
|
|
|
|
|
const v = $("url").value.trim();
|
|
|
|
|
if (!v) return;
|
|
|
|
|
// Blur so the tabs-event handler can update the URL bar to the actual
|
|
|
|
|
// loaded page — the "don't clobber the typed text" guard would otherwise
|
|
|
|
|
// keep the search keyword displayed while the results are loading.
|
|
|
|
|
$("url").blur();
|
|
|
|
|
T.navigate(v);
|
|
|
|
|
}
|
2026-07-29 13:54:34 +02:00
|
|
|
$("url").addEventListener("keydown", (e) => { if (e.key === "Enter") goURL(); });
|
Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:
- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
(+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
|
|
|
// Enter submits the search; keep the query visible so the user can refine
|
|
|
|
|
// it or search again — clearing it on submit lost context and made refining
|
|
|
|
|
// annoying (esp. when the engine's own results page uses its own search box).
|
|
|
|
|
$("search").addEventListener("keydown", (e) => { if (e.key === "Enter") { const q = $("search").value.trim(); if (q) T.search(q); } });
|
2026-07-29 13:54:34 +02:00
|
|
|
$("back").onclick = () => T.back();
|
|
|
|
|
$("fwd").onclick = () => T.forward();
|
|
|
|
|
$("reload").onclick = () => T.reload();
|
|
|
|
|
$("home").onclick = () => T.goHome();
|
2026-07-30 19:29:32 +02:00
|
|
|
$("logo").onclick = () => T.openSettings();
|
|
|
|
|
$("secbadge").onclick = () => { const r = $("secbadge").getBoundingClientRect(); T.toggleSiteInfo({ x: Math.round(r.left), y: Math.round(r.bottom + 4) }); };
|
2026-07-29 13:54:34 +02:00
|
|
|
|
2026-07-30 22:55:52 +02:00
|
|
|
// ---- search-engine picker: the magnifier button opens a custom overlay
|
|
|
|
|
// dropdown (real favicons); here we only track the current engine's name. ----
|
|
|
|
|
function setEnginePlaceholder(d) {
|
|
|
|
|
const cur = (d.engines || []).find((e) => e.id === d.current);
|
|
|
|
|
$("search").placeholder = cur ? "Search with " + cur.name : "Search";
|
2026-08-01 00:13:14 +02:00
|
|
|
const icon = $("engineIcon");
|
|
|
|
|
const sym = (cur && cur.sym) || "🔍";
|
|
|
|
|
if (cur && cur.favicon)
|
|
|
|
|
icon.innerHTML = `<img class="eimg" src="${cur.favicon.replace(/"/g, """)}" onerror="this.replaceWith(Object.assign(document.createElement('span'),{className:'esym',textContent:'${sym}'}))">`;
|
|
|
|
|
else icon.innerHTML = `<span class="esym">${sym}</span>`;
|
2026-07-30 20:58:45 +02:00
|
|
|
}
|
2026-07-30 22:55:52 +02:00
|
|
|
$("engineBtn").onclick = () => {
|
|
|
|
|
const r = $("engineBtn").getBoundingClientRect();
|
|
|
|
|
T.toggleEnginePicker({ x: Math.round(r.left), y: Math.round(r.bottom + 4) });
|
2026-07-30 20:58:45 +02:00
|
|
|
};
|
2026-07-30 22:55:52 +02:00
|
|
|
T.getSearchEngines().then(setEnginePlaceholder);
|
|
|
|
|
T.onEngines && T.onEngines(setEnginePlaceholder);
|
2026-07-30 20:58:45 +02:00
|
|
|
|
2026-08-02 13:02:47 +02:00
|
|
|
// ---- downloads button: badge + click toggles the panel ----
|
|
|
|
|
const dlBtn = $("downloads"), dlBadge = $("dlbadge");
|
|
|
|
|
function renderDownloads(items) {
|
|
|
|
|
const list = items || [];
|
|
|
|
|
const active = list.filter((d) => d.state === "progressing").length;
|
|
|
|
|
const doneNew = list.filter((d) => d.state === "completed").length;
|
|
|
|
|
const failed = list.filter((d) => d.state === "interrupted").length;
|
|
|
|
|
dlBtn.classList.toggle("spin", active > 0);
|
|
|
|
|
dlBtn.classList.toggle("done", active === 0 && doneNew > 0 && failed === 0);
|
|
|
|
|
dlBtn.classList.toggle("err", active === 0 && failed > 0);
|
|
|
|
|
const badge = active || (doneNew + failed);
|
|
|
|
|
if (badge > 0) { dlBadge.hidden = false; dlBadge.textContent = badge > 99 ? "99+" : String(badge); }
|
|
|
|
|
else { dlBadge.hidden = true; }
|
|
|
|
|
}
|
|
|
|
|
dlBtn.onclick = () => {
|
|
|
|
|
const r = dlBtn.getBoundingClientRect();
|
|
|
|
|
T.toggleDownloads({ x: Math.round(r.right - 340), y: Math.round(r.bottom + 4) });
|
|
|
|
|
};
|
|
|
|
|
T.getDownloads && T.getDownloads().then(renderDownloads);
|
|
|
|
|
T.onDownloads && T.onDownloads(renderDownloads);
|
|
|
|
|
|
2026-07-30 19:29:32 +02:00
|
|
|
// ---- favorites bar (new-tab page only) ----
|
|
|
|
|
let current = { url: "", title: "" }, bookmarks = [];
|
2026-07-30 08:16:55 +02:00
|
|
|
function renderBookmarks() {
|
|
|
|
|
const box = $("bookmarks");
|
2026-07-30 19:29:32 +02:00
|
|
|
box.innerHTML = bookmarks.length
|
|
|
|
|
? 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("")
|
|
|
|
|
: `<span class="bm-empty">No saved pages yet — click ☆ in the address bar to save one.</span>`;
|
2026-07-30 08:16:55 +02:00
|
|
|
box.querySelectorAll(".bm").forEach((el) => el.onclick = (e) => {
|
|
|
|
|
const i = Number(el.dataset.i);
|
2026-07-30 19:29:32 +02:00
|
|
|
if (e.target.dataset.x !== undefined) T.removeBookmark(bookmarks[i].url);
|
2026-07-30 08:16:55 +02:00
|
|
|
else T.navigate(bookmarks[i].url);
|
|
|
|
|
});
|
2026-07-30 19:29:32 +02:00
|
|
|
updateStar(); syncHeight();
|
2026-07-30 08:16:55 +02:00
|
|
|
}
|
|
|
|
|
function updateStar() {
|
2026-07-30 19:29:32 +02:00
|
|
|
const saved = !!current.url && bookmarks.some((b) => b.url === current.url);
|
2026-07-30 08:16:55 +02:00
|
|
|
const s = $("star"); s.textContent = saved ? "★" : "☆"; s.classList.toggle("on", saved);
|
|
|
|
|
s.title = saved ? "Remove from saved pages" : "Save this page";
|
2026-07-29 13:54:34 +02:00
|
|
|
}
|
2026-07-30 08:16:55 +02:00
|
|
|
$("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(); });
|
2026-07-29 13:54:34 +02:00
|
|
|
|
2026-07-30 19:29:32 +02:00
|
|
|
// ---- security badge + minimal registry indicator ----
|
2026-08-02 17:46:03 +02:00
|
|
|
// Shield colour communicates connection state at a glance:
|
|
|
|
|
// neutral — home / resolving (no site OR pending)
|
|
|
|
|
// secure — BCDN chain-verified (kind:"ok") OR https:// clearnet
|
|
|
|
|
// insecure — nxdomain / resolver error / plain http:// clearnet
|
2026-07-30 08:16:55 +02:00
|
|
|
function setBadge(d) {
|
|
|
|
|
const b = $("secbadge");
|
2026-07-30 19:29:32 +02:00
|
|
|
let cls = "secbadge", title = "Site information";
|
2026-08-02 17:46:03 +02:00
|
|
|
const isHttps = typeof d?.url === "string" && d.url.startsWith("https:");
|
|
|
|
|
const isHttp = typeof d?.url === "string" && d.url.startsWith("http:") && !isHttps;
|
2026-07-30 19:29:32 +02:00
|
|
|
if (!d || d.kind === "home") title = "Theseus";
|
2026-08-02 17:46:03 +02:00
|
|
|
else if (d.kind === "ok") { cls = "secbadge secure"; title = "Secure · BCDN via " + (d.source || "on-chain") + " — click for details"; }
|
|
|
|
|
else if (d.kind === "web" && isHttps) { cls = "secbadge secure"; title = "Secure · ICANN (HTTPS) — click for details"; }
|
|
|
|
|
else if (d.kind === "web" && isHttp) { cls = "secbadge insecure"; title = "Not secure · plain HTTP — click for details"; }
|
2026-08-02 13:02:47 +02:00
|
|
|
else if (d.kind === "web") title = "Connection · ICANN — click for details";
|
2026-07-30 08:16:55 +02:00
|
|
|
else if (d.kind === "resolving") title = "Resolving…";
|
2026-08-02 17:46:03 +02:00
|
|
|
else if (d.kind === "nxdomain") { cls = "secbadge insecure"; title = "Not registered on BCDN"; }
|
|
|
|
|
else if (d.kind === "error") { cls = "secbadge insecure"; title = "Resolution error"; }
|
2026-07-30 08:16:55 +02:00
|
|
|
b.className = cls; b.title = title;
|
|
|
|
|
}
|
2026-07-30 19:29:32 +02:00
|
|
|
function setReg(d) {
|
|
|
|
|
const r = $("reg");
|
2026-08-02 14:43:31 +02:00
|
|
|
if (!d || d.kind === "home") { r.hidden = true; r.innerHTML = ""; return; }
|
|
|
|
|
// Keep chips visible during "resolving" so the switch feels like a toggle,
|
|
|
|
|
// not a disappear-and-reappear (user complaint 2026-08-02).
|
|
|
|
|
if (d.kind === "resolving") { r.hidden = false; return; }
|
2026-07-30 19:29:32 +02:00
|
|
|
r.hidden = false;
|
2026-08-02 14:43:31 +02:00
|
|
|
// Two chips (toggle) when this host is a collision candidate — could be
|
|
|
|
|
// BCDN OR ICANN. Active one is coloured; inactive is greyed + clickable.
|
2026-08-02 13:02:47 +02:00
|
|
|
const isCand = !!d.tld && (d.kind === "ok" || d.kind === "web") && d.bcnrNativeTld === false;
|
|
|
|
|
const active = d.kind === "ok" ? "bcdn" : d.kind === "web" ? "icann" : null;
|
|
|
|
|
if (isCand) {
|
2026-08-02 14:43:31 +02:00
|
|
|
const bcdn = `<span class="reg bcdn ${active === "bcdn" ? "active" : "switch"}" data-c="bcdn" title="${active === "bcdn" ? "Serving from BCDN" : "Switch to BCDN"}">BCDN</span>`;
|
|
|
|
|
const icann = `<span class="reg icann ${active === "icann" ? "active" : "switch"}" data-c="icann" title="${active === "icann" ? "Serving from ICANN" : "Switch to ICANN"}">ICANN</span>`;
|
2026-08-02 19:26:47 +02:00
|
|
|
r.innerHTML = `<span class="tog">${bcdn}${icann}</span>`;
|
2026-08-02 13:02:47 +02:00
|
|
|
for (const chip of r.querySelectorAll(".reg.switch")) {
|
|
|
|
|
chip.onclick = () => T.collisionSwitch({ choice: chip.dataset.c, remember: "no" });
|
|
|
|
|
}
|
|
|
|
|
} else if (d.kind === "web") {
|
2026-08-02 19:26:47 +02:00
|
|
|
r.innerHTML = `<span class="reg single icann active">ICANN</span>`;
|
2026-08-02 13:02:47 +02:00
|
|
|
} else {
|
2026-08-02 19:26:47 +02:00
|
|
|
r.innerHTML = `<span class="reg single bcdn active">BCDN</span>`;
|
2026-08-02 13:02:47 +02:00
|
|
|
}
|
2026-07-30 19:29:32 +02:00
|
|
|
}
|
2026-07-30 08:16:55 +02:00
|
|
|
|
|
|
|
|
// ---- Tor ----
|
2026-07-29 13:54:34 +02:00
|
|
|
let torShown = false;
|
|
|
|
|
$("tor").onclick = () => { T.toggleTor(); if (!torShown) { $("tordisc").hidden = false; torShown = true; syncHeight(); } };
|
|
|
|
|
$("tordismiss").onclick = (e) => { e.preventDefault(); $("tordisc").hidden = true; syncHeight(); };
|
|
|
|
|
|
2026-07-30 08:16:55 +02:00
|
|
|
// ---- passive BCNR offer ----
|
2026-07-29 13:54:34 +02:00
|
|
|
T.onBcnrOffer((d) => {
|
|
|
|
|
const bar = $("bcnrbar");
|
2026-08-02 13:02:47 +02:00
|
|
|
if (d && d.host) { $("bcnrhost").textContent = d.host; $("bcnrreg").textContent = "BCDN"; bar.hidden = false; }
|
2026-07-30 08:16:55 +02:00
|
|
|
else bar.hidden = true;
|
2026-07-29 13:54:34 +02:00
|
|
|
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";
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-30 08:16:55 +02:00
|
|
|
// ---- tabs ----
|
2026-07-29 13:54:34 +02:00
|
|
|
T.onTabs((d) => {
|
|
|
|
|
$("back").disabled = !d.canBack; $("fwd").disabled = !d.canForward;
|
2026-07-30 19:29:32 +02:00
|
|
|
// loading indicator: progress bar + reload↔stop button
|
|
|
|
|
$("loadbar").classList.toggle("on", !!d.loading);
|
|
|
|
|
const rb = $("reload");
|
|
|
|
|
if (d.loading) { rb.innerHTML = STOP_SVG; rb.title = "Stop"; rb.onclick = () => T.stop(); }
|
|
|
|
|
else { rb.innerHTML = RELOAD_SVG; rb.title = "Reload"; rb.onclick = () => T.reload(); }
|
2026-07-29 13:54:34 +02:00
|
|
|
if (document.activeElement !== $("url")) $("url").value = d.url || "";
|
2026-07-30 08:16:55 +02:00
|
|
|
current.url = d.url || "";
|
|
|
|
|
const active = d.tabs.find((t) => t.active);
|
|
|
|
|
current.title = active ? active.title : "";
|
|
|
|
|
updateStar();
|
2026-07-29 13:54:34 +02:00
|
|
|
const box = $("tabs");
|
|
|
|
|
box.innerHTML = d.tabs.map((t) =>
|
2026-07-30 19:29:32 +02:00
|
|
|
`<div class="tab ${t.active ? "active" : ""}" data-id="${t.id}">${t.loading ? '<span class="spin"></span>' : ''}<span class="t">${(t.title||"New Tab").replace(/</g,"<")}</span><span class="x" data-close="${t.id}">✕</span></div>`).join("") +
|
2026-07-29 13:54:34 +02:00
|
|
|
`<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();
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-30 08:16:55 +02:00
|
|
|
// ---- provenance ----
|
2026-07-29 13:54:34 +02:00
|
|
|
T.onNav((d) => {
|
2026-07-30 19:29:32 +02:00
|
|
|
setBadge(d); setReg(d);
|
2026-07-29 13:54:34 +02:00
|
|
|
if (document.activeElement !== $("url")) {
|
|
|
|
|
if (d.kind === "home") $("url").value = "";
|
|
|
|
|
else if (d.host && !$("url").value) $("url").value = d.host;
|
|
|
|
|
}
|
2026-07-30 19:29:32 +02:00
|
|
|
// Favorites bar shows only on the new-tab / home page (hides once a page loads).
|
2026-07-30 08:16:55 +02:00
|
|
|
$("bookmarks").hidden = !(!d || d.kind === "home");
|
|
|
|
|
syncHeight();
|
2026-07-29 13:54:34 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
syncHeight();
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|