← back to Ventura Corridor

public/today.html

843 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Today · DW · Ventura Corridor</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<script>(function(){try{var t=localStorage.getItem('vc-theme');document.documentElement.dataset.theme=(t==='light'||t==='dark')?t:'dark';}catch(e){document.documentElement.dataset.theme='dark';}})();</script>
<link rel="stylesheet" href="/theme.css">
<script src="/theme.js" defer></script>
<style>
  :root {
    --noir: #0a0a0c; --noir-rise: #131316; --ink: #f0ece2;
    --ink-mute: #888475; --metal: #b89968; --metal-glow: #d4b683;
    --rule: #2a2622;
    --serif: 'Cormorant Garamond', 'Times New Roman', serif;
    --sans: 'Inter', -apple-system, system-ui, sans-serif;
    --mono: 'JetBrains Mono', ui-monospace, monospace;
  }
  @import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300;1,400&family=Inter:wght@100;200;300;400;600&family=JetBrains+Mono:wght@200;300;400&display=swap');
  html, body { margin: 0; background: var(--noir); color: var(--ink); font-family: var(--sans); font-weight: 300; }
  * { box-sizing: border-box; }
  header { position: sticky; top: 0; z-index: 10;
    background: linear-gradient(180deg, rgba(10,10,12,.97) 70%, rgba(10,10,12,0));
    padding: 22px 32px 16px; border-bottom: 1px solid var(--rule);
    display: flex; justify-content: space-between; align-items: baseline; gap: 24px; flex-wrap: wrap; }
  header h1 { font-family: var(--serif); font-weight: 400; font-size: 28px; margin: 0; letter-spacing: -0.01em; }
  header h1 em { font-style: italic; color: var(--metal); }
  .sub { font-size: 11px; letter-spacing: .26em; text-transform: uppercase; color: var(--ink-mute); margin-top: 2px; }
  nav.tabs { display: flex; gap: 4px; flex-wrap: wrap; }
  nav.tabs a { font-size: 9px; letter-spacing: .26em; text-transform: uppercase;
    color: var(--ink-mute); text-decoration: none; padding: 5px 10px; border: 1px solid var(--rule); }
  nav.tabs a.active, nav.tabs a:hover { color: var(--metal); border-color: var(--metal); }

  .day-hero {
    padding: 36px 32px 24px;
    background: radial-gradient(ellipse at top, rgba(184,153,104,0.08), transparent 70%);
    border-bottom: 1px solid var(--rule);
  }
  .day-hero .date {
    font-family: var(--mono); font-size: 11px; letter-spacing: .3em; color: var(--metal);
    text-transform: uppercase;
  }
  .day-hero h2 {
    font-family: var(--serif); font-style: italic; font-weight: 300;
    font-size: 48px; line-height: 1.05; margin: 8px 0 4px;
    letter-spacing: -0.02em;
  }
  .day-hero h2 em { color: var(--metal); font-style: normal; }
  .day-hero p { color: var(--ink-mute); font-size: 13px; max-width: 700px; line-height: 1.5; margin: 12px 0 0; }

  .stack { padding: 24px 32px; max-width: 1100px; }
  section.card {
    margin-bottom: 24px; padding: 22px 26px;
    background: var(--noir-rise); border: 1px solid var(--rule);
    transition: border-color 200ms ease;
  }
  section.card:hover { border-color: var(--metal); }
  section.card .head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap;
    margin-bottom: 14px;
  }
  section.card .head .num {
    font-family: var(--serif); font-style: italic; font-weight: 400;
    font-size: 56px; line-height: 0.9; color: var(--metal);
    font-variant-numeric: tabular-nums;
  }
  section.card .head .num em { font-style: normal; font-size: 12px; letter-spacing: .26em; text-transform: uppercase; color: var(--ink-mute); display: block; margin-top: 4px; }
  section.card .head .title {
    font-family: var(--serif); font-style: italic; font-weight: 400; font-size: 22px; color: var(--ink);
    flex: 1;
  }
  section.card .head .title small { display: block; font-family: var(--sans); font-style: normal; font-size: 10px; letter-spacing: .2em; color: var(--ink-mute); text-transform: uppercase; margin-top: 4px; }
  section.card .head .actions a, section.card .head .actions button {
    font-size: 9px; letter-spacing: .26em; text-transform: uppercase;
    color: var(--metal); text-decoration: none; padding: 6px 12px;
    border: 1px solid var(--metal); background: transparent; cursor: pointer;
    margin-left: 6px; font-family: inherit;
  }
  section.card .head .actions a:hover, section.card .head .actions button:hover { background: rgba(184,153,104,0.06); }

  .item {
    display: grid; grid-template-columns: auto 1fr auto; gap: 12px;
    padding: 10px 0; border-top: 1px solid var(--rule); align-items: center;
    font-size: 12px;
  }
  .item:first-child { border-top: 0; }
  .item .ico { font-size: 16px; color: var(--metal); width: 24px; text-align: center; }
  .item .nm { color: var(--ink); }
  .item .nm small { display: block; color: var(--ink-mute); font-size: 10px; margin-top: 2px; font-family: var(--mono); letter-spacing: .08em; }
  .item .acts a, .item .acts button {
    font-size: 9px; letter-spacing: .2em; text-transform: uppercase;
    color: var(--ink-mute); text-decoration: none; padding: 4px 8px;
    border: 1px solid var(--rule); background: transparent; cursor: pointer; margin-left: 4px; font-family: inherit;
  }
  .item .acts a:hover, .item .acts button:hover { color: var(--metal); border-color: var(--metal); }

  .empty { padding: 24px; text-align: center; font-style: italic; color: var(--ink-mute); font-size: 12px; }

  .totals { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 12px; font-size: 10px; letter-spacing: .15em; color: var(--ink-mute); text-transform: uppercase; }
  .totals span b { color: var(--metal); font-family: var(--serif); font-style: italic; font-size: 18px; margin-right: 6px; font-variant-numeric: tabular-nums; }
</style>
</head>
<body>

