[object Object]

← back to Rentv Website

real rentv images (front page + article photos) + full article-body scraping + mobile hamburger nav

f8c4ddaeedbef0b970c53690f711e12cd2f9d839 · 2026-07-20 14:51:21 -0700 · Steve

Files touched

Diff

commit f8c4ddaeedbef0b970c53690f711e12cd2f9d839
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 20 14:51:21 2026 -0700

    real rentv images (front page + article photos) + full article-body scraping + mobile hamburger nav
---
 data/news.json        | 92 ++++++++++++++++++++++++++++++++++-----------------
 data/ticker.json      |  2 +-
 public/article.html   | 19 +++++++++--
 public/index.html     | 28 ++++++++++++----
 public/section.html   | 23 ++++++++++---
 scripts/pull-news.mjs |  6 +++-
 server.js             | 32 ++++++++++--------
 7 files changed, 143 insertions(+), 59 deletions(-)

diff --git a/data/news.json b/data/news.json
index f5e1519..b9361f4 100644
--- a/data/news.json
+++ b/data/news.json
@@ -1,187 +1,217 @@
 {
  "source": "rentv.com — live",
- "fetched_at": "2026-07-20T21:06:12.438Z",
+ "fetched_at": "2026-07-20T21:46:22.972Z",
  "count": 30,
  "items": [
   {
    "id": "34218",
    "title": "Camden Property Trust Buys Gilbert, AZ Res Community in $124 Mil Deal",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34218",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20278_3150ERayRdGilbertKimHoidalCBREJul132026.jpg"
   },
   {
    "id": "34225",
    "title": "CentrePoint Properties Spends $30 Mil on Trio of Colorado Retail Assets",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34225",
-   "cat": "Retail"
+   "cat": "Retail",
+   "image": ""
   },
   {
    "id": "34227",
    "title": "MG Properties Buys 188-Unit Portland Res Asset",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34227",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20286_TupeloAlleyPortlandPaulKaseburgMGPropJul162026.jpg"
   },
   {
    "id": "34216",
    "title": "Meta Housing Well Underway with 300-Unit Res Community in Woodland Hills",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34216",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20277_21300WOxnardStWHillsHollyJenvyJul152026.jpg"
   },
   {
    "id": "34214",
    "title": "Intercontinental Real Estate Corporation Closes $69 Mil Retail Buy in Washington",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34214",
-   "cat": "Retail"
+   "cat": "Retail",
+   "image": "https://www.rentv.com/images/dynamic/news_20274_1402LakeTappsPkwySEAuburnWABruceBeckJul152026.jpg"
   },
   {
    "id": "34208",
    "title": "Eagle Partners and The Vistria Group Spend $132 Mil on Garden Grove Mixed-Income Res Community",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34208",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20267_12091BayportStGardenGroveCOSTARAnnieNoebelCornersCommJul142026.jpg"
   },
   {
    "id": "34204",
    "title": "Oceanside Res Community Trades for $77.5 Mil",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34204",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20262_3851SherbourneDrOceansideDANKIRKSEYClaudiaLipmannCBREJul142026.jpg"
   },
   {
    "id": "34210",
    "title": "BKM Capital Partners Boosts Pac Northwest Holdings with Purchase of 400k sf Industrial Buy in Washington",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34210",
-   "cat": "Industrial"
+   "cat": "Industrial",
+   "image": "https://www.rentv.com/images/dynamic/news_20270_SouthcenterWestBusinessParkTukwilaWASuleAygorenConnectJul142026.jpg"
   },
   {
    "id": "34203",
    "title": "The Bascom Group Pays Over $53 Mil for OC Res Community",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34203",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20261_6968CampusDrBuenaParkJerryFinkBascomJul132026.jpg"
   },
   {
    "id": "34199",
    "title": "Leasing Assignment for 650k sf San Francisco Office Tower Awarded to CBRE",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34199",
-   "cat": "Sales"
+   "cat": "Sales",
+   "image": "https://www.rentv.com/images/dynamic/news_20259_415NatomaSFranciscoIrineSpivakCBREJun162026.jpg"
   },
   {
    "id": "34202",
    "title": "SummerHill Apartment Communities Breaks Ground on New 251-Unit Bay Area Res Development",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34202",
-   "cat": "Development"
+   "cat": "Development",
+   "image": "https://www.rentv.com/images/dynamic/news_20260_11ElcaminoRealSMateoAnneMonaghanJul72026.jpg"
   },
   {
    "id": "34196",
    "title": "Rubicon Point Partners Spends $53 Mil on Silicon Valley Medical/Office Property",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34196",
-   "cat": "Sales"
+   "cat": "Sales",
+   "image": ""
   },
   {
    "id": "34195",
    "title": "Borstein Enterprises Picks Up 282k sf OC Industrial Asset",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34195",
-   "cat": "Industrial"
+   "cat": "Industrial",
+   "image": "https://www.rentv.com/images/dynamic/news_20256_15339BarrancaPkwyIrvineSavannahDurbinCWJul62026.jpg"
   },
   {
    "id": "34188",
    "title": "Chino Distribution Facility Leased by Auto Parts Distributor in $42 Mil Deal",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34188",
-   "cat": "Leases"
+   "cat": "Leases",
+   "image": "https://www.rentv.com/images/dynamic/news_20252_16380EuclidAveChinoElisabethManvilleSmartJul22026.jpg"
   },
   {
    "id": "34186",
    "title": "Santa Clara Industrial Property Trades for $25.75 Mil",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34186",
-   "cat": "Industrial"
+   "cat": "Industrial",
+   "image": "https://www.rentv.com/images/dynamic/news_20250_651MartinAveSClaraGraceLewisJLLJul12026.jpg"
   },
   {
    "id": "34181",
    "title": "Redwood West and Panattoni Buy The Pike Destination Center in Long Beach",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34181",
-   "cat": "Retail"
+   "cat": "Retail",
+   "image": "https://www.rentv.com/images/dynamic/news_20245_95SPineAveLongBeachJessicaSpauldingJun302026.jpg"
   },
   {
    "id": "34180",
    "title": "Kennedy Wilson and Jamison Form Partnership to Develop 4,000 Units of Affordable Housing in Los Angeles",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34180",
-   "cat": "Development"
+   "cat": "Development",
+   "image": "https://www.rentv.com/images/dynamic/news_20244_350SHopeBruceBeckJune302026.jpg"
   },
   {
    "id": "34086",
    "title": "Oak Park Investments and Arise Investments Acquire Westlake Village Warehouse/Office Building in $19 Mil Transaction",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34086",
-   "cat": "Industrial"
+   "cat": "Industrial",
+   "image": "https://www.rentv.com/images/dynamic/news_20161_5785LinderoCanyonRdWstlkVillageDannySoroudiAriseJune22026.jpg"
   },
   {
    "id": "34177",
    "title": "Holland Partner Group Closes $105 Mil Bay Area Res Buy",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34177",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20242_1432WestSanCarlosSt.jpg"
   },
   {
    "id": "34172",
    "title": "Haven Housing buys 264-Unit Res Community in Austin Metro Area",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34172",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20237_15001StrathavenPassPflugervilleTXElliottGolanConnectJun262026.jpg"
   },
   {
    "id": "34174",
    "title": "MG Properties Acquires Two Multifamily Properties in Gresham, OR in Deals Totaling $60.8 Mil",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34174",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20239_ParksideApartmentsGreshamORPaulKasebergMGPropJune262026.jpg"
   },
   {
    "id": "34171",
    "title": "Leading AI Firm Leases 1.1 msf of Space at Luke Field in Glendale, AZ",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34171",
-   "cat": "Leases"
+   "cat": "Leases",
+   "image": "https://www.rentv.com/images/dynamic/news_20236_7733LitchfieldRdGlendaleAZStaceyHershauerJun242026.jpg"
   },
   {
    "id": "34169",
    "title": "Seattle Area Res Community Refinanced with $82.5 Mil Loan from Mesa West Capital",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34169",
-   "cat": "Financing"
+   "cat": "Financing",
+   "image": "https://www.rentv.com/images/dynamic/news_20233_OlinFieldsEverettWABruceBeckJun242026.jpg"
   },
   {
    "id": "34160",
    "title": "Apollo Lands in Gardena",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34160",
-   "cat": "Sales"
+   "cat": "Sales",
+   "image": "https://www.rentv.com/images/dynamic/news_20227_12888CrenshawBlvdGardenaJadeTerryCityviewJun232026.jpg"
   },
   {
    "id": "34165",
    "title": "Phoenix Multifamily Community Lands New Owner",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34165",
-   "cat": "Multifamily"
+   "cat": "Multifamily",
+   "image": "https://www.rentv.com/images/dynamic/news_20229_BroadstoneSeventhStPhnxKimHoidalCBREJun222026.jpg"
   },
   {
    "id": "34158",
    "title": "Fairfield Residential Wraps Up Work on Houston High-Rise Res Development",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34158",
-   "cat": "Development"
+   "cat": "Development",
+   "image": "https://www.rentv.com/images/dynamic/news_20223_1010WaughDrHoustonAnneMonaghanJun232026.jpg"
   },
   {
    "id": "34155",
    "title": "Newly Constructed 1.18 msf AZ Industrial Campus Lands $122 Mil in Bridge Financing",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34155",
-   "cat": "Financing"
+   "cat": "Financing",
+   "image": "https://www.rentv.com/images/dynamic/news_20219_TheBaseGlendaleAZCOSTARGraceLewisJLLJun222026.jpg"
   },
   {
    "id": "34153",
    "title": "BKM Capital Partners Closes $95 Mil SoCal Biz Campus Buy",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34153",
-   "cat": "Sales"
+   "cat": "Sales",
+   "image": "https://www.rentv.com/images/dynamic/news_20218_CarsonIndustrialCntrCarsonSuleAygorenConnectJun222026.jpg"
   },
   {
    "id": "34150",
    "title": "Arizona Power Center Lands $38 Mil Refi",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34150",
-   "cat": "Financing"
+   "cat": "Financing",
+   "image": "https://www.rentv.com/images/dynamic/news_20216_1639SStapleyRdMesaAZChrisEggerMarcomJun182026.jpg"
   },
   {
    "id": "34148",
    "title": "Southwest Traders Spends $40 Mil on Menifee Industrial Facility",
    "url": "https://www.rentv.com/content/homepage/mainnews/news/34148",
-   "cat": "Industrial"
+   "cat": "Industrial",
+   "image": "https://www.rentv.com/images/dynamic/news_20214_33520ZeidersRdMenifeeLeanneDalyColliersJun192026.jpg"
   }
  ]
 }
