/* HBMIC sub-page effects: pastel background with WebGL cursor ripple + pastel sparkles (same feel as the main page). Load after #hbMain. Uses window.anime for sparkles when present. */ (function () { 'use strict'; var root = document.getElementById('hbMain'); if (!root) return; var RM = matchMedia('(prefers-reduced-motion: reduce)').matches; var mobile = matchMedia('(max-width: 767px)').matches; var host = document.createElement('div'); host.className = 'hb-fx'; host.setAttribute('aria-hidden', 'true'); host.style.cssText = 'position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden;background:#F7F6FC'; var src = document.createElement('canvas'), glc = document.createElement('canvas'); src.style.cssText = glc.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;display:block'; host.appendChild(src); host.appendChild(glc); root.insertBefore(host, root.firstChild); var ctx = src.getContext('2d'); var BLOBS = [ { c: '205,192,247', x: .88, y: .18, r: .55, sx: .08, sy: .07, p: 41 }, { c: '178,220,248', x: .78, y: .88, r: .60, sx: .09, sy: .08, p: 53 }, { c: '250,205,224', x: .98, y: .62, r: .42, sx: .07, sy: .07, p: 37 }, { c: '190,238,216', x: .10, y: .98, r: .40, sx: .08, sy: .06, p: 47 } ]; function draw2D(t) { var w = src.width, h = src.height, m = Math.max(w, h); ctx.fillStyle = '#F7F6FC'; ctx.fillRect(0, 0, w, h); for (var i = 0; i < BLOBS.length; i++) { var b = BLOBS[i], k = t / b.p * Math.PI * 2; var x = (b.x + Math.sin(k) * b.sx) * w, y = (b.y + Math.cos(k * .8) * b.sy) * h, r = b.r * m; var g = ctx.createRadialGradient(x, y, 0, x, y, r); g.addColorStop(0, 'rgba(' + b.c + ',.9)'); g.addColorStop(.55, 'rgba(' + b.c + ',.35)'); g.addColorStop(1, 'rgba(' + b.c + ',0)'); ctx.fillStyle = g; ctx.fillRect(0, 0, w, h); } // keep reading areas bright var wash = ctx.createLinearGradient(0, 0, w, 0); wash.addColorStop(0, 'rgba(255,255,255,.82)'); wash.addColorStop(mobile ? .9 : .55, 'rgba(255,255,255,.62)'); wash.addColorStop(1, 'rgba(255,255,255,.18)'); ctx.fillStyle = wash; ctx.fillRect(0, 0, w, h); } var gl = null, prog, tex, U = {}, N = 32, pts = new Float32Array(N * 4), ptI = 0; var mouse = { x: -9999, y: -9999, amt: 0, target: 0, last: 0 }; var VS = 'attribute vec2 p;varying vec2 v;void main(){v=p*.5+.5;gl_Position=vec4(p,0.,1.);}'; var FS = [ 'precision highp float;', 'uniform sampler2D u_tex;uniform vec2 u_res;uniform float u_time;uniform vec4 u_pts[32];uniform vec3 u_mouse;', 'varying vec2 v;', 'void main(){', ' vec2 px=v*u_res; vec2 disp=vec2(0.); float shade=0.;', ' for(int i=0;i<32;i++){', ' vec4 q=u_pts[i]; float age=u_time-q.z;', ' if(q.w<=0.||age<0.||age>2.8) continue;', ' vec2 d=px-q.xy; float dist=length(d)+.001;', ' float r=age*240.; float band=exp(-pow((dist-r)/80.,2.));', ' float amp=q.w*exp(-age*1.5)*smoothstep(0.,.18,age);', ' float w=sin((dist-r)*.05)*band*amp;', ' disp+=d/dist*w; shade+=w;', ' }', ' vec2 dm=px-u_mouse.xy; float g=exp(-dot(dm,dm)/(2.*160.*160.))*u_mouse.z;', ' disp+=-dm/160.*g*.55+vec2(sin(px.y*.03+u_time*1.6),cos(px.x*.03+u_time*1.3))*g*.45;', ' vec2 off=disp*13./u_res; vec2 ca=off*.5;', ' vec2 t=v+off;', ' vec3 col=vec3(texture2D(u_tex,t+ca).r,texture2D(u_tex,t).g,texture2D(u_tex,t-ca).b);', ' col+=shade*.045+g*.02;', ' gl_FragColor=vec4(clamp(col,0.,1.),1.);', '}' ].join('\n'); function sh(type, s) { var o = gl.createShader(type); gl.shaderSource(o, s); gl.compileShader(o); if (!gl.getShaderParameter(o, gl.COMPILE_STATUS)) throw new Error('shader'); return o; } try { if (RM) throw new Error('rm'); gl = glc.getContext('webgl', { alpha: false, antialias: false, premultipliedAlpha: false }); if (!gl) throw new Error('no webgl'); prog = gl.createProgram(); gl.attachShader(prog, sh(gl.VERTEX_SHADER, VS)); gl.attachShader(prog, sh(gl.FRAGMENT_SHADER, FS)); gl.linkProgram(prog); if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error('link'); gl.useProgram(prog); var buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW); var loc = gl.getAttribLocation(prog, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0); ['u_tex', 'u_res', 'u_time', 'u_pts', 'u_mouse'].forEach(function (n) { U[n] = gl.getUniformLocation(prog, n); }); tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.uniform1i(U.u_tex, 0); } catch (e) { gl = null; glc.style.display = 'none'; } var dpr = 1, cw = 1, ch = 1; function resize() { cw = Math.max(1, innerWidth); ch = Math.max(1, innerHeight); dpr = Math.min(window.devicePixelRatio || 1, 1.25); glc.width = Math.round(cw * dpr); glc.height = Math.round(ch * dpr); src.width = 360; src.height = Math.max(1, Math.round(360 * ch / cw)); if (gl) gl.viewport(0, 0, glc.width, glc.height); } function render(now) { var t = now / 1000; draw2D(RM ? 0 : t); if (!gl) return; gl.bindTexture(gl.TEXTURE_2D, tex); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, src); mouse.amt += (mouse.target - mouse.amt) * .06; if (now - mouse.last > 1600) mouse.target = Math.min(mouse.target, .35); gl.uniform2f(U.u_res, glc.width, glc.height); gl.uniform1f(U.u_time, t); gl.uniform4fv(U.u_pts, pts); gl.uniform3f(U.u_mouse, mouse.x * dpr, mouse.y * dpr, mouse.amt); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } var lastPt = { x: -1e4, y: -1e4, t: 0 }; function onPointer(e, force) { var x = e.clientX, y = ch - e.clientY; mouse.x = x; mouse.y = y; mouse.target = 1; mouse.last = performance.now(); var dx = x - lastPt.x, dy = y - lastPt.y, d = Math.sqrt(dx * dx + dy * dy), now = performance.now(); if (force || d > 46 || (d > 6 && now - lastPt.t > 110)) { var i = ptI * 4; pts[i] = x * dpr; pts[i + 1] = y * dpr; pts[i + 2] = now / 1000; pts[i + 3] = force ? 1 : Math.min(1, .35 + d / 120); ptI = (ptI + 1) % N; lastPt = { x: x, y: y, t: now }; } } resize(); window.addEventListener('resize', function () { resize(); if (RM) render(0); }); if (RM) { render(0); return; } (function loop(now) { requestAnimationFrame(loop); if (!document.hidden) render(now || 0); })(); window.addEventListener('pointermove', function (e) { onPointer(e, false); }, { passive: true }); window.addEventListener('pointerdown', function (e) { onPointer(e, true); }, { passive: true }); /* ---------- pastel sparkles ---------- */ var A = window.anime; var layer = root.querySelector('.hb-sparks'); if (!A || !layer) return; var COLORS = ['#C9B8F5', '#9FD3F7', '#F7B8D2', '#A8E6CF']; var alive = 0, last = { x: -1e4, y: -1e4, t: 0 }; function one(x, y, big) { if (alive > 46) return; var s = document.createElement('span'), size = big ? A.utils.random(9, 15) : A.utils.random(5, 11); s.className = 'hb-spark'; s.style.cssText = 'left:' + (x - size / 2) + 'px;top:' + (y - size / 2) + 'px;width:' + size + 'px;height:' + size + 'px;background:' + A.utils.randomPick(COLORS); layer.appendChild(s); alive++; A.animate(s, { x: A.utils.random(-26, 26) * (big ? 2 : 1), y: A.utils.random(-44, -8) * (big ? 1.6 : 1), rotate: A.utils.random(-120, 120), scale: [{ from: .2, to: 1, duration: 260, ease: 'outQuad' }, { to: 0, duration: 700, ease: 'inQuad' }], opacity: [{ from: 0, to: 1, duration: 200 }, { to: 0, duration: 760 }], onComplete: function () { s.remove(); alive--; } }); } window.hbBurst = function (x, y, n) { for (var i = 0; i < (n || 9); i++) one(x, y, true); }; window.addEventListener('pointermove', function (e) { if (e.pointerType !== 'mouse') return; var dx = e.clientX - last.x, dy = e.clientY - last.y, now = performance.now(); if (dx * dx + dy * dy < 196 || now - last.t < 28) return; last = { x: e.clientX, y: e.clientY, t: now }; one(e.clientX, e.clientY, false); }, { passive: true }); window.addEventListener('pointerdown', function (e) { if (e.pointerType !== 'mouse') window.hbBurst(e.clientX, e.clientY, 9); }, { passive: true }); })();