// ─── Configuration ──────────────────────────────────────────── // Each pair is [sourceId, targetId]. // Arrows go from the bottom-center of the source // to the top-center of the target. const CONNECTIONS = [ ['proj', 'fich'], ['proj', 'hack'], ['fich', 'rust'], ['rust', 'alac'], ['hack', 'alac'], ['alac', 'mine'], ]; // ─── Setup ──────────────────────────────────────────────────── const scene = document.getElementById('body'); const svg = document.getElementById('arrows'); // Create one SVG per connection const pathEls = CONNECTIONS.map(() => { const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p.setAttribute('class', 'arrow-path'); p.setAttribute('marker-end', 'url(#head)'); svg.appendChild(p); return p; }); // ─── Core: Manhattan path between two elements ──────────────── // r1 = bounding rect of source, r2 = bounding rect of target // Both rects are relative to the scene container. function manhattanPath(r1, r2) { let x1 = r1.left + r1.width / 2; // bottom-center of source const y1 = r1.top + r1.height + 10; let x2 = r2.left + r2.width / 2; // top-center of target const y2 = r2.top + 5; // stop 8px above target (arrowhead gap) let shift = 5; if (x1 > x2) { x1 -= shift; x2 += shift; } else if (x2 > x1) { x1 += shift; x2 -= shift; } const ymid = y1 + (y2 - y1) / 2; // elbow halfway between the two // M: move to source bottom // L: go straight down to mid-y // L: go horizontally to target x // L: go straight down to target top return `M ${x1} ${y1} L ${x1} ${ymid} L ${x2} ${ymid} L ${x2} ${y2}`; } // ─── Redraw all arrows ──────────────────────────────────────── function redraw() { const sceneRect = scene.getBoundingClientRect(); CONNECTIONS.forEach(([srcId, tgtId], i) => { const src = document.getElementById(srcId).getBoundingClientRect(); const tgt = document.getElementById(tgtId).getBoundingClientRect(); // Convert from viewport coords → scene-local coords const r1 = { left: src.left - sceneRect.left, top: src.top - sceneRect.top, width: src.width, height: src.height, }; const r2 = { left: tgt.left - sceneRect.left, top: tgt.top - sceneRect.top, width: tgt.width, height: tgt.height, }; pathEls[i].setAttribute('d', manhattanPath(r1, r2)); if (tgt.left > src.left) { pathEls[i].style.stroke = "#008000"; } else if (tgt.left < src.left){ pathEls[i].style.stroke = "#ff0000"; } else { pathEls[i].style.stroke = "#0000ff"; } }); } // ─── Drag logic ─────────────────────────────────────────────── document.querySelectorAll('.node').forEach(node => { node.addEventListener('mousedown', e => { e.preventDefault(); const startX = e.clientX; const startY = e.clientY; const originL = parseInt(node.style.left); const originT = parseInt(node.style.top); const onMove = e => { node.style.left = Math.max(0, originL + e.clientX - startX) + 'px'; node.style.top = Math.max(0, originT + e.clientY - startY) + 'px'; redraw(); }; const onUp = () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); }; window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp); }); }); // ─── Initial draw ───────────────────────────────────────────── // Wait two frames so the browser has laid out the divs requestAnimationFrame(() => requestAnimationFrame(redraw));