/* Shared components: Nav, Footer, FAB, BottomBar, BookingModal, Lightbox */ const { useState, useEffect, useRef, useMemo } = React; const D = window.BLO_DATA; const IMG = (p) => `media/images/${p}`; // --- Stars row --- const Stars = ({ n = 5 }) => ( {"★★★★★".slice(0, n)}{"☆☆☆☆☆".slice(0, 5 - n)} ); // --- Nav --- function Navbar({ route, setRoute, openBooking }) { const [open, setOpen] = useState(false); const links = [ { id: "home", label: "Home" }, { id: "services", label: "Services" }, { id: "memberships", label: "Offers" }, { id: "gallery", label: "Gallery" }, { id: "team", label: "Team" }, { id: "reviews", label: "Reviews" }, { id: "book", label: "Visit" }, ]; const go = (r) => { setRoute(r); setOpen(false); window.scrollTo({ top: 0, behavior: "smooth" }); }; return ( <>
go("home")}> Blo Beauty Lounge
WhatsApp
BloBeauty Lounge
{links.map(l => ( ))}
WhatsApp
); } // --- WhatsApp FAB --- function WhatsAppFab() { return ( ); } // --- Mobile bottom action bar --- function BottomBar({ openBooking }) { return (
Call WhatsApp
); } // --- Footer --- function Footer({ setRoute, openBooking }) { return ( ); } // --- Booking modal (Fresha-style flow simulation) --- function BookingModal({ open, onClose, preselect }) { const [step, setStep] = useState(0); const [service, setService] = useState(null); const [staff, setStaff] = useState(null); const [date, setDate] = useState(0); const [time, setTime] = useState(null); useEffect(() => { if (open) { setStep(0); setService(preselect || null); setStaff(null); setDate(0); setTime(null); document.body.style.overflow = "hidden"; } else { document.body.style.overflow = ""; } return () => { document.body.style.overflow = ""; }; }, [open, preselect]); if (!open) return null; const services = [ { id: "blowdry", name: "Blowdry & Style", meta: "45 min · from AED — (Fresha)" }, { id: "moroccan", name: "Moroccan Bath", meta: "45 min · AED 110.25 (H/H)" }, { id: "lash-lift", name: "Lash Lifting", meta: "60 min · AED 162.75 (H/H)" }, { id: "lash-ext", name: "Lash Extensions", meta: "2 hr · AED 159 (promo)" }, { id: "nails-fs", name: "Nails — Full Set", meta: "2 hr · AED 149 (promo)" }, { id: "combo", name: "Classic Combo", meta: "2 hr · AED 156.50 (H/H)" }, { id: "colour", name: "Hair Colour", meta: "2–3 hr · Price on Fresha" }, { id: "facial", name: "Facial", meta: "60 min · Price on Fresha" }, ]; const staffOptions = [ { id: "any", name: "Any available", meta: "We'll match you" }, ...D.team.slice(0, 6).map(t => ({ id: t.name.toLowerCase(), name: t.name, meta: `${t.role} · ${t.rating}★` })), ]; const dates = useMemo(() => { const arr = []; const today = new Date("2026-05-22"); const dow = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]; for (let i = 0; i < 10; i++) { const d = new Date(today.getFullYear(), today.getMonth(), today.getDate() + i); arr.push({ dow: dow[d.getDay()], day: d.getDate(), full: d }); } return arr; }, []); const times = ["10:00","10:30","11:00","11:30","12:00","12:30","13:00","13:30","14:00","14:30","15:00","15:30","16:00","16:30","17:00","17:30","18:00","18:30","19:00","19:30"]; // pseudo-busy slots vary by date const busy = useMemo(() => { const seed = (date * 7) % 19; return new Set([(seed) % 20, (seed + 5) % 20, (seed + 11) % 20, (seed + 14) % 20].map(i => times[i])); }, [date]); const canNext = [service, staff, time, true][step]; const stepTitles = ["Choose a service", "Choose a specialist", "Pick a time", "Confirm booking"]; const next = () => setStep(s => Math.min(s + 1, 3)); const back = () => setStep(s => Math.max(s - 1, 0)); const confirm = () => setStep(4); // success return (
e.stopPropagation()}>

{step < 4 ? stepTitles[step] : "Booking received"}

{step < 4 && (
{[0,1,2,3].map(i => )}
)}
{step === 0 && (
{services.map(s => ( ))}
)} {step === 1 && (
{staffOptions.map(s => ( ))}
)} {step === 2 && ( <>
{dates.map((d, i) => ( ))}
{times.map(t => ( ))}

Salon hours daily 09:00 – 21:00. Times in GST.

)} {step === 3 && service && staff && time && ( <>
Service{service.name}
{service.meta}
Specialist{staff.name}
When{dates[date].dow} {dates[date].day} May · {time}
WhereVilla 710
Jumeirah Beach Rd, Umm Suqeim 2

MVP wires this confirmation to Fresha's deep-link; Phase 2 captures payment & pre-auth natively.

)} {step === 4 && (

See you soon.

We've held your slot. A confirmation will land on WhatsApp.

Booking ref
BLO–{(Math.random()*9000+1000)|0}
)}
{step > 0 && step < 4 && } {step === 0 && Step 1 of 4} {step === 4 ? : (step < 3 ? : ) }
); } // --- Lightbox --- function Lightbox({ images, index, onClose, setIndex }) { useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); if (e.key === "ArrowRight") setIndex((i) => (i + 1) % images.length); if (e.key === "ArrowLeft") setIndex((i) => (i - 1 + images.length) % images.length); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [images.length, onClose, setIndex]); if (index === null) return null; return (
e.stopPropagation()} />
); } // expose Object.assign(window, { Navbar, Footer, WhatsAppFab, BottomBar, BookingModal, Lightbox, Stars, IMG, D });