<header>
  <div>
    <h1>Today · <em>DW pipeline</em></h1>
    <div class="sub">your one-page action plan <span id="freshness-pill" style="margin-left:14px;font-size:9px;letter-spacing:.18em;padding:2px 8px;border:1px solid var(--rule);color:var(--ink-mute)" title="Click for ingest details">data: —</span></div>
  </div>
  <nav class="tabs">
    <a href="/">map</a>
    <a href="/atlas.html">atlas</a>
    <a href="/pitches.html">pitches</a>
    <a href="/linkedin.html">linkedin</a>
    <a href="/pitches-map.html">map</a>
    <a href="/walk-route.html">walk route</a>
    <a href="/responses.html">responses</a>
    <a href="/postcards.html">postcards</a>
    <a href="/buildings.html">buildings</a>
    <a href="/magazine.html" style="color:var(--metal-glow)">📖 magazine</a>
    <a href="/issue" style="color:var(--accent,#a8763a)">★ issue</a>
    <a href="/scoreboard.html">🏆 score</a>
    <a href="/feedback.html" id="nav-feedback" style="color:var(--ink-mute)">✎ feedback</a>
    <a href="/admin/ig-drafts/" target="_blank">📷 ig drafts</a>
    <a href="/standup">🎙 standup</a>
    <a href="/cover-picker.html">⛧ cover</a>
    <a href="/coverage.html">▰ coverage</a>
    <a href="/magazine.html?quality=1" id="nav-quality" style="color:var(--ink-mute)">🔧 quality</a>
    <a href="/health.html">♥ health</a>
    <a href="/duplicates.html" id="nav-dupes" style="color:var(--ink-mute)">⊕ dupes</a>
    <a href="/calendar.html">▦ calendar</a>
    <a href="/crawl-derby.html">🏇 derby</a>
    <a href="/today.html" class="active">today</a>
    <span class="theme-toggle-host" style="display:inline-flex;align-items:center;margin-left:6px"></span>
  </nav>
</header>

<section style="padding:14px 32px;border-bottom:1px solid var(--rule);background:rgba(184,153,104,0.03);overflow-x:auto;white-space:nowrap" id="mag-activity-rail">
  <span style="font-family:var(--serif);font-style:italic;color:var(--metal);font-size:14px;margin-right:14px">The Corridor · just now:</span>
  <span id="mag-activity-strip" style="font-family:var(--mono);font-size:11px;color:var(--ink-mute)">…</span>
</section>

<section style="padding:14px 32px;border-bottom:1px solid var(--rule);background:rgba(184,153,104,0.02);overflow-x:auto;white-space:nowrap" id="gen-queue-rail">
  <span style="font-family:var(--serif);font-style:italic;color:var(--metal);font-size:14px;margin-right:14px">Up next on the gen queue:</span>
  <span id="gen-queue-strip" style="font-family:var(--mono);font-size:11px;color:var(--ink-mute)">…</span>
</section>

<section style="padding:14px 32px;border-bottom:1px solid var(--rule);background:rgba(212,182,131,0.05);display:none" id="draft-pick-rail">
  <span style="font-family:var(--serif);font-style:italic;color:var(--metal-glow);font-size:14px;margin-right:14px">Publish next:</span>
  <a id="draft-pick-link" href="#" style="font-family:var(--serif);font-style:italic;color:var(--ink);font-size:15px;text-decoration:none">…</a>
  <span id="draft-pick-meta" style="font-family:var(--mono);font-size:10px;color:var(--ink-mute);margin-left:14px">…</span>
</section>

<section class="day-hero">
  <div class="date" id="today-date">—</div>
  <h2>Three things, <em>in order</em>.</h2>
  <p>Walk first (zero cost, highest conversion). LinkedIn DMs second (warm 1st-degree, 20-25 max for safe rate). Postcards third (cold, paid, scale). Skip whatever doesn't fit your day — pipeline doesn't care if you skip a tier.</p>
  <div class="totals">
    <span><b id="t-walk">—</b>walk targets in queue</span>
    <span><b id="t-linkedin">—</b>linkedin matches</span>
    <span><b id="t-postcard">—</b>postcard ready</span>
    <span><b id="t-research">—</b>need research</span>
  </div>
</section>

<section style="padding:20px 32px;border-bottom:1px solid var(--rule);background:radial-gradient(ellipse at top right, rgba(212,182,131,0.05), transparent 70%)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal-glow);margin:0">Walk here next</h3>
    <a href="/buildings.html" style="color:var(--metal);text-decoration:none;font-size:10px;letter-spacing:.18em;text-transform:uppercase;border:1px solid var(--rule);padding:4px 10px">Full list ↗</a>
  </div>
  <div id="prio-strip" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:8px"></div>
</section>

<section id="fotd-section" style="display:none;padding:24px 32px;border-bottom:1px solid var(--rule);background:linear-gradient(135deg,rgba(212,182,131,0.06),transparent 70%)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal-glow);margin:0">📜 Feature of the day</h3>
    <a id="fotd-link" href="/magazine.html" style="color:var(--metal);text-decoration:none;font-size:10px;letter-spacing:.18em;text-transform:uppercase;border:1px solid var(--rule);padding:4px 10px">Open ↗</a>
  </div>
  <div id="fotd-body" style="margin-top:14px"></div>
</section>

<section id="inq-section" style="display:none;padding:20px 32px;border-bottom:1px solid var(--rule);background:linear-gradient(135deg,rgba(106,58,26,0.08),transparent 60%)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--accent,#a8763a);margin:0">⭐ Sponsor inquiries · last 7</h3>
    <span id="inq-stats" style="font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">—</span>
  </div>
  <div id="inq-list" style="margin-top:12px;display:flex;flex-direction:column;gap:6px"></div>
</section>

<section id="fotw-section" style="display:none;padding:18px 32px;border-bottom:1px solid var(--rule);background:radial-gradient(ellipse at left, rgba(106,58,26,0.06), transparent 70%)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:18px;color:var(--accent,#a8763a);margin:0">📰 Feature of the week</h3>
    <a id="fotw-link" href="/magazine.html" style="color:var(--metal);text-decoration:none;font-size:10px;letter-spacing:.18em;text-transform:uppercase;border:1px solid var(--rule);padding:3px 8px">Open ↗</a>
  </div>
  <div id="fotw-body" style="margin-top:8px;font-family:var(--serif);font-style:italic;font-size:14px;color:var(--ink-mute);line-height:1.5"></div>
</section>

