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