Add FamDone Brand component
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# FamChore v2 — AI Agent Reference
|
# FamDone v2 — AI Agent Reference
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# FamChore v2 — Architecture & Developer Reference
|
# FamDone v2 — Architecture & Developer Reference
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# FamChore v2 — Development Memory
|
# FamDone v2 — Development Memory
|
||||||
|
|
||||||
## 2026-08-17 — Schema collapse to single source of truth (Option 1)
|
## 2026-08-17 — Schema collapse to single source of truth (Option 1)
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Sidebar (collapsible to mini-mode)
|
### Sidebar (collapsible to mini-mode)
|
||||||
- Header: app name (FamChore)
|
- Header: app name (FamDone)
|
||||||
- Admin CTAs: Dashboard, Chores, Rewards (badge count), Bonuses
|
- Admin CTAs: Dashboard, Chores, Rewards (badge count), Bonuses
|
||||||
- Member CTAs: Dashboard, Preferences
|
- Member CTAs: Dashboard, Preferences
|
||||||
- Footer: family name, Settings (admin only), Log out
|
- Footer: family name, Settings (admin only), Log out
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# FamChore v2 — Rules
|
# FamDone v2 — Rules
|
||||||
|
|
||||||
## Auth
|
## Auth
|
||||||
- Every collection query includes `famId = @request.auth.famId` filter
|
- Every collection query includes `famId = @request.auth.famId` filter
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# FamChore — TODO
|
# FamDone — TODO
|
||||||
|
|
||||||
(Completed work is tracked in MEMORY.md / git history.)
|
(Completed work is tracked in MEMORY.md / git history.)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
||||||
|
After Width: | Height: | Size: 328 B |
@@ -1,5 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { title, subtitle, children }: {
|
import FamDone from './FamDone.svelte';
|
||||||
|
|
||||||
|
let {
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
children
|
||||||
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
children?: any;
|
children?: any;
|
||||||
@@ -7,7 +13,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="auth">
|
<main class="auth">
|
||||||
<a class="brand" href="/">FamChore</a>
|
<a class="brand" href="/"><FamDone /></a>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
{#if subtitle}
|
{#if subtitle}
|
||||||
@@ -26,7 +32,10 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
background: linear-gradient(135deg, #eef2ff 0%, #ffffff 55%, #eef2ff 100%);
|
background: linear-gradient(135deg, #eef2ff 0%, #ffffff 55%, #eef2ff 100%);
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
font-family:
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
sans-serif;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
.brand {
|
.brand {
|
||||||
@@ -56,6 +65,8 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.card { padding: 1.5rem; }
|
.card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { tag = 'span' }: { tag?: 'span' | 'h1' | 'h2' | 'h3' | 'a' | 'strong' | 'p' } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element this={tag} class="fam-done">
|
||||||
|
FamDone<svg
|
||||||
|
class="fam-done-check"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="#10b981"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" /></svg
|
||||||
|
></svelte:element
|
||||||
|
>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fam-done {
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.fam-done-check {
|
||||||
|
vertical-align: super;
|
||||||
|
position: relative;
|
||||||
|
top: -0.3em;
|
||||||
|
margin-left: 1px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FamDone from './FamDone.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
<footer class="app-footer">
|
<footer class="app-footer">
|
||||||
<span class="footer-text">FamChore</span>
|
<span class="footer-text"><FamDone /></span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
logoutIcon,
|
logoutIcon,
|
||||||
prefsIcon
|
prefsIcon
|
||||||
} from './icons';
|
} from './icons';
|
||||||
|
import FamDone from './FamDone.svelte';
|
||||||
|
|
||||||
let { famName = '', session = null, isParent = false, role = 'child' } = $props();
|
let { famName = '', session = null, isParent = false, role = 'child' } = $props();
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<span class="app-icon">✦</span>
|
<span class="app-icon">✦</span>
|
||||||
<span class="app-name">FamChore</span>
|
<span class="app-name"><FamDone /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default as Sidebar } from './Sidebar.svelte';
|
export { default as Sidebar } from './Sidebar.svelte';
|
||||||
|
export { default as FamDone } from './FamDone.svelte';
|
||||||
export { default as TopNav } from './TopNav.svelte';
|
export { default as TopNav } from './TopNav.svelte';
|
||||||
export { default as Footer } from './Footer.svelte';
|
export { default as Footer } from './Footer.svelte';
|
||||||
export { default as ViewHeader } from './ViewHeader.svelte';
|
export { default as ViewHeader } from './ViewHeader.svelte';
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export async function sendParentInviteEmail(opts: {
|
|||||||
await resend.emails.send({
|
await resend.emails.send({
|
||||||
from: 'no-reply@famchamp.ai',
|
from: 'no-reply@famchamp.ai',
|
||||||
to,
|
to,
|
||||||
subject: `You're invited to ${famName} on FamChore`,
|
subject: `You're invited to ${famName} on FamDone`,
|
||||||
html: `
|
html: `
|
||||||
<p>You've been invited as a parent on <strong>${famName}</strong>.</p>
|
<p>You've been invited as a parent on <strong>${famName}</strong>.</p>
|
||||||
<p><a href="${inviteLink}">Click here to join</a> and set up your password.</p>
|
<p><a href="${inviteLink}">Click here to join</a> and set up your password.</p>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
|
import FamDone from '$lib/components/FamDone.svelte';
|
||||||
import { homeIcon, checkCircleIcon, chevronRightIcon } from '$lib/components/icons';
|
import { homeIcon, checkCircleIcon, chevronRightIcon } from '$lib/components/icons';
|
||||||
import { readShortcut, shortcutTarget, type Shortcut } from '$lib/shortcut';
|
import { readShortcut, shortcutTarget, type Shortcut } from '$lib/shortcut';
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>FamChore — Chores done. Allowance earned.</title>
|
<title>FamDone — Chores done. Allowance earned.</title>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Turn family chores into points and pocket money. Assign chores, kids complete them, allowances are calculated automatically."
|
content="Turn family chores into points and pocket money. Assign chores, kids complete them, allowances are calculated automatically."
|
||||||
@@ -40,8 +41,8 @@
|
|||||||
<span class="pill">For busy families</span>
|
<span class="pill">For busy families</span>
|
||||||
<h1>Chores done. <span class="accent">Allowance earned.</span></h1>
|
<h1>Chores done. <span class="accent">Allowance earned.</span></h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
FamChore turns everyday household chores into points and pocket money. Assign the chores,
|
<FamDone /> turns everyday household chores into points and pocket money. Assign the chores,
|
||||||
let your kids see their progress live, and FamChore calculates the allowance automatically
|
let your kids see their progress live, and <FamDone /> calculates the allowance automatically
|
||||||
— no spreadsheets, no nagging.
|
— no spreadsheets, no nagging.
|
||||||
</p>
|
</p>
|
||||||
<ul class="hero-ticks">
|
<ul class="hero-ticks">
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
if (returnHandled || page.url.searchParams.get('checkout') !== 'return') return;
|
if (returnHandled || page.url.searchParams.get('checkout') !== 'return') return;
|
||||||
returnHandled = true;
|
returnHandled = true;
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
notices.success('Welcome to FamChore!', 'Your subscription is active.');
|
notices.success('Welcome to FamDone!', 'Your subscription is active.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startActivating();
|
startActivating();
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
<strong>Access paused</strong>
|
<strong>Access paused</strong>
|
||||||
<span>
|
<span>
|
||||||
{isParent
|
{isParent
|
||||||
? 'Add an access code or resume your subscription to keep using FamChore.'
|
? 'Add an access code or resume your subscription to keep using FamDone.'
|
||||||
: 'Your family access is paused.'}
|
: 'Your family access is paused.'}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
|
import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
|
||||||
|
import FamDone from '$lib/components/FamDone.svelte';
|
||||||
import TemplateIcon from '$lib/components/TemplateIcon.svelte';
|
import TemplateIcon from '$lib/components/TemplateIcon.svelte';
|
||||||
import { ICON_NAMES, TEMPLATE_COLORS } from '$lib/templateIcons';
|
import { ICON_NAMES, TEMPLATE_COLORS } from '$lib/templateIcons';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
@@ -82,7 +83,9 @@
|
|||||||
|
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<a class="brand" href="/"><span class="brand-icon">{@html homeIcon}</span> <strong>FamChore</strong></a>
|
<a class="brand" href="/"
|
||||||
|
><span class="brand-icon">{@html homeIcon}</span> <strong><FamDone /></strong></a
|
||||||
|
>
|
||||||
<nav>
|
<nav>
|
||||||
{#if data.authenticated}
|
{#if data.authenticated}
|
||||||
<form method="POST" action="?/logout" use:enhance>
|
<form method="POST" action="?/logout" use:enhance>
|
||||||
@@ -136,7 +139,11 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<ViewHeader title="Platform Admin" subtitle="Families, access codes and platform settings" hero />
|
<ViewHeader
|
||||||
|
title="Platform Admin"
|
||||||
|
subtitle="Families, access codes and platform settings"
|
||||||
|
hero
|
||||||
|
/>
|
||||||
|
|
||||||
<CardGrid>
|
<CardGrid>
|
||||||
<Card cols={2} title="Overview" accent="#6366f1">
|
<Card cols={2} title="Overview" accent="#6366f1">
|
||||||
@@ -174,7 +181,11 @@
|
|||||||
<span class="flag-name">debug</span>
|
<span class="flag-name">debug</span>
|
||||||
<form method="POST" action="?/togglePlatformFlag" use:enhance>
|
<form method="POST" action="?/togglePlatformFlag" use:enhance>
|
||||||
<input type="hidden" name="flag" value="debug" />
|
<input type="hidden" name="flag" value="debug" />
|
||||||
<button type="submit" class="toggle-btn" class:active={page.data.platformFlags?.debug}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="toggle-btn"
|
||||||
|
class:active={page.data.platformFlags?.debug}
|
||||||
|
>
|
||||||
{page.data.platformFlags?.debug ? 'ON' : 'OFF'}
|
{page.data.platformFlags?.debug ? 'ON' : 'OFF'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -254,7 +265,9 @@
|
|||||||
<td class="row-actions" data-label="Actions">
|
<td class="row-actions" data-label="Actions">
|
||||||
<form method="POST" action="?/toggleCode" use:enhance>
|
<form method="POST" action="?/toggleCode" use:enhance>
|
||||||
<input type="hidden" name="id" value={c.id} />
|
<input type="hidden" name="id" value={c.id} />
|
||||||
<button type="submit" class="link-btn">{c.active ? 'Disable' : 'Enable'}</button>
|
<button type="submit" class="link-btn"
|
||||||
|
>{c.active ? 'Disable' : 'Enable'}</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
{#if !c.usedBy.length}
|
{#if !c.usedBy.length}
|
||||||
<form method="POST" action="?/deleteCode" use:enhance>
|
<form method="POST" action="?/deleteCode" use:enhance>
|
||||||
@@ -297,8 +310,16 @@
|
|||||||
<td data-label="Parent">{fam.parentEmail || '—'}</td>
|
<td data-label="Parent">{fam.parentEmail || '—'}</td>
|
||||||
<td data-label="Members">{fam.memberCount}</td>
|
<td data-label="Members">{fam.memberCount}</td>
|
||||||
<td data-label="Plan">
|
<td data-label="Plan">
|
||||||
<span class="badge {fam.paymentMode === 'sub' ? 'on' : fam.active ? 'dur' : 'off'}">
|
<span
|
||||||
{modeLabel[fam.paymentMode] || fam.paymentMode}{fam.active ? '' : ' · paused'}
|
class="badge {fam.paymentMode === 'sub'
|
||||||
|
? 'on'
|
||||||
|
: fam.active
|
||||||
|
? 'dur'
|
||||||
|
: 'off'}"
|
||||||
|
>
|
||||||
|
{modeLabel[fam.paymentMode] || fam.paymentMode}{fam.active
|
||||||
|
? ''
|
||||||
|
: ' · paused'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Claims">
|
<td data-label="Claims">
|
||||||
@@ -308,18 +329,21 @@
|
|||||||
<span class="badge none">None</span>
|
<span class="badge none">None</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="row-actions" data-label="Actions"><a href="/{fam.slug}" class="link">View {@html chevronRightIcon}</a></td>
|
<td class="row-actions" data-label="Actions"
|
||||||
|
><a href="/{fam.slug}" class="link">View {@html chevronRightIcon}</a></td
|
||||||
|
>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</Card>
|
</div></Card
|
||||||
|
>
|
||||||
</CardGrid>
|
</CardGrid>
|
||||||
|
|
||||||
<CardGrid>
|
<CardGrid>
|
||||||
<Card cols={2} title="Chore Templates" accent="#2563eb">
|
<Card cols={2} title="Chore Templates" accent="#2563eb">
|
||||||
<div class="tpl-grid">
|
<div class="tpl-grid">
|
||||||
{#each (data.choreTemplates || []) as t}
|
{#each data.choreTemplates || [] as t}
|
||||||
<div class="tpl-card" style="border-left-color:{t.color}">
|
<div class="tpl-card" style="border-left-color:{t.color}">
|
||||||
<div class="tpl-head">
|
<div class="tpl-head">
|
||||||
<TemplateIcon name={t.icon} size={20} color={t.color} />
|
<TemplateIcon name={t.icon} size={20} color={t.color} />
|
||||||
@@ -364,7 +388,7 @@
|
|||||||
|
|
||||||
<Card cols={1} title="Reward Templates" accent="#8b5cf6">
|
<Card cols={1} title="Reward Templates" accent="#8b5cf6">
|
||||||
<div class="tpl-grid">
|
<div class="tpl-grid">
|
||||||
{#each (data.bonusTemplates || []) as t}
|
{#each data.bonusTemplates || [] as t}
|
||||||
<div class="tpl-card" style="border-left-color:{t.color}">
|
<div class="tpl-card" style="border-left-color:{t.color}">
|
||||||
<div class="tpl-head">
|
<div class="tpl-head">
|
||||||
<TemplateIcon name={t.icon} size={20} color={t.color} />
|
<TemplateIcon name={t.icon} size={20} color={t.color} />
|
||||||
@@ -429,7 +453,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<input type="hidden" name="type" value={tplType} />
|
<input type="hidden" name="type" value={tplType} />
|
||||||
<label>Name <input name="name" bind:value={tplVals.name} required /></label>
|
<label>Name <input name="name" bind:value={tplVals.name} required /></label>
|
||||||
<label>Description <input name="description" bind:value={tplVals.description} /></label>
|
<label
|
||||||
|
>Description <input name="description" bind:value={tplVals.description} /></label
|
||||||
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>
|
<label>
|
||||||
Icon
|
Icon
|
||||||
@@ -437,7 +463,9 @@
|
|||||||
{#each ICON_NAMES as n}<option value={n}>{n}</option>{/each}
|
{#each ICON_NAMES as n}<option value={n}>{n}</option>{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<span class="icon-preview"><TemplateIcon name={tplVals.icon} size={22} color={tplVals.color} /></span>
|
<span class="icon-preview"
|
||||||
|
><TemplateIcon name={tplVals.icon} size={22} color={tplVals.color} /></span
|
||||||
|
>
|
||||||
<div class="color-field">
|
<div class="color-field">
|
||||||
Colour
|
Colour
|
||||||
<div class="color-swatches">
|
<div class="color-swatches">
|
||||||
@@ -451,7 +479,8 @@
|
|||||||
? '#e5e7eb'
|
? '#e5e7eb'
|
||||||
: c.value}"
|
: c.value}"
|
||||||
title={c.label}
|
title={c.label}
|
||||||
onclick={() => (tplVals.color = c.value)}></button>
|
onclick={() => (tplVals.color = c.value)}
|
||||||
|
></button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" name="color" value={tplVals.color} />
|
<input type="hidden" name="color" value={tplVals.color} />
|
||||||
@@ -475,7 +504,12 @@
|
|||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Default Value
|
Default Value
|
||||||
<input type="number" step="any" name="defaultValue" bind:value={tplVals.defaultValue} />
|
<input
|
||||||
|
type="number"
|
||||||
|
step="any"
|
||||||
|
name="defaultValue"
|
||||||
|
bind:value={tplVals.defaultValue}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
{:else}
|
{:else}
|
||||||
<label>
|
<label>
|
||||||
@@ -535,7 +569,12 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="check">
|
<label class="check">
|
||||||
<input type="checkbox" name="isPocketMoney" value="true" bind:checked={tplVals.isPocketMoney} />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="isPocketMoney"
|
||||||
|
value="true"
|
||||||
|
bind:checked={tplVals.isPocketMoney}
|
||||||
|
/>
|
||||||
Pocket Money
|
Pocket Money
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -548,7 +587,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -560,9 +598,7 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
background:
|
background: radial-gradient(60rem 30rem at 50% -10rem, #eef2ff 0%, transparent 65%), #fafafa;
|
||||||
radial-gradient(60rem 30rem at 50% -10rem, #eef2ff 0%, transparent 65%),
|
|
||||||
#fafafa;
|
|
||||||
}
|
}
|
||||||
.topbar {
|
.topbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -581,7 +617,11 @@
|
|||||||
color: #111827;
|
color: #111827;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.topbar nav { display: flex; align-items: center; gap: 1rem; }
|
.topbar nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
.nav-link {
|
.nav-link {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #4b5563;
|
color: #4b5563;
|
||||||
@@ -594,7 +634,9 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.link-btn-nav:hover { color: #111827; }
|
.link-btn-nav:hover {
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -608,8 +650,12 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
main :global(.card-grid),
|
main :global(.card-grid),
|
||||||
main :global(.hero) { width: 100%; }
|
main :global(.hero) {
|
||||||
.head { text-align: center; }
|
width: 100%;
|
||||||
|
}
|
||||||
|
.head {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.head h1 {
|
.head h1 {
|
||||||
margin: 0 0 0.35rem;
|
margin: 0 0 0.35rem;
|
||||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||||
@@ -775,10 +821,18 @@
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
color: #374151;
|
color: #374151;
|
||||||
}
|
}
|
||||||
.fam-table tbody tr:last-child td { border-bottom: none; }
|
.fam-table tbody tr:last-child td {
|
||||||
.fam-table tbody tr { transition: background 0.12s ease; }
|
border-bottom: none;
|
||||||
.fam-table tbody tr:hover { background: #f8faff; }
|
}
|
||||||
.th-actions { text-align: right; }
|
.fam-table tbody tr {
|
||||||
|
transition: background 0.12s ease;
|
||||||
|
}
|
||||||
|
.fam-table tbody tr:hover {
|
||||||
|
background: #f8faff;
|
||||||
|
}
|
||||||
|
.th-actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.code-value {
|
.code-value {
|
||||||
font-family: ui-monospace, monospace;
|
font-family: ui-monospace, monospace;
|
||||||
@@ -894,7 +948,9 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.fam-link:hover { color: #4338ca; }
|
.fam-link:hover {
|
||||||
|
color: #4338ca;
|
||||||
|
}
|
||||||
.link {
|
.link {
|
||||||
color: #4338ca;
|
color: #4338ca;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -919,7 +975,11 @@
|
|||||||
|
|
||||||
/* ── Responsive: stack rows into cards with labels ── */
|
/* ── Responsive: stack rows into cards with labels ── */
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.table-wrap { border: none; overflow: visible; background: transparent; }
|
.table-wrap {
|
||||||
|
border: none;
|
||||||
|
overflow: visible;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
.fam-table,
|
.fam-table,
|
||||||
.fam-table tbody,
|
.fam-table tbody,
|
||||||
.fam-table tr,
|
.fam-table tr,
|
||||||
@@ -927,7 +987,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.fam-table thead { display: none; }
|
.fam-table thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.fam-table tbody tr {
|
.fam-table tbody tr {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
@@ -954,7 +1016,9 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.fam-table tbody td:empty { display: none; }
|
.fam-table tbody td:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.row-actions {
|
.row-actions {
|
||||||
justify-content: flex-end !important;
|
justify-content: flex-end !important;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
@@ -983,11 +1047,30 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
.tpl-head strong { font-size: 0.9rem; }
|
.tpl-head strong {
|
||||||
.tpl-meta { display: flex; gap: 0.3rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
|
font-size: 0.9rem;
|
||||||
.tpl-actions { display: flex; gap: 0.75rem; }
|
}
|
||||||
.tpl-new { display: flex; gap: 0.6rem; margin-top: 0.5rem; }
|
.tpl-meta {
|
||||||
.row { display: flex; align-items: flex-end; gap: 0.75rem; flex-wrap: wrap; }
|
display: flex;
|
||||||
|
gap: 0.3rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
}
|
||||||
|
.tpl-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
.tpl-new {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.6rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
.color-field {
|
.color-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -996,7 +1079,11 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
.color-swatches { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
.color-swatches {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
.swatch {
|
.swatch {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
@@ -1006,7 +1093,10 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
.swatch.selected { outline: 2px solid #111827; outline-offset: 1px; }
|
.swatch.selected {
|
||||||
|
outline: 2px solid #111827;
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
.icon-preview {
|
.icon-preview {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1016,10 +1106,23 @@
|
|||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
.badge.chore { background: #dbeafe; color: #1e40af; }
|
.badge.chore {
|
||||||
.badge.reward { background: #ede9fe; color: #6d28d9; }
|
background: #dbeafe;
|
||||||
.badge.pocket { background: #dcfce7; color: #166534; }
|
color: #1e40af;
|
||||||
.check { display: flex; align-items: center; gap: 0.4rem; }
|
}
|
||||||
|
.badge.reward {
|
||||||
|
background: #ede9fe;
|
||||||
|
color: #6d28d9;
|
||||||
|
}
|
||||||
|
.badge.pocket {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
.check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Modal (shared) ── */
|
/* ── Modal (shared) ── */
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -1041,7 +1144,9 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
.modal h3 { margin: 0 0 0.75rem; }
|
.modal h3 {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
}
|
||||||
.modal label {
|
.modal label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.6rem;
|
margin-bottom: 0.6rem;
|
||||||
@@ -1072,6 +1177,12 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
.modal-actions button[type='button'] { background: #e5e7eb; color: #374151; }
|
.modal-actions button[type='button'] {
|
||||||
.modal-actions button[type='submit'] { background: #6366f1; color: #fff; }
|
background: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
.modal-actions button[type='submit'] {
|
||||||
|
background: #6366f1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export async function POST({ request, url }) {
|
|||||||
await resend.emails.send({
|
await resend.emails.send({
|
||||||
from: 'no-reply@famchamp.ai',
|
from: 'no-reply@famchamp.ai',
|
||||||
to: email,
|
to: email,
|
||||||
subject: 'Reset your FamChore password',
|
subject: 'Reset your FamDone password',
|
||||||
html: `
|
html: `
|
||||||
<p>You requested a password reset for your FamChore account.</p>
|
<p>You requested a password reset for your FamDone account.</p>
|
||||||
<p><a href="${resetUrl}">Click here to reset your password</a></p>
|
<p><a href="${resetUrl}">Click here to reset your password</a></p>
|
||||||
<p>This link expires in 1 hour.</p>
|
<p>This link expires in 1 hour.</p>
|
||||||
<p>If you didn't request this, you can ignore this email.</p>
|
<p>If you didn't request this, you can ignore this email.</p>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AuthShell title="Log in" subtitle="Welcome back to FamChore.">
|
<AuthShell title="Log in" subtitle="Welcome back to FamDone.">
|
||||||
{#if showForgot}
|
{#if showForgot}
|
||||||
{#if forgotSuccess}
|
{#if forgotSuccess}
|
||||||
<p class="form-ok">
|
<p class="form-ok">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { PUBLIC_STRIPE_PUBLISHABLE_KEY } from '$app/env/public';
|
import { PUBLIC_STRIPE_PUBLISHABLE_KEY } from '$app/env/public';
|
||||||
import { loadStripe, type StripeEmbeddedCheckout } from '@stripe/stripe-js';
|
import { loadStripe, type StripeEmbeddedCheckout } from '@stripe/stripe-js';
|
||||||
import { Button, PricingPlans } from '$lib/components';
|
import { Button, PricingPlans } from '$lib/components';
|
||||||
|
import FamDone from '$lib/components/FamDone.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
import { homeIcon, chevronLeftIcon, chevronRightIcon } from '$lib/components/icons';
|
import { homeIcon, chevronLeftIcon, chevronRightIcon } from '$lib/components/icons';
|
||||||
|
|
||||||
@@ -56,12 +57,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><title>Pricing — FamChore</title></svelte:head>
|
<svelte:head><title>Pricing — FamDone</title></svelte:head>
|
||||||
|
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<a class="brand" href="/"
|
<a class="brand" href="/"
|
||||||
><span class="brand-icon">{@html homeIcon}</span> <strong>FamChore</strong></a
|
><span class="brand-icon">{@html homeIcon}</span> <strong><FamDone /></strong></a
|
||||||
>
|
>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/login" class="nav-link">Log in</a>
|
<a href="/login" class="nav-link">Log in</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user