feat(fitness): tinted body-part icons with semantic accents
Unifies PNG and SVG body-part images behind a single CSS-mask render path, so both now colorize with a --accent CSS variable. Accent splits by measurement type: --blue for proportion parts (chest, shoulders, waist, hips) and --nord8 for muscle parts (neck, biceps, forearms, thighs, calves). Stats cards gain a matching 8%-tint fill and accent-colored hover border. History page header image enlarged. Thigh SVG stroke-width bumped to 11 for better mask legibility.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.40.5",
|
||||
"version": "1.40.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -36,3 +36,9 @@ export function findBodyPart(slug: string): BodyPartCard | null {
|
||||
export function bodyPartSlug(card: BodyPartCard, lang: string): string {
|
||||
return lang === 'de' ? card.slugDe : card.key;
|
||||
}
|
||||
|
||||
const PROPORTION_KEYS: ReadonlySet<string> = new Set(['chest', 'shoulders', 'waist', 'hips']);
|
||||
|
||||
export function bodyPartAccent(key: string): string {
|
||||
return PROPORTION_KEYS.has(key) ? 'var(--blue)' : 'var(--nord8)';
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import DatePicker from '$lib/components/DatePicker.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
import { bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -360,18 +361,14 @@
|
||||
{#if !done}
|
||||
{#key step.key}
|
||||
<section class="card" in:fly={flyOpts}>
|
||||
<div class="hero">
|
||||
<div class="hero" style="--accent: {bodyPartAccent(step.key)}">
|
||||
{#if step.img}
|
||||
{#if step.img.endsWith('.svg')}
|
||||
<div
|
||||
class="hero-pic hero-svg"
|
||||
style="--hero-svg-src: url(/fitness/measure/{step.img})"
|
||||
role="img"
|
||||
aria-label={stepLabel(step)}
|
||||
></div>
|
||||
{:else}
|
||||
<img src="/fitness/measure/{step.img}" alt="" class="hero-pic" />
|
||||
{/if}
|
||||
<div
|
||||
class="hero-pic"
|
||||
style="--hero-src: url(/fitness/measure/{step.img})"
|
||||
role="img"
|
||||
aria-label={stepLabel(step)}
|
||||
></div>
|
||||
{:else}
|
||||
<div class="hero-placeholder" aria-hidden="true">
|
||||
<Ruler size={72} strokeWidth={1.4} />
|
||||
@@ -682,33 +679,21 @@
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(closest-side, var(--color-surface), transparent 70%),
|
||||
var(--color-bg-secondary);
|
||||
box-shadow: var(--shadow-md);
|
||||
position: relative;
|
||||
background: color-mix(in oklab, var(--accent, var(--color-primary)) 15%, transparent);
|
||||
}
|
||||
.hero-pic {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.hero-svg {
|
||||
mask-image: var(--hero-svg-src);
|
||||
-webkit-mask-image: var(--hero-svg-src);
|
||||
mask-image: var(--hero-src);
|
||||
-webkit-mask-image: var(--hero-src);
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-position: center;
|
||||
background-color: var(--color-text-primary);
|
||||
background-color: var(--accent, var(--color-primary));
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
img.hero-pic { filter: invert(1); }
|
||||
}
|
||||
:global(:root[data-theme="dark"]) img.hero-pic { filter: invert(1); }
|
||||
:global(:root[data-theme="light"]) img.hero-pic { filter: none; }
|
||||
|
||||
.hero-placeholder {
|
||||
display: flex;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import StatsRingGraph from '$lib/components/fitness/StatsRingGraph.svelte';
|
||||
import { BODY_PART_CARDS, bodyPartSlug } from '$lib/js/fitnessBodyParts';
|
||||
import { BODY_PART_CARDS, bodyPartSlug, bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
||||
|
||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
||||
const statsSlug = $derived(lang === 'en' ? 'stats' : 'statistik');
|
||||
@@ -390,16 +390,15 @@
|
||||
{@const cv = currentValue(card)}
|
||||
<a
|
||||
class="bp-card"
|
||||
style="--accent: {bodyPartAccent(card.key)}"
|
||||
href="/fitness/{statsSlug}/{historySlug}/{bodyPartSlug(card, lang)}"
|
||||
>
|
||||
<div class="bp-img-wrap" aria-hidden="true">
|
||||
{#if card.img && card.img.endsWith('.svg')}
|
||||
{#if card.img}
|
||||
<div
|
||||
class="bp-img bp-img-svg"
|
||||
style="--bp-svg-src: url(/fitness/measure/{card.img})"
|
||||
class="bp-img"
|
||||
style="--bp-src: url(/fitness/measure/{card.img})"
|
||||
></div>
|
||||
{:else if card.img}
|
||||
<img src="/fitness/measure/{card.img}" alt="" class="bp-img" />
|
||||
{:else}
|
||||
<Ruler size={36} strokeWidth={1.5} />
|
||||
{/if}
|
||||
@@ -989,10 +988,22 @@
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
transition: border-color var(--transition-normal), box-shadow var(--transition-normal), transform var(--transition-normal);
|
||||
}
|
||||
.bp-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: var(--accent, var(--color-primary));
|
||||
opacity: 0.08;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.bp-card:hover {
|
||||
border-color: var(--color-primary);
|
||||
border-color: var(--accent, var(--color-primary));
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.bp-img-wrap {
|
||||
@@ -1002,30 +1013,22 @@
|
||||
height: 3.25rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
background: color-mix(in oklab, var(--accent, var(--color-primary)) 18%, transparent);
|
||||
color: var(--accent, var(--color-primary));
|
||||
}
|
||||
.bp-img {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.bp-img-svg {
|
||||
mask-image: var(--bp-svg-src);
|
||||
-webkit-mask-image: var(--bp-svg-src);
|
||||
mask-image: var(--bp-src);
|
||||
-webkit-mask-image: var(--bp-src);
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-position: center;
|
||||
background-color: var(--color-text-primary);
|
||||
background-color: var(--accent, var(--color-primary));
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
img.bp-img { filter: invert(1); }
|
||||
}
|
||||
:global(:root[data-theme="dark"]) img.bp-img { filter: invert(1); }
|
||||
:global(:root[data-theme="light"]) img.bp-img { filter: none; }
|
||||
.bp-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
+14
-23
@@ -3,6 +3,7 @@
|
||||
import { ArrowLeft, Ruler, TrendingUp, TrendingDown, Minus as MinusIcon } from '@lucide/svelte';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import FitnessChart from '$lib/components/fitness/FitnessChart.svelte';
|
||||
import { bodyPartAccent } from '$lib/js/fitnessBodyParts';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -112,7 +113,7 @@
|
||||
<svelte:head><title>{t(card.labelKey, lang)} · {lang === 'en' ? 'History' : 'Verlauf'} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="detail-page">
|
||||
<header class="detail-header">
|
||||
<header class="detail-header" style="--accent: {bodyPartAccent(card.key)}">
|
||||
<a class="back-link" href="/fitness/{statsSlug}" aria-label={t('back', lang)}>
|
||||
<ArrowLeft size={18} />
|
||||
</a>
|
||||
@@ -121,13 +122,11 @@
|
||||
<h1>{t(card.labelKey, lang)}</h1>
|
||||
</div>
|
||||
<div class="head-img" aria-hidden="true">
|
||||
{#if card.img && card.img.endsWith('.svg')}
|
||||
{#if card.img}
|
||||
<div
|
||||
class="head-pic head-pic-svg"
|
||||
style="--bp-svg-src: url(/fitness/measure/{card.img})"
|
||||
class="head-pic"
|
||||
style="--bp-src: url(/fitness/measure/{card.img})"
|
||||
></div>
|
||||
{:else if card.img}
|
||||
<img src="/fitness/measure/{card.img}" alt="" class="head-pic" />
|
||||
{:else}
|
||||
<Ruler size={40} strokeWidth={1.5} />
|
||||
{/if}
|
||||
@@ -274,34 +273,26 @@
|
||||
.head-img {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
background: color-mix(in oklab, var(--accent, var(--color-primary)) 15%, transparent);
|
||||
color: var(--accent, var(--color-primary));
|
||||
}
|
||||
.head-pic {
|
||||
width: 2.6rem;
|
||||
height: 2.6rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
.head-pic-svg {
|
||||
mask-image: var(--bp-svg-src);
|
||||
-webkit-mask-image: var(--bp-svg-src);
|
||||
width: 4.1rem;
|
||||
height: 4.1rem;
|
||||
mask-image: var(--bp-src);
|
||||
-webkit-mask-image: var(--bp-src);
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-position: center;
|
||||
background-color: var(--color-text-primary);
|
||||
background-color: var(--accent, var(--color-primary));
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
img.head-pic { filter: invert(1); }
|
||||
}
|
||||
:global(:root[data-theme="dark"]) img.head-pic { filter: invert(1); }
|
||||
:global(:root[data-theme="light"]) img.head-pic { filter: none; }
|
||||
|
||||
.summary .stat-grid {
|
||||
display: grid;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
viewBox="0 0 230.97847 161.57938"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(10.583333,-67.733332)" fill="none" stroke="currentColor" stroke-width="7.9375" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g transform="translate(10.583333,-67.733332)" fill="none" stroke="currentColor" stroke-width="11" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m -5.2938776,75.349546 c 0,0 37.0210826,-4.638254 65.9220826,-3.451251 16.254828,0.667607 16.716356,0.857996 37.092959,6.092661 20.583856,5.287909 30.187556,12.829711 52.828226,19.244569 22.64067,6.414855 47.35673,7.358215 58.11105,22.263325 10.75432,14.90511 4.33946,48.11142 4.33946,48.11142 0,0 4.7168,25.47075 3.01875,35.47038 -1.69804,9.99963 -4.7168,22.26332 -4.7168,22.26332" />
|
||||
<path d="m -6.6145834,170.25168 c 0,0 19.8105854,15.09378 43.2059404,17.73519 23.395358,2.64141 79.431013,-10.1883 95.090803,-13.39573 15.6598,-3.20743 19.81059,1.88672 19.81059,1.88672 0,0 -6.7922,14.52776 -6.60353,28.86686 0.18867,14.33909 2.45274,19.62191 2.45274,19.62191" />
|
||||
<path d="m 30.275732,127.2863 c 0,0 18.901817,1.34096 48.200862,10.32508 27.805246,8.52607 33.634316,12.94496 33.634316,12.94496" />
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Reference in New Issue
Block a user