foxygit / FORTIS Log in
commit f841e7efbb8da3eeb2bbf3f73de01e348d3536b8
Author:     jens <jens.se@icloud.com>
AuthorDate: Fri May 1 22:01:57 2026 +0200
Commit:     jens <jens.se@icloud.com>
CommitDate: Fri May 1 22:05:12 2026 +0200

    Add RN app: nutrition, measurements, weight graph, profile editing, custom foods & exercises, 5 themes, MD docs updated
---
 FORTIS_HANDOFF.md                        | 452 +++++++++---------------------
 README.md                                | 454 ++++++++++++-------------------
 Träningsapp_Planering.md                 |  23 +-
 fortis/app/(tabs)/index.tsx              |   5 +-
 fortis/app/(tabs)/library.tsx            |  12 +-
 fortis/app/(tabs)/nutrition.tsx          | 147 +++++++---
 fortis/app/(tabs)/profile.tsx            | 263 +++++++++++++++++-
 fortis/app/(tabs)/schedule.tsx           |   2 +-
 fortis/app/_layout.tsx                   |  22 +-
 fortis/app/onboarding/index.tsx          |  72 ++++-
 fortis/app/workout/[id].tsx              |   2 +-
 fortis/package-lock.json                 |  48 ++--
 fortis/package.json                      |   4 +-
 fortis/src/constants/types.ts            |   1 +
 fortis/src/lib/macroCalc.ts              |  25 +-
 fortis/src/store/useMeasurementsStore.ts |  31 +++
 fortis/src/store/useNutritionStore.ts    |  14 +
 17 files changed, 889 insertions(+), 688 deletions(-)

diff --git a/FORTIS_HANDOFF.md b/FORTIS_HANDOFF.md
index 00a900a..c383146 100644
--- a/FORTIS_HANDOFF.md
+++ b/FORTIS_HANDOFF.md
@@ -1,380 +1,192 @@
-# FORTIS Träningsapp — Claude Code Handoff
+# FORTIS Träningsapp — Handoff-dokument

 ## Projektöversikt

 **Appnamn:** FORTIS
-**Stack:** React Native + Expo (målplattform) — prototyp byggd i HTML/JS
-**Nuvarande fas:** Fas 1 klar som HTML-prototyp, redo att migreras till React Native/Expo
-**Designriktning:** Mörk header / ljus content, accent #E85D26 (orange), minimalistisk men varm
+**Stack:** React Native + Expo SDK 54, Expo Router v6, TypeScript, Zustand
+**Nuvarande fas:** Fas 5 klar — RN-migration genomförd, appen körs fullt ut
+**Designriktning:** Mörk header / ljus content, accent `#E85D26` (orange), minimalistisk men varm

 ---

-## Vad som är byggt (Fas 1 — HTML-prototyp)
+## Vad som är byggt

-Filen `FORTIS-app.html` innehåller en fullt fungerande prototyp med:
+### HTML-prototyp (`FORTIS-app.html`)
+Fullt fungerande prototyp med alla grundfunktioner inkl. progressionsgraf per övning. Fungerar direkt i webbläsaren, data sparas i `window.storage`.

-- **Splash screen** — 3 sek animerad intro (roterande SVG-ring + progress-bar) → auto-redirect
-- **Onboarding** — 5-stegs wizard: välkommen → profil → mål → kalori/makroberäkning → klart
-  - Mifflin-St Jeor-formeln för TDEE-beräkning
-  - Sparar till storage efter steg 5
-- **Dashboard (Hem)** — SVG-kaloriering, makro-bars (P/K/F), vattendroppar, dagens pass, streak
-- **Schema** — veckostrip måndag–söndag, pass per dag, start av pass, lägg till nytt pass
-- **Aktiv träning** — sets/reps/vikt per set, ✓-knappar, 60 sek vilotimer, passtimer, spara till logg
-- **Övningsbibliotek** — 20 övningar, chip-filter på utrustning, fritextsök, detaljvy
-- **Kostlogg** — grundlayout med måltidssektion och makrospårning (ej fullständig)
-- **Profil** — profildata, stats, inställningar, reset
+### React Native-appen (`fortis/`)
+Fullt fungerande mobilapp. Kör med `cd fortis && npx expo start`.

----
+**Implementerade skärmar:**

-## Navigationstruktur
-
-Bottom nav med 5 flikar:
-```
-[ 🏠 Hem ] [ 📅 Schema ] [ 📚 Bibliotek ] [ 🍎 Kost ] [ 👤 Profil ]
-```
+| Skärm | Fil | Innehåll |
+|---|---|---|
+| Splash + Onboarding | `app/onboarding/index.tsx` | Animerad SVG-ring, 5-stegs wizard, TDEE-beräkning, takt-val |
+| Dashboard | `app/(tabs)/index.tsx` | Kaloriering, makrobars, vattendroppar, dagens pass, streak |
+| Schema | `app/(tabs)/schedule.tsx` | Veckostrip, pass per dag, starta pass |
+| Aktiv träning | `app/workout/[id].tsx` | Sets/reps/vikt, vilotimer, passtimer, logga pass |
+| Bibliotek | `app/(tabs)/library.tsx` | 75 övningar, filter, sök, egna övningar (+FAB, EGET-badge) |
+| Kost | `app/(tabs)/nutrition.tsx` | Måltider, mat-sökning, egna livsmedel, gram-input |
+| Profil | `app/(tabs)/profile.tsx` | Stats, viktgraf, mätningslogg, profil-edit, teman, achievements |

 ---

-## Designbeslut (dokumenterade)
+## Fil- och katalogstruktur

-### Färger
 ```
-Accent:     #E85D26  (orange — CTA, aktiva element)
-Accent2:    #2A6AE8  (blå — kolhydrater, sekundär)
-Grön:       #22A647  (fett, success)
-Bakgrund:   #F2F1ED  (varm off-white)
-Kort:       #FFFFFF
-Text:       #141414
-Muted:      #888880
-Border:     rgba(0,0,0,0.09)
-Dark header:#0F0F0F  (splash, aktiv träning)
+fortis/
+├── app/
+│   ├── _layout.tsx              # Root layout: laddar stores, route guard, StatusBar
+│   ├── (tabs)/
+│   │   ├── _layout.tsx          # Tab bar (5 flikar, Lucide-ikoner, theme-färger)
+│   │   ├── index.tsx            # Dashboard
+│   │   ├── schedule.tsx         # Träningsschema
+│   │   ├── library.tsx          # Övningsbibliotek
+│   │   ├── nutrition.tsx        # Kostlogg
+│   │   └── profile.tsx          # Profil & progress
+│   ├── onboarding/
+│   │   └── index.tsx            # SplashIntro + 5-stegs wizard
+│   └── workout/
+│       └── [id].tsx             # Aktiv träning (fullScreenModal)
+│
+└── src/
+    ├── constants/
+    │   ├── types.ts             # Alla TypeScript-gränssnitt
+    │   ├── exercises.ts         # 75 övningar (Exercise[])
+    │   ├── foods.ts             # 30 livsmedel (FoodItem[])
+    │   └── theme.ts             # 5 teman + Theme-interface
+    ├── store/
+    │   ├── useProfileStore.ts   # profile, macroGoals, hasLoaded, setProfile(), setMacroGoals()
+    │   ├── useWorkoutStore.ts   # workouts, workoutLog, calcStreak(), logWorkout()
+    │   ├── useNutritionStore.ts # foodLog, waterToday, customFoods, addFoodEntry(), addCustomFood()
+    │   ├── useMeasurementsStore.ts # measurements, addMeasurement(), removeMeasurement()
+    │   └── useThemeStore.ts     # themeId, setTheme() — persist: fortis_theme
+    ├── hooks/
+    │   └── useTheme.ts          # Returnerar aktiva temavärden från THEMES[themeId]
+    └── lib/
+        ├── storage.ts           # storageGet(), storageSet(), STORAGE_KEYS
+        └── macroCalc.ts         # calcTDEE(), calcMacros(), PACE_LABELS, today()
 ```

-### Filter-UX i biblioteket (beslutad best practice)
-- **Utrustning** → horisontellt scrollbara toggle-chips (alltid synliga)
-- **Muskelgrupp + Svårighet** → filtermodal via ikon med badge-räknare
-- Filter kombineras (Ben + Kettlebell = båda måste matcha)
-
-### Utrustningskategorier
-Kroppsvikt, Hantel, Kettlebell, Skivstång, Kabelmaskin, Maskin, Motståndsband, TRX, Löpband, Medicinboll
-
-### Muskelgruppskategorier
-Bröst, Rygg, Axlar, Armar, Ben, Core, Kondition, Helkropp, Stretching
-
 ---

-## Datamodeller (TypeScript-ready)
-
-```typescript
-interface Profile {
-  id: string
-  name: string
-  gender: 'male' | 'female' | 'other'
-  age: number
-  heightCm: number
-  weightKg: number
-  goalWeightKg: number
-  goal: 'lose' | 'maintain' | 'gain'
-  activityLevel: 'sedentary' | 'light' | 'moderate' | 'very_active'
-  macroGoals: MacroGoals
-  createdAt: string
-  updatedAt: string
-}
-
-interface MacroGoals {
-  calories: number
-  proteinG: number
-  carbsG: number
-  fatG: number
-}
+## Route guard

-interface Exercise {
-  id: string
-  name: string
-  category: string
-  muscleGroups: string[]
-  secondaryMuscles: string[]
-  equipment: string[]
-  difficulty: 'beginner' | 'intermediate' | 'advanced'
-  type: 'strength' | 'cardio' | 'flexibility'
-  instructions: string[]
-  gifUrl?: string
-  wgerId?: number        // wger.de exercise ID — för GIF/video-hämtning via API i Fas 2
-  isCustom: boolean
-}
+I `app/_layout.tsx` körs en `useEffect` på `[hasLoaded, profile]`:

-interface WorkoutSchedule {
-  id: string
-  name: string
-  dayOfWeek?: number        // 0=mån, 6=sön
-  repeatEveryXDays?: number
-  exercises: ScheduledExercise[]
-  estimatedMinutes: number
-  muscleGroups: string[]
-  createdAt: string
-}
-
-interface ScheduledExercise {
-  exerciseId: string
-  sets: number
-  reps?: number
-  durationSeconds?: number
-  targetWeightKg?: number
-  restSeconds: number
-  notes?: string
-}
-
-interface WorkoutLogEntry {
-  id: string
-  scheduleId?: string
-  date: string              // ISO YYYY-MM-DD
-  startedAt: string
-  endedAt?: string
-  durationMinutes: number
-  exercises: LoggedExercise[]
-  notes?: string
-  completedManually: boolean
-}
+```tsx
+useEffect(() => {
+  if (!hasLoaded) return
+  const inOnboarding = segments[0] === 'onboarding'
+  if (!profile && !inOnboarding) router.replace('/onboarding')
+  else if (profile && inOnboarding) router.replace('/(tabs)')
+}, [hasLoaded, profile])
+```

-interface LoggedExercise {
-  exerciseId: string
-  sets: LoggedSet[]
-}
+**Viktigt:** `Stack.Screen name` = filsökväg (`"onboarding/index"`), `router.replace` = URL (`'/onboarding'`), `segments[0]` = URL-segment (`'onboarding'`).

-interface LoggedSet {
-  reps?: number
-  weightKg?: number
-  durationSeconds?: number
-  completed: boolean
-}
+---

-interface FoodLogEntry {
-  id: string
-  date: string
-  meal: 'breakfast' | 'lunch' | 'dinner' | 'snack'
-  foodId: string
-  foodName: string
-  amountG: number
-  calories: number
-  proteinG: number
-  carbsG: number
-  fatG: number
-  barcode?: string
-}
+## Tema-system

-interface MeasurementEntry {
-  id: string
-  date: string
-  weightKg?: number
-  waistCm?: number
-  bodyFatPct?: number
-  notes?: string
-}
+Frikopplad modul — komponenter hårdkodar aldrig färger:

-interface WaterLogEntry {
-  id: string
-  date: string
-  totalMl: number
-  entries: { time: string; ml: number }[]
-}
+```tsx
+// I varje komponent:
+const theme = useTheme()
+// theme.accent, theme.card, theme.text, theme.muted, theme.border, etc.
 ```

----
-
-## Lagringsnycklar
+`useThemeStore.setTheme(id)` byter tema och persisterar. Nya teman läggs enbart till i `src/constants/theme.ts`.

-```
-fortis_profile           → Profile
-fortis_macrogoals        → MacroGoals
-fortis_workouts          → WorkoutSchedule[]
-fortis_workoutlog        → WorkoutLogEntry[]
-fortis_water_YYYY-MM-DD  → number (ml)
-fortis_measurements      → MeasurementEntry[]
-fortis_foodlog           → FoodLogEntry[]
-fortis_custom_foods      → FoodItem[]
-fortis_achievements      → string[]
+**Theme-interface:**
+```typescript
+interface Theme {
+  id: string; name: string
+  bg: string; card: string; outer: string
+  text: string; muted: string; border: string
+  accent: string; accentLight: string
+  header: string; headerText: string
+}
 ```

-**Persistens-strategi:** Designad för direkt mappning mot `AsyncStorage` (React Native) eller `Expo SQLite`. Exportera all data som en JSON-fil via Profil → Inställningar → Exportera.
-
 ---

