diff --git a/src/lib/components/CompletionSparkline.svelte b/src/lib/components/CompletionSparkline.svelte
index ca72bdc..264ae42 100644
--- a/src/lib/components/CompletionSparkline.svelte
+++ b/src/lib/components/CompletionSparkline.svelte
@@ -3,6 +3,13 @@
// Zero-days render as a 1 px baseline stub (never a gap, never zero
// height). The component self-hides when all bars are zero. Caller
// still guards on the user's settings toggle.
+ //
+ // Phase 9 polish: friendlier aria-label (sentence-form summary, not a
+ // numeric list), per-bar
tooltips with absolute date + count,
+ // and a 30 ms-stagger entrance animation that respects
+ // prefers-reduced-motion. Tabindex deliberately omitted: SVGs with
+ // role="img" are reachable by screen-reader graphic navigation
+ // without being part of the keyboard-tab order.
import type { DailyCompletionCount } from "$lib/types/app";
interface Props {
@@ -22,10 +29,12 @@
(width - BAR_GAP * Math.max(0, data.length - 1)) / Math.max(1, data.length),
);
+ let total = $derived(data.reduce((sum, d) => sum + d.count, 0));
+ let today = $derived(data.at(-1)?.count ?? 0);
+
let ariaLabel = $derived.by(() => {
if (data.length === 0) return "";
- const nums = data.map((d) => d.count).join(", ");
- return `Tasks completed over the last ${data.length} days: ${nums}`;
+ return `${today} completed today. ${total} total over the last ${data.length} days.`;
});
let hasAnyCompletion = $derived(data.some((d) => d.count > 0));
@@ -53,7 +62,31 @@
fill="currentColor"
opacity={d.count === 0 ? 0.35 : 0.85}
rx="1"
- />
+ class="bar"
+ style={`--bar-delay: ${i * 30}ms`}
+ >
+ {d.day}: {d.count} {d.count === 1 ? "task" : "tasks"}
+
{/each}
{/if}
+
+
diff --git a/src/lib/pages/TasksPage.svelte b/src/lib/pages/TasksPage.svelte
index d2a4eb6..e6e7eda 100644
--- a/src/lib/pages/TasksPage.svelte
+++ b/src/lib/pages/TasksPage.svelte
@@ -298,7 +298,7 @@
>
{#if todayCount() > 0}
{todayCount()} today
@@ -689,3 +689,27 @@
+
+