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 (
); } 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})`; }