design(kez-chat/web): restyle auth + claims pages to the dark theme (phase 6)

Completes visual consistency across the whole app — every surface now
uses the tactical-terminal token set, so the redesign can ship without
a light-on-dark login screen.

  • app.css: dark defaults for input/textarea/select (bg-elevated, token
    text/border/placeholder, accent focus) so forms that don't set an
    explicit bg still read correctly.
  • Landing / CreateAccount / Restore / Unlock: light utility classes →
    tokens (bg-white→surface, text-gray-*→text tiers, gray-900 buttons→
    accent, red/green/amber→danger/verified/warning).
  • Claims / AddClaim: same swap, plus the nostr publish panel + format
    toggle + status badges remapped (purple→accent, blue→accent,
    yellow→warning).

Now consistent end to end. Remaining polish (message ticks, day
separators, contact preview-card, skeletons, emoji-picker dark theme)
tracked for a follow-up; ready to deploy for a look.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Jason Tudisco 2026-05-27 21:54:26 -06:00
parent 40ebd63ed7
commit a9ef611622
7 changed files with 166 additions and 145 deletions

View File

@ -72,6 +72,27 @@ samp {
font-family: var(--font-mono);
}
/* Dark-theme defaults for form controls so any page that doesn't set an
explicit bg (the auth + claims forms) still reads correctly. Components
that set bg-elevated/bg-surface explicitly override this. */
input,
textarea,
select {
background-color: var(--color-elevated);
color: var(--color-text);
border-color: var(--color-border);
}
input::placeholder,
textarea::placeholder {
color: var(--color-text-muted);
}
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
outline: none;
border-color: var(--color-accent);
}
/* Cyan text-selection — reinforces the accent. */
::selection {
background: #28c8e840;

View File

@ -246,9 +246,9 @@
<div class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900">Add a claim</h1>
<h1 class="text-2xl font-bold text-text">Add a claim</h1>
<button
class="text-sm text-gray-500 hover:text-gray-900"
class="text-sm text-text-muted hover:text-text"
onclick={() => push("/claims")}
>
← Back to claims
@ -256,25 +256,25 @@
</div>
<!-- Stepper -->
<ol class="flex gap-2 text-xs text-gray-500">
<li class={step === "pick" ? "font-semibold text-gray-900" : ""}>1. Channel</li>
<ol class="flex gap-2 text-xs text-text-muted">
<li class={step === "pick" ? "font-semibold text-text" : ""}>1. Channel</li>
<li></li>
<li class={step === "identifier" ? "font-semibold text-gray-900" : ""}>2. Identifier</li>
<li class={step === "identifier" ? "font-semibold text-text" : ""}>2. Identifier</li>
<li></li>
<li class={step === "publish" ? "font-semibold text-gray-900" : ""}>3. Publish</li>
<li class={step === "publish" ? "font-semibold text-text" : ""}>3. Publish</li>
<li></li>
<li class={step === "done" ? "font-semibold text-gray-900" : ""}>4. Done</li>
<li class={step === "done" ? "font-semibold text-text" : ""}>4. Done</li>
</ol>
{#if step === "pick"}
<div class="grid sm:grid-cols-2 gap-3">
{#each CHANNELS as c}
<button
class="text-left border border-gray-200 rounded-lg p-4 bg-white hover:border-gray-400 transition"
class="text-left border border-border rounded-lg p-4 bg-surface hover:border-accent-dim transition"
onclick={() => pickChannel(c)}
>
<p class="font-semibold text-gray-900">{c.label}</p>
<p class="text-xs text-gray-500 mt-1 font-mono">{c.key}:&lt;&gt;</p>
<p class="font-semibold text-text">{c.label}</p>
<p class="text-xs text-text-muted mt-1 font-mono">{c.key}:&lt;&gt;</p>
</button>
{/each}
</div>
@ -286,7 +286,7 @@
onsubmit={(e) => { e.preventDefault(); buildAndSign(); }}
>
<div>
<label class="block text-sm font-medium text-gray-700" for="ident">
<label class="block text-sm font-medium text-text-secondary" for="ident">
{selected.identifierLabel}
</label>
<input
@ -294,22 +294,22 @@
type="text"
bind:value={identifierInput}
placeholder={selected.identifierPlaceholder}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md font-mono"
class="mt-1 w-full px-3 py-2 border border-border rounded-md font-mono"
autocomplete="off"
/>
{#if selected.key === "nostr" && nip07Available}
<button
type="button"
class="mt-2 inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-purple-300 bg-purple-50 text-purple-800 rounded-md hover:bg-purple-100"
class="mt-2 inline-flex items-center gap-1 px-3 py-1.5 text-xs border border-accent/40 bg-accent/10 text-accent rounded-md hover:bg-accent/20"
onclick={fillFromNostrExtension}
>
⚡ Use my nostr extension
</button>
<span class="ml-2 text-xs text-gray-500">Reads your pubkey via NIP-07 — no copy/paste.</span>
<span class="ml-2 text-xs text-text-muted">Reads your pubkey via NIP-07 — no copy/paste.</span>
{/if}
{#if identifierInput.trim()}
<p class="mt-1 text-xs text-gray-500">
Subject will be: <code class="bg-gray-100 px-1 rounded">{selected.toSubject(identifierInput)}</code>
<p class="mt-1 text-xs text-text-muted">
Subject will be: <code class="bg-elevated px-1 rounded">{selected.toSubject(identifierInput)}</code>
</p>
{/if}
</div>
@ -317,14 +317,14 @@
<div class="flex gap-2">
<button
type="button"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-4 py-2 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => { step = "pick"; selected = null; }}
>
Back
</button>
<button
type="submit"
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={identifierInput.trim().length === 0}
>
Sign claim
@ -336,30 +336,30 @@
{#if step === "publish" && envelope && selected}
<div class="grid lg:grid-cols-2 gap-6">
<section class="space-y-3">
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">
<h2 class="text-sm font-semibold text-text-secondary uppercase tracking-wide">
1. Publish on {selected.label}
</h2>
<pre class="whitespace-pre-wrap text-sm bg-gray-50 border border-gray-200 rounded p-4 leading-relaxed">{selected.instructions(envelope.payload.subject)}</pre>
<pre class="whitespace-pre-wrap text-sm bg-elevated border border-border rounded p-4 leading-relaxed">{selected.instructions(envelope.payload.subject)}</pre>
</section>
<section class="space-y-3">
<div class="flex items-center gap-2 flex-wrap">
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide flex-1">
<h2 class="text-sm font-semibold text-text-secondary uppercase tracking-wide flex-1">
2. Copy this:
</h2>
<div class="flex border border-gray-300 rounded overflow-hidden text-xs">
<div class="flex border border-border rounded overflow-hidden text-xs">
<button
class={`px-2 py-1 ${format === "compact" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`}
class={`px-2 py-1 ${format === "compact" ? "bg-accent text-accent-contrast" : "bg-surface text-text-secondary hover:bg-elevated"}`}
onclick={() => (format = "compact")}
title="kez:z1: — zstd + base64url. Fits in tight places like DNS TXT records, QR codes, chat messages."
>compact</button>
<button
class={`px-2 py-1 border-l border-gray-300 ${format === "markdown" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`}
class={`px-2 py-1 border-l border-border ${format === "markdown" ? "bg-accent text-accent-contrast" : "bg-surface text-text-secondary hover:bg-elevated"}`}
onclick={() => (format = "markdown")}
title="Human-readable; embeds the JSON inside a ```kez fence. Best for gists and README files."
>markdown</button>
<button
class={`px-2 py-1 border-l border-gray-300 ${format === "json" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`}
class={`px-2 py-1 border-l border-border ${format === "json" ? "bg-accent text-accent-contrast" : "bg-surface text-text-secondary hover:bg-elevated"}`}
onclick={() => (format = "json")}
title="Raw envelope JSON. Best for .well-known/kez.json and developer tooling."
>JSON</button>
@ -367,20 +367,20 @@
</div>
{#await renderArtifact(envelope, format)}
<pre class="text-xs bg-gray-900 text-gray-100 rounded p-4 font-mono leading-relaxed max-h-96 min-h-32 flex items-center justify-center text-gray-500">Computing…</pre>
<pre class="text-xs bg-accent text-text rounded p-4 font-mono leading-relaxed max-h-96 min-h-32 flex items-center justify-center text-text-muted">Computing…</pre>
{:then text}
<pre class="text-xs bg-gray-900 text-gray-100 rounded p-4 overflow-x-auto font-mono leading-relaxed max-h-96 overflow-y-auto whitespace-pre-wrap break-all">{text}</pre>
<pre class="text-xs bg-accent text-text rounded p-4 overflow-x-auto font-mono leading-relaxed max-h-96 overflow-y-auto whitespace-pre-wrap break-all">{text}</pre>
{#if format === "compact"}
<p class="text-xs text-gray-500">
<p class="text-xs text-text-muted">
{text.length} chars · zstd-compressed signed envelope, base64url-encoded.
</p>
{/if}
{:catch e}
<pre class="text-xs bg-red-50 border border-red-200 text-red-800 rounded p-4">Error: {e.message}</pre>
<pre class="text-xs bg-danger/10 border border-danger/40 text-danger rounded p-4">Error: {e.message}</pre>
{/await}
<button
class="px-3 py-2 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-3 py-2 text-sm border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={copyArtifact}
>
{copied ? "✓ Copied" : "Copy to clipboard"}
@ -389,23 +389,23 @@
</div>
{#if selected.key === "nostr" && nip07Available}
<div class="border border-purple-200 bg-purple-50 rounded-lg p-4">
<p class="text-sm font-semibold text-purple-900">⚡ One-click publish via your nostr extension</p>
<p class="mt-1 text-xs text-purple-800">
<div class="border border-accent/40 bg-accent/10 rounded-lg p-4">
<p class="text-sm font-semibold text-accent">⚡ One-click publish via your nostr extension</p>
<p class="mt-1 text-xs text-accent">
Wraps the markdown block in a normal nostr post (kind 1), asks
your extension to sign it, and broadcasts to the relay pool.
Verifiers (web + Rust CLI) will pick it up automatically.
</p>
<div class="mt-3 flex items-center gap-3">
<button
class="px-3 py-1.5 text-sm bg-purple-700 text-white rounded-md hover:bg-purple-800 disabled:opacity-50"
class="px-3 py-1.5 text-sm bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
onclick={publishViaNostrExtension}
disabled={nostrPublish.status === "pending"}
>
{nostrPublish.status === "pending" ? "Publishing…" : "Publish to nostr"}
</button>
{#if nostrPublish.status === "ok"}
<span class="text-xs text-green-800">
<span class="text-xs text-verified">
✓ Posted to {nostrPublish.result.ok.length} relay(s).
<a
href={nostrPublish.result.evidence_url}
@ -415,11 +415,11 @@
>view on njump.me</a>
</span>
{:else if nostrPublish.status === "error"}
<span class="text-xs text-red-800">{nostrPublish.message}</span>
<span class="text-xs text-danger">{nostrPublish.message}</span>
{/if}
</div>
{#if nostrPublish.status === "ok" && nostrPublish.result.failed.length > 0}
<p class="mt-2 text-xs text-amber-800">
<p class="mt-2 text-xs text-warning">
{nostrPublish.result.failed.length} relay(s) didn't ack:
{nostrPublish.result.failed.map((f) => f.relay).join(", ")}
</p>
@ -427,15 +427,15 @@
</div>
{/if}
<div class="flex gap-2 pt-4 border-t border-gray-200">
<div class="flex gap-2 pt-4 border-t border-border">
<button
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-4 py-2 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => { step = "identifier"; }}
>
Back
</button>
<button
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim"
onclick={saveAndDone}
>
Save claim
@ -444,9 +444,9 @@
{/if}
{#if step === "done" && envelope}
<div class="border border-green-300 bg-green-50 rounded-lg p-6">
<p class="text-lg font-semibold text-green-900">✓ Claim saved</p>
<p class="mt-2 text-sm text-green-800">
<div class="border border-verified/40 bg-verified/10 rounded-lg p-6">
<p class="text-lg font-semibold text-verified">✓ Claim saved</p>
<p class="mt-2 text-sm text-verified">
You signed a claim for
<code class="font-mono">{envelope.payload.subject}</code>.
Once you've published the proof on that channel, come back to the
@ -455,12 +455,12 @@
<div class="mt-4 flex gap-2">
<a
href="#/claims"
class="px-4 py-2 bg-green-700 text-white rounded-md hover:bg-green-800 no-underline"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim no-underline"
>
Back to claims
</a>
<button
class="px-4 py-2 border border-green-300 bg-white rounded-md text-green-800 hover:bg-green-100"
class="px-4 py-2 border border-verified/40 bg-surface rounded-md text-verified hover:bg-elevated"
onclick={() => {
step = "pick";
selected = null;

View File

@ -70,10 +70,10 @@
function statusBadge(c: StoredClaim) {
const v = c.last_verify;
if (!v) return { text: "Not verified", color: "bg-gray-100 text-gray-600" };
if (v.status === "ok") return { text: "✓ Verified", color: "bg-green-100 text-green-800" };
if (v.status === "fail") return { text: "✗ Failed", color: "bg-red-100 text-red-800" };
return { text: "— Skipped", color: "bg-yellow-100 text-yellow-800" };
if (!v) return { text: "Not verified", color: "bg-elevated text-text-secondary" };
if (v.status === "ok") return { text: "✓ Verified", color: "bg-verified/20 text-verified" };
if (v.status === "fail") return { text: "✗ Failed", color: "bg-danger/20 text-danger" };
return { text: "— Skipped", color: "bg-warning/20 text-warning" };
}
function formatChecked(iso: string): string {
@ -88,11 +88,11 @@
<div class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900">Claims</h1>
<h1 class="text-2xl font-bold text-text">Claims</h1>
<div class="flex gap-2">
{#if claims.length > 0}
<button
class="px-3 py-2 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 disabled:opacity-50"
class="px-3 py-2 text-sm border border-border rounded-md text-text-secondary hover:bg-elevated disabled:opacity-50"
onclick={verifyAll}
disabled={verifying.size > 0}
>
@ -101,14 +101,14 @@
{/if}
<a
href="#/claims/add"
class="px-3 py-2 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline"
class="px-3 py-2 text-sm bg-accent text-accent-contrast rounded-md hover:bg-accent-dim no-underline"
>
+ Add claim
</a>
</div>
</div>
<p class="text-sm text-gray-700">
<p class="text-sm text-text-secondary">
A claim is a signed envelope that says "I control this other account."
Publish the proof on the channel itself (a public gist, a DNS TXT
record, a nostr event, etc.) and anyone can verify it without
@ -117,13 +117,13 @@
</p>
{#if loading}
<p class="text-sm text-gray-500">Loading…</p>
<p class="text-sm text-text-muted">Loading…</p>
{:else if claims.length === 0}
<div class="border border-dashed border-gray-300 rounded-lg p-8 text-center">
<p class="text-gray-500">No claims yet.</p>
<div class="border border-dashed border-border rounded-lg p-8 text-center">
<p class="text-text-muted">No claims yet.</p>
<a
href="#/claims/add"
class="mt-3 inline-block px-3 py-2 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline"
class="mt-3 inline-block px-3 py-2 text-sm bg-accent text-accent-contrast rounded-md hover:bg-accent-dim no-underline"
>
Add your first claim
</a>
@ -134,67 +134,67 @@
{@const badge = statusBadge(c)}
{@const isVerifying = verifying.has(c.id)}
{@const isExpanded = expanded.has(c.id)}
<li class="border border-gray-200 rounded-lg p-4 bg-white">
<li class="border border-border rounded-lg p-4 bg-surface">
<div class="flex items-start justify-between gap-4">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<p class="font-mono font-semibold text-gray-900 truncate">
<p class="font-mono font-semibold text-text truncate">
{c.envelope.payload.subject}
</p>
<span class={`text-xs px-2 py-0.5 rounded font-medium ${badge.color}`}>
{badge.text}
</span>
</div>
<p class="mt-1 text-xs text-gray-500">
<p class="mt-1 text-xs text-text-muted">
Channel: <span class="font-mono">{c.channel}</span> ·
Signed: <span class="font-mono">{c.envelope.payload.created_at}</span>
</p>
{#if c.last_verify}
<p class="mt-1 text-xs text-gray-700">
<p class="mt-1 text-xs text-text-secondary">
{c.last_verify.summary}
<span class="text-gray-400">· checked {formatChecked(c.last_verify.checked_at)}</span>
<span class="text-text-muted">· checked {formatChecked(c.last_verify.checked_at)}</span>
</p>
{#if c.last_verify.evidence_url || c.last_verify.details}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-900 underline"
class="mt-1 text-xs text-text-muted hover:text-text underline"
onclick={() => toggleExpand(c.id)}
>
{isExpanded ? "Hide" : "Show"} details
</button>
{#if isExpanded}
<div class="mt-2 text-xs bg-gray-50 border border-gray-200 rounded p-3 space-y-2">
<div class="mt-2 text-xs bg-elevated border border-border rounded p-3 space-y-2">
{#if c.last_verify.evidence_url}
<div>
<span class="text-gray-500">Evidence URL:</span>
<span class="text-text-muted">Evidence URL:</span>
<a
href={c.last_verify.evidence_url}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-blue-700 hover:underline break-all"
class="font-mono text-accent hover:underline break-all"
>
{c.last_verify.evidence_url}
</a>
</div>
{/if}
{#if c.last_verify.details}
<pre class="whitespace-pre-wrap font-mono text-gray-700">{c.last_verify.details}</pre>
<pre class="whitespace-pre-wrap font-mono text-text-secondary">{c.last_verify.details}</pre>
{/if}
</div>
{/if}
{/if}
{:else if c.published_at}
<p class="mt-1 text-xs text-green-700">
<p class="mt-1 text-xs text-verified">
✓ You marked this published at {c.published_at}
</p>
{:else}
<p class="mt-1 text-xs text-amber-700">
<p class="mt-1 text-xs text-warning">
⚠ Not marked as published yet
</p>
{/if}
</div>
<div class="flex flex-col gap-2 shrink-0">
<button
class="text-xs px-3 py-1 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 disabled:opacity-50"
class="text-xs px-3 py-1 border border-border rounded-md text-text-secondary hover:bg-elevated disabled:opacity-50"
onclick={() => runVerify(c)}
disabled={isVerifying}
>
@ -202,14 +202,14 @@
</button>
{#if !c.published_at}
<button
class="text-xs px-3 py-1 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="text-xs px-3 py-1 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => togglePublished(c)}
>
Mark published
</button>
{/if}
<button
class="text-xs px-3 py-1 border border-gray-300 rounded-md text-gray-700 hover:bg-red-50 hover:border-red-300"
class="text-xs px-3 py-1 border border-border rounded-md text-text-secondary hover:bg-danger/10 hover:border-danger"
onclick={() => deleteClaim(c)}
>
Remove

View File

@ -93,27 +93,27 @@
</script>
<div class="space-y-6">
<h1 class="text-2xl font-bold text-gray-900">Create account</h1>
<h1 class="text-2xl font-bold text-text">Create account</h1>
{#if !serverInfo}
<p class="text-sm text-amber-700 bg-amber-50 border border-amber-200 rounded p-3">
<p class="text-sm text-warning bg-warning/10 border border-warning/40 rounded p-3">
Couldn't reach the chat server. Try refreshing.
</p>
{/if}
<!-- Stepper -->
<ol class="flex gap-2 text-xs text-gray-500">
<li class={step === "handle" ? "font-semibold text-gray-900" : ""}>1. Handle</li>
<ol class="flex gap-2 text-xs text-text-muted">
<li class={step === "handle" ? "font-semibold text-text" : ""}>1. Handle</li>
<li></li>
<li class={step === "seed" ? "font-semibold text-gray-900" : ""}>2. Back up seed</li>
<li class={step === "seed" ? "font-semibold text-text" : ""}>2. Back up seed</li>
<li></li>
<li class={step === "confirm" || step === "submitting" ? "font-semibold text-gray-900" : ""}>3. Confirm</li>
<li class={step === "confirm" || step === "submitting" ? "font-semibold text-text" : ""}>3. Confirm</li>
<li></li>
<li class={step === "done" ? "font-semibold text-gray-900" : ""}>4. Done</li>
<li class={step === "done" ? "font-semibold text-text" : ""}>4. Done</li>
</ol>
{#if error}
<p class="text-sm text-red-700 bg-red-50 border border-red-200 rounded p-3">
<p class="text-sm text-danger bg-danger/10 border border-danger/40 rounded p-3">
{error}
</p>
{/if}
@ -124,48 +124,48 @@
onsubmit={(e) => { e.preventDefault(); goToSeedStep(); }}
>
<div>
<label class="block text-sm font-medium text-gray-700" for="handle">
<label class="block text-sm font-medium text-text-secondary" for="handle">
Handle
</label>
<div class="mt-1 flex items-stretch border border-gray-300 rounded-md overflow-hidden bg-white">
<div class="mt-1 flex items-stretch border border-border rounded-md overflow-hidden bg-surface">
<input
id="handle"
type="text"
bind:value={handle}
placeholder="tudisco"
class="flex-1 px-3 py-2 outline-none text-gray-900"
class="flex-1 px-3 py-2 outline-none text-text"
autocomplete="off"
/>
{#if serverInfo}
<span class="px-3 py-2 text-gray-500 bg-gray-50 border-l border-gray-300">
<span class="px-3 py-2 text-text-muted bg-elevated border-l border-border">
@{serverInfo.server}
</span>
{/if}
</div>
<p class="mt-1 text-xs text-gray-500">
<p class="mt-1 text-xs text-text-muted">
Lowercase letters, digits, <code>-</code>, <code>_</code>. 332 chars.
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700" for="pw">
<label class="block text-sm font-medium text-text-secondary" for="pw">
Passphrase (encrypts your seed in this browser)
</label>
<input
id="pw"
type="password"
bind:value={passphrase}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md outline-none"
class="mt-1 w-full px-3 py-2 border border-border rounded-md outline-none"
autocomplete="new-password"
/>
<input
type="password"
bind:value={passphrase2}
placeholder="confirm passphrase"
class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-md outline-none"
class="mt-2 w-full px-3 py-2 border border-border rounded-md outline-none"
autocomplete="new-password"
/>
<p class="mt-1 text-xs text-gray-500">
<p class="mt-1 text-xs text-text-muted">
The seed itself is your real identity. The passphrase only
protects the local copy in this browser.
</p>
@ -173,7 +173,7 @@
<button
type="submit"
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={!serverInfo}
>
Continue
@ -183,19 +183,19 @@
{#if step === "seed" && id}
<div class="space-y-4">
<div class="border border-amber-300 bg-amber-50 rounded-lg p-4 space-y-3">
<p class="font-semibold text-amber-900">⚠️ Back up your seed now</p>
<p class="text-sm text-amber-800">
<div class="border border-warning/40 bg-warning/10 rounded-lg p-4 space-y-3">
<p class="font-semibold text-warning">⚠️ Back up your seed now</p>
<p class="text-sm text-warning">
This is the only way to recover your account on another device
(or after clearing this browser). The server doesn't have it.
Write it down or paste into a password manager.
</p>
<div class="mt-3 p-3 bg-white border border-amber-200 rounded font-mono text-sm break-all select-all">
<div class="mt-3 p-3 bg-surface border border-warning/40 rounded font-mono text-sm break-all select-all">
{seedHex}
</div>
<div class="flex gap-2">
<button
class="text-xs px-3 py-1 bg-amber-900 text-white rounded hover:bg-amber-800"
class="text-xs px-3 py-1 bg-warning/10 text-accent-contrast rounded hover:bg-warning/20"
onclick={() => copyToClipboard(seedHex)}
>
Copy seed
@ -203,7 +203,7 @@
</div>
</div>
<label class="flex items-start gap-2 text-sm text-gray-700">
<label class="flex items-start gap-2 text-sm text-text-secondary">
<input type="checkbox" bind:checked={seedAck} class="mt-1" />
I've saved this seed somewhere safe. I understand losing it means
losing my account permanently.
@ -211,13 +211,13 @@
<div class="flex gap-2">
<button
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-4 py-2 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => { step = "handle"; }}
>
Back
</button>
<button
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={!seedAck}
onclick={() => { step = "confirm"; }}
>
@ -229,21 +229,21 @@
{#if step === "confirm" && id}
<div class="space-y-4">
<p class="text-gray-700">Ready to register:</p>
<div class="border border-gray-200 rounded-lg p-4 bg-gray-50 space-y-2 text-sm">
<div><span class="text-gray-500">Handle:</span> <span class="font-mono font-semibold">{handle}@{serverInfo?.server}</span></div>
<div><span class="text-gray-500">Public key:</span> <code class="break-all">{id.identity}</code></div>
<p class="text-text-secondary">Ready to register:</p>
<div class="border border-border rounded-lg p-4 bg-elevated space-y-2 text-sm">
<div><span class="text-text-muted">Handle:</span> <span class="font-mono font-semibold">{handle}@{serverInfo?.server}</span></div>
<div><span class="text-text-muted">Public key:</span> <code class="break-all">{id.identity}</code></div>
</div>
<div class="flex gap-2">
<button
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-4 py-2 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => { step = "seed"; }}
>
Back
</button>
<button
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={working}
onclick={submitRegistration}
>
@ -254,17 +254,17 @@
{/if}
{#if step === "submitting"}
<p class="text-gray-700">Submitting registration to {serverInfo?.server}</p>
<p class="text-text-secondary">Submitting registration to {serverInfo?.server}</p>
{/if}
{#if step === "done"}
<div class="border border-green-300 bg-green-50 rounded-lg p-6">
<p class="text-lg font-semibold text-green-900">✓ Account created</p>
<p class="mt-2 text-sm text-green-800">
<div class="border border-verified/40 bg-verified/10 rounded-lg p-6">
<p class="text-lg font-semibold text-verified">✓ Account created</p>
<p class="mt-2 text-sm text-verified">
You are <span class="font-mono font-semibold">{handle}@{serverInfo?.server}</span>.
</p>
<button
class="mt-4 px-4 py-2 bg-green-700 text-white rounded-md hover:bg-green-800"
class="mt-4 px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim"
onclick={() => push("/dashboard")}
>
Go to dashboard

View File

@ -24,27 +24,27 @@
<div class="space-y-8">
<section>
<h1 class="text-3xl font-bold text-gray-900">Welcome to kez-chat</h1>
<p class="mt-2 text-gray-600">
<h1 class="text-3xl font-bold text-text">Welcome to kez-chat</h1>
<p class="mt-2 text-text-secondary">
A decentralized identity + chat system. Create an account, link your
online identities, prove who you are without trusting a central server.
</p>
</section>
{#if loading}
<p class="text-gray-500 text-sm">Checking local state…</p>
<p class="text-text-muted text-sm">Checking local state…</p>
{:else if existing}
<section class="border border-gray-200 rounded-lg p-6 bg-white">
<p class="text-sm text-gray-500 mb-1">Existing account on this device:</p>
<p class="text-xl font-mono font-semibold text-gray-900">
<section class="border border-border rounded-lg p-6 bg-surface">
<p class="text-sm text-text-muted mb-1">Existing account on this device:</p>
<p class="text-xl font-mono font-semibold text-text">
{existing.handle}@{existing.server}
</p>
<p class="mt-1 text-xs font-mono text-gray-500 break-all">
<p class="mt-1 text-xs font-mono text-text-muted break-all">
{existing.primary}
</p>
<div class="mt-4 flex gap-3">
<button
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim"
onclick={() => push("/unlock")}
>
Unlock
@ -54,21 +54,21 @@
{:else}
<section class="grid sm:grid-cols-2 gap-4">
<button
class="text-left border border-gray-200 rounded-lg p-6 bg-white hover:border-gray-400 transition"
class="text-left border border-border rounded-lg p-6 bg-surface hover:border-accent-dim transition"
onclick={() => push("/create")}
>
<h2 class="text-lg font-semibold text-gray-900">Create a new account</h2>
<p class="mt-1 text-sm text-gray-600">
<h2 class="text-lg font-semibold text-text">Create a new account</h2>
<p class="mt-1 text-sm text-text-secondary">
Generate a fresh key pair, pick a handle, back up your seed phrase.
</p>
</button>
<button
class="text-left border border-gray-200 rounded-lg p-6 bg-white hover:border-gray-400 transition"
class="text-left border border-border rounded-lg p-6 bg-surface hover:border-accent-dim transition"
onclick={() => push("/restore")}
>
<h2 class="text-lg font-semibold text-gray-900">Restore from seed</h2>
<p class="mt-1 text-sm text-gray-600">
<h2 class="text-lg font-semibold text-text">Restore from seed</h2>
<p class="mt-1 text-sm text-text-secondary">
Have a 64-char hex seed from another device? Paste it to recover
your identity.
</p>
@ -76,11 +76,11 @@
</section>
{/if}
<section class="text-sm text-gray-500 border-t border-gray-200 pt-6">
<p class="font-medium text-gray-700">What is this?</p>
<section class="text-sm text-text-muted border-t border-border pt-6">
<p class="font-medium text-text-secondary">What is this?</p>
<p class="mt-1">
Your identity is an Ed25519 keypair — not a username + password.
Account creation makes a handle (<code class="bg-gray-100 px-1 rounded">tudisco@kez.lat</code>),
Account creation makes a handle (<code class="bg-elevated px-1 rounded">tudisco@kez.lat</code>),
stores your seed locally under a passphrase, and registers your public
key with this server. There's no email, no recovery flow — keep the
seed safe.

View File

@ -62,9 +62,9 @@
</script>
<div class="space-y-6">
<h1 class="text-2xl font-bold text-gray-900">Restore from seed</h1>
<h1 class="text-2xl font-bold text-text">Restore from seed</h1>
<p class="text-sm text-gray-700 bg-amber-50 border border-amber-200 rounded p-3">
<p class="text-sm text-text-secondary bg-warning/10 border border-warning/40 rounded p-3">
<strong>v0.1 limitation:</strong> the seed alone doesn't tell us which
handle to restore. For now this flow doesn't work end-to-end — we'll
add <code>GET /v1/by-primary/&lt;id&gt;</code> on the server in v0.2
@ -76,37 +76,37 @@
onsubmit={(e) => { e.preventDefault(); submit(); }}
>
<div>
<label class="block text-sm font-medium text-gray-700" for="seed">
<label class="block text-sm font-medium text-text-secondary" for="seed">
Seed (64 hex characters)
</label>
<textarea
id="seed"
bind:value={seedHex}
rows="3"
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md font-mono text-sm"
class="mt-1 w-full px-3 py-2 border border-border rounded-md font-mono text-sm"
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700" for="pw">
<label class="block text-sm font-medium text-text-secondary" for="pw">
New passphrase for this device
</label>
<input
id="pw"
type="password"
bind:value={passphrase}
class="mt-1 w-full px-3 py-2 border border-gray-300 rounded-md"
class="mt-1 w-full px-3 py-2 border border-border rounded-md"
/>
<input
type="password"
bind:value={passphrase2}
placeholder="confirm"
class="mt-2 w-full px-3 py-2 border border-gray-300 rounded-md"
class="mt-2 w-full px-3 py-2 border border-border rounded-md"
/>
</div>
{#if error}
<p class="text-sm text-red-700 bg-red-50 border border-red-200 rounded p-3">
<p class="text-sm text-danger bg-danger/10 border border-danger/40 rounded p-3">
{error}
</p>
{/if}
@ -114,14 +114,14 @@
<div class="flex gap-2">
<button
type="button"
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
class="px-4 py-2 border border-border rounded-md text-text-secondary hover:bg-elevated"
onclick={() => push("/")}
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={working}
>
{working ? "Restoring…" : "Restore"}

View File

@ -82,10 +82,10 @@
</script>
<div class="space-y-6 max-w-md">
<h1 class="text-2xl font-bold text-gray-900">Unlock</h1>
<h1 class="text-2xl font-bold text-text">Unlock</h1>
{#if meta}
<p class="text-sm text-gray-600">
<p class="text-sm text-text-secondary">
Unlocking <span class="font-mono font-semibold">{meta.handle}@{meta.server}</span>
</p>
@ -93,7 +93,7 @@
<div class="space-y-3">
<button
type="button"
class="w-full px-4 py-3 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50 flex items-center justify-center gap-2"
class="w-full px-4 py-3 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50 flex items-center justify-center gap-2"
onclick={submitBiometric}
disabled={working}
>
@ -103,7 +103,7 @@
{#if !showPassphrase}
<button
type="button"
class="text-xs text-gray-500 hover:text-gray-900 underline"
class="text-xs text-text-muted hover:text-text underline"
onclick={() => (showPassphrase = true)}
>
Use passphrase instead
@ -122,31 +122,31 @@
bind:value={passphrase}
placeholder="passphrase"
autocomplete="current-password"
class="w-full px-3 py-2 border border-gray-300 rounded-md"
class="w-full px-3 py-2 border border-border rounded-md"
/>
{#if error}
<p class="text-sm text-red-700 bg-red-50 border border-red-200 rounded p-3">
<p class="text-sm text-danger bg-danger/10 border border-danger/40 rounded p-3">
{error}
</p>
{/if}
<button
type="submit"
class="px-4 py-2 bg-gray-900 text-white rounded-md hover:bg-gray-700 disabled:opacity-50"
class="px-4 py-2 bg-accent text-accent-contrast rounded-md hover:bg-accent-dim disabled:opacity-50"
disabled={working || passphrase.length === 0}
>
{working ? "Unlocking…" : "Unlock"}
</button>
</form>
{:else if error}
<p class="text-sm text-red-700 bg-red-50 border border-red-200 rounded p-3">
<p class="text-sm text-danger bg-danger/10 border border-danger/40 rounded p-3">
{error}
</p>
{/if}
<button
class="text-xs text-gray-500 hover:text-red-700 underline"
class="text-xs text-text-muted hover:text-danger underline"
onclick={forget}
>
Forget this account on this device