# Carposé Components Documentation

Welcome to the developer documentation for **Carposé** embeddable components — the widgets that display vehicle inventory, offers, stores, and team members directly on your website.

## How integration works

Every component is added to your page with a single `<div>` carrying a `data-carpose-component` attribute and your API key. The library loads the component, renders it inside an isolated [Shadow DOM](/misc-modules/01_shadow-dom-architecture), and initialises it automatically.

First, load the integration script once:

```html
<script type="text/javascript" src="https://integration.carpose.app/integration.js"></script>
```

Then drop in any component:

```html
<div
  data-carpose-component="vehicle-search"
  data-api-key="your-api-key"
  data-search-url-forward="/search-results"
></div>
```

Refer to each component's page for its specific configuration options.

## Colors & theming

Every component accepts the same four theming attributes on its mount element — they are read centrally, so this works for each `data-carpose-component` without exception:

| Attribute              | Description                                        |
| ---------------------- | -------------------------------------------------- |
| `data-primary-color`   | Overrides the primary theme color for this mount   |
| `data-secondary-color` | Overrides the secondary theme color for this mount |
| `data-accent-color`    | Overrides the accent theme color for this mount    |
| `data-color-scheme`    | `light`, `dark`, or `auto` (default)               |

The color values must be hex colors (`#31122d`); an invalid value is ignored with a console warning and the color configured in your Carposé account settings is used instead. Each override applies only to the component it is set on — other components on the same page keep the account colors.

With `data-color-scheme="auto"` (or the attribute omitted), the widget follows the host page's theme: the `<html>` element's `data-bs-theme`, `data-theme`, or `data-mui-color-scheme` attribute, or a `light`/`dark` class. The operating system's `prefers-color-scheme` is intentionally ignored — the widget follows your page, not the visitor's OS. Explicit `light` or `dark` pins the scheme.

```html
<div
  data-carpose-component="vehicle-slider"
  data-api-key="your-api-key"
  data-primary-color="#31122d"
  data-secondary-color="#d4b3cb"
  data-accent-color="#c79a3a"
  data-color-scheme="dark"
></div>
```

## Components

* [**Display & info components**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/01_components/README.md) — image and 360° viewers, vehicle sliders, CO2 and consumption labels, wishlist, comparison, the budget calculator, and the floating-buttons overlay
* [**Vehicle search modules**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/02_vehicle-search-modules/README.md) — the full search form, compact and AI search, the configurator, types browser, and vehicle groups
* [**Offer modules**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/03_offer-modules/README.md) — promotional offer sliders and detail items
* [**Events**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/04_events/README.md) — the DOM events components emit, with integration examples, plus the built-in Google Tag Manager bridge and its [consent gate](/events/09_consent)
* [**Misc modules**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/05_misc-modules/README.md) — Shadow DOM architecture, [URL structure](/misc-modules/08_url-structure), static form values, stores (list, map, single), and employees

## Integrations

