Image and text animation
diwali
@ -1,5 +1,6 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
output: 'export',
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
@ -7,6 +8,7 @@ const nextConfig = {
|
|||||||
hostname: '**.amazonaws.com',
|
hostname: '**.amazonaws.com',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
BIN
public/images/diwali/01-banner.jpg
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/images/diwali/02-red-praline-box.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/images/diwali/03-blue-praline-box-open.jpg
Normal file
|
After Width: | Height: | Size: 944 KiB |
BIN
public/images/diwali/04-blue-praline-box-two.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/diwali/05-gold-praline-box.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/images/diwali/06-red-bark-box.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
public/images/diwali/07-blue-spread.jpg
Normal file
|
After Width: | Height: | Size: 823 KiB |
@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
import { useRef } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import pralinePic from '/public/images/about-us/01-praline.jpg';
|
import pralinePic from '/public/images/about-us/01-praline.jpg';
|
||||||
import prAmPic from '/public/images/about-us/02-priyanka-amritanshu.jpg';
|
import prAmPic from '/public/images/about-us/02-priyanka-amritanshu.jpg';
|
||||||
@ -5,8 +7,40 @@ import dryingPic from '/public/images/about-us/03-drying.jpg';
|
|||||||
import chefPic from '/public/images/about-us/04-chef.jpg';
|
import chefPic from '/public/images/about-us/04-chef.jpg';
|
||||||
import barkPic from '/public/images/about-us/05-bark.jpg';
|
import barkPic from '/public/images/about-us/05-bark.jpg';
|
||||||
import italyPic from '/public/images/about-us/06-italy.jpg';
|
import italyPic from '/public/images/about-us/06-italy.jpg';
|
||||||
|
import useInView from '@/hooks/useInView';
|
||||||
|
|
||||||
export default function AboutUsPage() {
|
export default function AboutUsPage() {
|
||||||
|
const pralinePicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isPralinePic = useInView(pralinePicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
const prAmPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isPrAmPic = useInView(prAmPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
const dryingPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isDryingPic = useInView(dryingPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
const chefPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isChefPic = useInView(chefPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
const barkPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isBarkPic = useInView(barkPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
const italyPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isItalyPic = useInView(italyPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='overflow-x-hidden bg-white pt-28'>
|
<div className='overflow-x-hidden bg-white pt-28'>
|
||||||
<section className='flex flex-col items-center'>
|
<section className='flex flex-col items-center'>
|
||||||
@ -25,9 +59,10 @@ export default function AboutUsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={pralinePicRef}
|
||||||
src={pralinePic}
|
src={pralinePic}
|
||||||
alt='Praline'
|
alt='Praline'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isPralinePic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -40,9 +75,10 @@ export default function AboutUsPage() {
|
|||||||
{/* Left Column - Image */}
|
{/* Left Column - Image */}
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={prAmPicRef}
|
||||||
src={prAmPic}
|
src={prAmPic}
|
||||||
alt='Left Side Image'
|
alt='Left Side Image'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isPrAmPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -92,9 +128,10 @@ export default function AboutUsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={dryingPicRef}
|
||||||
src={dryingPic}
|
src={dryingPic}
|
||||||
alt='Beans Drying'
|
alt='Beans Drying'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isDryingPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -107,9 +144,10 @@ export default function AboutUsPage() {
|
|||||||
<section className='flex flex-col md:flex-row items-center'>
|
<section className='flex flex-col md:flex-row items-center'>
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={chefPicRef}
|
||||||
src={chefPic}
|
src={chefPic}
|
||||||
alt='Beans Drying'
|
alt='Beans Drying'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isChefPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -119,9 +157,10 @@ export default function AboutUsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={barkPicRef}
|
||||||
src={barkPic}
|
src={barkPic}
|
||||||
alt='Beans Drying'
|
alt='Beans Drying'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isBarkPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -150,9 +189,10 @@ export default function AboutUsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className='flex w-full'>
|
<div className='flex w-full'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={italyPicRef}
|
||||||
src={italyPic}
|
src={italyPic}
|
||||||
alt='Training'
|
alt='Training'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isItalyPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|||||||
185
src/app/(static)/diwali/page.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import Image from 'next/image';
|
||||||
|
import bannerPic from '/public/images/diwali/01-banner.jpg';
|
||||||
|
import redPralineBoxPic from '/public/images/diwali/02-red-praline-box.jpg';
|
||||||
|
import bluePralineBoxOpenPic from '/public/images/diwali/03-blue-praline-box-open.jpg';
|
||||||
|
import bluePralineBoxTwoPic from '/public/images/diwali/04-blue-praline-box-two.jpg';
|
||||||
|
import goldPralineBoxPic from '/public/images/diwali/05-gold-praline-box.jpg';
|
||||||
|
import redBarkBoxPic from '/public/images/diwali/06-red-bark-box.jpg';
|
||||||
|
// import blueSpreadPic from '/public/images/diwali/07-blue-spread.jpg';
|
||||||
|
|
||||||
|
export default function AboutUsPage() {
|
||||||
|
return (
|
||||||
|
<div className='overflow-x-hidden bg-white'>
|
||||||
|
<section className='flex flex-col items-center'>
|
||||||
|
<div className='w-full'>
|
||||||
|
<Image
|
||||||
|
src={bannerPic}
|
||||||
|
alt='Praline'
|
||||||
|
className='w-full h-auto'
|
||||||
|
sizes='100vw'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className='text-8xl font-normal text-justify font-samantha p-12'>
|
||||||
|
Sweet Moments with Mozimo
|
||||||
|
</p>
|
||||||
|
<div className='w-full text-xl text-justify pb-12'>
|
||||||
|
“Transforming cacao beans sourced straight from the pod into rich,
|
||||||
|
velvety smooth chocolate is pure magic. This is why we take great
|
||||||
|
pride in being called the 'Alchemists of the Chocolate
|
||||||
|
World'.”
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className='flex flex-col md:flex-row items-center'>
|
||||||
|
{/* Left Column - Image */}
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<Image
|
||||||
|
src={redPralineBoxPic}
|
||||||
|
alt='Left Side Image'
|
||||||
|
className='w-full h-auto'
|
||||||
|
sizes='100vw'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Right Column - Text with Header */}
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<div className='m-8 space-y-10'>
|
||||||
|
<p className='text-xl text-justify'>
|
||||||
|
Our journey at 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. We traversed the
|
||||||
|
globe to search for the finest beans, equipment, and techniques.
|
||||||
|
We ventured deep into remote cocoa farms to select the best beans
|
||||||
|
as we cherished the stories and traditions behind every harvest.
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-justify'>
|
||||||
|
Beholding our vision, we started the meticulous process of
|
||||||
|
crafting rich, exquisite, delectable chocolates, ultimately
|
||||||
|
perfecting the art. As we continue our journey at Mozimo, we are
|
||||||
|
proud to be a part of the global craft chocolate revolution
|
||||||
|
celebrating the diversity of cocoa.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className='flex flex-col md:flex-row items-center'>
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<div className='m-8 space-y-5'>
|
||||||
|
<p className='text-xl font-justify uppercase'>
|
||||||
|
Aritsan Craftmanship
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-justify pb-12'>
|
||||||
|
We are an atelier crafting chocolate from the finest single-source
|
||||||
|
beans. Our commitment to the art of making chocolate ensures that
|
||||||
|
each piece epitomizes supreme taste & quality.
|
||||||
|
</p>
|
||||||
|
<p className='text-xl font-justify uppercase'>
|
||||||
|
Ethics and Sustainability
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-justify pb-12'>
|
||||||
|
We are committed to sustainability and being ethical in all that
|
||||||
|
we do. From selecting ingredients from remote farmers to our
|
||||||
|
production practices, we prioritize sustainability and ethical
|
||||||
|
practices.
|
||||||
|
</p>
|
||||||
|
<p className='text-xl font-justify uppercase'>
|
||||||
|
Commitment to Excellence
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-justify'>
|
||||||
|
We are inspired to create the best-quality chocolate in our
|
||||||
|
pursuit of excellence. This involves refining ingredients,
|
||||||
|
updating processes, and continuously striving to become the best
|
||||||
|
in the game.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<Image
|
||||||
|
src={bluePralineBoxOpenPic}
|
||||||
|
alt='Beans Drying'
|
||||||
|
className='w-full h-auto'
|
||||||
|
sizes='100vw'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/* Fourth Three-Column Layout (Collections) */}
|
||||||
|
<section className='flex flex-col items-center'>
|
||||||
|
<div className='font-normal text-center p-12'>
|
||||||
|
<span className='text-6xl font-montera'>Specimens of</span>{' '}
|
||||||
|
<span className='text-8xl font-samantha'>
|
||||||
|
Exquisite Craftsmanship
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className='text-xl text-justify pb-12'>
|
||||||
|
Every creation of Mozimo's chocolate is a specimen of
|
||||||
|
craftsmanship of the highest quality by the finest chocolatiers.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className='flex flex-col md:flex-row items-center'>
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<Image
|
||||||
|
src={bluePralineBoxTwoPic}
|
||||||
|
alt='Beans Drying'
|
||||||
|
className='w-full h-auto'
|
||||||
|
sizes='100vw'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='w-full md:w-1/2'>
|
||||||
|
<Image
|
||||||
|
src={goldPralineBoxPic}
|
||||||
|
alt='Beans Drying'
|
||||||
|
className='w-full h-auto'
|
||||||
|
sizes='100vw'
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className='flex flex-col items-center'>
|
||||||
|
<p className='font-normal text-center p-12'>
|
||||||
|
<span className='text-6xl font-montera'>Immerse yourself in the</span>{' '}
|
||||||
|
<span className='text-8xl font-samantha'>
|
||||||
|
Italian Chocolate Experience
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-center pb-12'>
|
||||||
|
Indulge in the authentic artistry of the Cioccolaterias of Italy with
|
||||||
|
handcrafted artisanal chocolates by Mozimo.
|
||||||
|
</p>
|
||||||
|
<Image src={redBarkBoxPic} alt='Training' className='w-full h-auto' />
|
||||||
|
<p className='text-6xl text-center font-montera p-12'>
|
||||||
|
Experience the art of gifting this Diwali
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-center pb-12'>
|
||||||
|
It’s time to celebrate the joy of togetherness, love, and the luxury
|
||||||
|
of Mozimo’s chocolates. This Diwali, let’s embrace the sweetness of
|
||||||
|
life with our exquisite single-origin handcrafted chocolates, crafted
|
||||||
|
to elevate your festive moments.
|
||||||
|
</p>
|
||||||
|
<p className='text-xl text-center pb-12'>
|
||||||
|
Let our handcrafted chocolates be a part of your Diwali rituals.
|
||||||
|
Whether you’re enjoying them during family gatherings, using them as a
|
||||||
|
centerpiece for your celebrations, or gifting them to friends or
|
||||||
|
colleagues, they are sure to bring warmth and sweetness to every
|
||||||
|
moment.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
import { useRef } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import brandStoryPic from '/public/images/homepage/brand-story.jpg';
|
import brandStoryPic from '/public/images/homepage/brand-story.jpg';
|
||||||
import { HomepageVideo } from '@/components/homepage-video';
|
import { HomepageVideo } from '@/components/homepage-video';
|
||||||
@ -6,8 +8,16 @@ import { Collections } from '@/components/our-collection';
|
|||||||
import { ChocolateCategories } from '@/components/category-slider';
|
import { ChocolateCategories } from '@/components/category-slider';
|
||||||
import { InstagramFeed } from '@/components/instagram';
|
import { InstagramFeed } from '@/components/instagram';
|
||||||
import { Spotlight } from '@/components/spotlight';
|
import { Spotlight } from '@/components/spotlight';
|
||||||
|
import { AnimatedText } from '@/components/animated-text';
|
||||||
|
import useInView from '@/hooks/useInView';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const brandStoryPicRef = useRef<HTMLImageElement>(null);
|
||||||
|
const isBrandStoryPic = useInView(brandStoryPicRef, {
|
||||||
|
threshold: 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='overflow-x-hidden'>
|
<div className='overflow-x-hidden'>
|
||||||
<HeroSwiper />
|
<HeroSwiper />
|
||||||
@ -16,9 +26,15 @@ export default function HomePage() {
|
|||||||
{/* Left Column - Text with Header */}
|
{/* Left Column - Text with Header */}
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<div className='m-8 space-y-10'>
|
<div className='m-8 space-y-10'>
|
||||||
<h1 className='text-6xl font-normal text-justify font-montera'>
|
<AnimatedText finishClass='delay-300'>
|
||||||
|
<h2 className='text-6xl font-normal text-justify font-montera'>
|
||||||
Brand Story
|
Brand Story
|
||||||
</h1>
|
</h2>
|
||||||
|
</AnimatedText>
|
||||||
|
<AnimatedText
|
||||||
|
startClass='translate-y-8'
|
||||||
|
finishClass='translate-y-0'
|
||||||
|
>
|
||||||
<p className='text-xl text-justify'>
|
<p className='text-xl text-justify'>
|
||||||
Mozimo's journey is a passionate pursuit of crafting
|
Mozimo's journey is a passionate pursuit of crafting
|
||||||
exceptional chocolate that celebrates the origin of each cocoa
|
exceptional chocolate that celebrates the origin of each cocoa
|
||||||
@ -29,17 +45,19 @@ export default function HomePage() {
|
|||||||
using modern techniques to highlight their natural flavors.
|
using modern techniques to highlight their natural flavors.
|
||||||
</p>
|
</p>
|
||||||
<p className='text-xl text-justify'>
|
<p className='text-xl text-justify'>
|
||||||
Each chocolate is a masterpiece, capturing the essence of cocoa in
|
Each chocolate is a masterpiece, capturing the essence of cocoa
|
||||||
its purest form.
|
in its purest form.
|
||||||
</p>
|
</p>
|
||||||
|
</AnimatedText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Right Column - Image */}
|
{/* Right Column - Image */}
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<Image
|
<Image
|
||||||
|
ref={brandStoryPicRef}
|
||||||
src={brandStoryPic}
|
src={brandStoryPic}
|
||||||
alt='Right Side Image'
|
alt='Right Side Image'
|
||||||
className='w-full h-auto'
|
className={`w-full h-auto zoom ${isBrandStoryPic ? 'post' : 'pre'}`}
|
||||||
sizes='100vw'
|
sizes='100vw'
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -60,19 +78,26 @@ export default function HomePage() {
|
|||||||
{/* Right Column - Text */}
|
{/* Right Column - Text */}
|
||||||
<div className='w-full md:w-1/2'>
|
<div className='w-full md:w-1/2'>
|
||||||
<div className='m-8 space-y-10'>
|
<div className='m-8 space-y-10'>
|
||||||
|
<AnimatedText finishClass='delay-300'>
|
||||||
<h2 className='text-6xl font-normal text-justify font-montera'>
|
<h2 className='text-6xl font-normal text-justify font-montera'>
|
||||||
Discover the delicate art of our
|
Discover the delicate art of our
|
||||||
</h2>
|
</h2>
|
||||||
<h3 className='text-8xl font-normal text-justify font-samantha'>
|
<h3 className='text-8xl font-normal text-justify font-samantha'>
|
||||||
Chocolate Tempering
|
Chocolate Tempering
|
||||||
</h3>
|
</h3>
|
||||||
|
</AnimatedText>
|
||||||
|
<AnimatedText
|
||||||
|
startClass='translate-y-8'
|
||||||
|
finishClass='translate-y-0'
|
||||||
|
>
|
||||||
<p className='text-xl text-justify'>
|
<p className='text-xl text-justify'>
|
||||||
Crafted to perfection Mozimo delivers a sublime sensory experience
|
Crafted to perfection Mozimo delivers a sublime sensory
|
||||||
with every bite. Experience the epitome of indulgence with our
|
experience with every bite. Experience the epitome of indulgence
|
||||||
perfectly tempered chocolate: velvety smooth, exquisitely rich,
|
with our perfectly tempered chocolate: velvety smooth,
|
||||||
and artfully balanced. Each bite offers a symphony of nuanced
|
exquisitely rich, and artfully balanced. Each bite offers a
|
||||||
cocoa flavors, melting luxuriously!
|
symphony of nuanced cocoa flavors, melting luxuriously!
|
||||||
</p>
|
</p>
|
||||||
|
</AnimatedText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -97,6 +122,9 @@ export default function HomePage() {
|
|||||||
In the Spotlight
|
In the Spotlight
|
||||||
</div>
|
</div>
|
||||||
<Spotlight />
|
<Spotlight />
|
||||||
|
{/* <a href='#' className='pill-button'>
|
||||||
|
Button Text
|
||||||
|
</a> */}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
|
|||||||
} else {
|
} else {
|
||||||
setProduct(product);
|
setProduct(product);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
setError(err);
|
setError(err);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -39,7 +39,8 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Something went wrong: {error.message}</div>;
|
console.log(error);
|
||||||
|
return <div>Something went wrong</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
@ -52,7 +53,7 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
|
|||||||
<div className='flex flex-col md:flex-row gap-6 mt-4'>
|
<div className='flex flex-col md:flex-row gap-6 mt-4'>
|
||||||
{/* Product Images */}
|
{/* Product Images */}
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
{product?.images?.length > 0 && (
|
{!!product.images?.length && (
|
||||||
<img
|
<img
|
||||||
src={product.images[0].url}
|
src={product.images[0].url}
|
||||||
alt={product.title}
|
alt={product.title}
|
||||||
|
|||||||
@ -20,8 +20,8 @@ export default function ProductsPage() {
|
|||||||
<Link key={product.id} href={`/products/${product.handle}`}>
|
<Link key={product.id} href={`/products/${product.handle}`}>
|
||||||
<div className='border border-gray-200 p-4 rounded-lg shadow-lg'>
|
<div className='border border-gray-200 p-4 rounded-lg shadow-lg'>
|
||||||
<Image
|
<Image
|
||||||
src={product.thumbnail}
|
src={product.thumbnail as string}
|
||||||
alt={product.title}
|
alt={product.title as string}
|
||||||
width={300}
|
width={300}
|
||||||
height={300}
|
height={300}
|
||||||
className='object-cover rounded-md'
|
className='object-cover rounded-md'
|
||||||
|
|||||||
@ -63,3 +63,29 @@ body {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pill-button {
|
||||||
|
@apply inline-flex items-center bg-white text-black rounded-full px-4 py-2 cursor-pointer shadow-md transition-all duration-500 ease-in-out px-5 py-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-button:after {
|
||||||
|
@apply content-['→'] opacity-0 -ml-3 transition-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-button:after:hover {
|
||||||
|
@apply opacity-100 ml-2;
|
||||||
|
}
|
||||||
|
.pill-button:hover,
|
||||||
|
.pill-button:focus {
|
||||||
|
@apply shadow-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom {
|
||||||
|
@apply transition-[opacity,transform] duration-700 ease-out;
|
||||||
|
}
|
||||||
|
.zoom.pre {
|
||||||
|
@apply opacity-0 scale-95;
|
||||||
|
}
|
||||||
|
.zoom.post {
|
||||||
|
@apply opacity-100 scale-100;
|
||||||
|
}
|
||||||
|
|||||||
59
src/components/PillButton.module.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
.pillButton {
|
||||||
|
@apply relative bg-white text-black rounded-full px-6 py-2 text-lg font-sans flex items-center justify-center shadow-md transition duration-300 ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillButton:not(:hover) .arrow {
|
||||||
|
@apply hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
@apply ml-2 transform -translate-x-5 opacity-0 transition-transform duration-300 ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillButton:hover .arrow {
|
||||||
|
@apply transform duration-300 translate-x-0 opacity-100 transition-transform ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillButton:hover {
|
||||||
|
@apply shadow-lg transform duration-300 translate-x-0 opacity-100 transition-transform ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Button style */
|
||||||
|
/* .pillButton {
|
||||||
|
background-color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 30px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* Arrow style */
|
||||||
|
/* .arrow {
|
||||||
|
margin-left: 10px;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* Hover effect */
|
||||||
|
/* .pillButton:hover {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillButton:hover .arrow {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
*/
|
||||||
16
src/components/PillButton.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './PillButton.module.css';
|
||||||
|
|
||||||
|
type PillButtonProps = {
|
||||||
|
text: string; // text prop is now required
|
||||||
|
onClick?: () => void; // onClick is optional
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PillButton({ text, onClick }: PillButtonProps) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} className={styles.pillButton}>
|
||||||
|
{text}
|
||||||
|
<span className={styles.arrow}>→</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/components/animated-image.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// src/components/AnimatedImage.tsx
|
||||||
|
import React, { ImgHTMLAttributes, useRef } from 'react';
|
||||||
|
import useInView from '@/hooks/useInView';
|
||||||
|
|
||||||
|
interface AnimatedImageProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
className?: string;
|
||||||
|
threshold?: number; // New prop
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimatedImage: React.FC<AnimatedImageProps> = ({
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
className = '',
|
||||||
|
threshold,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<HTMLImageElement>(null);
|
||||||
|
const isVisible = useInView(ref, {
|
||||||
|
threshold: threshold ?? 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
ref={ref}
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
className={`opacity-0 transform scale-95 transition-opacity transition-transform duration-700 ease-out ${
|
||||||
|
isVisible ? 'opacity-100 scale-100' : ''
|
||||||
|
} ${className}`}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimatedImage;
|
||||||
36
src/components/animated-text.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// src/components/AnimatedText.tsx
|
||||||
|
import { ReactNode, useRef } from 'react';
|
||||||
|
import useInView from '@/hooks/useInView';
|
||||||
|
|
||||||
|
interface AnimatedTextProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
startClass?: string;
|
||||||
|
finishClass?: string;
|
||||||
|
threshold?: number; // New prop
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AnimatedText({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
startClass,
|
||||||
|
finishClass,
|
||||||
|
threshold,
|
||||||
|
}: AnimatedTextProps) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const isVisible = useInView(ref, {
|
||||||
|
threshold: threshold ?? 0.1,
|
||||||
|
triggerOnce: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={`transition-[opacity, transform] ease-out duration-1000 ${
|
||||||
|
isVisible ? 'opacity-100' : 'opacity-0'
|
||||||
|
} ${isVisible ? finishClass : startClass} ${className}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
src/hooks/useInView.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useState, useEffect, RefObject } from 'react';
|
||||||
|
|
||||||
|
interface UseInViewOptions {
|
||||||
|
threshold?: number | number[];
|
||||||
|
triggerOnce?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useInView = (
|
||||||
|
ref: RefObject<Element>,
|
||||||
|
options: UseInViewOptions = { threshold: 0.1, triggerOnce: true },
|
||||||
|
): boolean => {
|
||||||
|
const { threshold = 0.1, triggerOnce = true } = options;
|
||||||
|
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// Validate threshold
|
||||||
|
const isValidThreshold =
|
||||||
|
typeof threshold === 'number'
|
||||||
|
? threshold >= 0 && threshold <= 1
|
||||||
|
: Array.isArray(threshold) && threshold.every((t) => t >= 0 && t <= 1);
|
||||||
|
|
||||||
|
if (!isValidThreshold) {
|
||||||
|
console.warn(
|
||||||
|
'Invalid threshold value passed to useInView. It should be between 0 and 1.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
const prefersReducedMotion = window.matchMedia(
|
||||||
|
'(prefers-reduced-motion: reduce)',
|
||||||
|
);
|
||||||
|
if (prefersReducedMotion.matches) {
|
||||||
|
setIsVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
if (triggerOnce) {
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold },
|
||||||
|
);
|
||||||
|
|
||||||
|
observer.observe(element);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (element) observer.unobserve(element);
|
||||||
|
};
|
||||||
|
}, [ref, threshold, triggerOnce]);
|
||||||
|
|
||||||
|
return isVisible;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInView;
|
||||||