import React, { useRef, useEffect, useState } from 'react'; // Types for component props interface HeroProps { trustBadge?: { text: string; icons?: string[]; }; headline: { line1: string; line2: string; }; subtitle: string; buttons?: { primary?: { text: string; onClick?: () => void; }; secondary?: { text: string; onClick?: () => void; }; }; className?: string; } // Reusable Shader Background Hook const useShaderBackground = () => { const canvasRef = useRef(null); const animationFrameRef = useRef(null); const rendererRef = useRef(null); const pointersRef = useRef(null); // WebGL Renderer class class WebGLRenderer { private canvas: HTMLCanvasElement; private gl: WebGL2RenderingContext; private program: WebGLProgram | null = null; private vs: WebGLShader | null = null; private fs: WebGLShader | null = null; private buffer: WebGLBuffer | null = null; private scale: number; private shaderSource: string; private mouseMove: [number, number] = [0, 0]; private mouseCoords: [number, number] = [0, 0]; private pointerCoords: number[] = [0, 0]; private nbrOfPointers = 0; private vertexSrc = `#version 300 es precision highp float; in vec4 position; void main(){gl_Position=position;}`; private vertices = [-1, 1, -1, -1, 1, 1, 1, -1]; constructor(canvas: HTMLCanvasElement, scale: number) { this.canvas = canvas; this.scale = scale; this.gl = canvas.getContext('webgl2')!; this.gl.viewport(0, 0, canvas.width * scale, canvas.height * scale); this.shaderSource = defaultShaderSource; } updateShader(source: string) { this.reset(); this.shaderSource = source; this.setup(); this.init(); } updateMove(deltas: [number, number]) { this.mouseMove = deltas; } updateMouse(coords: [number, number]) { this.mouseCoords = coords; } updatePointerCoords(coords: number[]) { this.pointerCoords = coords; } updatePointerCount(nbr: number) { this.nbrOfPointers = nbr; } updateScale(scale: number) { this.scale = scale; this.gl.viewport(0, 0, this.canvas.width * scale, this.canvas.height * scale); } compile(shader: WebGLShader, source: string) { const gl = this.gl; gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { const error = gl.getShaderInfoLog(shader); console.error('Shader compilation error:', error); } } test(source: string) { let result = null; const gl = this.gl; const shader = gl.createShader(gl.FRAGMENT_SHADER)!; gl.shaderSource(shader, source); gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { result = gl.getShaderInfoLog(shader); } gl.deleteShader(shader); return result; } reset() { const gl = this.gl; if (this.program && !gl.getProgramParameter(this.program, gl.DELETE_STATUS)) { if (this.vs) { gl.detachShader(this.program, this.vs); gl.deleteShader(this.vs); } if (this.fs) { gl.detachShader(this.program, this.fs); gl.deleteShader(this.fs); } gl.deleteProgram(this.program); } } setup() { const gl = this.gl; this.vs = gl.createShader(gl.VERTEX_SHADER)!; this.fs = gl.createShader(gl.FRAGMENT_SHADER)!; this.compile(this.vs, this.vertexSrc); this.compile(this.fs, this.shaderSource); this.program = gl.createProgram()!; gl.attachShader(this.program, this.vs); gl.attachShader(this.program, this.fs); gl.linkProgram(this.program); if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(this.program)); } } init() { const gl = this.gl; const program = this.program!; this.buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.vertices), gl.STATIC_DRAW); const position = gl.getAttribLocation(program, 'position'); gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0); (program as any).resolution = gl.getUniformLocation(program, 'resolution'); (program as any).time = gl.getUniformLocation(program, 'time'); (program as any).move = gl.getUniformLocation(program, 'move'); (program as any).touch = gl.getUniformLocation(program, 'touch'); (program as any).pointerCount = gl.getUniformLocation(program, 'pointerCount'); (program as any).pointers = gl.getUniformLocation(program, 'pointers'); } render(now = 0) { const gl = this.gl; const program = this.program; if (!program || gl.getProgramParameter(program, gl.DELETE_STATUS)) return; gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.useProgram(program); gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); gl.uniform2f((program as any).resolution, this.canvas.width, this.canvas.height); gl.uniform1f((program as any).time, now * 1e-3); gl.uniform2f((program as any).move, ...this.mouseMove); gl.uniform2f((program as any).touch, ...this.mouseCoords); gl.uniform1i((program as any).pointerCount, this.nbrOfPointers); gl.uniform2fv((program as any).pointers, this.pointerCoords); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } } // Pointer Handler class class PointerHandler { private scale: number; private active = false; private pointers = new Map(); private lastCoords: [number, number] = [0, 0]; private moves: [number, number] = [0, 0]; constructor(element: HTMLCanvasElement, scale: number) { this.scale = scale; const map = (element: HTMLCanvasElement, scale: number, x: number, y: number): [number, number] => [x * scale, element.height - y * scale]; element.addEventListener('pointerdown', (e) => { this.active = true; this.pointers.set(e.pointerId, map(element, this.getScale(), e.clientX, e.clientY)); }); element.addEventListener('pointerup', (e) => { if (this.count === 1) { this.lastCoords = this.first; } this.pointers.delete(e.pointerId); this.active = this.pointers.size > 0; }); element.addEventListener('pointerleave', (e) => { if (this.count === 1) { this.lastCoords = this.first; } this.pointers.delete(e.pointerId); this.active = this.pointers.size > 0; }); element.addEventListener('pointermove', (e) => { if (!this.active) return; this.lastCoords = [e.clientX, e.clientY]; this.pointers.set(e.pointerId, map(element, this.getScale(), e.clientX, e.clientY)); this.moves = [this.moves[0] + e.movementX, this.moves[1] + e.movementY]; }); } getScale() { return this.scale; } updateScale(scale: number) { this.scale = scale; } get count() { return this.pointers.size; } get move(): [number, number] { return this.moves; } get coords(): number[] { return this.pointers.size > 0 ? Array.from(this.pointers.values()).flat() : [0, 0]; } get first(): [number, number] { return (this.pointers.values().next().value as [number, number]) || this.lastCoords; } } const resize = () => { if (!canvasRef.current) return; const canvas = canvasRef.current; const dpr = Math.max(1, 0.5 * window.devicePixelRatio); canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; if (rendererRef.current) { rendererRef.current.updateScale(dpr); } }; const loop = (now: number) => { if (!rendererRef.current || !pointersRef.current) return; rendererRef.current.updateMouse(pointersRef.current.first); rendererRef.current.updatePointerCount(pointersRef.current.count); rendererRef.current.updatePointerCoords(pointersRef.current.coords); rendererRef.current.updateMove(pointersRef.current.move); rendererRef.current.render(now); animationFrameRef.current = requestAnimationFrame(loop); }; useEffect(() => { if (!canvasRef.current) return; const canvas = canvasRef.current; const dpr = Math.max(1, 0.5 * window.devicePixelRatio); rendererRef.current = new WebGLRenderer(canvas, dpr); pointersRef.current = new PointerHandler(canvas, dpr); rendererRef.current.setup(); rendererRef.current.init(); resize(); if (rendererRef.current.test(defaultShaderSource) === null) { rendererRef.current.updateShader(defaultShaderSource); } loop(0); window.addEventListener('resize', resize); return () => { window.removeEventListener('resize', resize); if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } if (rendererRef.current) { rendererRef.current.reset(); } }; }, []); return canvasRef; }; // Reusable Hero Component const Hero: React.FC = ({ trustBadge, headline, subtitle, buttons, className = "" }) => { const canvasRef = useShaderBackground(); return (
{/* Hero Content Overlay */}
{/* Trust Badge */} {trustBadge && (
{trustBadge.icons && (
{trustBadge.icons.map((icon, index) => ( {icon} ))}
)} {trustBadge.text}
)}
{/* Main Heading with Animation */}