<section id="magazine-section" style="padding:20px 32px;border-bottom:1px solid var(--rule);background:rgba(184,153,104,0.04)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal-glow);margin:0">📖 The Corridor <span style="font-size:11px;letter-spacing:.18em;color:var(--ink-mute);margin-left:8px">corridor magazine</span></h3>
    <a href="/magazine.html" style="color:var(--metal);text-decoration:none;font-size:10px;letter-spacing:.18em;text-transform:uppercase;border:1px solid var(--rule);padding:4px 10px">Open issue ↗</a>
  </div>
  <div id="magazine-stats" style="margin-top:10px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">—</div>
  <div id="magazine-progress" style="margin-top:8px;height:6px;background:var(--noir);border:1px solid var(--rule);overflow:hidden">
    <div id="magazine-bar" style="height:100%;background:var(--metal-glow);width:0%;transition:width 0.6s"></div>
  </div>
</section>

<section id="stale-section" style="display:none;padding:20px 32px;border-bottom:1px solid var(--rule);background:rgba(182,101,101,0.05)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;flex-wrap:wrap;gap:10px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:#c47b7b;margin:0">Cold leads · re-touch or close</h3>
    <span id="stale-summary" style="font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">—</span>
  </div>
  <div id="stale-list" style="display:flex;flex-direction:column;gap:6px"></div>
</section>

<section class="trend" style="padding:24px 32px;border-bottom:1px solid var(--rule);background:var(--noir-rise)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:16px;flex-wrap:wrap;gap:12px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal);margin:0">14-day corridor velocity</h3>
    <div id="trend-stats" style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">—</div>
  </div>
  <svg id="trend-chart" viewBox="0 0 800 160" preserveAspectRatio="none" style="width:100%;height:160px;background:var(--noir);border:1px solid var(--rule)"></svg>
  <div id="trend-legend" style="display:flex;gap:16px;flex-wrap:wrap;margin-top:12px;font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)"></div>
</section>

<section style="padding:24px 32px;border-bottom:1px solid var(--rule);background:var(--noir-rise)">
  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:16px;flex-wrap:wrap;gap:12px">
    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal);margin:0">Recent activity</h3>
    <div id="activity-stats" style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">—</div>
  </div>
  <div id="activity-chips" style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px"></div>
  <div id="activity-feed" style="display:flex;flex-direction:column;gap:6px"></div>
  <div style="margin-top:12px;font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">
    auto-refresh every 60s · click any row to open the pitch
  </div>
</section>

<div class="stack">

<section class="card">
  <div class="head">
    <div class="num">1<em>walk-in</em></div>
    <div class="title">Walk over · same building + same block + 5-min walk
      <small>highest conversion · zero cost · take a sample book</small></div>
    <div class="actions">
      <a href="/pitches.html?prox=walking">All walks ↗</a>
    </div>
  </div>
  <div id="walk-list"></div>
</section>

<section class="card" id="inmail-card" style="display:none">
  <div class="head">
    <div class="num" id="inmail-num">—<em>inmail</em></div>
    <div class="title">InMail credits available · Sales Navigator
      <small id="inmail-sub">paid messages to non-connections · highest reply rate</small></div>
    <div class="actions">
      <a href="/sales-nav-signup.html">Manage subscription ↗</a>
      <a href="/pitches.html?status=approved">Pick targets ↗</a>
    </div>
  </div>
  <div id="inmail-detail"></div>
</section>

<section class="card">
  <div class="head">
    <div class="num">2<em>linkedin</em></div>
    <div class="title">DM your 1st-degree connections that match the corridor
      <small>copy each draft · paste into LinkedIn · max 50/day safe rate</small></div>
    <div class="actions">
      <a href="/linkedin.html">Open LinkedIn agent ↗</a>
    </div>
  </div>
  <div id="linkedin-list"></div>
</section>

<section class="card">
  <div class="head">
    <div class="num" id="postcard-count">—<em>postcard</em></div>
    <div class="title">Postcard batch · ≤0.5 mi from DW
      <small>upload to Lob · ~$0.82/piece · personalized</small></div>
    <div class="actions">
      <a href="/api/pitches/postcard-batch.csv?min_proximity=walk_10min" download>Download Lob CSV ↓</a>
    </div>
  </div>
  <div id="postcard-summary"></div>
</section>

<section class="card">
  <div class="head">
    <div class="num">4<em>research</em></div>
    <div class="title">Pitches that still need contact info
      <small>open Maps · find the right person · paste into the row</small></div>
    <div class="actions">
      <a href="/pitches.html?status=draft">All draft ↗</a>
    </div>
  </div>
  <div id="research-list"></div>
</section>

</div>

<script>
function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }

document.getElementById('today-date').textContent = new Date().toLocaleDateString('en-US', {
  weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
});

async function loadInMail() {
  try {
    const r = await fetch('/api/linkedin/subscription').then(r => r.json());
    if (r.tier && r.tier !== 'none') {
      document.getElementById('inmail-card').style.display = '';
      document.getElementById('inmail-num').innerHTML = `${r.credits_remaining||0}<em>inmail</em>`;
      const reply = r.used_this_month > 0 ? Math.round((r.replied_this_month||0)/r.used_this_month*100) : 0;
      const trial = r.trial_days_remaining != null ? `· trial ends in ${r.trial_days_remaining} days` : '';
      document.getElementById('inmail-sub').textContent = `${r.tier} · ${r.used_this_month||0} sent · ${r.replied_this_month||0} replies (${reply}%) ${trial}`;
      if (r.trial_status === 'ending_soon') {
        document.getElementById('inmail-detail').innerHTML = `<div style="padding:10px 14px;border:1px solid var(--metal);background:rgba(184,153,104,0.06);margin-top:10px;font-size:11px;color:var(--metal-glow)"><strong>Trial decision soon.</strong> Renew if you've sent ≥30 InMails AND ≥10% reply rate. <a href="/sales-nav-signup.html" style="color:var(--metal-glow);margin-left:6px">Decide ↗</a></div>`;
      }
    }
  } catch {}
}
loadInMail();