\ No newline at end of file
diff --git a/data/ticker.json b/data/ticker.json
index 275db1f..2fb0703 100644
--- a/data/ticker.json
+++ b/data/ticker.json
@@ -1,5 +1,5 @@
 {
-  "fetched_at": "2026-07-20T21:06:12.440Z",
+  "fetched_at": "2026-07-20T21:46:22.977Z",
   "la_office_vac": {
     "label": "LA OFFICE VAC",
     "value": "23.1%",
diff --git a/public/article.html b/public/article.html
index 2a1d3dc..c42f4c9 100644
--- a/public/article.html
+++ b/public/article.html
@@ -21,9 +21,19 @@
   .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:right}
   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;overflow-x:auto}
+  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}
   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{color:var(--red);border-left-color:var(--red)}
+  }
   .art{max-width:760px;margin:0 auto;padding:26px 0 10px}
   .crumb{font-size:12px;color:var(--sub);margin-bottom:16px}.crumb a{color:var(--red);font-weight:700}
   .chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--red);margin-bottom:8px}
@@ -58,7 +68,10 @@
   </div></header>
 
   <nav><div class="wrap">
-    <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>
+    <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>
+    </div>
   </div></nav>
 
   <div class="wrap"><article class="art" id="art">
@@ -118,6 +131,8 @@ async function load(){
     '<div class="src">📰 Republished live from RENTV.com. <a href="'+esc(src)+'" target="_blank" rel="noopener">View the full story on RENTV.com ↗</a></div>'+
     '<a class="back" href="/">← Back to all CRE news</a>';
 }
