← 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
A fullpage-results.jsonA rentv-fullpage.mjs
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 →