# @mochabug/adapt-web

Embed Adapt automations in any website.

```bash
npm install @mochabug/adapt-web
```

**CDN base:** `https://cdn.mochabug.com/adapt/web/__VERSION__/`

| Bundle | File | Includes |
|--------|------|----------|
| Full | `adapt-web.min.js` | Automation + Cap.js challenges |
| Core | `adapt-web.core.min.js` | Automation only (~40KB smaller) |
| Cap | `adapt-web.cap.min.js` | Cap.js challenge widget only |
| Headless | `adapt-core.min.js` | Headless client (no UI) |

### Composable loading

Bundles merge into a single `MbAdapt` global, so you can combine them. For example, headless + Cap for automations that need proof-of-work but no UI:

```html
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-core.min.js"></script>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.cap.min.js"></script>
<script>
  // MbAdapt has exports from both scripts
  var client = MbAdapt.createAdaptClient(
    MbAdapt.createConnectClient({ id: "YOUR_ID" }),
    "YOUR_ID"
  );
</script>
```

---

## 1. `<adapt-automation>`

The main component. Drop it in, set `automation-id`, done.

### Optimal — `<head>` access available

Preload the script and load the stylesheet in `<head>` to eliminate flash of unstyled content.

```html
<head>
  <link rel="preload" href="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.min.js" as="script">
  <link rel="stylesheet" href="https://cdn.mochabug.com/adapt/web/__VERSION__/styles.css">
</head>
<body>
  <adapt-automation automation-id="YOUR_ID" requires-challenge style="height: 600px"></adapt-automation>
  <script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.min.js"></script>
</body>
```

No challenges? Use the core bundle and drop `requires-challenge`:

```html
<head>
  <link rel="preload" href="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.core.min.js" as="script">
  <link rel="stylesheet" href="https://cdn.mochabug.com/adapt/web/__VERSION__/styles.css">
</head>
<body>
  <adapt-automation automation-id="YOUR_ID" style="height: 600px"></adapt-automation>
  <script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.core.min.js"></script>
</body>
```

### Embedded — no `<head>` access (Wix, Squarespace, WordPress, etc.)

Just the element and script. CSS is auto-injected at runtime.

```html
<adapt-automation automation-id="YOUR_ID" requires-challenge style="height: 600px"></adapt-automation>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.min.js"></script>
```

Without challenges:

```html
<adapt-automation automation-id="YOUR_ID" style="height: 600px"></adapt-automation>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.core.min.js"></script>
```

### ESM

```typescript
import '@mochabug/adapt-web';           // with challenges
import '@mochabug/adapt-web/styles.css';
```

```typescript
import '@mochabug/adapt-web/core';      // without challenges (lighter)
import '@mochabug/adapt-web/styles.css';
```

```html
<adapt-automation automation-id="YOUR_ID" style="height: 600px"></adapt-automation>
```

### Attributes

| Attribute | Description |
|-----------|-------------|
| `automation-id` | **Required.** Automation ID |
| `requires-challenge` | Require proof-of-work before starting (full bundle only) |
| `auth-token` | Client-side auth token |
| `session-token` | Server-created session token |
| `inherit-token` | Join an existing session |
| `challenge-token` | Pre-solved challenge token |
| `transmitter` | Transmitter ID |
| `inherit-from` | JSON object: `{"hash":"mb_session"}` or `{"param":"token"}` |
| `signals` | JSON object of initial signal values |
| `fork-display-mode` | `side-by-side` (default) or `dialog` |
| `side-by-side-split` | Split percentage, e.g. `60` |
| `dark-mode` | Enable dark mode |
| `auto-resizing` | Container follows iframe content height |
| `allow-floating` | Set to `"false"` to hide pop-out buttons and block user-initiated floating |
| `allow-docking` | Set to `"false"` to hide dock buttons and block user-initiated docking |
| `allow-dialog-docking` | Set to `"false"` to prevent tab splits inside floating dialog overlays |
| `floating-auto-resize` | Floating overlays auto-resize based on iframe content height |
| `persist` | Persist session across page refreshes |
| `confirm-on-close` | Show a confirmation dialog before the user leaves the page while a session is active |

Initial JSON options can be written directly on the element:

```html
<adapt-automation
  automation-id="YOUR_ID"
  inherit-from='{"hash":"mb_session"}'
  signals='{"key":"value"}'
  style="height: 600px"
></adapt-automation>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.core.min.js"></script>
```

For complex values that are easier to build in JavaScript, set properties before loading the bundle:

```html
<adapt-automation id="automation" automation-id="YOUR_ID" style="height: 600px"></adapt-automation>
<script>
  const el = document.getElementById('automation');
  el.inheritFrom = { hash: 'mb_session' };
  el.signals = { key: 'value' };
</script>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.core.min.js"></script>
```

