feat(catalog): redesign digital catalog spread with luxury European apothecary layout and mobile gesture navigation
This commit is contained in:
parent
30fb8f3e83
commit
00cfd1a2a0
@ -26,9 +26,11 @@ import {
|
||||
export interface CategoryMeta {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
lightBg: string;
|
||||
borderBg: string;
|
||||
color: string; // پایه تیره
|
||||
lightBg: string; // پنل روشن
|
||||
borderBg: string; // اکسنت
|
||||
accentColor?: string;
|
||||
secondaryColor?: string;
|
||||
icon: React.ElementType;
|
||||
tagline: string;
|
||||
startPage: number;
|
||||
@ -201,38 +203,41 @@ export default function CatalogPageSpread({
|
||||
// ==========================================
|
||||
if (pageType === "toc") {
|
||||
return (
|
||||
<div className="w-full h-full bg-slate-50 text-slate-800 p-5 md:p-7 flex flex-col justify-between select-none box-border" dir="rtl">
|
||||
<div className="w-full h-full bg-[#FAF8F5] text-slate-900 p-5 md:p-7 flex flex-col justify-between select-none box-border" dir="rtl" style={{ fontFamily: "'Vazirmatn', sans-serif" }}>
|
||||
{/* TOC Header */}
|
||||
<div className="border-b border-slate-200 pb-3 flex items-center justify-between shrink-0">
|
||||
<div className="border-b pb-3 flex items-center justify-between shrink-0" style={{ borderColor: "rgba(18,33,31,0.1)" }}>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-black text-canina-blue uppercase tracking-widest">
|
||||
<div className="flex items-center gap-1.5 text-[9.5px] font-black uppercase tracking-widest text-[#C88A3F]">
|
||||
<BookOpen className="w-3.5 h-3.5" />
|
||||
<span>فهرست تخصصی و راهنمای بالینی</span>
|
||||
</div>
|
||||
<h2 className="text-xl md:text-2xl font-black font-lalezar text-slate-900 mt-0.5">
|
||||
<h2 className="text-xl md:text-2xl font-black text-slate-900 mt-0.5 tracking-tight font-vazir">
|
||||
دستهبندیهای کاتالوگ دارویی
|
||||
</h2>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<span className="text-[10px] font-black text-slate-400 block font-mono">CANINA PHARMA</span>
|
||||
<span className="text-[9px] font-bold text-amber-600 uppercase">Germany</span>
|
||||
<span className="text-[10px] font-medium text-slate-400 block font-mono tracking-wider">CANINA PHARMA</span>
|
||||
<span className="text-[9px] font-medium text-[#C88A3F] uppercase">Germany</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Categories List (Compact luxury layout, perfectly fitted) */}
|
||||
<div className="my-auto space-y-2.5 py-2 flex-1 flex flex-col justify-center min-h-0">
|
||||
<div className="my-auto space-y-2 py-2 flex-1 flex flex-col justify-center min-h-0">
|
||||
{categoriesList.map((cat, idx) => {
|
||||
const IconComp = cat.icon;
|
||||
const accent = cat.accentColor || cat.borderBg || "#C88A3F";
|
||||
const sec = cat.secondaryColor || "#6B8F71";
|
||||
return (
|
||||
<div
|
||||
key={cat.id}
|
||||
onClick={() => onJumpToPage && onJumpToPage(cat.startPage)}
|
||||
className="bg-white border border-slate-200/90 hover:border-slate-300 rounded-xl p-2.5 sm:p-3 flex items-center justify-between gap-3 shadow-2xs hover:shadow-md transition-all cursor-pointer group"
|
||||
className="bg-white border rounded-xl p-2.5 sm:p-3 flex items-center justify-between gap-3 shadow-2xs hover:shadow-md transition-all cursor-pointer group"
|
||||
style={{ borderColor: "rgba(18,33,31,0.08)" }}
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
{/* Category Color Icon */}
|
||||
<div
|
||||
className="w-9 h-9 rounded-lg flex items-center justify-center text-white font-black shadow-xs shrink-0 group-hover:scale-105 transition-transform"
|
||||
className="w-9 h-9 rounded-lg flex items-center justify-center text-white font-medium shadow-xs shrink-0 group-hover:scale-105 transition-transform"
|
||||
style={{ backgroundColor: cat.color }}
|
||||
>
|
||||
<IconComp className="w-4 h-4" />
|
||||
@ -240,15 +245,15 @@ export default function CatalogPageSpread({
|
||||
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[9px] font-black text-slate-400 font-mono">
|
||||
<span className="text-[8.5px] font-medium font-mono" style={{ color: sec }}>
|
||||
SECTION 0{idx + 1}
|
||||
</span>
|
||||
<span className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: cat.color }} />
|
||||
<span className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: accent }} />
|
||||
</div>
|
||||
<h3 className="text-xs sm:text-sm font-black text-slate-900 group-hover:text-canina-blue transition-colors font-vazir truncate">
|
||||
<h3 className="text-xs sm:text-sm font-black text-slate-900 group-hover:text-[#C88A3F] transition-colors font-vazir truncate">
|
||||
{cat.name}
|
||||
</h3>
|
||||
<p className="text-[10px] text-slate-500 font-medium truncate mt-0.5">
|
||||
<p className="text-[9.5px] text-slate-500 font-normal truncate mt-0.5">
|
||||
{cat.tagline}
|
||||
</p>
|
||||
</div>
|
||||
@ -256,12 +261,12 @@ export default function CatalogPageSpread({
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<div className="text-left">
|
||||
<span className="text-[8px] text-slate-400 font-bold block">صفحه</span>
|
||||
<span className="text-[8px] text-slate-400 font-normal block">صفحه</span>
|
||||
<span className="text-xs font-black text-slate-900 font-mono">
|
||||
{cat.startPage < 10 ? `۰${cat.startPage}` : cat.startPage}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-6 h-6 rounded-full bg-slate-100 group-hover:bg-canina-blue group-hover:text-white text-slate-400 flex items-center justify-center transition-colors">
|
||||
<div className="w-6 h-6 rounded-full bg-slate-100 group-hover:bg-slate-900 group-hover:text-white text-slate-400 flex items-center justify-center transition-colors">
|
||||
<ChevronLeft className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
</div>
|
||||
@ -271,17 +276,17 @@ export default function CatalogPageSpread({
|
||||
</div>
|
||||
|
||||
{/* Quality Seal Note */}
|
||||
<div className="bg-amber-50/70 border border-amber-200/60 rounded-xl p-2.5 flex items-center gap-2.5 shrink-0">
|
||||
<Award className="w-4 h-4 text-amber-600 shrink-0" />
|
||||
<p className="text-[10px] text-amber-900 font-bold leading-relaxed">
|
||||
<div className="bg-[#FAF0DE]/60 border border-[#D97D3D]/20 rounded-xl p-2.5 flex items-center gap-2.5 shrink-0">
|
||||
<Award className="w-4 h-4 text-[#D97D3D] shrink-0" />
|
||||
<p className="text-[9.5px] text-[#2B1810] font-medium leading-relaxed">
|
||||
تمامی فرآوردههای کنینا آلمان مطابق با مونوگرافهای دارویی فارماکوپه اروپا (Ph. Eur.) و تحت استاندارد GMP تولید میشوند.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* TOC Footer */}
|
||||
<div className="border-t border-slate-200 pt-2 flex items-center justify-between text-[10px] text-slate-400 font-bold shrink-0">
|
||||
<div className="border-t pt-2 flex items-center justify-between text-[9.5px] text-slate-400 font-normal shrink-0" style={{ borderColor: "rgba(18,33,31,0.08)" }}>
|
||||
<span>راهنمای جامع کاتالوگ دارویی Canina Pharma</span>
|
||||
<span className="font-mono">صفحه ۰۲</span>
|
||||
<span className="font-mono text-[#C88A3F]">صفحه ۰۲</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -349,205 +354,258 @@ export default function CatalogPageSpread({
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. DEDICATED 1-PAGE LUXURY PRODUCT PRESENTATION
|
||||
// (صفحه اختصاصی و لوکس هر محصول)
|
||||
// 4. DEDICATED LUXURY PHARMACEUTICAL MONOGRAPH PRODUCT PRESENTATION
|
||||
// (صفحه اختصاصی، لوکس و مونوگراف کاتالوگ دارویی اروپایی)
|
||||
// ==========================================
|
||||
if (!product) return null;
|
||||
|
||||
const formBadge = getFormBadge(product.unit, product.name);
|
||||
const speciesBadge = getSpeciesBadge(product.suitableFor);
|
||||
const FormIcon = formBadge.icon;
|
||||
const SpeciesIcon = speciesBadge.icon;
|
||||
const themeColor = categoryMeta?.color || "#0284C7";
|
||||
|
||||
// Locked 5-category European luxury palette
|
||||
const baseDark = categoryMeta?.color || "#12211F";
|
||||
const panelLight = categoryMeta?.lightBg || "#F3EEE0";
|
||||
const accentColor = categoryMeta?.accentColor || categoryMeta?.borderBg || "#C88A3F";
|
||||
const secondaryColor = categoryMeta?.secondaryColor || "#6B8F71";
|
||||
const CategoryIcon = categoryMeta?.icon || Activity;
|
||||
|
||||
// Alternating layout dynamics based on page number (odd vs even pages)
|
||||
const isEvenPage = pageNumber % 2 === 0;
|
||||
|
||||
// Clean description without HTML
|
||||
const cleanDescription = (product.shortDescription || (product.description ? product.description.replace(/<[^>]*>/g, '') : '')).trim();
|
||||
|
||||
// Parse dosage into sequential steps if available
|
||||
const dosageRaw = (product.dosage_logic || product.feedingAdvice || "").trim();
|
||||
const dosageSteps = dosageRaw
|
||||
? dosageRaw
|
||||
.split(/(?:؛|\. |\n| - )/g)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 5)
|
||||
.slice(0, 3)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="w-full h-full bg-gradient-to-b from-slate-50 via-white to-slate-50 text-slate-800 p-4 sm:p-5 md:p-6 flex flex-col justify-between select-none box-border overflow-hidden relative" dir="rtl">
|
||||
{/* Decorative luxury gradient tint border on top edge */}
|
||||
<div
|
||||
className="absolute top-0 right-0 left-0 h-1.5 opacity-80"
|
||||
style={{ backgroundColor: themeColor }}
|
||||
className="w-full h-full flex flex-col justify-between select-none box-border overflow-hidden relative"
|
||||
style={{
|
||||
backgroundColor: panelLight,
|
||||
color: "#18201E",
|
||||
fontFamily: "'Vazirmatn', sans-serif",
|
||||
direction: "rtl"
|
||||
}}
|
||||
>
|
||||
{/* Category Delicate Line Watermark in Background */}
|
||||
<div
|
||||
className="absolute -bottom-14 -left-14 w-64 h-64 pointer-events-none opacity-[0.04] transition-all"
|
||||
style={{ color: baseDark }}
|
||||
>
|
||||
<CategoryIcon className="w-full h-full" strokeWidth={1} />
|
||||
</div>
|
||||
|
||||
{/* Subtle Luxury Gradient Ambient Aura */}
|
||||
<div
|
||||
className="absolute top-0 right-0 w-72 h-72 rounded-full pointer-events-none blur-3xl opacity-[0.08]"
|
||||
style={{ background: `radial-gradient(circle, ${accentColor} 0%, transparent 70%)` }}
|
||||
/>
|
||||
|
||||
{/* 1. Header Bar: Category, Species, Form, ArtNo */}
|
||||
<div className="border-b border-slate-200/80 pb-2 flex items-center justify-between gap-2 shrink-0 pt-0.5">
|
||||
|
||||
{/* Category Pill */}
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<span
|
||||
className="w-2.5 h-2.5 rounded-full shrink-0 shadow-2xs"
|
||||
style={{ backgroundColor: themeColor }}
|
||||
{/* Top Accent Rim (Ultra-thin luxury rule) */}
|
||||
<div
|
||||
className="h-1 w-full shrink-0"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
/>
|
||||
<span className="text-[10px] sm:text-[11px] font-black uppercase tracking-wider text-slate-800 font-vazir truncate whitespace-nowrap">
|
||||
|
||||
{/* 1. Header Bar: Minimal Monograph Classification */}
|
||||
<div className="px-5 pt-3 pb-2 flex items-center justify-between border-b shrink-0" style={{ borderColor: "rgba(0,0,0,0.06)" }}>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div
|
||||
className="w-6 h-6 rounded-full flex items-center justify-center shrink-0 border"
|
||||
style={{
|
||||
borderColor: `${accentColor}55`,
|
||||
backgroundColor: `${accentColor}12`,
|
||||
color: accentColor
|
||||
}}
|
||||
>
|
||||
<CategoryIcon className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[10px] font-medium block leading-none" style={{ color: secondaryColor }}>
|
||||
{product.category}
|
||||
</span>
|
||||
<span className="text-[8px] font-mono tracking-widest opacity-40 block mt-0.5" style={{ color: baseDark }}>
|
||||
CANINA VETERINARY DOSSIER
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Badges (Strictly whitespace-nowrap) */}
|
||||
<div className="flex items-center gap-1 sm:gap-1.5 text-[9px] font-bold shrink-0">
|
||||
<span className="bg-white text-slate-700 px-2 py-0.5 rounded-md flex items-center gap-1 border border-slate-200 shadow-3xs whitespace-nowrap">
|
||||
{speciesBadge.isBoth ? (
|
||||
<span className="flex items-center gap-0.5">
|
||||
<Dog className="w-3 h-3 text-slate-500 shrink-0" />
|
||||
<Cat className="w-3 h-3 text-slate-500 shrink-0" />
|
||||
</span>
|
||||
) : (
|
||||
<SpeciesIcon className="w-3 h-3 text-slate-500 shrink-0" />
|
||||
)}
|
||||
<span className="whitespace-nowrap">{speciesBadge.label}</span>
|
||||
</span>
|
||||
<span className="bg-white text-slate-700 px-2 py-0.5 rounded-md flex items-center gap-1 border border-slate-200 shadow-3xs whitespace-nowrap">
|
||||
<FormIcon className="w-3 h-3 text-slate-500 shrink-0" />
|
||||
<span className="whitespace-nowrap">{formBadge.label}</span>
|
||||
</span>
|
||||
<span className="bg-blue-50/90 text-canina-blue font-mono px-2 py-0.5 rounded-md border border-blue-100/90 whitespace-nowrap font-bold">
|
||||
<div className="flex items-center gap-2 text-left">
|
||||
<span
|
||||
className="text-[9px] font-mono font-medium px-2 py-0.5 rounded-full border"
|
||||
style={{
|
||||
borderColor: `${accentColor}40`,
|
||||
color: accentColor,
|
||||
backgroundColor: `${accentColor}08`
|
||||
}}
|
||||
>
|
||||
Art-Nr: {product.artNo}
|
||||
</span>
|
||||
<span className="text-[9px] font-medium opacity-60 hidden sm:inline" style={{ color: baseDark }}>
|
||||
{speciesBadge.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 2. Main Body Content (Luxury grid fitted strictly without overflow) */}
|
||||
<div className="flex-1 min-h-0 flex flex-col justify-between py-1.5 space-y-1.5">
|
||||
{/* 2. Main Body Content: Asymmetric editorial composition */}
|
||||
<div className="flex-1 min-h-0 px-5 py-2 flex flex-col justify-between">
|
||||
|
||||
{/* Top Product Hero Showcase (Image + Titles + Special Badge) */}
|
||||
<div className="flex items-center gap-3 bg-white border border-slate-200/90 rounded-2xl p-2.5 shrink-0 shadow-2xs relative overflow-hidden">
|
||||
{/* Hero Product Stage: Studio Lighting Mockup + Three-Tier Typography */}
|
||||
<div className={`flex items-center gap-4 py-1 shrink-0 ${isEvenPage ? 'flex-row' : 'flex-row-reverse'}`}>
|
||||
|
||||
{/* Studio Packshot with Realistic Lighting & Depth */}
|
||||
<div className="relative w-28 h-28 sm:w-32 sm:h-32 shrink-0 flex items-center justify-center">
|
||||
{/* Ambient Studio Spotlight Aura */}
|
||||
<div
|
||||
className="absolute -top-8 -right-8 w-24 h-24 rounded-full opacity-10 blur-xl pointer-events-none"
|
||||
style={{ backgroundColor: themeColor }}
|
||||
className="absolute inset-0 rounded-full blur-xl pointer-events-none opacity-20"
|
||||
style={{ background: `radial-gradient(circle, ${accentColor} 0%, transparent 70%)` }}
|
||||
/>
|
||||
{/* Thin Aesthetic Ring behind product */}
|
||||
<div
|
||||
className="absolute w-24 h-24 sm:w-28 sm:h-28 rounded-full border border-dashed pointer-events-none opacity-30"
|
||||
style={{ borderColor: accentColor }}
|
||||
/>
|
||||
|
||||
{/* Studio Image Box */}
|
||||
<div className="w-20 h-20 sm:w-24 sm:h-24 bg-slate-50/80 rounded-xl border border-slate-200/70 p-1.5 flex items-center justify-center shrink-0 relative shadow-inner">
|
||||
<div className="relative z-10 w-full h-full flex items-center justify-center">
|
||||
<SafeImage
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="w-full h-full"
|
||||
imgClassName="object-contain max-h-16 sm:max-h-20 filter drop-shadow-md"
|
||||
imgClassName="object-contain max-h-24 sm:max-h-28 filter drop-shadow-[0_12px_18px_rgba(0,0,0,0.22)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{product.unit && (
|
||||
<span className="absolute bottom-1 right-1 bg-slate-900/90 text-white font-mono text-[7px] sm:text-[8px] font-bold px-1.5 py-0.2 rounded shadow-2xs whitespace-nowrap">
|
||||
<span
|
||||
className="absolute bottom-0 right-1 text-[8px] font-mono font-medium px-1.5 py-0.5 rounded shadow-xs"
|
||||
style={{ backgroundColor: baseDark, color: panelLight }}
|
||||
>
|
||||
{product.unit}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title & Scientific Tagline */}
|
||||
<div className="min-w-0 flex-1 space-y-0.5">
|
||||
{/* Three-Tier Typography: Black Name, Advertising Subtitle, Editorial Body */}
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
{product.specialBadge && (
|
||||
<span
|
||||
className="text-[8px] font-black px-2 py-0.5 rounded-full text-white inline-block shadow-2xs whitespace-nowrap mb-0.5"
|
||||
style={{ backgroundColor: themeColor }}
|
||||
className="text-[8px] font-medium tracking-wide inline-block px-2 py-0.5 rounded-full mb-0.5 border"
|
||||
style={{
|
||||
color: baseDark,
|
||||
borderColor: `${accentColor}60`,
|
||||
backgroundColor: `${accentColor}18`
|
||||
}}
|
||||
>
|
||||
★ {product.specialBadge}
|
||||
{product.specialBadge}
|
||||
</span>
|
||||
)}
|
||||
<h2 className="text-sm sm:text-base font-black text-slate-900 font-vazir leading-tight truncate">
|
||||
|
||||
<h2
|
||||
className="text-base sm:text-lg font-black leading-tight truncate font-vazir tracking-tight"
|
||||
style={{ color: baseDark }}
|
||||
>
|
||||
{product.name}
|
||||
</h2>
|
||||
{product.scientificTagline && (
|
||||
<p className="text-[9px] sm:text-[10px] text-slate-600 font-bold italic truncate">
|
||||
{product.scientificTagline}
|
||||
|
||||
<p
|
||||
className="text-[10px] sm:text-[11px] font-medium leading-snug line-clamp-2"
|
||||
style={{ color: accentColor }}
|
||||
>
|
||||
{product.scientificTagline || "فرمولاسیون دارویی استاندارد جهت ارتقای سلامت زیستی و بالینی پت"}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-[9px] text-slate-500 font-medium line-clamp-2 leading-relaxed text-justify">
|
||||
{product.shortDescription || (product.description ? product.description.replace(/<[^>]*>/g, '') : '')}
|
||||
|
||||
<p
|
||||
className="text-[9px] font-normal leading-relaxed opacity-75 line-clamp-2 text-justify"
|
||||
style={{ color: baseDark }}
|
||||
>
|
||||
{cleanDescription}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Clinical Indications & Symptoms */}
|
||||
{/* Section 1: Clinical Indications (Flowing text with refined dot separators, no colored badges) */}
|
||||
{product.symptoms && product.symptoms.length > 0 && (
|
||||
<div className="space-y-1 shrink-0">
|
||||
<span className="text-[8px] font-black text-slate-400 uppercase tracking-widest block whitespace-nowrap">
|
||||
موارد مصرف و نشانههای بالینی:
|
||||
<div className="pt-1 pb-0.5 shrink-0 border-t" style={{ borderColor: "rgba(0,0,0,0.06)" }}>
|
||||
<span className="text-[9px] font-black block mb-0.5" style={{ color: accentColor }}>
|
||||
موارد مصرف بالینی
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{product.symptoms.slice(0, 4).map((symptom, i) => (
|
||||
<p className="text-[9px] font-normal leading-relaxed text-justify m-0" style={{ color: "#2B3633" }}>
|
||||
{product.symptoms.map((symptom, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{i > 0 && (
|
||||
<span
|
||||
key={i}
|
||||
className="bg-red-50 text-red-900 border border-red-200/80 rounded-md px-1.5 py-0.5 text-[8.5px] font-bold whitespace-nowrap shadow-3xs"
|
||||
>
|
||||
• {symptom}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Active Ingredients & Composition Table */}
|
||||
{product.main_ingredients && product.main_ingredients.length > 0 && (
|
||||
<div className="bg-slate-50/90 border border-slate-200/80 rounded-xl p-2 space-y-1 shrink-0">
|
||||
<div className="flex items-center gap-1 text-[8.5px] font-black text-canina-blue uppercase whitespace-nowrap">
|
||||
<Layers className="w-3 h-3 shrink-0" />
|
||||
<span>ترکیبات موثره و فرمولاسیون دارویی:</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{product.main_ingredients.slice(0, 4).map((ing, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="bg-white text-slate-800 border border-slate-200 rounded px-1.5 py-0.5 text-[8.5px] font-bold shadow-3xs whitespace-nowrap"
|
||||
>
|
||||
{ing}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Clinical Dosage Protocol */}
|
||||
{product.dosage_logic && (
|
||||
<div className="bg-amber-50/80 border border-amber-200/80 rounded-xl p-2 space-y-0.5 shrink-0">
|
||||
<div className="flex items-center gap-1 text-amber-900 font-black text-[8.5px] whitespace-nowrap">
|
||||
<ShieldCheck className="w-3 h-3 text-amber-600 shrink-0" />
|
||||
<span>پروتکل بالینی تجویز و دوز مصرف:</span>
|
||||
</div>
|
||||
<p className="text-[8.5px] text-amber-950 font-bold leading-relaxed line-clamp-2">
|
||||
{product.dosage_logic}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Specialist Endorsement */}
|
||||
{product.specialist && product.specialist.name && (
|
||||
<div className="bg-white border border-slate-200/80 rounded-xl p-2 flex items-center gap-2 shrink-0 shadow-3xs">
|
||||
<div className="w-7 h-7 rounded-full bg-canina-blue/10 border border-canina-blue/20 text-canina-blue flex items-center justify-center font-black text-xs font-mono shadow-3xs shrink-0 overflow-hidden">
|
||||
{product.specialist.image ? (
|
||||
<img
|
||||
src={product.specialist.image}
|
||||
alt={product.specialist.name}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLElement).style.display = 'none';
|
||||
}}
|
||||
className="inline-block w-1.5 h-1.5 rounded-full mx-1.5 align-middle"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
/>
|
||||
)}
|
||||
<span>{symptom}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section 2: Active Formulation (Short elegant paragraph, no tables) */}
|
||||
{product.main_ingredients && product.main_ingredients.length > 0 && (
|
||||
<div className="pt-1 pb-0.5 shrink-0 border-t" style={{ borderColor: "rgba(0,0,0,0.06)" }}>
|
||||
<span className="text-[9px] font-black block mb-0.5" style={{ color: accentColor }}>
|
||||
ترکیبات و فرمولاسیون
|
||||
</span>
|
||||
<p className="text-[9px] font-normal leading-relaxed text-justify m-0" style={{ color: "#2B3633" }}>
|
||||
{product.main_ingredients.join("، ")}.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Section 3: Clinical Dosage Protocol (Refined sequential numbering ۰۱, ۰۲ or single note) */}
|
||||
{dosageRaw && (
|
||||
<div className="pt-1 pb-1 shrink-0 border-t" style={{ borderColor: "rgba(0,0,0,0.06)" }}>
|
||||
<span className="text-[9px] font-black block mb-1" style={{ color: accentColor }}>
|
||||
پروتکل دوز و نحوه مصرف
|
||||
</span>
|
||||
{dosageSteps.length > 1 ? (
|
||||
<div className="space-y-0.5">
|
||||
{dosageSteps.map((step, idx) => {
|
||||
const numStr = idx === 0 ? "۰۱" : idx === 1 ? "۰۲" : "۰۳";
|
||||
return (
|
||||
<div key={idx} className="flex items-baseline gap-1.5 text-[8.5px] leading-relaxed">
|
||||
<span className="font-mono font-bold shrink-0 text-[9px]" style={{ color: secondaryColor }}>
|
||||
{numStr}
|
||||
</span>
|
||||
<span className="font-normal opacity-90" style={{ color: "#2B3633" }}>
|
||||
{step}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<Stethoscope className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1 truncate">
|
||||
<span className="text-[8.5px] font-black text-slate-900 truncate">{product.specialist.name}</span>
|
||||
{product.specialist.title && (
|
||||
<span className="text-[7.5px] text-slate-500 truncate font-medium">({product.specialist.title})</span>
|
||||
)}
|
||||
</div>
|
||||
{product.specialist.message && (
|
||||
<p className="text-[8px] text-slate-600 italic truncate mt-0.2">
|
||||
«{product.specialist.message}»
|
||||
<p className="text-[8.5px] font-normal leading-relaxed text-justify m-0" style={{ color: "#2B3633" }}>
|
||||
{dosageRaw}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{/* 3. Page Footer */}
|
||||
<div className="border-t border-slate-200/80 pt-1.5 flex items-center justify-between text-[8.5px] text-slate-400 font-bold shrink-0">
|
||||
<div className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
<Award className="w-3 h-3 text-amber-500 shrink-0" />
|
||||
<span>Canina Pharma Germany • استانداردهای بالینی GMP</span>
|
||||
</div>
|
||||
<span className="font-mono text-slate-600 bg-slate-100 px-2 py-0.2 rounded-md whitespace-nowrap font-bold">
|
||||
{/* 3. Page Footer: Minimal luxury accreditation & page number */}
|
||||
<div
|
||||
className="px-5 py-2 border-t flex items-center justify-between text-[8.5px] font-normal shrink-0"
|
||||
style={{ borderColor: "rgba(0,0,0,0.06)", color: "rgba(24, 32, 30, 0.6)" }}
|
||||
>
|
||||
<span>
|
||||
Canina Pharma Germany • Ph. Eur. & GMP Certified
|
||||
</span>
|
||||
<span className="font-mono font-medium" style={{ color: accentColor }}>
|
||||
صفحه {pageNumber < 10 ? `۰${pageNumber}` : pageNumber} از {totalPages}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -46,35 +46,52 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 1. Build Category Metadata with dynamic starting pages
|
||||
// 1. Build Category Metadata with dynamic starting pages and luxury European apothecary palette
|
||||
const categoryMetaList: CategoryMeta[] = useMemo(() => {
|
||||
const cats: { id: string; name: string; color: string; lightBg: string; borderBg: string; icon: React.ElementType; tagline: string; matcher: (p: Product) => boolean }[] = [
|
||||
const cats: {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
lightBg: string;
|
||||
borderBg: string;
|
||||
accentColor?: string;
|
||||
secondaryColor?: string;
|
||||
icon: React.ElementType;
|
||||
tagline: string;
|
||||
matcher: (p: Product) => boolean
|
||||
}[] = [
|
||||
{
|
||||
id: "joints",
|
||||
name: "مفاصل، استخوان و اسکلت",
|
||||
color: "#0284C7",
|
||||
lightBg: "bg-sky-50",
|
||||
borderBg: "border-sky-200",
|
||||
name: "مفاصل و استخوان",
|
||||
color: "#12211F", // پایه پاین تیره
|
||||
lightBg: "#F3EEE0", // پنل روشن استخوانی
|
||||
borderBg: "#C88A3F", // اکسنت کهربا / برنز
|
||||
accentColor: "#C88A3F",
|
||||
secondaryColor: "#6B8F71", // خزهای کمرنگ
|
||||
icon: Activity,
|
||||
tagline: "بازسازی غضروف، هیدروکسی آپاتیت طبیعی و تراکم استخوان",
|
||||
matcher: (p: Product) => (p.category || '').includes("مفاصل") || p.categorySlug === 'joints'
|
||||
matcher: (p: Product) => (p.category || '').includes("مفاصل") || (p.category || '').includes("استخوان") || p.categorySlug === 'joints'
|
||||
},
|
||||
{
|
||||
id: "special-care",
|
||||
name: "پوست، مو و بهداشت تخصصی",
|
||||
color: "#0D9488",
|
||||
lightBg: "bg-teal-50",
|
||||
borderBg: "border-teal-200",
|
||||
color: "#1A1F2E", // نیلی تیره
|
||||
lightBg: "#EFEDF5", // خاکستری مرواریدی
|
||||
borderBg: "#8C7BC9", // بنفش دوداری
|
||||
accentColor: "#8C7BC9",
|
||||
secondaryColor: "#B79FE0", // یاسی روشن
|
||||
icon: Sparkles,
|
||||
tagline: "تکنولوژی نقره میکروسیلور، اسیدهای چرب امگا و ترمیم بیولوژیک",
|
||||
matcher: (p: Product) => (p.category || '').includes("پوست") || (p.category || '').includes("مراقبت") || p.categorySlug === 'special-care'
|
||||
matcher: (p: Product) => (p.category || '').includes("پوست") || (p.category || '').includes("مراقبت") || (p.category || '').includes("بهداشت") || p.categorySlug === 'special-care'
|
||||
},
|
||||
{
|
||||
id: "immune",
|
||||
name: "سیستم ایمنی و گوارش",
|
||||
color: "#16A34A",
|
||||
lightBg: "bg-emerald-50",
|
||||
borderBg: "border-emerald-200",
|
||||
color: "#14241F", // سبز جنگلی تیره
|
||||
lightBg: "#EEF2E8", // سبز شیری
|
||||
borderBg: "#7FA05C", // سبز زیتونی
|
||||
accentColor: "#7FA05C",
|
||||
secondaryColor: "#C9A15A", // طلایی خاکرس
|
||||
icon: ShieldCheck,
|
||||
tagline: "خمیر آغوز متمرکز (Colostrum) و تقویت فوری ایمونوگلوبولینها",
|
||||
matcher: (p: Product) => (p.category || '').includes("ایمنی") || (p.category || '').includes("گوارش") || p.categorySlug === 'immune'
|
||||
@ -82,9 +99,11 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
{
|
||||
id: "vitamins",
|
||||
name: "ویتامینها و انرژیبخشها",
|
||||
color: "#D97706",
|
||||
lightBg: "bg-amber-50",
|
||||
borderBg: "border-amber-200",
|
||||
color: "#2B1810", // قهوهای سوخته تیره
|
||||
lightBg: "#FBF0DE", // کرم گرم
|
||||
borderBg: "#D97D3D", // نارنجی زنگاری
|
||||
accentColor: "#D97D3D",
|
||||
secondaryColor: "#E8A94B", // کهربای روشن
|
||||
icon: Zap,
|
||||
tagline: "تامین عناصر کمیاب، هموگلوبین و توان فیزیکی پتها",
|
||||
matcher: (p: Product) => (p.category || '').includes("ویتامین") || (p.category || '').includes("تقویت عمومی") || (p.category || '').includes("انرژی") || p.categorySlug === 'energy' || p.categorySlug === 'general'
|
||||
@ -92,9 +111,11 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
{
|
||||
id: "nutrition",
|
||||
name: "تغذیه تخصصی و رشد تولهها",
|
||||
color: "#8B5CF6",
|
||||
lightBg: "bg-purple-50",
|
||||
borderBg: "border-purple-200",
|
||||
color: "#1E1A2B", // بنفش شبانه
|
||||
lightBg: "#F2EEF8", // لاواندر شیری
|
||||
borderBg: "#9B6FB0", // بنفش عمیق
|
||||
accentColor: "#9B6FB0",
|
||||
secondaryColor: "#C79ED9", // ارکیده روشن
|
||||
icon: Baby,
|
||||
tagline: "شیرهای جایگزین مادر با پروتئین بالا و فرنیهای تغذیه تکمیلی",
|
||||
matcher: (p: Product) => (p.category || '').includes("تغذیه") || (p.category || '').includes("مکمل") || p.categorySlug === 'nutrition' || p.categorySlug === 'supplements' || (!p.category && !p.categorySlug)
|
||||
@ -111,6 +132,8 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
color: cat.color,
|
||||
lightBg: cat.lightBg,
|
||||
borderBg: cat.borderBg,
|
||||
accentColor: cat.accentColor || cat.borderBg,
|
||||
secondaryColor: cat.secondaryColor || cat.color,
|
||||
icon: cat.icon,
|
||||
tagline: cat.tagline,
|
||||
startPage: currentStartPage
|
||||
@ -170,20 +193,33 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
return rawTotal % 2 === 0 ? rawTotal : rawTotal + 1;
|
||||
}, [orderedProducts.length]);
|
||||
|
||||
// Determine if device is currently in single-page mode (mobile viewport < 768px)
|
||||
const isSinglePageMode = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
return window.innerWidth < 768;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 3D Page Flip Forward in Persian (Left page turns over to the right!)
|
||||
const handleNext = () => {
|
||||
if (currentPage < TOTAL_PAGES && !isFlipping) {
|
||||
setIsFlipping(true);
|
||||
setFlipDirection("next");
|
||||
|
||||
const single = isSinglePageMode();
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentPage(prev => {
|
||||
if (single) {
|
||||
return Math.min(prev + 1, TOTAL_PAGES);
|
||||
}
|
||||
if (prev === 1) return 2; // From cover (1) to spread (2-3)
|
||||
return Math.min(prev + 2, TOTAL_PAGES);
|
||||
});
|
||||
setIsFlipping(false);
|
||||
setFlipDirection(null);
|
||||
}, 650);
|
||||
}, single ? 350 : 650);
|
||||
}
|
||||
};
|
||||
|
||||
@ -193,14 +229,19 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
setIsFlipping(true);
|
||||
setFlipDirection("prev");
|
||||
|
||||
const single = isSinglePageMode();
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentPage(prev => {
|
||||
if (single) {
|
||||
return Math.max(prev - 1, 1);
|
||||
}
|
||||
if (prev <= 3) return 1; // Back to cover
|
||||
return Math.max(prev - 2, 1);
|
||||
});
|
||||
setIsFlipping(false);
|
||||
setFlipDirection(null);
|
||||
}, 650);
|
||||
}, single ? 350 : 650);
|
||||
}
|
||||
};
|
||||
|
||||
@ -657,18 +698,32 @@ export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||||
|
||||
</div>
|
||||
|
||||
{/* Mobile Single Page View with 3D Flip */}
|
||||
{/* Mobile Single Page View with Touch Gesture Support */}
|
||||
<div
|
||||
style={{
|
||||
aspectRatio: "1 / 1.414"
|
||||
}}
|
||||
className="flex md:hidden h-full max-h-full max-w-full bg-white rounded-2xl shadow-2xl overflow-hidden border border-slate-800 relative"
|
||||
>
|
||||
<div
|
||||
onClick={() => handleNext()}
|
||||
className="w-full h-full"
|
||||
className="flex md:hidden h-full max-h-full max-w-full bg-white rounded-2xl shadow-2xl overflow-hidden border border-slate-800 relative select-none"
|
||||
>
|
||||
<div className="w-full h-full relative">
|
||||
{renderSinglePage(currentPage)}
|
||||
|
||||
{/* Invisible Left & Right Tap Zones for Mobile Navigation */}
|
||||
<div
|
||||
onClick={(e) => { e.stopPropagation(); handleNext(); }}
|
||||
className="absolute left-0 top-0 bottom-0 w-1/4 z-30 cursor-pointer active:bg-black/5 transition-colors"
|
||||
title="صفحه بعدی"
|
||||
/>
|
||||
<div
|
||||
onClick={(e) => { e.stopPropagation(); handlePrev(); }}
|
||||
className="absolute right-0 top-0 bottom-0 w-1/4 z-30 cursor-pointer active:bg-black/5 transition-colors"
|
||||
title="صفحه قبلی"
|
||||
/>
|
||||
|
||||
{/* Subtle Swipe Guidance Hint on Bottom Center */}
|
||||
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 z-20 pointer-events-none opacity-40 bg-black/40 text-white text-[8px] px-2 py-0.5 rounded-full backdrop-blur-xs font-mono">
|
||||
← کشیدن برای ورق زدن →
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user