Initial commit

This commit is contained in:
2026-05-05 11:59:18 +03:00
commit 531f44130e
265 changed files with 32551 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
import { motion } from "motion/react";
import type { LucideIcon } from "lucide-react";
type AboutFeaturesSplitProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: { icon: string | LucideIcon; title: string; description: string }[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const AboutFeaturesSplit = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
imageSrc,
videoSrc,
}: AboutFeaturesSplitProps) => {
return (
<section
data-webild-section="AboutFeaturesSplit"
aria-label="About section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 mx-auto w-content-width">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="flex flex-col md:flex-row md:items-stretch gap-5">
<div className="flex flex-col justify-center gap-5 p-5 w-full md:w-4/10 2xl:w-3/10 card rounded-theme">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-center shrink-0 mb-1 size-8 primary-button rounded-theme">
{typeof item.icon === "function" ? (
<item.icon className="h-2/5 w-2/5 text-primary-cta-text" strokeWidth={1.5} />
) : (
<span className="text-sm text-primary-cta-text">{index + 1}</span>
)}
</div>
<h3 className="text-xl font-medium">{item.title}</h3>
<p className="text-base leading-tight">{item.description}</p>
</div>
{index < items.length - 1 && (
<div className="mt-5 border-b border-accent/40" />
)}
</motion.div>
))}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="p-5 w-full md:w-6/10 2xl:w-7/10 h-80 md:h-auto card rounded-theme overflow-hidden"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="About video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
</div>
</div>
</section>
);
};
export default AboutFeaturesSplit;

View File

@@ -0,0 +1,107 @@
import { motion } from "motion/react";
type AboutMediaOverlayProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const AboutMediaOverlay = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
}: AboutMediaOverlayProps) => {
return (
<section
data-webild-section="AboutMediaOverlay"
aria-label="About section"
className="relative w-full py-16 md:py-24"
>
<div className="relative flex items-center justify-center py-8 md:py-8 mx-auto w-content-width rounded-theme overflow-hidden">
<div className="absolute inset-0">
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="About video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
<div className="absolute inset-0 bg-foreground/40 backdrop-blur-xs pointer-events-none select-none" />
</div>
<div className="relative z-10 flex items-center justify-center px-5 py-8 mx-auto min-h-100 md:min-h-120 md:w-1/2 w-content-width">
<div className="flex flex-col items-center gap-2 text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="mb-1 px-3 py-1 text-sm card rounded-full"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance text-primary-cta-text"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-primary-cta-text"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
</div>
</div>
</section>
);
};
export default AboutMediaOverlay;

View File

@@ -0,0 +1,90 @@
import { motion } from "motion/react";
import { Quote } from "lucide-react";
type AboutTestimonialProps = {
tag: string;
quote: string;
author: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const AboutTestimonial = ({
tag,
quote,
author,
role,
imageSrc,
videoSrc,
}: AboutTestimonialProps) => {
return (
<section
data-webild-section="AboutTestimonial"
aria-label="Testimonial section"
className="relative w-full py-16 md:py-24"
>
<div className="grid grid-cols-1 md:grid-cols-5 gap-5 mx-auto w-content-width">
<div className="relative md:col-span-3 p-8 card rounded-theme">
<div className="absolute flex items-center justify-center -top-7 -left-7 md:-top-8 md:-left-8 size-12 primary-button rounded-theme">
<Quote className="size-5 text-primary-cta-text" strokeWidth={1.5} />
</div>
<div className="relative flex flex-col justify-center gap-5 py-8 h-full">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-fit px-3 py-1 mb-1 text-sm card rounded-full"
>
{tag}
</motion.span>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.1, ease: "easeOut" }}
className="text-3xl md:text-4xl font-medium leading-tight"
>
{quote}
</motion.p>
<div className="flex items-center gap-2">
<span className="text-base">{author}</span>
<span className="text-accent"></span>
<span className="text-base">{role}</span>
</div>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="md:col-span-2 aspect-square md:aspect-auto md:h-full card rounded-theme overflow-hidden"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Testimonial video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</motion.div>
</div>
</section>
);
};
export default AboutTestimonial;

View File

@@ -0,0 +1,56 @@
import { motion } from "motion/react";
interface AboutTextProps {
title: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
}
const AboutText = ({
title,
primaryButton,
secondaryButton,
}: AboutTextProps) => {
return (
<section
data-webild-section="AboutText"
aria-label="About section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-3 items-center">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="text-2xl md:text-5xl font-medium text-center leading-tight text-balance"
>
{title}
</motion.h2>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap gap-3 justify-center mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
</section>
);
};
export default AboutText;

View File

@@ -0,0 +1,79 @@
import { motion } from "motion/react";
interface AboutTextSplitProps {
title: string;
descriptions: string[];
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
}
const AboutTextSplit = ({
title,
descriptions,
primaryButton,
secondaryButton,
}: AboutTextSplitProps) => {
return (
<section
data-webild-section="AboutTextSplit"
aria-label="About section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 mx-auto w-content-width">
<div className="flex flex-col md:flex-row gap-3 md:gap-8">
<div className="w-full md:w-1/2">
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="text-7xl font-medium"
>
{title}
</motion.h2>
</div>
<div className="flex flex-col gap-5 w-full md:w-1/2">
{descriptions.map((desc, index) => (
<motion.p
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="text-base md:text-2xl leading-tight"
>
{desc}
</motion.p>
))}
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap max-md:justify-center gap-5">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
</div>
<div className="w-full border-b border-foreground/10" />
</div>
</section>
);
};
export default AboutTextSplit;

View File

@@ -0,0 +1,148 @@
import { motion } from "motion/react";
import { ArrowUpRight } from "lucide-react";
type BlogItem = {
category: string;
title: string;
excerpt: string;
authorName: string;
authorImageSrc: string;
date: string;
imageSrc: string;
href?: string;
onClick?: () => void;
};
type BlogMediaCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items?: BlogItem[];
};
const BlogMediaCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: BlogMediaCardsProps) => {
if (!items || items.length === 0) {
return null;
}
return (
<section
data-webild-section="BlogMediaCards"
aria-label="Blog section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{items.map((item, index) => (
<motion.a
key={index}
href={item.href ?? "#"}
onClick={item.onClick}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="card group flex flex-col justify-between gap-5 p-5 rounded-theme cursor-pointer"
>
<div className="flex flex-col gap-2">
<span className="card w-fit rounded-full px-2 py-0.5 text-xs mb-0.5">{item.category}</span>
<h3 className="text-2xl md:text-3xl font-medium leading-tight line-clamp-2">{item.title}</h3>
<p className="text-sm leading-tight opacity-75 line-clamp-2">{item.excerpt}</p>
<div className="flex items-center gap-3 mt-1">
<img
src={item.authorImageSrc}
alt={item.authorName}
className="size-8 rounded-full object-cover"
/>
<div className="flex flex-col">
<span className="text-sm font-medium">{item.authorName}</span>
<span className="text-xs opacity-75">{item.date}</span>
</div>
</div>
</div>
<div className="relative aspect-square rounded-theme overflow-hidden">
<img
src={item.imageSrc}
alt={item.title}
className="size-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute inset-0 flex items-center justify-center group-hover:bg-background/20 group-hover:backdrop-blur-xs transition-all duration-300">
<span className="primary-button flex items-center justify-center size-8 rounded-full opacity-0 group-hover:opacity-100 scale-75 group-hover:scale-100 transition-all duration-300">
<ArrowUpRight className="size-4 text-primary-cta-text" strokeWidth={2} />
</span>
</div>
</div>
</motion.a>
))}
</div>
</div>
</section>
);
};
export default BlogMediaCards;

View File

@@ -0,0 +1,151 @@
import { motion } from "motion/react";
import { ArrowUpRight } from "lucide-react";
type BlogItem = {
category: string;
title: string;
excerpt: string;
authorName: string;
authorImageSrc: string;
date: string;
imageSrc: string;
href?: string;
onClick?: () => void;
};
type BlogSimpleCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items?: BlogItem[];
};
const BlogSimpleCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: BlogSimpleCardsProps) => {
if (!items || items.length === 0) {
return null;
}
return (
<section
data-webild-section="BlogSimpleCards"
aria-label="Blog section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{items.map((item, index) => (
<motion.a
key={index}
href={item.href ?? "#"}
onClick={item.onClick}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="card group flex flex-col gap-5 p-5 rounded-theme cursor-pointer"
>
<div className="relative aspect-4/3 rounded-theme overflow-hidden">
<img
src={item.imageSrc}
alt={item.title}
className="size-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute inset-0 flex items-center justify-center group-hover:bg-background/20 group-hover:backdrop-blur-xs transition-all duration-300">
<span className="primary-button flex items-center justify-center size-8 rounded-full opacity-0 group-hover:opacity-100 scale-75 group-hover:scale-100 transition-all duration-300">
<ArrowUpRight className="size-4 text-primary-cta-text" strokeWidth={2} />
</span>
</div>
</div>
<div className="flex flex-1 flex-col justify-between gap-5">
<div className="flex flex-col gap-2">
<span className="primary-button w-fit rounded-full px-2 py-0.5 text-xs text-primary-cta-text">
{item.category}
</span>
<h3 className="text-xl font-medium leading-tight mt-1">{item.title}</h3>
<p className="text-sm leading-tight opacity-75">{item.excerpt}</p>
</div>
<div className="flex items-center gap-3">
<img
src={item.authorImageSrc}
alt={item.authorName}
className="size-8 rounded-full object-cover"
/>
<div className="flex flex-col">
<span className="text-sm font-medium">{item.authorName}</span>
<span className="text-xs opacity-75">{item.date}</span>
</div>
</div>
</div>
</motion.a>
))}
</div>
</div>
</section>
);
};
export default BlogSimpleCards;

View File

@@ -0,0 +1,158 @@
import { motion } from "motion/react";
import { ArrowUpRight } from "lucide-react";
type BlogItem = {
title: string;
excerpt: string;
authorName: string;
authorImageSrc: string;
date: string;
tags: string[];
imageSrc: string;
href?: string;
onClick?: () => void;
};
type BlogTagCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items?: BlogItem[];
};
const BlogTagCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: BlogTagCardsProps) => {
if (!items || items.length === 0) {
return null;
}
return (
<section
data-webild-section="BlogTagCards"
aria-label="Blog section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{items.map((item, index) => (
<motion.a
key={index}
href={item.href ?? "#"}
onClick={item.onClick}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="card group flex flex-col gap-5 p-5 rounded-theme cursor-pointer"
>
<div className="relative aspect-4/3 rounded-theme overflow-hidden">
<img
src={item.imageSrc}
alt={item.title}
className="size-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute inset-0 flex items-center justify-center group-hover:bg-background/20 group-hover:backdrop-blur-xs transition-all duration-300">
<span className="primary-button flex items-center justify-center size-8 rounded-full opacity-0 group-hover:opacity-100 scale-75 group-hover:scale-100 transition-all duration-300">
<ArrowUpRight className="size-4 text-primary-cta-text" strokeWidth={2} />
</span>
</div>
</div>
<div className="flex flex-1 flex-col justify-between gap-5">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<img
src={item.authorImageSrc}
alt={item.authorName}
className="size-5 rounded-full object-cover"
/>
<span className="text-xs opacity-75">
{item.authorName} {item.date}
</span>
</div>
<h3 className="text-xl font-medium leading-tight">{item.title}</h3>
<p className="text-sm leading-tight opacity-75">{item.excerpt}</p>
</div>
<div className="flex flex-wrap gap-2">
{item.tags.map((itemTag) => (
<span
key={itemTag}
className="primary-button rounded-full px-2 py-0.5 text-xs text-primary-cta-text"
>
{itemTag}
</span>
))}
</div>
</div>
</motion.a>
))}
</div>
</div>
</section>
);
};
export default BlogTagCards;

View File

@@ -0,0 +1,82 @@
import { motion } from "motion/react";
const ContactCenter = ({
tag,
title,
description,
inputPlaceholder,
buttonText,
termsText,
}: {
tag: string;
title: string;
description: string;
inputPlaceholder: string;
buttonText: string;
termsText?: string;
}) => {
return (
<section
data-webild-section="ContactCenter"
aria-label="Contact section"
className="relative w-full py-20"
>
<div className="w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex items-center justify-center py-20 card rounded-theme"
>
<div className="flex flex-col items-center w-full md:w-1/2 gap-3 px-5">
<span className="card rounded-full px-3 py-1 text-sm">{tag}</span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-5xl md:text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-8/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
<form className="flex flex-col md:flex-row w-full md:w-8/10 2xl:w-6/10 gap-3 md:gap-1 p-1 mt-2 card rounded-theme">
<input
type="email"
placeholder={inputPlaceholder}
aria-label="Email address"
className="flex-1 px-5 py-3 md:py-0 text-base text-center md:text-left bg-transparent placeholder:opacity-75 focus:outline-none truncate"
/>
<button
type="submit"
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{buttonText}
</button>
</form>
{termsText && (
<p className="text-xs opacity-75 text-center md:max-w-8/10 2xl:max-w-6/10">
{termsText}
</p>
)}
</div>
</motion.div>
</div>
</section>
);
};
export default ContactCenter;

View File

@@ -0,0 +1,70 @@
import { motion } from "motion/react";
const ContactCta = ({
tag,
text,
primaryButton,
secondaryButton,
}: {
tag: string;
text: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
}) => {
return (
<section
data-webild-section="ContactCta"
aria-label="Contact section"
className="relative w-full py-20"
>
<div className="w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex items-center justify-center py-20 px-5 md:px-8 card rounded-theme"
>
<div className="w-full md:w-3/4 flex flex-col items-center gap-3">
<span className="card rounded-full px-3 py-1 text-sm">{tag}</span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-4xl md:text-5xl font-medium text-center leading-tight text-balance"
>
{text}
</motion.h2>
<div className="flex flex-wrap justify-center gap-3 mt-1">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</motion.div>
</div>
</section>
);
};
export default ContactCta;

View File

