> For the complete documentation index, see [llms.txt](https://docs.carpose.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.carpose.de/events/02_vehicle-search.md).

# 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.md#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.md) 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.md)), 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.md#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.md) — 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.md)) 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');
  }
});
```
