/** * Interactive ASCII Grid Background * Renders a spring-based grid of ASCII characters that repel from the cursor. */ (function() { var canvas = document.createElement('canvas'); canvas.id = 'ascii-canvas'; canvas.style.position = 'fixed'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.width = '100vw'; canvas.style.height = '100vh'; canvas.style.zIndex = '-1'; canvas.style.pointerEvents = 'none'; canvas.style.opacity = '0.08'; // very subtle document.body.appendChild(canvas); var ctx = canvas.getContext('2d'); var particles = []; var mouse = { x: null, y: null, radius: 150 }; var chars = ['#', '+', '-', '=', '*', '%', '0', '1', 'x', 'y', 'z', '@', '&']; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; initGrid(); } function initGrid() { particles = []; var spacing = 45; // grid spacing var cols = Math.floor(canvas.width / spacing) + 2; var rows = Math.floor(canvas.height / spacing) + 2; for (var c = 0; c < cols; c++) { for (var r = 0; r < rows; r++) { var homeX = c * spacing - spacing/2; var homeY = r * spacing - spacing/2; particles.push({ x: homeX, y: homeY, homeX: homeX, homeY: homeY, vx: 0, vy: 0, char: chars[Math.floor(Math.random() * chars.length)] }); } } } window.addEventListener('resize', resize); window.addEventListener('mousemove', function(e) { mouse.x = e.clientX; mouse.y = e.clientY; }); window.addEventListener('mouseout', function() { mouse.x = null; mouse.y = null; }); function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = '10px "JetBrains Mono", monospace'; ctx.fillStyle = '#00f5ff'; ctx.strokeStyle = 'rgba(0, 245, 255, 0.04)'; ctx.lineWidth = 0.5; // Update physics for (var i = 0; i < particles.length; i++) { var p = particles[i]; // Spring force back to home var ax = (p.homeX - p.x) * 0.03; var ay = (p.homeY - p.y) * 0.03; // Repulsion from mouse if (mouse.x !== null && mouse.y !== null) { var dx = mouse.x - p.x; var dy = mouse.y - p.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < mouse.radius) { var force = (mouse.radius - dist) / mouse.radius; // Move away from mouse var angle = Math.atan2(dy, dx); ax -= Math.cos(angle) * force * 0.6; ay -= Math.sin(angle) * force * 0.6; } } p.vx = (p.vx + ax) * 0.85; // friction p.vy = (p.vy + ay) * 0.85; p.x += p.vx; p.y += p.vy; // Render character ctx.fillText(p.char, p.x - 3, p.y + 3); } // Draw web lines var spacing = 45; var threshold = spacing * 1.4; for (var i = 0; i < particles.length; i++) { var p1 = particles[i]; for (var j = i + 1; j < particles.length; j++) { var p2 = particles[j]; var dx = p1.x - p2.x; var dy = p1.y - p2.y; var dist = dx * dx + dy * dy; if (dist < threshold * threshold) { ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } } } requestAnimationFrame(draw); } resize(); draw(); })();