(async () => {
  const data = await fetch('/api/pitches').then(r => r.json());
  const rows = data.rows || [];

  // 1. Walk targets — same_building + same_block + walk_2min + walk_5min, status not in skip/sent/won/lost
  const walking = rows.filter(r =>
    ['same_building','same_block','walk_2min','walk_5min'].includes(r.dw_proximity)
    && !['skip','sent','won','lost'].includes(r.status)
  ).sort((a,b) => (a.priority||99) - (b.priority||99));
  document.getElementById('t-walk').textContent = walking.length;

  document.getElementById('walk-list').innerHTML = walking.slice(0, 8).map(r => {
    const ico = r.dw_proximity === 'same_building' ? '⚡' : r.dw_proximity === 'same_block' ? '★' : '◉';
    const dist = r.dw_proximity === 'same_building' ? 'same building' : r.dw_proximity === 'same_block' ? 'same block' : '<5 min walk';
    const gmaps = r.research_links?.gmaps;
    return `
      <div class="item">
        <div class="ico">${ico}</div>
        <div class="nm">${escapeHtml(r.name)}<small>${escapeHtml(r.address || '')} · ${dist} · ${escapeHtml(r.pitch_type || '')}</small></div>
        <div class="acts">
          ${gmaps ? `<a href="${gmaps}" target="_blank">Maps ↗</a>` : ''}
          <a href="/pitches.html?id=${r.id}">Detail ↗</a>
        </div>
      </div>`;
  }).join('') || `<div class="empty">No walking-distance targets queued. All walked!</div>`;

  // 2. LinkedIn — pitches with li_connection_status = '1st' AND li_dm_sent_at IS NULL (matched 1st-degree)
  const liReady = rows.filter(r => r.li_connection_status === '1st' && !r.li_dm_sent_at && !['skip','sent','won','lost'].includes(r.status));
  document.getElementById('t-linkedin').textContent = liReady.length;

  document.getElementById('linkedin-list').innerHTML = liReady.slice(0, 10).map(r => `
      <div class="item">
        <div class="ico">💼</div>
        <div class="nm">${escapeHtml(r.name)}<small>${escapeHtml(r.address || '')} · ${escapeHtml(r.pitch_type || '')}</small></div>
        <div class="acts">
          <a href="/linkedin.html">Open queue ↗</a>
        </div>
      </div>
    `).join('') || `<div class="empty">No 1st-degree LinkedIn matches yet. Import your Connections.csv on /linkedin.html.</div>`;

  // 3. Postcard count
  const postcardReady = rows.filter(r =>
    r.outreach_channel === 'postcard'
    && ['same_building','same_block','walk_2min','walk_5min','walk_10min'].includes(r.dw_proximity)
    && !['skip','sent','won','lost'].includes(r.status)
  );
  document.getElementById('t-postcard').textContent = postcardReady.length;
  document.getElementById('postcard-count').innerHTML = `${postcardReady.length}<em>postcard</em>`;
  const cost = (postcardReady.length * 0.82).toFixed(0);
  document.getElementById('postcard-summary').innerHTML = `
    <div class="item">
      <div class="ico">✉️</div>
      <div class="nm">Lob batch ready · ${postcardReady.length} addresses<small>~$${cost} at Lob's ~$0.82/piece (4×6 personalized + USPS) · or ~$${(postcardReady.length*0.25).toFixed(0)} via USPS EDDM (no personalization)</small></div>
      <div class="acts"></div>
    </div>`;

  // 4. Research queue — pitches with no contact_name AND priority ≤ 5 (top tier)
  const research = rows.filter(r =>
    !r.contact_name && (r.priority || 99) <= 5 && r.status === 'draft'
  ).sort((a,b) => (a.priority||99) - (b.priority||99));
  document.getElementById('t-research').textContent = research.length;

  document.getElementById('research-list').innerHTML = research.slice(0, 8).map(r => {
    const gmaps = r.research_links?.gmaps;
    return `
      <div class="item">
        <div class="ico">🔍</div>
        <div class="nm">${escapeHtml(r.name)}<small>${escapeHtml(r.address || '')} · pri ${r.priority} · ${escapeHtml(r.pitch_type || '')}</small></div>
        <div class="acts">
          ${gmaps ? `<a href="${gmaps}" target="_blank">Maps ↗</a>` : ''}
          <a href="/pitches.html?id=${r.id}">Fill in ↗</a>
        </div>
      </div>`;
  }).join('') || `<div class="empty">No research backlog! All top-tier pitches have contact info.</div>`;
})();

// ─── 14-day velocity trend chart (totals: sent / replied / walked / won) ───
(async () => {
  const data = await fetch('/api/snapshots?dim=totals&days=14').then(r => r.json()).catch(() => null);
  if (!data || !data.dates || data.dates.length === 0) {
    document.getElementById('trend-stats').textContent = 'no snapshot data yet';
    return;
  }
  const series = data.series || {};
  const dates = data.dates;
  // We chart the action metrics (skip 'all' since it's static at 5,386)
  const KEYS = [
    { key: 'sent',    color: '#b89968', label: 'sent' },
    { key: 'replied', color: '#d4b683', label: 'replied' },
    { key: 'walked',  color: '#6a9b73', label: 'walked' },
    { key: 'won',     color: '#d4b683', label: 'won', dashed: true },
  ];
  const W = 800, H = 160, PAD_L = 36, PAD_R = 12, PAD_T = 12, PAD_B = 24;
  const innerW = W - PAD_L - PAD_R;
  const innerH = H - PAD_T - PAD_B;

  let maxY = 0;
  for (const k of KEYS) {
    if (series[k.key]) maxY = Math.max(maxY, ...series[k.key]);
  }
  if (maxY < 5) maxY = 5; // floor to keep zero-baseline readable

  const xStep = dates.length > 1 ? innerW / (dates.length - 1) : innerW;

  const svg = document.getElementById('trend-chart');
  let svgInner = '';

  // Y-axis grid + labels
  for (const yFrac of [0, 0.25, 0.5, 0.75, 1]) {
    const y = PAD_T + innerH * (1 - yFrac);
    const v = Math.round(maxY * yFrac);
    svgInner += `<line x1="${PAD_L}" y1="${y}" x2="${W - PAD_R}" y2="${y}" stroke="#2a2622" stroke-width="0.5"/>`;
    svgInner += `<text x="${PAD_L - 6}" y="${y + 3}" text-anchor="end" fill="#888475" font-size="9" font-family="JetBrains Mono">${v}</text>`;
  }

  // X-axis labels (first / mid / last)
  for (const i of [0, Math.floor(dates.length / 2), dates.length - 1]) {
    if (dates[i]) {
      const x = PAD_L + xStep * i;
      const lbl = dates[i].slice(5); // "MM-DD"
      svgInner += `<text x="${x}" y="${H - 6}" text-anchor="middle" fill="#888475" font-size="9" font-family="JetBrains Mono">${lbl}</text>`;
    }
  }

  // Series lines
  for (const k of KEYS) {
    const arr = series[k.key];
    if (!arr) continue;
    const pts = arr.map((v, i) => `${PAD_L + xStep * i},${PAD_T + innerH * (1 - v / maxY)}`).join(' ');
    const dash = k.dashed ? 'stroke-dasharray="4 3"' : '';
    svgInner += `<polyline points="${pts}" fill="none" stroke="${k.color}" stroke-width="1.5" ${dash}/>`;
    // End-point dot
    const last = arr[arr.length - 1];
    const lastX = PAD_L + xStep * (arr.length - 1);
    const lastY = PAD_T + innerH * (1 - last / maxY);
    svgInner += `<circle cx="${lastX}" cy="${lastY}" r="3" fill="${k.color}"/>`;
  }
  svg.innerHTML = svgInner;

  // Legend
  document.getElementById('trend-legend').innerHTML = KEYS.map(k => {
    const last = (series[k.key] || []).slice(-1)[0] ?? 0;
    return `<span style="display:inline-flex;align-items:center;gap:6px"><span style="display:inline-block;width:14px;height:2px;background:${k.color};${k.dashed ? 'background-image:linear-gradient(to right,'+k.color+' 50%,transparent 50%);background-size:6px 2px' : ''}"></span>${k.label} <b style="color:var(--ink);font-family:var(--mono)">${last}</b></span>`;
  }).join('');

  // Stat strip
  const sentNow = series.sent ? series.sent.slice(-1)[0] : 0;
  const sentPrev = series.sent && series.sent.length >= 8 ? series.sent[series.sent.length - 8] : 0;
  const delta7 = sentNow - sentPrev;
  document.getElementById('trend-stats').innerHTML =
    `last 7 days: <b style="color:var(--metal-glow);font-family:var(--mono)">+${delta7}</b> sent · <b style="color:var(--metal);font-family:var(--mono)">${dates.length}</b> days captured`;
})();

// ─── Recent activity feed (iter 60) ─────────────────────────────────
const EVENT_ICON = {
  status_change: '✎', channel_change: '🔀',
  sent: '📤', replied: '📥', closed: '🔒',
  reply_text: '💬', followup_scheduled: '⏰',
  created: '✨'
};
const EVENT_COLOR = {
  sent: 'var(--metal)', replied: 'var(--metal-glow)',
  closed: 'var(--green)', won: 'var(--green)',
  status_change: 'var(--ink)', channel_change: 'var(--ink)',
  reply_text: 'var(--metal-glow)', followup_scheduled: 'var(--metal)',
  created: 'var(--ink-mute)'
};
function timeAgo(iso) {
  const d = new Date(iso);
  const s = Math.round((Date.now() - d.getTime()) / 1000);
  if (s < 60) return s + 's ago';
  if (s < 3600) return Math.round(s / 60) + 'm ago';
  if (s < 86400) return Math.round(s / 3600) + 'h ago';
  if (s < 604800) return Math.round(s / 86400) + 'd ago';
  return d.toLocaleDateString();
}
function escHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }
let activityFilter = '';
function renderActivityChips() {
  const types = [
    { id: '',                    label: 'All',         color: 'var(--metal)' },
    { id: 'sent',                label: '📤 Sent',     color: 'var(--metal)' },
    { id: 'replied',             label: '📥 Replied',  color: 'var(--metal-glow)' },
    { id: 'closed',              label: '🔒 Closed',   color: 'var(--green)' },
    { id: 'status_change',       label: '✎ Status',    color: 'var(--ink)' },
    { id: 'channel_change',      label: '🔀 Channel',  color: 'var(--ink)' },
    { id: 'reply_text',          label: '💬 Reply',    color: 'var(--metal-glow)' },
    { id: 'followup_scheduled',  label: '⏰ Follow-up', color: 'var(--metal)' },
    { id: 'created',             label: '✨ Created',  color: 'var(--ink-mute)' },
  ];
  document.getElementById('activity-chips').innerHTML = types.map(t => {
    const active = activityFilter === t.id;
    return `<button data-type="${t.id}" style="background:${active ? 'rgba(184,153,104,0.1)' : 'transparent'};border:1px solid ${active ? t.color : 'var(--rule)'};color:${active ? t.color : 'var(--ink-mute)'};padding:4px 10px;font-size:9px;letter-spacing:.2em;text-transform:uppercase;cursor:pointer;font-family:var(--sans)">${t.label}</button>`;
  }).join('');
  document.getElementById('activity-chips').querySelectorAll('button').forEach(b => {
    b.addEventListener('click', () => { activityFilter = b.dataset.type; renderActivityChips(); loadActivity(); });
  });
}
renderActivityChips();

async function loadActivity() {
  try {
    const url = activityFilter ? `/api/activity?limit=30&event_type=${activityFilter}` : '/api/activity?limit=30';
    const data = await fetch(url).then(r => r.json());
    const stats = data.stats || {};
    document.getElementById('activity-stats').innerHTML =
      `<b style="color:var(--metal-glow);font-family:var(--mono)">${stats.last_24h || 0}</b> events · 24h ` +
      `<b style="color:var(--metal);font-family:var(--mono);margin-left:14px">${stats.sent_7d || 0}</b> sent · 7d ` +
      `<b style="color:var(--metal-glow);font-family:var(--mono);margin-left:14px">${stats.replied_7d || 0}</b> replied · 7d`;
    const events = data.events || [];
    const feed = document.getElementById('activity-feed');
    if (!events.length) {
      feed.innerHTML = `<div style="padding:24px;text-align:center;color:var(--ink-mute);font-style:italic">No activity yet — first walk-in or DM will show here.</div>`;
      return;
    }
    feed.innerHTML = events.map(ev => {
      const icon = EVENT_ICON[ev.event_type] || '·';
      const color = EVENT_COLOR[ev.event_type] || 'var(--ink-mute)';
      const arrow = ev.old_value ? `${escHtml(ev.old_value)} → ${escHtml(ev.new_value || '')}` : escHtml(ev.new_value || '');
      return `
        <a href="/pitches.html?id=${ev.pitch_id}" target="_blank" style="display:grid;grid-template-columns:auto 1fr auto auto;gap:14px;align-items:center;padding:8px 12px;background:var(--noir);border:1px solid var(--rule);text-decoration:none;color:var(--ink);font-size:12px">
          <span style="color:${color};font-size:16px;width:22px;text-align:center">${icon}</span>
          <span><b style="font-family:var(--serif);font-size:14px;color:var(--metal-glow)">${escHtml(ev.biz_name)}</b> <span style="color:var(--ink-mute);margin-left:6px">${escHtml(ev.pitch_type || '')}</span></span>
          <span style="color:${color};font-family:var(--mono);font-size:10px;letter-spacing:.15em;text-transform:uppercase">${escHtml(ev.event_type)} ${arrow ? '· ' + arrow : ''}</span>
          <span style="color:var(--ink-mute);font-family:var(--mono);font-size:10px">${timeAgo(ev.occurred_at)}</span>
        </a>`;
    }).join('');
  } catch (e) {
    document.getElementById('activity-feed').innerHTML = `<div style="color:var(--ink-mute);padding:14px">activity feed offline</div>`;
  }
}
loadActivity();
setInterval(loadActivity, 60_000);

