773 lines
32 KiB
TypeScript
773 lines
32 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState, useEffect, useRef, useMemo } from "react";
|
||
import CatalogPageSpread, { CategoryMeta } from "./CatalogPageSpread";
|
||
import { Product } from "../../lib/data/products";
|
||
import {
|
||
ChevronRight,
|
||
ChevronLeft,
|
||
Printer,
|
||
Maximize2,
|
||
Minimize2,
|
||
BookOpen,
|
||
Search,
|
||
Sparkles,
|
||
Activity,
|
||
ShieldCheck,
|
||
Zap,
|
||
Baby,
|
||
X
|
||
} from "lucide-react";
|
||
|
||
interface FlipbookCatalogProps {
|
||
products: Product[];
|
||
}
|
||
|
||
// Normalize Persian / Arabic strings for accurate search
|
||
const normalizeSearchText = (str: string) => {
|
||
return (str || "")
|
||
.toLowerCase()
|
||
.replace(/ي/g, "ی")
|
||
.replace(/ك/g, "ک")
|
||
.replace(/[\u200B-\u200D\uFEFF]/g, "") // zero-width spaces
|
||
.trim();
|
||
};
|
||
|
||
export default function FlipbookCatalog({ products }: FlipbookCatalogProps) {
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||
|
||
// 3D Flipping animation state
|
||
const [isFlipping, setIsFlipping] = useState(false);
|
||
const [flipDirection, setFlipDirection] = useState<"next" | "prev" | null>(null);
|
||
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
|
||
// 1. Build Category Metadata with dynamic starting pages
|
||
const categoryMetaList: CategoryMeta[] = useMemo(() => {
|
||
let currentStartPage = 3; // Page 1 = Cover, Page 2 = TOC
|
||
|
||
const cats: { id: string; name: string; color: string; lightBg: string; borderBg: string; icon: React.ElementType; tagline: string; filterKey: string }[] = [
|
||
{
|
||
id: "joints",
|
||
name: "مفاصل، استخوان و اسکلت",
|
||
color: "#0284C7",
|
||
lightBg: "bg-sky-50",
|
||
borderBg: "border-sky-200",
|
||
icon: Activity,
|
||
tagline: "بازسازی غضروف، هیدروکسی آپاتیت طبیعی و تراکم استخوان",
|
||
filterKey: "مفاصل و استخوان"
|
||
},
|
||
{
|
||
id: "special-care",
|
||
name: "پوست، مو و بهداشت تخصصی",
|
||
color: "#0D9488",
|
||
lightBg: "bg-teal-50",
|
||
borderBg: "border-teal-200",
|
||
icon: Sparkles,
|
||
tagline: "تکنولوژی نقره میکروسیلور، اسیدهای چرب امگا و ترمیم بیولوژیک",
|
||
filterKey: "مراقبتهای ویژه (پوست، دندان و چشم)"
|
||
},
|
||
{
|
||
id: "immune",
|
||
name: "سیستم ایمنی و گوارش",
|
||
color: "#16A34A",
|
||
lightBg: "bg-emerald-50",
|
||
borderBg: "border-emerald-200",
|
||
icon: ShieldCheck,
|
||
tagline: "خمیر آغوز متمرکز (Colostrum) و تقویت فوری ایمونوگلوبولینها",
|
||
filterKey: "تقویت سیستم ایمنی و گوارش"
|
||
},
|
||
{
|
||
id: "vitamins",
|
||
name: "ویتامینها و انرژیبخشها",
|
||
color: "#D97706",
|
||
lightBg: "bg-amber-50",
|
||
borderBg: "border-amber-200",
|
||
icon: Zap,
|
||
tagline: "تامین عناصر کمیاب، هموگلوبین و توان فیزیکی پتها",
|
||
filterKey: "ویتامینها و انرژیبخشها"
|
||
},
|
||
{
|
||
id: "nutrition",
|
||
name: "تغذیه تخصصی و رشد تولهها",
|
||
color: "#8B5CF6",
|
||
lightBg: "bg-purple-50",
|
||
borderBg: "border-purple-200",
|
||
icon: Baby,
|
||
tagline: "شیرهای جایگزین مادر با پروتئین بالا و فرنیهای تغذیه تکمیلی",
|
||
filterKey: "تغذیه تخصصی"
|
||
}
|
||
];
|
||
|
||
return cats.map(cat => {
|
||
const count = products.filter(p => p.category === cat.filterKey || (cat.id === "vitamins" && p.category === "تقویت عمومی")).length;
|
||
const meta: CategoryMeta = {
|
||
...cat,
|
||
startPage: currentStartPage
|
||
};
|
||
currentStartPage += Math.max(count, 1);
|
||
return meta;
|
||
});
|
||
}, [products]);
|
||
|
||
// 2. Build Full Ordered List of Product Pages
|
||
const orderedProducts = useMemo(() => {
|
||
const list: { product: Product; categoryMeta: CategoryMeta; pageNum: number }[] = [];
|
||
let pageCounter = 3;
|
||
|
||
categoryMetaList.forEach(cat => {
|
||
const catProds = products.filter(p =>
|
||
p.category === (cat.id === "joints" ? "مفاصل و استخوان" :
|
||
cat.id === "special-care" ? "مراقبتهای ویژه (پوست، دندان و چشم)" :
|
||
cat.id === "immune" ? "تقویت سیستم ایمنی و گوارش" :
|
||
cat.id === "vitamins" ? (p.category === "ویتامینها و انرژیبخشها" || p.category === "تقویت عمومی") :
|
||
p.category === "تغذیه تخصصی")
|
||
);
|
||
|
||
catProds.forEach(prod => {
|
||
list.push({
|
||
product: prod,
|
||
categoryMeta: cat,
|
||
pageNum: pageCounter++
|
||
});
|
||
});
|
||
});
|
||
|
||
return list;
|
||
}, [products, categoryMetaList]);
|
||
|
||
// Total pages: Cover (1) + TOC (2) + All Products (N) + Back Cover
|
||
const TOTAL_PAGES = useMemo(() => {
|
||
const rawTotal = 2 + orderedProducts.length + 1;
|
||
return rawTotal % 2 === 0 ? rawTotal : rawTotal + 1;
|
||
}, [orderedProducts.length]);
|
||
|
||
// 3D Page Flip Forward in Persian (Left page turns over to the right!)
|
||
const handleNext = () => {
|
||
if (currentPage < TOTAL_PAGES && !isFlipping) {
|
||
setIsFlipping(true);
|
||
setFlipDirection("next");
|
||
|
||
setTimeout(() => {
|
||
setCurrentPage(prev => {
|
||
if (prev === 1) return 2; // From cover (1) to spread (2-3)
|
||
return Math.min(prev + 2, TOTAL_PAGES);
|
||
});
|
||
setIsFlipping(false);
|
||
setFlipDirection(null);
|
||
}, 650);
|
||
}
|
||
};
|
||
|
||
// 3D Page Flip Backward in Persian (Right page turns back over to the left!)
|
||
const handlePrev = () => {
|
||
if (currentPage > 1 && !isFlipping) {
|
||
setIsFlipping(true);
|
||
setFlipDirection("prev");
|
||
|
||
setTimeout(() => {
|
||
setCurrentPage(prev => {
|
||
if (prev <= 3) return 1; // Back to cover
|
||
return Math.max(prev - 2, 1);
|
||
});
|
||
setIsFlipping(false);
|
||
setFlipDirection(null);
|
||
}, 650);
|
||
}
|
||
};
|
||
|
||
// Direct Jump
|
||
const handleJumpToPage = (pageNum: number) => {
|
||
if (pageNum >= 1 && pageNum <= TOTAL_PAGES) {
|
||
if (pageNum === 1) {
|
||
setCurrentPage(1);
|
||
} else {
|
||
setCurrentPage(pageNum % 2 === 0 ? pageNum : pageNum - 1);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Keyboard navigation (RTL: Left Arrow advances forward, Right Arrow goes back)
|
||
useEffect(() => {
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
// Don't trigger if focus is on search input
|
||
if (document.activeElement?.tagName === "INPUT") return;
|
||
if (e.key === "ArrowLeft") handleNext();
|
||
if (e.key === "ArrowRight") handlePrev();
|
||
};
|
||
window.addEventListener("keydown", handleKeyDown);
|
||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||
}, [currentPage, isFlipping, TOTAL_PAGES]);
|
||
|
||
// Enhanced Search with multi-field matching & Persian normalization
|
||
const searchResults = useMemo(() => {
|
||
const query = normalizeSearchText(searchQuery);
|
||
if (!query) return [];
|
||
|
||
return orderedProducts.filter(item => {
|
||
const name = normalizeSearchText(item.product.name);
|
||
const nameEn = normalizeSearchText(item.product.nameEn || "");
|
||
const artNo = normalizeSearchText(item.product.artNo);
|
||
const tagline = normalizeSearchText(item.product.scientificTagline || "");
|
||
const desc = normalizeSearchText(item.product.description || "");
|
||
const category = normalizeSearchText(item.product.category || "");
|
||
const ingredients = item.product.main_ingredients?.map(normalizeSearchText).join(" ") || "";
|
||
const symptoms = item.product.symptoms?.map(normalizeSearchText).join(" ") || "";
|
||
|
||
return name.includes(query) ||
|
||
nameEn.includes(query) ||
|
||
artNo.includes(query) ||
|
||
tagline.includes(query) ||
|
||
desc.includes(query) ||
|
||
category.includes(query) ||
|
||
ingredients.includes(query) ||
|
||
symptoms.includes(query);
|
||
});
|
||
}, [searchQuery, orderedProducts]);
|
||
|
||
const toggleFullscreen = () => {
|
||
if (!containerRef.current) return;
|
||
if (!document.fullscreenElement) {
|
||
containerRef.current.requestFullscreen().catch(err => console.error(err));
|
||
setIsFullscreen(true);
|
||
} else {
|
||
document.exitFullscreen().catch(err => console.error(err));
|
||
setIsFullscreen(false);
|
||
}
|
||
};
|
||
|
||
const handlePrint = () => {
|
||
window.print();
|
||
};
|
||
|
||
// Helper to render a specific page number content for on-screen viewing
|
||
const renderSinglePage = (pageNum: number) => {
|
||
if (pageNum < 1 || pageNum > TOTAL_PAGES) {
|
||
return (
|
||
<div className="w-full h-full bg-slate-900 flex items-center justify-center text-slate-700 select-none">
|
||
<Sparkles className="w-8 h-8 opacity-20" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (pageNum === 1) {
|
||
return <CatalogPageSpread pageNumber={1} totalPages={TOTAL_PAGES} pageType="cover" />;
|
||
}
|
||
|
||
if (pageNum === 2) {
|
||
return (
|
||
<CatalogPageSpread
|
||
pageNumber={2}
|
||
totalPages={TOTAL_PAGES}
|
||
pageType="toc"
|
||
categoriesList={categoryMetaList}
|
||
onJumpToPage={handleJumpToPage}
|
||
/>
|
||
);
|
||
}
|
||
|
||
if (pageNum === TOTAL_PAGES) {
|
||
return <CatalogPageSpread pageNumber={TOTAL_PAGES} totalPages={TOTAL_PAGES} pageType="backcover" />;
|
||
}
|
||
|
||
// Product pages: pageNum 3 corresponds to index 0 of orderedProducts
|
||
const productItem = orderedProducts[pageNum - 3];
|
||
if (productItem) {
|
||
return (
|
||
<CatalogPageSpread
|
||
pageNumber={pageNum}
|
||
totalPages={TOTAL_PAGES}
|
||
pageType="product"
|
||
categoryMeta={productItem.categoryMeta}
|
||
product={productItem.product}
|
||
/>
|
||
);
|
||
}
|
||
|
||
return <CatalogPageSpread pageNumber={pageNum} totalPages={TOTAL_PAGES} pageType="backcover" />;
|
||
};
|
||
|
||
// Current page numbers on spread
|
||
const rightPageNum = currentPage === 1 ? 1 : currentPage;
|
||
const leftPageNum = currentPage === 1 ? 0 : currentPage + 1;
|
||
|
||
// Next spread page numbers (for upcoming pages during forward flip)
|
||
const nextRightPageNum = currentPage === 1 ? 2 : currentPage + 2;
|
||
const nextLeftPageNum = currentPage === 1 ? 3 : currentPage + 3;
|
||
|
||
// Prev spread page numbers (for upcoming pages during backward flip)
|
||
const prevRightPageNum = currentPage <= 3 ? 1 : currentPage - 2;
|
||
const prevLeftPageNum = currentPage <= 3 ? 0 : currentPage - 1;
|
||
|
||
return (
|
||
<div
|
||
ref={containerRef}
|
||
className={`w-full bg-slate-950 font-vazir text-slate-100 flex flex-col justify-between select-none print:bg-white print:p-0 print:m-0 print:h-auto ${
|
||
isFullscreen
|
||
? "h-screen p-3"
|
||
: "h-[calc(100vh-140px)] min-h-[580px] max-h-[880px] py-2 px-3 sm:px-6"
|
||
}`}
|
||
dir="rtl"
|
||
>
|
||
{/* ========================================================================= */}
|
||
{/* 1. TOP TOOLBAR (Controls, Search, Print, Fullscreen) - Fixed Height */}
|
||
{/* ========================================================================= */}
|
||
<div className="max-w-6xl w-full mx-auto h-14 shrink-0 bg-slate-900/95 border border-slate-800 backdrop-blur-xl rounded-2xl px-3 sm:px-4 flex items-center justify-between gap-3 shadow-xl z-30 print:hidden">
|
||
|
||
{/* Brand Title Badge */}
|
||
<div className="flex items-center gap-2.5 shrink-0">
|
||
<div className="w-8 h-8 rounded-xl bg-gradient-to-br from-amber-400 to-amber-500 text-slate-950 flex items-center justify-center font-black shadow-md font-mono text-xs">
|
||
CP
|
||
</div>
|
||
<div className="hidden sm:block">
|
||
<h1 className="text-xs sm:text-sm font-black text-white font-lalezar leading-tight">
|
||
کاتالوگ دیجیتال Canina Pharma
|
||
</h1>
|
||
<span className="text-[9px] text-amber-300 font-bold block">
|
||
شبیهساز ۳D ورقزن کتاب دارویی • ۳۰ صفحه اختصاصی A4
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Quick Search with Instant Jump & Dropdown */}
|
||
<div className="relative flex-1 max-w-xs md:max-w-sm mx-2">
|
||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" />
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی نام مکمل، کد کالا، ترکیبات..."
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === "Enter" && searchResults.length > 0) {
|
||
handleJumpToPage(searchResults[0].pageNum);
|
||
setSearchQuery("");
|
||
}
|
||
}}
|
||
className="w-full bg-slate-950 border border-slate-800 rounded-xl py-1.5 pr-8 pl-7 text-[11px] font-bold text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-amber-400/40"
|
||
/>
|
||
{searchQuery && (
|
||
<button
|
||
onClick={() => setSearchQuery("")}
|
||
className="absolute left-2 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white"
|
||
>
|
||
<X className="w-3.5 h-3.5" />
|
||
</button>
|
||
)}
|
||
|
||
{/* Search Dropdown Results */}
|
||
{searchQuery && (
|
||
<div className="absolute top-full mt-1.5 right-0 w-full bg-slate-900 border border-slate-700 rounded-xl shadow-2xl p-2 z-50 max-h-60 overflow-y-auto space-y-1">
|
||
{searchResults.length === 0 ? (
|
||
<div className="text-center py-3 text-xs text-slate-400 font-bold">
|
||
محصولی با عبارت «{searchQuery}» یافت نشد.
|
||
</div>
|
||
) : (
|
||
searchResults.map(item => (
|
||
<div
|
||
key={item.product.id}
|
||
onClick={() => {
|
||
handleJumpToPage(item.pageNum);
|
||
setSearchQuery("");
|
||
}}
|
||
className="p-1.5 hover:bg-slate-800 rounded-lg flex items-center justify-between cursor-pointer group transition-colors"
|
||
>
|
||
<div className="min-w-0 pr-1">
|
||
<span className="text-xs font-black text-white group-hover:text-amber-300 block truncate font-vazir">
|
||
{item.product.name}
|
||
</span>
|
||
<div className="flex items-center gap-2 text-[10px] text-slate-400 font-mono">
|
||
<span>کد: {item.product.artNo}</span>
|
||
<span>•</span>
|
||
<span className="text-slate-300">{item.product.category}</span>
|
||
</div>
|
||
</div>
|
||
<span className="text-[10px] font-black bg-amber-400 text-slate-950 px-2 py-0.5 rounded-md shrink-0 font-mono shadow-xs">
|
||
صفحه {item.pageNum}
|
||
</span>
|
||
</div>
|
||
))
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className="flex items-center gap-2 shrink-0">
|
||
<button
|
||
onClick={handlePrint}
|
||
className="bg-amber-400 hover:bg-amber-500 text-slate-950 font-black px-3 py-1.5 rounded-xl shadow-md transition-all flex items-center gap-1.5 text-xs cursor-pointer hover:scale-105 active:scale-95"
|
||
title="دانلود یا چاپ نسخه کامل PDF کاتالوگ (تمامی محصولات)"
|
||
>
|
||
<Printer className="w-3.5 h-3.5" />
|
||
<span className="hidden sm:inline">چاپ / دانلود کامل PDF</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={toggleFullscreen}
|
||
className="bg-slate-800 hover:bg-slate-700 text-white font-bold p-1.5 rounded-xl transition-all cursor-pointer border border-slate-700 hover:scale-105"
|
||
title="تمام صفحه"
|
||
>
|
||
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ========================================================================= */}
|
||
{/* 2. MAIN 3D FLIPBOOK STAGE (Auto-scaled within available height) */}
|
||
{/* ========================================================================= */}
|
||
<div className="max-w-6xl w-full mx-auto flex-1 min-h-0 flex items-center justify-center relative my-auto py-2 print:hidden overflow-hidden">
|
||
|
||
{/* Navigation Arrow - Previous (Right side in RTL: flips back to earlier pages) */}
|
||
<button
|
||
onClick={handlePrev}
|
||
disabled={currentPage <= 1 || isFlipping}
|
||
className={`absolute right-1 md:right-2 z-40 w-10 h-10 md:w-12 md:h-12 rounded-full bg-slate-900/90 hover:bg-amber-400 hover:text-slate-950 border border-slate-700 text-white flex items-center justify-center shadow-2xl transition-all cursor-pointer disabled:opacity-20 disabled:pointer-events-none hover:scale-110 active:scale-95 ${
|
||
currentPage <= 1 ? "hidden" : ""
|
||
}`}
|
||
aria-label="صفحه قبلی"
|
||
>
|
||
<ChevronRight className="w-5 h-5 md:w-6 md:h-6" />
|
||
</button>
|
||
|
||
{/* 3D Book Stage Container */}
|
||
<div className="w-full h-full flex items-center justify-center perspective-[2500px]">
|
||
|
||
{/* Desktop 3D Double Spread Book (Strict A4 Aspect Ratio & Height Bound) */}
|
||
<div
|
||
style={{
|
||
direction: "ltr",
|
||
aspectRatio: "1.414 / 1"
|
||
}}
|
||
className="hidden md:flex h-full max-h-full max-w-full bg-[#0a121e] rounded-3xl shadow-[0_25px_60px_-15px_rgba(0,0,0,0.9)] border border-slate-800/80 relative select-none"
|
||
>
|
||
{/* Center Spine Depth & Crease */}
|
||
<div className="absolute left-1/2 top-0 bottom-0 w-8 -translate-x-1/2 bg-gradient-to-r from-black/40 via-black/80 to-black/40 z-30 pointer-events-none shadow-inner" />
|
||
<div className="absolute left-1/2 top-0 bottom-0 w-[1px] -translate-x-1/2 bg-amber-400/20 z-30 pointer-events-none" />
|
||
|
||
{/* LEFT HALF OF SCREEN (Physical Left Page) */}
|
||
<div
|
||
onClick={() => handleNext()}
|
||
className="w-1/2 h-full bg-white rounded-l-3xl overflow-hidden relative cursor-pointer group shadow-2xl border-l border-slate-800"
|
||
style={{ direction: "rtl" }}
|
||
>
|
||
{currentPage === 1 ? (
|
||
// Inside Front Flyleaf when on Cover
|
||
<div className="w-full h-full bg-gradient-to-br from-[#071324] to-[#040d1a] p-6 text-white flex flex-col justify-between select-none" dir="rtl">
|
||
<div className="border-b border-white/10 pb-3 flex justify-between items-center text-[10px] text-slate-400 font-mono">
|
||
<span>CANINA PHARMA GERMANY</span>
|
||
<span>EST. 1984</span>
|
||
</div>
|
||
<div className="text-center space-y-3 max-w-xs mx-auto">
|
||
<div className="w-12 h-12 mx-auto rounded-2xl bg-amber-400/10 border border-amber-400/30 text-amber-400 flex items-center justify-center font-black text-lg font-mono shadow-inner">
|
||
CP
|
||
</div>
|
||
<h3 className="text-base font-black font-lalezar text-white">
|
||
کاتالوگ تخصصی کنینا آلمان
|
||
</h3>
|
||
<p className="text-[11px] text-slate-400 leading-relaxed font-medium">
|
||
جهت باز کردن کاتالوگ و شروع مطالعه، روی صفحه چپ یا کلید ← کیبورد را بزنید.
|
||
</p>
|
||
</div>
|
||
<div className="border-t border-white/10 pt-3 text-center text-[9px] text-slate-500 font-mono">
|
||
Official Veterinary Guide 2026
|
||
</div>
|
||
</div>
|
||
) : isFlipping && flipDirection === "next" ? (
|
||
// Underneath left page during next flip (shows upcoming left page)
|
||
renderSinglePage(nextLeftPageNum)
|
||
) : (
|
||
renderSinglePage(leftPageNum)
|
||
)}
|
||
|
||
{/* Dynamic Spine Curvature Shadow */}
|
||
<div className="absolute right-0 top-0 bottom-0 w-10 bg-gradient-to-l from-black/25 via-black/10 to-transparent pointer-events-none z-20" />
|
||
|
||
{/* Corner Curl Hover Hint */}
|
||
<div className="absolute bottom-0 left-0 w-12 h-12 bg-gradient-to-tr from-slate-400/40 via-slate-300/20 to-transparent opacity-0 group-hover:opacity-100 transition-all pointer-events-none rounded-tr-3xl" />
|
||
</div>
|
||
|
||
{/* RIGHT HALF OF SCREEN (Physical Right Page) */}
|
||
<div
|
||
onClick={() => handlePrev()}
|
||
className="w-1/2 h-full bg-white rounded-r-3xl overflow-hidden relative cursor-pointer group shadow-2xl border-r border-slate-800"
|
||
style={{ direction: "rtl" }}
|
||
>
|
||
{isFlipping && flipDirection === "prev" ? (
|
||
// Underneath right page during prev flip
|
||
renderSinglePage(prevRightPageNum)
|
||
) : (
|
||
renderSinglePage(rightPageNum)
|
||
)}
|
||
|
||
{/* Dynamic Spine Curvature Shadow */}
|
||
<div className="absolute left-0 top-0 bottom-0 w-10 bg-gradient-to-r from-black/25 via-black/10 to-transparent pointer-events-none z-20" />
|
||
|
||
{/* Corner Curl Hover Hint */}
|
||
<div className="absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl from-slate-400/40 via-slate-300/20 to-transparent opacity-0 group-hover:opacity-100 transition-all pointer-events-none rounded-tl-3xl" />
|
||
</div>
|
||
|
||
{/* 3D FLIPPING LEAF (NEXT / FORWARD: LEFT PAGE FLIPS OVER THE SPINE TO THE RIGHT!) */}
|
||
{isFlipping && flipDirection === "next" && (
|
||
<div
|
||
className="absolute left-0 top-0 w-1/2 h-full z-40 origin-right"
|
||
style={{
|
||
transformStyle: "preserve-3d",
|
||
animation: "flipLeftToRight 650ms cubic-bezier(0.645, 0.045, 0.355, 1.000) forwards"
|
||
}}
|
||
>
|
||
{/* Front Face (Current Left Page turning to the right) */}
|
||
<div
|
||
className="absolute inset-0 bg-white rounded-l-3xl overflow-hidden shadow-2xl"
|
||
style={{ direction: "rtl", backfaceVisibility: "hidden" }}
|
||
>
|
||
{currentPage === 1 ? (
|
||
<div className="w-full h-full bg-gradient-to-br from-[#071324] to-[#040d1a] p-6 text-white flex flex-col justify-between select-none">
|
||
<div className="border-b border-white/10 pb-3 text-xs font-mono">CANINA PHARMA</div>
|
||
<div className="text-center font-black font-lalezar text-base text-white">Canina Germany</div>
|
||
<div className="text-[10px] text-slate-500 font-mono">2026</div>
|
||
</div>
|
||
) : (
|
||
renderSinglePage(leftPageNum)
|
||
)}
|
||
{/* Dynamic Light Shadow during rotation */}
|
||
<div className="absolute inset-0 bg-gradient-to-l from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||
</div>
|
||
|
||
{/* Back Face (Upcoming Right Page landing on the right) */}
|
||
<div
|
||
className="absolute inset-0 bg-white rounded-r-3xl overflow-hidden shadow-2xl"
|
||
style={{
|
||
direction: "rtl",
|
||
transform: "rotateY(180deg)",
|
||
backfaceVisibility: "hidden"
|
||
}}
|
||
>
|
||
{renderSinglePage(nextRightPageNum)}
|
||
{/* Dynamic Light Shadow during landing */}
|
||
<div className="absolute inset-0 bg-gradient-to-r from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 3D FLIPPING LEAF (PREV / BACKWARD: RIGHT PAGE FLIPS OVER THE SPINE TO THE LEFT!) */}
|
||
{isFlipping && flipDirection === "prev" && (
|
||
<div
|
||
className="absolute right-0 top-0 w-1/2 h-full z-40 origin-left"
|
||
style={{
|
||
transformStyle: "preserve-3d",
|
||
animation: "flipRightToLeft 650ms cubic-bezier(0.645, 0.045, 0.355, 1.000) forwards"
|
||
}}
|
||
>
|
||
{/* Front Face (Current Right Page turning back to the left) */}
|
||
<div
|
||
className="absolute inset-0 bg-white rounded-r-3xl overflow-hidden shadow-2xl"
|
||
style={{ direction: "rtl", backfaceVisibility: "hidden" }}
|
||
>
|
||
{renderSinglePage(rightPageNum)}
|
||
<div className="absolute inset-0 bg-gradient-to-r from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||
</div>
|
||
|
||
{/* Back Face (Previous Left Page landing on the left) */}
|
||
<div
|
||
className="absolute inset-0 bg-white rounded-l-3xl overflow-hidden shadow-2xl"
|
||
style={{
|
||
direction: "rtl",
|
||
transform: "rotateY(180deg)",
|
||
backfaceVisibility: "hidden"
|
||
}}
|
||
>
|
||
{renderSinglePage(prevLeftPageNum === 0 ? 1 : prevLeftPageNum)}
|
||
<div className="absolute inset-0 bg-gradient-to-l from-black/50 via-black/20 to-transparent pointer-events-none" />
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
</div>
|
||
|
||
{/* Mobile Single Page View with 3D Flip */}
|
||
<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"
|
||
>
|
||
{renderSinglePage(currentPage)}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{/* Navigation Arrow - Next (Left side in RTL: flips forward) */}
|
||
<button
|
||
onClick={handleNext}
|
||
disabled={currentPage >= TOTAL_PAGES || isFlipping}
|
||
className={`absolute left-1 md:left-2 z-40 w-10 h-10 md:w-12 md:h-12 rounded-full bg-slate-900/90 hover:bg-amber-400 hover:text-slate-950 border border-slate-700 text-white flex items-center justify-center shadow-2xl transition-all cursor-pointer disabled:opacity-20 disabled:pointer-events-none hover:scale-110 active:scale-95 ${
|
||
currentPage >= TOTAL_PAGES ? "hidden" : ""
|
||
}`}
|
||
aria-label="صفحه بعدی"
|
||
>
|
||
<ChevronLeft className="w-5 h-5 md:w-6 md:h-6" />
|
||
</button>
|
||
|
||
</div>
|
||
|
||
{/* ========================================================================= */}
|
||
{/* 3. BOTTOM SLIDER & CATEGORY JUMP BAR - Fixed Height */}
|
||
{/* ========================================================================= */}
|
||
<div className="max-w-4xl w-full mx-auto h-14 shrink-0 bg-slate-900/95 border border-slate-800 backdrop-blur-xl rounded-2xl px-3 sm:px-4 flex items-center justify-between gap-3 shadow-xl z-30 print:hidden">
|
||
|
||
{/* Page Slider */}
|
||
<div className="flex items-center gap-2.5 flex-1 max-w-xs sm:max-w-sm">
|
||
<span className="text-[11px] font-black text-amber-300 shrink-0 font-mono">
|
||
{currentPage === 1 ? "جلد ۱" : `صفحه ${currentPage}-${Math.min(currentPage + 1, TOTAL_PAGES)}`}
|
||
</span>
|
||
|
||
<input
|
||
type="range"
|
||
min="1"
|
||
max={TOTAL_PAGES}
|
||
step="1"
|
||
value={currentPage}
|
||
onChange={(e) => handleJumpToPage(parseInt(e.target.value))}
|
||
className="w-full h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-amber-400"
|
||
/>
|
||
|
||
<span className="text-[11px] font-bold text-slate-400 shrink-0 font-mono">
|
||
/{TOTAL_PAGES}
|
||
</span>
|
||
</div>
|
||
|
||
{/* Category Jump Pills */}
|
||
<div className="flex items-center gap-1.5 overflow-x-auto max-w-full py-0.5">
|
||
<button
|
||
onClick={() => handleJumpToPage(1)}
|
||
className="px-2 py-1 rounded-lg text-[10px] font-black bg-slate-800 text-white hover:bg-slate-700 transition-all shrink-0 cursor-pointer"
|
||
>
|
||
جلد
|
||
</button>
|
||
<button
|
||
onClick={() => handleJumpToPage(2)}
|
||
className="px-2 py-1 rounded-lg text-[10px] font-black bg-slate-800 text-white hover:bg-slate-700 transition-all shrink-0 cursor-pointer"
|
||
>
|
||
فهرست
|
||
</button>
|
||
{categoryMetaList.map((cat) => (
|
||
<button
|
||
key={cat.id}
|
||
onClick={() => handleJumpToPage(cat.startPage)}
|
||
className="px-2 py-1 rounded-lg text-[10px] font-black text-white transition-all shrink-0 hover:scale-105 cursor-pointer shadow-xs"
|
||
style={{ backgroundColor: cat.color }}
|
||
>
|
||
{cat.name.split(" ")[0]}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
</div>
|
||
|
||
{/* ========================================================================= */}
|
||
{/* 4. COMPLETE PRINT-ONLY A4 MULTI-PAGE DOCUMENT (Export ALL products to PDF) */}
|
||
{/* ========================================================================= */}
|
||
<div className="hidden print:block w-full text-slate-900 bg-white" dir="rtl">
|
||
{/* Page 1: Cover */}
|
||
<div className="print-a4-page">
|
||
<CatalogPageSpread pageNumber={1} totalPages={TOTAL_PAGES} pageType="cover" />
|
||
</div>
|
||
|
||
{/* Page 2: Table of Contents */}
|
||
<div className="print-a4-page">
|
||
<CatalogPageSpread
|
||
pageNumber={2}
|
||
totalPages={TOTAL_PAGES}
|
||
pageType="toc"
|
||
categoriesList={categoryMetaList}
|
||
/>
|
||
</div>
|
||
|
||
{/* Pages 3 to N: All Dedicated Product Pages in Order */}
|
||
{orderedProducts.map((item) => (
|
||
<div key={item.product.id} className="print-a4-page">
|
||
<CatalogPageSpread
|
||
pageNumber={item.pageNum}
|
||
totalPages={TOTAL_PAGES}
|
||
pageType="product"
|
||
categoryMeta={item.categoryMeta}
|
||
product={item.product}
|
||
/>
|
||
</div>
|
||
))}
|
||
|
||
{/* Final Page: Back Cover */}
|
||
<div className="print-a4-page">
|
||
<CatalogPageSpread pageNumber={TOTAL_PAGES} totalPages={TOTAL_PAGES} pageType="backcover" />
|
||
</div>
|
||
</div>
|
||
|
||
{/* 3D Keyframe CSS Animations & Print Stylesheet */}
|
||
<style jsx global>{`
|
||
@keyframes flipLeftToRight {
|
||
0% {
|
||
transform: rotateY(0deg);
|
||
box-shadow: 5px 0 25px rgba(0,0,0,0.3);
|
||
}
|
||
50% {
|
||
transform: rotateY(90deg) scale(1.03);
|
||
box-shadow: 20px 0 50px rgba(0,0,0,0.6);
|
||
}
|
||
100% {
|
||
transform: rotateY(180deg) scale(1);
|
||
box-shadow: -5px 0 25px rgba(0,0,0,0.3);
|
||
}
|
||
}
|
||
|
||
@keyframes flipRightToLeft {
|
||
0% {
|
||
transform: rotateY(0deg);
|
||
box-shadow: -5px 0 25px rgba(0,0,0,0.3);
|
||
}
|
||
50% {
|
||
transform: rotateY(-90deg) scale(1.03);
|
||
box-shadow: -20px 0 50px rgba(0,0,0,0.6);
|
||
}
|
||
100% {
|
||
transform: rotateY(-180deg) scale(1);
|
||
box-shadow: 5px 0 25px rgba(0,0,0,0.3);
|
||
}
|
||
}
|
||
|
||
@media print {
|
||
@page {
|
||
size: A4 portrait;
|
||
margin: 0;
|
||
}
|
||
html, body {
|
||
background: #ffffff !important;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
color: #000000 !important;
|
||
}
|
||
.print-a4-page {
|
||
page-break-after: always !important;
|
||
break-after: page !important;
|
||
width: 210mm !important;
|
||
height: 297mm !important;
|
||
max-height: 297mm !important;
|
||
overflow: hidden !important;
|
||
box-sizing: border-box !important;
|
||
padding: 0 !important;
|
||
margin: 0 !important;
|
||
display: block !important;
|
||
}
|
||
}
|
||
`}</style>
|
||
|
||
</div>
|
||
);
|
||
}
|