{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "blog-post-card-grid",
  "title": "Blog Post Card Grid",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/blog-post-card-grid/BlogPostCardGrid.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { useState, useMemo, type FormEvent } from 'react'\nimport { ArrowRight, BookOpen, Check, Clock, Loader2, Mail, Star } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Separator } from '@/components/ui/separator'\n\nexport interface Author {\n  name: string\n  role: string\n  avatar: string\n  initials: string\n}\n\nexport interface BlogPost {\n  id: string\n  title: string\n  excerpt: string\n  category: 'Architecture' | 'Design Systems' | 'Performance' | 'Tutorials'\n  readTime: string\n  date: string\n  image: string\n  author: Author\n}\n\nexport interface BlogPostCardGridProps extends React.HTMLAttributes<HTMLElement> {\n  showFeatured?: boolean\n}\n\nconst categories = ['All', 'Architecture', 'Design Systems', 'Performance', 'Tutorials'] as const\ntype Category = (typeof categories)[number]\n\nconst featuredPost: BlogPost = {\n  id: 'featured-1',\n  title: 'Deconstructing Event-Driven Micro-Frontends at Scale',\n  excerpt:\n    'How we transitioned a monolithic dashboard into decoupled, independently deployable island architectures using web components, resilient event buses, and broadcast channels.',\n  category: 'Architecture',\n  readTime: '8 min read',\n  date: 'Aug 18, 2026',\n  image: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1200&auto=format&fit=crop&q=80',\n  author: {\n    name: 'Elena Rostova',\n    role: 'Principal Systems Architect',\n    avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',\n    initials: 'ER',\n  },\n}\n\nconst initialPosts: BlogPost[] = [\n  {\n    id: 'post-1',\n    title: 'Architecting Token Hierarchies in Tailwind CSS v4',\n    excerpt:\n      'A comprehensive guide to structured OKLCH color palettes, dynamic light and dark elevation layers, and type scale ergonomics.',\n    category: 'Design Systems',\n    readTime: '5 min read',\n    date: 'Aug 16, 2026',\n    image: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Marcus Chen',\n      role: 'Design Technologist',\n      avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80',\n      initials: 'MC',\n    },\n  },\n  {\n    id: 'post-2',\n    title: 'Eliminating Layout Shift in Hydrated Islands',\n    excerpt:\n      'Techniques for zero-CLS component streaming, skeleton synchronization, and sub-100ms Interaction to Next Paint (INP).',\n    category: 'Performance',\n    readTime: '6 min read',\n    date: 'Aug 14, 2026',\n    image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Sarah Jenkins',\n      role: 'Performance Engineer',\n      avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150&auto=format&fit=crop&q=80',\n      initials: 'SJ',\n    },\n  },\n  {\n    id: 'post-3',\n    title: 'Deterministic State Synchronization Over WebSockets',\n    excerpt:\n      'Building conflict-free collaborative data models with operational transformation and CRDTs for multi-tenant SaaS.',\n    category: 'Architecture',\n    readTime: '7 min read',\n    date: 'Aug 11, 2026',\n    image: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Devon Vance',\n      role: 'Distributed Systems Lead',\n      avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80',\n      initials: 'DV',\n    },\n  },\n  {\n    id: 'post-4',\n    title: 'Building Headless Accessible Dropdowns with Reka UI',\n    excerpt:\n      'Step-by-step implementation of focus trap management, keyboard navigation patterns, and polymorphic slot composition.',\n    category: 'Tutorials',\n    readTime: '4 min read',\n    date: 'Aug 08, 2026',\n    image: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Aaliyah Patel',\n      role: 'Frontend Engineer',\n      avatar: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=150&auto=format&fit=crop&q=80',\n      initials: 'AP',\n    },\n  },\n  {\n    id: 'post-5',\n    title: 'Fluid Typography and Motion Curves in Modern Web Apps',\n    excerpt:\n      'Using CSS clamp functions and spring physics to create tactile, responsive interfaces that adapt directly across devices.',\n    category: 'Design Systems',\n    readTime: '5 min read',\n    date: 'Aug 04, 2026',\n    image: 'https://images.unsplash.com/photo-1507238691740-187a5b1d37b8?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Lucas Meyer',\n      role: 'UI Engineer',\n      avatar: 'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=150&auto=format&fit=crop&q=80',\n      initials: 'LM',\n    },\n  },\n  {\n    id: 'post-6',\n    title: 'Continuous Benchmarking with Playwright and GitHub Actions',\n    excerpt:\n      'Automate regression detection for memory leaks, bundle sizes, and render latency directly within your CI pull request checks.',\n    category: 'Tutorials',\n    readTime: '6 min read',\n    date: 'Jul 30, 2026',\n    image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Elena Rostova',\n      role: 'Principal Systems Architect',\n      avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',\n      initials: 'ER',\n    },\n  },\n]\n\nconst additionalPosts: BlogPost[] = [\n  {\n    id: 'post-7',\n    title: 'Optimizing JavaScript Garbage Collection in High-Frequency Canvas',\n    excerpt:\n      'How object pooling and typed array buffers solved stuttering frame rates during real-time telemetry rendering.',\n    category: 'Performance',\n    readTime: '9 min read',\n    date: 'Jul 26, 2026',\n    image: 'https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Marcus Chen',\n      role: 'Design Technologist',\n      avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80',\n      initials: 'MC',\n    },\n  },\n  {\n    id: 'post-8',\n    title: 'Resilient Edge Caching Strategies for Dynamic Multi-Region APIs',\n    excerpt:\n      'Configuring stale-while-revalidate headers, cache tagging, and instant surrogate key purging across worldwide POPs.',\n    category: 'Architecture',\n    readTime: '7 min read',\n    date: 'Jul 22, 2026',\n    image: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Devon Vance',\n      role: 'Distributed Systems Lead',\n      avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80',\n      initials: 'DV',\n    },\n  },\n  {\n    id: 'post-9',\n    title: 'Designing High-Contrast Dark Modes without Washing Out Brand Identity',\n    excerpt:\n      'Tuning APCA perceptual contrast ratios, luminance curves, and colored alpha borders in enterprise dark themes.',\n    category: 'Design Systems',\n    readTime: '5 min read',\n    date: 'Jul 18, 2026',\n    image: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=800&auto=format&fit=crop&q=80',\n    author: {\n      name: 'Aaliyah Patel',\n      role: 'Frontend Engineer',\n      avatar: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=150&auto=format&fit=crop&q=80',\n      initials: 'AP',\n    },\n  },\n]\n\nexport function BlogPostCardGrid({ showFeatured = true, className, ...props }: BlogPostCardGridProps) {\n  const [activeCategory, setActiveCategory] = useState<Category>('All')\n  const [isLoadingMore, setIsLoadingMore] = useState(false)\n  const [showAllPosts, setShowAllPosts] = useState(false)\n  const [emailInput, setEmailInput] = useState('')\n  const [isSubscribed, setIsSubscribed] = useState(false)\n\n  const visiblePosts = useMemo(() => {\n    const pool = showAllPosts ? [...initialPosts, ...additionalPosts] : initialPosts\n    if (activeCategory === 'All') return pool\n    return pool.filter((p) => p.category === activeCategory)\n  }, [showAllPosts, activeCategory])\n\n  const isHeroVisible = showFeatured && (activeCategory === 'All' || featuredPost.category === activeCategory)\n\n  function handleCategorySelect(cat: Category) {\n    setActiveCategory(cat)\n  }\n\n  function handleLoadMore() {\n    setIsLoadingMore(true)\n    setTimeout(() => {\n      setShowAllPosts(true)\n      setIsLoadingMore(false)\n    }, 400)\n  }\n\n  function handleSubscribe(e: FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    if (!emailInput.includes('@')) return\n    setIsSubscribed(true)\n  }\n\n  return (\n    <section\n      data-slot=\"blog-post-card-grid\"\n      className={cn('bg-background w-full py-12 sm:py-16 lg:py-20', className)}\n      {...props}\n    >\n      <div className=\"mx-auto max-w-7xl space-y-12 px-4 sm:space-y-16 sm:px-6 lg:px-8\">\n        {/* Section Header */}\n        <div className=\"flex flex-col items-start justify-between gap-6 md:flex-row md:items-end\">\n          <div className=\"max-w-2xl space-y-3\">\n            <Badge variant=\"outline\" className=\"gap-1.5 px-3 py-1 text-xs font-medium\">\n              <BookOpen className=\"text-primary size-3.5\" aria-hidden=\"true\" />\n              From the Blog\n            </Badge>\n            <h2 className=\"text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl\">\n              Latest Articles &amp; Engineering Deep Dives\n            </h2>\n            <p className=\"text-muted-foreground text-base leading-relaxed sm:text-lg\">\n              Technical essays on software architecture, design systems, and frontend performance.\n            </p>\n          </div>\n\n          {/* Category Filter Pills */}\n          <div className=\"flex flex-wrap items-center gap-2\" role=\"tablist\" aria-label=\"Article categories\">\n            {categories.map((cat) => (\n              <Button\n                key={cat}\n                size=\"sm\"\n                variant={activeCategory === cat ? 'default' : 'outline'}\n                className=\"rounded-full text-xs font-medium\"\n                aria-selected={activeCategory === cat}\n                role=\"tab\"\n                onClick={() => handleCategorySelect(cat)}\n              >\n                {cat}\n              </Button>\n            ))}\n          </div>\n        </div>\n\n        {/* Featured Hero Article Card */}\n        {isHeroVisible && (\n          <Card className=\"group/hero border-border bg-card hover:border-primary/30 relative overflow-hidden shadow-xs transition-colors duration-300 hover:shadow-md\">\n            <div className=\"grid grid-cols-1 lg:grid-cols-12\">\n              {/* Hero Image Container */}\n              <div className=\"bg-muted relative aspect-video min-h-[260px] min-w-0 overflow-hidden sm:min-h-[340px] lg:col-span-7 lg:aspect-auto\">\n                <img\n                  src={featuredPost.image}\n                  alt={featuredPost.title}\n                  className=\"size-full object-cover transition-transform duration-500 ease-out group-hover/hero:scale-105\"\n                />\n                <div className=\"absolute top-4 left-4 flex flex-wrap items-center gap-2\">\n                  <Badge className=\"bg-primary text-primary-foreground border-transparent text-xs font-medium shadow-xs\">\n                    <Star className=\"mr-1 size-3\" aria-hidden=\"true\" />\n                    Featured Story\n                  </Badge>\n                  <Badge\n                    variant=\"secondary\"\n                    className=\"bg-background/90 text-foreground border-border/60 text-xs font-medium shadow-xs backdrop-blur-md\"\n                  >\n                    {featuredPost.category}\n                  </Badge>\n                </div>\n              </div>\n\n              {/* Hero Content Panel */}\n              <CardContent className=\"flex flex-col justify-between space-y-6 p-6 sm:p-8 lg:col-span-5 lg:p-10\">\n                <div className=\"space-y-4\">\n                  <div className=\"text-muted-foreground flex items-center gap-2 text-xs font-medium\">\n                    <BookOpen className=\"text-primary size-3.5\" aria-hidden=\"true\" />\n                    <span>Editorial Dispatch</span>\n                    <span>&bull;</span>\n                    <span className=\"inline-flex items-center gap-1\">\n                      <Clock className=\"size-3\" aria-hidden=\"true\" />\n                      {featuredPost.readTime}\n                    </span>\n                  </div>\n\n                  <h3 className=\"text-foreground group-hover/hero:text-primary text-2xl leading-tight font-bold tracking-tight transition-colors sm:text-3xl\">\n                    <a href=\"#\" className=\"focus-visible:ring-ring focus-visible:underline focus-visible:outline-none\">\n                      {featuredPost.title}\n                    </a>\n                  </h3>\n\n                  <p className=\"text-muted-foreground line-clamp-4 text-sm leading-relaxed sm:text-base\">\n                    {featuredPost.excerpt}\n                  </p>\n                </div>\n\n                <div className=\"space-y-5 pt-2\">\n                  <Separator className=\"bg-border/60\" />\n                  <div className=\"flex flex-wrap items-center justify-between gap-4\">\n                    <div className=\"flex items-center gap-3\">\n                      <Avatar size=\"default\" className=\"border-border/60 border shadow-xs\">\n                        <AvatarImage src={featuredPost.author.avatar} alt={featuredPost.author.name} />\n                        <AvatarFallback text={featuredPost.author.initials} />\n                      </Avatar>\n                      <div>\n                        <p className=\"text-foreground text-sm font-semibold\">{featuredPost.author.name}</p>\n                        <p className=\"text-muted-foreground text-xs\">\n                          {featuredPost.date} &middot; {featuredPost.author.role}\n                        </p>\n                      </div>\n                    </div>\n\n                    <Button size=\"sm\" className=\"group/btn gap-1.5\">\n                      <span>Read Article</span>\n                      <ArrowRight\n                        className=\"size-3.5 transition-transform duration-200 group-hover/btn:translate-x-1\"\n                        aria-hidden=\"true\"\n                      />\n                    </Button>\n                  </div>\n                </div>\n              </CardContent>\n            </div>\n          </Card>\n        )}\n\n        {/* 3-Column Article Card Grid */}\n        {visiblePosts.length > 0 ? (\n          <div className=\"grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3\">\n            {visiblePosts.map((post) => (\n              <Card\n                key={post.id}\n                className=\"group border-border bg-card hover:border-primary/30 flex flex-col justify-between overflow-hidden shadow-xs transition-colors duration-300 hover:shadow-md\"\n              >\n                {/* Article Image Placeholder with 16:9 Aspect */}\n                <div className=\"bg-muted relative aspect-video w-full overflow-hidden\">\n                  <img\n                    src={post.image}\n                    alt={post.title}\n                    loading=\"lazy\"\n                    className=\"size-full object-cover transition-transform duration-500 ease-out group-hover:scale-105\"\n                  />\n                  <div className=\"absolute top-3 left-3\">\n                    <Badge\n                      variant=\"secondary\"\n                      className=\"bg-background/85 text-foreground border-border/50 text-xs font-medium shadow-xs backdrop-blur-md\"\n                    >\n                      {post.category}\n                    </Badge>\n                  </div>\n                </div>\n\n                {/* Article Body */}\n                <div className=\"flex flex-1 flex-col justify-between space-y-4 p-5 sm:p-6\">\n                  <div className=\"space-y-2.5\">\n                    <h3 className=\"text-foreground group-hover:text-primary line-clamp-2 text-lg font-semibold tracking-tight transition-colors\">\n                      <a\n                        href=\"#\"\n                        className=\"focus-visible:ring-ring focus-visible:underline focus-visible:outline-none\"\n                      >\n                        {post.title}\n                      </a>\n                    </h3>\n                    <p className=\"text-muted-foreground line-clamp-2 text-sm leading-relaxed\">{post.excerpt}</p>\n                  </div>\n\n                  {/* Author Row & Reading Time Pill */}\n                  <div className=\"space-y-4 pt-2\">\n                    <Separator className=\"bg-border/60\" />\n                    <div className=\"flex items-center justify-between gap-3\">\n                      <div className=\"flex min-w-0 items-center gap-2.5\">\n                        <Avatar size=\"sm\" className=\"border-border/60 border\">\n                          <AvatarImage src={post.author.avatar} alt={post.author.name} />\n                          <AvatarFallback text={post.author.initials} />\n                        </Avatar>\n                        <div className=\"min-w-0 flex-1\">\n                          <p className=\"text-foreground truncate text-xs font-medium\">{post.author.name}</p>\n                          <p className=\"text-muted-foreground truncate text-xs\">{post.date}</p>\n                        </div>\n                      </div>\n\n                      <div className=\"text-muted-foreground bg-muted/60 flex shrink-0 items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium\">\n                        <Clock className=\"size-3\" aria-hidden=\"true\" />\n                        <span>{post.readTime}</span>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </Card>\n            ))}\n          </div>\n        ) : (\n          /* Empty State When No Posts Match Category */\n          <div className=\"border-border bg-card/40 space-y-3 rounded-xl border border-dashed p-10 text-center\">\n            <div className=\"bg-muted text-muted-foreground mx-auto flex size-12 items-center justify-center rounded-full\">\n              <BookOpen className=\"size-6\" aria-hidden=\"true\" />\n            </div>\n            <h3 className=\"text-foreground text-base font-semibold\">No articles found</h3>\n            <p className=\"text-muted-foreground text-sm\">\n              There are no articles published yet under &ldquo;{activeCategory}&rdquo;.\n            </p>\n            <Button variant=\"outline\" size=\"sm\" onClick={() => setActiveCategory('All')}>\n              Show all articles\n            </Button>\n          </div>\n        )}\n\n        {/* Load More Stories Button */}\n        {!showAllPosts && visiblePosts.length > 0 && (\n          <div className=\"flex justify-center pt-2\">\n            <Button\n              variant=\"outline\"\n              size=\"lg\"\n              className=\"gap-2 px-8 font-medium shadow-xs\"\n              disabled={isLoadingMore}\n              onClick={handleLoadMore}\n            >\n              {isLoadingMore && <Loader2 className=\"size-4 animate-spin\" aria-hidden=\"true\" />}\n              <span>{isLoadingMore ? 'Loading articles...' : 'Load More Stories'}</span>\n            </Button>\n          </div>\n        )}\n\n        {/* Newsletter Subscribe Box */}\n        <Card className=\"border-border bg-card/60 relative overflow-hidden shadow-xs\">\n          <CardContent className=\"relative z-10 flex flex-col items-center justify-between gap-8 p-8 sm:p-10 md:flex-row\">\n            <div className=\"max-w-xl space-y-2 text-center md:text-left\">\n              <div className=\"bg-primary/10 text-primary mx-auto flex size-10 items-center justify-center rounded-lg md:mx-0\">\n                <Mail className=\"size-5\" aria-hidden=\"true\" />\n              </div>\n              <h3 className=\"text-foreground text-2xl font-bold tracking-tight\">Subscribe to our editorial dispatch</h3>\n              <p className=\"text-muted-foreground text-sm leading-relaxed\">\n                Get weekly teardowns on frontend architecture, component design systems, and web performance delivered\n                straight to your inbox.\n              </p>\n            </div>\n\n            <div className=\"w-full max-w-md\">\n              {!isSubscribed ? (\n                <form className=\"flex flex-col gap-2 sm:flex-row\" onSubmit={handleSubscribe}>\n                  <Input\n                    value={emailInput}\n                    onChange={(e) => setEmailInput(e.target.value)}\n                    type=\"email\"\n                    placeholder=\"name@company.com\"\n                    required\n                    aria-label=\"Email address\"\n                    className=\"bg-background h-10 shadow-xs\"\n                  />\n                  <Button type=\"submit\" className=\"shrink-0\">\n                    Subscribe\n                  </Button>\n                </form>\n              ) : (\n                <div className=\"bg-primary/10 border-primary/20 text-foreground flex items-center gap-3 rounded-lg border p-3.5 text-sm font-medium\">\n                  <Check className=\"text-primary size-5 shrink-0\" aria-hidden=\"true\" />\n                  <span>You're subscribed! Check your inbox for the welcome issue.</span>\n                </div>\n              )}\n              {!isSubscribed && (\n                <p className=\"text-muted-foreground mt-2 text-center text-xs sm:text-left\">\n                  No spam. Sent bi-weekly. Unsubscribe with one click.\n                </p>\n              )}\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/BlogPostCardGrid.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/avatar.json",
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/input.json",
    "https://uipkge.dev/r/react/separator.json"
  ],
  "description": "Modern editorial magazine 3-column article card grid with featured hero story, reading time pills, category filter badges, author avatars, and newsletter subscription box.",
  "categories": [
    "media",
    "marketing",
    "content"
  ]
}