// ─── Priority strip (iter 67) — top 5 walkable bldgs at the top of /today ──
async function loadPrioStrip() {
  try {
    const data = await fetch('/api/buildings/priority?max_miles=0.5&min_unpitched=2&limit=5').then(r => r.json());
    const rows = data.rows || [];
    const strip = document.getElementById('prio-strip');
    if (!rows.length) {
      strip.innerHTML = `<div style="color:var(--ink-mute);padding:14px;font-style:italic">No unwalked walkable buildings — all priority targets done.</div>`;
      return;
    }
    strip.innerHTML = rows.map((b, i) => {
      const isDW = (b.bldg_address || '').startsWith('15442 ');
      return `
        <a href="/buildings.html?bldg=${encodeURIComponent(b.bldg_address)}" target="_blank" style="display:block;background:var(--noir-rise);border:1px solid ${isDW ? 'var(--metal-glow)' : 'var(--rule)'};padding:10px 12px;text-decoration:none;color:var(--ink)">
          <div style="display:flex;justify-content:space-between;align-items:baseline;gap:8px">
            <span style="font-family:var(--serif);font-style:italic;font-size:18px;color:var(--metal)">${String(i+1).padStart(2,'0')}</span>
            <span style="font-family:var(--mono);font-size:9px;color:var(--metal-glow);letter-spacing:.1em">${b.dw_miles} mi</span>
          </div>
          <div style="font-family:var(--serif);font-size:14px;color:var(--ink);margin-top:2px">${b.bldg_address}${isDW ? ' ⚡' : ''}</div>
          <div style="display:flex;gap:10px;font-size:9px;letter-spacing:.12em;color:var(--ink-mute);text-transform:uppercase;margin-top:6px">
            <span><b style="color:var(--metal);font-family:var(--mono);font-size:13px">${b.unpitched}</b> unpitched</span>
            <span style="color:var(--ink-mute)">/ ${b.tenants_total} tenants</span>
          </div>
        </a>`;
    }).join('');
  } catch (e) {
    document.getElementById('prio-strip').innerHTML = `<div style="color:var(--ink-mute);padding:14px">priority load failed</div>`;
  }
}
loadPrioStrip();

// ─── Data freshness pill (iter 69) ──────────────────────────────────
async function loadFreshness() {
  try {
    const d = await fetch('/api/data-freshness').then(r => r.json());
    const pill = document.getElementById('freshness-pill');
    if (!pill) return;
    const cls = ({ fresh: 'green', stale: 'metal', very_stale: 'red', never: 'red' })[d.status] || 'mute';
    const color = ({ fresh: 'var(--green,#6a9b73)', stale: 'var(--metal)', very_stale: 'var(--red,#b66565)', never: 'var(--red,#b66565)' })[d.status];
    const icon  = ({ fresh: '✓', stale: '⚠', very_stale: '⚠', never: '✕' })[d.status];
    const days  = d.days_since_ingest;
    pill.innerHTML = `data: <b style="color:${color};font-family:var(--mono);font-size:11px">${icon} ${days === null ? '—' : (days === 0 ? 'today' : days === 1 ? '1 day' : days + ' days')}</b> · ${(d.btrc_total || 0).toLocaleString()} BTRC rows`;
    pill.style.borderColor = color || 'var(--rule)';
    pill.style.color = color || 'var(--ink-mute)';
    pill.title = `Last BTRC ingest: ${d.last_btrc_ingest || 'never'}\nSource: ${d.source_url}\nRefresh command: ${d.ingest_command}`;
  } catch {}
}
loadFreshness();
setInterval(loadFreshness, 5 * 60_000);

// ─── Stale pitches (iter 71) ────────────────────────────────────────
async function loadStale() {
  try {
    const data = await fetch('/api/stale-pitches?days=14&limit=10').then(r => r.json());
    const rows = data.rows || [];
    const sect = document.getElementById('stale-section');
    if (!rows.length) { sect.style.display = 'none'; return; }
    sect.style.display = 'block';
    document.getElementById('stale-summary').innerHTML =
      `<b style="color:#c47b7b;font-family:var(--mono)">${rows.length}</b> sent ≥${data.days_threshold}d ago, no reply yet`;
    document.getElementById('stale-list').innerHTML = rows.map(r => `
      <a href="/responses.html?id=${r.id}" target="_blank" style="display:grid;grid-template-columns:auto 1fr auto auto auto;gap:14px;align-items:center;padding:8px 12px;background:var(--noir);border:1px solid var(--rule);text-decoration:none;color:var(--ink);font-size:12px">
        <span style="font-family:var(--mono);font-size:11px;color:#c47b7b;font-weight:500;min-width:50px">${r.days_silent}d</span>
        <span><b style="font-family:var(--serif);font-size:14px;color:var(--metal-glow)">${escHtml(r.name)}</b> <span style="color:var(--ink-mute);margin-left:6px">${escHtml(r.pitch_type || '')}</span></span>
        <span style="color:var(--ink-mute);font-size:10px;letter-spacing:.12em;text-transform:uppercase">${escHtml(r.outreach_channel || '—')}</span>
        <span style="color:var(--ink-mute);font-family:var(--mono);font-size:10px">sent ${new Date(r.sent_at).toLocaleDateString()}</span>
        <span style="color:var(--metal);font-size:10px;letter-spacing:.18em;text-transform:uppercase">log →</span>
      </a>
    `).join('');
  } catch {}
}
loadStale();
setInterval(loadStale, 5 * 60_000);