-## Komponentstruktur (React Native / Expo)
-
-```
-src/
-├── app/                        # Expo Router (file-based routing)
-│   ├── _layout.tsx             # Root layout + bottom tabs
-│   ├── (tabs)/
-│   │   ├── index.tsx           # Dashboard / Hem
-│   │   ├── schedule.tsx        # Träningsschema
-│   │   ├── library.tsx         # Övningsbibliotek
-│   │   ├── nutrition.tsx       # Kostlogg
-│   │   └── profile.tsx         # Profil & inställningar
-│   ├── onboarding/
-│   │   └── index.tsx           # Onboarding-wizard
-│   └── workout/
-│       └── [id].tsx            # Aktiv träningsvy
-│
-├── components/
-│   ├── shared/
-│   │   ├── ProgressRing.tsx    # SVG-kaloriering
-│   │   ├── MacroBar.tsx        # P/K/F progress-bar
-│   │   ├── WorkoutTimer.tsx    # Passtimer
-│   │   ├── RestTimer.tsx       # Vilotimer-overlay
-│   │   ├── ProgressionChart.tsx# Linjediagram per övning
-│   │   ├── StreakBadge.tsx     # Streak-display
-│   │   └── EquipChip.tsx      # Utrustningsfilter-chip
-│   ├── home/
-│   │   ├── CalorieCard.tsx
-│   │   ├── WaterTracker.tsx
-│   │   └── TodayWorkoutCard.tsx
-│   ├── schedule/
-│   │   ├── WeekStrip.tsx
-│   │   ├── WorkoutBlock.tsx
-│   │   └── AddWorkoutModal.tsx
-│   ├── library/
-│   │   ├── ExerciseCard.tsx
-│   │   ├── ExerciseDetailModal.tsx
-│   │   └── FilterModal.tsx
-│   ├── nutrition/
-│   │   ├── MealSection.tsx
-│   │   ├── AddFoodModal.tsx
-│   │   ├── BarcodeScanner.tsx  # Expo Camera
-│   │   └── MacroSummary.tsx
-│   └── profile/
-│       ├── WeightChart.tsx
-│       └── Achievements.tsx
-│
-├── store/
-│   ├── useProfileStore.ts      # Zustand store
-│   ├── useWorkoutStore.ts
-│   ├── useNutritionStore.ts
-│   └── useMeasurementStore.ts
-│
-├── lib/
-│   ├── storage.ts              # AsyncStorage wrapper
-│   ├── macroCalc.ts            # Mifflin-St Jeor
-│   ├── exportData.ts           # JSON-export
-│   └── exerciseData.ts         # Alla 20+ övningar (utbyggbar)
-│
-└── constants/
-    ├── colors.ts
-    ├── exercises.ts
-    └── theme.ts
-```
+## Kaloriformel

-### Tema-modulens ansvar (frikopplad från appen)
+Mifflin-St Jeor TDEE + pace-delta i `src/lib/macroCalc.ts`:

-Temat ska vara en **självständig modul** — appen konsumerar det, äger det inte.
+```typescript
+// calcTDEE() → BMR × aktivitetsmultiplikator
+// calcMacros(tdee, goal, pace, weightKg) → MacroGoals

-```
-src/constants/theme.ts        ← THEMES-objekt + typdefinitioner
-src/store/useThemeStore.ts    ← Zustand: activeTheme, setTheme(), persist
-src/hooks/useTheme.ts         ← hook: returnerar aktiva CSS-värden
+const PACE_DELTA = {
+  lose:     { slow: -250, moderate: -500, fast: -750 },
+  gain:     { slow: 200,  moderate: 350,  fast: 500  },
+  maintain: { slow: 0,    moderate: 0,    fast: 0    },
+}
+// Protein: weightKg × 2g  |  Fett: 25% av kalorier  |  Kolhydrater: resten
+// Minimum: 1 200 kcal/dag
 ```

-**Regler:**
-- Inga färgvärden hårdkodas i komponenter — alltid via `useTheme()`
-- `setTheme()` i store sköter persist (`AsyncStorage key: fortis_theme`)
-- Nya teman läggs **enbart** till i `theme.ts` — inga ändringar i komponenter
-- `_layout.tsx` applicerar temat på root-nivå via `ThemeProvider`
-
-**Nuvarande teman (från HTML-prototypen):**
-`fortis` · `dark` · `midnight` · `forest` · `stone`
-
 ---

-## Fas-plan
-
-| Fas | Innehåll | Status |
-|-----|----------|--------|
-| **Fas 1** | Splash, Onboarding, Dashboard, Schema, Aktiv träning, Bibliotek (grund) | ✅ Klar (HTML-prototyp) |
-| **Fas 2** | Progressionsgraf per övning, Achievements/streaks, Utbyggt bibliotek (GIFs) | 🔄 Pågår |
-| **Fas 3** | Fullständig kostlogg, Makrospårning, Matdatabas-sökning, Vattenlogg | 🔜 |
-| **Fas 4** | Barcodeläsare, Open Food Facts API, Notifikationer, Invägningsdag | 🔜 |
-| **Fas 5** | React Native-migration, offline-stöd, push-notiser, App Store | 🔜 |
-
----
+## Viktiga kända buggar & lösningar

-## Tech Stack (produktion)
-
-| Komponent | Val |
-|-----------|-----|
-| Framework | React Native + Expo SDK 51+ |
-| Routing | Expo Router (file-based) |
-| State | Zustand |
-| Persistens | AsyncStorage + Expo SQLite |
-| Charts | Victory Native |
-| Animationer | React Native Reanimated 3 |
-| Barcode | Expo Camera + expo-barcode-scanner |
-| Matdatabas | Open Food Facts API (https://world.openfoodfacts.org/api/v0/product/{barcode}.json) |
-| Icons | Expo Vector Icons / Lucide RN |
-| Notiser | Expo Notifications |
+| Problem | Orsak | Lösning |
+|---|---|---|
+| Functions i `StyleSheet.create()` | TS2322 — StyleSheet accepterar inte funktioner | Dela upp i `chip` + `chipActive`, använd `[s.chip, active && s.chipActive]` |
+| Route name mismatch | `Stack.Screen name` = filsökväg, inte URL | `name="onboarding/index"`, `router.replace('/onboarding')` |
+| ScrollView scrollas inte | `TouchableOpacity` wrapping blockerar scroll | Använd `View` (inte `TouchableOpacity`) som wrapper runt `ScrollView` i modaler |
+| `paddingBottom` på ScrollView | Måste ligga i `contentContainerStyle`, inte `style` | `contentContainerStyle={{ paddingBottom: 32 }}` |
+| SVG rotation web-varning | `rotation`/`origin`-props är inte valid SVG | Använd `transform="rotate(-90 65 65)"` istället |
+| Expo SVG-version | `react-native-svg@15.15.4` inte kompatibel med Expo 54 | Downgraded till `15.12.1` |
+| AsyncStorage-version | `@react-native-async-storage@3.0.2` ej kompatibel | Downgraded till `2.2.0` |

 ---

-## Övnings-dataset (nuvarande 20 st)
+## Lagringsnycklar

 ```typescript
