Web InnoventixFreeCode

Stats CTA CTA

Original · free

CTA with supporting stats

byWeb InnoventixReact + Tailwind
ctaxstatscta
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/ctax-stats-cta.json
ctax-stats-cta.tsx
"use client";

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

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

const STATS: Stat[] = [
  { label: "Teams shipping with Nimbus", value: 12000, suffix: "+" },
  { label: "Median platform uptime", value: 99.98, suffix: "%", decimals: 2 },
  { label: "Faster release cycles", value: 3.4, suffix: "x", decimals: 1 },
  { label: "Native integrations", value: 150, suffix: "+" },
];

type Status = "idle" | "invalid" | "success";

function formatNumber(value: number, decimals: number): string {
  return new Intl.NumberFormat("en-US", {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals,
  }).format(value);
}

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

  useEffect(() => {
    if (!play) return;
    if (reduce) {
      setDisplay(stat.value);
      return;
    }

    let raf = 0;
    const duration = 1500;
    const start = performance.now();

    const tick = (now: number) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);
      setDisplay(stat.value * eased);
      if (t < 1) raf = requestAnimationFrame(tick);
    };

    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [play, reduce, stat.value]);

  const decimals = stat.decimals ?? 0;

  return (
    <span className="tabular-nums">
      {stat.prefix}
      {formatNumber(display, decimals)}
      {stat.suffix}
    </span>
  );
}

