{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["motion"],"description":"Simple testimonials block with auto-rotating cards","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport { getAvatarUrl, getTestimonials } from \"@/lib/smoothui-data\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useRef, useState } from \"react\";\n\nconst TESTIMONIAL_COUNT = 4;\nconst AVATAR_SIZE = 96;\nconst DURATION = 5000; // ms\nconst BAR_WIDTH = 50;\nconst CIRCLE_SIZE = 12;\nconst BORDER_RADIUS_ACTIVE = 8;\nconst BORDER_RADIUS_INACTIVE = 999;\nconst MILLISECONDS_TO_SECONDS = 1000;\n\nconst testimonials = getTestimonials(TESTIMONIAL_COUNT).map((testimonial) => ({\n  avatar: testimonial.avatar,\n  name: testimonial.name,\n  quote: testimonial.content || \"\",\n  role: testimonial.role,\n}));\n\nexport function TestimonialsSimple() {\n  const shouldReduceMotion = useReducedMotion();\n  const [index, setIndex] = useState(0);\n  const timeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n  useEffect(() => {\n    timeoutRef.current = setTimeout(() => {\n      setIndex((prev) => (prev + 1) % testimonials.length);\n    }, DURATION);\n\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n    };\n  }, []);\n\n  return (\n    <section className=\"relative flex flex-col items-center bg-background py-16\">\n      <div className=\"flex w-full max-w-5xl flex-col items-center justify-center px-4\">\n        <div className=\"min-h-[120px] w-full\">\n          <AnimatePresence mode=\"wait\">\n            <motion.blockquote\n              animate={\n                shouldReduceMotion ? { opacity: 1 } : { opacity: 1, y: 0 }\n              }\n              className=\"mb-8 text-center font-semibold text-2xl text-foreground leading-tight md:text-4xl\"\n              exit={\n                shouldReduceMotion\n                  ? { opacity: 0, transition: { duration: 0 } }\n                  : { opacity: 0, y: -30 }\n              }\n              initial={\n                shouldReduceMotion ? { opacity: 1 } : { opacity: 0, y: 30 }\n              }\n              key={index}\n              transition={\n                shouldReduceMotion\n                  ? { duration: 0 }\n                  : { duration: 0.5, type: \"spring\" as const }\n              }\n            >\n              &ldquo;{testimonials[index].quote}&rdquo;\n            </motion.blockquote>\n          </AnimatePresence>\n        </div>\n        <div className=\"flex w-full max-w-lg items-center justify-center gap-8 pt-8\">\n          <AnimatePresence initial={false} mode=\"wait\">\n            <motion.div\n              animate={\n                shouldReduceMotion\n                  ? { opacity: 1 }\n                  : { filter: \"blur(0px)\", opacity: 1 }\n              }\n              className=\"flex items-center gap-4\"\n              exit={\n                shouldReduceMotion\n                  ? { opacity: 0, transition: { duration: 0 } }\n                  : { filter: \"blur(8px)\", opacity: 0 }\n              }\n              initial={\n                shouldReduceMotion\n                  ? { opacity: 1 }\n                  : { filter: \"blur(8px)\", opacity: 0 }\n              }\n              key={index}\n              transition={\n                shouldReduceMotion\n                  ? { duration: 0 }\n                  : { duration: 0.5, type: \"spring\" as const }\n              }\n            >\n              <img\n                alt={`${testimonials[index].name} avatar`}\n                className=\"h-12 w-12 rounded-full border bg-foreground/10 object-cover\"\n                draggable={false}\n                height={48}\n                src={getAvatarUrl(testimonials[index].avatar, AVATAR_SIZE)}\n                width={48}\n              />\n              <div className=\"mx-4 h-8 border-muted-foreground/30 border-l\" />\n              <div className=\"text-left\">\n                <div className=\"font-medium text-foreground text-lg italic\">\n                  {testimonials[index].name}\n                </div>\n                <div className=\"text-base text-muted-foreground\">\n                  {testimonials[index].role}\n                </div>\n              </div>\n            </motion.div>\n          </AnimatePresence>\n        </div>\n        {/* Progress Bar & Circles Indicator */}\n        <div className=\"mx-auto mt-8 flex w-full max-w-lg justify-center gap-3\">\n          {testimonials.map((testimonial, i) => {\n            const isActive = i === index;\n            return (\n              <motion.span\n                animate={\n                  shouldReduceMotion\n                    ? {\n                        borderRadius: isActive\n                          ? BORDER_RADIUS_ACTIVE\n                          : BORDER_RADIUS_INACTIVE,\n                        height: CIRCLE_SIZE,\n                        width: isActive ? BAR_WIDTH : CIRCLE_SIZE,\n                      }\n                    : {\n                        borderRadius: isActive\n                          ? BORDER_RADIUS_ACTIVE\n                          : BORDER_RADIUS_INACTIVE,\n                        height: CIRCLE_SIZE,\n                        width: isActive ? BAR_WIDTH : CIRCLE_SIZE,\n                      }\n                }\n                className=\"relative block overflow-hidden bg-foreground/10\"\n                initial={false}\n                key={`testimonial-${testimonial.name}-${i}`}\n                layout={!shouldReduceMotion}\n                style={{\n                  border: \"none\",\n                  maxWidth: BAR_WIDTH,\n                  minWidth: CIRCLE_SIZE,\n                }}\n                transition={\n                  shouldReduceMotion\n                    ? { duration: 0 }\n                    : {\n                        damping: 30,\n                        duration: 0.4,\n                        stiffness: 300,\n                        type: \"spring\" as const,\n                      }\n                }\n              >\n                {isActive && (\n                  <motion.div\n                    animate={{ width: \"100%\" }}\n                    className=\"absolute top-0 left-0 h-full rounded-lg bg-brand\"\n                    exit={{ width: 0 }}\n                    initial={\n                      shouldReduceMotion ? { width: \"100%\" } : { width: 0 }\n                    }\n                    key={index}\n                    transition={\n                      shouldReduceMotion\n                        ? { duration: 0 }\n                        : {\n                            duration: DURATION / MILLISECONDS_TO_SECONDS,\n                            ease: \"linear\",\n                          }\n                    }\n                  />\n                )}\n              </motion.span>\n            );\n          })}\n        </div>\n      </div>\n    </section>\n  );\n}\n\nexport default TestimonialsSimple;\n","path":"index.tsx","target":"components/smoothui/testimonials-1/index.tsx","type":"registry:block"}],"name":"testimonials-1","registryDependencies":["https://smoothui.dev/r/data.json","https://smoothui.dev/r/tokens.json"],"title":"Testimonials 1","type":"registry:block"}