fix heatmap in stats for bright theme

This commit is contained in:
Davide Scaini
2026-03-30 09:22:23 +02:00
parent ec6175b143
commit b78f921a3d
+28 -11
View File
@@ -6,12 +6,20 @@
let all: ActivitySummary[] = [];
let sport: Sport | 'all' = 'all';
let loading = true;
let theme = 'dark';
onMount(async () => {
const res = await fetch(`${import.meta.env.BASE_URL}data/index.json`);
const index: BASIndex = await res.json();
all = index.activities.filter(a => a.privacy !== 'private' && a.distance_m);
loading = false;
theme = document.documentElement.getAttribute('data-theme') ?? 'dark';
const obs = new MutationObserver(() => {
theme = document.documentElement.getAttribute('data-theme') ?? 'dark';
});
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
return () => obs.disconnect();
});
$: activities = sport === 'all' ? all : all.filter(a => a.sport === sport);
@@ -143,20 +151,29 @@
];
}
// Lerp from zinc-800 bg (#27272a = 39,39,42) toward target color
function applyIntensity(hex: string, intensity: number): string {
// Base cell color: zinc-800 dark (#27272a=39,39,42) or zinc-200 light (#e4e4e7=228,228,231)
$: emptyColor = theme === 'light' ? '#e4e4e7' : '#27272a';
$: baseRgb = theme === 'light'
? [228, 228, 231] as [number, number, number]
: [39, 39, 42] as [number, number, number];
// Lerp from base bg color toward target sport color
function applyIntensity(hex: string, intensity: number, base: [number, number, number]): string {
const [tr, tg, tb] = hexToRgb(hex);
return `rgb(${Math.round(39 + (tr - 39) * intensity)},${Math.round(39 + (tg - 39) * intensity)},${Math.round(42 + (tb - 42) * intensity)})`;
const [br, bg, bb] = base;
return `rgb(${Math.round(br + (tr - br) * intensity)},${Math.round(bg + (tg - bg) * intensity)},${Math.round(bb + (tb - bb) * intensity)})`;
}
// Precompute date→color as a reactive Map so Svelte tracks it directly in
// the template. (Calling a plain function with a static string arg won't
// re-trigger when byDate/maxDailyKm change — the Map reference does.)
$: cellColors = (() => {
const base = baseRgb;
const empty = emptyColor;
const m = new Map<string, string>();
for (const [date, sportMap] of byDateBySport) {
const total = byDate.get(date) ?? 0;
if (total === 0) { m.set(date, '#27272a'); continue; }
if (total === 0) { m.set(date, empty); continue; }
const intensity = 0.12 + pctRank(total, sortedDaily) * 0.88;
let tr = 0, tg = 0, tb = 0;
for (const [sp, dist] of sportMap) {
@@ -165,15 +182,15 @@
tr += cr * w; tg += cg * w; tb += cb * w;
}
const blended = `#${Math.round(tr).toString(16).padStart(2,'0')}${Math.round(tg).toString(16).padStart(2,'0')}${Math.round(tb).toString(16).padStart(2,'0')}`;
m.set(date, applyIntensity(blended, intensity));
m.set(date, applyIntensity(blended, intensity, base));
}
return m;
})();
// Legend: 6 swatches from dark to full sport color (or neutral for 'all')
$: legendColor = sport !== 'all' ? sportColor(sport) : '#00c8ff';
// Legend: 6 swatches from base bg to full sport color (or neutral for 'all')
$: legendColor = sport !== 'all' ? sportColor(sport) : (theme === 'light' ? '#0284c7' : '#00c8ff');
$: legendSwatches = [0, 0.18, 0.38, 0.58, 0.78, 1.0].map(t =>
t === 0 ? '#27272a' : applyIntensity(legendColor, t)
t === 0 ? emptyColor : applyIntensity(legendColor, t, baseRgb)
);
// Sport chips present in filtered data (for 'all' color key)
@@ -301,12 +318,12 @@
</div>
{#each week as date}
<div
class="w-[10px] h-[10px] rounded-[2px] {date && activitiesByDate.has(date) ? 'cursor-pointer' : ''} {date && date === pinnedDate ? 'ring-1 ring-white ring-offset-[1px] ring-offset-zinc-950' : ''}"
style="background:{cellColors.get(date) ?? '#27272a'}"
class="w-[10px] h-[10px] rounded-[2px] {date && activitiesByDate.has(date) ? 'cursor-pointer' : ''} {date && date === pinnedDate ? 'ring-1 ring-white ring-offset-[1px]' : ''}"
style="background:{cellColors.get(date) ?? emptyColor}; --tw-ring-offset-color: var(--bg-base)"
on:mouseenter={e => onCellEnter(date, e)}
on:mouseleave={onCellLeave}
on:click={e => onCellClick(date, e)}
/>
></div>
{/each}
</div>
{/each}