// Shared UI primitives — icons, buttons, listing card, sold tile // Brand glyphs as inline SVG — Lucide dropped these from the CDN bundle. const BRAND_SVG = { instagram: 'M12 2.2c3.2 0 3.6 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.21 15.58 2.2 15.2 2.2 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.21 8.8 2.2 12 2.2zm0 6.3a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zm5.4-.4a.85.85 0 1 0 0-1.7.85.85 0 0 0 0 1.7z', facebook: 'M22 12a10 10 0 1 0-11.56 9.88V14.9H7.9V12h2.54V9.8c0-2.5 1.5-3.9 3.78-3.9 1.1 0 2.24.2 2.24.2v2.46h-1.26c-1.25 0-1.64.78-1.64 1.57V12h2.78l-.44 2.9h-2.34v6.98A10 10 0 0 0 22 12z', youtube: 'M21.6 7.2a2.5 2.5 0 0 0-1.76-1.77C18.27 5 12 5 12 5s-6.27 0-7.84.43A2.5 2.5 0 0 0 2.4 7.2 26 26 0 0 0 2 12a26 26 0 0 0 .4 4.8 2.5 2.5 0 0 0 1.76 1.77C5.73 19 12 19 12 19s6.27 0 7.84-.43a2.5 2.5 0 0 0 1.76-1.77A26 26 0 0 0 22 12a26 26 0 0 0-.4-4.8zM10 15V9l5.2 3L10 15z', }; const Icon = ({ name, size = 20, stroke = 2, className = "" }) => { if (BRAND_SVG[name]) { return ( ); } return ; }; window.Icon = Icon; const formatPrice = (n) => { if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 2).replace(/\.?0+$/, '')}M`; return `$${(n / 1000).toFixed(0)}K`; }; const formatFullPrice = (n) => `$${n.toLocaleString('en-CA')}`; window.formatPrice = formatPrice; window.formatFullPrice = formatFullPrice; const ListingCard = ({ listing, onClick, faved, onFav }) => { return (
onClick && onClick(listing)}>
{listing.status === 'sold' ? 'Sold' : 'For Sale'} {listing.images.length} {listing.address}
{formatFullPrice(listing.price)}
{listing.address}
{listing.neighbourhood} · {listing.city}
{listing.beds > 0 && {listing.beds} bed} {listing.baths > 0 && {listing.baths} bath} {listing.sqft > 0 && {listing.sqft.toLocaleString()} sqft}
); }; window.ListingCard = ListingCard; const SoldTile = ({ item, onClick }) => { const overAsk = item.soldPrice - item.askPrice; const overAskPct = ((overAsk / item.askPrice) * 100).toFixed(1); return (
onClick && onClick(item)}> {item.address}
Sold {item.daysOnMarket != null && ( {item.daysOnMarket} days )}
{item.address}
{item.neighbourhood} · {item.city}
{formatFullPrice(item.soldPrice)} {overAsk > 0 && +{overAskPct}% over ask}
); }; window.SoldTile = SoldTile; const Toast = ({ message }) => message ?
{message}
: null; window.Toast = Toast; // Re-init lucide icons after every render const useLucide = () => { React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); }); }; window.useLucide = useLucide;