From be7e8da8269240973c5b3d97140e2694242e07a7 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 7 Jul 2026 10:04:20 -0700 Subject: [PATCH] Update HyperFrames plugin skills to 0.7.40 --- plugins/hyperframes/.codex-plugin/plugin.json | 2 +- .../skills/embedded-captions/SKILL.md | 12 +- .../modes/standard/_anatomy.md | 2 +- .../embedded-captions/references/rail.md | 2 +- .../embedded-captions/scripts/transcribe.cjs | 2 +- .../skills/faceless-explainer/SKILL.md | 8 +- .../references/motion-language.md | 4 +- .../faceless-explainer/scripts/audio.mjs | 20 +- .../scripts/lib/pad-frame-duration.mjs | 36 +++ .../scripts/transitions.mjs | 7 + plugins/hyperframes/skills/figma/SKILL.md | 122 ++++++++ .../hyperframes/skills/general-video/SKILL.md | 43 ++- .../skills/hyperframes-animation/SKILL.md | 2 +- .../adapters/gsap-easing-and-stagger.md | 127 ++++++-- .../adapters/gsap-timeline-and-labels.md | 2 +- .../rules/asr-keyword-glow.md | 4 +- .../rules/spring-pop-entrance.md | 3 +- .../scripts/animation-map.mjs | 3 +- .../scripts/package-loader.test.mjs | 2 +- .../skills/hyperframes-cli/SKILL.md | 4 +- .../skills/hyperframes-cli/agents/openai.yaml | 3 - .../references/init-and-scaffold.md | 2 +- .../references/upgrade-info-misc.md | 2 +- .../skills/hyperframes-core/SKILL.md | 5 +- .../references/script-format.md | 4 +- .../references/variables-and-media.md | 2 +- .../{claude => code-editorial}/FRAME.md | 10 +- .../caption-skin.html | 8 +- .../frame-showcase.html | 4 +- .../references/design-spec.md | 2 +- .../scripts/contrast-report.mjs | 3 +- .../scripts/package-loader.test.mjs | 2 +- .../skills/hyperframes-keyframes/SKILL.md | 2 +- .../skills/hyperframes-media/SKILL.md | 97 ------ .../hyperframes-registry/agents/openai.yaml | 3 - .../hyperframes/skills/hyperframes/SKILL.md | 42 +-- .../skills/hyperframes/agents/openai.yaml | 3 - plugins/hyperframes/skills/media-use/SKILL.md | 159 ++++++++-- .../audio}/assets/sfx/CREDITS.md | 0 .../audio}/assets/sfx/chime.mp3 | Bin .../audio}/assets/sfx/click-soft.mp3 | Bin .../audio}/assets/sfx/click.mp3 | Bin .../audio}/assets/sfx/error.mp3 | Bin .../audio}/assets/sfx/glitch-1.mp3 | Bin .../audio}/assets/sfx/glitch-2.mp3 | Bin .../audio}/assets/sfx/glitch-3.mp3 | Bin .../audio}/assets/sfx/impact-bass-1.mp3 | Bin .../audio}/assets/sfx/impact-bass-2.mp3 | Bin .../audio}/assets/sfx/key-press.mp3 | Bin .../audio}/assets/sfx/manifest.json | 0 .../audio}/assets/sfx/notification.mp3 | Bin .../audio}/assets/sfx/ping.mp3 | Bin .../audio}/assets/sfx/pop.mp3 | Bin .../audio}/assets/sfx/riser.mp3 | Bin .../audio}/assets/sfx/sparkle.mp3 | Bin .../audio}/assets/sfx/typing.mp3 | Bin .../audio}/assets/sfx/whoosh-cinematic.mp3 | Bin .../audio}/assets/sfx/whoosh-short.mp3 | Bin .../audio}/assets/sfx/whoosh.mp3 | Bin .../audio}/references/bgm.md | 0 .../audio}/references/captions/authoring.md | 0 .../audio}/references/captions/motion.md | 0 .../captions/transcript-handling.md | 0 .../audio}/references/remove-background.md | 0 .../audio}/references/requirements.md | 0 .../audio}/references/sfx.md | 0 .../audio}/references/transcribe.md | 0 .../audio}/references/tts-to-captions.md | 0 .../audio}/references/tts.md | 6 +- .../audio}/scripts/audio.mjs | 15 +- .../media-use/audio/scripts/audio.test.mjs | 49 +++ .../audio}/scripts/heygen-tts.mjs | 0 .../audio}/scripts/lib/bgm.mjs | 31 +- .../audio/scripts/lib/concurrency.mjs | 14 + .../audio/scripts/lib/concurrency.test.mjs | 41 +++ .../audio}/scripts/lib/heygen.mjs | 5 +- .../media-use/audio/scripts/lib/python.mjs | 63 ++++ .../audio/scripts/lib/python.test.mjs | 68 +++++ .../audio}/scripts/lib/sfx.mjs | 17 +- .../media-use/audio/scripts/lib/sfx.test.mjs | 69 +++++ .../audio}/scripts/lib/tts.mjs | 124 +++++++- .../audio/scripts/lib/tts.spawn.test.mjs | 143 +++++++++ .../media-use/audio/scripts/lib/tts.test.mjs | 66 +++++ .../audio}/scripts/lyria-recipe.py | 0 .../audio}/scripts/wait-bgm.mjs | 0 .../skills/media-use/references/operations.md | 226 ++++++++++++++ .../skills/media-use/scripts/audio-duck.mjs | 121 ++++++++ .../skills/media-use/scripts/lib/cache.mjs | 13 +- .../media-use/scripts/lib/codex-provider.mjs | 133 +++++++++ .../media-use/scripts/lib/coverage.test.mjs | 112 +++++++ .../skills/media-use/scripts/lib/cutlist.mjs | 184 ++++++++++++ .../media-use/scripts/lib/cutlist.test.mjs | 148 ++++++++++ .../skills/media-use/scripts/lib/duck.mjs | 89 ++++++ .../media-use/scripts/lib/duck.test.mjs | 118 ++++++++ .../skills/media-use/scripts/lib/freeze.mjs | 60 +++- .../media-use/scripts/lib/freeze.test.mjs | 46 +++ .../media-use/scripts/lib/local-models.mjs | 279 ++++++++++++++++++ .../scripts/lib/local-models.test.mjs | 154 ++++++++++ .../media-use/scripts/lib/local-run.mjs | 64 ++++ .../media-use/scripts/lib/local-run.test.mjs | 54 ++++ .../media-use/scripts/lib/mflux-provider.mjs | 95 ++++++ .../media-use/scripts/lib/parakeet-words.mjs | 27 ++ .../scripts/lib/parakeet-words.test.mjs | 44 +++ .../media-use/scripts/lib/providers.mjs | 34 +-- .../skills/media-use/scripts/lib/registry.mjs | 119 ++++++++ .../media-use/scripts/lib/registry.test.mjs | 159 ++++++++++ .../skills/media-use/scripts/lib/search.mjs | 24 ++ .../media-use/scripts/lib/search.test.mjs | 32 ++ .../skills/media-use/scripts/lib/specs.mjs | 81 +++++ .../media-use/scripts/lib/specs.test.mjs | 75 +++++ .../media-use/scripts/lib/telemetry.mjs | 74 +++++ .../media-use/scripts/lib/telemetry.test.mjs | 35 +++ .../scripts/lib/tts-local-provider.mjs | 64 ++++ .../skills/media-use/scripts/lib/usage.mjs | 57 ++++ .../media-use/scripts/lib/usage.test.mjs | 54 ++++ .../media-use/scripts/lib/voice-provider.mjs | 67 +++++ .../skills/media-use/scripts/lib/words.mjs | 18 ++ .../skills/media-use/scripts/resolve.mjs | 138 +++++++-- .../skills/media-use/scripts/resolve.test.mjs | 34 +++ .../skills/media-use/scripts/transcribe.mjs | 172 +++++++++++ .../media-use/scripts/transcript-cut.mjs | 238 +++++++++++++++ .../skills/motion-graphics/SKILL.md | 34 +-- .../motion-graphics/phases/source/guide.md | 2 +- .../skills/music-to-video/SKILL.md | 6 +- .../hyperframes/skills/pr-to-video/SKILL.md | 30 +- .../pr-to-video/references/code-vocabulary.md | 8 +- .../pr-to-video/references/motion-language.md | 4 +- .../pr-to-video/references/story-design.md | 12 +- .../pr-to-video/references/visual-design.md | 16 +- .../skills/pr-to-video/scripts/audio.mjs | 20 +- .../skills/pr-to-video/scripts/ingest.mjs | 4 +- .../scripts/lib/pad-frame-duration.mjs | 36 +++ .../pr-to-video/scripts/transitions.mjs | 7 + .../pr-to-video/sub-agents/frame-worker.md | 6 +- .../skills/product-launch-video/SKILL.md | 8 +- .../references/cut-catalog.md | 2 +- .../references/motion-language.md | 4 +- .../product-launch-video/scripts/audio.mjs | 4 +- .../scripts/lib/pad-frame-duration.mjs | 36 +++ .../scripts/lib/pad-frame-duration.test.mjs | 76 +++++ .../scripts/transitions.mjs | 7 + .../skills/remotion-to-hyperframes/SKILL.md | 2 +- plugins/hyperframes/skills/slideshow/SKILL.md | 23 +- .../skills/talking-head-recut/SKILL.md | 2 +- .../skills/website-to-video/SKILL.md | 19 +- .../references/capabilities.md | 4 +- .../references/step-3-storyboard.md | 6 +- 147 files changed, 4843 insertions(+), 448 deletions(-) create mode 100644 plugins/hyperframes/skills/faceless-explainer/scripts/lib/pad-frame-duration.mjs create mode 100644 plugins/hyperframes/skills/figma/SKILL.md delete mode 100644 plugins/hyperframes/skills/hyperframes-cli/agents/openai.yaml rename plugins/hyperframes/skills/hyperframes-creative/frame-presets/{claude => code-editorial}/FRAME.md (97%) rename plugins/hyperframes/skills/hyperframes-creative/frame-presets/{claude => code-editorial}/caption-skin.html (97%) rename plugins/hyperframes/skills/hyperframes-creative/frame-presets/{claude => code-editorial}/frame-showcase.html (99%) delete mode 100644 plugins/hyperframes/skills/hyperframes-media/SKILL.md delete mode 100644 plugins/hyperframes/skills/hyperframes-registry/agents/openai.yaml delete mode 100644 plugins/hyperframes/skills/hyperframes/agents/openai.yaml rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/CREDITS.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/chime.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/click-soft.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/click.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/error.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/glitch-1.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/glitch-2.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/glitch-3.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/impact-bass-1.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/impact-bass-2.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/key-press.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/manifest.json (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/notification.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/ping.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/pop.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/riser.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/sparkle.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/typing.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/whoosh-cinematic.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/whoosh-short.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/assets/sfx/whoosh.mp3 (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/bgm.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/captions/authoring.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/captions/motion.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/captions/transcript-handling.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/remove-background.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/requirements.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/sfx.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/transcribe.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/tts-to-captions.md (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/references/tts.md (97%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/audio.mjs (93%) create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/audio.test.mjs rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/heygen-tts.mjs (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/lib/bgm.mjs (89%) create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/concurrency.mjs create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/concurrency.test.mjs rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/lib/heygen.mjs (95%) create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/python.mjs create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/python.test.mjs rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/lib/sfx.mjs (85%) create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/sfx.test.mjs rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/lib/tts.mjs (67%) create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/tts.spawn.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/audio/scripts/lib/tts.test.mjs rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/lyria-recipe.py (100%) rename plugins/hyperframes/skills/{hyperframes-media => media-use/audio}/scripts/wait-bgm.mjs (100%) create mode 100644 plugins/hyperframes/skills/media-use/references/operations.md create mode 100644 plugins/hyperframes/skills/media-use/scripts/audio-duck.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/codex-provider.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/coverage.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/cutlist.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/cutlist.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/duck.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/duck.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/freeze.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/local-models.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/local-models.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/local-run.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/local-run.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/mflux-provider.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/parakeet-words.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/parakeet-words.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/registry.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/registry.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/search.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/search.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/specs.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/specs.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/telemetry.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/telemetry.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/tts-local-provider.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/usage.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/usage.test.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/voice-provider.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/lib/words.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/transcribe.mjs create mode 100644 plugins/hyperframes/skills/media-use/scripts/transcript-cut.mjs create mode 100644 plugins/hyperframes/skills/pr-to-video/scripts/lib/pad-frame-duration.mjs create mode 100644 plugins/hyperframes/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs create mode 100644 plugins/hyperframes/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs diff --git a/plugins/hyperframes/.codex-plugin/plugin.json b/plugins/hyperframes/.codex-plugin/plugin.json index fd3b9087..2242188f 100644 --- a/plugins/hyperframes/.codex-plugin/plugin.json +++ b/plugins/hyperframes/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "hyperframes", - "version": "0.7.25", + "version": "0.7.40", "description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.", "author": { "name": "HeyGen", diff --git a/plugins/hyperframes/skills/embedded-captions/SKILL.md b/plugins/hyperframes/skills/embedded-captions/SKILL.md index e5394212..b2c69f53 100644 --- a/plugins/hyperframes/skills/embedded-captions/SKILL.md +++ b/plugins/hyperframes/skills/embedded-captions/SKILL.md @@ -1,13 +1,13 @@ --- name: embedded-captions -description: 'Add captions to a talking-head video. ONE catalog (CATALOG.md) of 31 visual identities behind two engines: column-flow (captions composited INTO the scene — matte occlusion + mix-blend; cream/ink/editorial/keynote/documentary/loud/neon/glitch/chrome/velocity) and themed constitutions (anchor/ordnance/terminal/neonsign/stardust/stomp/scoreboard/transit/vhs/arcade/dossier/laser/thunder/hologram/biolume/aurora/spectrum/papercut/popup/chalkboard/graffiti/brush/inkwater/ransom/lastpage — e.g. a glyph-decode climax, a neon sign WRITTEN stroke by stroke, or the quiet `anchor` rail default). Route by identity, never by mode. Trigger on "captions/subtitles", "embed/cinematic captions", "VFX captions", "炸/特效/酷炫字幕", a named identity, or top-tier motion-graphics asks. Embedding every word is wrong for most talking-head content — `anchor` is the verbatim default. Pipeline: transcription → hyperframes remove-background matting → HTML render → ffmpeg overlay. Requires hyperframes and a single-subject clip.' +description: 'Add captions to a talking-head video. ONE catalog (CATALOG.md) of 35 visual identities behind two engines: column-flow (captions composited INTO the scene — matte occlusion + mix-blend; cream/ink/editorial/keynote/documentary/loud/neon/glitch/chrome/velocity) and themed constitutions (anchor/ordnance/terminal/neonsign/stardust/stomp/scoreboard/transit/vhs/arcade/dossier/laser/thunder/hologram/biolume/aurora/spectrum/papercut/popup/chalkboard/graffiti/brush/inkwater/ransom/lastpage — e.g. a glyph-decode climax, a neon sign WRITTEN stroke by stroke, or the quiet `anchor` rail default). Route by identity, never by mode. Trigger on "captions/subtitles", "embed/cinematic captions", "VFX captions", "炸/特效/酷炫字幕", a named identity, or top-tier motion-graphics asks. Embedding every word is wrong for most talking-head content — `anchor` is the verbatim default. Runs locally end-to-end (transcribes and mattes the subject itself, no API key). Requires hyperframes and a single-subject clip (multi-shot clips are split per shot).' metadata: tags: captions, embedded-captions, occlusion, matting, talking-head, rembg-matting, whisper, ffmpeg, cinematic --- # Embedded Captions -**One catalog, picked up front** ([CATALOG.md](CATALOG.md) — 17 identities; the three engines behind it are backend detail). **Standard** (default) builds a clean verbatim **rail** (lower-third subtitle carrying most text) + an **embed** climax composited _into_ the scene behind the subject at the peak. **Cinematic** is pure embed — no rail, every caption composited behind the subject (hero typography, accumulation, occlusion as the effect). **Theme** is a complete themed constitution — body paradigm × hero setpiece × front fx × plate reaction, composed from registries ([themes/README.md](themes/README.md)): `ordnance` `terminal` `neonsign` `stardust` `stomp`. Most explainer / voiceover is **Standard**; **embed is the scarce, earned peak** — embedding every word is the common mistake; Theme is for VFX-grade asks ("炸", "特效", "像 AE 做的"). +**One catalog, picked up front** ([CATALOG.md](CATALOG.md) — 35 identities; the engines behind it are backend detail). **Standard** (default) builds a clean verbatim **rail** (lower-third subtitle carrying most text) + an **embed** climax composited _into_ the scene behind the subject at the peak. **Cinematic** is pure embed — no rail, every caption composited behind the subject (hero typography, accumulation, occlusion as the effect). **Theme** is a complete themed constitution — body paradigm × hero setpiece × front fx × plate reaction, composed from registries ([themes/README.md](themes/README.md)): `ordnance` `terminal` `neonsign` `stardust` `stomp`. Most explainer / voiceover is **Standard**; **embed is the scarce, earned peak** — embedding every word is the common mistake; Theme is for VFX-grade asks ("炸", "特效", "像 AE 做的"). --- @@ -16,7 +16,7 @@ metadata: The craft prose below is long; the **pipeline itself is short** — and everything deterministic is computed or compiled, never hand-written: -1. **Decision gate** (refuse bad clips) → **pick ONE identity from [CATALOG.md](CATALOG.md)** (17 identities; engine/compiler derived by lookup — never surface a mode/category question) +1. **Decision gate** (refuse bad clips) → **pick ONE identity from [CATALOG.md](CATALOG.md)** (36 identities; engine/compiler derived by lookup — never surface a mode/category question) 2. `hyperframes init` (skip it if the project dir already exists with the video inside — `matte.cjs`/`transcribe.cjs` adopt any video in the dir as source.mp4) → **`bash scripts/prepare.sh `** (matte ∥ transcribe ∥ audio-envelope in parallel, then safe-zones v2 with scene palette/optics/lighting — one command, nothing forgotten) 3. **author a small JSON of creative choices** (read `safe-zones.json` first): Cinematic → `plan.json` → `fill-timings.cjs` → `fit-fonts.cjs` → `make-composition.cjs`; @@ -51,7 +51,7 @@ Rail-surface identities build exactly this (rail = `rail.html`, embed = the clim ## Step 0 — pick ONE identity from the CATALOG **One front-end, three engines behind.** The user picks an IDENTITY from -[CATALOG.md](CATALOG.md) (17 entries: 12 classic + 5 themed); the engine, +[CATALOG.md](CATALOG.md) (36 entries: 10 classic + 26 themed); the engine, compiler and authoring file are derived by lookup from the catalog row. **Never surface "Standard vs Cinematic vs Theme" as a question** — those are backend names (a product has one UX even with several engines). The catalog @@ -169,7 +169,7 @@ each loop costs seconds. Render once, when the previews pass. ## The DNA registry — ten visual languages (replaces the template catalog) -Both modes draw from **[dna/](dna/README.md)** — six art-directed visual languages that +Both modes draw from **[dna/](dna/README.md)** — ten art-directed visual languages that **parameterize per scene** (accent sampled from the footage, contact shadow along the measured light direction, depth-match blur, RMS-coupled hero amplitude): @@ -236,7 +236,7 @@ track has its own, much simpler spec → **[references/rail.md](references/rail. | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | [references/rail.md](references/rail.md) | **The rail track** — standard lower-third subtitle spec (the default; carries most text). | | [references/composition-craft.md](references/composition-craft.md) | **The embed-track playbook** — grouping, planes, climax pop, occlusion judgement, accumulation/persistence. Read before embedding. | -| [dna/README.md](dna/README.md) | **The DNA registry** — six scene-parameterized visual languages; how to pick. | +| [dna/README.md](dna/README.md) | **The DNA registry** — ten scene-parameterized visual languages; how to pick. | | [references/reference-bar.md](references/reference-bar.md) | **The taste bar** — per-register world-class references + the 5 positive checks. | | [references/aesthetic-principles.md](references/aesthetic-principles.md) | **The 18 rules.** Beat Veed AI on taste. Read first. | | [references/motion-vocabulary.md](references/motion-vocabulary.md) | 10 named motion primitives + tone→timing lookup | diff --git a/plugins/hyperframes/skills/embedded-captions/modes/standard/_anatomy.md b/plugins/hyperframes/skills/embedded-captions/modes/standard/_anatomy.md index 234f71c8..0eb3fa02 100644 --- a/plugins/hyperframes/skills/embedded-captions/modes/standard/_anatomy.md +++ b/plugins/hyperframes/skills/embedded-captions/modes/standard/_anatomy.md @@ -226,7 +226,7 @@ The gallery used a `setInterval` loop; HyperFrames needs the same beats as **abs ## Pairs with HF skills -- `hyperframes-media` — `remove-background` (the matte) + `transcribe` (word timings). +- `media-use` — `remove-background` (the matte) + `transcribe` (word timings). - `hyperframes-captions` — transcript consumption, grouping, positioning, exit guarantees, `fitTextFontSize`. - `hyperframes-animation/rules/asr-keyword-glow.md` — the verbatim active-word envelope. - `hyperframes-gsap` — single paused timeline, transform aliases, ease palette. diff --git a/plugins/hyperframes/skills/embedded-captions/references/rail.md b/plugins/hyperframes/skills/embedded-captions/references/rail.md index 1f676f72..580380ba 100644 --- a/plugins/hyperframes/skills/embedded-captions/references/rail.md +++ b/plugins/hyperframes/skills/embedded-captions/references/rail.md @@ -10,7 +10,7 @@ with only the climax(es) promoted to embed. Rail is not a fallback — it's the > **Implementation note.** A dedicated rail renderer is the next build step. The rail is a > plain `fg` caption track and maps cleanly onto hyperframes' native caption pipeline -> (`hyperframes-media` captions) — prefer reusing that over hand-rolling. Until wired, render +> (`media-use` captions) — prefer reusing that over hand-rolling. Until wired, render > the rail as a simple `data-caption-layer="fg"` composition (no matte overlay for these caps). ## Position & safe area diff --git a/plugins/hyperframes/skills/embedded-captions/scripts/transcribe.cjs b/plugins/hyperframes/skills/embedded-captions/scripts/transcribe.cjs index 81857597..5775d2b8 100644 --- a/plugins/hyperframes/skills/embedded-captions/scripts/transcribe.cjs +++ b/plugins/hyperframes/skills/embedded-captions/scripts/transcribe.cjs @@ -110,7 +110,7 @@ function main() { console.error("usage: transcribe.cjs [model] [language]"); process.exit(1); } - // Default = multilingual `small`, NOT `small.en`. Per hyperframes-media: ".en models + // Default = multilingual `small`, NOT `small.en`. Per media-use: ".en models // mistranslate non-English and mis-handle accented speech; default to small (auto-detects // language)." We hardcoded small.en before — it hallucinated a wrong transcript on an // accented speaker. Pass `small.en` only for known-clean-English; tough accents → a larger model. diff --git a/plugins/hyperframes/skills/faceless-explainer/SKILL.md b/plugins/hyperframes/skills/faceless-explainer/SKILL.md index 6a80d44e..895f5759 100644 --- a/plugins/hyperframes/skills/faceless-explainer/SKILL.md +++ b/plugins/hyperframes/skills/faceless-explainer/SKILL.md @@ -1,6 +1,6 @@ --- name: faceless-explainer -description: "turn arbitrary text — an article, notes, a topic, a brief — into a faceless explainer video, up to ~3 min (sweet spot 30-90s), where every visual is invented (typography, abstract graphics, diagrams, data-viz) rather than captured. There is no URL, no website capture, and no real assets. Use this skill for topic explainers, concept breakdowns, how-tos, listicles, and narrative explainers. Do not use it for a product launch/promo (use /product-launch-video), a tour of a real website (use /website-to-video), a GitHub PR (use /pr-to-video), captions on existing footage (use /embedded-captions), or a short unnarrated motion graphic (use /motion-graphics). If the intent is unclear, route through /hyperframes first. This is the shot-sequence architecture: every frame is authored as a time-coded shot sequence picked from a menu of golden blueprints, so frames develop over their full duration instead of freezing after entrance." +description: "Turn arbitrary text — an article, notes, a topic, a brief — into a faceless explainer video: there is no site or footage to capture, so the visuals are invented per scene (typography, abstract graphics, diagrams, data-viz). Use for topic explainers, concept breakdowns, how-tos, listicles. Not a product promo (/product-launch-video) or a site tour (/website-to-video). Unclear → /hyperframes." --- > **media-use**: Before sourcing audio/images, call `/media-use` to resolve BGM/SFX/images from the HeyGen catalog. Run `--adopt` first to register existing assets. See `/media-use` skill. @@ -25,7 +25,7 @@ Initialize only if `hyperframes.json` is missing. Name `` from the topi `npx hyperframes init "videos/" --non-interactive --example=blank` — `init` checks the installed skills against the latest on GitHub and updates the global set if any are out of date. -**Show sign-in status before the brief** — run `npx hyperframes auth status` and **relay its output verbatim (don't paraphrase or rewrite it).** It reports whether voice/BGM will use HeyGen or local engines and, when not signed in, how to sign in. **If not signed in, STOP and wait for the user to choose — sign in, or say "go"/"offline" to continue with local engines — before asking the brief or anything else.** Treat it as a real decision point, not a passing note; don't fold the choice into the brief question, and don't write keys into a per-repo `.env`. (In autonomous mode, note the status and continue offline.) See `../hyperframes-media` → Preflight for the canonical guidance. +**Show sign-in status before the brief** — run `npx hyperframes auth status` and **relay its output verbatim (don't paraphrase or rewrite it).** It reports whether voice/BGM will use HeyGen or local engines and, when not signed in, how to sign in. **If not signed in, STOP and wait for the user to choose — sign in, or say "go"/"offline" to continue with local engines — before asking the brief or anything else.** Treat it as a real decision point, not a passing note; don't fold the choice into the brief question, and don't write keys into a per-repo `.env`. (In autonomous mode, note the status and continue offline.) See `../media-use` → Preflight for the canonical guidance. **Gate:** `hyperframes.json` exists, and angle, length, aspect ratio, and language are locked; sign-in status was shown (signed in, or continuing offline). @@ -88,7 +88,7 @@ Start audio after Step 3 approval. Run it in the background, then continue to St `node /scripts/audio.mjs --script ./SCRIPT.md --storyboard ./STORYBOARD.md --hyperframes . --out ./audio_meta.json &` -The audio script handles narration, word timings, BGM lookup from HeyGen's music library, and timing metadata. BGM mood comes from the storyboard's `music:` field. This uses the HeyGen Audio API for retrieval, not generation, and the same `~/.heygen` credential as TTS. For provider details, read `../hyperframes-media/references/tts.md`. +The audio script handles narration, word timings, BGM lookup from HeyGen's music library, and timing metadata. BGM mood comes from the storyboard's `music:` field. This uses the HeyGen Audio API for retrieval, not generation, and the same `~/.heygen` credential as TTS. For provider details, read `../media-use/audio/references/tts.md`. If there is no narration and no `SCRIPT.md`, skip voice generation. BGM may still run if the storyboard has a music mood. @@ -200,7 +200,7 @@ The reusable, domain-agnostic shot shapes live in `../hyperframes-animation/blue | `[../hyperframes-animation/blueprints-index.md](../hyperframes-animation/blueprints-index.md)` | Step 3: role→blueprint menu. Step 4: pick the shot shape. | | `[../hyperframes-core/references/storyboard-format.md](../hyperframes-core/references/storyboard-format.md)` | Step 3: write `STORYBOARD.md`. | | `[../hyperframes-core/references/script-format.md](../hyperframes-core/references/script-format.md)` | Step 3: write `SCRIPT.md`. | -| `[../hyperframes-media/references/tts.md](../hyperframes-media/references/tts.md)` | Step 3.1: choose or understand TTS providers and voices. | +| `[../media-use/audio/references/tts.md](../media-use/audio/references/tts.md)` | Step 3.1: choose or understand TTS providers and voices. | | `[references/visual-design.md](references/visual-design.md)` | Step 4: write the frame's shot sequence (+ Layout vocabulary). | | `[references/motion-language.md](references/motion-language.md)` | Step 4: the motion vocabulary + the motion doctrine. | | `[references/cut-catalog.md](references/cut-catalog.md)` | Step 4-5: the cut catalog (worker builds within-frame seams). | diff --git a/plugins/hyperframes/skills/faceless-explainer/references/motion-language.md b/plugins/hyperframes/skills/faceless-explainer/references/motion-language.md index e1ec6934..6ba6f68b 100644 --- a/plugins/hyperframes/skills/faceless-explainer/references/motion-language.md +++ b/plugins/hyperframes/skills/faceless-explainer/references/motion-language.md @@ -96,7 +96,7 @@ These four rules are the difference between a clip that reads as a serious expla Elements should use **long-tail decel curves that let them settle smoothly. `power3` is enough in most cases.** No bouncy, no overshoot, no `back.out` / `bounce.out` / `elastic.out` as a default. -Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle. +Bouncy is the **#1 instant turn-off** in user-made Remotion / HyperFrames videos, and the agent almost never gets it right — it thinks bouncy adds emphasis, but it buys that emphasis at the cost of cleanliness. The serious motion-design shops feel the same. **Smooth always wins.** Overshoot is demoted to a **rare, explicitly-playful exception** (a consumer/fun logo slam, a deliberate bell-hit) — never the house style. Name the intent as a long-tail settle; the worker maps `power3` (or `expo.out` on a fast arrival). See `../hyperframes-animation/rules/spring-pop-entrance.md` — it now leads with the smooth settle. (The exact form of that settle is a critically-damped spring; the worker has a baked, seek-safe `springEase` — ζ=1 — in `../hyperframes-animation/adapters/gsap-easing-and-stagger.md` → Spring Eases for when the settle is the hero. Real physics, same doctrine — not a license for bounce.) ## 2. Sequential reveal in the back ~50%, timed to the voiceover @@ -115,7 +115,7 @@ The agent's two reflexive ways to fake "aliveness" both read cheap: - **No lazy breathing.** Scaling cards/text up and down in a circular loop to look "alive" is the cheap tell. Don't reach for it. - **No bad slow pan / push in the back half.** A slow pan or push on elements in the later ~50% of a scene **disrupts the viewer's sightline and causes eye discomfort** — it actively makes the frame worse, not better. -The fix for both is the same: **stagger element reveals in time with the script** (rule 2). And the governing principle: **"I'd rather have NO motion than BAD motion."** A held, still frame is better than a frame kept "alive" by breathing or a drifting camera. The **only sanctioned aliveness** during a hold is **subtle jitter** — a small low-amplitude jitter that keeps a frame from feeling dead without looking weak (it's in Claude videos now). Everything else holds. +The fix for both is the same: **stagger element reveals in time with the script** (rule 2). And the governing principle: **"I'd rather have NO motion than BAD motion."** A held, still frame is better than a frame kept "alive" by breathing or a drifting camera. The **only sanctioned aliveness** during a hold is **subtle jitter** — a small low-amplitude jitter that keeps a frame from feeling dead without looking weak (it's in the reference videos now). Everything else holds. ## 4. Internal seams are velocity-matched cuts diff --git a/plugins/hyperframes/skills/faceless-explainer/scripts/audio.mjs b/plugins/hyperframes/skills/faceless-explainer/scripts/audio.mjs index f1f9df1b..864e6621 100644 --- a/plugins/hyperframes/skills/faceless-explainer/scripts/audio.mjs +++ b/plugins/hyperframes/skills/faceless-explainer/scripts/audio.mjs @@ -1,12 +1,14 @@ #!/usr/bin/env node -// audio.mjs — faceless-explainer audio ADAPTER. The TTS / BGM / SFX implementation +// audio.mjs — audio ADAPTER (reuses the product-launch SCRIPT.md / STORYBOARD.md +// model; this file is intentionally identical across the reusing skills). The +// TTS / BGM / SFX implementation // no longer lives here: it is the shared engine at -// ../../hyperframes-media/scripts/audio.mjs. This file only (a) maps the -// frame model (SCRIPT.md frames + STORYBOARD.md music/sfx) into the +// ../../media-use/audio/scripts/audio.mjs. This file only (a) maps the +// product-launch model (SCRIPT.md frames + STORYBOARD.md music/sfx) into the // engine's neutral audio_request.json, (b) converts the engine's id-keyed // audio_meta back into the frame-keyed shape captions.mjs / assemble-index.mjs // already consume, and (c) keeps the local `sync-durations` pass (it rewrites -// STORYBOARD.md, which is this skill's concern). +// STORYBOARD.md, which is product-launch-specific). // // Three modes (unchanged CLI surface): // (default) generate — engine --only tts,bgm. BGM mode is "retrieve" (strict: @@ -27,7 +29,7 @@ import { fileURLToPath } from "node:url"; import { parseStoryboard } from "./lib/storyboard.mjs"; const HERE = dirname(fileURLToPath(import.meta.url)); -const DEFAULT_ENGINE = join(HERE, "..", "..", "hyperframes-media", "scripts", "audio.mjs"); +const DEFAULT_ENGINE = join(HERE, "..", "..", "media-use", "audio", "scripts", "audio.mjs"); const flag = (argv, name, def) => { const i = argv.indexOf(`--${name}`); @@ -86,9 +88,9 @@ function runEngine({ request, hyperframesDir, neutral, only, extra = [] }, die) if (r.status !== 0) die(`media audio engine exited ${r.status}`); } -// Engine neutral meta (id-keyed) → frame-keyed meta consumed by +// Engine neutral meta (id-keyed) → product-launch meta (frame-keyed) consumed by // captions.mjs / assemble-index.mjs. id is the zero-padded frame number. -function toFrameKeyedMeta(neutral) { +function toProductLaunchMeta(neutral) { const voices = (neutral.voices ?? []).map((v) => ({ frame: Number(v.id), path: v.path, @@ -152,7 +154,7 @@ function runGenerate(argv) { const neutral = neutralPath(outPath); runEngine({ request, hyperframesDir, neutral, only: "tts,bgm" }, die); - const meta = toFrameKeyedMeta(JSON.parse(readFileSync(neutral, "utf8"))); + const meta = toProductLaunchMeta(JSON.parse(readFileSync(neutral, "utf8"))); writeFileSync(outPath, JSON.stringify(meta, null, 2)); console.log( `✓ audio generate: ${meta.voices.length} voice + ${meta.bgm ? "1 bgm" : "no bgm"} → ${outPath}`, @@ -189,7 +191,7 @@ function runFetchSfx(argv) { // voices/bgm written by the earlier generate (--only tts,bgm) pass are preserved. runEngine({ request, hyperframesDir, neutral, only: "sfx" }, die); - const meta = toFrameKeyedMeta(JSON.parse(readFileSync(neutral, "utf8"))); + const meta = toProductLaunchMeta(JSON.parse(readFileSync(neutral, "utf8"))); writeFileSync(outPath, JSON.stringify(meta, null, 2)); console.log(`✓ audio fetch-sfx: ${meta.sfx.length} SFX cue(s) → ${outPath}`); } diff --git a/plugins/hyperframes/skills/faceless-explainer/scripts/lib/pad-frame-duration.mjs b/plugins/hyperframes/skills/faceless-explainer/scripts/lib/pad-frame-duration.mjs new file mode 100644 index 00000000..91471151 --- /dev/null +++ b/plugins/hyperframes/skills/faceless-explainer/scripts/lib/pad-frame-duration.mjs @@ -0,0 +1,36 @@ +// pad-frame-duration.mjs — keeps a frame's own #root/clip data-duration in +// sync with the padded index.html wrapper duration transitions.mjs computes. +// +// The frame's OWN internal file declares its #root/clip data-duration to the +// STORYBOARD's content-only length (frame-worker.md: duration is "fixed +// upstream"). When an outgoing transition pads the index.html WRAPPER's +// data-duration to cover the transition tail, the frame's own internal +// duration is left short — the render engine clip-gates the sub-composition's +// visible content at that shorter value, so content vanishes abruptly at +// content-end instead of fading gracefully through the wrapper's extended +// fade-out tween. Pad the frame's own file to match so both durations agree. + +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; + +export function padFrameInternalDuration(hyperframesDir, frameSrc, frameId, newDuration) { + const framePath = resolve(hyperframesDir, frameSrc); + let html; + try { + html = readFileSync(framePath, "utf8"); + } catch (err) { + if (err?.code === "ENOENT") return; + throw err; + } + const tagRe = /<[a-z][\w:-]*\s[^<>]*?>/gi; + let m; + while ((m = tagRe.exec(html)) !== null) { + const tag = m[0]; + if (!tag.includes(`data-composition-id="${frameId}"`)) continue; + if (!/data-duration="[\d.]+"/.test(tag)) continue; + const newTag = tag.replace(/data-duration="[\d.]+"/, `data-duration="${newDuration}"`); + if (newTag === tag) return; + writeFileSync(framePath, html.slice(0, m.index) + newTag + html.slice(m.index + tag.length)); + return; + } +} diff --git a/plugins/hyperframes/skills/faceless-explainer/scripts/transitions.mjs b/plugins/hyperframes/skills/faceless-explainer/scripts/transitions.mjs index d30a9fae..22ad13ff 100644 --- a/plugins/hyperframes/skills/faceless-explainer/scripts/transitions.mjs +++ b/plugins/hyperframes/skills/faceless-explainer/scripts/transitions.mjs @@ -28,6 +28,7 @@ import { join, resolve } from "node:path"; import { parseStoryboard } from "./lib/storyboard.mjs"; import { parseFormat } from "./lib/dimensions.mjs"; import { loadTransitionRegistry, transitionsByName } from "./lib/transition-registry.mjs"; +import { padFrameInternalDuration } from "./lib/pad-frame-duration.mjs"; const flag = (argv, name, def) => { const i = argv.indexOf(`--${name}`); @@ -183,6 +184,12 @@ function runInject(argv) { const dur = resolveDur(spec, rec, reg); const T = r3(incoming.start); // cut = incoming start (frames tile) outgoing.duration = r3(outgoing.duration + dur); // extend outgoing only + padFrameInternalDuration( + hyperframesDir, + order[i - 1].frame.src, + outgoing.id, + outgoing.duration, + ); gsapLines.push( ...buildGsap(rec, outgoing.id, incoming.id, dur, T, spec.direction, CW, CH, die), ); diff --git a/plugins/hyperframes/skills/figma/SKILL.md b/plugins/hyperframes/skills/figma/SKILL.md new file mode 100644 index 00000000..305d4a40 --- /dev/null +++ b/plugins/hyperframes/skills/figma/SKILL.md @@ -0,0 +1,122 @@ +--- +name: figma +description: Import Figma content into a HyperFrames composition — rendered assets, brand tokens, components, storyboard sections → reconstructed motion (frames read as states, not slides) via REST/CLI, with optional connector-assisted motion and shader paths when the needed Figma tools are available. Use when the user pastes a figma.com link or asks to bring a Figma design, frame, logo, brand, or animation into a video/composition. +--- + +# Figma → HyperFrames + +Bring the user's Figma work into a composition. **Split by capability** (design spec §2): + +| Phase | What | Transport | Surface | +| ----- | ------------------- | ---------------------------- | ----------------------------- | +| 1 | Static assets | REST | `hyperframes figma asset` | +| 2 | Brand tokens/styles | REST | `hyperframes figma tokens` | +| 3 | Components → HTML | REST | `hyperframes figma component` | +| 4 | Motion → GSAP | Optional Figma connector | `get_motion_context` if available | +| 5 | Shaders | Optional connector / manual export | connector tools if available | + +REST is used wherever it can be (usable at volume, headless); connector-assisted paths are only for areas where Figma exposes no REST equivalent (motion, shaders). Every path freezes assets locally so renders stay deterministic. Storyboard reconstructions compose Phase-1 asset exports (REST) with agent-driven timeline assembly — no connector needed. Existing frozen assets, manifest records, and bindings are unaffected by routing changes — the split only changes which credential the next import uses. + +## Auth — two credentials, scoped + +**Preflight — before the first CLI call, check a token exists**: shell env (`[ -n "$FIGMA_TOKEN" ]`) **or** the project `.env` (the CLI auto-loads it — a `.env` entry counts as configured). If neither, do NOT run the command to harvest the error — walk the user through the one-time setup first, then stop and wait: + +1. figma.com/settings → **Security** → **Personal access tokens** → Generate new token. +2. Scopes — read-only is all this integration ever needs (it never writes to Figma): **File content: Read-only** + **File metadata: Read-only**. Optionally **Variables: Read-only** for brand variables — that scope only works on Figma Enterprise; without it `tokens` degrades to published styles automatically (expected behavior, not an error — say so). +3. `export FIGMA_TOKEN="figd_…"` — and suggest persisting it (shell profile or project `.env`) so no future session repeats this. + +While onboarding, also set expectations in one breath: every import lands as a **local frozen file with recorded provenance** — renders never call Figma, re-running a command re-imports only what changed in Figma, and one token works for assets, brand tokens, and components across every file their Figma account can view. + +- **Phases 4–5 (motion/shaders):** require a connected Figma integration/tooling surface separate from the token. If the needed tools are unavailable or unauthenticated, tell the user that motion/shader import requires the Figma connector and stop; continue only with REST-backed assets/tokens/components. +- Say exactly which credential a failing phase needs — never present the split as broken. +- `BAD_TOKEN` (401) mid-flow → the token is expired/revoked; re-mint. `FORBIDDEN` (403) → missing read scope or no access to that file — check scopes + file visibility. `REQUIRES_ENTERPRISE` (403 on variables) → not a failure: styles fallback already ran. + +**Rate-limit awareness (spec §2.1):** connector motion/shader calls may be scarce on low-tier Figma plans (figma plan matrix as of 2026-07 — re-verify if quotas look off) — batch with `recursive:true` on the parent node when supported, skip verification screenshots unless asked, and cache raw connector responses so re-derivation never spends a second call. REST is per-minute (10+/min, per-endpoint buckets) — fine at volume, back off on 429. + +## Routing + +Parse the user's figma link with `parseFigmaRef` (URL, `fileKey:nodeId`, bare `fileKey`). Then by intent: + +- "use this layer / logo / image" → **Asset** (CLI) +- "pull my brand / colors / tokens" → **Tokens** (CLI) +- "build a scene from this frame" → **Component** (CLI) +- "import this animation / motion" → **Motion** (connector if available, below) +- a storyboard section / filmstrip of scene frames → **Storyboard** (below) +- shader fill/effect → **Shaders** (below) + +**Narrate every step for the user** — before each command say what you're about to pull from Figma; after it, say where the artifact landed (the frozen path / sidecar / component dir), what changed in the composition, and the immediate next action (preview, add printed variables, re-import to link bindings). The user should never have to ask "did it work?" or "now what?". + +## Assets (Phase 1 — CLI) + +```bash +hyperframes figma asset '' [--format svg|png|jpg|pdf] [--scale 2] [--description "..."] [--entity "..."] +``` + +Renders over REST, sanitizes SVG, freezes under `.media/images/`, appends the manifest with provenance, regenerates `.media/index.md` (the shared media-use inventory), prints an `` snippet. Idempotent per `fileKey:nodeId:format:scale:version`. Prefer SVG for vectors/logos (scalable, animatable), PNG `--scale 2` for raster fidelity. **Always pass `--description ""`** (it becomes the index row + ``); add `--entity ""` for named brand marks so media-use `resolve --entity` finds them later (entity hits match across image/icon). + +## Tokens (Phase 2 — CLI) + +```bash +hyperframes figma tokens +``` + +Imports variables as composition brand-variable entries + `figma-tokens.json` sidecar + binding-index records (`.media/figma-bindings.jsonl`). Variables are Enterprise-gated upstream: on other plans the command degrades to published-style metadata (values resolve at component-import time). Add the printed entries to the composition's `data-composition-variables`. + +**Import tokens before components** when both are wanted — that's what lets component colors link to brand variables instead of baking duplicates. + +## Components (Phase 3 — CLI) + +```bash +hyperframes figma component '' +``` + +Node tree → editable HTML at exact figma geometry, packaged as a registry item under `compositions/components//`. Vectors/boolean-ops auto-rasterize via Phase-1 export. Binding pass (spec §7.1, exact-ID only — never value matching): + +- Fill bound to an **imported** token → `var(--slug, #literal)` — brand refresh propagates. +- Bound to an **unknown** token → literal + `data-figma-unresolved` flag. The command tells you; offer the user: run `tokens` on the source (or library) file, then re-import the component to link them. Ask **once** per unknown library which file it is — never guess, never match by hex. + +## Motion (Phase 4 — connector-assisted, when available) + +**Usage beacon:** connector phases have no CLI touchpoint, so fire the skill beacon at start and finish (anonymous, consent-gated, never fails): `npx hyperframes events --skill=figma-motion` when you begin, `npx hyperframes events --skill=figma-motion --event=skill_completed --outcome=success|error` when done. Same for shaders (`figma-shaders`) and storyboards (`figma-storyboard`). + +No REST equivalent exists. If a connected Figma tool exposes the needed motion context, use it, then hand output to the pure helpers in `@hyperframes/core/figma`; otherwise stop and tell the user this phase needs the Figma connector: + +1. `get_motion_context(fileKey, nodeId)` — use `recursive:true` on the parent frame (one call for the whole scene, not one per element). Save the raw JSON next to the project (`.media/figma-cache/`) so retranslation is free. +2. Normalize into a `MotionDoc`: per animated property a `MotionTrack` { property (motion.dev name), values, times (0..1), ease[] (named or `[x1,y1,x2,y2]` bezier), duration, repeat }. Selector = the element's stable id (`#` from Phase-3 output or the authored scene). +3. `motionToGsap(doc)` → `emitTimelineScript(spec)` → inject as a `