@@ -142,6 +157,13 @@
justify-content: space-between;
gap: 0.5rem;
}
+ /* Back-to-this-week CTA: lives outside .view-tools on the header's top
+ row, pushed right. Always rendered (visibility-toggled) so the header
+ keeps its height when it appears/disappears. */
+ .header-now {
+ margin-left: auto;
+ align-self: flex-end;
+ }
.nav-btn {
padding: 0.2rem 0.5rem;
border: 1px solid #d1d5db;
@@ -154,6 +176,41 @@
.nav-btn:hover {
background: #f3f4f6;
}
+ .nav-btn.now-btn {
+ font-size: 0.8rem;
+ font-weight: 700;
+ white-space: nowrap;
+ }
+ /* Same pill CTA as the member hero. Always rendered (visibility-toggled)
+ so the ‹ › buttons never jump when it appears/disappears. */
+ .now-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ }
+ .now-btn span {
+ display: inline-block;
+ background: rgba(255, 255, 255, 0.92);
+ color: #4338ca;
+ border-radius: 999px;
+ padding: 0.35rem 0.9rem;
+ font-size: 0.8rem;
+ font-weight: 800;
+ white-space: nowrap;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+ }
+ .view-header.hero .now-btn span {
+ background: rgba(255, 255, 255, 0.92);
+ color: #4338ca;
+ }
+ .now-btn:hover span {
+ background: #fff;
+ }
+ .now-btn.now-hidden {
+ visibility: hidden;
+ pointer-events: none;
+ }
.nav-label {
font-size: 0.85rem;
color: #374151;
diff --git a/frontend/src/routes/[fam]/[username]/+page.svelte b/frontend/src/routes/[fam]/[username]/+page.svelte
index f37ad7c..2b72284 100644
--- a/frontend/src/routes/[fam]/[username]/+page.svelte
+++ b/frontend/src/routes/[fam]/[username]/+page.svelte
@@ -384,13 +384,12 @@
);
let weeklyTotal = $derived.by(() => {
- // Must match the pocket-money % threshold definition: all assigned
- // non-todo chores for the member (no season filter), weighted by the
- // number of days in the displayed period — so the hero total agrees
- // with the Goals progress instead of showing a different denominator.
- const chores = assigned.filter(
- (a) => (a.memberId === null || a.memberId === memberId) && !a.isTodo
- );
+ // Must match the pocket-money % threshold definition (and the family
+ // dashboard Done denominator): only the member's own chores count —
+ // personal assignments + shared chores they claimed. Unclaimed shared
+ // chores stay doable in the kanban but out of every denominator, so
+ // all views agree instead of drifting (e.g. 1/37 here vs 1/30 there).
+ const chores = assigned.filter((a) => a.memberId === memberId && !a.isTodo);
const periodDays =
Math.round(
(new Date(weekEnd + 'T00:00:00').getTime() - new Date(weekStart + 'T00:00:00').getTime()) /
@@ -407,6 +406,29 @@
(c.date?.slice(0, 10) || c.date) <= weekEnd
)
);
+ // Viewed-week daily activity for the hero background effect: completions
+ // per day across the 7-day window, plotted as a blurred white line.
+ let heroWeekActivity = $derived.by(() => {
+ const counts: number[] = [];
+ for (let i = 0; i < 7; i++) {
+ const d = addDays(weekStart, i);
+ counts.push(
+ weekCompletions.filter((c) => (c.date?.slice(0, 10) || c.date) === d).length
+ );
+ }
+ return counts;
+ });
+ let heroSpark = $derived.by(() => {
+ const max = Math.max(1, ...heroWeekActivity);
+ const pts = heroWeekActivity.map((v, i) => ({
+ x: 20 + i * ((700 - 40) / 6),
+ y: 100 - (v / max) * 72
+ }));
+ return {
+ line: pts.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' '),
+ pts
+ };
+ });
let weeklyDone = $derived(weekCompletions.length);
let weeklyPct = $derived(weeklyTotal > 0 ? Math.round((weeklyDone / weeklyTotal) * 100) : 0);
@@ -536,10 +558,10 @@
// ─── Threshold Goals (bonus configs the member is chasing) ───
let thresholdGoals = $derived.by(() => {
- // Member's assigned chores (non-todo) — used to compute period potential.
- const myChores = assigned.filter(
- (a) => (a.memberId === null || a.memberId === memberId) && !a.isTodo
- );
+ // Member's own chores (non-todo) — personal + claimed shared, never
+ // unclaimed shared — used to compute period potential. Matches
+ // weeklyTotal above, the family dashboard, and the server's % definition.
+ const myChores = assigned.filter((a) => a.memberId === memberId && !a.isTodo);
return bonusConfigs
.filter((cfg) => {
if (cfg.status !== 'active') return false;
@@ -1227,6 +1249,24 @@
{/if}
+