PageSourceSearch

https://pupwife.com/scripts/dependencies/webglAurora.js

js pupwife.com collected 2026-09-30 15:05:19 UTC 7,077 bytes, 218 lines download raw bytes

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.