Sirius Studio only looked in its own folder, bns/<name>/. Every site published the conventional way lives at bns/<label>/ (the s3 record), or inline in the h record, so the editor opened those names as "new site" with a template picker instead of the current build. The gateway's site read gains a read-only ?src=live view that follows the on-chain record: list and fetch from whatever bucket s3 points at, or serve the h HTML as index.html. Writes are unchanged and still confined to bns/<name>/. The editor now starts from the live page when there is no draft: relative image and link URLs are resolved against the public site URL, stylesheets are inlined (cross-origin ones such as font CDNs are kept as links and re-emitted on export), scripts dropped. When a draft exists but the name serves something else, or index.html on Sia is newer than the draft, a notice says so with a one-click import. An "Import live site" button is always available. Verified with aloevera.test, which is hosted at bns/aloevera/.
314 lines
16 KiB
HTML
314 lines
16 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Register a TLD — Sirius.X</title>
|
||
<meta name="description" content="Mint a top-level domain on Bitcoin Cash. Any wallet can register a TLD; second-level names can then be sold under it.">
|
||
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml">
|
||
<link rel="mask-icon" href="./assets/favicon.svg" color="#d6ff3d">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:site_name" content="Sirius.X">
|
||
<meta property="og:title" content="Register a TLD on Bitcoin Cash">
|
||
<meta property="og:description" content="Mint a TLD certificate. One BCH transaction, no registrar. Renewals are per-TLD policy.">
|
||
<meta property="og:url" content="https://silentmode.st/sirius-x/tld.html">
|
||
<meta property="og:image" content="https://silentmode.st/sirius-x/brand/banner.svg">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500;9..144,600;9..144,700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#050810; --panel:#141a24; --panel2:#0a0f1c; --line:rgba(255,255,255,.06);
|
||
--sky-mid:#0a0f1c; --sky-rim:#131a2a;
|
||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||
--chain-native:#0AC18E;
|
||
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
||
--serif:'Fraunces',Georgia,serif;
|
||
--sans:'DM Sans','Inter',system-ui,sans-serif;
|
||
--mono:'JetBrains Mono',ui-monospace,monospace;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html{scroll-behavior:smooth}
|
||
body{margin:0;
|
||
background:radial-gradient(ellipse 1200px 600px at 50% -5%,var(--sky-rim) 0%,transparent 60%),
|
||
radial-gradient(ellipse 900px 500px at 100% 100%,var(--sky-mid) 0%,transparent 55%),
|
||
radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),
|
||
var(--bg);
|
||
background-attachment:fixed;
|
||
color:var(--ink);font:16px/1.6 var(--sans);min-height:100vh;font-feature-settings:"ss01","ss02"}
|
||
h1,h2,h3{font-family:var(--serif);font-feature-settings:"ss01","ss02"}
|
||
code,.mono{font-family:var(--mono)}
|
||
html[data-font-cmp="ubuntu"] body,
|
||
html[data-font-cmp="ubuntu"] h1, html[data-font-cmp="ubuntu"] h2, html[data-font-cmp="ubuntu"] h3,
|
||
html[data-font-cmp="ubuntu"] p, html[data-font-cmp="ubuntu"] a, html[data-font-cmp="ubuntu"] button,
|
||
html[data-font-cmp="ubuntu"] span, html[data-font-cmp="ubuntu"] div,
|
||
html[data-font-cmp="ubuntu"] label, html[data-font-cmp="ubuntu"] input, html[data-font-cmp="ubuntu"] li{
|
||
font-family:'Ubuntu',system-ui,sans-serif !important
|
||
}
|
||
html[data-font-cmp="ubuntu"] code, html[data-font-cmp="ubuntu"] .mono{font-family:var(--mono) !important}
|
||
.font-picker{display:inline-flex;align-items:center;gap:2px;padding:2px;margin:0 10px;
|
||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:999px;
|
||
font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase}
|
||
.font-picker button{background:transparent;border:none;color:var(--dim);
|
||
padding:4px 10px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:inherit;
|
||
letter-spacing:inherit;text-transform:inherit;transition:color .12s}
|
||
.font-picker button:hover{color:var(--ink)}
|
||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||
a{color:var(--acid)}
|
||
.wrap{max-width:820px;margin:0 auto;padding:0 1.2rem}
|
||
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
|
||
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
|
||
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
|
||
.topnav .brand .g{color:var(--acid)}
|
||
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
|
||
.topnav a:hover{color:var(--ink);background:var(--panel)}
|
||
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
|
||
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
|
||
.topnav a.portal:hover{background:rgba(214,255,61,.18)}
|
||
header.hero{text-align:center;padding:3.4rem 1.2rem 1rem}
|
||
.mark{font-size:44px;line-height:1}
|
||
h1{font-size:clamp(1.7rem,4.5vw,2.3rem);margin:.3rem 0 .3rem}
|
||
h1 .g{color:var(--acid)}
|
||
.tag{color:var(--mut);font-size:1rem;max-width:600px;margin:0 auto}
|
||
h2{font-size:1.25rem;margin:0 0 .4rem}
|
||
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:2rem}
|
||
section:first-of-type{border-top:none;margin-top:1rem}
|
||
.btn{display:inline-block;text-decoration:none;padding:10px 18px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit}
|
||
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
|
||
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
|
||
.btn:hover{filter:brightness(1.12)}
|
||
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
|
||
.muted{color:var(--mut);font-size:13px}
|
||
.dim{color:var(--dim);font-size:12.5px}
|
||
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px;word-break:break-all}
|
||
.badge{display:inline-block;font-size:11px;padding:2px 10px;border-radius:999px;font-weight:600}
|
||
.b-ok{background:rgba(79,209,165,.15);color:var(--ok)}
|
||
.b-taken{background:rgba(246,118,138,.15);color:var(--taken)}
|
||
.b-check{background:rgba(139,152,169,.14);color:var(--mut)}
|
||
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:12px}
|
||
.status.ok{border-left:3px solid var(--ok)}
|
||
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
|
||
.status.warn{border-left:3px solid var(--warn);color:var(--warn)}
|
||
/* TLD search cell */
|
||
.search{max-width:600px;margin:1.5rem auto 0}
|
||
.searchrow{display:flex;gap:8px}
|
||
.searchrow input{flex:1;padding:14px 18px;font-size:17px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink);font-family:inherit;outline:none}
|
||
.searchrow input:focus{border-color:#4b7bec}
|
||
.searchhint{font-size:12.5px;color:var(--mut);margin-top:8px;text-align:center}
|
||
.result-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:14px 18px;margin-top:12px}
|
||
.result-card .lbl{font-family:ui-monospace,monospace;font-size:18px;color:var(--ink)}
|
||
.result-card .lbl .dot{color:var(--acid)}
|
||
/* Registered-TLDs table */
|
||
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
|
||
table.tlds th,table.tlds td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top}
|
||
table.tlds th{color:var(--mut);font-weight:500;font-size:11.5px;text-transform:uppercase;letter-spacing:.4px}
|
||
table.tlds .tld{font-family:ui-monospace,monospace;color:var(--acid);font-size:13.5px;font-weight:600}
|
||
table.tlds .cat{font-family:ui-monospace,monospace;color:var(--mut);font-size:11.5px;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.price, table.tlds .price{
|
||
color:var(--acid);font-family:ui-monospace,monospace;font-size:13px;font-weight:600;
|
||
padding:2px 8px;border-radius:6px;background:rgba(214,255,61,.10);white-space:nowrap;display:inline-block
|
||
}
|
||
.result-card{grid-template-columns:1fr auto auto auto !important}
|
||
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||
</style>
|
||
<script type="importmap">
|
||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||
</script>
|
||
<script src="./js/i18n.js?v=20260920b"></script>
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="topnav">
|
||
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
||
<a href="./#try">Try it</a>
|
||
<a href="./#search-input">✏️ Register</a>
|
||
<a href="./tld.html" class="here">🌐 TLD</a>
|
||
<a href="./market.html">🛒 Market</a>
|
||
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
||
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
||
</nav>
|
||
|
||
<header class="hero">
|
||
<div class="mark">🌐</div>
|
||
<h1>Register a <span class="g">TLD</span></h1>
|
||
<p class="tag">A TLD is a certificate on the Bitcoin Cash chain. You mint it, you own it — any
|
||
second-level name registered under it lives in your TLD's registry. One BCH transaction,
|
||
no ICANN. Renewals? Your TLD, your policy.</p>
|
||
</header>
|
||
|
||
<div class="wrap">
|
||
|
||
<section id="search-section">
|
||
<h2>Search a TLD</h2>
|
||
<p class="muted">Type a label (a–z, 0–9, hyphen; up to 16 chars). Available TLDs get a Register
|
||
button that opens the shared wallet-choice modal — the same flow name registration uses.
|
||
As TLD owner you earn <b style="color:var(--ok)">90 %</b> of every name registered under
|
||
your TLD. Renewals are your call — set them per TLD, or don't.</p>
|
||
<div class="search">
|
||
<div class="searchrow">
|
||
<input id="q" placeholder="your-tld" spellcheck="false" autocomplete="off" autofocus maxlength="16">
|
||
</div>
|
||
<div class="searchhint">Register minting to your token address; you become the registry operator.</div>
|
||
</div>
|
||
<div id="result"></div>
|
||
</section>
|
||
|
||
<section id="listing">
|
||
<h2>Registered TLDs</h2>
|
||
<p class="muted">Fetched from the on-chain TLD registry via the public gateway. Refresh the page for the latest.</p>
|
||
<div id="tlds-status" class="status">Loading…</div>
|
||
<table class="tlds" id="tlds-table" style="display:none">
|
||
<thead><tr><th>TLD</th><th>Mint price</th><th>Category</th></tr></thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<footer id="site-footer"></footer>
|
||
|
||
<script>
|
||
// Simple oracle stub: static chipnet rate for testing. Same number the
|
||
// portal uses. Mainnet swaps for a real price feed.
|
||
const CHIPNET_SATS_PER_USD = 250_000;
|
||
const DEFAULT_USD = 5;
|
||
|
||
const $ = (id) => document.getElementById(id);
|
||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||
const validLabel = (s) => /^[a-z0-9-]{1,16}$/.test(s);
|
||
const normLabel = (s) => String(s).toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 16);
|
||
|
||
// ---------- registered-TLD list ----------
|
||
let registeredTlds = null;
|
||
async function loadTldList() {
|
||
const status = $("tlds-status"), tbody = $("tlds-table").querySelector("tbody");
|
||
try {
|
||
const r = await fetch("https://navigate.st/api/tlds");
|
||
if (!r.ok) throw new Error("api " + r.status);
|
||
const j = await r.json();
|
||
const tlds = (j.tlds || []).map((t) => (typeof t === "string" ? { tld: t } : t));
|
||
tlds.sort((a, b) => a.tld.localeCompare(b.tld));
|
||
tbody.innerHTML = tlds.map((t) => {
|
||
const p = window.siriusPricing?.priceForTld(t.tld);
|
||
const priceCell = p ? `<span class="price" title="${esc(p.tier)}">${esc(window.siriusPricing.formatUsd(p.usd))}</span>` : "—";
|
||
return `<tr>
|
||
<td class="tld">.${esc(t.tld)}</td>
|
||
<td class="price-cell">${priceCell}</td>
|
||
<td class="cat">${esc(t.certificate || t.category || "")}</td>
|
||
</tr>`;
|
||
}).join("");
|
||
$("tlds-table").style.display = "";
|
||
status.className = "status ok";
|
||
status.textContent = `${tlds.length} TLDs registered on chipnet.`;
|
||
registeredTlds = tlds.map((t) => t.tld);
|
||
refreshSearch();
|
||
} catch (e) {
|
||
status.className = "status err";
|
||
status.textContent = "Could not load TLD list: " + (e.message || e);
|
||
}
|
||
}
|
||
loadTldList();
|
||
|
||
// ---------- search result renderer ----------
|
||
function renderResult(label, state) {
|
||
const res = $("result");
|
||
if (!label) { res.innerHTML = ""; return; }
|
||
if (state === "checking") {
|
||
res.innerHTML = `<div class="result-card">
|
||
<div class="lbl"><span class="dot">.</span>${esc(label)}</div>
|
||
<span class="badge b-check">checking…</span><span></span></div>`;
|
||
return;
|
||
}
|
||
if (state === "taken") {
|
||
res.innerHTML = `<div class="result-card">
|
||
<div class="lbl"><span class="dot">.</span>${esc(label)}</div>
|
||
<span class="badge b-taken">taken</span><span></span></div>
|
||
<p class="dim" style="margin-top:8px">Someone already minted <code>.${esc(label)}</code>. Try a
|
||
different label — TLDs are first-come, first-served.</p>`;
|
||
return;
|
||
}
|
||
const p = window.siriusPricing?.priceForTld(label);
|
||
const priceBadge = p
|
||
? `<span class="price" title="${esc(p.tier)}">${esc(window.siriusPricing.formatUsd(p.usd))}</span>`
|
||
: "";
|
||
res.innerHTML = `<div class="result-card">
|
||
<div class="lbl"><span class="dot">.</span>${esc(label)}</div>
|
||
<span class="badge b-ok">available</span>
|
||
${priceBadge}
|
||
<button class="btn acid" id="go-register">Register .${esc(label)} →</button>
|
||
</div>`;
|
||
$("go-register").onclick = () => beginRegister(label);
|
||
}
|
||
|
||
// ---------- search wiring ----------
|
||
let currentLabel = "";
|
||
function refreshSearch() {
|
||
const raw = normLabel($("q").value);
|
||
currentLabel = raw;
|
||
if (!raw) { renderResult("", null); return; }
|
||
if (!validLabel(raw)) { renderResult(raw, "invalid"); return; }
|
||
if (registeredTlds !== null) {
|
||
renderResult(raw, registeredTlds.includes(raw) ? "taken" : "ok");
|
||
return;
|
||
}
|
||
renderResult(raw, "checking");
|
||
}
|
||
$("q").addEventListener("input", () => {
|
||
$("q").value = normLabel($("q").value);
|
||
refreshSearch();
|
||
});
|
||
$("q").addEventListener("keydown", (e) => {
|
||
if (e.key === "Enter" && currentLabel && registeredTlds && !registeredTlds.includes(currentLabel)) {
|
||
beginRegister(currentLabel);
|
||
}
|
||
});
|
||
|
||
// ---------- mint delegation ----------
|
||
// Register button hands the label off to the shared modal (register-flow.js
|
||
// — window.siriusRegisterTld). That modal owns the wallet-choice, fund,
|
||
// confirm, and mint steps — same UX as name registration.
|
||
function beginRegister(label) {
|
||
// Label-based tier pricing lives in siriusPricing (js/pricing.js) so
|
||
// landing / tld / mint agree. Fall back to the flat DEFAULT_USD if the
|
||
// pricing module hasn't loaded for some reason.
|
||
const p = window.siriusPricing?.priceForTld(label);
|
||
const fee = p ? p.sats : BigInt(DEFAULT_USD * CHIPNET_SATS_PER_USD);
|
||
const call = () => window.siriusRegisterTld(label, { serviceFeeSats: fee });
|
||
if (typeof window.siriusRegisterTld === "function") { call(); return; }
|
||
// Module still loading — retry until it lands.
|
||
const btn = $("go-register");
|
||
const orig = btn?.textContent;
|
||
if (btn) { btn.disabled = true; btn.textContent = "loading…"; }
|
||
const t = setInterval(() => {
|
||
if (typeof window.siriusRegisterTld === "function") {
|
||
clearInterval(t);
|
||
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
||
call();
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
// ---------- ?q= deep-link (from landing links etc.) ----------
|
||
(() => {
|
||
const q = normLabel(new URLSearchParams(location.search).get("q") || "");
|
||
if (!q) return;
|
||
$("q").value = q;
|
||
const wait = setInterval(() => {
|
||
if (registeredTlds === null) return;
|
||
clearInterval(wait);
|
||
refreshSearch();
|
||
setTimeout(() => $("q").scrollIntoView({ behavior: "smooth", block: "start" }), 100);
|
||
}, 150);
|
||
})();
|
||
</script>
|
||
|
||
<script type="module" src="./js/register-flow.js?v=20260917bal"></script>
|
||
<script src="./js/pricing.js?v=20260917market"></script>
|
||
<script src="./js/session.js?v=20260917dash"></script>
|
||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||
<script defer src="./js/theme.js?v=20260910font"></script>
|
||
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||
<script defer src="./js/profile-menu.js?v=20260920m"></script>
|
||
</body>
|
||
</html>
|