fix drone
continuous-integration/drone Build is failing Details

This commit is contained in:
hardik 2026-05-29 06:54:18 +00:00
parent acca584f0d
commit ede4237281
24 changed files with 5554 additions and 5555 deletions

View File

@ -1,9 +1,9 @@
node_modules
dist
build
.git
.gitignore
Dockerfile
.dockerignore
README.md
node_modules
dist
build
.git
.gitignore
Dockerfile
.dockerignore
README.md
.env

View File

@ -1,41 +1,40 @@
kind: pipeline
name: Build Image
steps:
- name: Build Docker Image
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: git.midastix.com/ci-docker-repo-user/manicandles
registry: git.midastix.com
when:
branch:
- main
event:
- push
- name: Deploy Image
image: appleboy/drone-ssh
settings:
host: server1.midastix.com
username: root
key:
from_secret: server_ssh_pkey
port: 22
command_timeout: 5m
script:
- echo "Deploying ManiCandles"
- sudo /opt/deployable/manicandles/docker/deploy.sh
- echo "Deployment Completed"
when:
branch:
- main
event:
- push
kind: pipeline
type: docker
name: Build Image
steps:
- name: Build Docker Image
image: plugins/docker
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: git.midastix.com/ci-docker-repo-user/manicandles
registry: git.midastix.com
when:
branch:
- main
event:
- push
- name: Deploy image
image: appleboy/drone-ssh
settings:
host: server1.midastix.com
username: root
key:
from_secret: server_ssh_pkey
port: 22
command_timeout: 5m
script:
- echo "Deploying ManiCandles"
- sudo /opt/deployable/manicandles/docker/deploy.sh
- echo "Completed Deployment"
when:
branch:
- main
event:
- push

View File

@ -1,5 +1,5 @@
{
"recommendations": [
"arcanis.vscode-zipfs"
]
}
{
"recommendations": [
"arcanis.vscode-zipfs"
]
}

12
.vscode/settings.json vendored
View File

@ -1,6 +1,6 @@
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}

View File

@ -1,5 +1,5 @@
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode
# This file is automatically generated by @yarnpkg/sdks.
# Manual changes might be lost!
integrations:
- vscode

View File

@ -1,19 +1,19 @@
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,32 +1,32 @@
import { Switch, Route, Router as WouterRouter } from "wouter";
import NotFound from "@/pages/not-found";
import { Layout } from "@/components/Layout";
import Home from "@/pages/home";
import ProductsPage from "@/pages/ProductsPage";
import AboutPage from "@/pages/AboutPage";
import ContactPage from "@/pages/ContactPage";
function Router() {
return (
<Switch>
<Route path="/" component={Home} />
<Route path="/products" component={ProductsPage} />
<Route path="/about" component={AboutPage} />
<Route path="/contact" component={ContactPage} />
<Route component={NotFound} />
</Switch>
);
}
function App() {
return (
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
<Layout>
<Router />
</Layout>
</WouterRouter>
);
}
export default App;
import { Switch, Route, Router as WouterRouter } from "wouter";
import NotFound from "@/pages/not-found";
import { Layout } from "@/components/Layout";
import Home from "@/pages/home";
import ProductsPage from "@/pages/ProductsPage";
import AboutPage from "@/pages/AboutPage";
import ContactPage from "@/pages/ContactPage";
function Router() {
return (
<Switch>
<Route path="/" component={Home} />
<Route path="/products" component={ProductsPage} />
<Route path="/about" component={AboutPage} />
<Route path="/contact" component={ContactPage} />
<Route component={NotFound} />
</Switch>
);
}
function App() {
return (
<WouterRouter base={import.meta.env.BASE_URL.replace(/\/$/, "")}>
<Layout>
<Router />
</Layout>
</WouterRouter>
);
}
export default App;

View File

