Web InnoventixFreeCode

Staggered stat band with marquee ticker

Original · free

A dark stat band where 3D-tilt cards stagger into view with count-up figures over an animated gradient mesh, finished by an infinite marquee ticker of highlight chips that pauses on hover.

byWeb InnoventixReact + Tailwind
animated statsstagger animationmarquee tickerframer motion metricsgradient mesh backgroundcount up stats band
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/astat-stagger-marquee.json
astat-stagger-marquee.tsx
"use client";

import { useEffect, useRef, useState } from "react";
import { animate, motion, useInView, useReducedMotion, type Variants } from "motion/react";

type Stat = {
  value: number;
  prefix?: string;
  suffix?: string;
  decimals?: number;
  label: string;
};

const stats: Stat[] = [
  { value: 3.2, prefix: "£", suffix: "M", decimals: 1, label: "Revenue processed daily" },
  { value: 240, suffix: "+", label: "Countries and regions" },
  { value: 17, suffix: "x", label: "Return on investment" },
  { value: 500, suffix: "M", label: "API calls every month" },
];

const ticker = [
  "24% higher conversion",
  "Sub-50ms responses",
  "99.99% uptime",
  "0 config deploys",
  "SOC 2 certified",
  "6,200 five-star reviews",
  "12 global regions",
  "Zero-downtime releases",
];

function formatValue(v: number, s: Stat) {
  const body = s.decimals ? v.toFixed(s.decimals) : Math.round(v).toLocaleString("en-GB");
  return `${s.prefix ?? ""}${body}${s.suffix ?? ""}`;
}

function Counter({ stat, start }: { stat: Stat; start: boolean }) {
  const reduce = useReducedMotion();
  const [display, setDisplay] = useState(0);

  useEffect(() => {
    if (!start) return;
    if (reduce) {
      setDisplay(stat.value);
      return;
    }
    const controls = animate(0, stat.value, {
      duration: 2,
      ease: [0.16, 1, 0.3, 1],
      onUpdate: (v) => setDisplay(v),
    });
    return () => controls.stop();
  }, [start, reduce, stat.value]);

  return <span className="tabular-nums">{formatValue(display, stat)}</span>;
}

