// Résonance — Features §3.10 → §3.18 (v3.2)
// Life context tags · Word cloud · Constellation · Pre-activity ·
// Pause · Audio letter · Flow sync

const { useState: uSv, useEffect: uEv, useMemo: uMv, useRef: uRv } = React;

// ═════════════════════════════════════════════════════════════
// 3.10 — N1 State-of-the-day with 3 micro context tags
// ═════════════════════════════════════════════════════════════
function F310_Context({ go }) {
  const [tags, setTags] = uSv({ sleep: null, mental: null, form: null });
  const set = (k, v) => setTags(t => ({ ...t, [k]: t[k] === v ? null : v }));
  const filled = Object.values(tags).filter(Boolean).length;

  const Tag = ({ k, emoji, label }) => {
    const val = tags[k];
    return (
      <Glass style={{ padding: 14, marginBottom: 10 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
          <div style={{ fontSize: 22 }}>{emoji}</div>
          <div style={{ flex: 1, fontSize: 14, fontWeight: 600, color: RZ.fg1 }}>{label}</div>
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          {[['good','Bon'],['avg','Moyen'],['tough','Dur']].map(([v,l]) => (
            <div key={v} onClick={() => set(k, v)} style={{
              flex: 1, textAlign: 'center', padding: '10px 6px', borderRadius: 10,
              background: val === v ? RZ.black : 'rgba(255,255,255,0.5)',
              color: val === v ? RZ.white : RZ.fg1,
              border: val === v ? 'none' : '1px solid rgba(24,20,15,0.08)',
              fontSize: 12, fontWeight: 500, cursor: 'pointer',
            }}>{l}</div>
          ))}
        </div>
      </Glass>
    );
  };

  return (
    <Screen intent="nuance">
      <TopBar onBack={() => go('S6_Selector')}/>
      <div style={{ padding: '0 20px 40px' }}>
        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.blue, textTransform: 'uppercase', letterSpacing: 1.4, marginBottom: 8 }}>État du jour</div>
        <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 8, lineHeight: 1.25 }}>
          Avant de me parler — trois petites choses, si tu veux.
        </div>
        <div style={{ fontSize: 13, color: RZ.fg2, lineHeight: 1.55, marginBottom: 22 }}>
          Ces signaux nourrissent silencieusement mon écoute. Ils n'apparaîtront pas dans ton journal.
        </div>

        <Tag k="sleep" emoji="💤" label="Sommeil"/>
        <Tag k="mental" emoji="🧠" label="Charge mentale"/>
        <Tag k="form" emoji="💪" label="Forme générale"/>

        <PrimaryBtn onClick={() => go('S7_Record', { from: 'context', tags })} style={{ marginTop: 14 }}>
          {filled > 0 ? `Continuer · ${filled}/3` : 'Continuer'}
        </PrimaryBtn>
        <GhostBtn onClick={() => go('S7_Record')} style={{ display: 'block', margin: '6px auto 0' }}>Passer</GhostBtn>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.11 — 30-day word cloud
// ═════════════════════════════════════════════════════════════
const WORDS = [
  // [word, weight, register]
  ['genoux', 22, 'body'], ['jambes', 18, 'body'], ['épaules', 14, 'body'],
  ['dos', 11, 'body'], ['souffle', 13, 'body'], ['cœur', 9, 'body'],
  ['hanches', 7, 'body'], ['tendon', 6, 'body'],
  ['fatigue', 24, 'fusion'], ['lourd', 16, 'fusion'], ['léger', 12, 'fusion'],
  ['pression', 17, 'mind'], ['travail', 14, 'mind'], ['stress', 13, 'mind'],
  ['envie', 11, 'mind'], ['curieux', 8, 'mind'], ['confiance', 10, 'mind'],
  ['matin', 9, 'mind'], ['soir', 8, 'mind'],
  ['plaisir', 13, 'fusion'], ['fluide', 9, 'fusion'],
];
const REG_COLOR = { body: '#C66A3A', mind: '#3F6FB8', fusion: '#8A5B95' };

