← back to AbramsEgo

scripts/demo-video.js

79 lines

#!/usr/bin/env node
// Capture a ~50s click-through tour of the :9773 dashboard (norma-videos pattern:
// per-panel dwell with a red highlight box; muted/no-TTS cut for the landing page).
// Run: NODE_PATH=~/.claude/skills/app-demo-video/node_modules node scripts/demo-video.js
// Output: tmp/demo-raw/<hash>.webm — converted to mp4 by scripts/demo-video.sh
const { chromium } = require('playwright');
const path = require('path');

const BASE = process.env.EGO_URL || 'http://127.0.0.1:9773/';
const OUT_DIR = path.join(__dirname, '..', 'tmp', 'demo-raw');
const W = 1280, H = 720;

// h2 text prefixes, in tour order (matched with startsWith on the emoji+title)
const TOUR = [
  '💰 Self-Funding P&L',
  '🔌 Cost to run',
  '📈 Cost trend',
  '🛰 Fleet · Kamatera',
  '🖥 Fleet · Local',
  '🐤 Canary board',
  '🏛 Officers',
  '🏆 Wins',
  '💬 Live Claude sessions',
  '🧠 Model usage',
  '🤖 Ask AbramsEgo',
];
const DWELL_MS = 3600;   // per-panel hold
const INTRO_MS = 4500;   // top-of-page hold

(async () => {
  // skill's playwright (1.59.1) expects browser build 1217, which isn't cached;
  // point at the cached headless-shell build instead of downloading a new one
  const shell = process.env.HOME + '/Library/Caches/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-mac-arm64/chrome-headless-shell';
  const browser = await chromium.launch({ executablePath: shell });
  const ctx = await browser.newContext({
    viewport: { width: W, height: H },
    recordVideo: { dir: OUT_DIR, size: { width: W, height: H } },
    httpCredentials: { username: 'admin', password: 'DW2024!' },
  });
  const page = await ctx.newPage();
  await page.goto(BASE, { waitUntil: 'networkidle' }).catch(() => {});
  // let the snapshot panels populate
  await page.waitForTimeout(3000);

  await page.addStyleTag({ content: `
    .demo-hl { outline: 3px solid #ff4d4d !important; outline-offset: 4px;
               border-radius: 8px; transition: outline-color .3s; }
    html { scroll-behavior: smooth; }
  `});

  await page.waitForTimeout(INTRO_MS);

  for (const title of TOUR) {
    const hit = await page.evaluate((t) => {
      const h = [...document.querySelectorAll('h2')]
        .find(el => el.textContent.trim().startsWith(t.slice(0, 6)) && el.textContent.includes(t.slice(2).trim().split(' ')[0]));
      if (!h) return false;
      const panel = h.closest('section,div.panel,article') || h.parentElement;
      document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
      panel.classList.add('demo-hl');
      panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
      return true;
    }, title);
    if (!hit) console.error('panel not found:', title);
    await page.waitForTimeout(DWELL_MS);
  }

  // close on the top metrics bar
  await page.evaluate(() => {
    document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
    window.scrollTo({ top: 0, behavior: 'smooth' });
  });
  await page.waitForTimeout(3000);

  await ctx.close();
  await browser.close();
  console.log('webm written to', OUT_DIR);
})();