// app.jsx — App staff 4D Gym : design V2 + TOUS les outils de la V1 (actions fonctionnelles).
const { useState, useEffect, useRef } = React;
const ax = { fontFamily:'Archivo, sans-serif' };
const hk = { fontFamily:'"Hanken Grotesk", sans-serif' };
const mo = { fontFamily:'"Space Mono", monospace' };

// ---- Auth + API ----
const LS = { me:'4ds_me', code:'4ds_code', codeFor:'4ds_codeFor' };
const meName = () => localStorage.getItem(LS.me) || '';
// Vue admin "voir comme" : nom du membre dont on regarde l'interface (lecture seule).
// L'auth reste celle de l'admin (en-têtes), seul le "who" des données change.
const viewAsName = () => sessionStorage.getItem('4ds_viewas') || '';
const effName = () => viewAsName() || meName();
function authHeaders() { return { 'x-user': meName(), 'x-access-code': localStorage.getItem(LS.code) || '' }; }
async function api(path, opts = {}) {
  const r = await fetch(path, { ...opts, headers: { ...(opts.headers||{}), ...authHeaders() } });
  if (r.status === 401) { const e = new Error('auth'); e.auth = true; throw e; }
  if (!r.ok) throw new Error((await r.json().catch(()=>({}))).error || 'Erreur');
  return r.json();
}
async function act(id, action, payload = {}) {
  return api('/api/lead', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id, action, payload, who: meName() }) });
}

// ---- Dates ----
const pad = (n) => String(n).padStart(2,'0');
const todayISO = () => { const d=new Date(); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; };
const isSunday = (iso) => !!iso && new Date(iso+'T00:00:00').getDay()===0;
function plusDaysISO(n){ const d=new Date(); d.setDate(d.getDate()+n); if(d.getDay()===0) d.setDate(d.getDate()+1); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; }
// Rappel intra-journée : datetime ISO complet (avec heure) pour "rappeler aujourd'hui à Xh" / "dans Xh".
const todayAtISO = (h, m=0) => { const d=new Date(); d.setHours(h, m, 0, 0); return d.toISOString(); };
const inHoursISO = (h) => new Date(Date.now() + h*3600000).toISOString();
const hasTime = (iso) => typeof iso==='string' && iso.includes('T');
const fmtFR = (iso) => iso ? iso.slice(0,10).split('-').reverse().join('/') : '';
const ddmm = () => { const d=new Date(); return pad(d.getDate())+'/'+pad(d.getMonth()+1); };
const hhmm = () => { const d=new Date(); return pad(d.getHours())+'h'+pad(d.getMinutes()); };
const noteSign = () => (meName()||'Phoning') + ' · ' + ddmm() + ' ' + hhmm();

// ---- WhatsApp (mêmes messages que la V1) ----
const waNumber = (l) => {
  let d = (l.phone||'').replace(/\D/g,'');
  if (d.startsWith('0')) return '33'+d.slice(1);
  if (d.startsWith('33')) return d;
  return d ? '33'+d : '';
};
const waLink = (l, text) => 'https://wa.me/'+waNumber(l)+'?text='+encodeURIComponent(text);
const GYM_ADDR = '27 ancienne route de Caumont au Thor';
const WA_TEMPLATES = (l) => { const me = meName(); const p = (l.name||'').split(' ')[0]; return [
  ...(l.seanceProspect && l.seanceEssai ? [{ label:'📅 Confirmer la séance (Calendly)', text:`Salut ${p}, c'est ${me} de 4D Gym. J'ai bien vu ta réservation pour ta séance d'essai ${fmtSeance(l.seanceEssai)}, c'est tout bon 💪 On t'attend au ${GYM_ADDR}, viens comme tu es. À très vite !` }] : []),
  { label:'👋 Premier contact', text:`Salut ${p}, c'est ${me} de 4D Gym au Thor. Tu nous as laissé tes infos pour une séance d'essai, je reviens vers toi. On te la cale quand tu veux, tu penses à quel jour ?` },
  { label:'📞 Pas de réponse', text:`Salut ${p}, c'est ${me} de 4D Gym. J'ai essayé de t'appeler mais je t'ai loupé. Pas de pression, dis-moi quand je peux te rappeler et on en parle tranquille.` },
  { label:'🔁 Relance', text:`Salut ${p}, c'est ${me} de 4D Gym. Je reviens vers toi pour ta séance d'essai. Toujours motivé ? Si oui je te trouve un créneau qui t'arrange.` },
  { label:'✅ Confirmation', text:`Salut ${p}, c'est ${me}. C'est noté pour ta séance d'essai 💪 On t'attend au ${GYM_ADDR}, viens comme tu es. À très vite !` },
  { label:'📵 Absent (no-show)', text:`Salut ${p}, c'est ${me} de 4D Gym. On t'a loupé aujourd'hui, ça arrive. On la replace quand tu veux, je te garde ton créneau.` },
]; };

// ---- Mapping Notion -> design ----
function relWhen(iso) {
  if (!iso) return '';
  const d = new Date(iso+'T00:00:00'), now = new Date();
  const days = Math.round((new Date(now.toDateString()) - new Date(d.toDateString()))/86400000);
  if (days<=0) return "aujourd'hui"; if (days===1) return 'hier'; return `il y a ${days} j`;
}
// Date relative fine (horodatage complet) : "à l'instant", "il y a 12 min", "il y a 3 h", "hier 14h02"...
function relTime(iso) {
  if (!iso) return 'jamais';
  const d = new Date(iso), now = new Date(); const s = Math.floor((now - d)/1000);
  if (s < 60) return "à l'instant";
  if (s < 3600) return `il y a ${Math.floor(s/60)} min`;
  if (s < 86400 && d.toDateString()===now.toDateString()) return `aujourd'hui ${String(d.getHours()).padStart(2,'0')}h${String(d.getMinutes()).padStart(2,'0')}`;
  const days = Math.round((new Date(now.toDateString()) - new Date(d.toDateString()))/86400000);
  if (days===1) return `hier ${String(d.getHours()).padStart(2,'0')}h${String(d.getMinutes()).padStart(2,'0')}`;
  if (days < 7) return `il y a ${days} j`;
  return d.toLocaleDateString('fr-FR', { day:'2-digit', month:'2-digit' });
}
const ADMIN = 'Ismail';
function mapStatus(s){ if(s==='Nouveau')return'nouveau'; if(s==='RDV pris')return'essai'; if(s==='Appelé')return'contacte'; return'contacte'; }
function mapLead(l){ return { id:l.id, name:[l.prenom,l.nom].filter(Boolean).join(' ')||'Lead', plan:l.source||'Lead', status:mapStatus(l.statut), statutRaw:l.statut||'', when:relWhen(l.dateEntree), phone:l.telephone||'', relanceLe:l.relanceLe||'', urgent:l.statut==='Nouveau', converted:l.conversion==='Oui', convType:l.convType||'', convPar:l.convPar||'', seanceEssai:l.seanceEssai||'', seanceProspect:!!l.seanceProspect, assigne:l.assigne||'', dateEntree:l.dateEntree||'' }; }
const fmtSeance = (iso)=>{ if(!iso) return ''; const d=new Date(iso); return d.toLocaleDateString('fr-FR',{weekday:'short',day:'2-digit',month:'2-digit'})+' à '+d.toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}).replace(':','h'); };

// ---- Couleur d'accent de l'app (persistante) ----
function hexRgba(hex, a){ const h=hex.replace('#',''); const r=parseInt(h.slice(0,2),16),g=parseInt(h.slice(2,4),16),b=parseInt(h.slice(4,6),16); return `rgba(${r},${g},${b},${a})`; }
// Mélange une base sombre avec la couleur d'accent (t = dose d'accent) -> fond/cartes teintés.
function tint(baseHex, accHex, t){ const b=baseHex.replace('#',''), a=accHex.replace('#',''); const f=(i)=>Math.round(parseInt(b.substr(i,2),16)*(1-t)+parseInt(a.substr(i,2),16)*t); const h=(n)=>n.toString(16).padStart(2,'0'); return '#'+h(f(0))+h(f(2))+h(f(4)); }
const ACCENTS = [
  { name:'Blanc', hex:'#F3F5F1', ink:'#0C0D10' },
  { name:'Rose', hex:'#EC4899', ink:'#ffffff' },
  { name:'Bleu', hex:'#2563EB', ink:'#ffffff' },
];
const DEFAULT_ACCENT = { name:'Bleu', hex:'#2563EB', ink:'#ffffff' };
function lum(hex){ const h=hex.replace('#',''); const r=parseInt(h.slice(0,2),16),g=parseInt(h.slice(2,4),16),b=parseInt(h.slice(4,6),16); return (0.299*r+0.587*g+0.114*b)/255; }
const getMode = () => localStorage.getItem('4ds_mode') || 'night';
const currentAccent = () => { try{ const a=JSON.parse(localStorage.getItem('4ds_accent')||'null'); return (a && ACCENTS.some(x=>x.name===a.name)) ? a : DEFAULT_ACCENT; }catch(_){ return DEFAULT_ACCENT; } };

// Applique le thème complet : couleur d'accent + mode (jour/nuit).
function applyTheme(a, mode){
  const s=document.documentElement.style;
  const day = mode==='day';
  // En mode jour, une couleur d'accent trop claire devient sombre (sinon illisible sur fond clair).
  const eff = (day && lum(a.hex)>0.62) ? '#17181C' : (!day && lum(a.hex)<0.10 ? '#F3F5F1' : a.hex);
  const ink = lum(eff)>0.6 ? '#0C0D10' : '#ffffff';
  s.setProperty('--volt', eff);
  s.setProperty('--volt-ink', ink);
  s.setProperty('--volt-dim', hexRgba(eff, day?0.14:0.16));
  s.setProperty('--volt-line', hexRgba(eff, day?0.5:0.36));
  // amber (état "en attente") par défaut, surchargé par les palettes sociales
  s.setProperty('--amber', '#F6C254');
  s.setProperty('--amber-dim', 'rgba(246,194,84,0.14)');
  if(day){
    s.setProperty('--bg', tint('#F2F3F0', a.hex, 0.04));
    s.setProperty('--surface', '#ffffff');
    s.setProperty('--elev', '#ffffff');
    s.setProperty('--line', 'rgba(12,13,16,0.10)');
    s.setProperty('--text', '#0C0D10');
    s.setProperty('--muted', 'rgba(12,13,16,0.56)');
    s.setProperty('--faint', 'rgba(12,13,16,0.40)');
    s.setProperty('--navbg', 'rgba(255,255,255,0.88)');
    s.setProperty('--track', 'rgba(12,13,16,0.08)');
    // pas de palette : le fond et les cartes sont du même monde -> texte sur fond = texte des cartes
    s.setProperty('--on-bg', '#0C0D10');
    s.setProperty('--on-bg-muted', 'rgba(12,13,16,0.56)');
  } else {
    s.setProperty('--bg', tint('#0A0B0D', a.hex, 0.06));
    s.setProperty('--surface', tint('#15171C', a.hex, 0.10));
    s.setProperty('--elev', tint('#1C1F26', a.hex, 0.13));
    s.setProperty('--line', hexRgba(a.hex, 0.16));
    s.setProperty('--text', '#F3F5F1');
    s.setProperty('--muted', 'rgba(243,245,241,0.56)');
    s.setProperty('--faint', 'rgba(243,245,241,0.34)');
    s.setProperty('--navbg', 'rgba(12,13,16,0.92)');
    s.setProperty('--track', 'rgba(255,255,255,0.08)');
    s.setProperty('--on-bg', '#F3F5F1');
    s.setProperty('--on-bg-muted', 'rgba(243,245,241,0.56)');
  }
}

// ---- Palettes "réseaux" (thème complet : fond coloré + cartes claires). 2 niveaux de texte. ----
const PALETTES = [
  { id:'instagram', name:'Instagram', dot:'linear-gradient(135deg,#FEDA75,#FA7E1E,#D62976,#962FBF,#4F5BD5)',
    bg:'linear-gradient(160deg,#FEDA75 0%,#FA7E1E 20%,#D62976 50%,#962FBF 75%,#4F5BD5 100%)', onBg:'#FFFFFF', onBgMuted:'rgba(255,255,255,.82)',
    surface:'#FFFFFF', text:'#262626', muted:'rgba(38,38,38,.56)', faint:'rgba(38,38,38,.36)', line:'rgba(0,0,0,.08)',
    accent:'#E1306C', accentDim:'rgba(225,48,108,.12)', amber:'#C77A0A', navBg:'rgba(255,255,255,.92)' },
  { id:'facebook', name:'Facebook', dot:'#1877F2',
    bg:'#1877F2', onBg:'#FFFFFF', onBgMuted:'rgba(255,255,255,.82)',
    surface:'#FFFFFF', text:'#050505', muted:'rgba(5,5,5,.55)', faint:'rgba(5,5,5,.36)', line:'rgba(0,0,0,.08)',
    accent:'#1877F2', accentDim:'rgba(24,119,242,.12)', amber:'#C77A0A', navBg:'rgba(255,255,255,.95)' },
  { id:'snapchat', name:'Snapchat', dot:'#FFFC00',
    bg:'#FFFC00', onBg:'#0B0B0B', onBgMuted:'rgba(11,11,11,.66)',
    surface:'#FFFFFF', text:'#111111', muted:'rgba(17,17,17,.56)', faint:'rgba(17,17,17,.36)', line:'rgba(0,0,0,.08)',
    accent:'#0B0B0B', accentDim:'rgba(255,252,0,.7)', amber:'#B07A1E', navBg:'rgba(255,255,255,.95)' },
  { id:'whatsapp', name:'WhatsApp', dot:'#128C7E',
    bg:'#0B5C50', onBg:'#FFFFFF', onBgMuted:'rgba(255,255,255,.8)',
    surface:'#FFFFFF', text:'#0B141A', muted:'rgba(11,20,26,.55)', faint:'rgba(11,20,26,.36)', line:'rgba(0,0,0,.08)',
    accent:'#128C7E', accentDim:'rgba(18,140,126,.13)', amber:'#C77A0A', navBg:'rgba(255,255,255,.95)' },
  { id:'tiktok', name:'TikTok', dot:'linear-gradient(135deg,#FE2C55,#000,#25F4EE)',
    bg:'#000000', onBg:'#FFFFFF', onBgMuted:'rgba(255,255,255,.62)',
    surface:'#161616', text:'#FFFFFF', muted:'rgba(255,255,255,.56)', faint:'rgba(255,255,255,.36)', line:'rgba(255,255,255,.1)',
    accent:'#FE2C55', accentDim:'rgba(254,44,85,.16)', amber:'#F6C254', navBg:'rgba(0,0,0,.9)' },
];
function applyPalette(p){
  const s=document.documentElement.style;
  const ink = lum((p.accent||'#000').replace ? p.accent : '#000') > 0.6 ? '#0C0D10' : '#ffffff';
  s.setProperty('--bg', p.bg);
  s.setProperty('--surface', p.surface);
  s.setProperty('--elev', p.surface);
  s.setProperty('--line', p.line);
  s.setProperty('--text', p.text);
  s.setProperty('--muted', p.muted);
  s.setProperty('--faint', p.faint);
  s.setProperty('--on-bg', p.onBg);
  s.setProperty('--on-bg-muted', p.onBgMuted);
  s.setProperty('--navbg', p.navBg);
  s.setProperty('--track', hexRgba(p.text.length>=7?p.text:'#000000', 0.08));
  s.setProperty('--amber', p.amber);
  s.setProperty('--amber-dim', hexRgba(p.amber, 0.14));
  s.setProperty('--volt', p.accent);
  s.setProperty('--volt-ink', ink);
  s.setProperty('--volt-dim', p.accentDim);
  s.setProperty('--volt-line', hexRgba(p.accent, 0.45));
}
const currentPalette = () => { const id=localStorage.getItem('4ds_palette'); return id ? PALETTES.find(p=>p.id===id) : null; };
function setPalette(p){ if(p){ localStorage.setItem('4ds_palette', p.id); applyPalette(p); } else { localStorage.removeItem('4ds_palette'); applyTheme(currentAccent(), getMode()); } }
function applyAccent(a){ localStorage.removeItem('4ds_palette'); applyTheme(a, getMode()); localStorage.setItem('4ds_accent', JSON.stringify(a)); }
function setMode(m){ localStorage.setItem('4ds_mode', m); localStorage.removeItem('4ds_palette'); applyTheme(currentAccent(), m); }
async function setGlobalAccent(a){ applyAccent(a); try{ await api('/api/accent', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ accent:a }) }); }catch(_){} }
// Thèmes "réseaux" (PALETTES/applyPalette ci-dessus) gardés dormants : pas exposés dans l'UI pour l'instant,
// réactivables plus tard (cf. note mémoire). Version de base = couleur d'accent + mode jour/nuit.
(function boot(){ localStorage.removeItem('4ds_palette'); applyTheme(currentAccent(), getMode()); })();

// ════════ UI de base ════════
function Screen({ children, active, go }) {
  return <div style={{ height:'100%', display:'flex', flexDirection:'column', background:T.bg, color:T.text, ...hk }}>
    <div style={{ flex:1, overflowY:'auto', paddingTop:'max(20px, env(safe-area-inset-top))' }}>{children}</div>
    <BottomNav active={active} onNav={go} />
  </div>;
}
const SectionLabel = ({ children, action, onAction }) => (
  <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', margin:'0 0 12px' }}>
    <span style={{ ...mo, fontSize:11, letterSpacing:1, textTransform:'uppercase', color:T.onBgMuted }}>{children}</span>
    {action && <span onClick={onAction} style={{ ...hk, fontSize:13, color:T.volt, fontWeight:600, cursor:'pointer' }}>{action}</span>}
  </div>
);
const btn = (bg, color, extra={}) => ({ border:'none', cursor:'pointer', borderRadius:13, padding:'13px 0', ...hk, fontSize:14, fontWeight:700, background:bg, color, display:'flex', alignItems:'center', justifyContent:'center', gap:7, ...extra });
// Vert "conversion" pour les boutons d'action principaux (indépendant du thème).
const GREEN = '#22C55E', GINK = '#06270F';
const gbtn = (extra={}) => btn(GREEN, GINK, extra);
const obtn = { border:`1px solid ${T.line}`, cursor:'pointer', borderRadius:13, padding:'12px 0', ...hk, fontSize:13.5, fontWeight:600, background:'transparent', color:T.text };

// Boutons d'action d'un lead (tous les outils de la V1)
function LeadButtons({ lead, H }) {
  return <div>
    <div style={{ display:'flex', gap:12, marginBottom:14 }}>
      <button onClick={()=>H.call(lead)} style={{ flex:1, ...gbtn({ padding:'15px 0' }) }}><Icon name="phone" size={18} color={GINK} />Appeler</button>
      <button onClick={()=>H.wa(lead)} style={{ flex:1, ...btn('#1Fae5a','#fff', { padding:'15px 0' }) }}>📲 WhatsApp</button>
    </div>
    {lead.converted
      ? <div style={{ marginBottom:14 }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, background:'rgba(34,197,94,0.14)', border:'1px solid rgba(34,197,94,0.4)', borderRadius:12, padding:'10px 12px', marginBottom:8, ...hk, fontSize:13, fontWeight:600 }}>
            <span style={{ color:GREEN }}>✅ Converti</span>
            <span style={{ color:T.muted }}>{lead.convType?`· ${lead.convType}`:''}{lead.convPar?` · par ${lead.convPar}`:''}</span>
          </div>
          <button onClick={()=>H.deconv(lead)} style={{ width:'100%', ...obtn, color:'#ff8a8a', borderColor:'rgba(255,138,138,0.4)' }}>↩️ Annuler la conversion</button>
        </div>
      : <button onClick={()=>H.conv(lead)} style={{ width:'100%', ...btn('linear-gradient(135deg,#f59e0b,#fcd34d)','#1a1300', { padding:'15px 0' }), marginBottom:14, fontWeight:800 }}>💰 Converti</button>}
    <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
      <button onClick={()=>H.open(lead,'rdv')} style={obtn}>✅ RDV pris</button>
      <button onClick={()=>H.open(lead,'relance')} style={obtn}>🔁 À relancer</button>
      <button onClick={()=>H.noAnswer(lead)} style={{ ...obtn, gridColumn:'1 / span 2', color:T.amber, borderColor:'rgba(246,194,84,0.4)' }}>📵 Pas de réponse · rappeler dans 2h</button>
      <button onClick={()=>H.open(lead,'pasinteresse')} style={{ ...obtn, color:'#ff8a8a' }}>✖ Pas intéressé</button>
      <button onClick={()=>H.open(lead,'note')} style={obtn}>💬 Note</button>
      <button onClick={()=>H.mauvais(lead)} style={{ ...obtn, gridColumn:'1 / span 2', color:T.muted }}>📵 Mauvais numéro</button>
    </div>
  </div>;
}

