style: restyle settings pages to match parchment design system

- Move settings routes into (app) layout group for header/sidebar
- Replace all gray/white/dark-mode Tailwind classes with CSS variables
- Use rounded-sm, card-shadow, and parchment color tokens throughout
- Fix PasswordStrengthMeter to use themed colors
- Fix page titles from "KeepNotes" to "Crumbs"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 01:26:15 +01:00
co-authored by Claude Opus 4.6
parent d07607bcf1
commit ac83ebc4f0
4 changed files with 59 additions and 59 deletions
@@ -7,11 +7,11 @@
const labels = ['Very weak', 'Weak', 'Fair', 'Strong', 'Very strong'];
const colors = [
'bg-red-500',
'bg-orange-500',
'bg-yellow-500',
'bg-lime-500',
'bg-green-500'
'bg-[var(--destructive)]',
'bg-[var(--destructive)]',
'bg-[var(--primary)]',
'bg-[var(--success-text)]',
'bg-[var(--success-text)]'
];
</script>
@@ -20,13 +20,13 @@
<div class="flex gap-1">
{#each { length: 4 } as _, i}
<div
class="h-1 flex-1 rounded-full transition-colors {i <= result.score
class="h-1 flex-1 rounded-sm transition-colors {i <= result.score
? colors[result.score]
: 'bg-gray-200 dark:bg-gray-600'}"
: 'bg-[var(--border-subtle)]'}"
></div>
{/each}
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
<p class="mt-1 text-xs text-[var(--text-muted)]">
{labels[result.score]}
{#if result.feedback.warning}
— {result.feedback.warning}
@@ -141,44 +141,44 @@
</script>
<svelte:head>
<title>Settings - KeepNotes</title>
<title>Settings - Crumbs</title>
</svelte:head>
<div class="mx-auto max-w-2xl py-8">
<h1 class="mb-8 text-2xl font-bold text-gray-800 dark:text-gray-100">Settings</h1>
<div class="mx-auto max-w-2xl px-4 py-8">
<h1 class="mb-8 text-2xl font-bold text-[var(--text)]">Settings</h1>
<!-- Profile -->
<section class="mb-8 rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-100">Profile</h2>
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Profile</h2>
{#if profileMsg}
<div class="mb-4 rounded p-3 text-sm {profileError ? 'bg-red-50 text-red-600 dark:bg-red-900/30 dark:text-red-400' : 'bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400'}">
<div class="mb-4 rounded-sm border p-3 text-sm {profileError ? 'border-[var(--error-border)] bg-[var(--error-bg)] text-[var(--error-text)]' : 'border-[var(--success-bg)] bg-[var(--success-bg)] text-[var(--success-text)]'}">
{profileMsg}
</div>
{/if}
<div class="space-y-4">
<div>
<label for="display-name" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Display Name</label>
<label for="display-name" class="mb-1 block text-sm font-medium text-[var(--text)]">Display Name</label>
<input
id="display-name"
type="text"
bind:value={displayName}
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none focus:border-[var(--primary)]"
/>
</div>
<div>
<label for="email" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
<label for="email" class="mb-1 block text-sm font-medium text-[var(--text)]">Email</label>
<input
id="email"
type="email"
bind:value={email}
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none focus:border-[var(--primary)]"
/>
</div>
<button
onclick={saveProfile}
class="rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white hover:bg-amber-600"
class="rounded-sm bg-[var(--primary)] px-4 py-2 text-sm font-medium text-white hover:bg-[var(--primary-hover)]"
>
Save profile
</button>
@@ -186,11 +186,11 @@
</section>
<!-- Change Password -->
<section class="mb-8 rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-100">Change Password</h2>
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Change Password</h2>
{#if passwordMsg}
<div class="mb-4 rounded p-3 text-sm {passwordError ? 'bg-red-50 text-red-600 dark:bg-red-900/30 dark:text-red-400' : 'bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400'}">
<div class="mb-4 rounded-sm border p-3 text-sm {passwordError ? 'border-[var(--error-border)] bg-[var(--error-bg)] text-[var(--error-text)]' : 'border-[var(--success-bg)] bg-[var(--success-bg)] text-[var(--success-text)]'}">
{passwordMsg}
</div>
{/if}
@@ -200,24 +200,24 @@
type="password"
bind:value={currentPassword}
placeholder="Current password"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<input
type="password"
bind:value={newPassword}
placeholder="New password (min 8 characters)"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<PasswordStrengthMeter password={newPassword} />
<input
type="password"
bind:value={confirmNewPassword}
placeholder="Confirm new password"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<button
onclick={changePassword}
class="rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white hover:bg-amber-600"
class="rounded-sm bg-[var(--primary)] px-4 py-2 text-sm font-medium text-white hover:bg-[var(--primary-hover)]"
>
Change password
</button>
@@ -225,13 +225,13 @@
</section>
<!-- Active Sessions -->
<section class="mb-8 rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<div class="mb-4 flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-100">Active Sessions</h2>
<h2 class="text-lg font-semibold text-[var(--text)]">Active Sessions</h2>
{#if activeSessions.length > 1}
<button
onclick={revokeAllOtherSessions}
class="text-xs text-red-600 hover:text-red-700 dark:text-red-400"
class="text-xs text-[var(--destructive)] hover:underline"
>
Log out everywhere else
</button>
@@ -239,28 +239,28 @@
</div>
{#if !sessionsLoaded}
<p class="text-sm text-gray-500">Loading sessions...</p>
<p class="text-sm text-[var(--text-muted)]">Loading sessions...</p>
{:else if activeSessions.length === 0}
<p class="text-sm text-gray-500">No active sessions</p>
<p class="text-sm text-[var(--text-muted)]">No active sessions</p>
{:else}
<div class="space-y-3">
{#each activeSessions as session (session.id)}
<div class="flex items-center justify-between rounded-lg border border-gray-100 p-3 dark:border-gray-700">
<div class="flex items-center justify-between rounded-sm border border-[var(--border-subtle)] p-3">
<div>
<p class="text-sm font-medium text-gray-800 dark:text-gray-100">
<p class="text-sm font-medium text-[var(--text)]">
{formatAgent(session.userAgent)}
{#if session.isCurrent}
<span class="ml-2 rounded bg-green-100 px-2 py-0.5 text-xs text-green-800 dark:bg-green-900/30 dark:text-green-200">current</span>
<span class="ml-2 rounded-sm bg-[var(--success-bg)] px-2 py-0.5 text-xs text-[var(--success-text)]">current</span>
{/if}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-[var(--text-muted)]">
{session.ip || 'Unknown IP'} · Last active {formatDate(session.lastUsedAt)}
</p>
</div>
{#if !session.isCurrent}
<button
onclick={() => revokeSession(session.id)}
class="rounded px-3 py-1 text-xs text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
class="rounded-sm px-3 py-1 text-xs text-[var(--destructive)] hover:bg-[var(--error-bg)]"
>
Revoke
</button>
@@ -273,11 +273,11 @@
<!-- Admin: User Management Link -->
{#if data.user?.role === 'admin'}
<section class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-100">Administration</h2>
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Administration</h2>
<a
href="/settings/users"
class="text-sm text-amber-600 hover:text-amber-700 hover:underline dark:text-amber-400"
class="text-sm text-[var(--primary)] hover:text-[var(--primary-hover)] hover:underline"
>
Manage users →
</a>
@@ -86,21 +86,21 @@
</script>
<svelte:head>
<title>User Management - KeepNotes</title>
<title>User Management - Crumbs</title>
</svelte:head>
<div class="mx-auto max-w-2xl py-8">
<div class="mx-auto max-w-2xl px-4 py-8">
<div class="mb-8 flex items-center gap-4">
<a href="/settings" class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">← Settings</a>
<h1 class="text-2xl font-bold text-gray-800 dark:text-gray-100">User Management</h1>
<a href="/settings" class="text-sm text-[var(--text-muted)] hover:text-[var(--primary)]">← Settings</a>
<h1 class="text-2xl font-bold text-[var(--text)]">User Management</h1>
</div>
<!-- Create User -->
<section class="mb-8 rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-100">Create User</h2>
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Create User</h2>
{#if createMsg}
<div class="mb-4 rounded p-3 text-sm {createError ? 'bg-red-50 text-red-600 dark:bg-red-900/30 dark:text-red-400' : 'bg-green-50 text-green-600 dark:bg-green-900/30 dark:text-green-400'}">
<div class="mb-4 rounded-sm border p-3 text-sm {createError ? 'border-[var(--error-border)] bg-[var(--error-bg)] text-[var(--error-text)]' : 'border-[var(--success-bg)] bg-[var(--success-bg)] text-[var(--success-text)]'}">
{createMsg}
</div>
{/if}
@@ -110,31 +110,31 @@
type="email"
bind:value={newEmail}
placeholder="Email"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<input
type="text"
bind:value={newDisplayName}
placeholder="Display name (optional)"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<input
type="password"
bind:value={newPassword}
placeholder="Password (min 8 characters)"
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none placeholder:text-[var(--text-muted)] focus:border-[var(--primary)]"
/>
<PasswordStrengthMeter password={newPassword} />
<select
bind:value={newRole}
class="w-full rounded-lg border border-gray-300 px-4 py-2 text-sm outline-none focus:border-amber-500 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-100"
class="w-full rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 text-sm text-[var(--text)] outline-none focus:border-[var(--primary)]"
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<button
onclick={createUser}
class="rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white hover:bg-amber-600"
class="rounded-sm bg-[var(--primary)] px-4 py-2 text-sm font-medium text-white hover:bg-[var(--primary-hover)]"
>
Create user
</button>
@@ -142,25 +142,25 @@
</section>
<!-- User List -->
<section class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-800">
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-100">Users ({users.length})</h2>
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Users ({users.length})</h2>
<div class="space-y-3">
{#each users as user (user.id)}
<div class="flex items-center justify-between rounded-lg border border-gray-100 p-4 dark:border-gray-700">
<div class="flex items-center justify-between rounded-sm border border-[var(--border-subtle)] p-4">
<div>
<p class="font-medium text-gray-800 dark:text-gray-100">
<p class="font-medium text-[var(--text)]">
{user.displayName || user.email}
{#if user.role === 'admin'}
<span class="ml-2 rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-800 dark:bg-amber-900/30 dark:text-amber-200">admin</span>
<span class="ml-2 rounded-sm bg-[var(--primary)]/15 px-2 py-0.5 text-xs text-[var(--primary)]">admin</span>
{/if}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{user.email}</p>
<p class="text-sm text-[var(--text-muted)]">{user.email}</p>
</div>
<div class="flex items-center gap-2">
<button
onclick={() => toggleRole(user)}
class="rounded px-3 py-1 text-xs text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
class="rounded-sm px-3 py-1 text-xs text-[var(--text-muted)] hover:bg-[var(--border-subtle)]/50"
title={user.role === 'admin' ? 'Demote to user' : 'Promote to admin'}
>
{user.role === 'admin' ? 'Make user' : 'Make admin'}
@@ -168,7 +168,7 @@
{#if user.id !== data.user?.id}
<button
onclick={() => deleteUser(user.id)}
class="rounded px-3 py-1 text-xs text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
class="rounded-sm px-3 py-1 text-xs text-[var(--destructive)] hover:bg-[var(--error-bg)]"
>
Delete
</button>