updated wording
This commit is contained in:
@@ -53,6 +53,7 @@ export type NewItem = {
|
||||
url?: string | null;
|
||||
note?: string | null;
|
||||
target_price?: number | null;
|
||||
currency?: string | null;
|
||||
};
|
||||
|
||||
// ---- Lists ----------------------------------------------------------------
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
|
||||
const ticker =
|
||||
'CONSUME · ASCEND · ACCUMULATE · YOU DESERVE IT · MANIFEST THE DEBT · TREAT YOURSELF · ONE MORE WON’T HURT · ';
|
||||
'CONSUME · SPEND · ACCUMULATE · YOU DESERVE IT · TREAT YOURSELF · ONE MORE WON’T 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>
|
||||
|
||||
@@ -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 isn’t going to fill itself”</p>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user