hermes signin: one Sign in button + two chips, nav + mobile dark bg

Signin screen (both the Theseus addon panel and hermes.x/messenger):
- Open with Theseus ID and Create a new account stay as top-level
  actions (unchanged).
- Beneath them, a 'Sign in' divider + two chips [Name + password]
  [Seed phrase] drive one unified Sign in button. Toggle swaps the
  input area between the password form and the mnemonic textarea.
  The old 'Sign in with name + password' and 'Open an existing
  account' secondary buttons are gone.

Site nav restructure (site-hermes-x/):
- Header now: Hermes.X brand + Overview + Sign in icon (opens
  /messenger). Messenger tab, Branding tab, and Get Theseus pill are
  all removed from the header.
- Footer carries Branding + Open Messenger + Get Theseus links as
  secondary nav. Branding is 'here' on the brand kit page.

Mobile dark bg fix (shared.css):
- color-scheme: dark on :root so mobile browsers don't flip the
  page into a light theme that makes the sky-blue mark unreadable.
- html { background: var(--bg) } so the pre-body paint is dark too
  (Safari / Firefox mobile briefly show the html bg before body css
  loads).
This commit is contained in:
Local Dev 2026-10-22 03:08:40 +03:00
parent 975edba88f
commit 5984d39253

View file

