{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "testimonials-01",
  "title": "Customer Testimonials",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/testimonials-01/testimonials-01.component.ts",
      "content": "// Interactive customer case study and ROI telemetry workbench with industry filters,\n// verified customer metrics, tech stack tags, and autoplay navigation. Port of the Vue/React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, computed, signal } from '@angular/core'\nimport {\n  BadgeCheck,\n  CheckCircle2,\n  ChevronLeft,\n  ChevronRight,\n  LucideAngularModule,\n  Pause,\n  Play,\n  Quote,\n  Star,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent } from '@/ui/card/card.component'\n\nexport type TestimonialIndustry = 'saas' | 'healthtech' | 'fintech' | 'devtools'\nexport type IndustryFilter = 'all' | TestimonialIndustry\n\nexport interface CaseStudyMetric {\n  label: string\n  value: string\n  trend: string\n}\n\nexport interface CaseStudyTestimonial {\n  id: string\n  name: string\n  role: string\n  company: string\n  industry: TestimonialIndustry\n  initials: string\n  avatarBg: string\n  quote: string\n  subquote: string\n  rating: number\n  verifiedBadge: string\n  metrics: CaseStudyMetric[]\n  stack: string[]\n}\n\nexport const caseStudyTestimonials: CaseStudyTestimonial[] = [\n  {\n    id: '1',\n    name: 'Aisha Rahman',\n    role: 'VP of People & Operations',\n    company: 'Northwind Global Logistics',\n    industry: 'saas',\n    initials: 'AR',\n    avatarBg: 'bg-info/10 text-info border-info/20',\n    quote:\n      'We replaced four legacy spreadsheets and two disjointed SaaS subscriptions with UIPKGE blocks. Onboarding time dropped from 6 days to under 4 hours.',\n    subquote:\n      'The unbundled registry architecture gave our engineers total ownership without ever dealing with broken npm updates or rigid vendor locks.',\n    rating: 5,\n    verifiedBadge: 'Verified Enterprise Customer · 2,400+ Seats',\n    metrics: [\n      { label: 'Onboarding Velocity', value: '< 4 hours', trend: '-88% time' },\n      { label: 'SaaS Tooling Spend', value: '$140k / yr', trend: 'Saved' },\n      { label: 'Employee NPS', value: '+74', trend: 'Top Decile' },\n    ],\n    stack: ['Vue 3.5', 'Tailwind CSS v4', 'PostgreSQL', 'SCIM Okta'],\n  },\n  {\n    id: '2',\n    name: 'Marco Vidal',\n    role: 'Director of Information Security',\n    company: 'Helio Health Systems',\n    industry: 'healthtech',\n    initials: 'MV',\n    avatarBg: 'bg-success/10 text-success border-success/20',\n    quote:\n      'SOC 2 and HIPAA evidence collection went from a grueling quarterly nightmare to a continuous, automated background audit trail.',\n    subquote:\n      'Our external auditors completed our Type II examination in record time because every UI state change is cryptographically verifiable.',\n    rating: 5,\n    verifiedBadge: 'Verified Healthcare Provider · HIPAA Tier 1',\n    metrics: [\n      { label: 'Audit Prep Duration', value: '1.5 days', trend: 'Down from 3 wks' },\n      { label: 'Compliance Adherence', value: '100.0%', trend: '142 Controls' },\n      { label: 'Zero Trust Rollout', value: '14 Days', trend: '100% Org' },\n    ],\n    stack: ['React 19', 'Reka UI Primitives', 'Cloudflare Workers', 'AuditLog API'],\n  },\n  {\n    id: '3',\n    name: 'Tomoko Saito',\n    role: 'Staff Infrastructure Architect',\n    company: 'Pixel & Co Engine Labs',\n    industry: 'devtools',\n    initials: 'TS',\n    avatarBg: 'bg-chart-1/10 text-chart-1 border-chart-1/20',\n    quote:\n      'My favourite part is how blazing fast it is. Sub-20ms P99 search latencies across 50,000 workforce records without a single loading spinner.',\n    subquote:\n      'Keyboard shortcuts for every workflow make our engineering managers feel like they are operating a sleek CLI rather than a web dashboard.',\n    rating: 5,\n    verifiedBadge: 'Verified DevTools Customer · 450+ Devs',\n    metrics: [\n      { label: 'P99 Edge Latency', value: '18ms', trend: 'Global Edge' },\n      { label: 'Daily Hotkey Actions', value: '42k / day', trend: '+310%' },\n      { label: 'Memory Footprint', value: '< 12MB', trend: 'Zero bloat' },\n    ],\n    stack: ['Vue 3.5', 'Vite', 'Turborepo', 'WebAssembly'],\n  },\n  {\n    id: '4',\n    name: 'Julian Montgomery',\n    role: 'Chief Financial Officer',\n    company: 'Vanguard FinTech Matrix',\n    industry: 'fintech',\n    initials: 'JM',\n    avatarBg: 'bg-warning/10 text-warning border-warning/20',\n    quote:\n      'Multi-currency payroll runs across 34 countries used to require 5 days of manual reconciliation. Now it settles automatically with zero FX fee slippage.',\n    subquote:\n      'Direct ledger sync and real-time tax calculations give our board instant visibility into gross-to-net runway.',\n    rating: 5,\n    verifiedBadge: 'Verified FinTech Customer · $800M+ Volume',\n    metrics: [\n      { label: 'Payroll Settlement', value: 'Instant', trend: 'FedNow / SEPA' },\n      { label: 'FX Reconciliation', value: '0.00%', trend: 'Zero error' },\n      { label: 'Tax Auto-filing', value: '34 Regs', trend: 'Statutory' },\n    ],\n    stack: ['React 19', 'Next.js', 'Tailwind v4', 'Stripe Treasury'],\n  },\n]\n\nconst industryOptions: { id: IndustryFilter; label: string }[] = [\n  { id: 'all', label: 'All Industries' },\n  { id: 'saas', label: 'Enterprise SaaS' },\n  { id: 'healthtech', label: 'HealthTech' },\n  { id: 'devtools', label: 'Developer DX' },\n  { id: 'fintech', label: 'FinTech' },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-testimonials-01, [ui-testimonials-01]',\n  standalone: true,\n  // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent],\n  template: `\n    <section data-slot=\"testimonials-01\" [class]=\"rootClass\">\n      <div class=\"mx-auto max-w-7xl space-y-12 px-4 sm:px-6 lg:px-8\">\n        <!-- Section Header -->\n        <div class=\"flex flex-col gap-6 md:flex-row md:items-end md:justify-between\">\n          <div class=\"max-w-2xl space-y-3\">\n            <div class=\"inline-flex items-center gap-2\">\n              <span\n                ui-badge\n                variant=\"outline\"\n                class=\"border-primary/30 text-primary bg-primary/5 gap-1.5 px-2.5 py-1 font-mono text-xs tracking-wide uppercase\"\n              >\n                <lucide-icon [img]=\"BadgeCheck\" class=\"size-3.5\" />\n                Verified Case Studies\n              </span>\n              <span class=\"text-muted-foreground font-mono text-xs\">Real-World Customer Telemetry</span>\n            </div>\n            <h2 class=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl\">\n              Trusted by the Teams Building the Future.\n            </h2>\n            <p class=\"text-muted-foreground text-base leading-relaxed sm:text-lg\">\n              See how high-growth scaleups and enterprise leaders accelerate engineering velocity and operational\n              clarity.\n            </p>\n          </div>\n\n          <!-- Industry Filter Switcher -->\n          <div class=\"bg-muted/60 border-border flex flex-wrap items-center gap-1.5 rounded-lg border p-1\">\n            @for (cat of industryOptions; track cat.id) {\n              <button\n                type=\"button\"\n                class=\"rounded-md px-3 py-1.5 text-xs font-medium transition-colors\"\n                [class]=\"filterButtonClass(cat.id)\"\n                (click)=\"setIndustry(cat.id)\"\n              >\n                {{ cat.label }}\n              </button>\n            }\n          </div>\n        </div>\n\n        <!-- Featured Testimonial Canvas -->\n        <div ui-card class=\"bg-card border-border overflow-hidden shadow-md\">\n          <div class=\"grid grid-cols-1 lg:grid-cols-12\">\n            <!-- Left Main Quote Area (7 cols) -->\n            <div class=\"flex flex-col justify-between space-y-8 p-6 sm:p-10 lg:col-span-7\">\n              <div class=\"space-y-6\">\n                <!-- Rating & Badge -->\n                <div class=\"flex flex-wrap items-center justify-between gap-3\">\n                  <div class=\"text-warning flex items-center gap-1\">\n                    @for (s of stars(); track $index) {\n                      <lucide-icon [img]=\"Star\" class=\"fill-warning size-4\" />\n                    }\n                  </div>\n                  <span\n                    ui-badge\n                    variant=\"outline\"\n                    class=\"border-success/20 bg-success/10 text-success gap-1.5 font-mono text-xs\"\n                  >\n                    <lucide-icon [img]=\"CheckCircle2\" class=\"size-3\" />\n                    {{ activeTestimonial().verifiedBadge }}\n                  </span>\n                </div>\n\n                <!-- Quote Content -->\n                <div class=\"relative space-y-3\">\n                  <lucide-icon [img]=\"Quote\" class=\"text-primary/20 absolute -top-4 -left-3 -z-10 size-8\" />\n                  <p class=\"text-foreground text-xl leading-relaxed font-medium tracking-tight sm:text-2xl\">\n                    &ldquo;{{ activeTestimonial().quote }}&rdquo;\n                  </p>\n                  <p class=\"text-muted-foreground text-sm leading-relaxed\">\n                    {{ activeTestimonial().subquote }}\n                  </p>\n                </div>\n              </div>\n\n              <!-- Author Info & Controls -->\n              <div class=\"border-border flex flex-col justify-between gap-4 border-t pt-6 sm:flex-row sm:items-center\">\n                <div class=\"flex items-center gap-3.5\">\n                  <div\n                    class=\"flex size-11 items-center justify-center rounded-full border text-sm font-bold\"\n                    [class]=\"activeTestimonial().avatarBg\"\n                  >\n                    {{ activeTestimonial().initials }}\n                  </div>\n                  <div>\n                    <h4 class=\"text-foreground text-sm font-semibold\">{{ activeTestimonial().name }}</h4>\n                    <p class=\"text-muted-foreground text-xs\">\n                      {{ activeTestimonial().role }} ·\n                      <span class=\"text-foreground font-medium\">{{ activeTestimonial().company }}</span>\n                    </p>\n                  </div>\n                </div>\n\n                <!-- Navigation Buttons -->\n                <div class=\"flex items-center gap-2\">\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    class=\"size-8 rounded-lg\"\n                    aria-label=\"Previous customer story\"\n                    (click)=\"prev()\"\n                  >\n                    <lucide-icon [img]=\"ChevronLeft\" class=\"size-4\" />\n                  </button>\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    class=\"size-8 rounded-lg\"\n                    [attr.aria-label]=\"isAutoPlaying() ? 'Pause rotation' : 'Resume rotation'\"\n                    (click)=\"toggleAutoPlay()\"\n                  >\n                    @if (isAutoPlaying()) {\n                      <lucide-icon [img]=\"Pause\" class=\"text-primary size-3.5\" />\n                    } @else {\n                      <lucide-icon [img]=\"Play\" class=\"size-3.5\" />\n                    }\n                  </button>\n                  <button\n                    ui-button\n                    variant=\"outline\"\n                    size=\"icon\"\n                    class=\"size-8 rounded-lg\"\n                    aria-label=\"Next customer story\"\n                    (click)=\"next()\"\n                  >\n                    <lucide-icon [img]=\"ChevronRight\" class=\"size-4\" />\n                  </button>\n                </div>\n              </div>\n            </div>\n\n            <!-- Right Telemetry & Metrics Sidebar (5 cols) -->\n            <div\n              class=\"bg-muted/20 border-border flex flex-col justify-between space-y-6 border-t p-6 sm:p-8 lg:col-span-5 lg:border-t-0 lg:border-l\"\n            >\n              <div class=\"space-y-4\">\n                <div class=\"flex items-center justify-between\">\n                  <span class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">Impact Telemetry</span>\n                  <span class=\"text-success font-mono text-xs\">Production Validated</span>\n                </div>\n\n                <!-- Metrics Grid -->\n                <div class=\"space-y-3\">\n                  @for (m of activeTestimonial().metrics; track m.label) {\n                    <div\n                      class=\"border-border bg-card/80 flex items-center justify-between rounded-lg border p-3.5 shadow-xs\"\n                    >\n                      <div>\n                        <p class=\"text-muted-foreground text-xs\">{{ m.label }}</p>\n                        <p class=\"text-foreground mt-0.5 text-lg font-bold tracking-tight\">{{ m.value }}</p>\n                      </div>\n                      <span\n                        ui-badge\n                        variant=\"secondary\"\n                        class=\"bg-success/10 text-success font-mono text-xs font-semibold\"\n                      >\n                        {{ m.trend }}\n                      </span>\n                    </div>\n                  }\n                </div>\n\n                <!-- Stack Tags -->\n                <div class=\"space-y-2 pt-2\">\n                  <p class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">\n                    Tech Stack Architecture\n                  </p>\n                  <div class=\"flex flex-wrap gap-1.5\">\n                    @for (tech of activeTestimonial().stack; track $index) {\n                      <span ui-badge variant=\"outline\" class=\"bg-background font-mono text-xs\">\n                        {{ tech }}\n                      </span>\n                    }\n                  </div>\n                </div>\n              </div>\n\n              <!-- Story Navigation Dots -->\n              <div class=\"border-border/60 flex items-center justify-between border-t pt-4\">\n                <div class=\"flex items-center gap-1.5\">\n                  @for (t of filteredTestimonials(); track t.id; let i = $index) {\n                    <button\n                      type=\"button\"\n                      [attr.aria-label]=\"'Jump to testimonial ' + (i + 1)\"\n                      class=\"h-1.5 rounded-full transition-colors duration-300\"\n                      [class]=\"dotClass(i)\"\n                      (click)=\"goTo(i)\"\n                    ></button>\n                  }\n                </div>\n                <span class=\"text-muted-foreground font-mono text-xs\">\n                  {{ activeIndex() + 1 }} of {{ filteredTestimonials().length }} Stories\n                </span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </section>\n  `,\n})\nexport class UiTestimonials01Component implements OnInit, OnDestroy {\n  protected readonly BadgeCheck = BadgeCheck\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly ChevronLeft = ChevronLeft\n  protected readonly ChevronRight = ChevronRight\n  protected readonly Pause = Pause\n  protected readonly Play = Play\n  protected readonly Quote = Quote\n  protected readonly Star = Star\n\n  protected readonly industryOptions = industryOptions\n\n  @Input() set initialIndustry(v: IndustryFilter) {\n    this.activeIndustry.set(v ?? 'all')\n  }\n  @Input() set autoPlay(v: boolean) {\n    this.isAutoPlaying.set(v ?? true)\n  }\n  @Input('class') className?: string\n\n  readonly activeIndex = signal(0)\n  readonly activeIndustry = signal<IndustryFilter>('all')\n  readonly isAutoPlaying = signal(true)\n  private timer: ReturnType<typeof setInterval> | undefined\n\n  readonly filteredTestimonials = computed(() => {\n    const industry = this.activeIndustry()\n    if (industry === 'all') return caseStudyTestimonials\n    return caseStudyTestimonials.filter((t) => t.industry === industry)\n  })\n\n  readonly activeTestimonial = computed(() => {\n    const list = this.filteredTestimonials()\n    return list[this.activeIndex()] ?? list[0]!\n  })\n\n  readonly stars = computed(() => Array.from({ length: this.activeTestimonial().rating }, (_, i) => i))\n\n  ngOnInit(): void {\n    this.startTimer()\n  }\n\n  ngOnDestroy(): void {\n    if (this.timer) clearInterval(this.timer)\n  }\n\n  get rootClass(): string {\n    return cn('bg-background border-border relative w-full overflow-hidden border-y py-16 lg:py-24', this.className)\n  }\n\n  filterButtonClass(id: IndustryFilter): string {\n    return cn(\n      'rounded-md px-3 py-1.5 text-xs font-medium transition-colors',\n      this.activeIndustry() === id\n        ? 'bg-background text-foreground font-semibold shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  dotClass(i: number): string {\n    return cn(\n      'h-1.5 rounded-full transition-colors duration-300',\n      i === this.activeIndex() ? 'bg-primary w-6' : 'bg-muted-foreground/30 hover:bg-muted-foreground/60 w-2',\n    )\n  }\n\n  setIndustry(id: IndustryFilter): void {\n    this.activeIndustry.set(id)\n    this.activeIndex.set(0)\n  }\n\n  goTo(i: number): void {\n    this.activeIndex.set(i)\n  }\n\n  next(): void {\n    const len = this.filteredTestimonials().length\n    this.activeIndex.update((prev) => (prev + 1) % len)\n  }\n\n  prev(): void {\n    const len = this.filteredTestimonials().length\n    this.activeIndex.update((prev) => (prev - 1 + len) % len)\n  }\n\n  toggleAutoPlay(): void {\n    const nextVal = !this.isAutoPlaying()\n    this.isAutoPlaying.set(nextVal)\n    if (nextVal) this.startTimer()\n    else if (this.timer) clearInterval(this.timer)\n  }\n\n  private startTimer(): void {\n    if (this.timer) clearInterval(this.timer)\n    if (!this.isAutoPlaying()) return\n    this.timer = setInterval(() => this.next(), 6500)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/testimonials-01/testimonials-01.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/testimonials-01/index.ts",
      "content": "export {\n  UiTestimonials01Component,\n  type CaseStudyMetric,\n  type CaseStudyTestimonial,\n  type IndustryFilter,\n  type TestimonialIndustry,\n  caseStudyTestimonials,\n} from './testimonials-01.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/testimonials-01/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json"
  ],
  "description": "Interactive customer case study and ROI telemetry workbench with industry filters, verified customer metrics, tech stack tags, and autoplay navigation.",
  "categories": [
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "testimonial"
}