/* Fluffy Friends — shared shell: icons, header, footer, WhatsApp, helpers */ const { Button } = window.FluffyFriendsDesignSystem_a09c32; /* ——— Brand constants ——— */ const FLF = { phone: '+971569969546', phoneDisplay: '+971 56 996 9546', tel: '+971569969546', wa: '971569969546', email: 'info@fluffyfriends.ae', ig: 'fluffyfriends.ae', igUrl: 'https://instagram.com/fluffyfriends.ae', site: 'fluffyfriends.ae', googleReview: 'https://g.page/r/CRxDSU_uEt4gEAE/review', L: 'assets/logos/', A: 'assets/imagery/', }; function waLink(message) { const text = encodeURIComponent(message || "Hi Fluffy Friends! I'd love to book a groom for my pet. 🐾"); return `https://wa.me/${FLF.wa}?text=${text}`; } function openWhatsApp(message) { window.open(waLink(message), '_blank', 'noopener'); } /* ——— Lucide icon helper ——— */ function Icon({ name, size = 24, stroke = 'currentColor', strokeWidth = 2.2, style }) { const ref = React.useRef(null); React.useEffect(() => { if (!ref.current || !window.lucide || !lucide.icons) return; const key = name.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(''); const def = lucide.icons[key]; if (!def) return; ref.current.innerHTML = ''; const node = lucide.createElement(def); node.setAttribute('width', size); node.setAttribute('height', size); node.setAttribute('stroke', stroke); node.setAttribute('stroke-width', strokeWidth); ref.current.appendChild(node); }, [name, size, stroke, strokeWidth]); return ; } /* Inline WhatsApp glyph (brand mark, not a Lucide line icon) */ function WhatsAppGlyph({ size = 24 }) { return ( ); } /* Bunny silhouette stamp */ function Bunny({ src = 'flf-bunny-black.png', className, style }) { return ; } /* ——— Scroll reveal ——— */ function useReveal() { React.useEffect(() => { const els = document.querySelectorAll('.reveal:not(.in)'); if (!('IntersectionObserver' in window)) { els.forEach(e => e.classList.add('in')); return; } const io = new IntersectionObserver((entries) => { entries.forEach(en => { if (en.isIntersecting) { en.target.classList.add('in'); io.unobserve(en.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); els.forEach(e => io.observe(e)); return () => io.disconnect(); }); } /* ——— Header ——— */ function Header({ page, onNav, onBook, onStaff }) { const [scrolled, setScrolled] = React.useState(false); const [menuOpen, setMenuOpen] = React.useState(false); React.useEffect(() => { const fn = () => setScrolled(window.scrollY > 8); window.addEventListener('scroll', fn, { passive: true }); return () => window.removeEventListener('scroll', fn); }, []); // Lock body scroll while the mobile menu is open. React.useEffect(() => { document.body.style.overflow = menuOpen ? 'hidden' : ''; return () => { document.body.style.overflow = ''; }; }, [menuOpen]); const links = [['home', 'Home'], ['about', 'About'], ['contact', 'Contact']]; const go = (id) => { setMenuOpen(false); onNav(id); }; return ( go('home')} aria-label="Fluffy Friends home"> {links.map(([id, label]) => ( onNav(id)}>{label} ))} Staff login Book a groom setMenuOpen(o => !o)} aria-label={menuOpen ? 'Close menu' : 'Open menu'} aria-expanded={menuOpen} > {menuOpen && ( {links.map(([id, label]) => ( go(id)}>{label} ))} { setMenuOpen(false); onStaff(); }}> Staff login { setMenuOpen(false); onBook(); }} style={{ marginTop: 8, width: '100%' }}>Book a groom )} ); } /* ——— Footer ——— */ function Footer({ onNav, onBook }) { return ( ); } /* ——— Floating WhatsApp button ——— */ function WhatsAppFab() { return ( openWhatsApp()} aria-label="Book on WhatsApp"> Book on WhatsApp ); } Object.assign(window, { FLF, waLink, openWhatsApp, Icon, WhatsAppGlyph, Bunny, useReveal, Header, Footer, WhatsAppFab });