---
name: web-record
description: |
  Web-page recording / storyboard video skill (records webm, then by default transcodes to mp4 + grabs a cover frame + uploads to VOD and returns a CDN URL), built on Playwright Python.
  Supports: fixed-duration recording, condition-triggered stop (element appears / disappears), auto-scroll recording (top to bottom), custom storyboards, and parameterized templates.
  Storyboard scenes: highlight / focus / zoom / scroll / virtual-cursor click / type / hover / caption / title card / arrow / numbered sequence / redact / code-line highlight.

  Video only. For still screenshots (png/jpg) use web-screenshot / `web_screenshot`.

  Use this skill immediately whenever the user asks for any of:
  - Screen recording, record a web page, record a video, capture an interaction, produce a webm, screencast
  - Scroll recording, a video that pans the page from top to bottom
  - Multi-scene video, storyboard video, timeline-sequenced clips (highlight box / zoom-in / scrolling walkthrough)
  - Animated demos of clicks / typing / hover (virtual cursor + ripple / typewriter / triggering tooltips)
  - One-command clips from a template (github-repo-intro / github-readme-tour / github-code-tour, etc.)
triggers:
  - Screen recording, record a web page, record a video, capture an interaction, produce a webm, screencast
  - Scroll recording, a video that pans the page from top to bottom
  - Multi-scene video, storyboard video, timeline-sequenced clips
  - Animated demos of clicks / typing / hover (virtual cursor + ripple / typewriter / triggering tooltips)
  - One-command template clips, numbering and highlighting several regions in sequence and recording it
---

# Web Recording / Storyboard Video Skill (`web_record`)

The recording entry point. For still screenshots use **web-screenshot / `web_screenshot`** instead.

