From b7735d609fd9de8a1d0b4a2fa254c5dda6736099 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 25 Mar 2026 15:50:42 -0700 Subject: [PATCH] feat: add HyperFrames skills for AI coding tools Add compose-video, captions, transitions, media, social-media, and typography-and-assets skills for AI coding assistants. Remove duplicated typography/assets content from compose-video SKILL.md (now references typography-and-assets.md). Fix captions skill to reference --output-json-full flag matching actual whisper CLI usage. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/captions/SKILL.md | 291 --------- .claude/skills/compose-video/SKILL.md | 556 ------------------ .claude/skills/media/SKILL.md | 299 ---------- .claude/skills/social-media/SKILL.md | 226 ------- .claude/skills/transitions/SKILL.md | 206 ------- ...6-03-25-skills-and-transcription-design.md | 118 ---- skills/captions/SKILL.md | 138 +++++ skills/compose-video/SKILL.md | 136 +++++ skills/compose-video/patterns.md | 118 ++++ 9 files changed, 392 insertions(+), 1696 deletions(-) delete mode 100644 .claude/skills/captions/SKILL.md delete mode 100644 .claude/skills/compose-video/SKILL.md delete mode 100644 .claude/skills/media/SKILL.md delete mode 100644 .claude/skills/social-media/SKILL.md delete mode 100644 .claude/skills/transitions/SKILL.md delete mode 100644 docs/superpowers/specs/2026-03-25-skills-and-transcription-design.md create mode 100644 skills/captions/SKILL.md create mode 100644 skills/compose-video/SKILL.md create mode 100644 skills/compose-video/patterns.md diff --git a/.claude/skills/captions/SKILL.md b/.claude/skills/captions/SKILL.md deleted file mode 100644 index ac4c3c37a6..0000000000 --- a/.claude/skills/captions/SKILL.md +++ /dev/null @@ -1,291 +0,0 @@ ---- -name: captions -description: Build timed caption compositions from whisper.cpp transcripts in HyperFrames. Use when adding captions, subtitles, or timed text to a composition that has a transcript.json file. ---- - -# Captions - -Build HyperFrames caption compositions from whisper.cpp transcript data. - -## When to Use - -- Project has a `transcript.json` (output from whisper.cpp `--output-json`) -- User asks to "add captions", "add subtitles", or "show the words" -- Editing an existing captions composition - -## Whisper.cpp Transcript Format - -The transcript file is whisper.cpp's native JSON output. Word-level timestamps are in the `tokens` array within each segment: - -```json -{ - "transcription": [ - { - "timestamps": { "from": "00:00:00,000", "to": "00:00:05,000" }, - "offsets": { "from": 0, "to": 5000 }, - "text": " Hello world.", - "tokens": [ - { - "text": " Hello", - "offsets": { "from": 0, "to": 1000 }, - "p": 0.98 - }, - { - "text": " world", - "offsets": { "from": 1000, "to": 2000 }, - "p": 0.95 - } - ] - } - ] -} -``` - -Key fields per token: - -- `text` — word text (often has leading space for word-initial tokens) -- `offsets.from` / `offsets.to` — milliseconds -- `p` — confidence (0-1) - -### Normalizing to a Word Array - -Flatten the transcript into a simple word array for grouping: - -```js -const words = []; -for (const segment of transcript.transcription) { - for (const token of segment.tokens || []) { - const text = token.text.trim(); - if (!text) continue; - words.push({ - text: text, - start: token.offsets.from / 1000, - end: token.offsets.to / 1000, - }); - } -} -``` - -## Word Grouping - -Group words into caption lines. Never show more than 5 words at once. Break on: - -1. **Sentence boundaries** — `.` `?` `!` -2. **Pauses** — gap > 150ms between consecutive words -3. **Max word count** — 3-5 words per group (3 for portrait, 5 for landscape) - -```js -function groupTranscript(words, maxWords) { - const groups = []; - let current = []; - - words.forEach((word, i) => { - current.push(word); - - const next = words[i + 1]; - const isPause = next && next.start - word.end > 0.15; - const isSentenceEnd = /[.?!]/.test(word.text); - - if (current.length >= maxWords || isPause || isSentenceEnd || !next) { - groups.push({ - text: current.map((w) => w.text).join(" "), - start: current[0].start, - end: current[current.length - 1].end, - }); - current = []; - } - }); - return groups; -} -``` - -## Composition Structure - -Caption compositions follow the standard HyperFrames composition format: - -```html - -``` - -## Positioning - -- **Landscape (1920x1080):** Bottom third — `bottom: 80-120px`, centered horizontally -- **Portrait (1080x1920):** Lower middle — `bottom: 600-700px`, centered horizontally -- Use `position: absolute` on caption groups, stacked in the same container -- Set `pointer-events: none` on the composition so captions don't block clicks -- **Never use relative positioning for captions.** Relative positioning creates a single long line that overflows off screen. Caption groups must be absolutely positioned so hidden words don't affect layout. -- **Avoid covering the subject's face.** Position captions in the lower third or upper portion, away from the speaker. -- **Maintain adequate padding** from screen edges (80px minimum on sides) - -## Animation Patterns - -### Fade + slide (clean, modern) - -```js -tl.fromTo(el, { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.4, ease: "expo.out" }, start); -tl.to(el, { opacity: 0, y: -10, duration: 0.3, ease: "expo.out" }, end - 0.3); -``` - -### Pop-in bounce (playful) - -```js -tl.to(el, { autoAlpha: 1, scale: 1.1, duration: 0.2, ease: "power2.out" }, start); -tl.to(el, { scale: 1, duration: 0.1, ease: "power2.inOut" }, start + 0.2); -tl.to(el, { autoAlpha: 0, scale: 0, duration: 0.15, ease: "power2.in" }, end); -``` - -### Hard cut (no animation) - -```js -tl.set(el, { display: "flex" }, start); -tl.set(el, { display: "none" }, end); -``` - -## Dynamic Font Sizing - -If a caption group is too wide for the viewport, scale the font size down: - -```js -const maxWidth = viewportWidth - 160; // 80px padding each side -const el = document.createElement("div"); -el.className = "caption-group"; -el.textContent = group.text; -el.style.fontSize = baseFontSize + "px"; -container.appendChild(el); - -// Measure and scale if needed -const measured = el.getBoundingClientRect().width; -if (measured > maxWidth) { - const scale = maxWidth / measured; - el.style.fontSize = Math.floor(baseFontSize * scale) + "px"; -} -``` - -Scale stroke width and spacing proportionally when scaling font size. - -## Active Word Highlighting - -Highlight the currently spoken word within a caption group by changing its color: - -```js -// Create individual word spans within the group -const wordSpans = group.words.map((w) => { - const span = document.createElement("span"); - span.textContent = w.text + " "; - span.style.color = defaultColor; - el.appendChild(span); - return { span, start: w.start, end: w.end }; -}); - -// Animate each word's color at its start time -wordSpans.forEach((ws) => { - tl.to(ws.span, { color: accentColor, duration: 0.05 }, ws.start); - tl.to(ws.span, { color: defaultColor, duration: 0.05 }, ws.end); -}); -``` - -## Timing Rules - -- **Entrance animation must complete before exit starts.** If a group is shorter than entrance + exit duration, reduce animation durations. -- **Exit before next group enters.** Use `Math.min(group.end, nextGroup.start)` as the hide time. -- **Minimum display time:** 0.3s — skip groups shorter than this. -- **No overlapping caption groups.** Only one group visible at a time. -- **Sync to actual audio.** Captions must match word-level timestamps from the transcript, not estimated timing. - -## Constraints - -- **Deterministic.** No `Math.random()`, no `Date.now()`. -- **Register timeline.** `window.__timelines["captions"] = tl;` -- **IIFE scope.** Wrap script in `(function() { ... })()` to avoid globals. -- **Scoped CSS.** Prefix all selectors with `[data-composition-id="captions"]`. -- **Set `data-duration`.** Must match or exceed the last caption's end time. -- **Do not animate `visibility` or `display` on timed clips.** Use `opacity` or `autoAlpha`. -- **Do not use `tl.set()` for property changes that need to survive timeline seeking.** Use `tl.to()` with a short duration instead. -- **Keep it simple.** Simpler captions perform better than fancy ones. Avoid excessive effects. - -## Common Mistakes - -- Captions not synced to actual audio (using estimated timing instead of transcript) -- Multiple caption groups visible simultaneously — only ONE group at a time -- Text too small to read on mobile devices -- Insufficient contrast between text and video background -- Captions covering the subject's face -- Using relative positioning (creates overflowing single line) -- All words the same size and style (no visual hierarchy) -- Overusing animation effects (less is more) - -## Loading in the Root Composition - -```html -
-``` diff --git a/.claude/skills/compose-video/SKILL.md b/.claude/skills/compose-video/SKILL.md deleted file mode 100644 index dcff77af1c..0000000000 --- a/.claude/skills/compose-video/SKILL.md +++ /dev/null @@ -1,556 +0,0 @@ ---- -name: compose-video -description: Create HyperFrames HTML video compositions. Use when asked to create a video, build an animation, make a composition, add a title card, or generate any HTML-based video content for HyperFrames. ---- - -# Compose Video - -Create HyperFrames HTML compositions — the HTML files that become videos. This skill contains everything you need to generate valid compositions that work in the studio preview and the producer render pipeline. - -## When to Use - -- "Create a video about..." -- "Make a composition with..." -- "Build an animation that..." -- "Add a title card / lower third / overlay" -- "Wrap this video in a produced composition" -- Any task that produces an `index.html` for HyperFrames - -## How HyperFrames Works - -HTML is the source of truth for video. A composition is an HTML file where: - -- **HTML elements** with `data-*` attributes define what appears and when -- **CSS** controls positioning and appearance -- **GSAP timeline** drives animations -- **The framework** automatically handles clip visibility, media playback, and timeline sync - -You write the HTML. The framework does the rest. - -### Framework-Managed Behavior - -The framework reads data attributes and automatically manages: - -- **Primitive clip timeline entries** — the framework reads `data-start`, `data-duration`, and `data-track-index` from primitive clips and adds them to the composition's GSAP timeline. You do not manually add primitive clips to the timeline in scripts. -- **Media playback** (play, pause, seek) for `