From 85c6f89e36a9fbc343ceddde55462372dd9ca99a Mon Sep 17 00:00:00 2001 From: Stan Runge Date: Sun, 25 Oct 2026 12:56:39 +0000 Subject: [PATCH] music: styled hover/focus/tap tooltip with exact hours and date on the hours-per-day chart --- src/routes/trackers/music/+page.svelte | 96 ++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 7 deletions(-) diff --git a/src/routes/trackers/music/+page.svelte b/src/routes/trackers/music/+page.svelte index 7464a88..e7114dc 100644 --- a/src/routes/trackers/music/+page.svelte +++ b/src/routes/trackers/music/+page.svelte @@ -25,6 +25,41 @@ local: 'local files' }; const now = Date.now(); + + // Hours-per-day chart: a styled tooltip on hover, keyboard focus or tap (roving tabindex, + // arrow keys move between days, Escape closes). + let activeDay = $state(null); + let focusDay = $state(null); + let bars = $state([]); + const tabDay = $derived(focusDay ?? s.daily.length - 1); + const tipDay = $derived(activeDay === null ? null : s.daily[activeDay]); + const fullDayLabel = (day: string) => + new Date(`${day}T12:00:00Z`).toLocaleDateString('en-GB', { + weekday: 'short', + day: 'numeric', + month: 'short', + year: 'numeric' + }); + const exactHours = (h: number) => + `${h.toLocaleString('en-GB', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} h`; + const tipShift = (pos: number) => (pos < 0.15 ? '-10%' : pos > 0.85 ? '-90%' : '-50%'); + function moveDay(event: KeyboardEvent, i: number) { + if (event.key === 'Escape') { + activeDay = null; + return; + } + const last = s.daily.length - 1; + const next: Record = { + ArrowLeft: Math.max(0, i - 1), + ArrowRight: Math.min(last, i + 1), + Home: 0, + End: last + }; + if (!(event.key in next)) return; + event.preventDefault(); + focusDay = next[event.key]; + bars[focusDay]?.focus(); + }
@@ -124,14 +159,61 @@ >(last {s.daily.length} days) -
- {#each s.daily as d (d.day)} +
+
{ + if (e.pointerType === 'mouse') activeDay = null; + }} + > + {#each s.daily as d, i (d.day)} + + {/each} +
+ {#if tipDay && activeDay !== null} + {@const pos = (activeDay + 0.5) / s.daily.length}
- {/each} + class="pointer-events-none absolute bottom-full z-10 mb-2 rounded-md border border-[var(--site-border)] bg-[var(--site-surface)] px-3 py-1.5 text-sm whitespace-nowrap shadow-lg" + style="left: calc(0.5rem + {pos} * (100% - 1rem)); transform: translateX({tipShift(pos)})" + aria-hidden="true" + > + {exactHours(tipDay.hours)} + {fullDayLabel(tipDay.day)} ยท {tipDay.plays.toLocaleString('en-GB')} + {tipDay.plays === 1 ? 'play' : 'plays'} +
+ {/if}
{dayLabel(s.daily[0]?.day ?? '')}today