import { useEffect, useRef, useState, createContext, useContext, } from 'react' import type { ReactNode } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { X } from 'lucide-react' import { clsx } from 'clsx' import { twMerge } from 'tailwind-merge' function cn(...inputs: Parameters) { return twMerge(clsx(inputs)) } /* ─── types ─────────────────────────────────────────── */ export interface CardData { src: string title: string category: string content: ReactNode /** Optional thumbnail body fields (MUI-style card layout) */ summary?: string author?: string date?: string pages?: number } interface CarouselContextType { onCardClose: (index: number) => void currentIndex: number } /* ─── context ───────────────────────────────────────── */ const CarouselContext = createContext({ onCardClose: () => {}, currentIndex: 0, }) /* ─── Carousel ──────────────────────────────────────── */ export function Carousel({ items, initialScroll = 0, }: { items: ReactNode[] initialScroll?: number }) { const carouselRef = useRef(null) const [currentIndex, setCurrentIndex] = useState(0) const [paused, setPaused] = useState(false) useEffect(() => { if (carouselRef.current) { carouselRef.current.scrollLeft = initialScroll } }, [initialScroll]) /* Auto-advance: scroll one card-width every 4s. Pauses on hover. */ useEffect(() => { if (paused) return const el = carouselRef.current if (!el) return const id = window.setInterval(() => { if (!el) return const step = window.innerWidth < 768 ? 280 : 360 // card width + gap const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 4 el.scrollTo({ left: atEnd ? 0 : el.scrollLeft + step, behavior: 'smooth', }) }, 4000) return () => window.clearInterval(id) }, [paused]) function handleCardClose(index: number) { if (!carouselRef.current) return const cardWidth = window.innerWidth < 768 ? 230 : 350 carouselRef.current.scrollTo({ left: (cardWidth + 16) * (index + 1), behavior: 'smooth' }) setCurrentIndex(index) } function scrollByStep(direction: -1 | 1) { if (!carouselRef.current) return const step = window.innerWidth < 768 ? 280 : 360 carouselRef.current.scrollBy({ left: direction * step, behavior: 'smooth' }) } return (
setPaused(true)} onMouseLeave={() => setPaused(false)} > {/* Scrollable track */}
setPaused(true)} onTouchEnd={() => setPaused(false)} >
{items.map((item, index) => ( {item} ))}
{/* ── Glass pill controls — prev/next + auto state ── */}
{/* Prev */} {/* Auto state pill */}
{paused ? 'PAUSED' : 'AUTO'}
{/* Next */}
) } /* ─── Card ──────────────────────────────────────────── */ export function Card({ card, index, layout = false, }: { card: CardData index: number layout?: boolean }) { const [open, setOpen] = useState(false) const { onCardClose } = useContext(CarouselContext) useEffect(() => { function onKey(e: KeyboardEvent) { if (e.key === 'Escape') handleClose() } document.body.style.overflow = open ? 'hidden' : 'auto' window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [open]) function handleOpen() { setOpen(true) } function handleClose() { setOpen(false); onCardClose(index) } return ( <> {/* ── Modal ── */} {open && ( /* Outer: scroll host */
{/* Backdrop — click to close */} {/* Centering wrapper */}
e.stopPropagation()} > {/* Close button */} {/* Hero image strip */}
{card.title} {/* Stronger gradient — fully opaque at bottom for readable text */}
{/* Category badge — red, top */}
{card.category}
{/* Title — bottom, bright white with shadow for clarity */}

{card.title}

{/* Body */}
{card.content}
)} {/* ── Card thumbnail — MUI ActionAreaCard pattern, editorial palette ── */} { e.currentTarget.style.boxShadow = '0 12px 32px rgba(26,23,18,0.12)' e.currentTarget.style.borderColor = 'var(--ink-6)' }} onMouseLeave={(e) => { e.currentTarget.style.boxShadow = '0 1px 2px rgba(26,23,18,0.04)' e.currentTarget.style.borderColor = 'var(--rule-thin)' }} > {/* Media — image strip */}
{/* Category badge — overlaid bottom-right of image (RTL: visual start) */}
{card.category}
{/* Content — title + summary + meta */}
{/* Title + summary group — vertically centered in remaining space */}
{/* Title */}

{card.title}

{/* Summary */} {card.summary && (

{card.summary}

)}
{/* Meta footer — tight below summary */} {(card.author || card.date || card.pages) && (
{card.author && ( {card.author} )} {card.date && ( {card.date} )}
{card.pages && ( {card.pages.toLocaleString('fa-IR')} ص ← )}
)}
) } /* ─── BlurImage ─────────────────────────────────────── */ function BlurImage({ src, alt, className }: { src: string; alt: string; className?: string }) { const [loaded, setLoaded] = useState(false) return ( {alt} setLoaded(true)} className={cn('transition-[filter] duration-500', loaded ? 'blur-0' : 'blur-md', className)} /> ) } /* ─── useOutsideClick ───────────────────────────────── */ export function useOutsideClick( ref: React.RefObject, callback: () => void, ) { useEffect(() => { function listener(e: MouseEvent | TouchEvent) { if (!ref.current || ref.current.contains(e.target as Node)) return callback() } document.addEventListener('mousedown', listener) document.addEventListener('touchstart', listener) return () => { document.removeEventListener('mousedown', listener) document.removeEventListener('touchstart', listener) } }, [ref, callback]) }