// Compteur animé (la cagnotte qui monte).
function useCountUp(target, dur=1000){
  const [v,setV]=useState(0);
  useEffect(()=>{ let raf; const t0=performance.now();
    const tick=(now)=>{ const p=Math.min(1,(now-t0)/dur); setV(Math.round(target*(1-Math.pow(1-p,3)))); if(p<1) raf=requestAnimationFrame(tick); };
    raf=requestAnimationFrame(tick); return ()=>cancelAnimationFrame(raf);
  },[target]); return v;
}

// Carte cagnotte premium : relief, contour gradient animé, reflet, compteur qui monte.
const MOIS_FR = ['JANVIER','FÉVRIER','MARS','AVRIL','MAI','JUIN','JUILLET','AOÛT','SEPTEMBRE','OCTOBRE','NOVEMBRE','DÉCEMBRE'];
function CagnotteCard({ validee=0, attente=0, goal=300, avis=0, growth=null }){
  const val = useCountUp(validee); // le GROS chiffre = la prime réelle (validée), pas le potentiel
  const pctVal = Math.min(100, (validee/Math.max(1,goal))*100);
  const pctAtt = Math.min(100-pctVal, (attente/Math.max(1,goal))*100);
  const reste = Math.max(0, goal - validee);
  const done = validee>=goal;
  const mois = MOIS_FR[new Date().getMonth()];
  return <div className="cag-wrap" data-tour="cagnotte">
    <div className="cag-card">
      <div className="cag-shine" />
      <div className="cag-coin" style={{ top:56, right:20, fontSize:54 }}>💰</div>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
        <span style={{ ...mo, fontSize:11, letterSpacing:1.5, textTransform:'uppercase', color:'rgba(255,255,255,0.6)' }}>Prime du mois · {mois}</span>
        {growth!=null && growth>0 && <span style={{ ...hk, fontSize:12, fontWeight:800, padding:'3px 9px', borderRadius:999, color:'#06270F', background:GREEN }}>+{growth}%</span>}
      </div>
      <div className="cag-amount" style={ax}>{val.toLocaleString('fr-FR')} €</div>
      <div style={{ display:'flex', gap:16, margin:'2px 0 12px' }}>
        <span style={{ ...hk, fontSize:12.5, color:'rgba(255,255,255,0.85)', display:'flex', alignItems:'center', gap:6 }}>
          <i style={{ width:9, height:9, borderRadius:999, background:GREEN, boxShadow:'0 0 8px rgba(34,197,94,.6)' }} /> Validée <b style={{ color:'#fff' }}>{validee} €</b>
        </span>
        <span style={{ ...hk, fontSize:12.5, color:'rgba(255,255,255,0.85)', display:'flex', alignItems:'center', gap:6 }}>
          <i style={{ width:9, height:9, borderRadius:999, background:'rgba(250,204,21,0.85)' }} /> En attente <b style={{ color:'#fff' }}>{attente} €</b>
        </span>
      </div>
      <div className="cag-bar" style={{ display:'flex' }}>
        <i style={{ width:`${pctVal}%`, borderRadius:0, background:'linear-gradient(90deg,#16A34A,#22C55E)', boxShadow:'0 0 14px rgba(34,197,94,.6)' }} />
        <i style={{ width:`${pctAtt}%`, borderRadius:0, background:'linear-gradient(90deg,#FACC15,#FDE68A)', boxShadow:'none' }} />
      </div>
      <div style={{ ...hk, fontSize:12.5, color:'rgba(255,255,255,0.62)', marginTop:10 }}>
        Objectif {goal} €{done ? ' · atteint 🎉' : ` · plus que ${reste} €`}
      </div>
      {avis>0 && <div style={{ ...hk, fontSize:11.5, color:'rgba(203,251,69,0.85)', marginTop:4 }}>⭐ dont {avis} € de primes avis Google (déjà validé)</div>}
    </div>
  </div>;
}

// Hero de taux (% RDV pris, % convertis sur RDV) — repris du bilan V1.
function RateHero({ pct, cap, sub, warn }){
  return <div style={{ flex:1, background:T.surface, border:`1px solid ${warn?'#ef4444':T.line}`, borderRadius:18, padding:'16px 14px', textAlign:'center' }}>
    <div style={{ ...ax, fontSize:38, fontWeight:900, lineHeight:1, color:warn?'#f87171':T.text }}>{pct}<span style={{ fontSize:17, opacity:0.6 }}>%</span></div>
    <div style={{ ...hk, fontSize:12, color:T.muted, fontWeight:700, marginTop:3 }}>{cap}</div>
    <div style={{ height:6, borderRadius:999, background:T.line, margin:'10px 0 7px', overflow:'hidden' }}><i style={{ display:'block', height:'100%', borderRadius:999, width:Math.min(pct,100)+'%', background:warn?'#f87171':GREEN }} /></div>
    <div style={{ ...hk, fontSize:11, color:T.faint }}>{sub}</div>
  </div>;
}

// ════════ ACCUEIL — bilan du vendeur (inspiré V1) ════════
function ScreenDashboard({ d, go, H }) {
  const s = d.stats || {};
  const warn = s.rdv>=3 && (s.pctRdv - s.pctRdvConv) >= 25; // beaucoup de RDV mais peu convertis
  const t = Math.max(s.total||0, 1);
  // Convertis = vraies conversions du mois (Converti par), pas seulement les leads assignés récents
  const convN = s.closes||0;
  const wRdv = Math.round(((s.rdv||0)/t)*100), wConv = Math.min(100, Math.round((convN/t)*100));
  const fn = (lbl, w, n, color, cat) => (
    <div onClick={cat?()=>H.openCat(cat, lbl):undefined} style={{ display:'flex', alignItems:'center', gap:10, marginBottom:9, cursor:cat?'pointer':'default' }}>
      <span style={{ ...hk, fontSize:12, fontWeight:700, color:T.muted, width:74, flex:'0 0 auto' }}>{lbl}{cat?' ›':''}</span>
      <div style={{ flex:1, background:T.line, borderRadius:999, height:16, overflow:'hidden' }}><i style={{ display:'block', height:'100%', borderRadius:999, width:Math.max(w,3)+'%', background:color }} /></div>
      <span style={{ ...ax, fontSize:13, fontWeight:900, width:42, textAlign:'right', flex:'0 0 auto' }}>{n}</span>
    </div>
  );
  return <Screen active="home" go={go}>
    <div style={{ padding:'0 20px 24px' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:20 }}>
        <div><div style={{ ...hk, fontSize:14, color:T.onBgMuted }}>Salut,</div><div style={{ ...ax, fontSize:26, fontWeight:800, letterSpacing:-0.5, color:T.onBg }}>{d.me} 4D</div></div>
        <Avatar name={d.me} size={44} ring />
      </div>

      <CagnotteCard validee={d.cagnotte} attente={s.potentiel||0} goal={d.goal} avis={d.reviews?.prime||0} growth={s.growth} />

      {s.doubleFrom > 0 && (d.inscrits >= s.doubleFrom
        ? <div style={{ ...hk, fontSize:12.5, fontWeight:800, color:'#06270F', background:GREEN, borderRadius:12, padding:'10px 12px', margin:'14px 0 4px', textAlign:'center' }}>🔥 Bonus actif : tes primes sont DOUBLÉES (dès {s.doubleFrom} inscriptions)</div>
        : <div style={{ ...hk, fontSize:12, fontWeight:700, color:T.muted, background:T.surface, border:`1px solid ${T.line}`, borderRadius:12, padding:'10px 12px', margin:'14px 0 4px', textAlign:'center' }}>🔥 Plus que <b style={{ color:T.text }}>{s.doubleFrom - d.inscrits}</b> inscriptions pour débloquer la prime <b style={{ color:T.text }}>×2</b></div>)}

      <div data-tour="rates" style={{ display:'flex', gap:12, margin:'14px 0 12px' }}>
        <RateHero pct={s.pctRdv||0} cap="RDV pris" sub={`${s.rdv||0} sur ${s.traites||0} traités`} />
        <RateHero pct={s.pctRdvConv||0} cap="convertis sur RDV" sub={`${s.oui||0} sur ${s.rdv||0} RDV`} warn={warn} />
      </div>
      {warn && <div style={{ ...hk, fontSize:12, fontWeight:700, color:'#f87171', background:'rgba(239,68,68,0.12)', border:'1px solid rgba(239,68,68,0.3)', borderRadius:12, padding:'9px 12px', marginBottom:12, textAlign:'center' }}>⚠ Beaucoup de RDV mais peu de conversions, le closing est à travailler.</div>}

      <div data-tour="funnel" style={{ background:T.surface, border:`1px solid ${T.line}`, borderRadius:16, padding:14, marginBottom:12 }}>
        <div style={{ ...mo, fontSize:10.5, letterSpacing:1, color:T.faint, textTransform:'uppercase', marginBottom:11 }}>Entonnoir</div>
        {fn('Leads', 100, s.total||0, T.text)}
        {fn('RDV pris', wRdv, s.rdv||0, GREEN, 'rdv')}
        {fn('Convertis', wConv, convN, '#FACC15', 'convertis')}
      </div>

      <div data-tour="stats" style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:10, marginBottom:18 }}>
        {[['Leads actifs', d.leads.length, false, 'actifs'],['En cours', s.enCours||0, false, 'encours'],['Perdus', s.perdus||0, true, 'perdus']].map(([k,v,danger,cat])=>(
          <div key={k} onClick={()=> cat==='actifs' ? H.openCat('actifs', 'Leads à appeler', d.leads) : H.openCat(cat, k)}
            style={{ background:T.surface, borderRadius:14, padding:'13px 10px', textAlign:'center', border:`1px solid ${danger?'rgba(239,68,68,0.3)':T.line}`, cursor:'pointer' }}>
            <div style={{ ...ax, fontSize:22, fontWeight:900, color:danger?'#f87171':T.text }}>{v}</div>
            <div style={{ ...hk, fontSize:11, color:T.muted, marginTop:2 }}>{k} ›</div>
          </div>
        ))}
      </div>

      <div data-tour="avis"><AvisCard r={d.reviews} /></div>
      <div data-tour="scratch-card"><ScratchCard name={d.me} /></div>
    </div>
  </Screen>;
}

// ════════ LEADS — Nouveaux (urgent) + Relances + recherche ════════
const RED = '#ff4d4d';
function LeadRow({ lead, urgent, onClick, assignee }) {
  return <div onClick={onClick} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 14px', marginBottom:10, cursor:'pointer',
    background:T.surface, borderRadius:14, border: urgent?`1.5px solid ${RED}`:`1px solid ${T.line}`, boxShadow: urgent?`0 0 0 3px rgba(255,77,77,0.12)`:'none' }}>
    <Avatar name={lead.name} size={40} />
    <div style={{ flex:1, minWidth:0 }}>
      <div style={{ ...hk, fontSize:15, fontWeight:600, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{lead.name}</div>
      <div style={{ ...hk, fontSize:12, color:T.muted }}>{assignee ? `👤 ${assignee}${lead.when?` · ${lead.when}`:''}` : lead.plan}</div>
    </div>
    {lead.converted
      ? <span style={{ ...mo, fontSize:10, fontWeight:700, color:'#15803d', background:'rgba(34,197,94,0.16)', padding:'4px 9px', borderRadius:999 }}>✅ CONVERTI</span>
      : lead.seanceEssai
      ? <span style={{ ...mo, fontSize:10, fontWeight:700, color:T.amber, background:T.amberDim, padding:'4px 9px', borderRadius:999, whiteSpace:'nowrap' }}>📅 {fmtSeance(lead.seanceEssai)}</span>
      : lead.relanceLe
      ? <span style={{ ...mo, fontSize:10, fontWeight:700, color:T.blue, background:T.blueDim, padding:'4px 9px', borderRadius:999, whiteSpace:'nowrap' }}>🔁 {fmtFR(lead.relanceLe)}{hasTime(lead.relanceLe)?' '+new Date(lead.relanceLe).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}).replace(':','h'):''}</span>
      : urgent
      ? <span style={{ ...mo, fontSize:10, fontWeight:700, letterSpacing:0.5, color:'#fff', background:RED, padding:'4px 9px', borderRadius:999 }}>🚨 NOUVEAU</span>
      : <span style={{ ...mo, fontSize:10, fontWeight:700, color:T.muted, background:T.line, padding:'4px 9px', borderRadius:999 }}>{lead.statutRaw||'—'}</span>}
  </div>;
}

function ScreenLeads({ d, go, H, ver }) {
  const [q, setQ] = useState('');
  const [res, setRes] = useState(null);
  const [day, setDay] = useState(()=>todayISO()); // défaut : relances DU JOUR (tâches du jour). 'all' | date ISO
  useEffect(()=>{ if(q.trim().length<2){ setRes(null); return; } const t=setTimeout(async()=>{
    try{ const out=await api('/api/search?q='+encodeURIComponent(q)); setRes((out.leads||[]).map(mapLead)); }catch{ setRes([]); }
  },300); return ()=>clearTimeout(t); },[q, ver]);

  const searching = q.trim().length>=2;
  const me = d.me, today = todayISO();
  const tomorrow = (()=>{ const t=new Date(); t.setDate(t.getDate()+1); return `${t.getFullYear()}-${pad(t.getMonth()+1)}-${pad(t.getDate())}`; })();
  // Séances fixées (Calendly) à venir -> section dédiée, sorties des "Nouveaux"/"Relances"
  // Uniquement les séances RÉSERVÉES PAR LE PROSPECT (Calendly) -> à confirmer. (Celles fixées par l'employé = RDV pris, pas ici.)
  const seances = d.leads.filter(l=> l.seanceProspect && l.seanceEssai && l.seanceEssai.slice(0,10) >= today).sort((a,b)=>a.seanceEssai.localeCompare(b.seanceEssai));
  const seanceIds = new Set(seances.map(l=>l.id));
  const nouveaux = d.leads.filter(l=>l.urgent && !seanceIds.has(l.id));
  const relancesAll = d.leads.filter(l=>!l.urgent && !seanceIds.has(l.id));
  // À RAPPELER MAINTENANT : relances avec une HEURE déjà passée aujourd'hui (rappel intra-journée dû). Urgent.
  const nowMs = Date.now();
  const aRappeler = relancesAll.filter(l=> hasTime(l.relanceLe) && new Date(l.relanceLe).getTime() <= nowMs).sort((a,b)=>a.relanceLe.localeCompare(b.relanceLe));
  const aRappelerIds = new Set(aRappeler.map(l=>l.id));
  const relancesPipe = relancesAll.filter(l=> !aRappelerIds.has(l.id));
  // FILET A REPRENDRE : leads "Nouveau" assignés à un AUTRE conseiller, entrés avant aujourd'hui (pas traités) -> anti-perte si l'assigné est absent
  const aReprendre = (d.allLeads||[]).filter(l=> l.urgent && !l.seanceEssai && l.assigne && l.assigne!==me && l.dateEntree && l.dateEntree.slice(0,10) < today);
  // Relances filtrées par le jour choisi, puis groupées par date
  const relances = day==='all' ? relancesPipe : relancesPipe.filter(l=> (l.relanceLe||'').slice(0,10)===day);
  const groups = (()=>{ const by={}; relances.forEach(l=>{ const k=(l.relanceLe||'').slice(0,10)||'zzz'; (by[k]=by[k]||[]).push(l); }); return Object.keys(by).sort().map(k=>({ date:k, leads:by[k] })); })();
  const dayLabel = (iso)=> iso==='zzz'?'Sans date' : iso===today?"Aujourd'hui" : iso===tomorrow?'Demain' : new Date(iso+'T00:00:00').toLocaleDateString('fr-FR',{ weekday:'short', day:'2-digit', month:'2-digit' });

  const Header = ({ red, label, count }) => (
    <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'10px 14px', borderRadius:12, marginBottom:12,
      background: red?RED:'var(--track)', color: red?'#fff':T.onBg }}>
      <span style={{ ...hk, fontWeight:800, fontSize:14 }}>{label}</span>
      <span style={{ ...mo, fontSize:12, fontWeight:700, background: red?'rgba(255,255,255,0.22)':'var(--track)', borderRadius:999, padding:'2px 9px' }}>{count}</span>
    </div>
  );
  const Chip = ({ id, label }) => (
    <button onClick={()=>setDay(id)} style={{ ...hk, fontSize:13, fontWeight:700, padding:'7px 14px', borderRadius:999, flexShrink:0,
      border:`1px solid ${day===id?'transparent':T.line}`, background: day===id?T.volt:'transparent', color: day===id?T.ink:T.onBgMuted, cursor:'pointer' }}>{label}</button>
  );
  const customDay = day!=='all' && day!==today && day!==tomorrow;

  return <Screen active="leads" go={go}>
    <div style={{ padding:'0 20px 20px' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:12 }}>
        <div style={{ ...ax, fontSize:26, fontWeight:800, letterSpacing:-0.5, color:T.onBg }}>Leads</div>
        <button onClick={()=>H.walkin()} style={{ ...btn(T.volt, 'var(--volt-ink)'), padding:'9px 14px', fontSize:13.5, fontWeight:800, borderRadius:999 }}>➕ Sur place</button>
      </div>
      <input value={q} onChange={e=>setQ(e.target.value)} placeholder="🔍 Retrouver un lead (nom ou téléphone)…"
        style={{ width:'100%', background:T.surface, border:`1px solid ${T.line}`, borderRadius:13, padding:'12px 14px', color:T.text, ...hk, fontSize:14, outline:'none', marginBottom:16 }} />

      {searching ? (
        <>
          <SectionLabel>{res?`${res.length} résultat${res.length>1?'s':''}`:'Recherche…'}</SectionLabel>
          {(res||[]).map(l=>(<LeadRow key={l.id} lead={l} urgent={l.urgent} onClick={()=>H.openLead(l, res)} />))}
          {res && res.length===0 && <div style={{ ...hk, fontSize:13, color:T.onBgMuted, padding:'12px 0' }}>Personne trouvé.</div>}
        </>
      ) : (!nouveaux.length && !relancesAll.length && !aReprendre.length && !seances.length) ? (
        <div style={{ padding:'40px 0', textAlign:'center', color:T.onBgMuted }}><div style={{ fontSize:40, marginBottom:10 }}>✅</div>Aucun lead à traiter. Beau boulot.</div>
      ) : (
        <>
          {aRappeler.length>0 && <>
            <Header red label="🔴 À rappeler maintenant" count={aRappeler.length} />
            <div style={{ ...hk, fontSize:12, color:T.muted, margin:'-4px 0 10px' }}>Rappels de la journée dont l'heure est passée. À faire en priorité.</div>
            {aRappeler.map(l=>(<LeadRow key={l.id} lead={l} urgent onClick={()=>H.openLead(l, aRappeler)} />))}
            <div style={{ height:10 }} />
          </>}
          {seances.length>0 && <>
            <Header label="📅 Séances fixées" count={seances.length} />
            <div style={{ ...hk, fontSize:12, color:T.muted, margin:'-4px 0 10px' }}>Réservées par le prospect (Calendly). Pas besoin d'appeler à froid : ouvre la fiche et confirme par message.</div>
            {seances.map(l=>(<LeadRow key={l.id} lead={l} urgent={false} onClick={()=>H.openLead(l, seances)} />))}
            <div style={{ height:10 }} />
          </>}
          {aReprendre.length>0 && <>
            <Header red label="🆘 À reprendre" count={aReprendre.length} />
            <div style={{ ...hk, fontSize:12, color:T.muted, margin:'-4px 0 10px' }}>Leads pas encore traités par leur conseiller. Reprends-les avant de les perdre.</div>
            {aReprendre.map(l=>(<LeadRow key={l.id} lead={l} urgent assignee={l.assigne} onClick={()=>H.openLead(l, aReprendre)} />))}
            <div style={{ height:10 }} />
          </>}

          <Header red label="🚨 Nouveaux à appeler" count={nouveaux.length} />
          {nouveaux.length ? nouveaux.map(l=>(<LeadRow key={l.id} lead={l} urgent onClick={()=>H.openLead(l, nouveaux)} />))
            : <div style={{ ...hk, fontSize:13, color:T.onBgMuted, padding:'4px 0 16px' }}>Rien de nouveau, attaque les relances.</div>}

          <div style={{ height:8 }} />
          <Header label="🔁 Relances" count={relancesPipe.length} />
          <div style={{ display:'flex', gap:7, overflowX:'auto', paddingBottom:6, marginBottom:14, WebkitOverflowScrolling:'touch' }}>
            <Chip id="all" label="Tout" />
            <Chip id={today} label="Aujourd'hui" />
            <Chip id={tomorrow} label="Demain" />
            <label style={{ ...hk, fontSize:13, fontWeight:700, padding:'7px 14px', borderRadius:999, flexShrink:0, position:'relative', cursor:'pointer',
              border:`1px solid ${customDay?'transparent':T.line}`, background: customDay?T.volt:'transparent', color: customDay?T.ink:T.onBgMuted }}>
              📅 {customDay?fmtFR(day):'Un jour'}
              <input type="date" onChange={e=>e.target.value&&setDay(e.target.value)} style={{ position:'absolute', inset:0, opacity:0, cursor:'pointer' }} />
            </label>
          </div>
          {groups.length ? groups.map(g=>(
            <div key={g.date}>
              <div style={{ display:'flex', alignItems:'center', gap:8, margin:'14px 0 8px' }}>
                <span style={{ ...mo, fontSize:11.5, letterSpacing:0.5, textTransform:'uppercase', fontWeight:800, color:T.onBg }}>{dayLabel(g.date)}</span>
                {g.date!=='zzz' && <span style={{ ...hk, fontSize:11, color:T.faint }}>{fmtFR(g.date)}</span>}
                <span style={{ flex:1, height:1, background:T.line }} />
                <span style={{ ...mo, fontSize:11, fontWeight:700, color:T.muted }}>{g.leads.length}</span>
              </div>
              {g.leads.map(l=>(<LeadRow key={l.id} lead={l} urgent={false} onClick={()=>H.openLead(l, relances)} />))}
            </div>
          )) : <div style={{ ...hk, fontSize:13, color:T.faint, padding:'4px 0' }}>Aucune relance{day!=='all'?' ce jour-là':''}.</div>}
        </>
      )}
    </div>
  </Screen>;
}

