← back to Rentv
Unify RENTV: two-tier user/admin auth + fold consulting (/consulting) & promos (/press); rename rentv-v1 -> rentv (v0.9.0)
0dfdf6c3a73141100f1c289cf39875328cba72e7 · 2026-07-28 10:09:44 -0700 · Steve Abrams
- Role resolved from matched cred; adminOnly gates all internal data APIs + shells + defense-in-depth static guard
- user = front page only; admin = front page + all internal data (desk registry, subscribers, audience, admin, versions, consulting, press)
- /api/me echoes role; blog writes + subscriber list + audience/CRM now admin-only
- originals.json already absorbed from rentv-website (now redundant)
- ACCESS-USERS.md + .deploy.conf + package.json updated for rentv name
TK-10013
Files touched
M .deploy.confM package.jsonA public/consulting/admin/index.htmlA public/consulting/admin/lhf.jsA public/consulting/audit/advertise.pngA public/consulting/audit/archive.pngA public/consulting/audit/contact.pngA public/consulting/audit/index.pngA public/consulting/audit/jobworks.pngA public/consulting/audit/newsletter.pngA public/consulting/audit/providers.pngA public/consulting/audit/store.pngA public/consulting/audit/subscribe.pngA public/consulting/audit/subscriber.pngA public/consulting/deal-score.jsA public/consulting/intake.htmlA public/consulting/portal.htmlA public/consulting/signin.htmlA public/consulting/slideshow.htmlA public/consulting/versions/broadsheet.htmlA public/consulting/versions/dispatch.htmlA public/consulting/versions/marketplace.htmlA public/consulting/versions/modern.htmlA public/consulting/versions/observer.htmlA public/consulting/versions/terminal.htmlA public/consulting/versions/thewire.htmlA public/press/index.htmlA public/press/media/brand.mp4A public/press/media/captures/admin-analytics.pngA public/press/media/captures/admin-command.pngA public/press/media/captures/admin-index.pngA public/press/media/captures/admin-kanban.pngA public/press/media/captures/article.pngA public/press/media/captures/blog.pngA public/press/media/captures/cretalk.pngA public/press/media/captures/home.pngA public/press/media/captures/markets.pngA public/press/media/captures/publish.pngA public/press/media/captures/review.pngA public/press/media/captures/theme-midnight.pngA public/press/media/captures/theme-salmon.pngA public/press/media/captures/v-broadsheet.pngA public/press/media/captures/v-dashboard.pngA public/press/media/captures/v-magazine.pngA public/press/media/captures/v-terminal.pngA public/press/media/captures/v-wire.pngA public/press/media/captures/versions.pngA public/press/media/daily.mp4A public/press/media/deals.mp4A public/press/media/review.mp4M server.js
Diff
commit 0dfdf6c3a73141100f1c289cf39875328cba72e7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 28 10:09:44 2026 -0700
Unify RENTV: two-tier user/admin auth + fold consulting (/consulting) & promos (/press); rename rentv-v1 -> rentv (v0.9.0)
- Role resolved from matched cred; adminOnly gates all internal data APIs + shells + defense-in-depth static guard
- user = front page only; admin = front page + all internal data (desk registry, subscribers, audience, admin, versions, consulting, press)
- /api/me echoes role; blog writes + subscriber list + audience/CRM now admin-only
- originals.json already absorbed from rentv-website (now redundant)
- ACCESS-USERS.md + .deploy.conf + package.json updated for rentv name
TK-10013
---
.deploy.conf | 8 +-
package.json | 2 +-
public/consulting/admin/index.html | 70 +++
public/consulting/admin/lhf.js | 66 +++
public/consulting/audit/advertise.png | Bin 0 -> 341122 bytes
public/consulting/audit/archive.png | Bin 0 -> 199887 bytes
public/consulting/audit/contact.png | Bin 0 -> 169922 bytes
public/consulting/audit/index.png | Bin 0 -> 1003154 bytes
public/consulting/audit/jobworks.png | Bin 0 -> 170983 bytes
public/consulting/audit/newsletter.png | Bin 0 -> 163045 bytes
public/consulting/audit/providers.png | Bin 0 -> 322747 bytes
public/consulting/audit/store.png | Bin 0 -> 944331 bytes
public/consulting/audit/subscribe.png | Bin 0 -> 199006 bytes
public/consulting/audit/subscriber.png | Bin 0 -> 353313 bytes
public/consulting/deal-score.js | 112 +++++
public/consulting/intake.html | 154 ++++++
public/consulting/portal.html | 604 ++++++++++++++++++++++++
public/consulting/signin.html | 67 +++
public/consulting/slideshow.html | 143 ++++++
public/consulting/versions/broadsheet.html | 86 ++++
public/consulting/versions/dispatch.html | 86 ++++
public/consulting/versions/marketplace.html | 225 +++++++++
public/consulting/versions/modern.html | 86 ++++
public/consulting/versions/observer.html | 86 ++++
public/consulting/versions/terminal.html | 86 ++++
public/consulting/versions/thewire.html | 86 ++++
public/press/index.html | 58 +++
public/press/media/brand.mp4 | Bin 0 -> 1488670 bytes
public/press/media/captures/admin-analytics.png | Bin 0 -> 116181 bytes
public/press/media/captures/admin-command.png | Bin 0 -> 120561 bytes
public/press/media/captures/admin-index.png | Bin 0 -> 115243 bytes
public/press/media/captures/admin-kanban.png | Bin 0 -> 163206 bytes
public/press/media/captures/article.png | Bin 0 -> 786675 bytes
public/press/media/captures/blog.png | Bin 0 -> 82221 bytes
public/press/media/captures/cretalk.png | Bin 0 -> 660945 bytes
public/press/media/captures/home.png | Bin 0 -> 1504177 bytes
public/press/media/captures/markets.png | Bin 0 -> 184023 bytes
public/press/media/captures/publish.png | Bin 0 -> 77780 bytes
public/press/media/captures/review.png | Bin 0 -> 1340287 bytes
public/press/media/captures/theme-midnight.png | Bin 0 -> 572170 bytes
public/press/media/captures/theme-salmon.png | Bin 0 -> 602366 bytes
public/press/media/captures/v-broadsheet.png | Bin 0 -> 1006422 bytes
public/press/media/captures/v-dashboard.png | Bin 0 -> 227684 bytes
public/press/media/captures/v-magazine.png | Bin 0 -> 822894 bytes
public/press/media/captures/v-terminal.png | Bin 0 -> 195715 bytes
public/press/media/captures/v-wire.png | Bin 0 -> 947054 bytes
public/press/media/captures/versions.png | Bin 0 -> 133902 bytes
public/press/media/daily.mp4 | Bin 0 -> 1098925 bytes
public/press/media/deals.mp4 | Bin 0 -> 1820672 bytes
public/press/media/review.mp4 | Bin 0 -> 1430006 bytes
server.js | 97 ++--
51 files changed, 2087 insertions(+), 35 deletions(-)
diff --git a/.deploy.conf b/.deploy.conf
index 42a5d01f..acf4d60d 100644
--- a/.deploy.conf
+++ b/.deploy.conf
@@ -1,8 +1,12 @@
-PROJECT_NAME=rentv-v1
+PROJECT_NAME=rentv
DEPLOY_HOST=45.61.58.125
-DEPLOY_PATH=/root/public-projects/rentv-v1
+DEPLOY_PATH=/root/public-projects/rentv
HEALTH_URL=http://localhost:9704/api/health
INSTALL_CMD="npm ci --production"
BUILD_CMD=""
# Never overwrite prod's accumulated real newsletter signups with the local file.
RSYNC_EXTRA_EXCLUDES="data/subscribers.jsonl data/posts.json"
+# NOTE (2026-07-28): renamed from rentv-v1 -> rentv (unified RENTV app). The prod
+# dir /root/public-projects/rentv-v1 must be renamed to /root/public-projects/rentv
+# and pm2 re-registered as `rentv` as part of the Steve-gated cutover — see
+# ~/.claude/yolo-queue/pending-approval/rentv-consolidation-cutover.md
diff --git a/package.json b/package.json
index b94462aa..f6d8acad 100644
--- a/package.json
+++ b/package.json
@@ -1 +1 @@
-{"name":"rentv-v1","version":"0.8.0","private":true,"main":"server.js","scripts":{"start":"node server.js","pull":"node scripts/pull-news.mjs"},"dependencies":{"express":"^4.21.2"}}
\ No newline at end of file
+{"name":"rentv","version":"0.9.0","private":true,"main":"server.js","scripts":{"start":"node server.js","pull":"node scripts/pull-news.mjs"},"dependencies":{"express":"^4.21.2"}}
\ No newline at end of file
diff --git a/public/consulting/admin/index.html b/public/consulting/admin/index.html
new file mode 100644
index 00000000..9c64fc8c
--- /dev/null
+++ b/public/consulting/admin/index.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
+<title>RENTV.com · Growth & Social Command Center</title>
+<style>
+ :root{--ink:#14110e;--bg:#12100e;--panel:#1c1915;--gold:#c6a765;--line:#2c2823;--txt:#e9e2d6}
+ *{box-sizing:border-box}
+ body{margin:0;font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--txt)}
+ header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:center}
+ header h1{font-size:16px;font-weight:600;letter-spacing:.08em;margin:0}
+ header .eyebrow{color:var(--gold);font-size:11px;letter-spacing:.28em;text-transform:uppercase}
+ header a{color:var(--gold);text-decoration:none;font-size:13px}
+ .wrap{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;padding:20px 24px}
+ .panel{background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:16px 18px}
+ .panel h2{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--gold);margin:0 0 10px;display:flex;justify-content:space-between;align-items:center}
+ .panel h2 .tag{font-size:10px;color:#8a8272;letter-spacing:.06em}
+ textarea{width:100%;min-height:150px;background:#0e0c0a;color:#cfe6cf;border:1px solid var(--line);border-radius:4px;font-family:'SF Mono',ui-monospace,monospace;font-size:12px;padding:10px;resize:vertical}
+ button{background:var(--gold);color:#12100e;border:0;padding:8px 16px;font-weight:600;letter-spacing:.08em;font-size:12px;border-radius:4px;cursor:pointer;margin-top:8px}
+ button:hover{filter:brightness(1.08)}
+ .saved{color:#7ad17a;font-size:12px;margin-left:10px}
+ .note{font-size:12px;color:#8a8272;margin:0 0 8px}
+</style>
+</head>
+<body>
+ <header>
+ <div><div class="eyebrow">Integrated Social · Growth</div><h1>RENTV.com — Command Center</h1></div>
+ <div><a href="/portal">← Deliverable</a> <a href="/">Sign-in</a></div>
+ </header>
+
+ <section id="lhf"></section>
+<script src="/admin/lhf.js"></script>
+
+ <div class="wrap" id="wrap"></div>
+
+<script>
+// Each panel is a live-editable view of one data bucket. Edit the JSON, Save,
+// and the server rewrites data/<bucket>.json. Script runners (competitors-scan,
+// best-times, ig-pull, places-read, gen-media) repopulate these from real data.
+const PANELS = [
+ ['socials','Connected Accounts','Instagram · Facebook · TikTok · YouTube · LinkedIn · GBP'],
+ ['best-times','Best Times to Post','per-platform posting cadence'],
+ ['content-calendar','Content Calendar','planned posts / campaigns — DRAFT ONLY'],
+ ['competitors','Competitor Radar','who else is in the market + their social/ads'],
+ ['ads','Ad Signals','which platforms competitors advertise on'],
+ ['directories','Listings & Directories','GBP · Yelp · Apple · Bing'],
+ ['suggestions','Growth Moves','impact / effort ranked ideas'],
+ ['media','Generated Media','reels / graphics produced for this client'],
+];
+const wrap = document.getElementById('wrap');
+async function j(u,opt){ const r=await fetch(u,opt); return r.ok?r.json():null; }
+for (const [key,title,tag] of PANELS){
+ const p = document.createElement('div'); p.className='panel';
+ p.innerHTML = '<h2>'+title+'<span class="tag">'+tag+'</span></h2>'
+ + '<textarea id="ta-'+key+'">loading…</textarea>'
+ + '<div><button data-k="'+key+'">Save '+key+'</button><span class="saved" id="ok-'+key+'"></span></div>';
+ wrap.appendChild(p);
+ j('/api/admin/'+key).then(d=>{ document.getElementById('ta-'+key).value = JSON.stringify(d ?? [], null, 2); });
+}
+wrap.addEventListener('click', async (e)=>{
+ const b = e.target.closest('button[data-k]'); if(!b) return;
+ const k = b.dataset.k; const ta = document.getElementById('ta-'+k); let body;
+ try { body = JSON.parse(ta.value); } catch { document.getElementById('ok-'+k).textContent='✗ invalid JSON'; return; }
+ const r = await fetch('/api/admin/'+k,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
+ document.getElementById('ok-'+k).textContent = r.ok ? '✓ saved '+new Date().toLocaleTimeString() : '✗ error';
+ setTimeout(()=>{document.getElementById('ok-'+k).textContent='';},4000);
+});
+</script>
+</body>
+</html>
diff --git a/public/consulting/admin/lhf.js b/public/consulting/admin/lhf.js
new file mode 100644
index 00000000..216e7c9d
--- /dev/null
+++ b/public/consulting/admin/lhf.js
@@ -0,0 +1,66 @@
+// Low Hanging Fruit — canonical ranked read-only widget over /api/admin/suggestions.
+// SOURCE OF TRUTH. Copied verbatim into each portal's served static dir
+// (rentv: public/admin/lhf.js -> /admin/lhf.js ; car-wash: public/assets/lhf.js -> /assets/lhf.js).
+// Mount with: <section id="lhf"></section><script src="<served-path>/lhf.js"></script>
+// Ranks the suggestions bucket by ROI = impact/effort and time = effort; flags the
+// low-effort + high-impact "quick wins". Read-only; no data mutation.
+(function () {
+ var host = document.getElementById('lhf');
+ if (!host) return;
+
+ if (!document.getElementById('lhf-style')) {
+ var st = document.createElement('style');
+ st.id = 'lhf-style';
+ st.textContent =
+ '#lhf{margin:20px 24px 4px;background:#1c1915;border:1px solid #2c2823;border-radius:6px;padding:18px 20px}' +
+ '#lhf .lhf-h{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:2px}' +
+ '#lhf .lhf-h h2{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#c6a765;margin:0}' +
+ '#lhf .lhf-sub{color:#8a8272;font-size:12px}' +
+ '#lhf .lhf-note{color:#8a8272;font-size:12px;margin:8px 0 0}#lhf .lhf-note b{color:#c6a765}' +
+ '#lhf ol{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:10px}' +
+ '#lhf li{display:grid;grid-template-columns:34px 1fr;gap:12px;background:#161310;border:1px solid #2c2823;border-radius:5px;padding:12px 14px}' +
+ '#lhf li.fruit{border-color:#c6a765}' +
+ '#lhf .rk{font-size:20px;font-weight:800;color:#c6a765;line-height:1.1}' +
+ '#lhf .ti{font-weight:600;color:#e9e2d6}' +
+ '#lhf .badges{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}' +
+ '#lhf .b{font-size:10px;letter-spacing:.05em;text-transform:uppercase;padding:3px 8px;border-radius:3px;border:1px solid #2c2823;color:#b8ad97}' +
+ '#lhf .b.roi-high,#lhf .b.time-quick{color:#7ad17a;border-color:#3a5a3a}' +
+ '#lhf .b.roi-solid{color:#c6a765}' +
+ '#lhf .b.fruit{background:#c6a765;color:#12100e;border:0;font-weight:700}' +
+ '#lhf .rat{font-size:12.5px;color:#b8ad97;margin-top:2px}' +
+ '#lhf .empty{color:#8a8272;font-size:13px}';
+ document.head.appendChild(st);
+ }
+
+ host.innerHTML =
+ '<div class="lhf-h"><h2>⚡ Low Hanging Fruit</h2>' +
+ '<span class="lhf-sub">ranked by ROI ÷ time — fastest, highest-return moves first</span></div>' +
+ '<p class="lhf-note">⚡ <b>Quick wins</b> (low effort · high impact) leverage assets you already have — activate, don’t rebuild. That is why they are simple.</p>' +
+ '<ol id="lhf-list"><li class="empty">Loading growth moves…</li></ol>';
+
+ function esc(s) {
+ return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
+ return { '&': '&', '<': '<', '>': '>', '"': '"' }[c];
+ });
+ }
+ function TIME(e) { return e <= 2 ? ['Quick', 'time-quick'] : e === 3 ? ['Moderate', 'time-mod'] : ['Involved', 'time-inv']; }
+ function ROI(i, e) { var r = i / Math.max(1, e); return r >= 2 ? ['High', 'roi-high'] : r >= 1.2 ? ['Solid', 'roi-solid'] : ['Low', 'roi-low']; }
+
+ fetch('/api/admin/suggestions').then(function (r) { return r.ok ? r.json() : []; }).then(function (list) {
+ var el = document.getElementById('lhf-list');
+ list = (list || []).filter(function (s) { return s && s.title; });
+ if (!list.length) { el.innerHTML = '<li class="empty">No growth moves yet — add ideas in the Growth Moves panel below.</li>'; return; }
+ list.forEach(function (s) { s._i = Number(s.impact) || 0; s._e = Number(s.effort) || 3; s._roi = s._i / Math.max(1, s._e); s._fruit = s._e <= 2 && s._i >= 4; });
+ list.sort(function (a, b) { return b._roi - a._roi || a._e - b._e || b._i - a._i; });
+ el.innerHTML = list.map(function (s, idx) {
+ var t = TIME(s._e), ro = ROI(s._i, s._e);
+ return '<li class="' + (s._fruit ? 'fruit' : '') + '"><div class="rk">' + (idx + 1) + '</div><div>' +
+ '<div class="ti">' + esc(s.title) + '</div>' +
+ '<div class="badges">' + (s._fruit ? '<span class="b fruit">⚡ Quick win</span>' : '') +
+ '<span class="b ' + ro[1] + '">ROI ' + s._roi.toFixed(1) + '</span><span class="b ' + t[1] + '">⏱ ' + t[0] + '</span>' +
+ (s.category ? '<span class="b">' + esc(s.category) + '</span>' : '') +
+ '<span class="b">impact ' + s._i + ' · effort ' + s._e + '</span></div>' +
+ (s.rationale ? '<div class="rat">' + esc(s.rationale) + '</div>' : '') + '</div></li>';
+ }).join('');
+ }).catch(function () { document.getElementById('lhf-list').innerHTML = '<li class="empty">Could not load suggestions.</li>'; });
+})();
diff --git a/public/consulting/audit/advertise.png b/public/consulting/audit/advertise.png
new file mode 100644
index 00000000..205037f0
Binary files /dev/null and b/public/consulting/audit/advertise.png differ
diff --git a/public/consulting/audit/archive.png b/public/consulting/audit/archive.png
new file mode 100644
index 00000000..fa8b3f70
Binary files /dev/null and b/public/consulting/audit/archive.png differ
diff --git a/public/consulting/audit/contact.png b/public/consulting/audit/contact.png
new file mode 100644
index 00000000..0350275a
Binary files /dev/null and b/public/consulting/audit/contact.png differ
diff --git a/public/consulting/audit/index.png b/public/consulting/audit/index.png
new file mode 100644
index 00000000..b47b1c83
Binary files /dev/null and b/public/consulting/audit/index.png differ
diff --git a/public/consulting/audit/jobworks.png b/public/consulting/audit/jobworks.png
new file mode 100644
index 00000000..dd473e5b
Binary files /dev/null and b/public/consulting/audit/jobworks.png differ
diff --git a/public/consulting/audit/newsletter.png b/public/consulting/audit/newsletter.png
new file mode 100644
index 00000000..9b1c44ff
Binary files /dev/null and b/public/consulting/audit/newsletter.png differ
diff --git a/public/consulting/audit/providers.png b/public/consulting/audit/providers.png
new file mode 100644
index 00000000..c3e17358
Binary files /dev/null and b/public/consulting/audit/providers.png differ
diff --git a/public/consulting/audit/store.png b/public/consulting/audit/store.png
new file mode 100644
index 00000000..acac9ab9
Binary files /dev/null and b/public/consulting/audit/store.png differ
diff --git a/public/consulting/audit/subscribe.png b/public/consulting/audit/subscribe.png
new file mode 100644
index 00000000..20c6d848
Binary files /dev/null and b/public/consulting/audit/subscribe.png differ
diff --git a/public/consulting/audit/subscriber.png b/public/consulting/audit/subscriber.png
new file mode 100644
index 00000000..baaf0bdb
Binary files /dev/null and b/public/consulting/audit/subscriber.png differ
diff --git a/public/consulting/deal-score.js b/public/consulting/deal-score.js
new file mode 100644
index 00000000..459d94f1
--- /dev/null
+++ b/public/consulting/deal-score.js
@@ -0,0 +1,112 @@
+// deal-score.js — transparent, weight-normalized 0-100 Deal Score for the LA County CRE screener.
+// Works in Node (require) and the browser (global DealScore). SINGLE SOURCE OF TRUTH so the
+// property cards, the map color-by-score ramp, and the deals dashboard all rank identically.
+//
+// Philosophy: synthesize the signals we ALREADY computed elsewhere into one "chase-this-first" number.
+// - Every sub-score is a labeled 0-100 mapping of one real signal.
+// - dealScore = Σ(sub × weight) / Σ(weight) over ONLY the components that are present.
+// Missing signals drop out and the remaining weights re-normalize → never NaN, never a free ride.
+// - Bonuses (assumable, FHA-warrantable) are upside nudges with small weight, applied the same way.
+// - We are honest: proxy/low-confidence sub-scores are labeled as such in the breakdown so the UI
+// can show WHY a deal scored what it did. This is a screen, not an appraisal.
+(function (root, factory) {
+ if (typeof module === 'object' && module.exports) module.exports = factory();
+ else root.DealScore = factory();
+})(typeof self !== 'undefined' ? self : this, function () {
+
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
+ const fin = v => (typeof v === 'number' && isFinite(v)) ? v : null;
+ // linear map of x in [a,b] -> [0,100], clamped; guards a===b (div-by-zero) -> 50.
+ function ramp(x, a, b) { x = fin(x); if (x == null) return null; if (a === b) return 50; return clamp(100 * (x - a) / (b - a), 0, 100); }
+
+ // Component weights (relative; the formula re-normalizes over whatever is present).
+ const W = { rentRoi: 30, cap: 28, dscr: 22, assumable: 12, warrant: 8 };
+
+ // ---- sub-score mappings (each returns 0-100 or null when the signal is absent) ----
+
+ // Pro-forma gross rent yield. LA gross yields run ~5-10%; >18% is almost always a bad unit count.
+ // Clamp those outliers to low-confidence: cap the points at 60 AND halve the weight so a fishy
+ // listing can't top the board on a number we don't trust.
+ function rentRoiSub(grossYieldPro) {
+ const y = fin(grossYieldPro); if (y == null) return null;
+ if (y > 18) return { core: true, score: clamp(ramp(y, 4, 12), 0, 60), weight: W.rentRoi * 0.5, conf: 'low', label: 'Pro-forma rent yield (outlier — verify units)' };
+ return { core: true, score: ramp(y, 4, 12), weight: W.rentRoi, conf: 'proxy', label: 'Pro-forma rent yield' };
+ }
+
+ // Cap rate (commercial). 4% -> 0, 9% -> 100. Broker-stated unless verified.
+ function capSub(cap_rate, verified) {
+ const c = fin(cap_rate); if (c == null) return null;
+ return { core: true, score: ramp(c, 4, 9), weight: W.cap, conf: verified === true ? 'verified' : 'broker', label: verified === true ? 'Cap rate (verified)' : 'Cap rate (broker-stated)' };
+ }
+
+ // DSCR-readiness. Prefer the real leverage-math DSCR. When there's no in-place NOI but we have a
+ // rent estimate, derive a PROXY DSCR = est NOI vs the standard-loan debt service the caller passes.
+ // 1.0 -> 0, 1.5 -> 100 (1.25 = the bankable line ~ 50pts).
+ function dscrSub(dscr, proxyDscr) {
+ const real = fin(dscr);
+ if (real != null) return { core: true, score: ramp(real, 1.0, 1.5), weight: W.dscr, conf: 'derived', label: 'DSCR (from in-place cap)' };
+ const px = fin(proxyDscr);
+ if (px != null) return { core: true, score: ramp(px, 1.0, 1.5), weight: W.dscr * 0.7, conf: 'proxy', label: 'DSCR-readiness (rent-estimate proxy)' };
+ return null;
+ }
+
+ // Assumable FHA/VA bonus (heuristic, not title-verified). high > medium.
+ function assumableSub(assum) {
+ if (!assum || !assum.lk) return null;
+ const score = assum.lk === 'high' ? 100 : assum.lk === 'medium' ? 65 : null;
+ if (score == null) return null;
+ return { score, weight: W.assumable, conf: 'heuristic', label: 'Assumable FHA/VA (heuristic)' };
+ }
+
+ // FHA-warrantability bonus (condo only). Binary signal -> full credit when approved.
+ function warrantSub(fhaApproved) {
+ if (fhaApproved !== true) return null;
+ return { score: 100, weight: W.warrant, conf: 'signal', label: 'FHA-warrantable condo' };
+ }
+
+ // ---- main: assemble present components, weight-normalize, never NaN ----
+ // input: { grossYieldPro, cap_rate, verified, dscr, proxyDscr, assum, fhaApproved }
+ function compute(input) {
+ input = input || {};
+ const parts = [
+ rentRoiSub(input.grossYieldPro),
+ capSub(input.cap_rate, input.verified),
+ dscrSub(input.dscr, input.proxyDscr),
+ assumableSub(input.assum),
+ warrantSub(input.fhaApproved),
+ ].filter(p => p && fin(p.score) != null && fin(p.weight) != null && p.weight > 0);
+
+ if (!parts.length) return { score: null, parts: [], confidence: 'none' };
+
+ const wsum = parts.reduce((s, p) => s + p.weight, 0);
+ if (!(wsum > 0)) return { score: null, parts, confidence: 'none' };
+ const raw = parts.reduce((s, p) => s + p.score * p.weight, 0) / wsum;
+ const score = clamp(Math.round(raw), 0, 100);
+
+ // Confidence: how much of the FULL core weight budget is backed by present signals, and whether
+ // any core return signal (rent/cap/dscr) is present at all (bonuses alone = thin, untrustworthy).
+ const coreWeight = parts.filter(p => p.core).reduce((s, p) => s + p.weight, 0);
+ const coverage = coreWeight / (W.rentRoi + W.cap + W.dscr); // bonuses excluded from the denominator
+ const confidence = coreWeight <= 0 ? 'thin' : coverage >= 0.7 ? 'high' : coverage >= 0.35 ? 'med' : 'low';
+
+ return { score, parts, confidence, coverage: +coverage.toFixed(2) };
+ }
+
+ // Tier + color ramp for the map (sequential 0-100; distinct from the type + price palettes).
+ // 5 tiers so a legend key is readable. n/a -> grey.
+ const TIERS = [
+ { min: 80, c: '#16a34a', label: '80–100 · chase now' },
+ { min: 60, c: '#84cc16', label: '60–79 · strong' },
+ { min: 40, c: '#eab308', label: '40–59 · watch' },
+ { min: 20, c: '#f97316', label: '20–39 · weak' },
+ { min: 0, c: '#dc2626', label: '0–19 · pass' },
+ ];
+ const SCORE_NA = '#6b7280';
+ function scoreColor(score) {
+ const s = fin(score); if (s == null) return SCORE_NA;
+ for (const t of TIERS) if (s >= t.min) return t.c;
+ return SCORE_NA;
+ }
+
+ return { compute, scoreColor, TIERS, SCORE_NA, WEIGHTS: W, _ramp: ramp };
+});
diff --git a/public/consulting/intake.html b/public/consulting/intake.html
new file mode 100644
index 00000000..9d8003df
--- /dev/null
+++ b/public/consulting/intake.html
@@ -0,0 +1,154 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
+<title>New Website + Social — Intake · RENTV.com</title>
+<style>
+ :root{--ink:#14110e;--paper:#f6f2ec;--gold:#a9884f;--line:#dcd3c5}
+ *{box-sizing:border-box}
+ body{margin:0;font-family:system-ui,-apple-system,sans-serif;background:var(--paper);color:var(--ink);padding:0}
+ header{padding:40px 24px 20px;text-align:center;border-bottom:1px solid var(--line);background:#fff}
+ header .eyebrow{letter-spacing:.32em;text-transform:uppercase;font-size:11px;color:var(--gold)}
+ header h1{font-family:Georgia,serif;font-weight:400;font-size:30px;margin:.3em 0 .1em}
+ header p{color:#6b6357;margin:0}
+ form{max-width:760px;margin:0 auto;padding:28px 24px 80px}
+ fieldset{border:1px solid var(--line);background:#fff;margin:18px 0;padding:20px 22px}
+ legend{font-family:Georgia,serif;font-size:18px;padding:0 10px;color:var(--gold)}
+ label{display:block;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a7266;margin:14px 0 5px;font-weight:600}
+ input,textarea,select{width:100%;padding:11px 12px;border:1px solid var(--line);background:#faf8f4;font-size:15px;font-family:inherit;border-radius:2px}
+ textarea{min-height:76px;resize:vertical}
+ .two{display:grid;grid-template-columns:1fr 1fr;gap:0 18px}
+ .hint{font-size:12px;color:#9a9082;text-transform:none;letter-spacing:0;font-weight:400;margin-top:4px}
+ .check{display:flex;align-items:center;gap:8px;margin-top:10px}
+ .check input{width:auto}
+ button{background:var(--ink);color:var(--paper);border:0;padding:15px 34px;letter-spacing:.16em;text-transform:uppercase;font-size:13px;cursor:pointer;margin-top:8px}
+ button:hover{background:var(--gold)}
+ #done{display:none;background:#eef6ee;border:1px solid #b7d6b7;padding:18px;margin:18px 0;color:#25632a}
+</style>
+</head>
+<body>
+ <header>
+ <div class="eyebrow">Consulting Intake</div>
+ <h1>Let's build your website & social</h1>
+ <p>Answer what you can — this spawns your new site concepts and a social growth plan. No current website required.</p>
+ </header>
+
+ <form id="f">
+ <div id="done"></div>
+
+ <fieldset><legend>The Business</legend>
+ <div class="two">
+ <div><label>Business name*</label><input name="business_name" required></div>
+ <div><label>Industry / what you do</label><input name="industry" placeholder="e.g. car wash, yacht charters, law firm"></div>
+ </div>
+ <div class="two">
+ <div><label>City / service area</label><input name="location"></div>
+ <div><label>In business since</label><input name="since" placeholder="year"></div>
+ </div>
+ <label>One-line pitch (tagline)</label><input name="tagline" placeholder="What makes you the obvious choice?">
+ </fieldset>
+
+ <fieldset><legend>Contact</legend>
+ <div class="two">
+ <div><label>Your name</label><input name="contact_name"></div>
+ <div><label>Role</label><input name="contact_role"></div>
+ </div>
+ <div class="two">
+ <div><label>Email</label><input name="contact_email" type="email"></div>
+ <div><label>Phone</label><input name="contact_phone"></div>
+ </div>
+ </fieldset>
+
+ <fieldset><legend>Current Presence</legend>
+ <div class="check"><input type="checkbox" id="nosite" name="no_website" value="1"><label for="nosite" style="margin:0;text-transform:none;letter-spacing:0;font-weight:400">We don't have a website yet — build one from scratch</label></div>
+ <label>Current website (if any)</label><input name="current_website" placeholder="https://">
+ <div class="hint">Social handles — leave blank what you don't have; we'll set up the rest.</div>
+ <div class="two">
+ <div><label>Instagram</label><input name="ig"></div>
+ <div><label>Facebook</label><input name="fb"></div>
+ <div><label>TikTok</label><input name="tt"></div>
+ <div><label>YouTube</label><input name="yt"></div>
+ <div><label>LinkedIn</label><input name="li"></div>
+ <div><label>Google Business Profile</label><input name="gbp"></div>
+ </div>
+ </fieldset>
+
+ <fieldset><legend>Goals & Growth</legend>
+ <label>Top goals (one per line)</label><textarea name="goals" placeholder="More bookings Rank on Google Grow Instagram"></textarea>
+ <label>Who is your ideal customer?</label><textarea name="audience"></textarea>
+ <label>Direct competitors (one per line)</label><textarea name="competitors"></textarea>
+ </fieldset>
+
+ <fieldset><legend>Brand & Aesthetic</legend>
+ <label>Aesthetic direction</label>
+ <select name="aesthetic">
+ <option value="">— pick a starting feel —</option>
+ <option>Luxury / editorial</option>
+ <option>Modern / minimal</option>
+ <option>Bold / energetic</option>
+ <option>Warm / approachable</option>
+ <option>Classic / trustworthy</option>
+ </select>
+ <div class="hint">We'll generate several concept versions of your site in different high-end looks to choose from.</div>
+ <label>Brand colors / anything you love or hate</label><textarea name="brand_notes"></textarea>
+ <label>Assets you have (logo, photos, video?)</label><textarea name="assets"></textarea>
+ </fieldset>
+
+ <fieldset><legend>Command Center — assign to your CRM</legend>
+ <div class="hint">These are the deliverable modules in your portal. Check the ones you want us to build out and assign as tracked items in your CRM — pick one, a few, or all.</div>
+ <div class="check" style="margin-top:14px"><input type="checkbox" id="mod_all"><label for="mod_all" style="margin:0;text-transform:none;letter-spacing:0;font-weight:600">Select all</label></div>
+ <div id="modgrid" style="display:grid;grid-template-columns:1fr 1fr;gap:6px 18px;margin-top:10px"></div>
+ </fieldset>
+
+ <button type="submit">Submit intake →</button>
+ </form>
+
+<script>
+// Mirrors the portal hamburger (build.mjs CATS) so the client can assign any
+// deliverable module to their CRM. Keep in sync with CATS if the menu changes.
+var MODULES = [
+ ['summary','Executive Summary'],['swot','SWOT Analysis'],['reputation','Reputation & Authority'],
+ ['empire','The Empire & Sublease.com'],['competitors','Competitors'],['advertising','Where to Advertise'],
+ ['timing','Timing & Cadence'],['audit','Website Audit'],['teardown','Page-by-Page Teardown'],
+ ['social','Social Media Audit'],['suggested','Suggested New Websites'],['partners','Unlikely Partners'],
+ ['revenue','Unlikely Revenue'],['videos','Video + Brand Films'],['dealdesk','Live Deal Desk (demo)'],
+ ['roadmap','90-Day Roadmap']
+];
+(function(){
+ var grid = document.getElementById('modgrid');
+ grid.innerHTML = MODULES.map(function(m){
+ return '<label class="check" style="margin:0;text-transform:none;letter-spacing:0;font-weight:400">'
+ + '<input type="checkbox" class="modchk" value="'+m[0]+'" data-label="'+m[1]+'"> '+m[1]+'</label>';
+ }).join('');
+ document.getElementById('mod_all').addEventListener('change', function(e){
+ document.querySelectorAll('.modchk').forEach(function(c){ c.checked = e.target.checked; });
+ });
+})();
+
+document.getElementById('f').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const fd = new FormData(e.target);
+ const o = Object.fromEntries(fd.entries());
+ const payload = {
+ business_name: o.business_name, industry: o.industry, location: o.location, since: o.since,
+ tagline: o.tagline, contact_name: o.contact_name, contact_role: o.contact_role,
+ contact_email: o.contact_email, contact_phone: o.contact_phone,
+ current_website: o.no_website ? null : (o.current_website || null),
+ no_website: !!o.no_website,
+ socials: { instagram: o.ig||null, facebook: o.fb||null, tiktok: o.tt||null, youtube: o.yt||null, linkedin: o.li||null, google_business: o.gbp||null },
+ goals: (o.goals||'').split('\n').map(s=>s.trim()).filter(Boolean),
+ audience: o.audience, competitors: (o.competitors||'').split('\n').map(s=>s.trim()).filter(Boolean),
+ aesthetic: o.aesthetic, brand_notes: o.brand_notes, assets: o.assets,
+ crm_modules: Array.prototype.map.call(document.querySelectorAll('.modchk:checked'), function(c){ return { id: c.value, label: c.getAttribute('data-label') }; }),
+ };
+ const r = await fetch('/api/intake', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
+ const j = await r.json();
+ const d = document.getElementById('done');
+ d.textContent = j.message || 'Received — thank you.';
+ d.style.display = 'block';
+ d.scrollIntoView({ behavior:'smooth' });
+ e.target.reset();
+});
+</script>
+</body>
+</html>
diff --git a/public/consulting/portal.html b/public/consulting/portal.html
new file mode 100644
index 00000000..760122cf
--- /dev/null
+++ b/public/consulting/portal.html
@@ -0,0 +1,604 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Growth Strategy</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
+<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
+<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
+<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css">
+<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css">
+<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
+<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
+<script src="/portal/deal-score.js"></script>
+<style>
+:root{--bg:#0c1016;--panel:#121722;--ink:#eef1f6;--gold:#c8a15a;--line:rgba(200,161,90,.22);--dim:rgba(238,241,246,.66)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Jost',system-ui,sans-serif;font-size:19px;line-height:1.75;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Cormorant Garamond',Georgia,serif}a{color:inherit;text-decoration:none}
+.bar{position:fixed;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;justify-content:flex-start;padding:18px 34px;background:linear-gradient(180deg,rgba(12,16,22,.94),rgba(12,16,22,0));backdrop-filter:blur(5px)}
+.burger{width:30px;height:20px;position:relative;cursor:pointer;background:none;border:0}.burger span{position:absolute;left:0;right:0;height:1.6px;background:var(--ink);transition:.35s}
+.burger span:nth-child(1){top:1px}.burger span:nth-child(2){top:9px}.burger span:nth-child(3){top:17px}
+body.nav .burger span:nth-child(1){transform:translateY(8px) rotate(45deg)}body.nav .burger span:nth-child(2){opacity:0}body.nav .burger span:nth-child(3){transform:translateY(-8px) rotate(-45deg)}
+.menu{position:fixed;top:0;left:0;bottom:0;width:min(420px,86vw);z-index:39;background:var(--panel);border-right:1px solid var(--line);transform:translateX(-100%);transition:.42s cubic-bezier(.6,0,.2,1);padding:92px 40px;overflow:auto}
+.mbrand{display:block;font-family:'Cormorant Garamond',serif;font-size:26px;letter-spacing:.28em;text-transform:uppercase;color:var(--ink);margin-bottom:28px}
+.mbrand span{color:var(--gold)}
+.mbrand small{display:block;font-family:'Jost',system-ui,sans-serif;font-size:9px;letter-spacing:.32em;color:var(--gold);margin-top:4px}
+body.nav .menu{transform:none}body.nav .scrim{opacity:1;pointer-events:auto}
+.scrim{position:fixed;inset:0;z-index:38;background:rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:.42s}
+.menu .mh{font-family:'Cormorant Garamond',serif;font-size:13px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-bottom:18px}
+.menu a{display:block;font-family:'Cormorant Garamond',serif;font-size:25px;padding:10px 0;border-bottom:1px solid var(--line);opacity:.85;transition:.25s}
+.menu a:hover{opacity:1;color:var(--gold);padding-left:8px}
+.menu .minfo{margin-top:28px;border-top:1px solid var(--line);padding-top:20px}.menu .minfo p{font-size:13px;line-height:1.7;color:var(--dim);border:0;padding:2px 0}.menu .minfo a{display:inline;font-size:13px;color:var(--gold);border:0;padding:0}
+.hero{min-height:96vh;position:relative;display:flex;flex-direction:column;justify-content:center;padding:0 clamp(24px,7vw,120px);overflow:hidden}
+.slides{position:absolute;inset:0;z-index:0}
+.slide{position:absolute;inset:0;background-size:cover;background-position:center;opacity:0;transform:scale(1.04);transition:opacity 1.4s ease;will-change:transform,opacity}
+.slide.on{opacity:1;animation:kenburns 13s ease-out forwards}
+@keyframes kenburns{from{transform:scale(1.04) translate3d(0,0,0)}to{transform:scale(1.13) translate3d(-1.4%,-1.2%,0)}}
+.hero:after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,rgba(12,16,22,.94),rgba(12,16,22,.6) 55%,rgba(12,16,22,.35));z-index:1}
+.hero .in{position:relative;z-index:2;max-width:920px}
+.kick{font-size:12px;letter-spacing:.4em;text-transform:uppercase;color:var(--gold);margin-bottom:22px}
+.hero h1{font-family:'Cormorant Garamond',serif;font-weight:400;font-size:clamp(44px,7.5vw,104px);line-height:1}.hero h1 span{color:var(--gold)}
+.hero p{max-width:62ch;margin-top:24px;font-size:18px;color:var(--dim)}
+.metrics{display:flex;flex-wrap:wrap;gap:46px;margin-top:46px}
+.metrics .v{font-family:'Cormorant Garamond',serif;font-size:40px;color:var(--gold);line-height:1}
+.metrics .l{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--dim);margin-top:6px}
+section{padding:0 clamp(24px,7vw,120px);border-top:1px solid var(--line);scroll-margin-top:60px}
+.accbar{display:flex;gap:12px;justify-content:flex-end;padding:14px clamp(24px,7vw,120px);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
+.accbar button{background:none;border:1px solid var(--line);color:var(--gold);font-family:'Jost';font-size:11px;letter-spacing:.18em;text-transform:uppercase;padding:9px 18px;cursor:pointer}.accbar button:hover{background:var(--gold);color:var(--bg)}
+.acchead{width:100%;text-align:left;background:none;border:0;cursor:pointer;display:flex;gap:22px;padding:36px 0;color:var(--ink)}
+.acchead .htext{flex:1}.eyebrow{font-size:12.5px;letter-spacing:.36em;text-transform:uppercase;color:var(--gold);margin-bottom:12px}
+h2{font-family:'Cormorant Garamond',serif;font-weight:400;font-size:clamp(30px,4.4vw,54px);line-height:1.05;max-width:20ch}
+.acchead .chev{font-family:'Cormorant Garamond',serif;font-size:36px;color:var(--gold);transition:.3s;line-height:.7}
+section.open .chev{transform:rotate(45deg)}.acchead:hover h2{color:var(--gold);transition:.25s}
+.accbody{display:none;padding-bottom:60px}section.open .accbody{display:block;animation:af .45s ease}
+@keyframes af{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
+.lead2{max-width:70ch;color:var(--dim);margin-top:6px;font-size:20px}
+table{width:100%;border-collapse:collapse;margin-top:34px;font-size:16px}th,td{text-align:left;padding:16px 14px;border-bottom:1px solid var(--line);vertical-align:top;font-size:15.5px}
+th{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);font-weight:400}
+tr.you{background:rgba(200,161,90,.08)}tr.you td:first-child{color:var(--gold)}.stars{color:var(--gold);letter-spacing:2px}
+td.mo{font-family:'Cormorant Garamond',serif;color:var(--gold);font-size:18px;width:60px}
+.chartwrap{margin-top:34px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:22px 20px 14px;position:relative;height:340px}
+.chartwrap.tall{height:400px}.chartwrap .ct{font-family:'Cormorant Garamond',serif;font-size:15px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);margin-bottom:10px}
+.cards2{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1px;background:var(--line);margin-top:34px}
+.cards2 .c{background:var(--bg);padding:32px 28px}.cards2 .c h3{font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--gold);margin-bottom:10px}.cards2 .c p{color:var(--dim);font-size:16px;line-height:1.6}
+.flag{color:#e8a0a0}.good{color:#9fd6b0}
+.swotgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1px;background:var(--line);margin-top:34px}
+.swotq{background:var(--bg);padding:28px 26px}.swotq h3{font-family:'Cormorant Garamond',serif;font-size:22px;margin-bottom:12px}
+.swotq.pos{border-top:2px solid #9fd6b0}.swotq.neg{border-top:2px solid #e8a0a0}
+.swotq.pos h3{color:#9fd6b0}.swotq.neg h3{color:#e8a0a0}
+.swotq ul{margin:0;padding-left:18px}.swotq li{color:var(--dim);font-size:15px;line-height:1.55;margin:6px 0}
+.lhf-list{list-style:none;margin:34px 0 0;padding:0;display:grid;gap:14px}
+.lhf-item{display:grid;grid-template-columns:44px 1fr;gap:18px;background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:20px 22px}
+.lhf-item.fruit{border-color:var(--gold)}
+.lhf-rk{font-family:'Cormorant Garamond',Georgia,serif;font-size:32px;color:var(--gold);line-height:1}
+.lhf-ti{font-size:19px;color:var(--ink);font-weight:500;line-height:1.3}
+.lhf-badges{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}
+.lhf-b{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);padding:4px 10px;border-radius:3px}
+.lhf-b.fruit{background:var(--gold);color:var(--bg);border:0;font-weight:700}
+.lhf-rat{color:var(--dim);font-size:15px;line-height:1.55;margin-top:10px}
+.srclegend{display:flex;flex-wrap:wrap;gap:10px;margin:26px 0 4px}
+.srcb{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;padding:5px 11px;border-radius:3px;border:1px solid var(--line);white-space:nowrap}
+.srcb.v{color:#9fd6b0;border-color:rgba(159,214,176,.4)}
+.srcb.o{color:#78aaff;border-color:rgba(120,170,255,.4)}
+.srcb.r{color:var(--gold)}
+.srcb.d{color:#b78d3f}
+.srcb.a{color:var(--dim)}
+.srcb.e{color:#e8a0a0;border-color:rgba(232,160,160,.4)}
+td.srccell{white-space:nowrap;vertical-align:middle}
+.srcnote{margin-top:34px;display:flex;gap:22px;padding:28px 30px;border:1px solid var(--line);background:var(--panel)}
+.srcnote .i{font-size:26px;color:var(--gold);line-height:1}
+.srcnote h4{font-family:'Cormorant Garamond',serif;font-size:21px;color:var(--gold);margin-bottom:8px}
+.srcnote p{color:var(--dim);font-size:15px;line-height:1.6}
+.srcnote code,.accbody td code{font-family:ui-monospace,Menlo,monospace;font-size:12.5px;color:var(--ink);background:rgba(200,161,90,.12);padding:1px 6px;border-radius:3px}
+.flagbox{display:flex;gap:22px;margin-top:34px;padding:30px 32px;border:1px solid var(--gold);background:rgba(200,161,90,.07)}
+.flagbox .i{font-size:30px;color:var(--gold)}.flagbox h3{font-family:'Cormorant Garamond',serif;font-size:24px;color:var(--gold);margin-bottom:10px}.flagbox p{color:var(--ink);opacity:.92;font-size:16px;line-height:1.6}
+.vgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-top:20px}
+.vtile{background:var(--vb);border:1px solid var(--line);padding:28px 26px;transition:.3s;position:relative}.vtile:hover{transform:translateY(-5px);box-shadow:0 22px 60px rgba(0,0,0,.55)}
+.vswatch{display:flex;height:36px;border-radius:3px;overflow:hidden;margin-bottom:20px}.vswatch span{flex:1}
+.vname{font-family:'Cormorant Garamond',serif;font-size:26px;color:var(--vi)}.vmodel{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--vg);margin-top:2px}
+.vmood{color:var(--vi);opacity:.72;font-size:13px;margin-top:10px;min-height:38px}.vopen{margin-top:16px;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--vg)}
+.pcard{display:flex;gap:22px;padding:24px 0;border-bottom:1px solid var(--line)}.pnum{font-family:'Cormorant Garamond',serif;font-size:30px;color:var(--gold);min-width:44px}.pcard h4{font-family:'Cormorant Garamond',serif;font-size:21px;margin-bottom:6px}.pcard p{color:var(--dim);font-size:16px;line-height:1.6}
+.heybox{margin-top:34px;display:grid;grid-template-columns:minmax(260px,360px) 1fr;gap:1px;background:var(--line);border:1px solid var(--line)}.heybox>div{background:var(--bg)}
+.heybox .film{background:#000;position:relative;aspect-ratio:9/16;max-height:520px}.heybox .film video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
+.heybox .film .ph{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:var(--gold);font-size:11px;letter-spacing:.24em;text-transform:uppercase;background:linear-gradient(160deg,#123048,#0c1016);text-align:center;padding:20px}
+.heybox .copy{padding:38px 36px}.heybox .copy h3{font-family:'Cormorant Garamond',serif;font-size:27px;margin-bottom:14px}.heybox .copy p{color:var(--dim);font-size:15px}
+.heybox .spec{margin-top:20px;display:flex;flex-wrap:wrap;gap:10px}.heybox .spec span{font-size:10px;letter-spacing:.16em;text-transform:uppercase;border:1px solid var(--line);padding:6px 12px;color:var(--gold)}
+.films5{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-top:34px}
+.f5{border:1px solid var(--line);background:var(--panel)}.f5 .fv{position:relative;aspect-ratio:9/16;background:#000;overflow:hidden}.f5 .fv video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
+.f5 .fph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--gold);font-size:11px;letter-spacing:.2em;text-transform:uppercase;background:linear-gradient(160deg,#123048,#0c1016);opacity:.9}
+.f5 .ft{font-family:'Cormorant Garamond',serif;font-size:19px;color:var(--gold);padding:14px 16px 4px}.f5 .fa{font-size:12.5px;color:var(--dim);padding:0 16px 16px}
+.sugg{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1px;background:var(--line);margin-top:20px}.sugg .s{background:var(--bg);padding:26px 24px}.sugg .s .p{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold)}.sugg .s h4{font-family:'Cormorant Garamond',serif;font-size:20px;margin:8px 0}.sugg .s p{font-size:15px;color:var(--dim);line-height:1.55}
+.road{list-style:none;margin-top:20px;position:relative;padding-left:36px}
+.road:before{content:'';position:absolute;left:9px;top:10px;bottom:10px;width:2px;background:linear-gradient(180deg,var(--gold),rgba(200,161,90,.14));transform:scaleY(0);transform-origin:top;transition:transform 1.5s cubic-bezier(.5,0,.2,1)}
+.road.drawn:before{transform:scaleY(1)}
+.road li{padding:22px 0;border-bottom:1px solid var(--line);display:flex;gap:26px;position:relative}
+.road li:before{content:'';position:absolute;left:-32px;top:30px;width:12px;height:12px;border-radius:50%;background:var(--bg);border:2px solid var(--gold);box-shadow:0 0 0 4px var(--bg);transform:scale(0);transition:transform .5s cubic-bezier(.2,1.5,.4,1)}
+.road.drawn li:before{transform:scale(1)}
+.road.drawn li:nth-child(1):before{transition-delay:.30s}.road.drawn li:nth-child(2):before{transition-delay:.62s}.road.drawn li:nth-child(3):before{transition-delay:.94s}.road.drawn li:nth-child(4):before{transition-delay:1.26s}
+.road .w{font-family:'Cormorant Garamond',serif;color:var(--gold);font-size:15px;letter-spacing:.2em;text-transform:uppercase;min-width:130px}.road .d{color:var(--dim)}
+@media(prefers-reduced-motion:reduce){.slide.on{animation:none;transform:scale(1)}.road:before{transition:none;transform:scaleY(1)}.road li:before{transition:none;transform:scale(1)}.accbody{animation:none!important}}
+.rv{opacity:0;transform:translateY(22px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}.rv.in{opacity:1;transform:none}
+/* Live Deal Desk (real CRCP data) */
+#dealmap{height:440px;border:1px solid var(--line);border-radius:8px;margin-top:34px;background:#0a0d12}
+.leaflet-container{background:#0a0d12;font-family:'Jost',sans-serif}
+.leaflet-popup-content-wrapper{background:var(--panel);color:var(--ink);border:1px solid var(--line)}
+.leaflet-popup-tip{background:var(--panel)}
+.dealgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;margin-top:30px}
+.deal{display:flex;gap:14px;background:var(--panel);border:1px solid var(--line);border-radius:6px;padding:16px;transition:.25s}
+.deal:hover{transform:translateY(-3px);box-shadow:0 14px 40px rgba(0,0,0,.4)}
+.dscore{min-width:52px;height:52px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:'Cormorant Garamond',serif;font-size:24px;color:#fff;background:#555}
+.daddr{font-weight:500;font-size:15px;line-height:1.3}
+.dcity{font-size:12.5px;color:var(--dim);margin-top:2px}
+.dnums{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
+.dnums span{font-size:11px;letter-spacing:.06em;border:1px solid var(--line);color:var(--gold);padding:3px 8px;border-radius:3px}
+/* page-by-page teardown */
+.tdp{display:grid;grid-template-columns:minmax(280px,400px) 1fr;gap:26px;padding:34px 0;border-bottom:1px solid var(--line)}
+.tdshot img{width:100%;border:1px solid var(--line);border-radius:6px;display:block;transition:.3s}
+.tdshot a:hover img{transform:scale(1.02);box-shadow:0 16px 44px rgba(0,0,0,.5)}
+.tdcap{font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--dim);margin-top:8px;text-align:center}
+.tdhead{display:flex;align-items:center;gap:16px;margin-bottom:14px}
+.tdhead h3{font-size:clamp(20px,2.2vw,27px);font-weight:400}
+.tdgrade{min-width:52px;height:52px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:'Cormorant Garamond',serif;font-size:26px;color:#fff}
+.tdl{font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:#e8a0a0;margin:14px 0 6px}
+.tdl.fix{color:#9fd6b0}
+.tdp ul{list-style:none}
+.tdp ul li{padding:6px 0 6px 20px;position:relative;color:var(--dim);font-size:15.5px;line-height:1.55}
+.tdp ul li:before{content:'✕';position:absolute;left:0;color:#e8a0a0;font-size:12px;top:9px}
+.tdp ul.fixul li:before{content:'→';color:#9fd6b0}
+@media(max-width:820px){.tdp{grid-template-columns:1fr}}
+footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color:var(--dim);font-size:12px;letter-spacing:.14em;text-transform:uppercase;display:flex;justify-content:space-between;flex-wrap:wrap;gap:20px}
+@media(max-width:640px){.heybox{grid-template-columns:1fr}.metrics{gap:26px}}
+</style></head><body>
+<header class="bar"><button class="burger" onclick="document.body.classList.toggle('nav')" aria-label="Menu" aria-expanded="false" aria-controls="portalmenu"><span></span><span></span><span></span></button></header>
+<div class="scrim" onclick="document.body.classList.remove('nav')"></div>
+<nav class="menu" id="portalmenu" onclick="if(event.target.closest('a'))document.body.classList.remove('nav')">
+ <a class="mbrand" href="#summary">REN<span>TV</span>.com<small>Growth Strategy · 2026</small></a>
+ <div class="mh">Contents</div><a href="#summary">Executive Summary</a><a href="#swot">SWOT Analysis</a><a href="#lowhanging">Low Hanging Fruit</a><a href="#reputation">Reputation & Authority</a><a href="#empire">The Empire & Sublease.com</a><a href="#competitors">Competitors</a><a href="#wires">News + Conference Rivals</a><a href="#advertising">Where to Advertise</a><a href="#timing">Timing & Cadence</a><a href="#audit">Website Audit</a><a href="#teardown">Page-by-Page Teardown</a><a href="#social">Social Media Audit</a><a href="#suggested">Suggested New Websites</a><a href="#partners">Unlikely Partners</a><a href="#revenue">Unlikely Revenue</a><a href="#videos">Video + Brand Films</a><a href="#dealdesk">Live Deal Desk (demo)</a><a href="#roadmap">90-Day Roadmap</a><a href="#sources">Sources & Methodology</a>
+ <div class="minfo"><div class="mh">RENTV.com, Inc.</div><p>5757 W. Century Blvd., #700, Los Angeles, CA 90045</p><p>Steve Bloom — Owner / Producer · (310) 213-6409</p><p>Arnie Garfinkel — CRE Talk co-host · (818) 532-7660</p><p>Allen Wolfsheimer — News/Resources · (310) 836-8238</p><p>Daniel Ginzburg — CFO, Dandeana Corp · (424) 251-8405</p><p><span>rentv.com</span> · <a href="https://www.rentvreview.com" target="_blank" rel="noopener">The REview ↗</a> · <a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn ↗</a></p></div>
+</nav>
+
+<div class="hero" id="summary">
+ <div class="slides"><div class="slide on" style="background-image:url('https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40')"></div><div class="slide" style="background-image:url('https://images.unsplash.com/photo-1449824913935-59a10b8d2000?auto=format&fit=crop&w=1920&q=68&sat=-40')"></div><div class="slide" style="background-image:url('https://images.unsplash.com/photo-1460472178825-e5240623afd5?auto=format&fit=crop&w=1920&q=68&sat=-40')"></div><div class="slide" style="background-image:url('https://images.unsplash.com/photo-1515263487990-61b07816b324?auto=format&fit=crop&w=1920&q=68&sat=-40')"></div><div class="slide" style="background-image:url('https://images.unsplash.com/photo-1554469384-e58fac16e23a?auto=format&fit=crop&w=1920&q=68&sat=-40')"></div></div>
+ <div class="in">
+ <div class="kick">Los Angeles · Western U.S. · Confidential strategy brief · for Steve Bloom</div>
+ <h1 class="disp">REN<span>TV</span>.com</h1>
+ <p>Steve Bloom has quietly built a CRE media <b>empire</b>: the RENTV news brand (est. 1998), the State of the Market conference franchise, the CRE Talk interview show, "The REview" video platform, an active email operation — and the sublease.com domain with a relaunch plan in hand. What's missing is one modern web presence that unifies it, captures the audience, and monetizes the whole. This brief maps exactly that.</p>
+ <div class="metrics">
+ <div class="m"><div class="v">3</div><div class="l">Web properties (rentv · REview · sublease)</div></div>
+ <div class="m"><div class="v" data-c="19">0</div><div class="l">CRE Talk CEO episodes</div></div>
+ <div class="m"><div class="v" data-c="818">0</div><div class="l">LinkedIn followers (dormant)</div></div>
+ <div class="m"><div class="v">$175</div><div class="l">Conference ticket · real franchise</div></div>
+ </div>
+ </div>
+</div>
+
+<div class="accbar"><button id="xall">Expand all</button><button id="call">Collapse all</button></div>
+
+<section id="reputation"><div class="eyebrow">01 · Reputation & Authority</div><h2 class="disp">A trusted name — under-leveraged.</h2><div class="accbody">
+ <p class="lead2">RENTV has covered Western-U.S. commercial real estate since <b>1998</b> and runs the RENTV Conferences franchise — that's two-plus decades of brand equity and a live events audience most competitors would pay dearly for. The gap isn't credibility; it's <b>distribution and modernization</b>.</p>
+ <div class="cards2"><div class="c"><h3>Strengths</h3><p class="good">25-year archive & authority · the State of the Market conference franchise · CRE Talk (19+ CEO episodes, sponsored) · The REview video platform · an active email operation · deep Western-U.S. focus.</p></div><div class="c"><h3>Watch-outs</h3><p class="flag">Dated pre-mobile site · social dormant & unlinked · no newsletter capture visible · no paid distribution · younger CRE pros discover news elsewhere.</p></div><div class="c"><h3>Action</h3><p>Modernize the site, turn on a daily newsletter, activate LinkedIn — convert 25 years of authority into owned audience & recurring revenue.</p></div></div>
+</div></section>
+
+<section id="swot"><div class="eyebrow">Strategic Snapshot · SWOT</div><h2 class="disp">The whole board on one screen.</h2><div class="accbody">
+ <p class="lead2">The SWOT agent runs four lenses over RENTV's profile — internal <b>Strengths</b> and <b>Weaknesses</b>, external <b>Opportunities</b> and <b>Threats</b> — so the strategy that follows has a single, honest starting picture.</p>
+ <div class="swotgrid">
+ <div class="swotq pos"><h3>Strengths</h3><ul><li>25-year brand & archive — trusted CRE authority since 1998</li><li>State of the Market conference franchise ($175 ticket, live audience)</li><li>CRE Talk — 19+ sponsored CEO episodes</li><li>The REview video platform + an active email operation</li><li>Owns sublease.com (relaunch plan in hand)</li><li>Deep, defensible Western-U.S. focus</li></ul></div>
+ <div class="swotq neg"><h3>Weaknesses</h3><ul><li>Dated, pre-mobile website</li><li>Social accounts dormant & unlinked (LinkedIn 818 followers, idle)</li><li>No visible newsletter capture — audience not owned</li><li>No paid distribution</li><li>Three properties siloed — no cross-linking, separate lists</li></ul></div>
+ <div class="swotq pos"><h3>Opportunities</h3><ul><li>Daily newsletter — convert 25 years of authority into owned audience</li><li>Relaunch sublease.com as a marketplace attached to the media brand</li><li>Recurring plans to brokers / agents / broker-dealers ($99–$900+/mo)</li><li>Package video + brand films for sponsors</li><li>Referral engines: lenders, PropTech, universities, EDC orgs</li></ul></div>
+ <div class="swotq neg"><h3>Threats</h3><ul><li>National machines out-distribute regionally (Bisnow, CoStar/LoopNet, Crexi, The Real Deal)</li><li>Younger CRE pros discover news on other platforms</li><li>Platform algorithm shifts & ad-blocking erode reach</li><li>CRE downturn softens conference & sponsor spend</li></ul></div>
+ </div>
+ <div class="flagbox" style="margin-top:34px"><div class="i">⚡</div><div><h3 class="disp">Why the low-hanging fruit is low-hanging</h3><p>The quickest wins sit where your <b>Strengths</b> meet your <b>Opportunities</b> — they activate assets you <i>already own</i> (the 25-year brand, the State of the Market conference franchise, the dormant-but-real social accounts, the Constant Contact list) to capture demand that <i>already exists</i>. That is exactly what makes them simple: no new infrastructure, no long build — just switching on what is already there. Ranked by <b>ROI ÷ time</b>, the fastest, highest-return of these — your low-hanging fruit — are listed in order just below.</p></div></div>
+</div></section>
+
+
+<section id="lowhanging"><div class="eyebrow">Strategic Snapshot · Quick Wins</div><h2 class="disp">Your low-hanging fruit, ranked.</h2><div class="accbody">
+ <p class="lead2">The fastest, highest-return moves — ranked by <b>ROI ÷ time</b> so the quickest wins with the biggest payoff rise to the top. <b>⚡</b> marks a true quick win: low effort, high impact, activating assets you already own.</p>
+ <ol class="lhf-list"><li class="lhf-item fruit rv"><div class="lhf-rk">1</div><div><div class="lhf-ti">Activate + grow the existing (underused) social channels</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.5</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Social</span><span class="lhf-b">impact 5 · effort 2</span></div><p class="lhf-rat">RENTV HAS LinkedIn (818 followers) + Facebook but neither is linked from rentv.com and posting looks dormant; runs zero paid ads. CRE-news peers (Bisnow, The Real Deal) win via LinkedIn + newsletter. Link the profiles site-wide, post daily deal coverage to LinkedIn, stand up X + Instagram, and add a newsletter-capture — a low-effort, high-impact activation of assets that already exist.</p></div></li><li class="lhf-item fruit rv"><div class="lhf-rk">2</div><div><div class="lhf-ti">Modernize the dated legacy site into a fast, mobile-first CRE news experience</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.0</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Growth</span><span class="lhf-b">impact 4 · effort 2</span></div><p class="lhf-rat">Seeded from the client intake goals — refine with market research.</p></div></li><li class="lhf-item fruit rv"><div class="lhf-rk">3</div><div><div class="lhf-ti">Grow newsletter + subscriber audience for daily deal coverage</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.0</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Growth</span><span class="lhf-b">impact 4 · effort 2</span></div><p class="lhf-rat">Seeded from the client intake goals — refine with market research.</p></div></li><li class="lhf-item fruit rv"><div class="lhf-rk">4</div><div><div class="lhf-ti">Promote RENTV Conferences and drive event registrations</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.0</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Growth</span><span class="lhf-b">impact 4 · effort 2</span></div><p class="lhf-rat">Seeded from the client intake goals — refine with market research.</p></div></li><li class="lhf-item fruit rv"><div class="lhf-rk">5</div><div><div class="lhf-ti">Build an integrated-social engine to distribute CRE deal news (LinkedIn, X, Instagram)</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.0</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Growth</span><span class="lhf-b">impact 4 · effort 2</span></div><p class="lhf-rat">Seeded from the client intake goals — refine with market research.</p></div></li><li class="lhf-item fruit rv"><div class="lhf-rk">6</div><div><div class="lhf-ti">Improve SEO so RENTV ranks for Western-U.S. commercial real estate news</div><div class="lhf-badges"><span class="lhf-b fruit">⚡ Quick win</span><span class="lhf-b">ROI 2.0</span><span class="lhf-b">⏱ Quick</span><span class="lhf-b">Growth</span><span class="lhf-b">impact 4 · effort 2</span></div><p class="lhf-rat">Seeded from the client intake goals — refine with market research.</p></div></li></ol>
+</div></section>
+
+
+<section id="empire"><div class="eyebrow">02 · The Portfolio — one owner, three properties</div><h2 class="disp">You're not a website. You're a media company.</h2><div class="accbody">
+ <p class="lead2">Verified from your own materials (via Daniel Ginzburg, 2026): Steve Bloom operates <b>three web properties and four content franchises</b>. Today they live in silos — separate sites, separate lists, no cross-linking. Unified, they're a regional CRE media company with a marketplace attached.</p>
+ <div class="cards2">
+ <div class="c"><h3>rentv.com</h3><p>The news & resources brand since 1998 — deal coverage, Property Spotlight emails, the trusted name. <span class="flag">Dated pre-mobile build.</span></p></div>
+ <div class="c"><h3>The REview — rentvreview.com</h3><p>Your video platform "created by RENTV for the real estate industry" — home of CRE Talk's Producer Channel. <b>Boom's main site</b>, and the industry's free video hub. Underexposed.</p></div>
+ <div class="c"><h3>sublease.com</h3><p>A category-defining premium domain in relaunch phase — with a full business plan already drafted (April 2026). The marketplace leg of the empire.</p></div>
+ </div>
+ <div class="cards2" style="margin-top:20px">
+ <div class="c"><h3>CRE Talk with Steve & Arnie</h3><p>19+ CEO interviews (Shopoff, Rising, Penner, Sigal…), 4 sponsors incl. Rockefeller Group. A real, sponsored show — cut it into shorts and it feeds every channel.</p></div>
+ <div class="c"><h3>State of the Market Conferences</h3><p>The Greater LA edition: Hilton Culver City, $175/seat, panels with CBRE, Colliers, Cushman, Rexford, Trammell Crow + the CoStar market report. A live franchise competitors can't fake.</p></div>
+ <div class="c"><h3>Email operation (Constant Contact)</h3><p>Conferences, CRE Talk, and sponsored Property Spotlight sends (e.g. The Hanley Archive) already go out — but the website never captures new readers into the list.</p></div>
+ </div>
+
+ <div style="margin-top:56px"></div>
+ <div class="eyebrow">The Sublease.com play — from Scott's plan</div>
+ <h2 class="disp">"The internet's most valuable vacant address in real estate."</h2>
+ <p class="lead2">The April 2026 business plan (drafted in Claude) maps sublease.com as the definitive two-sided sublease marketplace — residential + commercial — with RENTV as the warm-start: <i>"The RENTV.com relationship gives Sublease.com immediate access to a commercial real estate audience and credibility with brokers."</i> The new RENTV site should be built with that hand-off in mind.</p>
+ <div class="cards2">
+ <div class="c"><h3>The market</h3><p><b>$650B+</b> U.S. rental market · <b>40M+</b> renters moving annually · <b>190M sf</b> of commercial sublease inventory · competition rising through 2026.</p></div>
+ <div class="c"><h3>The model</h3><p>Premium commercial listings ($1.8M Yr-2) · ~3% residential transaction fee ($2.2M) · screening ($600K) · AI sublease agreements ($400K) · broker CRM ($900K) → <b>~$5.9M Yr-2</b>.</p></div>
+ <div class="c"><h3>The ask</h3><p><b>$2.5M seed</b> · 18-month path to profitability · Phase I: 500 verified listings, 50 brokers, 10K users — the broker network RENTV already owns.</p></div>
+ </div>
+ <div class="chartwrap"><div class="ct">Sublease.com — Year-2 revenue mix from the plan ($5.9M)</div><canvas id="subChart"></canvas></div>
+ <p class="lead2" style="font-size:15px;opacity:.7">Source: "Sublease.com Business Plan & Market Strategy · April 2026" (confidential, attached by Daniel Ginzburg).</p>
+</div></section>
+
+<section id="competitors"><div class="eyebrow">03 · Competitive Landscape</div><h2 class="disp">The regional authority vs. the national machines.</h2><div class="accbody">
+ <p class="lead2">RENTV's edge is regional depth + events. The threats are national newsletter/data machines (Bisnow, CoStar) with huge email reach and modern product.</p>
+ <div class="chartwrap"><div class="ct">Reach vs. digital modernization — RENTV vs. the field</div><canvas id="compChart"></canvas></div>
+ <table><thead><tr><th>Outlet</th><th>Focus</th><th>Footprint</th><th>Model</th><th>Read</th></tr></thead><tbody><tr class="you"><td>RENTV.com (you)</td><td>CRE news · deals · conferences</td><td>Western U.S.</td><td>Since 1998</td><td>Deep regional authority + a live events franchise — on a dated site with dormant social</td></tr><tr class=""><td>Bisnow</td><td>News + events, national</td><td>National</td><td>Newsletter-first</td><td>The model to study: free newsletters → events → sponsorship. Massive email reach</td></tr><tr class=""><td>The Real Deal</td><td>CRE news, bold brand</td><td>NY / national</td><td>Aggressive digital</td><td>Strong brand, paywall + events, heavy social + video</td></tr><tr class=""><td>Commercial Observer</td><td>Editorial CRE</td><td>NY / national</td><td>Premium editorial</td><td>Long-form authority, events, print heritage</td></tr><tr class=""><td>GlobeSt (ALM)</td><td>News + data</td><td>National</td><td>Utility / data</td><td>Broad coverage, data tools, conference network</td></tr><tr class=""><td>CoStar News</td><td>Data-backed news</td><td>National</td><td>Authority / data</td><td>Owns the data layer; news drives the CoStar/LoopNet funnel</td></tr></tbody></table>
+ <p class="lead2"><b>Takeaway:</b> don't out-scale the nationals — out-<i>local</i> them. Own Western-U.S. deal coverage + events, and copy Bisnow's newsletter→events→sponsorship flywheel at regional scale.</p>
+</div></section>
+
+<section id="wires"><div class="eyebrow">Competitive deep-dive · news + conferences</div><h2 class="disp">The rivals who already pair news with a stage.</h2><div class="accbody">
+ <p class="lead2">RENTV isn’t just a news site — it’s a news brand with a <b>live conference franchise</b> (State of the Market). That pairing is the moat. Below are the outlets running the same <b>news + events</b> playbook, mapped from your backyard outward — <b>Los Angeles → California → the Western U.S.</b> The closer in, the more directly they compete for the same brokers, sponsors, and stage.</p>
+
+ <div class="eyebrow">① Los Angeles — the home turf · Same brokers · same sponsors · same stage</div>
+ <p class="lead2">These outlets compete with RENTV head-on — for the same LA deal coverage, the same sponsor dollars, and the same live audience. This is the tier that matters most, and the one where RENTV’s 25-year local authority plus the State of the Market franchise should win.</p>
+ <table><thead><tr><th>Outlet</th><th>News + conferences</th><th>Model / reach</th><th>RENTV’s angle</th></tr></thead><tbody><tr class="you"><td>RENTV.com — you</td><td>CRE news since 1998 + State of the Market conferences</td><td>Deep LA / Western-U.S. authority · a live event franchise</td><td>—</td></tr><tr><td><b>Bisnow — Los Angeles</b></td><td>LA news vertical + a packed LA event calendar (SoCal CRE Finance & Capital Markets, SoCal Industrial)</td><td>The newsletter→events→sponsorship machine, run locally</td><td>The most direct model rival — but a generic national voice. Out-<i>local</i> it with regional depth & relationships.</td></tr><tr><td><b>Los Angeles Business Journal</b></td><td>LA business news + the 31st-annual CRE Awards (Biltmore, Feb) + Valley CRE Awards</td><td>Print/digital + a marquee awards-gala franchise</td><td>Broad business focus, not CRE-native. RENTV owns the deal-desk expertise LABJ can’t match.</td></tr><tr><td><b>Connect CRE (California)</b></td><td>CA/LA news + Connect events & meetups; sells session video</td><td>Regional news + events + paid video (a REview parallel)</td><td>The closest structural twin. RENTV’s edge = a real conference franchise + a 25-yr archive.</td></tr><tr><td><b>The Real Deal — Los Angeles</b></td><td>Aggressive LA CRE news + forums / showcases</td><td>Strong brand, heavy digital + social, events</td><td>Wins on brand buzz; RENTV wins on regional trust + events. Don’t out-loud them — out-cover them.</td></tr><tr><td><b>NAIOP SoCal</b></td><td>Association news + I.CON West industrial conference (LA, 2026)</td><td>Membership + a big industrial conference</td><td>An association, not media — a partner as much as a rival (co-brand the event).</td></tr><tr><td><b>ULI Los Angeles</b></td><td>Local research/content + member convenings</td><td>Nonprofit authority + events</td><td>Authority ally, not a news competitor — a content + panel partner.</td></tr></tbody></table>
+ <div class="eyebrow" style="margin-top:52px">② California — the state fight · NorCal + SoCal + Inland Empire</div>
+ <p class="lead2">One step out: statewide players and the Bay Area’s own regional shops. RENTV can extend its SoCal authority up the state before the nationals localize any further.</p>
+ <table><thead><tr><th>Outlet</th><th>News + conferences</th><th>Model / reach</th><th>RENTV’s angle</th></tr></thead><tbody><tr><td><b>GlobeSt (ALM)</b></td><td>National CRE news + the GlobeSt Events series (CA multifamily / apartments events)</td><td>National authority + a large event slate</td><td>A scale rival. Compete on regional intimacy, not national breadth.</td></tr><tr><td><b>Connect CRE — California</b></td><td>Statewide news + events (NorCal, SoCal, Inland Empire)</td><td>A regional-news + event network</td><td>Direct footprint overlap; RENTV’s conference franchise is the differentiator.</td></tr><tr><td><b>The Registry (Bay Area)</b></td><td>Northern-California CRE news + events</td><td>The NorCal analog to RENTV’s SoCal model</td><td>Proof the model works up-state — a template to emulate (or partner with) in NorCal.</td></tr><tr><td><b>Bisnow — CA markets</b></td><td>SF, OC, San Diego & Sacramento verticals, each with events</td><td>Market-by-market news + events</td><td>Same playbook, more markets. RENTV’s answer: own the Western-U.S. regional lane deeper.</td></tr><tr><td><b>InterFace / Western States CREF</b></td><td>CA-focused CRE conferences (Inland Empire; CREF finance)</td><td>Conference-led, news-adjacent</td><td>Event rivals RENTV can out-flank by pairing coverage WITH the stage.</td></tr></tbody></table>
+ <div class="eyebrow" style="margin-top:52px">③ Western U.S. — the regional model · The template RENTV can own market-by-market</div>
+ <p class="lead2">The widest tier is less “competitor,” more “proof of model”: regional publishers who pair CRE news with a conference franchise in each Western market — exactly what RENTV can scale from LA outward.</p>
+ <table><thead><tr><th>Outlet</th><th>News + conferences</th><th>Model / reach</th><th>RENTV’s angle</th></tr></thead><tbody><tr><td><b>France Media — Western Real Estate Business + InterFace</b></td><td>The largest regional CRE publisher: magazines + e-news + InterFace conferences across the West</td><td>A news + regional-conference machine at scale</td><td>The at-scale version of RENTV’s own model — study it, then own the SoCal lane it’s thin in.</td></tr><tr><td><b>Colorado Real Estate Journal (CREJ)</b></td><td>Rocky-Mountain CRE news + the full CREJ Conference Series (Denver, 2026)</td><td>A market-owning news + conference franchise</td><td>The clearest “do this in your market” blueprint — RENTV = the CREJ of the West Coast.</td></tr><tr><td><b>AZRE / AZ Big Media (Arizona)</b></td><td>Arizona CRE magazine + the AZRE Forum, RED Awards & PTK</td><td>State-level news + awards + a forecast forum</td><td>Arizona’s RENTV. Same shape, adjacent market — an expansion or partnership target.</td></tr><tr><td><b>Connect CRE (regional West)</b></td><td>Regional news + events across Western metros</td><td>A network of regional news + events</td><td>Broad but shallow per-market; RENTV wins on depth where it plays.</td></tr></tbody></table>
+ <div class="flagbox" style="margin-top:52px"><div class="i">◆</div><div><h3 class="disp">The wedge nobody owns</h3><p>National machines (Bisnow, GlobeSt, CoStar) out-distribute everyone but speak in a generic voice; the regional models (France Media, CREJ, AZRE) prove a <b>news + conference franchise wins a market</b> — yet none of them own <b>Western-U.S. CRE with a live events franchise and 25 years of local authority</b> the way RENTV can. Modernize distribution — a daily newsletter, an active LinkedIn, a mobile site — and the deep-LA authority + the conference franchise become a position no national or out-of-region rival can copy.</p></div></div>
+</div></section>
+
+<section id="advertising"><div class="eyebrow">04 · Where to Advertise & Grow</div><h2 class="disp">Build the owned audience first.</h2><div class="accbody">
+ <p class="lead2">Ranked by ROI for a regional CRE-news brand. The pattern: <b>newsletter + LinkedIn</b> (owned + B2B) first, then search + sponsorship.</p>
+ <div class="chartwrap"><div class="ct">Channel priority — relative ROI for a CRE-news brand</div><canvas id="adChart"></canvas></div>
+ <table><thead><tr><th>Channel</th><th>Best for</th><th>Priority</th><th>Why / play</th></tr></thead><tbody><tr><td><b>LinkedIn (primary)</b></td><td>B2B CRE audience</td><td class="stars">★★★★★</td><td>The CRE audience lives here. Your company page has 818 followers and posts dormant — post daily deal coverage, have staff reshare, run "CRE news" thought-leadership ads.</td></tr><tr><td><b>Email newsletter</b></td><td>Owned audience</td><td class="stars">★★★★★</td><td>Bisnow's entire empire is newsletters → events → sponsorship. A free daily/weekly "RENTV Daily" is the single highest-leverage build. You own the list forever.</td></tr><tr><td><b>Google Ads (Search)</b></td><td>High-intent</td><td class="stars">★★★★☆</td><td>Own "commercial real estate news [market]", "[market] office/industrial deals". Capture brokers searching for the deal you just covered.</td></tr><tr><td><b>Sponsored content</b></td><td>Monetize + reach</td><td class="stars">★★★★☆</td><td>Brokerages & lenders pay to co-publish deal analysis. Doubles as revenue AND distribution when they reshare.</td></tr><tr><td><b>RENTV Conferences</b></td><td>Events flywheel</td><td class="stars">★★★★★</td><td>Your events ARE the moat. Cross-promote content↔conferences: every article plugs the summit; every attendee joins the newsletter.</td></tr><tr><td><b>X / Twitter</b></td><td>CRE deal community</td><td class="stars">★★★☆☆</td><td>CRE Twitter breaks + trades deal news. Auto-post headlines, engage brokers; low cost, real reach in-industry.</td></tr><tr><td><b>Programmatic / native</b></td><td>Awareness</td><td class="stars">★★★☆☆</td><td>Run native units on CRE + finance sites to pull new readers into the newsletter funnel. Measure cost-per-subscriber.</td></tr><tr><td><b>SEO</b></td><td>Compounding</td><td class="stars">★★★★☆</td><td>20+ years of archives = a ranking asset. Modernize the site (mobile + speed) and structure deal pages to own "[property] sells" search.</td></tr></tbody></table>
+</div></section>
+
+<section id="timing"><div class="eyebrow">05 · Timing & Cadence</div><h2 class="disp">Ride the deal + conference calendar.</h2><div class="accbody">
+ <p class="lead2">CRE news demand tracks the transaction & events calendar — quarterly reports, spring & fall conference seasons, year-end roundups. Publish daily; spike coverage on the peaks.</p>
+ <div class="chartwrap"><div class="ct">Deal activity vs. readership by month</div><canvas id="timeChart"></canvas></div>
+ <table><thead><tr><th>Month</th><th>Driver</th><th>The move</th></tr></thead><tbody><tr><td class="mo">Jan</td><td>Year-ahead outlook · forecasts</td><td class="act">Publish the "2026 Western-U.S. CRE Outlook"; open annual sponsor packages</td></tr><tr><td class="mo">Feb</td><td>Q4 earnings / REIT results</td><td class="act">Cover public-REIT & brokerage earnings; recap year-end deal volume</td></tr><tr><td class="mo">Mar</td><td>SPRING conference season</td><td class="act">Peak events: cover + host panels; drive summit registration</td></tr><tr><td class="mo">Apr</td><td>Q1 market reports</td><td class="act">Publish quarterly vacancy/cap-rate reports (data content = links + leads)</td></tr><tr><td class="mo">May</td><td>Deal season builds</td><td class="act">Sales/leasing volume rises — max deal coverage + newsletter growth push</td></tr><tr><td class="mo">Jun</td><td>Mid-year review</td><td class="act">"Biggest deals of H1" roundup — highly shareable, sponsor-friendly</td></tr><tr><td class="mo">Jul</td><td>Summer slow · evergreen</td><td class="act">Run explainers, market primers, and build the fall content pipeline</td></tr><tr><td class="mo">Aug</td><td>Pre-fall sell-in</td><td class="act">Lock Q4 conference sponsors; tease the fall summit</td></tr><tr><td class="mo">Sep</td><td>FALL conference peak</td><td class="act">Your marquee summit — content + events + sponsorship converge</td></tr><tr><td class="mo">Oct</td><td>Q3 reports · deal surge</td><td class="act">Quarterly data + heavy transaction coverage (year's busiest)</td></tr><tr><td class="mo">Nov</td><td>Year-end deal push</td><td class="act">"Deals of the year" build; open next-year sponsor renewals</td></tr><tr><td class="mo">Dec</td><td>Year in review · predictions</td><td class="act">Top-10 deals, people moves, and the "what's next" predictions issue</td></tr></tbody></table>
+ <p class="lead2"><b>Posting rhythm:</b> daily deal briefs, a weekly roundup newsletter, LinkedIn 1–2×/day on weekday mornings when brokers scan. Publish quarterly data reports on schedule — they earn links and leads.</p>
+</div></section>
+
+<section id="audit"><div class="eyebrow">06 · Website Audit</div><h2 class="disp">A 1998 site for a 2026 audience.</h2><div class="accbody">
+ <p class="lead2">Live audit of <b>rentv.com</b> — observed directly:</p>
+ <div class="cards2">
+ <div class="c"><h3>Architecture</h3><p class="flag">Legacy <b>table-based layout</b> with <b>window.open() popups</b> and non-UTF-8 encoding — a pre-responsive build. Breaks on mobile, where most CRE reading now happens.</p></div>
+ <div class="c"><h3>First impression</h3><p>No modern hero, no motion, dense text. The #1 upgrade: a <b>clean editorial front page</b> with a lead story, card grid, and a newsletter capture above the fold.</p></div>
+ <div class="c"><h3>Mobile & speed</h3><p class="flag">Not mobile-responsive; heavy legacy markup. Brokers scan on phones between meetings — this is lost readership every day.</p></div>
+ <div class="c"><h3>Conversion</h3><p>No visible newsletter capture or clear subscribe path. Every visitor should be one click from joining the list — the audience you own forever.</p></div>
+ </div>
+ <p class="lead2">Six ready-to-review redesign concepts below — each modeled on a CRE-news leader, with a modern editorial front page already built.</p>
+</div></section>
+
+<section id="teardown"><div class="eyebrow">07 · Page-by-Page Teardown</div><h2 class="disp">Every page, graded — with the fix.</h2><div class="accbody">
+ <p class="lead2">We captured every page of rentv.com live and put each through an adversarial review. The pattern: <b>the content operation works — the website destroys it at every conversion point.</b> Headline finding: the site still runs <b>Universal Analytics (UA-148799896-1)</b>, which Google shut down in 2023 — <b>there has been no traffic data for ~3 years</b>. Every decision below is currently being made blind.</p>
+ <div class="tdp rv"><div class="tdshot"><a href="/portal/audit/index.png" target="_blank"><img src="/portal/audit/index.png" loading="lazy" alt="index screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ea580c">D+</span><h3 class="disp">rentv.com (homepage)</h3></div><div class="tdl">Issues</div><ul><li>Above the fold is a flea market: a sublease.com banner owns the top-right quarter, plus two more ads and a video box — before a single headline.</li><li>Three-column table layout leaves the actual news ~45% of a fixed-width page; nav links are 11px gray text.</li><li>Every story gets identical tiny treatment — no lead story, no hierarchy, no way to scan. No bylines, no trust anchors.</li><li>"The REview" widget renders as an unexplained black box with no CTA.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Kill the top-of-fold ad wall; hero = one featured story card (large image, 24px+ headline, deck, byline).</li><li>Responsive grid: full-width hero → card feed → labeled sticky sponsor rail (max 2 ads).</li><li>Editorial tiers: lead story, top-3 row, then the feed — type scale does the talking.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/advertise.png" target="_blank"><img src="/portal/audit/advertise.png" loading="lazy" alt="advertise screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#dc2626">F</span><h3 class="disp">/advertise.php — the AD SALES page</h3></div><div class="tdl">Issues</div><ul><li>The page that makes the money is a wall of 12px small print — products listed with no pricing table, no media kit, no visuals of any ad unit.</li><li>Zero proof of audience: no open rates, no advertiser logos, no case study. A media buyer closes the tab in 8 seconds.</li><li>Sole CTA is "call us / raw email" — no form, no rate-card download, no buy path.</li><li>A sublease.com ad sits in the header of the page selling RENTV’s own inventory — self-defeating.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Rebuild as a media-kit landing page: 3-tier pricing table (Newsletter / eBlast / Display), audience stats in bold, "Request Media Kit" form.</li><li>Add a Current Advertisers logo row + one pull-quote (one phone call gets it).</li><li>Remove sublease.com from this page entirely.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/subscriber.png" target="_blank"><img src="/portal/audit/subscriber.png" loading="lazy" alt="subscriber screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ea580c">D</span><h3 class="disp">/subscriber_info.php — the $146/yr member page</h3></div><div class="tdl">Issues</div><ul><li>A real revenue product ($146/yr Executive Subscriber) sold via a dense bullet wall — free vs. paid never cleanly separated; unanswerable in 10 seconds.</li><li>No sample issue, no preview, no testimonial, no subscriber count — buying an information product sight-unseen.</li><li>CTA is plain text: "CLICK HERE or call us." No visible checkout; beige 2005-registrar aesthetic.</li><li>The News Archive is sold as a paid benefit — but the archive page is freely accessible to everyone.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Two-column conversion page: benefits (headline + one-liner + frequency) left, price card with live Stripe checkout right — add a $15/mo option.</li><li>Publish one Sample Issue link — the single biggest conversion lift available.</li><li>Gate the archive or stop selling it; never sell what you give away.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/providers.png" target="_blank"><img src="/portal/audit/providers.png" loading="lazy" alt="providers screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#dc2626">F</span><h3 class="disp">/service-providers.php — the marketplace</h3></div><div class="tdl">Issues</div><ul><li>~40 categories, every single one "(0)" listings — Accounting (0), Brokerage (0), Construction (0). A directory of failure that trains visitors never to return.</li><li>No search, no cards, no map, no filters — a 1999 Yellow Pages list even if inventory existed.</li><li>"Don’t Be Left Out!" CTA asks providers to join a visibly empty room.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>SEED BEFORE RELAUNCH: scrape LA County business-license data + web research (Exa) for CRE-category firms — contractors, brokers, architects, property managers, lenders — and pre-populate 10–15 real listings per major category with phone numbers. Offer each firm its free listing to claim; they upgrade. An empty directory is not a product; a full one is. <a href="/versions/marketplace.html" target="_blank" style="color:#9fd6b0;text-decoration:underline">→ We already built the seeded demo: 62 real LA providers, live here.</a></li><li>Rebuild as a searchable card grid (name, category badge, city, phone, Contact) with geography filter.</li><li>Monetize with a Featured Listing tier — after inventory exists, never before.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/store.png" target="_blank"><img src="/portal/audit/store.png" loading="lazy" alt="store screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ea580c">D-</span><h3 class="disp">/store.php — conferences/store</h3></div><div class="tdl">Issues</div><ul><li>Labeled "store," contains no product, price, or buy button — a press page wearing a store costume.</li><li>A FUTURE conference (Arizona, Oct 29 2026) is on the page with no Register button, no speakers, no price — revenue advertising that goes nowhere.</li><li>Past and future events unfiltered in one feed; sponsor logos outweigh the editorial 2:1.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Split: "Upcoming Events" above the fold (date, venue, price, Register Now) vs. "Past Coverage" archive grid.</li><li>Sell recorded sessions as an on-demand library: title, price, 90-second preview, buy button.</li><li>Rename the tab — Conferences and Store are different intents.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/archive.png" target="_blank"><img src="/portal/audit/archive.png" loading="lazy" alt="archive screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ea580c">D</span><h3 class="disp">/archive.php</h3></div><div class="tdl">Issues</div><ul><li>20 bare hyperlinks with no dates, story counts, previews, or search — a filing cabinet with the labels ripped off.</li><li>Freely accessible while being sold as a paid subscriber benefit.</li><li>No orientation: what years, what geographies, how fresh — unknowable.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Full-text search + geography/content-type filters over the archive.</li><li>Each link shows recent stories + a count ("482 articles, 2019–2026").</li><li>Pick a lane: gate it as a benefit, or use it free to drive newsletter signups.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/newsletter.png" target="_blank"><img src="/portal/audit/newsletter.png" loading="lazy" alt="newsletter screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ea580c">D+</span><h3 class="disp">/newsletter.php</h3></div><div class="tdl">Issues</div><ul><li>NINE fields for a free newsletter (address, zip, phone…) — Bisnow asks for one. Conversion ≈ zero.</li><li>Distorted-text homemade CAPTCHA ("rjvxk6") — broken-looking, inaccessible.</li><li>Right half of the page is empty void; no pitch, no sample, no reason to sign up — the heading is just "NEWSLETTER."</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Three fields max: Email (required), Name, Company. Delete the rest.</li><li>Invisible reCAPTCHA v3 or honeypot; kill the distorted image.</li><li>Add the pitch: "Join 50,000+ CRE professionals · twice-weekly SoCal deals · free" + a sample-issue link.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/subscribe.png" target="_blank"><img src="/portal/audit/subscribe.png" loading="lazy" alt="subscribe screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#dc2626">F</span><h3 class="disp">/subscribe.php — the paywall</h3></div><div class="tdl">Issues</div><ul><li>The paywall page is a cartoon dog GIF + "Hold on there sport!" + a login box. That is the entire page every non-subscriber hits.</li><li>No subscribe CTA, no path for non-members, no visible password reset — a dead end that costs conversions daily.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Replace with a real paywall intercept: what you’re missing, "$146/yr — Subscribe Now" primary button, login secondary.</li><li>Remove the dog and "sport" TODAY — before any redesign.</li><li>Wire a real password-reset flow; a locked-out subscriber is a churned subscriber.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/contact.png" target="_blank"><img src="/portal/audit/contact.png" loading="lazy" alt="contact screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#ca8a04">C-</span><h3 class="disp">/contact.php</h3></div><div class="tdl">Issues</div><ul><li>Twelve fields including FAX in 2026; same broken CAPTCHA; right half of the page is empty.</li><li>No visible email fallback if the form breaks (and on a Windows-1252 site, forms break).</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Six fields: Name, Company, Email, Phone (opt), Subject dropdown, Message.</li><li>Show a direct email + phone under the form; fix the half-empty template site-wide.</li></ul></div></div><div class="tdp rv"><div class="tdshot"><a href="/portal/audit/jobworks.png" target="_blank"><img src="/portal/audit/jobworks.png" loading="lazy" alt="jobworks screenshot"></a><div class="tdcap">live capture · click to open</div></div><div class="tdbody"><div class="tdhead"><span class="tdgrade" style="background:#dc2626">F</span><h3 class="disp">/jobworks.php — the jobs board</h3></div><div class="tdl">Issues</div><ul><li>Opens with an "Adobe Flash Player is no longer supported" error banner — Flash died Dec 2020; this has greeted every visitor for 5+ years.</li><li>Every job category is "(0)" — the second of three marketplace pillars with zero inventory.</li><li>Bare "create a listing" link with no pricing, reach, or pitch attached.</li></ul><div class="tdl fix">The fix</div><ul class="fixul"><li>Delete the Flash widget today; replace with static HTML.</li><li>Seed or kill: offer 10–15 subscriber firms free 90-day listings, or pull JobWorks from the nav until it has inventory.</li><li>One combined CRE Marketplace landing (live counts + featured listings + "Post a listing" CTA) instead of empty silos.</li></ul></div></div>
+ <p class="lead2" style="margin-top:30px"><b>Overall verdict:</b> a site that was genuinely valuable in 2005 and has received near-zero strategic investment since. News still publishes, conferences still run, and 50,000 opt-in subscribers is a real asset — but a broken Flash banner, an all-zeros marketplace, a cartoon-dog paywall and a phone-number ad page are actively destroying that asset. First fix, before any redesign: <b>install GA4 today</b> — until RENTV knows its own traffic, everything else is guesswork.</p>
+</div></section>
+
+<section id="social"><div class="eyebrow">08 · Social Media Audit</div><h2 class="disp">Real accounts, dormant and hidden.</h2><div class="accbody">
+ <div class="flagbox"><div class="i">⚠</div><div><h3 class="disp">You already make the content — it just never reaches the channels</h3><p>RENTV runs a real email operation (Constant Contact: Conferences, CRE Talk, sponsored Property Spotlights) and produces a real video show — yet the <b>LinkedIn page (818 followers) sits dormant</b>, <b>nothing is linked from rentv.com</b>, there's <b>no X or Instagram</b>, <b>no on-site newsletter capture</b>, and <b>zero paid distribution</b>. The content engine exists; the distribution engine was never plugged in. That's the single biggest growth lever on this list — and it's mostly free.</p></div></div>
+ <div class="chartwrap" style="margin-top:34px"><div class="ct">LinkedIn growth potential — activate + post daily (projection)</div><canvas id="socialChart"></canvas></div>
+ <div class="cards2" style="margin-top:34px">
+ <div class="c"><h3>LinkedIn — 818 followers</h3><p>Your #1 channel, dormant. <b>Fix:</b> link it site-wide, post daily deal coverage, have staff reshare, run thought-leadership ads. This is where the CRE audience & the sponsor money are.</p></div>
+ <div class="c"><h3>Facebook</h3><p class="flag">Exists, dormant. <b>Fix:</b> auto-syndicate headlines; low priority vs. LinkedIn but keep it current for older readers & reviews.</p></div>
+ <div class="c"><h3>X / Twitter — none</h3><p>CRE Twitter breaks & trades deal news. <b>Fix:</b> stand up @RENTV, auto-post headlines, engage brokers. Cheap, real in-industry reach.</p></div>
+ <div class="c"><h3>YouTube / Instagram — the CRE Talk goldmine</h3><p>You produce <b>CRE Talk</b> (19+ CEO episodes) and own <b>The REview</b> video platform — but neither feeds YouTube Shorts or IG Reels. Cut every episode into 30-second clips and the next generation of CRE pros finds you weekly.</p></div>
+ </div>
+</div></section>
+
+<section id="suggested"><div class="eyebrow">09 · Suggested New Websites</div><h2 class="disp">Six directions — each after a CRE-news leader.</h2><div class="accbody">
+ <p class="lead2">Modern, editorial, mobile-first — modeled on the outlets winning CRE news today. Each opens as a working front-page concept with real layout & motion. Pick a direction; we build it out.</p>
+ <div class="vgrid">
+ <a class="vtile rv" href="/versions/thewire.html" target="_blank" style="--vb:#111114;--vi:#f5f5f7;--vg:#e0243b">
+ <div class="vswatch"><span style="background:#111114"></span><span style="background:#1a1a1f"></span><span style="background:#e0243b"></span><span style="background:#f5f5f7"></span></div>
+ <div class="vname">The Wire</div><div class="vmodel">after The Real Deal</div>
+ <div class="vmood">Real-Deal energy — red-on-black newsroom, aggressive headlines.</div><div class="vopen">Open live concept →</div>
+ </a>
+ <a class="vtile rv" href="/versions/dispatch.html" target="_blank" style="--vb:#0d1b2a;--vi:#eef3f8;--vg:#3fb0ff">
+ <div class="vswatch"><span style="background:#0d1b2a"></span><span style="background:#14273d"></span><span style="background:#3fb0ff"></span><span style="background:#eef3f8"></span></div>
+ <div class="vname">Dispatch</div><div class="vmodel">after Bisnow</div>
+ <div class="vmood">Bisnow-style — newsletter-forward, clean, capture-first.</div><div class="vopen">Open live concept →</div>
+ </a>
+ <a class="vtile rv" href="/versions/observer.html" target="_blank" style="--vb:#14110c;--vi:#f6efe2;--vg:#c9a24b">
+ <div class="vswatch"><span style="background:#14110c"></span><span style="background:#1e1a12"></span><span style="background:#c9a24b"></span><span style="background:#f6efe2"></span></div>
+ <div class="vname">The Observer</div><div class="vmodel">after Commercial Observer</div>
+ <div class="vmood">Commercial-Observer editorial — serif authority, gold on ink.</div><div class="vopen">Open live concept →</div>
+ </a>
+ <a class="vtile rv" href="/versions/terminal.html" target="_blank" style="--vb:#0a0e12;--vi:#e9eef3;--vg:#37d39a">
+ <div class="vswatch"><span style="background:#0a0e12"></span><span style="background:#111820"></span><span style="background:#37d39a"></span><span style="background:#e9eef3"></span></div>
+ <div class="vname">Terminal</div><div class="vmodel">after CoStar / GlobeSt</div>
+ <div class="vmood">CoStar/GlobeSt utility — dark data terminal, live tickers & charts.</div><div class="vopen">Open live concept →</div>
+ </a>
+ <a class="vtile rv" href="/versions/broadsheet.html" target="_blank" style="--vb:#fbf3e9;--vi:#2a211a;--vg:#a8442b">
+ <div class="vswatch"><span style="background:#fbf3e9"></span><span style="background:#f4e7d5"></span><span style="background:#a8442b"></span><span style="background:#2a211a"></span></div>
+ <div class="vname">Broadsheet</div><div class="vmodel">after The Financial Times</div>
+ <div class="vmood">FT-style broadsheet — cream stock, classic authority, print heritage.</div><div class="vopen">Open live concept →</div>
+ </a>
+ <a class="vtile rv" href="/versions/modern.html" target="_blank" style="--vb:#0e0f11;--vi:#f2f3f5;--vg:#8b5cf6">
+ <div class="vswatch"><span style="background:#0e0f11"></span><span style="background:#17181b"></span><span style="background:#8b5cf6"></span><span style="background:#f2f3f5"></span></div>
+ <div class="vname">Modern</div><div class="vmodel">after Axios</div>
+ <div class="vmood">Axios-style modern — minimal, scannable, mobile-native.</div><div class="vopen">Open live concept →</div>
+ </a></div>
+</div></section>
+
+<section id="partners"><div class="eyebrow">10 · Unlikely Partners</div><h2 class="disp">Ten referral engines hiding in the industry.</h2><div class="accbody">
+ <p class="lead2">Your readers & sponsors already sit inside these organizations. Content + data + event swaps turn them into a low-cost pipeline of audience and revenue.</p>
+ <div class="pcard rv"><div class="pnum">01</div><div><h4>Major brokerages (CBRE, JLL, Cushman, Colliers)</h4><p>They generate the deals you cover — formalize data-sharing + co-published market reports. Their PR teams feed you scoops; you give their deals reach.</p></div></div><div class="pcard rv"><div class="pnum">02</div><div><h4>CRE associations (NAIOP, ULI, ICSC, BOMA)</h4><p>Co-branded content + event cross-promotion. Become the official news partner of the chapters across your Western-U.S. footprint.</p></div></div><div class="pcard rv"><div class="pnum">03</div><div><h4>CRE data providers (CoStar, Reonomy, CompStak)</h4><p>License data feeds to power live charts & deal pages — turns your news into a data product competitors can't match.</p></div></div><div class="pcard rv"><div class="pnum">04</div><div><h4>CRE law & finance firms</h4><p>Sponsor deal-analysis columns ("Financing this week, presented by ___"). Recurring sponsorship + expert bylines that build authority.</p></div></div><div class="pcard rv"><div class="pnum">05</div><div><h4>Conference & trade-show operators</h4><p>Co-host or media-partner other CRE events — extend the RENTV Conferences franchise without producing every show yourself.</p></div></div><div class="pcard rv"><div class="pnum">06</div><div><h4>PropTech startups</h4><p>Product-spotlight series + demo days. They pay for exposure to your broker/investor audience; you get fresh, forward-looking content.</p></div></div><div class="pcard rv"><div class="pnum">07</div><div><h4>Universities (USC Lusk, UCLA Ziman)</h4><p>Research partnerships + academic authority. Co-publish market studies; recruit the next generation of CRE readers early.</p></div></div><div class="pcard rv"><div class="pnum">08</div><div><h4>City economic-development orgs</h4><p>Market data + "why invest here" content for every metro you cover — free, credible, SEO-rich local coverage.</p></div></div><div class="pcard rv"><div class="pnum">09</div><div><h4>CRE lenders & banks</h4><p>Sponsor the "Financing" vertical + rate/market commentary. Lenders want mindshare with borrowers — you have exactly that audience.</p></div></div><div class="pcard rv"><div class="pnum">10</div><div><h4>Listing platforms (LoopNet, Crexi, Ten-X)</h4><p>Deal-flow content partnerships — surface notable listings as news; they drive traffic, you drive their listings.</p></div></div>
+</div></section>
+
+<section id="revenue"><div class="eyebrow">11 · Unlikely Revenue Streams</div><h2 class="disp">The audience is the asset. It can earn eight ways.</h2><div class="accbody">
+ <p class="lead2">News is the draw — but the brand, the list, and the events can each throw off high-margin recurring revenue. Bisnow proved the model; you have the regional authority to run it.</p>
+ <div class="chartwrap tall"><div class="ct">Target revenue mix</div><canvas id="revChart"></canvas></div>
+ <div class="cards2" style="margin-top:34px">
+ <div class="c"><h3>① Premium subscriptions</h3><p>A paid tier for deep deal data, archives & early access — recurring revenue off your most engaged readers.</p></div>
+ <div class="c"><h3>② RENTV Conferences (expand)</h3><p>Your events franchise is the crown jewel — add markets, sponsorships & ticket tiers. Content feeds events; events feed the list.</p></div>
+ <div class="c"><h3>③ Sponsored content</h3><p>Brokerages & lenders pay to co-publish deal analysis — revenue that also reshares into new reach.</p></div>
+ <div class="c"><h3>④ Newsletter sponsorships</h3><p>The Bisnow engine: a daily newsletter with a sponsor slot. Own the inbox, sell the placement.</p></div>
+ <div class="c"><h3>⑤ Data & market reports</h3><p>Package quarterly vacancy/cap-rate/deal-volume data into paid reports — high-margin, link-earning, lead-generating.</p></div>
+ <div class="c"><h3>⑥ CRE job board</h3><p>A careers board for brokers, analysts & developers — recurring listings revenue on an audience employers want.</p></div>
+ <div class="c"><h3>⑦ Webinars & virtual events</h3><p>Sponsored market-outlook webinars between the big conferences — low cost, high sponsor value.</p></div>
+ <div class="c"><h3>⑧ Video sponsorships</h3><p>Your video programming, sponsored: "Deal of the Week, presented by ___." Monetize content you already make.</p></div>
+ </div>
+
+ <div style="margin-top:56px"></div>
+ <div class="eyebrow">Sell PLANS to brokers, agents & broker-dealers — recurring cash flow</div>
+ <h2 class="disp">Turn the audience into subscribers with a rate card.</h2>
+ <p class="lead2">The Hanley Archive proves it: brokerages already pay RENTV to reach this audience. Productize that into published tiers — monthly cash flow instead of one-off sponsorships. Every deal you cover is a sales lead for a plan.</p>
+ <table><thead><tr><th>Plan</th><th>Who</th><th>Monthly</th><th>What they get</th></tr></thead><tbody>
+ <tr><td><b>Agent</b></td><td>Individual agents</td><td>$99</td><td>Profile page · their closings published as deal briefs · newsletter mention · directory listing</td></tr>
+ <tr><td><b>Broker Pro</b></td><td>Top producers</td><td>$299</td><td>Everything in Agent + priority deal coverage · quarterly CRE Talk clip feature · Property Spotlight slot</td></tr>
+ <tr><td><b>Brokerage</b></td><td>Firms / teams</td><td>$999</td><td>Firm-wide profiles · all closings covered · co-branded quarterly market report · conference discounts</td></tr>
+ <tr><td><b>Capital Partner</b></td><td>Lenders / broker-dealers / title</td><td>$2,500</td><td>Vertical sponsorship ("Financing presented by ___") · CRE Talk episode sponsor · conference panel seat · lead flow</td></tr>
+ </tbody></table>
+ <p class="lead2" style="font-size:15px;opacity:.75">100 Agent + 30 Broker Pro + 10 Brokerage + 6 Capital Partner ≈ <b>$44K/mo (~$530K/yr) recurring</b> — before conferences or the sublease.com broker CRM line ($900K in the plan).</p>
+
+ <div style="margin-top:48px"></div>
+ <div class="eyebrow">The Zoom economy — unlikely, high-margin, zero build</div>
+ <h2 class="disp">Paid calls: the fastest cash flow you can turn on.</h2>
+ <div class="sugg">
+ <div class="s"><div class="p">Monthly · $49/seat</div><h4>State of the Market LIVE</h4><p>A monthly Zoom market briefing — the conference franchise, miniaturized. Sponsor slot + paid seats; free for plan subscribers (drives upgrades).</p></div>
+ <div class="s"><div class="p">Members · $99/seat</div><h4>CEO Office Hours</h4><p>A CRE Talk alum (Shopoff, Rising, Sigal…) takes questions for 60 minutes, members-only. The guest list already exists — it's your show's rolodex.</p></div>
+ <div class="s"><div class="p">Quarterly · $199</div><h4>Broker Masterminds</h4><p>Curated 8-seat Zoom roundtables by asset class (industrial / multifamily / retail). Scarcity pricing; brokers pay to be in the room.</p></div>
+ <div class="s"><div class="p">Marketplace · 20% take</div><h4>Book-a-Dealmaker</h4><p>30-minute 1:1 calls with top brokers & lenders, booked through RENTV. They set the rate; RENTV takes 20% and owns the relationship.</p></div>
+ <div class="s"><div class="p">Per event · listing fee</div><h4>Deal War-Rooms</h4><p>Brokers pitch live listings to an investor audience on Zoom — listing fee + paid seats. Deal flow becomes an event.</p></div>
+ <div class="s"><div class="p">Sponsored · CE-style</div><h4>Paid Webinar Series</h4><p>"Financing in 2027," "1031 in practice" — lender-sponsored educational Zooms with paid certificates of attendance.</p></div>
+ </div>
+</div></section>
+
+<section id="videos"><div class="eyebrow">12 · Video + Brand Films</div><h2 class="disp">You make video — package it to sell.</h2><div class="accbody">
+ <p class="lead2">RENTV already produces <b>CRE Talk with Steve & Arnie</b> (Shopoff, Rising Realty, Mosaic, NewMark Merrill and 15+ more CEO interviews) and hosts it on <b>The REview</b> — plus a full conference program each year. That's a sponsored content library most CRE outlets would kill for. Standardize it into short, mobile-first formats and add a presenter-led brand film.</p>
+ <div style="margin-top:10px"></div>
+ <div class="eyebrow">The upgrade — a presenter over YOUR footage</div>
+ <h2 class="disp">A host, over real CRE footage. Motion, music, one action item.</h2>
+ <p class="lead2">The format to standardize: RENTV's own market/conference footage as the backdrop, a polished presenter delivering one message, fade in/out, a music bed, one clear CTA. Drop-in for LinkedIn, ads & the site hero.</p>
+ <div class="heybox">
+ <div class="film"><video id="heyfilm" autoplay muted loop playsinline></video><div class="ph" id="heyph">● RENTV brand film<br>presenter over your footage<br>— renders here when ready</div></div>
+ <div class="copy"><h3 class="disp">"The deal desk of the West."</h3><p>Presenter script: <i>"Since 1998, RENTV has covered the deals that move Western commercial real estate. Home of CRE Talk with Steve & Arnie, the State of the Market conferences, and The REview. Your action item: get it first — subscribe to the RENTV Daily."</i></p><div class="spec"><span>◐ Fade in / out</span><span>♪ Music bed</span><span>▶ Action item</span><span>🏙 Your footage</span><span>15–30s · LinkedIn</span></div></div>
+ </div>
+ <div style="margin-top:60px"></div>
+ <div class="eyebrow">New video formats to make</div>
+ <h2 class="disp">Eight shorts that own CRE attention.</h2>
+ <div class="sugg">
+ <div class="s"><div class="p">Daily · Deals</div><h4>Deal of the Week</h4><p>60-second breakdown of the biggest trade — who, how much, why it matters.</p></div>
+ <div class="s"><div class="p">Data · Weekly</div><h4>Market Minute</h4><p>One chart, one takeaway — cap rates, vacancy, absorption. Repurpose the report data.</p></div>
+ <div class="s"><div class="p">Events</div><h4>Conference Recap</h4><p>Cut every RENTV Conferences panel into shareable clips — content + promo in one.</p></div>
+ <div class="s"><div class="p">People</div><h4>Broker / Investor Spotlight</h4><p>Two-minute interview series — access is your moat; put faces on the deals.</p></div>
+ <div class="s"><div class="p">Explainer</div><h4>Why This Deal Matters</h4><p>Context video for the non-expert — grows the audience beyond insiders.</p></div>
+ <div class="s"><div class="p">Quarterly</div><h4>The Outlook</h4><p>A polished quarterly market outlook — a sponsor-ready flagship video.</p></div>
+ <div class="s"><div class="p">Development</div><h4>Groundbreaking Tour</h4><p>Walk the projects reshaping each metro — visual, shareable, developer-friendly.</p></div>
+ <div class="s"><div class="p">Annual</div><h4>Year in Review</h4><p>Top deals, people moves & predictions — the December franchise video.</p></div>
+ </div>
+ <div style="margin-top:60px"></div>
+ <div class="eyebrow">Five brand films — one per recommendation</div>
+ <h2 class="disp">Presenter shorts, each ending on an action item.</h2>
+ <p class="lead2">Each renders here as it finishes (refresh to pick up new ones).</p>
+ <div class="films5">
+ <div class="f5" data-key="reputation"><div class="fv"><video muted loop playsinline></video><div class="fph">● rendering</div></div><div class="ft">Authority</div><div class="fa">▶ Modernize + own the region</div></div>
+ <div class="f5" data-key="advertising"><div class="fv"><video muted loop playsinline></video><div class="fph">● rendering</div></div><div class="ft">Grow</div><div class="fa">▶ Newsletter + LinkedIn</div></div>
+ <div class="f5" data-key="competitors"><div class="fv"><video muted loop playsinline></video><div class="fph">● rendering</div></div><div class="ft">Compete</div><div class="fa">▶ Out-local the nationals</div></div>
+ <div class="f5" data-key="timing"><div class="fv"><video muted loop playsinline></video><div class="fph">● rendering</div></div><div class="ft">Timing</div><div class="fa">▶ Own the deal calendar</div></div>
+ <div class="f5" data-key="social"><div class="fv"><video muted loop playsinline></video><div class="fph">● rendering</div></div><div class="ft">Social</div><div class="fa">▶ Activate LinkedIn today</div></div>
+ </div>
+</div></section>
+
+<section id="dealdesk"><div class="eyebrow">13 · Live Deal Desk — demo with REAL data</div><h2 class="disp">What your modernized site can do: live, scored deal data.</h2><div class="accbody">
+ <p class="lead2">This is not a mockup — it's a working demo running on <b>real Los Angeles CRE listings</b> (multifamily, retail, mixed-use) with real cap rates and a transparent 0–100 deal score. A modernized RENTV pairs your deal <i>reporting</i> with live deal <i>data</i> like this — the product wedge none of the news competitors ship regionally.</p>
+ <div id="dealmap"></div>
+ <p class="lead2" style="font-size:15px;margin-top:14px;opacity:.7">Clustered map of live LA-area commercial listings · zoom to explode clusters · click a marker for price & type.</p>
+ <div class="chartwrap"><div class="ct">Real Census market data — median rent vs. income, LA-area cities</div><canvas id="demoChart"></canvas></div>
+ <div class="dealgrid">
+ <div class="deal rv">
+ <div class="dscore" data-score="77">77</div>
+ <div class="dbody"><div class="daddr">304 E 95th St</div><div class="dcity">Los Angeles · Multifamily · 6 units</div>
+ <div class="dnums"><span>$1.40M</span><span>10.4% cap</span><span>1.79 DSCR</span><span>16.7% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="77">77</div>
+ <div class="dbody"><div class="daddr">214 S Maryland Pkwy</div><div class="dcity">Las Vegas · Multifamily · 10 units</div>
+ <div class="dnums"><span>$1.45M</span><span>9.5% cap</span><span>1.63 DSCR</span><span>13.4% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="76">76</div>
+ <div class="dbody"><div class="daddr">5335 Harmony Avenue</div><div class="dcity">North Hollywood · Multifamily · 8 units</div>
+ <div class="dnums"><span>$1.20M</span><span>8.8% cap</span><span>1.51 DSCR</span><span>10.9% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="76">76</div>
+ <div class="dbody"><div class="daddr">553 Stanford Ave</div><div class="dcity">Los Angeles · Multifamily · 17 units</div>
+ <div class="dnums"><span>$1.38M</span><span>9.7% cap</span><span>1.67 DSCR</span><span>14.2% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="76">76</div>
+ <div class="dbody"><div class="daddr">3650 S Eastern Ave #200</div><div class="dcity">Las Vegas · Office · 1 units</div>
+ <div class="dnums"><span>$0.90M</span><span>10.0% cap</span><span>1.71 DSCR</span><span>15.1% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="75">75</div>
+ <div class="dbody"><div class="daddr">3804 Mt Pleasant St</div><div class="dcity">Houston · Multifamily · 15 units</div>
+ <div class="dnums"><span>$1.15M</span><span>8.6% cap</span><span>1.47 DSCR</span><span>10.1% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="75">75</div>
+ <div class="dbody"><div class="daddr">3815 Fannin St</div><div class="dcity">Houston · Multifamily · 15 units</div>
+ <div class="dnums"><span>$0.90M</span><span>8.9% cap</span><span>1.52 DSCR</span><span>11.1% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="75">75</div>
+ <div class="dbody"><div class="daddr">2915 Pearl Avenue</div><div class="dcity">Fort Worth · Multifamily · 1 units</div>
+ <div class="dnums"><span>$0.88M</span><span>9.1% cap</span><span>1.56 DSCR</span><span>11.9% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="75">75</div>
+ <div class="dbody"><div class="daddr">4430 Herring Dr</div><div class="dcity">Fort Worth · Multifamily · 1 units</div>
+ <div class="dnums"><span>$1.44M</span><span>9.2% cap</span><span>1.58 DSCR</span><span>12.2% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="74">74</div>
+ <div class="dbody"><div class="daddr">9415 S R L Thornton Fwy</div><div class="dcity">Dallas · Retail · 1 units</div>
+ <div class="dnums"><span>$0.90M</span><span>8.5% cap</span><span>1.45 DSCR</span><span>9.5% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="74">74</div>
+ <div class="dbody"><div class="daddr">12033 Misty Valley Dr</div><div class="dcity">Houston · Retail · 1 units</div>
+ <div class="dnums"><span>$1.00M</span><span>8.7% cap</span><span>1.49 DSCR</span><span>10.4% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="68">68</div>
+ <div class="dbody"><div class="daddr">11902 Perrin Beitel Rd</div><div class="dcity">San Antonio · Retail · 1 units</div>
+ <div class="dnums"><span>$1.05M</span><span>8.0% cap</span><span>1.37 DSCR</span><span>7.9% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="67">67</div>
+ <div class="dbody"><div class="daddr">3013 W 11th St</div><div class="dcity">Los Angeles · Multifamily · 9 units</div>
+ <div class="dnums"><span>$1.20M</span><span>7.7% cap</span><span>1.31 DSCR</span><span>6.6% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="66">66</div>
+ <div class="dbody"><div class="daddr">6236 Arroyo Glen St</div><div class="dcity">Los Angeles · Multifamily · 4 units</div>
+ <div class="dnums"><span>$0.99M</span><span>7.6% cap</span><span>1.30 DSCR</span><span>6.4% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="66">66</div>
+ <div class="dbody"><div class="daddr">1330 NW 54th St</div><div class="dcity">Miami · Office · 1 units</div>
+ <div class="dnums"><span>$0.80M</span><span>7.6% cap</span><span>1.30 DSCR</span><span>6.3% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="64">64</div>
+ <div class="dbody"><div class="daddr">901 N El Centro Avenue</div><div class="dcity">Hollywood · Multifamily · 5 units</div>
+ <div class="dnums"><span>$0.95M</span><span>7.7% cap</span><span>1.32 DSCR</span><span>6.7% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="62">62</div>
+ <div class="dbody"><div class="daddr">333 N Union Ave</div><div class="dcity">Los Angeles · Multifamily · 1 units</div>
+ <div class="dnums"><span>$1.20M</span><span>7.5% cap</span><span>1.28 DSCR</span><span>6.0% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="62">62</div>
+ <div class="dbody"><div class="daddr">1216 E 56th</div><div class="dcity">Long Beach · Multifamily · 4 units</div>
+ <div class="dnums"><span>$1.20M</span><span>7.5% cap</span><span>1.28 DSCR</span><span>6.0% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="62">62</div>
+ <div class="dbody"><div class="daddr">120 S 79th St</div><div class="dcity">Houston · Multifamily · 10 units</div>
+ <div class="dnums"><span>$0.75M</span><span>7.4% cap</span><span>1.27 DSCR</span><span>5.8% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="61">61</div>
+ <div class="dbody"><div class="daddr">8717 Humble Westfield Building D</div><div class="dcity">Houston · Office · 1 units</div>
+ <div class="dnums"><span>$1.09M</span><span>7.3% cap</span><span>1.25 DSCR</span><span>5.4% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="60">60</div>
+ <div class="dbody"><div class="daddr">4928 Lennox Blvd</div><div class="dcity">Inglewood · Multifamily · 4 units</div>
+ <div class="dnums"><span>$1.10M</span><span>7.3% cap</span><span>1.25 DSCR</span><span>5.3% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="60">60</div>
+ <div class="dbody"><div class="daddr">4454 Purdy Ln.</div><div class="dcity">West Palm Beach · Retail · 1 units</div>
+ <div class="dnums"><span>$8.50M</span><span>26.0% cap</span><span>4.45 DSCR</span><span>73.3% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="59">59</div>
+ <div class="dbody"><div class="daddr">13981 Southwest 232nd Street</div><div class="dcity">Miami · Retail · 1 units</div>
+ <div class="dnums"><span>$2.35M</span><span>16.0% cap</span><span>2.73 DSCR</span><span>36.8% CoC</span></div></div>
+ </div>
+ <div class="deal rv">
+ <div class="dscore" data-score="57">57</div>
+ <div class="dbody"><div class="daddr">10370-1/2 Ashton Avenue</div><div class="dcity">Los Angeles · Multifamily · 4 units</div>
+ <div class="dnums"><span>$2.10M</span><span>11.8% cap</span><span>2.03 DSCR</span><span>21.8% CoC</span></div></div>
+ </div></div>
+ <p class="lead2" style="font-size:15px;margin-top:16px;opacity:.7">Scores computed by a transparent weighted model (rent yield, cap rate, DSCR, financing signals) — every badge explains itself. Data: live LA CRE listing sample, July 2026.</p>
+</div></section>
+
+<section id="roadmap"><div class="eyebrow">14 · 90-Day Roadmap</div><h2 class="disp">What to do, in order.</h2><div class="accbody">
+ <ul class="road">
+ <li><div class="w">Weeks 1–2</div><div class="d">Cross-link the empire: rentv.com ↔ The REview ↔ conferences, everywhere. Link LinkedIn + Facebook site-wide and begin daily posting. Put a newsletter capture on every page (the Constant Contact lists already exist). Pick a website direction from the six concepts.</div></li>
+ <li><div class="w">Weeks 3–6</div><div class="d">Ship the new mobile-first front page. Launch the free "RENTV Daily" newsletter. Start Google Ads on regional CRE-news keywords. Sign the first sponsored-content partner.</div></li>
+ <li><div class="w">Weeks 6–10</div><div class="d">Open conference-sponsor + newsletter-sponsor packages. Sign 3 "unlikely partner" swaps (a brokerage, an association, a lender). Publish the first quarterly data report.</div></li>
+ <li><div class="w">Weeks 10–13</div><div class="d">Launch the video formats (Deal of the Week, Market Minute) + the presenter brand film. Measure cost-per-subscriber & sponsor pipeline; double down on the winner.</div></li>
+ </ul>
+</div></section>
+
+<section id="sources"><div class="eyebrow">15 · Sources & Methodology</div><h2 class="disp">Where every number comes from — and how.</h2><div class="accbody">
+ <p class="lead2">A brief is only as trustworthy as its sourcing, so here is the whole provenance map. Each row states <b>where the data came from</b> and <b>how it was produced</b> — and anything that is an estimate is labelled an estimate, never dressed up as measured analytics.</p>
+ <div class="srclegend"><span class="srcb v">Client-verified</span><span class="srcb o">Observed live</span><span class="srcb r">Real dataset</span><span class="srcb d">Computed</span><span class="srcb a">Analyst view</span><span class="srcb e">Illustrative</span><span class="srcb a">Decorative</span><span class="srcb d">Generated</span></div>
+ <table><thead><tr><th>Section of the brief</th><th>Where it comes from</th><th>How it’s produced</th><th>Provenance</th></tr></thead><tbody><tr><td><b>Portfolio, owner, team & contact</b></td><td>The client's own materials — the package supplied by Daniel Ginzburg (CFO, Dandeana Corp), Jul 2026</td><td>Transcribed into <code>data/client.json</code> and rendered verbatim</td><td class="srccell"><span class="srcb v">Client-verified</span></td></tr><tr><td><b>Sublease.com market size & revenue</b></td><td>"Sublease.com Business Plan & Market Strategy," April 2026 (confidential, client-supplied)</td><td>Figures ($650B market, ~$5.9M Year-2 mix) lifted straight from the plan and charted</td><td class="srccell"><span class="srcb v">Client-verified</span></td></tr><tr><td><b>Website & page-by-page audit</b></td><td>Live rentv.com — every page loaded and screenshotted directly</td><td>Adversarial teardown; a grade + fixes authored per page. The dead analytics tag (UA-148799896-1) was read from the live page source</td><td class="srccell"><span class="srcb o">Observed live</span></td></tr><tr><td><b>Live Deal Desk — listings & map</b></td><td>Real LA-area CRE listings sampled from CRCP into <code>data/deals-sample.json</code></td><td>Deal scores computed by a transparent weighted model (<code>deal-score.js</code>): rent yield, cap rate, DSCR, financing signals</td><td class="srccell"><span class="srcb r">Real dataset</span></td></tr><tr><td><b>Deal Desk — demographics chart</b></td><td>U.S. Census median gross rent & income, LA-area cities</td><td>Carried in the <code>demographics[]</code> block of deals-sample.json and charted live</td><td class="srccell"><span class="srcb r">Real dataset</span></td></tr><tr><td><b>SWOT snapshot</b></td><td>Derived from the reputation, revenue & competitive reads elsewhere in this brief</td><td>A small agent (<code>swotAgent()</code>) runs four lenses over one profile so the quadrant stays consistent with the rest</td><td class="srccell"><span class="srcb d">Computed</span></td></tr><tr><td><b>Competitors · channels · timing · partners · revenue</b></td><td>Analyst strategy, benchmarked to the Bisnow / CoStar / Real Deal / Commercial Observer playbooks</td><td>Authored as consulting recommendations — industry-standard models, not scraped metrics</td><td class="srccell"><span class="srcb a">Analyst view</span></td></tr><tr><td><b>News + conference rivals (LA → West)</b></td><td>Public web research on each outlet’s news + event programming (Bisnow, Connect CRE, France Media / Western RE Business, CREJ, AZRE, LABJ, The Registry, GlobeSt, NAIOP)</td><td>Compiled Jul 2026; each outlet’s conference franchise verified from its own events pages</td><td class="srccell"><span class="srcb o">Observed live</span></td></tr><tr><td><b>Charts (reach, ROI, readership, growth)</b></td><td>Illustrative CRE-shaped figures around one real anchor — the observed LinkedIn count (818)</td><td>Estimates for shape & direction only. The live site ran no analytics for ~3 years, so measured traffic does not exist to chart</td><td class="srccell"><span class="srcb e">Illustrative</span></td></tr><tr><td><b>Hero & section imagery</b></td><td>Unsplash editorial stock (desaturated skyline / CRE photography)</td><td>Hotlinked, decorative only — carries no data meaning</td><td class="srccell"><span class="srcb a">Decorative</span></td></tr><tr><td><b>Brand films</b></td><td>Generated presenter videos (HeyGen) over CRE footage</td><td>Rendered to <code>/portal/videos/heygen-*.mp4</code>; appear on the page as they finish</td><td class="srccell"><span class="srcb d">Generated</span></td></tr></tbody></table>
+ <div class="srcnote"><div class="i">⚙</div><div><h4>How this document is built</h4><p>This portal is generated, not hand-typed. Client-supplied facts live in <code>data/*.json</code>; a build step (<code>build.mjs</code>) renders them into the page and redraws the charts & map on every deploy — so the brief updates when the underlying data does. Nothing is invented at page-load: the charts read fixed datasets, the Deal Desk reads a real listing sample, and projections appear only where the row above is labelled <b>Illustrative</b>.</p></div></div>
+</div></section>
+
+<footer><span>RENTV.com · Growth Strategy Brief</span><span>Prepared for the 2026 consulting review</span><span>Confidential — for discussion</span></footer>
+
+<script>
+(function(){var s=document.querySelectorAll('.hero .slide'),i=0;if(s.length<2)return;setInterval(function(){s[i].classList.remove('on');i=(i+1)%s.length;s[i].classList.add('on');},5200);})();
+var REDUCE=window.matchMedia&&window.matchMedia('(prefers-reduced-motion:reduce)').matches;
+function countUp(el){var t=+el.dataset.c;if(!t)return;if(REDUCE){el.textContent=t.toLocaleString();return;}var d=1300,st=null;function step(ts){st=st||ts;var p=Math.min((ts-st)/d,1),e=1-Math.pow(1-p,3);el.textContent=Math.round(t*e).toLocaleString();if(p<1)requestAnimationFrame(step);}requestAnimationFrame(step);}
+var secs=[].slice.call(document.querySelectorAll('section[id]'));var accs=[];
+secs.forEach(function(sec){var eb=sec.querySelector(':scope>.eyebrow'),h2=sec.querySelector(':scope>h2');if(!eb&&!h2)return;accs.push(sec);
+ var head=document.createElement('button');head.type='button';head.className='acchead';var txt=document.createElement('div');txt.className='htext';if(eb)txt.appendChild(eb);if(h2)txt.appendChild(h2);
+ var chev=document.createElement('span');chev.className='chev';chev.textContent='+';head.appendChild(txt);head.appendChild(chev);sec.insertBefore(head,sec.querySelector('.accbody'));
+ head.addEventListener('click',function(){var open=sec.classList.toggle('open');chev.textContent=open?'\u2013':'+';if(open){initCharts();initDesk();drawRoad(sec);}});});
+function drawRoad(sec){var rd=sec.querySelector('.road');if(rd)setTimeout(function(){rd.classList.add('drawn');},140);}
+function setAll(o){accs.forEach(function(s){s.classList.toggle('open',o);var c=s.querySelector('.chev');if(c)c.textContent=o?'\u2013':'+';if(o)drawRoad(s);});if(o){initCharts();initDesk();}}
+document.getElementById('xall').onclick=function(){setAll(true);};document.getElementById('call').onclick=function(){setAll(false);};
+document.querySelectorAll('.menu a[href^="#"]').forEach(function(a){a.addEventListener('click',function(){var id=a.getAttribute('href').slice(1),s=document.getElementById(id);if(s&&!s.classList.contains('open')&&s.querySelector('.acchead')){s.querySelector('.acchead').click();}if(s)setTimeout(function(){s.scrollIntoView({behavior:'smooth'});},80);});});
+// Keep the burger's aria-expanded in sync with body.nav no matter which path (burger, scrim, or a menu link) toggles it.
+(function(){var burger=document.querySelector('.burger');if(!burger)return;function sync(){burger.setAttribute('aria-expanded',document.body.classList.contains('nav'));}new MutationObserver(sync).observe(document.body,{attributes:true,attributeFilter:['class']});sync();})();
+var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.12});
+document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
+document.querySelectorAll('.metrics .v[data-c]').forEach(function(el){var mo=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){countUp(el);mo.unobserve(el);}});});mo.observe(el);});
+var charted=false;
+function initCharts(){if(charted||!window.Chart)return;charted=true;
+ var gold='#c8a15a',dim='rgba(238,241,246,.6)',line='rgba(200,161,90,.18)';Chart.defaults.color=dim;Chart.defaults.font.family='Jost,system-ui,sans-serif';
+ var RM=window.matchMedia&&window.matchMedia('(prefers-reduced-motion:reduce)').matches;
+ Chart.defaults.animation=RM?false:{duration:1150,easing:'easeInOutQuart'};
+ if(!RM){Chart.defaults.animations={y:{from:function(c){return c.chart.scales.y?c.chart.scales.y.getPixelForValue(0):0;}}};}
+ var C={"competitors":{"labels":["RENTV","Bisnow","The Real Deal","Comm. Observer","GlobeSt","CoStar News"],"reach":[35,95,90,70,80,88],"digital":[28,96,92,75,70,90]},"advertising":{"labels":["LinkedIn","Newsletter","Google Search","Sponsored Content","Events","X / Social","Programmatic","SEO"],"roi":[95,98,82,78,88,60,55,84]},"timing":{"labels":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"deals":[60,68,82,88,80,72,58,55,86,92,84,78],"readership":[70,72,88,90,78,64,52,50,84,95,88,80]},"social":{"labels":["Now","+30d","+60d","+90d","+180d","+1yr"],"linkedin":[818,1400,2600,4200,7500,15000]},"revenue":{"labels":["Subscriptions","Conferences","Sponsored Content","Newsletter Ads","Data & Reports","Job Board","Webinars","Video Sponsorships"],"mix":[26,24,18,12,8,5,4,3]}};
+ new Chart(document.getElementById('compChart'),{type:'bar',data:{labels:C.competitors.labels,datasets:[{label:'Audience reach',data:C.competitors.reach,backgroundColor:'rgba(200,161,90,.75)'},{label:'Digital modernization',data:C.competitors.digital,backgroundColor:'rgba(120,170,255,.6)'}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{grid:{color:line},max:100},x:{grid:{display:false}}},plugins:{legend:{labels:{color:dim}}}}});
+ new Chart(document.getElementById('adChart'),{type:'bar',data:{labels:C.advertising.labels,datasets:[{label:'Relative ROI',data:C.advertising.roi,backgroundColor:C.advertising.roi.map(function(v){return v>=88?'rgba(200,161,90,.85)':'rgba(200,161,90,.4)';})}]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,scales:{x:{grid:{color:line},max:100},y:{grid:{display:false}}},plugins:{legend:{display:false}}}});
+ new Chart(document.getElementById('timeChart'),{type:'line',data:{labels:C.timing.labels,datasets:[{label:'Deal activity',data:C.timing.deals,borderColor:gold,backgroundColor:'rgba(200,161,90,.12)',fill:true,tension:.4},{label:'Readership',data:C.timing.readership,borderColor:'#78aaff',backgroundColor:'rgba(120,170,255,.08)',fill:true,tension:.4}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{grid:{color:line}},x:{grid:{display:false}}}}});
+ new Chart(document.getElementById('socialChart'),{type:'line',data:{labels:C.social.labels,datasets:[{label:'LinkedIn followers (projected)',data:C.social.linkedin,borderColor:gold,backgroundColor:'rgba(200,161,90,.14)',fill:true,tension:.4,pointRadius:4}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{grid:{color:line}},x:{grid:{display:false}}},plugins:{legend:{display:false}}}});
+ new Chart(document.getElementById('revChart'),{type:'doughnut',data:{labels:C.revenue.labels,datasets:[{data:C.revenue.mix,backgroundColor:['#c8a15a','#b78d3f','#a67c32','#d9b877','#8f6d2c','#e3c98d','#7c5f26','#efd9a3'],borderColor:'#0c1016',borderWidth:2}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{color:dim,boxWidth:12}}},cutout:'58%'}});
+ if(document.getElementById('subChart')){new Chart(document.getElementById('subChart'),{type:'doughnut',data:{labels:['Premium listings (comm.)','Transaction fee (resi.)','Broker CRM & leads','Tenant screening','AI legal docs'],datasets:[{data:[1.8,2.2,0.9,0.6,0.4],backgroundColor:['#c8a15a','#78aaff','#37d39a','#d9b877','#8f6d2c'],borderColor:'#0c1016',borderWidth:2}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'right',labels:{color:dim,boxWidth:12}}},cutout:'58%'}});}
+ var DG=[{"city":"Los Angeles","rent":2426,"income":69268,"renter_pct":88.6,"home_value":786000},{"city":"Santa Monica","rent":2239,"income":95139,"renter_pct":78.6,"home_value":1244400},{"city":"Pasadena","rent":2270,"income":87813,"renter_pct":84.8,"home_value":682300},{"city":"Long Beach","rent":1968,"income":74510,"renter_pct":79,"home_value":542600},{"city":"Burbank","rent":2397,"income":109953,"renter_pct":56.3,"home_value":1061200},{"city":"Glendale","rent":1938,"income":64564,"renter_pct":82.3,"home_value":800800}];
+ if(DG.length&&document.getElementById('demoChart')){new Chart(document.getElementById('demoChart'),{type:'bar',data:{labels:DG.map(function(x){return x.city;}),datasets:[{label:'Median gross rent ($/mo)',data:DG.map(function(x){return x.rent;}),backgroundColor:'rgba(200,161,90,.8)',yAxisID:'y'},{label:'Median income ($k/yr)',data:DG.map(function(x){return Math.round(x.income/1000);}),backgroundColor:'rgba(120,170,255,.6)',yAxisID:'y1'}]},options:{responsive:true,maintainAspectRatio:false,scales:{y:{grid:{color:line},position:'left'},y1:{grid:{display:false},position:'right'},x:{grid:{display:false}}},plugins:{legend:{labels:{color:dim}}}}});}
+}
+/* Live Deal Desk — real CRCP points + transparent score colors */
+var desked=false, deskMap=null;
+function scoreColor(s){try{if(window.DealScore&&DealScore.scoreColor)return DealScore.scoreColor(s);}catch(e){}return s>=80?'#16a34a':s>=60?'#ca8a04':s>=40?'#ea580c':s>=20?'#dc2626':'#7f1d1d';}
+function initDesk(){
+ document.querySelectorAll('.dscore').forEach(function(b){b.style.background=scoreColor(+b.dataset.score);});
+ if(desked||!window.L||!document.getElementById('dealmap'))return;desked=true;
+ var pts=[{"id":"crx2477882","address":"304 E 95th St","city":"Los Angeles","type":"Multifamily","price":1400000,"units":6,"cap_rate":10.43,"dscr":1.79,"coc":16.7,"score":77,"lat":33.950142861038,"lng":-118.269365751923},{"id":"crx1834484","address":"5335 Harmony Avenue","city":"North Hollywood","type":"Multifamily","price":1195000,"units":8,"cap_rate":8.84,"dscr":1.51,"coc":10.92,"score":76,"lat":34.167277489905,"lng":-118.366520195499},{"id":"crx2257159","address":"553 Stanford Ave","city":"Los Angeles","type":"Multifamily","price":1375000,"units":17,"cap_rate":9.73,"dscr":1.67,"coc":14.16,"score":76,"lat":34.041243837456,"lng":-118.242474847157},{"id":"crx1686510","address":"3013 W 11th St","city":"Los Angeles","type":"Multifamily","price":1195000,"units":9,"cap_rate":7.66,"dscr":1.31,"coc":6.63,"score":67,"lat":34.050909240654,"lng":-118.303601261465},{"id":"crx2406293","address":"901 N El Centro Avenue","city":"Hollywood","type":"Multifamily","price":949995,"units":5,"cap_rate":7.69,"dscr":1.32,"coc":6.74,"score":64,"lat":34.087174205039,"lng":-118.324501761593},{"id":"crx2066415","address":"333 N Union Ave","city":"Los Angeles","type":"Multifamily","price":1195000,"units":1,"cap_rate":7.5,"dscr":1.28,"coc":6.05,"score":62,"lat":34.067804842665,"lng":-118.263883967228},{"id":"crx2596637","address":"4928 Lennox Blvd","city":"Inglewood","type":"Multifamily","price":1100000,"units":4,"cap_rate":7.28,"dscr":1.25,"coc":5.25,"score":60,"lat":33.938136867687,"lng":-118.364133911619},{"id":"crx2412499","address":"10370-1/2 Ashton Avenue","city":"Los Angeles","type":"Multifamily","price":2095000,"units":4,"cap_rate":11.83,"dscr":2.03,"coc":21.79,"score":57,"lat":34.066214295719,"lng":-118.427319489806},{"id":"crx2474530","address":"8022 Felix","city":"Bell Gardens","type":"Multifamily","price":1299000,"units":4,"cap_rate":7.13,"dscr":1.22,"coc":4.7,"score":55,"lat":33.958096974841,"lng":-118.157210472053},{"id":"crx2553265","address":"9350 Tampa","city":"Northridge","type":"Multifamily","price":1590000,"units":1,"cap_rate":9.24,"dscr":1.58,"coc":12.37,"score":54,"lat":34.240055739989,"lng":-118.553499610246},{"id":"crx2455582","address":"1242 E 7th St","city":"Long Beach","type":"Multifamily","price":1150000,"units":4,"cap_rate":7.02,"dscr":1.2,"coc":4.3,"score":54,"lat":33.775281875225,"lng":-118.175375162905},{"id":"crx2036352","address":"833 Cedar","city":"Long Beach","type":"Multifamily","price":1850000,"units":8,"cap_rate":9,"dscr":1.54,"coc":11.5,"score":54,"lat":33.777189058744,"lng":-118.195120374326},{"id":"crx2287481","address":"920 W 3rd St","city":"Pomona","type":"Multifamily","price":975000,"units":4,"cap_rate":7,"dscr":1.2,"coc":4.23,"score":54,"lat":34.056636813334,"lng":-117.759325149572},{"id":"crx2414348","address":"16122 San Fernando Mission Boulevard","city":"Granada Hills","type":"Multifamily","price":1298000,"units":3,"cap_rate":6.66,"dscr":1.14,"coc":2.99,"score":53,"lat":34.271846376306,"lng":-118.483029483838},{"id":"crx2226450","address":"1476 W Holt Ave","city":"Pomona","type":"Multifamily","price":1900000,"units":9,"cap_rate":8.44,"dscr":1.45,"coc":9.46,"score":53,"lat":34.061733916319,"lng":-117.774182069667},{"id":"crx2601102","address":"6906 Marbrisa Avenue","city":"Huntington Park","type":"Multifamily","price":1100000,"units":4,"cap_rate":6.94,"dscr":1.19,"coc":4.01,"score":53,"lat":33.976813490268,"lng":-118.231433728665},{"address":"4511 ROSEWOOD AVE","city":"LOS ANGELES","price":3099000,"type":"Multifamily","lat":34.079063231527,"lng":-118.30259358568},{"address":"2310 Torrance Blvd","city":"Torrance","price":549000,"type":"Multifamily","lat":33.836332808536,"lng":-118.32402449154},{"address":"3584 Mulford","city":"Lynwood","price":1050000,"type":"Multifamily","lat":33.927085536997,"lng":-118.206005401231},{"address":"1114 6th Avenue","city":"Arcadia","price":1950000,"type":"Multifamily","lat":34.127751451215,"lng":-118.018679559088},{"address":"20528-& 20526 Hamlin Street","city":"Winnetka","price":1229777,"type":"Multifamily","lat":34.189128852464,"lng":-118.580402622871},{"address":"20280 Bloomfield Ave","city":"Cerritos","price":5686000,"type":"Retail","lat":33.846358872548,"lng":-118.063324205122},{"address":"411-413 E Rosecrans Avenue","city":"Compton","price":1499000,"type":"Mixed-use","lat":33.903423435568,"lng":-118.219309665387},{"address":"1165 Thompson Ave","city":"Glendale","price":8680000,"type":"Multifamily","lat":34.174077703835,"lng":-118.290064072362},{"address":"470 East Angeleno Avenue","city":"Burbank","price":10500000,"type":"Multifamily","lat":34.183204206636,"lng":-118.304293926677},{"address":"9545 Flower Street","city":"Bellflower","price":3600000,"type":"Multifamily","lat":33.882127452207,"lng":-118.130450330002},{"address":"436-440 W 2nd Street","city":"San Dimas","price":1499000,"type":"Multifamily","lat":34.108624442892,"lng":-117.814547804601},{"address":"1515 N Hollywood Way","city":"Burbank","price":1550000,"type":"Multifamily","lat":34.176563399169,"lng":-118.349355541007},{"address":"44 N Berkeley Avenue","city":"Pasadena","price":1100000,"type":"Multifamily","lat":34.14681959038,"lng":-118.109651638161},{"address":"716 South Long Beach Boulevard","city":"Compton","price":800000,"type":"Mixed-use","lat":33.890392055712,"lng":-118.207440314209},{"address":"1151 N Kingsley Dr","city":"Los Angeles","price":1250000,"type":"Multifamily","lat":34.09232946972,"lng":-118.302539428234},{"address":"2304 Torrance Blvd","city":"Torrance","price":1099000,"type":"Multifamily","lat":33.836335203955,"lng":-118.323872381877},{"address":"1453 Centinela Avenue","city":"Santa Monica","price":2025000,"type":"Multifamily","lat":34.037776734249,"lng":-118.466819247328},{"address":"149 S Bandy Avenue","city":"West Covina","price":2915000,"type":"Multifamily","lat":34.070098148879,"lng":-117.92053122181},{"address":"11904 Painter","city":"Whittier","price":1885000,"type":"Multifamily","lat":33.923220714026,"lng":-118.053202306299},{"address":"12321 Painter Avenue","city":"Whittier","price":1800000,"type":"Multifamily","lat":33.91977058666,"lng":-118.053337115208},{"address":"24300-24322 Main St.","city":"Santa Clarita","price":7500000,"type":"Multifamily","lat":34.379259701388,"lng":-118.528217731002},{"address":"1442 W 219th","city":"Torrance","price":1100000,"type":"Multifamily","lat":33.829085084361,"lng":-118.302539995978},{"address":"3456 Redondo Beach Boulevard","city":"Torrance","price":1590000,"type":"Multifamily","lat":33.880658970233,"lng":-118.333288533628},{"address":"7580 Mcgroarty Terrace","city":"Tujunga","price":2599995,"type":"Multifamily","lat":34.250701855968,"lng":-118.297994673936},{"address":"14030 Dicky Street","city":"Whittier","price":1350000,"type":"Multifamily","lat":33.942860037345,"lng":-118.029615770579},{"address":"2235 Deepgrove","city":"Rowland Heights","price":1090000,"type":"Multifamily","lat":33.979508047347,"lng":-117.910680047123},{"address":"2121 Pacific Coast Highway","city":"Hermosa Beach","price":3295000,"type":"Multifamily","lat":33.870379138673,"lng":-118.394694432872},{"address":"224 S Grand Avenue","city":"San Pedro","price":1995888,"type":"Multifamily","lat":33.742138100612,"lng":-118.290034174488},{"address":"918 S Cabrillo Ave","city":"Los Angeles","price":2327000,"type":"Multifamily","lat":34.060239063112,"lng":-118.367372753167},{"address":"7114 El Selinda Ave","city":"Bell Gardens","price":1175000,"type":"Multifamily","lat":33.968538266738,"lng":-118.160389769143},{"address":"9014 S. Fir Avenue","city":"Los Angeles","price":1250000,"type":"Multifamily","lat":33.954558453987,"lng":-118.237320326766},{"address":"3534 112","city":"Inglewood","price":799000,"type":"Multifamily","lat":33.932853866571,"lng":-118.333863897948},{"address":"24833 Railroad","city":"Newhall","price":5750000,"type":"Multifamily","lat":34.387243259843,"lng":-118.533318640546},{"address":"480 Rosecrans Ave","city":"Manhattan Beach","price":5200000,"type":"Multifamily","lat":33.90182392174,"lng":-118.41472919001},{"address":"9240 Palm Street","city":"Bellflower","price":5150000,"type":"Multifamily","lat":33.876540861793,"lng":-118.136915735526},{"address":"428 N Genesee Ave","city":"Los Angeles","price":1799000,"type":"Multifamily","lat":34.07880266685,"lng":-118.358307957711},{"address":"9746 Park Street","city":"Bellflower","price":4398000,"type":"Multifamily","lat":33.878388741604,"lng":-118.12620481271},{"address":"1652 Plaza Del Amo","city":"Torrance","price":2470000,"type":"Multifamily","lat":33.822299941491,"lng":-118.307685551283},{"address":"12001-17 Carson Street","city":"Hawaiian Gardens","price":6000000,"type":"Retail","lat":33.831574454933,"lng":-118.078132383864},{"address":"25601 Crenshaw Boulevard","city":"Torrance","price":1999999,"type":"Multifamily","lat":33.790831584222,"lng":-118.335339853056},{"address":"22438 Claretta","city":"Hawaiian Gardens","price":950000,"type":"Multifamily","lat":33.824609387582,"lng":-118.067503590368},{"address":"440 W Holt","city":"Pomona","price":849900,"type":"Multifamily","lat":34.06218396332,"lng":-117.755884798792},{"address":"9916 Whitaker Avenue","city":"North Hills","price":1099000,"type":"Multifamily","lat":34.250759097617,"lng":-118.498989265791},{"address":"1637-1651 Stanford Street","city":"Santa Monica","price":6000000,"type":"Multifamily","lat":34.032910172093,"lng":-118.465752903051},{"address":"222 N Locust","city":"Inglewood","price":1889000,"type":"Multifamily","lat":33.964993120174,"lng":-118.35033461244},{"address":"1223 N Kenmore Ave","city":"Los Angeles","price":2995000,"type":"Multifamily","lat":34.093871747796,"lng":-118.297446238393},{"address":"3204 Palmer Drive","city":"Los Angeles","price":1295000,"type":"Multifamily","lat":34.13382383873,"lng":-118.233679584893},{"address":"13525 Kornblum Avenue","city":"Hawthorne","price":1889000,"type":"Multifamily","lat":33.908861955003,"lng":-118.337377797579},{"address":"6513 Middleton","city":"Huntington Park","price":925000,"type":"Multifamily","lat":33.980305488539,"lng":-118.22903565441},{"address":"4860 Bell","city":"Bell","price":879999,"type":"Multifamily","lat":33.973385502608,"lng":-118.17920601716},{"address":"15057 Paddock","city":"Sylmar","price":1329000,"type":"Multifamily","lat":34.296041651591,"lng":-118.460530596693},{"address":"2012 S Garfield Avenue","city":"Monterey Park","price":1760000,"type":"Multifamily","lat":34.04082726539,"lng":-118.129087335025},{"address":"1063 Justin Ave","city":"Glendale","price":5400000,"type":"Multifamily","lat":34.168989304943,"lng":-118.288617367984},{"address":"1180 Myra Avenue","city":"Los Angeles","price":1695000,"type":"Multifamily","lat":34.093908422153,"lng":-118.281675043883},{"address":"473 E. 55th Street","city":"Long Beach","price":2925000,"type":"Multifamily","lat":33.856022440586,"lng":-118.18663153133},{"address":"1629 E Maple","city":"El Segundo","price":2799000,"type":"Multifamily","lat":33.927064462214,"lng":-118.398137305752},{"address":"518 E Washington","city":"Pasadena","price":3100000,"type":"Multifamily","lat":34.168871299715,"lng":-118.139792529651},{"address":"7306-7312 Neo Street","city":"Downey","price":5125000,"type":"Multifamily","lat":33.94598443895,"lng":-118.15289101858},{"address":"14812 Paramount Blvd","city":"Paramount","price":1199000,"type":"Retail","lat":33.898997586059,"lng":-118.159991810021},{"address":"5110 BAKMAN AVE","city":"NORTH HOLLYWOOD","price":33950000,"type":"Multifamily","lat":34.16321946553,"lng":-118.377324103428},{"address":"20518 Raymond Avenue","city":"Torrance","price":899000,"type":"Multifamily","lat":33.844331875746,"lng":-118.29619464118},{"address":"5326 Lorna Street","city":"Torrance","price":3700000,"type":"Multifamily","lat":33.838943189024,"lng":-118.370614823852},{"address":"2363 Torrance","city":"Torrance","price":1700000,"type":"Multifamily","lat":33.836432480818,"lng":-118.325371550841},{"address":"228 Rosemont Ave","city":"Los Angeles","price":1200000,"type":"Multifamily","lat":34.06953739908,"lng":-118.271597524711},{"address":"15131 Polk Street","city":"Sylmar","price":949000,"type":"Multifamily","lat":34.296728685212,"lng":-118.462020887753},{"address":"3644 Almeria Street","city":"San Pedro","price":1749000,"type":"Multifamily","lat":33.714110229092,"lng":-118.302212605086},{"address":"405 -411 N Euclid Ave","city":"Pasadena","price":5950000,"type":"Multifamily","lat":34.153417508775,"lng":-118.14306202251},{"address":"5032 Denny Avenue","city":"North Hollywood","price":1880000,"type":"Multifamily","lat":34.161902214065,"lng":-118.364729591585},{"address":"13461 Beach Ave","city":"Marina Del Rey","price":4500000,"type":"Multifamily","lat":33.990646890667,"lng":-118.445698216749},{"address":"1136 W 10th","city":"San Pedro","price":1349000,"type":"Multifamily","lat":33.735232858286,"lng":-118.301989115222},{"address":"1459 S Wooster St","city":"Los Angeles","price":1700000,"type":"Multifamily","lat":34.052556376006,"lng":-118.382955555764},{"address":"10516 Arrington Avenue","city":"Downey","price":2390000,"type":"Multifamily","lat":33.941741233282,"lng":-118.119293504971},{"address":"4212 La Rica","city":"Baldwin Park","price":5500000,"type":"Multifamily","lat":34.088867836858,"lng":-117.966910951493},{"address":"4253 South Centinela Avenue","city":"Los Angeles","price":1500000,"type":"Multifamily","lat":33.995365212519,"lng":-118.426564271581},{"address":"24047 Arch Street","city":"Newhall","price":714900,"type":"Multifamily","lat":34.37567848403,"lng":-118.522401570979},{"address":"11451 W Victory Boulevard","city":"North Hollywood","price":3199000,"type":"Multifamily","lat":34.186741432717,"lng":-118.381025590388},{"address":"350 W Wilson","city":"Glendale","price":3795000,"type":"Multifamily","lat":34.147966054438,"lng":-118.259645454322},{"address":"8480 Virginia","city":"South Gate","price":899000,"type":"Multifamily","lat":33.958278534626,"lng":-118.20668710932},{"address":"9234 Elizabeth Avenue","city":"South Gate","price":799000,"type":"Multifamily","lat":33.95101475371,"lng":-118.208684305097},{"address":"547 E Woodbury Road","city":"Altadena","price":1245000,"type":"Multifamily","lat":34.180295435449,"lng":-118.139227373},{"address":"1427 Euclid St","city":"Santa Monica","price":2275000,"type":"Multifamily","lat":34.02298599753,"lng":-118.486139105156},{"address":"5759 Hazeltine","city":"Van Nuys","price":928000,"type":"Multifamily","lat":34.174996577161,"lng":-118.440043523078},{"address":"14253 Gilmore Street","city":"Van Nuys","price":2645000,"type":"Multifamily","lat":34.187713436173,"lng":-118.443319588796},{"address":"2761 W Ave F","city":"Lancaster","price":1475000,"type":"Multifamily","lat":34.747803026989,"lng":-118.18301107632},{"address":"2804 W 85th","city":"Inglewood","price":1170000,"type":"Multifamily","lat":33.960798862888,"lng":-118.323249136235},{"address":"9416 Broadway","city":"Temple City","price":1350000,"type":"Multifamily","lat":34.099701282689,"lng":-118.064596493232},{"address":"14715 Chadron Ave.","city":"Gardena","price":8150000,"type":"Multifamily","lat":33.898109881635,"lng":-118.328713339619},{"address":"35 Arcadia Terrace","city":"Santa Monica","price":4500000,"type":"Multifamily","lat":34.009701963752,"lng":-118.493844216398},{"address":"195 S Wilson Avenue","city":"Pasadena","price":12500000,"type":"Multifamily","lat":34.142403534931,"lng":-118.128085429782},{"address":"415 S 6th","city":"Montebello","price":1099000,"type":"Multifamily","lat":34.00458337399,"lng":-118.108512686706},{"address":"110 S Eucalyptus Ave","city":"Inglewood","price":2650000,"type":"Multifamily","lat":33.962730526077,"lng":-118.359456159522},{"address":"124 Palos Verdes Boulevard","city":"Redondo Beach","price":7300000,"type":"Multifamily","lat":33.816968642346,"lng":-118.384187205369},{"address":"637 W 11th","city":"San Pedro","price":1398880,"type":"Multifamily","lat":33.734173267621,"lng":-118.290970946992},{"address":"1202 E E 76th St","city":"Los Angeles","price":693900,"type":"Multifamily","lat":33.971456426903,"lng":-118.253882897642},{"address":"210 W Lime Avenue","city":"Monrovia","price":1998000,"type":"Multifamily","lat":34.147984086232,"lng":-118.003423267951},{"address":"5431 Cartwright","city":"North Hollywood","price":1125000,"type":"Multifamily","lat":34.169118168403,"lng":-118.363679986975},{"address":"2815 Orchard Place","city":"South Gate","price":1800000,"type":"Multifamily","lat":33.95788351927,"lng":-118.221332022567},{"address":"4701 Saturn St","city":"Los Angeles","price":4199999,"type":"Multifamily","lat":34.043638010414,"lng":-118.339988311603},{"address":"13522 Penn","city":"Whittier","price":900000,"type":"Multifamily","lat":33.975373383928,"lng":-118.031592338694},{"address":"440 Raymond Avenue","city":"Santa Monica","price":7750000,"type":"Multifamily","lat":34.002423658085,"lng":-118.478536931542},{"address":"424 W Arbor Vitae Street","city":"Inglewood","price":750000,"type":"Multifamily","lat":33.952642428688,"lng":-118.362043561119},{"address":"1825 STANLEY AVE","city":"SIGNAL HILL","price":2400000,"type":"Multifamily","lat":33.790311584961,"lng":-118.162255047389},{"address":"3270 TWEEDY BLVD","city":"SOUTH GATE","price":3450000,"type":"Retail","lat":33.944974767168,"lng":-118.212994319759},{"address":"21512-21516 Norwalk Blvd","city":"Hawaiian Gardens","price":1050000,"type":"Retail","lat":33.834001927783,"lng":-118.071921459288},{"address":"5138 W 135th","city":"Hawthorne","price":1200000,"type":"Multifamily","lat":33.909453790503,"lng":-118.368166042353},{"address":"2210 W 229th","city":"Torrance","price":1229000,"type":"Multifamily","lat":33.818490878466,"lng":-118.320247014355},{"address":"1491 Atlantic Avenue","city":"Long Beach","price":2850000,"type":"Multifamily","lat":33.785545339374,"lng":-118.185095781139},{"address":"161 Alice St","city":"Arcadia","price":1330000,"type":"Multifamily","lat":34.131458275853,"lng":-118.026277277329},{"address":"4057 Baywood","city":"Los Angeles","price":1000000,"type":"Multifamily","lat":34.135286386181,"lng":-118.269617851103},{"address":"908-- 910 Victoria Avenue","city":"Venice","price":1799000,"type":"Multifamily","lat":33.993921338841,"lng":-118.455572111887},{"address":"579 W 14th","city":"San Pedro","price":2495000,"type":"Multifamily","lat":33.731449884971,"lng":-118.289662638286},{"address":"4202 Lexington Ave","city":"Los Angeles","price":1400000,"type":"Multifamily","lat":34.092769024017,"lng":-118.282613850653},{"address":"9614 Alondra","city":"Bellflower","price":2500000,"type":"Retail","lat":33.889293460127,"lng":-118.130170808843},{"address":"534 S Coronado St","city":"Los Angeles","price":2375000,"type":"Multifamily","lat":34.063246252121,"lng":-118.279488144112},{"address":"1932 E Washington Boulevard","city":"Pasadena","price":1395000,"type":"Multifamily","lat":34.169085827831,"lng":-118.110713125845},{"address":"4731-4733 Baldwin Park Blvd","city":"Baldwin Park","price":1695000,"type":"Multifamily","lat":34.098019117037,"lng":-117.964974131947},{"address":"14218 Sylvan Street","city":"Sherman Oaks","price":2298888,"type":"Multifamily","lat":34.184601845073,"lng":-118.442569521379},{"address":"936 North Curson Avenue","city":"West Hollywood","price":2299000,"type":"Multifamily","lat":34.08774600562,"lng":-118.355246608844},{"address":"21306 E Arrow Hwy","city":"Covina","price":4690000,"type":"Multifamily","lat":34.106548395389,"lng":-117.840146860706},{"address":"4437 Phelan Avenue","city":"Baldwin Park","price":1348800,"type":"Multifamily","lat":34.092912386374,"lng":-117.955440937985}];
+ deskMap=L.map('dealmap',{scrollWheelZoom:false}).setView([34.05,-118.3],10);
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',{attribution:'© OpenStreetMap © CARTO',maxZoom:18}).addTo(deskMap);
+ var cluster=L.markerClusterGroup();
+ pts.forEach(function(p){if(!p.lat||!p.lng)return;var m=L.circleMarker([p.lat,p.lng],{radius:7,color:p.score?scoreColor(+p.score):'#c8a15a',fillColor:p.score?scoreColor(+p.score):'#c8a15a',fillOpacity:.85,weight:1});
+ m.bindPopup('<b>'+(p.address||'')+'</b><br>'+(p.city||'')+(p.type?' · '+p.type:'')+(p.price?'<br>$'+Number(p.price).toLocaleString():'')+(p.score?'<br>Deal score: '+p.score:''));cluster.addLayer(m);});
+ deskMap.addLayer(cluster);
+ setTimeout(function(){deskMap.invalidateSize();},300);
+}
+fetch('/portal/videos/heygen-brandfilm.mp4',{method:'HEAD'}).then(function(r){if(r.ok){var v=document.getElementById('heyfilm');v.src='/portal/videos/heygen-brandfilm.mp4';v.play();var p=document.getElementById('heyph');if(p)p.style.display='none';}}).catch(function(){});
+document.querySelectorAll('.f5').forEach(function(c){var k=c.getAttribute('data-key'),src='/portal/videos/heygen-'+k+'.mp4';fetch(src,{method:'HEAD'}).then(function(r){if(r.ok){var v=c.querySelector('video');v.src=src;v.play();var p=c.querySelector('.fph');if(p)p.style.display='none';}}).catch(function(){});});
+</script>
+<a href="/portal/slideshow" style="position:fixed;bottom:20px;right:20px;z-index:60;background:#c6a765;color:#12100e;font-family:system-ui,-apple-system,sans-serif;font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;padding:11px 18px;border-radius:4px;text-decoration:none;box-shadow:0 8px 24px rgba(0,0,0,.45)">▶ Present</a>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/signin.html b/public/consulting/signin.html
new file mode 100644
index 00000000..27bdc336
--- /dev/null
+++ b/public/consulting/signin.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
+<title>RENTV.com · Consulting Portal</title>
+<style>
+ :root{--ink:#14110e;--paper:#f6f2ec;--gold:#a9884f;--line:#dcd3c5}
+ *{box-sizing:border-box}
+ body{margin:0;font-family:'Georgia',serif;background:var(--paper);color:var(--ink);min-height:100vh;display:grid;place-items:center;padding:32px}
+ .card{max-width:560px;width:100%;background:#fff;border:1px solid var(--line);box-shadow:0 24px 60px rgba(20,17,14,.08);padding:44px 48px}
+ .eyebrow{letter-spacing:.32em;text-transform:uppercase;font-size:11px;color:var(--gold);font-family:system-ui,sans-serif}
+ h1{font-size:34px;margin:.35em 0 .1em;font-weight:400;letter-spacing:.01em}
+ .sub{color:#6b6357;font-style:italic;margin:0 0 24px}
+ .grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 22px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:20px 0;margin:20px 0;font-family:system-ui,sans-serif;font-size:14px}
+ .grid b{display:block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:#9a9082;font-weight:600;margin-bottom:2px}
+ .creds{background:#faf7f1;border:1px dashed var(--gold);padding:16px 18px;margin:18px 0;font-family:'SF Mono',ui-monospace,monospace;font-size:13px}
+ .creds .row{display:flex;justify-content:space-between;padding:3px 0}
+ .creds .k{color:#9a9082}
+ a.enter{display:inline-block;margin-top:10px;background:var(--ink);color:#f6f2ec;text-decoration:none;padding:13px 26px;letter-spacing:.16em;text-transform:uppercase;font-size:12px;font-family:system-ui,sans-serif}
+ a.enter:hover{background:var(--gold)}
+ .foot{margin-top:22px;font-size:12px;color:#9a9082;font-family:system-ui,sans-serif}
+ .foot a{color:var(--gold)}
+ .gwrap{margin-top:16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-family:system-ui,sans-serif}
+ .gbtn{display:inline-flex;align-items:center;gap:10px;background:#fff;border:1px solid var(--line);color:#3c4043;padding:11px 18px;font-size:13px;border-radius:4px;cursor:not-allowed;opacity:.65}
+ .soon{font-size:11px;color:#9a9082;letter-spacing:.04em;text-transform:uppercase}
+</style>
+</head>
+<body>
+ <div class="card">
+ <div class="eyebrow">Consulting Engagement</div>
+ <h1>RENTV.com</h1>
+ <p class="sub">Commercial real estate news, deals & conferences.</p>
+
+ <div class="grid" id="meta">
+ <div><b>Industry</b>Commercial real estate news, deals & conferences</div>
+ <div><b>Location</b>Los Angeles / Western U.S.</div>
+ <div><b>Current Site</b>https://rentv.com</div>
+ <div><b>Engagement Opened</b>2026-07-17</div>
+ <div><b>Primary Contact</b></div>
+ <div><b>Reach</b>rentadmin@rentv.com · 424-419-4021</div>
+ </div>
+
+ <!-- Steve's standing rule: the initial sign-in page CARRIES the credentials. -->
+ <!-- SECURITY: never render the fleet-master credential in HTML. On a PUBLIC
+ page the whole block is suppressed (scaffold sets CREDS_BLOCK to a note);
+ a gated per-client portal renders only the client login. -->
+ <div class="creds"><div class="row"><span class="k">Portal</span><span>/portal & /admin</span></div><div class="row"><span class="k">Access</span><span>credentials delivered separately</span></div></div>
+
+ <a class="enter" href="/portal">Enter the deliverable →</a>
+
+ <a class="enter" style="background:transparent;color:var(--ink);border:1px solid var(--ink)" href="/admin">Growth command center</a>
+
+ <!-- Google login is a FUTURE upgrade; standard protection (Basic Auth) is live now. -->
+ <div class="gwrap">
+ <button class="gbtn" disabled title="Google sign-in is coming soon — credentials delivered separately">
+ <svg width="16" height="16" viewBox="0 0 48 48" aria-hidden="true"><path fill="#4285F4" d="M45 24.5c0-1.6-.1-2.8-.4-4H24v7.3h11.9c-.2 1.9-1.5 4.7-4.3 6.6l6.6 5.1C42 41.9 45 33.9 45 24.5z"/><path fill="#34A853" d="M24 46c5.7 0 10.5-1.9 14-5.1l-6.6-5.1c-1.8 1.2-4.2 2.1-7.4 2.1-5.7 0-10.5-3.8-12.2-9l-6.8 5.3C8.5 41.2 15.7 46 24 46z"/><path fill="#FBBC05" d="M11.8 28.9C11.3 27.5 11 26 11 24.5s.3-3 0.8-4.4l-6.8-5.3C3.7 17.6 3 20.9 3 24.5s.7 6.9 2 9.7l6.8-5.3z"/><path fill="#EA4335" d="M24 12.5c3.2 0 5.3 1.4 6.6 2.5l5.8-5.7C33 5.9 28.4 4 24 4 15.7 4 8.5 8.8 5 15.1l6.8 5.3C13.5 15.3 18.3 12.5 24 12.5z"/></svg>
+ Continue with Google
+ </button>
+ <span class="soon">coming soon · standard protection is active now</span>
+ </div>
+
+ <div class="foot">
+ New client, or no website yet? <a href="/intake">Start the intake questionnaire →</a>
+ </div>
+ </div>
+</body>
+</html>
diff --git a/public/consulting/slideshow.html b/public/consulting/slideshow.html
new file mode 100644
index 00000000..0be5f047
--- /dev/null
+++ b/public/consulting/slideshow.html
@@ -0,0 +1,143 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Slideshow · Consulting Deliverable</title>
+<!-- Canonical consulting Slideshow. Source of truth: ~/.claude/skills/consulting/assets/slideshow.html
+ Copied verbatim into each portal's public/slideshow.html; served (gated) at /slideshow.
+ Self-contained, zero deps: fetches the portal's own /portal deliverable and turns each
+ <section id> into a full-screen slide (kicker + headline + subhead). Keyboard/click/dot nav. -->
+<style>
+ :root{--bg:#0c1016;--panel:#12100e;--gold:#c6a765;--ink:#eef1f6;--dim:rgba(238,241,246,.66);--line:rgba(198,167,101,.28)}
+ *{margin:0;padding:0;box-sizing:border-box}
+ html,body{height:100%}
+ body{background:var(--bg);color:var(--ink);font-family:'Jost',system-ui,-apple-system,sans-serif;overflow:hidden}
+ .disp{font-family:'Cormorant Garamond',Georgia,serif}
+ #deck{position:fixed;inset:0}
+ .slide{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;padding:0 clamp(28px,9vw,140px);opacity:0;transform:translateY(24px);transition:opacity .5s ease,transform .5s ease;pointer-events:none}
+ .slide.on{opacity:1;transform:none;pointer-events:auto}
+ .kicker{font-size:clamp(11px,1.4vw,14px);letter-spacing:.36em;text-transform:uppercase;color:var(--gold);margin-bottom:22px}
+ .head{font-family:'Cormorant Garamond',Georgia,serif;font-weight:400;font-size:clamp(34px,6vw,86px);line-height:1.04;max-width:20ch}
+ .head span{color:var(--gold)}
+ .sub{margin-top:26px;max-width:60ch;font-size:clamp(15px,1.9vw,23px);line-height:1.5;color:var(--dim)}
+ .cover .head{font-size:clamp(48px,9vw,120px)}
+ .cover .meta{margin-top:34px;font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--dim)}
+ /* chrome */
+ .bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:rgba(255,255,255,.06);z-index:20}
+ .bar i{display:block;height:100%;background:var(--gold);width:0;transition:width .4s ease}
+ .count{position:fixed;bottom:16px;right:22px;font-size:12px;letter-spacing:.14em;color:var(--dim);z-index:20;font-variant-numeric:tabular-nums}
+ .dots{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);display:flex;gap:7px;z-index:20;max-width:70vw;flex-wrap:wrap;justify-content:center}
+ .dots b{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.22);cursor:pointer;transition:.25s}
+ .dots b.on{background:var(--gold);transform:scale(1.35)}
+ .hint{position:fixed;top:16px;left:22px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim);z-index:20}
+ .exit{position:fixed;top:14px;right:22px;z-index:20;color:var(--gold);text-decoration:none;font-size:12px;letter-spacing:.14em;text-transform:uppercase;border:1px solid var(--line);padding:7px 13px;border-radius:3px}
+ .exit:hover{background:var(--gold);color:var(--bg)}
+ .nav{position:fixed;top:0;bottom:0;width:22%;z-index:15;cursor:pointer}
+ .nav.l{left:0}.nav.r{right:0}
+ .err{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--dim);padding:40px}
+ @media(prefers-reduced-motion:reduce){.slide{transition:none}}
+</style>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
+</head>
+<body>
+<div class="hint">← → or space · Esc to exit</div>
+<a class="exit" href="/portal">✕ Deliverable</a>
+<div id="deck"><div class="err" id="err">Building the deck…</div></div>
+<div class="nav l" id="navL"></div><div class="nav r" id="navR"></div>
+<div class="dots" id="dots"></div>
+<div class="count" id="count"></div>
+<div class="bar"><i id="prog"></i></div>
+<script>
+(function(){
+ var deck=document.getElementById('deck'), slides=[], idx=0;
+ function esc(s){var d=document.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML;}
+ function txt(el){return el?el.textContent.replace(/\s+/g,' ').trim():'';}
+ // Cover heads keep the deliverable's <br>/<span> styling but are whitelist-sanitized:
+ // never render raw innerHTML from a parsed deliverable (XSS-safe). Only text, <br>, <span>.
+ function sanitizeHead(el){
+ var out='', ns=el.childNodes, i;
+ for(i=0;i<ns.length;i++){
+ var n=ns[i];
+ if(n.nodeType===3){ out+=esc(n.nodeValue); }
+ else if(n.nodeType===1){
+ var t=n.tagName.toLowerCase();
+ if(t==='br'){ out+='<br>'; }
+ else if(t==='span'){ out+='<span>'+esc(n.textContent)+'</span>'; }
+ else { out+=esc(n.textContent); }
+ }
+ }
+ return out;
+ }
+
+ // The deliverable lives at /portal on template-based portals, or at / on the
+ // originals (car-wash, fantasea). Try /portal, fall back to /.
+ var deliverableUrl='/portal';
+ function load(url){return fetch(url).then(function(r){if(!r.ok)return Promise.reject(r.status);deliverableUrl=url;return r.text();});}
+ load('/portal').catch(function(){return load('/');}).then(function(html){
+ var exit=document.querySelector('.exit'); if(exit) exit.href=deliverableUrl;
+ var doc=new DOMParser().parseFromString(html,'text/html');
+ var out=[];
+ // Cover slide from the deliverable's hero (h1 + first hero paragraph) or title.
+ var h1=doc.querySelector('.hero h1, h1');
+ var kick=doc.querySelector('.hero .kick, .kick, .eyebrow');
+ out.push({cover:true, kicker:txt(kick)||'Consulting Deliverable', head:(h1?sanitizeHead(h1):esc(document.title)), sub:txt(doc.querySelector('.hero p'))});
+ // One slide per section.
+ var secs=doc.querySelectorAll('section[id]');
+ if(!secs.length) secs=doc.querySelectorAll('section');
+ secs.forEach(function(s){
+ var k=s.querySelector(':scope > .eyebrow') || s.querySelector('.eyebrow');
+ var h=s.querySelector(':scope > h2, :scope h2, h2, h3');
+ var p=s.querySelector('.lead2, .lead, p');
+ var head=txt(h); if(!head) return;
+ out.push({kicker:txt(k), head:head, sub:txt(p)});
+ });
+ // Closing CTA slide.
+ out.push({cover:true, kicker:'The next step', head:'Pick a direction —<br>and we build it.', sub:'Every section above is one click from execution. Tell us where to start.'});
+ render(out);
+ }).catch(function(e){
+ document.getElementById('err').innerHTML='Could not load the deliverable to build the deck (/portal → '+esc(e)+').<br>Open the <a href="/portal" style="color:#c6a765">full deliverable</a> instead.';
+ });
+
+ function render(items){
+ deck.innerHTML='';
+ items.forEach(function(it,i){
+ var el=document.createElement('div');
+ el.className='slide'+(it.cover?' cover':'');
+ el.innerHTML=(it.kicker?'<div class="kicker">'+esc(it.kicker)+'</div>':'')
+ +'<h1 class="head disp">'+(it.cover?it.head:esc(it.head))+'</h1>'
+ +(it.sub?'<div class="sub">'+esc(it.sub)+'</div>':'')
+ +(it.cover&&i===0?'<div class="meta">Confidential strategy brief · press → to begin</div>':'');
+ deck.appendChild(el);
+ slides.push(el);
+ });
+ var dots=document.getElementById('dots');
+ slides.forEach(function(_,i){var b=document.createElement('b');b.onclick=function(){go(i);};dots.appendChild(b);});
+ go(0);
+ }
+ function go(n){
+ if(!slides.length) return;
+ idx=Math.max(0,Math.min(slides.length-1,n));
+ slides.forEach(function(s,i){s.classList.toggle('on',i===idx);});
+ var db=document.querySelectorAll('.dots b');
+ db.forEach(function(b,i){b.classList.toggle('on',i===idx);});
+ document.getElementById('count').textContent=(idx+1)+' / '+slides.length;
+ document.getElementById('prog').style.width=((idx)/(slides.length-1||1)*100)+'%';
+ }
+ function next(){go(idx+1);} function prev(){go(idx-1);}
+ document.getElementById('navR').onclick=next;
+ document.getElementById('navL').onclick=prev;
+ document.addEventListener('keydown',function(e){
+ if(e.key==='ArrowRight'||e.key===' '||e.key==='PageDown'){e.preventDefault();next();}
+ else if(e.key==='ArrowLeft'||e.key==='PageUp'){e.preventDefault();prev();}
+ else if(e.key==='Home'){go(0);} else if(e.key==='End'){go(slides.length-1);}
+ else if(e.key==='Escape'){location.href=deliverableUrl;}
+ });
+ // touch swipe
+ var x0=null;
+ document.addEventListener('touchstart',function(e){x0=e.touches[0].clientX;},{passive:true});
+ document.addEventListener('touchend',function(e){if(x0==null)return;var dx=e.changedTouches[0].clientX-x0;if(Math.abs(dx)>50){dx<0?next():prev();}x0=null;},{passive:true});
+})();
+</script>
+</body>
+</html>
diff --git a/public/consulting/versions/broadsheet.html b/public/consulting/versions/broadsheet.html
new file mode 100644
index 00000000..7f9dbce5
--- /dev/null
+++ b/public/consulting/versions/broadsheet.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Broadsheet concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#fbf3e9;--panel:#f4e7d5;--ink:#2a211a;--gold:#a8442b;--line:rgba(42,33,26,.18)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Georgia',serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Playfair Display',Georgia,serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#fff;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'Georgia',serif}
+.util{background:#2a211a;color:#f4e7d5;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'Playfair Display',Georgia,serif;font-weight:600;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'Georgia',serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#fff;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#fff;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'Playfair Display',Georgia,serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'Playfair Display',Georgia,serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'Playfair Display',Georgia,serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#fff;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#2a211a;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'Playfair Display',Georgia,serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'Playfair Display',Georgia,serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#2a211a;color:#f4e7d5;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · The Financial Times direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>Broadsheet · The Financial Times direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "Broadsheet" (The Financial Times direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/dispatch.html b/public/consulting/versions/dispatch.html
new file mode 100644
index 00000000..385d1b44
--- /dev/null
+++ b/public/consulting/versions/dispatch.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Dispatch concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#0d1b2a;--panel:#14273d;--ink:#eef3f8;--gold:#3fb0ff;--line:rgba(63,176,255,.30)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Inter',system-ui,sans-serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Inter',system-ui,sans-serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0b0b0c;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'Inter',system-ui,sans-serif}
+.util{background:#000;color:#c9ccd1;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'Inter',system-ui,sans-serif;font-weight:800;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'Inter',system-ui,sans-serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#0b0b0c;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#0b0b0c;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'Inter',system-ui,sans-serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'Inter',system-ui,sans-serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'Inter',system-ui,sans-serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#0b0b0c;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#0b0b0c;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'Inter',system-ui,sans-serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'Inter',system-ui,sans-serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#000;color:#aeb6bd;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · Bisnow direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>Dispatch · Bisnow direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "Dispatch" (Bisnow direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/marketplace.html b/public/consulting/versions/marketplace.html
new file mode 100644
index 00000000..b73b963b
--- /dev/null
+++ b/public/consulting/versions/marketplace.html
@@ -0,0 +1,225 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV Service-Provider Marketplace — seeded demo</title>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#0c1016;--panel:#121722;--ink:#eef1f6;--gold:#c8a15a;--line:rgba(200,161,90,.22);--dim:rgba(238,241,246,.66)}
+*{margin:0;padding:0;box-sizing:border-box}body{background:var(--bg);color:var(--ink);font-family:'Jost',system-ui,sans-serif}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0c1016;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px}
+header{padding:40px clamp(20px,6vw,80px) 24px;border-bottom:1px solid var(--line)}
+.k{font-size:11px;letter-spacing:.34em;text-transform:uppercase;color:var(--gold)}
+h1{font-family:'Cormorant Garamond',serif;font-weight:400;font-size:clamp(28px,4.4vw,48px);margin:8px 0 6px}
+header p{color:var(--dim);max-width:76ch;font-size:15.5px}
+.bar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;padding:18px clamp(20px,6vw,80px);border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:10}
+#q{flex:1;min-width:220px;padding:11px 14px;background:var(--panel);border:1px solid var(--line);color:var(--ink);border-radius:5px;font-size:14px}
+.chip{background:none;border:1px solid var(--line);color:var(--dim);font-size:12px;padding:8px 13px;border-radius:20px;cursor:pointer;font-family:inherit}
+.chip b{color:var(--gold)}.chip.on{background:var(--gold);color:#0c1016}.chip.on b{color:#0c1016}
+.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:14px;padding:24px clamp(20px,6vw,80px) 80px}
+.pc{background:var(--panel);border:1px solid var(--line);border-radius:7px;padding:18px;display:flex;flex-direction:column;gap:6px;transition:.25s}
+.pc:hover{transform:translateY(-3px);box-shadow:0 14px 40px rgba(0,0,0,.45)}
+.pcat{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold)}
+.pname{font-weight:500;font-size:15.5px;line-height:1.3}.psub{font-size:13px;color:var(--dim)}
+.pmeta{display:flex;flex-direction:column;gap:2px;font-size:12.5px;color:var(--dim);margin-top:4px}
+.src{font-size:10.5px;opacity:.55;letter-spacing:.04em}
+.pbtn{margin-top:auto;align-self:flex-start;background:none;border:1px solid var(--gold);color:var(--gold);font-size:11px;letter-spacing:.18em;text-transform:uppercase;padding:8px 16px;border-radius:4px;cursor:pointer;font-family:inherit}
+.pbtn:hover{background:var(--gold);color:#0c1016}
+</style></head><body>
+<a class="badge" href="/portal#teardown">◀ Back to the teardown · seeded DEMO</a>
+<header><div class="k">RENTV · R.E. Marketplace — working demo</div>
+<h1 class="disp">Service Providers — never launch empty again.</h1>
+<p>The live site shows "(0)" in every category. This demo is the fix: <b style="color:var(--gold)">62 real Los Angeles providers</b> — 40 brokers from live listing data (with phone numbers), plus web-researched firms across 4 more categories — seeded so the marketplace opens FULL. Each firm gets a free claimable listing; Featured placement becomes the paid tier.</p></header>
+<div class="bar"><input id="q" placeholder="Search providers, firms, cities…"><button class="chip on" data-cat="all">All <b>(62)</b></button><button class="chip" data-cat="Brokerage">Brokerage <b>(40)</b></button><button class="chip" data-cat="Property Management">Property Management <b>(7)</b></button><button class="chip" data-cat="Legal">Legal <b>(6)</b></button><button class="chip" data-cat="Construction / TI">Construction / TI <b>(5)</b></button><button class="chip" data-cat="Appraisal">Appraisal <b>(4)</b></button></div>
+<div class="grid" id="grid"><div class="pc" data-cat="Brokerage" data-s="errol spiro — miller & desatnik realty corp 16 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Errol Spiro — Miller & Desatnik Realty Corp</div><div class="psub">16 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 202-9166</span><span>✉ info@mdrealtycorp.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="robert stepp — stepp commercial 15 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Robert Stepp — Stepp Commercial</div><div class="psub">15 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 463-2916</span><span>✉ rstepp@steppcommercial.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="handoko chen — growth investment group 14 active listings · pasadena">
+ <div class="pcat">Brokerage</div><div class="pname">Handoko Chen — Growth Investment Group</div><div class="psub">14 active listings · Pasadena</div>
+ <div class="pmeta"><span>📞 (626) 594-4901</span><span>✉ han.chen@growthinvestmentgroup.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="michael toveg — stepp commercial 14 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Michael Toveg — Stepp Commercial</div><div class="psub">14 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 463-2916</span><span>✉ mtoveg@steppcommercial.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="cameron samimi — lyon stahl investment real estate 13 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Cameron Samimi — Lyon Stahl Investment Real Estate</div><div class="psub">13 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="jonathan taksa — taksa investment group 12 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Jonathan Taksa — Taksa Investment Group</div><div class="psub">12 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (424) 672-8220</span><span>✉ jon@taksainvestment.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="daniel withers — matthews 10 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Daniel Withers — Matthews</div><div class="psub">10 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (866) 889-0550</span><span>✉ contact@matthews.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="jenny eng — cbre la 10 active listings · glendale">
+ <div class="pcat">Brokerage</div><div class="pname">Jenny Eng — CBRE LA</div><div class="psub">10 active listings · Glendale</div>
+ <div class="pmeta"><span>📞 (818) 907-4606</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="cameron samimi — lyon stahl investment real estate, inc. 9 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Cameron Samimi — Lyon Stahl Investment Real Estate, Inc.</div><div class="psub">9 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ cameron@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="elizabeth maddox — myunits.com 9 active listings · burbank">
+ <div class="pcat">Brokerage</div><div class="pname">Elizabeth Maddox — Myunits.com</div><div class="psub">9 active listings · Burbank</div>
+ <div class="pmeta"><span>📞 (844) 698-6487</span><span>✉ elizabeth@myunits.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="taylor avakian — lyon stahl investment real estate 9 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Taylor Avakian — Lyon Stahl Investment Real Estate</div><div class="psub">9 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="bill ruane — estate properties 8 active listings · palos verdes estates">
+ <div class="pcat">Brokerage</div><div class="pname">Bill Ruane — Estate Properties</div><div class="psub">8 active listings · Palos Verdes Estates</div>
+ <div class="pmeta"><span>📞 (310) 877-2374</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="casey lins — kidder matthews 8 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Casey Lins — Kidder Matthews</div><div class="psub">8 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (877) 454-3337</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="johnnie stiegler — lyon stahl investment real estate 8 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Johnnie Stiegler — Lyon Stahl Investment Real Estate</div><div class="psub">8 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="vincent cook — kidder mathews los angeles 8 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Vincent Cook — Kidder Mathews Los Angeles</div><div class="psub">8 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (877) 454-3337</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="ken morgan — morgan skenderian investment real estate group 7 active listings · newport beach">
+ <div class="pcat">Brokerage</div><div class="pname">Ken Morgan — Morgan Skenderian Investment Real Estate Group</div><div class="psub">7 active listings · Newport Beach</div>
+ <div class="pmeta"><span>📞 (949) 251-8800</span><span>✉ kem@morganskenderian.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="stephen takahashi — lyon stahl investment real estate 7 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Stephen Takahashi — Lyon Stahl Investment Real Estate</div><div class="psub">7 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="woody stahl — lyon stahl investment real estate 7 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Woody Stahl — Lyon Stahl Investment Real Estate</div><div class="psub">7 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="brett lyon — lyon stahl investment real estate 6 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Brett Lyon — Lyon Stahl Investment Real Estate</div><div class="psub">6 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="donald heller — don heller group | christie's international real estate akg 6 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Donald Heller — Don Heller Group | Christie's International Real Estate AKG</div><div class="psub">6 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 466-7809</span><span>✉ sales@donhellergroup.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="haik bokhchalian — johnhart real estate 6 active listings · sherman oaks">
+ <div class="pcat">Brokerage</div><div class="pname">Haik Bokhchalian — JohnHart Real Estate</div><div class="psub">6 active listings · Sherman Oaks</div>
+ <div class="pmeta"><span>📞 (888) 550-4440</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="ix garcia — kim real estate, inc 6 active listings · oakland">
+ <div class="pcat">Brokerage</div><div class="pname">Ix Garcia — Kim Real Estate, Inc</div><div class="psub">6 active listings · Oakland</div>
+ <div class="pmeta"><span>📞 (925) 336-9746</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="jesse weinberg — jesse weinberg 6 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Jesse Weinberg — Jesse Weinberg</div><div class="psub">6 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 800-804-9132</span><span>✉ info@jesseweinberg.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="shant sherbetdjian — re/max commercial & investment realty 6 active listings · pasadena">
+ <div class="pcat">Brokerage</div><div class="pname">Shant Sherbetdjian — RE/MAX Commercial & Investment Realty</div><div class="psub">6 active listings · Pasadena</div>
+ <div class="pmeta"><span>📞 (310) 802-2500</span><span>✉ slampe@remaxcir.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="artin sarkissian — the art in real estate 5 active listings · glendale">
+ <div class="pcat">Brokerage</div><div class="pname">Artin Sarkissian — The Art In Real Estate</div><div class="psub">5 active listings · Glendale</div>
+ <div class="pmeta"><span>📞 (818) 241-3000</span><span>✉ info@theartinrealestate.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="justin white — centennial advisers 5 active listings · long beach">
+ <div class="pcat">Brokerage</div><div class="pname">Justin White — Centennial Advisers</div><div class="psub">5 active listings · Long Beach</div>
+ <div class="pmeta"><span>📞 (562) 269-4844</span><span>✉ info@centennialadvisers.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="kelly morgan — houze real estate mgmt & development 5 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Kelly Morgan — Houze Real Estate Mgmt & Development</div><div class="psub">5 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 836-3638</span><span>✉ info@kellymorgancommercial.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="matthew guerra — principle quality investments 5 active listings · whittier">
+ <div class="pcat">Brokerage</div><div class="pname">Matthew Guerra — Principle Quality Investments</div><div class="psub">5 active listings · Whittier</div>
+ <div class="pmeta"><span>📞 (909) 997-9765</span><span>✉ matthew.guerra@principlequalityinvestments.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="olga wright — lyon stahl investment real estate 5 active listings · el segundo">
+ <div class="pcat">Brokerage</div><div class="pname">Olga Wright — Lyon Stahl Investment Real Estate</div><div class="psub">5 active listings · El Segundo</div>
+ <div class="pmeta"><span>📞 (310) 425-9838</span><span>✉ leadership@lyonstahl.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="pat swanson — stream realty partners greater l.a. 5 active listings · irvine">
+ <div class="pcat">Brokerage</div><div class="pname">Pat Swanson — Stream Realty Partners Greater L.A.</div><div class="psub">5 active listings · Irvine</div>
+ <div class="pmeta"><span>📞 (214) 267-0400</span><span>✉ pat.swanson@streamrealty.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="reza ghobadi — colliers - los angeles - woodland hills, california 5 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Reza Ghobadi — Colliers - Los Angeles - Woodland Hills, California</div><div class="psub">5 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (818) 325-4142</span><span>✉ reza.ghobadi@colliers.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="blas fernandez — ikon properties 4 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Blas Fernandez — IKON Properties</div><div class="psub">4 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (818) 319-9191</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="chase simonton — re/max one 4 active listings · woodland hills">
+ <div class="pcat">Brokerage</div><div class="pname">Chase Simonton — RE/MAX One</div><div class="psub">4 active listings · Woodland Hills</div>
+ <div class="pmeta"><span>📞 (714) 721-7957</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="chris bosworth — cbre - atlanta 4 active listings · atlanta">
+ <div class="pcat">Brokerage</div><div class="pname">Chris Bosworth — CBRE - Atlanta</div><div class="psub">4 active listings · Atlanta</div>
+ <div class="pmeta"><span>📞 (404) 441-3849</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="gordon inman — keller williams realty - la harbor 4 active listings · rancho palos verdes">
+ <div class="pcat">Brokerage</div><div class="pname">Gordon Inman — Keller Williams Realty - LA Harbor</div><div class="psub">4 active listings · Rancho Palos Verdes</div>
+ <div class="pmeta"><span>📞 (310) 832-7272</span><span>✉ info@sanpedrochamber.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="hannah hampton — cbre la 4 active listings · glendale">
+ <div class="pcat">Brokerage</div><div class="pname">Hannah Hampton — CBRE LA</div><div class="psub">4 active listings · Glendale</div>
+ <div class="pmeta"><span>📞 (805) 630-3890</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="jacqueline carroll — compass 4 active listings · beverly hills">
+ <div class="pcat">Brokerage</div><div class="pname">Jacqueline Carroll — Compass</div><div class="psub">4 active listings · Beverly Hills</div>
+ <div class="pmeta"><span>📞 (212) 913-9058</span><span>✉ jacqueline.carroll@compass.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="john swartz — compass 4 active listings · beverly hills">
+ <div class="pcat">Brokerage</div><div class="pname">John Swartz — Compass</div><div class="psub">4 active listings · Beverly Hills</div>
+ <div class="pmeta"><span>📞 (212) 913-9058</span><span>✉ john.swartz@compass.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="jonathan nikfarjam — shield commercial real estate 4 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Jonathan Nikfarjam — Shield Commercial Real Estate</div><div class="psub">4 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (310) 593-9869</span><span>✉ contact@shieldcre.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Brokerage" data-s="josh kaplan — equity union 4 active listings · los angeles">
+ <div class="pcat">Brokerage</div><div class="pname">Josh Kaplan — Equity Union</div><div class="psub">4 active listings · Los Angeles</div>
+ <div class="pmeta"><span>📞 (800) 937-8489</span><span>✉ info@equityunion.com</span><span class="src">live listing data</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="los angeles property management group 30+ yrs residential + commercial across la & sfv">
+ <div class="pcat">Property Management</div><div class="pname">Los Angeles Property Management Group</div><div class="psub">30+ yrs residential + commercial across LA & SFV</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="cbre property management institutional-grade commercial pm">
+ <div class="pcat">Property Management</div><div class="pname">CBRE Property Management</div><div class="psub">Institutional-grade commercial PM</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="nai capital commercial pm + brokerage, la offices">
+ <div class="pcat">Property Management</div><div class="pname">NAI Capital Commercial</div><div class="psub">PM + brokerage, LA offices</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="crown commercial property management founder david crown · ~30 yrs commercial pm">
+ <div class="pcat">Property Management</div><div class="pname">Crown Commercial Property Management</div><div class="psub">Founder David Crown · ~30 yrs commercial PM</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="moss & company long-standing la multifamily/commercial manager">
+ <div class="pcat">Property Management</div><div class="pname">Moss & Company</div><div class="psub">Long-standing LA multifamily/commercial manager</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="beach front property management 20+ yrs — apartments, commercial, hud">
+ <div class="pcat">Property Management</div><div class="pname">Beach Front Property Management</div><div class="psub">20+ yrs — apartments, commercial, HUD</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Property Management" data-s="allview real estate leasing, tenant placement, rent collection">
+ <div class="pcat">Property Management</div><div class="pname">AllView Real Estate</div><div class="psub">Leasing, tenant placement, rent collection</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="shapero law firm commercial real estate law">
+ <div class="pcat">Legal</div><div class="pname">Shapero Law Firm</div><div class="psub">Commercial real estate law</div>
+ <div class="pmeta"><span>📞 (213) 394-6031</span><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="real estate law corporation leases + tenant-improvement work letters">
+ <div class="pcat">Legal</div><div class="pname">Real Estate Law Corporation</div><div class="psub">Leases + tenant-improvement work letters</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="lerner & weiss apc commercial re litigation & transactions">
+ <div class="pcat">Legal</div><div class="pname">Lerner & Weiss APC</div><div class="psub">Commercial RE litigation & transactions</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="stone & sallus llp real estate law, la">
+ <div class="pcat">Legal</div><div class="pname">Stone & Sallus LLP</div><div class="psub">Real estate law, LA</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="schorr law real property disputes">
+ <div class="pcat">Legal</div><div class="pname">Schorr Law</div><div class="psub">Real property disputes</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Legal" data-s="roseman law apc acquisition, development, leasing">
+ <div class="pcat">Legal</div><div class="pname">Roseman Law APC</div><div class="psub">Acquisition, development, leasing</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Construction / TI" data-s="clune construction national ti specialist, la office">
+ <div class="pcat">Construction / TI</div><div class="pname">Clune Construction</div><div class="psub">National TI specialist, LA office</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Construction / TI" data-s="turner construction commercial gc, los angeles">
+ <div class="pcat">Construction / TI</div><div class="pname">Turner Construction</div><div class="psub">Commercial GC, Los Angeles</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Construction / TI" data-s="swinerton commercial gc + interiors">
+ <div class="pcat">Construction / TI</div><div class="pname">Swinerton</div><div class="psub">Commercial GC + interiors</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Construction / TI" data-s="corporate contractors inc. tenant improvements">
+ <div class="pcat">Construction / TI</div><div class="pname">Corporate Contractors Inc.</div><div class="psub">Tenant improvements</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Construction / TI" data-s="howard building corporation la commercial interiors">
+ <div class="pcat">Construction / TI</div><div class="pname">Howard Building Corporation</div><div class="psub">LA commercial interiors</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Appraisal" data-s="cbre valuation & advisory institutional appraisal">
+ <div class="pcat">Appraisal</div><div class="pname">CBRE Valuation & Advisory</div><div class="psub">Institutional appraisal</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Appraisal" data-s="colliers valuation & advisory commercial valuation">
+ <div class="pcat">Appraisal</div><div class="pname">Colliers Valuation & Advisory</div><div class="psub">Commercial valuation</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Appraisal" data-s="newmark valuation & advisory commercial valuation">
+ <div class="pcat">Appraisal</div><div class="pname">Newmark Valuation & Advisory</div><div class="psub">Commercial valuation</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div><div class="pc" data-cat="Appraisal" data-s="integra realty resources — la independent cre appraisal">
+ <div class="pcat">Appraisal</div><div class="pname">Integra Realty Resources — LA</div><div class="psub">Independent CRE appraisal</div>
+ <div class="pmeta"><span class="src">web-researched — verify before publish</span></div>
+ <button class="pbtn">Contact</button></div></div>
+<script>
+const q=document.getElementById('q'),cards=[...document.querySelectorAll('.pc')],chipsE=[...document.querySelectorAll('.chip')];
+let cat='all';
+function apply(){const s=q.value.toLowerCase();cards.forEach(c=>{c.style.display=((cat==='all'||c.dataset.cat===cat)&&(!s||c.dataset.s.includes(s)))?'':'none';});}
+chipsE.forEach(ch=>ch.onclick=()=>{chipsE.forEach(x=>x.classList.remove('on'));ch.classList.add('on');cat=ch.dataset.cat;apply();});
+q.oninput=apply;
+document.querySelectorAll('.pbtn').forEach(b=>b.onclick=()=>alert('Demo — in production this opens a contact/lead form (and a lead is a billable event).'));
+</script></body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/modern.html b/public/consulting/versions/modern.html
new file mode 100644
index 00000000..0faa185c
--- /dev/null
+++ b/public/consulting/versions/modern.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Modern concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#0e0f11;--panel:#17181b;--ink:#f2f3f5;--gold:#8b5cf6;--line:rgba(139,92,246,.30)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Inter',system-ui,sans-serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Inter',system-ui,sans-serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0b0b0c;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'Inter',system-ui,sans-serif}
+.util{background:#000;color:#c9ccd1;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'Inter',system-ui,sans-serif;font-weight:800;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'Inter',system-ui,sans-serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#0b0b0c;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#0b0b0c;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'Inter',system-ui,sans-serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'Inter',system-ui,sans-serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'Inter',system-ui,sans-serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#0b0b0c;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#0b0b0c;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'Inter',system-ui,sans-serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'Inter',system-ui,sans-serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#000;color:#aeb6bd;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · Axios direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>Modern · Axios direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "Modern" (Axios direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/observer.html b/public/consulting/versions/observer.html
new file mode 100644
index 00000000..bc6a5f06
--- /dev/null
+++ b/public/consulting/versions/observer.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — The Observer concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#14110c;--panel:#1e1a12;--ink:#f6efe2;--gold:#c9a24b;--line:rgba(201,162,75,.30)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Jost',system-ui,sans-serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Cormorant Garamond',Georgia,serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0b0b0c;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'Jost',system-ui,sans-serif}
+.util{background:#000;color:#c9ccd1;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'Cormorant Garamond',Georgia,serif;font-weight:800;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'Jost',system-ui,sans-serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#0b0b0c;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#0b0b0c;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'Cormorant Garamond',Georgia,serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'Cormorant Garamond',Georgia,serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'Cormorant Garamond',Georgia,serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#0b0b0c;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#0b0b0c;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'Cormorant Garamond',Georgia,serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'Cormorant Garamond',Georgia,serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#000;color:#aeb6bd;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · Commercial Observer direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>The Observer · Commercial Observer direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "The Observer" (Commercial Observer direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/terminal.html b/public/consulting/versions/terminal.html
new file mode 100644
index 00000000..449ce06f
--- /dev/null
+++ b/public/consulting/versions/terminal.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Terminal concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#0a0e12;--panel:#111820;--ink:#e9eef3;--gold:#37d39a;--line:rgba(55,211,154,.28)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'IBM Plex Sans',system-ui,sans-serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0b0b0c;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'IBM Plex Sans',system-ui,sans-serif}
+.util{background:#000;color:#c9ccd1;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'IBM Plex Sans',system-ui,sans-serif;font-weight:800;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'IBM Plex Sans',system-ui,sans-serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#0b0b0c;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#0b0b0c;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#0b0b0c;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#0b0b0c;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#000;color:#aeb6bd;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · CoStar / GlobeSt direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>Terminal · CoStar / GlobeSt direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "Terminal" (CoStar / GlobeSt direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/consulting/versions/thewire.html b/public/consulting/versions/thewire.html
new file mode 100644
index 00000000..73c48c7e
--- /dev/null
+++ b/public/consulting/versions/thewire.html
@@ -0,0 +1,86 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — The Wire concept</title>
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;1,400&family=Cormorant+Garamond:ital,wght@0,400;1,400&family=Inter:wght@300;400;600;800&family=Jost:wght@300;400;500&family=IBM+Plex+Sans:wght@300;400;600&display=swap" rel="stylesheet">
+<style>
+:root{--bg:#111114;--panel:#1a1a1f;--ink:#f5f5f7;--gold:#e0243b;--line:rgba(224,36,59,.30)}
+*{margin:0;padding:0;box-sizing:border-box}
+body{background:var(--bg);color:var(--ink);font-family:'Inter',system-ui,sans-serif;font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased}
+.disp{font-family:'Playfair Display',Georgia,serif}a{color:inherit;text-decoration:none}
+.rv{opacity:0;transform:translateY(24px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
+.rv.in{opacity:1;transform:none}
+.badge{position:fixed;left:16px;bottom:16px;z-index:60;background:var(--gold);color:#0b0b0c;font-size:10px;letter-spacing:.2em;text-transform:uppercase;padding:7px 13px;border-radius:2px;font-family:'Inter',system-ui,sans-serif}
+.util{background:#000;color:#c9ccd1;font-size:12px}
+.util .in{max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;padding:8px 20px}
+.tick{display:flex;gap:22px;overflow:hidden;white-space:nowrap}.tick b{color:var(--gold)}
+.mast{border-bottom:2px solid var(--gold);max-width:1200px;margin:0 auto;padding:20px}
+.mast .in{display:flex;justify-content:space-between;align-items:flex-end}
+.logo{font-family:'Playfair Display',Georgia,serif;font-weight:800;font-size:34px;letter-spacing:-.5px}.logo span{color:var(--gold)}
+.logo small{display:block;font-size:10px;letter-spacing:.3em;text-transform:uppercase;color:var(--gold);margin-top:3px;font-family:'Inter',system-ui,sans-serif;font-weight:400}
+nav.sec{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;background:var(--bg)}
+nav.sec .in{max-width:1200px;margin:0 auto;display:flex;gap:2px;overflow:auto;padding:0 12px}
+nav.sec a{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:15px 13px;white-space:nowrap;border-bottom:3px solid transparent}
+nav.sec a:hover{color:var(--gold);border-color:var(--gold)}
+.breaking{background:var(--gold);color:#0b0b0c;font-size:13px}
+.breaking .in{max-width:1200px;margin:0 auto;display:flex;gap:14px;align-items:center;padding:10px 20px;overflow:hidden}
+.breaking b{background:#0b0b0c;color:var(--gold);font-size:11px;font-weight:800;text-transform:uppercase;padding:3px 9px;border-radius:2px;white-space:nowrap}
+.wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+.grid{display:grid;grid-template-columns:1fr 330px;gap:34px;padding:28px 0}
+.chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--gold);margin-bottom:6px}
+.lead .img{aspect-ratio:16/8;border-radius:6px;overflow:hidden;background:linear-gradient(135deg,var(--panel),var(--gold))}
+.lead .img img{width:100%;height:100%;object-fit:cover;filter:saturate(.85)}
+.lead h1{font-family:'Playfair Display',Georgia,serif;font-size:40px;line-height:1.1;margin:14px 0 8px}
+.lead p{opacity:.75;font-size:17px;max-width:54ch}.byl{font-size:12px;opacity:.6;margin-top:10px}
+.subs{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:24px;border-top:1px solid var(--line);padding-top:22px}
+.subs .img{aspect-ratio:16/9;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 60%,var(--panel)))}
+.subs h3{font-family:'Playfair Display',Georgia,serif;font-size:19px;margin:9px 0 4px;line-height:1.2}.subs p{font-size:13px;opacity:.7}
+.sh{display:flex;align-items:center;gap:12px;border-top:3px solid var(--gold);padding-top:10px;margin:30px 0 16px}.sh h2{font-size:15px;text-transform:uppercase;letter-spacing:.06em}
+.cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+.card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,var(--panel),color-mix(in srgb,var(--gold) 45%,var(--panel)))}
+.card h4{font-family:'Playfair Display',Georgia,serif;font-size:17px;line-height:1.22;margin:9px 0 4px}.card .byl{margin-top:6px}
+.rail>*{margin-bottom:24px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+.ns{background:var(--gold);color:#0b0b0c;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;opacity:.85;margin-bottom:12px}
+.ns input{width:100%;padding:11px;border:0;border-radius:4px;margin-bottom:8px;font-size:14px}
+.ns button{width:100%;padding:11px;background:#0b0b0c;color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+.mr{list-style:none;counter-reset:m}.mr li{counter-increment:m;display:flex;gap:12px;padding:11px 0;border-bottom:1px solid var(--line)}
+.mr li::before{content:counter(m);font-family:'Playfair Display',Georgia,serif;font-size:24px;color:var(--gold);line-height:1}.mr a{font-size:14px;font-weight:600;line-height:1.25}
+.conf{background:linear-gradient(135deg,var(--panel),var(--gold));border-radius:6px;padding:22px;color:#fff}
+.conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:'Playfair Display',Georgia,serif;font-size:22px;margin:6px 0 8px}
+.conf a{display:inline-block;background:#fff;color:#111;font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px;margin-top:6px}
+.mkt .row{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}.mkt .row:last-child{border:0}
+footer{background:#000;color:#aeb6bd;margin-top:30px;padding:36px 0;font-size:13px}
+footer .in{max-width:1200px;margin:0 auto;padding:0 20px;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:24px}
+footer .logo{color:#fff;font-size:24px}footer h5{color:var(--gold);font-size:11px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px}footer a{display:block;padding:3px 0}
+@media(max-width:860px){.grid{grid-template-columns:1fr}.cards,.subs{grid-template-columns:1fr 1fr}}
+</style></head><body>
+<a class="badge" href="/portal#suggested">◀ Concept · The Real Deal direction</a>
+<div class="util"><div class="in"><div class="tick"><span><b>CRE INDEX</b> 4,182 ▲0.6%</span><span><b>10-YR</b> 4.21%</span><span><b>LA VAC</b> 23.1%</span><span><b>IE CAP</b> 5.2%</span></div><div>Subscribe · Log In</div></div></div>
+<header class="mast"><div class="in"><div class="logo">REN<span>TV</span>.com<small>The Wire · The Real Deal direction</small></div><div style="font-size:12px;opacity:.7;text-align:right">Western U.S. Edition<br>Friday, July 17, 2026</div></div></header>
+<nav class="sec"><div class="in"><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a><a>Development</a><a>Markets</a><a>People</a><a>Conferences</a><a>Video</a></div></nav>
+<div class="breaking"><div class="in"><b>Breaking</b><span>$212M multifamily portfolio trades in the San Fernando Valley · Downtown LA tower lands 140K SF lease · IE industrial cap rates compress</span></div></div>
+<div class="wrap"><div class="grid">
+ <main>
+ <article class="lead rv"><div class="img"><img src="https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=1920&q=68&sat=-40" alt="" loading="lazy"></div><span class="chip" style="margin-top:14px">Deals · Los Angeles</span><h1 class="disp">Institutional Buyer Pays $212M for 8-Property Valley Multifamily Portfolio</h1><p>One of the largest SoCal multifamily trades of the year signals renewed confidence in workforce housing as rate expectations settle.</p><div class="byl">By RENTV Staff · 2 hours ago · 4 min read</div></article>
+ <div class="subs"><article class="rv"><div class="img"></div><span class="chip">Financing</span><h3>Bridge Lender Closes $95M Recap on Century City Office</h3><p>Sponsors extend runway as maturities loom across the Westside.</p></article><article class="rv"><div class="img"></div><span class="chip">Leases</span><h3>Life-Science Tenant Takes 140K SF in Pasadena</h3><p>Lab conversions keep the San Gabriel Valley pipeline moving.</p></article></div>
+ <div class="sh rv"><h2>Latest CRE News</h2></div>
+ <div class="cards">
+ <article class="card rv"><div class="img"></div><span class="chip">Sales</span><h4>Inland Empire Warehouse Trades at Record-Low Cap Rate</h4><div class="byl">RENTV Staff · 5h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Development</span><h4>320-Unit Mixed-Use Project Breaks Ground in San Diego</h4><div class="byl">RENTV Staff · 6h</div></article>
+ <article class="card rv"><div class="img"></div><span class="chip">Retail</span><h4>Grocery-Anchored Center Sells in Orange County for $61M</h4><div class="byl">RENTV Staff · 8h</div></article>
+ </div>
+ </main>
+ <aside class="rail">
+ <div class="ns box rv"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input placeholder="you@company.com"><button>Subscribe free</button></div>
+ <div class="rv"><div class="sh" style="margin-top:0"><h2>Most Read</h2></div><ol class="mr"><li><a>Blackstone venture buys SoCal logistics campus for $340M</a></li><li><a>Office-to-resi conversions accelerate in DTLA</a></li><li><a>Top 10 Western-U.S. multifamily deals of Q2</a></li><li><a>Where CRE cap rates are heading into 2027</a></li></ol></div>
+ <div class="conf rv"><div class="k">RENTV Conferences</div><h3>SoCal Real Estate Summit 2026</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Sept 24 · Los Angeles.</p><a>Register →</a></div>
+ <div class="box mkt rv"><div class="sh" style="margin-top:0"><h2>Market Snapshot</h2></div><div class="row"><span>LA Office Vacancy</span><b>23.1%</b></div><div class="row"><span>IE Industrial Cap</span><b>5.2%</b></div><div class="row"><span>SF Multifamily Rent YoY</span><b style="color:var(--gold)">+3.4%</b></div><div class="row"><span>Construction Starts</span><b>−8.7%</b></div></div>
+ </aside>
+</div></div>
+<footer><div class="in"><div><div class="logo">REN<span style="color:var(--gold)">TV</span>.com</div><p style="max-width:34ch;opacity:.8;margin-top:8px">CRE news, deals & conferences across the Western U.S. since 1998.</p></div><div><h5>Sections</h5><a>Deals</a><a>Sales</a><a>Leases</a><a>Financing</a></div><div><h5>Company</h5><a>About</a><a>Advertise</a><a>Conferences</a><a>Contact</a></div><div><h5>Follow</h5><a>LinkedIn</a><a>X</a><a>Newsletter</a></div></div></footer>
+<div style="max-width:1200px;margin:0 auto;padding:14px 20px;font-size:12px;opacity:.5">Concept "The Wire" (The Real Deal direction) · sample headlines for layout · one of six directions.</div>
+<script>
+const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
+document.querySelectorAll('.rv').forEach(el=>io.observe(el));
+</script>
+</body></html>
\ No newline at end of file
diff --git a/public/press/index.html b/public/press/index.html
new file mode 100644
index 00000000..a8a596b0
--- /dev/null
+++ b/public/press/index.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>RENTV — Press & Promos (Admin)</title>
+<style>
+ :root { --ink:#0d1b2a; --muted:#5b6b7b; --line:#e4e9ef; --bg:#f6f8fb; --accent:#0a6cff; }
+ * { box-sizing:border-box; }
+ body { margin:0; font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; color:var(--ink); background:var(--bg); }
+ header { padding:22px 28px; border-bottom:1px solid var(--line); background:#fff; display:flex; align-items:baseline; gap:14px; }
+ header h1 { margin:0; font-size:20px; letter-spacing:.2px; }
+ header .tag { font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:#fff; background:var(--accent); padding:3px 8px; border-radius:99px; }
+ header a { margin-left:auto; font-size:13px; color:var(--accent); text-decoration:none; }
+ main { max-width:1100px; margin:0 auto; padding:28px; }
+ h2 { font-size:13px; text-transform:uppercase; letter-spacing:.1em; color:var(--muted); margin:34px 0 14px; }
+ .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:18px; }
+ .card { background:#fff; border:1px solid var(--line); border-radius:12px; overflow:hidden; }
+ .card video { width:100%; display:block; background:#000; aspect-ratio:16/9; }
+ .card .body { padding:12px 14px; }
+ .card .body b { font-size:14px; }
+ .card .body span { display:block; color:var(--muted); font-size:12px; margin-top:2px; }
+ .caps { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:10px; }
+ .caps img { width:100%; border:1px solid var(--line); border-radius:8px; display:block; }
+</style>
+</head>
+<body>
+<header>
+ <h1>RENTV Press & Promos</h1><span class="tag">Admin</span>
+ <a href="/">← back to site</a>
+</header>
+<main>
+ <p style="color:var(--muted);max-width:640px">Launch recap films (HyperFrames, Steve VO) and the admin-surface capture set,
+ folded in from the former <code>rentv-promos</code> project. Internal use only.</p>
+
+ <h2>Recap films</h2>
+ <div class="grid" id="films"></div>
+
+ <h2>Launch captures</h2>
+ <div class="caps" id="caps"></div>
+</main>
+<script>
+ const films = [
+ ['brand', 'Brand film', 'RENTV identity + positioning'],
+ ['daily', 'Daily recap', 'The daily CRE news wrap'],
+ ['review', 'The REview', 'Video platform showcase'],
+ ['deals', 'Deals reel', 'Tracked transactions highlight'],
+ ];
+ document.getElementById('films').innerHTML = films.map(([f,t,s]) =>
+ `<div class="card"><video controls preload="metadata" src="/press/media/${f}.mp4"></video>
+ <div class="body"><b>${t}</b><span>${s}</span></div></div>`).join('');
+
+ const caps = ['home','markets','article','blog','cretalk','admin-index','admin-command','admin-analytics','admin-kanban'];
+ document.getElementById('caps').innerHTML = caps.map(c =>
+ `<a href="/press/media/captures/${c}.png" target="_blank"><img loading="lazy" src="/press/media/captures/${c}.png" alt="${c}"></a>`).join('');
+</script>
+</body>
+</html>
diff --git a/public/press/media/brand.mp4 b/public/press/media/brand.mp4
new file mode 100644
index 00000000..ca24c041
Binary files /dev/null and b/public/press/media/brand.mp4 differ
diff --git a/public/press/media/captures/admin-analytics.png b/public/press/media/captures/admin-analytics.png
new file mode 100644
index 00000000..354ab11a
Binary files /dev/null and b/public/press/media/captures/admin-analytics.png differ
diff --git a/public/press/media/captures/admin-command.png b/public/press/media/captures/admin-command.png
new file mode 100644
index 00000000..2ba2e5f5
Binary files /dev/null and b/public/press/media/captures/admin-command.png differ
diff --git a/public/press/media/captures/admin-index.png b/public/press/media/captures/admin-index.png
new file mode 100644
index 00000000..57d09788
Binary files /dev/null and b/public/press/media/captures/admin-index.png differ
diff --git a/public/press/media/captures/admin-kanban.png b/public/press/media/captures/admin-kanban.png
new file mode 100644
index 00000000..32675843
Binary files /dev/null and b/public/press/media/captures/admin-kanban.png differ
diff --git a/public/press/media/captures/article.png b/public/press/media/captures/article.png
new file mode 100644
index 00000000..0d23d798
Binary files /dev/null and b/public/press/media/captures/article.png differ
diff --git a/public/press/media/captures/blog.png b/public/press/media/captures/blog.png
new file mode 100644
index 00000000..5157f00d
Binary files /dev/null and b/public/press/media/captures/blog.png differ
diff --git a/public/press/media/captures/cretalk.png b/public/press/media/captures/cretalk.png
new file mode 100644
index 00000000..890cc75b
Binary files /dev/null and b/public/press/media/captures/cretalk.png differ
diff --git a/public/press/media/captures/home.png b/public/press/media/captures/home.png
new file mode 100644
index 00000000..242dc4d9
Binary files /dev/null and b/public/press/media/captures/home.png differ
diff --git a/public/press/media/captures/markets.png b/public/press/media/captures/markets.png
new file mode 100644
index 00000000..700c153d
Binary files /dev/null and b/public/press/media/captures/markets.png differ
diff --git a/public/press/media/captures/publish.png b/public/press/media/captures/publish.png
new file mode 100644
index 00000000..ef5b1108
Binary files /dev/null and b/public/press/media/captures/publish.png differ
diff --git a/public/press/media/captures/review.png b/public/press/media/captures/review.png
new file mode 100644
index 00000000..3c252cf9
Binary files /dev/null and b/public/press/media/captures/review.png differ
diff --git a/public/press/media/captures/theme-midnight.png b/public/press/media/captures/theme-midnight.png
new file mode 100644
index 00000000..96e0806c
Binary files /dev/null and b/public/press/media/captures/theme-midnight.png differ
diff --git a/public/press/media/captures/theme-salmon.png b/public/press/media/captures/theme-salmon.png
new file mode 100644
index 00000000..929efd78
Binary files /dev/null and b/public/press/media/captures/theme-salmon.png differ
diff --git a/public/press/media/captures/v-broadsheet.png b/public/press/media/captures/v-broadsheet.png
new file mode 100644
index 00000000..14e64aa4
Binary files /dev/null and b/public/press/media/captures/v-broadsheet.png differ
diff --git a/public/press/media/captures/v-dashboard.png b/public/press/media/captures/v-dashboard.png
new file mode 100644
index 00000000..c74c45df
Binary files /dev/null and b/public/press/media/captures/v-dashboard.png differ
diff --git a/public/press/media/captures/v-magazine.png b/public/press/media/captures/v-magazine.png
new file mode 100644
index 00000000..59170ba4
Binary files /dev/null and b/public/press/media/captures/v-magazine.png differ
diff --git a/public/press/media/captures/v-terminal.png b/public/press/media/captures/v-terminal.png
new file mode 100644
index 00000000..2c71fd54
Binary files /dev/null and b/public/press/media/captures/v-terminal.png differ
diff --git a/public/press/media/captures/v-wire.png b/public/press/media/captures/v-wire.png
new file mode 100644
index 00000000..85aec85a
Binary files /dev/null and b/public/press/media/captures/v-wire.png differ
diff --git a/public/press/media/captures/versions.png b/public/press/media/captures/versions.png
new file mode 100644
index 00000000..7db5cae4
Binary files /dev/null and b/public/press/media/captures/versions.png differ
diff --git a/public/press/media/daily.mp4 b/public/press/media/daily.mp4
new file mode 100644
index 00000000..4e9748c4
Binary files /dev/null and b/public/press/media/daily.mp4 differ
diff --git a/public/press/media/deals.mp4 b/public/press/media/deals.mp4
new file mode 100644
index 00000000..07ff0547
Binary files /dev/null and b/public/press/media/deals.mp4 differ
diff --git a/public/press/media/review.mp4 b/public/press/media/review.mp4
new file mode 100644
index 00000000..986ed6cd
Binary files /dev/null and b/public/press/media/review.mp4 differ
diff --git a/server.js b/server.js
index 03d81f2e..0c28140d 100644
--- a/server.js
+++ b/server.js
@@ -18,17 +18,37 @@ const readJSON = (f, fb) => { try { return JSON.parse(fs.readFileSync(path.join(
// asArr() forces an array at every consumption site so a malformed feed degrades to empty, never a 500.
const asArr = (x) => (Array.isArray(x) ? x : []);
-// ── Basic Auth gate (unified + client logins). Health stays open for smoke-tests. ──
-const CREDS = ['admin:DW2024!', 'Boomer:rentfree911', 'Daniel:Dandeana1992', 'Scott:Russia1980']
- .concat((process.env.BASIC_AUTH_EXTRA || '').split(',').map(s => s.trim()).filter(Boolean));
-const ACCEPTED = new Set(CREDS.map(c => 'Basic ' + Buffer.from(c).toString('base64')));
+// ── Two-tier auth: USER (front page only) + ADMIN (front page + all internal data). ──
+// Role is resolved from WHICH cred matched, then req.role drives every gate below.
+// In prod the single nginx basic-auth wall must contain EVERY login here (both tiers);
+// this app then splits front-page vs internal-data access by role. See ACCESS-USERS.md.
+const ROLE_CREDS = {
+ admin: ['admin:DW2024!'],
+ user: ['user:RentvUser2026!', 'Boomer:rentfree911', 'Daniel:Dandeana1992', 'Scott:Russia1980'],
+};
+// env overrides — extra ADMIN logins via BASIC_AUTH_ADMIN, extra USER logins via BASIC_AUTH_EXTRA.
+(process.env.BASIC_AUTH_ADMIN || '').split(',').map(s => s.trim()).filter(Boolean).forEach(c => ROLE_CREDS.admin.push(c));
+(process.env.BASIC_AUTH_EXTRA || '').split(',').map(s => s.trim()).filter(Boolean).forEach(c => ROLE_CREDS.user.push(c));
+const CRED_ROLE = new Map();
+for (const [role, list] of Object.entries(ROLE_CREDS)) {
+ for (const c of list) CRED_ROLE.set('Basic ' + Buffer.from(c).toString('base64'), role);
+}
app.get('/api/health', (_q, r) => r.json({ ok: true, at: new Date().toISOString() }));
+// Global authentication gate (any valid login passes) + role resolution onto req.role.
app.use((req, res, next) => {
- if (process.env.OPEN === '1') return next(); // local-preview bypass only; prod never sets OPEN
- if (ACCEPTED.has(req.headers.authorization || '')) return next();
+ if (process.env.OPEN === '1') { req.role = 'admin'; return next(); } // local-preview bypass; prod never sets OPEN
+ const role = CRED_ROLE.get(req.headers.authorization || '');
+ if (role) { req.role = role; return next(); }
res.set('WWW-Authenticate', 'Basic realm="RENTV"');
return res.status(401).send('Authentication required');
});
+// Admin-only gate: 403 for user-tier logins. Guards all INTERNAL data endpoints + shells.
+function adminOnly(req, res, next) {
+ if (req.role === 'admin') return next();
+ return res.status(403).send('Admin only — this area requires an admin login.');
+}
+// Expose the caller's role to the front end so shells can hide/show internal nav.
+app.get('/api/me', (req, res) => res.json({ role: req.role || 'user' }));
// ── Live news feed (served from the cron-refreshed cache) ──
app.get('/api/news', (_q, r) => {
@@ -120,8 +140,8 @@ app.post('/api/subscribe', (req, res) => {
catch (e) { return res.status(500).json({ ok: false, error: 'could not save' }); }
res.json({ ok: true });
});
-// Admin read (behind the same Basic-Auth gate) — count + recent signups.
-app.get('/api/subscribers', (_q, res) => {
+// Admin read — count + recent signups. Subscriber list is PII → admin-only.
+app.get('/api/subscribers', adminOnly, (_q, res) => {
let lines = [];
try { lines = fs.readFileSync(SUBS, 'utf8').split('\n').filter(Boolean); } catch { /* none yet */ }
const items = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
@@ -146,7 +166,7 @@ app.get('/api/posts/:id', (req, res) => {
if (!p) return res.status(404).json({ error: 'not found' });
res.json(p);
});
-app.post('/api/posts', (req, res) => {
+app.post('/api/posts', adminOnly, (req, res) => {
const b = req.body || {};
if (!clean(b.title)) return res.status(400).json({ ok: false, error: 'title required' });
const now = new Date().toISOString();
@@ -161,7 +181,7 @@ app.post('/api/posts', (req, res) => {
try { writePosts(a); } catch { return res.status(500).json({ ok: false, error: 'save failed' }); }
res.json({ ok: true, post });
});
-app.put('/api/posts/:id', (req, res) => {
+app.put('/api/posts/:id', adminOnly, (req, res) => {
const a = readPosts(), i = a.findIndex(x => x.id === req.params.id);
if (i < 0) return res.status(404).json({ ok: false, error: 'not found' });
const b = req.body || {}, f = ['title', 'cat', 'author', 'dek', 'image', 'body'];
@@ -171,7 +191,7 @@ app.put('/api/posts/:id', (req, res) => {
try { writePosts(a); } catch { return res.status(500).json({ ok: false, error: 'save failed' }); }
res.json({ ok: true, post: a[i] });
});
-app.delete('/api/posts/:id', (req, res) => {
+app.delete('/api/posts/:id', adminOnly, (req, res) => {
const a = readPosts(), n = a.filter(x => x.id !== req.params.id);
try { writePosts(n); } catch { return res.status(500).json({ ok: false, error: 'save failed' }); }
res.json({ ok: true, removed: a.length - n.length });
@@ -242,10 +262,12 @@ app.get('/deals', (_q, r) => r.sendFile(path.join(PUB, 'deals.html')));
app.get('/news/:id', (_q, r) => r.sendFile(path.join(PUB, 'article.html')));
// The REview — the folded-in video platform, served natively in the RENTV shell
app.get('/review', (_q, r) => r.sendFile(path.join(PUB, 'review.html')));
-// 10 site VERSIONS — picker + per-version homepage layouts (all on live data)
-app.get('/versions', (_q, r) => r.sendFile(path.join(PUB, 'versions', 'index.html')));
-// 10 ADMIN versions — picker + per-version dashboards over the real gated data
-app.get('/admin', (_q, r) => r.sendFile(path.join(PUB, 'admin', 'index.html')));
+// 10 site VERSIONS — internal design/layout tool → admin-only
+app.get('/versions', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'versions', 'index.html')));
+// 10 ADMIN versions — dashboards over the real internal data → admin-only
+app.get('/admin', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'admin', 'index.html')));
+// Broker & Owner Intelligence Desk shell → admin-only (its data APIs are gated below too)
+app.get('/desk', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'desk.html')));
// Blog — public index of original RENTV articles + the single-post reader
app.get('/blog', (_q, r) => r.sendFile(path.join(PUB, 'blog.html')));
app.get('/post/:id', (_q, r) => r.sendFile(path.join(PUB, 'post.html')));
@@ -266,9 +288,9 @@ async function usreProxy(upstream, req, res) {
res.json(await r.json());
} catch (e) { res.status(502).json({ rows: [], total: 0, error: 'registry unavailable' }); }
}
-app.get('/api/brokers', (req, res) => usreProxy('/api/brokers', req, res));
-app.get('/api/firms', (req, res) => usreProxy('/api/firms', req, res));
-app.get('/api/sublease', (req, res) => usreProxy('/api/sublease', req, res));
+app.get('/api/brokers', adminOnly, (req, res) => usreProxy('/api/brokers', req, res));
+app.get('/api/firms', adminOnly, (req, res) => usreProxy('/api/firms', req, res));
+app.get('/api/sublease', adminOnly, (req, res) => usreProxy('/api/sublease', req, res));
// Admin write-forwarder → usre backend (JSON body). Same-origin so /desk admin can add/withdraw
// sublease listings; the upstream is basic-auth-gated and loopback-only, so we inject the auth.
@@ -283,17 +305,11 @@ async function usreWrite(method, upstream, req, res) {
res.status(r.status).json(await r.json());
} catch (e) { res.status(502).json({ ok: false, error: 'registry unavailable' }); }
}
-// Admin-ONLY gate: the global basic-auth above accepts both the admin cred and the client
-// browse-login (Boomer); sublease writes must be admin-only, so a client can view /desk but
-// never inject/withdraw inventory. Served from OUTSIDE public/ so express.static can't bypass it.
-const ADMIN_CRED = 'Basic ' + Buffer.from(process.env.RENTV_ADMIN_CRED || 'admin:DW2024!').toString('base64');
-function adminGate(req, res, next) {
- if ((req.headers.authorization || '') === ADMIN_CRED) return next();
- return res.status(403).send('Admin only');
-}
-app.get('/desk-admin', adminGate, (_req, res) => res.sendFile(path.join(__dirname, 'admin', 'desk-admin.html')));
-app.post('/api/admin/sublease', adminGate, (req, res) => usreWrite('POST', '/api/admin/sublease', req, res));
-app.delete('/api/admin/sublease/:id', adminGate, (req, res) => {
+// Sublease writes + the desk-admin console are admin-only (role-resolved above). Served from
+// OUTSIDE public/ so express.static can't bypass the gate.
+app.get('/desk-admin', adminOnly, (_req, res) => res.sendFile(path.join(__dirname, 'admin', 'desk-admin.html')));
+app.post('/api/admin/sublease', adminOnly, (req, res) => usreWrite('POST', '/api/admin/sublease', req, res));
+app.delete('/api/admin/sublease/:id', adminOnly, (req, res) => {
const id = String(req.params.id).replace(/[^\d]/g, '');
if (!id) return res.status(400).json({ ok: false, error: 'bad id' });
return usreWrite('DELETE', '/api/admin/sublease/' + id, req, res);
@@ -403,14 +419,14 @@ function buildAudience(subs, subleaseRows) {
};
return { generated_at: new Date().toISOString(), stats, segments, contacts };
}
-app.get('/api/audience', async (_q, res) => {
+app.get('/api/audience', adminOnly, async (_q, res) => {
let subs = [];
try { subs = fs.readFileSync(SUBS, 'utf8').split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); } catch { /* none */ }
const subleaseRows = await fetchSubleaseRows();
res.set('Cache-Control', 'private, max-age=60');
res.json(buildAudience(subs, subleaseRows));
});
-app.get('/audience', (_q, r) => r.sendFile(path.join(PUB, 'audience.html')));
+app.get('/audience', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'audience.html')));
// ── Global site search — one box across every content surface. ──
// Result hrefs are ALWAYS internal (or external YouTube for videos); we never
@@ -537,5 +553,22 @@ app.get('/deals.xml', (req, res) => {
res.type('application/rss+xml').send(`<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0"><channel>\n <title>RENTV — CRE Deals & Transactions</title>\n <link>${B}/deals</link>\n <description>Tracked commercial real estate transactions across the Western U.S.</description>\n <language>en-us</language>\n${body}\n</channel></rss>\n`);
});
+// ── Folded-in admin-only areas ──────────────────────────────────────────────
+// /consulting — the former consulting-rentv-com deliverable (concept versions,
+// portal, slideshow), now an admin-only section of the unified site.
+app.get('/consulting', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'consulting', 'portal.html')));
+// /press — the rentv-promos recap video + launch captures, admin/press only.
+app.get('/press', adminOnly, (_q, r) => r.sendFile(path.join(PUB, 'press', 'index.html')));
+
+// ── Defense-in-depth static guard ───────────────────────────────────────────
+// The data APIs above are already admin-only; this additionally blocks user-tier
+// logins from loading any INTERNAL static shell/asset directly (desk, audience,
+// admin, versions, consulting, press). Front-page assets pass straight through.
+const INTERNAL_STATIC = /^\/(desk(\.html)?$|desk-assets\/|desk-admin|audience\.html|admin(\/|$|\.html)|versions(\/|$)|consulting(\/|$)|press(\/|$))/i;
+app.use((req, res, next) => {
+ if (req.role === 'admin') return next();
+ if (INTERNAL_STATIC.test(req.path)) return res.status(403).send('Admin only — this area requires an admin login.');
+ next();
+});
app.use(express.static(PUB, { extensions: ['html'] }));
-app.listen(PORT, () => console.log('rentv-v1 (full site, gated, live data) on ' + PORT));
+app.listen(PORT, () => console.log('rentv (unified site — user/admin tiers, live data) on ' + PORT));
← f2f1c0a7 auto-save: 2026-07-28T09:59:38 (3 files) — data/markets.json
·
back to Rentv
·
Fold /consulting dynamic API inline (intake + admin buckets) 6660ab36 →