// Particle field const canvas = document.getElementById('particles'); const ctx = canvas.getContext('2d'); let W, H, particles; function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; } function createParticles() { const count = Math.floor((W * H) / 8000); particles = Array.from({ length: count }, () => ({ x: Math.random() * W, y: Math.random() * H, vx: (Math.random() - 0.5) * 0.4, vy: (Math.random() - 0.5) * 0.4, r: Math.random() * 1.5 + 0.5, hue: Math.random() > 0.5 ? 180 : 300, })); } const CONNECT_DIST = 120; function draw() { ctx.clearRect(0, 0, W, H); for (let i = 0; i < particles.length; i++) { const p = particles[i]; p.x += p.vx; p.y += p.vy; if (p.x < 0 || p.x > W) p.vx *= -1; if (p.y < 0 || p.y > H) p.vy *= -1; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle = `hsla(${p.hue}, 100%, 70%, 0.8)`; ctx.fill(); for (let j = i + 1; j < particles.length; j++) { const q = particles[j]; const dx = p.x - q.x; const dy = p.y - q.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < CONNECT_DIST) { ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(q.x, q.y); ctx.strokeStyle = `hsla(${p.hue}, 100%, 70%, ${1 - dist / CONNECT_DIST})`; ctx.lineWidth = 0.4; ctx.stroke(); } } } requestAnimationFrame(draw); } window.addEventListener('resize', () => { resize(); createParticles(); }); resize(); createParticles(); draw(); // Typewriter subtitle const lines = [ 'bienvenido al universo digital', 'cada bit cuenta una historia', 'el código es poesía', ]; let lineIdx = 0, charIdx = 0, deleting = false; const typed = document.getElementById('typed'); function typeLoop() { const current = lines[lineIdx]; if (!deleting) { charIdx++; typed.textContent = current.slice(0, charIdx); if (charIdx === current.length) { deleting = true; setTimeout(typeLoop, 2000); return; } } else { charIdx--; typed.textContent = current.slice(0, charIdx); if (charIdx === 0) { deleting = false; lineIdx = (lineIdx + 1) % lines.length; } } setTimeout(typeLoop, deleting ? 40 : 80); } typeLoop(); // Live elapsed counter const start = Date.now(); const counterEl = document.getElementById('counter'); function updateCounter() { const s = Math.floor((Date.now() - start) / 1000); const h = String(Math.floor(s / 3600)).padStart(2, '0'); const m = String(Math.floor((s % 3600) / 60)).padStart(2, '0'); const sec = String(s % 60).padStart(2, '0'); counterEl.textContent = `UPTIME :: ${h}:${m}:${sec}`; requestAnimationFrame(updateCounter); } updateCounter();