← back to Rentv 2026
RENTV 2026: 5-template selector + ad-slots-for-sale system (all videos as sponsorable inventory)
827eb166af1f797f356f8ccb98a7cc2aacd63830 · 2026-08-07 11:23:45 -0700 · Steve (RENTV 2026)
- 5 distinct video-forward homepage templates (public/2026/{a..e}.html): Video Wall,
Editorial Lead, Cinematic, Magazine, Dashboard — each self-contained, live data,
sort+density on every grid, ad slots served in-context.
- Selector modal (public/2026/index.html): live iframe previews, full-screen switcher
with device toggle, 'Make this the site' -> data/site-template.json (admin-gated, local pick only).
- Ad-slots system (src/ad-system.cjs, one-line mount in server.js): banner/newsletter/video-surface
rate card (data/ad-slots.json) + ALL 154 RENTV videos pulled into sponsorable inventory live
(youtube+vimeo+originals+news-wire), monthly pricing, availability, reserve-inquiry flow (NO charge),
admin state, reservations queue.
- Rebuilt /advertise into a slots-for-sale marketplace; /ad-admin management surface
(created date+time chips, toggleable columns); /ad-slot.js on-page banner serving.
Local build in isolated worktree; prod deploy stays claude-rentv/Steve gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M .gitignoreA data/ad-slots.jsonA public/2026/a.htmlA public/2026/b.htmlA public/2026/c.htmlA public/2026/d.htmlA public/2026/e.htmlA public/2026/index.htmlA public/ad-admin.htmlA public/ad-slot.jsM public/advertise.htmlM server.jsA src/ad-system.cjs
Diff
commit 827eb166af1f797f356f8ccb98a7cc2aacd63830
Author: Steve (RENTV 2026) <steve@designerwallcoverings.com>
Date: Fri Aug 7 11:23:45 2026 -0700
RENTV 2026: 5-template selector + ad-slots-for-sale system (all videos as sponsorable inventory)
- 5 distinct video-forward homepage templates (public/2026/{a..e}.html): Video Wall,
Editorial Lead, Cinematic, Magazine, Dashboard — each self-contained, live data,
sort+density on every grid, ad slots served in-context.
- Selector modal (public/2026/index.html): live iframe previews, full-screen switcher
with device toggle, 'Make this the site' -> data/site-template.json (admin-gated, local pick only).
- Ad-slots system (src/ad-system.cjs, one-line mount in server.js): banner/newsletter/video-surface
rate card (data/ad-slots.json) + ALL 154 RENTV videos pulled into sponsorable inventory live
(youtube+vimeo+originals+news-wire), monthly pricing, availability, reserve-inquiry flow (NO charge),
admin state, reservations queue.
- Rebuilt /advertise into a slots-for-sale marketplace; /ad-admin management surface
(created date+time chips, toggleable columns); /ad-slot.js on-page banner serving.
Local build in isolated worktree; prod deploy stays claude-rentv/Steve gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
.gitignore | 5 +
data/ad-slots.json | 13 +
public/2026/a.html | 1350 ++++++++++++++++++++++++++++++++++++++
public/2026/b.html | 1471 +++++++++++++++++++++++++++++++++++++++++
public/2026/c.html | 1094 +++++++++++++++++++++++++++++++
public/2026/d.html | 1713 ++++++++++++++++++++++++++++++++++++++++++++++++
public/2026/e.html | 1410 +++++++++++++++++++++++++++++++++++++++
public/2026/index.html | 209 ++++++
public/ad-admin.html | 196 ++++++
public/ad-slot.js | 65 ++
public/advertise.html | 448 ++++++++-----
server.js | 6 +
src/ad-system.cjs | 290 ++++++++
13 files changed, 8123 insertions(+), 147 deletions(-)
diff --git a/.gitignore b/.gitignore
index eda8a131..b5b95ffa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,8 @@ ops/*.err
# Generated Boomer audio briefs (regenerable, rsync-excluded like other runtime data)
public/audio/
+
+# RENTV ad-system runtime state (not source)
+data/ad-reservations.jsonl
+data/ad-state.json
+data/site-template.json
diff --git a/data/ad-slots.json b/data/ad-slots.json
new file mode 100644
index 00000000..40a1d0fe
--- /dev/null
+++ b/data/ad-slots.json
@@ -0,0 +1,13 @@
+{
+ "updated_at": "2026-08-07",
+ "currency": "USD",
+ "note": "RENTV 2026 ad-slots rate card. Banner + newsletter + video-surface placements advertisers buy monthly. Video-sponsorship inventory (every RENTV video) is generated live by src/ad-system.cjs and merged at /api/ad/inventory. status: available | reserved | sold. flight = {start:'YYYY-MM', months:N}. price_monthly in USD. Real checkout/charge is intentionally NOT wired — /api/ad/reserve records an inquiry only; taking payment is Steve-gated.",
+ "placements": [
+ { "id": "home-leaderboard", "name": "Homepage Leaderboard", "surface": "Homepage", "position": "Top of homepage, above the fold", "format": "970×90 / responsive banner", "type": "banner", "price_monthly": 3500, "impressions_est": "~45k/mo", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" },
+ { "id": "home-infeed-1", "name": "Homepage In-Feed (Across the Market)", "surface": "Homepage", "position": "Inside the news river, slot 3", "format": "In-feed native card 300×250", "type": "banner", "price_monthly": 2200, "impressions_est": "~30k/mo", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" },
+ { "id": "section-sidebar", "name": "Section Sidebar Rectangle", "surface": "Section pages", "position": "Right rail on Sales / Leases / Financing / etc.", "format": "300×250 medium rectangle", "type": "banner", "price_monthly": 1500, "impressions_est": "~22k/mo", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" },
+ { "id": "review-masthead", "name": "The REview — Video Wall Masthead", "surface": "The REview", "position": "Top banner on the video wall (rentvreview coordinate)", "format": "970×250 billboard", "type": "banner", "price_monthly": 2800, "impressions_est": "~18k/mo", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" },
+ { "id": "newsletter-top", "name": "RENTV Daily — Top Banner", "surface": "Newsletter", "position": "Top-of-inbox in the daily CRE briefing", "format": "600×150 email banner", "type": "newsletter", "price_monthly": 4000, "impressions_est": "daily send", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" },
+ { "id": "footer-band", "name": "Site-Wide Footer Band", "surface": "All pages", "position": "Above the footer on every page", "format": "728×90 leaderboard", "type": "banner", "price_monthly": 1200, "impressions_est": "~60k/mo", "status": "available", "advertiser": null, "flight": null, "creative_url": null, "click_url": null, "created_at": "2026-08-07T00:00:00Z" }
+ ]
+}
diff --git a/public/2026/a.html b/public/2026/a.html
new file mode 100644
index 00000000..952f4f0c
--- /dev/null
+++ b/public/2026/a.html
@@ -0,0 +1,1350 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>RENTV · The Video Wall — 2026</title>
+<meta name="description" content="Western U.S. commercial real estate video — The Video Wall. News, deals, interviews and market intelligence from RENTV.">
+<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&display=swap" rel="stylesheet">
+<style>
+/* ─── TOKENS ─────────────────────────────────────────────────────────── */
+:root {
+ --ink: #14171a;
+ --sub: #5b636b;
+ --hairline: #e4e7ea;
+ --red: #c8102e;
+ --gold: #c9a227;
+ --bg: #f7f8fa;
+ --card-bg: #ffffff;
+ --header-h: 56px;
+ --col-min: 260px;
+ --radius: 6px;
+ --shadow: 0 2px 12px rgba(0,0,0,.08);
+ --shadow-lg: 0 8px 32px rgba(0,0,0,.18);
+}
+
+/* ─── RESET ──────────────────────────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+html { font-size: 16px; scroll-behavior: smooth; }
+body {
+ font-family: 'Inter', system-ui, sans-serif;
+ color: var(--ink);
+ background: var(--bg);
+ min-height: 100vh;
+}
+a { color: inherit; text-decoration: none; }
+button { font-family: inherit; cursor: pointer; border: none; background: none; }
+img { display: block; }
+
+/* ─── HEADER ─────────────────────────────────────────────────────────── */
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 200;
+ background: #fff;
+ border-bottom: 1px solid var(--hairline);
+ height: var(--header-h);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 20px;
+ gap: 12px;
+}
+.hdr-left, .hdr-right {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ min-width: 120px;
+}
+.hdr-right { justify-content: flex-end; }
+.hdr-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--sub);
+ padding: 6px 10px;
+ border-radius: 4px;
+ transition: background .15s, color .15s;
+ white-space: nowrap;
+}
+.hdr-btn:hover { background: var(--bg); color: var(--ink); }
+.hdr-btn svg { flex-shrink: 0; }
+.wordmark {
+ font-family: 'Playfair Display', serif;
+ font-size: 26px;
+ font-weight: 800;
+ letter-spacing: -0.5px;
+ line-height: 1;
+ color: var(--ink);
+}
+.wordmark span { color: var(--red); }
+.advertise-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--red);
+ border: 1.5px solid var(--red);
+ padding: 5px 12px;
+ border-radius: 4px;
+ letter-spacing: .3px;
+ transition: background .15s, color .15s;
+}
+.advertise-btn:hover { background: var(--red); color: #fff; }
+
+/* ─── AD SLOTS ───────────────────────────────────────────────────────── */
+.ad-strip {
+ background: #fff;
+ border-bottom: 1px solid var(--hairline);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 10px 20px;
+ min-height: 52px;
+}
+
+/* ─── HERO / FEATURED VIDEO ──────────────────────────────────────────── */
+.hero-section {
+ background: var(--ink);
+ position: relative;
+ overflow: hidden;
+}
+.hero-inner {
+ max-width: 1280px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 1fr 340px;
+ gap: 0;
+ min-height: 420px;
+}
+.hero-video-wrap {
+ position: relative;
+ background: #000;
+ cursor: pointer;
+ overflow: hidden;
+}
+.hero-video-wrap img.hero-thumb {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ opacity: .85;
+ transition: opacity .3s;
+}
+.hero-video-wrap:hover img.hero-thumb { opacity: .7; }
+.hero-play {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+}
+.hero-play-circle {
+ width: 72px;
+ height: 72px;
+ background: rgba(200,16,46,.9);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 24px rgba(200,16,46,.4);
+ transition: transform .2s, background .2s;
+}
+.hero-video-wrap:hover .hero-play-circle {
+ transform: scale(1.1);
+ background: var(--red);
+}
+.hero-play-circle svg { margin-left: 4px; }
+.hero-badge {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+ background: var(--red);
+ color: #fff;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 1.2px;
+ text-transform: uppercase;
+ padding: 4px 10px;
+ border-radius: 3px;
+}
+.hero-meta {
+ padding: 28px 28px 28px 28px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ color: #fff;
+}
+.hero-cat {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 1.5px;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 10px;
+}
+.hero-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 22px;
+ font-weight: 700;
+ line-height: 1.3;
+ color: #fff;
+ margin-bottom: 12px;
+}
+.hero-desc {
+ font-size: 13px;
+ color: rgba(255,255,255,.65);
+ line-height: 1.6;
+ flex: 1;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 5;
+ -webkit-box-orient: vertical;
+}
+.hero-cta {
+ margin-top: 20px;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ font-weight: 700;
+ color: #fff;
+ background: var(--red);
+ padding: 9px 18px;
+ border-radius: 4px;
+ align-self: flex-start;
+ transition: background .15s, transform .15s;
+}
+.hero-cta:hover { background: #a60d26; transform: translateY(-1px); }
+.hero-sponsor-strip {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: rgba(0,0,0,.55);
+ padding: 8px 16px;
+ font-size: 11px;
+ color: rgba(255,255,255,.6);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+/* ─── WALL MASTHEAD AD ───────────────────────────────────────────────── */
+.masthead-ad {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: #fff;
+ border-top: 1px solid var(--hairline);
+ border-bottom: 1px solid var(--hairline);
+ padding: 10px 20px;
+ min-height: 52px;
+}
+
+/* ─── CONTROLS BAR ───────────────────────────────────────────────────── */
+.controls-bar {
+ background: #fff;
+ border-bottom: 1px solid var(--hairline);
+ padding: 0 20px;
+ position: sticky;
+ top: var(--header-h);
+ z-index: 100;
+}
+.controls-inner {
+ max-width: 1280px;
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ flex-wrap: wrap;
+}
+/* Category chips */
+.cat-chips {
+ display: flex;
+ gap: 6px;
+ overflow-x: auto;
+ flex: 1;
+ scrollbar-width: none;
+ -webkit-overflow-scrolling: touch;
+}
+.cat-chips::-webkit-scrollbar { display: none; }
+.chip {
+ flex-shrink: 0;
+ font-size: 12px;
+ font-weight: 600;
+ padding: 5px 13px;
+ border-radius: 20px;
+ border: 1.5px solid var(--hairline);
+ color: var(--sub);
+ cursor: pointer;
+ transition: all .15s;
+ white-space: nowrap;
+ background: transparent;
+}
+.chip:hover { border-color: var(--ink); color: var(--ink); }
+.chip.active { background: var(--ink); color: #fff; border-color: var(--ink); }
+/* Sort + density */
+.wall-controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-shrink: 0;
+}
+.sort-select {
+ font-family: 'Inter', sans-serif;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--ink);
+ border: 1.5px solid var(--hairline);
+ border-radius: 4px;
+ padding: 5px 28px 5px 10px;
+ background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%235b636b'/%3E%3C/svg%3E") no-repeat right 8px center;
+ appearance: none;
+ cursor: pointer;
+ transition: border-color .15s;
+}
+.sort-select:hover, .sort-select:focus { border-color: var(--ink); outline: none; }
+.density-group {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--sub);
+}
+.density-group svg { flex-shrink: 0; }
+#density-slider {
+ width: 80px;
+ height: 4px;
+ accent-color: var(--ink);
+ cursor: pointer;
+}
+.result-count {
+ font-size: 12px;
+ color: var(--sub);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+/* ─── VIDEO WALL ─────────────────────────────────────────────────────── */
+.wall-section {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 24px 20px 48px;
+}
+.video-wall {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(var(--col-min), 1fr));
+ gap: 20px;
+}
+
+/* ─── VIDEO CARD ─────────────────────────────────────────────────────── */
+.video-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ cursor: pointer;
+ transition: transform .2s, box-shadow .2s;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+.video-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
+.video-card:hover .card-play-btn { opacity: 1; transform: translate(-50%,-50%) scale(1); }
+.card-thumb-wrap {
+ position: relative;
+ padding-top: 56.25%;
+ background: linear-gradient(135deg, #1a2332 0%, #2d3e55 60%, var(--ink) 100%);
+ overflow: hidden;
+}
+.card-thumb {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+.card-thumb-fallback {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 16px;
+ text-align: center;
+}
+.card-thumb-fallback span {
+ font-family: 'Playfair Display', serif;
+ font-size: 14px;
+ font-weight: 700;
+ color: rgba(255,255,255,.5);
+ line-height: 1.4;
+}
+.card-play-btn {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%,-50%) scale(.9);
+ width: 44px;
+ height: 44px;
+ background: rgba(200,16,46,.85);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ opacity: 0;
+ transition: opacity .2s, transform .2s;
+}
+.card-play-btn svg { margin-left: 3px; }
+.card-source-badge {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: .8px;
+ text-transform: uppercase;
+ padding: 3px 7px;
+ border-radius: 3px;
+ background: rgba(0,0,0,.65);
+ color: rgba(255,255,255,.9);
+ backdrop-filter: blur(2px);
+}
+.source-youtube { background: rgba(255,0,0,.75); }
+.source-vimeo { background: rgba(26,183,234,.75); }
+.source-original { background: rgba(200,16,46,.85); }
+.source-newswire { background: rgba(50,50,50,.75); }
+.sponsored-ribbon {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: var(--gold);
+ color: #fff;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 1px;
+ text-transform: uppercase;
+ padding: 3px 8px;
+}
+.card-body {
+ padding: 13px 14px 14px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.card-cat-chip {
+ display: inline-flex;
+ align-items: center;
+ align-self: flex-start;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .8px;
+ text-transform: uppercase;
+ color: var(--red);
+ padding: 2px 8px;
+ border: 1px solid rgba(200,16,46,.25);
+ border-radius: 3px;
+ cursor: pointer;
+ transition: background .15s;
+}
+.card-cat-chip:hover { background: rgba(200,16,46,.07); }
+.card-title {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.4;
+ color: var(--ink);
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.card-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: auto;
+ padding-top: 6px;
+}
+.card-plays {
+ font-size: 11px;
+ color: var(--sub);
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+.card-sponsor-cta {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--sub);
+ opacity: 0;
+ transition: opacity .2s;
+ text-decoration: none;
+ border-bottom: 1px dashed var(--sub);
+}
+.video-card:hover .card-sponsor-cta { opacity: 1; }
+
+/* ─── SKELETON LOADER ────────────────────────────────────────────────── */
+.skeleton {
+ background: linear-gradient(90deg, #e8eaed 25%, #f3f4f6 50%, #e8eaed 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.4s infinite;
+ border-radius: var(--radius);
+}
+@keyframes shimmer { to { background-position: -200% 0; } }
+.skel-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ overflow: hidden;
+ box-shadow: var(--shadow);
+}
+.skel-thumb { padding-top: 56.25%; position: relative; }
+.skel-thumb-inner { position: absolute; inset: 0; }
+.skel-body { padding: 14px; display: flex; flex-direction: column; gap: 8px; }
+.skel-chip { height: 16px; width: 60px; border-radius: 3px; }
+.skel-line { height: 12px; border-radius: 3px; }
+
+/* ─── MODAL ──────────────────────────────────────────────────────────── */
+.modal-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,.88);
+ z-index: 500;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+.modal-overlay.open { display: flex; }
+.modal-box {
+ width: 100%;
+ max-width: 960px;
+ background: #000;
+ border-radius: 8px;
+ overflow: hidden;
+ position: relative;
+ box-shadow: 0 24px 80px rgba(0,0,0,.8);
+}
+.modal-close {
+ position: absolute;
+ top: -40px;
+ right: 0;
+ color: rgba(255,255,255,.8);
+ font-size: 28px;
+ line-height: 1;
+ z-index: 10;
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: color .15s, background .15s;
+ cursor: pointer;
+}
+.modal-close:hover { color: #fff; background: rgba(255,255,255,.15); }
+.modal-video-wrap {
+ position: relative;
+ padding-top: 56.25%;
+ background: #000;
+}
+.modal-video-wrap iframe,
+.modal-video-wrap video {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+.modal-info {
+ padding: 18px 20px;
+ background: var(--ink);
+ color: #fff;
+}
+.modal-cat {
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 1.2px;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 6px;
+}
+.modal-title {
+ font-family: 'Playfair Display', serif;
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 1.3;
+ margin-bottom: 6px;
+}
+.modal-desc {
+ font-size: 13px;
+ color: rgba(255,255,255,.6);
+ line-height: 1.6;
+}
+.modal-actions {
+ display: flex;
+ gap: 12px;
+ margin-top: 14px;
+ flex-wrap: wrap;
+}
+.modal-action-btn {
+ font-size: 12px;
+ font-weight: 600;
+ padding: 7px 14px;
+ border-radius: 4px;
+ transition: background .15s;
+ cursor: pointer;
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+.modal-action-btn.primary { background: var(--red); color: #fff; }
+.modal-action-btn.primary:hover { background: #a60d26; }
+.modal-action-btn.secondary { background: rgba(255,255,255,.12); color: rgba(255,255,255,.85); }
+.modal-action-btn.secondary:hover { background: rgba(255,255,255,.2); }
+
+/* ─── FOOTER ─────────────────────────────────────────────────────────── */
+.site-footer {
+ background: var(--ink);
+ color: rgba(255,255,255,.6);
+ padding: 48px 20px 28px;
+}
+.footer-inner {
+ max-width: 1280px;
+ margin: 0 auto;
+}
+.footer-top {
+ display: grid;
+ grid-template-columns: 200px repeat(3, 1fr);
+ gap: 40px;
+ padding-bottom: 36px;
+ border-bottom: 1px solid rgba(255,255,255,.1);
+ margin-bottom: 24px;
+}
+.footer-brand .wordmark { font-size: 28px; color: #fff; margin-bottom: 12px; }
+.footer-brand .wordmark span { color: var(--red); }
+.footer-tagline {
+ font-size: 12px;
+ line-height: 1.6;
+ color: rgba(255,255,255,.45);
+ margin-bottom: 16px;
+}
+.footer-advertise {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--red);
+ border: 1.5px solid var(--red);
+ padding: 6px 14px;
+ border-radius: 4px;
+ transition: background .15s, color .15s;
+}
+.footer-advertise:hover { background: var(--red); color: #fff; }
+.footer-col h4 {
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 1.2px;
+ text-transform: uppercase;
+ color: rgba(255,255,255,.9);
+ margin-bottom: 14px;
+}
+.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: 9px; }
+.footer-col a {
+ font-size: 13px;
+ color: rgba(255,255,255,.5);
+ transition: color .15s;
+}
+.footer-col a:hover { color: rgba(255,255,255,.9); }
+.footer-bottom {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 12px;
+ color: rgba(255,255,255,.3);
+ flex-wrap: wrap;
+ gap: 8px;
+}
+.footer-bottom a { color: rgba(255,255,255,.4); transition: color .15s; }
+.footer-bottom a:hover { color: rgba(255,255,255,.8); }
+
+/* ─── TEMPLATE SWITCHER CHIP ─────────────────────────────────────────── */
+.tpl-switcher {
+ position: fixed;
+ bottom: 16px;
+ right: 16px;
+ z-index: 300;
+ background: var(--ink);
+ color: rgba(255,255,255,.85);
+ font-size: 11px;
+ font-weight: 700;
+ padding: 7px 13px;
+ border-radius: 20px;
+ box-shadow: 0 4px 16px rgba(0,0,0,.3);
+ transition: background .15s, transform .15s;
+ letter-spacing: .3px;
+}
+.tpl-switcher:hover { background: var(--red); transform: translateY(-1px); }
+
+/* ─── EMPTY STATE ────────────────────────────────────────────────────── */
+.empty-state {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 60px 20px;
+ color: var(--sub);
+}
+.empty-state h3 { font-size: 18px; margin-bottom: 8px; color: var(--ink); }
+.empty-state p { font-size: 14px; }
+
+/* ─── RESPONSIVE ─────────────────────────────────────────────────────── */
+@media (max-width: 900px) {
+ .hero-inner {
+ grid-template-columns: 1fr;
+ min-height: auto;
+ }
+ .hero-video-wrap { padding-top: 56.25%; position: relative; min-height: auto; }
+ .hero-video-wrap img.hero-thumb { position: absolute; inset: 0; width: 100%; height: 100%; }
+ .hero-meta { padding: 20px; }
+ .hero-title { font-size: 18px; }
+ .footer-top { grid-template-columns: 1fr 1fr; gap: 28px; }
+ .footer-brand { grid-column: 1 / -1; }
+}
+@media (max-width: 640px) {
+ .hdr-btn span { display: none; }
+ .advertise-btn span { display: none; }
+ .controls-inner { gap: 8px; }
+ .wall-controls { flex-wrap: wrap; }
+ .footer-top { grid-template-columns: 1fr; }
+ .result-count { display: none; }
+}
+@media (max-width: 480px) {
+ :root { --col-min: 160px; }
+ .wordmark { font-size: 22px; }
+}
+
+/* ─── UTILS ──────────────────────────────────────────────────────────── */
+.visually-hidden {
+ position: absolute; width: 1px; height: 1px;
+ overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
+}
+</style>
+</head>
+<body>
+
+<!-- ─── HEADER ──────────────────────────────────────────────────────── -->
+<header class="site-header" role="banner">
+ <div class="hdr-left">
+ <button class="hdr-btn" id="menu-btn" aria-label="Open navigation menu" aria-expanded="false" aria-controls="nav-drawer">
+ <svg width="18" height="14" viewBox="0 0 18 14" fill="none" aria-hidden="true">
+ <path d="M0 1h18M0 7h18M0 13h18" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
+ </svg>
+ <span>Explore</span>
+ </button>
+ </div>
+
+ <a href="/" class="wordmark" aria-label="RENTV — Home">
+ REN<span>TV</span>
+ </a>
+
+ <div class="hdr-right">
+ <a href="/advertise" class="advertise-btn" aria-label="Advertise on RENTV">
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
+ <path d="M1 6h10M7 2l4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ <span>Advertise</span>
+ </a>
+ <button class="hdr-btn" aria-label="Browse sections">
+ <span>Sections</span>
+ <svg width="10" height="6" viewBox="0 0 10 6" fill="none" aria-hidden="true">
+ <path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ </button>
+ </div>
+</header>
+
+<!-- ─── LEADERBOARD AD ───────────────────────────────────────────────── -->
+<div class="ad-strip">
+ <div data-ad-slot="home-leaderboard"></div>
+</div>
+
+<!-- ─── HERO / FEATURED VIDEO ────────────────────────────────────────── -->
+<section class="hero-section" id="hero" aria-label="Featured video">
+ <div class="hero-inner">
+ <div class="hero-video-wrap" id="hero-video-wrap" role="button" tabindex="0" aria-label="Play featured video">
+ <img id="hero-thumb" class="hero-thumb" src="" alt="" loading="eager">
+ <div class="hero-badge">Featured</div>
+ <div class="hero-play" aria-hidden="true">
+ <div class="hero-play-circle">
+ <svg width="22" height="22" viewBox="0 0 22 22" fill="none">
+ <path d="M7 4l13 7-13 7V4z" fill="#fff"/>
+ </svg>
+ </div>
+ </div>
+ <div class="hero-sponsor-strip" id="hero-sponsor"></div>
+ </div>
+ <div class="hero-meta">
+ <div class="hero-cat" id="hero-cat"></div>
+ <h1 class="hero-title" id="hero-title"></h1>
+ <p class="hero-desc" id="hero-desc"></p>
+ <a class="hero-cta" id="hero-cta" href="#">
+ Watch Now
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
+ <path d="M2 7h10M8 3l4 4-4 4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
+ </svg>
+ </a>
+ </div>
+ </div>
+</section>
+
+<!-- ─── WALL MASTHEAD AD + CONTROLS ─────────────────────────────────── -->
+<div class="masthead-ad">
+ <div data-ad-slot="review-masthead"></div>
+</div>
+
+<div class="controls-bar" role="navigation" aria-label="Video filter and sort controls">
+ <div class="controls-inner">
+ <!-- Category chips -->
+ <div class="cat-chips" role="tablist" aria-label="Filter by category" id="cat-chips">
+ <!-- injected by JS -->
+ </div>
+
+ <!-- Sort + density -->
+ <div class="wall-controls">
+ <label class="visually-hidden" for="sort-select">Sort videos</label>
+ <select class="sort-select" id="sort-select" aria-label="Sort videos">
+ <option value="newest">Newest</option>
+ <option value="most-viewed">Most Viewed</option>
+ <option value="title-az">Title A→Z</option>
+ <option value="category">Category</option>
+ </select>
+
+ <div class="density-grid" aria-label="Grid density" role="group">
+ <div class="density-group">
+ <svg width="13" height="13" viewBox="0 0 13 13" aria-hidden="true" fill="none">
+ <rect x="0" y="0" width="5" height="5" rx="1" fill="currentColor" opacity=".5"/>
+ <rect x="8" y="0" width="5" height="5" rx="1" fill="currentColor" opacity=".5"/>
+ <rect x="0" y="8" width="5" height="5" rx="1" fill="currentColor" opacity=".5"/>
+ <rect x="8" y="8" width="5" height="5" rx="1" fill="currentColor" opacity=".5"/>
+ </svg>
+ <label for="density-slider" class="visually-hidden">Card size</label>
+ <input type="range" id="density-slider" min="160" max="480" step="20" value="260"
+ aria-label="Grid density — card minimum width">
+ <svg width="18" height="13" viewBox="0 0 18 13" aria-hidden="true" fill="none">
+ <rect x="0" y="0" width="7" height="7" rx="1" fill="currentColor" opacity=".5"/>
+ <rect x="11" y="0" width="7" height="7" rx="1" fill="currentColor" opacity=".5"/>
+ <rect x="0" y="10" width="7" height="3" rx="1" fill="currentColor" opacity=".2"/>
+ <rect x="11" y="10" width="7" height="3" rx="1" fill="currentColor" opacity=".2"/>
+ </svg>
+ </div>
+ </div>
+
+ <span class="result-count" id="result-count" aria-live="polite"></span>
+ </div>
+ </div>
+</div>
+
+<!-- ─── VIDEO WALL ────────────────────────────────────────────────────── -->
+<main class="wall-section" role="main" aria-label="Video wall">
+ <div class="video-wall" id="video-wall" role="list">
+ <!-- Skeleton placeholders while loading -->
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:90%"></div><div class="skel-line skeleton" style="width:70%"></div></div>
+ </div>
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:85%"></div><div class="skel-line skeleton" style="width:60%"></div></div>
+ </div>
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:88%"></div><div class="skel-line skeleton" style="width:50%"></div></div>
+ </div>
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:75%"></div><div class="skel-line skeleton" style="width:65%"></div></div>
+ </div>
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:92%"></div><div class="skel-line skeleton" style="width:55%"></div></div>
+ </div>
+ <div class="skel-card" aria-hidden="true">
+ <div class="skel-thumb skeleton"><div class="skel-thumb-inner"></div></div>
+ <div class="skel-body"><div class="skel-chip skeleton"></div><div class="skel-line skeleton" style="width:80%"></div><div class="skel-line skeleton" style="width:70%"></div></div>
+ </div>
+ </div>
+</main>
+
+<!-- ─── FOOTER ────────────────────────────────────────────────────────── -->
+<footer class="site-footer" role="contentinfo">
+ <div class="footer-inner">
+ <div class="footer-top">
+ <div class="footer-brand">
+ <a href="/" class="wordmark" aria-label="RENTV — Home">REN<span>TV</span></a>
+ <p class="footer-tagline">Western U.S. commercial real estate news,<br>video and intelligence since 1998.</p>
+ <a href="/advertise" class="footer-advertise">Advertise on RENTV →</a>
+ </div>
+ <div class="footer-col">
+ <h4>Sections</h4>
+ <ul>
+ <li><a href="/deals">Sales</a></li>
+ <li><a href="/deals">Leases</a></li>
+ <li><a href="/deals">Financing</a></li>
+ <li><a href="/deals">Development</a></li>
+ </ul>
+ </div>
+ <div class="footer-col">
+ <h4>Intelligence</h4>
+ <ul>
+ <li><a href="/markets">Markets</a></li>
+ <li><a href="/review">The REview</a></li>
+ <li><a href="/cre-talk">CRE Talk</a></li>
+ <li><a href="/conferences">Conferences</a></li>
+ </ul>
+ </div>
+ <div class="footer-col">
+ <h4>Company</h4>
+ <ul>
+ <li><a href="/about">About RENTV</a></li>
+ <li><a href="/advertise">Advertise</a></li>
+ <li><a href="/subscribe">Subscribe</a></li>
+ <li><a href="https://www.rentvreview.com" target="_blank" rel="noopener">The REview</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <span>© 2026 RENTV.com · All rights reserved.</span>
+ <span>
+ <a href="/privacy">Privacy</a> ·
+ <a href="/terms">Terms</a> ·
+ <a href="/advertise">Advertise</a>
+ </span>
+ </div>
+ </div>
+</footer>
+
+<!-- ─── MODAL ─────────────────────────────────────────────────────────── -->
+<div class="modal-overlay" id="modal-overlay" role="dialog" aria-modal="true" aria-label="Video player">
+ <div class="modal-box">
+ <button class="modal-close" id="modal-close" aria-label="Close video">×</button>
+ <div class="modal-video-wrap" id="modal-video-wrap"></div>
+ <div class="modal-info">
+ <div class="modal-cat" id="modal-cat"></div>
+ <h2 class="modal-title" id="modal-title"></h2>
+ <p class="modal-desc" id="modal-desc"></p>
+ <div class="modal-actions">
+ <a class="modal-action-btn primary" id="modal-watch-link" href="#" target="_blank" rel="noopener">
+ Open Full Page
+ </a>
+ <a class="modal-action-btn secondary" id="modal-sponsor-link" href="#">
+ Sponsor This Video
+ </a>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- ─── TEMPLATE SWITCHER ─────────────────────────────────────────────── -->
+<a href="/2026/" class="tpl-switcher" aria-label="Switch 2026 template">◆ 2026 Templates — switch</a>
+
+<!-- ─── AD SLOT SCRIPT ────────────────────────────────────────────────── -->
+<script src="/ad-slot.js" defer></script>
+
+<!-- ─── MAIN SCRIPT ───────────────────────────────────────────────────── -->
+<script>
+(function () {
+ 'use strict';
+
+ /* ── Helpers ─────────────────────────────────────────────────────── */
+ function esc(str) {
+ if (str == null) return '';
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ function safe(str) {
+ // Returns text safe for innerHTML by escaping HTML
+ return esc(str);
+ }
+
+ /* ── LocalStorage helpers ─────────────────────────────────────────── */
+ const LS_SORT = 'rentv_wall_sort';
+ const LS_DENSITY = 'rentv_wall_density';
+ const LS_CAT = 'rentv_wall_cat';
+
+ function lsGet(key, fallback) {
+ try { return localStorage.getItem(key) || fallback; } catch(_) { return fallback; }
+ }
+ function lsSet(key, val) {
+ try { localStorage.setItem(key, String(val)); } catch(_) {}
+ }
+
+ /* ── State ───────────────────────────────────────────────────────── */
+ let ALL_ITEMS = []; // full inventory
+ let featuredItem = null;
+
+ let currentCat = lsGet(LS_CAT, 'All');
+ let currentSort = lsGet(LS_SORT, 'newest');
+ let currentDensity = parseInt(lsGet(LS_DENSITY, '260'), 10);
+
+ /* ── DOM refs ────────────────────────────────────────────────────── */
+ const wall = document.getElementById('video-wall');
+ const catChips = document.getElementById('cat-chips');
+ const sortSel = document.getElementById('sort-select');
+ const densitySl = document.getElementById('density-slider');
+ const resultCount = document.getElementById('result-count');
+
+ /* ── Restore persisted UI values ─────────────────────────────────── */
+ sortSel.value = currentSort;
+ densitySl.value = currentDensity;
+ document.documentElement.style.setProperty('--col-min', currentDensity + 'px');
+
+ /* ── Source label helper ──────────────────────────────────────────── */
+ function sourceLabel(source) {
+ const map = { youtube: 'YouTube', vimeo: 'Vimeo', original: 'RENTV Original', 'news-wire': 'Deal Wire' };
+ return map[source] || source;
+ }
+ function sourceCls(source) {
+ const map = { youtube: 'source-youtube', vimeo: 'source-vimeo', original: 'source-original', 'news-wire': 'source-newswire' };
+ return map[source] || '';
+ }
+
+ /* ── Sort ────────────────────────────────────────────────────────── */
+ function sortItems(items, mode) {
+ const copy = items.slice();
+ switch (mode) {
+ case 'most-viewed':
+ return copy.sort((a, b) => (b.plays || 0) - (a.plays || 0));
+ case 'title-az':
+ return copy.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
+ case 'category':
+ return copy.sort((a, b) => (a.cat || '').localeCompare(b.cat || '') || (a.title || '').localeCompare(b.title || ''));
+ case 'newest':
+ default:
+ return copy; // API order = newest
+ }
+ }
+
+ /* ── Filter ──────────────────────────────────────────────────────── */
+ function filterItems(items, cat) {
+ if (cat === 'All') return items;
+ return items.filter(i => i.cat === cat);
+ }
+
+ /* ── Render wall ─────────────────────────────────────────────────── */
+ function renderWall() {
+ let items = filterItems(ALL_ITEMS, currentCat);
+ items = sortItems(items, currentSort);
+
+ resultCount.textContent = items.length + ' video' + (items.length !== 1 ? 's' : '');
+
+ if (items.length === 0) {
+ wall.innerHTML = '<div class="empty-state"><h3>No videos in this category</h3><p>Try a different filter or select All.</p></div>';
+ return;
+ }
+
+ wall.innerHTML = items.map(item => renderCard(item)).join('');
+
+ // Attach click handlers
+ wall.querySelectorAll('.video-card').forEach(card => {
+ card.addEventListener('click', function(e) {
+ // If clicking the category chip, filter instead of open modal
+ if (e.target.closest('.card-cat-chip')) return;
+ const uid = card.dataset.uid;
+ const item = ALL_ITEMS.find(i => i.uid === uid);
+ if (item) openModal(item);
+ });
+ card.addEventListener('keydown', function(e) {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ card.click();
+ }
+ });
+ });
+
+ // Category chip clicks on cards
+ wall.querySelectorAll('.card-cat-chip').forEach(chip => {
+ chip.addEventListener('click', function(e) {
+ e.stopPropagation();
+ const cat = chip.dataset.cat;
+ if (cat) setCategory(cat);
+ });
+ });
+ }
+
+ function renderCard(item) {
+ const hasSponsor = item.sponsorship && item.sponsorship.status === 'sold';
+ const thumbHtml = buildThumbHtml(item);
+ const playsHtml = item.plays > 0
+ ? `<span class="card-plays" aria-label="${item.plays} plays">
+ <svg width="11" height="11" viewBox="0 0 11 11" fill="none" aria-hidden="true"><path d="M2 1.5l7.5 4-7.5 4V1.5z" fill="currentColor" opacity=".5"/></svg>
+ ${item.plays.toLocaleString()}
+ </span>`
+ : '';
+
+ return `
+ <article class="video-card"
+ role="listitem"
+ data-uid="${esc(item.uid)}"
+ tabindex="0"
+ aria-label="${esc(item.title)} — ${esc(item.cat)}">
+ <div class="card-thumb-wrap">
+ ${thumbHtml}
+ <div class="card-play-btn" aria-hidden="true">
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
+ <path d="M3 2l11 6-11 6V2z" fill="#fff"/>
+ </svg>
+ </div>
+ <span class="card-source-badge ${sourceCls(item.source)}">${safe(sourceLabel(item.source))}</span>
+ ${hasSponsor ? `<div class="sponsored-ribbon">SPONSORED · ${safe(item.sponsorship.advertiser)}</div>` : ''}
+ </div>
+ <div class="card-body">
+ <a class="card-cat-chip" href="#" data-cat="${esc(item.cat)}" aria-label="Filter by ${esc(item.cat)}">${safe(item.cat)}</a>
+ <h3 class="card-title">${safe(item.title)}</h3>
+ <div class="card-footer">
+ ${playsHtml}
+ ${!hasSponsor ? `<a class="card-sponsor-cta" href="/advertise?video=${esc(item.uid)}" tabindex="-1" aria-hidden="true">Sponsor this video</a>` : ''}
+ </div>
+ </div>
+ </article>`;
+ }
+
+ function buildThumbHtml(item) {
+ if (item.thumb && item.thumb !== '') {
+ // Skip bad vimeo-undefined items
+ if (item.uid === 'vimeo-undefined') {
+ return fallbackThumb(item);
+ }
+ return `<img class="card-thumb" src="${esc(item.thumb)}" alt="${esc(item.title)}" loading="lazy"
+ onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">
+ <div class="card-thumb-fallback" style="display:none"><span>${safe(item.title)}</span></div>`;
+ }
+ return fallbackThumb(item);
+ }
+
+ function fallbackThumb(item) {
+ return `<div class="card-thumb-fallback"><span>${safe(item.title)}</span></div>`;
+ }
+
+ /* ── Hero setup ──────────────────────────────────────────────────── */
+ function setupHero(item) {
+ featuredItem = item;
+ const heroThumb = document.getElementById('hero-thumb');
+ const heroTitle = document.getElementById('hero-title');
+ const heroCat = document.getElementById('hero-cat');
+ const heroDesc = document.getElementById('hero-desc');
+ const heroCta = document.getElementById('hero-cta');
+ const heroSponsor = document.getElementById('hero-sponsor');
+
+ heroTitle.textContent = item.title || 'Western U.S. Commercial Real Estate';
+ heroCat.textContent = item.cat || 'Featured';
+ heroDesc.textContent = item.desc || '';
+
+ if (item.thumb && item.thumb !== '' && item.uid !== 'vimeo-undefined') {
+ heroThumb.src = item.thumb;
+ heroThumb.alt = item.title || '';
+ } else {
+ heroThumb.style.display = 'none';
+ }
+
+ heroCta.href = item.view_url || '#';
+ heroCta.setAttribute('aria-label', 'Watch: ' + (item.title || 'Featured video'));
+
+ const heroWrap = document.getElementById('hero-video-wrap');
+ heroWrap.setAttribute('aria-label', 'Play: ' + (item.title || 'Featured video'));
+ heroWrap.addEventListener('click', () => openModal(item));
+ heroWrap.addEventListener('keydown', e => {
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openModal(item); }
+ });
+
+ if (item.sponsorship && item.sponsorship.status === 'sold') {
+ heroSponsor.textContent = 'Sponsored by ' + item.sponsorship.advertiser;
+ } else {
+ const price = item.sponsorship && item.sponsorship.price_monthly
+ ? item.sponsorship.price_monthly
+ : 600;
+ heroSponsor.innerHTML = `<span>Sponsor this feature slot — from $${price.toLocaleString()}/mo</span><a href="/advertise?video=${esc(item.uid)}" style="color:#c8102e;font-weight:700;margin-left:8px">Advertise →</a>`;
+ }
+ }
+
+ /* ── Category chips ──────────────────────────────────────────────── */
+ function buildCategoryChips(items) {
+ const cats = ['All', ...Array.from(new Set(items.map(i => i.cat).filter(Boolean))).sort()];
+ catChips.innerHTML = cats.map(cat =>
+ `<button class="chip${cat === currentCat ? ' active' : ''}"
+ role="tab"
+ aria-selected="${cat === currentCat}"
+ data-cat="${esc(cat)}">${safe(cat)}</button>`
+ ).join('');
+
+ catChips.querySelectorAll('.chip').forEach(btn => {
+ btn.addEventListener('click', () => setCategory(btn.dataset.cat));
+ });
+ }
+
+ function setCategory(cat) {
+ currentCat = cat;
+ lsSet(LS_CAT, cat);
+ catChips.querySelectorAll('.chip').forEach(btn => {
+ const active = btn.dataset.cat === cat;
+ btn.classList.toggle('active', active);
+ btn.setAttribute('aria-selected', active);
+ });
+ renderWall();
+ }
+
+ /* ── Sort + density controls ──────────────────────────────────────── */
+ sortSel.addEventListener('change', function() {
+ currentSort = this.value;
+ lsSet(LS_SORT, currentSort);
+ renderWall();
+ });
+
+ densitySl.addEventListener('input', function() {
+ currentDensity = parseInt(this.value, 10);
+ lsSet(LS_DENSITY, currentDensity);
+ document.documentElement.style.setProperty('--col-min', currentDensity + 'px');
+ });
+
+ /* ── Modal ───────────────────────────────────────────────────────── */
+ const overlay = document.getElementById('modal-overlay');
+ const modalClose = document.getElementById('modal-close');
+ const modalVideo = document.getElementById('modal-video-wrap');
+ const modalCat = document.getElementById('modal-cat');
+ const modalTitle = document.getElementById('modal-title');
+ const modalDesc = document.getElementById('modal-desc');
+ const modalWatchLink = document.getElementById('modal-watch-link');
+ const modalSponsorLink = document.getElementById('modal-sponsor-link');
+
+ function openModal(item) {
+ modalCat.textContent = item.cat || '';
+ modalTitle.textContent = item.title || '';
+ modalDesc.textContent = item.desc || '';
+ modalWatchLink.href = item.view_url || '#';
+ modalSponsorLink.href = '/advertise?video=' + encodeURIComponent(item.uid || '');
+
+ // Inject media
+ if (item.embed) {
+ // YouTube or Vimeo iframe
+ const src = item.embed + (item.embed.includes('?') ? '&' : '?') + 'autoplay=1&rel=0';
+ modalVideo.innerHTML = `<iframe src="${esc(src)}" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="${esc(item.title)}"></iframe>`;
+ } else if (item.mp4) {
+ modalVideo.innerHTML = `<video controls autoplay playsinline preload="metadata" poster="${esc(item.thumb || '')}">
+ <source src="${esc(item.mp4)}" type="video/mp4">
+ <p>Your browser doesn't support HTML5 video.</p>
+ </video>`;
+ } else if (item.view_url) {
+ // Fallback: open external link
+ window.open(item.view_url, '_blank', 'noopener');
+ return;
+ } else {
+ modalVideo.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:200px;color:#fff;font-size:14px;">Video unavailable</div>`;
+ }
+
+ overlay.classList.add('open');
+ document.body.style.overflow = 'hidden';
+ modalClose.focus();
+ }
+
+ function closeModal() {
+ overlay.classList.remove('open');
+ document.body.style.overflow = '';
+ // Stop iframe/video playback
+ modalVideo.innerHTML = '';
+ }
+
+ modalClose.addEventListener('click', closeModal);
+ overlay.addEventListener('click', function(e) {
+ if (e.target === overlay) closeModal();
+ });
+ document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape' && overlay.classList.contains('open')) closeModal();
+ });
+
+ /* ── Fetch data and bootstrap ─────────────────────────────────────── */
+ async function init() {
+ try {
+ // Fetch both endpoints in parallel
+ const [invRes, vidRes] = await Promise.allSettled([
+ fetch('/api/ad/inventory'),
+ fetch('/api/videos')
+ ]);
+
+ let inventoryItems = [];
+ let pinnedItem = null;
+
+ if (invRes.status === 'fulfilled' && invRes.value.ok) {
+ const invData = await invRes.value.json();
+ inventoryItems = (invData.items || []).filter(i =>
+ // Filter out the bad vimeo-undefined record for the wall but keep all others
+ i.uid !== 'vimeo-undefined'
+ );
+ }
+
+ if (vidRes.status === 'fulfilled' && vidRes.value.ok) {
+ const vidData = await vidRes.value.json();
+ pinnedItem = (vidData.items || []).find(i => i.pinned) || (vidData.items || [])[0] || null;
+ }
+
+ // Set up hero: prefer pinned from /api/videos, fall back to Featured from inventory
+ const heroItem = pinnedItem
+ ? {
+ uid: 'yt-' + (pinnedItem.id || pinnedItem.yt || 'featured'),
+ cat: pinnedItem.cat || 'Featured',
+ title: pinnedItem.title || '',
+ desc: pinnedItem.desc || '',
+ thumb: pinnedItem.thumb || '',
+ embed: pinnedItem.embed || (pinnedItem.yt ? 'https://www.youtube.com/embed/' + pinnedItem.yt : ''),
+ mp4: pinnedItem.mp4 || '',
+ view_url: pinnedItem.view_url || '',
+ plays: pinnedItem.plays || 0,
+ sponsorship: { status: 'available', price_monthly: 600 }
+ }
+ : inventoryItems.find(i => i.cat === 'Featured') || inventoryItems[0] || null;
+
+ if (heroItem) setupHero(heroItem);
+
+ ALL_ITEMS = inventoryItems;
+
+ if (ALL_ITEMS.length === 0) {
+ // Graceful empty state
+ wall.innerHTML = `<div class="empty-state">
+ <h3>Loading videos…</h3>
+ <p>Check back shortly or <a href="/review" style="color:var(--red)">visit The REview</a>.</p>
+ </div>`;
+ return;
+ }
+
+ buildCategoryChips(ALL_ITEMS);
+ renderWall();
+
+ } catch (err) {
+ console.error('RENTV Wall: init error', err);
+ wall.innerHTML = `<div class="empty-state">
+ <h3>Unable to load videos</h3>
+ <p>Please refresh the page. If the problem persists, <a href="/about" style="color:var(--red)">contact us</a>.</p>
+ </div>`;
+ }
+ }
+
+ init();
+
+})();
+</script>
+</body>
+</html>
diff --git a/public/2026/b.html b/public/2026/b.html
new file mode 100644
index 00000000..1838974f
--- /dev/null
+++ b/public/2026/b.html
@@ -0,0 +1,1471 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV — The Regional Authority in CRE</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,700;0,800;1,600&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+<style>
+/* ── TOKENS ────────────────────────────────────────────── */
+:root {
+ --ink: #14171a;
+ --sub: #5b636b;
+ --hair: #e4e7ea;
+ --red: #c8102e;
+ --gold: #b8860b;
+ --bg: #fff;
+ --cols: 4;
+ --font-serif: 'Playfair Display', Georgia, serif;
+ --font-body: 'Inter', system-ui, sans-serif;
+}
+
+/* ── RESET ─────────────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+html { scroll-behavior: smooth; }
+body {
+ font-family: var(--font-body);
+ color: var(--ink);
+ background: var(--bg);
+ line-height: 1.5;
+ font-size: 15px;
+ -webkit-font-smoothing: antialiased;
+}
+a { color: inherit; text-decoration: none; }
+img { display: block; max-width: 100%; }
+button { cursor: pointer; border: none; background: none; font: inherit; }
+
+/* ── TICKER ─────────────────────────────────────────────── */
+.ticker-strip {
+ background: var(--ink);
+ color: #fff;
+ font: 600 12px/1 var(--font-body);
+ letter-spacing: .03em;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ height: 34px;
+ position: relative;
+ z-index: 100;
+}
+.ticker-label {
+ background: var(--red);
+ color: #fff;
+ font-weight: 800;
+ font-size: 10px;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ padding: 0 12px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ white-space: nowrap;
+ flex-shrink: 0;
+ z-index: 1;
+}
+.ticker-scroll-wrap {
+ overflow: hidden;
+ flex: 1;
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+.ticker-track {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ white-space: nowrap;
+ animation: tickerScroll 40s linear infinite;
+ will-change: transform;
+}
+.ticker-track:hover { animation-play-state: paused; }
+.ticker-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 28px 0 0;
+ color: rgba(255,255,255,.85);
+}
+.ticker-item .t-metric {
+ font-weight: 800;
+ color: #fff;
+}
+.ticker-item .t-up { color: #4caf50; }
+.ticker-item .t-dn { color: #ff6b6b; }
+.ticker-sep { color: rgba(255,255,255,.25); padding: 0 8px; }
+@keyframes tickerScroll {
+ 0% { transform: translateX(0); }
+ 100% { transform: translateX(-50%); }
+}
+
+/* ── UTILITY BAR ─────────────────────────────────────────── */
+.util-bar {
+ border-bottom: 1px solid var(--hair);
+ background: #fafbfc;
+ padding: 0 24px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 30px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--sub);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+}
+.util-bar a { color: var(--sub); }
+.util-bar a:hover { color: var(--ink); }
+.util-advertise { color: var(--red) !important; font-weight: 700; }
+.util-advertise:hover { text-decoration: underline; }
+.util-date { font-weight: 500; letter-spacing: 0; text-transform: none; font-size: 11px; }
+
+/* ── STICKY HEADER ───────────────────────────────────────── */
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 200;
+ background: #fff;
+ border-bottom: 2px solid var(--ink);
+}
+.header-inner {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 24px;
+ height: 58px;
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ align-items: center;
+}
+.header-left { display: flex; align-items: center; gap: 20px; }
+.header-right { display: flex; align-items: center; justify-content: flex-end; gap: 20px; }
+.nav-btn {
+ font: 600 12px/1 var(--font-body);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ color: var(--ink);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 0;
+ border-bottom: 2px solid transparent;
+ transition: border-color .15s;
+}
+.nav-btn:hover { border-color: var(--red); color: var(--red); }
+.nav-btn svg { flex-shrink: 0; }
+.wordmark {
+ font-family: var(--font-serif);
+ font-weight: 800;
+ font-size: 28px;
+ letter-spacing: -.02em;
+ text-align: center;
+ line-height: 1;
+ text-decoration: none;
+}
+.wordmark .tv { color: var(--red); }
+
+/* ── SECTION NAV ─────────────────────────────────────────── */
+.section-nav {
+ border-bottom: 1px solid var(--hair);
+ background: #fff;
+}
+.section-nav-inner {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 24px;
+ display: flex;
+ align-items: center;
+ gap: 0;
+ overflow-x: auto;
+ scrollbar-width: none;
+}
+.section-nav-inner::-webkit-scrollbar { display: none; }
+.snav-link {
+ font: 600 12px/1 var(--font-body);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+ color: var(--sub);
+ padding: 10px 16px;
+ white-space: nowrap;
+ border-bottom: 2px solid transparent;
+ transition: color .15s, border-color .15s;
+}
+.snav-link:hover, .snav-link.active { color: var(--ink); border-color: var(--ink); }
+.snav-link.breaking { color: var(--red); border-color: var(--red); font-weight: 800; }
+
+/* ── MAIN LAYOUT ─────────────────────────────────────────── */
+.page-wrap {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+/* ── LEADERBOARD SLOT ────────────────────────────────────── */
+.leaderboard-wrap {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 12px 24px 8px;
+}
+
+/* ── LEAD SECTION ────────────────────────────────────────── */
+.lead-section {
+ display: grid;
+ grid-template-columns: 1fr 320px;
+ gap: 0;
+ border-bottom: 1px solid var(--hair);
+ padding: 28px 0 32px;
+}
+
+/* ── LEAD STORY ──────────────────────────────────────────── */
+.lead-story {
+ padding-right: 32px;
+ border-right: 1px solid var(--hair);
+}
+.lead-eyebrow {
+ font: 700 11px/1 var(--font-body);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 10px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.lead-eyebrow::after {
+ content: '';
+ flex: 1;
+ height: 1px;
+ background: var(--red);
+ opacity: .3;
+}
+.lead-headline {
+ font-family: var(--font-serif);
+ font-weight: 800;
+ font-size: clamp(26px, 3.2vw, 40px);
+ line-height: 1.1;
+ letter-spacing: -.02em;
+ margin-bottom: 12px;
+ color: var(--ink);
+}
+.lead-headline a:hover { color: var(--red); }
+.lead-dek {
+ font-size: 16px;
+ line-height: 1.6;
+ color: #2c3338;
+ margin-bottom: 18px;
+ max-width: 640px;
+}
+.lead-hero {
+ position: relative;
+ width: 100%;
+ aspect-ratio: 16/9;
+ overflow: hidden;
+ border-radius: 3px;
+ background: #1a1e24;
+ margin-bottom: 14px;
+}
+.lead-hero img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform .4s ease;
+}
+.lead-hero:hover img { transform: scale(1.015); }
+.lead-hero-gradient {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg, #1a2030 0%, #2c3a52 50%, #1e2d1e 100%);
+}
+.lead-byline {
+ font: 500 12px/1 var(--font-body);
+ color: var(--sub);
+ letter-spacing: .02em;
+}
+.lead-byline strong { color: var(--ink); font-weight: 700; }
+.lead-secondary-stories {
+ margin-top: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+}
+.secondary-story {
+ display: flex;
+ gap: 14px;
+ padding: 16px 0;
+ border-top: 1px solid var(--hair);
+}
+.secondary-story:first-child { border-top: none; padding-top: 0; }
+.sec-thumb {
+ width: 100px;
+ height: 68px;
+ flex-shrink: 0;
+ border-radius: 3px;
+ overflow: hidden;
+ background: #1a1e24;
+}
+.sec-thumb img { width: 100%; height: 100%; object-fit: cover; }
+.sec-thumb .thumb-gradient {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #1a2030, #2c3a52);
+}
+.sec-meta { flex: 1; min-width: 0; }
+.sec-cat {
+ font: 700 10px/1 var(--font-body);
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 4px;
+}
+.sec-title {
+ font-family: var(--font-serif);
+ font-weight: 700;
+ font-size: 15px;
+ line-height: 1.25;
+ color: var(--ink);
+}
+.sec-title a:hover { color: var(--red); }
+.sec-author {
+ font: 500 11px/1 var(--font-body);
+ color: var(--sub);
+ margin-top: 5px;
+}
+
+/* ── VIDEO RAIL (right 1/3) ─────────────────────────────── */
+.video-rail {
+ padding-left: 24px;
+ display: flex;
+ flex-direction: column;
+}
+.rail-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: 12px;
+ border-bottom: 2px solid var(--ink);
+ margin-bottom: 0;
+}
+.rail-title {
+ font: 700 11px/1 var(--font-body);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--ink);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.rail-title .dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--red);
+ display: inline-block;
+}
+.rail-sort {
+ font: 600 11px/1 var(--font-body);
+ color: var(--sub);
+ background: none;
+ border: 1px solid var(--hair);
+ border-radius: 4px;
+ padding: 4px 8px;
+ cursor: pointer;
+ appearance: none;
+}
+.rail-sort:focus { outline: 2px solid var(--red); outline-offset: 2px; }
+.video-rail .ad-wrap { margin: 12px 0; }
+.vid-card-rail {
+ border-top: 1px solid var(--hair);
+ padding: 14px 0;
+ display: flex;
+ gap: 12px;
+ position: relative;
+}
+.vid-card-rail:last-of-type { border-bottom: 1px solid var(--hair); }
+.vid-thumb-rail {
+ width: 110px;
+ height: 64px;
+ flex-shrink: 0;
+ border-radius: 4px;
+ overflow: hidden;
+ background: #111;
+ position: relative;
+}
+.vid-thumb-rail img { width: 100%; height: 100%; object-fit: cover; }
+.vid-play-badge {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0,0,0,.35);
+ transition: background .15s;
+}
+.vid-play-badge svg { filter: drop-shadow(0 1px 3px rgba(0,0,0,.6)); }
+.vid-thumb-rail:hover .vid-play-badge { background: rgba(200,16,46,.55); }
+.vid-sponsored-flag {
+ position: absolute;
+ bottom: 4px;
+ left: 4px;
+ background: var(--gold);
+ color: #fff;
+ font: 800 8px/1 var(--font-body);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ padding: 3px 5px;
+ border-radius: 2px;
+}
+.vid-sponsor-cta {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ background: rgba(0,0,0,.65);
+ color: rgba(255,255,255,.8);
+ font: 600 8px/1 var(--font-body);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+ padding: 3px 5px;
+ border-radius: 2px;
+ opacity: 0;
+ transition: opacity .15s;
+ white-space: nowrap;
+}
+.vid-card-rail:hover .vid-sponsor-cta { opacity: 1; }
+.vid-meta-rail { flex: 1; min-width: 0; }
+.vid-cat-rail {
+ font: 700 10px/1 var(--font-body);
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--sub);
+ margin-bottom: 4px;
+}
+.vid-title-rail {
+ font: 600 13px/1.3 var(--font-body);
+ color: var(--ink);
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.vid-title-rail a:hover { color: var(--red); }
+.vid-watch {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font: 700 10px/1 var(--font-body);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-top: 6px;
+}
+.vid-watch:hover { text-decoration: underline; }
+.view-all-link {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ font: 700 11px/1 var(--font-body);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ color: var(--sub);
+ border: 1px solid var(--hair);
+ border-radius: 4px;
+ padding: 10px;
+ margin-top: 12px;
+ transition: all .15s;
+}
+.view-all-link:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
+
+/* ── VIDEO ROW (horizontal scroll-snap) ─────────────────── */
+.section-divider {
+ border-top: 2px solid var(--ink);
+ padding-top: 24px;
+ margin-top: 32px;
+ margin-bottom: 20px;
+}
+.section-divider-inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+.section-label {
+ font: 700 11px/1 var(--font-body);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--ink);
+ white-space: nowrap;
+}
+.section-controls {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+.ctrl-select {
+ font: 600 11px/1 var(--font-body);
+ color: var(--sub);
+ background: none;
+ border: 1px solid var(--hair);
+ border-radius: 4px;
+ padding: 5px 8px;
+ cursor: pointer;
+ appearance: none;
+}
+.ctrl-select:focus { outline: 2px solid var(--red); outline-offset: 2px; }
+.ctrl-label {
+ font: 600 11px/1 var(--font-body);
+ color: var(--sub);
+ white-space: nowrap;
+}
+.ctrl-range {
+ width: 80px;
+ accent-color: var(--red);
+ cursor: pointer;
+}
+.video-row-wrap {
+ overflow-x: auto;
+ scroll-snap-type: x mandatory;
+ scrollbar-width: thin;
+ scrollbar-color: var(--hair) transparent;
+ -webkit-overflow-scrolling: touch;
+ padding-bottom: 8px;
+}
+.video-row {
+ display: flex;
+ gap: 16px;
+ width: max-content;
+ padding: 4px 2px;
+}
+.vid-card-h {
+ width: 240px;
+ scroll-snap-align: start;
+ flex-shrink: 0;
+ position: relative;
+ border-radius: 6px;
+ overflow: hidden;
+ background: #111;
+ box-shadow: 0 2px 8px rgba(0,0,0,.1);
+ transition: transform .2s, box-shadow .2s;
+ cursor: pointer;
+}
+.vid-card-h:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,.15); }
+.vid-card-h-thumb {
+ width: 100%;
+ aspect-ratio: 16/9;
+ position: relative;
+ overflow: hidden;
+}
+.vid-card-h-thumb img { width: 100%; height: 100%; object-fit: cover; }
+.vid-card-h-play {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0,0,0,.3);
+ transition: background .15s;
+}
+.vid-card-h:hover .vid-card-h-play { background: rgba(200,16,46,.5); }
+.vid-card-h-body { padding: 12px; background: #fff; border: 1px solid var(--hair); border-top: none; border-radius: 0 0 6px 6px; }
+.vid-card-h-cat {
+ font: 700 9px/1 var(--font-body);
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--sub);
+ margin-bottom: 5px;
+}
+.vid-card-h-title {
+ font: 600 13px/1.3 var(--font-body);
+ color: var(--ink);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.vid-sponsor-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 8px;
+}
+.vid-sponsor-flag-h {
+ background: var(--gold);
+ color: #fff;
+ font: 800 9px/1 var(--font-body);
+ letter-spacing: .06em;
+ text-transform: uppercase;
+ padding: 3px 6px;
+ border-radius: 2px;
+}
+.vid-sponsor-cta-h {
+ font: 700 9px/1 var(--font-body);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+ color: var(--sub);
+ border: 1px solid var(--hair);
+ border-radius: 3px;
+ padding: 3px 6px;
+ transition: all .15s;
+}
+.vid-sponsor-cta-h:hover { background: var(--red); color: #fff; border-color: var(--red); }
+
+/* ── NEWS RIVER ──────────────────────────────────────────── */
+.news-river-wrap { padding-bottom: 40px; }
+.news-grid {
+ display: grid;
+ grid-template-columns: repeat(var(--cols), 1fr);
+ gap: 28px 24px;
+ margin-top: 4px;
+}
+.story-card {
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+}
+.story-thumb {
+ width: 100%;
+ aspect-ratio: 16/10;
+ border-radius: 4px;
+ overflow: hidden;
+ background: #1a1e24;
+ margin-bottom: 12px;
+ position: relative;
+}
+.story-thumb img { width: 100%; height: 100%; object-fit: cover; transition: transform .35s; }
+.story-card:hover .story-thumb img { transform: scale(1.04); }
+.story-thumb .thumb-gradient {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #1a2030, #2c3a52);
+}
+.story-cat {
+ font: 700 10px/1 var(--font-body);
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 6px;
+}
+.story-title {
+ font-family: var(--font-serif);
+ font-weight: 700;
+ font-size: 16px;
+ line-height: 1.25;
+ color: var(--ink);
+ margin-bottom: 6px;
+}
+.story-title a:hover { color: var(--red); }
+.story-author {
+ font: 500 11px/1 var(--font-body);
+ color: var(--sub);
+ margin-top: auto;
+ padding-top: 6px;
+}
+/* Ad card inside the river */
+.story-card.ad-card {
+ border: none;
+ background: transparent;
+}
+
+/* ── FOOTER ──────────────────────────────────────────────── */
+.site-footer {
+ background: var(--ink);
+ color: rgba(255,255,255,.65);
+ padding: 40px 0 28px;
+ margin-top: 0;
+}
+.footer-inner {
+ max-width: 1320px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+.footer-top {
+ display: grid;
+ grid-template-columns: auto 1fr 1fr 1fr;
+ gap: 40px;
+ padding-bottom: 28px;
+ border-bottom: 1px solid rgba(255,255,255,.12);
+}
+.footer-wordmark {
+ font-family: var(--font-serif);
+ font-weight: 800;
+ font-size: 26px;
+ color: #fff;
+ letter-spacing: -.02em;
+}
+.footer-wordmark .tv { color: var(--red); }
+.footer-tagline {
+ font: 400 12px/1.5 var(--font-body);
+ color: rgba(255,255,255,.4);
+ margin-top: 6px;
+ max-width: 160px;
+}
+.footer-col-head {
+ font: 700 10px/1 var(--font-body);
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: rgba(255,255,255,.9);
+ margin-bottom: 14px;
+}
+.footer-links { list-style: none; display: flex; flex-direction: column; gap: 8px; }
+.footer-links a {
+ font: 400 13px/1 var(--font-body);
+ color: rgba(255,255,255,.55);
+ transition: color .15s;
+}
+.footer-links a:hover { color: #fff; }
+.footer-bot {
+ padding-top: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font: 400 11px/1 var(--font-body);
+ color: rgba(255,255,255,.3);
+}
+.footer-bot a { color: rgba(255,255,255,.45); }
+.footer-bot a:hover { color: rgba(255,255,255,.8); }
+
+/* ── SWITCH CHIP ─────────────────────────────────────────── */
+.switch-chip {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ z-index: 999;
+ background: var(--ink);
+ color: #fff;
+ font: 700 11px/1 var(--font-body);
+ letter-spacing: .04em;
+ padding: 10px 14px;
+ border-radius: 20px;
+ box-shadow: 0 4px 16px rgba(0,0,0,.25);
+ transition: background .15s, transform .15s;
+ white-space: nowrap;
+}
+.switch-chip:hover { background: var(--red); transform: translateY(-2px); }
+
+/* ── LOADING / STATES ────────────────────────────────────── */
+.skeleton {
+ background: linear-gradient(90deg, #f0f2f4 25%, #e4e7ea 50%, #f0f2f4 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.4s infinite;
+ border-radius: 4px;
+}
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+.empty-state {
+ grid-column: 1 / -1;
+ text-align: center;
+ padding: 40px;
+ color: var(--sub);
+ font: 400 14px/1.5 var(--font-body);
+}
+
+/* ── MODAL VIDEO OVERLAY ─────────────────────────────────── */
+.video-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ background: rgba(0,0,0,.88);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .2s;
+}
+.video-modal.open { opacity: 1; pointer-events: all; }
+.video-modal-inner {
+ width: 100%;
+ max-width: 840px;
+ position: relative;
+}
+.video-modal-close {
+ position: absolute;
+ top: -36px;
+ right: 0;
+ color: #fff;
+ font: 600 14px/1 var(--font-body);
+ cursor: pointer;
+ opacity: .7;
+ background: none;
+ border: none;
+}
+.video-modal-close:hover { opacity: 1; }
+.video-modal-frame {
+ width: 100%;
+ aspect-ratio: 16/9;
+ border-radius: 6px;
+ overflow: hidden;
+ background: #000;
+}
+.video-modal-frame iframe {
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
+/* ── RESPONSIVE ──────────────────────────────────────────── */
+@media (max-width: 1100px) {
+ :root { --cols: 3; }
+ .lead-section { grid-template-columns: 1fr 280px; }
+ .video-rail { padding-left: 20px; }
+}
+@media (max-width: 860px) {
+ :root { --cols: 2; }
+ .lead-section { grid-template-columns: 1fr; }
+ .lead-story { padding-right: 0; border-right: none; border-bottom: 1px solid var(--hair); padding-bottom: 24px; margin-bottom: 24px; }
+ .video-rail { padding-left: 0; }
+ .footer-top { grid-template-columns: 1fr 1fr; gap: 24px; }
+ .footer-top > :first-child { grid-column: 1 / -1; }
+}
+@media (max-width: 600px) {
+ :root { --cols: 1; }
+ .header-inner { padding: 0 16px; }
+ .page-wrap { padding: 0 16px; }
+ .leaderboard-wrap { padding: 8px 16px; }
+ .wordmark { font-size: 22px; }
+ .util-bar { padding: 0 16px; }
+ .section-nav-inner { padding: 0 16px; }
+ .footer-top { grid-template-columns: 1fr; }
+ .lead-headline { font-size: 24px; }
+}
+</style>
+</head>
+<body>
+
+<!-- ══ TICKER STRIP ════════════════════════════════════════ -->
+<div class="ticker-strip" role="marquee" aria-label="Market data">
+ <span class="ticker-label">● Markets</span>
+ <div class="ticker-scroll-wrap">
+ <div class="ticker-track" id="tickerTrack">
+ <span class="ticker-item"><span>Loading market data…</span></span>
+ </div>
+ </div>
+</div>
+
+<!-- ══ UTILITY BAR ═════════════════════════════════════════ -->
+<div class="util-bar">
+ <span class="util-date" id="utilDate"></span>
+ <div style="display:flex;align-items:center;gap:16px;">
+ <a href="/subscribe.html">Subscribe</a>
+ <a href="/advertise.html" class="util-advertise">Advertise →</a>
+ </div>
+</div>
+
+<!-- ══ STICKY HEADER ════════════════════════════════════════ -->
+<header class="site-header" role="banner">
+ <div class="header-inner">
+ <div class="header-left">
+ <button class="nav-btn" id="menuBtn" aria-label="Open navigation" aria-expanded="false">
+ <svg width="18" height="14" viewBox="0 0 18 14" fill="none" aria-hidden="true">
+ <rect y="0" width="18" height="2" rx="1" fill="currentColor"/>
+ <rect y="6" width="18" height="2" rx="1" fill="currentColor"/>
+ <rect y="12" width="18" height="2" rx="1" fill="currentColor"/>
+ </svg>
+ Explore
+ </button>
+ </div>
+ <a href="/" class="wordmark" aria-label="RENTV — Home">REN<span class="tv">TV</span></a>
+ <div class="header-right">
+ <a href="#section-nav" class="nav-btn">Sections</a>
+ <a href="https://www.rentvreview.com/" class="nav-btn" rel="noopener" target="_blank" style="color:var(--red);">
+ ▶ The REview
+ </a>
+ </div>
+ </div>
+</header>
+
+<!-- ══ SECTION NAV ══════════════════════════════════════════ -->
+<nav class="section-nav" id="section-nav" aria-label="Sections">
+ <div class="section-nav-inner">
+ <a href="/sales.html" class="snav-link active">Sales</a>
+ <a href="/leasing.html" class="snav-link">Leasing</a>
+ <a href="/markets.html" class="snav-link">Financing</a>
+ <a href="/markets.html" class="snav-link">Multifamily</a>
+ <a href="/markets.html" class="snav-link">Industrial</a>
+ <a href="/markets.html" class="snav-link">Office</a>
+ <a href="/markets.html" class="snav-link">Retail</a>
+ <a href="/markets.html" class="snav-link">Markets</a>
+ <a href="/deals.html" class="snav-link">Deals</a>
+ <a href="/hub.html" class="snav-link">Intelligence</a>
+ <a href="https://www.rentvreview.com/" class="snav-link breaking" rel="noopener" target="_blank">▶ Watch</a>
+ </div>
+</nav>
+
+<!-- ══ LEADERBOARD SLOT ═════════════════════════════════════ -->
+<div class="leaderboard-wrap">
+ <div data-ad-slot="home-leaderboard"></div>
+</div>
+
+<!-- ══ MAIN ═════════════════════════════════════════════════ -->
+<main class="page-wrap" id="main" role="main">
+
+ <!-- LEAD SECTION -->
+ <section class="lead-section" aria-label="Top stories">
+
+ <!-- LEFT: Lead + secondary stories -->
+ <div class="lead-story">
+ <div class="lead-eyebrow" id="leadEyebrow">Loading…</div>
+ <h1 class="lead-headline" id="leadHeadline">
+ <a href="#" id="leadLink">Retrieving top story…</a>
+ </h1>
+ <p class="lead-dek" id="leadDek">Western commercial real estate news — the deals, the capital, the market intelligence.</p>
+ <a href="#" class="lead-hero" id="leadHeroLink" aria-label="Read lead story">
+ <div class="lead-hero-gradient" id="leadGradient"></div>
+ <img id="leadImg" src="" alt="" style="display:none;" loading="eager">
+ </a>
+ <div class="lead-byline" id="leadByline"></div>
+
+ <!-- Secondary stories -->
+ <div class="lead-secondary-stories" id="secondaryStories" aria-label="Secondary stories"></div>
+ </div>
+
+ <!-- RIGHT: Video rail -->
+ <aside class="video-rail" aria-label="RENTV Video">
+ <div class="rail-header">
+ <span class="rail-title"><span class="dot"></span> RENTV Video</span>
+ <select class="rail-sort" id="railSort" aria-label="Sort video rail">
+ <option value="newest">Newest</option>
+ <option value="category">Category</option>
+ <option value="title">Title A→Z</option>
+ <option value="views">Most Viewed</option>
+ </select>
+ </div>
+ <div class="video-rail-items" id="railItems" style="flex:1;">
+ <!-- populated by JS -->
+ </div>
+ <div class="ad-wrap"><div data-ad-slot="section-sidebar"></div></div>
+ <a href="https://www.rentvreview.com/" class="view-all-link" rel="noopener" target="_blank">
+ All Videos → The REview
+ </a>
+ </aside>
+ </section>
+
+ <!-- VIDEO ROW SECTION -->
+ <div class="section-divider">
+ <div class="section-divider-inner">
+ <span class="section-label">▶ Latest Video</span>
+ <div class="section-controls">
+ <select class="ctrl-select" id="videoRowSort" aria-label="Sort video row">
+ <option value="newest">Newest</option>
+ <option value="category">Category</option>
+ <option value="title">Title A→Z</option>
+ <option value="views">Most Viewed</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ <div class="video-row-wrap" role="region" aria-label="Video row">
+ <div class="video-row" id="videoRow" aria-busy="true"></div>
+ </div>
+
+ <!-- NEWS RIVER SECTION -->
+ <div class="section-divider" style="margin-top:40px;">
+ <div class="section-divider-inner">
+ <span class="section-label">Across the Market</span>
+ <div class="section-controls">
+ <select class="ctrl-select" id="riverSort" aria-label="Sort news" aria-controls="newsGrid">
+ <option value="newest">Newest</option>
+ <option value="category">Category</option>
+ <option value="title">Title A→Z</option>
+ </select>
+ <span class="ctrl-label">Density</span>
+ <input type="range" class="ctrl-range" id="densitySlider"
+ min="1" max="5" step="1" value="4"
+ aria-label="Grid column count" aria-controls="newsGrid">
+ </div>
+ </div>
+ </div>
+ <div class="news-river-wrap">
+ <div class="news-grid" id="newsGrid" aria-busy="true" aria-label="News stories"></div>
+ </div>
+
+</main>
+
+<!-- ══ FOOTER ═══════════════════════════════════════════════ -->
+<footer class="site-footer" role="contentinfo">
+ <div class="footer-inner">
+ <div class="footer-top">
+ <div>
+ <div class="footer-wordmark">REN<span class="tv">TV</span></div>
+ <p class="footer-tagline">Western U.S. commercial real estate since 1998.</p>
+ </div>
+ <div>
+ <div class="footer-col-head">Sections</div>
+ <ul class="footer-links">
+ <li><a href="/sales.html">Sales</a></li>
+ <li><a href="/leasing.html">Leasing</a></li>
+ <li><a href="/markets.html">Financing</a></li>
+ <li><a href="/markets.html">Multifamily</a></li>
+ <li><a href="/markets.html">Industrial</a></li>
+ <li><a href="/markets.html">Deals</a></li>
+ </ul>
+ </div>
+ <div>
+ <div class="footer-col-head">Intelligence</div>
+ <ul class="footer-links">
+ <li><a href="/hub.html">Hub</a></li>
+ <li><a href="https://www.rentvreview.com/" rel="noopener" target="_blank">The REview</a></li>
+ <li><a href="/market-pulse.html">Market Pulse</a></li>
+ <li><a href="/subscribe.html">Newsletter</a></li>
+ <li><a href="/search.html">Search</a></li>
+ </ul>
+ </div>
+ <div>
+ <div class="footer-col-head">Company</div>
+ <ul class="footer-links">
+ <li><a href="/about.html">About RENTV</a></li>
+ <li><a href="/advertise.html">Advertise</a></li>
+ <li><a href="/services.html">Services</a></li>
+ <li><a href="mailto:info@rentv.com">Contact</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="footer-bot">
+ <span>© 2026 RENTV Inc. All rights reserved.</span>
+ <a href="/advertise.html">Advertise with RENTV</a>
+ </div>
+ </div>
+</footer>
+
+<!-- ══ SWITCH CHIP ═══════════════════════════════════════════ -->
+<a href="/2026/" class="switch-chip" aria-label="2026 Templates — switch">◆ 2026 Templates — switch</a>
+
+<!-- ══ VIDEO MODAL ═══════════════════════════════════════════ -->
+<div class="video-modal" id="videoModal" role="dialog" aria-modal="true" aria-label="Video player">
+ <div class="video-modal-inner">
+ <button class="video-modal-close" id="modalClose" aria-label="Close video">✕ Close</button>
+ <div class="video-modal-frame">
+ <iframe id="modalFrame" src="about:blank" allow="autoplay; fullscreen; picture-in-picture"
+ allowfullscreen title="Video player" loading="lazy"></iframe>
+ </div>
+ </div>
+</div>
+
+<!-- ══ AD SLOT INCLUDE ═══════════════════════════════════════ -->
+<script src="/ad-slot.js" defer></script>
+
+<!-- ══ APP SCRIPT ════════════════════════════════════════════ -->
+<script>
+(function () {
+ 'use strict';
+
+ /* ── XSS-safe text helpers ───────────────────────────── */
+ function esc(s) {
+ return String(s == null ? '' : s)
+ .replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
+ .replace(/"/g,'"').replace(/'/g,''');
+ }
+ function txt(el, str) { el.textContent = str == null ? '' : String(str); }
+
+ /* ── Date util ───────────────────────────────────────── */
+ var months = ['January','February','March','April','May','June',
+ 'July','August','September','October','November','December'];
+ var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
+ function fmtDate(d) {
+ return days[d.getDay()] + ', ' + months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
+ }
+
+ /* ── localStorage helpers (no-throw) ────────────────── */
+ function lsGet(k) { try { return localStorage.getItem(k); } catch(e) { return null; } }
+ function lsSet(k,v) { try { localStorage.setItem(k,v); } catch(e) {} }
+
+ /* ── Date bar ────────────────────────────────────────── */
+ var dateEl = document.getElementById('utilDate');
+ if (dateEl) txt(dateEl, fmtDate(new Date()));
+
+ /* ── Ticker ──────────────────────────────────────────── */
+ function buildTicker(metrics) {
+ var track = document.getElementById('tickerTrack');
+ if (!track) return;
+ var items = [];
+ Object.values(metrics).forEach(function(m) {
+ if (!m || typeof m !== 'object') return;
+ var arrow = m.dir === 'up' ? '▲' : m.dir === 'down' ? '▼' : '';
+ var cls = m.dir === 'up' ? 't-up' : m.dir === 'down' ? 't-dn' : '';
+ var val = m.value || '';
+ var pct = m.change_pct != null ? ' (' + (m.change_pct > 0 ? '+' : '') + m.change_pct + '%)' : '';
+ var bps = m.change_bps != null ? ' (' + (m.change_bps > 0 ? '+' : '') + m.change_bps + 'bps)' : '';
+ var change = pct || bps;
+ var label = esc(m.label || '');
+ var html = '<span class="ticker-item">' +
+ '<span class="t-label">' + label + '</span> ' +
+ '<span class="t-metric ' + cls + '">' + arrow + ' ' + esc(val) + '</span>' +
+ (change ? '<span style="color:rgba(255,255,255,.4)">' + esc(change) + '</span>' : '') +
+ '</span><span class="ticker-sep">|</span>';
+ items.push(html);
+ });
+ // static headlines
+ var headlines = [
+ 'BlackRock entity acquires 3,620-unit SoCal portfolio in $1.6B trade',
+ 'Seattle office and hotel asset lands $107M refi',
+ 'GO Industrial starts 1.1msf Phoenix facility',
+ 'Rockefeller Group acquires 34-acre Glendale site',
+ ];
+ headlines.forEach(function(h) {
+ items.push('<span class="ticker-item" style="color:rgba(255,255,255,.75)">' + esc(h) + '</span><span class="ticker-sep">●</span>');
+ });
+ var html = items.join('');
+ track.innerHTML = html + html; // doubled for seamless loop
+ }
+
+ fetch('/api/ticker')
+ .then(function(r){ return r.ok ? r.json() : {}; })
+ .then(buildTicker)
+ .catch(function(){ buildTicker({}); });
+
+ /* ── Image resolver ──────────────────────────────────── */
+ function resolveImg(img) {
+ if (!img) return null;
+ if (typeof img === 'string') return img;
+ if (typeof img === 'object' && img.image) return img.image;
+ return null;
+ }
+
+ /* ── LEAD + SECONDARY STORIES ────────────────────────── */
+ function renderLead(items) {
+ if (!items || !items.length) return;
+ var lead = items[0];
+ var eyebrow = document.getElementById('leadEyebrow');
+ var headline = document.getElementById('leadHeadline');
+ var link = document.getElementById('leadLink');
+ var heroLink = document.getElementById('leadHeroLink');
+ var img = document.getElementById('leadImg');
+ var gradient = document.getElementById('leadGradient');
+ var byline = document.getElementById('leadByline');
+ var dek = document.getElementById('leadDek');
+
+ if (eyebrow) { eyebrow.innerHTML = '<span>' + esc(lead.cat || 'Top Story') + '</span>'; }
+ if (link) { txt(link, lead.title); link.href = '/news/' + encodeURIComponent(lead.id || ''); }
+ if (heroLink) { heroLink.href = '/news/' + encodeURIComponent(lead.id || ''); }
+
+ var src = resolveImg(lead.image);
+ if (src && img) {
+ img.src = src;
+ img.alt = esc(lead.title || '');
+ img.style.display = '';
+ if (gradient) gradient.style.display = 'none';
+ img.onerror = function() { img.style.display = 'none'; if(gradient) gradient.style.display = ''; };
+ }
+
+ if (byline && lead.author) {
+ byline.innerHTML = 'By <strong>' + esc(lead.author) + '</strong>';
+ }
+ if (dek) {
+ // use a snippet from title as dek if no desc
+ txt(dek, lead.desc || 'Read the full story at RENTV — Western U.S. commercial real estate.');
+ }
+
+ // Secondary stories (items 1–4)
+ var secCont = document.getElementById('secondaryStories');
+ if (!secCont) return;
+ var html = '';
+ var secs = items.slice(1, 5);
+ secs.forEach(function(item) {
+ var src2 = resolveImg(item.image);
+ var thumb = src2
+ ? '<img src="' + esc(src2) + '" alt="' + esc(item.title) + '" loading="lazy" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'\'"><div class="thumb-gradient" style="display:none"></div>'
+ : '<div class="thumb-gradient"></div>';
+ html += '<article class="secondary-story">' +
+ '<div class="sec-thumb">' + thumb + '</div>' +
+ '<div class="sec-meta">' +
+ '<div class="sec-cat">' + esc(item.cat || '') + '</div>' +
+ '<div class="sec-title"><a href="/news/' + encodeURIComponent(item.id || '') + '">' + esc(item.title) + '</a></div>' +
+ (item.author ? '<div class="sec-author">By ' + esc(item.author) + '</div>' : '') +
+ '</div>' +
+ '</article>';
+ });
+ secCont.innerHTML = html;
+ }
+
+ /* ── VIDEO MODAL ─────────────────────────────────────── */
+ var modal = document.getElementById('videoModal');
+ var modalFrame = document.getElementById('modalFrame');
+ var modalClose = document.getElementById('modalClose');
+
+ function openVideo(embed) {
+ if (!embed || !modal || !modalFrame) return;
+ // ensure autoplay param
+ var url = embed + (embed.indexOf('?') === -1 ? '?' : '&') + 'autoplay=1';
+ modalFrame.src = url;
+ modal.classList.add('open');
+ document.body.style.overflow = 'hidden';
+ if (modalClose) modalClose.focus();
+ }
+
+ function closeVideo() {
+ if (!modal) return;
+ modal.classList.remove('open');
+ document.body.style.overflow = '';
+ if (modalFrame) modalFrame.src = 'about:blank';
+ }
+
+ if (modalClose) modalClose.addEventListener('click', closeVideo);
+ if (modal) {
+ modal.addEventListener('click', function(e) {
+ if (e.target === modal) closeVideo();
+ });
+ document.addEventListener('keydown', function(e) {
+ if (e.key === 'Escape') closeVideo();
+ });
+ }
+
+ /* ── PLAY ICON SVG ───────────────────────────────────── */
+ var PLAY_SVG = '<svg width="28" height="28" viewBox="0 0 28 28" fill="none" aria-hidden="true"><circle cx="14" cy="14" r="14" fill="rgba(255,255,255,0.92)"/><polygon points="11,9 21,14 11,19" fill="#c8102e"/></svg>';
+ var PLAY_SVG_SM = '<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="11" fill="rgba(255,255,255,0.9)"/><polygon points="8.5,6.5 16.5,11 8.5,15.5" fill="#c8102e"/></svg>';
+
+ /* ── VIDEO RAIL ──────────────────────────────────────── */
+ var allVideos = [];
+ var RAIL_SORT_KEY = 'rentv_rail_sort_v1';
+
+ function sortVideos(arr, mode) {
+ var sorted = arr.slice();
+ if (mode === 'category') {
+ sorted.sort(function(a,b){ return (a.cat||'').localeCompare(b.cat||''); });
+ } else if (mode === 'title') {
+ sorted.sort(function(a,b){ return (a.title||'').localeCompare(b.title||''); });
+ } else if (mode === 'views') {
+ sorted.sort(function(a,b){ return (b.plays||0) - (a.plays||0); });
+ }
+ // newest = default order from API
+ return sorted;
+ }
+
+ function renderRail(videos, sort) {
+ var cont = document.getElementById('railItems');
+ if (!cont) return;
+ var list = sortVideos(videos, sort).slice(0, 7);
+ if (!list.length) {
+ cont.innerHTML = '<div class="empty-state">No videos available.</div>';
+ return;
+ }
+ var html = '';
+ list.forEach(function(v) {
+ var isSold = v.sponsorship && v.sponsorship.status === 'sold';
+ var advertiser = (v.sponsorship && v.sponsorship.advertiser) || '';
+ var sponsorFlag = isSold
+ ? '<span class="vid-sponsored-flag">SPONSORED · ' + esc(advertiser) + '</span>'
+ : '<a href="/advertise?video=' + esc(v.uid || v.id || '') + '" class="vid-sponsor-cta" tabindex="-1" rel="nofollow">Sponsor this →</a>';
+ var thumb = v.thumb
+ ? '<img src="' + esc(v.thumb) + '" alt="' + esc(v.title) + '" loading="lazy" onerror="this.style.display=\'none\'">'
+ : '';
+ var embed = v.embed || '';
+ var viewUrl = v.view_url || 'https://www.rentvreview.com/';
+ html += '<div class="vid-card-rail">' +
+ '<div class="vid-thumb-rail">' +
+ '<a href="' + esc(viewUrl) + '" rel="noopener" target="_blank">' + thumb + '</a>' +
+ '<div class="vid-play-badge" role="button" tabindex="0" aria-label="Play: ' + esc(v.title) + '" ' +
+ 'onclick="window.__openVid(' + "'" + esc(embed) + "','" + esc(viewUrl) + "'" + ')"' +
+ ' onkeypress="if(event.key===\'Enter\')window.__openVid(' + "'" + esc(embed) + "','" + esc(viewUrl) + "'" + ')">' +
+ PLAY_SVG_SM + '</div>' +
+ sponsorFlag +
+ '</div>' +
+ '<div class="vid-meta-rail">' +
+ '<div class="vid-cat-rail">' + esc(v.cat || '') + '</div>' +
+ '<div class="vid-title-rail"><a href="' + esc(viewUrl) + '" rel="noopener" target="_blank">' + esc(v.title) + '</a></div>' +
+ '<a href="' + esc(viewUrl) + '" class="vid-watch" rel="noopener" target="_blank">▶ Watch</a>' +
+ '</div>' +
+ '</div>';
+ });
+ cont.innerHTML = html;
+ }
+
+ var railSortEl = document.getElementById('railSort');
+ if (railSortEl) {
+ var savedRailSort = lsGet(RAIL_SORT_KEY) || 'newest';
+ railSortEl.value = savedRailSort;
+ railSortEl.addEventListener('change', function() {
+ lsSet(RAIL_SORT_KEY, this.value);
+ renderRail(allVideos, this.value);
+ });
+ }
+
+ /* ── VIDEO ROW (horizontal) ──────────────────────────── */
+ var VIDEO_ROW_SORT_KEY = 'rentv_vrow_sort_v1';
+
+ function renderVideoRow(videos, sort) {
+ var row = document.getElementById('videoRow');
+ if (!row) return;
+ var list = sortVideos(videos, sort).slice(0, 20);
+ if (!list.length) {
+ row.innerHTML = '<div class="empty-state">No videos available.</div>';
+ return;
+ }
+ var html = '';
+ list.forEach(function(v) {
+ var isSold = v.sponsorship && v.sponsorship.status === 'sold';
+ var advertiser = (v.sponsorship && v.sponsorship.advertiser) || '';
+ var embed = v.embed || '';
+ var viewUrl = v.view_url || 'https://www.rentvreview.com/';
+ var sponsorBlock = isSold
+ ? '<div class="vid-sponsor-row"><span class="vid-sponsor-flag-h">SPONSORED · ' + esc(advertiser) + '</span></div>'
+ : '<div class="vid-sponsor-row"><a href="/advertise?video=' + esc(v.uid || v.id || '') + '" class="vid-sponsor-cta-h" rel="nofollow">Sponsor this →</a></div>';
+ var thumb = v.thumb
+ ? '<img src="' + esc(v.thumb) + '" alt="' + esc(v.title) + '" loading="lazy" onerror="this.style.display=\'none\'">'
+ : '';
+ html += '<div class="vid-card-h" ' +
+ 'onclick="window.__openVid(' + "'" + esc(embed) + "','" + esc(viewUrl) + "'" + ')" ' +
+ 'role="button" tabindex="0" aria-label="Play: ' + esc(v.title) + '" ' +
+ 'onkeypress="if(event.key===\'Enter\')window.__openVid(' + "'" + esc(embed) + "','" + esc(viewUrl) + "'" + ')">' +
+ '<div class="vid-card-h-thumb">' + thumb +
+ '<div class="vid-card-h-play">' + PLAY_SVG + '</div>' +
+ '</div>' +
+ '<div class="vid-card-h-body">' +
+ '<div class="vid-card-h-cat">' + esc(v.cat || '') + '</div>' +
+ '<div class="vid-card-h-title">' + esc(v.title) + '</div>' +
+ sponsorBlock +
+ '</div>' +
+ '</div>';
+ });
+ row.innerHTML = html;
+ row.setAttribute('aria-busy', 'false');
+ }
+
+ var videoRowSortEl = document.getElementById('videoRowSort');
+ if (videoRowSortEl) {
+ var savedVRowSort = lsGet(VIDEO_ROW_SORT_KEY) || 'newest';
+ videoRowSortEl.value = savedVRowSort;
+ videoRowSortEl.addEventListener('change', function() {
+ lsSet(VIDEO_ROW_SORT_KEY, this.value);
+ renderVideoRow(allVideos, this.value);
+ });
+ }
+
+ /* ── openVid global (called from inline handlers) ─────── */
+ window.__openVid = function(embed, fallback) {
+ if (embed && embed !== 'undefined' && embed.indexOf('undefined') === -1) {
+ openVideo(embed);
+ } else {
+ window.open(fallback || 'https://www.rentvreview.com/', '_blank', 'noopener');
+ }
+ };
+
+ /* ── NEWS RIVER ──────────────────────────────────────── */
+ var allNews = [];
+ var RIVER_SORT_KEY = 'rentv_river_sort_v1';
+ var DENSITY_KEY = 'rentv_density_v1';
+
+ function sortNews(arr, mode) {
+ var sorted = arr.slice();
+ if (mode === 'category') {
+ sorted.sort(function(a,b){ return (a.cat||'').localeCompare(b.cat||''); });
+ } else if (mode === 'title') {
+ sorted.sort(function(a,b){ return (a.title||'').localeCompare(b.title||''); });
+ }
+ return sorted;
+ }
+
+ function renderNewsGrid(news, sort, skipCount) {
+ var grid = document.getElementById('newsGrid');
+ if (!grid) return;
+ // items after lead (skipCount)
+ var items = news.slice(skipCount || 5);
+ var sorted = sortNews(items, sort);
+ if (!sorted.length) {
+ grid.innerHTML = '<div class="empty-state">No stories available.</div>';
+ grid.setAttribute('aria-busy', 'false');
+ return;
+ }
+ var html = '';
+ var adInserted = false;
+ sorted.forEach(function(item, i) {
+ // insert ad unit at position 2 (0-indexed)
+ if (!adInserted && i === 2) {
+ html += '<div class="story-card ad-card"><div data-ad-slot="home-infeed-1"></div></div>';
+ adInserted = true;
+ }
+ var src = resolveImg(item.image);
+ var thumb = src
+ ? '<img src="' + esc(src) + '" alt="' + esc(item.title) + '" loading="lazy" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'\'">'
+ + '<div class="thumb-gradient" style="display:none"></div>'
+ : '<div class="thumb-gradient"></div>';
+ html += '<article class="story-card">' +
+ '<a href="/news/' + encodeURIComponent(item.id || '') + '" class="story-thumb" aria-label="' + esc(item.title) + '">' +
+ thumb +
+ '</a>' +
+ '<div class="story-cat">' + esc(item.cat || '') + '</div>' +
+ '<h2 class="story-title"><a href="/news/' + encodeURIComponent(item.id || '') + '">' + esc(item.title) + '</a></h2>' +
+ (item.author ? '<div class="story-author">By ' + esc(item.author) + '</div>' : '') +
+ '</article>';
+ });
+ grid.innerHTML = html;
+ grid.setAttribute('aria-busy', 'false');
+ // refill new ad slots injected by this render
+ if (window.RENTVAdSlot) window.RENTVAdSlot.refill();
+ }
+
+ var riverSortEl = document.getElementById('riverSort');
+ var densityEl = document.getElementById('densitySlider');
+
+ if (riverSortEl) {
+ var savedRiverSort = lsGet(RIVER_SORT_KEY) || 'newest';
+ riverSortEl.value = savedRiverSort;
+ riverSortEl.addEventListener('change', function() {
+ lsSet(RIVER_SORT_KEY, this.value);
+ renderNewsGrid(allNews, this.value, 5);
+ });
+ }
+
+ if (densityEl) {
+ var savedDensity = lsGet(DENSITY_KEY) || '4';
+ densityEl.value = savedDensity;
+ document.documentElement.style.setProperty('--cols', savedDensity);
+ densityEl.addEventListener('input', function() {
+ var v = this.value;
+ document.documentElement.style.setProperty('--cols', v);
+ lsSet(DENSITY_KEY, v);
+ });
+ }
+
+ /* ── FETCH DATA ──────────────────────────────────────── */
+ // News
+ fetch('/api/news')
+ .then(function(r){ return r.ok ? r.json() : {items:[]}; })
+ .then(function(d) {
+ allNews = (d.items || []);
+ renderLead(allNews);
+ var sortVal = (riverSortEl && riverSortEl.value) || 'newest';
+ renderNewsGrid(allNews, sortVal, 5);
+ })
+ .catch(function(e) {
+ console.warn('News fetch failed:', e);
+ var grid = document.getElementById('newsGrid');
+ if (grid) grid.innerHTML = '<div class="empty-state">Unable to load stories. Please try again.</div>';
+ });
+
+ // Videos
+ fetch('/api/ad/inventory')
+ .then(function(r){ return r.ok ? r.json() : {items:[]}; })
+ .then(function(d) {
+ // filter out vimeo items with 'undefined' in embed
+ allVideos = (d.items || []).filter(function(v) {
+ return !v.embed || v.embed.indexOf('undefined') === -1;
+ });
+ var railSort = (railSortEl && railSortEl.value) || 'newest';
+ var rowSort = (videoRowSortEl && videoRowSortEl.value) || 'newest';
+ renderRail(allVideos, railSort);
+ renderVideoRow(allVideos, rowSort);
+ })
+ .catch(function(e) {
+ console.warn('Video fetch failed:', e);
+ var row = document.getElementById('videoRow');
+ if (row) row.innerHTML = '<div class="empty-state">Unable to load videos.</div>';
+ });
+
+ /* ── HAMBURGER MENU (simple toggle) ─────────────────── */
+ var menuBtn = document.getElementById('menuBtn');
+ if (menuBtn) {
+ menuBtn.addEventListener('click', function() {
+ var expanded = this.getAttribute('aria-expanded') === 'true';
+ this.setAttribute('aria-expanded', String(!expanded));
+ // scroll to section nav as a lightweight menu action
+ document.getElementById('section-nav').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
+ });
+ }
+
+})();
+</script>
+</body>
+</html>
diff --git a/public/2026/c.html b/public/2026/c.html
new file mode 100644
index 00000000..17195255
--- /dev/null
+++ b/public/2026/c.html
@@ -0,0 +1,1094 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>RENTV — Cinematic CRE</title>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+<style>
+/* ── RESET + TOKENS ────────────────────────────────────────────────── */
+*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
+:root{
+ --bg:#07090c;
+ --bg2:#0b0e12;
+ --bg3:#131820;
+ --bg4:#1a2030;
+ --text:#e7ecf1;
+ --text2:#9ba8b8;
+ --red:#c8102e;
+ --gold:#f0b429;
+ --border:rgba(255,255,255,0.08);
+ --card-w:280px;
+ --radius:6px;
+ --header-h:62px;
+}
+html{scroll-behavior:smooth}
+body{
+ background:var(--bg);
+ color:var(--text);
+ font-family:'Inter',sans-serif;
+ font-size:15px;
+ line-height:1.5;
+ overflow-x:hidden;
+ min-height:100vh;
+}
+
+/* ── SCROLLBAR ─────────────────────────────────────────────────────── */
+::-webkit-scrollbar{width:6px;height:6px}
+::-webkit-scrollbar-track{background:var(--bg2)}
+::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:3px}
+::-webkit-scrollbar-thumb:hover{background:var(--red)}
+
+/* ── HEADER ────────────────────────────────────────────────────────── */
+#site-header{
+ position:sticky;top:0;z-index:900;
+ height:var(--header-h);
+ background:rgba(7,9,12,0.92);
+ backdrop-filter:blur(16px) saturate(1.4);
+ -webkit-backdrop-filter:blur(16px) saturate(1.4);
+ border-bottom:1px solid var(--border);
+ display:flex;align-items:center;justify-content:space-between;
+ padding:0 24px;
+ gap:16px;
+}
+#site-header .hd-left,#site-header .hd-right{
+ display:flex;align-items:center;gap:16px;flex:1;
+}
+#site-header .hd-right{justify-content:flex-end}
+.wordmark{
+ font-family:'Playfair Display',serif;
+ font-weight:800;font-size:26px;
+ letter-spacing:-0.5px;
+ color:var(--text);
+ text-decoration:none;
+ flex-shrink:0;
+}
+.wordmark span{color:var(--red)}
+.hd-btn{
+ background:none;border:1px solid rgba(255,255,255,0.2);
+ color:var(--text2);
+ font-family:inherit;font-size:13px;font-weight:600;
+ padding:6px 14px;border-radius:4px;cursor:pointer;
+ transition:all .2s;white-space:nowrap;
+}
+.hd-btn:hover{color:var(--text);border-color:rgba(255,255,255,0.45)}
+.hd-btn.primary{
+ background:var(--red);border-color:var(--red);
+ color:#fff;
+}
+.hd-btn.primary:hover{background:#a50d24;border-color:#a50d24}
+
+/* ── CONTROLS BAR ──────────────────────────────────────────────────── */
+#controls-bar{
+ display:flex;align-items:center;gap:24px;
+ padding:14px 32px 10px;
+ background:var(--bg2);
+ border-bottom:1px solid var(--border);
+ flex-wrap:wrap;
+}
+#controls-bar label{
+ font-size:12px;font-weight:600;color:var(--text2);
+ text-transform:uppercase;letter-spacing:.06em;
+ display:flex;align-items:center;gap:8px;
+ white-space:nowrap;
+}
+#sort-select{
+ background:var(--bg3);border:1px solid var(--border);
+ color:var(--text);font-family:inherit;font-size:13px;font-weight:500;
+ padding:6px 28px 6px 10px;border-radius:4px;cursor:pointer;
+ appearance:none;-webkit-appearance:none;
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%239ba8b8'/%3E%3C/svg%3E");
+ background-repeat:no-repeat;background-position:right 8px center;
+ transition:border-color .2s;
+}
+#sort-select:hover,#sort-select:focus{border-color:var(--red);outline:none}
+.density-wrap{display:flex;align-items:center;gap:8px}
+#density-slider{
+ -webkit-appearance:none;appearance:none;
+ width:120px;height:4px;border-radius:2px;
+ background:linear-gradient(to right,var(--red) 0%,var(--red) 50%,var(--bg4) 50%);
+ outline:none;cursor:pointer;
+}
+#density-slider::-webkit-slider-thumb{
+ -webkit-appearance:none;appearance:none;
+ width:16px;height:16px;border-radius:50%;
+ background:var(--text);border:2px solid var(--red);
+ cursor:pointer;transition:transform .15s;
+}
+#density-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}
+.density-label{font-size:11px;color:var(--text2);min-width:18px;text-align:right}
+
+/* ── AD SLOTS ──────────────────────────────────────────────────────── */
+[data-ad-slot]{
+ width:100%;min-height:60px;
+ background:linear-gradient(135deg,rgba(200,16,46,0.04),rgba(240,180,41,0.04));
+ border:1px dashed rgba(240,180,41,0.2);
+ display:flex;align-items:center;justify-content:center;
+ color:rgba(240,180,41,0.5);font-size:11px;font-weight:600;
+ letter-spacing:.12em;text-transform:uppercase;
+ border-radius:4px;transition:opacity .3s;
+}
+[data-ad-slot]:empty::after{content:attr(data-ad-slot) " · ADVERTISING SLOT · RENTV"}
+#ad-masthead{margin:0;padding:0 32px;background:var(--bg2)}
+#ad-masthead [data-ad-slot]{border-radius:6px;min-height:72px}
+#ad-footer{padding:0 32px 32px;background:var(--bg)}
+#ad-footer [data-ad-slot]{min-height:90px}
+
+/* ── HERO ───────────────────────────────────────────────────────────── */
+#hero{
+ position:relative;width:100%;
+ background:var(--bg);
+ overflow:hidden;
+}
+#hero-inner{
+ position:relative;
+ max-width:1400px;margin:0 auto;
+ aspect-ratio:16/7;min-height:420px;
+ background:#0d1018;
+}
+@media(max-width:768px){#hero-inner{aspect-ratio:16/9;min-height:260px}}
+#hero-thumb{
+ position:absolute;inset:0;
+ background:var(--bg2);
+}
+#hero-thumb img{
+ width:100%;height:100%;object-fit:cover;
+ display:block;opacity:0.65;
+ filter:saturate(0.7);
+}
+#hero-gradient{
+ position:absolute;inset:0;
+ background:linear-gradient(
+ to right,
+ rgba(7,9,12,0.92) 0%,
+ rgba(7,9,12,0.7) 40%,
+ rgba(7,9,12,0.2) 65%,
+ transparent 100%
+ ),linear-gradient(
+ to top,
+ rgba(7,9,12,0.85) 0%,
+ transparent 50%
+ );
+}
+#hero-content{
+ position:absolute;
+ left:5%;bottom:0;top:0;
+ display:flex;flex-direction:column;
+ justify-content:center;
+ max-width:520px;
+ padding:40px 0;
+}
+.hero-eyebrow{
+ display:inline-flex;align-items:center;gap:8px;
+ font-size:11px;font-weight:700;letter-spacing:.15em;
+ text-transform:uppercase;color:var(--red);
+ margin-bottom:14px;
+}
+.hero-eyebrow::before{
+ content:'';display:block;
+ width:28px;height:2px;background:var(--red);border-radius:1px;
+}
+.hero-title{
+ font-family:'Playfair Display',serif;
+ font-size:clamp(24px,3.5vw,48px);
+ font-weight:800;line-height:1.15;
+ color:#fff;margin-bottom:14px;
+ text-shadow:0 2px 20px rgba(0,0,0,0.6);
+}
+.hero-desc{
+ font-size:clamp(13px,1.2vw,15px);
+ color:rgba(231,236,241,0.8);
+ line-height:1.6;margin-bottom:24px;
+ max-width:400px;
+}
+.hero-btns{display:flex;gap:12px;flex-wrap:wrap}
+.hero-play{
+ display:inline-flex;align-items:center;gap:8px;
+ background:var(--red);color:#fff;
+ font-size:14px;font-weight:700;
+ padding:12px 24px;border-radius:4px;
+ cursor:pointer;border:none;font-family:inherit;
+ transition:all .2s;text-decoration:none;
+ box-shadow:0 4px 20px rgba(200,16,46,0.4);
+}
+.hero-play:hover{background:#a50d24;transform:translateY(-1px)}
+.hero-play svg{flex-shrink:0}
+.hero-advert{
+ display:inline-flex;align-items:center;
+ background:transparent;
+ border:1px solid rgba(231,236,241,0.35);color:var(--text);
+ font-size:13px;font-weight:600;
+ padding:12px 20px;border-radius:4px;
+ cursor:pointer;font-family:inherit;
+ transition:all .2s;text-decoration:none;
+}
+.hero-advert:hover{border-color:var(--gold);color:var(--gold)}
+#hero-play-icon{
+ position:absolute;right:5%;top:50%;transform:translateY(-50%);
+ width:72px;height:72px;border-radius:50%;
+ background:rgba(200,16,46,0.85);
+ display:flex;align-items:center;justify-content:center;
+ cursor:pointer;transition:all .3s;
+ box-shadow:0 0 40px rgba(200,16,46,0.3);
+}
+#hero-play-icon:hover{background:var(--red);transform:translateY(-50%) scale(1.08)}
+@media(max-width:600px){#hero-play-icon{display:none}}
+
+/* ── RAILS SECTION ─────────────────────────────────────────────────── */
+#rails{padding:32px 0 48px}
+.rail{margin-bottom:40px}
+.rail-header{
+ display:flex;align-items:baseline;gap:16px;
+ padding:0 32px;margin-bottom:16px;
+}
+.rail-title{
+ font-family:'Playfair Display',serif;
+ font-size:20px;font-weight:700;
+ color:var(--text);
+}
+.rail-count{
+ font-size:12px;font-weight:500;color:var(--text2);
+}
+.rail-all{
+ margin-left:auto;font-size:12px;font-weight:600;
+ color:var(--red);text-decoration:none;
+ transition:opacity .2s;letter-spacing:.03em;
+}
+.rail-all:hover{opacity:.75}
+.rail-scroll-wrap{
+ position:relative;
+}
+.rail-row{
+ display:flex;gap:14px;
+ overflow-x:auto;
+ scroll-snap-type:x mandatory;
+ scroll-behavior:smooth;
+ padding:4px 32px 16px;
+ scrollbar-width:none;
+}
+.rail-row::-webkit-scrollbar{display:none}
+/* scroll arrows */
+.rail-arrow{
+ position:absolute;top:50%;transform:translateY(-50%);
+ z-index:10;width:40px;height:72px;
+ display:flex;align-items:center;justify-content:center;
+ cursor:pointer;border:none;
+ background:linear-gradient(to right,rgba(7,9,12,0.92),transparent);
+ color:var(--text);transition:opacity .2s;
+ opacity:0;
+}
+.rail-scroll-wrap:hover .rail-arrow{opacity:1}
+.rail-arrow.left{left:0}
+.rail-arrow.right{
+ right:0;
+ background:linear-gradient(to left,rgba(7,9,12,0.92),transparent);
+}
+.rail-arrow:hover{color:#fff}
+
+/* ── VIDEO CARD ────────────────────────────────────────────────────── */
+.v-card{
+ flex:0 0 var(--card-w);
+ width:var(--card-w);
+ scroll-snap-align:start;
+ border-radius:var(--radius);
+ overflow:hidden;
+ background:var(--bg3);
+ cursor:pointer;
+ position:relative;
+ transition:transform .22s,box-shadow .22s;
+ border:1px solid transparent;
+}
+.v-card:hover{
+ transform:translateY(-4px) scale(1.02);
+ box-shadow:0 12px 40px rgba(0,0,0,0.55);
+ border-color:rgba(255,255,255,0.12);
+}
+.v-card-thumb{
+ position:relative;width:100%;aspect-ratio:16/9;
+ overflow:hidden;background:var(--bg4);
+}
+.v-card-thumb img{
+ width:100%;height:100%;object-fit:cover;
+ display:block;transition:transform .4s;
+}
+.v-card:hover .v-card-thumb img{transform:scale(1.06)}
+.v-card-play{
+ position:absolute;inset:0;
+ display:flex;align-items:center;justify-content:center;
+ background:rgba(7,9,12,0.3);
+ opacity:0;transition:opacity .2s;
+}
+.v-card:hover .v-card-play{opacity:1}
+.v-card-play-btn{
+ width:44px;height:44px;border-radius:50%;
+ background:rgba(200,16,46,0.9);
+ display:flex;align-items:center;justify-content:center;
+ color:#fff;
+}
+.v-card-duration{
+ position:absolute;bottom:6px;right:6px;
+ background:rgba(0,0,0,0.75);color:#fff;
+ font-size:11px;font-weight:600;
+ padding:2px 5px;border-radius:3px;
+}
+.v-card-flag{
+ position:absolute;top:6px;left:6px;
+ font-size:10px;font-weight:700;letter-spacing:.04em;
+ padding:3px 7px;border-radius:3px;
+}
+.v-card-flag.sponsored{background:var(--gold);color:#000}
+.v-card-flag.available{
+ background:rgba(240,180,41,0.12);
+ border:1px solid rgba(240,180,41,0.3);
+ color:var(--gold);
+}
+.v-card-body{padding:10px 12px 12px}
+.v-card-cat{
+ font-size:10px;font-weight:700;letter-spacing:.1em;
+ text-transform:uppercase;color:var(--red);
+ margin-bottom:5px;
+}
+.v-card-title{
+ font-size:13px;font-weight:600;
+ color:var(--text);line-height:1.4;
+ display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
+ overflow:hidden;margin-bottom:6px;
+}
+.v-card-sponsor-link{
+ display:block;font-size:11px;font-weight:600;color:var(--gold);
+ text-decoration:none;margin-top:4px;opacity:0;transition:opacity .2s;
+}
+.v-card:hover .v-card-sponsor-link{opacity:1}
+
+/* thumb gradient fallback */
+.thumb-gradient{
+ width:100%;height:100%;
+ background:linear-gradient(135deg,var(--bg4),#1d2535);
+ display:flex;align-items:center;justify-content:center;
+ color:var(--text2);font-size:28px;
+}
+
+/* ── SPONSORED RAIL ────────────────────────────────────────────────── */
+.rail.sponsored .rail-title{color:var(--gold)}
+.rail.sponsored .rail-title::after{
+ content:'SPONSORED';
+ font-size:9px;font-family:'Inter',sans-serif;font-weight:700;
+ letter-spacing:.15em;color:var(--gold);
+ background:rgba(240,180,41,0.12);border:1px solid rgba(240,180,41,0.25);
+ padding:2px 6px;border-radius:3px;margin-left:10px;vertical-align:middle;
+}
+
+/* ── LIGHTBOX MODAL ─────────────────────────────────────────────────── */
+#modal-overlay{
+ display:none;position:fixed;inset:0;z-index:2000;
+ background:rgba(4,6,8,0.94);
+ backdrop-filter:blur(8px);
+ align-items:center;justify-content:center;
+ animation:fadeIn .2s ease;
+}
+#modal-overlay.open{display:flex}
+@keyframes fadeIn{from{opacity:0}to{opacity:1}}
+#modal-box{
+ position:relative;
+ width:min(860px,92vw);
+ background:var(--bg2);
+ border-radius:10px;
+ overflow:hidden;
+ box-shadow:0 30px 80px rgba(0,0,0,0.7);
+ border:1px solid var(--border);
+ animation:slideUp .25s ease;
+}
+@keyframes slideUp{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
+#modal-video-wrap{
+ position:relative;width:100%;aspect-ratio:16/9;
+ background:#000;
+}
+#modal-video-wrap iframe,#modal-video-wrap video{
+ position:absolute;inset:0;width:100%;height:100%;border:0;
+}
+#modal-info{padding:18px 22px 20px}
+#modal-eyebrow{
+ font-size:11px;font-weight:700;letter-spacing:.12em;
+ text-transform:uppercase;color:var(--red);margin-bottom:6px;
+}
+#modal-title{
+ font-family:'Playfair Display',serif;
+ font-size:clamp(16px,2vw,22px);font-weight:700;
+ color:var(--text);margin-bottom:8px;line-height:1.3;
+}
+#modal-desc{
+ font-size:13px;color:var(--text2);line-height:1.6;
+ display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;
+ overflow:hidden;
+}
+#modal-close{
+ position:absolute;top:12px;right:14px;
+ background:rgba(0,0,0,0.5);border:none;
+ color:var(--text);font-size:22px;line-height:1;
+ width:34px;height:34px;border-radius:50%;
+ cursor:pointer;display:flex;align-items:center;justify-content:center;
+ transition:background .2s;z-index:10;
+}
+#modal-close:hover{background:var(--red);color:#fff}
+
+/* ── FOOTER ─────────────────────────────────────────────────────────── */
+#site-footer{
+ background:var(--bg2);
+ border-top:1px solid var(--border);
+ padding:48px 32px 32px;
+}
+.footer-inner{
+ max-width:1100px;margin:0 auto;
+ display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:32px;
+}
+@media(max-width:700px){.footer-inner{grid-template-columns:1fr 1fr}}
+.footer-brand .wordmark{font-size:24px;display:inline-block;margin-bottom:12px}
+.footer-brand p{font-size:13px;color:var(--text2);line-height:1.6;max-width:220px}
+.footer-col h4{
+ font-size:11px;font-weight:700;letter-spacing:.1em;
+ text-transform:uppercase;color:var(--text2);
+ margin-bottom:14px;
+}
+.footer-col ul{list-style:none}
+.footer-col li{margin-bottom:8px}
+.footer-col a{
+ color:var(--text2);text-decoration:none;font-size:13px;
+ transition:color .2s;
+}
+.footer-col a:hover{color:var(--text)}
+.footer-bottom{
+ max-width:1100px;margin:32px auto 0;
+ padding-top:20px;border-top:1px solid var(--border);
+ display:flex;align-items:center;justify-content:space-between;
+ flex-wrap:wrap;gap:12px;
+}
+.footer-bottom p{font-size:12px;color:rgba(155,168,184,0.5)}
+
+/* ── SWITCH CHIP ─────────────────────────────────────────────────────── */
+#switch-chip{
+ position:fixed;bottom:24px;right:24px;z-index:800;
+ display:inline-flex;align-items:center;gap:7px;
+ background:rgba(7,9,12,0.9);
+ border:1px solid var(--border);
+ backdrop-filter:blur(10px);
+ color:var(--text2);font-size:12px;font-weight:600;
+ padding:8px 14px;border-radius:20px;
+ text-decoration:none;
+ transition:all .2s;
+ box-shadow:0 4px 20px rgba(0,0,0,0.4);
+}
+#switch-chip:hover{color:var(--text);border-color:rgba(255,255,255,0.3)}
+#switch-chip .chip-gem{color:var(--gold)}
+
+/* ── LOADING SKELETON ────────────────────────────────────────────────── */
+.skel{
+ background:linear-gradient(90deg,var(--bg3) 25%,var(--bg4) 50%,var(--bg3) 75%);
+ background-size:400% 100%;
+ animation:shimmer 1.5s infinite;
+ border-radius:var(--radius);
+}
+@keyframes shimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}
+.skel-card{
+ flex:0 0 var(--card-w);min-height:200px;
+}
+
+/* ── HERO SKELETON ────────────────────────────────────────────────────── */
+#hero-skeleton{
+ aspect-ratio:16/7;min-height:300px;
+ background:var(--bg2);
+ display:flex;align-items:flex-end;
+ padding:40px 5%;
+}
+.hero-skel-content{display:flex;flex-direction:column;gap:12px;max-width:400px}
+.hero-skel-eyebrow{height:12px;width:120px;border-radius:3px}
+.hero-skel-title{height:48px;width:340px;border-radius:4px}
+.hero-skel-desc{height:36px;width:280px;border-radius:4px}
+.hero-skel-btns{height:44px;width:200px;border-radius:4px}
+
+/* ── UTILITY ─────────────────────────────────────────────────────────── */
+.visually-hidden{
+ position:absolute;width:1px;height:1px;
+ padding:0;overflow:hidden;clip:rect(0,0,0,0);
+ white-space:nowrap;border:0;
+}
+</style>
+</head>
+<body>
+
+<!-- STICKY HEADER -->
+<header id="site-header" role="banner">
+ <div class="hd-left">
+ <button class="hd-btn" id="btn-explore" aria-label="Explore menu">
+ <svg width="16" height="12" viewBox="0 0 16 12" fill="currentColor" aria-hidden="true">
+ <rect width="16" height="2" rx="1"/><rect y="5" width="16" height="2" rx="1"/>
+ <rect y="10" width="16" height="2" rx="1"/>
+ </svg>
+ Explore
+ </button>
+ </div>
+
+ <a href="/" class="wordmark" aria-label="RENTV Home">REN<span>TV</span></a>
+
+ <div class="hd-right">
+ <button class="hd-btn" id="btn-sections">Sections</button>
+ <a href="/advertise.html" class="hd-btn primary">Advertise</a>
+ </div>
+</header>
+
+<!-- CONTROLS BAR (sort + density) -->
+<div id="controls-bar" role="toolbar" aria-label="Video controls">
+ <label for="sort-select">
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true">
+ <path d="M1 2h12M3 6h8M5 10h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/>
+ </svg>
+ Sort
+ <select id="sort-select" aria-label="Sort videos">
+ <option value="newest">Newest</option>
+ <option value="mostviewed">Most Viewed</option>
+ <option value="titleaz">Title A→Z</option>
+ </select>
+ </label>
+
+ <label>
+ <svg width="13" height="13" viewBox="0 0 13 13" fill="currentColor" aria-hidden="true">
+ <rect x="0" y="0" width="5" height="5" rx="1"/>
+ <rect x="8" y="0" width="5" height="5" rx="1"/>
+ <rect x="0" y="8" width="5" height="5" rx="1"/>
+ <rect x="8" y="8" width="5" height="5" rx="1"/>
+ </svg>
+ Size
+ <span class="density-wrap">
+ <input type="range" id="density-slider" min="200" max="380" step="10" value="280" aria-label="Card density / size">
+ <span class="density-label" id="density-label" aria-live="polite">M</span>
+ </span>
+ </label>
+</div>
+
+<!-- HERO SECTION -->
+<section id="hero" aria-label="Featured video">
+ <!-- Skeleton shown while loading -->
+ <div id="hero-skeleton" aria-hidden="true">
+ <div class="hero-skel-content">
+ <div class="skel hero-skel-eyebrow"></div>
+ <div class="skel hero-skel-title"></div>
+ <div class="skel hero-skel-desc"></div>
+ <div class="skel hero-skel-btns"></div>
+ </div>
+ </div>
+ <div id="hero-inner" style="display:none" role="img" aria-labelledby="hero-title">
+ <div id="hero-thumb">
+ <img id="hero-img" src="" alt="" loading="eager">
+ </div>
+ <div id="hero-gradient" aria-hidden="true"></div>
+ <div id="hero-content">
+ <div class="hero-eyebrow" id="hero-eyebrow">Featured</div>
+ <h1 class="hero-title" id="hero-title">Loading…</h1>
+ <p class="hero-desc" id="hero-desc"></p>
+ <div class="hero-btns">
+ <button class="hero-play" id="hero-play-btn" aria-label="Watch featured video">
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
+ <path d="M3 2l11 6-11 6V2z"/>
+ </svg>
+ Watch
+ </button>
+ <a href="/advertise.html" class="hero-advert">Advertise on RENTV</a>
+ </div>
+ </div>
+ <button id="hero-play-icon" aria-label="Play featured video">
+ <svg width="26" height="26" viewBox="0 0 26 26" fill="#fff" aria-hidden="true">
+ <path d="M5 3l18 10L5 23V3z"/>
+ </svg>
+ </button>
+ </div>
+</section>
+
+<!-- AD MASTHEAD -->
+<div id="ad-masthead" aria-label="Advertisement">
+ <div data-ad-slot="review-masthead" role="complementary"></div>
+</div>
+
+<!-- RAILS -->
+<main id="rails" aria-label="Video library">
+ <!-- Skeleton rails while loading -->
+ <div id="rails-skeleton" aria-hidden="true">
+ <div class="rail" id="skel-rail-1">
+ <div class="rail-header"><div class="rail-title skel" style="width:160px;height:22px;border-radius:3px"></div></div>
+ <div class="rail-row">
+ <div class="skel skel-card"></div>
+ <div class="skel skel-card"></div>
+ <div class="skel skel-card"></div>
+ <div class="skel skel-card"></div>
+ <div class="skel skel-card"></div>
+ </div>
+ </div>
+ </div>
+ <div id="rails-content" style="display:none"></div>
+</main>
+
+<!-- AD FOOTER BAND -->
+<div id="ad-footer" aria-label="Advertisement">
+ <div data-ad-slot="footer-band" role="complementary"></div>
+</div>
+
+<!-- FOOTER -->
+<footer id="site-footer" role="contentinfo">
+ <div class="footer-inner">
+ <div class="footer-brand">
+ <a href="/" class="wordmark" aria-label="RENTV Home">REN<span>TV</span></a>
+ <p>The Western U.S. commercial real estate authority since 1998. Video-first CRE news, intelligence, and deal coverage.</p>
+ </div>
+ <div class="footer-col">
+ <h4>Sections</h4>
+ <ul>
+ <li><a href="/deals.html">Deals</a></li>
+ <li><a href="/cre-talk.html">CRE Talk</a></li>
+ <li><a href="/conferences.html">Events</a></li>
+ <li><a href="/closings.html">Closings</a></li>
+ <li><a href="/directory.html">Directory</a></li>
+ </ul>
+ </div>
+ <div class="footer-col">
+ <h4>Intelligence</h4>
+ <ul>
+ <li><a href="/deals-log.html">Deal Log</a></li>
+ <li><a href="/hub.html">Market Hub</a></li>
+ <li><a href="/article.html">Analysis</a></li>
+ <li><a href="/desk.html">Desk</a></li>
+ </ul>
+ </div>
+ <div class="footer-col">
+ <h4>Company</h4>
+ <ul>
+ <li><a href="/about.html">About</a></li>
+ <li><a href="/advertise.html">Advertise</a></li>
+ <li><a href="/audience.html">Audience</a></li>
+ <li><a href="/consulting">Consulting</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <p>© 2026 RENTV / rentvreview.com. All rights reserved.</p>
+ <p>The REview — Face to Face with the Dealmakers</p>
+ </div>
+</footer>
+
+<!-- TEMPLATE SWITCH CHIP -->
+<a href="/2026/" id="switch-chip" aria-label="Switch 2026 template">
+ <span class="chip-gem">◆</span>
+ 2026 Templates — switch
+</a>
+
+<!-- LIGHTBOX MODAL -->
+<div id="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-hidden="true">
+ <div id="modal-box">
+ <button id="modal-close" aria-label="Close video">×</button>
+ <div id="modal-video-wrap" aria-label="Video player"></div>
+ <div id="modal-info">
+ <div id="modal-eyebrow"></div>
+ <h2 id="modal-title"></h2>
+ <p id="modal-desc"></p>
+ </div>
+ </div>
+</div>
+
+<!-- AD SLOT SCRIPT -->
+<script src="/ad-slot.js" defer></script>
+
+<script>
+/* ──────────────────────────────────────────────────────────────
+ RENTV CINEMATIC — Template C 2026
+ ────────────────────────────────────────────────────────────── */
+(function(){
+'use strict';
+
+// ── CONSTANTS ───────────────────────────────────────────────
+const LS_SORT = 'rentv_c_sort';
+const LS_DENSITY = 'rentv_c_density';
+const RAIL_ORDER = [
+ 'Featured',
+ 'RENTV Originals',
+ 'JLL TV',
+ 'Most Recent',
+ 'Industry Events',
+ 'Most Viewed',
+ 'CRE Talk',
+ 'CRE Talk · Clips',
+ 'Deal Wire · Video',
+];
+
+// cat → sponsored-rail flag (first rail = sponsored concept)
+const SPONSORED_CAT = 'Featured';
+
+// ── STATE ────────────────────────────────────────────────────
+let allItems = [];
+let heroItem = null;
+let sortMode = localStorage.getItem(LS_SORT) || 'newest';
+let densityVal = parseInt(localStorage.getItem(LS_DENSITY) || '280', 10);
+
+// ── SORT CONTROL ─────────────────────────────────────────────
+const sortSelect = document.getElementById('sort-select');
+sortSelect.value = sortMode;
+sortSelect.addEventListener('change', () => {
+ sortMode = sortSelect.value;
+ localStorage.setItem(LS_SORT, sortMode);
+ rebuildRails();
+});
+
+// ── DENSITY CONTROL ──────────────────────────────────────────
+const densitySlider = document.getElementById('density-slider');
+const densityLabel = document.getElementById('density-label');
+
+function applyDensity(val){
+ document.documentElement.style.setProperty('--card-w', val + 'px');
+ // Update gradient on slider track
+ const pct = ((val - 200) / (380 - 200)) * 100;
+ densitySlider.style.background =
+ `linear-gradient(to right,var(--red) 0%,var(--red) ${pct}%,var(--bg4) ${pct}%)`;
+ const labels = {200:'XS',240:'S',280:'M',320:'L',380:'XL'};
+ densityLabel.textContent = val <= 210 ? 'XS' : val <= 255 ? 'S' : val <= 305 ? 'M' : val <= 350 ? 'L' : 'XL';
+}
+
+densitySlider.value = densityVal;
+applyDensity(densityVal);
+densitySlider.addEventListener('input', () => {
+ densityVal = parseInt(densitySlider.value, 10);
+ localStorage.setItem(LS_DENSITY, densityVal);
+ applyDensity(densityVal);
+});
+
+// ── SORT FUNCTION ─────────────────────────────────────────────
+function sortItems(items){
+ const arr = [...items];
+ if(sortMode === 'newest'){
+ arr.sort((a,b)=>{
+ const da = a.date ? new Date(a.date).getTime() : 0;
+ const db = b.date ? new Date(b.date).getTime() : 0;
+ return db - da;
+ });
+ } else if(sortMode === 'mostviewed'){
+ arr.sort((a,b)=> (b.plays||0) - (a.plays||0));
+ } else if(sortMode === 'titleaz'){
+ arr.sort((a,b)=> (a.title||'').localeCompare(b.title||''));
+ }
+ return arr;
+}
+
+// ── THUMB HELPER ─────────────────────────────────────────────
+function thumbUrl(item){
+ if(item.thumb && !item.thumb.includes('undefined')){
+ return item.thumb;
+ }
+ return null;
+}
+
+// ── ESCAPE HTML ───────────────────────────────────────────────
+function esc(str){
+ if(!str) return '';
+ return String(str)
+ .replace(/&/g,'&')
+ .replace(/</g,'<')
+ .replace(/>/g,'>')
+ .replace(/"/g,'"')
+ .replace(/'/g,''');
+}
+
+// ── CARD BUILDER ──────────────────────────────────────────────
+function buildCard(item){
+ const div = document.createElement('div');
+ div.className = 'v-card';
+ div.setAttribute('role','button');
+ div.setAttribute('tabindex','0');
+ div.setAttribute('aria-label', 'Play: ' + (item.title||'Video'));
+
+ const thumb = thumbUrl(item);
+ const sp = item.sponsorship || {};
+ const sold = sp.status === 'sold';
+ const flagHtml = sold
+ ? `<span class="v-card-flag sponsored">SPONSORED · ${esc(sp.advertiser||'')}</span>`
+ : `<a class="v-card-flag available" href="/advertise.html?video=${esc(item.uid)}" title="Sponsor this video" tabindex="-1">Sponsor this video →</a>`;
+
+ div.innerHTML = `
+ <div class="v-card-thumb">
+ ${thumb
+ ? `<img src="${esc(thumb)}" alt="${esc(item.title||'')}" loading="lazy">`
+ : `<div class="thumb-gradient">🎬</div>`
+ }
+ <div class="v-card-play" aria-hidden="true">
+ <div class="v-card-play-btn">
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="#fff">
+ <path d="M2 1l11 6-11 6V1z"/>
+ </svg>
+ </div>
+ </div>
+ ${flagHtml}
+ </div>
+ <div class="v-card-body">
+ <div class="v-card-cat">${esc(item.cat||'')}</div>
+ <div class="v-card-title">${esc(item.title||'Untitled')}</div>
+ ${!sold ? `<a class="v-card-sponsor-link" href="/advertise.html?video=${esc(item.uid)}" tabindex="-1">Sponsor this video →</a>` : ''}
+ </div>`;
+
+ // click / keypress to open modal
+ const open = (e)=>{
+ e.preventDefault();
+ openModal(item);
+ };
+ div.addEventListener('click', open);
+ div.addEventListener('keydown',(e)=>{
+ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); openModal(item); }
+ });
+
+ return div;
+}
+
+// ── RAIL BUILDER ─────────────────────────────────────────────
+function buildRail(catName, items, isSponsored){
+ if(!items || !items.length) return null;
+
+ const rail = document.createElement('div');
+ rail.className = 'rail' + (isSponsored?' sponsored':'');
+ rail.setAttribute('aria-label', catName + ' videos');
+
+ // header
+ const hdr = document.createElement('div');
+ hdr.className = 'rail-header';
+ hdr.innerHTML = `
+ <h2 class="rail-title">${esc(catName)}</h2>
+ <span class="rail-count">${items.length} video${items.length!==1?'s':''}</span>
+ <a class="rail-all" href="/cre-talk.html" aria-label="View all ${esc(catName)} videos">View all →</a>
+ `;
+ rail.appendChild(hdr);
+
+ // scroll wrap
+ const wrap = document.createElement('div');
+ wrap.className = 'rail-scroll-wrap';
+
+ // arrows
+ const leftArr = document.createElement('button');
+ leftArr.className = 'rail-arrow left';
+ leftArr.setAttribute('aria-label', 'Scroll ' + catName + ' left');
+ leftArr.innerHTML = `<svg width="10" height="16" viewBox="0 0 10 16" fill="currentColor"><path d="M8 1L2 8l6 7"/></svg>`;
+
+ const rightArr = document.createElement('button');
+ rightArr.className = 'rail-arrow right';
+ rightArr.setAttribute('aria-label', 'Scroll ' + catName + ' right');
+ rightArr.innerHTML = `<svg width="10" height="16" viewBox="0 0 10 16" fill="currentColor"><path d="M2 1l6 7-6 7"/></svg>`;
+
+ // row
+ const row = document.createElement('div');
+ row.className = 'rail-row';
+ row.setAttribute('role','list');
+
+ const sorted = sortItems(items);
+ sorted.forEach(item => {
+ const card = buildCard(item);
+ card.setAttribute('role','listitem');
+ row.appendChild(card);
+ });
+
+ // arrow scroll
+ const SCROLL_AMT = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--card-w')) + 14 || 294;
+ leftArr.addEventListener('click', ()=> row.scrollBy({left: -SCROLL_AMT*2, behavior:'smooth'}));
+ rightArr.addEventListener('click', ()=> row.scrollBy({left: SCROLL_AMT*2, behavior:'smooth'}));
+
+ wrap.appendChild(leftArr);
+ wrap.appendChild(row);
+ wrap.appendChild(rightArr);
+ rail.appendChild(wrap);
+ return rail;
+}
+
+// ── REBUILD RAILS ─────────────────────────────────────────────
+function rebuildRails(){
+ const container = document.getElementById('rails-content');
+ container.innerHTML = '';
+
+ // group by cat
+ const grouped = {};
+ allItems.forEach(item => {
+ const c = item.cat || 'Other';
+ if(!grouped[c]) grouped[c] = [];
+ grouped[c].push(item);
+ });
+
+ // render in RAIL_ORDER, then any extras
+ const rendered = new Set();
+ RAIL_ORDER.forEach((cat, idx) => {
+ if(!grouped[cat] || !grouped[cat].length) return;
+ const rail = buildRail(cat, grouped[cat], cat === SPONSORED_CAT);
+ if(rail) container.appendChild(rail);
+ rendered.add(cat);
+ });
+
+ // any categories not in RAIL_ORDER
+ Object.keys(grouped).forEach(cat => {
+ if(rendered.has(cat)) return;
+ if(!grouped[cat].length) return;
+ const rail = buildRail(cat, grouped[cat], false);
+ if(rail) container.appendChild(rail);
+ });
+}
+
+// ── HERO RENDER ───────────────────────────────────────────────
+function renderHero(item){
+ if(!item) return;
+ heroItem = item;
+
+ const skeleton = document.getElementById('hero-skeleton');
+ const inner = document.getElementById('hero-inner');
+
+ const img = document.getElementById('hero-img');
+ const t = thumbUrl(item);
+ if(t){ img.src = t; img.alt = item.title||''; }
+ else { img.style.display='none'; }
+
+ document.getElementById('hero-eyebrow').textContent = item.cat || 'Featured';
+ document.getElementById('hero-title').textContent = item.title || 'RENTV';
+ document.getElementById('hero-desc').textContent = (item.desc||'').slice(0,180);
+
+ skeleton.style.display = 'none';
+ inner.style.display = 'block';
+
+ const playBtn = document.getElementById('hero-play-btn');
+ const playIcon = document.getElementById('hero-play-icon');
+ const doPlay = ()=> openModal(item);
+ playBtn.addEventListener('click', doPlay);
+ playIcon.addEventListener('click', doPlay);
+}
+
+// ── MODAL ─────────────────────────────────────────────────────
+const overlay = document.getElementById('modal-overlay');
+const modalBox = document.getElementById('modal-box');
+const modalVid = document.getElementById('modal-video-wrap');
+const modalClose= document.getElementById('modal-close');
+const modalEye = document.getElementById('modal-eyebrow');
+const modalTitl = document.getElementById('modal-title');
+const modalDesc = document.getElementById('modal-desc');
+
+function openModal(item){
+ // build video player
+ modalVid.innerHTML = '';
+ if(item.mp4){
+ const vid = document.createElement('video');
+ vid.src = item.mp4;
+ vid.controls = true;
+ vid.autoplay = true;
+ vid.style.cssText='position:absolute;inset:0;width:100%;height:100%;background:#000';
+ modalVid.appendChild(vid);
+ } else if(item.embed && !item.embed.includes('undefined')){
+ const fr = document.createElement('iframe');
+ fr.src = item.embed + (item.embed.includes('?')?'&':'?') + 'autoplay=1';
+ fr.allow = 'autoplay; fullscreen; picture-in-picture';
+ fr.allowFullscreen = true;
+ fr.setAttribute('loading','lazy');
+ modalVid.appendChild(fr);
+ } else if(item.view_url){
+ // fallback: link out
+ modalVid.innerHTML = `
+ <div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
+ background:var(--bg3);flex-direction:column;gap:16px;">
+ <svg width="56" height="56" viewBox="0 0 56 56" fill="none">
+ <circle cx="28" cy="28" r="27" stroke="#c8102e" stroke-width="2"/>
+ <path d="M20 16l22 12-22 12V16z" fill="#c8102e"/>
+ </svg>
+ <a href="${esc(item.view_url)}" target="_blank" rel="noopener"
+ style="color:var(--red);font-weight:700;font-size:15px;text-decoration:none;">
+ Watch on rentvreview.com →
+ </a>
+ </div>`;
+ } else {
+ modalVid.innerHTML = `<div style="position:absolute;inset:0;background:var(--bg3);display:flex;align-items:center;justify-content:center;color:var(--text2)">No preview available</div>`;
+ }
+
+ modalEye.textContent = item.cat || '';
+ modalTitl.textContent = item.title || 'Untitled';
+ modalDesc.textContent = item.desc || '';
+
+ overlay.classList.add('open');
+ overlay.setAttribute('aria-hidden','false');
+ document.body.style.overflow='hidden';
+ modalClose.focus();
+}
+
+function closeModal(){
+ overlay.classList.remove('open');
+ overlay.setAttribute('aria-hidden','true');
+ document.body.style.overflow='';
+ modalVid.innerHTML = ''; // stop video/audio
+}
+
+modalClose.addEventListener('click', closeModal);
+overlay.addEventListener('click', e => { if(e.target===overlay) closeModal(); });
+document.addEventListener('keydown', e => {
+ if(e.key==='Escape' && overlay.classList.contains('open')) closeModal();
+});
+
+// ── FETCH + INIT ─────────────────────────────────────────────
+async function init(){
+ try{
+ // parallel fetch
+ const [invRes, vidRes] = await Promise.all([
+ fetch('/api/ad/inventory'),
+ fetch('/api/videos'),
+ ]);
+ const invData = await invRes.json();
+ const vidData = await vidRes.json();
+
+ allItems = (invData.items || []).filter(i =>
+ i.title && i.uid && !i.uid.includes('undefined')
+ );
+
+ // find hero: prefer pinned from /api/videos, else Featured from inventory
+ const pinnedVid = (vidData.items||[]).find(v => v.pinned);
+ if(pinnedVid){
+ // map to inventory shape
+ heroItem = allItems.find(i =>
+ i.uid === 'yt-'+pinnedVid.id ||
+ i.title === pinnedVid.title
+ ) || {
+ uid: 'yt-' + pinnedVid.id,
+ title: pinnedVid.title,
+ desc: pinnedVid.desc,
+ cat: pinnedVid.cat || 'Featured',
+ thumb: pinnedVid.thumb,
+ embed: pinnedVid.embed,
+ view_url: pinnedVid.view_url,
+ mp4: pinnedVid.mp4,
+ sponsorship: {status:'available'}
+ };
+ } else {
+ heroItem = allItems.find(i => i.cat==='Featured') || allItems[0];
+ }
+
+ renderHero(heroItem);
+
+ // show rails
+ document.getElementById('rails-skeleton').style.display = 'none';
+ document.getElementById('rails-content').style.display = 'block';
+ rebuildRails();
+
+ } catch(err){
+ console.error('RENTV init error:', err);
+ // hide skeletons gracefully
+ document.getElementById('hero-skeleton').innerHTML =
+ '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text2);font-size:14px;">Could not load videos — <a href="/" style="color:var(--red);margin-left:6px;">refresh</a></div>';
+ document.getElementById('rails-skeleton').innerHTML =
+ '<div style="padding:32px;color:var(--text2);font-size:14px;">Could not load video library.</div>';
+ }
+}
+
+// ── SECTIONS MENU (simple scroll reveal) ─────────────────────
+document.getElementById('btn-sections').addEventListener('click', ()=>{
+ document.getElementById('rails').scrollIntoView({behavior:'smooth'});
+});
+document.getElementById('btn-explore').addEventListener('click', ()=>{
+ window.scrollTo({top:0,behavior:'smooth'});
+});
+
+// ── START ────────────────────────────────────────────────────
+init();
+
+})();
+</script>
+</body>
+</html>
diff --git a/public/2026/d.html b/public/2026/d.html
new file mode 100644
index 00000000..a63c35e0
--- /dev/null
+++ b/public/2026/d.html
@@ -0,0 +1,1713 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>RENTV · Western U.S. Commercial Real Estate · Magazine</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,700;0,800;1,600;1,700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+<style>
+/* ─── Design tokens ─── */
+:root {
+ --ink: #14171a;
+ --sub: #5b636b;
+ --hr: #e4e7ea;
+ --red: #c8102e;
+ --accent: #1a3a5c;
+ --gold: #b8860b;
+ --bg: #fafaf8;
+ --white: #ffffff;
+ --serif: 'Playfair Display', Georgia, serif;
+ --sans: 'Inter', system-ui, sans-serif;
+ --cols: 4;
+}
+
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+html { font-size: 16px; scroll-behavior: smooth; }
+body {
+ font-family: var(--sans);
+ background: var(--bg);
+ color: var(--ink);
+ -webkit-font-smoothing: antialiased;
+}
+
+/* ─── Utility ─── */
+.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
+a { color: inherit; text-decoration: none; }
+img { display: block; width: 100%; }
+
+/* ─── Top bar (issue dateline) ─── */
+.top-bar {
+ background: var(--ink);
+ color: #fff;
+ font-family: var(--sans);
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ text-align: center;
+ padding: 7px 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+}
+.top-bar .dateline { opacity: .75; }
+.top-bar .adv-link a {
+ color: #fff;
+ border: 1px solid rgba(255,255,255,.35);
+ border-radius: 2px;
+ padding: 3px 10px;
+ font-size: 10px;
+ letter-spacing: .1em;
+ transition: background .2s;
+}
+.top-bar .adv-link a:hover { background: rgba(255,255,255,.15); }
+
+/* ─── Header ─── */
+.site-header {
+ background: var(--white);
+ border-bottom: 2px solid var(--ink);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+.header-inner {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 24px;
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
+ align-items: center;
+ height: 68px;
+}
+.header-left, .header-right {
+ display: flex;
+ align-items: center;
+ gap: 24px;
+}
+.header-right { justify-content: flex-end; }
+
+.nav-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-family: var(--sans);
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ color: var(--ink);
+ padding: 6px 4px;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ transition: color .2s;
+}
+.nav-btn:hover { color: var(--red); }
+.nav-btn .hamburger {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.nav-btn .hamburger span {
+ display: block;
+ width: 18px;
+ height: 1.5px;
+ background: currentColor;
+ transition: background .2s;
+}
+
+.wordmark {
+ font-family: var(--serif);
+ font-size: 32px;
+ font-weight: 800;
+ letter-spacing: -.01em;
+ line-height: 1;
+ text-decoration: none;
+}
+.wordmark .tv { color: var(--red); }
+
+/* ─── Ad leaderboard ─── */
+.ad-leaderboard-wrap {
+ background: var(--white);
+ border-bottom: 1px solid var(--hr);
+ padding: 10px 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 36px;
+}
+.ad-label {
+ font-size: 9px;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ color: #aaa;
+ margin-bottom: 4px;
+ text-align: center;
+}
+
+/* ─── Section tabs ─── */
+.section-tabs {
+ background: var(--white);
+ border-bottom: 1px solid var(--hr);
+ overflow-x: auto;
+ scrollbar-width: none;
+}
+.section-tabs::-webkit-scrollbar { display: none; }
+.tabs-inner {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 24px;
+ display: flex;
+ gap: 0;
+}
+.tab-link {
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--sub);
+ padding: 13px 18px;
+ border-bottom: 2px solid transparent;
+ white-space: nowrap;
+ transition: color .2s, border-color .2s;
+ text-decoration: none;
+}
+.tab-link:hover, .tab-link.active { color: var(--ink); border-bottom-color: var(--red); }
+
+/* ─── Main container ─── */
+.container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+
+/* ─── Bento grid ─── */
+.bento {
+ display: grid;
+ grid-template-columns: repeat(12, 1fr);
+ grid-template-rows: auto;
+ gap: 2px;
+ background: var(--hr);
+ border: 2px solid var(--hr);
+ margin: 32px 0;
+}
+
+/* Bento cells */
+.bento > * { background: var(--white); }
+
+/* Feature story — spans cols 1-8, row 1-2 */
+.bento-feature {
+ grid-column: 1 / 9;
+ grid-row: 1 / 3;
+ position: relative;
+ overflow: hidden;
+ min-height: 480px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+}
+.bento-feature .bg-img {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform .4s ease;
+}
+.bento-feature:hover .bg-img { transform: scale(1.02); }
+.bento-feature .overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(to top, rgba(0,0,0,.82) 0%, rgba(0,0,0,.3) 55%, rgba(0,0,0,0) 100%);
+}
+.bento-feature .content {
+ position: relative;
+ z-index: 1;
+ padding: 36px 40px;
+ color: #fff;
+}
+.cat-tag {
+ display: inline-block;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ color: var(--red);
+ background: rgba(255,255,255,.12);
+ border: 1px solid rgba(200,16,46,.5);
+ border-radius: 2px;
+ padding: 3px 9px;
+ margin-bottom: 14px;
+}
+.cat-tag.on-light {
+ color: var(--red);
+ background: rgba(200,16,46,.07);
+ border-color: rgba(200,16,46,.25);
+}
+.bento-feature h1 {
+ font-family: var(--serif);
+ font-size: clamp(22px, 2.5vw, 38px);
+ font-weight: 800;
+ line-height: 1.18;
+ margin-bottom: 12px;
+ color: #fff;
+}
+.dek {
+ font-size: 15px;
+ line-height: 1.55;
+ opacity: .85;
+ max-width: 560px;
+}
+.read-more {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 18px;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: #fff;
+ border-bottom: 1px solid rgba(255,255,255,.4);
+ padding-bottom: 2px;
+ transition: border-color .2s;
+}
+.read-more:hover { border-color: var(--red); color: var(--red); }
+.read-more .arrow { font-size: 16px; }
+
+/* Video of the Week — col 9-12, row 1-3 */
+.bento-video-hero {
+ grid-column: 9 / 13;
+ grid-row: 1 / 4;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+.video-hero-label {
+ background: var(--ink);
+ color: #fff;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ padding: 10px 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.video-hero-label .play-icon {
+ width: 20px;
+ height: 20px;
+ background: var(--red);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 8px;
+ flex-shrink: 0;
+}
+.video-embed-wrap {
+ position: relative;
+ flex: 1;
+ min-height: 240px;
+ background: #000;
+}
+.video-embed-wrap iframe {
+ width: 100%;
+ height: 100%;
+ min-height: 240px;
+ border: none;
+ display: block;
+}
+.video-hero-meta {
+ padding: 16px 20px 20px;
+ border-top: 1px solid var(--hr);
+}
+.video-hero-meta h3 {
+ font-family: var(--serif);
+ font-size: 17px;
+ font-weight: 700;
+ line-height: 1.3;
+ margin-bottom: 8px;
+}
+.video-hero-meta p {
+ font-size: 13px;
+ color: var(--sub);
+ line-height: 1.5;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.video-hero-meta .see-all {
+ display: inline-block;
+ margin-top: 12px;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--red);
+}
+.video-hero-meta .see-all:hover { text-decoration: underline; }
+
+/* Story tile — row 3, col 1-4 */
+.bento-story {
+ grid-column: span 4;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+.bento-story .story-img {
+ aspect-ratio: 16/9;
+ overflow: hidden;
+ position: relative;
+ background: var(--hr);
+}
+.bento-story .story-img img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform .35s ease;
+}
+.bento-story:hover .story-img img { transform: scale(1.04); }
+.story-body {
+ padding: 20px 22px 22px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+.story-body h2 {
+ font-family: var(--serif);
+ font-size: 18px;
+ font-weight: 700;
+ line-height: 1.3;
+ margin-bottom: 8px;
+ flex: 1;
+}
+.story-body .meta {
+ font-size: 11px;
+ color: var(--sub);
+ letter-spacing: .04em;
+ text-transform: uppercase;
+ margin-top: 10px;
+}
+
+/* Row 4 — ad tile + 2 stories */
+.bento-ad-cell {
+ grid-column: span 4;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 220px;
+ background: #f5f5f3;
+ padding: 16px;
+ gap: 8px;
+}
+.bento-ad-cell .ad-tag {
+ font-size: 9px;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ color: #bbb;
+ border-top: 1px solid #ddd;
+ padding-top: 6px;
+ width: 100%;
+ text-align: center;
+}
+
+/* Pull-quote tile */
+.bento-pullquote {
+ grid-column: span 4;
+ background: var(--accent);
+ color: #fff;
+ padding: 36px 32px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+.bento-pullquote::before {
+ content: '\201C';
+ position: absolute;
+ top: -20px;
+ left: 16px;
+ font-family: var(--serif);
+ font-size: 160px;
+ line-height: 1;
+ color: rgba(255,255,255,.08);
+ pointer-events: none;
+}
+.bento-pullquote blockquote {
+ font-family: var(--serif);
+ font-size: clamp(17px, 1.5vw, 22px);
+ font-style: italic;
+ font-weight: 600;
+ line-height: 1.5;
+ position: relative;
+ z-index: 1;
+ margin-bottom: 16px;
+}
+.bento-pullquote cite {
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ opacity: .7;
+ font-style: normal;
+}
+
+/* Stats tile */
+.bento-stats {
+ grid-column: span 4;
+ background: var(--ink);
+ color: #fff;
+ padding: 28px 28px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 20px;
+}
+.bento-stats .stats-label {
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 4px;
+}
+.stat-row {
+ display: flex;
+ flex-direction: column;
+ border-bottom: 1px solid rgba(255,255,255,.1);
+ padding-bottom: 14px;
+}
+.stat-row:last-child { border-bottom: none; padding-bottom: 0; }
+.stat-number {
+ font-family: var(--serif);
+ font-size: 36px;
+ font-weight: 800;
+ line-height: 1;
+ color: #fff;
+}
+.stat-desc {
+ font-size: 12px;
+ color: rgba(255,255,255,.6);
+ margin-top: 4px;
+ line-height: 1.4;
+}
+
+/* ─── RENTV Originals strip ─── */
+.section-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 16px;
+ margin-bottom: 20px;
+}
+.section-title {
+ font-family: var(--serif);
+ font-size: 26px;
+ font-weight: 800;
+ line-height: 1;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.section-title::after {
+ content: '';
+ display: block;
+ width: 32px;
+ height: 3px;
+ background: var(--red);
+ border-radius: 2px;
+ margin-left: 6px;
+}
+.see-all-link {
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: var(--red);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+.see-all-link:hover { text-decoration: underline; }
+
+.originals-strip {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 16px;
+ margin-bottom: 48px;
+}
+.original-card {
+ position: relative;
+ border-radius: 3px;
+ overflow: hidden;
+ aspect-ratio: 16/10;
+ cursor: pointer;
+ background: var(--ink);
+}
+.original-card img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ opacity: .75;
+ transition: opacity .3s, transform .4s;
+}
+.original-card:hover img { opacity: .6; transform: scale(1.03); }
+.original-card .oc-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(to top, rgba(0,0,0,.8) 0%, transparent 55%);
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ padding: 18px;
+ color: #fff;
+}
+.original-card .oc-play {
+ width: 36px;
+ height: 36px;
+ background: var(--red);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 10px;
+ font-size: 13px;
+ transition: transform .2s;
+}
+.original-card:hover .oc-play { transform: scale(1.1); }
+.original-card h4 {
+ font-family: var(--serif);
+ font-size: 15px;
+ font-weight: 700;
+ line-height: 1.3;
+ margin-bottom: 4px;
+}
+.original-card p {
+ font-size: 11px;
+ opacity: .75;
+ line-height: 1.4;
+}
+.original-card.fallback-gradient {
+ background: linear-gradient(135deg, #1a3a5c 0%, #0e2240 100%);
+}
+
+/* ─── The REview section ─── */
+.review-section {
+ background: var(--ink);
+ color: #fff;
+ margin: 0 -24px;
+ padding: 44px 24px;
+}
+.review-inner {
+ max-width: 1400px;
+ margin: 0 auto;
+}
+.review-title {
+ font-family: var(--serif);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: .2em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 6px;
+}
+.review-headline {
+ font-family: var(--serif);
+ font-size: 30px;
+ font-weight: 800;
+ color: #fff;
+ margin-bottom: 28px;
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+}
+.review-headline .italic { font-style: italic; color: rgba(255,255,255,.7); }
+.review-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20px;
+}
+.review-vid-card {
+ position: relative;
+ border-radius: 2px;
+ overflow: hidden;
+ background: #111;
+ cursor: pointer;
+}
+.review-vid-card .thumb-wrap {
+ position: relative;
+ aspect-ratio: 16/9;
+ overflow: hidden;
+}
+.review-vid-card .thumb-wrap img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ opacity: .85;
+ transition: opacity .3s, transform .4s;
+}
+.review-vid-card:hover .thumb-wrap img { opacity: .7; transform: scale(1.03); }
+.review-vid-card .play-btn {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.review-vid-card .play-btn span {
+ width: 48px;
+ height: 48px;
+ background: rgba(200,16,46,.92);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ color: #fff;
+ box-shadow: 0 4px 20px rgba(0,0,0,.4);
+ transition: transform .2s, background .2s;
+}
+.review-vid-card:hover .play-btn span { transform: scale(1.12); background: var(--red); }
+.review-vid-card .sponsored-flag {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: var(--gold);
+ color: #fff;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ padding: 3px 8px;
+ border-radius: 2px;
+}
+.review-vid-card .sponsor-hover {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: rgba(0,0,0,.7);
+ color: rgba(255,255,255,.8);
+ font-size: 10px;
+ padding: 4px 10px;
+ border-radius: 2px;
+ opacity: 0;
+ transition: opacity .2s;
+ white-space: nowrap;
+}
+.review-vid-card:hover .sponsor-hover { opacity: 1; }
+.review-vid-meta {
+ padding: 14px 16px 16px;
+ background: rgba(255,255,255,.04);
+}
+.review-vid-meta .cat-tag-dark {
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 6px;
+}
+.review-vid-meta h4 {
+ font-family: var(--serif);
+ font-size: 15px;
+ font-weight: 700;
+ color: #fff;
+ line-height: 1.35;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+/* ─── Departments ─── */
+.departments {
+ margin: 48px 0;
+}
+.dept-grid {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 0;
+ border: 1px solid var(--hr);
+}
+.dept-col {
+ border-right: 1px solid var(--hr);
+ padding: 24px 22px 28px;
+}
+.dept-col:last-child { border-right: none; }
+.dept-name {
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 16px;
+ padding-bottom: 10px;
+ border-bottom: 2px solid var(--ink);
+}
+.dept-story {
+ margin-bottom: 16px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid var(--hr);
+}
+.dept-story:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
+.dept-story a {
+ font-family: var(--serif);
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 1.4;
+ color: var(--ink);
+ display: block;
+ margin-bottom: 5px;
+ transition: color .2s;
+}
+.dept-story a:hover { color: var(--red); }
+.dept-story .dept-meta {
+ font-size: 11px;
+ color: var(--sub);
+}
+
+/* ─── Intelligence / More stories grid ─── */
+.intelligence-section { margin: 48px 0; }
+.grid-controls {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+.grid-ctrl-group {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background: var(--white);
+ border: 1px solid var(--hr);
+ border-radius: 3px;
+ padding: 7px 14px;
+}
+.grid-ctrl-group label {
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ color: var(--sub);
+ white-space: nowrap;
+}
+.grid-ctrl-group select {
+ font-family: var(--sans);
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--ink);
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ outline: none;
+ padding-right: 4px;
+}
+.grid-ctrl-group input[type=range] {
+ width: 90px;
+ accent-color: var(--red);
+ cursor: pointer;
+}
+.grid-ctrl-group .density-val {
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--ink);
+ min-width: 18px;
+}
+
+.story-grid {
+ display: grid;
+ grid-template-columns: repeat(var(--cols), 1fr);
+ gap: 20px;
+ margin-top: 20px;
+}
+.story-card {
+ background: var(--white);
+ border: 1px solid var(--hr);
+ border-radius: 2px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ transition: box-shadow .25s, transform .25s;
+}
+.story-card:hover {
+ box-shadow: 0 8px 32px rgba(0,0,0,.1);
+ transform: translateY(-2px);
+}
+.story-card .img-wrap {
+ aspect-ratio: 16/9;
+ overflow: hidden;
+ background: var(--hr);
+ position: relative;
+}
+.story-card .img-wrap img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform .4s;
+}
+.story-card:hover .img-wrap img { transform: scale(1.04); }
+.story-card .img-wrap .no-img {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--serif);
+ font-size: 28px;
+ font-style: italic;
+ color: rgba(255,255,255,.4);
+}
+.story-card .card-body {
+ padding: 16px 18px 18px;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+.story-card .card-cat {
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 7px;
+}
+.story-card h3 {
+ font-family: var(--serif);
+ font-size: 15px;
+ font-weight: 700;
+ line-height: 1.35;
+ flex: 1;
+ color: var(--ink);
+ transition: color .2s;
+}
+.story-card:hover h3 { color: var(--red); }
+
+/* ─── Footer ─── */
+.site-footer {
+ background: var(--ink);
+ color: #fff;
+ padding: 56px 0 32px;
+ margin-top: 64px;
+}
+.footer-inner {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 24px;
+}
+.footer-top {
+ display: grid;
+ grid-template-columns: 1fr repeat(3, auto);
+ gap: 48px;
+ margin-bottom: 48px;
+}
+.footer-brand .wordmark {
+ font-size: 36px;
+ display: block;
+ margin-bottom: 12px;
+}
+.footer-brand p {
+ font-size: 13px;
+ color: rgba(255,255,255,.55);
+ line-height: 1.7;
+ max-width: 260px;
+}
+.footer-col h5 {
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: .16em;
+ text-transform: uppercase;
+ color: var(--red);
+ margin-bottom: 16px;
+}
+.footer-col a {
+ display: block;
+ font-size: 13px;
+ color: rgba(255,255,255,.65);
+ margin-bottom: 10px;
+ transition: color .2s;
+}
+.footer-col a:hover { color: #fff; }
+.footer-bottom {
+ border-top: 1px solid rgba(255,255,255,.1);
+ padding-top: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+.footer-bottom p {
+ font-size: 12px;
+ color: rgba(255,255,255,.4);
+}
+.footer-bottom .adv-btn {
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: .1em;
+ text-transform: uppercase;
+ color: #fff;
+ border: 1px solid rgba(255,255,255,.3);
+ border-radius: 2px;
+ padding: 8px 20px;
+ transition: background .2s;
+}
+.footer-bottom .adv-btn:hover { background: rgba(255,255,255,.1); }
+
+/* ─── Footer band ad ─── */
+.footer-ad-band {
+ background: #f0efe9;
+ border-top: 2px solid var(--hr);
+ padding: 16px 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+
+/* ─── Switch chip ─── */
+.switch-chip {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background: var(--ink);
+ color: #fff;
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: .08em;
+ padding: 9px 14px;
+ border-radius: 3px;
+ text-decoration: none;
+ z-index: 999;
+ box-shadow: 0 4px 16px rgba(0,0,0,.25);
+ transition: background .2s, transform .2s;
+ white-space: nowrap;
+}
+.switch-chip:hover { background: var(--red); transform: translateY(-1px); }
+
+/* ─── Video lightbox ─── */
+.lightbox {
+ display: none;
+ position: fixed;
+ inset: 0;
+ background: rgba(0,0,0,.92);
+ z-index: 1000;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 16px;
+}
+.lightbox.active { display: flex; }
+.lightbox iframe {
+ width: min(900px, 96vw);
+ aspect-ratio: 16/9;
+ border: none;
+}
+.lightbox-close {
+ position: absolute;
+ top: 20px;
+ right: 24px;
+ background: none;
+ border: none;
+ color: #fff;
+ font-size: 28px;
+ cursor: pointer;
+ opacity: .75;
+ transition: opacity .2s;
+}
+.lightbox-close:hover { opacity: 1; }
+
+/* ─── Gradient fallbacks for missing images ─── */
+.grad-1 { background: linear-gradient(135deg, #1a3a5c 0%, #c8102e 100%); }
+.grad-2 { background: linear-gradient(135deg, #2d5016 0%, #1a3a5c 100%); }
+.grad-3 { background: linear-gradient(135deg, #5c1a3a 0%, #1a2a5c 100%); }
+.grad-4 { background: linear-gradient(135deg, #3a1a0e 0%, #5c3a1a 100%); }
+.grad-5 { background: linear-gradient(135deg, #0e3a2a 0%, #1a3a5c 100%); }
+
+/* ─── Responsive ─── */
+@media (max-width: 1100px) {
+ .bento-feature { grid-column: 1 / 9; min-height: 380px; }
+ .bento-video-hero { grid-column: 9 / 13; }
+ .dept-grid { grid-template-columns: repeat(3, 1fr); }
+ .dept-col:nth-child(3) { border-right: none; }
+ .dept-col:nth-child(4) { border-top: 1px solid var(--hr); }
+ .dept-col:nth-child(5) { border-top: 1px solid var(--hr); border-right: none; }
+ .footer-top { grid-template-columns: 1fr 1fr; }
+}
+
+@media (max-width: 820px) {
+ .bento {
+ display: flex;
+ flex-direction: column;
+ background: var(--bg);
+ gap: 1px;
+ }
+ .bento-feature { min-height: 340px; }
+ .bento-video-hero { min-height: auto; }
+ .originals-strip { grid-template-columns: repeat(2, 1fr); }
+ .review-grid { grid-template-columns: 1fr; }
+ .dept-grid { grid-template-columns: repeat(2, 1fr); }
+ .dept-col { border-right: none; border-bottom: 1px solid var(--hr); }
+ .story-grid { --cols: 2 !important; }
+ .footer-top { grid-template-columns: 1fr; gap: 24px; }
+ .header-inner { grid-template-columns: auto 1fr auto; }
+ .wordmark { font-size: 26px; justify-self: center; }
+ .tabs-inner { gap: 0; }
+ .tab-link { padding: 12px 12px; font-size: 11px; }
+}
+
+@media (max-width: 520px) {
+ .originals-strip { grid-template-columns: 1fr; }
+ .story-grid { --cols: 1 !important; }
+ .bento-feature { min-height: 280px; }
+ .bento-feature h1 { font-size: 20px; }
+ .bento-feature .content { padding: 24px 20px; }
+ .dept-grid { grid-template-columns: 1fr; }
+ .footer-top { grid-template-columns: 1fr; }
+ .switch-chip { bottom: 12px; right: 12px; font-size: 10px; padding: 7px 10px; }
+}
+
+/* skeleton loader */
+.skeleton {
+ background: linear-gradient(90deg, #e8e8e8 25%, #f0f0f0 50%, #e8e8e8 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.4s infinite;
+ border-radius: 2px;
+}
+@keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
+
+.loading-text {
+ color: var(--sub);
+ font-size: 13px;
+ text-align: center;
+ padding: 32px;
+ font-style: italic;
+}
+</style>
+</head>
+<body>
+
+<!-- Top bar -->
+<div class="top-bar">
+ <span class="dateline" id="dateline">Western U.S. Edition</span>
+ <span style="font-size:12px; letter-spacing:.04em; opacity:.5;">The Western U.S. Commercial Real Estate Authority Since 1998</span>
+ <span class="adv-link"><a href="/advertise">Advertise →</a></span>
+</div>
+
+<!-- Site header -->
+<header class="site-header" role="banner">
+ <div class="header-inner">
+ <nav class="header-left" aria-label="Explore menu">
+ <button class="nav-btn" aria-label="Explore menu" aria-expanded="false">
+ <span class="hamburger" aria-hidden="true">
+ <span></span><span></span><span></span>
+ </span>
+ Explore
+ </button>
+ </nav>
+ <a href="/" class="wordmark" aria-label="RENTV home">REN<span class="tv">TV</span></a>
+ <nav class="header-right" aria-label="Sections menu">
+ <button class="nav-btn" aria-label="Sections menu">Sections ≡</button>
+ </nav>
+ </div>
+</header>
+
+<!-- Section tabs -->
+<nav class="section-tabs" aria-label="Content sections">
+ <div class="tabs-inner">
+ <a href="/section/sales" class="tab-link active">Sales</a>
+ <a href="/section/leases" class="tab-link">Leases</a>
+ <a href="/section/financing" class="tab-link">Financing</a>
+ <a href="/section/development" class="tab-link">Development</a>
+ <a href="/section/multifamily" class="tab-link">Multifamily</a>
+ <a href="/section/industrial" class="tab-link">Industrial</a>
+ <a href="/section/retail" class="tab-link">Retail</a>
+ <a href="/review.html" class="tab-link" style="color:var(--red);">The REview ▶</a>
+ </div>
+</nav>
+
+<!-- Leaderboard ad -->
+<div class="ad-leaderboard-wrap">
+ <div>
+ <div class="ad-label">Advertisement</div>
+ <div data-ad-slot="home-leaderboard" style="min-width:320px; min-height:32px;"></div>
+ </div>
+</div>
+
+<!-- Main content -->
+<main>
+ <div class="container">
+
+ <!-- ── BENTO GRID ── -->
+ <section class="bento" aria-label="Top stories" id="bento-grid">
+
+ <!-- Feature story (col 1-8, row 1-2) -->
+ <article class="bento-feature" id="feature-story" aria-label="Lead story">
+ <div class="overlay"></div>
+ <div class="content">
+ <div class="cat-tag" id="feature-cat">Loading…</div>
+ <h1 id="feature-title">Loading top story…</h1>
+ <p class="dek" id="feature-dek"></p>
+ <a href="#" class="read-more" id="feature-link" aria-label="Read full story">
+ Read Story <span class="arrow">→</span>
+ </a>
+ </div>
+ </article>
+
+ <!-- Video of the week (col 9-12, row 1-3) -->
+ <aside class="bento-video-hero" aria-label="Video of the Week">
+ <div class="video-hero-label">
+ <span class="play-icon" aria-hidden="true">▶</span>
+ Video of the Week
+ </div>
+ <div class="video-embed-wrap" id="votw-embed">
+ <div class="loading-text">Loading video…</div>
+ </div>
+ <div class="video-hero-meta" id="votw-meta">
+ <h3 id="votw-title"> </h3>
+ <p id="votw-desc"> </p>
+ <a href="https://www.rentvreview.com" class="see-all" target="_blank" rel="noopener">
+ Watch on The REview →
+ </a>
+ </div>
+ </aside>
+
+ <!-- Story 2 (row 3, col 1-4) -->
+ <article class="bento-story" id="story-2">
+ <div class="story-img"><div class="no-img grad-2" aria-hidden="true"><span>RENTV</span></div></div>
+ <div class="story-body">
+ <div class="cat-tag on-light" id="s2-cat"> </div>
+ <h2><a href="#" id="s2-link"> </a></h2>
+ </div>
+ </article>
+
+ <!-- Story 3 (row 3, col 5-8) -->
+ <article class="bento-story" id="story-3">
+ <div class="story-img"><div class="no-img grad-3" aria-hidden="true"><span>RENTV</span></div></div>
+ <div class="story-body">
+ <div class="cat-tag on-light" id="s3-cat"> </div>
+ <h2><a href="#" id="s3-link"> </a></h2>
+ </div>
+ </article>
+
+ <!-- Pull-quote -->
+ <div class="bento-pullquote" role="complementary" aria-label="Editor's pull quote">
+ <blockquote>
+ "The Western U.S. market is where capital decisions get made first — and RENTV is where the market reads about it."
+ </blockquote>
+ <cite>Steven Bloom, Founder & Publisher</cite>
+ </div>
+
+ <!-- Story 4 (row 4, col 1-4) -->
+ <article class="bento-story" id="story-4">
+ <div class="story-img"><div class="no-img grad-4" aria-hidden="true"><span>RENTV</span></div></div>
+ <div class="story-body">
+ <div class="cat-tag on-light" id="s4-cat"> </div>
+ <h2><a href="#" id="s4-link"> </a></h2>
+ </div>
+ </article>
+
+ <!-- In-feed ad -->
+ <div class="bento-ad-cell" role="complementary" aria-label="Advertisement">
+ <div data-ad-slot="home-infeed-1" style="width:100%;"></div>
+ <div class="ad-tag">Advertisement · <a href="/advertise" style="color:#999;">Sponsor this space →</a></div>
+ </div>
+
+ <!-- Stats tile -->
+ <div class="bento-stats" aria-label="Market statistics">
+ <div class="stats-label">Western Market · 2026</div>
+ <div class="stat-row">
+ <div class="stat-number">$1.6B</div>
+ <div class="stat-desc">BlackRock’s SoCal multifamily portfolio trade</div>
+ </div>
+ <div class="stat-row">
+ <div class="stat-number">5.9msf</div>
+ <div class="stat-desc">PCCP & Stonemont industrial portfolio acquisition</div>
+ </div>
+ <div class="stat-row">
+ <div class="stat-number">$107M</div>
+ <div class="stat-desc">Seattle office & hotel refi landmark deal</div>
+ </div>
+ </div>
+
+ </section>
+ <!-- /bento -->
+
+ <!-- ── RENTV ORIGINALS ── -->
+ <section class="originals-section" aria-labelledby="originals-heading">
+ <div class="section-header">
+ <h2 class="section-title" id="originals-heading">RENTV Originals</h2>
+ <a href="/vid/" class="see-all-link" aria-label="See all RENTV original videos">See all →</a>
+ </div>
+ <div class="originals-strip" id="originals-strip" role="list">
+ <div class="loading-text" style="grid-column:1/-1;">Loading originals…</div>
+ </div>
+ </section>
+
+ </div><!-- /container -->
+
+ <!-- ── THE REVIEW SECTION ── -->
+ <section class="review-section" aria-labelledby="review-heading">
+ <div class="review-inner">
+ <div class="review-title">From The REview — rentvreview.com</div>
+ <h2 class="review-headline" id="review-heading">
+ <span>The REview</span>
+ <span class="italic">— face to face with the dealmakers</span>
+ </h2>
+ <div class="review-grid" id="review-grid" role="list">
+ <div class="loading-text" style="color:rgba(255,255,255,.5);">Loading videos…</div>
+ </div>
+ </div>
+ </section>
+
+ <div class="container">
+
+ <!-- ── DEPARTMENTS ── -->
+ <section class="departments" aria-labelledby="dept-heading">
+ <div class="section-header">
+ <h2 class="section-title" id="dept-heading">Departments</h2>
+ </div>
+ <div class="dept-grid" id="dept-grid" role="navigation" aria-label="Story departments">
+ <div class="loading-text" style="grid-column:1/-1;">Loading departments…</div>
+ </div>
+ </section>
+
+ <!-- ── INTELLIGENCE / MORE STORIES ── -->
+ <section class="intelligence-section" aria-labelledby="intel-heading">
+ <div class="section-header">
+ <h2 class="section-title" id="intel-heading">More Stories</h2>
+ <div class="grid-controls" role="group" aria-label="Grid controls">
+ <div class="grid-ctrl-group">
+ <label for="story-sort">Sort</label>
+ <select id="story-sort" aria-label="Sort stories">
+ <option value="newest">Newest</option>
+ <option value="cat">Category</option>
+ <option value="title">Title A→Z</option>
+ </select>
+ </div>
+ <div class="grid-ctrl-group">
+ <label for="density-slider">Columns</label>
+ <input
+ type="range"
+ id="density-slider"
+ min="1" max="6" step="1" value="4"
+ aria-label="Column density"
+ aria-valuemin="1" aria-valuemax="6"
+ >
+ <span class="density-val" id="density-val" aria-live="polite">4</span>
+ </div>
+ </div>
+ </div>
+ <div class="story-grid" id="story-grid" role="list">
+ <div class="loading-text" style="grid-column:1/-1;">Loading stories…</div>
+ </div>
+ </section>
+
+ </div><!-- /container -->
+</main>
+
+<!-- Footer band ad -->
+<div class="footer-ad-band" role="complementary" aria-label="Advertisement">
+ <div class="ad-label">Advertisement</div>
+ <div data-ad-slot="footer-band"></div>
+</div>
+
+<!-- Footer -->
+<footer class="site-footer" role="contentinfo">
+ <div class="footer-inner">
+ <div class="footer-top">
+ <div class="footer-brand">
+ <a href="/" class="wordmark" aria-label="RENTV home">REN<span class="tv">TV</span></a>
+ <p>The Western U.S. commercial real estate authority since 1998. Covering sales, leases, financing, development, and the people making it happen.</p>
+ </div>
+ <div class="footer-col">
+ <h5>Sections</h5>
+ <a href="/section/sales">Sales</a>
+ <a href="/section/leases">Leases</a>
+ <a href="/section/financing">Financing</a>
+ <a href="/section/development">Development</a>
+ <a href="/section/multifamily">Multifamily</a>
+ <a href="/section/industrial">Industrial</a>
+ <a href="/section/retail">Retail</a>
+ </div>
+ <div class="footer-col">
+ <h5>Intelligence</h5>
+ <a href="/market-pulse.html">Market Pulse</a>
+ <a href="/deals.html">Deal Log</a>
+ <a href="/markets.html">Markets</a>
+ <a href="https://www.rentvreview.com" target="_blank" rel="noopener">The REview</a>
+ <a href="/re-search.html">RE Search</a>
+ </div>
+ <div class="footer-col">
+ <h5>Company</h5>
+ <a href="/about.html">About RENTV</a>
+ <a href="/advertise.html">Advertise</a>
+ <a href="/subscribe.html">Newsletter</a>
+ <a href="/syndicate.html">Syndication</a>
+ <a href="/press/">Press</a>
+ </div>
+ </div>
+ <div class="footer-bottom">
+ <p>© 2026 RENTV. All rights reserved. Western U.S. Edition.</p>
+ <a href="/advertise.html" class="adv-btn">Advertise with RENTV →</a>
+ </div>
+ </div>
+</footer>
+
+<!-- Switch chip -->
+<a href="/2026/" class="switch-chip" aria-label="Switch between 2026 templates">◆ 2026 Templates — switch</a>
+
+<!-- Video lightbox -->
+<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Video player">
+ <button class="lightbox-close" id="lightbox-close" aria-label="Close video">×</button>
+ <iframe id="lightbox-iframe" src="" allow="autoplay; encrypted-media" allowfullscreen title="Video player"></iframe>
+</div>
+
+<!-- Ad system -->
+<script src="/ad-slot.js" defer></script>
+
+<script>
+/* ─── Utility ─── */
+function esc(str) {
+ const d = document.createElement('div');
+ d.textContent = str;
+ return d.innerHTML;
+}
+function gradClass(i) {
+ return 'grad-' + ((i % 5) + 1);
+}
+
+/* ─── Dateline ─── */
+(function() {
+ const el = document.getElementById('dateline');
+ const d = new Date();
+ const fmt = d.toLocaleDateString('en-US', { year:'numeric', month:'long', day:'numeric' });
+ if (el) el.textContent = 'Western U.S. Edition · ' + fmt;
+})();
+
+/* ─── Lightbox ─── */
+const lightbox = document.getElementById('lightbox');
+const lbIframe = document.getElementById('lightbox-iframe');
+const lbClose = document.getElementById('lightbox-close');
+function openLightbox(embedUrl) {
+ lbIframe.src = embedUrl + '?autoplay=1&rel=0';
+ lightbox.classList.add('active');
+ document.body.style.overflow = 'hidden';
+}
+function closeLightbox() {
+ lightbox.classList.remove('active');
+ lbIframe.src = '';
+ document.body.style.overflow = '';
+}
+if (lbClose) lbClose.addEventListener('click', closeLightbox);
+lightbox.addEventListener('click', function(e) { if (e.target === lightbox) closeLightbox(); });
+document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeLightbox(); });
+
+/* ─── Sponsored flag helper ─── */
+function sponsoredFlag(sp) {
+ if (!sp) return '';
+ if (sp.status === 'sold' && sp.advertiser) {
+ return '<span class="sponsored-flag">★ SPONSORED · ' + esc(sp.advertiser) + '</span>';
+ }
+ return '<a href="/advertise?video=' + encodeURIComponent(sp.uid||'') + '" class="sponsor-hover" tabindex="-1" aria-label="Sponsor this video">Sponsor this →</a>';
+}
+
+/* ─── Load videos (pinned + REview grid) ─── */
+async function loadVideos() {
+ try {
+ const r = await fetch('/api/videos');
+ const data = await r.json();
+ const items = data.items || [];
+ const pinned = items.find(v => v.pinned) || items[0];
+ const reviewItems = items.filter(v => !v.pinned && v.yt).slice(0, 3);
+
+ // Video of the week
+ if (pinned) {
+ const embedWrap = document.getElementById('votw-embed');
+ if (pinned.embed || pinned.yt) {
+ const embedSrc = pinned.embed || ('https://www.youtube.com/embed/' + pinned.yt);
+ embedWrap.innerHTML = '<iframe src="' + embedSrc + '?rel=0" allow="accelerometer; autoplay; encrypted-media; gyroscope" allowfullscreen title="' + esc(pinned.title) + '" loading="lazy"></iframe>';
+ } else {
+ embedWrap.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#fff;font-family:var(--serif);font-style:italic;font-size:20px;">RENTV</div>';
+ }
+ const t = document.getElementById('votw-title');
+ const d = document.getElementById('votw-desc');
+ if (t) t.textContent = pinned.title;
+ if (d) d.textContent = pinned.desc || '';
+ }
+
+ // REview grid
+ const reviewGrid = document.getElementById('review-grid');
+ if (reviewItems.length) {
+ reviewGrid.innerHTML = reviewItems.map(function(v, i) {
+ const embedSrc = v.embed || ('https://www.youtube.com/embed/' + v.yt);
+ return '<article class="review-vid-card" role="listitem">' +
+ '<div class="thumb-wrap">' +
+ (v.thumb ? '<img src="' + esc(v.thumb) + '" alt="' + esc(v.title) + '" loading="lazy">' : '<div class="' + gradClass(i) + '" style="width:100%;height:100%;"></div>') +
+ '<div class="play-btn"><span aria-label="Play video">▶</span></div>' +
+ '</div>' +
+ '<div class="review-vid-meta">' +
+ '<div class="cat-tag-dark">' + esc(v.cat || 'Video') + '</div>' +
+ '<h4>' + esc(v.title) + '</h4>' +
+ '</div>' +
+ '</article>';
+ }).join('');
+
+ reviewGrid.querySelectorAll('.review-vid-card').forEach(function(card, i) {
+ const v = reviewItems[i];
+ if (!v) return;
+ const embedSrc = v.embed || ('https://www.youtube.com/embed/' + v.yt);
+ card.addEventListener('click', function() { openLightbox(embedSrc); });
+ card.setAttribute('tabindex', '0');
+ card.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openLightbox(embedSrc); } });
+ });
+ } else {
+ reviewGrid.innerHTML = '<div class="loading-text" style="color:rgba(255,255,255,.5);">No videos available.</div>';
+ }
+ } catch(e) {
+ console.warn('Videos failed:', e);
+ }
+}
+
+/* ─── Load ad inventory for originals strip ─── */
+async function loadOriginals() {
+ const strip = document.getElementById('originals-strip');
+ try {
+ // Use the /api/videos RENTV Originals items first, fall back to inventory
+ const r = await fetch('/api/videos');
+ const data = await r.json();
+ const originals = (data.items || []).filter(function(v) { return v.cat === 'RENTV Originals' || v.id && v.id.startsWith('orig-'); });
+
+ if (!originals.length) {
+ strip.innerHTML = '<p class="loading-text">No originals available.</p>';
+ return;
+ }
+
+ strip.innerHTML = originals.map(function(v, i) {
+ const hasMp4 = v.mp4;
+ const hasThumb = v.thumb;
+ const embedSrc = v.embed || (v.yt ? 'https://www.youtube.com/embed/' + v.yt : null);
+ const cardClass = 'original-card' + (hasThumb ? '' : ' fallback-gradient');
+ return '<div class="' + cardClass + '" role="listitem" tabindex="0" aria-label="' + esc(v.title) + '">' +
+ (hasThumb ? '<img src="' + esc(v.thumb) + '" alt="' + esc(v.title) + '" loading="lazy">' : '') +
+ '<div class="oc-overlay">' +
+ '<div class="oc-play" aria-hidden="true">▶</div>' +
+ '<h4>' + esc(v.title) + '</h4>' +
+ '<p>' + esc(v.desc || '') + '</p>' +
+ '</div>' +
+ '</div>';
+ }).join('');
+
+ strip.querySelectorAll('.original-card').forEach(function(card, i) {
+ const v = originals[i];
+ if (!v) return;
+ const embedSrc = v.embed || (v.yt ? 'https://www.youtube.com/embed/' + v.yt : null);
+ if (embedSrc) {
+ card.addEventListener('click', function() { openLightbox(embedSrc); });
+ card.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openLightbox(embedSrc); } });
+ card.style.cursor = 'pointer';
+ } else if (v.mp4) {
+ card.addEventListener('click', function() {
+ window.open(v.mp4, '_blank');
+ });
+ }
+ });
+ } catch(e) {
+ strip.innerHTML = '<p class="loading-text">Originals unavailable.</p>';
+ console.warn('Originals failed:', e);
+ }
+}
+
+/* ─── Load news ─── */
+let allNews = [];
+
+function catGradient(cat) {
+ const map = { 'Multifamily': 'grad-1', 'Financing': 'grad-2', 'Sales': 'grad-3', 'Industrial': 'grad-4', 'Leases': 'grad-5', 'Retail': 'grad-3', 'Development': 'grad-2' };
+ return map[cat] || 'grad-1';
+}
+
+async function loadNews() {
+ try {
+ const r = await fetch('/api/news');
+ const data = await r.json();
+ allNews = data.items || [];
+
+ if (!allNews.length) return;
+
+ // Feature story (item 0)
+ const feat = allNews[0];
+ const featEl = document.getElementById('feature-story');
+ const catEl = document.getElementById('feature-cat');
+ const titleEl= document.getElementById('feature-title');
+ const dekEl = document.getElementById('feature-dek');
+ const linkEl = document.getElementById('feature-link');
+
+ if (catEl) catEl.textContent = feat.cat || 'News';
+ if (titleEl) titleEl.textContent = feat.title;
+ if (linkEl) { linkEl.href = '/news/' + feat.id; }
+
+ if (feat.image) {
+ const img = document.createElement('img');
+ img.src = feat.image;
+ img.alt = feat.title;
+ img.className = 'bg-img';
+ img.setAttribute('aria-hidden', 'true');
+ img.loading = 'eager';
+ featEl.insertBefore(img, featEl.firstChild);
+ } else {
+ featEl.style.background = 'linear-gradient(135deg, #1a3a5c 0%, #0a1a30 100%)';
+ }
+
+ // Story 2 (item 1)
+ fillBentoStory('story-2', 's2-cat', 's2-link', allNews[1]);
+ // Story 3 (item 2)
+ fillBentoStory('story-3', 's3-cat', 's3-link', allNews[2]);
+ // Story 4 (item 3)
+ fillBentoStory('story-4', 's4-cat', 's4-link', allNews[3]);
+
+ // Departments
+ buildDepartments(allNews);
+
+ // More stories grid (items 4+)
+ renderStoryGrid(allNews);
+
+ } catch(e) {
+ console.warn('News failed:', e);
+ document.getElementById('feature-title').textContent = 'Unable to load stories.';
+ }
+}
+
+function fillBentoStory(storyId, catId, linkId, item) {
+ if (!item) return;
+ const storyEl = document.getElementById(storyId);
+ const catEl = document.getElementById(catId);
+ const linkEl = document.getElementById(linkId);
+
+ if (catEl) catEl.textContent = item.cat || 'News';
+ if (linkEl) { linkEl.href = '/news/' + item.id; linkEl.textContent = item.title; }
+
+ const imgWrap = storyEl ? storyEl.querySelector('.story-img') : null;
+ if (imgWrap) {
+ if (item.image) {
+ imgWrap.innerHTML = '<img src="' + esc(item.image) + '" alt="' + esc(item.title) + '" loading="lazy">';
+ } else {
+ imgWrap.innerHTML = '<div class="no-img ' + catGradient(item.cat) + '" aria-hidden="true"><span>RENTV</span></div>';
+ }
+ }
+}
+
+/* ─── Departments ─── */
+function buildDepartments(items) {
+ const deptGrid = document.getElementById('dept-grid');
+ const depts = ['Sales', 'Financing', 'Development', 'Multifamily', 'Industrial'];
+ const byDept = {};
+ depts.forEach(function(d) { byDept[d] = []; });
+
+ items.forEach(function(item) {
+ const c = item.cat;
+ if (byDept[c]) byDept[c].push(item);
+ else if (c === 'Retail' && byDept['Sales']) byDept['Sales'].push(item);
+ else if (c === 'Leases' && byDept['Industrial']) byDept['Industrial'].push(item);
+ });
+
+ deptGrid.innerHTML = depts.map(function(dept) {
+ const stories = byDept[dept].slice(0, 3);
+ const storyHtml = stories.length
+ ? stories.map(function(s) {
+ return '<div class="dept-story">' +
+ '<a href="/news/' + esc(s.id) + '">' + esc(s.title) + '</a>' +
+ '<span class="dept-meta">' + esc(s.cat) + '</span>' +
+ '</div>';
+ }).join('')
+ : '<div class="dept-story"><span class="dept-meta" style="color:#bbb;">No recent stories</span></div>';
+
+ return '<div class="dept-col" role="listitem">' +
+ '<div class="dept-name">' + esc(dept) + '</div>' +
+ storyHtml +
+ '</div>';
+ }).join('');
+}
+
+/* ─── Story grid with sort + density ─── */
+function renderStoryGrid(items) {
+ const grid = document.getElementById('story-grid');
+ const sort = document.getElementById('story-sort');
+ const density = document.getElementById('density-slider');
+ const densityVal = document.getElementById('density-val');
+
+ // Restore from localStorage
+ const savedSort = localStorage.getItem('rentv-story-sort') || 'newest';
+ const savedDensity = parseInt(localStorage.getItem('rentv-story-density') || '4', 10);
+
+ if (sort) sort.value = savedSort;
+ if (density) density.value = savedDensity;
+ if (densityVal) densityVal.textContent = savedDensity;
+ document.documentElement.style.setProperty('--cols', savedDensity);
+
+ function getSorted() {
+ const mode = sort ? sort.value : 'newest';
+ const list = items.slice(4); // skip bento items
+ if (mode === 'cat') return list.slice().sort(function(a,b) { return (a.cat||'').localeCompare(b.cat||''); });
+ if (mode === 'title') return list.slice().sort(function(a,b) { return a.title.localeCompare(b.title); });
+ return list; // newest = default order
+ }
+
+ function render() {
+ const sorted = getSorted();
+ if (!sorted.length) {
+ grid.innerHTML = '<p class="loading-text">No stories available.</p>';
+ return;
+ }
+ grid.innerHTML = sorted.map(function(item, i) {
+ const hasImg = !!item.image;
+ return '<article class="story-card" role="listitem">' +
+ '<a href="/news/' + esc(item.id) + '" aria-label="' + esc(item.title) + '">' +
+ '<div class="img-wrap">' +
+ (hasImg
+ ? '<img src="' + esc(item.image) + '" alt="' + esc(item.title) + '" loading="lazy">'
+ : '<div class="no-img ' + catGradient(item.cat) + '" aria-hidden="true"><span style="font-family:var(--serif);font-size:22px;font-style:italic;color:rgba(255,255,255,.35);">RENTV</span></div>'
+ ) +
+ '</div>' +
+ '<div class="card-body">' +
+ '<div class="card-cat">' + esc(item.cat || 'News') + '</div>' +
+ '<h3>' + esc(item.title) + '</h3>' +
+ '</div>' +
+ '</a>' +
+ '</article>';
+ }).join('');
+ }
+
+ render();
+
+ if (sort) {
+ sort.addEventListener('change', function() {
+ localStorage.setItem('rentv-story-sort', sort.value);
+ render();
+ });
+ }
+
+ if (density) {
+ density.addEventListener('input', function() {
+ const v = parseInt(density.value, 10);
+ document.documentElement.style.setProperty('--cols', v);
+ if (densityVal) densityVal.textContent = v;
+ localStorage.setItem('rentv-story-density', v);
+ });
+ }
+}
+
+/* ─── Init ─── */
+loadNews();
+loadVideos();
+loadOriginals();
+</script>
+
+</body>
+</html>
diff --git a/public/2026/e.html b/public/2026/e.html
new file mode 100644
index 00000000..23efaf81
--- /dev/null
+++ b/public/2026/e.html
@@ -0,0 +1,1410 @@
+<!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>
+</body>
+</html>
diff --git a/public/2026/index.html b/public/2026/index.html
new file mode 100644
index 00000000..6f072cec
--- /dev/null
+++ b/public/2026/index.html
@@ -0,0 +1,209 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV 2026 — Choose the Site · Template Selector</title>
+<meta name="description" content="RENTV.com 2026 redesign — preview five UI/UX directions and pick the final site.">
+<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&display=swap" rel="stylesheet">
+<style>
+ :root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--wash:#f5f6f8;--gold:#c6a24a}
+ *{box-sizing:border-box;margin:0}
+ body{font-family:Inter,system-ui,sans-serif;color:var(--ink);background:#0b0e12;line-height:1.45}
+ a{color:inherit;text-decoration:none}
+ .top{position:sticky;top:0;z-index:40;display:flex;align-items:center;justify-content:space-between;gap:16px;
+ padding:14px 22px;background:#0b0e12;border-bottom:1px solid #1c232b;color:#e7ecf1}
+ .brand{font-family:'Playfair Display',serif;font-weight:800;font-size:22px;letter-spacing:.02em}.brand span{color:var(--red)}
+ .top .k{font-size:11px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;color:#7b848d}
+ .chosen-tag{font-size:12px;font-weight:700;color:#8b949e}
+ .chosen-tag b{color:var(--gold)}
+ .hero{max-width:1240px;margin:0 auto;padding:34px 22px 8px;color:#e7ecf1}
+ .hero .eye{color:var(--red);font-weight:800;font-size:12px;letter-spacing:.18em;text-transform:uppercase}
+ .hero h1{font-family:'Playfair Display',serif;font-weight:800;font-size:clamp(30px,4vw,50px);line-height:1.05;margin:10px 0 10px;letter-spacing:-.01em}
+ .hero p{color:#aeb6bd;max-width:70ch;font-size:16px}
+ .hero .meta{margin-top:14px;color:#7b848d;font-size:13px}
+ .grid{max-width:1240px;margin:0 auto;padding:22px;display:grid;grid-template-columns:repeat(2,1fr);gap:22px}
+ @media(max-width:820px){.grid{grid-template-columns:1fr}}
+ .card{background:#11161c;border:1px solid #1c232b;border-radius:14px;overflow:hidden;display:flex;flex-direction:column;
+ transition:transform .18s,box-shadow .18s,border-color .18s}
+ .card:hover{transform:translateY(-3px);box-shadow:0 20px 50px rgba(0,0,0,.5);border-color:#2b3644}
+ .card.is-chosen{border-color:var(--gold);box-shadow:0 0 0 2px rgba(198,162,74,.35)}
+ .framewrap{position:relative;height:300px;background:#05080c;overflow:hidden;border-bottom:1px solid #1c232b}
+ .framewrap iframe{position:absolute;top:0;left:0;width:1400px;height:1050px;border:0;
+ transform:scale(.5);transform-origin:top left;pointer-events:none;background:#fff}
+ .framewrap .veil{position:absolute;inset:0;cursor:pointer;background:linear-gradient(180deg,rgba(5,8,12,0) 60%,rgba(5,8,12,.55))}
+ .framewrap .veil:hover{background:linear-gradient(180deg,rgba(200,16,46,.06),rgba(5,8,12,.5))}
+ .framewrap .open{position:absolute;left:14px;bottom:12px;background:rgba(11,14,18,.82);color:#fff;border:1px solid #2b3644;
+ border-radius:7px;padding:8px 13px;font-weight:800;font-size:12px;letter-spacing:.03em;backdrop-filter:blur(4px)}
+ .framewrap .letter{position:absolute;right:12px;top:12px;width:34px;height:34px;border-radius:8px;background:var(--red);color:#fff;
+ display:flex;align-items:center;justify-content:center;font-family:'Playfair Display',serif;font-weight:800;font-size:19px}
+ .cbody{padding:16px 18px 18px;color:#e7ecf1;display:flex;flex-direction:column;gap:8px;flex:1}
+ .cbody h3{font-family:'Playfair Display',serif;font-size:22px;font-weight:800}
+ .cbody .desc{color:#9aa4ad;font-size:14px;flex:1}
+ .cbody .tags{display:flex;flex-wrap:wrap;gap:6px}
+ .cbody .tags span{font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#8b949e;background:#0b0e12;border:1px solid #1c232b;border-radius:20px;padding:3px 9px}
+ .cbody .actions{display:flex;gap:8px;margin-top:6px}
+ .btn{cursor:pointer;border:0;font:inherit;font-weight:800;font-size:13px;border-radius:8px;padding:10px 15px;letter-spacing:.02em}
+ .btn.primary{background:var(--red);color:#fff}.btn.primary:hover{filter:brightness(1.08)}
+ .btn.ghost{background:transparent;color:#e7ecf1;border:1px solid #2b3644}.btn.ghost:hover{border-color:#3d4a5a}
+ .btn.gold{background:var(--gold);color:#1b1a12}.btn.gold:hover{filter:brightness(1.06)}
+ .btn.chosen-badge{background:rgba(198,162,74,.15);color:var(--gold);border:1px solid rgba(198,162,74,.4);cursor:default}
+ footer{max-width:1240px;margin:0 auto;padding:10px 22px 50px;color:#6b747d;font-size:12.5px}
+ footer a{color:#8b949e;text-decoration:underline}
+ /* ---------- Full-screen preview MODAL ---------- */
+ .ovl{position:fixed;inset:0;z-index:100;background:rgba(4,6,9,.82);backdrop-filter:blur(3px);opacity:0;pointer-events:none;transition:opacity .2s;display:flex;flex-direction:column}
+ .ovl.open{opacity:1;pointer-events:auto}
+ .mbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 16px;background:#0b0e12;border-bottom:1px solid #1c232b;color:#e7ecf1}
+ .mbar .switch{display:flex;gap:6px;background:#11161c;border:1px solid #1c232b;border-radius:9px;padding:4px}
+ .mbar .switch button{cursor:pointer;border:0;background:none;color:#9aa4ad;font:inherit;font-weight:800;font-size:13px;padding:7px 12px;border-radius:6px;display:flex;align-items:center;gap:7px}
+ .mbar .switch button .l{width:20px;height:20px;border-radius:5px;background:var(--red);color:#fff;display:flex;align-items:center;justify-content:center;font-family:'Playfair Display',serif;font-size:12px;font-weight:800}
+ .mbar .switch button.on{background:var(--red);color:#fff}
+ .mbar .switch button.on .l{background:#fff;color:var(--red)}
+ .mbar .title{font-family:'Playfair Display',serif;font-weight:800;font-size:18px;margin-right:auto}
+ .mbar .title small{font-family:Inter;font-weight:600;font-size:12px;color:#8b949e;margin-left:8px}
+ .mbar .dev{display:flex;gap:4px;background:#11161c;border:1px solid #1c232b;border-radius:9px;padding:4px}
+ .mbar .dev button{cursor:pointer;border:0;background:none;color:#9aa4ad;font:inherit;font-weight:700;font-size:12px;padding:6px 11px;border-radius:6px}
+ .mbar .dev button.on{background:#e7ecf1;color:#0b0e12}
+ .mbar .x{cursor:pointer;border:1px solid #2b3644;background:transparent;color:#e7ecf1;font-size:16px;line-height:1;border-radius:8px;width:38px;height:38px}
+ .stage{flex:1;overflow:auto;display:flex;justify-content:center;align-items:flex-start;padding:18px;background:#05080c}
+ .stage .device{width:100%;max-width:1400px;height:100%;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.6);transition:max-width .25s}
+ .stage .device.tablet{max-width:834px}.stage .device.phone{max-width:412px}
+ .stage .device iframe{width:100%;height:100%;border:0;display:block}
+ .toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(20px);z-index:200;background:#0b0e12;color:#fff;border:1px solid var(--gold);
+ border-radius:10px;padding:13px 20px;font-weight:700;opacity:0;pointer-events:none;transition:.25s;box-shadow:0 18px 50px rgba(0,0,0,.5)}
+ .toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
+ .toast b{color:var(--gold)}
+</style></head>
+<body>
+<header class="top">
+ <a class="brand" href="/">REN<span>TV</span> <span style="color:#7b848d;font-size:12px;font-family:Inter;font-weight:800;letter-spacing:.14em">/ 2026</span></a>
+ <span class="k">Choose the Site</span>
+ <span class="chosen-tag" id="chosenTag"></span>
+</header>
+
+<section class="hero">
+ <div class="eye">2026 Redesign · Pick the direction</div>
+ <h1>Five ways the new RENTV could look.</h1>
+ <p>A complete 2026 overhaul, video-first to coordinate with <b style="color:#e7ecf1">The REview</b> (rentvreview.com). Each template is a fully working homepage running on live data, with the new ad-slots system built in. Preview any one full-screen, flip between them, then lock your pick with <b style="color:var(--gold)">“Make this the site.”</b></p>
+ <div class="meta">Preview is local only — choosing does not deploy. Going live is a separate step. · <a href="/advertise" style="color:var(--red);font-weight:700">See the ad-slots system →</a></div>
+</section>
+
+<main class="grid" id="grid"></main>
+
+<footer>
+ RENTV.com 2026 template selector · templates <b>A–E</b> live under <code style="color:#8b949e">/2026/{a…e}.html</code> · ad-slots system at <a href="/advertise">/advertise</a>.
+ “Make this the site” records your pick to <code style="color:#8b949e">data/site-template.json</code> (admin-gated); it never publishes on its own.
+</footer>
+
+<!-- Full-screen preview modal -->
+<div class="ovl" id="ovl" role="dialog" aria-modal="true" aria-label="Template preview">
+ <div class="mbar">
+ <div class="switch" id="switch"></div>
+ <div class="title" id="mtitle"></div>
+ <div class="dev" id="dev">
+ <button data-w="desktop" class="on">🖥 Desktop</button>
+ <button data-w="tablet">▭ Tablet</button>
+ <button data-w="phone">▯ Phone</button>
+ </div>
+ <button class="btn gold" id="makeBtn">★ Make this the site</button>
+ <button class="btn ghost" id="openTab">Open ↗</button>
+ <button class="x" id="mclose" aria-label="Close">✕</button>
+ </div>
+ <div class="stage"><div class="device" id="device"><iframe id="mframe" title="Template preview" loading="lazy"></iframe></div></div>
+</div>
+<div class="toast" id="toast"></div>
+
+<script>
+const TPL=[
+ {k:'a',name:'The Video Wall',desc:'A premium streaming-style wall of videos — the whole catalog as a browsable grid. Closest to The REview, video is the hero.',tags:['Video-first','Grid','Streaming']},
+ {k:'b',name:'Editorial Lead',desc:'A newspaper-of-record front page: big lead story, a video rail alongside the news, breaking ticker. Serious CRE journalism, elevated.',tags:['News-led','Video rail','Ticker']},
+ {k:'c',name:'Cinematic',desc:'Netflix-for-CRE. A full-bleed featured video hero over dark, cinematic category rails you scroll sideways. The most immersive.',tags:['Dark','Cinematic','Rails']},
+ {k:'d',name:'Magazine',desc:'A glossy monthly translated to web — an art-directed bento grid of feature tiles, a Video of the Week, departments and pull-quotes.',tags:['Editorial','Bento','Whitespace']},
+ {k:'e',name:'Dashboard',desc:'A Bloomberg-style CRE terminal: market pulse KPIs, a live-video panel, the deal wire and a video library — dense, live, data-forward.',tags:['Terminal','Data','Live']}
+];
+const $=s=>document.querySelector(s), esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
+let CHOSEN=null;
+
+function cardHTML(t){
+ return `<div class="card" data-k="${t.k}" id="card-${t.k}">
+ <div class="framewrap">
+ <iframe src="/2026/${t.k}.html" title="${esc(t.name)} preview" loading="lazy" scrolling="no" aria-hidden="true"></iframe>
+ <div class="veil" data-open="${t.k}"></div>
+ <span class="letter">${t.k.toUpperCase()}</span>
+ <span class="open" data-open="${t.k}">◱ Preview full-screen</span>
+ </div>
+ <div class="cbody">
+ <h3>${esc(t.name)}</h3>
+ <div class="desc">${esc(t.desc)}</div>
+ <div class="tags">${t.tags.map(x=>`<span>${esc(x)}</span>`).join('')}</div>
+ <div class="actions">
+ <button class="btn primary" data-open="${t.k}">Preview</button>
+ <button class="btn ghost" data-make="${t.k}">Make this the site</button>
+ </div>
+ </div>
+ </div>`;
+}
+$('#grid').innerHTML=TPL.map(cardHTML).join('');
+
+// switch buttons in modal
+$('#switch').innerHTML=TPL.map(t=>`<button data-sw="${t.k}"><span class="l">${t.k.toUpperCase()}</span>${esc(t.name)}</button>`).join('');
+
+let cur='a';
+function openModal(k){
+ cur=k; const t=TPL.find(x=>x.k===k);
+ $('#mframe').src='/2026/'+k+'.html';
+ $('#mtitle').innerHTML=esc(t.name)+' <small>Template '+k.toUpperCase()+' · live preview</small>';
+ document.querySelectorAll('#switch button').forEach(b=>b.classList.toggle('on',b.dataset.sw===k));
+ $('#ovl').classList.add('open'); document.body.style.overflow='hidden';
+ refreshMakeBtn();
+}
+function closeModal(){ $('#ovl').classList.remove('open'); document.body.style.overflow=''; $('#mframe').src='about:blank'; }
+function refreshMakeBtn(){
+ const b=$('#makeBtn');
+ if(CHOSEN===cur){ b.textContent='✓ Current pick'; b.classList.remove('gold'); b.classList.add('chosen-badge'); }
+ else { b.textContent='★ Make this the site'; b.classList.add('gold'); b.classList.remove('chosen-badge'); }
+}
+// delegated clicks
+document.addEventListener('click',e=>{
+ const o=e.target.closest('[data-open]'); if(o){ openModal(o.dataset.open); return; }
+ const m=e.target.closest('[data-make]'); if(m){ makeSite(m.dataset.make); return; }
+ const sw=e.target.closest('[data-sw]'); if(sw){ openModal(sw.dataset.sw); return; }
+});
+$('#mclose').onclick=closeModal;
+$('#ovl').addEventListener('click',e=>{ if(e.target===$('#ovl')) closeModal(); });
+document.addEventListener('keydown',e=>{ if(e.key==='Escape'&&$('#ovl').classList.contains('open')) closeModal(); });
+$('#openTab').onclick=()=>window.open('/2026/'+cur+'.html','_blank');
+$('#makeBtn').onclick=()=>makeSite(cur);
+// device width toggle
+$('#dev').addEventListener('click',e=>{ const b=e.target.closest('button[data-w]'); if(!b)return;
+ document.querySelectorAll('#dev button').forEach(x=>x.classList.toggle('on',x===b));
+ const d=$('#device'); d.className='device'+(b.dataset.w==='tablet'?' tablet':b.dataset.w==='phone'?' phone':''); });
+
+function toast(html){ const t=$('#toast'); t.innerHTML=html; t.classList.add('show'); clearTimeout(t._t); t._t=setTimeout(()=>t.classList.remove('show'),3400); }
+
+async function makeSite(k){
+ const t=TPL.find(x=>x.k===k);
+ if(!confirm('Set Template '+k.toUpperCase()+' — “'+t.name+'” — as the chosen RENTV 2026 site?\n\nThis records your pick locally only. It does NOT deploy or publish.')) return;
+ try{
+ const r=await fetch('/api/ad/admin/site-template',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify({chosen:k})});
+ if(r.status===401||r.status===403){ toast('🔒 Sign in as admin to lock the pick. Preview still works. (Pick: <b>'+k.toUpperCase()+'</b>)'); return; }
+ const j=await r.json();
+ if(j&&j.ok){ CHOSEN=k; paintChosen(); refreshMakeBtn(); toast('★ Template <b>'+k.toUpperCase()+' — '+esc(t.name)+'</b> is now the chosen site. (Local pick — deploy is a separate step.)'); }
+ else toast('Could not save the pick'+(j&&j.error?': '+esc(j.error):'.'));
+ }catch(e){ toast('Network error saving the pick.'); }
+}
+function paintChosen(){
+ document.querySelectorAll('.card').forEach(c=>c.classList.toggle('is-chosen',c.dataset.k===CHOSEN));
+ document.querySelectorAll('[data-make]').forEach(b=>{
+ const on=b.dataset.make===CHOSEN;
+ b.textContent=on?'✓ Chosen site':'Make this the site';
+ b.classList.toggle('gold',on); b.classList.toggle('ghost',!on);
+ });
+ const tag=$('#chosenTag');
+ if(CHOSEN){ const t=TPL.find(x=>x.k===CHOSEN); tag.innerHTML='Chosen: <b>'+CHOSEN.toUpperCase()+' · '+esc(t.name)+'</b>'; }
+ else tag.textContent='No template chosen yet';
+}
+(async function initChosen(){
+ try{ const r=await fetch('/api/ad/site-template',{credentials:'same-origin'}); const j=await r.json(); CHOSEN=(j&&j.chosen)||null; }catch(e){}
+ paintChosen();
+})();
+</script>
+</body></html>
diff --git a/public/ad-admin.html b/public/ad-admin.html
new file mode 100644
index 00000000..b9c39b27
--- /dev/null
+++ b/public/ad-admin.html
@@ -0,0 +1,196 @@
+<!doctype html><html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV Ad-Slots — Admin</title>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
+<style>
+ :root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--wash:#f6f7f9;--gold:#c6a24a}
+ *{box-sizing:border-box;margin:0}
+ body{font-family:Inter,system-ui,sans-serif;color:var(--ink);background:var(--wash);line-height:1.45;font-size:14px}
+ a{color:var(--red)}
+ .top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:16px;flex-wrap:wrap;background:#0b0e12;color:#e7ecf1;padding:12px 20px;border-bottom:2px solid var(--red)}
+ .top .brand{font-family:'Playfair Display',serif;font-weight:800;font-size:19px}.top .brand span{color:var(--red)}
+ .top .k{font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#7b848d}
+ .top .who{margin-left:auto;font-size:12px;color:#8b949e}
+ .tabs{display:flex;gap:4px;background:#fff;border-bottom:1px solid var(--line);padding:0 16px;position:sticky;top:47px;z-index:19}
+ .tabs button{border:0;background:none;font:inherit;font-weight:800;color:var(--sub);padding:13px 14px;border-bottom:3px solid transparent;cursor:pointer}
+ .tabs button.on{color:var(--red);border-color:var(--red)}
+ .wrap{max-width:1300px;margin:0 auto;padding:20px}
+ .panel{display:none}.panel.on{display:block}
+ .bar{display:flex;flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:14px}
+ .bar h2{font-family:'Playfair Display',serif;font-size:22px}
+ .bar input.search{flex:1;min-width:200px;max-width:360px;padding:9px 12px;border:1px solid var(--line);border-radius:8px;font:inherit}
+ .coltoggle{display:flex;gap:6px;flex-wrap:wrap}
+ .coltoggle button{font:inherit;font-size:11px;font-weight:700;border:1px solid var(--line);background:#fff;border-radius:16px;padding:4px 10px;cursor:pointer;color:var(--sub)}
+ .coltoggle button.on{background:var(--ink);color:#fff;border-color:var(--ink)}
+ table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden;font-size:13px}
+ th,td{text-align:left;padding:9px 11px;border-bottom:1px solid var(--line);vertical-align:middle}
+ th{background:#f2f4f6;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--sub);cursor:pointer;white-space:nowrap;position:relative;resize:horizontal;overflow:hidden;min-width:60px}
+ th:hover{color:var(--ink)}
+ tr:hover td{background:#fafbfc}
+ td.hide,th.hide{display:none}
+ .st{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;padding:2px 8px;border-radius:20px}
+ .st.available{background:#eafaf0;color:#0f8a4d}.st.reserved{background:#fff6e6;color:#a06a00}.st.sold{background:#fdeaec;color:var(--red)}
+ input.cell,select.cell{font:inherit;font-size:12.5px;padding:6px 8px;border:1px solid var(--line);border-radius:6px;width:100%;max-width:170px;background:#fff}
+ .save{background:var(--ink);color:#fff;border:0;border-radius:6px;padding:7px 12px;font-weight:800;font-size:12px;cursor:pointer}
+ .save:hover{background:var(--red)}
+ .save.done{background:#0f8a4d}
+ .price{font-weight:800;font-variant-numeric:tabular-nums}
+ .thumb{width:70px;height:40px;object-fit:cover;border-radius:5px;background:linear-gradient(135deg,#1f2a37,#c8102e);display:block}
+ /* reservations */
+ .resgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
+ .rescard{background:#fff;border:1px solid var(--line);border-radius:11px;padding:15px 16px;display:flex;flex-direction:column;gap:6px}
+ .rescard .when{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#8a929b;background:var(--wash);border:1px solid var(--line);border-radius:16px;padding:3px 9px;align-self:flex-start}
+ .rescard h4{font-size:15px;font-weight:800}
+ .rescard .kind{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--red)}
+ .rescard .kv{font-size:13px;color:var(--sub)}.rescard .kv b{color:var(--ink)}
+ .rescard .amt{font-family:'Playfair Display',serif;font-size:20px;margin-top:2px}
+ .empty{padding:40px;text-align:center;color:var(--sub);background:#fff;border:1px dashed var(--line);border-radius:10px}
+ .flash{margin-bottom:12px;padding:10px 14px;border-radius:8px;font-weight:600;font-size:13px;display:none}
+ .flash.warn{display:block;background:#fff6e6;border:1px solid #ffe1a8;color:#8a5a00}
+ .chosenbox{background:#fff;border:1px solid var(--line);border-left:4px solid var(--gold);border-radius:10px;padding:14px 16px;margin-bottom:14px;font-size:13px}
+ .chosenbox b{color:var(--gold)}
+</style></head><body>
+<header class="top">
+ <span class="brand">REN<span>TV</span> · Ad-Slots Admin</span>
+ <span class="k">rate card · video sponsorships · reservations</span>
+ <span class="who" id="who">checking session…</span>
+</header>
+<div class="tabs">
+ <button data-tab="placements" class="on">Banner Slots</button>
+ <button data-tab="videos">Video Sponsorships</button>
+ <button data-tab="reservations">Reservations</button>
+</div>
+<div class="wrap">
+ <div class="flash warn" id="flash"></div>
+ <div class="chosenbox" id="chosenbox" style="display:none"></div>
+
+ <!-- PLACEMENTS -->
+ <section class="panel on" id="p-placements">
+ <div class="bar"><h2>Banner & placement rate card</h2></div>
+ <table id="tPlace"><thead><tr>
+ <th>Slot</th><th>Surface</th><th>Format</th><th>Price/mo</th><th>Status</th><th>Advertiser</th><th>Creative URL</th><th>Click URL</th><th>Created</th><th></th>
+ </tr></thead><tbody></tbody></table>
+ </section>
+
+ <!-- VIDEOS -->
+ <section class="panel" id="p-videos">
+ <div class="bar"><h2>Video sponsorship inventory</h2>
+ <input class="search" id="vSearch" placeholder="Search title / category / source (space = AND)…">
+ <div class="coltoggle" id="vCols"></div>
+ </div>
+ <div style="font-size:12px;color:var(--sub);margin-bottom:8px" id="vCount"></div>
+ <table id="tVid"><thead><tr>
+ <th data-c="thumb">Video</th><th data-c="title">Title</th><th data-c="source">Source</th><th data-c="cat">Category</th>
+ <th data-c="plays">Plays</th><th data-c="price">Price/mo</th><th data-c="status">Status</th><th data-c="advertiser">Advertiser</th><th data-c="save"></th>
+ </tr></thead><tbody></tbody></table>
+ <button class="save" id="vMore" style="margin:14px auto 0;display:none">Show more</button>
+ </section>
+
+ <!-- RESERVATIONS -->
+ <section class="panel" id="p-reservations">
+ <div class="bar"><h2>Reservations / buy inquiries</h2><span style="font-size:12px;color:var(--sub)" id="rCount"></span></div>
+ <div class="resgrid" id="resgrid"></div>
+ </section>
+</div>
+
+<script>
+const $=s=>document.querySelector(s),$$=s=>Array.from(document.querySelectorAll(s));
+const esc=s=>(s==null?'':String(s)).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
+const money=n=>'$'+Number(n||0).toLocaleString('en-US');
+// Steve rule: admin cards show created DATE + TIME in admin's local tz
+const fmtDT=iso=>{try{return new Date(iso).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'});}catch(e){return iso||'';}};
+async function jget(u){try{const r=await fetch(u,{credentials:'same-origin'});if(r.status===401||r.status===403){adminGate();return null;}return r.ok?await r.json():null;}catch(e){return null;}}
+async function jpost(u,b){try{const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(b)});if(r.status===401||r.status===403){adminGate();return null;}return await r.json();}catch(e){return null;}}
+let GATED=false;
+function adminGate(){ if(GATED)return; GATED=true; $('#who').textContent='not signed in as admin';
+ $('#flash').className='flash warn'; $('#flash').innerHTML='🔒 You are not signed in as an admin, so writes and the reservations queue are locked. <a href="/login">Sign in</a> then reload. (The public rate card + inventory still load read-only where available.)'; }
+
+/* tabs */
+$$('.tabs button').forEach(b=>b.onclick=()=>{ $$('.tabs button').forEach(x=>x.classList.toggle('on',x===b)); $$('.panel').forEach(p=>p.classList.toggle('on',p.id==='p-'+b.dataset.tab)); });
+
+/* chosen template banner */
+(async()=>{ const j=await jget('/api/ad/site-template'); if(j&&j.chosen){ $('#chosenbox').style.display='block'; $('#chosenbox').innerHTML='Chosen 2026 site: <b>Template '+esc(j.chosen.toUpperCase())+'</b> · set '+esc(fmtDT(j.updated_at))+' · <a href="/2026/">change in the selector →</a>'; } })();
+
+/* ---------- placements ---------- */
+async function loadPlace(){
+ const d=await jget('/api/ad/rate-card'); if(!d)return; $('#who').textContent='admin';
+ const tb=$('#tPlace tbody');
+ tb.innerHTML=(d.placements||[]).map(p=>{
+ const st=p.status||'available';
+ return `<tr data-id="${esc(p.id)}">
+ <td><b>${esc(p.name)}</b><div style="font-size:11px;color:var(--sub)">${esc(p.id)}</div></td>
+ <td>${esc(p.surface)}</td><td style="font-size:12px">${esc(p.format)}</td>
+ <td><input class="cell" data-f="price_monthly" type="number" value="${p.price_monthly||0}" style="max-width:100px"></td>
+ <td><select class="cell" data-f="status"><option ${st==='available'?'selected':''}>available</option><option ${st==='reserved'?'selected':''}>reserved</option><option ${st==='sold'?'selected':''}>sold</option></select></td>
+ <td><input class="cell" data-f="advertiser" value="${esc(p.advertiser||'')}" placeholder="—"></td>
+ <td><input class="cell" data-f="creative_url" value="${esc(p.creative_url||'')}" placeholder="https://…/banner.png"></td>
+ <td><input class="cell" data-f="click_url" value="${esc(p.click_url||'')}" placeholder="https://advertiser…"></td>
+ <td style="font-size:11px;color:var(--sub);white-space:nowrap">🕓 ${esc(fmtDT(p.created_at))}</td>
+ <td><button class="save" data-save-place>Save</button></td>
+ </tr>`;
+ }).join('');
+}
+$('#tPlace').addEventListener('click',async e=>{ const b=e.target.closest('[data-save-place]'); if(!b)return;
+ const tr=b.closest('tr'); const body={id:tr.dataset.id}; tr.querySelectorAll('.cell').forEach(c=>body[c.dataset.f]=c.value);
+ b.textContent='…'; const j=await jpost('/api/ad/admin/placement',body);
+ if(j&&j.ok){ b.textContent='✓ Saved'; b.classList.add('done'); setTimeout(()=>{b.textContent='Save';b.classList.remove('done');},1400); } else b.textContent='Save';
+});
+
+/* ---------- videos ---------- */
+let VID=[],vPage=80;
+const VCOLS=['thumb','title','source','cat','plays','price','status','advertiser'];
+function initVCols(){ $('#vCols').innerHTML=VCOLS.map(c=>`<button data-col="${c}" class="on">${c}</button>`).join('');
+ let hidden={}; try{hidden=JSON.parse(localStorage.getItem('rentv_admin_vcols')||'{}');}catch(e){}
+ VCOLS.forEach(c=>{ if(hidden[c]) toggleCol(c,false); });
+ $('#vCols').querySelectorAll('button').forEach(b=>{ if(hidden[b.dataset.col])b.classList.remove('on'); b.onclick=()=>{const on=!b.classList.contains('on');b.classList.toggle('on',on);toggleCol(b.dataset.col,on);persistCols();}; });
+}
+function toggleCol(c,show){ document.querySelectorAll(`#tVid [data-c="${c}"]`).forEach(el=>el.classList.toggle('hide',!show)); document.querySelectorAll(`#tVid td.col-${c}`).forEach(el=>el.classList.toggle('hide',!show)); }
+function persistCols(){ const hidden={}; $('#vCols').querySelectorAll('button').forEach(b=>{if(!b.classList.contains('on'))hidden[b.dataset.col]=1;}); try{localStorage.setItem('rentv_admin_vcols',JSON.stringify(hidden));}catch(e){} }
+async function loadVid(){ const d=await jget('/api/ad/inventory'); if(!d)return; VID=d.items||[]; renderVid(); }
+function vFilter(){ const q=($('#vSearch').value||'').toLowerCase().split(/\s+/).filter(Boolean);
+ return VID.filter(x=>{ const hay=(x.title+' '+x.cat+' '+x.source+' '+(x.sponsorship.advertiser||'')).toLowerCase(); return q.every(t=>hay.includes(t)); }); }
+function renderVid(){ const v=vFilter(); $('#vCount').textContent=v.length+' videos'; const slice=v.slice(0,vPage);
+ const hiddenBtns=$('#vCols').querySelectorAll('button');
+ $('#tVid tbody').innerHTML=slice.map(x=>{ const sp=x.sponsorship||{}; const st=sp.status||'available';
+ return `<tr data-uid="${esc(x.uid)}">
+ <td class="col-thumb" data-c="thumb">${x.thumb?`<img class="thumb" src="${esc(x.thumb)}" onerror="this.style.visibility='hidden'">`:'<div class="thumb"></div>'}</td>
+ <td class="col-title" data-c="title"><b style="font-weight:600">${esc(x.title||x.uid)}</b></td>
+ <td class="col-source" data-c="source">${esc(x.source)}</td>
+ <td class="col-cat" data-c="cat">${esc(x.cat||'')}</td>
+ <td class="col-plays" data-c="plays" style="font-variant-numeric:tabular-nums">${x.plays||''}</td>
+ <td class="col-price" data-c="price"><input class="cell" data-f="price_monthly" type="number" value="${sp.price_monthly||0}" style="max-width:90px"></td>
+ <td class="col-status" data-c="status"><select class="cell" data-f="status"><option ${st==='available'?'selected':''}>available</option><option ${st==='reserved'?'selected':''}>reserved</option><option ${st==='sold'?'selected':''}>sold</option></select></td>
+ <td class="col-advertiser" data-c="advertiser"><input class="cell" data-f="advertiser" value="${esc(sp.advertiser||'')}" placeholder="—"></td>
+ <td class="col-save" data-c="save"><button class="save" data-save-vid>Save</button></td>
+ </tr>`; }).join('');
+ // re-apply hidden columns
+ hiddenBtns.forEach(b=>{ if(!b.classList.contains('on')) toggleCol(b.dataset.col,false); });
+ $('#vMore').style.display=v.length>vPage?'block':'none';
+}
+$('#vSearch').addEventListener('input',()=>{vPage=80;renderVid();});
+$('#vMore').onclick=()=>{vPage+=80;renderVid();};
+$('#tVid').addEventListener('click',async e=>{ const b=e.target.closest('[data-save-vid]'); if(!b)return;
+ const tr=b.closest('tr'); const body={uid:tr.dataset.uid}; tr.querySelectorAll('.cell').forEach(c=>body[c.dataset.f]=c.value);
+ b.textContent='…'; const j=await jpost('/api/ad/admin/video',body);
+ if(j&&j.ok){ b.textContent='✓'; b.classList.add('done'); const idx=VID.findIndex(x=>x.uid===tr.dataset.uid); if(idx>=0)VID[idx].sponsorship=Object.assign(VID[idx].sponsorship,j.state); setTimeout(()=>{b.textContent='Save';b.classList.remove('done');},1400); } else b.textContent='Save';
+});
+
+/* ---------- reservations ---------- */
+async function loadRes(){ const d=await jget('/api/ad/admin/reservations'); if(!d)return;
+ $('#rCount').textContent=(d.count||0)+' inquiries'; const g=$('#resgrid');
+ if(!d.items||!d.items.length){ g.innerHTML='<div class="empty">No reservations yet. When someone reserves a slot or sponsors a video on <a href="/advertise">/advertise</a>, it lands here.</div>'; return; }
+ g.innerHTML=d.items.map(r=>{ const tot=(r.quoted_monthly||0)*(r.months||1);
+ return `<div class="rescard">
+ <span class="when" title="${esc(r.at)}">🕓 ${esc(fmtDT(r.at))}</span>
+ <span class="kind">${esc(r.kind)} · ${esc(r.months)} mo${r.start?' · from '+esc(r.start):''}</span>
+ <h4>${esc(r.target_name||r.target_id)}</h4>
+ <div class="kv"><b>${esc(r.advertiser||'—')}</b></div>
+ <div class="kv">${esc(r.contact_name||'')} · <a href="mailto:${esc(r.email)}">${esc(r.email)}</a>${r.phone?' · '+esc(r.phone):''}</div>
+ ${r.note?`<div class="kv" style="font-style:italic">“${esc(r.note)}”</div>`:''}
+ <div class="amt">${money(tot)} <span style="font-size:12px;color:var(--sub);font-family:Inter">(${money(r.quoted_monthly)}/mo)</span></div>
+ </div>`; }).join('');
+}
+
+initVCols(); loadPlace(); loadVid(); loadRes();
+</script>
+</body></html>
diff --git a/public/ad-slot.js b/public/ad-slot.js
new file mode 100644
index 00000000..e479e45a
--- /dev/null
+++ b/public/ad-slot.js
@@ -0,0 +1,65 @@
+/* RENTV ad-slot serving include — drop-in, zero deps.
+ *
+ * Usage on any page:
+ * <div data-ad-slot="home-leaderboard"></div>
+ * <script src="/ad-slot.js" defer></script>
+ *
+ * For each [data-ad-slot="<id>"] it GETs /api/ad/slot/<id> and renders:
+ * • SOLD/RESERVED with a creative → the advertiser's banner (image + click-through, rel=nofollow sponsored)
+ * • otherwise → a house "Your ad here — $X/mo" promo that links to /advertise?slot=<id>
+ * So an unsold slot still earns by advertising the slot itself.
+ */
+(function () {
+ var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); };
+ var money = function (n) { return '$' + Number(n || 0).toLocaleString('en-US'); };
+
+ function css() {
+ if (document.getElementById('ad-slot-css')) return;
+ var s = document.createElement('style'); s.id = 'ad-slot-css';
+ s.textContent = [
+ '.adslot{display:block;position:relative;overflow:hidden;border-radius:8px;min-height:60px}',
+ '.adslot .ad-house{display:flex;align-items:center;justify-content:center;gap:12px;text-align:center;',
+ ' min-height:90px;padding:14px 18px;border:1px dashed rgba(120,130,145,.45);border-radius:8px;',
+ ' background:repeating-linear-gradient(135deg,rgba(200,16,46,.04),rgba(200,16,46,.04) 10px,transparent 10px,transparent 20px);',
+ ' color:#5b636b;font:600 13px/1.35 Inter,system-ui,sans-serif}',
+ '.adslot .ad-house b{color:#c8102e;font-weight:800}',
+ '.adslot .ad-house .cta{margin-left:6px;background:#c8102e;color:#fff;font-weight:800;font-size:11px;',
+ ' text-transform:uppercase;letter-spacing:.05em;padding:6px 12px;border-radius:5px;white-space:nowrap}',
+ '.adslot .ad-live img{display:block;width:100%;height:auto}',
+ '.adslot .ad-flag{position:absolute;top:6px;right:6px;background:rgba(11,14,18,.8);color:#fff;',
+ ' font:700 9px/1 Inter;letter-spacing:.1em;text-transform:uppercase;padding:3px 6px;border-radius:3px}'
+ ].join('');
+ document.head.appendChild(s);
+ }
+
+ function render(el, slot) {
+ el.classList.add('adslot');
+ var sold = slot && (slot.status === 'sold' || slot.status === 'reserved') && slot.creative_url;
+ if (sold) {
+ var click = slot.click_url || '/advertise?slot=' + encodeURIComponent(slot.id);
+ el.innerHTML = '<a class="ad-live" href="' + esc(click) + '" target="_blank" rel="nofollow sponsored noopener" ' +
+ 'title="Sponsored by ' + esc(slot.advertiser || 'advertiser') + '">' +
+ '<span class="ad-flag">Sponsored</span>' +
+ '<img loading="lazy" alt="' + esc(slot.advertiser || 'Sponsor') + '" src="' + esc(slot.creative_url) + '"></a>';
+ return;
+ }
+ // house promo for an available slot — this IS the "slots for sale" pitch, inline
+ var price = slot ? money(slot.price_monthly) : '';
+ var name = slot ? esc(slot.name) : 'Ad space';
+ el.innerHTML = '<a class="ad-house" href="/advertise?slot=' + encodeURIComponent(slot ? slot.id : '') + '">' +
+ '<span>📣 <b>' + name + '</b> — your brand here' + (price ? ', <b>' + price + '/mo</b>' : '') + '</span>' +
+ '<span class="cta">Advertise →</span></a>';
+ }
+
+ function fill(el) {
+ var id = el.getAttribute('data-ad-slot'); if (!id) return;
+ fetch('/api/ad/slot/' + encodeURIComponent(id), { credentials: 'same-origin' })
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (j) { render(el, (j && j.slot) || { id: id, name: 'Ad space', status: 'available' }); })
+ .catch(function () { render(el, { id: id, name: 'Ad space', status: 'available' }); });
+ }
+
+ function boot() { css(); Array.prototype.forEach.call(document.querySelectorAll('[data-ad-slot]'), fill); }
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot();
+ window.RENTVAdSlot = { refill: boot };
+})();
diff --git a/public/advertise.html b/public/advertise.html
index a99708f0..619ca1a1 100644
--- a/public/advertise.html
+++ b/public/advertise.html
@@ -1,158 +1,312 @@
-<!doctype html>
-<html lang="en"><head>
+<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
-<title>Advertise — RENTV.com</title>
-<meta name="description" content="Reach Western-U.S. commercial real estate decision-makers through RENTV — newsletter, site, sponsored content, conferences and video.">
+<title>Advertise on RENTV — Buy Ad Slots · Sponsor CRE Video</title>
+<meta name="description" content="Buy monthly banner ad slots and sponsor commercial-real-estate videos on RENTV — the video-first CRE network. Live rate card, real availability, reserve online.">
<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">
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,700;0,800;1,600&family=Inter:wght@400;500;600;700;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}
+ :root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--wash:#f5f6f8;--up:#0f8a4d;--gold:#c6a24a;
+ --sans:'Inter',system-ui,sans-serif;--serif:'Playfair Display',Georgia,serif;--cols:4}
+ *{box-sizing:border-box;margin:0}
+ body{font-family:var(--sans);color:var(--ink);background:#fff;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:42px;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:13px 8px;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;margin-left:auto}
- 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}}
+ .wrap{max-width:1240px;margin:0 auto;padding:0 22px}
+ .top{position:sticky;top:0;z-index:30;display:flex;align-items:center;justify-content:space-between;height:56px;background:#0b0e12;color:#e7ecf1;padding:0 22px;border-bottom:2px solid var(--red)}
+ .top .brand{font-family:var(--serif);font-weight:800;font-size:23px}.top .brand span{color:var(--red)}
+ .top .nav{display:flex;gap:4px;align-items:center;font-size:12.5px;font-weight:700}
+ .top .nav a{padding:8px 11px;border-radius:7px;color:#c3cbd3}.top .nav a:hover{background:#161d25;color:#fff}
+ .top .nav a.cta{background:var(--red);color:#fff}
+ .phero{background:linear-gradient(120deg,#0b0e12 0%,#20121a 55%,#c8102e 140%);color:#fff;padding:56px 0 48px}
+ .phero .k{font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:#f0b7bf;margin-bottom:12px;font-weight:800}
+ .phero h1{font-family:var(--serif);font-size:clamp(32px,5vw,54px);line-height:1.06;letter-spacing:-.5px;max-width:20ch}
+ .phero p{max-width:62ch;margin-top:14px;font-size:17.5px;color:#e7ecf1;opacity:.95}
+ .phero .stats{display:flex;flex-wrap:wrap;gap:38px;margin-top:26px}
+ .phero .stats .v{font-family:var(--serif);font-size:34px;line-height:1}.phero .stats .l{font-size:12px;letter-spacing:.05em;text-transform:uppercase;color:#e7b9c1;margin-top:5px}
+ .phero .jump{margin-top:26px;display:flex;gap:10px;flex-wrap:wrap}
+ .phero .jump a{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.25);color:#fff;font-weight:800;font-size:13px;padding:11px 18px;border-radius:8px}
+ .phero .jump a.solid{background:#fff;color:#0b0e12;border-color:#fff}
+ section.blk{padding:44px 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:8px}
+ h2.big{font-family:var(--serif);font-size:clamp(25px,3.2vw,36px);line-height:1.12;letter-spacing:-.4px}
+ .lead2{max-width:70ch;color:var(--sub);margin-top:10px;font-size:16.5px}
+ /* rate-card placement grid */
+ .rate{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:26px}
+ @media(max-width:900px){.rate{grid-template-columns:1fr}}
+ .slot{border:1px solid var(--line);border-radius:12px;padding:20px 20px 18px;display:flex;flex-direction:column;gap:6px;transition:.2s;background:#fff;position:relative}
+ .slot:hover{transform:translateY(-3px);box-shadow:0 16px 40px rgba(0,0,0,.08);border-color:#cfd4d9}
+ .slot .surface{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--sub)}
+ .slot h3{font-family:var(--serif);font-size:21px}
+ .slot .pos{color:var(--sub);font-size:14px;flex:1}
+ .slot .fmt{font-size:12px;color:#8a929b}
+ .slot .row{display:flex;align-items:baseline;justify-content:space-between;margin-top:8px;gap:10px}
+ .slot .price{font-family:var(--serif);font-size:26px;color:var(--ink)}.slot .price span{font-size:13px;color:var(--sub);font-family:var(--sans);font-weight:600}
+ .slot .imp{font-size:12px;color:var(--sub)}
+ .status{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;padding:3px 9px;border-radius:20px}
+ .status.available{background:#eafaf0;color:#0f8a4d}
+ .status.reserved{background:#fff6e6;color:#a06a00}
+ .status.sold{background:#fdeaec;color:var(--red)}
+ .slot .buy{margin-top:12px;background:var(--red);color:#fff;border:0;border-radius:8px;padding:11px 14px;font-weight:800;font-size:13px;cursor:pointer;letter-spacing:.02em}
+ .slot .buy[disabled]{background:#e7eaee;color:#98a0a8;cursor:not-allowed}
+ .slot .buy:not([disabled]):hover{filter:brightness(1.07)}
+ /* video inventory */
+ .invhead{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:22px}
+ .invhead .count{font-size:13px;color:var(--sub);font-weight:600}
+ .controls{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-left:auto}
+ .controls label{font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--sub);display:flex;align-items:center;gap:8px}
+ .controls select{font:inherit;font-size:13px;font-weight:600;border:1px solid var(--line);border-radius:7px;padding:8px 10px;background:#fff;color:var(--ink)}
+ .controls input[type=range]{accent-color:var(--red)}
+ .filters{display:flex;flex-wrap:wrap;gap:7px;margin-top:16px}
+ .chip{cursor:pointer;border:1px solid var(--line);background:#fff;color:var(--ink);font:inherit;font-size:12.5px;font-weight:700;border-radius:20px;padding:6px 13px}
+ .chip:hover{border-color:var(--red);color:var(--red)}
+ .chip.on{background:var(--red);color:#fff;border-color:var(--red)}
+ .invgrid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px;margin-top:18px}
+ @media(max-width:900px){.invgrid{grid-template-columns:repeat(2,1fr)!important}}
+ @media(max-width:560px){.invgrid{grid-template-columns:1fr!important}}
+ .vslot{border:1px solid var(--line);border-radius:11px;overflow:hidden;background:#fff;display:flex;flex-direction:column;transition:.16s}
+ .vslot:hover{transform:translateY(-3px);box-shadow:0 14px 34px rgba(0,0,0,.09);border-color:#cfd4d9}
+ .vthumb{position:relative;aspect-ratio:16/9;background:linear-gradient(135deg,#1f2a37,#c8102e) center/cover;display:block}
+ .vthumb img{width:100%;height:100%;object-fit:cover;display:block}
+ .vthumb .src{position:absolute;left:8px;top:8px;background:rgba(11,14,18,.82);color:#fff;font-size:9.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;padding:3px 7px;border-radius:4px}
+ .vthumb .st{position:absolute;right:8px;top:8px}
+ .vthumb .sold-flag{position:absolute;left:8px;bottom:8px;background:var(--gold);color:#20180a;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.04em;padding:3px 8px;border-radius:4px}
+ .vbody{padding:11px 12px 13px;display:flex;flex-direction:column;gap:6px;flex:1}
+ .vbody .cat{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--red)}
+ .vbody h4{font:600 13.5px/1.3 var(--sans);flex:1}
+ .vbody .foot{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-top:2px}
+ .vbody .price{font-weight:800;font-size:15px}.vbody .price span{font-size:11px;color:var(--sub);font-weight:600}
+ .vbody .spbtn{width:100%;margin-top:4px;background:#0b0e12;color:#fff;border:0;border-radius:7px;padding:9px;font-weight:800;font-size:12.5px;cursor:pointer}
+ .vbody .spbtn[disabled]{background:#e7eaee;color:#98a0a8;cursor:not-allowed}
+ .vbody .spbtn:not([disabled]):hover{background:var(--red)}
+ .more{display:block;margin:22px auto 0;background:#fff;border:1px solid var(--line);border-radius:8px;padding:12px 22px;font-weight:800;cursor:pointer;font-size:13px}
+ .more:hover{border-color:var(--red);color:var(--red)}
+ footer{background:#0b0e12;color:#8b939b;padding:34px 0 28px;font-size:13px}
+ footer .cols{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:26px}
+ @media(max-width:820px){footer .cols{grid-template-columns:1fr 1fr}}
+ footer .logo{color:#fff;font-family:var(--serif);font-size:24px;margin-bottom:8px}footer .logo span{color:var(--red)}
+ footer h5{color:#e7ecf1;font-size:12px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}footer a{display:block;padding:3px 0;color:#9aa4ad}
+ footer .fine{border-top:1px solid #1c232b;margin-top:24px;padding-top:16px;color:#6b747d}
+ /* reserve modal */
+ .ovl{position:fixed;inset:0;z-index:100;background:rgba(6,9,12,.6);backdrop-filter:blur(3px);opacity:0;pointer-events:none;transition:.2s;display:flex;align-items:center;justify-content:center;padding:18px}
+ .ovl.open{opacity:1;pointer-events:auto}
+ .sheet{background:#fff;border-radius:14px;max-width:520px;width:100%;max-height:92vh;overflow:auto;box-shadow:0 30px 80px rgba(0,0,0,.4)}
+ .sheet .sh{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:20px 22px 8px}
+ .sheet .sh h3{font-family:var(--serif);font-size:22px}
+ .sheet .sh .sub{color:var(--sub);font-size:13px;margin-top:3px}
+ .sheet .sh .x{border:1px solid var(--line);background:#fff;width:34px;height:34px;border-radius:8px;font-size:15px;cursor:pointer}
+ .sheet .quote{margin:8px 22px 0;background:var(--wash);border:1px solid var(--line);border-radius:10px;padding:14px 16px;display:flex;justify-content:space-between;align-items:center;gap:12px}
+ .sheet .quote .q{font-size:13px;color:var(--sub)}.sheet .quote .q b{color:var(--ink)}
+ .sheet .quote .amt{font-family:var(--serif);font-size:24px}
+ form.rf{padding:14px 22px 22px;display:grid;gap:12px}
+ form.rf .two{display:grid;grid-template-columns:1fr 1fr;gap:12px}
+ @media(max-width:480px){form.rf .two{grid-template-columns:1fr}}
+ form.rf label{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--sub);display:block;margin-bottom:5px}
+ form.rf input,form.rf select,form.rf textarea{width:100%;padding:11px 13px;border:1px solid var(--line);border-radius:8px;font:inherit;font-size:14.5px}
+ form.rf textarea{min-height:70px;resize:vertical}
+ form.rf .submit{background:var(--red);color:#fff;border:0;border-radius:8px;padding:13px;font-weight:800;font-size:14px;cursor:pointer;margin-top:4px}
+ form.rf .note{font-size:12px;color:var(--sub);text-align:center}
+ form.rf .ok{display:none;padding:14px 16px;background:#ecfdf3;border:1px solid #a7f3d0;border-radius:9px;color:#067647;font-size:14px;font-weight:600}
</style></head><body>
+<header class="top">
+ <a class="brand" href="/">REN<span>TV</span></a>
+ <nav class="nav">
+ <a href="/">Home</a><a href="/review">The REview</a><a href="/markets">Markets</a><a href="#slots">Ad Slots</a><a href="#video">Sponsor Video</a><a href="/2026/">2026 Redesign</a>
+ </nav>
+</header>
- <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="/closings">Closings</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="/markets">Markets</a><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/conferences">Conferences</a><a href="/versions">Versions</a><a href="/blog">Blog</a>
- </div>
- </div></nav>
+<div class="phero"><div class="wrap">
+ <div class="k">Media Kit · 2026 · Buy online</div>
+ <h1>Put your firm in front of the CRE market — by the month.</h1>
+ <p>RENTV is the video-first commercial-real-estate network. Buy a monthly banner slot, or sponsor any one of our <b id="heroCount">150+</b> videos. Live rate card, real availability, reserve in two minutes — no charge until we confirm.</p>
+ <div class="stats">
+ <div><div class="v">1998</div><div class="l">Trusted since</div></div>
+ <div><div class="v">Daily</div><div class="l">Newsletter</div></div>
+ <div><div class="v" id="statVideos">—</div><div class="l">Sponsorable videos</div></div>
+ <div><div class="v" id="statSlots">—</div><div class="l">Banner slots</div></div>
+ </div>
+ <div class="jump"><a class="solid" href="#slots">See banner slots ↓</a><a href="#video">Sponsor a video ↓</a></div>
+</div></div>
- <div class="phero"><div class="wrap">
- <div class="k">Media Kit · 2026</div>
- <h1>Reach the people who move Western-U.S. commercial real estate.</h1>
- <p>Since 1998, RENTV has been where CRE professionals get their deals, financings and market intel. Put your firm in front of that audience across five integrated channels.</p>
- </div></div>
- <section class="blk"><div class="wrap">
- <div class="eyebrow rv">The audience</div><h2 class="big rv">Decision-makers, not eyeballs.</h2>
- <div class="statrow rv">
- <div><div class="v">1998</div><div class="l">Trusted since</div></div>
- <div><div class="v">Daily</div><div class="l">Newsletter cadence</div></div>
- <div><div class="v">600+</div><div class="l">Live conference audience</div></div>
- <div><div class="v">CRE</div><div class="l">100% industry-qualified</div></div>
- </div>
- </div></section>
- <section class="blk"><div class="wrap">
- <div class="eyebrow rv">Channels</div><h2 class="big rv">Five ways to reach the market — bundle for reach.</h2>
- <div class="cols3">
- <div class="tile rv"><div class="n">01</div><h3>Newsletter Sponsorship</h3><p>Top-of-inbox placement in the RENTV Daily — the single best way to reach active CRE professionals every morning.</p><div class="price">Weekly & monthly slots</div></div>
- <div class="tile rv"><div class="n">02</div><h3>Site Display</h3><p>Leaderboard and in-feed units across the news river and section pages — always-on brand presence beside the deals.</p><div class="price">CPM & flat-rate</div></div>
- <div class="tile rv"><div class="n">03</div><h3>Sponsored Content</h3><p>Native, RENTV-bylined articles that put your deal, product or point of view directly into the news feed.</p><div class="price">Per placement</div></div>
- <div class="tile rv"><div class="n">04</div><h3>Conference Sponsorship</h3><p>Stage, booth and delegate access at the State of the Market summits — the room where deals actually close.</p><div class="price">Tiered packages</div></div>
- <div class="tile rv"><div class="n">05</div><h3>Video & CRE Talk</h3><p>Pre-roll and named sponsorship on The REview and the CRE Talk interview series with regional CEOs.</p><div class="price">Series & per-episode</div></div>
- <div class="tile rv"><div class="n">★</div><h3>Integrated Package</h3><p>Combine newsletter + content + conference for one campaign that surrounds the buyer across the whole month. Best value.</p><div class="price">Custom — ask us</div></div>
+<section class="blk" id="slots"><div class="wrap">
+ <div class="eyebrow">Banner & placement slots · monthly</div>
+ <h2 class="big">Buy a slot. Own the position for the month.</h2>
+ <p class="lead2">Fixed monthly placements across the homepage, section pages, The REview and the RENTV Daily newsletter. One advertiser per slot — when it's yours, it's yours for the flight.</p>
+ <div class="rate" id="rate"></div>
+</div></section>
+
+<section class="blk" id="video"><div class="wrap">
+ <div class="eyebrow">Video sponsorship · every RENTV video</div>
+ <h2 class="big">Sponsor the content the market actually watches.</h2>
+ <p class="lead2">The entire RENTV video library is sponsorable — CRE Talk episodes, The REview interviews, JLL TV, the deal wire and RENTV Originals. Put your name on a title, a category or a single video, monthly.</p>
+ <div class="invhead">
+ <span class="count" id="invCount">Loading inventory…</span>
+ <div class="controls">
+ <label>Sort
+ <select id="sort">
+ <option value="featured">Featured first</option>
+ <option value="views">Most viewed</option>
+ <option value="price-hi">Price ↓</option>
+ <option value="price-lo">Price ↑</option>
+ <option value="title">Title A→Z</option>
+ </select>
+ </label>
+ <label>Density <input type="range" id="density" min="2" max="6" step="1" value="4"></label>
</div>
- </div></section>
- <section class="blk"><div class="wrap">
- <div class="eyebrow rv">Get started</div><h2 class="big rv">Request the 2026 media kit.</h2>
- <p class="lead2 rv">Tell us a little about your firm and we'll send rates, audience data and available slots. No obligation.</p>
- <form class="form rv" data-fakepost>
- <label>Name</label><input required placeholder="Your name">
- <label>Company</label><input required placeholder="Firm / brand">
- <label>Work email</label><input type="email" required placeholder="you@company.com">
- <label>Interested in</label><select><option>Integrated package</option><option>Newsletter sponsorship</option><option>Site display</option><option>Sponsored content</option><option>Conference sponsorship</option><option>Video / CRE Talk</option></select>
- <button class="cta" type="submit">Request media kit →</button>
- <div class="ok">✓ Thanks — the media kit is on its way. We'll be in touch within one business day.</div>
+ </div>
+ <div class="filters" id="filters"></div>
+ <div class="invgrid" id="invgrid"></div>
+ <button class="more" id="more" style="display:none">Show more videos</button>
+</div></section>
+
+<footer><div class="wrap"><div class="cols">
+ <div><div class="logo">REN<span>TV</span>.com</div><p style="max-width:36ch;color:#9aa4ad">The video-first commercial real estate network — deals, financing, development and conferences across the Western U.S. since 1998.</p></div>
+ <div><h5>Advertise</h5><a href="#slots">Banner slots</a><a href="#video">Sponsor video</a><a href="/subscribe">Newsletter</a><a href="/2026/">2026 redesign</a></div>
+ <div><h5>Watch</h5><a href="/review">The REview</a><a href="/cre-talk">CRE Talk</a><a href="/markets">Markets</a></div>
+ <div><h5>Company</h5><a href="/about">About</a><a href="/">Home</a></div>
+</div><div class="fine">© 2026 RENTV.com · Reserving a slot records an inquiry only — no charge is made until RENTV confirms availability and rate. · (310) 213-6409</div></div></footer>
+
+<!-- reserve / buy modal -->
+<div class="ovl" id="ovl" role="dialog" aria-modal="true" aria-label="Reserve ad slot">
+ <div class="sheet">
+ <div class="sh"><div><h3 id="rTitle">Reserve slot</h3><div class="sub" id="rSub"></div></div><button class="x" id="rClose" aria-label="Close">✕</button></div>
+ <div class="quote"><div class="q" id="rQuote"></div><div class="amt" id="rAmt"></div></div>
+ <form class="rf" id="rForm">
+ <div class="ok" id="rOk"></div>
+ <div class="two">
+ <div><label>Months</label><select id="fMonths"><option>1</option><option>3</option><option selected>6</option><option>12</option></select></div>
+ <div><label>Start month</label><input id="fStart" type="month"></div>
+ </div>
+ <div><label>Advertiser / brand</label><input id="fAdv" required placeholder="Your firm"></div>
+ <div class="two">
+ <div><label>Your name</label><input id="fName" required placeholder="Full name"></div>
+ <div><label>Phone</label><input id="fPhone" placeholder="Optional"></div>
+ </div>
+ <div><label>Work email</label><input id="fEmail" type="email" required placeholder="you@company.com"></div>
+ <div><label>Notes</label><textarea id="fNote" placeholder="Creative, targeting, questions…"></textarea></div>
+ <button class="submit" type="submit">Reserve — no charge yet</button>
+ <div class="note">We'll confirm availability & rate within one business day. No payment is taken online.</div>
</form>
- </div></section>
+ </div>
+</div>
- <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><a href="/multifamily">Multifamily</a><a href="/industrial">Industrial</a><a href="/retail">Retail</a><a href="/markets">Markets</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>
-<script src="/theme.js"></script>
-</body></html>
\ No newline at end of file
+const $=s=>document.querySelector(s), esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
+const money=n=>'$'+Number(n||0).toLocaleString('en-US');
+async function jget(u){try{const r=await fetch(u,{credentials:'same-origin'});return r.ok?await r.json():null;}catch(e){return null;}}
+
+/* ---------- banner rate card ---------- */
+async function loadRate(){
+ const d=await jget('/api/ad/rate-card'); const el=$('#rate'); const P=(d&&d.placements)||[];
+ $('#statSlots').textContent=P.length;
+ el.innerHTML=P.map(p=>{
+ const st=p.status||'available'; const avail=st==='available';
+ return `<div class="slot">
+ <span class="surface">${esc(p.surface)}</span>
+ <h3>${esc(p.name)}</h3>
+ <div class="pos">${esc(p.position)}</div>
+ <div class="fmt">${esc(p.format)} · ${esc(p.impressions_est||'')}</div>
+ <div class="row"><div class="price">${money(p.price_monthly)}<span>/mo</span></div><span class="status ${st}">${st}${p.advertiser?' · '+esc(p.advertiser):''}</span></div>
+ <button class="buy" data-kind="placement" data-id="${esc(p.id)}" data-name="${esc(p.name)}" data-price="${p.price_monthly}" ${avail?'':'disabled'}>${avail?'Reserve this slot':'Taken'}</button>
+ </div>`;
+ }).join('');
+}
+
+/* ---------- video sponsorship inventory (sort + density + filters) ---------- */
+let INV=[], curSource='', PAGE=60;
+const SORTS={
+ featured:(a,b)=>rank(a)-rank(b),
+ views:(a,b)=>(b.plays||0)-(a.plays||0),
+ 'price-hi':(a,b)=>(b.sponsorship.price_monthly||0)-(a.sponsorship.price_monthly||0),
+ 'price-lo':(a,b)=>(a.sponsorship.price_monthly||0)-(b.sponsorship.price_monthly||0),
+ title:(a,b)=>String(a.title||'').localeCompare(String(b.title||''))
+};
+const SRCRANK={vimeo:0,youtube:1,original:2,'news-wire':3,social:4};
+function rank(x){ // featured: episodes/originals first, then by plays
+ let r=SRCRANK[x.source]!=null?SRCRANK[x.source]:9;
+ if(x.kind==='episode')r-=0.5; if(x.pinned)r-=1;
+ return r - Math.min((x.plays||0)/1000,0.9);
+}
+function loadPersist(){
+ try{ const s=localStorage.getItem('rentv_adv_sort'); if(s)$('#sort').value=s;
+ const d=localStorage.getItem('rentv_adv_density'); if(d){$('#density').value=d;document.documentElement.style.setProperty('--cols',d);} }catch(e){}
+}
+async function loadInv(){
+ const d=await jget('/api/ad/inventory'); INV=(d&&d.items)||[];
+ const sum=(d&&d.summary)||{}; $('#statVideos').textContent=sum.total||INV.length; $('#heroCount').textContent=(sum.total||INV.length)+'';
+ // source filter chips
+ const srcs=['',...Object.keys(sum.by_source||{})];
+ const nice={'':'All','youtube':'The REview','vimeo':'CRE Talk','original':'RENTV Originals','news-wire':'Deal Wire','social':'Social'};
+ $('#filters').innerHTML=srcs.map(s=>`<button class="chip ${s===curSource?'on':''}" data-src="${s}">${esc(nice[s]||s)}${s?` · ${(sum.by_source||{})[s]||0}`:` · ${sum.total||INV.length}`}</button>`).join('');
+ render();
+}
+function view(){
+ let v=curSource?INV.filter(x=>x.source===curSource):INV.slice();
+ v.sort(SORTS[$('#sort').value]||SORTS.featured);
+ return v;
+}
+function render(){
+ const v=view(); const slice=v.slice(0,PAGE);
+ $('#invCount').textContent=v.length+' sponsorable video'+(v.length===1?'':'s')+(curSource?'':' · all sources');
+ $('#invgrid').innerHTML=slice.map(card).join('');
+ $('#more').style.display=v.length>PAGE?'block':'none';
+}
+function card(x){
+ const sp=x.sponsorship||{}; const sold=sp.status==='sold'; const reserved=sp.status==='reserved';
+ const thumb=x.thumb?`<img loading="lazy" src="${esc(x.thumb)}" alt="" onerror="this.style.display='none'">`:'';
+ const stat=sold?`<span class="status sold">Sold</span>`:reserved?`<span class="status reserved">Reserved</span>`:`<span class="status available">Open</span>`;
+ const soldFlag=sold?`<span class="sold-flag">◆ ${esc(sp.advertiser||'Sponsored')}</span>`:'';
+ const canBuy=!sold;
+ return `<div class="vslot">
+ <a class="vthumb" href="${esc(x.view_url||'#')}" ${x.view_url?'target="_blank" rel="noopener"':''} title="Watch">
+ ${thumb}<span class="src">${esc(x.source==='youtube'?'The REview':x.source==='vimeo'?'CRE Talk':x.source==='news-wire'?'Deal Wire':x.source==='original'?'Original':x.source)}</span>
+ <span class="st">${stat}</span>${soldFlag}
+ </a>
+ <div class="vbody">
+ <div class="cat">${esc(x.cat||'')}</div>
+ <h4>${esc(x.title||'Untitled')}</h4>
+ <div class="foot"><div class="price">${money(sp.price_monthly)}<span>/mo</span></div><div style="font-size:11px;color:var(--sub)">${x.plays?esc(x.plays)+' plays':''}</div></div>
+ <button class="spbtn" data-kind="video" data-id="${esc(x.uid)}" data-name="${esc(x.title||x.uid)}" data-price="${sp.price_monthly||0}" ${canBuy?'':'disabled'}>${canBuy?'Sponsor this →':'Sponsored'}</button>
+ </div>
+ </div>`;
+}
+$('#sort').addEventListener('change',()=>{try{localStorage.setItem('rentv_adv_sort',$('#sort').value)}catch(e){}; render();});
+$('#density').addEventListener('input',()=>{const v=$('#density').value;document.documentElement.style.setProperty('--cols',v);try{localStorage.setItem('rentv_adv_density',v)}catch(e){}});
+$('#filters').addEventListener('click',e=>{const b=e.target.closest('[data-src]');if(!b)return;curSource=b.dataset.src;PAGE=60;
+ document.querySelectorAll('#filters .chip').forEach(c=>c.classList.toggle('on',c===b));render();});
+$('#more').addEventListener('click',()=>{PAGE+=60;render();});
+
+/* ---------- reserve modal ---------- */
+let RES={};
+document.addEventListener('click',e=>{const b=e.target.closest('[data-kind]');if(!b||b.disabled)return;
+ RES={kind:b.dataset.kind,id:b.dataset.id,name:b.dataset.name,price:Number(b.dataset.price)||0};
+ $('#rTitle').textContent=(RES.kind==='video'?'Sponsor: ':'Reserve: ')+RES.name;
+ $('#rSub').textContent=RES.kind==='video'?'Video sponsorship · monthly':'Banner / placement slot · monthly';
+ updateQuote();
+ $('#rOk').style.display='none'; $('#rForm').querySelectorAll('input,select,textarea,button').forEach(x=>x.disabled=false);
+ $('#ovl').classList.add('open');});
+function updateQuote(){const m=Number($('#fMonths').value)||1;const tot=RES.price*m;
+ $('#rQuote').innerHTML=`<b>${money(RES.price)}</b>/mo × ${m} month${m===1?'':'s'}`;
+ $('#rAmt').textContent=money(tot);}
+$('#fMonths').addEventListener('change',updateQuote);
+$('#rClose').onclick=()=>$('#ovl').classList.remove('open');
+$('#ovl').addEventListener('click',e=>{if(e.target===$('#ovl'))$('#ovl').classList.remove('open');});
+document.addEventListener('keydown',e=>{if(e.key==='Escape')$('#ovl').classList.remove('open');});
+$('#rForm').addEventListener('submit',async e=>{e.preventDefault();
+ const email=$('#fEmail').value.trim();
+ if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){$('#fEmail').focus();return;}
+ const body={kind:RES.kind,target_id:RES.id,target_name:RES.name,months:Number($('#fMonths').value)||1,
+ start:$('#fStart').value||'',advertiser:$('#fAdv').value.trim(),contact_name:$('#fName').value.trim(),
+ email,phone:$('#fPhone').value.trim(),note:$('#fNote').value.trim(),quoted_monthly:RES.price};
+ const btn=$('#rForm .submit'); btn.disabled=true; btn.textContent='Reserving…';
+ const r=await fetch('/api/ad/reserve',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(body)});
+ const j=await r.json().catch(()=>null);
+ if(j&&j.ok){ const ok=$('#rOk'); ok.textContent='✓ '+j.message; ok.style.display='block';
+ $('#rForm').querySelectorAll('input,select,textarea').forEach(x=>x.disabled=true); btn.style.display='none';
+ loadRate(); loadInv(); // reflect the soft-hold
+ } else { btn.disabled=false; btn.textContent='Reserve — no charge yet'; alert((j&&j.error)?('Please check the form — '+j.error):'Could not reserve, please try again.'); }
+});
+
+/* today default for start month */
+(function(){const d=new Date();$('#fStart').value=d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0');})();
+loadPersist(); loadRate(); loadInv();
+</script>
+</body></html>
diff --git a/server.js b/server.js
index 665ca8f8..1ea6d220 100644
--- a/server.js
+++ b/server.js
@@ -2459,6 +2459,12 @@ app.use((req, res, next) => {
}
next();
});
+// ── RENTV 2026 Ad-Slots System (self-contained module) ──────────────────────
+// Rate card + all-videos sponsorship inventory + reservations + admin + the
+// "Make this the site" template pick. Mounted before the static fallthrough so
+// its /api/ad/* routes win. See src/ad-system.cjs for the whole feature.
+require('./src/ad-system.cjs')(app, { DATA, PUB, adminOnly });
+
// Inject the Front End ⇄ Backend toggle into HTML pages that express.static would serve
// (index + direct *.html + extensionless→.html), mirroring its extensions:['html'] resolution.
// Non-HTML assets (js/css/img/json/mp4) fall straight through to express.static untouched.
diff --git a/src/ad-system.cjs b/src/ad-system.cjs
new file mode 100644
index 00000000..1e3d97d0
--- /dev/null
+++ b/src/ad-system.cjs
@@ -0,0 +1,290 @@
+/* ────────────────────────────────────────────────────────────────────────────
+ * RENTV 2026 Ad-Slots System — self-contained, droppable Express module.
+ *
+ * Mount from server.js with ONE line, after the app + helpers exist and BEFORE
+ * express.static:
+ * require('./src/ad-system.cjs')(app, { DATA, PUB, adminOnly });
+ *
+ * What it does
+ * 1. Serves a RATE CARD of banner / newsletter / video-surface slots advertisers
+ * buy MONTHLY (data/ad-slots.json).
+ * 2. PULLS ALL RENTV VIDEOS into one sponsorable inventory, live at request time,
+ * from every source: videos.json (+pinned), originals.json, vimeo-library.json,
+ * public/vid/news-wire/*.mp4, public/social-videos/*.mp4. Each video is a
+ * sellable "sponsor this video" slot.
+ * 3. Persists only the SALES STATE (sold/reserved, price override, advertiser,
+ * flight) to data/ad-state.json — never rewrites the source video files.
+ * 4. Records buy inquiries to data/ad-reservations.jsonl (append-only). Real
+ * payment/checkout is intentionally NOT wired — that stays Steve-gated.
+ * 5. Stores the chosen 2026 homepage template (the "Make this the site" pick)
+ * in data/site-template.json.
+ *
+ * Zero new npm deps. CommonJS to match server.js.
+ * ──────────────────────────────────────────────────────────────────────────── */
+'use strict';
+const fs = require('fs');
+const path = require('path');
+
+module.exports = function mountAdSystem(app, opts) {
+ opts = opts || {};
+ const DATA = opts.DATA || path.join(__dirname, '..', 'data');
+ const PUB = opts.PUB || path.join(__dirname, '..', 'public');
+ // Fallback adminOnly: if the host app didn't pass one, gate admin routes closed
+ // rather than open (fail-safe). The real server passes its own adminOnly.
+ const adminOnly = typeof opts.adminOnly === 'function'
+ ? opts.adminOnly
+ : (req, res) => res.status(403).json({ ok: false, error: 'admin gate not wired' });
+
+ const readJSON = (f, fb) => { try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); } catch { return fb; } };
+ const writeJSON = (f, obj) => { try { fs.writeFileSync(path.join(DATA, f), JSON.stringify(obj, null, 2)); return true; } catch { return false; } };
+ const nowISO = () => new Date().toISOString();
+ const esc = s => String(s == null ? '' : s);
+
+ // ── sales-state store (the only thing we persist about videos) ──────────────
+ // { videos: { <uid>: {status, price_monthly, advertiser, flight, updated_at} },
+ // creatives: { <placementId>: {creative_url, click_url} } }
+ const stateFile = 'ad-state.json';
+ const loadState = () => readJSON(stateFile, { videos: {}, updated_at: null });
+ const saveState = st => { st.updated_at = nowISO(); return writeJSON(stateFile, st); };
+
+ // ── price heuristic for a video-sponsorship slot ────────────────────────────
+ // Reach-weighted monthly ask; admin can override per-video in ad-state.json.
+ function suggestPrice(v) {
+ let base = 300;
+ if (v.source === 'vimeo' && v.kind === 'episode') base = 900; // CRE Talk full episodes
+ else if (v.source === 'vimeo') base = 450; // Vimeo clips
+ else if (v.source === 'youtube') base = 600; // The REview YouTube
+ else if (v.source === 'original') base = 750; // RENTV Originals (brand)
+ else if (v.source === 'news-wire') base = 250; // deal-wire micro-videos
+ else if (v.source === 'social') base = 200; // generated social spots
+ const plays = Number(v.plays || 0);
+ const bump = plays > 500 ? 400 : plays > 100 ? 200 : plays > 25 ? 100 : 0;
+ // round to nearest $50
+ return Math.round((base + bump) / 50) * 50;
+ }
+
+ // ── build the unified video inventory from ALL sources, live ────────────────
+ function scanDir(rel, exts) {
+ const dir = path.join(PUB, rel);
+ let out = [];
+ try {
+ for (const f of fs.readdirSync(dir)) {
+ if (exts.some(e => f.toLowerCase().endsWith(e))) out.push({ file: f, rel: path.posix.join(rel, f) });
+ }
+ } catch { /* dir may not exist */ }
+ return out;
+ }
+ const titleize = s => s.replace(/\.[a-z0-9]+$/i, '').replace(/[-_]+/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+
+ function buildInventory() {
+ const items = [];
+ const push = it => { if (it && it.uid) items.push(it); };
+
+ // 1) The REview — YouTube (videos.json already merges pinned front)
+ const rev = readJSON('videos.json', { items: [] });
+ for (const v of (rev.items || [])) {
+ if (v.id == null || v.id === '') continue;
+ push({
+ uid: 'yt-' + v.id, source: 'youtube', kind: 'review',
+ title: v.title, desc: v.desc || '', cat: v.cat || 'The REview',
+ thumb: v.thumb || (v.yt ? `https://img.youtube.com/vi/${v.yt}/hqdefault.jpg` : null),
+ embed: v.embed || (v.yt ? `https://www.youtube.com/embed/${v.yt}` : null),
+ view_url: v.view_url || null, plays: v.views || 0, date: rev.fetched_at || null
+ });
+ }
+ // 2) Separately-pinned (in case not already in videos.json)
+ const pinned = readJSON('videos-pinned.json', { items: [] });
+ for (const v of (pinned.items || [])) {
+ const uid = 'yt-' + v.id;
+ if (!items.some(x => x.uid === uid)) push({
+ uid, source: 'youtube', kind: 'review', title: v.title, desc: v.desc || '',
+ cat: v.cat || 'Featured', thumb: v.thumb, embed: v.embed, view_url: v.view_url || null, plays: 0, date: null, pinned: true
+ });
+ }
+ // 3) RENTV Originals (local mp4, brand spots)
+ const orig = readJSON('originals.json', { items: [] });
+ for (const v of (orig.items || [])) {
+ push({
+ uid: 'orig-' + v.id, source: 'original', kind: 'brand', title: v.title, desc: v.desc || '',
+ cat: 'RENTV Originals', thumb: v.thumb, mp4: v.mp4, view_url: v.mp4, plays: 0, date: null
+ });
+ }
+ // 4) Vimeo library — CRE Talk episodes + clips (123)
+ const vim = readJSON('vimeo-library.json', {});
+ const vimItems = [].concat(vim.episodes || [], vim.items || [], vim.clips || []);
+ const seenV = new Set();
+ for (const v of vimItems) {
+ if (v.id == null || v.id === '') continue; // skip malformed rows (no real video id)
+ const uid = 'vimeo-' + v.id;
+ if (seenV.has(uid)) continue; seenV.add(uid);
+ push({
+ uid, source: 'vimeo', kind: v.kind || 'clip', title: v.title, desc: v.desc || '',
+ cat: v.kind === 'episode' ? 'CRE Talk' : 'CRE Talk · Clips',
+ thumb: v.thumb_local || v.thumb, embed: v.embed || `https://player.vimeo.com/video/${v.id}`,
+ view_url: v.url || null, plays: v.plays || 0, date: v.date || null
+ });
+ }
+ // 5) Deal-Wire micro-videos (public/vid/news-wire/*.mp4)
+ for (const f of scanDir('vid/news-wire', ['.mp4'])) {
+ const base = f.file.replace(/\.mp4$/i, '');
+ const poster = path.posix.join('/vid/news-wire', base + '.jpg');
+ push({
+ uid: 'newswire-' + base, source: 'news-wire', kind: 'deal-wire',
+ title: titleize(base), desc: 'RENTV News deal-wire video spot.', cat: 'Deal Wire · Video',
+ thumb: poster, mp4: '/' + f.rel, view_url: '/' + f.rel, plays: 0, date: null
+ });
+ }
+ // 6) Generated social spots (public/social-videos/*.mp4)
+ for (const f of scanDir('social-videos', ['.mp4'])) {
+ const base = f.file.replace(/\.mp4$/i, '');
+ push({
+ uid: 'social-' + base, source: 'social', kind: 'social', title: titleize(base),
+ desc: 'RENTV generated social video spot.', cat: 'Social', thumb: null, mp4: '/' + f.rel, view_url: '/' + f.rel, plays: 0, date: null
+ });
+ }
+
+ // apply sales-state + suggested pricing
+ const st = loadState();
+ for (const it of items) {
+ const ov = (st.videos && st.videos[it.uid]) || {};
+ it.sponsorship = {
+ status: ov.status || 'available',
+ price_monthly: (ov.price_monthly != null ? ov.price_monthly : suggestPrice(it)),
+ advertiser: ov.advertiser || null,
+ flight: ov.flight || null,
+ updated_at: ov.updated_at || null
+ };
+ it.suggested_price = suggestPrice(it);
+ }
+ return items;
+ }
+
+ // group counts by source for the inventory header
+ function summarize(items) {
+ const by = {}; let sold = 0, avail = 0;
+ for (const it of items) {
+ by[it.source] = (by[it.source] || 0) + 1;
+ if (it.sponsorship.status === 'sold') sold++; else if (it.sponsorship.status === 'available') avail++;
+ }
+ return { total: items.length, by_source: by, sold, available: avail };
+ }
+
+ /* ── PUBLIC ENDPOINTS ─────────────────────────────────────────────────────── */
+
+ // Banner / newsletter / surface rate card
+ app.get('/api/ad/rate-card', (_q, r) => {
+ r.set('Cache-Control', 'no-store');
+ const card = readJSON('ad-slots.json', { placements: [] });
+ // merge persisted creatives/status onto placements
+ const st = loadState();
+ const cr = st.creatives || {};
+ (card.placements || []).forEach(p => {
+ const c = cr[p.id];
+ if (c) { p.creative_url = c.creative_url || p.creative_url; p.click_url = c.click_url || p.click_url; if (c.status) p.status = c.status; if (c.advertiser) p.advertiser = c.advertiser; if (c.flight) p.flight = c.flight; }
+ });
+ r.json(card);
+ });
+
+ // A single banner placement (used by the on-page serving include)
+ app.get('/api/ad/slot/:id', (req, r) => {
+ r.set('Cache-Control', 'no-store');
+ const card = readJSON('ad-slots.json', { placements: [] });
+ const st = loadState(); const c = (st.creatives || {})[req.params.id] || {};
+ const p = (card.placements || []).find(x => x.id === req.params.id);
+ if (!p) return r.status(404).json({ ok: false, error: 'no such slot' });
+ const merged = Object.assign({}, p, c);
+ r.json({ ok: true, slot: merged });
+ });
+
+ // The unified video sponsorship inventory — ALL RENTV videos, pulled in live
+ app.get('/api/ad/inventory', (req, r) => {
+ r.set('Cache-Control', 'no-store');
+ let items = buildInventory();
+ const q = (req.query.source || '').trim();
+ if (q) items = items.filter(x => x.source === q);
+ const status = (req.query.status || '').trim();
+ if (status) items = items.filter(x => x.sponsorship.status === status);
+ r.json({ ok: true, summary: summarize(buildInventory()), count: items.length, items });
+ });
+
+ // Buy / reserve inquiry — records intent, NO charge (Steve-gated payment).
+ app.post('/api/ad/reserve', (req, r) => {
+ const b = req.body || {};
+ const rec = {
+ at: nowISO(),
+ kind: b.kind === 'video' ? 'video' : 'placement',
+ target_id: esc(b.target_id).slice(0, 80),
+ target_name: esc(b.target_name).slice(0, 160),
+ months: Math.max(1, Math.min(24, parseInt(b.months, 10) || 1)),
+ start: esc(b.start).slice(0, 7),
+ advertiser: esc(b.advertiser).slice(0, 120),
+ contact_name: esc(b.contact_name).slice(0, 120),
+ email: esc(b.email).slice(0, 160),
+ phone: esc(b.phone).slice(0, 40),
+ note: esc(b.note).slice(0, 600),
+ quoted_monthly: Number(b.quoted_monthly) || null,
+ ip: (req.headers['x-forwarded-for'] || req.ip || '').toString().split(',')[0].trim()
+ };
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(rec.email)) return r.status(400).json({ ok: false, error: 'valid email required' });
+ if (!rec.target_id) return r.status(400).json({ ok: false, error: 'target_id required' });
+ try { fs.appendFileSync(path.join(DATA, 'ad-reservations.jsonl'), JSON.stringify(rec) + '\n'); }
+ catch (e) { return r.status(500).json({ ok: false, error: 'could not record reservation' }); }
+ // mark the target as 'reserved' (soft-hold) so the board reflects the inquiry
+ const st = loadState();
+ if (rec.kind === 'video') { st.videos = st.videos || {}; const cur = st.videos[rec.target_id] || {}; if (cur.status !== 'sold') { cur.status = 'reserved'; cur.updated_at = nowISO(); st.videos[rec.target_id] = cur; } }
+ else { st.creatives = st.creatives || {}; const cur = st.creatives[rec.target_id] || {}; if (cur.status !== 'sold') { cur.status = 'reserved'; st.creatives[rec.target_id] = cur; } }
+ saveState(st);
+ r.json({ ok: true, message: 'Reservation received — RENTV ad sales will confirm availability and rate. No charge has been made.' });
+ });
+
+ // The chosen 2026 homepage template ("Make this the site" pick)
+ app.get('/api/ad/site-template', (_q, r) => r.json(readJSON('site-template.json', { chosen: null, options: ['a', 'b', 'c', 'd', 'e'], updated_at: null })));
+
+ /* ── ADMIN ENDPOINTS (adminOnly) ──────────────────────────────────────────── */
+
+ // Set/clear sold/reserved + price + advertiser + flight on a VIDEO slot
+ app.post('/api/ad/admin/video', adminOnly, (req, r) => {
+ const b = req.body || {}; const uid = esc(b.uid); if (!uid) return r.status(400).json({ ok: false, error: 'uid required' });
+ const st = loadState(); st.videos = st.videos || {};
+ const cur = st.videos[uid] || {};
+ if (b.status !== undefined) cur.status = ['available', 'reserved', 'sold'].includes(b.status) ? b.status : cur.status;
+ if (b.price_monthly !== undefined) cur.price_monthly = b.price_monthly === null ? undefined : Number(b.price_monthly);
+ if (b.advertiser !== undefined) cur.advertiser = b.advertiser || null;
+ if (b.flight !== undefined) cur.flight = b.flight || null;
+ cur.updated_at = nowISO();
+ st.videos[uid] = cur; saveState(st);
+ r.json({ ok: true, uid, state: cur });
+ });
+
+ // Set creative / status on a banner PLACEMENT
+ app.post('/api/ad/admin/placement', adminOnly, (req, r) => {
+ const b = req.body || {}; const id = esc(b.id); if (!id) return r.status(400).json({ ok: false, error: 'id required' });
+ const st = loadState(); st.creatives = st.creatives || {};
+ const cur = st.creatives[id] || {};
+ if (b.status !== undefined) cur.status = ['available', 'reserved', 'sold'].includes(b.status) ? b.status : cur.status;
+ if (b.creative_url !== undefined) cur.creative_url = b.creative_url || null;
+ if (b.click_url !== undefined) cur.click_url = b.click_url || null;
+ if (b.advertiser !== undefined) cur.advertiser = b.advertiser || null;
+ if (b.flight !== undefined) cur.flight = b.flight || null;
+ st.creatives[id] = cur; saveState(st);
+ r.json({ ok: true, id, state: cur });
+ });
+
+ // Read all reservations (admin queue)
+ app.get('/api/ad/admin/reservations', adminOnly, (_q, r) => {
+ let rows = [];
+ try { rows = fs.readFileSync(path.join(DATA, 'ad-reservations.jsonl'), 'utf8').trim().split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean); } catch { }
+ r.json({ ok: true, count: rows.length, items: rows.reverse() });
+ });
+
+ // Record the chosen template ("Make this the site")
+ app.post('/api/ad/admin/site-template', adminOnly, (req, r) => {
+ const chosen = esc((req.body || {}).chosen).toLowerCase();
+ if (!['a', 'b', 'c', 'd', 'e'].includes(chosen)) return r.status(400).json({ ok: false, error: 'chosen must be a|b|c|d|e' });
+ const rec = { chosen, options: ['a', 'b', 'c', 'd', 'e'], updated_at: nowISO(), note: 'Local selection only — going live is a separate Steve-gated deploy.' };
+ writeJSON('site-template.json', rec);
+ r.json({ ok: true, chosen, note: rec.note });
+ });
+
+ console.log('[ad-system] mounted: /api/ad/rate-card, /api/ad/inventory (all videos), /api/ad/reserve, /api/ad/admin/*');
+};
← 5b0ae439 auto-data-snapshot: 2026-08-07T10:41:58 (7 data files) — dat
·
back to Rentv 2026
·
RENTV 2026: element-numbering overlay (all templates) + demo 089da29b →