diff --git a/app/(tabs)/browse.tsx b/app/(tabs)/browse.tsx
index b526e5f..aac955f 100644
--- a/app/(tabs)/browse.tsx
+++ b/app/(tabs)/browse.tsx
@@ -1,32 +1,32 @@
/**
- * TabataFit Browse Screen
- * React Native UI — wired to shared data
+ * TabataFit Browse Screen - Premium Redesign
+ * React Native UI with glassmorphism
*/
-import { View, StyleSheet, ScrollView, Pressable, Dimensions, Text as RNText } from 'react-native'
+import { View, StyleSheet, ScrollView, Pressable, Dimensions, TextInput } from 'react-native'
import { useRouter } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
-import { LinearGradient } from 'expo-linear-gradient'
import { BlurView } from 'expo-blur'
import Ionicons from '@expo/vector-icons/Ionicons'
-import { useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHaptics } from '@/src/shared/hooks'
import {
COLLECTIONS,
- PROGRAMS,
getFeaturedCollection,
- COLLECTION_COLORS,
WORKOUTS,
} from '@/src/shared/data'
-import { useTranslatedCollections, useTranslatedPrograms, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
+import { useTranslatedCollections, useTranslatedWorkouts } from '@/src/shared/data/useTranslatedData'
import { StyledText } from '@/src/shared/components/StyledText'
+import { WorkoutCard } from '@/src/shared/components/WorkoutCard'
+import { CollectionCard } from '@/src/shared/components/CollectionCard'
import { useThemeColors, BRAND } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
import { RADIUS } from '@/src/shared/constants/borderRadius'
+import type { WorkoutCategory } from '@/src/shared/types'
const { width: SCREEN_WIDTH } = Dimensions.get('window')
@@ -40,18 +40,14 @@ const FONTS = {
CAPTION_2: 11,
}
-function TextButton({ children, onPress }: { children: string; onPress?: () => void }) {
- return (
-
-
- {children}
-
-
- )
-}
-
-// New Releases: last 4 workouts
-const NEW_RELEASES = WORKOUTS.slice(-4)
+const CATEGORIES: { id: WorkoutCategory | 'all'; translationKey: string }[] = [
+ { id: 'all', translationKey: 'common:categories.all' },
+ { id: 'full-body', translationKey: 'common:categories.fullBody' },
+ { id: 'core', translationKey: 'common:categories.core' },
+ { id: 'upper-body', translationKey: 'common:categories.upperBody' },
+ { id: 'lower-body', translationKey: 'common:categories.lowerBody' },
+ { id: 'cardio', translationKey: 'common:categories.cardio' },
+]
// ═══════════════════════════════════════════════════════════════════════════
// MAIN SCREEN
@@ -65,10 +61,32 @@ export default function BrowseScreen() {
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
+ const [searchQuery, setSearchQuery] = useState('')
+ const [selectedCategory, setSelectedCategory] = useState('all')
+
const featuredCollection = getFeaturedCollection()
const translatedCollections = useTranslatedCollections(COLLECTIONS)
- const translatedPrograms = useTranslatedPrograms(PROGRAMS)
- const translatedNewReleases = useTranslatedWorkouts(NEW_RELEASES)
+ const translatedWorkouts = useTranslatedWorkouts(WORKOUTS)
+
+ // Filter workouts based on search and category
+ const filteredWorkouts = useMemo(() => {
+ let filtered = translatedWorkouts
+
+ if (searchQuery.trim()) {
+ const query = searchQuery.toLowerCase()
+ filtered = filtered.filter(
+ (w) =>
+ w.title.toLowerCase().includes(query) ||
+ w.category.toLowerCase().includes(query)
+ )
+ }
+
+ if (selectedCategory !== 'all') {
+ filtered = filtered.filter((w) => w.category === selectedCategory)
+ }
+
+ return filtered
+ }, [translatedWorkouts, searchQuery, selectedCategory])
const handleWorkoutPress = (id: string) => {
haptics.buttonTap()
@@ -88,126 +106,151 @@ export default function BrowseScreen() {
showsVerticalScrollIndicator={false}
>
{/* Header */}
- {t('screens:browse.title')}
+
+
+ {t('screens:browse.title')}
+
+
+
+ {/* Search Bar */}
+
+
+
+
+ {searchQuery.length > 0 && (
+ setSearchQuery('')}
+ hitSlop={8}
+ >
+
+
+ )}
+
+
+ {/* Category Filter Chips */}
+
+ {CATEGORIES.map((cat) => (
+ {
+ haptics.buttonTap()
+ setSelectedCategory(cat.id)
+ }}
+ >
+ {selectedCategory === cat.id && (
+
+ )}
+
+ {t(cat.translationKey)}
+
+
+ ))}
+
{/* Featured Collection */}
- {featuredCollection && (
- handleCollectionPress(featuredCollection.id)}>
-
+
+
+ {t('screens:browse.featured')}
+
+
+ handleCollectionPress(featuredCollection.id)}
/>
-
-
-
- {t('screens:browse.featured')}
-
-
-
- {t(`content:collections.${featuredCollection.id}.title`, { defaultValue: featuredCollection.title })}
- {t(`content:collections.${featuredCollection.id}.description`, { defaultValue: featuredCollection.description })}
-
-
-
-
- {t('plurals.workout', { count: featuredCollection.workoutIds.length })}
-
-
-
-
+
)}
{/* Collections Grid */}
-
- {t('screens:browse.collections')}
-
- {translatedCollections.map((collection) => {
- const color = COLLECTION_COLORS[collection.id] ?? BRAND.PRIMARY
- return (
-
+
+
+ {t('screens:browse.collections')}
+
+
+
+ {translatedCollections.map((collection) => (
+ handleCollectionPress(collection.id)}
- >
-
-
- {collection.icon}
-
-
- {collection.title}
-
-
- {t('plurals.workout', { count: collection.workoutIds.length })}
-
-
- )
- })}
+ />
+ ))}
+
-
+ )}
- {/* Programs */}
+ {/* All Workouts Grid */}
- {t('screens:browse.programs')}
- {t('seeAll')}
+
+ {searchQuery ? t('screens:browse.searchResults') || 'Results' : t('screens:browse.allWorkouts') || 'All Workouts'}
+
+
+ {filteredWorkouts.length} {t('plurals.workout', { count: filteredWorkouts.length })}
+
-
- {translatedPrograms.map((program) => (
-
-
-
-
- {t(`levels.${program.level.toLowerCase()}`)}
-
-
-
- {program.title}
-
-
-
-
- {t('screens:browse.weeksCount', { count: program.weeks })}
-
-
-
- {t('screens:browse.timesPerWeek', { count: program.workoutsPerWeek })}
-
-
-
- ))}
-
-
-
- {/* New Releases */}
-
-
- {t('screens:browse.newReleases')}
-
- {translatedNewReleases.map((workout) => (
- handleWorkoutPress(workout.id)}
- >
-
-
-
-
- {workout.title}
-
- {t('durationLevel', { duration: workout.duration, level: t(`levels.${workout.level.toLowerCase()}`) })}
-
-
-
-
- ))}
+ {filteredWorkouts.length > 0 ? (
+
+ {filteredWorkouts.map((workout) => (
+ handleWorkoutPress(workout.id)}
+ />
+ ))}
+
+ ) : (
+
+
+
+ {t('screens:browse.noResults') || 'No workouts found'}
+
+
+ {t('screens:browse.tryDifferentSearch') || 'Try a different search or category'}
+
+
+ )}
@@ -218,8 +261,6 @@ export default function BrowseScreen() {
// STYLES
// ═══════════════════════════════════════════════════════════════════════════
-const COLLECTION_CARD_WIDTH = (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[3]) / 2
-
function createStyles(colors: ThemeColors) {
return StyleSheet.create({
container: {
@@ -233,52 +274,57 @@ function createStyles(colors: ThemeColors) {
paddingHorizontal: LAYOUT.SCREEN_PADDING,
},
- // Featured Collection
- featuredCard: {
- height: 200,
- borderRadius: RADIUS.GLASS_CARD,
- overflow: 'hidden',
- marginBottom: SPACING[8],
- marginTop: SPACING[4],
- ...colors.shadow.lg,
- },
- featuredBadge: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: 'rgba(0, 0, 0, 0.3)',
- paddingHorizontal: SPACING[2],
- paddingVertical: SPACING[1],
- borderRadius: RADIUS.SM,
- alignSelf: 'flex-start',
- margin: SPACING[4],
- gap: SPACING[1],
- },
- featuredBadgeText: {
- fontSize: 11,
- fontWeight: 'bold',
- color: '#FFFFFF',
- },
- featuredInfo: {
- position: 'absolute',
- bottom: SPACING[5],
- left: SPACING[5],
- right: SPACING[5],
- },
- featuredStats: {
- flexDirection: 'row',
- gap: SPACING[4],
- marginTop: SPACING[3],
- },
- featuredStat: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: SPACING[1],
+ // Header
+ header: {
+ marginBottom: SPACING[4],
},
- // Section
- section: {
+ // Search Bar
+ searchContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ height: 48,
+ borderRadius: RADIUS.LG,
+ borderWidth: 1,
+ borderColor: colors.border.glass,
+ paddingHorizontal: SPACING[4],
+ marginBottom: SPACING[4],
+ overflow: 'hidden',
+ },
+ searchInput: {
+ flex: 1,
+ marginLeft: SPACING[3],
+ marginRight: SPACING[2],
+ fontSize: FONTS.HEADLINE,
+ color: colors.text.primary,
+ height: '100%',
+ },
+
+ // Categories
+ categoriesContainer: {
marginBottom: SPACING[6],
},
+ categoriesScroll: {
+ gap: SPACING[2],
+ paddingRight: SPACING[4],
+ },
+ categoryChip: {
+ paddingHorizontal: SPACING[4],
+ paddingVertical: SPACING[2],
+ borderRadius: RADIUS.FULL,
+ overflow: 'hidden',
+ borderWidth: 1,
+ borderColor: colors.border.glass,
+ },
+ categoryChipActive: {
+ borderColor: BRAND.PRIMARY,
+ backgroundColor: `${BRAND.PRIMARY}30`,
+ },
+
+ // Sections
+ section: {
+ marginBottom: SPACING[8],
+ },
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
@@ -286,96 +332,25 @@ function createStyles(colors: ThemeColors) {
marginBottom: SPACING[4],
},
- // Collections Grid
+ // Collections
collectionsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: SPACING[3],
- marginTop: SPACING[3],
- },
- collectionCard: {
- width: COLLECTION_CARD_WIDTH,
- paddingVertical: SPACING[4],
- paddingHorizontal: SPACING[3],
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- borderWidth: 1,
- borderColor: colors.border.glass,
- gap: SPACING[1],
- },
- collectionIconBg: {
- width: 44,
- height: 44,
- borderRadius: 12,
- alignItems: 'center',
- justifyContent: 'center',
- marginBottom: SPACING[2],
- },
- collectionEmoji: {
- fontSize: 22,
},
- // Programs
- programsScroll: {
+ // Workouts Grid
+ workoutsGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
gap: SPACING[3],
},
- programCard: {
- width: 200,
- height: 140,
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- padding: SPACING[4],
- borderWidth: 1,
- borderColor: colors.border.glass,
- },
- programHeader: {
- flexDirection: 'row',
- justifyContent: 'flex-end',
- marginBottom: SPACING[2],
- },
- programLevelBadge: {
- backgroundColor: 'rgba(255, 107, 53, 0.2)',
- paddingHorizontal: SPACING[2],
- paddingVertical: SPACING[1],
- borderRadius: RADIUS.SM,
- },
- programMeta: {
- flexDirection: 'row',
- gap: SPACING[3],
- marginTop: SPACING[3],
- },
- programMetaItem: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: SPACING[1],
- },
- // New Releases
- releaseRow: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingVertical: SPACING[3],
- paddingHorizontal: SPACING[4],
- backgroundColor: colors.bg.surface,
- borderRadius: RADIUS.LG,
- marginBottom: SPACING[2],
- gap: SPACING[3],
- },
- releaseAvatar: {
- width: 44,
- height: 44,
- borderRadius: 22,
+ // Empty State
+ emptyState: {
alignItems: 'center',
justifyContent: 'center',
- },
- releaseInitial: {
- fontSize: 18,
- fontWeight: '700',
- color: '#FFFFFF',
- },
- releaseInfo: {
- flex: 1,
- gap: 2,
+ paddingVertical: SPACING[12],
},
})
}
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 91bbc31..363d36f 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,16 +1,16 @@
/**
- * TabataFit Home Screen
- * React Native UI — wired to shared data
+ * TabataFit Home Screen - Premium Redesign
+ * React Native UI with glassmorphism
*/
-import { View, StyleSheet, ScrollView, Pressable, Dimensions, Text as RNText, Image as RNImage } from 'react-native'
+import { View, StyleSheet, ScrollView, Pressable, Dimensions, Text as RNText } from 'react-native'
import { useRouter } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { LinearGradient } from 'expo-linear-gradient'
import { BlurView } from 'expo-blur'
import Ionicons from '@expo/vector-icons/Ionicons'
-import { useMemo } from 'react'
+import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useHaptics } from '@/src/shared/hooks'
import { useUserStore, useActivityStore } from '@/src/shared/stores'
@@ -22,50 +22,35 @@ import {
} from '@/src/shared/data'
import { useTranslatedWorkouts, useTranslatedCollections } from '@/src/shared/data/useTranslatedData'
import { StyledText } from '@/src/shared/components/StyledText'
+import { WorkoutCard } from '@/src/shared/components/WorkoutCard'
+import { CollectionCard } from '@/src/shared/components/CollectionCard'
import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
import { RADIUS } from '@/src/shared/constants/borderRadius'
+import type { WorkoutCategory } from '@/src/shared/types'
const { width: SCREEN_WIDTH } = Dimensions.get('window')
-const CARD_WIDTH = SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2
const FONTS = {
LARGE_TITLE: 34,
TITLE: 28,
TITLE_2: 22,
HEADLINE: 17,
- BODY: 17,
SUBHEADLINE: 15,
CAPTION_1: 12,
CAPTION_2: 11,
}
-// ═══════════════════════════════════════════════════════════════════════════
-// HELPERS
-// ═══════════════════════════════════════════════════════════════════════════
-
-function PrimaryButton({ children, onPress }: { children: string; onPress?: () => void }) {
- const colors = useThemeColors()
- const styles = useMemo(() => createStyles(colors), [colors])
- return (
-
-
- {children}
-
- )
-}
-
-function PlainButton({ children, onPress }: { children: string; onPress?: () => void }) {
- const colors = useThemeColors()
- const styles = useMemo(() => createStyles(colors), [colors])
- return (
-
- {children}
-
- )
-}
+const CATEGORIES: { id: WorkoutCategory | 'all'; key: string }[] = [
+ { id: 'all', key: 'all' },
+ { id: 'full-body', key: 'fullBody' },
+ { id: 'core', key: 'core' },
+ { id: 'upper-body', key: 'upperBody' },
+ { id: 'lower-body', key: 'lowerBody' },
+ { id: 'cardio', key: 'cardio' },
+]
// ═══════════════════════════════════════════════════════════════════════════
// MAIN SCREEN
@@ -82,8 +67,10 @@ export default function HomeScreen() {
const history = useActivityStore((s) => s.history)
const recentWorkouts = useMemo(() => history.slice(0, 3), [history])
+ const [selectedCategory, setSelectedCategory] = useState('all')
+
const featured = getFeaturedWorkouts()[0] ?? WORKOUTS[0]
- const popular = getPopularWorkouts(4)
+ const popular = getPopularWorkouts(6)
const translatedPopular = useTranslatedWorkouts(popular)
const translatedCollections = useTranslatedCollections(COLLECTIONS)
@@ -94,13 +81,21 @@ export default function HomeScreen() {
return t('greetings.evening')
})()
- const featuredTitle = t(`content:workouts.${featured.id}`, { defaultValue: featured.title })
-
const handleWorkoutPress = (id: string) => {
haptics.buttonTap()
router.push(`/workout/${id}`)
}
+ const handleCollectionPress = (id: string) => {
+ haptics.buttonTap()
+ router.push(`/collection/${id}`)
+ }
+
+ const filteredWorkouts = useMemo(() => {
+ if (selectedCategory === 'all') return translatedPopular
+ return translatedPopular.filter((w) => w.category === selectedCategory)
+ }, [translatedPopular, selectedCategory])
+
return (
- {/* Header */}
-
-
-
- {greeting}
-
+ {/* Hero Section */}
+
+
+ {greeting}
+
+
{userName}
+
+
+
-
-
-
- {/* Featured */}
- handleWorkoutPress(featured.id)}
- >
- {featured.thumbnailUrl ? (
-
- ) : (
-
- )}
-
-
-
- {'🔥 ' + t('screens:home.featured')}
-
-
-
-
- {featuredTitle}
-
-
- {t('workoutMeta', { duration: featured.duration, level: t(`levels.${featured.level.toLowerCase()}`), calories: featured.calories })}
-
-
-
- handleWorkoutPress(featured.id)}>{t('start')}
-
-
-
-
-
-
-
- {/* Continue Watching — from activity store */}
- {recentWorkouts.length > 0 && (
-
-
- {t('screens:home.recent')}
- {t('seeAll')}
-
-
- {recentWorkouts.map((result) => {
- const workout = WORKOUTS.find(w => w.id === result.workoutId)
- if (!workout) return null
- const workoutTitle = t(`content:workouts.${workout.id}`, { defaultValue: workout.title })
- return (
- handleWorkoutPress(result.workoutId)}
- >
-
-
-
-
- {workoutTitle}
-
- {t('calMin', { calories: result.calories, duration: result.durationMinutes })}
-
-
- )
- })}
-
-
- )}
-
- {/* Popular This Week */}
+ {/* Featured Workout */}
+
+
+
+ {t('screens:home.featured')}
+
+
+ handleWorkoutPress(featured.id)}
+ />
+
+
+ {/* Category Filter */}
- {t('screens:home.popularThisWeek')}
- {translatedPopular.map((item) => (
+ {CATEGORIES.map((cat) => (
handleWorkoutPress(item.id)}
+ key={cat.id}
+ style={[
+ styles.categoryChip,
+ selectedCategory === cat.id && styles.categoryChipActive,
+ ]}
+ onPress={() => {
+ haptics.buttonTap()
+ setSelectedCategory(cat.id)
+ }}
>
-
-
-
- {item.title}
- {t('units.minUnit', { count: item.duration })}
+ {selectedCategory === cat.id && (
+
+ )}
+
+ {t(`categories.${cat.key}`)}
+
))}
- {/* Collections */}
+ {/* Popular Workouts - Horizontal */}
+ {filteredWorkouts.length > 0 && (
+
+
+
+ {t('screens:home.popularThisWeek')}
+
+
+
+ {t('seeAll')}
+
+
+
+
+ {filteredWorkouts.map((workout) => (
+ handleWorkoutPress(workout.id)}
+ />
+ ))}
+
+
+ )}
+
+ {/* Collections Grid */}
- {t('screens:home.collections')}
- {translatedCollections.map((item) => (
- { haptics.buttonTap(); router.push(`/collection/${item.id}`) }}>
-
-
- {item.icon}
-
- {item.title}
-
- {t('plurals.workout', { count: item.workoutIds.length }) + ' \u00B7 ' + item.description}
-
-
-
-
-
- ))}
+
+
+ {t('screens:home.collections')}
+
+
+
+ {translatedCollections.map((collection) => (
+ handleCollectionPress(collection.id)}
+ />
+ ))}
+
@@ -276,89 +244,25 @@ function createStyles(colors: ThemeColors) {
paddingHorizontal: LAYOUT.SCREEN_PADDING,
},
- // Header
- header: {
+ // Hero Section
+ heroSection: {
+ marginBottom: SPACING[6],
+ },
+ heroHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
- marginBottom: SPACING[6],
+ marginTop: SPACING[2],
+ },
+ heroTitle: {
+ flex: 1,
+ marginRight: SPACING[3],
},
profileButton: {
- width: 40,
- height: 40,
- alignItems: 'center',
- justifyContent: 'center',
- },
-
- // Buttons
- primaryButton: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: BRAND.PRIMARY,
- paddingHorizontal: SPACING[4],
- paddingVertical: SPACING[3],
- borderRadius: RADIUS.SM,
- },
- buttonIcon: {
- marginRight: SPACING[2],
- },
- primaryButtonText: {
- fontSize: 14,
- fontWeight: '600',
- color: '#FFFFFF',
- },
- plainButtonText: {
- fontSize: FONTS.BODY,
- color: BRAND.PRIMARY,
- },
-
- // Featured
- featuredCard: {
- width: CARD_WIDTH,
- height: 220,
- borderRadius: RADIUS.GLASS_CARD,
- overflow: 'hidden',
- marginBottom: SPACING[8],
- ...colors.shadow.lg,
- },
- featuredBadge: {
- position: 'absolute',
- top: SPACING[4],
- left: SPACING[4],
- backgroundColor: 'rgba(255, 255, 255, 0.15)',
- paddingHorizontal: SPACING[3],
- paddingVertical: SPACING[1],
- borderRadius: RADIUS.SM,
- borderWidth: 1,
- borderColor: 'rgba(255, 255, 255, 0.2)',
- },
- featuredBadgeText: {
- fontSize: 11,
- fontWeight: 'bold',
- color: '#FFFFFF',
- },
- featuredContent: {
- position: 'absolute',
- bottom: 0,
- left: 0,
- right: 0,
- padding: SPACING[5],
- },
- featuredButtons: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: SPACING[3],
- marginTop: SPACING[4],
- },
- saveButton: {
width: 44,
height: 44,
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
- borderRadius: 22,
- borderWidth: 1,
- borderColor: 'rgba(255, 255, 255, 0.15)',
},
// Sections
@@ -371,61 +275,36 @@ function createStyles(colors: ThemeColors) {
alignItems: 'center',
marginBottom: SPACING[4],
},
- horizontalScroll: {
+
+ // Categories
+ categoriesScroll: {
+ gap: SPACING[2],
+ paddingRight: SPACING[4],
+ },
+ categoryChip: {
+ paddingHorizontal: SPACING[4],
+ paddingVertical: SPACING[2],
+ borderRadius: RADIUS.FULL,
+ overflow: 'hidden',
+ borderWidth: 1,
+ borderColor: colors.border.glass,
+ },
+ categoryChipActive: {
+ borderColor: BRAND.PRIMARY,
+ backgroundColor: `${BRAND.PRIMARY}30`,
+ },
+
+ // Workouts Scroll
+ workoutsScroll: {
gap: SPACING[3],
+ paddingRight: SPACING[4],
},
- // Continue Card
- continueCard: {
- width: 140,
- },
- continueThumb: {
- width: 140,
- height: 200,
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- alignItems: 'center',
- justifyContent: 'center',
- marginBottom: SPACING[2],
- },
-
- // Popular Card
- popularCard: {
- width: 120,
- },
- popularThumb: {
- width: 120,
- height: 120,
- borderRadius: RADIUS.LG,
- alignItems: 'center',
- justifyContent: 'center',
- marginBottom: SPACING[2],
- borderWidth: 1,
- borderColor: colors.border.glass,
- backgroundColor: colors.bg.overlay1,
- },
-
- // Collection Card
- collectionCard: {
- height: 80,
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- marginBottom: SPACING[3],
- borderWidth: 1,
- borderColor: colors.border.glass,
- },
- collectionContent: {
- flex: 1,
+ // Collections Grid
+ collectionsGrid: {
flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: SPACING[5],
- },
- collectionIcon: {
- fontSize: 28,
- marginRight: SPACING[4],
- },
- collectionText: {
- flex: 1,
+ flexWrap: 'wrap',
+ gap: SPACING[3],
},
})
}
diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx
index 4639f36..46d781f 100644
--- a/app/(tabs)/profile.tsx
+++ b/app/(tabs)/profile.tsx
@@ -1,40 +1,62 @@
/**
- * TabataFit Profile Screen
- * SwiftUI-first settings with native iOS look
+ * TabataFit Profile Screen — Premium React Native
+ * Apple Fitness+ inspired design, pure React Native components
*/
-import { View, StyleSheet, ScrollView } from 'react-native'
import { useRouter } from 'expo-router'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
import {
- Host,
- List,
- Section,
+ View,
+ ScrollView,
+ StyleSheet,
+ TouchableOpacity,
Switch,
- LabeledContent,
- DateTimePicker,
- Button,
- VStack,
- Text,
-} from '@expo/ui/swift-ui'
-import { useMemo } from 'react'
+ Text as RNText,
+ TextStyle,
+} from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import * as Linking from 'expo-linking'
+import Constants from 'expo-constants'
import { useTranslation } from 'react-i18next'
+import { useMemo } from 'react'
import { useUserStore } from '@/src/shared/stores'
import { requestNotificationPermissions, usePurchases } from '@/src/shared/hooks'
-import { StyledText } from '@/src/shared/components/StyledText'
-
import { useThemeColors, BRAND } from '@/src/shared/theme'
import type { ThemeColors } from '@/src/shared/theme/types'
-import { SPACING, LAYOUT } from '@/src/shared/constants/spacing'
-const FONTS = {
- LARGE_TITLE: 34,
- TITLE_2: 22,
- CAPTION_1: 12,
+// ═══════════════════════════════════════════════════════════════════════════
+// STYLED TEXT COMPONENT
+// ═══════════════════════════════════════════════════════════════════════════
+
+interface TextProps {
+ children: React.ReactNode
+ style?: TextStyle
+ size?: number
+ weight?: 'normal' | 'bold' | '600' | '700' | '800' | '900'
+ color?: string
+ center?: boolean
+}
+
+function Text({ children, style, size, weight, color, center }: TextProps) {
+ const colors = useThemeColors()
+ return (
+
+ {children}
+
+ )
}
// ═══════════════════════════════════════════════════════════════════════════
-// MAIN SCREEN
+// COMPONENT: PROFILE SCREEN
// ═══════════════════════════════════════════════════════════════════════════
export default function ProfileScreen() {
@@ -46,10 +68,28 @@ export default function ProfileScreen() {
const profile = useUserStore((s) => s.profile)
const settings = useUserStore((s) => s.settings)
const updateSettings = useUserStore((s) => s.updateSettings)
- const { restorePurchases } = usePurchases()
+ const updateProfile = useUserStore((s) => s.updateProfile)
+ const { restorePurchases, isPremium } = usePurchases()
- const isPremium = profile.subscription !== 'free'
const planLabel = isPremium ? 'TabataFit+' : t('profile.freePlan')
+ const avatarInitial = profile.name?.[0]?.toUpperCase() || 'U'
+
+ // Mock stats (replace with real data from activityStore when available)
+ const stats = {
+ workouts: 47,
+ streak: 12,
+ calories: 12500,
+ }
+
+ const handleSignOut = () => {
+ updateProfile({
+ name: '',
+ email: '',
+ subscription: 'free',
+ onboardingCompleted: false,
+ })
+ router.replace('/onboarding')
+ }
const handleRestore = async () => {
await restorePurchases()
@@ -63,43 +103,24 @@ export default function ProfileScreen() {
updateSettings({ reminders: enabled })
}
- const handleTimeChange = (date: Date) => {
- const hh = String(date.getHours()).padStart(2, '0')
- const mm = String(date.getMinutes()).padStart(2, '0')
- updateSettings({ reminderTime: `${hh}:${mm}` })
+ const handleRateApp = () => {
+ Linking.openURL('https://apps.apple.com/app/tabatafit/id1234567890')
}
- // Build initial date string for the picker (today at reminderTime)
- const today = new Date()
- const [rh, rm] = settings.reminderTime.split(':').map(Number)
- const pickerDate = new Date(today.getFullYear(), today.getMonth(), today.getDate(), rh, rm)
- const pickerInitial = pickerDate.toISOString()
+ const handleContactUs = () => {
+ Linking.openURL('mailto:contact@tabatafit.app')
+ }
- // Calculate total height for single SwiftUI island
- // insetGrouped style: ~50px top/bottom margins, section header ~35px, row ~44px
- const basePadding = 100 // top + bottom margins for insetGrouped
+ const handlePrivacyPolicy = () => {
+ router.push('/privacy')
+ }
- // Account section
- const accountRows = 1 + (isPremium ? 1 : 0) // plan, [+ restore]
- const accountHeight = 35 + accountRows * 44
+ const handleFAQ = () => {
+ Linking.openURL('https://tabatafit.app/faq')
+ }
- // Upgrade section (free users only)
- const upgradeHeight = isPremium ? 0 : 35 + 80 // header + VStack content
-
- // Workout section
- const workoutHeight = 35 + 3 * 44 // haptics, sound, voice
-
- // Notifications section
- const notificationRows = settings.reminders ? 2 : 1
- const notificationHeight = 35 + notificationRows * 44
-
- // About section
- const aboutHeight = 35 + 2 * 44 // version, privacy
-
- // Sign out section
- const signOutHeight = 44 // single button row
-
- const totalHeight = basePadding + accountHeight + upgradeHeight + workoutHeight + notificationHeight + aboutHeight + signOutHeight
+ // App version
+ const appVersion = Constants.expoConfig?.version ?? '1.0.0'
return (
@@ -108,125 +129,195 @@ export default function ProfileScreen() {
contentContainerStyle={[styles.scrollContent, { paddingBottom: insets.bottom + 100 }]}
showsVerticalScrollIndicator={false}
>
- {/* Header */}
-
- {t('profile.title')}
-
-
- {/* Profile Header Card */}
-
+ {/* ════════════════════════════════════════════════════════════════════
+ PROFILE HEADER CARD
+ ═══════════════════════════════════════════════════════════════════ */}
+
+
+ {/* Avatar with gradient background */}
-
- {profile.name?.[0] || '?'}
-
+
+ {avatarInitial}
+
-
-
+
+ {/* Name & Plan */}
+
+
{profile.name || t('profile.guest')}
-
- {isPremium && (
-
-
- {planLabel}
-
-
- )}
+
+
+
+ {planLabel}
+
+ {isPremium && (
+
+ ✓
+
+ )}
+
+
+
+ {/* Stats Row */}
+
+
+
+ 🔥 {stats.workouts}
+
+
+ {t('profile.statsWorkouts')}
+
+
+
+
+ 📅 {stats.streak}
+
+
+ {t('profile.statsStreak')}
+
+
+
+
+ ⚡️ {Math.round(stats.calories / 1000)}k
+
+
+ {t('profile.statsCalories')}
+
+
+
- {/* All Settings in Single SwiftUI Island */}
-
-
- {/* Account Section */}
-
-
- {planLabel}
-
- {isPremium && (
-
- )}
-
+ {/* ════════════════════════════════════════════════════════════════════
+ UPGRADE CTA (FREE USERS ONLY)
+ ═══════════════════════════════════════════════════════════════════ */}
+ {!isPremium && (
+
+ router.push('/paywall')}
+ >
+
+
+ ✨ {t('profile.upgradeTitle')}
+
+
+ {t('profile.upgradeDescription')}
+
+
+
+ {t('profile.learnMore')} →
+
+
+
+ )}
- {/* Upgrade CTA for Free Users */}
- {!isPremium && (
-
-
-
- {t('profile.upgradeTitle')}
-
-
- {t('profile.upgradeDescription')}
-
-
-
-
- )}
+ {/* ════════════════════════════════════════════════════════════════════
+ WORKOUT SETTINGS
+ ═══════════════════════════════════════════════════════════════════ */}
+ {t('profile.sectionWorkout')}
+
+
+ {t('profile.hapticFeedback')}
+ updateSettings({ haptics: v })}
+ trackColor={{ false: colors.bg.overlay1, true: BRAND.PRIMARY }}
+ thumbColor="#FFFFFF"
+ />
+
+
+ {t('profile.soundEffects')}
+ updateSettings({ soundEffects: v })}
+ trackColor={{ false: colors.bg.overlay1, true: BRAND.PRIMARY }}
+ thumbColor="#FFFFFF"
+ />
+
+
+ {t('profile.voiceCoaching')}
+ updateSettings({ voiceCoaching: v })}
+ trackColor={{ false: colors.bg.overlay1, true: BRAND.PRIMARY }}
+ thumbColor="#FFFFFF"
+ />
+
+
- {/* Workout Settings */}
-
- updateSettings({ haptics: v })}
- color={BRAND.PRIMARY}
- />
- updateSettings({ soundEffects: v })}
- color={BRAND.PRIMARY}
- />
- updateSettings({ voiceCoaching: v })}
- color={BRAND.PRIMARY}
- />
-
+ {/* ════════════════════════════════════════════════════════════════════
+ NOTIFICATIONS
+ ═══════════════════════════════════════════════════════════════════ */}
+ {t('profile.sectionNotifications')}
+
+
+ {t('profile.dailyReminders')}
+
+
+ {settings.reminders && (
+
+ {t('profile.reminderTime')}
+ {settings.reminderTime}
+
+ )}
+
- {/* Notification Settings */}
-
-
- {settings.reminders && (
-
-
-
- )}
-
+ {/* ════════════════════════════════════════════════════════════════════
+ ABOUT
+ ═══════════════════════════════════════════════════════════════════ */}
+ {t('profile.sectionAbout')}
+
+
+ {t('profile.version')}
+ {appVersion}
+
+
+ {t('profile.rateApp')}
+ ›
+
+
+ {t('profile.contactUs')}
+ ›
+
+
+ {t('profile.faq')}
+ ›
+
+
+ {t('profile.privacyPolicy')}
+ ›
+
+
- {/* About Section */}
-
-
- 1.0.0
-
-
-
+ {/* ════════════════════════════════════════════════════════════════════
+ ACCOUNT (PREMIUM USERS ONLY)
+ ═══════════════════════════════════════════════════════════════════ */}
+ {isPremium && (
+ <>
+ {t('profile.sectionAccount')}
+
+
+ {t('profile.restorePurchases')}
+ ›
+
+
+ >
+ )}
- {/* Sign Out */}
-
-
-
-
-
+ {/* ════════════════════════════════════════════════════════════════════
+ SIGN OUT
+ ═══════════════════════════════════════════════════════════════════ */}
+
+
+ {t('profile.signOut')}
+
+
)
@@ -246,34 +337,107 @@ function createStyles(colors: ThemeColors) {
flex: 1,
},
scrollContent: {
- paddingHorizontal: LAYOUT.SCREEN_PADDING,
+ flexGrow: 1,
},
-
- // Profile Header
- profileHeader: {
- flexDirection: 'row',
+ section: {
+ marginHorizontal: 16,
+ marginTop: 20,
+ backgroundColor: colors.bg.surface,
+ borderRadius: 10,
+ overflow: 'hidden',
+ },
+ sectionHeader: {
+ fontSize: 13,
+ fontWeight: '600',
+ color: colors.text.tertiary,
+ textTransform: 'uppercase',
+ marginLeft: 32,
+ marginTop: 20,
+ marginBottom: 8,
+ },
+ headerContainer: {
alignItems: 'center',
- paddingVertical: SPACING[5],
- gap: SPACING[4],
+ paddingVertical: 24,
+ paddingHorizontal: 16,
},
avatarContainer: {
- width: 60,
- height: 60,
- borderRadius: 30,
+ width: 90,
+ height: 90,
+ borderRadius: 45,
backgroundColor: BRAND.PRIMARY,
- alignItems: 'center',
justifyContent: 'center',
+ alignItems: 'center',
+ shadowColor: BRAND.PRIMARY,
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.5,
+ shadowRadius: 20,
+ elevation: 10,
},
- profileInfo: {
- flex: 1,
+ nameContainer: {
+ marginTop: 16,
+ alignItems: 'center',
},
- premiumBadge: {
- backgroundColor: 'rgba(255, 107, 53, 0.15)',
- paddingHorizontal: SPACING[3],
- paddingVertical: SPACING[1],
- borderRadius: 12,
- alignSelf: 'flex-start',
- marginTop: SPACING[1],
+ planContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginTop: 4,
+ gap: 4,
+ },
+ statsContainer: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ marginTop: 16,
+ gap: 32,
+ },
+ statItem: {
+ alignItems: 'center',
+ },
+ premiumContainer: {
+ paddingVertical: 16,
+ paddingHorizontal: 16,
+ },
+ premiumContent: {
+ gap: 4,
+ },
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ borderBottomWidth: 0.5,
+ borderBottomColor: colors.border.glassLight,
+ },
+ rowLast: {
+ borderBottomWidth: 0,
+ },
+ rowLabel: {
+ fontSize: 17,
+ color: colors.text.primary,
+ },
+ rowValue: {
+ fontSize: 17,
+ color: colors.text.tertiary,
+ },
+ rowTime: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ borderTopWidth: 0.5,
+ borderTopColor: colors.border.glassLight,
+ },
+ button: {
+ paddingVertical: 14,
+ alignItems: 'center',
+ },
+ destructive: {
+ fontSize: 17,
+ color: BRAND.DANGER,
+ },
+ signOutSection: {
+ marginTop: 20,
},
})
}
diff --git a/app/paywall.tsx b/app/paywall.tsx
index d808a28..66c3d44 100644
--- a/app/paywall.tsx
+++ b/app/paywall.tsx
@@ -3,7 +3,7 @@
* Premium subscription purchase flow
*/
-import React from 'react'
+import React, { useMemo } from 'react'
import {
View,
StyleSheet,
@@ -18,7 +18,8 @@ import Ionicons from '@expo/vector-icons/Ionicons'
import { useTranslation } from 'react-i18next'
import { useHaptics, usePurchases } from '@/src/shared/hooks'
-import { BRAND, darkColors } from '@/src/shared/theme'
+import { useThemeColors, BRAND, GRADIENTS } from '@/src/shared/theme'
+import type { ThemeColors } from '@/src/shared/theme/types'
import { SPACING } from '@/src/shared/constants/spacing'
import { RADIUS } from '@/src/shared/constants/borderRadius'
@@ -39,6 +40,18 @@ const PREMIUM_FEATURES = [
// COMPONENTS
// ═══════════════════════════════════════════════════════════════════════════
+interface PlanCardStyles {
+ planCard: object
+ planCardPressed: object
+ savingsBadge: object
+ savingsText: object
+ planInfo: object
+ planTitle: object
+ planPeriod: object
+ planPrice: object
+ checkmark: object
+}
+
function PlanCard({
title,
price,
@@ -46,6 +59,8 @@ function PlanCard({
savings,
isSelected,
onPress,
+ colors,
+ styles,
}: {
title: string
price: string
@@ -53,6 +68,8 @@ function PlanCard({
savings?: string
isSelected: boolean
onPress: () => void
+ colors: ThemeColors
+ styles: PlanCardStyles
}) {
const haptics = useHaptics()
@@ -66,8 +83,12 @@ function PlanCard({
onPress={handlePress}
style={({ pressed }) => [
styles.planCard,
- isSelected && styles.planCardSelected,
+ isSelected && { borderColor: BRAND.PRIMARY },
pressed && styles.planCardPressed,
+ {
+ backgroundColor: colors.bg.surface,
+ borderColor: isSelected ? BRAND.PRIMARY : colors.border.glass,
+ },
]}
>
{savings && (
@@ -76,10 +97,16 @@ function PlanCard({
)}
- {title}
- {period}
+
+ {title}
+
+
+ {period}
+
- {price}
+
+ {price}
+
{isSelected && (
@@ -98,6 +125,25 @@ export default function PaywallScreen() {
const router = useRouter()
const insets = useSafeAreaInsets()
const haptics = useHaptics()
+ const colors = useThemeColors()
+ const styles = useMemo(() => createStyles(colors), [colors])
+
+ // Extract plan card styles for the child component
+ const planCardStyles = useMemo(
+ () => ({
+ planCard: styles.planCard,
+ planCardPressed: styles.planCardPressed,
+ savingsBadge: styles.savingsBadge,
+ savingsText: styles.savingsText,
+ planInfo: styles.planInfo,
+ planTitle: styles.planTitle,
+ planPeriod: styles.planPeriod,
+ planPrice: styles.planPrice,
+ checkmark: styles.checkmark,
+ }),
+ [styles],
+ )
+
const {
monthlyPackage,
annualPackage,
@@ -148,15 +194,9 @@ export default function PaywallScreen() {
return (
- {/* Background Gradient */}
-
-
{/* Close Button */}
-
-
+
+
{PREMIUM_FEATURES.map((feature) => (
-
+
-
+
{t(`paywall.features.${feature.key}`)}
@@ -196,6 +236,8 @@ export default function PaywallScreen() {
savings={t('paywall.save50')}
isSelected={selectedPlan === 'annual'}
onPress={() => setSelectedPlan('annual')}
+ colors={colors}
+ styles={planCardStyles}
/>
setSelectedPlan('monthly')}
+ colors={colors}
+ styles={planCardStyles}
/>
{/* Price Note */}
{selectedPlan === 'annual' && (
-
+
{t('paywall.equivalent', { price: annualMonthlyEquivalent })}
)}
@@ -220,12 +264,12 @@ export default function PaywallScreen() {
disabled={isLoading}
>
-
+
{isLoading ? t('paywall.processing') : t('paywall.subscribe')}
@@ -234,10 +278,14 @@ export default function PaywallScreen() {
{/* Restore & Terms */}
- {t('paywall.restore')}
+
+ {t('paywall.restore')}
+
- {t('paywall.terms')}
+
+ {t('paywall.terms')}
+
@@ -248,162 +296,147 @@ export default function PaywallScreen() {
// STYLES
// ═══════════════════════════════════════════════════════════════════════════
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- gradient: {
- ...StyleSheet.absoluteFillObject,
- },
- closeButton: {
- position: 'absolute',
- top: SPACING[4],
- right: SPACING[4],
- width: 44,
- height: 44,
- alignItems: 'center',
- justifyContent: 'center',
- zIndex: 10,
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
- paddingHorizontal: SPACING[5],
- paddingTop: SPACING[8],
- },
- header: {
- alignItems: 'center',
- },
- title: {
- fontSize: 32,
- fontWeight: '700',
- color: '#FFF',
- textAlign: 'center',
- },
- subtitle: {
- fontSize: 16,
- color: darkColors.text.secondary,
- textAlign: 'center',
- marginTop: SPACING[2],
- },
- featuresGrid: {
- flexDirection: 'row',
- flexWrap: 'wrap',
- marginTop: SPACING[6],
- marginHorizontal: -SPACING[2],
- },
- featureItem: {
- width: '33%',
- alignItems: 'center',
- paddingVertical: SPACING[3],
- },
- featureIcon: {
- width: 48,
- height: 48,
- borderRadius: 24,
- backgroundColor: 'rgba(255, 107, 53, 0.15)',
- alignItems: 'center',
- justifyContent: 'center',
- marginBottom: SPACING[2],
- },
- featureText: {
- fontSize: 13,
- color: darkColors.text.secondary,
- textAlign: 'center',
- },
- plansContainer: {
- marginTop: SPACING[6],
- gap: SPACING[3],
- },
- planCard: {
- flexDirection: 'row',
- alignItems: 'center',
- backgroundColor: 'rgba(255, 255, 255, 0.08)',
- borderRadius: RADIUS.LG,
- padding: SPACING[4],
- borderWidth: 2,
- borderColor: 'transparent',
- },
- planCardSelected: {
- borderColor: BRAND.PRIMARY,
- backgroundColor: 'rgba(255, 107, 53, 0.1)',
- },
- planCardPressed: {
- opacity: 0.8,
- },
- savingsBadge: {
- position: 'absolute',
- top: -8,
- right: SPACING[3],
- backgroundColor: BRAND.PRIMARY,
- paddingHorizontal: SPACING[2],
- paddingVertical: 2,
- borderRadius: RADIUS.SM,
- },
- savingsText: {
- fontSize: 10,
- fontWeight: '700',
- color: '#FFF',
- },
- planInfo: {
- flex: 1,
- },
- planTitle: {
- fontSize: 16,
- fontWeight: '600',
- color: darkColors.text.primary,
- },
- planPeriod: {
- fontSize: 13,
- color: darkColors.text.tertiary,
- marginTop: 2,
- },
- planPrice: {
- fontSize: 20,
- fontWeight: '700',
- color: BRAND.PRIMARY,
- },
- checkmark: {
- marginLeft: SPACING[2],
- },
- priceNote: {
- fontSize: 13,
- color: darkColors.text.tertiary,
- textAlign: 'center',
- marginTop: SPACING[3],
- },
- ctaButton: {
- borderRadius: RADIUS.LG,
- overflow: 'hidden',
- marginTop: SPACING[6],
- },
- ctaButtonDisabled: {
- opacity: 0.6,
- },
- ctaGradient: {
- paddingVertical: SPACING[4],
- alignItems: 'center',
- },
- ctaText: {
- fontSize: 17,
- fontWeight: '600',
- color: '#FFF',
- },
- footer: {
- marginTop: SPACING[5],
- alignItems: 'center',
- gap: SPACING[4],
- },
- restoreText: {
- fontSize: 14,
- color: darkColors.text.tertiary,
- },
- termsText: {
- fontSize: 11,
- color: darkColors.text.tertiary,
- textAlign: 'center',
- lineHeight: 18,
- paddingHorizontal: SPACING[4],
- },
-})
+function createStyles(colors: ThemeColors) {
+ return StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: colors.bg.base,
+ },
+ closeButton: {
+ position: 'absolute',
+ top: SPACING[4],
+ right: SPACING[4],
+ width: 44,
+ height: 44,
+ alignItems: 'center',
+ justifyContent: 'center',
+ zIndex: 10,
+ },
+ scrollView: {
+ flex: 1,
+ },
+ scrollContent: {
+ paddingHorizontal: SPACING[5],
+ paddingTop: SPACING[8],
+ },
+ header: {
+ alignItems: 'center',
+ },
+ title: {
+ fontSize: 32,
+ fontWeight: '700',
+ color: colors.text.primary,
+ textAlign: 'center',
+ },
+ subtitle: {
+ fontSize: 16,
+ color: colors.text.secondary,
+ textAlign: 'center',
+ marginTop: SPACING[2],
+ },
+ featuresGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginTop: SPACING[6],
+ marginHorizontal: -SPACING[2],
+ },
+ featureItem: {
+ width: '33%',
+ alignItems: 'center',
+ paddingVertical: SPACING[3],
+ },
+ featureIcon: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: SPACING[2],
+ },
+ featureText: {
+ fontSize: 13,
+ textAlign: 'center',
+ },
+ plansContainer: {
+ marginTop: SPACING[6],
+ gap: SPACING[3],
+ },
+ planCard: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ borderRadius: RADIUS.LG,
+ padding: SPACING[4],
+ borderWidth: 2,
+ },
+ planCardPressed: {
+ opacity: 0.8,
+ },
+ savingsBadge: {
+ position: 'absolute',
+ top: -8,
+ right: SPACING[3],
+ backgroundColor: BRAND.PRIMARY,
+ paddingHorizontal: SPACING[2],
+ paddingVertical: 2,
+ borderRadius: RADIUS.SM,
+ },
+ savingsText: {
+ fontSize: 10,
+ fontWeight: '700',
+ color: colors.text.primary,
+ },
+ planInfo: {
+ flex: 1,
+ },
+ planTitle: {
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ planPeriod: {
+ fontSize: 13,
+ marginTop: 2,
+ },
+ planPrice: {
+ fontSize: 20,
+ fontWeight: '700',
+ },
+ checkmark: {
+ marginLeft: SPACING[2],
+ },
+ priceNote: {
+ fontSize: 13,
+ textAlign: 'center',
+ marginTop: SPACING[3],
+ },
+ ctaButton: {
+ borderRadius: RADIUS.LG,
+ overflow: 'hidden',
+ marginTop: SPACING[6],
+ },
+ ctaButtonDisabled: {
+ opacity: 0.6,
+ },
+ ctaGradient: {
+ paddingVertical: SPACING[4],
+ alignItems: 'center',
+ },
+ ctaText: {
+ fontSize: 17,
+ fontWeight: '600',
+ },
+ footer: {
+ marginTop: SPACING[5],
+ alignItems: 'center',
+ gap: SPACING[4],
+ },
+ restoreText: {
+ fontSize: 14,
+ },
+ termsText: {
+ fontSize: 11,
+ textAlign: 'center',
+ lineHeight: 18,
+ paddingHorizontal: SPACING[4],
+ },
+ })
+}