*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
margin: 0;
background: #020617;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.graph-wrapper {
position: relative;
width: 100%;
max-width: 1000px;
height: 600px;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
background: linear-gradient(135deg, #0f172a, #1e1b4b);
border: 1px solid rgba(255, 255, 255, 0.1);
}
#node-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.graph-overlay {
position: absolute;
bottom: 40px;
left: 40px;
z-index: 2;
pointer-events: none;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(12px);
padding: 24px 30px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.graph-overlay h2 {
color: #f8fafc;
margin: 0 0 8px 0;
font-size: 2rem;
letter-spacing: -0.5px;
}
.graph-overlay p {
color: #94a3b8;
margin: 0;
font-size: 1.125rem;
}
const canvas = document.getElementById("node-canvas");
const ctx = canvas.getContext("2d");
const width = 1000;
const height = 600;
canvas.width = width;
canvas.height = height;
const nodes = new Array();
const numNodes = 60;
for (let i = 0; i < numNodes; i++) {
const node = new Object();
node.x = Math.random() * width;
node.y = Math.random() * height;
node.vx = (Math.random() - 0.5) * 1.5;
node.vy = (Math.random() - 0.5) * 1.5;
node.radius = Math.random() * 2 + 2;
nodes.push(node);
}
const mouse = new Object();
mouse.x = width / 2;
mouse.y = height / 2;
mouse.radius = 120;
canvas.addEventListener("mousemove", function(e) {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * (width / rect.width);
mouse.y = (e.clientY - rect.top) * (height / rect.height);
});
function animate() {
ctx.clearRect(0, 0, width, height);
nodes.forEach(function(node) {
node.x += node.vx;
node.y += node.vy;
if (node.x < 0 || node.x > width) node.vx *= -1;
if (node.y < 0 || node.y > height) node.vy *= -1;
const dxMouse = mouse.x - node.x;
const dyMouse = mouse.y - node.y;
const distanceMouse = Math.sqrt(dxMouse * dxMouse + dyMouse * dyMouse);
if (distanceMouse < mouse.radius) {
const force = (mouse.radius - distanceMouse) / mouse.radius;
node.vx -= (dxMouse / distanceMouse) * force * 0.5;
node.vy -= (dyMouse / distanceMouse) * force * 0.5;
}
node.vx *= 0.99;
node.vy *= 0.99;
ctx.beginPath();
ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
ctx.fillStyle = "#3b82f6";
ctx.fill();
});
nodes.forEach(function(nodeA) {
nodes.forEach(function(nodeB) {
const dx = nodeA.x - nodeB.x;
const dy = nodeA.y - nodeB.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0 && dist < 120) {
ctx.beginPath();
ctx.moveTo(nodeA.x, nodeA.y);
ctx.lineTo(nodeB.x, nodeB.y);
const opacity = 1 - (dist / 120);
ctx.strokeStyle = "rgba(139, 92, 246, " + opacity + ")";
ctx.lineWidth = 1;
ctx.stroke();
}
});
});
window.requestAnimationFrame(animate);
}
animate();