@ -1,366 +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;
}
/* ─── 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,310 +1,310 @@
import { Link, useLocation } from "wouter";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
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";
// ── 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 }) {
const [location] = useLocation();
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileOpenSection, setMobileOpenSection] = useState<string | null>(null);
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 20);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
window.scrollTo(0, 0);
setMobileMenuOpen(false);
setMobileOpenSection(null);
}, [location]);
// Duplicate ticker items to create seamless loop
const allTickerItems = [...TICKER_ITEMS, ...TICKER_ITEMS];
return (
<Box sx={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
{/* ── Ticker Bar ── */}
<Box
className="ticker-bar"
sx={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 1200 }}
>
<div className="ticker-track">
{allTickerItems.map((item, i) => (
<span className="ticker-item" key={i}>
<span className="ticker-dot" />
{item.icon}
{item.text}
</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>
{/* Desktop Nav */}
<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
href={link.path}
className={`nav-link${location === link.path ? " active" : ""}`}
>
{link.name}
{link.dropdown && <ChevronDown size={13} className="nav-chevron" />}
</Link>
{/* Dropdown */}
{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)}
>
{mobileMenuOpen ? <X size={26} /> : <Menu size={26} />}
</IconButton>
</Toolbar>
</Container>
{/* Mobile Drawer */}
<AnimatePresence>
{mobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="mobile-menu"
>
{NAV_LINKS.map((link) => (
<div className="mobile-nav-section" key={link.name}>
{link.dropdown ? (
<>
<button
className={`mobile-nav-link${location === link.path ? " active" : ""}`}
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}
<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>
)
)}
</motion.div>
)}
</AnimatePresence>
</>
) : (
<Link
href={link.path}
className={`mobile-nav-link${location === link.path ? " active" : ""}`}
>
{link.name}
</Link>
)}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</AppBar>
{/* ── Page Content — offset for ticker + appbar ── */}
<Box component="main" sx={{ flexGrow: 1, pt: "112px" }}>
<AnimatePresence mode="wait">
<motion.div
key={location}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
{children}
</motion.div>
</AnimatePresence>
</Box>
{/* ── Scroll Candle ── */}
<ScrollCandle />
{/* ── Footer ── */}
<footer className="site-footer">
<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>
<span className="font-script" style={{ fontSize: "2rem", color: "var(--brand-gold-light)" }}>
Mani Candles
</span>
</div>
<p className="footer-tagline">Handmade Natural Heartmade</p>
</div>
<div className="footer-col">
<h4 className="footer-heading">Explore</h4>
{NAV_LINKS.map((link) => (
<Link key={link.path + link.name} href={link.path} className="footer-link">
{link.name}
</Link>
))}
</div>
<div className="footer-col">
<h4 className="footer-heading">Connect</h4>
<div style={{ display: "flex", gap: "0.75rem" }}>
<a href="#" className="social-btn"><Instagram size={18} /></a>
<a href="#" className="social-btn"><Facebook size={18} /></a>
<a href="#" className="social-btn"><Twitter size={18} /></a>
</div>
<p className="footer-copy">
© {new Date().getFullYear()} Mani Candles Co. All rights reserved.
</p>
</div>
</div>
</footer>
</Box>
);
}
import { Link, useLocation } from "wouter";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
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";
// ── 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 }) {
const [location] = useLocation();
const [scrolled, setScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileOpenSection, setMobileOpenSection] = useState<string | null>(null);
useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 20);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
window.scrollTo(0, 0);
setMobileMenuOpen(false);
setMobileOpenSection(null);
}, [location]);
// Duplicate ticker items to create seamless loop
const allTickerItems = [...TICKER_ITEMS, ...TICKER_ITEMS];
return (
<Box sx={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}>
{/* ── Ticker Bar ── */}
<Box
className="ticker-bar"
sx={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 1200 }}
>
<div className="ticker-track">
{allTickerItems.map((item, i) => (
<span className="ticker-item" key={i}>
<span className="ticker-dot" />
{item.icon}
{item.text}
</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>
{/* Desktop Nav */}
<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
href={link.path}
className={`nav-link${location === link.path ? " active" : ""}`}
>
{link.name}
{link.dropdown && <ChevronDown size={13} className="nav-chevron" />}
</Link>
{/* Dropdown */}
{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)}
>
{mobileMenuOpen ? <X size={26} /> : <Menu size={26} />}
</IconButton>
</Toolbar>
</Container>
{/* Mobile Drawer */}
<AnimatePresence>
{mobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="mobile-menu"
>
{NAV_LINKS.map((link) => (
<div className="mobile-nav-section" key={link.name}>
{link.dropdown ? (
<>
<button
className={`mobile-nav-link${location === link.path ? " active" : ""}`}
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}
<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>
)
)}
</motion.div>
)}
</AnimatePresence>
</>
) : (
<Link
href={link.path}
className={`mobile-nav-link${location === link.path ? " active" : ""}`}
>
{link.name}
</Link>
)}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</AppBar>
{/* ── Page Content — offset for ticker + appbar ── */}
<Box component="main" sx={{ flexGrow: 1, pt: "112px" }}>
<AnimatePresence mode="wait">
<motion.div
key={location}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.5, ease: "easeOut" }}
>
{children}
</motion.div>
</AnimatePresence>
</Box>
{/* ── Scroll Candle ── */}
<ScrollCandle />
{/* ── Footer ── */}
<footer className="site-footer">
<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>
<span className="font-script" style={{ fontSize: "2rem", color: "var(--brand-gold-light)" }}>
Mani Candles
</span>
</div>
<p className="footer-tagline">Handmade Natural Heartmade</p>
</div>
<div className="footer-col">
<h4 className="footer-heading">Explore</h4>
{NAV_LINKS.map((link) => (
<Link key={link.path + link.name} href={link.path} className="footer-link">
{link.name}
</Link>
))}
</div>
<div className="footer-col">
<h4 className="footer-heading">Connect</h4>
<div style={{ display: "flex", gap: "0.75rem" }}>
<a href="#" className="social-btn"><Instagram size={18} /></a>
<a href="#" className="social-btn"><Facebook size={18} /></a>
<a href="#" className="social-btn"><Twitter size={18} /></a>
</div>
<p className="footer-copy">
© {new Date().getFullYear()} Mani Candles Co. All rights reserved.
</p>
</div>
</div>
</footer>
</Box>
);
}

