import { View, Text, StyleSheet, RefreshControl, TouchableOpacity, ActivityIndicator, Platform, useWindowDimensions, ScrollView } from "react-native"; import { BlurView } from "expo-blur"; import { useMatchStore, useAuthStore, type PendingQuestion } from "../../src/store"; import { useEffect, useCallback, useState, useRef } from "react"; import { useFocusEffect } from "expo-router"; import { Ionicons } from "@expo/vector-icons"; import { useRouter } from "expo-router"; import Animated, { FadeIn, FadeInRight, FadeInUp, useSharedValue, useAnimatedScrollHandler, useAnimatedStyle, interpolate, Extrapolation, } from "react-native-reanimated"; import { GradientBackground, GlassButton, DecorativeSeparator } from "../../src/components/ui"; import { CompactHeader } from "../../src/components/discovery"; import { SwipeableMatchItem } from "../../src/components/matches"; import { colors, spacing, typography, radius } from "../../src/theme"; import { MatchesTutorial } from "../../src/components/tutorials"; import { hasSeenMatchesTutorial, markMatchesTutorialSeen } from "../../src/lib/matchesTutorialSeen"; // Feature colors - Matches uses Primary per DESIGN.md const PRIMARY = colors.primary; const PRIMARY_RGBA = 'rgba(225, 48, 108, '; const SECONDARY = colors.secondary; const SECONDARY_RGBA = 'rgba(155, 89, 182, '; const MAX_CONTENT_WIDTH = 500; const HEADER_SCROLL_DISTANCE = 100; const AnimatedFlatList = Animated.FlatList; export default function MatchesScreen() { const { matches, fetchMatches, markAllAsSeen, isLoading, isRefreshing, hasMore, isLoadingMore, totalCount, newMatchesCount, // Archive state and methods archivedMatches, showArchived, archiveMatch, unarchiveMatch, isLoadingArchived, // Pending state and methods pendingQuestions, isLoadingPending, // Their Turn state and methods theirTurnQuestions, isLoadingTheirTurn, fetchTheirTurnQuestions, currentView, setCurrentView, ensureViewLoaded, fetchPendingQuestions, fetchArchivedMatches, // Nudge state and methods nudgeCooldownUntil, isNudging, sendNudge, checkNudgeCooldown, } = useMatchStore(); const { user, couple, partner } = useAuthStore(); const router = useRouter(); const { width } = useWindowDimensions(); const isWideScreen = width > MAX_CONTENT_WIDTH; const [showTutorial, setShowTutorial] = useState(false); const [nudgeFeedback, setNudgeFeedback] = useState(null); const [headerHeight, setHeaderHeight] = useState(130); const scrollY = useSharedValue(0); const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, }); // Animated styles for collapsing header const heroStyle = useAnimatedStyle(() => { const opacity = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE * 0.7], [1, 0], Extrapolation.CLAMP ); const scale = interpolate( scrollY.value, [0, HEADER_SCROLL_DISTANCE], [1, 0.95], Extrapolation.CLAMP ); return { opacity, transform: [{ scale }] }; }); // Check if tutorial should be shown when screen is focused or matches change useFocusEffect( useCallback(() => { const checkTutorial = async () => { if (matches.length > 0) { const seen = await hasSeenMatchesTutorial(); if (!seen) { setShowTutorial(true); } } }; checkTutorial(); }, [matches.length]) ); const handleTutorialComplete = async () => { await markMatchesTutorialSeen(); setShowTutorial(false); }; // Load whichever view is showing, once. Returning to this tab reuses what is // already cached; the sync poll refreshes it when the server reports a change, // and pull-to-refresh remains the explicit way to force one. useFocusEffect( useCallback(() => { ensureViewLoaded(); // Cleanup: mark matches as seen when leaving the screen (if on Complete tab) return () => { const state = useMatchStore.getState(); if (state.currentView === 'active' && state.matches.length > 0 && state.newMatchesCount > 0) { state.markAllAsSeen(); } }; }, [ensureViewLoaded]) ); // The nudge cooldown only moves when this device sends a nudge, and sendNudge // already records the new cooldown, so one read on mount is enough. useEffect(() => { void checkNudgeCooldown(); }, [checkNudgeCooldown]); // Format cooldown remaining time const formatCooldownTime = (cooldownUntil: Date): string => { const now = new Date(); const diffMs = cooldownUntil.getTime() - now.getTime(); if (diffMs <= 0) return ''; const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (diffHours > 0) { return `${diffHours}h ${diffMins}m`; } return `${diffMins}m`; }; // Check if nudge is on cooldown const isNudgeOnCooldown = !!(nudgeCooldownUntil && new Date() < nudgeCooldownUntil); // Handle nudge button press const handleNudge = async () => { if (isNudging || isNudgeOnCooldown) return; const result = await sendNudge(); if (result.success) { setNudgeFeedback(result.notificationSent ? 'Nudge sent!' : 'Nudge recorded'); setTimeout(() => setNudgeFeedback(null), 3000); } }; const handleLoadMore = useCallback(() => { console.log('[Matches] onEndReached called', { isLoading, isLoadingMore, hasMore, matchesCount: matches.length }); if (!isLoading && !isLoadingMore && hasMore) { fetchMatches(false); } }, [isLoading, isLoadingMore, hasMore, matches.length]); // Track previous view to detect when leaving Complete tab const prevViewRef = useRef(currentView); // Mark matches as seen when navigating AWAY from the Complete tab useEffect(() => { const prevView = prevViewRef.current; prevViewRef.current = currentView; // If we were on 'active' and now switching to a different view, mark as seen if (prevView === 'active' && currentView !== 'active' && matches.length > 0) { markAllAsSeen(); } }, [currentView, matches.length, markAllAsSeen]); const renderItem = ({ item, index }: { item: any; index: number }) => { // Skip rendering if question was deleted if (!item.question) { return null; } const userResponse = item.responses?.find((r: any) => r.user_id === user?.id); const partnerResponse = item.responses?.find((r: any) => r.user_id !== user?.id); let userText = item.question.text; let partnerText = item.question.partner_text; if (item.question.partner_text && userResponse && partnerResponse) { const userTime = new Date(userResponse.created_at).getTime(); const partnerTime = new Date(partnerResponse.created_at).getTime(); if (userTime > partnerTime) { userText = item.question.partner_text; partnerText = item.question.text; } } const isYesYes = item.match_type === "yes_yes"; const isBothAnswered = item.match_type === "both_answered"; // Get the appropriate label for the match type const getMatchLabel = () => { if (isYesYes) return "YES + YES"; if (isBothAnswered) return "MATCHED"; return "YES + MAYBE"; }; // Get the appropriate icon for the match type const getMatchIcon = () => { if (isYesYes || isBothAnswered) return "heart"; return "heart-half"; }; const handleArchive = async () => { if (showArchived) { await unarchiveMatch(item.id); } else { await archiveMatch(item.id); } }; return ( router.push(`/chat/${item.id}`)} activeOpacity={0.8} > {/* Subtle gradient background - removed for flat style */} {/* Top silk highlight - removed for flat style */} {/* Premium icon container */} {userText} {item.question.partner_text && ( Partner: {partnerText} )} {getMatchLabel()} {item.is_new && ( NEW )} {new Date(item.created_at).toLocaleDateString()} {item.unreadCount > 0 && ( )} {/* Premium border */} ); }; const renderPendingItem = ({ item, index }: { item: PendingQuestion; index: number }) => { if (!item.question) { return null; } const packName = item.question.pack?.name ?? 'Unknown Pack'; const timeSince = getTimeSince(item.partnerAnsweredAt); return ( router.push({ pathname: "/(app)/swipe", params: { mode: 'pending', startQuestionId: item.question.id } })} activeOpacity={0.8} > {/* Pending icon container - hourglass */} {item.question.text} {packName} {timeSince} {/* Premium border */} ); }; const renderTheirTurnItem = ({ item, index }: { item: PendingQuestion; index: number }) => { if (!item.question) { return null; } const packName = item.question.pack?.name ?? 'Unknown Pack'; const timeSince = getTimeSince(item.partnerAnsweredAt); return ( {/* Their turn icon container - clock */} {item.question.text} {packName} {timeSince} {/* Premium border */} ); }; // Helper to get time since partner answered const getTimeSince = (dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMins < 1) return 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString(); }; const renderFooter = () => { if (!isLoadingMore) return null; return ( ); }; if (!user) { return ( ); } // Gate matches behind pairing - user must have a partner to view matches if (!partner) { return ( {/* Ambient Orbs - Commented out for flat look */} {/* Icon */} {/* Title section */} {couple ? "ALMOST THERE" : "CONNECT"} {couple ? "Waiting" : "Pair Up"} {/* Status badge */} {couple ? "INVITE SENT" : "MADE FOR TWO"} {/* Description */} {couple ? "Share your invite code so they can join you. Once paired, you'll discover your matches here!" : "Sauci is made for two! Connect with your partner to start discovering what you agree on." } {/* Feature hints */} See when you both agree Unlock hidden connections Chat about your matches {/* Bottom teaser */} {couple ? "Your partner is just a code away" : "Begin your journey together"} router.push("/pairing")} style={{ marginTop: spacing.lg }} > {couple ? "View Invite Code" : "Pair Now"} ); } return ( {/* Ambient Orbs - Commented out for flat look */} setHeaderHeight(e.nativeEvent.layout.height)} > router.push({ pathname: "/(app)/my-answers", params: { returnTo: "/(app)/matches" } })} > } /> item.id} contentContainerStyle={[ styles.list, isWideScreen && styles.listWide, { paddingTop: headerHeight }, ]} showsVerticalScrollIndicator={false} onScroll={scrollHandler} scrollEventThrottle={16} refreshControl={ { if (currentView === 'pending') { fetchPendingQuestions(); } else if (currentView === 'their_turn') { fetchTheirTurnQuestions(); } else if (currentView === 'archived') { fetchArchivedMatches(); } else { fetchMatches(true); } }} tintColor={colors.primary} colors={[colors.primary]} progressViewOffset={headerHeight} /> } onEndReached={(currentView === 'pending' || currentView === 'their_turn') ? undefined : handleLoadMore} onEndReachedThreshold={2} ListFooterComponent={(currentView === 'pending' || currentView === 'their_turn') ? null : renderFooter} ListHeaderComponent={ {/* Filter toggle - horizontal scrollable */} currentView !== 'active' && setCurrentView('active')} activeOpacity={0.7} > Complete {newMatchesCount > 0 && currentView !== 'active' && ( {newMatchesCount} )} currentView !== 'pending' && setCurrentView('pending')} activeOpacity={0.7} > {isLoadingPending ? ( ) : ( <> Your Turn {pendingQuestions.length > 0 && currentView !== 'pending' && ( {pendingQuestions.length} )} )} currentView !== 'their_turn' && setCurrentView('their_turn')} activeOpacity={0.7} > {isLoadingTheirTurn ? ( ) : ( <> Their Turn {theirTurnQuestions.length > 0 && currentView !== 'their_turn' && ( {theirTurnQuestions.length} )} )} currentView !== 'archived' && setCurrentView('archived')} activeOpacity={0.7} > {isLoadingArchived ? ( ) : ( <> Archived )} {/* Count badge */} {currentView === 'archived' ? `${archivedMatches.length} ARCHIVED` : currentView === 'pending' ? `${pendingQuestions.length} WAITING` : currentView === 'their_turn' ? `${theirTurnQuestions.length} SENT` : `${totalCount ?? matches.length} COMPLETE` } {/* Nudge button - only show in Their Turn view with questions */} {currentView === 'their_turn' && theirTurnQuestions.length > 0 && ( {nudgeFeedback ? ( {nudgeFeedback} ) : ( {isNudging ? ( ) : ( <> {isNudgeOnCooldown && nudgeCooldownUntil ? `Nudge in ${formatCooldownTime(nudgeCooldownUntil)}` : 'Nudge partner' } )} )} )} } ListEmptyComponent={ currentView === 'archived' ? ( {/* Archive icon */} {/* Description */} No Archived Matches Matches you archive will appear here. Swipe left on any match to archive it. setCurrentView('active')} style={{ marginTop: spacing.lg }} > View Active Matches ) : currentView === 'pending' ? ( {/* Pending icon */} {/* Description */} All Caught Up! You've answered all the questions your partner has swiped on. Keep swiping to discover more together! router.push("/")} style={{ marginTop: spacing.lg }} > Keep Swiping ) : currentView === 'their_turn' ? ( {/* Their Turn icon */} {/* Description */} Waiting for Partner All questions you've answered have been matched! Keep swiping to give your partner more to respond to. router.push("/")} style={{ marginTop: spacing.lg }} > Keep Swiping ) : ( {/* Description */} Answer questions together to discover what you both enjoy. Matches appear when you agree! {/* Feature hints */} Swipe right for yes Match when you both agree Chat about your matches {/* Bottom teaser */} Your first match is just a swipe away router.push("/")} style={{ marginTop: spacing.lg }} > Start Swiping ) } /> {/* Matches Tutorial Overlay */} {showTutorial && ( )} ); } const styles = StyleSheet.create({ container: { flex: 1, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, // Ambient orbs ambientOrb: { position: 'absolute', width: 300, height: 300, borderRadius: 150, }, orbTopRight: { top: 60, right: -40, }, orbBottomLeft: { bottom: 180, left: -40, }, orbGradient: { width: '100%', height: '100%', borderRadius: 150, }, stickyHeader: { position: "absolute", top: 0, left: 0, right: 0, zIndex: 10, overflow: "hidden", }, stickyHeaderWide: { left: "50%", right: "auto", width: MAX_CONTENT_WIDTH, transform: [{ translateX: -MAX_CONTENT_WIDTH / 2 }], }, headerActionButton: { width: 32, height: 32, borderRadius: 16, backgroundColor: colors.backgroundLight, justifyContent: "center", alignItems: "center", borderWidth: 1, borderColor: colors.border, }, // Header (Hero) header: { paddingTop: spacing.lg, paddingHorizontal: spacing.lg, paddingBottom: spacing.lg, alignItems: 'center', }, headerWide: { alignSelf: 'center', width: '100%', maxWidth: MAX_CONTENT_WIDTH, }, headerContent: { alignItems: 'center', width: '100%', }, headerLabel: { ...typography.caption2, fontWeight: '600', letterSpacing: 3, color: PRIMARY, marginBottom: spacing.xs, }, headerTitle: { ...typography.largeTitle, color: colors.text, textAlign: 'center', }, // Filter toggle - scrollable carousel filterScrollContainer: { flexGrow: 0, marginBottom: spacing.md, alignSelf: 'stretch', marginHorizontal: -(spacing.lg * 2), }, filterContainer: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, paddingHorizontal: spacing.lg * 2, }, filterTab: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: spacing.lg, paddingVertical: spacing.sm + 2, borderRadius: radius.full, gap: spacing.xs, height: 40, backgroundColor: colors.backgroundLight, borderWidth: 1, borderColor: colors.border, }, filterTabActive: { backgroundColor: `${PRIMARY_RGBA}0.15)`, borderColor: `${PRIMARY_RGBA}0.3)`, }, filterTabActiveSecondary: { backgroundColor: `${SECONDARY_RGBA}0.15)`, borderColor: `${SECONDARY_RGBA}0.3)`, }, filterTabText: { ...typography.caption1, color: colors.textTertiary, fontWeight: '500', }, filterTabTextActive: { color: PRIMARY, fontWeight: '600', }, pendingBadge: { backgroundColor: PRIMARY, borderRadius: 10, minWidth: 18, height: 18, paddingHorizontal: 5, justifyContent: 'center', alignItems: 'center', marginLeft: 2, }, theirTurnBadge: { backgroundColor: SECONDARY, }, pendingBadgeText: { ...typography.caption2, color: colors.text, fontWeight: '700', fontSize: 10, }, countBadgePremium: { flexDirection: 'row', alignItems: 'center', backgroundColor: colors.backgroundLight, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.full, borderWidth: 1, borderColor: colors.border, gap: spacing.xs, }, countTextPremium: { ...typography.caption2, fontWeight: '600', letterSpacing: 2, color: PRIMARY, }, // List list: { padding: spacing.lg, paddingTop: 0, paddingBottom: Platform.OS === 'ios' ? 120 : 100, }, listWide: { alignSelf: 'center', width: '100%', maxWidth: MAX_CONTENT_WIDTH, }, // Premium Match Card matchCardPremium: { marginBottom: spacing.sm, borderRadius: radius.lg, overflow: 'hidden', padding: spacing.md, backgroundColor: colors.backgroundLight, borderWidth: 1, borderColor: colors.border, }, matchCardNew: { backgroundColor: `${PRIMARY_RGBA}0.08)`, borderColor: `${PRIMARY_RGBA}0.4)`, borderLeftWidth: 3, borderLeftColor: PRIMARY, }, cardSilkHighlight: { display: 'none', }, cardPremiumBorder: { display: 'none', }, cardPendingBorder: { display: 'none', }, cardTheirTurnBorder: { display: 'none', }, matchRow: { flexDirection: "row", alignItems: "center", }, iconContainerPremium: { width: 44, height: 44, borderRadius: 22, backgroundColor: colors.background, borderWidth: 1, borderColor: colors.border, justifyContent: "center", alignItems: "center", marginRight: spacing.md, }, iconContainerYesYes: { backgroundColor: `${PRIMARY_RGBA}0.1)`, borderColor: `${PRIMARY_RGBA}0.2)`, }, iconContainerPending: { backgroundColor: `${PRIMARY_RGBA}0.15)`, borderColor: `${PRIMARY_RGBA}0.25)`, }, iconContainerTheirTurn: { backgroundColor: `${SECONDARY_RGBA}0.15)`, borderColor: `${SECONDARY_RGBA}0.25)`, }, content: { flex: 1, }, questionTextPremium: { ...typography.subhead, color: colors.text, fontWeight: "600", marginBottom: spacing.xs, lineHeight: 20, }, partnerTextPremium: { ...typography.caption1, color: colors.textSecondary, fontStyle: "italic", marginBottom: spacing.sm, }, metaRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", }, metaLeft: { flexDirection: "row", alignItems: "center", gap: spacing.xs, }, newBadge: { backgroundColor: PRIMARY, paddingHorizontal: spacing.xs + 2, paddingVertical: 2, borderRadius: radius.xs, }, newBadgeText: { ...typography.caption2, color: colors.text, fontWeight: "700", fontSize: 9, letterSpacing: 0.5, }, tagPremium: { backgroundColor: colors.background, paddingHorizontal: spacing.sm, paddingVertical: 3, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.border, }, tagPremiumHighlight: { backgroundColor: `${PRIMARY_RGBA}0.1)`, borderColor: `${PRIMARY_RGBA}0.2)`, }, tagTextPremium: { ...typography.caption2, color: SECONDARY, fontWeight: "600", letterSpacing: 1, }, tagTextPremiumHighlight: { color: PRIMARY, }, tagTheirTurn: { backgroundColor: `${SECONDARY_RGBA}0.1)`, borderColor: `${SECONDARY_RGBA}0.2)`, }, tagTextTheirTurn: { color: SECONDARY, }, datePremium: { ...typography.caption2, color: colors.textTertiary, fontStyle: 'italic', }, rightSection: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, marginLeft: spacing.sm, }, unreadBadgePremium: { width: 22, height: 22, borderRadius: 11, backgroundColor: PRIMARY, justifyContent: 'center', alignItems: 'center', }, chevronContainerPremium: { width: 28, height: 28, borderRadius: 14, backgroundColor: colors.background, borderWidth: 1, borderColor: colors.border, justifyContent: 'center', alignItems: 'center', }, chevronTheirTurn: { backgroundColor: `${SECONDARY_RGBA}0.1)`, borderColor: `${SECONDARY_RGBA}0.2)`, }, // Premium Empty State emptyContent: { width: '100%', alignItems: 'center', paddingHorizontal: spacing.md, paddingVertical: spacing.xl, }, emptyIconContainer: { width: 80, height: 80, borderRadius: 40, backgroundColor: colors.backgroundLight, borderWidth: 1, borderColor: colors.border, justifyContent: 'center', alignItems: 'center', marginBottom: spacing.lg, }, emptyTitle: { ...typography.title3, color: colors.text, marginBottom: spacing.sm, }, emptyDescription: { ...typography.body, color: colors.textSecondary, textAlign: 'center', marginBottom: spacing.xl, paddingHorizontal: spacing.md, }, emptyFeatures: { marginBottom: spacing.xl, }, emptyFeatureItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: spacing.sm, }, emptyFeatureText: { ...typography.subhead, color: colors.text, marginLeft: spacing.sm, }, emptyTeaser: { ...typography.footnote, fontStyle: 'italic', color: colors.textTertiary, textAlign: 'center', }, footerLoader: { paddingVertical: spacing.md, alignItems: "center", justifyContent: "center", }, pairingGateContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: spacing.lg, }, pairingGateContent: { width: '100%', maxWidth: MAX_CONTENT_WIDTH, alignItems: 'center', paddingHorizontal: spacing.md, }, pairingGateIconContainer: { width: 80, height: 80, borderRadius: 40, backgroundColor: `${PRIMARY_RGBA}0.1)`, borderWidth: 1, borderColor: `${PRIMARY_RGBA}0.2)`, justifyContent: 'center', alignItems: 'center', marginBottom: spacing.lg, }, pairingGateLabel: { ...typography.caption2, fontWeight: '600', letterSpacing: 3, color: PRIMARY, marginBottom: spacing.xs, }, pairingGateTitle: { ...typography.largeTitle, color: colors.text, textAlign: 'center', marginBottom: spacing.sm, }, pairingGateBadge: { backgroundColor: `${PRIMARY_RGBA}0.1)`, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: radius.full, borderWidth: 1, borderColor: `${PRIMARY_RGBA}0.2)`, marginBottom: spacing.lg, }, pairingGateBadgeText: { ...typography.caption2, fontWeight: '600', letterSpacing: 2, color: PRIMARY, }, pairingGateDescription: { ...typography.body, color: colors.textSecondary, textAlign: 'center', marginBottom: spacing.xl, paddingHorizontal: spacing.md, }, pairingGateFeatures: { marginBottom: spacing.xl, }, pairingGateFeatureItem: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: spacing.sm, }, pairingGateFeatureText: { ...typography.subhead, color: colors.text, marginLeft: spacing.sm, }, pairingGateTeaser: { ...typography.footnote, fontStyle: 'italic', color: colors.textTertiary, textAlign: 'center', }, // Nudge button styles nudgeContainer: { marginTop: spacing.md, alignItems: 'center', }, nudgeButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingHorizontal: spacing.lg, paddingVertical: spacing.sm + 2, borderRadius: radius.full, gap: spacing.xs, backgroundColor: `${SECONDARY_RGBA}0.15)`, borderWidth: 1, borderColor: `${SECONDARY_RGBA}0.3)`, minWidth: 140, }, nudgeButtonDisabled: { backgroundColor: colors.backgroundLight, borderColor: colors.border, }, nudgeButtonText: { ...typography.caption1, color: SECONDARY, fontWeight: '600', }, nudgeButtonTextDisabled: { color: colors.textTertiary, }, nudgeFeedback: { flexDirection: 'row', alignItems: 'center', gap: spacing.xs, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, }, nudgeFeedbackText: { ...typography.caption1, color: colors.success, fontWeight: '500', }, });