Performance improvements and Dockerfile update
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,6 +19,7 @@
|
||||
|
||||
# production
|
||||
/build
|
||||
.output/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
53
Dockerfile
53
Dockerfile
@ -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"]
|
||||
21
Makefile
21
Makefile
@ -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 \
|
||||
-
|
||||
|
||||
@ -16,7 +16,7 @@ docker_image: "{{ registry }}/{{ title }}:{{ tag }}"
|
||||
docker_container: "{{ title }}"
|
||||
docker_port: 3000
|
||||
|
||||
instagram_token: IGQWRQSUY4b3RQWU9ZARHlVVUFDaWxJZAWFOUVllM0NxMk1zSHJ5X2JGc2dpRUxyTjBaeDNhUm0yaFZAQeUotVU9VUmFEQkJxU25CdFp3bURSZAGtnLXhCZAHVId21SWUNiNjVzc0pjZAlhPNDRxTDFzZAEQ0ZAXoyVlpUaHcZD
|
||||
instagram_token: IGQWRhYzlibzE4VEpNYzZA2eHEtRkVLUXJiT3NyVU9mNjlNRzM1cmd2NzY0SGxtX1ZAVVGVwb1dkbFl4VlVWaW02aFhEakQzeUp3NjBCSktRR2lQYlgwV3UwMG1seTFsWDBMSzZAHejNYdlE5RFMzU0tqbS1OVEttN0UZD
|
||||
|
||||
|
||||
caddy_container: caddy
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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'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'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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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'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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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%" }}
|
||||
|
||||
@ -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'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">
|
||||
© 2024 Mozimo. All rights reserved.
|
||||
</p>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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">
|
||||
© 2024 Mozimo. All rights reserved.
|
||||
</p>
|
||||
</motion.div>
|
||||
</m.div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user