Components
Loading preview...
import React, { useEffect, useRef } from 'react';
/**
* FLASH UI - Botanical Digitalism Background
* A high-performance WebGL background replicating lush tropical foliage.
*
* CSS Variable Exposure:
* --bg-primary: #022c22 (Shadows)
* --bg-secondary: #064e3b (Mid-tones)
* --bg-accent: #4ade80 (Highlights/Interactive Glow)
*/
const BotanicalBackground = () => {
const canvasRef = useRef(null);
const mouseRef = useRef({ x: 0, y: 0 });
useEffect(() => {
const canvas = canvasRef.current;
const gl = canvas.getContext('webgl');
if (!gl) return;
const vsSource = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fsSource = `
precision highp float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec2 u_mouse;
vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
float snoise(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 = mod(i, 289.0);
vec3 p = permute( permute( 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);
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
uv.x *= u_resolution.x / u_resolution.y;
vec2 mouse = u_mouse / u_resolution.xy;
mouse.x *= u_resolution.x / u_resolution.y;
float t = u_time * 0.15;
vec2 drift = vec2(sin(t), cos(t * 0.7)) * 0.05;
float dist = distance(uv, mouse);
vec2 interaction = (uv - mouse) * (0.1 / (dist + 0.4));
float n1 = snoise((uv + drift - interaction) * 2.2);
float n2 = snoise((uv * 4.5 + n1 * 0.6));
float veins = smoothstep(0.88, 1.0, sin((uv.x + uv.y + n1 * 0.25) * 180.0)) * 0.25;
vec3 colorA = vec3(0.01, 0.17, 0.13); // Deepest Green
vec3 colorB = vec3(0.04, 0.35, 0.25); // Mid Forest
vec3 colorC = vec3(0.35, 0.85, 0.45); // Bright Lime
vec3 finalColor = mix(colorA, colorB, n1 * 0.5 + 0.5);
finalColor = mix(finalColor, colorC, pow(max(0.0, n2), 2.5));
finalColor += veins * (n2 + 0.4);
float flare = smoothstep(0.45, 0.0, dist);
finalColor += colorC * flare * 0.25;
gl_FragColor = vec4(finalColor * (1.0 - length(uv - 0.5) * 0.5), 1.0);
}
`;
const createShader = (type, source) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
};
const program = gl.createProgram();
gl.attachShader(program, createShader(gl.VERTEX_SHADER, vsSource));
gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fsSource));
gl.linkProgram(program);
gl.useProgram(program);
const vertices = new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const pos = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(pos);
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
const timeLoc = gl.getUniformLocation(program, "u_time");
const resLoc = gl.getUniformLocation(program, "u_resolution");
const mouseLoc = gl.getUniformLocation(program, "u_mouse");
const handleMouseMove = (e) => {
mouseRef.current = { x: e.clientX, y: window.innerHeight - e.clientY };
};
window.addEventListener('mousemove', handleMouseMove);
let animationFrame;
const render = (time) => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform1f(timeLoc, time * 0.001);
gl.uniform2f(resLoc, canvas.width, canvas.height);
gl.uniform2f(mouseLoc, mouseRef.current.x, mouseRef.current.y);
gl.drawArrays(gl.TRIANGLES, 0, 6);
animationFrame = requestAnimationFrame(render);
};
animationFrame = requestAnimationFrame(render);
return () => {
cancelAnimationFrame(animationFrame);
window.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return (
<div className="fixed inset-0 z-[-1] overflow-hidden bg-[#022c22]">
<canvas
ref={canvasRef}
className="w-full h-full block"
style={{ filter: 'contrast(1.1) brightness(1.05)' }}
/>
</div>
);
};
export default BotanicalBackground;