export default function CtaxStatsCta() {
  const reduce = useReducedMotion();
  const statsRef = useRef<HTMLDivElement | null>(null);
  const inView = useInView(statsRef, { once: true, amount: 0.4 });

  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<Status>("idle");

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
    setStatus(valid ? "success" : "invalid");
  };

  const initial = reduce ? undefined : { opacity: 0, y: 24 };
  const animate = reduce ? undefined : { opacity: 1, y: 0 };

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-24 sm:py-28 md:py-32 dark:bg-slate-950">
      <style>{`
        @keyframes ctaxsc-float {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(0, -28px, 0) scale(1.06); }
        }
        @keyframes ctaxsc-drift {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(24px, 18px, 0) scale(0.94); }
        }
        @keyframes ctaxsc-pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.55; transform: scale(0.7); }
        }
        @keyframes ctaxsc-sheen {
          0% { transform: translateX(-140%) skewX(-18deg); }
          60%, 100% { transform: translateX(260%) skewX(-18deg); }
        }
        .ctaxsc-blob-a { animation: ctaxsc-float 14s ease-in-out infinite; }
        .ctaxsc-blob-b { animation: ctaxsc-drift 18s ease-in-out infinite; }
        .ctaxsc-dot { animation: ctaxsc-pulse 2.4s ease-in-out infinite; }
        .ctaxsc-sheen { animation: ctaxsc-sheen 3.6s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .ctaxsc-blob-a,
          .ctaxsc-blob-b,
          .ctaxsc-dot,
          .ctaxsc-sheen {
            animation: none !important;
          }
        }
      `}</style>

      {/* Decorative background */}
      <div aria-hidden className="pointer-events-none absolute inset-0 overflow-hidden">
        <div className="ctaxsc-blob-a absolute -left-24 -top-24 h-80 w-80 rounded-full bg-indigo-300/40 blur-3xl dark:bg-indigo-600/25" />
        <div className="ctaxsc-blob-b absolute -bottom-32 -right-16 h-96 w-96 rounded-full bg-violet-300/40 blur-3xl dark:bg-violet-600/25" />
        <div
          className="absolute inset-0 opacity-[0.4] dark:opacity-[0.25]"
          style={{
            backgroundImage:
              "linear-gradient(to right, rgba(100,116,139,0.18) 1px, transparent 1px), linear-gradient(to bottom, rgba(100,116,139,0.18) 1px, transparent 1px)",
            backgroundSize: "48px 48px",
            maskImage:
              "radial-gradient(ellipse 70% 60% at 50% 40%, black 40%, transparent 100%)",
            WebkitMaskImage:
              "radial-gradient(ellipse 70% 60% at 50% 40%, black 40%, transparent 100%)",
          }}
        />
      </div>

      <div className="relative mx-auto max-w-5xl">
        <motion.div
          initial={initial}
          whileInView={animate}
          viewport={{ once: true, amount: 0.3 }}
          transition={{ duration: 0.6, ease: "easeOut" }}
          className="flex flex-col items-center text-center"
        >
          {/* Eyebrow badge */}
          <span className="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-white/70 px-4 py-1.5 text-xs font-semibold uppercase tracking-wider text-indigo-700 backdrop-blur dark:border-indigo-500/30 dark:bg-white/5 dark:text-indigo-300">
            <span className="ctaxsc-dot h-1.5 w-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400" />
            Ship faster, break less
          </span>

          {/* Headline */}
          <h2 className="mt-6 max-w-3xl text-balance text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl md:text-6xl dark:text-white">
            Turn scattered work into one calm, shippable pipeline
          </h2>

          {/* Subtext */}
          <p className="mt-6 max-w-2xl text-pretty text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-300">
            Nimbus unifies planning, code review, and release tracking so your
            team spends less time in stand-ups and more time shipping. Start
            free, no card and no lock-in.
          </p>

          {/* Email form */}
          <form
            onSubmit={handleSubmit}
            noValidate
            className="mt-9 flex w-full max-w-md flex-col gap-3 sm:flex-row"
          >
            <label htmlFor="ctaxsc-email" className="sr-only">
              Work email address
            </label>
            <input
              id="ctaxsc-email"
              type="email"
              inputMode="email"
              autoComplete="email"
              value={email}
              onChange={(e) => {
                setEmail(e.target.value);
                if (status !== "idle") setStatus("idle");
              }}
              placeholder="you@company.com"
              aria-invalid={status === "invalid"}
              aria-describedby="ctaxsc-status"
              className="w-full flex-1 rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/40 dark:border-slate-700 dark:bg-slate-900 dark:text-white dark:placeholder:text-slate-500"
            />
            <button
              type="submit"
              className="group relative inline-flex items-center justify-center gap-2 overflow-hidden rounded-xl bg-indigo-600 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-600/25 outline-none transition hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 active:scale-[0.98] dark:focus-visible:ring-offset-slate-950"
            >
              <span
                aria-hidden
                className="ctaxsc-sheen pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-white/25 blur-md"
              />
              <span className="relative">Start free trial</span>
              <svg
                aria-hidden
                viewBox="0 0 20 20"
                fill="none"
                className="relative h-4 w-4 transition-transform group-hover:translate-x-0.5"
              >
                <path
                  d="M4 10h11M11 5.5 15.5 10 11 14.5"
                  stroke="currentColor"
                  strokeWidth="1.75"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            </button>
          </form>

          {/* Status / microcopy */}
          <p
            id="ctaxsc-status"
            aria-live="polite"
            className="mt-3 min-h-5 text-sm"
          >
            {status === "success" ? (
              <span className="inline-flex items-center gap-1.5 font-medium text-emerald-600 dark:text-emerald-400">
                <svg aria-hidden viewBox="0 0 20 20" className="h-4 w-4" fill="none">
                  <path
                    d="m5 10.5 3 3 7-7.5"
                    stroke="currentColor"
                    strokeWidth="1.75"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
                You are in. Check your inbox to activate the trial.
              </span>
            ) : status === "invalid" ? (
              <span className="font-medium text-rose-600 dark:text-rose-400">
                Please enter a valid work email address.
              </span>
            ) : (
              <span className="text-slate-500 dark:text-slate-400">
                14-day trial · Cancel anytime · SOC 2 Type II
              </span>
            )}
          </p>

          {/* Secondary CTA */}
          <div className="mt-6">
            <a
              href="#book-demo"
              className="inline-flex items-center gap-1.5 rounded-lg px-2 py-1 text-sm font-semibold text-slate-700 underline-offset-4 outline-none transition hover:text-indigo-600 hover:underline focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-200 dark:hover:text-indigo-300"
            >
              Book a 20-minute demo
              <svg aria-hidden viewBox="0 0 20 20" fill="none" className="h-3.5 w-3.5">
                <path
                  d="M7 5.5 11.5 10 7 14.5"
                  stroke="currentColor"
                  strokeWidth="1.75"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            </a>
          </div>
        </motion.div>

        {/* Supporting stats */}
        <div
          ref={statsRef}
          className="mt-16 grid grid-cols-2 gap-px overflow-hidden rounded-2xl border border-slate-200 bg-slate-200 shadow-sm md:grid-cols-4 dark:border-slate-800 dark:bg-slate-800"
        >
          {STATS.map((stat, i) => (
            <motion.div
              key={stat.label}
              initial={initial}
              animate={inView ? animate : initial}
              transition={{ duration: 0.5, ease: "easeOut", delay: 0.08 * i }}
              className="flex flex-col items-center gap-1.5 bg-white px-4 py-8 text-center dark:bg-slate-900"
            >
              <span className="text-3xl font-bold tracking-tight text-indigo-600 sm:text-4xl dark:text-indigo-400">
                <CountUp stat={stat} play={inView} />
              </span>
              <span className="text-xs font-medium leading-snug text-slate-500 sm:text-sm dark:text-slate-400">
                {stat.label}
              </span>
            </motion.div>
          ))}
        </div>

        <p className="mt-6 text-center text-xs uppercase tracking-wider text-slate-400 dark:text-slate-500">
          Trusted by product teams in 40+ countries
        </p>
      </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 →