← back to Rentv 2026
rentv-v1: add /cre-talk interview-series page (live REview video grid, guest+sponsor CTAs) wired into nav+footer; real newsletter capture (POST /api/subscribe → append-only JSONL, gated /api/subscribers admin read); wire homepage+subscribe forms; gitignore subscribers.jsonl
59bb51bc8ce51d26b040ad7263e62dac5103577d · 2026-07-21 13:14:56 -0700 · Steve Abrams
Files touched
M .gitignoreM build.mjsM public/about.htmlM public/advertise.htmlM public/article.htmlM public/conferences.htmlA public/cre-talk.htmlM public/index.htmlM public/markets.htmlM public/review.htmlM public/section.htmlM public/subscribe.htmlM server.js
Diff
commit 59bb51bc8ce51d26b040ad7263e62dac5103577d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 21 13:14:56 2026 -0700
rentv-v1: add /cre-talk interview-series page (live REview video grid, guest+sponsor CTAs) wired into nav+footer; real newsletter capture (POST /api/subscribe → append-only JSONL, gated /api/subscribers admin read); wire homepage+subscribe forms; gitignore subscribers.jsonl
---
.gitignore | 1 +
build.mjs | 69 +++++++++++++++++++--
public/about.html | 18 +++++-
public/advertise.html | 18 +++++-
public/article.html | 2 +-
public/conferences.html | 18 +++++-
public/cre-talk.html | 162 ++++++++++++++++++++++++++++++++++++++++++++++++
public/index.html | 13 +++-
public/markets.html | 2 +-
public/review.html | 2 +-
public/section.html | 2 +-
public/subscribe.html | 22 +++++--
server.js | 28 +++++++++
13 files changed, 331 insertions(+), 26 deletions(-)
diff --git a/.gitignore b/.gitignore
index 1bcdbdcb..67aa2005 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ node_modules/
.env*
*.log
.DS_Store
+data/subscribers.jsonl
diff --git a/build.mjs b/build.mjs
index 0b37ebe2..6cbb89d5 100644
--- a/build.mjs
+++ b/build.mjs
@@ -95,7 +95,7 @@ const masthead = `
<div class="datebox"><span>Western U.S. Edition</span><br><span id="today"></span></div>
</div></header>`;
-const NAV = [['/', 'Home'], ['/sales', 'Sales'], ['/leases', 'Leases'], ['/financing', 'Financing'], ['/development', 'Development'], ['/multifamily', 'Multifamily'], ['/industrial', 'Industrial'], ['/retail', 'Retail'], ['/review', 'The REview'], ['/conferences', 'Conferences']];
+const NAV = [['/', 'Home'], ['/sales', 'Sales'], ['/leases', 'Leases'], ['/financing', 'Financing'], ['/development', 'Development'], ['/multifamily', 'Multifamily'], ['/industrial', 'Industrial'], ['/retail', 'Retail'], ['/review', 'The REview'], ['/cre-talk', 'CRE Talk'], ['/conferences', 'Conferences']];
const nav = (active) => `
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
@@ -109,7 +109,7 @@ const footer = `
<div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since ${BIZ.since}.</p></div>
<div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
<div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
- <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/subscribe">Newsletters</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
</div><div class="fine">© ${new Date().getFullYear()} RENTV.com · ${BIZ.addr} · ${BIZ.phone}</div></div></footer>`;
const baseJS = `
@@ -123,7 +123,19 @@ const baseJS = `
}).catch(function(){});})();
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
- document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});`;
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});`;
const page = (title, desc, active, main) => `<!doctype html>
<html lang="en"><head>
@@ -228,9 +240,9 @@ const subscribe = page('Subscribe', 'Get the free RENTV Daily — Western-U.S. c
</div>
<div class="tile rv" style="align-self:start">
<div class="n">Join free</div><h3>Start getting the Daily</h3>
- <form class="form" data-fakepost style="margin-top:8px">
+ <form class="form" data-subscribe="subscribe-page" style="margin-top:8px">
<label>Work email</label><input type="email" required placeholder="you@company.com">
- <label>First name</label><input placeholder="First name">
+ <label>First name</label><input data-name placeholder="First name">
<label>Primary interest</label><select><option>All Western-U.S. CRE</option><option>Sales</option><option>Leases</option><option>Financing</option><option>Development</option><option>Multifamily</option><option>Industrial</option><option>Retail</option></select>
<button class="cta" type="submit">Subscribe free →</button>
<div class="ok">✓ You're on the list. Watch for tomorrow's RENTV Daily.</div>
@@ -270,6 +282,51 @@ const about = page('About', 'RENTV.com — commercial real estate news, video, a
</div>
</div></section>`);
-const pages = { 'conferences.html': conferences, 'advertise.html': advertise, 'subscribe.html': subscribe, 'about.html': about };
+// ── CRE Talk (the interview series; surfaces the REAL video catalog live) ──
+const creTalk = page('CRE Talk', 'CRE Talk — RENTV\'s interview series with the CEOs and principals steering Western-U.S. commercial real estate.', '/cre-talk', `
+ <div class="phero"><div class="wrap">
+ <div class="k">The Interview Series</div>
+ <h1>CRE Talk.</h1>
+ <p>Candid, on-the-record conversations with the CEOs, principals and dealmakers steering Western-U.S. commercial real estate — where the market's really headed, from the people moving it.</p>
+ </div></div>
+ <section class="blk"><div class="wrap">
+ <div class="cols3 rv">
+ <div class="tile"><div class="n">Format</div><h3>15–25 minutes</h3><p>One leader, one host, no fluff — the strategy conversation you'd want over coffee, on the record.</p></div>
+ <div class="tile"><div class="n">Guests</div><h3>Regional principals</h3><p>The CEOs and heads of capital markets, development and brokerage across the West.</p></div>
+ <div class="tile"><div class="n">Where</div><h3>On The REview</h3><p>Every episode lives on RENTV's video platform — watch, share, and subscribe for new drops.</p></div>
+ </div>
+ </div></section>
+ <section class="blk"><div class="wrap">
+ <div class="eyebrow rv">Watch</div><h2 class="big rv">From The REview.</h2>
+ <p class="lead2 rv">The latest from RENTV's live video catalog. New CRE Talk conversations post here as they're recorded.</p>
+ <div class="cols3" id="vidgrid" style="margin-top:26px"><p style="color:var(--sub)">Loading the latest videos…</p></div>
+ <a class="cta rv" href="/review" style="margin-top:26px">See the full REview catalog →</a>
+ </div></section>
+ <section class="blk"><div class="wrap">
+ <div class="cols2">
+ <div class="tile rv"><div class="n">Be a guest</div><h3>Have a market view worth 20 minutes?</h3><p>We're always booking principals with something to say about where Western-U.S. CRE is going. Tell us about you.</p>
+ <form class="form" data-fakepost style="margin-top:8px"><label>Name & firm</label><input required placeholder="Name — Firm"><label>Email</label><input type="email" required placeholder="you@company.com"><button class="cta" type="submit">Pitch a segment →</button><div class="ok">✓ Thanks — our producers will be in touch.</div></form>
+ </div>
+ <div class="tile rv"><div class="n">Sponsor the series</div><h3>Put your brand on the conversation.</h3><p>Named sponsorship and pre-roll on CRE Talk reaches the exact decision-makers you want — before, during and after each episode.</p><a class="cta ghost" href="/advertise" style="margin-top:16px">See sponsor options →</a></div>
+ </div>
+ </div></section>
+ <script>
+ (function(){var g=document.getElementById('vidgrid');if(!g)return;
+ fetch('/api/videos').then(function(r){return r.json();}).then(function(d){
+ var items=(d.items||[]).slice(0,6);
+ if(!items.length){g.innerHTML='<p style="color:var(--sub)">Video catalog is refreshing — check back shortly.</p>';return;}
+ g.innerHTML=items.map(function(v){
+ var href=v.view_url||('https://www.rentvreview.com/Videos/View/'+encodeURIComponent(v.id||''));
+ var thumb=v.thumb||(v.yt?('https://img.youtube.com/vi/'+v.yt+'/hqdefault.jpg'):'');
+ return '<a class="tile" href="'+esc(href)+'" target="_blank" rel="noopener" style="padding:0;overflow:hidden">'+
+ '<div style="aspect-ratio:16/9;background:#111 center/cover'+(thumb?(" url('"+esc(thumb)+"')"):'')+'"></div>'+
+ '<div style="padding:16px 18px"><div class="n" style="font-size:11px;letter-spacing:.1em;text-transform:uppercase">'+esc(v.cat||'The REview')+'</div>'+
+ '<h3 style="font-size:18px;margin:6px 0 0">'+esc(v.title||'RENTV video')+'</h3></div></a>';
+ }).join('');
+ }).catch(function(){g.innerHTML='<p style="color:var(--sub)">Video catalog temporarily unavailable.</p>';});
+ })();
+ </script>`);
+
+const pages = { 'conferences.html': conferences, 'advertise.html': advertise, 'subscribe.html': subscribe, 'about.html': about, 'cre-talk.html': creTalk };
for (const [f, html] of Object.entries(pages)) writeFileSync(join(OUT, f), html);
console.log('Wrote company pages: ' + Object.keys(pages).join(', '));
diff --git a/public/about.html b/public/about.html
index 4528607c..ebf36de5 100644
--- a/public/about.html
+++ b/public/about.html
@@ -82,7 +82,7 @@
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
<div class="navlinks" id="nav">
- <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/conferences">Conferences</a>
+ <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences">Conferences</a>
</div>
</div></nav>
@@ -119,7 +119,7 @@
<div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
<div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
<div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
- <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/subscribe">Newsletters</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
</div><div class="fine">© 2026 RENTV.com · 5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045 · (310) 213-6409</div></div></footer>
<script>
var t=document.getElementById('today');if(t)t.textContent=new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
@@ -132,5 +132,17 @@
}).catch(function(){});})();
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
- document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});</script>
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});</script>
</body></html>
\ No newline at end of file
diff --git a/public/advertise.html b/public/advertise.html
index e4ed2f73..8095fe5a 100644
--- a/public/advertise.html
+++ b/public/advertise.html
@@ -82,7 +82,7 @@
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
<div class="navlinks" id="nav">
- <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/conferences">Conferences</a>
+ <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences">Conferences</a>
</div>
</div></nav>
@@ -128,7 +128,7 @@
<div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
<div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
<div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
- <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/subscribe">Newsletters</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
</div><div class="fine">© 2026 RENTV.com · 5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045 · (310) 213-6409</div></div></footer>
<script>
var t=document.getElementById('today');if(t)t.textContent=new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
@@ -141,5 +141,17 @@
}).catch(function(){});})();
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
- document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});</script>
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});</script>
</body></html>
\ No newline at end of file
diff --git a/public/article.html b/public/article.html
index 5b8882b4..d092e5e5 100644
--- a/public/article.html
+++ b/public/article.html
@@ -59,7 +59,7 @@
<body>
<div class="util"><div class="wrap">
<div><a href="/">RENTV.com</a> · Commercial Real Estate News</div>
- <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a class="sub" href="/">Subscribe</a></div>
+ <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a class="sub" href="/">Subscribe</a></div>
</div></div>
<header class="mast"><div class="wrap">
diff --git a/public/conferences.html b/public/conferences.html
index 3e95450c..f3949ed2 100644
--- a/public/conferences.html
+++ b/public/conferences.html
@@ -82,7 +82,7 @@
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
<div class="navlinks" id="nav">
- <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/conferences" class="on">Conferences</a>
+ <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences" class="on">Conferences</a>
</div>
</div></nav>
@@ -116,7 +116,7 @@
<div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
<div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
<div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
- <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/subscribe">Newsletters</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
</div><div class="fine">© 2026 RENTV.com · 5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045 · (310) 213-6409</div></div></footer>
<script>
var t=document.getElementById('today');if(t)t.textContent=new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
@@ -129,5 +129,17 @@
}).catch(function(){});})();
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
- document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});</script>
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});</script>
</body></html>
\ No newline at end of file
diff --git a/public/cre-talk.html b/public/cre-talk.html
new file mode 100644
index 00000000..8c933472
--- /dev/null
+++ b/public/cre-talk.html
@@ -0,0 +1,162 @@
+<!doctype html>
+<html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>CRE Talk — RENTV.com</title>
+<meta name="description" content="CRE Talk — RENTV's interview series with the CEOs and principals steering Western-U.S. commercial real estate.">
+<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,700;1,400&family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
+<style>
+ :root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--bg:#fff;--wash:#f5f6f8;--sans:'Inter',system-ui,sans-serif;--serif:'Playfair Display',Georgia,serif}
+ *{box-sizing:border-box;margin:0;padding:0}
+ body{font-family:var(--sans);color:var(--ink);background:var(--bg);line-height:1.5}
+ a{color:inherit;text-decoration:none}
+ .wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+ .util{background:var(--ink);color:#cfd4d9;font-size:12px}
+ .util .wrap{display:flex;justify-content:space-between;align-items:center;height:34px}
+ .ticker{display:flex;gap:20px;overflow:hidden;white-space:nowrap}.ticker b{color:#fff}.up{color:#4ade80}.dn{color:#f87171}
+ .util .acts a{margin-left:14px;color:#cfd4d9}.util .acts a.sub{color:#fff;background:var(--red);padding:4px 12px;border-radius:3px;font-weight:700}
+ .mast{border-bottom:2px solid var(--ink);padding:20px 0 14px}
+ .mast .wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:3px}
+ .logo{font-family:var(--serif);font-weight:700;font-size:34px;letter-spacing:-1px}.logo span{color:var(--red)}
+ .logo small{display:block;font-size:11px;font-weight:600;letter-spacing:3px;color:var(--sub);text-transform:uppercase;margin-top:2px;font-family:var(--sans)}
+ .datebox{font-size:12px;color:var(--sub);text-align:center}
+ nav{border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff;z-index:10}
+ nav .wrap{display:flex;gap:2px;align-items:center;height:46px}
+ .ham{display:none;background:none;border:0;font-size:24px;line-height:1;color:var(--ink);cursor:pointer;padding:8px 12px 8px 0;margin-right:4px}
+ .navlinks{display:flex;gap:2px;align-items:center;overflow-x:auto}
+ nav a{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:14px 13px;border-bottom:3px solid transparent;white-space:nowrap;cursor:pointer}
+ nav a:hover,nav a.on{color:var(--red);border-color:var(--red)}
+ @media(max-width:860px){
+ .ham{display:block;order:-1}
+ nav .wrap{height:52px}
+ .navlinks{display:none;position:absolute;top:52px;left:0;right:0;background:#fff;flex-direction:column;align-items:stretch;border-bottom:2px solid var(--ink);box-shadow:0 10px 24px rgba(0,0,0,.14);z-index:30;max-height:78vh;overflow-y:auto}
+ .navlinks.open{display:flex}
+ .navlinks a{padding:15px 20px;border-bottom:1px solid var(--line);border-left:4px solid transparent}
+ .navlinks a:hover,.navlinks a.on{color:var(--red);border-bottom:1px solid var(--line);border-left-color:var(--red)}
+ }
+ .phero{background:linear-gradient(135deg,#111827,#c8102e);color:#fff;padding:54px 0}
+ .phero .k{font-size:12px;letter-spacing:.22em;text-transform:uppercase;opacity:.85;margin-bottom:12px}
+ .phero h1{font-family:var(--serif);font-size:clamp(32px,5vw,52px);line-height:1.08;letter-spacing:-.5px;max-width:20ch}
+ .phero p{max-width:60ch;margin-top:14px;font-size:17px;opacity:.94}
+ .rv{opacity:0;transform:translateY(18px);transition:opacity .6s ease,transform .6s ease}.rv.in{opacity:1;transform:none}
+ @media(prefers-reduced-motion:reduce){.rv{transition:none;opacity:1;transform:none}}
+ section.blk{padding:52px 0;border-bottom:1px solid var(--line)}
+ .eyebrow{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.14em;color:var(--red);margin-bottom:10px}
+ h2.big{font-family:var(--serif);font-size:clamp(26px,3.4vw,38px);line-height:1.12;letter-spacing:-.4px;max-width:24ch}
+ .lead2{max-width:66ch;color:var(--sub);margin-top:12px;font-size:18px}
+ .cols3{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;margin-top:30px}
+ .cols2{display:grid;grid-template-columns:1fr 1fr;gap:22px;margin-top:30px}
+ @media(max-width:860px){.cols3{grid-template-columns:1fr}.cols2{grid-template-columns:1fr}}
+ .tile{border:1px solid var(--line);border-radius:8px;padding:26px 24px;transition:.25s;background:#fff}
+ .tile:hover{transform:translateY(-4px);box-shadow:0 18px 44px rgba(0,0,0,.09);border-color:#cfd4d9}
+ .tile .n{font-family:var(--serif);font-size:15px;color:var(--red)}
+ .tile h3{font-family:var(--serif);font-size:22px;margin:8px 0 8px}
+ .tile p{color:var(--sub);font-size:15px}
+ .tile .price{margin-top:14px;font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--ink)}
+ .statrow{display:flex;flex-wrap:wrap;gap:44px;margin-top:26px}
+ .statrow .v{font-family:var(--serif);font-size:40px;color:var(--red);line-height:1}
+ .statrow .l{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--sub);margin-top:6px}
+ .cta{display:inline-block;background:var(--red);color:#fff;font-weight:800;font-size:13px;text-transform:uppercase;letter-spacing:.06em;padding:13px 26px;border-radius:5px;cursor:pointer;border:0;margin-top:22px}
+ .cta.ghost{background:transparent;color:var(--ink);border:1.5px solid var(--ink)}
+ .cta:hover{filter:brightness(1.06)}
+ .form{max-width:520px;margin-top:26px}
+ .form label{display:block;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--sub);margin:14px 0 6px}
+ .form input,.form select{width:100%;padding:12px 14px;border:1px solid var(--line);border-radius:5px;font-size:15px;font-family:inherit}
+ .form .ok{display:none;margin-top:14px;padding:12px 16px;background:#ecfdf3;border:1px solid #a7f3d0;border-radius:6px;color:#067647;font-size:14px}
+ .kv{display:flex;justify-content:space-between;padding:11px 0;border-bottom:1px solid var(--line);font-size:15px}.kv b{font-weight:700}
+ footer{background:var(--ink);color:#aeb6bd;margin-top:0;padding:40px 0 30px;font-size:13px}
+ footer .cols{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:28px}
+ footer .logo{color:#fff;font-size:26px;margin-bottom:8px}footer h5{color:#fff;font-size:12px;text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}footer a{display:block;padding:4px 0;color:#aeb6bd}
+ footer .fine{border-top:1px solid #2a2f34;margin-top:26px;padding-top:16px;font-size:12px;color:#7c848b}
+ @media(max-width:860px){footer .cols{grid-template-columns:1fr 1fr}}
+</style></head><body>
+
+ <div class="util"><div class="wrap">
+ <div class="ticker" id="ticker"><span><b>CRE INDEX</b> …</span><span><b>10-YR</b> …</span><span><b>LA OFFICE VAC</b> …</span></div>
+ <div class="acts"><a href="/subscribe">Newsletters</a><a href="/advertise">Advertise</a><a href="/subscribe" class="sub">Subscribe</a></div>
+ </div></div>
+ <header class="mast"><div class="wrap">
+ <a class="logo" href="/">REN<span>TV</span>.com<small>Commercial Real Estate News</small></a>
+ <div class="datebox"><span>Western U.S. Edition</span><br><span id="today"></span></div>
+ </div></header>
+ <nav><div class="wrap">
+ <button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
+ <div class="navlinks" id="nav">
+ <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk" class="on">CRE Talk</a><a href="/conferences">Conferences</a>
+ </div>
+ </div></nav>
+
+ <div class="phero"><div class="wrap">
+ <div class="k">The Interview Series</div>
+ <h1>CRE Talk.</h1>
+ <p>Candid, on-the-record conversations with the CEOs, principals and dealmakers steering Western-U.S. commercial real estate — where the market's really headed, from the people moving it.</p>
+ </div></div>
+ <section class="blk"><div class="wrap">
+ <div class="cols3 rv">
+ <div class="tile"><div class="n">Format</div><h3>15–25 minutes</h3><p>One leader, one host, no fluff — the strategy conversation you'd want over coffee, on the record.</p></div>
+ <div class="tile"><div class="n">Guests</div><h3>Regional principals</h3><p>The CEOs and heads of capital markets, development and brokerage across the West.</p></div>
+ <div class="tile"><div class="n">Where</div><h3>On The REview</h3><p>Every episode lives on RENTV's video platform — watch, share, and subscribe for new drops.</p></div>
+ </div>
+ </div></section>
+ <section class="blk"><div class="wrap">
+ <div class="eyebrow rv">Watch</div><h2 class="big rv">From The REview.</h2>
+ <p class="lead2 rv">The latest from RENTV's live video catalog. New CRE Talk conversations post here as they're recorded.</p>
+ <div class="cols3" id="vidgrid" style="margin-top:26px"><p style="color:var(--sub)">Loading the latest videos…</p></div>
+ <a class="cta rv" href="/review" style="margin-top:26px">See the full REview catalog →</a>
+ </div></section>
+ <section class="blk"><div class="wrap">
+ <div class="cols2">
+ <div class="tile rv"><div class="n">Be a guest</div><h3>Have a market view worth 20 minutes?</h3><p>We're always booking principals with something to say about where Western-U.S. CRE is going. Tell us about you.</p>
+ <form class="form" data-fakepost style="margin-top:8px"><label>Name & firm</label><input required placeholder="Name — Firm"><label>Email</label><input type="email" required placeholder="you@company.com"><button class="cta" type="submit">Pitch a segment →</button><div class="ok">✓ Thanks — our producers will be in touch.</div></form>
+ </div>
+ <div class="tile rv"><div class="n">Sponsor the series</div><h3>Put your brand on the conversation.</h3><p>Named sponsorship and pre-roll on CRE Talk reaches the exact decision-makers you want — before, during and after each episode.</p><a class="cta ghost" href="/advertise" style="margin-top:16px">See sponsor options →</a></div>
+ </div>
+ </div></section>
+ <script>
+ (function(){var g=document.getElementById('vidgrid');if(!g)return;
+ fetch('/api/videos').then(function(r){return r.json();}).then(function(d){
+ var items=(d.items||[]).slice(0,6);
+ if(!items.length){g.innerHTML='<p style="color:var(--sub)">Video catalog is refreshing — check back shortly.</p>';return;}
+ g.innerHTML=items.map(function(v){
+ var href=v.view_url||('https://www.rentvreview.com/Videos/View/'+encodeURIComponent(v.id||''));
+ var thumb=v.thumb||(v.yt?('https://img.youtube.com/vi/'+v.yt+'/hqdefault.jpg'):'');
+ return '<a class="tile" href="'+esc(href)+'" target="_blank" rel="noopener" style="padding:0;overflow:hidden">'+
+ '<div style="aspect-ratio:16/9;background:#111 center/cover'+(thumb?(" url('"+esc(thumb)+"')"):'')+'"></div>'+
+ '<div style="padding:16px 18px"><div class="n" style="font-size:11px;letter-spacing:.1em;text-transform:uppercase">'+esc(v.cat||'The REview')+'</div>'+
+ '<h3 style="font-size:18px;margin:6px 0 0">'+esc(v.title||'RENTV video')+'</h3></div></a>';
+ }).join('');
+ }).catch(function(){g.innerHTML='<p style="color:var(--sub)">Video catalog temporarily unavailable.</p>';});
+ })();
+ </script>
+
+ <footer><div class="wrap"><div class="cols">
+ <div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
+ <div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
+ <div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
+ </div><div class="fine">© 2026 RENTV.com · 5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045 · (310) 213-6409</div></div></footer>
+<script>
+ var t=document.getElementById('today');if(t)t.textContent=new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
+ var hb=document.getElementById('hamBtn');if(hb)hb.addEventListener('click',function(){var o=document.getElementById('nav').classList.toggle('open');hb.setAttribute('aria-expanded',o);});
+ var esc=function(s){return (s||'').replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});};
+ (function(){fetch('/api/ticker').then(function(r){return r.json();}).then(function(t){
+ function cell(m){if(!m||!m.value)return '';var amt=m.change_pct!=null?Math.abs(m.change_pct)+'%':(m.change_bps!=null?Math.abs(m.change_bps)+'bps':'');var arr=m.dir==='up'?' <span class="up">▲'+amt+'</span>':(m.dir==='down'?' <span class="dn">▼'+amt+'</span>':'');return '<span><b>'+esc(m.label)+'</b> '+esc(m.value)+arr+'</span>';}
+ var h=[t.cre_index,t.ten_yr,t.la_office_vac,t.ie_industrial_cap].map(cell).filter(Boolean).join('');
+ var el=document.getElementById('ticker');if(h&&el)el.innerHTML=h;
+ }).catch(function(){});})();
+ var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
+ document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});</script>
+</body></html>
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 37e2c77e..7f3951f3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -95,7 +95,7 @@
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
<div class="navlinks" id="nav">
- <a data-cat="all" class="on">Home</a><a data-cat="Sales">Sales</a><a data-cat="Leases">Leases</a><a data-cat="Financing">Financing</a><a data-cat="Development">Development</a><a data-cat="Multifamily">Multifamily</a><a data-cat="Industrial">Industrial</a><a data-cat="Retail">Retail</a><a href="/review">The REview</a><a href="/conferences">Conferences</a>
+ <a data-cat="all" class="on">Home</a><a data-cat="Sales">Sales</a><a data-cat="Leases">Leases</a><a data-cat="Financing">Financing</a><a data-cat="Development">Development</a><a data-cat="Multifamily">Multifamily</a><a data-cat="Industrial">Industrial</a><a data-cat="Retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences">Conferences</a>
</div>
</div></nav>
@@ -156,7 +156,16 @@ async function load(){
const hamBtn=document.getElementById('hamBtn');
hamBtn.addEventListener('click',()=>{const open=document.getElementById('nav').classList.toggle('open');hamBtn.setAttribute('aria-expanded',open);});
document.getElementById('nav').addEventListener('click',e=>{const a=e.target.closest('a');if(!a)return;document.getElementById('nav').classList.remove('open');hamBtn.setAttribute('aria-expanded','false');if(!a.dataset.cat)return;e.preventDefault();document.querySelectorAll('#nav a').forEach(x=>x.classList.remove('on'));a.classList.add('on');cat=a.dataset.cat;render();window.scrollTo({top:0,behavior:'smooth'});});
-document.getElementById('subbtn').onclick=()=>{if((document.getElementById('email').value||'').includes('@')){document.getElementById('subok').style.display='block';}};
+document.getElementById('subbtn').onclick=function(){
+ var em=(document.getElementById('email').value||'').trim();
+ if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(em)){document.getElementById('email').focus();return;}
+ var btn=document.getElementById('subbtn');btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:em,source:'homepage-rail'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){document.getElementById('subok').style.display='block';document.getElementById('email').setAttribute('disabled','');}
+ else{btn.removeAttribute('disabled');}
+ }).catch(function(){btn.removeAttribute('disabled');});
+};
async function loadTicker(){
try{
const t=await (await fetch('/api/ticker')).json();
diff --git a/public/markets.html b/public/markets.html
index 86e47860..bb4a5421 100644
--- a/public/markets.html
+++ b/public/markets.html
@@ -49,7 +49,7 @@
<body>
<div class="util"><div class="wrap">
<span class="live"><span class="dot"></span> Live market data</span>
- <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a class="sub" href="/#news">Subscribe</a></div>
+ <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a class="sub" href="/#news">Subscribe</a></div>
</div></div>
<header class="mast"><div class="wrap">
<a class="logo" href="/">REN<span>TV</span>.com<small>Commercial Real Estate News</small></a>
diff --git a/public/review.html b/public/review.html
index 2e81c91b..6469710b 100644
--- a/public/review.html
+++ b/public/review.html
@@ -81,7 +81,7 @@
</head>
<body>
<div class="util"><div class="wrap">
- <div class="left"><a href="/">RENTV.com News</a><a href="/review">The REview</a><a href="https://sublease.com/" target="_blank" rel="noopener">Sublease.com</a></div>
+ <div class="left"><a href="/">RENTV.com News</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="https://sublease.com/" target="_blank" rel="noopener">Sublease.com</a></div>
<div class="acts"><a href="https://www.rentvreview.com/Account/Login" target="_blank" rel="noopener">Log In</a><a href="https://www.rentvreview.com/ContactUs" target="_blank" rel="noopener">Advertise</a><a class="sub" href="https://www.rentvreview.com/Account/Register" target="_blank" rel="noopener">Post a Video</a></div>
</div></div>
diff --git a/public/section.html b/public/section.html
index f901801b..4a349453 100644
--- a/public/section.html
+++ b/public/section.html
@@ -62,7 +62,7 @@
<body>
<div class="util"><div class="wrap">
<div><a href="/">RENTV.com</a> · Commercial Real Estate News</div>
- <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a class="sub" href="/">Subscribe</a></div>
+ <div class="acts"><a href="/">Home</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a class="sub" href="/">Subscribe</a></div>
</div></div>
<header class="mast"><div class="wrap">
diff --git a/public/subscribe.html b/public/subscribe.html
index 128af9b1..8d885cd2 100644
--- a/public/subscribe.html
+++ b/public/subscribe.html
@@ -82,7 +82,7 @@
<nav><div class="wrap">
<button class="ham" id="hamBtn" aria-label="Menu" aria-expanded="false">☰</button>
<div class="navlinks" id="nav">
- <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/conferences">Conferences</a>
+ <a href="/">Home</a><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences">Conferences</a>
</div>
</div></nav>
@@ -105,9 +105,9 @@
</div>
<div class="tile rv" style="align-self:start">
<div class="n">Join free</div><h3>Start getting the Daily</h3>
- <form class="form" data-fakepost style="margin-top:8px">
+ <form class="form" data-subscribe="subscribe-page" style="margin-top:8px">
<label>Work email</label><input type="email" required placeholder="you@company.com">
- <label>First name</label><input placeholder="First name">
+ <label>First name</label><input data-name placeholder="First name">
<label>Primary interest</label><select><option>All Western-U.S. CRE</option><option>Sales</option><option>Leases</option><option>Financing</option><option>Development</option><option>Multifamily</option><option>Industrial</option><option>Retail</option></select>
<button class="cta" type="submit">Subscribe free →</button>
<div class="ok">✓ You're on the list. Watch for tomorrow's RENTV Daily.</div>
@@ -120,7 +120,7 @@
<div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
<div><h5>Sections</h5><a href="/sales">Sales</a><a href="/leases">Leases</a><a href="/financing">Financing</a><a href="/development">Development</a></div>
<div><h5>Company</h5><a href="/about">About</a><a href="/advertise">Advertise</a><a href="/conferences">Conferences</a><a href="/subscribe">Subscribe</a></div>
- <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/subscribe">Newsletters</a></div>
+ <div><h5>Follow</h5><a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/subscribe">Newsletters</a></div>
</div><div class="fine">© 2026 RENTV.com · 5757 W. Century Blvd, Suite 700, Los Angeles, CA 90045 · (310) 213-6409</div></div></footer>
<script>
var t=document.getElementById('today');if(t)t.textContent=new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
@@ -133,5 +133,17 @@
}).catch(function(){});})();
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.1});
document.querySelectorAll('.rv').forEach(function(el){io.observe(el);});
- document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});</script>
+ document.querySelectorAll('form[data-fakepost]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();var ok=f.querySelector('.ok');if(ok){ok.style.display='block';}f.querySelectorAll('input,select,button').forEach(function(x){if(x.type!=='reset')x.setAttribute('disabled','');});});});
+ // Real newsletter capture → POST /api/subscribe (append-only JSONL; no external send-to-list).
+ document.querySelectorAll('form[data-subscribe]').forEach(function(f){f.addEventListener('submit',function(e){e.preventDefault();
+ var email=(f.querySelector('input[type=email]')||{}).value||'';
+ var name=(f.querySelector('input[data-name]')||{}).value||'';
+ var interest=(f.querySelector('select')||{}).value||'';
+ var ok=f.querySelector('.ok');var btn=f.querySelector('button');if(btn)btn.setAttribute('disabled','');
+ fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({email:email,name:name,interest:interest,source:f.getAttribute('data-subscribe')||'site'})})
+ .then(function(r){return r.json();}).then(function(j){
+ if(j&&j.ok){if(ok){ok.style.display='block';}f.querySelectorAll('input,select').forEach(function(x){x.setAttribute('disabled','');});}
+ else{if(btn)btn.removeAttribute('disabled');alert((j&&j.error)?('Please enter a valid email — '+j.error):'Could not subscribe, please try again.');}
+ }).catch(function(){if(btn)btn.removeAttribute('disabled');alert('Network error — please try again.');});
+ });});</script>
</body></html>
\ No newline at end of file
diff --git a/server.js b/server.js
index 2e3c9434..c95c3814 100644
--- a/server.js
+++ b/server.js
@@ -6,6 +6,7 @@ const fs = require('fs');
const path = require('path');
const express = require('express');
const app = express();
+app.use(express.json({ limit: '16kb' }));
const PORT = process.env.PORT || 9704;
const DATA = path.join(__dirname, 'data');
const PUB = path.join(__dirname, 'public');
@@ -47,6 +48,33 @@ app.get('/api/markets', (_q, r) => {
r.json(readJSON('markets.json', {}));
});
+// ── Newsletter capture: append-only local JSONL (NO external send-to-list). ──
+const SUBS = path.join(DATA, 'subscribers.jsonl');
+app.post('/api/subscribe', (req, res) => {
+ const b = req.body || {};
+ const email = String(b.email || '').trim().toLowerCase();
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return res.status(400).json({ ok: false, error: 'valid email required' });
+ const rec = {
+ email,
+ name: String(b.name || '').trim().slice(0, 120),
+ interest: String(b.interest || '').trim().slice(0, 60),
+ source: String(b.source || 'site').trim().slice(0, 40),
+ at: new Date().toISOString(),
+ ip: (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').toString().split(',')[0].trim(),
+ };
+ try { fs.appendFileSync(SUBS, JSON.stringify(rec) + '\n'); }
+ 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) => {
+ 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);
+ const uniq = new Set(items.map(x => x.email)).size;
+ res.json({ count: items.length, unique: uniq, items: items.slice(-500).reverse() });
+});
+
// ── Article reader proxy: fetch a single rentv.com story on demand + cache it. ──
const artCache = new Map(); // id -> { at, data }
const ART_TTL = 15 * 60 * 1000;
← 6abac16f auto-save: 2026-07-21T13:07:37 (3 files) — data/markets.json
·
back to Rentv 2026
·
rentv-v1: harden newsletter capture per contrarian — idempot 255f3538 →