fix heatmap in stats for bright theme
This commit is contained in:
@@ -6,12 +6,20 @@
|
|||||||
let all: ActivitySummary[] = [];
|
let all: ActivitySummary[] = [];
|
||||||
let sport: Sport | 'all' = 'all';
|
let sport: Sport | 'all' = 'all';
|
||||||
let loading = true;
|
let loading = true;
|
||||||
|
let theme = 'dark';
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const res = await fetch(`${import.meta.env.BASE_URL}data/index.json`);
|
const res = await fetch(`${import.meta.env.BASE_URL}data/index.json`);
|
||||||
const index: BASIndex = await res.json();
|
const index: BASIndex = await res.json();
|
||||||
all = index.activities.filter(a => a.privacy !== 'private' && a.distance_m);
|
all = index.activities.filter(a => a.privacy !== 'private' && a.distance_m);
|
||||||
loading = false;
|
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);
|
$: 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
|
// Base cell color: zinc-800 dark (#27272a=39,39,42) or zinc-200 light (#e4e4e7=228,228,231)
|
||||||
function applyIntensity(hex: string, intensity: number): string {
|
$: 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);
|
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
|
// 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
|
// the template. (Calling a plain function with a static string arg won't
|
||||||
// re-trigger when byDate/maxDailyKm change — the Map reference does.)
|
// re-trigger when byDate/maxDailyKm change — the Map reference does.)
|
||||||
$: cellColors = (() => {
|
$: cellColors = (() => {
|
||||||
|
const base = baseRgb;
|
||||||
|
const empty = emptyColor;
|
||||||
const m = new Map<string, string>();
|
const m = new Map<string, string>();
|
||||||
for (const [date, sportMap] of byDateBySport) {
|
for (const [date, sportMap] of byDateBySport) {
|
||||||
const total = byDate.get(date) ?? 0;
|
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;
|
const intensity = 0.12 + pctRank(total, sortedDaily) * 0.88;
|
||||||
let tr = 0, tg = 0, tb = 0;
|
let tr = 0, tg = 0, tb = 0;
|
||||||
for (const [sp, dist] of sportMap) {
|
for (const [sp, dist] of sportMap) {
|
||||||
@@ -165,15 +182,15 @@
|
|||||||
tr += cr * w; tg += cg * w; tb += cb * w;
|
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')}`;
|
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;
|
return m;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Legend: 6 swatches from dark to full sport color (or neutral for 'all')
|
// Legend: 6 swatches from base bg to full sport color (or neutral for 'all')
|
||||||
$: legendColor = sport !== 'all' ? sportColor(sport) : '#00c8ff';
|
$: legendColor = sport !== 'all' ? sportColor(sport) : (theme === 'light' ? '#0284c7' : '#00c8ff');
|
||||||
$: legendSwatches = [0, 0.18, 0.38, 0.58, 0.78, 1.0].map(t =>
|
$: 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)
|
// Sport chips present in filtered data (for 'all' color key)
|
||||||
@@ -301,12 +318,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{#each week as date}
|
{#each week as date}
|
||||||
<div
|
<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' : ''}"
|
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) ?? '#27272a'}"
|
style="background:{cellColors.get(date) ?? emptyColor}; --tw-ring-offset-color: var(--bg-base)"
|
||||||
on:mouseenter={e => onCellEnter(date, e)}
|
on:mouseenter={e => onCellEnter(date, e)}
|
||||||
on:mouseleave={onCellLeave}
|
on:mouseleave={onCellLeave}
|
||||||
on:click={e => onCellClick(date, e)}
|
on:click={e => onCellClick(date, e)}
|
||||||
/>
|
></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user