/* eslint-disable */
/*
  Symptothermie — application de suivi du cycle (symptothermie moderne).
  Port fidèle du prototype de design (React sans étape de build, via Babel).
  Le moteur d'analyse (rank / analyze / opening / statusFor) reproduit
  la classe `Component` du handoff, source de vérité métier.
*/
const { useState, useEffect, useRef, useMemo } = React;

/* ══════════════════════════════════════════════════════════════
   1. Utilitaires de dates réelles (jour de cycle dérivé d'une date)
   ══════════════════════════════════════════════════════════════ */
const DAY_MS = 86400000;
const pad2 = (n) => String(n).padStart(2, '0');
const ymd = (d) => d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate());
const parseYMD = (s) => { const [y, m, d] = s.split('-').map(Number); return new Date(y, m - 1, d); };
const isoToday = () => ymd(new Date());
const daysBetween = (a, b) => Math.round((parseYMD(b) - parseYMD(a)) / DAY_MS);
const addDays = (s, n) => { const d = parseYMD(s); d.setDate(d.getDate() + n); return ymd(d); };
const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
const frLong = (s) => cap(new Intl.DateTimeFormat('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' }).format(parseYMD(s)));
const frShort = (s) => new Intl.DateTimeFormat('fr-FR', { day: 'numeric', month: 'short' }).format(parseYMD(s));
const decimal = (n) => n.toFixed(2).replace('.', ',');
const decimal1 = (n) => n.toFixed(1).replace('.', ',');

/* ══════════════════════════════════════════════════════════════
   2. Moteur d'analyse — PORTÉ FIDÈLEMENT du prototype
   ══════════════════════════════════════════════════════════════ */
function rank(e) {
  if (!e) return 0;
  if (e.sens === 'mouillee' || e.glaire === 'filant') return 3;
  if (e.sens === 'humide' || e.glaire === 'cremeux') return 2;
  if (e.glaire === 'infertile') return 1;
  return 0;
}

function analyze(entries) {
  const t = entries.map((e) => e.temp);
  const n = entries.length;
  let firstHigh = -1, baseline = null, sommet = -1;
  for (let i = 6; i < n; i++) {
    if (t[i] == null) continue;
    const prev = [];
    for (let j = i - 6; j < i; j++) if (t[j] != null) prev.push(t[j]);
    if (prev.length < 4) continue;
    const m = Math.max.apply(null, prev);
    if (t[i] > m + 0.001) {
      let s = -1;
      for (let j = 0; j < i; j++) if (rank(entries[j]) >= 3) s = j;
      if (s < 0) continue;
      firstHigh = i; baseline = m; sommet = s;
      break;
    }
  }
  if (firstHigh < 0) return { done: false };
  let close = -1, third = -1;
  if (firstHigh + 2 < n && t[firstHigh + 1] != null && t[firstHigh + 2] != null &&
      t[firstHigh + 1] > baseline && t[firstHigh + 2] > baseline) {
    third = firstHigh + 2;
    if (t[third] >= baseline + 0.195) close = third;
    else if (firstHigh + 3 < n && t[firstHigh + 3] != null && t[firstHigh + 3] > baseline) close = firstHigh + 3;
  }
  return { done: close >= 0, firstHigh, baseline, sommet, close, third };
}

function opening(entries, settings, pastCycles) {
  const shortCycle = pastCycles.some((c) => c.len <= 25);
  if (shortCycle) return 1;
  let base = 6;
  if (settings.mode === 'experte' && pastCycles.length) {
    const earliest = Math.min.apply(null, pastCycles.map((c) => c.firstHigh).filter((x) => x > 0));
    if (isFinite(earliest)) base = Math.max(1, earliest - 8);
  }
  let firstGlaire = 99;
  for (let i = 0; i < entries.length; i++) {
    if (rank(entries[i]) >= 2) { firstGlaire = entries[i].d; break; }
  }
  return Math.min(base, firstGlaire);
}

const PHASE = {
  post:    { label: 'Infertile',    bg: '#DCC8EC', ink: '#5F4A78', sh: 'rgba(185,155,214,0.4)' },
  regles:  { label: 'Règles',       bg: '#F2BFCB', ink: '#8F4B5E', sh: 'rgba(232,160,180,0.45)' },
  pre:     { label: 'Infertile',    bg: '#F1E0B8', ink: '#8A6D3B', sh: 'rgba(224,196,140,0.4)' },
  tres:    { label: 'Très fertile', bg: '#F0AC90', ink: '#7E4630', sh: 'rgba(226,150,118,0.45)' },
  fertile: { label: 'Fertile',      bg: '#F6CDB4', ink: '#8A5A3E', sh: 'rgba(230,170,135,0.42)' },
};

function statusFor(day, entries, ana, open) {
  const e = entries[day - 1];
  const menses = e && (e.saign === 'fort' || e.saign === 'moyen');
  if (ana.done && day >= ana.close + 1) return { key: 'post', ...PHASE.post };
  if (day < open) {
    if (menses) return { key: 'regles', ...PHASE.regles };
    return { key: 'pre', ...PHASE.pre };
  }
  if (ana.done && day <= ana.close) {
    if (rank(e) >= 3) return { key: 'tres', ...PHASE.tres };
    return { key: 'fertile', ...PHASE.fertile };
  }
  if (!ana.done) {
    if (rank(e) >= 3) return { key: 'tres', ...PHASE.tres };
    return { key: 'fertile', ...PHASE.fertile };
  }
  return { key: 'post', ...PHASE.post };
}

/* ══════════════════════════════════════════════════════════════
   3. Libellés
   ══════════════════════════════════════════════════════════════ */
const glaireLabels = { rien: 'Rien observé', cremeux: 'Crémeux, épais', filant: 'Transparent, filant', infertile: 'Jaunâtre (post-ov.)' };
const sensLabels = { seche: 'sèche', humide: 'humide', mouillee: 'mouillée' };
const statusSubs = {
  post: 'Ta fenêtre est fermée, tu peux souffler',
  pre: 'Phase pré-ovulatoire, avant l’ouverture',
  fertile: 'Ta fenêtre de fertilité est ouverte',
  tres: 'Jours de grand bâtonnet : fertilité optimum',
  regles: 'Prends soin de toi aujourd’hui',
};

/* ══════════════════════════════════════════════════════════════
   4. Modèle de données & persistance (localStorage + Supabase)
   ══════════════════════════════════════════════════════════════ */
const LS_KEY = 'symptoAppV2';

const emptyEntry = (d) => ({ d, temp: null, sens: null, glaire: null, col: null, saign: null, rapport: null, perturb: [], note: '' });

function seed() {
  // Cycle d'exemple du handoff (fictif mais conforme) : aujourd'hui = J20.
  const cycleStart = addDays(isoToday(), -19);
  const entries = [
    { d: 1,  temp: 36.55, saign: 'fort' },
    { d: 2,  temp: 36.50, saign: 'fort' },
    { d: 3,  temp: 36.52, saign: 'moyen' },
    { d: 4,  temp: 36.48, saign: 'moyen' },
    { d: 5,  temp: 36.50, saign: 'spotting' },
    { d: 6,  temp: 36.45, sens: 'seche',    glaire: 'rien' },
    { d: 7,  temp: 36.50, sens: 'seche',    glaire: 'rien' },
    { d: 8,  temp: 36.47, sens: 'humide',   glaire: 'rien' },
    { d: 9,  temp: 36.52, sens: 'humide',   glaire: 'cremeux' },
    { d: 10, temp: 36.48, sens: 'humide',   glaire: 'cremeux' },
    { d: 11, temp: 36.45, sens: 'mouillee', glaire: 'filant' },
    { d: 12, temp: 36.42, sens: 'mouillee', glaire: 'filant' },
    { d: 13, temp: 36.40, sens: 'mouillee', glaire: 'filant' },
    { d: 14, temp: 36.45, sens: 'mouillee', glaire: 'filant' },
    { d: 15, temp: 36.65, sens: 'humide',   glaire: 'cremeux' },
    { d: 16, temp: 36.78, sens: 'seche',    glaire: 'infertile' },
    { d: 17, temp: 36.82, sens: 'seche',    glaire: 'rien' },
    { d: 18, temp: 36.85, sens: 'seche',    glaire: 'rien' },
    { d: 19, temp: 36.80, sens: 'seche',    glaire: 'rien' },
    { d: 20, temp: 36.83, sens: 'seche',    glaire: 'rien' },
  ].map((e) => Object.assign(emptyEntry(e.d), e));
  return {
    cycleStart,
    entries,
    settings: { mode: 'debutante', heure: '7h00', objectif: 'contraception', rappel: true, ambiance: true, afficherCol: true, prenom: '' },
    pastCycles: [
      { name: 'Cycle précédent', len: 28, open: 6, sommet: 13, close: 16, firstHigh: 14 },
      { name: 'Il y a 2 cycles', len: 29, open: 6, sommet: 15, close: 18, firstHigh: 16 },
    ],
    updatedAt: 0,
  };
}

// Données d'une vraie nouvelle utilisatrice (onboarding) : cycle démarré à la
// date fournie, aucun historique fabriqué (les règles d'analyse ne s'appliquent
// pleinement qu'à partir du 4e cycle observé).
function freshData(cycleStart, prenom) {
  const settings = seed().settings;
  if (prenom != null) settings.prenom = prenom;
  const d1 = Object.assign(emptyEntry(1), { saign: 'fort' });
  return { cycleStart, entries: [d1], settings, pastCycles: [], updatedAt: 0 };
}

// Normalise un blob : garantit une entrée dense pour chaque jour jusqu'à aujourd'hui.
function ensureToday(data) {
  const d = JSON.parse(JSON.stringify(data));
  if (!d.settings) d.settings = seed().settings;
  else d.settings = Object.assign({}, seed().settings, d.settings);
  if (!Array.isArray(d.entries)) d.entries = [];
  if (!Array.isArray(d.pastCycles)) d.pastCycles = [];
  if (!d.cycleStart) d.cycleStart = addDays(isoToday(), -(d.entries.length - 1 || 0));
  d.entries.forEach((e, i) => { if (!e || e.d == null) d.entries[i] = emptyEntry(i + 1); if (!Array.isArray(d.entries[i].perturb)) d.entries[i].perturb = []; });
  let today = daysBetween(d.cycleStart, isoToday()) + 1;
  if (today < 1) today = 1;
  while (d.entries.length < today) d.entries.push(emptyEntry(d.entries.length + 1));
  return d;
}