// ════════ PRIMES ════════
function WalletInner({ d, H }) {
  const [filter, setFilter] = useState('tout');
  const shown = d.conversions.filter(l => filter==='tout' || (filter==='valide' ? l.paid : !l.paid));
  // Conversion -> objet "lead" pour ouvrir la fiche (notes, annuler conversion, etc.).
  const asLead = (l)=> ({ id:l.id, name:l.name, phone:l.phone||'', converted:true, convType:(l.type==='abo'?'Abonnement MRR':l.type==='comptant'?'Comptant':''), convPar:l.convPar||'', statutRaw:l.statut||'', seanceEssai:l.seanceEssai||'', seanceProspect:false, relanceLe:'', urgent:false, plan:'Inscrit' });
  const openConv = (l)=> H && H.openLead(asLead(l), shown.map(asLead));
  return (
    <div style={{ padding:'0 20px' }}>
      <div style={{ ...ax, fontSize:26, fontWeight:800, letterSpacing:-0.5, marginBottom:16, color:T.onBg }}>Ma cagnotte</div>
      <div style={{ borderRadius:24, padding:22, marginBottom:18, background:'linear-gradient(150deg, #1B2410 0%, #15171C 60%)', border:`1px solid var(--volt-line)` }}>
        <span style={{ ...mo, fontSize:11, letterSpacing:1, textTransform:'uppercase', color:T.muted }}>Cagnotte du mois</span>
        <div style={{ ...ax, fontSize:54, fontWeight:800, letterSpacing:-2, color:T.volt, margin:'6px 0 16px' }}>{fmt(d.cagnotte)}</div>
        <div style={{ display:'flex', alignItems:'center', gap:7, ...hk, fontSize:12, color:T.muted }}><Icon name="target" size={14} color={T.muted} />Versement à la fin du mois · {d.inscrits} inscrits cumulés</div>
      </div>
      <div style={{ display:'flex', gap:6, background:T.surface, borderRadius:12, padding:4, marginBottom:14, border:`1px solid ${T.line}` }}>
        {[['tout','Tout'],['valide','Validé'],['attente','En attente']].map(([id,lb])=>(
          <button key={id} onClick={()=>setFilter(id)} style={{ flex:1, border:'none', cursor:'pointer', borderRadius:9, padding:'8px 0', ...hk, fontSize:13, fontWeight:600, background:filter===id?T.volt:'transparent', color:filter===id?'var(--volt-ink)':T.muted }}>{lb}</button>
        ))}
      </div>
      <div style={{ display:'flex', flexDirection:'column', paddingBottom:20 }}>
        {filter!=='attente' && d.reviews?.prime>0 && <div style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 0', borderBottom:`1px solid ${T.line}` }}>
          <div style={{ width:38, height:38, borderRadius:11, background:T.amberDim, display:'flex', alignItems:'center', justifyContent:'center', fontSize:18 }}>⭐</div>
          <div style={{ flex:1, minWidth:0 }}><div style={{ ...hk, fontSize:14, fontWeight:600, color:T.onBg }}>Prime avis Google</div><div style={{ ...hk, fontSize:12, color:T.onBgMuted }}>Bonus équipe du mois</div></div>
          <div style={{ ...ax, fontSize:17, fontWeight:800, color:T.onBg }}>+{d.reviews.prime}€</div>
        </div>}
        {shown.length===0 && !(filter!=='attente' && d.reviews?.prime>0) && <div style={{ ...hk, fontSize:13, color:T.onBgMuted, padding:'18px 0', textAlign:'center' }}>Aucune conversion ici.</div>}
        {shown.slice(0,80).map(l=>(
          <div key={l.id} onClick={()=>openConv(l)} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 0', borderBottom:`1px solid ${T.line}`, cursor:'pointer' }}>
            <div style={{ width:38, height:38, borderRadius:11, background:l.prime!=null?T.voltDim:T.amberDim, display:'flex', alignItems:'center', justifyContent:'center' }}><Icon name={l.prime!=null?'check':'check'} size={18} color={l.prime!=null?T.volt:T.amber} /></div>
            <div style={{ flex:1, minWidth:0 }}><div style={{ ...hk, fontSize:14, fontWeight:600, color:T.onBg }}>{l.name}</div><div style={{ ...hk, fontSize:12, color:T.onBgMuted }}>{l.type==='abo'?'Abonnement MRR':l.type==='comptant'?'Comptant':'Inscrit'}{l.date?` · ${relWhen(l.date)}`:''}</div></div>
            <div style={{ display:'flex', alignItems:'center', gap:8 }}><div style={{ ...ax, fontSize:17, fontWeight:800, color:l.prime!=null?T.onBg:T.onBgMuted }}>{l.prime!=null?`+${l.prime}€`:'✓'}</div><Icon name="arrow" size={16} color={T.onBgMuted} /></div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Carte avis Google : total en direct + objectif par paliers (gamification équipe, reprise V1)
const GOOGLE_REVIEW = 'https://search.google.com/local/writereview?placeid=ChIJC8LNYuD1tRIRrLn0LJjiQnU';
const REVIEW_MSG = `Salut ! Si tu te plais à 4D Gym, ça nous aiderait vraiment que tu laisses un petit avis Google. Ça te prend 2 min depuis ton tél 🙏

👉 ${GOOGLE_REVIEW}

Tu écris ce que tu veux : les coachs, la salle ouverte 24h/24, l'ambiance, le parking, les cours collectifs… ce qui te plaît le plus chez nous. Merci beaucoup 💚`;

function AvisCard({ r }) {
  if (!r || !r.ok) return null;
  const tiers = r.tiers||[];
  const [ask, setAsk] = useState(false);
  const [qr, setQr] = useState(false);
  const [copied, setCopied] = useState(false);
  const notifyAvis = (ch) => api('/api/invite', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ who: meName(), channel: ch }) }).catch(()=>{});
  const copyMsg = async () => { try{ await navigator.clipboard.writeText(REVIEW_MSG); }catch(_){} notifyAvis('avis-copy'); setCopied(true); setTimeout(()=>setCopied(false),1800); };
  const allDone = tiers.length && tiers.every(t=>t.unlocked);
  const pct = Math.min(100, Math.round((r.gained/Math.max(1,r.goalMax))*100));
  return <div style={{ background:T.surface, border:`1px solid ${T.line}`, borderRadius:18, padding:16, marginBottom:14 }}>
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:10 }}>
      <span style={{ ...hk, fontWeight:800, fontSize:14 }}>⭐ Avis Google</span>
      <span style={{ ...ax, fontWeight:900, fontSize:26, color:T.amber }}>{r.total}<span style={{ ...hk, fontSize:12, color:T.muted, fontWeight:600 }}> au total</span></span>
    </div>
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:6 }}>
      <span style={{ ...mo, fontSize:11, color:T.muted, textTransform:'uppercase' }}>Objectif du mois</span>
      <span style={{ ...ax, fontWeight:800, color:T.text }}>{r.gained}/{r.goalMax}</span>
    </div>
    <div style={{ position:'relative', height:8, borderRadius:8, background:'var(--track)', marginBottom:10 }}>
      <div style={{ width:`${pct}%`, height:'100%', borderRadius:8, background:allDone?'#22c55e':'linear-gradient(90deg,#f59e0b,#fcd34d)' }} />
      {tiers.map((t,i)=>{ const left=Math.min(100,Math.round((t.seuil/Math.max(1,r.goalMax))*100)); return <span key={i} style={{ position:'absolute', top:-2, bottom:-2, left:`${left}%`, width:2, background:t.unlocked?'#22c55e':T.faint, transform:'translateX(-1px)' }} />; })}
    </div>
    <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:6 }}>
      {tiers.map((t,i)=>(<span key={i} style={{ ...mo, fontSize:10.5, fontWeight:800, padding:'3px 8px', borderRadius:999, background:t.unlocked?'#13371f':'var(--track)', color:t.unlocked?'#4ade80':T.muted }}>{t.unlocked?'✅':'🔒'} {t.seuil} = {t.prime}€</span>))}
    </div>
    <div style={{ ...hk, fontSize:11.5, color:T.faint, marginBottom:12 }}>{allDone?`🎉 Tous les paliers débloqués, +${r.prime}€ chacun !`:`Plus que ${r.next.remaining} avis ce mois pour +${r.next.prime}€ chacun`}</div>
    <button onClick={()=>setAsk(true)} style={{ width:'100%', ...btn(T.amber, '#1a1205'), padding:'11px 0', fontSize:13.5 }}>📣 Demander un avis</button>
    {ask && <Sheet onClose={()=>setAsk(false)}>
      {sheetTitle('⭐ Récolter un avis Google')}
      <div style={{ ...hk, fontSize:13.5, color:T.muted, lineHeight:1.55, marginBottom:14 }}>Deux façons de faire : tu lui montres le QR code en face à face, ou tu lui envoies le lien. Chaque avis rapproche l'équipe de la prime 💚</div>
      <button onClick={()=>{ notifyAvis('avis-qr'); setAsk(false); setQr(true); }} style={{ width:'100%', ...gbtn(), padding:'14px 0', fontSize:15, marginBottom:14 }}>📱 Montrer le QR code (face à face)</button>
      <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.faint, marginBottom:8 }}>OU ENVOYER LE LIEN</div>
      <div style={{ background:T.elev, border:`1px solid ${T.line}`, borderRadius:12, padding:12, ...hk, fontSize:13, color:T.text, lineHeight:1.5, whiteSpace:'pre-wrap', maxHeight:'26vh', overflowY:'auto', marginBottom:12 }}>{REVIEW_MSG}</div>
      <div style={{ display:'flex', gap:9, marginBottom:9 }}>
        <a href={'https://wa.me/?text='+encodeURIComponent(REVIEW_MSG)} target="_blank" rel="noopener" onClick={()=>notifyAvis('avis-wa')} style={{ flex:1.4, textDecoration:'none', textAlign:'center', ...obtn, padding:'12px 0' }}>📲 Envoyer</a>
        <button onClick={copyMsg} style={{ flex:1, ...obtn, padding:'12px 0', color:copied?GREEN:T.text, borderColor:copied?GREEN:T.line }}>{copied?'✓ Copié':'Copier'}</button>
      </div>
      <a href={GOOGLE_REVIEW} target="_blank" rel="noopener" style={{ display:'block', textAlign:'center', textDecoration:'none', ...obtn, padding:'12px 0', color:T.amber, borderColor:'rgba(246,194,84,0.4)' }}>⭐ Ouvrir le lien d'avis</a>
    </Sheet>}
    {qr && <div onClick={()=>setQr(false)} style={{ position:'fixed', inset:0, zIndex:90, background:'rgba(0,0,0,0.85)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}>
      <div onClick={e=>e.stopPropagation()} style={{ background:'#fff', borderRadius:24, padding:'26px 24px', textAlign:'center', maxWidth:360, width:'100%', boxShadow:'0 20px 60px rgba(0,0,0,0.5)' }}>
        <div style={{ ...ax, fontWeight:900, fontSize:20, color:'#0C0D10', marginBottom:4 }}>Scanne pour nous noter ⭐</div>
        <div style={{ ...hk, fontSize:13.5, color:'rgba(12,13,16,0.6)', marginBottom:16 }}>Vise ce QR code avec ton appareil photo, ça ouvre direct l'avis Google.</div>
        <img src="/qr-avis.png" alt="QR avis Google 4D Gym" style={{ width:'100%', maxWidth:280, aspectRatio:'1', display:'block', margin:'0 auto', borderRadius:12 }} />
        <div style={{ ...mo, fontSize:11, letterSpacing:1, color:'rgba(12,13,16,0.45)', marginTop:14 }}>4D GYM · LE THOR</div>
        <button onClick={()=>setQr(false)} style={{ width:'100%', marginTop:18, border:'none', borderRadius:14, padding:'13px 0', background:'#0C0D10', color:'#fff', ...hk, fontSize:15, fontWeight:700, cursor:'pointer' }}>Fermer</button>
      </div>
    </div>}
  </div>;
}

// ════════ CARTE À GRATTER — offre aléatoire de la semaine pour l'entourage ════════
// Pool pondéré : gagnant à tous les coups, gros lots rares (marge maîtrisée). w = poids (chance relative).
const SCRATCH_POOL = [
  { id:'frais',       label:"Frais d'inscription offerts (57€)",        tier:'common', w:10 },
  { id:'essai',       label:"Séance d'essai gratuite + bilan",          tier:'common', w:10 },
  { id:'sem1',        label:"1 semaine d'essai gratuite",               tier:'common', w:9 },
  { id:'m1_20',       label:"-20% sur ton 1er mois",                    tier:'common', w:9 },
  { id:'moins10',     label:"-10€ sur ton inscription",                 tier:'common', w:8 },
  { id:'bilan',       label:"Bilan Anovator offert (balance à impédancemètre)", tier:'common', w:8 },
  { id:'duo',         label:"Pass duo : amène qui tu veux gratuit 1 semaine", tier:'common', w:7 },
  { id:'m1_30',       label:"-30% sur ton 1er mois",                    tier:'common', w:7 },
  { id:'j15',         label:"15 jours offerts",                         tier:'common', w:6 },
  { id:'frais_coach', label:"Frais offerts + 1 séance coaching",        tier:'common', w:6 },
  { id:'j10',         label:"10 jours offerts",                         tier:'common', w:6 },
  { id:'coach2',      label:"2 séances coaching découverte offertes",   tier:'common', w:5 },
  { id:'m1_50',       label:"-50% sur ton 1er mois",                    tier:'common', w:5 },
  { id:'m2_25',       label:"-25% sur tes 2 premiers mois",             tier:'common', w:5 },
  { id:'sem3',        label:"3 semaines offertes",                      tier:'rare',   w:3 },
  { id:'mois1',       label:"1 mois offert",                            tier:'rare',   w:3 },
  { id:'mois1_coach', label:"1 mois offert + bilan coaching",           tier:'rare',   w:2 },
  { id:'mois2',       label:"2 mois offerts",                           tier:'jackpot',w:1 },
  { id:'mois3',       label:"3 mois offerts",                           tier:'jackpot',w:1 },
];
const TIER = {
  common:  { badge:'OFFRE',      col:T.text,  bg:'var(--track)' },
  rare:    { badge:'RARE ✨',    col:T.amber, bg:'rgba(246,194,84,0.12)' },
  jackpot: { badge:'JACKPOT 🎰', col:GREEN,   bg:'rgba(34,197,94,0.14)' },
};
function scratchWeekKey(){ const d=new Date(); const o=new Date(Date.UTC(d.getFullYear(),d.getMonth(),d.getDate())); const day=o.getUTCDay()||7; o.setUTCDate(o.getUTCDate()+4-day); const ys=new Date(Date.UTC(o.getUTCFullYear(),0,1)); const wk=Math.ceil((((o-ys)/86400000)+1)/7); return o.getUTCFullYear()+'-W'+wk; }
// RNG seedé : même (user+semaine) -> même tirage, toujours. Impossible de re-rouler en vidant le cache.
function seededRng(str){ let h=1779033703 ^ str.length; for(let i=0;i<str.length;i++){ h=Math.imul(h ^ str.charCodeAt(i),3432918353); h=h<<13 | h>>>19; } return ()=>{ h=Math.imul(h ^ h>>>16,2246822507); h=Math.imul(h ^ h>>>13,3266489909); return ((h ^= h>>>16)>>>0)/4294967296; }; }
// Tirage déterministe (offre + code) à partir du prénom et de la semaine.
function scratchTicket(name, wk){ const rng=seededRng((name||'?')+'|'+wk); const tot=SCRATCH_POOL.reduce((s,o)=>s+o.w,0); let r=rng()*tot; let off=SCRATCH_POOL[0]; for(const o of SCRATCH_POOL){ if((r-=o.w)<0){ off=o; break; } } const A='ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; const code='4D-'+Array.from({length:4},()=>A[Math.floor(rng()*A.length)]).join(''); return { offer:off, code }; }

function ScratchCard({ name }) {
  const wk = scratchWeekKey();
  const DKEY = '4ds_scr_'+(name||'')+'_'+wk+'_done'; // état "gratté" par user + semaine
  const ticket = useState(()=> scratchTicket(name, wk))[0]; // 1 ticket déterministe / user / semaine
  const offer = ticket.offer, code = ticket.code;
  const tier = TIER[offer.tier] || TIER.common;
  const [done, setDone] = useState(()=>{ try{ return localStorage.getItem(DKEY)==='1'; }catch(_){ return false; } });
  const [boom, setBoom] = useState(false); // animation de dévoilement (déclenchée au grattage, pas si déjà fait)
  const cvRef = useRef(null), wrapRef = useRef(null);
  const glow = offer.tier==='jackpot' ? 'rgba(34,197,94,.45)' : offer.tier==='rare' ? 'rgba(246,194,84,.4)' : 'rgba(255,255,255,.22)';
  const confetti = useState(()=> Array.from({ length: offer.tier==='jackpot'?20:offer.tier==='rare'?12:0 }, ()=>({
    left: Math.round(Math.random()*98), delay: (Math.random()*0.3).toFixed(2), dur: (0.8+Math.random()*0.5).toFixed(2),
    col: ['#e30613','#ffffff','#22C55E','#F6C254','#7FC8FF'][Math.floor(Math.random()*5)],
  })))[0];
  const msg = `Salut ! Je bosse à 4D Gym au Thor, ouverte 24h/24. Cette semaine je peux te faire gagner : ${offer.label}. Tu présentes ce code à la salle : ${code}, valable jusqu'à dimanche. Tu passes ? 06 40 92 11 43, dis que tu viens de la part de ${name}.`;
  const notifyShare = ()=> api('/api/invite', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ who:name, channel:'scratch', detail:`${offer.label} (code ${code})` }) }).catch(()=>{});

  useEffect(()=>{
    if(done) return;
    const cv=cvRef.current, wrap=wrapRef.current; if(!cv||!wrap) return;
    const w=wrap.clientWidth, h=wrap.clientHeight, dpr=window.devicePixelRatio||1;
    cv.width=w*dpr; cv.height=h*dpr; cv.style.width=w+'px'; cv.style.height=h+'px';
    const ctx=cv.getContext('2d'); ctx.scale(dpr,dpr);
    // Panneau DORÉ (même or que le bouton "Demander un avis" : #F6C254)
    const g=ctx.createLinearGradient(0,0,w,h); g.addColorStop(0,'#FBE6A6'); g.addColorStop(.5,'#F6C254'); g.addColorStop(1,'#E0A93D');
    ctx.fillStyle=g; ctx.fillRect(0,0,w,h);
    // reflets dorés diagonaux
    ctx.strokeStyle='rgba(255,255,255,0.16)'; ctx.lineWidth=2.5; for(let x=-h;x<w;x+=13){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x+h,h); ctx.stroke(); }
    ctx.fillStyle='#5A3E0C'; ctx.textAlign='center'; ctx.font='900 17px "Hanken Grotesk", sans-serif';
    ctx.fillText('GRATTE ICI', w/2, h/2-2); ctx.font='700 11.5px "Hanken Grotesk", sans-serif'; ctx.fillStyle='rgba(90,62,12,0.78)'; ctx.fillText('découvre ton offre surprise', w/2, h/2+17);
    ctx.globalCompositeOperation='destination-out';
    let drawing=false, last=null, ended=false;
    const pos=(e)=>{ const r=cv.getBoundingClientRect(); const t=e.touches&&e.touches[0]?e.touches[0]:e; return { x:t.clientX-r.left, y:t.clientY-r.top }; };
    // pinceau bien plus gros -> on gratte beaucoup moins longtemps
    const scratch=(p)=>{ ctx.beginPath(); ctx.arc(p.x,p.y,34,0,7); ctx.fill(); if(last){ ctx.lineWidth=66; ctx.lineCap='round'; ctx.beginPath(); ctx.moveTo(last.x,last.y); ctx.lineTo(p.x,p.y); ctx.stroke(); } last=p; };
    const finish=()=>{ if(ended) return; ended=true; removeListeners();
      setBoom(true);
      cv.style.pointerEvents='none'; cv.style.animation='scratchcover .45s ease forwards';
      setTimeout(()=>{ try{ localStorage.setItem(DKEY,'1'); }catch(_){} setDone(true); }, 430);
    };
    // révélation dès ~38% gratté, vérifiée en continu pendant le grattage
    const check=()=>{ try{ const px=ctx.getImageData(0,0,cv.width,cv.height).data; let clear=0,n=0; for(let i=3;i<px.length;i+=4*60){ n++; if(px[i]===0) clear++; } if(clear/n>0.38) finish(); }catch(_){} };
    let mc=0;
    const down=(e)=>{ drawing=true; last=null; scratch(pos(e)); e.preventDefault(); };
    const move=(e)=>{ if(!drawing) return; scratch(pos(e)); if((++mc%5)===0) check(); e.preventDefault(); };
    const up=()=>{ if(!drawing) return; drawing=false; check(); };
    cv.addEventListener('mousedown',down); cv.addEventListener('mousemove',move); window.addEventListener('mouseup',up);
    cv.addEventListener('touchstart',down,{passive:false}); cv.addEventListener('touchmove',move,{passive:false}); window.addEventListener('touchend',up);
    function removeListeners(){ cv.removeEventListener('mousedown',down); cv.removeEventListener('mousemove',move); window.removeEventListener('mouseup',up); cv.removeEventListener('touchstart',down); cv.removeEventListener('touchmove',move); window.removeEventListener('touchend',up); }
    return removeListeners;
  },[done]);

  const revealed = done; // contenu visible
  return <div style={{ position:'relative', overflow:'hidden', background:`linear-gradient(135deg, ${T.voltDim}, ${T.surface})`, border:`1px solid var(--volt-line)`, borderRadius:18, padding:16, marginBottom:14 }}>
    <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:4 }}>
      <span style={{ ...ax, fontWeight:800, fontSize:15 }}>🎟️ Carte à gratter</span>
      <span style={{ ...mo, fontSize:9.5, fontWeight:800, color:T.muted, border:`1px solid ${T.line}`, borderRadius:999, padding:'2px 8px' }}>SEMAINE</span>
    </div>
    <div style={{ ...hk, fontSize:12.5, color:T.muted, lineHeight:1.5, marginBottom:11 }}>Une offre surprise par semaine pour tes proches. Gratte, puis envoie le code.</div>
    <div ref={wrapRef} className={boom?'scratch-reveal':undefined} style={{ '--reveal-glow':glow, position:'relative', height:120, borderRadius:14, overflow:'hidden', marginBottom:revealed?12:0, border:`1px solid ${T.line}`, background:tier.bg, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', textAlign:'center', padding:'0 14px' }}>
      <span style={{ ...mo, fontSize:9.5, fontWeight:800, color:tier.col, marginBottom:5, letterSpacing:1 }}>{tier.badge}</span>
      <span style={{ ...ax, fontSize:18, fontWeight:900, color:tier.col, lineHeight:1.2 }}>{offer.label}</span>
      {revealed && <span style={{ ...mo, fontSize:12, color:T.text, marginTop:7, background:'var(--track)', padding:'3px 10px', borderRadius:8, letterSpacing:1, animation: boom?'scratchpop .5s cubic-bezier(.2,1.3,.5,1) .12s both':undefined }}>CODE : {code}</span>}
      {boom && <div className="scratch-shine" />}
      {boom && confetti.map((c,i)=>(<span key={i} className="confetti" style={{ left:c.left+'%', background:c.col, animationDelay:c.delay+'s', animationDuration:c.dur+'s' }} />))}
      {!revealed && <canvas ref={cvRef} style={{ position:'absolute', inset:0, cursor:'pointer', touchAction:'none' }} />}
    </div>
    {revealed && <a href={'https://wa.me/?text='+encodeURIComponent(msg)} target="_blank" rel="noopener" onClick={notifyShare} style={{ display:'block', textAlign:'center', textDecoration:'none', ...gbtn(), padding:'12px 0', animation: boom?'scratchpop .5s cubic-bezier(.2,1.3,.5,1) .22s both':undefined }}>📲 Envoyer à un proche</a>}
  </div>;
}

// ════════ CLASSEMENT ════════
function RankInner({ d }) {
  const [mode, setMode] = useState('cagnotte'); // 'cagnotte' (€ du mois) | 'perf' (taux de transfo)
  const val = (p) => mode==='cagnotte' ? (p.total||0) : (p.taux||0);
  const big = (p) => mode==='cagnotte' ? `${p.total||0}€` : `${p.taux||0}%`;
  const sub = (p) => mode==='cagnotte' ? `${p.signed||0} inscrits ce mois` : `${p.convertis||0} convertis / ${p.traites||0}`;
  const lb = [...d.leaderboard].sort((a,b)=> val(b)-val(a) || (b.signed||0)-(a.signed||0));
  lb.forEach((p,i)=> p._rank = i+1);
  const top3 = lb.slice(0,3);
  const podium = top3.length>=3 ? [top3[1],top3[0],top3[2]] : top3;
  const heights=[104,134,88];
  const meRow = lb.find(x=>x.me);
  const isLeader = !!meRow && meRow._rank===1;
  const anyValue = lb.some(p=>val(p)>0);
  const toFirst = (lb.length&&meRow) ? Math.max(0, val(lb[0])-val(meRow)) : 0;
  const unit = mode==='cagnotte' ? '€' : ' pts';
  const pct = Math.min(100, Math.round((d.cagnotte/d.goal)*100));
  const Seg = ({ id, label }) => (
    <button onClick={()=>setMode(id)} style={{ flex:1, border:'none', cursor:'pointer', borderRadius:10, padding:'9px 0', ...hk, fontSize:13.5, fontWeight:700, background:mode===id?T.volt:'transparent', color:mode===id?T.ink:T.muted }}>{label}</button>
  );
  if (!d.leaderboard || !d.leaderboard.length) return <div style={{ padding:'70px 20px', textAlign:'center', color:T.muted, ...hk }}>Chargement du classement…</div>;
  return (
    <div style={{ padding:'0 20px' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
        <div style={{ ...ax, fontSize:26, fontWeight:800, letterSpacing:-0.5, color:T.onBg }}>Classement</div>
        <span style={{ ...mo, fontSize:12, color:T.onBgMuted, border:`1px solid ${T.line}`, padding:'6px 12px', borderRadius:999 }}>Équipe</span>
      </div>
      <div style={{ display:'flex', gap:6, background:T.surface, borderRadius:12, padding:4, marginBottom:16, border:`1px solid ${T.line}` }}>
        <Seg id="cagnotte" label="💰 Cagnotte" /><Seg id="perf" label="🎯 Performance" />
      </div>
      {mode==='perf' && <div style={{ ...hk, fontSize:11.5, color:T.onBgMuted, marginTop:-8, marginBottom:14, textAlign:'center' }}>Taux de transformation (convertis ÷ traités), équitable quels que soient les jours travaillés.</div>}

      <div style={{ display:'flex', alignItems:'flex-end', justifyContent:'center', gap:10, marginBottom:18 }}>
        {podium.map((p,k)=>{ const top=p._rank===1; return <div key={p.name} style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center' }}>
          <Avatar name={p.name} size={top?56:44} ring={p.me} />
          <div style={{ ...hk, fontSize:12, fontWeight:700, marginTop:8, color:p.me?T.volt:T.onBg }}>{p.me?'Toi':p.name.split(' ')[0]}</div>
          <div style={{ ...ax, fontSize:18, fontWeight:800, color:top?T.volt:T.onBg }}>{big(p)}</div>
          <div style={{ width:'100%', height:heights[k]??90, background:top?T.voltDim:T.surface, border:`1px solid ${top?'var(--volt-line)':T.line}`, borderRadius:'12px 12px 0 0', marginTop:8, display:'flex', alignItems:'flex-start', justifyContent:'center', paddingTop:10 }}><span style={{ ...ax, fontSize:30, fontWeight:800, color:top?T.volt:T.faint }}>{p._rank}</span></div>
        </div>; })}
      </div>
      <div style={{ background:T.surface, border:'1px solid var(--volt-line)', borderRadius:18, padding:16, marginBottom:14 }}>
        <div style={{ display:'flex', alignItems:'center', gap:10 }}><Icon name="bolt" size={18} color={T.volt} />
          <span style={{ ...hk, fontSize:14, fontWeight:600 }}>{!anyValue ? <b style={{ color:T.volt }}>La compétition démarre 🔥</b> : isLeader ? <b style={{ color:T.volt }}>Tu es en tête 🔥</b> : <>Plus que <b style={{ color:T.volt }}>{toFirst}{unit}</b> pour la 1<sup>re</sup> place</>}</span></div>
      </div>
      <AvisCard r={d.reviews} />
      <SectionLabel>Équipe 4D Gym</SectionLabel>
      <div style={{ display:'flex', flexDirection:'column', gap:8, paddingBottom:20 }}>
        {lb.map(p=>(<div key={p.name} style={{ display:'flex', alignItems:'center', gap:12, padding:'10px 14px', borderRadius:14, background:p.me?T.voltDim:T.surface, border:`1px solid ${p.me?'var(--volt-line)':T.line}` }}>
          <span style={{ ...ax, fontSize:16, fontWeight:800, width:22, color:p.me?T.volt:T.faint }}>{p._rank}</span>
          <Avatar name={p.name} size={34} ring={p.me} />
          <div style={{ flex:1 }}><div style={{ ...hk, fontSize:14, fontWeight:600 }}>{p.me?`Toi · ${p.name}`:p.name}</div><div style={{ ...hk, fontSize:11, color:T.muted }}>{sub(p)}</div></div>
          <span style={{ ...ax, fontSize:16, fontWeight:800, color:p.me?T.volt:T.text }}>{big(p)}</span>
        </div>))}
      </div>
    </div>
  );
}

// Onglet combiné Primes + Classement (toggle en haut).
function ScreenGains({ d, go, H }) {
  const [view, setView] = useState('primes');
  return <Screen active="gains" go={go}>
    <div style={{ padding:'12px 20px 0' }}>
      <div style={{ display:'flex', gap:6, background:T.surface, borderRadius:12, padding:4, border:`1px solid ${T.line}` }}>
        {[['primes','💰 Mes primes'],['rank','🏆 Classement']].map(([id,lb])=>(
          <button key={id} onClick={()=>setView(id)} style={{ flex:1, border:'none', cursor:'pointer', borderRadius:9, padding:'10px 0', ...hk, fontSize:14, fontWeight:700, background:view===id?T.volt:'transparent', color:view===id?T.ink:T.muted }}>{lb}</button>
        ))}
      </div>
    </div>
    {view==='primes' ? <WalletInner d={d} H={H} /> : <RankInner d={d} />}
  </Screen>;
}

// ════════ SÉANCES D'ESSAI DU JOUR ════════
function ScreenSessions({ d, go, H }) {
  const today = todayISO();
  const tomorrow = plusDaysISO(1);
  const all = (d.allLeads||[]).filter(l=> l.seanceEssai).map(l=>({ ...l, jour:l.seanceEssai.slice(0,10) }))
    .filter(l=> l.jour >= today)
    .sort((a,b)=> a.seanceEssai.localeCompare(b.seanceEssai));
  const todays = all.filter(l=> l.jour===today);
  const tomos = all.filter(l=> l.jour===tomorrow);
  const later = all.filter(l=> l.jour>tomorrow);
  const [waLead, setWaLead] = useState(null);
  const heureOf = (l)=> hasTime(l.seanceEssai) ? new Date(l.seanceEssai).toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'}).replace(':','h') : '';
  const dayPhrase = (l)=> l.jour===today ? "aujourd'hui" : l.jour===tomorrow ? 'demain' : 'le '+fmtFR(l.jour).slice(0,5);
  // 2 propositions courtes, ton 4D (tutoiement, pas de tournure bancale), date-aware (veille / jour J).
  const waMsgs = (l)=>{ const p=l.name.split(' ')[0]; const h=heureOf(l); const j=dayPhrase(l); const at=h?` à ${h}`:''; return [
    { label:'✅ Confirmer (à envoyer la veille)', text:`Salut ${p}, c'est ${d.me} de 4D Gym au Thor. Je te confirme ta séance d'essai ${j}${at}. On t'attend au ${GYM_ADDR}, viens comme tu es 💪` },
    { label:'📅 Rappel du jour J', text:`Salut ${p}, on se voit${h?` à ${h}`:''} tout à l'heure pour ta séance d'essai 💪 On t'attend, viens comme tu es. À toute !` },
  ]; };
  const Row = (l)=>{
    const heure = heureOf(l) || '—';
    return <div key={l.id} onClick={()=>H.openLead(l, all)} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 0', borderBottom:`1px solid ${T.line}`, cursor:'pointer' }}>
      <div style={{ width:54, textAlign:'center', flex:'0 0 auto' }}><div style={{ ...ax, fontSize:17, fontWeight:900, color:T.onBg }}>{heure}</div></div>
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ ...hk, fontSize:14.5, fontWeight:700, color:T.onBg, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{l.name}</div>
        <div style={{ display:'flex', gap:6, marginTop:3, flexWrap:'wrap' }}>
          <span style={{ ...mo, fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:999, background: l.seanceProspect?'rgba(124,58,237,0.16)':T.amberDim, color: l.seanceProspect?'#a78bfa':T.amber }}>{l.seanceProspect?'📅 Calendly':'👤 '+(l.assigne||'staff')}</span>
          {l.converted && <span style={{ ...mo, fontSize:9.5, fontWeight:700, padding:'2px 7px', borderRadius:999, background:'rgba(34,197,94,0.16)', color:GREEN }}>✅ inscrit</span>}
        </div>
      </div>
      <div style={{ display:'flex', gap:6, flex:'0 0 auto' }} onClick={e=>e.stopPropagation()}>
        <button onClick={()=>H.call(l)} title="Appeler" style={{ width:38, height:38, borderRadius:11, border:'none', cursor:'pointer', background:GREEN, display:'flex', alignItems:'center', justifyContent:'center' }}><Icon name="phone" size={17} color={GINK} /></button>
        <button onClick={()=>setWaLead(l)} title="WhatsApp" style={{ width:38, height:38, borderRadius:11, border:'none', cursor:'pointer', background:'#1Fae5a', display:'flex', alignItems:'center', justifyContent:'center', fontSize:17 }}>📲</button>
      </div>
    </div>;
  };
  return <Screen active="sessions" go={go}>
    <div style={{ padding:'0 20px 24px' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4 }}>
        <div style={{ ...ax, fontSize:26, fontWeight:800, letterSpacing:-0.5, color:T.onBg }}>Séances d'essai</div>
        <button onClick={()=>H.walkin()} style={{ ...btn(T.volt, 'var(--volt-ink)'), padding:'9px 14px', fontSize:13.5, fontWeight:800, borderRadius:999 }}>➕ Sur place</button>
      </div>
      <div style={{ ...hk, fontSize:13, color:T.onBgMuted, marginBottom:18 }}>Qui vient tester la salle. Pense à confirmer la veille ou le matin.</div>

      <SectionLabel>🎯 Aujourd'hui · {todays.length}</SectionLabel>
      {todays.length ? <div style={{ marginBottom:24 }}>{todays.map(Row)}</div>
        : <div style={{ ...hk, fontSize:13.5, color:T.faint, background:T.surface, border:`1px solid ${T.line}`, borderRadius:14, padding:'18px 16px', textAlign:'center', marginBottom:24 }}>Aucune séance d'essai aujourd'hui.</div>}

      {tomos.length>0 && <><SectionLabel>Demain · {tomos.length}</SectionLabel><div style={{ marginBottom:24 }}>{tomos.map(Row)}</div></>}
      {later.length>0 && <><SectionLabel>Plus tard · {later.length}</SectionLabel><div>{later.map(Row)}</div></>}
    </div>
    {waLead && <Sheet onClose={()=>setWaLead(null)}>
      {sheetTitle('Confirmer la séance')}
      <div style={{ ...hk, fontSize:13, color:T.muted, marginBottom:12 }}>Choisis le message, WhatsApp s'ouvre pré-rempli.</div>
      <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
        {waMsgs(waLead).map(t=>(
          <a key={t.label} href={'https://wa.me/'+(waLead.phone||'').replace(/[^0-9]/g,'').replace(/^0/,'33')+'?text='+encodeURIComponent(t.text)} target="_blank" rel="noopener" onClick={()=>setWaLead(null)} style={{ ...obtn, textAlign:'left', padding:'12px 14px', textDecoration:'none', display:'block' }}>
            <div style={{ ...hk, fontSize:13.5, fontWeight:700, marginBottom:5 }}>{t.label}</div>
            <div style={{ ...hk, fontSize:12.5, color:T.muted, lineHeight:1.45 }}>{t.text}</div>
          </a>
        ))}
      </div>
    </Sheet>}
  </Screen>;
}

// ════════ NOUVEAUTÉS (changelog) ════════
// Le plus récent en haut. À chaque grosse mise à jour, on ajoute un bloc.
const CHANGELOG = [
  { date:'15 juin 2026', title:'Version 1.0 — Lancement 🚀',
    news:[
      '🎨 Nouvelle app "staff" complète (design sombre), en ligne sur leads.4dgymclub.com, ajoutable à l\'écran d\'accueil (icône fusée)',
      '📞 Phoning : file du jour (nouveaux en rouge + relances), appel direct, WhatsApp pré-rempli, notes, statuts (RDV, pas intéressé, mauvais numéro)',
      '💰 Cagnotte du mois animée + primes (MRR 5€ / comptant 10€), versées au closeur',
      '📊 Tes 2 taux (RDV pris, convertis sur RDV) + entonnoir cliquable',
      '🏆 Classement 2 vues : Cagnotte (€) et Performance (taux de closing, équitable) — reparti à 0 le 15/06',
      '⭐ Avis Google gamifiés : paliers 30=50€, 50=70€, 100=100€ pour toute l\'équipe',
      '⭐ Récolter un avis : QR code à montrer en face à face + lien/message WhatsApp prêt',
      '🎟️ Carte à gratter hebdo pour l\'entourage (offre surprise, 1 ticket/semaine, code unique, partage WhatsApp)',
      '📅 Séances Calendly automatiques : les réservations des prospects arrivent dans "Séances fixées" (avec bouton Confirmer)',
      '🔴 Rappels dans la journée : "rappeler à midi/14h…" + "Pas de réponse → +2h" + section "À rappeler maintenant"',
      '🆘 Filet "À reprendre" : un lead d\'un conseiller absent ne se perd plus (visible par l\'équipe)',
      '🗓️ Relances jour par jour + sélecteur (aujourd\'hui / demain / un jour précis)',
      '💡 Cagnotte : potentiel à gagner affiché (leads chauds en attente)',
      '🤳 Photo de profil perso + couleur de l\'app (Volt, Vert, Bleu, Rose, Blanc) + mode Jour/Nuit',
      '🧭 Guide interactif au 1er lancement (+ "Revoir le guide")',
      '🔔 Notifications Telegram au patron (parrainage, carte à gratter, demande d\'avis)',
      '📈 Suivi des connexions de l\'équipe (côté admin) : qui s\'est connecté, combien de fois',
      '🐞 Bouton "Signaler un bug" + écran "Notes de version"',
      '✨ Écran de lancement : slogans motivants + citations de grandes personnalités',
    ],
    fixes:[
      'WhatsApp : n\'éjecte plus l\'app quand on envoie un message (correction PWA)',
      'Relances : affichées par date, plus de "lead déjà traité" qui revient',
      'Classement Performance : un closing compte même sur un vieux lead (par date de conversion)',
      'Cagnotte : inclut les primes avis pour atteindre l\'objectif 300€',
      'Mode jour : éléments qui étaient invisibles maintenant lisibles',
      'Affichage : l\'app remplit tout l\'écran (plus d\'espace vide en bas)',
      'Séances : on distingue celles réservées par le prospect (Calendly) de celles fixées par l\'employé',
    ],
  },
];

// ════════ PROFIL ════════
function ScreenProfile({ d, go, H, onLogout }) {
  const sortedM = [...d.leaderboard].sort((a,b)=>(b.total||0)-(a.total||0));
  const myRank = sortedM.findIndex(x=>x.me)+1; const rank = myRank>0 ? '#'+myRank : '—';
  const [acc, setAcc] = useState(()=>{ try{ return (JSON.parse(localStorage.getItem('4ds_accent')||'null')||{}).name||'Volt'; }catch(_){ return 'Volt'; } });
  const [mode, setModeS] = useState(getMode());
  const [info, setInfo] = useState(null);
  const [news, setNews] = useState(false);
  const [activity, setActivity] = useState(null);
  const [seenSheet, setSeenSheet] = useState(null);
  const [photoVer, setPhotoVer] = useState(0);
  const [photoBusy, setPhotoBusy] = useState(false);
  const fileRef = useRef(null);
  const isAdmin = d.me === ADMIN;
  const canSeeNotes = isAdmin || d.me === 'Dev';
  const onPickPhoto = (e) => {
    const file = e.target.files && e.target.files[0]; if(!file) return;
    if(!/^image\//.test(file.type)) { alert('Choisis une image.'); return; }
    setPhotoBusy(true);
    const img = new Image();
    img.onload = () => {
      const S=128, cv=document.createElement('canvas'); cv.width=S; cv.height=S; const ctx=cv.getContext('2d');
      const r=Math.max(S/img.width, S/img.height), w=img.width*r, h=img.height*r;
      ctx.drawImage(img, (S-w)/2, (S-h)/2, w, h);
      let q=0.6, url=cv.toDataURL('image/jpeg', q);
      while(url.length>42000 && q>0.3){ q-=0.1; url=cv.toDataURL('image/jpeg', q); }
      URL.revokeObjectURL(img.src);
      api('/api/photo', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ dataUrl:url }) })
        .then(res=>{ if(res&&res.ok){ window.__photos = { ...(window.__photos||{}), [d.me]: url }; setPhotoVer(v=>v+1); } else { alert('Échec : '+((res&&res.reason)||'envoi')); } })
        .catch(()=>alert('Échec de l\'envoi'))
        .finally(()=>setPhotoBusy(false));
    };
    img.onerror = ()=>{ setPhotoBusy(false); alert('Image illisible.'); };
    img.src = URL.createObjectURL(file);
  };
  useEffect(()=>{ if(isAdmin){ api('/api/activity').then(a=>setActivity(a.items||[])).catch(()=>{}); } },[]);
  const s = d.stats || {};
  const objectifsBody = <div style={{ display:'flex', flexDirection:'column', gap:12 }}>
    <div><b style={{ color:T.volt }}>💰 Cagnotte</b><br/>Objectif <b>{d.goal} €</b> ce mois.</div>
    {s.doubleFrom > 0 && <div><b style={{ color:GREEN }}>🔥 Bonus ×2</b><br/>Dès <b>{s.doubleFrom} inscriptions</b>, tes primes doublent : MRR <b>{(s.primeMrr||5)*2}€</b>, comptant <b>{(s.primeComptant||10)*2}€</b>.</div>}
    <div><b style={{ color:T.amber }}>⭐ Avis Google (équipe)</b><br/>{(d.reviews?.tiers||[]).map(t=>`${t.seuil} avis = ${t.prime}€`).join(' · ') || '—'} pour chaque membre.</div>
  </div>;
  const menu = [
    { label:'Objectifs', onClick:()=>setInfo({ title:'Tes objectifs', body:objectifsBody }) },
    { label:'Historique versements', onClick:()=>go('gains') },
    { label:'Leads archivés', onClick:()=>H.openCat('perdus','Leads archivés') },
    { label:'🧭 Revoir le guide', onClick:()=>H.replayTour() },
    ...(canSeeNotes ? [{ label:'📋 Notes de version', onClick:()=>setNews(true) }] : []),
    { label:'Notifications', onClick:()=>setInfo({ title:'Notifications', body:'Bientôt : rappels automatiques pour tes relances du jour et tes nouveaux leads.' }) },
    { label:'🐞 Signaler un bug', href:'https://wa.me/33640921143?text='+encodeURIComponent(`🐞 Bug app 4D\nDe : ${d.me}\nÉcran : \nCe qui s'est passé : \n`) },
    { label:'Aide & contact manager', href:'https://wa.me/33640921143?text='+encodeURIComponent("Salut, j'ai une question sur l'appli 4D") },
  ];
  return <Screen active="me" go={go}>
    <div style={{ padding:'0 20px' }}>
      <div style={{ display:'flex', alignItems:'center', gap:14, marginBottom:24 }}>
        <div onClick={()=>!photoBusy && fileRef.current && fileRef.current.click()} style={{ position:'relative', cursor:'pointer', opacity:photoBusy?0.5:1 }}>
          <Avatar name={d.me} size={64} ring key={photoVer} />
          <div style={{ position:'absolute', right:-2, bottom:-2, width:24, height:24, borderRadius:24, background:T.volt, border:`2px solid ${T.bg}`, display:'flex', alignItems:'center', justifyContent:'center' }}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--volt-ink)" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
          </div>
          <input ref={fileRef} type="file" accept="image/*" onChange={onPickPhoto} style={{ display:'none' }} />
        </div>
        <div><div style={{ ...ax, fontSize:24, fontWeight:800, color:T.onBg }}>{d.me}</div><div style={{ ...hk, fontSize:13, color:T.onBgMuted }}>{photoBusy?'Envoi de la photo…':'Touche ta photo pour la changer'}</div></div>
      </div>
      <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:10, marginBottom:26 }}>
        {[['Prime du mois',fmt(d.cagnotte),T.volt],['Inscrits',String(d.inscrits),T.text],['Rang',rank,T.text]].map(([k,v,c])=>(<div key={k} style={{ background:T.surface, borderRadius:16, padding:'14px 12px', border:`1px solid ${T.line}` }}><div style={{ ...ax, fontSize:22, fontWeight:800, color:c }}>{v}</div><div style={{ ...hk, fontSize:11, color:T.muted, marginTop:2 }}>{k}</div></div>))}
      </div>
      {isAdmin && <>
        <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.onBgMuted, margin:'4px 0 10px' }}>ACTIVITÉ ÉQUIPE · ADMIN</div>
        <div style={{ background:T.surface, border:`1px solid ${T.line}`, borderRadius:16, padding:'6px 14px', marginBottom:22 }}>
          {!activity && <div style={{ ...hk, fontSize:13, color:T.faint, padding:'12px 0' }}>Chargement…</div>}
          {activity && activity.map((m,i)=>{ const recent = m.lastSeen && (Date.now()-new Date(m.lastSeen).getTime() < 86400000); return (
            <div key={m.name} onClick={()=> m.count>0 && setSeenSheet(m)} style={{ display:'flex', alignItems:'center', gap:12, padding:'11px 0', borderBottom: i<activity.length-1?`1px solid ${T.line}`:'none', cursor: m.count>0?'pointer':'default' }}>
              <Avatar name={m.name} size={34} />
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ ...hk, fontSize:14, fontWeight:600 }}>{m.name}</div>
                <div style={{ ...hk, fontSize:11, color:T.faint }}>{m.count>0?`${m.count} connexion${m.count>1?'s':''}${m.count>0?' · voir ›':''}`:'pas encore connecté'}</div>
              </div>
              <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                <span style={{ width:7, height:7, borderRadius:7, background: m.lastSeen?(recent?GREEN:T.amber):T.faint }} />
                <span style={{ ...hk, fontSize:12, color: m.lastSeen?T.muted:T.faint }}>{relTime(m.lastSeen)}</span>
              </div>
            </div>); })}
        </div>
        <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.onBgMuted, margin:'4px 0 10px' }}>VOIR L'INTERFACE D'UN MEMBRE · ADMIN</div>
        <div style={{ background:T.surface, border:`1px solid ${T.line}`, borderRadius:16, padding:'6px 14px', marginBottom:22 }}>
          {(d.team||[]).filter(m=>m!==ADMIN).map((m,i,arr)=>(
            <div key={m} onClick={()=>H.setViewAs(m)} style={{ display:'flex', alignItems:'center', gap:12, padding:'11px 0', borderBottom:i<arr.length-1?`1px solid ${T.line}`:'none', cursor:'pointer' }}>
              <Avatar name={m} size={34} />
              <div style={{ flex:1, ...hk, fontSize:14, fontWeight:600 }}>{m}</div>
              <span style={{ ...hk, fontSize:12.5, fontWeight:700, color:'#a78bfa', display:'flex', alignItems:'center', gap:5 }}>👁 Voir ›</span>
            </div>
          ))}
          <div style={{ ...hk, fontSize:11, color:T.faint, padding:'8px 0 10px' }}>Lecture seule, sans compter de connexion.</div>
        </div>
      </>}
      <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.onBgMuted, margin:'4px 0 10px' }}>AFFICHAGE</div>
      <div style={{ display:'flex', gap:8, background:T.surface, borderRadius:12, padding:4, marginBottom:22, border:`1px solid ${T.line}` }}>
        {[['night','🌙 Nuit'],['day','☀️ Jour']].map(([id,lb])=>(
          <button key={id} onClick={()=>{ setMode(id); setModeS(id); }} style={{ flex:1, border:'none', cursor:'pointer', borderRadius:9, padding:'10px 0', ...hk, fontSize:14, fontWeight:700, background:mode===id?T.volt:'transparent', color:mode===id?T.ink:T.muted }}>{lb}</button>
        ))}
      </div>
      <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.onBgMuted, margin:'4px 0 10px' }}>COULEUR DE L'APP</div>
      <div style={{ display:'flex', gap:12, marginBottom:22, flexWrap:'wrap' }}>
        {ACCENTS.map(a=>(<button key={a.name} title={a.name} onClick={()=>{ applyAccent(a); setAcc(a.name); }}
          style={{ width:40, height:40, borderRadius:999, background:a.hex, cursor:'pointer',
            border: acc===a.name?'3px solid #fff':`2px solid ${T.line}`, boxShadow: acc===a.name?`0 0 0 3px ${hexRgba(a.hex,0.4)}`:'none' }} />))}
      </div>
      <div style={{ ...hk, fontSize:11.5, color:T.onBgMuted, marginBottom:20 }}>Juste pour toi, sur ton téléphone.</div>

      <div style={{ display:'flex', flexDirection:'column', gap:2, marginBottom:20 }}>
        {menu.map(it=> it.href
          ? <a key={it.label} href={it.href} target="_blank" rel="noopener" style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'15px 0', borderBottom:`1px solid ${T.line}`, ...hk, fontSize:15, cursor:'pointer', color:T.onBg, textDecoration:'none' }}>{it.label}<Icon name="arrow" size={18} color={T.onBgMuted} /></a>
          : <div key={it.label} onClick={it.onClick} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'15px 0', borderBottom:`1px solid ${T.line}`, ...hk, fontSize:15, cursor:'pointer', color:T.onBg }}>{it.label}<Icon name="arrow" size={18} color={T.onBgMuted} /></div>)}
      </div>
      <button onClick={onLogout} style={{ width:'100%', border:`1px solid ${T.line}`, background:'transparent', color:T.onBgMuted, borderRadius:14, padding:'14px 0', ...hk, fontSize:15, fontWeight:600, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', gap:8, marginBottom:20 }}><Icon name="logout" size={18} color={T.onBgMuted} />Se déconnecter</button>
    </div>
    {news && <Sheet onClose={()=>setNews(false)}>
      {sheetTitle('📋 Notes de version')}
      <div style={{ ...hk, fontSize:13, color:T.muted, marginBottom:14 }}>L'historique des évolutions de l'app.</div>
      <div style={{ display:'flex', flexDirection:'column', gap:18, maxHeight:'62vh', overflowY:'auto' }}>
        {CHANGELOG.map((v,i)=>(
          <div key={i}>
            <div style={{ display:'flex', alignItems:'baseline', gap:8, marginBottom:8 }}>
              <span style={{ ...ax, fontSize:16, fontWeight:800 }}>{v.title}</span>
              <span style={{ ...mo, fontSize:11, color:T.faint }}>{v.date}</span>
            </div>
            {v.news && v.news.length>0 && <>
              <div style={{ ...mo, fontSize:10, letterSpacing:0.5, color:T.volt, margin:'2px 0 6px' }}>✨ NOUVEAUTÉS</div>
              {v.news.map((t,k)=>(<div key={k} style={{ display:'flex', gap:8, ...hk, fontSize:13.5, color:T.text, lineHeight:1.45, marginBottom:6 }}><span style={{ color:T.volt }}>•</span><span>{t}</span></div>))}
            </>}
            {v.fixes && v.fixes.length>0 && <>
              <div style={{ ...mo, fontSize:10, letterSpacing:0.5, color:T.muted, margin:'10px 0 6px' }}>🔧 CORRECTIONS</div>
              {v.fixes.map((t,k)=>(<div key={k} style={{ display:'flex', gap:8, ...hk, fontSize:13.5, color:T.muted, lineHeight:1.45, marginBottom:6 }}><span>•</span><span>{t}</span></div>))}
            </>}
          </div>
        ))}
      </div>
      <button onClick={()=>setNews(false)} style={{ width:'100%', marginTop:16, ...obtn, color:T.muted }}>Fermer</button>
    </Sheet>}
    {info && <Sheet onClose={()=>setInfo(null)}>
      {sheetTitle(info.title)}
      <div style={{ ...hk, fontSize:14.5, color:T.text, lineHeight:1.6 }}>{info.body}</div>
      <button onClick={()=>setInfo(null)} style={{ width:'100%', marginTop:16, ...obtn, color:T.muted }}>Fermer</button>
    </Sheet>}
    {seenSheet && <Sheet onClose={()=>setSeenSheet(null)}>
      <div style={{ display:'flex', alignItems:'center', gap:12, marginBottom:14 }}><Avatar name={seenSheet.name} size={44} ring /><div><div style={{ ...ax, fontSize:18, fontWeight:800 }}>{seenSheet.name}</div><div style={{ ...hk, fontSize:12.5, color:T.muted }}>{seenSheet.count} connexion{seenSheet.count>1?'s':''} au total</div></div></div>
      <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.faint, marginBottom:8 }}>DERNIÈRES SESSIONS</div>
      <div style={{ display:'flex', flexDirection:'column', maxHeight:'52vh', overflowY:'auto' }}>
        {(seenSheet.history||[]).length===0 && <div style={{ ...hk, fontSize:13, color:T.faint, padding:'10px 0' }}>Aucun historique.</div>}
        {(seenSheet.history||[]).map((iso,i)=>{ const dt=new Date(iso); return (
          <div key={i} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'10px 0', borderBottom:`1px solid ${T.line}` }}>
            <span style={{ ...hk, fontSize:14, color:T.text }}>{dt.toLocaleDateString('fr-FR',{weekday:'short', day:'2-digit', month:'2-digit'})}</span>
            <span style={{ ...mo, fontSize:13, color:T.muted }}>{String(dt.getHours()).padStart(2,'0')}h{String(dt.getMinutes()).padStart(2,'0')}</span>
          </div>); })}
      </div>
      <button onClick={()=>setSeenSheet(null)} style={{ width:'100%', marginTop:16, ...obtn, color:T.muted }}>Fermer</button>
    </Sheet>}
  </Screen>;
}