export default function AstatStaggerMarquee() {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-60px" });

  const container: Variants = {
    hidden: {},
    show: { transition: { staggerChildren: 0.14, delayChildren: 0.1 } },
  };
  const item: Variants = {
    hidden: reduce ? { opacity: 0 } : { opacity: 0, y: 30, rotateX: -25 },
    show: {
      opacity: 1,
      y: 0,
      rotateX: 0,
      transition: { type: "spring", stiffness: 120, damping: 18 },
    },
  };

  return (
    <section className="relative isolate overflow-hidden bg-zinc-950 px-6 py-20 text-white md:py-28">
      <style>{`
        @keyframes astat3-marquee {
          from { transform: translateX(0); }
          to { transform: translateX(-50%); }
        }
        @keyframes astat3-drift {
          0%, 100% { transform: translate3d(0,0,0) scale(1); }
          50% { transform: translate3d(6%, -8%, 0) scale(1.15); }
        }
        @media (prefers-reduced-motion: reduce) {
          .astat3-marquee-track, .astat3-mesh { animation: none !important; }
        }
      `}</style>

      {/* animated gradient mesh */}
      <div className="pointer-events-none absolute inset-0 -z-10 opacity-70">
        <div
          className="astat3-mesh absolute -left-20 top-0 h-[420px] w-[420px] rounded-full blur-3xl [animation:astat3-drift_18s_ease-in-out_infinite]"
          style={{ background: "radial-gradient(circle, #6366f1, transparent 65%)" }}
        />
        <div
          className="astat3-mesh absolute right-0 top-10 h-[380px] w-[380px] rounded-full blur-3xl [animation:astat3-drift_22s_ease-in-out_infinite_reverse]"
          style={{ background: "radial-gradient(circle, #d946ef, transparent 65%)" }}
        />
        <div
          className="astat3-mesh absolute bottom-0 left-1/3 h-[360px] w-[360px] rounded-full blur-3xl [animation:astat3-drift_26s_ease-in-out_infinite]"
          style={{ background: "radial-gradient(circle, #06b6d4, transparent 65%)" }}
        />
      </div>
      {/* subtle grid overlay */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10 opacity-[0.08]"
        style={{
          backgroundImage:
            "linear-gradient(to right, #fff 1px, transparent 1px), linear-gradient(to bottom, #fff 1px, transparent 1px)",
          backgroundSize: "48px 48px",
        }}
      />

      <div ref={ref} className="relative mx-auto max-w-6xl">
        <motion.div
          initial={reduce ? { opacity: 0 } : { opacity: 0, y: 18 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-80px" }}
          transition={{ duration: 0.6, ease: "easeOut" }}
          className="mx-auto max-w-2xl text-center"
        >
          <p className="text-sm font-semibold uppercase tracking-widest text-fuchsia-300">
            Trusted at scale
          </p>
          <h2 className="mt-3 text-balance bg-gradient-to-r from-white via-fuchsia-100 to-indigo-200 bg-clip-text text-3xl font-bold tracking-tight text-transparent sm:text-4xl">
            The whole picture, staggered into view
          </h2>
        </motion.div>

        <motion.dl
          variants={container}
          initial="hidden"
          whileInView="show"
          viewport={{ once: true, margin: "-60px" }}
          style={{ perspective: 1000 }}
          className="mt-14 grid grid-cols-2 gap-4 lg:grid-cols-4"
        >
          {stats.map((stat) => (
            <motion.div
              key={stat.label}
              variants={item}
              whileHover={reduce ? undefined : { y: -6, scale: 1.03 }}
              className="group relative overflow-hidden rounded-2xl border border-white/10 bg-white/5 p-7 backdrop-blur-sm transition-colors hover:border-white/25 hover:bg-white/10"
            >
              <div className="pointer-events-none absolute -inset-px -z-10 rounded-2xl bg-gradient-to-br from-fuchsia-500/0 to-indigo-500/0 opacity-0 blur transition-opacity duration-500 group-hover:from-fuchsia-500/30 group-hover:to-indigo-500/30 group-hover:opacity-100" />
              <dd className="text-3xl font-bold tracking-tight sm:text-4xl">
                <Counter stat={stat} start={inView} />
              </dd>
              <dt className="mt-2 text-sm text-zinc-300">{stat.label}</dt>
            </motion.div>
          ))}
        </motion.dl>
      </div>

      {/* marquee ticker */}
      <div className="group relative mt-16 flex overflow-hidden [mask-image:linear-gradient(to_right,transparent,#000_12%,#000_88%,transparent)]">
        <div className="astat3-marquee-track flex shrink-0 items-center gap-4 pr-4 [animation:astat3-marquee_28s_linear_infinite] group-hover:[animation-play-state:paused]">
          {[...ticker, ...ticker].map((chip, i) => (
            <span
              key={i}
              className="inline-flex shrink-0 items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm text-zinc-200"
            >
              <svg viewBox="0 0 20 20" className="h-3.5 w-3.5 text-emerald-400" fill="none" aria-hidden>
                <path d="M4 11l3 3 9-9" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              {chip}
            </span>
          ))}
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →
Four Stat Metric Band

Four Stat Metric Band

Original

A four-column metric band with gradient figures inside a hairline-divided card, ideal for headline numbers like uptime, latency and growth.

Labelled Stats With Dividers

Labelled Stats With Dividers

Original

Three centred stats separated by responsive dividers, each pairing a headline figure with an uppercase label and supporting detail.

Trusted By Logo Cloud

Trusted By Logo Cloud

Original

A responsive six-up logo strip using simple inline-SVG marks and wordmarks with a grayscale-to-colour hover, for a trusted-by section.

Hero Stat With Supporting Copy

Hero Stat With Supporting Copy

Original

A two-column layout pairing one oversized hero statistic with a supporting paragraph and a checklist card explaining the number.

Count-up stats reveal

Count-up stats reveal

Original

A four-card stat band whose gradient numbers count up from zero as the section scrolls into view, with staggered blur-in cards, a pulsing eyebrow, floating aurora blobs and a shimmering underline sweep.

Animated SVG progress rings

Animated SVG progress rings

Original

Four circular SVG progress rings that draw their gradient arcs and count up to their target percentage the moment they enter view, over a slowly rotating conic halo with a soft glow pulse behind each ring.

Spotlight Hero

Spotlight Hero

Original

A centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero

Split Hero

Original

A two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero

Gradient Spotlight Hero

Original

A minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

App Preview Hero

App Preview Hero

Original

A centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero

Waitlist Capture Hero

Original

A dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

Image Backdrop Hero

Image Backdrop Hero

Original

A cinematic full-bleed hero with a layered image-style backdrop, overlaid left-aligned copy and a trusted-by logos strip.