-// Finns i HTML-prototypen, extrahera till src/constants/exercises.ts
-// Struktur per övning:
-{
-  id: 'pushup',
-  name: 'Armhävningar',
-  muscle: 'Bröst',          // primär
-  secondary: 'Triceps, Axlar',
-  equip: 'Kroppsvikt',
-  icon: '💪',
-  diff: 'Nybörjare',
-  type: 'Styrka',
-  isTimer: false,            // true = tidsbaserad (plank etc.)
-  instructions: string[]
-}
-```
-
-Utöka till minst 100 övningar i Fas 2. Strukturen är exportklar.
-
----
-
-## Export-format (JSON)
-
-```json
-{
-  "exportVersion": "1.0",
-  "exportedAt": "ISO-timestamp",
-  "profile": { },
-  "workoutSchedules": [ ],
-  "workoutLog": [ ],
-  "foodLog": [ ],
-  "measurements": [ ],
-  "waterLog": [ ],
-  "customFoods": [ ]
+export const STORAGE_KEYS = {
+  profile:          'fortis_profile',
+  workouts:         'fortis_workouts',
+  workoutLog:       'fortis_workoutlog',
+  foodLog:          'fortis_foodlog',
+  customFoods:      'fortis_custom_foods',
+  customExercises:  'fortis_custom_exercises',
+  measurements:     'fortis_measurements',
+  theme:            'fortis_theme',
+  water: (date) => `fortis_water_${date}`,
 }
 ```

 ---

-## Känd teknisk skuld / TODO
+## Teknisk skuld / TODO

-- [x] Filtermodal (muskelgrupp + svårighet) — implementerad i prototypen
-- [x] Övningsdatabas utökad till 75 övningar (från wger.de)
-- [ ] Migrera tema-system till frikopplad modul (`theme.ts` + `useThemeStore.ts` + `useTheme()`) — inga färger hårdkodas i komponenter
-- [ ] Lägg till `wgerId` på alla 75 övningar i `exerciseData.ts` vid RN-migration — möjliggör GIF/video-hämtning via `GET https://wger.de/api/v2/exerciseinfo/{wgerId}/`
-- [x] Användardefinierade övningar — formulär i biblioteket, `isCustom: true`, persist i `fortis_custom_exercises`, EGEN-badge + radering
-- [x] Kostlogg — 30 livsmedel, sök-modal per måltid, gram-input, live makrobars + kaloriräknare, persist i `fortis_foodlog`
-- [ ] Barcodeläsare är stub — kräver Expo Camera i RN
-- [x] Progressionsgraf — SVG-linjediagram per övning, PR-markering, stats (PR/pass/senast), nås via "Se progression" i övningsdetaljvyn
-- [x] Vilotimerns längd — använder nu `restSec` från övningskonfigurationen (fallback 60s)
-- [ ] Inga notifikationer ännu
-- [x] Onboarding kan hoppas över — "Hoppa över" på steg 1, skapar profil med defaults direkt
+- [ ] Progressionsgraf per övning i RN (finns i HTML-prototypen — kräver aggregering av `workoutLog` per `exerciseId`)
+- [ ] Barcodeläsare (Expo Camera) — streckkodsskanning för mat
+- [ ] Open Food Facts API-integration (`https://world.openfoodfacts.org/api/v0/product/{barcode}.json`)
+- [ ] Push-notifikationer (Expo Notifications) — pass-påminnelse, vattenintag, invägningsdag
+- [ ] Export av data till JSON
+- [ ] `wgerId` på alla 75 övningar (möjliggör GIF/video via `GET https://wger.de/api/v2/exerciseinfo/{id}/`)
+- [ ] Redigera/ta bort schemalagda pass i schema-skärmen
+- [ ] Inga notifikationer vid Achievement-upplåsning i RN (toast fanns i HTML-prototypen)

 ---

-## Starta prototypen
+## Fas-plan

-Öppna `FORTIS-app.html` direkt i webbläsaren. All data sparas i `window.storage` (Claude artifact API) — byt mot `AsyncStorage` i RN.
+| Fas | Innehåll | Status |
+|---|---|---|
+| **Fas 1** | Splash, Onboarding, Dashboard, Schema, Aktiv träning, Bibliotek | ✅ Klar |
+| **Fas 2** | Achievements/streaks, utbyggt bibliotek (75 övningar), egna övningar | ✅ Klar |
+| **Fas 3** | Kostlogg, makrospårning, matdatabas, vattenlogg, egna livsmedel | ✅ Klar |
+| **Fas 4** | Profil-redigering, viktgraf, mätningslogg, takt i onboarding | ✅ Klar |
+| **Fas 5** | React Native + Expo migration, AsyncStorage, offline-stöd | ✅ Klar |
+| **Fas 6** | Barcodeläsare, Open Food Facts API, Push-notifikationer | 🔜 Näst |

 ---

-*Handoff skapad: 2026-04-29*
+*Uppdaterad: 2026-05-01*
diff --git a/README.md b/README.md
index 99e17ee..ae16468 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # FORTIS — Tränings- och hälsoapp

-> Personlig träningsapp med fokus på progressionsspårning, kostlogg och schemaläggning.
-> Prototyp byggd i HTML/JS — målplattform React Native + Expo.
+> Personlig träningsapp byggd i React Native + Expo.
+> Spårning av träning, kost, vikt och progress med 5 teman och offline-stöd.

 ---

@@ -16,7 +16,7 @@
 - [Achievements](#achievements)
 - [Datalagring](#datalagring)
 - [Datamodeller](#datamodeller)
-- [Arkitektur (React Native)](#arkitektur-react-native)
+- [Arkitektur](#arkitektur)
 - [Roadmap](#roadmap)
 - [Tech stack](#tech-stack)

@@ -24,47 +24,51 @@

 ## Kom igång

-Prototypen är en enda HTML-fil — ingen installation krävs.
-
 ```bash
-# Öppna direkt i webbläsaren
-open FORTIS-app.html
+cd fortis
+npx expo start
 ```

-All data sparas i `window.storage` (Claude artifact API) under development.
-Vid React Native-migration byts detta mot `AsyncStorage` / `Expo SQLite`.
+- **iOS Simulator:** tryck `i` i terminalen
+- **Android Emulator:** tryck `a` i terminalen
+- **Fysisk enhet:** skanna QR-koden med Expo Go
+
+All data sparas lokalt med `AsyncStorage` — ingen server krävs.

 ---

 ## Funktioner

-### Implementerat i HTML-prototypen
+### Implementerat i React Native-appen

 | Funktion | Status | Detaljer |
 |---|---|---|
-| Splash screen | ✅ | 3 sek animerad SVG-ring + progress-bar → auto-redirect |
-| Onboarding | ✅ | 5-stegs wizard, Mifflin-St Jeor TDEE-beräkning, kan hoppas över |
-| Dashboard | ✅ | SVG-kaloriering, makrobars, vattenspårning, dagens pass, streak |
-| Träningsschema | ✅ | Veckostrip mån–sön, pass per dag, lägg till/ta bort |
-| Aktiv träning | ✅ | Sets/reps/vikt, vilotimer (per övning), passtimer, spara till logg |
+| Splash screen | ✅ | Animerad SVG-ring + progress-bar, 2.8 sek → fade |
+| Onboarding | ✅ | 5-stegs wizard, Mifflin-St Jeor TDEE, takt-val, kan hoppas över |
+| Route guard | ✅ | Redirect till onboarding om ingen profil, tillbaka vid profil |
+| Dashboard | ✅ | SVG-kaloriering, makrobars, vattendroppar, dagens pass, streak |
+| Träningsschema | ✅ | Veckostrip mån–sön, pass per dag, starta pass |
+| Aktiv träning | ✅ | Sets/reps/vikt, vilotimer per övning, passtimer, spara till logg |
 | Övningsbibliotek | ✅ | 75 övningar, utrustningschips, filtermodal (muskelgrupp + svårighet), sök |
-| Egna övningar | ✅ | Skapa/ta bort egna övningar, EGEN-badge, persist i `fortis_custom_exercises` |
-| Progressionsgraf | ✅ | SVG-linjediagram per övning, PR-markering, statistik |
+| Egna övningar | ✅ | Skapa/ta bort egna övningar, EGET-badge, persist i `fortis_custom_exercises` |
 | Kostlogg | ✅ | 30 livsmedel, sök per måltid, gram-input, live makrobars |
-| Achievements | ✅ | 10 achievements, toast-notis vid upplåsning, grid i profil |
-| Tema-system | ✅ | 5 teman, väljs i Profil, persist i storage |
-| Profil & stats | ✅ | Profildata, pass totalt, streak, viktmål, inställningar |
+| Egna livsmedel | ✅ | Skapa/ta bort egna livsmedel, EGET-badge, persist i `fortis_custom_foods` |
+| Mätningar | ✅ | Logga vikt, midja, fettprocent — persist i `fortis_measurements` |
+| Viktgraf | ✅ | SVG-linjegraf senaste 7 mätningarna med y-axel + datumrubriker |
+| Profil-redigering | ✅ | Redigera vikt, mål, takt, aktivitetsnivå — live makropreview |
+| Achievements | ✅ | 10 achievements, grid i profil med låsta/upplåsta |
+| Tema-system | ✅ | 5 teman, väljs i Profil, persist i `fortis_theme` |
+| 5 teman | ✅ | Fortis, Dark, Midnight, Forest, Stone |

-### Ej implementerat ännu (kräver React Native)
+### Ej implementerat ännu

 | Funktion | Fas | Blockerat av |
 |---|---|---|
 | Barcodeläsare | 4 | Expo Camera |
 | Open Food Facts API | 4 | Native network + barcode |
 | Push-notifikationer | 4 | Expo Notifications |
-| Invägningsdag med påminnelse | 4 | Notifications |
-| Progressionsgraf med Victory Native | 5 | RN-migration |
-| Offline-stöd | 5 | RN-migration |
+| Progressionsgraf per övning | — | Träningslogg saknar aggregering per övning i RN |
+| Export av data (JSON) | — | Inte prioriterat ännu |

 ---

@@ -72,55 +76,55 @@ Vid React Native-migration byts detta mot `AsyncStorage` / `Expo SQLite`.

 ### 🏠 Hem (Dashboard)
 Daglig översikt på ett ögonkast:
-- **Kaloriering** — SVG-cirkel med kcal in / mål, dynamisk färgfyllning
-- **Makrobars** — Protein / Kolhydrater / Fett med procent-fill
-- **Vattenspårning** — visuella droppar, +2,5 dl snabbknapp, dagsmål 2 500 ml
-- **Dagens pass** — namn, antal övningar, snabbstart-knapp
+- **Kaloriering** — SVG-cirkel med kcal loggat / mål
+- **Makrobars** — Protein / Kolhydrater / Fett med procentfyllning
+- **Vattenspårning** — visuella droppar, +250 ml snabbknapp, dagsmål 2 500 ml
+- **Dagens pass** — namn + antal övningar, länk till aktiv träning
 - **Streak-pill** — antal dagar i rad med loggat pass

 ### 📅 Schema
 - Veckostrip måndag–söndag, aktiv dag markerad
-- Pass per dag med övningslista
-- Lägg till nytt pass (namn + dag)
+- Pass per dag med övningar listade
 - Starta pass direkt → aktiv träningsvy

-#### Aktiv träning (modal)
+#### Aktiv träning
 - Varje övning med sets × reps × vikt, editerbara live
-- ✓-knapp per set → startar vilotimer (tid konfigurerad per övning)
+- ✓-knapp per set → startar vilotimer (tid från övningens `restSec`)
 - Passtimer (MM:SS) i headern
 - Avsluta → sparar till träningslogg

 ### 📚 Övningsbibliotek
 **Filtreringssystem — hybrid-UX:**
 - *Nivå 1:* Horisontellt scrollbara utrustningschips (alltid synliga)
-- *Nivå 2:* Filtermodal via Filter-knapp — muskelgrupp + svårighet
+- *Nivå 2:* Filtermodal — muskelgrupp + svårighet, badge visar antal aktiva
 - Fritextsök i realtid
-- Badge på filterknappen visar antal aktiva filter

-**Övningskort visar:** namn, primär muskelgrupp, utrustning, svårighet
 **Detaljvy:** steg-för-steg instruktioner, sekundära muskler
-**Progressionsvy:** SVG-linjediagram, PR-markering, stats (PR / antal pass / senast loggat)
+**Egna övningar:** "+ Ny övning"-FAB, EGET-badge, 🗑 ta bort i detaljvy

 ### 🍎 Kost
-- **Summarykortet** — kcal loggat / kvar, live makrobars
+- **Summarykortet** — kcal loggat / mål, live makroboxar
 - **Måltidssektioner:** Frukost / Lunch / Middag / Mellanmål
-- "+ Lägg till" per måltid → sök-modal med 30 livsmedel
-- Välj livsmedel → ange gram → bekräfta → makron beräknas automatiskt
-- ✕ på loggad post tar bort direkt, makrona uppdateras
+- "+ Lägg till" per måltid → sök-modal med alla livsmedel
+- **Egna livsmedel** — "Skapa eget livsmedel" i sök-modalen, formulär för namn + makron per 100g
+- EGET-badge + 🗑-knapp på egna i listan
+- ✕ på loggad post tar bort direkt

 ### 👤 Profil
-- Avatar med initialer, namn, mål + aktuell vikt
-- Stats: pass totalt · streak · vikt
-- **Achievements-grid** — 10 achievements, låsta 38% opacity
+- **Statistik** — pass totalt · streak · kcal/dag
+- **Progress** — "Logga"-knapp öppnar mätningsmodal (vikt, midja, fettprocent)
+  - SVG-linjegraf över senaste 7 viktmätningarna
+  - Lista på 4 senaste mätningarna
+- **Profil-data** — "Redigera"-knapp: vikt, målvikt, mål, takt, aktivitetsnivå + live makropreview
 - **Tema-väljare** — 5 färgswatches
-- Inställningar: kalorimål, protein, längd, målvikt, vattenmål
-- Återställ app (rensar all data)
+- **Achievements** — 10 achievements, låsta i 40% opacity
+- Återställ all data

 ---

 ## Övningsbibliotek

-75 övningar hämtade från [wger.de](https://wger.de) + egna (engelska namn, kan översättas).
+75 övningar från [wger.de](https://wger.de) + egna.

 ### Fördelning per muskelgrupp

@@ -135,57 +139,33 @@ Daglig översikt på ett ögonkast:
 | Kondition | 7 |
 | Helkropp | 1 |

-### Fördelning per utrustning
+### Övningsstruktur

-| Utrustning | Antal |
-|---|---|
-| Kroppsvikt | 33 |
-| Hantel | 19 |
-| Skivstång | 7 |
-| Maskin | 6 |
-| Kabelmaskin | 5 |
-| Kettlebell | 3 |
-| Motståndsband | 1 |
-| Löpband | 1 |
-
-### Utöka biblioteket
-Varje övning följer detta schema:
-```javascript
+```typescript
 {
   id: 'exercise_id',
   name: 'Exercise Name',
-  muscle: 'Ben',           // primär muskelgrupp
+  muscle: 'Ben',            // primär muskelgrupp
   secondary: 'Rumpa, Core', // sekundära (sträng)
-  equip: 'Kroppsvikt',     // måste matcha EQUIP_CHIPS
+  equip: 'Kroppsvikt',      // måste matcha EQUIP_CHIPS
   icon: '🦵',
-  diff: 'Nybörjare',       // Nybörjare | Medel | Avancerad
-  type: 'Styrka',          // Styrka | Kondition | Stretching
-  isTimer: false,          // true = tidsbaserad (plank, löpband etc.)
+  diff: 'Nybörjare',        // Nybörjare | Medel | Avancerad
+  type: 'Styrka',           // Styrka | Kondition | Rörlighet
+  isTimer: false,           // true = tidsbaserad (plank, löpband etc.)
   instructions: ['Steg 1', 'Steg 2', ...]
 }
 ```

-**wger.de API** kan användas för att hämta fler övningar:
+**wger.de API** för fler övningar:
 ```
 GET https://wger.de/api/v2/exerciseinfo/{id}/?format=json
 ```
-Lägg till `wgerId` på övningen för att koppla mot wger och hämta GIF/video i Fas 2.
-
-### Egna övningar
-
-Användaren kan skapa egna övningar direkt i biblioteket via **"+ Ny övning"**-knappen.
-
-- Fält: namn, emoji-ikon, muskelgrupp, sekundära muskler, utrustning, svårighet, typ, instruktioner (dynamiska steg)
-- Sparas till `fortis_custom_exercises` i storage med `isCustom: true`
-- Visas med orange **EIGEN**-badge och accentbård i listan
-- Detaljvyn visar 🗑 Ta bort-knapp (enbart för egna)
-- Fungerar fullt ut med aktiv träning, progression och schemaläggning

 ---

 ## Kostdatabas

-30 vanliga livsmedel, alla med makrovärden per 100g.
+30 vanliga livsmedel, alla med makrovärden per 100g. Egna livsmedel läggs till direkt i appen.

 | Kategori | Livsmedel |
 |---|---|
@@ -196,18 +176,16 @@ Användaren kan skapa egna övningar direkt i biblioteket via **"+ Ny övning"**
 | Frukt | Banan, Äpple, Blåbär |
 | Nötter & övrigt | Mandlar, Jordnötssmör, Olivolja, Avokado, Proteinpulver, Kidneybönor |

-**Utöka databasen** — lägg till objekt i `FOODS`-arrayen:
-```javascript
-{id: 'mitt_livsmedel', name: 'Mitt Livsmedel', cal: 200, prot: 15, carbs: 20, fat: 5}
+Eget livsmedel-schema:
+```typescript
+{ id: 'cf123', name: 'Hemmagjord granola', cal: 420, prot: 9, carbs: 58, fat: 16, isCustom: true }
 ```

-I Fas 4 ersätts/kompletteras detta med [Open Food Facts API](https://world.openfoodfacts.org/api/v0/product/{barcode}.json) via barcodeläsare.
-
 ---

 ## Tema-system

-5 inbyggda teman, väljs i Profil-fliken. Alla CSS-variabler byts atomärt.
+5 inbyggda teman, väljs i Profil-fliken.

 | Tema | Bakgrund | Accent | Karaktär |
 |---|---|---|---|
@@ -217,51 +195,32 @@ I Fas 4 ersätts/kompletteras detta med [Open Food Facts API](https://world.open
 | **Forest** | Naturgrön `#F0F4F0` | Grön `#2D9E5F` | Lugnt, naturligt |
 | **Stone** | Cool grå `#F5F5F4` | Indigo `#6366F1` | Modernt, minimalt |

-### CSS-variabler
-```css
---accent        /* CTA, aktiva element */
---accent2       /* Sekundär accent, kolhydrater */
---accent-light  /* Tint av accent för bakgrunder */
---bg            /* Sidans bakgrund */
---card          /* Kortbakgrund */
---text          /* Primär text */
---muted         /* Sekundär text, labels */
---border        /* Kortlinjer, separatorer */
---nav           /* Bottom navigation */
---header        /* Mörk header (hem, bibliotek etc.) */
---header-text   /* Text i mörk header */
---outer         /* Bakgrund utanför app-ramen */
-```
-
-### React Native-migration
-Temat ska vara en **frikopplad modul** — appen konsumerar, äger inte:
+Tema-systemet är frikopplat:
 ```
-src/constants/theme.ts     ← THEMES-objekt + typdefinitioner
-src/store/useThemeStore.ts ← Zustand: activeTheme, setTheme(), persist
-src/hooks/useTheme.ts      ← hook: returnerar aktiva värden
+src/constants/theme.ts     ← THEMES-objekt + Theme-interface
+src/store/useThemeStore.ts ← Zustand: themeId, setTheme(), persist
+src/hooks/useTheme.ts      ← hook: returnerar aktiva temavärden
 ```
-Inga färgvärden hårdkodas i komponenter. Nya teman läggs bara till i `theme.ts`.
+Inga färgvärden hårdkodas i komponenter. Nya teman läggs enbart till i `theme.ts`.

 ---

 ## Achievements

-10 achievements som låses upp automatiskt efter avslutat pass.
+10 achievements som låses upp baserat på träningshistorik.

 | Achievement | Ikon | Villkor |
 |---|---|---|
-| Första passet | 🏋️ | Slutför 1 pass |
-| 10 pass | 💪 | Slutför 10 pass totalt |
-| 50 pass | 🏆 | Slutför 50 pass totalt |
-| 100 pass | 🥇 | Slutför 100 pass totalt |
-| 3 i rad | 🔥 | 3 dagars träningsstreak |
-| Vecka på raken | 🔥 | 7 dagars träningsstreak |
-| Månadsstreak | 🔥 | 30 dagars träningsstreak |
-| Tidig fågel | 🌅 | Logga ett pass före 08:00 |
-| Allrounder | 🌟 | Träna 5 olika muskelgrupper |
-| Uthållighet | ⏱️ | Slutför ett pass längre än 60 min |
-
-Toast-notis slidar in uppifrån vid upplåsning. Visas som grid i Profil med låsta achievements i 38% opacity.
+| Första steget | 🏆 | Slutför 1 pass |
+| På rulle | 🔥 | 3 dagars streak |
+| Veckovinnare | 🔥🔥 | 7 dagars streak |
+| Hängiven | 💪 | 5 pass totalt |
+| Maskin | ⚡ | 10 pass totalt |
+| Elit | 👑 | 20 pass totalt |
+| Morgonfågel | 🌅 | Tränat innan kl 08:00 |
+| Maraton | 🏅 | 60+ min i ett pass |
+| Helkropp | 🎯 | Tränat 5+ muskelgrupper |
+| Personbästa | 📈 | Sätt ett personligt rekord |

 ---

@@ -271,33 +230,15 @@ Toast-notis slidar in uppifrån vid upplåsning. Visas som grid i Profil med lå

 | Nyckel | Typ | Innehåll |
 |---|---|---|
-| `fortis_profile` | `Profile` | Profildata + makromål |
-| `fortis_macrogoals` | `MacroGoals` | Kalorier, protein, kolhydrater, fett |
+| `fortis_profile` | `Profile` | Profildata inkl. makromål |
 | `fortis_workouts` | `WorkoutSchedule[]` | Schemalagda pass |
-| `fortis_workoutlog` | `WorkoutLogEntry[]` | Loggade genomförda pass |
+| `fortis_workoutlog` | `WorkoutLogEntry[]` | Genomförda pass |
 | `fortis_foodlog` | `FoodLogEntry[]` | Loggad mat per dag |
-| `fortis_water_YYYY-MM-DD` | `number` | Vattenintag i ml per datum |
-| `fortis_measurements` | `MeasurementEntry[]` | Vikt, midjemått, fettprocent |
-| `fortis_achievements` | `string[]` | Upplåsta achievement-IDs |
+| `fortis_water_YYYY-MM-DD` | `number` | Vattenintag i ml |
+| `fortis_measurements` | `MeasurementEntry[]` | Vikt, midja, fettprocent |
+| `fortis_custom_foods` | `FoodItem[]` | Egna livsmedel |
+| `fortis_custom_exercises` | `Exercise[]` | Egna övningar |
 | `fortis_theme` | `string` | Aktivt tema-ID |
-| `fortis_custom_foods` | `FoodItem[]` | Användarens egna livsmedel |
-| `fortis_custom_exercises` | `Exercise[]` | Användarens egna övningar (`isCustom: true`) |
-
-### Export-format
-```json
-{
-  "exportVersion": "1.0",
-  "exportedAt": "2026-04-29T12:00:00Z",
-  "profile": {},
-  "workoutSchedules": [],
-  "workoutLog": [],
-  "foodLog": [],
-  "measurements": [],
-  "waterLog": [],
-  "customFoods": []
-}
-```
-Export triggas via Profil → Exportera data → JSON-fil.

 ---

@@ -313,6 +254,7 @@ interface Profile {
   weightKg: number
   goalWeightKg: number
   goal: 'lose' | 'maintain' | 'gain'
+  pace: 'slow' | 'moderate' | 'fast'
   activityLevel: 'sedentary' | 'light' | 'moderate' | 'very_active'
   macroGoals: MacroGoals
   createdAt: string
@@ -329,61 +271,60 @@ interface MacroGoals {
 interface Exercise {
   id: string
   name: string
-  category: string
-  muscleGroups: string[]
-  secondaryMuscles: string[]
-  equipment: string[]
-  difficulty: 'beginner' | 'intermediate' | 'advanced'
-  type: 'strength' | 'cardio' | 'flexibility'
+  muscle: string
+  secondary?: string
+  equip: string
+  icon: string
+  diff: 'Nybörjare' | 'Medel' | 'Avancerad'
+  type: 'Styrka' | 'Kondition' | 'Rörlighet'
+  isTimer?: boolean
   instructions: string[]
-  gifUrl?: string
-  wgerId?: number       // kopplar till wger.de API för GIF/video
-  isCustom: boolean
+  wgerId?: number
+  isCustom?: boolean
 }

 interface WorkoutSchedule {
   id: string
   name: string
-  dayOfWeek?: number    // 0=mån … 6=sön
-  repeatEveryXDays?: number
+  day: number         // 0=mån … 6=sön
   exercises: ScheduledExercise[]
-  estimatedMinutes: number
-  muscleGroups: string[]
-  createdAt: string
 }

 interface ScheduledExercise {
   exerciseId: string
   sets: number
   reps?: number
-  durationSeconds?: number
-  targetWeightKg?: number
-  restSeconds: number
-  notes?: string
+  durationSec?: number
+  weight?: number
+  restSec: number
 }

 interface WorkoutLogEntry {
   id: string
-  scheduleId?: string
-  date: string          // ISO YYYY-MM-DD
+  workoutId: string
+  date: string
   startedAt: string
   endedAt?: string
-  durationMinutes: number
-  exercises: LoggedExercise[]
-  notes?: string
+  durationMin: number
+  exercises: LoggedSet[][]
   completedManually: boolean
 }

-interface LoggedExercise {
-  exerciseId: string
-  sets: LoggedSet[]
-}
-
 interface LoggedSet {
   reps?: number
-  weightKg?: number
-  durationSeconds?: number
-  completed: boolean
+  weight?: number
+  durationSec?: number
+  done: boolean
+}
+
+interface FoodItem {
+  id: string
+  name: string
+  cal: number
+  prot: number
+  carbs: number
+  fat: number
+  isCustom?: boolean
 }

 interface FoodLogEntry {
@@ -393,20 +334,10 @@ interface FoodLogEntry {
   foodId: string
   foodName: string
   amountG: number
-  calories: number
-  proteinG: number
-  carbsG: number
-  fatG: number
-  barcode?: string
-}
-
-interface FoodItem {
-  id: string
-  name: string
-  brand?: string
-  barcode?: string
-  per100g: { calories: number; proteinG: number; carbsG: number; fatG: number }
-  isCustom: boolean
+  cal: number
+  prot: number
+  carbs: number
+  fat: number
 }

 interface MeasurementEntry {
@@ -422,79 +353,46 @@ interface WaterLogEntry {
   id: string
   date: string
   totalMl: number
-  entries: { time: string; ml: number }[]
 }
 ```

 ---

-## Arkitektur (React Native)
+## Arkitektur

 ```
-src/
-├── app/                         # Expo Router (file-based routing)
-│   ├── _layout.tsx              # Root layout + ThemeProvider + bottom tabs
+fortis/
+├── app/
+│   ├── _layout.tsx              # Root layout, store-laddning, route guard
 │   ├── (tabs)/
-│   │   ├── index.tsx            # Dashboard / Hem
+│   │   ├── _layout.tsx          # Tab bar med 5 flikar (Lucide-ikoner)
+│   │   ├── index.tsx            # Dashboard
 │   │   ├── schedule.tsx         # Träningsschema
 │   │   ├── library.tsx          # Övningsbibliotek
 │   │   ├── nutrition.tsx        # Kostlogg
-│   │   └── profile.tsx          # Profil & inställningar
+│   │   └── profile.tsx          # Profil & progress
 │   ├── onboarding/
-│   │   └── index.tsx            # Onboarding-wizard (5 steg)
+│   │   └── index.tsx            # Splash-animation + 5-stegs wizard
 │   └── workout/
-│       └── [id].tsx             # Aktiv träningsvy
-│
-├── components/
-│   ├── shared/
-│   │   ├── ProgressRing.tsx     # SVG-kaloriering
-│   │   ├── MacroBar.tsx         # P/K/F progress-bar
-│   │   ├── WorkoutTimer.tsx     # Passtimer
-│   │   ├── RestTimer.tsx        # Vilotimer-overlay
-│   │   ├── ProgressionChart.tsx # Linjediagram (Victory Native)
-│   │   ├── StreakBadge.tsx      # Streak-display
-│   │   └── EquipChip.tsx        # Utrustningsfilter-chip
-│   ├── home/
-│   │   ├── CalorieCard.tsx
-│   │   ├── WaterTracker.tsx
-│   │   └── TodayWorkoutCard.tsx
-│   ├── schedule/
-│   │   ├── WeekStrip.tsx
-│   │   ├── WorkoutBlock.tsx
-│   │   └── AddWorkoutModal.tsx
-│   ├── library/
-│   │   ├── ExerciseCard.tsx
-│   │   ├── ExerciseDetailModal.tsx
-│   │   └── FilterModal.tsx
-│   ├── nutrition/
-│   │   ├── MealSection.tsx
-│   │   ├── AddFoodModal.tsx
-│   │   ├── BarcodeScanner.tsx   # Expo Camera
-│   │   └── MacroSummary.tsx
-│   └── profile/
-│       ├── WeightChart.tsx
-│       └── Achievements.tsx
-│
-├── store/                       # Zustand stores
-│   ├── useProfileStore.ts
-│   ├── useWorkoutStore.ts
-│   ├── useNutritionStore.ts
-│   ├── useMeasurementStore.ts
-│   └── useThemeStore.ts         # activeTheme, setTheme(), persist
+│       └── [id].tsx             # Aktiv träningsvy (fullscreen modal)-├── hooks/
-│   └── useTheme.ts              # returnerar aktiva temavärden
-│
-├── lib/
-│   ├── storage.ts               # AsyncStorage wrapper
-│   ├── macroCalc.ts             # Mifflin-St Jeor TDEE
-│   ├── exportData.ts            # JSON-export
-│   └── exerciseData.ts          # 75+ övningar (utbyggbar)
-│
-└── constants/
-    ├── colors.ts
-    ├── exercises.ts
-    └── theme.ts                 # THEMES-objekt + typdefinitioner
+└── src/
+    ├── constants/
+    │   ├── types.ts             # Alla TypeScript-gränssnitt
+    │   ├── exercises.ts         # 75 övningar
+    │   ├── foods.ts             # 30 livsmedel
+    │   └── theme.ts             # 5 teman + Theme-interface
+    ├── store/
+    │   ├── useProfileStore.ts   # Zustand: profile, macroGoals, hasLoaded
+    │   ├── useWorkoutStore.ts   # Zustand: workouts, workoutLog, calcStreak()
+    │   ├── useNutritionStore.ts # Zustand: foodLog, waterToday, customFoods
+    │   ├── useMeasurementsStore.ts # Zustand: measurements
+    │   └── useThemeStore.ts     # Zustand: themeId, persist
+    ├── hooks/
+    │   └── useTheme.ts          # Returnerar aktiva temavärden
+    └── lib/
+        ├── storage.ts           # AsyncStorage wrapper + STORAGE_KEYS
+        └── macroCalc.ts         # calcTDEE(), calcMacros(), PACE_LABELS
 ```

 ---
@@ -504,41 +402,41 @@ src/
 | Fas | Innehåll | Status |
 |---|---|---|
 | **Fas 1** | Splash, Onboarding, Dashboard, Schema, Aktiv träning, Bibliotek | ✅ Klar |
-| **Fas 2** | Progressionsgraf, Achievements/streaks, Utbyggt bibliotek | 🔄 Pågår |
-| **Fas 3** | Fullständig kostlogg, Makrospårning, Matdatabas, Vattenlogg | 🔄 Pågår |
-| **Fas 4** | Barcodeläsare, Open Food Facts API, Notifikationer, Invägningsdag | 🔜 |
-| **Fas 5** | React Native-migration, offline-stöd, push-notiser, App Store | 🔜 |
+| **Fas 2** | Progressionsgraf, Achievements/streaks, Utbyggt bibliotek (75 övningar) | ✅ Klar |
+| **Fas 3** | Kostlogg, Makrospårning, Matdatabas, Vattenlogg, Egna livsmedel | ✅ Klar |
+| **Fas 4** | Profil-redigering, Viktgraf, Mätningar, Egna livsmedel | ✅ Klar |
+| **Fas 5** | React Native + Expo migration, AsyncStorage, offline-stöd | ✅ Klar |
+| **Fas 6** | Barcodeläsare, Open Food Facts API, Push-notifikationer | 🔜 |

 ### Närmast kvar
-- [ ] Barcodeläsare (Expo Camera) — ersätter sök-modal med skannad streckkod
+- [ ] Barcodeläsare (Expo Camera) — ersätter sökning med skanning
 - [ ] Open Food Facts API-integration
 - [ ] Push-notifikationer (pass-påminnelse, vattenintag, invägningsdag)
-- [ ] Egna livsmedel — `isCustom: true`, persist i `fortis_custom_foods`
-- [x] Egna övningar — `isCustom: true`, persist i `fortis_custom_exercises`
-- [ ] `wgerId` på alla 75 övningar vid RN-migration (GIF/video via wger API)
+- [ ] Progressionsgraf per övning i RN
+- [ ] Export av all data till JSON

 ---

 ## Tech stack

-| Komponent | Prototyp | Produktion (RN) |
-|---|---|---|
-| UI | HTML + CSS | React Native + Expo SDK 51+ |
-| Routing | Tab-state i JS | Expo Router (file-based) |
-| State | Globalt `ST`-objekt | Zustand |
-| Persistens | `window.storage` | AsyncStorage + Expo SQLite |
-| Charts | SVG (hand-built) | Victory Native |
-| Animationer | CSS transitions | React Native Reanimated 3 |
-| Barcode | — (stub) | Expo Camera + expo-barcode-scanner |
-| Matdatabas | 30 inbyggda livsmedel | Open Food Facts API |
-| Icons | Emoji | Expo Vector Icons / Lucide RN |
-| Notiser | — | Expo Notifications |
+| Komponent | Val |
+|---|---|
+| Framework | React Native + Expo SDK 54 |
+| Routing | Expo Router v6 (file-based) |
+| State | Zustand |
+| Persistens | `@react-native-async-storage/async-storage` |
+| Charts | react-native-svg (hand-byggda SVG-grafer) |
+| Animationer | React Native Animated API |
+| Icons | lucide-react-native |
+| Barcode | — (ej implementerat, kräver Expo Camera) |
+| Notiser | — (ej implementerat, kräver Expo Notifications) |

 ### Kaloriformel
+
 TDEE beräknas med **Mifflin-St Jeor**:

 ```
-Män:   BMR = 10×vikt + 6.25×längd − 5×ålder + 5
+Män:     BMR = 10×vikt + 6.25×längd − 5×ålder + 5
 Kvinnor: BMR = 10×vikt + 6.25×längd − 5×ålder − 161

 TDEE = BMR × aktivitetsmultiplikator
@@ -547,12 +445,20 @@ TDEE = BMR × aktivitetsmultiplikator
   moderate:    × 1.55
   very_active: × 1.725

-Mål-justering:
-  lose:     TDEE − 500 kcal
-  maintain: TDEE
-  gain:     TDEE + 300 kcal
+Takt-justering:
+  lose:
+    slow:     TDEE − 250 kcal  (~0,25 kg/vecka)
+    moderate: TDEE − 500 kcal  (~0,5 kg/vecka)
+    fast:     TDEE − 750 kcal  (~0,75 kg/vecka)
+  gain:
+    slow:     TDEE + 200 kcal  (lean bulk, ~0,2 kg/vecka)
+    moderate: TDEE + 350 kcal  (~0,35 kg/vecka)
+    fast:     TDEE + 500 kcal  (~0,5 kg/vecka)
+  maintain:   TDEE (ingen justering)
+
+Minimum: 1 200 kcal/dag
 ```

 ---

-*Senast uppdaterad: 2026-04-29*
+*Senast uppdaterad: 2026-05-01*
diff --git a/Träningsapp_Planering.md b/Träningsapp_Planering.md
index 523543c..5cc7e67 100644
--- a/Träningsapp_Planering.md
+++ b/Träningsapp_Planering.md
@@ -1,7 +1,7 @@
 # Träningsapp — Planeringsspecifikation
-**Version:** 1.1
-**Status:** Planeringsfas
-**Målplattform:** JSX (prototyp) → React Native / Expo (produktion)
+**Version:** 1.2
+**Status:** Fas 1–5 klara — React Native-appen är live
+**Målplattform:** React Native + Expo SDK 54 (migration genomförd)

 ---

@@ -482,13 +482,14 @@ App

 ## 7. Fas-indelning

-| Fas | Innehåll | Prioritet |
-|-----|----------|-----------|
-| **Fas 1** | Splash + Onboarding, Dashboard, Övningslogg, Träningsschema (grundläggande) | Hög |
-| **Fas 2** | Övningsbibliotek, Progressionsgraf, Streak/Achievements | Hög |
-| **Fas 3** | Kostlogg, Kaloriräknare, Makrospårning, Vattenintag | Medel |
-| **Fas 4** | Barcodeläsare, Open Food Facts-integration, Notifikationer | Lägre |
-| **Fas 5** | Export till React Native, offline-stöd, push-notiser | Migration |
+| Fas | Innehåll | Status |
+|-----|----------|--------|
+| **Fas 1** | Splash + Onboarding, Dashboard, Övningslogg, Träningsschema | ✅ Klar |
+| **Fas 2** | Övningsbibliotek (75 övningar), Achievements, Egna övningar | ✅ Klar |
+| **Fas 3** | Kostlogg, Makrospårning, Matdatabas, Vattenintag, Egna livsmedel | ✅ Klar |
+| **Fas 4** | Profil-redigering, Viktgraf, Mätningslogg, Takt-val | ✅ Klar |
+| **Fas 5** | React Native + Expo migration, AsyncStorage, offline-stöd | ✅ Klar |
+| **Fas 6** | Barcodeläsare, Open Food Facts API, Push-notifikationer | 🔜 Näst |

 ---

@@ -507,4 +508,4 @@ App

 ---

-*Dokument uppdaterat: 2025-04-29*
+*Dokument uppdaterat: 2026-05-01*
diff --git a/fortis/app/(tabs)/index.tsx b/fortis/app/(tabs)/index.tsx
index 3c4936e..103aaec 100644
--- a/fortis/app/(tabs)/index.tsx
+++ b/fortis/app/(tabs)/index.tsx
@@ -25,8 +25,7 @@ function CalorieRing({ eaten, goal, accent }: { eaten: number; goal: number; acc
         fill="none"
         strokeDasharray={`${dash} ${circ}`}
         strokeLinecap="round"
-        rotation="-90"
-        origin="65,65"
+        transform="rotate(-90 65 65)"
       />
     </Svg>
   )
@@ -103,7 +102,7 @@ export default function HomeScreen() {
         )}
       </View>

-      <ScrollView style={s.scroll} showsVerticalScrollIndicator={false}>
+      <ScrollView style={s.scroll} contentContainerStyle={{ paddingBottom: 32 }} showsVerticalScrollIndicator={false}>
         {/* Calorie card */}
         <View style={s.card}>
           <Text style={s.sectionTitle}>Kalorier</Text>
diff --git a/fortis/app/(tabs)/library.tsx b/fortis/app/(tabs)/library.tsx
index 5a95ec0..23e55ce 100644
--- a/fortis/app/(tabs)/library.tsx
+++ b/fortis/app/(tabs)/library.tsx
@@ -17,8 +17,8 @@ function ExerciseDetailModal({ ex, onClose, onDelete }: { ex: Exercise; onClose:
   return (
     <Modal visible transparent animationType="slide" onRequestClose={onClose}>
       <TouchableOpacity style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }} activeOpacity={1} onPress={onClose}>
-        <TouchableOpacity activeOpacity={1}>
-          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, paddingBottom: 40, maxHeight: '90%' }}>
+        <View>
+          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, maxHeight: '90%' }} contentContainerStyle={{ paddingBottom: 40 }}>
             <View style={{ width: 40, height: 4, backgroundColor: t.border, borderRadius: 2, alignSelf: 'center', marginBottom: 16 }} />
             <Text style={{ color: t.text, fontSize: 20, fontWeight: '800', marginBottom: 12 }}>{ex.name}</Text>
             <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap', marginBottom: 16 }}>
@@ -49,7 +49,7 @@ function ExerciseDetailModal({ ex, onClose, onDelete }: { ex: Exercise; onClose:
               <Text style={{ color: t.text, fontSize: 15, fontWeight: '700' }}>Stäng</Text>
             </TouchableOpacity>
           </ScrollView>
-        </TouchableOpacity>
+        </View>
       </TouchableOpacity>
     </Modal>
   )
@@ -90,8 +90,8 @@ function CreateExerciseModal({ onClose, onSave }: { onClose: () => void; onSave:
   return (
     <Modal visible transparent animationType="slide" onRequestClose={onClose}>
       <TouchableOpacity style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }} activeOpacity={1} onPress={onClose}>
-        <TouchableOpacity activeOpacity={1}>
-          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, paddingBottom: 40, maxHeight: '95%' }} keyboardShouldPersistTaps="handled">
+        <View>
+          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, maxHeight: '95%' }} contentContainerStyle={{ paddingBottom: 40 }} keyboardShouldPersistTaps="handled">
             <View style={{ width: 40, height: 4, backgroundColor: t.border, borderRadius: 2, alignSelf: 'center', marginBottom: 16 }} />
             <Text style={{ color: t.text, fontSize: 20, fontWeight: '800', marginBottom: 16 }}>Skapa övning</Text>
             <Text style={labelStyle}>Namn</Text>
@@ -127,7 +127,7 @@ function CreateExerciseModal({ onClose, onSave }: { onClose: () => void; onSave:
               <TouchableOpacity style={{ flex: 2, backgroundColor: t.accent, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }} onPress={save}><Text style={{ color: '#FFF', fontWeight: '700', fontSize: 15 }}>Spara övning</Text></TouchableOpacity>
             </View>
           </ScrollView>
-        </TouchableOpacity>
+        </View>
       </TouchableOpacity>
     </Modal>
   )
diff --git a/fortis/app/(tabs)/nutrition.tsx b/fortis/app/(tabs)/nutrition.tsx
index 1511e6a..a23e1e4 100644
--- a/fortis/app/(tabs)/nutrition.tsx
+++ b/fortis/app/(tabs)/nutrition.tsx
@@ -1,12 +1,12 @@
 import { useState } from 'react'
 import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Modal, TextInput, FlatList } from 'react-native'
 import { SafeAreaView } from 'react-native-safe-area-context'
-import { Plus, X } from 'lucide-react-native'
+import { Plus, X, ChevronLeft } from 'lucide-react-native'
 import { useTheme } from '../../src/hooks/useTheme'
 import { useProfileStore } from '../../src/store/useProfileStore'
 import { useNutritionStore, todayFoodLog } from '../../src/store/useNutritionStore'
 import { FOODS } from '../../src/constants/foods'
-import { FoodLogEntry } from '../../src/constants/types'
+import { FoodLogEntry, FoodItem } from '../../src/constants/types'
 import { today } from '../../src/lib/macroCalc'

 const MEALS: { key: FoodLogEntry['meal']; label: string; icon: string }[] = [
@@ -20,9 +20,18 @@ function AddFoodModal({ meal, onClose }: { meal: FoodLogEntry['meal']; onClose:
   const theme = useTheme()
   const addFoodEntry = useNutritionStore((s) => s.addFoodEntry)
   const customFoods = useNutritionStore((s) => s.customFoods)
+  const addCustomFood = useNutritionStore((s) => s.addCustomFood)
+  const removeCustomFood = useNutritionStore((s) => s.removeCustomFood)
+
+  const [mode, setMode] = useState<'list' | 'create'>('list')
   const [search, setSearch] = useState('')
-  const [selected, setSelected] = useState<typeof FOODS[0] | null>(null)
+  const [selected, setSelected] = useState<FoodItem | null>(null)
   const [amount, setAmount] = useState('100')
+  const [cfName, setCfName] = useState('')
+  const [cfCal, setCfCal] = useState('')
+  const [cfProt, setCfProt] = useState('')
+  const [cfCarbs, setCfCarbs] = useState('')
+  const [cfFat, setCfFat] = useState('')

   const allFoods = [...FOODS, ...customFoods]
   const filtered = allFoods.filter((f) => !search || f.name.toLowerCase().includes(search.toLowerCase()))
@@ -53,18 +62,33 @@ function AddFoodModal({ meal, onClose }: { meal: FoodLogEntry['meal']; onClose:
     onClose()
   }

+  const saveCustomFood = async () => {
+    if (!cfName.trim() || !cfCal) return
+    await addCustomFood({
+      id: `cf${Date.now()}`,
+      name: cfName.trim(),
+      cal: Number(cfCal) || 0,
+      prot: Number(cfProt) || 0,
+      carbs: Number(cfCarbs) || 0,
+      fat: Number(cfFat) || 0,
+      isCustom: true,
+    })
+    setMode('list')
+    setCfName(''); setCfCal(''); setCfProt(''); setCfCarbs(''); setCfFat('')
+  }
+
   const s = StyleSheet.create({
     overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
     sheet: { backgroundColor: theme.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 20, paddingBottom: 40, maxHeight: '90%' },
     handle: { width: 40, height: 4, backgroundColor: theme.border, borderRadius: 2, alignSelf: 'center', marginBottom: 16 },
     title: { color: theme.text, fontSize: 18, fontWeight: '800', marginBottom: 12 },
     searchWrap: { flexDirection: 'row', alignItems: 'center', backgroundColor: theme.bg, borderRadius: 10, paddingHorizontal: 12, height: 42, borderWidth: 1, borderColor: theme.border, marginBottom: 12 },
-    searchInput: { flex: 1, color: theme.text, fontSize: 15, marginLeft: 6 },
-    foodItem: { paddingVertical: 10, paddingHorizontal: 4, borderBottomWidth: 1, borderBottomColor: theme.border, flexDirection: 'row' as const, justifyContent: 'space-between' as const },
+    searchInput: { flex: 1, color: theme.text, fontSize: 15 },
+    foodItem: { paddingVertical: 10, paddingHorizontal: 4, borderBottomWidth: 1, borderBottomColor: theme.border, flexDirection: 'row' as const, alignItems: 'center' as const },
     foodItemActive: { backgroundColor: theme.accentLight },
     foodName: { color: theme.text, fontSize: 14, fontWeight: '500' as const },
     foodNameActive: { color: theme.accent, fontWeight: '700' as const },
-    foodMeta: { color: theme.muted, fontSize: 12 },
+    foodMeta: { color: theme.muted, fontSize: 12, marginRight: 4 },
     preview: { backgroundColor: theme.bg, borderRadius: 12, padding: 14, marginTop: 12, gap: 8 },
     previewTitle: { color: theme.text, fontSize: 14, fontWeight: '700', marginBottom: 4 },
     previewRow: { flexDirection: 'row' as const, justifyContent: 'space-between' as const },
@@ -75,6 +99,8 @@ function AddFoodModal({ meal, onClose }: { meal: FoodLogEntry['meal']; onClose:
     amountInput: { backgroundColor: theme.bg, borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, color: theme.text, fontSize: 15, width: 80, borderWidth: 1, borderColor: theme.border, textAlign: 'center' as const },
     confirmBtn: { backgroundColor: theme.accent, borderRadius: 12, paddingVertical: 14, alignItems: 'center' as const, marginTop: 12 },
     confirmBtnText: { color: '#FFF', fontSize: 15, fontWeight: '700' },
+    fieldLabel: { color: theme.muted, fontSize: 11, fontWeight: '700' as const, letterSpacing: 0.8, textTransform: 'uppercase' as const, marginBottom: 6 },
+    fieldInput: { backgroundColor: theme.bg, borderRadius: 10, paddingHorizontal: 12, paddingVertical: 10, color: theme.text, fontSize: 15, borderWidth: 1, borderColor: theme.border, marginBottom: 12 },
   })

   return (
@@ -83,40 +109,85 @@ function AddFoodModal({ meal, onClose }: { meal: FoodLogEntry['meal']; onClose:
         <TouchableOpacity activeOpacity={1}>
           <View style={s.sheet}>
             <View style={s.handle} />
-            <Text style={s.title}>Lägg till mat</Text>
-            <View style={s.searchWrap}>
-              <TextInput style={s.searchInput} value={search} onChangeText={setSearch} placeholder="Sök livsmedel..." placeholderTextColor={theme.muted} />
-            </View>
-            <FlatList
-              data={filtered}
-              keyExtractor={(f) => f.id}
-              style={{ maxHeight: 200 }}
-              renderItem={({ item: f }) => (
-                <TouchableOpacity style={[s.foodItem, selected?.id === f.id && s.foodItemActive]} onPress={() => setSelected(f)}>
-                  <Text style={[s.foodName, selected?.id === f.id && s.foodNameActive]}>{f.name}</Text>
-                  <Text style={s.foodMeta}>{f.cal} kcal/100g</Text>
+            {mode === 'create' ? (
+              <ScrollView keyboardShouldPersistTaps="handled" showsVerticalScrollIndicator={false}>
+                <TouchableOpacity onPress={() => setMode('list')} style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 14 }}>
+                  <ChevronLeft size={18} color={theme.accent} />
+                  <Text style={{ color: theme.accent, fontSize: 14, fontWeight: '600' }}>Tillbaka</Text>
                 </TouchableOpacity>
-              )}
-            />
-            {selected && (
-              <View style={s.preview}>
-                <Text style={s.previewTitle}>{selected.name}</Text>
-                <View style={s.amountRow}>
-                  <Text style={s.amountLabel}>Mängd (g):</Text>
-                  <TextInput style={s.amountInput} value={amount} onChangeText={setAmount} keyboardType="numeric" />
+                <Text style={s.title}>Nytt livsmedel</Text>
+                <Text style={s.fieldLabel}>Namn</Text>
+                <TextInput style={s.fieldInput} value={cfName} onChangeText={setCfName} placeholder="Ex. Hemmagjord granola" placeholderTextColor={theme.muted} />
+                <Text style={s.fieldLabel}>Kalorier (per 100g)</Text>
+                <TextInput style={s.fieldInput} value={cfCal} onChangeText={setCfCal} keyboardType="decimal-pad" placeholder="kcal" placeholderTextColor={theme.muted} />
+                <View style={{ flexDirection: 'row', gap: 10 }}>
+                  {[['Protein (g)', cfProt, setCfProt], ['Kolhydrater (g)', cfCarbs, setCfCarbs], ['Fett (g)', cfFat, setCfFat]].map(([label, val, setter]) => (
+                    <View key={label as string} style={{ flex: 1 }}>
+                      <Text style={s.fieldLabel}>{label as string}</Text>
+                      <TextInput style={s.fieldInput} value={val as string} onChangeText={setter as (v: string) => void} keyboardType="decimal-pad" placeholder="g" placeholderTextColor={theme.muted} />
+                    </View>
+                  ))}
                 </View>
-                {macros && (
-                  <>
-                    <View style={s.previewRow}><Text style={s.previewLabel}>Kalorier</Text><Text style={s.previewVal}>{macros.cal} kcal</Text></View>
-                    <View style={s.previewRow}><Text style={s.previewLabel}>Protein</Text><Text style={s.previewVal}>{macros.prot}g</Text></View>
-                    <View style={s.previewRow}><Text style={s.previewLabel}>Kolhydrater</Text><Text style={s.previewVal}>{macros.carbs}g</Text></View>
-                    <View style={s.previewRow}><Text style={s.previewLabel}>Fett</Text><Text style={s.previewVal}>{macros.fat}g</Text></View>
-                  </>
-                )}
-                <TouchableOpacity style={s.confirmBtn} onPress={confirm}>
-                  <Text style={s.confirmBtnText}>Lägg till</Text>
+                <TouchableOpacity style={s.confirmBtn} onPress={saveCustomFood}>
+                  <Text style={s.confirmBtnText}>Spara livsmedel</Text>
                 </TouchableOpacity>
-              </View>
+              </ScrollView>
+            ) : (
+              <>
+                <Text style={s.title}>Lägg till mat</Text>
+                <View style={s.searchWrap}>
+                  <TextInput style={s.searchInput} value={search} onChangeText={setSearch} placeholder="Sök livsmedel..." placeholderTextColor={theme.muted} />
+                </View>
+                <FlatList
+                  data={filtered}
+                  keyExtractor={(f) => f.id}
+                  style={{ maxHeight: 200 }}
+                  renderItem={({ item: f }) => (
+                    <TouchableOpacity style={[s.foodItem, selected?.id === f.id && s.foodItemActive]} onPress={() => setSelected(f)}>
+                      <View style={{ flex: 1 }}>
+                        <Text style={[s.foodName, selected?.id === f.id && s.foodNameActive]}>{f.name}</Text>
+                        {f.isCustom && <Text style={{ color: theme.muted, fontSize: 10, fontWeight: '700' }}>EGET</Text>}
+                      </View>
+                      <Text style={s.foodMeta}>{f.cal} kcal/100g</Text>
+                      {f.isCustom && (
+                        <TouchableOpacity
+                          onPress={() => { removeCustomFood(f.id); if (selected?.id === f.id) setSelected(null) }}
+                          style={{ padding: 6 }}
+                          hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
+                        >
+                          <X size={14} color={theme.muted} />
+                        </TouchableOpacity>
+                      )}
+                    </TouchableOpacity>
+                  )}
+                  ListFooterComponent={
+                    <TouchableOpacity onPress={() => setMode('create')} style={{ flexDirection: 'row', alignItems: 'center', gap: 6, paddingVertical: 12, paddingHorizontal: 4 }}>
+                      <Plus size={15} color={theme.accent} />
+                      <Text style={{ color: theme.accent, fontSize: 14, fontWeight: '600' }}>Skapa eget livsmedel</Text>
+                    </TouchableOpacity>
+                  }
+                />
+                {selected && (
+                  <View style={s.preview}>
+                    <Text style={s.previewTitle}>{selected.name}</Text>
+                    <View style={s.amountRow}>
+                      <Text style={s.amountLabel}>Mängd (g):</Text>
+                      <TextInput style={s.amountInput} value={amount} onChangeText={setAmount} keyboardType="numeric" />
+                    </View>
+                    {macros && (
+                      <>
+                        <View style={s.previewRow}><Text style={s.previewLabel}>Kalorier</Text><Text style={s.previewVal}>{macros.cal} kcal</Text></View>
+                        <View style={s.previewRow}><Text style={s.previewLabel}>Protein</Text><Text style={s.previewVal}>{macros.prot}g</Text></View>
+                        <View style={s.previewRow}><Text style={s.previewLabel}>Kolhydrater</Text><Text style={s.previewVal}>{macros.carbs}g</Text></View>
+                        <View style={s.previewRow}><Text style={s.previewLabel}>Fett</Text><Text style={s.previewVal}>{macros.fat}g</Text></View>
+                      </>
+                    )}
+                    <TouchableOpacity style={s.confirmBtn} onPress={confirm}>
+                      <Text style={s.confirmBtnText}>Lägg till</Text>
+                    </TouchableOpacity>
+                  </View>
+                )}
+              </>
             )}
           </View>
         </TouchableOpacity>
@@ -167,7 +238,7 @@ export default function NutritionScreen() {
       <View style={s.header}>
         <Text style={s.title}>Kost</Text>
       </View>
-      <ScrollView style={s.scroll} showsVerticalScrollIndicator={false}>
+      <ScrollView style={s.scroll} contentContainerStyle={{ paddingBottom: 32 }} showsVerticalScrollIndicator={false}>
         <View style={s.summaryCard}>
           <Text style={s.summaryTitle}>Idag</Text>
           <View style={s.calRow}>
diff --git a/fortis/app/(tabs)/profile.tsx b/fortis/app/(tabs)/profile.tsx
index 709a076..2dabd93 100644
--- a/fortis/app/(tabs)/profile.tsx
+++ b/fortis/app/(tabs)/profile.tsx
@@ -1,10 +1,16 @@
-import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert } from 'react-native'
+import { useState } from 'react'
+import { View, Text, ScrollView, TouchableOpacity, StyleSheet, Alert, Modal, TextInput, Dimensions } from 'react-native'
 import { SafeAreaView } from 'react-native-safe-area-context'
+import { Pencil, Plus } from 'lucide-react-native'
+import { Svg, Polyline, Circle, Line, Text as SvgText } from 'react-native-svg'
+import { useMeasurementsStore } from '../../src/store/useMeasurementsStore'
 import { useTheme } from '../../src/hooks/useTheme'
 import { useThemeStore } from '../../src/store/useThemeStore'
 import { useProfileStore } from '../../src/store/useProfileStore'
 import { useWorkoutStore, calcStreak } from '../../src/store/useWorkoutStore'
 import { THEMES } from '../../src/constants/theme'
+import { calcTDEE, calcMacros, PACE_LABELS, today } from '../../src/lib/macroCalc'
+import { Profile, MeasurementEntry } from '../../src/constants/types'
 import AsyncStorage from '@react-native-async-storage/async-storage'

 const ACHIEVEMENT_DEFS = [
@@ -20,6 +26,212 @@ const ACHIEVEMENT_DEFS = [
   { id: 'pb', icon: '📈', name: 'Personbästa', desc: 'Sätt ett personligt rekord' },
 ]

+function WeightGraph({ data }: { data: MeasurementEntry[] }) {
+  const theme = useTheme()
+  const svgW = Dimensions.get('window').width - 72
+  const svgH = 130
+  const pl = 44, pr = 12, pt = 12, pb = 28
+
+  const pts = [...data]
+    .filter((m) => m.weightKg !== undefined)
+    .sort((a, b) => a.date.localeCompare(b.date))
+    .slice(-7)
+
+  if (pts.length < 2) {
+    return (
+      <View style={{ paddingVertical: 20, alignItems: 'center' }}>
+        <Text style={{ color: theme.muted, fontSize: 13 }}>Logga minst 2 mätningar för att se graf</Text>
+      </View>
+    )
+  }
+
+  const ws = pts.map((p) => p.weightKg!)
+  const minW = Math.min(...ws) - 0.5
+  const maxW = Math.max(...ws) + 0.5
+  const wRange = maxW - minW
+  const gW = svgW - pl - pr
+  const gH = svgH - pt - pb
+
+  const tx = (i: number) => pl + (i / (pts.length - 1)) * gW
+  const ty = (w: number) => pt + (1 - (w - minW) / wRange) * gH
+  const polyPts = pts.map((p, i) => `${tx(i)},${ty(p.weightKg!)}`).join(' ')
+  const minActual = Math.min(...ws)
+  const maxActual = Math.max(...ws)
+
+  return (
+    <Svg width={svgW} height={svgH}>
+      {[minActual, (minActual + maxActual) / 2, maxActual].map((w, i) => (
+        <Line key={i} x1={pl} y1={ty(w)} x2={svgW - pr} y2={ty(w)} stroke={theme.border} strokeWidth={1} strokeDasharray="3,4" />
+      ))}
+      {[minActual, maxActual].map((w, i) => (
+        <SvgText key={i} x={pl - 6} y={ty(w) + 4} fontSize={10} fill={theme.muted} textAnchor="end">{w.toFixed(1)}</SvgText>
+      ))}
+      <Polyline points={polyPts} fill="none" stroke={theme.accent} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" />
+      {pts.map((p, i) => (
+        <Circle key={i} cx={tx(i)} cy={ty(p.weightKg!)} r={3.5} fill={theme.accent} />
+      ))}
+      {[0, pts.length - 1].map((i) => (
+        <SvgText key={i} x={tx(i)} y={svgH - 6} fontSize={9} fill={theme.muted} textAnchor={i === 0 ? 'start' : 'end'}>
+          {pts[i].date.slice(5).replace('-', '/')}
+        </SvgText>
+      ))}
+    </Svg>
+  )
+}
+
+function LogMeasurementModal({ onClose }: { onClose: () => void }) {
+  const t = useTheme()
+  const addMeasurement = useMeasurementsStore((s) => s.addMeasurement)
+  const [weight, setWeight] = useState('')
+  const [waist, setWaist] = useState('')
+  const [bodyFat, setBodyFat] = useState('')
+
+  const save = async () => {
+    if (!weight && !waist && !bodyFat) return
+    await addMeasurement({
+      id: `m${Date.now()}`,
+      date: today(),
+      weightKg: weight ? Number(weight) : undefined,
+      waistCm: waist ? Number(waist) : undefined,
+      bodyFatPct: bodyFat ? Number(bodyFat) : undefined,
+    })
+    onClose()
+  }
+
+  const fieldStyle = { backgroundColor: t.bg, borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, color: t.text, fontSize: 15 as const, marginBottom: 14, borderWidth: 1, borderColor: t.border }
+  const labelStyle = { color: t.muted, fontSize: 11 as const, fontWeight: '700' as const, letterSpacing: 0.8, textTransform: 'uppercase' as const, marginBottom: 6 }
+
+  return (
+    <Modal visible transparent animationType="slide" onRequestClose={onClose}>
+      <TouchableOpacity style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }} activeOpacity={1} onPress={onClose}>
+        <View>
+          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, maxHeight: '80%' }} contentContainerStyle={{ paddingBottom: 40 }} keyboardShouldPersistTaps="handled">
+            <View style={{ width: 40, height: 4, backgroundColor: t.border, borderRadius: 2, alignSelf: 'center', marginBottom: 16 }} />
+            <Text style={{ color: t.text, fontSize: 20, fontWeight: '800', marginBottom: 20 }}>Logga mätning</Text>
+            {([['Vikt (kg)', weight, setWeight], ['Midja (cm)', waist, setWaist], ['Fettprocent (%)', bodyFat, setBodyFat]] as const).map(([label, val, setter]) => (
+              <View key={label}>
+                <Text style={labelStyle}>{label}</Text>
+                <TextInput style={fieldStyle} value={val} onChangeText={setter} keyboardType="decimal-pad" placeholderTextColor={t.muted} placeholder={label.split(' ')[1]?.replace(/[()]/g, '') ?? ''} />
+              </View>
+            ))}
+            <View style={{ flexDirection: 'row', gap: 10 }}>
+              <TouchableOpacity style={{ flex: 1, backgroundColor: t.bg, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }} onPress={onClose}>
+                <Text style={{ color: t.text, fontWeight: '700' }}>Avbryt</Text>
+              </TouchableOpacity>
+              <TouchableOpacity style={{ flex: 2, backgroundColor: t.accent, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }} onPress={save}>
+                <Text style={{ color: '#FFF', fontWeight: '800', fontSize: 15 }}>Spara</Text>
+              </TouchableOpacity>
+            </View>
+          </ScrollView>
+        </View>
+      </TouchableOpacity>
+    </Modal>
+  )
+}
+
+function EditProfileModal({ profile, onClose }: { profile: Profile; onClose: () => void }) {
+  const t = useTheme()
+  const setProfile = useProfileStore((s) => s.setProfile)
+  const setMacroGoals = useProfileStore((s) => s.setMacroGoals)
+
+  const [weight, setWeight] = useState(String(profile.weightKg))
+  const [goalWeight, setGoalWeight] = useState(String(profile.goalWeightKg))
+  const [goal, setGoal] = useState(profile.goal)
+  const [pace, setPace] = useState(profile.pace ?? 'moderate')
+  const [activity, setActivity] = useState(profile.activityLevel)
+
+  const previewMacros = (() => {
+    const tdee = calcTDEE({ gender: profile.gender, age: profile.age, heightCm: profile.heightCm, weightKg: Number(weight) || profile.weightKg, activityLevel: activity })
+    return calcMacros(tdee, goal, pace, Number(weight) || profile.weightKg)
+  })()
+
+  const save = async () => {
+    const updated: Profile = {
+      ...profile,
+      weightKg: Number(weight) || profile.weightKg,
+      goalWeightKg: Number(goalWeight) || profile.goalWeightKg,
+      goal, pace, activityLevel: activity,
+      macroGoals: previewMacros,
+      updatedAt: new Date().toISOString(),
+    }
+    await setProfile(updated)
+    await setMacroGoals(previewMacros)
+    onClose()
+  }
+
+  const fieldStyle = { backgroundColor: t.bg, borderRadius: 10, paddingHorizontal: 14, paddingVertical: 12, color: t.text, fontSize: 15 as const, marginBottom: 14, borderWidth: 1, borderColor: t.border }
+  const labelStyle = { color: t.muted, fontSize: 11 as const, fontWeight: '700' as const, letterSpacing: 0.8, textTransform: 'uppercase' as const, marginBottom: 6 }
+
+  return (
+    <Modal visible transparent animationType="slide" onRequestClose={onClose}>
+      <TouchableOpacity style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }} activeOpacity={1} onPress={onClose}>
+        <View>
+          <ScrollView style={{ backgroundColor: t.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, maxHeight: '92%' }} contentContainerStyle={{ paddingBottom: 40 }} keyboardShouldPersistTaps="handled">
+            <View style={{ width: 40, height: 4, backgroundColor: t.border, borderRadius: 2, alignSelf: 'center', marginBottom: 16 }} />
+            <Text style={{ color: t.text, fontSize: 20, fontWeight: '800', marginBottom: 20 }}>Redigera profil</Text>
+
+            <Text style={labelStyle}>Nuvarande vikt (kg)</Text>
+            <TextInput style={fieldStyle} value={weight} onChangeText={setWeight} keyboardType="decimal-pad" placeholderTextColor={t.muted} />
+
+            <Text style={labelStyle}>Målvikt (kg)</Text>
+            <TextInput style={fieldStyle} value={goalWeight} onChangeText={setGoalWeight} keyboardType="decimal-pad" placeholderTextColor={t.muted} />
+
+            <Text style={labelStyle}>Mål</Text>
+            <View style={{ flexDirection: 'row', gap: 8, marginBottom: 14 }}>
+              {([['lose','Gå ner'],['maintain','Hålla vikt'],['gain','Bygga muskler']] as const).map(([v, l]) => (
+                <TouchableOpacity key={v} onPress={() => setGoal(v)}
+                  style={{ flex: 1, paddingVertical: 10, borderRadius: 12, alignItems: 'center', backgroundColor: goal === v ? t.accent : t.bg, borderWidth: 1, borderColor: goal === v ? t.accent : t.border }}>
+                  <Text style={{ color: goal === v ? '#FFF' : t.text, fontSize: 13, fontWeight: '600' }}>{l}</Text>
+                </TouchableOpacity>
+              ))}
+            </View>
+
+            {goal !== 'maintain' && (
+              <>
+                <Text style={labelStyle}>Takt</Text>
+                {PACE_LABELS[goal].map((p) => (
+                  <TouchableOpacity key={p.pace} onPress={() => setPace(p.pace)}
+                    style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: t.bg, borderRadius: 12, padding: 14, marginBottom: 8, borderWidth: 1.5, borderColor: pace === p.pace ? t.accent : 'transparent' }}>
+                    <Text style={{ color: pace === p.pace ? t.accent : t.text, fontSize: 15, fontWeight: '700' }}>{p.label}</Text>
+                    <Text style={{ color: t.muted, fontSize: 13 }}>{p.desc}</Text>
+                  </TouchableOpacity>
+                ))}
+              </>
+            )}
+
+            <Text style={labelStyle}>Aktivitetsnivå</Text>
+            {([['sedentary','Stillasittande'],['light','Lätt aktiv'],['moderate','Måttligt aktiv'],['very_active','Mycket aktiv']] as const).map(([v, l]) => (
+              <TouchableOpacity key={v} onPress={() => setActivity(v)}
+                style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: t.bg, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, marginBottom: 8, borderWidth: 1.5, borderColor: activity === v ? t.accent : 'transparent' }}>
+                <Text style={{ color: activity === v ? t.accent : t.text, fontSize: 14, fontWeight: '600' }}>{l}</Text>
+              </TouchableOpacity>
+            ))}
+
+            <View style={{ backgroundColor: t.accentLight, borderRadius: 12, padding: 14, marginTop: 4, marginBottom: 16 }}>
+              <Text style={{ color: t.accent, fontSize: 13, fontWeight: '700', marginBottom: 8 }}>Nytt kaloriintag</Text>
+              <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
+                <Text style={{ color: t.text, fontSize: 22, fontWeight: '900' }}>{previewMacros.calories} kcal</Text>
+              </View>
+              <Text style={{ color: t.muted, fontSize: 12, marginTop: 4 }}>
+                P {previewMacros.proteinG}g · K {previewMacros.carbsG}g · F {previewMacros.fatG}g
+              </Text>
+            </View>
+
+            <View style={{ flexDirection: 'row', gap: 10 }}>
+              <TouchableOpacity style={{ flex: 1, backgroundColor: t.bg, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }} onPress={onClose}>
+                <Text style={{ color: t.text, fontWeight: '700' }}>Avbryt</Text>
+              </TouchableOpacity>
+              <TouchableOpacity style={{ flex: 2, backgroundColor: t.accent, borderRadius: 12, paddingVertical: 14, alignItems: 'center' }} onPress={save}>
+                <Text style={{ color: '#FFF', fontWeight: '800', fontSize: 15 }}>Spara</Text>
+              </TouchableOpacity>
+            </View>
+          </ScrollView>
+        </View>
+      </TouchableOpacity>
+    </Modal>
+  )
+}
+
 export default function ProfileScreen() {
   const theme = useTheme()
   const themeId = useThemeStore((s) => s.themeId)
@@ -28,6 +240,10 @@ export default function ProfileScreen() {
   const macroGoals = useProfileStore((s) => s.macroGoals)
   const workoutLog = useWorkoutStore((s) => s.workoutLog)
   const streak = calcStreak(workoutLog)
+  const measurements = useMeasurementsStore((s) => s.measurements)
+  const [showEdit, setShowEdit] = useState(false)
+  const [showLogMeasurement, setShowLogMeasurement] = useState(false)
+  const sortedMeasurements = [...measurements].sort((a, b) => b.date.localeCompare(a.date))

   const s = StyleSheet.create({
     safe: { flex: 1, backgroundColor: theme.outer },
@@ -82,7 +298,7 @@ export default function ProfileScreen() {
       <View style={s.header}>
         <Text style={s.title}>Profil</Text>
       </View>
-      <ScrollView style={s.scroll} showsVerticalScrollIndicator={false}>
+      <ScrollView style={s.scroll} contentContainerStyle={{ paddingBottom: 32 }} showsVerticalScrollIndicator={false}>
         {/* Stats */}
         <View style={s.card}>
           <Text style={s.sectionTitle}>Statistik</Text>
@@ -93,15 +309,54 @@ export default function ProfileScreen() {
           </View>
         </View>

+        {/* Progress graph */}
+        <View style={[s.card, { marginTop: 12 }]}>
+          <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 12 }}>
+            <Text style={[s.sectionTitle, { marginBottom: 0, flex: 1 }]}>Progress</Text>
+            <TouchableOpacity
+              onPress={() => setShowLogMeasurement(true)}
+              style={{ flexDirection: 'row', alignItems: 'center', gap: 4, backgroundColor: theme.accentLight, borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6 }}
+            >
+              <Plus size={14} color={theme.accent} />
+              <Text style={{ color: theme.accent, fontSize: 13, fontWeight: '700' }}>Logga</Text>
+            </TouchableOpacity>
+          </View>
+          <WeightGraph data={measurements} />
+          {sortedMeasurements.length === 0 ? (
+            <Text style={{ color: theme.muted, fontSize: 13, textAlign: 'center', paddingVertical: 8 }}>Inga mätningar ännu – logga din första!</Text>
+          ) : (
+            sortedMeasurements.slice(0, 4).map((m) => (
+              <View key={m.id} style={[s.row, { borderBottomWidth: 0 }]}>
+                <Text style={s.rowLabel}>{m.date.slice(5).replace('-', '/')}</Text>
+                <Text style={s.rowValue}>
+                  {[m.weightKg ? `${m.weightKg} kg` : null, m.waistCm ? `${m.waistCm} cm` : null, m.bodyFatPct ? `${m.bodyFatPct}%` : null].filter(Boolean).join(' · ')}
+                </Text>
+              </View>
+            ))
+          )}
+        </View>
+
         {/* Profile data */}
         {profile && (
           <View style={[s.card, { marginTop: 12 }]}>
-            <Text style={s.sectionTitle}>Profil</Text>
+            <View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 14 }}>
+              <Text style={[s.sectionTitle, { marginBottom: 0, flex: 1 }]}>Profil</Text>
+              <TouchableOpacity
+                onPress={() => setShowEdit(true)}
+                style={{ flexDirection: 'row', alignItems: 'center', gap: 4, backgroundColor: theme.accentLight, borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6 }}
+              >
+                <Pencil size={14} color={theme.accent} />
+                <Text style={{ color: theme.accent, fontSize: 13, fontWeight: '700' }}>Redigera</Text>
+              </TouchableOpacity>
+            </View>
             {[
               { label: 'Namn', value: profile.name },
               { label: 'Vikt', value: `${profile.weightKg} kg` },
+              { label: 'Målvikt', value: `${profile.goalWeightKg} kg` },
               { label: 'Längd', value: `${profile.heightCm} cm` },
               { label: 'Mål', value: profile.goal === 'lose' ? 'Gå ner' : profile.goal === 'gain' ? 'Bygga muskler' : 'Hålla vikt' },
+              ...(profile.goal !== 'maintain' && profile.pace ? [{ label: 'Takt', value: PACE_LABELS[profile.goal].find(p => p.pace === profile.pace)?.label ?? profile.pace }] : []),
+              { label: 'Kaloriintag', value: `${macroGoals.calories} kcal/dag` },
             ].map((r) => (
               <View key={r.label} style={s.row}>
                 <Text style={s.rowLabel}>{r.label}</Text>
@@ -144,6 +399,8 @@ export default function ProfileScreen() {
         </TouchableOpacity>
         <View style={{ height: 24 }} />
       </ScrollView>
+      {profile && showEdit && <EditProfileModal profile={profile} onClose={() => setShowEdit(false)} />}
+      {showLogMeasurement && <LogMeasurementModal onClose={() => setShowLogMeasurement(false)} />}
     </SafeAreaView>
   )
 }
diff --git a/fortis/app/(tabs)/schedule.tsx b/fortis/app/(tabs)/schedule.tsx
index 9214cac..381baa8 100644
--- a/fortis/app/(tabs)/schedule.tsx
+++ b/fortis/app/(tabs)/schedule.tsx
@@ -59,7 +59,7 @@ export default function ScheduleScreen() {
         ))}
       </View>

-      <ScrollView style={s.scroll} showsVerticalScrollIndicator={false}>
+      <ScrollView style={s.scroll} contentContainerStyle={{ paddingBottom: 32 }} showsVerticalScrollIndicator={false}>
         {dayWorkouts.length === 0 ? (
           <View style={s.emptyCard}>
             <Text style={s.emptyText}>Ingen träning {DAYS[selectedDay]}en.{'\n'}Vila eller lägg till ett pass.</Text>
diff --git a/fortis/app/_layout.tsx b/fortis/app/_layout.tsx
index 4dab1b3..5d23d96 100644
--- a/fortis/app/_layout.tsx
+++ b/fortis/app/_layout.tsx
@@ -1,11 +1,12 @@
 import { useEffect } from 'react'
-import { Stack } from 'expo-router'
+import { Stack, useRouter, useSegments } from 'expo-router'
 import { StatusBar } from 'expo-status-bar'
 import * as SplashScreen from 'expo-splash-screen'
 import { useThemeStore } from '../src/store/useThemeStore'
 import { useProfileStore } from '../src/store/useProfileStore'
 import { useWorkoutStore } from '../src/store/useWorkoutStore'
 import { useNutritionStore } from '../src/store/useNutritionStore'
+import { useMeasurementsStore } from '../src/store/useMeasurementsStore'

 SplashScreen.preventAutoHideAsync()

@@ -14,20 +15,35 @@ export default function RootLayout() {
   const loadProfile = useProfileStore((s) => s.loadProfile)
   const loadWorkouts = useWorkoutStore((s) => s.loadWorkouts)
   const loadNutrition = useNutritionStore((s) => s.loadNutrition)
+  const loadMeasurements = useMeasurementsStore((s) => s.loadMeasurements)
   const hasLoaded = useProfileStore((s) => s.hasLoaded)
+  const profile = useProfileStore((s) => s.profile)
+  const router = useRouter()
+  const segments = useSegments()

   useEffect(() => {
-    Promise.all([loadTheme(), loadProfile(), loadWorkouts(), loadNutrition()]).then(() => {
+    Promise.all([loadTheme(), loadProfile(), loadWorkouts(), loadNutrition(), loadMeasurements()]).then(() => {
       SplashScreen.hideAsync()
     })
   }, [])

+  // Route guard — runs after stores have loaded
+  useEffect(() => {
+    if (!hasLoaded) return
+    const inOnboarding = segments[0] === 'onboarding'
+    if (!profile && !inOnboarding) {
+      router.replace('/onboarding')
+    } else if (profile && inOnboarding) {
+      router.replace('/(tabs)')
+    }
+  }, [hasLoaded, profile])
+
   return (
     <>
       <StatusBar style="light" />
       <Stack screenOptions={{ headerShown: false }}>
         <Stack.Screen name="(tabs)" />
-        <Stack.Screen name="onboarding" />
+        <Stack.Screen name="onboarding/index" />
         <Stack.Screen name="workout/[id]" options={{ presentation: 'fullScreenModal' }} />
       </Stack>
     </>
diff --git a/fortis/app/onboarding/index.tsx b/fortis/app/onboarding/index.tsx
index 629d074..5a12934 100644
--- a/fortis/app/onboarding/index.tsx
+++ b/fortis/app/onboarding/index.tsx
@@ -1,30 +1,72 @@
-import { useState } from 'react'
-import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } from 'react-native'
+import { useState, useEffect, useRef } from 'react'
+import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Animated } from 'react-native'
 import { SafeAreaView } from 'react-native-safe-area-context'
 import { useRouter } from 'expo-router'
+import { Svg, Circle } from 'react-native-svg'
 import { useTheme } from '../../src/hooks/useTheme'
 import { useProfileStore } from '../../src/store/useProfileStore'
-import { calcTDEE, calcMacros } from '../../src/lib/macroCalc'
+import { calcTDEE, calcMacros, PACE_LABELS } from '../../src/lib/macroCalc'
 import { Profile } from '../../src/constants/types'

 const STEPS = 5

+function SplashIntro({ onDone }: { onDone: () => void }) {
+  const progress = useRef(new Animated.Value(0)).current
+  const opacity = useRef(new Animated.Value(1)).current
+  const rotate = useRef(new Animated.Value(0)).current
+
+  useEffect(() => {
+    Animated.parallel([
+      Animated.timing(progress, { toValue: 1, duration: 2400, useNativeDriver: false }),
+      Animated.loop(Animated.timing(rotate, { toValue: 1, duration: 1800, useNativeDriver: true })),
+    ]).start()
+    const t = setTimeout(() => {
+      Animated.timing(opacity, { toValue: 0, duration: 350, useNativeDriver: true }).start(onDone)
+    }, 2800)
+    return () => clearTimeout(t)
+  }, [])
+
+  const spin = rotate.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] })
+  const barWidth = progress.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%'] })
+
+  return (
+    <Animated.View style={{ flex: 1, backgroundColor: '#0F0F0F', alignItems: 'center', justifyContent: 'center', opacity }}>
+      <Animated.View style={{ transform: [{ rotate: spin }], marginBottom: 32 }}>
+        <Svg width={100} height={100} viewBox="0 0 100 100">
+          <Circle cx={50} cy={50} r={40} stroke="rgba(232,93,38,0.2)" strokeWidth={6} fill="none" />
+          <Circle cx={50} cy={50} r={40} stroke="#E85D26" strokeWidth={6} fill="none"
+            strokeDasharray="60 192" strokeLinecap="round" transform="rotate(-90 50 50)" />
+        </Svg>
+      </Animated.View>
+      <Text style={{ color: '#FFF', fontSize: 38, fontWeight: '900', letterSpacing: 6, marginBottom: 48 }}>FORTIS</Text>
+      <View style={{ width: 160, height: 4, backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: 2, overflow: 'hidden' }}>
+        <Animated.View style={{ height: 4, backgroundColor: '#E85D26', borderRadius: 2, width: barWidth }} />
+      </View>
+    </Animated.View>
+  )
+}
+
 type OBData = {
   name: string; gender: 'male' | 'female' | 'other'
   age: string; height: string; weight: string; goalWeight: string
-  goal: 'lose' | 'maintain' | 'gain'; activity: 'sedentary' | 'light' | 'moderate' | 'very_active'
+  goal: 'lose' | 'maintain' | 'gain'
+  pace: 'slow' | 'moderate' | 'fast'
+  activity: 'sedentary' | 'light' | 'moderate' | 'very_active'
 }

-const DEFAULT_OB: OBData = { name: '', gender: 'male', age: '', height: '', weight: '', goalWeight: '', goal: 'maintain', activity: 'moderate' }
+const DEFAULT_OB: OBData = { name: '', gender: 'male', age: '', height: '', weight: '', goalWeight: '', goal: 'maintain', pace: 'moderate', activity: 'moderate' }

 export default function OnboardingScreen() {
   const theme = useTheme()
   const router = useRouter()
   const setProfile = useProfileStore((s) => s.setProfile)
   const setMacroGoals = useProfileStore((s) => s.setMacroGoals)
+  const [showSplash, setShowSplash] = useState(true)
   const [step, setStep] = useState(1)
   const [data, setData] = useState<OBData>(DEFAULT_OB)

+  if (showSplash) return <SplashIntro onDone={() => setShowSplash(false)} />
+
   const set = (field: keyof OBData, val: string) => setData((d) => ({ ...d, [field]: val }))

   const macros = (() => {
@@ -32,7 +74,7 @@ export default function OnboardingScreen() {
     const h = Number(data.height) || 175
     const w = Number(data.weight) || 75
     const tdee = calcTDEE({ gender: data.gender, age, heightCm: h, weightKg: w, activityLevel: data.activity })
-    return calcMacros(tdee, data.goal, w)
+    return calcMacros(tdee, data.goal, data.pace, w)
   })()

   const save = async () => {
@@ -45,6 +87,7 @@ export default function OnboardingScreen() {
       weightKg: Number(data.weight) || 75,
       goalWeightKg: Number(data.goalWeight) || Number(data.weight) || 75,
       goal: data.goal,
+      pace: data.pace,
       activityLevel: data.activity,
       macroGoals: macros,
       createdAt: new Date().toISOString(),
@@ -58,7 +101,7 @@ export default function OnboardingScreen() {
   const skip = async () => {
     const p: Profile = {
       id: `p${Date.now()}`, name: 'Användare', gender: 'male', age: 25,
-      heightCm: 175, weightKg: 75, goalWeightKg: 75, goal: 'maintain',
+      heightCm: 175, weightKg: 75, goalWeightKg: 75, goal: 'maintain', pace: 'moderate',
       activityLevel: 'moderate', macroGoals: { calories: 2200, proteinG: 165, carbsG: 220, fatG: 73 },
       createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
     }
@@ -139,6 +182,21 @@ export default function OnboardingScreen() {
           </View>
           <Text style={s.label}>Målvikt (kg)</Text>
           <TextInput style={s.input} value={data.goalWeight} onChangeText={(v) => set('goalWeight', v)} placeholder="kg" keyboardType="decimal-pad" placeholderTextColor={theme.muted} />
+          {data.goal !== 'maintain' && (
+            <>
+              <Text style={s.label}>Takt</Text>
+              {PACE_LABELS[data.goal].map((p) => (
+                <TouchableOpacity
+                  key={p.pace}
+                  style={[{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: theme.card, borderRadius: 12, padding: 14, marginBottom: 8, borderWidth: 1.5, borderColor: data.pace === p.pace ? theme.accent : 'transparent' }]}
+                  onPress={() => set('pace', p.pace)}
+                >
+                  <Text style={{ color: data.pace === p.pace ? theme.accent : theme.text, fontSize: 15, fontWeight: '700' }}>{p.label}</Text>
+                  <Text style={{ color: theme.muted, fontSize: 13 }}>{p.desc}</Text>
+                </TouchableOpacity>
+              ))}
+            </>
+          )}
           <Text style={s.label}>Aktivitetsnivå</Text>
           {[
             ['sedentary','Stillasittande'],['light','Lätt aktiv (1–3 dagar/v)'],
diff --git a/fortis/app/workout/[id].tsx b/fortis/app/workout/[id].tsx
index 9b222c5..af2324e 100644
--- a/fortis/app/workout/[id].tsx
+++ b/fortis/app/workout/[id].tsx
@@ -121,7 +121,7 @@ export default function WorkoutScreen() {
         <View style={s.timerPill}><Text style={s.timerText}>{timerDisplay}</Text></View>
       </View>

-      <ScrollView style={s.scroll} showsVerticalScrollIndicator={false}>
+      <ScrollView style={s.scroll} contentContainerStyle={{ paddingBottom: 32 }} showsVerticalScrollIndicator={false}>
         {restTime !== null && (
           <View style={s.restBanner}>
             <Text style={s.restText}>⏱ Vila</Text>
diff --git a/fortis/package-lock.json b/fortis/package-lock.json
index 134b254..a09d2bd 100644
--- a/fortis/package-lock.json
+++ b/fortis/package-lock.json
@@ -9,7 +9,7 @@
       "version": "1.0.0",
       "dependencies": {
         "@expo/vector-icons": "^15.0.3",
-        "@react-native-async-storage/async-storage": "^3.0.2",
+        "@react-native-async-storage/async-storage": "^2.2.0",
         "@react-navigation/native": "^7.1.8",
         "expo": "~54.0.33",
         "expo-constants": "~18.0.13",
@@ -26,7 +26,7 @@
         "react-native-reanimated": "~4.1.1",
         "react-native-safe-area-context": "~5.6.0",
         "react-native-screens": "~4.16.0",
-        "react-native-svg": "^15.15.4",
+        "react-native-svg": "^15.12.1",
         "react-native-web": "~0.21.0",
         "react-native-worklets": "0.5.1",
         "zustand": "^5.0.12"
@@ -2390,16 +2390,15 @@
       }
     },
     "node_modules/@react-native-async-storage/async-storage": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-3.0.2.tgz",
-      "integrity": "sha512-XP0zDIl+1XoeuQ7f878qXKdl77zLwzLALPpxvNRc7ZtDh9ew36WSvOdQOhFkexMySapFAWxEbZxS8K8J2DU4eg==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
+      "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
       "license": "MIT",
       "dependencies": {
-        "idb": "8.0.3"
+        "merge-options": "^3.0.4"
       },
       "peerDependencies": {
-        "react": "*",
-        "react-native": "*"
+        "react-native": "^0.0.0-0 || >=0.65 <1.0"
       }
     },
     "node_modules/@react-native/assets-registry": {
@@ -5224,12 +5223,6 @@
       "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
       "license": "BSD-3-Clause"
     },
-    "node_modules/idb": {
-      "version": "8.0.3",
-      "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz",
-      "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==",
-      "license": "ISC"
-    },
     "node_modules/ieee754": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -5378,6 +5371,15 @@
         "node": ">=0.12.0"
       }
     },
+    "node_modules/is-plain-obj": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/is-wsl": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -6134,6 +6136,18 @@
       "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
       "license": "MIT"
     },
+    "node_modules/merge-options": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz",
+      "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==",
+      "license": "MIT",
+      "dependencies": {
+        "is-plain-obj": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/merge-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -7446,9 +7460,9 @@
       }
     },
     "node_modules/react-native-svg": {
-      "version": "15.15.4",
-      "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.4.tgz",
-      "integrity": "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==",
+      "version": "15.12.1",
+      "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz",
+      "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==",
       "license": "MIT",
       "dependencies": {
         "css-select": "^5.1.0",
diff --git a/fortis/package.json b/fortis/package.json
index ae2225d..8dfec1a 100644
--- a/fortis/package.json
+++ b/fortis/package.json
@@ -10,7 +10,7 @@
   },
   "dependencies": {
     "@expo/vector-icons": "^15.0.3",
-    "@react-native-async-storage/async-storage": "^3.0.2",
+    "@react-native-async-storage/async-storage": "^2.2.0",
     "@react-navigation/native": "^7.1.8",
     "expo": "~54.0.33",
     "expo-constants": "~18.0.13",
@@ -27,7 +27,7 @@
     "react-native-reanimated": "~4.1.1",
     "react-native-safe-area-context": "~5.6.0",
     "react-native-screens": "~4.16.0",
-    "react-native-svg": "^15.15.4",
+    "react-native-svg": "^15.12.1",
     "react-native-web": "~0.21.0",
     "react-native-worklets": "0.5.1",
     "zustand": "^5.0.12"
diff --git a/fortis/src/constants/types.ts b/fortis/src/constants/types.ts
index f5ee427..8db03a3 100644
--- a/fortis/src/constants/types.ts
+++ b/fortis/src/constants/types.ts
@@ -7,6 +7,7 @@ export interface Profile {
   weightKg: number
   goalWeightKg: number
   goal: 'lose' | 'maintain' | 'gain'
+  pace: 'slow' | 'moderate' | 'fast'
   activityLevel: 'sedentary' | 'light' | 'moderate' | 'very_active'
   macroGoals: MacroGoals
   createdAt: string
diff --git a/fortis/src/lib/macroCalc.ts b/fortis/src/lib/macroCalc.ts
index a96c87a..6b7bc5d 100644
--- a/fortis/src/lib/macroCalc.ts
+++ b/fortis/src/lib/macroCalc.ts
@@ -7,6 +7,26 @@ const ACTIVITY_MULT = {
   very_active: 1.725,
 } as const

+const PACE_DELTA: Record<Profile['goal'], Record<Profile['pace'], number>> = {
+  lose:     { slow: -250, moderate: -500, fast: -750 },
+  gain:     { slow: 200,  moderate: 350,  fast: 500  },
+  maintain: { slow: 0,    moderate: 0,    fast: 0    },
+}
+
+export const PACE_LABELS: Record<Profile['goal'], { pace: Profile['pace']; label: string; desc: string }[]> = {
+  lose: [
+    { pace: 'slow',     label: 'Långsamt',  desc: '−250 kcal/dag · ~0,25 kg/vecka' },
+    { pace: 'moderate', label: 'Måttligt',  desc: '−500 kcal/dag · ~0,5 kg/vecka' },
+    { pace: 'fast',     label: 'Snabbt',    desc: '−750 kcal/dag · ~0,75 kg/vecka' },
+  ],
+  gain: [
+    { pace: 'slow',     label: 'Lean bulk', desc: '+200 kcal/dag · ~0,2 kg/vecka' },
+    { pace: 'moderate', label: 'Måttligt',  desc: '+350 kcal/dag · ~0,35 kg/vecka' },
+    { pace: 'fast',     label: 'Aggressivt',desc: '+500 kcal/dag · ~0,5 kg/vecka' },
+  ],
+  maintain: [],
+}
+
 export function calcTDEE(p: Pick<Profile, 'gender' | 'age' | 'heightCm' | 'weightKg' | 'activityLevel'>): number {
   const bmr =
     p.gender === 'female'
@@ -18,10 +38,11 @@ export function calcTDEE(p: Pick<Profile, 'gender' | 'age' | 'heightCm' | 'weigh
 export function calcMacros(
   tdee: number,
   goal: Profile['goal'],
+  pace: Profile['pace'],
   weightKg: number,
 ): MacroGoals {
-  const calories =
-    goal === 'lose' ? tdee - 500 : goal === 'gain' ? tdee + 300 : tdee
+  const delta = PACE_DELTA[goal][pace]
+  const calories = Math.max(1200, tdee + delta)
   const proteinG = Math.round(weightKg * 2)
   const fatG = Math.round((calories * 0.25) / 9)
   const carbsG = Math.round((calories - proteinG * 4 - fatG * 9) / 4)
diff --git a/fortis/src/store/useMeasurementsStore.ts b/fortis/src/store/useMeasurementsStore.ts
new file mode 100644
index 0000000..bcfea9b
--- /dev/null
+++ b/fortis/src/store/useMeasurementsStore.ts
@@ -0,0 +1,31 @@
+import { create } from 'zustand'
+import { MeasurementEntry } from '../constants/types'
+import { storageGet, storageSet, STORAGE_KEYS } from '../lib/storage'
+
+interface MeasurementsStore {
+  measurements: MeasurementEntry[]
+  loadMeasurements: () => Promise<void>
+  addMeasurement: (entry: MeasurementEntry) => Promise<void>
+  removeMeasurement: (id: string) => Promise<void>
+}
+
+export const useMeasurementsStore = create<MeasurementsStore>((set, get) => ({
+  measurements: [],
+
+  loadMeasurements: async () => {
+    const measurements = await storageGet<MeasurementEntry[]>(STORAGE_KEYS.measurements, [])
+    set({ measurements })
+  },
+
+  addMeasurement: async (entry) => {
+    const measurements = [entry, ...get().measurements]
+    set({ measurements })
+    await storageSet(STORAGE_KEYS.measurements, measurements)
+  },
+
+  removeMeasurement: async (id) => {
+    const measurements = get().measurements.filter((m) => m.id !== id)
+    set({ measurements })
+    await storageSet(STORAGE_KEYS.measurements, measurements)
+  },
+}))
diff --git a/fortis/src/store/useNutritionStore.ts b/fortis/src/store/useNutritionStore.ts
index c106f2e..e91997d 100644
--- a/fortis/src/store/useNutritionStore.ts
+++ b/fortis/src/store/useNutritionStore.ts
@@ -12,6 +12,8 @@ interface NutritionStore {
   addFoodEntry: (entry: FoodLogEntry) => Promise<void>
   removeFoodEntry: (id: string) => Promise<void>
   addWater: (ml: number) => Promise<void>
+  addCustomFood: (food: FoodItem) => Promise<void>
+  removeCustomFood: (id: string) => Promise<void>
 }

 export const useNutritionStore = create<NutritionStore>((set, get) => ({
@@ -44,6 +46,18 @@ export const useNutritionStore = create<NutritionStore>((set, get) => ({
     set({ waterToday: next })
     await storageSet(STORAGE_KEYS.water(today()), next)
   },
+
+  addCustomFood: async (food) => {
+    const foods = [...get().customFoods, food]
+    set({ customFoods: foods })
+    await storageSet(STORAGE_KEYS.customFoods, foods)
+  },
+
+  removeCustomFood: async (id) => {
+    const foods = get().customFoods.filter((f) => f.id !== id)
+    set({ customFoods: foods })
+    await storageSet(STORAGE_KEYS.customFoods, foods)
+  },
 }))

 export function todayFoodLog(log: FoodLogEntry[]): FoodLogEntry[] {