diff --git a/app/onboarding.tsx b/app/onboarding.tsx
new file mode 100644
index 0000000..e24d092
--- /dev/null
+++ b/app/onboarding.tsx
@@ -0,0 +1,1127 @@
+/**
+ * TabataFit Onboarding — 6-Screen Conversion Funnel
+ * Problem → Empathy → Solution → Wow Moment → Personalization → Paywall
+ */
+
+import { useState, useRef, useEffect, useCallback } 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 Ionicons from '@expo/vector-icons/Ionicons'
+
+import { useTranslation } from 'react-i18next'
+import { useHaptics } 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 { BRAND, DARK, TEXT, PHASE, GLASS, BORDER } from '@/src/shared/constants/colors'
+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 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 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: 'time-outline' as const },
+ { id: 'low-motivation', labelKey: 'onboarding.empathy.lowMotivation' as const, icon: 'battery-dead-outline' as const },
+ { id: 'no-knowledge', labelKey: 'onboarding.empathy.noKnowledge' as const, icon: 'help-circle-outline' as const },
+ { id: 'no-gym', labelKey: 'onboarding.empathy.noGym' as const, icon: 'home-outline' as const },
+]
+
+function EmpathyScreen({
+ onNext,
+ barriers,
+ setBarriers,
+}: {
+ onNext: () => void
+ barriers: string[]
+ setBarriers: (b: string[]) => void
+}) {
+ const { t } = useTranslation('screens')
+ const haptics = useHaptics()
+
+ 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 ? TEXT.PRIMARY : TEXT.DISABLED}
+ >
+ {t('common:continue')}
+
+
+
+
+ )
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// SCREEN 3 — THE SOLUTION (Scientific Proof)
+// ═══════════════════════════════════════════════════════════════════════════
+
+function SolutionScreen({ onNext }: { onNext: () => void }) {
+ const { t } = useTranslation('screens')
+ const haptics = useHaptics()
+ 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-outline' as const, iconColor: BRAND.PRIMARY, titleKey: 'onboarding.wow.card1Title', subtitleKey: 'onboarding.wow.card1Subtitle' },
+ { icon: 'barbell-outline' as const, iconColor: PHASE.REST, titleKey: 'onboarding.wow.card2Title', subtitleKey: 'onboarding.wow.card2Subtitle' },
+ { icon: 'mic-outline' as const, iconColor: PHASE.PREP, titleKey: 'onboarding.wow.card3Title', subtitleKey: 'onboarding.wow.card3Subtitle' },
+ { icon: 'trending-up-outline' 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 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()
+
+ 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 [selectedPlan, setSelectedPlan] = useState<'premium-monthly' | 'premium-yearly'>('premium-yearly')
+ const featureAnims = useRef(PREMIUM_FEATURE_KEYS.map(() => new Animated.Value(0))).current
+
+ 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)
+ })
+ }, [])
+
+ return (
+
+
+ {t('onboarding.paywall.title')}
+
+
+ {/* Features */}
+
+ {PREMIUM_FEATURE_KEYS.map((featureKey, i) => (
+
+
+
+ {t(featureKey)}
+
+
+ ))}
+
+
+ {/* Pricing cards */}
+
+ {/* Annual */}
+ {
+ haptics.selection()
+ setSelectedPlan('premium-yearly')
+ }}
+ >
+
+
+ {t('onboarding.paywall.bestValue')}
+
+
+
+ {t('onboarding.paywall.yearlyPrice')}
+
+
+ {t('common:units.perYear')}
+
+
+ {t('onboarding.paywall.savePercent')}
+
+
+
+ {/* Monthly */}
+ {
+ haptics.selection()
+ setSelectedPlan('premium-monthly')
+ }}
+ >
+
+ {t('onboarding.paywall.monthlyPrice')}
+
+
+ {t('common:units.perMonth')}
+
+
+
+
+ {/* CTA */}
+ {
+ haptics.buttonTap()
+ onSubscribe(selectedPlan)
+ }}
+ >
+
+ {t('onboarding.paywall.trialCta')}
+
+
+
+ {/* Guarantees */}
+
+
+ {t('onboarding.paywall.guarantees')}
+
+
+
+ {/* Skip */}
+
+
+ {t('onboarding.paywall.skipButton')}
+
+
+
+ )
+}
+
+// ═══════════════════════════════════════════════════════════════════════════
+// MAIN ONBOARDING CONTROLLER
+// ═══════════════════════════════════════════════════════════════════════════
+
+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)
+
+ const finishOnboarding = useCallback(
+ (plan: 'free' | 'premium-monthly' | 'premium-yearly') => {
+ completeOnboarding({
+ name: name.trim() || 'Athlete',
+ fitnessLevel: level,
+ goal,
+ weeklyFrequency: frequency,
+ barriers,
+ })
+ if (plan !== 'free') {
+ setSubscription(plan)
+ }
+ router.replace('/(tabs)')
+ },
+ [name, level, goal, frequency, barriers]
+ )
+
+ const nextStep = useCallback(() => {
+ setStep((s) => Math.min(s + 1, TOTAL_STEPS))
+ }, [])
+
+ 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
+// ═══════════════════════════════════════════════════════════════════════════
+
+const styles = 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: DARK.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,
+ ...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: DARK.OVERLAY_1,
+ 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: DARK.SURFACE,
+ borderRadius: RADIUS.MD,
+ paddingHorizontal: SPACING[4],
+ color: TEXT.PRIMARY,
+ fontSize: 17,
+ borderWidth: 1,
+ borderColor: BORDER.GLASS,
+ },
+ segmentRow: {
+ flexDirection: 'row',
+ backgroundColor: DARK.SURFACE,
+ borderRadius: RADIUS.MD,
+ padding: 3,
+ gap: 2,
+ },
+ segmentButton: {
+ flex: 1,
+ height: 36,
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: RADIUS.SM,
+ },
+ segmentButtonActive: {
+ backgroundColor: DARK.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',
+ borderRadius: RADIUS.GLASS_CARD,
+ ...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],
+ },
+ skipButton: {
+ alignItems: 'center',
+ paddingVertical: SPACING[5],
+ marginTop: SPACING[2],
+ },
+})
+
+// ── Screen 4: Feature List Styles ──
+const wowStyles = 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,
+ },
+})
diff --git a/src/shared/components/OnboardingStep.tsx b/src/shared/components/OnboardingStep.tsx
new file mode 100644
index 0000000..0b87172
--- /dev/null
+++ b/src/shared/components/OnboardingStep.tsx
@@ -0,0 +1,108 @@
+/**
+ * TabataFit OnboardingStep
+ * Reusable wrapper for each onboarding screen — progress bar, animation, layout
+ */
+
+import { useRef, useEffect } from 'react'
+import { View, StyleSheet, Animated, Dimensions } from 'react-native'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+import { DARK, BRAND, TEXT } from '../constants/colors'
+import { SPACING, LAYOUT } from '../constants/spacing'
+import { DURATION, EASE } from '../constants/animations'
+
+const { width: SCREEN_WIDTH } = Dimensions.get('window')
+
+interface OnboardingStepProps {
+ step: number
+ totalSteps: number
+ children: React.ReactNode
+}
+
+export function OnboardingStep({ step, totalSteps, children }: OnboardingStepProps) {
+ const insets = useSafeAreaInsets()
+ const slideAnim = useRef(new Animated.Value(SCREEN_WIDTH)).current
+ const fadeAnim = useRef(new Animated.Value(0)).current
+ const progressAnim = useRef(new Animated.Value(0)).current
+
+ useEffect(() => {
+ // Reset position for new step
+ slideAnim.setValue(SCREEN_WIDTH)
+ fadeAnim.setValue(0)
+
+ // Animate in
+ Animated.parallel([
+ Animated.timing(slideAnim, {
+ toValue: 0,
+ duration: DURATION.NORMAL,
+ easing: EASE.EASE_OUT,
+ useNativeDriver: true,
+ }),
+ Animated.timing(fadeAnim, {
+ toValue: 1,
+ duration: DURATION.NORMAL,
+ easing: EASE.EASE_OUT,
+ useNativeDriver: true,
+ }),
+ ]).start()
+
+ // Animate progress bar
+ Animated.timing(progressAnim, {
+ toValue: step / totalSteps,
+ duration: DURATION.SLOW,
+ easing: EASE.EASE_OUT,
+ useNativeDriver: false,
+ }).start()
+ }, [step])
+
+ const progressWidth = progressAnim.interpolate({
+ inputRange: [0, 1],
+ outputRange: ['0%', '100%'],
+ })
+
+ return (
+
+ {/* Progress bar */}
+
+
+
+
+ {/* Step content */}
+
+ {children}
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: DARK.BASE,
+ },
+ progressTrack: {
+ height: 3,
+ backgroundColor: DARK.SURFACE,
+ marginHorizontal: LAYOUT.SCREEN_PADDING,
+ borderRadius: 2,
+ overflow: 'hidden',
+ },
+ progressFill: {
+ height: '100%',
+ backgroundColor: BRAND.PRIMARY,
+ borderRadius: 2,
+ },
+ content: {
+ flex: 1,
+ paddingHorizontal: LAYOUT.SCREEN_PADDING,
+ paddingTop: SPACING[8],
+ },
+})