The prompt that made it
as published# ANIMATED SHADER HERO — COMPONENT INTEGRATION
YOU ARE GIVEN A TASK TO INTEGRATE AN EXISTING REACT COMPONENT IN THE CODEBASE
THE CODEBASE SHOULD SUPPORT:
- SHADCN PROJECT STRUCTURE
- TAILWIND CSS
- TYPESCRIPT
IF IT DOESN'T, PROVIDE INSTRUCTIONS ON HOW TO SETUP PROJECT VIA SHADCN CLI, INSTALL TAILWIND OR TYPESCRIPT.
DETERMINE THE DEFAULT PATH FOR COMPONENTS AND STYLES.
IF DEFAULT PATH FOR COMPONENTS IS NOT /COMPONENTS/UI, PROVIDE INSTRUCTIONS ON WHY IT'S IMPORTANT TO CREATE THIS FOLDER
COPY-PASTE THIS COMPONENT TO /COMPONENTS/UI FOLDER:
```tsx
animated-shader-hero.tsx
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<HTMLCanvasElement>(null);
const animationFrameRef = useRef<number>();
const rendererRef = useRef<WebGLRenderer | null>(null);
const pointersRef = useRef<PointerHandler | null>(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 = [0, 0];
private mouseCoords = [0, 0];
private pointerCoords = [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[]) {
this.mouseMove = deltas;
}
updateMouse(coords: 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<number, number[]>();
private lastCoords = [0, 0];
private moves = [0, 0];
constructor(element: HTMLCanvasElement, scale: number) {
this.scale = scale;
const map = (element: HTMLCanvasElement, scale: number, x: number, y: 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() {
return this.moves;
}
get coords() {
return this.pointers.size > 0
? Array.from(this.pointers.values()).flat()
: [0, 0];
}
get first() {
return this.pointers.values().next().value || 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<HeroProps> = ({
trustBadge,
headline,
subtitle,
buttons,
className = ""
}) => {
const canvasRef = useShaderBackground();
return (
<div className={`relative w-full h-screen overflow-hidden bg-black ${className}`}>
<style jsx>{`
@keyframes fade-in-down {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fade-in-down 0.8s ease-out forwards;
}
.animate-fade-in-up {
animation: fade-in-up 0.8s ease-out forwards;
opacity: 0;
}
.animation-delay-200 {
animation-delay: 0.2s;
}
.animation-delay-400 {
animation-delay: 0.4s;
}
.animation-delay-600 {
animation-delay: 0.6s;
}
.animation-delay-800 {
animation-delay: 0.8s;
}
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.animate-gradient {
background-size: 200% 200%;
animation: gradient-shift 3s ease infinite;
}
`}</style>
<canvas
ref={canvasRef}
className="absolute inset-0 w-full h-full object-contain touch-none"
style={{ background: 'black' }}
/>
{/* Hero Content Overlay */}
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center text-white">
{/* Trust Badge */}
{trustBadge && (
<div className="mb-8 animate-fade-in-down">
<div className="flex items-center gap-2 px-6 py-3 bg-orange-500/10 backdrop-blur-md border border-orange-300/30 rounded-full text-sm">
{trustBadge.icons && (
<div className="flex">
{trustBadge.icons.map((icon, index) => (
<span key={index} className={`text-${index === 0 ? 'yellow' : index === 1 ? 'orange' : 'amber'}-300`}>
{icon}
</span>
))}
</div>
)}
<span className="text-orange-100">{trustBadge.text}</span>
</div>
</div>
)}
<div className="text-center space-y-6 max-w-5xl mx-auto px-4">
{/* Main Heading with Animation */}
<div className="space-y-2">
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold bg-gradient-to-r from-orange-300 via-yellow-400 to-amber-300 bg-clip-text text-transparent animate-fade-in-up animation-delay-200">
{headline.line1}
</h1>
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold bg-gradient-to-r from-yellow-300 via-orange-400 to-red-400 bg-clip-text text-transparent animate-fade-in-up animation-delay-400">
{headline.line2}
</h1>
</div>
{/* Subtitle with Animation */}
<div className="max-w-3xl mx-auto animate-fade-in-up animation-delay-600">
<p className="text-lg md:text-xl lg:text-2xl text-orange-100/90 font-light leading-relaxed">
{subtitle}
</p>
</div>
{/* CTA Buttons with Animation */}
{buttons && (
<div className="flex flex-col sm:flex-row gap-4 justify-center mt-10 animate-fade-in-up animation-delay-800">
{buttons.primary && (
<button
onClick={buttons.primary.onClick}
className="px-8 py-4 bg-gradient-to-r from-orange-500 to-yellow-500 hover:from-orange-600 hover:to-yellow-600 text-black rounded-full font-semibold text-lg transition-all duration-300 hover:scale-105 hover:shadow-xl hover:shadow-orange-500/25"
>
{buttons.primary.text}
</button>
)}
{buttons.secondary && (
<button
onClick={buttons.secondary.onClick}
className="px-8 py-4 bg-orange-500/10 hover:bg-orange-500/20 border border-orange-300/30 hover:border-orange-300/50 text-orange-100 rounded-full font-semibold text-lg transition-all duration-300 hover:scale-105 backdrop-blur-sm"
>
{buttons.secondary.text}
</button>
)}
</div>
)}
</div>
</div>
</div>
);
};
const defaultShaderSource = `#version 300 es
/*********
* made by Matthias Hurrle (@atzedent)
*
* To 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;
demo.tsx
import Hero from "@/components/ui/animated-shader-hero";
// Demo Component showing how to use the Hero
const HeroDemo: React.FC = () => {
const handlePrimaryClick = () => {
console.log('Get Started clicked!');
// Add your logic here
};
const handleSecondaryClick = () => {
console.log('Explore Features clicked!');
// Add your logic here
};
return (
<div className="w-full">
<Hero
trustBadge={{
text: "Trusted by forward-thinking teams.",
icons: ["✨"]
}}
headline={{
line1: "Launch Your",
line2: "Workflow Into Orbit"
}}
subtitle="Supercharge productivity with AI-powered automation and integrations built for the next generation of teams — fast, seamless, and limitless."
buttons={{
primary: {
text: "Get Started for Free",
onClick: handlePrimaryClick
},
secondary: {
text: "Explore Features",
onClick: handleSecondaryClick
}
}}
/>
{/* Additional content below hero */}
<div className="bg-gray-100 p-8">
<div className="max-w-4xl mx-auto">
<h2 className="text-3xl font-bold text-gray-800 mb-4">
How to Use the Hero Component
</h2>
<div className="bg-white p-6 rounded-lg shadow-sm">
<pre className="text-sm text-gray-600 overflow-x-auto">
{`<Hero
trustBadge={{
text: "Your trust badge text",
icons: ["🚀", "⭐", "✨"] // optional
}}
headline={{
line1: "Your First Line",
line2: "Your Second Line"
}}
subtitle="Your compelling subtitle text goes here..."
buttons={{
primary: {
text: "Primary CTA",
onClick: handlePrimaryClick
},
secondary: {
text: "Secondary CTA",
onClick: handleSecondaryClick
}
}}
className="custom-classes" // optional
/>`}
</pre>
</div>
</div>
</div>
</div>
);
};
export default HeroDemo;
```
## IMPLEMENTATION GUIDELINES
1. ANALYZE THE COMPONENT STRUCTURE AND IDENTIFY ALL REQUIRED DEPENDENCIES
2. REVIEW THE COMPONENT'S ARGUMENS AND STATE
3. IDENTIFY ANY REQUIRED CONTEXT PROVIDERS OR HOOKS AND INSTALL THEM
4. QUESTIONS TO ASK
- WHAT DATA/PROPS WILL BE PASSED TO THIS COMPONENT?
- ARE THERE ANY SPECIFIC STATE MANAGEMENT REQUIREMENTS?
- ARE THERE ANY REQUIRED ASSETS (IMAGES, ICONS, ETC.)?
- WHAT IS THE EXPECTED RESPONSIVE BEHAVIOR?
- WHAT IS THE BEST PLACE TO USE THIS COMPONENT IN THE APP?
## STEPS TO INTEGRATE
0. COPY PASTE ALL THE CODE ABOVE IN THE CORRECT DIRECTORIES
1. INSTALL EXTERNAL DEPENDENCIES
2. FILL IMAGE ASSETS WITH UNSPLASH STOCK IMAGES YOU KNOW EXIST
3. USE LUCIDE-REACT ICONS FOR SVGS OR LOGOS IF COMPONENT REQUIRES THEMAdds a render step. Works best with the HyperFrames or Remotion skills installed. Free, no sign-up.
Prompt by @pulkitxm. Prompts belong to their creators.