{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reviews-ugc-wall",
  "type": "registry:block",
  "title": "UGC wall",
  "description": "Even grid of customer photos and pull quotes with a Shop this chip on hover.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "reviews",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "reviews/reviews-ugc-wall.tsx",
      "content": "\"use client\"\n\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { ArrowUpRight } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\n\n/* UGC wall: an even grid of customer photos and pull quotes. Every tile is\n   square so rows line up at any width (`tall` is accepted but no longer used).\n   Tiles rise in on scroll; hovering or focusing a tile reveals \"Shop this\". */\n\ntype Tile =\n  | { kind: \"image\"; src: string; alt: string; handle: string; href?: string; tall?: boolean }\n  | { kind: \"quote\"; quote: string; handle: string; href?: string }\n\nconst EASE = [0.23, 1, 0.32, 1] as const\nconst RING = \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper\"\n\nconst DEFAULT_TILES: Tile[] = [\n  { kind: \"image\", src: \"/brands/morrow/greens.webp\", alt: \"Greens pouch on a kitchen counter\", handle: \"@slowmornings\", tall: true },\n  { kind: \"quote\", quote: \"The only greens my kids will steal from me. Tastes like pineapple juice.\", handle: \"@jen.cooks\" },\n  { kind: \"image\", src: \"/brands/morrow/magnesium.webp\", alt: \"Gummy jar on a nightstand\", handle: \"@morningstack\" },\n  { kind: \"quote\", quote: \"Electrolytes in my bottle before every run. No more 3pm slump.\", handle: \"@miles.with.mo\" },\n  { kind: \"image\", src: \"/brands/morrow/sparkling.webp\", alt: \"Sparkling cans in the fridge\", handle: \"@fridgegoals\", tall: true },\n  { kind: \"image\", src: \"/brands/morrow/hero.webp\", alt: \"Product lineup\", handle: \"@dailyritual\" },\n]\n\nfunction ShopChip() {\n  return (\n    <span className=\"pointer-events-none absolute bottom-3 right-3 inline-flex translate-y-1 scale-95 items-center gap-1 rounded-sk-chip bg-sk-ink px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.06em] text-sk-paper opacity-0 transition-[opacity,transform] duration-200 ease-sk-out group-hover:translate-y-0 group-hover:scale-100 group-hover:opacity-100 group-focus-visible:translate-y-0 group-focus-visible:scale-100 group-focus-visible:opacity-100 motion-reduce:transition-none [@media(hover:none)]:translate-y-0 [@media(hover:none)]:scale-100 [@media(hover:none)]:opacity-100\">\n      Shop this <ArrowUpRight size={12} aria-hidden />\n    </span>\n  )\n}\n\nexport function ReviewsUgcWall({\n  tiles = DEFAULT_TILES,\n  title = \"In your kitchens\",\n  onShop,\n  className,\n}: {\n  tiles?: Tile[]\n  title?: string\n  onShop?: (index: number) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  return (\n    <section data-sk-section=\"reviews\" data-sk-variant=\"reviews-ugc-wall\" className={cn(\"w-full\", className)} aria-label={title}>\n      <div className=\"mb-6 flex flex-col gap-2\">\n        <span className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">Tagged by customers</span>\n        <h2 className=\"font-sk-display text-2xl font-semibold leading-[1.1] tracking-[-0.02em] text-sk-ink sm:text-3xl\">{title}</h2>\n      </div>\n      <ul className=\"grid grid-cols-2 gap-3 md:grid-cols-3\">\n        {tiles.map((t, i) => {\n          const Tag = t.href ? \"a\" : \"button\"\n          const tagProps = t.href ? { href: t.href } : { type: \"button\" as const, onClick: () => onShop?.(i) }\n          return (\n            <motion.li\n              key={i}\n              className=\"aspect-square\"\n              initial={{ opacity: 0, y: reduce ? 0 : 8 }}\n              whileInView={{ opacity: 1, y: 0 }}\n              viewport={{ once: true, margin: \"-40px\" }}\n              transition={{ duration: reduce ? 0 : 0.45, ease: EASE, delay: reduce ? 0 : i * 0.05 }}\n            >\n              <Tag\n                {...tagProps}\n                aria-label={`Shop this, shared by ${t.handle}`}\n                className={cn(\"group relative block h-full w-full overflow-hidden rounded-sk-card border border-sk-line text-left transition-transform duration-200 active:scale-[0.97]\", RING)}\n              >\n                {t.kind === \"image\" ? (\n                  <span className=\"relative block h-full bg-sk-surface\">\n                    <img\n                      src={t.src}\n                      alt={t.alt}\n                      loading=\"lazy\"\n                      className=\"absolute inset-0 h-full w-full object-cover transition-transform duration-500 ease-sk-out group-hover:scale-[1.03] motion-reduce:transition-none\"\n                    />\n                    <span className=\"absolute left-3 top-3 rounded-sk-chip border border-sk-line bg-sk-card px-2 py-0.5 font-sk-mono text-[11px] text-sk-ink\">{t.handle}</span>\n                  </span>\n                ) : (\n                  <span className=\"flex h-full flex-col justify-between gap-3 bg-sk-surface p-4 sm:p-5\">\n                    <span className=\"line-clamp-6 font-sk-display text-[15px] font-semibold leading-[1.3] tracking-[-0.02em] text-sk-ink sm:text-lg\">&ldquo;{t.quote}&rdquo;</span>\n                    <span className=\"font-sk-mono text-[11px] text-sk-muted\">{t.handle}</span>\n                  </span>\n                )}\n                <ShopChip />\n              </Tag>\n            </motion.li>\n          )\n        })}\n      </ul>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/reviews/reviews-ugc-wall.tsx"
    }
  ],
  "meta": {
    "section": "reviews",
    "variant": "ugc",
    "niche": [
      "any"
    ],
    "commerce": [
      "reviews"
    ],
    "previewHeight": 760
  }
}