Files
metabloom/App.tsx
T
AI Company 41fadf80af
MetaBloom CI / app (push) Canceled after 0s
MetaBloom CI / api (push) Canceled after 0s
feat: deliver accessible UX and icon system
2026-07-21 19:56:05 +00:00

71 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { LinearGradient } from 'expo-linear-gradient';
import { StatusBar } from 'expo-status-bar';
import React, { useEffect, useMemo, useState } from 'react';
import { Alert, Pressable, SafeAreaView, ScrollView, Share, StyleSheet, Switch, Text, TextInput, View, useWindowDimensions } from 'react-native';
import { Activity, Apple, Bell, Camera, Check, ChevronRight, CircleUserRound, Download, Droplets, Footprints, HeartPulse, Home, Lightbulb, Minus, Moon, Plus, Scale, ShieldCheck, Sparkles, Sprout, TrendingUp, Utensils } from 'lucide-react-native';
type LucideIcon = any;
type Tab = 'today' | 'journal' | 'progress' | 'coach' | 'profile';
type Entry = { date: string; iso?: string; weight: number; water: number; steps: number; glucose?: number; sleep?: number; meals?: number; mood?: string };
type ProfileData = { name:string; goal:string; journey:string; glucoseUnit:'mg/dL'|'mmol/L'; reminders:boolean; complete:boolean };
const C = { ink:'#15211c', muted:'#637169', cream:'#f6f7f2', white:'#fff', green:'#1d6b50', mint:'#dcefe5', coral:'#ef835f', yellow:'#f4d58d', line:'#e5e9e3' };
const seed: Entry[] = [
{date:'Lun',weight:91.4,water:1500,steps:6100,glucose:112},{date:'Mar',weight:91.2,water:1800,steps:7400,glucose:108},
{date:'Mer',weight:91.1,water:1600,steps:6900,glucose:110},{date:'Jeu',weight:90.9,water:2000,steps:8200,glucose:105},
{date:'Ven',weight:90.8,water:2100,steps:8600,glucose:104},{date:'Sam',weight:90.7,water:1750,steps:7200,glucose:107},{date:'Auj.',weight:90.6,water:1250,steps:6240,glucose:103},
];
function Card({children, style}: any){return <View style={[s.card,style]}>{children}</View>}
function Pill({children, tone='green'}:any){return <View style={[s.pill,{backgroundColor:tone==='coral'?'#fff0eb':C.mint}]}><Text style={[s.pillText,{color:tone==='coral'?'#9b432a':C.green}]}>{children}</Text></View>}
function IconTile({icon:Icon,color=C.green,bg=C.mint,size=22}:any){return <View style={[s.iconTile,{backgroundColor:bg}]}><Icon size={size} color={color} strokeWidth={2.2}/></View>}
function Metric({icon,label,value,sub,onPress}:any){const content=<><IconTile icon={icon}/><Text style={s.metricLabel}>{label}</Text><Text style={s.metricValue}>{value}</Text><Text style={s.metricSub}>{sub}</Text></>;return onPress?<Pressable accessibilityRole="button" accessibilityLabel={`${label}, ${value}. Ajouter`} onPress={onPress} style={({pressed})=>[s.metric,pressed&&s.pressed]}>{content}</Pressable>:<View accessibilityLabel={`${label}, ${value}, ${sub}`} style={s.metric}>{content}</View>}
function ProgressBar({value,color=C.green}:any){return <View style={s.track}><View style={[s.fill,{width:`${Math.min(value,100)}%`,backgroundColor:color}]}/></View>}
function Nav({tab,setTab}:any){const items:any[]=[['today',Home,'Aujourdhui'],['journal',Plus,'Journal'],['progress',TrendingUp,'Progrès'],['coach',Sparkles,'Coach'],['profile',CircleUserRound,'Profil']];return <View accessibilityRole="tablist" style={s.nav}>{items.map(([id,Icon,label])=>{const active=tab===id;return <Pressable accessibilityLabel={label} accessibilityRole="tab" accessibilityState={{selected:active}} key={id} onPress={()=>setTab(id)} style={({pressed})=>[s.navItem,active&&s.navItemActive,pressed&&s.pressed]}><Icon size={22} color={active?C.green:'#829087'} strokeWidth={active?2.6:2}/><Text style={[s.navLabel,active&&s.active]}>{label}</Text></Pressable>})}</View>}
export default function App(){
const [tab,setTab]=useState<Tab>('today'); const [entries,setEntries]=useState(seed); const [water,setWater]=useState(1250); const [steps,setSteps]=useState(6240);
const [weight,setWeight]=useState('90.6'); const [glucose,setGlucose]=useState('103'); const [sleep,setSleep]=useState('7.2'); const [meals,setMeals]=useState(2); const [mood,setMood]=useState('🙂'); const [saved,setSaved]=useState(false); const [loaded,setLoaded]=useState(false); const [profile,setProfile]=useState<ProfileData>({name:'',goal:'Retrouver mon équilibre',journey:'Prévention',glucoseUnit:'mg/dL',reminders:false,complete:false}); const {width}=useWindowDimensions();
useEffect(()=>{AsyncStorage.getItem('metabloom-v2').then(v=>{if(v){const d=JSON.parse(v);setEntries(Array.isArray(d.entries)&&d.entries.length?d.entries:seed);setWater(typeof d.water==='number'?d.water:1250);setSteps(typeof d.steps==='number'?d.steps:6240);setProfile(d.profile||profile)}}).catch(()=>{}).finally(()=>setLoaded(true))},[]);
useEffect(()=>{if(loaded)AsyncStorage.setItem('metabloom-v2',JSON.stringify({entries,water,steps,profile})).catch(()=>{})},[entries,water,steps,profile,loaded]);
const score=Math.round((Math.min(water/2000,1)+Math.min(steps/8000,1)+.82+.75)*25);
const save=()=>{const w=parseFloat(weight.replace(',','.')); const g=parseFloat(glucose.replace(',','.')); const sl=parseFloat(sleep.replace(',','.')); if(!Number.isFinite(w)||w<30||w>300){Alert.alert('Valeur à vérifier','Saisis un poids compris entre 30 et 300 kg.');return} const glucoseBounds=profile.glucoseUnit==='mmol/L'?[1.1,33.3]:[20,600]; if(glucose.trim()&&(!Number.isFinite(g)||g<glucoseBounds[0]||g>glucoseBounds[1])){Alert.alert('Valeur à vérifier','La glycémie saisie semble hors de la plage de saisie autorisée. Vérifie lunité et la valeur.');return} if(!Number.isFinite(sl)||sl<0||sl>24){Alert.alert('Valeur à vérifier','Le sommeil doit être compris entre 0 et 24 heures.');return} const now=new Date();setEntries(x=>[...x.slice(-29),{date:'Auj.',iso:now.toISOString(),weight:w,water,steps,glucose:Number.isFinite(g)?g:undefined,sleep:sl,meals,mood}]);setSaved(true);setTimeout(()=>setSaved(false),2200)};
const reset=()=>{setEntries(seed);setWater(1250);setSteps(6240);setProfile({...profile,complete:false});AsyncStorage.removeItem('metabloom-v2')};
const content=useMemo(()=>tab==='today'?<Today name={profile.name} score={score} water={water} setWater={setWater} steps={steps} setSteps={setSteps} go={setTab}/>:tab==='journal'?<Journal weight={weight} setWeight={setWeight} glucose={glucose} setGlucose={setGlucose} glucoseUnit={profile.glucoseUnit} water={water} setWater={setWater} steps={steps} setSteps={setSteps} sleep={sleep} setSleep={setSleep} meals={meals} setMeals={setMeals} mood={mood} setMood={setMood} save={save} saved={saved}/>:tab==='progress'?<Progress entries={entries}/>:tab==='coach'?<Coach journey={profile.journey}/>:<Profile profile={profile} setProfile={setProfile} entries={entries} reset={reset}/>,[tab,score,water,steps,weight,glucose,sleep,meals,mood,saved,entries,profile]);
if(!loaded)return <SafeAreaView style={s.safe}/>;
if(!profile.complete)return <Onboarding profile={profile} setProfile={setProfile}/>;
return <SafeAreaView style={s.safe}><StatusBar style="dark"/><View style={[s.shell,{maxWidth:width>900?1120:680}]}>{content}<Nav tab={tab} setTab={setTab}/></View></SafeAreaView>
}
function Onboarding({profile,setProfile}:any){const [step,setStep]=useState(0);const journeys:[[string,LucideIcon]]|any=[['Prévention',Sprout],['Perte de poids',Scale],['Diabète type 2',HeartPulse],['Traitement GLP-1',Sparkles]];return <SafeAreaView style={s.safe}><StatusBar style="dark"/><ScrollView contentContainerStyle={s.onboard}><View style={s.logo}><Sprout size={28} color={C.white}/></View><Text style={s.eyebrow}>BIENVENUE SUR METABLOOM</Text><Text style={s.onboardTitle}>{step===0?'Prenons soin de toi, simplement.':'Quel parcours te ressemble ?'}</Text><Text style={s.lead}>{step===0?'Quelques informations suffisent pour personnaliser ton expérience. Tu gardes le contrôle de tes données.':'Tu pourras modifier ce choix à tout moment. MetaBloom ne pose aucun diagnostic.'}</Text>{step===0?<><Text style={s.inputLabel}>Comment souhaites-tu être appelé·e ?</Text><View style={s.inputRow}><TextInput accessibilityLabel="Prénom ou pseudonyme" placeholder="Prénom ou pseudonyme" value={profile.name} onChangeText={(name)=>setProfile({...profile,name})} style={s.input}/></View>{!profile.name.trim()&&<Text style={s.helper}>Ce nom permettra de personnaliser ton accueil.</Text>}<Text style={s.inputLabel}>Ton objectif principal</Text><View style={s.inputRow}><TextInput accessibilityLabel="Objectif principal" value={profile.goal} onChangeText={(goal)=>setProfile({...profile,goal})} style={s.input}/></View></>:<View style={{gap:10}}>{journeys.map(([j,Icon]:[string,LucideIcon])=><Pressable key={j} accessibilityRole="radio" accessibilityState={{selected:profile.journey===j}} onPress={()=>setProfile({...profile,journey:j})} style={({pressed})=>[s.choice,profile.journey===j&&s.choiceActive,pressed&&s.pressed]}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.tipTitle}>{j}</Text><Text style={s.body}>Conseils éducatifs et habitudes adaptés</Text></View>{profile.journey===j?<Check size={22} color={C.green}/>:<View style={{width:22}}/>}</Pressable>)}</View>}<Card style={s.safety}><Text style={s.tipTag}>À SAVOIR</Text><Text style={s.body}>MetaBloom est un outil de bien-être. Il ne remplace pas un professionnel de santé et ne modifie jamais un traitement.</Text></Card><Pressable disabled={step===0&&!profile.name.trim()} onPress={()=>step===0?setStep(1):setProfile({...profile,complete:true})} style={({pressed})=>[s.primary,step===0&&!profile.name.trim()&&{opacity:.4},pressed&&s.pressed]}><Text style={s.primaryText}>{step===0?'Continuer':'Accepter et commencer'}</Text></Pressable>{step===1&&<Pressable onPress={()=>setStep(0)} style={s.secondary}><Text style={s.secondaryText}>Retour</Text></Pressable>}</ScrollView></SafeAreaView>}
function Header({eyebrow,title,right}:any){return <View style={s.header}><View style={s.headerCopy}><Text style={s.eyebrow}>{eyebrow}</Text><Text style={s.title}>{title}</Text></View>{right&&<View style={s.headerAction}>{right}</View>}</View>}
function Today({name,score,water,setWater,steps,setSteps,go}:any){const date=new Intl.DateTimeFormat('fr-FR',{weekday:'long',day:'numeric',month:'long'}).format(new Date()).toUpperCase();return <ScrollView contentContainerStyle={s.page} showsVerticalScrollIndicator={false}>
<Header eyebrow={date} title={`Bonjour ${name}`} right={<View style={s.avatar}><Text style={s.avatarText}>{name[0]?.toUpperCase()}</Text></View>}/>
<LinearGradient colors={['#1d6b50','#2d8063']} style={s.hero}><View style={{flex:1}}><Pill>Aujourdhui</Pill><Text style={s.heroTitle}>Chaque petit pas compte.</Text><Text style={s.heroText}>Tu avances à ton rythme, sans pression. Encore une action douce pour compléter ta journée.</Text><Pressable onPress={()=>go('journal')} style={s.heroButton}><Text style={s.heroButtonText}>Ajouter une donnée </Text></Pressable></View><View style={s.ring}><Text style={s.ringBig}>{score}</Text><Text style={s.ringSmall}>/ 100</Text></View></LinearGradient>
<View style={s.sectionHead}><Text style={s.h2}>Mes essentiels</Text><Text style={s.caption}>Objectifs ajustables</Text></View>
<View style={s.grid}><Metric icon={Droplets} label="Hydratation" value={`${water/1000} L`} sub="sur 2 L" onPress={()=>setWater((x:number)=>Math.min(x+250,2500))}/><Metric icon={Footprints} label="Mouvement" value={steps.toLocaleString('fr-FR')} sub="sur 8 000 pas" onPress={()=>setSteps((x:number)=>x+500)}/><Metric icon={Utensils} label="Repas équilibrés" value="2 / 3" sub="très bien"/><Metric icon={Moon} label="Sommeil" value="7 h 12" sub="objectif 8 h"/></View>
<Card style={s.tip}><IconTile icon={Lightbulb} bg={C.yellow}/><View style={{flex:1}}><Text style={s.tipTag}>PETIT CONSEIL DU JOUR</Text><Text style={s.tipTitle}>Une marche de 10 minutes après le repas ?</Text><Text style={s.body}>Un peu de mouvement aide à se sentir mieux. Adapte toujours leffort à ta situation.</Text></View></Card>
<Text style={s.disclaimer}>MetaBloom accompagne vos habitudes. Il ne remplace ni un diagnostic, ni un professionnel de santé.</Text>
</ScrollView>}
function Journal({weight,setWeight,glucose,setGlucose,glucoseUnit,water,setWater,steps,setSteps,sleep,setSleep,meals,setMeals,mood,setMood,save,saved}:any){return <ScrollView contentContainerStyle={s.page} keyboardShouldPersistTaps="handled"><Header eyebrow="SUIVI COMPLET" title="Mon journal" right={<Pill>Hors ligne</Pill>}/><Text style={s.lead}>Le poids et le sommeil sont nécessaires pour enregistrer la journée. Le reste est facultatif.</Text>
<Card><Text style={s.h2}>Mesures du jour</Text><Text style={s.inputLabel}>Poids</Text><View style={s.inputRow}><TextInput accessibilityLabel="Poids en kilogrammes, requis" value={weight} onChangeText={setWeight} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>kg</Text></View><Text style={s.inputLabel}>Glycémie <Text style={s.optional}>facultatif · saisie manuelle</Text></Text><View style={s.inputRow}><TextInput accessibilityLabel="Glycémie facultative" value={glucose} onChangeText={setGlucose} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>{glucoseUnit}</Text></View><Text style={s.helper}>Ces valeurs servent uniquement à ton suivi personnel. Aucune interprétation médicale nest fournie.</Text></Card>
<Card><Text style={s.h2}>Habitudes</Text><Stepper icon="💧" label="Eau" value={`${water} ml`} minus={()=>setWater(Math.max(0,water-250))} plus={()=>setWater(water+250)}/><Stepper icon="👟" label="Pas" value={steps.toLocaleString('fr-FR')} minus={()=>setSteps(Math.max(0,steps-500))} plus={()=>setSteps(steps+500)}/></Card>
<Card><Text style={s.h2}>Bien-être</Text><Text style={s.inputLabel}>Sommeil</Text><View style={s.inputRow}><TextInput accessibilityLabel="Durée de sommeil" value={sleep} onChangeText={setSleep} keyboardType="decimal-pad" style={s.input}/><Text style={s.unit}>heures</Text></View><Text style={s.inputLabel}>Repas équilibrés</Text><View style={s.segment}>{[0,1,2,3].map(n=><Pressable accessibilityLabel={`${n} repas équilibrés`} key={n} onPress={()=>setMeals(n)} style={[s.segmentItem,meals===n&&s.segmentActive]}><Text style={[s.segmentText,meals===n&&{color:C.white}]}>{n}</Text></Pressable>)}</View><Text style={s.inputLabel}>Humeur</Text><View style={s.segment}>{['😔','😐','🙂','😄'].map(x=><Pressable accessibilityLabel={`Humeur ${x}`} key={x} onPress={()=>setMood(x)} style={[s.segmentItem,mood===x&&s.segmentActive]}><Text style={{fontSize:21}}>{x}</Text></Pressable>)}</View></Card>
<Card style={s.scan}><IconTile icon={Camera} bg="#fff0d7" color="#9b6327"/><Text style={s.tipTag}>REPAS</Text><Text style={s.tipTitle}>Scanner ou photographier un repas</Text><Text style={s.body}>La confirmation des aliments restera toujours nécessaire. Lanalyse automatique arrivera avec une base nutritionnelle validée.</Text></Card>
<Pressable accessibilityRole="button" accessibilityLabel={saved?'Journée enregistrée':'Enregistrer la journée'} onPress={save} style={({pressed})=>[s.primary,pressed&&s.pressed]}>{saved&&<Check size={20} color={C.white}/>}<Text style={s.primaryText}>{saved?'Journée enregistrée':'Enregistrer la journée'}</Text></Pressable>
</ScrollView>}
function Stepper({icon,label,value,minus,plus}:any){const Icon=icon==='💧'?Droplets:Footprints;return <View style={s.stepper}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.metricLabel}>{label}</Text><Text style={s.stepValue}>{value}</Text></View><Pressable accessibilityRole="button" accessibilityLabel={`Diminuer ${label}`} onPress={minus} style={({pressed})=>[s.stepBtn,pressed&&s.pressed]}><Minus size={20} color={C.green}/></Pressable><Pressable accessibilityRole="button" accessibilityLabel={`Augmenter ${label}`} onPress={plus} style={({pressed})=>[s.stepBtn,pressed&&s.pressed]}><Plus size={20} color={C.green}/></Pressable></View>}
function Progress({entries}:any){const recent:Entry[]=entries.slice(-7);if(recent.length===0)return <ScrollView contentContainerStyle={s.page}><Header eyebrow="MES TENDANCES" title="Mes progrès"/><Card style={s.empty}><IconTile icon={TrendingUp}/><Text style={s.tipTitle}>Tes tendances apparaîtront ici</Text><Text style={s.body}>Ajoute au moins deux journées pour suivre ton évolution, sans pression.</Text></Card></ScrollView>;const weights=recent.map(e=>e.weight).filter(Number.isFinite);const min=Math.min(...weights);const max=Math.max(...weights);const avg=(key:'water'|'steps')=>Math.round(recent.reduce((a,e)=>a+(Number(e[key])||0),0)/recent.length);const rawDelta=recent.length>1?recent.at(-1)!.weight-recent[0].weight:0;return <ScrollView contentContainerStyle={s.page}><Header eyebrow="7 DERNIERS JOURS" title="Mes progrès" right={<Pill tone="coral">{rawDelta<=0?'Baisse':'Hausse'} {Math.abs(rawDelta).toFixed(1).replace('.',',')} kg</Pill>}/><Text style={s.lead}>Observe les tendances, pas la perfection.</Text><Card><View style={s.sectionHead}><View style={{flex:1}}><Text style={s.metricLabel}>Tendance de poids</Text><Text style={s.bigNumber}>{recent.at(-1)!.weight} <Text style={s.bigUnit}>kg</Text></Text></View><Pill>Données personnelles</Pill></View><View style={s.chart}>{recent.map((e,i)=><View key={`${e.date}-${i}`} style={s.barCol}><Text style={s.barValue}>{e.weight}</Text><View style={[s.bar,{height:58+((max-e.weight)/(max-min||1))*55}]}/><Text style={s.barLabel}>{e.date}</Text></View>)}</View></Card>
<View style={s.grid}><Card style={s.stat}><IconTile icon={Droplets}/><Text style={s.bigNumber}>{(avg('water')/1000).toFixed(1).replace('.',',')} L</Text><Text style={s.body}>moyenne deau</Text></Card><Card style={s.stat}><IconTile icon={Footprints}/><Text style={s.bigNumber}>{avg('steps').toLocaleString('fr-FR')}</Text><Text style={s.body}>pas en moyenne</Text></Card></View>
<Card style={s.insight}><Text style={s.tipTag}>À RETENIR</Text><Text style={s.tipTitle}>Une semaine régulière 🌱</Text><Text style={s.body}>Tu as bougé au moins 6 000 pas chaque jour. Cette régularité compte davantage quune journée parfaite.</Text></Card></ScrollView>}
function Coach({journey}:any){const lessons:[[LucideIcon,string,string]]|any=[[Utensils,'Composer une assiette rassasiante','4 min'],[Footprints,'Bouger après un repas, à ton rythme','3 min'],[Lightbulb,'Sortir du tout ou rien','5 min']];return <ScrollView contentContainerStyle={s.page}><Header eyebrow="PARCOURS PERSONNALISÉ" title="Mon coach" right={<Pill>{journey}</Pill>}/><LinearGradient colors={['#563c82','#7659a6']} style={s.coachHero}><Sparkles size={24} color={C.yellow}/><Text style={s.heroTitle}>Ta semaine, sans pression.</Text><Text style={s.heroText}>3 petites étapes pédagogiques. Aucun conseil ne modifie ton traitement.</Text><ProgressBar value={42} color={C.yellow}/><Text style={s.coachProgress}>42 % du parcours hebdomadaire</Text></LinearGradient><Text style={s.h2}>Micro-leçons</Text>{lessons.map(([Icon,title,duration]:[LucideIcon,string,string])=><Pressable key={title} accessibilityRole="button" accessibilityLabel={`${title}, ${duration}`} style={({pressed})=>[s.card,s.lesson,pressed&&s.pressed]}><IconTile icon={Icon}/><View style={{flex:1}}><Text style={s.tipTitle}>{title}</Text><Text style={s.body}>{duration} · contenu éducatif</Text></View><ChevronRight size={22} color={C.muted}/></Pressable>)}<Card style={s.insight}><Text style={s.tipTag}>COACHING DU JOUR</Text><Text style={s.tipTitle}>Choisis une action si petite quelle paraît facile.</Text><Text style={s.body}>Par exemple : préparer une bouteille deau ou marcher cinq minutes. Tu décides selon ton énergie et les recommandations de ton soignant.</Text></Card><Card><IconTile icon={Apple}/><Text style={s.tipTag}>CONNEXIONS SANTÉ</Text><Text style={s.tipTitle}>Apple Santé & Health Connect</Text><Text style={s.body}>Disponible prochainement après activation des autorisations natives.</Text></Card></ScrollView>}
function Profile({profile,setProfile,entries,reset}:any){const exportData=async()=>{const csv=['date,poids,eau_ml,pas,glycemie',...entries.map((e:Entry)=>[e.iso||e.date,e.weight,e.water,e.steps,e.glucose||''].join(','))].join('\n');await Share.share({title:'Export MetaBloom',message:csv})};return <ScrollView contentContainerStyle={s.page}><Header eyebrow="MON ESPACE" title="Profil & sécurité"/><Card style={s.profile}><View style={s.avatarLarge}><Text style={s.avatarLargeText}>{profile.name[0]?.toUpperCase()}</Text></View><View style={{flex:1}}><Text style={s.tipTitle}>{profile.name}</Text><Text style={s.body}>{profile.goal} · {profile.journey}</Text></View></Card><Text style={s.h2}>Préférences</Text><Card style={s.setting}><IconTile icon={Activity}/><View style={{flex:1}}><Text style={s.metricLabel}>Unité glycémie</Text><Text style={s.body}>{profile.glucoseUnit}</Text></View><Pressable accessibilityRole="button" accessibilityLabel="Changer lunité de glycémie" onPress={()=>setProfile({...profile,glucoseUnit:profile.glucoseUnit==='mg/dL'?'mmol/L':'mg/dL'})} style={({pressed})=>[s.smallButton,pressed&&s.pressed]}><Text style={s.smallButtonText}>Changer</Text></Pressable></Card><Card style={s.setting}><IconTile icon={Bell}/><View style={{flex:1}}><Text style={s.metricLabel}>Rappels doux</Text><Text style={s.body}>{profile.reminders?'Activés à 19 h':'Désactivés'}</Text></View><Switch accessibilityLabel="Activer les rappels" value={profile.reminders} onValueChange={(reminders)=>setProfile({...profile,reminders})} trackColor={{true:C.green}}/></Card><Pressable accessibilityRole="button" accessibilityLabel="Exporter mes données CSV" onPress={exportData} style={({pressed})=>[s.secondaryCard,pressed&&s.pressed]}><Download size={20} color={C.green}/><Text style={s.secondaryCardText}>Exporter mes données CSV</Text></Pressable><Card style={s.safety}><IconTile icon={ShieldCheck}/><Text style={s.tipTag}>SÉCURITÉ & CONFIDENTIALITÉ</Text><Text style={s.tipTitle}>Tes données restent sous ton contrôle.</Text><Text style={s.body}>Tu peux les exporter ou les supprimer à tout moment. La synchronisation en ligne nest pas activée dans cette version.</Text></Card><Pressable onPress={()=>Alert.alert('Effacer les données locales ?','Cette action supprime les données de cet appareil et réinitialise lapplication.',[{text:'Annuler'},{text:'Effacer',style:'destructive',onPress:reset}])} style={s.danger}><Text style={s.dangerText}>Effacer les données de cet appareil</Text></Pressable><Text style={s.disclaimer}>En cas de malaise ou durgence, contactez immédiatement les services durgence. MetaBloom ne fournit pas de conseil thérapeutique.</Text></ScrollView>}
const s=StyleSheet.create({safe:{flex:1,backgroundColor:C.cream},shell:{flex:1,width:'100%',alignSelf:'center'},page:{padding:20,paddingTop:26,paddingBottom:120,gap:16},pressed:{opacity:.72,transform:[{scale:.98}]},header:{flexDirection:'row',justifyContent:'space-between',alignItems:'center',gap:12,marginBottom:4},headerCopy:{flex:1,minWidth:0},headerAction:{maxWidth:'44%'},eyebrow:{fontSize:11,fontWeight:'800',letterSpacing:1.4,color:C.green},title:{fontSize:28,fontWeight:'800',color:C.ink,letterSpacing:-.7,marginTop:4},avatar:{width:44,height:44,borderRadius:22,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},avatarText:{fontWeight:'800',color:C.ink,fontSize:17},hero:{borderRadius:28,padding:22,minHeight:230,flexDirection:'row',alignItems:'center',gap:14,overflow:'hidden'},heroTitle:{fontSize:27,lineHeight:32,fontWeight:'800',color:C.white,marginTop:18,maxWidth:360},heroText:{fontSize:14,lineHeight:21,color:'#dcebe4',marginTop:8,maxWidth:400},heroButton:{backgroundColor:C.white,borderRadius:14,paddingVertical:14,paddingHorizontal:16,alignSelf:'flex-start',marginTop:18,minHeight:48,justifyContent:'center'},heroButtonText:{color:C.green,fontWeight:'800'},ring:{width:96,height:96,borderWidth:8,borderColor:C.yellow,borderRadius:50,alignItems:'center',justifyContent:'center',backgroundColor:'#ffffff13'},ringBig:{color:C.white,fontWeight:'900',fontSize:28},ringSmall:{color:'#dcebe4',fontSize:11},pill:{borderRadius:99,paddingHorizontal:11,paddingVertical:6,alignSelf:'flex-start'},pillText:{fontSize:11,fontWeight:'800'},sectionHead:{flexDirection:'row',justifyContent:'space-between',alignItems:'center'},h2:{fontSize:19,fontWeight:'800',color:C.ink},caption:{fontSize:12,color:C.muted},grid:{flexDirection:'row',flexWrap:'wrap',gap:12},metric:{backgroundColor:C.white,borderRadius:20,padding:17,width:'48%',minWidth:150,minHeight:154,borderWidth:1,borderColor:C.line},iconTile:{width:44,height:44,borderRadius:14,alignItems:'center',justifyContent:'center',marginBottom:12},metricLabel:{fontSize:12,fontWeight:'700',color:C.muted},metricValue:{fontSize:22,fontWeight:'900',color:C.ink,marginTop:3},metricSub:{fontSize:11,color:C.muted,marginTop:3},card:{backgroundColor:C.white,borderRadius:22,padding:19,borderWidth:1,borderColor:C.line},tip:{flexDirection:'row',gap:14,backgroundColor:'#fffaf0'},tipTag:{fontSize:10,fontWeight:'900',letterSpacing:1.1,color:C.green,marginBottom:6},tipTitle:{fontSize:17,fontWeight:'800',color:C.ink,marginBottom:5},body:{fontSize:13,lineHeight:19,color:C.muted},disclaimer:{fontSize:11,lineHeight:17,textAlign:'center',color:C.muted,paddingHorizontal:16},nav:{position:'absolute',bottom:0,left:0,right:0,height:86,backgroundColor:'#fffffffa',borderTopWidth:1,borderColor:C.line,flexDirection:'row',paddingTop:8,paddingHorizontal:6},navItem:{flex:1,minHeight:62,alignItems:'center',justifyContent:'center',gap:4,borderRadius:16},navItemActive:{backgroundColor:'#edf7f1'},navLabel:{fontSize:10,fontWeight:'700',color:'#829087'},active:{color:C.green},lead:{fontSize:15,lineHeight:22,color:C.muted,marginTop:-8},inputLabel:{fontSize:13,fontWeight:'800',color:C.ink,marginTop:17,marginBottom:7},optional:{fontWeight:'500',color:C.muted},inputRow:{minHeight:56,borderWidth:1.5,borderColor:'#d8ded8',borderRadius:14,flexDirection:'row',alignItems:'center',paddingHorizontal:15,backgroundColor:C.white},input:{flex:1,fontSize:18,fontWeight:'800',color:C.ink,outlineStyle:'none'} as any,unit:{fontWeight:'700',color:C.muted},helper:{fontSize:11,lineHeight:16,color:C.muted,marginTop:12},stepper:{flexDirection:'row',alignItems:'center',gap:12,paddingVertical:13,borderBottomWidth:1,borderBottomColor:C.line},stepValue:{fontSize:17,fontWeight:'800',color:C.ink,marginTop:2},stepBtn:{width:48,height:48,borderRadius:14,backgroundColor:C.mint,alignItems:'center',justifyContent:'center'},stepBtnText:{fontSize:24,color:C.green,fontWeight:'700'},primary:{backgroundColor:C.green,borderRadius:16,minHeight:54,padding:16,alignItems:'center',justifyContent:'center',flexDirection:'row',gap:8},primaryText:{color:C.white,fontSize:15,fontWeight:'900'},track:{height:8,borderRadius:4,backgroundColor:'#ffffff3d',overflow:'hidden'},fill:{height:8,borderRadius:4},bigNumber:{fontSize:28,fontWeight:'900',color:C.ink,marginTop:4},bigUnit:{fontSize:14,color:C.muted},chart:{height:160,flexDirection:'row',alignItems:'flex-end',justifyContent:'space-between',marginTop:20},barCol:{flex:1,alignItems:'center'},bar:{width:22,backgroundColor:C.green,borderRadius:8,marginVertical:6},barValue:{fontSize:9,color:C.muted},barLabel:{fontSize:10,color:C.muted},stat:{width:'48%',minWidth:150},statIcon:{fontSize:22},empty:{alignItems:'center',paddingVertical:34},insight:{backgroundColor:C.mint},profile:{flexDirection:'row',alignItems:'center',gap:15},avatarLarge:{width:62,height:62,borderRadius:22,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},avatarLargeText:{fontSize:23,fontWeight:'900',color:C.ink},setting:{flexDirection:'row',alignItems:'center',gap:13,padding:15},chevron:{fontSize:28,color:C.muted},safety:{backgroundColor:C.mint},danger:{minHeight:48,padding:16,alignItems:'center',justifyContent:'center'},dangerText:{color:'#a23d33',fontWeight:'800'},onboard:{padding:24,paddingTop:54,paddingBottom:50,gap:16,maxWidth:560,width:'100%',alignSelf:'center'},logo:{width:58,height:58,borderRadius:20,backgroundColor:C.green,alignItems:'center',justifyContent:'center',marginBottom:8},logoText:{color:C.white,fontSize:28,fontWeight:'900'},onboardTitle:{fontSize:34,lineHeight:40,fontWeight:'900',color:C.ink,letterSpacing:-1},choice:{backgroundColor:C.white,borderWidth:1,borderColor:C.line,borderRadius:18,padding:15,minHeight:76,flexDirection:'row',alignItems:'center',gap:12},choiceActive:{borderWidth:2,borderColor:C.green,backgroundColor:C.mint},choiceEmoji:{fontSize:24},check:{fontSize:18,fontWeight:'900',color:C.green},secondary:{minHeight:48,padding:14,alignItems:'center',justifyContent:'center'},secondaryText:{color:C.green,fontWeight:'800'},segment:{flexDirection:'row',gap:8},segmentItem:{height:48,flex:1,borderRadius:13,backgroundColor:C.cream,alignItems:'center',justifyContent:'center',borderWidth:1,borderColor:C.line},segmentActive:{backgroundColor:C.green,borderColor:C.green},segmentText:{fontWeight:'800',color:C.ink},scan:{backgroundColor:'#fff8ed',borderStyle:'dashed',borderColor:'#d8a960'},coachHero:{borderRadius:26,padding:22,gap:14},coachProgress:{fontSize:12,fontWeight:'700',color:'#eee8f7'},lesson:{flexDirection:'row',alignItems:'center',gap:12,minHeight:82},lessonIcon:{width:48,height:48,borderRadius:15,backgroundColor:C.cream,alignItems:'center',justifyContent:'center'},smallButton:{minHeight:44,paddingHorizontal:12,paddingVertical:9,borderRadius:11,backgroundColor:C.mint,justifyContent:'center'},smallButtonText:{color:C.green,fontWeight:'800',fontSize:12},secondaryCard:{backgroundColor:C.white,borderRadius:16,borderWidth:1,borderColor:C.green,minHeight:52,padding:16,alignItems:'center',justifyContent:'center',flexDirection:'row',gap:8},secondaryCardText:{color:C.green,fontWeight:'900'}});