File size: 3,769 Bytes
1d68c54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"use client";

import { useRef, useEffect } from "react";

type Node = {
  x: number; y: number;
  vx: number; vy: number;
  r: number; pulse: number;
  type: "blue" | "green" | "red";
};

export default function HeroCanvas() {
  const ref = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    let W = 0, H = 0;
    let nodes: Node[] = [];
    let animId = 0;

    function resize() {
      W = canvas!.width = canvas!.offsetWidth;
      H = canvas!.height = canvas!.offsetHeight;
      buildNodes();
    }

    function buildNodes() {
      nodes = [];
      const count = Math.floor((W * H) / 18000);
      for (let i = 0; i < count; i++) {
        nodes.push({
          x: Math.random() * W, y: Math.random() * H,
          vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3,
          r: Math.random() * 2 + 1,
          pulse: Math.random() * Math.PI * 2,
          type: Math.random() < 0.1 ? "red" : Math.random() < 0.15 ? "green" : "blue",
        });
      }
    }

    function draw() {
      ctx!.clearRect(0, 0, W, H);

      // Grid
      ctx!.strokeStyle = "rgba(0,100,200,0.06)";
      ctx!.lineWidth = 0.5;
      for (let x = 0; x < W; x += 60) {
        ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke();
      }
      for (let y = 0; y < H; y += 60) {
        ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke();
      }

      // Update
      nodes.forEach(n => {
        n.x += n.vx; n.y += n.vy;
        if (n.x < 0 || n.x > W) n.vx *= -1;
        if (n.y < 0 || n.y > H) n.vy *= -1;
        n.pulse += 0.02;
      });

      // Edges
      for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
          const a = nodes[i], b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const dist = Math.sqrt(dx * dx + dy * dy);
          if (dist < 140) {
            const alpha = (1 - dist / 140) * 0.25;
            let color: string;
            if (a.type === "red" || b.type === "red") color = `rgba(255,45,85,${alpha * 0.8})`;
            else if (a.type === "green" || b.type === "green") color = `rgba(0,255,136,${alpha * 0.6})`;
            else color = `rgba(0,200,255,${alpha})`;
            ctx!.strokeStyle = color;
            ctx!.lineWidth = 0.5;
            ctx!.beginPath(); ctx!.moveTo(a.x, a.y); ctx!.lineTo(b.x, b.y); ctx!.stroke();
          }
        }
      }

      // Nodes
      nodes.forEach(n => {
        const pulse = 0.5 + 0.5 * Math.sin(n.pulse);
        let color: string, gcolor: string;
        if (n.type === "red") { color = `rgba(255,45,85,${0.4 + 0.5 * pulse})`; gcolor = "#FF2D55"; }
        else if (n.type === "green") { color = `rgba(0,255,136,${0.4 + 0.5 * pulse})`; gcolor = "#00FF88"; }
        else { color = `rgba(0,200,255,${0.35 + 0.4 * pulse})`; gcolor = "#00F5FF"; }

        const g = ctx!.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
        g.addColorStop(0, gcolor); g.addColorStop(1, "transparent");
        ctx!.fillStyle = g;
        ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx!.fill();
        ctx!.fillStyle = color;
        ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx!.fill();
      });

      animId = requestAnimationFrame(draw);
    }

    const onResize = () => { cancelAnimationFrame(animId); resize(); draw(); };
    window.addEventListener("resize", onResize);
    resize();
    draw();

    return () => { cancelAnimationFrame(animId); window.removeEventListener("resize", onResize); };
  }, []);

  return (
    <div className="hero-canvas-wrap">
      <canvas ref={ref} />
    </div>
  );
}