← back to Rentv Master Reel
master-gen.mjs
81 lines
import fs from 'fs';
const W = 1920, H = 1080;
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
// id, capture file (in media/captures/), on-screen label, narration text (Steve voice)
export const SCENES = [
['01a', 'home', 'A note for Boomer', "Boomer — Steve here. Before anything else: what you've built with RENTV is remarkable. For decades it's been the place the West Coast commercial real estate world goes to find out what actually closed, who financed it, and what it traded for. That reputation is the hard part — and you already own it."],
['01b', 'home', 'A note for Boomer', "So everything you're about to see is just a set of suggestions — ideas we sketched on top of your live feed, running on your real stories, built in a single day. None of it is a critique. It's a fan letter with working code attached. Take what's useful, ignore the rest, and know that every call stays yours."],
['02a', 'home', 'The front page', "Let's start where your readers start — the front page. The idea here is simple: let the brand lead, and let the wire run underneath it. RENTV Originals sits right up top, your editorial voice front and center, and then the live wire begins."],
['02b', 'home', 'The front page', "Every headline below it is your actual feed, pulled live from rentv dot com and updating all day — The Swig Company's refinancing in San Francisco, Tailwind's Sacramento buy. And the full section map is right there: Sales, Leases, Financing, Development, Multifamily, Retail, Industrial, and L.A. Commercial — so a broker who only cares about one lane gets straight to it."],
['03', 'article', 'In-shell reader', "When a reader clicks a headline, the story opens right inside the site — same masthead, same navigation, no jumping away. The photo's there, the category tag is there, and related stories wait at the bottom, so the natural next click is another RENTV story. It keeps people in your world — reading one more piece, and then another."],
['04', 'review', 'The REview', "The REview deserves its own stage, so we gave it one. Face to face with the dealmakers — your interview franchise, presented like the flagship it is. This is content nobody else in the space has: the people who actually move the buildings, on camera, in their own words."],
['05', 'markets', 'Live markets', "Here's a live Markets page — the ten-year treasury, capital-markets rates, the REIT board, the numbers a commercial real estate reader checks every single morning. It's built on public data feeds, so it refreshes on its own — no data entry, no upkeep. A reason to open RENTV before the first phone call of the day."],
['05b', 'pulse', 'Market Pulse', "And layered on top of the deals you already track, a Market Pulse — total transaction volume, deal count, the biggest trades, sliced by property type and by market. It turns your reporting into a dashboard the whole industry would check."],
['06', 'cretalk', 'CRE Talk', "CRE Talk — your video conversations, collected in one clean place, easy to browse and easy to play. The interviews are already great; this just gives them a proper home next to everything else."],
['07i', 'versions', '10 site versions', "Now the fun part. Instead of asking you to imagine a redesign, we built ten complete versions of the site — whole directions, not mockups, every one running your live stories right now. Let me walk you through them, so you can just point at the one that feels like RENTV."],
['07a', 'v-wire', 'The Wire', "The Wire — the modern newsroom. A lead story, a three-column river of headlines, a right rail for what's trending. Clean, fast, familiar to anyone who reads news online."],
['07b', 'v-broadsheet', 'Broadsheet', "Broadsheet — this one reads like a printed paper on the web. Dense serif columns, small photos, a front page that feels like the business section you grew up with."],
['07c', 'v-terminal', 'Terminal', "Terminal — a dark, data-first deal desk. If your reader lives in a Bloomberg window all day, this is the RENTV that feels like home."],
['07d', 'v-magazine', 'Magazine', "Magazine — big and editorial. One oversized feature up top, generous photography, the look of a glossy quarterly."],
['07e', 'v-dashboard', 'Dashboard', "Dashboard — a midnight command view. Every surface at a glance, for the reader who wants the whole market on one screen. And there are more still — Daily Digest, Photo Grid, Split, Minimal, and Feed. Ten directions; pick the one that's you."],
['08', 'theme-midnight', '10 themes', "On top of the versions, ten professional themes. One click in the toolbar restyles the entire site — RENTV Classic, Broadsheet, F.T. Salmon, Bloomberg Terminal, Midnight Capital, and more. Same content, different suit — real financial-publication palettes, and the site remembers each reader's choice."],
['09a', 'admin-index', 'Admin dashboards', "For your team, we sketched ten admin dashboards — because the newsroom behind the site matters as much as the site. A command center that puts every surface on one screen: subscribers, stories, videos, the feed."],
['09b', 'admin-command', 'Admin dashboards', "A kanban board for moving work from pitch to published. Analytics for what readers actually open. A classic layout, a sidebar, a split view, a console, cards, a clean table, and a minimal desk. Same live data in every one — pick the workflow that matches how your people actually work."],
['09c', 'desk', 'Broker & Owner Desk', "And underneath it, the real asset: a Broker and Owner Desk sitting on a live registry — hundreds of thousands of brokers, tens of thousands of firms, and a sublease desk. The kind of data most publishers would kill for, and you already have it in the backyard."],
['09d', 'social', 'Social command center', "Plus a social command center — LinkedIn, X, and Instagram in one place, a content calendar, and draft posts ready to go, so every deal you report can reach the audiences that live on those platforms."],
['10', 'blog', 'Publishing desk', "And a simple publishing desk. Write a post, hit publish, and it's live on the blog — no plugins, no fighting a content system. A subscriber list builds alongside it, so the newsletter audience you've earned has a place to keep growing."],
['11', 'home', 'rentv.agentabrams.com', "So that's the tour — a rebuilt front page, an in-shell reader, The REview, live markets and market pulse, CRE Talk, ten site versions, ten themes, ten dashboards, the intelligence desk, a social command center, and a publishing desk, all running on your live data. Boomer, none of this is a critique; it's a fan letter with working code attached. Every call is yours. It's all live at rentv dot agentabrams dot com. Talk soon."],
];
function emit(durs) {
// durs: array of per-scene durations (block audio + gap), aligned to narration.m4a
const clips = [], tw = [];
let t = 0;
const TOTAL = +durs.reduce((a, b) => a + b, 0).toFixed(2);
SCENES.forEach(([id, cap, label], i) => {
const d = durs[i], st = t.toFixed(3), du = d.toFixed(3);
const isCard = false;
clips.push(` <div id="s${i}" class="clip shot" data-start="${st}" data-duration="${du}" data-track-index="1"><img id="s${i}i" class="simg" src="media/captures/${cap}.png" alt="${esc(label)}"></div>`);
clips.push(` <div id="s${i}l" class="clip label" data-start="${st}" data-duration="${du}" data-track-index="2"><div class="li"><span class="num">${String(i + 1).padStart(2, '0')}</span><span class="txt">${esc(label)}</span></div></div>`);
tw.push(`tl.from('#s${i}i',{opacity:0,duration:0.4,ease:'power1.out'},${st});`);
tw.push(`tl.fromTo('#s${i}i',{scale:1.05},{scale:1.0,duration:${du},ease:'none'},${st});`);
tw.push(`tl.from('#s${i}l',{y:24,autoAlpha:0,duration:0.45,ease:'power2.out'},${(t + 0.15).toFixed(3)});`);
t += d;
});
const html = `<!doctype html><html lang="en"><head>
<meta charset="UTF-8" /><meta name="viewport" content="width=1920, height=1080" />
<title>RENTV — Master Reel</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:#0b1017}
.shot{overflow:hidden}.simg{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;will-change:transform,opacity}
.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:700;letter-spacing:.06em;padding:9px 13px;border-radius:8px;background:#0a6cff}
.li .txt{color:#fff;font-size:30px;font-weight:500;background:rgba(9,15,24,.9);padding:9px 20px;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="media/narration.m4a" data-start="0" data-volume="1"></audio>
${clips.join('\n')}
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
${tw.map(x => ' ' + x).join('\n')}
window.__timelines['main'] = tl;
</script>
</body></html>`;
fs.writeFileSync('index.html', html);
return TOTAL;
}
const [cmd, a] = process.argv.slice(2);
if (cmd === 'count') console.log(SCENES.length);
else if (cmd === 'text') process.stdout.write(SCENES[+a][3]);
else if (cmd === 'emit') console.log('TOTAL=' + emit(JSON.parse(fs.readFileSync(a, 'utf8'))));