body{margin:0;padding:0;-webkit-text-size-adjust:100%}a{color:inherit}:where(button,input[type=submit],input[type=reset]){max-width:100%;display:inline-flex;appearance:none;align-items:center;justify-content:center;height:fit-content;text-decoration:none;padding:calc(10px*var(--buttons-scale, 1)) calc(24px*var(--buttons-scale, 1));background:rgb(var(--color-button));color:rgba(var(--color-button-text), 1);text-align:center;font-family:var(--button-font-family);text-transform:var(--button-text-case, unset);letter-spacing:var(--button-letter-spacing-scale, 0);border-color:rgb(var(--color-button));cursor:pointer}@media screen and (min-width: 768px)and (max-width: 991.98px){:where(button,input[type=submit],input[type=reset]){padding:calc(10px*var(--buttons-scale, 1)) calc(16px*var(--buttons-scale, 1))}}:where(button,input[type=submit],input[type=reset])>span{display:block;position:relative;word-wrap:break-word;max-width:100%}table:not([class]) td,table:not([class]) th{padding:1em;border:.1rem solid rgba(var(--color-foreground), 0.2)}.shopify-challenge__container{position:fixed;left:0;top:0;right:0;bottom:0;z-index:9999;background:#fff;max-width:100%}h1,h2,h3,h4,h5,h6,.smi-h1,.smi-h2,.smi-h3,.smi-h4,.smi-h5,.smi-h6{font-family:var(--font-heading-family);font-style:var(--font-heading-style);font-weight:var(--font-heading-weight)}.smi-text-xs,.smi-text-sm,.smi-text-md,.smi-text-lg{font-family:var(--font-body-family)}.smi-text-menu{font-family:var(--menu-font-family)}.smi-text-button{font-family:var(--button-font-family)}.smi-text-button-purchase{font-family:var(--button-font-family)}.smi-text-sub{font-family:var(--subheading-font-family)}.smi-text-price-md,.smi-text-price-lg,.smi-text-price-xl{font-family:var(--price-font-family)}.focus-none{box-shadow:none !important;outline:0 !important}
.animated-gradient-text {
  font-size: 1.8rem;
  font-weight: 500;
  text-align: center;
  background-image: linear-gradient(to right, #DFFF00, #EFCCFE, #DFFF00);
  background-size: 300% 100%;
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  animation: gradientMove 6s linear infinite;
}

@keyframes gradientMove {
  0% { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}body
import {
  useRef,
  useEffect,
  useState,
  useMemo,
  useId
} from "react";
import "./CurvedLoop.css";

const CurvedLoop = ({
  marqueeText = "",
  speed = 2,
  className,
  curveAmount = 400,
  direction = "left",
  interactive = true,
}) => {
  const text = useMemo(() => {
    const hasTrailing = /\s|\u00A0$/.test(marqueeText);
    return (
      (hasTrailing ? marqueeText.replace(/\s+$/, "") : marqueeText) + "\u00A0"
    );
  }, [marqueeText]);

  const measureRef = useRef(null);
  const tspansRef = useRef([]);
  const pathRef = useRef(null);
  const [pathLength, setPathLength] = useState(0);
  const [spacing, setSpacing] = useState(0);
  const uid = useId();
  const pathId = `curve-${uid}`;
  const pathD = `M-100,40 Q500,${40 + curveAmount} 1540,40`;

  const dragRef = useRef(false);
  const lastXRef = useRef(0);
  const dirRef = useRef(direction);
  const velRef = useRef(0);

  useEffect(() => {
    if (measureRef.current)
      setSpacing(measureRef.current.getComputedTextLength());
  }, [text, className]);

  useEffect(() => {
    if (pathRef.current) setPathLength(pathRef.current.getTotalLength());
  }, [curveAmount]);

  useEffect(() => {
    if (!spacing) return;
    let frame = 0;
    const step = () => {
      tspansRef.current.forEach((t) => {
        if (!t) return;
        let x = parseFloat(t.getAttribute("x") || "0");
        if (!dragRef.current) {
          const delta =
            dirRef.current === "right" ? Math.abs(speed) : -Math.abs(speed);
          x += delta;
        }
        const maxX = (tspansRef.current.length - 1) * spacing;
        if (x < -spacing) x = maxX;
        if (x > maxX) x = -spacing;
        t.setAttribute("x", x.toString());
      });
      frame = requestAnimationFrame(step);
    };
    step();
    return () => cancelAnimationFrame(frame);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [spacing, speed]);

  const repeats =
    pathLength && spacing ? Math.ceil(pathLength / spacing) + 2 : 0;
  const ready = pathLength > 0 && spacing > 0;

  const onPointerDown = (e) => {
    if (!interactive) return;
    dragRef.current = true;
    lastXRef.current = e.clientX;
    velRef.current = 0;
    (e.target).setPointerCapture(e.pointerId);
  };

  const onPointerMove = (e) => {
    if (!interactive || !dragRef.current) return;
    const dx = e.clientX - lastXRef.current;
    lastXRef.current = e.clientX;
    velRef.current = dx;
    tspansRef.current.forEach((t) => {
      if (!t) return;
      let x = parseFloat(t.getAttribute("x") || "0");
      x += dx;
      const maxX = (tspansRef.current.length - 1) * spacing;
      if (x < -spacing) x = maxX;
      if (x > maxX) x = -spacing;
      t.setAttribute("x", x.toString());
    });
  };

  const endDrag = () => {
    if (!interactive) return;
    dragRef.current = false;
    dirRef.current = velRef.current > 0 ? "right" : "left";
  };

  const cursorStyle = interactive
    ? dragRef.current
      ? "grabbing"
      : "grab"
    : "auto";

  return (
    <div
      className="curved-loop-jacket"
      style={{ visibility: ready ? "visible" : "hidden", cursor: cursorStyle }}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={endDrag}
      onPointerLeave={endDrag}
    >
      <svg className="curved-loop-svg" viewBox="0 0 1440 120">
        <text
          ref={measureRef}
          xmlSpace="preserve"
          style={{ visibility: "hidden", opacity: 0, pointerEvents: "none" }}
        >
          {text}
        </text>
        <defs>
          <path
            ref={pathRef}
            id={pathId}
            d={pathD}
            fill="none"
            stroke="transparent"
          />
        </defs>
        {ready && (
          <text fontWeight="bold" xmlSpace="preserve" className={className}>
            <textPath href={`#${pathId}`} xmlSpace="preserve">
              {Array.from({ length: repeats }).map((_, i) => (
                <tspan
                  key={i}
                  x={i * spacing}
                  ref={(el) => {
                    if (el) tspansRef.current[i] = el;
                  }}
                >
                  {text}
                </tspan>
              ))}
            </textPath>
          </text>
        )}
      </svg>
    </div>
  );
};

export default CurvedLoop;
