From 5d6166df1b11074fd9744228e44a36d9c65b3d3f Mon Sep 17 00:00:00 2001 From: echo Date: Thu, 12 Mar 2026 17:56:46 +0100 Subject: [PATCH] redesign take 2 complete fix artefacts from previous dessign --- apps/admin/data/fitai.db | Bin 172032 -> 172032 bytes apps/mobile/src/app/(tabs)/attendance.tsx | 14 +- apps/mobile/src/app/(tabs)/goals.tsx | 104 +- apps/mobile/src/app/(tabs)/index.tsx | 943 ++++++++---------- apps/mobile/src/app/(tabs)/profile.tsx | 25 +- .../mobile/src/app/(tabs)/recommendations.tsx | 25 +- apps/mobile/src/components/ActivityRing.tsx | 128 +++ apps/mobile/src/components/Badge.tsx | 57 +- apps/mobile/src/components/CustomTabBar.tsx | 56 +- .../src/components/GoalProgressCard.tsx | 23 +- apps/mobile/src/components/IconContainer.tsx | 38 +- apps/mobile/src/components/MinimalButton.tsx | 86 +- apps/mobile/src/components/MinimalCard.tsx | 42 +- apps/mobile/src/components/ProgressBar.tsx | 23 +- apps/mobile/src/components/SectionHeader.tsx | 19 +- apps/mobile/src/styles/colors.ts | 143 +-- apps/mobile/src/styles/typography.ts | 93 +- 17 files changed, 941 insertions(+), 878 deletions(-) create mode 100644 apps/mobile/src/components/ActivityRing.tsx diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index c773da183b8c36a482ef4d2d34e1d7e2cb767d62..62294b2b057da644ea4c6b091d91bb0ec162c533 100644 GIT binary patch delta 167 zcmZoTz}0YoYl1Xm_Cy(H#_Wv=%i>uUGVm|lEa=eAuanCtDDTU}$SA7Gz`!8N%Nq;h zD?|7!ETUy;RmJHQ#pM-~FV0t(d^Vp$vMjZzGCnc6B(p5lFTWr)(>l&DbJP1SrRdH7 z@;MZQJ}~g_<%0p^1duAlYh@OV9DI{Hm_NGUc2@@ M#_ihkn65Gc0LDx-ZU6uP delta 162 zcmV;T0A2rpzzTrC3XmHCX^|X50co*dre6e+01uI~5kQ9z9Bly+A58)Q0Tn3#000#Y z4Fm))c4m5WXLxgWc$3keAd}s12q|o7XJ~Y6d0$~;bZK^FPH%8!X**sqX|m#OB3oP{ zWo~43a$#;`Wn3b&{%;5%67&EMz7Kp4Iu7^_whnX-G7a>zAt2lh2RsM}A58=RllPr4 QgDakgE1m(jE1m+?0lyG7a{vGU diff --git a/apps/mobile/src/app/(tabs)/attendance.tsx b/apps/mobile/src/app/(tabs)/attendance.tsx index ea2ea4f..d003dac 100644 --- a/apps/mobile/src/app/(tabs)/attendance.tsx +++ b/apps/mobile/src/app/(tabs)/attendance.tsx @@ -103,20 +103,22 @@ export default function AttendanceScreen() { > {/* Header */} - - 📍 Attendance + + Attendance {activeCheckIn - ? "You're crushing it today! 💪" + ? "You're crushing it today!" : history.length === 0 - ? "Ready to start your fitness journey? 🚀" - : "Track your gym visits and build streaks! 🔥"} + ? "Ready to start your fitness journey?" + : "Track your gym visits and build streaks!"} diff --git a/apps/mobile/src/app/(tabs)/goals.tsx b/apps/mobile/src/app/(tabs)/goals.tsx index cd40862..c6ebe43 100644 --- a/apps/mobile/src/app/(tabs)/goals.tsx +++ b/apps/mobile/src/app/(tabs)/goals.tsx @@ -150,21 +150,26 @@ export default function GoalsScreen() { > {/* Header */} - - - 🎯 Fitness Goals + + + Goals {activeGoals.length === 0 - ? "Ready to crush some goals? 💪" + ? "Ready to crush some goals?" : activeGoals.length === 1 - ? "You're on a mission! Keep it up! 🚀" - : `${activeGoals.length} goals in progress. Legend! ⭐`} + ? "You're on a mission! Keep it up!" + : `${activeGoals.length} goals in progress. Let's go!`} 0 && ( - - + + {activeGoals.length} - 🎯 Active + ACTIVE - - + + {completedGoals.length} - {completedGoals.length >= 5 ? "🏆" : "✅"} Completed + COMPLETED - - + + {avgProgress}% - {avgProgress >= 75 ? "🔥" : "📊"} Progress + PROGRESS @@ -287,8 +316,9 @@ export default function GoalsScreen() { {/* Active Goals */} setIsModalVisible(true)} /> {activeGoals.length === 0 ? ( @@ -331,7 +361,8 @@ export default function GoalsScreen() { {completedGoals.length > 0 && ( {completedGoals.map((goal) => ( @@ -386,15 +417,15 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start", - paddingHorizontal: 24, + paddingHorizontal: 20, paddingTop: 60, - paddingBottom: 24, + paddingBottom: 20, }, debugButton: { padding: 8, }, section: { - paddingHorizontal: 24, + paddingHorizontal: 20, marginBottom: 24, }, statsRow: { @@ -405,6 +436,7 @@ const styles = StyleSheet.create({ flex: 1, alignItems: "center", paddingVertical: 20, + paddingHorizontal: 12, borderRadius: 20, }, analyticsHeader: { @@ -433,18 +465,18 @@ const styles = StyleSheet.create({ }, fabContainer: { position: "absolute", - right: 24, + right: 20, bottom: 90, }, fab: { - width: 56, - height: 56, - borderRadius: 28, + width: 64, + height: 64, + borderRadius: 22, justifyContent: "center", alignItems: "center", - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.2, - shadowRadius: 8, - elevation: 4, + shadowOffset: { width: 0, height: 6 }, + shadowOpacity: 0.35, + shadowRadius: 12, + elevation: 8, }, }); diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index dfb3dbe..ecdeb1f 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -23,6 +23,7 @@ import { ProgressBar } from "../../components/ProgressBar"; import { TrackMealModal } from "../../components/TrackMealModal"; import { AddWaterModal } from "../../components/AddWaterModal"; import { ScanFoodModal } from "../../components/ScanFoodModal"; +import { ActivityRing } from "../../components/ActivityRing"; import { checkInsToActivities, completedGoalsToActivities, @@ -36,8 +37,9 @@ import { ActivityType, } from "../../utils/activityFeed"; -const CALORIE_GOAL = 2000; // kcal -const WATER_GOAL = 2000; // ml +const CALORIE_GOAL = 2000; +const WATER_GOAL = 2000; +const WORKOUT_GOAL = 3; export default function HomeScreen() { const { user } = useUser(); @@ -52,12 +54,9 @@ export default function HomeScreen() { const [calories, setCalories] = useState(0); const [waterIntake, setWaterIntake] = useState(0); - // Animation values - const streakPulse = useRef(new Animated.Value(1)).current; const caloriesBounce = useRef(new Animated.Value(1)).current; const waterBounce = useRef(new Animated.Value(1)).current; - // Refetch statistics and goals when screen comes into focus useFocusEffect( useCallback(() => { refetchStatistics(); @@ -73,46 +72,20 @@ export default function HomeScreen() { const getGreeting = () => { const hour = new Date().getHours(); - const greetings = { - morning: [ - "Rise and Shine", - "Good Morning", - "Morning Champion", - "Ready to Crush It", - ], - afternoon: [ - "Keep It Going", - "Good Afternoon", - "Stay Strong", - "You're Doing Great", - ], - evening: [ - "Evening Warrior", - "Good Evening", - "Almost There", - "Finish Strong", - ], - }; - - const randomGreeting = (arr: string[]) => - arr[Math.floor(Math.random() * arr.length)]; - - if (hour < 12) return randomGreeting(greetings.morning); - if (hour < 18) return randomGreeting(greetings.afternoon); - return randomGreeting(greetings.evening); + if (hour < 12) return "Good Morning"; + if (hour < 18) return "Good Afternoon"; + return "Good Evening"; }; - const getMotivationalEmoji = () => { - const emojis = ["💪", "🔥", "⚡", "🎯", "🚀", "✨"]; - return emojis[Math.floor(Math.random() * emojis.length)]; - }; - - const getStreakBadge = (streak: number) => { - if (streak >= 30) return "🏆"; - if (streak >= 14) return "🔥"; - if (streak >= 7) return "⭐"; - if (streak >= 3) return "🌟"; - return "💫"; + const getMotivationalMessage = () => { + const messages = [ + "Let's crush it today! 💪", + "Ready to level up? 🔥", + "You've got this! ⚡", + "Time to shine! ✨", + "Let's make it happen! 🚀", + ]; + return messages[Math.floor(Math.random() * messages.length)]; }; const handleSaveMeal = (meal: { @@ -122,11 +95,9 @@ export default function HomeScreen() { }) => { setCalories((prev) => prev + meal.calories); setTrackMealModalVisible(false); - - // Bounce animation Animated.sequence([ Animated.timing(caloriesBounce, { - toValue: 1.2, + toValue: 1.15, duration: 150, useNativeDriver: true, }), @@ -141,11 +112,9 @@ export default function HomeScreen() { const handleAddWater = (amount: number) => { setWaterIntake((prev) => prev + amount); setAddWaterModalVisible(false); - - // Bounce animation Animated.sequence([ Animated.timing(waterBounce, { - toValue: 1.2, + toValue: 1.15, duration: 150, useNativeDriver: true, }), @@ -157,14 +126,8 @@ export default function HomeScreen() { ]).start(); }; - const handleResetCalories = () => { - setCalories(0); - }; - - const handleResetWater = () => { - setWaterIntake(0); - }; - + const handleResetCalories = () => setCalories(0); + const handleResetWater = () => setWaterIntake(0); const handleAddScannedFood = (scannedCalories: number) => { setCalories((prev) => prev + scannedCalories); setScanFoodModalVisible(false); @@ -179,131 +142,67 @@ export default function HomeScreen() { await AsyncStorage.removeItem(`water_${today}`); }; - // Load persisted data on mount useEffect(() => { const loadPersistedData = async () => { const today = new Date().toDateString(); const storedCalories = await AsyncStorage.getItem(`calories_${today}`); const storedWater = await AsyncStorage.getItem(`water_${today}`); - - if (storedCalories) { - setCalories(parseInt(storedCalories, 10)); - } - if (storedWater) { - setWaterIntake(parseInt(storedWater, 10)); - } + if (storedCalories) setCalories(parseInt(storedCalories, 10)); + if (storedWater) setWaterIntake(parseInt(storedWater, 10)); }; - loadPersistedData(); }, []); - // Persist calories to AsyncStorage whenever it changes useEffect(() => { const persistCalories = async () => { const today = new Date().toDateString(); await AsyncStorage.setItem(`calories_${today}`, calories.toString()); }; - persistCalories(); }, [calories]); - // Persist water intake to AsyncStorage whenever it changes useEffect(() => { const persistWater = async () => { const today = new Date().toDateString(); await AsyncStorage.setItem(`water_${today}`, waterIntake.toString()); }; - persistWater(); }, [waterIntake]); + useEffect(() => { + const checkAndResetIfNeeded = async () => { + const lastResetDate = await AsyncStorage.getItem("lastResetDate"); + const today = new Date().toDateString(); + if (lastResetDate !== today) { + await resetAllCounters(); + } + }; + checkAndResetIfNeeded(); + }, []); + const checkInsThisWeek = statistics?.attendance.checkInsThisWeek || 0; const currentStreak = statistics?.attendance.currentStreak || 0; + const workoutsThisWeek = checkInsThisWeek; - // Combine activities from multiple sources const recentActivities = useMemo(() => { if (!statistics || !goals) return []; - const checkInActivities = checkInsToActivities(statistics.attendance).slice( 0, 2, - ); // Only last 2 check-ins + ); const completedGoalActivities = completedGoalsToActivities(goals).slice( 0, 2, - ); // Only last 2 completed goals - const newGoalActivities = newGoalsToActivities(goals, 7); // Last 7 days - + ); + const newGoalActivities = newGoalsToActivities(goals, 7); const allActivities = combineActivities( checkInActivities, completedGoalActivities, newGoalActivities, ); - - return getRecentActivities(allActivities, 5); // Show top 5 + return getRecentActivities(allActivities, 5); }, [statistics, goals]); - // Check for midnight reset - useEffect(() => { - const checkAndResetIfNeeded = async () => { - const lastResetDate = await AsyncStorage.getItem("lastResetDate"); - const today = new Date().toDateString(); - - if (lastResetDate !== today) { - await resetAllCounters(); - } - }; - - checkAndResetIfNeeded(); - - // Calculate time until midnight - const now = new Date(); - const midnight = new Date(); - midnight.setHours(24, 0, 0, 0); - const timeUntilMidnight = midnight.getTime() - now.getTime(); - - // Set timer for midnight reset - const midnightTimer = setTimeout(async () => { - await resetAllCounters(); - - // Set up daily interval after first midnight - const dailyInterval = setInterval( - async () => { - await resetAllCounters(); - }, - 24 * 60 * 60 * 1000, - ); // 24 hours - - return () => clearInterval(dailyInterval); - }, timeUntilMidnight); - - return () => clearTimeout(midnightTimer); - }, []); - - // Streak pulse animation - useEffect(() => { - const pulse = Animated.loop( - Animated.sequence([ - Animated.timing(streakPulse, { - toValue: 1.1, - duration: 1000, - useNativeDriver: true, - }), - Animated.timing(streakPulse, { - toValue: 1, - duration: 1000, - useNativeDriver: true, - }), - ]), - ); - - if (currentStreak >= 3) { - pulse.start(); - } - - return () => pulse.stop(); - }, [currentStreak]); - return ( } > - {/* Header Section */} - - + {/* Hero Header */} + + - {getGreeting()} {getMotivationalEmoji()} + {getGreeting()} 👋 - {user?.firstName || "Athlete"} + {user?.firstName || "Champion"} + + + {getMotivationalMessage()} - {currentStreak >= 3 && ( - - - {getStreakBadge(currentStreak)} {currentStreak} Day Streak! - - - )} {user?.imageUrl ? ( - + ) : ( - + )} - {/* Daily Stats Card - More Playful */} + {/* Activity Rings - Hero Section */} - - - - - - + + + + {calories} + + } + /> + + {waterIntake} + + } + /> + + {workoutsThisWeek} + + } + /> + + + {/* Streak Banner */} + {currentStreak >= 1 && ( + + 🔥 - {checkInsThisWeek} + {currentStreak} Day Streak! - Check-ins{"\n"}This Week + Keep it going - - - - - - - - - {calories} - - - Calories{"\n"}Tracked - - - - - - - - - {getStreakBadge(currentStreak)} - - - - {currentStreak} - - - Day{"\n"}Streak - - - + )} - {/* Quick Actions - More Playful */} + {/* Quick Actions */} - + console.log("Log workout tapped")} - activeOpacity={0.7} + onPress={() => console.log("Log workout")} + activeOpacity={0.85} style={[ - styles.actionCard, - { - backgroundColor: `${colors.primary}15`, - borderColor: `${colors.primary}30`, - borderWidth: 2, - }, + styles.quickActionCard, + { backgroundColor: colors.primary }, ]} > - - - - - 💪 Workout + + + + Workout + + + Log your session setTrackMealModalVisible(true)} - activeOpacity={0.7} + activeOpacity={0.85} style={[ - styles.actionCard, - { - backgroundColor: `${colors.success}15`, - borderColor: `${colors.success}30`, - borderWidth: 2, - }, + styles.quickActionCard, + { backgroundColor: colors.success }, ]} > - - - - - 🍽️ Track Meal + + + + Meal + + + Track calories setAddWaterModalVisible(true)} - activeOpacity={0.7} - style={[ - styles.actionCard, - { - backgroundColor: `${colors.info}15`, - borderColor: `${colors.info}30`, - borderWidth: 2, - }, - ]} + activeOpacity={0.85} + style={[styles.quickActionCard, { backgroundColor: colors.info }]} > - - - - - - - 💧 Add Water + + + + Hydrate + + + Add water setScanFoodModalVisible(true)} - activeOpacity={0.7} + activeOpacity={0.85} style={[ - styles.actionCard, - { - backgroundColor: `${colors.accent}15`, - borderColor: `${colors.accent}30`, - borderWidth: 2, - }, + styles.quickActionCard, + { backgroundColor: colors.accent }, ]} > - - - - - 📱 Scan Food + + + + Scan + + + Quick add - {/* Nutrition Progress - More Playful */} + {/* Today's Progress */} = CALORIE_GOAL - ? "Goal smashed! 🎉" - : calories >= CALORIE_GOAL * 0.75 - ? "Almost there! Keep going 💪" - : "Let's fuel that body! 🔥" - } + title="Today's Progress" + subtitle="Track your daily goals" /> - + + - 🔥 - - Calories - + 🔥 + + + + Calories + + + {calories >= CALORIE_GOAL + ? "Goal reached! 🎉" + : `${CALORIE_GOAL - calories} remaining`} + + - - {calories} / {CALORIE_GOAL} + + {calories}/{CALORIE_GOAL} - - {/* Hydration Progress - More Playful */} - - 💧 - - + + 💧 + + + Hydration {waterIntake >= WATER_GOAL - ? "Perfectly hydrated! 🌊" - : "Stay hydrated, champ! 💪"} + ? "Fully hydrated! 🌊" + : `${WATER_GOAL - waterIntake}ml remaining`} - - {waterIntake} / {WATER_GOAL} ml + + {waterIntake}/{WATER_GOAL} @@ -695,39 +563,41 @@ export default function HomeScreen() { {/* Recent Activity */} {recentActivities.length === 0 ? ( - - - 🏃 - - No Recent Activity - - - Check in at the gym or complete a goal to get started! 💪 - - + + 🏃 + + No Activity Yet + + + Start by checking in at the gym or completing a goal! + ) : ( @@ -735,17 +605,11 @@ export default function HomeScreen() { const iconName = getActivityIcon(activity.type); const emoji = getActivityEmoji(activity.type); const timeStr = formatActivityTime(activity.timestamp); - const durationStr = formatDuration(activity.duration); - - // Determine color based on activity type let iconColor = colors.primary; - if (activity.type === ActivityType.GOAL_COMPLETED) { + if (activity.type === ActivityType.GOAL_COMPLETED) iconColor = colors.success; - } else if (activity.type === ActivityType.GOAL_CREATED) { + else if (activity.type === ActivityType.GOAL_CREATED) iconColor = colors.accent; - } else if (activity.type === ActivityType.GYM_CHECKIN) { - iconColor = colors.primary; - } return ( - - - + {emoji} + - {emoji} {activity.title} + {activity.title} - {durationStr && ( + {activity.duration && ( - {durationStr} + {formatDuration(activity.duration)} )} @@ -795,139 +660,119 @@ export default function HomeScreen() { )} - setTrackMealModalVisible(false)} - onSave={handleSaveMeal} - onResetData={handleResetCalories} - /> - - setAddWaterModalVisible(false)} - onAdd={handleAddWater} - onResetData={handleResetWater} - /> - - setScanFoodModalVisible(false)} - onAddFood={handleAddScannedFood} - /> - - {/* Bottom Spacer for Tab Bar */} + + setTrackMealModalVisible(false)} + onSave={handleSaveMeal} + onResetData={handleResetCalories} + /> + setAddWaterModalVisible(false)} + onAdd={handleAddWater} + onResetData={handleResetWater} + /> + setScanFoodModalVisible(false)} + onAddFood={handleAddScannedFood} + /> ); } const styles = StyleSheet.create({ - container: { - flex: 1, - }, - scrollContent: { - paddingTop: 60, - }, - header: { + container: { flex: 1 }, + scrollContent: { paddingTop: 60, paddingBottom: 20 }, + section: { paddingHorizontal: 20, marginBottom: 28 }, + + // Hero Header + heroHeader: { flexDirection: "row", justifyContent: "space-between", - alignItems: "flex-start", - paddingHorizontal: 24, + alignItems: "center", + paddingHorizontal: 20, marginBottom: 24, }, - greetingContainer: { - flex: 1, - marginRight: 16, - }, - streakBadge: { - marginTop: 8, - paddingHorizontal: 12, - paddingVertical: 6, - backgroundColor: "rgba(136, 192, 208, 0.15)", - borderRadius: 999, - alignSelf: "flex-start", - }, - streakBadgeText: { - fontSize: 13, - fontWeight: "600", - color: "#88C0D0", - }, - avatar: { - width: 56, - height: 56, - borderRadius: 28, + heroTextContainer: { flex: 1, marginRight: 16 }, + heroAvatar: { + width: 64, + height: 64, + borderRadius: 20, borderWidth: 3, - borderColor: "#88C0D0", + borderColor: "#0066FF", }, - placeholderAvatar: { - width: 56, - height: 56, - borderRadius: 28, + heroPlaceholderAvatar: { + width: 64, + height: 64, + borderRadius: 20, justifyContent: "center", alignItems: "center", borderWidth: 3, - borderColor: "#88C0D0", + borderColor: "#0066FF", }, - section: { - paddingHorizontal: 24, - marginBottom: 24, + + // Activity Rings + ringsCard: { padding: 24 }, + ringsContainer: { + flexDirection: "row", + justifyContent: "space-around", + alignItems: "center", }, - statsCard: { + streakBanner: { + flexDirection: "row", + alignItems: "center", + marginTop: 24, + padding: 16, + borderRadius: 14, + }, + + // Quick Actions + quickActionsGrid: { flexDirection: "row", flexWrap: "wrap", gap: 12 }, + quickActionCard: { + width: "47%", + padding: 20, borderRadius: 20, }, - statsRow: { - flexDirection: "row", - justifyContent: "space-between", + quickActionIcon: { + width: 52, + height: 52, + borderRadius: 14, + justifyContent: "center", alignItems: "center", - paddingVertical: 8, - }, - statItem: { - alignItems: "center", - flex: 1, - }, - divider: { - width: 1, - height: 70, - }, - emojiIcon: { - fontSize: 28, - }, - actionGrid: { - flexDirection: "row", - flexWrap: "wrap", - gap: 12, - }, - actionCard: { - width: "48%", - alignItems: "center", - paddingVertical: 24, - borderRadius: 20, }, + + // Progress Cards + progressCard: { padding: 20 }, progressHeader: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", }, - progressLabelRow: { - flexDirection: "row", + progressLabelRow: { flexDirection: "row", alignItems: "center" }, + progressIcon: { + width: 48, + height: 48, + borderRadius: 14, + justifyContent: "center", alignItems: "center", }, - activityList: { - gap: 12, - }, - activityItem: { - flexDirection: "row", + + // Activity + activityList: { gap: 12 }, + activityItem: { flexDirection: "row", alignItems: "center", padding: 16 }, + activityIconContainer: { + width: 48, + height: 48, + borderRadius: 14, + justifyContent: "center", alignItems: "center", - gap: 12, - borderRadius: 16, - }, - activityInfo: { - flex: 1, - }, - emptyActivity: { - alignItems: "center", - paddingVertical: 40, - paddingHorizontal: 20, }, + activityInfo: { flex: 1, marginLeft: 14 }, + + // Empty + emptyCard: { alignItems: "center", padding: 40 }, }); diff --git a/apps/mobile/src/app/(tabs)/profile.tsx b/apps/mobile/src/app/(tabs)/profile.tsx index 10f86ed..2945f93 100644 --- a/apps/mobile/src/app/(tabs)/profile.tsx +++ b/apps/mobile/src/app/(tabs)/profile.tsx @@ -213,7 +213,10 @@ export default function ProfileScreen() { contentContainerStyle={styles.content} > {/* Header Card */} - + {user?.imageUrl ? ( @@ -221,29 +224,36 @@ export default function ProfileScreen() { - + )} {user?.fullName || "User"} {user?.primaryEmailAddress?.emailAddress} @@ -580,8 +590,9 @@ const styles = StyleSheet.create({ flex: 1, }, content: { - padding: 24, + padding: 20, paddingTop: 60, + paddingBottom: 100, }, profileCard: { alignItems: "center", diff --git a/apps/mobile/src/app/(tabs)/recommendations.tsx b/apps/mobile/src/app/(tabs)/recommendations.tsx index 4ce68e2..801af20 100644 --- a/apps/mobile/src/app/(tabs)/recommendations.tsx +++ b/apps/mobile/src/app/(tabs)/recommendations.tsx @@ -130,19 +130,24 @@ export default function RecommendationsScreen() { > {/* Header */} - - - ✨ AI Recommendations + + + Recommendations {recommendations.length === 0 - ? "Let's create your perfect plan! 🚀" - : `${recommendations.length} plan${recommendations.length !== 1 ? "s" : ""} ready for you! 💪`} + ? "Let's create your perfect plan!" + : `${recommendations.length} plan${recommendations.length !== 1 ? "s" : ""} ready for you!`} - + {unreadCount > 0 && ( { + Animated.timing(animatedValue, { + toValue: Math.min(progress, 100), + duration: 1200, + useNativeDriver: true, + }).start(); + }, [progress]); + + const strokeDashoffset = animatedValue.interpolate({ + inputRange: [0, 100], + outputRange: [circumference, 0], + }); + + return ( + + + + + + + + {icon ? ( + {icon} + ) : ( + + {Math.round(current)} + + )} + + + + {label} + + + / {goal.toLocaleString()} + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: "center", + }, + iconContainer: { + justifyContent: "center", + alignItems: "center", + }, +}); diff --git a/apps/mobile/src/components/Badge.tsx b/apps/mobile/src/components/Badge.tsx index 17fb59c..ef31f22 100644 --- a/apps/mobile/src/components/Badge.tsx +++ b/apps/mobile/src/components/Badge.tsx @@ -10,49 +10,41 @@ type BadgeVariant = | "danger" | "info" | "primary"; -type BadgeSize = "sm" | "md"; +type BadgeSize = "sm" | "md" | "lg"; interface BadgeProps { label: string; variant?: BadgeVariant; size?: BadgeSize; style?: StyleProp; + emoji?: string; } -/** - * Badge - Pill-shaped status indicator - * - * Variants: - * - neutral: Gray badge for general labels - * - success: Green badge for positive status - * - warning: Orange/yellow badge for warnings - * - danger: Red badge for errors or critical status - * - info: Blue badge for informational status - * - primary: Primary color badge - * - * Sizes: - * - sm: 5px vertical, 10px horizontal, 11px font - * - md: 6px vertical, 12px horizontal, 13px font (default) - */ export function Badge({ label, variant = "neutral", size = "md", style, + emoji, }: BadgeProps) { const { colors } = useTheme(); const sizeStyles = { sm: { - paddingVertical: 5, - paddingHorizontal: 10, + paddingVertical: 6, + paddingHorizontal: 12, fontSize: fontSize.xs, }, md: { - paddingVertical: 6, - paddingHorizontal: 12, + paddingVertical: 8, + paddingHorizontal: 14, fontSize: fontSize.sm, }, + lg: { + paddingVertical: 10, + paddingHorizontal: 18, + fontSize: fontSize.base, + }, }; const variantStyles: Record< @@ -64,24 +56,24 @@ export function Badge({ color: colors.textSecondary, }, success: { - backgroundColor: `${colors.success}20`, // 20% opacity - color: colors.success, + backgroundColor: colors.success, + color: colors.white, }, warning: { - backgroundColor: `${colors.warning}20`, - color: colors.warning, + backgroundColor: colors.warning, + color: colors.black, }, danger: { - backgroundColor: `${colors.danger}20`, - color: colors.danger, + backgroundColor: colors.danger, + color: colors.white, }, info: { - backgroundColor: `${colors.info}20`, - color: colors.info, + backgroundColor: colors.info, + color: colors.white, }, primary: { - backgroundColor: `${colors.primary}20`, - color: colors.primary, + backgroundColor: colors.primary, + color: colors.white, }, }; @@ -103,10 +95,11 @@ export function Badge({ { color: variantStyles[variant].color, fontSize: sizeStyles[size].fontSize, - fontWeight: fontWeight.medium, + fontWeight: fontWeight.bold, }, ]} > + {emoji && `${emoji} `} {label} @@ -115,7 +108,7 @@ export function Badge({ const styles = StyleSheet.create({ badge: { - borderRadius: 999, // Full pill shape + borderRadius: 12, alignSelf: "flex-start", }, label: { diff --git a/apps/mobile/src/components/CustomTabBar.tsx b/apps/mobile/src/components/CustomTabBar.tsx index 8ade46a..301bed3 100644 --- a/apps/mobile/src/components/CustomTabBar.tsx +++ b/apps/mobile/src/components/CustomTabBar.tsx @@ -1,21 +1,10 @@ import React from "react"; -import { View, StyleSheet, TouchableOpacity } from "react-native"; +import { View, StyleSheet, TouchableOpacity, Text } from "react-native"; import { BottomTabBarProps } from "@react-navigation/bottom-tabs"; import { Ionicons } from "@expo/vector-icons"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useTheme } from "../contexts/ThemeContext"; -/** - * CustomTabBar - Minimal bottom navigation with pill indicator - * - * Design: - * - Simple flat design (no floating, no glassmorphism) - * - Clean icons with outline/filled states - * - Small pill indicator below active tab - * - 56px height (reduced from 70px) - * - No animations (just opacity fade on press) - * - Theme-aware colors - */ export function CustomTabBar({ state, descriptors, @@ -71,6 +60,23 @@ export function CustomTabBar({ } }; + const getLabel = (routeName: string) => { + switch (routeName) { + case "index": + return "Home"; + case "goals": + return "Goals"; + case "attendance": + return "Attendance"; + case "recommendations": + return "Plans"; + case "profile": + return "Profile"; + default: + return ""; + } + }; + return ( {isFocused && ( @@ -97,6 +103,17 @@ export function CustomTabBar({ /> )} + + {getLabel(route.name)} + ); })} @@ -107,8 +124,9 @@ export function CustomTabBar({ const styles = StyleSheet.create({ container: { flexDirection: "row", - height: 56, + height: 70, borderTopWidth: 1, + paddingTop: 8, }, tabItem: { flex: 1, @@ -121,9 +139,13 @@ const styles = StyleSheet.create({ justifyContent: "center", }, indicator: { - width: 24, - height: 3, - borderRadius: 999, + width: 20, + height: 4, + borderRadius: 2, + marginTop: 4, + }, + label: { + fontSize: 11, marginTop: 4, }, }); diff --git a/apps/mobile/src/components/GoalProgressCard.tsx b/apps/mobile/src/components/GoalProgressCard.tsx index 20542d4..a8d1999 100644 --- a/apps/mobile/src/components/GoalProgressCard.tsx +++ b/apps/mobile/src/components/GoalProgressCard.tsx @@ -122,15 +122,17 @@ export function GoalProgressCard({ return ( - + {/* Header */} @@ -289,13 +291,14 @@ export function GoalProgressCard({ const styles = StyleSheet.create({ card: { - marginBottom: 12, + marginBottom: 16, + borderRadius: 20, }, header: { flexDirection: "row", justifyContent: "space-between", alignItems: "flex-start", - marginBottom: 12, + marginBottom: 16, }, titleRow: { flexDirection: "row", @@ -304,23 +307,23 @@ const styles = StyleSheet.create({ }, titleContainer: { flex: 1, - marginLeft: 12, + marginLeft: 14, }, actions: { flexDirection: "row", - gap: 8, + gap: 12, }, actionButton: { - padding: 4, + padding: 6, }, progressSection: { - marginBottom: 12, + marginBottom: 16, }, progressInfo: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", - marginBottom: 8, + marginBottom: 10, }, footer: { flexDirection: "row", diff --git a/apps/mobile/src/components/IconContainer.tsx b/apps/mobile/src/components/IconContainer.tsx index 2b98ab2..6193434 100644 --- a/apps/mobile/src/components/IconContainer.tsx +++ b/apps/mobile/src/components/IconContainer.tsx @@ -3,7 +3,7 @@ import { View, StyleSheet, ViewStyle, StyleProp } from "react-native"; import { useTheme } from "../contexts/ThemeContext"; type IconContainerVariant = "plain" | "subtle" | "colored"; -type IconContainerSize = "sm" | "md" | "lg"; +type IconContainerSize = "sm" | "md" | "lg" | "xl"; interface IconContainerProps { children: React.ReactNode; @@ -13,19 +13,6 @@ interface IconContainerProps { style?: StyleProp; } -/** - * IconContainer - Clean container for icons with subtle backgrounds - * - * Variants: - * - plain: No background, just the icon - * - subtle: Light background (surfaceSecondary) - * - colored: Custom background color (pass backgroundColor prop) - * - * Sizes: - * - sm: 32px circle - * - md: 40px circle (default) - * - lg: 48px circle - */ export function IconContainer({ children, variant = "subtle", @@ -37,19 +24,24 @@ export function IconContainer({ const sizeStyles: Record = { sm: { - width: 32, - height: 32, - borderRadius: 16, + width: 36, + height: 36, + borderRadius: 10, }, md: { - width: 40, - height: 40, - borderRadius: 20, - }, - lg: { width: 48, height: 48, - borderRadius: 24, + borderRadius: 14, + }, + lg: { + width: 56, + height: 56, + borderRadius: 16, + }, + xl: { + width: 64, + height: 64, + borderRadius: 18, }, }; diff --git a/apps/mobile/src/components/MinimalButton.tsx b/apps/mobile/src/components/MinimalButton.tsx index 284dc1c..628e88b 100644 --- a/apps/mobile/src/components/MinimalButton.tsx +++ b/apps/mobile/src/components/MinimalButton.tsx @@ -11,8 +11,13 @@ import { import { useTheme } from "../contexts/ThemeContext"; import { fontSize, fontWeight } from "../styles/typography"; -type ButtonVariant = "primary" | "secondary" | "tertiary" | "danger"; -type ButtonSize = "sm" | "md" | "lg"; +type ButtonVariant = + | "primary" + | "secondary" + | "tertiary" + | "danger" + | "success"; +type ButtonSize = "sm" | "md" | "lg" | "xl"; interface MinimalButtonProps { title: string; @@ -23,22 +28,9 @@ interface MinimalButtonProps { disabled?: boolean; style?: StyleProp; textStyle?: StyleProp; + fullWidth?: boolean; } -/** - * MinimalButton - Clean button component with solid colors - * - * Variants: - * - primary: Solid primary background - * - secondary: Outlined with primary color - * - tertiary: Text only, no background - * - danger: Solid danger background - * - * Sizes: - * - sm: 12px vertical padding, 16px horizontal - * - md: 14px vertical padding, 24px horizontal (default) - * - lg: 16px vertical padding, 32px horizontal - */ export function MinimalButton({ title, onPress, @@ -48,37 +40,42 @@ export function MinimalButton({ disabled = false, style, textStyle, + fullWidth = false, }: MinimalButtonProps) { const { colors } = useTheme(); const isDisabled = disabled || loading; - // Get button styles based on variant const getButtonStyle = (): ViewStyle => { const baseStyle: ViewStyle = { - borderRadius: 10, + borderRadius: 14, alignItems: "center", justifyContent: "center", opacity: isDisabled ? 0.5 : 1, }; - // Size-specific padding const sizeStyles: Record< ButtonSize, { paddingVertical: number; paddingHorizontal: number } > = { - sm: { paddingVertical: 12, paddingHorizontal: 16 }, - md: { paddingVertical: 14, paddingHorizontal: 24 }, - lg: { paddingVertical: 16, paddingHorizontal: 32 }, + sm: { paddingVertical: 12, paddingHorizontal: 20 }, + md: { paddingVertical: 16, paddingHorizontal: 28 }, + lg: { paddingVertical: 18, paddingHorizontal: 36 }, + xl: { paddingVertical: 20, paddingHorizontal: 44 }, }; const variantStyles: Record = { primary: { backgroundColor: colors.primary, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, }, secondary: { backgroundColor: "transparent", - borderWidth: 1.5, + borderWidth: 2, borderColor: colors.primary, }, tertiary: { @@ -86,6 +83,19 @@ export function MinimalButton({ }, danger: { backgroundColor: colors.danger, + shadowColor: colors.danger, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, + }, + success: { + backgroundColor: colors.success, + shadowColor: colors.success, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 4, }, }; @@ -93,29 +103,23 @@ export function MinimalButton({ ...baseStyle, ...sizeStyles[size], ...variantStyles[variant], + ...(fullWidth && { width: "100%" }), }; }; - // Get text styles based on variant const getTextStyle = (): TextStyle => { const baseTextStyle: TextStyle = { - fontSize: fontSize.base, - fontWeight: fontWeight.semibold, + fontSize: size === "sm" ? fontSize.sm : fontSize.md, + fontWeight: fontWeight.bold, + letterSpacing: 0.5, }; const variantTextStyles: Record = { - primary: { - color: colors.white, - }, - secondary: { - color: colors.primary, - }, - tertiary: { - color: colors.primary, - }, - danger: { - color: colors.white, - }, + primary: { color: colors.white }, + secondary: { color: colors.primary }, + tertiary: { color: colors.primary }, + danger: { color: colors.white }, + success: { color: colors.white }, }; return { @@ -129,7 +133,7 @@ export function MinimalButton({ style={[getButtonStyle(), style]} onPress={onPress} disabled={isDisabled} - activeOpacity={0.7} + activeOpacity={0.85} > {loading ? ( ); } - -const styles = StyleSheet.create({ - // No static styles needed - all dynamic based on theme -}); diff --git a/apps/mobile/src/components/MinimalCard.tsx b/apps/mobile/src/components/MinimalCard.tsx index 8b0d375..5de9e1d 100644 --- a/apps/mobile/src/components/MinimalCard.tsx +++ b/apps/mobile/src/components/MinimalCard.tsx @@ -8,28 +8,22 @@ import { } from "react-native"; import { useTheme } from "../contexts/ThemeContext"; -type CardVariant = "default" | "elevated" | "bordered"; +type CardVariant = "default" | "elevated" | "bordered" | "gradient"; interface MinimalCardProps { children: React.ReactNode; variant?: CardVariant; onPress?: () => void; style?: StyleProp; + padding?: number; } -/** - * MinimalCard - Clean card component without gradients - * - * Variants: - * - default: Subtle shadow on surface background - * - elevated: More prominent shadow - * - bordered: Border instead of shadow - */ export function MinimalCard({ children, variant = "default", onPress, style, + padding = 20, }: MinimalCardProps) { const { colors } = useTheme(); @@ -37,13 +31,20 @@ export function MinimalCard({ styles.base, { backgroundColor: colors.surface, + padding: padding, }, variant === "default" && styles.default, - variant === "elevated" && styles.elevated, + variant === "elevated" && { + ...styles.elevated, + backgroundColor: colors.surfaceElevated, + }, variant === "bordered" && { borderWidth: 1, borderColor: colors.border, }, + variant === "gradient" && { + backgroundColor: colors.surfaceElevated, + }, style, ]; @@ -52,7 +53,7 @@ export function MinimalCard({ {children} @@ -64,21 +65,20 @@ export function MinimalCard({ const styles = StyleSheet.create({ base: { - borderRadius: 12, - padding: 16, + borderRadius: 20, }, default: { - shadowColor: "#000", - shadowOffset: { width: 0, height: 1 }, - shadowOpacity: 0.05, - shadowRadius: 3, - elevation: 1, - }, - elevated: { shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, - elevation: 2, + elevation: 3, + }, + elevated: { + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.12, + shadowRadius: 16, + elevation: 6, }, }); diff --git a/apps/mobile/src/components/ProgressBar.tsx b/apps/mobile/src/components/ProgressBar.tsx index 362b9dd..ce80b38 100644 --- a/apps/mobile/src/components/ProgressBar.tsx +++ b/apps/mobile/src/components/ProgressBar.tsx @@ -3,43 +3,28 @@ import { View, StyleSheet, ViewStyle, StyleProp } from "react-native"; import { useTheme } from "../contexts/ThemeContext"; interface ProgressBarProps { - progress: number; // 0-1 (e.g., 0.75 for 75%) + progress: number; color?: string; backgroundColor?: string; height?: number; borderRadius?: number; style?: StyleProp; + animated?: boolean; } -/** - * ProgressBar - Simple linear progress indicator - * - * Usage: - * - Goal progress tracking - * - Loading states - * - Completion indicators - * - * Props: - * - progress: Value between 0 and 1 (e.g., 0.75 for 75%) - * - color: Custom fill color (defaults to theme primary) - * - backgroundColor: Custom track color (defaults to theme border) - * - height: Bar height in pixels (defaults to 8) - * - borderRadius: Corner radius (defaults to 999 for full pill shape) - */ export function ProgressBar({ progress, color, backgroundColor, - height = 8, + height = 10, borderRadius = 999, style, }: ProgressBarProps) { const { colors } = useTheme(); - // Clamp progress between 0 and 1 const clampedProgress = Math.min(Math.max(progress, 0), 1); - const trackColor = backgroundColor || colors.border; + const trackColor = backgroundColor || colors.surfaceElevated; const fillColor = color || colors.primary; return ( diff --git a/apps/mobile/src/components/SectionHeader.tsx b/apps/mobile/src/components/SectionHeader.tsx index c7004bf..b3b68b6 100644 --- a/apps/mobile/src/components/SectionHeader.tsx +++ b/apps/mobile/src/components/SectionHeader.tsx @@ -17,14 +17,6 @@ interface SectionHeaderProps { style?: StyleProp; } -/** - * SectionHeader - Clean section header with optional action button - * - * Usage: - * - Divides content into logical sections - * - Optional subtitle for additional context - * - Optional action button (e.g., "See All", "Add New") - */ export function SectionHeader({ title, subtitle, @@ -44,7 +36,7 @@ export function SectionHeader({ {subtitle} @@ -53,12 +45,7 @@ export function SectionHeader({ {actionLabel && onActionPress && ( - + {actionLabel} @@ -72,7 +59,7 @@ const styles = StyleSheet.create({ flexDirection: "row", justifyContent: "space-between", alignItems: "center", - marginBottom: 12, + marginBottom: 16, }, textContainer: { flex: 1, diff --git a/apps/mobile/src/styles/colors.ts b/apps/mobile/src/styles/colors.ts index 4ca01c0..8abdf6d 100644 --- a/apps/mobile/src/styles/colors.ts +++ b/apps/mobile/src/styles/colors.ts @@ -1,7 +1,6 @@ /** - * FitAI Color System - * Nord Color Palette - A minimal, arctic-inspired palette - * https://www.nordtheme.com/ + * FitAI Color System - BOLD MODERN + * Electric Blue palette with high-energy fitness app aesthetics */ export interface ColorScheme { @@ -12,6 +11,7 @@ export interface ColorScheme { // Accent Colors accent: string; + secondary: string; terracotta: string; sand: string; @@ -21,6 +21,11 @@ export interface ColorScheme { danger: string; info: string; + // Activity Ring Colors + calories: string; + water: string; + workouts: string; + // Neutrals background: string; surface: string; @@ -39,97 +44,119 @@ export interface ColorScheme { overlay: string; overlayLight: string; - // Legacy compatibility (will be phased out) + // Gradients (as arrays) + primaryGradient: string[]; + cardGradient: string[]; + + // Legacy compatibility white: string; black: string; } /** - * Light Mode Color Palette - * Nord Snow Storm (light backgrounds) with Polar Night (dark text) + * Light Mode - Bold & Energetic */ export const lightColors: ColorScheme = { - // Primary Colors (Nord Frost - Aurora blue-green) - primary: "#88C0D0", // Nord Frost 8 (main actions, cyan) - primaryDark: "#5E81AC", // Nord Frost 10 (dark blue) - primaryLight: "#8FBCBB", // Nord Frost 7 (pale cyan) + // Primary Colors - Electric Blue + primary: "#0066FF", + primaryDark: "#0052CC", + primaryLight: "#3385FF", // Accent Colors - accent: "#81A1C1", // Nord Frost 9 (blue-gray) - terracotta: "#D08770", // Nord Aurora 12 (orange - replaces terracotta) - sand: "#EBCB8B", // Nord Aurora 13 (yellow - warm accent) + accent: "#7B2CBF", // Purple + secondary: "#FF3B7A", // Hot Pink + terracotta: "#FF6B35", // Neon Orange + sand: "#FFD60A", // Electric Yellow - // Status Colors - success: "#A3BE8C", // Nord Aurora 14 (green) - warning: "#EBCB8B", // Nord Aurora 13 (yellow) - danger: "#BF616A", // Nord Aurora 11 (red) - info: "#81A1C1", // Nord Frost 9 (blue) + // Status Colors - Vibrant + success: "#00D26A", + warning: "#FFB800", + danger: "#FF3B3B", + info: "#00B8D9", - // Neutrals (Snow Storm palette) - background: "#ECEFF4", // Nord Snow Storm 3 (lightest) - surface: "#E5E9F0", // Nord Snow Storm 2 (medium) - surfaceElevated: "#D8DEE9", // Nord Snow Storm 1 (slightly darker) + // Activity Ring Colors + calories: "#FF6B35", // Orange for calories + water: "#00B8D9", // Cyan for water + workouts: "#0066FF", // Blue for workouts - // Text (Polar Night palette) - textPrimary: "#2E3440", // Nord Polar Night 0 (darkest) - textSecondary: "#3B4252", // Nord Polar Night 1 - textTertiary: "#4C566A", // Nord Polar Night 3 + // Neutrals - Bold dark on light + background: "#F5F5F7", + surface: "#FFFFFF", + surfaceElevated: "#FFFFFF", + + // Text - High contrast dark + textPrimary: "#1A1A1A", + textSecondary: "#4A4A4A", + textTertiary: "#8E8E93", // Borders - border: "#D8DEE9", // Nord Snow Storm 1 - borderLight: "#E5E9F0", // Nord Snow Storm 2 + border: "#E5E5EA", + borderLight: "#F0F0F5", // Overlays - overlay: "rgba(46, 52, 64, 0.5)", // Polar Night 0 - overlayLight: "rgba(46, 52, 64, 0.05)", + overlay: "rgba(0, 0, 0, 0.5)", + overlayLight: "rgba(0, 0, 0, 0.03)", + + // Gradients + primaryGradient: ["#0066FF", "#0052CC"], + cardGradient: ["#FFFFFF", "#F8F8FA"], // Legacy - white: "#ECEFF4", - black: "#2E3440", + white: "#FFFFFF", + black: "#1A1A1A", }; /** - * Dark Mode Color Palette - * Nord Polar Night (dark backgrounds) with Snow Storm (light text) + * Dark Mode - Premium & Immersive */ export const darkColors: ColorScheme = { - // Primary Colors (Nord Frost - adjusted for dark mode) - primary: "#88C0D0", // Nord Frost 8 (cyan - brighter on dark) - primaryDark: "#5E81AC", // Nord Frost 10 (dark blue) - primaryLight: "#8FBCBB", // Nord Frost 7 (pale cyan) + // Primary Colors - Electric Blue (brighter on dark) + primary: "#0A84FF", + primaryDark: "#0066FF", + primaryLight: "#5AC8FA", // Accent Colors - accent: "#81A1C1", // Nord Frost 9 (blue-gray) - terracotta: "#D08770", // Nord Aurora 12 (orange) - sand: "#EBCB8B", // Nord Aurora 13 (yellow) + accent: "#BF5AF2", // Purple + secondary: "#FF375F", // Hot Pink + terracotta: "#FF9500", // Orange + sand: "#FFD60A", // Yellow // Status Colors - success: "#A3BE8C", // Nord Aurora 14 (green) - warning: "#EBCB8B", // Nord Aurora 13 (yellow) - danger: "#BF616A", // Nord Aurora 11 (red) - info: "#81A1C1", // Nord Frost 9 (blue) + success: "#30D158", + warning: "#FFD60A", + danger: "#FF453A", + info: "#64D2FF", - // Neutrals (Polar Night palette) - background: "#2E3440", // Nord Polar Night 0 (darkest) - surface: "#3B4252", // Nord Polar Night 1 (medium dark) - surfaceElevated: "#434C5E", // Nord Polar Night 2 (lighter) + // Activity Ring Colors (even brighter for dark mode) + calories: "#FF9500", + water: "#64D2FF", + workouts: "#0A84FF", - // Text (Snow Storm palette) - textPrimary: "#ECEFF4", // Nord Snow Storm 3 (lightest) - textSecondary: "#E5E9F0", // Nord Snow Storm 2 - textTertiary: "#D8DEE9", // Nord Snow Storm 1 + // Neutrals - Dark backgrounds + background: "#000000", + surface: "#1C1C1E", + surfaceElevated: "#2C2C2E", + + // Text - Bright on dark + textPrimary: "#FFFFFF", + textSecondary: "#EBEBF5", + textTertiary: "#8E8E93", // Borders - border: "#434C5E", // Nord Polar Night 2 - borderLight: "#3B4252", // Nord Polar Night 1 + border: "#38383A", + borderLight: "#48484A", // Overlays overlay: "rgba(0, 0, 0, 0.6)", - overlayLight: "rgba(236, 239, 244, 0.05)", // Snow Storm 3 + overlayLight: "rgba(255, 255, 255, 0.05)", + + // Gradients + primaryGradient: ["#0A84FF", "#0066FF"], + cardGradient: ["#1C1C1E", "#2C2C2E"], // Legacy - white: "#ECEFF4", - black: "#2E3440", + white: "#FFFFFF", + black: "#000000", }; /** diff --git a/apps/mobile/src/styles/typography.ts b/apps/mobile/src/styles/typography.ts index 37bb3a1..08e91dd 100644 --- a/apps/mobile/src/styles/typography.ts +++ b/apps/mobile/src/styles/typography.ts @@ -1,53 +1,54 @@ /** - * FitAI Typography System - * Minimalist typography with clear hierarchy using system fonts + * FitAI Typography System - BOLD MODERN + * High-impact typography with clear hierarchy using system fonts */ import { TextStyle } from "react-native"; /** - * Font Sizes - * Refined scale with fewer sizes for clearer hierarchy + * Font Sizes - Larger for bold impact */ export const fontSize = { - xs: 11, - sm: 13, - base: 15, - md: 17, // Body emphasis - lg: 20, - xl: 24, - "2xl": 28, - "3xl": 34, - "4xl": 40, + xs: 12, + sm: 14, + base: 16, + md: 18, // Body emphasis + lg: 22, + xl: 26, + "2xl": 32, + "3xl": 40, + "4xl": 52, + "5xl": 64, } as const; /** - * Font Weights + * Font Weights - Emphasize bold */ export const fontWeight = { regular: "400" as TextStyle["fontWeight"], medium: "500" as TextStyle["fontWeight"], semibold: "600" as TextStyle["fontWeight"], bold: "700" as TextStyle["fontWeight"], + extrabold: "800" as TextStyle["fontWeight"], } as const; /** * Line Heights */ export const lineHeight = { - tight: 1.2, - normal: 1.5, - relaxed: 1.7, + tight: 1.15, + normal: 1.4, + relaxed: 1.6, } as const; /** * Letter Spacing */ export const letterSpacing = { - tight: -0.5, + tight: -1, normal: 0, wide: 0.5, - wider: 1, + wider: 1.5, } as const; /** @@ -58,11 +59,14 @@ export interface TypographyPresets { h1: TextStyle; h2: TextStyle; h3: TextStyle; + h4: TextStyle; body: TextStyle; bodyEmphasis: TextStyle; label: TextStyle; stat: TextStyle; + statLarge: TextStyle; caption: TextStyle; + button: TextStyle; } export const createTypographyPresets = ( @@ -70,25 +74,33 @@ export const createTypographyPresets = ( textSecondary: string, textTertiary: string, ): TypographyPresets => ({ - // Display Text (Screen Titles) + // Display Text (Screen Titles) - Extra Bold h1: { - fontSize: fontSize["3xl"], - fontWeight: fontWeight.bold, + fontSize: fontSize["4xl"], + fontWeight: fontWeight.extrabold, letterSpacing: letterSpacing.tight, - lineHeight: fontSize["3xl"] * lineHeight.tight, + lineHeight: fontSize["4xl"] * lineHeight.tight, color: textPrimary, }, - // Section Headers + // Section Headers - Bold h2: { - fontSize: fontSize.xl, + fontSize: fontSize["2xl"], + fontWeight: fontWeight.bold, + letterSpacing: -0.5, + color: textPrimary, + }, + + // Card Titles - Semibold + h3: { + fontSize: fontSize.lg, fontWeight: fontWeight.semibold, letterSpacing: -0.3, color: textPrimary, }, - // Card Titles - h3: { + // Small Headers + h4: { fontSize: fontSize.md, fontWeight: fontWeight.semibold, color: textPrimary, @@ -112,17 +124,27 @@ export const createTypographyPresets = ( // Labels (uppercase, spaced) label: { - fontSize: fontSize.sm, - fontWeight: fontWeight.medium, - letterSpacing: letterSpacing.wide, + fontSize: fontSize.xs, + fontWeight: fontWeight.semibold, + letterSpacing: letterSpacing.wider, textTransform: "uppercase", color: textTertiary, }, - // Stats/Numbers + // Stats/Numbers - Bold Large stat: { - fontSize: fontSize["2xl"], + fontSize: fontSize["3xl"], fontWeight: fontWeight.bold, + letterSpacing: -1, + color: textPrimary, + }, + + // Large Stats (Hero numbers) + statLarge: { + fontSize: fontSize["5xl"], + fontWeight: fontWeight.extrabold, + letterSpacing: -2, + lineHeight: fontSize["5xl"] * lineHeight.tight, color: textPrimary, }, @@ -132,6 +154,13 @@ export const createTypographyPresets = ( fontWeight: fontWeight.regular, color: textTertiary, }, + + // Button Text + button: { + fontSize: fontSize.base, + fontWeight: fontWeight.bold, + letterSpacing: 0.5, + }, }); /**