updated wording

This commit is contained in:
2026-06-17 16:39:43 +02:00
parent 8b1b9cedc2
commit 7a90ced98e
14 changed files with 257 additions and 79 deletions
+1
View File
@@ -53,6 +53,7 @@ export type NewItem = {
url?: string | null;
note?: string | null;
target_price?: number | null;
currency?: string | null;
};
// ---- Lists ----------------------------------------------------------------
+3 -4
View File
@@ -17,7 +17,7 @@
}
const ticker =
'CONSUME · ASCEND · ACCUMULATE · YOU DESERVE IT · MANIFEST THE DEBT · TREAT YOURSELF · ONE MORE WONT HURT · ';
'CONSUME · SPEND · ACCUMULATE · YOU DESERVE IT · TREAT YOURSELF · ONE MORE WONT HURT · DEBT IS A LIFESTYLE · ';
</script>
<div class="min-h-dvh flex flex-col">
@@ -67,7 +67,7 @@
<!-- Unverified banner -->
{#if auth.loaded && auth.user && !auth.user.email_verified}
<div class="border-b border-rose bg-rose/10 px-4 py-2 text-center text-xs text-rose">
✦ email unconfirmed — your indulgence awaits. lost the link?
✦ email not confirmed — confirm it to start tracking prices. lost the link?
<a href="/settings" class="underline">resend from settings</a>
</div>
{/if}
@@ -77,7 +77,6 @@
</main>
<footer class="border-t border-smoke px-4 py-6 text-center">
<p class="gospel text-base">spend now, ascend later</p>
<p class="label mt-1">consume·rs · self-hosted · rust + sveltekit</p>
<p class="gospel text-base">spend now, panic later</p>
</footer>
</div>
+10 -11
View File
@@ -9,7 +9,7 @@
</script>
<svelte:head>
<title>consume·rs — want more, ascend</title>
<title>consume·rs — want more, spend more</title>
</svelte:head>
{#if auth.loaded && auth.user}
@@ -18,28 +18,27 @@
<!-- Marketing hero -->
<section class="space-y-10">
<div class="space-y-4">
<p class="tag inline-block border-mint text-mint">self-hosted · rust core · ✦ blessed</p>
<h1
class="glitch font-display text-5xl font-bold leading-[0.95] sm:text-7xl"
data-text="WANT MORE. ASCEND. ACCUMULATE THE DEBT."
data-text="WANT MORE. SPEND MORE. ACCUMULATE THE DEBT."
>
WANT MORE. ASCEND. ACCUMULATE THE DEBT.
WANT MORE. SPEND MORE. ACCUMULATE THE DEBT.
</h1>
<p class="max-w-xl text-lg text-mute">
A serene little shrine to your every craving. Paste a product URL; we keep
vigil over the price and summon you the instant it falls. No feed. No
algorithm. Just you, your wants, and the gentle hum of impending debt.
A wishlist for your every craving. Paste a product link; we watch the price
and email you the moment it drops. No feed. No algorithm. Just you, your
wants, and the gentle hum of impending debt.
<span class="gospel">You deserve it.</span>
</p>
</div>
<div class="flex flex-wrap gap-3">
<a href="/register" class="btn btn-acid">begin ascension</a>
<a href="/login" class="btn btn-ghost">return to worship</a>
<a href="/register" class="btn btn-acid">create account</a>
<a href="/login" class="btn btn-ghost">log in</a>
</div>
<div class="grid gap-4 sm:grid-cols-3">
{#each [['I', 'COVET', 'group wants by topic'], ['II', 'PASTE URL', 'we keep the vigil'], ['III', 'BE SUMMONED', 'strike on the drop']] as [n, t, d]}
{#each [['I', 'MAKE A LIST', 'group wants by topic'], ['II', 'PASTE A LINK', 'we track the price'], ['III', 'GET AN EMAIL', 'the moment it drops']] as [n, t, d]}
<div class="panel p-5">
<p class="gospel text-4xl">{n}</p>
<p class="mt-1 font-display text-lg font-bold">{t}</p>
@@ -48,6 +47,6 @@
{/each}
</div>
<p class="gospel text-center text-lg">blessed are the carts, for they shall be filled</p>
<p class="gospel text-center text-lg">your cart isnt going to fill itself</p>
</section>
{/if}
+1 -1
View File
@@ -26,7 +26,7 @@
<div class="mx-auto max-w-md">
<div class="panel p-8">
<p class="label">password reset</p>
<h1 class="mb-6 font-display text-3xl font-bold">RECLAIM THE KEY</h1>
<h1 class="mb-6 font-display text-3xl font-bold">RESET PASSWORD</h1>
{#if done}
<p class="border-2 border-mint bg-mint/10 px-3 py-3 text-sm text-mint">
+6 -6
View File
@@ -54,9 +54,9 @@
<section class="space-y-8">
<div class="flex flex-wrap items-end justify-between gap-4">
<div>
<p class="label">your devotion</p>
<p class="label">your wishlists</p>
<h1 class="font-display text-4xl font-bold">YOUR LISTS</h1>
<p class="gospel mt-1 text-lg">each a temple to a different craving</p>
<p class="gospel mt-1 text-lg">a list for every craving</p>
</div>
<button class="btn btn-acid" onclick={() => (showForm = !showForm)}>
{showForm ? 'never mind' : 'new list +'}
@@ -67,7 +67,7 @@
<form class="panel panel-acid space-y-4 p-6" onsubmit={create}>
<div class="grid gap-4 sm:grid-cols-[5rem_1fr]">
<div>
<label class="label" for="emoji">glyph</label>
<label class="label" for="emoji">emoji</label>
<input id="emoji" class="field mt-1 text-center" bind:value={emoji} maxlength="4" placeholder="🛍" />
</div>
<div>
@@ -76,7 +76,7 @@
</div>
</div>
<div>
<label class="label" for="desc">creed <span class="text-mute">(optional)</span></label>
<label class="label" for="desc">description <span class="text-mute">(optional)</span></label>
<input id="desc" class="field mt-1" bind:value={description} maxlength="500" placeholder="what you tell yourself you need" />
</div>
{#if error}<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{error}</p>{/if}
@@ -85,7 +85,7 @@
{/if}
{#if !lists.loaded}
<p class="text-center text-mute flicker">summoning your lists…</p>
<p class="text-center text-mute flicker">loading your lists…</p>
{:else if lists.items.length === 0}
<div class="panel p-10 text-center">
<p class="gospel text-2xl">no lists yet</p>
@@ -112,7 +112,7 @@
class="text-xs text-mute opacity-0 transition-opacity hover:text-rose group-hover:opacity-100"
onclick={() => remove(l.id, l.name)}
>
renounce
delete
</button>
</div>
</div>
+113 -21
View File
@@ -27,6 +27,12 @@
let busy = $state(false);
let formError = $state('');
// inline edit
let editingId = $state<string | null>(null);
let edit = $state({ title: '', url: '', note: '', target: '', currency: '' });
let editError = $state('');
let editBusy = $state(false);
// tracking
let refetchingId = $state<string | null>(null);
let historyFor = $state<string | null>(null);
@@ -101,8 +107,57 @@
}
}
function startEdit(item: Item) {
editingId = item.id;
editError = '';
edit = {
title: item.title,
url: item.url ?? '',
note: item.note ?? '',
target: item.target_price != null ? String(item.target_price) : '',
currency: item.currency ?? ''
};
}
function cancelEdit() {
editingId = null;
editError = '';
}
async function saveEdit(item: Item) {
if (!edit.title.trim()) {
editError = 'title is required';
return;
}
editBusy = true;
editError = '';
try {
const t = edit.target.trim();
const tp = t ? Number(t) : null; // null clears the target → notify on any drop
if (t && !Number.isFinite(tp as number)) {
editError = 'target must be a number';
return;
}
const cur = edit.currency.trim().toUpperCase();
const updated = await listsApi.updateItem(item.id, {
title: edit.title.trim(),
url: edit.url.trim() || null,
note: edit.note.trim() || null,
target_price: tp,
...(cur ? { currency: cur } : {})
});
const i = items.findIndex((x) => x.id === item.id);
if (i >= 0) items[i] = updated;
editingId = null;
} catch (err) {
editError = err instanceof ApiError ? err.message : 'failed to save';
} finally {
editBusy = false;
}
}
async function removeItem(item: Item) {
if (!confirm(`cast out${item.title}”?`)) return;
if (!confirm(`remove${item.title}”?`)) return;
try {
await listsApi.removeItem(item.id);
items = items.filter((x) => x.id !== item.id);
@@ -120,7 +175,7 @@
if (i >= 0) items[i] = updated;
if (historyFor === item.id) history = await listsApi.history(item.id);
} catch (err) {
formError = err instanceof ApiError ? err.message : 'failed to keep vigil';
formError = err instanceof ApiError ? err.message : 'failed to check price';
} finally {
refetchingId = null;
}
@@ -137,7 +192,7 @@
try {
history = await listsApi.history(item.id);
} catch (err) {
formError = err instanceof ApiError ? err.message : 'failed to read the chronicle';
formError = err instanceof ApiError ? err.message : 'failed to load history';
} finally {
historyLoading = false;
}
@@ -167,9 +222,9 @@
renounced: 'border-smoke text-mute'
};
const STATUS_LABEL: Record<ItemStatus, string> = {
coveted: 'coveted',
acquired: 'acquired',
renounced: 'renounced'
coveted: 'want',
acquired: 'bought',
renounced: 'skip'
};
function money(v: number | null, cur: string | null) {
@@ -193,27 +248,27 @@
</div>
</div>
<!-- Add temptation -->
<!-- Add item -->
<form class="panel panel-acid space-y-4 p-6" onsubmit={addItem}>
<p class="label">add a temptation</p>
<input class="field" bind:value={title} maxlength="200" placeholder="what you covet" />
<p class="label">add an item</p>
<input class="field" bind:value={title} maxlength="200" placeholder="what do you want?" />
<div class="grid gap-4 sm:grid-cols-[1fr_8rem]">
<input class="field" bind:value={url} placeholder="product URL (we'll keep vigil on the price)" />
<input class="field" bind:value={url} placeholder="product link (we'll track the price)" />
<input class="field" bind:value={targetPrice} inputmode="decimal" placeholder="target price" />
</div>
<input class="field" bind:value={note} maxlength="1000" placeholder="note to self (optional)" />
{#if formError}<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{formError}</p>{/if}
<button class="btn btn-acid" disabled={busy}>{busy ? 'coveting…' : 'covet it +'}</button>
<button class="btn btn-acid" disabled={busy}>{busy ? 'adding…' : 'add item +'}</button>
</form>
{#if !loaded}
<p class="text-center text-mute flicker">unveiling temptations…</p>
<p class="text-center text-mute flicker">loading items…</p>
{:else if loadError}
<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{loadError}</p>
{:else if items.length === 0}
<div class="panel p-10 text-center">
<p class="gospel text-2xl">this list is bare</p>
<p class="mt-2 text-mute">paste a craving above to begin.</p>
<p class="gospel text-2xl">this list is empty</p>
<p class="mt-2 text-mute">add something you want above to begin.</p>
</div>
{:else}
<ul class="space-y-3">
@@ -265,7 +320,7 @@
<!-- Status is the primary control: a real, cyclable badge. -->
<button
class="tag shrink-0 cursor-pointer transition hover:brightness-125 {STATUS_STYLE[item.status]}"
title="click to cycle: coveted → acquired → renounced"
title="click to cycle: want → bought → skip"
onclick={() => cycleStatus(item)}
>
{STATUS_LABEL[item.status]}
@@ -277,7 +332,7 @@
{#if item.url}
<button
class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris disabled:opacity-40"
title="refetch price now (keep vigil)"
title="check the price now"
disabled={refetchingId === item.id}
onclick={() => refetchItem(item)}
>
@@ -285,12 +340,19 @@
</button>
<button
class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris"
title="price history (the chronicle)"
title="price history"
onclick={() => toggleHistory(item)}
>
{historyFor === item.id ? 'hide history' : 'history'}
</button>
{/if}
<button
class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris"
title="edit this item"
onclick={() => (editingId === item.id ? cancelEdit() : startEdit(item))}
>
{editingId === item.id ? 'close' : '✎ edit'}
</button>
<button
class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-rose hover:text-rose"
title="remove from this list"
@@ -300,19 +362,49 @@
</button>
</div>
{#if editingId === item.id}
<div class="space-y-3 border-t border-smoke pt-3">
<input class="field" bind:value={edit.title} maxlength="200" placeholder="title" />
<input class="field" bind:value={edit.url} placeholder="product URL" />
<div class="grid gap-3 sm:grid-cols-[1fr_8rem]">
<input
class="field"
bind:value={edit.target}
inputmode="decimal"
placeholder="target price (blank = notify on any drop)"
/>
<input
class="field"
bind:value={edit.currency}
maxlength="3"
placeholder="currency"
title="3-letter code, e.g. EUR — overrides the detected currency"
/>
</div>
<input class="field" bind:value={edit.note} maxlength="1000" placeholder="note" />
{#if editError}<p class="text-xs text-rose">{editError}</p>{/if}
<div class="flex justify-end gap-2 text-xs">
<button class="rounded border border-smoke px-3 py-1 text-mute hover:text-ink" onclick={cancelEdit}>cancel</button>
<button class="btn btn-acid px-3 py-1" disabled={editBusy} onclick={() => saveEdit(item)}>
{editBusy ? 'saving…' : 'save'}
</button>
</div>
</div>
{/if}
{#if onSale(item)}
<p class="gospel text-sm text-mint"> the price has fallen — your moment is upon you</p>
<p class="gospel text-sm text-mint">✦ price dropped — grab it now</p>
{/if}
{#if item.last_error}
<p class="text-xs text-rose">vigil faltered: {item.last_error}</p>
<p class="text-xs text-rose">price check failed: {item.last_error}</p>
{/if}
{#if historyFor === item.id}
<div class="border-t border-smoke pt-3">
{#if historyLoading}
<p class="text-xs text-mute flicker">unrolling the chronicle</p>
<p class="text-xs text-mute flicker">loading history</p>
{:else if history.length === 0}
<p class="text-xs text-mute">no observations yet — keep vigil to begin the record.</p>
<p class="text-xs text-mute">no price checks yet — hit refresh to start tracking.</p>
{:else}
<ul class="space-y-1 text-xs">
{#each history as h}
+1 -1
View File
@@ -29,7 +29,7 @@
<div class="mx-auto max-w-md">
<div class="panel p-8">
<p class="label">welcome back</p>
<h1 class="mb-6 font-display text-3xl font-bold">RETURN TO WORSHIP</h1>
<h1 class="mb-6 font-display text-3xl font-bold">WELCOME BACK</h1>
<form class="space-y-4" onsubmit={submit}>
<div>
+3 -3
View File
@@ -33,8 +33,8 @@
<div class="mx-auto max-w-md">
<div class="panel panel-acid p-8">
<p class="label">new devotee</p>
<h1 class="mb-6 font-display text-3xl font-bold">BEGIN ASCENSION</h1>
<p class="label">new here</p>
<h1 class="mb-6 font-display text-3xl font-bold">CREATE ACCOUNT</h1>
<form class="space-y-4" onsubmit={submit}>
<div>
@@ -55,7 +55,7 @@
{/if}
<button class="btn btn-acid w-full" disabled={busy}>
{busy ? 'carving…' : 'sign up'}
{busy ? 'creating…' : 'sign up'}
</button>
</form>
+2 -2
View File
@@ -31,7 +31,7 @@
<div class="mx-auto max-w-md">
<div class="panel panel-acid p-8">
<p class="label">set new password</p>
<h1 class="mb-6 font-display text-3xl font-bold">FORGE A NEW KEY</h1>
<h1 class="mb-6 font-display text-3xl font-bold">SET A NEW PASSWORD</h1>
{#if !token}
<p class="border-2 border-rose bg-rose/10 px-3 py-3 text-sm text-rose">
@@ -51,7 +51,7 @@
{#if error}
<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{error}</p>
{/if}
<button class="btn btn-acid w-full" disabled={busy}>{busy ? 'cutting…' : 'set password'}</button>
<button class="btn btn-acid w-full" disabled={busy}>{busy ? 'saving…' : 'set password'}</button>
</form>
{/if}
</div>
+4 -4
View File
@@ -59,9 +59,9 @@
{#if auth.loaded && auth.user}
<div class="mx-auto max-w-2xl space-y-6">
<div>
<p class="label">your rites</p>
<h1 class="font-display text-4xl font-bold">THE SANCTUM</h1>
<p class="gospel mt-1 text-lg">tune your devotion</p>
<p class="label">your account</p>
<h1 class="font-display text-4xl font-bold">SETTINGS</h1>
<p class="gospel mt-1 text-lg">tune your spending habit</p>
</div>
<!-- Verification status -->
@@ -102,7 +102,7 @@
<label class="flex cursor-pointer items-center gap-3">
<input type="checkbox" class="size-5 accent-mint" bind:checked={settings.notify_email} />
<span class="font-mono text-sm">summon me when the price falls</span>
<span class="font-mono text-sm">email me when a price drops</span>
</label>
{#if error}