Update : added scroll candle animation nd fixed header css #1

Merged
hardik merged 1 commits from new into main 2026-05-29 11:37:03 +05:30
17 changed files with 2579 additions and 870 deletions

View File

@ -1,6 +1,4 @@
import { Switch, Route, Router as WouterRouter } from "wouter"; import { Switch, Route, Router as WouterRouter } from "wouter";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import NotFound from "@/pages/not-found"; import NotFound from "@/pages/not-found";
import { Layout } from "@/components/Layout"; import { Layout } from "@/components/Layout";
@ -23,14 +21,11 @@ function Router() {
function App() { function App() {
return ( return (
<TooltipProvider>
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}> <WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
<Layout> <Layout>
<Router /> <Router />
</Layout> </Layout>
</WouterRouter> </WouterRouter>
<Toaster />
</TooltipProvider>
); );
} }

366
src/components/Layout.css Normal file
View File

@ -0,0 +1,366 @@
/* ─── Layout.css — Header & Footer ──────────────────────── */
/*
MARQUEE / TICKER BAR
*/
.ticker-bar {
background: var(--brand-purple-dark);
border-bottom: 1px solid rgba(212,162,76,0.25);
height: 36px;
overflow: hidden;
display: flex;
align-items: center;
}
.ticker-track {
display: flex;
align-items: center;
gap: 0;
animation: ticker-scroll 28s linear infinite;
white-space: nowrap;
will-change: transform;
}
.ticker-track:hover {
animation-play-state: paused;
}
@keyframes ticker-scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.ticker-item {
display: inline-flex;
align-items: center;
gap: 0.6rem;
font-family: var(--font-display);
font-size: 0.62rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: rgba(255,255,255,0.85);
padding: 0 2.5rem;
}
.ticker-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--brand-gold);
flex-shrink: 0;
display: inline-block;
}
/*
LOGO
*/
.logo-text {
font-family: var(--font-script);
font-size: 2rem;
color: #ffffff;
transition: color 0.3s ease;
line-height: 1;
}
.logo-text:hover { color: var(--brand-gold-light); }
.logo-img-wrap {
width: 44px;
height: 44px;
border-radius: 50%;
overflow: hidden;
border: 2px solid transparent;
transition: border-color 0.3s ease;
}
.logo-img-wrap:hover { border-color: var(--brand-gold); }
/*
NAV ITEM WITH DROPDOWN
*/
.nav-item {
position: relative;
}
.nav-link {
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #ffffff;
text-decoration: none;
padding-bottom: 2px;
border-bottom: 2px solid transparent;
transition: border-color 0.3s ease, color 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
cursor: pointer;
background: none;
border-left: none;
border-right: none;
border-top: none;
}
.nav-link:hover {
color: var(--brand-gold-light);
border-bottom-color: rgba(212,162,76,0.6);
}
.nav-link.active {
color: var(--brand-gold-light);
border-bottom-color: var(--brand-gold);
border-bottom-width: 2.5px;
text-shadow:
0 0 12px rgba(255,211,107,0.9),
0 0 24px rgba(212,162,76,0.5);
font-weight: 600;
background: rgba(212,162,76,0.12);
border-radius: 4px;
padding: 3px 10px 5px;
}
.nav-chevron {
transition: transform 0.25s ease;
opacity: 0.75;
}
.nav-item:hover .nav-chevron {
transform: rotate(180deg);
opacity: 1;
}
/* DROPDOWN PANEL */
.nav-dropdown {
position: absolute;
top: calc(100% + 18px);
left: 50%;
transform: translateX(-50%) translateY(-8px);
min-width: 200px;
background: rgba(45, 13, 82, 0.96);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(212,162,76,0.2);
border-radius: 8px;
padding: 0.6rem 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.25s ease;
box-shadow: 0 16px 40px rgba(0,0,0,0.35);
z-index: 200;
}
/* Arrow notch */
.nav-dropdown::before {
content: '';
position: absolute;
top: -7px;
left: 50%;
transform: translateX(-50%);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid rgba(212,162,76,0.25);
}
.nav-dropdown::after {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid rgba(45,13,82,0.96);
}
.nav-item:hover .nav-dropdown {
opacity: 1;
pointer-events: auto;
transform: translateX(-50%) translateY(0);
}
.dropdown-item {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.65rem 1.25rem;
font-family: var(--font-sans);
font-size: 0.83rem;
color: rgba(255,255,255,0.78);
text-decoration: none;
transition: background 0.2s ease, color 0.2s ease, padding-left 0.2s ease;
white-space: nowrap;
}
.dropdown-item:hover {
background: rgba(212,162,76,0.12);
color: var(--brand-gold-light);
padding-left: 1.6rem;
}
.dropdown-divider {
height: 1px;
background: rgba(255,255,255,0.08);
margin: 0.4rem 0;
}
/*
MOBILE TOGGLE
*/
.mobile-toggle {
background: none;
border: none;
color: #ffffff;
cursor: pointer;
display: flex;
align-items: center;
padding: 4px;
transition: color 0.3s ease;
}
.mobile-toggle:hover { color: var(--brand-gold-light); }
/*
MOBILE DRAWER
*/
.mobile-menu {
background: rgba(45, 13, 82, 0.97);
backdrop-filter: blur(16px);
border-bottom: 1px solid rgba(212, 162, 76, 0.2);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.mobile-nav-section {
display: flex;
flex-direction: column;
}
.mobile-nav-link {
font-family: var(--font-display);
font-size: 0.85rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #ffffff;
text-decoration: none;
padding: 0.8rem 0.5rem;
border-bottom: 1px solid rgba(255,255,255,0.07);
transition: color 0.3s ease, padding-left 0.2s ease;
display: block;
}
.mobile-nav-link:hover {
color: var(--brand-gold-light);
padding-left: 0.75rem;
}
.mobile-nav-link.active {
color: var(--brand-gold);
padding-left: 0.75rem;
text-shadow: 0 0 10px rgba(255,211,107,0.7);
font-weight: 600;
}
.mobile-dropdown-items {
display: flex;
flex-direction: column;
padding: 0.25rem 0 0.5rem 1rem;
gap: 0.1rem;
border-bottom: 1px solid rgba(255,255,255,0.07);
}
.mobile-dropdown-item {
font-family: var(--font-sans);
font-size: 0.82rem;
color: rgba(255,255,255,0.6);
text-decoration: none;
padding: 0.4rem 0.5rem;
transition: color 0.2s ease;
}
.mobile-dropdown-item:hover {
color: var(--brand-gold-light);
}
/*
FOOTER
*/
.site-footer {
background: var(--brand-purple-dark);
color: #ffffff;
padding: 4rem 1.5rem;
margin-top: 5rem;
}
.footer-inner {
max-width: 1280px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: 3rem;
text-align: center;
}
@media (min-width: 768px) {
.footer-inner {
grid-template-columns: 1fr 1fr 1fr;
text-align: left;
}
}
.footer-col {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
@media (min-width: 768px) {
.footer-col { align-items: flex-start; }
}
.footer-tagline {
font-family: var(--font-serif);
font-style: italic;
font-size: 1.1rem;
color: rgba(255,255,255,0.7);
}
.footer-heading {
font-family: var(--font-display);
font-size: 0.7rem;
letter-spacing: 0.25em;
text-transform: uppercase;
color: var(--brand-gold);
margin-bottom: 0.5rem;
}
.footer-link {
color: rgba(255,255,255,0.75);
font-size: 0.9rem;
text-decoration: none;
transition: color 0.3s ease;
}
.footer-link:hover { color: var(--brand-gold-light); }
.social-btn {
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255,255,255,0.08);
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
text-decoration: none;
transition: background 0.3s ease, color 0.3s ease;
}
.social-btn:hover {
background: var(--brand-gold);
color: var(--brand-purple-dark);
}
.footer-copy {
font-size: 0.78rem;
color: rgba(255,255,255,0.4);
margin-top: 1rem;
}

View File

@ -1,19 +1,71 @@
import { Link, useLocation } from "wouter"; import { Link, useLocation } from "wouter";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { Menu, X, Instagram, Facebook, Twitter } from "lucide-react"; import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import IconButton from "@mui/material/IconButton";
import { Menu, X, Instagram, Facebook, Twitter, ChevronDown, Flame, Sparkles, Star } from "lucide-react";
import { ScrollCandle } from "./ScrollCandle";
import "./Layout.css";
const logoPath = "/opengraph.jpg"; const logoPath = "/opengraph.jpg";
// ── Ticker messages ──────────────────────────────────────
const TICKER_ITEMS = [
{ icon: <Flame size={12} />, text: "Free shipping on orders over $50" },
{ icon: <Sparkles size={12} />, text: "New Collection — Summer Solstice" },
{ icon: <Star size={12} />, text: "Handpoured in small batches" },
{ icon: <Flame size={12} />, text: "100% Natural Soy Wax" },
{ icon: <Sparkles size={12} />, text: "Phthalate-free fragrance oils" },
{ icon: <Star size={12} />, text: "Gift wrapping available at checkout" },
];
// ── Nav structure with dropdowns ─────────────────────────
const NAV_LINKS = [
{ name: "Home", path: "/", dropdown: null },
{
name: "Products",
path: "/products",
dropdown: [
{ label: "All Collections", path: "/products" },
null, // divider
{ label: "Floral Scents", path: "/products" },
{ label: "Fresh Scents", path: "/products" },
{ label: "Woody Scents", path: "/products" },
{ label: "Sweet Scents", path: "/products" },
],
},
{
name: "About",
path: "/about",
dropdown: [
{ label: "Our Story", path: "/about" },
{ label: "Our Process", path: "/about" },
{ label: "Meet the Maker", path: "/about" },
],
},
{
name: "Contact",
path: "/contact",
dropdown: [
{ label: "Send a Message", path: "/contact" },
{ label: "Wholesale Inquiries", path: "/contact" },
null,
{ label: "Follow on Instagram", path: "/contact" },
],
},
];
export function Layout({ children }: { children: React.ReactNode }) { export function Layout({ children }: { children: React.ReactNode }) {
const [location] = useLocation(); const [location] = useLocation();
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileOpenSection, setMobileOpenSection] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => setScrolled(window.scrollY > 20);
setScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll); window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll);
}, []); }, []);
@ -21,83 +73,181 @@ export function Layout({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0); window.scrollTo(0, 0);
setMobileMenuOpen(false); setMobileMenuOpen(false);
setMobileOpenSection(null);
}, [location]); }, [location]);
const navLinks = [ // Duplicate ticker items to create seamless loop
{ name: "Home", path: "/" }, const allTickerItems = [...TICKER_ITEMS, ...TICKER_ITEMS];
{ name: "Products", path: "/products" },
{ name: "About", path: "/about" },
{ name: "Contact", path: "/contact" },
];
return ( return (
<div className="min-h-screen flex flex-col font-sans bg-background text-foreground"> <Box sx={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
<header
className={`fixed top-0 w-full z-50 transition-all duration-500 ease-in-out ${ {/* ── Ticker Bar ── */}
scrolled <Box
? "bg-background/80 backdrop-blur-md shadow-[0_1px_0_0_hsl(var(--accent))]" className="ticker-bar"
: "bg-transparent" sx={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 1200 }}
}`}
> >
<div className="max-w-7xl mx-auto px-6 h-24 flex items-center justify-between"> <div className="ticker-track">
<Link href="/" className="flex items-center gap-4 group"> {allTickerItems.map((item, i) => (
<div className="w-12 h-12 rounded-full overflow-hidden border-2 border-transparent group-hover:border-accent transition-colors duration-300"> <span className="ticker-item" key={i}>
<img src={logoPath} alt="Mani Candles Co. Logo" className="w-full h-full object-cover" /> <span className="ticker-dot" />
</div> {item.icon}
<span className="font-script text-3xl text-primary drop-shadow-sm group-hover:text-accent transition-colors duration-300"> {item.text}
Mani Candles
</span> </span>
))}
</div>
</Box>
{/* ── Main AppBar ── */}
<AppBar
position="fixed"
sx={{
top: 36, // sits below the ticker bar
background: scrolled
? "rgba(91,30,140,0.78)"
: "rgba(91,30,140,0.58)",
backdropFilter: "blur(22px) saturate(1.6)",
WebkitBackdropFilter: "blur(22px) saturate(1.6)",
boxShadow: scrolled ? "0 1px 0 0 rgba(212,162,76,0.5), 0 4px 24px rgba(58,18,96,0.35)" : "none",
transition: "background 0.5s ease, box-shadow 0.5s ease",
overflow: "visible",
}}
>
<Container maxWidth="xl">
<Toolbar
disableGutters
sx={{ height: 76, justifyContent: "space-between", overflow: "visible" }}
>
{/* Logo */}
<Link
href="/"
style={{ display: "flex", alignItems: "center", gap: "0.9rem", textDecoration: "none" }}
>
<div className="logo-img-wrap">
<img
src={logoPath}
alt="Mani Candles Co. Logo"
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</div>
<span className="logo-text">Mani Candles</span>
</Link> </Link>
<nav className="hidden md:flex items-center gap-8"> {/* Desktop Nav */}
{navLinks.map((link) => ( <Box
component="nav"
sx={{
display: { xs: "none", md: "flex" },
gap: 4,
alignItems: "center",
overflow: "visible",
position: "relative",
}}
>
{NAV_LINKS.map((link) => (
<div className="nav-item" key={link.path + link.name}>
<Link <Link
key={link.path}
href={link.path} href={link.path}
className={`font-display tracking-widest text-sm uppercase transition-colors duration-300 hover:text-accent ${ className={`nav-link${location === link.path ? " active" : ""}`}
location === link.path ? "text-accent" : "text-primary"
}`}
> >
{link.name} {link.name}
{link.dropdown && <ChevronDown size={13} className="nav-chevron" />}
</Link> </Link>
))}
</nav>
<button {/* Dropdown */}
className="md:hidden text-primary hover:text-accent transition-colors" {link.dropdown && (
<div className="nav-dropdown">
{link.dropdown.map((item, idx) =>
item === null ? (
<div key={`div-${idx}`} className="dropdown-divider" />
) : (
<Link key={item.label} href={item.path} className="dropdown-item">
{item.label}
</Link>
)
)}
</div>
)}
</div>
))}
</Box>
{/* Mobile Toggle */}
<IconButton
sx={{ display: { md: "none" }, color: "#ffffff" }}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
> >
{mobileMenuOpen ? <X size={28} /> : <Menu size={28} />} {mobileMenuOpen ? <X size={26} /> : <Menu size={26} />}
</button> </IconButton>
</div> </Toolbar>
</Container>
{/* Mobile Drawer */}
<AnimatePresence> <AnimatePresence>
{mobileMenuOpen && ( {mobileMenuOpen && (
<motion.div <motion.div
initial={{ opacity: 0, height: 0 }} initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }} animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }} exit={{ opacity: 0, height: 0 }}
className="md:hidden bg-background/95 backdrop-blur-md border-b border-accent/20 overflow-hidden" className="mobile-menu"
> >
<div className="px-6 py-8 flex flex-col gap-6"> {NAV_LINKS.map((link) => (
{navLinks.map((link) => ( <div className="mobile-nav-section" key={link.name}>
<Link {link.dropdown ? (
key={link.path} <>
href={link.path} <button
className={`font-display tracking-widest text-lg uppercase transition-colors duration-300 ${ className={`mobile-nav-link${location === link.path ? " active" : ""}`}
location === link.path ? "text-accent" : "text-primary" style={{ background: "none", border: "none", textAlign: "left", cursor: "pointer", width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center" }}
}`} onClick={() => setMobileOpenSection(mobileOpenSection === link.name ? null : link.name)}
> >
{link.name} {link.name}
<ChevronDown
size={14}
style={{
transition: "transform 0.25s ease",
transform: mobileOpenSection === link.name ? "rotate(180deg)" : "rotate(0deg)",
opacity: 0.7,
}}
/>
</button>
<AnimatePresence>
{mobileOpenSection === link.name && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="mobile-dropdown-items"
>
{link.dropdown.map((item, i) =>
item === null ? (
<div key={i} style={{ height: 1, background: "rgba(255,255,255,0.07)", margin: "0.2rem 0" }} />
) : (
<Link key={item.label} href={item.path} className="mobile-dropdown-item">
{item.label}
</Link> </Link>
))} )
</div> )}
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
</header> </>
) : (
<Link
href={link.path}
className={`mobile-nav-link${location === link.path ? " active" : ""}`}
>
{link.name}
</Link>
)}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</AppBar>
<main className="flex-grow pt-24"> {/* ── Page Content — offset for ticker + appbar ── */}
<Box component="main" sx={{ flexGrow: 1, pt: "112px" }}>
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key={location} key={location}
@ -109,52 +259,52 @@ export function Layout({ children }: { children: React.ReactNode }) {
{children} {children}
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
</main> </Box>
<footer className="bg-primary text-primary-foreground py-16 mt-20"> {/* ── Scroll Candle ── */}
<div className="max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-3 gap-12 text-center md:text-left"> <ScrollCandle />
<div className="flex flex-col items-center md:items-start gap-6">
<div className="flex items-center gap-4"> {/* ── Footer ── */}
<div className="w-16 h-16 rounded-full overflow-hidden bg-white p-1"> <footer className="site-footer">
<img src={logoPath} alt="Mani Candles Co. Logo" className="w-full h-full object-cover rounded-full" /> <div className="footer-inner">
<div className="footer-col">
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
<div style={{ width: 56, height: 56, borderRadius: "50%", overflow: "hidden", background: "#fff", padding: 3 }}>
<img
src={logoPath}
alt="Mani Candles Co."
style={{ width: "100%", height: "100%", objectFit: "cover", borderRadius: "50%" }}
/>
</div> </div>
<span className="font-script text-4xl text-secondary">Mani Candles</span> <span className="font-script" style={{ fontSize: "2rem", color: "var(--brand-gold-light)" }}>
Mani Candles
</span>
</div> </div>
<p className="font-serif italic text-xl text-secondary/80">Handmade Natural Heartmade</p> <p className="footer-tagline">Handmade Natural Heartmade</p>
</div> </div>
<div className="flex flex-col items-center md:items-start gap-4"> <div className="footer-col">
<h4 className="font-display tracking-widest text-accent uppercase mb-2">Explore</h4> <h4 className="footer-heading">Explore</h4>
{navLinks.map((link) => ( {NAV_LINKS.map((link) => (
<Link <Link key={link.path + link.name} href={link.path} className="footer-link">
key={link.path}
href={link.path}
className="hover:text-accent transition-colors font-sans font-light"
>
{link.name} {link.name}
</Link> </Link>
))} ))}
</div> </div>
<div className="flex flex-col items-center md:items-start gap-6"> <div className="footer-col">
<h4 className="font-display tracking-widest text-accent uppercase">Connect</h4> <h4 className="footer-heading">Connect</h4>
<div className="flex gap-4"> <div style={{ display: "flex", gap: "0.75rem" }}>
<a href="#" className="w-10 h-10 rounded-full bg-secondary/10 flex items-center justify-center hover:bg-accent hover:text-primary transition-all duration-300"> <a href="#" className="social-btn"><Instagram size={18} /></a>
<Instagram size={20} /> <a href="#" className="social-btn"><Facebook size={18} /></a>
</a> <a href="#" className="social-btn"><Twitter size={18} /></a>
<a href="#" className="w-10 h-10 rounded-full bg-secondary/10 flex items-center justify-center hover:bg-accent hover:text-primary transition-all duration-300">
<Facebook size={20} />
</a>
<a href="#" className="w-10 h-10 rounded-full bg-secondary/10 flex items-center justify-center hover:bg-accent hover:text-primary transition-all duration-300">
<Twitter size={20} />
</a>
</div> </div>
<p className="font-sans font-light text-sm text-secondary/60 mt-4"> <p className="footer-copy">
© {new Date().getFullYear()} Mani Candles Co. All rights reserved. © {new Date().getFullYear()} Mani Candles Co. All rights reserved.
</p> </p>
</div> </div>
</div> </div>
</footer> </footer>
</div> </Box>
); );
} }

View File

@ -0,0 +1,291 @@
/* ─── ScrollCandle.css ───────────────────────────────────── */
.scroll-candle-wrap {
position: fixed;
right: 28px;
bottom: 0px;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none;
filter: drop-shadow(0 0 18px rgba(255, 160, 40, 0.35));
}
/* ── FLAME SYSTEM ─────────────────────────────────── */
.candle-flame-wrap {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: -2px;
}
/* Outer glow halo */
.flame-glow {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48px;
height: 48px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 200, 50, 0.45) 0%, transparent 70%);
animation: glow-pulse 1.8s ease-in-out infinite alternate;
z-index: 0;
}
@keyframes glow-pulse {
0% {
transform: translateX(-50%) scale(1);
opacity: 0.7;
}
100% {
transform: translateX(-50%) scale(1.35);
opacity: 1;
}
}
/* Flame outer — large orange teardrop */
.flame-outer {
position: relative;
z-index: 2;
width: 18px;
height: 34px;
background: linear-gradient(to top, #ff6a00, #ffaa00, #ffe066);
border-radius: 50% 50% 30% 30% / 60% 60% 40% 40%;
transform-origin: bottom center;
animation: flicker-outer 0.9s ease-in-out infinite alternate;
box-shadow:
0 0 8px 3px rgba(255, 140, 0, 0.6),
0 0 18px 6px rgba(255, 100, 0, 0.4);
}
@keyframes flicker-outer {
0% {
transform: scaleX(1) scaleY(1) rotate(-2deg);
}
25% {
transform: scaleX(0.88) scaleY(1.05) rotate(2deg);
}
50% {
transform: scaleX(1.05) scaleY(0.95) rotate(-1deg);
}
75% {
transform: scaleX(0.92) scaleY(1.08) rotate(3deg);
}
100% {
transform: scaleX(1.02) scaleY(1.02) rotate(-2deg);
}
}
/* Flame inner — bright white-yellow core */
.flame-inner {
position: absolute;
bottom: 4px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
width: 8px;
height: 18px;
background: linear-gradient(to top, #fff8c5, #fff, rgba(255, 255, 255, 0));
border-radius: 50% 50% 30% 30% / 60% 60% 40% 40%;
animation: flicker-inner 0.7s ease-in-out infinite alternate;
opacity: 0.9;
}
@keyframes flicker-inner {
0% {
transform: translateX(-50%) scaleX(1) scaleY(1);
}
50% {
transform: translateX(-50%) scaleX(0.85) scaleY(1.1);
}
100% {
transform: translateX(-50%) scaleX(1.1) scaleY(0.9);
}
}
/* ── SMOKE PARTICLES ─────────────────────────────── */
.smoke-wrap {
position: absolute;
top: -18px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 20px;
pointer-events: none;
z-index: 4;
}
.smoke-particle {
position: absolute;
bottom: 0;
left: 50%;
width: 4px;
height: 4px;
border-radius: 50%;
background: rgba(200, 180, 220, 0.45);
transform: translateX(-50%);
animation: smoke-rise 2.4s ease-out infinite;
opacity: 0;
}
.smoke-particle:nth-child(2) {
animation-delay: 0.8s;
left: 40%;
}
.smoke-particle:nth-child(3) {
animation-delay: 1.6s;
left: 60%;
}
@keyframes smoke-rise {
0% {
transform: translateX(-50%) translateY(0) scale(0.6);
opacity: 0;
}
20% {
opacity: 0.5;
}
100% {
transform: translateX(-50%) translateY(-22px) scale(2);
opacity: 0;
}
}
/* ── WICK ──────────────────────────────────────────── */
.candle-wick {
width: 2px;
height: 8px;
background: linear-gradient(to bottom, #3a2000, #1a0f00);
border-radius: 1px;
position: relative;
z-index: 2;
margin-bottom: -1px;
}
/* ── CANDLE BODY ──────────────────────────────────── */
.candle-body {
position: relative;
width: 22px;
background: linear-gradient(to right,
#e8dcc8 0%,
#fdf6e3 25%,
#fff9f0 50%,
#fdf6e3 75%,
#d9cbb5 100%);
border-radius: 3px 3px 4px 4px;
transition: height 0.18s linear;
box-shadow:
inset -3px 0 6px rgba(0, 0, 0, 0.1),
inset 3px 0 4px rgba(255, 255, 255, 0.4);
overflow: visible;
}
/* Candle top melted rim */
.candle-body::before {
content: '';
position: absolute;
top: -3px;
left: -2px;
right: -2px;
height: 6px;
background: radial-gradient(ellipse at center, #ffe8b0 0%, #e8d5a0 100%);
border-radius: 50%;
z-index: 1;
}
/* ── WAX DRIPS ──────────────────────────────────────── */
.wax-drip {
position: absolute;
top: 2px;
width: 5px;
background: linear-gradient(to bottom, rgba(253, 246, 227, 0.95), rgba(232, 220, 200, 0.6));
border-radius: 0 0 4px 4px;
animation: drip-grow 4s ease-in-out infinite;
}
.wax-drip-1 {
left: -3px;
height: 14px;
animation-delay: 0s;
animation-duration: 4.5s;
}
.wax-drip-2 {
right: -3px;
height: 10px;
animation-delay: 1.2s;
animation-duration: 3.8s;
}
.wax-drip-3 {
left: 2px;
height: 18px;
animation-delay: 2.5s;
animation-duration: 5.2s;
}
@keyframes drip-grow {
0% {
height: 6px;
opacity: 0.7;
}
60% {
height: 20px;
opacity: 1;
}
100% {
height: 6px;
opacity: 0.7;
}
}
/* ── CANDLE BASE / HOLDER ────────────────────────── */
.candle-base {
width: 32px;
height: 10px;
background: linear-gradient(to bottom, #c8a96e, #a07840);
border-radius: 3px 3px 6px 6px;
box-shadow:
0 3px 8px rgba(0, 0, 0, 0.3),
inset 0 1px 2px rgba(255, 200, 100, 0.4);
position: relative;
}
/* Holder shine */
.candle-base::after {
content: '';
position: absolute;
top: 2px;
left: 5px;
width: 8px;
height: 3px;
background: rgba(255, 220, 140, 0.4);
border-radius: 50%;
}
/* ── SCROLL LABEL ───────────────────────────────── */
.candle-scroll-label {
margin-top: 6px;
font-family: var(--font-script);
font-size: 0.68rem;
color: rgba(212, 162, 76, 0.7);
letter-spacing: 0.05em;
white-space: nowrap;
text-align: center;
transition: opacity 0.3s ease;
}

View File

@ -0,0 +1,99 @@
import { useEffect, useState } from "react";
import "./ScrollCandle.css";
// Space taken by flame + wick + base + label + bottom offset + header (ticker 36 + AppBar 76) + safety gap
const NON_BODY_HEIGHT = 70 + 8 + 10 + 24 + 40 + 112 + 16;
const CANDLE_MIN_HEIGHT = 14; // px — almost fully melted
export function ScrollCandle() {
const [maxHeight, setMaxHeight] = useState(
() => Math.max(200, window.innerHeight - NON_BODY_HEIGHT)
);
const [candleHeight, setCandleHeight] = useState(
() => Math.max(200, window.innerHeight - NON_BODY_HEIGHT)
);
const [scrollPct, setScrollPct] = useState(0);
// Recalculate on resize
useEffect(() => {
const onResize = () => {
const newMax = Math.max(200, window.innerHeight - NON_BODY_HEIGHT);
setMaxHeight(newMax);
};
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
useEffect(() => {
const handleScroll = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const pct = docHeight > 0 ? Math.min(scrollTop / docHeight, 1) : 0;
setScrollPct(pct);
setCandleHeight(
Math.max(CANDLE_MIN_HEIGHT, maxHeight - pct * (maxHeight - CANDLE_MIN_HEIGHT))
);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [maxHeight]);
// Flame shrinks slightly as candle melts
const flameScale = 0.6 + (candleHeight / maxHeight) * 0.4;
// Label changes as candle melts
const label =
scrollPct < 0.15 ? "just lit" :
scrollPct < 0.45 ? "burning..." :
scrollPct < 0.75 ? "melting..." :
scrollPct < 0.92 ? "almost gone" :
"★ keep scrolling";
return (
<div className="scroll-candle-wrap" aria-hidden="true">
{/* ── Flame ── */}
<div
className="candle-flame-wrap"
style={{ transform: `scale(${flameScale})`, transformOrigin: "bottom center" }}
>
{/* Smoke */}
<div className="smoke-wrap">
<div className="smoke-particle" />
<div className="smoke-particle" />
<div className="smoke-particle" />
</div>
{/* Glow halo */}
<div className="flame-glow" />
{/* Outer flame */}
<div className="flame-outer">
{/* Inner core */}
<div className="flame-inner" />
</div>
</div>
{/* ── Wick ── */}
<div className="candle-wick" />
{/* ── Candle Body (shrinks on scroll) ── */}
<div className="candle-body" style={{ height: candleHeight }}>
<div className="wax-drip wax-drip-1" />
<div className="wax-drip wax-drip-2" />
<div className="wax-drip wax-drip-3" />
</div>
{/* ── Base / Holder ── */}
<div className="candle-base" />
{/* ── Label ── */}
<p
className="candle-scroll-label"
style={{ opacity: scrollPct > 0.02 ? 1 : 0 }}
>
{label}
</p>
</div>
);
}

View File

@ -1,176 +1,85 @@
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Great+Vibes&family=Inter:wght@300;400;500;600&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Great+Vibes&family=Inter:wght@300;400;500;600&display=swap');
@import "tailwindcss";
@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-card-border: hsl(var(--card-border));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-popover-border: hsl(var(--popover-border));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-primary-border: var(--primary-border);
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-secondary-border: var(--secondary-border);
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-muted-border: var(--muted-border);
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-accent-border: var(--accent-border);
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-destructive-border: var(--destructive-border);
--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
--color-chart-4: hsl(var(--chart-4));
--color-chart-5: hsl(var(--chart-5));
--color-sidebar: hsl(var(--sidebar));
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
--color-sidebar-border: hsl(var(--sidebar-border));
--color-sidebar-primary: hsl(var(--sidebar-primary));
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
--color-sidebar-primary-border: var(--sidebar-primary-border);
--color-sidebar-accent: hsl(var(--sidebar-accent));
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
--color-sidebar-accent-border: var(--sidebar-accent-border);
--color-sidebar-ring: hsl(var(--sidebar-ring));
--font-sans: var(--app-font-sans);
--font-serif: var(--app-font-serif);
--font-display: var(--app-font-display);
--font-script: var(--app-font-script);
--font-mono: var(--app-font-mono);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
/* ─── CSS Custom Properties ─────────────────────────────── */
:root { :root {
--background: 272 68% 94%;
--foreground: 272 65% 18%;
--card: 272 68% 97%;
--card-foreground: 272 65% 18%;
--card-border: 272 40% 85%;
--popover: 272 68% 97%;
--popover-foreground: 272 65% 18%;
--popover-border: 272 40% 85%;
--primary: 272 65% 34%;
--primary-foreground: 0 0% 100%;
--secondary: 272 68% 87%;
--secondary-foreground: 272 65% 25%;
--muted: 272 40% 90%;
--muted-foreground: 272 30% 50%;
--accent: 38 59% 56%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 272 40% 82%;
--input: 272 40% 82%;
--ring: 272 65% 34%;
--chart-1: 272 65% 34%;
--chart-2: 38 59% 56%;
--chart-3: 272 68% 70%;
--chart-4: 44 100% 71%;
--chart-5: 272 50% 55%;
--sidebar: 272 68% 87%;
--sidebar-foreground: 272 65% 18%;
--sidebar-border: 272 40% 80%;
--sidebar-primary: 272 65% 34%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 38 59% 56%;
--sidebar-accent-foreground: 0 0% 100%;
--sidebar-ring: 272 65% 34%;
--app-font-sans: 'Inter', sans-serif;
--app-font-serif: 'Cormorant Garamond', serif;
--app-font-display: 'Cinzel', serif;
--app-font-script: 'Great Vibes', cursive;
--app-font-mono: Menlo, monospace;
--radius: 0.75rem;
--brand-purple: #5B1E8C; --brand-purple: #5B1E8C;
--brand-purple-dark: #3A1260;
--brand-purple-mid: #7B3DB8;
--brand-lavender: #DCC6F2; --brand-lavender: #DCC6F2;
--brand-light-lavender: #EFE4FA; --brand-light-lavender:#EFE4FA;
--brand-card: #F8F2FD;
--brand-gold: #D4A24C; --brand-gold: #D4A24C;
--brand-flame-glow: #FFD36B; --brand-gold-light: #FFD36B;
--brand-flame-core: #FFF3C4; --brand-gold-core: #FFF3C4;
--brand-text: #2D0D52;
--brand-muted: #7B5A9E;
--brand-border: #D5BEF0;
--font-sans: 'Inter', sans-serif;
--font-serif: 'Cormorant Garamond', serif;
--font-display: 'Cinzel', serif;
--font-script: 'Great Vibes', cursive;
} }
.dark { /* ─── Reset / Base ───────────────────────────────────────── */
--background: 272 65% 10%; *, *::before, *::after {
--foreground: 272 68% 94%; box-sizing: border-box;
--card: 272 65% 15%; margin: 0;
--card-foreground: 272 68% 94%; padding: 0;
--card-border: 272 65% 25%;
--popover: 272 65% 15%;
--popover-foreground: 272 68% 94%;
--popover-border: 272 65% 25%;
--primary: 272 68% 87%;
--primary-foreground: 272 65% 18%;
--secondary: 272 65% 20%;
--secondary-foreground: 272 68% 94%;
--muted: 272 65% 20%;
--muted-foreground: 272 40% 70%;
--accent: 38 59% 56%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 272 65% 25%;
--input: 272 65% 25%;
--ring: 272 68% 87%;
--chart-1: 272 68% 87%;
--chart-2: 38 59% 56%;
--chart-3: 272 68% 70%;
--chart-4: 44 100% 71%;
--chart-5: 272 50% 55%;
} }
@layer base { html {
* { scroll-behavior: smooth;
@apply border-border;
}
body {
@apply font-sans antialiased bg-background text-foreground;
}
h1, h2, h3, h4, h5, h6 {
@apply font-serif;
}
} }
body {
font-family: var(--font-sans);
background-color: var(--brand-light-lavender);
color: var(--brand-text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-serif);
line-height: 1.2;
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
display: block;
}
/* ─── Keyframe Animations ────────────────────────────────── */
@keyframes flame-flicker { @keyframes flame-flicker {
0%, 100% { 0%, 100% { transform: scale(1) translateY(0); opacity: 0.9; }
transform: scale(1) translateY(0); 50% { transform: scale(1.05) translateY(-2px); opacity: 1; }
opacity: 0.9;
}
50% {
transform: scale(1.05) translateY(-2px);
opacity: 1;
}
} }
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
/* ─── Global Utility Classes ─────────────────────────────── */
.animate-flame { .animate-flame {
animation: flame-flicker 2s ease-in-out infinite alternate; animation: flame-flicker 2s ease-in-out infinite alternate;
} }
.font-script {
font-family: var(--font-script);
}
.font-display {
font-family: var(--font-display);
}
.font-serif {
font-family: var(--font-serif);
}

View File

@ -1,5 +1,13 @@
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import App from "./App"; import App from "./App";
import theme from "./theme";
import "./index.css"; import "./index.css";
createRoot(document.getElementById("root")!).render(<App />); createRoot(document.getElementById("root")!).render(
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);

View File

@ -1,139 +1,105 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Droplets, Flame, HeartHandshake } from "lucide-react"; import { Droplets, Flame, HeartHandshake } from "lucide-react";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import "./about.css";
const logoPath = "/opengraph.jpg"; const logoPath = "/opengraph.jpg";
export default function AboutPage() { const fadeInUp = {
const fadeInUp = {
hidden: { opacity: 0, y: 30 }, hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }, visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
}; };
const stagger = { const stagger = {
visible: { transition: { staggerChildren: 0.2 } }, visible: { transition: { staggerChildren: 0.2 } },
}; };
export default function AboutPage() {
return ( return (
<div className="w-full min-h-screen bg-background"> <Box>
{/* Hero */} {/* ── Hero ── */}
<section className="py-24 px-6 bg-card"> <section className="about-hero">
<motion.div <Container maxWidth="md">
initial="hidden" <motion.div initial="hidden" animate="visible" variants={stagger}>
animate="visible" <motion.h1 variants={fadeInUp}>The Light Behind the Wax</motion.h1>
variants={stagger} <div className="section-divider" />
className="max-w-4xl mx-auto text-center" <motion.p variants={fadeInUp}>
> Mani Candles Co. is a small handmade candle brand crafted with love, natural ingredients, and intention.
<motion.h1 variants={fadeInUp} className="font-serif text-5xl md:text-6xl lg:text-7xl text-primary mb-6"> Every candle is poured by hand in small batches, designed to bring warmth, luxury, and a touch of romance
The Light Behind the Wax into your sacred spaces.
</motion.h1>
<motion.div variants={fadeInUp} className="w-24 h-1 bg-accent mx-auto mb-8" />
<motion.p variants={fadeInUp} className="font-sans text-lg text-muted-foreground leading-relaxed">
Mani Candles Co. is a small handmade candle brand crafted with love, natural ingredients, and intention. Every candle is poured by hand in small batches, designed to bring warmth, luxury, and a touch of romance into your sacred spaces.
</motion.p> </motion.p>
</motion.div> </motion.div>
</Container>
</section> </section>
{/* Brand Values */} {/* ── Brand Values ── */}
<section className="py-24 px-6"> <section className="about-values">
<div className="max-w-6xl mx-auto"> <Container maxWidth="lg">
<div className="grid grid-cols-1 md:grid-cols-3 gap-12"> <div className="values-grid">
{[ {[
{ { Icon: HeartHandshake, title: "Handmade", desc: "We believe in the power of human touch. No machines, no mass production. Just careful hands pouring beautiful creations." },
icon: <HeartHandshake size={40} />, { Icon: Droplets, title: "Natural", desc: "Pure soy wax, lead-free cotton wicks, and premium fragrance oils infused with essential oils. Clean burns only." },
title: "Handmade", { Icon: Flame, title: "Heartmade", desc: "A candle is more than wax and scent. It's ambiance, memory, and comfort. We put our heart into every jar." },
desc: "We believe in the power of human touch. No machines, no mass production. Just careful hands pouring beautiful creations." ].map(({ Icon, title, desc }, i) => (
},
{
icon: <Droplets size={40} />,
title: "Natural",
desc: "Pure soy wax, lead-free cotton wicks, and premium fragrance oils infused with essential oils. Clean burns only."
},
{
icon: <Flame size={40} />,
title: "Heartmade",
desc: "A candle is more than wax and scent. It's ambiance, memory, and comfort. We put our heart into every jar."
}
].map((val, i) => (
<motion.div <motion.div
key={i} key={title}
className="value-card"
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={{ once: true }} viewport={{ once: true }}
variants={{ variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } }
}}
className="text-center p-8 bg-card rounded-xl shadow-sm border border-border/50 hover:shadow-md transition-shadow"
> >
<div className="text-accent mb-6 flex justify-center">{val.icon}</div> <div className="value-icon"><Icon size={40} /></div>
<h3 className="font-serif text-2xl text-primary mb-4">{val.title}</h3> <h3>{title}</h3>
<p className="font-sans text-muted-foreground">{val.desc}</p> <p>{desc}</p>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </Container>
</section> </section>
{/* Meet the Maker */} {/* ── Meet the Maker ── */}
<section className="py-24 px-6 bg-primary text-primary-foreground relative overflow-hidden"> <section className="about-maker">
<div className="absolute inset-0 bg-gradient-to-r from-transparent to-primary/50 mix-blend-overlay" /> <div className="maker-gradient-overlay" />
<div className="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-16 items-center relative z-10"> <Container maxWidth="lg">
<div className="maker-grid">
<motion.div <motion.div
className="maker-img-wrap"
initial={{ opacity: 0, x: -50 }} initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
className="aspect-square rounded-2xl overflow-hidden shadow-2xl relative"
> >
<img <img src={logoPath} alt="Mani Candles Co. Logo Art" />
src={logoPath} <div className="maker-img-ring" />
alt="Mani Candles Co. Logo Art"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 ring-1 ring-inset ring-white/20 rounded-2xl" />
</motion.div> </motion.div>
<motion.div <motion.div
className="maker-content"
initial={{ opacity: 0, x: 50 }} initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
> >
<h2 className="font-serif text-4xl md:text-5xl mb-6">Meet the Maker</h2> <h2>Meet the Maker</h2>
<div className="w-20 h-1 bg-accent mb-8" /> <div className="maker-divider" />
<div className="font-sans space-y-6 text-primary-foreground/90 text-lg leading-relaxed"> <p>What started as a quiet evening hobby quickly blossomed into a deep passion for the alchemy of scent and light.</p>
<p> <p>I wanted to create more than just candles; I wanted to craft experiences. The flicker of the flame, the slow release of carefully blended perfumesthese elements have the power to transform a chaotic day into a moment of pure serenity.</p>
What started as a quiet evening hobby quickly blossomed into a deep passion for the alchemy of scent and light. <p>Every Mani candle is a piece of my heart, poured in small batches to ensure that the luxury you experience is entirely personal and authentically handmade.</p>
</p> <span className="maker-signature">With love,</span>
<p>
I wanted to create more than just candles; I wanted to craft experiences. The flicker of the flame, the slow release of carefully blended perfumesthese elements have the power to transform a chaotic day into a moment of pure serenity.
</p>
<p>
Every Mani candle is a piece of my heart, poured in small batches to ensure that the luxury you experience is entirely personal and authentically handmade.
</p>
<div className="pt-6">
<span className="font-script text-4xl text-accent">With love,</span>
</div>
</div>
</motion.div> </motion.div>
</div> </div>
</Container>
</section> </section>
{/* Process Timeline */} {/* ── Process Timeline ── */}
<section className="py-24 px-6 bg-background"> <section className="about-process">
<div className="max-w-5xl mx-auto"> <Container maxWidth="md">
<motion.div <h2>Our Process</h2>
initial="hidden" <p className="subtitle">The journey of a Mani Candle.</p>
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
className="text-center mb-20"
>
<h2 className="font-serif text-4xl text-primary mb-4">Our Process</h2>
<p className="font-sans text-muted-foreground">The journey of a Mani Candle.</p>
</motion.div>
<div className="space-y-12"> <div className="timeline">
{[ {[
{ step: "01", title: "Sourcing", desc: "We select only the finest natural soy wax and premium, phthalate-free fragrances." }, { step: "01", title: "Sourcing", desc: "We select only the finest natural soy wax and premium, phthalate-free fragrances." },
{ step: "02", title: "Blending", desc: "Fragrances are meticulously mixed at the exact temperature for optimal throw." }, { step: "02", title: "Blending", desc: "Fragrances are meticulously mixed at the exact temperature for optimal throw." },
@ -143,24 +109,22 @@ export default function AboutPage() {
].map((item, i) => ( ].map((item, i) => (
<motion.div <motion.div
key={i} key={i}
className="timeline-item"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }} transition={{ delay: i * 0.1, duration: 0.5 }}
className="flex flex-col md:flex-row gap-6 md:gap-12 items-start md:items-center"
> >
<div className="font-serif text-6xl text-accent/30 font-light w-24 flex-shrink-0"> <div className="timeline-step">{item.step}</div>
{item.step} <div className="timeline-body">
</div> <h3>{item.title}</h3>
<div className="flex-grow pb-8 border-b border-border/50"> <p>{item.desc}</p>
<h3 className="font-serif text-2xl text-primary mb-2">{item.title}</h3>
<p className="font-sans text-muted-foreground">{item.desc}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </Container>
</section> </section>
</div> </Box>
); );
} }

View File

@ -1,6 +1,15 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Mail, MapPin, Phone, Instagram } from "lucide-react"; import { Mail, MapPin, Phone, Instagram } from "lucide-react";
import { HeartHandshake } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import "./contact.css";
const fadeInUp = {
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
};
export default function ContactPage() { export default function ContactPage() {
const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success">("idle"); const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success">("idle");
@ -8,197 +17,117 @@ export default function ContactPage() {
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setFormStatus("submitting"); setFormStatus("submitting");
// Simulate network request setTimeout(() => setFormStatus("success"), 1500);
setTimeout(() => {
setFormStatus("success");
}, 1500);
};
const fadeInUp = {
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
}; };
return ( return (
<div className="w-full min-h-screen bg-background"> <Box>
<section className="py-24 px-6 bg-card text-center"> {/* ── Hero ── */}
<motion.div <section className="contact-hero">
initial="hidden" <Container maxWidth="md">
animate="visible" <motion.div initial="hidden" animate="visible" variants={fadeInUp}>
variants={fadeInUp} <h1>Get in Touch</h1>
className="max-w-3xl mx-auto" <p>Whether you have a question about an order, wholesale inquiries, or simply want to share how much you love your new candle, we'd love to hear from you.</p>
> <div className="section-divider" />
<h1 className="font-serif text-5xl md:text-6xl text-primary mb-6">Get in Touch</h1>
<p className="font-sans text-muted-foreground text-lg mb-8">
Whether you have a question about an order, wholesale inquiries, or simply want to share how much you love your new candle, we'd love to hear from you.
</p>
<div className="w-24 h-1 bg-accent mx-auto" />
</motion.div> </motion.div>
</Container>
</section> </section>
<section className="py-20 px-6"> {/* ── Contact Body ── */}
<div className="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 gap-16"> <section className="contact-body">
<Container maxWidth="lg">
{/* Contact Info */} <div className="contact-grid">
{/* Info Panel */}
<motion.div <motion.div
className="contact-info-panel"
initial={{ opacity: 0, x: -30 }} initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
className="flex flex-col gap-10"
> >
<h2>Contact Information</h2>
<div className="contact-info-list">
{[
{ Icon: Mail, label: "Email", value: <a href="mailto:hello@manicandles.co">hello@manicandles.co</a> },
{ Icon: Phone, label: "Phone", value: <>Available MonFri, 9am5pm EST<br />+1 (555) 123-4567</> },
{ Icon: MapPin, label: "Studio", value: <>New York, NY<br />(By appointment only)</> },
].map(({ Icon, label, value }) => (
<div key={label} className="contact-info-item">
<div className="contact-info-icon"><Icon size={20} /></div>
<div> <div>
<h2 className="font-serif text-3xl text-primary mb-8">Contact Information</h2> <div className="contact-info-label">{label}</div>
<div className="space-y-6"> <div className="contact-info-value">{value}</div>
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-full bg-secondary/50 flex items-center justify-center text-primary flex-shrink-0">
<Mail size={20} />
</div> </div>
<div>
<h4 className="font-display tracking-widest uppercase text-sm text-primary mb-1">Email</h4>
<a href="mailto:hello@manicandles.co" className="font-sans text-muted-foreground hover:text-accent transition-colors">
hello@manicandles.co
</a>
</div> </div>
))}
</div> </div>
<div className="flex items-start gap-4"> <div className="contact-social-box">
<div className="w-12 h-12 rounded-full bg-secondary/50 flex items-center justify-center text-primary flex-shrink-0"> <h3>Follow Our Journey</h3>
<Phone size={20} /> <p>Join our community on Instagram for behind-the-scenes peeks, styling inspiration, and launch announcements.</p>
</div> <a href="#" className="contact-ig-link">
<div>
<h4 className="font-display tracking-widest uppercase text-sm text-primary mb-1">Phone</h4>
<p className="font-sans text-muted-foreground">
Available Mon-Fri, 9am - 5pm EST<br/>
+1 (555) 123-4567
</p>
</div>
</div>
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-full bg-secondary/50 flex items-center justify-center text-primary flex-shrink-0">
<MapPin size={20} />
</div>
<div>
<h4 className="font-display tracking-widest uppercase text-sm text-primary mb-1">Studio</h4>
<p className="font-sans text-muted-foreground">
New York, NY<br/>
(By appointment only)
</p>
</div>
</div>
</div>
</div>
<div className="p-8 bg-card border border-border rounded-xl mt-4">
<h3 className="font-serif text-2xl text-primary mb-4">Follow Our Journey</h3>
<p className="font-sans text-muted-foreground mb-6">
Join our community on Instagram for behind-the-scenes peeks, styling inspiration, and launch announcements.
</p>
<a href="#" className="inline-flex items-center gap-2 text-primary hover:text-accent transition-colors font-medium">
<Instagram size={20} /> <Instagram size={20} />
<span>@manicandles.co</span> <span>@manicandles.co</span>
</a> </a>
</div> </div>
</motion.div> </motion.div>
{/* Form */} {/* Form Panel */}
<motion.div <motion.div
className="contact-form-panel"
initial={{ opacity: 0, x: 30 }} initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }} animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
className="bg-white p-8 md:p-12 shadow-xl border border-border/50 rounded-none relative"
> >
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-primary via-accent to-primary" /> <div className="contact-form-top-bar" />
<h2 className="font-serif text-3xl text-primary mb-8">Send a Message</h2>
{formStatus === "success" ? ( {formStatus === "success" ? (
<motion.div <motion.div
className="contact-success"
initial={{ opacity: 0, scale: 0.9 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
className="flex flex-col items-center justify-center text-center py-12"
> >
<div className="w-16 h-16 bg-green-50 text-green-600 rounded-full flex items-center justify-center mb-6"> <div className="success-icon-wrap"><HeartHandshake size={32} /></div>
<HeartHandshake size={32} /> <h3>Thank you!</h3>
</div> <p>Your message has been received. We will get back to you as soon as possible.</p>
<h3 className="font-serif text-2xl text-primary mb-2">Thank you!</h3> <button className="send-another-btn" onClick={() => setFormStatus("idle")}>Send another</button>
<p className="font-sans text-muted-foreground">
Your message has been received. We will get back to you as soon as possible.
</p>
<button
onClick={() => setFormStatus("idle")}
className="mt-8 text-accent hover:text-primary transition-colors font-display uppercase tracking-widest text-sm border-b border-accent pb-1"
>
Send another
</button>
</motion.div> </motion.div>
) : ( ) : (
<form onSubmit={handleSubmit} className="space-y-6"> <>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <h2>Send a Message</h2>
<div className="space-y-2"> <form onSubmit={handleSubmit}>
<label htmlFor="name" className="font-display uppercase tracking-widest text-xs text-primary">Name</label> <div className="form-row">
<input <div className="form-field">
id="name" <label htmlFor="name" className="form-label">Name</label>
type="text" <input id="name" type="text" className="form-input" placeholder="Your name" required />
required
className="w-full bg-background border-b border-border py-3 px-4 focus:outline-none focus:border-accent transition-colors font-sans"
placeholder="Your name"
/>
</div> </div>
<div className="space-y-2"> <div className="form-field">
<label htmlFor="email" className="font-display uppercase tracking-widest text-xs text-primary">Email</label> <label htmlFor="email" className="form-label">Email</label>
<input <input id="email" type="email" className="form-input" placeholder="Your email" required />
id="email"
type="email"
required
className="w-full bg-background border-b border-border py-3 px-4 focus:outline-none focus:border-accent transition-colors font-sans"
placeholder="Your email"
/>
</div> </div>
</div> </div>
<div className="form-field">
<div className="space-y-2"> <label htmlFor="subject" className="form-label">Subject</label>
<label htmlFor="subject" className="font-display uppercase tracking-widest text-xs text-primary">Subject</label> <input id="subject" type="text" className="form-input" placeholder="How can we help?" required />
<input
id="subject"
type="text"
required
className="w-full bg-background border-b border-border py-3 px-4 focus:outline-none focus:border-accent transition-colors font-sans"
placeholder="How can we help?"
/>
</div> </div>
<div className="form-field">
<div className="space-y-2"> <label htmlFor="message" className="form-label">Message</label>
<label htmlFor="message" className="font-display uppercase tracking-widest text-xs text-primary">Message</label> <textarea id="message" className="form-textarea" placeholder="Write your message here..." required />
<textarea
id="message"
rows={5}
required
className="w-full bg-background border border-border py-3 px-4 focus:outline-none focus:border-accent transition-colors font-sans resize-none"
placeholder="Write your message here..."
/>
</div> </div>
<button type="submit" className="form-submit" disabled={formStatus === "submitting"}>
<button
type="submit"
disabled={formStatus === "submitting"}
className="w-full py-4 bg-primary text-primary-foreground font-display tracking-widest uppercase text-sm hover:bg-accent transition-colors disabled:opacity-70"
>
{formStatus === "submitting" ? "Sending..." : "Send Message"} {formStatus === "submitting" ? "Sending..." : "Send Message"}
</button> </button>
</form> </form>
</>
)} )}
</motion.div> </motion.div>
</div> </div>
</Container>
</section> </section>
<section className="py-16 text-center bg-background px-6"> {/* ── Closing Quote ── */}
<p className="font-script text-3xl text-primary max-w-xl mx-auto"> <section className="contact-quote">
"May the light of our candles bring peace to your mind and warmth to your soul." <p>"May the light of our candles bring peace to your mind and warmth to your soul."</p>
</p>
</section> </section>
</div> </Box>
); );
} }
// Ensure HeartHandshake is imported
import { HeartHandshake } from "lucide-react";

View File

@ -1,5 +1,7 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useState } from "react"; import { useState } from "react";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import birthdayImage from "@/assets/images/product-birthday.png"; import birthdayImage from "@/assets/images/product-birthday.png";
import eucalyptusImage from "@/assets/images/product-eucalyptus.png"; import eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import jasmineImage from "@/assets/images/product-jasmine.png"; import jasmineImage from "@/assets/images/product-jasmine.png";
@ -8,124 +10,56 @@ import linenImage from "@/assets/images/product-linen.png";
import oudImage from "@/assets/images/product-oud.png"; import oudImage from "@/assets/images/product-oud.png";
import roseImage from "@/assets/images/product-rose.png"; import roseImage from "@/assets/images/product-rose.png";
import sandalwoodImage from "@/assets/images/product-sandalwood.png"; import sandalwoodImage from "@/assets/images/product-sandalwood.png";
import "./products.css";
const PRODUCTS = [ const PRODUCTS = [
{ { id: 1, name: "Lavender Dream", scent: "Calming French Lavender, Vanilla, & Bergamot", price: "$28", image: lavenderImage, category: "Floral" },
id: 1, { id: 2, name: "Rose & Vanilla", scent: "Damask Rose, Sweet Vanilla Bean, & Amber", price: "$32", image: roseImage, category: "Floral" },
name: "Lavender Dream", { id: 3, name: "Eucalyptus Mint", scent: "Fresh Eucalyptus, Spearmint, & Cedar", price: "$28", image: eucalyptusImage, category: "Fresh" },
scent: "Calming French Lavender, Vanilla, & Bergamot", { id: 4, name: "Oud & Amber", scent: "Rich Oud Wood, Warm Amber, & Dark Musk", price: "$36", image: oudImage, category: "Woody" },
price: "$28", { id: 5, name: "Jasmine Night", scent: "Night-blooming Jasmine, Ylang Ylang, & Sandalwood", price: "$30", image: jasmineImage, category: "Floral" },
image: lavenderImage, { id: 6, name: "Sandalwood Cedar", scent: "Indian Sandalwood, Cedarwood, & Patchouli", price: "$34", image: sandalwoodImage, category: "Woody" },
category: "Floral", { id: 7, name: "Fresh Linen", scent: "Crisp Cotton, White Tea, & Lily of the Valley", price: "$26", image: linenImage, category: "Fresh" },
}, { id: 8, name: "Birthday Cake", scent: "Vanilla Buttercream, Sugar Spun, & Joy", price: "$28", image: birthdayImage, category: "Sweet" },
{
id: 2,
name: "Rose & Vanilla",
scent: "Damask Rose, Sweet Vanilla bean, & Amber",
price: "$32",
image: roseImage,
category: "Floral",
},
{
id: 3,
name: "Eucalyptus Mint",
scent: "Fresh Eucalyptus, Spearmint, & Cedar",
price: "$28",
image: eucalyptusImage,
category: "Fresh",
},
{
id: 4,
name: "Oud & Amber",
scent: "Rich Oud Wood, Warm Amber, & Dark Musk",
price: "$36",
image: oudImage,
category: "Woody",
},
{
id: 5,
name: "Jasmine Night",
scent: "Night-blooming Jasmine, Ylang Ylang, & Sandalwood",
price: "$30",
image: jasmineImage,
category: "Floral",
},
{
id: 6,
name: "Sandalwood Cedar",
scent: "Indian Sandalwood, Cedarwood, & Patchouli",
price: "$34",
image: sandalwoodImage,
category: "Woody",
},
{
id: 7,
name: "Fresh Linen",
scent: "Crisp Cotton, White Tea, & Lily of the Valley",
price: "$26",
image: linenImage,
category: "Fresh",
},
{
id: 8,
name: "Birthday Cake",
scent: "Vanilla Buttercream, Sugar Spun, & Joy",
price: "$28",
image: birthdayImage,
category: "Sweet",
},
]; ];
export default function ProductsPage() { export default function ProductsPage() {
const [filter, setFilter] = useState("All"); const [filter, setFilter] = useState("All");
const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))]; const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))];
const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter((p) => p.category === filter);
const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter(p => p.category === filter);
const fadeInUp = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.6 } },
};
return ( return (
<div className="w-full min-h-screen bg-background"> <Box>
<section className="py-24 px-6 text-center bg-card"> {/* ── Hero ── */}
<section className="products-hero">
<Container maxWidth="md">
<motion.div <motion.div
initial="hidden" initial={{ opacity: 0, y: 30 }}
animate="visible" animate={{ opacity: 1, y: 0 }}
variants={{ transition={{ duration: 0.8 }}
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8 } }
}}
className="max-w-3xl mx-auto"
> >
<h1 className="font-serif text-5xl md:text-6xl text-primary mb-6">Our Collection</h1> <h1>Our Collection</h1>
<p className="font-sans text-muted-foreground text-lg mb-8"> <p>Each candle is meticulously hand-poured with natural soy wax and premium fragrance oils, designed to elevate your everyday rituals.</p>
Each candle is meticulously hand-poured with natural soy wax and premium fragrance oils, designed to elevate your everyday rituals. <div className="section-divider" />
</p>
<div className="w-24 h-1 bg-accent mx-auto" />
</motion.div> </motion.div>
</Container>
</section> </section>
<section className="py-12 px-6"> {/* ── Products Body ── */}
<div className="max-w-7xl mx-auto"> <section className="products-body">
{/* Filters */} <Container maxWidth="xl">
{/* Filter Bar */}
<motion.div <motion.div
className="filter-bar"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="flex flex-wrap justify-center gap-4 mb-16"
> >
{categories.map((cat) => ( {categories.map((cat) => (
<button <button
key={cat} key={cat}
className={`filter-btn${filter === cat ? " active" : ""}`}
onClick={() => setFilter(cat)} onClick={() => setFilter(cat)}
className={`font-display tracking-widest text-sm uppercase px-6 py-2 transition-all duration-300 ${
filter === cat
? "text-primary border-b-2 border-accent"
: "text-muted-foreground hover:text-primary"
}`}
> >
{cat} {cat}
</button> </button>
@ -133,45 +67,34 @@ export default function ProductsPage() {
</motion.div> </motion.div>
{/* Grid */} {/* Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-16"> <div className="products-grid">
{filteredProducts.map((product, index) => ( {filteredProducts.map((product, index) => (
<motion.div <motion.div
key={product.id} key={product.id}
initial="hidden" className="prod-card"
whileInView="visible" initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
variants={{ transition={{ delay: (index % 4) * 0.1, duration: 0.6 }}
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { delay: (index % 4) * 0.1, duration: 0.6 } }
}}
className="group flex flex-col"
> >
<div className="relative aspect-[4/5] overflow-hidden mb-6 bg-card border border-border"> <div className="prod-img-wrap">
<img <img src={product.image} alt={product.name} />
src={product.image} <div className="prod-overlay">
alt={product.name} <button className="prod-overlay-btn">Add to Cart</button>
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-primary/30 opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex items-center justify-center backdrop-blur-[2px]">
<button className="px-8 py-3 bg-background text-primary font-display tracking-widest text-sm uppercase translate-y-8 group-hover:translate-y-0 transition-transform duration-500 hover:bg-accent hover:text-primary-foreground shadow-lg">
Add to Cart
</button>
</div> </div>
</div> </div>
<div className="flex-grow flex flex-col justify-between"> <div className="prod-info">
<div> <div>
<h3 className="font-serif text-2xl text-primary mb-2">{product.name}</h3> <h3 className="prod-name">{product.name}</h3>
<p className="font-sans text-sm text-muted-foreground mb-4 line-clamp-2"> <p className="prod-scent">{product.scent}</p>
{product.scent}
</p>
</div> </div>
<p className="font-sans font-medium text-accent text-lg">{product.price}</p> <p className="prod-price">{product.price}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </Container>
</section> </section>
</div> </Box>
); );
} }

229
src/pages/about.css Normal file
View File

@ -0,0 +1,229 @@
/* ─── about.css ──────────────────────────────────────────── */
/* HERO */
.about-hero {
padding: 6rem 1.5rem;
background: var(--brand-card);
text-align: center;
}
.about-hero h1 {
font-family: var(--font-serif);
font-size: clamp(2.5rem, 6vw, 4.5rem);
color: var(--brand-purple);
margin-bottom: 1rem;
}
.about-hero p {
font-family: var(--font-sans);
font-size: 1.1rem;
color: var(--brand-muted);
line-height: 1.8;
max-width: 680px;
margin: 0 auto;
}
/* BRAND VALUES */
.about-values {
padding: 6rem 1.5rem;
background: var(--brand-light-lavender);
}
.values-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
max-width: 1100px;
margin: 0 auto;
}
@media (min-width: 768px) {
.values-grid { grid-template-columns: 1fr 1fr 1fr; }
}
.value-card {
text-align: center;
padding: 2.5rem;
background: var(--brand-card);
border-radius: 0.75rem;
border: 1px solid rgba(213,190,240,0.5);
box-shadow: 0 2px 12px rgba(91,30,140,0.06);
transition: box-shadow 0.3s ease;
}
.value-card:hover {
box-shadow: 0 6px 24px rgba(91,30,140,0.12);
}
.value-icon {
color: var(--brand-gold);
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
}
.value-card h3 {
font-family: var(--font-serif);
font-size: 1.5rem;
color: var(--brand-purple);
margin-bottom: 0.75rem;
}
.value-card p {
font-family: var(--font-sans);
color: var(--brand-muted);
line-height: 1.7;
}
/* MEET THE MAKER */
.about-maker {
padding: 6rem 1.5rem;
background: var(--brand-purple);
color: #ffffff;
position: relative;
overflow: hidden;
}
.maker-gradient-overlay {
position: absolute;
inset: 0;
background: linear-gradient(to right, transparent, rgba(91,30,140,0.5));
mix-blend-mode: overlay;
}
.maker-grid {
display: grid;
grid-template-columns: 1fr;
gap: 4rem;
align-items: center;
max-width: 1100px;
margin: 0 auto;
position: relative;
z-index: 1;
}
@media (min-width: 1024px) {
.maker-grid { grid-template-columns: 1fr 1fr; }
}
.maker-img-wrap {
aspect-ratio: 1;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
position: relative;
}
.maker-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.maker-img-ring {
position: absolute;
inset: 0;
border-radius: 1rem;
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.2);
}
.maker-content h2 {
font-family: var(--font-serif);
font-size: clamp(2rem, 4vw, 3rem);
color: #ffffff;
margin-bottom: 0.75rem;
}
.maker-divider {
width: 80px;
height: 4px;
background: var(--brand-gold);
border-radius: 2px;
margin-bottom: 2rem;
}
.maker-content p {
font-family: var(--font-sans);
font-size: 1.05rem;
line-height: 1.8;
color: rgba(255,255,255,0.88);
margin-bottom: 1.25rem;
}
.maker-signature {
font-family: var(--font-script);
font-size: 2.25rem;
color: var(--brand-gold);
margin-top: 1.5rem;
display: block;
}
/* PROCESS TIMELINE */
.about-process {
padding: 6rem 1.5rem;
background: var(--brand-light-lavender);
}
.about-process h2 {
font-family: var(--font-serif);
font-size: clamp(2rem, 4vw, 2.8rem);
color: var(--brand-purple);
text-align: center;
margin-bottom: 0.5rem;
}
.about-process .subtitle {
font-family: var(--font-sans);
color: var(--brand-muted);
text-align: center;
margin-bottom: 5rem;
}
.timeline {
display: flex;
flex-direction: column;
gap: 3rem;
max-width: 900px;
margin: 0 auto;
}
.timeline-item {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 640px) {
.timeline-item {
flex-direction: row;
align-items: center;
gap: 3rem;
}
}
.timeline-step {
font-family: var(--font-serif);
font-size: 3.5rem;
color: rgba(212,162,76,0.3);
font-weight: 300;
min-width: 80px;
line-height: 1;
}
.timeline-body {
flex: 1;
padding-bottom: 2rem;
border-bottom: 1px solid rgba(213,190,240,0.5);
}
.timeline-body h3 {
font-family: var(--font-serif);
font-size: 1.4rem;
color: var(--brand-purple);
margin-bottom: 0.4rem;
}
.timeline-body p {
font-family: var(--font-sans);
color: var(--brand-muted);
}

316
src/pages/contact.css Normal file
View File

@ -0,0 +1,316 @@
/* ─── contact.css ────────────────────────────────────────── */
/* HERO */
.contact-hero {
padding: 6rem 1.5rem;
background: var(--brand-card);
text-align: center;
}
.contact-hero h1 {
font-family: var(--font-serif);
font-size: clamp(2.5rem, 6vw, 4rem);
color: var(--brand-purple);
margin-bottom: 1rem;
}
.contact-hero p {
font-family: var(--font-sans);
font-size: 1.05rem;
color: var(--brand-muted);
line-height: 1.8;
max-width: 640px;
margin: 0 auto 2rem;
}
/* CONTACT BODY */
.contact-body {
padding: 5rem 1.5rem;
background: var(--brand-light-lavender);
}
.contact-grid {
display: grid;
grid-template-columns: 1fr;
gap: 4rem;
max-width: 1100px;
margin: 0 auto;
}
@media (min-width: 1024px) {
.contact-grid { grid-template-columns: 1fr 1fr; }
}
/* INFO PANEL */
.contact-info-panel {
display: flex;
flex-direction: column;
gap: 2.5rem;
}
.contact-info-panel h2 {
font-family: var(--font-serif);
font-size: 1.9rem;
color: var(--brand-purple);
}
.contact-info-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.contact-info-item {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.contact-info-icon {
width: 48px;
height: 48px;
border-radius: 50%;
background: rgba(220,198,242,0.5);
display: flex;
align-items: center;
justify-content: center;
color: var(--brand-purple);
flex-shrink: 0;
}
.contact-info-label {
font-family: var(--font-display);
font-size: 0.65rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--brand-purple);
margin-bottom: 0.25rem;
}
.contact-info-value {
font-family: var(--font-sans);
color: var(--brand-muted);
font-size: 0.95rem;
line-height: 1.6;
}
.contact-info-value a {
color: var(--brand-muted);
text-decoration: none;
transition: color 0.3s ease;
}
.contact-info-value a:hover {
color: var(--brand-gold);
}
/* SOCIAL BOX */
.contact-social-box {
padding: 2rem;
background: var(--brand-card);
border-radius: 0.75rem;
border: 1px solid var(--brand-border);
}
.contact-social-box h3 {
font-family: var(--font-serif);
font-size: 1.5rem;
color: var(--brand-purple);
margin-bottom: 0.5rem;
}
.contact-social-box p {
font-family: var(--font-sans);
color: var(--brand-muted);
margin-bottom: 1.25rem;
font-size: 0.9rem;
line-height: 1.6;
}
.contact-ig-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--brand-purple);
font-family: var(--font-sans);
font-weight: 500;
text-decoration: none;
transition: color 0.3s ease;
}
.contact-ig-link:hover {
color: var(--brand-gold);
}
/* FORM PANEL */
.contact-form-panel {
background: #ffffff;
border-radius: 0px;
padding: 3rem;
box-shadow: 0 12px 40px rgba(91,30,140,0.1);
border: 1px solid rgba(213,190,240,0.4);
position: relative;
}
.contact-form-top-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to right, var(--brand-purple), var(--brand-gold), var(--brand-purple));
border-radius: 0;
}
.contact-form-panel h2 {
font-family: var(--font-serif);
font-size: 1.9rem;
color: var(--brand-purple);
margin-bottom: 2rem;
}
.form-row {
display: grid;
grid-template-columns: 1fr;
gap: 1.25rem;
margin-bottom: 1.25rem;
}
@media (min-width: 640px) {
.form-row { grid-template-columns: 1fr 1fr; }
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.4rem;
margin-bottom: 1.25rem;
}
.form-label {
font-family: var(--font-display);
font-size: 0.65rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--brand-purple);
}
.form-input,
.form-textarea {
width: 100%;
padding: 0.85rem 1rem;
background: var(--brand-light-lavender);
border: 1px solid var(--brand-border);
font-family: var(--font-sans);
font-size: 0.95rem;
color: var(--brand-text);
outline: none;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
border-radius: 0;
box-sizing: border-box;
}
.form-input:focus,
.form-textarea:focus {
border-color: var(--brand-gold);
box-shadow: 0 0 0 2px rgba(212,162,76,0.15);
}
.form-textarea {
resize: none;
height: 140px;
}
.form-submit {
width: 100%;
padding: 1rem;
background: var(--brand-purple);
color: #ffffff;
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
border: none;
cursor: pointer;
transition: background 0.3s ease;
margin-top: 0.5rem;
}
.form-submit:hover:not(:disabled) {
background: var(--brand-gold);
}
.form-submit:disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* SUCCESS STATE */
.contact-success {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 4rem 0;
}
.success-icon-wrap {
width: 64px;
height: 64px;
border-radius: 50%;
background: #f0fdf4;
color: #16a34a;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
}
.contact-success h3 {
font-family: var(--font-serif);
font-size: 1.75rem;
color: var(--brand-purple);
margin-bottom: 0.5rem;
}
.contact-success p {
font-family: var(--font-sans);
color: var(--brand-muted);
}
.send-another-btn {
margin-top: 2rem;
background: none;
border: none;
border-bottom: 1px solid var(--brand-gold);
padding-bottom: 3px;
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--brand-gold);
cursor: pointer;
transition: color 0.3s ease;
}
.send-another-btn:hover {
color: var(--brand-purple);
border-color: var(--brand-purple);
}
/* CLOSING QUOTE */
.contact-quote {
padding: 4rem 1.5rem;
text-align: center;
background: var(--brand-light-lavender);
}
.contact-quote p {
font-family: var(--font-script);
font-size: clamp(1.5rem, 3vw, 2rem);
color: var(--brand-purple);
max-width: 640px;
margin: 0 auto;
line-height: 1.6;
}

386
src/pages/home.css Normal file
View File

@ -0,0 +1,386 @@
/* ─── home.css ───────────────────────────────────────────── */
/* HERO */
.home-hero {
position: relative;
min-height: 90vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
padding: 5rem 1.5rem;
text-align: center;
}
.home-hero-bg {
position: absolute;
inset: 0;
background: linear-gradient(to bottom, rgba(220,198,242,0.4), var(--brand-light-lavender));
z-index: -1;
}
.home-hero-logo-wrap {
width: 140px;
height: 140px;
border-radius: 50%;
overflow: hidden;
border: 4px solid rgba(212,162,76,0.35);
box-shadow: 0 0 40px rgba(212,162,76,0.25);
position: relative;
margin: 0 auto 2rem;
}
.home-hero-flame {
position: absolute;
top: -12px;
right: -12px;
color: var(--brand-gold);
}
.home-hero h1 {
font-family: var(--font-serif);
font-size: clamp(2.5rem, 7vw, 5.5rem);
color: var(--brand-purple);
margin-bottom: 1rem;
drop-shadow: 0 2px 8px rgba(91,30,140,0.15);
}
.home-hero-tagline {
font-family: var(--font-script);
font-size: clamp(1.75rem, 4vw, 3rem);
color: var(--brand-gold);
margin-bottom: 3rem;
display: block;
}
.hero-cta {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 1rem 2.5rem;
background: var(--brand-purple);
color: #ffffff;
font-family: var(--font-display);
font-size: 0.78rem;
letter-spacing: 0.2em;
text-transform: uppercase;
text-decoration: none;
border: 1px solid transparent;
transition: background 0.4s ease, border-color 0.4s ease, color 0.4s ease, box-shadow 0.4s ease;
}
.hero-cta:hover {
background: transparent;
color: var(--brand-purple);
border-color: var(--brand-gold);
box-shadow: 0 0 20px rgba(212,162,76,0.35);
}
/* PHILOSOPHY */
.home-philosophy {
padding: 6rem 1.5rem;
background: var(--brand-card);
}
.home-philosophy h2 {
font-family: var(--font-serif);
font-size: clamp(2rem, 4vw, 3rem);
color: var(--brand-purple);
text-align: center;
margin-bottom: 0.75rem;
}
.section-divider {
width: 96px;
height: 4px;
background: var(--brand-gold);
margin: 0 auto 4rem;
border-radius: 2px;
}
.philosophy-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2.5rem;
max-width: 1100px;
margin: 0 auto;
}
@media (min-width: 768px) {
.philosophy-grid { grid-template-columns: 1fr 1fr 1fr; }
}
.philosophy-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 2.5rem;
border-radius: 1rem;
transition: background 0.4s ease;
}
.philosophy-card:hover {
background: rgba(220,198,242,0.3);
}
.philosophy-icon-wrap {
width: 64px;
height: 64px;
border-radius: 50%;
background: rgba(220,198,242,0.5);
display: flex;
align-items: center;
justify-content: center;
color: var(--brand-purple);
margin-bottom: 1.5rem;
}
.philosophy-card h3 {
font-family: var(--font-display);
font-size: 1.1rem;
letter-spacing: 0.1em;
color: var(--brand-purple);
margin-bottom: 0.75rem;
}
.philosophy-card p {
font-family: var(--font-sans);
color: var(--brand-muted);
line-height: 1.7;
}
/* FEATURED PRODUCTS */
.home-products {
padding: 6rem 1.5rem;
background: var(--brand-light-lavender);
}
.home-products-header {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-bottom: 4rem;
}
@media (min-width: 768px) {
.home-products-header {
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
}
.home-products h2 {
font-family: var(--font-serif);
font-size: clamp(2rem, 4vw, 3rem);
color: var(--brand-purple);
margin-bottom: 0.5rem;
}
.view-all-link {
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--brand-gold);
text-decoration: none;
border-bottom: 1px solid transparent;
padding-bottom: 2px;
transition: color 0.3s ease, border-color 0.3s ease;
white-space: nowrap;
}
.view-all-link:hover {
color: var(--brand-purple);
border-color: var(--brand-purple);
}
.product-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
max-width: 1280px;
margin: 0 auto;
}
@media (min-width: 600px) { .product-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) { .product-grid { grid-template-columns: repeat(4, 1fr); } }
.product-card {
cursor: pointer;
}
.product-img-wrap {
position: relative;
aspect-ratio: 1 / 1;
overflow: hidden;
background: var(--brand-card);
border-radius: 4px;
margin-bottom: 1.25rem;
}
.product-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.7s ease;
}
.product-card:hover .product-img-wrap img {
transform: scale(1.05);
}
.product-overlay {
position: absolute;
inset: 0;
background: rgba(91,30,140,0.22);
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(2px);
transition: opacity 0.5s ease;
}
.product-card:hover .product-overlay {
opacity: 1;
}
.product-overlay-btn {
padding: 0.75rem 1.5rem;
background: rgba(239,228,250,0.92);
color: var(--brand-purple);
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
border: none;
cursor: pointer;
transform: translateY(16px);
transition: transform 0.5s ease, background 0.3s ease;
}
.product-card:hover .product-overlay-btn {
transform: translateY(0);
}
.product-name {
font-family: var(--font-serif);
font-size: 1.4rem;
color: var(--brand-purple);
margin-bottom: 0.25rem;
}
.product-price {
font-family: var(--font-sans);
color: var(--brand-gold);
font-weight: 500;
}
/* QUOTE BANNER */
.home-quote {
padding: 8rem 1.5rem;
background: var(--brand-purple);
position: relative;
overflow: hidden;
text-align: center;
}
.home-quote-texture {
position: absolute;
inset: 0;
background-image: url('https://www.transparenttextures.com/patterns/stardust.png');
opacity: 0.1;
}
.home-quote blockquote {
font-family: var(--font-serif);
font-style: italic;
font-size: clamp(1.4rem, 3.5vw, 2.5rem);
color: #ffffff;
line-height: 1.6;
max-width: 900px;
margin: 1.5rem auto 2rem;
position: relative;
z-index: 1;
}
.home-quote-divider {
width: 64px;
height: 1px;
background: var(--brand-gold);
margin: 0 auto;
position: relative;
z-index: 1;
}
/* NEWSLETTER */
.home-newsletter {
padding: 6rem 1.5rem;
background: var(--brand-card);
text-align: center;
}
.home-newsletter h2 {
font-family: var(--font-serif);
font-size: clamp(1.8rem, 3.5vw, 2.5rem);
color: var(--brand-purple);
margin-bottom: 0.75rem;
}
.home-newsletter p {
font-family: var(--font-sans);
color: var(--brand-muted);
margin-bottom: 2rem;
max-width: 520px;
margin-left: auto;
margin-right: auto;
}
.newsletter-form {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 440px;
margin: 0 auto;
}
@media (min-width: 480px) {
.newsletter-form { flex-direction: row; }
}
.newsletter-input {
flex: 1;
padding: 0.85rem 1.25rem;
background: var(--brand-light-lavender);
border: 1px solid var(--brand-border);
font-family: var(--font-sans);
font-size: 0.95rem;
outline: none;
transition: border-color 0.3s ease;
}
.newsletter-input:focus {
border-color: var(--brand-gold);
box-shadow: 0 0 0 2px rgba(212,162,76,0.15);
}
.newsletter-btn {
padding: 0.85rem 2rem;
background: var(--brand-purple);
color: #ffffff;
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
border: none;
cursor: pointer;
transition: background 0.3s ease;
white-space: nowrap;
}
.newsletter-btn:hover {
background: var(--brand-gold);
}

View File

@ -1,154 +1,96 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Link } from "wouter"; import { Link } from "wouter";
import { Flame, Heart, Leaf, Sparkles } from "lucide-react"; import { Flame, Heart, Leaf, Sparkles } from "lucide-react";
import Box from "@mui/material/Box";
import Container from "@mui/material/Container";
import lavenderImage from "@/assets/images/product-lavender.png"; import lavenderImage from "@/assets/images/product-lavender.png";
import roseImage from "@/assets/images/product-rose.png"; import roseImage from "@/assets/images/product-rose.png";
import eucalyptusImage from "@/assets/images/product-eucalyptus.png"; import eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import oudImage from "@/assets/images/product-oud.png"; import oudImage from "@/assets/images/product-oud.png";
import "./home.css";
const logoPath = "/opengraph.jpg"; const logoPath = "/opengraph.jpg";
export default function Home() { const fadeInUp = {
const fadeInUp = {
hidden: { opacity: 0, y: 40 }, hidden: { opacity: 0, y: 40 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }, visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
}; };
const stagger = { const stagger = {
visible: { transition: { staggerChildren: 0.2 } }, visible: { transition: { staggerChildren: 0.2 } },
}; };
export default function Home() {
return ( return (
<div className="flex flex-col w-full"> <Box>
{/* Hero Section */} {/* ── Hero ── */}
<section className="relative min-h-[90vh] flex flex-col items-center justify-center overflow-hidden px-4 py-20"> <section className="home-hero">
<div className="absolute inset-0 bg-gradient-to-b from-secondary/30 to-background -z-10" /> <div className="home-hero-bg" />
<motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.div <motion.div variants={fadeInUp} style={{ position: "relative" }}>
initial="hidden" <div className="home-hero-logo-wrap">
animate="visible" <img src={logoPath} alt="Mani Candles Co." style={{ width: "100%", height: "100%", objectFit: "cover" }} />
variants={stagger}
className="flex flex-col items-center text-center max-w-4xl mx-auto"
>
<motion.div variants={fadeInUp} className="mb-8 relative">
<div className="w-32 h-32 md:w-48 md:h-48 rounded-full overflow-hidden border-4 border-accent/30 shadow-[0_0_40px_rgba(212,162,76,0.3)]">
<img src={logoPath} alt="Mani Candles Co." className="w-full h-full object-cover" />
</div> </div>
<div className="absolute -top-4 -right-4 text-accent animate-flame"> <div className="home-hero-flame animate-flame">
<Flame size={40} fill="currentColor" className="drop-shadow-[0_0_15px_rgba(255,211,107,0.8)]" /> <Flame size={40} fill="currentColor" style={{ filter: "drop-shadow(0 0 12px rgba(255,211,107,0.8))" }} />
</div> </div>
</motion.div> </motion.div>
<motion.h1 variants={fadeInUp} className="font-serif text-5xl md:text-7xl lg:text-8xl text-primary mb-6 drop-shadow-sm"> <motion.h1 variants={fadeInUp}>Mani Candles Co.</motion.h1>
Mani Candles Co.
</motion.h1>
<motion.p variants={fadeInUp} className="font-script text-3xl md:text-5xl text-accent mb-12"> <motion.span variants={fadeInUp} className="home-hero-tagline">
Handmade Natural Heartmade Handmade Natural Heartmade
</motion.p> </motion.span>
<motion.div variants={fadeInUp}> <motion.div variants={fadeInUp}>
<Link <Link href="/products" className="hero-cta">Shop the Collection</Link>
href="/products"
className="inline-flex items-center justify-center px-8 py-4 bg-primary text-primary-foreground font-display tracking-widest uppercase text-sm rounded-none border border-transparent hover:border-accent hover:bg-transparent hover:text-primary transition-all duration-500 ease-out shadow-lg hover:shadow-[0_0_20px_rgba(212,162,76,0.4)]"
>
Shop the Collection
</Link>
</motion.div> </motion.div>
</motion.div> </motion.div>
</section> </section>
{/* Why Mani Section */} {/* ── Philosophy ── */}
<section className="py-24 bg-card px-6"> <section className="home-philosophy">
<div className="max-w-6xl mx-auto"> <Container maxWidth="lg">
<motion.div <motion.div initial="hidden" whileInView="visible" viewport={{ once: true, margin: "-100px" }} variants={stagger}>
initial="hidden" <motion.h2 variants={fadeInUp}>The Mani Philosophy</motion.h2>
whileInView="visible" <div className="section-divider" />
viewport={{ once: true, margin: "-100px" }}
variants={stagger}
className="text-center mb-16"
>
<motion.h2 variants={fadeInUp} className="font-serif text-4xl md:text-5xl text-primary mb-4">
The Mani Philosophy
</motion.h2>
<motion.div variants={fadeInUp} className="w-24 h-1 bg-accent mx-auto" />
</motion.div> </motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12"> <div className="philosophy-grid">
{[
{ Icon: Leaf, title: "Natural", desc: "Crafted with pure soy wax and essential oils. Clean burns that respect your home and our earth." },
{ Icon: Sparkles, title: "Handmade", desc: "Poured by hand in small batches to ensure unparalleled quality and attention to detail." },
{ Icon: Heart, title: "Heartmade", desc: "Every candle is infused with love and intention, designed to bring warmth to your sacred spaces." },
].map(({ Icon, title, desc }, i) => (
<motion.div <motion.div
key={title}
className="philosophy-card"
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={{ once: true }} viewport={{ once: true }}
variants={fadeInUp} variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
className="flex flex-col items-center text-center p-8 rounded-2xl hover:bg-secondary/20 transition-colors duration-500"
> >
<div className="w-16 h-16 rounded-full bg-secondary/50 flex items-center justify-center text-primary mb-6"> <div className="philosophy-icon-wrap"><Icon size={32} /></div>
<Leaf size={32} /> <h3>{title}</h3>
</div> <p>{desc}</p>
<h3 className="font-display tracking-wider text-xl text-primary mb-4">Natural</h3>
<p className="font-sans text-muted-foreground leading-relaxed">
Crafted with pure soy wax and essential oils. Clean burns that respect your home and our earth.
</p>
</motion.div> </motion.div>
))}
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
className="flex flex-col items-center text-center p-8 rounded-2xl hover:bg-secondary/20 transition-colors duration-500"
>
<div className="w-16 h-16 rounded-full bg-secondary/50 flex items-center justify-center text-primary mb-6">
<Sparkles size={32} />
</div>
<h3 className="font-display tracking-wider text-xl text-primary mb-4">Handmade</h3>
<p className="font-sans text-muted-foreground leading-relaxed">
Poured by hand in small batches to ensure unparalleled quality and attention to detail.
</p>
</motion.div>
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
className="flex flex-col items-center text-center p-8 rounded-2xl hover:bg-secondary/20 transition-colors duration-500"
>
<div className="w-16 h-16 rounded-full bg-secondary/50 flex items-center justify-center text-primary mb-6">
<Heart size={32} />
</div>
<h3 className="font-display tracking-wider text-xl text-primary mb-4">Heartmade</h3>
<p className="font-sans text-muted-foreground leading-relaxed">
Every candle is infused with love and intention, designed to bring warmth to your sacred spaces.
</p>
</motion.div>
</div>
</div> </div>
</Container>
</section> </section>
{/* Featured Products */} {/* ── Featured Products ── */}
<section className="py-24 px-6 bg-background"> <section className="home-products">
<div className="max-w-7xl mx-auto"> <Container maxWidth="xl">
<motion.div <motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp} className="home-products-header">
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
className="flex flex-col md:flex-row justify-between items-end mb-16 gap-6"
>
<div> <div>
<h2 className="font-serif text-4xl md:text-5xl text-primary mb-4">Curated Favorites</h2> <h2>Curated Favorites</h2>
<div className="w-24 h-1 bg-accent" /> <div className="section-divider" style={{ margin: "0.5rem 0 0", width: 80 }} />
</div> </div>
<Link <Link href="/products" className="view-all-link">View All Candles</Link>
href="/products"
className="font-display tracking-widest uppercase text-sm text-accent hover:text-primary transition-colors border-b border-transparent hover:border-primary pb-1"
>
View All Candles
</Link>
</motion.div> </motion.div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8"> <div className="product-grid">
{[ {[
{ id: 1, name: "Lavender Dream", image: lavenderImage, price: "$28" }, { id: 1, name: "Lavender Dream", image: lavenderImage, price: "$28" },
{ id: 2, name: "Rose & Vanilla", image: roseImage, price: "$32" }, { id: 2, name: "Rose & Vanilla", image: roseImage, price: "$32" },
@ -157,82 +99,49 @@ export default function Home() {
].map((product, i) => ( ].map((product, i) => (
<motion.div <motion.div
key={product.id} key={product.id}
className="product-card"
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={{ once: true }} viewport={{ once: true }}
variants={{ variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.1, duration: 0.6 } } }}
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { delay: i * 0.1, duration: 0.6 } }
}}
className="group cursor-pointer"
> >
<div className="relative aspect-square overflow-hidden mb-6 bg-card"> <div className="product-img-wrap">
<img <img src={product.image} alt={product.name} />
src={product.image} <div className="product-overlay">
alt={product.name} <button className="product-overlay-btn">Quick Add</button>
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
/>
<div className="absolute inset-0 bg-primary/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex items-center justify-center backdrop-blur-[2px]">
<span className="px-6 py-3 bg-background/90 text-primary font-display tracking-widest text-sm uppercase translate-y-4 group-hover:translate-y-0 transition-transform duration-500">
Quick Add
</span>
</div> </div>
</div> </div>
<h3 className="font-serif text-2xl text-primary mb-2">{product.name}</h3> <h3 className="product-name">{product.name}</h3>
<p className="font-sans text-accent">{product.price}</p> <p className="product-price">{product.price}</p>
</motion.div> </motion.div>
))} ))}
</div> </div>
</div> </Container>
</section> </section>
{/* Quote Section */} {/* ── Quote ── */}
<section className="py-32 px-6 bg-primary relative overflow-hidden"> <section className="home-quote">
<div className="absolute inset-0 opacity-10 bg-[url('https://www.transparenttextures.com/patterns/stardust.png')]" /> <div className="home-quote-texture" />
<motion.div <motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
initial="hidden" <Flame className="animate-flame" size={48} style={{ color: "var(--brand-gold)", margin: "0 auto 1.5rem", display: "block" }} />
whileInView="visible" <blockquote>
viewport={{ once: true }}
variants={fadeInUp}
className="max-w-4xl mx-auto text-center relative z-10"
>
<Flame className="text-accent mx-auto mb-8 animate-flame" size={48} />
<p className="font-serif italic text-3xl md:text-5xl text-primary-foreground leading-relaxed mb-8">
"A candle loses nothing by lighting another candle. It only makes the room warmer, brighter, and more beautiful." "A candle loses nothing by lighting another candle. It only makes the room warmer, brighter, and more beautiful."
</p> </blockquote>
<div className="w-16 h-[1px] bg-accent mx-auto" /> <div className="home-quote-divider" />
</motion.div> </motion.div>
</section> </section>
{/* Newsletter */} {/* ── Newsletter ── */}
<section className="py-24 px-6 bg-card text-center"> <section className="home-newsletter">
<motion.div <motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
initial="hidden" <h2>Join Our Sanctuary</h2>
whileInView="visible" <p>Subscribe to receive gentle updates on new collections, exclusive access, and a little light in your inbox.</p>
viewport={{ once: true }} <form className="newsletter-form" onSubmit={(e) => e.preventDefault()}>
variants={fadeInUp} <input type="email" className="newsletter-input" placeholder="Your email address" required />
className="max-w-2xl mx-auto" <button type="submit" className="newsletter-btn">Subscribe</button>
>
<h2 className="font-serif text-4xl text-primary mb-4">Join Our Sanctuary</h2>
<p className="font-sans text-muted-foreground mb-8">
Subscribe to receive gentle updates on new collections, exclusive access, and a little light in your inbox.
</p>
<form className="flex flex-col sm:flex-row gap-4 max-w-md mx-auto" onSubmit={(e) => e.preventDefault()}>
<input
type="email"
placeholder="Your email address"
className="flex-grow px-6 py-3 bg-background border border-border focus:border-accent focus:outline-none focus:ring-1 focus:ring-accent font-sans rounded-none transition-all"
required
/>
<button
type="submit"
className="px-8 py-3 bg-primary text-primary-foreground font-display tracking-widest text-sm uppercase hover:bg-accent transition-colors duration-300"
>
Subscribe
</button>
</form> </form>
</motion.div> </motion.div>
</section> </section>
</div> </Box>
); );
} }

