Two related visibility fixes: 1) Light-mode --acid → #0AC18E (Bitcoin Cash brand primary, from whybitcoincash.com's palette per user). Direct swap from #088A66 (darkened variant) to the on-brand primary. Applied across chrome / settings / error / home / approval / messages / bchwallet / siawallet / screenshot editor. Dark mode's #d6ff3d is unchanged. 2) User-Agent no longer includes 'theseus-navigator/<ver>' or 'Electron/<ver>' tokens. Cloudflare's WAF was returning HTTP 503 'Service Unavailable' to any request carrying those (verified directly against whybitcoincash.com — same URL, same headers, only the UA differed; plain Chrome UA got 200, Theseus UA got 503). Strip both tokens via a stockChromeUA() helper called from applyAcceptLanguage(), which whenReady already invokes at boot. Standard practice: Brave, Vivaldi, Slack all do the same. Verified via CDP: navigator.userAgent now reports Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.191 Safari/537.36 — indistinguishable from stock Chrome.
253 lines
12 KiB
HTML
253 lines
12 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Load error</title>
|
|
<style>
|
|
:root { color-scheme: light dark;
|
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
|
--warn:#ffc75f; --err:#f6768a; --info:#4b7bec; }
|
|
@media (prefers-color-scheme: light) {
|
|
:root { --bg:#f6f8fb; --panel:#ffffff; --panel2:#f0f4f9; --line:rgba(0,0,0,.10);
|
|
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5;
|
|
/* Darker acid for light backgrounds — ~5.5:1 on white. */
|
|
--acid: #0AC18E; }
|
|
}
|
|
* { box-sizing: border-box }
|
|
html, body { margin: 0; height: 100% }
|
|
body {
|
|
background: radial-gradient(1100px 560px at 50% -12%, #16202e, var(--bg));
|
|
color: var(--ink); font: 15px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
display: grid; place-items: center; padding: 2rem 1.2rem;
|
|
}
|
|
.card { max-width: 640px; width: 100%; background: var(--panel); border: 1px solid var(--line);
|
|
border-radius: 14px; padding: 2rem 2.2rem 1.7rem; }
|
|
.head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 1rem; }
|
|
.icon { width: 42px; height: 42px; flex: none; border-radius: 10px; display: grid; place-items: center;
|
|
font-size: 22px; background: rgba(246,118,138,.10); }
|
|
.icon.warn { background: rgba(255,199,95,.10) }
|
|
.icon.info { background: rgba(75,123,236,.10) }
|
|
h1 { font-size: 1.15rem; font-weight: 600; margin: .1rem 0 .1rem; letter-spacing: .1px }
|
|
.sub { color: var(--mut); font-size: 13.5px; margin: 0; }
|
|
.body { color: var(--ink); font-size: 14px; line-height: 1.62; margin: 1rem 0 .6rem; }
|
|
.body b { color: var(--acid); }
|
|
.body .host { font-family: ui-monospace, monospace; background: var(--panel2); padding: 1px 6px;
|
|
border-radius: 5px; color: var(--acid); word-break: break-all; }
|
|
.tried { margin: 1rem 0 0; padding: 12px 14px; background: var(--panel2);
|
|
border: 1px dashed var(--line); border-radius: 10px; font-size: 12.5px; color: var(--mut); }
|
|
.tried .t { display: flex; gap: 8px; align-items: center; padding: 3px 0; }
|
|
.tried .t .m { width: 12px; text-align: center; color: var(--err); }
|
|
.tried .t .m.ok { color: var(--acid) }
|
|
.suggest { margin: 1rem 0 0; padding: 12px 14px; background: var(--panel2);
|
|
border: 1px solid rgba(214,255,61,.25); border-radius: 10px; font-size: 13px; color: var(--ink); }
|
|
.suggest .lbl { color: var(--mut); font-size: 12px; margin-bottom: 6px; }
|
|
.suggest .row { display: flex; flex-wrap: wrap; gap: 6px 8px; }
|
|
.suggest a { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px;
|
|
background: var(--panel); border: 1px solid var(--line); color: var(--acid);
|
|
font-family: ui-monospace, monospace; font-size: 12.5px; text-decoration: none; cursor: pointer; }
|
|
.suggest a:hover { border-color: rgba(214,255,61,.55); filter: brightness(1.05); }
|
|
.actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 1.3rem; }
|
|
.btn { font-family: inherit; font-size: 13.5px; padding: 9px 16px; border-radius: 9px;
|
|
border: 1px solid var(--line); background: var(--panel2); color: var(--ink);
|
|
cursor: pointer; transition: filter .1s, background .1s, border-color .1s; }
|
|
.btn:hover { background: var(--panel); border-color: rgba(214,255,61,.35); filter: brightness(1.05); }
|
|
.btn.primary { background: var(--acid); color: #0b0e14; border-color: transparent; font-weight: 600; }
|
|
.btn.primary:hover { background: var(--acid); filter: brightness(1.08); }
|
|
.details { margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--line);
|
|
font: 11.5px/1.55 ui-monospace, monospace; color: var(--dim); }
|
|
.details .row { display: grid; grid-template-columns: 78px 1fr; gap: 8px; }
|
|
.details .row b { color: var(--mut); font-weight: 500; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<div class="head">
|
|
<div class="icon" id="icon">✕</div>
|
|
<div>
|
|
<h1 id="title">Couldn't load this page</h1>
|
|
<p class="sub" id="sub">The tab kept its address so you can edit and try again.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="body" id="body"></div>
|
|
|
|
<div class="tried" id="tried" hidden>
|
|
<div class="t"><span class="m">✕</span> <span>Bitcoin Cash Domain Names (BCDN) — no record on chain</span></div>
|
|
<div class="t"><span class="m">✕</span> <span>Clearnet DNS — no host at this name either</span></div>
|
|
</div>
|
|
|
|
<div class="suggest" id="suggest" hidden>
|
|
<div class="lbl">Did you mean</div>
|
|
<div class="row" id="suggestRow"></div>
|
|
</div>
|
|
|
|
<div class="actions" id="actions"></div>
|
|
|
|
<div class="details">
|
|
<div class="row"><b>URL</b><span id="dUrl"></span></div>
|
|
<div class="row"><b>Error</b><span id="dErr"></span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const q = new URLSearchParams(location.search);
|
|
let kind = q.get("kind") || "generic"; // offline | name-not-registered | name-unreachable | unreachable | tls | generic
|
|
const host = q.get("host") || "";
|
|
const url = q.get("url") || "";
|
|
const code = q.get("code") || "";
|
|
const desc = q.get("desc") || "";
|
|
|
|
// Offline is a special case: if the browser process itself has no
|
|
// network, every load will fail here and we'd otherwise mis-report
|
|
// BCNR-eligible names as "not registered" (we couldn't reach the
|
|
// beacon to know). Detect it up front so the user sees a clear
|
|
// "you're offline" instead of a false verdict about the name.
|
|
if (typeof navigator !== "undefined" && navigator.onLine === false) kind = "offline";
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
const el = (tag, cls, txt) => { const e = document.createElement(tag); if (cls) e.className = cls; if (txt != null) e.textContent = txt; return e; };
|
|
const hostSpan = (h) => { const s = el("span", "host"); s.textContent = h || "(no host)"; return s; };
|
|
|
|
document.title = host ? "Error — " + host : "Load error";
|
|
$("dUrl").textContent = url || "(none)";
|
|
$("dErr").textContent = (code ? code + " " : "") + (desc || "");
|
|
|
|
// Kind → title, subtitle, body, actions.
|
|
const configs = {
|
|
"offline": {
|
|
icon: "⌀", iconCls: "info",
|
|
title: "You appear to be offline",
|
|
sub: "No network connection detected",
|
|
body: (b) => {
|
|
b.append("This browser can't reach the network. Wi-Fi may be off, the cable may be unplugged, or a VPN / proxy may be dropping traffic. ");
|
|
b.append("Reconnect and press ");
|
|
const kbd = document.createElement("b"); kbd.textContent = "Retry"; b.append(kbd);
|
|
b.append(".");
|
|
if (host) {
|
|
b.append(document.createElement("br"));
|
|
b.append(document.createElement("br"));
|
|
b.append("Trying to open ");
|
|
b.append(hostSpan(host));
|
|
b.append(" — the address is kept so you can retry once you're back online.");
|
|
}
|
|
},
|
|
actions: ["retry", "home"],
|
|
},
|
|
"name-not-registered": {
|
|
icon: "?", iconCls: "warn",
|
|
title: "That name isn't registered",
|
|
sub: "No BCDN record on chain, and no clearnet host at " + host,
|
|
body: (b) => {
|
|
b.append("Nothing answered when we asked for ");
|
|
b.append(hostSpan(host));
|
|
b.append(". Bitcoin Cash Domain Names had no record for it, and ordinary DNS has no host at that name either.");
|
|
b.append(document.createElement("br"));
|
|
b.append(document.createElement("br"));
|
|
b.append("If it's supposed to be yours, register it on the chain. If you were guessing, try a search instead.");
|
|
},
|
|
tried: true,
|
|
actions: ["register", "search", "retry", "home"],
|
|
},
|
|
"name-unreachable": {
|
|
icon: "⌘", iconCls: "warn",
|
|
title: "This name didn't resolve",
|
|
sub: "Your DNS resolver didn't know " + host,
|
|
body: (b) => {
|
|
b.append("We couldn't turn ");
|
|
b.append(hostSpan(host));
|
|
b.append(" into an address. Your network's resolver may be blocking it, offline, or the name simply doesn't exist. BCDN was tried first — if the host is a BCDN name, it's not registered on the chain either.");
|
|
},
|
|
actions: ["retry", "search", "register", "home"],
|
|
},
|
|
"unreachable": {
|
|
icon: "✕", iconCls: "",
|
|
title: "This site is unreachable",
|
|
sub: host ? ("Couldn't connect to " + host) : "The server didn't respond",
|
|
body: (b) => {
|
|
b.append("The name resolved, but the server at ");
|
|
b.append(hostSpan(host));
|
|
b.append(" didn't answer. It may be down, blocking your network, or reachable only through Tor.");
|
|
},
|
|
actions: ["retry", "tor-hint", "home"],
|
|
},
|
|
"tls": {
|
|
icon: "!", iconCls: "warn",
|
|
title: "The site's certificate isn't trustworthy",
|
|
sub: host ? ("HTTPS handshake with " + host + " failed") : "Certificate error",
|
|
body: (b) => {
|
|
b.append("The server at ");
|
|
b.append(hostSpan(host));
|
|
b.append(" presented a certificate the browser couldn't verify. This can mean the certificate has expired, doesn't match the hostname, was self-signed, or that someone is intercepting the connection.");
|
|
},
|
|
actions: ["retry", "home"],
|
|
},
|
|
"generic": {
|
|
icon: "✕", iconCls: "",
|
|
title: "Couldn't load this page",
|
|
sub: host ? ("Something went wrong loading " + host) : "Load failed",
|
|
body: (b) => {
|
|
b.append("Something interrupted the load of ");
|
|
b.append(hostSpan(host));
|
|
b.append(". Details are at the bottom of this page.");
|
|
},
|
|
actions: ["retry", "home"],
|
|
},
|
|
};
|
|
const c = configs[kind] || configs.generic;
|
|
$("icon").textContent = c.icon;
|
|
$("icon").className = "icon" + (c.iconCls ? " " + c.iconCls : "");
|
|
$("title").textContent = c.title;
|
|
$("sub").textContent = c.sub;
|
|
c.body($("body"));
|
|
if (c.tried) $("tried").hidden = false;
|
|
|
|
// Actions — order matters (first is primary). "register" only when we can
|
|
// construct a Sirius URL (need a host).
|
|
const btnDefs = {
|
|
retry: { label: "Retry", primary: true, fn: () => window.errorpage.retry(url) },
|
|
search: { label: "Search for '" + host + "'", primary: false, fn: () => window.errorpage.searchFor(host) },
|
|
register: { label: "Register on Sirius", primary: false, fn: () => window.errorpage.registerOnSirius(host) },
|
|
home: { label: "Home", primary: false, fn: () => window.errorpage.goHome() },
|
|
"tor-hint": {
|
|
label: "Open Tor guide", primary: false,
|
|
fn: () => window.errorpage.openExternal("https://silentmode.st/tools/#tor"),
|
|
},
|
|
};
|
|
const actionsBox = $("actions");
|
|
let primaryUsed = false;
|
|
for (const a of c.actions) {
|
|
const def = btnDefs[a]; if (!def) continue;
|
|
if (a === "register" && !host) continue;
|
|
if (a === "search" && !host) continue;
|
|
const b = el("button", "btn" + (def.primary && !primaryUsed ? " primary" : ""), def.label);
|
|
if (def.primary) primaryUsed = true;
|
|
b.addEventListener("click", def.fn);
|
|
actionsBox.appendChild(b);
|
|
}
|
|
|
|
// "Did you mean" — for BCNR-name failures, ask main for near-matches in
|
|
// the warm shared index (Levenshtein ≤ 2, same TLD). No network — the
|
|
// index is local, so this works offline too. Clicking a suggestion
|
|
// retries the load at that host, preserving the original path if any.
|
|
if ((kind === "name-not-registered" || kind === "name-unreachable") && host && window.errorpage?.bnsSimilar) {
|
|
window.errorpage.bnsSimilar(host).then((matches) => {
|
|
if (!Array.isArray(matches) || !matches.length) return;
|
|
const row = $("suggestRow");
|
|
let origPath = "/";
|
|
try { const u = new URL(url); origPath = u.pathname + u.search + u.hash; } catch {}
|
|
for (const name of matches) {
|
|
const a = el("a", null, name);
|
|
a.href = "#";
|
|
a.addEventListener("click", (ev) => {
|
|
ev.preventDefault();
|
|
window.errorpage.retry("https://" + name + origPath);
|
|
});
|
|
row.appendChild(a);
|
|
}
|
|
$("suggest").hidden = false;
|
|
}).catch(() => {});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|