This is the name shown to your family. The address stays at /{famSlug} even if you rename it — links you've shared keep working.

{#if fam?.slug}

Family page: /{fam.slug}

{/if}
{ return async ({ result }) => { if (result.type === 'error') alert(result.error || 'Failed to update payday'); }; }} class="payday-form" >

Payday: the week starts on this day and weekly earnings are settled at this time. Auto timezone follows each device.

Group chores into seasons. Toggle seasons on/off from the top nav.

    {#each data.seasons as s}
  • {s.name}
  • {/each}
{#if deletingSeason}
(deletingSeason = null)} role="presentation">
{/if}

Add a child. They'll pick their own colour after joining.

    {#each members as m}
  • {m.name} /{famSlug}/{handleOf(m.username)}
  • {/each}
{ return async ({ formData, result }) => { if (result.type === 'success' && result.data?.ok) { showQR = false; qrDataUrl = ''; issued = { otp: result.data.otp, joinUrl: result.data.joinUrl, name: String(formData.get('name') || '') }; } else if (result.type === 'success' && result.data?.error) { alert(result.data.error); } }; }} class="invite-form" >

Generates a 6-digit code valid for 20 minutes. The child enters it at the join link.

{#if issued?.otp}

Code for {issued.name} (valid 20 min):

{issued.otp}

{invitePath}

{#if showQR && qrDataUrl}
QR Code
{/if}
{/if}

Send an email invitation for another parent to join as an admin.

They will set up their own password on first login.

{#if fam?.paymentMode !== 'sub'} {#if hasCode}

Access active via access code {#if data.accessCode?.duration} — {formatCountdown(codeEntryDate, data.accessCode.duration)} (expires {formatShortDate(addMonthsUTC(codeEntryDate!, data.accessCode.duration))}) {:else} — never expires {/if}.

{ return async ({ result, update }) => { if (result.type === 'success') accessMsg = 'Access code revoked.'; else if (result.type === 'failure') accessMsg = (result.data as any)?.error || 'Revoke failed.'; await update(); }; }} >
{:else}

No access applied. Enter a valid access code to enable your family.

{ return async ({ result, update }) => { if (result.type === 'success' && result.data) { const d = result.data as { error?: string; ok?: boolean }; accessMsg = d.error || 'Code applied — access enabled.'; accessCodeInput = ''; } await update(); }; }} >
{/if} {#if accessMsg}

{accessMsg}

{/if}
{/if}

{fam?.paymentMode === 'sub' ? 'Current plan: Subscription.' : fam?.paymentMode === 'code' ? `Current plan: Access code${hasCode ? '' : ' (invalid)'}.` : fam?.paymentMode === 'canceled' ? 'Your subscription was canceled.' : 'No active plan.'}

{#if fam?.paymentMode === 'sub' && data.subStatus?.cancelAtPeriodEnd}

No active subscription. You have {timeUntil(data.subStatus.endsAt)} of access left (until {formatShortDate(String(data.subStatus.endsAt))}). No further charges.

{:else if fam?.paymentMode === 'sub'}
{ return async ({ result }) => { if (result.type === 'success' && (result.data as any)?.endsAt) { notices.success( 'Subscription ending', `Ends ${formatShortDate(String((result.data as any).endsAt))} — access continues until then, no further charges.` ); } else if (result.type === 'failure') { notices.error( 'Couldn’t end subscription', (result.data as any)?.error || 'Please try again.' ); } }; }} >

Ending stops future charges at the close of the paid period — your data is kept and you can resubscribe anytime. To switch plans, end this subscription first, then pick a new one from Plans.

{:else if fam?.paymentMode === 'code'}
{:else}
{/if}
{#if page.data.platformFlags?.debug}

Debug mode is enabled. These tools are for development and testing only.

{ return async ({ result, update }) => { if (result.type === 'success') alert('Week completed!'); await update(); }; }} >
{ return async ({ result, update }) => { if (result.type === 'success') alert('Test data generated!'); await update(); }; }} >
{/if}