🏆
Bravo championne !
Tu as fait TOUTES tes activitĂ©s aujourd'hui ! 🌈⭐🩄
FamilyHub
Live
— | Nantes, FR
Météo · Nantes
—
TĂąches restantes
4
2 urgentes
Activités de Léa
0/6
Pas encore commencé
ETF phare
35.42€
MSCI World +1.2%
📱 Notice familiale
Aucune notice.
Résumé
5
tĂąches
Complétées
1
Urgentes
2
Famille
3
Courses
1
Couleur
Taille
⭐ Mes activités du jour ⭐
—
🌟
0 / 6 activitĂ©s 🏆
Cours ETF
Données clés
Calendrier
Mars 2026
Présents à la maison
Météo rapide
⭐Activités de Léa
0/6 complétées
// ---- CLOCK ---- function tick(){ const n=new Date(); document.getElementById('tbClock').textContent=n.toLocaleDateString('fr-FR',{weekday:'short',day:'numeric',month:'short',year:'numeric'})+' — '+n.toLocaleTimeString('fr-FR'); const days=['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi']; const months=['janvier','fĂ©vrier','mars','avril','mai','juin','juillet','aoĂ»t','septembre','octobre','novembre','dĂ©cembre']; const el=document.getElementById('homeSubDate');if(el)el.textContent=days[n.getDay()]+' '+n.getDate()+' '+months[n.getMonth()]+' '+n.getFullYear(); const kd=document.getElementById('kidsDate');if(kd)kd.textContent='Aujourd\'hui, '+days[n.getDay()]+' '+n.getDate()+' '+months[n.getMonth()]; } tick();setInterval(tick,1000); // ---- NAV ---- function goPage(i,el){ document.querySelectorAll('.page').forEach((p,j)=>p.classList.toggle('active',j===i)); document.querySelectorAll('.nav-item').forEach(b=>b.classList.remove('active')); el.classList.add('active'); if(i===4)initCanvas(); } // ---- NOTICE ---- function updateNotice(){ const val=document.getElementById('noticeInput').value.trim(); const nd=document.getElementById('noticeDisplay'); if(val){nd.textContent=val;}else{nd.innerHTML='Aucune notice.';} } // ---- WHO IS HOME ---- const family=[{name:'Papa',initials:'P',status:true,color:'#4a9eff'},{name:'Maman',initials:'M',status:true,color:'#3dd68c'},{name:'LĂ©a',initials:'L',status:false,color:'#ff9dcb'},{name:'Tom',initials:'T',status:false,color:'#f0b84a'}]; function renderWhoHome(){ document.getElementById('whoHome').innerHTML=family.map(f=>`
${f.initials}
${f.name}${f.status?'Présent·e':'Absent·e'}
`).join(''); } function togPresence(name){const f=family.find(x=>x.name===name);if(f)f.status=!f.status;renderWhoHome();} renderWhoHome(); // ---- WEATHER ---- const WMO={0:'Ciel dĂ©gagĂ©',1:'Principalement dĂ©gagĂ©',2:'Partiellement nuageux',3:'Couvert',45:'Brouillard',51:'Bruine lĂ©gĂšre',61:'Pluie lĂ©gĂšre',63:'Pluie modĂ©rĂ©e',65:'Pluie forte',71:'Neige lĂ©gĂšre',80:'Averses lĂ©gĂšres',81:'Averses modĂ©rĂ©es',95:'Orage'}; const WI={0:'☀',1:'đŸŒ€',2:'⛅',3:'☁',45:'đŸŒ«',51:'🌩',61:'🌧',63:'🌧',65:'🌧',71:'🌹',80:'🌩',81:'🌧',95:'⛈'}; function wd(c){return WMO[c]||'Variable';}function wi(c){return WI[c]||'đŸŒ€';} async function fetchWeather(){ try{ const r=await fetch('https://api.open-meteo.com/v1/forecast?latitude=47.2184&longitude=-1.5536¤t=temperature_2m,weathercode,windspeed_10m,relativehumidity_2m,apparent_temperature,precipitation_probability&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,precipitation_probability_max&timezone=Europe/Paris&forecast_days=5'); const d=await r.json();const c=d.current,dl=d.daily;const code=c.weathercode; document.getElementById('hTemp').textContent=Math.round(c.temperature_2m)+'°C'; document.getElementById('hDesc').textContent=wi(code)+' '+wd(code); document.getElementById('weatherMini').innerHTML=`
${wi(code)}
${Math.round(c.temperature_2m)}°C
${wd(code)}
Vent ${Math.round(c.windspeed_10m)} km/h · ${c.relativehumidity_2m}%
`; const sr=new Date(dl.sunrise[0]).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); const ss=new Date(dl.sunset[0]).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); const dns=['Dim','Lun','Mar','Mer','Jeu','Ven','Sam']; const fcHtml=dl.time.slice(0,5).map((t,i)=>{const dt=new Date(t);return`
${i===0?'Auj.':dns[dt.getDay()]}
${wi(dl.weathercode[i])}
${Math.round(dl.temperature_2m_max[i])}°
${Math.round(dl.temperature_2m_min[i])}° · ${dl.precipitation_probability_max[i]}%
`;}).join(''); document.getElementById('weatherContent').innerHTML=`
Nantes, Pays de la Loire
${Math.round(c.temperature_2m)}°C
${wd(code)} · Ressenti ${Math.round(c.apparent_temperature)}°C
${wi(code)}
${c.relativehumidity_2m}%
Humidité
${Math.round(c.windspeed_10m)} km/h
Vent
${c.precipitation_probability}%
Précip.
${sr}
Lever
${ss}
Coucher
Prévisions 5 jours
${fcHtml}
`; }catch(e){document.getElementById('hTemp').textContent='N/A';document.getElementById('hDesc').textContent='Indisponible';} } fetchWeather(); // ---- MINI CALENDAR ---- let cy=2026,cm=2; const mNames=['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre']; function renderMiniCal(){ document.getElementById('miniCalLabel').textContent=mNames[cm]+' '+cy; const dows=document.getElementById('miniCalDows');dows.innerHTML=''; ['L','M','M','J','V','S','D'].forEach(d=>{const el=document.createElement('div');el.className='cal-dow';el.textContent=d;dows.appendChild(el);}); const grid=document.getElementById('miniCalGrid');grid.innerHTML=''; const first=new Date(cy,cm,1);const sd=(first.getDay()+6)%7; const dim=new Date(cy,cm+1,0).getDate();const pd=new Date(cy,cm,0).getDate(); const now=new Date(); for(let i=0;i11){cm=0;cy++;}if(cm<0){cm=11;cy--;}renderMiniCal();} renderMiniCal(); // ---- GOOGLE CALENDAR OAuth ---- let gcalInited=false; const GCAL_CLIENT_ID='810915699555-anqccesf1ihcprcdo64mj0bragpr0fgl.apps.googleusercontent.com'; function initGcal(){ loadGapiAndSignIn(GCAL_CLIENT_ID); } function loadGapiAndSignIn(clientId){ clientId=clientId||GCAL_CLIENT_ID; const s=document.createElement('script'); s.src='https://apis.google.com/js/api.js'; s.onload=()=>{ gapi.load('client:auth2',()=>{ gapi.client.init({clientId,scope:'https://www.googleapis.com/auth/calendar.readonly',discoveryDocs:['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest']}) .then(()=>{ const authInstance=gapi.auth2.getAuthInstance(); if(authInstance.isSignedIn.get()){showGcal();} else{authInstance.signIn().then(()=>showGcal()).catch(e=>{console.error(e);alert('Connexion annulée.');});} }).catch(e=>{console.error(e);alert('Erreur initialisation Google API. Vérifiez votre Client ID et les origines autorisées.');}); }); }; document.head.appendChild(s); } function showGcal(){ document.getElementById('gcalSub').textContent='maison.moineau.ipad@gmail.com'; gapi.client.calendar.events.list({ calendarId:'primary', timeMin:new Date().toISOString(), showDeleted:false,singleEvents:true, maxResults:30,orderBy:'startTime' }).then(r=>{ const events=r.result.items||[]; if(events.length===0){document.getElementById('gcalContent').innerHTML='
Aucun événement à venir.
';return;} let html='
'; events.forEach(ev=>{ const start=ev.start.dateTime||ev.start.date; const dt=new Date(start); const isAllDay=!ev.start.dateTime; const timeStr=isAllDay?dt.toLocaleDateString('fr-FR',{day:'numeric',month:'long'}):dt.toLocaleDateString('fr-FR',{day:'numeric',month:'long'})+' · '+dt.toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}); const color=ev.colorId?['#4a9eff','#3dd68c','#f07070','#f0b84a','#a78bfa','#ff9dcb'][parseInt(ev.colorId)%6]:'#4a9eff'; html+=`
${isAllDay?'📅':'🕐'}
${ev.summary||'Sans titre'}
${timeStr}
${ev.location?`
📍 ${ev.location}
`:''}
`; }); html+='
'; document.getElementById('gcalContent').innerHTML=html; }).catch(e=>{console.error(e);document.getElementById('gcalContent').innerHTML='
Erreur chargement calendrier.
';}); } // Auto-reconnect si Client ID dĂ©jĂ  sauvĂ© window.addEventListener('load',()=>{ // Auto-connect with pre-configured Client ID document.getElementById('gcalClientId').value=GCAL_CLIENT_ID; loadGapiAndSignIn(GCAL_CLIENT_ID); }); // ---- TODO ---- let todos=[ {id:1,text:'Acheter cadeau anniversaire LĂ©a',done:false,cat:'famille'}, {id:2,text:'Lait, Ɠufs, pain, yaourts',done:false,cat:'courses'}, {id:3,text:'Rappeler le mĂ©decin',done:false,cat:'urgent'}, {id:4,text:'Sortir les poubelles',done:true,cat:'famille'}, {id:5,text:'Commander livres Ă©cole',done:false,cat:'urgent'}, ]; let tFilt='all'; const catStyle={famille:'background:#1a2040;color:#4a9eff',courses:'background:#0d2010;color:#3dd68c',urgent:'background:#2d1010;color:#f07070',perso:'background:#1a1040;color:#a78bfa'}; const catLbl={famille:'Famille',courses:'Courses',urgent:'Urgent',perso:'Perso'}; function renderTodos(){ const list=document.getElementById('tdList');list.innerHTML=''; todos.filter(t=>tFilt==='all'||t.cat===tFilt).forEach(t=>{ const el=document.createElement('div');el.className='td-row'+(t.done?' done':''); el.innerHTML=`
${t.done?'✓':''}
${t.text}
${catLbl[t.cat]||t.cat}
×
`; list.appendChild(el); }); const total=todos.length,done=todos.filter(t=>t.done).length,urg=todos.filter(t=>t.cat==='urgent'&&!t.done).length,fam=todos.filter(t=>t.cat==='famille').length,cour=todos.filter(t=>t.cat==='courses').length; document.getElementById('tdTotal').textContent=total; document.getElementById('pDone').style.width=(total?Math.round(done/total*100):0)+'%';document.getElementById('pDoneV').textContent=done; document.getElementById('pUrg').style.width=(total?Math.round(urg/total*100):0)+'%';document.getElementById('pUrgV').textContent=urg; document.getElementById('pFam').style.width=(total?Math.round(fam/total*100):0)+'%';document.getElementById('pFamV').textContent=fam; document.getElementById('pCour').style.width=(total?Math.round(cour/total*100):0)+'%';document.getElementById('pCourV').textContent=cour; document.getElementById('hTasks').textContent=todos.filter(t=>!t.done).length; document.getElementById('hUrgent').textContent=urg+' urgente'+(urg>1?'s':''); } function addTodo(){const inp=document.getElementById('tdInp');const cat=document.getElementById('tdCat').value;if(!inp.value.trim())return;todos.unshift({id:Date.now(),text:inp.value,done:false,cat});inp.value='';renderTodos();} function togTodo(id){const t=todos.find(x=>x.id===id);if(t)t.done=!t.done;renderTodos();} function delTodo(id){todos=todos.filter(x=>x.id!==id);renderTodos();} function fTodo(el,cat){tFilt=cat;document.querySelectorAll('.fp').forEach(b=>b.classList.remove('active'));el.classList.add('active');renderTodos();} renderTodos(); // ---- CANVAS ---- let dCol='#ef4444',dSz=4,drawing=false,eraser=false,cinit=false,lx=0,ly=0; function initCanvas(){ if(cinit)return;cinit=true; const c=document.getElementById('dc'),ctx=c.getContext('2d'); c.width=c.offsetWidth||900;c.height=440;ctx.fillStyle='#fff';ctx.fillRect(0,0,c.width,c.height); function pos(e){const r=c.getBoundingClientRect();const s=e.touches?e.touches[0]:e;return[(s.clientX-r.left)*(c.width/r.width),(s.clientY-r.top)*(c.height/r.height)];} c.addEventListener('mousedown',e=>{drawing=true;[lx,ly]=pos(e);}); c.addEventListener('mousemove',e=>{if(!drawing)return;const[x,y]=pos(e);ctx.beginPath();ctx.moveTo(lx,ly);ctx.lineTo(x,y);ctx.strokeStyle=eraser?'#fff':dCol;ctx.lineWidth=eraser?dSz*4:dSz;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke();[lx,ly]=[x,y];}); c.addEventListener('mouseup',()=>drawing=false);c.addEventListener('mouseleave',()=>drawing=false); c.addEventListener('touchstart',e=>{e.preventDefault();drawing=true;[lx,ly]=pos(e);},{passive:false}); c.addEventListener('touchmove',e=>{e.preventDefault();if(!drawing)return;const[x,y]=pos(e);ctx.beginPath();ctx.moveTo(lx,ly);ctx.lineTo(x,y);ctx.strokeStyle=eraser?'#fff':dCol;ctx.lineWidth=eraser?dSz*4:dSz;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke();[lx,ly]=[x,y];},{passive:false}); c.addEventListener('touchend',()=>drawing=false); } function setDC(c,el){dCol=c;eraser=false;document.getElementById('erBtn').classList.remove('active');document.querySelectorAll('.csw').forEach(b=>b.classList.remove('active'));el.classList.add('active');} function setDS(s,el){dSz=s;document.querySelectorAll('.szb').forEach(b=>b.classList.remove('active'));el.classList.add('active');} function togEr(){eraser=!eraser;document.getElementById('erBtn').classList.toggle('active',eraser);} function clrDraw(){const c=document.getElementById('dc');const ctx=c.getContext('2d');ctx.fillStyle='#fff';ctx.fillRect(0,0,c.width,c.height);} function saveDraw(){const c=document.getElementById('dc');const a=document.createElement('a');a.download='dessin.png';a.href=c.toDataURL();a.click();} // ---- KIDS ACTIVITIES ---- const kidsActivities=[ {id:'dents',icon:'đŸŠ·',name:'Se brosser\nles dents',done:false,color:'#4d96ff'}, {id:'habiller',icon:'👗',name:"S'habiller\nseule",done:false,color:'#ff6b9d'}, {id:'lit',icon:'đŸ›ïž',name:'Faire\nson lit',done:false,color:'#a78bfa'}, {id:'jouets',icon:'🧾',name:'Ranger\nses jouets',done:false,color:'#ffd93d'}, {id:'bain',icon:'🛁',name:'Bain /\nDouche',done:false,color:'#6bcb77'}, {id:'legumes',icon:'đŸ„Š',name:'Manger ses\nlĂ©gumes',done:false,color:'#f0b84a'}, ]; // Web Audio for sounds const audioCtx=new (window.AudioContext||window.webkitAudioContext)(); function playCheckSound(){ const osc=audioCtx.createOscillator(); const gain=audioCtx.createGain(); osc.connect(gain);gain.connect(audioCtx.destination); osc.type='sine'; osc.frequency.setValueAtTime(523,audioCtx.currentTime); osc.frequency.setValueAtTime(659,audioCtx.currentTime+0.1); osc.frequency.setValueAtTime(784,audioCtx.currentTime+0.2); gain.gain.setValueAtTime(0.3,audioCtx.currentTime); gain.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.5); osc.start(audioCtx.currentTime);osc.stop(audioCtx.currentTime+0.5); } function playVictorySound(){ const notes=[523,659,784,1047,1319]; notes.forEach((freq,i)=>{ const osc=audioCtx.createOscillator(); const gain=audioCtx.createGain(); osc.connect(gain);gain.connect(audioCtx.destination); osc.type='sine'; osc.frequency.value=freq; const t=audioCtx.currentTime+i*0.12; gain.gain.setValueAtTime(0,t); gain.gain.linearRampToValueAtTime(0.35,t+0.05); gain.gain.exponentialRampToValueAtTime(0.001,t+0.4); osc.start(t);osc.stop(t+0.4); }); } // Confetti function launchConfetti(){ const canvas=document.getElementById('confettiCanvas'); canvas.style.display='block'; const ctx=canvas.getContext('2d'); canvas.width=window.innerWidth;canvas.height=window.innerHeight; const pieces=[]; const colors=['#ff6b9d','#ffd93d','#6bcb77','#4d96ff','#a78bfa','#f0b84a','#ff9f43']; for(let i=0;i<150;i++){ pieces.push({ x:Math.random()*canvas.width,y:-20, w:8+Math.random()*8,h:12+Math.random()*8, color:colors[Math.floor(Math.random()*colors.length)], vx:(Math.random()-0.5)*6,vy:3+Math.random()*5, rot:Math.random()*360,vr:(Math.random()-0.5)*8, opacity:1 }); } let frame=0; function draw(){ ctx.clearRect(0,0,canvas.width,canvas.height); let alive=false; pieces.forEach(p=>{ p.x+=p.vx;p.y+=p.vy;p.rot+=p.vr; if(frame>80)p.opacity-=0.015; if(p.opacity>0&&p.ya.id===actId); if(!act||act.done)return; // play sound try{playCheckSound();}catch(e){} act.done=true; // animate disappear then re-render const el=document.getElementById('kids-task-'+actId); if(el){ el.classList.add('disappearing'); setTimeout(()=>{renderKids();},620); }else{renderKids();} // check if all done const doneCount=kidsActivities.filter(a=>a.done).length; updateKidsCounter(doneCount); if(doneCount===kidsActivities.length){ setTimeout(()=>{ try{playVictorySound();}catch(e){} launchConfetti(); document.getElementById('congratsOverlay').classList.add('show'); },700); } } function updateKidsCounter(doneCount){ const total=kidsActivities.length; const pct=Math.round(doneCount/total*100); document.getElementById('kidsPbFill').style.width=pct+'%'; document.getElementById('kidsPbLabel').textContent=doneCount+' / '+total+' activitĂ©s'; document.getElementById('hKids').textContent=doneCount+'/'+total; document.getElementById('hKidsLabel').textContent=doneCount===total?'Tout fait ! 🎉':doneCount===0?'Pas encore commencĂ©':doneCount+' sur '+total+' ✓'; // right panel document.getElementById('kidsRpContent').innerHTML=kidsActivities.map(a=>`
${a.icon}${a.name.replace('\n',' ')}${a.done?'✅':'⬜'}
`).join(''); // done chips const done=kidsActivities.filter(a=>a.done); const doneRow=document.getElementById('kidsDoneRow'); const doneChips=document.getElementById('kidsDoneChips'); if(done.length>0){ doneRow.style.display='block'; doneChips.innerHTML=done.map(a=>`
${a.icon}${a.name.replace('\n',' ')}
`).join(''); }else{doneRow.style.display='none';} } function renderKids(){ const grid=document.getElementById('kidsGrid');grid.innerHTML=''; const pending=kidsActivities.filter(a=>!a.done); if(pending.length===0){grid.innerHTML='
🎉 Toutes les activitĂ©s sont terminĂ©es !
';return;} pending.forEach(a=>{ const el=document.createElement('div'); el.className='kids-task'; el.id='kids-task-'+a.id; el.style.borderColor=a.color+'44'; el.innerHTML=`
${a.icon}
${a.name.replace('\n','
')}
○
`; el.onclick=()=>tickActivity(a.id); grid.appendChild(el); }); updateKidsCounter(kidsActivities.filter(a=>a.done).length); } function resetKids(){ kidsActivities.forEach(a=>a.done=false); document.getElementById('congratsOverlay').classList.remove('show'); renderKids(); } function closeCongratsOverlay(){document.getElementById('congratsOverlay').classList.remove('show');} renderKids(); // ---- ETF ---- const etfData=[ {ticker:'EWLD',name:'MSCI World',price:35.42,prev:35.00,color:'#4a9eff',perf1m:3.1,ytd:6.4}, {ticker:'PAEEM',name:'Émergents',price:28.15,prev:28.03,color:'#a78bfa',perf1m:1.8,ytd:-0.9}, {ticker:'PANX',name:'Nasdaq 100',price:72.88,prev:73.10,color:'#f07070',perf1m:-1.2,ytd:4.2}, ]; let selEtf=0,selPer='1M'; function renderEtfCards(){ const c=document.getElementById('etfCards');c.innerHTML=''; etfData.forEach((e,i)=>{ const chg=e.price-e.prev,pct=(chg/e.prev*100); const el=document.createElement('div');el.className='etf-card'+(i===selEtf?' sel':''); el.style.borderTopColor=e.color; el.innerHTML=`
${e.ticker}
${e.name}
${e.price.toFixed(2)} €
${chg>=0?'+':''}${pct.toFixed(2)}%`; el.onclick=()=>{selEtf=i;renderEtfCards();renderEtfChart();};c.appendChild(el); }); const e=etfData[0];const chg=e.price-e.prev;const pct=(chg/e.prev*100); document.getElementById('hEtfVal').textContent=e.price.toFixed(2)+'€'; document.getElementById('hEtfChg').textContent=e.name+' '+(pct>=0?'+':'')+pct.toFixed(2)+'%'; document.getElementById('hEtfChg').className='kc '+(pct>=0?'up':'dn'); document.getElementById('etfTable').innerHTML=`${etfData.map(e=>``).join('')}
ETFPrix1MYTD
${e.name}${e.price.toFixed(2)}€${e.perf1m>=0?'+':''}${e.perf1m}%${e.ytd>=0?'+':''}${e.ytd}%
`; } function renderEtfChart(){ const e=etfData[selEtf];document.getElementById('etfChTitle').textContent=e.name+' — Cours'; const pts=selPer==='1M'?30:selPer==='3M'?90:selPer==='6M'?180:365; const data=[];let v=e.price*(0.90+Math.random()*0.04); for(let i=0;i[P+i*(W-2*P)/(data.length-1),H-P-(v-mn)/rng*(H-2*P)]); const path='M'+coords.map(c=>c[0].toFixed(1)+','+c[1].toFixed(1)).join('L'); const fill='M'+coords[0][0]+','+(H-P)+' L'+coords.map(c=>c[0].toFixed(1)+','+c[1].toFixed(1)).join('L')+' L'+coords[coords.length-1][0]+','+(H-P)+' Z'; document.getElementById('etfSvg').innerHTML=`