Code with comments (fold it out):
const canvas = document.getElementById('meshCanvas');
const ctx = canvas.getContext('2d', { alpha: true });
let width, height, particles = [];
let mouse = { x: 0, y: 0, active: false };
// --- Particle: a single node in the network ---
class Particle {
constructor() {
// Spawn at a random angle around screen center, at radius 160-176px
const cx = width / 2;
const cy = height / 2;
const angle = Math.random() * Math.PI * 2;
const distance = 160 + Math.random() * 16;
this.baseX = cx + Math.cos(angle) * distance;
this.baseY = cy + Math.sin(angle) * distance;
this.x = this.baseX;
this.y = this.baseY;
this.size = Math.random() * 2.6 + 1.5; // radius: 1.5–4.1px
this.angle = Math.random() * Math.PI * 2; // starting orbit phase
this.orbitSpeed = 0.06 + Math.random() * 0.016; // orbit speed, slightly randomized
}
update() {
// Orbit: rotate around base position (16px wobble)
this.angle += this.orbitSpeed;
let tx = this.baseX + Math.cos(this.angle) * 16;
let ty = this.baseY + Math.sin(this.angle) * 16;
// Mouse repulsion: if cursor is within 280px, push particle away
if (mouse.active) {
const dx = mouse.x - this.x;
const dy = mouse.y - this.y;
const dist = Math.hypot(dx, dy);
if (dist < 280) {
const force = (1 - dist / 280) * 0.42; // stronger force when closer
tx += dx * force;
ty += dy * force;
}
}
// Smooth interpolation (90% previous + 10% target) for laggy movement
this.x = this.x * 0.9 + tx * 0.1;
this.y = this.y * 0.9 + ty * 0.1;
}
draw() {
// Intensity: 1 when cursor is on the node, 0 beyond 260px
const dist = Math.hypot(this.x - mouse.x, this.y - mouse.y);
const intensity = Math.max(0, 1 - dist / 260);
// Red glow when mouse is close, cyan glow otherwise
if (intensity > 0.25) {
ctx.fillStyle = '#ff2d2d';
ctx.shadowBlur = 20;
ctx.shadowColor = '#ff6666';
} else {
ctx.fillStyle = '#00ddff';
ctx.shadowBlur = 8;
ctx.shadowColor = '#00ddff';
}
ctx.beginPath();
ctx.arc(this.x, this.y, this.size + intensity * 3.2, 0, Math.PI * 2);
ctx.fill();
}
}
// --- Connection lines between nearby particles ---
function connectParticles() {
ctx.lineWidth = 0.7;
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const d = Math.hypot(particles[i].x - particles[j].x, particles[i].y - particles[j].y);
if (d < 170) {
// Opacity fades from 1 (touching) to 0 (at 170px apart)
ctx.strokeStyle = `rgba(0, 221, 255, ${1 - d / 170})`;
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
}
// --- Animation loop ---
function animate() {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => { p.update(); p.draw(); });
connectParticles();
requestAnimationFrame(animate); // runs at ~60fps
}
// --- Mouse tracking ---
window.addEventListener('mousemove', (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
mouse.active = true;
});
window.addEventListener('mouseleave', () => mouse.active = false);
// --- Initialization ---
function init() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
particles = [];
// Particle count scales with day of month: max ~48 on day 28, min 3
const day = new Date().getDate();
const count = Math.max(3, Math.round(day * (48 / 28)));
for (let i = 0; i < count; i++) particles.push(new Particle());
animate();
}
window.addEventListener('resize', init);
init();
// --- Live Connections polling ---
// Every 15 seconds, re-fetch the page and scrape the "Live Connections"
// counter from the server-rendered HTML. This is how the number updates
// without a full page reload — no WebSocket, no SSE.
setInterval(() => {
const container = document.getElementById('live-visitors');
if (!container) return;
fetch(window.location.href)
.then(r => r.text())
.then(html => {
const temp = document.createElement('div');
temp.innerHTML = html;
const newContent = temp.querySelector('#live-visitors');
if (newContent) container.innerHTML = newContent.innerHTML;
});
}, 15000);
TL;DR:
- Nodes orbit in a loose ring around screen center, connected by cyan lines when close.
- Moving your mouse repels nearby nodes and turns them red.
- Number of nodes depends on the current day of the month.
- “Live Connections” counter is just fetch() polling every 15s - nothing real-time.
- When you grab a new copy via load and a new execution runs, the endpoint returns a value.
- It runs at z-index: -1 behind all page content - purely decorative.
Also: Project Skyscraper - Frequently Asked Questions
for reference.