Web InnoventixFreeCode

Dark CTA

Original · free

dark CTA section

byWeb InnoventixReact + Tailwind
ctaxdarkcta
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-dark.json
ctax-dark.tsx
"use client";

import {
  useId,
  useRef,
  useState,
  type FormEvent,
  type KeyboardEvent,
} from "react";
import { motion, useReducedMotion, type MotionProps } from "motion/react";

type Audience = "startups" | "enterprise";
type Status = "idle" | "invalid" | "success";

type Stat = { value: string; label: string };

type AudienceCopy = {
  tab: string;
  heading: string;
  sub: string;
  stats: Stat[];
};

const TABS: Audience[] = ["startups", "enterprise"];

const AUDIENCES: Record<Audience, AudienceCopy> = {
  startups: {
    tab: "For startups",
    heading: "Launch faster than your runway shrinks.",
    sub: "Push to a global edge in minutes, scale straight through your first ten thousand users, and stop babysitting infrastructure you were never hired to run.",
    stats: [
      { value: "4 min", label: "median first deploy" },
      { value: "12k+", label: "teams on the free tier" },
      { value: "99.99%", label: "uptime, tracked in public" },
    ],
  },
  enterprise: {
    tab: "For enterprise",
    heading: "Standardize every deploy across every team.",
    sub: "Role-based access, immutable audit logs, private networking, and a senior on-call line — all governed from a single control plane your security team will actually sign off on.",
    stats: [
      { value: "SOC 2", label: "Type II certified" },
      { value: "50 ms", label: "p95 API latency" },
      { value: "24 / 7", label: "senior on-call support" },
    ],
  },
};

