// Maison Yeya — shared components const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React; // ────────────────────────────────────────────────────────── // Logo / mark // ────────────────────────────────────────────────────────── function YeyaMark({ size = 40, color = "currentColor", className = "" }) { // Stylized "Y / bridge" abstraction inspired by the official wordmark, drawn // from primitives (not a tracing of the brand logo). return ( ); } function Wordmark({ size = 18, color = "currentColor" }) { return ( Maison Yeya ); } // ────────────────────────────────────────────────────────── // Vertical tracked rail label // ────────────────────────────────────────────────────────── function VRail({ children, side = "left", top = 80, style = {} }) { const tweaks = useContext(TweakCtx); if (!tweaks?.showVerticals) return null; const sideStyle = side === "left" ? { left: 24 } : { right: 24 }; return (
{children}
); } // ────────────────────────────────────────────────────────── // Tweak context // ────────────────────────────────────────────────────────── const TweakCtx = createContext({}); const ACCENTS = { champagne: { value: "oklch(0.78 0.035 75)", deep: "oklch(0.55 0.06 60)" }, blush: { value: "oklch(0.82 0.04 20)", deep: "oklch(0.55 0.06 20)" }, ink: { value: "oklch(0.42 0.02 60)", deep: "oklch(0.28 0.02 60)" }, rouge: { value: "oklch(0.45 0.12 25)", deep: "oklch(0.32 0.12 25)" } }; // ────────────────────────────────────────────────────────── // Persistent CTA cluster (used in nav) // ────────────────────────────────────────────────────────── function HeaderCTA({ onAppointment, onEnquiry, enquiryCount }) { return (
); } // ────────────────────────────────────────────────────────── // Site Header (sticky) // ────────────────────────────────────────────────────────── function SiteHeader({ route, setRoute, onAppointment, onEnquiry, enquiryCount }) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 16); window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => window.removeEventListener("scroll", onScroll); }, []); const nav = [ { id: "home", label: "Maison" }, { id: "bridal", label: "Bridal" }, { id: "couture", label: "Couture" }, { id: "atelier", label: "L'Atelier" }, { id: "press", label: "Press" }, { id: "contact", label: "Contact" } ]; return (
{/* Left: nav */} {/* Center: mark */} {/* Right: meta nav + CTA */}
{nav.slice(3).map(n => ( setRoute(n.id)}>{n.label} ))}
); } function NavLink({ active, onClick, children }) { return ( ); } // ────────────────────────────────────────────────────────── // Footer // ────────────────────────────────────────────────────────── function SiteFooter({ setRoute, onAppointment }) { return ( ); } function FooterCol({ title, items }) { return (
{title}
); } // ────────────────────────────────────────────────────────── // Look card (used everywhere) // ────────────────────────────────────────────────────────── function LookCard({ look, onOpen, onSave, saved, large = false, lift = false, dark = false, aspect = "2/3" }) { const [hover, setHover] = useState(false); return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{ display: "flex", flexDirection: "column", gap: 0, cursor: "pointer" }} onClick={() => onOpen && onOpen(look)}>
{look.name} {/* Hover overlay */}
View Look
{look.name}
{look.subtitle}
{String(look.looks).padStart(2, "0")} Looks
); } function Heart({ filled, size = 12 }) { return ( ); } // ────────────────────────────────────────────────────────── // Marquee // ────────────────────────────────────────────────────────── function Marquee({ items, dark = false }) { // Duplicate items so the keyframe wraps seamlessly const tail = items.concat(items); return (
{tail.map((t, i) => ( {t} ))}
); } // ────────────────────────────────────────────────────────── // Look detail drawer // ────────────────────────────────────────────────────────── function LookDrawer({ look, onClose, onSave, saved, onAppointment }) { useEffect(() => { if (!look) return; const onKey = (e) => { if (e.key === "Escape") onClose(); }; document.body.style.overflow = "hidden"; window.addEventListener("keydown", onKey); return () => { document.body.style.overflow = ""; window.removeEventListener("keydown", onKey); }; }, [look, onClose]); if (!look) return null; return (
{look.range}
{look.subtitle}

{look.name}

{look.name}/

"{look.note}"

Pieces from this series are available by private appointment at the atelier in Dubai Design District. Each gown is hand-made to measure; quotation provided after consultation.

); } function Spec({ label, value }) { return (
{label}
{value}
); } // ────────────────────────────────────────────────────────── // Enquiry Drawer // ────────────────────────────────────────────────────────── function EnquiryDrawer({ open, items, onClose, onRemove, onAppointment }) { useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === "Escape") onClose(); }; document.body.style.overflow = "hidden"; window.addEventListener("keydown", onKey); return () => { document.body.style.overflow = ""; window.removeEventListener("keydown", onKey); }; }, [open, onClose]); if (!open) return null; return (
Enquiry
Saved Looks
{items.length === 0 ? (

No looks saved yet. Browse Bridal or Couture and tap "Save" on any look to begin an enquiry.

) : (
{items.map(it => (
{it.name}/
{it.name}
{it.subtitle}
))}

We will prepare a quotation and arrange a private fitting at the atelier in Dubai Design District. Lead times will be confirmed at consultation.

)}
); } Object.assign(window, { YeyaMark, Wordmark, VRail, TweakCtx, ACCENTS, SiteHeader, SiteFooter, LookCard, Marquee, LookDrawer, EnquiryDrawer, Heart });