File size: 4,055 Bytes
691cdd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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})`;
}