{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"marketing-blog-sections-carousel","type":"registry:block","title":"Carousel","files":[{"path":"components/blocks/marketing/blog-sections/carousel.tsx","content":"import { useState, useRef, useEffect, TouchEvent } from 'react';\n\nimport { Button } from '@/components/ui/button';\nimport { Card, CardContent, CardFooter } from '@/components/ui/card';\nimport { Badge } from '@/components/ui/badge';\nimport {\n  CalendarIcon,\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  ArrowRightIcon,\n} from 'lucide-react';\n\ninterface BlogPost {\n  id: number;\n  title: string;\n  category: string;\n  date: string;\n  imageUrl: string;\n  excerpt: string;\n}\n\nconst blogPosts: BlogPost[] = [\n  {\n    id: 1,\n    title: 'How Marketing Analytics is Reshaping Business Strategies',\n    category: 'Analytics',\n    date: 'April 18, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80',\n    excerpt:\n      'Data-driven marketing is changing how companies make decisions. Learn how to leverage analytics for better results.',\n  },\n  {\n    id: 2,\n    title: \"The Rise of Video Marketing: Why You Can't Ignore It\",\n    category: 'Video',\n    date: 'April 12, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1626785774573-4b799315345d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2071&q=80',\n    excerpt:\n      'Video content has become an essential part of modern marketing strategies. Find out why and how to get started.',\n  },\n  {\n    id: 3,\n    title: 'Building Customer Loyalty Through Content Marketing',\n    category: 'Content',\n    date: 'April 5, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80',\n    excerpt:\n      'Create content that not only attracts but retains customers. Strategies for building long-term relationships through your content.',\n  },\n  {\n    id: 4,\n    title: 'Social Media Trends That Will Dominate in 2023',\n    category: 'Social Media',\n    date: 'March 29, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2074&q=80',\n    excerpt:\n      'Stay ahead of the curve with these emerging social media trends that will shape the digital landscape this year.',\n  },\n  {\n    id: 5,\n    title: 'Email Marketing Personalization: Going Beyond First Name',\n    category: 'Email',\n    date: 'March 22, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1596526131083-e8c633c948d2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1074&q=80',\n    excerpt:\n      \"Advanced techniques for personalizing your email campaigns that go well beyond simply using a subscriber's name.\",\n  },\n  {\n    id: 6,\n    title: 'Sustainable Marketing: Building Eco-Friendly Campaigns',\n    category: 'Sustainability',\n    date: 'March 15, 2023',\n    imageUrl:\n      'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80',\n    excerpt:\n      'How to integrate sustainability into your marketing strategy and connect with environmentally conscious consumers.',\n  },\n];\n\nexport default function BlogSectionSlider() {\n  const [currentIndex, setCurrentIndex] = useState(0);\n  const [isSwiping, setIsSwiping] = useState(false);\n  const [startX, setStartX] = useState(0);\n  const [screenSize, setScreenSize] = useState({\n    isMobile: false,\n    isTablet: false,\n    isDesktop: false,\n  });\n\n  const sliderRef = useRef<HTMLDivElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  // Calculate visible items based on screen size\n  const visibleItems = screenSize.isDesktop ? 3 : screenSize.isTablet ? 2 : 1;\n  const maxIndex = Math.max(0, blogPosts.length - visibleItems);\n\n  // Initialize and update screen size\n  useEffect(() => {\n    const updateScreenSize = () => {\n      if (typeof window !== 'undefined') {\n        const width = window.innerWidth;\n        setScreenSize({\n          isMobile: width < 640,\n          isTablet: width >= 640 && width < 1024,\n          isDesktop: width >= 1024,\n        });\n      }\n    };\n\n    // Initial check\n    updateScreenSize();\n\n    // Listen for resize\n    window.addEventListener('resize', updateScreenSize);\n\n    return () => window.removeEventListener('resize', updateScreenSize);\n  }, []);\n\n  // Ensure current index is valid when screen size changes\n  useEffect(() => {\n    setCurrentIndex((prev) => Math.min(prev, maxIndex));\n  }, [screenSize, maxIndex]);\n\n  // Handle navigation\n  function handlePrevious() {\n    setCurrentIndex((prev) => Math.max(0, prev - 1));\n  }\n\n  function handleNext() {\n    setCurrentIndex((prev) => Math.min(maxIndex, prev + 1));\n  }\n\n  // Scroll to current index\n  useEffect(() => {\n    if (sliderRef.current) {\n      const scrollToIndex = () => {\n        if (sliderRef.current) {\n          const cardWidth =\n            sliderRef.current.querySelector('.carousel-item')?.clientWidth || 0;\n          const scrollLeft = cardWidth * currentIndex;\n\n          sliderRef.current.scrollTo({\n            left: scrollLeft,\n            behavior: 'smooth',\n          });\n        }\n      };\n\n      // Small delay to ensure the DOM has updated\n      const timeoutId = setTimeout(scrollToIndex, 50);\n      return () => clearTimeout(timeoutId);\n    }\n  }, [currentIndex, screenSize]);\n\n  // Touch event handlers for swipe functionality\n  const handleTouchStart = (e: TouchEvent<HTMLDivElement>) => {\n    setIsSwiping(true);\n    setStartX(e.touches[0]!.clientX);\n  };\n\n  const handleTouchMove = (e: TouchEvent<HTMLDivElement>) => {\n    if (!isSwiping) return;\n\n    const currentX = e.touches[0]!.clientX;\n    const diff = startX - currentX;\n\n    // Prevent default to stop page scrolling during swipe\n    if (Math.abs(diff) > 5) {\n      e.preventDefault();\n    }\n  };\n\n  const handleTouchEnd = (e: TouchEvent<HTMLDivElement>) => {\n    if (!isSwiping) return;\n\n    const currentX = e.changedTouches[0]!.clientX;\n    const diff = startX - currentX;\n\n    // Determine if swipe is significant\n    if (Math.abs(diff) > 50) {\n      if (diff > 0 && currentIndex < maxIndex) {\n        handleNext();\n      } else if (diff < 0 && currentIndex > 0) {\n        handlePrevious();\n      }\n    }\n\n    setIsSwiping(false);\n  };\n\n  // Progress indicators\n  const renderProgressIndicators = () => {\n    return (\n      <div className=\"mt-6 flex justify-center space-x-2\">\n        {Array.from({ length: maxIndex + 1 }, (_, i) => (\n          <button\n            key={i}\n            onClick={() => setCurrentIndex(i)}\n            className={`h-2 rounded-full transition-all ${\n              i === currentIndex ? 'bg-primary w-6' : 'bg-primary/30 w-2'\n            }`}\n            aria-label={`Go to slide ${i + 1}`}\n          />\n        ))}\n      </div>\n    );\n  };\n\n  return (\n    <section className=\"py-12 md:py-20 lg:py-24\">\n      <div\n        ref={containerRef}\n        className=\"container mx-auto space-y-6 px-4 md:space-y-8 md:px-6 2xl:max-w-[1400px]\"\n      >\n        <div className=\"flex flex-wrap items-center justify-between gap-4\">\n          <div className=\"max-w-md space-y-1\">\n            <h2 className=\"text-2xl font-bold tracking-tight md:text-3xl\">\n              Latest Marketing Articles\n            </h2>\n            <p className=\"text-muted-foreground text-sm md:text-base\">\n              Stay updated with our most recent insights\n            </p>\n          </div>\n          <div className=\"flex hidden items-center space-x-2 sm:flex\">\n            <Button\n              variant=\"outline\"\n              size=\"icon\"\n              onClick={handlePrevious}\n              disabled={currentIndex === 0}\n              aria-label=\"Previous slide\"\n            >\n              <ChevronLeftIcon className=\"h-4 w-4\" />\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"icon\"\n              onClick={handleNext}\n              disabled={currentIndex === maxIndex}\n              aria-label=\"Next slide\"\n            >\n              <ChevronRightIcon className=\"h-4 w-4\" />\n            </Button>\n          </div>\n        </div>\n\n        <div className=\"relative overflow-hidden\">\n          <div\n            ref={sliderRef}\n            className=\"scrollbar-hide -mx-4 flex touch-pan-x snap-x snap-mandatory overflow-x-auto px-4 pt-1 pb-2 md:pb-4\"\n            style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}\n            onTouchStart={handleTouchStart}\n            onTouchMove={handleTouchMove}\n            onTouchEnd={handleTouchEnd}\n          >\n            {blogPosts.map((post) => (\n              <div\n                key={post.id}\n                className=\"carousel-item w-full flex-none snap-start px-2 sm:w-1/2 sm:px-4 lg:w-1/3\"\n              >\n                <Card className=\"flex h-full flex-col overflow-hidden p-0 shadow-sm transition-shadow hover:shadow-md\">\n                  <div className=\"relative h-40 overflow-hidden sm:h-48 md:h-52\">\n                    <img\n                      src={post.imageUrl}\n                      alt={post.title}\n                      className=\"object-cover transition-transform duration-300 hover:scale-105\"\n                    />\n                    <div className=\"absolute top-3 left-3\">\n                      <Badge className=\"bg-primary hover:bg-primary/90\">\n                        {post.category}\n                      </Badge>\n                    </div>\n                  </div>\n                  <CardContent className=\"flex-grow\">\n                    <div className=\"text-muted-foreground mb-2 flex items-center text-xs sm:mb-3 sm:text-sm\">\n                      <CalendarIcon className=\"mr-1 h-3 w-3\" />\n                      <span>{post.date}</span>\n                    </div>\n                    <h3 className=\"mb-2 line-clamp-2 text-base font-semibold sm:text-lg\">\n                      {post.title}\n                    </h3>\n                    <p className=\"text-muted-foreground line-clamp-2 text-xs sm:line-clamp-3 sm:text-sm\">\n                      {post.excerpt}\n                    </p>\n                  </CardContent>\n                  <CardFooter className=\"pb-6\">\n                    <Button\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      className=\"w-full text-sm\"\n                      asChild\n                    >\n                      <a href=\"#\" className=\"flex items-center justify-center\">\n                        Read Article\n                        <ArrowRightIcon className=\"ml-1 h-4 w-4\" />\n                      </a>\n                    </Button>\n                  </CardFooter>\n                </Card>\n              </div>\n            ))}\n          </div>\n\n          {/* Progress indicators for mobile */}\n          <div className=\"sm:hidden\">{renderProgressIndicators()}</div>\n\n          {/* Mobile navigation buttons - only shown on very small screens */}\n          <div className=\"mt-6 flex items-center justify-between sm:hidden\">\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={handlePrevious}\n              disabled={currentIndex === 0}\n              className=\"mr-2 h-9 flex-1 text-xs\"\n            >\n              <ChevronLeftIcon className=\"mr-1 h-4 w-4\" />\n              Prev\n            </Button>\n            <Button\n              variant=\"outline\"\n              size=\"sm\"\n              onClick={handleNext}\n              disabled={currentIndex === maxIndex}\n              className=\"ml-2 h-9 flex-1 text-xs\"\n            >\n              Next\n              <ChevronRightIcon className=\"ml-1 h-4 w-4\" />\n            </Button>\n          </div>\n        </div>\n\n        <div className=\"mt-2 flex justify-center sm:mt-8\">\n          <Button variant=\"outline\" className=\"w-full max-w-sm\" asChild>\n            <a href=\"#\">Browse All Articles</a>\n          </Button>\n        </div>\n      </div>\n    </section>\n  );\n}\n","type":"registry:component","target":"components/blocks/marketing/blog-sections/carousel.tsx"},{"path":"components/ui/button.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5\",\n        lg: \"h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        icon: \"size-9\",\n        \"icon-xs\":\n          \"size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\":\n          \"size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md\",\n        \"icon-lg\": \"size-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"button\"> &\n  VariantProps<typeof buttonVariants> & {\n    asChild?: boolean\n  }) {\n  const Comp = asChild ? Slot.Root : \"button\"\n\n  return (\n    <Comp\n      data-slot=\"button\"\n      data-variant={variant}\n      data-size={size}\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n","type":"registry:ui","target":"components/ui/button.tsx"},{"path":"components/ui/card.tsx","content":"import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"group/card flex flex-col gap-6 overflow-hidden rounded-xl bg-card py-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\n        \"font-heading text-base leading-normal font-medium group-data-[size=sm]/card:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-6 group-data-[size=sm]/card:px-4\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        \"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n","type":"registry:ui","target":"components/ui/card.tsx"},{"path":"components/ui/badge.tsx","content":"import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Slot } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        secondary:\n          \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\n        destructive:\n          \"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20\",\n        outline:\n          \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nfunction Badge({\n  className,\n  variant = \"default\",\n  asChild = false,\n  ...props\n}: React.ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n  const Comp = asChild ? Slot.Root : \"span\"\n\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    />\n  )\n}\n\nexport { Badge, badgeVariants }\n","type":"registry:ui","target":"components/ui/badge.tsx"}],"description":"A horizontal slider of article cards with cover images and titles. Use it to feature recent or popular posts in a compact strip readers can swipe.","dependencies":["class-variance-authority","lucide-react","radix-ui"]}