const AVATAR_GRADIENTS = [
  "from-indigo-400 to-violet-500",
  "from-sky-400 to-indigo-500",
  "from-violet-400 to-rose-400",
  "from-emerald-400 to-sky-500",
];

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export default function CtaxDark() {
  const prefersReduced = useReducedMotion();
  const uid = useId();

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

  const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);

  const copy = AUDIENCES[audience];
  const emailId = `${uid}-email`;
  const errId = `${uid}-email-error`;
  const panelId = `${uid}-panel`;

  const reveal = (delay: number): MotionProps =>
    prefersReduced
      ? {}
      : {
          initial: { opacity: 0, y: 24 },
          whileInView: { opacity: 1, y: 0 },
          viewport: { once: true, margin: "-60px" },
          transition: { duration: 0.6, delay, ease: "easeOut" },
        };

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (EMAIL_RE.test(email.trim())) {
      setStatus("success");
    } else {
      setStatus("invalid");
    }
  };

  const handleTabKey = (
    e: KeyboardEvent<HTMLButtonElement>,
    index: number,
  ) => {
    const keys = ["ArrowRight", "ArrowLeft", "Home", "End"];
    if (!keys.includes(e.key)) return;
    e.preventDefault();
    let next = index;
    if (e.key === "ArrowRight") next = (index + 1) % TABS.length;
    else if (e.key === "ArrowLeft") next = (index - 1 + TABS.length) % TABS.length;
    else if (e.key === "Home") next = 0;
    else next = TABS.length - 1;
    setAudience(TABS[next]);
    tabRefs.current[next]?.focus();
  };

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-4 py-24 dark:bg-slate-950 sm:py-32">
      <style>{`
        @keyframes ctaxdk-drift-a {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(26px, -30px, 0) scale(1.08); }
        }
        @keyframes ctaxdk-drift-b {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(-28px, 24px, 0) scale(1.1); }
        }
        @keyframes ctaxdk-spin {
          to { transform: rotate(360deg); }
        }
        @keyframes ctaxdk-pulse {
          0%, 100% { opacity: 1; transform: scale(1); }
          50% { opacity: 0.35; transform: scale(0.65); }
        }
        @keyframes ctaxdk-shimmer {
          0% { background-position: 0% 50%; }
          100% { background-position: 200% 50%; }
        }
        .ctaxdk-orb-a { animation: ctaxdk-drift-a 16s ease-in-out infinite; }
        .ctaxdk-orb-b { animation: ctaxdk-drift-b 19s ease-in-out infinite; }
        .ctaxdk-sheen { animation: ctaxdk-spin 26s linear infinite; }
        .ctaxdk-dot { animation: ctaxdk-pulse 2.4s ease-in-out infinite; }
        .ctaxdk-shimmer {
          background-size: 200% auto;
          animation: ctaxdk-shimmer 5s linear infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .ctaxdk-orb-a,
          .ctaxdk-orb-b,
          .ctaxdk-sheen,
          .ctaxdk-dot,
          .ctaxdk-shimmer {
            animation: none !important;
          }
        }
      `}</style>

      <div className="relative mx-auto max-w-6xl">
        {/* soft colored glow behind the dark panel */}
        <div
          aria-hidden="true"
          className="pointer-events-none absolute -inset-x-6 -bottom-12 -top-12"
        >
          <div className="ctaxdk-orb-a absolute left-[8%] top-0 h-56 w-56 rounded-full bg-indigo-500/30 blur-3xl dark:bg-indigo-500/20" />
          <div className="ctaxdk-orb-b absolute bottom-0 right-[6%] h-64 w-64 rounded-full bg-violet-500/30 blur-3xl dark:bg-violet-500/20" />
        </div>

        <motion.div
          {...reveal(0)}
          className="relative overflow-hidden rounded-[2rem] border border-white/10 bg-slate-900 px-6 py-14 shadow-2xl shadow-slate-950/40 sm:px-12 sm:py-16 lg:px-16"
        >
          {/* inner ambient decoration (dark in both themes by design) */}
          <div aria-hidden="true" className="pointer-events-none absolute inset-0">
            {/* rotating conic sheen */}
            <div className="ctaxdk-sheen absolute left-1/2 top-1/2 h-[140%] w-[140%] -translate-x-1/2 -translate-y-1/2 opacity-40 [background:conic-gradient(from_0deg,transparent_0deg,rgba(99,102,241,0.25)_60deg,transparent_140deg,rgba(139,92,246,0.22)_260deg,transparent_360deg)]" />
            {/* drifting orbs */}
            <div className="ctaxdk-orb-a absolute -left-10 top-4 h-52 w-52 rounded-full bg-indigo-600/40 blur-3xl" />
            <div className="ctaxdk-orb-b absolute -right-8 bottom-2 h-56 w-56 rounded-full bg-violet-600/40 blur-3xl" />
            {/* fine grid, faded toward edges */}
            <div
              className="absolute inset-0 opacity-[0.18] [background-image:linear-gradient(to_right,rgba(255,255,255,0.6)_1px,transparent_1px),linear-gradient(to_bottom,rgba(255,255,255,0.6)_1px,transparent_1px)] [background-size:44px_44px]"
              style={{
                maskImage:
                  "radial-gradient(120% 120% at 50% 0%, black 20%, transparent 72%)",
                WebkitMaskImage:
                  "radial-gradient(120% 120% at 50% 0%, black 20%, transparent 72%)",
              }}
            />
            {/* top hairline highlight */}
            <div className="absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-white/40 to-transparent" />
          </div>

          <div className="relative z-10 mx-auto max-w-3xl text-center">
            {/* eyebrow badge */}
            <motion.span
              {...reveal(0.05)}
              className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3.5 py-1.5 text-xs font-medium text-slate-300 backdrop-blur"
            >
              <span className="relative flex h-2 w-2">
                <span className="ctaxdk-dot absolute inline-flex h-full w-full rounded-full bg-emerald-400" />
                <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" />
              </span>
              Free tier forever · No credit card required
            </motion.span>

            {/* audience tabs */}
            <motion.div {...reveal(0.1)} className="mt-6 flex justify-center">
              <div
                role="tablist"
                aria-label="Choose your team size"
                className="inline-flex rounded-full border border-white/10 bg-white/5 p-1 backdrop-blur"
              >
                {TABS.map((key, i) => {
                  const selected = key === audience;
                  return (
                    <button
                      key={key}
                      ref={(el) => {
                        tabRefs.current[i] = el;
                      }}
                      role="tab"
                      id={`${uid}-tab-${key}`}
                      type="button"
                      aria-selected={selected}
                      aria-controls={panelId}
                      tabIndex={selected ? 0 : -1}
                      onClick={() => setAudience(key)}
                      onKeyDown={(e) => handleTabKey(e, i)}
                      className={`rounded-full px-4 py-1.5 text-sm font-medium transition focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 ${
                        selected
                          ? "bg-white text-slate-900 shadow-sm"
                          : "text-slate-300 hover:text-white"
                      }`}
                    >
                      {AUDIENCES[key].tab}
                    </button>
                  );
                })}
              </div>
            </motion.div>

            {/* headline + sub (tab panel) */}
            <div
              role="tabpanel"
              id={panelId}
              aria-labelledby={`${uid}-tab-${audience}`}
            >
              <motion.h2
                key={`h-${audience}`}
                initial={prefersReduced ? false : { opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.35, ease: "easeOut" }}
                className="mt-7 text-balance text-4xl font-semibold tracking-tight text-white sm:text-5xl"
              >
                {copy.heading}
              </motion.h2>
              <motion.p
                key={`p-${audience}`}
                initial={prefersReduced ? false : { opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.35, delay: 0.05, ease: "easeOut" }}
                className="mx-auto mt-5 max-w-2xl text-pretty text-base leading-relaxed text-slate-300 sm:text-lg"
              >
                {copy.sub}
              </motion.p>
            </div>

            {/* email capture / success */}
            <motion.div {...reveal(0.15)}>
              {status === "success" ? (
                <div
                  role="status"
                  className="mx-auto mt-9 flex max-w-md items-center gap-3 rounded-2xl border border-emerald-400/25 bg-emerald-400/10 px-5 py-4 text-left"
                >
                  <span className="flex h-9 w-9 flex-none items-center justify-center rounded-full bg-emerald-400/20 text-emerald-300">
                    <svg
                      viewBox="0 0 20 20"
                      fill="none"
                      className="h-5 w-5"
                      aria-hidden="true"
                    >
                      <path
                        d="M4.5 10.5l3.5 3.5 7.5-8"
                        stroke="currentColor"
                        strokeWidth="2"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                  </span>
                  <div className="min-w-0">
                    <p className="text-sm font-semibold text-emerald-100">
                      You&rsquo;re on the list.
                    </p>
                    <p className="truncate text-sm text-emerald-100/70">
                      We sent a magic link to {email.trim()}.
                    </p>
                  </div>
                  <button
                    type="button"
                    onClick={() => {
                      setStatus("idle");
                      setEmail("");
                    }}
                    className="ml-auto flex-none rounded-md px-2 py-1 text-xs font-medium text-emerald-200/80 underline-offset-2 hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-400"
                  >
                    Undo
                  </button>
                </div>
              ) : (
                <>
                  <form
                    onSubmit={handleSubmit}
                    noValidate
                    className="mx-auto mt-9 flex w-full max-w-md flex-col gap-3 sm:flex-row"
                  >
                    <div className="flex-1 text-left">
                      <label htmlFor={emailId} className="sr-only">
                        Work email
                      </label>
                      <input
                        id={emailId}
                        name="email"
                        type="email"
                        inputMode="email"
                        autoComplete="email"
                        placeholder="you@company.com"
                        value={email}
                        onChange={(e) => {
                          setEmail(e.target.value);
                          if (status !== "idle") setStatus("idle");
                        }}
                        aria-invalid={status === "invalid"}
                        aria-describedby={status === "invalid" ? errId : undefined}
                        className={`h-12 w-full rounded-xl border bg-white/5 px-4 text-sm text-white placeholder-slate-400 outline-none transition focus:bg-white/10 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 ${
                          status === "invalid"
                            ? "border-rose-400/60 focus-visible:ring-rose-400"
                            : "border-white/10 focus-visible:ring-indigo-400"
                        }`}
                      />
                    </div>
                    <button
                      type="submit"
                      className="group relative inline-flex h-12 flex-none items-center justify-center gap-2 overflow-hidden rounded-xl bg-gradient-to-r from-indigo-500 to-violet-500 px-6 text-sm font-semibold text-white shadow-lg shadow-indigo-500/30 transition hover:from-indigo-400 hover:to-violet-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-300 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
                    >
                      Start building free
                      <svg
                        viewBox="0 0 20 20"
                        fill="none"
                        aria-hidden="true"
                        className="h-4 w-4 transition-transform duration-200 group-hover:translate-x-0.5"
                      >
                        <path
                          d="M4 10h11m0 0l-4-4m4 4l-4 4"
                          stroke="currentColor"
                          strokeWidth="2"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                        />
                      </svg>
                    </button>
                  </form>
                  {status === "invalid" && (
                    <p
                      id={errId}
                      role="alert"
                      className="mx-auto mt-2.5 max-w-md text-left text-sm text-rose-300"
                    >
                      Please enter a valid email address.
                    </p>
                  )}
                  <p className="mt-3 text-xs text-slate-400">
                    14-day Pro trial · Cancel anytime · SOC 2 Type II
                  </p>
                </>
              )}
            </motion.div>

            {/* stats */}
            <motion.dl
              {...reveal(0.2)}
              className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/5 sm:grid-cols-3"
            >
              {copy.stats.map((s) => (
                <div key={s.label} className="bg-slate-900/40 px-6 py-6 text-center">
                  <dt className="sr-only">{s.label}</dt>
                  <dd>
                    <span className="block text-2xl font-semibold text-white">
                      {s.value}
                    </span>
                    <span className="mt-1 block text-xs text-slate-400">
                      {s.label}
                    </span>
                  </dd>
                </div>
              ))}
            </motion.dl>

            {/* social proof + secondary CTA */}
            <motion.div
              {...reveal(0.25)}
              className="mt-10 flex flex-col items-center gap-5 sm:flex-row sm:justify-center sm:gap-6"
            >
              <div className="flex items-center gap-3">
                <div className="flex -space-x-2" aria-hidden="true">
                  {AVATAR_GRADIENTS.map((g, i) => (
                    <span
                      key={i}
                      className={`h-8 w-8 rounded-full bg-gradient-to-br ${g} ring-2 ring-slate-900`}
                    />
                  ))}
                </div>
                <p className="text-sm text-slate-400">
                  <span className="ctaxdk-shimmer bg-gradient-to-r from-indigo-300 via-white to-violet-300 bg-clip-text font-semibold text-transparent">
                    2,400 teams
                  </span>{" "}
                  shipped to Orbit this week
                </p>
              </div>

              <a
                href="#book-a-demo"
                className="inline-flex items-center gap-1.5 rounded-xl border border-white/15 bg-white/0 px-5 py-2.5 text-sm font-medium text-slate-200 transition hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
              >
                Book a demo
                <svg
                  viewBox="0 0 20 20"
                  fill="none"
                  aria-hidden="true"
                  className="h-4 w-4"
                >
                  <path
                    d="M7 5l5 5-5 5"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              </a>
            </motion.div>
          </div>
        </motion.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 →