const { useState, useEffect, useRef, useCallback } = React;

/* ============================== DATA ============================== */
const CARDS = [
  { kind: "cover" },

  {
    kind: "statement",
    eyebrow: "Le constat",
    title: <>TikTok n'est plus<br/>une simple appli<br/>de vidéos.</>,
    lead: <>C'est devenu une véritable <span className="hl">plateforme de vente</span> : un écosystème complet pour créer, diffuser et <b>vendre</b> sans jamais quitter l'application.</>,
    pill: ["+20 millions", "d'utilisateurs actifs en France"],
  },

  {
    kind: "benefit",
    eyebrow: "Avantage 01",
    title: "Un nouveau canal de vente",
    lead: <>L'achat se fait <span className="hl">en 2 clics</span>, sans jamais sortir de TikTok.</>,
    flow: ["Voir", "Aimer", "Acheter"],
    points: [
      <><b>Vos produits intégrés</b> directement aux vidéos et aux lives</>,
      <>Un parcours d'achat <b>fluide et sécurisé</b>, de bout en bout</>,
      <>La logique « <b>discovery commerce</b> » : on achète par envie, pas par recherche</>,
    ],
  },

  {
    kind: "benefit",
    eyebrow: "Avantage 02",
    title: "Une audience large et nouvelle",
    lead: <>Touchez des clients que vos canaux habituels <span className="hl">n'atteignent pas</span>.</>,
    points: [
      <><b>33%</b> des utilisateurs ont déjà acheté un produit vu sur l'app</>,
      <>La France est le <b>1ᵉʳ marché européen</b> de TikTok Shop</>,
      <>La <b>recommandation sociale</b> remplace la publicité classique</>,
    ],
  },

  {
    kind: "benefit",
    eyebrow: "Idée reçue",
    title: <>« Il faut être<br/>influenceur. » Faux.</>,
    lead: <>Pas besoin d'une grosse communauté pour <span className="hl">démarrer</span>.</>,
    points: [
      <>TikTok <b>n'exige pas</b> une grosse audience pour ouvrir sa boutique</>,
      <>Ce qui compte : une <b>entreprise déclarée</b> et de <b>bons produits</b></>,
      <>Vous partez d'un compte fonctionnel, et <b>on construit le reste ensemble</b></>,
    ],
  },

  {
    kind: "benefit",
    eyebrow: "Avantage 03",
    title: <>L'affiliation :<br/>votre force de vente externe</>,
    lead: <>Des créateurs testent, recommandent et <span className="hl">vendent vos produits</span> pour vous.</>,
    band: { k: <>Vous payez <em>uniquement à la vente.</em></>, d: "Aucun salaire, aucun paiement fixe, juste une commission sur les ventes réalisées." },
    points: [
      <><b>Multipliez les créateurs</b> sans limite ni risque financier</>,
      <>Jusqu'à <b>20 à 40%</b> du chiffre peut provenir de l'affiliation</>,
    ],
  },

  {
    kind: "benefit",
    eyebrow: "Avantage 04",
    title: "Le live shopping : vendre en direct",
    lead: <>Démonstration, interaction et vente, <span className="hl">en temps réel</span>.</>,
    points: [
      <>Les achats en live ont été <b>multipliés par 3,4</b> en 6 mois</>,
      <><b>16%</b> des ventes TikTok Shop France se font en live</>,
      <>Le format où la <b>France est n°1 en Europe</b> (~860 lives/jour)</>,
    ],
  },

  {
    kind: "grid",
    eyebrow: "Avantage 05",
    title: "Tout piloter depuis un seul outil",
    lead: <>Le <span className="hl">Seller Center</span>, c'est votre arrière-boutique et votre caisse.</>,
    cells: [
      { i: "01", t: "Produits & stock", d: "Catalogue centralisé" },
      { i: "02", t: "Commandes", d: "Encaissement & suivi" },
      { i: "03", t: "Affiliés", d: "Votre force de vente" },
      { i: "04", t: "Données de vente", d: "Reporting & pilotage" },
    ],
    close: "Une activité structurée, mesurable et scalable.",
  },

  {
    kind: "proof",
    eyebrow: "La preuve",
    title: "En France, ça décolle vite.",
    stats: [
      { n: "×7", l: "volume de ventes (avr. → sept. 2025)" },
      { n: "×14", l: "essor des vidéos achetables" },
      { n: "16 500", l: "entreprises référencées en 6 mois" },
      { n: "70%", l: "de ces entreprises sont des PME" },
    ],
    note: "Lancé en France le 31 mars 2025 : les PME investissent en premier.",
    src: "Sources : TikTok & études sectorielles 2025 · chiffres indicatifs, à confirmer.",
  },

  { kind: "booking" },
];

