fix(sirius-x): profile-menu URLs, landing search, empty-wallet hint, drop Pantheon

Five UX fixes from a review pass:

1. profile-menu.js: dropdown links (New/Add/WC wallet, Admin) were
   absolute /sirius-x/portal.html paths. That's fine on silentmode.st
   but under the BCNR route (sirius.x/) the origin is different, so
   the gateway forwarded to Sia which returned 'NoSuchKey' XML. Now
   derive the base URL from the nav's own .portal anchor href — which
   is already set per page with the right relative path — so it works
   from the root, from subdirs, and under any BCNR gateway. Admin URL
   is derived from the same base.

2. Landing hero got a search input. Submitting it normalises the
   label ([a-z0-9-], 63 chars) and redirects to register.html?q=<label>.
   register.html now honours ?q= on load: prefills the input, triggers
   the parallel multi-TLD lookup, and scrolls into view. Single source
   of truth stays in register.html; the landing just hands it a query.

3. Portal TLD-mint error path now special-cases 'wallet is empty' and
   shows a friendlier message with the wallet's address and links to
   two chipnet faucets, so the fix is one click away instead of a
   guess.

4. Removed 'Pantheon' from every page's top nav — it's a section on
   the landing that anyone scrolling will discover, and keeping it in
   the nav crowded the bar.

5. Cache-buster ?v=20260907rel on the profile-menu.js script include
   across all 7 pages so browsers that cached the pre-fix version
   pick up the new one on next load.
This commit is contained in:
Local Dev 2026-09-07 20:32:14 +02:00
parent 3e57b1bb31
commit 1cca60c4df
8 changed files with 77 additions and 19 deletions

View file

