/* ═══════════════════════════════════════════════════════════════ FOXBOT PRO — MOBILE SHELL App container · Header · Bottom Nav · Bottom Sheet · Haptic ═══════════════════════════════════════════════════════════════ */ const { useState: useStateM, useEffect: useEffectM, useRef: useRefM, useCallback: useCallbackM } = React; /* ─── Haptic helper (silently noop on unsupported) ─── */ function haptic(pattern = 10) { try { if (navigator.vibrate) navigator.vibrate(pattern); } catch (e) { /* noop */ } } const HAPTIC = { tap: () => haptic(8), press: () => haptic(18), toggle: () => haptic(12), snap: () => haptic([8, 4, 8]), success: () => haptic([22, 14, 22]), warn: () => haptic([35, 22, 35]), err: () => haptic([50, 30, 50, 30, 50]), }; window.HAPTIC = HAPTIC; /* ─── Mobile detection hook ─── */ function useIsMobile(breakpoint = 768) { const [is, setIs] = useStateM(() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches ); useEffectM(() => { const mq = window.matchMedia(`(max-width: ${breakpoint}px)`); const onChange = (e) => setIs(e.matches); mq.addEventListener("change", onChange); return () => mq.removeEventListener("change", onChange); }, [breakpoint]); useEffectM(() => { document.documentElement.dataset.mobile = is ? "true" : "false"; }, [is]); return is; } window.useIsMobile = useIsMobile; /* ─── Brand mark (small fox glyph) ─── */ function MBrandMark() { return ( FoxBot Pro ); } /* ─── Header ─── */ const ROUTE_TITLES = { dashboard: "Dashboard", trading: "Trading", signaux: "Signaux", charts: "Charts", more: "Plus", journal: "Journal", stats: "Statistiques", settings: "Paramètres", volatile: "Volatiles", }; function MobileHeader({ route, mode, onToggleMode, onNotif }) { const [scrolled, setScrolled] = useStateM(false); useEffectM(() => { const onScroll = () => setScrolled(window.scrollY > 6); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); // Notif-dot uniquement si vraie raison de l'allumer : // - un signal éligible récent (≥ 7/10 actif) // - une position dont SL/TP est proche (< 0.5%) const store = window.useStore ? window.useStore() : (window.MOCK || {}); const hasNotif = (() => { const sigs = Array.isArray(store.SIGNALS) ? store.SIGNALS : []; if (sigs.some(s => s && s.state === "active" && (s.score || 0) >= 8)) return true; const pos = Array.isArray(store.POSITIONS) ? store.POSITIONS : []; return pos.some(p => { if (!p) return false; const px = (store.PRICES && store.PRICES[p.pair]) || p.current; const slDist = p.sl ? Math.abs((p.sl - px) / px * 100) : 99; const tp1Dist = p.tp1 ? Math.abs((p.tp1 - px) / px * 100) : 99; return slDist < 0.5 || tp1Dist < 0.5; }); })(); return (
FOXBOT PRO

{ROUTE_TITLES[route] || route}

); } /* ─── Bottom Nav ─── */ function MobileBottomNav({ route, onRoute, onMore }) { // Compteurs RÉELS depuis le store const store = window.useStore ? window.useStore() : (window.MOCK || {}); const posCount = Array.isArray(store.POSITIONS) ? store.POSITIONS.length : 0; const signalsCount = Array.isArray(store.SIGNALS) ? store.SIGNALS.filter(s => s && (s.state === "active" || (s.score >= 7 && s.direction))).length : 0; const items = [ { id: "dashboard", i: Icon.Home, l: "Home" }, { id: "trading", i: Icon.Activity, l: "Trading", badge: posCount || null }, { id: "signaux", i: Icon.Zap, l: "Signaux", badge: signalsCount || null }, { id: "charts", i: Icon.Chart, l: "Charts" }, { id: "more", i: MoreIcon, l: "Plus" }, ]; return ( ); } function MoreIcon({ size = 22 }) { return ( ); } /* ─── Bottom Sheet ─── */ function BottomSheet({ open, onClose, title, children, height }) { const [closing, setClosing] = useStateM(false); const [mounted, setMounted] = useStateM(open); useEffectM(() => { if (open) { setMounted(true); setClosing(false); } else if (mounted) { setClosing(true); const t = setTimeout(() => { setMounted(false); setClosing(false); }, 200); return () => clearTimeout(t); } }, [open]); useEffectM(() => { if (!mounted) return; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = ""; }; }, [mounted]); if (!mounted) return null; return (
{ HAPTIC.tap(); onClose(); }}>
{title &&

{title}

}
{children}
); } window.BottomSheet = BottomSheet; /* ─── More Sheet ─── */ function MoreSheet({ open, onClose, onRoute }) { const items = [ { id: "journal", i: Icon.Book, l: "Journal de trading" }, { id: "stats", i: Icon.Stats, l: "Statistiques" }, { id: "volatile", i: Icon.Flame, l: "Cryptos volatiles" }, { id: "settings", i: Icon.Settings, l: "Paramètres" }, { id: "alerts", i: Icon.Bell, l: "Notifications" }, { id: "help", i: Icon.AlertTriangle, l: "Aide & Support" }, ]; return (
{items.map(it => (
{ HAPTIC.tap(); onRoute(it.id); onClose(); }}> {it.l}
))}
); } window.MoreSheet = MoreSheet; /* ─── MobileApp — main shell ─── */ function MobileApp({ tweaks, setTweak, onAction }) { const [route, setRoute] = useStateM(() => { const h = (window.location.hash || "").replace("#/", ""); return ROUTE_TITLES[h] ? h : "dashboard"; }); const [moreOpen, setMoreOpen] = useStateM(false); useEffectM(() => { window.location.hash = "#/" + route; }, [route]); function handleRoute(id) { if (id === route) return; HAPTIC.tap(); setRoute(id); } return (
setTweak({ mode: tweaks.mode === "LIVE" ? "PAPER" : "LIVE" })} />
{route === "dashboard" && window.MobileDashboard && } {route === "trading" && window.MobileTrading && } {route === "trading" && !window.MobileTrading && } {route === "signaux" && window.MobileSignaux && } {route === "signaux" && !window.MobileSignaux && } {route === "charts" && window.MobileCharts && } {route === "charts" && !window.MobileCharts && } {(route === "journal" || route === "stats" || route === "settings" || route === "volatile") && ( window.MobileMore ? : )}
{ HAPTIC.tap(); setMoreOpen(true); }}/> setMoreOpen(false)} onRoute={handleRoute}/>
); } function MobileSoon({ name }) { return (
{name} arrive
Cet écran sera refait dans l'étape suivante. Tu peux valider le shell (header, nav, sheets) en attendant.
); } Object.assign(window, { MobileApp, MobileHeader, MobileBottomNav, MoreSheet, MBrandMark, HAPTIC });