// Cache local scopé par utilisateur (évite toute fuite entre comptes sur un appareil partagé).
const lsKey = (uid) => LS_KEY + (uid ? ':' + uid : '');
function loadLocal(uid) { try { const r = localStorage.getItem(lsKey(uid)); return r ? JSON.parse(r) : null; } catch (e) { return null; } }
function saveLocal(uid, d) { try { localStorage.setItem(lsKey(uid), JSON.stringify(d)); } catch (e) {} }

// Supabase (optionnel)
const CFG = window.SYMPTO_CONFIG || {};
// Création paresseuse : robuste si supabase-js se charge juste après app.jsx.
let _supa = null;
function getSupa() {
  if (_supa) return _supa;
  if (CFG.supabaseUrl && CFG.supabaseKey && window.supabase) {
    _supa = window.supabase.createClient(CFG.supabaseUrl, CFG.supabaseKey);
  }
  return _supa;
}
async function pullRemote(uid) {
  const c = getSupa();
  if (!c || !uid) return null;
  try {
    const { data, error } = await c.from('sympto_data').select('data').eq('user_id', uid).maybeSingle();
    if (error) return null;
    return data && data.data ? data.data : null;
  } catch (e) { return null; }
}
async function pushRemote(uid, blob) {
  const c = getSupa();
  if (!c || !uid) return;
  try { await c.from('sympto_data').upsert({ user_id: uid, data: blob, updated_at: new Date().toISOString() }); } catch (e) {}
}

// Messages d'erreur d'auth en français.
function frAuthError(msg) {
  const m = (msg || '').toLowerCase();
  if (m.includes('invalid login')) return 'Email ou mot de passe incorrect.';
  if (m.includes('already registered') || m.includes('already been registered') || m.includes('user already')) return 'Un compte existe déjà avec cet email. Connecte-toi.';
  if (m.includes('password should be at least') || m.includes('at least 6')) return 'Le mot de passe doit faire au moins 6 caractères.';
  if (m.includes('unable to validate email') || m.includes('invalid email') || m.includes('invalid format')) return 'Adresse email invalide.';
  if (m.includes('email not confirmed')) return 'Email pas encore confirmé — vérifie ta boîte mail.';
  if (m.includes('signups not allowed') || m.includes('signup is disabled')) return 'Les inscriptions sont désactivées côté serveur.';
  if (m.includes('failed to fetch') || m.includes('network')) return 'Connexion au serveur impossible. Réessaie.';
  return msg || 'Une erreur est survenue.';
}

/* ══════════════════════════════════════════════════════════════
   5. Petits composants réutilisables
   ══════════════════════════════════════════════════════════════ */
const CARD = { background: '#FFFFFF', borderRadius: 22, padding: 16, boxShadow: '0 6px 20px rgba(214,168,150,0.16)' };
const CARD_LG = { background: '#FFFFFF', borderRadius: 24, padding: 20, boxShadow: '0 6px 20px rgba(214,168,150,0.16)' };
const SECT_LABEL = { fontSize: 13, fontWeight: 700, color: '#C67A61', textTransform: 'uppercase', letterSpacing: '0.8px' };
const CTA = { width: '100%', minHeight: 54, border: 'none', borderRadius: 999, background: 'linear-gradient(135deg,#F2B49B,#E8A0B4)', color: '#FFFFFF', fontFamily: 'Quicksand,sans-serif', fontSize: 16, fontWeight: 700, cursor: 'pointer', boxShadow: '0 10px 26px rgba(232,160,180,0.4)' };

function Chip({ label, active, onClick, small }) {
  const st = active ? { bg: '#F8E3D6', bd: '#E8A18B', tc: '#B05F44' } : { bg: '#FDF9F5', bd: '#F0E2D6', tc: '#A8908D' };
  return (
    <button onClick={onClick} style={{
      padding: small ? '0 14px' : '0 16px', minHeight: 44, borderRadius: 999,
      border: '1.5px solid ' + st.bd, background: st.bg, color: st.tc,
      fontFamily: 'Quicksand,sans-serif', fontSize: small ? 13 : 14, fontWeight: 600, cursor: 'pointer',
    }}>{label}</button>
  );
}

function ChipRow({ children }) {
  return <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>{children}</div>;
}

function Section({ label, children }) {
  return (
    <div>
      <div style={{ ...SECT_LABEL, marginBottom: 10 }}>{label}</div>
      <ChipRow>{children}</ChipRow>
    </div>
  );
}

function Blobs({ on }) {
  if (!on) return null;
  const blob = (size, top, left, color, dur, delay) => (
    <div style={{
      position: 'absolute', width: size, height: size, top, left, borderRadius: '50%',
      background: color, filter: 'blur(42px)', opacity: 0.5,
      animation: 'floaty ' + dur + 's ease-in-out ' + delay + 's infinite alternate',
      pointerEvents: 'none', zIndex: 0,
    }} />
  );
  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none', zIndex: 0 }}>
      {blob(190, '4%', '-12%', '#F6CDB4', 11, 0)}
      {blob(160, '38%', '72%', '#DCC8EC', 13, 1.2)}
      {blob(150, '74%', '-8%', '#F2BFCB', 12, 0.6)}
      {blob(130, '12%', '68%', '#F0D9A8', 14, 2)}
    </div>
  );
}

function Burst() {
  const colors = ['#F2BFCB', '#F6CDB4', '#DCC8EC', '#F0D9A8', '#F0AC90'];
  const petals = [];
  for (let i = 0; i < 16; i++) {
    const left = 8 + (i * 37) % 84;
    petals.push(<div key={'pt' + i} style={{
      position: 'absolute', top: '26%', left: left + '%',
      width: 10 + (i % 3) * 4, height: 14 + (i % 4) * 4,
      borderRadius: '50% 50% 50% 50% / 60% 60% 40% 40%',
      background: colors[i % colors.length],
      animation: 'petalFall ' + (0.9 + (i % 5) * 0.14) + 's ease-in ' + ((i % 4) * 0.08) + 's forwards',
      opacity: 0,
    }} />);
  }
  return (
    <div style={{ position: 'absolute', inset: 0, zIndex: 60, pointerEvents: 'none' }}>
      <div style={{
        position: 'absolute', top: '38%', left: '50%', transform: 'translateX(-50%)',
        background: '#FFFFFF', borderRadius: 26, padding: '22px 30px',
        boxShadow: '0 20px 50px rgba(180,130,110,0.3)', textAlign: 'center', animation: 'popIn 0.4s ease',
      }}>
        <div style={{ width: 52, height: 52, borderRadius: '50%', background: 'linear-gradient(135deg,#F2B49B,#E8A0B4)', margin: '0 auto 10px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 26, fontWeight: 700 }}>✓</div>
        <div style={{ fontSize: 17, fontWeight: 700, color: '#5A474D' }}>C’est noté !</div>
        <div style={{ fontSize: 13, fontWeight: 500, color: '#A8908D', marginTop: 4 }}>Belle journée à toi</div>
      </div>
      {petals}
    </div>
  );
}

function Onboarding({ defaultDate, onFinish, onExample }) {
  const [date, setDate] = useState(defaultDate);
  const [prenom, setPrenom] = useState('');
  const inputStyle = { width: '100%', boxSizing: 'border-box', border: '1.5px solid #F4E6DC', borderRadius: 12, padding: '12px 14px', fontFamily: 'Quicksand,sans-serif', fontSize: 15, fontWeight: 600, color: '#5A474D', background: '#FDF9F5', outline: 'none' };
  const jour = date ? Math.max(1, daysBetween(date, defaultDate) + 1) : null;
  return (
    <React.Fragment>
      <Blobs on={true} />
      <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: '92px 26px 40px', boxSizing: 'border-box', animation: 'fadeUp 0.5s ease', WebkitOverflowScrolling: 'touch' }}>
        <div style={{ fontFamily: 'Caveat,cursive', fontSize: 44, fontWeight: 600, color: '#C67A61', lineHeight: 1 }}>Bienvenue ✿</div>
        <div style={{ fontSize: 15, fontWeight: 500, color: '#A8908D', marginTop: 10, lineHeight: 1.55 }}>Je vais t'accompagner tout doux dans l'observation de ton cycle. Pour commencer, deux petites choses.</div>

        <div style={{ ...CARD_LG, marginTop: 26 }}>
          <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Ton prénom</div>
          <input type="text" value={prenom} onChange={(e) => setPrenom(e.target.value)} placeholder="pour te dire bonjour (optionnel)" style={inputStyle} />
        </div>

        <div style={{ ...CARD_LG, marginTop: 14 }}>
          <div style={{ ...SECT_LABEL, marginBottom: 6 }}>Tes dernières règles</div>
          <div style={{ fontSize: 13, fontWeight: 500, color: '#A8908D', lineHeight: 1.5, marginBottom: 12 }}>Quel jour ont commencé tes dernières règles ? C'est le premier jour de ton cycle en cours.</div>
          <input type="date" max={defaultDate} value={date} onChange={(e) => setDate(e.target.value)} style={inputStyle} />
          {jour && <div style={{ fontSize: 13, fontWeight: 700, color: '#C67A61', marginTop: 12, textAlign: 'center' }}>{'Tu es à J' + jour + ' de ton cycle'}</div>}
        </div>

        <button onClick={() => date && onFinish(date, prenom.trim())} disabled={!date} style={{ ...CTA, marginTop: 22, opacity: date ? 1 : 0.5 }}>C'est parti</button>
        <button onClick={onExample} style={{ display: 'block', margin: '14px auto 0', background: 'none', border: 'none', color: '#A8908D', fontFamily: 'Quicksand,sans-serif', fontSize: 13.5, fontWeight: 600, cursor: 'pointer', textDecoration: 'underline' }}>Explorer avec un exemple d'abord</button>
      </div>
    </React.Fragment>
  );
}

