<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Glowing Particle Network</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = { darkMode: 'class' };
</script>
<style>
body { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body class="h-full relative flex items-center justify-center bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100 transition-colors duration-300">
<!-- Overlay Content -->
<div class="absolute z-10 text-center space-y-4 p-6 pointer-events-none">
<h1 class="text-4xl sm:text-6xl font-extrabold text-slate-900 dark:text-white tracking-tight drop-shadow-lg">
Particle <span class="text-cyan-500 dark:text-cyan-400">Constellation</span>
</h1>
<p class="text-slate-600 dark:text-slate-400 text-sm sm:text-base max-w-md mx-auto">
Move your cursor across the canvas to interact with the glowing particle network.
</p>
</div>
<!-- Canvas Container -->
<canvas id="particleCanvas" class="w-full h-full absolute inset-0 z-0"></canvas>
<script>
const canvas = document.getElementById("particleCanvas");
const ctx = canvas.getContext("2d");
let width, height;
let particles = [];
let mouse = { x: null, y: null, radius: 150 };
function initCanvas() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
createParticles();
}
class Particle {
constructor() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.size = Math.random() * 2 + 1;
this.vx = (Math.random() - 0.5) * 1.5;
this.vy = (Math.random() - 0.5) * 1.5;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
if (mouse.x !== null && mouse.y !== null) {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist < mouse.radius) {
let force = (mouse.radius - dist) / mouse.radius;
this.x -= (dx / dist) * force * 3;
this.y -= (dy / dist) * force * 3;
}
}
}
draw() {
const isDark = document.documentElement.classList.contains('dark');
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = isDark ? "#06b6d4" : "#0891b2";
ctx.shadowBlur = 10;
ctx.shadowColor = isDark ? "#06b6d4" : "#0891b2";
ctx.fill();
}
}
function createParticles() {
particles = [];
let count = Math.floor((width * height) / 10000);
for (let i = 0; i < count; i++) {
particles.push(new Particle());
}
}
function connectParticles() {
let maxDist = 120;
const isDark = document.documentElement.classList.contains('dark');
for (let a = 0; a < particles.length; a++) {
for (let b = a + 1; b < particles.length; b++) {
let dx = particles[a].x - particles[b].x;
let dy = particles[a].y - particles[b].y;
let dist = Math.sqrt(dx * dx + dy * dy);
if (dist < maxDist) {
let alpha = 1 - (dist / maxDist);
ctx.strokeStyle = isDark
? `rgba(6, 182, 212, ${alpha * 0.4})`
: `rgba(8, 145, 178, ${alpha * 0.3})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(particles[a].x, particles[a].y);
ctx.lineTo(particles[b].x, particles[b].y);
ctx.stroke();
}
}
}
}
function animate() {
const isDark = document.documentElement.classList.contains('dark');
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = isDark ? "#020617" : "#f8fafc";
ctx.fillRect(0, 0, width, height);
for (let p of particles) {
p.update();
p.draw();
}
connectParticles();
requestAnimationFrame(animate);
}
window.addEventListener("resize", initCanvas);
window.addEventListener("mousemove", (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
window.addEventListener("mouseleave", () => {
mouse.x = null;
mouse.y = null;
});
initCanvas();
animate();
</script>
</body>
</html>
/* Supports both Dark and Light mode via Tailwind class variant */
// Interactive scripts included inside demo view file