Journey demo
Write a journey, test it, clip it. Claude runs a user journey in Chrome, saves every screen as a frozen HTML page, lays the frames out on a storyboard the person edits in their browser, and only after they approve renders an MP4 with HyperFrames. Nothing is screen-recorded, so any word on any screen can change later without running the journey again.
Source of truth: STORYBOARD.md + snapshots/*.html in the demo folder. Everything else is generated.
Needs
- Chrome with the Claude in Chrome extension (to run the journey and capture)
- Node 18+, and
npx hyperframes(stills and the render; no install step) - Scripts in this skill:
scripts/freeze.js(capture),scripts/demo.mjs(build and the editable board). Run them by path:node <this skill>/scripts/demo.mjs …
The demo folder
docs/demos/<slug>/ in the person's repo, unless they name another place:
STORYBOARD.md the plan: frames, captions, clicks, timing, notes (edit)
snapshots/*.html the captured screens (edit text only)
index.html scenes/ boards/ .build/ generated by demo.mjs (never edit)
out/<slug>.mp4 the render
Suggest gitignoring scenes/ .build/ boards/stills/ out/.
1. The journey
Use a journey file when one exists (docs/journeys/*.md: Start:, numbered steps, Expect: checks,
> Note: lines). If the browser-journeys skill is installed, it owns that format and its run rules,
and you follow them. No journey yet? Write one with the person first: the demo is only as good as the
steps.
Before running, ask in one message, with your recommendation for each:
- Host: local or staging; never production unless they say so.
- Branch: when the journey offers a choice (copy from library or write new, saved card or new card), which path the demo shows. Default: the shortest happy path.
- Values: the journey's test values ("Journey test — please ignore") or demo-friendly ones you propose. Default: propose demo-friendly ones, and use them everywhere they appear.
- Message and audience: draft both from the journey; they confirm.
- Side effects: say what the run creates on that host (accounts, orders, bids) and the journey's cleanup, if it has one.
A journey written for another host (Start: local, seed commands, codes read from a dev log)? Its
setup lines belong to that host: ask for the equivalents on this one, never guess.
2. Run it and capture
resize_windowto 1440×900 and keep that size for the whole run: every frame must share one viewport, and the build refuses mixed sizes.- Do each step as the journey says. Check every
Expect:; on a failure, stop and report. A demo is never built on a broken flow. A capture run needn't go at a watchable pace: wait only until the page has settled before each freeze. - Sign-in and persona switches are the person's. Say "Sign in as and tell me when you're done", wait, then carry on. A code the journey prints to a dev log you started may be entered by you; never a password or a code from someone's inbox. Don't freeze sign-in screens (they show emails): the storyboard cuts from before sign-in to after it.
- Choose the frames: one click per frame. A frame is the screen right before a click the
viewer needs to follow the flow. Typing, ticking and choosing happen before the freeze, so the
frame shows them done; when the frame should show an empty field, freeze before typing. A screen
reached by URL or with nothing to press is a frame with no
click(it fades in). Skip setup and sign-in. Aim for 4–8 frames, and always freeze the final screen. - Freeze: paste the whole of
scripts/freeze.jsinto the page withjavascript_tool(once per page load;typeof window.__freezetells you), thenawait window.__freeze('03-open-the-orbit')(two-digit order, then a slug). Compare theviewportit returns with the first frame's; if it differs (a download or info bar opened), close the bar and freeze again. - Each freeze downloads
<name>.htmlto~/Downloads. Move them intosnapshots/when the run ends. Freezing a name again makes Chrome save<name> (1).html: delete the old download first. If Chrome asks to allow multiple downloads, the person clicks Allow.
3. Write STORYBOARD.md
HyperFrames' storyboard format. One frame per frozen screen:
---
message: Open an Orbit in under a minute
audience: YouTube creators deciding whether to try ShoutOrbit
journey: docs/journeys/create-an-orbit.md (steps 4–5, staging, 2026-11-13)
url: staging.shoutorbit.com
status: draft
---
## Frame 3: Open the Orbit (8-12s)
- duration: 4s
- snapshot: 03-open-the-orbit.html
- click: Open the Orbit
- scene: Dates and offer fill themselves in
The bottom of the form, dates already filled. The cursor presses "Open the Orbit".
Open question: the title field still shows its placeholder. Type it out on screen, or keep it?
click: the element's whole label, exactly: its visible text, value, placeholder or aria-label ("Place bid · $25", not "Place bid"). The build finds it on every run and fails loudly if it can't. Leave it out on a frame with no click.duration: seconds on screen, 3–5; 4s when left out.scene: the caption on screen: short, about what the viewer gets ("Dates fill themselves in"), not what they press.- Plan lines: what the frame shows and what the cursor does, in plain words.
- Open question: one line per thing the person must decide. Look at every frozen screen for: test or junk data on screen; personal data (emails, real names, account numbers); placeholders where real values belong; a caption that the screen contradicts ("live" over "Not taking bids yet"); stale dates. Raise them; never quietly fix them.
4. Build and hand over the board
node <skill>/scripts/demo.mjs serve docs/demos/<slug> --port 4321 # run in the background
It builds, then serves http://127.0.0.1:4321/. Open it for the person (Chrome or open) and stop.
On the board they can change captions, clicks, seconds, titles and order, remove frames, write
notes for Claude, retype words on the captured page itself (Edit page text; renaming the
clicked element carries the click along), and press Approve for render. Every save rebuilds
the stills.
5. Act on notes
When they say they've left notes, read STORYBOARD.md. For each > Note: line:
| They ask for | Change |
|---|---|
| Caption, timing, order, click | The frame's keys in STORYBOARD.md |
| Different words on the page | Text in that snapshots/*.html (text only: keep tags and classes) |
| A screen that wasn't captured, or a different state | Run that journey step again and freeze it |
| Something the flow can't show | Say so; offer the nearest true version |
Then turn the note into > Done: <what changed> (the board shows these as done), set
status: draft in the frontmatter (only the person approves), run
node <skill>/scripts/demo.mjs build docs/demos/<slug>, and tell them to refresh the board. The
server can keep running.
6. Render
Only when the frontmatter says status: approved, or the person says go in chat:
cd docs/demos/<slug> && npx hyperframes check && npx hyperframes render -o out/<slug>.mp4
check warnings about sub-compositions, track density and a silent film are expected. Open the MP4
for them. If the UI changes later, run the journey again, re-freeze the frames whose screens changed,
keep the storyboard, and render: that's the whole update.
Common mistakes
| Symptom | Cause and fix |
|---|---|
| Cursor lands below or beside its target | Frames captured at different window sizes, or the page was mid-scroll. Recapture at one size. |
nothing labelled "…" from the build | The click text doesn't match the screen exactly (case, spacing, a →). Copy it from the page. |
probe viewport was … | Headless Chrome's window chrome changed; adjust CHROME_UI in demo.mjs. |
| A frame shows an empty field | Frozen before typing. Fill, then freeze. |
| Images or fonts missing in a frame | Served cross-origin without CORS, so freeze.js kept the URL; they load only while that host is up. Note it as an open question. |
| Stripe or other iframes blank | freeze.js drops iframes. Freeze the screen before or after them, or say what's missing. |
Chrome not found | Set CHROME=/path/to/chrome. |
Privacy
A snapshot holds everything that was on screen, with images inlined. Before anyone shares the board, the folder or the video, check the frames for personal data, and raise what you find as open questions.