Three UX fixes based on operator feedback (2026-08-02):
1) 'Open with…' is now a FULL-PAGE in-tab interstitial (was a modal window).
- loadBns loads collision.html via loadFile with query params instead of
opening a BrowserWindow.
- collision.html is navigation-based: buttons redirect to a special
bns://collision-choose/?host=…&choice=…&remember=…&resturl=… URL.
- serveBns handles that URL: persists remember=name/tld choice, then returns
a meta-refresh to the real target. For BCDN it appends ?_collision=bcnr
one-shot marker; for ICANN it redirects to https://<host><path>.
- loadBns strips and honors the ?_collision one-shot marker so BCDN
redirects don't re-trigger the prompt.
- Old modal path (collisionPromptOnce) removed.
2) Auto-switch from the address-bar chip: clicking BCDN/ICANN chip sets a
transient per-tab collisionOverride that loadBns consumes ONCE for this
navigation only, bypassing the soft-mode prompt entirely.
3) Address-bar toggle: chips stay visible during 'resolving' (no more
flash-and-reappear); active chip is colored (acid green for BCDN, blue for
ICANN — matching collision.html's palette); inactive is greyed with border,
clickable to switch. Feels like a proper toggle.
Settings > Registries copy: operator's exact wording adopted (title stays
'Registries', body uses 'BCNR-unique' term for TLDs that only exist on BCNR).
170 lines
7.9 KiB
HTML
170 lines
7.9 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Open with…</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<style>
|
|
:root {
|
|
color-scheme: dark light;
|
|
--bg: #0f1116;
|
|
--fg: #e5e7eb;
|
|
--muted: #9ca3af;
|
|
--border: #2a2f3a;
|
|
--panel: #131722;
|
|
--panel-hover: #181e2b;
|
|
--bcdn: #d6ff3d; /* acid glow green (matches address-bar toggle) */
|
|
--icann: #4c9eff; /* blue (matches address-bar toggle) */
|
|
}
|
|
@media (prefers-color-scheme: light) {
|
|
:root { --bg:#f7f7f8; --fg:#111827; --muted:#5b6472; --border:#e5e7eb; --panel:#ffffff; --panel-hover:#f0f2f5; --bcdn:#7fa900; --icann:#1f6fd4; }
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html, body { margin: 0; min-height: 100%; }
|
|
body {
|
|
background: var(--bg); color: var(--fg);
|
|
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
padding: 40px 24px;
|
|
}
|
|
main { width: 100%; max-width: 720px; }
|
|
header { margin-bottom: 28px; }
|
|
h1 { margin: 0 0 8px; font-size: 22px; font-weight: 600; letter-spacing: -.01em; }
|
|
.sub { color: var(--muted); font-size: 14px; }
|
|
.host { color: var(--fg); font-weight: 600; }
|
|
|
|
.choices { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
|
|
@media (max-width: 620px) { .choices { grid-template-columns: 1fr; } }
|
|
|
|
.choice {
|
|
background: var(--panel); border: 2px solid var(--border); border-radius: 12px;
|
|
padding: 22px; color: var(--fg); text-align: left; cursor: pointer;
|
|
transition: border-color .12s, background .12s, transform .06s;
|
|
display: flex; flex-direction: column; gap: 8px;
|
|
}
|
|
.choice:hover { background: var(--panel-hover); }
|
|
.choice:active { transform: scale(.99); }
|
|
.choice.sel.bcdn { border-color: var(--bcdn); box-shadow: 0 0 0 1px var(--bcdn), 0 0 22px -8px var(--bcdn); }
|
|
.choice.sel.icann { border-color: var(--icann); box-shadow: 0 0 0 1px var(--icann), 0 0 22px -10px var(--icann); }
|
|
|
|
.choice .head { display: flex; align-items: center; gap: 12px; }
|
|
.choice .radio {
|
|
flex: none; width: 18px; height: 18px; border-radius: 50%;
|
|
border: 2px solid var(--border); position: relative;
|
|
}
|
|
.choice.sel.bcdn .radio { border-color: var(--bcdn); background: radial-gradient(circle, var(--bcdn) 40%, transparent 42%); }
|
|
.choice.sel.icann .radio { border-color: var(--icann); background: radial-gradient(circle, var(--icann) 40%, transparent 42%); }
|
|
.choice .icon { font-size: 22px; line-height: 1; }
|
|
.choice .title { font-weight: 600; font-size: 16px; }
|
|
.choice.bcdn .title { color: var(--bcdn); }
|
|
.choice.icann .title { color: var(--icann); }
|
|
.choice .desc { font-size: 13.5px; color: var(--muted); }
|
|
|
|
fieldset { border: 0; padding: 0; margin: 0 0 28px; display: flex; flex-direction: column; gap: 10px; }
|
|
fieldset legend {
|
|
color: var(--muted); font-size: 12px; padding: 0; margin-bottom: 8px;
|
|
text-transform: uppercase; letter-spacing: .06em;
|
|
}
|
|
fieldset .row { display: flex; align-items: center; gap: 10px; font-size: 14px; padding: 3px 0; }
|
|
fieldset .row input { accent-color: var(--bcdn); width: 16px; height: 16px; }
|
|
fieldset .row label { color: var(--fg); cursor: pointer; }
|
|
|
|
.actions { display: flex; justify-content: flex-end; gap: 12px; }
|
|
button.act {
|
|
border: 1px solid var(--border); background: var(--panel); color: var(--fg);
|
|
padding: 11px 22px; border-radius: 8px; font-size: 14px; cursor: pointer;
|
|
transition: background .1s, filter .1s;
|
|
}
|
|
button.act:hover { background: var(--panel-hover); }
|
|
button.act.primary { background: var(--bcdn); color: #111; border-color: var(--bcdn); font-weight: 600; }
|
|
button.act.primary:hover { filter: brightness(1.06); }
|
|
kbd { border: 1px solid var(--border); border-radius: 3px; padding: 0 5px; font: 11px monospace; color: var(--muted); margin-left: 6px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<header>
|
|
<h1><span class="host" id="host"></span> exists in two registries</h1>
|
|
<div class="sub">Choose which one to open. You can change this later in Settings.</div>
|
|
</header>
|
|
|
|
<div class="choices">
|
|
<div class="choice bcdn sel" data-choice="bcnr" tabindex="0">
|
|
<div class="head">
|
|
<span class="radio"></span><span class="icon">⛓</span>
|
|
<span class="title">BCNR / BCDN</span>
|
|
</div>
|
|
<div class="desc">On-chain registration. Owned by the holder of a digital name certificate. The registry served by the blockchain root.</div>
|
|
</div>
|
|
<div class="choice icann" data-choice="icann" tabindex="0">
|
|
<div class="head">
|
|
<span class="radio"></span><span class="icon">🌐</span>
|
|
<span class="title">ICANN / IANA</span>
|
|
</div>
|
|
<div class="desc">DNS service coordinated by the ICANN. The registry served by the incumbent root.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<fieldset>
|
|
<legend>Remember this choice</legend>
|
|
<div class="row"><input type="radio" name="remember" id="rem-no" value="no" checked><label for="rem-no">Ask again next time</label></div>
|
|
<div class="row"><input type="radio" name="remember" id="rem-name" value="name"><label for="rem-name">Always use for <span class="host" id="host2"></span></label></div>
|
|
<div class="row"><input type="radio" name="remember" id="rem-tld" value="tld"><label for="rem-tld">Always use for all <span id="tld"></span> names</label></div>
|
|
</fieldset>
|
|
|
|
<div class="actions">
|
|
<button class="act" id="cancel">Cancel<kbd>Esc</kbd></button>
|
|
<button class="act primary" id="confirm">Open<kbd>Enter</kbd></button>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
const qs = new URLSearchParams(location.search);
|
|
const host = qs.get("host") || "";
|
|
const tld = qs.get("tld") || "";
|
|
const resturl = qs.get("resturl") || "/";
|
|
document.getElementById("host").textContent = host;
|
|
document.getElementById("host2").textContent = host;
|
|
document.getElementById("tld").textContent = "." + tld;
|
|
document.title = "Open " + host + " with…";
|
|
|
|
let choice = "bcnr";
|
|
const bcdnEl = document.querySelector('.choice[data-choice="bcnr"]');
|
|
const icannEl = document.querySelector('.choice[data-choice="icann"]');
|
|
function select(c) {
|
|
choice = c;
|
|
bcdnEl.classList.toggle("sel", c === "bcnr");
|
|
icannEl.classList.toggle("sel", c === "icann");
|
|
// primary button colour follows the selected choice
|
|
const p = document.getElementById("confirm");
|
|
p.style.background = c === "bcnr"
|
|
? "var(--bcdn)"
|
|
: "var(--icann)";
|
|
p.style.borderColor = p.style.background;
|
|
p.style.color = c === "bcnr" ? "#111" : "#fff";
|
|
}
|
|
select("bcnr"); // initial paint
|
|
for (const el of [bcdnEl, icannEl]) {
|
|
el.addEventListener("click", () => select(el.dataset.choice));
|
|
el.addEventListener("keydown", (e) => { if (e.key === " " || e.key === "Enter") { e.preventDefault(); select(el.dataset.choice); } });
|
|
}
|
|
function remember() { return document.querySelector('input[name="remember"]:checked')?.value || "no"; }
|
|
function confirm() {
|
|
const q = new URLSearchParams({ host, tld, choice, remember: remember(), resturl });
|
|
// Navigate to the special bns:// endpoint that main.js's serveBns intercepts:
|
|
// it applies the remember choice and redirects to the real target.
|
|
location.href = "bns://collision-choose/?" + q.toString();
|
|
}
|
|
function cancel() {
|
|
if (history.length > 1) history.back();
|
|
else location.href = "about:blank";
|
|
}
|
|
document.getElementById("confirm").addEventListener("click", confirm);
|
|
document.getElementById("cancel").addEventListener("click", cancel);
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key === "Escape") cancel();
|
|
if (e.key === "Enter" && e.target.tagName !== "BUTTON") confirm();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|