# Comparison Pages Creation Guide

**Last Updated:** 2026-04-01

### Section 3 (dark block, homepage parity)

After the **All-in-one** block, `<section id="section-3" aria-labelledby="reporting-insights-heading">` must include only:

`<?php include __DIR__ . '/../sections/partials/compare-reporting-insights-lanxess.php'; ?>`

That partial renders the shared **“und vieles mehr…”** reporting-insights module (see `v2/sections/reporting-insights-section.php`) and the Lanxess quote—**not** `three-card-testimonials.php`. Three-card quotes belong in **“In bester Gesellschaft”** below. Verify with `php v2/scripts/dev-helpers/verify-compare-pages-reporting-section.php`.

## Overview

This guide provides step-by-step instructions for creating new comparison pages for competitor tools. Comparison pages follow a standardized structure optimized for performance, SEO, and user experience.

### Data baseline (GSC) for improving existing comparison URLs

When optimizing an **already-live** comparison page, pull Search Console (and GA4 if relevant) **before** rewriting hero, meta, or FAQs. See [PAGE_IMPROVEMENT_DATA_PLAYBOOK.md](../../content/PAGE_IMPROVEMENT_DATA_PLAYBOOK.md). **Minimum:** GSC → Performance → filter by **exact page URL** → review Queries (clicks, impressions, CTR, position). **Optional:** export query-level JSON with `php v2/scripts/tools/collect-tool-gsc-queries.php --path=/your/public/path --output=docs/.../data/gsc-queries.json` (requires `v2/config/google-api-credentials.php`; parent dirs for `--output` are created automatically).

**Current Template System:**

- **Unified Template (Recommended for NEW pages):** `compare_template_unified.php` - Data-driven, single template for all pages
- **Legacy Templates (Existing pages, migration ongoing):** `compare_template_details.php` and `compare_template_nodetails.php` - Still in use for existing pages during migration

## Quick Start

### For New Pages (Recommended)

1. **Review the workflow guardrails** in `docs/ai/cursor-playbook.md` and the comparison page Cursor rules
2. **Use the unified template system** - See `docs/guides/comparison-pages/COMPARISON_PAGES_UNIFIED_TEMPLATE.md` for complete guide
3. **Create competitor data file** in `v2/data/competitors/{slug}.php`
4. **Generate page** using unified template
5. **Test** the page

### For Legacy Template Updates (Existing Pages)

1. **Review the workflow guardrails** in `docs/ai/cursor-playbook.md` and the comparison page Cursor rules
2. **Choose the appropriate legacy template**:
   - **`compare_template_details.php`** - Use when competitor has detailed features/integrations to showcase
   - **`compare_template_nodetails.php`** - Use when competitor is well-known or doesn't need detailed feature breakdown
3. **Copy the selected template** to `compare_{competitor}.php`
4. **Update content** (meta tags, competitor name, comparison data, image paths)
5. **Generate image variants** using `scripts/generate_responsive_logos.js`
6. **Test** the page at `http://localhost:8003/v2/pages/compare_{competitor}.php`

## Workflow Essentials for Cursor

1. **Plan first**
   - Ask Cursor for a concrete checklist covering copy, markup, assets, schema, QA, and tests before editing.
   - Reject vague plans; insist on file paths, selector names, and validation steps.
2. **Provide scoped context**
   - Use `@Files`/`@Code` references instead of pasting entire pages.
   - Call out the specific sections you want updated (hero copy, pricing table, FAQ, schema).
3. **Choose the right edit tool**
   - Use Multi-Edit or targeted diffs for repeated blocks (tables, FAQ items).
   - For large copy rewrites, replace the entire block once instead of iterative tiny edits.
   - Leave shared includes and `comparison-pages.css` untouched unless the task explicitly requires updates.
4. **Validate immediately**
   - Preview the page locally, check hero alignment, carousel, FAQ toggles, and CTA buttons.
   - Run schema and JSON validation; confirm `srcset`, preload, and Ordio styling are intact.
   - Summarise the validation steps in the task handoff.

## Data Extraction and Migration

### Automated Data Extraction

When migrating existing comparison pages or updating competitor data, use the automated extraction script:

```bash
python3 scripts/data/extract_competitor_data.py
```

This script extracts:

- Hero content (H1, description)
- Comparison grid data (ratings, pricing, features)
- FAQ sections (questions and answers)
- Details sections (if present)
- Schema data (excluding meta tags)
- Rating distributions
- Detailed ratings

### Data Validation

After extraction, validate the data:

```bash
python3 scripts/data/validate_extracted_data.py
php scripts/data/validate_and_compare.php
```

The validation scripts check:

- Rating distribution sums match review counts
- All required fields present
- Data consistency
- Comparison with current `competitors_data.php`

### Common Pitfalls to Avoid

1. **Rating Distribution Mismatches**

   - Ensure rating distribution counts sum to total reviews
   - Check percentages sum to ~100%

2. **Missing Details Sections**

   - Verify `has_details` flag matches actual page content
   - Check Details section extraction for all content types (lists, paragraphs, mixed)

3. **Pricing Extraction**

   - Some pages may have `0.00` if pricing is not clearly displayed
   - Verify pricing from source pages manually if extraction fails

4. **FAQ Extraction**

   - Some pages may have FAQ in different structures
   - Verify FAQ count matches actual page content

5. **Description Accuracy**
   - Extracted descriptions should be full, not placeholder text
   - Verify similarity with source page descriptions

### Data Update Process

1. **Extract Data**: Run `extract_competitor_data.py`
2. **Validate Data**: Run validation scripts
3. **Compare**: Run `validate_and_compare.php` to identify discrepancies
4. **Update**: Use `update_competitors_data.php` or manual updates
5. **Verify**: Re-run comparison to confirm updates

## Step-by-Step Process

### Step 1: Prepare Images

#### Image Requirements

1. **Competitor Logo** (for comparison page hero):

   - Format: WebP
   - Naming: `{competitor}-vergleich-logo.webp`
   - Location: `v2/img/alternativen/`
   - Recommended size: 320x64px (aspect ratio ~5:1)

2. **Competitor Logo** (for tool cards and carousel):
   - Format: WebP
   - Naming: `{competitor}-logo.webp`
   - Location: `v2/img/alternativen/`
   - Recommended size: 160x160px (square)

#### Generate Responsive Variants

After adding the source images, run:

```bash
node scripts/generate_responsive_logos.js
```

This generates:

- **Regular logos**: `{competitor}-logo-64w.webp`, `{competitor}-logo-80w.webp`, `{competitor}-logo-128w.webp`, `{competitor}-logo-160w.webp`
- **Comparison logos**: `{competitor}-vergleich-logo-160w.webp`, `{competitor}-vergleich-logo-320w.webp`

### Step 2: Create Page File

1. **Choose the appropriate template**:

   - **`compare_template_details.php`** - Use when competitor has detailed features/integrations to showcase (expandable Details section)
   - **`compare_template_nodetails.php`** - Use when competitor is well-known or doesn't need detailed feature breakdown (uses invisible placeholder for height alignment)

2. **Copy the selected template**:

   ```bash
   # For pages with Details section:
   cp v2/pages/compare_template_details.php v2/pages/compare_{competitor}.php

   # OR for pages without Details section:
   cp v2/pages/compare_template_nodetails.php v2/pages/compare_{competitor}.php
   ```

3. **Update competitor-specific content**:
   - Replace competitor name throughout (title, meta tags, schema, content)
   - Update competitor description, rating, pricing
   - Update image paths (change `template_details-vergleich-logo-160w.webp` or `template_nodetails-vergleich-logo-160w.webp` to `{competitor}-vergleich-logo-160w.webp`)
   - If using Details template, update the Details section (features, integrations, special characteristics)

### Step 3: Update Meta Tags

Update the following in the `<head>` section:

```php
<title>{Competitor} Alternativen: Vergleich & Bewertung 2026 - Ordio</title>
<meta name="description" content="{Competitor} Alternativen im Vergleich 2026: Ordio vs {Competitor}. [Focus/Use Case] für [Target Audience]. Finde die beste [Category]-Alternative." />
<meta name="keywords" content="{Competitor} Alternativen, {Competitor} Vergleich, {Competitor} [Feature], {Competitor} Kosten, Ordio vs {Competitor}, [Category] Software" />
```

Update canonical URL:

```php
<link rel="canonical" href="https://www.ordio.com/alternativen/{competitor}-vergleich">
```

### Step 4: Update Open Graph Tags

Update OG tags for social sharing:

```php
<meta property="og:title" content="{Competitor} Alternativen: Vergleich & Bewertung 2026 - Ordio">
<meta property="og:description" content="{Competitor} Alternativen im Vergleich 2026: Ordio vs {Competitor}. [Description]">
<meta property="og:url" content="https://www.ordio.com/alternativen/{competitor}-vergleich">
```

### Step 5: Update Schema.org Markup

Update the JSON-LD structured data:

1. **WebPage** schema:

   - Update `@id`, `url`, `name`, `description`
   - Update `primaryImageOfPage` if needed

2. **Article** schema:

   - Update `@id`, `headline`, `description`
   - Update `datePublished` and `dateModified`

3. **Table** schema (comparison table):
   - Update competitor product information
   - Update pricing, ratings, features

4. **FAQPage:** Do **not** add `FAQPage` to the `<head>` `@graph`. FAQs use a single source of truth in `v2/data/compare-faqs/{slug}.json`; the page loads `v2/components/render-compare-faq.php` and emits JSON-LD after the footer via `v2/components/include-compare-faq-jsonld.php` (see Step 8).

### Step 6: Update Page Content

#### Hero Section

**For new template (`compare_template_v2.php`):**

The hero section is handled by the `hero.php` component:

```php
<?php
$generator = $pageGenerator; // For component compatibility
include __DIR__ . '/../components/comparison/hero.php';
?>
```

**Component Location:** `v2/components/comparison/hero.php`

**Required Variables:**

- `$competitor` - Array containing competitor data (name, category, focus)
- `$generator` - Instance of `ComparisonPageGenerator` class

**Component Features:**

- Automatically generates H1 with competitor name: `{Competitor} Alternativen: Vergleich & Bewertung 2026`
- Includes competitor-specific description text
- Displays static Ordio Schichtplanung illustration (SVG)
- Includes CTA buttons component
- Responsive design (mobile/desktop)
- Uses fallback values for missing data keys

**Image Requirements:**

- Hero illustration: `/v2/img/svg/comparison/hero-schichtplanung-card.svg` (214x262px)
- Image is preloaded with `fetchpriority="high"` and `loading="eager"` for LCP optimization

**For legacy templates (existing pages using `compare_template_details.php` or `compare_template_nodetails.php`):**

Update the main heading:

```php
<h1 class="...">
    {Competitor} Alternativen: <span class="text-ordio-blue">Vergleich</span> & <span class="text-ordio-blue">Bewertung</span> 2026
</h1>
```

Update the description paragraph with competitor-specific information.

#### Competitor Logo Image

Update the main competitor logo image:

```php
<img src="/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp"
     srcset="/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp 160w,
             /v2/img/alternativen/{competitor}-vergleich-logo-320w.webp 320w"
     sizes="(max-width: 640px) 160px, 160px"
     alt="{Competitor}"
     class="max-h-8 max-w-full object-contain"
     width="160"
     height="32"
     fetchpriority="high">
```

**Important**: Always include:

- `srcset` with 160w and 320w variants
- `sizes` attribute
- `width` and `height` attributes (prevents CLS)
- `fetchpriority="high"` for LCP optimization

#### Preload Link

Add preload link in `<head>` after canonical:

```php
<!-- Preload LCP image (competitor logo) -->
<link rel="preload" href="/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp" as="image" fetchpriority="high">
```

### Step 7: Update Comparison Section

The comparison section uses a card-based grid layout (not a table). Update the following:

#### Comparison Grid Structure

```php
<div class="comparison-grid" x-data="{ ordioDetailsHeight: 0 }" @height-changed="ordioDetailsHeight = $event.detail.height">
    <!-- Ordio Column -->
    <div class="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden ordio-card comparison-card">
        <!-- Ordio content -->
    </div>
    <!-- Competitor Column -->
    <div class="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden personio-card comparison-card">
        <!-- Competitor content -->
    </div>
</div>
```

#### Ordio Column Content

The Ordio column includes:

1. **Header** - Logo and star rating (4.9/5, 54 reviews)
2. **Ordio Comparison Content** - Include `ordio_comparison_content.php`
3. **Star Rating Distribution** - 5-star breakdown with percentages
4. **Ratings Section** - Reviewer ratings (Benutzerfreundlichkeit, Erfüllung der Anforderungen, etc.)
5. **Pricing Section** - Starter, Plus, Pro plans
6. **CTA Buttons** - "Kostenlos testen" and "Pläne ansehen"

#### Competitor Column Content

The competitor column structure depends on the template used:

**Details Template (`compare_template_details.php`):**

1. **Header** - Competitor logo and star rating
2. **Product Description** - Competitor overview
3. **Expandable Details Section** - Features ("Dazu zählen"), integrations ("Schnittstellen"), special characteristics ("Das macht [Competitor] besonders")
4. **Star Rating Distribution** - Competitor's rating breakdown
5. **Ratings Section** - Reviewer ratings
6. **Pricing Section** - Competitor pricing plans

**No Details Template (`compare_template_nodetails.php`):**

1. **Header** - Competitor logo and star rating
2. **Product Description** - Competitor overview
3. **Invisible Placeholder** - Maintains height alignment with Ordio Details section (no competitor details shown)
4. **Star Rating Distribution** - Competitor's rating breakdown
5. **Ratings Section** - Reviewer ratings
6. **Pricing Section** - Competitor pricing plans

**Key:** Use the appropriate template based on whether the competitor needs a Details section. Update competitor-specific data accordingly.

### Step 8: Update FAQ Section

Edit **once** in `v2/data/compare-faqs/{slug}.json` (`items[]` with `question` / `answer` HTML). The page should:

1. Set `$compare_faq_slug` to the JSON basename (e.g. `planday`), then `include` `v2/components/render-compare-faq.php` for the visible accordion.
2. After `footer.php`, `include` `v2/components/include-compare-faq-jsonld.php` so FAQPage JSON-LD matches the same file (plain text via `faq_answer_html_to_schema_plain_text()`). **Do not** duplicate FAQPage in the `<head>` `@graph`.

**Legacy hand-markup example (for reference only — prefer JSON + components above):**

```php
<details class="bg-white rounded-lg p-6 [&>summary::-webkit-details-marker]:hidden [&>summary::marker]:content-none group">
    <summary class="font-inter600 text-lg cursor-pointer flex items-center justify-between hover:text-ordio-blue transition-colors duration-200">
        <span>{Question}</span>
        <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
        </svg>
    </summary>
    <div class="mt-4 text-gray-600 transition-all duration-300 ease-in-out">
        <p class="mb-4">{Answer}</p>
    </div>
</details>
```

**Verification:** `php v2/scripts/dev-helpers/verify-faq-jsonld-parity.php --file=compare-faqs/{slug}.json` or `--all-compare`; `php v2/scripts/dev-helpers/audit-compare-faq-ssot.php`.

## Competitors Data Structure

### Overview

All competitor data is stored in `v2/data/competitors_data.php` in the `getAllCompetitorsData()` function. This centralized data file ensures consistency across all comparison pages and enables dynamic page generation.

### Data Structure

Each competitor entry follows this structure:

```php
'{competitor_slug}' => [
    'slug' => '{competitor_slug}',
    'name' => '{Competitor Name}',
    'rating' => '{rating}',           // e.g., '4.3'
    'reviews' => '{count}',           // e.g., '368'
    'description' => '{description}', // Minimum 200 words recommended
    'category' => '{Category}',
    'focus' => '{Focus Area}',
    'target' => '{Target Audience}',
    'logo_alt' => '{Alt Text}',
    'logo_class' => '{CSS Classes}',
    'pricing' => [
        'starting_price' => '{price}',  // Keep for backward compatibility
        'price_unit' => '{unit}',       // e.g., 'pro Standort pro Monat' (fallback if no plans)
        'currency' => '{currency}',      // e.g., 'EUR' or 'USD'
        'plans' => [                     // Optional: array of pricing plans
            [
                'name' => '{Plan Name}',                    // Required: e.g., 'Starter', 'Basic', 'Professional'
                'description' => '{Description}',            // Optional: plan description
                'price' => '{price}',                       // Required: numeric string (e.g., '89.00') or text ('Auf Anfrage', 'Kostenlos')
                'price_text' => '{Formatted Price}',        // Optional: formatted display text (e.g., '€89,00')
                'frequency' => '{Frequency}',               // Optional: e.g., '/ User / Monat', '/ Standort / Monat'
                'recommendation' => '{Recommendation}',     // Optional: e.g., 'Empfohlen für 1-5 User'
            ],
            // ... more plans
        ],
    ],
    'faq' => [
        [
            'question' => '{Question}',
            'answer' => '{Answer}',
        ],
        // Minimum 6 FAQ items required
    ],
    'rating_distribution' => [
        '5' => ['percentage' => {pct}, 'count' => {count}],
        '4' => ['percentage' => {pct}, 'count' => {count}],
        '3' => ['percentage' => {pct}, 'count' => {count}],
        '2' => ['percentage' => {pct}, 'count' => {count}],
        '1' => ['percentage' => {pct}, 'count' => {count}],
    ],
    // Must sum to 100%
    'schema' => [
        'name' => '{Competitor Name}',
        'description' => '{Description}',
        'url' => '{slug}-vergleich',
    ],
    'has_details' => true|false,
    // Optional: 'details' => [...], 'detailed_ratings' => [...]
]
```

### Data Quality Requirements

**Required Fields:**

- All fields listed above must be present
- No placeholder values (e.g., rating '4.9', reviews '54', pricing '89 EUR')
- Rating distributions must sum to exactly 100%
- Minimum 6 FAQ items per competitor
- Descriptions should be minimum 200 words, competitor-specific

**Validation:**

- Run `python3 scripts/audit_competitors_data.py` to check data quality
- Run `python3 scripts/validate_page_data_mapping.py` to verify page-to-data mapping
- Run `python3 scripts/identify_missing_data.py` to identify data gaps

**Updating Data:**

- Use `python3 scripts/update_competitors_data_safe.py --dry-run` to preview updates
- Always create backups before updating
- Test pages after data updates

### Data Extraction

**Extract from Comparison Pages:**

```bash
# Extract all competitor data
python3 scripts/extract_competitor_data_comprehensive.py

# Extract detailed ratings
python3 scripts/extract_detailed_ratings.py

# Extract competitor details
python3 scripts/extract_competitor_details.py

# Merge all extractions
python3 scripts/merge_extracted_data.py
```

**Update Data File:**

```bash
# Dry run first
python3 scripts/update_competitors_data_safe.py --dry-run

# Apply updates
python3 scripts/update_competitors_data_safe.py

# Fix rating distributions
python3 scripts/fix_rating_distributions.py
```

## Required Components

### PHP Includes

**For new template (`compare_template_v2.php`):**

All comparison pages must include:

```php
<?php
// Head section
include '../base/head.php';

// Header
$headerwidth = "w-full";
include '../base/header.php';

// Hero Section (new component-based approach)
$generator = $pageGenerator;
include __DIR__ . '/../components/comparison/hero.php';

// Customer logo marquee (shared partial; see docs/systems/shared-components/CUSTOMER_LOGO_MARQUEE.md)
$customer_logo_marquee_options = [
    'variant' => 'comparison',
    'show_label' => true,
    'label_text' => 'Täglich genutzt von +2.000+ Unternehmen',
    'duration_seconds' => 75,
    'outer_class' => 'mb-4',
];
include __DIR__ . '/../sections/partials/customer-logo-marquee.php';
unset($customer_logo_marquee_options);

// Comparison Grid
include __DIR__ . '/../components/comparison/grid.php';

// Legal Notice
include __DIR__ . '/../components/comparison/legal_notice.php';

// All-in-One Section
include __DIR__ . '/../components/comparison/all_in_one.php';

// Ordio Features
include __DIR__ . '/../components/comparison/ordio_features.php';

// Testimonials
include __DIR__ . '/../components/comparison/testimonials.php';

// Comparison Carousel
include __DIR__ . '/../components/comparison/compare_carousel.php';

// FAQ Section (generated by ComparisonPageGenerator)
echo $pageGenerator->generateFAQ();

// Footer
$color_fill = '#fff';
$color_background = '#fbfbfb';
$rotate = '0';
$margin_bottom = 'mb-24';
include '../base/footer.php';

// Lead Capture Popup
include '../components/lead-capture-popup.php';
?>
```

**For legacy templates (existing pages using `compare_template_details.php` or `compare_template_nodetails.php`):**

All comparison pages must include:

```php
<?php
// Head section
include '../base/head.php';

// Header
$headerwidth = "w-full";
include '../base/header.php';

// CTA Buttons
$ctaPositionLeft = 'true';
$ctaButtonsAOS = 'false';
$ctaButtonsLight = 'yes';
$showCallbackButton = 'yes';
include '../base/include_ctabuttons.php';

// Ordio Comparison Content
include '../components/ordio_comparison_content.php';

// Comparison Carousel
include '../base/compare_carousel.php';

// Footer
$color_fill = '#fff';
$color_background = '#fbfbfb';
$rotate = '0';
$margin_bottom = 'mb-24';
include '../base/footer.php';

// Lead Capture Popup
include '../components/lead-capture-popup.php';
?>
```

### Hero Component (`v2/components/comparison/hero.php`)

**Purpose:** Displays the hero section with competitor-specific content and Ordio illustration.

**Required Variables:**

- `$competitor` - Array containing:
  - `name` - Competitor name (e.g., "Personio")
  - `category` - Software category (e.g., "HR-Software")
  - `focus` - Target audience/focus (e.g., "Business Software")
- `$generator` - Instance of `ComparisonPageGenerator` class

**Features:**

- Generates H1: `{Competitor} Alternativen: Vergleich & Bewertung 2026`
- Includes competitor-specific description
- Displays Ordio Schichtplanung card illustration (SVG)
- Includes CTA buttons component
- Responsive layout (mobile/desktop)
- Fallback values for missing data keys

**Image Requirements:**

- Hero illustration: `/v2/img/svg/comparison/hero-schichtplanung-card.svg`
- Dimensions: 214x262px
- Optimization: `fetchpriority="high"`, `loading="eager"`

**Usage Example:**

```php
<?php
// Ensure $competitor and $generator are set before including
$generator = $pageGenerator;
include __DIR__ . '/../components/comparison/hero.php';
?>
```

### CSS File

Include the comparison pages CSS:

```php
<link rel="stylesheet" href="/v2/css/comparison-pages.css?v=<?php echo filemtime($_SERVER['DOCUMENT_ROOT'] . '/v2/css/comparison-pages.css'); ?>" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/v2/css/comparison-pages.css"></noscript>
```

**Note**: Use `filemtime()` for cache busting.

## Image Optimization Requirements

### Responsive Images

All images must use `srcset` and `sizes` attributes:

```html
<img
  src="/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp"
  srcset="
    /v2/img/alternativen/{competitor}-vergleich-logo-160w.webp 160w,
    /v2/img/alternativen/{competitor}-vergleich-logo-320w.webp 320w
  "
  sizes="(max-width: 640px) 160px, 160px"
  alt="{Competitor}"
  width="160"
  height="32"
/>
```

### Image Preloading

Preload the LCP (Largest Contentful Paint) image:

```html
<link
  rel="preload"
  href="/v2/img/alternativen/{competitor}-vergleich-logo-160w.webp"
  as="image"
  fetchpriority="high"
/>
```

### Image Dimensions

Always include explicit `width` and `height` attributes to prevent Cumulative Layout Shift (CLS).

## Performance Optimization Checklist

- [ ] Images are in WebP format
- [ ] Responsive image variants generated (160w, 320w)
- [ ] `srcset` and `sizes` attributes added to images
- [ ] `width` and `height` attributes added to images
- [ ] LCP image preloaded with `fetchpriority="high"`
- [ ] CSS file loaded with `media="print"` trick for non-blocking
- [ ] No inline `<style>` blocks (use `comparison-pages.css`)
- [ ] JavaScript optimized (use `requestAnimationFrame` for animations)
- [ ] AOS (Animate On Scroll) deferred with `requestIdleCallback`

## Annual Year Update

Each year, update comparison pages for the new calendar year.

**Update to new year:**
- Hero H1: `Vergleich & Bewertung 20XX`
- FAQ section titles: `Alternativen 20XX`
- Schema `datePublished` fallbacks: `ordio_get_page_published_iso('20XX-01-01T00:00:00+01:00')`
- `competitor_template_data.php`: default FAQ title "Alternativen 20XX"
- Meta, OG, and Twitter titles containing year

**Preserve (do NOT change):**
- Notice note: "Stand: [Month] 20XX" – indicates when OMR comparison data was last verified (e.g. "Stand: Oktober 2025")

**Files:** `v2/data/competitor_template_data.php`, all `v2/pages/compare_*.php`. Use bulk search/replace; verify Stand lines are excluded. See `.cursor/rules/comparison-pages-content.mdc` for full checklist.

## SEO Best Practices

### Meta Tags

- [ ] Unique, descriptive title (include year)
- [ ] Compelling meta description (155-160 characters)
- [ ] Relevant keywords (competitor name, features, use cases)
- [ ] Canonical URL set correctly
- [ ] Open Graph tags for social sharing
- [ ] Twitter Card tags

### Schema.org Markup

- [ ] WebPage schema with correct URL and description
- [ ] Article schema with headline and dates
- [ ] Table schema for comparison data
- [ ] Product schema for competitor and Ordio
- [ ] AggregateRating schema if applicable

### Content

- [ ] Unique, valuable content (not duplicated from other pages)
- [ ] H1 tag with competitor name and year
- [ ] Proper heading hierarchy (H1 → H2 → H3)
- [ ] Internal links to relevant Ordio pages
- [ ] FAQ section with competitor-specific questions
- [ ] Comparison tables with accurate data
- [ ] Copy uses du tone, mentions Ordio naturally, and avoids praising competitors

## Accessibility Requirements

- [ ] All images have descriptive `alt` attributes
- [ ] Buttons have `aria-label` attributes
- [ ] Links have descriptive text or `aria-label`
- [ ] Touch targets are minimum 44x44px (handled by CSS)
- [ ] Color contrast meets WCAG AA standards
- [ ] Keyboard navigation works correctly
- [ ] Screen reader friendly (semantic HTML)

## CSS Guidelines

### Use Comparison Pages CSS

All comparison page styles are in `v2/css/comparison-pages.css`. Do NOT add inline styles.

### Available Classes

- `.rating-bar-container` - Rating bar wrapper
- `.rating-bar-bg` - Rating bar background
- `.rating-bar-fill` - Rating bar fill
- `.comparison-grid` - Comparison grid layout
- `.comparison-card` - Comparison card container
- `.star-svg` - Star icon (filled)
- `.star-svg-empty` - Star icon (empty)

### Touch Target Sizing

Touch target sizing is automatically applied to main content links and buttons. Navbar, footer, and banner elements are excluded.

## File Structure

```
v2/
├── pages/
│   └── compare_{competitor}.php    # Comparison page
├── img/
│   └── alternativen/
│       ├── {competitor}-logo.webp
│       ├── {competitor}-logo-64w.webp
│       ├── {competitor}-logo-80w.webp
│       ├── {competitor}-logo-128w.webp
│       ├── {competitor}-logo-160w.webp
│       ├── {competitor}-vergleich-logo.webp
│       ├── {competitor}-vergleich-logo-160w.webp
│       └── {competitor}-vergleich-logo-320w.webp
├── css/
│   └── comparison-pages.css        # Shared CSS for all comparison pages
└── base/
    ├── head.php                     # Head section
    ├── header.php                   # Header/navbar
    ├── footer.php                   # Footer
    ├── include_ctabuttons.php       # CTA buttons
    └── compare_carousel.php         # Comparison carousel
```

## Data Validation Checklist

Before updating `competitors_data.php`, verify:

- [ ] Rating distribution counts sum to total reviews
- [ ] Rating percentages sum to ~100% (allow 95-105% range)
- [ ] All required fields present (rating, reviews, description, pricing)
- [ ] Description is full text, not placeholder
- [ ] Pricing information is accurate and matches source page
- [ ] FAQ count matches actual FAQ items on page
- [ ] Details section extraction complete (if `has_details` is true)
- [ ] Details sections have correct structure (sections array with title, type, content)
- [ ] Schema data matches page content
- [ ] No syntax errors in extracted JSON
- [ ] Comparison with current data shows expected discrepancies only

After updating `competitors_data.php`, verify:

- [ ] PHP syntax is valid (run `php -l v2/data/competitors_data.php`)
- [ ] No double commas or syntax errors
- [ ] All string values properly escaped
- [ ] Details sections properly formatted (if present)
- [ ] Re-run comparison script shows updated values
- [ ] Visual verification in browser matches expected content

## Testing Checklist

### Local Testing

Before publishing, test the page locally at `http://localhost:8003/v2/pages/compare_{competitor}.php`:

#### Visual Testing

- [ ] Page loads correctly (HTTP 200)
- [ ] Hero section displays correctly with competitor logo
- [ ] Company logos section scrolls smoothly
- [ ] Comparison grid displays side-by-side cards
- [ ] Ordio column shows all sections (content, ratings, pricing, CTA)
- [ ] Competitor column shows all sections
- [ ] Columns have equal heights (no misalignment)
- [ ] Expandable details sections work (click to expand/collapse)
- [ ] Star ratings display correctly
- [ ] Rating distribution bars show correct percentages
- [ ] Pricing cards display correctly
- [ ] CTA buttons are visible and styled correctly
- [ ] FAQ accordion opens/closes smoothly
- [ ] Comparison carousel displays other competitors
- [ ] Carousel navigation arrows work
- [ ] Carousel auto-rotates (stops on interaction)
- [ ] Footer displays correctly
- [ ] Lead capture popup appears (if triggered)

#### Responsive Design Testing

Test on multiple viewport sizes:

- [ ] **Mobile (375px)**: Layout stacks correctly, images load appropriate sizes
- [ ] **Tablet (768px)**: Layout adapts, carousel shows 2 cards
- [ ] **Desktop (1280px)**: Full layout, carousel shows 4 cards
- [ ] **Large Desktop (1920px)**: Content remains centered, no overflow

#### Functional Testing

- [ ] All CTA buttons link correctly
- [ ] Modal opens when "Kostenlos testen" clicked
- [ ] External links open in new tabs (if applicable)
- [ ] Internal links navigate correctly
- [ ] Carousel cards link to correct comparison pages
- [ ] FAQ items expand/collapse correctly
- [ ] No JavaScript errors in console
- [ ] Alpine.js components initialize correctly
- [ ] Height synchronization works between columns

#### Image Testing

- [ ] Hero logo loads with correct srcset
- [ ] Comparison column logos load correctly
- [ ] Carousel logos load with correct sizes
- [ ] All images have alt attributes
- [ ] Images use WebP format
- [ ] No broken image links
- [ ] Images load appropriate sizes for viewport

#### Performance Testing

Run Lighthouse audit:

- [ ] **Performance Score** > 90
- [ ] **LCP (Largest Contentful Paint)** < 2.5s
- [ ] **FID (First Input Delay)** < 100ms
- [ ] **CLS (Cumulative Layout Shift)** < 0.1
- [ ] **FCP (First Contentful Paint)** < 1.8s
- [ ] **TTI (Time to Interactive)** < 3.8s

#### Schema Validation