const INSTA = "https://www.instagram.com/capartenlive.academy/";

/* RDV config */
const WD = ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"];
const WD_LONG = ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"];
const MO = ["janv.", "f\u00e9vr.", "mars", "avr.", "mai", "juin", "juil.", "ao\u00fbt", "sept.", "oct.", "nov.", "d\u00e9c."];
const MO_LONG = ["janvier", "f\u00e9vrier", "mars", "avril", "mai", "juin", "juillet", "ao\u00fbt", "septembre", "octobre", "novembre", "d\u00e9cembre"];
const SLOTS_AM = ["09:00", "09:45", "10:30", "11:15"];
const SLOTS_PM = ["14:00", "14:45", "15:30", "16:15", "17:00"];

function buildDays() {
  const out = [];
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 1); // \u00e0 partir de demain
  let guard = 0;
  while (out.length < 20 && guard < 80) {
    const wd = d.getDay();
    if (wd >= 1 && wd <= 5) out.push(new Date(d));
    d.setDate(d.getDate() + 1);
    guard++;
  }
  return out;
}

/* ============================== QR ============================== */
function QR({ value }) {
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current || typeof qrcode === "undefined") return;
    try {
      const q = qrcode(0, "M");
      q.addData(value);
      q.make();
      ref.current.innerHTML = q.createSvgTag({ cellSize: 4, margin: 0, scalable: true });
    } catch (e) { /* noop */ }
  }, [value]);
  return <div className="qr"><div ref={ref} style={{ width: "100%", height: "100%" }} /></div>;
}

/* ============================== CARD RENDERERS ============================== */
function Cover() {
  return (
    <div className="inner cover-in">
      <div className="logo-tile anim"><img src="assets/logo.jpeg" alt="Ça Part en Live Academy" /></div>
      <div className="kick anim d1">Foire Expo de Nancy · 2026</div>
      <h1 className="display anim d2" style={{ marginTop: 16 }}>Vendez sur<br/><span className="pink">TikTok Shop</span></h1>
      <p className="sub anim d3">Le nouveau canal de vente directement intégré à TikTok, pensé pour les professionnels.</p>
      <div className="swipe-hint anim d4">
        <span className="dot">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18l6-6-6-6"/></svg>
        </span>
        Faites défiler pour découvrir
      </div>
    </div>
  );
}

function parseNum(str) {
  const m = String(str).match(/^(\D*)(\d[\d\s\u00A0\u202f.,]*\d|\d)([\s\S]*)$/);
  if (!m) return null;
  const prefix = m[1], suffix = m[3];
  const cleaned = m[2].replace(/[\s\u00A0\u202f]/g, "");
  let target, decimals = 0, sep = /[\s\u00A0\u202f]/.test(m[2]);
  if (/^\d+[.,]\d+$/.test(cleaned)) { decimals = cleaned.split(/[.,]/)[1].length; target = parseFloat(cleaned.replace(",", ".")); }
  else { target = parseInt(cleaned, 10); }
  return { prefix, suffix, target, decimals, sep };
}

function CountUp({ children, duration = 1400 }) {
  const str = String(children);
  const parsed = React.useMemo(() => parseNum(str), [str]);
  const ref = useRef(null);
  const [val, setVal] = useState(parsed ? parsed.target : null);
  useEffect(() => {
    if (!parsed) return;
    if (matchMedia("(prefers-reduced-motion: reduce)").matches) { setVal(parsed.target); return; }
    const el = ref.current; let raf = null, safety = null, animating = false;
    const run = () => {
      animating = true; setVal(0); const t0 = performance.now();
      const tick = (t) => {
        const p = Math.min(1, (t - t0) / duration);
        setVal(parsed.target * (1 - Math.pow(1 - p, 3)));
        if (p < 1) raf = requestAnimationFrame(tick); else { setVal(parsed.target); animating = false; }
      };
      raf = requestAnimationFrame(tick);
      safety = setTimeout(() => { setVal(parsed.target); animating = false; }, duration + 400);
    };
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting && !animating) run(); });
    }, { threshold: 0.2 });
    io.observe(el);
    return () => { io.disconnect(); if (raf) cancelAnimationFrame(raf); if (safety) clearTimeout(safety); };
  }, [parsed, duration]);
  if (!parsed) return <span ref={ref}>{str}</span>;
  const n = parsed.decimals ? val.toFixed(parsed.decimals) : String(Math.round(val));
  let [int, dec] = n.split(".");
  if (parsed.sep) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, "\u202f");
  return <span ref={ref}>{parsed.prefix}{dec ? int + "," + dec : int}{parsed.suffix}</span>;
}

