style.css gains the classes the views spelled out in full: .section-label (17 sites), .hint (20), .form-error (13), .alert-error (5), .row-card (5), .list-empty (5), .field (5), .page-shell (3), and the small row action .btn-sm (4) / .btn-sm-danger (3). Only exact runs moved, so nothing renders differently; spacing a site adds beyond a run stays a utility beside it. Kept: the Reminders and Timeline small buttons. They carry no text colour and inherit it, so putting them on .btn-sm would recolour them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -142,11 +142,11 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Account" />
|
||||
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Linked devices</h2>
|
||||
<p class="mb-6 max-w-xl text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<h2 class="mb-2 section-label">Linked devices</h2>
|
||||
<p class="mb-6 max-w-xl hint">
|
||||
Link the Inkwell desktop or mobile app to sync your notes. Create a device token here, then
|
||||
paste it into the app when it asks to connect. You can revoke a device at any time.
|
||||
</p>
|
||||
@@ -199,7 +199,7 @@ onMounted(() => {
|
||||
<BaseButton type="submit" :loading="creating">Create token</BaseButton>
|
||||
</form>
|
||||
|
||||
<p v-if="error" class="mb-4 text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="mb-4 form-error">{{ error }}</p>
|
||||
|
||||
<!-- Device list -->
|
||||
<div v-if="devices.loading" class="py-10 text-center text-sm text-neutral-400">Loading…</div>
|
||||
@@ -207,13 +207,13 @@ onMounted(() => {
|
||||
v-else-if="!devices.items.length"
|
||||
class="rounded-xl border border-dashed border-neutral-300 py-10 text-center dark:border-neutral-700"
|
||||
>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">No devices linked yet.</p>
|
||||
<p class="hint">No devices linked yet.</p>
|
||||
</div>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="d in devices.items"
|
||||
:key="d.id"
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<span class="text-neutral-400"><Icon name="device" /></span>
|
||||
@@ -226,7 +226,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-red-600 hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:text-red-400 dark:hover:bg-red-950/40"
|
||||
class="shrink-0 btn-sm-danger"
|
||||
@click="revoke(d.id, d.name)"
|
||||
>
|
||||
Revoke
|
||||
@@ -235,9 +235,9 @@ onMounted(() => {
|
||||
</ul>
|
||||
|
||||
<template v-if="storage">
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Storage</h2>
|
||||
<h2 class="mb-2 mt-10 section-label">Storage</h2>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">Attachments</p>
|
||||
@@ -247,8 +247,8 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Password</h2>
|
||||
<p class="mb-4 text-sm text-neutral-500 dark:text-neutral-400">Changing it signs you out everywhere else.</p>
|
||||
<h2 class="mb-2 mt-10 section-label">Password</h2>
|
||||
<p class="mb-4 hint">Changing it signs you out everywhere else.</p>
|
||||
<form class="flex max-w-sm flex-col gap-3" @submit.prevent="changePassword">
|
||||
<BaseInput
|
||||
id="current-password"
|
||||
@@ -273,9 +273,9 @@ onMounted(() => {
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Sessions</h2>
|
||||
<h2 class="mb-2 mt-10 section-label">Sessions</h2>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">Sign out everywhere else</p>
|
||||
|
||||
@@ -350,11 +350,11 @@ const gridBinds = computed(() => ({
|
||||
<template v-else>
|
||||
<template v-if="isMainBoard">
|
||||
<section v-if="pinnedNotes.length">
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Pinned</h2>
|
||||
<h2 class="mb-2 section-label">Pinned</h2>
|
||||
<NoteGrid :notes="pinnedNotes" v-bind="gridBinds" />
|
||||
</section>
|
||||
<section v-if="otherNotes.length" :class="pinnedNotes.length ? 'mt-8' : ''">
|
||||
<h2 v-if="pinnedNotes.length" class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<h2 v-if="pinnedNotes.length" class="mb-2 section-label">
|
||||
Others
|
||||
</h2>
|
||||
<NoteGrid :notes="otherNotes" v-bind="gridBinds" />
|
||||
|
||||
@@ -44,7 +44,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Forgot your password?</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">We'll email you a link to choose a new one.</p>
|
||||
<p class="mt-1 hint">We'll email you a link to choose a new one.</p>
|
||||
</div>
|
||||
|
||||
<p
|
||||
@@ -67,14 +67,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Send reset link</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Back to sign in</RouterLink
|
||||
>
|
||||
|
||||
@@ -52,7 +52,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Welcome back</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Sign in to your thoughts.</p>
|
||||
<p class="mt-1 hint">Sign in to your thoughts.</p>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-4" novalidate @submit.prevent="submit">
|
||||
@@ -77,7 +77,7 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
@@ -90,7 +90,7 @@ async function submit() {
|
||||
>
|
||||
</form>
|
||||
|
||||
<p v-if="config.allowRegistration" class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-if="config.allowRegistration" class="mt-6 text-center hint">
|
||||
No account?
|
||||
<RouterLink to="/register" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Create one</RouterLink
|
||||
|
||||
@@ -52,7 +52,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{{ invite ? "You're invited" : "Create your space" }}</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
{{ invite ? `Create your account on ${config.siteName}.` : "Start capturing in seconds." }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -86,14 +86,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Create account</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
Already have an account?
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Sign in</RouterLink
|
||||
|
||||
@@ -58,7 +58,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Choose a new password</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
You'll be signed out everywhere else, and your apps will ask you to sign in again.
|
||||
</p>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@ async function submit() {
|
||||
<p
|
||||
v-if="!token"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
This link is incomplete. Ask your admin for a new one.
|
||||
</p>
|
||||
@@ -91,14 +91,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Set password</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Back to sign in</RouterLink
|
||||
>
|
||||
|
||||
@@ -94,13 +94,13 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Settings" />
|
||||
|
||||
<AsyncState :loading="loading" :error="loadError" error-title="Couldn't load settings" @retry="load">
|
||||
<form class="flex flex-col gap-8" @submit.prevent="save">
|
||||
<section v-for="group in groups" :key="group.name" class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">{{ group.name }}</h2>
|
||||
<h2 class="section-label">{{ group.name }}</h2>
|
||||
|
||||
<div v-for="it in group.entries" :key="it.key" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between gap-6">
|
||||
@@ -126,14 +126,14 @@ onMounted(load);
|
||||
type="number"
|
||||
:min="it.minimum ?? undefined"
|
||||
:max="it.maximum ?? undefined"
|
||||
class="w-28 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-28 field"
|
||||
:value="Number(it.value)"
|
||||
@input="it.value = Number(($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<select
|
||||
v-else-if="it.choices"
|
||||
:id="it.key"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@change="it.value = ($event.target as HTMLSelectElement).value"
|
||||
>
|
||||
@@ -145,7 +145,7 @@ onMounted(load);
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
:placeholder="it.is_set ? 'Saved — type to replace' : ''"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@input="it.value = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
@@ -153,7 +153,7 @@ onMounted(load);
|
||||
v-else
|
||||
:id="it.key"
|
||||
type="text"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@input="it.value = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
@@ -178,7 +178,7 @@ onMounted(load);
|
||||
<BaseButton type="submit" :loading="saving" :disabled="!dirty">Save changes</BaseButton>
|
||||
<span v-if="saved && !dirty" class="text-sm text-green-600 dark:text-green-400">Saved.</span>
|
||||
<span v-else-if="!dirty && !error" class="text-sm text-neutral-400">No unsaved changes</span>
|
||||
<span v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</span>
|
||||
<span v-if="error" class="form-error">{{ error }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</AsyncState>
|
||||
|
||||
@@ -356,7 +356,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Sync" />
|
||||
|
||||
<div v-if="loading" class="py-10 text-center text-sm text-neutral-400">Loading…</div>
|
||||
@@ -392,7 +392,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
<p v-if="degraded.length" class="mt-3 text-xs text-amber-600 dark:text-amber-400">
|
||||
This server doesn't support: {{ degraded.join(", ") }}. Everything else syncs normally.
|
||||
</p>
|
||||
<p v-if="syncError" class="mt-3 text-sm text-red-600 dark:text-red-400">{{ syncError }}</p>
|
||||
<p v-if="syncError" class="mt-3 form-error">{{ syncError }}</p>
|
||||
<p v-else-if="autoError" class="mt-3 text-xs text-amber-600 dark:text-amber-400">
|
||||
The last automatic sync ({{ new Date(lastCycle!.at_ms).toLocaleTimeString() }}) didn't
|
||||
go through: {{ autoError }} It will keep trying on its own.
|
||||
@@ -428,7 +428,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">
|
||||
Working offline on this device
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
Everything works without a server — your notes are stored on this machine. Connect an
|
||||
Inkwell server if you want them to reach your other devices.
|
||||
</p>
|
||||
@@ -452,7 +452,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
Uses https unless you type http:// yourself.
|
||||
</p>
|
||||
|
||||
<p v-if="probeError" class="text-sm text-red-600 dark:text-red-400">{{ probeError }}</p>
|
||||
<p v-if="probeError" class="form-error">{{ probeError }}</p>
|
||||
|
||||
<!-- What answered, BEFORE any credentials are handed over -->
|
||||
<div
|
||||
@@ -531,7 +531,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<p v-if="linkError" class="text-sm text-red-600 dark:text-red-400">{{ linkError }}</p>
|
||||
<p v-if="linkError" class="form-error">{{ linkError }}</p>
|
||||
|
||||
<div>
|
||||
<BaseButton type="submit" :loading="linking" :disabled="!canLink">
|
||||
@@ -546,7 +546,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
updates: a hotkey that writes to the local store needs no server. -->
|
||||
<section class="mt-10 border-t border-neutral-200 pt-8 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">Quick capture</h2>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
A system-wide shortcut that opens a small window to write a note in, without
|
||||
bringing this one forward.
|
||||
</p>
|
||||
@@ -570,7 +570,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<p v-if="shortcutError" class="mt-2 text-sm text-red-600 dark:text-red-400">
|
||||
<p v-if="shortcutError" class="mt-2 form-error">
|
||||
{{ shortcutError }}
|
||||
</p>
|
||||
|
||||
@@ -585,10 +585,10 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
{{ shortcut.shortcut }} is saved but isn't active — something else on this
|
||||
system is holding it. Try a different combination.
|
||||
</p>
|
||||
<p v-else-if="shortcut.registered" class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-else-if="shortcut.registered" class="mt-2 hint">
|
||||
Press {{ shortcut.shortcut }} anywhere to capture a note.
|
||||
</p>
|
||||
<p v-else class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-else class="mt-2 hint">
|
||||
Off. There's no default on purpose — any combination picked for you is one
|
||||
taken away from something else on your machine.
|
||||
<button
|
||||
@@ -605,12 +605,12 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
has never touched a server still updates itself. -->
|
||||
<section class="mt-10 border-t border-neutral-200 pt-8 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">App updates</h2>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
This is version {{ update?.current_version ?? "—" }}.
|
||||
</p>
|
||||
|
||||
<fieldset v-if="offeredServer" class="mt-4">
|
||||
<legend class="text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<legend class="section-label">
|
||||
Updates from
|
||||
</legend>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
@@ -650,7 +650,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
|
||||
<!-- A server holds one build, so a channel only means something on the forge. -->
|
||||
<fieldset v-if="source === null" class="mt-4">
|
||||
<legend class="text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<legend class="section-label">
|
||||
Channel
|
||||
</legend>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
@@ -704,7 +704,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</div>
|
||||
<p
|
||||
v-else-if="checkedOnce && !updateError"
|
||||
class="mt-4 text-sm text-neutral-500 dark:text-neutral-400"
|
||||
class="mt-4 hint"
|
||||
>
|
||||
<template v-if="source">You're on the newest build {{ source }} has.</template>
|
||||
<template v-else>
|
||||
@@ -712,7 +712,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<p v-if="updateError" class="mt-4 text-sm text-red-600 dark:text-red-400">{{ updateError }}</p>
|
||||
<p v-if="updateError" class="mt-4 form-error">{{ updateError }}</p>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<BaseButton variant="ghost" :loading="checking" @click="checkUpdates">
|
||||
|
||||
@@ -137,7 +137,7 @@ onMounted(load);
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-5 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mb-5 hint">
|
||||
Your notes by when you captured them{{ hasRange ? " — filtered to the chosen dates" : "" }}.
|
||||
</p>
|
||||
|
||||
@@ -150,7 +150,7 @@ onMounted(load);
|
||||
<EmptyState v-if="items.length === 0" :title="empty.title" :subtitle="empty.subtitle" />
|
||||
<div v-else class="flex flex-col gap-8">
|
||||
<section v-for="group in groups" :key="group.key">
|
||||
<h2 class="mb-3 text-xs font-semibold uppercase tracking-wide text-neutral-400">{{ group.label }}</h2>
|
||||
<h2 class="mb-3 section-label">{{ group.label }}</h2>
|
||||
<NoteGrid :notes="group.notes" @open="openEditor" />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user