// EtapesScreen v3 — three layout concepts via Tweaks // Concept A: horizontal circuit // Concept B: editorial grid — numbered slabs // Concept C: serpentine river — flowing thread with detached transversaux const { useState, useEffect, useMemo, useRef } = React; const RIVER_ICON_BASE = 'assets/river_icons_couleurs/'; // ─── Concept C: Serpentine River ───────────────────────────── // 11 numbered stages arranged on an S-curve path that fills the viewport. // A glowing thread connects them; transversaux sit in the corner as detached cards. function EtapesRiver({ numbered, onPickEtape, onPickTransversal }) { const [revealedCount, setRevealedCount] = useState(0); // 9 nœuds sur 2 rangées : 5 (gauche→droite) + 4 (droite→gauche) const positions = useMemo(() => { const pts = []; const lanes = [ { y: 0.28, indices: [0, 1, 2, 3, 4], dir: 1 }, { y: 0.68, indices: [5, 6, 7, 8], dir: -1 }, ]; lanes.forEach(lane => { const n = lane.indices.length; lane.indices.forEach((idx, k) => { const t = (k + 0.5) / n; const x = lane.dir === 1 ? 0.08 + t * 0.84 : 0.92 - (k + 1) * (0.84 / (n + 1)); pts[idx] = { x, y: lane.y }; }); }); return pts; }, []); // Nœuds à afficher : 5a+5b → 1 nœud (étapeIndex 5, "Pour alumineries" par défaut) // 6a+6b → 1 nœud (étapeIndex 7, "Pour alumineries" par défaut) const displayNodes = useMemo(() => { function mergeCount(indices) { const ents = new Set(); indices.forEach(i => numbered[i].categories.forEach(c => c.services.forEach(s => s.entreprises.forEach(e => ents.add(e.nom))))); return ents.size; } const clean = nom => nom.replace(/^\d+[a-z]?\.\s*/i, ''); return [ { name: clean(numbered[0].nom), etapeIndex: 0, icon: 'icon_bauxite.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[0]).unique }, { name: clean(numbered[1].nom), etapeIndex: 1, icon: 'icon_raffinage.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[1]).unique }, { name: clean(numbered[2].nom), etapeIndex: 2, icon: 'icon_fusion.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[2]).unique }, { name: clean(numbered[3].nom), etapeIndex: 3, icon: 'icon_moulage_alliage.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[3]).unique }, { name: 'Transformation intermédiaire', etapeIndex: 5, icon: 'icon_transformation_int.svg', count: mergeCount([4, 5]) }, { name: 'Fabrication (pièces)', etapeIndex: 7, icon: 'icon_fabrication.svg', count: mergeCount([6, 7]) }, { name: clean(numbered[8].nom), etapeIndex: 8, icon: 'icon_distribution.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[8]).unique }, { name: clean(numbered[9].nom), etapeIndex: 9, icon: 'icon_recyclage.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[9]).unique }, { name: clean(numbered[10].nom), etapeIndex: 10, icon: 'icon_soutien_ecosystem.svg', count: window.SVAUtils.countEntreprisesInEtape(numbered[10]).unique }, ]; }, [numbered]); const pathD = useMemo(() => { if (positions.length < 9) return ''; const W = 1000, H = 600; const p = positions.map(pt => ({ x: pt.x * W, y: pt.y * H })); let d = `M ${p[0].x} ${p[0].y}`; for (let i = 1; i < p.length; i++) { const prev = p[i - 1]; const cur = p[i]; const dx = cur.x - prev.x; const dy = cur.y - prev.y; let c1x, c1y, c2x, c2y; if (i === 5) { // Courbe S entre rangée 1 (gauche→droite) et rangée 2 (droite→gauche). // Les tangentes doivent être horizontales aux deux extrémités pour s'aligner // avec les segments adjacents. Extension = demi-espacement entre nœuds (~84px). const halfSpacing = (p[1].x - p[0].x) * 0.75; c1x = prev.x + halfSpacing; c1y = prev.y; c2x = cur.x + halfSpacing; c2y = cur.y; } else { c1x = prev.x + dx * 0.5; c1y = prev.y; c2x = cur.x - dx * 0.5; c2y = cur.y; } d += ` C ${c1x} ${c1y}, ${c2x} ${c2y}, ${cur.x} ${cur.y}`; } return d; }, [positions]); useEffect(() => { let cancelled = false; const ids = []; for (let i = 0; i <= displayNodes.length; i++) { ids.push(setTimeout(() => { if (!cancelled) setRevealedCount(i); }, 200 + i * 110)); } return () => { cancelled = true; ids.forEach(clearTimeout); }; }, []); // For path-length based reveal we use stroke-dashoffset const pathRef = useRef(null); const [pathLen, setPathLen] = useState(0); useEffect(() => { if (pathRef.current) setPathLen(pathRef.current.getTotalLength()); }, [pathD]); const totalCount = useMemo(() => { const ents = new Set(); numbered.forEach(et => et.categories.forEach(c => c.services.forEach(s => s.entreprises.forEach(e => ents.add(e.nom))))); return ents.size; }, [numbered]); return (
Vallée de l'aluminium

Le circuit de l'aluminium

160+
entreprises
référencées
{displayNodes.map((node, i) => { const pos = positions[i]; if (!pos) return null; const visible = i < revealedCount; return ( ); })}
Région du Saguenay–Lac-St-Jean
); } function EtapesScreen3({ data, onPickEtape, onPickTransversal }) { const numbered = data.etapes.slice(0, 11); return ; } window.EtapesScreen3 = EtapesScreen3;