Performance improvements and Dockerfile update

This commit is contained in:
2026-04-19 09:48:53 +00:00
parent 31f88749f9
commit e94dec0036
17 changed files with 315 additions and 295 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@
# production
/build
.output/
# misc
.DS_Store

View File

@ -1,8 +1,6 @@
FROM node:22-alpine AS base
ARG NEXT_PUBLIC_INSTAGRAM_ACCESS_TOKEN
# Install dependencies only when needed
# Stage 1: Install dependencies
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
@ -18,54 +16,47 @@ RUN \
fi
# Rebuild the source code only when needed
# Stage 2: Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED=1
# Environment variables needed during build time
ARG INSTAGRAM_ACCESS_TOKEN
ENV INSTAGRAM_ACCESS_TOKEN=$INSTAGRAM_ACCESS_TOKEN
RUN \
if [ -f yarn.lock ]; then yarn run build; \
if [ -f yarn.lock ]; then yarn build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
else npm run build; \
fi
# Production image, copy all the files and run next
# Stage 3: Production image, copy all the files and run nitro
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED=1
# Set host to 0.0.0.0 to ensure it's accessible outside the container
ENV HOST=0.0.0.0
ENV PORT=3000
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN adduser --system --uid 1001 nitrojs
COPY --from=builder /app/public ./public
# Copy the build output from the builder stage
# TanStack Start/Nitro builds into the .output directory
COPY --from=builder --chown=nitrojs:nodejs /app/.output ./.output
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
USER nitrojs
EXPOSE 3000
ENV PORT=3000
# Health check to ensure the server is responding
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
# server.mjs is created by nitro build
CMD ["node", ".output/server/index.mjs"]

View File

@ -4,5 +4,26 @@ build-production: ## Build the production docker image.
--platform linux/amd64,linux/arm64/v8 \
--tag registry.tanshu.com/mozimo:latest \
$(if $(TAG),--tag registry.tanshu.com/mozimo:$(TAG)) \
--pull \
--push \
git@github.com:tanshu/mozimo.git
.PHONY: build-check
build-check: ## Multi-arch build without push (compile check)
@docker buildx build \
--platform linux/amd64,linux/arm64/v8 \
--tag mozimo:test \
--pull \
--progress=plain \
git@git.tanshu.com:tanshu/mozimo.git
.PHONY: build-check-local
build-check-local: ## Multi-arch build without push (compile check)
@git archive --format=tar HEAD | docker buildx build \
--platform linux/amd64 \
--tag barker:test \
--pull \
--progress=plain \
--load \
-

View File

@ -16,7 +16,7 @@ docker_image: "{{ registry }}/{{ title }}:{{ tag }}"
docker_container: "{{ title }}"
docker_port: 3000
instagram_token: IGQWRQSUY4b3RQWU9ZARHlVVUFDaWxJZAWFOUVllM0NxMk1zSHJ5X2JGc2dpRUxyTjBaeDNhUm0yaFZAQeUotVU9VUmFEQkJxU25CdFp3bURSZAGtnLXhCZAHVId21SWUNiNjVzc0pjZAlhPNDRxTDFzZAEQ0ZAXoyVlpUaHcZD
instagram_token: IGQWRhYzlibzE4VEpNYzZA2eHEtRkVLUXJiT3NyVU9mNjlNRzM1cmd2NzY0SGxtX1ZAVVGVwb1dkbFl4VlVWaW02aFhEakQzeUp3NjBCSktRR2lQYlgwV3UwMG1seTFsWDBMSzZAHejNYdlE5RFMzU0tqbS1OVEttN0UZD
caddy_container: caddy

View File

@ -9,8 +9,6 @@ const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
const eslintConfig = [{}];
export default eslintConfig;

View File

