Pure presentation component for rendering blog post cards in three density modes: full vertical (`default`), compact horizontal (`sm`), and portrait strip (`portrait`). ## Key Components ### `BlogCard` Main component that renders a `BlogPostSummary` in one of three layouts. All click/navigation logic is deferred to the caller via `href` and optional `target`/`rel` props. | Size | Layout | Use Case | |------|--------|----------| | `default` | Full vertical card with cover image, title, summary, author/date footer, and view count | Blog index grids | | `sm` | Compact horizontal (~80px) with thumbnail, title, category, summary | Chat inline rendering | | `portrait` | Delegates to `EntityPortraitCard` | Mixed-type content rails | **Image fallback chain:** `post.featured_image` → `placeholderUrl` (caller-resolved via `useOgPlaceholderUrl`) → `bg-ods-bg` slot background. ### `BlogCardSkeleton` Loading placeholder mirroring the shape of each density. Accepts the same `size` prop. ## Key Props | Prop | Type | Description | |------|------|-------------| | `post` | `BlogPostSummary` | Post data | | `href` | `string` | Resolved detail URL (caller provides) | | `target` | `'_blank'` | Opens in new tab (set by chat dispatch) | | `placeholderUrl` | `string \| null` | Fallback image when `featured_image` is absent | | `size` | `'default' \| 'sm' \| 'portrait'` | Card density | | `showTypeBadge` | `boolean` | Renders content-type chip in portrait mode | | `hasEmbeddedVideo` | `boolean` | Shows "Video" badge in `sm` mode | | `priority` | `boolean` | Passes Next.js `priority` to the cover `` | ## Usage Example ```typescript // Default vertical card (hub context) // Compact card for chat inline // Skeleton during load ``` > **No click logic lives here.** Callers wrap with `` (hub) or `` (lib) as appropriate. **Source:** [`blog-card.tsx`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/blog-card.tsx)