@ -243,8 +243,6 @@
<div class="actions">
<button class="primary" id="useVaultBtn" disabled>Open with Theseus ID</button>
<button class="secondary" id="toggleCreateBtn">Create a new account</button>
<button class="secondary" id="toggleNamePwBtn">Sign in with name + password</button>
<button class="secondary" id="toggleMnemonicBtn">Open an existing account</button>
</div>
<div class="mnemonic" id="createBox">
<input id="createHandle" type="text" placeholder="alice" autocomplete="off" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
@ -253,16 +251,30 @@
<button class="primary" id="useCreateBtn">Create account</button>
<div class="hint">You'll get a handle like <code>alice.erm</code>. We'll generate a seed phrase — back it up in Settings.</div>
</div>
<div class="mnemonic" id="namePwBox">
<div style="display:flex;align-items:center;gap:10px;width:100%;max-width:300px;margin-top:8px">
<span style="flex:1;height:1px;background:var(--line)"></span>
<span style="color:var(--mut);font-size:11.5px;letter-spacing:.4px;text-transform:uppercase">Sign in</span>
<span style="flex:1;height:1px;background:var(--line)"></span>
</div>
<div class="signin-chips" style="display:flex;gap:6px;width:100%;max-width:300px">
<button class="chip chip-active" data-method="namepw" style="flex:1;font:inherit;font-size:12px;padding:7px 8px;background:var(--sky);color:#06111a;border:1px solid var(--sky);border-radius:6px;cursor:pointer;font-weight:600">Name + password</button>
<button class="chip" data-method="seed" style="flex:1;font:inherit;font-size:12px;padding:7px 8px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:6px;cursor:pointer">Seed phrase</button>
</div>
<div id="methodNamepw" class="method" style="width:100%;max-width:300px;display:flex;flex-direction:column;gap:8px">
<input id="signinHandle" type="text" placeholder="alice" autocomplete="username" spellcheck="false" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
<input id="signinPassword" type="password" placeholder="password" autocomplete="current-password" style="font-family:inherit;font-size:13px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 10px;width:100%">
<button class="primary" id="useNamePwBtn">Sign in</button>
<div class="hint">Only works on this device. From a new device, use seed phrase.</div>
</div>
<div class="mnemonic" id="mnemonicBox">
<textarea id="mnemonicInput" placeholder="12 or 24 words, separated by spaces" autocomplete="off" spellcheck="false"></textarea>
<button class="primary" id="useMnemonicBtn">Open</button>
<div id="methodSeed" class="method" style="width:100%;max-width:300px;display:none;flex-direction:column;gap:8px">
<textarea id="mnemonicInput" placeholder="nsec1… or 12/24-word mnemonic — stays in this tab, never sent to the server" autocomplete="off" spellcheck="false" style="font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:10px;width:100%;min-height:86px;resize:vertical"></textarea>
<div class="hint">Paste your 12 or 24 words.</div>
</div>
<button class="primary" id="signinBtn" style="width:100%;max-width:300px;margin-top:4px">Sign in</button>
<div class="err" id="signinErr"></div>
<div class="hint" id="vaultHint"></div>
<div class="hint" style="margin-top:4px;max-width:300px">You'll stay signed in. Sign out from the ⋯ menu.</div>
@ -687,36 +699,6 @@ $("toggleCreateBtn").addEventListener("click", () => {
$("createBox").classList.toggle("show");
if ($("createBox").classList.contains("show")) $("createHandle").focus();
});
$("toggleNamePwBtn").addEventListener("click", () => {
$("createBox").classList.remove("show");
$("mnemonicBox").classList.remove("show");
$("namePwBox").classList.toggle("show");
if ($("namePwBox").classList.contains("show")) $("signinHandle").focus();
});
$("useNamePwBtn").addEventListener("click", async () => {
const handle = $("signinHandle").value.trim();
const password = $("signinPassword").value;
if (!handle) { err($("signinErr"), "enter your name"); return; }
if (!password) { err($("signinErr"), "enter your password"); return; }
err($("signinErr"), "");
const btn = $("useNamePwBtn"); const prev = btn.textContent;
btn.disabled = true; btn.textContent = "Signing in…";
try {
const r = await bridge.invoke("getBackup", { password });
if (!r || !r.ok) throw new Error((r && r.err) || "wrong password or no local account");
const r2 = await bridge.invoke("init", { mnemonic: r.mnemonic, handle: handle, remember: true });
if (!r2 || !r2.ok) throw new Error((r2 && r2.err) || "sign-in failed");
showApp({ pkHex: r2.pkHex, npub: r2.npub, handle: r2.handle || (handle + ".erm") });
} catch (e) {
err($("signinErr"), (e && e.message) || String(e));
} finally { btn.disabled = false; btn.textContent = prev; }
});
$("toggleMnemonicBtn").addEventListener("click", () => {
$("createBox").classList.remove("show");
$("namePwBox").classList.remove("show");
$("mnemonicBox").classList.toggle("show");
if ($("mnemonicBox").classList.contains("show")) $("mnemonicInput").focus();
});
$("useCreateBtn").addEventListener("click", () => {
const handle = $("createHandle").value.trim();
const p1 = $("createPassword").value;
@ -726,10 +708,55 @@ $("useCreateBtn").addEventListener("click", () => {
if (p1 !== p2) { err($("signinErr"), "passwords don't match"); return; }
initWithOpts({ create: true, handle, password: p1 });
});
$("useMnemonicBtn").addEventListener("click", () => {
const mnemonic = $("mnemonicInput").value;
initWithOpts({ useVault: false, mnemonic });
// --- unified Sign in button + chip switcher ---
function switchMethod(name) {
for (const c of document.querySelectorAll(".signin-chips .chip")) {
const active = c.dataset.method === name;
c.classList.toggle("chip-active", active);
c.style.background = active ? "var(--sky)" : "var(--panel)";
c.style.color = active ? "#06111a" : "var(--ink)";
c.style.borderColor = active ? "var(--sky)" : "var(--line)";
c.style.fontWeight = active ? "600" : "400";
}
$("methodNamepw").style.display = name === "namepw" ? "flex" : "none";
$("methodSeed").style.display = name === "seed" ? "flex" : "none";
state._signinMethod = name;
const focusEl = name === "namepw" ? $("signinHandle") : $("mnemonicInput");
setTimeout(() => focusEl && focusEl.focus(), 50);
}
state._signinMethod = "namepw";
for (const c of document.querySelectorAll(".signin-chips .chip")) {
c.addEventListener("click", () => switchMethod(c.dataset.method));
}
$("signinBtn").addEventListener("click", async () => {
const m = state._signinMethod || "namepw";
err($("signinErr"), "");
if (m === "namepw") {
const handle = $("signinHandle").value.trim();
const password = $("signinPassword").value;
if (!handle) { err($("signinErr"), "enter your name"); return; }
if (!password) { err($("signinErr"), "enter your password"); return; }
const btn = $("signinBtn"); const prev = btn.textContent;
btn.disabled = true; btn.textContent = "Signing in\u2026";
try {
const r = await bridge.invoke("getBackup", { password });
if (!r || !r.ok) throw new Error((r && r.err) || "wrong password or no local account");
const r2 = await bridge.invoke("init", { mnemonic: r.mnemonic, handle, remember: true });
if (!r2 || !r2.ok) throw new Error((r2 && r2.err) || "sign-in failed");
showApp({ pkHex: r2.pkHex, npub: r2.npub, handle: r2.handle || handle });
} catch (e) {
err($("signinErr"), (e && e.message) || String(e));
} finally { btn.disabled = false; btn.textContent = prev; }
} else {
const mnemonic = $("mnemonicInput").value.trim();
if (!mnemonic || mnemonic.split(/\s+/).length < 12) {
err($("signinErr"), "paste a 12- or 24-word mnemonic"); return;
}
initWithOpts({ useVault: false, mnemonic });
}
});
$("popOutBtn").addEventListener("click", () => bridge.invoke("open-messages"));
$("menuBtn").addEventListener("click", (e) => {
e.stopPropagation();