Moved to tanstack start from nextjs using google antigravity
No files matched your search
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"context7": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@upstash/context7-mcp", "--api-key", "ctx7sk-200afd5d-1a75-4c28-a3fb-3f3a26a721a0"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: 'standalone',
|
||||
// optional hardening
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: '**',
|
||||
},
|
||||
],
|
||||
},
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
return config;
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -6,17 +6,22 @@
|
||||
"node": ">=18.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"start": "node .output/server/index.mjs",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-router": "^1.168.23",
|
||||
"@tanstack/react-start": "^1.167.42",
|
||||
"@unpic/react": "^1.0.2",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"framer-motion": "^12.23.9",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "^15.4.4",
|
||||
"nitro": "^3.0.260415-beta",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"react-dom": "^19.1.0",
|
||||
"vite": "^8.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
@@ -26,7 +31,6 @@
|
||||
"@types/react-dom": "^19",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.32.0",
|
||||
"eslint-config-next": "^15.4.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg width="1920" height="1090" viewBox="0 0 1920 1090" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect y="-69.8184" width="1920" height="1282.91" fill="url(#pattern0_2586_368)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_2586_368" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2586_368" transform="matrix(0.000244308 0 0 0.000365631 -0.00034235 0)"/>
|
||||
</pattern>
|
||||
<image id="image0_2586_368" width="4096" height="2735" preserveAspectRatio="none" xlink:href="data:image/jpeg;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.5 MiB |
@@ -1,14 +0,0 @@
|
||||
<svg width="1920" height="1090" viewBox="0 0 1920 1090" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<mask id="mask0_2586_561" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1920" height="1090">
|
||||
<rect width="1920" height="1090" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_2586_561)">
|
||||
<rect x="-20" width="1954" height="1110" fill="url(#pattern0_2586_561)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<pattern id="pattern0_2586_561" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2586_561" transform="scale(0.000568182 0.0010002)"/>
|
||||
</pattern>
|
||||
<image id="image0_2586_561" width="1760" height="1000" preserveAspectRatio="none" xlink:href="data:image/jpeg;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1918" height="1090" viewBox="0 0 1918 1090" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="1918" height="1090" fill="url(#pattern0_2586_581)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_2586_581" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2586_581" transform="scale(0.0005683 0.001)"/>
|
||||
</pattern>
|
||||
<image id="image0_2586_581" width="1760" height="1000" preserveAspectRatio="none" xlink:href="data:image/jpeg;base64,/9j/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////7gAhQWRvYmUAZEAAAAABAwAQAwIDBgAAAAAAAAAAAAAAAP/bAIQAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQIBAQEBAQECAgICAgICAgICAgICAgMDAwMDAwMDAwMDAwMDAwEBAQEBAQECAQECAwICAgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMD/8IAEQgD6AbgAwERAAIRAQMRAf/EAQwAAAEEAgMBAQAAAAAAAAAAAAcFBggJAwQBAgoACwEAAgIDAQEBAQAAAAAAAAAABAUDBgECBwAICQoQAAEEAgICAgIBAwQCAgECBwMBAgQFBgcACBESEwkUFSEQIhYgMRcKQSMyGDAkQBlQQjM0JSYRAAICAgEDAwMCBQIEBAEBGQECAwQRBQYAIRIxEwdBIhRRYXEyIxUIgUKRoSQWscFSM/DRYhcQ4UM0JSDxcoJTJjCSNQlEGKJAULLCGRIAAQMCBAMFBgUCBQQBAQATAQARAiEDMUFREmFxBPCBkSITobHB0TIFEOFCIxTxUiBichUGMIIzJJJAQ6LSNCWywlNjkxZQcwfi8oMmF//aAAwDAQECEQMRAAAAsF4F91zI31jCJY4ZUrq0brDX4VWGgwVpvSLine truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 MiB |
@@ -1,9 +0,0 @@
|
||||
<svg width="1839" height="1090" viewBox="0 0 1839 1090" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect x="-79" width="1918" height="1090" fill="url(#pattern0_2586_583)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_2586_583" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2586_583" transform="scale(0.0005683 0.001)"/>
|
||||
</pattern>
|
||||
<image id="image0_2586_583" width="1760" height="1000" preserveAspectRatio="none" xlink:href="data:image/jpeg;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1014 KiB |
|
After Width: | Height: | Size: 943 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,76 @@
|
||||
import { createRootRoute, Outlet, HeadContent, Scripts } from '@tanstack/react-router';
|
||||
import appCss from './globals.css?url';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ charSet: "utf-8" },
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes" },
|
||||
{ title: "Mozimo - India's Premier European Style Bean to Bar Chocolate Experience" },
|
||||
{ name: "description", content: "Mozimo's journey is a passionate pursuit of crafting exceptional chocolate that celebrates the origin of each cocoa bean. Experience the epitome of indulgence with our perfectly tempered chocolate." },
|
||||
{ name: "keywords", content: "chocolate, bean to bar, artisanal chocolate, premium chocolate, Indian chocolate, European style chocolate, cocoa beans, chocolate tempering" },
|
||||
{ name: "author", content: "Mozimo Chocolate" },
|
||||
{ name: "google-site-verification", content: "vV-fEo3yXs8m0NUhB6JWhYI4pSH0uYVkbT8pgUWuU1I" },
|
||||
|
||||
// OpenGraph
|
||||
{ property: "og:title", content: "Mozimo - Premium Artisanal Chocolate" },
|
||||
{ property: "og:description", content: "India's Premier European Style Bean to Bar Chocolate Experience" },
|
||||
{ property: "og:url", content: "https://mozimo.com" },
|
||||
{ property: "og:site_name", content: "Mozimo Chocolate" },
|
||||
{ property: "og:image", content: "/og-image.jpg" },
|
||||
{ property: "og:locale", content: "en_US" },
|
||||
{ property: "og:type", content: "website" },
|
||||
|
||||
// Twitter
|
||||
{ name: "twitter:card", content: "summary_large_image" },
|
||||
{ name: "twitter:title", content: "Mozimo - Premium Artisanal Chocolate" },
|
||||
{ name: "twitter:description", content: "India's Premier European Style Bean to Bar Chocolate Experience" },
|
||||
{ name: "twitter:image", content: "/og-image.jpg" },
|
||||
],
|
||||
links: [
|
||||
{ rel: 'stylesheet', href: appCss },
|
||||
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
|
||||
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: 'anonymous' },
|
||||
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Playfair+Display:wght@400;500;600;700;800;900&display=swap' },
|
||||
{ rel: 'icon', href: '/favicon.ico' },
|
||||
{ rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' },
|
||||
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
|
||||
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
|
||||
{ rel: 'manifest', href: '/site.webmanifest' },
|
||||
|
||||
// Preload critical images
|
||||
{ rel: 'preload', as: 'image', href: '/banners/slider-1.jpg' },
|
||||
{ rel: 'preload', as: 'image', href: '/logo/logo.svg' },
|
||||
{ rel: 'preload', as: 'image', href: '/bst/bs1.svg' },
|
||||
|
||||
// DNS prefetch
|
||||
{ rel: 'dns-prefetch', href: '//fonts.googleapis.com' },
|
||||
{ rel: 'dns-prefetch', href: '//fonts.gstatic.com' },
|
||||
],
|
||||
}),
|
||||
component: RootLayout,
|
||||
notFoundComponent: () => {
|
||||
return (
|
||||
<div>
|
||||
<p>Not found!</p>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
});
|
||||
|
||||
function RootLayout() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
{/* We apply a generic font-sans class or remove Next.js specific font variables.
|
||||
Since the Inter Google Font is loaded via the stylesheet above, it will automatically apply
|
||||
if configured in Tailwind CSS. */}
|
||||
<body>
|
||||
<Outlet />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
export const Route = createFileRoute('/about')({
|
||||
component: AboutPage,
|
||||
});
|
||||
|
||||
// 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] },
|
||||
};
|
||||
|
||||
const fadeInLeft = {
|
||||
initial: { opacity: 0, x: -30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const fadeInRight = {
|
||||
initial: { opacity: 0, x: 30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const staggerContainer = {
|
||||
animate: {
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function AboutPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="pt-32 md:pt-40 pb-16 md:pb-24">
|
||||
<div className="w-full">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center space-y-8"
|
||||
>
|
||||
<motion.h1
|
||||
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(48px, 8vw, 80px)",
|
||||
lineHeight: "90%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Welcome to the World of Mozimo Magic
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
className="w-full px-8 md:px-16 lg:px-24"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p
|
||||
className="text-lg md:text-xl lg:text-2xl text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(18px, 3vw, 24px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
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>
|
||||
|
||||
{/* Hero Image */}
|
||||
<motion.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
|
||||
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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/0.jpg"
|
||||
alt="Mozimo chocolate hero image"
|
||||
layout="fullWidth"
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Our Founders Section */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<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
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInLeft}
|
||||
className="order-2 lg:order-1"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/1.jpg"
|
||||
alt="Priyanka and Amritanshu, founders of Mozimo, sitting in their chocolate shop"
|
||||
layout="fullWidth"
|
||||
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>
|
||||
|
||||
{/* Founders Text */}
|
||||
<motion.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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Our Founders
|
||||
</motion.h2>
|
||||
|
||||
<motion.h3
|
||||
className="text-2xl md:text-3xl font-semibold text-[#8B4513] font-renner"
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontSize: "clamp(20px, 3vw, 28px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Priyanka and Amritanshu.
|
||||
</motion.h3>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Capturing the Essence of Italian Chocolate Section */}
|
||||
<section className="py-16 md:py-24 bg-[#F5E6D3]">
|
||||
<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
|
||||
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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21]"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Capturing the essence of
|
||||
</motion.h2>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "Samantha Signature",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Italian Chocolate
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
|
||||
{/* Cocoa Farm Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="order-1 lg:order-2"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/2.jpg"
|
||||
alt="Cocoa beans drying in open-air structure with farmers tending to the beans"
|
||||
layout="fullWidth"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Chocolate Making Process Section */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<div className="w-full">
|
||||
{/* Two Images Side by Side */}
|
||||
<motion.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
|
||||
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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/3.jpg"
|
||||
alt="Chocolatier holding cocoa nibs with chocolate pieces and beans arranged artistically"
|
||||
layout="fullWidth"
|
||||
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
|
||||
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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/4.jpg"
|
||||
alt="Thick chocolate bars with whole hazelnuts on chocolate shavings"
|
||||
layout="fullWidth"
|
||||
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>
|
||||
|
||||
{/* Text Content */}
|
||||
<motion.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
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
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>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Vision and Passion Section */}
|
||||
<section className="py-16 md:py-24 bg-[#F5E6D3]">
|
||||
<div className="w-full">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center space-y-8 md:space-y-12"
|
||||
>
|
||||
{/* Founders Outdoor Image */}
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/5.jpg"
|
||||
alt="Priyanka and Amritanshu in white chef coats with modern tree sculpture in background"
|
||||
layout="fullWidth"
|
||||
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>
|
||||
|
||||
{/* Vision and Passion Text */}
|
||||
<motion.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 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,428 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import Image from "next/image";
|
||||
|
||||
// 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] },
|
||||
};
|
||||
|
||||
const fadeInLeft = {
|
||||
initial: { opacity: 0, x: -30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const fadeInRight = {
|
||||
initial: { opacity: 0, x: 30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const staggerContainer = {
|
||||
animate: {
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="pt-32 md:pt-40 pb-16 md:pb-24">
|
||||
<div className="w-full">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center space-y-8"
|
||||
>
|
||||
<motion.h1
|
||||
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(48px, 8vw, 80px)",
|
||||
lineHeight: "90%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Welcome to the World of Mozimo Magic
|
||||
</motion.h1>
|
||||
|
||||
<motion.div
|
||||
className="w-full px-8 md:px-16 lg:px-24"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p
|
||||
className="text-lg md:text-xl lg:text-2xl text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(18px, 3vw, 24px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
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>
|
||||
|
||||
{/* Hero Image */}
|
||||
<motion.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
|
||||
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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/0.jpg"
|
||||
alt="Mozimo chocolate hero image"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/10 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Our Founders Section */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<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
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInLeft}
|
||||
className="order-2 lg:order-1"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/1.jpg"
|
||||
alt="Priyanka and Amritanshu, founders of Mozimo, sitting in their chocolate shop"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Founders Text */}
|
||||
<motion.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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Our Founders
|
||||
</motion.h2>
|
||||
|
||||
<motion.h3
|
||||
className="text-2xl md:text-3xl font-semibold text-[#8B4513] font-renner"
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
fontSize: "clamp(20px, 3vw, 28px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Priyanka and Amritanshu.
|
||||
</motion.h3>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Capturing the Essence of Italian Chocolate Section */}
|
||||
<section className="py-16 md:py-24 bg-[#F5E6D3]">
|
||||
<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
|
||||
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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21]"
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Capturing the essence of
|
||||
</motion.h2>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-samantha"
|
||||
style={{
|
||||
fontFamily: "Samantha Signature",
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 48px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Italian Chocolate
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
|
||||
{/* Cocoa Farm Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="order-1 lg:order-2"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/2.jpg"
|
||||
alt="Cocoa beans drying in open-air structure with farmers tending to the beans"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Chocolate Making Process Section */}
|
||||
<section className="py-16 md:py-24 bg-white">
|
||||
<div className="w-full">
|
||||
{/* Two Images Side by Side */}
|
||||
<motion.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
|
||||
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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/3.jpg"
|
||||
alt="Chocolatier holding cocoa nibs with chocolate pieces and beans arranged artistically"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/4.jpg"
|
||||
alt="Thick chocolate bars with whole hazelnuts on chocolate shavings"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Text Content */}
|
||||
<motion.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
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
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>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Vision and Passion Section */}
|
||||
<section className="py-16 md:py-24 bg-[#F5E6D3]">
|
||||
<div className="w-full">
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-center space-y-8 md:space-y-12"
|
||||
>
|
||||
{/* Founders Outdoor Image */}
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/aboutus/5.jpg"
|
||||
alt="Priyanka and Amritanshu in white chef coats with modern tree sculpture in background"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
quality={90}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
|
||||
{/* Vision and Passion Text */}
|
||||
<motion.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 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
// Instagram Basic Display API configuration
|
||||
const INSTAGRAM_ACCESS_TOKEN = process.env.INSTAGRAM_ACCESS_TOKEN || 'IGQWRQSUY4b3RQWU9ZARHlVVUFDaWxJZAWFOUVllM0NxMk1zSHJ5X2JGc2dpRUxyTjBaeDNhUm0yaFZAQeUotVU9VUmFEQkJxU25CdFp3bURSZAGtnLXhCZAHVId21SWUNiNjVzc0pjZAlhPNDRxTDFzZAEQ0ZAXoyVlpUaHcZD';
|
||||
|
||||
interface InstagramPostData {
|
||||
id: string;
|
||||
caption?: string;
|
||||
media_type: string;
|
||||
media_url?: string;
|
||||
thumbnail_url?: string;
|
||||
permalink: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/api/instagram')({
|
||||
server: {
|
||||
handlers: {
|
||||
GET: async () => {
|
||||
try {
|
||||
if (!INSTAGRAM_ACCESS_TOKEN) {
|
||||
return Response.json(
|
||||
{ error: 'Instagram access token not configured' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// First, get the user ID using the access token
|
||||
const userResponse = await fetch(
|
||||
`https://graph.instagram.com/me?fields=id,username&access_token=${INSTAGRAM_ACCESS_TOKEN}`
|
||||
);
|
||||
|
||||
if (!userResponse.ok) {
|
||||
const errorText = await userResponse.text();
|
||||
console.error('Failed to fetch user info:', errorText);
|
||||
|
||||
// Check if token is expired
|
||||
if (errorText.includes('Session has expired') || errorText.includes('code":190')) {
|
||||
return Response.json(
|
||||
{
|
||||
error: 'Instagram access token has expired',
|
||||
details: 'The access token needs to be refreshed. Please generate a new token from Instagram Developer Console.',
|
||||
code: 'TOKEN_EXPIRED'
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error('Failed to authenticate with Instagram');
|
||||
}
|
||||
|
||||
const userData = await userResponse.json();
|
||||
console.log('Instagram user data:', userData);
|
||||
|
||||
// Fetch user's media (posts)
|
||||
const mediaResponse = await fetch(
|
||||
`https://graph.instagram.com/me/media?fields=id,caption,media_type,media_url,thumbnail_url,permalink,timestamp&access_token=${INSTAGRAM_ACCESS_TOKEN}&limit=6`
|
||||
);
|
||||
|
||||
if (!mediaResponse.ok) {
|
||||
const errorText = await mediaResponse.text();
|
||||
console.error('Failed to fetch media:', errorText);
|
||||
|
||||
// Check if token is expired
|
||||
if (errorText.includes('Session has expired') || errorText.includes('code":190')) {
|
||||
return Response.json(
|
||||
{
|
||||
error: 'Instagram access token has expired',
|
||||
details: 'The access token needs to be refreshed. Please generate a new token from Instagram Developer Console.',
|
||||
code: 'TOKEN_EXPIRED'
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch Instagram posts');
|
||||
}
|
||||
|
||||
const mediaData = await mediaResponse.json();
|
||||
console.log('Instagram media data:', mediaData);
|
||||
|
||||
// Process and format the posts
|
||||
const posts = mediaData.data.map((post: InstagramPostData) => ({
|
||||
id: post.id,
|
||||
caption: post.caption || '',
|
||||
mediaUrl: post.media_type === 'VIDEO' ? (post.thumbnail_url || post.media_url) : post.media_url,
|
||||
permalink: post.permalink,
|
||||
mediaType: post.media_type,
|
||||
timestamp: post.timestamp
|
||||
}));
|
||||
|
||||
return Response.json({ posts });
|
||||
} catch (error) {
|
||||
console.error('Instagram API Error:', error);
|
||||
return Response.json(
|
||||
{ error: 'Failed to fetch Instagram posts', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,97 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
// Instagram Basic Display API configuration
|
||||
const INSTAGRAM_ACCESS_TOKEN = process.env.INSTAGRAM_ACCESS_TOKEN || 'IGQWRQSUY4b3RQWU9ZARHlVVUFDaWxJZAWFOUVllM0NxMk1zSHJ5X2JGc2dpRUxyTjBaeDNhUm0yaFZAQeUotVU9VUmFEQkJxU25CdFp3bURSZAGtnLXhCZAHVId21SWUNiNjVzc0pjZAlhPNDRxTDFzZAEQ0ZAXoyVlpUaHcZD';
|
||||
|
||||
interface InstagramPostData {
|
||||
id: string;
|
||||
caption?: string;
|
||||
media_type: string;
|
||||
media_url?: string;
|
||||
thumbnail_url?: string;
|
||||
permalink: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
if (!INSTAGRAM_ACCESS_TOKEN) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Instagram access token not configured' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// First, get the user ID using the access token
|
||||
const userResponse = await fetch(
|
||||
`https://graph.instagram.com/me?fields=id,username&access_token=${INSTAGRAM_ACCESS_TOKEN}`
|
||||
);
|
||||
|
||||
if (!userResponse.ok) {
|
||||
const errorText = await userResponse.text();
|
||||
console.error('Failed to fetch user info:', errorText);
|
||||
|
||||
// Check if token is expired
|
||||
if (errorText.includes('Session has expired') || errorText.includes('code":190')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Instagram access token has expired',
|
||||
details: 'The access token needs to be refreshed. Please generate a new token from Instagram Developer Console.',
|
||||
code: 'TOKEN_EXPIRED'
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error('Failed to authenticate with Instagram');
|
||||
}
|
||||
|
||||
const userData = await userResponse.json();
|
||||
console.log('Instagram user data:', userData);
|
||||
|
||||
// Fetch user's media (posts)
|
||||
const mediaResponse = await fetch(
|
||||
`https://graph.instagram.com/me/media?fields=id,caption,media_type,media_url,thumbnail_url,permalink,timestamp&access_token=${INSTAGRAM_ACCESS_TOKEN}&limit=6`
|
||||
);
|
||||
|
||||
if (!mediaResponse.ok) {
|
||||
const errorText = await mediaResponse.text();
|
||||
console.error('Failed to fetch media:', errorText);
|
||||
|
||||
// Check if token is expired
|
||||
if (errorText.includes('Session has expired') || errorText.includes('code":190')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Instagram access token has expired',
|
||||
details: 'The access token needs to be refreshed. Please generate a new token from Instagram Developer Console.',
|
||||
code: 'TOKEN_EXPIRED'
|
||||
},
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch Instagram posts');
|
||||
}
|
||||
|
||||
const mediaData = await mediaResponse.json();
|
||||
console.log('Instagram media data:', mediaData);
|
||||
|
||||
// Process and format the posts
|
||||
const posts = mediaData.data.map((post: InstagramPostData) => ({
|
||||
id: post.id,
|
||||
caption: post.caption || '',
|
||||
mediaUrl: post.media_type === 'VIDEO' ? (post.thumbnail_url || post.media_url) : post.media_url,
|
||||
permalink: post.permalink,
|
||||
mediaType: post.media_type,
|
||||
timestamp: post.timestamp
|
||||
}));
|
||||
|
||||
return NextResponse.json({ posts });
|
||||
} catch (error) {
|
||||
console.error('Instagram API Error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch Instagram posts', details: error instanceof Error ? error.message : 'Unknown error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { motion } from "framer-motion";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
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";
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
// 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] },
|
||||
};
|
||||
|
||||
const fadeInLeft = {
|
||||
initial: { opacity: 0, x: -30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const fadeInRight = {
|
||||
initial: { opacity: 0, x: 30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const staggerContainer = {
|
||||
animate: {
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const categories = [
|
||||
"Bars",
|
||||
"Barks",
|
||||
"Pralines",
|
||||
"Spreads",
|
||||
"Dragees",
|
||||
"Gelatos",
|
||||
];
|
||||
|
||||
const partnerImages = [
|
||||
"/partners/p1.svg",
|
||||
"/partners/p2.svg",
|
||||
"/partners/p3.svg",
|
||||
"/partners/p4.svg",
|
||||
];
|
||||
|
||||
function Home() {
|
||||
const [currentCategory, setCurrentCategory] = useState(0);
|
||||
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
||||
const currentCategoryRef = useRef(currentCategory);
|
||||
|
||||
// Update ref when currentCategory changes
|
||||
useEffect(() => {
|
||||
currentCategoryRef.current = currentCategory;
|
||||
}, [currentCategory]);
|
||||
|
||||
// Optimized wheel event handler
|
||||
const handleWheel = useCallback((e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Determine scroll direction
|
||||
if (e.deltaY > 0) {
|
||||
// Scroll down - next category
|
||||
if (currentCategoryRef.current < categories.length - 1) {
|
||||
setCurrentCategory((prev) => prev + 1);
|
||||
}
|
||||
} else if (e.deltaY < 0) {
|
||||
// Scroll up - previous category
|
||||
if (currentCategoryRef.current > 0) {
|
||||
setCurrentCategory((prev) => prev - 1);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Wheel event for scroll-based category switching
|
||||
useEffect(() => {
|
||||
const section = categoriesSectionRef.current;
|
||||
if (!section) return;
|
||||
|
||||
let isScrolling = false;
|
||||
let scrollTimeout: NodeJS.Timeout;
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
if (isScrolling) return;
|
||||
|
||||
isScrolling = true;
|
||||
clearTimeout(scrollTimeout);
|
||||
|
||||
handleWheel(e);
|
||||
|
||||
// Reset scrolling flag after delay
|
||||
scrollTimeout = setTimeout(() => {
|
||||
isScrolling = false;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
section.addEventListener("wheel", onWheel, { passive: false });
|
||||
return () => {
|
||||
section.removeEventListener("wheel", onWheel);
|
||||
clearTimeout(scrollTimeout);
|
||||
};
|
||||
}, [handleWheel]);
|
||||
|
||||
const [spotlightIndex, setSpotlightIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setSpotlightIndex((prev) => (prev + 1) % partnerImages.length);
|
||||
}, 2500);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// Memoize expensive components
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
<BannerSlider />
|
||||
|
||||
{/* Brand Story */}
|
||||
|
||||
<Section background="white" id="about">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 items-center">
|
||||
{/* Brand Story - Left Text */}
|
||||
<motion.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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-moneta text-gradient p-4 md:p-0"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 58px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Brand Story
|
||||
</motion.h2>
|
||||
<div
|
||||
className="space-y-4 md:space-y-6 text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Mozimo's journey is a passionate pursuit of crafting
|
||||
exceptional chocolate that celebrates the origin of each cocoa
|
||||
bean.
|
||||
</motion.p>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
We meticulously roast, crack, winnow, and refine beans in-house,
|
||||
using modern techniques to highlight their natural flavors.
|
||||
</motion.p>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Each chocolate is a masterpiece, capturing the essence of cocoa
|
||||
in its purest form.
|
||||
</motion.p>
|
||||
</div>
|
||||
<motion.div
|
||||
className="pt-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<Button variant="white" size="lg" className="btn-premium">
|
||||
Discover more
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
{/* Brand Story - Right Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="relative order-1 lg:order-2 w-screen md:w-auto -mx-4 md:mx-0"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/bst/bs1.svg"
|
||||
alt="Cocoa pods and beans - Mozimo chocolate ingredients"
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-cover img-premium"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Chocolate Tempering - Left Video */}
|
||||
<motion.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
|
||||
className="relative w-full h-[400px] md:h-[500px] lg:h-[700px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500 clip-diagonal-top-right"
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
|
||||
>
|
||||
<video
|
||||
src="https://qa-dugout.s3.ap-south-1.amazonaws.com/testing/v1.mp4"
|
||||
className="w-full h-full object-cover"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
preload="auto"
|
||||
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>
|
||||
|
||||
{/* Chocolate Tempering - Right Text */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="space-y-4 md:space-y-6 order-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
|
||||
className="text-2xl md:text-3xl lg:text-4xl font-bold text-[#3C2A21] font-renner text-gradient"
|
||||
style={{
|
||||
fontWeight: 200,
|
||||
fontSize: "clamp(24px, 4vw, 40px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Discover the delicate art of our
|
||||
</motion.h2>
|
||||
<motion.h3
|
||||
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#8B4513] font-samantha"
|
||||
style={{
|
||||
fontFamily: "Samantha Signature",
|
||||
fontWeight: 400,
|
||||
fontStyle: "normal",
|
||||
fontSize: "clamp(35px, 8vw, 50px)",
|
||||
lineHeight: "clamp(20px, 4vw, 25px)",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Chocolate Tempering
|
||||
</motion.h3>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="w-100 h-px bg-gradient-to-r from-transparent via-[#8B4513] to-transparent"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Crafted to perfection Mozimo delivers a sublime sensory experience
|
||||
with every bite. Experience the epitome of indulgence with our
|
||||
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>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Our Collections Section */}
|
||||
<Section background="cream" id="shop">
|
||||
<motion.h2
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-2xl md:text-3xl lg:text-4xl font-bold text-[#3C2A21] text-center mb-8 md:mb-12 font-renner text-gradient"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(24px, 4vw, 36px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Our Collections
|
||||
</motion.h2>
|
||||
|
||||
<motion.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"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
>
|
||||
{[
|
||||
{
|
||||
src: "/collection/cl1.svg",
|
||||
title: "Bestsellers",
|
||||
alt: "Bestsellers - Mozimo chocolate bars with geometric patterns",
|
||||
},
|
||||
{
|
||||
src: "/collection/cl2.svg",
|
||||
title: "New Arrivals",
|
||||
alt: "New Arrivals - Stacked chocolate barks with nuts and toppings",
|
||||
},
|
||||
{
|
||||
src: "/collection/cl3.svg",
|
||||
title: "Gift Collection",
|
||||
alt: "Gift Collection - Mozimo packaged chocolate products and gift items",
|
||||
},
|
||||
].map((item) => (
|
||||
<motion.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"
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
// style={{ minHeight: "480px" }}
|
||||
>
|
||||
<Image
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
width={320}
|
||||
height={400}
|
||||
layout="constrained"
|
||||
className="object-contain img-premium group-hover:scale-105 transition-transform duration-500"
|
||||
draggable={false}
|
||||
style={{ maxHeight: "380px", width: "100%" }}
|
||||
/>
|
||||
|
||||
<div className="p-4 md:p-6 text-center mb-2 w-full">
|
||||
<h3
|
||||
className="text-lg md:text-xl font-semibold text-[#3C2A21] font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 20px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</Section>
|
||||
|
||||
<ProductCategory />
|
||||
{/* Social Media Call to Action */}
|
||||
<SocialMedia />
|
||||
|
||||
{/* In the Spotlight Section */}
|
||||
<Section background="white">
|
||||
<motion.h2
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-moneta text-[#703133] mb-6 md:mb-8 text-center text-gradient"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(28px, 5vw, 48px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
In the Spotlight
|
||||
</motion.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
|
||||
className="flex gap-8 py-6"
|
||||
animate={{ x: `-${spotlightIndex * 152}px` }}
|
||||
transition={{ type: "spring", stiffness: 80, damping: 18 }}
|
||||
>
|
||||
{Array(12)
|
||||
.fill(0)
|
||||
.map((_, i) => {
|
||||
const imgIdx = i % partnerImages.length;
|
||||
const img = partnerImages[imgIdx];
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-24 h-24 md:w-32 md:h-32 lg:w-36 lg:h-36 bg-white rounded-full flex items-center justify-center shadow-premium hover:shadow-premium-hover flex-shrink-0 transition-all duration-300"
|
||||
style={{
|
||||
boxShadow: "0 4px 20px 0 rgba(60,42,33,0.15)",
|
||||
margin: "20px 8px",
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
rotate: 5,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={img}
|
||||
alt={`Partner ${imgIdx + 1}`}
|
||||
width={120}
|
||||
height={120}
|
||||
layout="constrained"
|
||||
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>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Mozimo - India's Premier European Style Bean to Bar Chocolate Experience",
|
||||
description: "Mozimo's journey is a passionate pursuit of crafting exceptional chocolate that celebrates the origin of each cocoa bean. Experience the epitome of indulgence with our perfectly tempered chocolate.",
|
||||
keywords: "chocolate, bean to bar, artisanal chocolate, premium chocolate, Indian chocolate, European style chocolate, cocoa beans, chocolate tempering",
|
||||
authors: [{ name: "Mozimo Chocolate" }],
|
||||
creator: "Mozimo Chocolate",
|
||||
publisher: "Mozimo Chocolate",
|
||||
formatDetection: {
|
||||
email: false,
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
metadataBase: new URL('https://mozimo.com'),
|
||||
openGraph: {
|
||||
title: "Mozimo - Premium Artisanal Chocolate",
|
||||
description: "India's Premier European Style Bean to Bar Chocolate Experience",
|
||||
url: 'https://mozimo.com',
|
||||
siteName: 'Mozimo Chocolate',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.jpg',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Mozimo Chocolate - Premium Artisanal Chocolate',
|
||||
},
|
||||
],
|
||||
locale: 'en_US',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: "Mozimo - Premium Artisanal Chocolate",
|
||||
description: "India's Premier European Style Bean to Bar Chocolate Experience",
|
||||
images: ['/og-image.jpg'],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
verification: {
|
||||
google: 'your-google-verification-code',
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* Mobile viewport meta tag */}
|
||||
<meta name="google-site-verification" content="vV-fEo3yXs8m0NUhB6JWhYI4pSH0uYVkbT8pgUWuU1I" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Playfair+Display:wght@400;500;600;700;800;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
{/* Preload critical images */}
|
||||
<link rel="preload" as="image" href="/banners/b1.svg" />
|
||||
<link rel="preload" as="image" href="/logo/logo.svg" />
|
||||
<link rel="preload" as="image" href="/bst/bs1.svg" />
|
||||
|
||||
{/* DNS prefetch for external resources */}
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
</head>
|
||||
<body className={inter.className}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,486 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import Section from "@/components/Section";
|
||||
import Button from "@/components/Button";
|
||||
import BannerSlider from "@/components/BannerSlider";
|
||||
import Image from "next/image";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import ProductCategory from "@/components/ProductCategory";
|
||||
import SocialMedia from "@/components/SocialMedia";
|
||||
|
||||
// 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] },
|
||||
};
|
||||
|
||||
const fadeInLeft = {
|
||||
initial: { opacity: 0, x: -30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const fadeInRight = {
|
||||
initial: { opacity: 0, x: 30 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
};
|
||||
|
||||
const staggerContainer = {
|
||||
animate: {
|
||||
transition: {
|
||||
staggerChildren: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const categories = [
|
||||
"Bars",
|
||||
"Barks",
|
||||
"Pralines",
|
||||
"Spreads",
|
||||
"Dragees",
|
||||
"Gelatos",
|
||||
];
|
||||
|
||||
const partnerImages = [
|
||||
"/partners/p1.svg",
|
||||
"/partners/p2.svg",
|
||||
"/partners/p3.svg",
|
||||
"/partners/p4.svg",
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
const [currentCategory, setCurrentCategory] = useState(0);
|
||||
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
||||
const currentCategoryRef = useRef(currentCategory);
|
||||
|
||||
// Update ref when currentCategory changes
|
||||
useEffect(() => {
|
||||
currentCategoryRef.current = currentCategory;
|
||||
}, [currentCategory]);
|
||||
|
||||
// Optimized wheel event handler
|
||||
const handleWheel = useCallback((e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Determine scroll direction
|
||||
if (e.deltaY > 0) {
|
||||
// Scroll down - next category
|
||||
if (currentCategoryRef.current < categories.length - 1) {
|
||||
setCurrentCategory((prev) => prev + 1);
|
||||
}
|
||||
} else if (e.deltaY < 0) {
|
||||
// Scroll up - previous category
|
||||
if (currentCategoryRef.current > 0) {
|
||||
setCurrentCategory((prev) => prev - 1);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Wheel event for scroll-based category switching
|
||||
useEffect(() => {
|
||||
const section = categoriesSectionRef.current;
|
||||
if (!section) return;
|
||||
|
||||
let isScrolling = false;
|
||||
let scrollTimeout: NodeJS.Timeout;
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
if (isScrolling) return;
|
||||
|
||||
isScrolling = true;
|
||||
clearTimeout(scrollTimeout);
|
||||
|
||||
handleWheel(e);
|
||||
|
||||
// Reset scrolling flag after delay
|
||||
scrollTimeout = setTimeout(() => {
|
||||
isScrolling = false;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
section.addEventListener("wheel", onWheel, { passive: false });
|
||||
return () => {
|
||||
section.removeEventListener("wheel", onWheel);
|
||||
clearTimeout(scrollTimeout);
|
||||
};
|
||||
}, [handleWheel]);
|
||||
|
||||
const [spotlightIndex, setSpotlightIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setSpotlightIndex((prev) => (prev + 1) % partnerImages.length);
|
||||
}, 2500);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// Memoize expensive components
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white font-renner">
|
||||
<Header />
|
||||
<BannerSlider />
|
||||
|
||||
{/* Brand Story */}
|
||||
|
||||
<Section background="white" id="about">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 items-center">
|
||||
{/* Brand Story - Left Text */}
|
||||
<motion.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
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-[#3C2A21] font-moneta text-gradient p-4 md:p-0"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(32px, 5vw, 58px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Brand Story
|
||||
</motion.h2>
|
||||
<div
|
||||
className="space-y-4 md:space-y-6 text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Mozimo's journey is a passionate pursuit of crafting
|
||||
exceptional chocolate that celebrates the origin of each cocoa
|
||||
bean.
|
||||
</motion.p>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
We meticulously roast, crack, winnow, and refine beans in-house,
|
||||
using modern techniques to highlight their natural flavors.
|
||||
</motion.p>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Each chocolate is a masterpiece, capturing the essence of cocoa
|
||||
in its purest form.
|
||||
</motion.p>
|
||||
</div>
|
||||
<motion.div
|
||||
className="pt-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<Button variant="white" size="lg" className="btn-premium">
|
||||
Discover more
|
||||
</Button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
{/* Brand Story - Right Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="relative order-1 lg:order-2 w-screen md:w-auto -mx-4 md:mx-0"
|
||||
>
|
||||
<motion.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 } }}
|
||||
>
|
||||
<Image
|
||||
src="/bst/bs1.svg"
|
||||
alt="Cocoa pods and beans - Mozimo chocolate ingredients"
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Chocolate Tempering - Left Video */}
|
||||
<motion.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
|
||||
className="relative w-full h-[400px] md:h-[500px] lg:h-[700px] overflow-hidden shadow-premium hover:shadow-premium-hover transition-all duration-500 clip-diagonal-top-right"
|
||||
whileHover={{ scale: 1.02, transition: { duration: 0.3 } }}
|
||||
>
|
||||
<video
|
||||
src="https://qa-dugout.s3.ap-south-1.amazonaws.com/testing/v1.mp4"
|
||||
className="w-full h-full object-cover"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
preload="auto"
|
||||
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>
|
||||
|
||||
{/* Chocolate Tempering - Right Text */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInRight}
|
||||
className="space-y-4 md:space-y-6 order-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
|
||||
className="text-2xl md:text-3xl lg:text-4xl font-bold text-[#3C2A21] font-renner text-gradient"
|
||||
style={{
|
||||
fontWeight: 200,
|
||||
fontSize: "clamp(24px, 4vw, 40px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Discover the delicate art of our
|
||||
</motion.h2>
|
||||
<motion.h3
|
||||
className="text-4xl md:text-5xl lg:text-7xl font-bold text-[#8B4513] font-samantha"
|
||||
style={{
|
||||
fontFamily: "Samantha Signature",
|
||||
fontWeight: 400,
|
||||
fontStyle: "normal",
|
||||
fontSize: "clamp(35px, 8vw, 50px)",
|
||||
lineHeight: "clamp(20px, 4vw, 25px)",
|
||||
letterSpacing: "0%",
|
||||
color: "#703133",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Chocolate Tempering
|
||||
</motion.h3>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="w-100 h-px bg-gradient-to-r from-transparent via-[#8B4513] to-transparent"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileInView={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
|
||||
<motion.p
|
||||
className="text-gray-700 leading-relaxed font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 18px)",
|
||||
lineHeight: "160%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
Crafted to perfection Mozimo delivers a sublime sensory experience
|
||||
with every bite. Experience the epitome of indulgence with our
|
||||
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>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Our Collections Section */}
|
||||
<Section background="cream" id="shop">
|
||||
<motion.h2
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-2xl md:text-3xl lg:text-4xl font-bold text-[#3C2A21] text-center mb-8 md:mb-12 font-renner text-gradient"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(24px, 4vw, 36px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
Our Collections
|
||||
</motion.h2>
|
||||
|
||||
<motion.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"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
>
|
||||
{[
|
||||
{
|
||||
src: "/collection/cl1.svg",
|
||||
title: "Bestsellers",
|
||||
alt: "Bestsellers - Mozimo chocolate bars with geometric patterns",
|
||||
},
|
||||
{
|
||||
src: "/collection/cl2.svg",
|
||||
title: "New Arrivals",
|
||||
alt: "New Arrivals - Stacked chocolate barks with nuts and toppings",
|
||||
},
|
||||
{
|
||||
src: "/collection/cl3.svg",
|
||||
title: "Gift Collection",
|
||||
alt: "Gift Collection - Mozimo packaged chocolate products and gift items",
|
||||
},
|
||||
].map((item) => (
|
||||
<motion.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"
|
||||
whileHover={{
|
||||
scale: 1.02,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
// style={{ minHeight: "480px" }}
|
||||
>
|
||||
<Image
|
||||
src={item.src}
|
||||
alt={item.alt}
|
||||
width={320}
|
||||
height={400}
|
||||
className="object-contain img-premium group-hover:scale-105 transition-transform duration-500"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
style={{ maxHeight: "380px", width: "100%" }}
|
||||
/>
|
||||
|
||||
<div className="p-4 md:p-6 text-center mb-2 w-full">
|
||||
<h3
|
||||
className="text-lg md:text-xl font-semibold text-[#3C2A21] font-renner"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(16px, 2.5vw, 20px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "Renner*",
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</Section>
|
||||
|
||||
<ProductCategory />
|
||||
{/* Social Media Call to Action */}
|
||||
<SocialMedia />
|
||||
|
||||
{/* In the Spotlight Section */}
|
||||
<Section background="white">
|
||||
<motion.h2
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={fadeInUp}
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-moneta text-[#703133] mb-6 md:mb-8 text-center text-gradient"
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: "clamp(28px, 5vw, 48px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
}}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
In the Spotlight
|
||||
</motion.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
|
||||
className="flex gap-8 py-6"
|
||||
animate={{ x: `-${spotlightIndex * 152}px` }}
|
||||
transition={{ type: "spring", stiffness: 80, damping: 18 }}
|
||||
>
|
||||
{Array(12)
|
||||
.fill(0)
|
||||
.map((_, i) => {
|
||||
const imgIdx = i % partnerImages.length;
|
||||
const img = partnerImages[imgIdx];
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-24 h-24 md:w-32 md:h-32 lg:w-36 lg:h-36 bg-white rounded-full flex items-center justify-center shadow-premium hover:shadow-premium-hover flex-shrink-0 transition-all duration-300"
|
||||
style={{
|
||||
boxShadow: "0 4px 20px 0 rgba(60,42,33,0.15)",
|
||||
margin: "20px 8px",
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.1,
|
||||
rotate: 5,
|
||||
transition: { duration: 0.3 },
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={img}
|
||||
alt={`Partner ${imgIdx + 1}`}
|
||||
width={120}
|
||||
height={120}
|
||||
className="w-16 h-16 md:w-20 md:h-20 lg:w-20 lg:h-20 object-contain img-premium"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
const banners = [
|
||||
{ id: 1, src: "/banners/b1.svg", alt: "Mozimo Chocolate Banner 1" },
|
||||
{ id: 2, src: "/banners/b3.svg", alt: "Mozimo Chocolate Banner 2" },
|
||||
{ id: 3, src: "/banners/b2.svg", alt: "Mozimo Chocolate Banner 3" },
|
||||
{ id: 4, src: "/banners/b4.svg", alt: "Mozimo Chocolate Banner 4" },
|
||||
{ id: 1, src: "/banners/slider-5.jpg", alt: "Macha Collection" },
|
||||
{ id: 2, src: "/banners/slider-1.jpg", alt: "Mozimo Chocolate Banner 1" },
|
||||
{ id: 3, src: "/banners/slider-3.jpg", alt: "Mozimo Chocolate Banner 2" },
|
||||
{ id: 4, src: "/banners/slider-2.jpg", alt: "Mozimo Chocolate Banner 3" },
|
||||
{ id: 5, src: "/banners/slider-4.jpg", alt: "Mozimo Chocolate Banner 4" },
|
||||
];
|
||||
|
||||
export default function BannerSlider() {
|
||||
@@ -124,13 +123,9 @@ export default function BannerSlider() {
|
||||
<Image
|
||||
src={banners[currentIndex].src}
|
||||
alt={banners[currentIndex].alt}
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-cover img-premium"
|
||||
draggable={false}
|
||||
priority={currentIndex === 0}
|
||||
loading={currentIndex === 0 ? "eager" : "lazy"}
|
||||
sizes="100vw"
|
||||
quality={85}
|
||||
/>
|
||||
|
||||
{/* Premium overlay with gradient */}
|
||||
|
||||
@@ -1,76 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { ReactNode } from 'react';
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
children: ReactNode;
|
||||
variant?: 'primary' | 'secondary' | 'white';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
variant?: "primary" | "secondary" | "white";
|
||||
size?: "sm" | "md" | "lg";
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
onClick?: () => void; // if provided, this forces client usage by the parent
|
||||
href?: string;
|
||||
target?: string;
|
||||
rel?: string;
|
||||
type?: "button" | "submit" | "reset";
|
||||
}
|
||||
|
||||
export default function Button({
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
className = '',
|
||||
export default function Button({
|
||||
children,
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
className = "",
|
||||
onClick,
|
||||
href
|
||||
href,
|
||||
target,
|
||||
rel,
|
||||
type = "button",
|
||||
}: ButtonProps) {
|
||||
const baseClasses = 'font-renner rounded-full transition-all duration-500 inline-flex items-center justify-center relative overflow-hidden shadow-premium hover:shadow-premium-hover';
|
||||
|
||||
const variantClasses = {
|
||||
primary: 'bg-gradient-to-r from-[#8B4513] to-[#A0522D] text-white hover:from-[#3C2A21] hover:to-[#8B4513] shadow-glow hover:shadow-glow',
|
||||
secondary: 'bg-gradient-to-r from-[#DAA520] to-[#CD7F32] text-white hover:from-[#B8860B] hover:to-[#DAA520] shadow-glow hover:shadow-glow',
|
||||
white: 'bg-white text-[#3C2A21] shadow-premium hover:shadow-premium-hover hover:bg-gray-50 border border-gray-200 hover:border-[#8B4513]'
|
||||
const base =
|
||||
"font-renner rounded-full inline-flex items-center justify-center relative overflow-hidden " +
|
||||
"shadow-premium hover:shadow-premium-hover transition-transform duration-200 ease-out " +
|
||||
"hover:-translate-y-0.5 active:translate-y-0 active:scale-[0.98] focus-visible:outline-none " +
|
||||
"focus-visible:ring-2 focus-visible:ring-[#8B4513]/40";
|
||||
|
||||
const variantClasses: Record<NonNullable<ButtonProps["variant"]>, string> = {
|
||||
primary:
|
||||
"bg-gradient-to-r from-[#8B4513] to-[#A0522D] text-white " +
|
||||
"hover:from-[#3C2A21] hover:to-[#8B4513] shadow-glow hover:shadow-glow",
|
||||
secondary:
|
||||
"bg-gradient-to-r from-[#DAA520] to-[#CD7F32] text-white " +
|
||||
"hover:from-[#B8860B] hover:to-[#DAA520] shadow-glow hover:shadow-glow",
|
||||
white:
|
||||
"bg-white text-[#3C2A21] hover:bg-gray-50 border border-gray-200 hover:border-[#8B4513]",
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'px-3 py-2 text-sm md:px-4 md:py-2 md:text-sm',
|
||||
md: 'px-4 py-2 text-sm md:px-6 md:py-3 md:text-base',
|
||||
lg: 'px-6 py-3 text-base md:px-8 md:py-4 md:text-lg'
|
||||
|
||||
const sizeClasses: Record<NonNullable<ButtonProps["size"]>, string> = {
|
||||
sm: "px-3 py-2 text-sm md:px-4 md:py-2",
|
||||
md: "px-4 py-2 text-sm md:px-6 md:py-3 md:text-base",
|
||||
lg: "px-6 py-3 text-base md:px-8 md:py-4 md:text-lg",
|
||||
};
|
||||
|
||||
const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`;
|
||||
|
||||
const buttonContent = (
|
||||
<motion.div
|
||||
className={classes}
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: 'clamp(14px, 2.5vw, 16px)',
|
||||
lineHeight: '100%',
|
||||
letterSpacing: '0%'
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
y: -3,
|
||||
transition: { duration: 0.3, ease: "easeOut" }
|
||||
}}
|
||||
whileTap={{
|
||||
scale: 0.98,
|
||||
transition: { duration: 0.1 }
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Shimmer effect overlay */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -translate-x-full hover:translate-x-full transition-transform duration-700 ease-out" />
|
||||
|
||||
{/* Button content */}
|
||||
<span className="relative z-10">{children}</span>
|
||||
</motion.div>
|
||||
|
||||
const classes = `${base} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`;
|
||||
|
||||
// shimmer overlay via CSS only (no JS)
|
||||
const content = (
|
||||
<>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent
|
||||
group-hover:translate-x-full transition-transform duration-700 ease-out"
|
||||
/>
|
||||
<span
|
||||
className="relative z-10"
|
||||
style={{
|
||||
fontWeight: 300,
|
||||
fontSize: "clamp(14px, 2.5vw, 16px)",
|
||||
lineHeight: "100%",
|
||||
letterSpacing: "0%",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
// If it's a link, use Next <Link> for internal, <a> for external
|
||||
if (href) {
|
||||
const isExternal = /^https?:\/\//.test(href);
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<a href={href} target={target ?? "_blank"} rel={rel ?? "noopener noreferrer"} className={`group ${classes}`}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={href} className="inline-block">
|
||||
{buttonContent}
|
||||
</a>
|
||||
<Link to={href} className={`group ${classes}`}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return buttonContent;
|
||||
}
|
||||
|
||||
// Button element (onClick will require parent to be client; this file itself stays SSR-friendly)
|
||||
return (
|
||||
<button type={type} onClick={onClick} className={`group ${classes}`}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
export default function DesktopFooter() {
|
||||
return (
|
||||
@@ -25,13 +23,9 @@ export default function DesktopFooter() {
|
||||
src="/logo/logo.svg"
|
||||
alt="Mozimo Logo"
|
||||
className="object-contain drop-shadow-lg"
|
||||
priority
|
||||
sizes="40px"
|
||||
width={80}
|
||||
height={80}
|
||||
quality={95}
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
||||
layout="fixed"
|
||||
/>
|
||||
{/* Tagline */}
|
||||
<p
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Image } from "@unpic/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useLocation } from "@tanstack/react-router";
|
||||
|
||||
export default function Header() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
const pathname = useLocation({ select: (loc) => loc.pathname });
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
@@ -136,13 +134,8 @@ export default function Header() {
|
||||
<Image
|
||||
src="/logo/logo.svg"
|
||||
alt="Mozimo Logo"
|
||||
fill
|
||||
className="object-contain drop-shadow-lg"
|
||||
priority
|
||||
sizes="(max-width: 768px) 40px, 96px"
|
||||
quality={95}
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-contain drop-shadow-lg"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
@@ -293,11 +286,8 @@ export default function Header() {
|
||||
<Image
|
||||
src="/logo/logo.svg"
|
||||
alt="Mozimo Logo"
|
||||
fill
|
||||
className="object-contain drop-shadow-lg"
|
||||
priority
|
||||
sizes="(max-width: 768px) 40px, 96px"
|
||||
quality={95}
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-contain drop-shadow-lg"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
export default function MobileFooter() {
|
||||
return (
|
||||
@@ -24,13 +22,9 @@ export default function MobileFooter() {
|
||||
src="/logo/logo.svg"
|
||||
alt="Mozimo Logo"
|
||||
className="object-contain drop-shadow-lg"
|
||||
priority
|
||||
sizes="40px"
|
||||
width={80}
|
||||
height={80}
|
||||
quality={95}
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
|
||||
layout="fixed"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
"use client";
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
||||
import { LazyMotion, domAnimation, m } from "framer-motion";
|
||||
import Section from "@/components/Section";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function ProductCategory() {
|
||||
const [currentCategory, setCurrentCategory] = useState(0);
|
||||
const categoriesSectionRef = useRef<HTMLDivElement>(null);
|
||||
const imageCache = useRef<{ [key: string]: HTMLImageElement }>({});
|
||||
import { Image } from "@unpic/react";
|
||||
|
||||
const categories = [
|
||||
"Bars",
|
||||
@@ -34,180 +28,102 @@ export default function ProductCategory() {
|
||||
"/categories/c5.svg",
|
||||
"/categories/c6.svg",
|
||||
];
|
||||
|
||||
// Preload all images on component mount
|
||||
useEffect(() => {
|
||||
const preloadImages = async () => {
|
||||
const loadPromises = categoryImages.map((src) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (imageCache.current[src]) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
imageCache.current[src] = img;
|
||||
resolve();
|
||||
};
|
||||
img.onerror = () => resolve(); // Continue even if one fails
|
||||
img.src = src;
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(loadPromises);
|
||||
};
|
||||
|
||||
preloadImages();
|
||||
}, [categoryImages]);
|
||||
|
||||
// Ultra-fast animation variants (no delays)
|
||||
const fadeInLeft = {
|
||||
initial: { opacity: 0, x: -20 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.3, ease: [0.23, 1, 0.32, 1] },
|
||||
};
|
||||
|
||||
const fadeInRight = {
|
||||
initial: { opacity: 0, x: 20 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
transition: { duration: 0.3, ease: [0.23, 1, 0.32, 1] },
|
||||
};
|
||||
|
||||
|
||||
export default function ProductCategory() {
|
||||
const [currentCategory, setCurrentCategory] = useState(0);
|
||||
|
||||
// Instant category change with no debouncing
|
||||
const handleCategoryChange = useCallback((idx: number) => {
|
||||
setCurrentCategory(idx);
|
||||
}, []);
|
||||
|
||||
// Optimized category buttons
|
||||
// Preload only neighbor images ✅
|
||||
useEffect(() => {
|
||||
const next = (currentCategory + 1) % categoryImages.length;
|
||||
const prev = (currentCategory - 1 + categoryImages.length) % categoryImages.length;
|
||||
[next, prev].forEach((i) => {
|
||||
const img = new window.Image();
|
||||
img.src = categoryImages[i];
|
||||
});
|
||||
}, [currentCategory]);
|
||||
|
||||
const categoryButtons = useMemo(() => {
|
||||
return categories.map((category, idx) => {
|
||||
const isActive = idx === currentCategory;
|
||||
|
||||
return (
|
||||
<motion.li
|
||||
key={category}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.2, delay: idx * 0.02 }}
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<li key={category}>
|
||||
<a
|
||||
href={categoryLinks[idx]}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`group flex items-center gap-4 w-full text-left font-moneta transition-all duration-100 ease-out will-change-transform
|
||||
${
|
||||
isActive
|
||||
? "text-[#703133] font-normal"
|
||||
: "text-[#703133] opacity-70 font-light hover:opacity-90"
|
||||
}
|
||||
className={`group flex items-center gap-4 w-full text-left transition-opacity duration-150
|
||||
${isActive ? "opacity-100" : "opacity-70 hover:opacity-90"}
|
||||
`}
|
||||
style={{
|
||||
fontFamily: "MonetaSans-Regular",
|
||||
fontWeight: isActive ? 400 : 300,
|
||||
fontSize: isActive
|
||||
? "clamp(22px, 5vw, 64px)"
|
||||
: "clamp(18px, 4vw, 56px)",
|
||||
fontSize: isActive ? "clamp(22px, 5vw, 64px)" : "clamp(18px, 4vw, 56px)",
|
||||
lineHeight: "120%",
|
||||
letterSpacing: "0%",
|
||||
transform: isActive ? "translateX(4px)" : "translateX(0)",
|
||||
color: "#703133",
|
||||
}}
|
||||
onClick={() => handleCategoryChange(idx)}
|
||||
onMouseEnter={() => handleCategoryChange(idx)}
|
||||
onTouchStart={() => handleCategoryChange(idx)}
|
||||
>
|
||||
{/* Mobile optimized thumbnail */}
|
||||
<div className="block lg:hidden w-16 h-16 rounded-full overflow-hidden shrink-0 will-change-transform">
|
||||
{/* Mobile thumbnail */}
|
||||
<div className="block lg:hidden w-16 h-16 rounded-full overflow-hidden shrink-0">
|
||||
<Image
|
||||
src={categoryImages[idx]}
|
||||
alt={category}
|
||||
width={64}
|
||||
height={64}
|
||||
className={`w-full h-full object-cover transition-transform duration-100 ${
|
||||
isActive ? "scale-110" : "scale-100"
|
||||
}`}
|
||||
className={`w-full h-full object-cover transition-transform duration-150 ${isActive ? "scale-110" : "scale-100"}`}
|
||||
draggable={false}
|
||||
quality={60}
|
||||
priority={idx < 2}
|
||||
loading={idx < 2 ? "eager" : "lazy"}
|
||||
layout="fixed"
|
||||
/>
|
||||
</div>
|
||||
<span className="will-change-transform">{category}</span>
|
||||
|
||||
<span>{category}</span>
|
||||
</a>
|
||||
</motion.li>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}, [currentCategory, handleCategoryChange, categories, categoryImages, categoryLinks]);
|
||||
|
||||
// Ultra-optimized image switcher with instant transitions
|
||||
const categoryImageComponent = useMemo(
|
||||
() => (
|
||||
<div className="relative w-full h-full overflow-hidden rounded-2xl">
|
||||
{/* Render all images but show only active one */}
|
||||
{categoryImages.map((src, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={false}
|
||||
animate={{
|
||||
opacity: idx === currentCategory ? 1 : 0,
|
||||
scale: idx === currentCategory ? 1 : 0.95,
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.15,
|
||||
ease: [0.23, 1, 0.32, 1],
|
||||
}}
|
||||
className="absolute inset-0 will-change-transform"
|
||||
style={{
|
||||
zIndex: idx === currentCategory ? 2 : 1,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={categories[idx]}
|
||||
fill
|
||||
className="object-cover"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
priority={idx < 3}
|
||||
loading={idx < 3 ? "eager" : "lazy"}
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
[currentCategory, categories, categoryImages]
|
||||
);
|
||||
}, [currentCategory, handleCategoryChange]);
|
||||
|
||||
return (
|
||||
<Section background="white" id="categories">
|
||||
<div
|
||||
ref={categoriesSectionRef}
|
||||
className="grid grid-cols-1 lg:grid-cols-2 gap-0 items-stretch min-h-[400px] md:min-h-[500px]"
|
||||
>
|
||||
{/* Left Column - Categories List */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
variants={fadeInLeft}
|
||||
className="flex flex-col justify-center h-full bg-white px-4 md:px-8 py-6 md:py-12 order-2 lg:order-1"
|
||||
>
|
||||
<ul className="space-y-4 md:space-y-6">{categoryButtons}</ul>
|
||||
</motion.div>
|
||||
<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">
|
||||
<ul className="space-y-4 md:space-y-6">{categoryButtons}</ul>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Large Category Image */}
|
||||
<motion.div
|
||||
initial="initial"
|
||||
whileInView="animate"
|
||||
viewport={{ once: true, margin: "-100px" }}
|
||||
variants={fadeInRight}
|
||||
className="relative hidden lg:flex items-center justify-center h-full min-h-[300px] md:min-h-[400px] bg-white order-1 lg:order-2 p-4"
|
||||
>
|
||||
{categoryImageComponent}
|
||||
</motion.div>
|
||||
</div>
|
||||
{/* Right (render ONLY active image) */}
|
||||
<div className="relative hidden lg:flex items-center justify-center h-full min-h-[300px] md:min-h-[400px] bg-white order-1 lg:order-2 p-4">
|
||||
<div className="relative w-full h-full overflow-hidden rounded-2xl">
|
||||
<m.div
|
||||
key={currentCategory}
|
||||
initial={{ opacity: 0, scale: 0.985 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.18, ease: [0.23, 1, 0.32, 1] }}
|
||||
className="absolute inset-0"
|
||||
>
|
||||
<Image
|
||||
src={categoryImages[currentCategory]}
|
||||
alt={categories[currentCategory]}
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-cover"
|
||||
draggable={false}
|
||||
/>
|
||||
</m.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyMotion>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { Image } from "@unpic/react";
|
||||
import Section from "./Section";
|
||||
import { useInstagram } from "@/hooks/useInstagram";
|
||||
|
||||
@@ -245,10 +244,9 @@ export default function SocialMedia() {
|
||||
<Image
|
||||
src={post.mediaUrl}
|
||||
alt={post.caption || "Instagram post"}
|
||||
fill
|
||||
className="object-cover img-premium"
|
||||
layout="fullWidth"
|
||||
className="w-full h-full object-cover img-premium"
|
||||
draggable={false}
|
||||
quality={85}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = "/bst/bs1.svg";
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './app/__root'
|
||||
import { Route as AboutRouteImport } from './app/about'
|
||||
import { Route as IndexRouteImport } from './app/index'
|
||||
import { Route as ApiInstagramRouteImport } from './app/api/instagram'
|
||||
|
||||
const AboutRoute = AboutRouteImport.update({
|
||||
id: '/about',
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ApiInstagramRoute = ApiInstagramRouteImport.update({
|
||||
id: '/api/instagram',
|
||||
path: '/api/instagram',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/api/instagram': typeof ApiInstagramRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/api/instagram': typeof ApiInstagramRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
'/api/instagram': typeof ApiInstagramRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/about' | '/api/instagram'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/about' | '/api/instagram'
|
||||
id: '__root__' | '/' | '/about' | '/api/instagram'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AboutRoute: typeof AboutRoute
|
||||
ApiInstagramRoute: typeof ApiInstagramRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/about': {
|
||||
id: '/about'
|
||||
path: '/about'
|
||||
fullPath: '/about'
|
||||
preLoaderRoute: typeof AboutRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/api/instagram': {
|
||||
id: '/api/instagram'
|
||||
path: '/api/instagram'
|
||||
fullPath: '/api/instagram'
|
||||
preLoaderRoute: typeof ApiInstagramRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AboutRoute: AboutRoute,
|
||||
ApiInstagramRoute: ApiInstagramRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createRouter as createTanStackRouter } from '@tanstack/react-router';
|
||||
// Import the generated route tree
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
export function createRouter() {
|
||||
const router = createTanStackRouter({
|
||||
routeTree,
|
||||
scrollRestoration: true,
|
||||
});
|
||||
return router;
|
||||
}
|
||||
|
||||
export function getRouter() {
|
||||
return createRouter();
|
||||
}
|
||||
|
||||
// Register the router instance for type safety
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: ReturnType<typeof createRouter>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
|
||||
import viteReact from '@vitejs/plugin-react';
|
||||
import { nitro } from 'nitro/vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
resolve: {
|
||||
// Enables Vite to resolve imports using path aliases like in Next.js
|
||||
alias: {
|
||||
'@': '/src',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
tanstackStart({
|
||||
srcDirectory: 'src', // Points to the src directory
|
||||
router: {
|
||||
routesDirectory: 'app', // Maps the router to the app directory
|
||||
},
|
||||
}),
|
||||
viteReact(),
|
||||
nitro(),
|
||||
],
|
||||
});
|
||||