@@ -0,0 +1,108 @@
import { motion } from "motion/react";
type ContactSplitEmailProps = {
tag: string;
title: string;
description: string;
inputPlaceholder: string;
buttonText: string;
termsText?: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const ContactSplitEmail = ({
tag,
title,
description,
inputPlaceholder,
buttonText,
termsText,
imageSrc,
videoSrc,
}: ContactSplitEmailProps) => {
return (
<section
data-webild-section="ContactSplitEmail"
aria-label="Contact section"
className="relative w-full py-20"
>
<div className="w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-2 gap-5"
>
<div className="flex items-center justify-center py-15 md:py-20 card rounded-theme">
<div className="flex flex-col items-center w-full gap-3 px-5">
<span className="card rounded-full px-3 py-1 text-sm">{tag}</span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-5xl md:text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-8/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
<form className="flex flex-col md:flex-row w-full md:w-8/10 2xl:w-6/10 gap-3 md:gap-1 p-1 mt-2 card rounded-theme">
<input
type="email"
placeholder={inputPlaceholder}
aria-label="Email address"
className="flex-1 px-5 py-3 md:py-0 text-base text-center md:text-left bg-transparent placeholder:opacity-75 focus:outline-none truncate"
/>
<button
type="submit"
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{buttonText}
</button>
</form>
{termsText && (
<p className="text-xs opacity-75 text-center md:max-w-8/10 2xl:max-w-6/10">
{termsText}
</p>
)}
</div>
</div>
<div className="h-100 md:h-auto card rounded-theme overflow-hidden">
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Contact video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
</motion.div>
</div>
</section>
);
};
export default ContactSplitEmail;

View File

@@ -0,0 +1,135 @@
import { motion } from "motion/react";
type InputField = {
name: string;
type: string;
placeholder: string;
required?: boolean;
};
type TextareaField = {
name: string;
placeholder: string;
rows?: number;
required?: boolean;
};
type ContactSplitFormProps = {
tag: string;
title: string;
description: string;
inputs: InputField[];
textarea?: TextareaField;
buttonText: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const ContactSplitForm = ({
tag,
title,
description,
inputs,
textarea,
buttonText,
imageSrc,
videoSrc,
}: ContactSplitFormProps) => {
return (
<section
data-webild-section="ContactSplitForm"
aria-label="Contact section"
className="relative w-full py-20"
>
<div className="w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-2 md:auto-rows-fr gap-5"
>
<div className="p-5 md:p-8 card rounded-theme">
<form className="flex flex-col gap-5">
<div className="flex flex-col items-center gap-1 text-center">
<span className="card rounded-full px-3 py-1 text-sm">{tag}</span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-4xl font-medium text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-sm md:text-base leading-tight text-balance"
>
{description}
</motion.p>
</div>
<div className="flex flex-col gap-3">
{inputs.map((input) => (
<input
key={input.name}
type={input.type}
name={input.name}
placeholder={input.placeholder}
required={input.required}
aria-label={input.placeholder}
className="card rounded-theme h-9 px-3 w-full text-sm"
/>
))}
{textarea && (
<textarea
name={textarea.name}
placeholder={textarea.placeholder}
required={textarea.required}
rows={textarea.rows || 5}
aria-label={textarea.placeholder}
className="card rounded-theme p-3 w-full text-sm"
/>
)}
<button
type="submit"
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{buttonText}
</button>
</div>
</form>
</div>
<div className="h-100 md:h-full card rounded-theme overflow-hidden">
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Contact video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
</motion.div>
</div>
</section>
);
};
export default ContactSplitForm;

View File

@@ -0,0 +1,107 @@
import { motion } from "motion/react";
import { ChevronDown } from "lucide-react";
type FaqItem = {
question: string;
answer: string;
};
const FaqSimple = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FaqItem[];
}) => {
return (
<section
data-webild-section="FaqSimple"
aria-label="FAQ section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="flex flex-col gap-3">
{items.map((item, index) => (
<motion.details
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.05 + index * 0.05, ease: "easeOut" }}
className="card rounded-theme p-4 group"
>
<summary className="flex items-center justify-between gap-3 cursor-pointer list-none">
<h3 className="text-base md:text-lg font-medium leading-tight">{item.question}</h3>
<ChevronDown className="size-4 shrink-0 transition-transform duration-300 group-open:rotate-180" strokeWidth={2} />
</summary>
<p className="mt-2 text-sm leading-tight text-muted-foreground">{item.answer}</p>
</motion.details>
))}
</div>
</div>
</section>
);
};
export default FaqSimple;

View File

@@ -0,0 +1,139 @@
import { motion } from "motion/react";
import { ChevronDown } from "lucide-react";
type FaqItem = {
question: string;
answer: string;
};
type FaqSplitMediaProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FaqItem[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const FaqSplitMedia = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
imageSrc,
videoSrc,
}: FaqSplitMediaProps) => {
return (
<section
data-webild-section="FaqSplitMedia"
aria-label="FAQ section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-5 gap-5">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card relative md:col-span-2 h-80 md:h-auto rounded-theme overflow-hidden"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="FAQ video"
className="absolute inset-0 size-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="absolute inset-0 size-full object-cover"
/>
)}
</motion.div>
<div className="md:col-span-3 flex flex-col gap-3">
{items.map((item, index) => (
<motion.details
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="card rounded-theme p-4 group"
>
<summary className="flex items-center justify-between gap-3 cursor-pointer list-none">
<h3 className="text-base md:text-lg font-medium leading-tight">{item.question}</h3>
<ChevronDown className="size-4 shrink-0 transition-transform duration-300 group-open:rotate-180" strokeWidth={2} />
</summary>
<p className="mt-2 text-sm leading-tight text-muted-foreground">{item.answer}</p>
</motion.details>
))}
</div>
</div>
</div>
</section>
);
};
export default FaqSplitMedia;

View File

@@ -0,0 +1,107 @@
import { motion } from "motion/react";
import { ChevronDown } from "lucide-react";
type FaqItem = {
question: string;
answer: string;
};
const FaqTwoColumn = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FaqItem[];
}) => {
return (
<section
data-webild-section="FaqTwoColumn"
aria-label="FAQ section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-5">
{items.map((item, index) => (
<motion.details
key={index}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.05 + index * 0.05, ease: "easeOut" }}
className="card rounded-theme p-4 group"
>
<summary className="flex items-center justify-between gap-3 cursor-pointer list-none">
<h3 className="text-base md:text-lg font-medium leading-tight">{item.question}</h3>
<ChevronDown className="size-4 shrink-0 transition-transform duration-300 group-open:rotate-180" strokeWidth={2} />
</summary>
<p className="mt-2 text-sm leading-tight text-muted-foreground">{item.answer}</p>
</motion.details>
))}
</div>
</div>
</section>
);
};
export default FaqTwoColumn;

View File

