{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "testimonial-03",
  "title": "Testimonial 03",
  "description": "A two-column testimonial carousel with prev/next navigation and portrait image cards",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "src/registry/blocks/testimonial-03/testimonial.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ArrowLeft, ArrowRight, Star } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\ninterface Testimonial03Item {\n  name: string;\n  role: string;\n  image: string;\n  content: string;\n  rating: number;\n}\n\ninterface Testimonial03Props {\n  label?: string;\n  title?: string;\n  description?: string;\n  testimonials?: Testimonial03Item[];\n  cta?: {\n    primary?: { text: string; url: string };\n    secondary?: { text: string; url: string };\n  };\n  footnote?: string;\n  className?: string;\n}\n\nconst StarRating = ({\n  rating,\n  white,\n  className,\n}: {\n  rating: number;\n  white?: boolean;\n  className?: string;\n}) => (\n  <div className={cn(\"flex gap-0.5\", className)}>\n    {Array.from({ length: 5 }).map((_, i) => (\n      <Star\n        key={i}\n        className={cn(\n          \"size-3\",\n          white\n            ? i < Math.round(rating)\n              ? \"fill-white text-white\"\n              : \"fill-white/20 text-white/20\"\n            : i < Math.round(rating)\n              ? \"fill-yellow-400 text-yellow-400\"\n              : \"fill-muted-foreground/20 text-muted-foreground/20\",\n        )}\n      />\n    ))}\n  </div>\n);\n\nconst defaultTestimonials: Testimonial03Item[] = [\n  {\n    name: \"Emily Watson\",\n    role: \"Product Manager\",\n    image: \"https://www.shadcnship.com/images/avatars/avatar-1.webp\",\n    content:\n      \"Our team's velocity increased significantly. The components are production-ready and beautifully designed — we went from prototype to live in days, not weeks.\",\n    rating: 5,\n  },\n  {\n    name: \"Alex Chen\",\n    role: \"Frontend Developer\",\n    image: \"https://www.shadcnship.com/images/avatars/avatar-3.webp\",\n    content:\n      \"These components saved me weeks of development time. Copy, paste, customize — it's that simple. The TypeScript support and accessibility built-in are exactly what I need.\",\n    rating: 5,\n  },\n  {\n    name: \"Sarah Johnson\",\n    role: \"UI/UX Designer\",\n    image: \"https://www.shadcnship.com/images/avatars/avatar-2.webp\",\n    content:\n      \"The components are beautifully designed and fully customizable. Perfect for rapid prototyping and production alike. Dark mode works flawlessly out of the box.\",\n    rating: 5,\n  },\n  {\n    name: \"Michael Rodriguez\",\n    role: \"Full-Stack Engineer\",\n    image: \"https://www.shadcnship.com/images/avatars/avatar-4.webp\",\n    content:\n      \"Zero dependencies, fully typed, and production-ready. This is how component libraries should be built. No fighting with CSS frameworks or complex overrides. I dropped it into three different Next.js projects in a week — the API surface stayed consistent and nothing broke. It genuinely saved me days of setup and lets me focus on shipping features instead of maintaining UI primitives.\",\n    rating: 5,\n  },\n];\n\nconst Testimonial03 = ({\n  label = \"Testimonials\",\n  title = \"Loved by thousands of users.\",\n  description = \"See what developers, designers, and product teams are saying about shipping faster with our components.\",\n  testimonials = defaultTestimonials,\n  cta = {\n    primary: { text: \"Get started\", url: \"#\" },\n    secondary: { text: \"Browse blocks\", url: \"#\" },\n  },\n  footnote = \"Build faster and ship better — with production-ready components. Available for React and Next.js.\",\n  className,\n}: Testimonial03Props) => {\n  const [current, setCurrent] = useState(0);\n  const n = testimonials.length;\n  const prevIdx = (current - 1 + n) % n;\n  const nextIdx = (current + 1) % n;\n\n  const active = testimonials[current];\n  const prev = testimonials[prevIdx];\n  const next = testimonials[nextIdx];\n\n  return (\n    <section className={cn(\"container mx-auto px-8 py-12 md:py-24\", className)}>\n      {/* ── Header ── */}\n      <div className=\"flex max-w-2xl flex-col gap-4\">\n        <p className=\"text-sm font-semibold tracking-widest text-muted-foreground uppercase\">\n          {label}\n        </p>\n        <h2 className=\"text-4xl font-medium tracking-tight md:text-5xl\">\n          {title}\n        </h2>\n        <p className=\"text-muted-foreground md:text-lg\">{description}</p>\n      </div>\n\n      {/* ── Carousel ── */}\n      <div className=\"mt-12 flex flex-col gap-6 lg:grid lg:grid-cols-[auto_1fr] lg:items-end\">\n        {/* Left col: ← nav + prev card — desktop only */}\n        <div className=\"hidden lg:flex lg:flex-col lg:justify-between lg:gap-8\">\n          <button\n            onClick={() => setCurrent(prevIdx)}\n            aria-label=\"Previous testimonial\"\n            className=\"flex size-10 items-center justify-center rounded-full bg-foreground text-background transition-opacity hover:opacity-80\"\n          >\n            <ArrowLeft className=\"size-4\" />\n          </button>\n\n          {/* Prev card */}\n          <div className=\"w-52 rounded-2xl border bg-card p-4 shadow-none\">\n            <div className=\"mb-3 size-12 overflow-hidden rounded-full\">\n              <img\n                src={prev.image}\n                alt={prev.name}\n                className=\"size-full object-cover\"\n              />\n            </div>\n            <div className=\"flex items-start justify-between gap-2\">\n              <div className=\"flex flex-col gap-0.5\">\n                <p className=\"text-sm font-medium\">{prev.name}</p>\n                <p className=\"text-xs text-muted-foreground\">{prev.role}</p>\n              </div>\n              <div className=\"flex shrink-0 flex-col items-end gap-0.5\">\n                <p className=\"text-xs text-muted-foreground\">Grade</p>\n                <div className=\"flex items-center gap-1\">\n                  <span className=\"text-sm font-semibold\">{prev.rating}.0</span>\n                  <StarRating rating={prev.rating} />\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        {/* Right col: photo + content + info bar */}\n        <div className=\"flex flex-col gap-4 lg:flex-row lg:items-stretch lg:gap-8\">\n          {/* Center photo */}\n          <img\n            src={active.image}\n            alt={active.name}\n            className=\"w-full rounded-2xl object-cover aspect-video lg:aspect-3/4 lg:w-64 lg:shrink-0\"\n          />\n\n          {/* Right inner col: quote+next card on top, info bar on bottom */}\n          <div className=\"flex flex-1 flex-col gap-4\">\n            {/* Content row: stacked on mobile, side-by-side on desktop */}\n            <div className=\"flex flex-col gap-4 lg:flex-row lg:min-h-0 lg:flex-1 lg:items-start lg:gap-6\">\n              {/* Quote text */}\n              <p className=\"text-sm leading-relaxed text-muted-foreground lg:flex-1\">\n                &ldquo;{active.content}&rdquo;\n              </p>\n\n              {/* Next card */}\n              <div className=\"relative w-full self-start overflow-hidden rounded-2xl lg:w-2/5 lg:shrink-0\">\n                <img\n                  src={next.image}\n                  alt={next.name}\n                  className=\"aspect-video w-full object-cover\"\n                />\n                <div className=\"absolute inset-x-0 bottom-0 bg-linear-to-t from-black/80 via-black/20 to-transparent p-4\">\n                  <div className=\"flex items-end justify-between gap-2\">\n                    <div className=\"flex flex-col gap-0.5\">\n                      <p className=\"text-sm font-medium text-white\">\n                        {next.name}\n                      </p>\n                      <p className=\"text-xs text-white/60\">{next.role}</p>\n                    </div>\n                    <div className=\"flex flex-col items-end gap-0.5\">\n                      <p className=\"text-xs text-white/50\">Rating</p>\n                      <div className=\"flex items-center gap-1\">\n                        <span className=\"text-sm font-semibold text-white\">\n                          {next.rating}.0\n                        </span>\n                        <StarRating rating={next.rating} white />\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            {/* Info bar */}\n            <div className=\"flex w-full items-center gap-4 border-t pt-4 lg:w-2/3\">\n              {/* ← button — mobile only */}\n              <button\n                onClick={() => setCurrent(prevIdx)}\n                aria-label=\"Previous testimonial\"\n                className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-foreground text-background transition-opacity hover:opacity-80 lg:hidden\"\n              >\n                <ArrowLeft className=\"size-4\" />\n              </button>\n\n              <div className=\"flex flex-1 flex-col gap-0.5\">\n                <p className=\"font-medium\">{active.name}</p>\n                <p className=\"text-sm text-muted-foreground\">{active.role}</p>\n              </div>\n              <div className=\"flex items-center gap-3 border-l pl-4\">\n                <div className=\"flex flex-col items-end gap-0.5\">\n                  <p className=\"text-xs text-muted-foreground\">Rating</p>\n                  <div className=\"flex items-center gap-1\">\n                    <span className=\"text-sm font-semibold\">\n                      {active.rating}.0\n                    </span>\n                    <StarRating rating={active.rating} />\n                  </div>\n                </div>\n                <button\n                  onClick={() => setCurrent(nextIdx)}\n                  aria-label=\"Next testimonial\"\n                  className=\"flex size-10 shrink-0 items-center justify-center rounded-full bg-foreground text-background transition-opacity hover:opacity-80\"\n                >\n                  <ArrowRight className=\"size-4\" />\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      {/* ── Footer ── */}\n      <div className=\"mt-12 flex flex-col items-start justify-between gap-6 sm:flex-row sm:items-center\">\n        <div className=\"flex flex-wrap items-center gap-3\">\n          {cta?.primary && (\n            <Button size=\"lg\" className=\"rounded-full\" asChild>\n              <a href={cta.primary.url}>\n                {cta.primary.text}\n                <ArrowRight className=\"ml-1 size-4\" />\n              </a>\n            </Button>\n          )}\n          {cta?.secondary && (\n            <Button\n              size=\"lg\"\n              variant=\"outline\"\n              className=\"rounded-full\"\n              asChild\n            >\n              <a href={cta.secondary.url}>{cta.secondary.text}</a>\n            </Button>\n          )}\n        </div>\n        {footnote && (\n          <p className=\"max-w-xs text-sm text-muted-foreground sm:text-right\">\n            {footnote}\n          </p>\n        )}\n      </div>\n    </section>\n  );\n};\n\nexport default Testimonial03;\nexport type { Testimonial03Props, Testimonial03Item };\n",
      "type": "registry:component",
      "target": "components/testimonial-03.tsx"
    }
  ],
  "meta": {
    "image": "/r/previews/testimonial-03.webp",
    "imageDark": "/r/previews/testimonial-03-dark.webp"
  },
  "categories": [
    "social-proof"
  ],
  "type": "registry:block"
}