diff --git a/package.json b/package.json
index 560b46a..81525a0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "homepage",
- "version": "1.17.0",
+ "version": "1.17.1",
"private": true,
"type": "module",
"scripts": {
diff --git a/src/lib/components/fitness/FoodSearch.svelte b/src/lib/components/fitness/FoodSearch.svelte
index fcb5c51..94db09f 100644
--- a/src/lib/components/fitness/FoodSearch.svelte
+++ b/src/lib/components/fitness/FoodSearch.svelte
@@ -3,6 +3,7 @@
import { browser } from '$app/environment';
import { Heart, ExternalLink, ScanBarcode, X } from '@lucide/svelte';
import { detectFitnessLang, fitnessSlugs, t } from '$lib/js/fitnessI18n';
+ import MacroBreakdown from './MacroBreakdown.svelte';
/**
* @type {{
@@ -30,15 +31,6 @@
const isEn = $derived(lang === 'en');
const btnLabel = $derived(confirmLabel ?? t('log_food', lang));
- // SVG ring constants
- const RADIUS = 28;
- const ARC_DEGREES = 300;
- const ARC_LENGTH = (ARC_DEGREES / 360) * 2 * Math.PI * RADIUS;
- const ARC_ROTATE = 120;
- function strokeOffset(percent) {
- return ARC_LENGTH - (Math.min(percent, 100) / 100) * ARC_LENGTH;
- }
-
// --- Search state ---
let query = $state('');
let results = $state(initialResults ?? []);
@@ -126,21 +118,6 @@
};
});
- const macroPercent = $derived.by(() => {
- if (!selected?.per100g) return { protein: 0, fat: 0, carbs: 0 };
- const n = selected.per100g;
- const proteinCal = (n.protein ?? 0) * 4;
- const fatCal = (n.fat ?? 0) * 9;
- const carbsCal = (n.carbs ?? 0) * 4;
- const total = proteinCal + fatCal + carbsCal;
- if (total === 0) return { protein: 0, fat: 0, carbs: 0 };
- return {
- protein: Math.round(proteinCal / total * 100),
- fat: Math.round(fatCal / total * 100),
- carbs: 100 - Math.round(proteinCal / total * 100) - Math.round(fatCal / total * 100),
- };
- });
-
function confirm() {
if (!selected) return;
const grams = resolveGrams();
@@ -195,11 +172,7 @@
}
}
- function fmt(v) {
- if (v >= 100) return Math.round(v).toString();
- if (v >= 10) return v.toFixed(1);
- return v.toFixed(1);
- }
+
function sourceLabel(source) {
if (source === 'bls') return 'BLS';
@@ -518,63 +491,15 @@
{/if}
{#if previewNutrients}
-
-
- {previewNutrients.calories}
- kcal
-
-
-
-
- {#each [
- { pct: macroPercent.protein, label: isEn ? 'Protein' : 'Eiweiß', cls: 'fs-ring-protein', grams: previewNutrients.protein },
- { pct: macroPercent.fat, label: isEn ? 'Fat' : 'Fett', cls: 'fs-ring-fat', grams: previewNutrients.fat },
- { pct: macroPercent.carbs, label: isEn ? 'Carbs' : 'Kohlenh.', cls: 'fs-ring-carbs', grams: previewNutrients.carbs },
- ] as macro (macro.cls)}
-
-
- {macro.label}
- {fmt(macro.grams)}g
-
- {/each}
-
-
-
-
-
- {isEn ? 'Protein' : 'Eiweiß'}
- {fmt(previewNutrients.protein)} g
-
-
- {isEn ? 'Fat' : 'Fett'}
- {fmt(previewNutrients.fat)} g
-
-
- {isEn ? 'Saturated Fat' : 'Ges. Fettsäuren'}
- {fmt(previewNutrients.saturatedFat)} g
-
-
- {isEn ? 'Carbohydrates' : 'Kohlenhydrate'}
- {fmt(previewNutrients.carbs)} g
-
-
- {isEn ? 'Sugars' : 'Zucker'}
- {fmt(previewNutrients.sugars)} g
-
-
- {isEn ? 'Fiber' : 'Ballaststoffe'}
- {fmt(previewNutrients.fiber)} g
-
-
+
{/if}
@@ -923,92 +848,6 @@
font-size: 0.75rem;
color: var(--color-text-tertiary);
}
- .fs-detail-cal {
- text-align: center;
- margin: 0.25rem 0 0.25rem;
- }
- .fs-detail-cal-num {
- font-size: 2.2rem;
- font-weight: 800;
- color: var(--color-text-primary);
- line-height: 1;
- }
- .fs-detail-cal-unit {
- font-size: 1rem;
- font-weight: 600;
- color: var(--color-text-secondary);
- margin-left: 0.2rem;
- }
- .fs-detail-macros {
- display: flex;
- justify-content: space-around;
- margin: 0.25rem 0 0.5rem;
- }
- .fs-detail-macro {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.1rem;
- flex: 1;
- }
- .fs-detail-macro-label {
- font-size: 0.78rem;
- font-weight: 600;
- color: var(--color-text-primary);
- text-align: center;
- }
- .fs-detail-macro-val {
- font-size: 0.72rem;
- color: var(--color-text-tertiary);
- }
- .fs-ring-bg {
- fill: none;
- stroke: var(--color-border);
- stroke-width: 5;
- stroke-linecap: round;
- }
- .fs-ring-fill {
- fill: none;
- stroke-width: 5;
- stroke-linecap: round;
- transition: stroke-dashoffset 0.4s ease;
- }
- .fs-ring-text {
- font-size: 14px;
- font-weight: 700;
- fill: currentColor;
- text-anchor: middle;
- dominant-baseline: central;
- }
- .fs-ring-protein { stroke: var(--nord14); }
- .fs-ring-fat { stroke: var(--nord12); }
- .fs-ring-carbs { stroke: var(--nord9); }
- .fs-detail-rows {
- background: var(--color-surface);
- border-radius: 10px;
- padding: 0.5rem 0.75rem;
- border: 1px solid var(--color-border);
- }
- .fs-detail-row {
- display: flex;
- justify-content: space-between;
- padding: 0.3rem 0;
- border-bottom: 1px solid var(--color-border);
- font-size: 0.85rem;
- color: var(--color-text-primary);
- }
- .fs-detail-row:last-child {
- border-bottom: none;
- }
- .fs-detail-row.sub span:first-child {
- padding-left: 0.75rem;
- color: var(--color-text-tertiary);
- font-size: 0.8rem;
- }
- .fs-detail-row span:last-child {
- color: var(--color-text-secondary);
- font-variant-numeric: tabular-nums;
- }
/* ── Buttons ── */
.fs-actions {
diff --git a/src/lib/components/fitness/MacroBreakdown.svelte b/src/lib/components/fitness/MacroBreakdown.svelte
new file mode 100644
index 0000000..32d88de
--- /dev/null
+++ b/src/lib/components/fitness/MacroBreakdown.svelte
@@ -0,0 +1,161 @@
+
+
+
+ {#if showCalories}
+
+ {Math.round(calories)}
+ kcal
+
+ {/if}
+
+
+ {#each macros as macro (macro.color)}
+
+ {/each}
+
+
+ {#if showDetailRows}
+
+
+ {isEn ? 'Protein' : 'Eiweiß'}
+ {fmt(protein)} g
+
+
+ {isEn ? 'Fat' : 'Fett'}
+ {fmt(fat)} g
+
+
+ {isEn ? 'Saturated Fat' : 'Ges. Fettsäuren'}
+ {fmt(saturatedFat)} g
+
+
+ {isEn ? 'Carbohydrates' : 'Kohlenhydrate'}
+ {fmt(carbs)} g
+
+
+ {isEn ? 'Sugars' : 'Zucker'}
+ {fmt(sugars)} g
+
+
+ {isEn ? 'Fiber' : 'Ballaststoffe'}
+ {fmt(fiber)} g
+
+
+ {/if}
+
+
+
diff --git a/src/lib/components/fitness/RingGraph.svelte b/src/lib/components/fitness/RingGraph.svelte
new file mode 100644
index 0000000..1ded563
--- /dev/null
+++ b/src/lib/components/fitness/RingGraph.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ {#if label}
+ {label}
+ {/if}
+ {#if sublabel}
+ {sublabel}
+ {/if}
+
+
+
diff --git a/src/lib/components/fitness/StatsRingGraph.svelte b/src/lib/components/fitness/StatsRingGraph.svelte
new file mode 100644
index 0000000..c9839a5
--- /dev/null
+++ b/src/lib/components/fitness/StatsRingGraph.svelte
@@ -0,0 +1,78 @@
+
+
+
+ {#snippet extra()}
+ {#if target != null}
+ {@const pos = targetMarkerPos(target)}
+
+ {target}%
+ {/if}
+ {/snippet}
+
+
+
diff --git a/src/lib/components/recipes/NutritionSummary.svelte b/src/lib/components/recipes/NutritionSummary.svelte
index 264272d..bd1e85a 100644
--- a/src/lib/components/recipes/NutritionSummary.svelte
+++ b/src/lib/components/recipes/NutritionSummary.svelte
@@ -1,5 +1,6 @@