[object Object]

← back to Rentv Tour

RENTV full-site walkthrough video: HyperFrames tour of all 31 tabs (24 front + 7 admin), two acts, 49s

83d8a19e2e0f2ec18fd4d11bfd9b699e2ed02797 · 2026-07-28 12:26:33 -0700 · Steve Abrams

- capture.mjs screenshots every tab (Playwright httpCredentials, user vs admin roles)
- generate.mjs emits the composition from frames/manifest.json (act cards + per-tab image clips + labels)
- renders to renders/rentv-walkthrough.mp4 (1920x1080, 30fps)

Files touched

Diff

commit 83d8a19e2e0f2ec18fd4d11bfd9b699e2ed02797
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 12:26:33 2026 -0700

    RENTV full-site walkthrough video: HyperFrames tour of all 31 tabs (24 front + 7 admin), two acts, 49s
    
    - capture.mjs screenshots every tab (Playwright httpCredentials, user vs admin roles)
    - generate.mjs emits the composition from frames/manifest.json (act cards + per-tab image clips + labels)
    - renders to renders/rentv-walkthrough.mp4 (1920x1080, 30fps)
---
 .gitignore                        |   6 +
 capture.mjs                       |  73 ++++++
 generate.mjs                      | 127 +++++++++++
 package-lock.json                 |  63 ++++++
 package.json                      |  16 ++
 videos/site-tour/AGENTS.md        |  95 ++++++++
 videos/site-tour/CLAUDE.md        |  95 ++++++++
 videos/site-tour/hyperframes.json |  13 ++
 videos/site-tour/index.html       | 457 ++++++++++++++++++++++++++++++++++++++
 videos/site-tour/meta.json        |   5 +
 videos/site-tour/package.json     |  11 +
 11 files changed, 961 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f76280
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+videos/*/renders/
+videos/*/assets/
+frames/
+*.log
+.DS_Store
diff --git a/capture.mjs b/capture.mjs
new file mode 100644
index 0000000..f1680dd
--- /dev/null
+++ b/capture.mjs
@@ -0,0 +1,73 @@
+import { chromium } from 'playwright';
+import fs from 'fs';
+
+const BASE = 'https://rentv.agentabrams.com';
+const VW = 1440, VH = 810; // 16:9
+
+// FRONT END tabs — captured as the `user` login (public front page)
+const FRONT = [
+  ['/', 'Home'],
+  ['/hub', 'Intelligence Hub'],
+  ['/search', 'Search'],
+  ['/market-pulse', 'Rates & Capital Markets'],
+  ['/pulse', 'Market Pulse'],
+  ['/sector', 'Sector Deep-Dives'],
+  ['/metros', 'Deal Flow by Metro'],
+  ['/tools', 'CRE Toolkit'],
+  ['/watchlist', 'Deal Watchlist'],
+  ['/directory', 'CRE Directory'],
+  ['/compare', 'Deal Comparison'],
+  ['/brief', 'The RENTV Brief'],
+  ['/syndicate', 'Syndicate'],
+  ['/financing', 'Sections — Financing'],
+  ['/deals', 'Deals'],
+  ['/markets', 'Markets'],
+  ['/la-commercial', 'LA Commercial'],
+  ['/review', 'The REview'],
+  ['/cre-talk', 'CRE Talk'],
+  ['/conferences', 'Conferences'],
+  ['/blog', 'Blog'],
+  ['/about', 'About'],
+  ['/advertise', 'Advertise'],
+  ['/subscribe', 'Subscribe / Newsletters'],
+];
+
+// ADMIN backend tabs — captured as the `admin` login
+const ADMIN = [
+  ['/backend', 'Backend Hub'],
+  ['/desk', 'Broker & Owner Desk'],
+  ['/audience', 'Audience & CRM'],
+  ['/admin', 'Admin Dashboards'],
+  ['/versions', 'Site Versions'],
+  ['/consulting', 'Consulting'],
+  ['/press', 'Video Library'],
+];
+
+const pad = (n) => String(n).padStart(2, '0');
+
+async function shoot(role, creds, list, dir) {
+  const browser = await chromium.launch();
+  const ctx = await browser.newContext({ viewport: { width: VW, height: VH }, httpCredentials: creds, deviceScaleFactor: 2 });
+  const page = await ctx.newPage();
+  const manifest = [];
+  for (let i = 0; i < list.length; i++) {
+    const [route, label] = list[i];
+    const file = `${dir}/${pad(i)}-${route.replace(/\W+/g, '-').replace(/^-|-$/g, '') || 'home'}.png`;
+    let status = 0;
+    try {
+      const resp = await page.goto(BASE + route, { waitUntil: 'networkidle', timeout: 25000 });
+      status = resp ? resp.status() : 0;
+    } catch (e) { try { await page.waitForTimeout(1500); } catch {} }
+    await page.waitForTimeout(1200); // let hero/data settle
+    await page.screenshot({ path: file });
+    manifest.push({ i, route, label, role, status, file });
+    console.log(`  [${role}] ${pad(i)} ${route} -> ${status} ${label}`);
+  }
+  await browser.close();
+  return manifest;
+}
+
+const front = await shoot('user', { username: 'user', password: 'RentvUser2026!' }, FRONT, 'frames/front');
+const admin = await shoot('admin', { username: 'admin', password: 'DW2024!' }, ADMIN, 'frames/admin');
+fs.writeFileSync('frames/manifest.json', JSON.stringify({ front, admin, vw: VW, vh: VH }, null, 2));
+console.log(`\nDONE: ${front.length} front + ${admin.length} admin frames`);
diff --git a/generate.mjs b/generate.mjs
new file mode 100644
index 0000000..99cfb83
--- /dev/null
+++ b/generate.mjs
@@ -0,0 +1,127 @@
+import fs from 'fs';
+const M = JSON.parse(fs.readFileSync('videos/site-tour/assets/manifest.json', 'utf8'));
+const W = 1920, H = 1080;
+const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
+const FRONT_DUR = 1.25, ADMIN_DUR = 1.6;
+const clips = [];   // {html}
+const tweens = [];  // gsap lines
+
+const bgFill = (id) => `#${id}`;
+let t = 0;
+
+// helpers
+function card(id, tag, title, sub, start, dur, accent) {
+  clips.push(`  <section id="${id}" class="clip card" data-start="${start}" data-duration="${dur}" data-track-index="1">
+    <div class="card-inner">
+      ${tag ? `<div class="card-tag" id="${id}-tag" style="color:${accent}">${esc(tag)}</div>` : ''}
+      <h1 id="${id}-title">${esc(title)}</h1>
+      ${sub ? `<p id="${id}-sub">${esc(sub)}</p>` : ''}
+    </div>
+  </section>`);
+  tweens.push(`tl.from('#${id}-title',{y:36,autoAlpha:0,duration:0.6,ease:'power3.out'},${start.toFixed(3)});`);
+  if (tag) tweens.push(`tl.from('#${id}-tag',{y:-18,autoAlpha:0,duration:0.5,ease:'power2.out'},${(start+0.1).toFixed(3)});`);
+  if (sub) tweens.push(`tl.from('#${id}-sub',{y:20,autoAlpha:0,duration:0.5,ease:'power2.out'},${(start+0.25).toFixed(3)});`);
+}
+
+function frame(id, src, label, idx, count, actTag, accent, start, dur) {
+  // .clip WRAPPER (framework controls its visibility) with an inner <img> we animate.
+  clips.push(`  <div id="${id}" class="clip shot" data-start="${start}" data-duration="${dur}" data-track-index="2">
+    <img id="${id}-img" class="shot-img" src="${src}" alt="${esc(label)}">
+  </div>`);
+  clips.push(`  <div id="${id}-lbl" class="clip label" data-start="${start}" data-duration="${dur}" data-track-index="3">
+    <div class="lbl-inner" id="${id}-lbl-in">
+      <span class="lbl-num" style="background:${accent}">${String(idx).padStart(2,'0')} / ${count}</span>
+      <span class="lbl-txt">${esc(label)}</span>
+    </div>
+  </div>`);
+  // fade the inner img in, then a slow ken-burns zoom-out across the whole clip.
+  tweens.push(`tl.from('#${id}-img',{opacity:0,duration:0.35,ease:'power1.out'},${start.toFixed(3)});`);
+  tweens.push(`tl.fromTo('#${id}-img',{scale:1.06},{scale:1.0,duration:${dur.toFixed(3)},ease:'none'},${start.toFixed(3)});`);
+  tweens.push(`tl.from('#${id}-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},${(start+0.05).toFixed(3)});`);
+}
+
+// ---- TITLE ----
+card('title', 'RENTV.COM', 'Full Site Walkthrough', 'Every tab — Front End & Admin Backend', 0, 2.5, '#57b0ff');
+t = 2.5;
+
+// ---- FRONT END ACT ----
+const FA_START = t;
+card('front-act', 'ACT I', 'Front End', 'The public CRE news platform', t, 1.5, '#57b0ff');
+t += 1.5;
+const frontStart = t;
+M.front.forEach((f, i) => {
+  const st = frontStart + i * FRONT_DUR;
+  frame(`f${i}`, `assets/front/${f.file.split('/').pop()}`, f.label, i + 1, M.front.length, 'FRONT END', '#0a6cff', st, FRONT_DUR);
+});
+const frontEnd = frontStart + M.front.length * FRONT_DUR;
+// persistent act corner tag
+clips.push(`  <div id="tag-front" class="clip corner-tag" data-start="${frontStart}" data-duration="${(frontEnd-frontStart).toFixed(3)}" data-track-index="4"><span class="dot" style="background:#57b0ff"></span>FRONT END · user</div>`);
+tweens.push(`tl.from('#tag-front',{autoAlpha:0,duration:0.4},${frontStart.toFixed(3)});`);
+t = frontEnd;
+
+// ---- ADMIN ACT ----
+card('admin-act', 'ACT II', 'Admin Backend', 'Internal intelligence — admin only', t, 1.5, '#c8a15a');
+t += 1.5;
+const adminStart = t;
+M.admin.forEach((a, i) => {
+  const st = adminStart + i * ADMIN_DUR;
+  frame(`a${i}`, `assets/admin/${a.file.split('/').pop()}`, a.label, i + 1, M.admin.length, 'ADMIN', '#c8a15a', st, ADMIN_DUR);
+});
+const adminEnd = adminStart + M.admin.length * ADMIN_DUR;
+clips.push(`  <div id="tag-admin" class="clip corner-tag" data-start="${adminStart}" data-duration="${(adminEnd-adminStart).toFixed(3)}" data-track-index="4"><span class="dot" style="background:#c8a15a"></span>ADMIN · backend</div>`);
+tweens.push(`tl.from('#tag-admin',{autoAlpha:0,duration:0.4},${adminStart.toFixed(3)});`);
+t = adminEnd;
+
+// ---- OUTRO ----
+card('outro', '', 'rentv.agentabrams.com', 'One unified app · user + admin', t, 2.5, '#57b0ff');
+const TOTAL = +(t + 2.5).toFixed(2);
+
+const html = `<!doctype html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=1920, height=1080" />
+<title>RENTV — Full Site Walkthrough</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; }
+  .shot-img { width:100%; height:100%; object-fit:cover; object-position:top center; will-change:transform,opacity; display:block; }
+  .card { display:grid; place-items:center; }
+  .card-inner { text-align:center; padding:0 120px; }
+  .card-tag { font-size:22px; letter-spacing:.42em; font-weight:600; margin-bottom:22px; }
+  .card h1 { margin:0; color:#f3f7fc; font-size:104px; font-weight:500; letter-spacing:-.01em; line-height:1.02; }
+  .card p { margin:22px 0 0; color:#8ea3b8; font-size:30px; font-weight:300; letter-spacing:.01em; }
+  .label { left:56px; bottom:56px; right:auto; top:auto; width:auto; height:auto; }
+  .lbl-inner { display:flex; align-items:center; gap:16px; }
+  .label .lbl-num { color:#fff; font-size:20px; font-weight:600; letter-spacing:.05em; padding:9px 14px; border-radius:8px; box-shadow:0 6px 20px rgba(0,0,0,.35); }
+  .label .lbl-txt { color:#fff; font-size:30px; font-weight:500; letter-spacing:.005em;
+           background:rgba(9,15,24,.9); padding:9px 20px; border-radius:10px;
+           box-shadow:0 6px 20px rgba(0,0,0,.35); }
+  .corner-tag { display:flex; align-items:center; gap:11px; right:56px; top:50px; left:auto; bottom:auto;
+           width:auto; height:auto; color:#cdd9e6; font-size:19px; font-weight:600; letter-spacing:.14em;
+           background:rgba(9,15,24,.6); padding:9px 17px; border-radius:999px; }
+  .corner-tag .dot { width:10px; height:10px; border-radius:50%; display:inline-block; }
+</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>
+${clips.join('\n')}
+  </div>
+  <script>
+    window.__timelines = window.__timelines || {};
+    const tl = gsap.timeline({ paused: true });
+${tweens.map(x => '    ' + x).join('\n')}
+    window.__timelines['main'] = tl;
+  </script>
+</body>
+</html>
+`;
+
+fs.writeFileSync('videos/site-tour/index.html', html);
+console.log(`Wrote index.html — ${TOTAL}s, ${M.front.length} front + ${M.admin.length} admin frames, ${clips.length} clips, ${tweens.length} tweens`);
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..f73fc5f
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,63 @@
+{
+  "name": "rentv-tour",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "rentv-tour",
+      "version": "1.0.0",
+      "license": "ISC",
+      "devDependencies": {
+        "playwright": "^1.62.0"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/playwright": {
+      "version": "1.62.0",
+      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz",
+      "integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "playwright-core": "1.62.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "optionalDependencies": {
+        "fsevents": "2.3.2"
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.62.0",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.0.tgz",
+      "integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=20"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fd06570
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "rentv-tour",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "type": "commonjs",
+  "devDependencies": {
+    "playwright": "^1.62.0"
+  }
+}
diff --git a/videos/site-tour/AGENTS.md b/videos/site-tour/AGENTS.md
new file mode 100644
index 0000000..c4ff0ae
--- /dev/null
+++ b/videos/site-tour/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/site-tour/CLAUDE.md b/videos/site-tour/CLAUDE.md
new file mode 100644
index 0000000..c4ff0ae
--- /dev/null
+++ b/videos/site-tour/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/site-tour/hyperframes.json b/videos/site-tour/hyperframes.json
new file mode 100644
index 0000000..ec2e3c2
--- /dev/null
+++ b/videos/site-tour/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/site-tour/index.html b/videos/site-tour/index.html
new file mode 100644
index 0000000..733585a
--- /dev/null
+++ b/videos/site-tour/index.html
@@ -0,0 +1,457 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=1920, height=1080" />
+<title>RENTV — Full Site Walkthrough</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; }
+  .shot-img { width:100%; height:100%; object-fit:cover; object-position:top center; will-change:transform,opacity; display:block; }
+  .card { display:grid; place-items:center; }
+  .card-inner { text-align:center; padding:0 120px; }
+  .card-tag { font-size:22px; letter-spacing:.42em; font-weight:600; margin-bottom:22px; }
+  .card h1 { margin:0; color:#f3f7fc; font-size:104px; font-weight:500; letter-spacing:-.01em; line-height:1.02; }
+  .card p { margin:22px 0 0; color:#8ea3b8; font-size:30px; font-weight:300; letter-spacing:.01em; }
+  .label { left:56px; bottom:56px; right:auto; top:auto; width:auto; height:auto; }
+  .lbl-inner { display:flex; align-items:center; gap:16px; }
+  .label .lbl-num { color:#fff; font-size:20px; font-weight:600; letter-spacing:.05em; padding:9px 14px; border-radius:8px; box-shadow:0 6px 20px rgba(0,0,0,.35); }
+  .label .lbl-txt { color:#fff; font-size:30px; font-weight:500; letter-spacing:.005em;
+           background:rgba(9,15,24,.9); padding:9px 20px; border-radius:10px;
+           box-shadow:0 6px 20px rgba(0,0,0,.35); }
+  .corner-tag { display:flex; align-items:center; gap:11px; right:56px; top:50px; left:auto; bottom:auto;
+           width:auto; height:auto; color:#cdd9e6; font-size:19px; font-weight:600; letter-spacing:.14em;
+           background:rgba(9,15,24,.6); padding:9px 17px; border-radius:999px; }
+  .corner-tag .dot { width:10px; height:10px; border-radius:50%; display:inline-block; }
+</style>
+</head>
+<body>
+  <div id="root" data-composition-id="main" data-start="0" data-width="1920" data-height="1080" data-duration="49.2" data-fps="30">
+  <div id="bg" class="clip" data-start="0" data-duration="49.2" data-track-index="0"></div>
+  <section id="title" class="clip card" data-start="0" data-duration="2.5" data-track-index="1">
+    <div class="card-inner">
+      <div class="card-tag" id="title-tag" style="color:#57b0ff">RENTV.COM</div>
+      <h1 id="title-title">Full Site Walkthrough</h1>
+      <p id="title-sub">Every tab — Front End &amp; Admin Backend</p>
+    </div>
+  </section>
+  <section id="front-act" class="clip card" data-start="2.5" data-duration="1.5" data-track-index="1">
+    <div class="card-inner">
+      <div class="card-tag" id="front-act-tag" style="color:#57b0ff">ACT I</div>
+      <h1 id="front-act-title">Front End</h1>
+      <p id="front-act-sub">The public CRE news platform</p>
+    </div>
+  </section>
+  <div id="f0" class="clip shot" data-start="4" data-duration="1.25" data-track-index="2">
+    <img id="f0-img" class="shot-img" src="assets/front/00-home.png" alt="Home">
+  </div>
+  <div id="f0-lbl" class="clip label" data-start="4" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f0-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">01 / 24</span>
+      <span class="lbl-txt">Home</span>
+    </div>
+  </div>
+  <div id="f1" class="clip shot" data-start="5.25" data-duration="1.25" data-track-index="2">
+    <img id="f1-img" class="shot-img" src="assets/front/01-hub.png" alt="Intelligence Hub">
+  </div>
+  <div id="f1-lbl" class="clip label" data-start="5.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f1-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">02 / 24</span>
+      <span class="lbl-txt">Intelligence Hub</span>
+    </div>
+  </div>
+  <div id="f2" class="clip shot" data-start="6.5" data-duration="1.25" data-track-index="2">
+    <img id="f2-img" class="shot-img" src="assets/front/02-search.png" alt="Search">
+  </div>
+  <div id="f2-lbl" class="clip label" data-start="6.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f2-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">03 / 24</span>
+      <span class="lbl-txt">Search</span>
+    </div>
+  </div>
+  <div id="f3" class="clip shot" data-start="7.75" data-duration="1.25" data-track-index="2">
+    <img id="f3-img" class="shot-img" src="assets/front/03-market-pulse.png" alt="Rates &amp; Capital Markets">
+  </div>
+  <div id="f3-lbl" class="clip label" data-start="7.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f3-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">04 / 24</span>
+      <span class="lbl-txt">Rates &amp; Capital Markets</span>
+    </div>
+  </div>
+  <div id="f4" class="clip shot" data-start="9" data-duration="1.25" data-track-index="2">
+    <img id="f4-img" class="shot-img" src="assets/front/04-pulse.png" alt="Market Pulse">
+  </div>
+  <div id="f4-lbl" class="clip label" data-start="9" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f4-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">05 / 24</span>
+      <span class="lbl-txt">Market Pulse</span>
+    </div>
+  </div>
+  <div id="f5" class="clip shot" data-start="10.25" data-duration="1.25" data-track-index="2">
+    <img id="f5-img" class="shot-img" src="assets/front/05-sector.png" alt="Sector Deep-Dives">
+  </div>
+  <div id="f5-lbl" class="clip label" data-start="10.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f5-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">06 / 24</span>
+      <span class="lbl-txt">Sector Deep-Dives</span>
+    </div>
+  </div>
+  <div id="f6" class="clip shot" data-start="11.5" data-duration="1.25" data-track-index="2">
+    <img id="f6-img" class="shot-img" src="assets/front/06-metros.png" alt="Deal Flow by Metro">
+  </div>
+  <div id="f6-lbl" class="clip label" data-start="11.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f6-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">07 / 24</span>
+      <span class="lbl-txt">Deal Flow by Metro</span>
+    </div>
+  </div>
+  <div id="f7" class="clip shot" data-start="12.75" data-duration="1.25" data-track-index="2">
+    <img id="f7-img" class="shot-img" src="assets/front/07-tools.png" alt="CRE Toolkit">
+  </div>
+  <div id="f7-lbl" class="clip label" data-start="12.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f7-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">08 / 24</span>
+      <span class="lbl-txt">CRE Toolkit</span>
+    </div>
+  </div>
+  <div id="f8" class="clip shot" data-start="14" data-duration="1.25" data-track-index="2">
+    <img id="f8-img" class="shot-img" src="assets/front/08-watchlist.png" alt="Deal Watchlist">
+  </div>
+  <div id="f8-lbl" class="clip label" data-start="14" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f8-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">09 / 24</span>
+      <span class="lbl-txt">Deal Watchlist</span>
+    </div>
+  </div>
+  <div id="f9" class="clip shot" data-start="15.25" data-duration="1.25" data-track-index="2">
+    <img id="f9-img" class="shot-img" src="assets/front/09-directory.png" alt="CRE Directory">
+  </div>
+  <div id="f9-lbl" class="clip label" data-start="15.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f9-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">10 / 24</span>
+      <span class="lbl-txt">CRE Directory</span>
+    </div>
+  </div>
+  <div id="f10" class="clip shot" data-start="16.5" data-duration="1.25" data-track-index="2">
+    <img id="f10-img" class="shot-img" src="assets/front/10-compare.png" alt="Deal Comparison">
+  </div>
+  <div id="f10-lbl" class="clip label" data-start="16.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f10-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">11 / 24</span>
+      <span class="lbl-txt">Deal Comparison</span>
+    </div>
+  </div>
+  <div id="f11" class="clip shot" data-start="17.75" data-duration="1.25" data-track-index="2">
+    <img id="f11-img" class="shot-img" src="assets/front/11-brief.png" alt="The RENTV Brief">
+  </div>
+  <div id="f11-lbl" class="clip label" data-start="17.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f11-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">12 / 24</span>
+      <span class="lbl-txt">The RENTV Brief</span>
+    </div>
+  </div>
+  <div id="f12" class="clip shot" data-start="19" data-duration="1.25" data-track-index="2">
+    <img id="f12-img" class="shot-img" src="assets/front/12-syndicate.png" alt="Syndicate">
+  </div>
+  <div id="f12-lbl" class="clip label" data-start="19" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f12-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">13 / 24</span>
+      <span class="lbl-txt">Syndicate</span>
+    </div>
+  </div>
+  <div id="f13" class="clip shot" data-start="20.25" data-duration="1.25" data-track-index="2">
+    <img id="f13-img" class="shot-img" src="assets/front/13-financing.png" alt="Sections — Financing">
+  </div>
+  <div id="f13-lbl" class="clip label" data-start="20.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f13-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">14 / 24</span>
+      <span class="lbl-txt">Sections — Financing</span>
+    </div>
+  </div>
+  <div id="f14" class="clip shot" data-start="21.5" data-duration="1.25" data-track-index="2">
+    <img id="f14-img" class="shot-img" src="assets/front/14-deals.png" alt="Deals">
+  </div>
+  <div id="f14-lbl" class="clip label" data-start="21.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f14-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">15 / 24</span>
+      <span class="lbl-txt">Deals</span>
+    </div>
+  </div>
+  <div id="f15" class="clip shot" data-start="22.75" data-duration="1.25" data-track-index="2">
+    <img id="f15-img" class="shot-img" src="assets/front/15-markets.png" alt="Markets">
+  </div>
+  <div id="f15-lbl" class="clip label" data-start="22.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f15-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">16 / 24</span>
+      <span class="lbl-txt">Markets</span>
+    </div>
+  </div>
+  <div id="f16" class="clip shot" data-start="24" data-duration="1.25" data-track-index="2">
+    <img id="f16-img" class="shot-img" src="assets/front/16-la-commercial.png" alt="LA Commercial">
+  </div>
+  <div id="f16-lbl" class="clip label" data-start="24" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f16-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">17 / 24</span>
+      <span class="lbl-txt">LA Commercial</span>
+    </div>
+  </div>
+  <div id="f17" class="clip shot" data-start="25.25" data-duration="1.25" data-track-index="2">
+    <img id="f17-img" class="shot-img" src="assets/front/17-review.png" alt="The REview">
+  </div>
+  <div id="f17-lbl" class="clip label" data-start="25.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f17-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">18 / 24</span>
+      <span class="lbl-txt">The REview</span>
+    </div>
+  </div>
+  <div id="f18" class="clip shot" data-start="26.5" data-duration="1.25" data-track-index="2">
+    <img id="f18-img" class="shot-img" src="assets/front/18-cre-talk.png" alt="CRE Talk">
+  </div>
+  <div id="f18-lbl" class="clip label" data-start="26.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f18-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">19 / 24</span>
+      <span class="lbl-txt">CRE Talk</span>
+    </div>
+  </div>
+  <div id="f19" class="clip shot" data-start="27.75" data-duration="1.25" data-track-index="2">
+    <img id="f19-img" class="shot-img" src="assets/front/19-conferences.png" alt="Conferences">
+  </div>
+  <div id="f19-lbl" class="clip label" data-start="27.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f19-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">20 / 24</span>
+      <span class="lbl-txt">Conferences</span>
+    </div>
+  </div>
+  <div id="f20" class="clip shot" data-start="29" data-duration="1.25" data-track-index="2">
+    <img id="f20-img" class="shot-img" src="assets/front/20-blog.png" alt="Blog">
+  </div>
+  <div id="f20-lbl" class="clip label" data-start="29" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f20-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">21 / 24</span>
+      <span class="lbl-txt">Blog</span>
+    </div>
+  </div>
+  <div id="f21" class="clip shot" data-start="30.25" data-duration="1.25" data-track-index="2">
+    <img id="f21-img" class="shot-img" src="assets/front/21-about.png" alt="About">
+  </div>
+  <div id="f21-lbl" class="clip label" data-start="30.25" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f21-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">22 / 24</span>
+      <span class="lbl-txt">About</span>
+    </div>
+  </div>
+  <div id="f22" class="clip shot" data-start="31.5" data-duration="1.25" data-track-index="2">
+    <img id="f22-img" class="shot-img" src="assets/front/22-advertise.png" alt="Advertise">
+  </div>
+  <div id="f22-lbl" class="clip label" data-start="31.5" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f22-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">23 / 24</span>
+      <span class="lbl-txt">Advertise</span>
+    </div>
+  </div>
+  <div id="f23" class="clip shot" data-start="32.75" data-duration="1.25" data-track-index="2">
+    <img id="f23-img" class="shot-img" src="assets/front/23-subscribe.png" alt="Subscribe / Newsletters">
+  </div>
+  <div id="f23-lbl" class="clip label" data-start="32.75" data-duration="1.25" data-track-index="3">
+    <div class="lbl-inner" id="f23-lbl-in">
+      <span class="lbl-num" style="background:#0a6cff">24 / 24</span>
+      <span class="lbl-txt">Subscribe / Newsletters</span>
+    </div>
+  </div>
+  <div id="tag-front" class="clip corner-tag" data-start="4" data-duration="30.000" data-track-index="4"><span class="dot" style="background:#57b0ff"></span>FRONT END · user</div>
+  <section id="admin-act" class="clip card" data-start="34" data-duration="1.5" data-track-index="1">
+    <div class="card-inner">
+      <div class="card-tag" id="admin-act-tag" style="color:#c8a15a">ACT II</div>
+      <h1 id="admin-act-title">Admin Backend</h1>
+      <p id="admin-act-sub">Internal intelligence — admin only</p>
+    </div>
+  </section>
+  <div id="a0" class="clip shot" data-start="35.5" data-duration="1.6" data-track-index="2">
+    <img id="a0-img" class="shot-img" src="assets/admin/00-backend.png" alt="Backend Hub">
+  </div>
+  <div id="a0-lbl" class="clip label" data-start="35.5" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a0-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">01 / 7</span>
+      <span class="lbl-txt">Backend Hub</span>
+    </div>
+  </div>
+  <div id="a1" class="clip shot" data-start="37.1" data-duration="1.6" data-track-index="2">
+    <img id="a1-img" class="shot-img" src="assets/admin/01-desk.png" alt="Broker &amp; Owner Desk">
+  </div>
+  <div id="a1-lbl" class="clip label" data-start="37.1" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a1-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">02 / 7</span>
+      <span class="lbl-txt">Broker &amp; Owner Desk</span>
+    </div>
+  </div>
+  <div id="a2" class="clip shot" data-start="38.7" data-duration="1.6" data-track-index="2">
+    <img id="a2-img" class="shot-img" src="assets/admin/02-audience.png" alt="Audience &amp; CRM">
+  </div>
+  <div id="a2-lbl" class="clip label" data-start="38.7" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a2-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">03 / 7</span>
+      <span class="lbl-txt">Audience &amp; CRM</span>
+    </div>
+  </div>
+  <div id="a3" class="clip shot" data-start="40.3" data-duration="1.6" data-track-index="2">
+    <img id="a3-img" class="shot-img" src="assets/admin/03-admin.png" alt="Admin Dashboards">
+  </div>
+  <div id="a3-lbl" class="clip label" data-start="40.3" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a3-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">04 / 7</span>
+      <span class="lbl-txt">Admin Dashboards</span>
+    </div>
+  </div>
+  <div id="a4" class="clip shot" data-start="41.9" data-duration="1.6" data-track-index="2">
+    <img id="a4-img" class="shot-img" src="assets/admin/04-versions.png" alt="Site Versions">
+  </div>
+  <div id="a4-lbl" class="clip label" data-start="41.9" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a4-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">05 / 7</span>
+      <span class="lbl-txt">Site Versions</span>
+    </div>
+  </div>
+  <div id="a5" class="clip shot" data-start="43.5" data-duration="1.6" data-track-index="2">
+    <img id="a5-img" class="shot-img" src="assets/admin/05-consulting.png" alt="Consulting">
+  </div>
+  <div id="a5-lbl" class="clip label" data-start="43.5" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a5-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">06 / 7</span>
+      <span class="lbl-txt">Consulting</span>
+    </div>
+  </div>
+  <div id="a6" class="clip shot" data-start="45.1" data-duration="1.6" data-track-index="2">
+    <img id="a6-img" class="shot-img" src="assets/admin/06-press.png" alt="Video Library">
+  </div>
+  <div id="a6-lbl" class="clip label" data-start="45.1" data-duration="1.6" data-track-index="3">
+    <div class="lbl-inner" id="a6-lbl-in">
+      <span class="lbl-num" style="background:#c8a15a">07 / 7</span>
+      <span class="lbl-txt">Video Library</span>
+    </div>
+  </div>
+  <div id="tag-admin" class="clip corner-tag" data-start="35.5" data-duration="11.200" data-track-index="4"><span class="dot" style="background:#c8a15a"></span>ADMIN · backend</div>
+  <section id="outro" class="clip card" data-start="46.7" data-duration="2.5" data-track-index="1">
+    <div class="card-inner">
+      
+      <h1 id="outro-title">rentv.agentabrams.com</h1>
+      <p id="outro-sub">One unified app · user + admin</p>
+    </div>
+  </section>
+  </div>
+  <script>
+    window.__timelines = window.__timelines || {};
+    const tl = gsap.timeline({ paused: true });
+    tl.from('#title-title',{y:36,autoAlpha:0,duration:0.6,ease:'power3.out'},0.000);
+    tl.from('#title-tag',{y:-18,autoAlpha:0,duration:0.5,ease:'power2.out'},0.100);
+    tl.from('#title-sub',{y:20,autoAlpha:0,duration:0.5,ease:'power2.out'},0.250);
+    tl.from('#front-act-title',{y:36,autoAlpha:0,duration:0.6,ease:'power3.out'},2.500);
+    tl.from('#front-act-tag',{y:-18,autoAlpha:0,duration:0.5,ease:'power2.out'},2.600);
+    tl.from('#front-act-sub',{y:20,autoAlpha:0,duration:0.5,ease:'power2.out'},2.750);
+    tl.from('#f0-img',{opacity:0,duration:0.35,ease:'power1.out'},4.000);
+    tl.fromTo('#f0-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},4.000);
+    tl.from('#f0-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},4.050);
+    tl.from('#f1-img',{opacity:0,duration:0.35,ease:'power1.out'},5.250);
+    tl.fromTo('#f1-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},5.250);
+    tl.from('#f1-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},5.300);
+    tl.from('#f2-img',{opacity:0,duration:0.35,ease:'power1.out'},6.500);
+    tl.fromTo('#f2-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},6.500);
+    tl.from('#f2-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},6.550);
+    tl.from('#f3-img',{opacity:0,duration:0.35,ease:'power1.out'},7.750);
+    tl.fromTo('#f3-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},7.750);
+    tl.from('#f3-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},7.800);
+    tl.from('#f4-img',{opacity:0,duration:0.35,ease:'power1.out'},9.000);
+    tl.fromTo('#f4-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},9.000);
+    tl.from('#f4-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},9.050);
+    tl.from('#f5-img',{opacity:0,duration:0.35,ease:'power1.out'},10.250);
+    tl.fromTo('#f5-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},10.250);
+    tl.from('#f5-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},10.300);
+    tl.from('#f6-img',{opacity:0,duration:0.35,ease:'power1.out'},11.500);
+    tl.fromTo('#f6-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},11.500);
+    tl.from('#f6-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},11.550);
+    tl.from('#f7-img',{opacity:0,duration:0.35,ease:'power1.out'},12.750);
+    tl.fromTo('#f7-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},12.750);
+    tl.from('#f7-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},12.800);
+    tl.from('#f8-img',{opacity:0,duration:0.35,ease:'power1.out'},14.000);
+    tl.fromTo('#f8-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},14.000);
+    tl.from('#f8-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},14.050);
+    tl.from('#f9-img',{opacity:0,duration:0.35,ease:'power1.out'},15.250);
+    tl.fromTo('#f9-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},15.250);
+    tl.from('#f9-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},15.300);
+    tl.from('#f10-img',{opacity:0,duration:0.35,ease:'power1.out'},16.500);
+    tl.fromTo('#f10-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},16.500);
+    tl.from('#f10-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},16.550);
+    tl.from('#f11-img',{opacity:0,duration:0.35,ease:'power1.out'},17.750);
+    tl.fromTo('#f11-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},17.750);
+    tl.from('#f11-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},17.800);
+    tl.from('#f12-img',{opacity:0,duration:0.35,ease:'power1.out'},19.000);
+    tl.fromTo('#f12-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},19.000);
+    tl.from('#f12-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},19.050);
+    tl.from('#f13-img',{opacity:0,duration:0.35,ease:'power1.out'},20.250);
+    tl.fromTo('#f13-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},20.250);
+    tl.from('#f13-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},20.300);
+    tl.from('#f14-img',{opacity:0,duration:0.35,ease:'power1.out'},21.500);
+    tl.fromTo('#f14-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},21.500);
+    tl.from('#f14-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},21.550);
+    tl.from('#f15-img',{opacity:0,duration:0.35,ease:'power1.out'},22.750);
+    tl.fromTo('#f15-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},22.750);
+    tl.from('#f15-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},22.800);
+    tl.from('#f16-img',{opacity:0,duration:0.35,ease:'power1.out'},24.000);
+    tl.fromTo('#f16-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},24.000);
+    tl.from('#f16-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},24.050);
+    tl.from('#f17-img',{opacity:0,duration:0.35,ease:'power1.out'},25.250);
+    tl.fromTo('#f17-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},25.250);
+    tl.from('#f17-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},25.300);
+    tl.from('#f18-img',{opacity:0,duration:0.35,ease:'power1.out'},26.500);
+    tl.fromTo('#f18-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},26.500);
+    tl.from('#f18-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},26.550);
+    tl.from('#f19-img',{opacity:0,duration:0.35,ease:'power1.out'},27.750);
+    tl.fromTo('#f19-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},27.750);
+    tl.from('#f19-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},27.800);
+    tl.from('#f20-img',{opacity:0,duration:0.35,ease:'power1.out'},29.000);
+    tl.fromTo('#f20-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},29.000);
+    tl.from('#f20-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},29.050);
+    tl.from('#f21-img',{opacity:0,duration:0.35,ease:'power1.out'},30.250);
+    tl.fromTo('#f21-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},30.250);
+    tl.from('#f21-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},30.300);
+    tl.from('#f22-img',{opacity:0,duration:0.35,ease:'power1.out'},31.500);
+    tl.fromTo('#f22-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},31.500);
+    tl.from('#f22-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},31.550);
+    tl.from('#f23-img',{opacity:0,duration:0.35,ease:'power1.out'},32.750);
+    tl.fromTo('#f23-img',{scale:1.06},{scale:1.0,duration:1.250,ease:'none'},32.750);
+    tl.from('#f23-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},32.800);
+    tl.from('#tag-front',{autoAlpha:0,duration:0.4},4.000);
+    tl.from('#admin-act-title',{y:36,autoAlpha:0,duration:0.6,ease:'power3.out'},34.000);
+    tl.from('#admin-act-tag',{y:-18,autoAlpha:0,duration:0.5,ease:'power2.out'},34.100);
+    tl.from('#admin-act-sub',{y:20,autoAlpha:0,duration:0.5,ease:'power2.out'},34.250);
+    tl.from('#a0-img',{opacity:0,duration:0.35,ease:'power1.out'},35.500);
+    tl.fromTo('#a0-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},35.500);
+    tl.from('#a0-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},35.550);
+    tl.from('#a1-img',{opacity:0,duration:0.35,ease:'power1.out'},37.100);
+    tl.fromTo('#a1-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},37.100);
+    tl.from('#a1-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},37.150);
+    tl.from('#a2-img',{opacity:0,duration:0.35,ease:'power1.out'},38.700);
+    tl.fromTo('#a2-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},38.700);
+    tl.from('#a2-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},38.750);
+    tl.from('#a3-img',{opacity:0,duration:0.35,ease:'power1.out'},40.300);
+    tl.fromTo('#a3-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},40.300);
+    tl.from('#a3-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},40.350);
+    tl.from('#a4-img',{opacity:0,duration:0.35,ease:'power1.out'},41.900);
+    tl.fromTo('#a4-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},41.900);
+    tl.from('#a4-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},41.950);
+    tl.from('#a5-img',{opacity:0,duration:0.35,ease:'power1.out'},43.500);
+    tl.fromTo('#a5-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},43.500);
+    tl.from('#a5-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},43.550);
+    tl.from('#a6-img',{opacity:0,duration:0.35,ease:'power1.out'},45.100);
+    tl.fromTo('#a6-img',{scale:1.06},{scale:1.0,duration:1.600,ease:'none'},45.100);
+    tl.from('#a6-lbl-in',{y:26,autoAlpha:0,duration:0.4,ease:'power2.out'},45.150);
+    tl.from('#tag-admin',{autoAlpha:0,duration:0.4},35.500);
+    tl.from('#outro-title',{y:36,autoAlpha:0,duration:0.6,ease:'power3.out'},46.700);
+    tl.from('#outro-sub',{y:20,autoAlpha:0,duration:0.5,ease:'power2.out'},46.950);
+    window.__timelines['main'] = tl;
+  </script>
+</body>
+</html>
diff --git a/videos/site-tour/meta.json b/videos/site-tour/meta.json
new file mode 100644
index 0000000..2d08e44
--- /dev/null
+++ b/videos/site-tour/meta.json
@@ -0,0 +1,5 @@
+{
+  "id": "site-tour",
+  "name": "site-tour",
+  "createdAt": "2026-07-28T19:18:16.809Z"
+}
\ No newline at end of file
diff --git a/videos/site-tour/package.json b/videos/site-tour/package.json
new file mode 100644
index 0000000..1b02c4b
--- /dev/null
+++ b/videos/site-tour/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "site-tour",
+  "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"
+  }
+}

(oldest)  ·  back to Rentv Tour  ·  Add Steve cloned-voice narration (ElevenLabs) + re-pace tour 264d1c9 →