@@ -0,0 +1,159 @@
import { motion } from "motion/react";
type FeatureItem = {
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesAlternatingSplitProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesAlternatingSplit = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesAlternatingSplitProps) => {
return (
<section
data-webild-section="FeaturesAlternatingSplit"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="flex flex-col gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className={`flex flex-col gap-5 md:gap-8 p-5 md:p-8 card rounded-theme ${index % 2 === 0 ? "md:flex-row" : "md:flex-row-reverse"}`}
>
<div className="flex flex-col justify-center w-full md:w-1/2 gap-3">
<span className="flex items-center justify-center size-8 mb-1 text-sm rounded-theme primary-button text-primary-cta-text">
{index + 1}
</span>
<h3 className="text-4xl md:text-5xl font-medium leading-tight text-balance">{item.title}</h3>
<p className="text-base leading-tight text-balance">{item.description}</p>
{(item.primaryButton || item.secondaryButton) && (
<div className="flex flex-wrap gap-3 mt-2">
{item.primaryButton && (
<a
href={item.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{item.primaryButton.text}
</a>
)}
{item.secondaryButton && (
<a
href={item.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{item.secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="w-full md:w-1/2 aspect-square rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesAlternatingSplit;

View File

@@ -0,0 +1,142 @@
import { motion } from "motion/react";
import { ArrowRight } from "lucide-react";
type FeatureItem = {
title: string;
tags: string[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesArrowCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesArrowCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesArrowCardsProps) => {
return (
<section
data-webild-section="FeaturesArrowCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="flex flex-col gap-5 h-full cursor-pointer group"
>
<div className="aspect-square rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
)}
</div>
<div className="flex flex-col justify-between gap-3 p-4 md:p-5 flex-1 card rounded-theme">
<h3 className="text-xl md:text-2xl font-medium leading-tight">{item.title}</h3>
<div className="flex items-center justify-between gap-5">
<div className="flex flex-wrap items-center gap-2">
{item.tags.map((itemTag) => (
<span key={itemTag} className="card rounded-full px-3 py-1 text-sm">{itemTag}</span>
))}
</div>
<ArrowRight className="shrink-0 h-4 w-auto transition-transform duration-300 group-hover:-rotate-45" strokeWidth={1.5} />
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesArrowCards;

View File

@@ -0,0 +1,306 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import Marquee from "@/components/ui/marquee";
type FeatureCard = { title: string; description: string } & (
| { bentoComponent: "info-card-marquee"; infoCards: { icon: LucideIcon; label: string; value: string }[] }
| { bentoComponent: "tilted-stack-cards"; stackCards: [{ icon: LucideIcon; title: string; subtitle: string; detail: string }, { icon: LucideIcon; title: string; subtitle: string; detail: string }, { icon: LucideIcon; title: string; subtitle: string; detail: string }] }
| { bentoComponent: "animated-bar-chart" }
| { bentoComponent: "orbiting-icons"; centerIcon: LucideIcon; orbitIcons: LucideIcon[] }
| { bentoComponent: "icon-text-marquee"; centerIcon: LucideIcon; marqueeTexts: string[] }
| { bentoComponent: "chat-marquee"; aiIcon: LucideIcon; userIcon: LucideIcon; exchanges: { userMessage: string; aiResponse: string }[]; placeholder: string }
| { bentoComponent: "checklist-timeline"; heading: string; subheading: string; checklistItems: [{ label: string; detail: string }, { label: string; detail: string }, { label: string; detail: string }]; completedLabel: string }
| { bentoComponent: "media-stack"; mediaItems: [{ imageSrc?: string; videoSrc?: string }, { imageSrc?: string; videoSrc?: string }, { imageSrc?: string; videoSrc?: string }] }
);
interface FeaturesBentoProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
features: FeatureCard[];
}
const FeaturesBento = ({
tag,
title,
description,
primaryButton,
secondaryButton,
features,
}: FeaturesBentoProps) => {
return (
<section
data-webild-section="FeaturesBento"
aria-label="Features bento section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-6 auto-rows-fr gap-5 w-content-width mx-auto">
{features.map((feature, index) => {
const spanClass =
index % 5 === 0 ? "md:col-span-4" :
index % 5 === 1 ? "md:col-span-2" :
index % 5 === 2 ? "md:col-span-3" :
index % 5 === 3 ? "md:col-span-3" :
"md:col-span-6";
return (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className={`flex flex-col gap-4 p-4 md:p-5 card rounded-theme h-full ${spanClass}`}
>
<div className="relative h-72 overflow-hidden rounded-theme">
{feature.bentoComponent === "info-card-marquee" && (
<div className="absolute inset-0 flex flex-col gap-2 p-4 overflow-hidden">
<Marquee speed={30}>
{feature.infoCards.map((c) => {
const Icon = c.icon;
return (
<div key={c.label} className="flex items-center gap-3 card rounded-theme px-4 py-3">
<Icon className="size-5 shrink-0" strokeWidth={1.5} />
<div className="flex flex-col">
<span className="text-xs text-muted-foreground">{c.label}</span>
<span className="text-base font-medium">{c.value}</span>
</div>
</div>
);
})}
</Marquee>
</div>
)}
{feature.bentoComponent === "tilted-stack-cards" && (
<div className="absolute inset-0 flex items-center justify-center">
{feature.stackCards.map((card, i) => {
const Icon = card.icon;
const rotate = (i - 1) * 6;
const translate = (i - 1) * 8;
return (
<div
key={card.title}
style={{ transform: `rotate(${rotate}deg) translateX(${translate}px)`, zIndex: i }}
className="absolute w-48 card rounded-theme p-4 flex flex-col gap-2"
>
<Icon className="size-5" strokeWidth={1.5} />
<h4 className="text-sm font-medium leading-tight">{card.title}</h4>
<p className="text-xs leading-tight">{card.subtitle}</p>
<p className="text-2xs text-muted-foreground">{card.detail}</p>
</div>
);
})}
</div>
)}
{feature.bentoComponent === "animated-bar-chart" && (
<div className="absolute inset-0 flex items-end justify-around gap-2 p-4">
{[40, 65, 85, 55, 75, 95, 60].map((h, i) => (
<motion.div
key={i}
initial={{ height: 0 }}
whileInView={{ height: `${h}%` }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: i * 0.1, ease: "easeOut" }}
className="w-full bg-foreground/80 rounded-theme"
/>
))}
</div>
)}
{feature.bentoComponent === "orbiting-icons" && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="relative size-48">
<div className="absolute inset-0 rounded-full border border-foreground/10" />
<div className="absolute inset-4 rounded-full border border-foreground/10" />
<div className="absolute inset-1/2 -translate-x-1/2 -translate-y-1/2 size-12 card rounded-full flex items-center justify-center">
<feature.centerIcon className="size-5" strokeWidth={1.5} />
</div>
{feature.orbitIcons.slice(0, 6).map((Icon, i) => {
const angle = (i / Math.min(feature.orbitIcons.length, 6)) * Math.PI * 2;
const r = 80;
const x = Math.cos(angle) * r;
const y = Math.sin(angle) * r;
return (
<div
key={i}
style={{ left: `calc(50% + ${x}px)`, top: `calc(50% + ${y}px)` }}
className="absolute -translate-x-1/2 -translate-y-1/2 size-8 card rounded-full flex items-center justify-center"
>
<Icon className="size-4" strokeWidth={1.5} />
</div>
);
})}
</div>
</div>
)}
{feature.bentoComponent === "icon-text-marquee" && (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3">
<div className="size-12 card rounded-full flex items-center justify-center">
<feature.centerIcon className="size-5" strokeWidth={1.5} />
</div>
<Marquee speed={25} className="w-full">
{feature.marqueeTexts.map((t, i) => (
<span key={i} className="card rounded-full px-4 py-2 text-sm whitespace-nowrap">{t}</span>
))}
</Marquee>
</div>
)}
{feature.bentoComponent === "chat-marquee" && (
<div className="absolute inset-0 flex flex-col gap-2 p-4 overflow-hidden">
{feature.exchanges.slice(0, 3).map((ex, i) => {
const UserIcon = feature.userIcon;
const AiIcon = feature.aiIcon;
return (
<div key={i} className="flex flex-col gap-2">
<div className="flex items-start gap-2 self-end max-w-7/10">
<span className="card rounded-theme px-3 py-2 text-xs">{ex.userMessage}</span>
<UserIcon className="size-5 shrink-0" strokeWidth={1.5} />
</div>
<div className="flex items-start gap-2 self-start max-w-7/10">
<AiIcon className="size-5 shrink-0" strokeWidth={1.5} />
<span className="card rounded-theme px-3 py-2 text-xs">{ex.aiResponse}</span>
</div>
</div>
);
})}
<div className="mt-auto card rounded-theme px-3 py-2 text-xs text-muted-foreground">{feature.placeholder}</div>
</div>
)}
{feature.bentoComponent === "checklist-timeline" && (
<div className="absolute inset-0 flex flex-col gap-3 p-4">
<h4 className="text-sm font-medium">{feature.heading}</h4>
<p className="text-xs text-muted-foreground">{feature.subheading}</p>
<div className="flex flex-col gap-2 mt-2">
{feature.checklistItems.map((it) => (
<div key={it.label} className="flex items-start gap-2">
<div className="size-5 rounded-full primary-button flex items-center justify-center shrink-0">
<Check className="size-3 text-primary-cta-text" strokeWidth={2} />
</div>
<div className="flex flex-col">
<span className="text-xs font-medium">{it.label}</span>
<span className="text-2xs text-muted-foreground">{it.detail}</span>
</div>
</div>
))}
</div>
<span className="mt-auto text-xs font-medium">{feature.completedLabel}</span>
</div>
)}
{feature.bentoComponent === "media-stack" && (
<div className="absolute inset-0 flex items-center justify-center">
{feature.mediaItems.map((m, i) => {
const rotate = (i - 1) * 6;
const translate = (i - 1) * 12;
return (
<div
key={i}
style={{ transform: `rotate(${rotate}deg) translateX(${translate}px)`, zIndex: i }}
className="absolute w-40 aspect-square card rounded-theme overflow-hidden p-2"
>
{m.videoSrc ? (
<video
src={m.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={m.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
);
})}
</div>
)}
</div>
<div className="flex flex-col gap-1">
<h3 className="text-2xl font-medium leading-tight">{feature.title}</h3>
<p className="text-sm leading-tight">{feature.description}</p>
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
};
export default FeaturesBento;

View File

@@ -0,0 +1,123 @@
import { motion } from "motion/react";
import type { LucideIcon } from "lucide-react";
type FeatureItem = {
icon: LucideIcon;
title: string;
description: string;
};
interface FeaturesBorderGlowProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
features: FeatureItem[];
}
const FeaturesBorderGlow = ({
tag,
title,
description,
primaryButton,
secondaryButton,
features,
}: FeaturesBorderGlowProps) => {
return (
<section
data-webild-section="FeaturesBorderGlow"
aria-label="Features border glow section"
className="relative w-full py-20"
>
<div className="flex flex-col w-content-width mx-auto gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{features.map((feature, index) => {
const FeatureIcon = feature.icon;
return (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="relative flex flex-col justify-between gap-4 p-4 md:p-5 h-full min-h-60 card rounded-theme"
>
<div className="flex items-center justify-center size-12 primary-button rounded-theme">
<FeatureIcon className="size-4 text-primary-cta-text" strokeWidth={1.5} />
</div>
<div className="flex flex-col gap-1">
<h3 className="text-2xl font-medium leading-tight">{feature.title}</h3>
<p className="text-sm leading-tight">{feature.description}</p>
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
};
export default FeaturesBorderGlow;

View File

@@ -0,0 +1,121 @@
import { motion } from "motion/react";
import { Check, X } from "lucide-react";
interface FeaturesComparisonProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
negativeItems: string[];
positiveItems: string[];
}
const FeaturesComparison = ({
tag,
title,
description,
primaryButton,
secondaryButton,
negativeItems,
positiveItems,
}: FeaturesComparisonProps) => {
return (
<section
data-webild-section="FeaturesComparison"
aria-label="Features comparison section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-2 w-content-width md:w-6/10 mx-auto gap-5"
>
<div className="flex flex-col gap-4 p-4 md:p-5 card rounded-theme opacity-50">
{negativeItems.map((item) => (
<div key={item} className="flex items-center gap-2 text-base">
<X className="shrink-0 size-4" />
<span className="text-base truncate">{item}</span>
</div>
))}
</div>
<div className="flex flex-col gap-4 p-4 md:p-5 card rounded-theme">
{positiveItems.map((item) => (
<div key={item} className="flex items-center gap-2 text-base">
<Check className="shrink-0 size-4" />
<span className="text-base truncate">{item}</span>
</div>
))}
</div>
</motion.div>
</div>
</section>
);
};
export default FeaturesComparison;

View File

@@ -0,0 +1,147 @@
import { motion } from "motion/react";
type FeatureItem = {
title: string;
description: string;
tags: string[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesDetailedCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesDetailedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesDetailedCardsProps) => {
return (
<section
data-webild-section="FeaturesDetailedCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="flex flex-col w-content-width mx-auto gap-5">
{items.map((item) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex flex-col md:grid md:grid-cols-10 2xl:w-8/10 mx-auto gap-5 md:gap-8 p-5 md:p-8 cursor-pointer card rounded-theme group"
>
<div className="flex flex-col md:col-span-6 gap-3 md:gap-8">
<h3 className="text-3xl md:text-5xl font-medium leading-tight text-balance">{item.title}</h3>
<div className="flex flex-col mt-auto gap-5">
<div className="flex flex-wrap gap-2">
{item.tags.map((itemTag) => (
<span key={itemTag} className="card rounded-full px-3 py-1 text-sm">{itemTag}</span>
))}
</div>
<p className="text-base md:text-2xl leading-tight text-balance">
{item.description}
</p>
</div>
</div>
<div className="aspect-square md:col-span-4 rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
)}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesDetailedCards;

View File

@@ -0,0 +1,150 @@
import { motion } from "motion/react";
type StepItem = {
tag: string;
title: string;
subtitle: string;
description: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesDetailedStepsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
steps: StepItem[];
}
const FeaturesDetailedSteps = ({
tag,
title,
description,
primaryButton,
secondaryButton,
steps,
}: FeaturesDetailedStepsProps) => {
return (
<section
data-webild-section="FeaturesDetailedSteps"
aria-label="Features detailed steps section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="flex flex-col w-content-width mx-auto gap-5">
{steps.map((step, index) => {
const stepNumber = String(index + 1).padStart(2, "0");
const tilt = index % 2 === 0 ? "rotate-3" : "-rotate-3";
return (
<motion.div
key={step.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex flex-col md:flex-row justify-between 2xl:w-8/10 mx-auto gap-5 md:gap-8 p-5 md:p-8 card rounded-theme overflow-hidden"
>
<div className="flex flex-col justify-between w-full md:w-1/2">
<div className="flex flex-col gap-5">
<span className="w-fit card rounded-full px-3 py-1 mb-1 text-sm">{step.tag}</span>
<h3 className="text-5xl md:text-8xl font-medium leading-none">{step.title}</h3>
</div>
<div className="block md:hidden w-full h-px my-5 bg-foreground/20" />
<div className="flex flex-col gap-2 md:gap-5">
<h4 className="text-2xl md:text-3xl font-medium text-balance">{step.subtitle}</h4>
<p className="text-base md:text-lg leading-tight text-balance">{step.description}</p>
</div>
</div>
<div className="flex flex-col w-full md:w-35/100 gap-8">
<span className="hidden md:block self-end text-8xl font-medium">{stepNumber}</span>
<div className={`aspect-square rounded-theme overflow-hidden ${tilt}`}>
{step.videoSrc ? (
<video
src={step.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Step video"
className="w-full h-full object-cover"
/>
) : (
<img
src={step.imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
};
export default FeaturesDetailedSteps;

View File

@@ -0,0 +1,142 @@
import { motion } from "motion/react";
import { Plus } from "lucide-react";
type FeatureItem = {
title: string;
descriptions: string[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesFlipCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesFlipCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesFlipCardsProps) => {
return (
<section
data-webild-section="FeaturesFlipCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="group flex flex-col gap-4 p-4 md:p-5 card rounded-theme"
>
<div className="flex items-start justify-between gap-5">
<h3 className="text-2xl font-medium leading-tight">{item.title}</h3>
<div className="flex items-center justify-center shrink-0 size-8 primary-button rounded-theme transition-transform duration-300 group-hover:rotate-45">
<Plus className="size-4 text-primary-cta-text" />
</div>
</div>
<div className="relative overflow-hidden aspect-4/5 rounded-theme">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
)}
</div>
<div className="flex flex-col gap-2">
{item.descriptions.map((desc, i) => (
<p key={i} className="text-base leading-tight">{desc}</p>
))}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesFlipCards;

View File

@@ -0,0 +1,126 @@
import { motion } from "motion/react";
import type { LucideIcon } from "lucide-react";
import HoverPattern from "@/components/ui/hover-pattern";
type FeatureItem = {
icon: LucideIcon;
title: string;
description: string;
};
interface FeaturesIconCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
features: FeatureItem[];
}
const FeaturesIconCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
features,
}: FeaturesIconCardsProps) => {
return (
<section
data-webild-section="FeaturesIconCards"
aria-label="Features icon cards section"
className="relative w-full py-20"
>
<div className="flex flex-col w-content-width mx-auto gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{features.map((feature, index) => {
const FeatureIcon = feature.icon;
return (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="flex flex-col gap-4 p-4 md:p-5 card rounded-theme"
>
<HoverPattern className="flex items-center justify-center aspect-square rounded-theme">
<div className="relative z-10 flex items-center justify-center size-12 primary-button rounded-theme">
<FeatureIcon className="size-4 text-primary-cta-text" strokeWidth={1.5} />
</div>
</HoverPattern>
<div className="flex flex-col gap-1">
<h3 className="text-2xl font-medium leading-tight">{feature.title}</h3>
<p className="text-sm leading-tight">{feature.description}</p>
</div>
</motion.div>
);
})}
</div>
</div>
</section>
);
};
export default FeaturesIconCards;

View File

@@ -0,0 +1,147 @@
import { motion } from "motion/react";
type FeatureItem = {
label: string;
title: string;
bullets: string[];
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
};
interface FeaturesLabeledListProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesLabeledList = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesLabeledListProps) => {
return (
<section
data-webild-section="FeaturesLabeledList"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="flex flex-col gap-5 w-content-width mx-auto">
{items.map((item) => (
<motion.div
key={item.label}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex flex-col md:flex-row gap-5 md:gap-8 p-5 md:p-8 card rounded-theme"
>
<div className="w-full md:w-1/2 flex md:justify-start">
<h3 className="text-7xl font-medium leading-tight truncate">{item.label}</h3>
</div>
<div className="w-full h-px bg-foreground/20 md:hidden" />
<div className="flex flex-col w-full md:w-1/2 gap-5">
<h4 className="text-2xl md:text-3xl font-medium leading-tight">{item.title}</h4>
<div className="flex flex-wrap items-center gap-1">
{item.bullets.map((bulletText, i) => (
<span key={i} className="flex items-center gap-1">
<span className="text-base">{bulletText}</span>
{i < item.bullets.length - 1 && <span className="text-base"></span>}
</span>
))}
</div>
<div className="flex flex-wrap gap-3 mt-2">
<a
href={item.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{item.primaryButton.text}
</a>
<a
href={item.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{item.secondaryButton.text}
</a>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesLabeledList;

View File

@@ -0,0 +1,134 @@
import { motion } from "motion/react";
type FeatureItem = {
title: string;
description: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesMediaCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesMediaCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesMediaCardsProps) => {
return (
<section
data-webild-section="FeaturesMediaCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="flex flex-col gap-4 p-4 md:p-5 h-full card rounded-theme"
>
<div className="aspect-square rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
<div className="flex flex-col gap-1">
<h3 className="text-2xl font-medium leading-tight">{item.title}</h3>
<p className="text-sm leading-tight">{item.description}</p>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesMediaCards;

View File

@@ -0,0 +1,143 @@
import { motion } from "motion/react";
import type { LucideIcon } from "lucide-react";
import Marquee from "@/components/ui/marquee";
type FeatureItem = {
title: string;
description: string;
buttonIcon: LucideIcon;
buttonHref?: string;
buttonOnClick?: () => void;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesMediaCarouselProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesMediaCarousel = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesMediaCarouselProps) => {
return (
<section
data-webild-section="FeaturesMediaCarousel"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col w-full gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<Marquee speed={50} pauseOnHover>
{items.map((item) => {
const Icon = item.buttonIcon;
return (
<div key={item.title} className="relative overflow-hidden w-90 aspect-square md:aspect-3/2 rounded-theme shrink-0">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div className="absolute inset-x-0 bottom-0 h-1/3 bg-linear-to-t from-foreground/60 to-transparent" />
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-5 p-5">
<div className="flex flex-col min-w-0">
<h3 className="text-2xl md:text-3xl font-medium leading-tight text-background">{item.title}</h3>
<p className="text-sm md:text-base leading-tight text-background/75">{item.description}</p>
</div>
<a
href={item.buttonHref ?? "#"}
aria-label={item.buttonHref ? `Navigate to ${item.buttonHref}` : "Action button"}
className="flex items-center justify-center shrink-0 size-8 primary-button rounded-theme"
>
<Icon className="size-4 text-primary-cta-text" strokeWidth={1.5} />
</a>
</div>
</div>
);
})}
</Marquee>
</div>
</section>
);
};
export default FeaturesMediaCarousel;

View File

@@ -0,0 +1,163 @@
import { motion } from "motion/react";
import { BadgeCheck } from "lucide-react";
type FeatureItem = {
title: string;
description: string;
avatarSrc: string;
buttonText: string;
buttonHref?: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesProfileCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesProfileCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesProfileCardsProps) => {
return (
<section
data-webild-section="FeaturesProfileCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="group relative overflow-hidden aspect-5/6 rounded-theme"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Profile video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<a
href={item.buttonHref ?? "#"}
className="absolute top-5 right-5 z-20 primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{item.buttonText}
</a>
<div className="absolute inset-x-0 bottom-0 h-2/5 bg-linear-to-t from-foreground/80 to-transparent" />
<div className="absolute inset-x-0 bottom-0 z-10 p-3">
<div className="relative flex flex-col gap-1 p-3">
<div className="absolute inset-0 -z-10 card rounded-theme translate-y-full opacity-0 transition-all duration-400 ease-out group-hover:translate-y-0 group-hover:opacity-100" />
<div className="flex items-center gap-2">
<div className="size-8 shrink-0 overflow-hidden rounded-full secondary-button">
<img src={item.avatarSrc} alt="" className="h-full w-full object-cover" />
</div>
<h3 className="text-xl font-semibold leading-tight truncate text-background transition-colors duration-400 group-hover:text-foreground">
{item.title}
</h3>
<BadgeCheck className="size-5 shrink-0 text-background transition-colors duration-400 group-hover:text-foreground" strokeWidth={2} />
</div>
<div className="grid grid-rows-[0fr] transition-all duration-400 ease-out group-hover:grid-rows-[1fr]">
<p className="overflow-hidden text-sm leading-tight text-foreground opacity-0 transition-opacity duration-400 group-hover:opacity-100">
{item.description}
</p>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesProfileCards;

View File

@@ -0,0 +1,153 @@
import { motion } from "motion/react";
import { Info } from "lucide-react";
type FeatureItem = {
title: string;
description: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesRevealCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesRevealCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesRevealCardsProps) => {
return (
<section
data-webild-section="FeaturesRevealCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="group relative overflow-hidden aspect-6/7 rounded-theme"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div className="absolute top-5 left-5 z-20">
<div className="relative size-8 rounded-full bg-background flex items-center justify-center">
<span className="text-sm font-medium text-foreground transition-opacity duration-300 group-hover:opacity-0">{index + 1}</span>
<Info className="absolute size-4 text-foreground opacity-0 transition-opacity duration-300 group-hover:opacity-100" strokeWidth={1.5} />
</div>
</div>
<div className="absolute inset-x-0 bottom-0 h-2/5 bg-linear-to-t from-foreground/80 to-transparent" />
<div className="absolute inset-x-0 bottom-0 z-10 p-3">
<div className="relative flex flex-col gap-1 p-3">
<div className="absolute inset-0 -z-10 card rounded-theme translate-y-full opacity-0 transition-all duration-400 ease-out group-hover:translate-y-0 group-hover:opacity-100" />
<h3 className="text-2xl font-semibold leading-tight text-background transition-colors duration-400 group-hover:text-foreground">
{item.title}
</h3>
<div className="grid grid-rows-[0fr] transition-all duration-400 ease-out group-hover:grid-rows-[1fr]">
<p className="overflow-hidden text-sm leading-tight text-foreground opacity-0 transition-opacity duration-400 group-hover:opacity-100">
{item.description}
</p>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesRevealCards;

View File

@@ -0,0 +1,127 @@
import { motion } from "motion/react";
type FeatureItem = {
title: string;
description: string;
label: string;
value: string;
};
interface FeaturesStatisticsCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesStatisticsCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesStatisticsCardsProps) => {
return (
<section
data-webild-section="FeaturesStatisticsCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="flex flex-col h-full card rounded-theme"
>
<div className="flex flex-col flex-1 gap-8 p-5">
<div className="flex flex-col gap-1">
<h3 className="text-2xl md:text-3xl font-medium leading-tight truncate">{item.title}</h3>
<p className="text-base md:text-lg leading-tight">{item.description}</p>
</div>
<div className="flex items-center justify-between gap-2 mt-auto">
<div className="flex items-center min-w-0 flex-1 gap-2">
<span className="shrink-0 size-3 rounded-full bg-foreground" />
<span className="text-base truncate">{item.label}</span>
</div>
<span className="text-xl md:text-2xl font-medium">{item.value}</span>
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesStatisticsCards;

View File

@@ -0,0 +1,158 @@
import { motion } from "motion/react";
type FeatureItem = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesTaggedCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesTaggedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesTaggedCardsProps) => {
return (
<section
data-webild-section="FeaturesTaggedCards"
aria-label="Features section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto">
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="flex flex-col gap-5 h-full group"
>
<div className="relative aspect-square rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Feature video"
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover transition-transform duration-500 ease-in-out group-hover:scale-105"
/>
)}
<span className="absolute top-5 right-5 card rounded-full px-3 py-1 text-sm">{item.tag}</span>
</div>
<div className="flex flex-col gap-4 p-4 md:p-5 flex-1 card rounded-theme">
<h3 className="text-xl md:text-2xl font-medium leading-tight">{item.title}</h3>
<p className="text-base leading-tight">{item.description}</p>
{(item.primaryButton || item.secondaryButton) && (
<div className="flex flex-wrap gap-3 mt-2">
{item.primaryButton && (
<a
href={item.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{item.primaryButton.text}
</a>
)}
{item.secondaryButton && (
<a
href={item.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{item.secondaryButton.text}
</a>
)}
</div>
)}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesTaggedCards;

View File

@@ -0,0 +1,141 @@
import { motion } from "motion/react";
type FeatureItem = {
title: string;
description: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesTimelineCardsProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: FeatureItem[];
}
const FeaturesTimelineCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: FeaturesTimelineCardsProps) => {
return (
<section
data-webild-section="FeaturesTimelineCards"
aria-label="Features timeline section"
className="relative w-full py-20"
>
<div className="flex flex-col w-content-width mx-auto gap-8">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="relative flex flex-col gap-8">
<div className="absolute left-4 top-2 bottom-2 w-px bg-foreground/20" aria-hidden="true" />
{items.map((item, index) => (
<motion.div
key={item.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.1 + index * 0.05, ease: "easeOut" }}
className="relative flex gap-5 pl-8"
>
<div className="absolute left-2 top-2 size-5 rounded-full primary-button flex items-center justify-center">
<span className="text-2xs font-medium text-primary-cta-text">{index + 1}</span>
</div>
<div className="flex flex-col md:flex-row gap-5 md:gap-8 flex-1 p-4 md:p-5 card rounded-theme">
<div className="flex flex-col gap-2 w-full md:w-1/2">
<h3 className="text-3xl font-medium leading-tight text-balance">{item.title}</h3>
<p className="text-base leading-tight text-balance">{item.description}</p>
</div>
<div className="w-full md:w-1/2 aspect-video rounded-theme overflow-hidden">
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Timeline video"
className="w-full h-full object-cover"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default FeaturesTimelineCards;

View File

@@ -0,0 +1,56 @@
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
type FooterColumn = {
title: string;
items: FooterLink[];
};
const FooterBasic = ({
columns,
leftText,
rightText,
}: {
columns: FooterColumn[];
leftText: string;
rightText: string;
}) => {
return (
<footer
data-webild-section="FooterBasic"
aria-label="Site footer"
className="w-full pt-20 pb-8 border-t border-foreground/15"
>
<div className="w-content-width mx-auto">
<div className="w-full flex flex-wrap justify-between gap-y-8 mb-8">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">
<h3 className="text-sm opacity-50">{column.title}</h3>
{column.items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-base hover:opacity-75 transition-opacity"
>
{item.label}
</a>
))}
</div>
))}
</div>
<div className="w-full h-px bg-foreground/20" />
<div className="w-full flex items-center justify-between pt-5">
<span className="text-sm opacity-50">{leftText}</span>
<span className="text-sm opacity-50">{rightText}</span>
</div>
</div>
</footer>
);
};
export default FooterBasic;

View File

@@ -0,0 +1,51 @@
import { ChevronRight } from "lucide-react";
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
type FooterColumn = {
items: FooterLink[];
};
const FooterBrand = ({
brand,
columns,
}: {
brand: string;
columns: FooterColumn[];
}) => {
return (
<footer
data-webild-section="FooterBrand"
aria-label="Site footer"
className="w-full py-8 mt-20 overflow-hidden primary-button text-primary-cta-text"
>
<div className="w-content-width mx-auto flex flex-col gap-8 md:gap-8">
<h2 className="text-7xl md:text-9xl font-medium leading-none">{brand}</h2>
<div className="flex flex-col gap-8 mb-8 md:flex-row md:justify-between">
{columns.map((column, index) => (
<div key={index} className="flex flex-col items-start gap-3">
{column.items.map((item) => (
<div key={item.label} className="flex items-center gap-2 text-base">
<ChevronRight className="w-4 h-4" strokeWidth={3} aria-hidden="true" />
<a
href={item.href}
className="text-base text-primary-cta-text font-medium hover:opacity-75 transition-opacity"
>
{item.label}
</a>
</div>
))}
</div>
))}
</div>
</div>
</footer>
);
};
export default FooterBrand;

View File

@@ -0,0 +1,51 @@
import { ChevronRight } from "lucide-react";
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
type FooterColumn = {
items: FooterLink[];
};
const FooterBrandReveal = ({
brand,
columns,
}: {
brand: string;
columns: FooterColumn[];
}) => {
return (
<footer
data-webild-section="FooterBrandReveal"
aria-label="Site footer"
className="w-full py-8 mt-20 overflow-hidden primary-button text-primary-cta-text"
>
<div className="w-content-width mx-auto flex flex-col gap-8">
<h2 className="text-7xl md:text-9xl font-medium leading-none">{brand}</h2>
<div className="flex flex-col gap-8 mb-8 md:flex-row md:justify-between">
{columns.map((column, index) => (
<div key={index} className="flex flex-col items-start gap-3">
{column.items.map((item) => (
<div key={item.label} className="flex items-center gap-2 text-base">
<ChevronRight className="w-4 h-4" strokeWidth={3} aria-hidden="true" />
<a
href={item.href}
className="text-base text-primary-cta-text font-medium hover:opacity-75 transition-opacity"
>
{item.label}
</a>
</div>
))}
</div>
))}
</div>
</div>
</footer>
);
};
export default FooterBrandReveal;

View File

@@ -0,0 +1,53 @@
import type { LucideIcon } from "lucide-react";
type SocialLink = {
icon: string | LucideIcon;
href?: string;
onClick?: () => void;
};
const FooterMinimal = ({
brand,
copyright,
socialLinks,
}: {
brand: string;
copyright: string;
socialLinks?: SocialLink[];
}) => {
return (
<footer
data-webild-section="FooterMinimal"
aria-label="Site footer"
className="relative w-full py-20"
>
<div className="flex flex-col w-content-width mx-auto px-8 pb-5 rounded-theme card">
<h2 className="text-7xl md:text-9xl font-medium leading-none py-5">{brand}</h2>
<div className="h-px w-full mb-5 bg-foreground/50" />
<div className="flex flex-col gap-3 items-center justify-between md:flex-row">
<span className="text-base opacity-75">{copyright}</span>
{socialLinks && socialLinks.length > 0 && (
<div className="flex items-center gap-3">
{socialLinks.map((link, index) => {
const Icon = typeof link.icon === "string" ? null : link.icon;
return (
<a
key={index}
href={link.href}
className="flex items-center justify-center size-8 rounded-full primary-button text-primary-cta-text"
>
{Icon && <Icon className="w-4 h-4" strokeWidth={1.5} />}
</a>
);
})}
</div>
)}
</div>
</div>
</footer>
);
};
export default FooterMinimal;

View File

@@ -0,0 +1,70 @@
type FooterColumn = {
title: string;
items: { label: string; href?: string; onClick?: () => void }[];
};
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
const FooterSimple = ({
brand,
columns,
copyright,
links,
}: {
brand: string;
columns: FooterColumn[];
copyright: string;
links: FooterLink[];
}) => {
return (
<footer
data-webild-section="FooterSimple"
aria-label="Site footer"
className="w-full py-8 mt-20 primary-button text-primary-cta-text"
>
<div className="w-content-width mx-auto">
<div className="flex flex-col md:flex-row gap-8 md:gap-0 justify-between items-start mb-8">
<h2 className="text-4xl font-medium">{brand}</h2>
<div className="w-full md:w-fit flex flex-wrap gap-y-8 md:gap-8">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">
<h3 className="text-sm opacity-50">{column.title}</h3>
{column.items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-base text-primary-cta-text hover:opacity-75 transition-opacity"
>
{item.label}
</a>
))}
</div>
))}
</div>
</div>
<div className="flex items-center justify-between pt-8 border-t border-primary-cta-text/20">
<span className="text-sm opacity-50">{copyright}</span>
<div className="flex items-center gap-3">
{links.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm opacity-50 hover:opacity-75 transition-opacity"
>
{link.label}
</a>
))}
</div>
</div>
</div>
</footer>
);
};
export default FooterSimple;

View File

@@ -0,0 +1,70 @@
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
type FooterColumn = {
title: string;
items: FooterLink[];
};
const FooterSimpleCard = ({
brand,
columns,
copyright,
links,
}: {
brand: string;
columns: FooterColumn[];
copyright: string;
links: FooterLink[];
}) => {
return (
<footer
data-webild-section="FooterSimpleCard"
aria-label="Site footer"
className="w-full py-20"
>
<div className="w-content-width mx-auto p-8 rounded-theme card">
<div className="flex flex-col md:flex-row gap-8 md:gap-0 justify-between items-start mb-8">
<h2 className="text-4xl font-medium">{brand}</h2>
<div className="w-full md:w-fit flex flex-wrap gap-y-8 md:gap-8">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">
<h3 className="text-sm opacity-50">{column.title}</h3>
{column.items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-base hover:opacity-75 transition-opacity"
>
{item.label}
</a>
))}
</div>
))}
</div>
</div>
<div className="flex items-center justify-between pt-8 border-t border-foreground/20">
<span className="text-sm opacity-50">{copyright}</span>
<div className="flex items-center gap-3">
{links.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm opacity-50 hover:opacity-75 transition-opacity"
>
{link.label}
</a>
))}
</div>
</div>
</div>
</footer>
);
};
export default FooterSimpleCard;

View File

@@ -0,0 +1,96 @@
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
type FooterColumn = {
title: string;
items: FooterLink[];
};
type FooterSimpleMediaProps = ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }) & {
brand: string;
columns: FooterColumn[];
copyright: string;
links: FooterLink[];
};
const FooterSimpleMedia = ({
imageSrc,
videoSrc,
brand,
columns,
copyright,
links,
}: FooterSimpleMediaProps) => {
return (
<footer
data-webild-section="FooterSimpleMedia"
aria-label="Site footer"
className="relative w-full mt-20 overflow-hidden"
>
<div className="w-full aspect-square md:aspect-16/6 mask-fade-top-long">
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Footer video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
</div>
<div className="w-full py-8 primary-button text-primary-cta-text">
<div className="w-content-width mx-auto">
<div className="flex flex-col md:flex-row gap-8 md:gap-0 justify-between items-start mb-8">
<h2 className="text-4xl font-medium">{brand}</h2>
<div className="w-full md:w-fit flex flex-wrap gap-y-8 md:gap-8">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">
<h3 className="text-sm opacity-50">{column.title}</h3>
{column.items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-base text-primary-cta-text hover:opacity-75 transition-opacity"
>
{item.label}
</a>
))}
</div>
))}
</div>
</div>
<div className="flex items-center justify-between pt-8 border-t border-primary-cta-text/20">
<span className="text-sm opacity-50">{copyright}</span>
<div className="flex items-center gap-3">
{links.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm opacity-50 hover:opacity-75 transition-opacity"
>
{link.label}
</a>
))}
</div>
</div>
</div>
</div>
</footer>
);
};
export default FooterSimpleMedia;

View File

@@ -0,0 +1,70 @@
type FooterColumn = {
title: string;
items: { label: string; href?: string; onClick?: () => void }[];
};
type FooterLink = {
label: string;
href?: string;
onClick?: () => void;
};
const FooterSimpleReveal = ({
brand,
columns,
copyright,
links,
}: {
brand: string;
columns: FooterColumn[];
copyright: string;
links: FooterLink[];
}) => {
return (
<footer
data-webild-section="FooterSimpleReveal"
aria-label="Site footer"
className="w-full py-8 mt-20 primary-button text-primary-cta-text"
>
<div className="w-content-width mx-auto">
<div className="flex flex-col md:flex-row gap-8 md:gap-0 justify-between items-start mb-8">
<h2 className="text-4xl font-medium">{brand}</h2>
<div className="w-full md:w-fit flex flex-wrap gap-y-8 md:gap-8">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">
<h3 className="text-sm opacity-50">{column.title}</h3>
{column.items.map((item) => (
<a
key={item.label}
href={item.href}
className="text-base text-primary-cta-text hover:opacity-75 transition-opacity"
>
{item.label}
</a>
))}
</div>
))}
</div>
</div>
<div className="flex items-center justify-between pt-8 border-t border-primary-cta-text/20">
<span className="text-sm opacity-50">{copyright}</span>
<div className="flex items-center gap-3">
{links.map((link) => (
<a
key={link.label}
href={link.href}
className="text-sm opacity-50 hover:opacity-75 transition-opacity"
>
{link.label}
</a>
))}
</div>
</div>
</div>
</footer>
);
};
export default FooterSimpleReveal;

View File

@@ -0,0 +1,140 @@
import { motion } from "motion/react";
type HeroBillboardProps = {
tag?: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
avatars?: { src: string }[];
avatarsLabel?: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroBillboard = ({
tag,
title,
description,
primaryButton,
secondaryButton,
avatars,
avatarsLabel,
imageSrc,
videoSrc,
}: HeroBillboardProps) => {
return (
<section
data-webild-section="HeroBillboard"
aria-label="Hero section"
className="relative w-full pt-25 pb-20 md:py-hero-page-padding"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-3 text-center">
{avatars && avatars.length > 0 ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex items-center gap-3"
>
<div className="flex -space-x-2">
{avatars.map((avatar, i) => (
<img
key={i}
src={avatar.src}
alt=""
className="w-8 h-8 rounded-full border-2 border-background object-cover"
/>
))}
</div>
{avatarsLabel ? (
<span className="text-sm">{avatarsLabel}</span>
) : null}
</motion.div>
) : tag ? (
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
) : null}
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 w-full aspect-4/5 md:aspect-video"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
</div>
</section>
);
};
export default HeroBillboard;

View File

@@ -0,0 +1,100 @@
import { motion } from "motion/react";
type HeroBillboardBrandProps = {
brand: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroBillboardBrand = ({
brand,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
}: HeroBillboardBrandProps) => {
return (
<section
data-webild-section="HeroBillboardBrand"
aria-label="Hero section"
className="relative w-full pt-25 pb-20 md:py-hero-page-padding"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-end gap-5">
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-full text-9xl font-semibold text-balance text-right leading-none"
>
{brand}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="w-full md:w-1/2 text-lg md:text-2xl leading-tight text-balance text-right"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-end gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 w-full aspect-4/5 md:aspect-video"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
</div>
</section>
);
};
export default HeroBillboardBrand;

View File

@@ -0,0 +1,120 @@
import { motion } from "motion/react";
import Marquee from "@/components/ui/marquee";
type HeroBillboardCarouselProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
};
const HeroBillboardCarousel = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: HeroBillboardCarouselProps) => {
return (
<section
data-webild-section="HeroBillboardCarousel"
aria-label="Hero section"
className="relative flex flex-col items-center justify-center gap-8 w-full min-h-svh py-25"
>
<div className="flex flex-col items-center gap-3 w-content-width mx-auto text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="w-full mask-fade-x"
>
<Marquee speed={60} pauseOnHover={false}>
{items.map((item, i) => (
<div
key={i}
className="shrink-0 w-60 md:w-80 aspect-4/5 card rounded-theme overflow-hidden p-2"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</Marquee>
</motion.div>
</section>
);
};
export default HeroBillboardCarousel;

View File

@@ -0,0 +1,112 @@
import { motion } from "motion/react";
type HeroBillboardScrollProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroBillboardScroll = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
}: HeroBillboardScrollProps) => {
return (
<section
data-webild-section="HeroBillboardScroll"
aria-label="Hero section"
className="relative w-full pt-25 pb-20 md:py-hero-page-padding"
>
<div className="w-content-width mx-auto">
<div className="flex flex-col items-center gap-3 text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 40, rotateX: 15 }}
whileInView={{ opacity: 1, y: 0, rotateX: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.8, delay: 0.2, ease: "easeOut" }}
className="card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 w-full aspect-4/5 md:aspect-video mt-8"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
</div>
</section>
);
};
export default HeroBillboardScroll;

View File

@@ -0,0 +1,163 @@
import { motion } from "motion/react";
import { Star } from "lucide-react";
type Testimonial = {
name: string;
handle: string;
text: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type HeroBillboardTestimonialProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
testimonials: Testimonial[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroBillboardTestimonial = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
testimonials,
}: HeroBillboardTestimonialProps) => {
const testimonial = testimonials[0];
return (
<section
data-webild-section="HeroBillboardTestimonial"
aria-label="Hero section"
className="relative w-full pt-25 pb-20 md:py-hero-page-padding"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-3 text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="relative card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 w-full aspect-3/4 md:aspect-video"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
<figure className="absolute bottom-6 left-6 right-6 md:right-auto md:bottom-8 md:left-8 md:max-w-sm card rounded-theme p-4 flex flex-col gap-3">
<div className="flex gap-1">
{Array.from({ length: 5 }).map((_, i) => (
<Star
key={i}
className={`w-5 h-5 text-accent ${i < testimonial.rating ? "fill-accent" : "fill-transparent"}`}
strokeWidth={1.5}
/>
))}
</div>
<blockquote className="text-base leading-tight text-balance">
{testimonial.text}
</blockquote>
<figcaption className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
className="w-10 h-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt=""
className="w-10 h-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-sm font-medium">{testimonial.name}</span>
<span className="text-sm text-muted-foreground">{testimonial.handle}</span>
</div>
</figcaption>
</figure>
</motion.div>
</div>
</section>
);
};
export default HeroBillboardTestimonial;

View File

@@ -0,0 +1,126 @@
import { motion } from "motion/react";
type HeroBillboardTiltedCarouselProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
};
const HeroBillboardTiltedCarousel = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: HeroBillboardTiltedCarouselProps) => {
const tiltClasses = [
"-rotate-6 -translate-y-3",
"rotate-3 translate-y-3",
"-rotate-3 -translate-y-2",
"rotate-6 translate-y-4",
"-rotate-6 -translate-y-3",
"rotate-3 translate-y-3",
];
return (
<section
data-webild-section="HeroBillboardTiltedCarousel"
aria-label="Hero section"
className="relative flex flex-col items-center justify-center gap-8 w-full min-h-svh py-25 overflow-hidden"
>
<div className="flex flex-col items-center gap-3 w-content-width mx-auto text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex justify-center items-center gap-3 w-full max-w-[120vw] -mx-8"
>
{items.map((item, i) => (
<div
key={i}
className={`shrink-0 w-50 md:w-60 aspect-4/5 card rounded-theme overflow-hidden p-2 transform ${tiltClasses[i % tiltClasses.length]}`}
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</motion.div>
</section>
);
};
export default HeroBillboardTiltedCarousel;

View File

@@ -0,0 +1,101 @@
import { motion } from "motion/react";
type HeroBrandProps = {
brand: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroBrand = ({
brand,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
}: HeroBrandProps) => {
return (
<section
data-webild-section="HeroBrand"
aria-label="Hero section"
className="relative w-full h-svh overflow-hidden flex flex-col justify-end"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-0 z-1 h-3/5 bg-gradient-to-t from-foreground/70 via-foreground/30 to-transparent"
/>
<div className="relative z-10 w-content-width mx-auto pb-5">
<div className="flex flex-col gap-5">
<div className="w-full flex flex-col md:flex-row md:justify-between items-start md:items-end gap-3 md:gap-5">
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-full md:w-1/2 text-lg md:text-2xl text-balance text-primary-cta-text leading-tight"
>
{description}
</motion.p>
<div className="w-full md:w-1/2 flex justify-start md:justify-end">
<div className="flex flex-wrap gap-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="font-semibold text-primary-cta-text text-9xl leading-none text-balance"
>
{brand}
</motion.h1>
</div>
</div>
</section>
);
};
export default HeroBrand;

View File

@@ -0,0 +1,117 @@
import { motion } from "motion/react";
type HeroBrandCarouselProps = {
brand: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
};
const HeroBrandCarousel = ({
brand,
description,
primaryButton,
secondaryButton,
items,
}: HeroBrandCarouselProps) => {
const featured = items[0];
return (
<section
data-webild-section="HeroBrandCarousel"
aria-label="Hero section"
className="relative w-full h-svh overflow-hidden flex flex-col justify-end"
>
{featured.videoSrc ? (
<video
src={featured.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={featured.imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-0 z-1 h-3/5 bg-gradient-to-t from-foreground/70 via-foreground/30 to-transparent"
/>
<div className="relative z-10 w-content-width mx-auto pb-5">
<div className="flex flex-col gap-5">
<div className="w-full flex flex-col md:flex-row md:justify-between items-start md:items-end gap-3 md:gap-5">
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-full md:w-1/2 text-lg md:text-2xl text-balance text-primary-cta-text leading-tight"
>
{description}
</motion.p>
<div className="w-full md:w-1/2 flex justify-start md:justify-end">
<div className="flex flex-wrap gap-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="font-semibold text-primary-cta-text text-9xl leading-none text-balance"
>
{brand}
</motion.h1>
<div className="flex gap-3 pb-5">
{items.map((_, i) => (
<div
key={i}
className="relative h-1 w-full rounded-full overflow-hidden bg-primary-cta-text/20"
aria-hidden="true"
>
<div
className={`absolute inset-0 bg-primary-cta-text rounded-full origin-left ${i === 0 ? "scale-x-100" : "scale-x-0"}`}
/>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default HeroBrandCarousel;

View File

@@ -0,0 +1,144 @@
import { motion } from "motion/react";
import Marquee from "@/components/ui/marquee";
type HeroCenteredLogosProps = {
avatars: { src: string }[];
avatarText: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
logos: string[];
hideMedia?: boolean;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroCenteredLogos = ({
avatars,
avatarText,
title,
description,
primaryButton,
secondaryButton,
logos,
imageSrc,
videoSrc,
hideMedia = false,
}: HeroCenteredLogosProps) => {
return (
<section
data-webild-section="HeroCenteredLogos"
aria-label="Hero section"
className="relative h-svh w-full flex flex-col"
>
{!hideMedia && (
<div className="absolute inset-0 z-0">
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover"
/>
)}
<div className="absolute inset-0 bg-background/80" />
</div>
)}
<div className="relative z-10 flex-1 flex items-center justify-center">
<div className="flex flex-col items-center gap-3 pt-8 w-content-width mx-auto text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex items-center gap-3"
>
<div className="flex -space-x-2">
{avatars.map((avatar, i) => (
<img
key={i}
src={avatar.src}
alt=""
className="w-10 h-10 rounded-full border-2 border-background object-cover"
/>
))}
</div>
<span className="text-sm">{avatarText}</span>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="md:max-w-8/10 text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-2">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="relative z-10 w-content-width mx-auto pb-8 mask-fade-x"
>
<Marquee speed={30} pauseOnHover={false}>
{logos.map((logo, i) => (
<div key={i} className="shrink-0 px-4 py-2 card rounded-theme">
<span className="text-xl font-semibold whitespace-nowrap opacity-75">
{logo}
</span>
</div>
))}
</Marquee>
</motion.div>
</section>
);
};
export default HeroCenteredLogos;

View File

@@ -0,0 +1,137 @@
import { motion } from "motion/react";
type HeroOverlayProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
avatars?: { src: string }[];
avatarsLabel?: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroOverlay = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
avatars,
avatarsLabel,
}: HeroOverlayProps) => {
return (
<section
data-webild-section="HeroOverlay"
aria-label="Hero section"
className="relative w-full h-svh overflow-hidden flex flex-col justify-end"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-0 z-1 h-3/4 bg-gradient-to-t from-foreground/70 via-foreground/30 to-transparent"
/>
<div className="relative z-10 w-content-width mx-auto pb-8 md:pb-25">
<div className="flex flex-col gap-3 w-full md:w-3/5 lg:w-1/2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-fit card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-primary-cta-text text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-lg md:text-xl text-primary-cta-text leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
{avatars && avatars.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.45, ease: "easeOut" }}
className="mt-4 flex items-center gap-3"
>
<div className="flex -space-x-2">
{avatars.map((avatar, i) => (
<img
key={i}
src={avatar.src}
alt=""
className="w-10 h-10 rounded-full border-2 border-background object-cover"
/>
))}
</div>
{avatarsLabel ? (
<span className="text-sm text-primary-cta-text">{avatarsLabel}</span>
) : null}
</motion.div>
)}
</div>
</div>
</section>
);
};
export default HeroOverlay;

View File

@@ -0,0 +1,166 @@
import { motion } from "motion/react";
import { Star } from "lucide-react";
type Testimonial = {
name: string;
handle: string;
text: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type HeroOverlayTestimonialProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
testimonials: Testimonial[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroOverlayTestimonial = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
testimonials,
}: HeroOverlayTestimonialProps) => {
const testimonial = testimonials[0];
return (
<section
data-webild-section="HeroOverlayTestimonial"
aria-label="Hero section"
className="relative w-full h-svh overflow-hidden flex flex-col justify-start"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<img
src={imageSrc}
alt=""
className="absolute inset-0 w-full h-full object-cover"
/>
)}
<div
aria-hidden="true"
className="absolute inset-x-0 top-0 z-1 h-3/4 bg-gradient-to-b from-foreground/60 via-foreground/30 to-transparent"
/>
<div className="relative z-10 w-content-width mx-auto pt-35">
<div className="flex flex-col gap-3 w-full md:w-3/5 lg:w-1/2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="w-fit card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-primary-cta-text text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-lg md:text-xl text-primary-cta-text leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.figure
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.45, ease: "easeOut" }}
className="absolute z-10 bottom-3 left-3 right-3 md:left-auto md:bottom-8 md:right-8 md:max-w-sm card rounded-theme p-4 flex flex-col gap-3"
>
<div className="flex gap-1">
{Array.from({ length: 5 }).map((_, i) => (
<Star
key={i}
className={`w-5 h-5 text-accent ${i < testimonial.rating ? "fill-accent" : "fill-transparent"}`}
strokeWidth={1.5}
/>
))}
</div>
<blockquote className="text-base leading-tight text-balance">
{testimonial.text}
</blockquote>
<figcaption className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
className="w-10 h-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt=""
className="w-10 h-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-sm font-medium">{testimonial.name}</span>
<span className="text-sm text-muted-foreground">{testimonial.handle}</span>
</div>
</figcaption>
</motion.figure>
</section>
);
};
export default HeroOverlayTestimonial;

View File

@@ -0,0 +1,112 @@
import { motion } from "motion/react";
type HeroSplitProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroSplit = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
}: HeroSplitProps) => {
return (
<section
data-webild-section="HeroSplit"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col md:flex-row items-center gap-10 md:gap-20 w-content-width mx-auto">
<div className="flex flex-col items-center md:items-start gap-3 w-full md:w-1/2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="max-w-8/10 text-lg md:text-xl leading-tight text-center md:text-left"
>
{description}
</motion.p>
<div className="flex flex-wrap max-md:justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 w-full md:w-1/2 h-100 md:h-[65vh] md:max-h-[75svh]"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
</div>
</section>
);
};
export default HeroSplit;

View File

@@ -0,0 +1,143 @@
import { motion } from "motion/react";
type KpiItem = {
value: string;
label: string;
};
type HeroSplitKpiProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
kpis: [KpiItem, KpiItem, KpiItem];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroSplitKpi = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
kpis,
}: HeroSplitKpiProps) => {
const kpiPositions = [
"top-[5%] left-0",
"top-[40%] right-0",
"bottom-[5%] left-[5%]",
];
return (
<section
data-webild-section="HeroSplitKpi"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col md:flex-row items-center gap-8 w-content-width mx-auto">
<div className="w-full md:w-1/2">
<div className="flex flex-col items-center md:items-start gap-3">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="max-w-8/10 text-lg md:text-xl leading-tight text-center md:text-left"
>
{description}
</motion.p>
<div className="flex flex-wrap max-md:justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<div className="relative w-full md:w-1/2 h-100 md:h-[65vh] md:max-h-[75svh]">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="w-full h-full card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5 scale-80"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</motion.div>
{kpis.map((kpi, i) => (
<motion.div
key={i}
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.5, delay: 0.4 + i * 0.1, ease: "easeOut" }}
className={`absolute flex flex-col items-center card rounded-theme backdrop-blur-sm p-4 ${kpiPositions[i]}`}
>
<p className="text-2xl md:text-4xl font-medium">{kpi.value}</p>
<p className="text-sm md:text-base text-muted-foreground">{kpi.label}</p>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default HeroSplitKpi;

View File

@@ -0,0 +1,124 @@
import { motion } from "motion/react";
type HeroSplitMediaGridProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: [
{ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never },
{ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never }
];
};
const HeroSplitMediaGrid = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: HeroSplitMediaGridProps) => {
return (
<section
data-webild-section="HeroSplitMediaGrid"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col md:flex-row items-center gap-8 w-content-width mx-auto">
<div className="w-full md:w-1/2">
<div className="flex flex-col items-center md:items-start gap-3">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="max-w-8/10 text-lg md:text-xl leading-tight text-center md:text-left"
>
{description}
</motion.p>
<div className="flex flex-wrap max-md:justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="w-full md:w-1/2 grid grid-cols-2 gap-3"
>
{items.map((item, i) => (
<div
key={i}
className="h-80 md:h-[55vh] card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</motion.div>
</div>
</section>
);
};
export default HeroSplitMediaGrid;

View File

@@ -0,0 +1,165 @@
import { motion } from "motion/react";
import { Star } from "lucide-react";
type Testimonial = {
name: string;
handle: string;
text: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type HeroSplitTestimonialProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
testimonials: Testimonial[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const HeroSplitTestimonial = ({
tag,
title,
description,
primaryButton,
secondaryButton,
imageSrc,
videoSrc,
testimonials,
}: HeroSplitTestimonialProps) => {
const testimonial = testimonials[0];
return (
<section
data-webild-section="HeroSplitTestimonial"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col md:flex-row items-center gap-8 w-content-width mx-auto">
<div className="w-full md:w-1/2">
<div className="flex flex-col items-center md:items-start gap-3">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="max-w-8/10 text-lg md:text-xl leading-tight text-center md:text-left"
>
{description}
</motion.p>
<div className="flex flex-wrap max-md:justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="relative w-full md:w-1/2 aspect-3/4 md:aspect-auto md:h-[65vh] md:max-h-[75svh] card rounded-theme overflow-hidden p-3 xl:p-4 2xl:p-5"
>
{videoSrc ? (
<video
src={videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
<figure className="absolute bottom-6 left-6 right-6 md:left-auto md:max-w-1/2 card rounded-theme p-4 flex flex-col gap-3">
<div className="flex gap-1">
{Array.from({ length: 5 }).map((_, i) => (
<Star
key={i}
className={`w-5 h-5 text-accent ${i < testimonial.rating ? "fill-accent" : "fill-transparent"}`}
strokeWidth={1.5}
/>
))}
</div>
<blockquote className="text-base leading-tight text-balance">
{testimonial.text}
</blockquote>
<figcaption className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
className="w-10 h-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt=""
className="w-10 h-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-sm font-medium">{testimonial.name}</span>
<span className="text-sm text-muted-foreground">{testimonial.handle}</span>
</div>
</figcaption>
</figure>
</motion.div>
</div>
</section>
);
};
export default HeroSplitTestimonial;

View File

@@ -0,0 +1,156 @@
import { motion } from "motion/react";
type HeroSplitVerticalMarqueeProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
leftItems: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
rightItems: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
};
const HeroSplitVerticalMarquee = ({
tag,
title,
description,
primaryButton,
secondaryButton,
leftItems,
rightItems,
}: HeroSplitVerticalMarqueeProps) => {
return (
<section
data-webild-section="HeroSplitVerticalMarquee"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col md:flex-row items-center gap-8 w-content-width mx-auto">
<div className="w-full md:w-1/2">
<div className="flex flex-col items-center md:items-start gap-3">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-7xl 2xl:text-8xl font-medium text-center md:text-left text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="max-w-8/10 text-lg md:text-xl leading-tight text-center md:text-left"
>
{description}
</motion.p>
<div className="flex flex-wrap max-md:justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="w-full md:w-1/2 h-100 md:h-[75vh] flex gap-3 overflow-hidden"
>
<div className="flex-1 overflow-hidden mask-fade-y">
<div className="flex flex-col gap-3 animate-marquee-vertical">
{[...leftItems, ...leftItems, ...leftItems, ...leftItems].map((item, i) => (
<div
key={i}
className="shrink-0 aspect-square card rounded-theme overflow-hidden p-2 xl:p-3 2xl:p-4"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</div>
</div>
<div className="flex-1 overflow-hidden mask-fade-y">
<div className="flex flex-col gap-3 animate-marquee-vertical-reverse">
{[...rightItems, ...rightItems, ...rightItems, ...rightItems].map((item, i) => (
<div
key={i}
className="shrink-0 aspect-square card rounded-theme overflow-hidden p-2 xl:p-3 2xl:p-4"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</div>
</div>
</motion.div>
</div>
</section>
);
};
export default HeroSplitVerticalMarquee;

View File

@@ -0,0 +1,162 @@
import { motion } from "motion/react";
type HeroTiltedCardsProps = {
tag: string;
title: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton: { text: string; href: string };
items: ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never })[];
};
const HeroTiltedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
items,
}: HeroTiltedCardsProps) => {
const galleryStyles = [
"-rotate-6 z-10 -translate-y-5",
"rotate-6 z-20 translate-y-5 -ml-15",
"-rotate-6 z-30 -translate-y-5 -ml-15",
"rotate-6 z-40 translate-y-5 -ml-15",
"-rotate-6 z-50 -translate-y-5 -ml-15",
];
return (
<section
data-webild-section="HeroTiltedCards"
aria-label="Hero section"
className="relative flex items-center w-full h-fit md:h-svh pt-25 pb-20 md:py-0"
>
<div className="flex flex-col items-center gap-8 w-full md:w-content-width mx-auto">
<div className="flex flex-col items-center gap-3 w-content-width mx-auto text-center">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-balance"
>
{title}
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-base md:text-lg leading-tight text-balance"
>
{description}
</motion.p>
<div className="flex flex-wrap justify-center gap-3 mt-3">
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="hidden md:flex justify-center items-center w-full"
>
<div className="flex items-center justify-center">
{items.map((item, i) => (
<div
key={i}
className={`relative w-1/4 aspect-4/5 card rounded-theme overflow-hidden p-2 transition-transform duration-500 ease-out hover:scale-110 ${galleryStyles[i % galleryStyles.length]}`}
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="md:hidden grid grid-cols-2 gap-3 w-content-width mx-auto"
>
{items.slice(0, 4).map((item, i) => (
<div
key={i}
className="aspect-4/5 card rounded-theme overflow-hidden p-2"
>
{item.videoSrc ? (
<video
src={item.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Hero media"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={item.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
))}
</motion.div>
</div>
</section>
);
};
export default HeroTiltedCards;

View File

@@ -0,0 +1,100 @@
import { motion } from "motion/react";
type ContentItem =
| { type: "paragraph"; text: string }
| { type: "list"; items: string[] }
| { type: "numbered-list"; items: string[] };
type ContentSection = {
heading: string;
content: ContentItem[];
};
const PolicyContent = ({
title,
subtitle,
sections,
}: {
title: string;
subtitle?: string;
sections: ContentSection[];
}) => {
return (
<section
data-webild-section="PolicyContent"
aria-label="Policy content"
className="relative w-full pt-40 pb-20"
>
<div className="w-content-width mx-auto">
<div className="md:max-w-1/2 mx-auto flex flex-col gap-5">
<div className="flex flex-col gap-3">
<motion.h1
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="text-3xl md:text-4xl font-medium leading-tight text-balance"
>
{title}
</motion.h1>
{subtitle && (
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.1, ease: "easeOut" }}
className="text-sm opacity-50"
>
{subtitle}
</motion.p>
)}
</div>
<div className="w-full h-px bg-foreground/20" />
<div className="flex flex-col gap-5">
{sections.map((policySection, sectionIndex) => (
<motion.div
key={policySection.heading}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.05 + sectionIndex * 0.05, ease: "easeOut" }}
className="flex flex-col gap-3"
>
<h2 className="text-xl md:text-2xl font-medium leading-tight">{policySection.heading}</h2>
{policySection.content.map((item, i) => {
if (item.type === "paragraph") {
return (
<p key={i} className="text-sm md:text-base opacity-75 leading-relaxed">
{item.text}
</p>
);
}
if (item.type === "numbered-list") {
return (
<ol key={i} className="flex flex-col gap-3 pl-5 text-sm md:text-base opacity-75 leading-relaxed list-decimal">
{item.items.map((li, j) => (
<li key={j}>{li}</li>
))}
</ol>
);
}
return (
<ul key={i} className="flex flex-col gap-3 pl-5 text-sm md:text-base opacity-75 leading-relaxed list-disc">
{item.items.map((li, j) => (
<li key={j}>{li}</li>
))}
</ul>
);
})}
</motion.div>
))}
</div>
</div>
</div>
</section>
);
};
export default PolicyContent;

View File

@@ -0,0 +1,122 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type Metric = {
value: string;
title: string;
features: string[];
};
type MetricsFeatureCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
};
const MetricsFeatureCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
metrics,
}: MetricsFeatureCardsProps) => {
return (
<section
data-webild-section="MetricsFeatureCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{metrics.map((metric) => (
<div
key={metric.value}
className="flex flex-col justify-between gap-5 p-5 h-full card rounded-theme"
>
<div className="flex flex-col gap-0">
<span className="text-7xl font-medium leading-none truncate">{metric.value}</span>
<span className="text-xl truncate">{metric.title}</span>
</div>
<div className="flex flex-col gap-3 pt-5 border-t border-accent">
{metric.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-sm leading-tight">{feature}</span>
</div>
))}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default MetricsFeatureCards;

View File

@@ -0,0 +1,129 @@
import { motion } from "motion/react";
import { Sparkles, type LucideIcon } from "lucide-react";
type Metric = {
value: string;
title: string;
description: string;
icon: string | LucideIcon;
};
type MetricsGradientCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
};
const MetricsGradientCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
metrics,
}: MetricsGradientCardsProps) => {
return (
<section
data-webild-section="MetricsGradientCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{metrics.map((metric) => (
<div
key={metric.value}
className="relative flex flex-col items-center justify-center gap-0 p-5 min-h-70 h-full card rounded-theme"
>
<span
className="text-9xl font-medium leading-none text-center truncate"
style={{
backgroundImage:
"linear-gradient(to bottom, var(--color-foreground) 0%, var(--color-foreground) 20%, transparent 72%)",
WebkitBackgroundClip: "text",
backgroundClip: "text",
WebkitTextFillColor: "transparent",
}}
>
{metric.value}
</span>
<span className="mt-[-0.75em] text-4xl font-medium text-center truncate">
{metric.title}
</span>
<p className="max-w-9/10 md:max-w-7/10 mt-2 text-base leading-tight text-center line-clamp-2">
{metric.description}
</p>
<div className="absolute bottom-5 left-5 flex items-center justify-center size-10 primary-button rounded-full">
<Sparkles className="w-4 h-4 text-primary-cta-text" />
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default MetricsGradientCards;

View File

@@ -0,0 +1,114 @@
import { motion } from "motion/react";
import { Sparkles, type LucideIcon } from "lucide-react";
type Metric = {
icon: string | LucideIcon;
title: string;
value: string;
};
type MetricsIconCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
};
const MetricsIconCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
metrics,
}: MetricsIconCardsProps) => {
return (
<section
data-webild-section="MetricsIconCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{metrics.map((metric) => (
<div
key={metric.value}
className="flex flex-col items-center justify-center gap-3 p-5 min-h-70 h-full card rounded-theme"
>
<div className="flex items-center justify-center gap-2">
<div className="flex items-center justify-center size-8 primary-button rounded-full">
<Sparkles className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-xl truncate">{metric.title}</span>
</div>
<span className="text-7xl font-medium leading-none truncate">{metric.value}</span>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default MetricsIconCards;

View File

@@ -0,0 +1,130 @@
import { motion } from "motion/react";
type Metric = {
value: string;
title: string;
description: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type MetricsMediaCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
};
const MetricsMediaCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
metrics,
}: MetricsMediaCardsProps) => {
return (
<section
data-webild-section="MetricsMediaCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5 w-content-width mx-auto">
{metrics.map((metric) => (
<motion.div
key={metric.value}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="grid grid-cols-2 gap-5"
>
<div className="flex flex-col justify-between gap-5 p-5 aspect-square card rounded-theme">
<span className="text-5xl md:text-6xl font-medium leading-tight truncate">
{metric.value}
</span>
<div className="flex flex-col gap-2">
<span className="text-xl md:text-2xl font-medium truncate">{metric.title}</span>
<div className="w-full h-px bg-accent" />
<p className="text-base leading-tight truncate">{metric.description}</p>
</div>
</div>
<div className="rounded-theme overflow-hidden aspect-square">
{metric.videoSrc ? (
<video
src={metric.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Metric video"
className="w-full h-full object-cover"
/>
) : (
<img src={metric.imageSrc} alt="" className="w-full h-full object-cover" />
)}
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default MetricsMediaCards;

View File

@@ -0,0 +1,77 @@
import { motion } from "motion/react";
type Metric = {
value: string;
description: string;
};
type MetricsMinimalCardsProps = {
tag: string;
title: string;
metrics: Metric[];
};
const MetricsMinimalCards = ({ tag, title, metrics }: MetricsMinimalCardsProps) => {
return (
<section
data-webild-section="MetricsMinimalCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col gap-5">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 w-fit text-sm md:hidden"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-3xl md:text-5xl font-medium leading-tight text-balance"
>
{title}
</motion.h2>
</div>
<div className="w-full h-px bg-accent" />
<div className="flex flex-col md:flex-row md:items-start gap-8">
<span className="hidden md:block card rounded-full px-3 py-1 mb-1 text-sm">{tag}</span>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-2 gap-5 flex-1"
>
{metrics.map((metric) => (
<div
key={metric.value}
className="flex flex-col justify-between gap-5 p-5 md:p-8 aspect-video card rounded-theme"
>
<span className="text-7xl font-medium leading-none truncate">{metric.value}</span>
<div className="flex flex-col gap-5">
<div className="w-full h-px bg-accent" />
<p className="text-base md:text-lg leading-tight text-balance">
{metric.description}
</p>
</div>
</div>
))}
</motion.div>
</div>
</div>
</section>
);
};
export default MetricsMinimalCards;

View File

@@ -0,0 +1,109 @@
import { motion } from "motion/react";
type Metric = {
value: string;
description: string;
};
type MetricsSimpleCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
};
const MetricsSimpleCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
metrics,
}: MetricsSimpleCardsProps) => {
return (
<section
data-webild-section="MetricsSimpleCards"
aria-label="Metrics section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{metrics.map((metric) => (
<div
key={metric.value}
className="flex flex-col justify-between gap-5 p-5 min-h-70 h-full card rounded-theme"
>
<span className="text-7xl md:text-8xl font-medium leading-none truncate">
{metric.value}
</span>
<p className="text-base leading-tight text-balance">{metric.description}</p>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default MetricsSimpleCards;

View File

@@ -0,0 +1,146 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
description: string;
features: string[];
primaryButton: { text: string; href: string };
secondaryButton?: { text: string; href: string };
};
type PricingCenteredCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingCenteredCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingCenteredCardsProps) => {
return (
<section
data-webild-section="PricingCenteredCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{plans.map((plan) => (
<div
key={plan.tag}
className="flex flex-col items-center gap-3 p-5 h-full card rounded-theme text-center"
>
<span className="card rounded-full px-5 py-2 text-sm">{plan.tag}</span>
<div className="flex flex-col gap-1">
<span className="text-5xl font-medium">{plan.price}</span>
<span className="text-base">{plan.description}</span>
</div>
<div className="flex flex-col gap-3 w-full">
<a
href={plan.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text w-full"
>
{plan.primaryButton.text}
</a>
{plan.secondaryButton && (
<a
href={plan.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text w-full"
>
{plan.secondaryButton.text}
</a>
)}
</div>
<div className="w-full h-px bg-foreground/20" />
<div className="flex flex-col gap-3 w-full">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-base text-left">{feature}</span>
</div>
))}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default PricingCenteredCards;

View File

@@ -0,0 +1,152 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
description: string;
features: string[];
highlight?: string;
primaryButton: { text: string; href: string };
secondaryButton?: { text: string; href: string };
};
type PricingHighlightedCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingHighlightedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingHighlightedCardsProps) => {
return (
<section
data-webild-section="PricingHighlightedCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{plans.map((plan) => (
<div key={plan.tag} className="flex flex-col h-full">
{plan.highlight ? (
<div className="primary-button rounded-theme px-5 py-2 mb-2 text-sm text-center text-primary-cta-text">
{plan.highlight}
</div>
) : (
<div className="px-5 py-2 mb-2 text-sm invisible">placeholder</div>
)}
<div className="flex flex-col items-center gap-3 p-5 flex-1 card rounded-theme text-center">
<div className="flex flex-col gap-1">
<span className="text-5xl font-medium">{plan.price}</span>
<span className="text-xl font-medium">{plan.tag}</span>
</div>
<div className="h-px w-full bg-foreground/20" />
<div className="flex flex-col gap-3 w-full">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-base text-left">{feature}</span>
</div>
))}
</div>
<div className="flex flex-col gap-3 w-full mt-auto">
<a
href={plan.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text w-full"
>
{plan.primaryButton.text}
</a>
{plan.secondaryButton && (
<a
href={plan.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text w-full"
>
{plan.secondaryButton.text}
</a>
)}
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default PricingHighlightedCards;

View File

@@ -0,0 +1,143 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton?: { text: string; href: string };
features: string[];
};
type PricingLayeredCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingLayeredCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingLayeredCardsProps) => {
return (
<section
data-webild-section="PricingLayeredCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{plans.map((plan) => (
<div key={plan.tag} className="flex flex-col gap-3 p-3 h-full card rounded-theme">
<div className="flex flex-col gap-3 p-5 secondary-button rounded-theme">
<span className="card rounded-full px-3 py-1 w-fit text-sm">{plan.tag}</span>
<div className="flex flex-col gap-1">
<span className="text-5xl font-medium">{plan.price}</span>
<span className="text-base">{plan.description}</span>
</div>
<div className="flex flex-col gap-3">
<a
href={plan.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text w-full"
>
{plan.primaryButton.text}
</a>
{plan.secondaryButton && (
<a
href={plan.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text w-full"
>
{plan.secondaryButton.text}
</a>
)}
</div>
</div>
<div className="flex flex-col gap-3 p-3">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-base leading-tight">{feature}</span>
</div>
))}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default PricingLayeredCards;

View File

@@ -0,0 +1,148 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
period: string;
features: string[];
primaryButton: { text: string; href: string };
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type PricingMediaCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingMediaCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingMediaCardsProps) => {
return (
<section
data-webild-section="PricingMediaCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="flex flex-col gap-5 w-content-width mx-auto">
{plans.map((plan) => (
<motion.div
key={plan.tag}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex flex-col md:flex-row gap-5 md:gap-8 p-5 card rounded-theme"
>
<div className="w-full md:w-1/2 aspect-square md:aspect-4/3 rounded-theme overflow-hidden">
{plan.videoSrc ? (
<video
src={plan.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Plan video"
className="w-full h-full object-cover"
/>
) : (
<img src={plan.imageSrc} alt="" className="w-full h-full object-cover" />
)}
</div>
<div className="flex flex-col justify-center gap-5 w-full md:w-1/2">
<span className="card rounded-full px-3 py-1 w-fit text-sm">
{plan.price}
{plan.period}
</span>
<h3 className="text-4xl md:text-5xl font-medium truncate">{plan.tag}</h3>
<div className="flex flex-col gap-3">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-sm leading-tight">{feature}</span>
</div>
))}
</div>
<a
href={plan.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text w-fit mt-1"
>
{plan.primaryButton.text}
</a>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default PricingMediaCards;

View File

@@ -0,0 +1,124 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
description: string;
features: string[];
};
type PricingSimpleCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingSimpleCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingSimpleCardsProps) => {
return (
<section
data-webild-section="PricingSimpleCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{plans.map((plan) => (
<div key={plan.tag} className="flex flex-col gap-3 p-5 h-full card rounded-theme">
<span className="card rounded-full px-5 py-2 w-fit text-sm">{plan.tag}</span>
<div className="flex flex-col gap-1">
<span className="text-5xl font-medium">{plan.price}</span>
<span className="text-base">{plan.description}</span>
</div>
<div className="w-full h-px bg-foreground/20" />
<div className="flex flex-col gap-3">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-base">{feature}</span>
</div>
))}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default PricingSimpleCards;

View File

@@ -0,0 +1,153 @@
import { motion } from "motion/react";
import { Check } from "lucide-react";
type PricingPlan = {
tag: string;
price: string;
period: string;
description: string;
primaryButton: { text: string; href: string };
secondaryButton?: { text: string; href: string };
featuresTitle: string;
features: string[];
};
type PricingSplitCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
plans: PricingPlan[];
};
const PricingSplitCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
plans,
}: PricingSplitCardsProps) => {
return (
<section
data-webild-section="PricingSplitCards"
aria-label="Pricing section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<div className="flex flex-col gap-5 w-content-width mx-auto">
{plans.map((plan) => (
<motion.div
key={plan.tag}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex flex-col md:flex-row gap-5 md:gap-8 p-5 md:p-8 card rounded-theme"
>
<div className="flex flex-col justify-between gap-5 w-full md:w-1/2">
<div className="flex flex-col gap-3">
<span className="card rounded-full px-3 py-1 w-fit text-sm">{plan.tag}</span>
<div className="flex items-baseline gap-1">
<span className="text-5xl md:text-6xl font-medium">{plan.price}</span>
<span className="text-2xl">{plan.period}</span>
</div>
<p className="text-xl md:text-2xl leading-tight text-balance">{plan.description}</p>
</div>
<div className="flex flex-col gap-3">
<a
href={plan.primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text w-full"
>
{plan.primaryButton.text}
</a>
{plan.secondaryButton && (
<a
href={plan.secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text w-full"
>
{plan.secondaryButton.text}
</a>
)}
</div>
</div>
<div className="w-full h-px bg-foreground/20 md:hidden" />
<div className="flex flex-col gap-5 w-full md:w-1/2">
<h3 className="text-xl font-medium">{plan.featuresTitle}</h3>
<div className="flex flex-col gap-3">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-3">
<div className="flex items-center justify-center shrink-0 size-6 primary-button rounded-full">
<Check className="w-4 h-4 text-primary-cta-text" />
</div>
<span className="text-base leading-tight">{feature}</span>
</div>
))}
</div>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
};
export default PricingSplitCards;

View File

@@ -0,0 +1,121 @@
import { motion } from "motion/react";
import { ArrowUpRight } from "lucide-react";
type ProductMediaCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
products?: {
name: string;
price: string;
imageSrc: string;
onClick?: () => void;
}[];
};
const ProductMediaCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
products,
}: ProductMediaCardsProps) => {
return (
<section
data-webild-section="ProductMediaCards"
aria-label="Products section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{products?.map((product) => (
<button
key={product.name}
onClick={product.onClick}
className="group h-full flex flex-col gap-5 p-5 text-left card rounded-theme cursor-pointer"
>
<div className="aspect-square rounded-theme overflow-hidden">
<img src={product.imageSrc} alt="" className="w-full h-full object-cover" />
</div>
<div className="flex items-center justify-between gap-3">
<div className="flex-1 min-w-0">
<h3 className="text-base font-medium truncate">{product.name}</h3>
<p className="text-2xl font-medium">{product.price}</p>
</div>
<div className="flex items-center justify-center size-8 shrink-0 rounded-full primary-button">
<ArrowUpRight className="w-4 h-4 text-primary-cta-text transition-transform duration-300 group-hover:rotate-45" />
</div>
</div>
</button>
))}
</motion.div>
</div>
</section>
);
};
export default ProductMediaCards;

View File

@@ -0,0 +1,140 @@
import { motion } from "motion/react";
import { Plus, Minus } from "lucide-react";
type ProductQuantityCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
products?: {
name: string;
price: string;
imageSrc: string;
onAddToCart?: (quantity: number) => void;
}[];
};
const ProductQuantityCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
products,
}: ProductQuantityCardsProps) => {
return (
<section
data-webild-section="ProductQuantityCards"
aria-label="Products section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{products?.map((product) => (
<div key={product.name} className="h-full flex flex-col gap-5 p-5 card rounded-theme">
<div className="aspect-square rounded-theme overflow-hidden">
<img src={product.imageSrc} alt="" className="w-full h-full object-cover" />
</div>
<div className="flex flex-col gap-3">
<h3 className="text-xl font-medium truncate">{product.name}</h3>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<button
type="button"
className="flex items-center justify-center size-8 rounded-full card cursor-pointer"
aria-label="Decrease quantity"
>
<Minus className="w-4 h-4" />
</button>
<span className="w-fit text-base text-center font-medium">1</span>
<button
type="button"
className="flex items-center justify-center size-8 rounded-full card cursor-pointer"
aria-label="Increase quantity"
>
<Plus className="w-4 h-4" />
</button>
</div>
<button
type="button"
onClick={() => product.onAddToCart?.(1)}
className="primary-button rounded-theme h-8 px-5 text-base text-primary-cta-text font-medium cursor-pointer"
>
{product.price}
</button>
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default ProductQuantityCards;

View File

@@ -0,0 +1,134 @@
import { motion } from "motion/react";
import { Star } from "lucide-react";
type ProductRatingCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
products?: {
brand: string;
name: string;
price: string;
rating: number;
reviewCount: string;
imageSrc: string;
onClick?: () => void;
}[];
};
const ProductRatingCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
products,
}: ProductRatingCardsProps) => {
return (
<section
data-webild-section="ProductRatingCards"
aria-label="Products section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{products?.map((product) => (
<button
key={product.name}
onClick={product.onClick}
className="group h-full flex flex-col gap-5 p-5 text-left card rounded-theme cursor-pointer"
>
<div className="aspect-square rounded-theme overflow-hidden">
<img src={product.imageSrc} alt="" className="w-full h-full object-cover" />
</div>
<div className="flex flex-col gap-2">
<span className="secondary-button rounded-full w-fit px-2 py-0.5 text-sm text-secondary-cta-text">
{product.brand}
</span>
<div className="flex flex-col gap-1">
<h3 className="text-xl font-medium truncate">{product.name}</h3>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 text-accent">
{Array.from({ length: product.rating }).map((_, i) => (
<Star key={i} className="w-4 h-4 fill-current" />
))}
</div>
<span className="text-sm">({product.reviewCount})</span>
</div>
</div>
<p className="text-2xl font-medium">{product.price}</p>
</div>
</button>
))}
</motion.div>
</div>
</section>
);
};
export default ProductRatingCards;

View File

@@ -0,0 +1,131 @@
import { motion } from "motion/react";
import { ArrowUpRight } from "lucide-react";
type ProductVariantCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
products?: {
name: string;
variant: string;
price: string;
imageSrc: string;
onClick?: () => void;
}[];
};
const ProductVariantCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
products,
}: ProductVariantCardsProps) => {
return (
<section
data-webild-section="ProductVariantCards"
aria-label="Products section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{products?.map((product) => (
<button
key={product.name}
onClick={product.onClick}
className="group h-full flex flex-col gap-5 p-5 text-left card rounded-theme cursor-pointer"
>
<div className="relative aspect-square rounded-theme overflow-hidden">
<img
src={product.imageSrc}
alt=""
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
/>
<div className="absolute inset-0 flex items-center justify-center group-hover:bg-background/20 group-hover:backdrop-blur-xs transition-all duration-300">
<div className="flex items-center justify-center size-12 rounded-full primary-button opacity-0 group-hover:opacity-100 scale-75 group-hover:scale-100 transition-all duration-300">
<ArrowUpRight className="w-4 h-4 text-primary-cta-text" />
</div>
</div>
</div>
<div className="flex items-center justify-between gap-3">
<div className="flex flex-col flex-1 min-w-0">
<h3 className="text-xl font-medium truncate leading-tight text-balance">
{product.name}
</h3>
<p className="text-sm text-foreground/60">{product.variant}</p>
</div>
<span className="text-xl font-medium shrink-0">{product.price}</span>
</div>
</button>
))}
</motion.div>
</div>
</section>
);
};
export default ProductVariantCards;

