'use client';
/**
* @author: @nuelst
* @description: Card Flip - MVP Development Theme
* @version: 1.1.0
* @date: 2025-01-14
* @license: MIT
* @website: https://nueslt.vercel.app
* @github: https://github.com/nuelst
*/
import { cn } from '@/lib/utils';
import { ArrowRight, Code2, Copy, Rocket, Zap } from 'lucide-react';
import { useState } from 'react';
export interface CardFlipProps {
title?: string;
subtitle?: string;
description?: string;
features?: string[];
}
export default function CardFlip({
title = 'Build MVPs Fast',
subtitle = 'Launch your idea in record time',
description = 'Copy, paste, customize—and launch your MVP faster than ever with our developer-first component library.',
features = [
'Copy & Paste Ready',
'Developer-First',
'MVP Optimized',
'Zero Setup Required',
],
}: CardFlipProps) {
const [isFlipped, setIsFlipped] = useState(false);
return (
<div
className="group relative h-[360px] w-full max-w-[300px] [perspective:2000px]"
onMouseEnter={() => setIsFlipped(true)}
onMouseLeave={() => setIsFlipped(false)}
>
<div
className={cn(
'relative h-full w-full',
'[transform-style:preserve-3d]',
'transition-all duration-700',
isFlipped
? '[transform:rotateY(180deg)]'
: '[transform:rotateY(0deg)]',
)}
>
{/* Front of card */}
<div
className={cn(
'absolute inset-0 h-full w-full',
'[transform:rotateY(0deg)] [backface-visibility:hidden]',
'overflow-hidden rounded-2xl',
'bg-gradient-to-br from-white via-slate-50 to-slate-100',
'dark:from-zinc-900 dark:via-zinc-900/95 dark:to-zinc-800',
'border border-slate-200 dark:border-zinc-800/50',
'shadow-lg dark:shadow-xl',
'transition-all duration-700',
'group-hover:shadow-xl dark:group-hover:shadow-2xl',
'group-hover:border-primary/20 dark:group-hover:border-primary/30',
isFlipped ? 'opacity-0' : 'opacity-100',
)}
>
{/* Background gradient effect */}
<div className="from-primary/5 dark:from-primary/10 absolute inset-0 bg-gradient-to-br via-transparent to-blue-500/5 dark:to-blue-500/10" />
{/* Animated code blocks */}
<div className="absolute inset-0 flex items-center justify-center pt-20">
<div className="relative flex h-[100px] w-[200px] flex-col items-center justify-center gap-2">
{/* Code blocks animation */}
{[...Array(6)].map((_, i) => (
<div
key={i}
className={cn(
'h-3 w-full rounded-sm',
'from-primary/20 via-primary/30 to-primary/20 bg-gradient-to-r',
'animate-[slideIn_2s_ease-in-out_infinite]',
'opacity-0',
)}
style={{
width: `${60 + Math.random() * 40}%`,
animationDelay: `${i * 0.2}s`,
marginLeft: `${Math.random() * 20}%`,
}}
/>
))}
{/* Central rocket icon */}
<div className="absolute inset-0 flex items-center justify-center">
<div
className={cn(
'h-12 w-12 rounded-xl',
'from-primary via-primary/90 to-primary/80 bg-gradient-to-br',
'flex items-center justify-center',
'shadow-primary/25 shadow-lg',
'animate-pulse',
'transition-all duration-500 group-hover:scale-110 group-hover:rotate-12',
)}
>
<Rocket className="h-6 w-6 text-white" />
</div>
</div>
</div>
</div>
{/* Bottom content */}
<div className="absolute right-0 bottom-0 left-0 p-5">
<div className="flex items-center justify-between gap-3">
<div className="space-y-1.5">
<h3 className="text-lg leading-snug font-semibold tracking-tight text-zinc-900 transition-all duration-500 ease-out group-hover:translate-y-[-4px] dark:text-white">
{title}
</h3>
<p className="line-clamp-2 text-sm tracking-tight text-zinc-600 transition-all delay-[50ms] duration-500 ease-out group-hover:translate-y-[-4px] dark:text-zinc-300">
{subtitle}
</p>
</div>
<div className="group/icon relative">
<div
className={cn(
'absolute inset-[-8px] rounded-lg transition-opacity duration-300',
'from-primary/20 via-primary/10 bg-gradient-to-br to-transparent',
'opacity-0 group-hover/icon:opacity-100',
)}
/>
<Zap className="text-primary relative z-10 h-5 w-5 transition-all duration-300 group-hover/icon:scale-110 group-hover/icon:rotate-12" />
</div>
</div>
</div>
</div>
{/* Back of card */}
<div
className={cn(
'absolute inset-0 h-full w-full',
'[transform:rotateY(180deg)] [backface-visibility:hidden]',
'rounded-2xl p-5',
'bg-gradient-to-br from-white via-slate-50 to-slate-100',
'dark:from-zinc-900 dark:via-zinc-900/95 dark:to-zinc-800',
'border border-slate-200 dark:border-zinc-800',
'shadow-lg dark:shadow-xl',
'flex flex-col',
'transition-all duration-700',
'group-hover:shadow-xl dark:group-hover:shadow-2xl',
'group-hover:border-primary/20 dark:group-hover:border-primary/30',
!isFlipped ? 'opacity-0' : 'opacity-100',
)}
>
{/* Background gradient */}
<div className="from-primary/5 dark:from-primary/10 absolute inset-0 rounded-2xl bg-gradient-to-br via-transparent to-blue-500/5 dark:to-blue-500/10" />
<div className="relative z-10 flex-1 space-y-5">
<div className="space-y-2">
<div className="mb-2 flex items-center gap-2">
<div className="from-primary via-primary/90 to-primary/80 flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br">
<Code2 className="h-4 w-4 text-white" />
</div>
<h3 className="text-lg leading-snug font-semibold tracking-tight text-zinc-900 transition-all duration-500 ease-out group-hover:translate-y-[-2px] dark:text-white">
{title}
</h3>
</div>
<p className="line-clamp-2 text-sm tracking-tight text-zinc-600 transition-all duration-500 ease-out group-hover:translate-y-[-2px] dark:text-zinc-400">
{description}
</p>
</div>
<div className="space-y-2.5">
{features.map((feature, index) => {
const icons = [Copy, Code2, Rocket, Zap];
const IconComponent = icons[index % icons.length];
return (
<div
key={feature}
className="flex items-center gap-3 text-sm text-zinc-700 transition-all duration-500 dark:text-zinc-300"
style={{
transform: isFlipped
? 'translateX(0)'
: 'translateX(-10px)',
opacity: isFlipped ? 1 : 0,
transitionDelay: `${index * 100 + 200}ms`,
}}
>
<div className="bg-primary/10 dark:bg-primary/20 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md">
<IconComponent className="text-primary h-3 w-3" />
</div>
<span className="font-medium">{feature}</span>
</div>
);
})}
</div>
</div>
<div className="relative z-10 mt-auto border-t border-slate-200 pt-4 dark:border-zinc-800">
<div
className={cn(
'group/start relative',
'flex items-center justify-between',
'rounded-lg p-2.5',
'transition-all duration-300',
'bg-gradient-to-r from-slate-100 via-slate-100 to-slate-100',
'dark:from-zinc-800 dark:via-zinc-800 dark:to-zinc-800',
'hover:from-primary/10 hover:via-primary/5 hover:to-transparent',
'dark:hover:from-primary/20 dark:hover:via-primary/10 dark:hover:to-transparent',
'hover:scale-[1.02] hover:cursor-pointer',
'hover:border-primary/20 border border-transparent',
)}
>
<span className="group-hover/start:text-primary text-sm font-semibold text-zinc-900 transition-colors duration-300 dark:text-white">
Start Building
</span>
<div className="group/icon relative">
<div
className={cn(
'absolute inset-[-6px] rounded-lg transition-all duration-300',
'from-primary/20 via-primary/10 bg-gradient-to-br to-transparent',
'scale-90 opacity-0 group-hover/start:scale-100 group-hover/start:opacity-100',
)}
/>
<ArrowRight className="text-primary relative z-10 h-4 w-4 transition-all duration-300 group-hover/start:translate-x-1 group-hover/start:scale-110" />
</div>
</div>
</div>
</div>
</div>
<style jsx>{`
@keyframes slideIn {
0% {
transform: translateX(-100px);
opacity: 0;
}
50% {
transform: translateX(0);
opacity: 0.8;
}
100% {
transform: translateX(100px);
opacity: 0;
}
}
`}</style>
</div>
);
}