function F311_WordCloud({ go }) {
  const [zoom, setZoom] = uSv(null); // word focused
  const positioned = uMv(() => {
    let rng = 7; const r = () => { rng = (rng*9301+49297) % 233280; return rng/233280; };
    const out = []; const W = 340, H = 280;
    const sorted = [...WORDS].sort((a,b) => b[1] - a[1]);
    for (const [w, weight, reg] of sorted) {
      let placed = false;
      for (let try_ = 0; try_ < 40 && !placed; try_++) {
        const fs = 11 + weight * 0.95;
        const x = 20 + r() * (W - 40);
        const y = 30 + r() * (H - 50);
        const w_ = w.length * fs * 0.5;
        const h_ = fs * 1.2;
        const bb = { x: x - w_/2, y: y - h_/2, w: w_, h: h_ };
        const overlap = out.some(p => !(bb.x + bb.w + 4 < p.bb.x || bb.x > p.bb.x + p.bb.w + 4 || bb.y + bb.h + 2 < p.bb.y || bb.y > p.bb.y + p.bb.h + 2));
        if (!overlap) { out.push({ w, fs, x, y, reg, bb, weight }); placed = true; }
      }
    }
    return out;
  }, []);

  return (
    <Screen intent="echo" tabBar={<TabBar active="echo" onChange={id => go(id === 'journal' ? 'S5_Journal' : id === 'nuance' ? 'S6_Selector' : 'S13_Echo')}/>}>
      <TopBar onBack={() => go('S13_Echo')}/>
      <div style={{ padding: '0 20px 120px' }}>
        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.mauve, textTransform: 'uppercase', letterSpacing: 1.4, marginBottom: 6 }}>Nuage · 30 jours</div>
        <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 6 }}>Ce que ton corps dit.</div>
        <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.5, marginBottom: 18 }}>
          Les mots que tu as prononcés ces 30 derniers jours. Plus c'est gros, plus c'est revenu.
        </div>

        <Glass style={{ padding: 0, position: 'relative', overflow: 'hidden', height: 300, marginBottom: 14 }}>
          <svg viewBox="0 0 340 280" width="100%" height="100%" style={{ display: 'block' }}>
            {positioned.map((p, i) => (
              <text key={i} x={p.x} y={p.y} fontSize={p.fs} fontWeight={p.weight > 15 ? 700 : 500}
                fill={REG_COLOR[p.reg]} opacity={zoom && zoom !== p.w ? 0.18 : 0.92}
                textAnchor="middle" fontFamily="Inter" letterSpacing="-0.3"
                style={{ cursor: 'pointer', transition: 'opacity 220ms ease' }}
                onClick={() => setZoom(zoom === p.w ? null : p.w)}>
                {p.w}
              </text>
            ))}
          </svg>
        </Glass>

        <div style={{ display: 'flex', gap: 14, marginBottom: 20, fontSize: 11, color: RZ.fg2 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: REG_COLOR.body }}/>Corps
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: REG_COLOR.mind }}/>Esprit
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: REG_COLOR.fusion }}/>Fusion
          </div>
        </div>

        {zoom && (() => {
          const w = positioned.find(p => p.w === zoom);
          const occ = Math.round(w.weight / 2);
          return (
            <Glass raised style={{ padding: 16, marginBottom: 14, animation: 'rz-slide-up 240ms ease-out' }}>
              <div style={{ fontSize: 10, fontWeight: 600, color: REG_COLOR[w.reg], textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }}>
                {w.reg === 'body' ? 'Corps' : w.reg === 'mind' ? 'Esprit' : 'Fusion'}
              </div>
              <div style={{ fontSize: 22, fontWeight: 700, color: RZ.fg1, marginBottom: 8, fontStyle: 'italic' }}>« {zoom} »</div>
              <div style={{ fontSize: 13, color: RZ.fg2, lineHeight: 1.6, marginBottom: 12 }}>
                Tu as parlé de tes <b>{zoom}</b> {occ} fois depuis le 1er avril.
              </div>
              <div style={{ display: 'flex', gap: 4, alignItems: 'flex-end', height: 40, marginBottom: 12 }}>
                {[...Array(30)].map((_, i) => {
                  const h = ((i * 7 + zoom.length * 3) % 30) / 30;
                  const active = h > 0.3;
                  return <div key={i} style={{ flex: 1, height: active ? `${20 + h * 18}px` : 3, background: active ? REG_COLOR[w.reg] : 'rgba(24,20,15,0.08)', borderRadius: 1, opacity: active ? 0.6 + h * 0.4 : 1 }}/>;
                })}
              </div>
              <div style={{ fontSize: 10, color: RZ.fg3, display: 'flex', justifyContent: 'space-between' }}>
                <span>1 avril</span><span>aujourd'hui</span>
              </div>
              <div onClick={() => setZoom(null)} style={{ marginTop: 14, fontSize: 11, color: RZ.fg3, textAlign: 'center', cursor: 'pointer', textDecoration: 'underline' }}>
                Long-tap pour exclure ce mot
              </div>
            </Glass>
          );
        })()}

        <Glass style={{ padding: 14 }}>
          <div style={{ fontSize: 10, fontWeight: 600, color: RZ.fg3, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 6 }}>Ce que je ne fais pas</div>
          <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.55 }}>
            Pas de top-3, pas de comparaison entre utilisateurs, jamais d'alerte médicale.
          </div>
        </Glass>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.12 — Annual constellation (horizontal thread)
