neu-fx.js (7119B)
1 (function() { 2 'use strict'; 3 4 var root = document.documentElement; 5 var rootStyle = root.style; 6 var tmx = 0.5, tmy = 0.5, smx = 0.5, smy = 0.5; 7 8 // ===== WebGL Background ===== 9 var canvas = document.createElement('canvas'); 10 canvas.style.cssText = 'position:fixed;inset:0;z-index:-1;width:100%;height:100%'; 11 document.body.prepend(canvas); 12 13 var gl = canvas.getContext('webgl', { alpha: false, antialias: false }); 14 if (!gl) { canvas.remove(); return; } 15 16 document.body.style.background = 'none'; 17 18 var VERT = 'attribute vec2 p;void main(){gl_Position=vec4(p,0,1);}'; 19 var FRAG = [ 20 'precision mediump float;', 21 'uniform float t;', 22 'uniform vec2 r,m;', 23 'uniform vec3 cb,cl,cd,ca,cm,ct;', 24 '', 25 'float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}', 26 'float noise(vec2 p){', 27 ' vec2 i=floor(p),f=fract(p);', 28 ' f=f*f*(3.-2.*f);', 29 ' return mix(mix(hash(i),hash(i+vec2(1,0)),f.x),', 30 ' mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);', 31 '}', 32 '', 33 'void main(){', 34 ' vec2 u=gl_FragCoord.xy/r;', 35 ' float n=noise(u*3.+t*.08)*.015;', 36 ' vec2 du=u+n;', 37 ' vec2 d=(du-vec2(.5,.85))*vec2(1.,r.x/r.y);', 38 ' float dist=length(d);', 39 ' vec3 c=mix(cl,cb,smoothstep(.0,.35,dist));', 40 ' c=mix(c,cd,smoothstep(.35,.65,dist));', 41 ' vec2 p1=vec2(.25+sin(t*.2)*.2,.7+cos(t*.15)*.2);', 42 ' vec2 p2=vec2(.75+cos(t*.18)*.15,.3+sin(t*.22)*.15);', 43 ' vec2 p3=vec2(.5+sin(t*.12)*.25,.5+cos(t*.1)*.2);', 44 ' c+=ca*exp(-8.*length(u-p1))*.08;', 45 ' c+=cm*exp(-10.*length(u-p2))*.05;', 46 ' c+=ct*exp(-7.*length(u-p3))*.04;', 47 ' c+=ca*exp(-6.*length(u-m))*.05;', 48 ' float vig=1.-smoothstep(.4,1.2,length(u-.5));', 49 ' c*=.88+.12*vig;', 50 ' gl_FragColor=vec4(c,1.);', 51 '}' 52 ].join('\n'); 53 54 function mk(type, src) { 55 var s = gl.createShader(type); 56 gl.shaderSource(s, src); 57 gl.compileShader(s); 58 return s; 59 } 60 61 var prog = gl.createProgram(); 62 gl.attachShader(prog, mk(gl.VERTEX_SHADER, VERT)); 63 gl.attachShader(prog, mk(gl.FRAGMENT_SHADER, FRAG)); 64 gl.linkProgram(prog); 65 gl.useProgram(prog); 66 67 var buf = gl.createBuffer(); 68 gl.bindBuffer(gl.ARRAY_BUFFER, buf); 69 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW); 70 var a = gl.getAttribLocation(prog, 'p'); 71 gl.enableVertexAttribArray(a); 72 gl.vertexAttribPointer(a, 2, gl.FLOAT, false, 0, 0); 73 74 var uf = function(n) { return gl.getUniformLocation(prog, n); }; 75 var uT = uf('t'), uR = uf('r'), uM = uf('m'); 76 var uCB = uf('cb'), uCL = uf('cl'), uCD = uf('cd'); 77 var uCA = uf('ca'), uCM = uf('cm'), uCT = uf('ct'); 78 79 var raf; 80 81 function hex(h) { 82 var n = parseInt(h.replace('#', ''), 16); 83 return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255]; 84 } 85 86 function hexToRgb(h) { 87 return [parseInt(h.slice(1,3),16), parseInt(h.slice(3,5),16), parseInt(h.slice(5,7),16)]; 88 } 89 90 function syncColors() { 91 var s = getComputedStyle(root); 92 var g = function(v) { return hex(s.getPropertyValue(v).trim()); }; 93 gl.uniform3fv(uCB, g('--base')); 94 gl.uniform3fv(uCL, g('--base-light')); 95 gl.uniform3fv(uCD, g('--base-dark')); 96 gl.uniform3fv(uCA, g('--accent')); 97 gl.uniform3fv(uCM, g('--magenta')); 98 gl.uniform3fv(uCT, g('--teal')); 99 var rgb = hexToRgb(s.getPropertyValue('--accent').trim()); 100 spot.style.background = 'radial-gradient(circle,rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.07) 0%,transparent 70%)'; 101 } 102 103 function resize() { 104 var dpr = Math.min(devicePixelRatio, 1.5); 105 canvas.width = innerWidth * dpr; 106 canvas.height = innerHeight * dpr; 107 gl.viewport(0, 0, canvas.width, canvas.height); 108 } 109 110 addEventListener('resize', resize); 111 resize(); 112 113 new MutationObserver(syncColors).observe( 114 root, { attributes: true, attributeFilter: ['data-theme'] } 115 ); 116 117 function loop(t) { 118 smx += (tmx - smx) * 0.03; 119 smy += (tmy - smy) * 0.03; 120 gl.uniform1f(uT, t * .001); 121 gl.uniform2f(uR, canvas.width, canvas.height); 122 gl.uniform2f(uM, smx, smy); 123 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 124 raf = requestAnimationFrame(loop); 125 } 126 127 // ===== Cursor Spotlight ===== 128 var spot = document.createElement('div'); 129 spot.style.cssText = 'position:fixed;pointer-events:none;z-index:9998;width:500px;height:500px;border-radius:50%;transform:translate(-50%,-50%);will-change:left,top;opacity:0;transition:opacity 0.4s'; 130 document.body.appendChild(spot); 131 132 syncColors(); 133 raf = requestAnimationFrame(loop); 134 135 document.addEventListener('visibilitychange', function() { 136 if (document.hidden) cancelAnimationFrame(raf); 137 else raf = requestAnimationFrame(loop); 138 }); 139 140 // ===== Unified Mouse Handler ===== 141 var lastTilt = null; 142 var queued = false; 143 144 document.addEventListener('mousemove', function(e) { 145 var nx = e.clientX / innerWidth; 146 var ny = e.clientY / innerHeight; 147 tmx = nx; 148 tmy = 1 - ny; 149 150 spot.style.left = e.clientX + 'px'; 151 spot.style.top = e.clientY + 'px'; 152 spot.style.opacity = '1'; 153 154 var el = e.target.closest('.stat-card, .mode-card'); 155 if (lastTilt && lastTilt !== el) { 156 lastTilt.style.removeProperty('--ry'); 157 lastTilt.style.removeProperty('--rx'); 158 } 159 lastTilt = el; 160 if (el) { 161 var rect = el.getBoundingClientRect(); 162 var cx = (e.clientX - rect.left) / rect.width - 0.5; 163 var cy = (e.clientY - rect.top) / rect.height - 0.5; 164 el.style.setProperty('--ry', (cx * 5) + 'deg'); 165 el.style.setProperty('--rx', (-cy * 5) + 'deg'); 166 } 167 168 if (queued) return; 169 queued = true; 170 requestAnimationFrame(function() { 171 queued = false; 172 var lx = (nx - .5) * 2, ly = (ny - .5) * 2; 173 var v = function(o, b) { 174 return (-lx*o).toFixed(1) + 'px ' + (-ly*o).toFixed(1) + 'px ' + b + 'px var(--shadow-dark),' + 175 (lx*o).toFixed(1) + 'px ' + (ly*o).toFixed(1) + 'px ' + b + 'px var(--shadow-light)'; 176 }; 177 rootStyle.setProperty('--neu-raised', v(4, 10)); 178 rootStyle.setProperty('--neu-raised-sm', v(3, 7)); 179 rootStyle.setProperty('--neu-raised-lg', v(6, 14)); 180 }); 181 }); 182 183 document.addEventListener('mouseleave', function() { 184 spot.style.opacity = '0'; 185 if (lastTilt) { 186 lastTilt.style.removeProperty('--ry'); 187 lastTilt.style.removeProperty('--rx'); 188 lastTilt = null; 189 } 190 rootStyle.removeProperty('--neu-raised'); 191 rootStyle.removeProperty('--neu-raised-sm'); 192 rootStyle.removeProperty('--neu-raised-lg'); 193 }); 194 195 // ===== Grain Texture ===== 196 var gc = document.createElement('canvas'); 197 gc.width = gc.height = 128; 198 var gx = gc.getContext('2d'); 199 var id = gx.createImageData(128, 128); 200 for (var i = 0; i < id.data.length; i += 4) { 201 id.data[i] = id.data[i+1] = id.data[i+2] = Math.random() * 255 | 0; 202 id.data[i+3] = 18; 203 } 204 gx.putImageData(id, 0, 0); 205 var ov = document.createElement('div'); 206 ov.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:9999;background:url(' + gc.toDataURL() + ');mix-blend-mode:overlay;opacity:.5'; 207 document.body.appendChild(ov); 208 })();