View File

@ -1,291 +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;
/* ─── 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

@ -1,99 +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>
);
}
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,85 +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');
/* ─── CSS Custom Properties ─────────────────────────────── */
:root {
--brand-purple: #5B1E8C;
--brand-purple-dark: #3A1260;
--brand-purple-mid: #7B3DB8;
--brand-lavender: #DCC6F2;
--brand-light-lavender:#EFE4FA;
--brand-card: #F8F2FD;
--brand-gold: #D4A24C;
--brand-gold-light: #FFD36B;
--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;
}
/* ─── Reset / Base ───────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
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 {
0%, 100% { transform: scale(1) translateY(0); 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 {
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);
}
@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');
/* ─── CSS Custom Properties ─────────────────────────────── */
:root {
--brand-purple: #5B1E8C;
--brand-purple-dark: #3A1260;
--brand-purple-mid: #7B3DB8;
--brand-lavender: #DCC6F2;
--brand-light-lavender:#EFE4FA;
--brand-card: #F8F2FD;
--brand-gold: #D4A24C;
--brand-gold-light: #FFD36B;
--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;
}
/* ─── Reset / Base ───────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
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 {
0%, 100% { transform: scale(1) translateY(0); 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 {
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,13 +1,13 @@
import { createRoot } from "react-dom/client";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import App from "./App";
import theme from "./theme";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);
import { createRoot } from "react-dom/client";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import App from "./App";
import theme from "./theme";
import "./index.css";
createRoot(document.getElementById("root")!).render(
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);

View File

@ -1,130 +1,130 @@
import { motion } from "framer-motion";
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 fadeInUp = {
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
};
const stagger = {
visible: { transition: { staggerChildren: 0.2 } },
};
export default function AboutPage() {
return (
<Box>
{/* ── Hero ── */}
<section className="about-hero">
<Container maxWidth="md">
<motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.h1 variants={fadeInUp}>The Light Behind the Wax</motion.h1>
<div className="section-divider" />
<motion.p variants={fadeInUp}>
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.div>
</Container>
</section>
{/* ── Brand Values ── */}
<section className="about-values">
<Container maxWidth="lg">
<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: Droplets, title: "Natural", desc: "Pure soy wax, lead-free cotton wicks, and premium fragrance oils infused with essential oils. Clean burns only." },
{ 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." },
].map(({ Icon, title, desc }, i) => (
<motion.div
key={title}
className="value-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
>
<div className="value-icon"><Icon size={40} /></div>
<h3>{title}</h3>
<p>{desc}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Meet the Maker ── */}
<section className="about-maker">
<div className="maker-gradient-overlay" />
<Container maxWidth="lg">
<div className="maker-grid">
<motion.div
className="maker-img-wrap"
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<img src={logoPath} alt="Mani Candles Co. Logo Art" />
<div className="maker-img-ring" />
</motion.div>
<motion.div
className="maker-content"
initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<h2>Meet the Maker</h2>
<div className="maker-divider" />
<p>What started as a quiet evening hobby quickly blossomed into a deep passion for the alchemy of scent and light.</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>
<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>
<span className="maker-signature">With love,</span>
</motion.div>
</div>
</Container>
</section>
{/* ── Process Timeline ── */}
<section className="about-process">
<Container maxWidth="md">
<h2>Our Process</h2>
<p className="subtitle">The journey of a Mani Candle.</p>
<div className="timeline">
{[
{ 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: "03", title: "Pouring", desc: "Each jar is wicked and poured entirely by hand, ensuring an even, smooth finish." },
{ step: "04", title: "Curing", desc: "Candles rest and cure for weeks to let the wax and fragrance bind perfectly." },
{ step: "05", title: "Packaging", desc: "Lovingly labeled, boxed, and prepared to illuminate your home." },
].map((item, i) => (
<motion.div
key={i}
className="timeline-item"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
>
<div className="timeline-step">{item.step}</div>
<div className="timeline-body">
<h3>{item.title}</h3>
<p>{item.desc}</p>
</div>
</motion.div>
))}
</div>
</Container>
</section>
</Box>
);
}
import { motion } from "framer-motion";
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 fadeInUp = {
hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
};
const stagger = {
visible: { transition: { staggerChildren: 0.2 } },
};
export default function AboutPage() {
return (
<Box>
{/* ── Hero ── */}
<section className="about-hero">
<Container maxWidth="md">
<motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.h1 variants={fadeInUp}>The Light Behind the Wax</motion.h1>
<div className="section-divider" />
<motion.p variants={fadeInUp}>
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.div>
</Container>
</section>
{/* ── Brand Values ── */}
<section className="about-values">
<Container maxWidth="lg">
<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: Droplets, title: "Natural", desc: "Pure soy wax, lead-free cotton wicks, and premium fragrance oils infused with essential oils. Clean burns only." },
{ 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." },
].map(({ Icon, title, desc }, i) => (
<motion.div
key={title}
className="value-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
>
<div className="value-icon"><Icon size={40} /></div>
<h3>{title}</h3>
<p>{desc}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Meet the Maker ── */}
<section className="about-maker">
<div className="maker-gradient-overlay" />
<Container maxWidth="lg">
<div className="maker-grid">
<motion.div
className="maker-img-wrap"
initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<img src={logoPath} alt="Mani Candles Co. Logo Art" />
<div className="maker-img-ring" />
</motion.div>
<motion.div
className="maker-content"
initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<h2>Meet the Maker</h2>
<div className="maker-divider" />
<p>What started as a quiet evening hobby quickly blossomed into a deep passion for the alchemy of scent and light.</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>
<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>
<span className="maker-signature">With love,</span>
</motion.div>
</div>
</Container>
</section>
{/* ── Process Timeline ── */}
<section className="about-process">
<Container maxWidth="md">
<h2>Our Process</h2>
<p className="subtitle">The journey of a Mani Candle.</p>
<div className="timeline">
{[
{ 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: "03", title: "Pouring", desc: "Each jar is wicked and poured entirely by hand, ensuring an even, smooth finish." },
{ step: "04", title: "Curing", desc: "Candles rest and cure for weeks to let the wax and fragrance bind perfectly." },
{ step: "05", title: "Packaging", desc: "Lovingly labeled, boxed, and prepared to illuminate your home." },
].map((item, i) => (
<motion.div
key={i}
className="timeline-item"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }}
>
<div className="timeline-step">{item.step}</div>
<div className="timeline-body">
<h3>{item.title}</h3>
<p>{item.desc}</p>
</div>
</motion.div>
))}
</div>
</Container>
</section>
</Box>
);
}