View File

@@ -0,0 +1,98 @@
import { motion } from "motion/react";
import Marquee from "@/components/ui/marquee";
const SocialProofMarquee = ({
tag,
title,
description,
primaryButton,
secondaryButton,
names,
}: {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
names: string[];
}) => {
return (
<section
data-webild-section="SocialProofMarquee"
aria-label="Social proof section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="px-3 py-1 mb-1 text-sm card rounded-full"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
>
<Marquee speed={45}>
{names.map((name, index) => (
<div key={index} className="shrink-0 px-5 py-3 rounded-theme card">
<span className="text-2xl font-semibold whitespace-nowrap opacity-75">{name}</span>
</div>
))}
</Marquee>
</motion.div>
</div>
</section>
);
};
export default SocialProofMarquee;

View File

@@ -0,0 +1,148 @@
import { motion } from "motion/react";
import { Link2, type LucideIcon } from "lucide-react";
type SocialLink = {
icon: string | LucideIcon;
url: string;
};
type TeamMember = {
name: string;
role: string;
description: string;
socialLinks: SocialLink[];
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamDetailedCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
members: TeamMember[];
};
const TeamDetailedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
members,
}: TeamDetailedCardsProps) => {
return (
<section
data-webild-section="TeamDetailedCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{members.map((member) => (
<div key={member.name} className="relative aspect-4/5 rounded-theme overflow-hidden">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover"
/>
) : (
<img src={member.imageSrc} alt="" className="w-full h-full object-cover" />
)}
<div className="absolute bottom-5 left-5 right-5 flex flex-col gap-2 p-5 card rounded-theme">
<div className="flex items-start justify-between gap-3">
<span className="text-2xl font-medium leading-tight truncate">{member.name}</span>
<span className="secondary-button rounded-full px-3 py-1 text-xs leading-tight text-secondary-cta-text truncate">
{member.role}
</span>
</div>
<p className="text-base leading-tight">{member.description}</p>
<div className="flex gap-3 mt-1">
{member.socialLinks.map((link) => (
<a
key={link.url}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center size-9 primary-button rounded-full"
>
<Link2 className="w-4 h-4 text-primary-cta-text" />
</a>
))}
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TeamDetailedCards;

