feature/coming_soon #3

Merged
hardik merged 4 commits from feature/coming_soon into main 2025-12-06 19:35:57 +05:30
12 changed files with 1000 additions and 94 deletions
Showing only changes of commit 2f79c22280 - Show all commits

Binary file not shown.

View File

@ -1,12 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Vite + React + TS</title> <title>The Kalawati</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>
</html> </html>

View File

@ -16,7 +16,8 @@
"@mui/material": "^7.3.2", "@mui/material": "^7.3.2",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-intersection-observer": "^9.16.0" "react-intersection-observer": "^9.16.0",
"react-router-dom": "^7.8.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",

View File

@ -1,18 +1,23 @@
// import { useState } from 'react' import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
// import reactLogo from './assets/react.svg' import "./App.css";
// import viteLogo from '/vite.svg' import VintageComingSoonPage from "./comingsoon/comingsoon";
import './App.css' import DarkProductShowcase from "./product/product";
// import VintageComingSoon from './comingsoon/comingsoon' // import OtherPage from "./pages/OtherPage"; // example if you add more pages
import VintageComingSoonPage from './comingsoon/comingsoon'
function App() { function App() {
// const [count, setCount] = useState(0)
return ( return (
<Router>
<Routes>
{/* Default route */}
<Route path="/" element={<VintageComingSoonPage />} />
<VintageComingSoonPage /> {/* Example extra routes */}
<Route path="/home" element={<DarkProductShowcase />} />
{/* <Route path="/about" element={<AboutPage />} /> */}
{/* <Route path="/contact" element={<ContactPage />} /> */}
</Routes>
</Router>
); );
} }
export default App export default App;

BIN
src/assets/bag1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

BIN
src/assets/bag2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

BIN
src/assets/group1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
src/assets/solo1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
src/assets/solo2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View File

