← back to Rentv 2026
public/2026/e.html
1413 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RENTV — CRE Market Dashboard</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:wght@700;800&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
/* ── TOKENS ── */
:root {
--bg: #0b0f16;
--surface: #131820;
--panel: #181e28;
--border: #252d3d;
--border-hi: #2f3a50;
--text: #e2e8f0;
--text-muted: #64748b;
--text-dim: #94a3b8;
--red: #c8102e;
--green: #22c55e;
--red-dn: #f04141;
--gold: #f0b429;
--blue: #3b82f6;
--mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', monospace;
--serif: 'Playfair Display', Georgia, serif;
--sans: 'Inter', system-ui, sans-serif;
--radius: 8px;
--radius-sm: 5px;
--ticker-h: 40px;
}
/* ── RESET ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 14px;
line-height: 1.5;
overflow-x: hidden;
}
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { cursor: pointer; font-family: inherit; }
/* ── UTILITY ── */
.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.up { color: var(--green); }
.dn { color: var(--red-dn); }
.flat { color: var(--text-dim); }
.gold { color: var(--gold); }
.red { color: var(--red); }
.sr-only { position: absolute; width: 1px; height: 1px; clip: rect(0,0,0,0); overflow: hidden; }
/* ── LIVE PULSE ── */
@keyframes pulse-ring {
0% { transform: scale(.8); opacity: .9; }
70% { transform: scale(1.6); opacity: 0; }
100% { transform: scale(.8); opacity: 0; }
}
.live-dot {
position: relative;
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 10px;
font-weight: 700;
letter-spacing: .08em;
color: var(--red);
text-transform: uppercase;
}
.live-dot::before {
content: '';
width: 8px; height: 8px;
border-radius: 50%;
background: var(--red);
display: inline-block;
flex-shrink: 0;
}
.live-dot::after {
content: '';
position: absolute;
left: 0; top: 50%;
transform: translateY(-50%);
width: 8px; height: 8px;
border-radius: 50%;
background: var(--red);
animation: pulse-ring 1.8s ease-out infinite;
}
/* ── TICKER STRIP ── */
#ticker-strip {
height: var(--ticker-h);
background: #0d1219;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
overflow: hidden;
position: relative;
}
#ticker-strip::before,
#ticker-strip::after {
content: '';
position: absolute;
top: 0; bottom: 0;
width: 60px;
z-index: 2;
pointer-events: none;
}
#ticker-strip::before { left: 0; background: linear-gradient(to right, #0d1219, transparent); }
#ticker-strip::after { right: 0; background: linear-gradient(to left, #0d1219, transparent); }
.ticker-label {
flex-shrink: 0;
padding: 0 16px;
font-size: 10px;
font-weight: 700;
letter-spacing: .1em;
color: var(--red);
text-transform: uppercase;
border-right: 1px solid var(--border);
height: 100%;
display: flex;
align-items: center;
gap: 8px;
z-index: 3;
background: #0d1219;
}
#ticker-track-wrap {
flex: 1;
overflow: hidden;
height: 100%;
display: flex;
align-items: center;
}
#ticker-track {
display: flex;
gap: 0;
white-space: nowrap;
will-change: transform;
}
.ticker-item {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 0 24px;
border-right: 1px solid var(--border);
height: var(--ticker-h);
font-family: var(--mono);
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.ticker-item .t-label { color: var(--text-muted); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }
.ticker-item .t-value { font-weight: 600; }
.ticker-item .t-chg { font-size: 11px; }
/* ── HEADER ── */
#site-header {
background: var(--surface);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 100;
}
.header-inner {
max-width: 1600px;
margin: 0 auto;
padding: 0 20px;
height: 56px;
display: flex;
align-items: center;
gap: 16px;
}
.header-left, .header-right {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
}
.header-right { justify-content: flex-end; }
.wordmark {
font-family: var(--serif);
font-size: 22px;
font-weight: 800;
letter-spacing: -.02em;
line-height: 1;
flex-shrink: 0;
}
.wordmark .tv { color: var(--red); }
.nav-btn {
background: none;
border: none;
color: var(--text-dim);
font-size: 13px;
font-weight: 500;
padding: 6px 10px;
border-radius: var(--radius-sm);
transition: color .15s, background .15s;
}
.nav-btn:hover { color: var(--text); background: var(--border); }
.advertise-btn {
background: var(--red);
color: #fff;
border: none;
border-radius: var(--radius-sm);
padding: 7px 14px;
font-size: 12px;
font-weight: 700;
letter-spacing: .04em;
text-transform: uppercase;
transition: opacity .15s;
text-decoration: none;
display: inline-block;
}
.advertise-btn:hover { opacity: .85; }
.hamburger-btn {
background: none;
border: none;
color: var(--text-dim);
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px;
border-radius: var(--radius-sm);
transition: color .15s;
}
.hamburger-btn:hover { color: var(--text); }
.hamburger-btn span {
display: block;
width: 18px;
height: 2px;
background: currentColor;
border-radius: 1px;
}
/* ── AD SLOT BANNER (LEADERBOARD) ── */
#ad-leaderboard {
max-width: 1600px;
margin: 0 auto;
padding: 12px 20px 0;
}
/* ── MAIN LAYOUT ── */
.dashboard-wrap {
max-width: 1600px;
margin: 0 auto;
padding: 16px 20px 80px;
display: grid;
grid-template-columns: 1fr 340px;
grid-template-rows: auto auto auto;
gap: 14px;
}
/* ── PANEL BASE ── */
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
gap: 10px;
}
.panel-title {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 8px;
}
.panel-title .icon { font-size: 12px; }
.panel-body { padding: 14px 16px; }
/* ── MARKET PULSE PANEL ── */
#panel-market {
grid-column: 1;
grid-row: 2;
}
.market-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 10px;
}
.kpi-tile {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 4px;
transition: border-color .15s;
}
.kpi-tile:hover { border-color: var(--border-hi); }
.kpi-label {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .09em;
color: var(--text-muted);
}
.kpi-value {
font-family: var(--mono);
font-size: 18px;
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 1.1;
}
.kpi-delta {
font-family: var(--mono);
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.kpi-source {
font-size: 9px;
color: var(--text-muted);
margin-top: 2px;
}
.section-divider {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--text-muted);
padding: 6px 0 4px;
border-top: 1px solid var(--border);
margin-top: 6px;
grid-column: 1 / -1;
}
/* ── LIVE VIDEO PANEL ── */
#panel-video {
grid-column: 1;
grid-row: 1;
}
.video-layout {
display: grid;
grid-template-columns: 1fr 240px;
gap: 0;
}
.video-main {
position: relative;
}
.video-embed-wrap {
position: relative;
padding-top: 56.25%;
background: #000;
}
.video-embed-wrap iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
}
.video-meta {
padding: 12px 16px;
border-top: 1px solid var(--border);
}
.video-cat {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--red);
margin-bottom: 4px;
}
.video-title {
font-family: var(--serif);
font-size: 16px;
font-weight: 700;
line-height: 1.3;
margin-bottom: 4px;
}
.video-desc {
font-size: 12px;
color: var(--text-dim);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-playlist {
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 340px;
}
.playlist-item {
display: flex;
gap: 0;
border-bottom: 1px solid var(--border);
transition: background .15s;
cursor: pointer;
}
.playlist-item:hover { background: var(--surface); }
.playlist-item.active { background: rgba(200,16,46,.08); border-left: 2px solid var(--red); }
.playlist-thumb {
width: 80px;
height: 52px;
object-fit: cover;
flex-shrink: 0;
background: var(--bg);
}
.playlist-thumb-placeholder {
width: 80px;
height: 52px;
flex-shrink: 0;
background: var(--bg);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: var(--text-muted);
}
.playlist-info {
padding: 6px 10px;
flex: 1;
min-width: 0;
}
.playlist-cat {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .07em;
color: var(--text-muted);
}
.playlist-title {
font-size: 11px;
font-weight: 600;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-top: 2px;
}
/* ── DEAL WIRE PANEL ── */
#panel-news {
grid-column: 2;
grid-row: 1 / 3;
}
.news-list { display: flex; flex-direction: column; }
.news-item {
display: flex;
gap: 10px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
transition: background .15s;
text-decoration: none;
color: inherit;
}
.news-item:hover { background: var(--surface); }
.news-item:last-child { border-bottom: none; }
.news-thumb {
width: 52px;
height: 38px;
object-fit: cover;
border-radius: 4px;
flex-shrink: 0;
background: var(--bg);
}
.news-thumb-placeholder {
width: 52px;
height: 38px;
border-radius: 4px;
flex-shrink: 0;
background: var(--border);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.news-content { flex: 1; min-width: 0; }
.news-cat {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .07em;
margin-bottom: 2px;
}
.news-headline {
font-size: 12px;
font-weight: 600;
line-height: 1.35;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Category color coding */
.cat-Multifamily { color: #60a5fa; }
.cat-Financing { color: var(--gold); }
.cat-Industrial { color: #a78bfa; }
.cat-Sales { color: var(--green); }
.cat-Office { color: #fb923c; }
.cat-Retail { color: #34d399; }
.cat-Featured { color: var(--red); }
.cat-default { color: var(--text-muted); }
/* ── AD SIDEBAR PANEL ── */
#panel-ad-sidebar {
grid-column: 2;
grid-row: 3;
}
/* ── VIDEO LIBRARY PANEL ── */
#panel-library {
grid-column: 1 / -1;
grid-row: 4;
}
/* Library controls */
.library-controls {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
.ctrl-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .07em;
color: var(--text-muted);
flex-shrink: 0;
}
.ctrl-select {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
font-family: var(--sans);
font-size: 12px;
font-weight: 500;
padding: 5px 10px;
cursor: pointer;
outline: none;
transition: border-color .15s;
appearance: none;
-webkit-appearance: none;
padding-right: 26px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' fill='none'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2364748b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
}
.ctrl-select:hover, .ctrl-select:focus { border-color: var(--border-hi); }
.density-wrap {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.density-icon { font-size: 13px; color: var(--text-muted); }
input[type=range].density-slider {
-webkit-appearance: none;
appearance: none;
width: 100px;
height: 4px;
background: var(--border);
border-radius: 2px;
outline: none;
cursor: pointer;
}
input[type=range].density-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px; height: 14px;
border-radius: 50%;
background: var(--red);
cursor: pointer;
transition: transform .1s;
}
input[type=range].density-slider::-webkit-slider-thumb:hover { transform: scale(1.2); }
input[type=range].density-slider::-moz-range-thumb {
width: 14px; height: 14px;
border-radius: 50%;
background: var(--red);
border: none;
cursor: pointer;
}
/* Video grid */
#video-grid {
display: grid;
gap: 12px;
margin-top: 14px;
--cols: 4;
grid-template-columns: repeat(var(--cols), 1fr);
}
.vid-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
overflow: hidden;
transition: border-color .15s, transform .15s;
display: flex;
flex-direction: column;
position: relative;
}
.vid-card:hover { border-color: var(--border-hi); transform: translateY(-2px); }
.vid-card-thumb-wrap {
position: relative;
padding-top: 56.25%;
background: var(--bg);
flex-shrink: 0;
}
.vid-card-thumb {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.vid-card-thumb-placeholder {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: var(--text-muted);
}
.vid-play-icon {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 36px; height: 36px;
border-radius: 50%;
background: rgba(0,0,0,.6);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity .15s;
}
.vid-play-icon::after {
content: '';
border-style: solid;
border-width: 7px 0 7px 13px;
border-color: transparent transparent transparent #fff;
margin-left: 2px;
}
.vid-card:hover .vid-play-icon { opacity: 1; }
.sponsored-flag {
position: absolute;
top: 8px; left: 8px;
background: var(--gold);
color: #000;
font-size: 9px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .07em;
padding: 2px 6px;
border-radius: 3px;
z-index: 2;
}
.sponsor-hover-link {
position: absolute;
top: 8px; right: 8px;
background: rgba(0,0,0,.75);
color: var(--gold);
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .06em;
padding: 3px 7px;
border-radius: 3px;
z-index: 2;
opacity: 0;
transition: opacity .15s;
text-decoration: none;
border: 1px solid rgba(240,180,41,.3);
}
.vid-card:hover .sponsor-hover-link { opacity: 1; }
.vid-card-body {
padding: 10px 12px;
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.vid-card-cat {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
}
.vid-card-title {
font-size: 12px;
font-weight: 600;
line-height: 1.35;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.vid-card-link {
margin-top: auto;
font-size: 10px;
color: var(--text-muted);
text-decoration: none;
padding-top: 4px;
}
.vid-card-link:hover { color: var(--red); }
/* ── AD SLOTS (styled as panels) ── */
[data-ad-slot] {
background: var(--panel);
border: 1px dashed var(--border-hi);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
min-height: 80px;
color: var(--text-muted);
font-size: 11px;
font-weight: 500;
letter-spacing: .04em;
text-align: center;
}
[data-ad-slot="home-leaderboard"] { min-height: 90px; width: 100%; }
[data-ad-slot="section-sidebar"] { min-height: 250px; }
[data-ad-slot="footer-band"] { min-height: 90px; }
/* ── FOOTER ── */
footer {
background: var(--surface);
border-top: 1px solid var(--border);
padding: 40px 20px 24px;
margin-top: 20px;
}
.footer-inner {
max-width: 1600px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr auto auto auto;
gap: 40px;
align-items: start;
}
.footer-brand .wordmark { font-size: 20px; margin-bottom: 8px; }
.footer-tagline { font-size: 12px; color: var(--text-muted); max-width: 240px; line-height: 1.5; }
.footer-col h4 {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .1em;
color: var(--text-muted);
margin-bottom: 12px;
}
.footer-col a {
display: block;
font-size: 13px;
color: var(--text-dim);
margin-bottom: 8px;
transition: color .15s;
}
.footer-col a:hover { color: var(--text); }
.footer-bottom {
max-width: 1600px;
margin: 24px auto 0;
padding-top: 16px;
border-top: 1px solid var(--border);
font-size: 11px;
color: var(--text-muted);
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
/* ── SWITCH CHIP ── */
.switch-chip {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--surface);
border: 1px solid var(--border-hi);
border-radius: 20px;
padding: 8px 14px;
font-size: 11px;
font-weight: 600;
color: var(--text-dim);
text-decoration: none;
z-index: 200;
transition: all .15s;
display: flex;
align-items: center;
gap: 6px;
backdrop-filter: blur(8px);
box-shadow: 0 4px 20px rgba(0,0,0,.4);
}
.switch-chip:hover { color: var(--text); border-color: var(--red); }
.switch-chip .chip-icon { color: var(--red); }
/* ── LOADING STATE ── */
.skeleton {
background: linear-gradient(90deg, var(--surface) 25%, var(--border) 50%, var(--surface) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-sm);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ── ERROR STATE ── */
.empty-state {
color: var(--text-muted);
font-size: 12px;
text-align: center;
padding: 20px;
}
/* ── RESPONSIVE ── */
@media (max-width: 1100px) {
.dashboard-wrap {
grid-template-columns: 1fr;
grid-template-rows: auto;
}
#panel-video { grid-column: 1; grid-row: 1; }
#panel-news { grid-column: 1; grid-row: 2; }
#panel-market { grid-column: 1; grid-row: 3; }
#panel-ad-sidebar { grid-column: 1; grid-row: 4; }
#panel-library { grid-column: 1; grid-row: 5; }
.video-layout { grid-template-columns: 1fr; }
.video-playlist { flex-direction: row; overflow-x: auto; overflow-y: hidden; max-height: none; border-left: none; border-top: 1px solid var(--border); }
.playlist-item { flex-direction: column; min-width: 140px; border-bottom: none; border-right: 1px solid var(--border); }
.playlist-thumb, .playlist-thumb-placeholder { width: 100%; height: 80px; }
#video-grid { --cols: 2; }
}
@media (max-width: 680px) {
.header-inner { padding: 0 12px; gap: 8px; }
.wordmark { font-size: 18px; }
.nav-btn { display: none; }
.dashboard-wrap { padding: 10px 12px 80px; gap: 10px; }
#video-grid { --cols: 1; }
.market-grid { grid-template-columns: repeat(2, 1fr); }
.footer-inner { grid-template-columns: 1fr 1fr; gap: 24px; }
.footer-brand { grid-column: 1 / -1; }
}
</style>
</head>
<body>
<!-- ── TICKER STRIP ── -->
<div id="ticker-strip" role="marquee" aria-label="CRE Market Ticker">
<div class="ticker-label">
<span class="live-dot">Live</span>
<span style="display:none" id="ticker-time"></span>
</div>
<div id="ticker-track-wrap">
<div id="ticker-track">
<div class="ticker-item">
<span class="t-label">Loading…</span>
</div>
</div>
</div>
</div>
<!-- ── HEADER ── -->
<header id="site-header">
<div class="header-inner">
<div class="header-left">
<button class="hamburger-btn" aria-label="Explore menu">
<span></span><span></span><span></span>
</button>
<button class="nav-btn" onclick="location='/'">Explore</button>
</div>
<a href="/" class="wordmark" aria-label="RENTV Home">REN<span class="tv">TV</span></a>
<div class="header-right">
<button class="nav-btn">Sections</button>
<a href="/advertise" class="advertise-btn" aria-label="Advertise on RENTV">Advertise →</a>
</div>
</div>
</header>
<!-- ── LEADERBOARD AD ── -->
<div id="ad-leaderboard">
<div data-ad-slot="home-leaderboard" aria-label="Advertisement"></div>
</div>
<!-- ── DASHBOARD MAIN ── -->
<main class="dashboard-wrap">
<!-- 1. LIVE VIDEO PANEL -->
<section class="panel" id="panel-video" aria-label="Featured Video">
<div class="panel-header">
<h2 class="panel-title">
<span class="icon">▶</span>
Live Video — The REview
<span class="live-dot" style="margin-left:8px">Live</span>
</h2>
<a href="https://www.rentvreview.com" target="_blank" rel="noopener" style="font-size:11px;color:var(--text-muted);transition:color .15s" onmouseover="this.style.color='var(--red)'" onmouseout="this.style.color='var(--text-muted)'">rentvreview.com →</a>
</div>
<div class="video-layout">
<div class="video-main">
<div class="video-embed-wrap" id="featured-embed-wrap">
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-size:13px;">Loading…</div>
</div>
<div class="video-meta" id="featured-meta">
<div class="video-cat" id="featured-cat"></div>
<div class="video-title" id="featured-title"></div>
<div class="video-desc" id="featured-desc"></div>
</div>
</div>
<div class="video-playlist" id="video-playlist"></div>
</div>
</section>
<!-- 2. DEAL WIRE PANEL -->
<section class="panel" id="panel-news" aria-label="Deal Wire">
<div class="panel-header">
<h2 class="panel-title"><span class="icon">◈</span>Deal Wire</h2>
<a href="/news" style="font-size:11px;color:var(--text-muted);transition:color .15s" onmouseover="this.style.color='var(--red)'" onmouseout="this.style.color='var(--text-muted)'">All deals →</a>
</div>
<div id="news-list" class="news-list" role="list">
<div class="empty-state">Loading…</div>
</div>
</section>
<!-- 3. MARKET PULSE PANEL -->
<section class="panel" id="panel-market" aria-label="Market Pulse">
<div class="panel-header">
<h2 class="panel-title"><span class="icon">◎</span>Market Pulse</h2>
<span class="mono" style="font-size:10px;color:var(--text-muted)" id="market-time"></span>
</div>
<div class="panel-body">
<div class="market-grid" id="market-grid">
<div class="empty-state" style="grid-column:1/-1">Loading…</div>
</div>
</div>
</section>
<!-- 4. AD SIDEBAR PANEL -->
<section class="panel" id="panel-ad-sidebar" style="padding:14px" aria-label="Advertisement">
<div data-ad-slot="section-sidebar" aria-label="Advertisement"></div>
</section>
<!-- 5. VIDEO LIBRARY PANEL -->
<section class="panel" id="panel-library" aria-label="Video Library">
<div class="panel-header">
<h2 class="panel-title"><span class="icon">⊞</span>Video Library — 155 Videos</h2>
<div class="library-controls" role="group" aria-label="Library display controls">
<label class="ctrl-label" for="lib-sort">Sort</label>
<select id="lib-sort" class="ctrl-select" aria-label="Sort videos">
<option value="newest">Newest</option>
<option value="views">Most Viewed</option>
<option value="cat">Category</option>
<option value="title">Title A→Z</option>
</select>
<div class="density-wrap">
<span class="density-icon" aria-hidden="true">⊟</span>
<input type="range" id="lib-density" class="density-slider" min="2" max="6" step="1" value="4" aria-label="Grid column density">
<span class="density-icon" aria-hidden="true">⊞</span>
</div>
</div>
</div>
<div class="panel-body" style="padding-top:0">
<div id="video-grid" role="list" aria-label="Video cards"></div>
<div id="lib-loading" class="empty-state">Loading library…</div>
</div>
</section>
</main>
<!-- ── FOOTER AD BAND ── -->
<div style="max-width:1600px;margin:0 auto;padding:0 20px">
<div data-ad-slot="footer-band" aria-label="Advertisement"></div>
</div>
<!-- ── FOOTER ── -->
<footer>
<div class="footer-inner">
<div class="footer-brand">
<div class="wordmark">REN<span class="tv">TV</span></div>
<p class="footer-tagline">Western U.S. commercial real estate news. The deals break here first — since 1998.</p>
</div>
<div class="footer-col">
<h4>Sections</h4>
<a href="/news">News</a>
<a href="/deals">Deals</a>
<a href="/financing">Financing</a>
<a href="/industrial">Industrial</a>
<a href="/multifamily">Multifamily</a>
</div>
<div class="footer-col">
<h4>Intelligence</h4>
<a href="/markets">Market Data</a>
<a href="/cre-talk">CRE Talk</a>
<a href="https://www.rentvreview.com" target="_blank" rel="noopener">The REview</a>
<a href="/conferences">Conferences</a>
</div>
<div class="footer-col">
<h4>Company</h4>
<a href="/about">About</a>
<a href="/advertise">Advertise</a>
<a href="/audience">Audience</a>
<a href="/contact">Contact</a>
</div>
</div>
<div class="footer-bottom">
<span>© 2026 <strong style="color:var(--text-dim)">RENTV</strong> — Western CRE Authority</span>
<a href="/advertise" style="color:var(--red);font-weight:600">Advertise on RENTV →</a>
</div>
</footer>
<!-- ── SWITCH CHIP ── -->
<a href="/2026/" class="switch-chip" aria-label="Switch to another 2026 template">
<span class="chip-icon">◆</span>
2026 Templates — switch
</a>
<!-- ── AD SYSTEM ── -->
<script src="/ad-slot.js" defer></script>
<!-- ── DASHBOARD SCRIPTS ── -->
<script>
(function () {
'use strict';
/* ─── HELPERS ─── */
function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({
'&':'&','<':'<','>':'>','"':'"',"'":'''
})[c]);
}
function dirClass(dir) {
if (dir === 'up') return 'up';
if (dir === 'down') return 'dn';
return 'flat';
}
function dirArrow(dir) {
if (dir === 'up') return '▲';
if (dir === 'down') return '▼';
return '—';
}
function catClass(cat) {
const map = {
Multifamily:'cat-Multifamily', Financing:'cat-Financing',
Industrial:'cat-Industrial', Sales:'cat-Sales',
Office:'cat-Office', Retail:'cat-Retail',
Featured:'cat-Featured'
};
return map[cat] || 'cat-default';
}
async function fetchJSON(url) {
try {
const r = await fetch(url);
if (!r.ok) throw new Error(r.status);
return await r.json();
} catch(e) {
console.warn('fetchJSON failed', url, e);
return null;
}
}
/* ─── TICKER ─── */
async function initTicker() {
const data = await fetchJSON('/api/ticker');
if (!data) return;
const items = Object.values(data).filter(v => v && v.label && v.value);
const track = document.getElementById('ticker-track');
track.innerHTML = '';
// Build items × 3 for seamless loop
const buildSet = () => items.map(item => {
const dc = dirClass(item.dir);
const arrow = dirArrow(item.dir);
const chg = item.change_pct != null
? `${item.change_pct > 0 ? '+' : ''}${item.change_pct}%`
: item.change_bps != null
? `${item.change_bps > 0 ? '+' : ''}${item.change_bps} bps`
: '';
return `<div class="ticker-item">
<span class="t-label">${esc(item.label)}</span>
<span class="t-value mono ${dc}">${esc(item.value)}</span>
${chg ? `<span class="t-chg mono ${dc}">${arrow} ${esc(chg)}</span>` : ''}
</div>`;
}).join('');
track.innerHTML = buildSet() + buildSet() + buildSet();
// Animate
let x = 0;
const singleW = track.scrollWidth / 3;
let last = null;
function tick(ts) {
if (last !== null) {
const dt = ts - last;
x += dt * 0.04; // px/ms speed
if (x >= singleW) x -= singleW;
track.style.transform = `translateX(-${x.toFixed(2)}px)`;
}
last = ts;
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
}
/* ─── MARKET PULSE ─── */
async function initMarket() {
const data = await fetchJSON('/api/markets');
const grid = document.getElementById('market-grid');
if (!data) { grid.innerHTML = '<div class="empty-state">Market data unavailable</div>'; return; }
const timeEl = document.getElementById('market-time');
if (data.fetched_at) {
const d = new Date(data.fetched_at);
timeEl.textContent = d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}) + ' · ' + (data.source || '');
}
let html = '';
// Treasury Curve
if (data.curve?.length) {
html += `<div class="section-divider">Treasury Curve</div>`;
data.curve.forEach(item => {
const dc = dirClass(item.dir);
const chg = item.change_bps != null ? `${item.change_bps > 0 ? '+' : ''}${item.change_bps} bps` : '';
html += `<div class="kpi-tile">
<div class="kpi-label">${esc(item.label)}</div>
<div class="kpi-value mono ${dc}">${esc(item.value)}</div>
${chg ? `<div class="kpi-delta mono ${dc}">${dirArrow(item.dir)} ${esc(chg)}</div>` : ''}
</div>`;
});
}
// Spreads
if (data.spreads?.length) {
html += `<div class="section-divider">Spreads</div>`;
data.spreads.forEach(item => {
const dc = dirClass(item.dir);
html += `<div class="kpi-tile">
<div class="kpi-label">${esc(item.label)}</div>
<div class="kpi-value mono ${dc}">${esc(item.value)}</div>
</div>`;
});
}
// REITs
if (data.reits?.length) {
html += `<div class="section-divider">REIT Indices</div>`;
data.reits.forEach(item => {
const dc = dirClass(item.dir);
const chg = item.change_pct != null ? `${item.change_pct > 0 ? '+' : ''}${item.change_pct}%` : '';
html += `<div class="kpi-tile">
<div class="kpi-label">${esc(item.label)}</div>
<div class="kpi-value mono ${dc}">${esc(item.value)}</div>
${chg ? `<div class="kpi-delta mono ${dc}">${dirArrow(item.dir)} ${esc(chg)}</div>` : ''}
</div>`;
});
}
// Fundamentals
if (data.fundamentals?.length) {
html += `<div class="section-divider">Fundamentals</div>`;
data.fundamentals.forEach(item => {
html += `<div class="kpi-tile">
<div class="kpi-label">${esc(item.label)}</div>
<div class="kpi-value mono">${esc(item.value)}</div>
${item.as_of ? `<div class="kpi-source">${esc(item.as_of)} · ${esc(item.source || '')}</div>` : ''}
</div>`;
});
}
grid.innerHTML = html || '<div class="empty-state">No data</div>';
}
/* ─── LIVE VIDEO ─── */
let allVideos = [];
async function initVideo() {
const data = await fetchJSON('/api/videos');
if (!data?.items) return;
allVideos = data.items.filter(v => v.embed || v.mp4);
// Move pinned to front
const pinned = allVideos.find(v => v.pinned);
if (pinned) {
allVideos = [pinned, ...allVideos.filter(v => v !== pinned)];
}
if (!allVideos.length) return;
// Render playlist (skip first 1 = featured)
renderPlaylist(allVideos);
setFeatured(allVideos[0], 0);
}
function renderPlaylist(videos) {
const container = document.getElementById('video-playlist');
container.innerHTML = videos.slice(0, 10).map((v, i) => {
const hasThumb = v.thumb && !v.thumb.includes('/media/originals/');
return `<div class="playlist-item ${i === 0 ? 'active' : ''}" data-idx="${i}" role="button" tabindex="0" aria-label="${esc(v.title)}">
${hasThumb
? `<img class="playlist-thumb" src="${esc(v.thumb)}" alt="" loading="lazy">`
: `<div class="playlist-thumb-placeholder">▶</div>`
}
<div class="playlist-info">
<div class="playlist-cat ${catClass(v.cat)}">${esc(v.cat || '')}</div>
<div class="playlist-title">${esc(v.title)}</div>
</div>
</div>`;
}).join('');
container.addEventListener('click', e => {
const item = e.target.closest('.playlist-item');
if (!item) return;
const idx = parseInt(item.dataset.idx, 10);
container.querySelectorAll('.playlist-item').forEach(el => el.classList.remove('active'));
item.classList.add('active');
setFeatured(videos[idx], idx);
});
container.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.target.closest('.playlist-item')?.click();
}
});
}
function setFeatured(video, idx) {
const wrap = document.getElementById('featured-embed-wrap');
const catEl = document.getElementById('featured-cat');
const titleEl = document.getElementById('featured-title');
const descEl = document.getElementById('featured-desc');
catEl.textContent = video.cat || '';
catEl.className = 'video-cat ' + catClass(video.cat);
titleEl.textContent = video.title || '';
descEl.textContent = video.desc || '';
if (video.embed) {
wrap.innerHTML = `<iframe
src="${esc(video.embed)}?rel=0&modestbranding=1"
title="${esc(video.title)}"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
loading="lazy"
></iframe>`;
} else if (video.mp4) {
wrap.innerHTML = `<video controls style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" preload="metadata">
<source src="${esc(video.mp4)}" type="video/mp4">
</video>`;
}
}
/* ─── DEAL WIRE ─── */
async function initNews() {
const data = await fetchJSON('/api/news');
const list = document.getElementById('news-list');
if (!data?.items?.length) { list.innerHTML = '<div class="empty-state">No deals found</div>'; return; }
const items = data.items.slice(0, 20);
list.innerHTML = items.map(item => {
const hasImg = item.image && !item.image.includes('/img/news/news_');
const localImg = item.image ? `https://www.rentv.com${item.image.startsWith('/') ? '' : '/'}${item.image}` : '';
// Use rentv.com origin for images since we don't have them locally
return `<a href="${esc(item.url || `/news/${item.id}`)}" class="news-item" target="_blank" rel="noopener" role="listitem" aria-label="${esc(item.title)}">
${item.image
? `<img class="news-thumb" src="${esc('https://www.rentv.com' + item.image)}" alt="" loading="lazy" onerror="this.style.display='none'">`
: `<div class="news-thumb-placeholder">◈</div>`
}
<div class="news-content">
<div class="news-cat ${catClass(item.cat)}">${esc(item.cat || '')}</div>
<div class="news-headline">${esc(item.title)}</div>
</div>
</a>`;
}).join('');
}
/* ─── VIDEO LIBRARY ─── */
let libItems = [];
let libSortBy = localStorage.getItem('lib-sort') || 'newest';
let libDensity = parseInt(localStorage.getItem('lib-density') || '4', 10);
async function initLibrary() {
const loadEl = document.getElementById('lib-loading');
const data = await fetchJSON('/api/ad/inventory');
if (!data?.items) { loadEl.textContent = 'Library unavailable.'; return; }
loadEl.style.display = 'none';
libItems = data.items;
// Restore controls
const sortEl = document.getElementById('lib-sort');
const densityEl = document.getElementById('lib-density');
sortEl.value = libSortBy;
densityEl.value = libDensity;
applyDensity(libDensity);
sortEl.addEventListener('change', () => {
libSortBy = sortEl.value;
localStorage.setItem('lib-sort', libSortBy);
renderLibrary();
});
densityEl.addEventListener('input', () => {
libDensity = parseInt(densityEl.value, 10);
localStorage.setItem('lib-density', libDensity);
applyDensity(libDensity);
});
renderLibrary();
}
function applyDensity(cols) {
document.getElementById('video-grid').style.setProperty('--cols', cols);
}
function sortedItems() {
const items = [...libItems];
switch (libSortBy) {
case 'views':
return items.sort((a, b) => (b.plays || 0) - (a.plays || 0));
case 'cat':
return items.sort((a, b) => (a.cat || '').localeCompare(b.cat || ''));
case 'title':
return items.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
case 'newest':
default:
return items.sort((a, b) => {
const da = a.date ? new Date(a.date) : 0;
const db = b.date ? new Date(b.date) : 0;
return db - da;
});
}
}
function renderLibrary() {
const grid = document.getElementById('video-grid');
const items = sortedItems();
if (!items.length) { grid.innerHTML = '<div class="empty-state">No videos found.</div>'; return; }
grid.innerHTML = items.map(item => {
const sp = item.sponsorship || {};
const isSold = sp.status === 'sold';
const advertiser = sp.advertiser || '';
const hasThumb = item.thumb && !item.thumb.startsWith('/media');
const linkUrl = item.view_url || (item.embed ? item.embed : '#');
return `<article class="vid-card" role="listitem">
<a href="${esc(linkUrl)}" target="_blank" rel="noopener" aria-label="${esc(item.title)}" tabindex="0">
<div class="vid-card-thumb-wrap">
${hasThumb
? `<img class="vid-card-thumb" src="${esc(item.thumb)}" alt="" loading="lazy" onerror="this.style.display='none'">`
: `<div class="vid-card-thumb-placeholder">▶</div>`
}
<div class="vid-play-icon" aria-hidden="true"></div>
${isSold
? `<div class="sponsored-flag">Sponsored · ${esc(advertiser)}</div>`
: `<a class="sponsor-hover-link" href="/advertise?video=${esc(item.uid)}" aria-label="Sponsor this video" tabindex="-1">Sponsor this →</a>`
}
</div>
<div class="vid-card-body">
<div class="vid-card-cat ${catClass(item.cat)}">${esc(item.cat || '')}</div>
<div class="vid-card-title">${esc(item.title)}</div>
</div>
</a>
${item.view_url && item.view_url !== linkUrl
? `<div style="padding:0 12px 10px"><a class="vid-card-link" href="${esc(item.view_url)}" target="_blank" rel="noopener">Watch on REview →</a></div>`
: ''
}
</article>`;
}).join('');
}
/* ─── BOOTSTRAP ─── */
document.addEventListener('DOMContentLoaded', () => {
initTicker();
initMarket();
initVideo();
initNews();
initLibrary();
});
})();
</script>
<script src="/2026/elements.js" defer></script>
<script src="/template-toggle.js" defer></script>
</body>
</html>