+const hamBtn=document.getElementById('hamBtn');
+hamBtn.addEventListener('click',()=>{const open=document.getElementById('nav').classList.toggle('open');hamBtn.setAttribute('aria-expanded',open);});
 load();
 </script>
 </body>
diff --git a/public/index.html b/public/index.html
index 4f7dde1..642e1be 100644
--- a/public/index.html
+++ b/public/index.html
@@ -22,9 +22,19 @@
   .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:right}
   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;overflow-x:auto}
+  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)}
+  }
   .breaking{background:var(--red);color:#fff;font-size:13px}
   .breaking .wrap{display:flex;align-items:center;gap:14px;height:40px;overflow:hidden}
   .breaking .tag{background:#fff;color:var(--red);font-weight:800;text-transform:uppercase;font-size:11px;letter-spacing:.08em;padding:3px 9px;border-radius:2px;flex:none}
@@ -81,8 +91,11 @@
     <div class="datebox"><span id="edition">Western U.S. Edition</span><br><span id="today"></span></div>
   </div></header>
 
-  <nav><div class="wrap" 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="#conf">Conferences</a>
+  <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="#conf">Conferences</a>
+    </div>
   </div></nav>
 
   <div class="breaking"><div class="wrap">
@@ -118,11 +131,12 @@
 document.getElementById('today').textContent = new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
 let ALL=[], cat='all';
 const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
+const bgimg=o=>(o&&o.image)?(' style="background:#12161a url(\''+esc(o.image)+'\') center/cover"'):'';
 function render(){
   const items = cat==='all'?ALL:ALL.filter(x=>x.cat===cat);
   const lead=items[0];
-  if(lead){document.getElementById('lead').innerHTML='<a href="/news/'+encodeURIComponent(lead.id)+'"><div class="img"></div><span class="chip">'+esc(lead.cat)+' · Western U.S.</span><h1>'+esc(lead.title)+'</h1><div class="byl">By RENTV Staff · live from rentv.com</div></a>';}
-  const cards=items.slice(1,13).map(x=>'<a class="card" href="/news/'+encodeURIComponent(x.id)+'"><div class="img"></div><span class="chip">'+esc(x.cat)+'</span><h4>'+esc(x.title)+'</h4><div class="byl">RENTV Staff</div></a>').join('');
+  if(lead){document.getElementById('lead').innerHTML='<a href="/news/'+encodeURIComponent(lead.id)+'"><div class="img"'+bgimg(lead)+'></div><span class="chip">'+esc(lead.cat)+' · Western U.S.</span><h1>'+esc(lead.title)+'</h1><div class="byl">By RENTV Staff · live from rentv.com</div></a>';}
+  const cards=items.slice(1,13).map(x=>'<a class="card" href="/news/'+encodeURIComponent(x.id)+'"><div class="img"'+bgimg(x)+'></div><span class="chip">'+esc(x.cat)+'</span><h4>'+esc(x.title)+'</h4><div class="byl">RENTV Staff</div></a>').join('');
   document.getElementById('cards').innerHTML=cards||'<p style="color:var(--sub)">No items in this section right now.</p>';
   document.getElementById('breaking').innerHTML=ALL.slice(0,6).map(x=>'<span>'+esc(x.title)+'</span>').join(' · ').repeat(2);
   document.getElementById('mostread').innerHTML=ALL.slice(0,5).map(x=>'<li><a href="/news/'+encodeURIComponent(x.id)+'">'+esc(x.title)+'</a></li>').join('');
@@ -138,7 +152,9 @@ async function load(){
     }
   }catch(e){document.getElementById('lead').querySelector('.byl').textContent='Live feed temporarily unavailable — retrying…';}
 }
-document.getElementById('nav').addEventListener('click',e=>{const a=e.target.closest('a[data-cat]');if(!a)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'});});
+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';}};
 async function loadTicker(){
   try{
diff --git a/public/section.html b/public/section.html
index f2fbb71..2dacce4 100644
--- a/public/section.html
+++ b/public/section.html
@@ -21,9 +21,19 @@
   .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:right}
   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;overflow-x:auto}
+  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)}
+  }
   .head{padding:30px 0 6px}
   .head .chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--red);margin-bottom:4px}
   .head h1{font-family:var(--serif);font-size:34px;letter-spacing:-.5px}