// ═════════════════════════════════════════════════════════════
function F312_Constellation({ go }) {
  const [season, setSeason] = uSv('all');
  const [picked, setPicked] = uSv(null);
  const data = uMv(() => {
    const states = ['resonance','equilibre','tension','dissonance','alerte'];
    const bias = { winter: [0.15,0.25,0.30,0.20,0.10], spring: [0.30,0.35,0.20,0.10,0.05], summer: [0.35,0.30,0.20,0.10,0.05], fall: [0.20,0.30,0.25,0.18,0.07] };
    const seasonOf = m => m < 3 ? 'winter' : m < 6 ? 'spring' : m < 9 ? 'summer' : 'fall';
    let rng = 13;
    const r = () => { rng = (rng*9301+49297) % 233280; return rng/233280; };
    const out = [];
    for (let m = 0; m < 12; m++) {
      const days = 8 + Math.floor(r() * 6); // ~10 nuances per month
      const w = bias[seasonOf(m)];
      for (let d = 0; d < days; d++) {
        const x = r();
        let acc = 0; let s = 'equilibre';
        for (let k = 0; k < 5; k++) { acc += w[k]; if (x < acc) { s = states[k]; break; } }
        out.push({ m, d, s, jitter: r() });
      }
    }
    return out;
  }, []);

  const filtered = season === 'all' ? data : data.filter(p => {
    const seasonMonths = { winter: [11,0,1], spring: [2,3,4], summer: [5,6,7], fall: [8,9,10] };
    return seasonMonths[season].includes(p.m);
  });

  const W = 1200, H = 120;
  const months = ['Jan','Fév','Mar','Avr','Mai','Juin','Juil','Aoû','Sep','Oct','Nov','Déc'];
  const COLOR = { resonance: '#8A5B95', equilibre: '#90B8F0', tension: '#D98964', dissonance: '#F0A090', alerte: '#FC442C' };
  const ALT = { resonance: 0.85, equilibre: 0.65, tension: 0.45, dissonance: 0.30, alerte: 0.15 };

  return (
    <Screen intent="echo" tabBar={<TabBar active="echo" onChange={id => go(id === 'journal' ? 'S5_Journal' : id === 'nuance' ? 'S6_Selector' : 'S13_Echo')}/>}>
      <TopBar onBack={() => go('S13_Echo')}/>
      <div style={{ padding: '0 20px 120px' }}>
        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.mauve, textTransform: 'uppercase', letterSpacing: 1.4, marginBottom: 6 }}>Constellation · 2026</div>
        <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 6 }}>Une année comme un fil.</div>
        <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.5, marginBottom: 18 }}>
          Pas de podium, pas de moyenne. Chaque point est une nuance déposée.
        </div>

        <div style={{ display: 'flex', gap: 6, marginBottom: 14, overflowX: 'auto' }}>
          {[['all','Année'],['winter','Hiver'],['spring','Printemps'],['summer','Été'],['fall','Automne']].map(([v,l]) => (
            <Chip key={v} on={season === v} onClick={() => setSeason(v)}>{l}</Chip>
          ))}
        </div>

        <Glass style={{ padding: 12, overflowX: 'auto', marginBottom: 14 }}>
          <div style={{ width: season === 'all' ? W : W * 0.34, height: H + 30, position: 'relative' }}>
            <svg viewBox={`0 0 ${season === 'all' ? W : W * 0.34} ${H + 30}`} width="100%" height={H + 30}>
              {/* Month dividers */}
              {(season === 'all' ? months : months.filter((_, i) => {
                const sm = { winter: [11,0,1], spring: [2,3,4], summer: [5,6,7], fall: [8,9,10] };
                return sm[season].includes(i);
              })).map((m, i, arr) => (
                <g key={m}>
                  <line x1={(i+1)/arr.length * (season === 'all' ? W : W*0.34)} y1="10" x2={(i+1)/arr.length * (season === 'all' ? W : W*0.34)} y2={H} stroke="rgba(24,20,15,0.08)" strokeDasharray="2 3"/>
                  <text x={(i+0.5)/arr.length * (season === 'all' ? W : W*0.34)} y={H + 22} fontSize="10" fill={RZ.fg3} fontFamily="Inter" textAnchor="middle">{m}</text>
                </g>
              ))}
              {/* Harmony zone */}
              <line x1="0" y1={H * 0.25} x2={season === 'all' ? W : W*0.34} y2={H * 0.25} stroke="rgba(138,91,149,0.15)" strokeDasharray="4 5"/>

              {filtered.map((p, i) => {
                const totalSpan = season === 'all' ? 12 : 3;
                const monthInSpan = season === 'all' ? p.m : (season === 'winter' ? [11,0,1].indexOf(p.m) : season === 'spring' ? p.m - 2 : season === 'summer' ? p.m - 5 : p.m - 8);
                const x = ((monthInSpan + p.d / 12) / totalSpan) * (season === 'all' ? W : W*0.34);
                const y = (1 - ALT[p.s]) * (H - 20) + 10 + p.jitter * 6 - 3;
                const r2 = picked === i ? 6 : 3.2;
                return (
                  <circle key={i} cx={x} cy={y} r={r2} fill={COLOR[p.s]} opacity={0.85}
                    style={{ cursor: 'pointer', transition: 'r 200ms' }}
                    onClick={() => setPicked(picked === i ? null : i)}/>
                );
              })}
            </svg>
          </div>
        </Glass>

        {picked !== null && filtered[picked] && (
          <Glass raised style={{ padding: 14, marginBottom: 14, animation: 'rz-slide-up 240ms' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
              <div style={{ width: 14, height: 14, borderRadius: '50%', background: COLOR[filtered[picked].s] }}/>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: RZ.fg1 }}>{months[filtered[picked].m]} · jour {filtered[picked].d + 1}</div>
                <div style={{ fontSize: 11, color: RZ.fg3 }}>{STATE_META[filtered[picked].s].label}</div>
              </div>
              <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '6px 10px', background: 'rgba(24,20,15,0.05)', borderRadius: 16, cursor: 'pointer' }}>
                <Icon name="play" size={11}/>
                <span style={{ fontSize: 11 }}>10 s</span>
              </div>
            </div>
          </Glass>
        )}

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, fontSize: 11, color: RZ.fg2 }}>
          {Object.entries(COLOR).map(([k, c]) => (
            <div key={k} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: c }}/>{STATE_META[k].label}
            </div>
          ))}
        </div>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.13 — Pre-Activity Nuance selector (4 options)
