Blocks

CTA 67

CTA
Text Align CenterImage/Video BottomButtonsMultiple Images
CTA 67

Code

relume-cta67.tsx
import { Button, type ButtonProps } from "@/components/ui/button";

type ImageProps = {
  src: string;
  alt?: string;
};

type Props = {
  heading: string;
  description: string;
  button: string;
  images: ImageProps[];
};

export type Cta67Props = React.ComponentPropsWithoutRef<"section"> & Partial<Props>;

const ImageItem = ({ image }: { image: ImageProps }) => (
  <div className="relative w-[60vw] pt-[75%] sm:w-[18rem] md:w-[26rem]">
    <img
      className="absolute inset-0 size-full rounded-image object-cover"
      src={image.src}
      alt={image.alt}
    />
  </div>
);

export const Cta67 = (props: Cta67Props) => {
  const { heading, description, button, images } = {
    ...Cta67Defaults,
    ...props,
  };
  return (
    <section className="relative">
      <div className="flex h-[calc(100vh-8.75rem)] items-end justify-center px-[5%] pt-12 md:h-[calc(100vh-10.25rem)] md:pt-20 lg:h-auto lg:pt-28">
        <div className="flex flex-col items-center px-[5%]">
          <div className="mx-auto max-w-lg text-center">
            <h2 className="mb-5 text-h2 font-bold md:mb-6">{heading}</h2>
            <p className="text-medium">{description}</p>
          </div>
        </div>
      </div>

      <div className="pt-6 pb-16 md:pb-24 lg:pt-8 lg:pb-28">
        <div className="flex w-screen justify-start overflow-hidden">
          <div className="mt-[7.25rem] mb-12 grid shrink-0 grid-cols-1 gap-y-4 md:mt-[8.25rem] md:mb-18 lg:mt-[8.75rem] lg:mb-20">
            <div className="grid w-full animate-marquee-top auto-cols-fr grid-cols-2 gap-4 self-center">
              <div className="grid w-full grid-flow-col gap-4">
                {images.slice(0, 6).map((image, index) => (
                  <ImageItem key={index} image={image} />
                ))}
              </div>
              <div className="grid w-full grid-flow-col gap-4">
                {images.slice(6, 12).map((image, index) => (
                  <ImageItem key={index + 6} image={image} />
                ))}
              </div>
            </div>
            <div className="grid w-full animate-marquee-bottom grid-cols-2 gap-4 self-center">
              <div className="grid w-full grid-flow-col gap-4">
                {images.slice(12, 18).map((image, index) => (
                  <ImageItem key={index + 12} image={image} />
                ))}
              </div>
              <div className="grid w-full grid-flow-col gap-4">
                {images.slice(18, 24).map((image, index) => (
                  <ImageItem key={index + 18} image={image} />
                ))}
              </div>
            </div>
          </div>
        </div>
        <div className="sticky bottom-20 flex items-start justify-center">
          <Button className="px-9 py-4 text-lg">{button}</Button>
        </div>
      </div>
    </section>
  );
};

export const Cta67Defaults: Props = {
  heading: "Medium length heading goes here",
  description:
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.",
  button: "Mega Button",
  images: Array.from({ length: 24 }, (_, i) => ({
    src: "https://d22po4pjz3o32e.cloudfront.net/placeholder-image.svg",
    alt: `Relume placeholder image ${i + 1}`,
  })),
};

npm i @radix-ui/react-checkbox @radix-ui/react-label @radix-ui/react-slot class-variance-authority@0.7.1 clsx@^2.1.1 embla-carousel-react@^8.5.2 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2