249 lines
7.0 KiB
TypeScript
249 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
AnimatePresence,
|
|
motion,
|
|
useInView,
|
|
useScroll,
|
|
useTransform,
|
|
} from "framer-motion";
|
|
import { Plus } from "lucide-react";
|
|
import React from "react";
|
|
import { useRef, useState } from "react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const projects = [
|
|
{
|
|
id: 1,
|
|
title: "بهینهسازی گردش کار طراحی",
|
|
category: "رابط و تجربه کاربری",
|
|
date: "۲۰ خرداد ۱۴۰۴",
|
|
image:
|
|
"/block-media/shadcnblocks/block/photos/studio-republic-fotKKqWNMQ4-unsplash.jpg",
|
|
},
|
|
{
|
|
id: 2,
|
|
title: "دسترسی یکپارچه جهانی",
|
|
category: "فناوری ابری",
|
|
date: "۱۸ خرداد ۱۴۰۴",
|
|
image:
|
|
"/block-media/shadcnblocks/block/photos/israel-andrade-YI_9SivVt_s-unsplash.jpg",
|
|
},
|
|
];
|
|
|
|
const ProjectItem = ({
|
|
project,
|
|
index,
|
|
containerRef,
|
|
hoveredIndex,
|
|
setHoveredIndex,
|
|
mousePos,
|
|
setMousePos,
|
|
}: {
|
|
project: {
|
|
id: number;
|
|
title: string;
|
|
category: string;
|
|
date: string;
|
|
image: string;
|
|
};
|
|
index: number;
|
|
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
hoveredIndex: number | null;
|
|
setHoveredIndex: (index: number | null) => void;
|
|
mousePos: { x: number; y: number };
|
|
setMousePos: (pos: { x: number; y: number }) => void;
|
|
}) => {
|
|
const { scrollYProgress } = useScroll({
|
|
target: containerRef,
|
|
offset: ["start end", "end start"],
|
|
});
|
|
|
|
const scrollScale = useTransform(
|
|
scrollYProgress,
|
|
[0, 0.5, 1],
|
|
[0.95, 1, 1.05],
|
|
);
|
|
const scrollY = useTransform(scrollYProgress, [0, 1], [50, -50]);
|
|
const scrollOpacity = useTransform(
|
|
scrollYProgress,
|
|
[0, 0.2, 0.8, 1],
|
|
[0.7, 1, 1, 0.7],
|
|
);
|
|
|
|
const handleMouseMove = (e: React.MouseEvent) => {
|
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
setMousePos({
|
|
x: e.clientX - rect.left,
|
|
y: e.clientY - rect.top,
|
|
});
|
|
setHoveredIndex(index);
|
|
};
|
|
|
|
const handleMouseLeave = () => {
|
|
setHoveredIndex(null);
|
|
};
|
|
|
|
return (
|
|
<motion.div
|
|
key={project.id}
|
|
className="group relative cursor-pointer"
|
|
variants={{
|
|
hidden: { opacity: 0, y: 60, scale: 0.95 },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
scale: 1,
|
|
transition: {
|
|
duration: 0.8,
|
|
ease: [0.25, 0.46, 0.45, 0.94],
|
|
},
|
|
},
|
|
}}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseLeave={handleMouseLeave}
|
|
whileHover={{ scale: 1.02 }}
|
|
transition={{ duration: 0.6, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
>
|
|
<motion.div
|
|
className="relative overflow-hidden rounded-xl"
|
|
style={{
|
|
scale: scrollScale,
|
|
y: scrollY,
|
|
opacity: scrollOpacity,
|
|
}}
|
|
>
|
|
<motion.img
|
|
src={project.image}
|
|
alt={project.title}
|
|
className="h-full w-full rounded-xl object-cover"
|
|
initial={{ scale: 1.1 }}
|
|
animate={{ scale: hoveredIndex === index ? 1.15 : 1.1 }}
|
|
transition={{
|
|
duration: 0.8,
|
|
ease: [0.25, 0.46, 0.45, 0.94],
|
|
}}
|
|
/>
|
|
<motion.div
|
|
className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent"
|
|
initial={{ opacity: 0.6 }}
|
|
animate={{ opacity: hoveredIndex === index ? 0.8 : 0.6 }}
|
|
transition={{ duration: 0.4 }}
|
|
/>
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center text-white">
|
|
<motion.div
|
|
className="flex items-center gap-2 text-sm text-white/80 lg:mb-4"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: hoveredIndex === index ? -5 : 0 }}
|
|
transition={{ duration: 0.5, delay: 0.1, ease: "easeOut" }}
|
|
>
|
|
<p className="text-lg font-medium">{project.date}</p>
|
|
<span>|</span>
|
|
<p>{project.category}</p>
|
|
</motion.div>
|
|
<motion.h2
|
|
className="px-4 text-center text-xl font-semibold md:text-4xl"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{
|
|
opacity: 1,
|
|
y: hoveredIndex === index ? -10 : 0,
|
|
scale: hoveredIndex === index ? 1.05 : 1,
|
|
}}
|
|
transition={{ duration: 0.5, delay: 0.2, ease: "easeOut" }}
|
|
>
|
|
{project.title}
|
|
</motion.h2>
|
|
</div>
|
|
</motion.div>
|
|
|
|
<AnimatePresence>
|
|
{hoveredIndex === index && (
|
|
<motion.div
|
|
key={`icon-${project.id}`}
|
|
initial={{ opacity: 0, scale: 0, rotate: -180 }}
|
|
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
|
exit={{ opacity: 0, scale: 0, rotate: 180 }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 400,
|
|
damping: 25,
|
|
duration: 0.3,
|
|
}}
|
|
style={{
|
|
top: mousePos.y - 24,
|
|
left: mousePos.x - 24,
|
|
position: "absolute",
|
|
pointerEvents: "none",
|
|
}}
|
|
className="z-20 flex h-12 w-12 items-center justify-center rounded-full border border-white/30 bg-white/20 text-white shadow-lg backdrop-blur-sm"
|
|
>
|
|
<motion.div
|
|
transition={{
|
|
duration: 2,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: "linear",
|
|
}}
|
|
>
|
|
<Plus size={20} />
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
interface Projects3Props {
|
|
className?: string;
|
|
}
|
|
|
|
const Projects3 = ({ className }: Projects3Props) => {
|
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
|
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
const isInView = useInView(containerRef, { once: true, margin: "-100px" });
|
|
|
|
return (
|
|
<section dir="rtl" lang="fa" className={cn("py-32", className)}>
|
|
<div className="container space-y-16">
|
|
<h1 className="text-center text-4xl font-semibold md:text-6xl">
|
|
پروژههای ما
|
|
</h1>
|
|
|
|
<motion.div
|
|
ref={containerRef}
|
|
className="mx-auto grid max-w-7xl grid-cols-1 gap-8 lg:gap-12"
|
|
variants={{
|
|
hidden: { opacity: 0 },
|
|
visible: {
|
|
opacity: 1,
|
|
transition: {
|
|
staggerChildren: 0.3,
|
|
delayChildren: 0.2,
|
|
},
|
|
},
|
|
}}
|
|
initial="hidden"
|
|
animate={isInView ? "visible" : "hidden"}
|
|
>
|
|
{projects.map((project, index) => (
|
|
<ProjectItem
|
|
key={project.id}
|
|
project={project}
|
|
index={index}
|
|
containerRef={containerRef}
|
|
hoveredIndex={hoveredIndex}
|
|
setHoveredIndex={setHoveredIndex}
|
|
mousePos={mousePos}
|
|
setMousePos={setMousePos}
|
|
/>
|
|
))}
|
|
</motion.div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Projects3 };
|