// Shared UI: Nav, Footer, Reveal, helpers
const { useState, useEffect, useRef, useContext, createContext } = React;
// Router context
const RouterContext = createContext({ route: { name: 'home' }, navigate: () => {} });
const useRouter = () => useContext(RouterContext);
// Format AED
function aed(n) {
return 'AED ' + n.toLocaleString('en-US');
}
// Reveal-on-scroll
function Reveal({ children, delay = 0, as: As = 'div', className = '', ...rest }) {
const ref = useRef(null);
const [seen, setSeen] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } });
},
{ threshold: 0.12, rootMargin: '0px 0px -40px 0px' }
);
io.observe(el);
return () => io.disconnect();
}, []);
return (
{children}
);
}
// Link that uses router navigate
function NavLink({ to, params, children, className = '', onClick }) {
const { navigate } = useRouter();
return (
{
e.preventDefault();
if (onClick) onClick(e);
navigate(to, params);
}}
>
{children}
);
}
// Top bar
function TopNav({ arabicAccent }) {
const { route, navigate } = useRouter();
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 12);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
const links = [
{ to: 'collections', label: 'Collections' },
{ to: 'bespoke', label: 'Bespoke' },
{ to: 'concierge', label: 'Concierge' },
{ to: 'story', label: 'Our Story' },
{ to: 'boutiques', label: 'Boutiques' },
{ to: 'contact', label: 'Contact' },
];
return (
<>
>
);
}
// Footer
function Footer() {
const { navigate } = useRouter();
return (
<>
>
);
}
// Generic split layout — image + text
function Split({ image, alt, kicker, title, body, cta, reverse, tall }) {
return (
<>
{kicker}
{body}
{cta && (
{cta}
)}
>
);
}
// Caravan divider — animated dotted line referencing the caravan name + 4-bead pattern from Sak
function CaravanDivider({ label }) {
return (
<>
>
);
}
// Product card
function ProductCard({ product, onClick }) {
return (
<>
{product.type}
{product.name}
{COLLECTIONS.find(c => c.id === product.collection)?.name}
{aed(product.price)}
>
);
}
// Quiet "scroll" indicator
function ScrollHint({ label = 'Scroll' }) {
return (
<>
{label}
>
);
}
Object.assign(window, {
useRouter, RouterContext, aed,
Reveal, NavLink, TopNav, Footer, Split, CaravanDivider, ProductCard, ScrollHint,
});