|
| 1 | +--- |
| 2 | +name: contribute |
| 3 | +description: Scaffold, build, validate, and ship new HyperFrames registry components (caption styles, VFX blocks, transitions, lower thirds). Guides the full workflow from idea to merged PR. Use when user says "contribute", "new component", "add a block", "create a caption style", "build a transition", or wants to add something to the HyperFrames registry. |
| 4 | +--- |
| 5 | + |
| 6 | +# Contribute to HyperFrames Registry |
| 7 | + |
| 8 | +Guide the user from idea to merged PR for a new registry block. |
| 9 | + |
| 10 | +## Workflow |
| 11 | + |
| 12 | +``` |
| 13 | +1. Clarify → 2. Scaffold → 3. Build → 4. Validate → 5. Preview → 6. Ship |
| 14 | +``` |
| 15 | + |
| 16 | +### Step 1: Clarify |
| 17 | + |
| 18 | +Ask what they're building: |
| 19 | + |
| 20 | +- **Caption style** — word-level karaoke, specific animation/highlight pattern |
| 21 | +- **VFX block** — Three.js 3D effect, shader, particle system |
| 22 | +- **Transition** — scene-to-scene transition effect |
| 23 | +- **Block** — lower third, social card, title card, data viz |
| 24 | + |
| 25 | +Then ask: |
| 26 | +- One-sentence description of the effect |
| 27 | +- Visual reference (URL, screenshot, or description) |
| 28 | +- Who uses this and when? |
| 29 | + |
| 30 | +### Step 2: Scaffold |
| 31 | + |
| 32 | +Create the registry block structure in the hyperframes repo: |
| 33 | + |
| 34 | +``` |
| 35 | +registry/blocks/{block-name}/ |
| 36 | + {block-name}.html |
| 37 | + registry-item.json |
| 38 | +``` |
| 39 | + |
| 40 | +**Naming convention:** |
| 41 | + |
| 42 | +| Block name | ID prefix for elements | Example IDs | |
| 43 | +|-----------|----------------------|-------------| |
| 44 | +| `cap-hormozi` | `hz` | `hz-cg-0`, `hz-cw-3` | |
| 45 | +| `cap-typewriter` | `tw` | `tw-cg-0`, `tw-ch-0-5` | |
| 46 | +| `vfx-chrome` | `vc` | `vc-canvas` | |
| 47 | + |
| 48 | +Use a 2-3 letter prefix derived from the block name. ALL element IDs in the composition must use this prefix to avoid collisions when embedded as sub-compositions. |
| 49 | + |
| 50 | +**registry-item.json template:** |
| 51 | + |
| 52 | +```json |
| 53 | +{ |
| 54 | + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", |
| 55 | + "name": "{block-name}", |
| 56 | + "type": "hyperframes:block", |
| 57 | + "title": "{Human Title}", |
| 58 | + "description": "{one sentence}", |
| 59 | + "stability": "experimental", |
| 60 | + "dimensions": { "width": 1920, "height": 1080 }, |
| 61 | + "duration": 10, |
| 62 | + "tags": ["{category}", "{subcategory}"], |
| 63 | + "files": [ |
| 64 | + { |
| 65 | + "path": "{block-name}.html", |
| 66 | + "target": "compositions/{block-name}.html", |
| 67 | + "type": "hyperframes:composition" |
| 68 | + } |
| 69 | + ] |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +### Step 3: Build |
| 74 | + |
| 75 | +Apply the correct template based on component type. See [templates.md](templates.md) for copy-paste starters. |
| 76 | + |
| 77 | +#### Caption blocks |
| 78 | + |
| 79 | +Read the captions reference in the hyperframes skill BEFORE writing. |
| 80 | + |
| 81 | +**Non-negotiable caption rules:** |
| 82 | +- Font: **96px minimum** for proportional fonts (Montserrat, Figtree). **64-72px is acceptable for monospace** (JetBrains Mono, Chakra Petch) since monospace characters are wider. |
| 83 | +- Readability: `-webkit-text-stroke: 2-3px` OR multi-layer `text-shadow` |
| 84 | +- Overflow: call `window.__hyperframes.fitTextFontSize()` on every group |
| 85 | +- Karaoke: highlight active word via `tl.to(wordEl, { color/scale }, WORDS[wi].start)` |
| 86 | +- Hard kill: `tl.set(groupEl, { opacity: 0, visibility: "hidden" }, g.end)` on EVERY group |
| 87 | +- **Never use `tl.from(el, { opacity: 0 })` at the same position as `tl.set(el, { opacity: 1 })`** — the from clobbers the set. Use `tl.to` instead. |
| 88 | + |
| 89 | +**Per-character animation** (typewriter, scramble, fade-in letter by letter): |
| 90 | +- Wrap each character in its own `<span>` with ID `{prefix}-ch-{groupIndex}-{charIndex}` |
| 91 | +- Stagger `tl.set(charSpan, { opacity: 1 }, charStartTime)` at computed intervals based on the word timestamps |
| 92 | +- For cursors and decorative elements: use `tl.set` at fixed intervals for blinking — NOT CSS animation (not seekable) |
| 93 | + |
| 94 | +**Positioning variants:** |
| 95 | +- Centered (default): `display: flex; align-items: center; justify-content: center;` |
| 96 | +- Lower-third centered: `position: absolute; bottom: 100px; left: 0; width: 100%; text-align: center;` |
| 97 | +- Left-aligned: `position: absolute; bottom: 100px; left: 120px; text-align: left;` |
| 98 | +- Right-aligned: `position: absolute; bottom: 100px; right: 120px; text-align: right;` |
| 99 | + |
| 100 | +#### VFX blocks (Three.js) |
| 101 | + |
| 102 | +- Use `three@0.147.0` from CDN (global script, not ES modules) |
| 103 | +- `tl.eventCallback("onUpdate", renderScene); renderScene();` — NO requestAnimationFrame |
| 104 | +- State proxy pattern: GSAP animates plain JS object, render function applies to Three.js |
| 105 | +- Seeded PRNG (`mulberry32`) for any randomness |
| 106 | + |
| 107 | +#### All component types |
| 108 | + |
| 109 | +- `data-composition-id` MUST match `window.__timelines["id"]` |
| 110 | +- All element IDs prefixed with block abbreviation (see naming table above) |
| 111 | +- `gsap.timeline({ paused: true })` — always paused |
| 112 | +- No `Math.random()`, no `Date.now()` |
| 113 | +- Duration from `data-duration`, not GSAP timeline length |
| 114 | + |
| 115 | +### Step 4: Validate |
| 116 | + |
| 117 | +Run these in order. All must pass before proceeding. |
| 118 | + |
| 119 | +```bash |
| 120 | +hyperframes lint # 0 errors required |
| 121 | +hyperframes validate --no-contrast # 0 console errors required |
| 122 | +``` |
| 123 | + |
| 124 | +Common fixes: |
| 125 | +- `timed_element_missing_clip_class` → add `class="clip"` to timed elements |
| 126 | +- `timeline_id_mismatch` → make `window.__timelines["X"]` match `data-composition-id="X"` |
| 127 | +- `composition_file_too_large` → warning only, acceptable for complex blocks |
| 128 | + |
| 129 | +### Step 5: Preview |
| 130 | + |
| 131 | +```bash |
| 132 | +hyperframes render -o preview.mp4 |
| 133 | +hyperframes snapshot --at "1.0,3.0,5.0,7.0" |
| 134 | +``` |
| 135 | + |
| 136 | +Inspect every snapshot: |
| 137 | +- [ ] Text readable at 50% zoom (captions) |
| 138 | +- [ ] Animation visible at every timestamp (no blank frames) |
| 139 | +- [ ] No overflow or clipping |
| 140 | + |
| 141 | +### Step 6: Ship |
| 142 | + |
| 143 | +1. Create branch: `git checkout -b feat/registry-{block-name}` |
| 144 | +2. Format: `npx oxfmt registry/blocks/{block-name}/*.html` |
| 145 | +3. Stage only registry files: `git add registry/blocks/{block-name}/` |
| 146 | +4. Commit with descriptive message |
| 147 | +5. Push and open PR with rendered MP4 preview linked |
| 148 | + |
| 149 | +## Quality Gate |
| 150 | + |
| 151 | +Before opening the PR, verify: |
| 152 | + |
| 153 | +- [ ] `hyperframes lint` → 0 errors |
| 154 | +- [ ] `hyperframes validate` → 0 console errors |
| 155 | +- [ ] Preview video shows the effect clearly |
| 156 | +- [ ] All IDs unique and prefixed with block abbreviation |
| 157 | +- [ ] `npx oxfmt --check` passes on HTML files |
0 commit comments