2026-09-06 18:53:17 +02:00
|
|
|
// Transforms the "🔑 Sign in" nav pill on every sirius.x page into a dropdown
|
|
|
|
|
// exposing the three wallet-onboarding options directly (New / Import /
|
|
|
|
|
// WizardConnect). Signed-in state is read from localStorage.'siriusProfile'
|
|
|
|
|
// (portal.html writes/clears it on sign-in / sign-out) — when present, the
|
|
|
|
|
// menu switches to "profile" mode: address + sign-out link.
|
|
|
|
|
//
|
|
|
|
|
// One include line per page: <script defer src="/sirius-x/js/profile-menu.js"></script>
|
|
|
|
|
// (path is absolute to silentmode.st root; the BCNR gateway serves the same URL).
|
|
|
|
|
|
|
|
|
|
(() => {
|
|
|
|
|
const NS = "silentmode.st";
|
|
|
|
|
const PORTAL_URL = "/sirius-x/portal.html";
|
|
|
|
|
|
|
|
|
|
// Find the .portal anchor already in the nav (the "Sign in" pill).
|
|
|
|
|
const anchor = document.querySelector(".topnav a.portal");
|
|
|
|
|
if (!anchor) return; // page has no nav-portal button; nothing to do
|
|
|
|
|
|
|
|
|
|
// Read profile state — portal.html sets this after successful sign-in with
|
2026-09-07 00:15:43 +02:00
|
|
|
// { address, tokenAddress, signedInAt }; clears on sign-out. The read has to
|
|
|
|
|
// run every time the button label refreshes or the menu opens, because
|
|
|
|
|
// sign-in can happen on this same page (portal.html) or in another tab
|
|
|
|
|
// *after* this script's initial run.
|
2026-09-06 18:53:17 +02:00
|
|
|
const readProfile = () => {
|
|
|
|
|
try { return JSON.parse(localStorage.getItem("siriusProfile") || "null"); } catch { return null; }
|
|
|
|
|
};
|
|
|
|
|
|
2026-09-07 00:15:43 +02:00
|
|
|
// Build wrapper + button + (empty) menu around the existing pill.
|
2026-09-06 18:53:17 +02:00
|
|
|
const wrap = document.createElement("div");
|
|
|
|
|
wrap.className = "profile-wrap";
|
|
|
|
|
const btn = document.createElement("button");
|
|
|
|
|
btn.type = "button";
|
|
|
|
|
btn.className = anchor.className; // reuse .portal (and .here if present)
|
|
|
|
|
btn.setAttribute("aria-haspopup", "menu");
|
|
|
|
|
btn.setAttribute("aria-expanded", "false");
|
|
|
|
|
const menu = document.createElement("div");
|
|
|
|
|
menu.className = "profile-menu";
|
|
|
|
|
menu.setAttribute("role", "menu");
|
|
|
|
|
menu.hidden = true;
|
|
|
|
|
|
2026-09-07 00:15:43 +02:00
|
|
|
// Re-renders the button label AND the menu innerHTML from the current
|
|
|
|
|
// profile in localStorage. Called at load, on every open(), and whenever
|
|
|
|
|
// another tab updates the storage key.
|
|
|
|
|
function render() {
|
|
|
|
|
const profile = readProfile();
|
|
|
|
|
btn.textContent = profile ? "🔑 " + shortAddr(profile.address) + " ▾" : "🔑 Wallet ▾";
|
|
|
|
|
if (profile) {
|
|
|
|
|
menu.innerHTML = `
|
|
|
|
|
<div class="profile-header">
|
|
|
|
|
<span class="profile-hint">Signed in</span>
|
|
|
|
|
<code>${escapeAttr(profile.address)}</code>
|
|
|
|
|
</div>
|
|
|
|
|
<a href="${PORTAL_URL}" role="menuitem">📋 My names</a>
|
|
|
|
|
<a href="/sirius-x/admin/" role="menuitem">🛠 Admin</a>
|
|
|
|
|
<hr>
|
|
|
|
|
<a href="#" role="menuitem" data-action="signout">🚪 Sign out</a>
|
|
|
|
|
`;
|
|
|
|
|
} else {
|
|
|
|
|
menu.innerHTML = `
|
|
|
|
|
<div class="profile-header"><span class="profile-hint">Wallet onboarding</span></div>
|
|
|
|
|
<a href="${PORTAL_URL}?mode=new" role="menuitem">🆕 New wallet <span class="mi-hint">generate a fresh seed</span></a>
|
|
|
|
|
<a href="${PORTAL_URL}?mode=import" role="menuitem">📥 Add wallet <span class="mi-hint">import a seed phrase</span></a>
|
|
|
|
|
<a href="${PORTAL_URL}?mode=wc" role="menuitem">🔗 WizardConnect <span class="mi-hint">Cashonize / Paytaca</span></a>
|
|
|
|
|
<hr>
|
|
|
|
|
<a href="${PORTAL_URL}" role="menuitem">🔑 Open sign-in page →</a>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2026-09-06 18:53:17 +02:00
|
|
|
}
|
2026-09-07 00:15:43 +02:00
|
|
|
render();
|
2026-09-06 18:53:17 +02:00
|
|
|
|
2026-09-07 00:15:43 +02:00
|
|
|
// Toggle + close-on-outside. Re-render on open so a sign-in that happened
|
|
|
|
|
// after page load (same tab: portal.html; other tab: storage event) shows
|
|
|
|
|
// up without a full reload.
|
|
|
|
|
const open = () => { render(); menu.hidden = false; btn.setAttribute("aria-expanded", "true"); };
|
2026-09-06 18:53:17 +02:00
|
|
|
const close = () => { menu.hidden = true; btn.setAttribute("aria-expanded", "false"); };
|
2026-09-07 00:15:43 +02:00
|
|
|
// Storage events fire only in *other* tabs, but they cover the cross-tab case.
|
|
|
|
|
// Same-tab: portal.html dispatches "siriusProfileChanged" on window after
|
|
|
|
|
// it writes/clears the key; render on that too.
|
|
|
|
|
window.addEventListener("storage", (e) => { if (e.key === "siriusProfile") render(); });
|
|
|
|
|
window.addEventListener("siriusProfileChanged", render);
|
2026-09-06 18:53:17 +02:00
|
|
|
btn.addEventListener("click", (e) => { e.stopPropagation(); menu.hidden ? open() : close(); });
|
|
|
|
|
document.addEventListener("click", (e) => { if (!wrap.contains(e.target)) close(); });
|
|
|
|
|
document.addEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
|
|
|
|
|
|
|
|
|
|
// Sign-out clears localStorage and reloads (portal.html re-reads state).
|
|
|
|
|
menu.addEventListener("click", (e) => {
|
|
|
|
|
const t = e.target.closest("[data-action]");
|
|
|
|
|
if (t?.dataset.action === "signout") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
try { localStorage.removeItem("siriusProfile"); } catch {}
|
|
|
|
|
// Also reload the current page so any signed-in UI resets.
|
|
|
|
|
location.reload();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wrap.appendChild(btn);
|
|
|
|
|
wrap.appendChild(menu);
|
|
|
|
|
anchor.replaceWith(wrap);
|
|
|
|
|
|
|
|
|
|
// Inject the dropdown's CSS once. Kept inline so a single script tag is
|
|
|
|
|
// the whole install; consumers don't need a matching stylesheet.
|
|
|
|
|
if (!document.getElementById("profile-menu-css")) {
|
|
|
|
|
const s = document.createElement("style");
|
|
|
|
|
s.id = "profile-menu-css";
|
|
|
|
|
s.textContent = `
|
|
|
|
|
.topnav .profile-wrap { position: relative; margin-left: auto; }
|
|
|
|
|
.topnav .profile-wrap button.portal { cursor: pointer; font-family: inherit; }
|
|
|
|
|
.topnav .profile-menu {
|
|
|
|
|
position: absolute; right: 0; top: calc(100% + 6px);
|
|
|
|
|
min-width: 240px; z-index: 30;
|
|
|
|
|
background: rgba(20, 26, 36, .98); backdrop-filter: blur(8px);
|
|
|
|
|
border: 1px solid rgba(255,255,255,.12); border-radius: 12px;
|
|
|
|
|
padding: 8px; display: flex; flex-direction: column; gap: 2px;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0,0,0,.4);
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu[hidden] { display: none; }
|
|
|
|
|
.topnav .profile-menu > a {
|
|
|
|
|
display: flex; flex-direction: column; gap: 2px;
|
|
|
|
|
padding: 8px 10px; border-radius: 8px;
|
|
|
|
|
color: #e7eaf1; text-decoration: none; font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu > a:hover { background: rgba(214,255,61,.10); color: #d6ff3d; }
|
|
|
|
|
.topnav .profile-menu .mi-hint {
|
|
|
|
|
color: #5e6678; font-size: 11.5px; font-weight: 400; letter-spacing: 0;
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu hr {
|
|
|
|
|
border: 0; border-top: 1px solid rgba(255,255,255,.08); margin: 4px 2px;
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu .profile-header {
|
|
|
|
|
padding: 6px 10px 10px; display: flex; flex-direction: column; gap: 4px;
|
|
|
|
|
border-bottom: 1px solid rgba(255,255,255,.08); margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu .profile-hint {
|
|
|
|
|
color: #8b98a9; font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
|
|
|
|
|
}
|
|
|
|
|
.topnav .profile-menu code {
|
|
|
|
|
font-family: ui-monospace, monospace; font-size: 11.5px;
|
|
|
|
|
color: #e7eaf1; word-break: break-all; background: transparent;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
document.head.appendChild(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------- helpers ----------
|
|
|
|
|
function shortAddr(a) {
|
|
|
|
|
if (!a) return "wallet";
|
|
|
|
|
const i = a.indexOf(":");
|
|
|
|
|
const body = i > 0 ? a.slice(i + 1) : a;
|
|
|
|
|
return body.slice(0, 6) + "…" + body.slice(-4);
|
|
|
|
|
}
|
|
|
|
|
function escapeAttr(s) {
|
|
|
|
|
return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[c]));
|
|
|
|
|
}
|
|
|
|
|
})();
|