398
src/app/page.tsx
398
src/app/page.tsx
@ -7,9 +7,9 @@ import Section from "@/components/Section";
|
||||
import Button from "@/components/Button";
|
||||
import BannerSlider from "@/components/BannerSlider";
|
||||
import Image from "next/image";
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { useInstagram } from "@/hooks/useInstagram";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import ProductCategory from "@/components/ProductCategory";
|
||||
import SocialMedia from "@/components/SocialMedia";
|
||||
|
||||
// Premium animation variants
|
||||
const fadeInUp = {
|
||||
@ -46,14 +46,6 @@ const categories = [
|
||||
"Dragees",
|
||||
"Gelatos",
|
||||
];
|
||||
const categoryImages = [
|
||||
"/categories/c1.svg",
|
||||
"/categories/c2.svg",
|
||||
"/categories/c3.svg",
|
||||
"/categories/c4.svg",
|
||||
"/categories/c5.svg",
|
||||
"/categories/c6.svg",
|
||||
];
|
||||
|
||||
const partnerImages = [
|
||||
"/partners/p1.svg",
|
||||
@ -127,14 +119,18 @@ export default function Home() {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const { posts, loading, error, isTokenExpired } = useInstagram();
|
||||
|
||||
// Memoize expensive components
|
||||
const BrandStorySection = useMemo(
|
||||
() => (
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
<BannerSlider />
|
||||
|
||||
{/* Brand Story */}
|
||||
|
||||
<Section background="white" id="about">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12 lg:gap-16 items-center">
|
||||
{/* Left Column - Text */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 items-center">
|
||||
{/* Brand Story - Left Text */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
@ -143,7 +139,7 @@ export default function Home() {
|
||||
className="space-y-6 md:space-y-8 order-2 lg:order-1 px-4 md:px-0" // ← Added mobile padding
|
||||
>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-moneta text-gradient"
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-moneta text-gradient p-4 md:p-0"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 58px)",
|
||||
@ -208,21 +204,17 @@ export default function Home() {
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Column - Image */}
|
||||
{/* Brand Story - Right Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="relative order-1 lg:order-2 w-screen md:w-auto -mx-4 md:mx-0" // ← Made full-width on mobile
|
||||
className="relative order-1 lg:order-2 w-screen md:w-auto -mx-4 md:mx-0"
|
||||
>
|
||||
<motion.div
|
||||
className="relative w-full h-[300px] md:h-[400px] lg:h-[500px] rounded-none md:rounded-xl overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500" // ← Removed rounded corners on mobile
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
className="relative w-full h-[300px] md:h-[400px] lg:h-[500px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500 clip-diagonal-bottom-left"
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
|
||||
>
|
||||
<Image
|
||||
src="/bst/bs1.svg"
|
||||
@ -232,40 +224,21 @@ export default function Home() {
|
||||
draggable={false}
|
||||
quality={85}
|
||||
/>
|
||||
{/* Premium overlay for depth */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</Section>
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
<BannerSlider />
|
||||
|
||||
{BrandStorySection}
|
||||
|
||||
{/* Chocolate Tempering Section */}
|
||||
<Section background="white">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 md:gap-12 lg:gap-16 items-center">
|
||||
{/* Left Column - Image */}
|
||||
{/* Chocolate Tempering - Left Video */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInLeft}
|
||||
className="relative order-1 lg:order-1 w-screen md:w-auto -mx-4 md:mx-0" // ← Added for mobile edge-to-edge
|
||||
className="relative order-3 lg:order-3 w-screen md:w-auto -mx-4 md:mx-0 py-4 md:py-0"
|
||||
>
|
||||
<motion.div
|
||||
className="relative w-full h-[400px] md:h-[500px] lg:h-[700px] rounded-none md:rounded-xl overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500" // ← Changed to rounded-none for mobile
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
className="relative w-full h-[400px] md:h-[500px] lg:h-[700px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500 clip-diagonal-top-right"
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
|
||||
>
|
||||
<video
|
||||
src="/bst/v1.mp4"
|
||||
@ -276,20 +249,18 @@ export default function Home() {
|
||||
playsInline
|
||||
preload="auto"
|
||||
aria-label="Chocolate pouring from metallic spout - Mozimo chocolate tempering process"
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
{/* Premium overlay for depth */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Column - Text */}
|
||||
{/* Chocolate Tempering - Right Text */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="space-y-4 md:space-y-6 order-2 lg:order-2 px-4 md:px-0" // ← Added px-4 for mobile padding
|
||||
className="space-y-4 md:space-y-6 order-4 lg:order-4 px-4 md:px-0 ml-4 md:ml-8 lg:ml-12"
|
||||
>
|
||||
<div className="space-y-3 md:space-y-4">
|
||||
<motion.h2
|
||||
@ -316,6 +287,7 @@ export default function Home() {
|
||||
letterSpacing: "0%",
|
||||
fontStyle: "italic",
|
||||
color: "#703133",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
@ -324,7 +296,6 @@ export default function Home() {
|
||||
</motion.h3>
|
||||
</div>
|
||||
|
||||
{/* Premium decorative line */}
|
||||
<motion.div
|
||||
className="w-100 h-px bg-gradient-to-r from-transparent via-[#8B4513] to-transparent"
|
||||
initial={{ scaleX: 0 }}
|
||||
@ -442,318 +413,9 @@ export default function Home() {
|
||||
</motion.div>
|
||||
</Section>
|
||||
|
||||
{/* Product Categories Section */}
|
||||
<Section background="white" id="categories">
|
||||
<div
|
||||
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: "-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">
|
||||
{categories.map((category, idx) => (
|
||||
<motion.li
|
||||
key={category}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: idx * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<button
|
||||
className={`group flex items-center gap-4 transition-all duration-700 ease-out w-full text-left font-moneta hover:scale-105
|
||||
${
|
||||
idx === currentCategory
|
||||
? "text-[#703133] font-normal"
|
||||
: "text-[#703133] opacity-60 font-light"
|
||||
}
|
||||
`}
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: idx === currentCategory ? 400 : 300,
|
||||
fontSize:
|
||||
idx === currentCategory
|
||||
? "clamp(22px, 5vw, 64px)"
|
||||
: "clamp(18px, 4vw, 56px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
transition: "all 0.7s cubic-bezier(0.25,0.46,0.45,0.94)",
|
||||
}}
|
||||
onClick={() => setCurrentCategory(idx)}
|
||||
onMouseEnter={() => setCurrentCategory(idx)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{/* Show only on mobile */}
|
||||
<div className="block lg:hidden w-10 h-10 rounded-full overflow-hidden shrink-0">
|
||||
<img
|
||||
src={categoryImages[idx]}
|
||||
alt={category}
|
||||
className="w-full h-full object-cover"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category name */}
|
||||
<span className="text-left">{category}</span>
|
||||
</button>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Column - Large Category Image (desktop only) */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
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"
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.img
|
||||
key={currentCategory}
|
||||
src={categoryImages[currentCategory]}
|
||||
alt={categories[currentCategory]}
|
||||
initial={{ opacity: 0, scale: 1.05 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: 0.7, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||
className="w-full h-full object-cover rounded-2xl shadow-premium hover:shadow-premium-hover transition-all duration-500 img-premium"
|
||||
draggable={false}
|
||||
style={{ maxHeight: "600px", maxWidth: "100%" }}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<ProductCategory />
|
||||
{/* Social Media Call to Action */}
|
||||
<Section background="cream" padding="md">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center mb-8 md:mb-12"
|
||||
>
|
||||
<h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-moneta text-[#703133] mb-6 md:mb-8 text-gradient"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(28px, 5vw, 48px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
>
|
||||
Follow us on Instagram
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div className="flex overflow-x-auto no-scrollbar snap-x snap-mandatory gap-4 px-4 sm:grid sm:grid-cols-2 lg:grid-cols-3 sm:gap-6 md:gap-8 max-w-7xl mx-auto">
|
||||
{loading ? (
|
||||
<>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="bg-white rounded-lg shadow-premium h-64 md:h-96 skeleton snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: i * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : error ? (
|
||||
isTokenExpired ? (
|
||||
<div className="col-span-full text-center py-12 sm:col-span-2 lg:col-span-3">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-md mx-auto"
|
||||
>
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-amber-200 to-orange-300 rounded-full mx-auto mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-2xl">🔗</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-[#703133] mb-2">
|
||||
Instagram Connection Expired
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Our Instagram connection needs to be refreshed. We're
|
||||
working on getting it back up!
|
||||
</p>
|
||||
<div className="text-sm text-gray-500">
|
||||
In the meantime, follow us @mozimo_chocolate
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Left Block - Chocolate Spread Jar */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInLeft}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="relative h-64 md:h-96 bg-gradient-to-br from-amber-50 to-orange-50 flex items-center justify-center">
|
||||
<motion.div
|
||||
className="text-center"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="w-32 h-32 bg-gradient-to-br from-amber-200 to-orange-300 rounded-full mx-auto mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-4xl">🍫</span>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 mx-4 shadow-premium">
|
||||
<div className="font-semibold text-[#703133] text-lg">
|
||||
MOZIMO
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
SINGLE ORIGIN HAZELNUT SPREAD 45%
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Middle Block - Magazine Article */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="p-4 md:p-6">
|
||||
<div className="bg-red-600 h-32 rounded-lg mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-white text-2xl">🍫</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-gray-200 h-20 rounded-lg flex items-center justify-center shadow-premium">
|
||||
<span className="text-lg">🍰</span>
|
||||
</div>
|
||||
<div className="bg-gray-200 h-20 rounded-lg flex items-center justify-center shadow-premium">
|
||||
<span className="text-lg">👨🍳</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-700 space-y-2 leading-relaxed">
|
||||
<p>
|
||||
<strong>By RUPALI DEAN</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
MOZIMO'S CHOCOLATE PIE BY PRIYANKA GUPTA
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
isolat[ing] beans to not only single ori- but also to
|
||||
single farms so as to express unique complexity.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Block - World Chocolate Day */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="p-4 md:p-6 text-center">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-2xl font-bold text-[#8B4513] mb-2 text-gradient">
|
||||
World Chocolate Day
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
Celebrating the art of chocolate making
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-amber-100 to-orange-100 rounded-lg p-4 shadow-premium">
|
||||
<div className="text-4xl mb-2">🍫</div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Join us in celebrating the world's favorite treat
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
posts?.slice(0, 3).map((post, index) => (
|
||||
<motion.div
|
||||
key={post.id}
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={
|
||||
index === 0
|
||||
? fadeInLeft
|
||||
: index === 1
|
||||
? fadeInUp
|
||||
: fadeInRight
|
||||
}
|
||||
className="snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<a
|
||||
href={post.permalink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<div className="relative h-64 md:h-96">
|
||||
<Image
|
||||
src={post.mediaUrl}
|
||||
alt={post.caption || "Instagram post"}
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = "/bst/bs1.svg";
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent" />
|
||||
{post.mediaType === "VIDEO" && (
|
||||
<div className="absolute top-4 right-4 bg-black/50 rounded-full p-2">
|
||||
<svg
|
||||
className="w-4 h-4 text-white"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M8 5v10l8-5-8-5z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-4 left-4 right-4">
|
||||
<div className="glass rounded-lg p-3 border border-white/20">
|
||||
<p className="text-sm text-gray-800 line-clamp-2">
|
||||
{post.caption?.slice(0, 100)}...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</motion.div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
<SocialMedia />
|
||||
|
||||
{/* In the Spotlight Section */}
|
||||
<Section background="white">
|
||||
@ -785,7 +447,6 @@ export default function Home() {
|
||||
animate={{ x: `-${spotlightIndex * 152}px` }}
|
||||
transition={{ type: "spring", stiffness: 80, damping: 18 }}
|
||||
>
|
||||
{/* Create 12 tiles (3 sets of 4 logos) */}
|
||||
{Array(12)
|
||||
.fill(0)
|
||||
.map((_, i) => {
|
||||
@ -795,7 +456,10 @@ export default function Home() {
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-24 h-24 md:w-32 md:h-32 lg:w-36 lg:h-36 bg-white rounded-full flex items-center justify-center shadow-premium hover:shadow-premium-hover flex-shrink-0 transition-all duration-300"
|
||||
style={{ boxShadow: "0 2px 12px 0 rgba(60,42,33,0.10)" }}
|
||||
style={{
|
||||
boxShadow: "0 4px 20px 0 rgba(60,42,33,0.15)", // Increased blur and opacity
|
||||
margin: "8px", // Added margin to give space for shadow
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
rotate: 5,
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import Image from 'next/image';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
|
||||
const banners = [
|
||||
{ id: 1, src: '/banners/b1.svg', alt: 'Mozimo Chocolate Banner 1' },
|
||||
{ id: 2, src: '/banners/b2.svg', alt: 'Mozimo Chocolate Banner 2' },
|
||||
{ id: 3, src: '/banners/b3.svg', alt: 'Mozimo Chocolate Banner 3' },
|
||||
{ id: 4, src: '/banners/b4.svg', alt: 'Mozimo Chocolate Banner 4' },
|
||||
{ id: 1, src: "/banners/b1.svg", alt: "Mozimo Chocolate Banner 1" },
|
||||
{ id: 2, src: "/banners/b3.svg", alt: "Mozimo Chocolate Banner 2" },
|
||||
{ id: 3, src: "/banners/b2.svg", alt: "Mozimo Chocolate Banner 3" },
|
||||
{ id: 4, src: "/banners/b4.svg", alt: "Mozimo Chocolate Banner 4" },
|
||||
];
|
||||
|
||||
export default function BannerSlider() {
|
||||
@ -17,7 +17,7 @@ export default function BannerSlider() {
|
||||
const [showArrows, setShowArrows] = useState(false);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
// Auto-advance slider
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@ -37,7 +37,7 @@ export default function BannerSlider() {
|
||||
|
||||
const goToPrevious = () => {
|
||||
setDirection(-1);
|
||||
setCurrentIndex((prevIndex) =>
|
||||
setCurrentIndex((prevIndex) =>
|
||||
prevIndex === 0 ? banners.length - 1 : prevIndex - 1
|
||||
);
|
||||
};
|
||||
@ -49,7 +49,7 @@ export default function BannerSlider() {
|
||||
|
||||
const slideVariants = {
|
||||
enter: (direction: number) => ({
|
||||
x: direction > 0 ? '100%' : '-100%',
|
||||
x: direction > 0 ? "100%" : "-100%",
|
||||
opacity: 1,
|
||||
}),
|
||||
center: {
|
||||
@ -57,9 +57,9 @@ export default function BannerSlider() {
|
||||
opacity: 1,
|
||||
},
|
||||
exit: (direction: number) => ({
|
||||
x: direction < 0 ? '100%' : '-100%',
|
||||
x: direction < 0 ? "100%" : "-100%",
|
||||
opacity: 1,
|
||||
})
|
||||
}),
|
||||
};
|
||||
|
||||
const swipeConfidenceThreshold = 10000;
|
||||
@ -76,7 +76,7 @@ export default function BannerSlider() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative w-full h-[calc(100vh-80px)] md:h-screen overflow-hidden bg-gradient-to-br from-[#1a0f0a] to-[#2d1810]"
|
||||
onMouseEnter={() => {
|
||||
@ -105,7 +105,7 @@ export default function BannerSlider() {
|
||||
exit="exit"
|
||||
transition={{
|
||||
x: { type: "spring", stiffness: 300, damping: 30 },
|
||||
duration: 0.4
|
||||
duration: 0.4,
|
||||
}}
|
||||
drag="x"
|
||||
dragConstraints={{ left: 0, right: 0 }}
|
||||
@ -128,41 +128,43 @@ export default function BannerSlider() {
|
||||
className="object-cover img-premium"
|
||||
draggable={false}
|
||||
priority={currentIndex === 0}
|
||||
loading={currentIndex === 0 ? 'eager' : 'lazy'}
|
||||
loading={currentIndex === 0 ? "eager" : "lazy"}
|
||||
sizes="100vw"
|
||||
quality={85}
|
||||
/>
|
||||
|
||||
|
||||
{/* Premium overlay with gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Premium Shop Now Button */}
|
||||
<div className="absolute bottom-16 md:bottom-20 left-1/2 transform -translate-x-1/2 z-10">
|
||||
<motion.button
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
y: -2,
|
||||
transition: { duration: 0.3 }
|
||||
}}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="glass text-[#3C2A21] px-8 md:px-12 py-4 md:py-5 rounded-full font-semibold text-base md:text-lg shadow-premium hover:shadow-premium-hover transition-all duration-300 font-renner border border-white/20 hover:border-white/40"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: '16px',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '0%',
|
||||
fontFamily: 'Renner'
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
{currentIndex === 0 && (
|
||||
<div className="absolute bottom-16 md:bottom-20 left-1/2 transform -translate-x-1/2 z-10">
|
||||
<motion.button
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
y: -2,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="bg-white text-[#3C2A21] px-8 md:px-12 py-4 md:py-5 rounded-full font-semibold text-base md:text-lg shadow-premium hover:shadow-premium-hover transition-all duration-300 font-renner border border-white/20 hover:border-white/40"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "22px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Premium Navigation Arrows - Hidden on mobile */}
|
||||
<AnimatePresence>
|
||||
@ -179,8 +181,18 @@ export default function BannerSlider() {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label="Previous banner"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.button>
|
||||
|
||||
@ -195,8 +207,18 @@ export default function BannerSlider() {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label="Next banner"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.button>
|
||||
</>
|
||||
@ -212,8 +234,18 @@ export default function BannerSlider() {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label="Previous banner"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.button>
|
||||
|
||||
@ -224,22 +256,32 @@ export default function BannerSlider() {
|
||||
whileTap={{ scale: 0.95 }}
|
||||
aria-label="Next banner"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* Premium Dots Indicator */}
|
||||
<div className="absolute bottom-6 md:bottom-8 left-1/2 transform -translate-x-1/2 z-20 flex space-x-2 md:space-x-3">
|
||||
<div className="absolute bottom-6 md:bottom-8 left-1/2 transform -translate-x-1/2 z-20 hidden md:flex space-x-2 md:space-x-3">
|
||||
{banners.map((_, index) => (
|
||||
<motion.button
|
||||
key={index}
|
||||
onClick={() => goToSlide(index)}
|
||||
className={`w-2 h-2 md:w-3 md:h-3 rounded-full transition-all duration-300 ${
|
||||
index === currentIndex
|
||||
? 'bg-white scale-110 shadow-glow'
|
||||
: 'bg-white/50 hover:bg-white/70 hover:scale-110'
|
||||
? "bg-white scale-110 shadow-glow"
|
||||
: "bg-white/50 hover:bg-white/70 hover:scale-110"
|
||||
}`}
|
||||
whileHover={{ scale: 1.2 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
@ -260,4 +302,4 @@ export default function BannerSlider() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ export default function Header() {
|
||||
<>
|
||||
{/* Mobile Header - Only visible on mobile */}
|
||||
<motion.header
|
||||
className="fixed top-0 z-[60] w-full md:hidden"
|
||||
className="fixed top-0 z-[60] w-full md:hidden p-1 "
|
||||
initial={false}
|
||||
animate={{
|
||||
width: "100%",
|
||||
@ -75,7 +75,7 @@ export default function Header() {
|
||||
ease: [0.25, 0.46, 0.45, 0.94],
|
||||
}}
|
||||
>
|
||||
<nav className="relative bg-white border border-white/20 shadow-premium w-full h-full flex items-center px-4">
|
||||
<nav className="relative bg-white border border-white/20 shadow-premium w-full h-full flex items-center px-4 rounded-[15px]">
|
||||
{/* Left: Hamburger Menu */}
|
||||
<div className="absolute left-4 top-1/2 -translate-y-1/2">
|
||||
<motion.button
|
||||
@ -136,7 +136,7 @@ export default function Header() {
|
||||
fill
|
||||
className="object-contain drop-shadow-lg"
|
||||
priority
|
||||
sizes="40px"
|
||||
sizes="60px"
|
||||
quality={95}
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
||||
@ -215,7 +215,7 @@ export default function Header() {
|
||||
|
||||
{/* Desktop Header - Only visible on desktop */}
|
||||
<motion.header
|
||||
className="fixed top-0 z-50 hidden md:block"
|
||||
className="fixed top-0 z-50 hidden md:block "
|
||||
initial={false}
|
||||
animate={{
|
||||
width: isScrolled ? "100%" : "1440px",
|
||||
@ -242,9 +242,9 @@ export default function Header() {
|
||||
href="#about"
|
||||
className="text-gray-800 hover:text-[#8B4513] transition-all duration-300 font-renner relative group"
|
||||
style={{
|
||||
fontWeight: 100,
|
||||
fontFamily: "Renner",
|
||||
fontSize: "18px",
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "22px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
@ -257,9 +257,9 @@ export default function Header() {
|
||||
href="#shop"
|
||||
className="text-gray-800 hover:text-[#8B4513] transition-all duration-300 font-renner relative group"
|
||||
style={{
|
||||
fontWeight: 100,
|
||||
fontFamily: "Renner",
|
||||
fontSize: "18px",
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "22px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
@ -309,9 +309,9 @@ export default function Header() {
|
||||
<motion.button
|
||||
className="flex items-center space-x-1 text-gray-800 hover:text-[#8B4513] transition-all duration-300 font-renner relative group"
|
||||
style={{
|
||||
fontWeight: 100,
|
||||
fontFamily: "Renner",
|
||||
fontSize: "18px",
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "22px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
@ -427,7 +427,7 @@ export default function Header() {
|
||||
className="block text-lg font-renner text-gray-800 hover:text-[#8B4513] transition-all duration-300 py-3 border-b border-gray-100"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner",
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "18px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
@ -442,7 +442,7 @@ export default function Header() {
|
||||
className="block text-lg font-renner text-gray-800 hover:text-[#8B4513] transition-all duration-300 py-3 border-b border-gray-100"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner",
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "18px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
@ -457,7 +457,7 @@ export default function Header() {
|
||||
className="block text-lg font-renner text-gray-800 hover:text-[#8B4513] transition-all duration-300 py-3 border-b border-gray-100"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontFamily: "Renner",
|
||||
fontFamily: "Renner*",
|
||||
fontSize: "18px",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
|
||||
152
src/components/ProductCategory.tsx
Normal file
152
src/components/ProductCategory.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
"use client";
|
||||
import { useState, useRef, useCallback, useMemo } from "react";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { motion } from "framer-motion";
|
||||
import Section from "@/components/Section";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function ProductCategory() {
|
||||
const [currentCategory, setCurrentCategory] = useState(0);
|
||||
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
||||
const fadeInLeft = {
|
||||
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 = [
|
||||
"Bars",
|
||||
"Barks",
|
||||
"Pralines",
|
||||
"Spreads",
|
||||
"Dragees",
|
||||
"Gelatos",
|
||||
];
|
||||
const categoryImages = [
|
||||
"/categories/c1.svg",
|
||||
"/categories/c2.svg",
|
||||
"/categories/c3.svg",
|
||||
"/categories/c4.svg",
|
||||
"/categories/c5.svg",
|
||||
"/categories/c6.svg",
|
||||
];
|
||||
|
||||
// Memoize category change handler for better performance
|
||||
const handleCategoryChange = useCallback((idx: number) => {
|
||||
setCurrentCategory(idx);
|
||||
}, []);
|
||||
|
||||
// Memoize category buttons to prevent unnecessary re-renders
|
||||
const categoryButtons = useMemo(() => {
|
||||
return categories.map((category, idx) => (
|
||||
<motion.li
|
||||
key={category}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.4, delay: idx * 0.05 }} // Reduced delay
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<button
|
||||
className={`group flex items-center gap-4 transition-all duration-200 ease-out w-full text-left font-moneta hover:scale-105
|
||||
${
|
||||
idx === currentCategory
|
||||
? "text-[#703133] font-normal"
|
||||
: "text-[#703133] opacity-60 font-light"
|
||||
}
|
||||
`}
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: idx === currentCategory ? 400 : 300,
|
||||
fontSize:
|
||||
idx === currentCategory
|
||||
? "clamp(22px, 5vw, 64px)"
|
||||
: "clamp(18px, 4vw, 56px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
transition: "all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1)", // Even faster transition
|
||||
}}
|
||||
onClick={() => handleCategoryChange(idx)}
|
||||
onMouseEnter={() => handleCategoryChange(idx)}
|
||||
onTouchStart={() => handleCategoryChange(idx)} // Add this for mobile touch
|
||||
tabIndex={-1}
|
||||
>
|
||||
{/* Show only on mobile */}
|
||||
<div className="block lg:hidden w-16 h-16 rounded-full overflow-hidden shrink-0">
|
||||
<Image
|
||||
src={categoryImages[idx]}
|
||||
alt={category}
|
||||
width={56}
|
||||
height={56}
|
||||
className="w-full h-full object-cover"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
sizes="(max-width: 375px) 40px, (max-width: 640px) 44px, (max-width: 768px) 48px, 56px"
|
||||
/>
|
||||
</div>
|
||||
{/* Category name */}
|
||||
<span className="text-left">{category}</span>
|
||||
</button>
|
||||
</motion.li>
|
||||
));
|
||||
}, [currentCategory, handleCategoryChange]);
|
||||
|
||||
// Memoize the category image component
|
||||
const categoryImageComponent = useMemo(
|
||||
() => (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.img
|
||||
key={currentCategory}
|
||||
src={categoryImages[currentCategory]}
|
||||
alt={categories[currentCategory]}
|
||||
initial={{ opacity: 0, scale: 1.01 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.99 }}
|
||||
transition={{ duration: 0.25, ease: [0.4, 0.0, 0.2, 1] }} // Super fast transition
|
||||
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}
|
||||
style={{ maxHeight: "600px", maxWidth: "100%" }}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
),
|
||||
[currentCategory]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
{/* Product Categories Section */}
|
||||
<Section background="white" id="categories">
|
||||
<div
|
||||
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: "-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) */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
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"
|
||||
>
|
||||
{categoryImageComponent}
|
||||
</motion.div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
284
src/components/SocialMedia.tsx
Normal file
284
src/components/SocialMedia.tsx
Normal file
@ -0,0 +1,284 @@
|
||||
"use client";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Section from "./Section";
|
||||
import { useInstagram } from "@/hooks/useInstagram";
|
||||
|
||||
export default function SocialMedia() {
|
||||
const { posts, loading, error, isTokenExpired } = useInstagram();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const fadeInUp = {
|
||||
initial: { opacity: 0, y: 30 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const fadeInLeft = {
|
||||
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] },
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Only auto-scroll on mobile (<768px)
|
||||
if (window.innerWidth >= 768) return;
|
||||
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
let scrollAmount = 0;
|
||||
const cardWidth = container.firstElementChild?.clientWidth || 300;
|
||||
const scrollStep = cardWidth + 16; // 16px gap between items
|
||||
let direction = 1; // 1 = right, -1 = left
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (!container) return;
|
||||
scrollAmount += scrollStep * direction;
|
||||
|
||||
// Reverse direction if hitting ends
|
||||
if (
|
||||
scrollAmount >= container.scrollWidth - container.clientWidth ||
|
||||
scrollAmount <= 0
|
||||
) {
|
||||
direction *= -1;
|
||||
}
|
||||
|
||||
container.scrollTo({
|
||||
left: scrollAmount,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 3000); // scroll every 3s
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Section background="cream" padding="md">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center mb-8 md:mb-12"
|
||||
>
|
||||
<h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-moneta text-[#703133] mb-6 md:mb-8 text-gradient"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(28px, 5vw, 48px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
>
|
||||
Follow us on Instagram
|
||||
</h2>
|
||||
</motion.div>
|
||||
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex overflow-x-auto no-scrollbar snap-x snap-mandatory gap-4 px-4 sm:grid sm:grid-cols-2 lg:grid-cols-3 sm:gap-6 md:gap-8 max-w-7xl mx-auto"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="bg-white rounded-lg shadow-premium h-64 md:h-96 skeleton snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: i * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : error ? (
|
||||
isTokenExpired ? (
|
||||
<div className="col-span-full text-center py-12 sm:col-span-2 lg:col-span-3">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="max-w-md mx-auto"
|
||||
>
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-amber-200 to-orange-300 rounded-full mx-auto mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-2xl">🔗</span>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-[#703133] mb-2">
|
||||
Instagram Connection Expired
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Our Instagram connection needs to be refreshed. We're
|
||||
working on getting it back up!
|
||||
</p>
|
||||
<div className="text-sm text-gray-500">
|
||||
In the meantime, follow us @mozimo_chocolate
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Left Block - Chocolate Spread Jar */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInLeft}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="relative h-64 md:h-96 bg-gradient-to-br from-amber-50 to-orange-50 flex items-center justify-center">
|
||||
<motion.div
|
||||
className="text-center"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="w-32 h-32 bg-gradient-to-br from-amber-200 to-orange-300 rounded-full mx-auto mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-4xl">🍫</span>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 mx-4 shadow-premium">
|
||||
<div className="font-semibold text-[#703133] text-lg">
|
||||
MOZIMO
|
||||
</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
SINGLE ORIGIN HAZELNUT SPREAD 45%
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Middle Block - Magazine Article */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="p-4 md:p-6">
|
||||
<div className="bg-red-600 h-32 rounded-lg mb-4 flex items-center justify-center shadow-premium">
|
||||
<span className="text-white text-2xl">🍫</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div className="bg-gray-200 h-20 rounded-lg flex items-center justify-center shadow-premium">
|
||||
<span className="text-lg">🍰</span>
|
||||
</div>
|
||||
<div className="bg-gray-200 h-20 rounded-lg flex items-center justify-center shadow-premium">
|
||||
<span className="text-lg">👨🍳</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-700 space-y-2 leading-relaxed">
|
||||
<p>
|
||||
<strong>By RUPALI DEAN</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
MOZIMO'S CHOCOLATE PIE BY PRIYANKA GUPTA
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
isolat[ing] beans to not only single ori- but also to
|
||||
single farms so as to express unique complexity.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Right Block - World Chocolate Day */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2 snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<div className="p-4 md:p-6 text-center">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-2xl font-bold text-[#8B4513] mb-2 text-gradient">
|
||||
World Chocolate Day
|
||||
</h3>
|
||||
<p className="text-gray-600">
|
||||
Celebrating the art of chocolate making
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gradient-to-br from-amber-100 to-orange-100 rounded-lg p-4 shadow-premium">
|
||||
<div className="text-4xl mb-2">🍫</div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Join us in celebrating the world's favorite treat
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
posts?.slice(0, 3).map((post, index) => (
|
||||
<motion.div
|
||||
key={post.id}
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={
|
||||
index === 0 ? fadeInLeft : index === 1 ? fadeInUp : fadeInRight
|
||||
}
|
||||
className="snap-start w-[calc(80vw-1rem)] flex-shrink-0 sm:w-auto bg-white rounded-lg shadow-premium overflow-hidden hover:shadow-premium-hover transition-all duration-500 transform hover:-translate-y-2"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
>
|
||||
<a
|
||||
href={post.permalink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<div className="relative h-64 md:h-96">
|
||||
<Image
|
||||
src={post.mediaUrl}
|
||||
alt={post.caption || "Instagram post"}
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = "/bst/bs1.svg";
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent" />
|
||||
{post.mediaType === "VIDEO" && (
|
||||
<div className="absolute top-4 right-4 bg-black/50 rounded-full p-2">
|
||||
<svg
|
||||
className="w-4 h-4 text-white"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M8 5v10l8-5-8-5z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute bottom-4 left-4 right-4">
|
||||
<div className="glass rounded-lg p-3 border border-white/20">
|
||||
<p className="text-sm text-gray-800 line-clamp-2">
|
||||
{post.caption?.slice(0, 100)}...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</motion.div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user