[object Object]

← back to Rentv Tour

Component video batch (18, Steve voice) + real bearded-Steve avatar (group 63e9372d) replacing placeholder

1a67a3b8756d3bde16dab28f53fbfb9330ee1ee1 · 2026-07-28 14:19:05 -0700 · Steve Abrams

Files touched

Diff

commit 1a67a3b8756d3bde16dab28f53fbfb9330ee1ee1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 14:19:05 2026 -0700

    Component video batch (18, Steve voice) + real bearded-Steve avatar (group 63e9372d) replacing placeholder
---
 AVATAR-STEVE.md                    |  12 +++--
 batch-components.sh                |  17 +++++++
 component-set.mjs                  |  85 +++++++++++++++++++++++++++++++++
 steve-real.jpg                     | Bin 0 -> 94841 bytes
 videos/components/AGENTS.md        |  95 +++++++++++++++++++++++++++++++++++++
 videos/components/CLAUDE.md        |  95 +++++++++++++++++++++++++++++++++++++
 videos/components/hyperframes.json |  13 +++++
 videos/components/index.html       |  40 ++++++++++++++++
 videos/components/meta.json        |   5 ++
 videos/components/package.json     |  11 +++++
 10 files changed, 369 insertions(+), 4 deletions(-)

diff --git a/AVATAR-STEVE.md b/AVATAR-STEVE.md
index f194c84..a9aa3b8 100644
--- a/AVATAR-STEVE.md
+++ b/AVATAR-STEVE.md
@@ -4,7 +4,7 @@ Real-person digital twin of Steve Abrams, built from his headshot. Used as the P
 presenter on RENTV videos (Master Reel + pitch films), driven by his cloned ElevenLabs voice.
 
 ## Appearance
-- Reference: /Users/macstudio3/Projects/Kamatera/DW-Agents/agent-avatars/steve.png
+- Reference: /Users/macstudio3/Projects/rentv-tour/steve-real.jpg (real bearded Steve, from iCloud share)
 - Source: photo-based (HeyGen photo_avatar)
 
 ## Voice
@@ -12,8 +12,8 @@ presenter on RENTV videos (Master Reel + pitch films), driven by his cloned Elev
 - ElevenLabs voice_id: Xa9qV4wNbvSkdUWsYLzq ("Steve Abrams")
 
 ## HeyGen
-- Group ID: c574ed3e978a46d1a9651b4ef11537b3   (stable identity anchor — never changes)
-- Look ID (landscape): 42fb945907ec4f358254ca4c1cd890e4
+- Group ID: 63e9372d3d2a47448997cbdbea5121ab   (stable identity anchor — never changes)
+- Look ID (landscape): b2105c3cc1fd4464b11cd24c417b5751
 - Avatar type: photo_avatar
 - Supported engines: avatar_iv, avatar_iii
 - Created: 2026-07-28 (status: processing/training at creation)
@@ -21,7 +21,7 @@ presenter on RENTV videos (Master Reel + pitch films), driven by his cloned Elev
 - Last Synced: 2026-07-28
 
 ⚠️ look_ids are ephemeral — resolve fresh from the group_id at runtime via
-`heygen avatar looks list --group-id c574ed3e978a46d1a9651b4ef11537b3`. Never hardcode look_id
+`heygen avatar looks list --group-id 63e9372d3d2a47448997cbdbea5121ab`. Never hardcode look_id
 as the primary reference.
 
 ## Usage (DTD verdict A, 2026-07-28)
@@ -29,3 +29,7 @@ PIP bookends on the LONGER videos only — Steve avatar in a bottom-right pictur
 the first ~20s and last ~20s of the Master Reel + 4 pitch films; screenshots fill the middle;
 ElevenLabs voice throughout. The ~17 short component videos stay clean (no avatar — too short
 for a 20s bookend).
