1// ð webglAurora.js - subtle aurora overlay (background layer) 2(() => { 3 if (window.__webglAuroraInitialized) return; 4 5 // --- container --- 6 // Use existing #aurora-overlay div instead of creating new one 7 const auroraContainer = document.getElementById("aurora-overlay"); 8 if (!auroraContainer) { 9 console.warn("Aurora overlay div not found"); 10 return; 11 } 12 13 if (typeof THREE === 'undefined') { 14 console.warn('webglAurora: THREE is not loaded'); 15 return; 16 } 17 18 if (auroraContainer.querySelector('canvas[data-site-bg="aurora"]')) { 19 window.__webglAuroraInitialized = true; 20 return; 21 } 22 23 window.__webglAuroraInitialized = true; 24 25 // --- THREE.js scene --- 26 const scene = new THREE.Scene(); 27 const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 28 function readViewportCss() { 29 if (window.PupwifeViewportSize && typeof window.PupwifeViewportSize.css === 'function') { 30 return window.PupwifeViewportSize.css(); 31 } 32 return { width: window.innerWidth, height: window.innerHeight }; 33 } 34 35 function readViewportDpr() { 36 if (window.PupwifeViewportSize && typeof window.PupwifeViewportSize.pixelRatio === 'function') { 37 return window.PupwifeViewportSize.pixelRatio(2); 38 } 39 return window.devicePixelRatio; 40 } 41 42 const viewport = readViewportCss(); 43 const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); 44 renderer.setSize(viewport.width, viewport.height); 45 renderer.setPixelRatio(readViewportDpr()); 46 renderer.domElement.dataset.siteBg = 'aurora'; 47 auroraContainer.appendChild(renderer.domElement); 48 49 // --- shader material --- 50 const auroraMaterial = new THREE.ShaderMaterial({ 51 uniforms: { 52 uTime: { value: 0 }, 53 uFadeIn: { value: 0.0 }, // Fade in progress (0-1) 54 uScrollFade: { value: 1.0 }, // Scroll-based fade (1 = fully visible, 0 = hidden) 55 uAccentMix: { value: 0.0 }, 56 uAccentColor: { value: new THREE.Vector3(0.4, 0.1, 0.5) } 57 }, 58 vertexShader: ` 59 varying vec2 vUv; 60 void main() { 61 vUv = uv; 62 gl_Position = vec4(position, 1.0); 63 } 64 `, 65 fragmentShader: ` 66 uniform float uTime; 67 uniform float uFadeIn; 68 uniform float uScrollFade; 69 uniform float uAccentMix; 70 uniform vec3 uAccentColor; 71 varying vec2 vUv; 72 73 vec3 auroraColor(float t) { 74 vec3 purple = vec3(0.4, 0.1, 0.5); 75 vec3 teal = vec3(0.2, 0.6, 0.5); 76 vec3 base = mix(purple, teal, t); 77 vec3 accent = mix(base, uAccentColor, 0.55); 78 return mix(base, accent, uAccentMix); 79 } 80 81 void main() { 82 // subtle horizontal wave, low frequency for natural variation 83 float wave = sin(vUv.x * 2.0 + uTime * 0.1) * 0.5 + 0.5; 84 85 // slight vertical wiggle for movement 86 float verticalWave = sin(vUv.y * 10.0 + uTime * 0.2) * 0.05; 87 88 // vertical fade: top visible -> bottom transparent 89 float fade = smoothstep(0.0, 1.0, vUv.y + verticalWave); 90 91 vec3 color = auroraColor(wave) * fade; 92 93 // Apply vertical fade, fade-in animation, and scroll-based fade 94 gl_FragColor = vec4(color, fade * 0.8 * uFadeIn * uScrollFade);
94 // combine all fade factors 95 } 96 `, 97 transparent: true 98 }); 99 100 // --- plane --- 101 const plane = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), auroraMaterial); 102 // Start with the plane completely transparent 103 plane.visible = false; 104 scene.add(plane); 105 106 // Fade in control 107 let fadeStartTime = 0; 108 const FADE_DURATION = 1.5; // seconds (matching caustics timing) 109 110 // Expose function to show aurora with fade 111 window.__webglShowAurora = () => { 112 plane.visible = true; 113 fadeStartTime = auroraMaterial.uniforms.uTime.value; 114 }; 115 116 // Auto-start fade in after a brief delay 117 setTimeout(() => { 118 if (window.__webglShowAurora) window.__webglShowAurora(); 119 }, 100); 120 121 // --- resize handler --- 122 window.addEventListener("resize", () => { 123 const vp = readViewportCss(); 124 renderer.setSize(vp.width, vp.height); 125 renderer.setPixelRatio(readViewportDpr()); 126 }); 127 128 // --- scroll fade handling --- 129 const FADE_START = 100; // scroll position where fade starts 130 const FADE_DISTANCE = 500; // distance over which fade occurs 131 let lastScrollY = window.scrollY; 132 let targetScrollFade = 1.0; 133 let currentScrollFade = 1.0; 134 135 function updateScrollFade() { 136 // Smoothly interpolate towards target value 137 const scrollDiff = targetScrollFade - currentScrollFade; 138 currentScrollFade += scrollDiff * 0.1; // Smooth easing 139 140 // Update shader uniform 141 auroraMaterial.uniforms.uScrollFade.value = currentScrollFade; 142 } 143 144 // Smooth scroll handling with requestAnimationFrame 145 function onScroll() { 146 const scrollY = window.scrollY; 147 // Calculate target fade value based on scroll position 148 targetScrollFade = 1.0 - Math.min(Math.max((scrollY - FADE_START) / FADE_DISTANCE, 0), 1); 149 lastScrollY = scrollY; 150 } 151 152 // Add scroll listener 153 window.addEventListener('scroll', onScroll, { passive: true }); 154 155 // --- animation --- 156 const clock = new THREE.Clock(); 157 function animate() { 158 const currentTime = clock.getElapsedTime(); 159 auroraMaterial.uniforms.uTime.value = currentTime; 160 161 // Handle fade in animation 162 if (plane.visible && fadeStartTime > 0) { 163 const fadeProgress = Math.min(1.0, (currentTime - fadeStartTime) / FADE_DURATION); 164 auroraMaterial.uniforms.uFadeIn.value = fadeProgress; 165 if (fadeProgress >= 1.0) { 166 fadeStartTime = 0; // Animation complete 167 } 168 } 169 170 // Update scroll-based fade 171 updateScrollFade(); 172 173 accentCurrent += (accentTarget - accentCurrent) * 0.04; 174 auroraMaterial.uniforms.uAccentMix.value = accentCurrent; 175 176 renderer.render(scene, camera); 177 requestAnimationFrame(animate); 178 } 179 180 function parseCssColorToVec3(color) { 181 if (!color || typeof color !== 'string') return null; 182 var c = color.trim(); 183 var match = c.match(/rgba?\(([^)]+)\)/); 184 if (match) { 185 var parts = match[1].split(',').map(function (p) { return parseFloat(p.trim()); }); 186 return new THREE.Vector3(parts[0] / 255, parts[1] / 255, parts[2] / 255); 187 } 188 if (c.charAt(0) === '#') { 189 c = c.slice(1); 190 if (c.length === 3) { 191 c = c[0] + c[0] + c[1] + c[1] + c[2] + c[2]; 192 } 193 if (c.length === 6) { 194 return new THREE.Vector3( 195 parseInt(c.slice(0, 2), 16) / 255, 196 parseInt(c.slice(2, 4), 16) / 255,
197 parseInt(c.slice(4, 6), 16) / 255 198 ); 199 } 200 } 201 return null; 202 } 203 204 var accentTarget = 0; 205 var accentCurrent = 0; 206 207 window.__webglSetAuroraAccent = function (cssColor) { 208 var vec = parseCssColorToVec3(cssColor); 209 if (vec) { 210 auroraMaterial.uniforms.uAccentColor.value.copy(vec); 211 accentTarget = 0.62; 212 } else { 213 accentTarget = 0; 214 } 215 }; 216 217 animate(); 218})();
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.