canina/frontend/application/components/catalog/FlipbookCatalog.tsx
parsa aghaei 8d1b786a24
All checks were successful
Deploy Canina / deploy (push) Successful in 1m50s
chore(brand): rename all Persian occurrences of کانینا to کنینا across the project
2026-08-16 16:40:08 +03:30

773 lines
32 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}