// ═════════════════════════════════════════════════════════════
function F313_Selector({ go }) {
  const opts = [
    { id: 'pre',  emoji: '🌅', label: 'Pré-activité',     desc: 'Ce que ton corps te dit avant',   tag: 'NOUVEAU' },
    { id: 'post', emoji: '🏃', label: 'Post-activité',    desc: 'Lier à une séance Strava' },
    { id: 'day',  emoji: '☀️', label: 'État du jour',     desc: 'Sans activité particulière' },
    { id: 'free', emoji: '💭', label: 'Expression libre', desc: 'Pour parler, simplement' },
  ];
  return (
    <Screen intent="nuance" tabBar={<TabBar active="nuance" onChange={id => go(id === 'journal' ? 'S5_Journal' : id === 'nuance' ? 'S6_Selector' : 'S13_Echo')}/>}>
      <TopBar/>
      <div style={{ padding: '0 20px 120px' }}>
        <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 6 }}>Déposer une nuance</div>
        <div style={{ fontSize: 13, color: RZ.fg2, marginBottom: 22 }}>Quatre portes. Aucune n'est meilleure.</div>
        {opts.map(o => (
          <Glass key={o.id} raised={o.id === 'pre'} onClick={() => go(o.id === 'day' ? 'F310_Context' : o.id === 'pre' ? 'F313_Pre' : 'S7_Record', { type: o.id })} style={{ padding: 16, marginBottom: 10, cursor: 'pointer' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
              <div style={{ fontSize: 26 }}>{o.emoji}</div>
              <div style={{ flex: 1 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <div style={{ fontSize: 15, fontWeight: 600, color: RZ.fg1 }}>{o.label}</div>
                  {o.tag && <div style={{ fontSize: 9, fontWeight: 700, color: RZ.mauve, background: 'rgba(138,91,149,0.14)', padding: '2px 7px', borderRadius: 8, letterSpacing: 0.5 }}>{o.tag}</div>}
                </div>
                <div style={{ fontSize: 12, color: RZ.fg2, marginTop: 2 }}>{o.desc}</div>
              </div>
              <Icon name="chev" size={14} color={RZ.fg3}/>
            </div>
          </Glass>
        ))}
      </div>
    </Screen>
  );
}

// 3.13 — Pre-activity record screen (with arc preview)
function F313_Pre({ go }) {
  return (
    <Screen intent="nuance">
      <TopBar onBack={() => go('F313_Selector')}/>
      <div style={{ padding: '0 24px 40px', display: 'flex', flexDirection: 'column', height: 'calc(100% - 80px)' }}>
        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.orange, textTransform: 'uppercase', letterSpacing: 1.4, marginBottom: 8 }}>Pré-activité</div>
        <div style={{ fontSize: 22, fontWeight: 600, color: RZ.fg1, letterSpacing: -0.3, lineHeight: 1.3, marginBottom: 24 }}>
          « Qu'est-ce que ton corps te dit, juste avant de commencer ? »
        </div>

        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <button style={{
            width: 140, height: 140, borderRadius: '50%',
            background: `radial-gradient(circle at 40% 35%, ${RZ.orange}ee, ${RZ.orange}70)`,
            boxShadow: `0 14px 36px -10px ${RZ.orange}90, inset 0 0 18px rgba(255,255,255,0.35)`,
            border: 'none', animation: 'rz-breath 4s ease-in-out infinite', cursor: 'pointer',
          }}>
            <Icon name="mic" size={44} color="#fff"/>
          </button>
        </div>

        <Glass style={{ padding: 14, marginBottom: 14 }}>
          <div style={{ fontSize: 10, fontWeight: 600, color: RZ.fg3, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }}>Au retour de ta séance</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
              <div style={{ width: 14, height: 14, borderRadius: '50%', background: RZ.orange, opacity: 0.85 }}/>
              <div style={{ width: 22, height: 1, background: 'rgba(24,20,15,0.2)', borderTop: '1px dashed rgba(24,20,15,0.3)' }}/>
              <div style={{ width: 14, height: 14, borderRadius: '50%', background: 'rgba(24,20,15,0.06)', border: '1.5px dashed rgba(24,20,15,0.25)' }}/>
            </div>
            <div style={{ flex: 1, fontSize: 12, color: RZ.fg2, lineHeight: 1.5 }}>
              Je te proposerai une nuance d'après pour relier les deux.
            </div>
          </div>
        </Glass>

        <SecondaryBtn onClick={() => go('S6_Selector')}>Plus tard</SecondaryBtn>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.14 — Pause Résonance (settings entry + immersive state)
// ═════════════════════════════════════════════════════════════
function F314_Pause({ go }) {
  const [days, setDays] = uSv(7);
  return (
    <Screen intent="settings">
      <TopBar onBack={() => go('S15_Settings')} title="Pause Résonance"/>
      <div style={{ padding: '0 20px 60px' }}>
        <div style={{ margin: '12px auto 22px' }}><NuanceViz state="equilibre" size={140}/></div>
        <div style={{ fontSize: 22, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 10, textAlign: 'center' }}>L'app qui s'efface.</div>
        <div style={{ fontSize: 13, color: RZ.fg2, lineHeight: 1.6, marginBottom: 24, textAlign: 'center' }}>
          Pendant ta pause : aucune notification. Strava continue d'agréger en silence. Ton compteur Harmonie est <b>gelé</b>, jamais remis à zéro.
        </div>

        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.fg3, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }}>Durée</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 22 }}>
          {[7, 14, 30].map(d => (
            <Glass key={d} onClick={() => setDays(d)} raised={days === d} style={{
              flex: 1, padding: 16, textAlign: 'center', cursor: 'pointer',
              border: days === d ? `2px solid ${RZ.mauve}` : '1px solid rgba(255,255,255,0.7)',
            }}>
              <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3 }}>{d}</div>
              <div style={{ fontSize: 11, color: RZ.fg3, marginTop: 2 }}>jours</div>
            </Glass>
          ))}
        </div>

        <PrimaryBtn onClick={() => go('F314_Active', { days })}>Mettre en pause · {days} jours</PrimaryBtn>
        <GhostBtn onClick={() => go('S15_Settings')} style={{ display: 'block', margin: '8px auto 0' }}>Annuler</GhostBtn>
      </div>
    </Screen>
  );
}

