// sections-sim.jsx — Cadre téléphone + simulation interactive de l'app Mobile. // // Architecture hybride : // 1. Tente de charger l'app réelle via iframe (mobile-demo/index.html?demo=1) // 2. En fallback : affiche une démo interactive statique avec les écrans // PhoneSearchScreen → PhoneSeatScreen → PhoneTicketScreen déjà définis // dans sections.jsx. Pas besoin de build séparé — fonctionne toujours. // // Le composant `MobileDemoFrame` est exposé sur `window` et utilisé : // - dans la section Features (taille "md" — intégré entre les cartes) // - éventuellement comme section dédiée via le wrapper `BookingSimulator` const { useState: simUseState, useEffect: simUseEffect, useRef: simUseRef } = React; // ---------- Démo interactive statique (fallback / mode par défaut) ---------- // Affiche les vrais écrans de l'app avec navigation entre les étapes. function StaticPhoneDemo({ lang = "fr" }) { const [screen, setScreen] = simUseState(0); // 0=search, 1=seats, 2=ticket, 3=list const screens = [ { component: PhoneSearchScreen, label: lang === "fr" ? "Recherche" : "Search" }, { component: PhoneSeatScreen, label: lang === "fr" ? "Siège" : "Seat" }, { component: PhoneTicketScreen, label: "Billet" }, { component: PhoneTicketsListScreen, label: lang === "fr" ? "Mes billets" : "My tickets" }, ]; const Current = screens[screen].component; // Auto-advance toutes les 5s si pas d'interaction const [autoPlay, setAutoPlay] = simUseState(true); simUseEffect(() => { if (!autoPlay) return; const id = setInterval(() => { setScreen(s => (s + 1) % screens.length); }, 5000); return () => clearInterval(id); }, [autoPlay, screens.length]); const goTo = (idx) => { setAutoPlay(false); setScreen(idx); }; const next = () => { setAutoPlay(false); setScreen(s => (s + 1) % screens.length); }; return (
{/* Écran actuel */}
{/* Navigation dots */}
{screens.map((s, i) => ( ))}
{/* Hint tap */}
{lang === "fr" ? "Tapez pour naviguer" : "Tap to navigate"}
); } // ---------- Composant principal : MobileDemoFrame ---------- function MobileDemoFrame({ size = "md", lang = "fr" }) { const [status, setStatus] = simUseState("idle"); // idle | checking | ok | missing | loaded const iframeRef = simUseRef(null); const wrapRef = simUseRef(null); // Lazy : on ne déclenche la vérification + chargement de l'iframe que lorsque // le cadre entre dans le viewport. Évite de télécharger ~13 MB de bundle Vite + // images au chargement initial de la page si l'utilisateur n'arrive jamais // jusqu'à la section. rootMargin élargi pour pré-charger un peu en avance. simUseEffect(() => { const node = wrapRef.current; if (!node) return; if (typeof IntersectionObserver === "undefined") { // Vieux navigateur : on charge tout de suite. setStatus("checking"); return; } const obs = new IntersectionObserver((entries) => { if (entries.some(e => e.isIntersecting)) { setStatus("checking"); obs.disconnect(); } }, { rootMargin: "400px" }); obs.observe(node); return () => obs.disconnect(); }, []); // Vérification présence + identité du build mobile-demo, déclenchée uniquement // une fois passé en "checking" (donc après entrée dans le viewport). simUseEffect(() => { if (status !== "checking") return; let cancelled = false; // Cache standard : le HTML du mobile-demo change rarement et le navigateur // saura le revalider via ETag si besoin. "no-store" forçait un download // complet à chaque visite, avant même l'affichage de la simulation. fetch("mobile-demo/index.html") .then(async (res) => { if (cancelled) return; if (!res.ok) { setStatus("missing"); return; } const text = await res.text(); // Signature attendue : assets/index-XXXXXX.js (build Vite) const isMobileBuild = /assets\/index-[A-Za-z0-9_-]+\.js/.test(text); if (!isMobileBuild) { setStatus("missing"); return; } setStatus("ok"); }) .catch(() => { if (!cancelled) setStatus("missing"); }); return () => { cancelled = true; }; }, [status]); const reload = () => { if (status === "loaded" && iframeRef.current) { iframeRef.current.src = `mobile-demo/index.html?demo=1&t=${Date.now()}`; setStatus("ok"); } else { // Reset la démo statique setStatus("missing"); } }; return (
{(status === "idle" || status === "checking") && (

{lang === "fr" ? "Préparation…" : "Preparing…"}

)} {/* Fallback : démo interactive statique avec les vrais écrans */} {status === "missing" && (
)} {(status === "ok" || status === "loaded") && (