File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
@ -281,13 +281,13 @@ export default function Home() {
|
|||||||
<motion.h3
|
<motion.h3
|
||||||
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#8B4513] font-samantha"
|
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#8B4513] font-samantha"
|
||||||
style={{
|
style={{
|
||||||
fontWeight: 300,
|
fontWeight: 400,
|
||||||
fontSize: "clamp(36px, 6vw, 60px)",
|
fontSize: "clamp(36px, 6vw, 60px)",
|
||||||
lineHeight: "120%",
|
lineHeight: "120%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
fontStyle: "italic",
|
fontStyle: "italic",
|
||||||
color: "#703133",
|
color: "#703133",
|
||||||
fontFamily: "Renner*",
|
fontFamily: "Samantha Signature",
|
||||||
}}
|
}}
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
|
|||||||
@ -161,7 +161,6 @@ export default function BannerSlider() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="relative z-10">Shop Now</span>
|
<span className="relative z-10">Shop Now</span>
|
||||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full hover:translate-x-full transition-transform duration-700 ease-out rounded-full" />
|
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -119,9 +119,10 @@ export default function Header() {
|
|||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.6 }}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative w-10 h-10"
|
className="relative"
|
||||||
animate={{
|
animate={{
|
||||||
scale: isScrolled ? 1.1 : 1,
|
width: isScrolled ? 40 : 60, // shrink on scroll
|
||||||
|
height: isScrolled ? 40 : 60,
|
||||||
}}
|
}}
|
||||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||||
whileHover={{
|
whileHover={{
|
||||||
@ -136,7 +137,7 @@ export default function Header() {
|
|||||||
fill
|
fill
|
||||||
className="object-contain drop-shadow-lg"
|
className="object-contain drop-shadow-lg"
|
||||||
priority
|
priority
|
||||||
sizes="60px"
|
sizes="(max-width: 768px) 40px, 96px"
|
||||||
quality={95}
|
quality={95}
|
||||||
placeholder="blur"
|
placeholder="blur"
|
||||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
||||||
@ -272,35 +273,28 @@ export default function Header() {
|
|||||||
|
|
||||||
{/* Center Logo */}
|
{/* Center Logo */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex items-center justify-center absolute left-1/2 transform -translate-x-1/2"
|
className="relative"
|
||||||
initial={{ opacity: 0, y: -20 }}
|
animate={{
|
||||||
animate={{ opacity: 1, y: 0 }}
|
width: isScrolled ? 75 : 96, // shrink logo when scrolled
|
||||||
transition={{ duration: 0.6 }}
|
height: isScrolled ? 75 : 96,
|
||||||
|
scale: isScrolled ? 1 : 1, // optional subtle scaling
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.5, ease: "easeInOut" }}
|
||||||
|
whileHover={{
|
||||||
|
scale: 1.15,
|
||||||
|
rotate: 5,
|
||||||
|
transition: { duration: 0.3 },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<motion.div
|
<Image
|
||||||
className="relative w-12 h-12"
|
src="/logo/logo.svg"
|
||||||
animate={{
|
alt="Mozimo Logo"
|
||||||
scale: isScrolled ? 1.1 : 1,
|
fill
|
||||||
}}
|
className="object-contain drop-shadow-lg"
|
||||||
transition={{ duration: 0.5, ease: "easeInOut" }}
|
priority
|
||||||
whileHover={{
|
sizes="(max-width: 768px) 40px, 96px"
|
||||||
scale: 1.15,
|
quality={95}
|
||||||
rotate: 5,
|
/>
|
||||||
transition: { duration: 0.3 },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/logo/logo.svg"
|
|
||||||
alt="Mozimo Logo"
|
|
||||||
fill
|
|
||||||
className="object-contain drop-shadow-lg"
|
|
||||||
priority
|
|
||||||
sizes="48px"
|
|
||||||
quality={95}
|
|
||||||
placeholder="blur"
|
|
||||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Right Icons */}
|
{/* Right Icons */}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useRef, useCallback, useMemo } from "react";
|
import { useState, useRef, useCallback, useMemo, useEffect } from "react";
|
||||||
import { AnimatePresence } from "framer-motion";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import Section from "@/components/Section";
|
import Section from "@/components/Section";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@ -8,17 +7,8 @@ import Image from "next/image";
|
|||||||
export default function ProductCategory() {
|
export default function ProductCategory() {
|
||||||
const [currentCategory, setCurrentCategory] = useState(0);
|
const [currentCategory, setCurrentCategory] = useState(0);
|
||||||
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
||||||
const fadeInLeft = {
|
const imageCache = useRef<{ [key: string]: HTMLImageElement }>({});
|
||||||
initial: { opacity: 0, x: -30 },
|
|
||||||
animate: { opacity: 1, x: 0 },
|
|
||||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
||||||
};
|
|
||||||
|
|
||||||
const fadeInRight = {
|
|
||||||
initial: { opacity: 0, x: 30 },
|
|
||||||
animate: { opacity: 1, x: 0 },
|
|
||||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
||||||
};
|
|
||||||
const categories = [
|
const categories = [
|
||||||
"Bars",
|
"Bars",
|
||||||
"Barks",
|
"Barks",
|
||||||
@ -36,117 +26,176 @@ export default function ProductCategory() {
|
|||||||
"/categories/c6.svg",
|
"/categories/c6.svg",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Memoize category change handler for better performance
|
// Preload all images on component mount
|
||||||
|
useEffect(() => {
|
||||||
|
const preloadImages = async () => {
|
||||||
|
const loadPromises = categoryImages.map((src) => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
if (imageCache.current[src]) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new window.Image();
|
||||||
|
img.onload = () => {
|
||||||
|
imageCache.current[src] = img;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
img.onerror = () => resolve(); // Continue even if one fails
|
||||||
|
img.src = src;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(loadPromises);
|
||||||
|
};
|
||||||
|
|
||||||
|
preloadImages();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Ultra-fast animation variants (no delays)
|
||||||
|
const fadeInLeft = {
|
||||||
|
initial: { opacity: 0, x: -20 },
|
||||||
|
animate: { opacity: 1, x: 0 },
|
||||||
|
transition: { duration: 0.3, ease: [0.23, 1, 0.32, 1] },
|
||||||
|
};
|
||||||
|
|
||||||
|
const fadeInRight = {
|
||||||
|
initial: { opacity: 0, x: 20 },
|
||||||
|
animate: { opacity: 1, x: 0 },
|
||||||
|
transition: { duration: 0.3, ease: [0.23, 1, 0.32, 1] },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instant category change with no debouncing
|
||||||
const handleCategoryChange = useCallback((idx: number) => {
|
const handleCategoryChange = useCallback((idx: number) => {
|
||||||
setCurrentCategory(idx);
|
setCurrentCategory(idx);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Memoize category buttons to prevent unnecessary re-renders
|
// Optimized category buttons
|
||||||
const categoryButtons = useMemo(() => {
|
const categoryButtons = useMemo(() => {
|
||||||
return categories.map((category, idx) => (
|
return categories.map((category, idx) => {
|
||||||
<motion.li
|
const isActive = idx === currentCategory;
|
||||||
key={category}
|
|
||||||
initial={{ opacity: 0, x: -20 }}
|
return (
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
<motion.li
|
||||||
transition={{ duration: 0.4, delay: idx * 0.05 }} // Reduced delay
|
key={category}
|
||||||
viewport={{ once: true }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
>
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
<button
|
transition={{ duration: 0.2, delay: idx * 0.02 }}
|
||||||
className={`group flex items-center gap-4 transition-all duration-200 ease-out w-full text-left font-moneta hover:scale-105
|
viewport={{ once: true }}
|
||||||
${
|
>
|
||||||
idx === currentCategory
|
<button
|
||||||
? "text-[#703133] font-normal"
|
className={`group flex items-center gap-4 w-full text-left font-moneta transition-all duration-100 ease-out will-change-transform
|
||||||
: "text-[#703133] opacity-60 font-light"
|
${
|
||||||
}
|
isActive
|
||||||
`}
|
? "text-[#703133] font-normal"
|
||||||
style={{
|
: "text-[#703133] opacity-70 font-light hover:opacity-90"
|
||||||
fontFamily: "MonetaSans-Regular",
|
}
|
||||||
fontWeight: idx === currentCategory ? 400 : 300,
|
`}
|
||||||
fontSize:
|
style={{
|
||||||
idx === currentCategory
|
fontFamily: "MonetaSans-Regular",
|
||||||
|
fontWeight: isActive ? 400 : 300,
|
||||||
|
fontSize: isActive
|
||||||
? "clamp(22px, 5vw, 64px)"
|
? "clamp(22px, 5vw, 64px)"
|
||||||
: "clamp(18px, 4vw, 56px)",
|
: "clamp(18px, 4vw, 56px)",
|
||||||
lineHeight: "120%",
|
lineHeight: "120%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
transition: "all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1)", // Even faster transition
|
transform: isActive ? "translateX(4px)" : "translateX(0)",
|
||||||
}}
|
}}
|
||||||
onClick={() => handleCategoryChange(idx)}
|
onClick={() => handleCategoryChange(idx)}
|
||||||
onMouseEnter={() => handleCategoryChange(idx)}
|
onMouseEnter={() => handleCategoryChange(idx)}
|
||||||
onTouchStart={() => handleCategoryChange(idx)} // Add this for mobile touch
|
onTouchStart={() => handleCategoryChange(idx)}
|
||||||
tabIndex={-1}
|
>
|
||||||
>
|
{/* Mobile optimized thumbnail */}
|
||||||
{/* Show only on mobile */}
|
<div className="block lg:hidden w-16 h-16 rounded-full overflow-hidden shrink-0 will-change-transform">
|
||||||
<div className="block lg:hidden w-16 h-16 rounded-full overflow-hidden shrink-0">
|
<Image
|
||||||
<Image
|
src={categoryImages[idx]}
|
||||||
src={categoryImages[idx]}
|
alt={category}
|
||||||
alt={category}
|
width={64}
|
||||||
width={56}
|
height={64}
|
||||||
height={56}
|
className={`w-full h-full object-cover transition-transform duration-100 ${
|
||||||
className="w-full h-full object-cover"
|
isActive ? "scale-110" : "scale-100"
|
||||||
draggable={false}
|
}`}
|
||||||
quality={85}
|
draggable={false}
|
||||||
sizes="(max-width: 375px) 40px, (max-width: 640px) 44px, (max-width: 768px) 48px, 56px"
|
quality={60}
|
||||||
/>
|
priority={idx < 2}
|
||||||
</div>
|
loading={idx < 2 ? "eager" : "lazy"}
|
||||||
{/* Category name */}
|
/>
|
||||||
<span className="text-left">{category}</span>
|
</div>
|
||||||
</button>
|
<span className="will-change-transform">{category}</span>
|
||||||
</motion.li>
|
</button>
|
||||||
));
|
</motion.li>
|
||||||
|
);
|
||||||
|
});
|
||||||
}, [currentCategory, handleCategoryChange]);
|
}, [currentCategory, handleCategoryChange]);
|
||||||
|
|
||||||
// Memoize the category image component
|
// Ultra-optimized image switcher with instant transitions
|
||||||
const categoryImageComponent = useMemo(
|
const categoryImageComponent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<AnimatePresence mode="wait">
|
<div className="relative w-full h-full overflow-hidden rounded-2xl">
|
||||||
<motion.img
|
{/* Render all images but show only active one */}
|
||||||
key={currentCategory}
|
{categoryImages.map((src, idx) => (
|
||||||
src={categoryImages[currentCategory]}
|
<motion.div
|
||||||
alt={categories[currentCategory]}
|
key={idx}
|
||||||
initial={{ opacity: 0, scale: 1.01 }}
|
initial={false}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{
|
||||||
exit={{ opacity: 0, scale: 0.99 }}
|
opacity: idx === currentCategory ? 1 : 0,
|
||||||
transition={{ duration: 0.25, ease: [0.4, 0.0, 0.2, 1] }} // Super fast transition
|
scale: idx === currentCategory ? 1 : 0.95,
|
||||||
className="w-full h-full object-cover rounded-2xl shadow-premium hover:shadow-premium-hover transition-all duration-200 img-premium" // Faster shadow
|
}}
|
||||||
draggable={false}
|
transition={{
|
||||||
style={{ maxHeight: "600px", maxWidth: "100%" }}
|
duration: 0.15,
|
||||||
/>
|
ease: [0.23, 1, 0.32, 1],
|
||||||
</AnimatePresence>
|
}}
|
||||||
|
className="absolute inset-0 will-change-transform"
|
||||||
|
style={{
|
||||||
|
zIndex: idx === currentCategory ? 2 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={src}
|
||||||
|
alt={categories[idx]}
|
||||||
|
fill
|
||||||
|
className="object-cover"
|
||||||
|
draggable={false}
|
||||||
|
quality={85}
|
||||||
|
priority={idx < 3}
|
||||||
|
loading={idx < 3 ? "eager" : "lazy"}
|
||||||
|
sizes="(max-width: 768px) 100vw, 50vw"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
[currentCategory]
|
[currentCategory]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white font-renner">
|
<Section background="white" id="categories">
|
||||||
{/* Product Categories Section */}
|
<div
|
||||||
<Section background="white" id="categories">
|
ref={categoriesSectionRef}
|
||||||
<div
|
className="grid grid-cols-1 lg:grid-cols-2 gap-0 items-stretch min-h-[400px] md:min-h-[500px]"
|
||||||
ref={categoriesSectionRef}
|
>
|
||||||
className="grid grid-cols-1 lg:grid-cols-2 gap-0 items-stretch min-h-[400px] md:min-h-[500px]"
|
{/* Left Column - Categories List */}
|
||||||
|
<motion.div
|
||||||
|
initial="initial"
|
||||||
|
whileInView="animate"
|
||||||
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
|
variants={fadeInLeft}
|
||||||
|
className="flex flex-col justify-center h-full bg-white px-4 md:px-8 py-6 md:py-12 order-2 lg:order-1"
|
||||||
>
|
>
|
||||||
{/* Left Column - Categories List */}
|
<ul className="space-y-4 md:space-y-6">{categoryButtons}</ul>
|
||||||
<motion.div
|
</motion.div>
|
||||||
initial="initial"
|
|
||||||
whileInView="animate"
|
|
||||||
viewport={{ once: true, margin: "-50px" }}
|
|
||||||
variants={fadeInLeft}
|
|
||||||
className="flex flex-col justify-center h-full bg-white px-4 md:px-8 py-6 md:py-12 order-2 lg:order-1"
|
|
||||||
>
|
|
||||||
<ul className="space-y-4 md:space-y-6">{categoryButtons}</ul>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Right Column - Large Category Image (desktop only) */}
|
{/* Right Column - Large Category Image */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial="initial"
|
initial="initial"
|
||||||
whileInView="animate"
|
whileInView="animate"
|
||||||
viewport={{ once: true, margin: "-50px" }}
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
variants={fadeInRight}
|
variants={fadeInRight}
|
||||||
className="relative hidden lg:flex items-center justify-center h-full min-h-[300px] md:min-h-[400px] bg-white order-1 lg:order-2"
|
className="relative hidden lg:flex items-center justify-center h-full min-h-[300px] md:min-h-[400px] bg-white order-1 lg:order-2 p-4"
|
||||||
>
|
>
|
||||||
{categoryImageComponent}
|
{categoryImageComponent}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user