/* Symphony — shared components */
// Note: React.useState/React.useEffect/React.useRef are accessed via React.* to avoid scope collisions across babel scripts
// ───────────── Icons (inline SVG, monoline) ─────────────
const Icon = {
Star: (p) => ,
Arrow: (p) => ,
Check: (p) => ,
Phone: (p) => ,
WhatsApp: (p) => ,
Mail: (p) => ,
Pin: (p) => ,
Clock: (p) => ,
Sparkle: (p)=> ,
Shield: (p) => ,
Parking: (p)=> ,
Laser: (p) => ,
};
// ───────────── Fresha rating badge ─────────────
function FreshaBadge({ compact }) {
return (
★★★★★
4.9{compact ? '' : ' · 908 reviews'}
Fresha
);
}
// ───────────── Navbar ─────────────
function Navbar({ route, onNavigate }) {
const links = [
['home', 'Home'],
['treatments', 'Treatments'],
['laser', 'Laser'],
['team', 'Team'],
['reviews', 'Reviews'],
['contact', 'Contact'],
];
return (
);
}
// ───────────── WhatsApp floating ─────────────
function WhatsAppFab() {
return (
WhatsApp us
);
}
// ───────────── Footer ─────────────
function Footer({ onNavigate }) {
return (
);
}
// ───────────── Booking form (consultation) ─────────────
function BookingForm({ compact, presetTreatment }) {
const [form, setForm] = React.useState({
name: "", phone: "", email: "",
treatment: presetTreatment || "", date: "", notes: "",
});
const [errors, setErrors] = React.useState({});
const [submitted, setSubmitted] = React.useState(false);
const validate = () => {
const e = {};
if (!form.name.trim()) e.name = "Please enter your name";
if (!form.phone.match(/^[+0-9 ()-]{7,}$/)) e.phone = "Add a contactable number";
if (form.email && !form.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) e.email = "Check the email format";
if (!form.treatment) e.treatment = "Pick a treatment area";
return e;
};
const submit = (ev) => {
ev.preventDefault();
const e = validate();
setErrors(e);
if (Object.keys(e).length === 0) {
setSubmitted(true);
}
};
if (submitted) {
return (
Thank you, {form.name.split(' ')[0]}
One of our team will reach out within a few hours to confirm your consultation.
Prefer right now? WhatsApp or call us.
);
}
const f = (k, v) => setForm({ ...form, [k]: v });
return (
);
}
// ───────────── Before/After slider ─────────────
function BeforeAfter({ before, after, label = "Botox · forehead", before_w, after_w }) {
const ref = React.useRef(null);
const [pos, setPos] = React.useState(50);
const dragging = React.useRef(false);
React.useEffect(() => {
const move = (e) => {
if (!dragging.current || !ref.current) return;
const r = ref.current.getBoundingClientRect();
const x = (e.touches ? e.touches[0].clientX : e.clientX) - r.left;
setPos(Math.max(0, Math.min(100, (x / r.width) * 100)));
};
const up = () => { dragging.current = false; };
window.addEventListener('mousemove', move);
window.addEventListener('touchmove', move);
window.addEventListener('mouseup', up);
window.addEventListener('touchend', up);
return () => {
window.removeEventListener('mousemove', move);
window.removeEventListener('touchmove', move);
window.removeEventListener('mouseup', up);
window.removeEventListener('touchend', up);
};
}, []);
return (
{ dragging.current = true; }}
onTouchStart={() => { dragging.current = true; }}>
Before
After
);
}
// ───────────── Trust strip ─────────────
function TrustStrip() {
const cells = [
{ icon: , label: "Fresha", value: "4.9 ★ · 908" },
{ icon: , label: "Certified", value: "DHA-Approved" },
{ icon: , label: "Device", value: "Candela GentleMax Pro" },
{ icon: ,label: "Founded", value: "French Aesthetics" },
{ icon: ,label: "On-site", value: "Free Parking" },
];
return (
{cells.map((c, i) => (
))}
);
}
// ───────────── Reviews carousel ─────────────
function ReviewsCarousel({ reviews }) {
return (
{reviews.map((r, i) => (
{'★'.repeat(r.stars)}
"{r.quote}"
{r.name[0]}
{r.name}
{r.src} · {r.date}
))}
);
}
// ───────────── Breadcrumbs ─────────────
function Crumbs({ trail, onNavigate }) {
return (
);
}
Object.assign(window, {
Icon, FreshaBadge, Navbar, WhatsAppFab, Footer,
BookingForm, BeforeAfter, TrustStrip, ReviewsCarousel, Crumbs,
});