- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
204 lines
10 KiB
TypeScript
204 lines
10 KiB
TypeScript
"use client";
|
||
import { motion, useMotionValue, useTransform, animate } from "motion/react";
|
||
import { ChevronLeft } from "lucide-react";
|
||
import { useEffect, useState } from "react";
|
||
import { toPersian } from "../lib/utils";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
|
||
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], // Custom cubic-bezier for a more polished feel
|
||
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>
|
||
);
|
||
}
|
||
|
||
function Typewriter({ text }: { text: string }) {
|
||
const [displayText, setDisplayText] = useState("");
|
||
useEffect(() => {
|
||
let i = 0;
|
||
const interval = setInterval(() => {
|
||
setDisplayText(text.slice(0, i));
|
||
i++;
|
||
if (i > text.length) clearInterval(interval);
|
||
}, 150);
|
||
return () => clearInterval(interval);
|
||
}, [text]);
|
||
|
||
return (
|
||
<div className="text-4xl lg:text-6xl font-black text-canina-blue font-vazir flex flex-row-reverse items-center">
|
||
<span className="relative">
|
||
{toPersian(displayText)}
|
||
<motion.span
|
||
animate={{ opacity: [0, 1, 0] }}
|
||
transition={{ duration: 0.8, repeat: Infinity }}
|
||
className="absolute -left-1 top-0 h-full w-[3px] bg-canina-blue"
|
||
/>
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
import { useRouter } from 'next/navigation';
|
||
|
||
export default function Hero({ banners = [] }: { banners?: any[] }) {
|
||
const router = useRouter();
|
||
const getText = useSettingsStore(state => state.getText);
|
||
const title = banners.length > 0 ? banners[0].title : getText('hero_title', "تخصص آلمانی در خدمت\nسلامت پتهای خانگی");
|
||
const subtitle = banners.length > 0 ? banners[0].subtitle : getText(
|
||
'hero_desc',
|
||
'بیش از <span className="font-bold text-canina-blue">۴۰ سال</span> تجربه نوآورانه در تولید مکملهای درمانی با بالاترین استاندارد کیفی «گرید دارویی اختصاصی». راهکار هوشمند برای هر نیاز بالینی.'
|
||
);
|
||
const titleParts = title.split('\n');
|
||
|
||
return (
|
||
<section className="relative overflow-hidden bg-white py-20 lg:py-32 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">{getText('hero_badge', "تخصص دارویی از آلمان")}</span>
|
||
</div>
|
||
|
||
<h1 className="text-4xl lg:text-7xl font-black text-medical-gray-900 leading-[1.1] mb-6">
|
||
{titleParts[0]} <br />
|
||
{titleParts[1] && <span className="text-canina-blue font-vazir">{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-medical-gray-900 text-white px-10 py-5 rounded-full font-bold text-lg hover:bg-canina-blue 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-[48px]"
|
||
>
|
||
{getText('hero_btn_advisor', "دستیار سلامت پت")}
|
||
<ChevronLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
||
</button>
|
||
<button
|
||
onClick={() => router.push('/shop')}
|
||
className="bg-white border-2 border-medical-gray-200 text-medical-gray-700 px-10 py-5 rounded-full font-bold text-lg hover:border-canina-blue hover:text-canina-blue focus-visible:ring-4 focus-visible:ring-canina-blue/30 focus-visible:outline-none transition-all font-vazir min-h-[48px]"
|
||
>
|
||
{getText('hero_btn_products', "مشاهده محصولات")}
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* Visual Element */}
|
||
<div className="lg:w-1/2 relative">
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9, rotate: -5 }}
|
||
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
||
transition={{ duration: 1, ease: "easeOut" }}
|
||
className="relative z-10"
|
||
>
|
||
<div className="aspect-square bg-gradient-to-br from-slate-200 to-slate-300 rounded-[2rem] overflow-hidden border-4 border-white shadow-2xl relative">
|
||
<img
|
||
src={banners.length > 0 && banners[0].imageUrl ? banners[0].imageUrl : "https://images.unsplash.com/photo-1583337130417-3346a1be7dee?w=800&q=80"}
|
||
alt={banners.length > 0 ? banners[0].title : "German Veterinary Expert"}
|
||
className="w-full h-full object-cover grayscale-[20%] hover:grayscale-0 transition-all duration-700"
|
||
loading="eager"
|
||
referrerPolicy="no-referrer"
|
||
/>
|
||
<div className="absolute bottom-0 left-0 right-0 p-8 bg-gradient-to-t from-black/80 to-transparent text-white text-right">
|
||
<div className="text-sm font-medium mb-1 opacity-80 uppercase tracking-widest text-[10px]">{getText('hero_image_badge', "سرآمد علمی در پزشکی پتها")}</div>
|
||
<div className="text-xl font-bold italic tracking-tighter">{getText('hero_image_title', "مکملهای تایید شده دامپزشکی")}</div>
|
||
</div>
|
||
|
||
{/* Quality Badge inside the image container to prevent text collision */}
|
||
<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-24 h-24 lg:w-28 lg:h-28 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-xl lg:text-2xl font-black text-canina-blue leading-none tracking-tighter">DE</span>
|
||
<span className="text-[7px] lg:text-[8px] font-bold text-medical-gray-500 uppercase tracking-widest mt-0.5 leading-tight">{getText('hero_quality_standard', "استاندارد کیفی آلمان")}</span>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stats - Centered, full-width row under Hero content to decrease cognitive load */}
|
||
<div className="max-w-7xl mx-auto 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-1 md:grid-cols-3 gap-8 py-12 border-t border-medical-gray-150"
|
||
>
|
||
<div className="flex flex-col items-center justify-center text-center">
|
||
<Typewriter text={getText('hero_stat_founded', "سال تأسیس ۱۹۸۴")} />
|
||
</div>
|
||
<div className="flex flex-col items-center justify-center text-center">
|
||
<StatCounter target={40} />
|
||
<div className="text-sm text-medical-gray-600 font-extrabold uppercase tracking-tight mt-3 font-vazir">{getText('hero_stat_agencies', "نمایندگی فعال")}</div>
|
||
</div>
|
||
<div className="flex flex-col items-center justify-center text-center">
|
||
<div className="relative overflow-hidden group">
|
||
<motion.div
|
||
className="text-4xl lg:text-6xl font-black text-canina-blue font-vazir relative z-10"
|
||
>
|
||
۱۰۰٪
|
||
</motion.div>
|
||
<motion.div
|
||
initial={{ x: "-100%" }}
|
||
animate={{ x: "200%" }}
|
||
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
|
||
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent skew-x-12 z-20 pointer-events-none"
|
||
/>
|
||
</div>
|
||
<div className="text-sm text-medical-gray-600 font-extrabold uppercase tracking-tight mt-3 font-vazir">{getText('hero_stat_german_formula', "فرمول آلمانی")}</div>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|