function F314_Active({ go, ctx }) {
  const days = ctx?.days || 7;
  return (
    <Screen intent="settings" noStatus>
      <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg, #1F1A14 0%, #0F0C09 100%)' }}>
        <div style={{ position: 'absolute', inset: 0, opacity: 0.18, background: `radial-gradient(circle at 50% 35%, ${RZ.mauve}, transparent 60%)`, filter: 'blur(40px)' }}/>
      </div>
      <div style={{ position: 'relative', zIndex: 2, padding: 60, height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', color: '#FAF8F5' }}>
        <div style={{ width: 120, height: 120, borderRadius: '50%', background: `radial-gradient(circle at 40% 35%, ${RZ.mauve}cc, ${RZ.mauve}33)`, animation: 'rz-breath 6s ease-in-out infinite', marginBottom: 36, boxShadow: `0 0 60px ${RZ.mauve}40` }}/>
        <div style={{ fontSize: 10, fontWeight: 600, color: 'rgba(250,248,245,0.5)', textTransform: 'uppercase', letterSpacing: 2, marginBottom: 14 }}>Pause active · {days} jours</div>
        <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: -0.3, lineHeight: 1.4, marginBottom: 18, maxWidth: 300 }}>
          Résonance est en pause.<br/>Ton corps n'a pas besoin de toi ici aujourd'hui.
        </div>
        <div style={{ fontSize: 12, color: 'rgba(250,248,245,0.6)', marginBottom: 32 }}>Reprend automatiquement le 15 mai.</div>
        <div onClick={() => go('S15_Settings')} style={{ fontSize: 12, color: 'rgba(250,248,245,0.6)', textDecoration: 'underline', cursor: 'pointer' }}>Reprendre maintenant</div>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.17 — Audio letter to self
// ═════════════════════════════════════════════════════════════
function F317_Letter({ go }) {
  const [playing, setPlaying] = uSv(false);
  const [t, setT] = uSv(0);
  const total = 78;
  uEv(() => {
    if (!playing) return;
    const id = setInterval(() => setT(x => x + 1 < total ? x + 1 : (setPlaying(false), total)), 1000);
    return () => clearInterval(id);
  }, [playing]);

  const segments = [
    { from: 0, to: 18, kind: 'extract', label: '« Les jambes… vraiment lourdes ce matin. »', date: '3 avril' },
    { from: 18, to: 26, kind: 'transition', label: 'Tu parlais de fatigue début avril.' },
    { from: 26, to: 44, kind: 'extract', label: '« Quelque chose qui se desserre, je crois. »', date: '14 avril' },
    { from: 44, to: 54, kind: 'transition', label: 'Puis quelque chose s\'est desserré…' },
    { from: 54, to: 78, kind: 'extract', label: '« Confiant. Léger. Curieux d\'entrer dans mai. »', date: '28 avril' },
  ];
  const cur = segments.find(s => t >= s.from && t < s.to) || segments[0];

  return (
    <Screen intent="echo" tabBar={<TabBar active="echo" onChange={id => go(id === 'journal' ? 'S5_Journal' : id === 'nuance' ? 'S6_Selector' : 'S13_Echo')}/>}>
      <TopBar onBack={() => go('S13_Echo')}/>
      <div style={{ padding: '0 20px 120px' }}>
        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.mauve, textTransform: 'uppercase', letterSpacing: 1.4, marginBottom: 6 }}>Lettre à toi · Avril 2026</div>
        <div style={{ fontSize: 24, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 6 }}>Ton mois, par ta voix.</div>
        <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.55, marginBottom: 24 }}>
          Trois extraits que j'ai gardés. Du lien entre eux, doucement.
        </div>

        <Glass raised style={{ padding: 18, marginBottom: 14, position: 'relative', overflow: 'hidden' }}>
          {/* Waveform-like bars */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 2, height: 60, marginBottom: 18 }}>
            {[...Array(60)].map((_, i) => {
              const inThis = i / 60 * total;
              const seg = segments.find(s => inThis >= s.from && inThis < s.to);
              const isExtract = seg?.kind === 'extract';
              const h = isExtract ? 14 + ((i * 13) % 40) : 6;
              const passed = (i / 60) * total < t;
              return <div key={i} style={{ flex: 1, height: h, background: passed ? RZ.mauve : isExtract ? 'rgba(138,91,149,0.35)' : 'rgba(24,20,15,0.12)', borderRadius: 1 }}/>;
            })}
          </div>

          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: RZ.fg3, fontVariantNumeric: 'tabular-nums', marginBottom: 16 }}>
            <span>0:{String(t).padStart(2,'0')}</span><span>1:18</span>
          </div>

          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16 }}>
            <div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(24,20,15,0.05)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
              <Icon name="back" size={14}/>
            </div>
            <button onClick={() => setPlaying(p => !p)} style={{
              width: 56, height: 56, borderRadius: '50%',
              background: `radial-gradient(circle at 40% 35%, ${RZ.mauve}ee, ${RZ.mauve}80)`,
              boxShadow: `0 8px 20px -6px ${RZ.mauve}80`, border: 'none', cursor: 'pointer',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>
              <Icon name={playing ? 'pause' : 'play'} size={22} color="#fff"/>
            </button>
            <div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(24,20,15,0.05)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
              <Icon name="arrowRight" size={14}/>
            </div>
          </div>
        </Glass>

        <Glass style={{ padding: 14, marginBottom: 14 }}>
          <div style={{ fontSize: 10, fontWeight: 600, color: cur.kind === 'extract' ? RZ.blue : RZ.mauve, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 8 }}>
            {cur.kind === 'extract' ? `Extrait · ${cur.date}` : 'Transition'}
          </div>
          <div style={{ fontSize: 14, color: RZ.fg1, lineHeight: 1.6, fontStyle: cur.kind === 'extract' ? 'italic' : 'normal' }}>
            {cur.label}
          </div>
        </Glass>

        <div style={{ fontSize: 10, fontWeight: 600, color: RZ.fg3, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }}>Structure</div>
        {segments.map((s, i) => (
          <div key={i} onClick={() => setT(s.from)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 0', borderBottom: '1px solid rgba(24,20,15,0.05)', cursor: 'pointer' }}>
            <div style={{ width: 6, height: 6, borderRadius: '50%', background: s.kind === 'extract' ? RZ.blue : RZ.mauve }}/>
            <div style={{ flex: 1, fontSize: 12, color: RZ.fg1 }}>{s.kind === 'extract' ? `Extrait — ${s.date}` : 'Transition IA'}</div>
            <div style={{ fontSize: 11, color: RZ.fg3, fontVariantNumeric: 'tabular-nums' }}>0:{String(s.from).padStart(2,'0')}</div>
          </div>
        ))}

        <Glass style={{ padding: 14, marginTop: 16 }}>
          <div style={{ fontSize: 10, fontWeight: 600, color: RZ.fg3, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 6 }}>Reste privé</div>
          <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.55 }}>
            Téléchargeable en m4a. Jamais partagé, jamais en cloud public.
          </div>
        </Glass>
      </div>
    </Screen>
  );
}