View File

@ -1,133 +1,133 @@
import { motion } from "framer-motion";
import { Mail, MapPin, Phone, Instagram } from "lucide-react";
import { HeartHandshake } from "lucide-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() {
const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success">("idle");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setFormStatus("submitting");
setTimeout(() => setFormStatus("success"), 1500);
};
return (
<Box>
{/* ── Hero ── */}
<section className="contact-hero">
<Container maxWidth="md">
<motion.div initial="hidden" animate="visible" variants={fadeInUp}>
<h1>Get in Touch</h1>
<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" />
</motion.div>
</Container>
</section>
{/* ── Contact Body ── */}
<section className="contact-body">
<Container maxWidth="lg">
<div className="contact-grid">
{/* Info Panel */}
<motion.div
className="contact-info-panel"
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<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 className="contact-info-label">{label}</div>
<div className="contact-info-value">{value}</div>
</div>
</div>
))}
</div>
<div className="contact-social-box">
<h3>Follow Our Journey</h3>
<p>Join our community on Instagram for behind-the-scenes peeks, styling inspiration, and launch announcements.</p>
<a href="#" className="contact-ig-link">
<Instagram size={20} />
<span>@manicandles.co</span>
</a>
</div>
</motion.div>
{/* Form Panel */}
<motion.div
className="contact-form-panel"
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<div className="contact-form-top-bar" />
{formStatus === "success" ? (
<motion.div
className="contact-success"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
>
<div className="success-icon-wrap"><HeartHandshake size={32} /></div>
<h3>Thank you!</h3>
<p>Your message has been received. We will get back to you as soon as possible.</p>
<button className="send-another-btn" onClick={() => setFormStatus("idle")}>Send another</button>
</motion.div>
) : (
<>
<h2>Send a Message</h2>
<form onSubmit={handleSubmit}>
<div className="form-row">
<div className="form-field">
<label htmlFor="name" className="form-label">Name</label>
<input id="name" type="text" className="form-input" placeholder="Your name" required />
</div>
<div className="form-field">
<label htmlFor="email" className="form-label">Email</label>
<input id="email" type="email" className="form-input" placeholder="Your email" required />
</div>
</div>
<div className="form-field">
<label htmlFor="subject" className="form-label">Subject</label>
<input id="subject" type="text" className="form-input" placeholder="How can we help?" required />
</div>
<div className="form-field">
<label htmlFor="message" className="form-label">Message</label>
<textarea id="message" className="form-textarea" placeholder="Write your message here..." required />
</div>
<button type="submit" className="form-submit" disabled={formStatus === "submitting"}>
{formStatus === "submitting" ? "Sending..." : "Send Message"}
</button>
</form>
</>
)}
</motion.div>
</div>
</Container>
</section>
{/* ── Closing Quote ── */}
<section className="contact-quote">
<p>"May the light of our candles bring peace to your mind and warmth to your soul."</p>
</section>
</Box>
);
import { motion } from "framer-motion";
import { Mail, MapPin, Phone, Instagram } from "lucide-react";
import { HeartHandshake } from "lucide-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() {
const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success">("idle");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setFormStatus("submitting");
setTimeout(() => setFormStatus("success"), 1500);
};
return (
<Box>
{/* ── Hero ── */}
<section className="contact-hero">
<Container maxWidth="md">
<motion.div initial="hidden" animate="visible" variants={fadeInUp}>
<h1>Get in Touch</h1>
<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" />
</motion.div>
</Container>
</section>
{/* ── Contact Body ── */}
<section className="contact-body">
<Container maxWidth="lg">
<div className="contact-grid">
{/* Info Panel */}
<motion.div
className="contact-info-panel"
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<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 className="contact-info-label">{label}</div>
<div className="contact-info-value">{value}</div>
</div>
</div>
))}
</div>
<div className="contact-social-box">
<h3>Follow Our Journey</h3>
<p>Join our community on Instagram for behind-the-scenes peeks, styling inspiration, and launch announcements.</p>
<a href="#" className="contact-ig-link">
<Instagram size={20} />
<span>@manicandles.co</span>
</a>
</div>
</motion.div>
{/* Form Panel */}
<motion.div
className="contact-form-panel"
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.8 }}
>
<div className="contact-form-top-bar" />
{formStatus === "success" ? (
<motion.div
className="contact-success"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
>
<div className="success-icon-wrap"><HeartHandshake size={32} /></div>
<h3>Thank you!</h3>
<p>Your message has been received. We will get back to you as soon as possible.</p>
<button className="send-another-btn" onClick={() => setFormStatus("idle")}>Send another</button>
</motion.div>
) : (
<>
<h2>Send a Message</h2>
<form onSubmit={handleSubmit}>
<div className="form-row">
<div className="form-field">
<label htmlFor="name" className="form-label">Name</label>
<input id="name" type="text" className="form-input" placeholder="Your name" required />
</div>
<div className="form-field">
<label htmlFor="email" className="form-label">Email</label>
<input id="email" type="email" className="form-input" placeholder="Your email" required />
</div>
</div>
<div className="form-field">
<label htmlFor="subject" className="form-label">Subject</label>
<input id="subject" type="text" className="form-input" placeholder="How can we help?" required />
</div>
<div className="form-field">
<label htmlFor="message" className="form-label">Message</label>
<textarea id="message" className="form-textarea" placeholder="Write your message here..." required />
</div>
<button type="submit" className="form-submit" disabled={formStatus === "submitting"}>
{formStatus === "submitting" ? "Sending..." : "Send Message"}
</button>
</form>
</>
)}
</motion.div>
</div>
</Container>
</section>
{/* ── Closing Quote ── */}
<section className="contact-quote">
<p>"May the light of our candles bring peace to your mind and warmth to your soul."</p>
</section>
</Box>
);
}