// ════════ Feuilles (sheets) ════════
function Sheet({ children, onClose, tall }) {
  return <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:60, background:'rgba(0,0,0,0.6)', display:'flex', alignItems:'flex-end', justifyContent:'center' }}>
    <div onClick={e=>e.stopPropagation()} style={{ position:'relative', width:'100%', maxWidth:480, background:T.surface, borderRadius:'22px 22px 0 0', padding:'20px 20px calc(20px + env(safe-area-inset-bottom))', border:`1px solid ${T.line}`, ...hk, color:T.text, maxHeight:'94vh', overflowY:'auto' }}>
      {onClose && <div style={{ position:'sticky', top:0, zIndex:6, display:'flex', justifyContent:'flex-end', height:0, marginBottom:-6, pointerEvents:'none' }}>
        <button onClick={onClose} aria-label="Fermer" style={{ pointerEvents:'auto', width:36, height:36, borderRadius:999, border:`1px solid ${T.line}`, background:T.elev||T.bg, color:T.muted, fontSize:18, lineHeight:1, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', boxShadow:'0 2px 8px rgba(0,0,0,0.3)' }}>✕</button>
      </div>}
      {children}
    </div>
  </div>;
}
const sheetTitle = (t) => <div style={{ ...ax, fontSize:18, fontWeight:800, marginBottom:14 }}>{t}</div>;

// Fiche d'un lead, SWIPEABLE : glisse gauche/droite pour passer au lead suivant/précédent.
function LeadSheet({ state, setSheet, H, onClose }) {
  const { list, index } = state;
  const lead = list[index];
  const [dir, setDir] = useState('next');
  const [notes, setNotes] = useState(null);
  const startX = useRef(null);
  useEffect(()=>{ if(!lead) return; setNotes(null);
    api('/api/notes?id='+encodeURIComponent(lead.id)).then(j=>setNotes((j.notes||[]).filter(n=>n.text&&n.text.trim()))).catch(()=>setNotes([]));
  },[lead && lead.id]);
  if (!lead) return null;
  const go = (delta) => { const ni = index + delta; if (ni >= 0 && ni < list.length) { setDir(delta>0?'next':'prev'); setSheet({ list, index: ni }); } };
  const onStart = (x) => { startX.current = x; };
  const onEnd = (x) => { if (startX.current == null) return; const dx = x - startX.current; startX.current = null; if (dx < -45) go(1); else if (dx > 45) go(-1); };
  return <Sheet onClose={onClose} tall>
    <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:12, paddingRight:42 }}>
      <button onClick={()=>go(-1)} disabled={index<=0} style={{ background:'none', border:'none', color:index>0?T.text:T.line, fontSize:24, cursor:'pointer', padding:'0 6px' }}>‹</button>
      <span style={{ ...mo, fontSize:11, color:T.faint, letterSpacing:1 }}>{index+1} / {list.length} · glisse ← →</span>
      <button onClick={()=>go(1)} disabled={index>=list.length-1} style={{ background:'none', border:'none', color:index<list.length-1?T.text:T.line, fontSize:24, cursor:'pointer', padding:'0 6px' }}>›</button>
    </div>
    <div key={lead.id} style={{ animation:`${dir==='next'?'lsnext':'lsprev'} .18s ease` }}>
      {/* Zone de swipe = haut de la carte (les boutons gardent un clic normal) */}
      <div onTouchStart={e=>onStart(e.touches[0].clientX)} onTouchEnd={e=>onEnd(e.changedTouches[0].clientX)}
        onPointerDown={e=>{ onStart(e.clientX); try{ e.currentTarget.setPointerCapture(e.pointerId); }catch(_){} }}
        onPointerUp={e=>onEnd(e.clientX)} style={{ touchAction:'pan-y', cursor:'grab' }}>
        <div style={{ display:'flex', alignItems:'center', gap:12, marginBottom:16 }}>
          <Avatar name={lead.name} size={48} ring />
          <div style={{ minWidth:0 }}>
            <div style={{ ...ax, fontSize:18, fontWeight:800, display:'flex', alignItems:'center', gap:8 }}>{lead.name}
              {lead.urgent && <span style={{ ...mo, fontSize:9, fontWeight:700, color:'#fff', background:RED, padding:'2px 7px', borderRadius:999 }}>NOUVEAU</span>}</div>
            <div style={{ ...hk, fontSize:13, color:T.muted }}>{lead.phone||'pas de numéro'}{lead.plan?` · ${lead.plan}`:''}</div>
          </div>
        </div>
        <div style={{ marginBottom:14 }}>
          <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.faint, marginBottom:6 }}>HISTORIQUE</div>
          {notes===null && <div style={{ ...hk, fontSize:12, color:T.faint }}>Chargement des notes…</div>}
          {notes && notes.length===0 && <div style={{ ...hk, fontSize:12, color:T.faint }}>Aucune note pour l'instant.</div>}
          {notes && notes.length>0 && <div style={{ display:'flex', flexDirection:'column', gap:6, maxHeight:150, overflowY:'auto' }}>
            {notes.map((n,i)=>(<div key={i} style={{ ...hk, fontSize:12.5, lineHeight:1.4, color:T.text, background:T.bg, border:`1px solid ${T.line}`, borderRadius:10, padding:'8px 10px' }}>{n.text}</div>))}
          </div>}
        </div>
      </div>
      <LeadButtons lead={lead} H={H} />
    </div>
    <button onClick={onClose} style={{ width:'100%', marginTop:12, ...obtn, color:T.muted }}>Fermer</button>
  </Sheet>;
}