- [ ] Copy JSON-LD from page source
- [ ] Paste into [Google Rich Results Test](https://search.google.com/test/rich-results)
- [ ] Verify no errors
- [ ] Check all required fields present:
  - [ ] WebPage schema
  - [ ] Article schema
  - [ ] Table schema with Product entities
  - [ ] BreadcrumbList schema
  - [ ] FAQPage JSON-LD (post-footer script from `compare-faqs/{slug}.json`, not duplicated in `<head>`)
  - [ ] Product schema (Ordio)

#### Accessibility Testing

- [ ] Run axe DevTools extension
- [ ] Check color contrast (WCAG AA minimum)
- [ ] Test keyboard navigation (Tab through page)
- [ ] Verify focus states visible
- [ ] Test with screen reader (VoiceOver/NVDA)
- [ ] Verify all images have descriptive alt text
- [ ] Check touch targets minimum 44x44px

### Production Testing

After deployment, test on production:

- [ ] Page loads at `https://www.ordio.com/alternativen/{competitor}-vergleich`
- [ ] All images load correctly
- [ ] Schema validates (Google Rich Results Test)
- [ ] Performance metrics acceptable
- [ ] Mobile experience works correctly
- [ ] No console errors
- [ ] Analytics tracking works (if applicable)

## Common Issues and Solutions

### Issue: Images not displaying

**Symptoms:** Broken image icons, 404 errors in console

**Solution**:

- Verify image files exist in `v2/img/alternativen/`
- Check file names match exactly (case-sensitive)
- Verify `srcset` paths are correct
- Run `node scripts/generate_responsive_logos.js` to generate variants
- Check file permissions

### Issue: Comparison columns not equal height

**Symptoms:** Columns misaligned, visual imbalance

**Solution**:

- Ensure Alpine.js is loaded (`x-data` attribute present)
- Verify `@height-changed` event listeners are set up
- Check that expandable sections dispatch height events
- Inspect browser console for Alpine.js errors
- Verify `comparison-grid` class is applied

### Issue: Expandable sections not working

**Symptoms:** Click doesn't expand/collapse, no animation

**Solution**:

- Ensure Alpine.js is loaded via `head.php`
- Verify `x-data="{ open: false }"` is present
- Check `@click="open = !open"` handler
- Verify `x-show="open"` and `x-transition` attributes
- Check browser console for JavaScript errors

### Issue: Navbar looks broken

**Symptoms:** Styling issues, layout problems

**Solution**:

- Ensure `comparison-pages.css` is loaded correctly
- Verify no inline styles override navbar styles
- Check that CSS file version is correct
- Verify `headerwidth = "w-full"` is set
- Check for CSS conflicts

### Issue: Layout shifts (CLS)

**Symptoms:** Content jumps during page load

**Solution**:

- Add explicit `width` and `height` attributes to all images
- Preload LCP image with `fetchpriority="high"`
- Ensure images are optimized (WebP format)
- Check for dynamic content loading above fold
- Verify font loading strategy

### Issue: Poor PageSpeed score

**Symptoms:** Low Lighthouse performance score

**Solution**:

- Optimize images (WebP, responsive variants)
- Preload LCP image (competitor logo)
- Defer non-critical CSS (`media="print"` trick)
- Optimize JavaScript (use `requestAnimationFrame`, `requestIdleCallback`)
- Check for render-blocking resources
- Minimize third-party scripts

### Issue: Carousel not working

**Symptoms:** Carousel doesn't rotate, navigation broken

**Solution**:

- Verify Alpine.js is loaded
- Check `compare_carousel.php` include is present
- Verify `tools_data.php` has competitor data
- Check browser console for errors
- Verify carousel JavaScript function is defined
- Test on different viewport sizes

### Issue: Schema validation errors

**Symptoms:** Google Rich Results Test shows errors

**Solution**:

- Validate JSON syntax (use jsonlint.com)
- Check all required fields present
- Verify URLs are absolute and correct
- Ensure dates use ISO 8601 format
- Check for trailing commas in JSON
- Verify Product schemas have required fields

### Issue: FAQ accordion not animating

**Symptoms:** FAQ opens instantly without transition

**Solution**:

- Verify `x-transition` attribute is present
- Check CSS transitions are defined
- Ensure `transition-all duration-300 ease-in-out` classes present
- Verify Alpine.js is loaded
- Check for CSS conflicts

## Scripts Reference

### generate_responsive_logos.js

Generates responsive image variants for logos.

**Usage**:

```bash
node scripts/generate_responsive_logos.js
```

**Requirements**: `sharp`, `glob` npm packages

### convert-to-webp.js

Converts PNG/JPG images to WebP format.

**Usage**:

```bash
node scripts/convert-to-webp.js "v2/img/alternativen"
```

**Requirements**: `sharp` npm package

### generate-resized-webp.js

Generates resized WebP variants for specific use cases.

**Usage**:

```bash
node scripts/generate-resized-webp.js "v2/img/alternativen"
```

**Requirements**: `sharp` npm package

### FAQ JSON-LD parity and SSOT (comparison pages)

```bash
php v2/scripts/dev-helpers/verify-faq-jsonld-parity.php --all-compare
php v2/scripts/dev-helpers/audit-compare-faq-ssot.php
php v2/scripts/dev-helpers/audit-faq-json-internal-links.php '--glob=v2/data/compare-faqs/*.json'
```

See `docs/content/FAQ_WEBSITE_STANDARD.md` § FAQPage JSON-LD.

## Best Practices

1. **Always duplicate an existing page** - Don't create from scratch
2. **Generate image variants** before creating the page
3. **Test locally** before committing
4. **Validate schema markup** using Google Rich Results Test
5. **Check PageSpeed Insights** for performance issues
6. **Review on mobile devices** for responsive design
7. **Verify all links** work correctly
8. **Check for typos** in competitor names and content

## Related Cursor Rules

When working with comparison pages, the following Cursor rules apply:

- **[comparison-pages-core.mdc](../../../.cursor/rules/comparison-pages-core.mdc)** - Core patterns: templates, hero, comparison grid, schema, meta tags, validation
- **[comparison-pages-content.mdc](../../../.cursor/rules/comparison-pages-content.mdc)** - Content patterns: edge cases, page structure, competitor data, Alpine.js
- **[shared-patterns.mdc](../../../.cursor/rules/shared-patterns.mdc)** - Universal validation checklist and shared patterns
- **[global.mdc](../../../.cursor/rules/global.mdc)** - Planning requirements and research checklists (always applies)

See [docs/ai/RULE_TO_DOC_MAPPING.md](../../ai/RULE_TO_DOC_MAPPING.md) for complete rule-to-documentation mapping.

## Support

For questions or issues:

- Review existing comparison pages for examples
- Check `comparison-pages.css` for available styles
- Refer to this guide for step-by-step instructions
