Spaces:
Sleeping
Sleeping
| 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})`; | |
| } | |