// RDV / relance / pas intéressé / note
function ActionSheet({ state, onClose, onDone }) {
  const { lead, mode } = state;
  const isRdv = mode==='rdv', isRel = mode==='relance', dated = isRdv||isRel;
  const [date, setDate] = useState(isRdv?plusDaysISO(1):isRel?plusDaysISO(1):'');
  const [time, setTime] = useState('18:00');
  const [note, setNote] = useState('');
  const [busy, setBusy] = useState(false);
  const titles = { rdv:'Séance d’essai le', relance:'Relancer le', pasinteresse:'Pas intéressé', note:'Note rapide' };
  const quicks = isRdv ? [['Aujourd’hui',0],['Demain',1],['Dans 2 j',2]] : isRel ? [['Demain',1],['Dans 2 j',2],['Dans 1 sem',7]] : [];
  const submit = async (skipDate) => {
    if (dated && !skipDate && isSunday(date)) { setDate(plusDaysISO(1)); return; }
    setBusy(true);
    const sig = note.trim() ? (noteSign()+' : '+note.trim()) : '';
    try {
      if (isRdv) { const p={}; if(!skipDate&&date) p.date = time?new Date(`${date}T${time}`).toISOString():date; if(sig)p.note=sig; await act(lead.id,'rdv',p); }
      else if (isRel) { if(!date){setBusy(false);return;} const p={date}; if(sig)p.note=sig; await act(lead.id,'relance',p); }
      else if (mode==='pasinteresse') { await act(lead.id,'pasinteresse', sig?{note:sig}:{}); }
      else { if(!sig){setBusy(false);return;} await act(lead.id,'note',{note:sig}); }
      onDone((isRdv||mode==='pasinteresse') ? lead.id : null);
    } catch(e){ setBusy(false); alert('Échec : '+e.message); }
  };
  return <Sheet onClose={onClose}>
    {sheetTitle(titles[mode])}
    {dated && <>
      {isRel && <>
        <div style={{ ...mo, fontSize:10, letterSpacing:0.5, color:T.faint, marginBottom:6 }}>RAPPELER AUJOURD'HUI À</div>
        <div style={{ display:'flex', gap:8, marginBottom:10, flexWrap:'wrap' }}>
          {[['Dans 1h',inHoursISO(1)],['🕛 Midi',todayAtISO(12)],['14h',todayAtISO(14)],['16h',todayAtISO(16)],['17h',todayAtISO(17)]].map(([lb,iso])=>(
            <button key={lb} onClick={()=>setDate(iso)} style={{ ...obtn, padding:'8px 12px', fontSize:13, flex:'1 0 28%', borderColor: date===iso?T.volt:T.line, color: date===iso?T.volt:T.text }}>{lb}</button>))}
        </div>
        <div style={{ ...mo, fontSize:10, letterSpacing:0.5, color:T.faint, marginBottom:6 }}>OU UN AUTRE JOUR</div>
      </>}
      <div style={{ display:'flex', gap:8, marginBottom:12, flexWrap:'wrap' }}>
        {quicks.map(([lb,n])=>(<button key={lb} onClick={()=>setDate(plusDaysISO(n))} style={{ ...obtn, flex:1, padding:'9px 0', fontSize:13 }}>{lb}</button>))}
      </div>
      <div style={{ display:'flex', gap:10, marginBottom:12 }}>
        <input type="date" value={(date||'').slice(0,10)} onChange={e=>setDate(e.target.value)} style={{ flex:1, background:T.elev, border:`1px solid ${T.line}`, borderRadius:12, padding:'12px', color:T.text, ...hk, fontSize:15, colorScheme:'dark' }} />
        {isRdv && <input type="time" value={time} onChange={e=>setTime(e.target.value)} style={{ width:110, background:T.elev, border:`1px solid ${T.line}`, borderRadius:12, padding:'12px', color:T.text, ...hk, fontSize:15, colorScheme:'dark' }} />}
      </div>
      {isSunday(date) && <div style={{ color:T.amber, fontSize:12, marginBottom:10 }}>Fermé le dimanche, choisis un autre jour.</div>}
    </>}
    <div style={{ ...mo, fontSize:11, color:T.faint, marginBottom:6 }}>{noteSign()}</div>
    <textarea value={note} onChange={e=>setNote(e.target.value)} rows={3} placeholder="Ce qui s'est dit, objection, à rappeler…" style={{ width:'100%', background:T.elev, border:`1px solid ${T.line}`, borderRadius:12, padding:'12px', color:T.text, ...hk, fontSize:14, resize:'none', marginBottom:14, outline:'none' }} />
    <div style={{ display:'flex', gap:10 }}>
      <button onClick={onClose} style={{ ...obtn, flex:1, color:T.muted }}>Annuler</button>
      {isRdv && <button onClick={()=>submit(true)} disabled={busy} style={{ ...obtn, flex:1 }}>Sans date</button>}
      <button onClick={()=>submit(false)} disabled={busy} style={{ flex:1.4, ...gbtn() }}>{busy?'…':'Valider'}</button>
    </div>
  </Sheet>;
}

