<script lang="ts">
	import { page } from '$app/state';
	import { enhance } from '$app/forms';
	import { onMount } from 'svelte';
	import { famStore } from '$lib/stores/fam.svelte';
	import { memberApi } from '$lib/client/api';
	import { formatDDMMYY, formatShortDate } from '$lib/format';
	import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
	import type { AssignedChore, Completion, ChoreTemplate, BonusConfig, Reward } from '$lib/types';
	import {
		weekStart as tzWeekStart,
		addDaysStr,
		todayInTz,
		weekdayInTz,
		wallClockToUtc,
		resolveTz,
		periodStart,
		periodEnd
	} from '../../../../../timezone.ts';

	interface Notification {
		id: string;
		famId: string;
		memberId: string;
		message: string;
		read: boolean;
		created: string;
	}

	let { data } = $props();

	let role = $state(data.role || 'child');
	let famSlug = $derived(page.params.fam);
	let username = $derived(page.params.username);

	// ─── Family timezone (resolved) ───
	const rawFamTz = $derived(data.timezone || data.fam?.timezone || 'auto');
	const famTz = $derived(resolveTz(rawFamTz));

	// ─── Parent View (admin overview) ───
	let summary = $state(data.summary);
	let today = $derived(todayInTz(famTz));

	let simulateEow = $state(!!(data.settings?.simulateEow ?? data.simulateEow));
	let eowPreview = $state<any>(null);
	const responseTags = [
		'👏 well done',
		'😊 really pleased',
		'🎯 you deserved that',
		'💪 great effort',
		'🙏 thanks'
	];
	let selectedMessage = $state<Record<string, string>>({});
	let customMessage = $state<Record<string, string>>({});
	let toast = $state('');

	let parentMembers = $derived(famStore.initialized ? famStore.members : data.members || []);
	let parentTemplates = $derived(famStore.initialized ? famStore.templates : data.templates || []);
	let parentAssigned = $derived(famStore.initialized ? famStore.assigned : data.assigned || []);
	let parentRewards = $derived(famStore.initialized ? famStore.rewards : data.rewards || []);
	let parentCompletions = $derived(
		famStore.initialized ? famStore.completions : data.completions || []
	);
	let parentBonusConfigs = $derived(
		famStore.initialized ? famStore.bonusConfigs : data.bonusConfigs || []
	);

	// Admin overview stat tiles (summed across this week's member summaries).
	let adminTiles = $derived.by(() => {
		const sums = summary?.summaries || [];
		return {
			members: parentMembers.length,
			pts: sums.reduce((t: number, s: any) => t + (Number(s.pointsEarned) || 0), 0),
			money: sums.reduce((t: number, s: any) => t + (Number(s.moneyEarned) || 0), 0),
			chores: sums.reduce((t: number, s: any) => t + (Number(s.choresCompleted) || 0), 0)
		};
	});

	function totalChoresFor(memberId: string): number {
		return parentAssigned.filter((a: any) => a.memberId === memberId).length;
	}

	function todayCompletionsFor(memberId: string) {
		return parentCompletions.filter(
			(c: any) => c.memberId === memberId && (c.date?.slice(0, 10) || c.date) === today
		);
	}

	function choreNameFor(assignedChoreId: string): string {
		const a = parentAssigned.find((x: any) => x.id === assignedChoreId);
		if (!a) return '?';
		const t = parentTemplates.find((x: any) => x.id === a.templateId);
		return a.customName || t?.name || '?';
	}

	function memberInSummary(memberId: string) {
		return summary?.summaries?.find((s: any) => s.memberId === memberId);
	}

	function claimableRewards() {
		return parentRewards.filter(
			(r: any) =>
				r.rewardType !== 'points' && (r.status === 'unclaimed' || r.status === 'requested')
		);
	}

	function memberClaimable(memberId: string) {
		return claimableRewards().filter((r: any) => r.memberId === memberId);
	}

	function memberRequested(memberId: string) {
		return parentRewards.filter(
			(r: any) => r.rewardType !== 'points' && r.memberId === memberId && r.status === 'requested'
		);
	}
	function memberOutstanding(memberId: string) {
		return parentRewards.filter(
			(r: any) => r.rewardType !== 'points' && r.memberId === memberId && r.status === 'unclaimed'
		);
	}

	let _confetti: any;
	async function fire() {
		if (!_confetti) _confetti = await import('@hiseb/confetti').then((m) => m.default);
		_confetti({
			count: 80,
			size: 4,
			velocity: 500,
			fade: true,
			position: { x: window.innerWidth / 2, y: 0 }
		});
	}

	function showToast(msg: string) {
		toast = msg;
		setTimeout(() => (toast = ''), 4000);
	}

	function handleResult({ result }, onSuccess = null) {
		const d = result.data || {};
		if (d.error) showToast(d.error);
		else if (result.type === 'success') {
			if (onSuccess) onSuccess(d);
			fire();
		}
	}

	function rewardLabel(r: any): string {
		if (r.rewardType === 'cash') return `£${Number(r.value).toFixed(2)}`;
		if (r.rewardType === 'points') return `${r.value} pts`;
		return r.label;
	}

	function manualConfigs() {
		return parentBonusConfigs.filter((b: any) => {
			if (!(b.status === 'active' && b.type === 'manual')) return false;
			if (b.occurrence === 'once') {
				const hasReward = parentRewards.some(
					(r: any) => r.bonusConfigId === b.id && (!b.memberId || r.memberId === b.memberId)
				);
				if (hasReward) return false;
			}
			return true;
		});
	}

	// ─── Child View (kanban) ───
	let memberId = $state(data.memberId || '');
	let deviceToken = $state(data.token || '');
	let famId = $state(data.famId || '');
	let memberName = $state(data.memberName || '');
	let memberColor = $state(data.memberColor || '#6366f1');
	let editingName = $state(false);
	let nameInput = $state('');
	let showColorPicker = $state(false);

	let templates = $derived(
		famStore.initialized
			? (famStore.templates as ChoreTemplate[])
			: (data.templates as ChoreTemplate[])
	);
	let assigned = $derived(
		famStore.initialized
			? (famStore.assigned as AssignedChore[])
			: (data.assigned as AssignedChore[])
	);
	let completions = $derived(
		famStore.initialized
			? (famStore.completions as Completion[])
			: (data.completions as Completion[])
	);
	let rewards = $derived(
		famStore.initialized ? (famStore.rewards as Reward[]) : (data.rewards as Reward[])
	);
	let bonusConfigs = $derived(
		famStore.initialized
			? (famStore.bonusConfigs as BonusConfig[])
			: (data.bonusConfigs as BonusConfig[])
	);

	let loading = $state(true);
	let error = $state('');

	let claimError = $state('');
	let todayChild = $derived(todayInTz(famTz));
	let togglingIds = $state<string>('');

	// ─── Payday countdown (child view) ───
	let nowMs = $state(Date.now());
	let eowFired = $state(false);
	$effect(() => {
		const id = setInterval(() => (nowMs = Date.now()), 1000);
		return () => clearInterval(id);
	});
	const paydayDay = $derived(data.payday != null ? Number(data.payday) : 1);
	const paydayTime = $derived(data.paydayTime || '18:00');
	const isPaydayToday = $derived(weekdayInTz(new Date(), famTz) === paydayDay);
	const paydayTarget = $derived.by(() =>
		new Date(wallClockToUtc(todayInTz(famTz), paydayTime, famTz)).getTime()
	);
	const secondsLeft = $derived(Math.max(0, Math.floor((paydayTarget - nowMs) / 1000)));
	const countdownHours = $derived(Math.floor(secondsLeft / 3600));
	const countdownMinutes = $derived(Math.floor((secondsLeft % 3600) / 60));
	const countdownSecs = $derived(secondsLeft % 60);
	const showCountdown = $derived(isPaydayToday && secondsLeft > 0);
	$effect(() => {
		if (role !== 'child' || !isPaydayToday) return;
		if (secondsLeft > 0 || eowFired) return;
		if (!deviceToken || !famId) return;
		eowFired = true;
		memberApi.payday(deviceToken, famId).catch(() => {});
	});

	function paydayWeekStart(): string {
		return tzWeekStart(paydayDay, famTz);
	}

	function addDays(dateStr: string, days: number): string {
		return addDaysStr(dateStr, days);
	}

	function monthStartStr(): string {
		const d = new Date();
		return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`;
	}

	const todayIso = todayInTz(famTz);
	const currentWeek = paydayWeekStart();
	let weekStart = $state(currentWeek);
	let weekEnd = $derived(addDays(weekStart, 6));

	const isCurrentWeek = $derived(weekStart === currentWeek);
	const daysLeft = $derived(
		Math.max(
			0,
			Math.round(
				(new Date(addDays(weekStart, 7) + 'T00:00:00').getTime() - new Date(todayIso + 'T00:00:00').getTime()) /
					86400000
			)
		)
	);

	let memberChores = $derived.by(() => {
		if (role === 'parent' || !memberId) return [];
		const activeSeasonIds = new Set(famStore.seasons.filter((s) => s.active).map((s) => s.id));
		return assigned.filter((a) => {
			if (a.memberId !== memberId) return false;
			if (a.isTodo) return false;
			if (!a.seasonIds || a.seasonIds.length === 0) return true;
			if (activeSeasonIds.size === 0) return true;
			return a.seasonIds.some((sid) => activeSeasonIds.has(sid));
		});
	});

	// Todos — one-off items with deadlines (sorted: soonest first)
	let memberTodos = $derived.by(() =>
		assigned
			.filter((a) => a.memberId === memberId && a.isTodo)
			.sort((a, b) => {
				const da = a.completeBy || '9999-99-99';
				const db = b.completeBy || '9999-99-99';
				if (da !== db) return da.localeCompare(db);
				return (a.startDate || '').localeCompare(b.startDate || '');
			})
	);

	let dailyPending = $derived(
		memberChores.filter((a) => a.frequency === 'daily' && !isCompleted(a.id, todayChild))
	);
	let weeklyPending = $derived(
		memberChores.filter((a) => a.frequency === 'weekly' && !isCompleted(a.id, todayChild))
	);

	// Traffic light: days until todo deadline (only non-emoji todos)
	function todoUrgency(todo: AssignedChore): 'blue' | 'green' | 'orange' | 'red' | 'black' {
		if (!todo.completeBy) return 'blue';
		const days = Math.round(
			(new Date(todo.completeBy + 'T00:00:00').getTime() -
				new Date(todayChild + 'T00:00:00').getTime()) /
				86400000
		);
		if (days <= 0) return 'black';
		if (days === 1) return 'red';
		if (days === 2) return 'orange';
		if (days === 3) return 'green';
		return 'blue';
	}
	let completedToday = $derived(
		completions.filter((c) => c.memberId === memberId && c.date?.slice(0, 10) === todayChild)
	);

	let weeklyTotal = $derived.by(() => {
		const dailyChores = memberChores.filter((a) => a.frequency === 'daily');
		const weeklyChores = memberChores.filter((a) => a.frequency === 'weekly');
		const activeTodos = memberTodos.filter((t) => t.type !== 'emoji').length;
		return dailyChores.length * 7 + weeklyChores.length + activeTodos;
	});

	let weekCompletions = $derived(
		completions.filter(
			(c) =>
				c.memberId === memberId &&
				(c.date?.slice(0, 10) || c.date) >= weekStart &&
				(c.date?.slice(0, 10) || c.date) <= weekEnd
		)
	);
	let weeklyDone = $derived(weekCompletions.length);
	let weeklyPct = $derived(weeklyTotal > 0 ? Math.round((weeklyDone / weeklyTotal) * 100) : 0);

	let weekRewards = $derived(
		rewards.filter((r) => r.memberId === memberId && (r.date?.slice(0, 10) || r.date) >= weekStart)
	);

	let allTimeCash = $derived.by(() =>
		rewards
			.filter((r) => r.memberId === memberId && r.rewardType === 'cash' && r.status === 'claimed')
			.reduce((sum, r) => sum + Number(r.value), 0)
	);

	let allTimePoints = $derived.by(() =>
		rewards
			.filter((r) => r.memberId === memberId && r.rewardType === 'points' && r.status === 'claimed')
			.reduce((sum, r) => sum + Number(r.value), 0)
	);

	// Week-to-date totals from chore completions (not rewards — rewards are created at EOW)
	let weekChoreCash = $derived.by(() =>
		weekCompletions.reduce((sum, c) => {
			const chore = assigned.find((a) => a.id === c.assignedChoreId);
			return sum + (chore?.type === 'money' ? Number(chore.value) : 0);
		}, 0)
	);
	let weekChorePoints = $derived.by(() =>
		weekCompletions.reduce((sum, c) => {
			const chore = assigned.find((a) => a.id === c.assignedChoreId);
			return sum + (chore?.type === 'points' ? Number(chore.value) : 0);
		}, 0)
	);

	let pendingCash = $derived.by(() =>
		weekRewards
			.filter((r) => r.rewardType === 'cash' && r.status !== 'claimed')
			.reduce((sum, r) => sum + Number(r.value), 0)
	);
	// Money owed to this member across all time (incl. carry-over from past weeks).
	// Payday-gated rewards are excluded until their settleDate — the wallet shows
	// them separately as a locked "pays out on payday" line.
	let owedCash = $derived.by(() =>
		rewards
			.filter(
				(r) =>
					r.memberId === memberId &&
					r.rewardType === 'cash' &&
					r.status !== 'claimed' &&
					!paydayLocked(r)
			)
			.reduce((sum, r) => sum + Number(r.value), 0)
	);
	let weekPointsEarned = $derived.by(() =>
		weekRewards
			.filter((r) => r.rewardType === 'points')
			.reduce((sum, r) => sum + Number(r.value), 0)
	);
	const pendingRewards = $derived(
		rewards.filter(
			(r) =>
				r.memberId === memberId &&
				r.rewardType !== 'points' &&
				(r.status === 'unclaimed' || r.status === 'requested')
		)
	);
	// Payday-gated bonus rewards unlock on their settleDate (stamped server-side).
	function paydayLocked(r: any) {
		return r.claimable === 'payday' && r.settleDate && todayChild < r.settleDate;
	}
	let weekBonusTallies = $derived.by(() => {
		const map = new Map<string, number>();
		for (const r of weekRewards) {
			if (r.memberId !== memberId) continue;
			const name = bonusConfigs.find((b: BonusConfig) => b.id === r.bonusConfigId)?.name || r.label;
			map.set(name, (map.get(name) || 0) + 1);
		}
		return [...map.entries()].map(([name, count]) => ({ name, count }));
	});

	// ─── Threshold Goals (bonus configs the member is chasing) ───
	let thresholdGoals = $derived.by(() => {
		return bonusConfigs
			.filter((cfg) => {
				if (cfg.status !== 'active') return false;
				if (cfg.type === 'manual') return false;
				if (cfg.target !== 'individual') return false;
				return !cfg.memberId || cfg.memberId === memberId;
			})
			.map((cfg) => {
				const per = cfg.period || 'weekly';
				const pStart = periodStart(per, paydayDay, famTz);
				const pEnd = periodEnd(per, pStart);

				const periodCompletions = completions.filter(
					(c) =>
						c.memberId === memberId &&
						(c.date?.slice(0, 10) || c.date) >= pStart &&
						(c.date?.slice(0, 10) || c.date) <= pEnd
				);

				let current = 0;
				if (cfg.type === 'threshold') {
					current = periodCompletions.reduce((sum, c) => {
						const chore = assigned.find((a) => a.id === c.assignedChoreId);
						return sum + (chore?.type === 'points' ? Number(chore.value) : 0);
					}, 0);
				} else if (cfg.type === 'count') {
					current = periodCompletions.length;
				}

				const existingReward = rewards.find(
					(r) => r.bonusConfigId === cfg.id && r.memberId === memberId
				);
				const criteria = Number(cfg.criteriaValue) || 0;
				const achieved = existingReward ? true : criteria > 0 && current >= criteria;

				return {
					config: cfg,
					current,
					criteriaValue: criteria,
					achieved,
					rewardStatus: existingReward?.status || null,
					periodStart: pStart,
					periodEnd: pEnd
				};
			});
	});

	function shortDate(dateStr: string): string {
		const d = new Date(dateStr + 'T00:00:00');
		const day = String(d.getDate()).padStart(2, '0');
		const mon = String(d.getMonth() + 1).padStart(2, '0');
		return `${day}/${mon}/${String(d.getFullYear()).slice(2)}`;
	}
	const MONTHS = [
		'January',
		'February',
		'March',
		'April',
		'May',
		'June',
		'July',
		'August',
		'September',
		'October',
		'November',
		'December'
	];
	function headDate(): Date {
		return new Date((isCurrentWeek ? todayIso : weekEnd) + 'T00:00:00');
	}
	function headDay(): number {
		return headDate().getDate();
	}
	function headMonth(): string {
		const d = headDate();
		return `${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
	}
	function navigate(amount: number) {
		weekStart = addDays(weekStart, amount * 7);
	}
	function jumpToNow() {
		weekStart = currentWeek;
	}

	onMount(async () => {
		if (role === 'parent') return;

		if (!deviceToken) {
			deviceToken = localStorage.getItem('deviceToken') || '';
		} else {
			localStorage.setItem('deviceToken', deviceToken);
		}

		if (!deviceToken) {
			error = 'No device token found. Use the link from your invite.';
			loading = false;
			return;
		}

		if (data.verified && data.famId && data.memberId) {
			memberId = data.memberId;
			famId = data.famId;
			loading = false;
		} else {
			error = 'Invalid device token. Use the link from your invite.';
			loading = false;
			return;
		}

		await loadAndDismiss();
	});

	async function loadAndDismiss() {
		if (!deviceToken || !famId) return;
		try {
			const res = await fetch('/api/members/notifications', {
				headers: {
					'x-device-token': deviceToken,
					'x-device-famid': famId
				}
			});
			if (!res.ok) return;
			const all: Notification[] = await res.json();
			for (const n of all.filter((n) => !n.read)) {
				await fetch(`/api/members/notifications/${n.id}/dismiss`, {
					method: 'POST',
					headers: {
						'x-device-token': deviceToken,
						'x-device-famid': famId
					}
				});
			}
		} catch {}
	}

	function isCompleted(assignedChoreId: string, date: string): boolean {
		return completions.some(
			(c) => c.assignedChoreId === assignedChoreId && c.date?.slice(0, 10) === date
		);
	}

	function findCompletion(assignedChoreId: string) {
		const match = (c: Completion) =>
			c.assignedChoreId === assignedChoreId && (c.date?.slice(0, 10) || c.date) === todayChild;
		const optimistic = completions.find((c) => match(c) && c.id.startsWith('optimistic-'));
		return optimistic || completions.find(match);
	}

	async function toggle(chore: AssignedChore) {
		if (togglingIds) return;
		togglingIds = chore.id;

		const wasCompleted = isCompleted(chore.id, todayChild);
		if (wasCompleted) {
			const existing = findCompletion(chore.id);
			if (existing) {
				famStore.applyRecord('completions', existing, 'delete');
			}
		} else {
			famStore.applyRecord(
				'completions',
				{
					id: 'optimistic-' + chore.id,
					famId,
					memberId,
					assignedChoreId: chore.id,
					date: todayChild,
					completedAt: new Date().toISOString()
				} as Completion,
				'create'
			);
		}

		try {
			await memberApi.toggleCompletion(deviceToken, famId, chore.id, todayChild);
			const optimistic = completions.find((c) => c.id === 'optimistic-' + chore.id);
			if (optimistic) {
				famStore.applyRecord('completions', optimistic, 'delete');
			}
		} catch (e) {
			if (wasCompleted) {
				famStore.applyRecord(
					'completions',
					{
						id: 'revert-' + chore.id + '-' + Date.now(),
						famId,
						memberId,
						assignedChoreId: chore.id,
						date: todayChild,
						completedAt: new Date().toISOString()
					} as Completion,
					'create'
				);
			} else {
				const optimistic = completions.find((c) => c.id === 'optimistic-' + chore.id);
				if (optimistic) {
					famStore.applyRecord('completions', optimistic, 'delete');
				}
			}
			console.error('Toggle failed:', e);
		} finally {
			togglingIds = '';
		}
	}

	function templateFor(chore: AssignedChore): ChoreTemplate | undefined {
		return templates.find((t) => t.id === chore.templateId);
	}

	function choreName(chore: AssignedChore): string {
		return chore.customName || templateFor(chore)?.name || 'Chore';
	}
</script>

{#if role === 'parent'}
	<!-- Parent View: Admin Overview -->
	<ViewHeader
		title={famStore.fam?.name || 'Dashboard'}
		subtitle={summary?.weekStart ? `Week of ${formatDDMMYY(summary.weekStart)}` : ''}
		hero
	/>

	{#if toast}
		<div class="toast">{toast}</div>
	{/if}

	<!-- WEEK-TO-DATE TILES -->
	<div class="tiles">
		<div class="tile tile-members">
			<span class="tile-val">{adminTiles.members}</span>
			<span class="tile-lbl">members</span>
		</div>
		<div class="tile tile-pts">
			<span class="tile-val">{adminTiles.pts}</span>
			<span class="tile-lbl">points this week</span>
		</div>
		<div class="tile tile-cash">
			<span class="tile-val">£{adminTiles.money.toFixed(2)}</span>
			<span class="tile-lbl">cash this week</span>
		</div>
		<div class="tile tile-chores">
			<span class="tile-val">{adminTiles.chores}</span>
			<span class="tile-lbl">chores done</span>
		</div>
	</div>

	<div class="kanban-scroll">
		<div class="kanban-inner">
		<CardGrid>
		<Card cols={1} title="Members">
			{#each parentMembers as m}
				{@const total = totalChoresFor(m.id)}
				{@const todays = todayCompletionsFor(m.id)}
				{@const done = todays.length}
				{@const pct = total > 0 ? Math.round((done / total) * 100) : 0}
				{@const s = memberInSummary(m.id)}
				<div class="member-card">
					<div class="card-header">
						<span class="dot" style="background:{m.color}"></span>
						<span class="member-name">{m.name}</span>
						<a href="/{famSlug}/{m.name}" class="link">Kanban</a>
					</div>
					<div class="stats">
						<span>Points: {s?.pointsEarned ?? 0}</span>
						<span>Money: £{(s?.moneyEarned ?? 0).toFixed(2)}</span>
					</div>
					<div class="progress-row">
						<span class="label">Today:</span>
						<div class="bar-wrap">
							<div class="bar-fill" style="width:{pct}%"></div>
						</div>
						<span class="count">{done}/{total}</span>
					</div>
					{#if done > 0}
						<ul class="done-list">
							{#each todays as c}
								<li>
									✅ {choreNameFor(c.assignedChoreId)}
									<form
										method="POST"
										action="?/revoke"
										use:enhance={() => {
											return async (args) => handleResult(args);
										}}
										class="revoke-form"
									>
										<input type="hidden" name="id" value={c.id} />
										<button type="submit" class="revoke-btn" title="Revoke">↩</button>
									</form>
								</li>
							{/each}
						</ul>
					{/if}
				</div>
			{/each}
		</Card>

		<Card cols={1} title="Triggers">
			{#if manualConfigs().length === 0}
				<p class="empty">No manual bonus configs.</p>
			{:else}
				{#each manualConfigs() as bc}
					{@const val =
						bc.rewardType === 'cash'
							? `£${Number(bc.rewardValue).toFixed(2)}`
							: bc.rewardType === 'points'
								? `${bc.rewardValue} pts`
								: bc.rewardValue}
					{@const targeted = bc.memberId
						? parentMembers.find((m: any) => m.id === bc.memberId)
						: null}
					<div class="trigger-card">
						<div class="trigger-head">
							<span class="trigger-name">{bc.name}</span>
							<span class="trigger-value">{val}</span>
						</div>
						{#if targeted}
							<form
								method="POST"
								action="?/trigger"
								use:enhance={() => {
									return async (args: any) => handleResult(args);
								}}
							>
								<input type="hidden" name="configId" value={bc.id} />
								<input type="hidden" name="memberId" value={targeted.id} />
								<div class="trigger-row">
									<span class="trigger-target" style="color:{targeted.color}"
										>■ {targeted.name}</span
									>
									<Button type="submit" size="sm" variant="primary">Award</Button>
								</div>
							</form>
						{:else}
							<form
								method="POST"
								action="?/trigger"
								use:enhance={() => {
									return async (args: any) => handleResult(args);
								}}
							>
								<input type="hidden" name="configId" value={bc.id} />
								<div class="trigger-row">
									<select name="memberId" class="trigger-select">
										<option value="">Select member</option>
										{#each parentMembers as m}
											<option value={m.id}>{m.name}</option>
										{/each}
									</select>
									<Button type="submit" size="sm" variant="primary">Award</Button>
								</div>
							</form>
						{/if}
					</div>
				{/each}
			{/if}
		</Card>

		<Card cols={1} title="Claims" accent="#f59e0b">
			{#if claimableRewards().length === 0}
				<p class="empty">No outstanding claims</p>
			{:else}
				{#each parentMembers as m}
					{@const requested = memberRequested(m.id)}
					{@const outstanding = memberOutstanding(m.id)}
					{@const claimable = memberClaimable(m.id)}
					{@const cashClaimable = claimable.filter((r: any) => r.rewardType === 'cash')}
					{@const cashRequested = requested.filter((r: any) => r.rewardType === 'cash')}
					{@const requestedTotal = cashRequested.reduce(
						(sum: number, r: any) => sum + Number(r.value),
						0
					)}
					{@const total = cashClaimable.reduce((sum: number, r: any) => sum + Number(r.value), 0)}
					{@const hasClaims = requested.length > 0 || outstanding.length > 0}
					{#if hasClaims}
						<div class="member-claims">
							<h4><span class="dot" style="background:{m.color}"></span> {m.name}</h4>
							{#if total > 0}
								<p class="member-total">£{total.toFixed(2)} owed</p>
							{/if}

							<!-- Issue All: only when payday has run (rewards are requested) -->
							{#if cashRequested.length > 0}
								<form
									class="issue-all-form"
									method="POST"
									action="?/issueAll"
									use:enhance={() => {
										return async (args: any) => handleResult(args);
									}}
								>
									<input type="hidden" name="memberId" value={m.id} />
									<input type="hidden" name="message" value={selectedMessage[m.id] || ''} />
									<Button type="submit" size="sm" variant="secondary"
										>Issue All ({cashRequested.length}) · £{requestedTotal.toFixed(2)}</Button
									>
								</form>
							{/if}

							{#if requested.length > 0}
								<p class="section-label">Requested</p>
								{#each requested as r}
									<form
										method="POST"
										action="?/claim"
										use:enhance={() => {
											return async (args: any) => handleResult(args);
										}}
									>
										<input type="hidden" name="id" value={r.id} />
										<input type="hidden" name="message" value={selectedMessage[m.id] || ''} />
										<div class="payment-row">
											<span>{r.label}</span>
											<div class="payment-right">
												<span class="value">{rewardLabel(r)}</span>
												<Button type="submit" size="sm" variant="primary">Issue</Button>
											</div>
										</div>
									</form>
								{/each}
							{/if}

							{#if outstanding.length > 0}
								<p class="section-label">Outstanding</p>
								{#each outstanding as r}
									<div class="payment-row outstanding">
										<span>{r.label}</span>
										<span class="value">
											{rewardLabel(r)}
											{#if paydayLocked(r)}
												<span class="owe-locked">🔒 {formatShortDate(r.settleDate)}</span>
											{/if}
										</span>
									</div>
								{/each}
							{/if}

							<div class="response-area">
								<p class="respond-label">Response:</p>
								<div class="tags">
									{#each responseTags as tag}
										<button
											type="button"
											class="tag"
											class:selected={selectedMessage[m.id] === tag}
											onclick={() =>
												(selectedMessage[m.id] = selectedMessage[m.id] === tag ? '' : tag)}
											>{tag}</button
										>
									{/each}
								</div>
								<input
									type="text"
									class="custom-msg"
									placeholder="Or type your own..."
									bind:value={customMessage[m.id]}
									oninput={() => {
										if (customMessage[m.id]) selectedMessage[m.id] = customMessage[m.id];
									}}
								/>
							</div>
						</div>
					{/if}
				{/each}
			{/if}
		</Card>
	</CardGrid>
		</div>
	</div>
	<CardGrid>
		<Card cols={3} title="Debug: Preview payday" accent="#f59e0b">
			<p class="eow-desc">
				Read-only preview of what the payday rollover will settle for this week. Nothing here is
				written.
			</p>

			<form
				method="POST"
				action="?/previewEow"
				use:enhance={() => {
					return async ({ result }) => {
						const d = (result as any).data || {};
						if (d.error) toast = d.error;
						else {
							eowPreview = d.preview;
							simulateEow = !!d.simulateEow;
						}
					};
				}}
			>
				<Button type="submit" size="sm" variant="secondary">Preview payday</Button>
			</form>

			{#if simulateEow}
				<p class="eow-mode-on">
					👀 Preview mode is ON — child dashboards show a "payday preview" notice.
				</p>
				<form
					method="POST"
					action="?/setEow"
					use:enhance={() => {
						return async ({ result }) => {
							const d = (result as any).data || {};
							if (d.error) toast = d.error;
							else simulateEow = !!d.simulateEow;
						};
					}}
				>
					<input type="hidden" name="on" value="false" />
					<Button type="submit" size="sm" variant="ghost">Turn off preview</Button>
				</form>
			{/if}

			{#if eowPreview}
				<div class="eow-out">
					<p class="eow-range">
						Week {formatDDMMYY(eowPreview.weekStart)} → {formatDDMMYY(eowPreview.weekEnd)}
					</p>
					<p class="eow-reset">
						On payday these {eowPreview.completionsThisWeek} completions reset; week starts anew at
						{formatDDMMYY(eowPreview.nextWeekStart)}.
					</p>

					<div class="eow-summaries">
						{#each eowPreview.summaries as s}
							<div class="eow-row">
								<span class="eow-name">{s.memberName}</span>
								<span class="eow-pts">{s.pointsEarned} pts</span>
								<span class="eow-money">£{Number(s.moneyEarned || 0).toFixed(2)}</span>
								<span class="eow-chores">{s.choresCompleted} chores</span>
							</div>
						{/each}
					</div>

					{#if eowPreview.predictedRewards.length}
						<p class="eow-sec">Rewards that would be auto-created</p>
						<div class="eow-rewards">
							{#each eowPreview.predictedRewards as r}
								<span class="eow-reward"
									>{r.memberName} · {r.config} · {r.type === 'cash'
										? '£' + Number(r.value).toFixed(2)
										: r.value + ' pts'}</span
								>
							{/each}
						</div>
					{:else}
						<p class="eow-none">No new rewards would be auto-created this week.</p>
					{/if}
				</div>
			{/if}
		</Card>
	</CardGrid>
{:else}
	<!-- Child View -->
	{#if loading}
		<p class="empty">Loading...</p>
	{:else if error}
		<p class="error">{error}</p>
	{:else}
		{#if simulateEow}
			<div class="preview-notice">
				👀 <b>Payday preview</b> — your parent is checking this week's payday. Nothing is paid
				out yet.
			</div>
		{/if}
		{#if owedCash > 0}
			<div class="payday-banner">
				🎉 You've earned <b>£{owedCash.toFixed(2)}</b> — go get it from your parent!
			</div>
		{/if}
		{#if showCountdown}
			<div class="payday-countdown">
				<div class="pd-icon">💰</div>
				<div class="pd-body">
					<p class="pd-title">Payday is today!</p>
					<p class="pd-num">
						{#if countdownHours > 0}
							{countdownHours}h {countdownMinutes}m left
						{:else}
							{countdownMinutes}m {countdownSecs}s left
						{/if}
					</p>
					<p class="pd-sub">Your earnings get settled at {paydayTime}</p>
				</div>
			</div>
		{/if}
		<!-- HERO -->
		<header class="hero">
			<div class="hero-top">
				<div class="hero-id">
					<span
						class="color-dot hero-dot"
						style="background:{memberColor}"
						role="button"
						tabindex="0"
						onclick={(e) => {
							e.stopPropagation();
							showColorPicker = !showColorPicker;
						}}
						onkeydown={(e) => e.key === 'Enter' && (showColorPicker = !showColorPicker)}
					></span>
					{#if editingName}
						<input
							type="text"
							bind:value={nameInput}
							class="name-edit name-edit-dark"
							onkeydown={async (e: KeyboardEvent) => {
								if (e.key === 'Enter') {
									editingName = false;
									if (nameInput && nameInput !== memberName) {
										const old = memberName;
										memberName = nameInput;
										try {
											const res = await fetch('/api/members/me', {
												method: 'PATCH',
												headers: {
													'Content-Type': 'application/json',
													'x-device-token': deviceToken,
													'x-device-famid': famId
												},
												body: JSON.stringify({ name: nameInput })
											});
											if (!res.ok) memberName = old;
										} catch {
											memberName = old;
										}
									}
								} else if (e.key === 'Escape') {
									editingName = false;
									nameInput = memberName;
								}
							}}
							onblur={() => {
								editingName = false;
								nameInput = memberName;
							}}
							autofocus
						/>
					{:else}
						<span
							class="hero-name"
							role="button"
							tabindex="0"
							onclick={() => {
								nameInput = memberName;
								editingName = true;
							}}
							onkeydown={(e) =>
								e.key === 'Enter' && ((nameInput = memberName), (editingName = true))}
						>
							{memberName || username}
						</span>
					{/if}
				</div>
				<div class="hero-date">
					<span class="hero-day">{headDay()}</span>
					<span class="hero-month">{headMonth()}</span>
				</div>
			</div>

			{#if showColorPicker}
				<!-- svelte-ignore a11y_no_static_element_interactions -->
				<div class="color-picker-overlay" onclick={() => (showColorPicker = false)}>
					<div class="color-picker-popup" onclick={(e) => e.stopPropagation()}>
						<p>Pick a color:</p>
						<div class="color-swatches">
							{#each ['#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6', '#3b82f6', '#6366f1', '#a855f7', '#ec4899', '#78716c'] as color}
								<button
									class="swatch"
									style="background:{color}"
									aria-label={color}
									onclick={async () => {
										const old = memberColor;
										memberColor = color;
										showColorPicker = false;
										try {
											const res = await fetch('/api/members/me', {
												method: 'PATCH',
												headers: {
													'Content-Type': 'application/json',
													'x-device-token': deviceToken,
													'x-device-famid': famId
												},
												body: JSON.stringify({ color })
											});
											if (!res.ok) memberColor = old;
										} catch {
											memberColor = old;
										}
									}}
								></button>
							{/each}
						</div>
					</div>
				</div>
			{/if}

			<div class="hero-progress">
				<span class="hero-pct">{weeklyTotal > 0 ? weeklyPct : 0}%</span>
				<div class="bar-wrap hero-bar">
					<div class="bar-fill hero-fill" style="width:{weeklyPct}%"></div>
				</div>
				<span class="hero-count">
					{weeklyTotal > 0 ? `${weeklyDone}/${weeklyTotal} done` : 'no chores yet'}
				</span>
			</div>

			<div class="hero-meta">
				{#if weekBonusTallies.length > 0}
					<div class="hero-pills">
						{#each weekBonusTallies as t}
							<span class="hero-pill">🎉 {t.name} ×{t.count}</span>
						{/each}
					</div>
				{:else}
					<span class="hero-null">no bonuses earned yet</span>
				{/if}
				<div class="hero-days"><b>{daysLeft}</b> days until payday</div>
			</div>

			<nav class="hero-nav">
				<button class="hero-nav-btn" onclick={() => navigate(-1)} aria-label="Previous week"
					>◀</button
				>
				<button class="hero-nav-btn cur" onclick={jumpToNow}
					>{isCurrentWeek ? 'This week' : `${shortDate(weekStart)}`}</button
				>
				<button class="hero-nav-btn" onclick={() => navigate(1)} aria-label="Next week">▶</button>
			</nav>
		</header>

		<!-- WEEK-TO-DATE TILES -->
		<div class="tiles">
			<div class="tile tile-cash">
				<span class="tile-val">£{weekChoreCash.toFixed(2)}</span>
				<span class="tile-lbl">cash this week</span>
			</div>
			<div class="tile tile-pts">
				<span class="tile-val">{weekChorePoints}</span>
				<span class="tile-lbl">points this week</span>
			</div>
		</div>

		<!-- GOALS (threshold bonus configs) -->
		{#if thresholdGoals.length > 0}
			<div class="goals">
				{#each thresholdGoals as goal}
					{@const pct =
						goal.criteriaValue > 0
							? Math.min(100, Math.round((goal.current / goal.criteriaValue) * 100))
							: 0}
					<div class="goal-card" class:goal-achieved={goal.achieved}>
						<div class="goal-head">
							<span class="goal-name">{goal.config.name}</span>
							<span class="goal-reward">
								{#if goal.config.rewardType === 'cash'}
									£{Number(goal.config.rewardValue).toFixed(2)}
								{:else if goal.config.rewardType === 'prize'}
									🎁
								{:else}
									{goal.config.rewardValue} pts
								{/if}
							</span>
						</div>
						<div class="goal-bar-wrap">
							<div class="goal-bar-fill" style="width:{pct}%"></div>
						</div>
						<div class="goal-foot">
							<span class="goal-progress"
								>{Math.min(goal.current, goal.criteriaValue)} / {goal.criteriaValue}</span
							>
							{#if goal.achieved}
								<span class="goal-badge">✅ earned</span>
							{:else}
								<span class="goal-pct">{pct}%</span>
							{/if}
						</div>
					</div>
				{/each}
			</div>
		{/if}

		<!-- KANBAN -->
		<div class="kanban">
			<div class="column col-daily">
				<h2>🎯 Daily ({dailyPending.length})</h2>
				{#if dailyPending.length === 0}
					<p class="empty">All done!</p>
				{:else}
					{#each dailyPending as chore}
						<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
							<span class="checkbox">⬜</span>
							<span class="chore-name">{choreName(chore)}</span>
							<span class="chore-value">{chore.value} {chore.type}</span>
						</button>
					{/each}
				{/if}
			</div>
			<div class="column col-weekly">
				<h2>
					📅 Weekly ({weeklyPending.length +
						memberTodos.filter((t) => !isCompleted(t.id, todayChild)).length})
				</h2>
				{#if weeklyPending.length === 0 && memberTodos.filter((t) => !isCompleted(t.id, todayChild)).length === 0}
					<p class="empty">All done!</p>
				{:else}
					{#each weeklyPending as chore}
						<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
							<span class="checkbox">⬜</span>
							<span class="chore-name">{choreName(chore)}</span>
							<span class="chore-value">{chore.value} {chore.type}</span>
						</button>
					{/each}
					{#each memberTodos.filter((t) => !isCompleted(t.id, todayChild)) as todo}
						{@const urgency = todoUrgency(todo)}
						{#if todo.type === 'emoji'}
							<div
								class="chore todo-chore todo-display"
								class:todo-blue={urgency === 'blue'}
								class:todo-green={urgency === 'green'}
								class:todo-orange={urgency === 'orange'}
								class:todo-red={urgency === 'red'}
								class:todo-black={urgency === 'black'}
							>
								<span class="checkbox">📋</span>
								<span class="chore-name">{todo.customName || 'Todo'}</span>
								<span class="todo-label">todo</span>
								<span class="chore-value">🎯</span>
							</div>
						{:else}
							<button
								class="chore todo-chore"
								class:todo-blue={urgency === 'blue'}
								class:todo-green={urgency === 'green'}
								class:todo-orange={urgency === 'orange'}
								class:todo-red={urgency === 'red'}
								class:todo-black={urgency === 'black'}
								onclick={() => toggle(todo)}
								disabled={!!togglingIds}
							>
								<span class="checkbox">📋</span>
								<span class="chore-name">{todo.customName || 'Todo'}</span>
								<span class="todo-label">todo</span>
								<span class="chore-value">{todo.value} {todo.type}</span>
							</button>
						{/if}
					{/each}
				{/if}
			</div>
			<div class="column col-done">
				<h2>✅ Done ({completedToday.length})</h2>
				{#if completedToday.length === 0}
					<p class="empty">Nothing yet</p>
				{:else}
					{#each completedToday as c}
						{@const chore = assigned.find((a) => a.id === c.assignedChoreId)}
						{#if chore}
							<button class="chore done" onclick={() => toggle(chore)} disabled={!!togglingIds}>
								<span class="checkbox">✅</span>
								<span class="chore-name">{choreName(chore)}</span>
								<span class="chore-value">{chore.value} {chore.type}</span>
							</button>
						{/if}
					{/each}
				{/if}
			</div>
		</div>

		<!-- WALLET / CLAIMS -->
		<div class="wallet">
			<div class="wallet-top">
				<span class="wallet-lbl">💰 Wallet</span>
				{#if allTimeCash > 0 || allTimePoints > 0}
					<span class="wallet-total">
						<span class="wallet-cash">£{allTimeCash.toFixed(2)}</span>
						<span class="wallet-dot">·</span>
						<span class="wallet-pts">{allTimePoints} pts</span>
					</span>
				{/if}
			</div>
			{#if pendingRewards.length === 0}
				<p class="wallet-empty">No rewards waiting</p>
			{:else}
				<div class="wallet-rewards">
					{#each pendingRewards as r}
						{@const isReq = r.status === 'requested'}
						<div class="wallet-reward">
							<span class="wr-label">{r.label}</span>
							<span class="wr-val">
								{#if r.rewardType === 'cash'}
									£{Number(r.value).toFixed(2)}
								{:else if r.rewardType === 'prize'}
									🎁
								{:else}
									{r.value} pts
								{/if}
							</span>
							{#if r.rewardType === 'points'}
								<span class="wr-pending">✓ credited</span>
							{:else if isReq}
								<span class="wr-pending">⏳ waiting</span>
							{:else if paydayLocked(r)}
								<span class="wr-pending wr-locked"
									>🔒 pays out {formatShortDate(r.settleDate)}</span
								>
							{:else}
								<button
									class="wr-cta"
									onclick={async () => {
										try {
											await memberApi.claimReward(deviceToken, famId, r.id);
										} catch (e) {
											claimError = e instanceof Error ? e.message : 'Claim failed';
										}
									}}>Request</button
								>
							{/if}
						</div>
					{/each}
				</div>
			{/if}
		</div>
	{/if}
{/if}

<style>
	.toast {
		position: fixed;
		top: 3.5rem;
		right: 1.5rem;
		background: #dc2626;
		color: white;
		padding: 0.6rem 1rem;
		border-radius: 8px;
		font-size: 0.85rem;
		z-index: 999;
		box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
		animation: fadein 0.2s;
	}
	@keyframes fadein {
		from {
			opacity: 0;
			transform: translateY(-10px);
		}
		to {
			opacity: 1;
			transform: translateY(0);
		}
	}

	.empty {
		color: #9ca3af;
		font-size: 0.85rem;
		text-align: center;
		padding: 1.5rem;
	}

	.member-card {
		border: 1px solid #e5e7eb;
		border-left: 4px solid #6366f1;
		border-radius: 10px;
		padding: 0.65rem 0.85rem;
		background: white;
		margin-bottom: 0.6rem;
		transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
	}
	.member-card:hover {
		transform: translateY(-1px);
		box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
		border-color: #c7d2fe;
	}
	.member-card:last-child {
		margin-bottom: 0;
	}
	.card-header {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		margin-bottom: 0.4rem;
	}
	.dot {
		display: inline-block;
		width: 10px;
		height: 10px;
		border-radius: 50%;
		flex-shrink: 0;
	}
	.member-name {
		font-weight: 600;
		flex: 1;
		font-size: 0.9rem;
	}
	.link {
		font-size: 0.8rem;
		color: #6366f1;
		text-decoration: none;
	}
	.stats {
		display: flex;
		gap: 0.75rem;
		font-size: 0.8rem;
		color: #6b7280;
		margin-bottom: 0.4rem;
	}
	.progress-row {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		margin-bottom: 0.3rem;
	}
	.label {
		font-size: 0.75rem;
		color: #6b7280;
		min-width: 2.5rem;
	}
	.bar-wrap {
		flex: 1;
		height: 8px;
		background: #e5e7eb;
		border-radius: 4px;
		overflow: hidden;
	}
	.bar-fill {
		height: 100%;
		background: linear-gradient(90deg, #6366f1, #8b5cf6);
		border-radius: 4px;
		transition: width 0.3s;
	}
	.count {
		font-size: 0.75rem;
		color: #6b7280;
		min-width: 3rem;
		text-align: right;
	}
	.done-list {
		list-style: none;
		padding: 0;
		margin: 0;
	}
	.done-list li {
		font-size: 0.75rem;
		padding: 0.05rem 0;
		color: #059669;
		display: flex;
		align-items: center;
		gap: 0.25rem;
	}
	.revoke-form {
		display: inline;
	}
	.revoke-btn {
		background: none;
		border: 1px solid transparent;
		border-radius: 3px;
		cursor: pointer;
		padding: 0 2px;
		font-size: 0.75rem;
		line-height: 1.2;
		color: #9ca3af;
	}
	.revoke-btn:hover {
		color: #dc2626;
		border-color: #fca5a5;
		background: #fef2f2;
	}

	.trigger-card {
		border: 1px solid #e5e7eb;
		border-left: 3px solid #22c55e;
		border-radius: 10px;
		padding: 0.65rem 0.85rem;
		background: #f0fdf4;
		margin-bottom: 0.6rem;
		transition: transform 0.15s, box-shadow 0.15s;
	}
	.trigger-card:hover {
		transform: translateY(-1px);
		box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
	}
	.trigger-card:last-child {
		margin-bottom: 0;
	}
	.trigger-head {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 0.4rem;
	}
	.trigger-name {
		font-weight: 600;
		font-size: 0.9rem;
	}
	.trigger-value {
		font-size: 0.85rem;
		color: #059669;
		font-weight: 600;
	}
	.trigger-row {
		display: flex;
		gap: 0.4rem;
	}
	.trigger-select {
		flex: 1;
		padding: 0.35rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.8rem;
	}

	.member-claims {
		margin-bottom: 0.75rem;
	}
	.member-claims:last-child {
		margin-bottom: 0;
	}
	.member-claims h4 {
		display: flex;
		align-items: center;
		gap: 0.3rem;
		margin: 0 0 0.2rem;
		font-size: 0.85rem;
	}
	.payment-row {
		display: flex;
		justify-content: space-between;
		font-size: 0.8rem;
		padding: 0.1rem 0;
	}
	.payment-row .value {
		font-weight: 600;
		color: #059669;
	}
	.payment-right {
		display: flex;
		align-items: center;
		gap: 0.4rem;
	}
	.respond-label {
		font-size: 0.8rem;
		color: #6b7280;
		margin: 0.4rem 0 0.3rem;
	}
	.tags {
		display: flex;
		gap: 0.3rem;
		flex-wrap: wrap;
	}
	.tag {
		font-size: 0.75rem;
		padding: 0.2rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 999px;
		background: white;
		cursor: pointer;
	}
	.tag:hover {
		background: #f3f4f6;
	}
	.tag.selected {
		background: #6366f1;
		color: white;
		border-color: #6366f1;
	}
	.custom-msg {
		margin-top: 0.3rem;
		padding: 0.3rem 0.5rem;
		border: 1px solid #d1d5db;
		border-radius: 4px;
		font-size: 0.8rem;
		width: 100%;
		box-sizing: border-box;
	}

	.error {
		color: #dc2626;
		padding: 1rem;
		text-align: center;
	}

	.section-label {
		font-size: 0.75rem;
		font-weight: 600;
		color: #6b7280;
		margin: 0.5rem 0 0.25rem;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}
	.member-total {
		font-size: 0.85rem;
		font-weight: 600;
		color: #2563eb;
		margin: 0 0 0.25rem;
	}
	.payment-row.outstanding {
		opacity: 0.6;
	}

	.color-dot {
		display: inline-block;
		width: 18px;
		height: 18px;
		border-radius: 50%;
		border: 2px solid #e5e7eb;
		cursor: pointer;
		flex-shrink: 0;
	}
	.name-edit {
		font-size: 1.2rem;
		padding: 0.1rem 0.3rem;
		border: 1px solid #6366f1;
		border-radius: 4px;
		outline: none;
		font-weight: 700;
	}
	.color-picker-overlay {
		position: fixed;
		inset: 0;
		z-index: 100;
		display: flex;
		align-items: center;
		justify-content: center;
		background: rgba(0, 0, 0, 0.3);
	}
	.color-picker-popup {
		background: white;
		padding: 1.25rem;
		border-radius: 12px;
		box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
	}
	.color-picker-popup p {
		margin: 0 0 0.75rem;
		font-weight: 600;
		font-size: 0.9rem;
	}
	.color-swatches {
		display: flex;
		gap: 0.5rem;
		flex-wrap: wrap;
	}
	.swatch {
		width: 36px;
		height: 36px;
		border-radius: 50%;
		border: 2px solid #e5e7eb;
		cursor: pointer;
	}
	.swatch:hover {
		border-color: #6366f1;
		transform: scale(1.1);
	}

	/* ── Child view ── */

	.hero {
		background: linear-gradient(135deg, #6366f1, #8b5cf6 55%, #a855f7);
		border-radius: 16px;
		padding: 1.25rem 1.5rem;
		margin-bottom: 1rem;
		color: #fff;
		box-shadow: 0 10px 30px rgba(99, 102, 241, 0.35);
	}
	.hero-top {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.75rem;
		margin-bottom: 1rem;
	}
	.hero-id {
		display: flex;
		align-items: center;
		gap: 0.6rem;
		min-width: 0;
	}
	.hero-dot {
		border-color: rgba(255, 255, 255, 0.6);
		box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35);
	}
	.hero-name {
		font-size: 1.5rem;
		font-weight: 800;
		color: #fff;
		cursor: pointer;
		border-bottom: 1px dashed rgba(255, 255, 255, 0.4);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.name-edit-dark {
		background: rgba(255, 255, 255, 0.92);
		color: #1e1b4b;
		border-color: #fff;
		font-size: 1.2rem;
	}
	.hero-date {
		display: flex;
		align-items: baseline;
		gap: 0.45rem;
		flex-shrink: 0;
	}
	.hero-day {
		font-size: 1.7rem;
		font-weight: 800;
		line-height: 1;
		color: #fff;
	}
	.hero-month {
		font-size: 0.85rem;
		font-weight: 600;
		color: rgba(255, 255, 255, 0.85);
	}
	.hero-progress {
		display: flex;
		align-items: center;
		gap: 0.75rem;
		margin-bottom: 0.7rem;
	}
	.hero-pct {
		font-size: 1.5rem;
		font-weight: 800;
		color: #fff;
		flex-shrink: 0;
		min-width: 3.4rem;
	}
	.hero-bar {
		flex: 1;
		height: 12px;
		background: rgba(255, 255, 255, 0.28);
	}
	.hero-fill {
		background: #fff;
	}
	.hero-count {
		font-size: 0.8rem;
		font-weight: 700;
		color: rgba(255, 255, 255, 0.95);
		flex-shrink: 0;
		white-space: nowrap;
	}
	.hero-meta {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.75rem;
		margin-bottom: 0.9rem;
		flex-wrap: wrap;
	}
	.hero-pills {
		display: flex;
		gap: 0.4rem;
		flex-wrap: wrap;
	}
	.hero-pill {
		font-size: 0.75rem;
		font-weight: 700;
		padding: 3px 10px;
		border-radius: 999px;
		background: rgba(255, 255, 255, 0.2);
		color: #fff;
		border: 1px solid rgba(255, 255, 255, 0.5);
	}
	.hero-null {
		font-size: 0.75rem;
		color: rgba(255, 255, 255, 0.7);
	}
	.hero-days {
		font-size: 0.85rem;
		color: rgba(255, 255, 255, 0.9);
	}
	.hero-days b {
		font-size: 1.4rem;
		margin-right: 0.15rem;
	}
	.hero-nav {
		display: flex;
		gap: 0.4rem;
	}
	.hero-nav-btn {
		font-size: 0.85rem;
		font-weight: 700;
		padding: 0.35rem 0.9rem;
		border: none;
		border-radius: 8px;
		background: rgba(255, 255, 255, 0.2);
		color: #fff;
		cursor: pointer;
	}
	.hero-nav-btn:hover {
		background: rgba(255, 255, 255, 0.32);
	}
	.hero-nav-btn.cur {
		flex: 1;
		background: #fff;
		color: #6d28d9;
	}

	/* ── Kanban scroll for parent view cards ── */
	.kanban-scroll {
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		margin-bottom: 1rem;
	}
	.kanban-inner {
		min-width: 450px;
	}

	.tiles {
		display: grid;
		grid-template-columns: 1fr 1fr;
		gap: 0.75rem;
		margin-bottom: 1rem;
	}
	.tile {
		border-radius: 14px;
		padding: 0.9rem 1.1rem;
		color: #fff;
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
		box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
	}
	.tile-cash {
		background: linear-gradient(135deg, #059669, #10b981);
	}
	.tile-pts {
		background: linear-gradient(135deg, #7c3aed, #a855f7);
	}
	.tile-members {
		background: linear-gradient(135deg, #6366f1, #8b5cf6);
	}
	.tile-chores {
		background: linear-gradient(135deg, #0284c7, #38bdf8);
	}
	.tile-val {
		font-size: 1.6rem;
		font-weight: 800;
	}
	.tile-lbl {
		font-size: 0.75rem;
		text-transform: uppercase;
		letter-spacing: 0.06em;
		opacity: 0.9;
	}

	/* ── Goals (threshold bonus progress) ── */
	.goals {
		display: flex;
		flex-direction: column;
		gap: 0.6rem;
		margin-bottom: 1rem;
	}
	.goal-card {
		background: #fff;
		border: 1px solid #e5e7eb;
		border-left: 4px solid #6366f1;
		border-radius: 10px;
		padding: 0.75rem 1rem;
		transition: border-color 0.2s;
	}
	.goal-achieved {
		border-left-color: #10b981;
		background: #f0fdf4;
	}
	.goal-head {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 0.4rem;
	}
	.goal-name {
		font-size: 0.9rem;
		font-weight: 700;
		color: #1f2937;
	}
	.goal-reward {
		font-size: 0.85rem;
		font-weight: 800;
		color: #6366f1;
		white-space: nowrap;
	}
	.goal-achieved .goal-reward {
		color: #059669;
	}
	.goal-bar-wrap {
		height: 8px;
		background: #f3f4f6;
		border-radius: 4px;
		overflow: hidden;
		margin-bottom: 0.35rem;
	}
	.goal-bar-fill {
		height: 100%;
		background: linear-gradient(90deg, #6366f1, #8b5cf6);
		border-radius: 4px;
		transition: width 0.3s ease;
	}
	.goal-achieved .goal-bar-fill {
		background: linear-gradient(90deg, #10b981, #34d399);
	}
	.goal-foot {
		display: flex;
		justify-content: space-between;
		align-items: center;
	}
	.goal-progress {
		font-size: 0.75rem;
		color: #6b7280;
		font-weight: 600;
	}
	.goal-badge {
		font-size: 0.75rem;
		font-weight: 700;
		color: #059669;
	}
	.goal-pct {
		font-size: 0.75rem;
		font-weight: 600;
		color: #6366f1;
	}

	/* ── Kanban ── */
	.kanban {
		display: grid;
		grid-template-columns: 1fr 1fr 1fr;
		gap: 0.75rem;
		margin-bottom: 1rem;
	}
	.column {
		border: 1px solid #e5e7eb;
		border-top: 4px solid #6366f1;
		border-radius: 12px;
		padding: 0.75rem;
		background: #fafbff;
	}
	.column h2 {
		margin: 0 0 0.75rem;
		font-size: 0.95rem;
	}
	.col-daily {
		border-top-color: #6366f1;
		background: #eef2ff;
	}
	.col-weekly {
		border-top-color: #f59e0b;
		background: #fffbeb;
	}
	.col-done {
		border-top-color: #10b981;
		background: #ecfdf5;
	}

	.chore {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		width: 100%;
		padding: 0.5rem 0.6rem;
		margin-bottom: 0.4rem;
		border: 1px solid #e5e7eb;
		border-radius: 8px;
		background: white;
		cursor: pointer;
		text-align: left;
		font-size: 0.85rem;
	}
	.chore:hover {
		transform: translateY(-1px);
		box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
		border-color: #c7d2fe;
	}

	/* ── Todo traffic light borders ── */
	.chore.todo-chore {
		border-left: 4px solid #3b82f6;
	}
	.chore.todo-blue {
		border-left-color: #3b82f6;
	}
	.chore.todo-green {
		border-left-color: #22c55e;
	}
	.chore.todo-orange {
		border-left-color: #f97316;
	}
	.chore.todo-red {
		border-left-color: #ef4444;
	}
	.chore.todo-black {
		border-left-color: #1f2937;
	}
	.todo-label {
		font-size: 0.62rem;
		font-weight: 700;
		text-transform: uppercase;
		letter-spacing: 0.03em;
		background: #fef3c7;
		color: #b45309;
		border: 1px solid #fcd34d;
		border-radius: 4px;
		padding: 1px 5px;
		flex-shrink: 0;
	}
	.todo-display {
		cursor: default;
		opacity: 0.85;
	}
	.todo-display:hover {
		transform: none;
		box-shadow: none;
		border-top-color: #e5e7eb;
		border-right-color: #e5e7eb;
		border-bottom-color: #e5e7eb;
	}
	.chore:disabled {
		opacity: 0.5;
		cursor: not-allowed;
		transform: none;
		box-shadow: none;
	}
	.done {
		opacity: 0.7;
	}
	.checkbox {
		font-size: 1rem;
		flex-shrink: 0;
	}
	.chore-name {
		flex: 1;
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.chore-value {
		font-size: 0.75rem;
		color: #6b7280;
		font-weight: 600;
		white-space: nowrap;
	}

	.wallet {
		background: #fff;
		border: 1px solid #e5e7eb;
		border-radius: 14px;
		padding: 1rem 1.25rem;
	}
	.wallet-top {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 0.5rem;
		margin-bottom: 0.5rem;
	}
	.wallet-lbl {
		font-size: 0.8rem;
		font-weight: 800;
		color: #374151;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}
	.wallet-total {
		display: flex;
		gap: 0.4rem;
		align-items: center;
		font-size: 0.9rem;
		font-weight: 800;
	}
	.wallet-cash {
		color: #059669;
	}
	.wallet-pts {
		color: #7c3aed;
	}
	.wallet-dot {
		color: #d1d5db;
	}
	.wallet-empty {
		margin: 0;
		font-size: 0.85rem;
		color: #9ca3af;
	}
	.wallet-rewards {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}
	.wallet-reward {
		display: flex;
		align-items: center;
		gap: 0.6rem;
		padding: 0.6rem 0.8rem;
		border-radius: 10px;
		background: #fafbfc;
		border: 1px solid #eeeff2;
	}
	.wr-label {
		flex: 1;
		font-weight: 600;
		font-size: 0.85rem;
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.wr-val {
		font-weight: 800;
		color: #059669;
	}
	.wr-pending {
		font-size: 0.75rem;
		font-weight: 600;
		color: #6b7280;
	}
	.wr-locked {
		color: #d97706;
	}
	.owe-locked {
		margin-left: 0.35rem;
		font-size: 0.72rem;
		font-weight: 600;
		color: #d97706;
	}
	.wr-cta {
		font-size: 0.72rem;
		font-weight: 700;
		padding: 3px 12px;
		background: linear-gradient(135deg, #6366f1, #8b5cf6);
		color: #fff;
		border: none;
		border-radius: 6px;
		cursor: pointer;
	}
	.wr-cta:hover {
		filter: brightness(1.1);
	}

	.eow-desc {
		margin: 0 0 0.6rem;
	}

	.eow-mode-on {
		display: flex;
		align-items: center;
		gap: 0.5rem;
		margin: 0.6rem 0;
		padding: 0.6rem 0.8rem;
		border: 1px dashed #f59e0b;
		border-radius: 8px;
		background: #fffbeb;
		color: #92400e;
		font-size: 0.85rem;
	}

	.preview-notice {
		margin-bottom: 1rem;
		padding: 0.85rem 1.1rem;
		border-radius: 12px;
		border: 1px dashed #f59e0b;
		background: #fffbeb;
		color: #92400e;
		font-size: 0.95rem;
		line-height: 1.4;
	}

	.payday-banner {
		display: flex;
		align-items: center;
		gap: 0.4rem;
		margin-bottom: 1rem;
		padding: 0.85rem 1.1rem;
		border-radius: 12px;
		background: linear-gradient(135deg, #f59e0b, #fbbf24);
		color: #451a03;
		font-weight: 600;
		font-size: 1.05rem;
		box-shadow: 0 6px 16px rgba(245, 158, 11, 0.35);
	}
	.payday-banner b {
		font-weight: 800;
	}

	.payday-countdown {
		display: flex;
		align-items: center;
		gap: 0.9rem;
		margin-bottom: 1rem;
		padding: 1rem 1.2rem;
		border-radius: 12px;
		background: linear-gradient(135deg, #4f46e5, #7c3aed);
		color: #fff;
		box-shadow: 0 6px 16px rgba(79, 70, 229, 0.35);
	}
	.pd-icon {
		font-size: 1.8rem;
	}
	.pd-body {
		display: flex;
		flex-direction: column;
		gap: 0.15rem;
	}
	.pd-title {
		margin: 0;
		font-weight: 700;
		font-size: 0.95rem;
		opacity: 0.9;
	}
	.pd-num {
		margin: 0;
		font-size: 1.7rem;
		font-weight: 800;
		letter-spacing: 0.5px;
		font-variant-numeric: tabular-nums;
	}
	.pd-sub {
		margin: 0;
		font-size: 0.8rem;
		opacity: 0.85;
	}
	.eow-out {
		margin-top: 0.75rem;
		border-top: 1px dashed #e5e7eb;
		padding-top: 0.6rem;
	}
	.eow-range,
	.eow-reset {
		margin: 0.2rem 0;
		font-size: 0.85rem;
	}
	.eow-reset {
		color: #6b7280;
		font-size: 0.8rem;
	}
	.eow-summaries {
		display: flex;
		flex-direction: column;
		gap: 0.3rem;
		margin: 0.6rem 0;
	}
	.eow-row {
		display: flex;
		align-items: center;
		gap: 0.6rem;
		padding: 0.35rem 0.5rem;
		border: 1px solid #eeeff2;
		border-radius: 8px;
		background: #fafbfc;
		font-size: 0.85rem;
	}
	.eow-name {
		flex: 1;
		font-weight: 600;
	}
	.eow-pts {
		color: #7c3aed;
		font-weight: 700;
	}
	.eow-money {
		color: #059669;
		font-weight: 700;
	}
	.eow-chores {
		color: #6b7280;
	}
	.eow-sec {
		font-size: 0.85rem;
		font-weight: 700;
		margin: 0.6rem 0 0.3rem;
	}
	.eow-rewards {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
	}
	.eow-reward {
		font-size: 0.8rem;
		padding: 0.3rem 0.55rem;
		background: #eef2ff;
		color: #4338ca;
		border-radius: 6px;
	}
	.eow-none {
		font-size: 0.8rem;
		color: #9ca3af;
	}
</style>
