const { useState, useEffect, useRef, useMemo } = React; /* ---------- useInView (replaces framer-motion's whileInView) ---------- */ const useInView = (threshold = 0.2) => { const ref = useRef(null); const [inView, setInView] = useState(false); useEffect(() => { const node = ref.current; if (!node) return; const obs = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setInView(true); obs.disconnect(); } }, { threshold, rootMargin: '0px 0px -80px 0px' } ); obs.observe(node); return () => obs.disconnect(); }, [threshold]); return [ref, inView]; }; /* ---------- SectionWithMockup: text + a JSX mockup (e.g. phone) ---------- */ const SectionWithMockup = ({ title, description, mockup, reverseLayout = false }) => { const [ref, inView] = useInView(0.15); const baseT = { transition: 'opacity .7s ease, transform .7s ease', opacity: inView ? 1 : 0, transform: inView ? 'translateY(0)' : 'translateY(50px)', }; const textT = { ...baseT, transitionDelay: '0s' }; const mockT = { ...baseT, transitionDelay: '.2s' }; return (
{/* Text */}

{title}

{description}

{/* Mockup slot — JSX renders the phone */}
{/* ambient glow behind phone */}
{mockup}
{/* Bottom hairline gradient */}
); }; /* ---------- PhoneMockup: realistic FoxBot Pro mobile dashboard in JSX ---------- */ const PhoneMockup = ({ variant = 'dashboard', tilt = 0 }) => { return (
{/* Outer bezel */}
{/* Screen */}
{/* Status bar */}
11:34 {/* notification crossed bell */} {/* battery */}
22
{/* App header */}

{variant === 'dashboard' ? 'Dashboard' : 'Signaux'}

{/* PAPER pill (orange like real app) */} PAPER {/* bell button */}
{variant === 'dashboard' && } {variant === 'signaux' && } {/* Bottom nav — 5 tabs, active gets purple top indicator */}
{[ { l: 'HOME', active: variant === 'dashboard' }, { l: 'TRADING', active: false }, { l: 'SIGNAUX', active: variant === 'signaux' }, { l: 'CHARTS', active: false }, { l: 'PLUS', active: false }, ].map((t, i) => (
{t.active &&
} {t.l}
))}
{/* home indicator */}
); }; const NavIcon = ({ name, active }) => { const c = active ? 'var(--accent-2)' : 'var(--ink-3)'; if (name === 'HOME') return ( ); if (name === 'TRADING') return ( ); if (name === 'SIGNAUX') return ( ); if (name === 'CHARTS') return ( ); if (name === 'PLUS') return ( ); return null; }; /* Mobile dashboard screen — matches the real FoxBot Pro mobile UI */ const PhoneDashboardScreen = () => { const [pnl, setPnl] = useState(1842.40); useEffect(() => { const id = setInterval(() => setPnl(p => +(p + (Math.random() - 0.4) * 1.2).toFixed(2)), 1600); return () => clearInterval(id); }, []); // sparkline behind P&L const spark = useMemo(() => { const w = 280, h = 80; const pts = []; let y = h * 0.7; for (let i = 0; i <= 40; i++) { const x = (i / 40) * w; y -= 0.4 + Math.sin(i / 3) * 0.7 + Math.random() * 0.3; pts.push(`${x.toFixed(1)},${Math.max(8, y).toFixed(1)}`); } return 'M' + pts.join(' L'); }, []); return (
{/* Hero P&L card */}
P&L TOTAL · 30J LIVE
+€{pnl.toLocaleString('fr-FR', { minimumFractionDigits: 2 })}
{/* chips row */}
↑ +18.42% ⚡ 3 positions Capital €10K
{/* sparkline subtle in background */}
{/* KPI 2x2 */}
{[ { l: 'WIN RATE', v: '67.4', u: '%', c: 'var(--ink)', sub: '60W · 29L', bar: { v: 67.4, c: 'var(--green)' } }, { l: 'TRADES JOUR',v: '4', u: '/ jour', c: 'var(--ink)', sub: '7J 23 · 30J 89', spark: true }, { l: 'DRAWDOWN', v: '-1.24',u: '%', c: 'var(--red)', sub: 'max -4.82%', bar: { v: 25, c: 'var(--red)' } }, { l: 'POSITIONS', v: '3', u: 'actives', c: 'var(--ink)', sub: <>Exposition €2 420 }, ].map((k, i) => (
{k.l}
{k.v} {k.u && {k.u}}
{k.bar && (
)} {k.spark && ( )}
{k.sub}
))}
{/* Opportunités */}

Opportunités

Voir tout
SOL
SOL/USDT
Cassure ATH · momentum
↑ LONG
9.7
); }; /* Mobile signaux screen — matches real production */ const PhoneSignauxScreen = () => { const signals = [ { pair: 'BTC/USDT', dir: 'LONG', dirArrow: '↑', dirC: 'var(--green)', chg: '+3.84%', chgC: 'var(--green)', score: '9.4', scoreC: 'var(--green)', icon: '₿', iconBg: '#F7931A' }, { pair: 'ETH/USDT', dir: 'SHORT', dirArrow: '↓', dirC: 'var(--red)', chg: '-1.42%', chgC: 'var(--red)', score: '8.2', scoreC: 'var(--accent-2)', icon: 'ETH', iconBg: '#627EEA' }, { pair: 'SOL/USDT', dir: 'LONG', dirArrow: '↑', dirC: 'var(--green)', chg: '+5.62%', chgC: 'var(--green)', score: '9.7', scoreC: 'var(--green)', icon: 'SOL', iconBg: '#9945FF' }, { pair: 'XAU/USDT', dir: 'SHORT', dirArrow: '↓', dirC: 'var(--red)', chg: '-0.60%', chgC: 'var(--red)', score: '7.4', scoreC: 'var(--accent-2)', icon: 'XAU', iconBg: '#C2A633' }, ]; return (
{/* Big Scanner button — purple→magenta gradient */} {/* Filter chips */}
{[ { l: 'Tous', c: '4', active: true }, { l: 'LONG', c: '2', active: false }, { l: 'SHORT', c: '2', active: false }, { l: '≥ 8/10', c: '3', active: false }, ].map((c, i) => (
{c.l} {c.c}
))}
{/* Signal cards */}
{signals.map((s, i) => (
1 ? 9 : 13, fontWeight: 700, flexShrink: 0, }}>{s.icon}
{s.pair}
{s.dirArrow} {s.dir} {s.chg}
{s.score} /10
))}
); }; /* ---------- Shader background (Three.js radial lines, tinted Foxbot) ---------- */ const ShaderAnimation = ({ opacity = 0.55 }) => { const containerRef = useRef(null); const stateRef = useRef({ rafId: null, renderer: null, uniforms: null, onResize: null }); useEffect(() => { const container = containerRef.current; if (!container || !window.THREE) return; const THREE = window.THREE; container.innerHTML = ''; const camera = new THREE.Camera(); camera.position.z = 1; const scene = new THREE.Scene(); const geometry = new THREE.PlaneBufferGeometry(2, 2); const uniforms = { time: { type: 'f', value: 1.0 }, resolution: { type: 'v2', value: new THREE.Vector2() }, }; const vertexShader = ` void main() { gl_Position = vec4(position, 1.0); } `; // Adapted: same radial-line maths, but the final colour is mixed // through a Foxbot warm-orange ramp so it matches the brand. const fragmentShader = ` precision highp float; uniform vec2 resolution; uniform float time; float random(in float x) { return fract(sin(x) * 1e4); } void main(void) { vec2 uv = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y); vec2 fMosaicScal = vec2(4.0, 2.0); vec2 vScreenSize = vec2(256.0, 256.0); uv.x = floor(uv.x * vScreenSize.x / fMosaicScal.x) / (vScreenSize.x / fMosaicScal.x); uv.y = floor(uv.y * vScreenSize.y / fMosaicScal.y) / (vScreenSize.y / fMosaicScal.y); float t = time * 0.06 + random(uv.x) * 0.4; float lineWidth = 0.0008; vec3 raw = vec3(0.0); for (int j = 0; j < 3; j++) { for (int i = 0; i < 5; i++) { raw[j] += lineWidth * float(i*i) / abs(fract(t - 0.01 * float(j) + float(i) * 0.01) * 1.0 - length(uv)); } } // Compress into a single luminance signal, then tint cool / purple. float k = clamp((raw.r + raw.g + raw.b) / 3.0, 0.0, 2.5); vec3 deep = vec3(0.024, 0.024, 0.047); // bg-base vec3 ember = vec3(0.290, 0.140, 0.470); // dim purple vec3 flame = vec3(0.608, 0.365, 0.940); // accent-500 vec3 hot = vec3(0.867, 0.333, 0.659); // accent-mag vec3 col = mix(deep, ember, smoothstep(0.0, 0.35, k)); col = mix(col, flame, smoothstep(0.35, 0.9, k)); col = mix(col, hot, smoothstep(0.9, 1.6, k)); gl_FragColor = vec4(col, 1.0); } `; const material = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); renderer.domElement.style.display = 'block'; renderer.domElement.style.width = '100%'; renderer.domElement.style.height = '100%'; const onResize = () => { const r = container.getBoundingClientRect(); renderer.setSize(r.width, r.height); uniforms.resolution.value.x = renderer.domElement.width; uniforms.resolution.value.y = renderer.domElement.height; }; onResize(); window.addEventListener('resize', onResize); const animate = () => { stateRef.current.rafId = requestAnimationFrame(animate); uniforms.time.value += 0.05; renderer.render(scene, camera); }; animate(); stateRef.current = { rafId: stateRef.current.rafId, renderer, uniforms, onResize }; return () => { if (stateRef.current.rafId) cancelAnimationFrame(stateRef.current.rafId); window.removeEventListener('resize', onResize); renderer.dispose(); if (renderer.domElement.parentNode) renderer.domElement.parentNode.removeChild(renderer.domElement); }; }, []); return (