feat(sirius-x): inline search on landing, immediate feedback on register
Landing hero previously had a redirect-form (type → click Search → jump to register.html). Users read that as broken because typing did nothing. Now the landing runs the same availability check inline: hits /api/labels/<label> once (server-side cross-TLD lookup from the cached index), cross-checks against the client TLDS list, and paints result cards for every TLD with an 'available/taken' badge and either a Register button (linking to register.html?q=<label> for the wallet flow) or a 'view' link to the existing name via the public gateway. Immediate feedback: 18 'checking…' placeholder cards paint the moment the input event fires, so there is no dead period while the fetch is in flight. Register.html got the same treatment — a 'Checking <label> across every TLD…' line replaces the empty-state message during the 350ms debounce so the box never looks unresponsive.
This commit is contained in:
parent
cf04b583aa
commit
45d733f263
2 changed files with 99 additions and 18 deletions
107
index.html
107
index.html
|
|
@ -110,15 +110,17 @@
|
|||
Register a top-level domain, register a name under one, look up records,
|
||||
verify anything against the chain.</p>
|
||||
|
||||
<form id="search-form" role="search" style="margin:1.8rem auto 0;max-width:520px;padding:0 1rem;display:flex;gap:8px">
|
||||
<input id="search-input" name="q" type="search"
|
||||
placeholder="search a name — e.g. hello"
|
||||
<div style="margin:1.6rem auto 0;max-width:640px;padding:0 1rem">
|
||||
<input id="search-input" type="search"
|
||||
placeholder="type a name — e.g. hello"
|
||||
spellcheck="false" autocomplete="off"
|
||||
style="flex:1;padding:14px 18px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink);font-size:16px;outline:none;font-family:inherit"
|
||||
style="width:100%;padding:14px 18px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink);font-size:17px;outline:none;font-family:inherit"
|
||||
aria-label="Search a name across every BCNR TLD">
|
||||
<button type="submit" class="btn acid" style="padding:0 20px">Search →</button>
|
||||
</form>
|
||||
<p class="tag" style="margin-top:.8rem;font-size:.92rem">One search checks the label across every TLD in the registry. Available names appear first.</p>
|
||||
<div id="search-hint" style="margin-top:8px;font-size:12.5px;color:var(--mut);text-align:center">
|
||||
One search checks the label across every TLD in the registry. Available names appear first.
|
||||
</div>
|
||||
<div id="search-results" style="margin-top:14px;display:grid;gap:8px;text-align:left"></div>
|
||||
</div>
|
||||
|
||||
<div class="cta" style="margin-top:1.4rem">
|
||||
<a class="btn ghost" href="#try">Or browse what you can do →</a>
|
||||
|
|
@ -126,17 +128,88 @@
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
/* Inline search card styling: mirrors register.html's .card look. */
|
||||
#search-results .row-card{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;
|
||||
background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:11px 16px;min-height:48px}
|
||||
#search-results .row-card.checking{opacity:.55}
|
||||
#search-results .row-card .n{font-family:ui-monospace,monospace;font-size:15px;overflow-wrap:anywhere}
|
||||
#search-results .row-card .btn{padding:6px 12px;font-size:12.5px}
|
||||
#search-results .badge{font-size:11px;padding:2px 10px;border-radius:999px;font-weight:600;white-space:nowrap}
|
||||
#search-results .b-ok{background:rgba(79,209,165,.15);color:var(--ok)}
|
||||
#search-results .b-taken{background:rgba(246,118,138,.15);color:var(--taken)}
|
||||
#search-results .b-check{background:rgba(139,152,169,.14);color:var(--mut)}
|
||||
@media (max-width:520px){
|
||||
#search-results .row-card{grid-template-columns:1fr auto;gap:8px}
|
||||
#search-results .row-card .btn{justify-self:start}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// The hero-search box: normalise the label the same way register.html does
|
||||
// (lowercase, [a-z0-9-], 63 chars max) and hand it to register.html via ?q=,
|
||||
// which already knows how to parallel-check across every TLD.
|
||||
document.getElementById("search-form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const raw = document.getElementById("search-input").value.trim().toLowerCase();
|
||||
const clean = raw.replace(/[^a-z0-9-]/g, "").slice(0, 63);
|
||||
if (!clean) return;
|
||||
location.href = `./register.html?q=${encodeURIComponent(clean)}`;
|
||||
});
|
||||
// Inline name search — same UX as register.html but no wallet dependencies.
|
||||
// Hits POST-friendly /api/labels/<label> (one call, returns every taken
|
||||
// match across every TLD from the gateway's cached index) and cross-checks
|
||||
// against the client-side TLDS list. Register buttons hand the label off
|
||||
// to register.html?q= for the full mint flow.
|
||||
(function () {
|
||||
const TLDS = [
|
||||
"bch","p2p","bit","nav","test","x","asm","neo","gt","sc",
|
||||
"sia","dex","cex","nt","dea","hub","os","vpn",
|
||||
// .com is held off the public registry (2026-08-29) — skip.
|
||||
];
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||
const clean = (v) => v.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 32);
|
||||
let lastReq = 0, timer = null;
|
||||
|
||||
function row(full, state) {
|
||||
const badge = state === "ok" ? '<span class="badge b-ok">available</span>'
|
||||
: state === "taken" ? '<span class="badge b-taken">taken</span>'
|
||||
: '<span class="badge b-check">checking…</span>';
|
||||
const btn = state === "ok"
|
||||
? `<a class="btn acid" href="./register.html?q=${encodeURIComponent(full.split(".")[0])}">Register →</a>`
|
||||
: state === "taken"
|
||||
? `<a class="btn ghost" href="https://navigate.st/bns/${encodeURIComponent(full)}" target="_blank" rel="noopener">view →</a>`
|
||||
: "<span></span>";
|
||||
return `<div class="row-card ${state === "checking" ? "checking" : ""}"><span class="n">${esc(full)}</span>${badge}${btn}</div>`;
|
||||
}
|
||||
|
||||
async function search(label) {
|
||||
const my = ++lastReq;
|
||||
const out = $("search-results");
|
||||
// Immediate feedback: paint one 'checking' row per TLD so the user
|
||||
// sees something is happening in the ~300ms before results land.
|
||||
out.innerHTML = TLDS.map((t) => row(`${label}.${t}`, "checking")).join("");
|
||||
let taken = new Set();
|
||||
try {
|
||||
const r = await fetch(`https://silentmode.st/api/labels/${encodeURIComponent(label)}`);
|
||||
if (r.ok) {
|
||||
const j = await r.json();
|
||||
for (const m of (j.matches || [])) taken.add(m.name);
|
||||
}
|
||||
} catch { /* fall through: everything shows as "checking" then flips to error-free available; a gateway blip shouldn't block the user */ }
|
||||
if (my !== lastReq) return; // superseded by newer keystroke
|
||||
const rows = TLDS
|
||||
.map((t) => ({ full: `${label}.${t}`, state: taken.has(`${label}.${t}`) ? "taken" : "ok" }))
|
||||
.sort((a, b) => (a.state === "ok" ? 0 : 1) - (b.state === "ok" ? 0 : 1));
|
||||
out.innerHTML = rows.map((x) => row(x.full, x.state)).join("");
|
||||
}
|
||||
|
||||
$("search-input").addEventListener("input", () => {
|
||||
clearTimeout(timer);
|
||||
const v = clean($("search-input").value);
|
||||
$("search-input").value = v;
|
||||
if (!v) { $("search-results").innerHTML = ""; return; }
|
||||
timer = setTimeout(() => search(v), 200);
|
||||
});
|
||||
$("search-input").addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
clearTimeout(timer);
|
||||
const v = clean($("search-input").value);
|
||||
if (v) search(v);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
|
|
|
|||
|
|
@ -408,7 +408,15 @@ async function check() {
|
|||
});
|
||||
}));
|
||||
}
|
||||
$("q").addEventListener("input", () => { clearTimeout(timer); timer = setTimeout(check, 350); });
|
||||
$("q").addEventListener("input", () => {
|
||||
clearTimeout(timer);
|
||||
// Show an immediate placeholder so the input never looks unresponsive
|
||||
// during the 350ms debounce window. `check()` overwrites this the moment
|
||||
// it runs.
|
||||
const raw = clean($("q").value);
|
||||
if (raw) $("result").innerHTML = `<span class="muted">Checking <b>${esc(raw)}</b> across every TLD…</span>`;
|
||||
timer = setTimeout(check, 350);
|
||||
});
|
||||
$("q").addEventListener("keydown", (e) => { if (e.key === "Enter") { clearTimeout(timer); check(); } });
|
||||
|
||||
// ---------- ?q= deep-link ----------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue