# blog-index-hero Full Instructions

## Overview

The blog index page uses a modern hero section matching product page styling, three-column category cards, and **server-side pagination** with navigational category links (`/insights/`, `/insights/lexikon/`, etc.). Listing page size is `ordio_blog_listing_posts_per_page()` (18 posts per page for the index and paginated categories).

## Current architecture (production)

- **`v2/pages/blog/index.php`**: Loads summaries, renders one page of PostCards, `Pagination.php`, `blog-pagination.min.js` (keyboard nav). No Alpine filter on the post grid; no inline JSON of all posts.
- **Do not reintroduce** the old Alpine `blogIndexFilterData` + all-posts-in-DOM pattern without an explicit performance review (HTML size / Semrush 2 MB threshold). See `docs/content/blog/guides/INDEX_PAGE_OPTIMIZATION.md`.

The sections below document **legacy** Alpine patterns retained for reference and for `blog-index-filter.js` if reused elsewhere.

## BlogIndexHero Component

### Purpose

Branded hero section for blog index and category pages with pill badge, title, description, and background gradient pattern.

### File Location

`v2/components/blog/BlogIndexHero.php`

### Required Props

- `$badge` (string): Badge text (e.g., "Blog", "Insights", category name)
- `$title` (string): Hero title
- `$description` (string): Hero description text
- `$variant` (string): 'index' | 'category' (default: 'index')

### Styling Requirements

- **Background**: Use `blog-page-body-gradient` class on body element
- **Badge**: White background with opacity (`bg-white bg-opacity-40`), rounded pill
- **Title**: Gilroy Bold font, responsive sizing (42px mobile, 72px desktop)
- **Description**: Responsive font size (16px mobile, 18px desktop), max-width for readability

### Usage Pattern

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

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

### CSS Classes

- `.blog-index-hero` - Hero section container
- `.blog-index-hero-badge` - Pill badge styling
- `.blog-index-hero-title` - Title styling
- `.blog-index-hero-description` - Description styling
- `.blog-page-body-gradient` - Background gradient pattern (applied to body)

## CategoryCards Component

### Purpose

Three-column grid of category cards linking to category pages with icons, names, post counts, and descriptions.

### File Location

`v2/components/blog/CategoryCards.php`

### Auto-Loading

Component automatically loads categories from `load_blog_categories()` if not provided.

### Category Colors

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

### Responsive Grid

- Mobile: 1 column
- Tablet (768px+): 2 columns
- Desktop (1024px+): 3 columns

### Hover Effects

- Card lift (`translateY(-4px)`)
- Icon scale (`scale(1.1)`)
- Arrow slide (`translateX(4px)`)
- Category-specific border colors

### CSS Classes

- `.category-cards-section` - Section container
- `.category-cards-grid` - Grid container
- `.category-card` - Individual card
- `.category-card-icon` - Icon wrapper
- `.category-card-title` - Category name
- `.category-card-count` - Post count
- `.category-card-description` - Description text
- `.category-card-arrow` - Arrow indicator

## Blog Index Dynamic Filtering (legacy / not used on live index)

### Purpose (historical)

Alpine.js-powered client-side filtering with dynamic pagination and URL state management. **Not used** on the current `index.php` post grid; prefer navigational links + server pagination.

### File Location

`v2/js/blog-index-filter.js`

### Alpine.js Integration

```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()">
```

### Filter Buttons Pattern

```php
<button
    @click="filterByCategory('lexikon')"
    :class="{ 'active': isCategoryActive('lexikon') }"
    class="blog-filter-button"
    type="button">
    Lexikon
    <span class="blog-filter-count">(<span x-text="getCategoryCount('lexikon')"></span>)</span>
</button>
```

### Post Visibility Pattern

```php
<?php foreach ($all_posts as $index => $post): ?>
    <div class="blog-post-card-wrapper"
         data-post-index="<?php echo intval($index); ?>"
         data-category="<?php echo htmlspecialchars($post['category'], ENT_QUOTES, 'UTF-8'); ?>"
         x-show="isPostVisible('<?php echo htmlspecialchars($post['category'], ENT_QUOTES, 'UTF-8'); ?>', <?php echo intval($index); ?>)"
         x-transition:enter="transition ease-out duration-300"
         x-transition:enter-start="opacity-0 transform translate-y-4"
         x-transition:enter-end="opacity-100 transform translate-y-0">
        <!-- Post card content -->
    </div>
<?php endforeach; ?>
```

### Pagination Pattern

