import React, { useEffect } from 'react'; import { Image, ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { router } from 'expo-router'; import { useApp } from '../context/AppContext'; import { useSafeAnalytics } from '../services/analytics'; import { getWelcomeHeadlineVariant, WelcomeHeadlineVariant } from '../services/experiments'; import { APP_STORE_RATING, getTestimonialForLanguage, translatedNote } from '../constants/socialProof'; import { Language } from '../types'; const getWelcomeCopy = (language: Language, variant: WelcomeHeadlineVariant) => { if (language === 'de') { return { // Variante A (symptom): steigt direkt in Emmas akuten Moment ein. // Variante B (contrast): grenzt gegen kostenlose Identifikations-Apps ab. headline: variant === 'symptom' ? 'Gelbe Blätter. Und jetzt?' : 'Andere Apps sagen dir, wie deine Pflanze heißt.', subline: variant === 'symptom' ? 'GreenLens nennt dir die wahrscheinlichsten Ursachen, was du selbst prüfen solltest und die eine sichere Sache, die du jetzt tun kannst.' : 'GreenLens sagt dir, was mit ihr los ist: wahrscheinliche Ursache, was du prüfen sollst, was du als Nächstes tust - und ein Check, ob es gewirkt hat.', cta: 'Meine Pflanze prüfen', login: 'Ich habe schon ein Konto', demoScan: 'Erst ausprobieren - ohne Konto', legal: 'Mit dem Fortfahren akzeptierst du unsere Datenschutzerklärung und AGB.', ratingValue: APP_STORE_RATING.value.toLocaleString('de-DE', { minimumFractionDigits: 1 }), ratingCount: `${APP_STORE_RATING.count} Bewertungen`, }; } if (language === 'es') { return { headline: variant === 'symptom' ? 'Hojas amarillas. ¿Y ahora?' : 'Otras apps te dicen cómo se llama tu planta.', subline: variant === 'symptom' ? 'GreenLens te da las causas más probables, qué revisar tú mismo y la única cosa segura que puedes hacer ahora.' : 'GreenLens te dice qué le pasa: causa probable, qué revisar, qué hacer después - y una comprobación de si funcionó.', cta: 'Revisar mi planta', login: 'Ya tengo cuenta', demoScan: 'Probar primero - sin cuenta', legal: 'Al continuar aceptas nuestra Política de privacidad y Términos.', ratingValue: APP_STORE_RATING.value.toLocaleString('es-ES', { minimumFractionDigits: 1 }), ratingCount: `${APP_STORE_RATING.count} valoraciones`, }; } return { headline: variant === 'symptom' ? 'Yellow leaves. Now what?' : 'Other apps tell you what your plant is called.', subline: variant === 'symptom' ? 'GreenLens gives you the most likely causes, what to check yourself, and the one safe thing to do right now.' : "GreenLens tells you what's wrong with it: likely cause, what to check, what to do next - and a follow-up on whether it worked.", cta: 'Check my plant', login: 'I already have an account', demoScan: 'Try it first - no account needed', legal: 'By continuing you agree to our Privacy Policy and Terms.', ratingValue: APP_STORE_RATING.value.toLocaleString('en-US', { minimumFractionDigits: 1 }), ratingCount: `${APP_STORE_RATING.count} ratings`, }; }; /** * Proof-Punkte statt Testimonial: greifen, solange keine echten Store-Zitate * hinterlegt sind. Beide Zeilen sind Produktaussagen, keine Behauptungen ueber * Ergebnisse - sie halten damit die Positionierung "Triage statt Diagnose". */ const getProofPoints = (language: Language): string[] => { if (language === 'de') { return [ 'Wahrscheinlichste Ursachen - mit Angabe, wie sicher wir sind', 'Konkrete Prüfschritte statt allgemeiner Pflegetipps', 'Nach 7 Tagen fragen wir nach, ob es gewirkt hat', ]; } if (language === 'es') { return [ 'Causas más probables - indicando qué tan seguros estamos', 'Pasos concretos de revisión, no consejos genéricos', 'A los 7 días preguntamos si funcionó', ]; } return [ 'Most likely causes - with how confident we actually are', 'Concrete things to check, not generic care tips', 'After 7 days we ask whether it worked', ]; }; export default function OnboardingScreen() { const { language } = useApp(); const { height, width } = useWindowDimensions(); const compact = height < 700 || width < 380; const posthog = useSafeAnalytics(); const insets = useSafeAreaInsets(); // Der Hero bekommt den Rest der Hoehe, faellt aber nie unter diese Grenze. // Vorher war er fest auf 52% - dadurch hat die laengere Headline-Variante // den dritten Button aus dem Bildschirm geschoben. const heroMinHeight = Math.round(height * (compact ? 0.34 : 0.40)); const [variant, setVariant] = React.useState(null); const copy = getWelcomeCopy(language, variant ?? 'symptom'); const testimonial = getTestimonialForLanguage(language); const proofPoints = getProofPoints(language); useEffect(() => { let active = true; void getWelcomeHeadlineVariant().then((assigned) => { if (!active) return; setVariant(assigned); posthog.capture('onboarding_welcome_viewed', { welcome_variant: assigned }); }); return () => { active = false; }; }, [posthog]); return ( GreenLens {/* Rating und Anzahl immer gemeinsam: 5,0 aus 6 Bewertungen ist ehrlich und ueberpruefbar - eine nackte Sternezahl waere es nicht. */} {copy.ratingValue} · {copy.ratingCount} {testimonial ? ( „{testimonial.quote}" {testimonial.author} {testimonial.isTranslated ? ` · ${translatedNote(language)}` : ''} {Array.from({ length: testimonial.stars }).map((_, i) => ( ))} ) : ( /* Kein belegtes Zitat vorhanden → Argument statt erfundenem Social Proof. */ {proofPoints.map((point) => ( {point} ))} )} {/* Erst rendern, wenn die Variante feststeht. Sonst blitzt fuer die Haelfte der Nutzer kurz die falsche Headline auf und wechselt dann - das sieht kaputt aus und verfaelscht den Test. */} {/* maxFontSizeMultiplier: groessere Systemschrift darf die Headline wachsen lassen, aber nicht die CTAs aus dem Screen schieben. */} {copy.headline} {copy.subline} { posthog.capture('onboarding_started', { welcome_variant: variant ?? 'unassigned' }); router.push('/onboarding/slides'); }} activeOpacity={0.86} > {copy.cta} router.push('/auth/login')} style={styles.loginLink}> {copy.login} { posthog.capture('onboarding_demo_scan_started', { welcome_variant: variant ?? 'unassigned', }); router.push('/scanner'); }} style={styles.demoLink} > {copy.demoScan} {copy.legal} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0a110b', }, scroll: { flex: 1, }, scrollContent: { // Fuellt mindestens den Bildschirm. Wird der Inhalt hoeher (lange // Headline-Variante, grosse Systemschrift, kleines Geraet), scrollt der // Screen - statt die unteren Buttons abzuschneiden. flexGrow: 1, }, heroBlock: { width: '100%', // Nimmt den Platz, den das Sheet uebrig laesst, faellt aber nie unter // heroMinHeight. Vorher fest 52% - das hat nur bei kurzer Copy gepasst. flexGrow: 1, flexShrink: 0, }, hero: { width: '100%', }, heroImageContent: { backgroundColor: '#0a110b', }, heroShadeTop: { position: 'absolute', left: 0, right: 0, top: 0, height: 120, backgroundColor: 'rgba(10,17,11,0.4)', }, heroSafe: { flex: 1, justifyContent: 'space-between', paddingHorizontal: 24, paddingBottom: 32, }, heroTopRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', width: '100%', }, brandRow: { flexDirection: 'row', alignItems: 'center', gap: 10, flexShrink: 1, }, logo: { width: 36, height: 36, borderRadius: 10, backgroundColor: '#fff', }, brandName: { flexShrink: 1, color: '#ffffff', fontSize: 24, fontWeight: '900', }, brandAccent: { color: '#8ba885', }, ratingPill: { flexShrink: 1, flexDirection: 'row', alignItems: 'center', gap: 6, marginLeft: 10, backgroundColor: 'rgba(0, 0, 0, 0.55)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.25)', borderRadius: 999, paddingHorizontal: 14, paddingVertical: 8, }, ratingText: { color: '#ffffff', fontSize: 12, fontWeight: '700', }, ratingCount: { flexShrink: 1, color: '#c9d3c2', fontSize: 11, fontWeight: '600', }, proofRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 8, marginBottom: 8, }, proofText: { flex: 1, color: '#e6efe2', fontSize: 13.5, lineHeight: 18, fontWeight: '600', }, testimonialCard: { backgroundColor: 'rgba(16, 26, 18, 0.75)', borderRadius: 16, padding: 16, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.08)', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.25, shadowRadius: 14, elevation: 4, marginBottom: 8, }, testimonialCardCompact: { padding: 12, }, testimonialText: { color: '#ffffff', fontSize: 14.5, lineHeight: 20, fontStyle: 'italic', marginBottom: 10, }, testimonialMeta: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', }, testimonialAuthor: { flex: 1, flexShrink: 1, marginRight: 8, color: '#a6d66f', fontSize: 13, fontWeight: '700', }, starsRow: { flexDirection: 'row', gap: 1, }, sheet: { flexShrink: 0, backgroundColor: '#0c160d', borderTopLeftRadius: 28, borderTopRightRadius: 28, marginTop: -20, borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.05)', }, sheetHandle: { alignSelf: 'center', width: 44, height: 5, borderRadius: 3, backgroundColor: 'rgba(255,255,255,0.2)', marginTop: 12, marginBottom: 8, }, sheetContent: { paddingHorizontal: 24, paddingTop: 12, paddingBottom: 12, }, headline: { color: '#ffffff', fontSize: 28, lineHeight: 34, fontWeight: '900', textAlign: 'center', marginBottom: 10, }, headlineCompact: { fontSize: 24, lineHeight: 29, }, subline: { color: '#dae6d6', fontSize: 16, lineHeight: 22, fontWeight: '500', textAlign: 'center', }, spacer: { height: 14, }, cta: { minHeight: 58, paddingVertical: 16, borderRadius: 16, backgroundColor: '#437824', alignItems: 'center', justifyContent: 'center', marginBottom: 4, }, ctaText: { color: '#f8f7ef', fontSize: 18, fontWeight: '800', }, loginLink: { alignItems: 'center', paddingVertical: 9, }, loginText: { color: '#a6d66f', fontSize: 15, fontWeight: '800', }, demoLink: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, minHeight: 50, paddingVertical: 13, borderRadius: 14, borderWidth: 1.5, borderColor: '#a6d66f', backgroundColor: 'rgba(166, 214, 111, 0.05)', marginTop: 6, marginBottom: 10, }, demoText: { color: '#a6d66f', fontSize: 15, fontWeight: '700', }, legal: { color: '#8d9382', fontSize: 11, lineHeight: 14, fontWeight: '500', textAlign: 'center', }, });