{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "blog-post-card-grid",
  "title": "Blog Post Card Grid",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/blog-post-card-grid/BlogPostCardGrid.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport { ArrowRight, BookOpen, Check, Clock, Loader2, Mail, Star } from 'lucide-vue-next'\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\ninterface Props {\n  showFeatured?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  showFeatured: true,\n})\n\nconst categories = ['All', 'Architecture', 'Design Systems', 'Performance', 'Tutorials'] as const\ntype Category = (typeof categories)[number]\n\nconst activeCategory = ref<Category>('All')\nconst isLoadingMore = ref(false)\nconst showAllPosts = ref(false)\nconst emailInput = ref('')\nconst isSubscribed = ref(false)\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\nconst visiblePosts = computed(() => {\n  const pool = showAllPosts.value ? [...initialPosts, ...additionalPosts] : initialPosts\n  if (activeCategory.value === 'All') return pool\n  return pool.filter((p) => p.category === activeCategory.value)\n})\n\nconst isHeroVisible = computed(() => {\n  if (!props.showFeatured) return false\n  return activeCategory.value === 'All' || featuredPost.category === activeCategory.value\n})\n\nfunction handleCategorySelect(cat: Category) {\n  activeCategory.value = cat\n}\n\nfunction handleLoadMore() {\n  isLoadingMore.value = true\n  setTimeout(() => {\n    showAllPosts.value = true\n    isLoadingMore.value = false\n  }, 400)\n}\n\nfunction handleSubscribe() {\n  if (!emailInput.value.includes('@')) return\n  isSubscribed.value = true\n}\n</script>\n\n<template>\n  <section data-slot=\"blog-post-card-grid\" :class=\"cn('bg-background w-full py-12 sm:py-16 lg:py-20', props.class)\">\n    <div class=\"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 class=\"flex flex-col items-start justify-between gap-6 md:flex-row md:items-end\">\n        <div class=\"max-w-2xl space-y-3\">\n          <Badge variant=\"outline\" class=\"gap-1.5 px-3 py-1 text-xs font-medium\">\n            <BookOpen class=\"text-primary size-3.5\" aria-hidden=\"true\" />\n            From the Blog\n          </Badge>\n          <h2 class=\"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 class=\"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 class=\"flex flex-wrap items-center gap-2\" role=\"tablist\" aria-label=\"Article categories\">\n          <Button\n            v-for=\"cat in categories\"\n            :key=\"cat\"\n            size=\"sm\"\n            :variant=\"activeCategory === cat ? 'default' : 'outline'\"\n            class=\"rounded-full text-xs font-medium\"\n            :aria-selected=\"activeCategory === cat\"\n            role=\"tab\"\n            @click=\"handleCategorySelect(cat)\"\n          >\n            {{ cat }}\n          </Button>\n        </div>\n      </div>\n\n      <!-- Featured Hero Article Card -->\n      <Card\n        v-if=\"isHeroVisible\"\n        class=\"group/hero border-border bg-card hover:border-primary/30 relative overflow-hidden shadow-xs transition-colors duration-300 hover:shadow-md\"\n      >\n        <div class=\"grid grid-cols-1 lg:grid-cols-12\">\n          <!-- Hero Image Container -->\n          <div\n            class=\"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          >\n            <img\n              :src=\"featuredPost.image\"\n              :alt=\"featuredPost.title\"\n              class=\"size-full object-cover transition-transform duration-500 ease-out group-hover/hero:scale-105\"\n            />\n            <div class=\"absolute top-4 left-4 flex flex-wrap items-center gap-2\">\n              <Badge class=\"bg-primary text-primary-foreground border-transparent text-xs font-medium shadow-xs\">\n                <Star class=\"mr-1 size-3\" aria-hidden=\"true\" />\n                Featured Story\n              </Badge>\n              <Badge\n                variant=\"secondary\"\n                class=\"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 class=\"flex flex-col justify-between space-y-6 p-6 sm:p-8 lg:col-span-5 lg:p-10\">\n            <div class=\"space-y-4\">\n              <div class=\"text-muted-foreground flex items-center gap-2 text-xs font-medium\">\n                <BookOpen class=\"text-primary size-3.5\" aria-hidden=\"true\" />\n                <span>Editorial Dispatch</span>\n                <span>&bull;</span>\n                <span class=\"inline-flex items-center gap-1\">\n                  <Clock class=\"size-3\" aria-hidden=\"true\" />\n                  {{ featuredPost.readTime }}\n                </span>\n              </div>\n\n              <h3\n                class=\"text-foreground group-hover/hero:text-primary text-2xl leading-tight font-bold tracking-tight transition-colors sm:text-3xl\"\n              >\n                <a href=\"#\" class=\"focus-visible:ring-ring focus-visible:underline focus-visible:outline-none\">\n                  {{ featuredPost.title }}\n                </a>\n              </h3>\n\n              <p class=\"text-muted-foreground line-clamp-4 text-sm leading-relaxed sm:text-base\">\n                {{ featuredPost.excerpt }}\n              </p>\n            </div>\n\n            <div class=\"space-y-5 pt-2\">\n              <Separator class=\"bg-border/60\" />\n              <div class=\"flex flex-wrap items-center justify-between gap-4\">\n                <div class=\"flex items-center gap-3\">\n                  <Avatar size=\"default\" class=\"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 class=\"text-foreground text-sm font-semibold\">{{ featuredPost.author.name }}</p>\n                    <p class=\"text-muted-foreground text-xs\">\n                      {{ featuredPost.date }} &middot; {{ featuredPost.author.role }}\n                    </p>\n                  </div>\n                </div>\n\n                <Button size=\"sm\" class=\"group/btn gap-1.5\">\n                  <span>Read Article</span>\n                  <ArrowRight\n                    class=\"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      <!-- 3-Column Article Card Grid -->\n      <div v-if=\"visiblePosts.length > 0\" class=\"grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3\">\n        <Card\n          v-for=\"post in visiblePosts\"\n          :key=\"post.id\"\n          class=\"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 class=\"bg-muted relative aspect-video w-full overflow-hidden\">\n            <img\n              :src=\"post.image\"\n              :alt=\"post.title\"\n              loading=\"lazy\"\n              class=\"size-full object-cover transition-transform duration-500 ease-out group-hover:scale-105\"\n            />\n            <div class=\"absolute top-3 left-3\">\n              <Badge\n                variant=\"secondary\"\n                class=\"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 class=\"flex flex-1 flex-col justify-between space-y-4 p-5 sm:p-6\">\n            <div class=\"space-y-2.5\">\n              <h3\n                class=\"text-foreground group-hover:text-primary line-clamp-2 text-lg font-semibold tracking-tight transition-colors\"\n              >\n                <a href=\"#\" class=\"focus-visible:ring-ring focus-visible:underline focus-visible:outline-none\">\n                  {{ post.title }}\n                </a>\n              </h3>\n              <p class=\"text-muted-foreground line-clamp-2 text-sm leading-relaxed\">\n                {{ post.excerpt }}\n              </p>\n            </div>\n\n            <!-- Author Row & Reading Time Pill -->\n            <div class=\"space-y-4 pt-2\">\n              <Separator class=\"bg-border/60\" />\n              <div class=\"flex items-center justify-between gap-3\">\n                <div class=\"flex min-w-0 items-center gap-2.5\">\n                  <Avatar size=\"sm\" class=\"border-border/60 border\">\n                    <AvatarImage :src=\"post.author.avatar\" :alt=\"post.author.name\" />\n                    <AvatarFallback :text=\"post.author.initials\" />\n                  </Avatar>\n                  <div class=\"min-w-0 flex-1\">\n                    <p class=\"text-foreground truncate text-xs font-medium\">{{ post.author.name }}</p>\n                    <p class=\"text-muted-foreground truncate text-xs\">{{ post.date }}</p>\n                  </div>\n                </div>\n\n                <div\n                  class=\"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                >\n                  <Clock class=\"size-3\" aria-hidden=\"true\" />\n                  <span>{{ post.readTime }}</span>\n                </div>\n              </div>\n            </div>\n          </div>\n        </Card>\n      </div>\n\n      <!-- Empty State When No Posts Match Category -->\n      <div v-else class=\"border-border bg-card/40 space-y-3 rounded-xl border border-dashed p-10 text-center\">\n        <div class=\"bg-muted text-muted-foreground mx-auto flex size-12 items-center justify-center rounded-full\">\n          <BookOpen class=\"size-6\" aria-hidden=\"true\" />\n        </div>\n        <h3 class=\"text-foreground text-base font-semibold\">No articles found</h3>\n        <p class=\"text-muted-foreground text-sm\">\n          There are no articles published yet under &ldquo;{{ activeCategory }}&rdquo;.\n        </p>\n        <Button variant=\"outline\" size=\"sm\" @click=\"activeCategory = 'All'\"> Show all articles </Button>\n      </div>\n\n      <!-- Load More Stories Button -->\n      <div v-if=\"!showAllPosts && visiblePosts.length > 0\" class=\"flex justify-center pt-2\">\n        <Button\n          variant=\"outline\"\n          size=\"lg\"\n          class=\"gap-2 px-8 font-medium shadow-xs\"\n          :disabled=\"isLoadingMore\"\n          @click=\"handleLoadMore\"\n        >\n          <Loader2 v-if=\"isLoadingMore\" class=\"size-4 animate-spin\" aria-hidden=\"true\" />\n          <span>{{ isLoadingMore ? 'Loading articles...' : 'Load More Stories' }}</span>\n        </Button>\n      </div>\n\n      <!-- Newsletter Subscribe Box -->\n      <Card class=\"border-border bg-card/60 relative overflow-hidden shadow-xs\">\n        <CardContent class=\"relative z-10 flex flex-col items-center justify-between gap-8 p-8 sm:p-10 md:flex-row\">\n          <div class=\"max-w-xl space-y-2 text-center md:text-left\">\n            <div class=\"bg-primary/10 text-primary mx-auto flex size-10 items-center justify-center rounded-lg md:mx-0\">\n              <Mail class=\"size-5\" aria-hidden=\"true\" />\n            </div>\n            <h3 class=\"text-foreground text-2xl font-bold tracking-tight\">Subscribe to our editorial dispatch</h3>\n            <p class=\"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 class=\"w-full max-w-md\">\n            <template v-if=\"!isSubscribed\">\n              <form class=\"flex flex-col gap-2 sm:flex-row\" @submit.prevent=\"handleSubscribe\">\n                <Input\n                  v-model=\"emailInput\"\n                  type=\"email\"\n                  placeholder=\"name@company.com\"\n                  required\n                  aria-label=\"Email address\"\n                  class=\"bg-background h-10 shadow-xs\"\n                />\n                <Button type=\"submit\" class=\"shrink-0\"> Subscribe </Button>\n              </form>\n              <p class=\"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            </template>\n            <div\n              v-else\n              class=\"bg-primary/10 border-primary/20 text-foreground flex items-center gap-3 rounded-lg border p-3.5 text-sm font-medium\"\n            >\n              <Check class=\"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          </div>\n        </CardContent>\n      </Card>\n    </div>\n  </section>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/BlogPostCardGrid.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/avatar.json",
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/input.json",
    "https://uipkge.dev/r/vue/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"
  ]
}