/**
* TabataFit Onboarding — 6-Screen Conversion Funnel
* Problem → Empathy → Solution → Wow Moment → Personalization → Paywall
*/
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
import {
View,
StyleSheet,
Pressable,
Animated,
Dimensions,
ScrollView,
TextInput,
} from 'react-native'
import { useRouter } from 'expo-router'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { Icon } from '@/src/shared/components/Icon'
import { Alert } from 'react-native'
import { useTranslation } from 'react-i18next'
import { useHaptics, usePurchases } from '@/src/shared/hooks'
import { useUserStore } from '@/src/shared/stores'
import { OnboardingStep } from '@/src/shared/components/OnboardingStep'
import { StyledText } from '@/src/shared/components/StyledText'
import { useThemeColors, BRAND, PHASE } 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 { DURATION, EASE, SPRING } from '@/src/shared/constants/animations'
import { track, identifyUser, setUserProperties, trackScreen } from '@/src/shared/services/analytics'
import type { FitnessLevel, FitnessGoal, WeeklyFrequency } from '@/src/shared/types'
const { width: SCREEN_WIDTH } = Dimensions.get('window')
const TOTAL_STEPS = 6
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 1 — THE PROBLEM
// ═══════════════════════════════════════════════════════════════════════════
function ProblemScreen({ onNext }: { onNext: () => void }) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
const clockScale = useRef(new Animated.Value(0.8)).current
const clockOpacity = useRef(new Animated.Value(0)).current
const textOpacity = useRef(new Animated.Value(0)).current
useEffect(() => {
// Clock animation
Animated.parallel([
Animated.spring(clockScale, {
toValue: 1,
...SPRING.BOUNCY,
useNativeDriver: true,
}),
Animated.timing(clockOpacity, {
toValue: 1,
duration: DURATION.SLOW,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}),
]).start()
// Text fade in after clock
setTimeout(() => {
Animated.timing(textOpacity, {
toValue: 1,
duration: DURATION.SLOW,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}).start()
}, 400)
}, [])
return (
{t('onboarding.problem.title')}
{t('onboarding.problem.subtitle1')}
{t('onboarding.problem.subtitle2')}
{
haptics.buttonTap()
onNext()
}}
>
{t('onboarding.problem.cta')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 2 — EMPATHY
// ═══════════════════════════════════════════════════════════════════════════
const BARRIERS = [
{ id: 'no-time', labelKey: 'onboarding.empathy.noTime' as const, icon: 'clock' as const },
{ id: 'low-motivation', labelKey: 'onboarding.empathy.lowMotivation' as const, icon: 'battery.0percent' as const },
{ id: 'no-knowledge', labelKey: 'onboarding.empathy.noKnowledge' as const, icon: 'questionmark.circle' as const },
{ id: 'no-gym', labelKey: 'onboarding.empathy.noGym' as const, icon: 'house' as const },
]
function EmpathyScreen({
onNext,
barriers,
setBarriers,
}: {
onNext: () => void
barriers: string[]
setBarriers: (b: string[]) => void
}) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
const toggleBarrier = (id: string) => {
haptics.selection()
if (barriers.includes(id)) {
setBarriers(barriers.filter((b) => b !== id))
} else if (barriers.length < 2) {
setBarriers([...barriers, id])
}
}
return (
{t('onboarding.empathy.title')}
{t('onboarding.empathy.chooseUpTo')}
{BARRIERS.map((item) => {
const selected = barriers.includes(item.id)
return (
toggleBarrier(item.id)}
>
{t(item.labelKey)}
)
})}
{
if (barriers.length > 0) {
haptics.buttonTap()
onNext()
}
}}
>
0 ? '#FFFFFF' : colors.text.disabled}
>
{t('common:continue')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 3 — THE SOLUTION (Scientific Proof)
// ═══════════════════════════════════════════════════════════════════════════
function SolutionScreen({ onNext }: { onNext: () => void }) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
const tabataHeight = useRef(new Animated.Value(0)).current
const cardioHeight = useRef(new Animated.Value(0)).current
const citationOpacity = useRef(new Animated.Value(0)).current
useEffect(() => {
// Animate bars
Animated.sequence([
Animated.delay(300),
Animated.parallel([
Animated.spring(tabataHeight, {
toValue: 1,
...SPRING.GENTLE,
useNativeDriver: false,
}),
Animated.spring(cardioHeight, {
toValue: 1,
...SPRING.GENTLE,
useNativeDriver: false,
}),
]),
Animated.delay(200),
Animated.timing(citationOpacity, {
toValue: 1,
duration: DURATION.SLOW,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}),
]).start()
}, [])
const MAX_BAR_HEIGHT = 160
return (
{t('onboarding.solution.title')}
{/* Comparison bars */}
{/* Tabata bar */}
{t('onboarding.solution.tabataCalories')}
{t('onboarding.solution.tabata')}
{t('onboarding.solution.tabataDuration')}
{/* VS */}
{t('onboarding.solution.vs')}
{/* Cardio bar */}
{t('onboarding.solution.cardioCalories')}
{t('onboarding.solution.cardio')}
{t('onboarding.solution.cardioDuration')}
{/* Citation */}
{t('onboarding.solution.citation')}
{t('onboarding.solution.citationAuthor')}
{
haptics.buttonTap()
onNext()
}}
>
{t('onboarding.solution.cta')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 4 — WOW MOMENT (Staggered Feature Reveal)
// ═══════════════════════════════════════════════════════════════════════════
const WOW_FEATURES = [
{ icon: 'timer' as const, iconColor: BRAND.PRIMARY, titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
{ icon: 'dumbbell' as const, iconColor: PHASE.REST, titleKey: 'onboarding.wow.card2Title', subtitleKey: 'onboarding.wow.card2Subtitle' },
{ icon: 'mic' as const, iconColor: PHASE.PREP, titleKey: 'onboarding.wow.card3Title', subtitleKey: 'onboarding.wow.card3Subtitle' },
{ icon: 'arrow.up.right' as const, iconColor: PHASE.COMPLETE, titleKey: 'onboarding.wow.card4Title', subtitleKey: 'onboarding.wow.card4Subtitle' },
] as const
function WowScreen({ onNext }: { onNext: () => void }) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
const wowStyles = useMemo(() => createWowStyles(colors), [colors])
const rowAnims = useRef(WOW_FEATURES.map(() => ({
opacity: new Animated.Value(0),
translateY: new Animated.Value(20),
}))).current
const ctaOpacity = useRef(new Animated.Value(0)).current
const [ctaReady, setCtaReady] = useState(false)
useEffect(() => {
// Staggered reveal: each row fades in + slides up, 150ms apart, starting at 300ms
const STAGGER_DELAY = 150
const ROW_DURATION = DURATION.NORMAL // 300ms
const START_DELAY = 300
WOW_FEATURES.forEach((_, i) => {
setTimeout(() => {
Animated.parallel([
Animated.timing(rowAnims[i].opacity, {
toValue: 1,
duration: ROW_DURATION,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}),
Animated.timing(rowAnims[i].translateY, {
toValue: 0,
duration: ROW_DURATION,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}),
]).start()
}, START_DELAY + i * STAGGER_DELAY)
})
// CTA fades in 200ms after last row finishes
const ctaDelay = START_DELAY + (WOW_FEATURES.length - 1) * STAGGER_DELAY + ROW_DURATION + 200
setTimeout(() => {
setCtaReady(true)
Animated.timing(ctaOpacity, {
toValue: 1,
duration: ROW_DURATION,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}).start()
}, ctaDelay)
}, [])
return (
{t('onboarding.wow.title')}
{t('onboarding.wow.subtitle')}
{/* Feature list */}
{WOW_FEATURES.map((feature, i) => (
{t(feature.titleKey)}
{t(feature.subtitleKey)}
))}
{/* CTA fades in after all rows */}
{
if (ctaReady) {
haptics.buttonTap()
onNext()
}
}}
>
{t('common:next')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 5 — PERSONALIZATION
// ═══════════════════════════════════════════════════════════════════════════
const LEVELS: { value: FitnessLevel; labelKey: string }[] = [
{ value: 'beginner', labelKey: 'common:levels.beginner' },
{ value: 'intermediate', labelKey: 'common:levels.intermediate' },
{ value: 'advanced', labelKey: 'common:levels.advanced' },
]
const GOALS: { value: FitnessGoal; labelKey: string }[] = [
{ value: 'weight-loss', labelKey: 'onboarding.personalization.goals.weightLoss' },
{ value: 'cardio', labelKey: 'onboarding.personalization.goals.cardio' },
{ value: 'strength', labelKey: 'onboarding.personalization.goals.strength' },
{ value: 'wellness', labelKey: 'onboarding.personalization.goals.wellness' },
]
const FREQUENCIES: { value: WeeklyFrequency; labelKey: string }[] = [
{ value: 2, labelKey: 'onboarding.personalization.frequencies.2x' },
{ value: 3, labelKey: 'onboarding.personalization.frequencies.3x' },
{ value: 5, labelKey: 'onboarding.personalization.frequencies.5x' },
]
function PersonalizationScreen({
onNext,
name,
setName,
level,
setLevel,
goal,
setGoal,
frequency,
setFrequency,
}: {
onNext: () => void
name: string
setName: (n: string) => void
level: FitnessLevel
setLevel: (l: FitnessLevel) => void
goal: FitnessGoal
setGoal: (g: FitnessGoal) => void
frequency: WeeklyFrequency
setFrequency: (f: WeeklyFrequency) => void
}) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
return (
{t('onboarding.personalization.title')}
{/* Name input */}
{t('onboarding.personalization.yourName')}
{/* Fitness Level */}
{t('onboarding.personalization.fitnessLevel')}
{LEVELS.map((item) => (
{
haptics.selection()
setLevel(item.value)
}}
>
{t(item.labelKey)}
))}
{/* Goal */}
{t('onboarding.personalization.yourGoal')}
{GOALS.map((item) => (
{
haptics.selection()
setGoal(item.value)
}}
>
{t(item.labelKey)}
))}
{/* Frequency */}
{t('onboarding.personalization.weeklyFrequency')}
{FREQUENCIES.map((item) => (
{
haptics.selection()
setFrequency(item.value)
}}
>
{t(item.labelKey)}
))}
{name.trim().length > 0 && (
{t('onboarding.personalization.readyMessage')}
)}
{
if (name.trim()) {
haptics.buttonTap()
onNext()
}
}}
>
{t('common:continue')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// SCREEN 6 — PAYWALL
// ═══════════════════════════════════════════════════════════════════════════
const PREMIUM_FEATURE_KEYS = [
'onboarding.paywall.features.unlimited',
'onboarding.paywall.features.offline',
'onboarding.paywall.features.stats',
'onboarding.paywall.features.noAds',
] as const
function PaywallScreen({
onSubscribe,
onSkip,
}: {
onSubscribe: (plan: 'premium-monthly' | 'premium-yearly') => void
onSkip: () => void
}) {
const { t } = useTranslation('screens')
const haptics = useHaptics()
const colors = useThemeColors()
const styles = useMemo(() => createStyles(colors), [colors])
const {
isLoading,
monthlyPackage,
annualPackage,
purchasePackage,
restorePurchases,
} = usePurchases()
const [selectedPlan, setSelectedPlan] = useState<'premium-monthly' | 'premium-yearly'>('premium-yearly')
const [isPurchasing, setIsPurchasing] = useState(false)
const featureAnims = useRef(PREMIUM_FEATURE_KEYS.map(() => new Animated.Value(0))).current
const handlePlanSelect = (plan: 'premium-monthly' | 'premium-yearly') => {
haptics.selection()
setSelectedPlan(plan)
track('onboarding_paywall_plan_selected', { plan })
}
useEffect(() => {
// Staggered feature fade-in
PREMIUM_FEATURE_KEYS.forEach((_, i) => {
setTimeout(() => {
Animated.timing(featureAnims[i], {
toValue: 1,
duration: DURATION.NORMAL,
easing: EASE.EASE_OUT,
useNativeDriver: true,
}).start()
}, i * 100)
})
}, [])
// Get localized prices from RevenueCat packages
const yearlyPrice = annualPackage?.product.priceString ?? t('onboarding.paywall.yearlyPrice')
const monthlyPrice = monthlyPackage?.product.priceString ?? t('onboarding.paywall.monthlyPrice')
const handlePurchase = async () => {
if (isPurchasing) return
const pkg = selectedPlan === 'premium-yearly' ? annualPackage : monthlyPackage
const price = selectedPlan === 'premium-yearly'
? (annualPackage?.product.priceString ?? t('onboarding.paywall.yearlyPrice'))
: (monthlyPackage?.product.priceString ?? t('onboarding.paywall.monthlyPrice'))
track('onboarding_paywall_purchase_tapped', { plan: selectedPlan, price })
// DEV mode: if RevenueCat hasn't loaded or has no packages, show simulated purchase dialog
if (__DEV__ && (isLoading || !pkg)) {
haptics.buttonTap()
const planLabel = selectedPlan === 'premium-yearly'
? `Annual (${t('onboarding.paywall.yearlyPrice')})`
: `Monthly (${t('onboarding.paywall.monthlyPrice')})`
Alert.alert(
'Confirm Subscription',
`Subscribe to TabataFit+ ${planLabel}?\n\nThis is a sandbox purchase — no real charge.`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Subscribe',
onPress: () => {
track('onboarding_paywall_purchase_success', { plan: selectedPlan })
onSubscribe(selectedPlan)
},
},
]
)
return
}
if (isLoading || !pkg) return
setIsPurchasing(true)
haptics.buttonTap()
try {
const result = await purchasePackage(pkg)
if (result.success) {
track('onboarding_paywall_purchase_success', { plan: selectedPlan })
onSubscribe(selectedPlan)
}
} finally {
setIsPurchasing(false)
}
}
const handleRestore = async () => {
haptics.buttonTap()
const restored = await restorePurchases()
track('onboarding_paywall_restored', { success: !!restored })
if (restored) {
// User has premium now, complete onboarding
onSubscribe('premium-yearly')
}
}
return (
{t('onboarding.paywall.title')}
{/* Features */}
{PREMIUM_FEATURE_KEYS.map((featureKey, i) => (
{t(featureKey)}
))}
{/* Pricing cards */}
{/* Annual */}
handlePlanSelect('premium-yearly')}
>
{t('onboarding.paywall.bestValue')}
{yearlyPrice}
{t('common:units.perYear')}
{t('onboarding.paywall.savePercent')}
{/* Monthly */}
handlePlanSelect('premium-monthly')}
>
{monthlyPrice}
{t('common:units.perMonth')}
{/* CTA */}
{isPurchasing ? '...' : t('onboarding.paywall.trialCta')}
{/* Guarantees */}
{t('onboarding.paywall.guarantees')}
{/* Restore Purchases */}
{t('onboarding.paywall.restorePurchases')}
{/* Skip */}
{
track('onboarding_paywall_skipped')
onSkip()
}}
>
{t('onboarding.paywall.skipButton')}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// MAIN ONBOARDING CONTROLLER
// ═══════════════════════════════════════════════════════════════════════════
const STEP_NAMES: Record = {
1: 'problem',
2: 'empathy',
3: 'solution',
4: 'wow',
5: 'personalization',
6: 'paywall',
}
export default function OnboardingScreen() {
const router = useRouter()
const [step, setStep] = useState(1)
// Personalization state
const [barriers, setBarriers] = useState([])
const [name, setName] = useState('')
const [level, setLevel] = useState('beginner')
const [goal, setGoal] = useState('cardio')
const [frequency, setFrequency] = useState(3)
const completeOnboarding = useUserStore((s) => s.completeOnboarding)
const setSubscription = useUserStore((s) => s.setSubscription)
// Analytics: track time per step and total onboarding time
const onboardingStartTime = useRef(Date.now())
const stepStartTime = useRef(Date.now())
// Track onboarding_started + first step viewed on mount
useEffect(() => {
trackScreen('onboarding')
track('onboarding_started')
track('onboarding_step_viewed', { step: 1, step_name: STEP_NAMES[1] })
}, [])
const finishOnboarding = useCallback(
(plan: 'free' | 'premium-monthly' | 'premium-yearly') => {
const totalTime = Date.now() - onboardingStartTime.current
track('onboarding_completed', {
plan,
total_time_ms: totalTime,
steps_completed: step,
})
const userData = {
name: name.trim() || 'Athlete',
fitnessLevel: level,
goal,
weeklyFrequency: frequency,
barriers,
}
completeOnboarding(userData)
// Identify user in PostHog for session replay linking
const userId = `user_${Date.now()}` // In production, use actual user ID from backend
identifyUser(userId, {
name: userData.name,
fitness_level: level,
fitness_goal: goal,
weekly_frequency: frequency,
subscription_plan: plan,
onboarding_completed_at: new Date().toISOString(),
barriers: barriers.join(','),
})
if (plan !== 'free') {
setSubscription(plan)
}
router.replace('/(tabs)')
},
[name, level, goal, frequency, barriers, step]
)
const nextStep = useCallback(() => {
const now = Date.now()
const timeOnStep = now - stepStartTime.current
// Track step completed
track('onboarding_step_completed', {
step,
step_name: STEP_NAMES[step],
time_on_step_ms: timeOnStep,
})
// Track specific step data
if (step === 2) {
track('onboarding_barriers_selected', {
barriers,
barrier_count: barriers.length,
})
}
if (step === 5) {
track('onboarding_personalization_completed', {
name_provided: name.trim().length > 0,
level,
goal,
frequency,
})
}
const next = Math.min(step + 1, TOTAL_STEPS)
stepStartTime.current = now
// Track next step viewed
track('onboarding_step_viewed', { step: next, step_name: STEP_NAMES[next] })
setStep(next)
}, [step, barriers, name, level, goal, frequency])
const prevStep = useCallback(() => {
if (step > 1) {
const prev = step - 1
stepStartTime.current = Date.now()
track('onboarding_step_back', { from_step: step, to_step: prev })
setStep(prev)
}
}, [step])
const renderStep = () => {
switch (step) {
case 1:
return
case 2:
return (
)
case 3:
return
case 4:
return
case 5:
return (
)
case 6:
return (
finishOnboarding(plan)}
onSkip={() => finishOnboarding('free')}
/>
)
default:
return null
}
}
return (
{renderStep()}
)
}
// ═══════════════════════════════════════════════════════════════════════════
// STYLES
// ═══════════════════════════════════════════════════════════════════════════
function createStyles(colors: ThemeColors) {
return StyleSheet.create({
// Layout helpers
screenCenter: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
screenFull: {
flex: 1,
},
titleCenter: {
textAlign: 'center',
},
subtitle: {
textAlign: 'center',
},
bottomAction: {
position: 'absolute',
bottom: SPACING[4],
left: 0,
right: 0,
},
// CTA Button
ctaButton: {
height: LAYOUT.BUTTON_HEIGHT,
backgroundColor: BRAND.PRIMARY,
borderRadius: RADIUS.GLASS_BUTTON,
alignItems: 'center',
justifyContent: 'center',
},
ctaButtonDisabled: {
backgroundColor: colors.bg.elevated,
},
// ── Screen 2: Barriers ──
barrierGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: SPACING[3],
marginTop: SPACING[8],
justifyContent: 'center',
},
barrierCard: {
width: (SCREEN_WIDTH - LAYOUT.SCREEN_PADDING * 2 - SPACING[3]) / 2,
paddingVertical: SPACING[6],
alignItems: 'center',
borderRadius: RADIUS.GLASS_CARD,
...colors.glass.base,
},
barrierCardSelected: {
borderColor: BRAND.PRIMARY,
backgroundColor: 'rgba(255, 107, 53, 0.1)',
},
// ── Screen 3: Comparison ──
comparisonContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'flex-end',
marginTop: SPACING[10],
paddingHorizontal: SPACING[8],
gap: SPACING[4],
},
barColumn: {
alignItems: 'center',
flex: 1,
},
barTrack: {
width: 60,
height: 160,
backgroundColor: colors.bg.overlay1,
borderRadius: RADIUS.SM,
overflow: 'hidden',
marginVertical: SPACING[3],
justifyContent: 'flex-end',
},
barFill: {
width: '100%',
borderRadius: RADIUS.SM,
},
barTabata: {
backgroundColor: BRAND.PRIMARY,
},
barCardio: {
backgroundColor: PHASE.REST,
},
vsContainer: {
paddingBottom: 80,
},
citation: {
marginTop: SPACING[8],
paddingHorizontal: SPACING[4],
},
citationText: {
textAlign: 'center',
fontStyle: 'italic',
lineHeight: 20,
},
citationAuthor: {
textAlign: 'center',
marginTop: SPACING[2],
},
// ── Screen 5: Personalization ──
personalizationContent: {
paddingBottom: SPACING[10],
},
fieldGroup: {
marginTop: SPACING[6],
},
fieldLabel: {
letterSpacing: 1.5,
marginBottom: SPACING[2],
},
textInput: {
height: LAYOUT.BUTTON_HEIGHT_SM,
backgroundColor: colors.bg.surface,
borderRadius: RADIUS.MD,
paddingHorizontal: SPACING[4],
color: colors.text.primary,
fontSize: 17,
borderWidth: 1,
borderColor: colors.border.glass,
},
segmentRow: {
flexDirection: 'row',
backgroundColor: colors.bg.surface,
borderRadius: RADIUS.MD,
padding: 3,
gap: 2,
},
segmentButton: {
flex: 1,
height: 36,
alignItems: 'center',
justifyContent: 'center',
borderRadius: RADIUS.SM,
},
segmentButtonActive: {
backgroundColor: colors.bg.elevated,
},
readyMessage: {
textAlign: 'center',
marginTop: SPACING[6],
},
// ── Screen 6: Paywall ──
paywallContent: {
paddingBottom: SPACING[10],
},
featuresList: {
marginTop: SPACING[8],
gap: SPACING[4],
},
featureRow: {
flexDirection: 'row',
alignItems: 'center',
},
pricingCards: {
flexDirection: 'row',
gap: SPACING[3],
marginTop: SPACING[8],
},
pricingCard: {
flex: 1,
paddingVertical: SPACING[5],
alignItems: 'center',
justifyContent: 'center',
borderRadius: RADIUS.GLASS_CARD,
...colors.glass.base,
},
pricingCardSelected: {
borderColor: BRAND.PRIMARY,
borderWidth: 2,
backgroundColor: 'rgba(255, 107, 53, 0.08)',
},
bestValueBadge: {
backgroundColor: BRAND.PRIMARY,
paddingHorizontal: SPACING[3],
paddingVertical: SPACING[1],
borderRadius: RADIUS.SM,
marginBottom: SPACING[2],
},
trialButton: {
height: LAYOUT.BUTTON_HEIGHT,
backgroundColor: BRAND.PRIMARY,
borderRadius: RADIUS.GLASS_BUTTON,
alignItems: 'center',
justifyContent: 'center',
marginTop: SPACING[6],
},
guarantees: {
alignItems: 'center',
marginTop: SPACING[4],
},
restoreButton: {
alignItems: 'center',
paddingVertical: SPACING[3],
},
skipButton: {
alignItems: 'center',
paddingVertical: SPACING[5],
marginTop: SPACING[2],
},
})
}
// ── Screen 4: Feature List Styles ──
function createWowStyles(colors: ThemeColors) {
return StyleSheet.create({
list: {
gap: SPACING[5],
marginTop: SPACING[4],
},
row: {
flexDirection: 'row',
alignItems: 'center',
gap: SPACING[4],
},
iconCircle: {
width: 44,
height: 44,
borderRadius: 22,
alignItems: 'center',
justifyContent: 'center',
},
textCol: {
flex: 1,
},
})
}