342 lines
18 KiB
TypeScript
342 lines
18 KiB
TypeScript
/* eslint-disable @next/next/no-img-element */
|
||
"use client";
|
||
import { motion, AnimatePresence, animate } from "motion/react";
|
||
import { ChevronLeft, ChevronRight, Globe, ShieldCheck, Calendar, ArrowLeft } from "lucide-react";
|
||
import { useEffect, useState, useRef } from "react";
|
||
import { toPersian } from "../lib/utils";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import { useRouter } from 'next/navigation';
|
||
import { Banner } from "../lib/types";
|
||
|
||
function StatCounter({ target }: { target: number }) {
|
||
const [displayValue, setDisplayValue] = useState(0);
|
||
|
||
useEffect(() => {
|
||
const controls = animate(0, target, {
|
||
duration: 3,
|
||
ease: [0.16, 1, 0.3, 1],
|
||
onUpdate: (latest) => setDisplayValue(Math.round(latest))
|
||
});
|
||
return controls.stop;
|
||
}, [target]);
|
||
|
||
return (
|
||
<motion.div
|
||
initial={{ scale: 0.8, opacity: 0 }}
|
||
animate={{ scale: 1, opacity: 1 }}
|
||
transition={{ duration: 2.5, ease: "easeOut" }}
|
||
className="text-4xl lg:text-6xl font-black text-canina-blue font-vazir origin-right flex items-center"
|
||
>
|
||
<motion.span
|
||
animate={{ scale: [1, 1.1, 1] }}
|
||
transition={{ duration: 3, ease: "easeInOut" }}
|
||
>
|
||
{toPersian(displayValue)}+
|
||
</motion.span>
|
||
</motion.div>
|
||
);
|
||
}
|
||
|
||
export default function Hero({ banners = [], onShopNavigate }: { banners?: Banner[]; onShopNavigate?: () => void }) {
|
||
const router = useRouter();
|
||
const getText = useSettingsStore((state) => state?.getText || ((_k: string, fb: string) => fb));
|
||
const isInitialized = useSettingsStore((state) => state?.isInitialized ?? false);
|
||
const [mounted, setMounted] = useState(false);
|
||
const [currentSlide, setCurrentSlide] = useState(0);
|
||
const [isPaused, setIsPaused] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
}, []);
|
||
|
||
const heroBanners = banners.filter(b => b.isActive !== false && b.imageUrl);
|
||
const totalSlides = heroBanners.length;
|
||
|
||
// Auto-advance slider
|
||
useEffect(() => {
|
||
if (totalSlides <= 1 || isPaused) return;
|
||
const interval = setInterval(() => {
|
||
setCurrentSlide(prev => (prev + 1) % totalSlides);
|
||
}, 5000);
|
||
return () => clearInterval(interval);
|
||
}, [totalSlides, isPaused]);
|
||
|
||
const activeBanner = totalSlides > 0 ? heroBanners[currentSlide] : null;
|
||
|
||
const defaultTitle = activeBanner?.title || (banners.length > 0 && banners[0].title ? banners[0].title : "تخصص آلمانی در خدمت\nسلامت پتهای خانگی");
|
||
const defaultSubtitle = activeBanner?.subtitle || (banners.length > 0 && banners[0].subtitle ? banners[0].subtitle : 'از سال ۱۹۸۴، شرکت Canina pharma GmbH در برگیش گلادباخ آلمان، با بهرهگیری از مواد اولیه طبیعی و فرآیندهای پیشرفته، استاندارد طلایی مکملهای دامپزشکی را تعریف میکند. کنینا نماینده رسمی این برند در ایران است.');
|
||
|
||
const title = (mounted && isInitialized ? getText('hero_title', '') : '') || defaultTitle;
|
||
const subtitle = (mounted && isInitialized ? getText('hero_desc', '') : '') || defaultSubtitle;
|
||
const titleParts = title.split('\n');
|
||
|
||
const activeImageUrl = activeBanner?.imageUrl || (mounted && isInitialized ? getText('hero_image_url', '') : '') || (banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "/assets/images/hero-section-image.png");
|
||
|
||
const handleNextSlide = () => {
|
||
if (totalSlides > 1) {
|
||
setCurrentSlide(prev => (prev + 1) % totalSlides);
|
||
}
|
||
};
|
||
|
||
const handlePrevSlide = () => {
|
||
if (totalSlides > 1) {
|
||
setCurrentSlide(prev => (prev - 1 + totalSlides) % totalSlides);
|
||
}
|
||
};
|
||
|
||
const touchStartX = useRef<number | null>(null);
|
||
const touchEndX = useRef<number | null>(null);
|
||
const minSwipeDistance = 40;
|
||
|
||
const onTouchStart = (e: React.TouchEvent) => {
|
||
touchEndX.current = null;
|
||
touchStartX.current = e.targetTouches[0].clientX;
|
||
};
|
||
|
||
const onTouchMove = (e: React.TouchEvent) => {
|
||
touchEndX.current = e.targetTouches[0].clientX;
|
||
};
|
||
|
||
const onTouchEnd = () => {
|
||
if (!touchStartX.current || !touchEndX.current) return;
|
||
const distance = touchStartX.current - touchEndX.current;
|
||
const isLeftSwipe = distance > minSwipeDistance;
|
||
const isRightSwipe = distance < -minSwipeDistance;
|
||
|
||
if (isLeftSwipe) {
|
||
handleNextSlide();
|
||
} else if (isRightSwipe) {
|
||
handlePrevSlide();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<section className="relative overflow-hidden bg-white py-16 lg:py-28 border-b border-medical-gray-100">
|
||
{/* Background patterns */}
|
||
<div className="absolute top-0 left-0 w-full h-full opacity-[0.03] pointer-events-none select-none overflow-hidden">
|
||
<div className="absolute top-0 right-0 w-[800px] h-[800px] bg-canina-blue rounded-full blur-[120px] -mr-96 -mt-96" />
|
||
<div className="absolute bottom-0 left-0 w-[600px] h-[600px] bg-medical-gray-500 rounded-full blur-[100px] -ml-40 -mb-40" />
|
||
</div>
|
||
|
||
<div className="max-w-7xl mx-auto px-4 relative flex flex-col lg:flex-row items-center gap-12 mb-16 lg:mb-20">
|
||
{/* Content */}
|
||
<div className="lg:w-1/2 text-center lg:text-right">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ duration: 0.6 }}
|
||
>
|
||
<div className="inline-flex items-center gap-2 px-3 py-1 bg-canina-blue/5 border border-canina-blue/10 rounded-full mb-6">
|
||
<span className="w-2 h-2 rounded-full bg-canina-blue animate-pulse" />
|
||
<span className="text-canina-blue text-xs font-bold uppercase tracking-widest leading-none">
|
||
{mounted && isInitialized ? getText('hero_badge', "از ۱۹۸۴ در خدمت سلامت حیوانات") : "از ۱۹۸۴ در خدمت سلامت حیوانات"}
|
||
</span>
|
||
</div>
|
||
|
||
<h1 className="text-4xl lg:text-7xl font-black text-medical-gray-900 leading-[1.2] mb-6 font-lalezar tracking-wide">
|
||
{titleParts[0]} <br />
|
||
{titleParts[1] && <span className="text-canina-blue">{titleParts[1]}</span>}
|
||
</h1>
|
||
|
||
<p
|
||
className="text-lg lg:text-xl text-medical-gray-600 mb-10 max-w-2xl mx-auto lg:mx-0 leading-relaxed font-vazir animate-fade-in"
|
||
dangerouslySetInnerHTML={{
|
||
__html: subtitle
|
||
}}
|
||
/>
|
||
|
||
<div className="flex flex-wrap justify-center lg:justify-start gap-4 sm:gap-6">
|
||
<button
|
||
onClick={() => {
|
||
const element = document.getElementById('canino-advisor');
|
||
if (element) {
|
||
element.scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
}}
|
||
className="bg-canina-blue text-white px-10 py-5 rounded-full font-bold text-lg hover:bg-canina-blue/90 hover:shadow-2xl focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all flex items-center gap-2 group font-vazir min-h-[58px] shadow-lg shadow-canina-blue/20 cursor-pointer"
|
||
>
|
||
{mounted && isInitialized ? getText('hero_btn_advisor', "دستیار سلامت پت") : "دستیار سلامت پت"}
|
||
<ChevronLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
if (onShopNavigate) {
|
||
onShopNavigate();
|
||
} else {
|
||
router.push('/shop');
|
||
}
|
||
}}
|
||
className="bg-white border-2 border-medical-gray-900 text-medical-gray-900 px-10 py-5 rounded-full font-bold text-lg hover:bg-medical-gray-50 focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all font-vazir min-h-[58px] shadow-sm cursor-pointer"
|
||
>
|
||
{mounted && isInitialized ? getText('hero_btn_products', "مشاهده محصولات") : "مشاهده محصولات"}
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* Visual Element & Interactive Slider */}
|
||
<div
|
||
className="lg:w-1/2 relative w-full max-w-lg lg:max-w-none mx-auto"
|
||
onMouseEnter={() => setIsPaused(true)}
|
||
onMouseLeave={() => setIsPaused(false)}
|
||
>
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, rotate: -3 }}
|
||
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||
className="relative z-10"
|
||
>
|
||
<div
|
||
className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2.5rem] overflow-hidden border-4 border-white shadow-2xl relative group touch-pan-y"
|
||
onTouchStart={onTouchStart}
|
||
onTouchMove={onTouchMove}
|
||
onTouchEnd={onTouchEnd}
|
||
>
|
||
<AnimatePresence mode="wait">
|
||
<motion.img
|
||
key={activeImageUrl}
|
||
src={activeImageUrl}
|
||
alt={activeBanner?.title || "Canina Pharma Germany"}
|
||
initial={{ opacity: 0, scale: 1.05 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
exit={{ opacity: 0, scale: 0.98 }}
|
||
transition={{ duration: 0.5 }}
|
||
className="w-full h-full object-cover"
|
||
loading="eager"
|
||
referrerPolicy="no-referrer"
|
||
/>
|
||
</AnimatePresence>
|
||
|
||
{/* Gradient overlay and slide caption */}
|
||
<div className="absolute bottom-0 left-0 right-0 p-6 sm:p-8 bg-gradient-to-t from-black/85 via-black/40 to-transparent text-white text-right">
|
||
<div className="text-sm font-medium mb-1 opacity-80 uppercase tracking-widest text-[10px]">
|
||
{mounted && isInitialized ? getText('hero_image_badge', "سرآمد علمی در پزشکی پتها") : "سرآمد علمی در پزشکی پتها"}
|
||
</div>
|
||
<div className="text-lg sm:text-xl font-bold italic tracking-tighter">
|
||
{activeBanner?.title || (mounted && isInitialized ? getText('hero_image_title', "مکملهای تایید شده دامپزشکی با گواهی IFS") : "مکملهای تایید شده دامپزشکی با گواهی IFS")}
|
||
</div>
|
||
{activeBanner?.linkUrl && (
|
||
<a
|
||
href={activeBanner.linkUrl}
|
||
className="inline-flex items-center gap-1 text-xs text-canina-gold hover:underline font-bold mt-2"
|
||
>
|
||
<span>اطلاعات بیشتر و خرید</span>
|
||
<ArrowLeft className="w-3.5 h-3.5" />
|
||
</a>
|
||
)}
|
||
</div>
|
||
|
||
{/* Slider Navigation Arrows (shown if multiple slides exist) */}
|
||
{totalSlides > 1 && (
|
||
<>
|
||
<button
|
||
onClick={handlePrevSlide}
|
||
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
|
||
aria-label="اسلاید قبلی"
|
||
>
|
||
<ChevronRight className="w-5 h-5" />
|
||
</button>
|
||
<button
|
||
onClick={handleNextSlide}
|
||
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/40 hover:bg-black/70 text-white backdrop-blur-md flex items-center justify-center transition-all opacity-0 group-hover:opacity-100 z-30"
|
||
aria-label="اسلاید بعدی"
|
||
>
|
||
<ChevronLeft className="w-5 h-5" />
|
||
</button>
|
||
|
||
{/* Dot Indicators */}
|
||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex items-center gap-2 z-30 bg-black/40 backdrop-blur-md px-3 py-1.5 rounded-full">
|
||
{heroBanners.map((_, idx) => (
|
||
<button
|
||
key={idx}
|
||
onClick={() => setCurrentSlide(idx)}
|
||
className={`transition-all rounded-full ${
|
||
currentSlide === idx
|
||
? "w-6 h-2 bg-canina-gold"
|
||
: "w-2 h-2 bg-white/60 hover:bg-white"
|
||
}`}
|
||
aria-label={`اسلاید ${idx + 1}`}
|
||
/>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Quality Badge inside the image container */}
|
||
<motion.div
|
||
animate={{ rotate: 360 }}
|
||
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
|
||
className="absolute top-6 right-6 lg:top-8 lg:right-8 z-20 w-20 h-20 lg:w-24 lg:h-24 bg-white shadow-2xl rounded-full p-2 border-2 border-dashed border-canina-blue flex items-center justify-center text-center"
|
||
>
|
||
<div className="flex flex-col items-center">
|
||
<span className="text-lg lg:text-xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
|
||
<span className="text-[6px] lg:text-[7px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">
|
||
{mounted && isInitialized ? getText('hero_quality_standard', "استاندارد کیفی آلمان") : "استاندارد کیفی آلمان"}
|
||
</span>
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* 3D Action Badge */}
|
||
<div className="absolute bottom-24 right-6 bg-canina-gold text-medical-gray-900 px-3.5 py-1.5 rounded-2xl text-xs font-black shadow-2xl z-20 flex items-center gap-1.5 font-vazir border border-white/20 select-none animate-bounce">
|
||
<span className="text-sm">✨</span>
|
||
<span>فرمول ۳کاره</span>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stats */}
|
||
<div className="max-w-7xl mx-auto px-2 sm:px-4 relative">
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.8, duration: 0.6 }}
|
||
className="grid grid-cols-3 gap-2 sm:gap-6 py-6 sm:py-10 border-t border-medical-gray-150"
|
||
>
|
||
{/* Stat 1: Founded Year */}
|
||
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
|
||
<div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
|
||
<Calendar className="w-4 h-4 sm:w-7 sm:h-7" />
|
||
</div>
|
||
<div className="flex flex-col items-center sm:items-start text-center sm:text-right">
|
||
<div className="text-base sm:text-3xl font-black text-canina-blue font-vazir leading-none">۱۹۸۴</div>
|
||
<div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">
|
||
{mounted && isInitialized ? getText('hero_stat_founded', "سال تاسیس برند") : "سال تاسیس برند"}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stat 2: Active Agencies */}
|
||
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
|
||
<div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
|
||
<Globe className="w-4 h-4 sm:w-7 sm:h-7" />
|
||
</div>
|
||
<div className="flex flex-col items-center sm:items-start text-center sm:text-right">
|
||
<div className="flex items-baseline justify-center sm:justify-start">
|
||
<StatCounter target={40} />
|
||
</div>
|
||
<div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">
|
||
{mounted && isInitialized ? getText('hero_stat_agencies', "نمایندگی فعال") : "نمایندگی فعال"}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stat 3: German Formula */}
|
||
<div className="bg-medical-gray-50/50 border border-medical-gray-100 rounded-2xl sm:rounded-3xl p-2.5 sm:p-6 flex flex-col sm:flex-row items-center justify-center text-center sm:text-right gap-2 sm:gap-6 group hover:shadow-xl hover:shadow-canina-blue/5 transition-all">
|
||
<div className="w-9 h-9 sm:w-14 sm:h-14 bg-white rounded-xl sm:rounded-2xl flex items-center justify-center text-canina-blue shadow-sm group-hover:bg-canina-blue group-hover:text-white transition-all duration-300 flex-shrink-0">
|
||
<ShieldCheck className="w-4 h-4 sm:w-7 sm:h-7" />
|
||
</div>
|
||
<div className="flex flex-col items-center sm:items-start text-center sm:text-right">
|
||
<div className="text-base sm:text-3xl font-black text-canina-blue font-vazir leading-none">۱۰۰٪</div>
|
||
<div className="text-[9px] sm:text-xs text-medical-gray-500 font-extrabold uppercase tracking-tight mt-1 sm:mt-2 font-vazir">
|
||
{mounted && isInitialized ? getText('hero_stat_german_formula', "فرمول آلمانی") : "فرمول آلمانی"}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|