/** * Géométrie de la spirale de Fibonacci (pure, sans React) — partagée par le * composant et le script de génération des icônes. */ type Square = { x: number; y: number; s: number }; type Dir = "right" | "down" | "left" | "up"; const DIRS: Dir[] = ["right", "down", "left", "up"]; export function buildSpiral(levels = 8) { const fib = [1, 1]; while (fib.length < levels) fib.push(fib[fib.length - 1] + fib[fib.length - 2]); const squares: Square[] = [{ x: 0, y: 0, s: 1 }]; const dirs: Dir[] = ["right"]; // direction "virtuelle" du premier carré let bbox = { x: 0, y: 0, w: 1, h: 1 }; for (let i = 1; i < fib.length; i++) { const s = fib[i]; const d = DIRS[(i - 1) % 4]; let sq: Square; if (d === "right") sq = { x: bbox.x + bbox.w, y: bbox.y, s }; else if (d === "down") sq = { x: bbox.x, y: bbox.y + bbox.h, s }; else if (d === "left") sq = { x: bbox.x - s, y: bbox.y, s }; else sq = { x: bbox.x, y: bbox.y - s, s }; squares.push(sq); dirs.push(d); const x0 = Math.min(bbox.x, sq.x); const y0 = Math.min(bbox.y, sq.y); const x1 = Math.max(bbox.x + bbox.w, sq.x + sq.s); const y1 = Math.max(bbox.y + bbox.h, sq.y + sq.s); bbox = { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }; } // Centre de l'arc d'un carré = coin situé du côté du carré précédent // (opposé à sa direction) et du côté de la direction suivante. const opposite: Record = { right: "left", left: "right", up: "down", down: "up", }; const corner = (sq: Square, a: Dir, b: Dir) => { const x = a === "left" || b === "left" ? sq.x : sq.x + sq.s; const y = a === "up" || b === "up" ? sq.y : sq.y + sq.s; return { x, y }; }; const centers = squares.map((sq, i) => { if (i === 0) return { x: sq.x, y: sq.y }; // coin haut-gauche du 1er carré const next = i + 1 < dirs.length ? dirs[i + 1] : DIRS[i % 4]; return corner(sq, opposite[dirs[i]], next); }); let d = ""; let prev: { x: number; y: number } | null = null; squares.forEach((sq, i) => { const c = centers[i]; // Les deux extrémités possibles de l'arc : à distance s sur chaque axe. const dx = c.x === sq.x ? sq.x + sq.s : sq.x - 0; // extrémité horizontale const dy = c.y === sq.y ? sq.y + sq.s : sq.y - 0; const pH = { x: c.x === sq.x ? dx : sq.x, y: c.y }; const pV = { x: c.x, y: c.y === sq.y ? dy : sq.y }; let from = pV; let to = pH; if (prev) { const same = (p: { x: number; y: number }) => Math.abs(p.x - prev!.x) < 1e-9 && Math.abs(p.y - prev!.y) < 1e-9; if (same(pH)) { from = pH; to = pV; } } const cross = (from.x - c.x) * (to.y - c.y) - (from.y - c.y) * (to.x - c.x); const sweep = cross > 0 ? 1 : 0; if (!prev) d += `M ${from.x} ${from.y} `; d += `A ${sq.s} ${sq.s} 0 0 ${sweep} ${to.x} ${to.y} `; prev = to; }); return { squares, path: d.trim(), bbox }; }