(() => { const canvas = document.getElementById("starfield"); if (!canvas) return; const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)")?.matches; if (reduceMotion) return; const ctx = canvas.getContext("2d", { alpha: true }); const cfg = { get perf(){ return (document.documentElement.dataset.perf || "auto").toLowerCase(); }, starCount(){ return this.perf === "high" ? 1100 : this.perf === "medium" ? 650 : this.perf === "low" ? 0 : 500; }, fps(){ return this.perf === "high" ? 60 : this.perf === "medium" ? 30 : this.perf === "low" ? 0 : 25; }, dpr(){ return this.perf === "high" ? Math.min(devicePixelRatio||1, 2) : 1; }, twinkle(){ return this.perf === "high" ? 0.45 : 0.35; }, }; let w=0,h=0,dpr=1,stars=[],mouseX=0,mouseY=0,last=performance.now(),acc=0; let lastPerf = ""; function rand(min,max){ return min + Math.random()*(max-min); } function newStar(randomZ=false){ return { x: rand(-1,1), y: rand(-1,1), z: randomZ ? Math.random() : 1, r: rand(0.6,1.7), t: Math.random()*Math.PI*2 }; } function initStars(){ const n = cfg.starCount(); stars = Array.from({length:n}, ()=>newStar(true)); } function resize(){ dpr = cfg.dpr(); w = Math.floor(window.innerWidth); h = Math.floor(window.innerHeight); 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); initStars(); } function project(s){ const cx=w*0.5, cy=h*0.5; const p = 1/(s.z*1.25); return { x: cx + s.x*cx*p, y: cy + s.y*cy*p, size: s.r*p }; } function tick(dt){ ctx.clearRect(0,0,w,h); const g = ctx.createRadialGradient(w*0.5,h*0.45,0,w*0.5,h*0.45,Math.min(w,h)*0.85); g.addColorStop(0,"rgba(66,245,255,0.04)"); g.addColorStop(0.5,"rgba(255,61,242,0.02)"); g.addColorStop(1,"rgba(0,0,0,0)"); ctx.fillStyle=g; ctx.fillRect(0,0,w,h); const mx=(mouseX-w*0.5)/(w*0.5), my=(mouseY-h*0.5)/(h*0.5); const speed = (cfg.perf==="high" ? 0.03 : 0.022); for(let i=0;iw+60||p.y<-60||p.y>h+60) continue; const tw = 0.7 + Math.sin(s.t)*cfg.twinkle()*0.25; const alpha = Math.min(0.95, (0.18 + (1-s.z)*0.85)*tw); const par = 0.22*(1-s.z); const x2 = p.x + mx*18*par; const y2 = p.y + my*12*par; ctx.fillStyle = `rgba(240,247,255,${alpha})`; ctx.beginPath(); ctx.arc(x2,y2,Math.max(0.7,p.size*0.55),0,Math.PI*2); ctx.fill(); } } function frame(now){ const perf = cfg.perf; if(perf !== lastPerf){ lastPerf = perf; resize(); } const fps = cfg.fps(); if(fps <= 0){ requestAnimationFrame(frame); return; } const dt = Math.min((now-last)/1000, 0.05); last = now; acc += dt; const step = 1/fps; while(acc >= step){ tick(step); acc -= step; } requestAnimationFrame(frame); } window.addEventListener("mousemove",(e)=>{ mouseX=e.clientX; mouseY=e.clientY; }, {passive:true}); window.addEventListener("resize", resize, {passive:true}); resize(); mouseX = window.innerWidth*0.5; mouseY = window.innerHeight*0.5; requestAnimationFrame(frame); })();