@ -1,6 +1,9 @@
import { createRootRoute, Outlet, HeadContent, Scripts } from '@tanstack/react-router';
import { LazyMotion } from 'framer-motion';
import appCss from './globals.css?url';
const loadFeatures = () => import('framer-motion').then(res => res.domAnimation);
export const Route = createRootRoute({
head: () => ({
meta: [
@ -68,7 +71,9 @@ function RootLayout() {
Since the Inter Google Font is loaded via the stylesheet above, it will automatically apply
if configured in Tailwind CSS. */}
<body>
<Outlet />
<LazyMotion features={loadFeatures} strict>
<Outlet />
</LazyMotion>
<Scripts />
</body>
</html>

View File

@ -1,9 +1,11 @@
import { createFileRoute } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { m } from "framer-motion";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
// Footer is lazy loaded
import { Image } from "@unpic/react";
import { lazy, Suspense } from "react";
const Footer = lazy(() => import("@/components/Footer"));
export const Route = createFileRoute('/about')({
component: AboutPage,
});
@ -43,14 +45,14 @@ function AboutPage() {
{/* Hero Section */}
<section className="pt-32 md:pt-40 pb-16 md:pb-24">
<div className="w-full">
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInUp}
className="text-center space-y-8"
>
<motion.h1
<m.h1
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#3C2A21] font-samantha"
style={{
fontFamily: "MonetaSans-Regular",
@ -64,9 +66,9 @@ function AboutPage() {
transition={{ duration: 0.3 }}
>
Welcome to the World of Mozimo Magic
</motion.h1>
</m.h1>
<motion.div
<m.div
className="w-full px-8 md:px-16 lg:px-24"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -85,17 +87,17 @@ function AboutPage() {
>
Indulge in the Luxurious. Immerse in the Captivating. Savor the Fresh. At Mozimo, we create a chocolate experience that transports you to a world of opulence, with mesmerizing aromas and visuals, and delights your taste buds with the vibrant flavors of freshly crafted chocolates. Join us on a journey of pure luxury, captivating moments, and a fresh perspective on the art of chocolate-making.
</p>
</motion.div>
</m.div>
{/* Hero Image */}
<motion.div
<m.div
className="relative w-full mt-12"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
viewport={{ once: true }}
>
<motion.div
<m.div
className="relative w-full h-[400px] md:h-[500px] lg:h-[600px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500 rounded-lg"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
>
@ -106,9 +108,9 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 via-transparent to-transparent" />
</motion.div>
</motion.div>
</motion.div>
</m.div>
</m.div>
</m.div>
</div>
</section>
@ -118,14 +120,14 @@ function AboutPage() {
<div className="w-full">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Founders Image */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInLeft}
className="order-2 lg:order-1"
>
<motion.div
<m.div
className="relative w-full h-[400px] md:h-[500px] lg:h-[600px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
>
@ -136,18 +138,18 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</motion.div>
</m.div>
</m.div>
{/* Founders Text */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInRight}
className="space-y-6 md:space-y-8 order-1 lg:order-2 px-8 md:px-16 lg:px-24"
>
<motion.h2
<m.h2
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
style={{
fontFamily: "MonetaSans-Regular",
@ -161,9 +163,9 @@ function AboutPage() {
transition={{ duration: 0.3 }}
>
Our Founders
</motion.h2>
</m.h2>
<motion.h3
<m.h3
className="text-2xl md:text-3xl font-semibold text-[#8B4513] font-renner"
style={{
fontWeight: 500,
@ -176,9 +178,9 @@ function AboutPage() {
transition={{ duration: 0.3 }}
>
Priyanka and Amritanshu.
</motion.h3>
</m.h3>
<motion.p
<m.p
className="text-gray-700 leading-relaxed font-renner"
style={{
fontWeight: 300,
@ -193,8 +195,8 @@ function AboutPage() {
viewport={{ once: true }}
>
are the proud founders of Mozimo, a bean-to-bar chocolate shop that celebrates the rich and distinctive flavors of single origin cocoa beans. Their journey with Mozimo has been a passionate pursuit of creating exceptional chocolate that tantalizes the taste buds and tells the unique story of each cocoa bean&apos;s origin.
</motion.p>
</motion.div>
</m.p>
</m.div>
</div>
</div>
</section>
@ -204,14 +206,14 @@ function AboutPage() {
<div className="w-full">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
{/* Italian Chocolate Text */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInLeft}
className="order-2 lg:order-1 space-y-6 md:space-y-8 px-8 md:px-16 lg:px-24"
>
<motion.h2
<m.h2
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21]"
style={{
fontFamily: "MonetaSans-Regular",
@ -225,8 +227,8 @@ function AboutPage() {
transition={{ duration: 0.3 }}
>
Capturing the essence of
</motion.h2>
<motion.h2
</m.h2>
<m.h2
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
style={{
fontFamily: "Samantha Signature",
@ -240,9 +242,9 @@ function AboutPage() {
transition={{ duration: 0.3 }}
>
Italian Chocolate
</motion.h2>
</m.h2>
<motion.p
<m.p
className="text-gray-700 leading-relaxed font-renner"
style={{
fontWeight: 300,
@ -257,18 +259,18 @@ function AboutPage() {
viewport={{ once: true }}
>
With over 15 years of hospitality experience, these avid travelers scoured the globe for the finest cocoa beans and techniques. Deep in remote cocoa farms, they cultivated relationships with farmers committed to sustainability. Carefully selecting beans, they honor each harvest&apos;s stories. In their cozy workshop, they roast, crack, and refine beans with modern techniques, capturing their essence in every Mozimo chocolate bar.
</motion.p>
</motion.div>
</m.p>
</m.div>
{/* Cocoa Farm Image */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInRight}
className="order-1 lg:order-2"
>
<motion.div
<m.div
className="relative w-full h-[400px] md:h-[500px] lg:h-[600px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
>
@ -279,8 +281,8 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</motion.div>
</m.div>
</m.div>
</div>
</div>
</section>
@ -289,14 +291,14 @@ function AboutPage() {
<section className="py-16 md:py-24 bg-white">
<div className="w-full">
{/* Two Images Side by Side */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={staggerContainer}
className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-12 mb-12 md:mb-16"
>
<motion.div
<m.div
variants={fadeInLeft}
className="relative w-full h-[300px] md:h-[400px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
@ -308,9 +310,9 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</m.div>
<motion.div
<m.div
variants={fadeInRight}
className="relative w-full h-[300px] md:h-[400px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
@ -322,18 +324,18 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</motion.div>
</m.div>
</m.div>
{/* Text Content */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInUp}
className="w-full space-y-6 md:space-y-8 px-8 md:px-16 lg:px-24"
>
<motion.p
<m.p
className="text-gray-700 leading-relaxed font-renner"
style={{
fontWeight: 300,
@ -348,9 +350,9 @@ function AboutPage() {
viewport={{ once: true }}
>
Under the mentorship of the master chocolatier Gabriele Rinaudo, Priyanka and Amritanshu dedicated themselves to mastering the intricate art of chocolate making. With unwavering determination and a thirst for knowledge, they immersed themselves in the world of cocoa, learning the nuances of sourcing the finest ingredients and perfecting the delicate techniques that transform raw beans into exquisite chocolate creations.
</motion.p>
</m.p>
<motion.p
<m.p
className="text-gray-700 leading-relaxed font-renner"
style={{
fontWeight: 300,
@ -365,15 +367,15 @@ function AboutPage() {
viewport={{ once: true }}
>
In addition to their training under Gabriele Rinaudo, they embarked on a transformative journey to Italy to further enrich their understanding of the art of chocolate making. This immersive experience in Italy not only broadened their knowledge but also deepened their appreciation for the timeless artistry and dedication that define the world of chocolate making.
</motion.p>
</motion.div>
</m.p>
</m.div>
</div>
</section>
{/* Vision and Passion Section */}
<section className="py-16 md:py-24 bg-[#F5E6D3]">
<div className="w-full">
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -381,7 +383,7 @@ function AboutPage() {
className="text-center space-y-8 md:space-y-12"
>
{/* Founders Outdoor Image */}
<motion.div
<m.div
className="relative w-full h-[400px] md:h-[500px] lg:h-[600px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500"
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
>
@ -392,10 +394,10 @@ function AboutPage() {
className="w-full h-full object-cover img-premium"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</m.div>
{/* Vision and Passion Text */}
<motion.div
<m.div
className="w-full space-y-6 md:space-y-8 px-8 md:px-16 lg:px-24"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -414,12 +416,14 @@ function AboutPage() {
>
Passionate about chocolate as a medium for creativity and exploration, they view it not just as a confection but as a canvas for artistic expression. Constantly seeking inspiration, they push the boundaries of chocolate-making, committed to crafting sensory experiences that delight the taste buds, eyes, and soul. Proud to be part of the global craft chocolate revolution, they aim to inspire others while championing cocoa diversity and sustainability. Back in the workshop, cacao beans are roasted, cracked, and refined with modern techniques, crafting each bar of Mozimo chocolate into a masterpiece, capturing the essence of its origins.
</p>
</motion.div>
</motion.div>
</m.div>
</m.div>
</div>
</section>
<Footer />
<Suspense fallback={<div className="h-[300px] w-full skeleton bg-gray-100 animate-pulse" />}>
<Footer />
</Suspense>
</div>
);
}

View File

@ -1,14 +1,14 @@
import { createFileRoute } from '@tanstack/react-router';
import { motion } from "framer-motion";
import { m } from "framer-motion";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
// Footer is lazy loaded
import Section from "@/components/Section";
import Button from "@/components/Button";
import BannerSlider from "@/components/BannerSlider";
import { Image } from "@unpic/react";
import { useState, useEffect, useRef, useCallback } from "react";
import ProductCategory from "@/components/ProductCategory";
import SocialMedia from "@/components/SocialMedia";
import { useState, useEffect, useRef, useCallback, lazy, Suspense } from "react";
const ProductCategory = lazy(() => import("@/components/ProductCategory"));
const Footer = lazy(() => import("@/components/Footer"));
export const Route = createFileRoute('/')({
component: Home,
@ -17,30 +17,35 @@ export const Route = createFileRoute('/')({
// Premium animation variants
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] },
animate: { opacity: 1, y: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } },
};
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] },
animate: { opacity: 1, x: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } },
};
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] },
animate: { opacity: 1, x: 0, transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] as const } },
};
const staggerContainer = {
initial: { opacity: 0 },
animate: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.1,
},
},
};
const itemUp = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0, transition: { duration: 0.6 } }
};
const categories = [
"Bars",
"Barks",
@ -134,14 +139,14 @@ function Home() {
<Section background="white" id="about">
<div className="grid grid-cols-1 lg:grid-cols-2 items-center">
{/* Brand Story - Left Text */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInLeft}
className="space-y-6 md:space-y-8 order-2 lg:order-1 px-4 md:px-0" // ← Added mobile padding
>
<motion.h2
<m.h2
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,
@ -155,7 +160,7 @@ function Home() {
transition={{ duration: 0.3 }}
>
Brand Story
</motion.h2>
</m.h2>
<div
className="space-y-4 md:space-y-6 text-gray-700 leading-relaxed font-renner"
style={{
@ -166,7 +171,7 @@ function Home() {
fontFamily: "Renner*",
}}
>
<motion.p
<m.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
@ -175,8 +180,8 @@ function Home() {
Mozimo&apos;s journey is a passionate pursuit of crafting
exceptional chocolate that celebrates the origin of each cocoa
bean.
</motion.p>
<motion.p
</m.p>
<m.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
@ -184,8 +189,8 @@ function Home() {
>
We meticulously roast, crack, winnow, and refine beans in-house,
using modern techniques to highlight their natural flavors.
</motion.p>
<motion.p
</m.p>
<m.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
@ -193,9 +198,9 @@ function Home() {
>
Each chocolate is a masterpiece, capturing the essence of cocoa
in its purest form.
</motion.p>
</m.p>
</div>
<motion.div
<m.div
className="pt-4"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -205,17 +210,17 @@ function Home() {
<Button variant="white" size="lg" className="btn-premium">
Discover more
</Button>
</motion.div>
</motion.div>
</m.div>
</m.div>
{/* Brand Story - Right Image */}
<motion.div
<m.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"
>
<motion.div
<m.div
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 } }}
>
@ -227,18 +232,18 @@ function Home() {
draggable={false}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</motion.div>
</m.div>
</m.div>
{/* Chocolate Tempering - Left Video */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
variants={fadeInLeft}
className="relative order-3 lg:order-3 w-screen md:w-auto -mx-4 md:mx-0 py-4 md:py-0"
>
<motion.div
<m.div
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 } }}
>
@ -253,11 +258,11 @@ function Home() {
aria-label="Chocolate pouring from metallic spout - Mozimo chocolate tempering process"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</motion.div>
</m.div>
</m.div>
{/* Chocolate Tempering - Right Text */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -265,7 +270,7 @@ function Home() {
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
<m.h2
className="text-2xl md:text-3xl lg:text-4xl font-bold text-[#3C2A21] font-renner text-gradient"
style={{
fontWeight: 200,
@ -279,8 +284,8 @@ function Home() {
transition={{ duration: 0.3 }}
>
Discover the delicate art of our
</motion.h2>
<motion.h3
</m.h2>
<m.h3
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#8B4513] font-samantha"
style={{
fontFamily: "Samantha Signature",
@ -295,10 +300,10 @@ function Home() {
transition={{ duration: 0.3 }}
>
Chocolate Tempering
</motion.h3>
</m.h3>
</div>
<motion.div
<m.div
className="w-100 h-px bg-gradient-to-r from-transparent via-[#8B4513] to-transparent"
initial={{ scaleX: 0 }}
whileInView={{ scaleX: 1 }}
@ -306,7 +311,7 @@ function Home() {
viewport={{ once: true }}
/>
<motion.p
<m.p
className="text-gray-700 leading-relaxed font-renner"
style={{
fontWeight: 300,
@ -325,14 +330,14 @@ function Home() {
perfectly tempered chocolate: velvety smooth, exquisitely rich,
and artfully balanced. Each bite offers a symphony of nuanced
cocoa flavors, melting luxuriously!
</motion.p>
</motion.div>
</m.p>
</m.div>
</div>
</Section>
{/* Our Collections Section */}
<Section background="cream" id="shop">
<motion.h2
<m.h2
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -349,9 +354,9 @@ function Home() {
transition={{ duration: 0.3 }}
>
Our Collections
</motion.h2>
</m.h2>
<motion.div
<m.div
className="flex overflow-x-auto no-scrollbar snap-x snap-mandatory gap-4 px-4 sm:px-0 sm:grid sm:grid-cols-2 lg:grid-cols-3 sm:gap-6 md:gap-8"
variants={staggerContainer}
initial="initial"
@ -375,7 +380,7 @@ function Home() {
alt: "Gift Collection - Mozimo packaged chocolate products and gift items",
},
].map((item) => (
<motion.div
<m.div
key={item.title}
variants={fadeInUp}
className="snap-start w-[calc(50%)] flex-shrink-0 sm:w-auto sm:flex-shrink sm:min-w-0 group flex flex-col items-center justify-end transition-all duration-500 hover:-translate-y-2"
@ -391,9 +396,8 @@ function Home() {
width={320}
height={400}
layout="constrained"
className="object-contain img-premium group-hover:scale-105 transition-transform duration-500"
className="object-contain img-premium group-hover:scale-105 transition-transform duration-500 max-h-[380px] w-full"
draggable={false}
style={{ maxHeight: "380px", width: "100%" }}
/>
<div className="p-4 md:p-6 text-center mb-2 w-full">
@ -410,18 +414,20 @@ function Home() {
{item.title}
</h3>
</div>
</motion.div>
</m.div>
))}
</motion.div>
</m.div>
</Section>
<ProductCategory />
<Suspense fallback={<div className="h-[500px] w-full skeleton bg-gray-100 animate-pulse" />}>
<ProductCategory />
</Suspense>
{/* Social Media Call to Action */}
<SocialMedia />
{/* <SocialMedia /> */}
{/* In the Spotlight Section */}
<Section background="white">
<motion.h2
<m.h2
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -438,10 +444,10 @@ function Home() {
transition={{ duration: 0.3 }}
>
In the Spotlight
</motion.h2>
</m.h2>
<div className="w-full flex justify-center items-center py-8 md:py-12">
<div className="w-full max-w-[700px] px-4 overflow-hidden">
<motion.div
<m.div
className="flex gap-8 py-6"
animate={{ x: `-${spotlightIndex * 152}px` }}
transition={{ type: "spring", stiffness: 80, damping: 18 }}
@ -452,7 +458,7 @@ function Home() {
const imgIdx = i % partnerImages.length;
const img = partnerImages[imgIdx];
return (
<motion.div
<m.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={{
@ -474,15 +480,17 @@ function Home() {
className="w-16 h-16 md:w-20 md:h-20 lg:w-20 lg:h-20 object-contain img-premium"
draggable={false}
/>
</motion.div>
</m.div>
);
})}
</motion.div>
</m.div>
</div>
</div>
</Section>
<Footer />
<Suspense fallback={<div className="h-[300px] w-full skeleton bg-gray-100 animate-pulse" />}>
<Footer />
</Suspense>
</div>
);
}

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { m, AnimatePresence } from "framer-motion";
import { Image } from "@unpic/react";
const banners = [
@ -95,7 +95,7 @@ export default function BannerSlider() {
{/* Banner Images with Premium Animations */}
<AnimatePresence initial={false} custom={direction} mode="popLayout">
<motion.div
<m.div
key={`${currentIndex}-${direction}`}
custom={direction}
variants={slideVariants}
@ -130,13 +130,13 @@ export default function BannerSlider() {
{/* Premium overlay with gradient */}
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
</motion.div>
</m.div>
</AnimatePresence>
{/* Premium Shop Now Button */}
{currentIndex === 0 && (
<div className="absolute bottom-16 md:bottom-20 left-1/2 transform -translate-x-1/2 z-10">
<motion.a
<m.a
href="https://shop.mozimo.in/"
target="_blank"
rel="noopener noreferrer"
@ -159,7 +159,7 @@ export default function BannerSlider() {
}}
>
<span className="relative z-10">Shop Now</span>
</motion.a>
</m.a>
</div>
)}
@ -167,7 +167,7 @@ export default function BannerSlider() {
<AnimatePresence>
{showArrows && (
<>
<motion.button
<m.button
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
@ -191,9 +191,9 @@ export default function BannerSlider() {
d="M15 19l-7-7 7-7"
/>
</svg>
</motion.button>
</m.button>
<motion.button
<m.button
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
@ -217,14 +217,14 @@ export default function BannerSlider() {
d="M9 5l7 7-7 7"
/>
</svg>
</motion.button>
</m.button>
</>
)}
</AnimatePresence>
{/* Mobile Navigation Arrows */}
<div className="md:hidden absolute inset-x-0 top-1/2 transform -translate-y-1/2 z-20 flex justify-between items-center px-4">
<motion.button
<m.button
onClick={goToPrevious}
className="glass text-white p-3 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium"
whileHover={{ scale: 1.1, rotate: -5 }}
@ -244,9 +244,9 @@ export default function BannerSlider() {
d="M15 19l-7-7 7-7"
/>
</svg>
</motion.button>
</m.button>
<motion.button
<m.button
onClick={goToNext}
className="glass text-white p-3 rounded-full backdrop-blur-sm transition-all duration-300 border border-white/20 hover:border-white/40 shadow-premium"
whileHover={{ scale: 1.1, rotate: 5 }}
@ -266,13 +266,13 @@ export default function BannerSlider() {
d="M9 5l7 7-7 7"
/>
</svg>
</motion.button>
</m.button>
</div>
{/* Premium Dots Indicator */}
<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
<m.button
key={index}
onClick={() => goToSlide(index)}
className={`w-2 h-2 md:w-3 md:h-3 rounded-full transition-all duration-300 ${
@ -289,7 +289,7 @@ export default function BannerSlider() {
{/* Premium Progress Bar */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-white/10 z-20">
<motion.div
<m.div
className="h-full bg-gradient-to-r from-[#8B4513] to-[#DAA520]"
initial={{ width: 0 }}
animate={{ width: "100%" }}

View File

@ -1,4 +1,4 @@
import { motion } from "framer-motion";
import { m } from "framer-motion";
import { Image } from "@unpic/react";
export default function DesktopFooter() {
@ -11,7 +11,7 @@ export default function DesktopFooter() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 relative z-10">
{/* Column 1: Brand Identity */}
<motion.div
<m.div
className="space-y-4 sm:col-span-2 lg:col-span-1"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -41,10 +41,10 @@ export default function DesktopFooter() {
India&apos;s Premier European style bean-to-bar chocolate
experience.
</p>
</motion.div>
</m.div>
{/* Column 2: Get in touch */}
<motion.div
<m.div
className="space-y-4"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -73,7 +73,7 @@ export default function DesktopFooter() {
fontFamily: "Renner*",
}}
>
<motion.div
<m.div
className="flex items-center space-x-2"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -89,8 +89,8 @@ export default function DesktopFooter() {
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
</svg>
<span className="text-xs md:text-sm">0172-4045414</span>
</motion.div>
<motion.div
</m.div>
<m.div
className="flex items-start space-x-2"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -112,12 +112,12 @@ export default function DesktopFooter() {
<span className="text-xs md:text-sm">
SCO 8, Inner Market, 9-D, Sector 9, Chandigarh, 160009
</span>
</motion.div>
</m.div>
</div>
</motion.div>
</m.div>
{/* Column 3: Know more */}
<motion.div
<m.div
className="space-y-4"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -152,7 +152,7 @@ export default function DesktopFooter() {
"Terms & Conditions",
"Shipping Policy",
].map((link, index) => (
<motion.li
<m.li
key={link}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -166,13 +166,13 @@ export default function DesktopFooter() {
{link}
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[#8B4513] to-[#DAA520] transition-all duration-300 group-hover:w-full"></span>
</a>
</motion.li>
</m.li>
))}
</ul>
</motion.div>
</m.div>
{/* Column 4: FAQ, Store, Ordering, Social Media */}
<motion.div
<m.div
className="space-y-4 sm:col-span-2 lg:col-span-1"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -191,7 +191,7 @@ export default function DesktopFooter() {
}}
>
{["FAQ", "Locate our store", "Bulk Ordering"].map((link, index) => (
<motion.div
<m.div
key={link}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -205,7 +205,7 @@ export default function DesktopFooter() {
{link}
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[#8B4513] to-[#DAA520] transition-all duration-300 group-hover:w-full"></span>
</a>
</motion.div>
</m.div>
))}
</div>
@ -225,7 +225,7 @@ export default function DesktopFooter() {
</h3>
<div className="flex space-x-3 md:space-x-4">
{/* Facebook */}
<motion.a
<m.a
href="https://www.facebook.com/share/16vPmvcQV7/?mibextid=wwXIfr"
target="_blank"
rel="noopener noreferrer"
@ -245,9 +245,9 @@ export default function DesktopFooter() {
>
<path d="M22.675 0h-21.35C.595 0 0 .592 0 1.326v21.348C0 23.408.595 24 1.325 24h11.495v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.797.143v3.24l-1.918.001c-1.504 0-1.797.715-1.797 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116C23.406 24 24 23.408 24 22.674V1.326C24 .592 23.406 0 22.675 0" />
</svg>
</motion.a>
</m.a>
{/* Instagram */}
<motion.a
<m.a
href="https://www.instagram.com/mozimo.choc?igsh=bTVzbGo0enV3b3Jn"
target="_blank"
rel="noopener noreferrer"
@ -267,9 +267,9 @@ export default function DesktopFooter() {
>
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 1.366.062 2.633.334 3.608 1.308.974.974 1.246 2.241 1.308 3.608.058 1.266.069 1.646.069 4.85s-.012 3.584-.07 4.85c-.062 1.366-.334 2.633-1.308 3.608-.974.974-2.241 1.246-3.608 1.308-1.266.058-1.646.069-4.85.069s-3.584-.012-4.85-.07c-1.366-.062-2.633-.334-3.608-1.308-.974-.974-1.246-2.241-1.308-3.608C2.175 15.647 2.163 15.267 2.163 12s.012-3.584.07-4.85c.062-1.366.334-2.633 1.308-3.608C4.515 2.567 5.782 2.295 7.148 2.233 8.414 2.175 8.794 2.163 12 2.163zm0-2.163C8.741 0 8.332.012 7.052.07 5.771.128 4.659.334 3.678 1.315c-.98.98-1.187 2.092-1.245 3.373C2.012 5.668 2 6.077 2 12c0 5.923.012 6.332.07 7.612.058 1.281.265 2.393 1.245 3.373.98.98 2.092 1.187 3.373 1.245C8.332 23.988 8.741 24 12 24s3.668-.012 4.948-.07c1.281-.058 2.393-.265 3.373-1.245.98-.98 1.187-2.092 1.245-3.373.058-1.28.07-1.689.07-7.612 0-5.923-.012-6.332-.07-7.612-.058-1.281-.265-2.393-1.245-3.373-.98-.98-2.092-1.187-3.373-1.245C15.668.012 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zm0 10.162a3.999 3.999 0 1 1 0-7.998 3.999 3.999 0 0 1 0 7.998zm6.406-11.845a1.44 1.44 0 1 0 0 2.88 1.44 1.44 0 0 0 0-2.88z" />
</svg>
</motion.a>
</m.a>
{/* YouTube */}
<motion.a
<m.a
href="https://www.youtube.com/@MozimoChocolates"
target="_blank"
rel="noopener noreferrer"
@ -289,9 +289,9 @@ export default function DesktopFooter() {
>
<path d="M23.498 6.186a2.994 2.994 0 0 0-2.107-2.117C19.163 3.5 12 3.5 12 3.5s-7.163 0-9.391.569A2.994 2.994 0 0 0 .502 6.186C0 8.413 0 12 0 12s0 3.587.502 5.814a2.994 2.994 0 0 0 2.107 2.117C4.837 20.5 12 20.5 12 20.5s7.163 0 9.391-.569a2.994 2.994 0 0 0 2.107-2.117C24 15.587 24 12 24 12s0-3.587-.502-5.814zM9.545 15.568V8.432l6.545 3.568-6.545 3.568z" />
</svg>
</motion.a>
</m.a>
{/* LinkedIn */}
<motion.a
<m.a
href="https://www.linkedin.com/company/mozimochocolates/"
target="_blank"
rel="noopener noreferrer"
@ -311,14 +311,14 @@ export default function DesktopFooter() {
>
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.761 0 5-2.239 5-5v-14c0-2.761-2.239-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.268c-.966 0-1.75-.784-1.75-1.75s.784-1.75 1.75-1.75 1.75.784 1.75 1.75-.784 1.75-1.75 1.75zm15.5 11.268h-3v-5.604c0-1.337-.025-3.063-1.868-3.063-1.868 0-2.154 1.459-2.154 2.967v5.7h-3v-10h2.881v1.367h.041c.401-.761 1.381-1.563 2.841-1.563 3.039 0 3.6 2.001 3.6 4.601v5.595z" />
</svg>
</motion.a>
</m.a>
</div>
</div>
</motion.div>
</m.div>
</div>
{/* Bottom Bar */}
<motion.div
<m.div
className="border-t border-[#e5e5e5] mt-6 md:mt-8 pt-6 md:pt-8 text-center text-sm font-renner relative z-10"
style={{
fontWeight: 300,
@ -335,7 +335,7 @@ export default function DesktopFooter() {
<p className="text-xs md:text-sm">
&copy; 2024 Mozimo. All rights reserved.
</p>
</motion.div>
</m.div>
</footer>
);
}

View File

@ -1,4 +1,4 @@
import { motion, AnimatePresence } from "framer-motion";
import { m, AnimatePresence, useScroll, useMotionValueEvent } from "framer-motion";
import { Image } from "@unpic/react";
import { useState, useEffect } from "react";
import { useLocation } from "@tanstack/react-router";
@ -8,15 +8,11 @@ export default function Header() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const pathname = useLocation({ select: (loc) => loc.pathname });
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY;
setIsScrolled(scrollPosition > 100);
};
const { scrollY } = useScroll();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useMotionValueEvent(scrollY, "change", (latest: number) => {
setIsScrolled(latest > 100);
});
// Close mobile menu when clicking outside
useEffect(() => {
@ -59,7 +55,7 @@ export default function Header() {
return (
<>
{/* Mobile Header - Only visible on mobile */}
<motion.header
<m.header
className="fixed top-0 z-[60] w-full md:hidden p-1 "
initial={false}
animate={{
@ -78,7 +74,7 @@ export default function Header() {
<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
<m.button
className="hamburger-button p-2 text-gray-800 hover:text-[#8B4513] transition-all duration-300 relative min-w-[40px] min-h-[40px] flex items-center justify-center rounded-full hover:bg-white/20 backdrop-blur-sm"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
@ -86,20 +82,20 @@ export default function Header() {
aria-label="Toggle mobile menu"
>
<div className="w-6 h-6 flex flex-col justify-center items-center">
<motion.span
<m.span
className="w-6 h-0.5 bg-current block transition-all duration-300"
animate={{
rotate: isMobileMenuOpen ? 45 : 0,
y: isMobileMenuOpen ? 6 : 0,
}}
/>
<motion.span
<m.span
className="w-6 h-0.5 bg-current block mt-1 transition-all duration-300"
animate={{
opacity: isMobileMenuOpen ? 0 : 1,
}}
/>
<motion.span
<m.span
className="w-6 h-0.5 bg-current block mt-1 transition-all duration-300"
animate={{
rotate: isMobileMenuOpen ? -45 : 0,
@ -107,18 +103,18 @@ export default function Header() {
}}
/>
</div>
</motion.button>
</m.button>
</div>
{/* Center: Logo */}
<div className="absolute left-1/2 transform -translate-x-1/2">
<motion.div
<m.div
className="flex items-center justify-center"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<motion.div
<m.div
className="relative"
animate={{
width: isScrolled ? 40 : 60, // shrink on scroll
@ -137,13 +133,13 @@ export default function Header() {
layout="fullWidth"
className="w-full h-full object-contain drop-shadow-lg"
/>
</motion.div>
</motion.div>
</m.div>
</m.div>
</div>
{/* Right: Action Buttons */}
<div className="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-1">
<motion.button
<m.button
className="p-2"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
@ -162,9 +158,9 @@ export default function Header() {
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</motion.button>
</m.button>
<motion.button
<m.button
className="p-2"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
@ -183,9 +179,9 @@ export default function Header() {
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
/>
</svg>
</motion.button>
</m.button>
<motion.button
<m.button
className="p-2"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
@ -204,13 +200,13 @@ export default function Header() {
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</motion.button>
</m.button>
</div>
</nav>
</motion.header>
</m.header>
{/* Desktop Header - Only visible on desktop */}
<motion.header
<m.header
className="fixed top-0 z-50 hidden md:block "
initial={false}
animate={{
@ -234,7 +230,7 @@ export default function Header() {
<div className="flex justify-between items-center w-full px-20">
{/* Left Navigation */}
<div className="flex items-center space-x-16">
<motion.a
<m.a
href={pathname === "/about" ? "/" : "/about"}
className="text-gray-800 hover:text-[#8B4513] transition-all duration-300 font-renner relative group"
style={{
@ -248,8 +244,8 @@ export default function Header() {
>
{pathname === "/about" ? "Home" : "About us"}
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[#8B4513] to-[#DAA520] transition-all duration-300 group-hover:w-full"></span>
</motion.a>
<motion.a
</m.a>
<m.a
href="https://shop.mozimo.in/"
target="_blank"
rel="noopener noreferrer"
@ -265,11 +261,11 @@ export default function Header() {
>
Shop
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[#8B4513] to-[#DAA520] transition-all duration-300 group-hover:w-full"></span>
</motion.a>
</m.a>
</div>
{/* Center Logo */}
<motion.div
<m.div
className="relative"
animate={{
width: isScrolled ? 75 : 96, // shrink logo when scrolled
@ -289,12 +285,12 @@ export default function Header() {
layout="fullWidth"
className="w-full h-full object-contain drop-shadow-lg"
/>
</motion.div>
</m.div>
{/* Right Icons */}
<div className="flex items-center space-x-8">
{/* More Dropdown */}
<motion.button
<m.button
className="flex items-center space-x-1 text-gray-800 hover:text-[#8B4513] transition-all duration-300 font-renner relative group"
style={{
fontWeight: 300,
@ -307,7 +303,7 @@ export default function Header() {
aria-label="More options"
>
<span>More</span>
<motion.svg
<m.svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
@ -322,10 +318,10 @@ export default function Header() {
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</motion.svg>
</motion.button>
</m.svg>
</m.button>
<motion.button
<m.button
className="p-3 text-gray-800 hover:text-[#8B4513] transition-all duration-300 relative min-w-[40px] min-h-[40px] flex items-center justify-center rounded-full hover:bg-white/20 backdrop-blur-sm"
whileHover={{ scale: 1.1, rotate: 5 }}
whileTap={{ scale: 0.95 }}
@ -344,8 +340,8 @@ export default function Header() {
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
</motion.button>
<motion.button
</m.button>
<m.button
className="p-3 text-gray-800 hover:text-[#8B4513] transition-all duration-300 relative min-w-[40px] min-h-[40px] flex items-center justify-center rounded-full hover:bg-white/20 backdrop-blur-sm"
whileHover={{ scale: 1.1, rotate: -5 }}
whileTap={{ scale: 0.95 }}
@ -364,8 +360,8 @@ export default function Header() {
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</motion.button>
<motion.button
</m.button>
<m.button
className="p-3 text-gray-800 hover:text-[#8B4513] transition-all duration-300 relative min-w-[40px] min-h-[40px] flex items-center justify-center rounded-full hover:bg-white/20 backdrop-blur-sm"
whileHover={{ scale: 1.1, y: -2 }}
whileTap={{ scale: 0.95 }}
@ -384,23 +380,23 @@ export default function Header() {
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
/>
</svg>
</motion.button>
</m.button>
</div>
</div>
</nav>
</motion.header>
</m.header>
{/* Mobile Menu Overlay - Only visible on mobile */}
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
<m.div
className="mobile-menu fixed inset-0 z-50 bg-black/50 backdrop-blur-sm md:hidden"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
<m.div
className="absolute top-[72px] left-0 right-0 bg-white/95 backdrop-blur-md border-t border-white/20 shadow-premium"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
@ -410,7 +406,7 @@ export default function Header() {
<div className="px-6 py-8 space-y-6">
{/* Mobile Navigation Links */}
<div className="space-y-4">
<motion.a
<m.a
href={pathname === "/about" ? "/" : "/about"}
className="block text-lg font-renner text-gray-800 hover:text-[#8B4513] transition-all duration-300 py-3 border-b border-gray-100"
style={{
@ -424,8 +420,8 @@ export default function Header() {
whileHover={{ x: 10 }}
>
{pathname === "/about" ? "Home" : "About us"}
</motion.a>
<motion.a
</m.a>
<m.a
href="https://shop.mozimo.in/"
target="_blank"
rel="noopener noreferrer"
@ -441,8 +437,8 @@ export default function Header() {
whileHover={{ x: 10 }}
>
Shop
</motion.a>
<motion.a
</m.a>
<m.a
href="#categories"
className="block text-lg font-renner text-gray-800 hover:text-[#8B4513] transition-all duration-300 py-3 border-b border-gray-100"
style={{
@ -456,11 +452,11 @@ export default function Header() {
whileHover={{ x: 10 }}
>
Categories
</motion.a>
</m.a>
</div>
</div>
</motion.div>
</motion.div>
</m.div>
</m.div>
)}
</AnimatePresence>
</>

View File

@ -1,4 +1,4 @@
import { motion } from "framer-motion";
import { m } from "framer-motion";
import { Image } from "@unpic/react";
export default function MobileFooter() {
@ -11,7 +11,7 @@ export default function MobileFooter() {
<div className="px-4 py-8 flex flex-col gap-8 relative z-10">
{/* Logo at top */}
<motion.div
<m.div
className="flex"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
@ -26,12 +26,12 @@ export default function MobileFooter() {
height={80}
layout="fixed"
/>
</motion.div>
</m.div>
{/* Two column layout */}
<div className="grid grid-cols-2 gap-6">
{/* Left Column - Contact Info */}
<motion.div
<m.div
className="space-y-4"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -60,7 +60,7 @@ export default function MobileFooter() {
fontFamily: "Renner*",
}}
>
<motion.div
<m.div
className="flex items-center space-x-2"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -76,8 +76,8 @@ export default function MobileFooter() {
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" />
</svg>
<span className="text-xs md:text-sm">0172-4045414</span>
</motion.div>
<motion.div
</m.div>
<m.div
className="flex items-start space-x-2"
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -99,7 +99,7 @@ export default function MobileFooter() {
<span className="text-xs md:text-sm">
SCO 8, Inner Market, 9-D, Sector 9, Chandigarh, 160009
</span>
</motion.div>
</m.div>
</div>
{/* Find Us On */}
@ -118,7 +118,7 @@ export default function MobileFooter() {
</h3>
<div className="flex">
{/* Social icons with original animations */}
<motion.a
<m.a
href="https://www.facebook.com/share/16vPmvcQV7/?mibextid=wwXIfr"
target="_blank"
rel="noopener noreferrer"
@ -138,8 +138,8 @@ export default function MobileFooter() {
>
<path d="M22.675 0h-21.35C.595 0 0 .592 0 1.326v21.348C0 23.408.595 24 1.325 24h11.495v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.797.143v3.24l-1.918.001c-1.504 0-1.797.715-1.797 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116C23.406 24 24 23.408 24 22.674V1.326C24 .592 23.406 0 22.675 0" />
</svg>
</motion.a>
<motion.a
</m.a>
<m.a
href="https://www.instagram.com/mozimo.choc?igsh=bTVzbGo0enV3b3Jn"
target="_blank"
rel="noopener noreferrer"
@ -159,8 +159,8 @@ export default function MobileFooter() {
>
<path d="M12 2.163c3.204 0 3.584.012 4.85.07 1.366.062 2.633.334 3.608 1.308.974.974 1.246 2.241 1.308 3.608.058 1.266.069 1.646.069 4.85s-.012 3.584-.07 4.85c-.062 1.366-.334 2.633-1.308 3.608-.974.974-2.241 1.246-3.608 1.308-1.266.058-1.646.069-4.85.069s-3.584-.012-4.85-.07c-1.366-.062-2.633-.334-3.608-1.308-.974-.974-1.246-2.241-1.308-3.608C2.175 15.647 2.163 15.267 2.163 12s.012-3.584.07-4.85c.062-1.366.334-2.633 1.308-3.608C4.515 2.567 5.782 2.295 7.148 2.233 8.414 2.175 8.794 2.163 12 2.163zm0-2.163C8.741 0 8.332.012 7.052.07 5.771.128 4.659.334 3.678 1.315c-.98.98-1.187 2.092-1.245 3.373C2.012 5.668 2 6.077 2 12c0 5.923.012 6.332.07 7.612.058 1.281.265 2.393 1.245 3.373.98.98 2.092 1.187 3.373 1.245C8.332 23.988 8.741 24 12 24s3.668-.012 4.948-.07c1.281-.058 2.393-.265 3.373-1.245.98-.98 1.187-2.092 1.245-3.373.058-1.28.07-1.689.07-7.612 0-5.923-.012-6.332-.07-7.612-.058-1.281-.265-2.393-1.245-3.373-.98-.98-2.092-1.187-3.373-1.245C15.668.012 15.259 0 12 0zm0 5.838a6.162 6.162 0 1 0 0 12.324 6.162 6.162 0 0 0 0-12.324zm0 10.162a3.999 3.999 0 1 1 0-7.998 3.999 3.999 0 0 1 0 7.998zm6.406-11.845a1.44 1.44 0 1 0 0 2.88 1.44 1.44 0 0 0 0-2.88z" />
</svg>
</motion.a>
<motion.a
</m.a>
<m.a
href="https://www.youtube.com/@MozimoChocolates"
target="_blank"
rel="noopener noreferrer"
@ -180,8 +180,8 @@ export default function MobileFooter() {
>
<path d="M23.498 6.186a2.994 2.994 0 0 0-2.107-2.117C19.163 3.5 12 3.5 12 3.5s-7.163 0-9.391.569A2.994 2.994 0 0 0 .502 6.186C0 8.413 0 12 0 12s0 3.587.502 5.814a2.994 2.994 0 0 0 2.107 2.117C4.837 20.5 12 20.5 12 20.5s7.163 0 9.391-.569a2.994 2.994 0 0 0 2.107-2.117C24 15.587 24 12 24 12s0-3.587-.502-5.814zM9.545 15.568V8.432l6.545 3.568-6.545 3.568z" />
</svg>
</motion.a>
<motion.a
</m.a>
<m.a
href="https://www.linkedin.com/company/mozimochocolates/"
target="_blank"
rel="noopener noreferrer"
@ -201,14 +201,14 @@ export default function MobileFooter() {
>
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.761 0 5-2.239 5-5v-14c0-2.761-2.239-5-5-5zm-11 19h-3v-10h3v10zm-1.5-11.268c-.966 0-1.75-.784-1.75-1.75s.784-1.75 1.75-1.75 1.75.784 1.75 1.75-.784 1.75-1.75 1.75zm15.5 11.268h-3v-5.604c0-1.337-.025-3.063-1.868-3.063-1.868 0-2.154 1.459-2.154 2.967v5.7h-3v-10h2.881v1.367h.041c.401-.761 1.381-1.563 2.841-1.563 3.039 0 3.6 2.001 3.6 4.601v5.595z" />
</svg>
</motion.a>
</m.a>
{/* Other social icons... */}
</div>
</div>
</motion.div>
</m.div>
{/* Right Column - Know more and other links */}
<motion.div
<m.div
className="space-y-4"
initial={{ opacity: 0, x: 10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -247,7 +247,7 @@ export default function MobileFooter() {
"Locate our store",
"Bulk Ordering",
].map((link, index) => (
<motion.li
<m.li
key={link}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
@ -261,15 +261,15 @@ export default function MobileFooter() {
{link}
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-gradient-to-r from-[#8B4513] to-[#DAA520] transition-all duration-300 group-hover:w-full"></span>
</a>
</motion.li>
</m.li>
))}
</ul>
</div>
</motion.div>
</m.div>
</div>
{/* Copyright - original styling preserved */}
<motion.div
<m.div
className="border-t border-[#e5e5e5] mt-6 md:mt-8 pt-6 md:pt-8 text-center text-sm font-renner relative z-10"
style={{
fontWeight: 300,
@ -286,7 +286,7 @@ export default function MobileFooter() {
<p className="text-xs md:text-sm">
&copy; 2024 Mozimo. All rights reserved.
</p>
</motion.div>
</m.div>
</div>
</footer>
);

View File

@ -1,5 +1,5 @@
import { useState, useCallback, useMemo, useEffect } from "react";
import { LazyMotion, domAnimation, m } from "framer-motion";
import { m } from "framer-motion";
import Section from "@/components/Section";
import { Image } from "@unpic/react";
@ -94,7 +94,7 @@ import { Image } from "@unpic/react";
return (
<Section background="white" id="categories">
<LazyMotion features={domAnimation}>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-0 items-stretch min-h-[400px] md:min-h-[500px]">
{/* Left */}
<div 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">
@ -123,7 +123,7 @@ import { Image } from "@unpic/react";
</div>
</div>
</div>
</LazyMotion>
</Section>
);
}

View File

@ -1,6 +1,6 @@
'use client';
import { motion } from 'framer-motion';
import { m } from 'framer-motion';
import { ReactNode } from 'react';
interface SectionProps {
@ -35,7 +35,7 @@ export default function Section({
return (
<section id={id} className={classes}>
<motion.div
<m.div
initial={{ opacity: 0, y: 60 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{
@ -50,7 +50,7 @@ export default function Section({
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-16 md:w-24 h-1 bg-gradient-to-r from-transparent via-[#8B4513]/30 to-transparent opacity-0 hover:opacity-100 transition-opacity duration-500" />
{children}
</motion.div>
</m.div>
</section>
);
}

View File

@ -1,5 +1,5 @@
import { useEffect, useRef } from "react";
import { motion } from "framer-motion";
import { m } from "framer-motion";
import { Image } from "@unpic/react";
import Section from "./Section";
import { useInstagram } from "@/hooks/useInstagram";
@ -60,7 +60,7 @@ export default function SocialMedia() {
return (
<Section background="cream" padding="md">
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -79,7 +79,7 @@ export default function SocialMedia() {
>
Follow us on Instagram
</h2>
</motion.div>
</m.div>
<div
ref={scrollRef}
@ -88,7 +88,7 @@ export default function SocialMedia() {
{loading ? (
<>
{[1, 2, 3].map((i) => (
<motion.div
<m.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 }}
@ -101,7 +101,7 @@ export default function SocialMedia() {
) : error ? (
isTokenExpired ? (
<div className="col-span-full text-center py-12 sm:col-span-2 lg:col-span-3">
<motion.div
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
@ -121,12 +121,12 @@ export default function SocialMedia() {
<div className="text-sm text-gray-500">
In the meantime, follow us @mozimo_chocolate
</div>
</motion.div>
</m.div>
</div>
) : (
<>
{/* Left Block - Chocolate Spread Jar */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -135,7 +135,7 @@ export default function SocialMedia() {
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
<m.div
className="text-center"
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.3 }}
@ -151,12 +151,12 @@ export default function SocialMedia() {
SINGLE ORIGIN HAZELNUT SPREAD 45%
</div>
</div>
</motion.div>
</m.div>
</div>
</motion.div>
</m.div>
{/* Middle Block - Magazine Article */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -191,10 +191,10 @@ export default function SocialMedia() {
</p>
</div>
</div>
</motion.div>
</m.div>
{/* Right Block - World Chocolate Day */}
<motion.div
<m.div
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: "-50px" }}
@ -218,12 +218,12 @@ export default function SocialMedia() {
</p>
</div>
</div>
</motion.div>
</m.div>
</>
)
) : (
posts?.slice(0, 3).map((post, index) => (
<motion.div
<m.div
key={post.id}
initial="initial"
whileInView="animate"
@ -273,7 +273,7 @@ export default function SocialMedia() {
</div>
</div>
</a>
</motion.div>
</m.div>
))
)}
</div>

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -13,15 +13,10 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}