1(() => { 2 "use strict"; 3 const canvas = document.querySelector(".nebula-flow"); 4 const hero = document.querySelector("#hero"); 5 const source = document.querySelector(".hero-environment > img"); 6 const reduced = matchMedia("(prefers-reduced-motion: reduce)"); 7 const compact = matchMedia("(max-width: 980px)"); 8 if (!canvas || !source || !hero || navigator.connection?.saveData) return; 9 const gl = canvas.getContext("webgl", { alpha: true, antialias: false, depth: false, powerPreference: "low-power", premultipliedAlpha: false }); 10 if (!gl) return; 11 12 const vertexSource = `attribute vec2 a_position; 13 varying vec2 v_uv; 14 void main(){v_uv=vec2((a_position.x+1.0)*0.5,(1.0-a_position.y)*0.5);gl_Position=vec4(a_position,0.0,1.0);}`; 15 const fragmentSource = `precision mediump float; 16 varying vec2 v_uv; 17 uniform sampler2D u_image; 18 uniform vec2 u_cover; 19 uniform float u_time; 20 uniform vec3 u_shock; 21 void main(){ 22 vec2 uv=(v_uv-0.5)*u_cover+0.5; 23 vec4 still=texture2D(u_image,uv); 24 float mask=smoothstep(0.32,0.63,uv.x)*(1.0-smoothstep(0.38,0.575,uv.y)); 25 mask*=smoothstep(0.015,0.13,still.g-still.r); 26 float t=u_time; 27 // Follow the luminous texture already painted into the rim. 28 float rim=mask*smoothstep(0.10,0.27,still.g-still.r)*smoothstep(0.57,0.9,still.g); 29 vec2 radial=normalize(vec2((uv.x-0.70)*2.35,(uv.y+0.07)*0.82)); 30 vec2 tangent=vec2(-radial.y,radial.x); 31 float current=sin(uv.y*29.0+uv.x*11.0-t*1.3); 32 float filament=sin(uv.y*83.0-uv.x*26.0+t*2.1); 33 vec2 flow=vec2(sin(uv.y*10.0+t*0.19)+0.5*cos(uv.x*14.0-t*0.11), 34 cos(uv.x*9.0+t*0.13)+0.45*sin(uv.y*16.0-t*0.17)); 35 flow*=0.0065*mask; 36 flow+=tangent*(current*0.007+filament*0.002)*rim; 37 flow+=radial*sin(uv.y*43.0+t*1.7)*0.0025*rim; 38 vec2 delta=uv-u_shock.xy; 39 float wave=exp(-pow((length(delta)-u_shock.z*0.064)*68.0,2.0))*exp(-u_shock.z*0.85); 40 flow+=normalize(delta+vec2(0.0001))*wave*0.008*mask; 41 vec3 cloud=texture2D(u_image,uv+flow).rgb; 42 float light=sin(uv.x*7.0+uv.y*5.0-t*0.25)*0.022; 43 cloud+=vec3(0.05,0.7,0.72)*light*mask; 44 float surge=pow(0.5+0.5*sin(uv.y*17.0+uv.x*8.0-t*1.1),4.0); 45 cloud+=vec3(0.22,0.75,0.70)*rim*(0.035+surge*0.17+filament*0.024); 46 gl_FragColor=vec4(cloud,mask*0.94); 47 }`; 48
49 function compile(type, code) { 50 const shader = gl.createShader(type); 51 gl.shaderSource(shader, code); gl.compileShader(shader); 52 if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { gl.deleteShader(shader); return null; } 53 return shader; 54 } 55 const vs = compile(gl.VERTEX_SHADER, vertexSource); 56 const fs = compile(gl.FRAGMENT_SHADER, fragmentSource); 57 if (!vs || !fs) return; 58 const program = gl.createProgram(); 59 gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); 60 gl.deleteShader(vs); gl.deleteShader(fs); 61 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return; 62 gl.useProgram(program); 63 const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 64 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW); 65 const position = gl.getAttribLocation(program, "a_position"); 66 gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0); 67 const coverUniform = gl.getUniformLocation(program, "u_cover"); 68 const timeUniform = gl.getUniformLocation(program, "u_time"); 69 const shockUniform = gl.getUniformLocation(program, "u_shock"); 70 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); 71 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 72 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 73 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 74 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 75 gl.uniform1i(gl.getUniformLocation(program, "u_image"), 0); 76 let ready = false, visible = true, enabled = !reduced.matches; 77 let raf = 0, last = 0, clock = 0, rendered = 0, lost = false; 78 let shock = { x: .8, y: .2, born: -100 }; 79 window.addEventListener("scalz:atmosphere-entry", e => { shock = { x: e.detail.x, y: e.detail.y, born: clock }; }); 80 81 function resize() { 82 const { width, height } = hero.getBoundingClientRect(); 83 const scale = Math.min(1.25, devicePixelRatio || 1, 1800 / Math.max(width, height)); 84 canvas.width = Math.max(1, Math.round(width * scale)); 85 canvas.height = Math.max(1, Math.round(height * scale)); 86 gl.viewport(0, 0, canvas.width, canvas.height); 87 const imageRatio = 1.5, screenRatio = width / height; 88 gl.uniform2f(coverUniform, Math.min(1, screenRatio / imageRatio), Math.min(1, imageRatio / screenRatio)); 89 } 90 function frame(now) { 91 if (!ready || !enabled || !visible || document.hidden || reduced.matches || compact.matches || lost) { raf = 0; last = 0; return; } 92 if (last) clock += Math.min(100, now - last) / 1000; 93 last = now; 94 if (now - rendered > 32) { 95 gl.uniform1f(timeUniform, clock); gl.uniform3f(shockUniform, shock.x, shock.y, Math.min(30,clock-shock.born)); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); rendered = now; 96 } 97 raf = requestAnimationFrame(frame); 98 } 99 function sync() { 100 if (raf) cancelAnimationFrame(raf); 101 raf = 0; last = 0; 102 if (ready && enabled && visible && !document.hidden && !reduced.matches && !compact.matches && !lost) raf = requestAnimationFrame(frame); 103 } 104 function load() { 105 if (!source.naturalWidth) return; 106 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source); 107 ready = true; canvas.dataset.renderer = "webgl-portal-flow"; resize(); sync(); 108 } 109 source.complete ? load() : source.addEventListener("load", load, { once: true }); 110 new ResizeObserver(resize).observe(hero); 111 new IntersectionObserver(entries => { visible = entries[0].isIntersecting; sync(); }, { threshold: .03 }).observe(hero); 112 window.addEventListener("scalz:preview-effects", e => { enabled = Boolean(e.detail.enabled); sync(); }); 113 document.addEventListener("visibilitychange", sync); 114 reduced.addEventListener("change", () => { enabled = !reduced.matches && !document.documentElement.classList.contains("preview-effects-off"); sync(); }); 115 compact.addEventListener("change", sync); 116 canvas.addEventListener("webglcontextlost", e => { e.preventDefault(); lost = true; sync(); canvas.style.opacity = "0"; }); 117})();
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.