**Script location**: this skill has no `scripts/` of its own — it reuses `record.py` and the `_media_screenshot/` package from the web-screenshot directory (`skill.json`'s `entry.scriptPath` points relatively at `../web-screenshot/scripts/record.py`). Everywhere the commands below say **`<RecordScript>`**, substitute:

```
<SkillDir>/../web-screenshot/scripts/record.py
```

> Path convention: read the system-injected `Base directory for this skill: <path>` as `<SkillDir>`. Never hardcode an absolute path.

## Prerequisites

- **Python 3.9+**
- **The `playwright` pip package + the chromium engine**: the first run **bootstraps automatically** (`pip install playwright` + `playwright install chromium`).
- **ffmpeg** (optional): only needed on the standalone `--no-upload` path, to trim the loading segment off the start. On the default upload path ab-render trims it in the same transcode pass, so no local ffmpeg is required.

## Output and upload

Default behavior (it does **not** just drop a webm): record `.webm` → hand off to ab-render to transcode to mp4 + grab a cover frame → upload to VOD → print a playable CDN URL, and **delete the local webm** (VOD holds the copy).

- `--no-upload`: skip the finishing steps and keep only the local `.webm` (in this case local ffmpeg makes a best-effort trim of the start; without ffmpeg the raw clip is kept and a notice printed).
- `--keep-webm`: keep the local `.webm` even after a successful upload.
- `--cover-at-sec`: cover-frame timestamp, default `0.5` seconds (avoids a blank first frame).
- `--vod-title`: VOD asset title, defaults to the output file name.

Requires the env vars **`PRIV_TOKEN`** (auth) and **`REMOTION_RENDER_API_URL`** (ab-render address). Without `PRIV_TOKEN` it degrades automatically to "local file only" and says so on stderr; an upload failure degrades the same way, so an already-recorded file is never lost.

## Recording usage

### Fixed-duration recording

```bash
python3 <RecordScript> \
  --url "https://example.com" \
  --output "demo.webm" \
  --viewport "1280,720" \
  --duration 8000
```

### Stop on a condition (pair it with max-duration so it can't hang)

```bash
# Stop once the "loaded" element appears
python3 <RecordScript> \
  --url "https://example.com" \
  --output "loaded.webm" \
  --stop-when-selector "div.loaded" \
  --max-duration 30000

# Stop once the loading spinner disappears
python3 <RecordScript> \
  --url "https://example.com" \
  --output "spinner_done.webm" \
  --stop-when-hidden ".spinner" \
  --max-duration 30000
```

### Combined: whichever fires first wins

```bash
python3 <RecordScript> \
  --url "https://example.com" \
  --output "race.webm" \
  --duration 15000 \
  --stop-when-selector ".done" \
  --max-duration 20000
```

### Wait for the page to be ready before recording

```bash
python3 <RecordScript> \
  --url "https://app.example.com" \
  --output "after_login.webm" \
  --storage-state "./auth.json" \
  --wait-for-selector "#dashboard" \
  --duration 6000
```

### Auto-scroll recording (top to bottom)

```bash
python3 <RecordScript> \
  --url "https://example.com" \
  --output "scroll.webm" \
  --viewport "1440,900" \
  --scroll-through \
  --scroll-step 50 \
  --scroll-interval 60 \
  --scroll-pause-top 1000 \
  --scroll-pause-bottom 1500
```

### Template videos (fast path)

`templates/` ships parameterized storyboard templates that turn common structures into a one-line command:

```bash
# List available templates
python3 <RecordScript> --list-templates

# Record any GitHub repo with the github-repo-intro template
python3 <RecordScript> \
  --template github-repo-intro \
  --param repo_url=https://github.com/user/repo \
  --output repo.webm
```

Repeat `--param key=value` to override the template's defaults (label text, colors, zoom factor, etc.). A template's `_meta.params` declares the parameters it accepts.

**Current templates**:

| Name | Description | Required params |
|------|-------------|-----------------|
| `github-repo-intro` | GitHub repo trio: highlight the project name → zoom the star count → scroll the README | `repo_url` |
| `github-readme-tour` | Title card → project name → cursor hovers star → README scroll → closing call to action | `repo_url` |
| `github-code-tour` | Jump to a file → highlight a line range → caption walkthrough | `file_url` / `start_line` / `end_line` |

**Adding a template**: drop a JSON file under `<SkillDir>/../web-screenshot/templates/`, write the scenes in storyboard format, and use `{{var_name}}` placeholders. Optionally add `_meta.params` to declare parameters (marked `required: true` / `default: ...`). A placeholder that is the entire string is auto-converted to int/float/bool (e.g. `"scale": "{{zoom_scale}}"` renders as a number).

### Custom storyboards

Compose a sequence of "scenes" into one video; scenes are joined by an overlay cross-fade (**never a black frame**).

```bash
python3 <RecordScript> \
  --url "https://github.com/user/repo" \
  --output "story.webm" \
  --viewport "1440,900" \
  --storyboard story.json
```

Example `story.json`:

```json
{
  "url": "https://github.com/user/repo",
  "viewport": [1440, 900],
  "waitForReadySelectors": ["article.markdown-body"],
  "settleMs": 1200,
  "transition": { "fadeOutMs": 320, "gapMs": 180 },
  "scenes": [
    {
      "type": "highlight",
      "selector": "strong[itemprop=\"name\"] a",
      "color": "#ff3b30",
      "lineWidth": 5,
      "padding": 10,
      "label": "Project name",
      "duration": 3000
    },
    {
      "type": "zoom",
      "target": "page",
      "selector": "#repo-stars-counter-star",
      "scale": 2.4,
      "rampInMs": 900,
      "holdMs": 2200,
      "rampOutMs": 700,
      "label": "⭐ Star count"
    },
    {
      "type": "scroll",
      "fromSelector": "article.markdown-body",
      "step": 50,
      "interval": 70,
      "duration": 10000,
      "holdAtEnd": 800
    }
  ]
}
```

**Scene types**:

*Visual emphasis*
- `highlight` — box around an element + label; `padding` / `lineWidth` / `color` / `duration`
- `focus` — dim the surroundings (spotlight) + outline the element + label
- `zoom` — zoom animation
  - `target: "element"` (default) — scale the target element itself (elastic curve)
  - `target: "page"` — zoom the whole page around the target element's center, like browser zoom
  - `scale` / `rampInMs` / `rampOutMs` / `holdMs` / `dim` / `label` / `color`
- `scroll` — scroll to a target. `fromSelector` start, `toSelector` end, `step` / `interval` / `duration` / `holdAtEnd`
- `wait` — plain pause for `duration`

*Interaction simulation (virtual cursor)*
- `click` — cursor flies to the element → ripple → really fires `el.click()`. `moveMs` / `preHoldMs` / `postHoldMs` / `rippleMs` / `color` / `realClick` (default true; set false to animate without clicking)
- `type` — cursor flies to the input → focus → types character by character. `text` / `delay` (ms per char) / `clearFirst` / `moveCursor`
- `hover` — cursor moves onto the element and triggers a native hover; typical for surfacing a tooltip / hover card. `moveMs` / `holdMs`

*Annotation*
- `arrow` — draw an SVG curved arrow from one anchor to another. Anchors `from` / `to` look like `{selector, side}` (side: center/left/right/top/bottom) or `{x, y}`. `color` / `lineWidth` / `label` / `duration`
- `sequence` — several selectors get ①②③ circular badges + boxes + labels in order. `items: [{selector, label}, ...]` / `stepMs` controls the gap between steps / `color` / `holdMs`
- `caption` — caption bar sliding in from the top/bottom. `text` / `position: top|bottom` / `background` / `color` / `fontSize` / `duration`
- `title-card` — full-screen opening/closing title card. `title` / `subtitle` (optional) / `background` / `accent` / `duration`
- `redact` — mask a region: blur / pixelate / solid. `selectors: [...]` or `selector` / `mode: "blur" | "pixelate" | "solid"` / `color` (when solid) / `duration`

*GitHub-specific*
- `code-lines` — highlight a contiguous code-line range (matches the new React blob `[data-line-number="{n}"]` by default; the old table-based blob needs `linePattern: "#LC{n}"`). `startLine` / `endLine` / `linePattern` / `color` / `label` / `duration`

**Automatic trimming**: in storyboard mode the recorder waits for the page to genuinely settle (networkidle + skeletons gone), then trims the leading load segment afterwards with frame-accurate ffmpeg, so the final video length ≈ the sum of the scene durations.

### Full flag list

| Flag | Description | Default |
|------|-------------|---------|
| `-u` / `--url` | Target URL (required) | — |
| `-o` / `--output` | Local `.webm` output path | `recording.webm` |
| `-b` / `--browser` | `chromium` / `firefox` / `webkit` (chromium recommended) | `chromium` |
| `--device` | Device emulation | none |
| `--viewport` | `"width,height"` | none |
| `--duration` | Fixed recording length (ms) | none |
| `--stop-when-selector` | Stop once this selector appears | none |
| `--stop-when-hidden` | Stop once this selector disappears | none |
| `--max-duration` | Safety cap for condition modes (ms) | 60000 |
| `--scroll-through` | Auto-scroll from top to bottom | off |
| `--scroll-step` | Pixels per step | 60 |
| `--scroll-interval` | Interval between steps (ms) | 50 |
| `--scroll-pause-top` | Pause at the top (ms) | 800 |
| `--scroll-pause-bottom` | Pause at the bottom (ms) | 1200 |
| `--storyboard` | Storyboard JSON file path | none |
| `--template` | Template name (`templates/<name>.json`); mutually exclusive with `--storyboard` | none |
| `--param` | Template parameter `key=value` (repeatable) | none |
| `--list-templates` | List available templates and exit | — |
| `--wait-for-selector` | Wait for this selector before recording | none |
| `--wait-for-timeout` | Fixed wait before recording (ms) | none |
| `--color-scheme` | prefers-color-scheme | none |
| `--user-agent` | Override the User-Agent | none |
| `--timeout` | Playwright global timeout (ms) | none |
| `--ignore-https-errors` | Ignore certificate errors | off |
| `--storage-state` | storageState JSON file | none |
| `--cookies` | cookies JSON string or file | none |
| `--no-upload` | Skip the upload; keep only the local webm | off (uploads by default) |
| `--vod-title` | Title for the VOD upload | output file name |
| `--cover-at-sec` | Cover-frame timestamp (seconds) | 0.5 |
| `--keep-webm` | Keep the local webm after a successful upload | off (deleted by default) |

At least one of `--duration` / `--stop-when-selector` / `--stop-when-hidden` / `--scroll-through` / `--storyboard` / `--template` must be supplied; when several are combined, **whichever fires first stops the recording**, with `--max-duration` as the backstop. A template / storyboard JSON may carry its own `url`, in which case `--url` can be omitted.

## Error handling

- **The first run is slow**: the script auto-runs `pip install playwright` + `playwright install chromium`. Wait it out once.
- **`ModuleNotFoundError: playwright`**: the bootstrap did not complete; run `pip install playwright && playwright install chromium` manually.
- **`Executable doesn't exist`**: the browser engine is missing; run `playwright install chromium`.
- **`Timeout ... exceeded`**: raise `--timeout` / `--wait-for-timeout`, or switch to a more reliable `--wait-for-selector`.
- **No recording file produced**: check that `--output` ends in `.webm`; read the `stop reason` on stderr.
- **Got a local path but no CDN URL**: usually `PRIV_TOKEN` is unset, or the finishing steps failed and it degraded — stderr has the reason.
- **Loading artifacts still visible at the start of a storyboard**: raise `settleMs` / add `waitForReadySelectors`; the `--no-upload` path additionally needs local ffmpeg.
- **Session expired**: re-export `storageState` or update the cookies JSON — `domain` / `path` must match.
- **HTTPS certificate errors**: in test environments add `--ignore-https-errors`.
