/* global React, ReactDOM */
const { useState, useEffect, useRef, useCallback } = React;
const RITUELS = window.SITE_DATA.rituels;
const STATS = window.SITE_DATA.stats;
const ACCORDION_ITEMS = window.SITE_DATA.accordion;
const BENEFITS_5T = window.SITE_DATA.benefits5T;
const BIENFAITS = window.SITE_DATA.bienfaits;
const TESTIMONIALS = window.SITE_DATA.testimonials;
/* -----------------------------------------------------------
Hooks
----------------------------------------------------------- */
function useReveal() {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
el.classList.add('in');
io.unobserve(el);
}
});
},
{ threshold: 0.15, rootMargin: '0px 0px -60px 0px' }
);
io.observe(el);
return () => io.disconnect();
}, []);
return ref;
}
function useCounter(target, duration = 2000) {
const [val, setVal] = useState(0);
const ref = useRef(null);
const startedRef = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !startedRef.current) {
startedRef.current = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(Math.round(target * eased));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
},
{ threshold: 0.4 }
);
io.observe(el);
return () => io.disconnect();
}, [target, duration]);
return [val, ref];
}
/* -----------------------------------------------------------
Reusable
----------------------------------------------------------- */
function Reveal({ as: Tag = 'div', className = '', style, children, delay = 0, ...rest }) {
const ref = useReveal();
return (
{children}
);
}
function Placeholder({ tone = 'green', label, className = '', style }) {
const cls = tone === 'gold' ? 'ph-gold' : 'ph-green';
return (
setOpen(false)} className="lg:hidden fixed inset-0 z-[55]" style={{ background: 'rgba(26,26,26,0.4)' }} />
)}
>
);
}
/* -----------------------------------------------------------
Hero
----------------------------------------------------------- */
function Hero() {
return (
{/* LEFT */}
Head Spa Japonais · Frameries, Belgique
Reconnectez-vous
À Votre Beauté
Naturelle
Soins sur-mesure du cuir chevelu, massage crânien et détente profonde.
Une parenthèse exclusivement dédiée aux femmes.
{/* RIGHT */}
{/* Decorative line */}
{/* Scroll indicator */}
Scroll
);
}
/* -----------------------------------------------------------
Rituels — Carrousel
----------------------------------------------------------- */
function Rituels() {
const [index, setIndex] = useState(0);
const [perView, setPerView] = useState(3);
useEffect(() => {
const update = () => {
const w = window.innerWidth;
if (w < 640) setPerView(1);
else if (w < 1024) setPerView(2);
else if (w < 1280) setPerView(3);
else setPerView(3);
};
update();
window.addEventListener('resize', update);
return () => window.removeEventListener('resize', update);
}, []);
const total = RITUELS.length;
const maxIndex = Math.max(0, total - perView);
const safeIndex = Math.min(index, maxIndex);
const prev = useCallback(() => setIndex((i) => Math.max(0, i - 1)), []);
const next = useCallback(() => setIndex((i) => Math.min(maxIndex, i + 1)), [maxIndex]);
useEffect(() => {
const onKey = (e) => {
if (e.key === 'ArrowLeft') prev();
if (e.key === 'ArrowRight') next();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [prev, next]);
// Touch swipe
const touchStart = useRef(0);
const onTouchStart = (e) => { touchStart.current = e.touches[0].clientX; };
const onTouchEnd = (e) => {
const diff = e.changedTouches[0].clientX - touchStart.current;
if (diff > 50) prev();
if (diff < -50) next();
};
const slideWidth = 100 / perView;
const translate = -safeIndex * slideWidth;
const dots = maxIndex + 1;
return (
Nos offres
Nos Rituels
À s'offrir ou à offrir.
{/* Arrow — prev */}
{/* Viewport */}
{RITUELS.map((r, i) => (
))}
{/* Arrow — next */}
= maxIndex}
aria-label="Rituel suivant"
>
{/* Dots */}
{Array.from({ length: dots }).map((_, i) => (
setIndex(i)}
className={`carousel-dot ${i === safeIndex ? 'active' : ''}`}
aria-label={`Aller au groupe ${i + 1}`}
aria-selected={i === safeIndex}
/>
))}
{/* Counter */}
{String(safeIndex + 1).padStart(2, '0')}
/
{String(dots).padStart(2, '0')}
Voir tous les rituels en détail →
);
}
function RitualCard({ ritual, delay }) {
const phTone = ritual.tone === 'gold' ? 'ph-gold' : 'ph-green';
return (
{ e.target.style.display = 'none'; }}
/>
{ritual.name}
{ritual.badge && (
{ritual.badge}
)}
{ritual.name}
{ritual.details}
{ritual.reveal}
);
}
/* -----------------------------------------------------------
Stats
----------------------------------------------------------- */
function Stat({ value, suffix, label }) {
const [val, ref] = useCounter(value, 2000);
return (
);
}
function Stats() {
return (
);
}
/* -----------------------------------------------------------
Experience
----------------------------------------------------------- */
function Experience() {
return (
{/* Decorative vertical line */}
L'Expérience BIO HEAD SPA
Un Sanctuaire De Calme
Pour Chaque Soin
Que Vous Recevez
Nous combinons des techniques traditionnelles japonaises avec des produits
100% naturels et bio, sans huiles essentielles. Chaque rituel est un protocole
complet et sur-mesure : diagnostic du cuir chevelu, gommage, massage crânien,
soin capillaire, vapeur douce… pour relâcher le corps, apaiser l'esprit
et révéler la beauté naturelle de vos cheveux.
Voir le rituel
Sur rendez-vous
Mar–Sam · 10h30–18h
);
}
/* -----------------------------------------------------------
5 Temps
----------------------------------------------------------- */
function CinqTemps() {
const [open, setOpen] = useState(0);
return (
100% Naturel
« Chaque geste, un souffle. »
Le protocole
Votre Rituel En 5 Temps
Laissez votre parcours bien-être commencer par un diagnostic personnalisé
et un massage régénérant.
Réserver
{ACCORDION_ITEMS.map((item, i) => {
const isOpen = open === i;
return (
setOpen(isOpen ? -1 : i)} aria-expanded={isOpen}>
{item.title}
+
);
})}
{BENEFITS_5T.map((b) => (
))}
);
}
/* -----------------------------------------------------------
Bienfaits
----------------------------------------------------------- */
function Bienfaits() {
return (
Soins authentiques, gestes précis.
Bienfaits
Apaisez Vos Sens Avec Un Soin Thérapeutique
Chaque protocole est pensé pour rééquilibrer le corps et l'esprit.
Une parenthèse pour soi, sans concession sur la qualité des soins.
{BIENFAITS.map((b, i) => (
{b}
))}
);
}
/* -----------------------------------------------------------
Testimonials
----------------------------------------------------------- */
function Testimonials() {
return (
Témoignages
Ce Qu'elles En Disent
{TESTIMONIALS.map((t, i) => (
"
{t.text}
— {t.author}
{'★'.repeat(t.stars)}
))}
Laisser un avis sur Google →
);
}
/* -----------------------------------------------------------
Contact
----------------------------------------------------------- */
function Contact() {
return (
);
}
function InfoLine({ icon, children }) {
const icons = {
pin: (
),
clock: (
),
phone: (
),
};
return (
{icons[icon]}
{children}
);
}
/* -----------------------------------------------------------
Footer
----------------------------------------------------------- */
function Footer() {
return (
);
}
function FooterCol({ title, children }) {
return (
);
}
function SocialIcon({ href, label, children }) {
return (
{ e.currentTarget.style.color = 'var(--gold)'; e.currentTarget.style.borderColor = 'var(--gold)'; }}
onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--footer-text)'; e.currentTarget.style.borderColor = 'rgba(168,159,148,0.35)'; }}
>
{children}
);
}
/* -----------------------------------------------------------
App
----------------------------------------------------------- */
function App() {
return (
<>
>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(
);