Retour au catalogue

Marquee 3D

Marquee avec rotation perspective 3D sur les elements.

marqueesimple Both Responsive a11y
minimaluniversalstacked
Theme
"use client";

import { Sparkles } from "lucide-react";

interface Marquee3DItem {
  text: string;
  highlight?: boolean;
}

interface Marquee3DProps {
  items?: Marquee3DItem[];
  speed?: number;
}

const mockItems: Marquee3DItem[] = [
  { text: "Design Systems" },
  { text: "UI Engineering", highlight: true },
  { text: "Creative Dev" },
  { text: "Motion Design", highlight: true },
  { text: "Brand Identity" },
  { text: "Web Performance" },
  { text: "Accessibility", highlight: true },
  { text: "Prototyping" },
  { text: "Design Tokens" },
  { text: "Component Library", highlight: true },
];

export default function Marquee3D({
  items = mockItems,
  speed = 30,
}: Marquee3DProps) {
  const doubled = [...items, ...items];

  return (
    <section
      className="overflow-hidden"
      style={{
        padding: "5rem 0",
        background: "var(--color-background-alt)",
        perspective: "600px",
      }}
    >
      <div
        className="mb-10 text-center"
        style={{ perspectiveOrigin: "center center" }}
      >
        <p
          className="text-xs font-semibold uppercase tracking-widest"
          style={{ color: "var(--color-foreground-light)" }}
        >
          Nos expertises
        </p>
      </div>

      {/* Row 1 - tilted forward */}
      <div
        className="overflow-hidden mb-4"
        style={{
          transform: "rotateX(8deg) rotateY(-2deg)",
          transformStyle: "preserve-3d",
        }}
      >
        <div
          className="flex gap-4 whitespace-nowrap"
          style={{ animation: `marquee-3d-left ${speed}s linear infinite` }}
        >
          {doubled.map((item, i) => (
            <div
              key={`row1-${i}`}
              className="inline-flex items-center gap-2 px-6 py-3 rounded-xl shrink-0 transition-shadow duration-300"
              style={{
                background: item.highlight ? "var(--color-accent)" : "var(--color-background-card)",
                border: `1px solid ${item.highlight ? "var(--color-accent)" : "var(--color-border)"}`,
                color: item.highlight ? "var(--color-background)" : "var(--color-foreground)",
                boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
              }}
            >
              {item.highlight && <Sparkles size={14} />}
              <span className="text-sm font-semibold tracking-wide">
                {item.text}
              </span>
            </div>
          ))}
        </div>
      </div>

      {/* Row 2 - tilted differently, reverse direction */}
      <div
        className="overflow-hidden"
        style={{
          transform: "rotateX(4deg) rotateY(3deg)",
          transformStyle: "preserve-3d",
        }}
      >
        <div
          className="flex gap-4 whitespace-nowrap"
          style={{ animation: `marquee-3d-right ${speed * 1.2}s linear infinite` }}
        >
          {[...doubled].reverse().map((item, i) => (
            <div
              key={`row2-${i}`}
              className="inline-flex items-center gap-2 px-6 py-3 rounded-xl shrink-0"
              style={{
                background: "var(--color-background-card)",
                border: "1px solid var(--color-border)",
                color: "var(--color-foreground-muted)",
                boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
              }}
            >
              <span className="text-sm font-medium tracking-wide">
                {item.text}
              </span>
            </div>
          ))}
        </div>
      </div>

      <style>{`
        @keyframes marquee-3d-left {
          from { transform: translateX(0); }
          to   { transform: translateX(-50%); }
        }
        @keyframes marquee-3d-right {
          from { transform: translateX(-50%); }
          to   { transform: translateX(0); }
        }
      `}</style>
    </section>
  );
}

Avis

Marquee 3D — React Marquee Section — Incubator