function ConvSheet({ lead, team, onClose, onDone }) {
  const [type, setType] = useState(null);
  const [who, setWho] = useState(meName());
  const [busy, setBusy] = useState(false);
  const submit = async () => {
    if (!type) return; setBusy(true);
    try { await act(lead.id,'converti',{ type, convertiPar: who }); onDone(type==='Abonnement MRR'?5:10, who); }
    catch(e){ setBusy(false); alert('Échec : '+e.message); }
  };
  const TypeBtn = ({ val, lb, sub }) => (
    <button onClick={()=>setType(val)} style={{ flex:1, cursor:'pointer', borderRadius:14, padding:'14px 0', ...hk, fontWeight:800, fontSize:15,
      background: type===val?T.volt:'transparent', color: type===val?'var(--volt-ink)':T.text, border:`1px solid ${type===val?T.volt:T.line}` }}>{lb}<br/><small style={{ fontWeight:600, opacity:0.7 }}>{sub}</small></button>
  );
  return <Sheet onClose={onClose}>
    {sheetTitle('Conversion 💰')}
    <div style={{ ...hk, fontSize:13, color:T.muted, marginBottom:12 }}>Type d'offre et qui a conclu.</div>
    <div style={{ display:'flex', gap:10, marginBottom:16 }}><TypeBtn val="Abonnement MRR" lb="Abonnement" sub="+5€" /><TypeBtn val="Comptant" lb="Comptant" sub="+10€" /></div>
    <div style={{ ...mo, fontSize:11, color:T.faint, marginBottom:8 }}>CONCLU PAR</div>
    <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:16 }}>
      {team.map(n=>(<button key={n} onClick={()=>setWho(n)} style={{ cursor:'pointer', borderRadius:999, padding:'8px 14px', ...hk, fontSize:13, fontWeight:600, background:who===n?T.voltDim:'transparent', color:who===n?T.volt:T.muted, border:`1px solid ${who===n?'var(--volt-line)':T.line}` }}>{n}</button>))}
    </div>
    <div style={{ display:'flex', gap:10 }}>
      <button onClick={onClose} style={{ ...obtn, flex:1, color:T.muted }}>Annuler</button>
      <button onClick={submit} disabled={busy||!type} style={{ flex:1.4, ...btn(type?GREEN:T.line, type?GINK:T.faint) }}>{busy?'…':'Valider'}</button>
    </div>
  </Sheet>;
}

function WaSheet({ lead, onClose }) {
  return <Sheet onClose={onClose}>
    {sheetTitle('Message WhatsApp')}
    <div style={{ ...hk, fontSize:13, color:T.muted, marginBottom:12 }}>Choisis le cas, WhatsApp s'ouvre pré-rempli.</div>
    <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
      {WA_TEMPLATES(lead).map(t=>(<a key={t.label} href={waLink(lead,t.text)} target="_blank" rel="noopener" onClick={onClose} style={{ ...obtn, textAlign:'left', padding:'13px 14px', textDecoration:'none', display:'block' }}>{t.label}</a>))}
    </div>
    <button onClick={onClose} style={{ width:'100%', marginTop:12, ...obtn, color:T.muted }}>Annuler</button>
  </Sheet>;
}

// Ajout d'un prospect "Sur place" (walk-in) — 3 cas : juste passé / séance d'essai / inscrit direct.
function WalkinSheet({ onClose, onDone }) {
  const [prenom, setPrenom] = useState('');
  const [nom, setNom] = useState('');
  const [phone, setPhone] = useState('');
  const [email, setEmail] = useState('');
  const [mode, setMode] = useState('passe');
  const [type, setType] = useState('Abonnement MRR');
  const [seance, setSeance] = useState(()=>{ const d=new Date(); d.setHours(d.getHours()+1,0,0,0); return d.toISOString().slice(0,16); });
  const [note, setNote] = useState('');
  const [busy, setBusy] = useState(false);
  const inp = { width:'100%', background:T.bg, border:`1px solid ${T.line}`, borderRadius:12, padding:'12px 14px', color:T.text, ...hk, fontSize:14, outline:'none', boxSizing:'border-box' };
  const submit = async () => {
    if(!prenom.trim() && !phone.trim()){ alert('Mets au moins un prénom ou un téléphone.'); return; }
    setBusy(true);
    try {
      const sig = note.trim() ? (noteSign()+' : '+note.trim()) : '';
      const body = { prenom:prenom.trim(), nom:nom.trim(), phone:phone.trim(), email:email.trim(), mode, note:sig };
      if(mode==='essai') body.seance = new Date(seance).toISOString();
      if(mode==='inscrit') body.type = type;
      const r = await api('/api/walkin', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
      if(r && r.ok){ onDone && onDone(mode, { name:[prenom.trim(),nom.trim()].filter(Boolean).join(' '), phone:phone.trim(), type }); } else { alert('Échec : '+((r&&r.reason)||'enregistrement')); setBusy(false); }
    } catch(e){ alert('Échec : '+(e.message||'')); setBusy(false); }
  };
  const Seg = ({ id, label }) => (
    <button onClick={()=>setMode(id)} style={{ flex:1, border:'none', cursor:'pointer', borderRadius:9, padding:'10px 4px', ...hk, fontSize:12.5, fontWeight:700, background:mode===id?T.volt:'transparent', color:mode===id?T.ink:T.muted }}>{label}</button>
  );
  return <Sheet onClose={onClose}>
    {sheetTitle('➕ Prospect sur place')}
    <div style={{ display:'flex', flexDirection:'column', gap:10, marginBottom:14 }}>
      <div style={{ display:'flex', gap:10 }}>
        <input value={prenom} onChange={e=>setPrenom(e.target.value)} placeholder="Prénom" style={{ ...inp, flex:1 }} />
        <input value={nom} onChange={e=>setNom(e.target.value)} placeholder="Nom" style={{ ...inp, flex:1 }} />
      </div>
      <input value={email} onChange={e=>setEmail(e.target.value)} placeholder="Adresse mail" inputMode="email" autoCapitalize="off" style={inp} />
      <input value={phone} onChange={e=>setPhone(e.target.value)} placeholder="Téléphone" inputMode="tel" style={inp} />
    </div>
    <div style={{ ...mo, fontSize:10, letterSpacing:1, color:T.faint, marginBottom:8 }}>SON ÉTAT</div>
    <div style={{ display:'flex', gap:6, background:T.surface, borderRadius:12, padding:4, marginBottom:14, border:`1px solid ${T.line}` }}>
      <Seg id="passe" label="👋 Juste passé" /><Seg id="essai" label="📅 Séance d'essai" /><Seg id="inscrit" label="✅ Inscrit" />
    </div>
    {mode==='essai' && <div style={{ marginBottom:14 }}>
      <div style={{ ...hk, fontSize:12.5, color:T.muted, marginBottom:6 }}>Date et heure de la séance</div>
      <input type="datetime-local" value={seance} onChange={e=>setSeance(e.target.value)} style={inp} />
    </div>}
    {mode==='inscrit' && <div style={{ marginBottom:14 }}>
      <div style={{ ...hk, fontSize:12.5, color:T.muted, marginBottom:6 }}>Type d'inscription (prime créditée à ton nom)</div>
      <div style={{ display:'flex', gap:8 }}>
        {[['Abonnement MRR','Abonnement (MRR)'],['Comptant','Comptant']].map(([id,lb])=>(
          <button key={id} onClick={()=>setType(id)} style={{ flex:1, ...obtn, padding:'12px 0', background:type===id?T.voltDim:'transparent', borderColor:type===id?'var(--volt-line)':T.line, color:type===id?T.volt:T.text, fontWeight:700 }}>{lb}</button>
        ))}
      </div>
    </div>}
    <div style={{ ...mo, fontSize:11, color:T.faint, marginBottom:6 }}>{noteSign()}</div>
    <textarea value={note} onChange={e=>setNote(e.target.value)} rows={2} placeholder={mode==='essai' ? "Veut tester la muscu · vient après le boulot · amène un pote…" : mode==='inscrit' ? "Abonnement 12 mois · payé en 1 fois · objectif perte de poids…" : "Reviendra plus tard · venu avec sa femme · veut réfléchir…"} style={{ ...inp, marginBottom:14, resize:'none' }} />
    <button onClick={submit} disabled={busy} style={{ width:'100%', ...gbtn(), padding:'14px 0', fontSize:15, opacity:busy?0.6:1 }}>{busy?'Enregistrement…':mode==='inscrit'?'✅ Enregistrer l\'inscription':mode==='essai'?'📅 Enregistrer la séance':'Enregistrer le prospect'}</button>
    <button onClick={onClose} style={{ width:'100%', marginTop:10, ...obtn, color:T.muted }}>Annuler</button>
  </Sheet>;
}

// Liste des leads d'une catégorie (clic sur une chip / l'entonnoir de l'accueil).
function CategorySheet({ cat, title, who, presetLeads, H, onClose }) {
  const [leads, setLeads] = useState(presetLeads || null);
  useEffect(()=>{ if(presetLeads){ setLeads(presetLeads); return; } setLeads(null);
    api('/api/category?who='+encodeURIComponent(who)+'&cat='+encodeURIComponent(cat))
      .then(r=>setLeads((r.leads||[]).map(mapLead))).catch(()=>setLeads([]));
  },[cat]);
  return <Sheet onClose={onClose}>
    {sheetTitle(title + (leads ? ` · ${leads.length}` : ''))}
    {leads===null && <div style={{ ...hk, fontSize:13, color:T.faint, padding:'12px 0' }}>Chargement…</div>}
    {leads && leads.length===0 && <div style={{ ...hk, fontSize:13, color:T.faint, padding:'12px 0' }}>Aucun lead dans cette catégorie.</div>}
    {leads && leads.map(l=>(<LeadRow key={l.id} lead={l} urgent={l.urgent} onClick={()=>H.openLead(l, leads)} />))}
    <button onClick={onClose} style={{ width:'100%', marginTop:8, ...obtn, color:T.muted }}>Fermer</button>
  </Sheet>;
}

// Compresse une image (128px JPEG) et l'envoie comme photo de profil. Renvoie le data URL.
function uploadProfilePhoto(file, name){
  return new Promise((resolve, reject)=>{
    if(!file || !/^image\//.test(file.type)) return reject('image');
    const img = new Image();
    img.onload = ()=>{
      const S=128, cv=document.createElement('canvas'); cv.width=S; cv.height=S; const ctx=cv.getContext('2d');
      const r=Math.max(S/img.width, S/img.height), w=img.width*r, h=img.height*r;
      ctx.drawImage(img, (S-w)/2, (S-h)/2, w, h);
      let q=0.6, url=cv.toDataURL('image/jpeg', q);
      while(url.length>42000 && q>0.3){ q-=0.1; url=cv.toDataURL('image/jpeg', q); }
      URL.revokeObjectURL(img.src);
      api('/api/photo', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ dataUrl:url }) })
        .then(res=>{ if(res&&res.ok){ window.__photos = { ...(window.__photos||{}), [name]: url }; resolve(url); } else reject((res&&res.reason)||'envoi'); })
        .catch(()=>reject('envoi'));
    };
    img.onerror = ()=>reject('illisible');
    img.src = URL.createObjectURL(file);
  });
}

