/* Billboard AR Experience — UI kit FX (hand-ported, no deps) Premium motion inspired by 21st.dev (shaders + tilt), implemented in plain WebGL + React. Everything degrades gracefully and respects prefers-reduced-motion. Loaded BEFORE the section files so and are available as globals (same pattern as Reveal/Section). */ const { useEffect: useFxEffect, useRef: useFxRef, useState: useFxState } = React; const __fxReduce = () => window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; /* ============================================================ ShaderBG — flowing azul→violeta→magenta gradient (WebGL). Sits behind the hero content, blended over the photo. If WebGL is unavailable the canvas simply renders nothing and the CSS bloom underneath carries the look. ============================================================ */ function ShaderBG({ opacity = 0.9, style }) { const ref = useFxRef(null); useFxEffect(() => { const canvas = ref.current; if (!canvas) return; const gl = canvas.getContext("webgl", { antialias: true, alpha: true, premultipliedAlpha: false }); if (!gl) return; const VERT = "attribute vec2 p; void main(){ gl_Position = vec4(p, 0.0, 1.0); }"; const FRAG = [ "precision highp float;", "uniform vec2 u_res; uniform float u_t;", "vec3 BLUE = vec3(0.176, 0.357, 1.000);", "vec3 VIOL = vec3(0.545, 0.239, 1.000);", "vec3 MAG = vec3(0.878, 0.129, 0.541);", "vec3 CORAL = vec3(1.000, 0.302, 0.427);", "float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }", "float noise(vec2 p){", " vec2 i = floor(p), f = fract(p);", " vec2 u = f * f * (3.0 - 2.0 * f);", " return mix(mix(hash(i + vec2(0.0,0.0)), hash(i + vec2(1.0,0.0)), u.x),", " mix(hash(i + vec2(0.0,1.0)), hash(i + vec2(1.0,1.0)), u.x), u.y);", "}", "float fbm(vec2 p){ float v = 0.0, a = 0.5; for(int i = 0; i < 5; i++){ v += a * noise(p); p *= 2.0; a *= 0.5; } return v; }", "void main(){", " vec2 uv = gl_FragCoord.xy / u_res.xy;", " vec2 q = uv; q.x *= u_res.x / u_res.y;", " float t = u_t * 0.05;", " float n = fbm(q * 2.2 + vec2(t, t * 0.6));", " float m = fbm(q * 1.3 - vec2(t * 0.4, t));", " float g = uv.y * 0.55 + n * 0.85 + m * 0.45;", " vec3 col = mix(BLUE, VIOL, smoothstep(0.10, 0.62, g));", " col = mix(col, MAG, smoothstep(0.45, 0.98, g));", " col = mix(col, CORAL, smoothstep(0.82, 1.20, g) * 0.45);", " float bloom = smoothstep(1.0, 0.0, uv.y);", // brightest at the bottom " col *= 0.50 + 0.95 * bloom;", " float alpha = 0.30 + 0.70 * bloom;", " gl_FragColor = vec4(col, alpha);", "}" ].join("\n"); const compile = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s; }; const prog = gl.createProgram(); gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT)); gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG)); gl.linkProgram(prog); if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return; gl.useProgram(prog); const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW); const loc = gl.getAttribLocation(prog, "p"); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0); const uRes = gl.getUniformLocation(prog, "u_res"); const uT = gl.getUniformLocation(prog, "u_t"); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); const dpr = Math.min(window.devicePixelRatio || 1, 2); const resize = () => { const w = canvas.clientWidth, h = canvas.clientHeight; if (!w || !h) return; canvas.width = Math.round(w * dpr); canvas.height = Math.round(h * dpr); gl.viewport(0, 0, canvas.width, canvas.height); }; const draw = (t) => { gl.uniform2f(uRes, canvas.width, canvas.height); gl.uniform1f(uT, t); gl.drawArrays(gl.TRIANGLES, 0, 3); }; let raf = null, start = null; resize(); window.addEventListener("resize", resize); if (__fxReduce()) { draw(8.0); // a single, pleasant static frame } else { const loop = (ts) => { if (start === null) start = ts; draw((ts - start) / 1000); raf = requestAnimationFrame(loop); }; raf = requestAnimationFrame(loop); } return () => { if (raf) cancelAnimationFrame(raf); window.removeEventListener("resize", resize); }; }, []); return (