173
src/pages/products.css Normal file
View File

@ -0,0 +1,173 @@
/* ─── products.css ───────────────────────────────────────── */
/* HERO */
.products-hero {
padding: 6rem 1.5rem;
background: var(--brand-card);
text-align: center;
}
.products-hero h1 {
font-family: var(--font-serif);
font-size: clamp(2.5rem, 6vw, 4rem);
color: var(--brand-purple);
margin-bottom: 1rem;
}
.products-hero p {
font-family: var(--font-sans);
font-size: 1.05rem;
color: var(--brand-muted);
line-height: 1.8;
max-width: 640px;
margin: 0 auto 2rem;
}
/* FILTERS */
.products-body {
padding: 5rem 1.5rem;
background: var(--brand-light-lavender);
}
.filter-bar {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
margin-bottom: 4rem;
}
.filter-btn {
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.2em;
text-transform: uppercase;
padding: 0.5rem 1.5rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
color: var(--brand-muted);
cursor: pointer;
transition: color 0.3s ease, border-color 0.3s ease;
}
.filter-btn:hover {
color: var(--brand-purple);
}
.filter-btn.active {
color: var(--brand-purple);
border-bottom-color: var(--brand-gold);
}
/* PRODUCT GRID */
.products-grid {
display: grid;
grid-template-columns: 1fr;
gap: 3rem 2rem;
max-width: 1280px;
margin: 0 auto;
}
@media (min-width: 640px) { .products-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1024px) { .products-grid { grid-template-columns: repeat(4, 1fr); } }
.prod-card {
display: flex;
flex-direction: column;
cursor: pointer;
}
.prod-img-wrap {
position: relative;
aspect-ratio: 4 / 5;
overflow: hidden;
background: var(--brand-card);
border: 1px solid var(--brand-border);
margin-bottom: 1.25rem;
}
.prod-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1s ease;
}
.prod-card:hover .prod-img-wrap img {
transform: scale(1.05);
}
.prod-overlay {
position: absolute;
inset: 0;
background: rgba(91,30,140,0.3);
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(2px);
transition: opacity 0.5s ease;
}
.prod-card:hover .prod-overlay {
opacity: 1;
}
.prod-overlay-btn {
padding: 0.75rem 2rem;
background: var(--brand-light-lavender);
color: var(--brand-purple);
font-family: var(--font-display);
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
border: none;
cursor: pointer;
transform: translateY(32px);
transition: transform 0.5s ease, background 0.3s ease, color 0.3s ease;
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}
.prod-card:hover .prod-overlay-btn {
transform: translateY(0);
}
.prod-overlay-btn:hover {
background: var(--brand-gold);
color: #ffffff;
}
.prod-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.prod-name {
font-family: var(--font-serif);
font-size: 1.4rem;
color: var(--brand-purple);
margin-bottom: 0.25rem;
}
.prod-scent {
font-family: var(--font-sans);
font-size: 0.85rem;
color: var(--brand-muted);
line-height: 1.5;
margin-bottom: 0.75rem;
/* two-line clamp */
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.prod-price {
font-family: var(--font-sans);
font-weight: 500;
font-size: 1.05rem;
color: var(--brand-gold);
}

63
src/theme.ts Normal file
View File

@ -0,0 +1,63 @@
import { createTheme } from "@mui/material/styles";
const theme = createTheme({
palette: {
mode: "light",
primary: {
main: "#5B1E8C", // deep brand purple
light: "#DCC6F2", // lavender
dark: "#3A1260",
contrastText: "#ffffff",
},
secondary: {
main: "#D4A24C", // brand gold / accent
light: "#FFD36B",
dark: "#A07830",
contrastText: "#ffffff",
},
background: {
default: "#EFE4FA", // light lavender bg
paper: "#F8F2FD", // card bg
},
text: {
primary: "#2D0D52",
secondary: "#7B5A9E",
},
divider: "#D5BEF0",
},
typography: {
fontFamily: "'Inter', sans-serif",
h1: { fontFamily: "'Cormorant Garamond', serif" },
h2: { fontFamily: "'Cormorant Garamond', serif" },
h3: { fontFamily: "'Cormorant Garamond', serif" },
h4: { fontFamily: "'Cormorant Garamond', serif" },
h5: { fontFamily: "'Cormorant Garamond', serif" },
h6: { fontFamily: "'Cormorant Garamond', serif" },
subtitle1: { fontFamily: "'Cinzel', serif" },
subtitle2: { fontFamily: "'Cinzel', serif" },
},
shape: {
borderRadius: 12,
},
components: {
MuiButton: {
styleOverrides: {
root: {
textTransform: "none",
borderRadius: 0,
fontFamily: "'Cinzel', serif",
letterSpacing: "0.15em",
},
},
},
MuiAppBar: {
styleOverrides: {
root: {
boxShadow: "none",
},
},
},
},
});
export default theme;

View File

@ -1,13 +1,12 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "path"; import path from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
const projectRoot = fileURLToPath(new URL(".", import.meta.url)); const projectRoot = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react()],
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(projectRoot, "src"), "@": path.resolve(projectRoot, "src"),