);
}
// 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 (