From 2a642bbfa4631983c3e10235936c5ee1f492dd10 Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Sat, 12 Sep 2026 18:23:57 +0530 Subject: [PATCH] feat(ui): add autonomous self-moving particle drift and mobile touch interactivity --- public/js/app.js | 126 ++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 50 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 5a89ccc..191bc7a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -84,7 +84,7 @@ } } - // Interactive Particle Background + // Autonomous & Interactive Particle Background function initInteractiveParticles() { const canvas = document.getElementById('particles'); if (!canvas) return; @@ -98,61 +98,80 @@ height = canvas.height = window.innerHeight; }); - // Mouse tracking for interactivity - const mouse = { + // Mouse & Touch tracking for interactivity + const pointer = { x: null, y: null, - radius: 140 + radius: 150 }; window.addEventListener('mousemove', e => { - mouse.x = e.clientX; - mouse.y = e.clientY; + pointer.x = e.clientX; + pointer.y = e.clientY; }); window.addEventListener('mouseleave', () => { - mouse.x = null; - mouse.y = null; + pointer.x = null; + pointer.y = null; }); - // Click to push particles slightly + window.addEventListener('touchmove', e => { + if (e.touches.length > 0) { + pointer.x = e.touches[0].clientX; + pointer.y = e.touches[0].clientY; + } + }, { passive: true }); + + window.addEventListener('touchend', () => { + pointer.x = null; + pointer.y = null; + }); + + // Click to push particles outward window.addEventListener('click', e => { for (const p of particles) { const dx = p.x - e.clientX; const dy = p.y - e.clientY; const dist = Math.sqrt(dx * dx + dy * dy); - if (dist < 180 && dist > 0) { - const force = (180 - dist) / 180; - p.vx += (dx / dist) * force * 3; - p.vy += (dy / dist) * force * 3; + if (dist < 220 && dist > 0) { + const force = (220 - dist) / 220; + p.disturbVx += (dx / dist) * force * 4; + p.disturbVy += (dy / dist) * force * 4; } } }); const particles = []; - const count = Math.min(50, Math.floor(width / 26)); + const count = Math.min(80, Math.max(35, Math.floor(width / 20))); for (let i = 0; i < count; i++) { + const angle = Math.random() * Math.PI * 2; + const speed = 0.35 + Math.random() * 0.45; particles.push({ x: Math.random() * width, y: Math.random() * height, - vx: (Math.random() - 0.5) * 0.45, - vy: (Math.random() - 0.5) * 0.45, - radius: Math.random() * 1.5 + 1 + baseVx: Math.cos(angle) * speed, + baseVy: Math.sin(angle) * speed, + disturbVx: 0, + disturbVy: 0, + radius: Math.random() * 1.5 + 1, + phase: Math.random() * Math.PI * 2, + twinkleSpeed: 0.0018 + Math.random() * 0.0025 }); } - function animate() { + function animate(time) { ctx.clearRect(0, 0, width, height); - // Connect particles to each other + // Connect particles to each other (constellation network) for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; - const dist = Math.sqrt(dx * dx + dy * dy); - if (dist < 115) { - ctx.strokeStyle = `rgba(255, 255, 255, ${0.06 * (1 - dist / 115)})`; + const distSq = dx * dx + dy * dy; + if (distSq < 15625) { // 125px + const dist = Math.sqrt(distSq); + ctx.strokeStyle = `rgba(255, 255, 255, ${0.075 * (1 - dist / 125)})`; ctx.lineWidth = 0.8; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); @@ -162,47 +181,54 @@ } } - // Update & draw particles + connect to mouse + // Update & draw each particle for (const p of particles) { - p.x += p.vx; - p.y += p.vy; + // Continuous organic autonomous motion: base velocity + subtle harmonic wave drift + const waveX = Math.sin(time * 0.0012 + p.phase) * 0.22; + const waveY = Math.cos(time * 0.0015 + p.phase) * 0.22; - // Dampen velocity back to normal - p.vx *= 0.985; - p.vy *= 0.985; + p.x += p.baseVx + p.disturbVx + waveX; + p.y += p.baseVy + p.disturbVy + waveY; - // Boundary bounce - if (p.x < 0) { p.x = 0; p.vx *= -1; } - if (p.x > width) { p.x = width; p.vx *= -1; } - if (p.y < 0) { p.y = 0; p.vy *= -1; } - if (p.y > height) { p.y = height; p.vy *= -1; } + // Dampen interactive disturbance back to zero (leaves autonomous base speed intact) + p.disturbVx *= 0.94; + p.disturbVy *= 0.94; - // Interaction with mouse cursor - if (mouse.x !== null && mouse.y !== null) { - const mdx = mouse.x - p.x; - const mdy = mouse.y - p.y; - const mDist = Math.sqrt(mdx * mdx + mdy * mdy); + // Seamless boundary wrap with padding so particles never bunch at edges + const pad = 15; + if (p.x < -pad) p.x = width + pad; + if (p.x > width + pad) p.x = -pad; + if (p.y < -pad) p.y = height + pad; + if (p.y > height + pad) p.y = -pad; - if (mDist < mouse.radius) { - // Connect line to mouse - ctx.strokeStyle = `rgba(77, 184, 255, ${0.18 * (1 - mDist / mouse.radius)})`; + // Interaction with mouse/touch pointer + if (pointer.x !== null && pointer.y !== null) { + const mdx = pointer.x - p.x; + const mdy = pointer.y - p.y; + const mDistSq = mdx * mdx + mdy * mdy; + + if (mDistSq < pointer.radius * pointer.radius) { + const mDist = Math.sqrt(mDistSq); + // Connect line to pointer + ctx.strokeStyle = `rgba(255, 255, 255, ${0.2 * (1 - mDist / pointer.radius)})`; ctx.lineWidth = 0.9; ctx.beginPath(); ctx.moveTo(p.x, p.y); - ctx.lineTo(mouse.x, mouse.y); + ctx.lineTo(pointer.x, pointer.y); ctx.stroke(); - // Repel gently if very close - if (mDist < 70 && mDist > 0) { - const repel = (70 - mDist) / 70; - p.x -= (mdx / mDist) * repel * 1.5; - p.y -= (mdy / mDist) * repel * 1.5; + // Repel gently if close + if (mDist < 75 && mDist > 0) { + const repel = (75 - mDist) / 75; + p.disturbVx -= (mdx / mDist) * repel * 1.6; + p.disturbVy -= (mdy / mDist) * repel * 1.6; } } } - // Draw particle dot - ctx.fillStyle = 'rgba(255, 255, 255, 0.28)'; + // Breathing particle glow + const alpha = 0.25 + Math.sin(time * p.twinkleSpeed + p.phase) * 0.14; + ctx.fillStyle = `rgba(255, 255, 255, ${alpha.toFixed(3)})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); @@ -211,7 +237,7 @@ requestAnimationFrame(animate); } - animate(); + requestAnimationFrame(animate); } // Safe HTML helper