@@ -60,8 +70,11 @@
     <div class="datebox"><span>Western U.S. Edition</span><br><span id="today"></span></div>
   </div></header>
 
-  <nav><div class="wrap" 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>
+  <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>
+    </div>
   </div></nav>
 
   <div class="wrap">
@@ -97,6 +110,8 @@ document.getElementById('chip').textContent=CAT+' · Western U.S.';
 document.getElementById('crumbcat').textContent=CAT;
 // mark active nav
 document.querySelectorAll('#nav a').forEach(a=>{if(a.getAttribute('href')==='/'+slug)a.classList.add('on');});
+const hamBtn=document.getElementById('hamBtn');
+hamBtn.addEventListener('click',()=>{const open=document.getElementById('nav').classList.toggle('open');hamBtn.setAttribute('aria-expanded',open);});
 async function load(){
   try{
     const d=await (await fetch('/api/news')).json();
@@ -105,7 +120,7 @@ async function load(){
     if(!items.length){box.className='';box.innerHTML='<p class="empty">No '+esc(CAT)+' stories in the live feed right now. <a href="/" style="color:var(--red);font-weight:700">Back to the homepage →</a></p>';return;}
     document.getElementById('count').textContent=items.length+' live '+(items.length===1?'story':'stories');
     box.className='cards';
-    box.innerHTML=items.map(x=>'<a class="card" href="/news/'+encodeURIComponent(x.id)+'"><div class="img"></div><h4>'+esc(x.title)+'</h4><div class="byl">RENTV Staff · read in-shell</div></a>').join('');
+    box.innerHTML=items.map(x=>'<a class="card" href="/news/'+encodeURIComponent(x.id)+'"><div class="img"'+((x.image)?(' style="background:#12161a url(\''+esc(x.image)+'\') center/cover"'):'')+'></div><h4>'+esc(x.title)+'</h4><div class="byl">RENTV Staff · read in-shell</div></a>').join('');
   }catch(e){
     document.getElementById('cards').innerHTML='<p class="empty">Live feed temporarily unavailable — <a href="/" style="color:var(--red);font-weight:700">back to homepage</a>.</p>';
   }
diff --git a/scripts/pull-news.mjs b/scripts/pull-news.mjs
index 5e853f3..4a15bf2 100644
--- a/scripts/pull-news.mjs
+++ b/scripts/pull-news.mjs
@@ -18,6 +18,10 @@ const clean = (t) => t.replace(/&amp;/g,'&').replace(/&#39;/g,"'").replace(/&quo
 
 try {
   const html = await fetchDecoded('https://www.rentv.com/');
+  // Map each story id -> its real property photo (linked-image form: <a href='...news/ID'><img src='.../images/dynamic/news_*.jpg'>)
+  const imgMap = {};
+  const ire = /href=['"][^'"]*\/news\/(\d+)['"]\s*>\s*<img[^>]+src=['"]([^'"]+\/images\/dynamic\/news_[^'"]+\.(?:jpe?g|png))['"]/gi;
+  let im; while ((im = ire.exec(html))) { if (!imgMap[im[1]]) imgMap[im[1]] = im[2].startsWith('http') ? im[2] : 'https://www.rentv.com/' + im[2].replace(/^\//, ''); }
   const re = /<a[^>]+href="([^"]*content\/homepage\/mainnews\/news\/(\d+))"[^>]*>([^<]{20,160})<\/a>/gi;
   const seen = new Set(), items = []; let m;
   while ((m = re.exec(html)) && items.length < 30) {
@@ -32,7 +36,7 @@ try {
       : /res |multifamily|apartment|unit/.test(t) ? 'Multifamily'
       : /industrial|warehouse|logistics/.test(t) ? 'Industrial'
       : 'Sales';
-    items.push({ id, title, url: m[1].startsWith('http') ? m[1] : 'https://www.rentv.com/' + m[1].replace(/^\//,''), cat });
+    items.push({ id, title, url: m[1].startsWith('http') ? m[1] : 'https://www.rentv.com/' + m[1].replace(/^\//,''), cat, image: imgMap[id] || '' });
   }
   const payload = { source: 'rentv.com — live', fetched_at: new Date().toISOString(), count: items.length, items };
   writeFileSync(join(OUT, 'news.json'), JSON.stringify(payload, null, 1));
diff --git a/server.js b/server.js
index b9d494d..5ca272a 100644
--- a/server.js
+++ b/server.js
@@ -57,22 +57,26 @@ async function fetchArticle(id) {
   let html = buf.toString('utf8');
   if ((html.match(/�/g) || []).length > 5) html = buf.toString('latin1');
 
-  const og = (p) => { const m = html.match(new RegExp('<meta[^>]+property=["\']og:' + p + '["\'][^>]+content=["\']([^"\']+)', 'i')); return m ? decode(m[1]) : ''; };
-  let title = og('title') || (html.match(/<title>([^<]+)<\/title>/i)?.[1] || '').replace(/\s*[|\-–]\s*RENTV.*/i, '');
-  title = decode(title);
-  const image = og('image');
+  // RENTV is a classic server-rendered table site — the story lives in <td class="header"> + following cells,
+  // and the real property photo is a /images/dynamic/news_*.jpg. Extract those directly (no <p> tags exist).
+  const hM = html.match(/<td class="header"[^>]*>([\s\S]*?)<\/td>/i);
+  let title = hM ? stripTags(hM[1]) : (html.match(/<title>([^<]+)<\/title>/i)?.[1] || 'RENTV Story');
+  title = decode(title).replace(/\s*[|\-–]\s*RENTV.*/i, '') || 'RENTV Story';
 
-  // Body: grab the largest cluster of <p> blocks (the article copy).
-  const paras = [];
-  const pre = html.replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '');
-  let m; const pRe = /<p[^>]*>([\s\S]*?)<\/p>/gi;
-  while ((m = pRe.exec(pre))) {
-    const txt = stripTags(m[1]);
-    if (txt.length >= 60 && !/^(©|copyright|all rights|advertise|subscribe|follow us)/i.test(txt)) paras.push(txt);
-  }
-  // de-dupe + cap
+  const imM = html.match(/["'](\/?(?:https?:\/\/[^"']*)?\/?images\/dynamic\/news_\d+_[^"']+\.(?:jpe?g|png))["']/i);
+  let image = imM ? imM[1] : '';
+  if (image && !/^https?:/i.test(image)) image = 'https://www.rentv.com/' + image.replace(/^\//, '');
+
+  // Body: take the region from the headline to a trailing marker, split on <br><br> into real paragraphs.
+  const start = hM ? hM.index + hM[0].length : 0;
+  let region = html.slice(start);
+  const endM = region.search(/Return to|class="header-column"|Comment on this|Source:\s|id="footer"|<!--\s*footer/i);
+  if (endM > 0) region = region.slice(0, endM);
+  const paras = region.split(/<br\s*\/?>\s*(?:&nbsp;\s*)?<br\s*\/?>/i)
+    .map(chunk => stripTags(chunk).replace(/^\d{1,2}\/\d{1,2}\/\d{2,4}\s*/, '').trim())
+    .filter(t => t.length >= 40 && !/^(©|copyright|all rights|advertise|subscribe|follow us|return to)/i.test(t));
   const body = [...new Set(paras)].slice(0, 40);
-  return { id, url, title: title || 'RENTV Story', image, body, fetched_at: new Date().toISOString() };
+  return { id, url, title, image, body, fetched_at: new Date().toISOString() };
 }
 
 app.get('/api/article/:id', async (req, res) => {

← 2ed5be5 fork of rentv-v1 → independent rentv-website (port 9705); in  ·  back to Rentv Website  ·  center masthead wordmark (newspaper-style centered top) 0565bfb →