// Furnishfy — shared UI primitives const { useState, useEffect, useRef, useCallback } = React; // ---------- money ---------- function money(n) { return "$" + n.toLocaleString("en-US"); } // ---------- tiny icon set ---------- function Icon({ name, size = 20, stroke = 1.4, style }) { const p = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", style }; switch (name) { case "bag": return (); case "search": return (); case "menu": return (); case "close": return (); case "arrow": return (); case "arrowL": return (); case "plus": return (); case "minus": return (); case "star": return (); case "check": return (); case "truck": return (); case "leaf": return (); case "shield": return (); case "globe": return (); default: return null; } } // ---------- placeholder imagery ---------- function Placeholder({ hue = 40, name, label = "product shot", style, className = "" }) { return (
{name &&
{name}
}
{label}
); } // ---------- stars ---------- function Stars({ n = 5, size = 13 }) { return ( {Array.from({ length: 5 }).map((_, i) => ( ))} ); } // ---------- reveal on scroll ---------- function useReveal() { useEffect(() => { const els = document.querySelectorAll(".reveal:not(.in)"); const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.12 }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }); } // ---------- product card ---------- function ProductCard({ p, go, index = 0 }) { const hasImg = p.thumbnail && !p.thumbnail.includes('no-product'); return (
go("product", p.id)}>
{hasImg ? {p.en} : } {p.badge && ( {p.badge.en} )}
{p.en}
{p.material.en}
{money(p.price)}
{p.old &&
{money(p.old)}
}
); } Object.assign(window, { money, Icon, Placeholder, Stars, useReveal, ProductCard, useState, useEffect, useRef, useCallback });