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 node_modules
dist dist
build build
.git .git
.gitignore .gitignore
Dockerfile Dockerfile
.dockerignore .dockerignore
README.md README.md
.env .env

View File

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

View File

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

12
.vscode/settings.json vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,99 +1,99 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import "./ScrollCandle.css"; import "./ScrollCandle.css";
// Space taken by flame + wick + base + label + bottom offset + header (ticker 36 + AppBar 76) + safety gap // 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 NON_BODY_HEIGHT = 70 + 8 + 10 + 24 + 40 + 112 + 16;
const CANDLE_MIN_HEIGHT = 14; // px — almost fully melted const CANDLE_MIN_HEIGHT = 14; // px — almost fully melted
// //
export function ScrollCandle() { export function ScrollCandle() {
const [maxHeight, setMaxHeight] = useState( const [maxHeight, setMaxHeight] = useState(
() => Math.max(200, window.innerHeight - NON_BODY_HEIGHT) () => Math.max(200, window.innerHeight - NON_BODY_HEIGHT)
); );
const [candleHeight, setCandleHeight] = useState( const [candleHeight, setCandleHeight] = useState(
() => Math.max(200, window.innerHeight - NON_BODY_HEIGHT) () => Math.max(200, window.innerHeight - NON_BODY_HEIGHT)
); );
const [scrollPct, setScrollPct] = useState(0); const [scrollPct, setScrollPct] = useState(0);
// Recalculate on resize // Recalculate on resize
useEffect(() => { useEffect(() => {
const onResize = () => { const onResize = () => {
const newMax = Math.max(200, window.innerHeight - NON_BODY_HEIGHT); const newMax = Math.max(200, window.innerHeight - NON_BODY_HEIGHT);
setMaxHeight(newMax); setMaxHeight(newMax);
}; };
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize);
}, []); }, []);
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const scrollTop = window.scrollY; const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight; const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const pct = docHeight > 0 ? Math.min(scrollTop / docHeight, 1) : 0; const pct = docHeight > 0 ? Math.min(scrollTop / docHeight, 1) : 0;
setScrollPct(pct); setScrollPct(pct);
setCandleHeight( setCandleHeight(
Math.max(CANDLE_MIN_HEIGHT, maxHeight - pct * (maxHeight - CANDLE_MIN_HEIGHT)) Math.max(CANDLE_MIN_HEIGHT, maxHeight - pct * (maxHeight - CANDLE_MIN_HEIGHT))
); );
}; };
window.addEventListener("scroll", handleScroll, { passive: true }); window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll);
}, [maxHeight]); }, [maxHeight]);
// Flame shrinks slightly as candle melts // Flame shrinks slightly as candle melts
const flameScale = 0.6 + (candleHeight / maxHeight) * 0.4; const flameScale = 0.6 + (candleHeight / maxHeight) * 0.4;
// Label changes as candle melts // Label changes as candle melts
const label = const label =
scrollPct < 0.15 ? "just lit" : scrollPct < 0.15 ? "just lit" :
scrollPct < 0.45 ? "burning..." : scrollPct < 0.45 ? "burning..." :
scrollPct < 0.75 ? "melting..." : scrollPct < 0.75 ? "melting..." :
scrollPct < 0.92 ? "almost gone" : scrollPct < 0.92 ? "almost gone" :
"★ keep scrolling"; "★ keep scrolling";
return ( return (
<div className="scroll-candle-wrap" aria-hidden="true"> <div className="scroll-candle-wrap" aria-hidden="true">
{/* ── Flame ── */} {/* ── Flame ── */}
<div <div
className="candle-flame-wrap" className="candle-flame-wrap"
style={{ transform: `scale(${flameScale})`, transformOrigin: "bottom center" }} style={{ transform: `scale(${flameScale})`, transformOrigin: "bottom center" }}
> >
{/* Smoke */} {/* Smoke */}
<div className="smoke-wrap"> <div className="smoke-wrap">
<div className="smoke-particle" /> <div className="smoke-particle" />
<div className="smoke-particle" /> <div className="smoke-particle" />
<div className="smoke-particle" /> <div className="smoke-particle" />
</div> </div>
{/* Glow halo */} {/* Glow halo */}
<div className="flame-glow" /> <div className="flame-glow" />
{/* Outer flame */} {/* Outer flame */}
<div className="flame-outer"> <div className="flame-outer">
{/* Inner core */} {/* Inner core */}
<div className="flame-inner" /> <div className="flame-inner" />
</div> </div>
</div> </div>
{/* ── Wick ── */} {/* ── Wick ── */}
<div className="candle-wick" /> <div className="candle-wick" />
{/* ── Candle Body (shrinks on scroll) ── */} {/* ── Candle Body (shrinks on scroll) ── */}
<div className="candle-body" style={{ height: candleHeight }}> <div className="candle-body" style={{ height: candleHeight }}>
<div className="wax-drip wax-drip-1" /> <div className="wax-drip wax-drip-1" />
<div className="wax-drip wax-drip-2" /> <div className="wax-drip wax-drip-2" />
<div className="wax-drip wax-drip-3" /> <div className="wax-drip wax-drip-3" />
</div> </div>
{/* ── Base / Holder ── */} {/* ── Base / Holder ── */}
<div className="candle-base" /> <div className="candle-base" />
{/* ── Label ── */} {/* ── Label ── */}
<p <p
className="candle-scroll-label" className="candle-scroll-label"
style={{ opacity: scrollPct > 0.02 ? 1 : 0 }} style={{ opacity: scrollPct > 0.02 ? 1 : 0 }}
> >
{label} {label}
</p> </p>
</div> </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'); @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 ─────────────────────────────── */ /* ─── CSS Custom Properties ─────────────────────────────── */
:root { :root {
--brand-purple: #5B1E8C; --brand-purple: #5B1E8C;
--brand-purple-dark: #3A1260; --brand-purple-dark: #3A1260;
--brand-purple-mid: #7B3DB8; --brand-purple-mid: #7B3DB8;
--brand-lavender: #DCC6F2; --brand-lavender: #DCC6F2;
--brand-light-lavender:#EFE4FA; --brand-light-lavender:#EFE4FA;
--brand-card: #F8F2FD; --brand-card: #F8F2FD;
--brand-gold: #D4A24C; --brand-gold: #D4A24C;
--brand-gold-light: #FFD36B; --brand-gold-light: #FFD36B;
--brand-gold-core: #FFF3C4; --brand-gold-core: #FFF3C4;
--brand-text: #2D0D52; --brand-text: #2D0D52;
--brand-muted: #7B5A9E; --brand-muted: #7B5A9E;
--brand-border: #D5BEF0; --brand-border: #D5BEF0;
--font-sans: 'Inter', sans-serif; --font-sans: 'Inter', sans-serif;
--font-serif: 'Cormorant Garamond', serif; --font-serif: 'Cormorant Garamond', serif;
--font-display: 'Cinzel', serif; --font-display: 'Cinzel', serif;
--font-script: 'Great Vibes', cursive; --font-script: 'Great Vibes', cursive;
} }
/* ─── Reset / Base ───────────────────────────────────────── */ /* ─── Reset / Base ───────────────────────────────────────── */
*, *::before, *::after { *, *::before, *::after {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
body { body {
font-family: var(--font-sans); font-family: var(--font-sans);
background-color: var(--brand-light-lavender); background-color: var(--brand-light-lavender);
color: var(--brand-text); color: var(--brand-text);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
line-height: 1.6; line-height: 1.6;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: var(--font-serif); font-family: var(--font-serif);
line-height: 1.2; line-height: 1.2;
} }
a { a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
img { img {
max-width: 100%; max-width: 100%;
display: block; display: block;
} }
/* ─── Keyframe Animations ────────────────────────────────── */ /* ─── Keyframe Animations ────────────────────────────────── */
@keyframes flame-flicker { @keyframes flame-flicker {
0%, 100% { transform: scale(1) translateY(0); opacity: 0.9; } 0%, 100% { transform: scale(1) translateY(0); opacity: 0.9; }
50% { transform: scale(1.05) translateY(-2px); opacity: 1; } 50% { transform: scale(1.05) translateY(-2px); opacity: 1; }
} }
@keyframes fadeInUp { @keyframes fadeInUp {
from { opacity: 0; transform: translateY(24px); } from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
/* ─── Global Utility Classes ─────────────────────────────── */ /* ─── Global Utility Classes ─────────────────────────────── */
.animate-flame { .animate-flame {
animation: flame-flicker 2s ease-in-out infinite alternate; animation: flame-flicker 2s ease-in-out infinite alternate;
} }
.font-script { .font-script {
font-family: var(--font-script); font-family: var(--font-script);
} }
.font-display { .font-display {
font-family: var(--font-display); font-family: var(--font-display);
} }
.font-serif { .font-serif {
font-family: var(--font-serif); font-family: var(--font-serif);
} }

View File

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

View File

@ -1,130 +1,130 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Droplets, Flame, HeartHandshake } from "lucide-react"; import { Droplets, Flame, HeartHandshake } from "lucide-react";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import "./about.css"; import "./about.css";
const logoPath = "/opengraph.jpg"; const logoPath = "/opengraph.jpg";
const fadeInUp = { const fadeInUp = {
hidden: { opacity: 0, y: 30 }, hidden: { opacity: 0, y: 30 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }, visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } },
}; };
const stagger = { const stagger = {
visible: { transition: { staggerChildren: 0.2 } }, visible: { transition: { staggerChildren: 0.2 } },
}; };
export default function AboutPage() { export default function AboutPage() {
return ( return (
<Box> <Box>
{/* ── Hero ── */} {/* ── Hero ── */}
<section className="about-hero"> <section className="about-hero">
<Container maxWidth="md"> <Container maxWidth="md">
<motion.div initial="hidden" animate="visible" variants={stagger}> <motion.div initial="hidden" animate="visible" variants={stagger}>
<motion.h1 variants={fadeInUp}>The Light Behind the Wax</motion.h1> <motion.h1 variants={fadeInUp}>The Light Behind the Wax</motion.h1>
<div className="section-divider" /> <div className="section-divider" />
<motion.p variants={fadeInUp}> <motion.p variants={fadeInUp}>
Mani Candles Co. is a small handmade candle brand crafted with love, natural ingredients, and intention. 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 Every candle is poured by hand in small batches, designed to bring warmth, luxury, and a touch of romance
into your sacred spaces. into your sacred spaces.
</motion.p> </motion.p>
</motion.div> </motion.div>
</Container> </Container>
</section> </section>
{/* ── Brand Values ── */} {/* ── Brand Values ── */}
<section className="about-values"> <section className="about-values">
<Container maxWidth="lg"> <Container maxWidth="lg">
<div className="values-grid"> <div className="values-grid">
{[ {[
{ Icon: HeartHandshake, title: "Handmade", desc: "We believe in the power of human touch. No machines, no mass production. Just careful hands pouring beautiful creations." }, { Icon: HeartHandshake, 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: 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." }, { 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) => ( ].map(({ Icon, title, desc }, i) => (
<motion.div <motion.div
key={title} key={title}
className="value-card" className="value-card"
initial="hidden" initial="hidden"
whileInView="visible" whileInView="visible"
viewport={{ once: true }} viewport={{ once: true }}
variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { delay: i * 0.2, duration: 0.6 } } }} 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> <div className="value-icon"><Icon size={40} /></div>
<h3>{title}</h3> <h3>{title}</h3>
<p>{desc}</p> <p>{desc}</p>
</motion.div> </motion.div>
))} ))}
</div> </div>
</Container> </Container>
</section> </section>
{/* ── Meet the Maker ── */} {/* ── Meet the Maker ── */}
<section className="about-maker"> <section className="about-maker">
<div className="maker-gradient-overlay" /> <div className="maker-gradient-overlay" />
<Container maxWidth="lg"> <Container maxWidth="lg">
<div className="maker-grid"> <div className="maker-grid">
<motion.div <motion.div
className="maker-img-wrap" className="maker-img-wrap"
initial={{ opacity: 0, x: -50 }} initial={{ opacity: 0, x: -50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
> >
<img src={logoPath} alt="Mani Candles Co. Logo Art" /> <img src={logoPath} alt="Mani Candles Co. Logo Art" />
<div className="maker-img-ring" /> <div className="maker-img-ring" />
</motion.div> </motion.div>
<motion.div <motion.div
className="maker-content" className="maker-content"
initial={{ opacity: 0, x: 50 }} initial={{ opacity: 0, x: 50 }}
whileInView={{ opacity: 1, x: 0 }} whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
> >
<h2>Meet the Maker</h2> <h2>Meet the Maker</h2>
<div className="maker-divider" /> <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>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>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> <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> <span className="maker-signature">With love,</span>
</motion.div> </motion.div>
</div> </div>
</Container> </Container>
</section> </section>
{/* ── Process Timeline ── */} {/* ── Process Timeline ── */}
<section className="about-process"> <section className="about-process">
<Container maxWidth="md"> <Container maxWidth="md">
<h2>Our Process</h2> <h2>Our Process</h2>
<p className="subtitle">The journey of a Mani Candle.</p> <p className="subtitle">The journey of a Mani Candle.</p>
<div className="timeline"> <div className="timeline">
{[ {[
{ step: "01", title: "Sourcing", desc: "We select only the finest natural soy wax and premium, phthalate-free fragrances." }, { step: "01", title: "Sourcing", desc: "We select only the finest natural soy wax and premium, phthalate-free fragrances." },
{ step: "02", title: "Blending", desc: "Fragrances are meticulously mixed at the exact temperature for optimal throw." }, { step: "02", title: "Blending", desc: "Fragrances are meticulously mixed at the exact temperature for optimal throw." },
{ step: "03", title: "Pouring", desc: "Each jar is wicked and poured entirely by hand, ensuring an even, smooth finish." }, { 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: "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." }, { step: "05", title: "Packaging", desc: "Lovingly labeled, boxed, and prepared to illuminate your home." },
].map((item, i) => ( ].map((item, i) => (
<motion.div <motion.div
key={i} key={i}
className="timeline-item" className="timeline-item"
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ delay: i * 0.1, duration: 0.5 }} transition={{ delay: i * 0.1, duration: 0.5 }}
> >
<div className="timeline-step">{item.step}</div> <div className="timeline-step">{item.step}</div>
<div className="timeline-body"> <div className="timeline-body">
<h3>{item.title}</h3> <h3>{item.title}</h3>
<p>{item.desc}</p> <p>{item.desc}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</Container> </Container>
</section> </section>
</Box> </Box>
); );
} }

View File

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

View File

@ -1,100 +1,100 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useState } from "react"; import { useState } from "react";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import birthdayImage from "@/assets/images/product-birthday.png"; import birthdayImage from "@/assets/images/product-birthday.png";
import eucalyptusImage from "@/assets/images/product-eucalyptus.png"; import eucalyptusImage from "@/assets/images/product-eucalyptus.png";
import jasmineImage from "@/assets/images/product-jasmine.png"; import jasmineImage from "@/assets/images/product-jasmine.png";
import lavenderImage from "@/assets/images/product-lavender.png"; import lavenderImage from "@/assets/images/product-lavender.png";
import linenImage from "@/assets/images/product-linen.png"; import linenImage from "@/assets/images/product-linen.png";
import oudImage from "@/assets/images/product-oud.png"; import oudImage from "@/assets/images/product-oud.png";
import roseImage from "@/assets/images/product-rose.png"; import roseImage from "@/assets/images/product-rose.png";
import sandalwoodImage from "@/assets/images/product-sandalwood.png"; import sandalwoodImage from "@/assets/images/product-sandalwood.png";
import "./products.css"; import "./products.css";
const PRODUCTS = [ const PRODUCTS = [
{ id: 1, name: "Lavender Dream", scent: "Calming French Lavender, Vanilla, & Bergamot", price: "$28", image: lavenderImage, category: "Floral" }, { id: 1, 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: 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: 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: 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: 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: 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: 7, name: "Fresh Linen", scent: "Crisp Cotton, White Tea, & Lily of the Valley", price: "$26", image: linenImage, category: "Fresh" },
{ id: 8, name: "Birthday Cake", scent: "Vanilla Buttercream, Sugar Spun, & Joy", price: "$28", image: birthdayImage, category: "Sweet" }, { id: 8, name: "Birthday Cake", scent: "Vanilla Buttercream, Sugar Spun, & Joy", price: "$28", image: birthdayImage, category: "Sweet" },
]; ];
export default function ProductsPage() { export default function ProductsPage() {
const [filter, setFilter] = useState("All"); const [filter, setFilter] = useState("All");
const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))]; const categories = ["All", ...Array.from(new Set(PRODUCTS.map((p) => p.category)))];
const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter((p) => p.category === filter); const filteredProducts = filter === "All" ? PRODUCTS : PRODUCTS.filter((p) => p.category === filter);
return ( return (
<Box> <Box>
{/* ── Hero ── */} {/* ── Hero ── */}
<section className="products-hero"> <section className="products-hero">
<Container maxWidth="md"> <Container maxWidth="md">
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} transition={{ duration: 0.8 }}
> >
<h1>Our Collection</h1> <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> <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" /> <div className="section-divider" />
</motion.div> </motion.div>
</Container> </Container>
</section> </section>
{/* ── Products Body ── */} {/* ── Products Body ── */}
<section className="products-body"> <section className="products-body">
<Container maxWidth="xl"> <Container maxWidth="xl">
{/* Filter Bar */} {/* Filter Bar */}
<motion.div <motion.div
className="filter-bar" className="filter-bar"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
> >
{categories.map((cat) => ( {categories.map((cat) => (
<button <button
key={cat} key={cat}
className={`filter-btn${filter === cat ? " active" : ""}`} className={`filter-btn${filter === cat ? " active" : ""}`}
onClick={() => setFilter(cat)} onClick={() => setFilter(cat)}
> >
{cat} {cat}
</button> </button>
))} ))}
</motion.div> </motion.div>
{/* Grid */} {/* Grid */}
<div className="products-grid"> <div className="products-grid">
{filteredProducts.map((product, index) => ( {filteredProducts.map((product, index) => (
<motion.div <motion.div
key={product.id} key={product.id}
className="prod-card" className="prod-card"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }} whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }} viewport={{ once: true, margin: "-50px" }}
transition={{ delay: (index % 4) * 0.1, duration: 0.6 }} transition={{ delay: (index % 4) * 0.1, duration: 0.6 }}
> >
<div className="prod-img-wrap"> <div className="prod-img-wrap">
<img src={product.image} alt={product.name} /> <img src={product.image} alt={product.name} />
<div className="prod-overlay"> <div className="prod-overlay">
<button className="prod-overlay-btn">Add to Cart</button> <button className="prod-overlay-btn">Add to Cart</button>
</div> </div>
</div> </div>
<div className="prod-info"> <div className="prod-info">
<div> <div>
<h3 className="prod-name">{product.name}</h3> <h3 className="prod-name">{product.name}</h3>
<p className="prod-scent">{product.scent}</p> <p className="prod-scent">{product.scent}</p>
</div> </div>
<p className="prod-price">{product.price}</p> <p className="prod-price">{product.price}</p>
</div> </div>
</motion.div> </motion.div>
))} ))}
</div> </div>
</Container> </Container>
</section> </section>
</Box> </Box>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5144
yarn.lock

File diff suppressed because it is too large Load Diff