462 lines
18 KiB
TypeScript
462 lines
18 KiB
TypeScript
"use client";
|
|
import { cn } from "@/lib/utils";
|
|
import React, { useState, useEffect } from "react";
|
|
import { BentoGrid, BentoGridItem } from "../ui/bento-grid";
|
|
import {
|
|
IconBoxAlignRightFilled,
|
|
IconClipboardCopy,
|
|
IconFileBroken,
|
|
IconSignature,
|
|
IconTableColumn,
|
|
} from "@tabler/icons-react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import { FaPlay } from "react-icons/fa";
|
|
|
|
export function BentoFeatures() {
|
|
const variants = {
|
|
hidden: { opacity: 0, y: 50 },
|
|
visible: { opacity: 1, y: 0 },
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
whileInView="visible"
|
|
variants={variants}
|
|
transition={{ duration: 0.5 }}
|
|
className="max-w-6xl mx-auto py-20"
|
|
>
|
|
<div className="text-center mb-16">
|
|
<motion.h2
|
|
// className="text-4xl md:text-5xl bg-clip-text text-transparent bg-gradient-to-r from-purple-500 to-blue-500"
|
|
className="text-4xl md:text-5xl bg-clip-text text-white"
|
|
variants={{
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: { opacity: 1, y: 0 }
|
|
}}
|
|
>
|
|
All-in-One Platform for AI-Powered Services
|
|
</motion.h2>
|
|
</div>
|
|
|
|
<BentoGrid className="md:auto-rows-[20rem]">
|
|
{items.map((item, i) => (
|
|
<BentoGridItem
|
|
key={i}
|
|
title={item.title}
|
|
description={item.description}
|
|
header={item.header}
|
|
className={cn("[&>p:text-lg] ", item.className)}
|
|
icon={item.icon}
|
|
/>
|
|
))}
|
|
</BentoGrid>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
const SkeletonOne = () => {
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
const [animationState, setAnimationState] = useState('idle');
|
|
|
|
// Control animation cycle
|
|
useEffect(() => {
|
|
if (isHovered) {
|
|
const animationSequence = async () => {
|
|
setAnimationState('drag');
|
|
setTimeout(() => setAnimationState('drop'), 1500);
|
|
setTimeout(() => setAnimationState('complete'), 2000);
|
|
setTimeout(() => setAnimationState('drag'), 3500);
|
|
};
|
|
|
|
animationSequence();
|
|
const interval = setInterval(animationSequence, 4000);
|
|
return () => clearInterval(interval);
|
|
} else {
|
|
setAnimationState('idle');
|
|
}
|
|
}, [isHovered]);
|
|
|
|
return (
|
|
<motion.div
|
|
className="flex flex-1 w-full h-full min-h-[6rem] bg-gradient-to-br from-purple-900/50 to-blue-900/50 flex-col space-y-2 rounded-xl overflow-hidden"
|
|
onHoverStart={() => setIsHovered(true)}
|
|
onHoverEnd={() => setIsHovered(false)}
|
|
>
|
|
<div className="flex flex-col space-y-2 p-6">
|
|
<motion.h2
|
|
className="text-4xl font-bold text-white"
|
|
animate={{ scale: isHovered ? 1.05 : 1 }}
|
|
>
|
|
No Coding Needed
|
|
</motion.h2>
|
|
<motion.div
|
|
className="h-2 w-32 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-4"
|
|
animate={{ width: isHovered ? "40%" : "8rem" }}
|
|
/>
|
|
<motion.p
|
|
className="text-neutral-300 mt-2 flex items-center gap-2"
|
|
animate={{ x: isHovered ? 10 : 0 }}
|
|
>
|
|
Learn more
|
|
<motion.span
|
|
animate={{ x: isHovered ? 5 : 0 }}
|
|
transition={{ repeat: isHovered ? Infinity : 0, duration: 0.6 }}
|
|
>
|
|
→
|
|
</motion.span>
|
|
</motion.p>
|
|
</div>
|
|
|
|
{/* Drag and drop animation container */}
|
|
<div className="relative p-6 h-64 flex items-center justify-center">
|
|
{/* Drop zone */}
|
|
<motion.div
|
|
className="absolute w-36 h-36 rounded-lg border-2 border-dashed border-indigo-400/50 flex items-center justify-center"
|
|
style={{ right: '20%', bottom: '20%' }}
|
|
animate={{
|
|
borderColor: animationState === 'drag' ? 'rgba(129, 140, 248, 0.8)' : 'rgba(129, 140, 248, 0.5)',
|
|
scale: animationState === 'drag' ? 1.05 : 1,
|
|
backgroundColor: animationState === 'complete' ? 'rgba(129, 140, 248, 0.15)' : 'rgba(129, 140, 248, 0.05)'
|
|
}}
|
|
>
|
|
<motion.div
|
|
className="text-indigo-300 text-sm font-medium"
|
|
animate={{ opacity: animationState === 'complete' ? 0 : 0.8 }}
|
|
>
|
|
Drop here
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
{/* File being dragged */}
|
|
<motion.div
|
|
className="absolute flex flex-col items-center justify-center gap-1"
|
|
style={{ left: '20%', top: '20%' }}
|
|
animate={{
|
|
x: animationState === 'idle' ? 0 : animationState === 'drag' ? 100 : animationState === 'drop' || animationState === 'complete' ? 130 : 0,
|
|
y: animationState === 'idle' ? 0 : animationState === 'drag' ? 80 : animationState === 'drop' || animationState === 'complete' ? 100 : 0,
|
|
opacity: animationState === 'complete' ? 0 : 1,
|
|
scale: animationState === 'drag' ? 1.1 : animationState === 'drop' ? 0.95 : 1,
|
|
rotate: animationState === 'drag' ? 2 : 0,
|
|
}}
|
|
transition={{
|
|
type: animationState === 'drop' ? 'spring' : 'tween',
|
|
stiffness: 200,
|
|
damping: 15
|
|
}}
|
|
>
|
|
{/* File icon */}
|
|
<div className="w-16 h-20 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-lg shadow-lg flex flex-col overflow-hidden">
|
|
<div className="h-3 w-full bg-white/20"></div>
|
|
<div className="flex-1 flex items-center justify-center text-white font-bold text-xs">
|
|
WORKFLOW
|
|
</div>
|
|
</div>
|
|
<motion.div
|
|
className="text-indigo-200 text-xs"
|
|
animate={{ opacity: animationState === 'idle' ? 1 : 0 }}
|
|
>
|
|
Drag me
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
{/* Success checkmark that appears after drop */}
|
|
<motion.div
|
|
className="absolute text-green-400 text-2xl"
|
|
style={{ right: '20%', bottom: '20%' }}
|
|
animate={{
|
|
opacity: animationState === 'complete' ? 1 : 0,
|
|
scale: animationState === 'complete' ? [0, 1.2, 1] : 0,
|
|
}}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-10 w-10" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
|
</svg>
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const SkeletonTwo = () => {
|
|
const [isTyping, setIsTyping] = useState(false);
|
|
|
|
return (
|
|
<motion.div
|
|
className="flex flex-1 w-full h-full min-h-[2rem] bg-gradient-to-br from-purple-900/20 to-blue-900/20 flex-col items-center justify-center p-6 rounded-xl cursor-pointer"
|
|
onHoverStart={() => setIsTyping(true)}
|
|
onHoverEnd={() => setIsTyping(false)}
|
|
>
|
|
<div className="flex flex-col space-y-4 w-full">
|
|
<div className="relative w-full h-20 bg-black/40 rounded-xl overflow-hidden group">
|
|
<AnimatePresence>
|
|
{isTyping && (
|
|
<motion.div
|
|
className="absolute inset-0 flex items-center justify-start px-4"
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
>
|
|
<motion.div
|
|
initial={{ width: 0 }}
|
|
animate={{
|
|
width: "auto",
|
|
transition: {
|
|
duration: 2,
|
|
repeat: Infinity,
|
|
repeatType: "reverse"
|
|
}
|
|
}}
|
|
className="overflow-hidden whitespace-nowrap text-purple-300"
|
|
>
|
|
console.log("Hello World!");
|
|
</motion.div>
|
|
<motion.div
|
|
animate={{ opacity: [0, 1, 0] }}
|
|
transition={{ duration: 0.8, repeat: Infinity }}
|
|
className="text-purple-300 ml-1"
|
|
>
|
|
|
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
<motion.div
|
|
className="space-y-2"
|
|
animate={{
|
|
opacity: isTyping ? 1 : 0.5,
|
|
transition: { duration: 1, repeat: Infinity, repeatType: "reverse" }
|
|
}}
|
|
>
|
|
<div className="h-3 w-3/4 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-full" />
|
|
<div className="h-3 w-1/2 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-full" />
|
|
<div className="h-3 w-5/6 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-full" />
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const SkeletonThree = () => {
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
|
|
return (
|
|
<motion.div
|
|
className="flex flex-1 w-full h-full min-h-[6rem] bg-gradient-to-br from-purple-900/20 to-blue-900/20 flex-col items-center justify-center p-6 rounded-xl"
|
|
onHoverStart={() => setIsHovered(true)}
|
|
onHoverEnd={() => setIsHovered(false)}
|
|
>
|
|
<div className="relative w-full h-full bg-black/40 rounded-xl overflow-hidden p-6">
|
|
<motion.div
|
|
className="flex flex-col space-y-4"
|
|
animate={{ y: isHovered ? -10 : 0 }}
|
|
>
|
|
{/* <div className="flex items-center justify-between">
|
|
<div className="h-8 w-24 bg-purple-500/30 rounded-full" />
|
|
<motion.div
|
|
className="text-2xl font-bold text-green-400"
|
|
animate={{ scale: isHovered ? 1.1 : 1 }}
|
|
>
|
|
$0
|
|
</motion.div>
|
|
</div> */}
|
|
|
|
{/* Graph container */}
|
|
<motion.div
|
|
className="h-20 w-full bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-xl relative overflow-hidden"
|
|
animate={{
|
|
scale: isHovered ? 1.02 : 1,
|
|
backgroundColor: isHovered ? "rgba(139, 92, 246, 0.3)" : "rgba(139, 92, 246, 0.2)"
|
|
}}
|
|
>
|
|
{/* Growing graph line */}
|
|
<motion.div
|
|
className="absolute bottom-0 left-0 w-full h-full"
|
|
initial={{ opacity: 0.7 }}
|
|
animate={{ opacity: isHovered ? 1 : 0.7 }}
|
|
>
|
|
<svg width="100%" height="100%" viewBox="0 0 100 50" preserveAspectRatio="none">
|
|
<motion.path
|
|
d="M0,50 L0,35 C10,40 20,20 30,25 C40,30 50,10 60,15 C70,20 80,5 90,10 L100,5 L100,50 Z"
|
|
fill="rgba(52, 211, 153, 0.2)"
|
|
stroke="rgba(52, 211, 153, 0.8)"
|
|
strokeWidth="2"
|
|
initial={{ pathLength: 0.3, y: 20 }}
|
|
animate={{
|
|
pathLength: isHovered ? 1 : 0.3,
|
|
y: isHovered ? 0 : 20
|
|
}}
|
|
transition={{ duration: 1.5 }}
|
|
/>
|
|
</svg>
|
|
</motion.div>
|
|
</motion.div>
|
|
|
|
{/* <div className="flex justify-between items-center">
|
|
<div className="h-6 w-16 bg-purple-500/30 rounded-full" />
|
|
<motion.div
|
|
className="text-xl font-bold text-green-400"
|
|
animate={{ scale: isHovered ? 1.1 : 1 }}
|
|
>
|
|
$499
|
|
</motion.div>
|
|
</div> */}
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const SkeletonFour = () => {
|
|
return (
|
|
<motion.div
|
|
className="flex flex-1 w-full h-full min-h-[6rem] bg-gradient-to-br from-purple-900/20 to-blue-900/20 flex-col items-center justify-center p-8 rounded-xl"
|
|
>
|
|
<div className="flex flex-col space-y-4 w-full">
|
|
<motion.div
|
|
className="bg-purple-500/20 p-4 rounded-xl cursor-pointer group relative overflow-hidden"
|
|
whileHover={{ scale: 1.02 }}
|
|
>
|
|
<motion.div
|
|
className="absolute inset-0 bg-purple-500/20 translate-x-[-100%] group-hover:translate-x-0 transition-transform duration-300"
|
|
/>
|
|
<div className="relative flex items-center space-x-4">
|
|
<motion.div
|
|
className="p-2 bg-purple-500/40 rounded-lg"
|
|
whileHover={{ rotate: 360 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
<IconBoxAlignRightFilled className="h-6 w-6 text-purple-200" />
|
|
</motion.div>
|
|
<span className="text-white font-medium">Form Builder</span>
|
|
</div>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
className="bg-white/10 p-4 rounded-xl cursor-pointer group relative overflow-hidden"
|
|
whileHover={{ scale: 1.02 }}
|
|
>
|
|
<motion.div
|
|
className="absolute inset-0 bg-white/10 translate-x-[-100%] group-hover:translate-x-0 transition-transform duration-300"
|
|
/>
|
|
<div className="relative flex items-center space-x-4">
|
|
<motion.div
|
|
className="p-2 bg-white/20 rounded-lg"
|
|
whileHover={{ rotate: 360 }}
|
|
transition={{ duration: 0.4 }}
|
|
>
|
|
<IconClipboardCopy className="h-6 w-6 text-white" />
|
|
</motion.div>
|
|
<span className="text-white font-medium">Newsletter Creator</span>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
|
|
// const SkeletonFive = () => {
|
|
// return (
|
|
// <motion.div
|
|
// className="flex flex-1 w-full h-full min-h-[6rem] dark:bg-dot-white/[0.2] bg-dot-black/[0.2] flex-col items-center justify-center p-8"
|
|
// >
|
|
// <div className="grid grid-cols-2 gap-4 w-full">
|
|
// <div className="bg-purple-500/20 p-4 rounded-xl aspect-square flex items-center justify-center">
|
|
// <motion.div
|
|
// animate={{ rotate: 360 }}
|
|
// transition={{ duration: 8, repeat: Infinity, ease: "linear" }}
|
|
// className="w-12 h-12 rounded-full border-4 border-purple-500 border-t-transparent"
|
|
// />
|
|
// </div>
|
|
// <div className="bg-blue-500/20 p-4 rounded-xl aspect-square flex items-center justify-center">
|
|
// <motion.div
|
|
// initial={{ scale: 0.8 }}
|
|
// animate={{ scale: 1.2 }}
|
|
// transition={{ duration: 2, repeat: Infinity, repeatType: "reverse" }}
|
|
// className="w-12 h-12 bg-gradient-to-tr from-blue-500 to-purple-500 rounded-lg"
|
|
// />
|
|
// </div>
|
|
// <div className="bg-green-500/20 p-4 rounded-xl aspect-square flex items-center justify-center">
|
|
// <motion.div
|
|
// initial={{ opacity: 0.5 }}
|
|
// animate={{ opacity: 1 }}
|
|
// transition={{ duration: 2, repeat: Infinity, repeatType: "reverse" }}
|
|
// className="w-12 h-12 bg-gradient-to-br from-green-500 to-blue-500 rounded-full"
|
|
// />
|
|
// </div>
|
|
// <div className="bg-pink-500/20 p-4 rounded-xl aspect-square flex items-center justify-center">
|
|
// <motion.div
|
|
// animate={{
|
|
// scale: [1, 1.2, 1],
|
|
// rotate: [0, 90, 0]
|
|
// }}
|
|
// transition={{ duration: 4, repeat: Infinity }}
|
|
// className="w-12 h-12 bg-gradient-to-bl from-pink-500 to-purple-500 rounded-lg"
|
|
// />
|
|
// </div>
|
|
// </div>
|
|
// </motion.div>
|
|
// );
|
|
// };
|
|
|
|
const items = [
|
|
{
|
|
title: "Drag & Drop No-Code Builder",
|
|
description: (
|
|
<span className="text-sm">
|
|
Easily create robust MicroSaaS applications using our intuitive drag-and-drop interface. Empower yourself to design custom workflows without writing a single line of code, streamlining operations and enhancing efficiency.
|
|
</span>
|
|
),
|
|
header: <SkeletonOne />,
|
|
className: "md:col-span-1 md:row-span-2",
|
|
icon: <IconClipboardCopy className="h-4 w-4 text-neutral-500" />,
|
|
},
|
|
{
|
|
title: "Writing Simplified",
|
|
description: (
|
|
<span className="text-sm">
|
|
Transform your writing process with the power of AI. Use the built-in AI writer to generate, refine, or continue content effortlessly.
|
|
</span>
|
|
),
|
|
header: <SkeletonTwo />,
|
|
className: "md:col-span-1",
|
|
icon: <IconFileBroken className="h-4 w-4 text-neutral-500" />,
|
|
},
|
|
{
|
|
title: "Monetize Smarter",
|
|
description: (
|
|
<span className="text-sm">
|
|
Launch your own community or tap into our marketplace to sell AI subscriptions to SMBs and beyond. Transform your ideas into profitable ventures.
|
|
</span>
|
|
),
|
|
header: <SkeletonThree />,
|
|
className: "md:col-span-1",
|
|
icon: <IconSignature className="h-4 w-4 text-neutral-500" />,
|
|
},
|
|
{
|
|
title: "Integrated Creative Tools",
|
|
description: (
|
|
<span className="text-sm">
|
|
From forms to video editors, AI writers to newsletter creators, we've got you covered. Access a comprehensive suite of tools designed to enhance your creative workflow.
|
|
</span>
|
|
),
|
|
header: <SkeletonFour />,
|
|
className: "md:col-span-2",
|
|
icon: <IconTableColumn className="h-4 w-4 text-neutral-500" />,
|
|
},
|
|
// {
|
|
// title: "Smart Templates",
|
|
// description: (
|
|
// <span className="text-sm">
|
|
// Access a library of intelligent templates that adapt to your needs. Each template is powered by AI to provide personalized starting points.
|
|
// </span>
|
|
// ),
|
|
// header: <SkeletonFive />,
|
|
// className: "md:col-span-1",
|
|
// icon: <IconBoxAlignRightFilled className="h-4 w-4 text-neutral-500" />,
|
|
// }
|
|
];
|