import React, { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Animated, Easing, FlatList, Image, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { useApp } from '../../context/AppContext'; import { useColors } from '../../constants/Colors'; import { useSafeAnalytics } from '../../services/analytics'; import { OnboardingProgressService } from '../../services/onboardingProgressService'; import { PreAuthOnboardingService } from '../../services/preAuthOnboardingService'; import { Language } from '../../types'; // Avatar path matching copied file const BOT_AVATAR = require('../../assets/robot_monstera_avatar.jpg'); interface Message { id: string; sender: 'bot' | 'user'; text: string; subtext?: string; timestamp: string; } const getChatCopy = (language: Language) => { if (language === 'de') { return { title: 'GreenLens', skip: 'Überspringen', continue: 'Weiter', today: 'Heute, 20:42 Uhr', typing: 'schreibt...', // Reihenfolge folgt dem Nutzer, nicht dem Unternehmen: erst sein Problem, // dann Kontext, zuletzt die Marketing-Frage. botGreeting: 'Hi! Ich helfe dir herauszufinden, was mit deinen Pflanzen los ist. 🌿', botSituation: 'Damit ich weiß, wo wir starten: Hat gerade eine deiner Pflanzen ein Problem?', botSymptom: 'Verstanden. Was siehst du genau? Das grenzt die möglichen Ursachen am stärksten ein.', botNoSymptom: 'Gut zu wissen - dann richten wir alles auf Vorbeugung aus.', botLight: 'Licht entscheidet bei den meisten Problemen mit. Wie viel Licht bekommt deine Pflanze?', botExperience: 'Fast geschafft. Wie sicher fühlst du dich bei Pflanzenpflege? Danach passe ich an, wie ausführlich ich erkläre.', botSource: 'Letzte Frage, rein aus Neugier: Wie hast du von GreenLens gehört?', botComplete: 'Perfekt. Ich stelle dir jetzt deinen Plan zusammen.', stepSituation: 'Hat eine Pflanze gerade ein Problem?', stepSymptom: 'Was siehst du?', stepLight: 'Wie viel Licht bekommt sie?', stepExperience: 'Wie sicher fühlst du dich?', stepSource: 'Wie hast du von uns gehört?', situations: { acute: 'Ja - und es wird schnell schlechter', slow: 'Ja - es verändert sich langsam', prevention: 'Nein - ich will vorbeugen', }, symptoms: { yellow: 'Gelbe Blätter', brown_tips: 'Braune Spitzen oder Ränder', drooping: 'Hängt oder lässt die Blätter fallen', spots: 'Flecken oder Belag', pests: 'Krabbeltiere', other: 'Etwas anderes', }, sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Freunde oder Familie', search: 'Google oder Suche', // Bewusst unwahrscheinliche Optionen: sie brechen das Muster der // Standard-Attributionsfrage und machen den letzten Schritt leichter. tv: 'Im Fernsehen (haben wir nicht - aber schön wärs)', radio: 'Im Radio zwischen zwei Songs', plant: 'Meine Pflanze hat es mir empfohlen', other: 'Etwas anderes', }, experiences: { beginner: 'Unsicher - ich fange gerade an', intermediate: 'Geht so - ich habe ein paar Pflanzen', advanced: 'Sicher - ich kenne mich aus', }, lights: { bright_indirect: { title: 'Helles, indirektes Licht', sub: 'In der Nähe eines sonnigen Fensters, ohne direkte Strahlen', }, low_light: { title: 'Wenig Licht', sub: 'Weit weg von Fenstern, meistens Schatten', }, direct_sunlight: { title: 'Direktes Sonnenlicht', sub: 'Den ganzen Tag direkt an einem sonnigen Fenster', }, }, }; } if (language === 'es') { return { title: 'GreenLens', skip: 'Omitir', continue: 'Continuar', today: 'Hoy, 8:42 PM', typing: 'escribiendo...', botGreeting: '¡Hola! Te ayudo a averiguar qué les pasa a tus plantas. 🌿', botSituation: 'Para saber por dónde empezamos: ¿alguna de tus plantas tiene un problema ahora mismo?', botSymptom: 'Entendido. ¿Qué ves exactamente? Esto es lo que más acota las causas posibles.', botNoSymptom: 'Bien saberlo - entonces lo enfocamos todo en la prevención.', botLight: 'La luz influye en la mayoría de los problemas. ¿Cuánta luz recibe tu planta?', botExperience: 'Casi listo. ¿Qué tan seguro te sientes cuidando plantas? Con eso ajusto cuánto explico.', botSource: 'Última pregunta, por curiosidad: ¿cómo te enteraste de GreenLens?', botComplete: 'Perfecto. Ahora preparo tu plan.', stepSituation: '¿Alguna planta tiene un problema?', stepSymptom: '¿Qué ves?', stepLight: '¿Cuánta luz recibe?', stepExperience: '¿Qué tan seguro te sientes?', stepSource: '¿Cómo nos conociste?', situations: { acute: 'Sí - y empeora rápido', slow: 'Sí - cambia poco a poco', prevention: 'No - quiero prevenir', }, symptoms: { yellow: 'Hojas amarillas', brown_tips: 'Puntas o bordes marrones', drooping: 'Se cae o pierde hojas', spots: 'Manchas o residuo', pests: 'Bichos', other: 'Otra cosa', }, sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Amigos o familia', search: 'Google o búsqueda', tv: 'En la tele (no tenemos anuncios - pero ojalá)', radio: 'En la radio entre dos canciones', plant: 'Me la recomendó mi planta', other: 'Otra cosa', }, experiences: { beginner: 'Inseguro - recién empiezo', intermediate: 'Más o menos - tengo algunas plantas', advanced: 'Seguro - sé bastante', }, lights: { bright_indirect: { title: 'Luz brillante e indirecta', sub: 'Cerca de una ventana soleada, sin sol directo', }, low_light: { title: 'Poca luz', sub: 'Lejos de las ventanas, principalmente sombra', }, direct_sunlight: { title: 'Luz solar directa', sub: 'Directamente en una ventana soleada todo el día', }, }, }; } return { title: 'GreenLens', skip: 'Skip', continue: 'Continue', today: 'Today, 8:42 PM', typing: 'typing...', botGreeting: "Hi! I'll help you work out what's going on with your plants. 🌿", botSituation: 'So I know where to start: is one of your plants having a problem right now?', botSymptom: 'Got it. What exactly do you see? This narrows down the possible causes more than anything else.', botNoSymptom: "Good to know - then we'll focus everything on prevention.", botLight: 'Light plays a role in most problems. How much light does your plant get?', botExperience: 'Almost done. How confident do you feel about plant care? I use that to adjust how much I explain.', botSource: 'Last question, purely out of curiosity: how did you hear about GreenLens?', botComplete: "Perfect. I'm putting your plan together now.", stepSituation: 'Is a plant having a problem?', stepSymptom: 'What do you see?', stepLight: 'How much light does it get?', stepExperience: 'How confident do you feel?', stepSource: 'How did you hear about us?', situations: { acute: "Yes - and it's getting worse fast", slow: "Yes - it's changing slowly", prevention: 'No - I want to prevent problems', }, symptoms: { yellow: 'Yellow leaves', brown_tips: 'Brown tips or edges', drooping: 'Drooping or dropping leaves', spots: 'Spots or residue', pests: 'Bugs', other: 'Something else', }, sources: { app_store: 'App Store / Play Store', instagram: 'Instagram', tiktok: 'TikTok', friend: 'Friends or family', search: 'Google or search', tv: "On TV (we don't run ads - but a girl can dream)", radio: 'On the radio between two songs', plant: 'My plant recommended it', other: 'Something else', }, experiences: { beginner: "Not confident - I'm just starting", intermediate: 'So-so - I have a few plants', advanced: 'Confident - I know my way around', }, lights: { bright_indirect: { title: 'Bright, indirect light', sub: 'Near a sunny window, but no harsh rays', }, low_light: { title: 'Low light', sub: 'Far from windows, mostly shadow', }, direct_sunlight: { title: 'Direct sunlight', sub: 'Directly in a sunny window all day', }, }, }; }; /** Fragenreihenfolge des Chats. Bewusst: Problem zuerst, Attribution zuletzt. */ const STEP_KEYS = ['situation', 'symptom', 'lightLevel', 'experienceLevel', 'acquisitionSource'] as const; type StepKey = (typeof STEP_KEYS)[number]; const TOTAL_STEPS = STEP_KEYS.length; const TypingIndicator = ({ colors }: { colors: any }) => { const dot1 = useRef(new Animated.Value(0)).current; const dot2 = useRef(new Animated.Value(0)).current; const dot3 = useRef(new Animated.Value(0)).current; useEffect(() => { const animateDot = (value: Animated.Value, delay: number) => { return Animated.loop( Animated.sequence([ Animated.delay(delay), Animated.timing(value, { toValue: -6, duration: 250, useNativeDriver: true, }), Animated.timing(value, { toValue: 0, duration: 250, useNativeDriver: true, }), Animated.delay(350), ]) ); }; const anim1 = animateDot(dot1, 0); const anim2 = animateDot(dot2, 120); const anim3 = animateDot(dot3, 240); anim1.start(); anim2.start(); anim3.start(); return () => { anim1.stop(); anim2.stop(); anim3.stop(); }; }, [dot1, dot2, dot3]); return ( ); }; export default function OnboardingChatScreen() { const router = useRouter(); const posthog = useSafeAnalytics(); const { session, colorPalette, language } = useApp(); // This screen is hardcoded dark (#131e14 backgrounds), so always use the // dark color set - theme-dependent text would be unreadable in light mode. const colors = useColors(true, colorPalette); const copy = getChatCopy(language); const { height } = useWindowDimensions(); const compact = height < 700; const [currentStep, setCurrentStep] = useState(0); const [messages, setMessages] = useState([]); const [isTyping, setIsTyping] = useState(false); const [showOptions, setShowOptions] = useState(false); const [answers, setAnswers] = useState({ situation: null as string | null, symptom: null as string | null, lightLevel: null as string | null, experienceLevel: null as string | null, acquisitionSource: null as string | null, }); const flatListRef = useRef(null); const inputScrollRef = useRef(null); const floatAnim = useRef(new Animated.Value(0)).current; // Float animation loop for the background monstera pot useEffect(() => { const float = () => { Animated.sequence([ Animated.timing(floatAnim, { toValue: -12, duration: 3000, easing: Easing.inOut(Easing.sin), useNativeDriver: true, }), Animated.timing(floatAnim, { toValue: 0, duration: 3000, easing: Easing.inOut(Easing.sin), useNativeDriver: true, }), ]).start(() => float()); }; float(); }, [floatAnim]); // Initial Bot Greeting useEffect(() => { setIsTyping(true); setShowOptions(false); const timer1 = setTimeout(() => { setMessages([ { id: 'greeting', sender: 'bot', text: copy.botGreeting, timestamp: copy.today, }, ]); setIsTyping(false); // Delay before asking first question const timer2 = setTimeout(() => { setIsTyping(true); const timer3 = setTimeout(() => { setMessages((prev) => [ ...prev, { id: 'question-situation', sender: 'bot', text: copy.botSituation, timestamp: copy.today, }, ]); setIsTyping(false); setShowOptions(true); // Only show options after question is fully posted! }, 800); return () => clearTimeout(timer3); }, 500); return () => clearTimeout(timer2); }, 1200); return () => clearTimeout(timer1); }, []); // Scroll to bottom on messages update useEffect(() => { setTimeout(() => { flatListRef.current?.scrollToEnd({ animated: true }); }, 100); }, [messages, isTyping]); useEffect(() => { inputScrollRef.current?.scrollTo({ y: 0, animated: false }); }, [currentStep]); const selectOption = (id: string, label: string) => { // Add user bubble const userMsg: Message = { id: `user-${currentStep}-${Date.now()}`, sender: 'user', text: label, timestamp: copy.today, }; setMessages((prev) => [...prev, userMsg]); // Wer "vorbeugen" waehlt, hat kein Symptom - die Symptomfrage wird // uebersprungen. Ohne das antwortet der Bot "dann beugen wir vor" und // fragt im selben Atemzug "Was siehst du?". const skipsSymptom = currentStep === 0 && id === 'prevention'; const nextStep = skipsSymptom ? 2 : currentStep + 1; setCurrentStep(nextStep); // Save answer const nextAnswers = { ...answers }; const stepKey: StepKey | undefined = STEP_KEYS[currentStep]; if (stepKey) nextAnswers[stepKey] = id; setAnswers(nextAnswers); // Trigger bot response typing setIsTyping(true); setShowOptions(false); // Hide immediately when user answers const delay = setTimeout(() => { let botText = ''; let botSubtext = undefined; if (nextStep === 1) { botText = copy.botSymptom; } else if (nextStep === 2) { // Nach dem uebersprungenen Symptomschritt zuerst die Ueberleitung, // damit die Antwort des Nutzers nicht unkommentiert bleibt. botText = skipsSymptom ? `${copy.botNoSymptom} ${copy.botLight}` : copy.botLight; } else if (nextStep === 3) { botText = copy.botExperience; } else if (nextStep === 4) { botText = copy.botSource; } else if (nextStep === TOTAL_STEPS) { botText = copy.botComplete; } setMessages((prev) => [ ...prev, { id: `bot-${nextStep}-${Date.now()}`, sender: 'bot', text: botText, subtext: botSubtext, timestamp: copy.today, }, ]); setIsTyping(false); setShowOptions(true); // Re-enable options after bot finishes typing }, 850); return () => clearTimeout(delay); }; const handleBack = () => { if (currentStep === 0) { router.back(); return; } // Gegenstueck zum uebersprungenen Symptomschritt: von Schritt 2 zurueck // landet ein Vorbeuger wieder bei der Situationsfrage, nicht bei einer // Symptomfrage, die er nie gesehen hat. const skippedSymptom = currentStep === 2 && answers.situation === 'prevention'; const prevStep = skippedSymptom ? 0 : currentStep - 1; setCurrentStep(prevStep); // Revert last answers const nextAnswers = { ...answers }; const prevKey: StepKey | undefined = STEP_KEYS[prevStep]; if (prevKey) nextAnswers[prevKey] = null; setAnswers(nextAnswers); // Remove user bubble and bot bubble from messages history setMessages((prev) => { const copyMsgs = [...prev]; // Pop bot question bubble and user response bubble copyMsgs.pop(); copyMsgs.pop(); return copyMsgs; }); }; const onSkip = () => { posthog.capture('onboarding_chat_skipped', { step: currentStep }); router.replace('/onboarding/health-check'); }; const onFinish = () => { if (session?.userId) { if (answers.acquisitionSource) OnboardingProgressService.setAcquisitionSource(session.userId, answers.acquisitionSource); if (answers.experienceLevel) OnboardingProgressService.setExperienceLevel(session.userId, answers.experienceLevel); } if (answers.acquisitionSource) void PreAuthOnboardingService.setAnswer('acquisitionSource', answers.acquisitionSource); if (answers.experienceLevel) void PreAuthOnboardingService.setAnswer('experienceLevel', answers.experienceLevel); if (answers.lightLevel) void PreAuthOnboardingService.setAnswer('lightLevel', answers.lightLevel); // Situation und Symptom steuern spaeter die Personalisierung des Plans. if (answers.situation) void PreAuthOnboardingService.setAnswer('situation', answers.situation); if (answers.symptom) void PreAuthOnboardingService.setAnswer('symptom', answers.symptom); posthog.capture('onboarding_chat_completed', { situation: answers.situation, symptom: answers.symptom, light: answers.lightLevel, experience: answers.experienceLevel, source: answers.acquisitionSource, }); router.replace('/onboarding/health-check'); }; // Build options list based on current question step const renderOptions = () => { if (!showOptions || isTyping) return null; // Schritt 1 - Situation. Erste Frage gehoert dem Nutzer, nicht dem Marketing. if (currentStep === 0) { return ( {copy.stepSituation} {([ { id: 'acute', label: copy.situations.acute, icon: 'alert-circle-outline' as const }, { id: 'slow', label: copy.situations.slow, icon: 'trending-down-outline' as const }, { id: 'prevention', label: copy.situations.prevention, icon: 'shield-checkmark-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } // Schritt 2 - Symptom. Der Moment, in dem sich der Nutzer wiedererkennt. if (currentStep === 1) { return ( {copy.stepSymptom} {([ { id: 'yellow', label: copy.symptoms.yellow, icon: 'leaf-outline' as const }, { id: 'brown_tips', label: copy.symptoms.brown_tips, icon: 'flame-outline' as const }, { id: 'drooping', label: copy.symptoms.drooping, icon: 'trending-down-outline' as const }, { id: 'spots', label: copy.symptoms.spots, icon: 'ellipse-outline' as const }, { id: 'pests', label: copy.symptoms.pests, icon: 'bug-outline' as const }, { id: 'other', label: copy.symptoms.other, icon: 'help-circle-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } // Schritt 3 - Licht. if (currentStep === 2) { return ( {copy.stepLight} {([ { id: 'bright_indirect', label: copy.lights.bright_indirect.title, sub: copy.lights.bright_indirect.sub, icon: 'wb-twilight' }, { id: 'low_light', label: copy.lights.low_light.title, sub: copy.lights.low_light.sub, icon: 'brightness-4' }, { id: 'direct_sunlight', label: copy.lights.direct_sunlight.title, sub: copy.lights.direct_sunlight.sub, icon: 'sunny' }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} {opt.sub} ))} ); } // Schritt 4 - Erfahrung. Formuliert als Sicherheitsgefuehl, nicht als // Wissenstest: Emma soll sich nicht schon hier als Anfaengerin abstempeln. if (currentStep === 3) { return ( {copy.stepExperience} {([ { id: 'beginner', label: copy.experiences.beginner, icon: 'leaf-outline' as const }, { id: 'intermediate', label: copy.experiences.intermediate, icon: 'sunny-outline' as const }, { id: 'advanced', label: copy.experiences.advanced, icon: 'flask-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } // Schritt 5 - Attribution. Ganz ans Ende verschoben: die Frage nuetzt uns, // nicht dem Nutzer. Die unwahrscheinlichen Optionen (TV, Radio, "meine // Pflanze") brechen das Muster und machen den letzten Tap leicht. if (currentStep === 4) { return ( {copy.stepSource} {([ { id: 'app_store', label: copy.sources.app_store, icon: 'logo-apple' as const }, { id: 'instagram', label: copy.sources.instagram, icon: 'logo-instagram' as const }, { id: 'tiktok', label: copy.sources.tiktok, icon: 'musical-notes-outline' as const }, { id: 'friend', label: copy.sources.friend, icon: 'people-outline' as const }, { id: 'search', label: copy.sources.search, icon: 'search-outline' as const }, { id: 'tv', label: copy.sources.tv, icon: 'tv-outline' as const }, { id: 'radio', label: copy.sources.radio, icon: 'radio-outline' as const }, { id: 'plant', label: copy.sources.plant, icon: 'leaf-outline' as const }, { id: 'other', label: copy.sources.other, icon: 'sparkles-outline' as const }, ]).map((opt) => ( selectOption(opt.id, opt.label)}> {opt.label} ))} ); } // Alle Fragen beantwortet -> Abschluss-CTA. if (currentStep >= TOTAL_STEPS) { return ( {copy.continue} ); } return null; }; const renderMessageItem = ({ item }: { item: Message }) => { const isBot = item.sender === 'bot'; return ( {isBot && ( )} {item.text} {item.subtext ? ( {item.subtext} ) : null} ); }; const progressPercent = `${Math.min(Math.round((currentStep / TOTAL_STEPS) * 100), 100)}%`; return ( {/* Soft stepwise gradient mask to blend bottom image edge with background */} {/* Header Bar */} {copy.skip} {/* Chat History Panel */} item.id} renderItem={renderMessageItem} contentContainerStyle={styles.chatListContent} showsVerticalScrollIndicator={false} ListHeaderComponent={ {copy.today} } ListFooterComponent={ isTyping ? ( ) : null } /> {/* Input/Selection Panel. Scrollbar und in der Hoehe gedeckelt: der Attributions-Schritt hat 9 Optionen, die bei grosser Systemschrift sonst den Chatverlauf und den Weiter-Button verdraengen. */} {renderOptions()} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#131e14', }, robotBackground: { position: 'absolute', top: 0, left: 0, width: '100%', height: 380, opacity: 0.28, }, maskContainer: { position: 'absolute', top: 260, left: 0, right: 0, height: 120, }, maskLine: { height: 20, width: '100%', }, optionRowLeft: { flexDirection: 'row', alignItems: 'center', }, safe: { flex: 1, justifyContent: 'space-between', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: 'rgba(255,255,255,0.08)', }, headerBack: { width: 38, height: 38, borderRadius: 19, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(255,255,255,0.04)', }, progressContainer: { flex: 1, marginHorizontal: 16, }, progressBarTrack: { height: 6, borderRadius: 3, backgroundColor: 'rgba(255,255,255,0.08)', overflow: 'hidden', }, progressBarFill: { height: 6, borderRadius: 3, backgroundColor: '#a5d56e', }, headerSkip: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 14, backgroundColor: 'rgba(255,255,255,0.04)', }, skipText: { color: '#dae6d6', fontSize: 13, fontWeight: '700', }, chatPanel: { flex: 1, paddingHorizontal: 16, }, chatListContent: { paddingVertical: 16, gap: 16, }, timeDivider: { alignSelf: 'center', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 999, paddingHorizontal: 12, paddingVertical: 4, marginBottom: 16, }, timeDividerText: { color: '#8d9382', fontSize: 11, fontWeight: '700', }, messageRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 10, marginVertical: 4, }, messageRowLeft: { alignSelf: 'flex-start', maxWidth: '85%', }, messageRowRight: { alignSelf: 'flex-end', flexDirection: 'row-reverse', maxWidth: '85%', }, chatAvatar: { width: 32, height: 32, borderRadius: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', }, bubble: { paddingHorizontal: 16, paddingVertical: 12, borderRadius: 18, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 1, }, bubbleLeft: { borderBottomLeftRadius: 3, }, bubbleRight: { borderBottomRightRadius: 3, }, bubbleText: { fontSize: 15, lineHeight: 21, fontWeight: '600', }, bubbleSub: { fontSize: 12, marginTop: 4, color: '#8d9382', }, typingContainer: { flexDirection: 'row', alignItems: 'center', gap: 4, width: 36, height: 18, justifyContent: 'center', }, typingDot: { width: 6, height: 6, borderRadius: 3, }, inputPanel: { flexShrink: 1, maxHeight: '58%', borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: 'rgba(255,255,255,0.08)', backgroundColor: '#131e14', }, inputPanelContent: { paddingTop: 12, paddingBottom: 24, paddingHorizontal: 16, }, optionsWrap: { gap: 10, }, stepTitle: { color: '#8d9382', fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 4, textAlign: 'center', }, optionsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, justifyContent: 'center', }, pillBtn: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 14, paddingVertical: 10, borderRadius: 999, borderWidth: 1.5, backgroundColor: '#101e11', }, pillEmoji: { fontSize: 14, }, pillLabel: { fontSize: 13.5, fontWeight: '700', }, optionsVertical: { gap: 8, }, optionRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 14, borderRadius: 14, borderWidth: 1, backgroundColor: '#101e11', }, rowText: { fontSize: 14.5, fontWeight: '700', }, bentoOption: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderRadius: 14, borderWidth: 1, backgroundColor: '#101e11', }, bentoCopy: { flex: 1, gap: 2, marginRight: 12, }, bentoTitle: { fontSize: 15, fontWeight: '700', }, bentoSub: { fontSize: 11.5, fontWeight: '600', lineHeight: 15, }, footerWrap: { paddingVertical: 8, }, continueCta: { minHeight: 56, paddingVertical: 16, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, continueCtaText: { fontSize: 16.5, fontWeight: '900', }, });