{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "post-list",
  "version": "3.1.0",
  "category": "blogging",
  "meta": {
    "preview": "https://ui.manifest.build/previews/post-list.png",
    "version": "3.1.0",
    "changelog": {
      "1.0.0": "Initial release with list, grid and carousel variants",
      "1.1.0": "Added fullwidth variant with pagination for fullscreen mode",
      "2.0.0": "BREAKING: Removed id from Post interface. Use array index for key.",
      "2.0.1": "Added aria-labels to pagination and carousel navigation buttons for accessibility",
      "2.0.2": "Moved demo data to separate file for cleaner component code",
      "2.0.3": "Added comprehensive JSDoc documentation",
      "2.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "2.0.5": "Fixed defensive property access to handle empty objects and null values",
      "2.0.6": "Fixed circular dependency by moving Post interface to types.ts",
      "2.0.7": "Added bg-card background and margin to list variant for better dark/light mode support",
      "3.0.0": "BREAKING: Removed pagination (onPageChange, postsPerPage, control). Fullwidth variant now renders all posts.",
      "3.0.1": "Removed default content data - component only renders explicitly provided data",
      "3.0.2": "Fixed missing tooltip dependency for shadcn CLI installation",
      "3.0.3": "Replaced key={index} with stable content-based keys",
      "3.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with list, grid and carousel variants",
    "1.1.0": "Added fullwidth variant with pagination for fullscreen mode",
    "2.0.0": "BREAKING: Removed id from Post interface. Use array index for key.",
    "2.0.1": "Added aria-labels to pagination and carousel navigation buttons for accessibility",
    "2.0.2": "Moved demo data to separate file for cleaner component code",
    "2.0.3": "Added comprehensive JSDoc documentation",
    "2.0.4": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "2.0.5": "Fixed defensive property access to handle empty objects and null values",
    "2.0.6": "Fixed circular dependency by moving Post interface to types.ts",
    "2.0.7": "Added bg-card background and margin to list variant for better dark/light mode support",
    "3.0.0": "BREAKING: Removed pagination (onPageChange, postsPerPage, control). Fullwidth variant now renders all posts.",
    "3.0.1": "Removed default content data - component only renders explicitly provided data",
    "3.0.2": "Fixed missing tooltip dependency for shadcn CLI installation",
    "3.0.3": "Replaced key={index} with stable content-based keys",
    "3.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
  },
  "title": "Post List",
  "author": "MNFST, Inc",
  "description": "Post list with list, grid, carousel, and fullwidth variants. Fullwidth mode shows paginated posts.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "tooltip",
    "https://ui.manifest.build/r/manifest-types.json"
  ],
  "files": [
    {
      "path": "registry/blogging/post-list.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\nimport { cn } from '@/lib/utils'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { useState } from 'react'\nimport type { Post } from './types'\nimport { PostCard } from './post-card'\nimport { demoPosts } from './demo/blogging'\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * PostListProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the PostList component, a blog post list with multiple layout variants.\n */\nexport interface PostListProps {\n  data?: {\n    /** Array of blog posts to display. */\n    posts?: Post[]\n  }\n  actions?: {\n    /** Called when the read more button is clicked on a post. */\n    onReadMore?: (post: Post) => void\n  }\n  appearance?: {\n    /**\n     * Layout variant for the post list.\n     * @default \"list\"\n     */\n    variant?: 'list' | 'grid' | 'carousel' | 'fullwidth'\n    /**\n     * Number of columns for grid and fullwidth variants.\n     * @default 2\n     */\n    columns?: 2 | 3 | 4\n    /**\n     * Whether to show author information on post cards.\n     * @default true\n     */\n    showAuthor?: boolean\n    /**\n     * Whether to show category labels on post cards.\n     * @default true\n     */\n    showCategory?: boolean\n  }\n}\n\n/**\n * A blog post list component with multiple layout variants.\n * Supports list, grid, carousel, and fullwidth paginated modes.\n *\n * Features:\n * - Four layout variants (list, grid, carousel, fullwidth)\n * - Responsive grid columns\n * - Carousel with touch-friendly navigation\n * - Fullwidth mode with pagination\n * - Configurable author and category display\n *\n * @component\n * @example\n * ```tsx\n * <PostList\n *   data={{\n *     posts: [\n *       {\n *         title: \"Getting Started Guide\",\n *         excerpt: \"Learn the basics...\",\n *         coverImage: \"https://example.com/image.jpg\",\n *         author: { name: \"Sarah Chen\" },\n *         publishedAt: \"2024-01-15\"\n *       }\n *     ]\n *   }}\n *   appearance={{\n *     variant: \"grid\",\n *     columns: 3,\n *     showAuthor: true\n *   }}\n *   actions={{\n *     onReadMore: (post) => console.log(\"Read:\", post.title)\n *   }}\n * />\n * ```\n */\nexport function PostList({ data, actions, appearance }: PostListProps) {\n  const resolved: NonNullable<PostListProps['data']> = data ?? { posts: demoPosts }\n  const posts = resolved.posts ?? []\n  const onReadMore = actions?.onReadMore\n  const variant = appearance?.variant ?? 'list'\n  const columns = appearance?.columns ?? 2\n  const showAuthor = appearance?.showAuthor ?? true\n  const showCategory = appearance?.showCategory ?? true\n  const [currentIndex, setCurrentIndex] = useState(0)\n\n  // List variant\n  if (variant === 'list') {\n    return (\n      <div className=\"space-y-3 m-3 bg-card rounded-lg p-3\">\n        {posts.slice(0, 3).map((post) => (\n          <PostCard\n            key={post.title || post.url}\n            data={{ post }}\n            appearance={{ variant: \"horizontal\", showAuthor, showCategory }}\n            actions={{ onReadMore }}\n          />\n        ))}\n      </div>\n    )\n  }\n\n  // Grid variant (inline mode - show only 4 posts)\n  if (variant === 'grid') {\n    return (\n      <div\n        className={cn(\n          'grid gap-4 grid-cols-1',\n          columns === 2 ? 'sm:grid-cols-2' : 'sm:grid-cols-3'\n        )}\n      >\n        {posts.slice(0, 4).map((post) => (\n          <PostCard\n            key={post.title || post.url}\n            data={{ post }}\n            appearance={{ variant: \"compact\", showImage: false, showAuthor, showCategory }}\n            actions={{ onReadMore }}\n          />\n        ))}\n      </div>\n    )\n  }\n\n  // Fullwidth variant\n  if (variant === 'fullwidth') {\n    const getGridColsClass = () => {\n      switch (columns) {\n        case 2:\n          return 'sm:grid-cols-2'\n        case 3:\n          return 'sm:grid-cols-2 lg:grid-cols-3'\n        case 4:\n          return 'sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'\n        default:\n          return 'sm:grid-cols-2'\n      }\n    }\n\n    return (\n      <div className=\"space-y-6 p-6\">\n        <div className={cn('grid gap-6 grid-cols-1', getGridColsClass())}>\n          {posts.map((post) => (\n            <PostCard\n              key={post.title || post.url}\n              data={{ post }}\n              appearance={{ variant: \"default\", showAuthor, showCategory }}\n              actions={{ onReadMore }}\n            />\n          ))}\n        </div>\n      </div>\n    )\n  }\n\n  // Carousel variant\n  const maxIndexMobile = posts.length - 1\n  const maxIndexTablet = Math.max(0, posts.length - 2)\n  const maxIndexDesktop = Math.max(0, posts.length - 3)\n\n  const prev = () => {\n    setCurrentIndex((i) => Math.max(0, i - 1))\n  }\n\n  const next = () => {\n    setCurrentIndex((i) => i + 1)\n  }\n\n  const isAtStart = currentIndex === 0\n  const isAtEndMobile = currentIndex >= maxIndexMobile\n  const isAtEndTablet = currentIndex >= maxIndexTablet\n  const isAtEndDesktop = currentIndex >= maxIndexDesktop\n\n  return (\n    <div className=\"relative\">\n      <div className=\"overflow-hidden rounded-lg\">\n        {/* Mobile: 1 card, slides by 100% */}\n        <div\n          className=\"flex transition-transform duration-300 ease-out md:hidden\"\n          style={{ transform: `translateX(-${currentIndex * 100}%)` }}\n        >\n          {posts.map((post) => (\n            <div key={post.title || post.url} className=\"w-full shrink-0 px-0.5\">\n              <PostCard\n                data={{ post }}\n                appearance={{ variant: \"compact\", showAuthor, showCategory }}\n                actions={{ onReadMore }}\n              />\n            </div>\n          ))}\n        </div>\n\n        {/* Tablet: 2 cards visible, slides by 50% */}\n        <div\n          className=\"hidden md:flex lg:hidden transition-transform duration-300 ease-out\"\n          style={{ transform: `translateX(-${currentIndex * 50}%)` }}\n        >\n          {posts.map((post) => (\n            <div key={post.title || post.url} className=\"w-1/2 shrink-0 px-1.5\">\n              <PostCard\n                data={{ post }}\n                appearance={{ variant: \"compact\", showAuthor, showCategory }}\n                actions={{ onReadMore }}\n              />\n            </div>\n          ))}\n        </div>\n\n        {/* Desktop: 3 cards visible, slides by 33.333% */}\n        <div\n          className=\"hidden lg:flex transition-transform duration-300 ease-out\"\n          style={{ transform: `translateX(-${currentIndex * (100 / 3)}%)` }}\n        >\n          {posts.map((post) => (\n            <div key={post.title || post.url} className=\"w-1/3 shrink-0 px-1.5\">\n              <PostCard\n                data={{ post }}\n                appearance={{ variant: \"compact\", showAuthor, showCategory }}\n                actions={{ onReadMore }}\n              />\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className=\"mt-3 flex items-center justify-between px-2\">\n        <div className=\"flex gap-1\">\n          {posts.map((_, i) => (\n            <button\n              key={i}\n              onClick={() => setCurrentIndex(i)}\n              aria-label={`Go to slide ${i + 1}`}\n              className={cn(\n                'h-1.5 rounded-full transition-all cursor-pointer',\n                i === currentIndex\n                  ? 'w-4 bg-foreground'\n                  : 'w-1.5 bg-muted-foreground/30'\n              )}\n            />\n          ))}\n        </div>\n        {/* Mobile navigation */}\n        <div className=\"flex gap-1 md:hidden\">\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={prev}\n            disabled={isAtStart}\n            aria-label=\"Previous post\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={next}\n            disabled={isAtEndMobile}\n            aria-label=\"Next post\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n        {/* Tablet navigation */}\n        <div className=\"hidden md:flex lg:hidden gap-1\">\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={prev}\n            disabled={isAtStart}\n            aria-label=\"Previous post\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={next}\n            disabled={isAtEndTablet}\n            aria-label=\"Next post\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n        {/* Desktop navigation */}\n        <div className=\"hidden lg:flex gap-1\">\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={prev}\n            disabled={isAtStart}\n            aria-label=\"Previous post\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n          </Button>\n          <Button\n            variant=\"outline\"\n            size=\"icon\"\n            className=\"h-8 w-8\"\n            onClick={next}\n            disabled={isAtEndDesktop}\n            aria-label=\"Next post\"\n          >\n            <ChevronRight className=\"h-4 w-4\" />\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/post-list.tsx"
    },
    {
      "path": "registry/blogging/post-card.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\n\n// Import types from shared types file to avoid circular dependencies\nimport type { Post } from './types'\n// Re-export for backward compatibility\nexport type { Post } from './types'\n\nimport { demoPost } from './demo/blogging'\n\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * PostCardProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the PostCard component, a blog post card with multiple layout variants.\n */\nexport interface PostCardProps {\n  data?: {\n    /** The blog post to display. */\n    post?: Post\n  }\n  actions?: {\n    /** Called when the read more button is clicked. */\n    onReadMore?: (post: Post) => void\n  }\n  appearance?: {\n    /**\n     * Card layout variant.\n     * @default \"default\"\n     */\n    variant?: 'default' | 'compact' | 'horizontal' | 'covered'\n    /**\n     * Whether to show the cover image.\n     * @default true\n     */\n    showImage?: boolean\n    /**\n     * Whether to show author information.\n     * @default true\n     */\n    showAuthor?: boolean\n    /**\n     * Whether to show the category label.\n     * @default true\n     */\n    showCategory?: boolean\n  }\n}\n\n/**\n * A blog post card component with multiple layout variants.\n * Supports default, compact, horizontal, and covered (overlay) styles.\n *\n * Features:\n * - Four layout variants (default, compact, horizontal, covered)\n * - Cover image with hover zoom effect\n * - Author avatar and info display\n * - Category and tags display\n * - Read more action button\n * - Responsive design\n *\n * @component\n * @example\n * ```tsx\n * <PostCard\n *   data={{\n *     post: {\n *       title: \"Getting Started Guide\",\n *       excerpt: \"Learn the basics of our component library.\",\n *       coverImage: \"https://example.com/image.jpg\",\n *       author: { name: \"Sarah Chen\", avatar: \"https://example.com/avatar.jpg\" },\n *       publishedAt: \"2024-01-15\",\n *       readTime: \"5 min read\",\n *       tags: [\"Tutorial\", \"Components\"],\n *       category: \"Tutorial\"\n *     }\n *   }}\n *   actions={{\n *     onReadMore: (post) => console.log(\"Read more:\", post.title)\n *   }}\n *   appearance={{\n *     variant: \"default\",\n *     showImage: true,\n *     showAuthor: true,\n *     showCategory: true\n *   }}\n * />\n * ```\n */\nexport function PostCard({ data, actions, appearance }: PostCardProps) {\n  const resolved: NonNullable<PostCardProps['data']> = data ?? { post: demoPost }\n  const post = resolved.post\n  if (!post) {\n    return null\n  }\n  const onReadMore = actions?.onReadMore\n  const variant = appearance?.variant ?? 'default'\n  const showImage = appearance?.showImage ?? true\n  const showAuthor = appearance?.showAuthor ?? true\n  const showCategory = appearance?.showCategory ?? true\n\n  // Handle \"Read more\" click - only call callback if provided, otherwise do nothing\n  // This lets users decide to use an external link or open fullscreen mode\n  const handleReadMore = () => {\n    if (onReadMore) {\n      onReadMore(post)\n    }\n  }\n\n  const formatDate = (dateStr: string) => {\n    return new Date(dateStr).toLocaleDateString('en-US', {\n      month: 'short',\n      day: 'numeric',\n      year: 'numeric'\n    })\n  }\n\n  if (variant === 'covered') {\n    return (\n      <div className=\"relative overflow-hidden rounded-lg border\">\n        <div className=\"min-h-[280px] sm:aspect-[16/9] sm:min-h-0 w-full\">\n          {post.coverImage ? (\n            <img\n              src={post.coverImage}\n              alt={post.title || ''}\n              className=\"absolute inset-0 h-full w-full object-cover\"\n            />\n          ) : (\n            <div className=\"absolute inset-0 h-full w-full bg-muted\" />\n          )}\n        </div>\n        {/* Minimal overlay - solid color instead of gradient per ChatGPT guidelines */}\n        <div className=\"absolute inset-0 bg-black/60\" />\n        <div className=\"absolute inset-0 flex flex-col justify-end p-4 text-white\">\n          <div>\n            {showCategory && post.category && (\n              <p className=\"text-[10px] font-medium uppercase tracking-wide text-white/70\">\n                {post.category}\n              </p>\n            )}\n            {post.title && (\n              <h2 className=\"mt-1 text-lg font-semibold leading-tight\">\n                {post.title}\n              </h2>\n            )}\n            {post.excerpt && (\n              <p className=\"mt-1 line-clamp-2 text-sm text-white/80\">\n                {post.excerpt}\n              </p>\n            )}\n            {post.tags && post.tags.length > 0 && (\n              <div className=\"mt-2 flex flex-wrap gap-1\">\n                {post.tags.slice(0, 2).map((tag) => (\n                  <span\n                    key={tag}\n                    className=\"rounded-md bg-white/20 px-2 py-0.5 text-xs\"\n                  >\n                    {tag}\n                  </span>\n                ))}\n              </div>\n            )}\n            <div className=\"mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n              {showAuthor && (\n                <div className=\"flex items-center gap-2\">\n                  {post.author?.avatar && (\n                    <img\n                      src={post.author.avatar}\n                      alt={post.author?.name || ''}\n                      className=\"h-6 w-6 rounded-full ring-1 ring-white/30\"\n                    />\n                  )}\n                  <div className=\"text-xs\">\n                    {post.author?.name && (\n                      <p className=\"font-medium\">{post.author.name}</p>\n                    )}\n                    {post.publishedAt && (\n                      <p className=\"text-white/60\">\n                        {formatDate(post.publishedAt)}\n                      </p>\n                    )}\n                  </div>\n                </div>\n              )}\n              <Button\n                size=\"sm\"\n                variant=\"secondary\"\n                className=\"w-full sm:w-auto\"\n                onClick={handleReadMore}\n              >\n                Read article\n              </Button>\n            </div>\n          </div>\n        </div>\n      </div>\n    )\n  }\n\n  if (variant === 'horizontal') {\n    return (\n      <div className=\"flex flex-col sm:flex-row gap-4 rounded-lg border bg-card p-3\">\n        {showImage && post.coverImage && (\n          <div className=\"aspect-video sm:aspect-square sm:h-24 sm:w-24 shrink-0 overflow-hidden rounded-md\">\n            <img\n              src={post.coverImage}\n              alt={post.title || ''}\n              className=\"h-full w-full object-cover\"\n            />\n          </div>\n        )}\n        <div className=\"flex flex-1 flex-col justify-between\">\n          <div>\n            {showCategory && post.category && (\n              <p className=\"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n                {post.category}\n              </p>\n            )}\n            {post.title && (\n              <h3 className=\"line-clamp-2 text-sm font-medium leading-tight\">\n                {post.title}\n              </h3>\n            )}\n            {post.excerpt && (\n              <p className=\"mt-1 line-clamp-2 text-xs text-muted-foreground\">\n                {post.excerpt}\n              </p>\n            )}\n            {post.tags && post.tags.length > 0 && (\n              <div className=\"mt-1.5 flex flex-wrap gap-1\">\n                {post.tags.slice(0, 2).map((tag) => (\n                  <span\n                    key={tag}\n                    className=\"rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground\"\n                  >\n                    {tag}\n                  </span>\n                ))}\n              </div>\n            )}\n          </div>\n          <div className=\"mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n            <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n              {showAuthor && post.author?.avatar && (\n                <img\n                  src={post.author.avatar}\n                  alt={post.author?.name || ''}\n                  className=\"h-4 w-4 rounded-full\"\n                />\n              )}\n              {post.publishedAt && (\n                <span>{formatDate(post.publishedAt)}</span>\n              )}\n              {post.readTime && (\n                <>\n                  <span>·</span>\n                  <span>{post.readTime}</span>\n                </>\n              )}\n            </div>\n            <Button\n              size=\"sm\"\n              className=\"w-full sm:w-auto\"\n              onClick={handleReadMore}\n            >\n              Read\n            </Button>\n          </div>\n        </div>\n      </div>\n    )\n  }\n\n  if (variant === 'compact') {\n    return (\n      <div className=\"flex h-full flex-col justify-between rounded-lg border bg-card p-3\">\n        <div>\n          {showCategory && post.category && (\n            <p className=\"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n              {post.category}\n            </p>\n          )}\n          {post.title && (\n            <h3 className=\"line-clamp-2 text-sm font-medium\">{post.title}</h3>\n          )}\n          {post.excerpt && (\n            <p className=\"mt-1 line-clamp-2 text-xs text-muted-foreground\">\n              {post.excerpt}\n            </p>\n          )}\n          {post.tags && post.tags.length > 0 && (\n            <div className=\"mt-1.5 flex flex-wrap gap-1\">\n              {post.tags.slice(0, 2).map((tag) => (\n                <span\n                  key={tag}\n                  className=\"rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground\"\n                >\n                  {tag}\n                </span>\n              ))}\n            </div>\n          )}\n        </div>\n        <div className=\"mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n          <div className=\"flex items-center gap-2\">\n            {showAuthor && post.author?.avatar && (\n              <img\n                src={post.author.avatar}\n                alt={post.author?.name || ''}\n                className=\"h-5 w-5 rounded-full\"\n              />\n            )}\n            {post.publishedAt && (\n              <span className=\"text-xs text-muted-foreground\">\n                {formatDate(post.publishedAt)}\n              </span>\n            )}\n          </div>\n          <Button size=\"sm\" onClick={handleReadMore}>\n            Read more\n          </Button>\n        </div>\n      </div>\n    )\n  }\n\n  // Default variant\n  return (\n    <div className=\"flex h-full flex-col overflow-hidden rounded-lg border bg-card\">\n      {showImage && post.coverImage && (\n        <div className=\"aspect-video overflow-hidden\">\n          <img\n            src={post.coverImage}\n            alt={post.title || ''}\n            className=\"h-full w-full object-cover transition-transform hover:scale-105\"\n          />\n        </div>\n      )}\n      <div className=\"flex flex-1 flex-col justify-between p-4\">\n        <div>\n          {showCategory && post.category && (\n            <p className=\"mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n              {post.category}\n            </p>\n          )}\n          {post.title && (\n            <h3 className=\"line-clamp-2 font-medium\">{post.title}</h3>\n          )}\n          {post.excerpt && (\n            <p className=\"mt-2 line-clamp-2 text-sm text-muted-foreground\">\n              {post.excerpt}\n            </p>\n          )}\n          {post.tags && post.tags.length > 0 && (\n            <div className=\"mt-2 flex flex-wrap gap-1\">\n              {post.tags.slice(0, 2).map((tag) => (\n                <span\n                  key={tag}\n                  className=\"rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground\"\n                >\n                  {tag}\n                </span>\n              ))}\n            </div>\n          )}\n        </div>\n        <div className=\"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n          {showAuthor && (\n            <div className=\"flex items-center gap-2\">\n              {post.author?.avatar && (\n                <img\n                  src={post.author.avatar}\n                  alt={post.author?.name || ''}\n                  className=\"h-6 w-6 rounded-full\"\n                />\n              )}\n              <div className=\"text-xs\">\n                {post.author?.name && (\n                  <p className=\"font-medium\">{post.author.name}</p>\n                )}\n                {post.publishedAt && (\n                  <p className=\"text-muted-foreground\">\n                    {formatDate(post.publishedAt)}\n                  </p>\n                )}\n              </div>\n            </div>\n          )}\n          <Button size=\"sm\" onClick={handleReadMore}>\n            Read\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/post-card.tsx"
    },
    {
      "path": "registry/blogging/post-detail.tsx",
      "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';\nimport { Calendar, Clock, ExternalLink, Maximize2 } from 'lucide-react';\nimport { useMemo } from 'react';\nimport type { Post } from './types';\nimport { demoPostDetailData } from './demo/blogging';\n\n// DOM-based allowlist HTML sanitizer for post content\nconst ALLOWED_TAGS = new Set([\n  'p', 'br', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li',\n  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre',\n  'span', 'div', 'img', 'figure', 'figcaption', 'hr',\n]);\nconst ALLOWED_ATTRS: Record<string, Set<string>> = {\n  a: new Set(['href', 'target', 'rel', 'title']),\n  img: new Set(['src', 'alt', 'width', 'height']),\n  '*': new Set(['class', 'id']),\n};\nconst DANGEROUS_URL = /^\\s*(javascript|data):/i;\n\nfunction sanitizeNode(node: Node, doc: Document): void {\n  const children = Array.from(node.childNodes);\n  for (const child of children) {\n    if (child.nodeType === 3 /* TEXT */) continue;\n    if (child.nodeType !== 1 /* ELEMENT */) {\n      child.remove();\n      continue;\n    }\n    const el = child as Element;\n    const tag = el.tagName.toLowerCase();\n    if (!ALLOWED_TAGS.has(tag)) {\n      // Unwrap: keep text content, discard the tag\n      while (el.firstChild) node.insertBefore(el.firstChild, el);\n      el.remove();\n      continue;\n    }\n    // Strip disallowed attributes\n    const tagAllowed = ALLOWED_ATTRS[tag];\n    const globalAllowed = ALLOWED_ATTRS['*'];\n    for (const attr of Array.from(el.attributes)) {\n      const name = attr.name.toLowerCase();\n      if (!tagAllowed?.has(name) && !globalAllowed?.has(name)) {\n        el.removeAttribute(attr.name);\n      }\n    }\n    // Block dangerous URL schemes on href/src\n    for (const urlAttr of ['href', 'src']) {\n      const val = el.getAttribute(urlAttr);\n      if (val && DANGEROUS_URL.test(val)) el.removeAttribute(urlAttr);\n    }\n    sanitizeNode(el, doc);\n  }\n}\n\nfunction sanitizeHtml(html: string): string {\n  if (typeof document === 'undefined') return html;\n  const doc = new DOMParser().parseFromString(html, 'text/html');\n  sanitizeNode(doc.body, doc);\n  return doc.body.innerHTML;\n}\n\nfunction TagList({\n  tags,\n  maxVisible = 2,\n  size = 'default',\n}: {\n  tags: string[];\n  maxVisible?: number;\n  size?: 'small' | 'default';\n}) {\n  const visibleTags = tags.slice(0, maxVisible);\n  const remainingTags = tags.slice(maxVisible);\n  const hasMore = remainingTags.length > 0;\n\n  const tagClass =\n    size === 'small'\n      ? 'rounded-full bg-muted px-2.5 py-0.5 text-xs font-medium'\n      : 'rounded-full bg-muted px-3 py-1 text-xs font-medium';\n\n  return (\n    <>\n      {visibleTags.map((tag) => (\n        <span key={tag} className={tagClass}>\n          {tag}\n        </span>\n      ))}\n      {hasMore && (\n        <TooltipProvider delayDuration={0}>\n          <Tooltip>\n            <TooltipTrigger asChild>\n              <span className={`${tagClass} cursor-default`}>+{remainingTags.length}</span>\n            </TooltipTrigger>\n            <TooltipContent>\n              <p>{remainingTags.join(', ')}</p>\n            </TooltipContent>\n          </Tooltip>\n        </TooltipProvider>\n      )}\n    </>\n  );\n}\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * PostDetailProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for the PostDetail component, a full post detail view with Medium-style typography.\n */\nexport interface PostDetailProps {\n  data?: {\n    /** The main blog post to display. */\n    post?: Post;\n    /** HTML content of the post body. */\n    content?: string;\n    /** Related posts to show at the bottom of the article. */\n    relatedPosts?: Post[];\n  };\n  actions?: {\n    /** Called when the back button is clicked. */\n    onBack?: () => void;\n    /** Called when the read more button is clicked (inline mode). */\n    onReadMore?: () => void;\n    /** Called when a related post is clicked. */\n    onReadRelated?: (post: Post) => void;\n  };\n  appearance?: {\n    /**\n     * Whether to show the cover image.\n     * @default true\n     */\n    showCover?: boolean;\n    /**\n     * Whether to show author information.\n     * @default true\n     */\n    showAuthor?: boolean;\n    /**\n     * Display mode for the component.\n     * - inline: Compact card view with truncated content\n     * - pip: Picture-in-picture view with truncated content\n     * - fullscreen: Full article view with complete content\n     * @default \"fullscreen\"\n     */\n    displayMode?: 'inline' | 'pip' | 'fullscreen';\n  };\n}\n\n/**\n * A full post detail component with Medium-style typography.\n * Supports inline preview and fullscreen reading modes.\n *\n * Features:\n * - Medium-style typography and spacing\n * - Cover image display\n * - Author info with avatar\n * - Tag list with overflow tooltip\n * - Related posts section\n * - Inline (truncated) and fullscreen modes\n * - MCP Apps display mode integration\n *\n * @component\n * @example\n * ```tsx\n * <PostDetail\n *   data={{\n *     post: {\n *       id: \"1\",\n *       title: \"Getting Started\",\n *       excerpt: \"Learn the basics...\",\n *       coverImage: \"https://example.com/cover.jpg\",\n *       author: { name: \"Sarah Chen\", avatar: \"https://example.com/avatar.jpg\" },\n *       publishedAt: \"2024-01-15\",\n *       readTime: \"5 min read\",\n *       tags: [\"Tutorial\", \"Components\"],\n *       category: \"Tutorial\"\n *     },\n *     content: \"<p>Full post content here...</p>\",\n *     relatedPosts: [...]\n *   }}\n *   actions={{\n *     onReadMore: () => console.log(\"Expand to fullscreen\"),\n *     onReadRelated: (post) => console.log(\"Read related:\", post.title)\n *   }}\n *   appearance={{\n *     showCover: true,\n *     showAuthor: true,\n *     displayMode: \"fullscreen\"\n *   }}\n * />\n * ```\n */\nexport function PostDetail({ data, actions, appearance }: PostDetailProps) {\n  const resolved: NonNullable<PostDetailProps['data']> = data ?? demoPostDetailData;\n  const post = resolved.post;\n  const rawContent = resolved.content;\n  const content = useMemo(() => rawContent ? sanitizeHtml(rawContent) : undefined, [rawContent]);\n  const relatedPosts = resolved.relatedPosts ?? [];\n  const onReadMore = actions?.onReadMore;\n  const showCover = appearance?.showCover ?? true;\n  const showAuthor = appearance?.showAuthor ?? true;\n\n  const displayMode = appearance?.displayMode ?? 'inline';\n\n  const handleReadMore = () => {\n    onReadMore?.();\n  };\n\n  const formatDate = (dateStr: string) => {\n    return new Date(dateStr).toLocaleDateString('en-US', {\n      month: 'long',\n      day: 'numeric',\n      year: 'numeric',\n    });\n  };\n\n  // Inline mode - card view with truncated content\n  if (displayMode === 'inline') {\n    return (\n      <div className=\"flex flex-col sm:flex-row gap-4 rounded-lg border bg-card p-3\">\n        {showCover && post?.coverImage && (\n          <div className=\"aspect-video sm:aspect-square sm:h-24 sm:w-24 shrink-0 overflow-hidden rounded-md\">\n            <img\n              src={post.coverImage}\n              alt={post?.title || ''}\n              className=\"h-full w-full object-cover\"\n            />\n          </div>\n        )}\n\n        <div className=\"flex flex-1 flex-col justify-between min-w-0\">\n          <div>\n            <div className=\"flex items-start justify-between gap-2\">\n              <div className=\"flex-1 min-w-0\">\n                {post?.category && (\n                  <p className=\"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n                    {post.category}\n                  </p>\n                )}\n\n                {post?.title && (\n                  <h1 className=\"line-clamp-2 text-sm font-bold leading-tight\">{post.title}</h1>\n                )}\n              </div>\n              <button\n                onClick={handleReadMore}\n                className=\"shrink-0 p-1 rounded-md hover:bg-muted transition-colors text-muted-foreground hover:text-foreground cursor-pointer\"\n                aria-label=\"Expand to fullscreen\"\n              >\n                <Maximize2 className=\"h-4 w-4\" />\n              </button>\n            </div>\n\n            {post?.excerpt && (\n              <p className=\"mt-1 line-clamp-2 text-xs text-muted-foreground\">{post.excerpt}</p>\n            )}\n\n            {post?.tags && post.tags.length > 0 && (\n              <div className=\"mt-1.5 flex flex-wrap gap-1\">\n                <TagList tags={post.tags} maxVisible={2} size=\"small\" />\n              </div>\n            )}\n          </div>\n\n          <div className=\"mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n            <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n              {showAuthor && post?.author?.avatar && (\n                <img\n                  src={post.author.avatar}\n                  alt={post?.author?.name || ''}\n                  className=\"h-4 w-4 rounded-full\"\n                />\n              )}\n              {showAuthor && post?.author?.name && <span>{post.author.name}</span>}\n              {post?.publishedAt && (\n                <span className=\"flex items-center gap-1\">\n                  <Calendar className=\"h-3 w-3\" />\n                  {formatDate(post.publishedAt)}\n                </span>\n              )}\n              {post?.readTime && (\n                <span className=\"flex items-center gap-1\">\n                  <Clock className=\"h-3 w-3\" />\n                  {post.readTime}\n                </span>\n              )}\n            </div>\n\n            <Button size=\"sm\" onClick={handleReadMore}>\n              Read\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // PiP mode - horizontal layout with image on left, similar to post-card horizontal\n  if (displayMode === 'pip') {\n    return (\n      <div className=\"flex flex-col sm:flex-row gap-4 rounded-lg border bg-card p-3\">\n        {showCover && post?.coverImage && (\n          <div className=\"aspect-video sm:aspect-square sm:h-24 sm:w-24 shrink-0 overflow-hidden rounded-md\">\n            <img\n              src={post.coverImage}\n              alt={post?.title || ''}\n              className=\"h-full w-full object-cover\"\n            />\n          </div>\n        )}\n\n        <div className=\"flex flex-1 flex-col justify-between min-w-0\">\n          <div>\n            {post?.category && (\n              <p className=\"mb-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n                {post.category}\n              </p>\n            )}\n\n            {post?.title && (\n              <h1 className=\"line-clamp-2 text-sm font-bold leading-tight\">{post.title}</h1>\n            )}\n\n            {post?.excerpt && (\n              <p className=\"mt-1 line-clamp-2 text-xs text-muted-foreground\">{post.excerpt}</p>\n            )}\n\n            {post?.tags && post.tags.length > 0 && (\n              <div className=\"mt-1.5 flex flex-wrap gap-1\">\n                <TagList tags={post.tags} maxVisible={2} size=\"small\" />\n              </div>\n            )}\n          </div>\n\n          <div className=\"mt-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n            <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n              {showAuthor && post?.author?.avatar && (\n                <img\n                  src={post.author.avatar}\n                  alt={post?.author?.name || ''}\n                  className=\"h-4 w-4 rounded-full\"\n                />\n              )}\n              {showAuthor && post?.author?.name && <span>{post.author.name}</span>}\n              {post?.publishedAt && (\n                <span className=\"flex items-center gap-1\">\n                  <Calendar className=\"h-3 w-3\" />\n                  {formatDate(post.publishedAt)}\n                </span>\n              )}\n              {post?.readTime && (\n                <span className=\"flex items-center gap-1\">\n                  <Clock className=\"h-3 w-3\" />\n                  {post.readTime}\n                </span>\n              )}\n            </div>\n            <Button size=\"sm\" onClick={handleReadMore}>\n              Read\n            </Button>\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  // Fullscreen mode\n  return (\n    <div className=\"min-h-screen fs-mode bg-background\">\n      <article className=\"mx-auto w-full max-w-[680px] px-6 py-10\">\n        {showCover && post?.coverImage && (\n          <div className=\"aspect-video w-full overflow-hidden rounded-lg mb-8\">\n            <img\n              src={post.coverImage}\n              alt={post?.title || ''}\n              className=\"h-full w-full object-cover\"\n            />\n          </div>\n        )}\n        {post?.category && (\n          <p className=\"mb-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n            {post.category}\n          </p>\n        )}\n\n        {post?.title && (\n          <h1 className=\"text-[32px] font-bold leading-[1.25] tracking-tight md:text-[42px]\">\n            {post.title}\n          </h1>\n        )}\n\n        {post?.tags && post.tags.length > 0 && (\n          <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n            <TagList tags={post.tags} maxVisible={2} size=\"default\" />\n          </div>\n        )}\n\n        {showAuthor && post?.author && (\n          <div className=\"mt-8 flex items-center gap-4 border-b pb-8\">\n            {post.author.avatar && (\n              <img\n                src={post.author.avatar}\n                alt={post.author.name || ''}\n                className=\"h-12 w-12 rounded-full\"\n              />\n            )}\n            <div>\n              {post.author.name && <p className=\"font-medium\">{post.author.name}</p>}\n              {(post?.publishedAt || post?.readTime) && (\n                <div className=\"flex items-center gap-3 text-sm text-muted-foreground\">\n                  {post?.publishedAt && (\n                    <span className=\"flex items-center gap-1\">\n                      <Calendar className=\"h-3.5 w-3.5\" />\n                      {formatDate(post.publishedAt)}\n                    </span>\n                  )}\n                  {post?.readTime && (\n                    <span className=\"flex items-center gap-1\">\n                      <Clock className=\"h-3.5 w-3.5\" />\n                      {post.readTime}\n                    </span>\n                  )}\n                </div>\n              )}\n            </div>\n          </div>\n        )}\n\n        {/* Medium-style content */}\n        <div className=\"mt-10\">\n          {post?.excerpt && (\n            <p className=\"text-[21px] leading-[1.8] text-muted-foreground mb-8\">{post.excerpt}</p>\n          )}\n          {content && (\n            <div\n              className=\"\n                text-[21px] leading-[1.8] tracking-[-0.003em]\n                [&>p]:mb-8\n                [&>h2]:text-[26px] [&>h2]:font-bold [&>h2]:mt-12 [&>h2]:mb-4 [&>h2]:leading-[1.3]\n                [&>h3]:text-[22px] [&>h3]:font-bold [&>h3]:mt-10 [&>h3]:mb-3 [&>h3]:leading-[1.3]\n                [&>ul]:mb-8 [&>ul]:pl-6 [&>ul>li]:mb-2\n                [&>ol]:mb-8 [&>ol]:pl-6 [&>ol>li]:mb-2\n                [&>blockquote]:border-l-4 [&>blockquote]:border-foreground [&>blockquote]:pl-6 [&>blockquote]:my-8 [&>blockquote]:italic\n              \"\n              dangerouslySetInnerHTML={{ __html: content }}\n            />\n          )}\n        </div>\n\n        {relatedPosts && relatedPosts.length > 0 && (\n          <div className=\"mt-16 border-t pt-10\">\n            <h3 className=\"mb-6 text-lg font-semibold\">Related Posts</h3>\n            <div className=\"space-y-4\">\n              {relatedPosts.map((related) => (\n                <a\n                  key={related.title || related.url}\n                  href={related.url || '#'}\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                  className=\"flex w-full items-center gap-4 rounded-lg p-3 text-left transition-colors hover:bg-muted cursor-pointer\"\n                >\n                  {related.coverImage && (\n                    <div className=\"h-16 w-16 shrink-0 overflow-hidden rounded-lg\">\n                      <img\n                        src={related.coverImage}\n                        alt={related.title || ''}\n                        className=\"h-full w-full object-cover\"\n                      />\n                    </div>\n                  )}\n                  <div className=\"min-w-0 flex-1\">\n                    {related.title && <p className=\"font-medium\">{related.title}</p>}\n                    {related.excerpt && (\n                      <p className=\"mt-1 line-clamp-1 text-sm text-muted-foreground\">\n                        {related.excerpt}\n                      </p>\n                    )}\n                    {related.readTime && (\n                      <p className=\"mt-1 text-xs text-muted-foreground\">{related.readTime}</p>\n                    )}\n                  </div>\n                  <ExternalLink className=\"h-4 w-4 shrink-0 text-muted-foreground\" />\n                </a>\n              ))}\n            </div>\n          </div>\n        )}\n      </article>\n    </div>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/ui/post-detail.tsx"
    },
    {
      "path": "registry/blogging/demo/blogging.ts",
      "content": "// Demo data for Blogging category components\n// This file contains sample data used for component previews and documentation\n\nimport type { Post } from '../types';\n\n// Single post for PostCard default\nexport const demoPost: Post = {\n  title: 'Getting Started with Agentic UI Components',\n  excerpt:\n    'Learn how to build conversational interfaces with our comprehensive component library designed for AI-powered applications.',\n  coverImage: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800',\n  author: {\n    name: 'Sarah Chen',\n    avatar: 'https://i.pravatar.cc/150?u=sarah',\n  },\n  publishedAt: '2024-01-15',\n  readTime: '5 min read',\n  tags: ['Tutorial', 'Components'],\n  category: 'Tutorial',\n};\n\n// Demo content for PostDetail (HTML content for full article view)\nexport const demoPostContent = `\n  <p>Building modern AI-powered applications requires a new approach to UI design. Traditional web components don't always translate well to conversational interfaces, where context and flow are paramount.</p>\n\n  <p>Our Agentic UI component library provides a collection of purpose-built components that work seamlessly within chat interfaces. From payment flows to product displays, each component is designed with the unique constraints of conversational UIs in mind.</p>\n\n  <h2>Key Features</h2>\n  <p>Each component supports three display modes: inline (within the chat flow), fullscreen (for complex interactions), and picture-in-picture (persistent visibility). This flexibility allows you to create rich, interactive experiences without breaking the conversational flow.</p>\n\n  <p>Components are designed mobile-first and touch-friendly, ensuring a great experience across all devices. They automatically adapt to light and dark themes, and integrate seamlessly with MCP tools for backend communication.</p>\n`;\n\n// Related posts for PostDetail\nexport const demoRelatedPosts: Post[] = [\n  {\n    title: 'Designing for Conversational Interfaces',\n    excerpt:\n      'Best practices for creating intuitive UI components that work within chat environments.',\n    coverImage: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800',\n    author: { name: 'Alex Rivera', avatar: 'https://i.pravatar.cc/150?u=alex' },\n    publishedAt: '2024-01-12',\n    readTime: '8 min read',\n    tags: ['Design', 'UX'],\n    category: 'Design',\n    url: 'https://example.com/posts/designing-conversational-interfaces',\n  },\n  {\n    title: 'MCP Integration Patterns',\n    excerpt: 'How to leverage Model Context Protocol for seamless backend communication.',\n    coverImage: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800',\n    author: { name: 'Jordan Kim', avatar: 'https://i.pravatar.cc/150?u=jordan' },\n    publishedAt: '2024-01-10',\n    readTime: '12 min read',\n    tags: ['MCP', 'Backend'],\n    category: 'Development',\n    url: 'https://example.com/posts/mcp-integration-patterns',\n  },\n];\n\n// Full PostDetail demo data (combines post, content, and relatedPosts)\nexport const demoPostDetailData = {\n  post: {\n    ...demoPost,\n    tags: ['Tutorial', 'Components', 'AI', 'React', 'TypeScript'],\n  },\n  content: demoPostContent,\n  relatedPosts: demoRelatedPosts,\n};\n\n// 15 posts for PostList default\nexport const demoPosts: Post[] = [\n  {\n    title: 'Getting Started with Agentic UI Components',\n    excerpt:\n      'Learn how to build conversational interfaces with our comprehensive component library designed for AI-powered applications.',\n    coverImage: 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800',\n    author: {\n      name: 'Sarah Chen',\n      avatar: 'https://i.pravatar.cc/150?u=sarah',\n    },\n    publishedAt: '2024-01-15',\n    readTime: '5 min read',\n    tags: ['Tutorial', 'Components', 'AI'],\n    category: 'Tutorial',\n  },\n  {\n    title: 'Designing for Conversational Interfaces with Manifest UI',\n    excerpt:\n      'Best practices for creating intuitive UI components that work within chat environments.',\n    coverImage: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800',\n    author: {\n      name: 'Alex Rivera',\n      avatar: 'https://i.pravatar.cc/150?u=alex',\n    },\n    publishedAt: '2024-01-12',\n    readTime: '8 min read',\n    tags: ['Design', 'UX'],\n    category: 'Design',\n  },\n  {\n    title: 'MCP Integration Patterns',\n    excerpt:\n      'How to leverage Model Context Protocol for seamless backend communication in your agentic applications.',\n    coverImage: 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=800',\n    author: {\n      name: 'Jordan Kim',\n      avatar: 'https://i.pravatar.cc/150?u=jordan',\n    },\n    publishedAt: '2024-01-10',\n    readTime: '12 min read',\n    tags: ['MCP', 'Backend', 'Integration'],\n    category: 'Development',\n  },\n  {\n    title: 'Building Payment Flows in Chat',\n    excerpt:\n      'A complete guide to implementing secure, user-friendly payment experiences within conversational interfaces.',\n    coverImage: 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=800',\n    author: {\n      name: 'Morgan Lee',\n      avatar: 'https://i.pravatar.cc/150?u=morgan',\n    },\n    publishedAt: '2024-01-08',\n    readTime: '10 min read',\n    tags: ['Payments', 'Security'],\n    category: 'Tutorial',\n  },\n  {\n    title: 'Real-time Collaboration in AI Apps',\n    excerpt:\n      'Implementing WebSocket connections and real-time updates for collaborative agentic experiences.',\n    coverImage: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800',\n    author: {\n      name: 'Casey Taylor',\n      avatar: 'https://i.pravatar.cc/150?u=casey',\n    },\n    publishedAt: '2024-01-06',\n    readTime: '15 min read',\n    tags: ['WebSocket', 'Real-time', 'Collaboration'],\n    category: 'Development',\n  },\n  {\n    title: 'Accessibility in Chat Interfaces',\n    excerpt:\n      'Making your conversational UI accessible to all users with screen readers and keyboard navigation.',\n    coverImage: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=800',\n    author: {\n      name: 'Jamie Park',\n      avatar: 'https://i.pravatar.cc/150?u=jamie',\n    },\n    publishedAt: '2024-01-04',\n    readTime: '9 min read',\n    tags: ['Accessibility', 'A11y', 'UX'],\n    category: 'Design',\n  },\n  {\n    title: 'State Management for Complex Workflows',\n    excerpt:\n      'Managing complex multi-step workflows in agentic applications using modern state patterns.',\n    coverImage: 'https://images.unsplash.com/photo-1501785888041-af3ef285b470?w=800',\n    author: {\n      name: 'Drew Martinez',\n      avatar: 'https://i.pravatar.cc/150?u=drew',\n    },\n    publishedAt: '2024-01-02',\n    readTime: '11 min read',\n    tags: ['State', 'Workflow', 'Architecture'],\n    category: 'Development',\n  },\n  {\n    title: 'Testing Conversational Components',\n    excerpt: 'Strategies for unit testing and integration testing of chat-based UI components.',\n    coverImage: 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=800',\n    author: {\n      name: 'Riley Johnson',\n      avatar: 'https://i.pravatar.cc/150?u=riley',\n    },\n    publishedAt: '2023-12-30',\n    readTime: '8 min read',\n    tags: ['Testing', 'Quality', 'CI/CD'],\n    category: 'Development',\n  },\n  {\n    title: 'Theming and Dark Mode Support',\n    excerpt: 'Implementing flexible theming systems with dark mode for agentic UI components.',\n    coverImage: 'https://images.unsplash.com/photo-1475924156734-496f6cac6ec1?w=800',\n    author: {\n      name: 'Avery Williams',\n      avatar: 'https://i.pravatar.cc/150?u=avery',\n    },\n    publishedAt: '2023-12-28',\n    readTime: '7 min read',\n    tags: ['Theming', 'Dark Mode', 'CSS'],\n    category: 'Design',\n  },\n  {\n    title: 'Performance Optimization Techniques',\n    excerpt: 'Optimizing render performance and reducing bundle size in chat applications.',\n    coverImage: 'https://images.unsplash.com/photo-1518173946687-a2e8a36af77a?w=800',\n    author: {\n      name: 'Quinn Anderson',\n      avatar: 'https://i.pravatar.cc/150?u=quinn',\n    },\n    publishedAt: '2023-12-25',\n    readTime: '13 min read',\n    tags: ['Performance', 'Optimization', 'React'],\n    category: 'Development',\n  },\n  {\n    title: 'Error Handling and Recovery',\n    excerpt:\n      'Graceful error handling patterns and user-friendly recovery flows in conversational UIs.',\n    coverImage: 'https://images.unsplash.com/photo-1509316975850-ff9c5deb0cd9?w=800',\n    author: {\n      name: 'Sage Thompson',\n      avatar: 'https://i.pravatar.cc/150?u=sage',\n    },\n    publishedAt: '2023-12-22',\n    readTime: '10 min read',\n    tags: ['Error Handling', 'UX', 'Resilience'],\n    category: 'Development',\n  },\n  {\n    title: 'Internationalization Best Practices',\n    excerpt: 'Making your agentic UI components work across languages and locales.',\n    coverImage: 'https://images.unsplash.com/photo-1426604966848-d7adac402bff?w=800',\n    author: {\n      name: 'Blake Garcia',\n      avatar: 'https://i.pravatar.cc/150?u=blake',\n    },\n    publishedAt: '2023-12-20',\n    readTime: '9 min read',\n    tags: ['i18n', 'Localization', 'Global'],\n    category: 'Design',\n  },\n  {\n    title: 'Mobile-First Chat Design',\n    excerpt: 'Designing conversational interfaces that work beautifully on mobile devices.',\n    coverImage: 'https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=800',\n    author: {\n      name: 'Charlie Brown',\n      avatar: 'https://i.pravatar.cc/150?u=charlie',\n    },\n    publishedAt: '2023-12-18',\n    readTime: '8 min read',\n    tags: ['Mobile', 'Responsive', 'Design'],\n    category: 'Design',\n  },\n  {\n    title: 'Analytics and User Insights',\n    excerpt: 'Tracking user interactions and deriving insights from conversational UI usage.',\n    coverImage: 'https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?w=800',\n    author: {\n      name: 'Sydney Chen',\n      avatar: 'https://i.pravatar.cc/150?u=sydney',\n    },\n    publishedAt: '2023-12-15',\n    readTime: '11 min read',\n    tags: ['Analytics', 'Insights', 'Data'],\n    category: 'Tutorial',\n  },\n  {\n    title: 'Building Reusable Component Libraries',\n    excerpt: 'Creating a scalable component library for agentic UIs that teams can share.',\n    coverImage: 'https://images.unsplash.com/photo-1465056836900-8f1e940f2114?w=800',\n    author: {\n      name: 'Taylor Swift',\n      avatar: 'https://i.pravatar.cc/150?u=taylor',\n    },\n    publishedAt: '2023-12-12',\n    readTime: '14 min read',\n    tags: ['Components', 'Library', 'Scalability'],\n    category: 'Development',\n  },\n];\n",
      "type": "registry:lib",
      "target": "components/ui/demo/blogging.ts"
    }
  ],
  "categories": [
    "blogging"
  ],
  "type": "registry:block"
}