← back to Crcp Pitch Video

capture-walkthrough.js

84 lines

/*
 * capture-walkthrough.js — drive CRCP (local :9911) with Playwright, click through the
 * lead-finding workflow + every left-panel control, and emit for each step:
 *   walkthrough/shotNN.png  (1920x1080 screenshot)  +  a manifest entry with the clicked
 *   element's bounding box (for the Remotion highlight), a caption, and a narration line.
 */
const { chromium } = require('playwright-core');
const fs = require('fs');
const path = require('path');

const SHELL = process.env.HOME + '/Library/Caches/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-mac-arm64/chrome-headless-shell';
const BASE = 'http://127.0.0.1:9911';
const OUT = path.join(__dirname, 'walkthrough');

// Each step: what to do, what to highlight, and what to say.
const STEPS = [
  { url: '/broker-grid.html', hl: '#count', label: 'Your lead database',
    cap: 'Every broker & agent — one screen', vo: "This is your C-R-C-P command center. Every broker and agent in the database, right here, ready to work." },
  { click: '#pills, .rail .rsec:nth-child(1) h4', railExpand: 1, hl: '.rail .rsec:nth-child(1)', label: 'Left panel · Agent type',
    cap: 'Filter by the kind of agent', vo: "On the left panel, open Agent Type to narrow the list to exactly the kind of agent you want." },
  { clickChip: '#fType .chip', hl: '#fType', label: 'Pick a segment',
    cap: 'Click a segment — grid filters instantly', vo: "Click a segment, and the whole grid instantly filters to just those agents." },
  { railExpand: 3, hl: '.rail .rsec:nth-child(3)', label: 'Left panel · Company / firm',
    cap: 'Target a specific brokerage', vo: "Want a specific brokerage? Open Company, and pick a firm to see only its agents." },
  { clickChip: '#fFirm .chip', hl: '#fFirm', label: 'Choose a firm',
    cap: 'Only that brokerage’s agents', vo: "Now you’re looking at just that brokerage — a focused list of real, licensed people." },
  { railExpand: 4, hl: '#fFields', label: 'Left panel · Fields / columns',
    cap: 'Show or hide any column', vo: "Open Fields, and show or hide any column — turn on State and C-A confidence to see exactly where each agent is licensed." },
  { clickHeader: 'listings', hl: 'th[data-k="listings"]', label: 'Sort by activity',
    cap: 'Busiest agents = your best leads', vo: "Here’s the key move for finding leads. Click Listings to sort by activity — the busiest agents, the ones closing deals, rise straight to the top." },
  { hl: '#sortsel', label: 'Or sort any field',
    cap: 'Verified license · state · assets', vo: "You can also sort by any field from this menu — verified license, state, or total assets." },
  { type: ['#q', 'realty'], hl: '#q', label: 'Search instantly',
    cap: 'Name · firm · phone · email', vo: "Search by name, firm, phone, or email to jump straight to who you’re looking for." },
  { clearType: '#q', clickRow: '.brow', waitModal: 1, hl: '#modal', label: 'Open a lead',
    cap: 'Full card: contact + listings + sales', vo: "Click any agent to open their full card — phone, email, LinkedIn, and their current listings and recent sales." },
  { hl: '#mgen', label: 'Draft outreach in one click',
    cap: 'Generate a ready-to-send pitch', vo: "And right here, generate a ready-to-send pitch, tailored to that agent. Your outreach, drafted for you." },
  { closeModal: 1, hl: '#csv', label: 'Export your list',
    cap: 'CSV → your CRM or dialer', vo: "Happy with your filtered list? Export it to C-S-V and load it into your C-R-M or dialer." },
  { url: '/gov-agents.html', hl: '#metros', label: '21 elite markets',
    cap: 'Every agent, 20 priciest cities', vo: "Need more markets? The Government-Licensed tab gives you every agent across the twenty priciest cities in America." },
  { clickChip: '#metros .mchip', hl: '#metros', label: 'Pick a market',
    cap: 'Beverly Hills · Aspen · Greenwich…', vo: "Click a market to load every licensed agent there — Beverly Hills, Aspen, Greenwich, and more." },
  { type: ['#q', 'a'], hl: '#q', label: 'Search the market',
    cap: 'Targeted lead list in seconds', vo: "Search within it, sort by license type, and you’ve got a targeted lead list in seconds." },
];

const sleep = ms => new Promise(r => setTimeout(r, ms));

(async () => {
  fs.mkdirSync(OUT, { recursive: true });
  const browser = await chromium.launch({ executablePath: SHELL, headless: true });
  const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 }, deviceScaleFactor: 1, httpCredentials: { username: 'admin', password: 'DW2024!' } });
  const page = await ctx.newPage();
  const manifest = [];

  for (let i = 0; i < STEPS.length; i++) {
    const s = STEPS[i];
    try {
      if (s.url) { await page.goto(BASE + s.url, { waitUntil: 'domcontentloaded' }); await sleep(1800); }
      if (s.railExpand) { const h = page.locator(`.rail .rsec:nth-child(${s.railExpand}) > h4`); await h.click().catch(() => {}); await sleep(500); }
      if (s.click && !s.railExpand) { await page.locator(s.click.split(',')[0]).first().click().catch(() => {}); await sleep(400); }
      if (s.clickChip) { await page.locator(s.clickChip).first().click().catch(() => {}); await sleep(800); }
      if (s.clickHeader) { await page.locator(`th[data-k="${s.clickHeader}"]`).click().catch(() => {}); await sleep(800); }
      if (s.type) { await page.fill(s.type[0], ''); await page.type(s.type[0], s.type[1], { delay: 45 }); await sleep(900); }
      if (s.clearType) { await page.fill(s.clearType, ''); await sleep(500); }
      if (s.clickRow) { await page.locator(s.clickRow).first().click().catch(() => {}); }
      if (s.waitModal) { await sleep(2200); }
      if (s.closeModal) { await page.locator('#ov .x, #modal .x').first().click().catch(() => {}); await sleep(600); }
      await sleep(400);
      const shot = `shot${String(i).padStart(2, '0')}.png`;
      await page.screenshot({ path: path.join(OUT, shot) });
      let box = null;
      try { box = await page.locator(s.hl).first().boundingBox(); } catch (_) {}
      manifest.push({ i, shot, label: s.label, cap: s.cap, vo: s.vo, box });
      console.log(`  step ${i}: ${s.label} ${box ? `[${Math.round(box.x)},${Math.round(box.y)} ${Math.round(box.width)}x${Math.round(box.height)}]` : '(no box)'}`);
    } catch (e) { console.log(`  step ${i} ERROR: ${e.message.split('\n')[0]}`); }
  }
  fs.writeFileSync(path.join(OUT, 'manifest.json'), JSON.stringify(manifest, null, 1));
  console.log(`✔ ${manifest.length} steps → ${OUT}/manifest.json`);
  await browser.close();
})();