← back to Ventura Corridor

public/calendar.html

128 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Calendar · The Corridor</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;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;--green:#6a9b73;--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:32px;max-width:1100px;margin:0 auto}
.legend{margin-bottom:24px;display:flex;gap:18px;align-items:center;flex-wrap:wrap;font-size:11px;color:var(--ink-mute)}
.legend .swatches{display:flex;gap:4px;align-items:center}
.legend .sw{width:14px;height:14px;border:1px solid var(--rule)}
.heat{display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:start;background:var(--noir-rise);border:1px solid var(--rule);padding:24px;overflow-x:auto}
.weekdays{display:grid;grid-template-rows:repeat(7,16px);row-gap:3px;font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink-mute);padding-top:18px}
.grid{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,16px);gap:3px}
.month-row{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,16px);column-gap:3px;row-gap:3px;align-items:center}
.day{width:16px;height:16px;border:1px solid var(--rule);background:rgba(255,255,255,0.02);cursor:default;transition:transform 0.1s}
.day:hover{transform:scale(1.4);z-index:5;outline:1px solid var(--metal-glow)}
.day.l1{background:rgba(184,153,104,0.22)}
.day.l2{background:rgba(184,153,104,0.45)}
.day.l3{background:rgba(184,153,104,0.7)}
.day.l4{background:rgba(212,182,131,1)}
.month-labels{margin-bottom:6px;display:flex;font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--ink-mute);padding-left:34px;gap:0}
.month-labels span{flex:1;text-align:left;letter-spacing:.18em;text-transform:uppercase}

.summary{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-bottom:24px}
.stat{background:var(--noir-rise);border:1px solid var(--rule);padding:24px}
.stat .v{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:42px;line-height:1;color:var(--metal-glow);font-weight:500}
.stat .l{font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-top:8px}
</style>
</head>
<body>
<header>
  <h1>Publication <em>calendar</em></h1>
  <div class="sub">last 90 days · feature gen heatmap (PT timezone)</div>
</header>
<nav>
  <a href="/today.html">today</a>
  <a href="/magazine.html">magazine</a>
  <a href="/issue">issue</a>
  <a href="/coverage.html">coverage</a>
  <a href="/duplicates.html">dupes</a>
  <a href="/calendar.html" class="active">calendar</a>
</nav>
<main>
<div class="summary">
  <div class="stat"><div class="v" id="t-total">—</div><div class="l">total features (90d)</div></div>
  <div class="stat"><div class="v" id="t-pub">—</div><div class="l">published in window</div></div>
  <div class="stat"><div class="v" id="t-best">—</div><div class="l">best day count</div></div>
  <div class="stat"><div class="v" id="t-active">—</div><div class="l">active days</div></div>
</div>

<div class="legend">
  <span>less</span>
  <div class="swatches">
    <span class="sw"></span>
    <span class="sw l1"></span>
    <span class="sw l2"></span>
    <span class="sw l3"></span>
    <span class="sw l4"></span>
  </div>
  <span>more</span>
  <span style="margin-left:auto;font-style:italic;font-family:'Cormorant Garamond',serif">hover any cell for date + count</span>
</div>

<div id="grid-host"></div>
</main>
<script>
function fmt(n){return Number(n).toLocaleString('en-US')}
async function load() {
  const d = await fetch('/api/magazine/calendar?days=90').then(r => r.json());
  const map = new Map();
  for (const r of (d.rows || [])) map.set(r.day, r);

  // Build a calendar covering the window. Anchor end on today PT.
  const days = 90;
  const cells = [];
  const now = new Date();
  for (let i = days - 1; i >= 0; i--) {
    const dt = new Date(now);
    dt.setDate(now.getDate() - i);
    const k = dt.toISOString().slice(0, 10);
    const hit = map.get(k);
    cells.push({ date: dt, key: k, gen: hit ? Number(hit.gen) : 0, pub: hit ? Number(hit.pub) : 0 });
  }

  // Stats
  const total = cells.reduce((a, c) => a + c.gen, 0);
  const pub   = cells.reduce((a, c) => a + c.pub, 0);
  const best  = Math.max(0, ...cells.map(c => c.gen));
  const active = cells.filter(c => c.gen > 0).length;
  document.getElementById('t-total').textContent = fmt(total);
  document.getElementById('t-pub').textContent = fmt(pub);
  document.getElementById('t-best').textContent = fmt(best);
  document.getElementById('t-active').textContent = fmt(active);

  const max = Math.max(1, best);
  const tier = (n) => n === 0 ? '' : n / max <= 0.25 ? ' l1' : n / max <= 0.5 ? ' l2' : n / max <= 0.85 ? ' l3' : ' l4';
  const dayCells = cells.map(c => {
    const ts = c.date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
    return `<div class="day${tier(c.gen)}" title="${ts} · ${c.gen} generated · ${c.pub} published"></div>`;
  });

  // First column might be padded so cells align with weekday rows (Sun=0..Sat=6)
  const firstDay = cells[0].date.getDay();
  const blanks = Array.from({ length: firstDay }, () => `<div class="day" style="visibility:hidden"></div>`);
  const all = [...blanks, ...dayCells];

  // Inject as a single grid; CSS uses auto-flow:column to fill 7-row weeks.
  const wd = `<div class="weekdays"><span>Sun</span><span></span><span>Tue</span><span></span><span>Thu</span><span></span><span>Sat</span></div>`;
  const g = `<div class="grid">${all.join('')}</div>`;
  document.getElementById('grid-host').innerHTML = `<div class="heat">${wd}${g}</div>`;
}
load();
</script>
</body>
</html>