# CTA section

High contrast closing CTA panel with brand glow, two actions, reassurance line and an animated beam border.

- Type: block
- Category: Conversion
- Tier: Free
- Dependencies: lucide-react, motion

## Install

```bash
npx shadcn@latest add https://facadeui.com/r/cta-section.json
```

## Usage

```tsx
import { CtaSection } from "@/components/facade/cta-section";

export default function Page() {
  return <CtaSection />;
}
```

## Source

```tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "motion/react";
import { ArrowRight, Check, Phone } from "lucide-react";
import { cn } from "@/lib/utils";

export interface CtaSectionProps {
  eyebrow?: string;
  title?: string;
  description?: string;
  primaryCta?: { label: string; href: string };
  secondaryCta?: { label: string; href: string };
  /** Short reassurance points shown below the buttons. */
  reassurance?: string[];
  className?: string;
}

const EASE = [0.22, 1, 0.36, 1] as const;

export function CtaSection({
  eyebrow = "Booking now for this week",
  title = "Your neighbors already switched. Your turn.",
  description = "Get a written, flat-rate quote in under 10 minutes. If we can't beat your last invoice, the first service call is on us.",
  primaryCta = { label: "Get my free quote", href: "#quote" },
  secondaryCta = { label: "(303) 555-0187", href: "tel:+13035550187" },
  reassurance = ["No obligation", "Licensed & insured in Colorado", "Same-day slots open"],
  className,
}: CtaSectionProps) {
  const reduce = useReducedMotion();

  return (
    <section className={cn("bg-background px-4 py-20 text-foreground sm:px-8 sm:py-28", className)}>
      <motion.div
        initial={{ opacity: 0, y: 24, scale: 0.98 }}
        whileInView={{ opacity: 1, y: 0, scale: 1 }}
        viewport={{ once: true, margin: "-80px 0px" }}
        transition={{ duration: 0.9, ease: EASE }}
        className="relative mx-auto max-w-6xl overflow-hidden rounded-[1.75rem] p-px"
      >
        {/* Animated beam border */}
        <div aria-hidden className="absolute inset-0 rounded-[1.75rem] bg-foreground/10" />
        <motion.div
          aria-hidden
          className="absolute left-1/2 top-1/2 aspect-square w-[200%] -translate-x-1/2 -translate-y-1/2"
          style={{
            background:
              "conic-gradient(from 0deg, transparent 0deg, transparent 280deg, color-mix(in oklch, var(--brand) 80%, transparent) 320deg, var(--brand-foreground) 340deg, transparent 360deg)",
          }}
          animate={reduce ? undefined : { rotate: 360 }}
          transition={{ duration: 7, repeat: Infinity, ease: "linear" }}
        />

        {/* Panel */}
        <div className="relative isolate overflow-hidden rounded-[calc(1.75rem-1px)] bg-primary px-6 py-16 text-primary-foreground sm:px-12 sm:py-20 lg:px-20 lg:py-24">
          {/* Brand glow */}
          <div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
            <motion.div
              className="absolute -top-1/2 left-1/2 h-[140%] w-[90%] -translate-x-1/2 rounded-full bg-brand/40 blur-[110px]"
              animate={reduce ? undefined : { opacity: [0.55, 0.85, 0.55], scale: [1, 1.06, 1] }}
              transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
            />
            <div
              className="absolute inset-0 [mask-image:radial-gradient(ellipse_at_center,black_20%,transparent_75%)]"
              style={{
                backgroundImage:
                  "linear-gradient(to right, var(--primary-foreground) 1px, transparent 1px), linear-gradient(to bottom, var(--primary-foreground) 1px, transparent 1px)",
                backgroundSize: "48px 48px",
                opacity: 0.08,
              }}
            />
            <div className="absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t from-primary to-transparent" />
          </div>

          <div className="mx-auto max-w-3xl text-center">
            <motion.p
              initial={{ opacity: 0, y: 8 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.6, delay: 0.2, ease: EASE }}
              className="inline-flex items-center gap-2 rounded-full border border-primary-foreground/15 bg-primary-foreground/5 px-3 py-1 text-xs font-medium text-primary-foreground/80 backdrop-blur"
            >
              <span className="relative flex size-1.5">
                <span className="absolute inline-flex size-full animate-ping-slow rounded-full bg-success" />
                <span className="relative inline-flex size-1.5 rounded-full bg-success" />
              </span>
              {eyebrow}
            </motion.p>

            <motion.h2
              initial={{ opacity: 0, y: 14 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.8, delay: 0.28, ease: EASE }}
              className="mt-6 font-display text-4xl font-semibold leading-[1.02] tracking-tight text-balance sm:text-6xl"
            >
              {title}
            </motion.h2>

            <motion.p
              initial={{ opacity: 0, y: 14 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.8, delay: 0.36, ease: EASE }}
              className="mx-auto mt-5 max-w-xl text-lg leading-relaxed text-primary-foreground/70 text-pretty"
            >
              {description}
            </motion.p>

            <motion.div
              initial={{ opacity: 0, y: 14 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.8, delay: 0.44, ease: EASE }}
              className="mt-10 flex flex-col items-stretch justify-center gap-3 sm:flex-row sm:items-center"
            >
              <a
                href={primaryCta.href}
                className="group relative inline-flex h-13 items-center justify-center gap-2 overflow-hidden rounded-full bg-brand px-7 py-3.5 text-[15px] font-semibold text-brand-foreground shadow-[0_10px_40px_-10px_var(--brand)] transition-transform hover:-translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
              >
                <span
                  aria-hidden
                  className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-brand-foreground/25 to-transparent transition-transform duration-700 group-hover:translate-x-full"
                />
                <span className="relative">{primaryCta.label}</span>
                <ArrowRight className="relative size-4 transition-transform group-hover:translate-x-0.5" aria-hidden />
              </a>
              <a
                href={secondaryCta.href}
                className="inline-flex items-center justify-center gap-2 rounded-full border border-primary-foreground/20 bg-primary-foreground/5 px-7 py-3.5 text-[15px] font-semibold text-primary-foreground backdrop-blur transition-colors hover:bg-primary-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-primary"
              >
                <Phone className="size-4" aria-hidden />
                {secondaryCta.label}
              </a>
            </motion.div>

            {reassurance.length > 0 && (
              <motion.ul
                initial={{ opacity: 0 }}
                whileInView={{ opacity: 1 }}
                viewport={{ once: true }}
                transition={{ duration: 0.8, delay: 0.6 }}
                className="mt-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-primary-foreground/60"
              >
                {reassurance.map((r) => (
                  <li key={r} className="flex items-center gap-1.5">
                    <Check className="size-3.5 text-brand" aria-hidden />
                    {r}
                  </li>
                ))}
              </motion.ul>
            )}
          </div>
        </div>
      </motion.div>
    </section>
  );
}

export default CtaSection;
```
