1// Animated Background with Light Rays, Stars, and Shooting Stars 2(() => { 3 function initCanvas() { 4 const canvas = document.getElementById('bg-rays'); 5 if (!canvas) return; 6 const ctx = canvas.getContext('2d', { alpha: true, desynchronized: true }); 7 8 // Performance / vibe knobs 9 const FPS = 24; 10 const DPR_CAP = 1.6; 11 const STAR_DENSITY = 0.00006; 12 const RAYS = 9; 13 const RAY_SOFTNESS = 0.20; 14 const RAY_INTENSITY = 0.22; 15 const DRIFT_SPEED = 0.12; 16 const TWINKLE = 0.35; 17 18 // Colors (muted purple/grey) 19 const PUR = [160, 120, 255]; 20 const PUR2 = [120, 80, 210]; 21 const GRY = [220, 220, 235]; 22 23 const clamp = (v, a, b) => Math.max(a, Math.min(b, v)); 24 const mix = (a, b, t) => a + (b - a) * t; 25 const rgba = (rgb, a) => `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${a})`; 26 27 let DPR = Math.min(DPR_CAP, window.devicePixelRatio || 1); 28 let W = 0, H = 0; 29 let running = true; 30 let initialized = false; 31 let rayIntensity = 0; 32 let targetIntensity = 0; 33 let animationStarted = false; 34 35 document.addEventListener("visibilitychange", () => { 36 running = !document.hidden; 37 }); 38 39 function resize() { 40 const section = canvas.parentElement; 41 if (!section) return; 42 43 // Get computed styles to ensure accurate dimensions 44 const computedStyle = window.getComputedStyle(section); 45 const paddingTop = parseFloat(computedStyle.paddingTop) || 0; 46 const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0; 47 48 // Force layout recalculation 49 section.offsetHeight; 50 51 const rect = section.getBoundingClientRect(); 52 DPR = Math.min(DPR_CAP, window.devicePixelRatio || 1); 53 54 // Ensure minimum stable dimensions 55 const width = Math.max(rect.width, window.innerWidth, 800); 56 const height = Math.max(rect.height, section.scrollHeight, paddingTop + paddingBottom + 400, 500); 57 58 W = canvas.width = Math.floor(width * DPR); 59 H = canvas.height = Math.floor(height * DPR); 60 canvas.style.width = width + "px"; 61 canvas.style.height = height + "px"; 62 63 buildStars(); 64 buildRays(); 65 66 if (!initialized) { 67 initialized = true; 68 requestAnimationFrame(loop); 69 } 70 } 71 72 // Multiple event listeners for stability 73 addEventListener("resize", resize, { passive: true }); 74 addEventListener("load", resize, { passive: true }); 75 76 // Intersection Observer to detect when section is visible and trigger animation 77 const observer = new IntersectionObserver((entries) => { 78 entries.forEach(entry => { 79 if (entry.isIntersecting && !animationStarted) { 80 targetIntensity = RAY_INTENSITY; 81 animationStarted = true; 82 setTimeout(resize, 100); 83 } 84 }); 85 }, { threshold: 0.3 }); 86 observer.observe(canvas.parentElement); 87 88 // Stars 89 let stars = []; 90 let shootingStars = []; 91 function rand(n) { 92 const s = Math.sin(n) * 10000; 93 return s - Math.floor(s); 94 } 95 96 function buildStars() { 97 const count = Math.floor(W * H / (DPR * DPR) * STAR_DENSITY); 98 stars = new Array(count); 99 for (let i = 0; i < count; i++) { 100 const r = rand(i * 12.9898 + 78.233); 101 const z = rand(i * 93.9898 + 12.233); 102 const size = (0.6 + 1.8 * Math.pow(z, 2.2)) * DPR; 103 stars[i] = { 104 x: rand(i * 1.3 + 9.1) * W, 105 y: rand(i * 2.1 + 3.7) * H, 106 z, 107 size, 108 tw: rand(i * 7.7 + 5.5) * Math.PI * 2 109 }; 110 } 111 } 112 113 // Rays 114 let rays = []; 115 function buildRays() { 116 rays = []; 117 const cx = W * 0.5; 118 const cy = H * 0.5; 119 for (let i = 0; i < RAYS; i++) { 120 const a = mix(-0.55, 0.55, (i + 0.5) / RAYS); 121 const w = mix(0.10, 0.22, rand(i * 4.2 + 1.1)); 122 const len = mix(0.75, 1.10, rand(i * 2.6 + 9.7)) * Math.max(W, H); 123 const phase = rand(i * 8.8 + 2.2) * Math.PI * 2; 124 const tintMix = rand(i * 5.5 + 0.9); 125 const col = tintMix < 0.35 ? PUR : (tintMix < 0.70 ? PUR2 : GRY); 126 127 rays.push({ cx, cy, a, w, len, phase, col }); 128 } 129 } 130 131 // Rendering
132 function drawBackgroundBase() { 133 ctx.clearRect(0, 0, W, H); 134 } 135 136 function drawRays(time) { 137 const t = time * 0.001; 138 139 // Animate ray intensity 140 if (rayIntensity < targetIntensity) { 141 rayIntensity += 0.0055; 142 if (rayIntensity > targetIntensity) rayIntensity = targetIntensity; 143 } 144 145 for (const r of rays) { 146 const wob = 0.15 * Math.sin(t * 0.25 + r.phase); 147 const width = r.w * (1 + 0.35 * Math.sin(t * 0.22 + r.phase * 1.3)); 148 const ang = r.a + wob; 149 150 const g = ctx.createRadialGradient(r.cx, r.cy, 0, r.cx, r.cy, r.len); 151 g.addColorStop(0, rgba(r.col, rayIntensity * 0.85)); 152 g.addColorStop(RAY_SOFTNESS, rgba(r.col, rayIntensity * 0.35)); 153 g.addColorStop(1, rgba(r.col, 0)); 154 155 ctx.save(); 156 ctx.fillStyle = g; 157 ctx.beginPath(); 158 ctx.moveTo(r.cx, r.cy); 159 ctx.arc(r.cx, r.cy, r.len, ang - width, ang + width); 160 ctx.closePath(); 161 ctx.fill(); 162 ctx.restore(); 163 } 164 } 165 166 function drawStars(time) { 167 const t = time * 0.001; 168 const vx = Math.cos(t * 0.05) * DRIFT_SPEED * DPR; 169 const vy = Math.sin(t * 0.05) * DRIFT_SPEED * DPR; 170 171 for (let i = 0; i < stars.length; i++) { 172 const s = stars[i]; 173 const sp = (0.15 + 1.05 * s.z); 174 s.x += vx * sp; 175 s.y += vy * sp; 176 177 if (s.x < -10) s.x = W + 10; 178 if (s.x > W + 10) s.x = -10; 179 if (s.y < -10) s.y = H + 10; 180 if (s.y > H + 10) s.y = -10; 181 182 const tw = 0.65 + TWINKLE * 0.35 * Math.sin(t * 0.9 + s.tw); 183 const a = (0.08 + 0.35 * Math.pow(s.z, 1.6)) * tw; 184 185 const tint = (i % 17 === 0) ? PUR : GRY; 186 ctx.fillStyle = rgba(tint, a); 187 188 ctx.beginPath(); 189 ctx.arc(s.x, s.y, s.size, 0, Math.PI * 2); 190 ctx.fill(); 191 } 192 } 193 194 function drawShootingStars(time) { 195 const t = time * 0.001; 196 197 // Create new shooting star occasionally 198 if (Math.random() < 0.008) { 199 shootingStars.push({ 200 x: Math.random() * W * 0.3, 201 y: -20, 202 vx: Math.random() * 4 + 2, 203 vy: Math.random() * 3 + 3, 204 life: 1, 205 decay: 0.012 206 }); 207 } 208 209 // Update and draw shooting stars 210 for (let i = shootingStars.length - 1; i >= 0; i--) { 211 const s = shootingStars[i]; 212 s.x += s.vx * DPR; 213 s.y += s.vy * DPR; 214 s.life -= s.decay; 215 216 if (s.life <= 0 || s.y > H + 50) { 217 shootingStars.splice(i, 1); 218 continue; 219 } 220 221 // Draw trail 222 const g = ctx.createLinearGradient(s.x, s.y, s.x - s.vx * 50, s.y - s.vy * 50); 223 g.addColorStop(0, rgba(PUR, s.life * 1.2)); 224 g.addColorStop(1, rgba(PUR, 0)); 225 226 ctx.strokeStyle = g; 227 ctx.lineWidth = 3; 228 ctx.beginPath(); 229 ctx.moveTo(s.x, s.y); 230 ctx.lineTo(s.x - s.vx * 50, s.y - s.vy * 50); 231 ctx.stroke(); 232 } 233 } 234 235 function drawVignette() { 236 const v = ctx.createRadialGradient(W * 0.55, H * 0.45, 0, W * 0.55, H * 0.45, Math.max(W, H) * 0.78); 237 v.addColorStop(0, 'rgba(0,0,0,0)'); 238 v.addColorStop(1, 'rgba(0,0,0,.42)'); 239 ctx.fillStyle = v; 240 ctx.fillRect(0, 0, W, H); 241 } 242 243 // FPS cap loop 244 const frameInterval = 1000 / FPS; 245 let lastDraw = 0; 246 247 function loop(now) { 248 requestAnimationFrame(loop); 249 if (!running) return; 250 if (now - lastDraw < frameInterval) return; 251 lastDraw = now; 252 253 drawBackgroundBase(); 254 drawRays(now); 255 drawStars(now); 256 drawShootingStars(now); 257 drawVignette(); 258 } 259 260 // Initial setup with stable timing 261 resize(); 262 // Additional resize calls with delays to catch late layout changes 263 setTimeout(resize, 100); 264 setTimeout(resize, 300); 265 setTimeout(resize, 500); 266 } 267 268 // Enhanced initialization timing 269 if (document.readyState === 'loading') { 270 document.addEventListener('DOMContentLoaded', () => { 271 setTimeout(initCanvas, 50); 272 }); 273 } else { 274 setTimeout(initCanvas, 50); 275 } 276})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.