Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions plugins/hyperframes/skills/hyperframes-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: hyperframes-cli
description: HyperFrames CLI tool — hyperframes init, lint, preview, render, transcribe, tts, doctor, browser, info, upgrade, compositions, docs, benchmark. Use when scaffolding a project, linting or validating compositions, previewing in the studio, rendering to video, transcribing audio, generating TTS, or troubleshooting the HyperFrames environment.
description: HyperFrames CLI tool — hyperframes init, lint, inspect, preview, render, transcribe, tts, doctor, browser, info, upgrade, compositions, docs, benchmark. Use when scaffolding a project, linting, validating, inspecting visual layout in compositions, previewing in the studio, rendering to video, transcribing audio, generating TTS, or troubleshooting the HyperFrames environment.
---

# HyperFrames CLI
Expand All @@ -12,10 +12,11 @@ Everything runs through `npx hyperframes`. Requires Node.js >= 22 and FFmpeg.
1. **Scaffold** — `npx hyperframes init my-video`
2. **Write** — author HTML composition (see the `hyperframes` skill)
3. **Lint** — `npx hyperframes lint`
4. **Preview** — `npx hyperframes preview`
5. **Render** — `npx hyperframes render`
4. **Visual inspect** — `npx hyperframes inspect`
5. **Preview** — `npx hyperframes preview`
6. **Render** — `npx hyperframes render`

Lint before preview — catches missing `data-composition-id`, overlapping tracks, unregistered timelines.
Lint and inspect before preview. `lint` catches missing `data-composition-id`, overlapping tracks, and unregistered timelines. `inspect` opens the rendered composition in headless Chrome, seeks through the timeline, and reports text spilling out of bubbles/containers or off the canvas.

## Scaffolding

Expand All @@ -42,6 +43,27 @@ npx hyperframes lint --json # machine-readable

Lints `index.html` and all files in `compositions/`. Reports errors (must fix), warnings (should fix), and info (with `--verbose`).

## Visual Inspect

```bash
npx hyperframes inspect # inspect rendered layout over the timeline
npx hyperframes inspect ./my-project # specific project
npx hyperframes inspect --json # agent-readable findings
npx hyperframes inspect --samples 15 # denser timeline sweep
npx hyperframes inspect --at 1.5,4,7.25 # explicit hero-frame timestamps
```

Use this after `lint` and `validate`, especially for compositions with speech bubbles, cards, captions, or tight typography. It reports:

- Text extending outside the nearest visual container or bubble
- Text clipped by its own fixed-width/fixed-height box
- Text extending outside the composition canvas
- Children escaping clipping containers

Errors should be fixed before rendering. Warnings are surfaced for agent review; add `--strict` to fail on warnings too. Repeated static issues are collapsed by default so JSON output stays compact for LLM context windows. If overflow is intentional for an entrance/exit animation, mark the element or ancestor with `data-layout-allow-overflow`. If a decorative element should never be audited, mark it with `data-layout-ignore`.

`npx hyperframes layout` remains available as a compatibility alias for the same visual inspection pass.

## Previewing

```bash
Expand All @@ -51,6 +73,20 @@ npx hyperframes preview --port 4567 # custom port (default 3002)

Hot-reloads on file changes. Opens the studio in your browser automatically.

When handing a project back to the user, use the Studio project URL, not the
source `index.html` path:

```text
http://localhost:<port>/#project/<project-name>
```

Use the actual port from the preview output and the project directory name. For
example, after `npx hyperframes preview --port 3017` in `codex-openai-video`,
report `http://localhost:3017/#project/codex-openai-video`.

Treat `index.html` as source-code context only. It is fine to link it as an
implementation file, but do not label it as the project or preview surface.

## Rendering

