Immersive animated background with aurora waves, twinkling stars, and mouse-reactive glow effects
Copy URL or Code
Paste to your AI coding assistant and customize:
Done. Your AI handles the rest.
Fully customizable. 4 color schemes, adjustable speed/glow/stars. Three.js WebGL for smooth 60fps. Perfect for hero sections and landing pages.
"use client";
/**
* COSMIC GLOW BACKGROUND - Animated WebGL Background
*
* Props:
* - colorScheme: "forest" | "ocean" | "sunset" | "cosmic" (default: "forest")
* - waveSpeed: number (default: 1.0) - Animation speed multiplier
* - glowIntensity: number (default: 1.0) - Glow brightness
* - starDensity: number (default: 1.0) - Star count multiplier
* - className: string - CSS classes
* - style: React.CSSProperties - Inline styles
*/
import React, { useEffect, useRef, useMemo } from "react";
import * as THREE from "three";
export type ColorScheme = "forest" | "ocean" | "sunset" | "cosmic";
export interface CosmicGlowBackgroundProps {
className?: string;
style?: React.CSSProperties;
colorScheme?: ColorScheme;
waveSpeed?: number;
glowIntensity?: number;
starDensity?: number;
}
const COLOR_PALETTES: Record<ColorScheme, {
wave1: [number, number, number];
wave2: [number, number, number];
wave3: [number, number, number];
glow: [number, number, number];
center: [number, number, number];
stars: [number, number, number];
bgTint: [number, number, number];
bgColor: string;
}> = {
forest: {
wave1: [0.2, 1.0, 0.6], wave2: [0.1, 0.9, 0.7], wave3: [0.4, 0.85, 0.45],
glow: [0.5, 1.0, 0.75], center: [0.2, 0.65, 0.4], stars: [0.8, 1.0, 0.9],
bgTint: [0.0, 0.07, 0.04], bgColor: "#021a14",
},
ocean: {
wave1: [0.2, 0.7, 1.0], wave2: [0.1, 0.85, 0.95], wave3: [0.3, 0.6, 1.0],
glow: [0.4, 0.85, 1.0], center: [0.2, 0.5, 0.8], stars: [0.9, 0.95, 1.0],
bgTint: [0.0, 0.04, 0.08], bgColor: "#011420",
},
sunset: {
wave1: [1.0, 0.5, 0.2], wave2: [1.0, 0.35, 0.4], wave3: [0.95, 0.6, 0.3],
glow: [1.0, 0.7, 0.5], center: [0.8, 0.4, 0.2], stars: [1.0, 0.95, 0.85],
bgTint: [0.08, 0.02, 0.04], bgColor: "#1a0812",
},
cosmic: {
wave1: [1.0, 0.4, 0.8], wave2: [0.6, 0.3, 1.0], wave3: [0.8, 0.5, 1.0],
glow: [1.0, 0.7, 1.0], center: [0.5, 0.3, 0.8], stars: [1.0, 0.9, 0.95],
bgTint: [0.05, 0.0, 0.08], bgColor: "#0d0018",
},
};
export default function CosmicGlowBackground({
className = "",
style,
colorScheme = "forest",
waveSpeed = 1.0,
glowIntensity = 1.0,
starDensity = 1.0,
}: CosmicGlowBackgroundProps) {
const containerRef = useRef<HTMLDivElement>(null);
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
const animationRef = useRef<number | undefined>(undefined);
const palette = useMemo(() => COLOR_PALETTES[colorScheme], [colorScheme]);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
let scene: THREE.Scene, camera: THREE.OrthographicCamera;
let renderer: THREE.WebGLRenderer, clock: THREE.Clock;
let pointerPos = new THREE.Vector2(0.5, 0.5);
let pointerTarget = new THREE.Vector2(0.5, 0.5);
const shaderUniforms = {
uTime: { value: 0.0 },
uResolution: { value: new THREE.Vector2(container.clientWidth, container.clientHeight) },
uPointer: { value: new THREE.Vector2(0.5, 0.5) },
uSpeed: { value: waveSpeed },
uGlow: { value: glowIntensity },
uStars: { value: starDensity },
uWave1: { value: new THREE.Vector3(...palette.wave1) },
uWave2: { value: new THREE.Vector3(...palette.wave2) },
uWave3: { value: new THREE.Vector3(...palette.wave3) },
uGlowColor: { value: new THREE.Vector3(...palette.glow) },
uCenterColor: { value: new THREE.Vector3(...palette.center) },
uStarColor: { value: new THREE.Vector3(...palette.stars) },
uBgTint: { value: new THREE.Vector3(...palette.bgTint) },
};
const vertexShader = `
varying vec2 vUv;
void main() { vUv = uv; gl_Position = vec4(position, 1.0); }
`;
const fragmentShader = `
uniform vec2 uResolution; uniform float uTime; uniform vec2 uPointer;
uniform float uSpeed, uGlow, uStars;
uniform vec3 uWave1, uWave2, uWave3, uGlowColor, uCenterColor, uStarColor, uBgTint;
varying vec2 vUv;
mat2 rotate2D(float a) { float s=sin(a),c=cos(a); return mat2(c,-s,s,c); }
float auroraWave(vec2 p, float ph, float fr) { return sin(p.x*fr+ph)*0.32*sin(p.y*fr*0.48+ph*0.72); }
float neonGlow(float d, float w, float b) { return b*w/(abs(d)+w*0.52); }
vec3 mod289v3(vec3 x) { return x-floor(x*(1.0/289.0))*289.0; }
vec2 mod289v2(vec2 x) { return x-floor(x*(1.0/289.0))*289.0; }
vec3 permuteVec(vec3 x) { return mod289v3(((x*34.0)+1.0)*x); }
float simplex2D(vec2 v) {
const vec4 C=vec4(0.211324865405187,0.366025403784439,-0.577350269189626,0.024390243902439);
vec2 i=floor(v+dot(v,C.yy)); vec2 x0=v-i+dot(i,C.xx);
vec2 i1=(x0.x>x0.y)?vec2(1.0,0.0):vec2(0.0,1.0);
vec4 x12=x0.xyxy+C.xxzz; x12.xy-=i1; i=mod289v2(i);
vec3 p=permuteVec(permuteVec(i.y+vec3(0.0,i1.y,1.0))+i.x+vec3(0.0,i1.x,1.0));
vec3 m=max(0.5-vec3(dot(x0,x0),dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)),0.0);
m=m*m; m=m*m;
vec3 x=2.0*fract(p*C.www)-1.0; vec3 h=abs(x)-0.5;
vec3 ox=floor(x+0.5); vec3 a0=x-ox;
m*=(1.79284291400159-0.85373472095314*(a0*a0+h*h));
vec3 g; g.x=a0.x*x0.x+h.x*x0.y; g.yz=a0.yz*x12.xz+h.yz*x12.yw;
return 130.0*dot(m,g);
}
float randomHash(vec2 p) { return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453); }
float starField(vec2 uv, float time, float density) {
float gs=140.0+density*20.0; vec2 grid=floor(uv*gs); vec2 gf=fract(uv*gs)-0.5;
float sv=randomHash(grid); float th=0.982-density*0.01;
if(sv<th) return 0.0;
float tw=sin(time*2.2+grid.x*1.1+grid.y*0.9)*0.5+0.5;
return smoothstep(0.09,0.0,length(gf))*tw*(sv-th)*95.0;
}
void main() {
vec2 uv=(vUv-0.5)*2.0; uv.x*=uResolution.x/uResolution.y;
vec2 uv0=uv; vec3 col=vec3(0.0); float time=uTime*0.42*uSpeed;
col+=((simplex2D(uv*0.52+time*0.018)+1.0)*0.5)*uBgTint*0.28;
vec2 pUv=(uPointer-0.5)*2.0; pUv.x*=uResolution.x/uResolution.y;
float pDist=length(uv-pUv); uv+=(pUv-uv)*(0.28/(pDist+0.52));
float pGlow=0.12/(pDist+0.12); pGlow*=(sin(uTime*1.6)*0.5+0.5)*0.68+0.32;
col+=pGlow*uGlowColor*0.14*uGlow;
uv*=rotate2D(time*0.048); float wn=simplex2D(uv*2.1+time*0.22)*0.11;
float c1=sin(time*0.28)*0.5+0.5, c2=sin(time*0.28+2.1)*0.5+0.5, c3=sin(time*0.28+4.2)*0.5+0.5;
col+=uWave1*(0.8+c1*0.2)*neonGlow(uv.y-auroraWave(uv,time*1.55,2.1)+wn,0.032,0.82*uGlow);
col+=uWave2*(0.8+c2*0.2)*neonGlow(uv.y+0.42-auroraWave(uv+vec2(1.05,0.52),time*1.22,2.55)+wn*0.78,0.032,0.82*uGlow);
col+=uWave3*(0.8+c3*0.2)*neonGlow(uv.y-0.42-auroraWave(uv+vec2(-0.52,1.05),time*1.85,1.82)+wn*1.18,0.032,0.82*uGlow);
float dist=length(uv0);
col+=uCenterColor*abs(sin(dist*4.2-time*2.1))*exp(-dist*0.52)*0.28*uGlow;
col+=starField(uv0*2.1+time*0.012,uTime,uStars)*uStarColor*0.65;
col+=exp(-dist*1.05)*0.28*uCenterColor*uGlow;
col*=smoothstep(0.0,1.0,1.0-dist*0.48);
gl_FragColor=vec4(pow(col,vec3(0.96)),1.0);
}
`;
function setup() {
scene=new THREE.Scene(); clock=new THREE.Clock();
camera=new THREE.OrthographicCamera(-1,1,1,-1,0.1,10); camera.position.z=1;
renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(container.clientWidth,container.clientHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
container.appendChild(renderer.domElement); rendererRef.current=renderer;
const mesh=new THREE.Mesh(new THREE.PlaneGeometry(2,2),new THREE.ShaderMaterial({uniforms:shaderUniforms,vertexShader,fragmentShader}));
scene.add(mesh);
}
function onPointer(e: MouseEvent) { const r=container.getBoundingClientRect(); pointerTarget.set((e.clientX-r.left)/r.width,1-(e.clientY-r.top)/r.height); }
function onTouch(e: TouchEvent) { if(e.touches.length) { const r=container.getBoundingClientRect(); pointerTarget.set((e.touches[0].clientX-r.left)/r.width,1-(e.touches[0].clientY-r.top)/r.height); }}
function onResize() { renderer.setSize(container.clientWidth,container.clientHeight); shaderUniforms.uResolution.value.set(container.clientWidth,container.clientHeight); }
function render() { animationRef.current=requestAnimationFrame(render); shaderUniforms.uTime.value=clock.getElapsedTime(); pointerPos.lerp(pointerTarget,0.055); shaderUniforms.uPointer.value.copy(pointerPos); renderer.render(scene,camera); }
setup(); render();
container.addEventListener("mousemove",onPointer);
container.addEventListener("touchmove",onTouch);
window.addEventListener("resize",onResize);
return () => {
if(animationRef.current) cancelAnimationFrame(animationRef.current);
container.removeEventListener("mousemove",onPointer);
container.removeEventListener("touchmove",onTouch);
window.removeEventListener("resize",onResize);
rendererRef.current?.dispose();
rendererRef.current?.domElement.parentNode?.removeChild(rendererRef.current.domElement);
};
}, [palette,waveSpeed,glowIntensity,starDensity]);
return <div ref={containerRef} className={className} style={{width:"100%",height:"100%",overflow:"hidden",backgroundColor:palette.bgColor,...style}} />;
}
export { CosmicGlowBackground };// BASIC USAGE - Just drop it in
import CosmicGlowBackground from "@/components/CosmicGlowBackground";
// Full screen hero
<div className="h-screen relative">
<CosmicGlowBackground className="absolute inset-0" />
<div className="relative z-10">Your content</div>
</div>
// CHANGE COLOR SCHEME
<CosmicGlowBackground colorScheme="ocean" /> // Blue waves
<CosmicGlowBackground colorScheme="sunset" /> // Orange/red waves
<CosmicGlowBackground colorScheme="cosmic" /> // Purple waves
<CosmicGlowBackground colorScheme="forest" /> // Green waves (default)
// ADJUST ANIMATION
<CosmicGlowBackground waveSpeed={0.5} /> // Slower animation
<CosmicGlowBackground waveSpeed={2.0} /> // Faster animation
<CosmicGlowBackground glowIntensity={1.5} /> // Brighter glow
<CosmicGlowBackground starDensity={2.0} /> // More stars
// COMBINE OPTIONS
<CosmicGlowBackground
colorScheme="cosmic"
waveSpeed={0.8}
glowIntensity={1.2}
starDensity={1.5}
/>
// FIXED SIZE CONTAINER
<div style={{ height: "400px" }}>
<CosmicGlowBackground />
</div>