diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db
index c773da1..62294b2 100644
Binary files a/apps/admin/data/fitai.db and b/apps/admin/data/fitai.db differ
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,
+ },
});
/**