Image and text animation

diwali
This commit is contained in:
2024-09-27 01:00:12 +05:30
parent 8d0a8903b4
commit 127e024374
19 changed files with 536 additions and 42 deletions

View File

@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: {
remotePatterns: [
{
@ -7,7 +8,8 @@ const nextConfig = {
hostname: '**.amazonaws.com',
},
],
},
unoptimized: true,
},
};
export default nextConfig;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 KiB

View File

@ -1,3 +1,5 @@
'use client';
import { useRef } from 'react';
import Image from 'next/image';
import pralinePic from '/public/images/about-us/01-praline.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 barkPic from '/public/images/about-us/05-bark.jpg';
import italyPic from '/public/images/about-us/06-italy.jpg';
import useInView from '@/hooks/useInView';
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 (
<div className='overflow-x-hidden bg-white pt-28'>
<section className='flex flex-col items-center'>
@ -25,9 +59,10 @@ export default function AboutUsPage() {
</div>
<div className='w-full'>
<Image
ref={pralinePicRef}
src={pralinePic}
alt='Praline'
className='w-full h-auto'
className={`w-full h-auto zoom ${isPralinePic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -40,9 +75,10 @@ export default function AboutUsPage() {
{/* Left Column - Image */}
<div className='w-full md:w-1/2'>
<Image
ref={prAmPicRef}
src={prAmPic}
alt='Left Side Image'
className='w-full h-auto'
className={`w-full h-auto zoom ${isPrAmPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -92,9 +128,10 @@ export default function AboutUsPage() {
</div>
<div className='w-full md:w-1/2'>
<Image
ref={dryingPicRef}
src={dryingPic}
alt='Beans Drying'
className='w-full h-auto'
className={`w-full h-auto zoom ${isDryingPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -107,9 +144,10 @@ export default function AboutUsPage() {
<section className='flex flex-col md:flex-row items-center'>
<div className='w-full md:w-1/2'>
<Image
ref={chefPicRef}
src={chefPic}
alt='Beans Drying'
className='w-full h-auto'
className={`w-full h-auto zoom ${isChefPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -119,9 +157,10 @@ export default function AboutUsPage() {
</div>
<div className='w-full md:w-1/2'>
<Image
ref={barkPicRef}
src={barkPic}
alt='Beans Drying'
className='w-full h-auto'
className={`w-full h-auto zoom ${isBarkPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -150,9 +189,10 @@ export default function AboutUsPage() {
</div>
<div className='flex w-full'>
<Image
ref={italyPicRef}
src={italyPic}
alt='Training'
className='w-full h-auto'
className={`w-full h-auto zoom ${isItalyPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',

View 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 &apos;Alchemists of the Chocolate
World&apos;.
</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&apos;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&apos;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'>
Its time to celebrate the joy of togetherness, love, and the luxury
of Mozimos chocolates. This Diwali, lets 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 youre 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>
);
}

View File

@ -1,3 +1,5 @@
'use client';
import { useRef } from 'react';
import Image from 'next/image';
import brandStoryPic from '/public/images/homepage/brand-story.jpg';
import { HomepageVideo } from '@/components/homepage-video';
@ -6,8 +8,16 @@ import { Collections } from '@/components/our-collection';
import { ChocolateCategories } from '@/components/category-slider';
import { InstagramFeed } from '@/components/instagram';
import { Spotlight } from '@/components/spotlight';
import { AnimatedText } from '@/components/animated-text';
import useInView from '@/hooks/useInView';
export default function HomePage() {
const brandStoryPicRef = useRef<HTMLImageElement>(null);
const isBrandStoryPic = useInView(brandStoryPicRef, {
threshold: 0.1,
triggerOnce: true,
});
return (
<div className='overflow-x-hidden'>
<HeroSwiper />
@ -16,30 +26,38 @@ export default function HomePage() {
{/* Left Column - Text with Header */}
<div className='w-full md:w-1/2'>
<div className='m-8 space-y-10'>
<h1 className='text-6xl font-normal text-justify font-montera'>
Brand Story
</h1>
<p className='text-xl text-justify'>
Mozimo&apos;s journey is a passionate pursuit of crafting
exceptional chocolate that celebrates the origin of each cocoa
bean.
</p>
<p className='text-xl text-justify'>
We meticulously roast, crack, winnow, and refine beans in-house,
using modern techniques to highlight their natural flavors.
</p>
<p className='text-xl text-justify'>
Each chocolate is a masterpiece, capturing the essence of cocoa in
its purest form.
</p>
<AnimatedText finishClass='delay-300'>
<h2 className='text-6xl font-normal text-justify font-montera'>
Brand Story
</h2>
</AnimatedText>
<AnimatedText
startClass='translate-y-8'
finishClass='translate-y-0'
>
<p className='text-xl text-justify'>
Mozimo&apos;s journey is a passionate pursuit of crafting
exceptional chocolate that celebrates the origin of each cocoa
bean.
</p>
<p className='text-xl text-justify'>
We meticulously roast, crack, winnow, and refine beans in-house,
using modern techniques to highlight their natural flavors.
</p>
<p className='text-xl text-justify'>
Each chocolate is a masterpiece, capturing the essence of cocoa
in its purest form.
</p>
</AnimatedText>
</div>
</div>
{/* Right Column - Image */}
<div className='w-full md:w-1/2'>
<Image
ref={brandStoryPicRef}
src={brandStoryPic}
alt='Right Side Image'
className='w-full h-auto'
className={`w-full h-auto zoom ${isBrandStoryPic ? 'post' : 'pre'}`}
sizes='100vw'
style={{
width: '100%',
@ -60,19 +78,26 @@ export default function HomePage() {
{/* Right Column - Text */}
<div className='w-full md:w-1/2'>
<div className='m-8 space-y-10'>
<h2 className='text-6xl font-normal text-justify font-montera'>
Discover the delicate art of our
</h2>
<h3 className='text-8xl font-normal text-justify font-samantha'>
Chocolate Tempering
</h3>
<p className='text-xl text-justify'>
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!
</p>
<AnimatedText finishClass='delay-300'>
<h2 className='text-6xl font-normal text-justify font-montera'>
Discover the delicate art of our
</h2>
<h3 className='text-8xl font-normal text-justify font-samantha'>
Chocolate Tempering
</h3>
</AnimatedText>
<AnimatedText
startClass='translate-y-8'
finishClass='translate-y-0'
>
<p className='text-xl text-justify'>
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!
</p>
</AnimatedText>
</div>
</div>
</section>
@ -97,6 +122,9 @@ export default function HomePage() {
In the Spotlight
</div>
<Spotlight />
{/* <a href='#' className='pill-button'>
Button Text
</a> */}
</section>
</div>
);

View File

@ -24,7 +24,7 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
} else {
setProduct(product);
}
} catch (err) {
} catch (err: unknown) {
setError(err);
} finally {
setIsLoading(false);
@ -39,7 +39,8 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
}
if (error) {
return <div>Something went wrong: {error.message}</div>;
console.log(error);
return <div>Something went wrong</div>;
}
if (!product) {
@ -52,7 +53,7 @@ const ProductDetails: FC<ProductDetailsProps> = ({ handle }) => {
<div className='flex flex-col md:flex-row gap-6 mt-4'>
{/* Product Images */}
<div className='flex-1'>
{product?.images?.length > 0 && (
{!!product.images?.length && (
<img
src={product.images[0].url}
alt={product.title}

View File

@ -20,8 +20,8 @@ export default function ProductsPage() {
<Link key={product.id} href={`/products/${product.handle}`}>
<div className='border border-gray-200 p-4 rounded-lg shadow-lg'>
<Image
src={product.thumbnail}
alt={product.title}
src={product.thumbnail as string}
alt={product.title as string}
width={300}
height={300}
className='object-cover rounded-md'

View File

@ -63,3 +63,29 @@ body {
font-weight: 400;
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;
}

View 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;
}
*/

View 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>
);
}

View 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;

View 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
View 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;