+
+
+## 2026-07-28 CORRECTION
+Old placeholder avatar (group c574ed3e, dark-haired stock man) was WRONG — not Steve. Rebuilt from Steve's REAL bearded headshot. REPLACED placeholder.
diff --git a/batch-components.sh b/batch-components.sh
new file mode 100755
index 0000000..c6bd125
--- /dev/null
+++ b/batch-components.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+cd /Users/macstudio3/Projects/rentv-tour
+SAY=~/.claude/skills/clone-voice/scripts/say.sh
+ok=0
+for id in $(node component-set.mjs ids); do
+  txt=$(node component-set.mjs vo $id)
+  bash $SAY "$txt" /tmp/c-$id.wav >/dev/null 2>&1
+  ffmpeg -v error -y -i /tmp/c-$id.wav -c:a aac -b:a 160k videos/components/assets/vo.m4a >/dev/null 2>&1
+  dur=$(ffprobe -v error -show_entries format=duration -of default=nk=1:nw=1 videos/components/assets/vo.m4a 2>/dev/null)
+  node component-set.mjs emit $id $dur >/dev/null 2>&1
+  (cd videos/components && npx hyperframes render --output renders/$id.mp4 >/dev/null 2>&1)
+  if [ -f videos/components/renders/$id.mp4 ]; then
+    has_a=$(ffprobe -v error -show_entries stream=codec_type -of csv=p=0 videos/components/renders/$id.mp4 2>/dev/null | grep -c audio)
+    echo "done $id ($(ls -la videos/components/renders/$id.mp4|awk '{printf "%.1fMB",$5/1048576}'), audio=$has_a)"; ok=$((ok+1))
+  else echo "FAIL $id"; fi
+done
+echo "BATCH DONE: $ok/18 rendered"
diff --git a/component-set.mjs b/component-set.mjs
new file mode 100644
index 0000000..61d84fb
--- /dev/null
+++ b/component-set.mjs
@@ -0,0 +1,85 @@
+import fs from 'fs';
+const W = 1920, H = 1080;
+const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
+// id, label, tier(front|admin), one-line VO (Steve cloned voice)
+export const COMPONENTS = [
+  // ── Front end ──
+  ['home', 'RENTV Front Page', 'front', "The RENTV front page — live commercial real estate news for the Western U.S."],
+  ['markets', 'Markets', 'front', "Markets — C.R.E. fundamentals and a live snapshot across every tracked metro."],
+  ['deals', 'Deals', 'front', "Deals — real commercial transactions, filterable by property type, deal type, and market."],
+  ['pulse', 'Market Pulse', 'front', "Market Pulse — the analytics layer over every tracked deal, from volume to top transactions."],
+  ['review', 'The REview', 'front', "The Review — RENTV's commercial real estate video platform, native in the shell."],
+  ['hub', 'Intelligence Hub', 'front', "The Intelligence Hub — every RENTV tool and data surface, one click away."],
+  ['search', 'Search', 'front', "Global search — one box across news, deals, videos, and the whole platform."],
+  ['directory', 'CRE Directory', 'front', "The C.R.E. Directory — firms and players across the Western U.S. commercial market."],
+  ['la-commercial', 'LA Commercial', 'front', "L.A. Commercial — property-level intelligence and transactions for the Los Angeles market."],
+  ['brief', 'The RENTV Brief', 'front', "The RENTV Brief — the daily digest of what moved in Western U.S. commercial real estate."],
+  // ── Admin backend ──
+  ['backend', 'Backend Hub', 'admin', "The RENTV backend hub — every internal tool in one place, admin only."],
+  ['desk', 'Broker & Owner Desk', 'admin', "The Broker and Owner Desk — a live registry of three hundred eighty-two thousand brokers and thirty-four thousand firms, plus sublease inventory."],
+  ['audience', 'Audience & CRM', 'admin', "Audience and C.R.M. — every subscriber and sublease broker, auto-segmented into one prospect list."],
+  ['admin', 'Admin Dashboards', 'admin', "Ten admin dashboards, each a different back-office view over the real, live data."],
+  ['versions', 'Site Versions', 'admin', "Ten front-page design versions — pick the look, all running on live data."],
+  ['consulting', 'Consulting', 'admin', "The consulting deliverable — RENTV's growth strategy, concepts, and command center, folded in."],
+  ['press', 'Video Library', 'admin', "The video library — every RENTV film, walkthrough, and recap in one admin gallery."],
+  ['social', 'Social Media', 'admin', "Social Media — LinkedIn, X, and Instagram channels, the content calendar, and draft posts, all in one place."],
+];
+const byId = Object.fromEntries(COMPONENTS.map(c => [c[0], c]));
+
+function emit(id, voDur) {
+  const [, label, tier] = byId[id];
+  const accent = tier === 'admin' ? '#c8a15a' : '#0a6cff';
+  const tag = tier === 'admin' ? 'BACKEND' : 'FRONT END';
+  const TITLE = 1.3;
+  const TOTAL = +Math.max(voDur + 0.4, TITLE + 3.0).toFixed(2);
+  const shotStart = TITLE, shotDur = +(TOTAL - TITLE).toFixed(3);
+  const html = `<!doctype html>
+<html lang="en"><head>
+<meta charset="UTF-8" /><meta name="viewport" content="width=1920, height=1080" />
+<title>RENTV — ${esc(label)}</title>
+<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
+<style>
+  body{margin:0;background:#070b10;font-family:system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif}
+  #root{position:relative;width:${W}px;height:${H}px;overflow:hidden}
+  .clip{position:absolute;inset:0}
+  #bg{background:radial-gradient(120% 120% at 50% 0%,#0d1725 0%,#070b10 60%)}
+  .shot{overflow:hidden}
+  .simg{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;will-change:transform,opacity}
+  .card{display:grid;place-items:center}
+  .ci{text-align:center;padding:0 120px}
+  .ci .tag{font-size:20px;letter-spacing:.4em;font-weight:600;color:${accent};margin-bottom:18px}
+  .ci h1{margin:0;color:#f3f7fc;font-size:88px;font-weight:500;letter-spacing:-.01em}
+  .label{left:56px;bottom:60px;right:auto;top:auto;width:auto;height:auto}
+  .li{display:flex;align-items:center;gap:14px}
+  .li .num{color:#fff;font-size:18px;font-weight:600;letter-spacing:.08em;padding:8px 13px;border-radius:8px;background:${accent}}
+  .li .txt{color:#fff;font-size:30px;font-weight:500;background:rgba(9,15,24,.9);padding:8px 18px;border-radius:10px}
+</style></head>
+<body>
+  <div id="root" data-composition-id="main" data-start="0" data-width="${W}" data-height="${H}" data-duration="${TOTAL}" data-fps="30">
+  <div id="bg" class="clip" data-start="0" data-duration="${TOTAL}" data-track-index="0"></div>
+  <audio id="vo" src="assets/vo.m4a" data-start="0" data-volume="1"></audio>
+  <section id="title" class="clip card" data-start="0" data-duration="${TITLE}" data-track-index="1"><div class="ci"><div class="tag" id="tt">${tag}</div><h1 id="th">${esc(label)}</h1></div></section>
+  <div id="shot" class="clip shot" data-start="${shotStart}" data-duration="${shotDur}" data-track-index="2"><img id="simg" class="simg" src="assets/${id}.png" alt="${esc(label)}"></div>
+  <div id="lbl" class="clip label" data-start="${shotStart}" data-duration="${shotDur}" data-track-index="3"><div id="li" class="li"><span class="num">${tag}</span><span class="txt">${esc(label)}</span></div></div>
+  </div>
+  <script>
+    window.__timelines = window.__timelines || {};
+    const tl = gsap.timeline({ paused: true });
+    tl.from('#th',{y:32,autoAlpha:0,duration:0.55,ease:'power3.out'},0.12);
+    tl.from('#tt',{y:-14,autoAlpha:0,duration:0.45},0.04);
+    tl.from('#simg',{opacity:0,duration:0.35,ease:'power1.out'},${shotStart.toFixed(3)});
+    tl.fromTo('#simg',{scale:1.07},{scale:1.0,duration:${shotDur},ease:'none'},${shotStart.toFixed(3)});
+    tl.from('#li',{y:24,autoAlpha:0,duration:0.4,ease:'power2.out'},${(shotStart + 0.1).toFixed(3)});
+    window.__timelines['main'] = tl;
+  </script>
+</body></html>`;
+  fs.writeFileSync('videos/components/index.html', html);
+  return TOTAL;
+}
+
+// ── CLI ──
+const [cmd, a, b] = process.argv.slice(2);
+if (cmd === 'ids') console.log(COMPONENTS.map(c => c[0]).join(' '));
+else if (cmd === 'vo') process.stdout.write(byId[a][3]);
+else if (cmd === 'emit') console.log('TOTAL=' + emit(a, parseFloat(b)));
diff --git a/steve-real.jpg b/steve-real.jpg
new file mode 100644
index 0000000..7ca34d8
Binary files /dev/null and b/steve-real.jpg differ
diff --git a/videos/components/AGENTS.md b/videos/components/AGENTS.md
new file mode 100644
index 0000000..c4ff0ae
--- /dev/null
+++ b/videos/components/AGENTS.md
@@ -0,0 +1,95 @@
+# HyperFrames Composition Project
+
+## Skills — USE THESE FIRST
+
+**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
+
+**Doing anything with HyperFrames?** Start at `/hyperframes` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), confirms your brief up front (the intent layer), and routes every "make me a…" request (a video, a deck, a composition port) to the right workflow. Read it first, especially when there's no project context to orient you. The workflows it routes to:
+
+- `/product-launch-video` — any **website** URL or brief / script → a product launch / SaaS / promo video, or a site tour / showcase featuring the site's own captured visuals.
+- `/faceless-explainer` — arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
+- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
+- `/talking-head-recut` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
+- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
+- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
+- `/music-to-video` — a **music track** (audio file, or video to pull audio from) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid.
+- `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video.
+- `/general-video` — fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes authoring flow, any length.
+
+**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
+
+The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent.
+
+**Changing how real footage or images look or reveal?** Load `/media-use` and its media-treatment policy before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
+
+> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
+
+> **Skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
+> the specific skill you need (the `/hyperframes` router does this automatically before
+> entering a workflow), or bare `npx hyperframes skills update` to refresh the core set plus
+> everything already installed — neither pulls the full set. Restart the agent session so
+> newly installed skills load.
+
+## Commands
+
+```bash
+npm run dev          # start the preview server (long-running — keep it alive in background)
+npm run check        # lint + runtime + layout + motion + contrast (one command)
+npm run render       # render to MP4
+npm run publish      # publish and get a shareable link
+npx hyperframes lint --verbose  # include info-level findings
+npx hyperframes lint --json     # machine-readable output for CI
+npx hyperframes docs <topic> # reference docs in terminal
+```
+
+> **`npm run dev` is a long-running server, not a one-shot command.** It blocks until stopped.
+> In Claude Code, always run it with `run_in_background: true`. Never run it as a foreground
+> command — it will time out and the server will die, breaking the browser preview.
+
+> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
+
+## Documentation
+
+**For quick reference**, use the local CLI docs command (no network required):
+
+```bash
+npx hyperframes docs <topic>
+```
+
+Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
+
+**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
+
+```
+https://hyperframes.heygen.com/llms.txt
+```
+
+## Project Structure
+
+- `index.html` — main composition (root timeline)
+- `compositions/` — sub-compositions referenced via `data-composition-src`
+- `meta.json` — project metadata (id, name)
+- `transcript.json` — whisper word-level transcript (if generated)
+
+## Linting — ALWAYS RUN AFTER CHANGES
+
+After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
+
+```bash
+npm run check
+```
+
+Fix all errors before presenting the result. Warnings should be reviewed before rendering.
+
+## Key Rules
+
+1. Every timed element needs `data-start`, `data-duration`, and `data-track-index`
+2. Elements with timing **MUST** have `class="clip"` — the framework uses this for visibility control
+3. Timelines must be paused and registered on `window.__timelines`:
+   ```js
+   window.__timelines = window.__timelines || {};
+   window.__timelines["composition-id"] = gsap.timeline({ paused: true });
+   ```
+4. Videos use `muted` with a separate `<audio>` element for the audio track
+5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
+6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
diff --git a/videos/components/CLAUDE.md b/videos/components/CLAUDE.md
new file mode 100644
index 0000000..c4ff0ae
--- /dev/null
+++ b/videos/components/CLAUDE.md
@@ -0,0 +1,95 @@
+# HyperFrames Composition Project
+
+## Skills — USE THESE FIRST
+
+**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
+
+**Doing anything with HyperFrames?** Start at `/hyperframes` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), confirms your brief up front (the intent layer), and routes every "make me a…" request (a video, a deck, a composition port) to the right workflow. Read it first, especially when there's no project context to orient you. The workflows it routes to:
+
+- `/product-launch-video` — any **website** URL or brief / script → a product launch / SaaS / promo video, or a site tour / showcase featuring the site's own captured visuals.
+- `/faceless-explainer` — arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
+- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
+- `/talking-head-recut` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
+- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
+- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
+- `/music-to-video` — a **music track** (audio file, or video to pull audio from) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid.
+- `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video.
+- `/general-video` — fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes authoring flow, any length.
+
+**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
+
+The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent.
+
+**Changing how real footage or images look or reveal?** Load `/media-use` and its media-treatment policy before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
+
+> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
+
+> **Skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
+> the specific skill you need (the `/hyperframes` router does this automatically before
+> entering a workflow), or bare `npx hyperframes skills update` to refresh the core set plus
+> everything already installed — neither pulls the full set. Restart the agent session so
+> newly installed skills load.
+
+## Commands
+
+```bash
+npm run dev          # start the preview server (long-running — keep it alive in background)
+npm run check        # lint + runtime + layout + motion + contrast (one command)
+npm run render       # render to MP4
+npm run publish      # publish and get a shareable link
+npx hyperframes lint --verbose  # include info-level findings
+npx hyperframes lint --json     # machine-readable output for CI
+npx hyperframes docs <topic> # reference docs in terminal
+```
+
+> **`npm run dev` is a long-running server, not a one-shot command.** It blocks until stopped.
+> In Claude Code, always run it with `run_in_background: true`. Never run it as a foreground
+> command — it will time out and the server will die, breaking the browser preview.
+
+> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
+
+## Documentation
+
+**For quick reference**, use the local CLI docs command (no network required):
+
+```bash
+npx hyperframes docs <topic>
+```
+
+Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
+
+**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
+
+```
+https://hyperframes.heygen.com/llms.txt
+```
+
+## Project Structure
+
+- `index.html` — main composition (root timeline)
+- `compositions/` — sub-compositions referenced via `data-composition-src`
+- `meta.json` — project metadata (id, name)
+- `transcript.json` — whisper word-level transcript (if generated)
+
+## Linting — ALWAYS RUN AFTER CHANGES
+
+After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
+
+```bash
+npm run check
+```
+
+Fix all errors before presenting the result. Warnings should be reviewed before rendering.
+
+## Key Rules
+
+1. Every timed element needs `data-start`, `data-duration`, and `data-track-index`
+2. Elements with timing **MUST** have `class="clip"` — the framework uses this for visibility control
+3. Timelines must be paused and registered on `window.__timelines`:
+   ```js
+   window.__timelines = window.__timelines || {};
+   window.__timelines["composition-id"] = gsap.timeline({ paused: true });
+   ```
+4. Videos use `muted` with a separate `<audio>` element for the audio track
+5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
+6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
diff --git a/videos/components/hyperframes.json b/videos/components/hyperframes.json
new file mode 100644
index 0000000..ec2e3c2
--- /dev/null
+++ b/videos/components/hyperframes.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "https://hyperframes.heygen.com/schema/hyperframes.json",
+  "registry": "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
+  "paths": {
+    "blocks": "compositions",
+    "components": "compositions/components",
+    "assets": "assets"
+  },
+  "media": {
+    "autoProxy": true
+  },
+  "authoringSkill": "product-launch-video"
+}
diff --git a/videos/components/index.html b/videos/components/index.html
new file mode 100644
index 0000000..4b82114
--- /dev/null
+++ b/videos/components/index.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html lang="en"><head>
+<meta charset="UTF-8" /><meta name="viewport" content="width=1920, height=1080" />
+<title>RENTV — Social Media</title>
+<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
+<style>
+  body{margin:0;background:#070b10;font-family:system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif}
+  #root{position:relative;width:1920px;height:1080px;overflow:hidden}
+  .clip{position:absolute;inset:0}
+  #bg{background:radial-gradient(120% 120% at 50% 0%,#0d1725 0%,#070b10 60%)}
+  .shot{overflow:hidden}
+  .simg{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;will-change:transform,opacity}
+  .card{display:grid;place-items:center}
+  .ci{text-align:center;padding:0 120px}
+  .ci .tag{font-size:20px;letter-spacing:.4em;font-weight:600;color:#c8a15a;margin-bottom:18px}
+  .ci h1{margin:0;color:#f3f7fc;font-size:88px;font-weight:500;letter-spacing:-.01em}
+  .label{left:56px;bottom:60px;right:auto;top:auto;width:auto;height:auto}
+  .li{display:flex;align-items:center;gap:14px}
+  .li .num{color:#fff;font-size:18px;font-weight:600;letter-spacing:.08em;padding:8px 13px;border-radius:8px;background:#c8a15a}
+  .li .txt{color:#fff;font-size:30px;font-weight:500;background:rgba(9,15,24,.9);padding:8px 18px;border-radius:10px}
+</style></head>
+<body>
+  <div id="root" data-composition-id="main" data-start="0" data-width="1920" data-height="1080" data-duration="7.51" data-fps="30">
+  <div id="bg" class="clip" data-start="0" data-duration="7.51" data-track-index="0"></div>
+  <audio id="vo" src="assets/vo.m4a" data-start="0" data-volume="1"></audio>
+  <section id="title" class="clip card" data-start="0" data-duration="1.3" data-track-index="1"><div class="ci"><div class="tag" id="tt">BACKEND</div><h1 id="th">Social Media</h1></div></section>
+  <div id="shot" class="clip shot" data-start="1.3" data-duration="6.21" data-track-index="2"><img id="simg" class="simg" src="assets/social.png" alt="Social Media"></div>
+  <div id="lbl" class="clip label" data-start="1.3" data-duration="6.21" data-track-index="3"><div id="li" class="li"><span class="num">BACKEND</span><span class="txt">Social Media</span></div></div>
+  </div>
+  <script>
+    window.__timelines = window.__timelines || {};
+    const tl = gsap.timeline({ paused: true });
+    tl.from('#th',{y:32,autoAlpha:0,duration:0.55,ease:'power3.out'},0.12);
+    tl.from('#tt',{y:-14,autoAlpha:0,duration:0.45},0.04);
+    tl.from('#simg',{opacity:0,duration:0.35,ease:'power1.out'},1.300);
+    tl.fromTo('#simg',{scale:1.07},{scale:1.0,duration:6.21,ease:'none'},1.300);
+    tl.from('#li',{y:24,autoAlpha:0,duration:0.4,ease:'power2.out'},1.400);
+    window.__timelines['main'] = tl;
+  </script>
+</body></html>
\ No newline at end of file
diff --git a/videos/components/meta.json b/videos/components/meta.json
new file mode 100644
index 0000000..1b5b4b8
--- /dev/null
+++ b/videos/components/meta.json
@@ -0,0 +1,5 @@
+{
+  "id": "components",
+  "name": "components",
+  "createdAt": "2026-07-28T21:10:36.704Z"
+}
\ No newline at end of file
diff --git a/videos/components/package.json b/videos/components/package.json
new file mode 100644
index 0000000..c144144
--- /dev/null
+++ b/videos/components/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "components",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "npx --yes hyperframes@0.7.78 preview",
+    "check": "npx --yes hyperframes@0.7.78 check",
+    "render": "npx --yes hyperframes@0.7.78 render",
+    "publish": "npx --yes hyperframes@0.7.78 publish"
+  }
+}

← 5986d75 Master Reel: curated hero montage + Steve HeyGen avatar circ  ·  back to Rentv Tour  ·  auto-save: 2026-07-28T14:31:09 (3 files) — batch-pitch.sh pi 8653362 →