View File

@ -1,100 +1,100 @@
import { motion } from "framer-motion";
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 eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import jasmineImage from "@/assets/images/product-jasmine.png";
import lavenderImage from "@/assets/images/product-lavender.png";
import linenImage from "@/assets/images/product-linen.png";
import oudImage from "@/assets/images/product-oud.png";
import roseImage from "@/assets/images/product-rose.png";
import sandalwoodImage from "@/assets/images/product-sandalwood.png";
import "./products.css";
const PRODUCTS = [
{ id: 1, name: "Lavender Dream", scent: "Calming French Lavender, Vanilla, & Bergamot", price: "$28", image: lavenderImage, category: "Floral" },
{ 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() {
const [filter, setFilter] = useState("All");
const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))];
const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter((p) => p.category === filter);
return (
<Box>
{/* ── Hero ── */}
<section className="products-hero">
<Container maxWidth="md">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<h1>Our Collection</h1>
<p>Each candle is meticulously hand-poured with natural soy wax and premium fragrance oils, designed to elevate your everyday rituals.</p>
<div className="section-divider" />
</motion.div>
</Container>
</section>
{/* ── Products Body ── */}
<section className="products-body">
<Container maxWidth="xl">
{/* Filter Bar */}
<motion.div
className="filter-bar"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
{categories.map((cat) => (
<button
key={cat}
className={`filter-btn${filter === cat ? " active" : ""}`}
onClick={() => setFilter(cat)}
>
{cat}
</button>
))}
</motion.div>
{/* Grid */}
<div className="products-grid">
{filteredProducts.map((product, index) => (
<motion.div
key={product.id}
className="prod-card"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ delay: (index % 4) * 0.1, duration: 0.6 }}
>
<div className="prod-img-wrap">
<img src={product.image} alt={product.name} />
<div className="prod-overlay">
<button className="prod-overlay-btn">Add to Cart</button>
</div>
</div>
<div className="prod-info">
<div>
<h3 className="prod-name">{product.name}</h3>
<p className="prod-scent">{product.scent}</p>
</div>
<p className="prod-price">{product.price}</p>
</div>
</motion.div>
))}
</div>
</Container>
</section>
</Box>
);
}
import { motion } from "framer-motion";
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 eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import jasmineImage from "@/assets/images/product-jasmine.png";
import lavenderImage from "@/assets/images/product-lavender.png";
import linenImage from "@/assets/images/product-linen.png";
import oudImage from "@/assets/images/product-oud.png";
import roseImage from "@/assets/images/product-rose.png";
import sandalwoodImage from "@/assets/images/product-sandalwood.png";
import "./products.css";
const PRODUCTS = [
{ id: 1, name: "Lavender Dream", scent: "Calming French Lavender, Vanilla, & Bergamot", price: "$28", image: lavenderImage, category: "Floral" },
{ 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() {
const [filter, setFilter] = useState("All");
const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))];
const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter((p) => p.category === filter);
return (
<Box>
{/* ── Hero ── */}
<section className="products-hero">
<Container maxWidth="md">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<h1>Our Collection</h1>
<p>Each candle is meticulously hand-poured with natural soy wax and premium fragrance oils, designed to elevate your everyday rituals.</p>
<div className="section-divider" />
</motion.div>
</Container>
</section>
{/* ── Products Body ── */}
<section className="products-body">
<Container maxWidth="xl">
{/* Filter Bar */}
<motion.div
className="filter-bar"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2 }}
>
{categories.map((cat) => (
<button
key={cat}
className={`filter-btn${filter === cat ? " active" : ""}`}
onClick={() => setFilter(cat)}
>
{cat}
</button>
))}
</motion.div>
{/* Grid */}
<div className="products-grid">
{filteredProducts.map((product, index) => (
<motion.div
key={product.id}
className="prod-card"
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ delay: (index % 4) * 0.1, duration: 0.6 }}
>
<div className="prod-img-wrap">
<img src={product.image} alt={product.name} />
<div className="prod-overlay">
<button className="prod-overlay-btn">Add to Cart</button>
</div>
</div>
<div className="prod-info">
<div>
<h3 className="prod-name">{product.name}</h3>
<p className="prod-scent">{product.scent}</p>
</div>
<p className="prod-price">{product.price}</p>
</div>
</motion.div>
))}
</div>
</Container>
</section>
</Box>
);
}

