Install Dependencies: Install the required dependencies using npm:
npm install framer-motion
Add animations to tailwind.config file:
module.exports = {
theme: {
extend: {
animation: {
"border-flow": "border-flow 3s ease-in-out infinite",
"border-flow-reverse": "border-flow-reverse 3s ease-in-out infinite",
"border-flow-vertical": "border-flow-vertical 3s ease-in-out infinite",
"border-flow-vertical-reverse": "border-flow-vertical-reverse 3s ease-in-out infinite",
},
keyframes: {
"border-flow": {
"0%": { transform: "translateX(-100%)" },
"100%": { transform: "translateX(100%)" },
},
"border-flow-reverse": {
"0%": { transform: "translateX(100%)" },
"100%": { transform: "translateX(-100%)" },
},
"border-flow-vertical": {
"0%": { transform: "translateY(-100%)" },
"100%": { transform: "translateY(100%)" },
},
"border-flow-vertical-reverse": {
"0%": { transform: "translateY(100%)" },
"100%": { transform: "translateY(-100%)" },
},
},
},
},
},
};
"use client";
import { cn } from "@/lib/utils";
import {
Brain,
Code,
Hammer,
Lightbulb,
Puzzle,
Rocket,
SquareTerminal,
Users,
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { motion, useAnimation } from "framer-motion";
const features = [
{
title: "Designed for Innovators",
description:
"Empowering coders, creators, and forward-thinkers to build the future.",
icon: <Lightbulb className="size-6 text-muted-foreground" />,
},
{
title: "Engineered for Builders",
description:
"For those who turn ideas into solutions, one line of code at a time.",
icon: <Hammer className="size-6 text-muted-foreground" />,
},
{
title: "Built for Developers",
description: "Built for engineers, developers and indiehackers",
icon: <SquareTerminal className="size-6 text-muted-foreground" />,
},
{
title: "Crafted for Problem Solvers",
description:
"Supporting those who love to break down complex problems into elegant code.",
icon: <Puzzle className="size-6 text-muted-foreground" />,
},
{
title: "Optimized for Thinkers",
description:
"Made for those who push boundaries, dream big, and innovate relentlessly.",
icon: <Brain className="size-6 text-muted-foreground" />,
},
{
title: "Built for Code Enthusiasts",
description:
"For developers and creators driven by curiosity and a passion for tech.",
icon: <Code className="size-6 text-muted-foreground" />,
},
{
title: "Empowering Visionaries",
description:
"Fueling the aspirations of engineers, makers, and ambitious thinkers everywhere.",
icon: <Rocket className="size-6 text-muted-foreground" />,
},
{
title: "Tailored for Collaborators",
description:
"Enabling teams to connect, share knowledge, and create together.",
icon: <Users className="size-6 text-muted-foreground" />,
},
];
const containerVariants = {
visible: {
transition: {
delayChildren: 0.5,
staggerChildren: 0.5,
},
},
hidden: {
transition: {
delayChildren: 0.5,
staggerChildren: 0.5,
},
},
};
const childrenVariants = {
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: {
duration: 0.6,
ease: "easeInOut",
},
},
hidden: {
opacity: 0,
y: 10,
filter: "blur(5px)",
transition: {
duration: 0.6,
ease: "easeInOut",
},
},
};
const FeaturesSection = (): JSX.Element => {
const containerRef = useRef<HTMLDivElement>(null);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [isHovering, setIsHovering] = useState(false);
const handleMouseMove = (e: React.MouseEvent) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
setMousePosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
};
return (
<motion.section
className="container flex flex-col max-w-7xl min-h-[350px] w-full justify-center mx-auto p-6 sm:py-14 items-center gap-y-12 lg:gap-y-16"
variants={containerVariants}
animate="visible"
initial="hidden"
>
<div className="flex flex-col">
<motion.div
className="relative inline-flex w-min mx-auto h-8 select-none overflow-hidden rounded-full pt-[2px] px-[1.5px] pb-[1px] focus:outline-none"
variants={{
visible: {
opacity: 1,
y: 0,
},
hidden: {
opacity: 0,
y: 2,
},
}}
>
<span className="absolute inset-[-1000%] animate-[spin_3s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,hsl(var(--primary))_0%,hsl(var(--muted))_50%,hsl(var(--primary))_100%)]" />
<span className="inline-flex h-full w-full cursor-pointer items-center justify-center rounded-full bg-background px-4 py-1 font-medium text-sm text-foreground backdrop-blur-3xl">
Features
</span>
</motion.div>
<motion.h2
className="leading-[1.1]! mt-6 mb-0 text-center tracking-tight font-semibold text-3xl text-foreground md:text-5xl lg:text-center"
variants={childrenVariants}
>
Build like a Designer
</motion.h2>
<motion.p
className="mt-4 max-w-lg text-center text-lg text-muted-foreground lg:text-center px-2"
variants={childrenVariants}
>
Optiq UI makes your app look beautiful and easy to use with premade
sections and components.
</motion.p>
</div>
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 mx-auto"
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
ref={containerRef}
transition={{ duration: 0.6, ease: "easeInOut" }}
style={{
background: isHovering
? `radial-gradient(200px circle at ${mousePosition.x}px ${mousePosition.y}px, hsl(var(--primary)) -200%, transparent 100%)`
: "none",
}}
>
{features.map((feature, index) => (
<motion.div
className={cn(
"w-full h-full flex flex-col border-border relative py-10 group/card overflow-hidden max-w-7xl",
{
"lg:border-t": index % 3 === 0 && index < 4,
"lg:border-b":
((index % 4 === 0 || index === features.length - 1) &&
index >= 4) ||
index === 2 ||
index === 1,
"lg:border-l":
(index >= 4 && index === features.length - 1) || index === 3,
"lg:border-r": (index >= 4 && index % 4 === 0) || index === 0,
}
)}
key={index}
variants={{
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
},
hidden: {
opacity: 0,
y: 10,
filter: "blur(5px)",
},
}}
transition={{ duration: 0.6, ease: "easeInOut" }}
>
{/* Animated border overlay */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute inset-0 opacity-0 group-hover/card:opacity-100 transition-opacity duration-300">
{/* Top border */}
<div className="absolute top-0 left-0 w-full h-px">
<div className="absolute w-full h-full animate-border-flow bg-gradient-to-r from-transparent via-primary to-transparent" />
</div>
{/* Right border */}
<div className="absolute top-0 right-0 w-px h-full">
<div className="absolute w-full h-full animate-border-flow-vertical bg-gradient-to-b from-transparent via-primary to-transparent" />
</div>
{/* Bottom border */}
<div className="absolute bottom-0 left-0 w-full h-px">
<div className="absolute w-full h-full animate-border-flow-reverse bg-gradient-to-r from-transparent via-primary to-transparent" />
</div>
{/* Left border */}
<div className="absolute top-0 left-0 w-px h-full">
<div className="absolute w-full h-full animate-border-flow-vertical-reverse bg-gradient-to-b from-transparent via-primary to-transparent" />
</div>
</div>
</div>
<div className="opacity-0 group-hover/card:opacity-100 transition duration-200 absolute inset-0 h-full w-full bg-gradient-to-t from-primary/35 dark:from-primary/30 to-transparent"></div>
<div
className={cn(
"mb-4 mt-2 px-10 transition-all duration-200 group-hover/card:translate-x-2",
{
"lg:group-hover/card:-translate-x-2 lg:group-hover/card:-translate-y-0":
index === 2 || index === 6,
"lg:group-hover/card:translate-x-2 lg:group-hover/card:-translate-y-0":
index === 1 || index === 5,
"lg:group-hover/card:-translate-y-2 lg:group-hover/card:translate-x-0":
index === 4 || index === features.length - 1,
"lg:group-hover/card:translate-y-2 lg:group-hover/card:translate-x-0":
index === 0 || index === 3,
}
)}
>
{feature.icon}
</div>
<div
className={cn(
"px-10 z-10 transition-all duration-200 group-hover/card:translate-x-2",
{
"lg:group-hover/card:-translate-x-2 lg:group-hover/card:-translate-y-0":
index === 2 || index === 6,
"lg:group-hover/card:translate-x-2 lg:group-hover/card:-translate-y-0":
index === 1 || index === 5,
"lg:group-hover/card:-translate-y-2 lg:group-hover/card:translate-x-0":
index === 4 || index === features.length - 1,
"lg:group-hover/card:translate-y-2 lg:group-hover/card:translate-x-0":
index === 0 || index === 3,
}
)}
>
<h2 className="text-xl font-bold text-foreground relative text-left mt-0">
{feature.title}
</h2>
<p className="text-muted-foreground text-md max-w-xs pr-4 text-left mt-2">
{feature.description}
</p>
</div>
<div
className={cn(
"absolute bg-neutral-700 transition-all duration-200 group-hover/card:h-[0.4rem] group-hover/card:bg-primary",
{
"lg:left-[calc(50%-3rem)] lg:top-0 rounded-tr-full rounded-br-full lg:rounded-br-full lg:rounded-bl-full lg:rounded-tr-none lg:rounded-tl-none w-1 h-[6rem] lg:w-[6rem] lg:h-1 lg:group-hover/card:w-[6.5rem] lg:group-hover/card:h-1 group-hover/card:h-[6.5rem]":
index === 0 || index === 3,
"lg:left-0 lg:top-[calc(50%-3rem)] rounded-tr-full rounded-br-full w-1 h-[6rem] group-hover/card:h-[6.5rem]":
index === 1 || index === 5,
"lg:right-0 lg:top-[calc(50%-3rem)] rounded-tr-full rounded-br-full lg:rounded-tl-full lg:rounded-bl-full lg:rounded-tr-none lg:rounded-br-none w-1 h-[6rem] group-hover/card:h-[6.5rem]":
index === 2 || index === 6,
"lg:left-[calc(50%-3rem)] lg:bottom-0 rounded-tr-full rounded-br-full lg:rounded-br-none lg:rounded-bl-none lg:rounded-tr-full lg:rounded-tl-full lg:w-[6rem] lg:h-1 w-1 h-[6rem] lg:group-hover/card:w-[6.5rem] lg:group-hover/card:h-1 group-hover/card:h-[6.5rem]":
index === 4 || index === features.length - 1,
}
)}
></div>
</motion.div>
))}
</motion.div>
</motion.section>
);
};
export default FeaturesSection;