// src/ui.jsx — shared primitives: Btn, Pill, AddToCart stepper variants, Placeholder, SectionHeader, Rating, FeeRow const { useState, useEffect, useRef, useMemo, useCallback } = React; // Placeholder image tile. Pass `img="keyword,keyword"` to render an Unsplash Source photo with a // graceful fallback to the striped placeholder if it fails to load (or network's blocked). // `seed` locks a specific image for a given product so it doesn't swap on re-render. function Ph({ label, aspect = "1 / 1", radius, style, img, seed, children }) { const [loaded, setLoaded] = React.useState(false); const [errored, setErrored] = React.useState(false); // img can be: undefined | string keyword | { url } | { keyword } let src = null; if (img && !errored) { if (typeof img === "string") { src = `https://loremflickr.com/600/600/${encodeURIComponent(img)}/all?lock=${encodeURIComponent(seed || img)}`; } else if (img.url) { src = img.url; } else if (img.keyword) { src = `https://loremflickr.com/600/600/${encodeURIComponent(img.keyword)}/all?lock=${encodeURIComponent(seed || img.keyword)}`; } } return (
{src && ( {label setLoaded(true)} onError={() => setErrored(true)} /> )} {(!src || !loaded) && {label}} {src && loaded &&
} {children}
); } // Pill — small capsule for metadata (eta, fee, tags) function Pill({ children, tone = "default", icon, style }) { const tones = { default: { bg: "var(--surface-2)", color: "var(--ink-2)", border: "var(--line)" }, navy: { bg: "var(--navy-tint)", color: "var(--navy)", border: "transparent" }, accent: { bg: "var(--accent-tint)", color: "var(--accent)", border: "transparent" }, ghost: { bg: "transparent", color: "var(--ink-3)", border: "var(--line)" }, dark: { bg: "var(--navy-fill)", color: "#fff", border: "transparent" }, ok: { bg: "var(--ok-tint)", color: "var(--ok)", border: "transparent" }, }[tone]; return ( {icon && {icon}} {children} ); } // Btn — primary / secondary / ghost function Btn({ children, variant = "primary", size = "md", full, icon, iconRight, onClick, type = "button", style, disabled, ...rest }) { const sizes = { sm: { h: 32, px: 14, fs: 13 }, md: { h: 44, px: 18, fs: 14 }, lg: { h: 52, px: 22, fs: 15 }, }[size]; const variants = { primary: { bg: "var(--navy-fill)", color: "#fff", border: "transparent" }, secondary: { bg: "var(--surface)", color: "var(--ink)", border: "var(--line)" }, ghost: { bg: "transparent", color: "var(--ink-2)", border: "transparent" }, accent: { bg: "var(--accent)", color: "#fff", border: "transparent" }, inverse: { bg: "#fff", color: "var(--navy)", border: "transparent" }, }[variant]; return ( ); } // SectionHeader — used throughout function SectionHeader({ eyebrow, title, action, children, style }) { return (
{eyebrow &&
{eyebrow}
} {title &&

{title}

} {children}
{action}
); } // Rating — star + number function Rating({ value, reviews, size = 13 }) { return ( {value} {reviews != null && ({reviews.toLocaleString()})} ); } // Add-to-cart — three behaviours exposed via tweaks: // • 'morph' : single Add button morphs into stepper once qty > 0 // • 'stepper': always a stepper (– 0 +) — most Instacart-like // • 'quick' : primary Add, quantity via long-press / right-click menu function AddStepper({ qty, onAdd, onRemove, size = "md", pattern = "morph", compact, tone = "navy" }) { const h = size === "sm" ? 32 : size === "lg" ? 48 : 38; const fs = size === "sm" ? 13 : 14; const toneMap = { navy: { bg: "var(--navy-fill)", color: "#fff" }, outline: { bg: "var(--surface)", color: "var(--ink)" }, }[tone]; if (pattern === "stepper" || qty > 0) { if (qty === 0 && pattern === "stepper") { // Always-stepper: show "Add" that immediately becomes –0+. Simpler: show +. return ( ); } return (
{qty}
); } // morph / quick: single + or Add label return ( ); } // Sticky cart-floater button function CartFloater({ count, subtotal, onClick, visible }) { if (!visible || count === 0) return null; return (
); } // Ask-Opa floater — persistent shortcut to the AI hero from any screen. // Bottom-left so it doesn't conflict with the centered CartFloater or the // bottom-right tweaks FAB. Distinct shape: narrow pill with warm accent ring. function AskOpaFloater({ visible, onClick }) { if (!visible) return null; return (
); } // Toast — tiny, top-center, auto-dismiss. Used for subtle confirmations. function Toast({ message, visible }) { if (!visible) return null; return (
{message}
); } // BodegaMark — tiny "Ω" glyph + wordmark, navy. Doesn't touch real Opa brand beyond using the exclamation. function OpaMark({ inverted, size = 24 }) { return ( Opa ! ); } Object.assign(window, { Ph, Pill, Btn, SectionHeader, Rating, AddStepper, CartFloater, AskOpaFloater, Toast, OpaMark });