/* App shell — nav 6 zones, shell multi-langues, vues secondaires, réglages réels,
   thème sombre, navigation mobile, panneau Architecture + mapping. */

const NAV = [
  { k: "today", label: "Aujourd'hui", icon: "today" },
  { k: "apprendre", label: "Apprendre", icon: "learn" },
  { k: "cartes", label: "Cartes", icon: "grid" },
  { k: "atelier", label: "Atelier", icon: "spark" },
  { k: "dialogues", label: "Dialogues", icon: "chat" },
  { k: "produire", label: "Produire", icon: "pen" },
  { k: "pilotage", label: "Pilotage", icon: "chart" },
];
const MOBILE_NAV_KEYS = ["today", "atelier", "cartes", "dialogues", "pilotage"];

/* ---------- Shell multi-langues ---------- */
function LangShell({ lang, setLang }) {
  const [open, setOpen] = useState(false);
  const mods = window.LANGUES_DATA.modules;
  const cur = mods.find(m => m.id === lang) || mods[0];
  return (
    <div style={{ position: "relative" }}>
      <button onClick={() => setOpen(o => !o)} style={{
        display: "flex", alignItems: "center", gap: 10, width: "100%", padding: "9px 11px",
        borderRadius: 11, border: "1px solid var(--rule)", background: "var(--card)", cursor: "pointer", textAlign: "left",
      }}>
        <span style={{ width: 26, height: 26, borderRadius: 7, background: cur.accent, color: "#fff", display: "grid", placeItems: "center", flexShrink: 0 }} className="kr">{cur.native[0]}</span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: "block", fontWeight: 700, fontSize: 13.5, color: "var(--ink)" }} className="lbl-full">{cur.label}</span>
          <span className="faint lbl-full" style={{ fontSize: 11 }}>Module actif</span>
        </span>
        <Icon name="chevron" size={16} style={{ color: "var(--ink-faint)", transform: open ? "rotate(180deg)" : "none", transition: "transform .2s" }} />
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }} />
          <div className="fade-up" style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, width: 252, zIndex: 31, background: "var(--paper)", border: "1px solid var(--rule)", borderRadius: 14, boxShadow: "var(--shadow-pop)", padding: 10 }}>
            <div className="eyebrow" style={{ padding: "4px 8px 8px" }}>Langues</div>
            <div style={{ display: "grid", gap: 4 }}>
              {mods.map(m => {
                const on = m.id === lang;
                const prevu = m.id !== "ko" || m.state === "prévu";
                return (
                  <button key={m.id} disabled={prevu} onClick={() => { setLang(m.id); setOpen(false); }} style={{
                    display: "flex", alignItems: "center", gap: 10, padding: "9px 8px", borderRadius: 10, border: "none",
                    background: on ? "var(--accent-tint)" : "transparent", cursor: prevu ? "default" : "pointer", textAlign: "left", opacity: prevu ? .55 : 1,
                  }}>
                    <span className="kr" style={{ width: 26, height: 26, borderRadius: 7, background: m.accent, color: "#fff", display: "grid", placeItems: "center", flexShrink: 0, fontSize: 13 }}>{m.native[0]}</span>
                    <span style={{ flex: 1, minWidth: 0 }}>
                      <span style={{ display: "block", fontWeight: 700, fontSize: 13.5 }}>{m.label}</span>
                      <span className="faint" style={{ fontSize: 11 }}>{prevu ? "prévu" : `${m.items.toLocaleString("fr-FR")} items`}</span>
                    </span>
                    {prevu ? <Icon name="lock" size={15} style={{ color: "var(--ink-faint)" }} /> : on ? <Icon name="check" size={16} style={{ color: "var(--accent)" }} /> : null}
                  </button>
                );
              })}
            </div>
            <div style={{ borderTop: "1px solid var(--rule)", marginTop: 8, paddingTop: 8, display: "grid", gap: 2 }}>
              <button className="side-min" disabled title="À brancher au data-loader" style={{ fontSize: 13 }}><Icon name="layers" size={16} /> Sources de données</button>
              <button className="side-min" disabled title="À définir quand les routes paramétrées seront stabilisées" style={{ fontSize: 13 }}><Icon name="spark" size={16} /> Raccourcis</button>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function Sidebar({ route, go, onMethode, onReglages, onArchi, lang, setLang }) {
  return (
    <nav className="sidebar" style={{ width: 232, flexShrink: 0, background: "var(--rose)", borderRight: "1px solid var(--rose-line)", display: "flex", flexDirection: "column", padding: "22px 16px", position: "sticky", top: 0, height: "100vh" }}>
      <div style={{ padding: "0 2px 14px" }}>
        <div className="eyebrow lbl-full" style={{ color: "var(--accent)", marginBottom: 10 }}>Cahier de langues</div>
        <LangShell lang={lang} setLang={setLang} />
      </div>
      <div style={{ display: "grid", gap: 3 }}>
        {NAV.map(n => {
          const on = route === n.k;
          return (
            <button key={n.k} onClick={() => go(n.k)} className="navbtn" style={{
              display: "flex", alignItems: "center", gap: 12, padding: "10px 12px", borderRadius: 11, border: "none", cursor: "pointer", textAlign: "left", width: "100%",
              background: on ? "var(--card)" : "transparent", color: on ? "var(--accent-deep)" : "var(--ink-soft)",
              boxShadow: on ? "var(--shadow-card)" : "none", fontFamily: "var(--serif)", fontSize: 16.5, transition: "background .15s, color .15s",
            }}
            onMouseEnter={e => { if (!on) e.currentTarget.style.background = "rgba(255,255,255,.45)"; }}
            onMouseLeave={e => { if (!on) e.currentTarget.style.background = "transparent"; }}>
              <span style={{ color: on ? "var(--accent)" : "var(--ink-faint)" }}><Icon name={n.icon} size={20} /></span>
              <span className="lbl-full">{n.label}</span>
            </button>
          );
        })}
      </div>
      <div style={{ marginTop: "auto", display: "grid", gap: 3, paddingTop: 14 }}>
        <button onClick={onArchi} className="side-min"><Icon name="layers" size={19} /> <span className="lbl-full">Architecture</span></button>
        <button onClick={onMethode} className="side-min"><Icon name="book" size={19} /> <span className="lbl-full">Méthode</span></button>
        <button onClick={onReglages} className="side-min"><Icon name="settings" size={19} /> <span className="lbl-full">Réglages</span></button>
      </div>
    </nav>
  );
}

