← back to Ventura Corridor

public/news/archive.html

109 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>News archive · Ventura Corridor</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="alternate" type="application/rss+xml" title="Ventura Corridor — News from the Boulevard" href="/news/feed.xml">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400;1,500&family=Inter:wght@200;300;400;500&family=JetBrains+Mono:wght@200;300&display=swap');
:root{--noir:#0a0a0c;--noir-rise:#131316;--ink:#f0ece2;--ink-mute:#888475;--metal:#b89968;--metal-glow:#d4b683;--rule:#2a2622}
*{box-sizing:border-box}body{margin:0;background:var(--noir);color:var(--ink);font-family:Inter,sans-serif;font-weight:300}
header{padding:30px 32px 16px;border-bottom:1px solid var(--rule)}
header h1{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:400;font-size:32px;margin:0}
header h1 em{color:var(--metal)}
header .sub{font-size:11px;letter-spacing:.26em;text-transform:uppercase;color:var(--ink-mute);margin-top:4px}
nav{padding:10px 32px;border-bottom:1px solid var(--rule);display:flex;gap:12px;flex-wrap:wrap;font-size:9px;letter-spacing:.26em;text-transform:uppercase}
nav a{color:var(--ink-mute);text-decoration:none;padding:5px 10px;border:1px solid var(--rule)}
nav a:hover, nav a.active{color:var(--metal);border-color:var(--metal)}

main{padding:48px 32px;max-width:1100px;margin:0 auto}
.week{margin-bottom:48px;padding-bottom:32px;border-bottom:1px solid var(--rule)}
.week:last-child{border-bottom:none}
.week-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:18px;gap:14px}
.week-label{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:28px;color:var(--metal-glow);margin:0}
.week-count{font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute)}
.items{display:grid;grid-template-columns:repeat(2,1fr);gap:18px 32px}
@media (max-width:840px){.items{grid-template-columns:1fr}}
.item{padding:14px 0;border-bottom:1px dotted var(--rule);display:flex;flex-direction:column;gap:6px}
.item .biz{font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.22em;text-transform:uppercase;color:var(--metal)}
.item .biz a{color:inherit;text-decoration:none}
.item .biz a:hover{color:var(--metal-glow)}
.item h4{font-family:'Cormorant Garamond',serif;font-weight:500;font-size:18px;line-height:1.25;margin:0}
.item h4 a{color:var(--ink);text-decoration:none}
.item h4 a:hover{color:var(--metal-glow)}
.item .meta{font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.14em;color:var(--ink-mute);text-transform:uppercase}
.empty{padding:80px 0;text-align:center;color:var(--ink-mute);font-style:italic;font-family:'Cormorant Garamond',serif;font-size:18px}
</style>
</head>
<body>
<header>
  <h1>News <em>archive</em></h1>
  <div class="sub">scraped articles bucketed by ISO week, newest first</div>
</header>
<nav>
  <a href="/">Map</a>
  <a href="/magazine.html">Magazine</a>
  <a href="/this-week.html">This week</a>
  <a href="/news.html">News</a>
  <a href="/news/archive.html" class="active">Archive</a>
  <a href="/coverage.html">Coverage</a>
</nav>
<main id="content">
  <div class="empty">Loading…</div>
</main>
<script>
const escHtml = s => String(s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const fmtWeek = w => {
  const d = new Date(w);
  if (isNaN(d.getTime())) return w;
  const end = new Date(d);
  end.setDate(end.getDate() + 6);
  const m = { month: 'short', day: 'numeric' };
  return d.toLocaleDateString('en-US', m) + ' – ' + end.toLocaleDateString('en-US', m) + ', ' + d.getFullYear();
};
const fmtDate = s => {
  if (!s) return '';
  const d = new Date(s);
  if (isNaN(d.getTime())) return '';
  return d.toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric' });
};
const safeUrl = raw => {
  try { const u = new URL(String(raw||'')); if (u.protocol==='http:'||u.protocol==='https:') return u.href; } catch{}
  return '#';
};
async function load() {
  try {
    const r = await fetch('/api/news/archive');
    const d = await r.json();
    const weeks = d.weeks || [];
    const c = document.getElementById('content');
    if (!weeks.length) { c.innerHTML = '<div class="empty">No archived news yet.</div>'; return; }
    c.innerHTML = weeks.map(w => {
      const items = (w.items || []).map(it => {
        const date = it.date;
        const text = it.summary || it.excerpt || '';
        let srcHost = ''; try { srcHost = new URL(it.source_url).hostname.replace(/^www\./, ''); } catch(e) {}
        return `<div class="item">
          <div class="biz"><a href="/business/${it.business_id}/news">${escHtml(it.business_name||'')}${it.city?' · '+escHtml(it.city):''}</a></div>
          <h4><a href="${escHtml(safeUrl(it.source_url))}" target="_blank" rel="noopener noreferrer">${escHtml((it.title||'(untitled)').slice(0,140))}</a></h4>
          <div class="meta">${escHtml(fmtDate(date))}${srcHost ? ' · Source: ' + escHtml(srcHost) : ''}</div>
        </div>`;
      }).join('');
      return `<section class="week">
        <div class="week-head">
          <h3 class="week-label">${escHtml(fmtWeek(w.week))}</h3>
          <span class="week-count">${w.n} article${w.n===1?'':'s'}</span>
        </div>
        <div class="items">${items}</div>
      </section>`;
    }).join('');
  } catch(e){
    document.getElementById('content').innerHTML = '<div class="empty">Failed to load archive.</div>';
  }
}
load();
</script>
</body>
</html>