// Shared UI primitives for Fussy Finds const { useState, useEffect, useRef, useMemo, useCallback } = React; const fmtINR = (n) => '₹' + Number(n).toLocaleString('en-IN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); // Star row (outline + fill) function Stars({ value = 5, size = 14 }) { const full = Math.round(value); return ( {[1, 2, 3, 4, 5].map(i => ( ))} ); } // Placeholder product "image" — striped with a mono label function ProductArt({ product, ratio = '1 / 1', size = 'md' }) { const labelMap = { home: 'home', kitchen: 'kitchen', beauty: 'beauty', tech: 'tech', wellness: 'wellness', desk: 'desk' }; // Use real image if available const imgs = Array.isArray(product.images) ? product.images : []; const mainImg = imgs.find(u => u && u.trim()) || null; if (mainImg) { return (
{product.name} { e.target.style.display = 'none'; e.target.parentNode.style.background = product.swatch || '#FFC7A8'; }} />
); } // Fallback: coloured placeholder with product name return (
{product.name.split(' ').map((w, i) => (
{w}
))}
product · {labelMap[product.cat] || product.cat}
); } // Product card (2 styles toggled via CSS class) function ProductCard({ product, onAdd, onOpen, cardStyle = 'sticker', wishlisted = false, onWishlist }) { // Safe guards — API may return null/string for these fields const tags = Array.isArray(product.tags) ? product.tags : []; const price = parseInt(product.price) || 0; const compare = parseInt(product.compare) || 0; const reviews = parseInt(product.reviews) || 0; const rating = parseFloat(product.rating) || 0; const discount = compare > price ? Math.round((1 - price / compare) * 100) : 0; const outOfStock = product.stock === 0 || product.stock === '0'; const tagEl = outOfStock ? OUT OF STOCK : tags.includes('viral') ? VIRAL ✦ : tags.includes('bestseller') ? BESTSELLER : tags.includes('new') ? NEW : null; const HeartBtn = () => ( ); if (cardStyle === 'minimal') { return (
e.currentTarget.style.transform = 'translateY(-3px)'} onMouseOut={e => e.currentTarget.style.transform = 'translateY(0)'} >
{tagEl &&
{tagEl}
} {discount > 0 && (
−{discount}%
)}

{product.name}

{fmtINR(product.price)}
{rating} {outOfStock ? Out of stock : }
); } // "sticker" default — bold border + shadow + colored footer return (
{ e.currentTarget.style.transform = 'translateY(-3px)'; e.currentTarget.style.boxShadow = 'var(--ff-shadow-lg)'; }} onMouseOut={e => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'var(--ff-shadow)'; }} >
{tagEl &&
{tagEl}
} {discount > 0 && (
−{discount}%
)}

{product.name}

{product.tagline}

