79819184

Date: 2025-11-13 16:57:05
Score: 0.5
Natty:
Report link

One approch to fix this is to use one more dashed line with same background as of page background.

Please refer to following code (its same as of you just some changes):

import { useEffect, useLayoutEffect, useRef, useState } from "react";
import gsap from "gsap";

interface BoxItem {
  label: string;
  color: string;
}

interface Line {
  x1: number;
  y1: number;
  x2: number;
  y2: number;
}

const boxData: BoxItem[] = [
  { label: "Event", color: "#ff6b6b" },
  { label: "Date", color: "#4dabf7" },
  { label: "Fuel", color: "#f06595" },
  { label: "Message", color: "#51cf66" },
  { label: "Work", color: "#d0bfff" },
  { label: "Data", color: "#74c0fc" },
  { label: "Food", color: "#ffd43b" },
  { label: "Style", color: "#ced4da" },
];

export default function Home() {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const centerBoxRef = useRef<HTMLDivElement | null>(null);
  const boxRefs = useRef<(HTMLDivElement | null)[]>([]);

  const [lines, setLines] = useState<Line[]>([]);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
  const [activeLines, setActiveLines] = useState<Record<number, boolean>>({});
  const timeoutRefs = useRef<any>({});
  const animatingLines = useRef<Set<number>>(new Set());

  // Track container size
  useEffect(() => {
    const updateDimensions = () => {
      if (containerRef.current) {
        setDimensions({
          width: containerRef.current.offsetWidth,
          height: containerRef.current.offsetHeight,
        });
      }
    };
    updateDimensions();
    window.addEventListener("resize", updateDimensions);
    return () => window.removeEventListener("resize", updateDimensions);
  }, []);

  // Calculate line positions
  useLayoutEffect(() => {
    const updateLines = () => {
      if (!centerBoxRef.current || !containerRef.current) return;

      const containerRect = containerRef.current.getBoundingClientRect();
      const centerRect = centerBoxRef.current.getBoundingClientRect();
      const centerX = centerRect.left - containerRect.left + centerRect.width / 2;
      const centerY = centerRect.top - containerRect.top + centerRect.height / 2;

      const newLines: Line[] = boxRefs.current.map((box) => {
        if (!box) return { x1: 0, y1: 0, x2: 0, y2: 0 };
        const boxRect = box.getBoundingClientRect();
        const x2 = boxRect.left - containerRect.left + boxRect.width / 2;
        const y2 = boxRect.top - containerRect.top + boxRect.height / 2;
        return { x1: centerX, y1: centerY, x2, y2 };
      });

      setLines(newLines);
    };

    updateLines();
    const observer = new ResizeObserver(updateLines);
    if (containerRef.current) observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, [dimensions]);

  const calculateCurvePath = (line: Line) => {
    const cpX = (line.x1 + line.x2) / 2 + (line.y2 - line.y1) * -0.21;
    const cpY = (line.y1 + line.y2) / 2 - (line.x2 - line.x1) * -0.21;
    return {
      path: `M${line.x1},${line.y1} Q${cpX},${cpY} ${line.x2},${line.y2}`,
    };
  };

const animateLine = (index: number, color: string) => {
  const path = document.getElementById(`animated-line-${index}`) as SVGPathElement | null;
  if (!path || animatingLines.current.has(index)) return;

  animatingLines.current.add(index);

  const length = path.getTotalLength();

  // ✅ Key fix: make one full-length dash to reveal progressively
  path.style.strokeDasharray = `${length}`;
  path.style.strokeDashoffset = `${length}`;
  path.style.stroke = color;
  path.style.opacity = "1";

  gsap.to(path, {
    strokeDashoffset: 0,
    duration: 0.8,
    ease: "power1.inOut",
    onComplete: () => {
      setActiveLines((prev) => ({ ...prev, [index]: true }));
      timeoutRefs.current[index] = setTimeout(() => reverseLine(index), 2000);
    },
  });
};

  const reverseLine = (index: number) => {
    const path = document.getElementById(`animated-line-${index}`) as SVGPathElement | null;
    if (!path) return;

    const length = path.getTotalLength();
    gsap.to(path, {
      strokeDashoffset: length,
      duration: 0.6,
      ease: "power2.inOut",
      onComplete: () => {
        path.style.opacity = "0";
        animatingLines.current.delete(index);
        setActiveLines((prev) => ({ ...prev, [index]: false }));
      },
    });
  };

  const handleBoxClick = (index: number, color: string) => {
    if (animatingLines.current.has(index)) return;

    if (timeoutRefs.current[index]) {
      clearTimeout(timeoutRefs.current[index]);
    }

    if (!activeLines[index]) {
      animateLine(index, color);
    }
  };

  const handleCenterClick = () => {
    boxData.forEach((box, i) => {
      if (!activeLines[i] && !animatingLines.current.has(i)) {
        animateLine(i, box.color);
      }
    });
  };

  return (
    <div
      ref={containerRef}
      className="relative w-full h-screen bg-gradient-to-br from-pink-100 to-blue-100 overflow-hidden"
    >
      <svg className="absolute top-0 left-0 w-full h-full pointer-events-none">
        <defs>
          <linearGradient id="line-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" stopColor="#cccccc" />
            <stop offset="100%" stopColor="#cccccc" stopOpacity="0.8" />
          </linearGradient>

          {/* New: background-matching gradient */}
          <linearGradient id="bg-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" stopColor="#ffe3ec" />  {/* matches from-pink-100 */}
            <stop offset="100%" stopColor="#d0ebff" /> {/* matches to-blue-100 */}
          </linearGradient>
        </defs>

        {lines.map((line, i) => {
          const { path } = calculateCurvePath(line);
          return (
            <g key={i}>
              {/* Static gray dashed line */}
              <path
                id={`dashed-line-${i}`}
                d={path}
                stroke="url(#line-gradient)"
                strokeWidth="2"
                strokeDasharray="8, 4" 
                fill="none"
              />
              {/* Animated colored dashed overlay */}
              <path
                id={`animated-line-${i}`}
                d={path}
                stroke="transparent"
                strokeWidth="2"
                strokeDasharray="8, 4"
                fill="none"
                style={{ opacity: 0 }}
              />
              {/* static white or background gap line */}
              <path
                id={`dashed-line-${i}`}
                d={path}
                stroke="url(#bg-gradient)"
                strokeWidth="2"
                strokeDasharray="8, 8"
                fill="none"
              />
              {/* Endpoint circle */}
              <circle cx={line.x2} cy={line.y2} r="6" fill={boxData[i].color} />
            </g>
          );
        })}
      </svg>

      {/* Center Circle */}
      <div
        ref={centerBoxRef}
        onClick={(e) => {
          e.stopPropagation();
          handleCenterClick();
        }}
        className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-16 h-16 md:w-24 md:h-24 bg-white rounded-full shadow-lg grid place-items-center font-bold text-lg md:text-xl cursor-pointer z-10"
      >
        Any
      </div>

      {/* Outer Boxes */}
      {boxData.map((box, i) => {
        const angle = (360 / boxData.length) * i;
        const radius = Math.min(dimensions.width, dimensions.height) * 0.35;
        const rad = (angle * Math.PI) / 180;
        const centerX = dimensions.width / 2;
        const centerY = dimensions.height / 2;
        const x = centerX + radius * Math.cos(rad);
        const y = centerY + radius * Math.sin(rad);

        return (
          <div
            key={i}
            ref={(el) => (boxRefs.current[i] = el)}
            onClick={(e) => {
              e.stopPropagation();
              handleBoxClick(i, box.color);
            }}
            className="absolute w-14 h-14 md:w-20 md:h-20 rounded-full shadow grid place-items-center text-xs md:text-sm font-bold cursor-pointer text-white"
            style={{
              backgroundColor: box.color,
              left: `${x}px`,
              top: `${y}px`,
              transform: "translate(-50%, -50%)",
            }}
          >
            {box.label}
          </div>
        );
      })}
    </div>
  );
}

enter image description here

Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Akshay singh