// Rappel fun au lancement si l'employé n'a pas de photo de profil.
function PhotoNudge({ name, onClose }){
  const ref = useRef(null);
  const [busy, setBusy] = useState(false);
  const pick = (e)=>{ const f=e.target.files&&e.target.files[0]; if(!f) return; setBusy(true);
    uploadProfilePhoto(f, name).then(()=>{ setBusy(false); onClose(true); }).catch(()=>{ setBusy(false); alert('Échec, réessaie.'); }); };
  return <div onClick={()=>onClose(false)} style={{ position:'fixed', inset:0, zIndex:88, background:'rgba(0,0,0,0.78)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}>
    <div onClick={e=>e.stopPropagation()} style={{ background:T.elev, border:`1px solid var(--volt-line)`, borderRadius:22, padding:'26px 22px', maxWidth:360, width:'100%', textAlign:'center', boxShadow:'0 20px 60px rgba(0,0,0,0.55)' }}>
      <div style={{ fontSize:46, marginBottom:8 }}>🫥</div>
      <div style={{ ...ax, fontSize:20, fontWeight:900, marginBottom:8 }}>Hé {name}, on voit pas ta tête !</div>
      <div style={{ ...hk, fontSize:14, color:T.muted, lineHeight:1.5, marginBottom:18 }}>Pour l'instant t'es un joli rond gris 🤡. Mets une photo de profil, ça motive et ça fait classe dans le classement.</div>
      <input ref={ref} type="file" accept="image/*" onChange={pick} style={{ display:'none' }} />
      <button onClick={()=>!busy && ref.current && ref.current.click()} style={{ width:'100%', ...gbtn(), padding:'14px 0', fontSize:15, marginBottom:10, opacity:busy?0.6:1 }}>{busy?'Envoi…':'📸 Mettre ma photo'}</button>
      <button onClick={()=>onClose(false)} style={{ background:'none', border:'none', color:T.faint, ...hk, fontSize:13, cursor:'pointer' }}>Plus tard…</button>
    </div>
  </div>;
}

function WinPopup({ win, onClose }) {
  const lead = win.lead;
  const p = (lead?.name||'').split(' ')[0];
  const me = meName();
  // Le partage contient le lien de l'optin PARRAINAGE (porte le prénom du parrain pour le tracking Notion).
  // wa.me direct (PAS raccourci) pour ouvrir le partage de façon fluide ; le filleul, lui, ouvre une vraie
  // page web (notre domaine) = pas de friction.
  const optinLink = 'https://invite.4dgymclub.com/parrainage.html?de='+encodeURIComponent(p||'');
  const shareText = `Je viens de m'inscrire à 4D Gym au Thor 💪 Viens tester avec moi, ta séance d'essai est offerte et on gagne chacun 1 mois. Inscris-toi ici 👉 ${optinLink}`;
  const shareLink = 'https://wa.me/?text='+encodeURIComponent(shareText);
  // Ce qu'on envoie AU nouveau membre (sur SON WhatsApp), avec le lien court cliquable dedans.
  const memberMsg = `Salut ${p}, bienvenue dans la team 💪 Petit cadeau : invite un pote et vous gagnez chacun 1 mois offert (et 2 mois pour toi si tu laisses un avis Google). Tu l'invites en 1 clic ici, choisis qui tu veux 👉 ${shareLink}`;
  const parrLink = (lead && lead.phone) ? waLink(lead, memberMsg) : 'https://wa.me/?text='+encodeURIComponent(memberMsg);
  const sendParr = () => api('/api/invite', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ who: me, channel:'parrainage', detail: lead?.name||'' }) }).catch(()=>{});
  return <div style={{ position:'fixed', inset:0, zIndex:70, background:'rgba(0,0,0,0.7)', display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}>
    <div style={{ background:T.surface, border:`1px solid var(--volt-line)`, borderRadius:24, padding:'30px 24px', textAlign:'center', maxWidth:340, ...hk, animation:'splashin .3s ease' }}>
      <div style={{ fontSize:46 }}>🎉</div>
      <div style={{ ...ax, fontSize:22, fontWeight:800, color:T.text, marginTop:8 }}>Félicitations</div>
      <div style={{ ...ax, fontSize:42, fontWeight:900, color:T.volt, margin:'8px 0' }}>+{win.gain}€</div>
      <div style={{ ...hk, fontSize:14.5, color:T.muted, marginBottom:16, lineHeight:1.4 }}>
        {win.total!=null
          ? <>Bravo <b style={{ color:T.text }}>{win.who}</b> ! Tu es à <b style={{ color:T.volt }}>{win.total}€</b> ce mois 🔥</>
          : <>Bravo <b style={{ color:T.text }}>{win.who}</b>, une conversion de plus 🔥</>}
      </div>
      <div style={{ ...hk, fontSize:13, color:T.text, fontWeight:700, marginBottom:4 }}>Dernière étape : envoie-lui le parrainage 👇</div>
      <div style={{ ...hk, fontSize:12, color:T.faint, marginBottom:12, lineHeight:1.4 }}>On envoie le message à <b style={{ color:T.text }}>{p||'lui'}</b>. Il clique le lien dedans et invite ses propres potes. 1 mois offert pour chacun.</div>
      <a href={parrLink} target="_blank" rel="noopener" onClick={()=>{ sendParr(); onClose(); }} style={{ display:'block', textDecoration:'none', textAlign:'center', ...btn('#1Fae5a','#fff'), padding:'15px 0', fontSize:15, fontWeight:800 }}>📲 Envoyer le parrainage à {p||'lui'}</a>
      <div onClick={onClose} style={{ ...hk, fontSize:12.5, color:T.faint, marginTop:12, cursor:'pointer', textDecoration:'underline' }}>Plus tard</div>
    </div>
  </div>;
}

// ════════ SPLASH de motivation (slogans, repris de la V1) ════════
const SLOGANS = [
  (n)=>`${n}, aujourd'hui tu vas tout arracher 🔥`,
  (n)=>`Allez ${n}, mode gorille activé 🦍`,
  (n)=>`${n}, chaque appel te rapproche d'un RDV. Fonce 💪`,
  (n)=>`C'est ta journée ${n}. Décroche, souris, signe.`,
  (n)=>`${n}, transforme les "peut-être" en "c'est calé" ✅`,
  (n)=>`On lâche rien ${n}, on fait péter le score 🚀`,
  (n)=>`${n}, le téléphone c'est ton arme. Charge.`,
  (n)=>`Un appel de plus que les autres ${n}, c'est ça la diff.`,
  (n)=>`${n}, les leads n'attendent que toi. Go.`,
  (n)=>`Personne ne décroche comme toi ${n}. Montre-leur 🏆`,
  (n)=>`${n}, un "non" te rapproche du prochain "oui".`,
  (n)=>`Aujourd'hui ${n}, t'es en mission. Que des signatures.`,
  (n)=>`${n}, ta voix au téléphone c'est ta meilleure pub.`,
  (n)=>`Chaque inscription c'est ta prime qui monte ${n} 💸`,
  (n)=>`${n}, chaque appel peut devenir une inscription 🔥`,
  (n)=>`Réveille le closer en toi ${n}.`,
  (n)=>`${n}, on appelle, on cale, on encaisse.`,
  (n)=>`Le premier appel est le plus dur ${n}. Après c'est du plaisir.`,
  (n)=>`${n}, souris au téléphone, ça s'entend.`,
  (n)=>`Today ${n} ne lâche aucun lead.`,
  (n)=>`${n}, t'as le téléphone, t'as le talent. Action.`,
  (n)=>`Objectif du jour ${n} : remplir le planning d'essais.`,
  (n)=>`${n}, un lead appelé dans l'heure c'est un lead converti.`,
  (n)=>`La gagne c'est un état d'esprit ${n}. Active-le.`,
  (n)=>`${n}, chaque "j'ai pas le temps" cache un "convaincs-moi".`,
  (n)=>`On va faire trembler le classement ${n} 📈`,
  (n)=>`${n}, ta cagnotte se construit appel par appel.`,
  (n)=>`Le silence ne signe pas ${n}. Décroche.`,
  (n)=>`${n}, sois la raison pour laquelle quelqu'un s'inscrit aujourd'hui.`,
  (n)=>`Pas de lead froid pour toi ${n}, juste des futurs membres.`,
  (n)=>`${n}, relance, relance, relance. C'est là que ça signe.`,
  (n)=>`T'es chaud ${n} ? Le téléphone aussi. Go.`,
  (n)=>`${n}, transforme la salle un appel à la fois.`,
  (n)=>`Aujourd'hui ${n}, t'écris ton record.`,
  (n)=>`${n}, le meilleur vendeur c'est celui qui rappelle.`,
  (n)=>`Un essai calé c'est une victoire ${n}. Empile-les.`,
  (n)=>`${n}, ils cherchent une raison de venir. Donne-la-leur.`,
  (n)=>`Une fiche, un appel, une inscription. Go ${n} 🔥`,
  (n)=>`${n}, énergie à fond, sourire branché, on y va.`,
  (n)=>`Chaque seconde au tel ${n}, c'est de l'argent.`,
  (n)=>`${n}, tu connais ta salle mieux que personne. Vends-la.`,
  (n)=>`On respire, on appelle, on signe ${n}.`,
  (n)=>`${n}, la concurrence dort, toi tu décroches.`,
  (n)=>`T'es pas là pour appeler ${n}, t'es là pour convertir.`,
  (n)=>`${n}, fais-en un de plus. Toujours un de plus.`,
  (n)=>`La régularité bat le talent ${n}. Et toi t'as les deux.`,
  (n)=>`${n}, ton planning d'essais ne va pas se remplir tout seul.`,
  (n)=>`Aujourd'hui ${n}, chaque lead repart avec une date.`,
  (n)=>`${n}, le classement adore les acharnés. Sois-en un.`,
  (n)=>`On démarre fort ${n}. Premier appel, maintenant 📞`,
  (n)=>`${n}, t'as un don pour mettre les gens à l'aise. Sers-t'en.`,
  (n)=>`Le Thor attend ses nouveaux membres ${n}. Va les chercher.`,
  (n)=>`${n}, un sourire, une date d'essai, et c'est plié.`,
  (n)=>`Le succès aime les tenaces ${n}. Rappelle encore.`,
  (n)=>`${n}, aujourd'hui on remplit la salle 🏋️`,
  // Prendre soin des adhérents (fidélisation, accueil, service) — pas que les leads
  (n)=>`${n}, un membre qu'on appelle par son prénom, c'est un membre qui reste.`,
  (n)=>`Aujourd'hui ${n}, fais sourire au moins un adhérent 😊`,
  (n)=>`${n}, prends 2 min pour montrer un exo à quelqu'un, ça change tout.`,
  (n)=>`Un adhérent bien accueilli ${n}, c'est un parrainage qui arrive.`,
  (n)=>`${n}, on signe des leads, mais on chouchoute ceux qui sont déjà là.`,
  (n)=>`T'as pas vu un habitué depuis 15 jours ${n} ? Un petit message et il revient.`,
  (n)=>`${n}, le sourire à l'accueil vaut autant qu'une signature.`,
  (n)=>`Prends des nouvelles ${n}, demande comment se passent les séances.`,
  (n)=>`${n}, une salle propre et une équipe présente, c'est ça qui fidélise.`,
  (n)=>`On garde les anciens heureux ${n}, pas que les nouveaux.`,
  (n)=>`${n}, un conseil au bon moment, c'est un abonnement qui se renouvelle.`,
  (n)=>`Repère le membre qui galère ${n} et file-lui un coup de main.`,
  (n)=>`${n}, chaque adhérent doit se sentir chez lui à 4D Gym.`,
  (n)=>`Un membre content ${n}, c'est ta meilleure pub.`,
  (n)=>`${n}, fidéliser coûte moins cher que recruter. Bichonne les adhérents.`,
  (n)=>`${n}, l'accueil d'aujourd'hui c'est la réinscription de demain.`,
  // Bonne humeur / bien-être de l'équipe — kiffer, profiter, être heureux
  (n)=>`${n}, kiffe ta journée, le reste suivra ✨`,
  (n)=>`Bonne humeur d'abord ${n}, les résultats après.`,
  (n)=>`${n}, prends du plaisir, ça s'entend au tel et à l'accueil.`,
  (n)=>`Profite ${n}, tu bosses dans une salle de ouf, savoure 😎`,
  (n)=>`${n}, un sourire pour toi avant un sourire pour les autres.`,
  (n)=>`Détends-toi ${n}, t'es exactement là où il faut.`,
  (n)=>`La bonne énergie ${n}, ça se travaille comme un muscle 💚`,
  (n)=>`${n}, fais une pause, respire, repars plus fort.`,
  (n)=>`Aujourd'hui ${n}, sois heureux d'abord, performant ensuite.`,
  (n)=>`Le boulot c'est mieux quand on rigole ${n}. Lâche-toi.`,
  (n)=>`Kiffe le moment ${n}, tu te souviendras de ces journées.`,
  (n)=>`${n}, prends soin de toi autant que des autres.`,
  (n)=>`Bonne vibe ${n} : tu donnes le ton de la salle aujourd'hui.`,
  (n)=>`${n}, profite de la vie. On bosse pour vivre, pas l'inverse.`,
  (n)=>`Un café, une bonne playlist, et c'est parti ${n} 🎶`,
  // Citations de grandes personnalités (affichées avec l'auteur)
  ()=>({ q:"La discipline est le pont entre les objectifs et les réussites.", by:"Jim Rohn" }),
  ()=>({ q:"Le succès c'est tomber sept fois, se relever huit.", by:"Proverbe japonais" }),
  ()=>({ q:"Tout le monde a un plan jusqu'au premier coup de poing.", by:"Mike Tyson" }),
  ()=>({ q:"Le seul endroit où le succès vient avant le travail, c'est le dictionnaire.", by:"Vince Lombardi" }),
  ()=>({ q:"La motivation te lance, l'habitude te fait tenir.", by:"Jim Ryun" }),
  ()=>({ q:"On ne perd jamais : soit on gagne, soit on apprend.", by:"Nelson Mandela" }),
  ()=>({ q:"Le succès, c'est aller d'échec en échec sans perdre l'enthousiasme.", by:"Winston Churchill" }),
  ()=>({ q:"Fais ce que tu peux, avec ce que tu as, là où tu es.", by:"Theodore Roosevelt" }),
  ()=>({ q:"La chance sourit aux audacieux.", by:"Virgile" }),
  ()=>({ q:"Les gens oublieront ce que tu as dit, mais jamais ce que tu leur as fait ressentir.", by:"Maya Angelou" }),
  ()=>({ q:"Sois toi-même, les autres sont déjà pris.", by:"Oscar Wilde" }),
  ()=>({ q:"Le bonheur n'est pas une destination, c'est une façon de voyager.", by:"Margaret Lee Runbeck" }),
  ()=>({ q:"Fais de ta vie un rêve, et d'un rêve une réalité.", by:"Saint-Exupéry" }),
  ()=>({ q:"Ce n'est pas la montagne qu'on conquiert, mais soi-même.", by:"Edmund Hillary" }),
];
function Splash({ text, onDone }) {
  useEffect(()=>{ const t=setTimeout(onDone, 3600); return ()=>clearTimeout(t); },[]);
  return <div onClick={onDone} style={{ position:'fixed', inset:0, zIndex:80, background:T.bg, color:T.text, ...hk, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:20, padding:30 }}>
    <img className="splash-logo" src="/icon-512.png" alt="4D Gym" width={104} height={104} style={{ borderRadius:24, boxShadow:'0 14px 40px rgba(0,0,0,0.55)' }} />
    {text && typeof text === 'object' && text.q
      ? <div style={{ maxWidth:360, textAlign:'center', animation:'splashin .35s ease' }}>
          <div style={{ ...ax, fontSize:21, fontWeight:700, fontStyle:'italic', lineHeight:1.4 }}>« {text.q} »</div>
          <div style={{ ...mo, fontSize:12.5, color:T.muted, marginTop:12, letterSpacing:0.5, textTransform:'uppercase' }}>— {text.by}</div>
        </div>
      : <div style={{ ...ax, fontSize:23, fontWeight:800, textAlign:'center', lineHeight:1.3, maxWidth:340, animation:'splashin .35s ease' }}>{text}</div>}
    <div style={{ width:120, height:4, borderRadius:4, background:'var(--track)', overflow:'hidden' }}><div style={{ height:'100%', background:T.volt, animation:'splashbar 3.6s linear forwards' }} /></div>
  </div>;
}

