stageshow
Build faux product UI that looks like a real product, as a still illustration or an animated demo with a cursor. Browser and app windows, dashboards, phones, notifications, terminals, bento grids and cursor walkthroughs, framed, masked and cropped the way a marketing page, feature card or docs hero needs them. Use whenever someone wants a fake or mocked UI, a product demo or walkthrough animation, a hero or feature-card illustration of an app, a product shot in the style of a polished marketing site, or an animated cursor doing things in an interface, even if they never say "faux UI". React and plain CSS.
Install
npx skills add https://github.com/benjitaylor/skills --skill stageshowRehearse
A dress rehearsal: run the whole demo at real speed and watch it as the audience will, before anyone does. Read every image you make, and confirm the page you filmed runs the code you wrote. The rules the film lines enforce are in framing.md, craft.md and motion.md; this file is how to film and what to watch.
Prove the still before choreography
Render the opening and the payoff at both shipping widths beside the gold at shown size; the reduced-motion poster is not the opening, and it has to work as the only frame (motion.md, Story). Ask the still questions in verify.md (Framing, Hierarchy and density), then:
- Can the viewer find the focal object and what changes before any branding or copy?
- Compare the quality (hierarchy, density, spacing, crop, material) against the gold, separately from whether every object in the brief is present.
- Do the same for the busiest mid-story frame and every resting state that changed. A clean tool report does not close a composition question.
The host's own controls
A demo lives in a page with a picker, playback buttons and labels, and those get judged too. scripts/layout.mjs checks ordinary UI outside a Stage, including a bento tile's inner crop (nested-composition-crop):
Run it on the viewer page, the direct scene route and the real destination page at its real width. Moving geometry is reported as incomplete, not passed. Watch each control at rest, hover in and out, pressed, focused, label changed and interrupted mid-transition; geometry cannot vouch for text rendering, focus styling or a filtered silhouette.
Film
scripts/film.mjs opens a page in headless Chrome and shoots the stage at a fixed interval into one contact sheet at true size. It prints console and [stageshow] warnings (loop seams, missing targets); --help lists every flag.
First time: copy scripts/ beside kit/, npm i -D playwright-core (and postcss for the CSS check); CHROME_PATH picks another Chrome. The kit itself needs neither.
In the playground, film the direct ?capture=<name> route (the ordinary viewer wraps the scene in an iframe); on a site, the real page URL (open an embedded frame's URL directly when needed). Still watch playback, pause, resume and Replay in the viewer yourself.
- Selection and marks. Film discovers every visible
.ss-stage(hidden compact or wide twins skipped);--selectnarrows;--atand--zoomneed one stage. Callact.mark('drop')at a beat and film with--at drop; never guess delays. - Close-ups.
--zoom x,y,w,his in stage design pixels at high resolution;--zoom target:<name>[,pad]follows adata-targetevery frame. Stills wait for a rollingCountto land. - Hover stages.
--hoverfilms rest, hover and the settle after leaving; the rest frame must stand as a still and leaving must glide back without a jump. - Looks and widths.
--darkfilms the dark theme;--width 566for a 550 column;--slow 6 --every 40samples a landing or press closely (stage and CSS both slow). - Jumps. Film records geometry every animation frame and reports
jump:anddrift:candidates at their timestamps. Fix accidental ones; mark an intentional step narrowly withdata-snap.node scripts/jumps.mjs "<url>/?capture=<name>" --ms 30000 --report shots/jumps.jsonruns a longer scan without screenshot overhead. - Coverage. Empty, short or gapped recordings (gaps over 1.6x the typical interval, intervals over 40ms) are
inconclusive, exit 2. Camera omissions and same-box text changes are listed for manual review. - Reports.
--report <path>writes JSON (statusandexitCode, with each stage'scoverage,findingsandmanualunderstages);--strictexits 1 on candidates;no-issues-detectedis not approval; exit 3 on persistent dev reloads. - Class clashes.
node scripts/class-clash.mjs <demo folders>(needs postcss) checks overlapping class namespaces. A scoped.rowis fine, an unscoped one is not; complex roots are inconclusive. Check repeated demos together on the host too.
Lines that are never intentional
Not candidates for judgment: fix the cause and re-shoot. film.mjs --gate ends rehearsal passed only when all are at 0. Clearing a line by hiding, removing or marking (data-crop, data-snap) the thing it points at is a bug, and the fresh eye re-judges every line you call intentional.
A [stageshow] selection drift kit warning (a text selection that isn't under the I-beam) fails the gate too.
Other film lines
Candidates, each named beside its rule. In craft.md: touching (text within 5px of a box) and tight lines (bars closer than the text they stand for), lopsided, uneven, near-tangent, half-pixel glyph, squeezed. In motion.md: covers, dashed edge resizes, handoff shift, settle snap, fly size, hard edge, appears, then moves, near-empty frames, lands on a transform, steps in its box. Defined only here:
clipped:is a mark sliced by a container edge inside the stage; fix it, or mark the containerdata-croponly if the crop is deliberate.unclipped:means the page decides the crop: give the stageoverflow: hidden.double cornermeans two rounded clips share an edge with different radii: give one the corner.appears with no entranceandvanishes with no exitcan be missed in a sampling gap; film each appear and disappear at 50ms. Instant typing feedback may be intentional:data-typingordata-snapon the exact changing node, never a whole field or subtree.shrunk, not laid out for the phone: drawn at 0.72means the type will read small at that width: give it acompact.grown past the design sizemeans it reads large;small text: X renders at Npxis text under 10px, checked at widths under 600.- A pen tip intentionally leading a draw carries
data-lead.
What to watch
- Framing first. Film the still and clear every
framing:andcut by the crop:line before motion work; after moving anything, re-shoot the whole loop. - Every frame, at the real width, including the figure in view before it has loaded.
- Measure, don't eyeball. Compare computed styles across the page (one shadow, plate, radius set); sample a morph every animation frame, not from a sheet.
- The loop, end to end. Film one full loop plus the start of the next; investigate every
loop seamwarning. - Text and glyphs. Inspect pixels at
--slow 4after a move; a compositing layer is a possible fix, not proof the pixels stayed still. Nothing spills out of its box on any frame. - The whole drag, hover to settle at speed, then stepped: the surface never lifts before the press; track the hand's hotspot through every transform. A temporary zero-size child at the grip point makes the browser project every transform (subtract the parent border); a hand-written matrix can miss separate
translate,rotateorscale. - Overlays land. Highlights, selections and markers sit on the thing they describe at every scale; check a drag box against the items it claims.
- Crops explain themselves. Every edge where screen meets stage fades, bleeds with intent, or is a real window edge.
- Edges and corners. Zoom into hover fills and selected rows: rounded corners intact under a parent's
overflow: hidden. - The narrowest shipping host. Film the whole loop at 390 and at the brief's own width when it names one, gutters included, read as closely as the wide pass.
- Every look shipped, filmed separately.
- Check the host. The demo works in the real page at every shipping width (plate, ground, padding, crop). Page backgrounds stay in the host; a component brings no full-page wrapper.
- Reduced motion.
--reducedshows the poster, still and complete; overlays fromrect()sit right in it; toggle the preference during playback and back. Drop presentation waits; keep task timing and reading time. - Live pacing. The virtual clock proves geometry, not frame rate: log rAF timestamps against the transform on the real page; repeated poses then a jump mean a clock bug. Pause, offscreen, hidden tab and finished
oncescenes stop their work; resume and replay never skip a beat or replay an accumulation. Say which browser and device you checked. - Typecheck. Run the host's typecheck (
npm run typecheckfrom the skill folder); report unrelated failures separately, never filter them into a pass. - Blueprint grid. Lines sit at
--ss-grid-xand--ss-grid-y; set them on the Stage so a window's edge lands on a grid line. - What the tools can't sign off. A contact sheet misses a one-frame flash; a scan does not know whether a morph, a story or a gesture is right. Watch playback at speed, slowed transitions, loop plus next start, an interruption, reduced motion, a return from offscreen; again after the final edit. The playground isolates each example in an iframe; Pause and Step advance one clock; Phone changes width but not touch, so check touch on a device or with input emulation.
Then put your sheet beside the closest gold's: is this instantly a real product, quiet, with one clear story? Iterate until yes.
The last pass
verify.md from frames; --gate passing on the wide loop, the phone loop and --states; a fresh-eye review (review.md) returning nothing; REHEARSAL.md written beside the component. It lists the widths, looks, interactions and stages captured and the cases not. An empty, late or gapped capture is inconclusive, not a pass.