← back to Rentv 2026
blog: add sort (Newest/Oldest/Title/Category) + density slider to the RENTV Originals grid per the every-grid-gets-sort+density rule — posts load client-side in full so the sort is honest (whole-set reorder, not a page); refactored fetch→render, both controls persist to localStorage
fc0ee30d1aff3fad1f8e0281f7a8c2e89b2b3671 · 2026-08-05 16:59:17 -0700 · Steve
Files touched
Diff
commit fc0ee30d1aff3fad1f8e0281f7a8c2e89b2b3671
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 16:59:17 2026 -0700
blog: add sort (Newest/Oldest/Title/Category) + density slider to the RENTV Originals grid per the every-grid-gets-sort+density rule — posts load client-side in full so the sort is honest (whole-set reorder, not a page); refactored fetch→render, both controls persist to localStorage
---
public/blog.html | 50 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 44 insertions(+), 6 deletions(-)
diff --git a/public/blog.html b/public/blog.html
index 1ffc0eba..90a2f20c 100644
--- a/public/blog.html
+++ b/public/blog.html
@@ -9,7 +9,12 @@ a{color:inherit;text-decoration:none}.wrap{max-width:1100px;margin:0 auto;paddin
.mast{border-bottom:2px solid var(--ink);padding:18px 0 12px;text-align:center}.logo{font-family:'Playfair Display',serif;font-weight:700;font-size:34px;letter-spacing:-1px}.logo span{color:var(--red)}
.logo small{display:block;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:var(--sub);margin-top:2px}
.hd{padding:26px 0 6px}.hd h1{font-family:'Playfair Display',serif;font-size:32px}.hd p{color:var(--sub);margin-top:4px}
-.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:22px;padding:22px 0 60px}
+.controls{display:flex;justify-content:flex-end;align-items:center;gap:16px;flex-wrap:wrap;padding:6px 0 0}
+.controls label{display:inline-flex;align-items:center;gap:7px;font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--sub)}
+.controls select{font:600 13px 'Inter';padding:7px 10px;border:1.5px solid var(--line);border-radius:8px;background:#fff;color:var(--ink);cursor:pointer}
+.controls select:focus{outline:0;border-color:var(--red)}
+.controls input[type=range]{width:104px;accent-color:var(--red);cursor:pointer}
+.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--min,300px),1fr));gap:22px;padding:16px 0 60px}
.pc{border:1px solid var(--line);border-radius:8px;overflow:hidden;display:block;transition:.2s}.pc:hover{transform:translateY(-4px);box-shadow:0 16px 40px rgba(0,0,0,.08)}
.pc .img{aspect-ratio:16/9;background:linear-gradient(135deg,#475569,#c8102e)}.pc .img img{width:100%;height:100%;object-fit:cover;display:block}
.pc .b{padding:16px 18px}.pc .chip{font-size:11px;font-weight:800;text-transform:uppercase;color:var(--red)}.pc h3{font-family:'Playfair Display',serif;font-size:20px;margin:6px 0}.pc .dek{font-size:14px;color:var(--sub)}.pc .byl{font-size:12px;color:var(--sub);margin-top:8px}
@@ -19,14 +24,47 @@ footer{border-top:1px solid var(--line);color:var(--sub);font-size:12px;padding:
<div class="util"><div class="wrap"><a href="/">RENTV.com · Commercial Real Estate News</a><a href="/admin/publish">Publish →</a></div></div>
<header class="mast"><div class="wrap"><a class="logo" href="/">REN<span>TV</span>.com<small>RENTV Originals</small></a></div></header>
<div class="wrap"><div class="hd"><h1>RENTV Originals</h1><p>Original reporting and analysis from the RENTV newsroom.</p></div>
+<div class="controls">
+ <label>Sort
+ <select id="sort">
+ <option value="newest">Newest</option>
+ <option value="oldest">Oldest</option>
+ <option value="title">Title A–Z</option>
+ <option value="cat">Category</option>
+ </select>
+ </label>
+ <label>Density <input id="dens" type="range" min="1" max="4" step="1" value="2" aria-label="Grid density (card size)"></label>
+</div>
<div class="grid" id="grid"><p class="empty">Loading…</p></div></div>
<footer><div class="wrap">RENTV.com · <a href="/" style="color:var(--red)">back to the front page</a></div></footer>
<script>
var esc=function(s){return (s||'').replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});};
+var $=function(id){return document.getElementById(id);};
+// Every grid gets sort + density (Steve's standing rule). Posts load client-side in full,
+// so sorting is honest — it reorders the whole set, not just a visible page.
+var ALL=[],
+ sortKey=localStorage.getItem('rentv.blog.sort')||'newest',
+ dens=String(Math.max(1,Math.min(4,parseInt(localStorage.getItem('rentv.blog.dens'),10)||2))),
+ MINW={'1':'380px','2':'320px','3':'270px','4':'235px'}; // higher slider = smaller min = denser
+function applyDens(){ $('grid').style.setProperty('--min', MINW[dens]||'320px'); }
+function sortItems(a){ var r=a.slice();
+ if(sortKey==='oldest') r.sort(function(x,y){return (x.created_at||'').localeCompare(y.created_at||'');});
+ else if(sortKey==='title') r.sort(function(x,y){return (x.title||'').localeCompare(y.title||'');});
+ else if(sortKey==='cat') r.sort(function(x,y){return (x.cat||'').localeCompare(y.cat||'')||(y.created_at||'').localeCompare(x.created_at||'');});
+ else r.sort(function(x,y){return (y.created_at||'').localeCompare(x.created_at||'');}); // newest (default)
+ return r;
+}
+function card(p){var img=p.image?'<div class="img"><img src="'+esc(p.image)+'" alt="" onerror="this.parentNode.style.background=\'linear-gradient(135deg,#475569,#c8102e)\';this.remove()"></div>':'<div class="img"></div>';
+ return '<a class="pc" href="/post/'+p.id+'">'+img+'<div class="b"><span class="chip">'+esc(p.cat)+'</span><h3>'+esc(p.title)+'</h3>'+(p.dek?'<div class="dek">'+esc(p.dek)+'</div>':'')+'<div class="byl">By '+esc(p.author)+' · '+esc((p.created_at||'').slice(0,10))+'</div></div></a>';}
+function render(){ var g=$('grid');
+ if(!ALL.length){g.innerHTML='<p class="empty">No published articles yet. <a href="/admin/publish" style="color:var(--red)">Publish the first →</a></p>';return;}
+ g.innerHTML=sortItems(ALL).map(card).join('');
+}
+$('sort').value=sortKey;
+$('dens').value=dens; applyDens();
+$('sort').onchange=function(e){sortKey=e.target.value;localStorage.setItem('rentv.blog.sort',sortKey);render();};
+$('dens').oninput=function(e){dens=e.target.value;localStorage.setItem('rentv.blog.dens',dens);applyDens();};
fetch('/api/posts?status=published').then(function(r){return r.json();}).then(function(d){
- var g=document.getElementById('grid'),items=d.items||[];
- if(!items.length){g.innerHTML='<p class="empty">No published articles yet. <a href="/admin/publish" style="color:var(--red)">Publish the first →</a></p>';return;}
- g.innerHTML=items.map(function(p){var img=p.image?'<div class="img"><img src="'+esc(p.image)+'" alt="" onerror="this.parentNode.style.background=\'linear-gradient(135deg,#475569,#c8102e)\';this.remove()"></div>':'<div class="img"></div>';
- return '<a class="pc" href="/post/'+p.id+'">'+img+'<div class="b"><span class="chip">'+esc(p.cat)+'</span><h3>'+esc(p.title)+'</h3>'+(p.dek?'<div class="dek">'+esc(p.dek)+'</div>':'')+'<div class="byl">By '+esc(p.author)+' · '+esc((p.created_at||'').slice(0,10))+'</div></div></a>';}).join('');
-}).catch(function(){document.getElementById('grid').innerHTML='<p class="empty">Blog temporarily unavailable.</p>';});
+ ALL=d.items||[]; render();
+}).catch(function(){$('grid').innerHTML='<p class="empty">Blog temporarily unavailable.</p>';});
</script></body></html>
← 050f7edc PR intel: add Tavily as a 3rd free-tier search backend + PR_
·
back to Rentv 2026
·
blog density: Cody gate — hide the density slider on phones b69f5973 →