diff --git a/TODO.md b/TODO.md index 31211b8..cd31feb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,52 @@ # Gravl - Feature Roadmap +## 🎹 Design Overhaul - Fitness App Feel + +**MĂ„l:** En professionell, atletisk kĂ€nsla - inte en hobby-app med emojis. + +### FĂ€rgpalett +- [ ] PrimĂ€r: Mörk bakgrund (#0a0a0f eller liknande) +- [ ] Accent: Energisk orange/röd (#ff6b35) eller electric blue (#00d4ff) +- [ ] Text: Ljus pĂ„ mörk (#ffffff, #a1a1aa för sekundĂ€r) +- [ ] Gradienter: Subtila, inte rainbow + +### Typografi +- [ ] Rubrik: Bold, kondenserad sans-serif (Inter, Oswald, eller liknande) +- [ ] Body: Clean sans-serif +- [ ] Siffror/stats: Monospace eller tabular för alignment + +### Ikoner & Grafik +- [ ] **Bort med ALLA emojis** - ersĂ€tt med: + - SVG-ikoner (Lucide, Heroicons, eller custom) + - Stiliserade fitness-silhuetter för workout-typer + - Abstrakta former/linjer istĂ€llet för cartoonish grafik +- [ ] Coach-avatar: Stiliserad silhuett eller initialer, inte emoji +- [ ] Workout-ikoner: Dumbbell, barbell, kettlebell som rena linjeikoner + +### UI-komponenter +- [ ] Kort: Subtila skuggor, mjuka kanter, inte "bubbliga" +- [ ] Knappar: Solid eller outlined, inte gradient-rainbow +- [ ] Progress bars: Tunna, eleganta +- [ ] Kalender: Minimalistisk, fĂ€rgkodade dots/bars + +### Bilder +- [ ] Hero-bilder: Högkvalitativa trĂ€ningsbilder (Unsplash fitness) +- [ ] Bakgrunder: Mörka texturer eller subtila patterns +- [ ] Inga clip-art eller cartoon-style + +### Animation +- [ ] Subtila micro-interactions +- [ ] Smooth transitions (300ms ease) +- [ ] Loading states: Skeleton screens, inte spinners med emojis + +### Inspirations-appar +- Nike Training Club +- FITBOD +- Strong +- Hevy + +--- + ## 🔐 Onboarding & Signup - [ ] Registrering/inloggning (email + lösenord) - [ ] Onboarding-wizard med steg-för-steg guide diff --git a/frontend/src/components/Icons.jsx b/frontend/src/components/Icons.jsx new file mode 100644 index 0000000..bfcbb40 --- /dev/null +++ b/frontend/src/components/Icons.jsx @@ -0,0 +1,276 @@ +// Clean SVG icons for the Gravl app +// Replaces emojis with professional vector icons + +export const Icons = { + // Navigation & UI + home: ( + + + + + ), + chart: ( + + + + + + ), + user: ( + + + + + ), + logout: ( + + + + + + ), + arrowLeft: ( + + + + + ), + arrowRight: ( + + + + + ), + chevronLeft: ( + + + + ), + chevronRight: ( + + + + ), + chevronDown: ( + + + + ), + plus: ( + + + + + ), + check: ( + + + + ), + + // Workout types + dumbbell: ( + + + + + + ), + arm: ( + + + + + + + ), + leg: ( + + + + + + + ), + back: ( + + + + + + + ), + chest: ( + + + + + + ), + shoulder: ( + + + + + + + ), + core: ( + + + + + + + ), + fullBody: ( + + + + + + + + + ), + + // Activity & rest + walking: ( + + + + + + + + ), + yoga: ( + + + + + + + ), + swimming: ( + + + + + + + ), + cycling: ( + + + + + + + ), + sleep: ( + + + + ), + + // Stats & metrics + fire: ( + + + + + ), + calendar: ( + + + + + + + ), + target: ( + + + + + + ), + trophy: ( + + + + + + + + ), + + // Coach avatar (silhouette) + coach: ( + + + + + + + ), + + // Brand + gravl: ( + + + + + + ), +} + +// Icon component wrapper +export function Icon({ name, size = 24, className = '', style = {} }) { + const icon = Icons[name] + if (!icon) return null + + return ( + + {icon} + + ) +} + +// Helper to get workout icon name based on workout name +export function getWorkoutIconName(name) { + const lower = name.toLowerCase() + if (lower.includes('push') || lower.includes('bröst') || lower.includes('chest')) return 'chest' + if (lower.includes('pull') || lower.includes('rygg') || lower.includes('back')) return 'back' + if (lower.includes('ben') || lower.includes('leg') || lower.includes('lower')) return 'leg' + if (lower.includes('axlar') || lower.includes('shoulder')) return 'shoulder' + if (lower.includes('arm')) return 'arm' + if (lower.includes('core') || lower.includes('mage')) return 'core' + if (lower.includes('helkropp') || lower.includes('full')) return 'fullBody' + if (lower.includes('överkropp') || lower.includes('upper')) return 'chest' + if (lower.includes('underkropp')) return 'leg' + return 'dumbbell' +} + +// Helper to get activity icon name +export function getActivityIconName(activity) { + const lower = activity.toLowerCase() + if (lower.includes('promenad') || lower.includes('walk')) return 'walking' + if (lower.includes('yoga') || lower.includes('stretch')) return 'yoga' + if (lower.includes('sim') || lower.includes('swim')) return 'swimming' + if (lower.includes('cyk') || lower.includes('bike')) return 'cycling' + return 'walking' +} + +export default Icon diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index ea40a13..ee18d3e 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' import { useAuth } from '../context/AuthContext' +import { Icon, getActivityIconName } from '../components/Icons' const API_URL = '/api' @@ -7,13 +8,13 @@ const API_URL = '/api' const getCoachGreeting = (user, todayWorkout) => { const hour = new Date().getHours() const name = user?.name?.split(' ')[0] || 'du' - + if (todayWorkout) { // There's a workout today if (hour < 10) { - return `Godmorgon ${name}! Idag kör vi ${todayWorkout.name.toLowerCase()}. Redo? đŸ’Ș` + return `Godmorgon ${name}! Idag kör vi ${todayWorkout.name.toLowerCase()}. Redo?` } else if (hour < 14) { - return `${todayWorkout.name} stĂ„r pĂ„ schemat idag. Dags att köra! đŸ‹ïž` + return `${todayWorkout.name} stĂ„r pĂ„ schemat idag. Dags att köra!` } else if (hour < 18) { return `Eftermiddagspass? ${todayWorkout.name} vĂ€ntar pĂ„ dig.` } else { @@ -22,23 +23,23 @@ const getCoachGreeting = (user, todayWorkout) => { } else { // Rest day if (hour < 10) { - return `Godmorgon ${name}! Vilodag idag – perfekt för Ă„terhĂ€mtning. 🧘` + return `Godmorgon ${name}! Vilodag idag – perfekt för Ă„terhĂ€mtning.` } else if (hour < 14) { - return `Ingen trĂ€ning schemalagd. Ta en promenad eller stretcha lite? đŸš¶` + return `Ingen trĂ€ning schemalagd. Ta en promenad eller stretcha lite?` } else if (hour < 18) { - return `Vila Ă€r ocksĂ„ trĂ€ning! LĂ€tt rörelse eller mobilitet idag? 🧘` + return `Vila Ă€r ocksĂ„ trĂ€ning! LĂ€tt rörelse eller mobilitet idag?` } else { - return `Lugn kvĂ€ll ${name}. Ladda batterierna till nĂ€sta pass! 😮` + return `Lugn kvĂ€ll ${name}. Ladda batterierna till nĂ€sta pass!` } } } // Rest day tips const restDayTips = [ - { icon: 'đŸš¶', text: '30 min promenad' }, - { icon: '🧘', text: 'Yoga/stretching' }, - { icon: '🏊', text: 'Simning' }, - { icon: '🚮', text: 'LĂ€tt cykling' }, + { iconName: 'walking', text: 'Promenad' }, + { iconName: 'yoga', text: 'Stretching' }, + { iconName: 'swimming', text: 'Simning' }, + { iconName: 'cycling', text: 'Cykling' }, ] // Get weekday names @@ -89,12 +90,12 @@ function Dashboard({ onStartWorkout, onNavigate }) {
-

