hermes-site/messenger/index.html
Local Dev 9cf5901935 hermes: in-panel messenger with Theseus ID signin, spread-wings mark
The sidebar panel used to be a launcher that opened the standalone Messages
window; now it IS the messenger — signin screen with "Open with Theseus ID"
(derives the Nostr key from the unlocked vault seed) or "Open an existing
account" (12/24-word mnemonic fallback), then a contacts + thread view with
a narrow-width single-column fallback. The ⧉ button still pops the standalone
window out, so the Ctrl+Shift+E flow keeps working.

- main.js: expose a global._hermesAPI with status/canUseVault/init/inbox/
  send/markThreadRead/contacts* so the addon can proxy panel calls without
  granting it ipcRenderer or crossing the HERMES_ONLY sender gate.
  hermesEmit also fans out on an in-process __hermes_bus so the panel gets
  live inbox/status pushes decoupled from hermesWin's lifetime.
- bundled-addons/hermes/index.js: proxies each allow-listed method onto
  global._hermesAPI, forwards bus events to api.emit for the panel.
- panel.html: signin + app screens, mobile-style single column under 540px.
- Mark updated to the spread-wings variant: envelope sized up (34×22),
  three tapered trails extending outside the viewBox so the wings don't
  bunch at the back of the envelope at any size. Applied to the addon
  icon (manifest base64), the panel header, both /hermes sites, and
  site-hermes-x's favicon + mark SVGs.
2026-10-21 03:00:17 +03:00