// Tour guidé au premier lancement : projecteur sur les vrais éléments + bulle. Impossible à quitter.
function Tour({ name, onDone }) {
  const steps = [
    { sel:null, title:`Bienvenue ${name} 👋`, text:`Je te montre l'appli en 30 secondes, écran par écran. Suis le guide, c'est rapide.` },
    { sel:'cagnotte', title:'💰 Ta cagnotte', text:`C'est ta prime du mois, en euros. Elle monte à chaque inscription que tu signes : MRR 5€, comptant 10€. Plus tu closes, plus elle grimpe 🔥` },
    { sel:'rates', title:'🎯 Tes deux taux', text:`À gauche "RDV pris" : sur les leads que tu as traités, combien ont pris un rendez-vous (ta prise de RDV au téléphone). À droite "convertis sur RDV" : sur ces RDV, combien se sont inscrits (ton closing). Plus c'est haut, mieux c'est.` },
    { sel:'funnel', title:'📉 L\'entonnoir', text:`Le parcours d'un lead, étape par étape : Leads contactés → RDV pris → Convertis (inscrits). Tu vois où ça coince. Touche "RDV pris" ou "Convertis" pour voir les fiches concernées.` },
    { sel:'stats', title:'📊 Tes leads d\'un coup d\'œil', text:`Leads actifs (à appeler), en cours, et perdus. Touche un chiffre pour ouvrir la liste correspondante.` },
    { sel:'avis', title:'⭐ Avis Google', text:`Les avis rapportent des primes à TOUTE l'équipe : 30 avis = 50€, 50 = 70€, 100 = 100€ chacun. Touche "Demander un avis" : tu montres le QR code à un membre en face à face, ou tu lui envoies le lien. On joue collectif 💚` },
    { sel:'scratch-card', title:'🎟️ Carte à gratter', text:`On tient à ce que tu puisses gâter ton entourage. Chaque semaine, une offre à faire gagner à tes proches : gratte-la, puis envoie le code par WhatsApp. Ils en profitent, et toi tu remplis la salle.` },
    { sel:'nav-leads', title:'📞 Leads', text:`Tes leads à appeler : les nouveaux en rouge (urgent), puis les relances. Touche une fiche pour agir (Appeler, WhatsApp, RDV, Converti…), glisse à gauche/droite pour passer à la suivante.` },
    { sel:'nav-sessions', title:'📅 Séances', text:`Toutes les séances d'essai du jour, triées par heure : qui vient tester la salle (Calendly ou fixée par l'équipe). Appelle ou envoie un WhatsApp en 1 touche pour confirmer.` },
    { sel:'nav-gains', title:'💳 Primes & Classement', text:`Au même endroit : ta cagnotte du mois en détail (toutes tes conversions) et le classement de l'équipe (Cagnotte € + Performance, ton taux de closing). Bascule avec le sélecteur en haut.` },
    { sel:'nav-me', title:'🤳 Ton profil', text:`Première mission : touche ton avatar (le bouton 📷) et mets TA photo, on veut voir ta tête, pas un rond gris ! Tu changes aussi la couleur de l'app et le mode Jour/Nuit. Allez ${name}, au boulot 💪` },
  ];
  const [i, setI] = useState(0);
  const [rect, setRect] = useState(null);
  const s = steps[i], last = i===steps.length-1;
  useEffect(()=>{
    if(!s.sel){ setRect(null); return; }
    const el = document.querySelector(`[data-tour="${s.sel}"]`);
    if(!el){ setRect(null); return; }
    // Centre la cible en faisant defiler le conteneur scrollable interne (pas toute la page).
    let p = el.parentElement;
    while(p){ const o=getComputedStyle(p).overflowY; if(o==='auto'||o==='scroll'){ const er=el.getBoundingClientRect(), pr=p.getBoundingClientRect(); p.scrollTop += (er.top - pr.top) - (p.clientHeight/2 - er.height/2); break; } p=p.parentElement; }
    const m = ()=> setRect(el.getBoundingClientRect());
    const raf = requestAnimationFrame(m);
    const t1 = setTimeout(m, 120), t2 = setTimeout(m, 360);
    return ()=>{ cancelAnimationFrame(raf); clearTimeout(t1); clearTimeout(t2); };
  },[i]);
  const pad=8;
  const hole = rect ? { top:Math.max(6,rect.top-pad), left:Math.max(6,rect.left-pad), width:rect.width+pad*2, height:rect.height+pad*2 } : null;
  let tip;
  if(!rect) tip = { top:'50%', transform:'translateY(-50%)' };
  else if(rect.top > window.innerHeight*0.5) tip = { bottom:(window.innerHeight - rect.top) + 16 };
  else tip = { top: rect.bottom + 16 };
  return <>
    <div style={{ position:'fixed', inset:0, zIndex:84 }} /> {/* bloque toute interaction avec l'app */}
    {hole
      ? <div style={{ position:'fixed', top:hole.top, left:hole.left, width:hole.width, height:hole.height, borderRadius:14, boxShadow:'0 0 0 9999px rgba(8,9,11,0.82)', border:'2px solid var(--volt)', zIndex:85, pointerEvents:'none', transition:'all .3s ease' }} />
      : <div style={{ position:'fixed', inset:0, background:'rgba(8,9,11,0.82)', zIndex:85, pointerEvents:'none' }} />}
    <div style={{ position:'fixed', left:18, right:18, maxWidth:440, margin:'0 auto', zIndex:86, background:T.elev, border:'1px solid var(--volt-line)', borderRadius:16, padding:18, boxShadow:'0 14px 44px rgba(0,0,0,.55)', animation:'splashin .25s ease', ...tip }}>
      <div style={{ ...ax, fontSize:18, fontWeight:800, marginBottom:6 }}>{s.title}</div>
      <div style={{ ...hk, fontSize:14, color:T.muted, lineHeight:1.5, marginBottom:14 }}>{s.text}</div>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:12 }}>
        <div style={{ display:'flex', gap:6 }}>{steps.map((_,k)=>(<span key={k} style={{ width:k===i?18:6, height:6, borderRadius:6, background:k===i?T.volt:T.line, transition:'all .25s' }} />))}</div>
        <button onClick={()=> last?onDone():setI(i+1)} style={{ ...gbtn(), padding:'10px 24px', fontSize:14 }}>{last?'Terminer 🔥':'Suivant'}</button>
      </div>
    </div>
  </>;
}

// ════════ LOGIN ════════
function Login({ onDone }) {
  const [team, setTeam] = useState([]); const [who, setWho] = useState(''); const [pwd, setPwd] = useState('');
  const [err, setErr] = useState(''); const [busy, setBusy] = useState(false); const [showPwd, setShowPwd] = useState(false);
  useEffect(()=>{ api('/api/config').then(c=>{ setTeam(c.team||[]); if(c.photos) window.__photos=c.photos; }).catch(()=>setTeam([])); },[]);
  const submit = async () => {
    setBusy(true); setErr('');
    localStorage.setItem(LS.me,who); localStorage.setItem(LS.code,pwd); localStorage.setItem(LS.codeFor,who);
    try { await api('/api/stats?who='+encodeURIComponent(who)); onDone(); }
    catch(e){ setErr('Mot de passe incorrect'); } finally { setBusy(false); }
  };
  return <div style={{ minHeight:'100%', background:T.bg, color:T.text, ...hk, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', padding:'40px 24px' }}>
    <img src="/icon-512.png" alt="4D Gym" width={92} height={92} style={{ borderRadius:22, marginBottom:10, boxShadow:'0 12px 34px rgba(0,0,0,0.55)' }} />
    <div style={{ ...mo, fontSize:19, fontWeight:700, letterSpacing:3, textTransform:'uppercase', color:T.text, marginBottom:30 }}>App staff</div>
    {!who ? <>
      <div style={{ ...hk, fontSize:16, marginBottom:16 }}>Qui es-tu ?</div>
      <div style={{ display:'flex', flexDirection:'column', gap:10, width:'100%', maxWidth:320 }}>
        {team.map(n=>(<button key={n} onClick={()=>setWho(n)} style={{ display:'flex', alignItems:'center', gap:12, background:T.surface, border:`1px solid ${T.line}`, borderRadius:14, padding:'12px 14px', color:T.text, ...hk, fontSize:16, fontWeight:600, cursor:'pointer' }}><Avatar name={n} size={36} />{n}</button>))}
        {!team.length && <div style={{ color:T.faint, fontSize:13, textAlign:'center' }}>Chargement…</div>}
      </div>
    </> : <>
      <Avatar name={who} size={56} ring />
      <div style={{ ...ax, fontSize:22, fontWeight:800, margin:'12px 0 16px' }}>{who}</div>
      <div style={{ position:'relative', width:'100%', maxWidth:320, marginBottom:10 }}>
        <input type={showPwd?'text':'password'} value={pwd} autoFocus onChange={e=>setPwd(e.target.value)} onKeyDown={e=>{ if(e.key==='Enter') submit(); }} placeholder="Mot de passe" style={{ width:'100%', background:T.surface, border:`1px solid ${err?'#ff6b6b':T.line}`, borderRadius:14, padding:'14px 46px 14px 16px', color:T.text, ...hk, fontSize:16, outline:'none', boxSizing:'border-box' }} />
        <button type="button" onClick={()=>setShowPwd(v=>!v)} aria-label={showPwd?'Masquer le mot de passe':'Afficher le mot de passe'} style={{ position:'absolute', right:6, top:'50%', transform:'translateY(-50%)', background:'none', border:'none', cursor:'pointer', padding:8, color:showPwd?T.text:T.muted, display:'flex', alignItems:'center' }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">
            <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" /><circle cx="12" cy="12" r="3" />
            {!showPwd && <path d="M4 4l16 16" />}
          </svg>
        </button>
      </div>
      {err && <div style={{ color:'#ff8a8a', fontSize:13, marginBottom:10 }}>{err}</div>}
      <button onClick={submit} disabled={busy} style={{ width:'100%', maxWidth:320, ...gbtn(), fontSize:16, padding:'14px 0', marginBottom:12 }}>{busy?'…':'Se connecter'}</button>
      <button onClick={()=>{ setWho(''); setPwd(''); setErr(''); }} style={{ background:'none', border:'none', color:T.muted, ...hk, fontSize:13, cursor:'pointer' }}>Changer d'utilisateur</button>
    </>}
  </div>;
}

// ════════ APP ════════
function App() {
  const [authed, setAuthed] = useState(!!localStorage.getItem(LS.code));
  const [tab, setTab] = useState('home');
  const [d, setD] = useState(null);
  const [err, setErr] = useState('');
  const [leadSheet, setLeadSheet] = useState(null);
  const [actionSheet, setActionSheet] = useState(null);
  const [convSheet, setConvSheet] = useState(null);
  const [waSheet, setWaSheet] = useState(null);
  const [win, setWin] = useState(null);
  const [splash, setSplash] = useState(null);
  const [ver, setVer] = useState(0);
  const [catSheet, setCatSheet] = useState(null);
  const [onboard, setOnboard] = useState(false);
  const [photoNudge, setPhotoNudge] = useState(false);
  const [walkin, setWalkin] = useState(false);
  const [viewAs, setViewAsState] = useState(viewAsName());
  const isAdmin = meName() === ADMIN;
  const setViewAs = (name) => {
    if (name) { sessionStorage.setItem('4ds_viewas', name); } else { sessionStorage.removeItem('4ds_viewas'); }
    setViewAsState(name||''); setTab('home'); setD(null); setErr('');
  };
  // Garde-fou : aucune action d'écriture en vue admin (on regarde, on ne modifie pas l'identité du membre).
  const guardRO = () => { if (viewAsName()) { alert('👁 Tu es en vue admin (lecture seule).\nReviens sur ton profil pour agir.'); return true; } return false; };

  const load = async (opts={}) => {
    const me = effName();
    const impersonating = !!viewAsName();
    const light = !!opts.light; // refresh léger : pas de log connexion, pas de gros scan classement
    try {
      const [stats, leadsR, convR, revR, cfgR] = await Promise.all([
        api('/api/stats?who='+encodeURIComponent(me)), api('/api/leads'),
        api('/api/conversions?who='+encodeURIComponent(me)),
        api('/api/reviews').catch(()=>null), api('/api/config').catch(()=>null),
      ]);
      if (cfgR && cfgR.photos) window.__photos = cfgR.photos;
      const allMapped = (leadsR.leads||[]).map(mapLead);
      const myLeads = allMapped.filter(l=>l.assigne===me||!l.assigne)
        // nouveaux (urgent) d'abord, puis relances triées par date (la plus en retard / ancienne en premier)
        .sort((a,b)=>{ if(a.urgent!==b.urgent) return a.urgent?-1:1; if(a.urgent) return 0; return (a.relanceLe||'9999').localeCompare(b.relanceLe||'9999'); });
      setD(prev => ({ me, stats, goal:300, cagnotte:(stats.cagnotte||0) + (revR?.prime||0), inscrits:stats.closes||0, pctConv:stats.pctConv||0,
        leads:myLeads, allLeads:allMapped, conversions:convR.items||[], leaderboard:(prev&&prev.leaderboard)||[], team:(cfgR&&cfgR.team)||[], reviews:revR }));
      setVer(v=>v+1);
      // Enregistre la connexion du membre (throttlé côté serveur). PAS en vue admin ni en refresh léger.
      if (!impersonating && !light) api('/api/seen', { method:'POST' }).catch(()=>{});
      // Classement (scan lourd des leads) : seulement en chargement complet, pas à chaque refresh léger.
      if (!light) api('/api/leaderboard?me='+encodeURIComponent(me)).then(lb=> setD(prev=> prev ? { ...prev, leaderboard: lb.items||[] } : prev)).catch(()=>{});
    } catch(e){ if(e.auth){ logout(); return; } setErr(e.message||'Erreur'); }
  };
  const logout = () => { localStorage.removeItem(LS.code); sessionStorage.removeItem('4ds_splash'); setAuthed(false); setD(null); setTab('home'); };
  useEffect(()=>{ if(authed) load(); },[authed, viewAs]);
  // Refresh léger auto toutes les 90s, uniquement si l'app est au premier plan et qu'aucune fenêtre n'est ouverte.
  useEffect(()=>{ if(!authed) return;
    const id = setInterval(()=>{ if(document.visibilityState==='visible' && !leadSheet && !actionSheet && !convSheet && !walkin && !catSheet) load({light:true}); }, 90000);
    return ()=>clearInterval(id);
  },[authed, leadSheet, actionSheet, convSheet, walkin, catSheet]);
  // Refresh quand l'app revient au premier plan (rouvre le raccourci / rebascule dessus).
  useEffect(()=>{ if(!authed) return;
    const onVis = ()=>{ if(document.visibilityState==='visible') load({light:true}); };
    document.addEventListener('visibilitychange', onVis); window.addEventListener('focus', onVis);
    return ()=>{ document.removeEventListener('visibilitychange', onVis); window.removeEventListener('focus', onVis); };
  },[authed]);
  // Splash de motivation : une fois par session, au lancement (pas à chaque refresh de données)
  useEffect(()=>{ if(authed && !sessionStorage.getItem('4ds_splash')){ const n=meName(); const QN=14; /* citations en fin de liste */ const idx = Math.random()<0.3 ? (SLOGANS.length-QN+Math.floor(Math.random()*QN)) : Math.floor(Math.random()*(SLOGANS.length-QN)); setSplash(SLOGANS[idx](n)); sessionStorage.setItem('4ds_splash','1'); } },[authed]);
  // Tour guidé : une seule fois par appareil, au premier lancement (force l'Accueil).
  useEffect(()=>{ if(authed && !localStorage.getItem('4ds_onboarded')){ setTab('home'); setOnboard(true); } },[authed]);
  // Rappel photo de profil (fun) : 1×/session si pas de photo, et seulement après le tour de bienvenue.
  useEffect(()=>{ if(authed && d && !onboard && !viewAs && !sessionStorage.getItem('4ds_photonudge') && !(window.__photos||{})[d.me]){ sessionStorage.setItem('4ds_photonudge','1'); setPhotoNudge(true); } },[d, authed, onboard]);

  const closeAll = () => { setLeadSheet(null); setActionSheet(null); setConvSheet(null); setWaSheet(null); setCatSheet(null); };
  const dropLead = (id) => setD(prev => prev ? { ...prev, leads: prev.leads.filter(l=>l.id!==id) } : prev);
  const afterAction = (dropId) => { closeAll(); if (dropId) dropLead(dropId); load(); };

  // Handlers passés à tous les écrans/cartes
  const H = {
    call: (l) => { if(l.phone) window.location.href='tel:'+l.phone.replace(/\s/g,''); },
    wa: (l) => { setLeadSheet(null); setWaSheet({ lead:l }); },
    conv: (l) => { if(guardRO())return; setLeadSheet(null); setConvSheet({ lead:l }); },
    open: (l, mode) => { if(guardRO())return; setLeadSheet(null); setActionSheet({ lead:l, mode }); },
    mauvais: async (l) => { if(guardRO())return; try{ await act(l.id,'mauvaisnum',{}); afterAction(l.id); }catch(e){ alert('Échec : '+e.message); } },
    noAnswer: async (l) => { if(guardRO())return; try{ await act(l.id,'relance',{ date: inHoursISO(2) }); closeAll(); load(); }catch(e){ alert('Échec : '+e.message); } },
    deconv: async (l) => { if(guardRO())return; try{ await act(l.id,'deconverti',{}); closeAll(); load(); }catch(e){ alert('Échec : '+e.message); } },
    openLead: (l, list) => { const L = (list&&list.length)?list:(d?d.leads:[l]); const i = Math.max(0, L.findIndex(x=>x.id===l.id)); setLeadSheet({ list:L, index:i }); },
    openCat: (cat, title, presetLeads) => setCatSheet({ cat, title, presetLeads }),
    replayTour: () => { closeAll(); setTab('home'); setOnboard(true); },
    walkin: () => { if(guardRO())return; setWalkin(true); },
    isAdmin, viewAs, setViewAs,
  };

  if (!authed) return <Login onDone={()=>setAuthed(true)} />;
  if (err) return <div style={{ minHeight:'100%', background:T.bg, color:T.text, ...hk, display:'flex', flexDirection:'column', gap:12, alignItems:'center', justifyContent:'center', padding:24 }}><div>Erreur : {err}</div><button onClick={()=>{ setErr(''); load(); }} style={{ ...gbtn(), padding:'10px 18px' }}>Réessayer</button></div>;
  if (!d) return <><div style={{ minHeight:'100%', background:T.bg, color:T.muted, ...hk, display:'flex', alignItems:'center', justifyContent:'center' }}>Chargement…</div>{splash && <Splash text={splash} onDone={()=>setSplash(null)} />}</>;

  const go = (t) => { setTab(t); load(); };
  const props = { d, go, H, ver };
  return <div style={{ height:'100%', paddingTop: viewAs?32:0, boxSizing:'border-box' }}>
    {viewAs && <div onClick={()=>setViewAs('')} style={{ position:'fixed', top:0, left:'50%', transform:'translateX(-50%)', zIndex:9999, width:'100%', maxWidth:480, background:'#7c3aed', color:'#fff', ...hk, fontSize:12.5, fontWeight:700, padding:'7px 16px', display:'flex', alignItems:'center', justifyContent:'space-between', cursor:'pointer', boxShadow:'0 2px 10px rgba(0,0,0,0.35)' }}>
      <span>👁 Vue de <b>{viewAs}</b> · lecture seule</span>
      <span style={{ textDecoration:'underline' }}>Revenir à mon profil</span>
    </div>}
    {tab==='home' && <ScreenDashboard {...props} />}
    {tab==='leads' && <ScreenLeads {...props} />}
    {tab==='sessions' && <ScreenSessions d={d} go={go} H={H} />}
    {tab==='gains' && <ScreenGains d={d} go={go} H={H} />}
    {tab==='me' && <ScreenProfile d={d} go={go} H={H} onLogout={logout} />}
    {catSheet && <CategorySheet cat={catSheet.cat} title={catSheet.title} who={meName()} presetLeads={catSheet.presetLeads} H={H} onClose={()=>setCatSheet(null)} />}
    {leadSheet && <LeadSheet state={leadSheet} setSheet={setLeadSheet} H={H} onClose={()=>setLeadSheet(null)} />}
    {actionSheet && <ActionSheet state={actionSheet} onClose={()=>setActionSheet(null)} onDone={afterAction} />}
    {convSheet && <ConvSheet lead={convSheet.lead} team={d.team} onClose={()=>setConvSheet(null)} onDone={(gain, who)=>{ const lead=convSheet.lead; const id=lead.id; closeAll(); dropLead(id); const total = who===meName() ? (d.cagnotte||0)+gain : null; setWin({ gain, who, total, lead }); load(); }} />}
    {waSheet && <WaSheet lead={waSheet.lead} onClose={()=>setWaSheet(null)} />}
    {walkin && <WalkinSheet onClose={()=>setWalkin(false)} onDone={(mode, info)=>{ setWalkin(false);
      if(mode==='inscrit'){ const gain = (info&&info.type==='Comptant') ? (d.stats?.primeComptant||10) : (d.stats?.primeMrr||5); setWin({ gain, who:meName(), total:(d.cagnotte||0)+gain, lead:{ name:(info&&info.name)||'', phone:(info&&info.phone)||'' } }); setTab('home'); }
      else { setTab(mode==='essai'?'sessions':'leads'); }
      load(); }} />}
    {win && <WinPopup win={win} onClose={()=>setWin(null)} />}
    {splash && <Splash text={splash} onDone={()=>setSplash(null)} />}
    {onboard && tab==='home' && <Tour name={d.me} onDone={()=>{ localStorage.setItem('4ds_onboarded','1'); setOnboard(false); }} />}
    {photoNudge && <PhotoNudge name={d.me} onClose={(done)=>{ setPhotoNudge(false); if(done) setVer(v=>v+1); }} />}
  </div>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
