← back to Rentv Tour

component-set.mjs

86 lines

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)));