242 lines
12 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hermes.x/messenger — browser messenger (preview)</title>
<meta name="description" content="The web version of Hermes: encrypted DMs over Nostr, addressed by .bch name, running in your browser. Preview of the UI; functional release pending.">
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="../assets/shared.css">
<style>
/* ---- status banner ---- */
.status-banner{
background:rgba(255,199,95,.08);
border:1px solid rgba(255,199,95,.26);
border-radius:12px;
padding:14px 18px;
margin:1.5rem auto;
max-width:780px;
font-size:14px;
color:var(--ink);
display:flex;gap:14px;align-items:flex-start;
}
.status-banner .dot{
width:10px;height:10px;border-radius:50%;background:var(--warn);
margin-top:6px;flex-shrink:0;animation:pulse 2s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity:.4 } }
.status-banner b{color:var(--warn)}
/* ---- mockup frame ---- */
.mockup{
background:var(--panel);border:1px solid var(--line2);border-radius:16px;
overflow:hidden;margin:1.5rem auto;max-width:860px;
box-shadow:0 20px 60px rgba(0,0,0,.4);
}
.mockup .window-bar{
display:flex;align-items:center;gap:8px;padding:10px 14px;
background:var(--panel2);border-bottom:1px solid var(--line);
}
.mockup .window-bar .dot{width:11px;height:11px;border-radius:50%;background:#444a56}
.mockup .window-bar .dot:nth-child(1){background:#ff5f57}
.mockup .window-bar .dot:nth-child(2){background:#febc2e}
.mockup .window-bar .dot:nth-child(3){background:#28c840}
.mockup .window-bar .title{color:var(--mut);font-size:12px;margin-left:10px}
.mockup .window-bar .status{margin-left:auto;font-size:11px;color:var(--ok);padding:2px 10px;border-radius:999px;background:rgba(79,209,165,.14)}
.mockup .window-bar .npub{color:var(--mut);font-size:11px;font-family:ui-monospace,monospace;margin-left:12px}
.mockup .npub b{color:var(--sky)}
.mockup .app{display:grid;grid-template-columns:220px 1fr;min-height:420px}
.mockup .side{background:var(--panel2);border-right:1px solid var(--line);display:flex;flex-direction:column}
.mockup .side-head{padding:10px;display:flex;gap:6px;border-bottom:1px solid var(--line)}
.mockup .side-head input{flex:1;background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:6px 10px;color:var(--mut);font-size:12px}
.mockup .side-head button{background:var(--sky);color:#06111a;border:none;border-radius:6px;padding:6px 10px;font-weight:600;cursor:pointer;font-size:13px}
.mockup .contact{display:flex;gap:10px;align-items:center;padding:9px 12px;border-bottom:1px solid rgba(255,255,255,.03);cursor:pointer}
.mockup .contact.active{background:var(--panel3)}
.mockup .contact .avatar{width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#2a3a4a,#1a2432);display:flex;align-items:center;justify-content:center;font-size:11px;color:var(--mut);font-weight:600;text-transform:uppercase}
.mockup .contact .meta{flex:1;min-width:0}
.mockup .contact .name{font-size:12.5px;color:var(--ink);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mockup .contact .last{font-size:11px;color:var(--mut);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mockup .contact .badge{background:var(--sky);color:#06111a;font-size:10px;font-weight:700;padding:1px 7px;border-radius:999px}
.mockup .thread{display:flex;flex-direction:column;background:var(--bg)}
.mockup .thread-head{padding:12px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px}
.mockup .thread-head .name{font-weight:600;font-size:13px}
.mockup .thread-head .pk{font-family:ui-monospace,monospace;font-size:10.5px;color:var(--mut)}
.mockup .thread-body{flex:1;padding:14px 16px;display:flex;flex-direction:column;gap:6px}
.mockup .bubble{max-width:70%;padding:7px 11px;border-radius:14px;font-size:13px;line-height:1.4}
.mockup .bubble.in{background:#1d2634;align-self:flex-start;border-bottom-left-radius:4px}
.mockup .bubble.out{background:#0d2a3a;align-self:flex-end;border-bottom-right-radius:4px;color:#d9ecfa}
.mockup .bubble .time{display:block;font-size:10px;color:var(--dim);margin-top:3px}
.mockup .compose{display:flex;gap:8px;padding:10px 14px;border-top:1px solid var(--line);background:var(--panel)}
.mockup .compose input{flex:1;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:9px 11px;color:var(--mut);font-size:13px}
.mockup .compose button{background:var(--sky);color:#06111a;border:none;padding:0 18px;border-radius:8px;font-weight:600;cursor:pointer}
.plan-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media (max-width:700px){.plan-grid{grid-template-columns:1fr}}
.plan{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px}
.plan h3{display:flex;align-items:center;gap:8px}
.plan ul{margin:.5rem 0 0;padding:0 0 0 1.2rem;color:var(--mut);font-size:14px;line-height:1.7}
.mockup-caption{text-align:center;color:var(--dim);font-size:12px;margin-top:-.5rem;margin-bottom:1.5rem}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../">
<svg viewBox="0 0 64 64" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" transform="rotate(-20 32 32)"><rect x="18" y="24" width="34" height="22" rx="1.5"/><path d="M18 25 L35 39 L52 25"/><path d="M18 36 L-4 36"/></g></svg>
hermes<span class="x">.x</span>
</a>
<a href="../">Overview</a>
<a href="./" class="here">Messenger</a>
<a href="../branding/">Branding</a>
<a href="https://theseus.x/" class="get">Get Theseus</a>
</nav>
<header class="hero">
<div class="mark" style="color:var(--sky); line-height:0; display:inline-flex" aria-hidden="true">
<svg viewBox="0 0 64 64" width="96" height="96"><g fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" transform="rotate(-20 32 32)"><rect x="18" y="24" width="34" height="22" rx="1.5"/><path d="M18 25 L35 39 L52 25"/><path d="M18 28 L-4 24"/><path d="M18 36 L-6 36"/><path d="M18 44 L0 46"/></g></svg>
</div>
<h1>Messenger</h1>
<p class="tag">Hermes in a browser tab — end-to-end encrypted DMs over Nostr,
addressed by <code class="inline">.bch</code> name. The desktop client has
the richest flavour; the web messenger meets you where you are.</p>
</header>
<div class="status-banner">
<div class="dot"></div>
<div>
<b>Preview.</b> The desktop messenger inside <a href="https://theseus.x/">Theseus</a>
is live today — threaded chat, persistent history, address book, keyboard
shortcut <code class="inline">Ctrl+Shift+E</code>. The web-only client
(what this page becomes) is in development: BIP-39 → Nostr key derivation,
BCH-name resolution via gateway, NIP-17 wrap/unwrap, WebSocket to relays,
IndexedDB inbox. Everything client-side, nothing uploaded.
</div>
</div>
<div class="mockup" aria-label="Messenger UI preview">
<div class="window-bar">
<div class="dot"></div><div class="dot"></div><div class="dot"></div>
<div class="title">Hermes · hermes.x/messenger</div>
<span class="npub">your npub: <b>npub1ab…z89</b></span>
<span class="status">1/1 relay</span>
</div>
<div class="app">
<aside class="side">
<div class="side-head"><input value="Search contacts…" readonly><button>+</button></div>
<div class="contact">
<div class="avatar">AL</div>
<div class="meta"><div class="name">alice</div><div class="last">you: see you tomorrow</div></div>
</div>
<div class="contact active">
<div class="avatar">BO</div>
<div class="meta"><div class="name">bob</div><div class="last">great, send me the link</div></div>
<div class="badge">2</div>
</div>
<div class="contact">
<div class="avatar">TE</div>
<div class="meta"><div class="name">test</div><div class="last">chipnet is working</div></div>
</div>
</aside>
<section class="thread">
<div class="thread-head">
<div>
<div class="name">bob</div>
<div class="pk">023bf8c1…9ad2</div>
</div>
</div>
<div class="thread-body">
<div class="bubble in">hey, did you register your name yet?<span class="time">11:38</span></div>
<div class="bubble out">just now — bob.bch is live<span class="time">11:40</span></div>
<div class="bubble in">nice. send me the hermes.x link<span class="time">11:42</span></div>
<div class="bubble out">https://hermes.x/messenger<span class="time">11:43</span></div>
<div class="bubble in">great, send me the link<span class="time">11:44</span></div>
</div>
<div class="compose">
<input value="Message…" readonly>
<button>Send</button>
</div>
</section>
</div>
</div>
<div class="mockup-caption">Non-interactive preview — live build pending (see roadmap below).</div>
<div class="wrap narrow">
<section>
<h2>Two clients, one identity</h2>
<div class="plan-grid">
<div class="plan">
<h3>Desktop (Theseus) <span class="src ok">live</span></h3>
<ul>
<li>Full crypto in-process (NIP-17, nip44 v2)</li>
<li>Persistent inbox + read-state on disk</li>
<li>Address book (local JSON + vCard import)</li>
<li>Sidebar panel, Ctrl+Shift+E shortcut</li>
<li>Vault-bind: no re-typing the mnemonic</li>
<li>Reverse-resolve pk → .bch name</li>
</ul>
<div class="cta" style="justify-content:flex-start;margin-top:1.2rem">
<a class="btn primary" href="https://theseus.x/">Get Theseus</a>
</div>
</div>
<div class="plan">
<h3>Web (this page) <span class="src next">building</span></h3>
<ul>
<li>Same crypto, same identity (same wallet seed derivation)</li>
<li>BCH name resolution via public gateway</li>
<li>WebSocket to Nostr relays from the browser</li>
<li>IndexedDB inbox — survives tab close, scoped to this origin</li>
<li>No download, no install, no account</li>
<li>Mnemonic entry only — never stored in plaintext</li>
</ul>
<div class="cta" style="justify-content:flex-start;margin-top:1.2rem">
<a class="btn ghost" href="../">Why Hermes</a>
</div>
</div>
</div>
</section>
<section>
<h2>Why wait for the web client</h2>
<p class="lede">The web messenger isn't ready yet because of three things we
want to get right before shipping:</p>
<ol class="steps">
<li><b>Key handling in a tab.</b>
<span>A wallet mnemonic is the most sensitive bit of data a user has. In
the desktop app it lives in main-process memory; in a tab it has to
survive page reloads without ever landing in plaintext anywhere. We're
wiring a WebCrypto-backed non-extractable key path with the vault PIN
as the gate.</span></li>
<li><b>Resolver gateway.</b>
<span>The desktop talks to a chipnet Fulcrum directly. A browser can't
— it needs an HTTPS gateway that answers <code class="inline">np</code>
/ <code class="inline">nr</code> for a name. The gateway is live
(<code class="inline">navigate.st/api/dns/alice.bch</code>) but the
client-side cache + signature-verification path still has an edge case
with lapsed names.</span></li>
<li><b>Relay persistence.</b>
<span>Desktop keeps a long-lived WebSocket to each relay. A tab doesn't,
because browsers throttle background sockets. The web client needs
offline queueing and a well-defined "catch-up" fetch on return —
otherwise you re-open the tab and the inbox feels empty.</span></li>
</ol>
<p>Until the three are solid, <b>install Theseus</b> for the real thing.
The web client will land here when the above are green.</p>
<div class="cta">
<a class="btn primary" href="https://theseus.x/">Get Theseus</a>
<a class="btn ghost" href="../branding/">Brand kit</a>
</div>
</section>
</div>
<footer>
<div>hermes.x — a <a href="https://silentmode.st/">Silent Mode</a> project.</div>
<div class="fine">Preview only. The functional web client is being built.</div>
</footer>
</body>
</html>