{rating} · {reviews.toLocaleString()}
{fmtINR(product.price)} {compare > price && ( {fmtINR(product.compare)} )}
{outOfStock ? Out of stock : }
); } // ─── Theme Switcher — Myntra-style floating palette ───────────────────────── const FF_THEMES = { warm: { name: 'Warm', sub: 'Fraunces · DM Sans', file: 'theme-warm.css', preview: ['#D4007A', '#FAFAF8', '#181510'] }, editorial: { name: 'Editorial', sub: 'Cormorant · Inter', file: 'theme-editorial.css', preview: ['#F9F8F5', '#111110', '#9B9590'] }, bold: { name: 'Bold', sub: 'Space Grotesk · Energetic', file: 'theme-bold.css', preview: ['#FFF8EC', '#E4439C', '#D4FF4D'] }, mono: { name: 'Mono', sub: 'Grayscale · Minimal', file: 'theme-mono.css', preview: ['#FFFFFF', '#000000', '#888888'] }, myntra: { name: 'Myntra', sub: 'Indian Commerce · Bold Pink', file: 'theme-myntra.css', preview: ['#F4F4F4', '#FF3F6C', '#282C3F'] }, }; // Resolve base path (works from both root and /pages/ subdirectory) const _themePath = () => { const p = window.location.pathname; return p.includes('/pages/') ? '../' : './'; }; function ThemeSwitcher() { const [open, setOpen] = React.useState(false); const [activeTheme, setActiveTheme] = React.useState(() => localStorage.getItem('ff_theme') || 'warm'); React.useEffect(() => { injectTheme(activeTheme); }, []); const injectTheme = (key) => { const theme = FF_THEMES[key]; if (!theme) return; // Remove existing theme link if any const existing = document.getElementById('ff-theme-link'); if (existing) existing.remove(); // Inject new theme CSS const link = document.createElement('link'); link.id = 'ff-theme-link'; link.rel = 'stylesheet'; link.href = _themePath() + theme.file; document.head.appendChild(link); localStorage.setItem('ff_theme', key); setActiveTheme(key); }; return (
{open && (
Site theme
{Object.entries(FF_THEMES).map(([key, theme]) => ( ))}
Saved for your visits
)}
); } // Icon helpers const Icon = { Search: (p) => , Bag: (p) => , User: (p) => , Close: (p) => , Menu: (p) => , Arrow: (p) => , Heart: (p) => , Check: (p) => , Box: (p) => , Truck: (p) => , Tag: (p) => , Filter: (p) => , Plus: (p) => , Minus: (p) => , Trash: (p) => , Chart: (p) => , Grid: (p) => , Star: (p) => , }; // Header function Header({ route, goto, cart, openCart, user, onLogin, search, setSearch }) { const count = (cart || []).reduce((s, i) => s + i.qty, 0); const [mopen, setMopen] = useState(false); return (
{/* Announcement bar */}
FREE SHIPPING OVER ₹999 ✦ ALL SALES FINAL ✦ COD AVAILABLE
Fussy Finds
setSearch(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && search) location.href = FF_URLS.search(search); }} placeholder="Search 2,000+ viral finds…" className="ff-input" style={{ paddingLeft: 42, paddingRight: 16, borderRadius: 999 }} />
{user && ( )}
{mopen && (
setSearch(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && search) location.href = FF_URLS.search(search); }} placeholder="Search…" className="ff-input" style={{ paddingLeft: 42, borderRadius: 999 }} />
Shop Viral ✦ New Drops Blog Recipes ✦ Spotlight
)}
); } // Footer function Footer() { const base = (() => { const p = location.pathname; return p.includes('/pages/') ? '../' : './'; })(); const [email, setEmail] = React.useState(''); const [joined, setJoined] = React.useState(false); const [subLoading, setSubLoading] = React.useState(false); const [subError, setSubError] = React.useState(''); const handleJoin = async () => { if (!email || !email.includes('@')) { setSubError('Enter a valid email'); return; } setSubLoading(true); setSubError(''); try { // Get referral code from URL if present const ref = new URLSearchParams(location.search).get('ref') || ''; const res = await fetch('/api/newsletter.php?action=subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, ref }), }); const data = await res.json(); if (data.ok) { setJoined(true); } else { setSubError(data.error || 'Something went wrong'); } } catch (e) { setSubError('Could not subscribe. Try again.'); } finally { setSubLoading(false); } }; const SHOP_LINKS = [ { l: 'All Finds', href: base + '/shop' }, { l: 'Viral ✦', href: base + '/shop?filter=viral' }, { l: 'New Drops', href: base + '/shop?filter=new' }, { l: 'Bestsellers', href: base + '/shop?filter=bestseller' }, { l: 'Blog', href: base + '/blog' }, { l: 'Recipes', href: base + '/recipes' }, { l: 'Under ₹999', href: base + '/shop?sort=price_asc' }, ]; const HELP_LINKS = [ { l: 'Shipping Info', href: base + '/shipping-info' }, { l: 'Track Order', href: base + '/account?tab=orders' }, { l: 'Contact Us', href: 'mailto:hello@fussyfind.com' }, { l: 'FAQs', href: base + '/faq' }, ]; const BRAND_LINKS = [ { l: 'Our Story', href: base + '/about' }, { l: 'Journal', href: '/about' }, { l: 'Creator Program', href: 'mailto:collab@fussyfind.com' }, { l: 'Press', href: 'mailto:press@fussyfind.com' }, ]; const LinkCol = ({ title, links }) => (

{title}

); return ( ); } // Toast system function Toast({ toast, onDismiss }) { useEffect(() => { if (!toast) return; const t = setTimeout(onDismiss, 2800); return () => clearTimeout(t); }, [toast]); if (!toast) return null; return (
{toast}
); } Object.assign(window, { Stars, ProductArt, ProductCard, Icon, Header, Footer, Toast, fmtINR, ThemeSwitcher, FF_THEMES });