```bash
Expand Down
17 changes: 17 additions & 0 deletions plugins/hyperframes/skills/hyperframes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,28 @@ When no `visual-style.md` or animation direction is provided, follow [house-styl
## Output Checklist

- [ ] `npx hyperframes lint` and `npx hyperframes validate` both pass
- [ ] `npx hyperframes inspect` passes, or every reported overflow is intentionally marked
- [ ] Contrast warnings addressed (see Quality Checks below)
- [ ] Layout issues addressed (see Quality Checks below)
- [ ] Animation choreography verified (see Quality Checks below)

## Quality Checks

### Visual Inspect

`hyperframes inspect` runs the composition in headless Chrome, seeks through the timeline, and maps visual layout issues with timestamps, selectors, bounding boxes, and fix hints. Run it after `lint` and `validate`:

```bash
npx hyperframes inspect
npx hyperframes inspect --json
```

Failures usually mean text is spilling out of a bubble/card, a fixed-size label is clipping dynamic copy, or text has moved off the canvas. Fix by increasing container size or padding, reducing font size or letter spacing, adding a real `max-width` so text wraps inside the container, or using `window.__hyperframes.fitTextFontSize(...)` for dynamic copy.

Use `--samples 15` for dense videos and `--at 1.5,4,7.25` for specific hero frames. Repeated static issues are collapsed by default to avoid flooding agent context. If overflow is intentional for an entrance/exit animation, mark the element or ancestor with `data-layout-allow-overflow`. If a decorative element should never be audited, mark it with `data-layout-ignore`.

`hyperframes layout` is the compatibility alias for the same check.

### Contrast

`hyperframes validate` runs a WCAG contrast audit by default. It seeks to 5 timestamps, screenshots the page, samples background pixels behind every text element, and computes contrast ratios. Failures appear as warnings:
Expand Down
7 changes: 5 additions & 2 deletions plugins/hyperframes/skills/website-to-hyperframes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ Build each composition following the storyboard. After each one: self-review for

**Read:** [references/step-7-validate.md](references/step-7-validate.md)

Lint, validate, snapshot, preview. Deliver the preview to the user first — only render to MP4 on explicit request.
Lint, validate, snapshot, preview. Deliver the localhost Studio project URL
(`http://localhost:<port>/#project/<project-name>`) to the user first — only
render to MP4 on explicit request. Do not treat `index.html` as the project
handoff link; it is source-code context only.

