{
  "name": "testimonials/carousel-features",
  "type": "registry:component",
  "description": "Feature-focused testimonial carousel",
  "files": [
    {
      "path": "example/Testimonials/SectionTestimonials_CarouselFeatures.astro",
      "type": "registry:component",
      "content": "---\nimport Card from '@@/components/ui/Card.astro';\nimport { Icon } from 'astro-icon/components';\n\ninterface TestimonialItem {\n  name: string;\n  title: string;\n  quote: string;\n  avatar: string;\n  company: string;\n}\n\nconst testimonialItems: TestimonialItem[] = [\n  {\n    name: 'Denis Slavska',\n    title: 'CTO, Ailitic',\n    quote: 'They tailor their solutions to our specific needs and goals.',\n    avatar:\n      'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?q=80&w=160&auto=format&fit=crop',\n    company: 'Ailitic',\n  },\n  {\n    name: 'Jahan Melad',\n    title: 'Project Manager, Buildwave',\n    quote: 'They organized their work and internal management was outstanding.',\n    avatar:\n      'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?q=80&w=160&auto=format&fit=crop',\n    company: 'Buildwave',\n  },\n  {\n    name: 'Jim Halpert',\n    title: 'Lead Engineering, Inhive Space',\n    quote: 'Working with them was a great experience.',\n    avatar:\n      'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?q=80&w=160&auto=format&fit=crop',\n    company: 'InHive',\n  },\n  {\n    name: 'Sarah Chen',\n    title: 'VP of Engineering, TechFlow',\n    quote: 'Their attention to detail and commitment to quality is unmatched.',\n    avatar:\n      'https://images.unsplash.com/photo-1494790108377-be9c29b29330?q=80&w=160&auto=format&fit=crop',\n    company: 'TechFlow',\n  },\n  {\n    name: 'Michael Roberts',\n    title: 'Director of Product, DataSync',\n    quote: 'Exceptional team that delivers results beyond expectations.',\n    avatar:\n      'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?q=80&w=160&auto=format&fit=crop',\n    company: 'DataSync',\n  },\n];\n---\n\n<section class=\"relative overflow-hidden py-20 sm:py-24\">\n  <div class=\"container\">\n    <div class=\"mb-10\">\n      <div\n        class=\"rounded-card mb-4 inline-flex items-center gap-2 px-4 py-2 text-sm\"\n        data-scheme=\"shift\"\n      >\n        <span class=\"bg-accent h-2 w-2 rounded-full\"></span>\n        Testimonials\n      </div>\n      <h2 class=\"h2 mb-4\">\n        What People Say About <span class=\"sp-emphasis\">Pablo</span>\n      </h2>\n      <p class=\"text-fg-sub mb-6 max-w-3xl text-base sm:text-lg md:text-xl\">\n        Discover how Pablo is transforming the way people build websites\n      </p>\n      <!-- Controls -->\n      <div class=\"flex items-center gap-2\">\n        <button\n          type=\"button\"\n          data-action=\"prev\"\n          aria-label=\"Previous testimonial\"\n          class=\"carousel-btn group\"\n        >\n          <span\n            data-scheme=\"bg\"\n            class=\"shadow-2 inline-flex h-10 w-10 items-center justify-center rounded-full\"\n          >\n            <Icon name=\"lucide:chevron-left\" size={18} />\n          </span>\n        </button>\n        <button\n          type=\"button\"\n          data-action=\"next\"\n          aria-label=\"Next testimonial\"\n          class=\"carousel-btn group\"\n        >\n          <span\n            data-scheme=\"bg\"\n            class=\"shadow-2 inline-flex h-10 w-10 items-center justify-center rounded-full\"\n          >\n            <Icon name=\"lucide:chevron-right\" size={18} />\n          </span>\n        </button>\n      </div>\n    </div>\n\n    <!-- Carousel -->\n    <div class=\"relative mt-10 sm:mt-16\" data-carousel-features-root>\n      <!-- Viewport -->\n      <div class=\"overflow-hidden\">\n        <div\n          data-carousel-track\n          class=\"flex w-full items-stretch gap-4 transition-transform duration-500 sm:gap-6\"\n        >\n          {\n            testimonialItems.map((item, index) => (\n              <div\n                data-slide={index}\n                data-slide-original\n                class=\"flex w-full min-w-full shrink-0 sm:w-[calc(50%-0.5rem)] sm:min-w-[calc(50%-0.5rem)] lg:w-[calc(33.333%-1rem)] lg:min-w-[calc(33.333%-1rem)]\"\n              >\n                <Card className=\"shadow-8 h-full min-h-[400px] w-full flex flex-col gap-8 p-6 sm:min-h-[500px] sm:p-8 lg:min-h-[600px] lg:p-10\">\n                  <div class=\"flex items-start justify-between\">\n                    <img\n                      src={item.avatar}\n                      alt={item.name}\n                      class=\"h-16 w-16 rounded-full object-cover\"\n                    />\n                    <div class=\"rounded-full bg-[color-mix(in_srgb,_var(--sp-fg)_4%,_transparent)] px-3 py-1.5\">\n                      <span class=\"text-fg-sub text-xs font-medium\">\n                        {item.company}\n                      </span>\n                    </div>\n                  </div>\n\n                  <blockquote class=\"text-fg flex-1 text-xl font-medium leading-relaxed sm:text-2xl\">\n                    \"{item.quote}\"\n                  </blockquote>\n\n                  <div class=\"mt-auto\">\n                    <p class=\"text-fg font-semibold\">{item.name}</p>\n                    <p class=\"text-fg-sub text-sm\">{item.title}</p>\n                  </div>\n                </Card>\n              </div>\n            ))\n          }\n          <!-- Trailing spacer for right padding -->\n          <div class=\"w-6 shrink-0\"></div>\n        </div>\n      </div>\n    </div>\n  </div>\n</section>\n\n<style>\n  .carousel-btn {\n    cursor: pointer;\n    transition:\n      transform 0.15s cubic-bezier(0.4, 0, 0.2, 1),\n      opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1);\n  }\n\n  .carousel-btn:hover:not(:disabled) {\n    transform: translateY(1px) scale(0.99);\n    opacity: 0.9;\n  }\n\n  .carousel-btn:active:not(:disabled) {\n    transform: translateY(2px) scale(0.98);\n    opacity: 0.8;\n  }\n\n  .carousel-btn:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n    pointer-events: none;\n  }\n</style>\n\n<script>\n  function initTestimonialCarousel() {\n    const root = document.querySelector(\"[data-carousel-features-root]\");\n    if (!root) return;\n\n    const track = root.querySelector(\n      \"[data-carousel-track]\",\n    ) as HTMLElement | null;\n\n    // ボタンはrootの外側にあるので、親要素から探す\n    const container = root.closest(\".container\");\n    const prev = container?.querySelector(\n      '[data-action=\"prev\"]',\n    ) as HTMLButtonElement | null;\n    const next = container?.querySelector(\n      '[data-action=\"next\"]',\n    ) as HTMLButtonElement | null;\n\n    if (!track) return;\n\n    const slides = Array.from(\n      track.querySelectorAll(\"[data-slide-original]\"),\n    ) as HTMLElement[];\n    if (slides.length === 0) return;\n\n    let index = 0;\n\n    // 画面サイズに応じた表示枚数を取得\n    const getVisibleSlides = () => {\n      const width = window.innerWidth;\n      if (width >= 1024) return 3; // lg\n      if (width >= 640) return 2; // sm\n      return 1; // mobile\n    };\n\n    // スライドを更新\n    const updateSlide = () => {\n      // 実際の要素の幅とギャップを計算\n      if (slides.length === 0) return;\n\n      const firstSlide = slides[0];\n      const slideWidth = firstSlide.offsetWidth;\n      // gap-4 sm:gap-6 = 1rem (16px) on mobile, 1.5rem (24px) on sm+\n      const gap = window.innerWidth >= 640 ? 24 : 16;\n      const moveDistance = slideWidth + gap;\n      const offset = -index * moveDistance;\n\n      track.style.transform = `translateX(${offset}px)`;\n    };\n\n    // ボタンの状態を更新\n    const updateButtonStates = () => {\n      if (!prev || !next) return;\n\n      const visibleSlides = getVisibleSlides();\n      const maxIndex = Math.max(0, slides.length - visibleSlides);\n      prev.disabled = index === 0;\n      next.disabled = index >= maxIndex;\n    };\n\n    // インデックスを設定\n    const setIndex = (newIndex: number) => {\n      // 画面サイズに応じた表示枚数を考慮\n      const visibleSlides = getVisibleSlides();\n      const maxIndex = Math.max(0, slides.length - visibleSlides);\n      index = Math.max(0, Math.min(newIndex, maxIndex));\n      updateSlide();\n      updateButtonStates();\n    };\n\n    // 前へ\n    const goPrev = () => {\n      setIndex(index - 1);\n    };\n\n    // 次へ\n    const goNext = () => {\n      setIndex(index + 1);\n    };\n\n    prev?.addEventListener(\"click\", goPrev);\n    next?.addEventListener(\"click\", goNext);\n\n    // ウィンドウリサイズ時に再計算\n    window.addEventListener(\"resize\", () => {\n      updateSlide();\n      updateButtonStates();\n    });\n\n    // 初期表示\n    updateSlide();\n    updateButtonStates();\n  }\n\n  if (document.readyState === \"loading\") {\n    document.addEventListener(\"DOMContentLoaded\", initTestimonialCarousel);\n  } else {\n    initTestimonialCarousel();\n  }\n</script>\n"
    }
  ],
  "category": "example",
  "registryDependencies": [
    "carousel"
  ]
}