[object Object]

← back to Rentv Tour

auto-save: 2026-07-28T15:31:27 (2 files) — fullpage-results.json rentv-fullpage.mjs

521eac452b3bf7c70fa2ce457eb3b6d34031b876 · 2026-07-28 15:31:30 -0700 · Steve Abrams

Files touched

Diff

commit 521eac452b3bf7c70fa2ce457eb3b6d34031b876
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 15:31:30 2026 -0700

    auto-save: 2026-07-28T15:31:27 (2 files) — fullpage-results.json rentv-fullpage.mjs
---
 fullpage-results.json | 128 +++++++++++++++++++++++++++++++++
 rentv-fullpage.mjs    | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 322 insertions(+)

diff --git a/fullpage-results.json b/fullpage-results.json
new file mode 100644
index 0000000..b2f1856
--- /dev/null
+++ b/fullpage-results.json
@@ -0,0 +1,128 @@
+[
+  {
+    "name": "home",
+    "route": "/",
+    "status": 200,
+    "height": 2645
+  },
+  {
+    "name": "article",
+    "route": "/news/34251",
+    "status": 200,
+    "height": 2470
+  },
+  {
+    "name": "review",
+    "route": "/review",
+    "status": 200,
+    "height": 3292
+  },
+  {
+    "name": "markets",
+    "route": "/markets",
+    "status": 200,
+    "height": 1497
+  },
+  {
+    "name": "pulse",
+    "route": "/pulse",
+    "status": 200,
+    "height": 1713
+  },
+  {
+    "name": "cretalk",
+    "route": "/cre-talk",
+    "status": 200,
+    "height": 2712
+  },
+  {
+    "name": "blog",
+    "route": "/blog",
+    "status": 200,
+    "height": 1099
+  },
+  {
+    "name": "versions",
+    "route": "/versions",
+    "status": 200,
+    "height": 1084
+  },
+  {
+    "name": "v-wire",
+    "route": "/versions/wire.html",
+    "status": 200,
+    "height": 1893
+  },
+  {
+    "name": "v-broadsheet",
+    "route": "/versions/broadsheet.html",
+    "status": 200,
+    "height": 2409
+  },
+  {
+    "name": "v-terminal",
+    "route": "/versions/terminal.html",
+    "status": 200,
+    "height": 1238
+  },
+  {
+    "name": "v-magazine",
+    "route": "/versions/magazine.html",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "v-dashboard",
+    "route": "/versions/dashboard.html",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "admin-index",
+    "route": "/admin",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "admin-command",
+    "route": "/admin/command.html",
+    "status": 200,
+    "height": 1133
+  },
+  {
+    "name": "admin-kanban",
+    "route": "/admin/kanban.html",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "publish",
+    "route": "/admin",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "desk",
+    "route": "/desk",
+    "status": 200,
+    "height": 1092
+  },
+  {
+    "name": "social",
+    "route": "/social",
+    "status": 200,
+    "height": 1080
+  },
+  {
+    "name": "theme-midnight",
+    "route": "/ (theme)",
+    "status": 200,
+    "height": 2645
+  },
+  {
+    "name": "theme-salmon",
+    "route": "/ (theme)",
+    "status": 200,
+    "height": 2645
+  }
+]
\ No newline at end of file
diff --git a/rentv-fullpage.mjs b/rentv-fullpage.mjs
new file mode 100644
index 0000000..f167d11
--- /dev/null
+++ b/rentv-fullpage.mjs
@@ -0,0 +1,194 @@
+import { chromium } from 'playwright';
+import fs from 'fs';
+
+const OUT = '/Users/macstudio3/Projects/rentv-master-reel/media/fullpage';
+const BASE = 'https://rentv.agentabrams.com';
+const ADMIN = { username: 'admin', password: 'DW2024!' };
+const USER = { username: 'user', password: 'RentvUser2026!' };
+
+fs.mkdirSync(OUT, { recursive: true });
+
+const CLEAN_CSS = `#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;font-family:-apple-system,sans-serif}#cap-header .cap-brand{font-size:23px;font-weight:800;color:#0d1b2a;text-decoration:none}#cap-header .cap-brand span{color:#c0392b}#cap-header button{background:none;border:0;color:#0d1b2a;display:inline-flex;padding:9px}`;
+
+async function normalize(page) {
+  try {
+    await page.addStyleTag({ content: CLEAN_CSS });
+    await page.evaluate(() => {
+      const k = e => e.closest('.drawer,#drawer,aside');
+      document.querySelectorAll('header,nav,.util,.mast,.topnav').forEach(e => { if (!k(e)) e.style.display = 'none'; });
+      if (!document.getElementById('cap-header')) {
+        const bar = document.createElement('div');
+        bar.id = 'cap-header';
+        bar.innerHTML = `<button><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><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(600);
+  } catch (e) { /* clean page, skip */ }
+}
+
+// Scroll the whole page top->bottom->top with small waits so lazy content renders.
+async function primeLazy(page) {
+  try {
+    await page.evaluate(async () => {
+      const sleep = ms => new Promise(r => setTimeout(r, ms));
+      const H = () => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
+      const step = Math.max(400, Math.floor(window.innerHeight * 0.85));
+      let y = 0;
+      const total = H();
+      while (y < total + step) { window.scrollTo(0, y); await sleep(120); y += step; }
+      window.scrollTo(0, H()); await sleep(300);
+      window.scrollTo(0, 0); await sleep(300);
+    });
+    // one more settle for any images kicked off by scroll
+    await page.waitForTimeout(400);
+  } catch (e) { /* ignore */ }
+}
+
+async function shoot(context, name, route, { doNormalize = true, before = null } = {}) {
+  const page = await context.newPage();
+  let status = 'ERR', height = 0;
+  try {
+    const resp = await page.goto(BASE + route, { waitUntil: 'domcontentloaded', timeout: 60000 });
+    status = resp ? resp.status() : 'no-resp';
+    await page.waitForTimeout(1500);
+    if (before) await before(page);
+    await primeLazy(page);
+    try { await page.waitForLoadState('networkidle', { timeout: 8000 }); } catch (e) {}
+    if (doNormalize) await normalize(page);
+    // ensure at top before full-page grab
+    await page.evaluate(() => window.scrollTo(0, 0));
+    await page.waitForTimeout(300);
+    await page.screenshot({ path: `${OUT}/${name}.png`, fullPage: true });
+    height = await page.evaluate(() => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight));
+    console.log(`OK  ${name}.png  <- ${route}  [${status}]  h=${height}px`);
+  } catch (e) {
+    console.log(`FAIL ${name}.png <- ${route}  [${status}] ${e.message.split('\n')[0]}`);
+    status = 'FAIL:' + status;
+  } finally {
+    await page.close();
+  }
+  return { name, route, status, height };
+}
+
+const results = [];
+
+(async () => {
+  const browser = await chromium.launch();
+  const vp = { viewport: { width: 1920, height: 1080 }, deviceScaleFactor: 1 };
+
+  const userCtx = await browser.newContext({ ...vp, httpCredentials: USER });
+  const adminCtx = await browser.newContext({ ...vp, httpCredentials: ADMIN });
+
+  // ---- USER pages ----
+  results.push(await shoot(userCtx, 'home', '/'));
+
+  // article: open a REAL /news/<id> or /post/<id> — grab first headline href from HOME (/)
+  {
+    const page = await userCtx.newPage();
+    let articleHref = null, status = 'ERR';
+    try {
+      const resp = await page.goto(BASE + '/', { waitUntil: 'domcontentloaded', timeout: 60000 });
+      status = resp ? resp.status() : 'no-resp';
+      await page.waitForTimeout(1800);
+      articleHref = await page.evaluate(() => {
+        const sel = ['a[href*="/news/"]', 'a[href*="/post/"]', 'a[href*="/article"]', 'a[href*="/story"]', '.headline a', 'h2 a', 'h3 a', '.card a', '.lead a'];
+        for (const s of sel) {
+          for (const a of document.querySelectorAll(s)) {
+            const h = a.getAttribute('href') || '';
+            if (/\/(news|post|article|story)\/[^/]+/.test(h)) return h;
+          }
+        }
+        return null;
+      });
+      console.log('article href ->', articleHref, '(home status ' + status + ')');
+    } catch (e) { console.log('home article-scan err', e.message); }
+    await page.close();
+    if (articleHref) {
+      const full = articleHref.startsWith('http') ? articleHref.replace(BASE, '') : articleHref;
+      results.push(await shoot(userCtx, 'article', full));
+    } else {
+      results.push(await shoot(userCtx, 'article', '/article'));
+    }
+  }
+
+  results.push(await shoot(userCtx, 'review', '/review'));
+  results.push(await shoot(userCtx, 'markets', '/markets'));
+  results.push(await shoot(userCtx, 'pulse', '/pulse'));
+  results.push(await shoot(userCtx, 'cretalk', '/cre-talk'));
+  results.push(await shoot(userCtx, 'blog', '/blog'));
+
+  // ---- ADMIN pages ----
+  results.push(await shoot(adminCtx, 'versions', '/versions'));
+  results.push(await shoot(adminCtx, 'v-wire', '/versions/wire.html'));
+  results.push(await shoot(adminCtx, 'v-broadsheet', '/versions/broadsheet.html'));
+  results.push(await shoot(adminCtx, 'v-terminal', '/versions/terminal.html'));
+  results.push(await shoot(adminCtx, 'v-magazine', '/versions/magazine.html'));
+  results.push(await shoot(adminCtx, 'v-dashboard', '/versions/dashboard.html'));
+  results.push(await shoot(adminCtx, 'admin-index', '/admin'));
+  results.push(await shoot(adminCtx, 'admin-command', '/admin/command.html'));
+  results.push(await shoot(adminCtx, 'admin-kanban', '/admin/kanban.html'));
+  results.push(await shoot(adminCtx, 'publish', '/admin'));
+  results.push(await shoot(adminCtx, 'desk', '/desk'));
+  results.push(await shoot(adminCtx, 'social', '/social'));
+
+  // ---- THEME captures (user ctx, home page). Set the theme.js CSS vars + body class directly. ----
+  // From public/theme.js: Midnight Capital (idx 4), FT Salmon (idx 2).
+  const THEME_MIDNIGHT = { accent:"#c9a84c", ink:"#f0f2f5", sub:"#8fa3b8", line:"#1e2d40", bg:"#0d1b2a", wash:"#132336", serif:"'DM Serif Display', Georgia, serif", sans:"'Archivo', system-ui, sans-serif", dark:true };
+  const THEME_SALMON   = { accent:"#00538b", ink:"#1a1a1a", sub:"#5c5c5c", line:"#d4c9b8", bg:"#fff1e5", wash:"#fde8d1", serif:"'Source Serif 4', Georgia, serif", sans:"'Inter', system-ui, sans-serif", dark:false };
+
+  async function applyTheme(page, t) {
+    await page.evaluate((t) => {
+      const r = document.documentElement;
+      r.style.setProperty('--red', t.accent); r.style.setProperty('--ink', t.ink);
+      r.style.setProperty('--sub', t.sub); r.style.setProperty('--line', t.line);
+      r.style.setProperty('--bg', t.bg); r.style.setProperty('--wash', t.wash);
+      r.style.setProperty('--serif', t.serif); r.style.setProperty('--sans', t.sans);
+      document.body.classList.toggle('theme-dark', !!t.dark);
+      // dark-surface overrides (mirrors theme.js injected style block)
+      if (!document.getElementById('cap-theme-dark-fix')) {
+        const st = document.createElement('style'); st.id = 'cap-theme-dark-fix';
+        st.textContent =
+          "body.theme-dark .util,body.theme-dark footer,body.theme-dark .ns,body.theme-dark .mast{background:var(--wash)!important}" +
+          "body.theme-dark .util,body.theme-dark .util a,body.theme-dark footer,body.theme-dark footer a,body.theme-dark footer p{color:var(--sub)!important}" +
+          "body.theme-dark footer .logo,body.theme-dark footer h5{color:var(--ink)!important}" +
+          "body.theme-dark .ns{color:var(--ink)!important}body.theme-dark .ns p{color:var(--sub)!important}" +
+          "body.theme-dark .card .img,body.theme-dark .lead .img{filter:brightness(.85)}";
+        document.head.appendChild(st);
+      }
+    }, t);
+    await page.waitForTimeout(700);
+  }
+
+  async function themeShot(name, theme) {
+    const page = await userCtx.newPage();
+    let status = 'ERR', height = 0;
+    try {
+      const resp = await page.goto(BASE + '/', { waitUntil: 'domcontentloaded', timeout: 60000 });
+      status = resp ? resp.status() : 'no-resp';
+      await page.waitForTimeout(1500);
+      await applyTheme(page, theme);
+      await primeLazy(page);
+      // re-apply after scroll in case lazy content mounted un-themed nodes
+      await applyTheme(page, theme);
+      try { await page.waitForLoadState('networkidle', { timeout: 8000 }); } catch (e) {}
+      await normalize(page);
+      await page.evaluate(() => window.scrollTo(0, 0));
+      await page.waitForTimeout(300);
+      await page.screenshot({ path: `${OUT}/${name}.png`, fullPage: true });
+      height = await page.evaluate(() => Math.max(document.body.scrollHeight, document.documentElement.scrollHeight));
+      console.log(`OK  ${name}.png  theme applied [${status}]  h=${height}px`);
+    } catch (e) { console.log(`FAIL ${name}.png ${e.message.split('\n')[0]}`); status = 'FAIL'; }
+    await page.close();
+    return { name, route: '/ (theme)', status, height };
+  }
+  results.push(await themeShot('theme-midnight', THEME_MIDNIGHT));
+  results.push(await themeShot('theme-salmon', THEME_SALMON));
+
+  await browser.close();
+
+  console.log('\n===== SUMMARY (name | route | status | height) =====');
+  for (const r of results) console.log(`${String(r.status).padEnd(10)} h=${String(r.height).padStart(6)}px  ${r.name}.png\t<- ${r.route}`);
+  fs.writeFileSync('/Users/macstudio3/Projects/rentv-tour/fullpage-results.json', JSON.stringify(results, null, 2));
+})();

← 6cceb3f auto-save: 2026-07-28T15:01:16 (7 files) — videos/pitch/inde  ·  back to Rentv Tour  ·  rentv-tour: 3-act walkthrough — add Growth act (ACT I) befor 75dec76 →