// ═════════════════════════════════════════════════════════════
// 3.18 — Flow sync (cycle, in Settings → Integrations)
// ═════════════════════════════════════════════════════════════
function F318_Flow({ go }) {
  const [step, setStep] = uSv('intro'); // intro | oauth | done
  return (
    <Screen intent="settings">
      <TopBar onBack={() => go('S15_Settings')} title="Connecter Flow"/>
      <div style={{ padding: '0 20px 60px' }}>
        {step === 'intro' && (
          <>
            <div style={{ width: 64, height: 64, margin: '12px 0 22px', borderRadius: 16, background: `linear-gradient(135deg, ${RZ.blue} 0%, ${RZ.mauve} 100%)`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <span style={{ fontSize: 28 }}>🌙</span>
            </div>
            <div style={{ fontSize: 22, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 12 }}>Le cycle hormonal, écouté — pas surveillé.</div>
            <div style={{ fontSize: 13, color: RZ.fg2, lineHeight: 1.6, marginBottom: 22 }}>
              Si tu connectes Flow, je lis <b>uniquement</b> la phase de ton cycle pour adapter mon écoute. Aucune autre donnée n'entre dans Résonance.
            </div>

            <Glass style={{ padding: 14, marginBottom: 10 }}>
              <div style={{ fontSize: 10, fontWeight: 600, color: RZ.mauve, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }}>Ce que je lis</div>
              {['Folliculaire','Ovulatoire','Lutéale','Menstruelle'].map(p => (
                <div key={p} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0', fontSize: 13, color: RZ.fg1 }}>
                  <Icon name="check" size={14} color={RZ.mauve}/>
                  Phase {p.toLowerCase()}
                </div>
              ))}
            </Glass>

            <Glass style={{ padding: 14, marginBottom: 22 }}>
              <div style={{ fontSize: 10, fontWeight: 600, color: '#A82E1A', textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }}>Ce que je ne lis pas</div>
              {['Symptômes','Dates précises','Humeur','Notes personnelles'].map(p => (
                <div key={p} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0', fontSize: 13, color: RZ.fg2 }}>
                  <Icon name="close" size={14} color={RZ.fg3}/>
                  {p}
                </div>
              ))}
            </Glass>

            <div style={{ fontSize: 12, color: RZ.fg2, lineHeight: 1.6, fontStyle: 'italic', marginBottom: 22 }}>
              La phase reste invisible dans l'app. Elle sert uniquement de contexte silencieux à mon écoute. Révocable en 1 tap, à tout moment.
            </div>

            <PrimaryBtn onClick={() => setStep('oauth')}>Connecter Flow (OAuth)</PrimaryBtn>
            <GhostBtn onClick={() => go('S15_Settings')} style={{ display: 'block', margin: '8px auto 0' }}>Pas pour l'instant</GhostBtn>
          </>
        )}
        {step === 'oauth' && (
          <div style={{ padding: '40px 0', textAlign: 'center' }}>
            <div style={{ width: 60, height: 60, borderRadius: '50%', border: `3px solid ${RZ.mauve}`, borderTopColor: 'transparent', margin: '0 auto 20px', animation: 'rz-spin 1s linear infinite' }}/>
            <div style={{ fontSize: 14, color: RZ.fg2 }}>Redirection vers Flow…</div>
            <div onClick={() => setStep('done')} style={{ marginTop: 30, fontSize: 12, color: RZ.fg3, textDecoration: 'underline', cursor: 'pointer' }}>(simuler la fin du flow OAuth)</div>
          </div>
        )}
        {step === 'done' && (
          <div style={{ padding: '40px 0', textAlign: 'center' }}>
            <div style={{ margin: '0 auto 22px' }}><NuanceViz state="resonance" size={140}/></div>
            <div style={{ fontSize: 20, fontWeight: 700, color: RZ.fg1, letterSpacing: -0.3, marginBottom: 10 }}>C'est connecté.</div>
            <div style={{ fontSize: 13, color: RZ.fg2, lineHeight: 1.55, maxWidth: 280, margin: '0 auto 28px' }}>
              Je continue à écouter ce que tu me dis — avec un peu plus de contexte, en silence.
            </div>
            <PrimaryBtn onClick={() => go('S15_Settings')} style={{ maxWidth: 260, margin: '0 auto' }}>Retour aux paramètres</PrimaryBtn>
          </div>
        )}
      </div>
    </Screen>
  );
}

Object.assign(window, {
  F310_Context, F311_WordCloud, F312_Constellation,
  F313_Selector, F313_Pre,
  F314_Pause, F314_Active,
  F317_Letter, F318_Flow,
});