* [**Inventory fragment**](/misc-modules/09_server-rendered-inventory) — the inventory as ready-made HTML a CMS plugin or template embeds server-side
* [**React integration**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/06_react-integration/README.md) — the standard script integration
* [**Wix integration**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/08_wix-integration/README.md) — embedding the vehicle search on Wix via a Custom Element
* [**Model Context Protocol**](https://gitlab.com/carpose-de/dev-docs/-/tree/main/07_model-context-protocol/README.md) — exposing inventory data to AI assistants (experimental)


# Image Slider Viewer

### Overview <a href="#overview" id="overview"></a>

The `Image Slider` component displays a collection of images in an interactive carousel with thumbnail navigation and lightbox functionality. It provides a responsive and touch-friendly way to browse through multiple images of a vehicle or property.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Image Slider component in a standard HTML page:

```html
<div 
  data-carpose-component="image-slider"
  data-api-key="your-api-key"
  data-images="image1.jpg,image2.jpg,image3.jpg"
  data-slides-per-view="4"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                                     |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "image-slider"                                    |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services if images are fetched dynamically. |
| `data-images`            | string | -       | Comma-separated list of image URLs to display in the slider.                                    |
| `data-slides-per-view`   | number | 4       | Number of thumbnail slides to display per view. Adjust based on available space.                |

### Features <a href="#features" id="features"></a>

* Main image carousel with navigation controls
* Thumbnail navigation below the main carousel
* Lightbox gallery for fullscreen image viewing
* Touch-friendly navigation for mobile devices
* Pagination indicator showing current slide position
* Responsive design that adapts to different screen sizes

### Example <a href="#example" id="example"></a>

```html
<!-- Basic implementation with direct image URLs -->
<div 
  data-carpose-component="image-slider"
  data-images="https://example.com/car1.jpg,https://example.com/car2.jpg,https://example.com/car3.jpg"
  data-slides-per-view="4"
></div>

<!-- Implementation with API key for dynamic image loading -->
<div 
  data-carpose-component="image-slider"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
  data-slides-per-view="3"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Image Display**:
   * The main carousel displays one image at a time with navigation arrows
   * Images can be navigated by swiping or using the navigation arrows
   * A pagination indicator shows the current position (e.g., "1/5")
2. **Thumbnail Navigation**:
   * Thumbnails are displayed below the main carousel
   * Clicking a thumbnail jumps directly to that image
   * The number of visible thumbnails is controlled by the `data-slides-per-view` attribute
3. **Lightbox Gallery**:
   * Clicking on the main image opens a fullscreen lightbox gallery
   * The lightbox allows for additional navigation and zoom functionality
   * Users can close the lightbox by clicking outside the image or using the close button
4. **Responsive Behavior**:
   * The component adapts to the container size
   * On smaller screens, the thumbnail view may show fewer thumbnails
   * Touch gestures are supported for navigation on mobile devices


# 360° Interior Viewer

### Overview <a href="#overview" id="overview"></a>

The `Interior Viewer` component displays an interactive 360° panoramic view of a vehicle's interior. It provides navigation controls for panning, zooming, and fullscreen viewing, allowing users to explore the interior of a vehicle in detail.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Interior Viewer component in a standard HTML page:

```html
<div 
  data-carpose-component="interior-viewer"
  data-api-key="your-api-key"
  data-identifier="your-identifier"
  data-vehicle-id="your-car-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "interior-viewer"                           |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |
| `data-identifier`        | string | -       | Identifier for the interior panorama image. Required if car-id is not provided.           |
| `data-vehicle-id`        | string | -       | ID of the car to display interior panorama for. Required if identifier is not provided.   |

### Features <a href="#features" id="features"></a>

* Interactive 360° panoramic view of vehicle interiors
* Navigation controls for:
  * Panning up, down, left, and right
  * Zooming in and out
  * Fullscreen viewing (on non-mobile devices)
* Loading indicator while panorama is being fetched
* Fallback screen when no interior panorama is available
* Overlay with usage hints

### Example <a href="#example" id="example"></a>

```html
<!-- Using identifier -->
<div 
  data-carpose-component="interior-viewer"
  data-api-key="your-api-key"
  data-identifier="vehicle-interior-123"
></div>

<!-- Using car ID -->
<div 
  data-carpose-component="interior-viewer"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the interior panorama data from the API using either the provided identifier or car ID.
   * A loading screen is displayed while the image is being loaded.
   * If no panorama is available, a message is displayed indicating that no 360° interior view is available.
2. **Navigation Controls**:
   * Arrow buttons allow panning up, down, left, and right
   * Plus/minus buttons control zoom level
   * Fullscreen button toggles fullscreen mode (not available on mobile devices)
   * Users can also navigate by dragging the panorama with their mouse or touch gestures
3. **Responsive Behavior**:
   * The component adapts to the container size
   * Controls are accessible on both desktop and mobile devices
   * Fullscreen option is automatically disabled on mobile devices


# 360° Exterior Viewer

### Overview <a href="#overview" id="overview"></a>

The `Exterior Viewer` component displays an interactive 360° view of a vehicle's exterior. It provides navigation controls for rotating the vehicle, autoplay functionality, and fullscreen viewing, allowing users to examine the exterior of a vehicle from all angles.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Exterior Viewer component in a standard HTML page:

```html
<div
  data-carpose-component="exterior-viewer"
  data-api-key="your-api-key"
  data-identifier="your-identifier"
  data-vehicle-id="your-car-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "exterior-viewer"                           |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |
| `data-identifier`        | string | -       | Identifier for the exterior images. Required if car-id is not provided.                   |
| `data-vehicle-id`        | string | -       | ID of the car to display exterior view for. Required if identifier is not provided.       |

### Features <a href="#features" id="features"></a>

* Interactive 360° view of vehicle exteriors
* Navigation controls for:
  * Rotating left and right
  * Autoplay with play/pause functionality
  * Fullscreen viewing (on non-mobile devices)
* Loading indicator while images are being fetched
* Fallback screen when no exterior images are available
* Overlay with usage hints
* Event notifications when viewer loads successfully or encounters errors

### Example <a href="#example" id="example"></a>

```html
<!-- Using identifier -->
<div
  data-carpose-component="exterior-viewer"
  data-api-key="your-api-key"
  data-identifier="vehicle-exterior-123"
></div>

<!-- Using car ID -->
<div
  data-carpose-component="exterior-viewer"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the exterior images from the API using either the provided identifier or car ID.
   * A loading screen is displayed while the images are being loaded.
   * If no images are available, a message is displayed indicating that no 360° exterior view is available.
   * The component triggers custom events to notify parent components of successful loading or errors.
2. **Navigation Controls**:
   * Left/right arrow buttons allow rotating the vehicle view
   * Play/pause button toggles automatic rotation
   * Fullscreen button toggles fullscreen mode (not available on mobile devices)
   * Users can also navigate by dragging the view with their mouse or touch gestures
3. **Responsive Behavior**:
   * The component adapts to the container size
   * Controls are accessible on both desktop and mobile devices
   * Fullscreen option is automatically disabled on mobile devices
4. **Events**:

   * `carpose-vehicle-viewer-load`: Triggered when the viewer loads successfully. `data.viewer` is `"exterior"`.
   * `carpose-vehicle-viewer-unavailable`: Triggered when no exterior images are available, or when the media fetch fails. `data.viewer` is `"exterior"`; `data.reason` distinguishes "no media at all" (`no-media`) from "fetch failed" (`fetch-failed`). `detail.component` reports whichever widget this viewer is mounted inside (e.g. `exterior-viewer` when mounted standalone as above, or `vehicle-detail` when it's nested inside the vehicle detail view) — it no longer doubles as the failure-mode discriminator.

   See [Vehicle Search: Viewer](/events/02_vehicle-search#viewer) for the full event reference.


# Vehicle Slider

{% hint style="info" %}
The Vehicle Slider displays a carousel of inventory vehicles, ordered randomly or by newest first.
{% endhint %}

## Introduction

The `vehicle-slider` component renders an interactive carousel of vehicles from your inventory. It is ideal for showcasing featured vehicles on landing pages or highlighting specific stock.

## Basic Usage

```html
<div
  data-carpose-component="vehicle-slider"
  data-api-key="your-api-key"
></div>
```

## Attributes

| Attribute                 | Required | Description                                                                                                         |
| ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component`  | Yes      | Must be `vehicle-slider`                                                                                            |
| `data-api-key`            | Yes      | Your Carposé API key                                                                                                |
| `data-ordering`           | No       | Order of vehicles, `random` or `latest` (default `random`)                                                          |
| `data-search-url-forward` | No       | Search page a vehicle opens on when clicked. Overrides the `vehicleSearchMainPageUrl` setting for this slider only. |
| `data-primary-color`      | No       | Per-mount color override                                                                                            |
| `data-secondary-color`    | No       | Per-mount color override                                                                                            |
| `data-accent-color`       | No       | Per-mount color override                                                                                            |

The slider always shows up to 10 vehicles.

## Forwarding to a Search Page

The slider is meant for pages that carry no vehicle search of their own — a homepage, a landing page. Clicking a card there cannot open the vehicle in place, so the browser is sent to your search page with the vehicle deep-linked (`?vehicle=…`), which opens it directly.

That target is normally the **Vehicle Search Main Page URL** configured in your Carposé settings. `data-search-url-forward` overrides it for a single slider — useful when a landing page should lead into a specific search page rather than the default one.

```html
<!-- Cards open the vehicle on /fahrzeugsuche instead of the configured default -->
<div
  data-carpose-component="vehicle-slider"
  data-api-key="your-api-key"
  data-search-url-forward="/fahrzeugsuche"
></div>
```

The value may be absolute (`https://example.de/fahrzeugsuche`) or site-relative (`/fahrzeugsuche`); a relative path is resolved against the page the slider sits on. Only `http` and `https` targets are accepted — anything else is ignored, and the configured setting is used instead.

The attribute affects this slider's cards only. Other Carposé components on the same page keep following the configured setting.

## Behavior

* Fetches vehicles from your inventory based on the configuration
* Carousel with touch/swipe support and navigation arrows
* Responsive and lazy-loads images

## Notes

* Hidden if no vehicles match the criteria
* See also Vehicle Similarity Slider for "you may also like" carousels


# Vehicle Similarity Slider

{% hint style="info" %}
The Vehicle Similarity Slider shows vehicles similar to a given vehicle — a "you may also like" carousel.
{% endhint %}

## Introduction

The `vehicle-similarity-slider` component fetches vehicles similar to a reference vehicle (based on its specifications) and renders them in a responsive carousel with bullet pagination.

## Basic Usage

```html
<div
  data-carpose-component="vehicle-similarity-slider"
  data-api-key="your-api-key"
  data-id="your-vehicle-id"
></div>
```

## Attributes

| Attribute                | Required | Description                                                  |
| ------------------------ | -------- | ------------------------------------------------------------ |
| `data-carpose-component` | Yes      | Must be `vehicle-similarity-slider`                          |
| `data-api-key`           | Yes      | API key for authentication with the Carposé backend services |
| `data-id`                | Yes      | ID of the reference vehicle to find similar vehicles for     |

## Behavior

* Fetches similar vehicles from the API based on the reference vehicle's specifications
* Renders a carousel with bullet pagination — 3 slides per view on desktop, 1 on mobile
* Each card links to the corresponding vehicle detail page

## Notes

* Requires a valid `data-id`; nothing is shown if there are no similar vehicles
* Pairs well with a vehicle detail page


# CO2 Class Label

### Overview <a href="#overview" id="overview"></a>

The `CO2 Class Label` component displays a vehicle's CO2 efficiency classification using a standardized energy label. It fetches the CO2 class data from the API and presents it with an explanatory headline and description.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the CO2 Class Label component in a standard HTML page:

```html
<div 
  data-carpose-component="co2-class-label"
  data-api-key="your-api-key"
  data-vehicle-id="your-car-id"
  data-type="combined"
  data-headline="CO2-Effizienzklasse"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default               | Description                                                                                                                                                                                                         |
| ------------------------ | ------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -                     | Identifies the component type, must be set to "co2-class-label"                                                                                                                                                     |
| `data-api-key`           | string | -                     | API key for authentication with the Carpose backend services. Required for data fetching.                                                                                                                           |
| `data-vehicle-id`        | string | -                     | ID of the car for which to display the CO2 class.                                                                                                                                                                   |
| `data-type`              | string | -                     | Which CO2 class to show: `combined`, `weightedCombined` (plug-in hybrids, weighted combined) or `emptyBattery` (plug-in hybrids, discharged battery). Required; an unknown value renders nothing and logs an error. |
| `data-headline`          | string | "CO2-Effizienzklasse" | Headline text displayed above the efficiency label.                                                                                                                                                                 |

### Features <a href="#features" id="features"></a>

* Displays a vehicle's CO2 efficiency class (A+, A, B, C, D, E, F, G)
* Shows explanatory text about how the classification is determined
* Fetches data dynamically from the Carpose API
* Responsive layout that adapts to different screen sizes

### Example <a href="#example" id="example"></a>

```html
<!-- WLTP CO2 Efficiency Class -->
<div 
  data-carpose-component="co2-class-label"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
  data-type="combined"
  data-headline="CO2-Effizienzklasse (WLTP)"
></div>

<!-- NEDC CO2 Efficiency Class -->
<div 
  data-carpose-component="co2-class-label"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
  data-type="weightedCombined"
  data-headline="CO2-Effizienzklasse (NEDC)"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the CO2 class data from the API using the provided car ID and type.
   * If the specified type doesn't exist in the API response, an error is logged to the console.
   * The component doesn't render anything if the CO2 class data cannot be retrieved.
2. **Display**:
   * When data is successfully loaded, the component displays:
     * The headline specified in the `data-headline` attribute
     * A standard explanatory text about how the CO2 class is determined
     * The energy efficiency label (A+ to G) based on the fetched data
3. **Responsive Behavior**:
   * The component adapts to different screen sizes
   * On larger screens, it typically occupies one-third of the available width
   * On smaller screens, it takes up half or full width depending on the viewport size


# Consumption Box

### Overview <a href="#overview" id="overview"></a>

The `Consumption Box` component displays comprehensive fuel consumption, emission, and efficiency information for vehicles. It dynamically adapts the displayed information based on the vehicle's powertrain type (combustion, electric, or plug-in hybrid).

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Consumption Box component in a standard HTML page:

```html
<div 
  data-carpose-component="consumption-box"
  data-api-key="your-api-key"
  data-vehicle-id="your-car-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "consumption-box"                           |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |
| `data-vehicle-id`        | string | -       | ID of the car for which to display consumption information.                               |

### Features <a href="#features" id="features"></a>

* Dynamically displays different consumption information based on the vehicle type
* For combustion engine vehicles:
  * CO₂ class (combined)
  * Emission class information
  * Fuel consumption values
* For plug-in hybrid vehicles:
  * CO₂ class (combined, weighted combined, and with empty battery)
  * Emission class information
  * Fuel consumption with empty battery
  * Electric range
* For electric vehicles:
  * CO₂ class (combined)
  * Emission class information
  * Power consumption
  * Electric range
* Includes standardized legal disclaimer about consumption measurement methods

### Example <a href="#example" id="example"></a>

```html
<div 
  data-carpose-component="consumption-box"
  data-api-key="your-api-key"
  data-vehicle-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the car details from the API using the provided car ID.
   * The component only renders if the car has consumption data available.
2. **Dynamic Content**:
   * The component determines the powertrain type (combustion, electric, or plug-in hybrid) of the vehicle.
   * Based on the powertrain type, it displays the appropriate consumption information components.
   * Each sub-component fetches its specific data as needed.
3. **Component Structure**:
   * The component displays a header "Detaillierte Verbrauchswerte" (Detailed Consumption Values).
   * It then renders different combinations of sub-components based on the vehicle type.
   * A standardized legal disclaimer is displayed at the bottom with information about the measurement methods.
4. **Responsive Behavior**:
   * The component adapts to different screen sizes.
   * On larger screens, information is displayed in a multi-column layout.
   * On smaller screens, information stacks vertically for better readability.


# Wishlist

{% hint style="warning" %}
The Wishlist is a **feature of the** [**Vehicle Search Form**](/vehicle-search-modules/01_vehicle-search-form), not a standalone embeddable component. There is no `data-carpose-component="wishlist"` — it is enabled with the `data-wishlist` attribute on the search form.
{% endhint %}

## Introduction

When wishlist functionality is enabled, visitors can save vehicles to a favourites list that persists between sessions (stored in the browser's `localStorage`). A floating wishlist button gives access to a slide-out drawer of saved vehicles, from which the visitor can send an inquiry, remove vehicles, or open a vehicle's detail page.

## Enabling the Wishlist

```html
<div
  data-carpose-component="vehicle-search-form"
  data-api-key="your-api-key"
  data-wishlist="true"
></div>
```

## Behavior

* A wishlist button appears on each vehicle card, and a floating wishlist button gives access to the saved-vehicles drawer
* Wishlist state persists across sessions via `localStorage`
* Visitors can send inquiries for multiple saved vehicles at once
* Success messages are shown when adding or removing vehicles

## Notes

* The wishlist is stored per-browser; no account is required
* See also [Vehicle Comparison](/components/09_vehicle-comparison), which can be enabled alongside the wishlist
* For the full list of search-form options, see the [Vehicle Search Form](/vehicle-search-modules/01_vehicle-search-form)


# Vehicle Comparison

{% hint style="warning" %}
Vehicle Comparison is a **feature of the** [**Vehicle Search Form**](/vehicle-search-modules/01_vehicle-search-form), not a standalone embeddable component. There is no `data-carpose-component="comparison"` — it is enabled with the `data-compare` attribute on the search form.
{% endhint %}

## Introduction

When comparison is enabled, visitors can add up to **3 vehicles** to a comparison list (persisted in `localStorage`) and view them side by side in a modal. The comparison shows key specifications — price and VAT, registration date and mileage, power and engine, transmission and fuel type, and additional specs.

## Enabling Comparison

```html
<div
  data-carpose-component="vehicle-search-form"
  data-api-key="your-api-key"
  data-compare="true"
></div>
```

## Behavior

* A comparison button appears on vehicle cards and detail pages (including an icon overlay on thumbnails), and a floating comparison button opens the side-by-side view
* Comparison state persists across sessions via `localStorage`
* Responsive layout: 1 vehicle (full width), 2 vehicles (50/50), 3 vehicles (equal thirds); mobile uses a horizontal scroll view
* A warning is shown when trying to add more than 3 vehicles

## Notes

* Can be combined with the [Wishlist](/components/08_wishlist) (`data-wishlist="true"`)
* For the full list of search-form options, see the [Vehicle Search Form](/vehicle-search-modules/01_vehicle-search-form)


# Budget Calculator

{% hint style="warning" %}
The Budget Calculator is a **feature of the** [**Vehicle Search Form**](/vehicle-search-modules/01_vehicle-search-form), not a standalone embeddable component. There is no `data-carpose-component="budget-calculator"` — it is enabled with the `data-budget-calculator` attribute on the search form.
{% endhint %}

## Introduction

When the budget calculator is enabled, a floating button gives visitors access to a financing calculator. It lets a visitor work out an estimated monthly rate while browsing search results.

## Enabling the Budget Calculator

```html
<div
  data-carpose-component="vehicle-search-form"
  data-api-key="your-api-key"
  data-budget-calculator="true"
></div>
```

## Behavior

* A floating button gives access to the calculator from anywhere in the search results
* Opens the calculator without leaving the search results
* The calculation is an estimate only and is not a binding financing offer

## Notes

* Can be combined with other search-form features (wishlist, comparison, history, rates)
* For the full list of search-form options, see the [Vehicle Search Form](/vehicle-search-modules/01_vehicle-search-form)


# Floating Buttons

{% hint style="info" %}
The floating buttons are a page-level overlay (in a corner of the screen) that gives visitors quick access to actions such as live chat, your custom links, the purchase-request and test-drive wizards, and — where a vehicle search is on the page — wishlist, comparison, history, and the AI assistant.
{% endhint %}

## Overview

The floating-buttons overlay can show:

* **Custom menu entries** — links and grouped buttons that you configure in the Carposé dashboard (label, icon, URL).
* **Live chat** — a chat button backed by Brevo, when enabled for your account.
* **Built-in feature buttons** — wishlist, history, comparison, budget calculator, and the AI assistant, shown according to your account settings and what is present on the page.
* **Wizard buttons** — "Fahrzeug verkaufen" opens the [Purchase Request Form](/components/12_purchase-request-form) and "Probefahrt buchen" opens the [Test Drive Form](/components/13_test-drive-form), each in a dialog. See [Wizard buttons](#wizard-buttons) below.

What appears in the overlay is configured by the dealer in the Carposé dashboard. On the page you only enable the overlay and, optionally, override the theme colors.

## Adding the overlay

There are two ways to add the overlay to a page.

### Option 1 — from the integration script (recommended)

Add the `data-carpose-floating-buttons` attribute (and your API key) directly to the integration script tag. The overlay is then mounted automatically — no extra element is needed:

```html
<script
  type="text/javascript"
  src="https://integration.carpose.app/integration.js"
  data-carpose-floating-buttons
  data-api-key="your-api-key"
></script>
```

### Option 2 — as a mount element

Like any other component, you can also place an explicit mount `<div>`:

```html
<div
  data-carpose-component="floating-buttons"
  data-api-key="your-api-key"
></div>
```

Only one floating-buttons overlay is shown per page.

## Configuration Attributes

| Attribute                       | Where                | Required       | Default         | Description                                                                                                                   |
| ------------------------------- | -------------------- | -------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `data-api-key`                  | script tag / element | Yes            | —               | Your Carposé API key                                                                                                          |
| `data-carpose-floating-buttons` | script tag           | Yes (Option 1) | —               | Its presence mounts the overlay. Set it to `"false"` to force the overlay off (for example on pages where you don't want it). |
| `data-primary-color`            | script tag / element | No             | account default | Override the primary theme color                                                                                              |
| `data-secondary-color`          | script tag / element | No             | account default | Override the secondary theme color                                                                                            |
| `data-accent-color`             | script tag / element | No             | account default | Override the accent theme color                                                                                               |
| `data-color-scheme`             | script tag / element | No             | account default | `"light"` or `"dark"`                                                                                                         |
| `data-purchase-request`         | script tag / element | No             | account default | `"true"` / `"false"` — show the "Fahrzeug verkaufen" button. See [Wizard buttons](#wizard-buttons)                            |
| `data-test-drive`               | script tag / element | No             | `false`         | `"true"` / `"false"` — show the "Probefahrt buchen" button. See [Wizard buttons](#wizard-buttons)                             |

## Wizard buttons

Two of the wizards can be reached from the overlay instead of, or in addition to, an embed of their own. Opened this way they always use their default copy, every optional step switched on, and no success redirect, and they take the privacy link from your account settings — there is no mount element whose attributes they could read.

| Attribute               | Button               | Default              |
| ----------------------- | -------------------- | -------------------- |
| `data-purchase-request` | "Fahrzeug verkaufen" | Your account setting |
| `data-test-drive`       | "Probefahrt buchen"  | Off                  |

Both go on the floating-buttons script tag or element and take `"true"` or `"false"`, overriding the account setting for that page:

```html
<script
  type="text/javascript"
  src="https://integration.carpose.app/integration.js"
  data-carpose-floating-buttons
  data-api-key="your-api-key"
  data-purchase-request="true"
></script>
```

Events from a wizard opened here carry `component: "floating-buttons"` rather than the wizard's own component name.

## Live chat

Live chat is provided through **Brevo**. It is enabled and configured (provider and account ID) by the dealer in the Carposé dashboard. When enabled, a chat button appears in the overlay; clicking it opens the Brevo chat widget. No extra code is required on your side — including the integration script is enough.

## Notes

* Custom menu entries (links and groups) are managed entirely in the Carposé dashboard; only `http://` and `https://` links are allowed.
* The wishlist, comparison, history, budget calculator, and AI assistant buttons appear based on your account settings (and, for some, whether a vehicle search component is on the page). See the [Vehicle Search Form](/vehicle-search-modules/01_vehicle-search-form) for those features.


# Purchase Request Form

{% hint style="info" %}
The purchase request form ("Ankaufanfrage") is a four-step wizard in which a visitor offers you their own vehicle. They describe the car, its condition, optionally upload photos, and leave their contact details — you receive the enquiry as a lead.
{% endhint %}

## Overview

This is the reverse of a vehicle enquiry: the visitor is the seller and you are the buyer. The wizard collects, step by step:

1. **Fahrzeug** — make, model, first registration and mileage, plus optional details behind a "Weitere Angaben" disclosure (variant, fuel type, transmission, power, engine displacement, body type, colour, doors, seats, VIN, HSN/TSN, licence plate, previous owners).
2. **Zustand** — accident damage and, if affirmed, a free-text damage description; exterior and technical condition, next inspection, service history, tyre set, roadworthiness, non-smoker, and equipment features.
3. **Fotos** — optional photo upload (can be switched off entirely).
4. **Kontakt** — price expectation, desired selling period, any outstanding financing, the location selector, a free-text message, and the seller's contact details.

Only eight fields are mandatory: make, model, month **and** year of first registration, mileage, first name, last name, e-mail address, and the privacy consent checkbox. Everything else is optional, so a visitor can send a usable enquiry in under a minute.

The wizard will not let a visitor skip forward past a step whose required fields are incomplete. Going back is always possible via the "Zurück" button; on viewports of 768px and wider the progress bar additionally renders as a stepper whose completed steps are clickable. Below that width it is a plain progress bar with a "Schritt n von m" caption, so "Zurück" is the only way back.

## Adding the form

### Option 1 — inline

The wizard renders directly in the page:

```html
<div
  data-carpose-component="purchase-request-form"
  data-api-key="your-api-key"
  data-privacy-url="https://your-site.de/datenschutz"
></div>
```

### Option 2 — behind a button

Set `data-variant="modal"` and the page shows only a button; the wizard opens in a dialog:

```html
<div
  data-carpose-component="purchase-request-form"
  data-api-key="your-api-key"
  data-variant="modal"
  data-title="Wir kaufen Ihr Auto"
  data-subtitle="In vier Schritten zum Angebot"
  data-button-label="Jetzt bewerten lassen"
  data-privacy-url="https://your-site.de/datenschutz"
></div>
```

### Option 3 — from the floating buttons

The wizard can also be reached from the [floating-buttons overlay](/components/11_floating-buttons) as a "Fahrzeug verkaufen" button. Enable it for your account in the Carposé dashboard, or override that per page by putting `data-purchase-request="true"` (or `"false"`) on the floating-buttons script tag or element — see [Wizard buttons](/components/11_floating-buttons#wizard-buttons).

Opened this way the wizard uses its default copy and takes the privacy link from your account settings, since there is no mount element to read attributes from.

## Configuration Attributes

| Attribute           | Required | Default                                                             | Description                                                                                                                                                                                                                                                        |
| ------------------- | -------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data-api-key`      | Yes      | —                                                                   | Your Carposé API key                                                                                                                                                                                                                                               |
| `data-variant`      | No       | `inline`                                                            | `inline` renders the wizard in place; `modal` renders a button that opens it in a dialog                                                                                                                                                                           |
| `data-title`        | No       | `Fahrzeug verkaufen`                                                | Headline above the wizard                                                                                                                                                                                                                                          |
| `data-subtitle`     | No       | `Sagen Sie uns, was Sie fahren — wir melden uns mit einem Angebot.` | Intro line. Set it to an empty value (`data-subtitle=""`) to hide it — that is different from omitting the attribute, which keeps the default                                                                                                                      |
| `data-button-label` | No       | `Fahrzeug verkaufen`                                                | Caption of the trigger button, for `data-variant="modal"`. Note this falls back to the built-in default, **not** to your `data-title` — set both if you want a custom caption on the button as well                                                                |
| `data-photo-upload` | No       | `true`                                                              | `"false"` removes the photo step entirely, leaving a three-step wizard                                                                                                                                                                                             |
| `data-store-select` | No       | `true`                                                              | `"false"` hides the location selector. With the selector on, a single location is assigned silently and only two or more locations produce a visible choice                                                                                                        |
| `data-success-url`  | No       | —                                                                   | Redirect here after a successful submission instead of showing the confirmation panel. Only `http://` and `https://` URLs are accepted                                                                                                                             |
| `data-privacy-url`  | No       | —                                                                   | Target of the link in the privacy consent checkbox. Only `http://` and `https://` URLs are accepted. Omitting it does not remove the checkbox — consent stays mandatory — but its label then renders as plain text with nothing to click, so set it on every embed |

## Photos

When the photo step is enabled, the visitor can attach images of the vehicle by picking them or dropping them onto the step. The wizard checks each file before anything is sent and names the ones it turns away:

| Limit            | Value              |
| ---------------- | ------------------ |
| Formats          | JPEG, PNG and WebP |
| Size per file    | 10 MB              |
| Number of images | 12                 |

The server applies its own ceiling on top of that and may accept fewer.

Photos are uploaded **after** the enquiry itself has been stored, so a failed upload never costs you the lead — the confirmation tells the visitor which images did not go through and that you will be in touch regardless. Because the upload runs before the wizard reports success, it also delays the success event and any `data-success-url` redirect; see [the success event](/events/02_vehicle-search#vehicle-purchase-form-success-event).

## After submission

On success the visitor sees a confirmation panel and receives an e-mail acknowledgement; the enquiry appears among your leads in the Carposé dashboard. If you set `data-success-url`, the visitor is redirected there instead.

If the same visitor sends many requests in a short time, the server rate-limits them and the wizard shows a message asking them to try again in a few minutes.

## Events

The wizard dispatches `carpose-vehicle-purchase-form-*` events for open, step, submit, success and error. See [Vehicle Search events](/events/02_vehicle-search#purchase) for the payloads.

## Notes

* All wizard copy is German.
* The form renders inside a Shadow DOM, so your page styles cannot affect it and its styles cannot leak out. It does inherit your page's font family on purpose, so it blends into your typography. See [Shadow DOM Architecture](/misc-modules/01_shadow-dom-architecture).
* More than one purchase request form can be placed on the same page — for example an inline one and a modal trigger.


# Test Drive Booking

{% hint style="info" %}
The test-drive booking wizard lets a visitor pick a car from your test-drive fleet, choose a free day and time, and leave their contact details. You receive the appointment as a booking request and confirm it.
{% endhint %}

## Overview

The wizard has three steps:

1. **Fahrzeug** — a tile grid of every vehicle in your test-drive fleet, each with its photo and name. The visitor picks one.
2. **Termin** — the days that still have free slots for that vehicle, and the free times on the chosen day. The first bookable day is preselected, and the expected duration is shown next to the times.
3. **Kontakt** — salutation, first and last name, e-mail, optional phone number and message, and the privacy consent checkbox.

First name, last name, a valid e-mail address and the consent checkbox are mandatory; everything else is optional.

Availability is loaded per vehicle from your fleet's real calendar, so a visitor only ever sees times that are genuinely free. If someone books a slot while another visitor is filling in the form, the second visitor is returned to the appointment step with a refreshed list of times rather than losing what they typed.

A new booking is created with the status **pending** — it is a request, not a confirmed appointment. The visitor sees a summary of the vehicle, date and time, plus a note that you will confirm by e-mail.

## Adding the wizard

### Option 1 — inline

```html
<div
  data-carpose-component="test-drive-form"
  data-api-key="your-api-key"
  data-privacy-url="https://your-site.de/datenschutz"
></div>
```

### Option 2 — behind a button

```html
<div
  data-carpose-component="test-drive-form"
  data-api-key="your-api-key"
  data-variant="modal"
  data-title="Fahren Sie Probe"
  data-button-label="Jetzt Probe fahren"
  data-privacy-url="https://your-site.de/datenschutz"
></div>
```

### Option 3 — from the floating buttons

The wizard can also open from the [floating-buttons overlay](/components/11_floating-buttons) as a "Probefahrt buchen" button. Put `data-test-drive="true"` on the floating-buttons script tag or element to switch it on for a page — see [Wizard buttons](/components/11_floating-buttons#wizard-buttons).

Opened this way the wizard uses its default copy and takes the privacy link from your account settings, since there is no mount element to read attributes from.

## Configuration Attributes

| Attribute             | Required | Default                                                                           | Description                                                                                                                                                                                                                                                                  |
| --------------------- | -------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-api-key`        | Yes      | —                                                                                 | Your Carposé API key                                                                                                                                                                                                                                                         |
| `data-variant`        | No       | `inline`                                                                          | `inline` renders the wizard in place; `modal` renders a button that opens it in a dialog                                                                                                                                                                                     |
| `data-title`          | No       | `Probefahrt buchen`                                                               | Headline above the wizard                                                                                                                                                                                                                                                    |
| `data-subtitle`       | No       | `Wählen Sie ein Fahrzeug und einen Termin — wir bestätigen Ihnen die Probefahrt.` | Intro line. Set it to an empty value (`data-subtitle=""`) to hide it — that is different from omitting the attribute, which keeps the default                                                                                                                                |
| `data-button-label`   | No       | `Probefahrt buchen`                                                               | Caption of the trigger button, for `data-variant="modal"`. Note this falls back to the built-in default, **not** to your `data-title` — set both if you want a custom caption on the button as well                                                                          |
| `data-vehicle-layout` | No       | `tiles` inline, `list` in the modal                                               | How the first step presents the fleet: `tiles` (photo-led cards) or `list` (one row per vehicle). The dialog defaults to a list because tiles need more width than it offers; set the attribute to force either layout. An unknown value falls back to the variant's default |
| `data-success-url`    | No       | —                                                                                 | Redirect here after a successful booking instead of showing the confirmation. Only `http://` and `https://` URLs are accepted                                                                                                                                                |
| `data-privacy-url`    | No       | —                                                                                 | Target of the link in the privacy consent checkbox. Only `http://` and `https://` URLs are accepted                                                                                                                                                                          |

## Your fleet

The vehicles shown are the **active** vehicles in your test-drive fleet, managed in the Carposé dashboard — this is a separate list from your vehicle inventory. Their photos and booking calendars come from the same place.

Two states are worth knowing, because neither is an error:

* **No vehicles at all** — the wizard says that no cars are currently available for a test drive. Check that your fleet has at least one active vehicle.
* **A vehicle with no free slots** — picking it reaches the appointment step and reports that no dates are currently available. The visitor can go back and choose another car.

If the fleet itself cannot be loaded, the wizard shows an error with a retry button.

## Events

The wizard dispatches `carpose-vehicle-test-drive-*` events for open, step, submit, success and error. See [Vehicle Search events](/events/02_vehicle-search#test-drive) for the payloads.

## Notes

* All wizard copy is German.
* The wizard renders inside a Shadow DOM, so your page styles cannot affect it and its styles cannot leak out. It does inherit your page's font family on purpose, so it blends into your typography. See [Shadow DOM Architecture](/misc-modules/01_shadow-dom-architecture).
* This is the standalone fleet wizard. Booking a test drive for one specific vehicle from an offer page is a separate flow — see [Offer Item](/offer-modules/02_offer-item).
* More than one test-drive wizard can be placed on the same page — for example an inline one and a modal trigger.


# Vehicle Search Form

The `VehicleSearchForm` component is a comprehensive vehicle search form that allows users to filter cars by various criteria and display matching results. It includes pagination, filtering options, and the ability to redirect to a search results page.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the VehicleSearchForm component in a standard HTML page, you only need two required attributes:

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
></div>
```

This minimal setup will display a search form with inline results. See the [Examples](#examples) section below for more advanced configurations including wishlist, comparison, and redirect functionality.

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

#### Required Attributes <a href="#required-attributes" id="required-attributes"></a>

| Attribute                | Type   | Description                                                         |
| ------------------------ | ------ | ------------------------------------------------------------------- |
| `data-carpose-component` | string | Identifies the component type, must be set to "vehicle-search-form" |
| `data-api-key`           | string | API key for authentication with the Carpose backend services        |

#### Optional Attributes <a href="#optional-attributes" id="optional-attributes"></a>

| Attribute                      | Type    | Default   | Description                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------ | ------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-search-url-forward`      | string  | null      | URL to redirect to when search button is clicked. When set, no results are shown inline - instead saves form state to localStorage and redirects to the specified URL. The target page must have another car-search-form component to read the localStorage state and display the results. When null, results are displayed inline below the form.                    |
| `data-force-scroll-to-top`     | boolean | false     | When true, forces window to scroll to the top when changing pages or filters.                                                                                                                                                                                                                                                                                         |
| `data-enable-page-load-focus`  | boolean | false     | When true, enables focus behavior when a page loads.                                                                                                                                                                                                                                                                                                                  |
| `data-default-form-state-hash` | string  | null      | Default form state (base64-encoded) to use when resetting the form. If not provided, a new empty form state will be created.                                                                                                                                                                                                                                          |
| `data-wishlist`                | boolean | false     | When true, enables wishlist functionality allowing users to save cars to a favorites list.                                                                                                                                                                                                                                                                            |
| `data-compare`                 | boolean | false     | When true, enables comparison functionality allowing users to compare up to 3 cars side by side.                                                                                                                                                                                                                                                                      |
| `data-history`                 | boolean | false     | When true, enables visit history functionality allowing users to see their recently viewed car detail pages.                                                                                                                                                                                                                                                          |
| `data-rates`                   | boolean | false     | When true, enables display of leasing and financing payment options with an interactive dropdown selector. When false, only cash price is displayed.                                                                                                                                                                                                                  |
| `data-ai-assistant`            | boolean | false     | When true, enables AI-powered natural language search functionality allowing users to describe their desired car in plain text (e.g., "red car with leather seats").                                                                                                                                                                                                  |
| `data-ai-only`                 | boolean | false     | When true (and `data-ai-assistant="true"`), collapses the form to show only the AI natural-language input — all filter selects, buttons, and the floating AI button are hidden. Ideal for a dedicated AI search landing page. Has no effect unless the AI assistant is enabled.                                                                                       |
| `data-contextual-count`        | boolean | false     | When true, each filter dropdown (manufacturer, model, fuel type, condition, vehicle type, engine type, colour, store) shows a live count of how many vehicles match each option given the other currently active filters. When false, options are shown without counts.                                                                                               |
| `data-infinite-scroll`         | boolean | false     | When true, replaces the numbered pagination control with infinite scroll: the next page of results loads automatically as the user scrolls near the bottom of the list. Already-loaded cards stay interactive while the next page loads. When false (default), results use numbered pagination.                                                                       |
| `data-price-alert`             | boolean | false     | When true, enables the price-alert feature: visitors can subscribe with their name and email to be notified when a vehicle's price changes.                                                                                                                                                                                                                           |
| `data-used-car-energy-costs`   | boolean | false     | When true, the estimated yearly energy/fuel cost box is also shown for used cars. By default this box is only displayed for new cars.                                                                                                                                                                                                                                 |
| `data-description-mode`        | string  | auto      | Controls how the equipment/description section is rendered on the vehicle detail page. Values: `formatted` (collapsible accordion groups parsed from HTML), `features` (structured feature tag list), `plain` (raw HTML rendered with basic list styling). When omitted, uses `formatted` if the vehicle has an HTML description, otherwise falls back to `features`. |
| `data-url-mode`                | string  | `query`   | URL form the listing writes: `query` (`?vehicle=…`, `?cp-page=2`) or `path` (`/fahrzeug/…/`, `/seite/2/`). Only an explicit `path` switches the form; every other value is `query`. Overrides the account setting. The path form requires sub-path routing on your website — see [URL Structure](/misc-modules/08_url-structure).                                     |
| `data-base-path`               | string  | derived   | Base path the listing URLs are built on. Derived from the current URL when omitted, which is correct whenever the widget sits on the page its links point at. Set it only for an embed whose links have to lead to a different search page.                                                                                                                           |
| `data-url-slug-only`           | boolean | false     | When true, vehicles are addressed by their slug alone (`?vehicle=audi-q3-s-line`) instead of `{slug}--{uuid}`. Requires former slugs to stay resolvable — see [URL Structure](/misc-modules/08_url-structure).                                                                                                                                                        |
| `data-card-variant`            | string  | `classic` | Card layout for result cards: `classic` (portrait card, image on top), `compact` (dense portrait card for high-density grids) or `horizontal` (full-width row, image left, price right).                                                                                                                                                                              |
| `data-heading-level`           | string  | `2`       | Heading level the vehicle names render at: `"2"`, `"3"` or `"4"` (an `h` prefix is accepted). No card ever emits an `h1` — pick the level that continues your page outline without skipping one.                                                                                                                                                                      |

{% hint style="info" %}
**Account-level defaults.** The boolean feature flags (`data-rates`, `data-ai-assistant`, `data-ai-only`, `data-contextual-count`, `data-wishlist`, `data-compare`, `data-history`, `data-price-alert`, `data-used-car-energy-costs`, `data-infinite-scroll`) can also be enabled centrally for your account. When the corresponding `data-*` attribute is present on the element it always wins; when it is omitted, the value configured on your Carpose account is used instead. Set the attribute explicitly to `"false"` to force a feature off even when it is enabled account-wide.
{% endhint %}

### Features <a href="#features" id="features"></a>

* Filtering by manufacturer, model, condition, mileage, construction year, engine type, vehicle type, fuel type, store, and price
* Additional detailed filters accessible via "weitere Filter anzeigen" button
* Real-time vehicle count display
* Contextual per-option result counts on filters (when enabled)
* Reset button to clear all filters
* Pagination for search results, or infinite scroll as an alternative (when enabled)
* Responsive layout that adapts to different screen sizes
* AI-powered natural language search with conversational interface (when enabled)
* Wishlist functionality for saving favorite vehicles (when enabled)
* Comparison functionality for comparing up to 3 cars side by side (when enabled)
* Visit history functionality for tracking recently viewed car detail pages (when enabled)
* Leasing and financing rate display with interactive payment type selector (when enabled)
* Price-alert subscriptions for individual vehicles (when enabled)
* Estimated yearly energy/fuel costs shown for used cars (when enabled)
* Icon-overlay buttons on car thumbnails for quick wishlist and comparison actions
* Floating action buttons for easy access to AI assistant, wishlist, comparison, and history (when enabled)

### AI Assistant Functionality <a href="#ai-assistant-functionality" id="ai-assistant-functionality"></a>

**Important:** The AI Assistant feature requires the AI Module License to be active on your account. Without this license, the AI assistant will not provide results and searches will fail. Contact your Carpose representative to enable the AI Module License.

When AI assistant functionality is enabled via the `data-ai-assistant="true"` attribute, users can:

1. Search for vehicles using natural language descriptions (e.g., "red car with leather seats", "add panoramic sunroof")
2. Build upon previous searches conversationally (e.g., first "BMW with automatic transmission", then "add navigation system")
3. Access the AI assistant through:

* An input field directly in the search form (when enabled to show in form)
* A floating AI button that opens a slide-out drawer with full history

4. View their search history with timestamps
5. Remove individual search queries from history
6. Clear entire search history
7. Toggle AI assistant visibility in the form via a switch in the drawer

After a successful AI search, the matching filters are applied to the form automatically and the page scrolls to the results.

#### How It Works <a href="#how-it-works" id="how-it-works"></a>

* Users describe their desired vehicle in natural language (German)
* The AI processes the query and returns filter settings
* Filter settings are automatically applied to the search form
* Results are displayed and the page scrolls to the car list
* Context is maintained across multiple queries for refinement
* When using the drawer, it automatically closes after a successful search

#### Example with AI Assistant Enabled <a href="#example-with-ai-assistant-enabled" id="example-with-ai-assistant-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-ai-assistant="true"
></div>
```

### Wishlist Functionality <a href="#wishlist-functionality" id="wishlist-functionality"></a>

When wishlist functionality is enabled via the `data-wishlist="true"` attribute, users can:

1. Save vehicles to a wishlist that persists between sessions (stored in browser localStorage)
2. Access saved vehicles through a floating wishlist button that appears in the UI
3. View all saved vehicles in a slide-out drawer
4. Select vehicles from their wishlist to send an inquiry
5. Remove vehicles from their wishlist
6. Navigate directly to vehicle detail pages from the wishlist

A floating wishlist button gives access to the saved-vehicles drawer whenever the wishlist is enabled.

#### Example with Wishlist Enabled <a href="#example-with-wishlist-enabled" id="example-with-wishlist-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-wishlist="true"
></div>
```

### Comparison Functionality <a href="#comparison-functionality" id="comparison-functionality"></a>

When comparison functionality is enabled via the `data-compare="true"` attribute, users can:

1. Add up to 3 vehicles to a comparison list that persists between sessions (stored in browser localStorage)
2. Access the comparison view through a floating comparison button that appears in the UI
3. View all selected vehicles side by side in a modal for easy comparison
4. Compare key vehicle specifications including:

* Price and VAT information
* Registration date and mileage
* Power and engine specifications
* Transmission and fuel type
* Additional specifications (cubic capacity, seats, doors, colors)

5. Remove vehicles from the comparison
6. Navigate directly to vehicle detail pages from the comparison view
7. Responsive layout: 1 car (full width), 2 cars (50/50 split), 3 cars (equal thirds)
8. Mobile-optimized horizontal scroll view for comparing vehicles on smaller screens

A floating comparison button opens the side-by-side comparison modal whenever comparison is enabled.

#### Example with Comparison Enabled <a href="#example-with-comparison-enabled" id="example-with-comparison-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-compare="true"
></div>
```

#### Example with Both Wishlist and Comparison Enabled <a href="#example-with-both-wishlist-and-comparison-enabled" id="example-with-both-wishlist-and-comparison-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-wishlist="true"
data-compare="true"
></div>
```

### History Functionality <a href="#history-functionality" id="history-functionality"></a>

When history functionality is enabled via the `data-history="true"` attribute, users can:

1. Automatically track all visited car detail pages (stored in browser localStorage)
2. Access visit history through a floating history button that appears in the UI
3. View all recently visited vehicles in a slide-out drawer (most recent first)
4. Navigate back to previously viewed vehicle detail pages
5. Clear the entire visit history
6. History is limited to the last 50 visited vehicles

Visited vehicles are tracked automatically, and a floating history button opens the recently-viewed drawer whenever history is enabled.

#### Example with History Enabled <a href="#example-with-history-enabled" id="example-with-history-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-history="true"
></div>
```

#### Example with All Features Enabled (AI Assistant, Wishlist, Comparison, and History) <a href="#example-with-all-features-enabled-ai-assistant-wishlist-comparison-and-history" id="example-with-all-features-enabled-ai-assistant-wishlist-comparison-and-history"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-ai-assistant="true"
data-wishlist="true"
data-compare="true"
data-history="true"
></div>
```

### Rates Functionality <a href="#rates-functionality" id="rates-functionality"></a>

When rates functionality is enabled via the `data-rates="true"` attribute, the price display changes to show financing options:

**Without `data-rates` (default behavior):**

* Only the cash price is displayed
* Simple static price presentation
* Shows "MwSt. ausweisbar" for VAT deductible vehicles

**With `data-rates="true"`:**

* Interactive payment type selector with dropdown menu
* Users can switch between payment options:
* **Barpreis** (Cash price)
* **Leasing** (Monthly lease payment - shown if available)
* **Finanzierung** (Monthly financing payment - shown if available)
* Dropdown button shows the currently selected payment type
* Price display updates dynamically when switching payment types
* Tax information shown based on selected payment type

#### Example with Rates Enabled <a href="#example-with-rates-enabled" id="example-with-rates-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-rates="true"
></div>
```

#### Example with All Features Including Rates <a href="#example-with-all-features-including-rates" id="example-with-all-features-including-rates"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-ai-assistant="true"
data-wishlist="true"
data-compare="true"
data-history="true"
data-rates="true"
></div>
```

### Contextual Counts Functionality <a href="#contextual-counts-functionality" id="contextual-counts-functionality"></a>

When contextual counts are enabled via the `data-contextual-count="true"` attribute, each filter option shows how many vehicles would match if it were selected, taking the other active filters into account:

* Counts are displayed next to the options in the manufacturer, model, fuel type, condition, vehicle type, engine type, colour, and store filters.
* Counts update as the user changes other filters, so they always reflect the current search context.
* The count for a given filter ignores that filter's own current selection (so the user can see all available options), but respects every other active filter.
* Helps users avoid selecting combinations that would return zero results.

#### Example with Contextual Counts Enabled <a href="#example-with-contextual-counts-enabled" id="example-with-contextual-counts-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-contextual-count="true"
></div>
```

### Infinite Scroll Functionality <a href="#infinite-scroll-functionality" id="infinite-scroll-functionality"></a>

When infinite scroll is enabled via the `data-infinite-scroll="true"` attribute, the numbered pagination control is replaced by automatic loading:

* As the user scrolls toward the bottom of the results list, the next page is fetched and appended automatically.
* Already-loaded cards stay fully interactive while the next page loads; a loading spinner is shown at the bottom of the list.
* Loading stops once the last page has been reached.
* On a full page reload the list resets to the first page.

When the attribute is omitted or set to `"false"` (default), results use the standard numbered pagination control instead.

#### Example with Infinite Scroll Enabled <a href="#example-with-infinite-scroll-enabled" id="example-with-infinite-scroll-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-infinite-scroll="true"
></div>
```

### Price Alert Functionality <a href="#price-alert-functionality" id="price-alert-functionality"></a>

When the price-alert feature is enabled via the `data-price-alert="true"` attribute, visitors can subscribe to price-change notifications for a specific vehicle:

* A price-alert action opens a modal where the visitor enters their name and email address and accepts the privacy policy.
* On submission, the subscription is registered with the Carpose backend.
* The visitor is notified by email when the vehicle's price changes.

#### Example with Price Alert Enabled <a href="#example-with-price-alert-enabled" id="example-with-price-alert-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-price-alert="true"
></div>
```

### Used Car Energy Costs Functionality <a href="#used-car-energy-costs-functionality" id="used-car-energy-costs-functionality"></a>

The estimated yearly energy/fuel cost box (part of the consumption display) is shown for new cars by default. When `data-used-car-energy-costs="true"` is set, the same estimate is also displayed for used cars:

* Shows an estimated yearly energy or fuel cost based on the vehicle's consumption data.
* Only affects used vehicles; new vehicles always show the estimate regardless of this attribute.
* Has no effect on vehicles that do not have consumption data.

#### Example with Used Car Energy Costs Enabled <a href="#example-with-used-car-energy-costs-enabled" id="example-with-used-car-energy-costs-enabled"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-used-car-energy-costs="true"
></div>
```

### Examples <a href="#examples" id="examples"></a>

#### Basic Example <a href="#basic-example" id="basic-example"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
></div>
```

#### Full Example with All Options <a href="#full-example-with-all-options" id="full-example-with-all-options"></a>

```html
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-force-scroll-to-top="true"
data-enable-page-load-focus="true"
data-default-form-state-hash="eyJtYW51ZmFjdHVyZXIiOiIifQ=="
data-ai-assistant="true"
data-contextual-count="true"
data-wishlist="true"
data-compare="true"
data-history="true"
data-rates="true"
data-price-alert="true"
data-used-car-energy-costs="true"
data-infinite-scroll="true"
></div>
```

#### Two-Page Setup Example <a href="#two-page-setup-example" id="two-page-setup-example"></a>

When using `data-search-url-forward`, you need two pages:

**Page 1: Search Form (e.g., /index.html)**

```html
<!-- Form only - redirects to results page on submit -->
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-ai-assistant="true"
data-wishlist="true"
data-compare="true"
data-history="true"
data-rates="true"
></div>
```

**Page 2: Results Page (e.g., /search-results.html)**

```html
<!-- Form + Results - displays results inline based on localStorage -->
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-ai-assistant="true"
data-wishlist="true"
data-compare="true"
data-history="true"
data-rates="true"
></div>
```

In this setup:

* The form on Page 1 saves the form state to localStorage and redirects to `/search-results`
* The form on Page 2 reads the form state from localStorage and displays the results inline

#### AI-Only Landing Page Setup <a href="#ai-only-landing-page-setup" id="ai-only-landing-page-setup"></a>

For a dedicated AI search landing page, add `data-ai-only="true"` so the form collapses to just the AI input and (together with `data-search-url-forward`) redirects to a results page:

**Page 1: AI Search Landing Page (e.g., /ai-search.html)**

```html
<!-- Only AI input - no filters shown, redirects to results page -->
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-search-url-forward="/search-results"
data-ai-assistant="true"
data-ai-only="true"
></div>
```

**Page 2: Results Page (e.g., /search-results.html)**

```html
<!-- Full form + AI + Results - displays results with AI history available -->
<div
data-carpose-component="vehicle-search-form"
data-api-key="your-api-key"
data-ai-assistant="true"
data-wishlist="true"
data-compare="true"
data-history="true"
data-rates="true"
></div>
```

In this AI-only setup:

* Page 1 shows ONLY the AI input field (all filters, buttons hidden)
* Floating AI button is hidden
* After user enters their search query, the AI processes it
* AI query and hash are saved to localStorage for Page 2
* Form state is saved to localStorage for Page 2
* User is automatically redirected to `/search-results`
* Page 2 loads with the AI search history and results already available
* User can continue refining their search conversationally on Page 2

### Behavior <a href="#behavior" id="behavior"></a>

1. **Search Mode**:

* **With `data-search-url-forward` set**: Clicking the search button redirects to the specified URL. The form state is saved to localStorage for that specific page path. No results are shown inline. The target page must include another car-search-form component to read the localStorage state and display the results.
* **Without `data-search-url-forward` (or set to null)**: Search results are displayed inline directly below the form on the same page.

2. **Form State Persistence**:

* Form state is stored in browser localStorage with page-specific keys.
* State persists across page reloads and browser sessions.

3. **Form Reset**:

* The reset button restores form to default state.
* The default state is determined by `data-default-form-state-hash` if provided, otherwise a new empty state is created.

4. **Scrolling Behavior**:

* Control scroll behavior with `data-force-scroll-to-top` and `data-enable-page-load-focus`.
* After successful AI assistant searches, the page automatically scrolls to the car list results.

5. **AI Assistant Behavior**:

* Enable AI-powered natural language search with `data-ai-assistant="true"`.
* **Requires AI Module License** - Feature will not work without active license.
* Users can describe their desired vehicle in German natural language.

**Standard Mode (without `data-search-url-forward`):**

* AI assistant is accessible through:
  * Input field in the search form (can be toggled on/off via drawer switch)
  * Floating button with AI icon that opens a slide-out drawer
* Search history with timestamps is maintained and persists in browser localStorage.
* Conversational interface allows building upon previous searches.
* Context is maintained across multiple queries for refinement.
* After a successful search:
  * Filter settings are automatically applied to the form
  * If opened from drawer, the drawer automatically closes
  * Page scrolls smoothly to the car list results
* Users can:
  * View their full search history with relative timestamps
  * Remove individual queries from history (triggers re-processing of remaining queries)
  * Clear entire search history
  * Toggle visibility of AI input in the main form
* Typing animation in the placeholder text provides visual guidance on how to use the feature.

**AI-Only Mode (`data-ai-only="true"` together with `data-ai-assistant="true"`):**

* Only the AI input field is shown — all filter selects, buttons, and controls (including the floating AI button) are hidden.
* Each search is independent (no conversational context between queries).
* When combined with `data-search-url-forward`, a successful search saves the form state and AI query to localStorage and redirects to the target page, which loads with the AI history and results already available.
* This mode is ideal for a dedicated AI search landing page that redirects to a results page.

6. **Wishlist Behavior**:

* Enable wishlist functionality with `data-wishlist="true"`.
* Wishlist state persists between sessions through localStorage.
* Users can send inquiries for multiple vehicles at once from their wishlist.
* Success messages are shown when adding or removing vehicles from the wishlist.

7. **Comparison Behavior**:

* Enable comparison functionality with `data-compare="true"`.
* Comparison state persists between sessions through localStorage.
* Users can compare up to 3 vehicles side by side.
* Warning message is shown when trying to add more than 3 vehicles.
* Success messages are shown when adding or removing vehicles from the comparison.
* Responsive column layout: 1 car (full width), 2 cars (50/50), 3 cars (equal thirds).

8. **History Behavior**:

* Enable visit history functionality with `data-history="true"`.
* History automatically tracks all visited car detail pages.
* History state persists between sessions through localStorage.
* Most recently visited vehicles appear at the top of the list.
* History is automatically limited to the last 50 visited vehicles.
* Users can clear their entire visit history.
* No individual delete - history is meant for tracking, not managing.
* Floating history button displays with a clock icon.

9. **Rates Behavior**:

* Enable payment rate options with `data-rates="true"`.
* When disabled (default): Only cash price is shown with a simple static display.
* When enabled: Interactive payment selector appears with dropdown menu.
* Available payment types:
  * Cash price (Barpreis) - always available
  * Monthly leasing rate - shown only if vehicle has leasing data
  * Monthly financing rate - shown only if vehicle has financing data
* Users can switch between payment types using the dropdown button.
* Selected payment type is displayed on the button label.
* Price display dynamically updates when switching payment types.
* Tax information (VAT deductible status) is shown based on payment type.

10. **Pagination**:

* When inline search results are displayed, results are paginated with 9 items per page.
* Pagination controls are shown at the bottom of the results when there are multiple pages.

11. **Contextual Counts Behavior**:

* Enable per-option result counts with `data-contextual-count="true"`.
* Each filter option shows how many vehicles match it given the other active filters.
* A filter's own current selection is ignored when computing its counts; all other active filters are respected.
* Counts refresh whenever the surrounding filters change.

12. **Infinite Scroll Behavior**:

* Enable infinite scroll with `data-infinite-scroll="true"`.
* Replaces the numbered pagination control; the next page loads automatically as the user nears the bottom of the list.
* Already-loaded cards remain interactive while the next page loads.
* Loading stops once the last page has been reached.
* On a full page reload the list resets to the first page.

13. **Price Alert Behavior**:

* Enable price-alert subscriptions with `data-price-alert="true"`.
* Opens a modal where the visitor submits their name, email, and privacy consent.
* The visitor is notified by email when the vehicle's price changes.

14. **Used Car Energy Costs Behavior**:

* Enable used-car energy/fuel cost estimates with `data-used-car-energy-costs="true"`.
* The estimated yearly energy/fuel cost box is shown for new cars by default; this attribute extends it to used cars.
* Has no effect on vehicles without consumption data.

### Live Attribute Updates <a href="#live-attribute-updates" id="live-attribute-updates"></a>

The search form watches its own `data-*` attributes after it has mounted. If you change an attribute at runtime — for example toggling `data-wishlist` from `"false"` to `"true"` from your own JavaScript — the form picks up the change and re-applies the affected feature without a page reload.

### Deep-Linking & SEO <a href="#deep-linking-and-seo" id="deep-linking-and-seo"></a>

#### Linking directly to a vehicle <a href="#linking-directly-to-a-vehicle" id="linking-directly-to-a-vehicle"></a>

The search reads a `vehicle` query parameter from the page URL and opens that vehicle's detail view automatically. Use it to link or share a direct link to a specific vehicle:

```
https://your-site.example/search?vehicle=audi-q3-s-line--efbb7d07-0101-11f0-8de6-960000658564
```

The parameter value follows the `{slug}--{uuid}` pattern — a human-readable slug, two dashes, then the vehicle UUID. The bare UUID works on its own too (`?vehicle=efbb7d07-0101-11f0-8de6-960000658564`); the slug is optional and only improves readability and SEO. Any other query parameters already on the URL (such as `utm_*` tracking) are preserved.

#### Linking to a result page <a href="#linking-to-a-result-page" id="linking-to-a-result-page"></a>

The current page of the result list is written to the URL the same way, as `cp-page`:

```
https://your-site.example/search?cp-page=2
```

Every page of the listing is a real link, so a crawler can follow the results past the first page and a shared link opens the page it names. Page 1 carries no parameter, so the first page keeps exactly one address. The parameter is namespaced as `cp-page` rather than `page` because content management systems commonly reserve `page` for their own pagination.

#### Path URLs instead of query parameters <a href="#path-urls" id="path-urls"></a>

Both states can also ride in the path instead of the query string — `/fahrzeug/{slug}--{uuid}/` and `/seite/2/`. This is an account setting, and can be overridden per embed with `data-url-mode="path"`. It requires your website to route those sub-paths back to the page holding the search; the query form works everywhere and stays the default.

See [URL Structure](/misc-modules/08_url-structure) for both forms, the host requirements and the order to follow when switching an existing search over.

#### Structured data (JSON-LD) <a href="#structured-data-json-ld" id="structured-data-json-ld"></a>

When a vehicle's detail view is shown, the component automatically injects [schema.org](https://schema.org) vehicle structured data as a JSON-LD `<script>` so search engines can index the vehicle. This is fully automatic — no configuration or extra markup is required on your side.


# Vehicle Search Small

### Overview <a href="#overview" id="overview"></a>

The `VehicleSearchSmall` component is a lightweight vehicle search form that provides a simplified interface with basic filtering options. It's designed for minimal space usage while still allowing users to search for vehicles by key criteria.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the VehicleSearchSmall component in a standard HTML page:

```html
<div 
  data-carpose-component="vehicle-search-small"
  data-api-key="your-api-key"
  data-search-url-forward="/search-results"
  data-default-form-state-hash="eyJtYW51ZmFjdHVyZXIiOiIifQ=="
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                      | Type   | Default | Description                                                                                                         |
| ------------------------------ | ------ | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component`       | string | -       | Identifies the component type, must be set to "car-search-small"                                                    |
| `data-api-key`                 | string | -       | API key for authentication with the Carpose backend services. Required for data fetching.                           |
| `data-search-url-forward`      | string | null    | URL to forward to when search button is clicked. Required for redirecting to the full search results page.          |
| `data-default-form-state-hash` | string | null    | Default form state hash to use when initializing the form. If not provided, a new empty form state will be created. |

### Features <a href="#features" id="features"></a>

* Filtering by manufacturer, model, and condition
* Real-time vehicle count display
* Search button to redirect to full search results
* Responsive layout that adapts to different screen sizes

### Examples <a href="#examples" id="examples"></a>

#### Basic Example <a href="#basic-example" id="basic-example"></a>

```html
<!-- Basic implementation with required attributes -->
<div 
  data-carpose-component="vehicle-search-small"
  data-api-key="your-api-key"
  data-search-url-forward="/vehicles"
></div>
```

#### With Default Form State <a href="#with-default-form-state" id="with-default-form-state"></a>

```html
<!-- Implementation with default form state (pre-selected manufacturer) -->
<div 
  data-carpose-component="vehicle-search-small"
  data-api-key="your-api-key"
  data-search-url-forward="/vehicles"
  data-default-form-state-hash="eyJtYW51ZmFjdHVyZXIiOiIxIn0="
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Search Mode**:
   * When the search button is clicked, the component redirects to the URL specified in `data-search-url-forward` with the form state hash appended.
   * The full search page will load with the current filters applied.
2. **Form Initialization**:
   * The form is initialized with the default state from `data-default-form-state-hash` if provided.
   * If not provided, an empty form state is created.
3. **Real-time Count**:
   * As filters are changed, the vehicle count updates in real-time to show available matches.


# Vehicle Search (AI)

### Overview <a href="#overview" id="overview"></a>

The `Vehicle Search AI` component provides a natural language search interface for vehicles. It allows users to search for cars using conversational queries rather than structured filters, making it more intuitive and user-friendly.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Vehicle Search AI component in a standard HTML page:

```html
<div
  data-carpose-component="vehicle-search-ai"
  data-api-key="your-api-key"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "car-search-ai"                             |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |

### Features <a href="#features" id="features"></a>

* Natural language search input for intuitive queries
* Results displayed as car cards with vehicle details
* Sorting options for search results:
  * Price (ascending/descending)
  * Mileage (ascending/descending)
  * Power (ascending/descending)
* Pagination for navigating through search results
* Loading indicators during search
* "No results" message when no matching vehicles are found
* Session storage persistence for search state (query, page, sorting)

### Example <a href="#example" id="example"></a>

```html
<div
  data-carpose-component="vehicle-search-ai"
  data-api-key="your-api-key"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Search Interface**:
   * The component displays a prominent search input with a placeholder suggesting example queries.
   * Users can type natural language queries like "I'm looking for a city car" or "Show me SUVs with less than 50,000 km".
   * The search button triggers the query and displays loading indicators during the search process.
2. **Results Display**:
   * Search results are displayed as car cards below the search interface.
   * Each car card shows key information about the vehicle.
   * A count of total matching vehicles is displayed above the results.
   * If no matching vehicles are found, a friendly message is displayed.
3. **Sorting and Pagination**:
   * Once results are displayed, a sorting dropdown appears allowing users to sort by price, mileage, or power.
   * If there are more than 30 results, pagination controls appear at the bottom of the page.
   * Changing the sort order resets to the first page of results.
   * Clicking a pagination number fetches the corresponding page of results.
4. **State Persistence**:
   * The component stores the current search query, page number, and sorting preference in session storage.
   * If the user leaves and returns to the page, their previous search state is restored.
   * This allows users to continue browsing from where they left off.
5. **Responsive Behavior**:
   * The component adapts to different screen sizes.
   * On mobile devices, the sorting control appears above the results count for better usability.


# Vehicle Types Browser

{% hint style="info" %}
The Vehicle Types Browser displays the body types in your inventory as tiles so visitors can start browsing by type.
{% endhint %}

## Introduction

The `vehicle-types-browser` component is the first step for visitors who know they want an SUV or an estate but not yet which model. It shows every body type that currently has vehicles in stock as a tile with an illustration, the name and — optionally — the number of matching vehicles. Clicking a tile opens your vehicle search page with the type filter applied.

## Basic Usage

```html
<div
  data-carpose-component="vehicle-types-browser"
  data-api-key="your-api-key"
></div>
```

## Attributes

| Attribute                                                                              | Required | Default    | Description                                                                                                                     |
| -------------------------------------------------------------------------------------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component`                                                               | Yes      | —          | Must be `vehicle-types-browser`                                                                                                 |
| `data-api-key`                                                                         | Yes      | —          | Your Carposé API key                                                                                                            |
| `data-layout`                                                                          | No       | `carousel` | `carousel` for a horizontally scrolling row with arrows, `grid` for a static grid                                               |
| `data-grid-columns`                                                                    | No       | `4`        | Columns of the grid on desktop: `1`, `2`, `3` or `4`. One column on phones. Ignored in carousel layout.                         |
| `data-show-count`                                                                      | No       | `true`     | Set to `"false"` to hide the number of vehicles per type                                                                        |
| `data-title`                                                                           | No       | —          | Optional headline rendered above the tiles                                                                                      |
| `data-primary-color`, `data-secondary-color`, `data-accent-color`, `data-color-scheme` | No       | account    | See [Colors and color scheme](https://gitlab.com/carpose-de/dev-docs/-/tree/main/05_misc-modules/09_colors-and-color-scheme.md) |

## Where a click leads

A tile forwards to the vehicle search page configured on your account (`vehicleSearchMainPageUrl` in the Carposé administration) with the chosen type preselected. If the browser is embedded on that very page, the search form on it is filtered in place instead of navigating.

## Examples

Four-column grid:

```html
<div
  data-carpose-component="vehicle-types-browser"
  data-api-key="your-api-key"
  data-layout="grid"
  data-grid-columns="4"
></div>
```

Two columns for a sidebar, without counts:

```html
<div
  data-carpose-component="vehicle-types-browser"
  data-api-key="your-api-key"
  data-layout="grid"
  data-grid-columns="2"
  data-show-count="false"
></div>
```

## Behavior

* Types are sorted by the number of vehicles, most first
* Only types with at least one vehicle in stock are shown; counts reflect the current inventory
* Responsive: the carousel scrolls with touch and arrows, the grid collapses to one column on phones

## Notes

* Live demo: [carpose.de/demo/fahrzeugtypen](https://carpose.de/demo/fahrzeugtypen)


# Vehicle Group

### Overview <a href="#overview" id="overview"></a>

The `Vehicle Group` component displays a closed collection of vehicles — a group curated in the Carposé admin — with sorting capabilities. Users can sort the vehicles by price, mileage, or power in ascending or descending order.

For presenting a group as a promotion whose cards lead into the vehicle search instead, see [Featured Vehicles](/vehicle-search-modules/07_featured-vehicles).

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Vehicle Group component in a standard HTML page:

```html
<div
  data-carpose-component="vehicle-group"
  data-api-key="your-api-key"
  data-id="your-vehicle-group-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type    | Default         | Description                                                                                                                                                             |
| ------------------------ | ------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string  | -               | Identifies the component type, must be set to "vehicle-group" (the former "car-group" selector still works but is deprecated and will be removed in v3.0)               |
| `data-api-key`           | string  | -               | API key for authentication with the Carposé backend services. Required for data fetching.                                                                               |
| `data-id`                | string  | -               | ID of the vehicle group to display. Required — nothing renders without it.                                                                                              |
| `data-card-variant`      | string  | `classic`       | Card layout: `classic` (portrait card, image on top), `compact` (dense portrait card for high-density grids) or `horizontal` (full-width row, image left, price right). |
| `data-rates`             | boolean | account setting | When `true`, cards show leasing and financing payment options.                                                                                                          |

Like every component, it also accepts the [global color overrides](/#colors--theming) (`data-primary-color`, `data-secondary-color`, `data-accent-color`, `data-color-scheme`).

### Features <a href="#features" id="features"></a>

* Displays a collection of vehicles belonging to a specific group
* Provides sorting options for the displayed vehicles:
  * Price (ascending/descending)
  * Mileage (ascending/descending)
  * Power (ascending/descending)
* Responsive layout that adapts to different screen sizes
* Each vehicle is displayed as a vehicle card in the layout picked by `data-card-variant`

### Example <a href="#example" id="example"></a>

```html
<div
  data-carpose-component="vehicle-group"
  data-api-key="your-api-key"
  data-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the group data from the API using the provided vehicle group ID and API key.
   * The component loads all vehicles associated with the specified group.
2. **Sorting**:
   * Users can sort the displayed vehicles using the dropdown menu at the top.
   * Sorting options include:
     * Price (ascending/descending)
     * Mileage (ascending/descending)
     * Power (ascending/descending)
   * The default sort order is determined by the vehicle group configuration in the backend.
   * Users can clear the sorting to return to the default order.
3. **Vehicle Display**:
   * Vehicles are displayed in a responsive grid layout as vehicle cards.
   * Each card shows key information about the vehicle and resolves in place — the group is a self-contained listing.


# Featured Vehicles

{% hint style="info" %}
Featured Vehicles presents a vehicle group as a promotion — a slider or tile grid whose cards lead into your vehicle search.
{% endhint %}

## Introduction

The `featured-vehicles` component reads the same groups as the [Vehicle Group](/vehicle-search-modules/06_vehicle-group) component but behaves the opposite way: it shows no sorting or payment-type controls, and clicking a card opens the vehicle on your search page instead of resolving it in place. The closed group is a destination — this is a doorway. Use it for a "deals of the month" strip on the homepage or a promotional section on a landing page.

## Basic Usage

```html
<div
  data-carpose-component="featured-vehicles"
  data-api-key="your-api-key"
  data-purpose="monthly-offers"
></div>
```

## Attributes

| Attribute                 | Required                          | Description                                                                                                                                                                                         |
| ------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component`  | Yes                               | Must be `featured-vehicles`                                                                                                                                                                         |
| `data-api-key`            | Yes                               | Your Carposé API key                                                                                                                                                                                |
| `data-id`                 | One of `data-id` / `data-purpose` | ID of the vehicle group to display. Wins over `data-purpose` when both are set.                                                                                                                     |
| `data-purpose`            | One of `data-id` / `data-purpose` | Website placement to resolve to a group, e.g. `monthly-offers`. See [Addressing by purpose](#addressing-by-purpose).                                                                                |
| `data-variant`            | No                                | Layout: `slider` (default) or `tiles`                                                                                                                                                               |
| `data-card-variant`       | No                                | Card layout: `classic` (default), `compact` or `horizontal` — see [Card Variants](#card-variants)                                                                                                   |
| `data-headline`           | No                                | Headline rendered above the vehicles                                                                                                                                                                |
| `data-heading-level`      | No                                | Heading level the vehicle names render at: `"2"`, `"3"` or `"4"` (an `h` prefix is accepted). Defaults to `h2`, or `h3` when `data-headline` is set — the headline then acts as the section's `h2`. |
| `data-search-url-forward` | No                                | Search page a card opens the vehicle on. Overrides the `vehicleSearchMainPageUrl` setting for this mount only.                                                                                      |
| `data-rates`              | No                                | When `true`, cards show leasing and financing payment options. Defaults to your account setting.                                                                                                    |

Like every component, it also accepts the [global color overrides](/#colors--theming) (`data-primary-color`, `data-secondary-color`, `data-accent-color`, `data-color-scheme`).

If neither `data-id` nor `data-purpose` is set, the component renders nothing and logs a console warning.

## Addressing by purpose

`data-purpose` names a website *placement* instead of a fixed group. The dealer marks a vehicle group with that purpose in the Carposé admin and can move the purpose to another group later — without touching the embed on the website. A purpose is unique within a company, so it resolves to at most one group.

```html
<!-- Always shows whichever group currently carries the 'monthly-offers' purpose -->
<div
  data-carpose-component="featured-vehicles"
  data-api-key="your-api-key"
  data-purpose="monthly-offers"
  data-headline="Angebote des Monats"
></div>
```

While no group carries the purpose, the component renders nothing — that is not an error, the dealer simply has not marked a group yet.

Use `data-id` instead when the embed should stay pinned to one specific group:

```html
<div
  data-carpose-component="featured-vehicles"
  data-api-key="your-api-key"
  data-id="550e8400-e29b-41d4-a716-446655440000"
  data-variant="tiles"
></div>
```

## Layouts

* `slider` (default) — a swipeable carousel, for a homepage strip that must not push the page down. Shows up to 3 vehicles at a time on desktop, 1 on mobile.
* `tiles` — a responsive grid that shows the whole set at once.

## Card Variants

`data-card-variant` picks the card itself, independent of the layout:

* `classic` (default) — portrait card, image on top, spec chips, price and a "Mehr Details" button
* `compact` — dense portrait card with label/value spec rows, for high-density grids
* `horizontal` — full-width list row, image left, price right

## Forwarding into the Search

Featured Vehicles sits on pages that carry no vehicle search of their own, so a card cannot open the vehicle in place. Clicking a card sends the browser to your search page with the vehicle deep-linked (`?vehicle=…`), which opens it directly.

That target is normally the **Vehicle Search Main Page URL** configured in your Carposé settings. `data-search-url-forward` overrides it for a single mount. The value may be absolute or site-relative; only `http` and `https` targets are accepted — anything else is ignored and the configured setting is used instead.

## Behavior

* Shows a skeleton grid while loading
* Renders nothing when the group is empty or the purpose is unassigned — no headline over a blank strip
* Vehicles follow the group's configured ordering
* Includes the WLTP information notice resolving the `*` behind consumption values (Pkw-EnVKV)

## Notes

* See [Vehicle Group](/vehicle-search-modules/06_vehicle-group) for the closed listing with sorting controls
* See [Vehicle Slider](/components/04_vehicle-slider) for a carousel of general inventory rather than a curated group


# Offer Slider

### Overview <a href="#overview" id="overview"></a>

The `Offer Slider` component displays promotional offers from a specified offer group. It provides three display variants: a carousel slider (default), a tile grid, or a vertical list layout.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Offer Slider component in a standard HTML page:

```html
<div
  data-carpose-component="offer-slider"
  data-api-key="your-api-key"
  data-id="your-offer-group-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                                                                                                                                                      |
| ------------------------ | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "offer-slider"                                                                                                                                                     |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching.                                                                                                                        |
| `data-id`                | string | -       | ID of the offer group to display. Required to fetch the specific collection of offers.                                                                                                                           |
| `data-offer-urls`        | JSON   | -       | Optional map of offer IDs to redirect configuration. When a user clicks "zum Angebot" for a matched offer ID, the browser navigates to the configured URL instead of opening the detail modal. See format below. |

#### `data-offer-urls` Format <a href="#data-offer-urls-format" id="data-offer-urls-format"></a>

```json
{
  "offer-id-1": { "url": "https://example.com/offer/1", "target": "_blank" },
  "offer-id-2": { "url": "https://example.com/offer/2", "target": "_self" }
}
```

| Field    | Type   | Description                                              |
| -------- | ------ | -------------------------------------------------------- |
| `url`    | string | The URL to navigate to when the offer button is clicked  |
| `target` | string | Browser target: `_blank` (new tab) or `_self` (same tab) |

Offers not listed in the map fall back to the default modal behavior.

### Display Variants <a href="#display-variants" id="display-variants"></a>

#### Slider Variant (Default) <a href="#slider-variant-default" id="slider-variant-default"></a>

The default carousel view with navigation controls:

* Displays one offer at a time with navigation arrows and pagination dots
* Shows image on the left and content on the right on desktop
* Stacks image above content on mobile

### Features <a href="#features" id="features"></a>

* Multiple display variants (slider, tile, list)
* Responsive layout that adapts to different screen sizes
* Navigation controls for slider variant
* For each offer displays:
  * Headline and subheadline
  * Offer image
  * Details list
  * Condition information (including multiple condition variants when the offer defines them)
  * Link to the full offer page

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches offer group data from the API using the provided offer group ID and API key.
   * The component loads all offers associated with the specified offer group.
2. **Display Variant**:
   * The component renders according to the specified variant (slider, tile, or list).
   * If no variant is specified, it defaults to the slider view.
3. **Slider Navigation** (Slider variant only):
   * Users can navigate between offers using the arrow buttons or pagination dots.
   * The slider transitions smoothly between offers.
4. **Offer Display**:
   * Each offer is displayed with its headline, subheadline, and image.
   * Detailed information is shown in a list format.
   * Each offer includes a link to view the full offer details.
   * If available, image captions and consumption text are displayed.
   * Images are displayed with consistent heights (350px for slider and list, adjustable for tiles).
5. **Responsive Layout**:
   * All variants are fully responsive and adapt to different screen sizes.
   * Layout adjusts automatically based on the device viewport.

### Custom Events <a href="#custom-events" id="custom-events"></a>

The Offer Slider component dispatches custom events for tracking user interactions and component state changes.

For detailed information about the event system, event types, analytics integration, and best practices, see the [Events documentation](/events/01_introduction).

#### Quick Event Reference <a href="#quick-event-reference" id="quick-event-reference"></a>

* `carpose-offer-select`: When user clicks "zum Angebot" button
* `carpose-offer-contact-form-success`: When a user successfully submits contact about an offer

See the full [Offer events](/events/06_offer) catalogue for the complete list, including the contact form's submit/error states.

**Note**: All offer events are dispatched under the `carpose-` prefix (never `carpose-de-`) in the [v2 envelope](/events/01_introduction#the-v2-envelope). There is no dedicated component-loaded event for the Offer Slider.


# Offer Item

### Overview <a href="#overview" id="overview"></a>

The `Offer Item` component displays a detailed view of a specific promotional offer, including images, conditions, highlights, and an integrated contact form. It serves as the detailed view after clicking on an offer in the Offer Slider.

{% hint style="info" %}
An offer's content — images, prices, condition variants, highlights, the OEM configurator code, and the share link — is configured by the dealer in the Carposé dashboard. The component only needs the offer's `data-id`; it renders whatever the dashboard provides.
{% endhint %}

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Offer Item component in a standard HTML page:

```html
<div 
  data-carpose-component="offer-item"
  data-api-key="your-api-key"
  data-id="your-offer-id"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "offer-item"                                |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |
| `data-id`                | string | -       | ID of the specific offer to display. Required unless the ID is provided in the URL route. |

### Features <a href="#features" id="features"></a>

* Displays comprehensive information about a promotional offer
* Image carousel with thumbnails for easy navigation
* Highlights section with icons and descriptions
* Detailed condition information (price, financing terms, etc.). When an offer defines several **condition variants** — for example different financing or leasing options — they are shown as selectable tabs, each with its own pricing and terms
* Manufacturer (OEM) configurator code with a copy button, plus a link to open the manufacturer's configurator, when provided for the offer
* A share link for the offer
* Related vehicles notification when similar cars are available
* Integrated contact form with:
  * Topic selection
  * Store/location selection
  * Contact information fields
  * Privacy policy consent

### Example <a href="#example" id="example"></a>

```html
<div 
  data-carpose-component="offer-item"
  data-api-key="your-api-key"
  data-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches offer data from the API using the provided offer ID and API key.
   * The component also checks for existing vehicles that match the offer's manufacturer and model criteria.
2. **Image Carousel**:
   * The component displays images in a carousel with navigation controls.
   * Thumbnails below allow direct navigation to specific images.
   * Autoplay is enabled by default but stops when manual navigation occurs.
3. **Related Vehicles**:
   * If matching vehicles exist in inventory, a notification is displayed.
   * Clicking on this notification navigates to the search page with appropriate filters applied.
4. **Contact Form**:
   * The integrated form validates required fields.
   * On submission, it sends the inquiry data to the backend.
   * Success/error messages are displayed after submission.
5. **Content Display**:
   * Headline and subheadline are prominently displayed.
   * Condition information shows price and financing terms.
   * Highlights section displays important features with icons.
   * Additional text content is rendered from HTML.


# Introduction

This section provides an overview of the JavaScript events emitted by the Carposé React application. These events serve as integration points for manual intervention, custom analytics, and behavioral tracking. **Carposé does not track or collect user behavior by default.** Instead, it leaves full control to the implementer to decide when and how such tracking should occur. Concretely, this means the app only dispatches plain DOM `CustomEvent`s — it never calls `dataLayer.push`, `gtag`, `fbq`, or any other analytics SDK itself.

The following pages detail all available event types, including their payloads and recommended use cases. These events can be used to build custom logging, analytics pipelines, or trigger external workflows based on user interaction within the Carposé app.

### Naming Grammar <a href="#naming-grammar" id="naming-grammar"></a>

Every public event name follows the same grammar:

```
carpose-<entity>-<feature>-<action>
```

* **entity** — one of `vehicle`, `offer`, `store`, `employee`, `ui`. This is **not** the UI surface the interaction happened on — it is whatever the event's `data` payload describes. For example, the wishlist, the vehicle comparison tray, the AI search assistant, and the budget calculator are all distinct UI surfaces, but each of them acts on vehicle data, so all of their events live under `carpose-vehicle-*` and all carry the same vehicle payload shape (see [Vehicle Search](/events/02_vehicle-search)). The surface itself is reported separately, in `detail.component`.
* **feature** — an optional grouping such as `contact`, `search`, `wishlist`, `comparison`, `budget`, `viewer`, `test-drive`, `purchase`, `configurator`. Some events (e.g. `carpose-vehicle-select`, `carpose-offer-select`, `carpose-store-select`) have no feature segment.
* **action** — what happened, e.g. `select`, `open`, `add`, `form-success`.

### The v2 Envelope <a href="#the-v2-envelope" id="the-v2-envelope"></a>

Every event is dispatched on `document.body` with `bubbles: true`, so a single listener attached anywhere in the ancestor chain (including directly on `document.body`) will catch it. The `CustomEvent.detail` object always has this shape:

```json
{
  "message": "Human-readable label, e.g. \"Vehicle select\"",
  "type": "vehicle | offer | store | employee | ui",
  "feature": "Optional grouping, e.g. \"contact\" — omitted when the event has none",
  "action": "What happened, e.g. \"select\" or \"form-success\"",
  "channel": "Optional contact/search channel, e.g. \"phone\", \"mail\", \"whatsapp\", \"form\", \"ai\" — omitted when not applicable",
  "component": "The mounted widget that rendered the interaction, e.g. \"vehicle-search-form\", \"vehicle-slider\"",
  "version": 2,
  "data": "Event-specific payload — stable per entity, see the catalogue pages"
}
```

Field reference:

| Field       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `message`   | Human-readable label for the event. Convenient for debug logging, not meant to be parsed.                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `type`      | The entity — see Naming Grammar above.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `feature`   | Optional. The feature grouping segment of the event name, when the event has one.                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `action`    | The action segment of the event name.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `channel`   | Optional. Present on contact and some search events to indicate the channel used (`phone`, `mail`, `whatsapp`, `form`, `ai`). Note that `channel` alone does not always distinguish everything you might expect — see the callout on `carpose-vehicle-search-submit` in [Vehicle Search](/events/02_vehicle-search).                                                                                                                                                                                                           |
| `component` | The mounted widget instance that rendered the interaction (the value passed to `data-carpose-component` when the widget was embedded), e.g. `vehicle-search-form`, `vehicle-slider`, `offer-item`. Several widgets can share the same event name — e.g. a vehicle card can be rendered by the search form, a vehicle slider, or the vehicle detail page — so `component` is how you tell them apart. A few narrowly-scoped components (like the 360° exterior viewer) report their own name directly instead of an ancestor's. |
| `version`   | Always `2` for events documented on this page.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `data`      | The event-specific payload. Its shape is stable per entity — every `vehicle` event carries the same vehicle fields, every `offer` event the same offer fields, and so on — see the catalogue pages for exact shapes.                                                                                                                                                                                                                                                                                                           |

### Internal Command Events — Do Not Track <a href="#internal-command-events" id="internal-command-events"></a>

A small number of events under the **`carpose-internal-*`** namespace exist purely as cross-component commands inside the Carposé app itself — for example, telling a search form to preset a vehicle type, or telling it that the AI assistant's state changed. They are dispatched on **`window`**, not `document.body`, do **not** use the v2 envelope described above, and their shape can change at any time without notice.

**Do not attach analytics or business logic to any `carpose-internal-*` event.** They are not part of the public event surface, are not listed in the catalogue pages, and are excluded from the breaking-change guarantees described on the [Deprecated Events](/events/08_deprecated-events) page.


# Vehicle Search

This page documents every `carpose-vehicle-*` event dispatched by the Carposé application. This is the largest event group because **`vehicle` is an entity, not a UI surface**: the vehicle card grid, the vehicle detail page, the wishlist, the comparison tray, the AI search assistant, the budget calculator, the test-drive booking wizard and the purchase request form all act on vehicle data, so all of their events live here, distinguished by `detail.component` (see [Introduction](/events/01_introduction#the-v2-envelope)). Carposé itself does **not** track user behavior; manual implementation is required.

All events below are dispatched in the [v2 envelope](/events/01_introduction) on `document.body`.

## Select

### Vehicle Select Event <a href="#vehicle-select-event" id="vehicle-select-event"></a>

Dispatched when a user clicks on a vehicle card to view its details.

**Event Name**: `carpose-vehicle-select`

**Event Data**:

```json
{
  message: "Vehicle select",
  type: "vehicle",
  action: "select",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-select', function(event) {
  const vehicle = event.detail.data;

  console.log('User selected vehicle:', vehicle.name);
  console.log('Rendered by:', event.detail.component);
});
```

## Contact

The `channel` field distinguishes how the user is contacting about the vehicle: `phone`, `mail`, `whatsapp`, or `form` (the multi-step contact form flow).

The three `form` events below — submit, success and error — are shared across every contact form that posts to a vehicle's `/vehicles/{id}/contact` endpoint. That currently means the vehicle card's contact modal (component reflects wherever that card is mounted, e.g. `vehicle-search-form` or `vehicle-detail`) and the wishlist drawer's "Anfrage senden" contact form (`component: "floating-buttons"`, the standard mount for the wishlist floating button), which submits for the first vehicle selected in the drawer. Both mirror the same shape: submit is dispatched after client-side validation passes and before the API call, success after it resolves, error in the catch.

### Vehicle Contact Phone Event <a href="#vehicle-contact-phone-event" id="vehicle-contact-phone-event"></a>

Dispatched when a user clicks a `tel:` contact link for a vehicle.

**Event Name**: `carpose-vehicle-contact-phone`

**Event Data**:

```json
{
  message: "Vehicle contact phone",
  type: "vehicle",
  feature: "contact",
  action: "phone",
  channel: "phone",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-phone', function(event) {
  const vehicle = event.detail.data;

  console.log('Phone contact initiated for:', vehicle.name);
});
```

### Vehicle Contact Mail Event <a href="#vehicle-contact-mail-event" id="vehicle-contact-mail-event"></a>

Dispatched when a user clicks a `mailto:` contact link for a vehicle.

**Event Name**: `carpose-vehicle-contact-mail`

**Event Data**:

```json
{
  message: "Vehicle contact mail",
  type: "vehicle",
  feature: "contact",
  action: "mail",
  channel: "mail",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-mail', function(event) {
  const vehicle = event.detail.data;

  console.log('Mail contact initiated for:', vehicle.name);
});
```

### Vehicle Contact WhatsApp Event <a href="#vehicle-contact-whatsapp-event" id="vehicle-contact-whatsapp-event"></a>

Dispatched when a user clicks a WhatsApp contact link for a vehicle.

**Event Name**: `carpose-vehicle-contact-whatsapp`

**Event Data**:

```json
{
  message: "Vehicle contact WhatsApp",
  type: "vehicle",
  feature: "contact",
  action: "whatsapp",
  channel: "whatsapp",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-whatsapp', function(event) {
  const vehicle = event.detail.data;

  console.log('WhatsApp contact initiated for:', vehicle.name);
});
```

### Vehicle Contact Form Open Event <a href="#vehicle-contact-form-open-event" id="vehicle-contact-form-open-event"></a>

Dispatched when a user opens the vehicle contact form modal.

**Event Name**: `carpose-vehicle-contact-form-open`

**Event Data**:

```json
{
  message: "Vehicle contact form open",
  type: "vehicle",
  feature: "contact",
  action: "form-open",
  channel: "form",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-form-open', function(event) {
  const vehicle = event.detail.data;

  console.log('Contact form opened for:', vehicle.name);
});
```

### Vehicle Contact Form Submit Event <a href="#vehicle-contact-form-submit-event" id="vehicle-contact-form-submit-event"></a>

Dispatched when a user submits the vehicle contact form, before the API call resolves.

**Event Name**: `carpose-vehicle-contact-form-submit`

**Event Data**:

```json
{
  message: "Vehicle contact form submit",
  type: "vehicle",
  feature: "contact",
  action: "form-submit",
  channel: "form",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-form-submit', function(event) {
  const vehicle = event.detail.data;

  console.log('Contact form submitted for:', vehicle.name);
});
```

### Vehicle Contact Form Success Event <a href="#vehicle-contact-form-success-event" id="vehicle-contact-form-success-event"></a>

Dispatched when the vehicle contact form submission succeeds. This is the vehicle equivalent of the old `carpose-car-contact` event and the one to use for lead-generation conversions.

**Event Name**: `carpose-vehicle-contact-form-success`

**Event Data**:

```json
{
  message: "Vehicle contact form success",
  type: "vehicle",
  feature: "contact",
  action: "form-success",
  channel: "form",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-form-success', function(event) {
  const vehicle = event.detail.data;

  console.log('Contact form submitted successfully for:', vehicle.name);
});
```

### Vehicle Contact Form Error Event <a href="#vehicle-contact-form-error-event" id="vehicle-contact-form-error-event"></a>

Dispatched when the vehicle contact form submission fails.

**Event Name**: `carpose-vehicle-contact-form-error`

**Event Data**:

```json
{
  message: "Vehicle contact form error",
  type: "vehicle",
  feature: "contact",
  action: "form-error",
  channel: "form",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-contact-form-error', function(event) {
  const vehicle = event.detail.data;

  console.log('Contact form submission failed for:', vehicle.name);
});
```

## Test Drive

These events cover two different entry points, told apart by `detail.component`:

* the **standalone booking wizard** ([Test Drive Booking](/components/13_test-drive-form)), where the visitor picks a car from your test-drive fleet before choosing a slot — `component: "test-drive-form"`, or `"floating-buttons"` when it is opened from the overlay;
* a **vendor integration** on a vehicle detail page (e.g. AutoUncle), which only opens an external flow — `component: "vehicle-detail"`.

Only the wizard dispatches the step, submit, success and error events; the vendor integration dispatches nothing beyond `open`. None of these events carry a vehicle payload — the wizard's fleet is a separate list from your vehicle inventory, so `data` describes the booking, not a vehicle.

Booking a test drive for one specific vehicle from an offer page is a different flow with its own events — see [Offer](/events/06_offer#test-drive).

### Vehicle Test Drive Open Event <a href="#vehicle-test-drive-open-event" id="vehicle-test-drive-open-event"></a>

Dispatched when a user opens a test drive booking flow — either the standalone wizard (via its modal trigger or the floating button) or a vendor integration such as AutoUncle.

**Event Name**: `carpose-vehicle-test-drive-open`

**Event Data**:

```json
{
  message: "Vehicle test drive open",
  type: "vehicle",
  feature: "test-drive",
  action: "open",
  component: "vehicle-detail",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-test-drive-open', function(event) {
  const vehicle = event.detail.data;

  console.log('Test drive flow opened for:', vehicle.name);
});
```

### Vehicle Test Drive Step Event <a href="#vehicle-test-drive-step-event" id="vehicle-test-drive-step-event"></a>

Dispatched each time the visitor completes a step of the booking wizard and moves on. `data.step` is the number of the step just completed, counting from 1, so a full booking produces `1` (vehicle chosen) and `2` (date and time chosen). Step 3 has no step event — its completion is the submit.

Because the event only fires once a step's required fields validate, the last step you receive tells you where a visitor dropped out.

**Event Name**: `carpose-vehicle-test-drive-step`

**Event Data**:

```json
{
  message: "Vehicle test drive step",
  type: "vehicle",
  feature: "test-drive",
  action: "step",
  component: "test-drive-form",
  version: 2,
  data: {
    step: 1
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-test-drive-step', function(event) {
  console.log('Completed test drive step:', event.detail.data.step);
});
```

### Vehicle Test Drive Submit Event <a href="#vehicle-test-drive-submit-event" id="vehicle-test-drive-submit-event"></a>

Dispatched after the contact step validates and before the booking request is sent. Pair it with success and error to measure how often submissions fail.

**Event Name**: `carpose-vehicle-test-drive-submit`

**Event Data**:

```json
{
  message: "Vehicle test drive submit",
  type: "vehicle",
  feature: "test-drive",
  action: "submit",
  component: "test-drive-form",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-test-drive-submit', function() {
  console.log('Test drive booking submitted');
});
```

### Vehicle Test Drive Success Event <a href="#vehicle-test-drive-success-event" id="vehicle-test-drive-success-event"></a>

Dispatched when the booking has been created. `status` is the booking's state, which is `pending` for a new request — you still confirm the appointment.

This is the event to use as a conversion.

**Event Name**: `carpose-vehicle-test-drive-success`

**Event Data**:

```json
{
  message: "Vehicle test drive success",
  type: "vehicle",
  feature: "test-drive",
  action: "success",
  component: "test-drive-form",
  version: 2,
  data: {
    bookingId: "b1f2c3d4-5678-90ab-cdef-1234567890ab",
    status: "pending"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-test-drive-success', function(event) {
  const booking = event.detail.data;

  console.log('Test drive booked:', booking.bookingId, booking.status);
});
```

### Vehicle Test Drive Error Event <a href="#vehicle-test-drive-error-event" id="vehicle-test-drive-error-event"></a>

Dispatched when the booking request fails. `status` is the HTTP status, or `0` when the request never reached the server (offline, blocked, timed out).

A `400` means the slot was taken while the visitor was filling in the form; the wizard returns them to the appointment step with refreshed availability. A `429` means the visitor is being rate-limited.

**Event Name**: `carpose-vehicle-test-drive-error`

**Event Data**:

```json
{
  message: "Vehicle test drive error",
  type: "vehicle",
  feature: "test-drive",
  action: "error",
  component: "test-drive-form",
  version: 2,
  data: {
    status: 400
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-test-drive-error', function(event) {
  console.warn('Test drive booking failed with status:', event.detail.data.status);
});
```

## Purchase

These events come from the [Purchase Request Form](/components/12_purchase-request-form) — the wizard in which a visitor offers **their own** vehicle to you. They all carry `channel: "form"` and `feature: "purchase"`.

`detail.component` is `purchase-request-form` for an embedded wizard, or `floating-buttons` when it is opened from the overlay.

None of these events carry a vehicle payload: the vehicle being described is the visitor's own car and does not exist in your inventory, so `data` stays limited to progress and error information. The details the visitor entered are in the enquiry itself, in the Carposé dashboard — they are deliberately not put on the page, since these events are meant for analytics tools.

### Vehicle Purchase Form Open Event <a href="#vehicle-purchase-form-open-event" id="vehicle-purchase-form-open-event"></a>

Dispatched when the wizard is opened from a trigger — the button of a `data-variant="modal"` embed, or the "Fahrzeug verkaufen" floating button. An inline embed is visible immediately and dispatches nothing.

**Event Name**: `carpose-vehicle-purchase-form-open`

**Event Data**:

```json
{
  message: "Vehicle purchase form open",
  type: "vehicle",
  feature: "purchase",
  action: "form-open",
  channel: "form",
  component: "purchase-request-form",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-purchase-form-open', function(event) {
  console.log('Purchase request wizard opened from:', event.detail.component);
});
```

### Vehicle Purchase Form Step Event <a href="#vehicle-purchase-form-step-event" id="vehicle-purchase-form-step-event"></a>

Dispatched each time the visitor completes a step and moves on. `data.step` counts the visible steps from 1, so it reflects what the visitor actually saw: with the photo step enabled you receive `1`, `2` and `3`; with `data-photo-upload="false"` you receive `1` and `2`. The final step has no step event — its completion is the submit.

**Event Name**: `carpose-vehicle-purchase-form-step`

**Event Data**:

```json
{
  message: "Vehicle purchase form step",
  type: "vehicle",
  feature: "purchase",
  action: "form-step",
  channel: "form",
  component: "purchase-request-form",
  version: 2,
  data: {
    step: 1
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-purchase-form-step', function(event) {
  console.log('Completed purchase request step:', event.detail.data.step);
});
```

### Vehicle Purchase Form Submit Event <a href="#vehicle-purchase-form-submit-event" id="vehicle-purchase-form-submit-event"></a>

Dispatched after the final step validates and before the enquiry is sent.

**Event Name**: `carpose-vehicle-purchase-form-submit`

**Event Data**:

```json
{
  message: "Vehicle purchase form submit",
  type: "vehicle",
  feature: "purchase",
  action: "form-submit",
  channel: "form",
  component: "purchase-request-form",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-purchase-form-submit', function() {
  console.log('Purchase request submitted');
});
```

### Vehicle Purchase Form Success Event <a href="#vehicle-purchase-form-success-event" id="vehicle-purchase-form-success-event"></a>

Dispatched once the enquiry has been stored **and any photos have finished uploading**. This is the event to use as a conversion.

Two consequences of that ordering worth planning for:

* With photos attached, the event lags the actual storing of the enquiry by however long the upload takes. If the visitor closes the tab in between, the enquiry is safely stored but you never receive the event — treat a missing success event as "unknown", not as "no lead".
* A failed photo upload never turns a stored enquiry into a failure. It still reports success and does **not** produce an error event; the visitor is told in the confirmation which images did not go through.

`data-success-url` redirects at the same point, so a visitor with many photos waits on the last step until the uploads are done.

**Event Name**: `carpose-vehicle-purchase-form-success`

**Event Data**:

```json
{
  message: "Vehicle purchase form success",
  type: "vehicle",
  feature: "purchase",
  action: "form-success",
  channel: "form",
  component: "purchase-request-form",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-purchase-form-success', function() {
  console.log('Purchase request stored');
});
```

### Vehicle Purchase Form Error Event <a href="#vehicle-purchase-form-error-event" id="vehicle-purchase-form-error-event"></a>

Dispatched when the enquiry could not be stored. `status` is the HTTP status, or `0` when the request never reached the server.

A `429` means the visitor is being rate-limited. Other statuses usually mean the server rejected a field; where it can, the wizard jumps the visitor back to the step that owns that field and marks it.

**Event Name**: `carpose-vehicle-purchase-form-error`

**Event Data**:

```json
{
  message: "Vehicle purchase form error",
  type: "vehicle",
  feature: "purchase",
  action: "form-error",
  channel: "form",
  component: "purchase-request-form",
  version: 2,
  data: {
    status: 429
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-purchase-form-error', function(event) {
  console.warn('Purchase request failed with status:', event.detail.data.status);
});
```

## Search

### Vehicle Search Submit Event <a href="#vehicle-search-submit-event" id="vehicle-search-submit-event"></a>

Dispatched when a user-initiated search runs (pagination and the automatic initial page load are excluded).

**Event Name**: `carpose-vehicle-search-submit`

**Event Data**:

```json
{
  message: "Vehicle search submit",
  type: "vehicle",
  feature: "search",
  action: "submit",
  channel: "form",
  component: "vehicle-search-form",
  version: 2,
  data: {
    filters: { "manufacturer": "Renault", "priceMax": 30000 },
    resultCount: 42,
    origin: "form"
  }
}
```

> **Important:** this event's `channel` is always `"form"` — even for AI-originated searches. Use `data.origin` (`"form"` or `"ai"`) to tell them apart, not `channel`. An AI-driven search (started via the AI search assistant, which applies filters and then runs the normal search) flows through this same event so the funnel stays whole, but if you split reporting by `channel` you will silently merge AI-driven and manually-filled searches together. Always branch on `data.origin` when you need to distinguish them.

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-search-submit', function(event) {
  const { filters, resultCount, origin } = event.detail.data;

  // origin, not channel, is what tells you this search came from the AI assistant.
  console.log(origin === 'ai' ? 'AI-driven search' : 'Form search', resultCount, 'results');
});
```

### Vehicle Search AI Open Event <a href="#vehicle-search-ai-open-event" id="vehicle-search-ai-open-event"></a>

Dispatched when a user opens the AI search assistant.

**Event Name**: `carpose-vehicle-search-ai-open`

**Event Data**:

```json
{
  message: "Vehicle search AI open",
  type: "vehicle",
  feature: "search",
  action: "ai-open",
  channel: "ai",
  component: "vehicle-search-ai",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-search-ai-open', function(event) {
  console.log('AI search assistant opened');
});
```

### Vehicle Search AI Submit Event <a href="#vehicle-search-ai-submit-event" id="vehicle-search-ai-submit-event"></a>

Dispatched when a user submits a query to the AI search assistant.

**Event Name**: `carpose-vehicle-search-ai-submit`

**Event Data**:

```json
{
  message: "Vehicle search AI submit",
  type: "vehicle",
  feature: "search",
  action: "ai-submit",
  channel: "ai",
  component: "vehicle-search-ai",
  version: 2,
  data: {
    query: "electric SUV under 40000 euros"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-search-ai-submit', function(event) {
  console.log('AI query submitted:', event.detail.data.query);
});
```

### Vehicle Search AI Reset Event <a href="#vehicle-search-ai-reset-event" id="vehicle-search-ai-reset-event"></a>

Dispatched when a user resets the AI search assistant's conversation history.

**Event Name**: `carpose-vehicle-search-ai-reset`

**Event Data**:

```json
{
  message: "Vehicle search AI reset",
  type: "vehicle",
  feature: "search",
  action: "ai-reset",
  channel: "ai",
  component: "vehicle-search-ai",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-search-ai-reset', function(event) {
  console.log('AI search assistant reset');
});
```

### Vehicle Search Type Select Event <a href="#vehicle-search-type-select-event" id="vehicle-search-type-select-event"></a>

Dispatched when a user selects a vehicle type in the vehicle types browser.

**Event Name**: `carpose-vehicle-search-type-select`

**Event Data**:

```json
{
  message: "Vehicle search type select",
  type: "vehicle",
  feature: "search",
  action: "type-select",
  component: "vehicle-types-browser",
  version: 2,
  data: {
    vehicleTypeId: "suv"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-search-type-select', function(event) {
  console.log('Vehicle type selected:', event.detail.data.vehicleTypeId);
});
```

## Wishlist

### Vehicle Wishlist Add Event <a href="#vehicle-wishlist-add-event" id="vehicle-wishlist-add-event"></a>

Dispatched when a vehicle is added to the wishlist.

**Event Name**: `carpose-vehicle-wishlist-add`

**Event Data**:

```json
{
  message: "Vehicle wishlist add",
  type: "vehicle",
  feature: "wishlist",
  action: "add",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech",
    count: 3
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-wishlist-add', function(event) {
  const vehicle = event.detail.data;

  console.log('Added to wishlist:', vehicle.name, '- total items:', vehicle.count);
});
```

### Vehicle Wishlist Remove Event <a href="#vehicle-wishlist-remove-event" id="vehicle-wishlist-remove-event"></a>

Dispatched when a vehicle is removed from the wishlist.

**Event Name**: `carpose-vehicle-wishlist-remove`

**Event Data**:

```json
{
  message: "Vehicle wishlist remove",
  type: "vehicle",
  feature: "wishlist",
  action: "remove",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech",
    count: 2
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-wishlist-remove', function(event) {
  const vehicle = event.detail.data;

  console.log('Removed from wishlist:', vehicle.name, '- remaining items:', vehicle.count);
});
```

### Vehicle Wishlist Clear Event <a href="#vehicle-wishlist-clear-event" id="vehicle-wishlist-clear-event"></a>

Dispatched when the entire wishlist is cleared.

**Event Name**: `carpose-vehicle-wishlist-clear`

**Event Data**:

```json
{
  message: "Vehicle wishlist clear",
  type: "vehicle",
  feature: "wishlist",
  action: "clear",
  component: "vehicle-search-form",
  version: 2,
  data: {
    count: 0
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-wishlist-clear', function(event) {
  console.log('Wishlist cleared');
});
```

### Vehicle Wishlist Open Event <a href="#vehicle-wishlist-open-event" id="vehicle-wishlist-open-event"></a>

Dispatched when a user opens the wishlist panel.

**Event Name**: `carpose-vehicle-wishlist-open`

**Event Data**:

```json
{
  message: "Vehicle wishlist open",
  type: "vehicle",
  feature: "wishlist",
  action: "open",
  component: "floating-buttons",
  version: 2,
  data: {
    count: 3
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-wishlist-open', function(event) {
  console.log('Wishlist opened with', event.detail.data.count, 'items');
});
```

## Comparison

### Vehicle Comparison Add Event <a href="#vehicle-comparison-add-event" id="vehicle-comparison-add-event"></a>

Dispatched when a vehicle is added to the comparison tray.

**Event Name**: `carpose-vehicle-comparison-add`

**Event Data**:

```json
{
  message: "Vehicle comparison add",
  type: "vehicle",
  feature: "comparison",
  action: "add",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech",
    count: 2
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-comparison-add', function(event) {
  const vehicle = event.detail.data;

  console.log('Added to comparison:', vehicle.name, '- total items:', vehicle.count);
});
```

### Vehicle Comparison Remove Event <a href="#vehicle-comparison-remove-event" id="vehicle-comparison-remove-event"></a>

Dispatched when a vehicle is removed from the comparison tray.

**Event Name**: `carpose-vehicle-comparison-remove`

**Event Data**:

```json
{
  message: "Vehicle comparison remove",
  type: "vehicle",
  feature: "comparison",
  action: "remove",
  component: "vehicle-search-form",
  version: 2,
  data: {
    id: "car-456",
    name: "Renault 4 E-Tech elektrisch Evolution 120 Urban Range",
    offerNumber: "OF123456",
    manufacturer: "Renault",
    model: "4 E-Tech",
    count: 1
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-comparison-remove', function(event) {
  const vehicle = event.detail.data;

  console.log('Removed from comparison:', vehicle.name, '- remaining items:', vehicle.count);
});
```

### Vehicle Comparison Open Event <a href="#vehicle-comparison-open-event" id="vehicle-comparison-open-event"></a>

Dispatched when a user opens the comparison view.

**Event Name**: `carpose-vehicle-comparison-open`

**Event Data**:

```json
{
  message: "Vehicle comparison open",
  type: "vehicle",
  feature: "comparison",
  action: "open",
  component: "floating-buttons",
  version: 2,
  data: {
    count: 2
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-comparison-open', function(event) {
  console.log('Comparison opened with', event.detail.data.count, 'items');
});
```

## Budget

### Vehicle Budget Open Event <a href="#vehicle-budget-open-event" id="vehicle-budget-open-event"></a>

Dispatched when a user opens the budget calculator.

**Event Name**: `carpose-vehicle-budget-open`

**Event Data**:

```json
{
  message: "Vehicle budget open",
  type: "vehicle",
  feature: "budget",
  action: "open",
  component: "floating-buttons",
  version: 2,
  data: {}
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-budget-open', function(event) {
  console.log('Budget calculator opened');
});
```

### Vehicle Budget Apply Event <a href="#vehicle-budget-apply-event" id="vehicle-budget-apply-event"></a>

Dispatched when a user applies their budget settings and closes the calculator.

**Event Name**: `carpose-vehicle-budget-apply`

**Event Data**:

```json
{
  message: "Vehicle budget apply",
  type: "vehicle",
  feature: "budget",
  action: "apply",
  component: "floating-buttons",
  version: 2,
  data: {
    monthlyBudget: 350,
    annualMileage: 15000
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-budget-apply', function(event) {
  const { monthlyBudget, annualMileage } = event.detail.data;

  console.log('Budget applied:', monthlyBudget, '€/month,', annualMileage, 'km/year');
});
```

## Viewer

Both viewer events carry `data.viewer`, one of `"exterior"`, `"interior"` or `"slider"`, so a single listener can tell which viewer type fired. `carpose-vehicle-viewer-unavailable` fires exactly once per occurrence, and `data.reason` distinguishes *why* it fired — `"no-media"` means no media exists for the vehicle at all (the shared fallback screen shown by every viewer type when it has nothing to display), while `"fetch-failed"` means the viewer's own media fetch rejected.

`detail.component` on both viewer events reports whichever widget the viewer is mounted inside — same as every other tracking event — rather than a fixed identity for the viewer itself. Nested inside a vehicle's detail view it reports `vehicle-detail`; mounted standalone (see [360° Exterior Viewer](/components/03_360-exterior-viewer)) it reports the viewer's own component type (e.g. `exterior-viewer`).

### Vehicle Viewer Load Event <a href="#vehicle-viewer-load-event" id="vehicle-viewer-load-event"></a>

Dispatched when a 360°/image viewer successfully loads its media.

**Event Name**: `carpose-vehicle-viewer-load`

**Event Data**:

```json
{
  message: "Vehicle viewer load",
  type: "vehicle",
  feature: "viewer",
  action: "load",
  component: "vehicle-detail",
  version: 2,
  data: {
    viewer: "exterior",
    message: "Viewer loaded"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-viewer-load', function(event) {
  console.log('Viewer loaded:', event.detail.data.viewer);
});
```

### Vehicle Viewer Unavailable Event <a href="#vehicle-viewer-unavailable-event" id="vehicle-viewer-unavailable-event"></a>

Dispatched exactly once when a viewer has no media to show, either because none exists or because the fetch for it failed.

**Event Name**: `carpose-vehicle-viewer-unavailable`

**Event Data**:

```json
{
  message: "Vehicle viewer unavailable",
  type: "vehicle",
  feature: "viewer",
  action: "unavailable",
  component: "vehicle-detail",
  version: 2,
  data: {
    viewer: "exterior",
    message: "No view available",
    reason: "fetch-failed"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-vehicle-viewer-unavailable', function(event) {
  const { viewer, reason } = event.detail.data;

  // reason tells you whether this is "no media at all" or a fetch failure.
  if (reason === 'fetch-failed') {
    console.log(viewer, 'viewer failed to load its media');
  } else {
    console.log('No', viewer, 'media available for this vehicle');
  }
});
```


# Offer

This page documents every `carpose-offer-*` event dispatched by the Carposé application, covering the Offer Slider and Offer Item components.

All events below are dispatched in the [v2 envelope](/events/01_introduction) on `document.body`. Every `offer` event carries the same base payload shape: `{ id, headline, subHeadline }`, with feature-specific fields added on top where noted.

## Select

### Offer Select Event <a href="#offer-select-event" id="offer-select-event"></a>

Dispatched when a user clicks through to view an offer (the "zum Angebot" button).

**Event Name**: `carpose-offer-select`

**Event Data**:

```json
{
  message: "Offer select",
  type: "offer",
  action: "select",
  component: "offer-slider",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-select', function(event) {
  const offer = event.detail.data;

  console.log('Offer selected:', offer.headline);
});
```

## Contact

> **Known limitation:** offer contact is form-only. There is no `carpose-offer-contact-phone`, `carpose-offer-contact-mail` or `carpose-offer-contact-whatsapp` — offers have no associated store, so there is no phone number, email address or WhatsApp number to attribute a click to. If you need channel-specific contact tracking, use the [vehicle contact events](/events/02_vehicle-search#contact) on the underlying vehicle instead.

### Offer Contact Form Submit Event <a href="#offer-contact-form-submit-event" id="offer-contact-form-submit-event"></a>

Dispatched when a user submits the offer contact form, before the API call resolves.

**Event Name**: `carpose-offer-contact-form-submit`

**Event Data**:

```json
{
  message: "Offer contact form submit",
  type: "offer",
  feature: "contact",
  action: "form-submit",
  channel: "form",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-contact-form-submit', function(event) {
  const offer = event.detail.data;

  console.log('Contact form submitted for offer:', offer.headline);
});
```

### Offer Contact Form Success Event <a href="#offer-contact-form-success-event" id="offer-contact-form-success-event"></a>

Dispatched when the offer contact form submission succeeds. This is the offer equivalent of the old `carpose-offer-contact` event and the one to use for lead-generation conversions.

**Event Name**: `carpose-offer-contact-form-success`

**Event Data**:

```json
{
  message: "Offer contact form success",
  type: "offer",
  feature: "contact",
  action: "form-success",
  channel: "form",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-contact-form-success', function(event) {
  const offer = event.detail.data;

  console.log('Contact form submitted successfully for offer:', offer.headline);
});
```

### Offer Contact Form Error Event <a href="#offer-contact-form-error-event" id="offer-contact-form-error-event"></a>

Dispatched when the offer contact form submission fails.

**Event Name**: `carpose-offer-contact-form-error`

**Event Data**:

```json
{
  message: "Offer contact form error",
  type: "offer",
  feature: "contact",
  action: "form-error",
  channel: "form",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-contact-form-error', function(event) {
  const offer = event.detail.data;

  console.log('Contact form submission failed for offer:', offer.headline);
});
```

## Test Drive

### Offer Test Drive Open Event <a href="#offer-test-drive-open-event" id="offer-test-drive-open-event"></a>

Dispatched when a user opens the test drive booking dialog for an offer.

**Event Name**: `carpose-offer-test-drive-open`

**Event Data**:

```json
{
  message: "Offer test drive open",
  type: "offer",
  feature: "test-drive",
  action: "open",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-test-drive-open', function(event) {
  const offer = event.detail.data;

  console.log('Test drive booking opened for offer:', offer.headline);
});
```

### Offer Test Drive Submit Event <a href="#offer-test-drive-submit-event" id="offer-test-drive-submit-event"></a>

Dispatched when a user submits the test drive booking form, before the API call resolves.

**Event Name**: `carpose-offer-test-drive-submit`

**Event Data**:

```json
{
  message: "Offer test drive submit",
  type: "offer",
  feature: "test-drive",
  action: "submit",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-test-drive-submit', function(event) {
  const offer = event.detail.data;

  console.log('Test drive booking submitted for offer:', offer.headline);
});
```

### Offer Test Drive Success Event <a href="#offer-test-drive-success-event" id="offer-test-drive-success-event"></a>

Dispatched when a test drive booking succeeds.

**Event Name**: `carpose-offer-test-drive-success`

**Event Data**:

```json
{
  message: "Offer test drive success",
  type: "offer",
  feature: "test-drive",
  action: "success",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen",
    bookingId: "booking-789",
    status: "confirmed"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-test-drive-success', function(event) {
  const { headline, bookingId, status } = event.detail.data;

  console.log('Test drive booked for offer:', headline, bookingId, status);
});
```

### Offer Test Drive Error Event <a href="#offer-test-drive-error-event" id="offer-test-drive-error-event"></a>

Dispatched when a test drive booking fails.

**Event Name**: `carpose-offer-test-drive-error`

**Event Data**:

```json
{
  message: "Offer test drive error",
  type: "offer",
  feature: "test-drive",
  action: "error",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-test-drive-error', function(event) {
  const offer = event.detail.data;

  console.log('Test drive booking failed for offer:', offer.headline);
});
```

## Configurator

### Offer Configurator Open Event <a href="#offer-configurator-open-event" id="offer-configurator-open-event"></a>

Dispatched when a user opens the manufacturer configurator link for an offer.

**Event Name**: `carpose-offer-configurator-open`

**Event Data**:

```json
{
  message: "Offer configurator open",
  type: "offer",
  feature: "configurator",
  action: "open",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen",
    url: "https://configurator.manufacturer.example/model/123"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-configurator-open', function(event) {
  const { headline, url } = event.detail.data;

  console.log('Configurator opened for offer:', headline, url);
});
```

### Offer Configurator Code Copy Event <a href="#offer-configurator-code-copy-event" id="offer-configurator-code-copy-event"></a>

Dispatched when a user copies the manufacturer configurator code for an offer.

**Event Name**: `carpose-offer-configurator-code-copy`

**Event Data**:

```json
{
  message: "Offer configurator code copy",
  type: "offer",
  feature: "configurator",
  action: "code-copy",
  component: "offer-item",
  version: 2,
  data: {
    id: "offer-123",
    headline: "Sommer-Leasingaktion",
    subHeadline: "Jetzt bis zu 20% sparen",
    code: "ABC-123-XYZ"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-offer-configurator-code-copy', function(event) {
  const { headline, code } = event.detail.data;

  console.log('Configurator code copied for offer:', headline, code);
});
```


# Store and Employee

This page documents the `carpose-store-*`, `carpose-employee-*` and `carpose-ui-*` events dispatched by the Carposé application.

All events below are dispatched in the [v2 envelope](/events/01_introduction) on `document.body`.

## Store

Every `store` event carries the same base payload shape: `{ id, name }`.

> **Note:** `carpose-store-select` is dispatched only from the store map marker (`Store/Map.tsx`). The plain store list (`Store/List.tsx`) has no click handler on its rows, so browsing a list of stores does not by itself emit a select event — only interacting with a map marker does.

### Store Select Event <a href="#store-select-event" id="store-select-event"></a>

Dispatched when a user selects a store marker on the store map.

**Event Name**: `carpose-store-select`

**Event Data**:

```json
{
  message: "Store select",
  type: "store",
  action: "select",
  component: "store-map",
  version: 2,
  data: {
    id: "store-42",
    name: "Autohaus Musterstadt"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-store-select', function(event) {
  const store = event.detail.data;

  console.log('Store selected:', store.name);
});
```

### Store Contact Phone Event <a href="#store-contact-phone-event" id="store-contact-phone-event"></a>

Dispatched when a user clicks a `tel:` contact link for a store.

**Event Name**: `carpose-store-contact-phone`

**Event Data**:

```json
{
  message: "Store contact phone",
  type: "store",
  feature: "contact",
  action: "phone",
  channel: "phone",
  component: "store-list",
  version: 2,
  data: {
    id: "store-42",
    name: "Autohaus Musterstadt"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-store-contact-phone', function(event) {
  const store = event.detail.data;

  console.log('Phone contact initiated for store:', store.name);
});
```

### Store Contact Mail Event <a href="#store-contact-mail-event" id="store-contact-mail-event"></a>

Dispatched when a user clicks a `mailto:` contact link for a store.

**Event Name**: `carpose-store-contact-mail`

**Event Data**:

```json
{
  message: "Store contact mail",
  type: "store",
  feature: "contact",
  action: "mail",
  channel: "mail",
  component: "store-list",
  version: 2,
  data: {
    id: "store-42",
    name: "Autohaus Musterstadt"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-store-contact-mail', function(event) {
  const store = event.detail.data;

  console.log('Mail contact initiated for store:', store.name);
});
```

## Employee

> **Note:** there is no `carpose-employee-select`. Employee cards are static — only their `tel:` and `mailto:` links are interactive, so those are the only employee events that exist.

### Employee Contact Phone Event <a href="#employee-contact-phone-event" id="employee-contact-phone-event"></a>

Dispatched when a user clicks a `tel:` contact link for an employee.

**Event Name**: `carpose-employee-contact-phone`

**Event Data**:

```json
{
  message: "Employee contact phone",
  type: "employee",
  feature: "contact",
  action: "phone",
  channel: "phone",
  component: "employee-list",
  version: 2,
  data: {
    id: "employee-7",
    firstname: "Max",
    lastname: "Mustermann"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-employee-contact-phone', function(event) {
  const employee = event.detail.data;

  console.log('Phone contact initiated for employee:', employee.firstname, employee.lastname);
});
```

### Employee Contact Mail Event <a href="#employee-contact-mail-event" id="employee-contact-mail-event"></a>

Dispatched when a user clicks a `mailto:` contact link for an employee.

**Event Name**: `carpose-employee-contact-mail`

**Event Data**:

```json
{
  message: "Employee contact mail",
  type: "employee",
  feature: "contact",
  action: "mail",
  channel: "mail",
  component: "employee-list",
  version: 2,
  data: {
    id: "employee-7",
    firstname: "Max",
    lastname: "Mustermann"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-employee-contact-mail', function(event) {
  const employee = event.detail.data;

  console.log('Mail contact initiated for employee:', employee.firstname, employee.lastname);
});
```

## UI

### Floating Button Click Event <a href="#floating-button-click-event" id="floating-button-click-event"></a>

Dispatched when a user clicks a configured floating link button (a generic, non-vehicle/offer/store/employee floating action).

**Event Name**: `carpose-ui-floating-button-click`

**Event Data**:

```json
{
  message: "Floating button click",
  type: "ui",
  action: "floating-button-click",
  component: "floating-buttons",
  version: 2,
  data: {
    label: "Zum Konfigurator",
    url: "https://example.com/configurator"
  }
}
```

**How to Listen**:

```javascript
document.body.addEventListener('carpose-ui-floating-button-click', function(event) {
  const { label, url } = event.detail.data;

  console.log('Floating button clicked:', label, url);
});
```


# Deprecated Events

This page exists purely to help integrators find the replacement for an old event name. **This page documents removal, not deprecation in the usual sense**: as of this release, none of the event names below are emitted by Carposé anymore. There is no fallback, no dual-dispatch period, and no console warning. They are listed here only so a dealer grepping their GTM/GA4 configuration for an old trigger name can find what to change it to.

This page is expected to be removed from the documentation in v3.0, matching the deprecation convention used for the `CAR_*` `ComponentType` aliases (see `ComponentTypes.ts`).

## Two Breaking Changes

If you have a live GTM, GA4, or custom listener wired to any event or field named below, **it will silently stop recording the moment this version deploys** — there is no error, no console warning, nothing to alert you. Both of the following ship together in this release:

1. **Every old event name is gone.** Not renamed-with-an-alias, not deprecated-with-a-warning — simply no longer dispatched. See the mapping table below for the replacement.
2. **`detail.type` changed from `"car"` to `"vehicle"`.** Any logic that branches on `event.detail.type === 'car'` needs to branch on `'vehicle'` instead.

`detail.data` itself keeps its previous shape for equivalent events, so code that reads fields like `event.detail.data.name` continues to work unchanged — it is the event *name* and the envelope's `type` field that changed, not the payload shape.

**Action required:** update every listener and every GTM/GA4 trigger to the new event names before deploying this version, or tracking for that interaction goes dark with no warning.

## Old Name → New Name Mapping

| Old name                                                                             | New name                               | Notes                                                                                                                                                                                |
| ------------------------------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `carpose-car-select`                                                                 | `carpose-vehicle-select`               |                                                                                                                                                                                      |
| `carpose-car-contact-phone`                                                          | `carpose-vehicle-contact-phone`        |                                                                                                                                                                                      |
| `carpose-car-contact-email`                                                          | `carpose-vehicle-contact-mail`         | Also note the `-email` → `-mail` action rename.                                                                                                                                      |
| `carpose-car-contact-whatsapp`                                                       | `carpose-vehicle-contact-whatsapp`     |                                                                                                                                                                                      |
| `carpose-car-contact-modal-open`                                                     | `carpose-vehicle-contact-form-open`    | The contact UI is a form flow now, not a single modal.                                                                                                                               |
| `carpose-car-contact`                                                                | `carpose-vehicle-contact-form-success` | The success point of the contact form flow — see [Vehicle Search](/events/02_vehicle-search#vehicle-contact-form-success-event).                                                     |
| `carpose-offer-select`                                                               | `carpose-offer-select` (unchanged)     | The name is unchanged, but the envelope is now v2 — see [Introduction](/events/01_introduction#the-v2-envelope).                                                                     |
| `carpose-offer-contact`                                                              | `carpose-offer-contact-form-success`   | See [Offer](/events/06_offer#offer-contact-form-success-event).                                                                                                                      |
| `carpose-de-exterior-viewer-loaded`                                                  | `carpose-vehicle-viewer-load`          | Also moves under the `vehicle` entity — see [Vehicle Search](/events/02_vehicle-search#vehicle-viewer-load-event).                                                                   |
| `carpose-de-exterior-no-viewer-available`                                            | `carpose-vehicle-viewer-unavailable`   |                                                                                                                                                                                      |
| `carpose-de-<type>-no-view-available` (e.g. `carpose-de-interior-no-view-available`) | `carpose-vehicle-viewer-unavailable`   | Both of the old per-viewer-type "unavailable" events collapse into one event; use `data.viewer` to tell viewer types apart — see [Vehicle Search](/events/02_vehicle-search#viewer). |

## Internal Command Rename

| Old name                      | New name                               | Notes                                                                                                                                                                                                                           |
| ----------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `carpose-preset-vehicle-type` | `carpose-internal-preset-vehicle-type` | Moved into the reserved `carpose-internal-*` namespace. This was never a public tracking event and is now explicitly marked as such — **do not track it**. See [Introduction](/events/01_introduction#internal-command-events). |

## Events With No Old Equivalent

Everything else in the [Vehicle Search](/events/02_vehicle-search), [Offer](/events/06_offer) and [Store, Employee & UI](/events/07_store-and-employee) catalogues (contact form submit/error states, test-drive events, search events, wishlist, comparison, budget, offer configurator, store/employee contact, and the UI floating-button event) is new in this release and has no old name to migrate from.


# Example: Google Integration

#### Google Analytics 4 <a href="#google-analytics-4" id="google-analytics-4"></a>

```javascript
// Offer events
document.body.addEventListener('carpose-offer-select', function(event) {
  const offerData = event.detail.data;

  gtag('event', 'offer_select', {
    offer_id: offerData.id,
    offer_headline: offerData.headline,
    event_category: 'offer_interaction',
    event_label: offerData.subHeadline
  });
});

document.body.addEventListener('carpose-offer-contact-form-success', function(event) {
  const offerData = event.detail.data;

  gtag('event', 'offer_contact', {
    offer_id: offerData.id,
    offer_headline: offerData.headline,
    event_category: 'lead_generation',
    event_label: 'contact_form'
  });
});

// Vehicle events
document.body.addEventListener('carpose-vehicle-select', function(event) {
  const vehicleData = event.detail.data;

  gtag('event', 'vehicle_selected', {
    vehicle_id: vehicleData.id,
    vehicle_name: vehicleData.name,
    manufacturer: vehicleData.manufacturer,
    model: vehicleData.model,
    offer_number: vehicleData.offerNumber,
    event_category: 'vehicle_interaction'
  });
});

document.body.addEventListener('carpose-vehicle-contact-form-open', function(event) {
  const vehicleData = event.detail.data;

  gtag('event', 'vehicle_contact_form_open', {
    vehicle_id: vehicleData.id,
    vehicle_name: vehicleData.name,
    manufacturer: vehicleData.manufacturer,
    event_category: 'lead_generation',
    event_label: 'form_open'
  });
});

document.body.addEventListener('carpose-vehicle-contact-form-success', function(event) {
  const vehicleData = event.detail.data;

  gtag('event', 'vehicle_contact_submitted', {
    vehicle_id: vehicleData.id,
    vehicle_name: vehicleData.name,
    manufacturer: vehicleData.manufacturer,
    offer_number: vehicleData.offerNumber,
    event_category: 'lead_generation',
    event_label: 'contact_form'
  });
});

// data.origin — not detail.channel — is what tells you a search came from the AI assistant.
document.body.addEventListener('carpose-vehicle-search-submit', function(event) {
  const { filters, resultCount, origin } = event.detail.data;

  gtag('event', 'vehicle_search', {
    result_count: resultCount,
    search_origin: origin,
    event_category: origin === 'ai' ? 'ai_search' : 'form_search'
  });
});
```

### Google Tag Manager (dataLayer) <a href="#google-tag-manager-datalayer" id="google-tag-manager-datalayer"></a>

```javascript
// Offer events
document.body.addEventListener('carpose-offer-select', function(event) {
  const offerData = event.detail.data;

  if (window.dataLayer) {
    window.dataLayer.push({
      event: 'offer_interaction',
      offer_id: offerData.id,
      offer_headline: offerData.headline,
      offer_subheadline: offerData.subHeadline,
      interaction_type: 'selection'
    });
  }
});

document.body.addEventListener('carpose-offer-contact-form-success', function(event) {
  const offerData = event.detail.data;

  if (window.dataLayer) {
    window.dataLayer.push({
      event: 'lead_generation',
      offer_id: offerData.id,
      offer_headline: offerData.headline,
      lead_type: 'contact_request'
    });
  }
});

// Vehicle events
document.body.addEventListener('carpose-vehicle-select', function(event) {
  const vehicleData = event.detail.data;

  if (window.dataLayer) {
    window.dataLayer.push({
      event: 'vehicle_interaction',
      vehicle_id: vehicleData.id,
      vehicle_name: vehicleData.name,
      manufacturer: vehicleData.manufacturer,
      model: vehicleData.model,
      offer_number: vehicleData.offerNumber,
      interaction_type: 'selection'
    });
  }
});

document.body.addEventListener('carpose-vehicle-contact-form-open', function(event) {
  const vehicleData = event.detail.data;

  if (window.dataLayer) {
    window.dataLayer.push({
      event: 'vehicle_contact_form',
      vehicle_id: vehicleData.id,
      vehicle_name: vehicleData.name,
      manufacturer: vehicleData.manufacturer,
      form_action: 'open'
    });
  }
});

document.body.addEventListener('carpose-vehicle-contact-form-success', function(event) {
  const vehicleData = event.detail.data;

  if (window.dataLayer) {
    window.dataLayer.push({
      event: 'lead_generation',
      vehicle_id: vehicleData.id,
      vehicle_name: vehicleData.name,
      manufacturer: vehicleData.manufacturer,
      offer_number: vehicleData.offerNumber,
      lead_type: 'vehicle_contact_form'
    });
  }
});
```

#### One Trigger for the Whole Vehicle-Search Experience <a href="#one-trigger-for-the-whole-vehicle-search-experience" id="one-trigger-for-the-whole-vehicle-search-experience"></a>

Because every event that touches vehicle data — the search results, the wishlist, the comparison tray, the AI assistant, and so on — shares the `carpose-vehicle-` prefix and the same v2 envelope, you don't need a separate GTM trigger per event name. A single "Custom Event" trigger matching the prefix, paired with variables that read `feature`, `action`, `channel` and `data.origin` off the envelope, covers the whole surface:

```javascript
// One trigger for the whole vehicle-search experience; split by detail fields in GTM.
['select', 'contact-phone', 'contact-mail', 'contact-whatsapp', 'wishlist-add', 'comparison-add'].forEach(function (suffix) {
    document.body.addEventListener('carpose-vehicle-' + suffix, function (event) {
        var d = event.detail;
        window.dataLayer && window.dataLayer.push({
            event: 'carpose_' + d.type + '_' + d.action,
            carpose_entity: d.type,
            carpose_feature: d.feature || '',
            carpose_action: d.action,
            carpose_channel: d.channel || '',
            // origin only exists on carpose-vehicle-search-submit; it is what distinguishes
            // an AI-driven search from a form search, since channel is "form" for both.
            carpose_origin: (d.data && d.data.origin) || '',
            carpose_component: d.component,
            carpose_data: d.data,
        });
    });
});
```

In GTM itself, configure a Custom Event trigger with the event name set to match `carpose-vehicle-.*` (regex match), then define Data Layer Variables for `carpose_feature`, `carpose_action`, `carpose_channel`, `carpose_origin` and `carpose_component` to slice reporting without adding a new trigger every time a vehicle event is added to the catalogue.


# Example: Facebook Pixel

```javascript
// Offer events
document.body.addEventListener('carpose-offer-select', function(event) {
  const offerData = event.detail.data;

  if (typeof fbq !== 'undefined') {
    fbq('track', 'ViewContent', {
      content_name: offerData.headline,
      content_category: 'vehicle_offer',
      content_ids: [offerData.id]
    });
  }
});

document.body.addEventListener('carpose-offer-contact-form-success', function(event) {
  const offerData = event.detail.data;

  if (typeof fbq !== 'undefined') {
    fbq('track', 'Lead', {
      content_name: offerData.headline,
      content_category: 'vehicle_inquiry'
    });
  }
});

// Vehicle events
document.body.addEventListener('carpose-vehicle-select', function(event) {
  const vehicleData = event.detail.data;

  if (typeof fbq !== 'undefined') {
    fbq('track', 'ViewContent', {
      content_name: vehicleData.name,
      content_category: 'vehicle_detail',
      content_ids: [vehicleData.id],
      custom_manufacturer: vehicleData.manufacturer,
      custom_model: vehicleData.model
    });
  }
});

document.body.addEventListener('carpose-vehicle-contact-form-open', function(event) {
  const vehicleData = event.detail.data;

  if (typeof fbq !== 'undefined') {
    fbq('track', 'InitiateCheckout', {
      content_name: vehicleData.name,
      content_category: 'vehicle_inquiry',
      content_ids: [vehicleData.id]
    });
  }
});

document.body.addEventListener('carpose-vehicle-contact-form-success', function(event) {
  const vehicleData = event.detail.data;

  if (typeof fbq !== 'undefined') {
    fbq('track', 'Lead', {
      content_name: vehicleData.name,
      content_category: 'vehicle_inquiry',
      custom_manufacturer: vehicleData.manufacturer,
      custom_offer_number: vehicleData.offerNumber
    });
  }
});
```


# Example: Multiple Event Listeners

<pre class="language-javascript"><code class="lang-javascript">// Listen to all carpose events
document.body.addEventListener('carpose-offer-select', handleOfferEvent);
document.body.addEventListener('carpose-offer-contact-form-submit', handleOfferEvent);
document.body.addEventListener('carpose-offer-contact-form-success', handleOfferEvent);

<strong>document.body.addEventListener('carpose-vehicle-select', handleVehicleEvent);
</strong>document.body.addEventListener('carpose-vehicle-contact-form-open', handleVehicleEvent);
document.body.addEventListener('carpose-vehicle-contact-form-success', handleVehicleEvent);

function handleOfferEvent(event) {
  const { message, type, data } = event.detail;
  console.log(`${type} event: ${message}`, data);
  console.log('Offer:', data.headline);
}

function handleVehicleEvent(event) {
  const { message, type, data } = event.detail;
  console.log(`${type} event: ${message}`, data);
  console.log('Vehicle:', data.name, 'by', data.manufacturer);
}
</code></pre>


# Consent

Carposé emits JavaScript events and sends no data to any analytics provider on its own — see [Introduction](/events/01_introduction). It does keep functional data in the browser's `localStorage` and `sessionStorage`, which is unrelated to analytics and described under [Scope](#scope-what-the-gate-covers) below.

Optionally, Carposé can forward those events to Google Tag Manager for you, instead of you writing the listeners yourself. That built-in bridge **only ever forwards an event once the visitor has consented to analytics**.

## Enabling the tracking bridge

The bridge is off by default. There are two ways to switch it on.

### Option 1 — in the Carposé dashboard (recommended)

Enable **Google Analytics Tracking** in your account settings. Every page that embeds Carposé picks it up on the next page load — no change to your website is needed, and you can switch it off again the same way.

### Option 2 — on the integration script

Add the `data-carpose-google-analytics-tracking` attribute to the integration script tag. It overrides the account setting, which is useful to switch tracking on for one site only, or to keep it off on a staging system while it is enabled for the account:

```html
<script
  type="text/javascript"
  src="https://integration.carpose.app/integration.js"
  data-carpose-google-analytics-tracking="true"
  data-api-key="your-api-key"
></script>
```

### How the two combine

| `data-carpose-google-analytics-tracking` | Result                                           |
| ---------------------------------------- | ------------------------------------------------ |
| `"true"`                                 | Bridge active, whatever the account setting says |
| `"false"`                                | Bridge off, whatever the account setting says    |
| present but empty, or absent             | The account setting decides — off by default     |

{% hint style="info" %}
"Active" means the bridge is listening — not that anything is sent. Every single push is additionally gated by consent. Tracking enabled plus no consent still means no tracking.
{% endhint %}

The attribute is namespaced per destination. Further tracking destinations will get their own setting and their own attribute, so they can be switched independently.

## How consent is handled

The bridge **fails closed**. Under GDPR Art. 6(1)(a) and §25 TDDDG analytics requires prior opt-in, so no consent signal is treated as refusal, never as permission.

* **Nothing is forwarded until analytics consent is granted.** The listeners attach immediately — they store nothing — but each forward is checked at the moment the event fires.
* **Events fired before consent are dropped permanently.** They are never buffered and never replayed. A visitor who accepts halfway through a session is measured from that point on, not retroactively.
* **Withdrawal takes effect immediately**, on the next event, without a page reload.
* **The consent check itself writes no cookies and no storage.** It reads the consent state fresh on every page load.
* **There is no bypass.** No attribute, setting or global switches the gate off.

{% hint style="info" %}
This is stricter than delegating to Google Consent Mode alone. With plain delegation the events land in `window.dataLayer` regardless — readable by every other tag on the page — and Consent Mode replays queued tags when consent is granted later, so a "reject, then accept" visitor would still be measured retroactively. The gate prevents both.
{% endhint %}

## Scope: what the gate covers

The consent gate governs **one thing**: whether Carposé events are forwarded to an analytics destination. It does not switch parts of the app off.

Carposé keeps functional data in the browser to deliver the features the visitor is using. This storage is **not** affected by consent state and keeps working whether or not analytics consent was given:

| Stored                                                 | Where            | Purpose                                                         |
| ------------------------------------------------------ | ---------------- | --------------------------------------------------------------- |
| API response cache (5 min TTL)                         | `localStorage`   | Avoids re-fetching vehicle and settings data on every page view |
| Account settings and feature flags                     | `localStorage`   | Configuration of the embedded modules                           |
| Wishlist, comparison, recently viewed vehicles, budget | `localStorage`   | Features the visitor actively uses                              |
| Search form state, AI assistant history                | `localStorage`   | Restores the visitor's own inputs across page views             |
| Per-session UI state                                   | `sessionStorage` | Cleared when the tab closes                                     |

None of this is transmitted to Google or any other analytics provider, and none of it is used for profiling or cross-site recognition — it stays in the visitor's browser and serves the module they are looking at.

{% hint style="info" %}
Whether this functional storage requires consent on your site — under §25 (2) TDDDG, storage strictly necessary for a service the user explicitly requested is exempt — is an assessment for you and your data protection officer, based on which modules you embed. Carposé does not make that determination for you, and the analytics gate described here does not depend on it.
{% endhint %}

## Recognised consent signals

Consent counts as granted when **any** of the following reports it. A source that never reports anything is treated as unknown, which is not a grant.

### 1. Google Consent Mode v2

`analytics_storage` is read from the `consent` `default` / `update` commands in `window.dataLayer`, including commands issued before Carposé loads and any later updates. The most recent command wins.

```javascript
gtag('consent', 'update', { analytics_storage: 'granted' });
```

Supported out of the box by Usercentrics, Cookiebot, Consentmanager, Borlabs and most other commercial CMPs.

### 2. IAB TCF v2.2

Read via `window.__tcfapi`. Consent counts as granted when `gdprApplies` is `false`, or when both purpose **1** (store and access information on a device) and purpose **8** (measure content performance) are consented.

### 3. The Carposé consent API

For consent tools that speak neither of the above. Set the initial state before the Carposé script loads:

```javascript
window.carposeConsent = { analytics: true };
```

And report every change:

```javascript
window.dispatchEvent(new CustomEvent('carpose-consent-update', {
  detail: { analytics: true }   // false on withdrawal
}));
```

The event goes on `window`, not `document.body`, so it can be dispatched before the body exists. Call it from your CMP's own consent callback, both when consent is given and when it is withdrawn.

## CMP compatibility

| Consent tool             | Covered by                                      |
| ------------------------ | ----------------------------------------------- |
| Usercentrics             | Consent Mode                                    |
| Cookiebot                | Consent Mode                                    |
| Consentmanager           | Consent Mode                                    |
| Borlabs Cookie           | Consent Mode                                    |
| Complianz (WordPress)    | Consent Mode **only if configured** — see below |
| Custom / in-house banner | Carposé consent API                             |

### WordPress and Complianz

WordPress consent plugins commonly implement the **WP Consent API** (`wp_has_consent('statistics')`), which Carposé does **not** read.

This matters for Complianz, the most widespread WordPress CMP: its free version implements the WP Consent API natively, but places Google Consent Mode setup with Tag Manager behind Complianz Premium, and TCF behind a further paid add-on. A site on free Complianz may therefore emit none of the three recognised signals — in which case the gate stays shut and no analytics data is collected at all.

Such sites need the Carposé consent API wired into the plugin's consent callback:

```javascript
document.addEventListener('wp_listen_for_consent_change', function (e) {
  const changed = e.detail;
  if ('statistics' in changed) {
    window.dispatchEvent(new CustomEvent('carpose-consent-update', {
      detail: { analytics: changed.statistics === 'allow' }
    }));
  }
});
```

## Troubleshooting: no data in Google Analytics

Work through this in order:

1. **Is tracking enabled at all?** Check the **Google Analytics Tracking** setting in the dashboard, and make sure no `data-carpose-google-analytics-tracking="false"` on the script tag is overriding it.
2. **Did you consent yourself while testing?** Accept analytics in the banner, then reload. Events fired before you accepted are gone — they are not replayed.
3. **Does your CMP emit a recognised signal?** In the browser console, `window.dataLayer.filter(e => e[0] === 'consent')` should show a `consent` command containing `analytics_storage`. If it is empty and you are on WordPress, this is almost certainly the Complianz case above — wire the Carposé consent API.
4. **Is GTM itself configured?** The bridge fills `window.dataLayer`. Turning those pushes into GA4 hits is still your GTM container's job.

## Migrating from a custom tracking script

If you previously forwarded Carposé events with your own listeners or a hosted bridge script:

* **Remove the old script when you switch the toggle on.** Both will run, and every event will be counted twice.
* **Check that the old script was consent-gated.** Hand-written bridges typically push unconditionally and rely on GTM. The built-in bridge does not, which is the reason to switch.
* **Expect the numbers to drop.** After the switch you measure only consenting visitors. That is the intended outcome, not a regression, so compare periods with this in mind.


# Shadow DOM Architecture

{% hint style="info" %}
All Carposé components render inside a Shadow DOM for complete style isolation from the host page.
{% endhint %}

## Overview

Every Carposé component is rendered within a Shadow DOM. This guarantees complete style isolation from the host page: component styles never leak out, and the host site's CSS never affects the components.

## Why Shadow DOM?

* **Style isolation** — component styles don't leak to the host page, and host styles don't affect components
* **Encapsulation** — components are self-contained units
* **Consistency** — components render the same way across different websites
* **No CSS conflicts** — avoids class-name collisions with the host page

## How It Works

When a component is initialised, a shadow root is attached to the host element, the component's styles are injected into that shadow root, and the component is rendered inside it. Each component instance has its own style scope.

## Architecture Diagram

```
Host Page
└── <div data-carpose-component="…">
    └── #shadow-root
        ├── <style> (injected styles)
        └── <div> (React app root)
            └── React component tree
```

## Notes

* All components use Shadow DOM by default — no configuration is required
* Style isolation is automatic


# URL Structure

{% hint style="info" %}
The vehicle search writes the open vehicle and the current result page into the page URL, so every state is linkable, shareable and crawlable. Two URL forms are available; the query form is the default and works on every platform.
{% endhint %}

## Overview

The search keeps two pieces of state in the URL:

* **the open vehicle** — which detail view is shown
* **the result page** — which page of the listing is shown

Both are written with `pushState`, so browser back and forward move through them, and both are read back on a fresh page load. A crawler, a shared link and a bookmark therefore all open exactly what the sender saw.

## The two URL forms

|                   | Query form (default)      | Path form                    |
| ----------------- | ------------------------- | ---------------------------- |
| Vehicle           | `?vehicle={slug}--{uuid}` | `/fahrzeug/{slug}--{uuid}/`  |
| Result page       | `?cp-page=2`              | `/seite/2/`                  |
| Host requirements | none                      | sub-path routing (see below) |

Full examples on a search page at `https://your-site.example/fahrzeugsuche/`:

```
Query form
https://your-site.example/fahrzeugsuche/?vehicle=audi-q3-s-line--efbb7d07-0101-11f0-8de6-960000658564
https://your-site.example/fahrzeugsuche/?cp-page=2

Path form
https://your-site.example/fahrzeugsuche/fahrzeug/audi-q3-s-line--efbb7d07-0101-11f0-8de6-960000658564/
https://your-site.example/fahrzeugsuche/seite/2/
```

The vehicle value follows the `{slug}--{uuid}` pattern in both forms — a human-readable slug, two dashes, then the vehicle UUID. See [Addressing a vehicle](#addressing-a-vehicle) for the shorter slug-only form. Other query parameters already on the URL (such as `utm_*` tracking) and the hash are preserved.

Page 1 never carries a marker, so the first page keeps exactly one address. A URL carries at most one marker: opening a vehicle from page 2 leads to the vehicle, and a page link shown on a detail view leads back to the list.

{% hint style="success" %}
**Links written before a switch keep working.** Both forms are always *read*, whichever one is configured. Only newly written links follow the configured form, so URLs indexed under `?vehicle=` continue to open the right vehicle after a switch to the path form.
{% endhint %}

## Choosing the form

The form is an account setting, because three places have to agree on it: the search widget, the server-rendered SEO fallback of your CMS plugin, and the sitemap Carposé generates for your inventory. Set it on your Carposé account and all three follow.

The path form is strictly opt-in. An account without the setting, and an account where it is cleared again, both use the query form — so withdrawing the switch immediately returns every newly written link to the form that works without any routing on your side.

For an individual embed, the `data-url-mode` attribute overrides the account setting:

```html
<div
  data-carpose-component="vehicle-search-form"
  data-api-key="your-api-key"
  data-url-mode="path"
></div>
```

| Attribute            | Type    | Default | Description                                                                                                                                                                         |
| -------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-url-mode`      | string  | `query` | URL form of the listing. Only an explicit `path` switches it; every other value — including an unset or cleared account setting — is the query form. Overrides the account setting. |
| `data-base-path`     | string  | derived | Base path the listing URLs are built on. Only needed for an embed whose links have to lead to a *different* search page than the one it sits on.                                    |
| `data-url-slug-only` | boolean | `false` | Address vehicles by their slug alone instead of `{slug}--{uuid}`. Overrides the account setting.                                                                                    |

### `data-base-path`

Normally you do not set this. The marker segments (`fahrzeug`, `seite`) make the base path self-describing: everything in front of the marker is the base, so the widget derives it from the URL it was opened with — the value that cannot disagree with where the widget actually sits.

Name a base path only when a second search is embedded on another page and its links should lead to your main search page instead:

```html
<div
  data-carpose-component="vehicle-search-form"
  data-api-key="your-api-key"
  data-url-mode="path"
  data-base-path="/fahrzeugsuche/"
></div>
```

## Addressing a vehicle <a href="#addressing-a-vehicle" id="addressing-a-vehicle"></a>

By default a vehicle is addressed as `{slug}--{uuid}`: a readable slug, two dashes, then the vehicle UUID. The UUID is what the vehicle is actually looked up by, and it never changes. The slug is derived from the vehicle title and carries no meaning for the lookup — it exists for readability and for search engines.

That split is deliberate. A vehicle title can change after an import, and with it the slug; because the UUID does the resolving, a link written under the old title keeps opening the right vehicle.

With `data-url-slug-only` — or the matching account setting — the UUID is left out and the slug becomes the address:

```
Default     /fahrzeugsuche/?vehicle=audi-q3-s-line--efbb7d07-0101-11f0-8de6-960000658564
Slug only   /fahrzeugsuche/?vehicle=audi-q3-s-line
```

**The two switches are independent.** Slug-only changes *what* addresses the vehicle, the URL form changes *where* it sits — either can be used without the other. Slug-only needs nothing from your website, so it works on the query form as shown above. Combining both gives the shortest URL available:

```
/fahrzeugsuche/fahrzeug/audi-q3-s-line/
```

{% hint style="warning" %}
**Only switch this on if former slugs stay resolvable.** Without the UUID the slug is the sole identifier, so a vehicle whose title changes gets a new address and the old one has to keep resolving on the server. Where that is not guaranteed, keep the default form — it is immune to title changes by construction.
{% endhint %}

Reading is unaffected by the setting: a `{slug}--{uuid}` link written before the switch keeps resolving through its UUID. Only newly written links use the short form. A value that is neither a UUID nor a valid slug is discarded and removed from the URL rather than being looked up.

## Host requirements for the path form

The query form needs nothing from your website. The path form does: your site has to answer `/fahrzeugsuche/fahrzeug/…/` and `/fahrzeugsuche/seite/2/` with the same page that holds the search widget. Without that routing every deep link is an ordinary 404 — which is why the query form stays the default.

### WordPress

Register the two segments as page endpoints and flush the rewrite rules once, on plugin activation and whenever the setting changes:

```php
add_action('init', function () {
    add_rewrite_endpoint('fahrzeug', EP_PAGES);
    add_rewrite_endpoint('seite', EP_PAGES);
});
```

{% hint style="warning" %}
`flush_rewrite_rules()` has to run once after registering the endpoints. Without it WordPress keeps answering 404 until someone re-saves the permalink settings — a failure that looks like a broken plugin.

If your site uses a German permalink setup that moves WordPress' own `pagination_base` to `seite`, the two collide. Keep the query form on those sites.
{% endhint %}

### Other platforms

Any host that can route sub-paths back to a page works the same way — a rewrite rule, a catch-all route, or a wildcard page. Website builders that do not allow arbitrary sub-paths (Wix among them) cannot serve the path form; use the query form there, which is fully indexable on its own.

## Switching an existing search over

Both switches change the addresses in your sitemap, so both follow the same order — prepare the target addresses first, flip the setting last.

**To the path form:**

1. Make the sub-path routing live on your website and confirm a deep link returns HTTP 200.
2. Add a permanent redirect (301) from the query form to the path form, so already-indexed URLs keep their history.
3. Flip the account setting. Widget, server-rendered fallback and sitemap change together.

**To slug-only:** no routing is involved, but the addresses still change. Confirm that a slug-only URL resolves, add the 301 from `{slug}--{uuid}` to the slug, then flip the setting.

Flipping the setting first leaves your sitemap listing URLs that still answer 404.

## Structured data

Whichever form is active, the `url` in the injected [schema.org](https://schema.org) JSON-LD matches the links on the page — the listing emits an `ItemList` of `Car` entries, and a detail view emits the vehicle itself. No configuration is required.


# Inventory Fragment

{% hint style="info" %}
A dealer's inventory as ready-made HTML instead of a JavaScript widget: a run of `<article>` elements your CMS plugin or template drops straight into a page of its own. Public — no API key, no authentication.
{% endhint %}

## When to use it

The [vehicle search modules](/vehicle-search-modules/01_vehicle-search-form) render in the browser and are the right choice for almost every site. Reach for the fragment when the markup has to exist in the server response: a CMS plugin producing crawlable listing pages, a static build, or a template that has to work without JavaScript.

It carries the same data as the widget, in HTML you own and style yourself.

## The endpoint

```
GET /inventory/{companySlug}/fragment?mode=index&limit=50&offset=0
GET /inventory/{companySlug}/fragment?mode=detail&vehicle={slug}
```

`{companySlug}` is the dealer's Carposé slug — the same one the vehicle sitemap is published under.

### Response

```
Content-Type:     text/html; charset=UTF-8
Cache-Control:    public, max-age=3600
X-Carpose-Total:  1109
X-Robots-Tag:     noindex
```

The body is a run of `<article>` elements and nothing around them — no doctype, no `<html>`, `<head>`, `<body>` or `<footer>`, no `<style>` block, no `class` attributes, and no template indentation. Drop it into your own container and style it with your own selectors.

`X-Carpose-Total` is the dealer's whole active inventory, not the number of articles in this response. Page count, the boundary where your listing should answer 404, and a meta description that names a number all come from this header rather than from counting rows.

{% hint style="info" %}
`X-Robots-Tag: noindex` applies to the fragment URL itself, never to your page. It stops the raw endpoint from being indexed instead of your listing; the markup you render is yours and is indexed normally.
{% endhint %}

### Query parameters

| Parameter | Type                | Default | Description                                                                |
| --------- | ------------------- | ------- | -------------------------------------------------------------------------- |
| `mode`    | `index` \| `detail` | `index` | A page of the inventory, or one vehicle in full. Any other value is a 400. |
| `limit`   | integer             | `50`    | Articles per response, clamped to 1…500.                                   |
| `offset`  | integer             | `0`     | How many articles to skip. Negative values are read as `0`.                |
| `vehicle` | string              | —       | Required for `mode=detail`; missing is a 400, unknown is a 404.            |

`limit` and `offset` page over the **whole** inventory, in one order spanning every location. You never have to map a page number onto a location.

### What the two modes carry

|                       | `mode=index`        | `mode=detail`       |
| --------------------- | ------------------- | ------------------- |
| Images                | the lead image only | the whole gallery   |
| Equipment description | omitted             | included, sanitized |
| JSON-LD `image`       | the lead image only | every image         |

Images arrive in gallery order — `_1` before `_10`, numerically.

### Addressing a vehicle

`vehicle` accepts every form a deep link can carry, so you can hand back whatever your listing already holds:

```
?vehicle=audi-q3-s-line--efbb7d07-0101-11f0-8de6-960000658564   the canonical token
?vehicle=efbb7d07-0101-11f0-8de6-960000658564                   the UUID alone
?vehicle=audi-q3-s-line                                         the slug alone
```

All three resolve to the same article. The token is also the article's `id`, so a listing can hand its own anchor straight back as the detail request. See [URL Structure](/misc-modules/08_url-structure) for how the token is formed and when an account is set to slug-only addressing.

## Links inside the fragment

Every article already points where it should. The `<a>` in the article and the `url` in the JSON-LD both carry the dealer's own vehicle URL, built from their account settings — URL form, slug-only, base page. There is nothing to rewrite on your side.

{% hint style="warning" %}
**The account needs a vehicle-search page URL.** Without it Carposé does not know where the dealer's vehicles live, so articles come back with no link and the JSON-LD omits `url` rather than pointing somewhere wrong. Set the search page URL on the account before you integrate.
{% endhint %}

## Elements you will see

`article` `h2` `h3` `p` `div` `span` `a` `img` `dl` `dt` `dd` `ul` `ol` `li` `strong` `em` `br`, plus one `script[type="application/ld+json"]` per article.

Shape of one article:

```html
<article id="{slug}--{uuid}">
  <h2>Vehicle title</h2>
  <div><a href="https://dealer.example/fahrzeugsuche/?vehicle=…">Zum Fahrzeug</a></div>
  <div><img src="…" alt="Vehicle title" loading="lazy"></div>
  <dl><dt>Erstzulassung</dt><dd>2020-02-01</dd>…</dl>
  <div><!-- equipment description, mode=detail only --></div>
  <script type="application/ld+json">{"@context":"https://schema.org","@type":"Car",…}</script>
</article>
```

{% hint style="warning" %}
**Sanitize it anyway.** The equipment description is dealer-authored text imported from mobile.de, and Carposé already restricts it to the elements listed above. Running your own allowlist over the response before you print it is still the right call — it is the only check that survives a change on our side, and it costs a single pass.
{% endhint %}

## Caching

Responses are cached for an hour and rebuilt the moment the dealer's inventory or link settings change, so a fresh import shows up without waiting out the TTL. `Cache-Control: public, max-age=3600` is safe to honour in your own cache or CDN.

## Errors

| Status | Cause                                                                        |
| ------ | ---------------------------------------------------------------------------- |
| 400    | `mode` is neither `index` nor `detail`, or `mode=detail` without `vehicle`   |
| 404    | unknown company slug, or `vehicle` names a vehicle this dealer does not have |

An offset past the end of the inventory is not an error — it answers 200 with an empty body. Compare `offset` against `X-Carpose-Total` to decide where your own listing stops.


# Static Form Values

{% hint style="info" %}
The standardised, predefined values used by Carposé search forms (vehicle types, fuel types, transmission).
{% endhint %}

## Overview

Carposé search forms use a set of predefined values for filter options. These values are standardised across the platform to ensure consistency; the displayed labels may be localised based on the configured language.

## Vehicle Types

| Value        | Label       |
| ------------ | ----------- |
| `car`        | Car / PKW   |
| `motorcycle` | Motorcycle  |
| `truck`      | Truck / LKW |
| `trailer`    | Trailer     |
| `bus`        | Bus         |
| `caravan`    | Caravan     |

## Fuel Types

| Value      | Label           |
| ---------- | --------------- |
| `petrol`   | Petrol / Benzin |
| `diesel`   | Diesel          |
| `electric` | Electric        |
| `hybrid`   | Hybrid          |
| `lpg`      | LPG             |
| `cng`      | CNG             |
| `hydrogen` | Hydrogen        |

## Transmission Types

| Value            | Label          |
| ---------------- | -------------- |
| `manual`         | Manual         |
| `automatic`      | Automatic      |
| `semi-automatic` | Semi-Automatic |

## Notes

* These values are consistent across all Carposé components
* Custom values are not supported for these fields


# Store

### Overview <a href="#overview" id="overview"></a>

The `Store` component displays the detail view of a single store/location from the Carpose backend. It shows the store image with manufacturer logos, address, contact details, an optional excerpt, and action links for the route planner and an optional forward URL.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Store component in a standard HTML page:

```html
<div
  data-carpose-component="store"
  data-api-key="your-api-key"
  data-id="your-store-id"
  data-forward-url="/your-store-page"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                                  |
| ------------------------ | ------ | ------- | -------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "store"                                        |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching.    |
| `data-id`                | string | -       | ID of the specific store to display. Required.                                               |
| `data-forward-url`       | string | -       | Optional URL used by the "zum Standort" action link. When omitted, the link is not rendered. |

### Features <a href="#features" id="features"></a>

* Displays the store image with overlaid manufacturer logos
* Shows company name, address, phone, and e-mail with click-to-call and click-to-mail links
* Optional rich-text excerpt rendered from HTML
* Route planner link generated from the store address (opens Google Maps in a new tab)
* Optional forward link to a dedicated store page

### Example <a href="#example" id="example"></a>

```html
<div
  data-carpose-component="store"
  data-api-key="your-api-key"
  data-id="2368b317-00ee-11f0-8de6-960000658564"
  data-forward-url="/standorte/musterstadt"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches the store data from the API using the provided store ID and API key.
   * Nothing is rendered until the store data has loaded.
2. **Manufacturer Bar**:
   * When the store has associated manufacturers, their logos are rendered as an overlay on the store image.
   * When there are no manufacturers, the bar is hidden.
3. **Action Links**:
   * The route planner link is rendered when the store has a street, zip, and town, and opens Google Maps in a new tab.
   * The "zum Standort" link is rendered only when `data-forward-url` is set.


# Store List

### Overview <a href="#overview" id="overview"></a>

The `Store List` component displays a list of all stores/locations from the Carpose backend. It shows a loading indicator while fetching data and then renders each store using the Store Item component.

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation.

### Usage <a href="#usage" id="usage"></a>

To use the Store List component in a standard HTML page:

```html
<div 
  data-carpose-component="store-list"
  data-api-key="your-api-key"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

The component reads configuration from data attributes on the provided element:

| Attribute                | Type   | Default | Description                                                                               |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "store-list"                                |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching. |

### Features <a href="#features" id="features"></a>

* Displays a list of all store locations
* Shows a loading indicator while data is being fetched
* Responsive layout that adapts to different screen sizes
* Each store is displayed with its details (name, address, contact information)

### Example <a href="#example" id="example"></a>

```html
<div 
  data-carpose-component="store-list"
  data-api-key="your-api-key"
></div>
```

### Behavior <a href="#behavior" id="behavior"></a>

1. **Data Loading**:
   * On initialization, the component fetches store data from the API using the provided API key.
   * While data is loading, a loading screen is displayed.
   * Once data is loaded, the list of stores is rendered.
2. **Store Display**:
   * Each store is displayed using the Store Item component.
   * Stores are displayed in the order returned by the API.
   * Each store item typically includes the store name, address, contact information, and possibly an image.


# Employee List

### Overview

The `EmployeeList` component displays a list of employees with optional grouping by teams. It provides a tabbed interface when multiple teams are present, making it easy to navigate between different team members.

### Installation

The component is part of the Carpose application and requires no separate installation.

### Usage

To use the EmployeeList component in a standard HTML page:

```html
<div 
  data-carpose-component="employee-list"
  data-api-key="your-api-key"
  data-config-show-team-headline="true"
></div>
```

### Configuration Attributes

The component reads configuration from data attributes on the provided element:

| Attribute                        | Type    | Default | Description                                                                                                      |
| -------------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `data-carpose-component`         | string  | -       | Identifies the component type, must be set to "employee-list"                                                    |
| `data-api-key`                   | string  | -       | API key for authentication with the Carpose backend services. Required for data fetching.                        |
| `data-filter-team`               | string  | null    | Optional filter to show employees from a specific team only. Can be a team name or UUID.                         |
| `data-filter-store`              | string  | null    | Optional filter to show employees from a specific store only. Can be a store name or UUID.                       |
| `data-config-show-team-headline` | boolean | true    | Whether team names are shown as headlines above the employee list. On by default; set to `"false"` to hide them. |

### Features

* Display employees with their details in a responsive grid layout
* Group employees by team with a tabbed interface
* Filter employees by team or store
* Sort employees based on their order property
* Organize teams based on their order property

### Examples

#### Basic Example - All Employees

```html
<div 
  data-carpose-component="employee-list"
  data-api-key="your-api-key"
></div>
```

#### Filtered by Team

```html
<div 
  data-carpose-component="employee-list"
  data-api-key="your-api-key"
  data-filter-team="Sales Team"
  data-config-show-team-headline="true"
></div>
```

#### Filtered by Store

```html
<div 
  data-carpose-component="employee-list"
  data-api-key="your-api-key"
  data-filter-store="Main Store"
></div>
```

#### Filtered by Team and Store IDs

```html
<div 
  data-carpose-component="employee-list"
  data-api-key="your-api-key"
  data-filter-team="550e8400-e29b-41d4-a716-446655440000"
  data-filter-store="550e8400-e29b-41d4-a716-446655440001"
></div>
```

### Behavior

1. **Data Loading**:
   * On initialization, the component fetches employee data from the API using the provided API key.
   * If filter attributes are set, the data is filtered accordingly.
2. **Team Grouping**:
   * Without filters, employees are grouped by their teams and displayed in tabs.
   * Teams are sorted based on their order property.
   * Within each team, employees are sorted based on their individual order property.
3. **Filtering**:
   * When `data-filter-team` is provided, only employees from that team are displayed.
   * When `data-filter-store` is provided, only employees from that store are displayed.
   * Filters can be applied using either names or UUIDs.
4. **Team Headlines**:
   * When `data-config-show-team-headline` is true, team names are displayed as headlines above the employee lists.
   * When employees are filtered by team, the team name is displayed as a headline (if `data-config-show-team-headline` is true).


# Employee Single

{% hint style="info" %}
The Employee Single component displays the detailed profile of one employee.
{% endhint %}

## Introduction

The `employee-single` component shows a detailed view of a single employee, including contact information, role, and associated store. It is useful for team-member profile pages or contact pages.

## Basic Usage

```html
<div
  data-carpose-component="employee-single"
  data-api-key="your-api-key"
  data-id="550e8400-e29b-41d4-a716-446655440000"
></div>
```

## Attributes

| Attribute                        | Required | Description                                                   |
| -------------------------------- | -------- | ------------------------------------------------------------- |
| `data-carpose-component`         | Yes      | Must be `employee-single`                                     |
| `data-api-key`                   | Yes      | Your Carposé API key                                          |
| `data-id`                        | Yes      | The UUID of the employee to display                           |
| `data-config-show-team-headline` | No       | Show the employee's team name as a headline (default `false`) |

## Behavior

* Fetches and displays the specified employee's details
* Contact information includes email and phone where available
* The associated store is shown with a link to the store page

## Notes

* Requires a valid employee UUID; shows an error if not found
* See also [Employee List](/misc-modules/05_employee-list) for a list of employees


# Employee Selection

{% hint style="info" %}
The Employee Selection component displays a fixed, hand-picked set of employees in the order you list them.
{% endhint %}

## Introduction

The `employee-selection` component renders a specific selection of employees — chosen by UUID — in a simple two-column card grid. Unlike the [Employee List](/misc-modules/05_employee-list), it has no tabs, headlines, filters, or team grouping: it shows exactly the employees you list, in the order given. This is useful for "your contacts" sections or curated team strips.

## Basic Usage

```html
<div
  data-carpose-component="employee-selection"
  data-api-key="your-api-key"
  data-ids="550e8400-e29b-41d4-a716-446655440000,7c9e6679-7425-40de-944b-e07fc1f90ae7"
></div>
```

## Attributes

| Attribute                | Required | Description                                                                        |
| ------------------------ | -------- | ---------------------------------------------------------------------------------- |
| `data-carpose-component` | Yes      | Must be `employee-selection`                                                       |
| `data-api-key`           | Yes      | Your Carposé API key                                                               |
| `data-ids`               | Yes      | Comma-separated list of employee UUIDs to display, in the order they should appear |

## Behavior

* Fetches and displays only the listed employees, in the given order
* Renders a two-column card grid with no tabs, headlines, filters, or grouping
* UUIDs that do not match an employee are skipped

## Notes

* For a full, team-grouped list of employees, use [Employee List](/misc-modules/05_employee-list)
* For a single employee profile, use [Employee Single](/misc-modules/06_employee-single)


# Store Map

### Overview <a href="#overview" id="overview"></a>

The `Store Map` component shows every store/location of the account as a marker on a Google Map. Clicking a marker opens an info window with the store's name, address, phone, e-mail and a route-planner link. It complements the [Store List](/misc-modules/04_store-list) (all stores as a list) and the [Store](/misc-modules/03_store-item) item (one store on its own page).

### Installation <a href="#installation" id="installation"></a>

The component is part of the Carpose application and requires no separate installation. It does, however, load the Google Maps JavaScript API and therefore needs a Google Maps API key of your own.

### Usage <a href="#usage" id="usage"></a>

```html
<div
  data-carpose-component="store-map"
  data-api-key="your-api-key"
  data-google-api-key="your-google-maps-api-key"
></div>
```

### Configuration Attributes <a href="#configuration-attributes" id="configuration-attributes"></a>

| Attribute                | Type   | Default | Description                                                                                                 |
| ------------------------ | ------ | ------- | ----------------------------------------------------------------------------------------------------------- |
| `data-carpose-component` | string | -       | Identifies the component type, must be set to "store-map"                                                   |
| `data-api-key`           | string | -       | API key for authentication with the Carpose backend services. Required for data fetching.                   |
| `data-google-api-key`    | string | -       | Your Google Maps JavaScript API key. Required — without it Google renders an error tile instead of the map. |

### Google Maps API key <a href="#google-maps-api-key" id="google-maps-api-key"></a>

Create the key in the Google Cloud Console with the **Maps JavaScript API** enabled and restrict it to your website's domains (HTTP referrer restriction). A key restricted to other referrers fails with `RefererNotAllowedMapError` in the browser console and shows "This page didn't load Google Maps correctly" in place of the map.

### Consent gate <a href="#consent-gate" id="consent-gate"></a>

Google Maps transfers data to Google and may set cookies, so the component does not load it on its own. It first renders a placeholder with a "Google Maps laden" button, the provider name (Google Ireland Limited), a short notice and a link to Google's privacy policy. Only after the visitor clicks the button are the stores fetched and the map loaded.

The decision is kept for the browser session (`sessionStorage`), so the visitor is not asked again on the next page with a map; it is not persisted beyond the session and no cookie is written by Carposé.

### Features <a href="#features" id="features"></a>

* One marker per store with coordinates; stores without latitude/longitude are skipped
* Map centre and zoom are calculated from the marker bounds, so all stores are visible on load
* Info window with name, address, phone (click-to-call), e-mail (click-to-mail) and a route-planner link that opens Google Maps in a new tab
* Muted, grey-scale map style so the markers stand out
* Emits `carpose-store-select` when a marker is clicked, see [Store and Employee events](/events/07_store-and-employee)

### Behavior <a href="#behavior" id="behavior"></a>

1. **Consent**: the placeholder is shown until the visitor accepts, or immediately skipped when consent was already given in this session.
2. **Data Loading**: after consent the component fetches all stores of the account and computes the bounds.
3. **Interaction**: clicking a marker opens its info window and closes any other; the close button dismisses it.

### Notes <a href="#notes" id="notes"></a>

* Store coordinates are maintained in the Carposé administration under Stores; a store without coordinates does not appear on the map but still appears in the list.
* Live demo: [carpose.de/demo/standortkarte](https://carpose.de/demo/standortkarte)


# Getting Started

Carpose.de offers a straightforward integration using React. To utilize the provided components, simply add the following script to dynamically load our library.

{% code lineNumbers="true" fullWidth="false" %}

```javascript
<script type="text/javascript" src="https://integration.carpose.app/integration.js"></script>
```

{% endcode %}

To incorporate the respective components, simply insert the provided HTML snippets into your webpage. These snippets will load the necessary resources and initialize the desired Carposé components seamlessly into your project.


# Demo

Explore the full range of our components with our interactive demo. See how each element functions and interacts in real-time to help you envision seamless integration into your project.

-> <https://carpose.de/demo>


# Getting Started

### Overview <a href="#overview" id="overview"></a>

The Carposé MCP (Model Context Protocol) server provides AI assistants with access to your dealership's vehicle inventory, employee data, and store information.

**Key Features:**

* ✅ **Universal HTTP Access** - Works with any MCP-compatible client
* ✅ **No Installation Required** - Serverless deployment via Cloudflare Workers
* ✅ **Secure Authentication** - API key-based access control
* ✅ **Real-time Data** - Direct connection to your Carposé database
* ✅ **Rich Querying** - Advanced filtering, pagination, and sorting
* ✅ **Multi-tenant Support** - Each API key scopes data to your organization

### Example Usage <a href="#example-usage" id="example-usage"></a>

Here are some example queries you can ask your AI assistant:

#### Employee Queries <a href="#employee-queries" id="employee-queries"></a>

* "Show me all employees in the sales team"
* "Find the contact information for managers (Geschäftsführer)"
* "List employees at the Munich store"

#### Vehicle Queries <a href="#vehicle-queries" id="vehicle-queries"></a>

* "Show me all electric vehicles under €50,000"
* "List BMW vehicles with less than 50,000km mileage"
* "Find all vehicles built after 2020, sorted by price"
* "Show me vehicles at the Berlin store with automatic transmission"

#### Inventory Queries <a href="#inventory-queries" id="inventory-queries"></a>

* "What manufacturers do we currently have in stock?"
* "List all available models from Mercedes-Benz"
* "Which fuel types are available in our inventory?"

### Tips for Best Results <a href="#tips-for-best-results" id="tips-for-best-results"></a>

#### Use Pagination to Avoid Large Responses <a href="#use-pagination-to-avoid-large-responses" id="use-pagination-to-avoid-large-responses"></a>

When listing vehicles, always use pagination to avoid overwhelming responses:

> "List the first 10 vehicles, sorted by price"

Your AI assistant will automatically use `itemsPerPage: 10` to keep responses manageable.

#### Combine Filters for Precise Results <a href="#combine-filters-for-precise-results" id="combine-filters-for-precise-results"></a>

You can combine multiple filters:

> "Show me diesel vehicles from BMW or Audi, built between 2020 and 2024, priced under €40,000, with less than 80,000km, sorted by mileage ascending"

#### Navigate Results with Hydra Pagination <a href="#navigate-results-with-hydra-pagination" id="navigate-results-with-hydra-pagination"></a>

The server returns responses in Hydra JSON-LD format with pagination links. Your AI assistant can automatically:

* See total item counts
* Navigate to next/previous pages
* Jump to first/last pages

Just ask naturally:

> "Show me the next page of results"

### Troubleshooting <a href="#troubleshooting" id="troubleshooting"></a>

#### AI assistant doesn't recognize the Carposé tools <a href="#ai-assistant-doesnt-recognize-the-carpose-tools" id="ai-assistant-doesnt-recognize-the-carpose-tools"></a>

**Problem**: Your AI assistant responds "I don't have access to Carposé tools"

**Solutions**:

1. Verify the configuration file/settings are correct for your client
2. Check that the JSON syntax is valid (use a JSON validator if using config files)
3. Ensure your client was restarted after editing the configuration
4. Check that your API key is correct (no extra spaces or quotes)
5. Test the connection by calling the health endpoint: `curl https://mcp.carpose.de/health`

#### "Authentication required" error <a href="#authentication-required-error" id="authentication-required-error"></a>

**Problem**: Tools return "X-CARPOSE-API-KEY header is required"

**Solutions**:

1. Verify your API key is correctly set in the `headers` section
2. Make sure the key is wrapped in quotes: `"X-CARPOSE-API-KEY": "your_key"`
3. Contact your administrator to verify the API key is still valid

### Configuration Examples <a href="#configuration-examples" id="configuration-examples"></a>

#### Basic Configuration (Claude Desktop - Single Tenant) <a href="#basic-configuration-claude-desktop---single-tenant" id="basic-configuration-claude-desktop---single-tenant"></a>

```json
{
  "mcpServers": {
    "carpose": {
      "url": "https://mcp.carpose.de",
      "transport": {
        "type": "http"
      },
      "headers": {
        "X-CARPOSE-API-KEY": "abc123xyz456"
      }
    }
  }
}
```

#### Multiple Environments (Development & Production) <a href="#multiple-environments-development--production" id="multiple-environments-development--production"></a>

```json
{
  "mcpServers": {
    "carpose-prod": {
      "url": "https://mcp.carpose.de",
      "transport": {
        "type": "http"
      },
      "headers": {
        "X-CARPOSE-API-KEY": "prod_api_key_here"
      }
    },
    "carpose-dev": {
      "url": "https://mcp-dev.carpose.workers.dev",
      "transport": {
        "type": "http"
      },
      "headers": {
        "X-CARPOSE-API-KEY": "dev_api_key_here"
      }
    }
  }
}
```

This allows you to switch between production and development environments.

#### Environment Variables (For Custom Integrations) <a href="#environment-variables-for-custom-integrations" id="environment-variables-for-custom-integrations"></a>

If you're building a custom integration, you can store the API key in environment variables:

```bash
# .env file
CARPOSE_MCP_URL=https://mcp.carpose.de
CARPOSE_API_KEY=your_api_key_here
```

Then reference them in your code:

```javascript
const transport = new HttpClientTransport({
  url: process.env.CARPOSE_MCP_URL,
  headers: {
    'X-CARPOSE-API-KEY': process.env.CARPOSE_API_KEY
  }
});
```


# Getting Started

{% hint style="info" %}
This page is for embedding the Carposé vehicle search on a **Wix** site. For plain HTML and other site builders, use the standard [React integration](/react-integration/01_getting-started).
{% endhint %}

## What you can embed

On Wix, the Carposé **vehicle search** is available as a widget. The other Carposé components remain available through the standard `data-carpose-component` embed on non-Wix pages.

## Install from the Wix App Market (recommended)

The easiest way to add Carposé to a Wix site is to install the app from the Wix App Market:

{% hint style="success" %}
**Install Carposé:** <https://www.wix.com/app-market/install/ccc0bb90-c381-4a93-92c6-9bab00f36945>
{% endhint %}

1. Open the [Carposé app in the Wix App Market](https://www.wix.com/app-market/install/ccc0bb90-c381-4a93-92c6-9bab00f36945) and add it to your site.
2. In the Wix Editor, add the **Carposé vehicle search** widget to the page where you want it.
3. Open the widget's **settings panel** and enter your Carposé **API key**, plus any options you want to enable.

## Configuration

All options are set through the widget's settings panel — you don't edit HTML directly. At a minimum, set your **API key**. The panel also exposes the same feature toggles as the standard search form (wishlist, comparison, history, AI assistant, financing rates, contextual counts, a forwarding URL for results, theme colors, and so on). Leaving a toggle unset falls back to your Carposé account default.

See the [Vehicle Search Form](/vehicle-search-modules/01_vehicle-search-form) for what each feature does.

## Manual setup (Custom Element)

If you'd rather not use the App Market, you can add the search as a Wix **Custom Element** manually:

1. In the Wix Editor, add a **Custom Element** widget to your page.
2. Set its **Server URL** to:

   ```
   https://integration.carpose.app/integration-wix.js
   ```

   This registers the element under the tag `<carpose-vehicle-search>`.
3. Open the widget's settings and enter your Carposé **API key** and options.

## Editor vs. published site

In the Wix **Editor**, the widget shows a small placeholder ("Carposé Fahrzeugsuche") instead of the live search — the editor runs custom elements in a sandbox where the search can't fully load. On the **published site** (and in Wix Preview) the real vehicle search loads normally. This is expected and needs no configuration.

## Notes

* Place only one vehicle search per page.
* For manual Custom Element setup, use the Wix-specific loader URL (`integration-wix.js`) as the Server URL — not the standard `integration.js`.