function Statement({ c }) {
  return (
    <div className="inner">
      <div className="eyebrow anim">{c.eyebrow}</div>
      <h1 className="display anim d1">{c.title}</h1>
      <p className="lead anim d2">{c.lead}</p>
      {c.pill && (
        <div className="pill anim d3"><b><CountUp>{c.pill[0]}</CountUp></b><span>{c.pill[1]}</span></div>
      )}
    </div>
  );
}

function Benefit({ c }) {
  return (
    <div className="inner">
      <div className="eyebrow anim">{c.eyebrow}</div>
      <h2 className="title anim d1">{c.title}</h2>
      <p className="lead anim d2">{c.lead}</p>
      {c.flow && (
        <div className="flow anim d3">
          {c.flow.map((f, i) => (
            <React.Fragment key={i}>
              <span className="chip">{f}</span>
              {i < c.flow.length - 1 && <span className="arr">›</span>}
            </React.Fragment>
          ))}
        </div>
      )}
      {c.band && (
        <div className="band anim d3"><div className="k">{c.band.k}</div><div className="d">{c.band.d}</div></div>
      )}
      {c.points && (
        <ul className="points anim d4">
          {c.points.map((p, i) => <li key={i}>{p}</li>)}
        </ul>
      )}
    </div>
  );
}

function GridCard({ c }) {
  return (
    <div className="inner">
      <div className="eyebrow anim">{c.eyebrow}</div>
      <h2 className="title anim d1">{c.title}</h2>
      <p className="lead anim d2">{c.lead}</p>
      <div className="grid2 anim d3">
        {c.cells.map((g, i) => (
          <div className="gcell" key={i}>
            <div className="gi">{g.i}</div>
            <div className="gt">{g.t}</div>
            <div className="gd">{g.d}</div>
          </div>
        ))}
      </div>
      <p className="sub anim d4" style={{ marginTop: 18, color: "var(--paper)", fontWeight: 600 }}>{c.close}</p>
    </div>
  );
}

function Proof({ c }) {
  return (
    <div className="inner">
      <div className="eyebrow cyan anim">{c.eyebrow}</div>
      <h2 className="title anim d1">{c.title}</h2>
      <div className="stats anim d2">
        {c.stats.map((s, i) => (
          <div className="stat" key={i}><div className="n"><CountUp>{s.n}</CountUp></div><div className="l">{s.l}</div></div>
        ))}
      </div>
      <p className="sub anim d3" style={{ marginTop: 18, fontSize: 15 }}>{c.note}</p>
      <p className="src anim d4">{c.src}</p>
    </div>
  );
}

