# Blog Component API Documentation

**Last Updated:** 2026-01-10

**Note**: Added documentation for BlogIndexHero, CategoryCards, CategoryCardsRelated, and Blog Index Dynamic Filtering components.

Complete API documentation for all blog components, including props, usage examples, and implementation details.

## Overview

This document provides detailed API documentation for all blog components used in the WordPress to PHP migration. Each component includes prop definitions, usage examples, and implementation notes.

## Component Index

1. [PostCard](#postcard)
2. [PostHeader](#postheader)
3. [PostContent](#postcontent)
4. [RelatedPosts](#relatedposts)
5. [BlogFAQ](#blogfaq)
6. [Pagination](#pagination)
7. [CategoryNav](#categorynav)
8. [Breadcrumbs](#breadcrumbs)
9. [TopicHubHero](#topichubhero)
10. [BlogIndexHero](#blogindexhero)
11. [CategoryCards](#categorycards)
12. [CategoryCardsRelated](#categorycardsrelated)
13. [Blog Index Dynamic Filtering](#blog-index-dynamic-filtering)

## PostCard

**File**: `v2/components/blog/PostCard.php`

**Purpose**: Displays a blog post preview card with featured image, category badge, title, excerpt, and publication date.

### Props

| Prop                | Type        | Required | Default            | Description                          |
| ------------------- | ----------- | -------- | ------------------ | ------------------------------------ |
| `$title`            | string      | Yes      | -                  | Post title                           |
| `$url`              | string      | Yes      | -                  | Post URL                             |
| `$category`         | string      | No       | ''                 | Category slug                        |
| `$category_label`   | string      | No       | ucfirst($category) | Category display name                |
| `$excerpt`          | string      | No       | ''                 | Post excerpt/preview                 |
| `$publication_date` | string      | No       | ''                 | ISO 8601 publication date            |
| `$modified_date`    | string      | No       | ''                 | ISO 8601 modified date (optional)    |
| `$featured_image`   | array\|null | No       | null               | Featured image data                  |
| `$show_excerpt`     | bool        | No       | true               | Whether to show excerpt              |
| `$variant`          | string      | No       | 'default'          | Card variant: 'default' \| 'compact' |

### Featured Image Structure

```php
$featured_image = [
    'src' => '/insights/bilder/image.webp',
    'alt' => 'Image alt text',
    'width' => 1024,
    'height' => 576,
    'srcset' => [
        ['src' => '/insights/bilder/image-640w.webp', 'width' => 640],
        ['src' => '/insights/bilder/image-1024w.webp', 'width' => 1024]
    ]
];
```

### Usage Examples

**Default Card**:

```php
$title = $post['title'];
$url = $post['url'];
$category = $post['category'];
$category_label = get_category_label($category);
$excerpt = $post['excerpt'];
$publication_date = $post['publication_date'];
$featured_image = $post['featured_image'];
include '../../components/blog/PostCard.php';
```

**Compact Card (for Related Posts)**:

```php
$title = $post['title'];
$url = $post['url'];
$category = $post['category'];
$category_label = get_category_label($category);
$excerpt = $post['excerpt'];
$publication_date = $post['publication_date'];
$featured_image = $post['featured_image'];
$variant = 'compact';
$show_excerpt = false;
include '../../components/blog/PostCard.php';
```

**Note**: In compact variant, dates are hidden visually to ensure alignment with other content types (templates, tools, downloads) that don't display dates. Date meta tags are still included for SEO purposes.

### Features

- Responsive images with srcset
- Schema markup (BlogPosting)
- Accessibility (ARIA labels, semantic HTML)
- Lazy loading for images
- Category badge display
- Date formatting (hidden in compact variant for carousel alignment)
- Last updated badge display (when 7+ days after publication, with refresh icon)
- **Compact Variant**: Dates are hidden visually but meta tags preserved for SEO

## PostHeader

**File**: `v2/components/blog/PostHeader.php`

**Purpose**: Displays post header with featured image, title, category, publication date, author, and reading time.

### Props

| Prop                | Type        | Required | Default            | Description               |
| ------------------- | ----------- | -------- | ------------------ | ------------------------- |
| `$title`            | string      | Yes      | -                  | Post title (H1)           |
| `$category`         | string      | No       | ''                 | Category slug             |
| `$category_label`   | string      | No       | ucfirst($category) | Category display name     |
| `$publication_date` | string      | No       | ''                 | ISO 8601 publication date |
| `$modified_date`    | string      | No       | ''                 | ISO 8601 modified date (optional) |
| `$author`           | array\|null | No       | null               | Author data (name, url)   |
| `$reading_time`     | int\|null   | No       | null               | Reading time in minutes   |
| `$featured_image`   | array\|null | No       | null               | Featured image data       |

### Author Structure

```php
$author = [
    'name' => 'Emma',
    'url' => '/author/emma/'
];
```

### Usage Example

```php
$title = $post['title'];
$category = $post['category'];
$category_label = get_category_label($category);
$publication_date = $post['publication_date'];
$modified_date = $post['modified_date'] ?? '';
$author = $post['author'];
$reading_time = calculate_reading_time($post['content']['word_count']);
$featured_image = $post['featured_image'];
include '../../components/blog/PostHeader.php';
```

### Features

- Featured image with fetchpriority="high"
- Responsive image srcset
- Schema markup (Article, Person)
- Category link
- Author display with link
- Reading time calculation
- Last updated date display (when 7+ days after publication, with refresh icon)
- Date formatting (e.g., "1. September 2023")

## PostContent

**File**: `v2/components/blog/PostContent.php`

**Purpose**: Renders post HTML content with sanitization, image optimization, and link processing.

### Props

| Prop              | Type   | Required | Default | Description                   |
| ----------------- | ------ | -------- | ------- | ----------------------------- |
| `$html_content`   | string | Yes      | -       | Post HTML content             |
| `$images`         | array  | No       | []      | Image data for optimization   |
| `$internal_links` | array  | No       | []      | Internal links for processing |

### Usage Example

```php
$html_content = $post['content']['html'];
$images = $post['images'];
$internal_links = $post['internal_links'];
include '../../components/blog/PostContent.php';
```

### Features

- **Content Cleanup**: Automatically removes author names and CTA sections from content
  - Author removal: Removes paragraphs containing "Autor: [Name]" or "Von [Name]" patterns
  - CTA removal: Removes divs with `bg-ordio-sand` classes containing CTA text ("7 Tage kostenlos testen", etc.)
  - Defensive cleanup ensures runtime safety even if content wasn't pre-cleaned
- HTML sanitization (via `sanitizeHtmlOutput()`)
- Image removal (since featured image is displayed in header)
- WordPress wrapper div removal (removes container/border styling)
- Internal link processing
- Prose typography classes
- Responsive content width

### Content Cleanup Details

The PostContent component performs defensive cleanup to ensure author names and CTAs are removed even if they weren't filtered during extraction. This multi-layer approach ensures:

1. **Author Removal**:

   - Paragraphs with `author-name` class
   - Standalone paragraphs containing "Autor: [Name]" or "Von [Name]"
   - Short paragraphs (< 50 chars) starting with author patterns

2. **CTA Removal**:
   - Divs with `bg-ordio-sand` classes containing CTA text
   - Divs with `order-3` class containing CTA text
   - Any div containing CTA text with CTA-like classes

This defensive cleanup ensures content is always clean, regardless of extraction/cleanup script execution.

## RelatedPosts

**File**: `v2/components/blog/RelatedPosts.php`

**Purpose**: Displays related posts using PostCard component.

### Props

| Prop             | Type   | Required | Default            | Description                |
| ---------------- | ------ | -------- | ------------------ | -------------------------- |
| `$related_posts` | array  | Yes      | -                  | Array of related post data |
| `$limit`         | int    | No       | 14                 | Maximum number of posts    |
| `$heading`       | string | No       | 'Ähnliche Artikel' | Heading text               |

### Related Post Structure

```php
$related_posts = [
    [
        'title' => 'Related Post Title',
        'url' => '/insights/category/slug/',
        'category' => 'lexikon',
        'category_label' => 'Lexikon',
        'excerpt' => 'Post excerpt...',
        'publication_date' => '2023-09-01T10:40:44+00:00',
        'featured_image' => [...]
    ],
    // ...
];
```

### Usage Example

```php
$related_posts = load_related_posts($slug, $category, 14);
$heading = 'Ähnliche Artikel';
include '../../components/blog/RelatedPosts.php';
```

### Features

- Uses PostCard component (compact variant)
- Responsive grid layout
- Limits posts to specified count
- Empty state handling

## Pagination

**File**: `v2/components/blog/Pagination.php`

**Purpose**: Displays pagination navigation with previous/next links and page numbers.

### Props

| Prop                 | Type   | Required | Default      | Description                   |
| -------------------- | ------ | -------- | ------------ | ----------------------------- |
| `$current_page`      | int    | No       | 1            | Current page number (1-based) |
| `$total_pages`       | int    | No       | 1            | Total number of pages         |
| `$posts_per_page`    | int    | No       | 10           | Posts per page                |
| `$base_url`          | string | No       | '/insights/' | Base URL for pagination       |
| `$show_page_numbers` | bool   | No       | true         | Show page numbers             |

### Usage Example

```php
$current_page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$total_pages = ceil($total_posts / $posts_per_page);
$base_url = '/insights/';
$show_page_numbers = true;
include '../../components/blog/Pagination.php';
```

### Features

- Previous/next links with rel="prev"/rel="next"
- Page number display (max 7 pages)
- Ellipsis for large page counts
- Disabled states for first/last page
- Accessibility (aria-label, aria-current)
- URL generation (handles page 1 without /page/1/)

## PillarHero

**File**: `v2/components/blog/PillarHero.php`

**Purpose**: Displays a hero card for pillar pages with image, title, description, and CTA. Used on blog index page to feature pillar pages prominently.

### Props

| Prop              | Type   | Required | Default   | Description                  |
| ----------------- | ------ | -------- | --------- | ---------------------------- |
| `$slug`           | string | Yes      | ''        | Pillar page slug             |
| `$title`          | string | Yes      | ''        | Pillar page title            |
| `$url`            | string | Yes      | '#'       | Pillar page URL              |
| `$description`    | string | No       | ''        | Optional description/excerpt |
| `$image_url`      | string | No       | null      | Optional featured image URL  |
| `$category_label` | string | No       | 'Lexikon' | Category label               |

### Usage Example

```php
$slug = 'dienstplan';
$title = 'Dienstplan 2026: Definition, Erstellung & Software';
$url = '/insights/dienstplan/';
$description = 'Alles rund um Dienstplan-Erstellung...';
$category_label = 'Lexikon';
include '../../components/blog/PillarHero.php';
```

### Features

- Responsive hero card design
- Image with overlay gradient
- Category badge
- CTA button with arrow icon
- Smooth hover animations
- Accessibility attributes (aria-label, schema.org)
- Focus states for keyboard navigation

## SkeletonCard

**File**: `v2/components/blog/SkeletonCard.php`

**Purpose**: Loading placeholder component that matches PostCard dimensions. Prevents Cumulative Layout Shift (CLS) during content loading.

### Props

| Prop     | Type | Required | Default | Description              |
| -------- | ---- | -------- | ------- | ------------------------ |
| `$count` | int  | No       | 1       | Number of skeleton cards |

### Usage Example

```php
$skeleton_count = 6;
include '../../components/blog/SkeletonCard.php';
```

### Features

- Matches PostCard dimensions exactly
- Animated shimmer effect
- Prevents CLS during loading
- Automatically hidden when content loads
- Accessibility: aria-hidden and role="presentation"

## CategoryNav

**File**: `v2/components/blog/CategoryNav.php`

**Purpose**: Displays category navigation with active states, links, and post counts.

### Props

| Prop                | Type   | Required | Default | Description                           |
| ------------------- | ------ | -------- | ------- | ------------------------------------- |
| `$current_category` | string | No       | ''      | Current category slug                 |
| `$categories`       | array  | No       | []      | Array of category data                |
| `$variant`          | string | No       | 'tabs'  | Navigation variant: 'tabs' \| 'links' |

### Category Structure

```php
$categories = [
    [
        'slug' => '',
        'name' => 'Alle Artikel',
        'url' => '/insights/',
        'post_count' => 99
    ],
    [
        'slug' => 'lexikon',
        'name' => 'Lexikon',
        'url' => '/insights/lexikon/',
        'post_count' => 22
    ],
    // ...
];
```

### Usage Example

```php
$current_category = $category;
$categories = load_blog_categories();
$variant = 'tabs';
include '../../components/blog/CategoryNav.php';
```

### Features

- Active state highlighting
- Post count display
- Two variants (tabs/links)
- Accessibility (aria-current)
- Responsive design

## Blog Related Posts Carousel

**File**: `v2/base/blog_related_carousel.php`

**Purpose**: Displays a rotating carousel of related blog posts with intelligent matching based on topic clusters, semantic similarity, and relationship types. Uses Alpine.js for interactivity with navigation arrows, pagination dots, and auto-rotation.

### Props

| Prop               | Type   | Required | Default            | Description                           |
| ------------------ | ------ | -------- | ------------------ | ------------------------------------- |
| `$currentPostSlug` | string | Yes      | -                  | Slug of the current post to exclude   |
| `$currentCategory` | string | Yes      | -                  | Category of the current post          |
| `$limit`           | int    | No       | 12                 | Maximum number of related posts       |
| `$heading`         | string | No       | 'Ähnliche Artikel' | Heading text for the carousel section |

### Related Posts Algorithm

The carousel uses `load_related_posts_enhanced()` which implements a multi-tier fallback system:

1. **Tier 1: Existing Related Posts** - Uses `related_posts` array from post JSON if available
2. **Tier 2: Cluster Matching** - Matches posts with same primary/secondary clusters
3. **Tier 3: Topic Matching** - Matches posts with shared topics
4. **Tier 4: Category Matching** - Matches posts from same category
5. **Tier 5: Recency Boost** - Applies recency multiplier for recent posts

### Features

- **Intelligent Matching**: Uses cluster and topic data for better relevance
- **Auto-Calculation**: Automatically calculates related posts for new posts without manual configuration
- **Carousel Functionality**:
  - Navigation arrows (desktop only)
  - Pagination dots (if >3 posts)
  - Auto-rotation every 6 seconds
  - Stops auto-rotation on user interaction
- **Responsive Design**:
  - Desktop (≥1024px): 3 cards per view
  - Tablet (768-1023px): 2 cards per view
  - Mobile (<768px): 1 card per view
- **Accessibility**: ARIA labels, keyboard navigation, focus states

### Usage Example

```php
<?php
// In blog post template (v2/pages/blog/post.php)
$currentPostSlug = $slug;
$currentCategory = $category;
$heading = 'Ähnliche Artikel';
include __DIR__ . '/../../base/blog_related_carousel.php';
?>
```

### Styling

Carousel-specific styles are scoped to `#blog-related-carousel-section` and included inline in the component. The component uses existing PostCard components (compact variant) for consistent design.

**Visual Alignment Standards**:

- All image wrappers use consistent 16:9 aspect ratio across all content types (blog posts, templates, tools, downloads, products, industries)
- Badges have consistent `margin-bottom: 0.75rem` spacing
- Titles have consistent `min-height: 2.75rem` for 2-line titles
- Dates are hidden in compact variant (carousel mode) to ensure alignment across mixed content types
- Flexbox layout ensures equal card heights and proper content distribution
- Excel visuals, icon wrappers, and regular images all respect the same aspect ratio constraints

### Data Sources

- **Primary**: `related_posts` array in post JSON files
- **Fallback**: `docs/data/blog-cluster-mapping.json` for cluster data
- **Fallback**: `docs/data/blog-topics-extracted.json` for topic data

### Future-Proofing

The algorithm automatically works for new posts without requiring manual `related_posts` configuration. It gracefully falls back through tiers to ensure posts always have related content.

See [Related Posts Logic](./RELATED_POSTS_LOGIC.md) for complete algorithm documentation.

---

## Breadcrumbs

**File**: `v2/components/blog/Breadcrumbs.php`

**Purpose**: Displays breadcrumb navigation with schema markup for SEO. **Note**: Breadcrumbs are visually hidden using the `.sr-only` CSS class but remain in the HTML for SEO benefits (schema markup) and accessibility (screen readers).

## Title Keyword Highlighting

**Function**: `highlight_title_keywords()` in `v2/config/blog-template-helpers.php`

**Purpose**: Automatically highlights 1-2 keywords in blog post titles using Ordio blue (#4D8EF3) to enhance visual appeal and brand consistency.

### Keyword Selection Rules

1. **Brand Keywords** (highest priority):

   - "Ordio" is always highlighted when present

2. **Structural Keywords**:

   - Text before colon (:) is highlighted
   - Text before dash (–) is highlighted

3. **Default** (first significant words):
   - First 1-2 significant words (excluding German stop words) are highlighted
   - Stop words include: der, die, das, und, oder, für, mit, im, etc.

### Usage

The function is automatically applied in `PostHeader` component:

```php
<h1 class="post-header-title ...">
    <?php echo highlight_title_keywords($title); ?>
</h1>
```

### Examples

- "Employer Branding – Definition, Strategien & Tipps" → "**Employer Branding**" highlighted
- "24-Stunden-Dienst: Regeln, Vorteile & Pflichten" → "**24-Stunden-Dienst**" highlighted
- "Dein Weg zu Ordio: Unser Sales-Prozess" → "**Ordio**" highlighted
- "Arbeitszeitmodelle – Überblick, Beispiele & Lösungen" → "**Arbeitszeitmodelle**" highlighted

### CSS Styling

Highlighted keywords use the `.title-keyword` class:

```css
.post-header-title .title-keyword {
  color: var(--ordio-blue);
  font-weight: inherit;
}
```

### Props

| Prop              | Type  | Required | Default | Description               |
| ----------------- | ----- | -------- | ------- | ------------------------- |
| `$items`          | array | Yes      | -       | Array of breadcrumb items |
| `$include_schema` | bool  | No       | true    | Include schema markup     |

### Breadcrumb Structure

```php
$items = [
    ['name' => 'Home', 'url' => '/'],
    ['name' => 'Blog', 'url' => '/insights/'],
    ['name' => 'Lexikon', 'url' => '/insights/lexikon/'],
    ['name' => 'Post Title', 'url' => '/insights/lexikon/post-slug/']
];
```

### Usage Example

```php
$items = [
    ['name' => 'Home', 'url' => '/'],
    ['name' => 'Blog', 'url' => '/insights/'],
    ['name' => $post['title'], 'url' => $post['url']]
];
include '../../components/blog/Breadcrumbs.php';
```

### Features

- Schema markup (BreadcrumbList)
- Responsive display
- Separator between items
- Current page highlighting
- Accessibility (aria-label)

## TopicHubHero

**File**: `v2/components/blog/TopicHubHero.php`

**Purpose**: Displays topic hub hero section with topic name, description, and statistics.

### Props

| Prop           | Type   | Required | Default | Description        |
| -------------- | ------ | -------- | ------- | ------------------ |
| `$topic_id`    | string | No       | ''      | Topic identifier   |
| `$topic_name`  | string | Yes      | -       | Topic display name |
| `$description` | string | No       | ''      | Topic description  |
| `$statistics`  | array  | No       | []      | Statistics data    |

### Statistics Structure

```php
$statistics = [
    'post_count' => 69,
    'last_updated' => '2025-03-31T18:58:44+00:00',
    'subtopics_count' => 5
];
```

### Usage Example

```php
$topic_name = $topic_data['name'];
$description = $topic_data['description'];
$statistics = [
    'post_count' => count($related_posts),
    'last_updated' => null,
    'subtopics_count' => 0
];
include '../../components/blog/TopicHubHero.php';
```

### Features

- Topic title (H1)
- Description display
- Statistics with icons
- Responsive layout
- Date formatting

## Common Patterns

### Loading Post Data

```php
require_once __DIR__ . '/../../config/blog-template-helpers.php';

// Load single post
$post = load_blog_post($category, $slug);

// Load posts by category
$posts = load_blog_posts_by_category($category, $limit, $offset);

// Load all posts
$posts = load_all_blog_posts($limit, $offset);

// Load related posts
$related_posts = load_related_posts($slug, $category, 14);
```

### Setting Component Props

```php
// Extract data from post
$title = $post['title'] ?? '';
$url = $post['url'] ?? '#';
$category = $post['category'] ?? '';
$category_label = get_category_label($category);
$excerpt = $post['excerpt'] ?? '';
$publication_date = $post['publication_date'] ?? '';
$featured_image = $post['featured_image'] ?? null;

// Include component
include '../../components/blog/PostCard.php';
```

### Error Handling

```php
// Check if post exists
if (!$post) {
    http_response_code(404);
    die('Post not found');
}

// Check if category is valid
$valid_categories = ['lexikon', 'ratgeber', 'inside-ordio'];
if (!in_array($category, $valid_categories)) {
    http_response_code(404);
    die('Category not found');
}
```

## Accessibility Guidelines

### Semantic HTML

- Use `<article>` for post cards
- Use `<header>` for post headers
- Use `<nav>` for navigation
- Use `<time>` for dates
- Use `<main>` for main content

### ARIA Attributes

- `aria-label` for navigation
- `aria-current` for active states
- `aria-disabled` for disabled links
- `itemscope` and `itemtype` for schema

### Keyboard Navigation

- Visible focus states
- Logical tab order
- Keyboard-accessible pagination
- Skip links (if needed)

## Performance Guidelines

### Image Optimization

- Use WebP format
- Provide srcset for responsive images
- Use lazy loading for below-fold images
- Use fetchpriority="high" for hero images
- Include explicit width/height attributes

### Component Loading

- Load only required data
- Use pagination to limit posts
- Cache post data when possible
- Minimize database/file operations

## ResourceCard Component

**File:** `v2/components/blog/ResourceCard.php`

**Purpose:** Unified card component for displaying blog posts, templates, downloads, and tools in mixed resource carousels.

### Props

- `type` (string, required): Resource type - `'blog'`, `'template'`, `'download'`, or `'tool'`
- `title` (string, required): Resource title
- `url` (string, required): Resource URL
- `description` (string): Resource description/excerpt
- `image` (array|null): Image data with `src`, `alt`, `width`, `height` keys
- `badge` (string): Badge text (e.g., 'Lexikon', 'Vorlage', 'Download', 'Rechner')
- `badge_color` (string): Badge CSS color class
- `show_excerpt` (bool, default: true): Whether to show description
- `variant` (string, default: 'compact'): Card variant - `'default'` or `'compact'`
- `resource_data` (array): Full resource data array for type-specific rendering

### Usage

```php
<?php
$type = 'template';
$title = 'Dienstplan Excel Vorlage';
$url = '/vorlagen/dienstplan-excel-vorlage';
$description = 'Kostenlose Dienstplan Excel Vorlage...';
$badge = 'Vorlage';
$badge_color = 'bg-ordio-blue bg-opacity-10 text-ordio-blue';
$variant = 'compact';
$show_excerpt = false;
$resource_data = [
    'type' => 'template',
    'category' => 'shift_planning',
    'tags' => ['dienstplan', 'excel']
];
include __DIR__ . '/../../components/blog/ResourceCard.php';
?>
```

### Badge Colors

- **Blog posts**: Category-based colors (Lexikon: blue, Ratgeber: green, Inside Ordio: purple)
- **Templates**: Ordio blue (`bg-ordio-blue bg-opacity-10 text-ordio-blue`)
- **Downloads**: Green (`bg-green-100 text-green-800`)
- **Tools**: Purple (`bg-purple-100 text-purple-800`)

### Special Handling

- **Blog posts**: Automatically delegates to `PostCard.php` for backward compatibility
- **Tools**: Displays icon instead of image if `icon` is provided in `resource_data`
- **Downloads**: Supports image display from `image` array

### Related Functions

- `get_resource_badge($resource_type, $resource_data)` - Get badge text and color
- `load_related_resources_unified($post_slug, $category, $limit)` - Load mixed resources

## BlogIndexHero

**File**: `v2/components/blog/BlogIndexHero.php`

**Purpose**: Displays a branded hero section for blog index and category pages with pill badge, title, description, and background gradient pattern matching product page styling.

### Props

| Prop           | Type   | Required | Default | Description                                          |
| -------------- | ------ | -------- | ------- | ---------------------------------------------------- |
| `$badge`       | string | No       | 'Blog'  | Badge text (e.g., "Blog", "Insights", category name) |
| `$title`       | string | No       | ''      | Hero title                                           |
| `$description` | string | No       | ''      | Hero description text                                |
| `$variant`     | string | No       | 'index' | Variant: 'index' \| 'category'                       |

### Usage Examples

**Blog Index Page**:

```php
$badge = 'Blog';
$title = 'Tipps zu Schichtplanung & HR-Management';
$description = 'Entdecke praktische Tipps zur Schichtplanung Zeiterfassung HR-Tech und Teamorganisation.';
$variant = 'index';
include __DIR__ . '/../../components/blog/BlogIndexHero.php';
```

**Category Page**:

```php
$badge = $category_data['name'];
$title = $category_data['name'];
$description = $category_data['description'];
$variant = 'category';
include __DIR__ . '/../../components/blog/BlogIndexHero.php';
```

### Features

- Pill badge with hover effects
- Large title with Gilroy Bold font
- Description text with optimal reading width
- Background gradient pattern matching product pages (`blog-page-body-gradient`)
- Responsive design (mobile-first)
- Category page variant with full-width content

### Styling

- Background: `blog-page-body-gradient` class applies radial dot pattern
- Badge: White background with opacity, rounded pill shape
- Title: Responsive sizing (42px mobile, 72px desktop)
- Description: Responsive font size (16px mobile, 18px desktop)

## CategoryCards

**File**: `v2/components/blog/CategoryCards.php`

**Purpose**: Displays a three-column grid of category cards linking to category pages. Each card shows category icon, name, post count, and description with hover effects.

### Props

| Prop          | Type  | Required | Default | Description                                          |
| ------------- | ----- | -------- | ------- | ---------------------------------------------------- |
| `$categories` | array | No       | auto    | Array of category data (auto-loaded if not provided) |

### Category Data Structure

```php
$categories = [
    [
        'slug' => 'lexikon',
        'name' => 'Lexikon',
        'url' => '/insights/lexikon/',
        'post_count' => 54,
        'description' => 'Fachbegriffe rund um Arbeitsrecht Schichtmodelle und HR-Prozesse einfach erklärt.'
    ],
    // ... more categories
];
```

### Usage Example

```php
// Auto-loads categories if not provided
include __DIR__ . '/../../components/blog/CategoryCards.php';

// Or provide custom categories
$categories = [
    // ... category data
];
include __DIR__ . '/../../components/blog/CategoryCards.php';
```

### Features

- Three-column grid (responsive: 1 col mobile, 2 cols tablet, 3 cols desktop)
- Category-specific icons and colors
- Post count display
- Hover effects with smooth transitions
- Arrow indicator on hover
- Category-specific hover colors matching badge colors

### Category Colors

- **Lexikon**: Blue (`#EFF6FF` background, `#4D8EF3` text)
- **Ratgeber**: Green (`#D1FAE5` background, `#059669` text)
- **Inside Ordio**: Grey (`#F3F4F6` background, `#6B7280` text)

### Icons

Each category has a unique SVG icon:

- **Lexikon**: Book icon
- **Ratgeber**: Lightbulb icon
- **Inside Ordio**: Building icon

## CategoryCardsRelated

**File**: `v2/components/blog/CategoryCardsRelated.php`

**Purpose**: Displays blog index card + two other category cards (excluding current category). Used on category pages to link to related content.

### Props

| Prop                | Type   | Required | Default | Description                      |
| ------------------- | ------ | -------- | ------- | -------------------------------- |
| `$current_category` | string | Yes      | ''      | Current category slug to exclude |

### Usage Example

```php
// On category page after pagination
$current_category_slug = $page_category_slug; // Use preserved category slug
include __DIR__ . '/../../components/blog/CategoryCardsRelated.php';
```

### Features

- Always displays blog index card as first card
- Shows two other category cards (excluding current category)
- Uses same styling and layout as CategoryCards component
- Three-column grid (responsive: 1 col mobile, 2 cols tablet, 3 cols desktop)
- Category-specific icons and colors
- Hover effects with smooth transitions

### Card Order

1. **Blog Index Card** (always first)

   - Name: "Alle Artikel"
   - URL: `/insights/`
   - Description: "Entdecke alle Artikel zu Schichtplanung, Zeiterfassung und HR-Management."
   - Icon: Grid/list icon
   - Colors: Grey (`#F3F4F6` background, `#6B7280` text)

2. **Two Other Category Cards** (excluding current)
   - Selected from remaining categories (lexikon, ratgeber, inside-ordio)
   - Uses same category-specific icons and colors as CategoryCards

### Implementation Details

- Filters out current category from available categories
- Selects first 2 categories from filtered list
- Combines blog index card with selected categories
- Uses same CSS classes and styling as CategoryCards for consistency

## Blog Index Dynamic Filtering

**File**: `v2/js/blog-index-filter.js`

**Purpose**: Alpine.js component for dynamic category filtering on blog index page. Supports client-side filtering, pagination, and URL state management.

### Alpine.js Data Function

```javascript
blogIndexFilterData(allPosts, categories, postsPerPage);
```

### Parameters

| Parameter      | Type   | Required | Default | Description                    |
| -------------- | ------ | -------- | ------- | ------------------------------ |
| `allPosts`     | array  | Yes      | []      | All blog posts (from PHP)      |
| `categories`   | object | No       | {}      | Category data with post counts |
| `postsPerPage` | number | No       | 12      | Posts per page                 |

### Data Properties

| Property           | Type    | Description                                     |
| ------------------ | ------- | ----------------------------------------------- |
| `selectedCategory` | string  | Currently selected category filter ('' for all) |
| `allPosts`         | array   | All blog posts                                  |
| `filteredPosts`    | array   | Filtered posts (computed)                       |
| `currentPage`      | number  | Current page number                             |
| `postsPerPage`     | number  | Posts per page                                  |
| `isFiltering`      | boolean | Whether filtering animation is in progress      |
| `totalPages`       | number  | Total pages (computed)                          |
| `categoryCounts`   | object  | Post counts per category (computed)             |

### Methods

| Method                                | Description                                 |
| ------------------------------------- | ------------------------------------------- |
| `init()`                              | Initialize component, read URL state        |
| `initFromURL()`                       | Read filter state from URL query parameters |
| `filterByCategory(category)`          | Filter posts by category                    |
| `goToPage(page)`                      | Navigate to specific page                   |
| `updateURL()`                         | Update browser URL without reload           |
| `isCategoryActive(category)`          | Check if category is currently active       |
| `getCategoryCount(category)`          | Get post count for category                 |
| `isPostVisible(postCategory, index)`  | Check if post should be visible             |
| `postMatchesCategory(post, category)` | Check if post matches category filter       |
| `formatDate(dateString)`              | Format date string for display              |

### Usage Example

**PHP Template**:

```php
<section id="blog-posts-section"
         x-data="blogIndexFilterData(<?php echo htmlspecialchars(json_encode($all_posts), ENT_QUOTES, 'UTF-8'); ?>, <?php echo htmlspecialchars(json_encode($categories), ENT_QUOTES, 'UTF-8'); ?>, <?php echo intval($posts_per_page); ?>)"
         x-init="init()"
         class="blog-posts-section">

    <!-- Filter Buttons -->
    <div class="blog-filter-buttons">
        <button @click="filterByCategory('')" :class="{ 'active': isCategoryActive('') }">
            Alle Artikel (<span x-text="getCategoryCount('')"></span>)
        </button>
        <!-- More filter buttons -->
    </div>

    <!-- Posts Grid -->
    <div class="blog-posts-grid">
        <?php foreach ($all_posts as $index => $post): ?>
            <div x-show="isPostVisible('<?php echo $post['category']; ?>', <?php echo $index; ?>)">
                <!-- Post card -->
            </div>
        <?php endforeach; ?>
    </div>

    <!-- Pagination -->
    <div x-show="totalPages > 1">
        <button @click="goToPage(currentPage - 1)" :disabled="currentPage === 1">Previous</button>
        <!-- Page numbers -->
        <button @click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">Next</button>
    </div>
</section>
```

### Features

- **Client-side filtering**: No page reload required
- **Dynamic pagination**: Adapts to filtered results
- **URL state management**: Filter state in URL query parameters
- **Smooth animations**: Fade transitions (300-500ms)
- **Back/forward support**: Browser history integration
- **SEO-friendly**: All posts rendered server-side

### URL Parameters

- `?category=lexikon` - Filter by category
- `?page=2` - Navigate to page 2
- `?category=ratgeber&page=1` - Combined filter and page

### Animation Timing

- Enter: 300ms ease-out
- Leave: 200ms ease-in
- Supports `prefers-reduced-motion` media query

## BlogImageLightbox Component

**File**: `v2/components/blog/BlogImageLightbox.php`

**Purpose**: Displays a full-screen modal lightbox for blog post images when clicked. Uses Alpine.js for state management and interactions.

### Overview

The `BlogImageLightbox.php` component provides a full-screen modal lightbox for displaying blog post images. It includes full accessibility support, keyboard navigation, and smooth animations matching Ordio design.

### Alpine.js State

The lightbox requires the following Alpine.js state on the parent element (typically `<body>`):

```javascript
x-data="{
  imageLightboxOpen: false,
  imageLightboxSrc: '',
  imageLightboxAlt: '',
  imageLightboxSrcset: '',
  imageLoaded: false,
  imageError: false,
  openImageLightbox(src, alt, srcset = '') {
    // Uses largest image from srcset if available
    let finalSrc = src;
    if (srcset && window.blogImageLightboxUtils) {
      const largestSrc = window.blogImageLightboxUtils.getLargestImageFromSrcset(srcset);
      if (largestSrc) {
        finalSrc = largestSrc;
      }
    }
    this.imageLightboxSrc = finalSrc;
    this.imageLightboxAlt = alt || 'Bild';
    this.imageLightboxSrcset = srcset || '';
    this.imageLightboxOpen = true;
    document.body.style.overflow = 'hidden';
    // Focus close button for accessibility
    this.$nextTick(() => {
      const closeBtn = document.querySelector('.blog-image-lightbox-close');
      if (closeBtn) {
        closeBtn.focus();
      }
    });
  },
  closeImageLightbox() {
    this.imageLightboxOpen = false;
    document.body.style.overflow = '';
    setTimeout(() => {
      if (!this.imageLightboxOpen) {
        this.imageLightboxSrc = '';
        this.imageLightboxAlt = '';
        this.imageLightboxSrcset = '';
      }
    }, 300);
  }
}"
```

### Image Trigger Setup

Images in blog post content are automatically wrapped in clickable containers by `PostContent.php`. The wrapper includes:

- `class="blog-image-lightbox-trigger"` - CSS class for styling
- `@click="openImageLightbox(...)"` - Alpine.js click handler
- `role="button"` - Accessibility role
- `tabindex="0"` - Keyboard accessibility
- `aria-label` - Screen reader label

### Features

- **Responsive Images**: Automatically uses largest image from `srcset` attribute
- **Keyboard Navigation**: Escape key closes modal, Tab focuses close button
- **Accessibility**: Full ARIA support, focus management, screen reader announcements
- **Smooth Animations**: Fade and scale transitions matching Ordio design
- **Body Scroll Lock**: Prevents background scrolling when modal is open
- **Mobile Optimized**: Touch-friendly close button and responsive sizing

### CSS Classes

- `.blog-image-lightbox-modal` - Main modal container
- `.blog-image-lightbox-close` - Close button
- `.blog-image-lightbox-container` - Image container wrapper
- `.blog-image-lightbox-image` - Lightbox image
- `.blog-image-lightbox-trigger` - Clickable image wrapper in post content

### JavaScript Utilities

The lightbox uses `v2/js/blog-image-lightbox.js` which provides:

- `getLargestImageFromSrcset(srcset)` - Extracts largest image URL from srcset string
- `preloadImage(src)` - Optional image preloading utility

### Accessibility

- **ARIA Attributes**: `role="dialog"`, `aria-modal="true"`, `aria-label` with image alt text
- **Keyboard Support**: Escape key closes, Tab navigates to close button
- **Focus Management**: Focus moves to close button on open, returns to trigger on close
- **Screen Reader**: Announces modal opening and image alt text

### Browser Support

- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile Safari (iOS)
- Chrome Mobile (Android)

## Related Documentation

- [Template Development Guide](TEMPLATE_DEVELOPMENT_GUIDE.md) - Template structure and usage
- [Data Structure Mapping](DATA_STRUCTURE_MAPPING.md) - Data format documentation
- [Blog Template Best Practices](BLOG_TEMPLATE_BEST_PRACTICES.md) - Best practices guide
- [Related Posts Logic](RELATED_POSTS_LOGIC.md) - Related posts algorithm documentation
- [Resource Matching Guide](RESOURCE_MATCHING_GUIDE.md) - Resource matching logic
