Browser recorder
Records whole-browser-window demo clips of a live site at 60 fps, one take per browser and theme, with the site’s own shadow drawn around the window. Browser recorder is one of the Claude Code skills I wrote for building this site, with the site-specific values turned into placeholders and examples so it works on any project. The skill is the SKILL.md below, exactly as it ships in the zip.
Install
unzip browser-recorder.zip -d ~/.claude/skillsThat installs it for every project. For one project, unzip into that project’s .claude/skills/ instead. Either way, Claude Code reads the folder on its next session and applies the skill when a request matches its description:
Record whole-browser-window demo clips of a live site at 60 fps, one take per browser and theme (Chrome and Safari, light and dark), so a visitor sees their own browser. Drives the page over CDP or AppleScript, records with screencapture, clips the window with a vector mask onto a drawn ground carrying the site's own shadow, and encodes for the page. Use when a page needs a video of the site in use and a drawn toolbar or a pasted bitmap would not do.
Inside the zip
- —
browser-recorder/SKILL.md· 6 KB - —
browser-recorder/scripts/encode.sh· 3 KB - —
browser-recorder/scripts/plate.py· 3 KB
The skill
A demo clip of a website is most convincing when it shows a real browser window: the true toolbar, the true corners, a real cursor, and the page at the size it is read at. A drawn CSS toolbar or a screenshot pasted over a video reads as a fake. This skill records real windows and serves the visitor the one that matches their browser and theme.
Shape of the output
- One clip per browser and theme:
<clip>-chrome.mp4,-chrome-dark,-safari,-safari-dark, each with a.jpgposter. The page picks by user agent and colour scheme. - The frame holds the window plus a margin for its shadow (56 pt each side, 36 above, 80 below, measured from a real macOS shadow), scaled so the window itself lands at the width the page displays it. Encode at 60 fps.
- The window’s corners and shadow in the file are drawn, not captured: the macOS shadow and the window’s outermost anti-aliased pixels are discarded, the content is clipped with a rounded-rectangle mask inset three pixels (so the clip edge blends flat content, never the window’s own one-pixel rim), and laid on a ground of the page colour carrying the site’s own CSS shadow at the size the window displays. Video has no alpha, so this is the only way the clip’s shadow can match the still images around it.
- On the page, the clip is wider than its column by the margin ratio and offset by the side margin, so the window fills the column and the shadow bleeds into the gutters.
The rig
Four pieces, all in scripts/ of this skill or described below.
- Stage. Hide the Dock (
osascript: set the Dock’s autohide), park the pointer off frame and away from every screen edge (a bottom edge reveals the Dock and its tooltips reach into the frame), and raise a backdrop window painted the page colour behind the browser: a Chrome app window (--app=data:text/html,...) on its own remote-debugging port, sized to the whole screen. The backdrop hides the desktop in the margin. - Browser. Chrome: a clean profile per take (
--user-data-dir,--no-first-run,--disable-extensions,--force-dark-modefor the dark takes), a window sized so the page area is 1200 by 720, driven over CDP on a remote-debugging port. Safari: driven with AppleScriptdo JavaScript(Develop menu: Allow JavaScript from Apple Events), other windows minimised first, and macOS itself switched to dark appearance for dark takes (Safari has no per-window dark mode). Measure the window’s outer size from the page (outerHeight,screenY) so the recording region is exact. - Record.
screencapture -x -v -V <seconds> -R <x,y,w,h> out.movfor the window plus margins. It writes a variable-frame-rate file with only changed frames. - Encode.
ffmpeg: pad the tail, resample to 60 fps, trim to the motion, scale to the frame width, clip with the mask and composite on the ground (scripts/plate.pydraws both), H.264 high profile,-movflags +faststart. A first-frame poster from the same frame.
Driving the page
Script the sequence from inside the page wherever possible. Keyboard shortcuts should be
dispatched as KeyboardEvents on window from a page script, not sent as OS keystrokes:
a scripted OS keystroke in Safari lands hundreds of milliseconds after the call, and a
palette that opens late reads as a slow site. Typing goes through the input’s value setter
plus an input event, one character every 140 ms. Scrolling uses two eased flicks, not a
single jump, so it reads as a hand on a trackpad.
Overlays the viewer should notice (which keys were pressed) are drawn by the page itself:
inject a popover element once, before recording, so its first paint is not on camera;
have the page watch for the state it announces (the palette’s dialog) and time the fade
from that, because a fade timed from the driver races the page.
Lessons that cost a take each
- Warm everything first. Open the palette once and close it so its chunk and index are cached. Open the lightbox and step one photo past the last one the take will show, then close it: each slide decodes the photo entering the track’s next slot, and a cold photo stutters the slide before it.
- Ignore the capture’s first second when finding the first motion. screencapture’s opening frames alternate between two states, a constant frame difference that passes for motion and leaves a second of stillness before the real first move.
- Trim to motion. Find the first and last changed frames on a 160 px wide difference (loud threshold for the first move, a quiet one for the eased tail), start six frames before the first, hold the last frame briefly, and cap the length.
- Hands off the machine. A window raised during a take (a terminal you typed into) is
in the recording. Run batches under
caffeinate -dims. - Safari’s progress bar stays full long after load; a
history.pushStateclears it. - Safari’s window height rounds differently from Chrome’s; compute the frame height the
way ffmpeg’s
scale=W:-2does (nearest even) or the overlay refuses the mismatch. - The clean Chrome profile must not pin a colour scheme; let
--force-dark-modeand the page decide. - Corner cruft comes from painting around the window instead of clipping it. Clip.
Verify before installing
Extract frame grids with ffmpeg (select and tile) at the moments that matter: the
shortcut and the thing it opens must appear in the same frame, the landing page must appear
a few frames after Enter. Measure the cadence of motion runs with a frame difference: a
healthy 60 fps capture has few duplicate frames inside a run; a stutter shows as a
three- or four-frame gap between unique frames. Then install the eight files and regenerate
whatever manifest the page’s video component reads for dimensions.