fitness: theme-reactive chart colors, bar outline fix, and stats label polish
All checks were successful
CI / update (push) Successful in 2m17s
All checks were successful
CI / update (push) Successful in 2m17s
- Stats and exercise pages: chart colors adapt to light/dark theme reactively - FitnessChart: remove bar outline (borderWidth 0 for bar type) - Stats: workouts icon/card use --color-primary, plural-aware label, rename labels
This commit is contained in:
@@ -54,7 +54,7 @@
|
|||||||
backgroundColor: ds.backgroundColor ?? (type === 'bar'
|
backgroundColor: ds.backgroundColor ?? (type === 'bar'
|
||||||
? (nordColors[i % nordColors.length])
|
? (nordColors[i % nordColors.length])
|
||||||
: 'transparent'),
|
: 'transparent'),
|
||||||
borderWidth: ds.borderWidth ?? (type === 'line' ? 2 : 1),
|
borderWidth: ds.borderWidth ?? (type === 'line' ? 2 : 0),
|
||||||
pointRadius: ds.pointRadius ?? (type === 'line' ? 3 : 0),
|
pointRadius: ds.pointRadius ?? (type === 'line' ? 3 : 0),
|
||||||
pointBackgroundColor: ds.pointBackgroundColor || ds.borderColor || nordColors[i % nordColors.length],
|
pointBackgroundColor: ds.pointBackgroundColor || ds.borderColor || nordColors[i % nordColors.length],
|
||||||
tension: ds.tension ?? 0.3,
|
tension: ds.tension ?? 0.3,
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getExerciseById } from '$lib/data/exercises';
|
import { getExerciseById } from '$lib/data/exercises';
|
||||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
function checkDark() {
|
||||||
|
if (typeof document === 'undefined') return false;
|
||||||
|
const t = document.documentElement.dataset.theme;
|
||||||
|
if (t === 'dark') return true;
|
||||||
|
if (t === 'light') return false;
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dark = $state(checkDark());
|
||||||
|
onMount(() => {
|
||||||
|
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const onMql = () => { dark = checkDark(); };
|
||||||
|
mql.addEventListener('change', onMql);
|
||||||
|
const obs = new MutationObserver(() => { dark = checkDark(); });
|
||||||
|
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||||
|
return () => { mql.removeEventListener('change', onMql); obs.disconnect(); };
|
||||||
|
});
|
||||||
|
|
||||||
|
const primary = $derived(dark ? '#88C0D0' : '#5E81AC');
|
||||||
|
|
||||||
let activeTab = $state('about');
|
let activeTab = $state('about');
|
||||||
|
|
||||||
const exercise = $derived(data.exercise?.exercise ?? getExerciseById(data.exercise?.id));
|
const exercise = $derived(data.exercise?.exercise ?? getExerciseById(data.exercise?.id));
|
||||||
@@ -21,7 +42,7 @@
|
|||||||
const points = charts.est1rmOverTime ?? [];
|
const points = charts.est1rmOverTime ?? [];
|
||||||
return withTrend({
|
return withTrend({
|
||||||
labels: points.map((/** @type {any} */ p) => new Date(p.date).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })),
|
labels: points.map((/** @type {any} */ p) => new Date(p.date).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })),
|
||||||
datasets: [{ label: 'Est. 1RM (kg)', data: points.map((/** @type {any} */ p) => p.value), borderColor: '#88C0D0' }]
|
datasets: [{ label: 'Est. 1RM (kg)', data: points.map((/** @type {any} */ p) => p.value), borderColor: primary }]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +100,7 @@
|
|||||||
* @param {{ labels: string[], datasets: Array<any> }} chartData
|
* @param {{ labels: string[], datasets: Array<any> }} chartData
|
||||||
* @param {string} trendColor
|
* @param {string} trendColor
|
||||||
*/
|
*/
|
||||||
function withTrend(chartData, trendColor = '#5E81AC') {
|
function withTrend(chartData, trendColor = primary) {
|
||||||
const values = chartData.datasets[0]?.data;
|
const values = chartData.datasets[0]?.data;
|
||||||
if (!values || values.length < 3) return chartData;
|
if (!values || values.length < 3) return chartData;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,31 @@
|
|||||||
<script>
|
<script>
|
||||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||||
import { Dumbbell, Route, Flame } from 'lucide-svelte';
|
import { Dumbbell, Route, Flame } from 'lucide-svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
function checkDark() {
|
||||||
|
if (typeof document === 'undefined') return false;
|
||||||
|
const t = document.documentElement.dataset.theme;
|
||||||
|
if (t === 'dark') return true;
|
||||||
|
if (t === 'light') return false;
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dark = $state(checkDark());
|
||||||
|
onMount(() => {
|
||||||
|
const mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const onMql = () => { dark = checkDark(); };
|
||||||
|
mql.addEventListener('change', onMql);
|
||||||
|
const obs = new MutationObserver(() => { dark = checkDark(); });
|
||||||
|
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||||
|
return () => { mql.removeEventListener('change', onMql); obs.disconnect(); };
|
||||||
|
});
|
||||||
|
|
||||||
|
const primary = $derived(dark ? '#88C0D0' : '#5E81AC');
|
||||||
|
const primaryFill = $derived(dark ? 'rgba(136, 192, 208, 0.15)' : 'rgba(94, 129, 172, 0.15)');
|
||||||
|
|
||||||
const stats = $derived(data.stats ?? {});
|
const stats = $derived(data.stats ?? {});
|
||||||
|
|
||||||
const workoutsChartData = $derived({
|
const workoutsChartData = $derived({
|
||||||
@@ -11,7 +33,7 @@
|
|||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Workouts',
|
label: 'Workouts',
|
||||||
data: stats.workoutsChart?.data ?? [],
|
data: stats.workoutsChart?.data ?? [],
|
||||||
backgroundColor: '#88C0D0'
|
backgroundColor: primary
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25,7 +47,7 @@
|
|||||||
label: '± 1σ',
|
label: '± 1σ',
|
||||||
data: stats.weightChart.upper,
|
data: stats.weightChart.upper,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
backgroundColor: 'rgba(94, 129, 172, 0.15)',
|
backgroundColor: primaryFill,
|
||||||
fill: '+1',
|
fill: '+1',
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
@@ -46,7 +68,7 @@
|
|||||||
{
|
{
|
||||||
label: 'Trend',
|
label: 'Trend',
|
||||||
data: stats.weightChart.sma,
|
data: stats.weightChart.sma,
|
||||||
borderColor: '#5E81AC',
|
borderColor: primary,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
borderWidth: 3,
|
borderWidth: 3,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
@@ -73,17 +95,17 @@
|
|||||||
<div class="lifetime-card workouts">
|
<div class="lifetime-card workouts">
|
||||||
<div class="card-icon"><Dumbbell size={24} /></div>
|
<div class="card-icon"><Dumbbell size={24} /></div>
|
||||||
<div class="card-value">{stats.totalWorkouts ?? 0}</div>
|
<div class="card-value">{stats.totalWorkouts ?? 0}</div>
|
||||||
<div class="card-label">Workouts</div>
|
<div class="card-label">{(stats.totalWorkouts ?? 0) === 1 ? 'Workout' : 'Workouts'}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lifetime-card tonnage">
|
<div class="lifetime-card tonnage">
|
||||||
<div class="card-icon"><Flame size={24} /></div>
|
<div class="card-icon"><Flame size={24} /></div>
|
||||||
<div class="card-value">{stats.totalTonnage ?? 0}<span class="card-unit">t</span></div>
|
<div class="card-value">{stats.totalTonnage ?? 0}<span class="card-unit">t</span></div>
|
||||||
<div class="card-label">Tonnage Lifted</div>
|
<div class="card-label">Lifted</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lifetime-card cardio">
|
<div class="lifetime-card cardio">
|
||||||
<div class="card-icon"><Route size={24} /></div>
|
<div class="card-icon"><Route size={24} /></div>
|
||||||
<div class="card-value">{stats.totalCardioKm ?? 0}<span class="card-unit">km</span></div>
|
<div class="card-value">{stats.totalCardioKm ?? 0}<span class="card-unit">km</span></div>
|
||||||
<div class="card-label">Cardio Distance</div>
|
<div class="card-label">Distance Covered</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -145,7 +167,7 @@
|
|||||||
opacity: 0.08;
|
opacity: 0.08;
|
||||||
}
|
}
|
||||||
.lifetime-card.workouts::before {
|
.lifetime-card.workouts::before {
|
||||||
background: var(--nord8);
|
background: var(--color-primary);
|
||||||
}
|
}
|
||||||
.lifetime-card.tonnage::before {
|
.lifetime-card.tonnage::before {
|
||||||
background: var(--nord12);
|
background: var(--nord12);
|
||||||
@@ -163,8 +185,8 @@
|
|||||||
margin-bottom: 0.15rem;
|
margin-bottom: 0.15rem;
|
||||||
}
|
}
|
||||||
.workouts .card-icon {
|
.workouts .card-icon {
|
||||||
color: var(--nord8);
|
color: var(--color-primary);
|
||||||
background: color-mix(in srgb, var(--nord8) 15%, transparent);
|
background: color-mix(in srgb, var(--color-primary) 15%, transparent);
|
||||||
}
|
}
|
||||||
.tonnage .card-icon {
|
.tonnage .card-icon {
|
||||||
color: var(--nord12);
|
color: var(--nord12);
|
||||||
@@ -187,10 +209,10 @@
|
|||||||
margin-left: 0.15rem;
|
margin-left: 0.15rem;
|
||||||
}
|
}
|
||||||
.card-label {
|
.card-label {
|
||||||
font-size: 0.65rem;
|
font-size: 0.7rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user