function Booking() {
  const days = useRef(buildDays()).current;
  const [step, setStep] = useState("slot");        // slot | form | done
  const [dayI, setDayI] = useState(0);
  const [slot, setSlot] = useState(null);
  const [form, setForm] = useState({ entreprise: "", tel: "", email: "", vendeur: "" });
  const [err, setErr] = useState("");
  const [count, setCount] = useState(0);
  const [saved, setSaved] = useState(null);

  useEffect(() => {
    try { setCount(JSON.parse(localStorage.getItem("cpl_rdv") || "[]").length); } catch (e) {}
  }, []);

  // RDV confirmé → on remet le parcours à zéro (index) et on revient à l'accueil
  // après un court délai (laisse voir la confirmation). Le timer est annulé si
  // l'utilisateur agit avant (ex: « Prendre un autre RDV ») ou quitte la page.
  useEffect(() => {
    if (step !== "done") return;
    try { localStorage.removeItem("cpl_idx"); } catch (e) {}
    const t = setTimeout(() => { window.location.href = "Accueil.html"; }, 7000);
    return () => clearTimeout(t);
  }, [step]);

  const day = days[dayI];
  const fmtLong = (d) => `${WD_LONG[d.getDay()]} ${d.getDate()} ${MO_LONG[d.getMonth()]}`;

  const pickSlot = (s) => { setSlot(s); setErr(""); };

  const goForm = () => {
    if (!slot) { setErr("Choisissez d'abord un créneau."); return; }
    setErr(""); setStep("form");
    const v = document.querySelector(".card.booking");
    if (v) v.scrollTo({ top: 0, behavior: "smooth" });
  };

  const confirm = (e) => {
    e.preventDefault();
    if (!form.entreprise.trim() || !form.tel.trim()) { setErr("Le nom de l'entreprise et le téléphone sont nécessaires."); return; }
    const rec = {
      entreprise: form.entreprise.trim(), tel: form.tel.trim(), email: form.email.trim(),
      vendeur: form.vendeur, day: day.toISOString(), slot, ts: new Date().toISOString(),
    };
    try {
      const all = JSON.parse(localStorage.getItem("cpl_rdv") || "[]");
      all.push(rec); localStorage.setItem("cpl_rdv", JSON.stringify(all)); setCount(all.length);
    } catch (e) {}
    setSaved(rec); setErr(""); setStep("done");
    try { window.cplSyncFoireRdv && window.cplSyncFoireRdv(rec, "academy"); } catch (e) {}
    const v = document.querySelector(".card.booking");
    if (v) v.scrollTo({ top: 0, behavior: "smooth" });
  };

  const reset = () => {
    setStep("slot"); setSlot(null); setSaved(null);
    setForm({ entreprise: "", tel: "", email: "", vendeur: "" }); setErr("");
    const v = document.querySelector(".card.booking");
    if (v) v.scrollTo({ top: 0, behavior: "smooth" });
  };

  const exportRdv = () => {
    try {
      const all = JSON.parse(localStorage.getItem("cpl_rdv") || "[]");
      const txt = all.map(r => {
        const d = new Date(r.day);
        return [fmtLong(d), r.slot, r.entreprise, r.tel, r.email, r.vendeur === "oui" ? "Déjà vendeur" : r.vendeur === "non" ? "Pas encore vendeur" : ""].join(" \t ");
      }).join("\n");
      if (navigator.clipboard) navigator.clipboard.writeText(txt);
      alert(all.length + " RDV copié(s) dans le presse-papier.\nCollez-les dans un mail ou un tableur.");
    } catch (e) {}
  };

  // group days by month for the month tag
  let lastMonth = -1;

  return (
    <div className="inner">
      {step === "slot" && (
        <React.Fragment>
          <div className="bk-kicker anim"><span className="num">10</span><span className="ln" />Réservez votre rendez-vous</div>
          <h2 className="title anim d1">Un RDV de 45 min, à votre rythme</h2>
          <p className="book-intro anim d1">On se déplace à votre bureau pour construire votre boutique TikTok Shop. Choisissez le moment qui vous arrange.</p>

          <div className="book-steps anim d2"><i className="on" /><i /><i /></div>

          <div className="anim d2">
            <div className="daystrip no-swipe">
              {days.map((d, i) => {
                const showMonth = d.getMonth() !== lastMonth;
                lastMonth = d.getMonth();
                return (
                  <button key={i} className={"daypill" + (i === dayI ? " sel" : "")}
                    onClick={() => { setDayI(i); setSlot(null); }}>
                    <span className="wd">{WD[d.getDay()]}</span>
                    <span className="dn">{d.getDate()}</span>
                    <span className="mo">{MO[d.getMonth()]}</span>
                  </button>
                );
              })}
            </div>
          </div>

          <div className="slot-sec anim d3">
            <h4>Matin · 9h à 12h</h4>
            <div className="slots">
              {SLOTS_AM.map(s => (
                <button key={s} className={"slot" + (slot === s ? " sel" : "")} onClick={() => pickSlot(s)}>{s}</button>
              ))}
            </div>
          </div>
          <div className="slot-sec anim d4">
            <h4 className="aft">Après-midi · 14h à 18h</h4>
            <div className="slots">
              {SLOTS_PM.map(s => (
                <button key={s} className={"slot" + (slot === s ? " sel" : "")} onClick={() => pickSlot(s)}>{s}</button>
              ))}
            </div>
          </div>

          {err && <div className="err">{err}</div>}

          <div className="anim d5" style={{ marginTop: 20 }}>
            <button className="submit" onClick={goForm}>
              {slot ? `Continuer · ${day.getDate()} ${MO[day.getMonth()]} à ${slot}` : "Continuer"}
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
            </button>
          </div>

          <div className="mini-contact anim d5">
            <div>
              <QR value={INSTA} />
              <div className="qr-cap">@capartenlive.academy</div>
            </div>
            <div className="cinfo">
              <span className="lbl">Une question ?</span>
              <a href="tel:+33374721295">03 74 72 12 95</a>
              <a href="mailto:contact@capartenliveacademy.fr">contact@capartenliveacademy.fr</a>
            </div>
          </div>

          {count > 0 && (
            <div className="rdvcount">{count} RDV enregistré(s) sur cet appareil <button onClick={exportRdv}>exporter</button></div>
          )}
        </React.Fragment>
      )}

      {step === "form" && (
        <React.Fragment>
          <div className="bk-kicker anim"><span className="num">10</span><span className="ln" />Vos coordonnées</div>
          <h2 className="title anim d1">Presque terminé !</h2>

          <div className="book-steps anim d1"><i className="on" /><i className="on" /><i /></div>

          <div className="selbar anim d1">
            <div>
              <div className="s1">Votre créneau</div>
              <div className="s2">{fmtLong(day)} · {slot}</div>
            </div>
            <button onClick={() => setStep("slot")}>modifier</button>
          </div>

          <form onSubmit={confirm}>
            <div className="field anim d2">
              <span className="field-lbl">Entreprise <span className="req">*</span></span>
              <input placeholder="Nom de votre entreprise" value={form.entreprise} onChange={e => setForm({ ...form, entreprise: e.target.value })} />
            </div>
            <div className="field anim d2">
              <span className="field-lbl">Téléphone <span className="req">*</span></span>
              <input placeholder="06 12 34 56 78" inputMode="tel" value={form.tel} onChange={e => setForm({ ...form, tel: e.target.value })} />
            </div>
            <div className="field anim d3">
              <span className="field-lbl">E-mail</span>
              <input placeholder="vous@entreprise.fr" inputMode="email" value={form.email} onChange={e => setForm({ ...form, email: e.target.value })} />
            </div>
            <div className="field anim d3">
              <span className="field-lbl">Vous vendez déjà en ligne ?</span>
              <div className="toggle2">
                <button type="button" className={form.vendeur === "oui" ? "on" : ""} onClick={() => setForm({ ...form, vendeur: "oui" })}>Oui</button>
                <button type="button" className={form.vendeur === "non" ? "on" : ""} onClick={() => setForm({ ...form, vendeur: "non" })}>Pas encore</button>
              </div>
            </div>

            {err && <div className="err">{err}</div>}

            <div className="anim d4" style={{ marginTop: 22 }}>
              <button className="submit" type="submit">
                Confirmer le rendez-vous
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
              </button>
            </div>
          </form>
        </React.Fragment>
      )}

      {step === "done" && saved && (
        <React.Fragment>
          <div className="recap anim">
            <div className="chk">
              <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#25F4EE" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
            </div>
            <div className="big">Rendez-vous noté !</div>
            <div className="when">{fmtLong(new Date(saved.day))}<br/>à {saved.slot}</div>
            <div className="det">
              <b>{saved.entreprise}</b><br/>
              {saved.tel}{saved.email ? " · " + saved.email : ""}<br/>
              On vous appelle pour confirmer. À très vite !
            </div>
            <div className="actions">
              <button className="ghostbtn" onClick={reset}>Prendre un autre RDV</button>
              <button className="ghostbtn" onClick={() => { try { localStorage.removeItem("cpl_idx"); } catch (e) {} window.location.href = "Accueil.html"; }}>Retour à l'accueil</button>
            </div>
          </div>

          <div className="mini-contact anim d2">
            <div>
              <QR value={INSTA} />
              <div className="qr-cap">@capartenlive.academy</div>
            </div>
            <div className="cinfo">
              <span className="lbl">Ça Part en Live Academy</span>
              <a href="tel:+33374721295">03 74 72 12 95</a>
              <a href={INSTA} target="_blank" rel="noopener">@capartenlive.academy</a>
            </div>
          </div>

          {count > 0 && (
            <div className="rdvcount">{count} RDV enregistré(s) sur cet appareil <button onClick={exportRdv}>exporter</button></div>
          )}
        </React.Fragment>
      )}
    </div>
  );
}

