(() => { document.addEventListener("DOMContentLoaded", () => { const canvas = document.getElementById("backgroundCanvas"); if (!canvas) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const ctx = canvas.getContext("2d", { alpha: true, desynchronized: true }); const isMobile = window.matchMedia("(max-width: 768px)").matches; const DPR_CAP = Math.min(1.5, isMobile ? 1.0 : 1.25); const getOrbCount = () => { const baseCount = Math.floor((window.innerWidth * window.innerHeight) / 40000); return Math.min(Math.max(baseCount, isMobile ? 15 : 25), isMobile ? 30 : 50); }; const R_MIN = isMobile ? 3 : 4; const R_MAX = isMobile ? 8 : 12; const SPEED = isMobile ? 0.08 : 0.12; const PINK = [245, 169, 184]; const BLUE = [91, 207, 250]; let w = 0, h = 0, dpr = 1; let orbs = []; let raf = 0; let last = 0; function resize() { w = window.innerWidth; h = window.innerHeight; dpr = Math.min(DPR_CAP, window.devicePixelRatio || 1); canvas.width = Math.floor(w * dpr); canvas.height = Math.floor(h * dpr); canvas.style.width = w + "px"; canvas.style.height = h + "px"; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); createOrbs(); last = 0; } function createOrbs() { const count = getOrbCount(); orbs = new Array(count); for (let i = 0; i < count; i++) { const r = Math.random() * (R_MAX - R_MIN) + R_MIN; const color = Math.random() > 0.5 ? PINK : BLUE; orbs[i] = { x: Math.random() * w, y: Math.random() * h, r, a: Math.random() * 0.15 + 0.4, vx: (Math.random() - 0.5) * SPEED, vy: (Math.random() - 0.5) * SPEED, color: color }; } } function step() { ctx.clearRect(0, 0, w, h); for (let i = 0; i < orbs.length; i++) { const o = orbs[i]; o.x += o.vx; o.y += o.vy; const margin = o.r * 3; if (o.x < -margin) o.x = w + margin; else if (o.x > w + margin) o.x = -margin; if (o.y < -margin) o.y = h + margin; else if (o.y > h + margin) o.y = -margin; ctx.beginPath(); ctx.arc(o.x, o.y, o.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${o.color[0]}, ${o.color[1]}, ${o.color[2]}, ${o.a})`; ctx.fill(); } } function tick(now) { if (document.hidden) { raf = 0; return; } if (!last) last = now; if (now - last >= 16) { last = now; step(); } raf = requestAnimationFrame(tick); } let resizeTimer; window.addEventListener("resize", () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(resize, 150); }, { passive: true }); document.addEventListener("visibilitychange", () => { if (document.hidden) { cancelAnimationFrame(raf); raf = 0; } else if (!raf) { last = 0; raf = requestAnimationFrame(tick); } }); resize(); raf = requestAnimationFrame(tick); }); })();