diff --git a/App.tsx b/App.tsx
index ae06b50..9854baf 100644
--- a/App.tsx
+++ b/App.tsx
@@ -3,6 +3,9 @@ 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 };
@@ -16,17 +19,18 @@ const seed: Entry[] = [
function Card({children, style}: any){return {children}}
function Pill({children, tone='green'}:any){return {children}}
-function Metric({emoji,label,value,sub,onPress}:any){return [s.metric,pressed&&{opacity:.7}]}>{emoji}{label}{value}{sub}}
+function IconTile({icon:Icon,color=C.green,bg=C.mint,size=22}:any){return }
+function Metric({icon,label,value,sub,onPress}:any){const content=<>{label}{value}{sub}>;return onPress?[s.metric,pressed&&s.pressed]}>{content}:{content}}
function ProgressBar({value,color=C.green}:any){return }
-function Nav({tab,setTab}:any){const items:[Tab,string,string][]=[['today','⌂','Aujourd’hui'],['journal','+','Journal'],['progress','↗','Progrès'],['coach','✦','Coach'],['profile','○','Profil']];return {items.map(([id,icon,label])=>setTab(id)} style={s.navItem}>{icon}{label})}}
+function Nav({tab,setTab}:any){const items:any[]=[['today',Home,'Aujourd’hui'],['journal',Plus,'Journal'],['progress',TrendingUp,'Progrès'],['coach',Sparkles,'Coach'],['profile',CircleUserRound,'Profil']];return {items.map(([id,Icon,label])=>{const active=tab===id;return setTab(id)} style={({pressed})=>[s.navItem,active&&s.navItemActive,pressed&&s.pressed]}>{label}})}}
export default function App(){
const [tab,setTab]=useState('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({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(d.entries||seed);setWater(d.water||1250);setSteps(d.steps||6240);setProfile(d.profile||profile)}}).finally(()=>setLoaded(true))},[]);
+ 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(!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(g && (gglucoseBounds[1])){Alert.alert('Valeur à vérifier','La glycémie saisie semble hors de la plage de saisie autorisée. Vérifie l’unité et la valeur.');return} if(!sl||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:g||undefined,sleep:sl,meals,mood}]);setSaved(true);setTimeout(()=>setSaved(false),2200)};
+ 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)||gglucoseBounds[1])){Alert.alert('Valeur à vérifier','La glycémie saisie semble hors de la plage de saisie autorisée. Vérifie l’unité 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'?:tab==='journal'?:tab==='progress'?}/>
+ {name[0]?.toUpperCase()}}/>
Aujourd’huiChaque petit pas compte.Tu avances à ton rythme, sans pression. Encore une action douce pour compléter ta journée.go('journal')} style={s.heroButton}>Ajouter une donnée →{score}/ 100
Mes essentielsObjectifs ajustables
- setWater((x:number)=>Math.min(x+250,2500))}/>setSteps((x:number)=>x+500)}/>
- ☀️PETIT CONSEIL DU JOURUne marche de 10 minutes après le repas ?Un peu de mouvement aide à se sentir mieux. Adapte toujours l’effort à ta situation.
+ setWater((x:number)=>Math.min(x+250,2500))}/>setSteps((x:number)=>x+500)}/>
+ PETIT CONSEIL DU JOURUne marche de 10 minutes après le repas ?Un peu de mouvement aide à se sentir mieux. Adapte toujours l’effort à ta situation.
MetaBloom accompagne vos habitudes. Il ne remplace ni un diagnostic, ni un professionnel de santé.
}
-function Journal({weight,setWeight,glucose,setGlucose,glucoseUnit,water,setWater,steps,setSteps,sleep,setSleep,meals,setMeals,mood,setMood,save,saved}:any){return ☁ Prêt à synchroniser}/>Note ce qui compte pour toi. Rien n’est obligatoire.
- Mesures du jourPoids facultatifkgGlycémie facultatif · saisie manuelle{glucoseUnit}Ces valeurs servent uniquement à ton suivi personnel. Aucune interprétation médicale n’est fournie.
+function Journal({weight,setWeight,glucose,setGlucose,glucoseUnit,water,setWater,steps,setSteps,sleep,setSleep,meals,setMeals,mood,setMood,save,saved}:any){return Hors ligne}/>Le poids et le sommeil sont nécessaires pour enregistrer la journée. Le reste est facultatif.
+ Mesures du jourPoidskgGlycémie facultatif · saisie manuelle{glucoseUnit}Ces valeurs servent uniquement à ton suivi personnel. Aucune interprétation médicale n’est fournie.
HabitudessetWater(Math.max(0,water-250))} plus={()=>setWater(water+250)}/>setSteps(Math.max(0,steps-500))} plus={()=>setSteps(steps+500)}/>
Bien-êtreSommeilheuresRepas équilibrés{[0,1,2,3].map(n=>setMeals(n)} style={[s.segmentItem,meals===n&&s.segmentActive]}>{n})}Humeur{['😔','😐','🙂','😄'].map(x=>setMood(x)} style={[s.segmentItem,mood===x&&s.segmentActive]}>{x})}
- REPAS📷 Scanner ou photographier un repasLa confirmation des aliments restera toujours nécessaire. L’analyse automatique arrivera avec une base nutritionnelle validée.
- [s.primary,pressed&&{opacity:.8}]}>{saved?'✓ Journée enregistrée':'Enregistrer ma journée'}
+ REPASScanner ou photographier un repasLa confirmation des aliments restera toujours nécessaire. L’analyse automatique arrivera avec une base nutritionnelle validée.
+ [s.primary,pressed&&s.pressed]}>{saved&&}{saved?'Journée enregistrée':'Enregistrer la journée'}
}
-function Stepper({icon,label,value,minus,plus}:any){return {icon}{label}{value}−+}
+function Stepper({icon,label,value,minus,plus}:any){const Icon=icon==='💧'?Droplets:Footprints;return {label}{value}[s.stepBtn,pressed&&s.pressed]}>[s.stepBtn,pressed&&s.pressed]}>}
-function Progress({entries}:any){const recent=entries.slice(-7);const min=Math.min(...recent.map((e:Entry)=>e.weight)); const max=Math.max(...recent.map((e:Entry)=>e.weight));const avg=(key:'water'|'steps')=>Math.round(recent.reduce((a:number,e:Entry)=>a+e[key],0)/recent.length);const delta=(recent.at(-1).weight-recent[0].weight).toFixed(1).replace('.',',');return {Number(delta)<=0?'↘':'↗'} {Math.abs(Number(delta))} kg}/>Observe les tendances, pas la perfection.Tendance de poids{recent.at(-1).weight} kgDonnées dynamiques{recent.map((e:Entry,i:number)=>{e.weight}{e.date})}
- 💧{(avg('water')/1000).toFixed(1).replace('.',',')} Lmoyenne d’eau👟{avg('steps').toLocaleString('fr-FR')}pas en moyenne
+function Progress({entries}:any){const recent:Entry[]=entries.slice(-7);if(recent.length===0)return Tes tendances apparaîtront iciAjoute au moins deux journées pour suivre ton évolution, sans pression.;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 {rawDelta<=0?'Baisse':'Hausse'} {Math.abs(rawDelta).toFixed(1).replace('.',',')} kg}/>Observe les tendances, pas la perfection.Tendance de poids{recent.at(-1)!.weight} kgDonnées personnelles{recent.map((e,i)=>{e.weight}{e.date})}
+ {(avg('water')/1000).toFixed(1).replace('.',',')} Lmoyenne d’eau{avg('steps').toLocaleString('fr-FR')}pas en moyenne
À RETENIRUne semaine régulière 🌱Tu as bougé au moins 6 000 pas chaque jour. Cette régularité compte davantage qu’une journée parfaite.}
-function Coach({journey}:any){const lessons=[['🥗','Composer une assiette rassasiante','4 min'],['🚶','Bouger après un repas, à ton rythme','3 min'],['🧠','Sortir du tout ou rien','5 min']];return ✨ {journey}}/>Ta semaine, sans pression.3 petites étapes pédagogiques. Aucun conseil ne modifie ton traitement.42 % du parcours hebdomadaireMicro-leçons{lessons.map((x,i)=>{x[0]}{x[1]}{x[2]} · contenu éducatif›)}COACHING DU JOURChoisis une action si petite qu’elle paraît facile.Par exemple : préparer une bouteille d’eau ou marcher cinq minutes. Tu décides selon ton énergie et les recommandations de ton soignant.CONNEXIONS SANTÉApple Santé & Health ConnectArchitecture prête pour importer pas, activité et sommeil avec permission explicite. L’activation nécessite un build natif et les comptes développeur Apple/Google.}
+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 {journey}}/>Ta semaine, sans pression.3 petites étapes pédagogiques. Aucun conseil ne modifie ton traitement.42 % du parcours hebdomadaireMicro-leçons{lessons.map(([Icon,title,duration]:[LucideIcon,string,string])=>[s.card,s.lesson,pressed&&s.pressed]}>{title}{duration} · contenu éducatif)}COACHING DU JOURChoisis une action si petite qu’elle paraît facile.Par exemple : préparer une bouteille d’eau ou marcher cinq minutes. Tu décides selon ton énergie et les recommandations de ton soignant.CONNEXIONS SANTÉApple Santé & Health ConnectDisponible prochainement après activation des autorisations natives.}
-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 {profile.name[0]?.toUpperCase()}{profile.name}{profile.goal} · {profile.journey}Préférences🩸Unité glycémie{profile.glucoseUnit}setProfile({...profile,glucoseUnit:profile.glucoseUnit==='mg/dL'?'mmol/L':'mg/dL'})} style={s.smallButton}>Changer🔔Rappels doux{profile.reminders?'Activés à 19 h':'Désactivés'}setProfile({...profile,reminders})} trackColor={{true:C.green}}/>⇧ Exporter mes données CSVSÉCURITÉ & CONFIDENTIALITÉContrôle, export et effacement.Le backend auto-hébergeable fournit authentification, consentement, isolation des comptes, export et suppression. Une validation RGPD/HDS/MDR reste obligatoire avant production.Alert.alert('Effacer les données locales ?','Cette action réinitialise l’application.',[{text:'Annuler'},{text:'Effacer',style:'destructive',onPress:reset}])} style={s.danger}>Effacer mes données localesEn cas de malaise ou d’urgence, contactez immédiatement les services d’urgence. MetaBloom ne fournit pas de conseil thérapeutique.}
+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 {profile.name[0]?.toUpperCase()}{profile.name}{profile.goal} · {profile.journey}PréférencesUnité glycémie{profile.glucoseUnit}setProfile({...profile,glucoseUnit:profile.glucoseUnit==='mg/dL'?'mmol/L':'mg/dL'})} style={({pressed})=>[s.smallButton,pressed&&s.pressed]}>ChangerRappels doux{profile.reminders?'Activés à 19 h':'Désactivés'}setProfile({...profile,reminders})} trackColor={{true:C.green}}/>[s.secondaryCard,pressed&&s.pressed]}>Exporter mes données CSVSÉCURITÉ & CONFIDENTIALITÉTes données restent sous ton contrôle.Tu peux les exporter ou les supprimer à tout moment. La synchronisation en ligne n’est pas activée dans cette version.Alert.alert('Effacer les données locales ?','Cette action supprime les données de cet appareil et réinitialise l’application.',[{text:'Annuler'},{text:'Effacer',style:'destructive',onPress:reset}])} style={s.danger}>Effacer les données de cet appareilEn cas de malaise ou d’urgence, contactez immédiatement les services d’urgence. MetaBloom ne fournit pas de conseil thérapeutique.}
-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},header:{flexDirection:'row',justifyContent:'space-between',alignItems:'center',marginBottom:4},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:13,paddingHorizontal:16,alignSelf:'flex-start',marginTop:18},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,borderWidth:1,borderColor:C.line},metricEmoji:{fontSize:24,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'},tipIcon:{width:46,height:46,borderRadius:14,backgroundColor:C.yellow,alignItems:'center',justifyContent:'center'},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:82,backgroundColor:'#fffffffa',borderTopWidth:1,borderColor:C.line,flexDirection:'row',paddingTop:9},navItem:{flex:1,alignItems:'center',justifyContent:'center',gap:3},navIcon:{fontSize:22,color:'#829087'},navLabel:{fontSize:9,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:{height:56,borderWidth:1,borderColor:C.line,borderRadius:14,flexDirection:'row',alignItems:'center',paddingHorizontal:15,backgroundColor:C.cream},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:44,height:44,borderRadius:13,backgroundColor:C.mint,alignItems:'center',justifyContent:'center'},stepBtnText:{fontSize:24,color:C.green,fontWeight:'700'},primary:{backgroundColor:C.green,borderRadius:16,padding:17,alignItems:'center'},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},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:{padding:16,alignItems:'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,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:{padding:14,alignItems:'center'},secondaryText:{color:C.green,fontWeight:'800'},segment:{flexDirection:'row',gap:8},segmentItem:{height:46,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},lessonIcon:{width:48,height:48,borderRadius:15,backgroundColor:C.cream,alignItems:'center',justifyContent:'center'},smallButton:{paddingHorizontal:12,paddingVertical:9,borderRadius:11,backgroundColor:C.mint},smallButtonText:{color:C.green,fontWeight:'800',fontSize:12},secondaryCard:{backgroundColor:C.white,borderRadius:16,borderWidth:1,borderColor:C.green,padding:16,alignItems:'center'},secondaryCardText:{color:C.green,fontWeight:'900'}});
+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'}});
diff --git a/README.md b/README.md
index dc19c42..d82f53a 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ Les captures mobiles sont disponibles dans [`screenshots/`](./screenshots/).
| Couche | Technologie |
|---|---|
| Application | Expo SDK 57, React Native 0.86, React 19, TypeScript |
+| Design system | Icônes vectorielles Lucide, composants accessibles, zones tactiles 44 px minimum |
| Stockage local | AsyncStorage |
| API | FastAPI, SQLAlchemy, Pydantic |
| Données serveur | PostgreSQL en production, SQLite en développement |
diff --git a/package-lock.json b/package-lock.json
index 8eebc1d..cc247b2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,9 +12,11 @@
"expo": "~57.0.7",
"expo-linear-gradient": "~57.0.1",
"expo-status-bar": "~57.0.1",
+ "lucide-react-native": "^1.25.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.0",
+ "react-native-svg": "15.15.4",
"react-native-web": "^0.21.2"
},
"devDependencies": {
@@ -2045,6 +2047,12 @@
"node": ">=0.6"
}
},
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "license": "ISC"
+ },
"node_modules/bplist-creator": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
@@ -2441,6 +2449,56 @@
"hyphenate-style-name": "^1.0.3"
}
},
+ "node_modules/css-select": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
+ "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.0.14",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/css-tree/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -2520,6 +2578,61 @@
"integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==",
"license": "MIT"
},
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2547,6 +2660,18 @@
"node": ">= 0.8"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
@@ -4175,6 +4300,17 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react-native": {
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.25.0.tgz",
+ "integrity": "sha512-OI/X62YcYqoBghVaxNRmZ9v3pGZ9e7kY7CH4eeGgkW4qPyX8JMO77Nj/QdmS632nlx1DR2fuWmqnneIt1RmsOg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-native": "*",
+ "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0"
+ }
+ },
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -4190,6 +4326,12 @@
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
"license": "Apache-2.0"
},
+ "node_modules/mdn-data": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
+ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
+ "license": "CC0-1.0"
+ },
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
@@ -4675,6 +4817,18 @@
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
"node_modules/nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
@@ -5185,6 +5339,21 @@
}
}
},
+ "node_modules/react-native-svg": {
+ "version": "15.15.4",
+ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.4.tgz",
+ "integrity": "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==",
+ "license": "MIT",
+ "dependencies": {
+ "css-select": "^5.1.0",
+ "css-tree": "^1.1.3",
+ "warn-once": "0.1.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-web": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
@@ -6042,6 +6211,12 @@
"makeerror": "1.0.12"
}
},
+ "node_modules/warn-once": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
+ "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
+ "license": "MIT"
+ },
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
diff --git a/package.json b/package.json
index 2d7f49a..827284b 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,11 @@
"expo": "~57.0.7",
"expo-linear-gradient": "~57.0.1",
"expo-status-bar": "~57.0.1",
+ "lucide-react-native": "^1.25.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.0",
+ "react-native-svg": "15.15.4",
"react-native-web": "^0.21.2"
},
"devDependencies": {
@@ -21,7 +23,7 @@
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
- "typecheck": "tsc --noEmit",
+ "typecheck": "node --stack_size=8192 node_modules/typescript/bin/tsc --noEmit",
"build:web": "expo export --platform web"
},
"private": true
diff --git a/screenshots/01-onboarding.png b/screenshots/01-onboarding.png
index 0cadb6d..cf870f6 100644
Binary files a/screenshots/01-onboarding.png and b/screenshots/01-onboarding.png differ
diff --git a/screenshots/02-parcours.png b/screenshots/02-parcours.png
index 49cd3af..d10787a 100644
Binary files a/screenshots/02-parcours.png and b/screenshots/02-parcours.png differ
diff --git a/screenshots/03-aujourdhui.png b/screenshots/03-aujourdhui.png
index 447435e..a64186f 100644
Binary files a/screenshots/03-aujourdhui.png and b/screenshots/03-aujourdhui.png differ
diff --git a/screenshots/04-journal.png b/screenshots/04-journal.png
index a6b4143..d704363 100644
Binary files a/screenshots/04-journal.png and b/screenshots/04-journal.png differ
diff --git a/screenshots/05-progres.png b/screenshots/05-progres.png
index a1d68a7..d9fcf3f 100644
Binary files a/screenshots/05-progres.png and b/screenshots/05-progres.png differ
diff --git a/screenshots/06-coach.png b/screenshots/06-coach.png
index 6246d4b..e437a88 100644
Binary files a/screenshots/06-coach.png and b/screenshots/06-coach.png differ
diff --git a/screenshots/07-profil.png b/screenshots/07-profil.png
index 1f73405..2446d63 100644
Binary files a/screenshots/07-profil.png and b/screenshots/07-profil.png differ