@ -85,7 +85,6 @@
<a href="../docs/">📚 Docs</a> <a href="../docs/">📚 Docs</a>
<a href="../theseus/">🧭 Theseus</a> <a href="../theseus/">🧭 Theseus</a>
<a href="../register.html">🪪 Register</a> <a href="../register.html">🪪 Register</a>
<a href="../#pantheon">Pantheon</a>
<a href="./" class="here">🛠 Admin</a> <a href="./" class="here">🛠 Admin</a>
<a href="../portal.html" class="portal">🔑 Sign in</a> <a href="../portal.html" class="portal">🔑 Sign in</a>
</nav> </nav>
@ -303,6 +302,6 @@ $("mint-btn").addEventListener("click", async () => {
} }
}); });
</script> </script>
<script defer src="../js/profile-menu.js"></script> <script defer src="../js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -222,6 +222,6 @@
<a href="https://silentmode.st/">silentmode.st</a> <a href="https://silentmode.st/">silentmode.st</a>
</footer> </footer>
<script defer src="../js/profile-menu.js"></script> <script defer src="../js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -429,6 +429,6 @@ process.exit(0);"</pre>
<a href="../register.html">register a name</a> <a href="../register.html">register a name</a>
</footer> </footer>
<script defer src="../js/profile-menu.js"></script> <script defer src="../js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -96,7 +96,6 @@
<a href="./docs/">📚 Docs</a> <a href="./docs/">📚 Docs</a>
<a href="./theseus/">🧭 Theseus</a> <a href="./theseus/">🧭 Theseus</a>
<a href="./register.html">🪪 Register</a> <a href="./register.html">🪪 Register</a>
<a href="#pantheon">Pantheon</a>
<a href="#roadmap">Roadmap</a> <a href="#roadmap">Roadmap</a>
<a href="./brand/">🎨 Brand</a> <a href="./brand/">🎨 Brand</a>
<a href="https://silentmode.st/" rel="noopener">Silent Mode ↗</a> <a href="https://silentmode.st/" rel="noopener">Silent Mode ↗</a>
@ -108,15 +107,37 @@
<h1>Names on the <span class="g">chain</span>.</h1> <h1>Names on the <span class="g">chain</span>.</h1>
<p class="tag">Sirius.X is the entry point for BCNR — Bitcoin Cash Name Registry. <p class="tag">Sirius.X is the entry point for BCNR — Bitcoin Cash Name Registry.
Register a top-level domain, register a name under one, look up records, Register a top-level domain, register a name under one, look up records,
verify anything against the chain. Backed by Sia storage, mirrored on the verify anything against the chain.</p>
operator's VPS, name published on chain — this page survives if any single
mirror goes down.</p> <form id="search-form" role="search" style="margin:1.8rem auto 0;max-width:520px;padding:0 1rem;display:flex;gap:8px">
<div class="cta"> <input id="search-input" name="q" type="search"
<a class="btn acid" href="#try">Get started</a> placeholder="search a name — e.g. hello"
<a class="btn ghost" href="#docs">Read the docs</a> 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"
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 class="cta" style="margin-top:1.4rem">
<a class="btn ghost" href="#try">Or browse what you can do →</a>
<a class="btn ghost" href="./docs/">Read the docs</a>
</div> </div>
</header> </header>
<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)}`;
});
</script>
<div class="wrap"> <div class="wrap">
<section id="try"> <section id="try">
@ -312,6 +333,6 @@ process.exit(0);"</pre>
Alpha on a test network (chipnet). You hold your own keys; verify everything against the chain. Alpha on a test network (chipnet). You hold your own keys; verify everything against the chain.
</footer> </footer>
<script defer src="./js/profile-menu.js"></script> <script defer src="./js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -9,12 +9,26 @@
(() => { (() => {
const NS = "silentmode.st"; const NS = "silentmode.st";
const PORTAL_URL = "/sirius-x/portal.html";
// Find the .portal anchor already in the nav (the "Sign in" pill). // Find the .portal anchor already in the nav (the "Sign in" pill).
const anchor = document.querySelector(".topnav a.portal"); const anchor = document.querySelector(".topnav a.portal");
if (!anchor) return; // page has no nav-portal button; nothing to do if (!anchor) return; // page has no nav-portal button; nothing to do
// Reuse the anchor's own href as the base for the dropdown links. The nav
// sets it to the right relative path per page (./portal.html at root,
// ../portal.html in subdirs). An absolute /sirius-x/portal.html would
// break under the BCNR route (sirius.x/) — the origin is different, so
// the gateway hits Sia with a key that does not exist and returns
// NoSuchKey. Deriving from the anchor keeps every context correct.
const PORTAL_URL = anchor.getAttribute("href") || "./portal.html";
// Admin lives one directory up-or-over relative to the same root — same
// trick: replace the portal.html basename with admin/ on the resolved
// portal path. If the anchor is a hash or empty for some reason, fall
// back to a same-directory link.
const ADMIN_URL = PORTAL_URL.includes("portal.html")
? PORTAL_URL.replace(/portal\.html.*$/, "admin/")
: "./admin/";
// Read profile state — portal.html sets this after successful sign-in with // Read profile state — portal.html sets this after successful sign-in with
// { address, tokenAddress, signedInAt }; clears on sign-out. The read has to // { address, tokenAddress, signedInAt }; clears on sign-out. The read has to
// run every time the button label refreshes or the menu opens, because // run every time the button label refreshes or the menu opens, because
@ -50,7 +64,7 @@
<code>${escapeAttr(profile.address)}</code> <code>${escapeAttr(profile.address)}</code>
</div> </div>
<a href="${PORTAL_URL}" role="menuitem">📋 My names</a> <a href="${PORTAL_URL}" role="menuitem">📋 My names</a>
<a href="/sirius-x/admin/" role="menuitem">🛠 Admin</a> <a href="${ADMIN_URL}" role="menuitem">🛠 Admin</a>
<hr> <hr>
<a href="#" role="menuitem" data-action="signout">🚪 Sign out</a> <a href="#" role="menuitem" data-action="signout">🚪 Sign out</a>
`; `;

View file

@ -787,8 +787,18 @@ $("tld-submit").addEventListener("click", async () => {
setTimeout(() => loadNames(), 2500); setTimeout(() => loadNames(), 2500);
} catch (e) { } catch (e) {
msg.style.color = "var(--taken)"; msg.style.color = "var(--taken)";
msg.textContent = "Failed: " + (e.message || e); const raw = String(e.message || e);
push("error: " + (e.message || e)); // The most common failure by far: wallet has no chipnet coins. Turn the
// opaque error into a nudge toward a faucet.
if (/wallet is empty/i.test(raw)) {
msg.innerHTML = `Failed: wallet is empty. Fund <code style="font-size:11.5px">${esc(wallet.address)}</code>
with chipnet coins first — free from
<a href="https://tbch.googol.cash/" target="_blank" rel="noopener">tbch.googol.cash</a> or
<a href="https://chipnet.bch.ninja/faucet" target="_blank" rel="noopener">chipnet.bch.ninja/faucet</a>.`;
} else {
msg.textContent = "Failed: " + raw;
}
push("error: " + raw);
$("tld-submit").disabled = false; $("tld-submit").disabled = false;
updateTldPrice(); // reset button label updateTldPrice(); // reset button label
} finally { } finally {
@ -798,6 +808,6 @@ $("tld-submit").addEventListener("click", async () => {
// Initial paint on page load so the card shows the price even before sign-in. // Initial paint on page load so the card shows the price even before sign-in.
updateTldPrice(); updateTldPrice();
</script> </script>
<script defer src="./js/profile-menu.js"></script> <script defer src="./js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -410,6 +410,20 @@ async function check() {
$("q").addEventListener("input", () => { clearTimeout(timer); timer = setTimeout(check, 350); }); $("q").addEventListener("input", () => { clearTimeout(timer); timer = setTimeout(check, 350); });
$("q").addEventListener("keydown", (e) => { if (e.key === "Enter") { clearTimeout(timer); check(); } }); $("q").addEventListener("keydown", (e) => { if (e.key === "Enter") { clearTimeout(timer); check(); } });
// ---------- ?q= deep-link ----------
// The landing page has a search box that submits to register.html?q=<label>,
// so people can search from the sirius.x home without clicking through. If
// the URL carries a `q`, prefill the input and trigger the same lookup.
(() => {
const q = new URLSearchParams(location.search).get("q");
if (!q) return;
const clean = q.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 63);
if (!clean) return;
$("q").value = clean;
// Small delay so the results container is in the layout before we scroll.
setTimeout(() => { check(); $("q").scrollIntoView({ behavior: "smooth", block: "start" }); }, 100);
})();
// ---------- flow state ---------- // ---------- flow state ----------
const state = { name: null, wallet: null, session: null, client: null, quote: null, result: null }; const state = { name: null, wallet: null, session: null, client: null, quote: null, result: null };
@ -856,6 +870,6 @@ function stepDone(){
}; };
} }
</script> </script>
<script defer src="./js/profile-menu.js"></script> <script defer src="./js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>

View file

@ -170,6 +170,6 @@ sha256sum &lt;downloaded-file&gt;.exe</pre>
Alpha, Windows for now. macOS and Linux builds are on the roadmap. Alpha, Windows for now. macOS and Linux builds are on the roadmap.
</footer> </footer>
<script defer src="../js/profile-menu.js"></script> <script defer src="../js/profile-menu.js?v=20260907rel"></script>
</body> </body>
</html> </html>