// Veille v2 — kinetic, dramatic hero with mega type const { useEffect, useRef } = React; function Veille2Canvas() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const dpr = Math.min(window.devicePixelRatio || 1, 2); let raf = 0, w = 0, h = 0; const PARTS = []; const COUNT = 90; const STREAMS = 6; function resize() { const r = canvas.getBoundingClientRect(); w = r.width; h = r.height; canvas.width = w * dpr; canvas.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } function init() { resize(); PARTS.length = 0; for (let i = 0; i < COUNT; i++) { PARTS.push({ x: Math.random() * w, y: Math.random() * h, vx: (Math.random() - 0.5) * 0.4, vy: (Math.random() - 0.5) * 0.4, r: Math.random() * 2.5 + 0.5, a: Math.random() * 0.6 + 0.2, hue: 200 + Math.random() * 30, }); } } function step() { ctx.clearRect(0, 0, w, h); const t = performance.now() * 0.0004; // Aurora streams for (let i = 0; i < STREAMS; i++) { const yBase = h * (0.15 + i * 0.13); ctx.beginPath(); const grad = ctx.createLinearGradient(0, 0, w, 0); grad.addColorStop(0, 'rgba(77,123,214,0)'); grad.addColorStop(0.5, `rgba(${i % 2 ? '180,200,240' : '120,160,230'},${0.18 + i * 0.02})`); grad.addColorStop(1, 'rgba(77,123,214,0)'); ctx.strokeStyle = grad; ctx.lineWidth = 1.5; ctx.moveTo(0, yBase); for (let x = 0; x <= w; x += 12) { const y = yBase + Math.sin((x * 0.004) + t * (i + 1) * 3 + i * 0.7) * (30 + i * 8); ctx.lineTo(x, y); } ctx.stroke(); } // Particles for (const p of PARTS) { p.x += p.vx; p.y += p.vy; if (p.x < -10) p.x = w + 10; else if (p.x > w + 10) p.x = -10; if (p.y < -10) p.y = h + 10; else if (p.y > h + 10) p.y = -10; const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r * 8); grad.addColorStop(0, `hsla(${p.hue}, 60%, 80%, ${p.a})`); grad.addColorStop(1, 'transparent'); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(p.x, p.y, p.r * 8, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = `rgba(255,255,255,${p.a + 0.3})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill(); } // Connections for (let i = 0; i < PARTS.length; i++) { for (let j = i + 1; j < PARTS.length; j++) { const a = PARTS[i], b = PARTS[j]; const dx = a.x - b.x, dy = a.y - b.y; const d2 = dx * dx + dy * dy; if (d2 < 18000) { const alpha = (1 - d2 / 18000) * 0.22; ctx.strokeStyle = `rgba(120,160,230,${alpha})`; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); } } } raf = requestAnimationFrame(step); } init(); step(); window.addEventListener('resize', init); return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', init); }; }, []); return ; } function Veille2({ data, onWake }) { const stats = React.useMemo(() => { const ents = new Set(); let services = 0; let alumineries = 0; data.etapes.forEach(et => et.categories.forEach(c => { c.services.forEach(s => { if (s.nom) services++; s.entreprises.forEach(e => ents.add(e.nom)); }); })); return { entreprises: ents.size, etapes: 9, services, region: 'SLSJ' }; }, [data]); // Animated counters on mount const [animated, setAnimated] = React.useState({ etapes: 0, ent: 0, srv: 0, pct: 0 }); useEffect(() => { const start = performance.now(); const dur = 1400; let raf = 0; const tick = (t) => { const p = Math.min(1, (t - start) / dur); const e = 1 - Math.pow(1 - p, 3); setAnimated({ etapes: Math.round(9 * e), ent: Math.round(160 * e), srv: Math.round(stats.services * e), pct: Math.round(107 * e), }); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [stats.services]); return (
SVA
La Vallée de l'aluminium

Découvrez le circuit de l'aluminium

Neuf étapes, des centaines d'entreprises, une filière entière ancrée dans la région.

Touchez l'écran pour commencer
{animated.etapes}
Étapes du circuit
{animated.ent}+
Entreprises
{animated.srv}
Services référencés
{(animated.pct / 10).toFixed(1).replace('.', ',')}G$
générés dans
la région
); } window.Veille2 = Veille2;