đŸ‹ïž Gravl

+

Gravl

@@ -103,20 +104,20 @@ function Dashboard({ onStartWorkout, onNavigate }) { {/* Week Calendar - TOP */}
- {formatWeekRange(currentWeekStart)} -
@@ -128,14 +129,14 @@ function Dashboard({ onStartWorkout, onNavigate }) { const workout = program?.days?.find(d => d.day_number === dayNum) return ( -
hasWorkout && workout && onStartWorkout(workout)} > {name} {date.getDate()} - {hasWorkout && ●} + {hasWorkout && }
) })} @@ -145,7 +146,9 @@ function Dashboard({ onStartWorkout, onNavigate }) { {/* Coach Section with Today's Action */}
-
đŸ§”â€â™‚ïž
+
+ +

{getCoachGreeting(user, todayWorkout)}

@@ -163,7 +166,7 @@ function Dashboard({ onStartWorkout, onNavigate }) {
- → +
) : ( @@ -171,14 +174,17 @@ function Dashboard({ onStartWorkout, onNavigate }) {
{restDayTips.map((tip, i) => ( - {tip.icon} {tip.text} + + + {tip.text} + ))}
-
@@ -197,7 +203,7 @@ function Dashboard({ onStartWorkout, onNavigate }) { Denna vecka
- đŸ’Ș + Streak: 5
diff --git a/frontend/src/pages/WorkoutSelectPage.jsx b/frontend/src/pages/WorkoutSelectPage.jsx index f47cf21..fcd7597 100644 --- a/frontend/src/pages/WorkoutSelectPage.jsx +++ b/frontend/src/pages/WorkoutSelectPage.jsx @@ -1,32 +1,18 @@ import { useState, useEffect } from 'react' +import { Icon, getWorkoutIconName } from '../components/Icons' const API_URL = '/api' -// Workout icons based on type/name -const getWorkoutIcon = (name) => { - const lower = name.toLowerCase() - if (lower.includes('push') || lower.includes('bröst') || lower.includes('chest')) return 'đŸ’Ș' - if (lower.includes('pull') || lower.includes('rygg') || lower.includes('back')) return 'đŸ‹ïž' - if (lower.includes('ben') || lower.includes('leg') || lower.includes('lower')) return 'đŸŠ”' - if (lower.includes('axlar') || lower.includes('shoulder')) return '🎯' - if (lower.includes('arm')) return 'đŸ’Ș' - if (lower.includes('core') || lower.includes('mage')) return 'đŸ”„' - if (lower.includes('helkropp') || lower.includes('full')) return '⚡' - if (lower.includes('överkropp') || lower.includes('upper')) return 'đŸ’Ș' - if (lower.includes('underkropp') || lower.includes('lower')) return 'đŸŠ”' - return 'đŸ‹ïž' -} - -// Workout color based on type +// Workout color based on type - more subtle/muted palette const getWorkoutColor = (name) => { const lower = name.toLowerCase() - if (lower.includes('push') || lower.includes('bröst')) return '#ef4444' // Red - if (lower.includes('pull') || lower.includes('rygg')) return '#3b82f6' // Blue - if (lower.includes('ben') || lower.includes('leg')) return '#8b5cf6' // Purple - if (lower.includes('axlar')) return '#f59e0b' // Orange - if (lower.includes('överkropp') || lower.includes('upper')) return '#10b981' // Green - if (lower.includes('underkropp') || lower.includes('lower')) return '#6366f1' // Indigo - return '#6366f1' // Default indigo + if (lower.includes('push') || lower.includes('bröst')) return 'var(--workout-push)' + if (lower.includes('pull') || lower.includes('rygg')) return 'var(--workout-pull)' + if (lower.includes('ben') || lower.includes('leg')) return 'var(--workout-legs)' + if (lower.includes('axlar')) return 'var(--workout-shoulders)' + if (lower.includes('överkropp') || lower.includes('upper')) return 'var(--workout-upper)' + if (lower.includes('underkropp') || lower.includes('lower')) return 'var(--workout-lower)' + return 'var(--workout-default)' } function WorkoutSelectPage({ onBack, onSelectWorkout }) { @@ -72,7 +58,9 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) { return (
- +

VĂ€lj pass

@@ -84,20 +72,20 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
{program?.days?.map((workout) => { - const icon = getWorkoutIcon(workout.name) + const iconName = getWorkoutIconName(workout.name) const color = getWorkoutColor(workout.name) const isSelected = selectedWorkout?.id === workout.id const exerciseCount = workout.exercises?.filter(e => e.name).length || 0 - + return ( -
handleSelect(workout)} >
- {icon} +

{workout.name}

@@ -114,7 +102,9 @@ function WorkoutSelectPage({ onBack, onSelectWorkout }) {
{isSelected && ( -
✓
+
+ +
)}
)