View File

@ -1,229 +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);
}
/* ─── 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);
}

View File

@ -1,316 +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;
}
/* ─── 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;
}

View File

@ -1,386 +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);
}
/* ─── 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,147 +1,147 @@
import { motion } from "framer-motion";
import { Link } from "wouter";
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 roseImage from "@/assets/images/product-rose.png";
import eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import oudImage from "@/assets/images/product-oud.png";
import "./home.css";
const logoPath = "/opengraph.jpg";
const fadeInUp = {
hidden: { opacity: 0, y: 40 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
};
const stagger = {
visible: { transition: { staggerChildren: 0.2 } },
};
export default function Home() {
return (
<Box>
{/* ── Hero ── */}
<section className="home-hero">
<div className="home-hero-bg" />
<motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.div variants={fadeInUp} style={{ position: "relative" }}>
<div className="home-hero-logo-wrap">
<img src={logoPath} alt="Mani Candles Co." style={{ width: "100%", height: "100%", objectFit: "cover" }} />
</div>
<div className="home-hero-flame animate-flame">
<Flame size={40} fill="currentColor" style={{ filter: "drop-shadow(0 0 12px rgba(255,211,107,0.8))" }} />
</div>
</motion.div>
<motion.h1 variants={fadeInUp}>Mani Candles Co.</motion.h1>
<motion.span variants={fadeInUp} className="home-hero-tagline">
Handmade Natural Heartmade
</motion.span>
<motion.div variants={fadeInUp}>
<Link href="/products" className="hero-cta">Shop the Collection</Link>
</motion.div>
</motion.div>
</section>
{/* ── Philosophy ── */}
<section className="home-philosophy">
<Container maxWidth="lg">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true, margin: "-100px" }} variants={stagger}>
<motion.h2 variants={fadeInUp}>The Mani Philosophy</motion.h2>
<div className="section-divider" />
</motion.div>
<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
key={title}
className="philosophy-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
>
<div className="philosophy-icon-wrap"><Icon size={32} /></div>
<h3>{title}</h3>
<p>{desc}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Featured Products ── */}
<section className="home-products">
<Container maxWidth="xl">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp} className="home-products-header">
<div>
<h2>Curated Favorites</h2>
<div className="section-divider" style={{ margin: "0.5rem 0 0", width: 80 }} />
</div>
<Link href="/products" className="view-all-link">View All Candles</Link>
</motion.div>
<div className="product-grid">
{[
{ id: 1, name: "Lavender Dream", image: lavenderImage, price: "$28" },
{ id: 2, name: "Rose & Vanilla", image: roseImage, price: "$32" },
{ id: 3, name: "Eucalyptus Mint", image: eucalyptusImage, price: "$28" },
{ id: 4, name: "Oud & Amber", image: oudImage, price: "$36" },
].map((product, i) => (
<motion.div
key={product.id}
className="product-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.1, duration: 0.6 } } }}
>
<div className="product-img-wrap">
<img src={product.image} alt={product.name} />
<div className="product-overlay">
<button className="product-overlay-btn">Quick Add</button>
</div>
</div>
<h3 className="product-name">{product.name}</h3>
<p className="product-price">{product.price}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Quote ── */}
<section className="home-quote">
<div className="home-quote-texture" />
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
<Flame className="animate-flame" size={48} style={{ color: "var(--brand-gold)", margin: "0 auto 1.5rem", display: "block" }} />
<blockquote>
"A candle loses nothing by lighting another candle. It only makes the room warmer, brighter, and more beautiful."
</blockquote>
<div className="home-quote-divider" />
</motion.div>
</section>
{/* ── Newsletter ── */}
<section className="home-newsletter">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
<h2>Join Our Sanctuary</h2>
<p>Subscribe to receive gentle updates on new collections, exclusive access, and a little light in your inbox.</p>
<form className="newsletter-form" onSubmit={(e) => e.preventDefault()}>
<input type="email" className="newsletter-input" placeholder="Your email address" required />
<button type="submit" className="newsletter-btn">Subscribe</button>
</form>
</motion.div>
</section>
</Box>
);
}
import { motion } from "framer-motion";
import { Link } from "wouter";
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 roseImage from "@/assets/images/product-rose.png";
import eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import oudImage from "@/assets/images/product-oud.png";
import "./home.css";
const logoPath = "/opengraph.jpg";
const fadeInUp = {
hidden: { opacity: 0, y: 40 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
};
const stagger = {
visible: { transition: { staggerChildren: 0.2 } },
};
export default function Home() {
return (
<Box>
{/* ── Hero ── */}
<section className="home-hero">
<div className="home-hero-bg" />
<motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.div variants={fadeInUp} style={{ position: "relative" }}>
<div className="home-hero-logo-wrap">
<img src={logoPath} alt="Mani Candles Co." style={{ width: "100%", height: "100%", objectFit: "cover" }} />
</div>
<div className="home-hero-flame animate-flame">
<Flame size={40} fill="currentColor" style={{ filter: "drop-shadow(0 0 12px rgba(255,211,107,0.8))" }} />
</div>
</motion.div>
<motion.h1 variants={fadeInUp}>Mani Candles Co.</motion.h1>
<motion.span variants={fadeInUp} className="home-hero-tagline">
Handmade Natural Heartmade
</motion.span>
<motion.div variants={fadeInUp}>
<Link href="/products" className="hero-cta">Shop the Collection</Link>
</motion.div>
</motion.div>
</section>
{/* ── Philosophy ── */}
<section className="home-philosophy">
<Container maxWidth="lg">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true, margin: "-100px" }} variants={stagger}>
<motion.h2 variants={fadeInUp}>The Mani Philosophy</motion.h2>
<div className="section-divider" />
</motion.div>
<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
key={title}
className="philosophy-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }}
>
<div className="philosophy-icon-wrap"><Icon size={32} /></div>
<h3>{title}</h3>
<p>{desc}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Featured Products ── */}
<section className="home-products">
<Container maxWidth="xl">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp} className="home-products-header">
<div>
<h2>Curated Favorites</h2>
<div className="section-divider" style={{ margin: "0.5rem 0 0", width: 80 }} />
</div>
<Link href="/products" className="view-all-link">View All Candles</Link>
</motion.div>
<div className="product-grid">
{[
{ id: 1, name: "Lavender Dream", image: lavenderImage, price: "$28" },
{ id: 2, name: "Rose & Vanilla", image: roseImage, price: "$32" },
{ id: 3, name: "Eucalyptus Mint", image: eucalyptusImage, price: "$28" },
{ id: 4, name: "Oud & Amber", image: oudImage, price: "$36" },
].map((product, i) => (
<motion.div
key={product.id}
className="product-card"
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.1, duration: 0.6 } } }}
>
<div className="product-img-wrap">
<img src={product.image} alt={product.name} />
<div className="product-overlay">
<button className="product-overlay-btn">Quick Add</button>
</div>
</div>
<h3 className="product-name">{product.name}</h3>
<p className="product-price">{product.price}</p>
</motion.div>
))}
</div>
</Container>
</section>
{/* ── Quote ── */}
<section className="home-quote">
<div className="home-quote-texture" />
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
<Flame className="animate-flame" size={48} style={{ color: "var(--brand-gold)", margin: "0 auto 1.5rem", display: "block" }} />
<blockquote>
"A candle loses nothing by lighting another candle. It only makes the room warmer, brighter, and more beautiful."
</blockquote>
<div className="home-quote-divider" />
</motion.div>
</section>
{/* ── Newsletter ── */}
<section className="home-newsletter">
<motion.div initial="hidden" whileInView="visible" viewport={{ once: true }} variants={fadeInUp}>
<h2>Join Our Sanctuary</h2>
<p>Subscribe to receive gentle updates on new collections, exclusive access, and a little light in your inbox.</p>
<form className="newsletter-form" onSubmit={(e) => e.preventDefault()}>
<input type="email" className="newsletter-input" placeholder="Your email address" required />
<button type="submit" className="newsletter-btn">Subscribe</button>
</form>
</motion.div>
</section>
</Box>
);
}

View File

@ -1,173 +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);
}
/* ─── 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);
}

View File

@ -1,63 +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;
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,29 +1,29 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import { fileURLToPath } from "url";
const projectRoot = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(projectRoot, "src"),
},
dedupe: ["react", "react-dom"],
},
root: projectRoot,
build: {
outDir: path.resolve(projectRoot, "dist"),
emptyOutDir: true,
},
server: {
port: 5173,
host: "0.0.0.0",
},
preview: {
port: 4173,
host: "0.0.0.0",
},
});
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import { fileURLToPath } from "url";
const projectRoot = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(projectRoot, "src"),
},
dedupe: ["react", "react-dom"],
},
root: projectRoot,
build: {
outDir: path.resolve(projectRoot, "dist"),
emptyOutDir: true,
},
server: {
port: 5173,
host: "0.0.0.0",
},
preview: {
port: 4173,
host: "0.0.0.0",
},
});

5144
yarn.lock

File diff suppressed because it is too large Load Diff