171 lines
7.3 KiB
TypeScript
171 lines
7.3 KiB
TypeScript
"use client";
|
|
|
|
import { ChevronUp } from "lucide-react";
|
|
import React, { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { useContent } from "@/components/layout/content-provider";
|
|
|
|
import type { CarouselApi } from "@/components/ui/carousel";
|
|
import {
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
CarouselNext,
|
|
CarouselPrevious,
|
|
} from "@/components/ui/carousel";
|
|
|
|
const MOBILE_BREAKPOINT = 768;
|
|
const AUTOPLAY_DELAY_MS = 3200;
|
|
|
|
const FeatureCarousel = () => {
|
|
const content = useContent();
|
|
const { featureCarousel } = content.pages.home;
|
|
const carouselItems = featureCarousel.items;
|
|
|
|
const [openCardIndex, setOpenCardIndex] = useState<number | null>(null);
|
|
const [isMobile, setIsMobile] = useState(false);
|
|
const [carouselApi, setCarouselApi] = useState<CarouselApi | null>(null);
|
|
|
|
useEffect(() => {
|
|
const mq = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
|
const handler = () => setIsMobile(mq.matches);
|
|
handler();
|
|
mq.addEventListener("change", handler);
|
|
return () => mq.removeEventListener("change", handler);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!isMobile || !carouselApi) return;
|
|
const timer = setInterval(() => {
|
|
carouselApi.scrollNext();
|
|
}, AUTOPLAY_DELAY_MS);
|
|
return () => clearInterval(timer);
|
|
}, [isMobile, carouselApi]);
|
|
|
|
const handleCardClick = (index: number) => {
|
|
if (!isMobile) return;
|
|
setOpenCardIndex((prev) => (prev === index ? null : index));
|
|
};
|
|
|
|
return (
|
|
<section id="rahavarha" className="py-32">
|
|
<div className="relative container mx-auto max-w-7xl px-6">
|
|
<h2 className="text-heading-2 z-10 mb-10 text-center font-semibold tracking-tight text-foreground">
|
|
{featureCarousel.heading}
|
|
</h2>
|
|
|
|
<Carousel
|
|
opts={{
|
|
align: "center",
|
|
loop: true,
|
|
duration: 20,
|
|
}}
|
|
setApi={setCarouselApi}
|
|
className="w-full"
|
|
dir="ltr"
|
|
>
|
|
<CarouselContent className="py-5">
|
|
{carouselItems.map((item, index) => (
|
|
<CarouselItem
|
|
key={index}
|
|
className="text-background select-none md:basis-1/2 lg:basis-1/3 dark:text-foreground"
|
|
>
|
|
<DottedDiv className="group h-150 w-full mx-auto p-4">
|
|
<div
|
|
className={cn(
|
|
"group/card relative h-full w-full bg-muted/50 p-4 transition-colors duration-300 ease-out group-hover:bg-muted",
|
|
isMobile && "cursor-pointer"
|
|
)}
|
|
data-open={openCardIndex === index}
|
|
onClick={() => handleCardClick(index)}
|
|
role={isMobile ? "button" : undefined}
|
|
aria-expanded={isMobile ? openCardIndex === index : undefined}
|
|
>
|
|
<div className="relative h-full w-full overflow-hidden rounded-3xl">
|
|
<img
|
|
src={item.imageSrc}
|
|
alt={item.title}
|
|
className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-105 group-active:scale-105 md:group-hover:scale-105 md:group-active:scale-105"
|
|
/>
|
|
{/* Overlay: hover on desktop, open state on mobile */}
|
|
<div
|
|
className={cn(
|
|
"absolute inset-0 bg-black/0 backdrop-blur-none transition-all duration-300 ease-out",
|
|
"md:group-hover:bg-black/50 md:group-hover:backdrop-blur-md md:group-active:bg-black/50 md:group-active:backdrop-blur-md",
|
|
"max-md:group-data-[open=true]/card:bg-black/50 max-md:group-data-[open=true]/card:backdrop-blur-md"
|
|
)}
|
|
aria-hidden
|
|
/>
|
|
|
|
{/* Text overlay */}
|
|
<div
|
|
className={cn(
|
|
"absolute inset-4 -ml-4 flex flex-col items-center justify-between p-6 sm:p-8 opacity-0 translate-y-4 transition-all duration-300 ease-out",
|
|
"md:group-hover:opacity-100 md:group-hover:translate-y-0 md:group-active:opacity-100 md:group-active:translate-y-0",
|
|
"max-md:group-data-[open=true]/card:opacity-100 max-md:group-data-[open=true]/card:translate-y-0"
|
|
)}
|
|
>
|
|
<div className="flex flex-1 flex-col items-center justify-center pointer-events-none">
|
|
<h3 className="text-heading-3 text-center font-semibold tracking-tight text-background dark:text-foreground">
|
|
{item.title}
|
|
</h3>
|
|
<div className="mx-auto mt-2 h-1 w-6 rounded-full bg-background/90 dark:bg-foreground/90" />
|
|
<p className="text-body-sm mt-6 max-w-sm px-1 text-center leading-relaxed tracking-tight text-background/90 dark:text-foreground/90">
|
|
{item.description}
|
|
</p>
|
|
</div>
|
|
<Link
|
|
href={item.link}
|
|
onClick={(e) => e.stopPropagation()}
|
|
className="pointer-events-auto group/cta mb-2 flex cursor-pointer flex-col items-center justify-center transition-transform duration-300 ease-out group-hover/cta:-translate-y-1"
|
|
>
|
|
<ChevronUp
|
|
size={28}
|
|
className="text-background dark:text-foreground"
|
|
/>
|
|
<p className="text-body mt-1 font-medium tracking-tight text-background dark:text-foreground">
|
|
{featureCarousel.viewDetails}
|
|
</p>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DottedDiv>
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
|
|
<div className="mt-5 flex justify-end gap-3">
|
|
<CarouselPrevious className="static size-12" />
|
|
<CarouselNext variant="default" className="static size-12" />
|
|
</div>
|
|
</Carousel>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { FeatureCarousel };
|
|
|
|
const DottedDiv = ({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
}) => (
|
|
<div className={cn("relative", className)}>
|
|
<div className="absolute top-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
|
|
<div className="absolute bottom-4 -left-[28px] h-[1.5px] w-[100%] bg-muted" />
|
|
<div className="absolute -top-25 left-4 h-[130%] w-[1.5px] bg-muted" />
|
|
<div className="absolute -top-25 right-4 h-[130%] w-[1.5px] bg-muted" />
|
|
<div className="absolute top-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
|
<div className="absolute top-[12.5px] right-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
|
<div className="absolute bottom-[12.5px] left-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
|
<div className="absolute right-[12.5px] bottom-[12.5px] z-10 size-2 rounded-full bg-foreground" />
|
|
{children}
|
|
</div>
|
|
);
|