# FunnelBuilder

> A form for building a funnel: name it, add steps from events or pages, reorder them and set the conversion time window.

Source: https://docs.nasaqui.com/components/funnel-builder

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/funnel-builder.json
```

FunnelBuilder is the editor for a funnel definition. A funnel has a name, two or more ordered steps and a window in which someone must complete them. Steps come from a list of tracked events and pages. The same event can be used twice, and steps are reordered with up and down buttons, so it works with keyboard and touch.

## When to use

- Creating or editing a funnel in an analytics or marketing tool.

## When not to use

- Showing results: use [`FunnelChart`](https://docs.nasaqui.com/components/funnel-chart).
- Free-form conditions: use [`RuleBuilder`](https://docs.nasaqui.com/components/rule-builder).

## Import

```tsx
import { FunnelBuilder } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { FunnelBuilder, type FunnelSource } from "@fadymondy/nasaq/web";

const sources: FunnelSource[] = [
  { id: "home", kind: "page", label: "Home page", detail: "/" },
  { id: "signup", kind: "event", label: "Signed up", detail: "user_signed_up" },
];

<FunnelBuilder sources={sources} onSave={async (funnel) => { await api.saveFunnel(funnel); }} />;
```

## Anatomy

```
FunnelBuilder        data-slot="funnel-builder"   (Card + form)
  name               Field + Input
  steps              ordered list: number, label, detail, move up, move down, remove
  add step           Select of events and pages
  window             number + unit (hour, day, week)
  footer             Save, with an error message when onSave fails
```

## API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `sources` | `FunnelSource[]` | required | `{ id, kind: "event" \| "page", label, detail? }`. |
| `defaultValue` | `FunnelDefinition` | `EMPTY_FUNNEL` | `{ name, steps, window: { amount, unit } }`. |
| `onChange` | `(value) => void` | none | Called on every edit. |
| `onSave` | `(value) => Promise<void \| { error?: string }>` | required | Save is enabled with a name and at least 2 steps. An `{ error }` keeps the form open. |
| `title / description` | `ReactNode` | built-in | Header. |
| `className / labels` | | none | Classes; `Partial<FunnelBuilderLabels>` strings. |

`FunnelBuilderStep` is `{ id, sourceId, kind, label, detail? }`; `id` is unique inside the funnel.

## Examples

Editing an existing funnel:

```tsx
import { FunnelBuilder } from "@fadymondy/nasaq/web";

<FunnelBuilder sources={sources} defaultValue={saved} onSave={update} />;
```

Live preview of steps:

```tsx
import { FunnelBuilder } from "@fadymondy/nasaq/web";

<FunnelBuilder sources={sources} onChange={(v) => setSteps(v.steps)} onSave={save} />;
```

## Accessibility

| Key | Action |
| --- | --- |
| Tab | Move through fields and step buttons. |
| Enter / Space | Press a move, remove or add button. |

Move buttons have names that include the step ("Move Signed up up") and are disabled at the ends.

## RTL & i18n

Step numbers, arrows and layout follow the writing direction. Event names and paths stay left-to-right. Built-in English and Arabic strings.

## Styling & tokens

Standard field, card and button tokens. Logical spacing only.

## Do / Don't

- Do give the time window a sensible default (7 days).
- Do show the event name under a friendly label so analysts can verify it.
- Don't allow saving with fewer than two steps.

## Related

- [FunnelChart](https://docs.nasaqui.com/components/funnel-chart)
- [RuleBuilder](https://docs.nasaqui.com/components/rule-builder)

## Lab

https://docs.nasaqui.com/?path=/docs/components-analytics-funnel-builder--docs

## Code

### React

```tsx
import { FunnelBuilder, type FunnelSource } from "@fadymondy/nasaq/web";

const sources: FunnelSource[] = [
  { id: "home", kind: "page", label: "Home page", detail: "/" },
  { id: "signup", kind: "event", label: "Signed up", detail: "user_signed_up" },
];

<FunnelBuilder sources={sources} onSave={async (funnel) => { await api.saveFunnel(funnel); }} />;
```

### shadcn

```tsx
import { FunnelBuilder, type FunnelSource } from "@/components/ui/funnel-builder";

const sources: FunnelSource[] = [
  { id: "home", kind: "page", label: "Home page", detail: "/" },
  { id: "signup", kind: "event", label: "Signed up", detail: "user_signed_up" },
];

<FunnelBuilder sources={sources} onSave={async (funnel) => { await api.saveFunnel(funnel); }} />;
```

### Vue

```vue
<script setup lang="ts">
import { NqFunnelBuilder, type FunnelSource } from "@fadymondy/nasaq/vue";

const sources: FunnelSource[] = [
  { id: "home", kind: "page", label: "Home page", detail: "/" },
  { id: "signup", kind: "event", label: "Signed up", detail: "user_signed_up" },
  { id: "order", kind: "event", label: "Placed an order", detail: "order_placed" },
];

