gsmol/web/index.html

172 lines
9.3 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>smol mail</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- One sprite, referenced by <use>; same-document so the CSP needs no exception. -->
<svg class="sprite" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<symbol id="i-compose" viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></symbol>
<symbol id="i-inbox" viewBox="0 0 24 24"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></symbol>
<symbol id="i-sent" viewBox="0 0 24 24"><path d="M22 2 11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/></symbol>
<symbol id="i-contacts" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></symbol>
<symbol id="i-settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></symbol>
<symbol id="i-auto" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" stroke="none"/></symbol>
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></symbol>
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></symbol>
<symbol id="i-reply" viewBox="0 0 24 24"><path d="M9 17 4 12l5-5"/><path d="M4 12h11a5 5 0 0 1 5 5v2"/></symbol>
<symbol id="i-back" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></symbol>
</svg>
<header class="topbar">
<div class="brand">smol mail</div>
<input id="search" class="search" placeholder="search mail" aria-label="search mail"
spellcheck="false" hidden>
<div id="top-actions" class="top-actions" hidden>
<button id="theme" class="icon-only" aria-label="theme"><svg class="icon"><use href="#i-auto"/></svg></button>
<button id="settings-open" class="icon-only" aria-label="settings" title="settings"><svg class="icon"><use href="#i-settings"/></svg></button>
<div id="account" class="account"></div>
<div id="avatar" class="avatar" aria-hidden="true"></div>
</div>
</header>
<main id="setup" class="setup" hidden>
<h2>welcome</h2>
<p>Identity is one 32-byte master secret. Back it up: it is the only secret, and losing it means losing the address.</p>
<div class="row">
<button id="create-identity">create a new identity</button>
</div>
<p class="hint">or restore from a saved master:</p>
<div class="row restore-row">
<label>master<input id="restore-master" placeholder="64 hex characters" spellcheck="false"></label>
<label>address (optional)<input id="restore-address" placeholder="alice@example.org, if already registered"
spellcheck="false"></label>
<label id="restore-key-label">server's public key
<input id="restore-key" placeholder="base32" spellcheck="false"></label>
<button id="restore-identity">restore</button>
</div>
</main>
<main id="app" hidden>
<nav class="rail" aria-label="folders">
<button id="write" class="compose"><svg class="icon"><use href="#i-compose"/></svg>compose</button>
<div id="folders">
<button data-folder="inbox" aria-label="inbox" class="active" aria-current="true">
<svg class="icon"><use href="#i-inbox"/></svg><span class="label">inbox</span>
<span id="count-inbox" class="count"></span>
</button>
<button data-folder="requests" aria-label="requests">
<svg class="icon"><use href="#i-inbox"/></svg><span class="label">requests</span>
<span id="count-requests" class="count"></span>
</button>
<button data-folder="sent" aria-label="sent"><svg class="icon"><use href="#i-sent"/></svg><span class="label">sent</span></button>
<button data-folder="contacts" aria-label="contacts"><svg class="icon"><use href="#i-contacts"/></svg><span class="label">contacts</span></button>
</div>
</nav>
<div class="listpane">
<div id="list-toolbar" class="toolbar">
<div id="mail-tools" class="tools">
<button id="fetch" class="primary"><svg class="icon"><use href="#i-refresh"/></svg><span class="label">fetch</span></button>
<span id="list-status" class="list-status"></span>
</div>
<form id="import-form" class="tools" hidden>
<input name="uri" placeholder="smol://alice@example.org/…" aria-label="smol:// address to import"
spellcheck="false" required>
<button class="primary">import</button>
</form>
</div>
<section id="list" class="list" aria-label="messages"></section>
</div>
<article id="view" class="view" aria-label="message"><p class="placeholder">select a message</p></article>
</main>
<dialog id="compose">
<form id="compose-form" method="dialog">
<input name="to" required spellcheck="false" placeholder="to: alice@example.org or smol://…">
<input name="subject" spellcheck="false" placeholder="subject">
<textarea name="body" rows="12" placeholder="message"></textarea>
<footer>
<label class="anon"><input type="checkbox" name="anonymous"> anonymous — omit my address</label>
<span id="compose-error" class="error"></span>
<button type="button" id="compose-cancel">cancel</button>
<button id="send" class="filled" value="send">send</button>
</footer>
</form>
</dialog>
<dialog id="settings">
<h2>settings</h2>
<section>
<h3><span id="step-server" class="step" data-n="1">1</span> your server</h3>
<p class="hint">Pin the key of the server that will hold your mail. Get it from its operator over a
channel you already trust — registering, recalling and fetching all refuse an unpinned server.</p>
<form id="pin-form">
<label>host<input name="host" placeholder="example.org" spellcheck="false" required></label>
<label>public key<input name="key" placeholder="base32" spellcheck="false" required></label>
<button class="filled">pin</button>
</form>
<ul id="pins" class="kv"></ul>
</section>
<section>
<h3><span id="step-address" class="step" data-n="2">2</span> your address</h3>
<p class="hint">Claim a username on a server you have pinned. This is what people write to.</p>
<form id="register-form">
<label>address<input name="address" placeholder="alice@example.org" spellcheck="false" required></label>
<label>invite token<input name="token" placeholder="if required"></label>
<button class="filled">register</button>
</form>
<details>
<summary>already registered elsewhere?</summary>
<p class="hint">Restoring a master brings back the identity, not the memory of what address it was
registered under in a different browser — the server was never asked. Recall it here: this
looks the address up and checks it resolves to this identity's key, without registering anything.</p>
<form id="recall-form">
<label>address<input name="address" placeholder="alice@example.org" spellcheck="false" required></label>
<button>recall</button>
</form>
</details>
</section>
<section>
<h3>your identity</h3>
<div id="identity-card"></div>
</section>
<section>
<h3>fetch</h3>
<label class="check-row">
<input type="checkbox" id="leave-on-server">
<span>
<span class="check-title">leave mail on server</span>
<span class="hint">Fetch keeps a copy on the server instead of deleting it; delete a message
here to remove it from both.</span>
</span>
</label>
</section>
<section>
<h3>backup</h3>
<p class="hint">Mail, once fetched, exists only here unless "leave mail on server" is on above.
This backs up the local inbox, requests, sent mail, contacts and pinned servers into one file.
It never includes the master: that has its own reveal-and-copy flow above, meant for a
password manager, not a downloadable file.</p>
<div class="backup-row">
<button type="button" id="export-data">export</button>
<button type="button" id="import-data-trigger">import</button>
<input id="import-data" type="file" accept="application/json" hidden>
</div>
</section>
<footer><button id="settings-close">close</button></footer>
</dialog>
<div id="status" class="toast" role="status" aria-live="polite" hidden></div>
<script type="module" src="js/app.js"></script>
</body>
</html>