**Gate:** `npx hyperframes lint` and `npx hyperframes validate` pass with zero errors.
**Gate:** `npx hyperframes lint` and `npx hyperframes validate` pass with zero errors, and the final response includes the active Studio project URL.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ After building the composition, check WITH ACTUAL CODE:
- [ ] No full-screen dark linear gradients (H.264 creates visible banding — use solid + localized radial glows)
- [ ] Timeline registered: `window.__timelines["comp-id"] = tl`
- [ ] Colors match DESIGN.md exactly (paste the HEX value, don't approximate)
- [ ] **Every `<template>` root element** — not just `index.html`, but every sub-composition's root — has `data-start="0"` **and** `data-duration="<beat_seconds>"`. The linter warns `root_composition_missing_data_start` / `root_composition_missing_data_duration` when missing; without `data-duration` the runtime may infer `Infinity` on repeating animations and stall playback.
- [ ] **Every `<template>` root element** — not just `index.html`, but every sub-composition's root — has `data-start="0"`. The linter warns `root_composition_missing_data_start` when missing. Authoring `data-duration="<beat_seconds>"` on the root is also recommended for compositions whose GSAP timeline uses repeating animations (`repeat: -1` or large `repeat: N`); without it the runtime may infer `Infinity` and stall playback. The linter flags those repeating shapes directly via `gsap_infinite_repeat` and `gsap_repeat_ceil_overshoot`.
- [ ] **Caption exits have a hard kill.** If you animate captions out with `tl.to(groupEl, { opacity: 0 }, group.end)`, follow it with `tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end)` as a deterministic kill — per-word karaoke tweens can override the exit tween and leave captions stuck on screen. Linter: `caption_exit_missing_hard_kill`.
- [ ] **No duplicate media nodes.** If the same image/video source is referenced twice with identical `data-start` + `data-duration`, the compiler discovers it twice and can double-render. Dedupe by using a single `<img>` with appropriate z-layering, or stagger the `data-start` values. Linter: `duplicate_media_discovery_risk`.

Expand Down Expand Up @@ -164,3 +164,66 @@ These exist because the capture engine is deterministic. Violations produce brok
- **Never use ANY CSS `transform` for centering** — not `translate(-50%, -50%)`, not `translateX(-50%)`, not `translateY(-50%)`. GSAP animates the `transform` property, which overwrites ALL CSS transforms including centering. The element flies offscreen. Use flexbox centering instead: `display:flex; align-items:center; justify-content:center` on a wrapper div. The linter catches this (`gsap_css_transform_conflict`) but only if you run it.
- **Minimum font sizes**: 20px body, 16px labels
- **No full-screen dark linear gradients** — H.264 banding

---

## Load-bearing rules for animation authoring

Rules below came out of two independent website-to-hyperframes builds (2026-04-20) where compositions lint-clean and still ship broken — elements that never appear, ambient motion that doesn't scrub, entrance tweens that silently kill their target. The linter cannot catch these; the rules must be followed by the author.

- **No iframes for captured content.** Iframes do not seek deterministically with the timeline — the capture engine cannot scrub inside them, so they appear frozen (or blank) in the rendered output. If the source you're stylizing is a live web app, use the screenshots from `capture/` as stacked panels or layered images, not live embeds.

- **Never stack two transform tweens on the same element.** A common failure: a `y` entrance plus a `scale` Ken Burns on the same `<img>`. The second tween's `immediateRender: true` writes the element's initial state at construction time, overwriting whatever the first tween set — leaving the element invisible or offscreen with no lint warning. A secondary mechanism: `tl.from()` resets to its declared "from" state when the playhead is seeked past the timeline's end, so an element that looked correct in linear playback vanishes in the capture engine's non-linear seek. Fix one of two ways:

```html
<!-- BAD: two transforms on one element -->
<img class="hero" src="..." />
<script>
tl.from(".hero", { y: 50, opacity: 0, duration: 0.6 }, 0);
tl.to(".hero", { scale: 1.04, duration: beat }, 0); // kills the entrance
</script>

<!-- GOOD option A: combine into one tween -->
<script>
tl.fromTo(
".hero",
{ y: 50, opacity: 0, scale: 1.0 },
{ y: 0, opacity: 1, scale: 1.04, duration: beat, ease: "none" },
0,
);
</script>

<!-- GOOD option B: split across parent + child -->
<div class="hero-wrap"><img class="hero" src="..." /></div>
<script>
tl.from(".hero-wrap", { y: 50, opacity: 0, duration: 0.6 }, 0); // entrance on parent
tl.to(".hero", { scale: 1.04, duration: beat }, 0); // Ken Burns on child
</script>
```

- **Prefer `tl.fromTo()` over `tl.from()` inside `.clip` scenes.** `gsap.from()` sets `immediateRender: true` by default, which writes the "from" state at timeline construction — before the `.clip` scene's `data-start` is active. Elements can flash visible, start from the wrong position, or skip their entrance entirely when the scene is seeked non-linearly (which the capture engine does). Explicit `fromTo` makes the state at every timeline position deterministic:

```js
// BRITTLE: immediateRender interacts badly with scene boundaries
tl.from(el, { opacity: 0, y: 50, duration: 0.6 }, t);

// DETERMINISTIC: state is defined at both ends, no immediateRender surprise
tl.fromTo(el, { opacity: 0, y: 50 }, { opacity: 1, y: 0, duration: 0.6 }, t);
```

- **Ambient pulses must attach to the seekable `tl`, never bare `gsap.to()`.** Auras, shimmers, gentle float loops, logo breathing — all of these must be added to the scene's timeline, not fired standalone. Standalone tweens run on wallclock time and do not scrub with the capture engine, so the effect is absent in the rendered video even though it looks correct in the studio preview:

```js
// BAD: lives outside the timeline, never renders in capture
gsap.to(".aura", { scale: 1.08, yoyo: true, repeat: 5, duration: 1.2 });

// GOOD: seekable, deterministic, renders
tl.to(".aura", { scale: 1.08, yoyo: true, repeat: 5, duration: 1.2 }, 0);
```

- **Hard-kill every scene boundary, not just captions.** The caption hard-kill rule above generalizes: any element whose visibility changes at a beat boundary needs a deterministic `tl.set()` kill after its fade, because later tweens on the same element (or `immediateRender` from a sibling tween) can resurrect it. Apply to every element with an exit animation:

```js
tl.to(el, { opacity: 0, duration: 0.3 }, beatEnd);
tl.set(el, { opacity: 0, visibility: "hidden" }, beatEnd + 0.3); // deterministic kill
```
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,32 @@ npx hyperframes preview

Open the studio in a browser. Scrub through every beat.

### Handoff URL

The Studio URL is the project handoff surface. In the final response, report the
active preview URL with the project hash:

```text
http://localhost:<port>/#project/<project-name>
```

Use the actual port selected by `hyperframes preview` and the project name shown
by the preview command. If you run `hyperframes preview --port 3017` for a
project directory named `codex-openai-video`, the project URL is:

```text
http://localhost:3017/#project/codex-openai-video
```

Do **not** present `index.html` as the project link. `index.html` is the source
file for agents and editors; the user-facing project is the running HyperFrames
Studio preview. You may include source file links as secondary context, but the
primary "Project" or "Preview" line must be the localhost Studio URL.

If a render was also requested, still include the Studio URL first so the user
can scrub and inspect the project. Include the MP4 path as the rendered output,
not as a replacement for the project URL.

## Render (on-demand only)

**Do NOT render automatically as part of the pipeline.** Preview is the delivery — the user scrubs, spots anything they want tweaked, and you iterate. Rendering to MP4 takes minutes of wall-clock time per pass and is wasted work if the user wants changes.
Expand Down