sites: scroll-spy tracks scroll position, fires the last section on bottom
The IntersectionObserver-based spy only fired when a section entered a narrow band 40–45% down the viewport. Short sections at the tail of a page — Operator key and Roadmap on theseus.x/extensions, whatever came last on the other pages — never made it into that band because the page runs out of scroll before their top crosses 40%, so their sidebar links stayed dim no matter how far the reader scrolled. Rewrote the spy across every page with a sidebar (theseus.x home / plug-ins / extensions and sirius.x home / tld / theseus / docs / admin / brand): it listens to window scroll, picks the last section whose top has crossed 35% of the viewport, and — when the window is scrolled to within 4px of the bottom — snaps to the final section regardless of where its top is. Every link in the rail can now activate. theseus.x/extensions also had the spy IIFE bundled with the manifest loader in one <script> block near the middle of the page, so at run time the parser hadn't yet reached the last three sections and only Bundled/Community/Build were ever registered. Moved the spy to its own <script> just before </body> so it sees every section that ships.
This commit is contained in:
parent
0cb4aec5df
commit
f2c3480d43
20 changed files with 1820 additions and 1630 deletions
913
admin/index.html
913
admin/index.html
|
|
@ -1,451 +1,462 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Admin — Sirius.X</title>
|
||||
<meta name="description" content="Sirius.x operator admin — mint TLDs, manage the registry, verify chain health. Operator-key gated.">
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
|
||||
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:900px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:2.8rem 1.2rem 1rem}
|
||||
.mark{font-size:40px;line-height:1}
|
||||
h1{font-size:clamp(1.5rem,4vw,2rem);margin:.3rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:.98rem;max-width:600px;margin:0 auto}
|
||||
h2{font-size:1.2rem;margin:0 0 .35rem}
|
||||
h3{font-size:.98rem;margin:0 0 .3rem;font-family:ui-monospace,monospace}
|
||||
section{padding:1.8rem 0;border-top:1px solid var(--line);margin-top:1.6rem}
|
||||
section:first-of-type{border-top:none;margin-top:.8rem}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin-top:.8rem}
|
||||
label{display:block;font-size:11.5px;color:var(--mut);margin:12px 0 4px;text-transform:uppercase;letter-spacing:.4px}
|
||||
.field-hint{color:var(--dim);font-size:11px;margin-left:8px;text-transform:none;letter-spacing:0}
|
||||
input[type=text],textarea{
|
||||
width:100%;padding:9px 12px;border-radius:8px;border:1px solid #ffffff22;background:#0e131b;
|
||||
color:var(--ink);font-size:13px;font-family:ui-monospace,monospace;outline:none
|
||||
}
|
||||
textarea{min-height:68px;resize:vertical}
|
||||
input:focus,textarea:focus{border-color:#4b7bec}
|
||||
.btn{display:inline-block;text-decoration:none;padding:9px 16px;border-radius:9px;background:#4b7bec;color:#fff;font-size:13px;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.small{padding:5px 10px;font-size:12px}
|
||||
.btn:hover{filter:brightness(1.12)}
|
||||
.btn:disabled{opacity:.45;cursor:not-allowed}
|
||||
.row{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px;align-items:center}
|
||||
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
|
||||
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:10px}
|
||||
.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)}
|
||||
.hidden{display:none}
|
||||
.badge{display:inline-block;font-size:10.5px;padding:2px 8px;border-radius:999px;font-weight:600;letter-spacing:.3px}
|
||||
.b-op{background:rgba(214,255,61,.14);color:var(--acid)}
|
||||
.b-other{background:rgba(139,152,169,.14);color:var(--mut)}
|
||||
.b-mem{background:rgba(255,199,95,.14);color:var(--warn)}
|
||||
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
|
||||
table.tlds th,table.tlds td{padding:8px 10px;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}
|
||||
table.tlds .records{font-family:ui-monospace,monospace;color:var(--dim);font-size:11.5px;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.steps-log{margin-top:10px;font-family:ui-monospace,monospace;font-size:12px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;max-height:140px;overflow-y:auto;display:none}
|
||||
.steps-log.on{display:block}
|
||||
.steps-log div::before{content:"› ";color:var(--acid)}
|
||||
.msg{font-size:13px;margin-top:10px}
|
||||
.msg.ok{color:var(--ok)} .msg.err{color:var(--taken)}
|
||||
footer{border-top:1px solid var(--line);padding:1.8rem 0 3rem;color:var(--dim);font-size:12.5px;text-align:center;margin-top:2rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</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"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus<span class="g">.X</span></a>
|
||||
<a href="./" class="here">🛠 Admin</a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#signin" title="Sign in" ><span class="ic" aria-hidden="true">🔑</span><span class="lb">Sign in</span></a>
|
||||
<a href="#me" title="Signed in" ><span class="ic" aria-hidden="true">👤</span><span class="lb">Session</span></a>
|
||||
<a href="#mint" title="Mint a new TLD" ><span class="ic" aria-hidden="true">✏️</span><span class="lb">Mint TLD</span></a>
|
||||
<a href="#registry" title="TLD registry" ><span class="ic" aria-hidden="true">📋</span><span class="lb">Registry</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark">🛠</div>
|
||||
<h1><span class="g">Admin</span> panel</h1>
|
||||
<p class="tag">Operator-side tools: mint new TLDs, browse the registry, verify chain health.
|
||||
Sign in with the wallet the ecosystem knows as the operator; unfamiliar wallets get
|
||||
read-only access to the registry.</p>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="signin">
|
||||
<h2>Sign in</h2>
|
||||
<p class="status">Chipnet-only. Keys stay in this browser tab; a mint transaction is signed
|
||||
locally and broadcast direct to the electrum. Operator gate is client-side — the covenant
|
||||
that enforces it on-chain is a mainnet requirement (Decentralized.DNS/DESIGN-tld-registry.md §5.2).</p>
|
||||
<div class="card">
|
||||
<label for="seed">Recovery phrase</label>
|
||||
<textarea id="seed" placeholder="word1 word2 … word12" spellcheck="false" autocomplete="off"></textarea>
|
||||
<div class="row">
|
||||
<button class="btn acid" id="signin-btn">Sign in →</button>
|
||||
<span class="field-hint" id="signin-hint">seed stays in memory only</span>
|
||||
</div>
|
||||
<div id="signin-err" class="msg err hidden"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="me" class="hidden">
|
||||
<h2>Signed in <span class="badge b-op" id="me-role" style="margin-left:8px">operator</span></h2>
|
||||
<div class="card">
|
||||
<label>Address</label>
|
||||
<div><code id="me-addr">—</code></div>
|
||||
<label style="margin-top:12px">Token address (holds the TLD certificates)</label>
|
||||
<div><code id="me-taddr">—</code></div>
|
||||
<div class="row">
|
||||
<button class="btn ghost small" id="signout-btn">Sign out</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mint" class="hidden">
|
||||
<h2>Mint a new TLD</h2>
|
||||
<p class="tag" style="max-width:none">Adds a per-TLD certificate to the TLD beacon. The cert
|
||||
lands in your token address. Cost is chain fees + beacon dust (~1,300 chipnet sats).</p>
|
||||
<div class="card">
|
||||
<label>TLD label <span class="field-hint">1–16 chars, [a-z0-9-]</span></label>
|
||||
<input type="text" id="mint-tld" placeholder="e.g. shop, dao, wiki" maxlength="16">
|
||||
|
||||
<label>Records JSON <span class="field-hint">optional; leave empty for defaults</span></label>
|
||||
<textarea id="mint-records" placeholder='e.g. {"policy":"open","fee_bps":500}'>{}</textarea>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn acid" id="mint-btn">Mint TLD →</button>
|
||||
<span class="field-hint" id="mint-hint"></span>
|
||||
</div>
|
||||
<div class="steps-log" id="mint-log"></div>
|
||||
<div class="msg" id="mint-msg"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="registry">
|
||||
<h2>TLD registry</h2>
|
||||
<p class="tag" style="max-width:none;font-size:.92rem">Every TLD registered on the beacon. Operators
|
||||
(signed-in) can toggle a TLD's visibility on the public /api/tlds listing — hidden TLDs stop
|
||||
appearing in the name-search UIs, but on-chain registrations under them keep resolving.</p>
|
||||
<div class="card" id="op-token-card" style="margin-top:.8rem">
|
||||
<label>Operator token <span class="field-hint">stored per browser tab, not sent anywhere except the /api/tld-visibility POST</span></label>
|
||||
<input type="password" id="op-token" placeholder="paste BNS_OPERATOR_TOKEN">
|
||||
<div class="row">
|
||||
<button class="btn ghost small" id="op-token-save">Save token</button>
|
||||
<span id="op-token-msg" class="dim"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="registry-status">Loading current registry…</div>
|
||||
<div id="registry-table"></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
// Cache-buster bump when the bundle changes (Chrome's ES-module map caches
|
||||
// aggressively per plain URL). Portal.html has the same pattern.
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260908split";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||
|
||||
// Operator gate: address that owns the chipnet TLD beacon + seeded TLDs.
|
||||
// Client-side only — the covenant that enforces this on-chain lands with the
|
||||
// mainnet TLD-registry work. For chipnet this is honour-system; a curious
|
||||
// visitor can bypass by pasting any seed, but non-operator UPDs/TREGs won't
|
||||
// carry the countersignature future clients will check.
|
||||
const OPERATOR_ADDRESS = "bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu";
|
||||
|
||||
let wallet = null;
|
||||
let isOperator = false;
|
||||
|
||||
// ---------- sign in ----------
|
||||
$("signin-btn").addEventListener("click", async () => {
|
||||
const btn = $("signin-btn");
|
||||
const errBox = $("signin-err");
|
||||
errBox.classList.add("hidden");
|
||||
const raw = $("seed").value.trim();
|
||||
if (!raw) { errBox.textContent = "Enter your seed phrase."; errBox.classList.remove("hidden"); return; }
|
||||
btn.disabled = true; btn.textContent = "Unlocking…";
|
||||
try {
|
||||
wallet = await BNS.BuiltInWallet.fromMnemonic(raw);
|
||||
} catch (e) {
|
||||
errBox.textContent = "Could not unlock: " + (e.message || e);
|
||||
errBox.classList.remove("hidden");
|
||||
btn.disabled = false; btn.textContent = "Sign in →";
|
||||
return;
|
||||
}
|
||||
$("seed").value = "";
|
||||
btn.disabled = false; btn.textContent = "Sign in →";
|
||||
isOperator = wallet.address === OPERATOR_ADDRESS;
|
||||
await enterAdmin();
|
||||
});
|
||||
|
||||
$("signout-btn").addEventListener("click", () => {
|
||||
wallet = null; isOperator = false;
|
||||
$("me").classList.add("hidden");
|
||||
$("mint").classList.add("hidden");
|
||||
$("signin").classList.remove("hidden");
|
||||
});
|
||||
|
||||
async function enterAdmin() {
|
||||
$("signin").classList.add("hidden");
|
||||
$("me").classList.remove("hidden");
|
||||
$("me-addr").textContent = wallet.address;
|
||||
$("me-taddr").textContent = wallet.tokenAddress;
|
||||
const role = $("me-role");
|
||||
role.textContent = isOperator ? "operator" : "read-only";
|
||||
role.className = "badge " + (isOperator ? "b-op" : "b-other");
|
||||
if (isOperator) {
|
||||
$("mint").classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- operator token ----------
|
||||
// Stored in sessionStorage so refreshing the tab keeps the token for the
|
||||
// session but a full browser close forgets it. Not localStorage — the
|
||||
// weaker persistence is the right default for a POST-write credential.
|
||||
const opToken = () => { try { return sessionStorage.getItem("bnsOperatorToken") || ""; } catch { return ""; } };
|
||||
function setOpTokenMsg(text, ok) {
|
||||
const el = $("op-token-msg"); el.textContent = text;
|
||||
el.style.color = ok === true ? "var(--ok)" : ok === false ? "var(--taken)" : "var(--dim)";
|
||||
}
|
||||
$("op-token").value = opToken();
|
||||
$("op-token-save").addEventListener("click", () => {
|
||||
try { sessionStorage.setItem("bnsOperatorToken", $("op-token").value.trim()); } catch {}
|
||||
setOpTokenMsg(opToken() ? "saved for this session" : "cleared", opToken() ? true : null);
|
||||
});
|
||||
|
||||
// ---------- TLD registry with hide/unhide ----------
|
||||
// Fetches BOTH /api/tlds?include_hidden=1 (so we see the hidden ones the
|
||||
// public listing filters out) and the current /api/tld-visibility. Renders
|
||||
// one row per TLD with a checkbox for the hidden flag — flipping it POSTs
|
||||
// to /api/tld-visibility with the operator token.
|
||||
async function loadRegistry() {
|
||||
const status = $("registry-status");
|
||||
const table = $("registry-table");
|
||||
status.className = "status";
|
||||
status.textContent = "Loading current registry from the chain…";
|
||||
table.innerHTML = "";
|
||||
try {
|
||||
const [rTlds, rVis] = await Promise.all([
|
||||
fetch("https://silentmode.st/api/tlds?include_hidden=1"),
|
||||
fetch("https://silentmode.st/api/tld-visibility"),
|
||||
]);
|
||||
if (!rTlds.ok) throw new Error("api/tlds " + rTlds.status);
|
||||
if (!rVis.ok) throw new Error("api/tld-visibility " + rVis.status);
|
||||
const body = await rTlds.json();
|
||||
const vis = await rVis.json();
|
||||
const hidden = new Set(vis.hidden || []);
|
||||
const tlds = (body.tlds || []).sort((a, b) => a.tld.localeCompare(b.tld));
|
||||
status.className = "status ok";
|
||||
status.textContent = `${tlds.length} TLDs on the beacon · ${hidden.size} hidden from public listing`;
|
||||
let html = '<table class="tlds"><thead><tr><th>TLD</th><th>Names</th><th>Advertised</th><th>Hidden from public</th></tr></thead><tbody>';
|
||||
for (const t of tlds) {
|
||||
const isHidden = hidden.has(t.tld);
|
||||
html += `<tr>
|
||||
<td class="tld">.${esc(t.tld)}</td>
|
||||
<td class="cat">${t.registered_count ?? 0}</td>
|
||||
<td class="cat">${t.in_advertised ? "yes" : ""}</td>
|
||||
<td><label class="row" style="gap:6px;margin:0;font-size:12.5px;color:var(--mut)">
|
||||
<input type="checkbox" data-hide-tld="${esc(t.tld)}" ${isHidden ? "checked" : ""}>
|
||||
${isHidden ? '<span class="badge b-mem">hidden</span>' : '<span class="dim">public</span>'}
|
||||
</label></td>
|
||||
</tr>`;
|
||||
}
|
||||
html += "</tbody></table>";
|
||||
table.innerHTML = html;
|
||||
// Wire visibility checkboxes.
|
||||
table.querySelectorAll("input[data-hide-tld]").forEach((cb) => {
|
||||
cb.addEventListener("change", async (e) => {
|
||||
const tld = e.target.getAttribute("data-hide-tld");
|
||||
const hide = e.target.checked;
|
||||
if (!opToken()) {
|
||||
e.target.checked = !hide;
|
||||
setOpTokenMsg("paste your operator token first — required for /api/tld-visibility writes", false);
|
||||
return;
|
||||
}
|
||||
e.target.disabled = true;
|
||||
try {
|
||||
const r = await fetch("https://silentmode.st/api/tld-visibility", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", authorization: "Bearer " + opToken() },
|
||||
body: JSON.stringify({ tld, hide }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const j = await r.json().catch(() => ({}));
|
||||
throw new Error(j.error || "http " + r.status);
|
||||
}
|
||||
setOpTokenMsg(`.${tld} is now ${hide ? "hidden" : "public"}`, true);
|
||||
setTimeout(loadRegistry, 400); // refresh row states + counts
|
||||
} catch (err) {
|
||||
e.target.checked = !hide;
|
||||
setOpTokenMsg("update failed: " + err.message, false);
|
||||
} finally {
|
||||
e.target.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
status.className = "status err";
|
||||
status.textContent = "Could not load registry: " + (e.message || e);
|
||||
}
|
||||
}
|
||||
loadRegistry();
|
||||
|
||||
// ---------- mint a new TLD ----------
|
||||
$("mint-btn").addEventListener("click", async () => {
|
||||
if (!wallet || !isOperator) return;
|
||||
const tld = $("mint-tld").value.trim().toLowerCase();
|
||||
if (!/^[a-z0-9-]{1,16}$/.test(tld)) {
|
||||
$("mint-msg").className = "msg err";
|
||||
$("mint-msg").textContent = "TLD label must be 1–16 chars [a-z0-9-]";
|
||||
return;
|
||||
}
|
||||
let records = {};
|
||||
const rawRecords = $("mint-records").value.trim();
|
||||
if (rawRecords) {
|
||||
try { records = JSON.parse(rawRecords); }
|
||||
catch (e) { $("mint-msg").className = "msg err"; $("mint-msg").textContent = "Records JSON is invalid: " + e.message; return; }
|
||||
}
|
||||
const btn = $("mint-btn");
|
||||
const msg = $("mint-msg");
|
||||
const log = $("mint-log");
|
||||
log.className = "steps-log on"; log.innerHTML = "";
|
||||
msg.textContent = ""; msg.className = "msg";
|
||||
const push = (t) => { const d = document.createElement("div"); d.textContent = t; log.appendChild(d); log.scrollTop = log.scrollHeight; };
|
||||
btn.disabled = true; btn.textContent = "Signing…";
|
||||
let el = null;
|
||||
try {
|
||||
el = await BNS.connect();
|
||||
push("connected to chipnet");
|
||||
const res = await BNS.registerTldWithBuiltInWallet(el, {
|
||||
wallet, tld, records,
|
||||
onProgress: (step) => push(step),
|
||||
});
|
||||
push("broadcast: " + (res.txid || res.txId || "(unknown)"));
|
||||
msg.className = "msg ok";
|
||||
msg.textContent = "Minted .${tld}. Category " + (res.category || "unknown") + ". Registry refresh below in ~30s.";
|
||||
btn.textContent = "Mint TLD →"; btn.disabled = false;
|
||||
$("mint-tld").value = "";
|
||||
$("mint-records").value = "{}";
|
||||
setTimeout(loadRegistry, 3000);
|
||||
} catch (e) {
|
||||
msg.className = "msg err";
|
||||
msg.textContent = "Mint failed: " + (e.message || e);
|
||||
push("error: " + (e.message || e));
|
||||
btn.disabled = false; btn.textContent = "Mint TLD →";
|
||||
} finally {
|
||||
try { el?.close?.(); } catch {}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const map = new Map();
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) map.set(el, a); });
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (!en.isIntersecting) return;
|
||||
const a = map.get(en.target); if (!a) return;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
a.classList.add("active");
|
||||
});
|
||||
}, { rootMargin: "-40% 0px -55% 0px", threshold: 0 });
|
||||
map.forEach((_, el) => io.observe(el));
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Admin — Sirius.X</title>
|
||||
<meta name="description" content="Sirius.x operator admin — mint TLDs, manage the registry, verify chain health. Operator-key gated.">
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
|
||||
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:900px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:2.8rem 1.2rem 1rem}
|
||||
.mark{font-size:40px;line-height:1}
|
||||
h1{font-size:clamp(1.5rem,4vw,2rem);margin:.3rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:.98rem;max-width:600px;margin:0 auto}
|
||||
h2{font-size:1.2rem;margin:0 0 .35rem}
|
||||
h3{font-size:.98rem;margin:0 0 .3rem;font-family:ui-monospace,monospace}
|
||||
section{padding:1.8rem 0;border-top:1px solid var(--line);margin-top:1.6rem}
|
||||
section:first-of-type{border-top:none;margin-top:.8rem}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin-top:.8rem}
|
||||
label{display:block;font-size:11.5px;color:var(--mut);margin:12px 0 4px;text-transform:uppercase;letter-spacing:.4px}
|
||||
.field-hint{color:var(--dim);font-size:11px;margin-left:8px;text-transform:none;letter-spacing:0}
|
||||
input[type=text],textarea{
|
||||
width:100%;padding:9px 12px;border-radius:8px;border:1px solid #ffffff22;background:#0e131b;
|
||||
color:var(--ink);font-size:13px;font-family:ui-monospace,monospace;outline:none
|
||||
}
|
||||
textarea{min-height:68px;resize:vertical}
|
||||
input:focus,textarea:focus{border-color:#4b7bec}
|
||||
.btn{display:inline-block;text-decoration:none;padding:9px 16px;border-radius:9px;background:#4b7bec;color:#fff;font-size:13px;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.small{padding:5px 10px;font-size:12px}
|
||||
.btn:hover{filter:brightness(1.12)}
|
||||
.btn:disabled{opacity:.45;cursor:not-allowed}
|
||||
.row{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px;align-items:center}
|
||||
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
|
||||
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:10px}
|
||||
.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)}
|
||||
.hidden{display:none}
|
||||
.badge{display:inline-block;font-size:10.5px;padding:2px 8px;border-radius:999px;font-weight:600;letter-spacing:.3px}
|
||||
.b-op{background:rgba(214,255,61,.14);color:var(--acid)}
|
||||
.b-other{background:rgba(139,152,169,.14);color:var(--mut)}
|
||||
.b-mem{background:rgba(255,199,95,.14);color:var(--warn)}
|
||||
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
|
||||
table.tlds th,table.tlds td{padding:8px 10px;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}
|
||||
table.tlds .records{font-family:ui-monospace,monospace;color:var(--dim);font-size:11.5px;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.steps-log{margin-top:10px;font-family:ui-monospace,monospace;font-size:12px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;max-height:140px;overflow-y:auto;display:none}
|
||||
.steps-log.on{display:block}
|
||||
.steps-log div::before{content:"› ";color:var(--acid)}
|
||||
.msg{font-size:13px;margin-top:10px}
|
||||
.msg.ok{color:var(--ok)} .msg.err{color:var(--taken)}
|
||||
footer{border-top:1px solid var(--line);padding:1.8rem 0 3rem;color:var(--dim);font-size:12.5px;text-align:center;margin-top:2rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</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"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus<span class="g">.X</span></a>
|
||||
<a href="./" class="here">🛠 Admin</a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#signin" title="Sign in" ><span class="ic" aria-hidden="true">🔑</span><span class="lb">Sign in</span></a>
|
||||
<a href="#me" title="Signed in" ><span class="ic" aria-hidden="true">👤</span><span class="lb">Session</span></a>
|
||||
<a href="#mint" title="Mint a new TLD" ><span class="ic" aria-hidden="true">✏️</span><span class="lb">Mint TLD</span></a>
|
||||
<a href="#registry" title="TLD registry" ><span class="ic" aria-hidden="true">📋</span><span class="lb">Registry</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark">🛠</div>
|
||||
<h1><span class="g">Admin</span> panel</h1>
|
||||
<p class="tag">Operator-side tools: mint new TLDs, browse the registry, verify chain health.
|
||||
Sign in with the wallet the ecosystem knows as the operator; unfamiliar wallets get
|
||||
read-only access to the registry.</p>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="signin">
|
||||
<h2>Sign in</h2>
|
||||
<p class="status">Chipnet-only. Keys stay in this browser tab; a mint transaction is signed
|
||||
locally and broadcast direct to the electrum. Operator gate is client-side — the covenant
|
||||
that enforces it on-chain is a mainnet requirement (Decentralized.DNS/DESIGN-tld-registry.md §5.2).</p>
|
||||
<div class="card">
|
||||
<label for="seed">Recovery phrase</label>
|
||||
<textarea id="seed" placeholder="word1 word2 … word12" spellcheck="false" autocomplete="off"></textarea>
|
||||
<div class="row">
|
||||
<button class="btn acid" id="signin-btn">Sign in →</button>
|
||||
<span class="field-hint" id="signin-hint">seed stays in memory only</span>
|
||||
</div>
|
||||
<div id="signin-err" class="msg err hidden"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="me" class="hidden">
|
||||
<h2>Signed in <span class="badge b-op" id="me-role" style="margin-left:8px">operator</span></h2>
|
||||
<div class="card">
|
||||
<label>Address</label>
|
||||
<div><code id="me-addr">—</code></div>
|
||||
<label style="margin-top:12px">Token address (holds the TLD certificates)</label>
|
||||
<div><code id="me-taddr">—</code></div>
|
||||
<div class="row">
|
||||
<button class="btn ghost small" id="signout-btn">Sign out</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mint" class="hidden">
|
||||
<h2>Mint a new TLD</h2>
|
||||
<p class="tag" style="max-width:none">Adds a per-TLD certificate to the TLD beacon. The cert
|
||||
lands in your token address. Cost is chain fees + beacon dust (~1,300 chipnet sats).</p>
|
||||
<div class="card">
|
||||
<label>TLD label <span class="field-hint">1–16 chars, [a-z0-9-]</span></label>
|
||||
<input type="text" id="mint-tld" placeholder="e.g. shop, dao, wiki" maxlength="16">
|
||||
|
||||
<label>Records JSON <span class="field-hint">optional; leave empty for defaults</span></label>
|
||||
<textarea id="mint-records" placeholder='e.g. {"policy":"open","fee_bps":500}'>{}</textarea>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn acid" id="mint-btn">Mint TLD →</button>
|
||||
<span class="field-hint" id="mint-hint"></span>
|
||||
</div>
|
||||
<div class="steps-log" id="mint-log"></div>
|
||||
<div class="msg" id="mint-msg"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="registry">
|
||||
<h2>TLD registry</h2>
|
||||
<p class="tag" style="max-width:none;font-size:.92rem">Every TLD registered on the beacon. Operators
|
||||
(signed-in) can toggle a TLD's visibility on the public /api/tlds listing — hidden TLDs stop
|
||||
appearing in the name-search UIs, but on-chain registrations under them keep resolving.</p>
|
||||
<div class="card" id="op-token-card" style="margin-top:.8rem">
|
||||
<label>Operator token <span class="field-hint">stored per browser tab, not sent anywhere except the /api/tld-visibility POST</span></label>
|
||||
<input type="password" id="op-token" placeholder="paste BNS_OPERATOR_TOKEN">
|
||||
<div class="row">
|
||||
<button class="btn ghost small" id="op-token-save">Save token</button>
|
||||
<span id="op-token-msg" class="dim"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="registry-status">Loading current registry…</div>
|
||||
<div id="registry-table"></div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
|
||||
<script type="module">
|
||||
// Cache-buster bump when the bundle changes (Chrome's ES-module map caches
|
||||
// aggressively per plain URL). Portal.html has the same pattern.
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260908split";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||
|
||||
// Operator gate: address that owns the chipnet TLD beacon + seeded TLDs.
|
||||
// Client-side only — the covenant that enforces this on-chain lands with the
|
||||
// mainnet TLD-registry work. For chipnet this is honour-system; a curious
|
||||
// visitor can bypass by pasting any seed, but non-operator UPDs/TREGs won't
|
||||
// carry the countersignature future clients will check.
|
||||
const OPERATOR_ADDRESS = "bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu";
|
||||
|
||||
let wallet = null;
|
||||
let isOperator = false;
|
||||
|
||||
// ---------- sign in ----------
|
||||
$("signin-btn").addEventListener("click", async () => {
|
||||
const btn = $("signin-btn");
|
||||
const errBox = $("signin-err");
|
||||
errBox.classList.add("hidden");
|
||||
const raw = $("seed").value.trim();
|
||||
if (!raw) { errBox.textContent = "Enter your seed phrase."; errBox.classList.remove("hidden"); return; }
|
||||
btn.disabled = true; btn.textContent = "Unlocking…";
|
||||
try {
|
||||
wallet = await BNS.BuiltInWallet.fromMnemonic(raw);
|
||||
} catch (e) {
|
||||
errBox.textContent = "Could not unlock: " + (e.message || e);
|
||||
errBox.classList.remove("hidden");
|
||||
btn.disabled = false; btn.textContent = "Sign in →";
|
||||
return;
|
||||
}
|
||||
$("seed").value = "";
|
||||
btn.disabled = false; btn.textContent = "Sign in →";
|
||||
isOperator = wallet.address === OPERATOR_ADDRESS;
|
||||
await enterAdmin();
|
||||
});
|
||||
|
||||
$("signout-btn").addEventListener("click", () => {
|
||||
wallet = null; isOperator = false;
|
||||
$("me").classList.add("hidden");
|
||||
$("mint").classList.add("hidden");
|
||||
$("signin").classList.remove("hidden");
|
||||
});
|
||||
|
||||
async function enterAdmin() {
|
||||
$("signin").classList.add("hidden");
|
||||
$("me").classList.remove("hidden");
|
||||
$("me-addr").textContent = wallet.address;
|
||||
$("me-taddr").textContent = wallet.tokenAddress;
|
||||
const role = $("me-role");
|
||||
role.textContent = isOperator ? "operator" : "read-only";
|
||||
role.className = "badge " + (isOperator ? "b-op" : "b-other");
|
||||
if (isOperator) {
|
||||
$("mint").classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- operator token ----------
|
||||
// Stored in sessionStorage so refreshing the tab keeps the token for the
|
||||
// session but a full browser close forgets it. Not localStorage — the
|
||||
// weaker persistence is the right default for a POST-write credential.
|
||||
const opToken = () => { try { return sessionStorage.getItem("bnsOperatorToken") || ""; } catch { return ""; } };
|
||||
function setOpTokenMsg(text, ok) {
|
||||
const el = $("op-token-msg"); el.textContent = text;
|
||||
el.style.color = ok === true ? "var(--ok)" : ok === false ? "var(--taken)" : "var(--dim)";
|
||||
}
|
||||
$("op-token").value = opToken();
|
||||
$("op-token-save").addEventListener("click", () => {
|
||||
try { sessionStorage.setItem("bnsOperatorToken", $("op-token").value.trim()); } catch {}
|
||||
setOpTokenMsg(opToken() ? "saved for this session" : "cleared", opToken() ? true : null);
|
||||
});
|
||||
|
||||
// ---------- TLD registry with hide/unhide ----------
|
||||
// Fetches BOTH /api/tlds?include_hidden=1 (so we see the hidden ones the
|
||||
// public listing filters out) and the current /api/tld-visibility. Renders
|
||||
// one row per TLD with a checkbox for the hidden flag — flipping it POSTs
|
||||
// to /api/tld-visibility with the operator token.
|
||||
async function loadRegistry() {
|
||||
const status = $("registry-status");
|
||||
const table = $("registry-table");
|
||||
status.className = "status";
|
||||
status.textContent = "Loading current registry from the chain…";
|
||||
table.innerHTML = "";
|
||||
try {
|
||||
const [rTlds, rVis] = await Promise.all([
|
||||
fetch("https://silentmode.st/api/tlds?include_hidden=1"),
|
||||
fetch("https://silentmode.st/api/tld-visibility"),
|
||||
]);
|
||||
if (!rTlds.ok) throw new Error("api/tlds " + rTlds.status);
|
||||
if (!rVis.ok) throw new Error("api/tld-visibility " + rVis.status);
|
||||
const body = await rTlds.json();
|
||||
const vis = await rVis.json();
|
||||
const hidden = new Set(vis.hidden || []);
|
||||
const tlds = (body.tlds || []).sort((a, b) => a.tld.localeCompare(b.tld));
|
||||
status.className = "status ok";
|
||||
status.textContent = `${tlds.length} TLDs on the beacon · ${hidden.size} hidden from public listing`;
|
||||
let html = '<table class="tlds"><thead><tr><th>TLD</th><th>Names</th><th>Advertised</th><th>Hidden from public</th></tr></thead><tbody>';
|
||||
for (const t of tlds) {
|
||||
const isHidden = hidden.has(t.tld);
|
||||
html += `<tr>
|
||||
<td class="tld">.${esc(t.tld)}</td>
|
||||
<td class="cat">${t.registered_count ?? 0}</td>
|
||||
<td class="cat">${t.in_advertised ? "yes" : ""}</td>
|
||||
<td><label class="row" style="gap:6px;margin:0;font-size:12.5px;color:var(--mut)">
|
||||
<input type="checkbox" data-hide-tld="${esc(t.tld)}" ${isHidden ? "checked" : ""}>
|
||||
${isHidden ? '<span class="badge b-mem">hidden</span>' : '<span class="dim">public</span>'}
|
||||
</label></td>
|
||||
</tr>`;
|
||||
}
|
||||
html += "</tbody></table>";
|
||||
table.innerHTML = html;
|
||||
// Wire visibility checkboxes.
|
||||
table.querySelectorAll("input[data-hide-tld]").forEach((cb) => {
|
||||
cb.addEventListener("change", async (e) => {
|
||||
const tld = e.target.getAttribute("data-hide-tld");
|
||||
const hide = e.target.checked;
|
||||
if (!opToken()) {
|
||||
e.target.checked = !hide;
|
||||
setOpTokenMsg("paste your operator token first — required for /api/tld-visibility writes", false);
|
||||
return;
|
||||
}
|
||||
e.target.disabled = true;
|
||||
try {
|
||||
const r = await fetch("https://silentmode.st/api/tld-visibility", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", authorization: "Bearer " + opToken() },
|
||||
body: JSON.stringify({ tld, hide }),
|
||||
});
|
||||
if (!r.ok) {
|
||||
const j = await r.json().catch(() => ({}));
|
||||
throw new Error(j.error || "http " + r.status);
|
||||
}
|
||||
setOpTokenMsg(`.${tld} is now ${hide ? "hidden" : "public"}`, true);
|
||||
setTimeout(loadRegistry, 400); // refresh row states + counts
|
||||
} catch (err) {
|
||||
e.target.checked = !hide;
|
||||
setOpTokenMsg("update failed: " + err.message, false);
|
||||
} finally {
|
||||
e.target.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
status.className = "status err";
|
||||
status.textContent = "Could not load registry: " + (e.message || e);
|
||||
}
|
||||
}
|
||||
loadRegistry();
|
||||
|
||||
// ---------- mint a new TLD ----------
|
||||
$("mint-btn").addEventListener("click", async () => {
|
||||
if (!wallet || !isOperator) return;
|
||||
const tld = $("mint-tld").value.trim().toLowerCase();
|
||||
if (!/^[a-z0-9-]{1,16}$/.test(tld)) {
|
||||
$("mint-msg").className = "msg err";
|
||||
$("mint-msg").textContent = "TLD label must be 1–16 chars [a-z0-9-]";
|
||||
return;
|
||||
}
|
||||
let records = {};
|
||||
const rawRecords = $("mint-records").value.trim();
|
||||
if (rawRecords) {
|
||||
try { records = JSON.parse(rawRecords); }
|
||||
catch (e) { $("mint-msg").className = "msg err"; $("mint-msg").textContent = "Records JSON is invalid: " + e.message; return; }
|
||||
}
|
||||
const btn = $("mint-btn");
|
||||
const msg = $("mint-msg");
|
||||
const log = $("mint-log");
|
||||
log.className = "steps-log on"; log.innerHTML = "";
|
||||
msg.textContent = ""; msg.className = "msg";
|
||||
const push = (t) => { const d = document.createElement("div"); d.textContent = t; log.appendChild(d); log.scrollTop = log.scrollHeight; };
|
||||
btn.disabled = true; btn.textContent = "Signing…";
|
||||
let el = null;
|
||||
try {
|
||||
el = await BNS.connect();
|
||||
push("connected to chipnet");
|
||||
const res = await BNS.registerTldWithBuiltInWallet(el, {
|
||||
wallet, tld, records,
|
||||
onProgress: (step) => push(step),
|
||||
});
|
||||
push("broadcast: " + (res.txid || res.txId || "(unknown)"));
|
||||
msg.className = "msg ok";
|
||||
msg.textContent = "Minted .${tld}. Category " + (res.category || "unknown") + ". Registry refresh below in ~30s.";
|
||||
btn.textContent = "Mint TLD →"; btn.disabled = false;
|
||||
$("mint-tld").value = "";
|
||||
$("mint-records").value = "{}";
|
||||
setTimeout(loadRegistry, 3000);
|
||||
} catch (e) {
|
||||
msg.className = "msg err";
|
||||
msg.textContent = "Mint failed: " + (e.message || e);
|
||||
push("error: " + (e.message || e));
|
||||
btn.disabled = false; btn.textContent = "Mint TLD →";
|
||||
} finally {
|
||||
try { el?.close?.(); } catch {}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
// Sidenav scroll-spy: highlight the section currently in view.
|
||||
// Uses scroll-position tracking so short sections at the end of the page
|
||||
// still activate their link when the reader scrolls to them.
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const entries = [];
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
|
||||
if (!entries.length) return;
|
||||
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
|
||||
const update = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
|
||||
const line = window.scrollY + window.innerHeight * 0.35;
|
||||
let active = entries[0].link;
|
||||
for (const e of entries) {
|
||||
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
|
||||
else break;
|
||||
}
|
||||
setActive(active);
|
||||
};
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
609
brand/index.html
609
brand/index.html
|
|
@ -1,299 +1,310 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Brand — Sirius.X</title>
|
||||
<meta name="description" content="Sirius.X brand kit — logo, wordmark, avatar, banner, favicon, colour palette, and usage guidance. All assets served as SVG so they stay crisp at any size.">
|
||||
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
||||
<link rel="mask-icon" href="./favicon.svg" color="#d6ff3d">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Sirius.X">
|
||||
<meta property="og:title" content="Sirius.X brand kit">
|
||||
<meta property="og:description" content="Logo, wordmark, avatar, banner, favicon, colour palette. All SVG, downloadable, MIT.">
|
||||
<meta property="og:url" content="https://silentmode.st/sirius-x/brand/">
|
||||
<meta property="og:image" content="https://silentmode.st/sirius-x/brand/banner.svg">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:1040px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
|
||||
.mark{font-size:44px;line-height:1}
|
||||
h1{font-size:clamp(1.7rem,4.6vw,2.3rem);margin:.3rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
|
||||
section{padding:2.2rem 0;border-top:1px solid var(--line);margin-top:2rem}
|
||||
section:first-of-type{border-top:none;margin-top:1.2rem}
|
||||
h2{font-size:1.35rem;margin:0 0 .35rem}
|
||||
h3{font-size:1rem;margin:0 0 .3rem}
|
||||
.lede{color:var(--mut);margin:0 0 1.4rem;max-width:720px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||
.asset{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px 16px;display:flex;flex-direction:column}
|
||||
.asset .preview{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:20px;
|
||||
margin-bottom:12px;min-height:130px;display:flex;align-items:center;justify-content:center}
|
||||
.asset .preview.wide{padding:0}
|
||||
.asset .preview.wide img{width:100%;height:auto;display:block;border-radius:9px}
|
||||
.asset .preview.avatar{padding:0}
|
||||
.asset .preview.avatar img{width:104px;height:104px;display:block;border-radius:14px}
|
||||
.asset .preview.favicon img{width:64px;height:64px;display:block}
|
||||
.asset .preview.mono{background:#eef1f7;color:#0b0e14}
|
||||
.asset h3{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||||
.asset .meta{color:var(--dim);font-size:12.5px;margin:2px 0 10px;font-family:ui-monospace,monospace}
|
||||
.asset .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto}
|
||||
.btn{display:inline-block;text-decoration:none;padding:6px 12px;border-radius:8px;background:transparent;
|
||||
border:1px solid var(--line);color:var(--ink);font-size:12.5px;font-family:inherit;cursor:pointer}
|
||||
.btn:hover{background:var(--panel2)}
|
||||
.btn.acid{background:var(--acid);color:#0b0e14;border-color:var(--acid);font-weight:600}
|
||||
.btn.acid:hover{filter:brightness(1.1)}
|
||||
.swatches{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:1rem}
|
||||
.sw{border:1px solid var(--line);border-radius:10px;overflow:hidden}
|
||||
.sw .chip{height:70px}
|
||||
.sw .info{padding:10px 12px;font-size:12.5px;font-family:ui-monospace,monospace;color:var(--mut)}
|
||||
.sw .info b{color:var(--ink);display:block;font-family:inherit}
|
||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||||
border-radius:10px;padding:12px 15px;color:var(--mut);font-size:13.5px;margin-top:1.2rem}
|
||||
.note b{color:var(--ink)}
|
||||
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
|
||||
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</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"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus<span class="g">.X</span></a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#logo" title="Logo & wordmark" ><span class="ic" aria-hidden="true">✦</span><span class="lb">Logo</span></a>
|
||||
<a href="#avatar" title="Avatar & favicon" ><span class="ic" aria-hidden="true">🎭</span><span class="lb">Avatar</span></a>
|
||||
<a href="#banner" title="Social banner" ><span class="ic" aria-hidden="true">🖼</span><span class="lb">Banner</span></a>
|
||||
<a href="#palette" title="Colour palette" ><span class="ic" aria-hidden="true">🎨</span><span class="lb">Palette</span></a>
|
||||
<a href="#typography" title="Typography" ><span class="ic" aria-hidden="true">🅰️</span><span class="lb">Typography</span></a>
|
||||
<a href="#usage" title="Usage & licence" ><span class="ic" aria-hidden="true">📜</span><span class="lb">Usage</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark">🎨</div>
|
||||
<h1>Sirius.X <span class="g">brand kit</span></h1>
|
||||
<p class="tag">Logo, wordmark, avatar, banner, favicon, and the colour palette. Every asset ships as
|
||||
SVG so it stays crisp at any size and reads correctly in light and dark contexts. Free to reuse
|
||||
with attribution.</p>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="logo">
|
||||
<h2>Logo & wordmark</h2>
|
||||
<p class="lede">A 4-point sparkle star plus <b>Sirius.X</b>, with the <code>.x</code> TLD picked out
|
||||
in acid green. The star alone works when you have no room for the wordmark; the full lockup is
|
||||
the default choice everywhere else.</p>
|
||||
<div class="grid">
|
||||
<div class="asset">
|
||||
<div class="preview"><img src="./logo.svg" alt="sirius.x logo" style="max-width:100%;height:auto"></div>
|
||||
<h3>Wordmark <span class="meta">SVG · 320×64</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Full colour, dark backgrounds. Use this by default.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./logo.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./logo.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset">
|
||||
<div class="preview mono" style="color:#0b0e14"><img src="./logo-mono.svg" alt="sirius.x mono" style="max-width:100%;height:auto;color:inherit"></div>
|
||||
<h3>Wordmark, monochrome <span class="meta">SVG · currentColor</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Uses <code>currentColor</code>, inherits from the
|
||||
surrounding text. Print, embossing, single-colour merch.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./logo-mono.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./logo-mono.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="avatar">
|
||||
<h2>Avatar & favicon</h2>
|
||||
<p class="lede">Square icons for social profiles, Nostr, GitHub, and browser tabs.</p>
|
||||
<div class="grid">
|
||||
<div class="asset">
|
||||
<div class="preview avatar"><img src="./avatar.svg" alt="sirius.x avatar"></div>
|
||||
<h3>Avatar <span class="meta">SVG · 512×512</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Square profile picture. Social platforms apply
|
||||
their own round mask, so corners stay hard.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./avatar.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./avatar.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset">
|
||||
<div class="preview favicon"><img src="./favicon.svg" alt="sirius.x favicon"></div>
|
||||
<h3>Favicon <span class="meta">SVG · 64×64, 12px radius</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Rounded browser-tab icon. Scales down to 16×16
|
||||
cleanly; used on every Sirius.X page.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./favicon.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./favicon.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="banner">
|
||||
<h2>Social banner</h2>
|
||||
<p class="lede">1200×630 (OpenGraph / Twitter Card canonical). Composition survives aggressive
|
||||
centre-cropping — the star and tagline both stay visible even after Facebook and Discord chop
|
||||
the sides.</p>
|
||||
<div class="asset">
|
||||
<div class="preview wide"><img src="./banner.svg" alt="sirius.x banner"></div>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./banner.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./banner.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="palette">
|
||||
<h2>Colour palette</h2>
|
||||
<p class="lede">Silent Mode family colours. Dark chromes are the default; acid green is the
|
||||
single accent that consistently signals "this is Sirius.X".</p>
|
||||
<div class="swatches">
|
||||
<div class="sw"><div class="chip" style="background:#d6ff3d"></div><div class="info"><b>Acid</b>#d6ff3d — accent, links, active nav</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#0b0e14"></div><div class="info"><b>Background</b>#0b0e14 — page canvas</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#141a24"></div><div class="info"><b>Panel</b>#141a24 — cards, elevated surfaces</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#18202c"></div><div class="info"><b>Panel 2</b>#18202c — nested / step items</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#e7eaf1"></div><div class="info"><b>Ink</b>#e7eaf1 — body text on dark</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#8b98a9"></div><div class="info"><b>Muted</b>#8b98a9 — secondary text</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#5e6678"></div><div class="info"><b>Dim</b>#5e6678 — tertiary text, dividers</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#4fd1a5"></div><div class="info"><b>OK</b>#4fd1a5 — success / available</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#f6768a"></div><div class="info"><b>Taken</b>#f6768a — errors / unavailable</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#ffc75f"></div><div class="info"><b>Warn</b>#ffc75f — caution, holds</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="typography">
|
||||
<h2>Typography</h2>
|
||||
<p class="lede">System font stack, so nothing needs to load and every OS renders the wordmark in
|
||||
the same family the reader is used to.</p>
|
||||
<div class="asset" style="max-width:640px">
|
||||
<p style="margin:0;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
font-size:36px;font-weight:600;letter-spacing:-.5px">The quick brown fox</p>
|
||||
<p style="margin:.4rem 0 0;color:var(--mut);font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
font-size:16px">jumps over the lazy dog · 0123456789 · Sirius.X</p>
|
||||
<div class="meta" style="margin-top:12px">system-ui, -apple-system, Segoe UI, Roboto, sans-serif</div>
|
||||
</div>
|
||||
<div class="note">The wordmark's <code>.x</code> is a <code><tspan fill="#d6ff3d"></code> —
|
||||
don't render the whole word in acid, and don't render the star without the star's exact 4-point
|
||||
path (it's a mark, not a generic asterisk).</div>
|
||||
</section>
|
||||
|
||||
<section id="usage">
|
||||
<h2>Usage & licence</h2>
|
||||
<p class="lede">All assets are MIT. Reuse them for third-party tools that integrate with Sirius.X
|
||||
(wallets, indexers, explorers, blog posts about BCNR). Keep the star's proportions intact and
|
||||
don't recolour the acid to a similar-but-different green — palette drift is what makes brands
|
||||
look untrustworthy over time.</p>
|
||||
<ul style="color:var(--mut);font-size:14px;padding-left:1.2rem">
|
||||
<li><b style="color:var(--ink)">Do</b> use the mark to indicate "supports Sirius.X" or "hosted on Sirius.X".</li>
|
||||
<li><b style="color:var(--ink)">Do</b> shrink to the favicon at 16×16 — the SVG scales cleanly.</li>
|
||||
<li><b style="color:var(--ink)">Don't</b> imply endorsement of a specific product without asking.</li>
|
||||
<li><b style="color:var(--ink)">Don't</b> stretch, skew, or re-tint the star.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const map = new Map();
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) map.set(el, a); });
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (!en.isIntersecting) return;
|
||||
const a = map.get(en.target); if (!a) return;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
a.classList.add("active");
|
||||
});
|
||||
}, { rootMargin: "-40% 0px -55% 0px", threshold: 0 });
|
||||
map.forEach((_, el) => io.observe(el));
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Brand — Sirius.X</title>
|
||||
<meta name="description" content="Sirius.X brand kit — logo, wordmark, avatar, banner, favicon, colour palette, and usage guidance. All assets served as SVG so they stay crisp at any size.">
|
||||
<link rel="icon" href="./favicon.svg" type="image/svg+xml">
|
||||
<link rel="mask-icon" href="./favicon.svg" color="#d6ff3d">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Sirius.X">
|
||||
<meta property="og:title" content="Sirius.X brand kit">
|
||||
<meta property="og:description" content="Logo, wordmark, avatar, banner, favicon, colour palette. All SVG, downloadable, MIT.">
|
||||
<meta property="og:url" content="https://silentmode.st/sirius-x/brand/">
|
||||
<meta property="og:image" content="https://silentmode.st/sirius-x/brand/banner.svg">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:1040px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
|
||||
.mark{font-size:44px;line-height:1}
|
||||
h1{font-size:clamp(1.7rem,4.6vw,2.3rem);margin:.3rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
|
||||
section{padding:2.2rem 0;border-top:1px solid var(--line);margin-top:2rem}
|
||||
section:first-of-type{border-top:none;margin-top:1.2rem}
|
||||
h2{font-size:1.35rem;margin:0 0 .35rem}
|
||||
h3{font-size:1rem;margin:0 0 .3rem}
|
||||
.lede{color:var(--mut);margin:0 0 1.4rem;max-width:720px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||
.asset{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px 16px;display:flex;flex-direction:column}
|
||||
.asset .preview{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:20px;
|
||||
margin-bottom:12px;min-height:130px;display:flex;align-items:center;justify-content:center}
|
||||
.asset .preview.wide{padding:0}
|
||||
.asset .preview.wide img{width:100%;height:auto;display:block;border-radius:9px}
|
||||
.asset .preview.avatar{padding:0}
|
||||
.asset .preview.avatar img{width:104px;height:104px;display:block;border-radius:14px}
|
||||
.asset .preview.favicon img{width:64px;height:64px;display:block}
|
||||
.asset .preview.mono{background:#eef1f7;color:#0b0e14}
|
||||
.asset h3{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||||
.asset .meta{color:var(--dim);font-size:12.5px;margin:2px 0 10px;font-family:ui-monospace,monospace}
|
||||
.asset .row{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto}
|
||||
.btn{display:inline-block;text-decoration:none;padding:6px 12px;border-radius:8px;background:transparent;
|
||||
border:1px solid var(--line);color:var(--ink);font-size:12.5px;font-family:inherit;cursor:pointer}
|
||||
.btn:hover{background:var(--panel2)}
|
||||
.btn.acid{background:var(--acid);color:#0b0e14;border-color:var(--acid);font-weight:600}
|
||||
.btn.acid:hover{filter:brightness(1.1)}
|
||||
.swatches{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:1rem}
|
||||
.sw{border:1px solid var(--line);border-radius:10px;overflow:hidden}
|
||||
.sw .chip{height:70px}
|
||||
.sw .info{padding:10px 12px;font-size:12.5px;font-family:ui-monospace,monospace;color:var(--mut)}
|
||||
.sw .info b{color:var(--ink);display:block;font-family:inherit}
|
||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||||
border-radius:10px;padding:12px 15px;color:var(--mut);font-size:13.5px;margin-top:1.2rem}
|
||||
.note b{color:var(--ink)}
|
||||
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
|
||||
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</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"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus<span class="g">.X</span></a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#logo" title="Logo & wordmark" ><span class="ic" aria-hidden="true">✦</span><span class="lb">Logo</span></a>
|
||||
<a href="#avatar" title="Avatar & favicon" ><span class="ic" aria-hidden="true">🎭</span><span class="lb">Avatar</span></a>
|
||||
<a href="#banner" title="Social banner" ><span class="ic" aria-hidden="true">🖼</span><span class="lb">Banner</span></a>
|
||||
<a href="#palette" title="Colour palette" ><span class="ic" aria-hidden="true">🎨</span><span class="lb">Palette</span></a>
|
||||
<a href="#typography" title="Typography" ><span class="ic" aria-hidden="true">🅰️</span><span class="lb">Typography</span></a>
|
||||
<a href="#usage" title="Usage & licence" ><span class="ic" aria-hidden="true">📜</span><span class="lb">Usage</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark">🎨</div>
|
||||
<h1>Sirius.X <span class="g">brand kit</span></h1>
|
||||
<p class="tag">Logo, wordmark, avatar, banner, favicon, and the colour palette. Every asset ships as
|
||||
SVG so it stays crisp at any size and reads correctly in light and dark contexts. Free to reuse
|
||||
with attribution.</p>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="logo">
|
||||
<h2>Logo & wordmark</h2>
|
||||
<p class="lede">A 4-point sparkle star plus <b>Sirius.X</b>, with the <code>.x</code> TLD picked out
|
||||
in acid green. The star alone works when you have no room for the wordmark; the full lockup is
|
||||
the default choice everywhere else.</p>
|
||||
<div class="grid">
|
||||
<div class="asset">
|
||||
<div class="preview"><img src="./logo.svg" alt="sirius.x logo" style="max-width:100%;height:auto"></div>
|
||||
<h3>Wordmark <span class="meta">SVG · 320×64</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Full colour, dark backgrounds. Use this by default.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./logo.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./logo.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset">
|
||||
<div class="preview mono" style="color:#0b0e14"><img src="./logo-mono.svg" alt="sirius.x mono" style="max-width:100%;height:auto;color:inherit"></div>
|
||||
<h3>Wordmark, monochrome <span class="meta">SVG · currentColor</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Uses <code>currentColor</code>, inherits from the
|
||||
surrounding text. Print, embossing, single-colour merch.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./logo-mono.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./logo-mono.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="avatar">
|
||||
<h2>Avatar & favicon</h2>
|
||||
<p class="lede">Square icons for social profiles, Nostr, GitHub, and browser tabs.</p>
|
||||
<div class="grid">
|
||||
<div class="asset">
|
||||
<div class="preview avatar"><img src="./avatar.svg" alt="sirius.x avatar"></div>
|
||||
<h3>Avatar <span class="meta">SVG · 512×512</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Square profile picture. Social platforms apply
|
||||
their own round mask, so corners stay hard.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./avatar.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./avatar.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="asset">
|
||||
<div class="preview favicon"><img src="./favicon.svg" alt="sirius.x favicon"></div>
|
||||
<h3>Favicon <span class="meta">SVG · 64×64, 12px radius</span></h3>
|
||||
<p class="lede" style="margin-bottom:.6rem">Rounded browser-tab icon. Scales down to 16×16
|
||||
cleanly; used on every Sirius.X page.</p>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./favicon.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./favicon.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="banner">
|
||||
<h2>Social banner</h2>
|
||||
<p class="lede">1200×630 (OpenGraph / Twitter Card canonical). Composition survives aggressive
|
||||
centre-cropping — the star and tagline both stay visible even after Facebook and Discord chop
|
||||
the sides.</p>
|
||||
<div class="asset">
|
||||
<div class="preview wide"><img src="./banner.svg" alt="sirius.x banner"></div>
|
||||
<div class="row">
|
||||
<a class="btn acid" href="./banner.svg" download>Download SVG</a>
|
||||
<a class="btn" href="./banner.svg" target="_blank">Open</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="palette">
|
||||
<h2>Colour palette</h2>
|
||||
<p class="lede">Silent Mode family colours. Dark chromes are the default; acid green is the
|
||||
single accent that consistently signals "this is Sirius.X".</p>
|
||||
<div class="swatches">
|
||||
<div class="sw"><div class="chip" style="background:#d6ff3d"></div><div class="info"><b>Acid</b>#d6ff3d — accent, links, active nav</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#0b0e14"></div><div class="info"><b>Background</b>#0b0e14 — page canvas</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#141a24"></div><div class="info"><b>Panel</b>#141a24 — cards, elevated surfaces</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#18202c"></div><div class="info"><b>Panel 2</b>#18202c — nested / step items</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#e7eaf1"></div><div class="info"><b>Ink</b>#e7eaf1 — body text on dark</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#8b98a9"></div><div class="info"><b>Muted</b>#8b98a9 — secondary text</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#5e6678"></div><div class="info"><b>Dim</b>#5e6678 — tertiary text, dividers</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#4fd1a5"></div><div class="info"><b>OK</b>#4fd1a5 — success / available</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#f6768a"></div><div class="info"><b>Taken</b>#f6768a — errors / unavailable</div></div>
|
||||
<div class="sw"><div class="chip" style="background:#ffc75f"></div><div class="info"><b>Warn</b>#ffc75f — caution, holds</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="typography">
|
||||
<h2>Typography</h2>
|
||||
<p class="lede">System font stack, so nothing needs to load and every OS renders the wordmark in
|
||||
the same family the reader is used to.</p>
|
||||
<div class="asset" style="max-width:640px">
|
||||
<p style="margin:0;font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
font-size:36px;font-weight:600;letter-spacing:-.5px">The quick brown fox</p>
|
||||
<p style="margin:.4rem 0 0;color:var(--mut);font-family:system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
font-size:16px">jumps over the lazy dog · 0123456789 · Sirius.X</p>
|
||||
<div class="meta" style="margin-top:12px">system-ui, -apple-system, Segoe UI, Roboto, sans-serif</div>
|
||||
</div>
|
||||
<div class="note">The wordmark's <code>.x</code> is a <code><tspan fill="#d6ff3d"></code> —
|
||||
don't render the whole word in acid, and don't render the star without the star's exact 4-point
|
||||
path (it's a mark, not a generic asterisk).</div>
|
||||
</section>
|
||||
|
||||
<section id="usage">
|
||||
<h2>Usage & licence</h2>
|
||||
<p class="lede">All assets are MIT. Reuse them for third-party tools that integrate with Sirius.X
|
||||
(wallets, indexers, explorers, blog posts about BCNR). Keep the star's proportions intact and
|
||||
don't recolour the acid to a similar-but-different green — palette drift is what makes brands
|
||||
look untrustworthy over time.</p>
|
||||
<ul style="color:var(--mut);font-size:14px;padding-left:1.2rem">
|
||||
<li><b style="color:var(--ink)">Do</b> use the mark to indicate "supports Sirius.X" or "hosted on Sirius.X".</li>
|
||||
<li><b style="color:var(--ink)">Do</b> shrink to the favicon at 16×16 — the SVG scales cleanly.</li>
|
||||
<li><b style="color:var(--ink)">Don't</b> imply endorsement of a specific product without asking.</li>
|
||||
<li><b style="color:var(--ink)">Don't</b> stretch, skew, or re-tint the star.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
// Sidenav scroll-spy: highlight the section currently in view.
|
||||
// Uses scroll-position tracking so short sections at the end of the page
|
||||
// still activate their link when the reader scrolls to them.
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const entries = [];
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
|
||||
if (!entries.length) return;
|
||||
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
|
||||
const update = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
|
||||
const line = window.scrollY + window.innerHeight * 0.35;
|
||||
let active = entries[0].link;
|
||||
for (const e of entries) {
|
||||
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
|
||||
else break;
|
||||
}
|
||||
setActive(active);
|
||||
};
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -125,9 +125,9 @@
|
|||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
<script src="../js/i18n.js?v=20260920f"></script>
|
||||
<script src="../js/i18n.js?v=20260920g"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -651,20 +651,31 @@ process.exit(0);"</pre>
|
|||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
// Sidenav scroll-spy: highlight the section currently in view.
|
||||
// Uses scroll-position tracking so short sections at the end of the page
|
||||
// still activate their link when the reader scrolls to them.
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const map = new Map();
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) map.set(el, a); });
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (!en.isIntersecting) return;
|
||||
const a = map.get(en.target); if (!a) return;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
a.classList.add("active");
|
||||
});
|
||||
}, { rootMargin: "-40% 0px -55% 0px", threshold: 0 });
|
||||
map.forEach((_, el) => io.observe(el));
|
||||
const entries = [];
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
|
||||
if (!entries.length) return;
|
||||
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
|
||||
const update = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
|
||||
const line = window.scrollY + window.innerHeight * 0.35;
|
||||
let active = entries[0].link;
|
||||
for (const e of entries) {
|
||||
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
|
||||
else break;
|
||||
}
|
||||
setActive(active);
|
||||
};
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "wo die Seitendateien liegen (der s3-Eintrag)",
|
||||
"Not hosted on Sia": "Nicht auf Sia gehostet",
|
||||
"Sirius.X hosting — free, on Sia storage": "Sirius.X-Hosting — kostenlos, auf Sia-Speicher",
|
||||
"Another Sia folder (advanced)": "Anderer Sia-Ordner (erweitert)"
|
||||
"Another Sia folder (advanced)": "Anderer Sia-Ordner (erweitert)",
|
||||
"Price is fixed in": "Preis festgelegt in",
|
||||
"US dollars — the BCH amount follows the market": "US-Dollar — der BCH-Betrag folgt dem Markt",
|
||||
"BCH — the dollar value follows the market": "BCH — der Dollarwert folgt dem Markt",
|
||||
"Accept between": "Akzeptiere zwischen"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "πού βρίσκονται τα αρχεία της σελίδας (η εγγραφή s3)",
|
||||
"Not hosted on Sia": "Δεν φιλοξενείται στο Sia",
|
||||
"Sirius.X hosting — free, on Sia storage": "Φιλοξενία Sirius.X — δωρεάν, στον αποθηκευτικό χώρο Sia",
|
||||
"Another Sia folder (advanced)": "Άλλος φάκελος Sia (για προχωρημένους)"
|
||||
"Another Sia folder (advanced)": "Άλλος φάκελος Sia (για προχωρημένους)",
|
||||
"Price is fixed in": "Η τιμή είναι σταθερή σε",
|
||||
"US dollars — the BCH amount follows the market": "Δολάρια ΗΠΑ — το ποσό σε BCH ακολουθεί την αγορά",
|
||||
"BCH — the dollar value follows the market": "BCH — η αξία σε δολάρια ακολουθεί την αγορά",
|
||||
"Accept between": "Αποδοχή μεταξύ"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "dónde viven los archivos de la página (el registro s3)",
|
||||
"Not hosted on Sia": "No alojado en Sia",
|
||||
"Sirius.X hosting — free, on Sia storage": "Alojamiento Sirius.X — gratis, en almacenamiento Sia",
|
||||
"Another Sia folder (advanced)": "Otra carpeta de Sia (avanzado)"
|
||||
"Another Sia folder (advanced)": "Otra carpeta de Sia (avanzado)",
|
||||
"Price is fixed in": "El precio se fija en",
|
||||
"US dollars — the BCH amount follows the market": "Dólares — la cantidad en BCH sigue al mercado",
|
||||
"BCH — the dollar value follows the market": "BCH — el valor en dólares sigue al mercado",
|
||||
"Accept between": "Aceptar entre"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "où se trouvent les fichiers de la page (l'enregistrement s3)",
|
||||
"Not hosted on Sia": "Pas hébergé sur Sia",
|
||||
"Sirius.X hosting — free, on Sia storage": "Hébergement Sirius.X — gratuit, sur le stockage Sia",
|
||||
"Another Sia folder (advanced)": "Un autre dossier Sia (avancé)"
|
||||
"Another Sia folder (advanced)": "Un autre dossier Sia (avancé)",
|
||||
"Price is fixed in": "Le prix est fixé en",
|
||||
"US dollars — the BCH amount follows the market": "Dollars US — le montant en BCH suit le marché",
|
||||
"BCH — the dollar value follows the market": "BCH — la valeur en dollars suit le marché",
|
||||
"Accept between": "Accepter entre"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "onde ficam os ficheiros da página (o registo s3)",
|
||||
"Not hosted on Sia": "Não alojado no Sia",
|
||||
"Sirius.X hosting — free, on Sia storage": "Alojamento Sirius.X — grátis, no armazenamento Sia",
|
||||
"Another Sia folder (advanced)": "Outra pasta Sia (avançado)"
|
||||
"Another Sia folder (advanced)": "Outra pasta Sia (avançado)",
|
||||
"Price is fixed in": "O preço é fixado em",
|
||||
"US dollars — the BCH amount follows the market": "Dólares — o montante em BCH segue o mercado",
|
||||
"BCH — the dollar value follows the market": "BCH — o valor em dólares segue o mercado",
|
||||
"Accept between": "Aceitar entre"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,5 +609,9 @@
|
|||
"where the page files live (the s3 record)": "где лежат файлы страницы (запись s3)",
|
||||
"Not hosted on Sia": "Не размещено на Sia",
|
||||
"Sirius.X hosting — free, on Sia storage": "Хостинг Sirius.X — бесплатно, в хранилище Sia",
|
||||
"Another Sia folder (advanced)": "Другая папка Sia (для опытных)"
|
||||
"Another Sia folder (advanced)": "Другая папка Sia (для опытных)",
|
||||
"Price is fixed in": "Цена зафиксирована в",
|
||||
"US dollars — the BCH amount follows the market": "Доллары США — сумма в BCH следует за рынком",
|
||||
"BCH — the dollar value follows the market": "BCH — долларовая стоимость следует за рынком",
|
||||
"Accept between": "Принимать в диапазоне"
|
||||
}
|
||||
|
|
|
|||
1150
index.html
1150
index.html
File diff suppressed because it is too large
Load diff
|
|
@ -10,7 +10,7 @@
|
|||
// is the persistent device session (js/session.js): sign in once, come back
|
||||
// without a prompt until you sign out.
|
||||
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd";
|
||||
|
||||
const API = "https://silentmode.st";
|
||||
const TLD_API = "https://navigate.st/api/tlds?include_hidden=1";
|
||||
|
|
@ -685,14 +685,34 @@ async function signedHeaders(msg) {
|
|||
// ---------- sell (marketplace listing) ----------
|
||||
const RATE = () => window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000;
|
||||
let activeListing = null;
|
||||
function sellMode() { return $("sell-mode").value; }
|
||||
function paintSellMode() {
|
||||
const usd = sellMode() === "usd";
|
||||
$("sell-usd-terms").classList.toggle("hidden", !usd);
|
||||
paintBand();
|
||||
}
|
||||
function paintBand() {
|
||||
const usd = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")) || 0;
|
||||
const band = Math.min(90, Math.max(5, Number(String($("sell-band").value).replace(/\D/g, "")) || 25));
|
||||
const sats = Math.round(usd * RATE());
|
||||
$("sell-band-hint").textContent = usd > 0
|
||||
? `% of today's rate: you will receive between ${fmtBch(Math.round(sats * (1 - band / 100)))} and ${fmtBch(Math.round(sats * (1 + band / 100)))}`
|
||||
: "% of today's BCH rate";
|
||||
}
|
||||
$("sell-mode").addEventListener("change", paintSellMode);
|
||||
$("sell-band").addEventListener("input", paintBand);
|
||||
function paintSell() {
|
||||
const st = $("sell-status");
|
||||
paintSellMode();
|
||||
$("sell-rate").textContent = `1 BCH ≈ ${fmtUsd(Math.round(1e8 / RATE() * 100) / 100)}${window.siriusPricing?.priceInfo ? " · " + window.siriusPricing.priceInfo() : ""}`;
|
||||
if (activeListing) {
|
||||
st.className = "status ok"; st.textContent = "This name is listed on the market.";
|
||||
$("sell-form").classList.add("hidden"); $("sell-active").classList.remove("hidden");
|
||||
const p = BigInt(activeListing.price_sats);
|
||||
$("sell-kv").innerHTML = [["Price", `${esc(fmtBch(p))} (≈ ${esc(fmtUsd(Math.round(Number(p) / RATE() * 100) / 100))})`], ["Listed", esc(new Date(activeListing.created_at).toLocaleString())], ["Offer", `<code>${esc(activeListing.partial_tx.slice(0, 40))}…</code>`], ["Market page", `<a href="./market/" target="_blank" rel="noopener">open →</a>`]]
|
||||
const p = BigInt(activeListing.price_sats || 0);
|
||||
const priceRow = activeListing.kind === "usd"
|
||||
? ["Price", `${esc(fmtUsd(activeListing.target_cents / 100))} fixed · today ≈ ${esc(fmtBch(Math.round(activeListing.target_cents / 100 * RATE())))} · band ${esc(fmtBch(activeListing.floor_sats))} – ${esc(fmtBch(activeListing.ceil_sats))}`]
|
||||
: ["Price", `${esc(fmtBch(p))} (≈ ${esc(fmtUsd(Math.round(Number(p) / RATE() * 100) / 100))})`];
|
||||
$("sell-kv").innerHTML = [priceRow, ["Listed", esc(new Date(activeListing.created_at).toLocaleString())], ["Offer", `<code>${esc(activeListing.partial_tx.slice(0, 40))}…</code>`], ["Market page", `<a href="./market/" target="_blank" rel="noopener">open →</a>`]]
|
||||
.map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
||||
} else {
|
||||
st.className = "status"; st.textContent = "Not for sale. Set a price to list it.";
|
||||
|
|
@ -705,7 +725,7 @@ async function loadListing(name) {
|
|||
if (current.name === name) paintSell();
|
||||
}
|
||||
const bchStr = (sats) => (Number(sats) / 1e8).toFixed(8).replace(/0+$/, "").replace(/\.$/, "");
|
||||
$("sell-usd").addEventListener("input", () => { const v = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")); $("sell-bch").value = v > 0 ? bchStr(Math.round(v * RATE())) : ""; });
|
||||
$("sell-usd").addEventListener("input", () => { const v = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")); $("sell-bch").value = v > 0 ? bchStr(Math.round(v * RATE())) : ""; paintBand(); });
|
||||
$("sell-bch").addEventListener("input", () => { const b = Number(String($("sell-bch").value).replace(/[^\d.]/g, "")); $("sell-usd").value = b > 0 ? String(Math.round(b * 1e8 / RATE() * 100) / 100) : ""; });
|
||||
$("sell-list").addEventListener("click", async () => {
|
||||
if (!current.entry || !wallet) return;
|
||||
|
|
@ -721,6 +741,33 @@ $("sell-list").addEventListener("click", async () => {
|
|||
const commitment = [...new TextEncoder().encode(raw)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||
const cert = utxos.find((u) => u.token?.category === current.entry.category && u.token?.nft?.commitment === commitment);
|
||||
if (!cert) throw new Error("certificate UTXO not found in this wallet");
|
||||
if (sellMode() === "usd") {
|
||||
// Dollar price: certificate goes into the UsdListing covenant.
|
||||
const usd = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")) || 0;
|
||||
if (usd < 0.5) throw new Error("set a dollar price of at least $0.50");
|
||||
const band = Math.min(90, Math.max(5, Number(String($("sell-band").value).replace(/\D/g, "")) || 25));
|
||||
const targetCents = Math.round(usd * 100);
|
||||
const nowSats = Math.round(usd * RATE());
|
||||
const floorSats = Math.max(10000, Math.round(nowSats * (1 - band / 100))), ceilSats = Math.round(nowSats * (1 + band / 100));
|
||||
const orc = await (await fetch(`${API}/api/price/oracle`, { cache: "no-store" })).json();
|
||||
if (!orc.pubkey) throw new Error("the price oracle is not available right now");
|
||||
push(`moving the certificate into the contract · $${usd} · band ${fmtBch(floorSats)} – ${fmtBch(ceilSats)}`);
|
||||
const built = BNS.buildUsdListingTx({ name: current.name, certificateUtxo: cert, utxos, sellerAddress: cert.address, oraclePk: orc.pubkey, targetCents, floorSats, ceilSats, records: current.entry.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode });
|
||||
const signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h));
|
||||
const txid = await BNS.broadcast(el, signed.hex);
|
||||
push("broadcast " + txid.slice(0, 16) + "…");
|
||||
const listing = { v: 2, kind: "usd", name: raw, seller: cert.address, seller_pkh: built.sellerPkh, category: current.entry.category, target_cents: targetCents, floor_sats: String(floorSats), ceil_sats: String(ceilSats), max_age_secs: BNS.DEFAULT_MAX_AGE_SECS, oracle_pk: orc.pubkey, redeem_hex: built.redeemHex, covenant_address: built.address, outpoint: { txid, vout: 0, satoshis: "1000" }, token: { category: cert.token.category, amount: String(cert.token.amount ?? 0), nft: { capability: cert.token.nft.capability, commitment: cert.token.nft.commitment } } };
|
||||
let posted = null;
|
||||
for (let i = 0; i < 6 && !posted; i++) {
|
||||
const r = await fetch(`${API}/api/market`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(listing) });
|
||||
const j = await r.json().catch(() => ({}));
|
||||
if (r.ok) posted = j.listing; else if (r.status === 409) { push("waiting for the contract output to relay…"); await new Promise((res) => setTimeout(res, 4000)); } else throw new Error(j.error || `API ${r.status}`);
|
||||
}
|
||||
if (!posted) throw new Error("the market did not see the contract yet — press Refresh in a minute; the certificate is safe in the contract");
|
||||
activeListing = posted; paintSell(); refreshBalance();
|
||||
setMsg("sell-msg", `Listed at ${fmtUsd(usd)}. Buyers pay the dollar amount at the oracle rate; you receive between ${fmtBch(floorSats)} and ${fmtBch(ceilSats)}.`, "ok");
|
||||
return;
|
||||
}
|
||||
push("found certificate · signing the offer (SINGLE|ANYONECANPAY)");
|
||||
const listing = BNS.buildListing({ name: current.name, certificateUtxo: cert, priceSats: sats, sellerAddress: cert.address, addressToLockingBytecode: BNS.addressToLockingBytecode, keyFor: (h) => wallet.keyFor(h) });
|
||||
const r = await fetch(`${API}/api/market`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(listing) });
|
||||
|
|
@ -736,6 +783,22 @@ $("sell-cancel").addEventListener("click", async () => {
|
|||
if (!current.entry || !wallet || !activeListing) return;
|
||||
if (!confirm(`Cancel the listing for ${current.name}? This also moves the certificate once so the signed offer becomes void.`)) return;
|
||||
const push = logger("sell-log"); const btn = $("sell-cancel"); btn.disabled = true; btn.textContent = "Cancelling…"; setMsg("sell-msg", "");
|
||||
if (activeListing.kind === "usd") {
|
||||
let el = null;
|
||||
try {
|
||||
el = await BNS.connect(); push("connected to chipnet");
|
||||
const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses);
|
||||
const built = BNS.buildUsdCancelTx({ listing: activeListing, sellerAddress: wallet.address, sellerTokenAddress: wallet.tokenAddress, utxos, records: current.entry.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode, keyFor: (h) => wallet.keyFor(h) });
|
||||
const txid = await BNS.broadcast(el, built.hex);
|
||||
push("certificate reclaimed · broadcast " + txid.slice(0, 16) + "…");
|
||||
const h = await signedHeaders(`BNS-MARKET1\n${current.name}\n{ts}`);
|
||||
await fetch(`${API}/api/market/${encodeURIComponent(current.name)}`, { method: "DELETE", headers: h }).catch(() => {});
|
||||
activeListing = null; paintSell(); setTimeout(() => { loadHoldings(); refreshBalance(); }, 3000);
|
||||
setMsg("sell-msg", "Listing cancelled. The certificate is back in your wallet; every quote issued for that listing is void.", "ok");
|
||||
} catch (e) { setMsg("sell-msg", "Failed: " + (e.message || e), "err"); push("error: " + (e.message || e)); }
|
||||
finally { btn.disabled = false; btn.textContent = "Cancel listing →"; try { el?.close?.(); } catch {} }
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const h = await signedHeaders(`BNS-MARKET1\n${current.name}\n{ts}`);
|
||||
const r = await fetch(`${API}/api/market/${encodeURIComponent(current.name)}`, { method: "DELETE", headers: h });
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
(function () {
|
||||
var STORE = "sirius-lang";
|
||||
var CACHE = "sirius-i18n:";
|
||||
var VERSION = "20260920f"; // bump when dictionaries change
|
||||
var VERSION = "20260920g"; // bump when dictionaries change
|
||||
var LANGS = {
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
|
|
|
|||
38
js/market.js
38
js/market.js
|
|
@ -7,7 +7,7 @@
|
|||
// stays valid while their certificate UTXO is unspent, so the gateway prunes
|
||||
// stale offers and the buyer's broadcast is the final arbiter.
|
||||
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd";
|
||||
|
||||
const API = "https://silentmode.st";
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
|
@ -16,6 +16,9 @@ const fmtInt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|||
const bch = (sats) => (window.siriusPricing?.formatBch || ((s) => (Number(s) / 1e8).toFixed(8).replace(/0+$/, "").replace(/\.$/, "") + " BCH"))(sats);
|
||||
const bits = (sats) => (window.siriusPricing?.formatBits || ((s) => (Number(s) / 100).toLocaleString("en-US") + " bits"))(sats);
|
||||
const usd = (sats) => { const r = window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000; const v = Number(sats) / r; return (window.siriusPricing?.formatUsd || ((n) => "$" + n.toFixed(2)))(Math.round(v * 100) / 100); };
|
||||
const fmtUsdC = (cents) => (window.siriusPricing?.formatUsd || ((n) => "$" + n.toFixed(2)))(Math.round(Number(cents)) / 100);
|
||||
const satsForCents = (cents) => Math.round(Number(cents) / 100 * (window.siriusPricing?.CHIPNET_SATS_PER_USD || 400000));
|
||||
const clampSats = (sats, l) => Math.min(Number(l.ceil_sats), Math.max(Number(l.floor_sats), sats));
|
||||
const shortAddr = (a) => (a ? a.replace(/^[^:]+:/, "").slice(0, 8) + "…" + a.slice(-5) : "—");
|
||||
|
||||
let listings = [];
|
||||
|
|
@ -53,7 +56,8 @@ function render() {
|
|||
const keep = tldSel.value;
|
||||
tldSel.innerHTML = `<option value="">All TLDs</option>` + tlds.map((t) => `<option value="${esc(t)}"${t === keep ? " selected" : ""}>.${esc(t)}</option>`).join("");
|
||||
let rows = listings.filter((l) => (!q || l.name.includes(q)) && (!tldSel.value || l.tld === tldSel.value));
|
||||
rows.sort((a, b) => sort === "name" ? a.name.localeCompare(b.name) : sort === "new" ? Date.parse(b.created_at) - Date.parse(a.created_at) : Number(a.price_sats) - Number(b.price_sats));
|
||||
const priceOf = (l) => (l.kind === "usd" ? clampSats(satsForCents(l.target_cents), l) : Number(l.price_sats));
|
||||
rows.sort((a, b) => sort === "name" ? a.name.localeCompare(b.name) : sort === "new" ? Date.parse(b.created_at) - Date.parse(a.created_at) : priceOf(a) - priceOf(b));
|
||||
$("count").textContent = `${rows.length} of ${listings.length}`;
|
||||
const status = $("status");
|
||||
document.querySelector(".toolbar").hidden = !listings.length;
|
||||
|
|
@ -72,7 +76,9 @@ function render() {
|
|||
$("list").innerHTML = rows.length ? rows.map((l) => `
|
||||
<div class="row">
|
||||
<div class="n">${esc(l.label)}.<span class="tld">${esc(l.tld)}</span> ${wallet && l.seller === wallet.address ? '<span class="badge">yours</span>' : ""}</div>
|
||||
<div class="price"><b>${esc(bch(l.price_sats))}</b><span>≈ ${esc(usd(l.price_sats))}</span></div>
|
||||
<div class="price">${l.kind === "usd"
|
||||
? `<b>${esc(fmtUsdC(l.target_cents))}</b><span>fixed in USD · today ≈ ${esc(bch(satsForCents(l.target_cents)))}</span>`
|
||||
: `<b>${esc(bch(l.price_sats))}</b><span>≈ ${esc(usd(l.price_sats))}</span>`}</div>
|
||||
<div><button class="btn acid small" data-buy="${esc(l.name)}" ${wallet && l.seller === wallet.address ? "disabled" : ""}>Buy →</button></div>
|
||||
<div class="meta"><span>seller ${esc(shortAddr(l.seller))}</span><span>·</span><span>listed ${esc(new Date(l.created_at).toLocaleDateString())}</span><span>·</span><a href="${API}/bns/${encodeURIComponent(l.name)}/" target="_blank" rel="noopener">view site →</a></div>
|
||||
</div>`).join("") : `<div class="empty">Nothing matches that filter.</div>`;
|
||||
|
|
@ -86,13 +92,15 @@ function push(t) { const log = $("buy-steps"); log.className = "steps on"; const
|
|||
function openBuy(name) {
|
||||
selected = listings.find((l) => l.name === name); if (!selected) return;
|
||||
$("buy-name").textContent = name;
|
||||
const price = BigInt(selected.price_sats);
|
||||
const check = BNS.verifyListing(selected, { category: selected.category });
|
||||
const isUsd = selected.kind === "usd";
|
||||
const price = BigInt(isUsd ? clampSats(satsForCents(selected.target_cents), selected) : selected.price_sats);
|
||||
const check = isUsd ? BNS.verifyUsdListing(selected) : BNS.verifyListing(selected, { category: selected.category });
|
||||
$("buy-kv").innerHTML = [
|
||||
["Price", `${esc(bch(price))} (≈ ${esc(usd(price))})`],
|
||||
isUsd ? ["Price", `${esc(fmtUsdC(selected.target_cents))} fixed · about ${esc(bch(price))} at today's rate (the exact amount is set by the oracle quote when you buy, between ${esc(bch(selected.floor_sats))} and ${esc(bch(selected.ceil_sats))})`]
|
||||
: ["Price", `${esc(bch(price))} (≈ ${esc(usd(price))})`],
|
||||
["Seller", esc(selected.seller)],
|
||||
["You receive", "the name's certificate, to your token address"],
|
||||
["Offer check", check.ok ? "seller signature valid" : "INVALID: " + esc(check.error)],
|
||||
["Offer check", check.ok ? (isUsd ? "contract terms verified" : "seller signature valid") : "INVALID: " + esc(check.error)],
|
||||
["Extra", "≈ 16 bits dust + fee"],
|
||||
].map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
||||
$("buy-steps").className = "steps"; $("buy-steps").innerHTML = ""; setMsg("");
|
||||
|
|
@ -113,9 +121,19 @@ $("buy-go").addEventListener("click", async () => {
|
|||
el = await BNS.connect(); push("connected to chipnet");
|
||||
const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses);
|
||||
push(`wallet has ${utxos.filter((u) => !u.token).length} spendable coin(s)`);
|
||||
const built = BNS.completeSale({ listing: selected, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode });
|
||||
push(`built sale · price ${bch(built.costs.priceSats)} · fee ${bits(built.costs.chainFeeSats)}`);
|
||||
const signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.sellerInputIndex] });
|
||||
let built, signed;
|
||||
if (selected.kind === "usd") {
|
||||
const q = await (await fetch(`${API}/api/price/oracle?txid=${encodeURIComponent(selected.outpoint.txid)}`, { cache: "no-store" })).json();
|
||||
if (!q.message) throw new Error(q.error || "no oracle quote");
|
||||
push(`oracle quote: 1 BCH = $${(q.price_cents / 100).toFixed(2)} (median of ${q.median_of} exchanges)`);
|
||||
built = BNS.buildUsdBuyTx({ listing: selected, quote: q, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode });
|
||||
push(`you pay ${bch(built.priceSats)} · fee ${bits(built.fee)}`);
|
||||
signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.covenantInputIndex] });
|
||||
} else {
|
||||
built = BNS.completeSale({ listing: selected, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode });
|
||||
push(`built sale · price ${bch(built.costs.priceSats)} · fee ${bits(built.costs.chainFeeSats)}`);
|
||||
signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.sellerInputIndex] });
|
||||
}
|
||||
push("signed your inputs (the seller's signature is already in place)");
|
||||
const txid = await BNS.broadcast(el, signed.hex);
|
||||
push("broadcast: " + txid);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
// Injects its own modal HTML into <body> and its own CSS into <head> on load
|
||||
// so the host page needs nothing but a single script include.
|
||||
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[c]));
|
||||
|
|
|
|||
14
js/studio.js
14
js/studio.js
|
|
@ -11,7 +11,7 @@
|
|||
// The gateway never holds a key: it verifies each upload's signature
|
||||
// against the current NFT owner and refuses writes outside bns/<name>/.
|
||||
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd";
|
||||
import { initAssistant } from "./studio-ai.js?v=20260920ai";
|
||||
|
||||
const API = "https://silentmode.st";
|
||||
|
|
@ -443,9 +443,17 @@ async function publish() {
|
|||
await putFile("_studio.json", data, "application/json");
|
||||
pushStep("saved editor project (_studio.json)");
|
||||
dirty = false; $("btn-draft").disabled = true;
|
||||
// Point the name at the folder if it does not already.
|
||||
// Point the name at the folder if it does not already. Re-read the
|
||||
// registry first: `entry` from boot can be stale or missing (a name
|
||||
// indexed after the page opened), and a stale view here means a
|
||||
// needless on-chain transaction on every publish.
|
||||
try { const r = await fetch(`${API}/api/name/${encodeURIComponent(name)}`, { cache: "no-store" }); if (r.ok) entry = await r.json(); } catch {}
|
||||
const rec = entry?.records || {};
|
||||
if (rec.s3 !== folder) {
|
||||
const norm = (v) => String(v || "").trim().replace(/\/+$/, "") + "/";
|
||||
const pointsHere = norm(rec.s3) === folder;
|
||||
if (!pointsHere && !entry) {
|
||||
pushStep("the registry has not indexed this name yet — files are up; pointing the name is skipped until it appears (or set Hosting in the dashboard)");
|
||||
} else if (!pointsHere) {
|
||||
pushStep(`name points at ${rec.s3 ? `"${rec.s3}"` : "nothing"} — setting s3 = ${folder} on chain`);
|
||||
const next = { ...rec, s3: folder };
|
||||
delete next.h; // inline HTML would shadow the Sia site
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@
|
|||
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
@media (max-width:560px){.row{grid-template-columns:1fr auto}.row .price{grid-column:1/-1;text-align:left}}
|
||||
</style>
|
||||
<script src="../js/i18n.js?v=20260920f"></script>
|
||||
<script src="../js/i18n.js?v=20260920g"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="topnav">
|
||||
|
|
@ -147,11 +147,11 @@
|
|||
<footer id="site-footer"></footer>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
<script src="../js/session.js?v=20260917dash"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script type="module" src="../js/market.js?v=20260920bits"></script>
|
||||
<script type="module" src="../js/market.js?v=20260920usd"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260917market"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
|
|
|
|||
20
portal.html
20
portal.html
|
|
@ -259,7 +259,7 @@
|
|||
.addrow{grid-template-columns:1fr 1fr}
|
||||
}
|
||||
</style>
|
||||
<script src="./js/i18n.js?v=20260920f"></script>
|
||||
<script src="./js/i18n.js?v=20260920g"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -360,11 +360,23 @@
|
|||
<p class="hint">You sign an offer once: "whoever pays me this price gets the certificate". The offer is stored by the gateway and shown on the <a href="./market" target="_blank" rel="noopener">market</a>. A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.</p>
|
||||
<div class="status" id="sell-status">Checking the market…</div>
|
||||
<div id="sell-form">
|
||||
<label style="margin-top:0">Price is fixed in</label>
|
||||
<select id="sell-mode" style="max-width:360px">
|
||||
<option value="usd">US dollars — the BCH amount follows the market</option>
|
||||
<option value="bch">BCH — the dollar value follows the market</option>
|
||||
</select>
|
||||
<div class="row" style="align-items:flex-end">
|
||||
<div><label style="margin-top:0">Price (USD)</label><input type="text" id="sell-usd" placeholder="25" inputmode="decimal" style="max-width:160px"></div>
|
||||
<div><label style="margin-top:0">Price (BCH)</label><input type="text" id="sell-bch" placeholder="0.0625" inputmode="decimal" style="max-width:200px"></div>
|
||||
<span class="dim" id="sell-rate"></span>
|
||||
</div>
|
||||
<div id="sell-usd-terms">
|
||||
<div class="row" style="align-items:flex-end">
|
||||
<div><label style="margin-top:0">Accept between</label><input type="text" id="sell-band" value="25" inputmode="numeric" style="max-width:90px"></div>
|
||||
<span class="dim" id="sell-band-hint"></span>
|
||||
</div>
|
||||
<p class="hint">A dollar price lives in a small contract that holds the certificate while it is for sale. A buyer pays the dollar amount at the Sirius.X price oracle's rate, and the contract keeps the payout inside this band whatever the oracle says, so a wrong or old quote can never take the price outside it. Your name's records are frozen while listed; cancelling returns the certificate.</p>
|
||||
</div>
|
||||
<div class="row end"><button class="btn acid" id="sell-list">List for sale →</button></div>
|
||||
</div>
|
||||
<div id="sell-active" class="hidden">
|
||||
|
|
@ -677,15 +689,15 @@
|
|||
<footer id="site-footer"></footer>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
<script src="./js/session.js?v=20260917dash"></script>
|
||||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="./js/pricing.js?v=20260920bits"></script>
|
||||
<script type="module" src="./js/dashboard.js?v=20260920s3"></script>
|
||||
<script type="module" src="./js/dashboard.js?v=20260920usd2"></script>
|
||||
<script defer src="./js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||
<script type="module" src="./js/register-flow.js?v=20260920bits"></script>
|
||||
<script type="module" src="./js/register-flow.js?v=20260920usd"></script>
|
||||
<script defer src="./js/profile-menu.js?v=20260920bits"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@
|
|||
.muted{color:var(--mut);font-size:13px}
|
||||
code{font-family:var(--mono);background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-size:12px}
|
||||
</style>
|
||||
<script src="./js/i18n.js?v=20260920f"></script>
|
||||
<script src="./js/i18n.js?v=20260920g"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bar">
|
||||
|
|
@ -229,7 +229,7 @@
|
|||
</div>
|
||||
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
<script src="./js/session.js?v=20260917dash"></script>
|
||||
<script src="./vendor/grapesjs/grapes.min.js?v=0.23.6"></script>
|
||||
|
|
@ -244,6 +244,6 @@
|
|||
<script src="./vendor/grapesjs/style-bg.js?v=2.0.2"></script>
|
||||
<script src="./vendor/grapesjs/touch.js?v=0.1.1"></script>
|
||||
<script src="./vendor/grapesjs/parser-postcss.js?v=1.0.3"></script>
|
||||
<script type="module" src="./js/studio.js?v=20260920pc"></script>
|
||||
<script type="module" src="./js/studio.js?v=20260920usd"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,248 +1,259 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Theseus Navigator — the browser that speaks Bitcoin Cash names — Sirius.X</title>
|
||||
<meta name="description" content="Theseus Navigator is a Chromium-based browser with native Bitcoin Cash Domain Names resolution built in — no OS-level daemon, no DNS shim, no root-store install. Download for Windows.">
|
||||
<!-- Theseus.X has its own compass-mark favicon and logo — reuse them here so
|
||||
tabs on this subpage read as Theseus (compass), not Sirius (star), and
|
||||
shared-link previews carry Theseus's own OG image. Copied from
|
||||
silentmode.st/bns/theseus.x/assets/ to keep sirius.x self-contained. -->
|
||||
<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="Theseus Navigator">
|
||||
<meta property="og:title" content="Theseus Navigator — the browser that speaks Bitcoin Cash names">
|
||||
<meta property="og:description" content="Native .bch and BCNR name resolution built into a Chromium browser. No OS-level daemon, no root-store install. Download for Windows.">
|
||||
<meta property="og:url" content="https://silentmode.st/sirius-x/theseus/">
|
||||
<meta property="og:image" content="https://silentmode.st/sirius-x/theseus/assets/logo.svg">
|
||||
<meta name="theme-color" content="#0b0e14">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
--chain:#d6ff3d; --sia:#b39ddb; --srv:#4fd1a5; --ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:980px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:5rem 1.2rem 1rem}
|
||||
.mark{font-size:56px;line-height:1}
|
||||
h1{font-size:clamp(2rem,5.5vw,2.9rem);margin:.4rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:1.05rem;max-width:660px;margin:0 auto}
|
||||
.cta{margin-top:1.8rem;display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
|
||||
.btn{display:inline-block;text-decoration:none;padding:11px 20px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;transition:filter .1s}
|
||||
.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)}
|
||||
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem}
|
||||
section:first-of-type{border-top:none;margin-top:1.5rem}
|
||||
h2{font-size:1.5rem;margin:0 0 .4rem}
|
||||
h3{font-size:1.1rem;margin:0 0 .35rem}
|
||||
.lede{color:var(--mut);margin:0 0 1.6rem;max-width:720px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
||||
.card h3{margin:0 0 .4rem;display:flex;align-items:center;gap:8px}
|
||||
.card p{margin:0;color:var(--mut);font-size:14px}
|
||||
.card .cta{margin-top:14px;justify-content:flex-start}
|
||||
.card .sub{color:var(--dim);font-size:12.5px;margin-top:10px;font-family:ui-monospace,monospace;word-break:break-all}
|
||||
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
|
||||
.d-chain{background:var(--chain)} .d-sia{background:var(--sia)} .d-srv{background:var(--srv)}
|
||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||||
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
|
||||
.note.warn{border-left-color:var(--warn)}
|
||||
.note b{color:var(--ink)}
|
||||
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
|
||||
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
|
||||
font-family:ui-monospace,monospace;font-size:13px;color:var(--ink);margin:1rem 0}
|
||||
ol.steps{counter-reset:s;list-style:none;padding:0;margin:1.2rem 0 0;display:grid;gap:10px}
|
||||
ol.steps li{counter-increment:s;display:grid;grid-template-columns:30px 1fr;gap:14px;align-items:start;
|
||||
background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:13px 16px}
|
||||
ol.steps li::before{content:counter(s);grid-column:1;grid-row:1;
|
||||
display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
|
||||
background:rgba(214,255,61,.14);color:var(--acid);font-size:12.5px;font-weight:700}
|
||||
ol.steps li > *{grid-column:2;min-width:0}
|
||||
ol.steps b{color:var(--ink)} ol.steps span{color:var(--mut);font-size:14px}
|
||||
.pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(214,255,61,.13);color:var(--acid);margin-left:8px;vertical-align:middle;font-weight:500}
|
||||
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</a>
|
||||
<a href="../market/">🛒 Market</a>
|
||||
<a href="./" class="here"><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<span class="g">.X</span></a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#download" title="Download" ><span class="ic" aria-hidden="true">⬇</span><span class="lb">Download</span></a>
|
||||
<a href="#how" title="How Theseus works" ><span class="ic" aria-hidden="true">⚙️</span><span class="lb">How it works</span></a>
|
||||
<a href="#verify" title="Verify a download" ><span class="ic" aria-hidden="true">🔐</span><span class="lb">Verify</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark"><img src="./assets/favicon.svg" alt="" width="72" height="72" style="vertical-align:middle;filter:drop-shadow(0 0 24px rgba(214,255,61,.15))"></div>
|
||||
<h1>Theseus <span class="g">Navigator</span></h1>
|
||||
<p class="tag">A Chromium-based browser with native <b>Bitcoin Cash Domain Names</b> resolution built in.
|
||||
No OS-level daemon, no DNS shim, no root-store install. Type <code>hello.bch</code> in the
|
||||
address bar and it just works.</p>
|
||||
<div class="cta">
|
||||
<a class="btn acid" href="#download">Download for Windows</a>
|
||||
<a class="btn ghost" href="#how">How it works</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="download">
|
||||
<h2>Download</h2>
|
||||
<p class="lede">Theseus ships from its own site — <a href="https://silentmode.st/bns/theseus.x/#download" rel="noopener"><b>theseus.x</b></a> — so the version and checksums you see are always the current build, not a snapshot cached here. Windows installer + portable, plus Ariadne Resolver (lighter, no-browser).</p>
|
||||
<div class="cta" style="justify-content:flex-start;margin-top:1.2rem">
|
||||
<a class="btn acid" href="https://silentmode.st/bns/theseus.x/#download" rel="noopener">Open theseus.x downloads →</a>
|
||||
<a class="btn ghost" href="https://silentmode.st/bns/theseus.x/" rel="noopener">Full Theseus site</a>
|
||||
</div>
|
||||
<div class="note" style="margin-top:1.4rem"><b>Why not list versions here?</b> Theseus updates weekly; a hardcoded version on this page went stale within days last time. theseus.x is the single source of truth — its own operator ships the current build, and Sirius.X links there instead of duplicating.</div>
|
||||
</section>
|
||||
|
||||
<section id="how">
|
||||
<h2>How Theseus works</h2>
|
||||
<p class="lede">Theseus is a Chromium wrapper (Electron) with two things regular browsers don't
|
||||
have: in-process BCNR name resolution, and a certificate-verify hook that trusts on-chain
|
||||
TLS fingerprints instead of the OS root store for BCNR hostnames.</p>
|
||||
<ol class="steps">
|
||||
<li><b>Address bar accepts <code>.bch</code> / <code>.x</code> / any BCNR TLD.</b>
|
||||
<span>The URL parser is short-circuited: any hostname whose TLD is in the registry is
|
||||
resolved by walking the chain via the built-in <code>bns.js</code> — never through DNS.</span></li>
|
||||
<li><b>Records decide how the site is served.</b>
|
||||
<span><code>h</code> renders inline HTML from the OP_RETURN payload itself. <code>s3</code>
|
||||
serves from a Sia bucket via the in-app gateway. <code>ip</code> connects to a server by
|
||||
IP. <code>u</code> redirects. Priority is handled by the shared <code>record-picker</code>.</span></li>
|
||||
<li><b>TLS verifies against on-chain fingerprints.</b>
|
||||
<span>Electron's <code>setCertificateVerifyProc</code> is intercepted: for BCNR hostnames,
|
||||
instead of walking the OS trust chain, the leaf's SHA-256 fingerprint is compared to the
|
||||
<code>tls</code> record on chain. Matches → trusted. No <code>tls</code> record → the
|
||||
browser falls back to the local root CA.</span></li>
|
||||
<li><b>Provenance indicator in the URL bar.</b>
|
||||
<span>A tiny badge shows whether the page came from the chain, from Sia, or from an
|
||||
IP-configured server — with the NFT category shown on hover. The decentralized padlock.</span></li>
|
||||
</ol>
|
||||
<div class="note"><b>What Theseus is NOT.</b> Not a wallet — for that, use Deviant, Cashonize,
|
||||
Paytaca, or the built-in browser wallet at
|
||||
<a href="../#search-input">the name search</a>.
|
||||
Not a hosting service — for that, register a name and point its records at any host (Sia, VPS,
|
||||
your own server). Theseus just makes those names openable in a normal-feeling browser.</div>
|
||||
</section>
|
||||
|
||||
<section id="verify">
|
||||
<h2>Verify a download</h2>
|
||||
<p class="lede">The binaries are unsigned (Windows will show a SmartScreen warning). Before
|
||||
running, verify the SHA-256 against what the operator publishes on-chain — the current
|
||||
hashes live on the theseus.x download page itself, alongside each release:</p>
|
||||
<pre>PowerShell:
|
||||
Get-FileHash <downloaded-file>.exe
|
||||
|
||||
Bash:
|
||||
sha256sum <downloaded-file>.exe</pre>
|
||||
<p class="lede">Compare the digest to the hash panel on
|
||||
<a href="https://silentmode.st/bns/theseus.x/#verify" rel="noopener">theseus.x/#verify</a>.
|
||||
For a stronger guarantee, sign in with the wallet and check that the release-signing key
|
||||
matches the on-chain identity for <code>releases.silentmode.bch</code>.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const map = new Map();
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) map.set(el, a); });
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (!en.isIntersecting) return;
|
||||
const a = map.get(en.target); if (!a) return;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
a.classList.add("active");
|
||||
});
|
||||
}, { rootMargin: "-40% 0px -55% 0px", threshold: 0 });
|
||||
map.forEach((_, el) => io.observe(el));
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Theseus Navigator — the browser that speaks Bitcoin Cash names — Sirius.X</title>
|
||||
<meta name="description" content="Theseus Navigator is a Chromium-based browser with native Bitcoin Cash Domain Names resolution built in — no OS-level daemon, no DNS shim, no root-store install. Download for Windows.">
|
||||
<!-- Theseus.X has its own compass-mark favicon and logo — reuse them here so
|
||||
tabs on this subpage read as Theseus (compass), not Sirius (star), and
|
||||
shared-link previews carry Theseus's own OG image. Copied from
|
||||
silentmode.st/bns/theseus.x/assets/ to keep sirius.x self-contained. -->
|
||||
<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="Theseus Navigator">
|
||||
<meta property="og:title" content="Theseus Navigator — the browser that speaks Bitcoin Cash names">
|
||||
<meta property="og:description" content="Native .bch and BCNR name resolution built into a Chromium browser. No OS-level daemon, no root-store install. Download for Windows.">
|
||||
<meta property="og:url" content="https://silentmode.st/sirius-x/theseus/">
|
||||
<meta property="og:image" content="https://silentmode.st/sirius-x/theseus/assets/logo.svg">
|
||||
<meta name="theme-color" content="#0b0e14">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<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);
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
--chain:#d6ff3d; --sia:#b39ddb; --srv:#4fd1a5; --ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,#131a2a 0%,transparent 60%),radial-gradient(ellipse 900px 500px at 100% 100%,#0a0f1c 0%,transparent 55%),radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),var(--bg);
|
||||
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:980px;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)}
|
||||
.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)}
|
||||
|
||||
/* Adaptive side nav — same shape as index.html and Theseus.X */
|
||||
:root{--sn-w:200px}
|
||||
.sidenav{position:fixed;top:56px;left:0;bottom:0;width:var(--sn-w);z-index:15;
|
||||
background:transparent;padding:32px 12px 22px 18px;
|
||||
overflow-y:auto;pointer-events:none;transition:width .15s ease}
|
||||
.sidenav a{display:flex;align-items:center;gap:14px;color:var(--mut);text-decoration:none;
|
||||
padding:9px 12px;border-radius:10px;
|
||||
font-size:14.5px;font-weight:500;opacity:.35;pointer-events:auto;
|
||||
transition:opacity .12s,color .12s,background .12s,backdrop-filter .12s}
|
||||
.sidenav a .ic{flex:none;width:22px;font-size:18px;line-height:1;text-align:center;
|
||||
display:none;justify-content:center;align-items:center}
|
||||
.sidenav a .lb{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.sidenav a:hover{opacity:1;color:var(--ink);background:rgba(11,14,20,.72);backdrop-filter:blur(8px)}
|
||||
.sidenav a.active{opacity:.85;color:var(--acid)}
|
||||
.sidenav a.active:hover{opacity:1}
|
||||
section{scroll-margin-top:70px}
|
||||
@media (max-width:1180px){
|
||||
:root{--sn-w:64px}
|
||||
.sidenav{padding:22px 6px}
|
||||
.sidenav a{justify-content:center;padding:12px 4px;gap:0}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
@media (max-width:720px){
|
||||
:root{--sn-w:0}
|
||||
body{padding-bottom:64px}
|
||||
.sidenav{top:auto;bottom:0;left:0;right:0;width:100%;height:auto;
|
||||
padding:4px;flex-direction:row;background:rgba(11,14,20,.52);
|
||||
display:flex;justify-content:space-around;overflow-x:auto}
|
||||
.sidenav a{flex:1;min-width:56px;padding:10px 6px;justify-content:center;gap:0;opacity:.5}
|
||||
.sidenav a .ic{display:inline-flex}
|
||||
.sidenav a .lb{display:none}
|
||||
}
|
||||
|
||||
header.hero{text-align:center;padding:5rem 1.2rem 1rem}
|
||||
.mark{font-size:56px;line-height:1}
|
||||
h1{font-size:clamp(2rem,5.5vw,2.9rem);margin:.4rem 0 .3rem}
|
||||
h1 .g{color:var(--acid)}
|
||||
.tag{color:var(--mut);font-size:1.05rem;max-width:660px;margin:0 auto}
|
||||
.cta{margin-top:1.8rem;display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
|
||||
.btn{display:inline-block;text-decoration:none;padding:11px 20px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;transition:filter .1s}
|
||||
.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)}
|
||||
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem}
|
||||
section:first-of-type{border-top:none;margin-top:1.5rem}
|
||||
h2{font-size:1.5rem;margin:0 0 .4rem}
|
||||
h3{font-size:1.1rem;margin:0 0 .35rem}
|
||||
.lede{color:var(--mut);margin:0 0 1.6rem;max-width:720px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
||||
.card h3{margin:0 0 .4rem;display:flex;align-items:center;gap:8px}
|
||||
.card p{margin:0;color:var(--mut);font-size:14px}
|
||||
.card .cta{margin-top:14px;justify-content:flex-start}
|
||||
.card .sub{color:var(--dim);font-size:12.5px;margin-top:10px;font-family:ui-monospace,monospace;word-break:break-all}
|
||||
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
|
||||
.d-chain{background:var(--chain)} .d-sia{background:var(--sia)} .d-srv{background:var(--srv)}
|
||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||||
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
|
||||
.note.warn{border-left-color:var(--warn)}
|
||||
.note b{color:var(--ink)}
|
||||
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
|
||||
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
|
||||
font-family:ui-monospace,monospace;font-size:13px;color:var(--ink);margin:1rem 0}
|
||||
ol.steps{counter-reset:s;list-style:none;padding:0;margin:1.2rem 0 0;display:grid;gap:10px}
|
||||
ol.steps li{counter-increment:s;display:grid;grid-template-columns:30px 1fr;gap:14px;align-items:start;
|
||||
background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:13px 16px}
|
||||
ol.steps li::before{content:counter(s);grid-column:1;grid-row:1;
|
||||
display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
|
||||
background:rgba(214,255,61,.14);color:var(--acid);font-size:12.5px;font-weight:700}
|
||||
ol.steps li > *{grid-column:2;min-width:0}
|
||||
ol.steps b{color:var(--ink)} ol.steps span{color:var(--mut);font-size:14px}
|
||||
.pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(214,255,61,.13);color:var(--acid);margin-left:8px;vertical-align:middle;font-weight:500}
|
||||
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
/* Deviant-family fonts + Fraunces/Ubuntu reader toggle. */
|
||||
h1,h2,h3{font-family:'Fraunces',Georgia,serif;font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:'JetBrains Mono',ui-monospace,monospace}
|
||||
body{font-family:'DM Sans','Inter',system-ui,sans-serif !important;font-feature-settings:"ss01","ss02"}
|
||||
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:'JetBrains Mono',ui-monospace,monospace !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:'JetBrains Mono',monospace;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}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg><span class="g">Sirius.X</span></a>
|
||||
<a href="../tld.html">🌐 TLD</a>
|
||||
<a href="../market/">🛒 Market</a>
|
||||
<a href="./" class="here"><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<span class="g">.X</span></a>
|
||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||
</nav>
|
||||
|
||||
<aside class="sidenav" aria-label="On this page">
|
||||
<a href="#download" title="Download" ><span class="ic" aria-hidden="true">⬇</span><span class="lb">Download</span></a>
|
||||
<a href="#how" title="How Theseus works" ><span class="ic" aria-hidden="true">⚙️</span><span class="lb">How it works</span></a>
|
||||
<a href="#verify" title="Verify a download" ><span class="ic" aria-hidden="true">🔐</span><span class="lb">Verify</span></a>
|
||||
</aside>
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark"><img src="./assets/favicon.svg" alt="" width="72" height="72" style="vertical-align:middle;filter:drop-shadow(0 0 24px rgba(214,255,61,.15))"></div>
|
||||
<h1>Theseus <span class="g">Navigator</span></h1>
|
||||
<p class="tag">A Chromium-based browser with native <b>Bitcoin Cash Domain Names</b> resolution built in.
|
||||
No OS-level daemon, no DNS shim, no root-store install. Type <code>hello.bch</code> in the
|
||||
address bar and it just works.</p>
|
||||
<div class="cta">
|
||||
<a class="btn acid" href="#download">Download for Windows</a>
|
||||
<a class="btn ghost" href="#how">How it works</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<section id="download">
|
||||
<h2>Download</h2>
|
||||
<p class="lede">Theseus ships from its own site — <a href="https://silentmode.st/bns/theseus.x/#download" rel="noopener"><b>theseus.x</b></a> — so the version and checksums you see are always the current build, not a snapshot cached here. Windows installer + portable, plus Ariadne Resolver (lighter, no-browser).</p>
|
||||
<div class="cta" style="justify-content:flex-start;margin-top:1.2rem">
|
||||
<a class="btn acid" href="https://silentmode.st/bns/theseus.x/#download" rel="noopener">Open theseus.x downloads →</a>
|
||||
<a class="btn ghost" href="https://silentmode.st/bns/theseus.x/" rel="noopener">Full Theseus site</a>
|
||||
</div>
|
||||
<div class="note" style="margin-top:1.4rem"><b>Why not list versions here?</b> Theseus updates weekly; a hardcoded version on this page went stale within days last time. theseus.x is the single source of truth — its own operator ships the current build, and Sirius.X links there instead of duplicating.</div>
|
||||
</section>
|
||||
|
||||
<section id="how">
|
||||
<h2>How Theseus works</h2>
|
||||
<p class="lede">Theseus is a Chromium wrapper (Electron) with two things regular browsers don't
|
||||
have: in-process BCNR name resolution, and a certificate-verify hook that trusts on-chain
|
||||
TLS fingerprints instead of the OS root store for BCNR hostnames.</p>
|
||||
<ol class="steps">
|
||||
<li><b>Address bar accepts <code>.bch</code> / <code>.x</code> / any BCNR TLD.</b>
|
||||
<span>The URL parser is short-circuited: any hostname whose TLD is in the registry is
|
||||
resolved by walking the chain via the built-in <code>bns.js</code> — never through DNS.</span></li>
|
||||
<li><b>Records decide how the site is served.</b>
|
||||
<span><code>h</code> renders inline HTML from the OP_RETURN payload itself. <code>s3</code>
|
||||
serves from a Sia bucket via the in-app gateway. <code>ip</code> connects to a server by
|
||||
IP. <code>u</code> redirects. Priority is handled by the shared <code>record-picker</code>.</span></li>
|
||||
<li><b>TLS verifies against on-chain fingerprints.</b>
|
||||
<span>Electron's <code>setCertificateVerifyProc</code> is intercepted: for BCNR hostnames,
|
||||
instead of walking the OS trust chain, the leaf's SHA-256 fingerprint is compared to the
|
||||
<code>tls</code> record on chain. Matches → trusted. No <code>tls</code> record → the
|
||||
browser falls back to the local root CA.</span></li>
|
||||
<li><b>Provenance indicator in the URL bar.</b>
|
||||
<span>A tiny badge shows whether the page came from the chain, from Sia, or from an
|
||||
IP-configured server — with the NFT category shown on hover. The decentralized padlock.</span></li>
|
||||
</ol>
|
||||
<div class="note"><b>What Theseus is NOT.</b> Not a wallet — for that, use Deviant, Cashonize,
|
||||
Paytaca, or the built-in browser wallet at
|
||||
<a href="../#search-input">the name search</a>.
|
||||
Not a hosting service — for that, register a name and point its records at any host (Sia, VPS,
|
||||
your own server). Theseus just makes those names openable in a normal-feeling browser.</div>
|
||||
</section>
|
||||
|
||||
<section id="verify">
|
||||
<h2>Verify a download</h2>
|
||||
<p class="lede">The binaries are unsigned (Windows will show a SmartScreen warning). Before
|
||||
running, verify the SHA-256 against what the operator publishes on-chain — the current
|
||||
hashes live on the theseus.x download page itself, alongside each release:</p>
|
||||
<pre>PowerShell:
|
||||
Get-FileHash <downloaded-file>.exe
|
||||
|
||||
Bash:
|
||||
sha256sum <downloaded-file>.exe</pre>
|
||||
<p class="lede">Compare the digest to the hash panel on
|
||||
<a href="https://silentmode.st/bns/theseus.x/#verify" rel="noopener">theseus.x/#verify</a>.
|
||||
For a stronger guarantee, sign in with the wallet and check that the release-signing key
|
||||
matches the on-chain identity for <code>releases.silentmode.bch</code>.</p>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer id="site-footer"></footer>
|
||||
|
||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||
<script src="../js/pricing.js?v=20260920bits"></script>
|
||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
||||
<script defer src="../js/theme.js?v=20260920compact"></script>
|
||||
<script defer src="../js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
// Sidenav scroll-spy: highlight the section currently in view.
|
||||
// Uses scroll-position tracking so short sections at the end of the page
|
||||
// still activate their link when the reader scrolls to them.
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const entries = [];
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
|
||||
if (!entries.length) return;
|
||||
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
|
||||
const update = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
|
||||
const line = window.scrollY + window.innerHeight * 0.35;
|
||||
let active = entries[0].link;
|
||||
for (const e of entries) {
|
||||
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
|
||||
else break;
|
||||
}
|
||||
setActive(active);
|
||||
};
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
39
tld.html
39
tld.html
|
|
@ -148,9 +148,9 @@
|
|||
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" } }
|
||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js?v=20260920usd" } }
|
||||
</script>
|
||||
<script src="./js/i18n.js?v=20260920f"></script>
|
||||
<script src="./js/i18n.js?v=20260920g"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -341,7 +341,7 @@
|
|||
})();
|
||||
</script>
|
||||
|
||||
<script type="module" src="./js/register-flow.js?v=20260920bits"></script>
|
||||
<script type="module" src="./js/register-flow.js?v=20260920usd"></script>
|
||||
<script src="./js/pricing.js?v=20260920bits"></script>
|
||||
<script src="./js/session.js?v=20260917dash"></script>
|
||||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||||
|
|
@ -349,20 +349,31 @@
|
|||
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||
<script defer src="./js/profile-menu.js?v=20260920bits"></script>
|
||||
<script>
|
||||
// Sidenav scroll-spy: highlight the section currently in view.
|
||||
// Uses scroll-position tracking so short sections at the end of the page
|
||||
// still activate their link when the reader scrolls to them.
|
||||
(() => {
|
||||
const links = document.querySelectorAll(".sidenav a[href^='#']");
|
||||
if (!links.length) return;
|
||||
const map = new Map();
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) map.set(el, a); });
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
entries.forEach((en) => {
|
||||
if (!en.isIntersecting) return;
|
||||
const a = map.get(en.target); if (!a) return;
|
||||
links.forEach((l) => l.classList.remove("active"));
|
||||
a.classList.add("active");
|
||||
});
|
||||
}, { rootMargin: "-40% 0px -55% 0px", threshold: 0 });
|
||||
map.forEach((_, el) => io.observe(el));
|
||||
const entries = [];
|
||||
links.forEach((a) => { const el = document.querySelector(a.getAttribute("href")); if (el) entries.push({ el, link: a }); });
|
||||
if (!entries.length) return;
|
||||
const setActive = (link) => { links.forEach((l) => l.classList.remove("active")); link.classList.add("active"); };
|
||||
const update = () => {
|
||||
const doc = document.documentElement;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
if (scrollBottom >= (doc.scrollHeight - 4)) { setActive(entries[entries.length - 1].link); return; }
|
||||
const line = window.scrollY + window.innerHeight * 0.35;
|
||||
let active = entries[0].link;
|
||||
for (const e of entries) {
|
||||
if (e.el.getBoundingClientRect().top + window.scrollY <= line) active = e.link;
|
||||
else break;
|
||||
}
|
||||
setActive(active);
|
||||
};
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue