"use client"; import type { MotionValue } from "framer-motion"; import { motion, useAnimationControls, useScroll, useTransform, } from "framer-motion"; import { Pause, Play, RotateCcw } from "lucide-react"; import React, { useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; const getTextPositionClassName = ( textPosition: "left" | "right" | "top" | "bottom", ) => { let textPositionClassName = ""; switch (textPosition) { case "left": textPositionClassName = "md:left-10 md:top-1/3 md:-translate-y-1/3"; break; case "right": textPositionClassName = "md:right-10 md:top-1/2 md:-translate-y-1/2"; break; case "top": textPositionClassName = "md:top-10 md:left-1/2 md:-translate-x-1/2"; break; case "bottom": textPositionClassName = "md:bottom-10 md:left-1/2 md:-translate-x-1/2"; break; default: textPositionClassName = "md:left-10 md:top-1/2 md:-translate-y-1/2"; break; } return textPositionClassName; }; const getCardPositions = (total: number, index: number) => { if (total <= 1) { return [0, 0, 0, 1, 1]; } const step = 1 / total; const center = step * index + step / 2; const range = [ 0, Math.max(0, center - step), center, Math.min(1, center + step), 1, ]; for (let i = 1; i < range.length; i++) { range[i] = Math.max(range[i], range[i - 1]); } return range; }; interface Visual { text: string; className: string; image: string; textPosition: "left" | "right" | "top" | "bottom"; } interface VisualCardProps { visual: Visual; index: number; total: number; scrollXProgress: MotionValue; } const VisualCard = ({ visual, index, total, scrollXProgress, }: VisualCardProps) => { const textPositionClassName = getTextPositionClassName(visual.textPosition); const range = getCardPositions(total, index); const opacity = useTransform(scrollXProgress, range, [0, 0, 1, 0, 0]); const x = useTransform(scrollXProgress, range, [ "100%", "100%", "0%", "-100%", "-100%", ]); return ( {visual.text} {visual.text} ); }; const GalleryPaginationDot = ({ index, scrollXProgress, total, isPlaying, currentVisualIndex, }: { index: number; scrollXProgress: MotionValue; total: number; isPlaying: boolean; currentVisualIndex: number; }) => { const range = getCardPositions(total, index); const controls = useAnimationControls(); const width = useTransform(scrollXProgress, range, [8, 8, 50, 8, 8]); useEffect(() => { if (isPlaying && currentVisualIndex === index) { controls.set({ width: 0, }); controls.start({ width: "100%", }); } }, [currentVisualIndex, isPlaying, index, controls]); const handleClick = (index: number) => { document.getElementById(`visual-${index}`)?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center", }); }; return ( handleClick(index)} aria-label={`رفتن به اسلاید ${new Intl.NumberFormat("fa-IR").format(index + 1)}`} className="relative inline-block h-2 rounded-full bg-foreground/50 hover:bg-foreground/70" > {isPlaying && currentVisualIndex === index && ( )} ); }; interface Gallery35Props { heading?: string; visuals?: Visual[]; className?: string; } const defaultVisuals: Visual[] = [ { text: "اصول طراحی مدرن رابط‌هایی شهودی می‌سازد که کاربران به آن اعتماد و علاقه دارند.", className: "bg-blue-100 dark:bg-blue-900", image: "/block-media/shadcnblocks/block/guri4/img11.png", textPosition: "left", }, { text: "فرایند خلاق نوآوری را با طراحی تکراری و بازخورد کاربر پیش می‌برد.", className: "bg-green-100 dark:bg-green-900", image: "/block-media/shadcnblocks/block/guri3/img5.jpeg", textPosition: "top", }, { text: "تحلیل بصری داده‌های پیچیده را به داشبوردهای واضح و قابل‌اقدام تبدیل می‌کند.", className: "bg-purple-100 dark:bg-purple-900", image: "/block-media/shadcnblocks/block/photos/simone-hutsch-uR__S5GX8Io-unsplash.jpg", textPosition: "bottom", }, { text: "هنر دیجیتال زیبایی‌شناسی کلاسیک را با قابلیت‌های تعاملی مدرن ترکیب می‌کند.", className: "bg-pink-100 dark:bg-pink-900", image: "/block-media/shadcnblocks/block/lummi/bw15.jpeg", textPosition: "top", }, { text: "مرکز نوآوری فناوری پیشرفته را با طراحی انسان‌محور در کنار هم قرار می‌دهد.", className: "bg-orange-100 dark:bg-orange-900", image: "/block-media/shadcnblocks/block/guri3/img23.jpeg", textPosition: "left", }, { text: "تحلیل تصویری داده‌ها، مسیر مشتری را از آگاهی تا تبدیل در یک نمای بصری نمایش می‌دهد.", className: "bg-indigo-100 dark:bg-indigo-900", image: "/block-media/shadcnblocks/block/guri3/img4.jpeg", textPosition: "top", }, ]; const Gallery35 = ({ className, heading = "گالری محصول", visuals = defaultVisuals, }: Gallery35Props) => { const containerRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [currentVisualIndex, setCurrentVisualIndex] = useState(0); const { scrollXProgress } = useScroll({ container: containerRef, }); useEffect(() => { const unsubscribe = scrollXProgress.on("change", (progress) => { if (isPlaying) return; let closestIndex = 0; let minDistance = Infinity; visuals.forEach((_, index) => { const range = getCardPositions(visuals.length, index); const visualPosition = range[2]; const distance = Math.abs(progress - visualPosition); if (distance < minDistance) { minDistance = distance; closestIndex = index; } }); setCurrentVisualIndex(closestIndex); }); return () => unsubscribe(); }, [scrollXProgress, visuals.length, isPlaying, visuals]); useEffect(() => { if (!isPlaying) return; const intervalId = setInterval(() => { if (currentVisualIndex === visuals.length - 1) { setIsPlaying(false); return currentVisualIndex; } const nextIndex = currentVisualIndex + 1; document.getElementById(`visual-${nextIndex}`)?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center", }); setCurrentVisualIndex(nextIndex); }, 2000); return () => clearInterval(intervalId); }, [isPlaying, visuals.length, currentVisualIndex]); const restartAnimation = () => { setCurrentVisualIndex(0); document.getElementById(`visual-0`)?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center", }); setIsPlaying(true); }; const isLastSlide = currentVisualIndex === visuals.length - 1 && !isPlaying; return (

{heading}

{visuals.map((visual, index) => { return ( ); })}
{visuals.map((_, index) => { return ( ); })}
); }; export { Gallery35 };