import React, { useEffect, useRef, useState } from 'react'; import { Image, ScrollView, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useSafeAnalytics } from '../../services/analytics'; import { useColors } from '../../constants/Colors'; import { useApp } from '../../context/AppContext'; import { Language } from '../../types'; type ColorsType = ReturnType; const getSlidesCopy = (language: Language) => { if (language === 'de') { return { slides: [ { // Slide 1 = Triage. Bewusst NICHT die Identifikation: die kann Google // Lens gratis. Der Pitch startet erst nach der Identifikation. title: 'Was ist mit meiner Pflanze los?', body: 'Fotografiere das Symptom. Du bekommst die wahrscheinlichsten Ursachen - und was du prüfen sollst.', }, { title: 'Ein konkreter Plan. Für diese Pflanze.', body: 'Sofortmaßnahme, 7-Tage-Plan, und danach die Frage: Hat es gewirkt?', }, { // Der Screen, den keine Konkurrenz-App hat. Beantwortet den Einwand // "Eine andere App hat mir selbstbewusst etwas Falsches gesagt." title: 'Wir sagen dir auch, wenn wir unsicher sind.', body: 'Ein Foto zeigt keine Wurzeln und keine Erdfeuchte. Deshalb Wahrscheinlichkeiten statt erfundener Gewissheit.', }, ], causeTitle: 'Wahrscheinlichste Ursache', causePrimary: 'Überwässerung', causePrimaryLevel: '64 %', causeSecondary: 'Nährstoffmangel', causeSecondaryLevel: '41 %', checkLabel: 'Prüfe zuerst', checkItems: 'Erde 3 cm tief · Abzugslöcher · Blattunterseiten', planLabel: '7-Tage-Plan', planNow: 'Heute: nicht gießen', planFollowUp: 'Tag 7: Wir fragen nach, ob es besser wird', uncertainTitle: 'Was ein Foto nicht zeigt', uncertainItems: ['Wurzeln', 'Erdfeuchte', 'Vorgeschichte'], uncertainNote: 'Deshalb: Wahrscheinlichkeiten statt Diagnose.', continueLabel: 'Weiter', }; } if (language === 'es') { return { slides: [ { title: '¿Qué le pasa a mi planta?', body: 'Fotografía el síntoma. Recibes las causas más probables - y qué revisar.', }, { title: 'Un plan concreto. Para esta planta.', body: 'Acción inmediata, plan de 7 días, y luego la pregunta: ¿funcionó?', }, { title: 'También te decimos cuándo no estamos seguros.', body: 'Una foto no muestra las raíces ni la humedad. Por eso probabilidades, no certezas inventadas.', }, ], causeTitle: 'Causa más probable', causePrimary: 'Exceso de riego', causePrimaryLevel: '64 %', causeSecondary: 'Falta de nutrientes', causeSecondaryLevel: '41 %', checkLabel: 'Revisa primero', checkItems: 'Sustrato a 3 cm · Drenaje · Reverso de las hojas', planLabel: 'Plan de 7 días', planNow: 'Hoy: no regar', planFollowUp: 'Día 7: preguntamos si va mejor', uncertainTitle: 'Lo que una foto no muestra', uncertainItems: ['Raíces', 'Humedad', 'Historial'], uncertainNote: 'Por eso: probabilidades, no diagnóstico.', continueLabel: 'Continuar', }; } return { slides: [ { title: "What's wrong with my plant?", body: 'Photograph the symptom. You get the most likely causes - and what to check.', }, { title: 'A concrete plan. For this plant.', body: 'Immediate action, a 7-day plan, and then the question: did it work?', }, { title: "We also tell you when we're not sure.", body: 'A photo cannot show roots or soil moisture. So: probabilities, not invented certainty.', }, ], causeTitle: 'Most likely cause', causePrimary: 'Overwatering', causePrimaryLevel: '64%', causeSecondary: 'Nutrient deficiency', causeSecondaryLevel: '41%', checkLabel: 'Check first', checkItems: 'Soil 3 cm deep · Drainage holes · Leaf undersides', planLabel: '7-day plan', planNow: 'Today: do not water', planFollowUp: 'Day 7: we ask whether it improved', uncertainTitle: 'What a photo cannot show', uncertainItems: ['Roots', 'Soil moisture', 'History'], uncertainNote: 'So: probabilities, not diagnosis.', continueLabel: 'Continue', }; }; type SlidesCopy = ReturnType; const OVERLAY_TEXT_MAX_MULTIPLIER = 1.2; /** Slide 1: Ursachen-Ranking statt Prozent-Behauptung. */ function CauseRankingOverlay({ copy, colors }: { copy: SlidesCopy; colors: ColorsType }) { return ( <> {copy.causeTitle} {copy.causePrimary} · {copy.causePrimaryLevel} {copy.causeSecondary} · {copy.causeSecondaryLevel} {/* In derselben Karte statt als zweites absolutes Element: zwei frei positionierte Overlays ueberlappen sich, sobald Uebersetzungen die Karte wachsen lassen. */} {copy.checkLabel} {copy.checkItems} ); } /** Slide 2: der Plan inklusive Follow-up - das ist der Kaufgrund. */ function PlanOverlay({ copy }: { copy: SlidesCopy }) { return ( {copy.planLabel} {copy.planNow} {copy.planFollowUp} ); } /** Slide 3: die Grenzen des Verfahrens, sichtbar gemacht. */ function UncertaintyOverlay({ copy }: { copy: SlidesCopy }) { return ( {copy.uncertainTitle} {copy.uncertainItems.map((item) => ( {item} ))} {copy.uncertainNote} ); } export default function OnboardingSlidesScreen() { const router = useRouter(); const scrollRef = useRef(null); const { height } = useWindowDimensions(); const insets = useSafeAreaInsets(); const compact = height < 700; // Das Bild bekommt den Platz, den das Sheet uebrig laesst - aber nie weniger // als diese Untergrenze. Feste Prozenthoehen haben bei groesserer // System-Schriftgroesse den CTA aus dem Bildschirm geschoben. const imageMinHeight = Math.round(height * (compact ? 0.46 : 0.42)); const { language, isDarkMode, colorPalette } = useApp(); const colors = useColors(isDarkMode, colorPalette); const posthog = useSafeAnalytics(); const [page, setPage] = useState(0); const copy = getSlidesCopy(language); const slide = copy.slides[page]; useEffect(() => { scrollRef.current?.scrollTo({ y: 0, animated: false }); posthog.capture('onboarding_slide_viewed', { index: page, slide: ['triage', 'plan', 'uncertainty'][page] }); }, [page, posthog]); const next = () => { if (page < copy.slides.length - 1) { setPage(page + 1); } else { router.replace('/onboarding/source'); } }; const back = () => { if (page > 0) { setPage(page - 1); } else { router.back(); } }; return ( {page === 0 && } {page === 1 && } {page === 2 && } {/* maxFontSizeMultiplier: die Systemschriftgroesse darf die Headline vergroessern, aber nicht so weit, dass sie das halbe Sheet frisst. */} {slide.title} {slide.body} {copy.slides.map((_, index) => ( ))} {copy.continueLabel} ); } const styles = StyleSheet.create({ container: { flex: 1, }, scrollContent: { // Fuellt mindestens den Bildschirm; wird der Inhalt bei sehr grosser // Schrift hoeher, scrollt der Screen statt den CTA abzuschneiden. flexGrow: 1, }, imageArea: { flexGrow: 1, flexShrink: 0, position: 'relative', overflow: 'hidden', }, image: { ...StyleSheet.absoluteFillObject, }, imageSafeArea: { position: 'absolute', top: 0, left: 0, right: 0, zIndex: 2, }, backBtn: { marginLeft: 16, marginTop: 8, width: 38, height: 38, borderRadius: 19, backgroundColor: 'rgba(255,255,255,0.85)', alignItems: 'center', justifyContent: 'center', }, // Scan frame overlay (slide 1) scanFrameWrap: { position: 'absolute', top: '22%', left: '20%', right: '20%', bottom: '26%', }, cornerTL: { position: 'absolute', top: 0, left: 0, width: 30, height: 30, borderTopWidth: 4, borderLeftWidth: 4, borderTopLeftRadius: 8, }, cornerTR: { position: 'absolute', top: 0, right: 0, width: 30, height: 30, borderTopWidth: 4, borderRightWidth: 4, borderTopRightRadius: 8, }, cornerBL: { position: 'absolute', bottom: 0, left: 0, width: 30, height: 30, borderBottomWidth: 4, borderLeftWidth: 4, borderBottomLeftRadius: 8, }, cornerBR: { position: 'absolute', bottom: 0, right: 0, width: 30, height: 30, borderBottomWidth: 4, borderRightWidth: 4, borderBottomRightRadius: 8, }, // Health card overlay (slide 2) healthCard: { position: 'absolute', bottom: 34, left: 16, right: 16, zIndex: 1, backgroundColor: 'rgba(255,255,255,0.96)', borderRadius: 18, padding: 14, gap: 8, }, healthCardHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 2, }, healthCardIcon: { width: 28, height: 28, borderRadius: 14, backgroundColor: '#fdeaea', alignItems: 'center', justifyContent: 'center', }, healthCardTitle: { fontSize: 16, fontWeight: '800', color: '#1f2520', }, healthRow: { flexDirection: 'row', alignItems: 'center', gap: 8, borderRadius: 10, paddingHorizontal: 10, paddingVertical: 8, }, healthRowWarning: { backgroundColor: '#fdeaea', }, healthRowWarningText: { fontSize: 13.5, fontWeight: '700', color: '#C62828', }, healthRowSuccess: { backgroundColor: '#e8f3e3', }, healthRowSuccessText: { fontSize: 13.5, fontWeight: '700', color: '#2e7d32', }, // Neutrale Zeile: bewusst grau, damit "weniger wahrscheinlich" und // "was wir nicht sehen" optisch NICHT wie eine Zusage aussehen. healthRowNeutral: { backgroundColor: '#f1f2f0', }, healthRowNeutralText: { fontSize: 13.5, fontWeight: '600', color: '#5b6158', }, overlayEyebrow: { fontSize: 10.5, fontWeight: '900', letterSpacing: 0.9, textTransform: 'uppercase', color: '#6b7280', marginBottom: 8, }, overlayNote: { fontSize: 11.5, lineHeight: 15, fontWeight: '600', color: '#6b7280', marginTop: 8, }, checkInline: { borderTopWidth: 1, borderTopColor: 'rgba(0,0,0,0.08)', paddingTop: 9, marginTop: 2, }, // Ein Stack statt zwei absoluter Elemente: die Bloecke koennen sich nicht // mehr ueberlappen, egal wie lang der uebersetzte Text wird. checkChipLabel: { fontSize: 10, fontWeight: '900', letterSpacing: 0.8, textTransform: 'uppercase', color: '#6b7280', marginBottom: 3, }, checkChipText: { fontSize: 12.5, lineHeight: 17, fontWeight: '700', color: '#1f2520', }, // Reminder chips overlay (slide 3) // Bottom sheet sheet: { // Hoehe folgt dem Inhalt statt einem festen Prozentwert - sonst haengt es // von Textlaenge und Systemschriftgroesse ab, ob der CTA noch passt. flexShrink: 0, borderTopLeftRadius: 28, borderTopRightRadius: 28, marginTop: -24, paddingHorizontal: 24, paddingTop: 32, alignItems: 'center', }, title: { fontSize: 30, fontWeight: '900', textAlign: 'center', marginBottom: 10, }, body: { fontSize: 15.5, lineHeight: 22, textAlign: 'center', maxWidth: 320, marginBottom: 20, }, dots: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 22, }, dot: { width: 8, height: 8, borderRadius: 4, }, dotActive: { width: 26, height: 8, borderRadius: 4, }, cta: { alignSelf: 'stretch', minHeight: 58, paddingVertical: 16, borderRadius: 14, alignItems: 'center', justifyContent: 'center', }, ctaText: { fontSize: 17, fontWeight: '800', }, });