← back to Rentv Tour
capture.mjs
102 lines
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
// Normalize the header for clean captures: kill the old top-tab bars, overlay a single
// minimal header — hamburger (upper-left) + RENTV brand + person/sign-in icon (upper-right).
await page.addStyleTag({ content: `
#cap-header{position:sticky;top:0;z-index:99999;display:flex;align-items:center;justify-content:space-between;
height:64px;padding:0 24px;background:#fff;border-bottom:1px solid #e6eaef;box-shadow:0 1px 0 rgba(0,0,0,.02);
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;}
#cap-header .cap-brand{font-size:23px;font-weight:800;letter-spacing:.01em;color:#0d1b2a;text-decoration:none;}
#cap-header .cap-brand span{color:#c0392b;}
#cap-header button{background:none;border:0;cursor:pointer;color:#0d1b2a;display:inline-flex;align-items:center;padding:9px;border-radius:9px;}
` });
await page.evaluate(() => {
const inDrawer = (el) => el.closest('.drawer,#drawer,aside');
// hide every existing top chrome + horizontal tab bar (but never the hamburger drawer contents)
document.querySelectorAll('header, nav, .util, .mast, .topnav, .top-tabs, .subnav, .tabs').forEach((el) => {
if (!inDrawer(el)) el.style.display = 'none';
});
if (!document.getElementById('cap-header')) {
const bar = document.createElement('div');
bar.id = 'cap-header';
bar.innerHTML =
'<button aria-label="Menu"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>'
+ '<a class="cap-brand" href="/">REN<span>TV</span></a>'
+ '<button aria-label="Sign in / up"><svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="3.6"/><path d="M4.5 20a7.5 7.5 0 0 1 15 0" stroke-linecap="round"/></svg></button>';
document.body.insertBefore(bar, document.body.firstChild);
}
window.scrollTo(0, 0);
});
await page.waitForTimeout(250);
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`);