// ─── Magazine progress (iter 74) ────────────────────────────────────
async function loadMagazineStats() {
  try {
    const d = await fetch('/api/magazine/stats').then(r => r.json());
    const total = Number(d.total || 0);
    const corr = Number(d.corridor_total || 1);
    const pct = Math.min(100, (100 * total / corr));
    document.getElementById('magazine-stats').innerHTML =
      `<b style="color:var(--metal-glow);font-family:var(--mono);font-size:13px">${total.toLocaleString()}</b> features written · ` +
      `<b style="color:var(--metal);font-family:var(--mono)">${d.draft || 0}</b> drafts · ` +
      `<b style="color:var(--green,#6a9b73);font-family:var(--mono)">${d.published || 0}</b> published · ` +
      `<b style="color:var(--metal);font-family:var(--mono)">${d.gen_24h || 0}</b> generated last 24h · ` +
      `coverage <b style="color:var(--metal-glow);font-family:var(--mono)">${pct.toFixed(1)}%</b> of ${corr.toLocaleString()} businesses`;
    document.getElementById('magazine-bar').style.width = pct + '%';
  } catch {}
}
loadMagazineStats();
setInterval(loadMagazineStats, 30_000);

// Feature of the day (iter 85)
async function loadFOTD() {
  try {
    const d = await fetch('/api/magazine/feature-of-the-day').then(r => r.json());
    const f = d.feature;
    if (!f) return;
    document.getElementById('fotd-section').style.display = 'block';
    document.getElementById('fotd-link').href = '/magazine/' + f.id;
    document.getElementById('fotd-body').innerHTML = `
      <h2 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:32px;line-height:1.05;color:var(--ink);margin:0 0 6px">
        <a href="/magazine/${f.id}" style="color:inherit;text-decoration:none">${escHtml(f.headline || f.biz_name)}</a>
      </h2>
      <div style="font-family:var(--serif);font-style:italic;color:var(--ink-mute);font-size:15px;margin-bottom:14px">${escHtml(f.subhead || '')}</div>
      <p style="font-family:var(--serif);font-size:15px;line-height:1.55;color:var(--ink);margin:0 0 12px;max-width:720px">${escHtml((f.editorial || '').slice(0, 320))}…</p>
      ${f.pull_quote ? `<div style="font-family:var(--serif);font-style:italic;color:var(--metal-glow);font-size:18px;border-left:2px solid var(--metal);padding-left:14px;margin:12px 0">"${escHtml(f.pull_quote)}"</div>` : ''}
      <div style="font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute);margin-top:10px">${escHtml(f.biz_name)}${f.biz_address ? ' · ' + escHtml(f.biz_address) : ''}${f.city ? ' · ' + escHtml(f.city) : ''} · ${escHtml(f.category_tag || '')}</div>
    `;
  } catch {}
}
loadFOTD();

async function loadFOTW() {
  try {
    const d = await fetch('/api/magazine/feature-of-the-week').then(r => r.json());
    const f = d.feature;
    if (!f) return;
    document.getElementById('fotw-section').style.display = 'block';
    document.getElementById('fotw-link').href = '/magazine/' + f.id;
    document.getElementById('fotw-body').innerHTML = `
      <a href="/magazine/${f.id}" style="font-family:var(--serif);font-size:18px;font-style:italic;color:var(--ink);text-decoration:none">${escHtml(f.headline || f.biz_name)}</a>
      <span style="color:var(--ink-mute);font-size:12px;margin-left:6px">— ${escHtml(f.biz_name)} · ${escHtml(f.category_tag || '')}</span>
    `;
  } catch {}
}
loadFOTW();