async function save(_funnel: unknown) {
  await Promise.resolve();
}
</script>

<template>
  <NqFunnelBuilder :sources="sources" :on-save="save" />
</template>
```

### Blade

```blade
@php
    $sources = [
        ['id' => 'home', 'kind' => 'page', 'label' => 'Home page', 'detail' => '/'],
        ['id' => 'signup', 'kind' => 'event', 'label' => 'Signed up', 'detail' => 'user_signed_up'],
        ['id' => 'order', 'kind' => 'event', 'label' => 'Placed an order', 'detail' => 'order_placed'],
    ];
@endphp
<x-nq::funnel-builder :sources="$sources"
    x-on:save="window.__saved = JSON.stringify($event.detail.value); $event.detail.wait(Promise.resolve())" />
```

### HTML + Alpine

```html
<div data-slot="funnel-builder" x-data="nqFunnelBuilder(JSON.parse('{\u0022sources\u0022:[{\u0022id\u0022:\u0022home\u0022,\u0022kind\u0022:\u0022page\u0022,\u0022label\u0022:\u0022Home page\u0022,\u0022detail\u0022:\u0022\\\/\u0022},{\u0022id\u0022:\u0022signup\u0022,\u0022kind\u0022:\u0022event\u0022,\u0022label\u0022:\u0022Signed up\u0022,\u0022detail\u0022:\u0022user_signed_up\u0022},{\u0022id\u0022:\u0022order\u0022,\u0022kind\u0022:\u0022event\u0022,\u0022label\u0022:\u0022Placed an order\u0022,\u0022detail\u0022:\u0022order_placed\u0022}],\u0022value\u0022:{\u0022name\u0022:\u0022\u0022,\u0022steps\u0022:[],\u0022window\u0022:{\u0022amount\u0022:7,\u0022unit\u0022:\u0022day\u0022}},\u0022labels\u0022:{\u0022saved\u0022:\u0022Funnel saved.\u0022,\u0022failed\u0022:\u0022Could not save the funnel. Try again.\u0022,\u0022moveUp\u0022:\u0022Move {label} up\u0022,\u0022moveDown\u0022:\u0022Move {label} down\u0022,\u0022remove\u0022:\u0022Remove {label}\u0022}}'))" x-id="['nq-funnel']"
    x-on:save="window.__saved = JSON.stringify($event.detail.value); $event.detail.wait(Promise.resolve())" class="flex flex-col gap-4 rounded-card border border-border bg-card py-4 text-card-foreground">
    <form novalidate class="flex flex-col gap-4" x-on:submit.prevent="submit()">
        <div data-slot="card-header" class="grid auto-rows-min items-start gap-1 px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto]"><h3 data-slot="card-title" class="text-label text-foreground">Funnel builder</h3>
            <div data-slot="card-description" class="text-body-sm text-muted-foreground">Pick the steps people take, in order, and how long they have to finish them.</div></div>
        <div data-slot="card-content" class="px-4 flex flex-col gap-5"><template x-if="message && message.tone === 'danger'">
                <div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="message.text"></span></div>
            </div>
    </div>
            </template>
            <template x-if="message && message.tone === 'success'">
                <div data-slot="alert" data-tone="success" role="status"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-success/30 bg-nq-success-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="message.text"></span></div>
            </div>
    </div>
            </template>
            <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     x-model="nameInvalid" class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Funnel name</label>
                <input data-slot="input" type="text"
                    dir="auto" x-model="name" x-on:input="edited()" placeholder="Signup to first order" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
                <div data-slot="field-error" role="alert" x-show="invalid"  style="display: none"  class="text-caption text-nq-danger-text">Give the funnel a name.</div>
</div>
            <section x-bind:aria-labelledby="$id('nq-funnel', '-steps')" class="flex flex-col gap-2">
                <h4 x-bind:id="$id('nq-funnel', '-steps')" class="text-label text-foreground">Steps</h4>
                <p x-show="steps.length === 0" class="rounded-card border border-dashed border-border p-4 text-body-sm text-muted-foreground">No steps yet. Add the first event or page below.</p>
                <ol x-show="steps.length > 0" x-cloak style="display: none" class="flex flex-col gap-2">
                    <template x-for="(s, i) in steps" :key="s.id">
                        <li data-slot="funnel-builder-step" class="flex items-center gap-3 rounded-card border border-border p-2 ps-3">
                            <span class="flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-caption text-muted-foreground">
                                <bdi x-text="i + 1"></bdi>
                            </span>
                            <span class="flex min-w-0 flex-1 flex-col">
                                <span dir="auto" class="truncate text-label text-foreground" x-text="s.label"></span>
                                <span class="flex items-center gap-1.5 text-caption text-muted-foreground">
                                    <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border text-muted-foreground"><svg aria-hidden="true" x-show="s.kind === 'event'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M14 4.1 12 6"/>
  <path d="m5.1 8-2.9-.8"/>
  <path d="m6 12-1.9 2"/>
  <path d="M7.2 2.2 8 5.1"/>
  <path d="M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z"/>
