App copy
This commit is contained in:
@@ -15,24 +15,35 @@ const getSlidesCopy = (language: Language) => {
|
||||
return {
|
||||
slides: [
|
||||
{
|
||||
title: 'Scanne jede Pflanze',
|
||||
body: 'Richte die Kamera auf eine Pflanze und GreenLens erkennt sie in Sekunden.',
|
||||
// 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: 'Health Check & Pflegeplan',
|
||||
body: 'GreenLens erkennt Probleme früh und erstellt deinen Rettungsplan.',
|
||||
title: 'Ein konkreter Plan. Für diese Pflanze.',
|
||||
body: 'Sofortmaßnahme, 7-Tage-Plan, und danach die Frage: Hat es gewirkt?',
|
||||
},
|
||||
{
|
||||
title: 'Nie mehr Gießen vergessen',
|
||||
body: 'Smarte Erinnerungen und deine Pflanzen-Bibliothek halten alles im Blick.',
|
||||
// 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.',
|
||||
},
|
||||
],
|
||||
resultChip: 'Monstera · 98%',
|
||||
healthCheckLabel: 'Health Check',
|
||||
overwateringDetected: 'Überwässerung erkannt',
|
||||
rescuePlanReady: '7-Tage-Rettungsplan bereit',
|
||||
waterReminder: 'Monstera gießen — heute',
|
||||
fertilizeReminder: 'Basilikum düngen — in 3 Tagen',
|
||||
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',
|
||||
};
|
||||
}
|
||||
@@ -40,53 +51,70 @@ const getSlidesCopy = (language: Language) => {
|
||||
return {
|
||||
slides: [
|
||||
{
|
||||
title: 'Escanea cualquier planta',
|
||||
body: 'Apunta la cámara a una planta y GreenLens la identifica en segundos.',
|
||||
title: '¿Qué le pasa a mi planta?',
|
||||
body: 'Fotografía el síntoma. Recibes las causas más probables - y qué revisar.',
|
||||
},
|
||||
{
|
||||
title: 'Chequeo de salud y plan de cuidados',
|
||||
body: 'GreenLens detecta problemas a tiempo y crea tu plan de rescate.',
|
||||
title: 'Un plan concreto. Para esta planta.',
|
||||
body: 'Acción inmediata, plan de 7 días, y luego la pregunta: ¿funcionó?',
|
||||
},
|
||||
{
|
||||
title: 'No olvides regar nunca más',
|
||||
body: 'Recordatorios inteligentes y tu biblioteca de plantas lo mantienen todo al día.',
|
||||
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.',
|
||||
},
|
||||
],
|
||||
resultChip: 'Monstera · 98%',
|
||||
healthCheckLabel: 'Chequeo de salud',
|
||||
overwateringDetected: 'Exceso de riego detectado',
|
||||
rescuePlanReady: 'Plan de rescate de 7 días listo',
|
||||
waterReminder: 'Regar Monstera — hoy',
|
||||
fertilizeReminder: 'Abonar albahaca — en 3 días',
|
||||
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: 'Scan Any Plant',
|
||||
body: 'Point your camera at a plant and GreenLens identifies it in seconds.',
|
||||
title: "What's wrong with my plant?",
|
||||
body: 'Photograph the symptom. You get the most likely causes - and what to check.',
|
||||
},
|
||||
{
|
||||
title: 'Health Check & Care Plan',
|
||||
body: 'GreenLens spots problems early and builds a rescue plan for you.',
|
||||
title: 'A concrete plan. For this plant.',
|
||||
body: 'Immediate action, a 7-day plan, and then the question: did it work?',
|
||||
},
|
||||
{
|
||||
title: 'Never Forget Watering',
|
||||
body: 'Smart reminders and your personal plant library keep everything on track.',
|
||||
title: "We also tell you when we're not sure.",
|
||||
body: 'A photo cannot show roots or soil moisture. So: probabilities, not invented certainty.',
|
||||
},
|
||||
],
|
||||
resultChip: 'Monstera · 98%',
|
||||
healthCheckLabel: 'Health Check',
|
||||
overwateringDetected: 'Overwatering detected',
|
||||
rescuePlanReady: '7-day rescue plan ready',
|
||||
waterReminder: 'Water Monstera — today',
|
||||
fertilizeReminder: 'Fertilize Basil — in 3 days',
|
||||
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',
|
||||
};
|
||||
};
|
||||
|
||||
function ScanFrameOverlay({ resultChip, colors }: { resultChip: string; colors: ColorsType }) {
|
||||
type SlidesCopy = ReturnType<typeof getSlidesCopy>;
|
||||
|
||||
/** Slide 1: Ursachen-Ranking statt Prozent-Behauptung. */
|
||||
function CauseRankingOverlay({ copy, colors }: { copy: SlidesCopy; colors: ColorsType }) {
|
||||
return (
|
||||
<>
|
||||
<View style={styles.scanFrameWrap} pointerEvents="none">
|
||||
@@ -95,77 +123,75 @@ function ScanFrameOverlay({ resultChip, colors }: { resultChip: string; colors:
|
||||
<View style={[styles.cornerBL, { borderColor: colors.primary }]} />
|
||||
<View style={[styles.cornerBR, { borderColor: colors.primary }]} />
|
||||
</View>
|
||||
<View style={styles.resultChip}>
|
||||
<Ionicons name="leaf" size={15} color={colors.primary} />
|
||||
<Text style={styles.resultChipText}>{resultChip}</Text>
|
||||
<View style={styles.healthCard}>
|
||||
<Text style={styles.overlayEyebrow}>{copy.causeTitle}</Text>
|
||||
<View style={[styles.healthRow, styles.healthRowWarning]}>
|
||||
<Ionicons name="alert-circle" size={15} color="#C62828" />
|
||||
<Text style={styles.healthRowWarningText}>
|
||||
{copy.causePrimary} · {copy.causePrimaryLevel}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[styles.healthRow, styles.healthRowNeutral]}>
|
||||
<Ionicons name="help-circle-outline" size={15} color="#6b7280" />
|
||||
<Text style={styles.healthRowNeutralText}>
|
||||
{copy.causeSecondary} · {copy.causeSecondaryLevel}
|
||||
</Text>
|
||||
</View>
|
||||
{/* In derselben Karte statt als zweites absolutes Element: zwei frei
|
||||
positionierte Overlays ueberlappen sich, sobald Uebersetzungen die
|
||||
Karte wachsen lassen. */}
|
||||
<View style={styles.checkInline}>
|
||||
<Text style={styles.checkChipLabel}>{copy.checkLabel}</Text>
|
||||
<Text style={styles.checkChipText}>{copy.checkItems}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function HealthCardOverlay({
|
||||
label,
|
||||
overwateringDetected,
|
||||
rescuePlanReady,
|
||||
}: {
|
||||
label: string;
|
||||
overwateringDetected: string;
|
||||
rescuePlanReady: string;
|
||||
}) {
|
||||
/** Slide 2: der Plan inklusive Follow-up - das ist der Kaufgrund. */
|
||||
function PlanOverlay({ copy }: { copy: SlidesCopy }) {
|
||||
return (
|
||||
<View style={styles.healthCard}>
|
||||
<View style={styles.healthCardHeader}>
|
||||
<View style={styles.healthCardIcon}>
|
||||
<Ionicons name="medkit" size={16} color="#C62828" />
|
||||
<Ionicons name="calendar" size={16} color="#2e7d32" />
|
||||
</View>
|
||||
<Text style={styles.healthCardTitle}>{label}</Text>
|
||||
</View>
|
||||
<View style={[styles.healthRow, styles.healthRowWarning]}>
|
||||
<Ionicons name="warning" size={15} color="#C62828" />
|
||||
<Text style={styles.healthRowWarningText}>{overwateringDetected}</Text>
|
||||
<Text style={styles.healthCardTitle}>{copy.planLabel}</Text>
|
||||
</View>
|
||||
<View style={[styles.healthRow, styles.healthRowSuccess]}>
|
||||
<Ionicons name="checkmark-circle" size={15} color="#2e7d32" />
|
||||
<Text style={styles.healthRowSuccessText}>{rescuePlanReady}</Text>
|
||||
<Ionicons name="water-outline" size={15} color="#2e7d32" />
|
||||
<Text style={styles.healthRowSuccessText}>{copy.planNow}</Text>
|
||||
</View>
|
||||
<View style={[styles.healthRow, styles.healthRowNeutral]}>
|
||||
<Ionicons name="chatbubble-ellipses-outline" size={15} color="#6b7280" />
|
||||
<Text style={styles.healthRowNeutralText}>{copy.planFollowUp}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function ReminderChipsOverlay({ waterReminder, fertilizeReminder }: { waterReminder: string; fertilizeReminder: string }) {
|
||||
const [waterLabel, waterMeta] = splitReminder(waterReminder);
|
||||
const [fertilizeLabel, fertilizeMeta] = splitReminder(fertilizeReminder);
|
||||
/** Slide 3: die Grenzen des Verfahrens, sichtbar gemacht. */
|
||||
function UncertaintyOverlay({ copy }: { copy: SlidesCopy }) {
|
||||
return (
|
||||
<>
|
||||
<View style={[styles.reminderChip, styles.reminderChipTop]}>
|
||||
<View style={[styles.reminderIcon, { backgroundColor: '#dff2e6' }]}>
|
||||
<Ionicons name="water" size={16} color="#2e7d32" />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.reminderLabel}>{waterLabel}</Text>
|
||||
<Text style={styles.reminderMeta}>{waterMeta}</Text>
|
||||
<View style={styles.healthCard}>
|
||||
<View style={styles.healthCardHeader}>
|
||||
<View style={styles.healthCardIcon}>
|
||||
<Ionicons name="eye-off-outline" size={16} color="#6b7280" />
|
||||
</View>
|
||||
<Text style={styles.healthCardTitle}>{copy.uncertainTitle}</Text>
|
||||
</View>
|
||||
<View style={[styles.reminderChip, styles.reminderChipBottom]}>
|
||||
<View style={[styles.reminderIcon, { backgroundColor: '#e3f3c8' }]}>
|
||||
<Ionicons name="leaf" size={16} color="#558b2f" />
|
||||
{copy.uncertainItems.map((item) => (
|
||||
<View key={item} style={[styles.healthRow, styles.healthRowNeutral]}>
|
||||
<Ionicons name="close-circle-outline" size={15} color="#6b7280" />
|
||||
<Text style={styles.healthRowNeutralText}>{item}</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.reminderLabel}>{fertilizeLabel}</Text>
|
||||
<Text style={styles.reminderMeta}>{fertilizeMeta}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
))}
|
||||
<Text style={styles.overlayNote}>{copy.uncertainNote}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// Splits "Water Monstera — today" into ["Water Monstera", "today"] for a two-line chip.
|
||||
function splitReminder(text: string): [string, string] {
|
||||
const parts = text.split('—').map((part) => part.trim());
|
||||
if (parts.length === 2) return [parts[0], parts[1]];
|
||||
return [text, ''];
|
||||
}
|
||||
|
||||
export default function OnboardingSlidesScreen() {
|
||||
const router = useRouter();
|
||||
const { height } = useWindowDimensions();
|
||||
@@ -178,7 +204,7 @@ export default function OnboardingSlidesScreen() {
|
||||
const slide = copy.slides[page];
|
||||
|
||||
useEffect(() => {
|
||||
posthog.capture('onboarding_slide_viewed', { index: page });
|
||||
posthog.capture('onboarding_slide_viewed', { index: page, slide: ['triage', 'plan', 'uncertainty'][page] });
|
||||
}, [page, posthog]);
|
||||
|
||||
const next = () => {
|
||||
@@ -203,9 +229,9 @@ export default function OnboardingSlidesScreen() {
|
||||
<Image
|
||||
source={
|
||||
page === 0
|
||||
? require('../../assets/paywall_scan_background.png')
|
||||
? require('../../assets/onboarding_health_scan_mockup_vertical.png')
|
||||
: page === 1
|
||||
? require('../../assets/onboarding_health_scan_mockup_vertical.png')
|
||||
? require('../../assets/paywall_scan_background.png')
|
||||
: require('../../assets/welcome_botanical_header.png')
|
||||
}
|
||||
style={styles.image}
|
||||
@@ -216,17 +242,9 @@ export default function OnboardingSlidesScreen() {
|
||||
<Ionicons name="arrow-back" size={20} color="#1f2520" />
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
{page === 0 && <ScanFrameOverlay resultChip={copy.resultChip} colors={colors} />}
|
||||
{page === 1 && (
|
||||
<HealthCardOverlay
|
||||
label={copy.healthCheckLabel}
|
||||
overwateringDetected={copy.overwateringDetected}
|
||||
rescuePlanReady={copy.rescuePlanReady}
|
||||
/>
|
||||
)}
|
||||
{page === 2 && (
|
||||
<ReminderChipsOverlay waterReminder={copy.waterReminder} fertilizeReminder={copy.fertilizeReminder} />
|
||||
)}
|
||||
{page === 0 && <CauseRankingOverlay copy={copy} colors={colors} />}
|
||||
{page === 1 && <PlanOverlay copy={copy} />}
|
||||
{page === 2 && <UncertaintyOverlay copy={copy} />}
|
||||
</View>
|
||||
<View style={[styles.sheet, { backgroundColor: colors.surface }]}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>{slide.title}</Text>
|
||||
@@ -334,24 +352,6 @@ const styles = StyleSheet.create({
|
||||
borderRightWidth: 4,
|
||||
borderBottomRightRadius: 8,
|
||||
},
|
||||
resultChip: {
|
||||
position: 'absolute',
|
||||
bottom: '10%',
|
||||
left: 20,
|
||||
right: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
backgroundColor: 'rgba(255,255,255,0.94)',
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
resultChipText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '700',
|
||||
color: '#1f2520',
|
||||
},
|
||||
// Health card overlay (slide 2)
|
||||
healthCard: {
|
||||
position: 'absolute',
|
||||
@@ -406,43 +406,54 @@ const styles = StyleSheet.create({
|
||||
fontWeight: '700',
|
||||
color: '#2e7d32',
|
||||
},
|
||||
// Reminder chips overlay (slide 3)
|
||||
reminderChip: {
|
||||
position: 'absolute',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
backgroundColor: 'rgba(255,255,255,0.96)',
|
||||
borderRadius: 999,
|
||||
paddingVertical: 8,
|
||||
paddingRight: 18,
|
||||
paddingLeft: 8,
|
||||
// Neutrale Zeile: bewusst grau, damit "weniger wahrscheinlich" und
|
||||
// "was wir nicht sehen" optisch NICHT wie eine Zusage aussehen.
|
||||
healthRowNeutral: {
|
||||
backgroundColor: '#f1f2f0',
|
||||
},
|
||||
reminderChipTop: {
|
||||
top: '24%',
|
||||
right: 20,
|
||||
healthRowNeutralText: {
|
||||
fontSize: 13.5,
|
||||
fontWeight: '600',
|
||||
color: '#5b6158',
|
||||
},
|
||||
reminderChipBottom: {
|
||||
top: '42%',
|
||||
left: 20,
|
||||
overlayEyebrow: {
|
||||
fontSize: 10.5,
|
||||
fontWeight: '900',
|
||||
letterSpacing: 0.9,
|
||||
textTransform: 'uppercase',
|
||||
color: '#6b7280',
|
||||
marginBottom: 8,
|
||||
},
|
||||
reminderIcon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overlayNote: {
|
||||
fontSize: 11.5,
|
||||
lineHeight: 15,
|
||||
fontWeight: '600',
|
||||
color: '#6b7280',
|
||||
marginTop: 8,
|
||||
},
|
||||
reminderLabel: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
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',
|
||||
},
|
||||
reminderMeta: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#5a8a3d',
|
||||
},
|
||||
// Reminder chips overlay (slide 3)
|
||||
// Bottom sheet
|
||||
sheet: {
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user