/* Sub-pages: Services, Memberships, Gallery, Team, Reviews, Book */
const { useState: _S, useEffect: _E, useMemo: _M, useRef: _R } = React;
// --- Generic page header ---
function PageHead({ eyebrow, title, italic, sub }) {
return (
{eyebrow}
{title} {italic && {italic} }
{sub &&
{sub}
}
);
}
// ===================== SERVICES =====================
function ServicesPage({ openBookingFor }) {
const [active, setActive] = _S(D.categories[0].id);
// observe category in view
_E(() => {
const sections = D.categories.map(c => document.getElementById(`svc-${c.id}`)).filter(Boolean);
const onScroll = () => {
const y = window.scrollY + 160;
let curr = sections[0]?.id;
for (const s of sections) { if (s.offsetTop <= y) curr = s.id; }
if (curr) setActive(curr.replace("svc-", ""));
};
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const jumpTo = (id) => {
const el = document.getElementById(`svc-${id}`);
if (el) window.scrollTo({ top: el.offsetTop - 130, behavior: "smooth" });
};
return (
{D.categories.map(c => (
jumpTo(c.id)}>
{c.title}
))}
{D.categories.map(c => (
{c.items.map((it, i) => (
{it.name}
{it.duration}{it.flag && {it.flag} }
{it.price !== null ? (
<>
{it.was &&
AED {it.was} }
AED {it.price}
{it.note}
>
) : (
{it.note}
)}
openBookingFor(it.name)}>Book
))}
))}
Don't see a price? It sits inside Fresha's booking flow. Confirm at checkout, or WhatsApp us for a quick quote.
);
}
// ===================== MEMBERSHIPS / OFFERS =====================
function MembershipsPage({ openBookingFor }) {
// Proposed memberships — explicitly flagged as proposal, not invented standing menu
const proposals = [
{
name: "The Blowdry Card",
tier: "Proposal — client to confirm",
colour: "var(--cream)",
price: "—", priceUnit: "8 blowdries",
perks: ["Save vs walk-in", "Any stylist, any time", "Transferable to a friend"],
},
{
name: "The Glow Membership",
tier: "Proposal — client to confirm",
colour: "var(--plum)",
textColour: "var(--ivory)",
price: "—", priceUnit: "monthly",
perks: ["1 blowdry + 1 mani per month", "10% off all add-ons", "Priority Happy-Hour booking"],
featured: true,
},
{
name: "The Bridal Block",
tier: "Proposal — client to confirm",
colour: "var(--cream)",
price: "—", priceUnit: "package",
perks: ["Hair + makeup + lashes + Moroccan bath", "Dedicated team on the day", "Trial session included"],
},
];
return (
Live & verified
{D.offers.map((o, i) => (
{o.kicker}
{o.saving &&
{o.saving} }
{o.name}
{o.duration}
AED
{o.price}
{o.was && was {o.was} }
{o.copy}
openBookingFor(o.name)}>Book →
))}
Proposed memberships
{proposals.map((p, i) => (
{p.featured &&
Most popular }
{p.tier}
{p.name}
{p.price}
{p.priceUnit}
{p.perks.map((perk, k) => (
✦ {perk}
))}
Express interest
))}
Memberships shown above are draft proposals for the new website — pricing and inclusions to be confirmed by Blo Out.
);
}
// ===================== GALLERY =====================
function GalleryPage() {
const [filter, setFilter] = _S("all");
const [lb, setLb] = _S(null);
const all = _M(() => {
return [
...D.gallery.work.map(f => ({ f, kind: "work" })),
...D.gallery.venue.map(f => ({ f, kind: "venue" })),
];
}, []);
const filtered = filter === "all" ? all : all.filter(x => x.kind === filter);
const images = filtered.map(x => x.f);
return (
{["all","work","venue"].map(f => (
setFilter(f)}>
{f === "all" ? "Everything" : f === "work" ? "Hair · Nails · Lashes" : "The Salon"}
))}
{filtered.map((g, i) => (
setLb(i)}>
))}
Phase 2: live Instagram feed (@blooutbeauty ).
setLb(null)} setIndex={setLb}/>
);
}
// ===================== TEAM =====================
function TeamPage({ openBookingFor }) {
const [filter, setFilter] = _S("all");
const groups = [
{ id: "all", label: "Everyone" },
{ id: "lashes", label: "Lashes" },
{ id: "hair", label: "Hair" },
{ id: "nails", label: "Nails" },
{ id: "spa", label: "Spa & Brows" },
];
const matchesGroup = (t) => {
if (filter === "all") return true;
if (filter === "lashes") return /Lash/i.test(t.role);
if (filter === "hair") return /Hair|Color/i.test(t.role);
if (filter === "nails") return /Nail/i.test(t.role);
if (filter === "spa") return /Brow|Facial|Moroccan/i.test(t.role);
};
return (
{groups.map(g => (
setFilter(g.id)}>{g.label}
))}
{D.team.filter(matchesGroup).map(t => (
openBookingFor(`${t.name} — ${t.role}`)}>
{t.rating}★
{t.name}
{t.role}
{!t.confirmed &&
photo: stand-in }
))}
A note on portraits
Only three name-to-photo mappings are confirmed (Gemma, Fouzia, Perveen).
The other portraits are stand-ins from Blo Out's own staff gallery while we collect
consented headshots mapped to each specialist.
);
}
// ===================== REVIEWS =====================
function ReviewsPage() {
return (
From {D.brand.reviewCount.toLocaleString()} reviews on Fresha
Most-praised: Moroccan Bath, Lashes, Nails.
Fresha rating re-pulled {D.brand.reviewLatest}. The aggregate is a moving target — we'll refresh on launch day.
Moroccan Bath
Lash Lift
Nail Art
Caviar Treatment
Friendly staff
{D.reviews.map((r, i) => (
{"★★★★★".slice(0, r.stars)}{"☆☆☆☆☆".slice(0, 5 - r.stars)}
{r.text}
{r.name} · {r.date}
{r.source}
))}
What to expect
A tiny minority of low reviews flag colour expectations and Moroccan-bath glove add-ons. We pre-empt that by
always consulting on colour first and listing
what's included in every Moroccan-bath booking on the booking page.
Honest expectations, every time.
);
}
// ===================== BOOK (visit) =====================
function BookPage({ openBooking }) {
return (
Online
Book online.
MVP deep-links to our Fresha listing. Pick service, specialist, time — confirmed by SMS.
Open booking →
Fastest
WhatsApp.
Send the service and your preferred day. Most replies inside 15 minutes during opening hours.
Open WhatsApp →
Old-fashioned
Call us.
Speak to the front desk for bridal blocks, group bookings, or to hold a Moroccan-bath room mid-Happy-Hour.
{D.brand.phone}
);
}
// Small map block used on Book page (no h2 wrapper)
function MapSectionLite() {
return (
Find us
Villa 710, the Light House.
On Jumeirah Beach Road, Umm Suqeim 2 — opposite the beach, minutes from Madinat Jumeirah.
Blo Out Beauty LoungeVilla 710
Address
Villa 710, Jumeirah Beach Road Umm Suqeim 2, Dubai
WhatsApp
{D.brand.whatsapp}candidate — confirm
Map
{D.brand.coords.lat}, {D.brand.coords.lng}
);
}
Object.assign(window, { ServicesPage, MembershipsPage, GalleryPage, TeamPage, ReviewsPage, BookPage });