</svg>                                        <svg aria-hidden="true" x-show="s.kind !== 'event'" style="display: none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/>
  <path d="M14 2v5a1 1 0 0 0 1 1h5"/>
  <path d="M10 9H8"/>
  <path d="M16 13H8"/>
  <path d="M16 17H8"/>
</svg>                                        <span x-text="s.kind === 'event' ? 'Event' : 'Page view'"></span></span>
                                    <bdi x-show="s.detail" dir="ltr" class="truncate" x-text="s.detail"></bdi>
                                </span>
                            </span>
                            <span class="flex shrink-0 items-center">
                                <button data-slot="button"
     type="button"                         x-bind:aria-label="say(&#039;moveUp&#039;, s.label)" x-bind:disabled="i === 0" x-on:click="move(i, -1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m5 12 7-7 7 7"/>
  <path d="M12 19V5"/>
</svg></button>
                                <button data-slot="button"
     type="button"                         x-bind:aria-label="say(&#039;moveDown&#039;, s.label)" x-bind:disabled="i === steps.length - 1" x-on:click="move(i, 1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 5v14"/>
  <path d="m19 12-7 7-7-7"/>
</svg></button>
                                <button data-slot="button"
     type="button"                         x-bind:aria-label="say(&#039;remove&#039;, s.label)" x-on:click="removeAt(i)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                            </span>
                        </li>
                    </template>
                </ol>
                <p x-show="stepsInvalid" x-cloak style="display: none" role="alert" class="text-body-sm text-danger">A funnel needs at least two steps.</p>
            </section>
            <section aria-label="Add a step" class="flex flex-col gap-2 rounded-card bg-muted/40 p-3">
                <span class="text-label text-foreground">Add a step</span>
                <div class="flex flex-wrap items-end gap-2">
                    <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022event\u0022]'), false)" x-modelable="value" x-bind="root"
            x-model="kindValue" aria-label="Add a step" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('event')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Event</button>
                        <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('page')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Page view</button>
</div>
                    <div class="min-w-48 flex-1" x-show="kind === 'event'">
                        <div data-slot="select" x-data="nqSelect(null, false)" x-modelable="value" x-id="['nq-select']" x-model="pickEvent" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Add a step" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? 'Choose\u2026'" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                            <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('signup', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('signup')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Signed up</span>
</div>
                                                                    <div data-slot="select-item" x-bind="item('order', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('order')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Placed an order</span>
</div>
    </div>
</template>
    </div>
                    </div>
                    <div class="min-w-48 flex-1" x-show="kind === 'page'" style="display: none">
                        <div data-slot="select" x-data="nqSelect(null, false)" x-modelable="value" x-id="['nq-select']" x-model="pickPage" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Add a step" class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? 'Choose\u2026'" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                            <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('home', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('home')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Home page</span>
</div>
    </div>
</template>
    </div>
                    </div>
                    <button data-slot="button"
     type="button"                         x-bind:disabled="!pick" x-on:click="addStep()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
  <path d="M12 5v14"/>
</svg>                        Add step</button>
                </div>
            </section>
            <div class="flex flex-col gap-1.5">
                <span x-bind:id="$id('nq-funnel', '-window')" class="text-label text-foreground">Conversion window</span>
                <div class="flex items-center gap-2" role="group" x-bind:aria-labelledby="$id('nq-funnel', '-window')">
                    <input data-slot="input" type="number"
     dir="ltr"                 min="1" inputmode="numeric" aria-label="Conversion window" x-model.number="amount" x-on:change="setAmount()" class="min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control text-start w-24">
                    <div data-slot="select" x-data="nqSelect(null, false)" x-modelable="value" x-id="['nq-select']" x-model="unit" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        aria-label="Unit" class="flex h-control min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] w-32">
    <span data-slot="select-value" x-text="label() ?? ''" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                        <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('hour', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('hour')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">hours</span>
</div>
                            <div data-slot="select-item" x-bind="item('day', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('day')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">days</span>
</div>
                            <div data-slot="select-item" x-bind="item('week', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('week')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">weeks</span>
</div>
    </div>
</template>
    </div>
                    <bdi dir="ltr" class="text-caption text-muted-foreground" x-text="windowKey()"></bdi>
                </div>
                <span class="text-caption text-muted-foreground">People must finish all steps within this time of the first one.</span>
            </div></div>
        <div data-slot="card-footer" class="flex items-center px-4 justify-end gap-2"><button data-slot="button"
     type="button"                         x-bind:disabled="pending" x-on:click="reset()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Reset</button>
            <button data-slot="button"
     type="submit"                         x-bind:disabled="pending" x-bind:aria-busy="pending ? &#039;true&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        Save funnel</button></div>
    </form>
</div>
```