View File

@@ -0,0 +1,122 @@
import { motion } from "motion/react";
type TeamMember = {
name: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamGlassCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
members: TeamMember[];
};
const TeamGlassCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
members,
}: TeamGlassCardsProps) => {
return (
<section
data-webild-section="TeamGlassCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{members.map((member) => (
<div key={member.name} className="relative aspect-4/5 rounded-theme overflow-hidden">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover"
/>
) : (
<img src={member.imageSrc} alt="" className="w-full h-full object-cover" />
)}
<div className="absolute inset-x-0 bottom-0 h-1/3 backdrop-blur-xl bg-gradient-to-b from-transparent to-foreground/40" />
<div className="absolute inset-x-5 bottom-5 flex flex-col text-background">
<span className="text-2xl font-medium leading-tight truncate">{member.name}</span>
<span className="text-base leading-tight truncate">{member.role}</span>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TeamGlassCards;

View File

@@ -0,0 +1,142 @@
import { motion } from "motion/react";
type TeamMember = {
name: string;
role: string;
detail: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamGroup = {
title: string;
members: TeamMember[];
};
type TeamListCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
groups: TeamGroup[];
};
const TeamListCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
groups,
}: TeamListCardsProps) => {
return (
<section
data-webild-section="TeamListCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex flex-col gap-8"
>
{groups.map((group) => (
<div key={group.title} className="p-5 card rounded-theme">
<h3 className="mb-3 text-2xl md:text-3xl font-medium">{group.title}</h3>
<div className="flex flex-col divide-y divide-accent border-t border-accent">
{group.members.map((member) => (
<div key={member.name} className="flex items-center gap-3 py-5 last:pb-0">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="relative size-12 md:size-16 shrink-0 overflow-hidden rounded-theme">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover"
/>
) : (
<img src={member.imageSrc} alt="" className="w-full h-full object-cover" />
)}
</div>
<div className="flex flex-col min-w-0">
<span className="text-lg md:text-xl font-medium leading-tight truncate">
{member.name}
</span>
<span className="text-base leading-tight opacity-75 truncate">
{member.role}
</span>
</div>
</div>
<span className="text-sm md:text-lg font-medium shrink-0">{member.detail}</span>
</div>
))}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TeamListCards;

View File

@@ -0,0 +1,92 @@
import { motion } from "motion/react";
type TeamMember = {
name: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamMinimalCardsProps = {
tag: string;
title: string;
members: TeamMember[];
};
const TeamMinimalCards = ({ tag, title, members }: TeamMinimalCardsProps) => {
return (
<section
data-webild-section="TeamMinimalCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-5 md:gap-8 w-content-width mx-auto">
<div className="flex flex-col gap-5">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="md:hidden card rounded-full px-3 py-1 w-fit text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-3xl md:text-5xl font-medium leading-tight text-balance"
>
{title}
</motion.h2>
</div>
<div className="w-full h-px bg-accent" />
<div className="flex flex-col md:flex-row md:items-start gap-8">
<span className="hidden md:block card rounded-full px-3 py-1 mb-1 text-sm">{tag}</span>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-2 gap-5 flex-1"
>
{members.map((member) => (
<div
key={member.name}
className="flex flex-col gap-5 p-5 card rounded-theme overflow-hidden"
>
<div className="relative aspect-square md:aspect-5/4 rounded-theme overflow-hidden">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover"
/>
) : (
<img src={member.imageSrc} alt="" className="w-full h-full object-cover" />
)}
</div>
<div className="flex flex-col gap-5">
<div className="w-full h-px bg-accent" />
<div className="flex flex-col">
<span className="text-xl font-medium leading-tight truncate">{member.name}</span>
<span className="text-base leading-tight truncate">{member.role}</span>
</div>
</div>
</div>
))}
</motion.div>
</div>
</div>
</section>
);
};
export default TeamMinimalCards;

View File

@@ -0,0 +1,126 @@
import { motion } from "motion/react";
type TeamMember = {
name: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamOverlayCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
members: TeamMember[];
};
const TeamOverlayCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
members,
}: TeamOverlayCardsProps) => {
return (
<section
data-webild-section="TeamOverlayCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5 w-content-width mx-auto"
>
{members.map((member) => (
<div key={member.name} className="relative aspect-4/5 card rounded-theme">
<div className="relative w-full h-full rounded-theme overflow-hidden">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover"
/>
) : (
<img src={member.imageSrc} alt="" className="w-full h-full object-cover" />
)}
<div className="absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-b from-transparent to-foreground/60" />
<div className="absolute bottom-5 left-5 right-5 flex items-center justify-between gap-3 p-3 card rounded-theme">
<span className="text-xl font-medium leading-tight truncate">{member.name}</span>
<span className="primary-button rounded-full px-3 py-2 text-sm leading-tight text-primary-cta-text truncate">
{member.role}
</span>
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TeamOverlayCards;

View File

@@ -0,0 +1,126 @@
import { motion } from "motion/react";
type TeamMember = {
name: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TeamStackedCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
members: TeamMember[];
};
const TeamStackedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
members,
}: TeamStackedCardsProps) => {
return (
<section
data-webild-section="TeamStackedCards"
aria-label="Team section"
className="relative w-full py-20"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<a
href={primaryButton.href}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</a>
)}
{secondaryButton && (
<a
href={secondaryButton.href}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
className="flex flex-wrap justify-center gap-y-5"
>
{members.map((member) => (
<div
key={member.name}
className="flex flex-col items-center w-[55%] md:w-[28%] -mx-[4%] md:-mx-[2%] text-center"
>
<div className="p-3 mb-3 w-full aspect-square card rounded-theme overflow-hidden">
{member.videoSrc ? (
<video
src={member.videoSrc}
autoPlay
muted
loop
playsInline
aria-label="Member video"
className="w-full h-full object-cover rounded-theme"
/>
) : (
<img
src={member.imageSrc}
alt=""
className="w-full h-full object-cover rounded-theme"
/>
)}
</div>
<span className="w-4/5 text-2xl font-medium leading-tight truncate">{member.name}</span>
<span className="w-4/5 text-base leading-tight opacity-75 truncate">{member.role}</span>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TeamStackedCards;

View File

@@ -0,0 +1,116 @@
import { motion } from "motion/react";
type Avatar = {
name: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialAvatarCardProps = {
tag: string;
title: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
avatars: Avatar[];
};
const TestimonialAvatarCard = ({
tag,
title,
primaryButton,
secondaryButton,
avatars,
}: TestimonialAvatarCardProps) => {
const visibleAvatars = avatars.slice(0, 5);
const remainingCount = avatars.length - visibleAvatars.length;
return (
<section
data-webild-section="TestimonialAvatarCard"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-theme flex flex-col items-center gap-5 py-8 px-8"
>
<div className="flex flex-col items-center gap-3">
<span className="card rounded-full px-3 py-1 mb-1 text-sm">{tag}</span>
<motion.h3
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="md:max-w-7/10 text-3xl md:text-5xl font-medium leading-tight text-center text-balance"
>
{title}
</motion.h3>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<div className="flex items-center mt-1 -space-x-5">
{visibleAvatars.map((avatar) =>
avatar.videoSrc ? (
<video
key={avatar.name}
src={avatar.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={avatar.name}
className="size-14 md:size-20 rounded-full border-2 border-background object-cover"
/>
) : (
<img
key={avatar.name}
src={avatar.imageSrc}
alt={avatar.name}
className="size-14 md:size-20 rounded-full border-2 border-background object-cover"
/>
)
)}
{remainingCount > 0 && (
<div className="card flex items-center justify-center size-14 md:size-20 rounded-full border-2 border-background">
<span className="text-sm md:text-base font-medium">+{remainingCount}</span>
</div>
)}
</div>
</motion.div>
</div>
</section>
);
};
export default TestimonialAvatarCard;

View File

@@ -0,0 +1,145 @@
import { motion } from "motion/react";
type Testimonial = {
title: string;
quote: string;
name: string;
role: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialDetailedCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialDetailedCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialDetailedCardsProps) => {
return (
<section
data-webild-section="TestimonialDetailedCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="grid grid-cols-1 gap-5 md:grid-cols-2"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="card rounded-theme grid grid-cols-1 md:grid-cols-2 overflow-hidden">
<div className="flex flex-col justify-between gap-5 p-5 md:p-6">
<div className="flex flex-col gap-3">
<h3 className="text-2xl md:text-3xl font-medium leading-tight">{testimonial.title}</h3>
<blockquote className="text-base md:text-lg leading-tight opacity-75">
&ldquo;{testimonial.quote}&rdquo;
</blockquote>
</div>
<div className="flex items-center gap-3">
<div className="flex flex-col">
<span className="text-base font-medium leading-tight">{testimonial.name}</span>
<span className="text-sm leading-tight opacity-75">{testimonial.role}</span>
</div>
</div>
</div>
<div className="aspect-square overflow-hidden">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="w-full h-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="w-full h-full object-cover"
/>
)}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialDetailedCards;

View File

@@ -0,0 +1,177 @@
import { motion } from "motion/react";
import Marquee from "@/components/ui/marquee";
type Testimonial = {
name: string;
role: string;
quote: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialMarqueeCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialMarqueeCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialMarqueeCardsProps) => {
const half = Math.ceil(testimonials.length / 2);
const topRow = testimonials.slice(0, half);
const bottomRow = testimonials.slice(half);
return (
<section
data-webild-section="TestimonialMarqueeCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="flex flex-col gap-5"
>
<Marquee speed={40}>
{topRow.map((testimonial, index) => (
<div key={`top-${testimonial.name}-${index}`} className="card rounded-theme shrink-0 w-72 md:w-80 p-5">
<div className="flex flex-col justify-between gap-5 h-full">
<p className="text-lg leading-tight line-clamp-3">{testimonial.quote}</p>
<div className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="size-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="size-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-base font-medium leading-tight">{testimonial.name}</span>
<span className="text-sm leading-tight opacity-75">{testimonial.role}</span>
</div>
</div>
</div>
</div>
))}
</Marquee>
<Marquee speed={40} reverse>
{bottomRow.map((testimonial, index) => (
<div key={`bottom-${testimonial.name}-${index}`} className="card rounded-theme shrink-0 w-72 md:w-80 p-5">
<div className="flex flex-col justify-between gap-5 h-full">
<p className="text-lg leading-tight line-clamp-3">{testimonial.quote}</p>
<div className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="size-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="size-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-base font-medium leading-tight">{testimonial.name}</span>
<span className="text-sm leading-tight opacity-75">{testimonial.role}</span>
</div>
</div>
</div>
</div>
))}
</Marquee>
</motion.div>
</div>
</section>
);
};
export default TestimonialMarqueeCards;

View File

@@ -0,0 +1,169 @@
import { Star } from "lucide-react";
import { motion } from "motion/react";
type Testimonial = {
name: string;
role: string;
company: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type Metric = {
value: string;
label: string;
};
type TestimonialMetricsCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
metrics: [Metric, Metric, Metric];
};
const TestimonialMetricsCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
metrics,
}: TestimonialMetricsCardsProps) => {
return (
<section
data-webild-section="TestimonialMetricsCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="relative aspect-3/4 rounded-theme overflow-hidden">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="w-full h-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="w-full h-full object-cover"
/>
)}
<div className="card rounded-theme absolute inset-x-5 bottom-5 flex flex-col gap-2 p-4">
<div className="flex gap-1 mb-1">
{Array.from({ length: testimonial.rating }).map((_, i) => (
<Star key={i} className="size-5 text-accent fill-current" strokeWidth={1.5} />
))}
</div>
<span className="text-2xl font-medium leading-tight">{testimonial.name}</span>
<div className="flex flex-col">
<span className="text-base leading-tight">{testimonial.role}</span>
<span className="text-base leading-tight opacity-75">{testimonial.company}</span>
</div>
</div>
</div>
))}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.4, ease: "easeOut" }}
className="card rounded-theme flex flex-col md:flex-row items-center justify-between p-8"
>
{metrics.map((metric, index) => (
<div key={metric.label} className="flex flex-col md:flex-row items-center w-full md:flex-1">
<div className="flex flex-col items-center flex-1 gap-1 text-center py-5 md:py-0">
<span className="text-5xl font-medium">{metric.value}</span>
<span className="text-base">{metric.label}</span>
</div>
{index < 2 && (
<div className="w-full h-px md:h-20 md:w-px bg-foreground/20" />
)}
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialMetricsCards;

View File

@@ -0,0 +1,142 @@
import { Star } from "lucide-react";
import { motion } from "motion/react";
type Testimonial = {
name: string;
role: string;
company: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialOverlayCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialOverlayCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialOverlayCardsProps) => {
return (
<section
data-webild-section="TestimonialOverlayCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="relative aspect-3/4 rounded-theme overflow-hidden">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="w-full h-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="w-full h-full object-cover"
/>
)}
<div className="card rounded-theme absolute inset-x-5 bottom-5 flex flex-col gap-2 p-4">
<div className="flex gap-1 mb-1">
{Array.from({ length: testimonial.rating }).map((_, i) => (
<Star key={i} className="size-5 text-accent fill-current" strokeWidth={1.5} />
))}
</div>
<span className="text-2xl font-medium leading-tight">{testimonial.name}</span>
<div className="flex flex-col">
<span className="text-base leading-tight">{testimonial.role}</span>
<span className="text-base leading-tight opacity-75">{testimonial.company}</span>
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialOverlayCards;

View File

@@ -0,0 +1,137 @@
import { motion } from "motion/react";
type Testimonial = {
name: string;
role: string;
quote: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialQuoteCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialQuoteCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialQuoteCardsProps) => {
return (
<section
data-webild-section="TestimonialQuoteCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="card rounded-theme flex flex-col gap-4 p-5">
<div className="size-24 overflow-hidden rounded-theme">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="w-full h-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="w-full h-full object-cover"
/>
)}
</div>
<div className="flex flex-col gap-1">
<span className="text-2xl font-medium leading-tight">{testimonial.name}</span>
<span className="text-base leading-tight opacity-75">{testimonial.role}</span>
</div>
<p className="text-lg leading-tight">{testimonial.quote}</p>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialQuoteCards;

View File

@@ -0,0 +1,145 @@
import { Star } from "lucide-react";
import { motion } from "motion/react";
type Testimonial = {
name: string;
role: string;
quote: string;
rating: number;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialRatingCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialRatingCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialRatingCardsProps) => {
return (
<section
data-webild-section="TestimonialRatingCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="grid grid-cols-1 md:grid-cols-3 gap-5"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="card rounded-theme flex flex-col justify-between gap-5 h-full p-5">
<div className="flex flex-col items-start gap-5">
<div className="flex gap-1">
{Array.from({ length: testimonial.rating }).map((_, i) => (
<Star key={i} className="size-5 text-accent fill-current" strokeWidth={1.5} />
))}
</div>
<p className="text-lg leading-tight">{testimonial.quote}</p>
</div>
<div className="flex items-center gap-3">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="size-10 rounded-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.name}
className="size-10 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-base font-medium leading-tight">{testimonial.name}</span>
<span className="text-sm leading-tight opacity-75">{testimonial.role}</span>
</div>
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialRatingCards;

View File

@@ -0,0 +1,165 @@
import { motion } from "motion/react";
type Testimonial = {
tag: string;
title: string;
quote: string;
name: string;
date: string;
} & ({ avatarImageSrc: string; avatarVideoSrc?: never } | { avatarVideoSrc: string; avatarImageSrc?: never })
& ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialSplitCardsProps = {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
testimonials: Testimonial[];
};
const TestimonialSplitCards = ({
tag,
title,
description,
primaryButton,
secondaryButton,
testimonials,
}: TestimonialSplitCardsProps) => {
return (
<section
data-webild-section="TestimonialSplitCards"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col gap-8 w-content-width mx-auto">
<div className="flex flex-col items-center gap-2">
<motion.span
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="card rounded-full px-3 py-1 mb-1 text-sm"
>
{tag}
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-6xl font-medium text-center text-balance"
>
{title}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="md:max-w-6/10 text-lg leading-tight text-center"
>
{description}
</motion.p>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-3">
{primaryButton && (
<motion.a
href={primaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="primary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-primary-cta-text"
>
{primaryButton.text}
</motion.a>
)}
{secondaryButton && (
<motion.a
href={secondaryButton.href}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.35, ease: "easeOut" }}
className="secondary-button rounded-theme h-9 px-6 inline-flex items-center justify-center text-sm text-secondary-cta-text"
>
{secondaryButton.text}
</motion.a>
)}
</div>
)}
</div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-10%" }}
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
className="flex flex-col gap-5"
>
{testimonials.map((testimonial) => (
<div key={testimonial.name} className="card rounded-theme flex flex-col md:grid md:grid-cols-2 overflow-hidden">
<div className="flex flex-col justify-between gap-5 md:gap-8 p-5 md:p-8">
<div className="flex flex-col gap-3 md:gap-5">
<span className="card rounded-full px-3 py-1 w-fit text-sm">{testimonial.tag}</span>
<h3 className="text-3xl md:text-4xl font-medium leading-tight">{testimonial.title}</h3>
<p className="text-base md:text-lg leading-tight opacity-75">{testimonial.quote}</p>
</div>
<div className="flex items-center gap-3">
{testimonial.avatarVideoSrc ? (
<video
src={testimonial.avatarVideoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.name}
className="size-12 rounded-full object-cover"
/>
) : (
<img
src={testimonial.avatarImageSrc}
alt={testimonial.name}
className="size-12 rounded-full object-cover"
/>
)}
<div className="flex flex-col">
<span className="text-base font-medium leading-tight">{testimonial.name}</span>
<span className="text-sm leading-tight opacity-75">{testimonial.date}</span>
</div>
</div>
</div>
<div className="relative min-h-80 h-full md:aspect-square">
{testimonial.videoSrc ? (
<video
src={testimonial.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={testimonial.title}
className="w-full h-full object-cover"
/>
) : (
<img
src={testimonial.imageSrc}
alt={testimonial.title}
className="w-full h-full object-cover"
/>
)}
</div>
</div>
))}
</motion.div>
</div>
</section>
);
};
export default TestimonialSplitCards;

View File

@@ -0,0 +1,102 @@
import { Star } from "lucide-react";
import { motion } from "motion/react";
type Avatar = {
name: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
type TestimonialTrustCardProps = {
quote: string;
rating: number;
author: string;
avatars: Avatar[];
};
const TestimonialTrustCard = ({
quote,
rating,
author,
avatars,
}: TestimonialTrustCardProps) => {
const visibleAvatars = avatars.slice(0, 6);
const remainingCount = avatars.length - visibleAvatars.length;
return (
<section
data-webild-section="TestimonialTrustCard"
aria-label="Testimonials section"
className="relative w-full py-16 md:py-24"
>
<div className="flex flex-col items-center gap-5 w-content-width mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, ease: "easeOut" }}
className="flex gap-1"
>
{Array.from({ length: rating }).map((_, i) => (
<Star key={i} className="size-6 text-accent fill-current" strokeWidth={1.5} />
))}
</motion.div>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.05, ease: "easeOut" }}
className="text-3xl md:text-5xl font-medium leading-tight text-center text-balance"
>
{quote}
</motion.p>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.15, ease: "easeOut" }}
className="text-xl text-center"
>
{author}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.6, delay: 0.25, ease: "easeOut" }}
className="flex items-center justify-center -space-x-5"
>
{visibleAvatars.map((avatar) =>
avatar.videoSrc ? (
<video
key={avatar.name}
src={avatar.videoSrc}
autoPlay
muted
loop
playsInline
aria-label={avatar.name}
className="size-12 md:size-16 rounded-full border-2 border-background object-cover"
/>
) : (
<img
key={avatar.name}
src={avatar.imageSrc}
alt={avatar.name}
className="size-12 md:size-16 rounded-full border-2 border-background object-cover"
/>
)
)}
{remainingCount > 0 && (
<div className="card flex items-center justify-center size-12 md:size-16 rounded-full border-2 border-background">
<span className="text-sm md:text-base font-medium">+{remainingCount}</span>
</div>
)}
</motion.div>
</div>
</section>
);
};
export default TestimonialTrustCard;