// constellation.jsx — La carte-constellation des 38 communes de la Pévèle Carembault // Nœuds lumineux + arcs qui pulsent à chaque Exaucement. const COMMUNES = [ // 38 communes Pévèle Carembault (approximation géographique normalisée 0..1) { n: "Orchies", x: 0.66, y: 0.46, big: true }, { n: "Templeuve-en-Pévèle",x: 0.42, y: 0.40, big: true }, { n: "Cysoing", x: 0.34, y: 0.32, big: true }, { n: "Pont-à-Marcq", x: 0.34, y: 0.50 }, { n: "Phalempin", x: 0.22, y: 0.62 }, { n: "Thumeries", x: 0.40, y: 0.66 }, { n: "Ostricourt", x: 0.28, y: 0.74 }, { n: "Wahagnies", x: 0.34, y: 0.70 }, { n: "Mons-en-Pévèle", x: 0.46, y: 0.56 }, { n: "Mérignies", x: 0.40, y: 0.52 }, { n: "Avelin", x: 0.30, y: 0.42 }, { n: "Attiches", x: 0.36, y: 0.58 }, { n: "Ennevelin", x: 0.42, y: 0.46 }, { n: "Tourmignies", x: 0.32, y: 0.56 }, { n: "Moncheaux", x: 0.40, y: 0.74 }, { n: "Genech", x: 0.42, y: 0.32 }, { n: "Bachy", x: 0.46, y: 0.22 }, { n: "Cobrieux", x: 0.40, y: 0.26 }, { n: "Bourghelles", x: 0.32, y: 0.26 }, { n: "Wannehain", x: 0.46, y: 0.28 }, { n: "Mouchin", x: 0.50, y: 0.30 }, { n: "Louvil", x: 0.36, y: 0.28 }, { n: "Bersée", x: 0.46, y: 0.46 }, { n: "Bouvignies", x: 0.62, y: 0.54 }, { n: "Coutiches", x: 0.56, y: 0.58 }, { n: "Beuvry-la-Forêt", x: 0.74, y: 0.52 }, { n: "Auchy-lez-Orchies", x: 0.62, y: 0.42 }, { n: "Landas", x: 0.74, y: 0.40 }, { n: "Nomain", x: 0.62, y: 0.34 }, { n: "Saméon", x: 0.78, y: 0.34 }, { n: "Aix-en-Pévèle", x: 0.54, y: 0.40 }, { n: "Cappelle-en-Pévèle", x: 0.50, y: 0.46 }, { n: "Chemy", x: 0.20, y: 0.54 }, { n: "La Neuville", x: 0.24, y: 0.46 }, { n: "Faumont", x: 0.56, y: 0.66 }, { n: "Rosult", x: 0.84, y: 0.42 }, { n: "Tilloy-lez-Marchiennes", x: 0.86, y: 0.50 }, { n: "Marchiennes", x: 0.86, y: 0.58 }, ]; // Pseudo-random but deterministic graph of "connections" (lignes lumineuses entre communes voisines) function buildEdges(nodes) { const edges = []; nodes.forEach((a, i) => { // Connect each node to its 2-3 closest neighbors const sorted = nodes .map((b, j) => ({ b, j, d: Math.hypot(a.x - b.x, a.y - b.y) })) .filter(o => o.j !== i) .sort((x, y) => x.d - y.d) .slice(0, 2 + (i % 2)); sorted.forEach(({ j }) => { const key = [Math.min(i, j), Math.max(i, j)].join("-"); if (!edges.find(e => e.key === key)) { edges.push({ key, i: Math.min(i, j), j: Math.max(i, j) }); } }); }); return edges; } const EDGES = buildEdges(COMMUNES); function Constellation({ variant = "hero", showLabels = "few" }) { // variant: "hero" (large), "compact" // showLabels: "all" | "few" | "none" const W = 1000, H = 750; const [pulses, setPulses] = React.useState([]); // active arc pulses const [activeNode, setActiveNode] = React.useState(null); const idRef = React.useRef(0); // Trigger random "exaucement" pulses React.useEffect(() => { const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduced) return; const interval = setInterval(() => { const edge = EDGES[Math.floor(Math.random() * EDGES.length)]; const id = ++idRef.current; setPulses(p => [...p, { id, ...edge, t: Date.now() }]); setActiveNode(Math.random() < 0.5 ? edge.i : edge.j); setTimeout(() => { setPulses(p => p.filter(x => x.id !== id)); }, 2400); }, 900); return () => clearInterval(interval); }, []); return (