Jade-Infra-test / src /components /InteractiveBackground.jsx
rushiljain's picture
Upload 29 files
691cdd0 verified
import React, { useEffect, useRef } from 'react';
// Interactive background with flowing waves/spirals in brand colors.
// Renders on a canvas for performance and reacts to scroll and time.
export default function InteractiveBackground() {
const canvasRef = useRef(null);
const rafRef = useRef(0);
const timeRef = useRef(0);
const dimsRef = useRef({ w: 0, h: 0, dpr: 1 });
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const resize = () => {
const dpr = Math.min(2, window.devicePixelRatio || 1);
const w = canvas.clientWidth;
const h = canvas.clientHeight;
canvas.width = Math.floor(w * dpr);
canvas.height = Math.floor(h * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
dimsRef.current = { w, h, dpr };
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(canvas);
const drawWaves = (t) => {
const { w, h } = dimsRef.current;
// transparent base to let page bg through; slight warm-cool wash
const wash = ctx.createLinearGradient(0, 0, w, h);
wash.addColorStop(0, 'rgba(255,247,237,0.55)'); // warm
wash.addColorStop(1, 'rgba(236,253,245,0.55)'); // cool
ctx.fillStyle = wash;
ctx.fillRect(0, 0, w, h);
// Parameters
const scrollY = window.scrollY || 0;
const baseSpeed = 0.0018;
const parallax = scrollY * 0.08;
// Brand colors
const green = '#007746';
const orange = '#d39b23';
// Helper to draw a sine wave ribbon
const ribbon = (phase, amp, freq, thickness, color, yOffset = 0) => {
ctx.save();
ctx.beginPath();
const midY = h * 0.55 + yOffset;
for (let x = 0; x <= w; x += 2) {
const prog = x / w;
const spiral = Math.sin(prog * 6.283 + phase) * 0.6; // spiral-ish modulation
const y =
midY +
Math.sin(x * freq + phase) * amp +
Math.cos((x * freq) / 2 + phase * 0.8) * (amp * 0.35) +
spiral * 16;
if (x === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.strokeStyle = color;
ctx.lineWidth = thickness;
ctx.globalAlpha = 0.55;
ctx.shadowColor = color;
ctx.shadowBlur = 16;
ctx.stroke();
ctx.restore();
};
// Green top ribbon
ribbon(t * 3 + parallax * 0.015, 24, 0.012, 6, hexToRgba(green, 0.55), -40);
// Orange middle ribbon
ribbon(t * 2 + parallax * 0.01, 28, 0.0105, 7, hexToRgba(orange, 0.55), 0);
// Green thin accent
ribbon(t * 4.2 + parallax * 0.02, 18, 0.018, 3, hexToRgba(green, 0.45), 32);
// Orange thin accent
ribbon(t * 5 + parallax * 0.018, 14, 0.02, 2, hexToRgba(orange, 0.4), -22);
};
const render = () => {
const { w, h } = dimsRef.current;
if (w === 0 || h === 0) return;
const ctx = canvas.getContext('2d');
// Clear with transparency to stack with site background if any
ctx.clearRect(0, 0, w, h);
const t = (timeRef.current += 0.6); // time base for wave evolution
drawWaves(t * 0.001);
rafRef.current = requestAnimationFrame(render);
};
rafRef.current = requestAnimationFrame(render);
const onScroll = () => {
// allow scroll to affect next frame via parallax calc
};
window.addEventListener('scroll', onScroll, { passive: true });
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
ro.disconnect();
window.removeEventListener('scroll', onScroll);
};
}, []);
return (
<div className="pointer-events-none fixed inset-0 -z-10" aria-hidden="true">
<canvas ref={canvasRef} className="h-full w-full" />
</div>
);
}
function hexToRgba(hex, a = 1) {
const clean = hex.replace('#', '');
const bigint = parseInt(clean, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}