async function loadInquiries() {
  try {
    const d = await fetch('/api/sponsor/inquiries').then(r => r.json());
    const rows = (d.rows || []).slice(0, 7);
    if (!rows.length) return;
    document.getElementById('inq-section').style.display = 'block';
    document.getElementById('inq-stats').innerHTML = `<b style="color:var(--accent,#a8763a);font-family:var(--mono)">${d.count}</b> total · ${rows.filter(r => r.status === 'new').length} new`;
    document.getElementById('inq-list').innerHTML = rows.map(r => {
      const ts = new Date(r.received_at).toLocaleString('en-US',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});
      const tierColor = ({ cover: 'var(--accent,#a8763a)', spread: 'var(--metal-glow)', ask: 'var(--ink-mute)' })[r.tier] || 'var(--ink-mute)';
      const statusColor = ({ new: 'var(--accent,#a8763a)', responded: 'var(--metal)', closed: 'var(--green,#6a9b73)', spam: 'var(--ink-mute)' })[r.status] || 'var(--ink-mute)';
      return `<div style="display:grid;grid-template-columns:auto 1fr auto auto auto;gap:14px;align-items:center;padding:7px 10px;background:var(--noir);border:1px solid var(--rule);font-size:12px">
        <span style="color:${tierColor};font-family:var(--mono);font-size:9px;letter-spacing:.22em;text-transform:uppercase;font-weight:500">${escHtml(r.tier || 'ask')}</span>
        <a href="/magazine/${r.feature_id}" target="_blank" style="color:var(--ink);text-decoration:none"><b style="font-family:var(--serif);font-size:14px;color:var(--metal-glow)">${escHtml(r.biz_name || '?')}</b> <span style="color:var(--ink-mute);font-family:var(--mono);font-size:10px;margin-left:6px">${escHtml(r.contact_name || '')} · ${escHtml(r.contact_email || '')}</span></a>
        <span style="color:${statusColor};font-family:var(--mono);font-size:9px;letter-spacing:.18em;text-transform:uppercase">${escHtml(r.status || 'new')}</span>
        <span style="color:var(--ink-mute);font-family:var(--mono);font-size:9px">${ts}</span>
        <span>
          <button onclick="patchInq(${r.id}, 'responded')" style="background:transparent;border:1px solid var(--rule);color:var(--metal);font-size:8px;letter-spacing:.18em;text-transform:uppercase;padding:3px 6px;cursor:pointer">↩ replied</button>
          <button onclick="patchInq(${r.id}, 'closed')" style="background:transparent;border:1px solid var(--rule);color:var(--green,#6a9b73);font-size:8px;letter-spacing:.18em;text-transform:uppercase;padding:3px 6px;cursor:pointer">✓ done</button>
        </span>
      </div>`;
    }).join('');
  } catch {}
}
async function patchInq(id, status) {
  try {
    await fetch(`/api/sponsor/inquiries/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) });
    loadInquiries();
  } catch (e) { alert('Failed: ' + e.message); }
}
loadInquiries();
setInterval(loadInquiries, 60_000);

async function loadQualityPill() {
  try {
    const d = await fetch('/api/magazine/quality-flags').then(r => r.json());
    const a = document.getElementById('nav-quality');
    if (!a) return;
    if (d.count > 0) {
      a.style.color = 'var(--metal-glow)';
      a.innerHTML = `🔧 quality <span style="background:var(--metal);color:#0a0a0c;padding:0 6px;margin-left:4px;font-size:8px;letter-spacing:.1em;font-weight:600">${d.count}</span>`;
      a.title = `${d.summary.thin} thin · ${d.summary.no_pull_quote} no pull-quote · ${d.summary.stale_draft} stale draft · ${d.summary.no_category} uncategorized`;
    } else {
      a.style.color = 'var(--green)';
      a.textContent = '🔧 quality ✓';
      a.title = 'all features pass quality checks';
    }
  } catch {}
}
loadQualityPill();
setInterval(loadQualityPill, 5 * 60_000);

async function loadMagActivity() {
  try {
    const d = await fetch('/api/magazine/activity?limit=12').then(r => r.json());
    const ICONS = {
      'feature.generated': '✨', 'feature.published': '★', 'feature.reviewed': '✓',
      'cover.set': '⛧', 'inquiry.received': '$',
      'feedback.typo': '✎', 'feedback.correction': '✎', 'feedback.tip': '※',
      'feedback.complaint': '!', 'feedback.praise': '♥', 'feedback.note': '·'
    };
    const items = (d.events || []).map(e => {
      const ic = ICONS[e.kind] || '·';
      const ago = (() => {
        const ms = Date.now() - new Date(e.at).getTime();
        const m = Math.floor(ms / 60000);
        if (m < 60) return m + 'm ago';
        const h = Math.floor(m / 60);
        if (h < 24) return h + 'h ago';
        return Math.floor(h / 24) + 'd ago';
      })();
      const label = (e.label || '').toString().replace(/[<>]/g, '').slice(0, 50);
      return `<span style="margin-right:24px;color:var(--ink-mute)"><span style="color:var(--metal-glow);margin-right:4px">${ic}</span>${label}<span style="color:var(--metal);margin-left:6px;font-size:9px">${ago}</span></span>`;
    });
    const pill = document.getElementById('mag-activity-strip');
    if (pill) pill.innerHTML = items.join('') || '<i style="font-style:italic;font-family:var(--serif)">no recent activity</i>';
  } catch {}
}
loadMagActivity();
setInterval(loadMagActivity, 90_000);

async function loadGenQueue() {
  try {
    const d = await fetch('/api/magazine/gen-queue?limit=8').then(r => r.json());
    const items = (d.queue || []).map(q => {
      const ads = Number(q.paid_ads_count || 0);
      const adsHtml = ads > 0 ? `<span style="color:var(--accent,#a8763a);margin-left:4px;font-size:9px">$${ads}</span>` : '';
      const name = String(q.name || '').slice(0, 28);
      const city = q.city ? `<span style="color:var(--metal);margin-left:4px;font-size:9px">${q.city.slice(0, 12)}</span>` : '';
      return `<span style="margin-right:24px;color:var(--ink-mute)">${name}${adsHtml}${city}</span>`;
    });
    const pill = document.getElementById('gen-queue-strip');
    if (pill) pill.innerHTML = items.join('') || '<i style="font-style:italic;font-family:var(--serif)">queue empty</i>';
  } catch {}
}
loadGenQueue();
setInterval(loadGenQueue, 5 * 60_000);

async function loadDupesPill() {
  try {
    const d = await fetch('/api/magazine/duplicates').then(r => r.json());
    const a = document.getElementById('nav-dupes');
    if (!a) return;
    if (d.groups > 0) {
      a.style.color = 'var(--metal-glow)';
      a.innerHTML = `⊕ dupes <span style="background:var(--metal);color:#0a0a0c;padding:0 6px;margin-left:4px;font-size:8px;letter-spacing:.1em;font-weight:600">${d.groups}</span>`;
      a.title = `${d.groups} duplicate-headline groups affecting ${d.total_affected} features — qwen3 templating signal`;
    } else {
      a.style.color = 'var(--green)';
      a.textContent = '⊕ dupes ✓';
      a.title = 'every headline is unique';
    }
  } catch {}
}
loadDupesPill();
setInterval(loadDupesPill, 5 * 60_000);

async function loadDraftPick() {
  try {
    const d = await fetch('/api/magazine/draft-pick-of-the-night').then(r => r.json());
    const rail = document.getElementById('draft-pick-rail');
    if (!d.pick) { rail.style.display = 'none'; return; }
    rail.style.display = 'block';
    const link = document.getElementById('draft-pick-link');
    link.textContent = d.pick.headline || d.pick.biz_name;
    link.href = '/magazine/' + d.pick.id;
    const meta = document.getElementById('draft-pick-meta');
    meta.textContent = `${d.pick.biz_name} · ${d.pick.editorial_len}c · ${d.pick.category_tag || '—'} · score ${d.pick.score}`;
  } catch {}
}
loadDraftPick();
setInterval(loadDraftPick, 5 * 60_000);

async function updateFeedbackPill() {
  try {
    const d = await fetch('/api/feedback').then(r => r.json());
    const unread = (d.rows || []).filter(r => !r.reviewed).length;
    const a = document.getElementById('nav-feedback');
    if (!a) return;
    if (unread > 0) {
      a.style.color = 'var(--metal-glow)';
      a.innerHTML = `✎ feedback <span style="background:var(--metal);color:#0a0a0c;padding:0 6px;margin-left:4px;font-size:8px;letter-spacing:.1em;font-weight:600">${unread}</span>`;
    } else {
      a.style.color = 'var(--ink-mute)';
      a.textContent = '✎ feedback';
    }
  } catch {}
}
updateFeedbackPill();
setInterval(updateFeedbackPill, 60_000);
</script>
</body>
</html>