shadimage

Marquee

src/components/thegridcn/marquee.tsx

"use client";

import type * as React from "react";
import { cn } from "@/lib/utils";

interface MarqueeProps extends React.HTMLAttributes<HTMLDivElement> {
  children: React.ReactNode;
  direction?: "left" | "right";
  pauseOnHover?: boolean;
  speed?: "slow" | "normal" | "fast";
  variant?: "default" | "subtle";
}

export function Marquee({
  children,
  speed = "normal",
  direction = "left",
  pauseOnHover = true,
  variant = "default",
  className,
  ...props
}: MarqueeProps) {
  const durations = { fast: "15s", normal: "25s", slow: "40s" };
  const dur = durations[speed];
  const dir = direction === "right" ? "reverse" : "normal";

  return (
    <div
      data-slot="tron-marquee"
      className={cn(
        "relative overflow-hidden",
        variant === "default"
          ? "rounded border border-primary/20 bg-card/80 py-3 backdrop-blur-sm"
          : "py-2",
        pauseOnHover && "hover:[&_.marquee-track]:pause",
        className
      )}
      {...props}
    >
      {variant === "default" && (
        <div className="pointer-events-none absolute inset-0 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(0,0,0,0.03)_2px,rgba(0,0,0,0.03)_4px)]" />
      )}

      <div className="flex gap-8">
        {[0, 1].map((copy) => (
          <div
            key={copy}
            className="marquee-track flex shrink-0 items-center gap-8"
            style={{
              animation: `marqueeScroll ${dur} linear infinite`,
              animationDirection: dir,
            }}
          >
            {children}
          </div>
        ))}
      </div>

      <style jsx>{`
        @keyframes marqueeScroll {
          0% { transform: translateX(0); }
          100% { transform: translateX(calc(-100% - 2rem)); }
        }
        .marquee-track.pause {
          animation-play-state: paused !important;
        }
      `}</style>

      {/* Fade edges */}
      <div
        className={cn(
          "pointer-events-none absolute inset-y-0 left-0 w-12 bg-gradient-to-r to-transparent",
          variant === "default" ? "from-card/80" : "from-background"
        )}
      />
      <div
        className={cn(
          "pointer-events-none absolute inset-y-0 right-0 w-12 bg-gradient-to-l to-transparent",
          variant === "default" ? "from-card/80" : "from-background"
        )}
      />
    </div>
  );
}
Interaction
marquee