theseus/messages.html
Local Dev c9106d4ede fix(theseus/light): darker acid (#3a5c00) + add missing overrides in addon panels
Previous #4d7300 (0.3.10) was still too light against actual white
backgrounds — several tint fills (rgba(214,255,61,X)) and unpatched
addon panels were making the effective color feel bright green. Two
fixes bundled:

1) Bump --acid in every top-level page's light-media block from
   #4d7300 to #3a5c00 — same hue, ~7:1 contrast on #ffffff (was ~5.5:1).
2) Add the missing light-media --acid override to the addon panels
   that were still resolving to #d6ff3d: bchwallet/panel.html,
   siawallet/panel.html, and screenshot/editor.css (was #b4e024, now
   #3a5c00 to match).

Dark mode unchanged. Tint fills (rgba backgrounds at low alpha) still
stay as-is — at 8–15% opacity the specific hue barely matters and the
darker foreground now dominates.
2026-09-07 00:56:33 +02:00

242 lines
9.3 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Messages — Theseus</title>
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#4fd1a5; --err:#f6768a; --warn:#ffc75f;
}
@media (prefers-color-scheme: light) {
:root{
--bg:#f6f8fb; --panel:#ffffff; --panel2:#f0f4f9; --line:rgba(0,0,0,.10);
--ink:#1a1f28; --mut:#3c4453; --dim:#697280;
/* Darker acid for light backgrounds — ~5.5:1 on white. */
--acid: #3a5c00;
}
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; background:var(--bg); color:var(--ink);
font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
display:flex; flex-direction:column;
}
header{
padding:12px 16px; border-bottom:1px solid var(--line);
background:rgba(11,14,20,.9); display:flex; align-items:center; gap:12px;
flex-wrap:wrap;
}
header h1{font-size:14px;font-weight:600;margin:0;letter-spacing:.2px}
header .mark{font-size:20px}
#ident{
color:var(--mut); font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
font-size:12px; margin-left:auto; user-select:text;
}
#ident b{color:var(--acid); font-weight:500}
#status{
font-size:11px; padding:2px 8px; border-radius:999px;
background:var(--panel); color:var(--mut);
}
#status.ok{color:var(--ok); background:rgba(79,209,165,.12)}
#status.err{color:var(--err); background:rgba(246,118,138,.12)}
main{display:grid; grid-template-columns:1fr; gap:0; flex:1; min-height:0}
#setup, #app{padding:16px; overflow:auto}
#setup{display:none; max-width:520px; margin:auto; align-self:center}
#app{display:none; grid-template-rows:1fr auto; gap:12px}
body.mode-setup #setup{display:block}
body.mode-app #app{display:grid}
#inbox{
overflow:auto; background:var(--panel); border:1px solid var(--line);
border-radius:10px; padding:8px;
}
.msg{padding:8px 10px; border-radius:8px; margin-bottom:6px; background:var(--panel2)}
.msg .from{font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:11px; color:var(--mut); margin-bottom:3px}
.msg .from b{color:var(--acid); font-weight:500}
.msg .body{white-space:pre-wrap; word-break:break-word}
.msg .time{float:right; color:var(--dim); font-size:11px}
.empty{color:var(--dim); text-align:center; padding:24px 0}
#compose{display:grid; grid-template-columns:200px 1fr auto; gap:8px}
input, textarea, button{
font:inherit; color:var(--ink); background:var(--panel);
border:1px solid var(--line); border-radius:8px; padding:10px 12px;
}
textarea{resize:vertical; min-height:44px; max-height:200px}
input:focus, textarea:focus{outline:none; border-color:var(--acid)}
button{cursor:pointer; background:var(--acid); color:#0a0d13; font-weight:600; border-color:transparent}
button:hover{filter:brightness(1.05)}
button:disabled{opacity:.5; cursor:default}
.lede{color:var(--mut); font-size:13px; margin:0 0 12px}
#setup label{display:block; margin-bottom:12px; color:var(--mut); font-size:12px}
#setup textarea{width:100%; margin-top:6px; min-height:80px}
.row{display:flex; gap:8px; align-items:center; margin-top:8px}
#setupErr{color:var(--err); font-size:12px; min-height:1em; margin-top:8px}
#sendErr{color:var(--err); font-size:12px; min-height:1em; margin-top:4px}
.warn{
font-size:12px; color:var(--warn); padding:8px 10px;
background:rgba(255,199,95,.08); border:1px solid rgba(255,199,95,.24);
border-radius:8px; margin-bottom:12px;
}
code.inline{
font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
background:rgba(255,255,255,.05); padding:1px 5px; border-radius:4px; font-size:12px;
}
</style>
</head>
<body class="mode-setup">
<header>
<span class="mark">⛓️✉️</span>
<h1>Hermes Messages</h1>
<span id="status">connecting…</span>
<span id="ident"></span>
</header>
<main>
<section id="setup">
<p class="lede">Hermes derives its Nostr identity at
<code class="inline">silentmode/messenger/0</code> — a purpose disjoint from your
wallet's transaction key, so a compromised relay never yields tx-signing power.
Nothing is stored on disk beyond the vault; close the panel and the identity is dropped.</p>
<div class="warn">This is a proof build. Use a wallet you don't hold funds in.</div>
<div id="vaultBind" style="display:none; margin-bottom:16px">
<div class="row">
<button id="btnVault">Sign in with password vault</button>
<span style="color:var(--mut); font-size:12px">recommended — no mnemonic to re-type</span>
</div>
<div style="text-align:center; color:var(--dim); font-size:12px; margin:14px 0">— or —</div>
</div>
<label>
Mnemonic (12 or 24 words)
<textarea id="mnemonic" placeholder="word word word …" autocomplete="off" spellcheck="false"></textarea>
</label>
<div class="row">
<button id="btnInit">Open messages</button>
</div>
<div id="setupErr"></div>
</section>
<section id="app">
<div id="inbox"><div class="empty">no messages yet — subscribed to inbox…</div></div>
<div>
<div id="compose">
<input id="to" placeholder="to (e.g. alice.bch or 64-char hex pk)" autocomplete="off">
<textarea id="text" placeholder="message" rows="1"></textarea>
<button id="btnSend">Send</button>
</div>
<div id="sendErr"></div>
</div>
</section>
</main>
<script>
const $ = (id) => document.getElementById(id);
const setMode = (m) => { document.body.className = "mode-" + m; };
const setStatus = (text, cls = "") => {
const el = $("status");
el.textContent = text;
el.className = cls;
};
const shortPk = (pk) => pk ? pk.slice(0, 8) + "…" + pk.slice(-4) : "";
// Hermes messaging is a .bch-first product — the default TLD is noise in the
// chat list. Strip it for display; other TLDs stay (they disambiguate).
const displayName = (name) => name && name.endsWith(".bch") ? name.slice(0, -4) : name;
async function refreshInbox() {
const res = await hermes.inbox();
if (!res.ok) return;
const box = $("inbox");
if (!res.messages.length) {
box.innerHTML = '<div class="empty">no messages yet — subscribed to inbox…</div>';
return;
}
box.innerHTML = "";
for (const m of res.messages) {
const el = document.createElement("div");
el.className = "msg";
const from = m.senderName ? `<b>${displayName(m.senderName)}</b> · ${shortPk(m.senderPkHex)}` : shortPk(m.senderPkHex);
const when = new Date(m.createdAt * 1000).toLocaleTimeString();
el.innerHTML = `<div class="from">${from}<span class="time">${when}</span></div>` +
`<div class="body"></div>`;
el.querySelector(".body").textContent = m.text; // never innerHTML for user content
box.appendChild(el);
}
box.scrollTop = box.scrollHeight;
}
function afterInit(res) {
if (!res.ok) { $("setupErr").textContent = "error: " + res.err; return false; }
$("ident").innerHTML = "your npub: <b>" + res.npub.slice(0, 12) + "…" + res.npub.slice(-6) + "</b>"
+ (res.source ? " <span style='color:var(--dim); font-size:11px'>(" + res.source + ")</span>" : "");
setStatus(res.relaysConnected + "/" + res.relaysTotal + " relays",
res.relaysConnected > 0 ? "ok" : "");
setMode("app");
refreshInbox();
return true;
}
$("btnInit").addEventListener("click", async () => {
const mnemonic = $("mnemonic").value.trim();
if (!mnemonic) { $("setupErr").textContent = "enter a mnemonic"; return; }
$("btnInit").disabled = true;
$("setupErr").textContent = "";
const res = await hermes.init(mnemonic);
$("btnInit").disabled = false;
afterInit(res);
});
$("btnVault").addEventListener("click", async () => {
$("btnVault").disabled = true;
$("setupErr").textContent = "";
const res = await hermes.initFromVault();
$("btnVault").disabled = false;
afterInit(res);
});
$("btnSend").addEventListener("click", async () => {
const to = $("to").value.trim();
const text = $("text").value.trim();
if (!to || !text) { $("sendErr").textContent = "to and message required"; return; }
$("btnSend").disabled = true;
$("sendErr").textContent = "";
const res = await hermes.send(to, text);
$("btnSend").disabled = false;
if (!res.ok) { $("sendErr").textContent = "error: " + res.err; return; }
$("sendErr").textContent = "sent to " + res.accepted + "/" + res.total + " relay(s) — recipient pk " + shortPk(res.recipientPkHex);
$("text").value = "";
});
hermes.onMessage(() => refreshInbox());
hermes.onStatus((s) => {
setStatus(s.relaysConnected + "/" + s.relaysTotal + " relays",
s.relaysConnected > 0 ? "ok" : "err");
});
// Query status on load: maybe already initialised from a previous open.
hermes.status().then(async (r) => {
if (r && r.ok && r.ready) {
$("ident").innerHTML = "your npub: <b>" + r.npub.slice(0, 12) + "…" + r.npub.slice(-6) + "</b>";
setStatus(r.relaysConnected + "/" + r.relaysTotal + " relays", "ok");
setMode("app");
refreshInbox();
return;
}
setStatus("locked", "");
// Show the vault sign-in shortcut iff the vault is currently unlocked AND
// was set up from a mnemonic (so it carries the messenger root).
const v = await hermes.canUseVault();
if (v && v.ok && v.available) $("vaultBind").style.display = "block";
});
</script>
</body>
</html>