# Demo CTA Pattern for Blog Posts

**Last Updated:** 2026-01-13

## Overview

All demo booking CTAs in blog posts should use Alpine.js modal buttons that open the demo booking modal directly, rather than linking to `/demo-vereinbaren`. This provides a better user experience by keeping users on the page and opening the booking form in a modal overlay.

## Pattern Implementation

### Standard Modal Button Pattern

**Always use Alpine.js store for modal buttons:**

```html
<button
  type="button"
  data-event-type="button_click"
  data-event-name="Demo vereinbaren"
  @click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"
  class="font-inter500 text-ordio-blue hover:text-blue-600 underline cursor-pointer bg-transparent border-none p-0"
>
  Demo vereinbaren
</button>
```

### Inline Link Style (Within Paragraphs)

For demo CTAs that appear inline within paragraph text:

```html
<button
  type="button"
  data-event-type="button_click"
  data-event-name="Demo vereinbaren"
  @click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"
  class="font-inter500 text-ordio-blue hover:text-blue-600 underline cursor-pointer bg-transparent border-none p-0"
>
  Demo vereinbaren
</button>
```

**Styling classes:** `font-inter500 text-ordio-blue hover:text-blue-600 underline cursor-pointer bg-transparent border-none p-0`

### Standalone Button Style

For demo CTAs that appear as standalone buttons:

```html
<button
  type="button"
  data-event-type="button_click"
  data-event-name="Demo vereinbaren"
  @click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"
  class="font-inter500 text-sm sm:text-base bg-white border border-[#EDEFF3] text-[#333] hover:bg-gray-50 shadow-md hover:shadow-lg p-4 px-6 rounded-full transition-all duration-200 min-h-[48px] inline-flex items-center justify-center"
>
  Demo vereinbaren
</button>
```

**Styling classes:** `font-inter500 text-sm sm:text-base bg-white border border-[#EDEFF3] text-[#333] hover:bg-gray-50 shadow-md hover:shadow-lg p-4 px-6 rounded-full transition-all duration-200 min-h-[48px] inline-flex items-center justify-center`

## Required Attributes

### Always Include:

1. **`type="button"`** - Prevents form submission if button is inside a form
2. **`data-event-type="button_click"`** - Required for GTM tracking
3. **`data-event-name="Demo vereinbaren"`** - Required for GTM tracking
4. **`@click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"`** - Alpine.js click handler to open modal

### Modal Parameters

The `$store.modal.open()` function takes two parameters:

- **Title:** `'Unverbindliche Demo vereinbaren'` (modal title)
- **Button text:** `'Unverbindliche Demo vereinbaren'` (submit button text in modal)

## Automatic Conversion

The `PostContent.php` component automatically converts any `<a>` tags with `href` containing `demo-vereinbaren` to modal buttons during content processing. This ensures:

- Existing posts with demo links are automatically converted
- Future posts with demo links are automatically converted
- Consistent behavior across all blog posts

### Conversion Logic

The conversion happens in `v2/components/blog/PostContent.php` and:

- Detects links with `href` containing `demo-vereinbaren`
- Determines context (inline vs standalone) based on parent element
- Converts to `<button>` with appropriate styling
- Preserves original link text
- Adds required tracking attributes

## What NOT to Do

### ❌ Never Use Links to Demo Page

```html
<!-- WRONG - Don't link to demo page -->
<a href="/demo-vereinbaren">Demo vereinbaren</a>
<a href="https://www.ordio.com/demo-vereinbaren">Demo vereinbaren</a>
```

### ❌ Never Use Local Variables

```html
<!-- WRONG - Local variables don't work with modal system -->
<button @click="modelOpen = true">Demo vereinbaren</button>
```

### ❌ Never Use Toggle Pattern

```html
<!-- WRONG - Toggle pattern doesn't work -->
<button @click="modelOpen = !modelOpen">Demo vereinbaren</button>
```

## Examples

### Example 1: Inline Demo Link in Paragraph

**Before (incorrect):**

```html
<p>
  Schau dir unverbindlich an, wie du Zuschläge bei Ordio automatisch berechnen
  lassen kannst!
  <a href="https://www.ordio.com/demo-vereinbaren">Demo vereinbaren</a>
</p>
```

**After (correct):**

```html
<p>
  Schau dir unverbindlich an, wie du Zuschläge bei Ordio automatisch berechnen
  lassen kannst!
  <button
    type="button"
    data-event-type="button_click"
    data-event-name="Demo vereinbaren"
    @click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"
    class="font-inter500 text-ordio-blue hover:text-blue-600 underline cursor-pointer bg-transparent border-none p-0"
  >
    Demo vereinbaren
  </button>
</p>
```

### Example 2: Standalone Demo Button

**Before (incorrect):**

```html
<div>
  <a href="/demo-vereinbaren">Demo vereinbaren</a>
</div>
```

**After (correct):**

```html
<div>
  <button
    type="button"
    data-event-type="button_click"
    data-event-name="Demo vereinbaren"
    @click="$store.modal.open('Unverbindliche Demo vereinbaren', 'Unverbindliche Demo vereinbaren')"
    class="font-inter500 text-sm sm:text-base bg-white border border-[#EDEFF3] text-[#333] hover:bg-gray-50 shadow-md hover:shadow-lg p-4 px-6 rounded-full transition-all duration-200 min-h-[48px] inline-flex items-center justify-center"
  >
    Demo vereinbaren
  </button>
</div>
```

## Technical Details

### Alpine.js Store

The modal system uses Alpine.js store defined in `v2/base/head.php`:

```javascript
Alpine.store("modal", {
  modelOpen: false,
  modalContentTitle: "Kostenlos und unverbindlich testen",
  modalContentButton: "",
  open(title, button) {
    this.modelOpen = true;
    this.modalContentTitle = title || "Kostenlos und unverbindlich testen";
    this.modalContentButton = button || "";
  },
  close() {
    this.modelOpen = false;
  },
});
```

### DOM Processing

When `PostContent.php` processes blog post HTML:

1. Uses XPath to find all `<a>` tags with `href` containing `demo-vereinbaren`
2. Checks parent element to determine context (inline vs standalone)
3. Creates `<button>` element with appropriate attributes
4. Replaces original `<a>` tag with new `<button>`

### Compatibility

- Works with Alpine.js (loaded via `head.php`)
- Compatible with GTM tracking (via `data-event-type` and `data-event-name`)
- Accessible (uses semantic `<button>` element)
- SEO-friendly (button text preserved for context)

## Related Documentation

- [Blog Content Processing Guide](../guides/blog-content-processing.md)
- [CTA Button Patterns](../../development/CTA_BUTTON_PATTERNS.md)
- [Alpine.js Modal System](../../development/ALPINE_MODAL_SYSTEM.md)

## Maintenance

### When Adding New Blog Posts

1. Use modal buttons directly in content (recommended)
2. Or use links - they will be automatically converted by `PostContent.php`

### When Migrating WordPress Content

1. Search for `/demo-vereinbaren` links
2. Convert to modal buttons using patterns above
3. Or leave as links - automatic conversion will handle them

### Verification

To verify demo CTAs are working:

1. Load blog post in browser
2. Click demo button
3. Verify modal opens with booking form
4. Check browser console for tracking events

## Support

For questions or issues:

- Check `v2/components/blog/PostContent.php` for conversion logic
- Review `v2/base/include_ctabuttons.php` for button styling patterns
- See `.cursor/rules/blog-cta-patterns.mdc` for Cursor rules