@ -7,6 +7,12 @@ import TwitterIcon from '@mui/icons-material/Twitter';
import EmailIcon from '@mui/icons-material/Email'; import EmailIcon from '@mui/icons-material/Email';
import { keyframes } from '@emotion/react'; import { keyframes } from '@emotion/react';
import { styled } from '@mui/system'; import { styled } from '@mui/system';
import image1 from '../assets/group1.jpg'
import image2 from '../assets/solo1.jpg'
import image3 from '../assets/solo2.jpg'
import image4 from '../assets/bag1.jpg'
import image5 from '../assets/bag2.jpg'
// Color palette inspired by Indian heritage // Color palette inspired by Indian heritage
const colors = { const colors = {
@ -57,6 +63,60 @@ const scrollReveal = keyframes`
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
`; `;
const shimmer = keyframes`
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
`;
const textGlow = keyframes`
0% { text-shadow: 0 0 5px rgba(212, 160, 23, 0.3); }
50% { text-shadow: 0 0 15px rgba(212, 160, 23, 0.6); }
100% { text-shadow: 0 0 5px rgba(212, 160, 23, 0.3); }
`;
const borderFlow = keyframes`
0% { border-color: ${colors.accent}; }
33% { border-color: ${colors.secondary}; }
66% { border-color: ${colors.border}; }
100% { border-color: ${colors.accent}; }
`;
const typewriter = keyframes`
from { width: 0; }
to { width: 100%; }
`;
const fadeInLeft = keyframes`
from { opacity: 0; transform: translateX(-50px); }
to { opacity: 1; transform: translateX(0); }
`;
const fadeInRight = keyframes`
from { opacity: 0; transform: translateX(50px); }
to { opacity: 1; transform: translateX(0); }
`;
const bounce = keyframes`
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-15px); }
60% { transform: translateY(-7px); }
`;
const flipIn = keyframes`
0% { transform: perspective(400px) rotateY(90deg); opacity: 0; }
100% { transform: perspective(400px) rotateY(0deg); opacity: 1; }
`;
const zoomIn = keyframes`
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
`;
const spinSlow = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
// Styled components // Styled components
const VintageBox = styled(Box)({ const VintageBox = styled(Box)({
position: 'relative', position: 'relative',
@ -158,6 +218,35 @@ const AnimatedSection = styled(Box)({
animation: `${scrollReveal} 1s forwards`, animation: `${scrollReveal} 1s forwards`,
}); });
const GlowingText = styled(Typography)({
animation: `${textGlow} 3s ease-in-out infinite`,
});
const BorderFlowBox = styled(Box)({
border: `1px solid ${colors.accent}`,
animation: `${borderFlow} 6s infinite linear`,
});
const TypewriterText = styled(Typography)({
overflow: 'hidden',
whiteSpace: 'nowrap',
margin: '0 auto',
animation: `${typewriter} 4s steps(40, end)`,
});
const BounceBox = styled(Box)({
animation: `${bounce} 2s infinite`,
});
const FlipInBox = styled(Box)({
animation: `${flipIn} 1s forwards`,
transformOrigin: 'center',
});
const ZoomInBox = styled(Box)({
animation: `${zoomIn} 1s forwards`,
});
// Kalawati story sections with updated Pexels images // Kalawati story sections with updated Pexels images
const storyChapters = [ const storyChapters = [
{ {
@ -165,42 +254,42 @@ const storyChapters = [
title: 'Where Curiosity Met Craft', title: 'Where Curiosity Met Craft',
content: content:
'Fresh out of fashion school, I found myself drawn to looms, dye pits, and village homes. I organized workshops to listen, learn, and share space with artisans, forging a bond between design, community, and culture.', 'Fresh out of fashion school, I found myself drawn to looms, dye pits, and village homes. I organized workshops to listen, learn, and share space with artisans, forging a bond between design, community, and culture.',
image: 'https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image1}`,
}, },
{ {
year: '2020', year: '2020',
title: 'A Name Was Born', title: 'A Name Was Born',
content: content:
'During the pandemic, I launched a campaign to highlight artisans hit by lockdowns. The Kalawati was born—named after women who hold culture in their palms and creativity in their hearts.', 'During the pandemic, I launched a campaign to highlight artisans hit by lockdowns. The Kalawati was born—named after women who hold culture in their palms and creativity in their hearts.',
image: 'https://images.pexels.com/photos/31308739/pexels-photo-31308739.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image2}`,
}, },
{ {
year: '2021', year: '2021',
title: 'Strengthening the Circle', title: 'Strengthening the Circle',
content: content:
'I spent the year mapping artisan strengths, connecting traditional skills to modern demand, and building trust for a long-term vision of community and craft.', 'I spent the year mapping artisan strengths, connecting traditional skills to modern demand, and building trust for a long-term vision of community and craft.',
image: 'https://images.pexels.com/photos/3772504/pexels-photo-3772504.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image3}`,
}, },
{ {
year: '2022', year: '2022',
title: 'Systems That Serve People', title: 'Systems That Serve People',
content: content:
'Through the JSW Foundation Fellowship, I built systems blending business and empathy—brand building for rural women, value chain development, and sustainable income pathways.', 'Through the JSW Foundation Fellowship, I built systems blending business and empathy—brand building for rural women, value chain development, and sustainable income pathways.',
image: 'https://images.pexels.com/photos/163064/pexels-photo-163064.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image4}`,
}, },
{ {
year: '2023', year: '2023',
title: 'Quiet Creation', title: 'Quiet Creation',
content: content:
'Working with artisan clusters, we prototyped collections with handloom, embroidery, and natural dyes—crafting stories you could wear.', 'Working with artisan clusters, we prototyped collections with handloom, embroidery, and natural dyes—crafting stories you could wear.',
image: 'https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image1}`,
}, },
{ {
year: '2024', year: '2024',
title: 'Kalawati Arrives', title: 'Kalawati Arrives',
content: content:
'The Kalawati opens its doors as a brand and movement, offering ethically made handcrafted apparel and decor. Join us to co-create, support heritage, and connect to roots.', 'The Kalawati opens its doors as a brand and movement, offering ethically made handcrafted apparel and decor. Join us to co-create, support heritage, and connect to roots.',
image: 'https://images.pexels.com/photos/774859/pexels-photo-774859.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image5}`,
}, },
]; ];
@ -209,22 +298,22 @@ const processSteps = [
{ {
title: 'Design Inspiration', title: 'Design Inspiration',
description: 'Drawing from traditional Indian motifs and contemporary aesthetics', description: 'Drawing from traditional Indian motifs and contemporary aesthetics',
image: 'https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image4}`,
}, },
{ {
title: 'Material Selection', title: 'Material Selection',
description: 'Choosing the finest natural fibers and dyes', description: 'Choosing the finest natural fibers and dyes',
image: 'https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image5}`,
}, },
{ {
title: 'Artisan Crafting', title: 'Artisan Crafting',
description: 'Skilled hands weaving stories into fabric', description: 'Skilled hands weaving stories into fabric',
image: 'https://images.pexels.com/photos/163064/pexels-photo-163064.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image4}`,
}, },
{ {
title: 'Quality Assurance', title: 'Quality Assurance',
description: 'Meticulous inspection ensuring perfection', description: 'Meticulous inspection ensuring perfection',
image: 'https://images.pexels.com/photos/3772504/pexels-photo-3772504.jpeg?auto=compress&cs=tinysrgb&w=500', image: `${image5}`,
}, },
]; ];
@ -287,10 +376,12 @@ export const VintageComingSoonPage: React.FC = () => {
const handleSubscribe = (e: React.MouseEvent<HTMLButtonElement>) => { const handleSubscribe = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); e.preventDefault();
if (email && email.includes('@')) {
console.log('Subscribed with email:', email); console.log('Subscribed with email:', email);
setSubscribed(true); setSubscribed(true);
setEmail(''); setEmail('');
setTimeout(() => setSubscribed(false), 3000); setTimeout(() => setSubscribed(false), 3000);
}
}; };
return ( return (
@ -310,6 +401,39 @@ export const VintageComingSoonPage: React.FC = () => {
}} }}
/> />
{/* Animated decorative elements */}
<Box
sx={{
position: 'fixed',
top: '10%',
right: '5%',
width: '100px',
height: '100px',
backgroundImage: `linear-gradient(90deg, ${colors.accent}20, ${colors.secondary}30, ${colors.accent}20)`,
backgroundSize: '200px 100px',
borderRadius: '50%',
animation: `${shimmer} 2s infinite linear, ${spinSlow} 20s infinite linear`,
zIndex: 0,
opacity: 0.3,
}}
/>
<Box
sx={{
position: 'fixed',
bottom: '15%',
left: '5%',
width: '80px',
height: '80px',
backgroundImage: `linear-gradient(90deg, ${colors.secondary}20, ${colors.accent}30, ${colors.secondary}20)`,
backgroundSize: '200px 100px',
borderRadius: '50%',
animation: `${shimmer} 3s infinite linear, ${spinSlow} 25s infinite linear reverse`,
zIndex: 0,
opacity: 0.3,
}}
/>
{/* Parallax background elements */} {/* Parallax background elements */}
<Box <Box
sx={{ sx={{
@ -318,14 +442,18 @@ export const VintageComingSoonPage: React.FC = () => {
right: '5%', right: '5%',
width: '200px', width: '200px',
height: '200px', height: '200px',
backgroundImage: 'url(https://images.pexels.com/photos/28382914/pexels-photo-28382914.jpeg?auto=compress&cs=tinysrgb&w=500)', // backgroundColor: 'black',
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center',
opacity: 0.1, opacity: 0.1,
zIndex: 0, zIndex: 0,
transform: `rotate(${scrollPosition * 0.02}deg)`, transform: `rotate(${scrollPosition * 0.02}deg)`,
transition: 'transform 0.3s ease-out', transition: 'transform 0.3s ease-out',
borderRadius: '50%', // makes it circular
overflow: 'hidden', // ensures image stays inside the circle
}} }}
/> />
<Box <Box
sx={{ sx={{
position: 'fixed', position: 'fixed',
@ -333,15 +461,19 @@ export const VintageComingSoonPage: React.FC = () => {
left: '5%', left: '5%',
width: '150px', width: '150px',
height: '150px', height: '150px',
backgroundImage: 'url(https://images.pexels.com/photos/145939/pexels-photo-145939.jpeg?auto=compress&cs=tinysrgb&w=500)', // backgroundColor: 'black',
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center',
opacity: 0.1, opacity: 0.1,
zIndex: 0, zIndex: 0,
transform: `rotate(${-scrollPosition * 0.03}deg)`, transform: `rotate(${-scrollPosition * 0.03}deg)`,
transition: 'transform 0.3s ease-out', transition: 'transform 0.3s ease-out',
borderRadius: '50%', // circular shape
overflow: 'hidden', // ensures the image fits inside the circle
}} }}
/> />
<Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1, py: 6 }} ref={containerRef}> <Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1, py: 6 }} ref={containerRef}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${colors.border}`, py: 4, mb: 4 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${colors.border}`, py: 4, mb: 4 }}>
@ -361,6 +493,7 @@ export const VintageComingSoonPage: React.FC = () => {
</AnimatedSection> </AnimatedSection>
<AnimatedSection ref={heroRef} style={{ animationDelay: heroInView ? '0.2s' : '0s' }}> <AnimatedSection ref={heroRef} style={{ animationDelay: heroInView ? '0.2s' : '0s' }}>
<Box sx={{ display: 'flex', gap: 2 }}> <Box sx={{ display: 'flex', gap: 2 }}>
<BounceBox>
<IconButton <IconButton
href="https://instagram.com" href="https://instagram.com"
target="_blank" target="_blank"
@ -368,6 +501,8 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<InstagramIcon /> <InstagramIcon />
</IconButton> </IconButton>
</BounceBox>
<BounceBox sx={{ animationDelay: '0.1s' }}>
<IconButton <IconButton
href="https://facebook.com" href="https://facebook.com"
target="_blank" target="_blank"
@ -375,6 +510,8 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<FacebookIcon /> <FacebookIcon />
</IconButton> </IconButton>
</BounceBox>
<BounceBox sx={{ animationDelay: '0.2s' }}>
<IconButton <IconButton
href="https://twitter.com" href="https://twitter.com"
target="_blank" target="_blank"
@ -382,6 +519,7 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<TwitterIcon /> <TwitterIcon />
</IconButton> </IconButton>
</BounceBox>
</Box> </Box>
</AnimatedSection> </AnimatedSection>
</Box> </Box>
@ -450,7 +588,7 @@ export const VintageComingSoonPage: React.FC = () => {
flex: 1, flex: 1,
}} }}
> >
<Typography <GlowingText
variant="h1" variant="h1"
sx={{ sx={{
fontWeight: 300, fontWeight: 300,
@ -462,7 +600,7 @@ export const VintageComingSoonPage: React.FC = () => {
}} }}
> >
<span style={{ fontWeight: 400, color: colors.dark }}>A Journey</span> Woven with Stories, People & Purpose <span style={{ fontWeight: 400, color: colors.dark }}>A Journey</span> Woven with Stories, People & Purpose
</Typography> </GlowingText>
<Typography <Typography
variant="h5" variant="h5"
sx={{ sx={{
@ -544,7 +682,7 @@ export const VintageComingSoonPage: React.FC = () => {
{/* Email Subscription */} {/* Email Subscription */}
<Box> <Box>
<Box sx={{ display: 'flex', gap: 3, mb: 3, flexDirection: { xs: 'column', sm: 'row' } }}> <BorderFlowBox sx={{ display: 'flex', gap: 3, mb: 3, flexDirection: { xs: 'column', sm: 'row' }, p: '1px', borderRadius: '0' }}>
<input <input
type="email" type="email"
value={email} value={email}
@ -555,7 +693,7 @@ export const VintageComingSoonPage: React.FC = () => {
flex: 1, flex: 1,
padding: '16px 24px', padding: '16px 24px',
borderRadius: '0', borderRadius: '0',
border: `1px solid ${colors.border}`, border: 'none',
fontSize: '16px', fontSize: '16px',
outline: 'none', outline: 'none',
background: colors.paper, background: colors.paper,
@ -579,7 +717,7 @@ export const VintageComingSoonPage: React.FC = () => {
> >
Notify Me Notify Me
</VintagePulseButton> </VintagePulseButton>
</Box> </BorderFlowBox>
{subscribed && ( {subscribed && (
<Typography <Typography
sx={{ sx={{
@ -613,7 +751,7 @@ export const VintageComingSoonPage: React.FC = () => {
'&::before': { '&::before': {
content: '""', content: '""',
position: 'absolute', position: 'absolute',
top: 0, top: "20%",
left: '50%', left: '50%',
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
width: '1px', width: '1px',
@ -788,9 +926,10 @@ export const VintageComingSoonPage: React.FC = () => {
'&:hover': { '&:hover': {
transform: 'translateY(-5px)', transform: 'translateY(-5px)',
}, },
height: '100%',
}} }}
> >
<VintageImageFrame <FlipInBox
sx={{ sx={{
height: '200px', height: '200px',
width: '100%', width: '100%',
@ -799,7 +938,6 @@ export const VintageComingSoonPage: React.FC = () => {
backgroundPosition: 'center', backgroundPosition: 'center',
mb: 3, mb: 3,
filter: 'sepia(0.2) contrast(1.05)', filter: 'sepia(0.2) contrast(1.05)',
animation: `${scaleIn} 1s ease`,
}} }}
/> />
<Typography variant="h6" sx={{ fontWeight: 400, color: colors.ink, mb: 2, fontFamily: '"Playfair Display", serif' }}> <Typography variant="h6" sx={{ fontWeight: 400, color: colors.ink, mb: 2, fontFamily: '"Playfair Display", serif' }}>
@ -847,6 +985,7 @@ export const VintageComingSoonPage: React.FC = () => {
Join our journey to weave stories, empower artisans, and celebrate Indian heritage. Be the first to experience our handcrafted collections. Join our journey to weave stories, empower artisans, and celebrate Indian heritage. Be the first to experience our handcrafted collections.
</Typography> </Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 3, mb: 4 }}> <Box sx={{ display: 'flex', justifyContent: 'center', gap: 3, mb: 4 }}>
<BounceBox>
<IconButton <IconButton
href="https://instagram.com" href="https://instagram.com"
target="_blank" target="_blank"
@ -854,6 +993,8 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<InstagramIcon /> <InstagramIcon />
</IconButton> </IconButton>
</BounceBox>
<BounceBox sx={{ animationDelay: '0.1s' }}>
<IconButton <IconButton
href="https://facebook.com" href="https://facebook.com"
target="_blank" target="_blank"
@ -861,6 +1002,8 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<FacebookIcon /> <FacebookIcon />
</IconButton> </IconButton>
</BounceBox>
<BounceBox sx={{ animationDelay: '0.2s' }}>
<IconButton <IconButton
href="https://twitter.com" href="https://twitter.com"
target="_blank" target="_blank"
@ -868,12 +1011,15 @@ export const VintageComingSoonPage: React.FC = () => {
> >
<TwitterIcon /> <TwitterIcon />
</IconButton> </IconButton>
</BounceBox>
<BounceBox sx={{ animationDelay: '0.3s' }}>
<IconButton <IconButton
href="mailto:contact@thekalawati.com" href="mailto:contact@thekalawati.com"
sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }} sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }}
> >
<EmailIcon /> <EmailIcon />
</IconButton> </IconButton>
</BounceBox>
</Box> </Box>
<Typography <Typography
variant="caption" variant="caption"

708
src/product/product.tsx Normal file
View File

@ -0,0 +1,708 @@
import React, { useState } from 'react';
import { Box, Typography, Button, Container, IconButton, Grid, Card, Chip, CardMedia } from '@mui/material';
import { useInView } from 'react-intersection-observer';
import InstagramIcon from '@mui/icons-material/Instagram';
import FacebookIcon from '@mui/icons-material/Facebook';
import TwitterIcon from '@mui/icons-material/Twitter';
import EmailIcon from '@mui/icons-material/Email';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import VisibilityIcon from '@mui/icons-material/Visibility';
import { keyframes } from '@emotion/react';
import { styled } from '@mui/system';
import image1 from '../assets/group1.jpg'
import image2 from '../assets/solo1.jpg'
import image3 from '../assets/solo2.jpg'
import image4 from '../assets/bag1.jpg'
import image5 from '../assets/bag2.jpg'
// Lighter color palette for product showcase
const colors = {
lightBase: '#F8F7F4', // Very light cream
lightPaper: '#FFFFFF', // White
lightInk: '#3E3E3E', // Dark gray for text
accent: '#C19A6B', // Muted gold
secondary: '#8E7D6D', // Muted taupe
highlight: '#E8D9C7', // Light sand
border: '#D1C6B5', // Light tan border
dark: '#5C4B3A', // Dark brown for contrast
};
// Smooth animations
const fadeIn = keyframes`
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
`;
const scaleIn = keyframes`
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
`;
const float = keyframes`
0% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
100% { transform: translateY(0px); }
`;
const drawUnderline = keyframes`
0% { width: 0; }
100% { width: 100%; }
`;
const smoothPulse = keyframes`
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(193, 154, 107, 0.3); }
70% { transform: scale(1.01); box-shadow: 0 0 0 8px rgba(193, 154, 107, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(193, 154, 107, 0); }
`;
// Styled components
const LightVintageBox = styled(Box)({
position: 'relative',
backgroundColor: colors.lightBase,
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%235C4B3A' fill-opacity='0.05' fill-rule='evenodd'/%3E%3C/svg%3E")`,
opacity: 0.1,
zIndex: 0,
},
});
const LightVintageButton = styled(Button)({
position: 'relative',
overflow: 'hidden',
border: `1px solid ${colors.border}`,
color: colors.lightInk,
backgroundColor: colors.lightPaper,
fontWeight: 400,
letterSpacing: '0.5px',
textTransform: 'uppercase',
padding: '10px 22px',
borderRadius: '2px',
transition: 'all 0.3s ease',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
'&:hover': {
backgroundColor: colors.highlight,
color: colors.dark,
borderColor: colors.accent,
transform: 'translateY(-2px)',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
},
'&::after': {
content: '""',
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
height: '1px',
backgroundColor: colors.accent,
transform: 'scaleX(0)',
transformOrigin: 'right',
transition: 'transform 0.3s ease',
},
'&:hover::after': {
transform: 'scaleX(1)',
transformOrigin: 'left',
},
});
const LightVintagePulseButton = styled(LightVintageButton)({
animation: `${smoothPulse} 3s infinite`,
'&:hover': {
animation: 'none',
},
});
const LightVintageUnderline = styled(Box)({
position: 'relative',
display: 'inline-block',
'&::after': {
content: '""',
position: 'absolute',
bottom: '-4px',
left: 0,
width: '0',
height: '1.5px',
backgroundColor: colors.accent,
animation: `${drawUnderline} 1.2s forwards`,
animationDelay: '0.3s',
},
});
const AnimatedSection = styled(Box)({
opacity: 0,
transform: 'translateY(20px)',
animation: `${fadeIn} 0.8s forwards`,
});
// Product data
const products = [
{
id: 1,
name: 'Handwoven Silk Saree',
price: '₹12,999',
description: 'Traditional Banarasi silk with zari work',
image: `${image5}`,
category: 'Clothing',
tags: ['Handmade', 'Premium'],
},
{
id: 2,
name: 'Block Print Cotton Kurta',
price: '₹3,499',
description: 'Hand-block printed with natural dyes',
image: `${image4}`,
category: 'Clothing',
tags: ['Eco-friendly', 'Handmade'],
},
{
id: 3,
name: 'Brass Pooja Thali',
price: '₹2,899',
description: 'Handcrafted brass prayer set with intricate designs',
image: `${image4}`,
category: 'Home Decor',
tags: ['Ritual', 'Handcrafted'],
},
{
id: 4,
name: 'Embroidered Jutti',
price: '₹1,899',
description: 'Traditional Punjabi footwear with phulkari work',
image: `${image5}`,
category: 'Footwear',
tags: ['Handmade', 'Comfort'],
},
{
id: 5,
name: 'Handloom Cushion Covers',
price: '₹1,299',
description: 'Set of 2 handwoven cushion covers',
image: `${image4}`,
category: 'Home Decor',
tags: ['Handmade', 'Set'],
},
{
id: 6,
name: 'Silver Tribal Jewelry Set',
price: '₹5,999',
description: 'Traditional tribal necklace and earrings set',
image: `${image5}`,
category: 'Jewelry',
tags: ['Handcrafted', 'Silver'],
},
];
const categories = [
{ name: 'All', value: 'all' },
{ name: 'Clothing', value: 'Clothing' },
{ name: 'Home Decor', value: 'Home Decor' },
{ name: 'Jewelry', value: 'Jewelry' },
{ name: 'Footwear', value: 'Footwear' },
];
export const LightProductShowcase: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState('all');
const [hoveredProduct, setHoveredProduct] = useState<number | null>(null);
// Intersection Observer for scroll animations
const [heroRef, heroInView] = useInView({ threshold: 0.1, triggerOnce: true });
const [productsRef, productsInView] = useInView({ threshold: 0.1, triggerOnce: true });
const [footerRef, footerInView] = useInView({ threshold: 0.1, triggerOnce: true });
const filteredProducts = selectedCategory === 'all'
? products
: products.filter(product => product.category === selectedCategory);
return (
<LightVintageBox sx={{ minHeight: '100vh', color: colors.lightInk, overflow: 'hidden', position: 'relative' }}>
{/* Paper texture overlay */}
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundImage:
'url("data:image/svg+xml,%3Csvg width=\'600\' height=\'600\' viewBox=\'0 0 600 600\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noiseFilter\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.65\' numOctaves=\'3\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noiseFilter)\' opacity=\'0.03\'/%3E%3C/svg%3E")',
opacity: 0.3,
zIndex: 0,
}}
/>
{/* Animated decorative elements */}
<Box
sx={{
position: 'fixed',
top: '15%',
right: '5%',
width: '80px',
height: '80px',
background: `linear-gradient(135deg, ${colors.accent}15, ${colors.secondary}20, ${colors.accent}15)`,
borderRadius: '50%',
animation: `${float} 8s ease-in-out infinite`,
zIndex: 0,
opacity: 0.4,
}}
/>
<Box
sx={{
position: 'fixed',
bottom: '20%',
left: '5%',
width: '60px',
height: '60px',
background: `linear-gradient(135deg, ${colors.secondary}15, ${colors.accent}20, ${colors.secondary}15)`,
borderRadius: '50%',
animation: `${float} 7s ease-in-out infinite reverse`,
zIndex: 0,
opacity: 0.4,
}}
/>
<Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1, py: 5 }}>
{/* Header */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: `1px solid ${colors.border}`, py: 3, mb: 4 }}>
<AnimatedSection ref={heroRef} style={{ animationDelay: heroInView ? '0.1s' : '0s' }}>
<Typography
variant="h3"
sx={{
fontWeight: 500,
color: colors.lightInk,
letterSpacing: '2px',
fontFamily: '"Playfair Display", serif',
}}
>
<LightVintageUnderline>The Kalawati Collection</LightVintageUnderline>
</Typography>
</AnimatedSection>
<AnimatedSection ref={heroRef} style={{ animationDelay: heroInView ? '0.2s' : '0s' }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<IconButton
href="https://instagram.com"
target="_blank"
sx={{
color: colors.secondary,
'&:hover': { color: colors.accent, transform: 'scale(1.1)' },
transition: 'all 0.3s ease'
}}
>
<InstagramIcon />
</IconButton>
<IconButton
href="https://facebook.com"
target="_blank"
sx={{
color: colors.secondary,
'&:hover': { color: colors.accent, transform: 'scale(1.1)' },
transition: 'all 0.3s ease'
}}
>
<FacebookIcon />
</IconButton>
<IconButton
href="https://twitter.com"
target="_blank"
sx={{
color: colors.secondary,
'&:hover': { color: colors.accent, transform: 'scale(1.1)' },
transition: 'all 0.3s ease'
}}
>
<TwitterIcon />
</IconButton>
</Box>
</AnimatedSection>
</Box>
{/* Hero Section */}
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: 'center',
gap: 6,
py: 6,
minHeight: '50vh',
}}
>
<AnimatedSection
ref={heroRef}
style={{
animationDelay: heroInView ? '0.3s' : '0s',
flex: 1,
}}
>
<Box
sx={{
height: { xs: '350px', md: '450px' },
backgroundImage: `url(${image2})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
borderRadius: '4px',
boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
animation: `${scaleIn} 1s ease`,
}}
/>
</AnimatedSection>
<AnimatedSection
ref={heroRef}
style={{
animationDelay: heroInView ? '0.4s' : '0s',
flex: 1,
}}
>
<Typography
variant="h3"
sx={{
fontWeight: 300,
mb: 3,
fontSize: { xs: '2.2rem', md: '2.8rem' },
lineHeight: 1.3,
color: colors.lightInk,
fontFamily: '"Playfair Display", serif',
}}
>
<span style={{ fontWeight: 400, color: colors.dark }}>Timeless</span> Craftsmanship, Modern Elegance
</Typography>
<Typography
variant="h6"
sx={{
mb: 4,
color: colors.secondary,
fontWeight: 300,
fontStyle: 'italic',
fontFamily: '"Cormorant Garamond", serif',
pl: 3,
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
left: 0,
top: 0,
height: '100%',
width: '2px',
background: colors.accent,
},
}}
>
Each piece tells a story of tradition, skill, and the hands that crafted it with love and dedication.
</Typography>
<LightVintagePulseButton
variant="contained"
sx={{
px: 4,
py: 1.5,
fontWeight: 300,
fontFamily: '"Cormorant Garamond", serif',
letterSpacing: '1.5px',
color: colors.lightInk,
'&:hover': { background: colors.highlight },
}}
>
Explore Collection
</LightVintagePulseButton>
</AnimatedSection>
</Box>
{/* Category Filter */}
<Box sx={{ my: 6, textAlign: 'center' }}>
<AnimatedSection style={{ animationDelay: '0.1s' }}>
<Typography
variant="h5"
sx={{
mb: 3,
fontWeight: 300,
color: colors.lightInk,
fontFamily: '"Playfair Display", serif',
}}
>
Browse Categories
</Typography>
</AnimatedSection>
<Box sx={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap', gap: 1.5, mb: 5 }}>
{categories.map((category) => (
<Chip
key={category.value}
label={category.name}
onClick={() => setSelectedCategory(category.value)}
variant={selectedCategory === category.value ? "filled" : "outlined"}
sx={{
color: selectedCategory === category.value ? colors.lightPaper : colors.lightInk,
backgroundColor: selectedCategory === category.value ? colors.accent : 'transparent',
borderColor: colors.border,
fontFamily: '"Cormorant Garamond", serif',
fontSize: '1rem',
padding: '8px 16px',
'&:hover': {
backgroundColor: selectedCategory === category.value ? colors.accent : colors.highlight,
},
}}
/>
))}
</Box>
</Box>
{/* Products Grid */}
<Box ref={productsRef} sx={{ my: 8 }}>
<Grid container spacing={3}>
{filteredProducts.map((product, index) => (
<Grid key={product.id}>
<AnimatedSection
style={{
animationDelay: productsInView ? `${index * 0.1}s` : '0s',
}}
>
<Card
sx={{
background: colors.lightPaper,
border: `1px solid ${colors.border}`,
borderRadius: '4px',
overflow: 'hidden',
transition: 'all 0.3s ease',
height: '100%',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: `0 8px 24px rgba(0,0,0,0.1)`,
},
}}
onMouseEnter={() => setHoveredProduct(product.id)}
onMouseLeave={() => setHoveredProduct(null)}
>
<Box sx={{ position: 'relative', overflow: 'hidden' }}>
<CardMedia
component="img"
height="300"
image={product.image}
alt={product.name}
sx={{
transition: 'all 0.5s ease',
transform: hoveredProduct === product.id ? 'scale(1.05)' : 'scale(1)',
filter: 'brightness(0.98)',
}}
/>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `linear-gradient(to bottom, transparent 0%, ${colors.lightBase} 100%)`,
opacity: hoveredProduct === product.id ? 0.8 : 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
transition: 'all 0.3s ease',
}}
>
<IconButton sx={{ color: colors.lightInk, background: `${colors.lightPaper}`, '&:hover': { background: colors.accent, color: colors.lightPaper } }}>
<FavoriteBorderIcon />
</IconButton>
<IconButton sx={{ color: colors.lightInk, background: `${colors.lightPaper}`, '&:hover': { background: colors.accent, color: colors.lightPaper } }}>
<ShoppingCartIcon />
</IconButton>
<IconButton sx={{ color: colors.lightInk, background: `${colors.lightPaper}`, '&:hover': { background: colors.accent, color: colors.lightPaper } }}>
<VisibilityIcon />
</IconButton>
</Box>
<Box sx={{ position: 'absolute', top: 10, right: 10 }}>
{product.tags.map((tag, i) => (
<Chip
key={i}
label={tag}
size="small"
sx={{
background: colors.accent,
color: colors.lightPaper,
fontSize: '0.7rem',
height: '22px',
mb: 0.5,
display: 'block',
}}
/>
))}
</Box>
</Box>
<Box sx={{ p: 2.5, flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
<Typography
variant="h6"
sx={{
fontWeight: 400,
color: colors.lightInk,
fontFamily: '"Playfair Display", serif',
mb: 1,
}}
>
{product.name}
</Typography>
<Typography
variant="body2"
sx={{
color: colors.secondary,
fontFamily: '"Cormorant Garamond", serif',
mb: 2,
flexGrow: 1,
fontSize: '0.95rem',
}}
>
{product.description}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography
variant="h6"
sx={{
color: colors.accent,
fontFamily: '"Playfair Display", serif',
}}
>
{product.price}
</Typography>
<LightVintageButton size="small">
Add to Cart
</LightVintageButton>
</Box>
</Box>
</Card>
</AnimatedSection>
</Grid>
))}
</Grid>
</Box>
{/* Call to Action */}
<Box sx={{ my: 8, py: 8, textAlign: 'center', background: colors.highlight, borderRadius: '4px' }}>
<Container maxWidth="md">
<AnimatedSection style={{ animationDelay: '0.1s' }}>
<Typography
variant="h4"
sx={{
mb: 3,
fontWeight: 300,
color: colors.lightInk,
fontFamily: '"Playfair Display", serif',
}}
>
Join Our Artisan Community
</Typography>
</AnimatedSection>
<AnimatedSection style={{ animationDelay: '0.2s' }}>
<Typography
variant="body1"
sx={{
color: colors.secondary,
mb: 5,
fontSize: '1.05rem',
fontFamily: '"Cormorant Garamond", serif',
lineHeight: 1.7,
}}
>
By purchasing our products, you're not just buying beautiful handcrafted items—you're supporting traditional artisans and helping preserve India's rich cultural heritage for future generations.
</Typography>
</AnimatedSection>
<AnimatedSection style={{ animationDelay: '0.3s' }}>
<LightVintagePulseButton
variant="contained"
sx={{
px: 5,
py: 1.5,
fontWeight: 300,
fontFamily: '"Cormorant Garamond", serif',
letterSpacing: '1.5px',
color: colors.lightInk,
'&:hover': { background: colors.highlight },
}}
>
Discover Our Story
</LightVintagePulseButton>
</AnimatedSection>
</Container>
</Box>
{/* Footer */}
<Box ref={footerRef} sx={{ py: 5, textAlign: 'center', borderTop: `1px solid ${colors.border}` }}>
<AnimatedSection style={{ animationDelay: footerInView ? '0.1s' : '0s' }}>
<Typography
variant="h6"
sx={{
mb: 2,
fontWeight: 300,
color: colors.lightInk,
letterSpacing: '3px',
fontFamily: '"Playfair Display", serif',
textTransform: 'uppercase',
}}
>
Handcrafted Heritage
</Typography>
<Typography
variant="body2"
sx={{
color: colors.secondary,
mb: 3,
maxWidth: '600px',
margin: '0 auto',
fontSize: '0.95rem',
fontFamily: '"Cormorant Garamond", serif',
lineHeight: 1.6,
}}
>
Each piece tells a story of India's rich heritage and skilled artisanship.
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2, mb: 3 }}>
<IconButton
href="https://instagram.com"
target="_blank"
sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }}
>
<InstagramIcon />
</IconButton>
<IconButton
href="https://facebook.com"
target="_blank"
sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }}
>
<FacebookIcon />
</IconButton>
<IconButton
href="https://twitter.com"
target="_blank"
sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }}
>
<TwitterIcon />
</IconButton>
<IconButton
href="mailto:contact@thekalawati.com"
sx={{ color: colors.secondary, '&:hover': { color: colors.accent, transform: 'scale(1.1)' }, transition: 'all 0.3s ease' }}
>
<EmailIcon />
</IconButton>
</Box>
<Typography
variant="caption"
sx={{ color: colors.secondary, opacity: 0.7, fontSize: '0.8rem', fontFamily: '"Cormorant Garamond", serif' }}
>
© {new Date().getFullYear()} Handcrafted Heritage. All rights reserved.
</Typography>
</AnimatedSection>
</Box>
</Container>
</LightVintageBox>
);
};
export default LightProductShowcase;

View File

@ -1731,6 +1731,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cookie@npm:^1.0.1":
version: 1.0.2
resolution: "cookie@npm:1.0.2"
checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b
languageName: node
linkType: hard
"cosmiconfig@npm:^7.0.0": "cosmiconfig@npm:^7.0.0":
version: 7.1.0 version: 7.1.0
resolution: "cosmiconfig@npm:7.1.0" resolution: "cosmiconfig@npm:7.1.0"
@ -3771,6 +3778,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-router-dom@npm:^7.8.2":
version: 7.8.2
resolution: "react-router-dom@npm:7.8.2"
dependencies:
react-router: "npm:7.8.2"
peerDependencies:
react: ">=18"
react-dom: ">=18"
checksum: 10c0/b285182ffa1b26df5025f6e7952edca066928d5688d3447b02e4a7e699ca16941e8a42ecad65ab505914e27fed04a2023c92ae3ebf838226b4381a2e3a69ae01
languageName: node
linkType: hard
"react-router@npm:7.8.2":
version: 7.8.2
resolution: "react-router@npm:7.8.2"
dependencies:
cookie: "npm:^1.0.1"
set-cookie-parser: "npm:^2.6.0"
peerDependencies:
react: ">=18"
react-dom: ">=18"
peerDependenciesMeta:
react-dom:
optional: true
checksum: 10c0/e3122c2949bcad5e9c990cfb88e9cbd139e5a2a5c1d29664732623907a488634c0ddbf673d07af8f113d418f66270c174f014de8b885996722f431d09f5734be
languageName: node
linkType: hard
"react-transition-group@npm:^4.4.5": "react-transition-group@npm:^4.4.5":
version: 4.4.5 version: 4.4.5
resolution: "react-transition-group@npm:4.4.5" resolution: "react-transition-group@npm:4.4.5"
@ -4049,6 +4084,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"set-cookie-parser@npm:^2.6.0":
version: 2.7.1
resolution: "set-cookie-parser@npm:2.7.1"
checksum: 10c0/060c198c4c92547ac15988256f445eae523f57f2ceefeccf52d30d75dedf6bff22b9c26f756bd44e8e560d44ff4ab2130b178bd2e52ef5571bf7be3bd7632d9a
languageName: node
linkType: hard
"set-function-length@npm:^1.2.2": "set-function-length@npm:^1.2.2":
version: 1.2.2 version: 1.2.2
resolution: "set-function-length@npm:1.2.2" resolution: "set-function-length@npm:1.2.2"
@ -4393,6 +4435,7 @@ __metadata:
react: "npm:^19.1.1" react: "npm:^19.1.1"
react-dom: "npm:^19.1.1" react-dom: "npm:^19.1.1"
react-intersection-observer: "npm:^9.16.0" react-intersection-observer: "npm:^9.16.0"
react-router-dom: "npm:^7.8.2"
typescript: "npm:~5.8.3" typescript: "npm:~5.8.3"
typescript-eslint: "npm:^8.39.1" typescript-eslint: "npm:^8.39.1"
vite: "npm:^7.1.2" vite: "npm:^7.1.2"