{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hero-bestseller-shelf",
  "type": "registry:block",
  "title": "Best-seller shelf hero",
  "description": "Store rating headline over the top products with rank badges, stars, price and quick add; scrolls sideways with a peek on mobile.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "hero",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json",
    "https://storekit-eight.vercel.app/r/price.json",
    "https://storekit-eight.vercel.app/r/stars.json",
    "https://storekit-eight.vercel.app/r/badge.json"
  ],
  "files": [
    {
      "path": "hero/hero-bestseller-shelf.tsx",
      "content": "\"use client\"\n\nimport { useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Check, Plus } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\nimport { Price } from \"@/components/storekit/ui/price\"\nimport { Stars } from \"@/components/storekit/ui/stars\"\nimport { Badge } from \"@/components/storekit/ui/badge\"\n\n/* Best-seller shelf as the hero. A short headline with store-wide rating,\n   then the top products above the fold with image, rating, price and a\n   quick add that confirms in place. Four cards on desktop; on mobile the\n   shelf scrolls sideways with the next card peeking so it reads as more.\n   Rank badges call out the top sellers. */\n\nconst EASE = [0.23, 1, 0.32, 1] as const\n\nexport type ShelfProduct = {\n  id: string\n  title: string\n  href: string\n  image: string\n  price: number\n  compareAt?: number\n  rating?: number\n  reviews?: number\n  blurb?: string\n}\n\nconst SAMPLE: ShelfProduct[] = [\n  { id: \"1\", title: \"Daily Greens\", href: \"#\", image: \"/placeholder.webp\", price: 4900, compareAt: 5900, rating: 4.8, reviews: 2140 },\n  { id: \"2\", title: \"Electrolytes\", href: \"#\", image: \"/placeholder.webp\", price: 3200, rating: 4.7, reviews: 980 },\n  { id: \"3\", title: \"Magnesium Night\", href: \"#\", image: \"/placeholder.webp\", price: 2900, rating: 4.9, reviews: 1630 },\n  { id: \"4\", title: \"Protein\", href: \"#\", image: \"/placeholder.webp\", price: 5400, rating: 4.6, reviews: 720 },\n]\n\nexport function HeroBestsellerShelf({\n  eyebrow = \"Best sellers\",\n  headline = \"What everyone is restocking.\",\n  rating = { value: 4.8, count: 12400 },\n  link = { label: \"Shop all best sellers\", href: \"#\" },\n  products = SAMPLE,\n  ranked = true,\n  onAdd,\n  className,\n}: {\n  eyebrow?: string\n  headline?: string\n  rating?: { value: number; count: number } | null\n  link?: { label: string; href: string } | null\n  products?: ShelfProduct[]\n  /** Show \"#1, #2...\" badges. */\n  ranked?: boolean\n  onAdd?: (id: string) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"hero\", \"hero-bestseller-shelf\")\n  const [added, setAdded] = useState<Record<string, boolean>>({})\n\n  const add = (p: ShelfProduct, i: number) => {\n    onAdd?.(p.id)\n    setAdded((a) => ({ ...a, [p.id]: true }))\n    sk.emit(\"convert\", \"add_to_cart\", i)\n  }\n\n  return (\n    <section ref={sk.ref} {...sk.attrs} className={cn(\"bg-sk-paper text-sk-ink\", className)}>\n      <div className=\"mx-auto w-full max-w-6xl py-10 sm:py-14\">\n        <div className=\"flex flex-col gap-3 px-4 sm:flex-row sm:items-end sm:justify-between sm:px-8\">\n          <div>\n            <p className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{eyebrow}</p>\n            <h1 className=\"mt-3 text-balance font-sk-display font-semibold leading-[1.08] tracking-[-0.02em]\" style={{ fontSize: \"clamp(1.85rem, 3.6vw, 2.75rem)\" }}>\n              {headline}\n            </h1>\n            {rating ? (\n              <p className=\"mt-3 flex items-center gap-2 text-sm text-sk-muted\">\n                <Stars value={rating.value} size={14} />\n                <span>\n                  <span className=\"font-medium text-sk-ink tabular-nums\">{rating.value.toFixed(1)}</span> from {rating.count.toLocaleString(\"en-US\")} reviews\n                </span>\n              </p>\n            ) : null}\n          </div>\n          {link ? (\n            <a\n              href={link.href}\n              onClick={() => sk.emit(\"interact\", \"shop_all\")}\n              className=\"inline-flex min-h-11 shrink-0 items-center self-start text-sm font-medium underline decoration-sk-line underline-offset-4 outline-none transition-[text-decoration-color] hover:decoration-sk-ink focus-visible:ring-2 focus-visible:ring-sk-accent sm:self-auto\"\n            >\n              {link.label}\n            </a>\n          ) : null}\n        </div>\n\n        <ul className=\"mt-6 flex snap-x snap-mandatory gap-3 overflow-x-auto scroll-px-4 px-4 pb-2 [scrollbar-width:none] sm:scroll-px-8 sm:px-8 lg:grid lg:grid-cols-4 lg:gap-4 lg:overflow-visible\">\n          {products.map((p, i) => {\n            const done = added[p.id]\n            return (\n              <motion.li\n                key={p.id}\n                initial={{ opacity: 0, y: reduce ? 0 : 12 }}\n                animate={{ opacity: 1, y: 0 }}\n                transition={{ duration: 0.5, ease: EASE, delay: reduce ? 0 : i * 0.06 }}\n                className=\"w-[68%] shrink-0 snap-start sm:w-[40%] lg:w-auto\"\n              >\n                <article className=\"group flex h-full flex-col overflow-hidden rounded-sk-card border border-sk-line bg-sk-card\">\n                  <a href={p.href} onClick={() => sk.emit(\"interact\", \"product\", i)} className=\"relative block aspect-square overflow-hidden bg-sk-surface outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sk-accent\">\n                    <img src={p.image} alt={p.title} className=\"absolute inset-0 h-full w-full object-cover transition-transform duration-700 ease-sk-out group-hover:scale-[1.04]\" />\n                    {ranked ? (\n                      <span className=\"absolute left-2.5 top-2.5\">\n                        <Badge tone={i === 0 ? \"accent\" : \"neutral\"}>#{i + 1}</Badge>\n                      </span>\n                    ) : null}\n                  </a>\n                  <div className=\"flex flex-1 flex-col p-3 sm:p-4\">\n                    <h2 className=\"text-[15px] font-semibold leading-tight\">\n                      <a href={p.href} className=\"outline-none hover:underline focus-visible:underline\">\n                        {p.title}\n                      </a>\n                    </h2>\n                    {typeof p.rating === \"number\" ? <Stars value={p.rating} count={p.reviews} size={12} className=\"mt-1.5 [&>span]:text-[12px]\" /> : null}\n                    <div className=\"mt-auto flex items-center justify-between gap-2 pt-3\">\n                      <Price amount={p.price} compareAt={p.compareAt} size=\"sm\" />\n                      <button\n                        type=\"button\"\n                        onClick={() => add(p, i)}\n                        aria-label={done ? `${p.title} added to cart` : `Add ${p.title} to cart`}\n                        className={cn(\n                          \"sk-btn relative grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-full outline-none transition-[background-color,color,transform] duration-200 ease-sk-out focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-card active:scale-[0.92]\",\n                          done ? \"bg-sk-savings text-sk-paper\" : \"bg-sk-accent text-sk-accent-ink hover:brightness-[1.06]\"\n                        )}\n                      >\n                        <AnimatePresence initial={false} mode=\"popLayout\">\n                          <motion.span\n                            key={done ? \"done\" : \"add\"}\n                            initial={{ y: reduce ? 0 : 14, opacity: 0 }}\n                            animate={{ y: 0, opacity: 1 }}\n                            exit={{ y: reduce ? 0 : -14, opacity: 0 }}\n                            transition={{ type: \"spring\", stiffness: 520, damping: 32 }}\n                            className=\"grid place-items-center\"\n                          >\n                            {done ? <Check size={16} aria-hidden /> : <Plus size={16} aria-hidden />}\n                          </motion.span>\n                        </AnimatePresence>\n                      </button>\n                    </div>\n                  </div>\n                </article>\n              </motion.li>\n            )\n          })}\n        </ul>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/hero/hero-bestseller-shelf.tsx"
    }
  ],
  "docs": "Wire `onAdd` to your cart; each add fires a convert event.",
  "meta": {
    "section": "hero",
    "variant": "bestseller-shelf",
    "niche": [
      "any"
    ],
    "commerce": [
      "atc"
    ],
    "previewHeight": 600
  }
}