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); 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. */ /* Cyan text-selection — reinforces the accent. */
::selection { ::selection {
background: #28c8e840; background: #28c8e840;

View File

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

View File

@ -70,10 +70,10 @@
function statusBadge(c: StoredClaim) { function statusBadge(c: StoredClaim) {
const v = c.last_verify; const v = c.last_verify;
if (!v) return { text: "Not verified", color: "bg-gray-100 text-gray-600" }; if (!v) return { text: "Not verified", color: "bg-elevated text-text-secondary" };
if (v.status === "ok") return { text: "✓ Verified", color: "bg-green-100 text-green-800" }; if (v.status === "ok") return { text: "✓ Verified", color: "bg-verified/20 text-verified" };
if (v.status === "fail") return { text: "✗ Failed", color: "bg-red-100 text-red-800" }; if (v.status === "fail") return { text: "✗ Failed", color: "bg-danger/20 text-danger" };
return { text: "— Skipped", color: "bg-yellow-100 text-yellow-800" }; return { text: "— Skipped", color: "bg-warning/20 text-warning" };
} }
function formatChecked(iso: string): string { function formatChecked(iso: string): string {
@ -88,11 +88,11 @@
<div class="space-y-6"> <div class="space-y-6">
<div class="flex items-center justify-between"> <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"> <div class="flex gap-2">
{#if claims.length > 0} {#if claims.length > 0}
<button <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} onclick={verifyAll}
disabled={verifying.size > 0} disabled={verifying.size > 0}
> >
@ -101,14 +101,14 @@
{/if} {/if}
<a <a
href="#/claims/add" 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 + Add claim
</a> </a>
</div> </div>
</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." 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 Publish the proof on the channel itself (a public gist, a DNS TXT
record, a nostr event, etc.) and anyone can verify it without record, a nostr event, etc.) and anyone can verify it without
@ -117,13 +117,13 @@
</p> </p>
{#if loading} {#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} {:else if claims.length === 0}
<div class="border border-dashed border-gray-300 rounded-lg p-8 text-center"> <div class="border border-dashed border-border rounded-lg p-8 text-center">
<p class="text-gray-500">No claims yet.</p> <p class="text-text-muted">No claims yet.</p>
<a <a
href="#/claims/add" 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 Add your first claim
</a> </a>
@ -134,67 +134,67 @@
{@const badge = statusBadge(c)} {@const badge = statusBadge(c)}
{@const isVerifying = verifying.has(c.id)} {@const isVerifying = verifying.has(c.id)}
{@const isExpanded = expanded.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 items-start justify-between gap-4">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap"> <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} {c.envelope.payload.subject}
</p> </p>
<span class={`text-xs px-2 py-0.5 rounded font-medium ${badge.color}`}> <span class={`text-xs px-2 py-0.5 rounded font-medium ${badge.color}`}>
{badge.text} {badge.text}
</span> </span>
</div> </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> · Channel: <span class="font-mono">{c.channel}</span> ·
Signed: <span class="font-mono">{c.envelope.payload.created_at}</span> Signed: <span class="font-mono">{c.envelope.payload.created_at}</span>
</p> </p>
{#if c.last_verify} {#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} {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> </p>
{#if c.last_verify.evidence_url || c.last_verify.details} {#if c.last_verify.evidence_url || c.last_verify.details}
<button <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)} onclick={() => toggleExpand(c.id)}
> >
{isExpanded ? "Hide" : "Show"} details {isExpanded ? "Hide" : "Show"} details
</button> </button>
{#if isExpanded} {#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} {#if c.last_verify.evidence_url}
<div> <div>
<span class="text-gray-500">Evidence URL:</span> <span class="text-text-muted">Evidence URL:</span>
<a <a
href={c.last_verify.evidence_url} href={c.last_verify.evidence_url}
target="_blank" target="_blank"
rel="noopener noreferrer" 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} {c.last_verify.evidence_url}
</a> </a>
</div> </div>
{/if} {/if}
{#if c.last_verify.details} {#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} {/if}
</div> </div>
{/if} {/if}
{/if} {/if}
{:else if c.published_at} {: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} ✓ You marked this published at {c.published_at}
</p> </p>
{:else} {:else}
<p class="mt-1 text-xs text-amber-700"> <p class="mt-1 text-xs text-warning">
⚠ Not marked as published yet ⚠ Not marked as published yet
</p> </p>
{/if} {/if}
</div> </div>
<div class="flex flex-col gap-2 shrink-0"> <div class="flex flex-col gap-2 shrink-0">
<button <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)} onclick={() => runVerify(c)}
disabled={isVerifying} disabled={isVerifying}
> >
@ -202,14 +202,14 @@
</button> </button>
{#if !c.published_at} {#if !c.published_at}
<button <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)} onclick={() => togglePublished(c)}
> >
Mark published Mark published
</button> </button>
{/if} {/if}
<button <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)} onclick={() => deleteClaim(c)}
> >
Remove Remove

View File

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

View File

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

View File

@ -62,9 +62,9 @@
</script> </script>
<div class="space-y-6"> <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 <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 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 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(); }} onsubmit={(e) => { e.preventDefault(); submit(); }}
> >
<div> <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) Seed (64 hex characters)
</label> </label>
<textarea <textarea
id="seed" id="seed"
bind:value={seedHex} bind:value={seedHex}
rows="3" 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> ></textarea>
</div> </div>
<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 New passphrase for this device
</label> </label>
<input <input
id="pw" id="pw"
type="password" type="password"
bind:value={passphrase} 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 <input
type="password" type="password"
bind:value={passphrase2} bind:value={passphrase2}
placeholder="confirm" 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> </div>
{#if error} {#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} {error}
</p> </p>
{/if} {/if}
@ -114,14 +114,14 @@
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
type="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("/")} onclick={() => push("/")}
> >
Cancel Cancel
</button> </button>
<button <button
type="submit" 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} disabled={working}
> >
{working ? "Restoring…" : "Restore"} {working ? "Restoring…" : "Restore"}

View File

@ -82,10 +82,10 @@
</script> </script>
<div class="space-y-6 max-w-md"> <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} {#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> Unlocking <span class="font-mono font-semibold">{meta.handle}@{meta.server}</span>
</p> </p>
@ -93,7 +93,7 @@
<div class="space-y-3"> <div class="space-y-3">
<button <button
type="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} onclick={submitBiometric}
disabled={working} disabled={working}
> >
@ -103,7 +103,7 @@
{#if !showPassphrase} {#if !showPassphrase}
<button <button
type="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)} onclick={() => (showPassphrase = true)}
> >
Use passphrase instead Use passphrase instead
@ -122,31 +122,31 @@
bind:value={passphrase} bind:value={passphrase}
placeholder="passphrase" placeholder="passphrase"
autocomplete="current-password" 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} {#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} {error}
</p> </p>
{/if} {/if}
<button <button
type="submit" 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} disabled={working || passphrase.length === 0}
> >
{working ? "Unlocking…" : "Unlock"} {working ? "Unlocking…" : "Unlock"}
</button> </button>
</form> </form>
{:else if error} {: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} {error}
</p> </p>
{/if} {/if}
<button <button
class="text-xs text-gray-500 hover:text-red-700 underline" class="text-xs text-text-muted hover:text-danger underline"
onclick={forget} onclick={forget}
> >
Forget this account on this device Forget this account on this device