// ShaderAnimation — WebGL filament/glow shader, recolored to Eurekant brand blues. // Adaptado de un fragment shader genérico: en vez de canales RGB independientes, // se mezcla un degradé denim → picton controlado por la intensidad del patrón. function ShaderAnimation({ colorA = '#137dc5', colorB = '#2199ea', opacity = 0.55, style = {} }) { const containerRef = React.useRef(null); const stateRef = React.useRef(null); React.useEffect(() => { if (!containerRef.current || !window.THREE) return; const THREE = window.THREE; const container = containerRef.current; const vertexShader = ` void main() { gl_Position = vec4( position, 1.0 ); } `; const fragmentShader = ` precision highp float; uniform vec2 resolution; uniform float time; uniform vec3 colorA; uniform vec3 colorB; void main(void) { vec2 uv = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y); float t = time * 0.05; float lineWidth = 0.0014; float intensity = 0.0; for (int i = 0; i < 5; i++) { intensity += lineWidth * float(i * i) / abs(fract(t - 0.01 * float(i)) * 5.0 - length(uv) + mod(uv.x + uv.y, 0.2)); } float clampedIntensity = clamp(intensity, 0.0, 1.0); vec3 color = clamp(mix(colorA, colorB, clamp(uv.x * 0.5 + 0.5, 0.0, 1.0)) * clampedIntensity, 0.0, 1.0); gl_FragColor = vec4(color, clampedIntensity * 0.7); } `; const hexToVec3 = (hex) => { const c = hex.replace('#', ''); const r = parseInt(c.substring(0, 2), 16) / 255; const g = parseInt(c.substring(2, 4), 16) / 255; const b = parseInt(c.substring(4, 6), 16) / 255; return new THREE.Vector3(r, g, b); }; const camera = new THREE.Camera(); camera.position.z = 1; const scene = new THREE.Scene(); const geometry = new THREE.PlaneGeometry(2, 2); const uniforms = { time: { value: 1.0 }, resolution: { value: new THREE.Vector2() }, colorA: { value: hexToVec3(colorA) }, colorB: { value: hexToVec3(colorB) }, }; const material = new THREE.ShaderMaterial({ uniforms, vertexShader, fragmentShader, transparent: true, }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(renderer.domElement); const onResize = () => { const width = container.clientWidth; const height = container.clientHeight; renderer.setSize(width, height); uniforms.resolution.value.x = renderer.domElement.width; uniforms.resolution.value.y = renderer.domElement.height; }; onResize(); window.addEventListener('resize', onResize, false); let animationId = 0; let running = false; const animate = () => { if (!running) return; animationId = requestAnimationFrame(animate); uniforms.time.value += 0.022; renderer.render(scene, camera); }; renderer.render(scene, camera); // primer frame estático, sin arrancar el loop const io = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting && !running) { running = true; uniforms.time.value = 1.0; animate(); } }); }, { threshold: 0.2 }); io.observe(container); stateRef.current = { renderer, geometry, material, onResize }; return () => { io.disconnect(); running = false; window.removeEventListener('resize', onResize); cancelAnimationFrame(animationId); if (container && renderer.domElement && container.contains(renderer.domElement)) { container.removeChild(renderer.domElement); } renderer.dispose(); geometry.dispose(); material.dispose(); }; }, [colorA, colorB]); return (
); } Object.assign(window, { ShaderAnimation });