function BottomNav({ route, go, onMethode, onReglages, onArchi }) {
  const [moreOpen, setMoreOpen] = useState(false);
  const mobileNav = NAV.filter(n => MOBILE_NAV_KEYS.includes(n.k));
  const overflowNav = NAV.filter(n => !MOBILE_NAV_KEYS.includes(n.k));
  const openAction = (fn) => {
    setMoreOpen(false);
    if (fn) fn();
  };
  return (
    <>
      {moreOpen && <div className="bottomnav-more-backdrop" onClick={() => setMoreOpen(false)} />}
      {moreOpen && (
        <div className="bottomnav-more fade-up">
          {overflowNav.map(n => (
            <button key={n.k} onClick={() => { setMoreOpen(false); go(n.k); }}><Icon name={n.icon} size={18} /> {n.label}</button>
          ))}
          <button onClick={() => openAction(onReglages)}><Icon name="settings" size={18} /> Réglages</button>
          <button onClick={() => openAction(onMethode)}><Icon name="book" size={18} /> Méthode</button>
          <button onClick={() => openAction(onArchi)}><Icon name="layers" size={18} /> Architecture</button>
        </div>
      )}
      <nav className="bottomnav">
        {mobileNav.map(n => {
          const on = route === n.k;
          return (
            <button key={n.k} onClick={() => { setMoreOpen(false); go(n.k); }} style={{ flex: 1, border: "none", background: "transparent", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", gap: 3, padding: "8px 2px", color: on ? "var(--accent)" : "var(--ink-faint)" }}>
              <Icon name={n.icon} size={21} />
              <span style={{ fontSize: 10, fontWeight: 600 }}>{n.label}</span>
            </button>
          );
        })}
        <button onClick={() => setMoreOpen(o => !o)} aria-expanded={moreOpen} aria-label="Plus" style={{ flex: 1, border: "none", background: "transparent", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", gap: 3, padding: "8px 2px", color: moreOpen ? "var(--accent)" : "var(--ink-faint)" }}>
          <Icon name="settings" size={21} />
          <span style={{ fontSize: 10, fontWeight: 600 }}>Plus</span>
        </button>
      </nav>
    </>
  );
}

/* ---------- Module landing (langue non-coréenne) ---------- */
function ModuleLanding({ lang, setLang }) {
  const m = window.LANGUES_DATA.modules.find(x => x.id === lang);
  return (
    <div style={{ maxWidth: 720, margin: "40px auto 0" }} className="fade-up">
      <Eyebrow>Module {m.state}</Eyebrow>
      <h1 style={{ fontSize: 44, margin: "6px 0 0" }}>{m.label} <span className="kr faint" style={{ fontSize: 30 }}>{m.native}</span></h1>
      <p className="muted" style={{ fontSize: 16, margin: "10px 0 22px", maxWidth: 520 }}>
        {m.state === "active"
          ? `Module actif — ${m.items.toLocaleString("fr-FR")} mots. Il reprend la même structure : Aujourd'hui, Apprendre, Cartes, Dialogues, Produire, Pilotage.`
          : "Ce module est prévu. Les données ne sont pas encore chargées."}
      </p>
      {m.state === "active" ? (
        <div className="card" style={{ padding: "var(--pad-card)", display: "flex", gap: 18, alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 200 }}>
            <div className="faint" style={{ fontSize: 12, marginBottom: 6 }}>Progression · {Math.round(m.progress * 100)}%</div>
            <Progress value={m.progress} />
          </div>
          <Btn kind="primary" icon="play" disabled title="Module non branché : priorité au moteur coréen">Ouvrir le module</Btn>
          <Btn kind="ghost" onClick={() => setLang("ko")}>Revenir au coréen</Btn>
        </div>
      ) : (
        <EmptyState icon="lock" title="Module pas encore disponible" body="Il apparaîtra ici dès que ses données seront prêtes." action={<Btn kind="secondary" size="sm" onClick={() => setLang("ko")}>Revenir au coréen</Btn>} />
      )}
    </div>
  );
}

/* ---------- Slide-over generic ---------- */
function SlideOver({ open, onClose, title, eyebrow, width = 460, children, footer }) {
  if (!open) return null;
  return (
    <>
      <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(30,20,12,.34)", zIndex: 50 }} />
      <aside role="dialog" aria-modal="true" aria-label={title} style={{ position: "fixed", top: 0, right: 0, height: "100vh", width: `min(${width}px, 94vw)`, background: "var(--paper)", borderLeft: "1px solid var(--rule)", boxShadow: "var(--shadow-pop)", zIndex: 51, display: "flex", flexDirection: "column" }}>
        <div style={{ padding: "20px 24px", borderBottom: "1px solid var(--rule)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <div>{eyebrow && <Eyebrow>{eyebrow}</Eyebrow>}<div style={{ fontFamily: "var(--serif)", fontSize: 25, color: "var(--accent-deep)" }}>{title}</div></div>
          <button onClick={onClose} className="btn btn-ghost btn-sm">Fermer ✕</button>
        </div>
        <div className="no-sb" style={{ overflowY: "auto", padding: 24, display: "grid", gap: 14, flex: 1 }}>{children}</div>
        {footer && <div style={{ padding: 18, borderTop: "1px solid var(--rule)" }}>{footer}</div>}
      </aside>
    </>
  );
}

/* ---------- Méthode ---------- */
function MethodePanel({ open, onClose }) {
  const sections = [
    { t: "Le geste de base", b: "Regarder · accrocher · cacher · répondre · corriger · revoir. Chaque carte passe par ce cycle court — c'est le cœur du rappel actif." },
    { t: "Rappel actif avant QCM", b: "On cherche d'abord à se souvenir, sans aide. Le QCM ne vient qu'après l'effort — sinon on reconnaît au lieu de produire." },
    { t: "Logique Leitner / SRS", b: "Une bonne réponse fait monter la carte d'une boîte ; un raté la renvoie en boîte 1. Plus la boîte est haute, plus l'intervalle s'allonge." },
    { t: "Les quatre brins", b: "Étude explicite · input compréhensible · production · fluidité. Une séance équilibrée touche les quatre." },
    { t: "Le jour type", b: "Réviser les dus, reprendre les ratés, découvrir un peu de neuf, écouter un dialogue, produire une phrase." },
    { t: "Nommer ses erreurs", b: "Forme · son · batchim · sens · contexte · aide. Mettre un mot sur l'erreur, c'est commencer à la réparer." },
  ];
  return (
    <SlideOver open={open} onClose={onClose} eyebrow="Référence" title="Méthode"
      footer={<Btn kind="primary" onClick={onClose} icon="today">Revenir à Aujourd'hui</Btn>}>
      {sections.map((s, i) => <Collapsible key={i} title={s.t} defaultOpen={i === 0}><p className="muted" style={{ fontSize: 14.5, margin: 0, lineHeight: 1.6 }}>{s.b}</p></Collapsible>)}
      <div className="card" style={{ padding: 18 }}>
        <Eyebrow style={{ marginBottom: 10 }}>Ressources</Eyebrow>
        <div style={{ display: "grid", gap: 8 }}>
          {["Méthode (PDF)", "Hangeul (PDF)", "Structure (PDF)"].map(r => (
            <button key={r} disabled title="PDF à brancher quand le fichier canonique sera présent" style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", borderRadius: 10, background: "var(--card-2)", border: "1px solid var(--rule)", color: "var(--ink)", fontSize: 14, fontWeight: 600, textAlign: "left" }}>
              <Icon name="book" size={17} style={{ color: "var(--ochre)" }} /> {r}
            </button>
          ))}
        </div>
      </div>
    </SlideOver>
  );
}

/* ---------- Architecture + mapping ---------- */
function ArchiPanel({ open, onClose, go }) {
  const map = window.LANGUES_DATA.mapping;
  return (
    <SlideOver open={open} onClose={onClose} eyebrow="Plan de l'app" title="Architecture" width={560}>
      <div className="card" style={{ padding: 18 }}>
        <Eyebrow style={{ marginBottom: 8 }}>6 zones principales</Eyebrow>
        <p className="muted" style={{ fontSize: 14, margin: "0 0 10px" }}>Aujourd'hui · Apprendre · Cartes · Dialogues · Produire · Pilotage. Méthode, Réglages et Architecture restent en panneaux latéraux.</p>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {NAV.map(n => <span key={n.k} className="chip"><Icon name={n.icon} size={13} /> {n.label}</span>)}
        </div>
      </div>

      <Collapsible eyebrow="Continuité" title="Mapping ancien onglet → nouvelle position" defaultOpen>
        <div style={{ display: "grid", gap: 7 }}>
          {map.map((m, i) => (
            <div key={i} style={{ display: "grid", gridTemplateColumns: "1fr auto 1.2fr", gap: 8, alignItems: "center", padding: "9px 12px", borderRadius: "var(--radius-sm)", background: "var(--card-2)", border: "1px solid var(--rule)" }}>
              <span style={{ fontSize: 13, fontWeight: 600 }}>{m[0]}</span>
              <Icon name="chevronR" size={15} style={{ color: "var(--ink-faint)" }} />
              <span><span style={{ fontSize: 13, fontWeight: 600, color: "var(--accent-deep)" }}>{m[1]}</span><span className="faint" style={{ display: "block", fontSize: 11.5 }}>{m[2]}</span></span>
            </div>
          ))}
        </div>
      </Collapsible>

      <Collapsible eyebrow="Robustesse" title="États gérés partout" hint="vide · chargement · erreur">
        <p className="muted" style={{ fontSize: 14, margin: 0 }}>Chaque liste prévoit un état <strong>vide</strong> (invitation à agir), un état <strong>chargement</strong> (squelette) et un état <strong>erreur</strong> (recharger). Aucune fonction de l'outil actuel n'a été retirée — seulement déplacée et repliée.</p>
      </Collapsible>

      <Btn kind="secondary" onClick={() => { onClose(); go("today"); }} icon="today">Aller à Aujourd'hui</Btn>
    </SlideOver>
  );
}

/* ---------- Réglages (contrôles réels) ---------- */
function SetRow({ label, hint, children }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 0", borderBottom: "1px solid var(--rule)" }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 600 }}>{label}</div>
        {hint && <div className="faint" style={{ fontSize: 12 }}>{hint}</div>}
      </div>
      <div style={{ flexShrink: 0 }}>{children}</div>
    </div>
  );
}
function Toggle({ on, onChange, label = "option" }) {
  return (
    <button onClick={() => onChange(!on)} aria-label={label} aria-pressed={on} style={{ width: 44, height: 26, borderRadius: 999, border: "none", cursor: "pointer", background: on ? "var(--accent)" : "var(--rule)", position: "relative", transition: "background .2s" }}>
      <span style={{ position: "absolute", top: 3, left: on ? 21 : 3, width: 20, height: 20, borderRadius: 999, background: "#fff", transition: "left .2s", boxShadow: "0 1px 3px rgba(0,0,0,.2)" }} />
    </button>
  );
}
function Stepper({ value, onChange, min = 1, max = 99, step = 1, unit, label = "valeur" }) {
  return (
    <div style={{ display: "inline-flex", alignItems: "center", gap: 0, border: "1px solid var(--rule)", borderRadius: 9, overflow: "hidden", background: "var(--card)" }}>
      <button aria-label={`Diminuer ${label}`} onClick={() => onChange(Math.max(min, value - step))} style={{ border: "none", background: "transparent", cursor: "pointer", padding: "6px 11px", fontSize: 16, color: "var(--ink-soft)" }}>−</button>
      <span aria-live="polite" style={{ minWidth: 44, textAlign: "center", fontSize: 13.5, fontWeight: 600, fontFamily: "var(--mono)" }}>{value}{unit || ""}</span>
      <button aria-label={`Augmenter ${label}`} onClick={() => onChange(Math.min(max, value + step))} style={{ border: "none", background: "transparent", cursor: "pointer", padding: "6px 11px", fontSize: 16, color: "var(--ink-soft)" }}>+</button>
    </div>
  );
}
function MiniSelect({ value, options, onChange }) {
  return (
    <select value={value} onChange={e => onChange(e.target.value)} style={{ padding: "7px 10px", borderRadius: 9, border: "1px solid var(--rule)", background: "var(--card)", color: "var(--ink)", fontFamily: "var(--sans)", fontSize: 13.5, fontWeight: 600, cursor: "pointer" }}>
      {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
    </select>
  );
}

const ACCENT_OPTIONS = [
  { id: "corail", label: "Corail", c: "#b8554a" },
  { id: "indigo", label: "Indigo", c: "#4a5d99" },
  { id: "vert", label: "Vert", c: "#4f6f5a" },
  { id: "ambre", label: "Ambre", c: "#a87422" },
  { id: "rose", label: "Rose", c: "#b75c7a" },
];

function ReglagesPanel({ open, onClose, dark, setDark, accent, setAccent }) {
  const fallbackDefaults = () => (window.LANGUES_DATA && window.LANGUES_DATA.settingsDefaults) || {};
  const drillOptions = () => ((window.LANGUES_DATA && window.LANGUES_DATA.drillModes) || [
    { id: "mixte", label: "Mixte" },
    { id: "rappel", label: "Rappel" },
    { id: "reconnaissance", label: "Reconnaissance" },
    { id: "dictee", label: "Dictée" },
    { id: "fluidite", label: "Fluidité" },
  ]).map((m) => ({ value: m.id, label: m.label }));
  const mergeSettings = (base) => {
    const defaults = base || fallbackDefaults();
    const stored = window.LangStore ? window.LangStore.settings.get() : {};
    const registry = window.LangStore && window.LangStore.profiles ? window.LangStore.profiles.all() : [];
    const profiles = Array.from(new Set([...(defaults.profiles || []), ...(registry || []), ...(stored.profiles || [])].filter(Boolean)));
    return { ...defaults, ...stored, profiles };
  };
  const [settingsDefaults, setSettingsDefaults] = useState(fallbackDefaults);
  const [s, setS] = useState(() => mergeSettings(fallbackDefaults()));
  const loadSettings = (base = settingsDefaults) => mergeSettings(base);
  const set = (k, v) => setS(p => {
    const next = { ...p, [k]: v };
    if (window.LangStore) window.LangStore.settings.set({ [k]: v });
    return next;
  });
  const changeProfile = (v) => {
    if (!window.LangStore) { set("profile", v); return; }
    window.LangStore.setProfile(v);
    const next = loadSettings();
    setS(next);
    if (typeof next.dark === "boolean") setDark(next.dark);
    if (next.accent) setAccent(next.accent);
  };
  const createProfile = () => {
    const name = window.prompt("Nom du profil");
    if (!name || !name.trim()) return;
    const profile = name.trim();
    const profiles = window.LangStore && window.LangStore.profiles
      ? window.LangStore.profiles.add(profile)
      : Array.from(new Set([...(s.profiles || []), profile]));
    if (window.LangStore) {
      window.LangStore.setProfile(profile);
      window.LangStore.settings.set({ profile, profiles });
    }
    setS({ ...loadSettings(), profile, profiles });
  };
  const setTheme = (v) => {
    setDark(v);
    if (window.LangStore) window.LangStore.settings.set({ dark: v });
  };
  const setAccentTheme = (v) => {
    setAccent(v);
    set("accent", v);
  };
  const exportSettings = () => {
    const json = window.LangStore ? window.LangStore.exportAll() : JSON.stringify({ settings: s }, null, 2);
    const blob = new Blob([json], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `langues2-${s.profile || "profil"}-${new Date().toISOString().slice(0, 10)}.json`;
    document.body.appendChild(a);
    a.click();
    a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  const importSettings = () => {
    const raw = window.prompt("Collez une sauvegarde JSON Langues 2.0");
    if (!raw) return;
    try {
      if (window.LangStore) window.LangStore.importAll(raw);
      const imported = JSON.parse(raw);
      if (window.LangStore && imported.profile) window.LangStore.setProfile(imported.profile);
      const next = loadSettings();
      setS(next);
      if (typeof next.dark === "boolean") setDark(next.dark);
    } catch (e) {
      window.alert("Sauvegarde invalide.");
    }
  };
  useEffect(() => {
    if (!open) return;
    let alive = true;
    const applyDefaults = (base) => {
      if (!alive) return;
      const defaults = base || fallbackDefaults();
      if (window.LANGUES_DATA) window.LANGUES_DATA.settingsDefaults = defaults;
      setSettingsDefaults(defaults);
      const next = loadSettings(defaults);
      setS(next);
      if (typeof next.dark === "boolean") setDark(next.dark);
      if (next.accent) setAccent(next.accent);
    };
    const loader = window.LangData && window.LangData.loadSettingsDefaults
      ? window.LangData.loadSettingsDefaults()
      : Promise.resolve(fallbackDefaults());
    loader.then(applyDefaults).catch(() => applyDefaults(fallbackDefaults()));
    return () => { alive = false; };
  }, [open]);
  return (
    <SlideOver open={open} onClose={onClose} eyebrow="Préférences" title="Réglages" width={440}
      footer={<div style={{ display: "flex", gap: 8 }}><Btn kind="secondary" size="sm" icon="upload" onClick={importSettings}>Importer</Btn><Btn kind="secondary" size="sm" icon="download" onClick={exportSettings}>Exporter</Btn><span style={{ flex: 1 }} /><Btn kind="danger" size="sm" icon="trash" disabled title="Désactivé : suppression locale à demander explicitement">Reset</Btn></div>}>
      <div>
        <Eyebrow style={{ marginBottom: 4 }}>Profil</Eyebrow>
        <SetRow label="Profil actif"><MiniSelect value={s.profile} options={(s.profiles || []).map(p => ({ value: p, label: p }))} onChange={changeProfile} /></SetRow>
        <SetRow label="Créer / sélectionner un profil"><Btn kind="ghost" size="sm" icon="plus" onClick={createProfile}>Nouveau</Btn></SetRow>
        <SetRow label="Thème sombre" hint="Garde l'ambiance papier, en encre claire"><Toggle label="Thème sombre" on={dark} onChange={setTheme} /></SetRow>
        <SetRow label="Accent" hint="Couleur des boutons, jauges et éléments actifs"><AccentSwatches value={accent || s.accent || "corail"} onChange={setAccentTheme} /></SetRow>
        <SetRow label="Recharger les données"><Btn kind="ghost" size="sm" icon="refresh" onClick={() => window.location.reload()}>Recharger</Btn></SetRow>
      </div>
      <div>
        <Eyebrow style={{ marginBottom: 4 }}>Session</Eyebrow>
        <SetRow label="Durée du parcours" hint="minutes par défaut"><Stepper label="la durée du parcours" value={s.parcoursDuration} onChange={v => set("parcoursDuration", v)} min={5} max={180} step={5} unit="m" /></SetRow>
        <SetRow label="Drill du jour" hint="mode par défaut dans Aujourd'hui"><MiniSelect value={s.todayDrillMode || "mixte"} options={drillOptions()} onChange={v => set("todayDrillMode", v)} /></SetRow>
        <SetRow label="Taille de session" hint="plafond d'items par séance"><Stepper label="la taille de session" value={s.sessionSize} onChange={v => set("sessionSize", v)} min={5} max={100} step={5} /></SetRow>
        <SetRow label="Nouveaux par jour" hint="cap de nouveaux items ; réduit/suspendu si la dette ou les erreurs montent"><Stepper label="les nouveaux par jour" value={s.newPerDay} onChange={v => set("newPerDay", v)} min={0} max={40} step={2} /></SetRow>
        <SetRow label="Cible de rétention SRS"><MiniSelect value={String(s.retention)} options={[{ value: "0.8", label: "80 %" }, { value: "0.9", label: "90 %" }, { value: "0.95", label: "95 %" }]} onChange={v => set("retention", parseFloat(v))} /></SetRow>
        <SetRow label="Type d'item"><MiniSelect value={s.itemType} options={[{ value: "mixte", label: "Mixte" }, { value: "nom", label: "Noms" }, { value: "verbe", label: "Verbes" }]} onChange={v => set("itemType", v)} /></SetRow>
      </div>
      <div>
        <Eyebrow style={{ marginBottom: 4 }}>Questions</Eyebrow>
        <SetRow label="Mode de question"><MiniSelect value={s.questionMode} options={[{ value: "rappel", label: "Rappel actif" }, { value: "qcm", label: "QCM" }, { value: "dictee", label: "Dictée" }]} onChange={v => set("questionMode", v)} /></SetRow>
        <SetRow label="Nombre de choix QCM"><Stepper label="le nombre de choix QCM" value={s.qcmChoices} onChange={v => set("qcmChoices", v)} min={2} max={6} /></SetRow>
      </div>
      <div>
        <Eyebrow style={{ marginBottom: 4 }}>Bibliothèque</Eyebrow>
        <SetRow label="Taille de liste"><Stepper label="la taille de liste" value={s.listSize} onChange={v => set("listSize", v)} min={10} max={200} step={10} /></SetRow>
        <SetRow label="Filtre de statut"><MiniSelect value={s.statusFilter} options={[{ value: "tous", label: "Tous" }, { value: "vus", label: "Vus" }, { value: "nonvus", label: "Non vus" }]} onChange={v => set("statusFilter", v)} /></SetRow>
        <SetRow label="Restaurer une sauvegarde"><Btn kind="ghost" size="sm" icon="upload" onClick={importSettings}>Restaurer</Btn></SetRow>
      </div>
      <p className="faint" style={{ fontSize: 12.5, margin: 0 }}>Les réglages restent séparés de l'apprentissage.</p>
    </SlideOver>
  );
}

/* ---------- Tweaks accent ---------- */
function AccentSwatches({ value, onChange }) {
  return (
    <div style={{ display: "flex", gap: 8, padding: "4px 0", flexWrap: "wrap", justifyContent: "flex-end" }}>
      {ACCENT_OPTIONS.map(o => (
        <button key={o.id} type="button" title={o.label} aria-label={o.label} onClick={() => onChange(o.id)} style={{ width: 32, height: 32, borderRadius: 999, cursor: "pointer", background: o.c, border: value === o.id ? "2px solid var(--ink)" : "2px solid transparent", boxShadow: value === o.id ? "0 0 0 2px " + o.c : "none" }} />
      ))}
    </div>
  );
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "todayLayout": "guide",
  "dialoguePlayer": "transcript",
  "transReveal": "hover",
  "cardStyle": "classique",
  "density": "normal"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRoute] = useState("today");
  const [routeParams, setRouteParams] = useState({});
  const [lang, setLang] = useState("ko");
  const [dialogueId, setDialogueId] = useState(null);
  const [dialogueMode, setDialogueMode] = useState("lecture");
  const [methode, setMethode] = useState(false);
  const [reglages, setReglages] = useState(false);
  const [archi, setArchi] = useState(false);
  const [dark, setDark] = useState(() => !!(window.LangStore && window.LangStore.settings.get().dark));
  const [accent, setAccentState] = useState(() => (window.LangStore && window.LangStore.settings.get().accent) || "corail");
  const mainRef = useRef(null);
  const setDarkPreference = useCallback((value) => {
    setDark(!!value);
    if (window.LangStore && window.LangStore.settings && window.LangStore.settings.set) window.LangStore.settings.set({ dark: !!value });
  }, []);
  const setAccentPreference = useCallback((value) => {
    const next = value || "corail";
    setAccentState(next);
    if (window.LangStore && window.LangStore.settings && window.LangStore.settings.set) window.LangStore.settings.set({ accent: next });
  }, []);

  const go = useCallback((k, opts = {}) => {
    // Tout changement de page coupe l'audio en cours : pas de voix qui continue
    // après qu'on a quitté une séance, un dialogue ou l'onglet Hangeul.
    if (window.LangAudio && window.LangAudio.stop) { try { window.LangAudio.stop(); } catch (e) {} }
    setRoute(k);
    setRouteParams(opts);
    if (k === "dialogues" && opts.dialogue) { setDialogueId(opts.dialogue); if (opts.mode) setDialogueMode(opts.mode); }
    else if (k === "dialogues") { setDialogueId(null); if (opts.mode) setDialogueMode(opts.mode); }
    else setDialogueId(null);
    if (mainRef.current) mainRef.current.scrollTop = 0;
  }, []);

  useEffect(() => {
    document.documentElement.setAttribute("data-density", t.density);
  }, [t.density]);
  useEffect(() => {
    document.documentElement.setAttribute("data-accent", accent || "corail");
  }, [accent]);
  useEffect(() => {
    if (dark) document.documentElement.setAttribute("data-theme", "dark");
    else document.documentElement.removeAttribute("data-theme");
  }, [dark]);
  useEffect(() => {
    let alive = true;
    if (!window.LangAudio) return undefined;
    const loader = window.LangData && window.LangData.loadAudioManifest
      ? window.LangData.loadAudioManifest()
      : fetch("/data/audio_manifest.json").then(r => r.ok ? r.json() : Promise.reject(new Error("manifest indisponible")));
    loader
      .then(m => { if (alive) window.LangAudio.loadManifest(m || {}); })
      .catch(() => { if (alive) window.LangAudio.loadManifest({}); });
    return () => { alive = false; };
  }, []);

  return (
    <div style={{ display: "flex", minHeight: "100vh", alignItems: "stretch" }}>
      <Sidebar route={route} go={go} lang={lang} setLang={setLang} onMethode={() => setMethode(true)} onReglages={() => setReglages(true)} onArchi={() => setArchi(true)} />

      <main ref={mainRef} style={{ flex: 1, minWidth: 0, overflowY: "auto", height: "100vh", padding: "40px clamp(18px, 3.5vw, 52px) 100px" }}>
        {lang !== "ko" ? <ModuleLanding lang={lang} setLang={setLang} /> : (
          <>
            {route === "today" && <TodayPage t={t} go={go} />}
            {route === "runner" && <RunnerPage go={go} routeParams={routeParams} />}
            {route === "apprendre" && <ApprendrePage go={go} routeParams={routeParams} />}
            {route === "cartes" && <CartesPage t={t} go={go} routeParams={routeParams} />}
            {route === "atelier" && <AtelierPage go={go} routeParams={routeParams} />}
            {route === "dialogues" && <DialoguesPage t={t} openId={dialogueId} setOpenId={setDialogueId} mode={routeParams.mode || dialogueMode} />}
            {route === "produire" && <ProduirePage go={go} routeParams={routeParams} />}
            {route === "pilotage" && <PilotagePage go={go} routeParams={routeParams} />}
          </>
        )}
      </main>

      <BottomNav route={route} go={go} onMethode={() => setMethode(true)} onReglages={() => setReglages(true)} onArchi={() => setArchi(true)} />
      <MethodePanel open={methode} onClose={() => setMethode(false)} />
      <ReglagesPanel open={reglages} onClose={() => setReglages(false)} dark={dark} setDark={setDarkPreference} accent={accent} setAccent={setAccentPreference} />
      <ArchiPanel open={archi} onClose={() => setArchi(false)} go={go} />

      <TweaksPanel>
        <TweakSection label="Structure" />
        <TweakRadio label="Aujourd'hui" value={t.todayLayout} options={[{ value: "guide", label: "Guidé" }, { value: "tableau", label: "Tableau" }, { value: "intention", label: "Intention" }]} onChange={v => setTweak("todayLayout", v)} />
        <TweakRadio label="Lecteur de dialogue" value={t.dialoguePlayer} options={[{ value: "transcript", label: "Transcript" }, { value: "ligne", label: "Focus ligne" }]} onChange={v => setTweak("dialoguePlayer", v)} />
        <TweakRadio label="Style de carte" value={t.cardStyle} options={[{ value: "classique", label: "Classique" }, { value: "minimal", label: "Minimal" }, { value: "boxes", label: "Boîtes" }]} onChange={v => setTweak("cardStyle", v)} />
        <TweakSection label="Confort" />
        <TweakRadio label="Densité" value={t.density} options={[{ value: "aere", label: "Aéré" }, { value: "normal", label: "Normal" }, { value: "compact", label: "Compact" }]} onChange={v => setTweak("density", v)} />
        <TweakRadio label="Traduction" value={t.transReveal} options={[{ value: "hover", label: "Survol" }, { value: "tap", label: "Au clic" }]} onChange={v => setTweak("transReveal", v)} />
        <TweakToggle label="Thème sombre" value={dark} onChange={setDarkPreference} />
        <TweakSection label="Couleur" />
        <TweakRow label="Accent"><AccentSwatches value={accent} onChange={setAccentPreference} /></TweakRow>
      </TweaksPanel>
    </div>
  );
}

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