### JS-only properties

Set via element reference — not available as HTML attributes.

| Property | Type |
|----------|------|
| `capWidgetOptions` | `{ workerCount?: number; i18n?: CapWidgetI18n }` |
| `classNames` | `{ root?: string; iframe?: string; statusMessage?: string; statusCard?: string }` |
| `styles` | `Partial<CSSStyleDeclaration>` |
| `persistOptions` | `{ storage?: 'session' \| 'local'; ttl?: number; key?: string }` |
| `text` | `StatusText` |
| `theme` | `AdaptTheme` |

`signals` and `inheritFrom` are also available as JS properties. They are initial options; set them before the element initializes.

### Events

| Event | Detail |
|-------|--------|
| `adapt-session` | `{ status: StatusJson, fork?: string }` |
| `adapt-output` | `{ output: Output }` |
| `adapt-fork-active` | `{ active: boolean }` |

### Methods

| Method | Description |
|--------|-------------|
| `newSession()` | Ends the current session and starts a new one. Returns a `Promise<void>`. |

---

## 2. `<adapt-cap>`

Standalone proof-of-work challenge widget. Use when you manage the automation client yourself.

Requires a `client` JS property — set it after the element is in the DOM.

### Optimal

```html
<head>
  <link rel="preload" href="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.cap.min.js" as="script">
  <link rel="stylesheet" href="https://cdn.mochabug.com/adapt/web/__VERSION__/styles.css">
</head>
<body>
  <adapt-cap automation-id="YOUR_ID"></adapt-cap>
  <script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.cap.min.js"></script>
  <script>
    var el = document.querySelector('adapt-cap');
    el.client = MbAdapt.createConnectClient({ id: 'YOUR_ID' });
    el.addEventListener('adapt-cap-solve', function(e) {
      console.log('Token:', e.detail.token);
    });
  </script>
</body>
```

### Embedded

```html
<adapt-cap automation-id="YOUR_ID"></adapt-cap>
<script src="https://cdn.mochabug.com/adapt/web/__VERSION__/adapt-web.cap.min.js"></script>
<script>
  var el = document.querySelector('adapt-cap');
  el.client = MbAdapt.createConnectClient({ id: 'YOUR_ID' });
  el.addEventListener('adapt-cap-solve', function(e) {
    console.log('Token:', e.detail.token);
  });
</script>
```

### ESM

```typescript
import { createConnectClient } from '@mochabug/adapt-web/cap';
import '@mochabug/adapt-web/styles.css';

const el = document.querySelector('adapt-cap')!;
el.client = createConnectClient({ id: 'YOUR_ID' });
el.addEventListener('adapt-cap-solve', (e) => {
  console.log('Token:', e.detail.token);
});
```

### Attributes

| Attribute | Description |
|-----------|-------------|
| `automation-id` | **Required.** Automation ID |
| `dark-mode` | Enable dark mode |
| `worker-count` | Number of WASM workers |

### JS-only properties

| Property | Type |
|----------|------|
| `client` | `AutomationClient` — **required**, set via JS |
| `i18n` | `CapWidgetI18n` — label overrides (set before `client`) |

### Events

| Event | Detail |
|-------|--------|
| `adapt-cap-solve` | `{ token: string, expires: Date }` |
| `adapt-cap-error` | `{ error: Error }` |

---

## Styling

Three ways to theme, from simplest to most powerful:

### 1. `theme` prop (recommended)

Set the `theme` JS property for semantic theming. Derives 30+ CSS variables from a few tokens.

```javascript
const el = document.querySelector('adapt-automation');
el.theme = {
  mode: 'dark',
  primary: '#6366f1',
  background: '#0f172a',
  surface: '#1e293b',
  text: '#f1f5f9',
  border: '#334155',
  font: '"Inter", sans-serif',
};
```

**`AdaptTheme` interface:**

| Token | Effect |
|-------|--------|
| `mode` | `'light'` or `'dark'` — toggles dark mode class |
| `primary` | Accent color — derives separator, drop target, spinner colors |
| `background` | Root bg, active tab bg, status card bg |
| `surface` | Panel content bg, tab bar bg |
| `text` | Active tab text, status text, cap widget text |
| `textSecondary` | Inactive tab text |
| `border` | Tab separators, status card border, cap border |
| `font` | All panel UI text + cap widget |
| `vars` | `Record<string, string>` — direct variable overrides (key = name without `--mb-adapt-` prefix) |

The `vars` escape hatch lets you override any variable:

```javascript
el.theme = {
  primary: '#6366f1',
  vars: {
    'floating-shadow': 'none',
    'floating-border': '2px solid #6366f1',
    'cap-border-radius': '0px',
  },
};
```

### 2. CSS custom properties

Set `--mb-adapt-*` variables on `.mb-adapt` or any ancestor. No Shadow DOM — standard CSS inheritance works.

```css
.mb-adapt {
  --mb-adapt-fork-bg: #ffffff;
  --mb-adapt-fork-tab-bg: #f5f5f5;
  --mb-adapt-fork-tab-color: #1a1a1a;
  --mb-adapt-fork-separator: #e0e0e0;
}

.mb-adapt--dark {
  --mb-adapt-fork-bg: #1e1e1e;
  --mb-adapt-fork-tab-bg: #2a2a2a;
  --mb-adapt-fork-tab-color: #e0e0e0;
  --mb-adapt-fork-separator: #3a3a3a;
}
```

### 3. Direct CSS on internal classes

No Shadow DOM — all internal elements use plain CSS classes. Target them directly for effects that CSS variables can't express: animations, pseudo-elements, transitions, media queries.

**Animated gradient toolbar (liquid glass):**

```css
@keyframes toolbar-glow {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

/* Light mode — warm aurora */
.mb-adapt .mb-group-header {
  background: linear-gradient(
    135deg,
    rgba(99, 102, 241, 0.08) 0%,
    rgba(168, 85, 247, 0.08) 25%,
    rgba(236, 72, 153, 0.06) 50%,
    rgba(99, 102, 241, 0.08) 100%
  ) !important;
  background-size: 300% 300%;
  animation: toolbar-glow 8s ease infinite;
}

/* Dark mode — deep neon */
.mb-adapt--dark .mb-group-header {
  background: linear-gradient(
    135deg,
    rgba(99, 102, 241, 0.15) 0%,
    rgba(168, 85, 247, 0.12) 25%,
    rgba(236, 72, 153, 0.10) 50%,
    rgba(99, 102, 241, 0.15) 100%
  ) !important;
  background-size: 300% 300%;
  animation: toolbar-glow 8s ease infinite;
}
```

**Key classes you can target:**

| Class | Element |
|-------|---------|
| `.mb-group-header` | Tab toolbar |
| `.mb-tab` | Individual tab |
| `.mb-tab[data-active="true"]` | Active tab |
| `.mb-group-content` | Panel content area |
| `.mb-floating-overlay` | Floating panel container |
| `.mb-layout-separator` | Resize handle between panels |
| `.mb-drag-ghost` | Tab drag preview |
| `.mb-adapt__status-card` | Error/stopped status card |
| `.mb-adapt__status-icon` | Status card icon |
| `.mb-adapt-minimized-tab` | Minimized panel tab |
| `.mb-drop-overlay` | Drop target highlight |

All three approaches compose — use `theme` for colors, CSS variables for fine-tuning, and direct class targeting for animations.

<details>
<summary>Full CSS variable reference</summary>

### General

| Variable | Light default | Dark default | Description |
|---|---|---|---|
| `--mb-adapt-bg` | `transparent` | | Root & group backgrounds |
| `--mb-adapt-font` | `system-ui, -apple-system, sans-serif` | | All panel UI text |
| `--mb-adapt-button-hover-bg` | `rgba(128,128,128,0.2)` | `rgba(128,128,128,0.3)` | Close/popout/action button hover |
| `--mb-adapt-separator-active` | `rgba(59,130,246,0.5)` | `rgba(99,130,246,0.6)` | Resize handle hover/active |
| `--mb-adapt-border-radius` | `8px` | | Iframe border radius |

### Toolbar and tabs

| Variable | Light default | Dark default | Description |
|---|---|---|---|
| `--mb-adapt-fork-bg` | `#ffffff` | `#1e1e1e` | Panel content background |
| `--mb-adapt-fork-tab-bg` | `#f3f3f3` | `#252526` | Toolbar / inactive tab bg |
| `--mb-adapt-fork-tab-active-bg` | `#ffffff` | `#1e1e1e` | Active tab background |
| `--mb-adapt-fork-tab-color` | `rgb(51,51,51)` | `#ffffff` | Active tab text |
| `--mb-adapt-fork-tab-inactive-color` | `rgba(51,51,51,0.7)` | `#969696` | Inactive tab text |
| `--mb-adapt-fork-separator` | `rgba(128,128,128,0.35)` | `rgb(68,68,68)` | Tab/panel borders |
| `--mb-adapt-tab-radius` | `0` | | Tab border-radius (use `999px` for pill shape) |
| `--mb-adapt-tab-shadow` | `none` | | Tab box-shadow |
| `--mb-adapt-tab-active-shadow` | `none` | | Active tab box-shadow |
| `--mb-adapt-tab-gap` | `0px` | | Tab margin (spacing between tabs) |
| `--mb-adapt-tab-padding` | `0 14px` | | Tab padding |
| `--mb-adapt-tab-font-size` | `13px` | | Tab label font size |
| `--mb-adapt-toolbar-height` | `40px` | | Toolbar / tab bar height |
| `--mb-adapt-toolbar-padding` | `0` | | Toolbar inner padding (standard CSS shorthand) |
| `--mb-adapt-tab-min-width` | `100px` | | Tab minimum width |
| `--mb-adapt-tab-spacing` | `6px` | | Gap between tab label and action buttons |

### Floating panels (elevation)

| Variable | Light default | Dark default | Description |
|---|---|---|---|
| `--mb-adapt-floating-shadow` | `0 25px 50px -12px rgba(0,0,0,0.25), 0 12px 24px -8px rgba(0,0,0,0.15)` | `… rgba(0,0,0,0.5), … rgba(0,0,0,0.3)` | Overlay box-shadow |
| `--mb-adapt-floating-border` | `none` | `1px solid rgba(255,255,255,0.06)` | Overlay border |
| `--mb-adapt-floating-backdrop` | `none` | | Overlay backdrop-filter |
| `--mb-adapt-floating-radius` | `8px` | | Overlay border-radius |
| `--mb-adapt-status-card-shadow` | `0 4px 24px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.04)` | `… rgba(0,0,0,0.25), … rgba(0,0,0,0.15)` | Status card box-shadow |
| `--mb-adapt-drag-ghost-shadow` | `0 4px 12px rgba(0,0,0,0.15)` | `0 4px 12px rgba(0,0,0,0.35)` | Drag ghost box-shadow |

### Drop targets

| Variable | Light default | Dark default |
|---|---|---|
| `--mb-adapt-drop-header-bg` | `rgba(99,102,241,0.18)` | `rgba(129,140,248,0.22)` |
| `--mb-adapt-drop-center-bg` | `rgba(99,102,241,0.12)` | `rgba(129,140,248,0.15)` |
| `--mb-adapt-drop-split-bg` | `rgba(99,102,241,0.14)` | `rgba(129,140,248,0.18)` |
| `--mb-adapt-drop-border` | `rgba(99,102,241,0.55)` | `rgba(129,140,248,0.6)` |

### Status cards

| Variable | Light default | Dark default |
|---|---|---|
| `--mb-adapt-status-card-bg` | `#ffffff` | `#1e293b` |
| `--mb-adapt-status-card-border` | `#e5e7eb` | `#334155` |
| `--mb-adapt-status-icon-bg` | `#fef2f2` | `#351c1c` |
| `--mb-adapt-status-text` | `#374151` | `#e2e8f0` |

### Cap widget

| Variable | Light default | Dark default |
|---|---|---|
| `--mb-adapt-cap-background` | `#ffffff` | `#1e293b` |
| `--mb-adapt-cap-border-color` | `#e2e8f0` | `#334155` |
| `--mb-adapt-cap-border-radius` | `16px` | |
| `--mb-adapt-cap-height` | `72px` | |
| `--mb-adapt-cap-width` | `380px` | |
| `--mb-adapt-cap-padding` | `20px 28px` | |
| `--mb-adapt-cap-gap` | `20px` | |
| `--mb-adapt-cap-color` | `#1e293b` | `#f1f5f9` |
| `--mb-adapt-cap-checkbox-size` | `36px` | |
| `--mb-adapt-cap-checkbox-border` | `2px solid #cbd5e1` | `2px solid #475569` |
| `--mb-adapt-cap-checkbox-radius` | `10px` | |
| `--mb-adapt-cap-checkbox-background` | `#f8fafc` | `#0f172a` |
| `--mb-adapt-cap-spinner-color` | `#6366f1` | `#818cf8` |
| `--mb-adapt-cap-spinner-bg` | `#e2e8f0` | `#334155` |
| `--mb-adapt-cap-spinner-thickness` | `3px` | |
| `--mb-adapt-cap-font` | `inherit` | |

### Z-index / stacking

| Variable | Default | Description |
|---|---|---|
| `--mb-adapt-z-base` | `0` | Base z-index offset — added to all internal z-index values |

Set `--mb-adapt-z-base` to shift all internal z-index values. Useful when embedding inside modals or drawers that have their own stacking context. Example: `--mb-adapt-z-base: 10000` lifts all layers by 10000.

Internal stacking order from low to high: separators (1), resize handles (10), minimized tabs (100), floating panels (100000+), status/cap (200000), confirm dialog (300000), drop targets (999998), drag ghost (999999). All values are offset by `--mb-adapt-z-base`.

</details>

---

## License

ISC (c) mochabug AB