function CardBody({ c }) {
  switch (c.kind) {
    case "cover": return <Cover />;
    case "statement": return <Statement c={c} />;
    case "benefit": return <Benefit c={c} />;
    case "grid": return <GridCard c={c} />;
    case "proof": return <Proof c={c} />;
    case "booking": return <Booking />;
    default: return null;
  }
}

/* ============================== APP ============================== */
function App() {
  const N = CARDS.length;
  const [idx, setIdx] = useState(() => {
    const s = parseInt(localStorage.getItem("cpl_idx") || "0", 10);
    return isNaN(s) || s < 0 || s >= N ? 0 : s;
  });
  const [drag, setDrag] = useState(0);
  const start = useRef(null);
  const startY = useRef(null);
  const horiz = useRef(false);

  useEffect(() => {
    try { localStorage.setItem("cpl_idx", String(idx)); } catch (e) {}
    document.documentElement.setAttribute("data-ph", String(idx % 6));
    document.documentElement.setAttribute("data-card", CARDS[idx] ? CARDS[idx].kind : "");
  }, [idx]);

  const go = useCallback((n) => setIdx(p => Math.max(0, Math.min(N - 1, n))), [N]);
  const next = useCallback(() => go(idx + 1), [idx, go]);
  const prev = useCallback(() => go(idx - 1), [idx, go]);

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "ArrowRight" || e.key === "PageDown") { e.preventDefault(); next(); }
      else if (e.key === "ArrowLeft" || e.key === "PageUp") { e.preventDefault(); prev(); }
      else if (e.key === "Home") go(0);
      else if (e.key === "End") go(N - 1);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [next, prev, go, N]);

  // touch swipe (ignore when starting on an input/button/link)
  const onStart = (e) => {
    const tag = (e.target.tagName || "").toLowerCase();
    if (!["input", "button", "textarea", "a", "select"].includes(tag) && !e.target.closest("form, .submit, .no-swipe")) {
      const t = e.touches[0];
      start.current = t.clientX; startY.current = t.clientY; horiz.current = false;
      return;
    }
    start.current = null;
  };
  const onMove = (e) => {
    if (start.current == null) return;
    const t = e.touches[0];
    const dx = t.clientX - start.current;
    const dy = t.clientY - startY.current;
    if (!horiz.current) {
      if (Math.abs(dx) > 8 || Math.abs(dy) > 8) horiz.current = Math.abs(dx) > Math.abs(dy);
      if (!horiz.current) return;
    }
    e.preventDefault();
    let d = dx;
    if ((idx === 0 && dx > 0) || (idx === N - 1 && dx < 0)) d = dx * 0.32; // rubber band
    setDrag(d);
  };
  const onEnd = () => {
    if (start.current == null) return;
    const w = window.innerWidth;
    const th = Math.min(90, w * 0.18);
    if (drag < -th) next();
    else if (drag > th) prev();
    setDrag(0); start.current = null; horiz.current = false;
  };

  const pct = -(idx * 100);
  const trackStyle = {
    transform: `translateX(calc(${pct}% + ${drag}px))`,
    transition: drag ? "none" : "transform .5s var(--ease)",
  };

  return (
    <div className="stage">
      <a className="homebtn" href="Accueil.html" aria-label="Accueil" onClick={() => { try { localStorage.removeItem("cpl_idx"); } catch (e) {} }}>
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11l9-8 9 8M5 9v11h14V9"/></svg>
      </a>
      <div className="topbar">
        <div className="seg">
          {CARDS.map((_, i) => (
            <span key={i} className={i < idx ? "done" : i === idx ? "active" : ""}><i /></span>
          ))}
        </div>
        <div className="counter">{String(idx + 1).padStart(2, "0")} / {String(N).padStart(2, "0")}</div>
      </div>

      <div className="viewport" onTouchStart={onStart} onTouchMove={onMove} onTouchEnd={onEnd}>
        <div className="track" style={trackStyle}>
          {CARDS.map((c, i) => (
            <section
              className={"card " + c.kind + (i === idx ? " live" : "")}
              data-screen-label={"Carte " + String(i + 1).padStart(2, "0")}
              key={i}
            >
              <CardBody c={c} />
            </section>
          ))}
        </div>
        {idx > 0 && <div className="edge l" onClick={prev} />}
        {idx < N - 1 && CARDS[idx].kind !== "booking" && <div className="edge r" onClick={next} />}
      </div>

      <div className="navbar">
        <button className="nav-btn" onClick={prev} disabled={idx === 0} aria-label="Précédent">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
        </button>
        <div className="nav-label">{idx === 0 ? "Ça Part en Live Academy" : idx === N - 1 ? "Prenez RDV" : "TikTok Shop pour les pros"}</div>
        <button className="nav-btn navnext" onClick={next} disabled={idx === N - 1} aria-label="Suivant">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18l6-6-6-6"/></svg>
        </button>
      </div>
    </div>
  );
}

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