{headline.line1}

{headline.line2}

{/* Subtitle with Animation */}

{subtitle}

{/* CTA Buttons with Animation */} {buttons && (
{buttons.primary && ( )} {buttons.secondary && ( )}
)}
); }; const defaultShaderSource = `#version 300 es /********* * made by Matthias Hurrle (@atzedent) * *\tTo explore strange new worlds, to seek out new life *and new civilizations, to boldly go where no man has *gone before. */ precision highp float; out vec4 O; uniform vec2 resolution; uniform float time; #define FC gl_FragCoord.xy #define T time #define R resolution #define MN min(R.x,R.y) // Returns a pseudo random number for a given point (white noise) float rnd(vec2 p) { p=fract(p*vec2(12.9898,78.233)); p+=dot(p,p+34.56); return fract(p.x*p.y); } // Returns a pseudo random number for a given point (value noise) float noise(in vec2 p) { vec2 i=floor(p), f=fract(p), u=f*f*(3.-2.*f); float a=rnd(i), b=rnd(i+vec2(1,0)), c=rnd(i+vec2(0,1)), d=rnd(i+1.); return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); } // Returns a pseudo random number for a given point (fractal noise) float fbm(vec2 p) { float t=.0, a=1.; mat2 m=mat2(1.,-.5,.2,1.2); for (int i=0; i<5; i++) { t+=a*noise(p); p*=2.*m; a*=.5; } return t; } float clouds(vec2 p) { float d=1., t=.0; for (float i=.0; i<3.; i++) { float a=d*fbm(i*10.+p.x*.2+.2*(1.+i)*p.y+d+i*i+p); t=mix(t,d,a); d=a; p*=2./(i+1.); } return t; } void main(void) { vec2 uv=(FC-.5*R)/MN,st=uv*vec2(2,1); vec3 col=vec3(0); float bg=clouds(vec2(st.x+T*.5,-st.y)); uv*=1.-.3*(sin(T*.2)*.5+.5); for (float i=1.; i<12.; i++) { uv+=.1*cos(i*vec2(.1+.01*i, .8)+i*i+T*.5+.1*uv.x); vec2 p=uv; float d=length(p); col+=.00125/d*(cos(sin(i)*vec3(1,2,3))+1.); float b=noise(i+p+bg*1.731); col+=.002*b/length(max(p,vec2(b*p.x*.02,p.y))); col=mix(col,vec3(bg*.25,bg*.137,bg*.05),d); } O=vec4(col,1); }`; export default Hero;