const AUTH_INPUT = { width: '100%', boxSizing: 'border-box', border: '1.5px solid #F4E6DC', borderRadius: 12, padding: '13px 14px', fontFamily: 'Quicksand,sans-serif', fontSize: 15, fontWeight: 600, color: '#5A474D', background: '#FDF9F5', outline: 'none' };
const AUTH_LINK = { display: 'block', margin: '14px auto 0', background: 'none', border: 'none', color: '#A8908D', fontFamily: 'Quicksand,sans-serif', fontSize: 13.5, fontWeight: 600, cursor: 'pointer' };
const authErr = (t) => <div style={{ fontSize: 13, fontWeight: 600, color: '#B05F44', background: '#F8E3D6', borderRadius: 12, padding: '10px 12px', lineHeight: 1.4 }}>{t}</div>;
const authOk = (t) => <div style={{ fontSize: 13, fontWeight: 600, color: '#5F7A5A', background: '#E4F0DE', borderRadius: 12, padding: '10px 12px', lineHeight: 1.4 }}>{t}</div>;

function Auth() {
  const [mode, setMode] = useState('login'); // 'login' | 'signup' | 'forgot'
  const [email, setEmail] = useState('');
  const [pw, setPw] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [info, setInfo] = useState('');

  const submit = async () => {
    setErr(''); setInfo('');
    const c = getSupa();
    if (!c) { setErr('Le serveur n’est pas configuré.'); return; }
    if (!email) { setErr('Renseigne ton email.'); return; }
    if (mode !== 'forgot' && !pw) { setErr('Renseigne ton mot de passe.'); return; }
    setBusy(true);
    try {
      if (mode === 'forgot') {
        const { error } = await c.auth.resetPasswordForEmail(email.trim(), { redirectTo: window.location.origin + window.location.pathname });
        if (error) throw error;
        setInfo('Si un compte existe avec cet email, tu recevras un lien pour choisir un nouveau mot de passe. Pense à regarder tes spams.');
      } else if (mode === 'signup') {
        const { data, error } = await c.auth.signUp({ email: email.trim(), password: pw });
        if (error) throw error;
        // Si la confirmation d'email est activée, aucune session n'est renvoyée.
        if (!data.session) { setInfo('Compte créé ! Vérifie tes emails pour confirmer, puis connecte-toi.'); setMode('login'); }
        // Sinon onAuthStateChange prend le relais et connecte automatiquement.
      } else {
        const { error } = await c.auth.signInWithPassword({ email: email.trim(), password: pw });
        if (error) throw error;
      }
    } catch (e) { setErr(frAuthError(e && e.message)); }
    setBusy(false);
  };

  const go = (m) => { setErr(''); setInfo(''); setMode(m); };
  const subtitle = mode === 'login' ? 'Contente de te revoir, connecte-toi.' : mode === 'signup' ? 'Crée ton compte, tout doux.' : 'On va réinitialiser ton mot de passe.';
  const cta = busy ? 'Un instant…' : (mode === 'login' ? 'Se connecter' : mode === 'signup' ? 'Créer mon compte' : 'Envoyer le lien');

  return (
    <React.Fragment>
      <Blobs on={true} />
      <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: '92px 26px 40px', boxSizing: 'border-box', animation: 'fadeUp 0.5s ease', display: 'flex', flexDirection: 'column', justifyContent: 'center', WebkitOverflowScrolling: 'touch' }}>
        <div style={{ textAlign: 'center', marginBottom: 8 }}>
          <div style={{ fontFamily: 'Caveat,cursive', fontSize: 46, fontWeight: 600, color: '#C67A61', lineHeight: 1 }}>Symptothermie ✿</div>
          <div style={{ fontSize: 14.5, fontWeight: 500, color: '#A8908D', marginTop: 10, lineHeight: 1.5 }}>{subtitle}</div>
        </div>

        <div style={{ ...CARD_LG, marginTop: 22, display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div>
            <div style={{ ...SECT_LABEL, marginBottom: 8 }}>Email</div>
            <input type="email" autoComplete="email" inputMode="email" value={email} onChange={(e) => setEmail(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && mode === 'forgot') submit(); }} placeholder="toi@exemple.com" style={AUTH_INPUT} />
          </div>
          {mode !== 'forgot' && (
            <div>
              <div style={{ ...SECT_LABEL, marginBottom: 8 }}>Mot de passe</div>
              <input type="password" autoComplete={mode === 'login' ? 'current-password' : 'new-password'} value={pw} onChange={(e) => setPw(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') submit(); }} placeholder={mode === 'signup' ? 'au moins 6 caractères' : '••••••••'} style={AUTH_INPUT} />
            </div>
          )}

          {err && authErr(err)}
          {info && authOk(info)}

          <button onClick={submit} disabled={busy} style={{ ...CTA, marginTop: 4, opacity: busy ? 0.6 : 1 }}>{cta}</button>
        </div>

        {mode === 'login' && <button onClick={() => go('forgot')} style={AUTH_LINK}>Mot de passe oublié ?</button>}
        {mode === 'login' && <button onClick={() => go('signup')} style={AUTH_LINK}>Pas encore de compte ? En créer un</button>}
        {mode === 'signup' && <button onClick={() => go('login')} style={AUTH_LINK}>Déjà un compte ? Se connecter</button>}
        {mode === 'forgot' && <button onClick={() => go('login')} style={AUTH_LINK}>Retour à la connexion</button>}
      </div>
    </React.Fragment>
  );
}

function ResetPassword({ onDone }) {
  const [pw, setPw] = useState('');
  const [pw2, setPw2] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [ok, setOk] = useState(false);

  const submit = async () => {
    setErr('');
    if (pw.length < 6) { setErr('Le mot de passe doit faire au moins 6 caractères.'); return; }
    if (pw !== pw2) { setErr('Les deux mots de passe ne correspondent pas.'); return; }
    setBusy(true);
    const c = getSupa();
    try {
      const { error } = await c.auth.updateUser({ password: pw });
      if (error) throw error;
      setOk(true);
      setTimeout(onDone, 1300);
    } catch (e) { setErr(frAuthError(e && e.message)); setBusy(false); }
  };

  return (
    <React.Fragment>
      <Blobs on={true} />
      <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: '92px 26px 40px', boxSizing: 'border-box', animation: 'fadeUp 0.5s ease', display: 'flex', flexDirection: 'column', justifyContent: 'center', WebkitOverflowScrolling: 'touch' }}>
        <div style={{ textAlign: 'center', marginBottom: 8 }}>
          <div style={{ fontFamily: 'Caveat,cursive', fontSize: 44, fontWeight: 600, color: '#C67A61', lineHeight: 1 }}>Nouveau mot de passe ✿</div>
          <div style={{ fontSize: 14.5, fontWeight: 500, color: '#A8908D', marginTop: 10, lineHeight: 1.5 }}>Choisis un nouveau mot de passe pour ton compte.</div>
        </div>
        <div style={{ ...CARD_LG, marginTop: 22, display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div>
            <div style={{ ...SECT_LABEL, marginBottom: 8 }}>Nouveau mot de passe</div>
            <input type="password" autoComplete="new-password" value={pw} onChange={(e) => setPw(e.target.value)} placeholder="au moins 6 caractères" style={AUTH_INPUT} />
          </div>
          <div>
            <div style={{ ...SECT_LABEL, marginBottom: 8 }}>Confirme le mot de passe</div>
            <input type="password" autoComplete="new-password" value={pw2} onChange={(e) => setPw2(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') submit(); }} placeholder="••••••••" style={AUTH_INPUT} />
          </div>
          {err && authErr(err)}
          {ok && authOk('Mot de passe mis à jour ! Te voilà connectée.')}
          <button onClick={submit} disabled={busy || ok} style={{ ...CTA, marginTop: 4, opacity: (busy || ok) ? 0.6 : 1 }}>{ok ? 'C’est bon !' : (busy ? 'Un instant…' : 'Valider')}</button>
        </div>
      </div>
    </React.Fragment>
  );
}

/* ══════════════════════════════════════════════════════════════
   6. Courbe SVG v2 (port fidèle de buildChart) — taille fixe scrollable,
      axe fixe, 5 lignes d'observations, sélection jour, plein écran.
   ══════════════════════════════════════════════════════════════ */
function Chart({ entries, ana, open, sel, onSelect, land, seq }) {
  const k = land ? 0.66 : 1;
  const tMin = 36.2, tMax = 37.0;
  const nDays = Math.max(entries.length + 2, 24);
  const colW = land ? 42 : 46, T = land ? 18 : 24, axW = 46;
  const plotBot = land ? 165 : 280, rowsY = land ? 177 : 296, rowH = land ? 25 : 38, nRows = 5;
  const dayY = rowsY + nRows * rowH + (land ? 16 : 22);
  const bandY = dayY + (land ? 7 : 9);
  const H = bandY + 12 + 6;
  const W = nDays * colW + 12;
  const x = (d) => 6 + (d - 0.5) * colW;
  const y = (t) => T + (tMax - t) * ((plotBot - T) / (tMax - tMin));
  const ry = (i) => rowsY + i * rowH + rowH / 2;
  const pe = { pointerEvents: 'none' };
  const kids = [];
  const axisKids = [];

  for (let g = 36.2; g <= 37.01; g += 0.2) {
    const gy = y(g);
    kids.push(<line key={'g' + g.toFixed(2)} x1={0} x2={W} y1={gy} y2={gy} stroke="#F4E6DC" strokeWidth={1} />);
    axisKids.push(<text key={'at' + g.toFixed(2)} x={axW - 6} y={gy + 5} textAnchor="end" fontSize={13} fontWeight={600} fill="#C9B3AB" fontFamily="Quicksand">{decimal1(g)}</text>);
  }
  ['règles', 'glaire', 'sens.', 'col', 'rapport'].forEach((nm, i) => {
    axisKids.push(<text key={'rl' + i} x={axW - 6} y={ry(i) + 4} textAnchor="end" fontSize={12} fontWeight={700} fill="#C9B3AB" fontFamily="Quicksand">{nm}</text>);
    kids.push(<line key={'rs' + i} x1={0} x2={W} y1={rowsY + i * rowH} y2={rowsY + i * rowH} stroke="#F8EEE6" strokeWidth={1} />);
  });
  kids.push(<line key="rsend" x1={0} x2={W} y1={rowsY + nRows * rowH} y2={rowsY + nRows * rowH} stroke="#F8EEE6" strokeWidth={1} />);

  // sélection + zones de tap
  for (let d = 1; d <= entries.length; d++) {
    if (d === sel) kids.push(<rect key={'sel' + d} x={x(d) - colW / 2} y={6} width={colW} height={H - 12} rx={12} fill="rgba(198,122,97,0.09)" />);
    kids.push(<rect key={'tap' + d} x={x(d) - colW / 2} y={0} width={colW} height={H} fill="transparent" style={{ cursor: 'pointer' }} onClick={() => onSelect(d)} />);
  }

  // numéros de jour + bande de phases + symboles d'observations
  for (let d = 1; d <= nDays; d++) {
    kids.push(<text key={'d' + d} x={x(d)} y={dayY} textAnchor="middle" fontSize={13} fontWeight={d === sel ? 700 : 600} fill={d === sel ? '#B05F44' : '#C9B3AB'} fontFamily="Quicksand" style={pe}>{d}</text>);
    if (d > entries.length) continue;
    const st = statusFor(d, entries, ana, open);
    kids.push(<rect key={'ph' + d} x={x(d) - colW / 2 + 4} y={bandY} width={colW - 8} height={12} fill={st.bg} rx={4} style={pe} />);
    const e = entries[d - 1];
    const cx = x(d);
    const dash = (kk, yy) => <line key={kk} x1={cx - 8 * k} x2={cx + 8 * k} y1={yy} y2={yy} stroke="#DCCABE" strokeWidth={3 * k} strokeLinecap="round" style={pe} />;
    // règles
    if (e.saign === 'fort') kids.push(<circle key={'sg' + d} cx={cx} cy={ry(0)} r={12 * k} fill="#E8798F" style={pe} />);
    else if (e.saign === 'moyen') kids.push(<circle key={'sg' + d} cx={cx} cy={ry(0)} r={9 * k} fill="#E8798F" style={pe} />);
    else if (e.saign === 'spotting') kids.push(<circle key={'sg' + d} cx={cx} cy={ry(0)} r={6.5 * k} fill="none" stroke="#E8798F" strokeWidth={3 * k} style={pe} />);
    // glaire
    if (e.glaire === 'filant') kids.push(<rect key={'gl' + d} x={cx - 5.5 * k} y={ry(1) - 16 * k} width={11 * k} height={32 * k} rx={5.5 * k} fill="#F0AC90" style={pe} />);
    else if (e.glaire === 'cremeux') kids.push(<rect key={'gl' + d} x={cx - 5.5 * k} y={ry(1) - 10 * k} width={11 * k} height={20 * k} rx={5.5 * k} fill="#E3B98F" style={pe} />);
    else if (e.glaire === 'infertile') kids.push(<rect key={'gl' + d} x={cx - 5.5 * k} y={ry(1) - 8 * k} width={11 * k} height={16 * k} rx={5.5 * k} fill="#E0C98A" style={pe} />);
    else if (e.glaire === 'rien') kids.push(dash('gl' + d, ry(1)));
    // sensation
    if (e.sens === 'mouillee') kids.push(<circle key={'se' + d} cx={cx} cy={ry(2)} r={8 * k} fill="#C67A61" style={pe} />);
    else if (e.sens === 'humide') kids.push(<circle key={'se' + d} cx={cx} cy={ry(2)} r={7 * k} fill="none" stroke="#C67A61" strokeWidth={3 * k} style={pe} />);
    else if (e.sens === 'seche') kids.push(dash('se' + d, ry(2)));
    // col
    if (e.col) {
      kids.push(<line key={'cl' + d} x1={cx} x2={cx} y1={ry(3) - 14 * k} y2={ry(3) + 14 * k} stroke="#EADDD3" strokeWidth={4 * k} strokeLinecap="round" style={pe} />);
      const cy3 = e.col === 'haut' ? ry(3) - 10 * k : e.col === 'moyen' ? ry(3) : ry(3) + 10 * k;
      kids.push(<circle key={'cd' + d} cx={cx} cy={cy3} r={6.5 * k} fill="#B99BD6" style={pe} />);
    }
    // rapport
    if (e.rapport) kids.push(<text key={'rp' + d} x={cx} y={ry(4) + 8 * k} textAnchor="middle" fontSize={23 * k} fill={e.rapport === 'non' ? '#E8798F' : '#E8A0B4'} style={pe}>{e.rapport === 'non' ? '♥' : '♡'}</text>);
  }

  // ligne de base
  if (ana.done) {
    kids.push(<line key="bl" x1={x(Math.max(1, ana.firstHigh - 5)) - colW / 2} x2={x(entries.length) + colW / 2} y1={y(ana.baseline)} y2={y(ana.baseline)} stroke="#B99BD6" strokeWidth={2.5} strokeDasharray="7 6" style={pe} />);
    kids.push(<text key="blt" x={x(entries.length) + colW / 2 - 4} y={y(ana.baseline) - 8} textAnchor="end" fontSize={13} fontWeight={700} fill="#9678BC" fontFamily="Quicksand" style={pe}>{'ligne de base ' + decimal(ana.baseline)}</text>);
  }
  // tracé animé
  let dstr = '', started = false;
  entries.forEach((e) => {
    if (e.temp == null) return;
    dstr += (started ? ' L ' : 'M ') + x(e.d).toFixed(1) + ' ' + y(e.temp).toFixed(1);
    started = true;
  });
  kids.push(<path key={'p' + seq} d={dstr} fill="none" stroke="#E8A0B4" strokeWidth={3} strokeLinecap="round" strokeLinejoin="round" strokeDasharray={3000} strokeDashoffset={3000} style={{ animation: 'drawline 1.8s ease-out forwards', pointerEvents: 'none' }} />);
  entries.forEach((e) => {
    if (e.temp == null) return;
    const isSel = e.d === sel;
    kids.push(<circle key={'c' + e.d} cx={x(e.d)} cy={y(e.temp)} r={isSel ? 8 : 5.5} fill={isSel ? '#E8A0B4' : '#FFFFFF'} stroke="#E8A0B4" strokeWidth={2.5} style={pe} />);
    if (isSel) kids.push(<text key={'ct' + e.d} x={x(e.d)} y={y(e.temp) - 16} textAnchor="middle" fontSize={15} fontWeight={700} fill="#B05F44" fontFamily="Quicksand" style={pe}>{decimal(e.temp)}</text>);
  });
  if (ana.done) {
    for (let i = ana.firstHigh; i <= ana.close; i++) {
      const e = entries[i];
      if (e && e.temp != null && e.temp > ana.baseline) kids.push(<circle key={'h' + i} cx={x(e.d)} cy={y(e.temp)} r={12} fill="none" stroke="#C67A61" strokeWidth={2.5} style={pe} />);
    }
    const sd = entries[ana.sommet].d;
    kids.push(<circle key="som" cx={x(sd)} cy={dayY - 4.5} r={11} fill="none" stroke="#E4C87E" strokeWidth={2.5} style={pe} />);
  }

  return (
    <div style={{ display: 'flex' }}>
      <svg width={axW} height={H} style={{ flexShrink: 0, display: 'block' }}>{axisKids}</svg>
      <div
        style={{ overflowX: 'auto', WebkitOverflowScrolling: 'touch', flex: 1, minWidth: 0 }}
        ref={(el) => { if (el && el._seq !== seq) { el._seq = seq; el.scrollLeft = Math.max(0, x(sel) - el.clientWidth / 2); } }}
      >
        <svg width={W} height={H} style={{ display: 'block' }}>{kids}</svg>
      </div>
    </div>
  );
}

function GloItem({ sym, label }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      <div style={{ width: 36, display: 'flex', justifyContent: 'center', flexShrink: 0 }}>{sym}</div>
      <span style={{ fontSize: 13, fontWeight: 600, color: '#5A474D', lineHeight: 1.3 }}>{label}</span>
    </div>
  );
}
function GloGroup({ title, children }) {
  return (
    <React.Fragment>
      <div style={{ fontSize: 12, fontWeight: 700, color: '#A8908D', textTransform: 'uppercase', letterSpacing: '0.6px', marginBottom: 8 }}>{title}</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px 12px', marginBottom: 16 }}>{children}</div>
    </React.Fragment>
  );
}
function Glossary() {
  const stick = (pos) => (
    <div style={{ width: 4, height: 28, borderRadius: 3, background: '#EADDD3', position: 'relative' }}>
      <div style={{ position: 'absolute', ...pos, left: -4.5, width: 13, height: 13, borderRadius: '50%', background: '#B99BD6' }} />
    </div>
  );
  return (
    <div style={{ ...CARD_LG, marginTop: 14 }}>
      <div style={{ ...SECT_LABEL, marginBottom: 14 }}>Glossaire des symboles</div>
      <GloGroup title="Règles">
        <GloItem sym={<div style={{ width: 24, height: 24, borderRadius: '50%', background: '#E8798F' }} />} label="Abondant" />
        <GloItem sym={<div style={{ width: 18, height: 18, borderRadius: '50%', background: '#E8798F' }} />} label="Moyen" />
        <GloItem sym={<div style={{ width: 13, height: 13, borderRadius: '50%', border: '3px solid #E8798F', boxSizing: 'border-box' }} />} label="Spotting" />
      </GloGroup>
      <GloGroup title="Glaire cervicale">
        <GloItem sym={<div style={{ width: 11, height: 32, borderRadius: 6, background: '#F0AC90' }} />} label="Transparent, filant — très fertile" />
        <GloItem sym={<div style={{ width: 11, height: 20, borderRadius: 6, background: '#E3B98F' }} />} label="Crémeux, épais — fertile" />
        <GloItem sym={<div style={{ width: 11, height: 16, borderRadius: 6, background: '#E0C98A' }} />} label="Jaunâtre, pâteux — post-ovulation" />
        <GloItem sym={<div style={{ width: 16, height: 3, borderRadius: 2, background: '#DCCABE' }} />} label="Rien observé" />
      </GloGroup>
      <GloGroup title="Sensation">
        <GloItem sym={<div style={{ width: 16, height: 16, borderRadius: '50%', background: '#C67A61' }} />} label="Mouillée" />
        <GloItem sym={<div style={{ width: 14, height: 14, borderRadius: '50%', border: '3px solid #C67A61', boxSizing: 'border-box' }} />} label="Humide" />
        <GloItem sym={<div style={{ width: 16, height: 3, borderRadius: 2, background: '#DCCABE' }} />} label="Sèche" />
      </GloGroup>
      <GloGroup title="Col de l'utérus">
        <GloItem sym={stick({ top: -1 })} label="Haut, ouvert — très fertile" />
        <GloItem sym={stick({ top: 7.5 })} label="Moyen — fertile" />
        <GloItem sym={stick({ bottom: -1 })} label="Bas, fermé — infertile" />
      </GloGroup>
      <GloGroup title="Rapport">
        <GloItem sym={<div style={{ fontSize: 22, color: '#E8A0B4', lineHeight: 1 }}>♡</div>} label="Protégé" />
        <GloItem sym={<div style={{ fontSize: 22, color: '#E8798F', lineHeight: 1 }}>♥</div>} label="Non protégé" />
      </GloGroup>
      <div style={{ fontSize: 12, fontWeight: 700, color: '#A8908D', textTransform: 'uppercase', letterSpacing: '0.6px', marginBottom: 8 }}>Sur la courbe</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px 12px' }}>
        <GloItem sym={<div style={{ width: 16, height: 16, borderRadius: '50%', border: '3px solid #C67A61', boxSizing: 'border-box', background: '#FFFFFF' }} />} label="Température haute entourée" />
        <GloItem sym={<div style={{ width: 16, height: 16, borderRadius: '50%', border: '2.5px solid #E4C87E', boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 9, fontWeight: 700, color: '#B05F44' }}>14</div>} label="Jour sommet (numéro cerclé d'or)" />
        <GloItem sym={<div style={{ width: 20, height: 0, borderTop: '3px dashed #B99BD6' }} />} label="Ligne de base" />
      </div>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════
   7. Application
   ══════════════════════════════════════════════════════════════ */
function App() {
  const [session, setSession] = useState(undefined); // undefined = en cours, null = déconnecté
  const [recovery, setRecovery] = useState(false); // arrivée via lien de réinitialisation
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [onboarding, setOnboarding] = useState(false);
  const [screen, setScreen] = useState('home');
  const [calTab, setCalTab] = useState('cycle');
  const [burst, setBurst] = useState(false);
  const [chartSeq, setChartSeq] = useState(0);
  const [selDay, setSelDay] = useState(null); // jour sélectionné sur la courbe (null = aujourd'hui)
  const [fullChart, setFullChart] = useState(false); // overlay courbe plein écran
  const [editDay, setEditDay] = useState(null); // jour édité par la Saisie (null = aujourd'hui)
  const [form, setForm] = useState({ temp: 3680, sens: null, glaire: null, col: null, saign: 'aucun', rapport: null, perturb: [], note: '' });
  const dataRef = useRef(null);
  const burstT = useRef(null);

  const hideLoader = () => { const el = document.getElementById('loading'); if (el) el.style.display = 'none'; };

  // 1) Session d'authentification (Supabase Auth).
  useEffect(() => {
    const c = getSupa();
    if (!c) { setSession(null); return; }
    c.auth.getSession().then(({ data }) => setSession(data.session || null));
    const { data: sub } = c.auth.onAuthStateChange((event, s) => {
      setSession(s || null);
      if (event === 'PASSWORD_RECOVERY') setRecovery(true); // lien de réinitialisation cliqué
    });
    return () => { try { sub.subscription.unsubscribe(); } catch (e) {} };
  }, []);

  const uid = session && session.user ? session.user.id : null;

  // 2) Données de l'utilisateur connecté : cache local d'abord, puis Supabase si plus récent.
  useEffect(() => {
    if (session === undefined) return; // session pas encore déterminée
    if (!uid) { dataRef.current = null; setData(null); setLoading(false); hideLoader(); return; }
    let mounted = true;
    setLoading(true);
    (async () => {
      const local = loadLocal(uid);
      const remote = await pullRemote(uid);
      if (!mounted) return;
      let d, onboard = false;
      if (local) {
        d = ensureToday(local);
        if (remote && (remote.updatedAt || 0) > (local.updatedAt || 0)) d = ensureToday(remote);
      } else if (remote) {
        d = ensureToday(remote); // compte déjà utilisé sur un autre appareil
      } else {
        d = ensureToday(freshData(isoToday())); // nouveau compte → onboarding
        onboard = true;
      }
      dataRef.current = d;
      setOnboarding(onboard);
      setData(d);
      setLoading(false);
      hideLoader();
    })();

    // Synchro temps réel, limitée à la ligne de cet utilisateur.
    let channel = null; const c = getSupa();
    if (c) {
      channel = c.channel('sympto-' + uid)
        .on('postgres_changes', { event: '*', schema: 'public', table: 'sympto_data', filter: 'user_id=eq.' + uid }, (payload) => {
          const r = payload.new && payload.new.data;
          if (r && (r.updatedAt || 0) > ((dataRef.current && dataRef.current.updatedAt) || 0)) {
            const nd = ensureToday(r);
            dataRef.current = nd;
            setData(nd);
          }
        })
        .subscribe();
    }
    return () => { mounted = false; if (channel && c) c.removeChannel(channel); };
  }, [session]);

  // Toute mutation passe par commit : horodate, setState, sauve local, pousse Supabase.
  function commit(next) {
    next.updatedAt = Date.now();
    dataRef.current = next;
    setData(next);
    saveLocal(uid, next);
    pushRemote(uid, next);
  }
  const mutate = (fn) => {
    const next = JSON.parse(JSON.stringify(dataRef.current));
    fn(next);
    commit(next);
  };

  const finishOnboarding = (dateStr, prenom) => {
    commit(ensureToday(freshData(dateStr, prenom)));
    setOnboarding(false);
    setScreen('home');
  };
  const loadExample = () => {
    commit(ensureToday(seed()));
    setOnboarding(false);
    setScreen('home');
  };
  const logout = async () => {
    const c = getSupa();
    if (c) { try { await c.auth.signOut(); } catch (e) {} }
    setScreen('home'); setCalTab('cycle');
  };
  const deleteAccount = async () => {
    if (!window.confirm('Supprimer définitivement ton compte et TOUTES tes données ? Cette action est irréversible.')) return;
    const c = getSupa();
    try {
      const { error } = await c.rpc('delete_own_account');
      if (error) throw error;
    } catch (e) { window.alert('Suppression impossible : ' + ((e && e.message) || 'réessaie plus tard') + '.'); return; }
    try { localStorage.removeItem(lsKey(uid)); } catch (e) {}
    try { await c.auth.signOut(); } catch (e) {}
    setScreen('home'); setCalTab('cycle');
  };

  if (session === undefined) {
    return <Blobs on={true} />;
  }
  if (recovery) {
    return <ResetPassword onDone={() => setRecovery(false)} />;
  }
  if (!session) {
    return <Auth />;
  }
  if (loading || !data) {
    return <Blobs on={true} />;
  }
  if (onboarding) {
    return <Onboarding defaultDate={isoToday()} onFinish={finishOnboarding} onExample={loadExample} />;
  }

  /* ---- valeurs dérivées ---- */
  const entries = data.entries;
  const settings = data.settings;
  const pastCycles = data.pastCycles;
  const ana = analyze(entries);
  const open = opening(entries, settings, pastCycles);
  const today = entries.length;
  const status = statusFor(today, entries, ana, open);
  const eToday = entries[today - 1];
  const avgLen = pastCycles.length ? Math.round(pastCycles.reduce((a, c) => a + c.len, 0) / pastCycles.length) : 28;
  const daysLeft = avgLen + 1 - today;
  const savedToday = !!(eToday && eToday.temp != null);
  const active = (s) => (screen === s ? '#C67A61' : '#C9B3AB');

  /* ---- navigation ---- */
  const go = (s) => { if (s === 'courbe') setChartSeq((n) => n + 1); setScreen(s); };
  const openSaisie = (day) => {
    const d = day || today;
    const e = entries[d - 1] || emptyEntry(d);
    setEditDay(d);
    setForm({
      temp: e.temp != null ? Math.round(e.temp * 100) : 3680,
      sens: e.sens || null, glaire: e.glaire || null, col: e.col || null,
      saign: e.saign || 'aucun', rapport: e.rapport || null,
      perturb: Array.isArray(e.perturb) ? e.perturb.slice() : [], note: e.note || '',
    });
    setScreen('saisie');
  };

  /* ---- saisie ---- */
  const setField = (k, v) => setForm((f) => ({ ...f, [k]: (f[k] === v && k !== 'temp' && k !== 'note') ? null : v }));
  const togglePerturb = (p) => setForm((f) => ({ ...f, perturb: f.perturb.includes(p) ? f.perturb.filter((x) => x !== p) : f.perturb.concat([p]) }));
  const chip = (k, v, l) => <Chip key={v} label={l} active={form[k] === v} onClick={() => setField(k, v)} />;

  const save = () => {
    const d = editDay || today;
    mutate((nx) => {
      while (nx.entries.length < d) nx.entries.push(emptyEntry(nx.entries.length + 1));
      nx.entries[d - 1] = {
        d, temp: form.temp / 100, sens: form.sens, glaire: form.glaire, col: form.col,
        saign: form.saign === 'aucun' ? null : form.saign, rapport: form.rapport,
        perturb: form.perturb, note: form.note,
      };
    });
    setBurst(true);
    clearTimeout(burstT.current);
    burstT.current = setTimeout(() => { setBurst(false); setScreen('home'); setEditDay(null); }, 1500);
  };

  const setSetting = (k, v) => mutate((nx) => { nx.settings[k] = v; });

  const startNewCycle = () => {
    if (!window.confirm('Commencer un nouveau cycle aujourd’hui ? Le cycle en cours sera rangé dans ton historique.')) return;
    mutate((nx) => {
      const a = analyze(nx.entries);
      const len = nx.entries.length;
      nx.pastCycles = [{
        name: 'Cycle du ' + frShort(nx.cycleStart),
        len,
        open: opening(nx.entries, nx.settings, nx.pastCycles),
        sommet: a.done ? nx.entries[a.sommet].d : 0,
        close: a.done ? nx.entries[a.close].d : len,
        firstHigh: a.done ? nx.entries[a.firstHigh].d : 0,
      }].concat(nx.pastCycles).slice(0, 12);
      nx.cycleStart = isoToday();
      nx.entries = [Object.assign(emptyEntry(1), { saign: 'fort' })];
    });
    setScreen('home');
  };

  /* ---- calendrier ---- */
  const calDays = [];
  for (let d = 1; d <= Math.max(avgLen, today); d++) {
    if (d > entries.length) {
      calDays.push({ n: d, bg: '#FBF4EE', ring: '#F0E2D6', tc: '#C9B3AB' });
    } else {
      const s = statusFor(d, entries, ana, open);
      const e = entries[d - 1];
      const menses = e && (e.saign === 'fort' || e.saign === 'moyen' || e.saign === 'spotting');
      calDays.push({ n: d, bg: menses ? '#F2BFCB' : s.bg, ring: d === today ? '#B05F44' : 'transparent', tc: menses ? '#8F4B5E' : s.ink });
    }
  }

  const segs = (len, close) => {
    const mens = 5, fert = Math.max(0, close - 6 + 1);
    return [
      { w: (mens / len * 100).toFixed(1) + '%', bg: '#F2BFCB' },
      { w: (fert / len * 100).toFixed(1) + '%', bg: '#F6CDB4' },
      { w: ((len - close) / len * 100).toFixed(1) + '%', bg: '#DCC8EC' },
    ];
  };
  const cyclesList = [
    { name: 'Cycle en cours', len: 'J' + today, sub: ana.done ? 'Fenêtre fermée à J' + entries[ana.close].d + ' · sommet J' + entries[ana.sommet].d : 'En observation', segs: segs(avgLen, ana.done ? entries[ana.close].d : Math.min(17, avgLen - 1)) },
    ...pastCycles.map((c) => ({ name: c.name, len: c.len + ' jours', sub: c.sommet ? 'Sommet J' + c.sommet + ' · fenêtre fermée à J' + c.close : 'Cycle non analysé', segs: segs(c.len, c.close) })),
  ];

  /* ---- analyse (courbe) ---- */
  const anaRows = ana.done ? [
    { k: 'Ouverture de la fenêtre', v: 'J' + open + (settings.mode === 'experte' ? ' (jour Doering)' : ' (règle J6)') },
    { k: 'Jour sommet', v: 'J' + entries[ana.sommet].d },
    { k: 'Ligne de base', v: decimal(ana.baseline) + ' °C' },
    { k: '1re température haute', v: 'J' + entries[ana.firstHigh].d + ' · ' + decimal(entries[ana.firstHigh].temp) + ' °C' },
    { k: '3e haute', v: 'J' + entries[ana.third].d + ' · +' + decimal(entries[ana.third].temp - ana.baseline) + ' °C' },
  ] : [
    { k: 'Ouverture de la fenêtre', v: 'J' + open },
    { k: 'Jour sommet', v: 'pas encore détecté' },
  ];
  const anaConclusion = ana.done
    ? 'Ta fenêtre de fertilité s’est fermée le soir de J' + entries[ana.close].d + ', 12 h après l’heure de prise. Tu es en phase infertile post-ovulatoire.'
    : 'Ta montée de température n’est pas encore confirmée : continue tes observations, tout vient à point.';

  const prenom = (settings.prenom || '').trim();

  /* ══════════ écrans ══════════ */
  const Home = (
    <div key="home" data-screen-label="Accueil" style={screenStyle('78px 22px 120px')}>
      <div style={{ fontFamily: 'Caveat,cursive', fontSize: 36, fontWeight: 600, color: '#C67A61', lineHeight: 1 }}>{'Bonjour' + (prenom ? ' ' + prenom : '') + ' ✿'}</div>
      <div style={{ fontSize: 14, fontWeight: 500, color: '#A8908D', marginTop: 4 }}>{frLong(isoToday()) + ' · Jour ' + today + ' du cycle'}</div>

      <div style={{ display: 'flex', justifyContent: 'center', margin: '26px 0 22px' }}>
        <div style={{ width: 236, height: 236, background: status.bg, animation: 'morph 9s ease-in-out infinite', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 4, boxShadow: '0 18px 40px ' + status.sh, borderRadius: '47% 53% 60% 40%/45% 55% 45% 55%' }}>
          <div style={{ fontSize: 15, fontWeight: 600, color: status.ink, opacity: 0.75 }}>{'Jour ' + today}</div>
          <div style={{ fontSize: 26, fontWeight: 700, color: status.ink, letterSpacing: '0.2px' }}>{status.label}</div>
          <div style={{ fontSize: 13, fontWeight: 500, color: status.ink, opacity: 0.8, maxWidth: 160, textAlign: 'center', lineHeight: 1.45 }}>{statusSubs[status.key]}</div>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
        <div style={CARD}>
          <div style={{ fontSize: 12, fontWeight: 600, color: '#A8908D', textTransform: 'uppercase', letterSpacing: '0.6px' }}>Température</div>
          <div style={{ fontSize: 24, fontWeight: 700, color: '#5A474D', marginTop: 6 }}>{eToday && eToday.temp != null ? decimal(eToday.temp) + ' °C' : '—'}</div>
          <div style={{ fontSize: 12, fontWeight: 500, color: '#C9B3AB', marginTop: 2 }}>{savedToday ? 'prise à ' + settings.heure : 'pas encore saisie'}</div>
        </div>
        <div style={CARD}>
          <div style={{ fontSize: 12, fontWeight: 600, color: '#A8908D', textTransform: 'uppercase', letterSpacing: '0.6px' }}>Glaire</div>
          <div style={{ fontSize: 17, fontWeight: 700, color: '#5A474D', marginTop: 10 }}>{eToday ? (glaireLabels[eToday.glaire] || 'Rien observé') : '—'}</div>
          <div style={{ fontSize: 12, fontWeight: 500, color: '#C9B3AB', marginTop: 2 }}>{eToday && eToday.sens ? 'sensation ' + sensLabels[eToday.sens] : ''}</div>
        </div>
      </div>

      <div style={{ ...CARD, padding: 18, marginTop: 12, display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ width: 38, height: 38, borderRadius: '50%', background: '#DCC8EC', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}><div style={{ width: 14, height: 14, borderRadius: '50%', background: '#FFFFFF' }} /></div>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700 }}>{ana.done ? 'Fenêtre de fertilité fermée' : 'Fenêtre de fertilité'}</div>
            <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', lineHeight: 1.5 }}>{ana.done ? 'Depuis J' + entries[ana.close].d + ' au soir, 12 h après la prise' : 'Montée de température en cours d’observation'}</div>
          </div>
        </div>
        <div style={{ height: 1, background: '#F4E6DC' }} />
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ width: 38, height: 38, borderRadius: '50%', background: '#F2BFCB', flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}><div style={{ width: 14, height: 14, borderRadius: '50%', background: '#FFFFFF', animation: 'dotPulse 2.4s ease-in-out infinite' }} /></div>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700 }}>Prochaines règles</div>
            <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', lineHeight: 1.5 }}>{daysLeft > 0 ? 'Estimées dans ' + daysLeft + ' jours (cycles d’environ ' + avgLen + ' jours)' : 'Elles peuvent arriver d’un jour à l’autre'}</div>
          </div>
        </div>
      </div>

      <button onClick={() => openSaisie(today)} style={{ ...CTA, marginTop: 18 }}>{savedToday ? 'Modifier ma journée' : 'Saisir ma journée'}</button>
    </div>
  );

  const Saisie = (
    <div key="saisie" data-screen-label="Saisie du jour" style={screenStyle('78px 22px 130px')}>
      <div style={{ fontSize: 22, fontWeight: 700 }}>Ma journée</div>
      <div style={{ fontSize: 13.5, fontWeight: 500, color: '#A8908D', marginTop: 2 }}>{frLong(addDays(data.cycleStart, (editDay || today) - 1)) + ' · J' + (editDay || today) + ' — prends ton temps, tout doux'}</div>

      <div style={{ ...CARD_LG, marginTop: 18 }}>
        <div style={SECT_LABEL}>Température au réveil</div>
        <div style={{ textAlign: 'center', fontSize: 44, fontWeight: 700, color: '#5A474D', margin: '10px 0 2px' }}>{decimal(form.temp / 100) + ' °C'}</div>
        <input type="range" className="sympto-wheel" min="3600" max="3750" step="5" value={form.temp} onChange={(ev) => setForm((f) => ({ ...f, temp: parseInt(ev.target.value, 10) }))} />
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, fontWeight: 600, color: '#C9B3AB', marginTop: 2 }}><span>36,0</span><span>36,5</span><span>37,0</span><span>37,5</span></div>
        <div style={{ fontSize: 12, fontWeight: 500, color: '#A8908D', marginTop: 10, textAlign: 'center' }}>Avant 7h30, avant de te lever, toujours à la même heure</div>
      </div>

      <div style={{ ...CARD_LG, marginTop: 12, display: 'flex', flexDirection: 'column', gap: 18 }}>
        <Section label="Sensation">
          {chip('sens', 'seche', 'Sèche')}{chip('sens', 'humide', 'Humide')}{chip('sens', 'mouillee', 'Mouillée')}
        </Section>
        <Section label="Glaire cervicale">
          {chip('glaire', 'rien', 'Rien')}{chip('glaire', 'cremeux', 'Crémeux, épais')}{chip('glaire', 'filant', 'Transparent, filant')}{chip('glaire', 'infertile', 'Jaunâtre, pâteux')}
        </Section>
        {settings.afficherCol && (
          <Section label="Col de l'utérus">
            {chip('col', 'bas', 'Bas, fermé')}{chip('col', 'moyen', 'Moyen')}{chip('col', 'haut', 'Haut, ouvert')}
          </Section>
        )}
        <Section label="Saignements">
          {chip('saign', 'aucun', 'Aucun')}{chip('saign', 'spotting', 'Spotting')}{chip('saign', 'moyen', 'Moyen')}{chip('saign', 'fort', 'Abondant')}
        </Section>
        <Section label="Rapport">
          {chip('rapport', 'protege', 'Protégé')}{chip('rapport', 'non', 'Non protégé')}
        </Section>
      </div>

      <div style={{ ...CARD_LG, marginTop: 12 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 4 }}>Événements perturbants</div>
        <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', marginBottom: 12 }}>Ils peuvent influencer ta température — coche pour t'en souvenir</div>
        <ChipRow>
          {['Fièvre / maladie', 'Alcool (+2 verres)', 'Sommeil perturbé', 'Prise après 7h30', 'Stress / choc', 'Repas lourd tardif', 'Médicament', 'Voyage / décalage', 'Changement de thermomètre', 'Vaccination'].map((p) => (
            <Chip key={p} label={p} small active={form.perturb.includes(p)} onClick={() => togglePerturb(p)} />
          ))}
        </ChipRow>
      </div>

      <div style={{ ...CARD_LG, marginTop: 12 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Petite note</div>
        <textarea value={form.note} onChange={(ev) => { const v = ev.target.value; setForm((f) => ({ ...f, note: v })); }} placeholder="Comment te sens-tu aujourd'hui ?" style={{ width: '100%', boxSizing: 'border-box', minHeight: 74, border: '1.5px solid #F4E6DC', borderRadius: 16, padding: '12px 14px', fontFamily: 'Quicksand,sans-serif', fontSize: 14, fontWeight: 500, color: '#5A474D', resize: 'vertical', background: '#FDF9F5', outline: 'none' }} />
      </div>

      <button onClick={save} style={{ ...CTA, marginTop: 18 }}>Enregistrer ma journée</button>
    </div>
  );

  // Jour sélectionné sur la courbe (par défaut aujourd'hui), borné au cycle.
  const sel = Math.min(Math.max(1, selDay == null ? today : selDay), entries.length);
  const selE = entries[sel - 1];
  const selObs = (() => {
    if (!selE) return '—';
    const parts = [];
    if (selE.saign) parts.push('saignements ' + ({ fort: 'abondants', moyen: 'moyens', spotting: 'spotting' }[selE.saign] || selE.saign));
    if (selE.glaire && selE.glaire !== 'rien') parts.push(glaireLabels[selE.glaire].toLowerCase());
    else if (!selE.saign) parts.push('pas de glaire');
    if (selE.sens) parts.push('sensation ' + sensLabels[selE.sens]);
    return parts.length ? parts.join(' · ') : '—';
  })();
  const selTitle = 'Jour ' + sel + (sel === today ? ' · aujourd’hui' : '');
  const selTemp = selE && selE.temp != null ? decimal(selE.temp) + ' °C' : '—';
  const selPhase = statusFor(sel, entries, ana, open);

  const openFull = () => {
    setFullChart(true);
    setChartSeq((n) => n + 1);
    try {
      const el = document.documentElement;
      const lock = () => { if (window.screen && screen.orientation && screen.orientation.lock) screen.orientation.lock('landscape').catch(() => {}); };
      if (el.requestFullscreen) el.requestFullscreen().then(lock).catch(lock);
      else lock();
    } catch (e) {}
  };
  const closeFull = () => {
    setFullChart(false);
    try { if (window.screen && screen.orientation && screen.orientation.unlock) screen.orientation.unlock(); } catch (e) {}
    try { if (document.fullscreenElement && document.exitFullscreen) document.exitFullscreen(); } catch (e) {}
  };

  const Courbe = (
    <div key={'courbe' + chartSeq} data-screen-label="Courbe" style={screenStyle('78px 18px 120px')}>
      <div style={{ padding: '0 6px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
        <div>
          <div style={{ fontSize: 22, fontWeight: 700 }}>Ma courbe</div>
          <div style={{ fontSize: 13.5, fontWeight: 500, color: '#A8908D', marginTop: 2 }}>{'Cycle en cours — jour ' + today}</div>
        </div>
        <button onClick={openFull} style={{ display: 'flex', alignItems: 'center', gap: 7, minHeight: 44, padding: '0 16px', borderRadius: 999, border: '1.5px solid #E8A18B', background: '#F8E3D6', color: '#B05F44', fontFamily: 'Quicksand,sans-serif', fontSize: 13.5, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>
          <svg width="16" height="16" viewBox="0 0 16 16"><path d="M2 6V2h4M14 10v4h-4M2 2l5 5M14 14L9 9" fill="none" stroke="#B05F44" strokeWidth={1.8} strokeLinecap="round" /></svg>
          Plein écran
        </button>
      </div>
      <div style={{ ...CARD_LG, padding: '14px 6px 8px', marginTop: 16 }}>
        <Chart entries={entries} ana={ana} open={open} sel={sel} onSelect={setSelDay} seq={chartSeq} />
        <div style={{ fontSize: 12, fontWeight: 600, color: '#C9B3AB', textAlign: 'center', padding: '6px 0 4px' }}>Fais glisser la courbe · touche un jour pour le détail</div>
      </div>
      <div style={{ ...CARD_LG, padding: '16px 18px', marginTop: 12, display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontSize: 14.5, fontWeight: 700 }}>{selTitle}</div>
          <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', marginTop: 3, lineHeight: 1.45 }}>{selObs}</div>
        </div>
        <div style={{ textAlign: 'right', flexShrink: 0 }}>
          <div style={{ fontSize: 20, fontWeight: 700, color: '#5A474D' }}>{selTemp}</div>
          <div style={{ display: 'inline-block', marginTop: 4, padding: '4px 12px', borderRadius: 999, background: selPhase.bg, color: selPhase.ink, fontSize: 12, fontWeight: 700 }}>{selPhase.label}</div>
        </div>
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, margin: '14px 6px 0' }}>
        <Legend dot={<div style={{ width: 12, height: 12, borderRadius: '50%', background: '#E8A0B4' }} />}>Température</Legend>
        <Legend dot={<div style={{ width: 14, height: 0, borderTop: '2px dashed #B99BD6' }} />}>Ligne de base</Legend>
        <Legend dot={<div style={{ width: 12, height: 12, borderRadius: '50%', border: '2.5px solid #C67A61', boxSizing: 'border-box' }} />}>Hautes</Legend>
        <Legend dot={<div style={{ width: 13, height: 13, borderRadius: '50%', border: '2.5px solid #E4C87E', boxSizing: 'border-box' }} />}>Jour sommet</Legend>
      </div>
      <div style={{ ...CARD_LG, marginTop: 14 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 12 }}>Analyse du cycle</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {anaRows.map((r, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: '#A8908D' }}>{r.k}</div>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#5A474D', textAlign: 'right' }}>{r.v}</div>
            </div>
          ))}
        </div>
        <div style={{ marginTop: 14, padding: '12px 14px', borderRadius: 16, background: '#F6EEFA', fontSize: 13, fontWeight: 600, color: '#7C639A', lineHeight: 1.5 }}>{anaConclusion}</div>
      </div>
      <Glossary />
    </div>
  );

  const calBtn = (label, on, onClick) => (
    <button onClick={onClick} style={{ flex: 1, minHeight: 44, borderRadius: 999, border: '1.5px solid ' + (on ? '#E8A18B' : '#F0E2D6'), background: on ? '#F8E3D6' : '#FDF9F5', color: on ? '#B05F44' : '#A8908D', fontFamily: 'Quicksand,sans-serif', fontSize: 14, fontWeight: 700, cursor: 'pointer' }}>{label}</button>
  );
  const Cal = (
    <div key="cal" data-screen-label="Calendrier" style={screenStyle('78px 22px 120px')}>
      <div style={{ fontSize: 22, fontWeight: 700 }}>Mon cycle</div>
      <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
        {calBtn('Cycle en cours', calTab === 'cycle', () => setCalTab('cycle'))}
        {calBtn('Historique', calTab === 'hist', () => setCalTab('hist'))}
      </div>

      {calTab === 'cycle' ? (
        <React.Fragment>
          <div style={{ ...CARD_LG, padding: 18, marginTop: 14 }}>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gap: 8 }}>
              {calDays.map((d) => (
                <button key={d.n} onClick={() => d.n <= today && openSaisie(d.n)} style={{ aspectRatio: '1', borderRadius: '50%', background: d.bg, border: '2px solid ' + d.ring, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13, fontWeight: 700, color: d.tc, boxSizing: 'border-box', fontFamily: 'Quicksand,sans-serif', cursor: d.n <= today ? 'pointer' : 'default', padding: 0 }}>{d.n}</button>
              ))}
            </div>
          </div>
          <div style={{ ...CARD_LG, padding: '16px 18px', marginTop: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <LegendDot color="#F2BFCB">Règles</LegendDot>
            <LegendDot color="#F6CDB4">Fertile</LegendDot>
            <LegendDot color="#F0AC90">Très fertile</LegendDot>
            <LegendDot color="#DCC8EC">Infertile</LegendDot>
          </div>
          <div style={{ fontSize: 12, fontWeight: 500, color: '#C9B3AB', textAlign: 'center', marginTop: 14 }}>Touche un jour passé pour le compléter ou le corriger.</div>
        </React.Fragment>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 14 }}>
          {cyclesList.map((cy, i) => (
            <div key={i} style={{ ...CARD_LG, padding: 18 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                <div style={{ fontSize: 15, fontWeight: 700 }}>{cy.name}</div>
                <div style={{ fontSize: 12.5, fontWeight: 600, color: '#A8908D' }}>{cy.len}</div>
              </div>
              <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', marginTop: 2 }}>{cy.sub}</div>
              <div style={{ display: 'flex', height: 14, borderRadius: 999, overflow: 'hidden', marginTop: 12 }}>
                {cy.segs.map((s, j) => <div key={j} style={{ width: s.w, background: s.bg }} />)}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );

  const modeDeb = settings.mode === 'debutante';
  const Reglages = (
    <div key="reglages" data-screen-label="Réglages" style={screenStyle('78px 22px 120px')}>
      <div style={{ fontSize: 22, fontWeight: 700 }}>Mes réglages</div>

      <div style={{ ...CARD_LG, marginTop: 16 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Mon compte</div>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: '#5A474D', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{session && session.user ? session.user.email : ''}</div>
          <button onClick={logout} style={{ flexShrink: 0, minHeight: 40, padding: '0 16px', border: '1.5px solid #F0E2D6', borderRadius: 999, background: '#FDF9F5', color: '#A8908D', fontFamily: 'Quicksand,sans-serif', fontSize: 13.5, fontWeight: 700, cursor: 'pointer' }}>Se déconnecter</button>
        </div>
      </div>

      <div style={{ ...CARD_LG, marginTop: 12 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Mon prénom</div>
        <input type="text" value={settings.prenom} onChange={(ev) => setSetting('prenom', ev.target.value)} placeholder="pour te dire bonjour" style={{ width: '100%', boxSizing: 'border-box', border: '1.5px solid #F4E6DC', borderRadius: 12, padding: '11px 14px', fontFamily: 'Quicksand,sans-serif', fontSize: 14, fontWeight: 600, color: '#5A474D', background: '#FDF9F5', outline: 'none' }} />
      </div>

      <div style={{ ...CARD_LG, marginTop: 12 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Mon niveau</div>
        <div style={{ display: 'flex', gap: 8 }}>
          {calBtn('Débutante', modeDeb, () => setSetting('mode', 'debutante'))}
          {calBtn('Experte', !modeDeb, () => setSetting('mode', 'experte'))}
        </div>
        <div style={{ fontSize: 12.5, fontWeight: 500, color: '#A8908D', lineHeight: 1.55, marginTop: 12 }}>
          {modeDeb
            ? 'Les 12 premiers cycles : ta fenêtre s’ouvre à J6, ou à l’apparition de la glaire si elle arrive avant. Sauf si ta 1re haute est à J12 ou avant : le jour Doering prend le relais.'
            : 'À partir du 13e cycle : ta fenêtre s’ouvre au jour Doering (1re haute la plus précoce − 8), ou à l’apparition de la glaire si elle arrive avant.'}
        </div>
      </div>

      <div style={{ ...CARD_LG, marginTop: 12 }}>
        <div style={{ ...SECT_LABEL, marginBottom: 10 }}>Mon objectif</div>
        <ChipRow>
          <Chip label="Contraception" active={settings.objectif === 'contraception'} onClick={() => setSetting('objectif', 'contraception')} />
          <Chip label="Conception" active={settings.objectif === 'conception'} onClick={() => setSetting('objectif', 'conception')} />
          <Chip label="Observation" active={settings.objectif === 'observation'} onClick={() => setSetting('objectif', 'observation')} />
        </ChipRow>
      </div>

      <div style={{ ...CARD_LG, padding: '8px 20px', marginTop: 12 }}>
        <Row>
          <div style={{ fontSize: 14.5, fontWeight: 600 }}>Heure de prise habituelle</div>
          <input type="text" value={settings.heure} onChange={(ev) => setSetting('heure', ev.target.value)} style={{ width: 72, textAlign: 'center', border: '1.5px solid #F4E6DC', borderRadius: 12, padding: '9px 6px', fontFamily: 'Quicksand,sans-serif', fontSize: 14, fontWeight: 700, color: '#5A474D', background: '#FDF9F5', outline: 'none' }} />
        </Row>
        <div style={{ height: 1, background: '#F4E6DC' }} />
        <Row>
          <div style={{ fontSize: 14.5, fontWeight: 600 }}>Rappel du matin</div>
          <Toggle on={settings.rappel} onClick={() => setSetting('rappel', !settings.rappel)} />
        </Row>
        <div style={{ height: 1, background: '#F4E6DC' }} />
        <Row>
          <div style={{ fontSize: 14.5, fontWeight: 600 }}>Afficher le col de l'utérus</div>
          <Toggle on={settings.afficherCol} onClick={() => setSetting('afficherCol', !settings.afficherCol)} />
        </Row>
        <div style={{ height: 1, background: '#F4E6DC' }} />
        <Row>
          <div style={{ fontSize: 14.5, fontWeight: 600 }}>Ambiance animée</div>
          <Toggle on={settings.ambiance} onClick={() => setSetting('ambiance', !settings.ambiance)} />
        </Row>
      </div>

      <div style={{ background: '#F6EEFA', borderRadius: 24, padding: 20, marginTop: 12 }}>
        <div style={{ fontSize: 13, fontWeight: 700, color: '#7C639A', textTransform: 'uppercase', letterSpacing: '0.8px', marginBottom: 8 }}>À retenir</div>
        <div style={{ fontSize: 13, fontWeight: 500, color: '#7C639A', lineHeight: 1.65 }}>Si tes cycles durent 25 jours ou moins, tu es considérée fertile dès le premier jour. La fenêtre se ferme le soir de la 3e température haute, si elle dépasse la ligne de base d'au moins 0,20 °C, 12 h après l'heure de prise. Maximum 2 températures manquantes parmi les 6 basses, 1 parmi les 3 hautes.</div>
      </div>

      <button onClick={startNewCycle} style={{ width: '100%', minHeight: 52, marginTop: 16, border: '1.5px solid #E8A18B', borderRadius: 999, background: '#FDF9F5', color: '#B05F44', fontFamily: 'Quicksand,sans-serif', fontSize: 15, fontWeight: 700, cursor: 'pointer' }}>Mes règles ont commencé — nouveau cycle</button>

      <div style={{ fontSize: 11.5, fontWeight: 500, color: '#C9B3AB', textAlign: 'center', marginTop: 14, lineHeight: 1.5 }}>{getSupa() ? 'Synchro activée — tes données suivent tous tes appareils.' : 'Données enregistrées sur cet appareil.'}</div>

      <button onClick={deleteAccount} style={{ display: 'block', margin: '20px auto 4px', background: 'none', border: 'none', color: '#C88', fontFamily: 'Quicksand,sans-serif', fontSize: 13, fontWeight: 600, cursor: 'pointer', textDecoration: 'underline' }}>Supprimer mon compte</button>
    </div>
  );

  const screens = { home: Home, saisie: Saisie, courbe: Courbe, cal: Cal, reglages: Reglages };

  return (
    <React.Fragment>
      <Blobs on={settings.ambiance} />
      {screens[screen]}

      {/* Tab bar flottante */}
      <div style={{ position: 'absolute', left: 14, right: 14, bottom: 16, height: 72, borderRadius: 999, background: 'rgba(255,255,255,0.82)', backdropFilter: 'blur(14px)', WebkitBackdropFilter: 'blur(14px)', boxShadow: '0 10px 30px rgba(180,130,110,0.2)', display: 'flex', alignItems: 'center', justifyContent: 'space-around', padding: '0 10px', zIndex: 30 }}>
        <TabButton onClick={() => go('home')} color={active('home')} label="Accueil">
          <path d="M4 11.5 12 5l8 6.5V20H4z" fill="none" stroke={active('home')} strokeWidth={2} strokeLinejoin="round" />
        </TabButton>
        <TabButton onClick={() => go('courbe')} color={active('courbe')} label="Courbe">
          <polyline points="3,16 8,12 12,14 21,6" fill="none" stroke={active('courbe')} strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
        </TabButton>
        <button onClick={() => openSaisie(today)} style={{ width: 56, height: 56, borderRadius: '50%', border: 'none', background: 'linear-gradient(135deg,#F2B49B,#E8A0B4)', color: '#FFFFFF', fontSize: 28, fontWeight: 500, cursor: 'pointer', boxShadow: '0 8px 20px rgba(232,160,180,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginTop: -22, lineHeight: 1, fontFamily: 'Quicksand,sans-serif' }}>+</button>
        <TabButton onClick={() => go('cal')} color={active('cal')} label="Cycle">
          <rect x="4" y="5" width="16" height="15" rx="3" fill="none" stroke={active('cal')} strokeWidth={2} />
          <line x1="4" y1="10" x2="20" y2="10" stroke={active('cal')} strokeWidth={2} />
        </TabButton>
        <TabButton onClick={() => go('reglages')} color={active('reglages')} label="Réglages">
          <circle cx="12" cy="12" r="7.5" fill="none" stroke={active('reglages')} strokeWidth={2} />
          <circle cx="12" cy="12" r="2.5" fill={active('reglages')} />
        </TabButton>
      </div>

      {burst && <Burst />}

      {fullChart && (
        <div style={{ position: 'fixed', inset: 0, zIndex: 200, background: 'linear-gradient(180deg,#FDF7F1,#FAF0E7)', display: 'flex', flexDirection: 'column', padding: '14px 16px', boxSizing: 'border-box' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginBottom: 10 }}>
            <div style={{ fontSize: 17, fontWeight: 700, color: '#5A474D' }}>{'Ma courbe — jour ' + today}</div>
            <button onClick={closeFull} style={{ display: 'flex', alignItems: 'center', gap: 7, minHeight: 40, padding: '0 16px', borderRadius: 999, border: '1.5px solid #E8A18B', background: '#F8E3D6', color: '#B05F44', fontFamily: 'Quicksand,sans-serif', fontSize: 13.5, fontWeight: 700, cursor: 'pointer' }}>✕ Fermer</button>
          </div>
          <div style={{ flex: 1, minHeight: 0, background: '#FFFFFF', borderRadius: 20, padding: '8px 6px', boxShadow: '0 6px 20px rgba(214,168,150,0.16)', overflow: 'auto' }}>
            <Chart entries={entries} ana={ana} open={open} sel={sel} onSelect={setSelDay} land seq={chartSeq} />
          </div>
          <div style={{ fontSize: 11.5, fontWeight: 600, color: '#C9B3AB', textAlign: 'center', marginTop: 8 }}>Tourne ton téléphone en paysage · touche un jour pour le détail</div>
        </div>
      )}
    </React.Fragment>
  );
}

/* ---- helpers de rendu ---- */
function screenStyle(padding) {
  return { position: 'absolute', inset: 0, overflow: 'auto', padding, boxSizing: 'border-box', animation: 'fadeUp 0.42s ease', WebkitOverflowScrolling: 'touch' };
}
function TabButton({ onClick, color, label, children }) {
  return (
    <button onClick={onClick} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3, background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'Quicksand,sans-serif', minWidth: 52, minHeight: 52, justifyContent: 'center' }}>
      <svg width="22" height="22" viewBox="0 0 24 24">{children}</svg>
      <span style={{ fontSize: 10.5, fontWeight: 700, color }}>{label}</span>
    </button>
  );
}
function Legend({ dot, children }) {
  return <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 600, color: '#A8908D' }}>{dot}{children}</div>;
}
function LegendDot({ color, children }) {
  return <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5, fontWeight: 600, color: '#A8908D' }}><div style={{ width: 14, height: 14, borderRadius: '50%', background: color, flexShrink: 0 }} />{children}</div>;
}
function Row({ children }) {
  return <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', minHeight: 56, gap: 12 }}>{children}</div>;
}
function Toggle({ on, onClick }) {
  return (
    <button onClick={onClick} style={{ width: 54, height: 32, borderRadius: 999, border: 'none', background: on ? '#E8A0B4' : '#EADDD3', cursor: 'pointer', position: 'relative', padding: 0, transition: 'background 0.25s', flexShrink: 0 }}>
      <div style={{ position: 'absolute', top: 3, left: on ? 25 : 3, width: 26, height: 26, borderRadius: '50%', background: '#FFFFFF', boxShadow: '0 2px 6px rgba(0,0,0,0.18)', transition: 'left 0.25s' }} />
    </button>
  );
}

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