```php
<div class="blog-pagination-wrapper" x-show="totalPages > 1" x-cloak>
    <button @click="goToPage(currentPage - 1)" :disabled="currentPage === 1">Previous</button>
    <template x-for="page in Array.from({length: totalPages}, (_, i) => i + 1)">
        <button @click="goToPage(page)" :class="{ 'active': page === currentPage }">
            <span x-text="page"></span>
        </button>
    </template>
    <button @click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">Next</button>
</div>
```

### Key Methods

- `filterByCategory(category)` - Filter posts by category
- `goToPage(page)` - Navigate to specific page
- `isPostVisible(postCategory, index)` - Check post visibility
- `getCategoryCount(category)` - Get post count for category
- `isCategoryActive(category)` - Check if category is active

### URL State Management

- Filter state stored in `?category=lexikon` query parameter
- Page state stored in `?page=2` query parameter
- Combined: `?category=ratgeber&page=1`
- Back/forward buttons supported via `popstate` event listener

### Animation Timing

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

### Performance Considerations

- All posts rendered server-side for SEO
- Client-side filtering uses `x-show` for efficient DOM updates
- Smooth scroll to posts section on filter/page change
- No layout shift (CLS) - posts maintain consistent dimensions

## Blog Index Page Structure

### Required Order

1. **Hero Section** - BlogIndexHero component
2. **Pillar Cards** - Two pillar page cards (only on page 1)
3. **Category Cards** - CategoryCards component (only on page 1)
4. **Latest Posts Section** - Posts grid with filter buttons and pagination

### Required Classes

- Body: `blog-page blog-page-body-gradient`
- Hero section: `.blog-index-hero`
- Posts section: `#blog-posts-section` (for scroll targeting)

### JavaScript Loading

```php
<script src="/v2/js/blog-index-filter.min.js?v=<?php echo filemtime(__DIR__ . '/../../js/blog-index-filter.min.js'); ?>" defer></script>
```

## Category Pages

### Hero Usage

Category pages use BlogIndexHero with category-specific content:

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

### Required Classes

- Body: `blog-page blog-page-body-gradient`

## CSS Requirements

### Filter Buttons

- `.blog-filter-buttons-wrapper` - Container
- `.blog-filter-buttons` - Button group
- `.blog-filter-button` - Individual button
- `.blog-filter-button.active` - Active state
- `.blog-filter-count` - Post count styling

### Pagination

- `.blog-pagination-wrapper` - Container
- `.blog-pagination` - Navigation container
- `.blog-pagination-button` - Button styling
- `.blog-pagination-button.active` - Active page
- `.blog-pagination-button.disabled` - Disabled state

### Post Cards

- `.blog-post-card-wrapper` - Wrapper for Alpine.js visibility
- `.blog-posts-grid` - Grid container

## Best Practices

1. **Always render all posts server-side** - Required for SEO
2. **Use `x-show` not `x-if`** - Maintains DOM structure for smooth animations
3. **Include `x-cloak` on pagination** - Prevents flash of unstyled content
4. **Scroll to posts section** - Use `scrollIntoView` on filter/page change
5. **Support reduced motion** - Use CSS media query for accessibility
6. **Minify JavaScript** - Always use `.min.js` version in production
7. **Validate JSON encoding** - Use `htmlspecialchars(json_encode())` for safety

## Common Pitfalls

1. **Missing `blog-page-body-gradient` class** - Hero background won't display
2. **Incorrect JSON encoding** - Can break Alpine.js initialization
3. **Missing `x-init="init()"`** - Filtering won't initialize
4. **Not using minified JS** - Performance impact
5. **Missing `data-category` attribute** - Post visibility won't work correctly

## Testing Checklist

- [ ] Hero displays with background gradient
- [ ] Category cards show correct post counts
- [ ] Filter buttons update grid dynamically
- [ ] Pagination adapts to filtered results
- [ ] URL updates without page reload
- [ ] Back/forward buttons work correctly
- [ ] Animations are smooth (300-500ms)
- [ ] Mobile responsive (1 col → 2 cols → 3 cols)
- [ ] Reduced motion support works
- [ ] All posts render server-side (check source)

## Related Files

- `v2/components/blog/BlogIndexHero.php` - Hero component
- `v2/components/blog/CategoryCards.php` - Category cards component
- `v2/js/blog-index-filter.js` - Filtering JavaScript
- `v2/css/blog-base.min.css`, `v2/css/blog-index.min.css` - Blog styles (hero, cards, filters, pagination). When adding new index/category styles, add to blog-index.css.
- `v2/pages/blog/index.php` - Blog index page template
- `v2/pages/blog/category.php` - Category page template
