{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "testimonials-1",
  "title": "Testimonials 1: scrolling wall of quotes",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A wall of testimonial cards in three columns that scroll at different speeds in opposite directions, pausing on hover, with a plain list on phones.",
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/avatar.json",
    "https://ui.ballmac.com/r/marquee.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/testimonials-1/testimonials-1.tsx",
      "content": "// Ballmac UI: Testimonials 1. https://ui.ballmac.com/blocks/testimonials-1\nimport * as React from \"react\"\n\nimport { Avatar, AvatarFallback } from \"@/components/ballmac/avatar\"\nimport { Marquee } from \"@/components/ballmac/marquee\"\nimport { cn } from \"@/lib/utils\"\n\ntype Testimonial = {\n  quote: string\n  name: string\n  role: string\n}\n\ntype Testimonials1Props = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n  /** Small label above the heading. */\n  eyebrow?: string\n  /** The heading. */\n  title?: string\n  /** One sentence under the heading. */\n  description?: string\n  /** Quotes, spread across up to three scrolling columns. */\n  testimonials?: Testimonial[]\n}\n\nconst defaultTestimonials: Testimonial[] = [\n  { quote: \"We replaced three internal dashboards in a week. The components read like code we would have written, just calmer.\", name: \"Maya Okafor\", role: \"Staff engineer, Acme\" },\n  { quote: \"The dock and window pieces made our web app feel native to our Mac users on day one.\", name: \"Daniel Reyes\", role: \"Design lead, Lumen\" },\n  { quote: \"I asked our agent for a pricing page and it picked the right blocks, wired them up and the build passed.\", name: \"Sara Lindqvist\", role: \"Founder, Halcyon\" },\n  { quote: \"Motion that respects reduced-motion settings out of the box. That alone saved us an audit finding.\", name: \"Arjun Mehta\", role: \"Accessibility lead, Meridian\" },\n  { quote: \"Installs into its own folder, so our shadcn/ui button never got overwritten. Small thing, big trust.\", name: \"Lena Fischer\", role: \"Frontend engineer, Parallax\" },\n  { quote: \"The globe went into our hero in five minutes and follows our brand color in both themes.\", name: \"Tom Becker\", role: \"Growth engineer, Acme\" },\n  { quote: \"Keyboard support everywhere. Our power users noticed before we told them.\", name: \"Hana Sato\", role: \"Product manager, Lumen\" },\n  { quote: \"Honest dependencies, real TypeScript types, no surprise packages. It passes our review checklist.\", name: \"Chris Walker\", role: \"Platform engineer, Halcyon\" },\n  { quote: \"Our landing page finally looks as considered as the product behind it.\", name: \"Nadia Haddad\", role: \"Head of brand, Meridian\" },\n]\n\nconst initials = (name: string) =>\n  name\n    .split(\" \")\n    .map((part) => part[0])\n    .join(\"\")\n    .slice(0, 2)\n\nfunction TestimonialCard({ testimonial }: { testimonial: Testimonial }) {\n  return (\n    <figure data-slot=\"testimonial-card\" className=\"w-full rounded-2xl border bg-card p-5 text-card-foreground shadow-[0_1px_2px_0_rgb(0_0_0/0.04)]\">\n      <blockquote className=\"text-[15px] leading-relaxed text-pretty\">&ldquo;{testimonial.quote}&rdquo;</blockquote>\n      <figcaption className=\"mt-4 flex items-center gap-3\">\n        <Avatar>\n          <AvatarFallback>{initials(testimonial.name)}</AvatarFallback>\n        </Avatar>\n        <div className=\"min-w-0 text-sm\">\n          <p className=\"font-medium\">{testimonial.name}</p>\n          <p className=\"truncate text-muted-foreground\">{testimonial.role}</p>\n        </div>\n      </figcaption>\n    </figure>\n  )\n}\n\nfunction Testimonials1({\n  eyebrow = \"Loved by builders\",\n  title = \"Teams ship calmer interfaces with it.\",\n  description = \"What developers and designers say after putting it in front of their users.\",\n  testimonials = defaultTestimonials,\n  className,\n  ...props\n}: Testimonials1Props) {\n  const columns = [0, 1, 2].map((c) => testimonials.filter((_, i) => i % 3 === c)).filter((col) => col.length > 0)\n  return (\n    <section data-slot=\"testimonials-1\" className={cn(\"mx-auto max-w-6xl px-4 py-20 sm:px-6\", className)} {...props}>\n      <div className=\"mx-auto max-w-2xl text-center\">\n        <p className=\"text-sm font-medium text-primary\">{eyebrow}</p>\n        <h2 className=\"mt-3 text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-5xl\">{title}</h2>\n        <p className=\"mt-4 text-lg text-pretty text-muted-foreground\">{description}</p>\n      </div>\n      {/* Scrolling columns on larger screens; a plain list keeps every quote readable on phones. */}\n      <div className=\"relative mt-14 hidden h-[560px] grid-cols-3 gap-5 md:grid\">\n        {columns.map((col, i) => (\n          <Marquee key={i} vertical reverse={i === 1} speed={22 + i * 4} gap={20} fade className=\"h-full\">\n            {col.map((t) => (\n              <TestimonialCard key={t.name} testimonial={t} />\n            ))}\n          </Marquee>\n        ))}\n      </div>\n      <ul className=\"mt-12 grid gap-4 md:hidden\">\n        {testimonials.slice(0, 4).map((t) => (\n          <li key={t.name}>\n            <TestimonialCard testimonial={t} />\n          </li>\n        ))}\n      </ul>\n    </section>\n  )\n}\n\nexport { Testimonials1, type Testimonials1Props, type Testimonial }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/testimonials-1/testimonials-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "testimonials",
      "social proof",
      "quotes",
      "marquee"
    ],
    "version": "1.0.0",
    "updated": "2026-09-29",
    "ai": {
      "summary": "Social proof section: pass `testimonials` ({ quote, name, role }[]); they are spread across three vertical marquees. Replace the placeholder quotes with real ones.",
      "whenToUse": [
        "Landing pages after features or pricing",
        "Showing many short quotes without a carousel"
      ],
      "whenNotToUse": [
        "One or two quotes (use a single large quote in a section)"
      ],
      "composesWith": [
        "features-1",
        "pricing-1",
        "cta-1"
      ],
      "a11y": [],
      "customization": [
        "eyebrow, title, description, testimonials props",
        "Column speed and direction are set per column in the source"
      ]
    },
    "examples": [
      "testimonials-1-demo"
    ],
    "url": "https://ui.ballmac.com/components/testimonials-1"
  },
  "categories": [
    "blocks",
    "testimonials"
  ],
  "type": "registry:block"
}