← back to Cncp Failures Mockups

public/index.html

167 lines

<!doctype html><html><head>
<meta charset="utf-8"><title>CNCP Failures — 6 mockup variants</title>
<style>
  :root { --bg:#0a0c12; --fg:#e6eef8; --muted:#7c8aa5; --accent:#7c3aed; --panel:#161a26; --border:#252b3d; }
  body { margin:0; background:var(--bg); color:var(--fg); font-family: -apple-system,BlinkMacSystemFont,'SF Pro Text',system-ui,sans-serif; min-height:100vh; }
  .hero { padding:36px 32px 18px; max-width:1280px; margin:0 auto; }
  .hero h1 { font-size:24px; font-weight:700; margin:0 0 4px; letter-spacing:-0.01em; }
  .hero .sub { color:var(--muted); font-size:14px; max-width:720px; }
  .controls { padding:6px 32px 12px; max-width:1280px; margin:0 auto; display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
  .ctrl-btn { background:var(--panel); border:1px solid var(--border); color:var(--fg); padding:6px 12px; border-radius:6px; cursor:pointer; font-size:12px; }
  .ctrl-btn:hover { background:#1f2535; }
  .ctrl-btn.active { background:var(--accent); border-color:var(--accent); color:#fff; }
  .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(440px,1fr)); gap:18px; padding:18px 32px 60px; max-width:1280px; margin:0 auto; }
  .card { background:var(--panel); border:1px solid var(--border); border-radius:10px; overflow:hidden; display:flex; flex-direction:column; transition: transform 0.15s, border-color 0.15s; cursor:pointer; }
  .card:hover { transform:translateY(-2px); border-color:var(--accent); }
  .card:hover .preview::after { background:rgba(124,58,237,0.08); }
  .card .preview::after { content:''; position:absolute; inset:0; background:transparent; transition:background 0.15s; pointer-events:none; }
  .card .preview { aspect-ratio: 16/10; width:100%; background:#000; border-bottom:1px solid var(--border); overflow:hidden; position:relative; }
  .card iframe { width:200%; height:200%; transform-origin: 0 0; transform: scale(0.5); border:none; pointer-events:none; }
  .card .info { padding:12px 14px 14px; }
  .card .info h3 { font-size:14px; font-weight:600; margin:0 0 2px; }
  .card .info .desc { color:var(--muted); font-size:12px; line-height:1.45; margin-bottom:10px; }
  .card .info .open { display:inline-block; background:transparent; color:var(--accent); border:1px solid var(--accent); padding:5px 12px; border-radius:5px; font-size:11.5px; text-decoration:none; }
  .card .info .open:hover { background:var(--accent); color:#fff; }
  .full { position:fixed; inset:0; background:#000; z-index:10; display:none; }
  .full iframe { width:100%; height:100%; border:none; }
  .full .close { position:absolute; top:14px; right:14px; background:var(--panel); border:1px solid var(--border); color:var(--fg); padding:8px 14px; border-radius:6px; cursor:pointer; font-size:12px; z-index:11; }
  .full .nav { position:absolute; bottom:18px; left:50%; transform:translateX(-50%); display:flex; gap:6px; background:rgba(0,0,0,0.7); padding:8px 12px; border-radius:8px; z-index:11; backdrop-filter:blur(8px); }
  .full .nav .ctrl-btn { font-size:11px; }
  kbd { background:#1f2535; border:1px solid var(--border); padding:1px 6px; border-radius:3px; font-size:11px; font-family:ui-monospace,Menlo,monospace; }
</style>
<!-- Meta Pixel — INERT until set-fb-pixel.sh is run (does NOT load fbevents.js or hit Meta until flipped) -->
<script>
(function(){
  var PIXEL_ID = '{{FACEBOOK_PIXEL_ID}}';
  // Inert until flipped — the brace check prevents BOTH the loader IIFE
  // AND the init call from running, so fbevents.js is never fetched.
  if (PIXEL_ID.indexOf('{{') !== -1) return;
  !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
  n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
  n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
  t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
  document,'script','https://connect.facebook.net/en_US/fbevents.js');
  fbq('init', PIXEL_ID);
  fbq('track', 'PageView');
})();
</script>
<!-- noscript fallback is added by set-fb-pixel.sh on flip (omitted here to avoid unflipped img phone-home) -->
<!-- End Meta Pixel -->
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
  </head><body>
<div class="hero">
  <h1>CNCP Failures — 6 mockup variants</h1>
  <p class="sub">Each tile is a self-contained reimagining of the Failures + Hermes panel. Same data; six different display directions. Click <b>Open</b> for full-screen, then use <kbd>←</kbd>/<kbd>→</kbd> to flip between variants.</p>
</div>
<div class="controls">
  <span style="color:var(--muted); font-size:12px;">Data source:</span>
  <button class="ctrl-btn active" id="mode-live" onclick="setMode('live')">LIVE (CNCP)</button>
  <button class="ctrl-btn" id="mode-seed" onclick="setMode('seed')">SEED (design-stable)</button>
  <span id="meta" style="color:var(--muted); font-size:12px; margin-left:auto;"></span>
  <button class="ctrl-btn" onclick="refreshAll()">↻ Refresh</button>
</div>
<div class="grid" id="grid"></div>

<div class="full" id="full">
  <button class="close" onclick="closeFull()">✕ Close (Esc)</button>
  <iframe id="full-frame"></iframe>
  <div class="nav">
    <button class="ctrl-btn" onclick="navVariant(-1)">← Prev</button>
    <span id="full-label" style="color:var(--muted); font-size:12px; align-self:center; min-width:200px; text-align:center;"></span>
    <button class="ctrl-btn" onclick="navVariant(1)">Next →</button>
  </div>
</div>

<script>
const variants = [
  { id:'v1-terminal',   title:'1. Terminal · Log-Stream',   desc:'Green-on-black CRT, monospace, severity inline. Built for the engineer who lives in tmux.' },
  { id:'v2-newspaper',  title:'2. Newspaper · Wire-Service', desc:'Old-style dispatch with masthead, columns, byline. Reads failures as news, not noise.' },
  { id:'v3-heatmap',    title:'3. Heatmap · Dashboard',     desc:'Density-first: 24-hour severity grid + glanceable stat cells. SRE dashboard energy.' },
  { id:'v4-kanban',     title:'4. Kanban · Status Board',   desc:'Four lanes (triage → investigating → resolved → dismissed). Atlassian-ish, action-oriented.' },
  { id:'v5-timeline',   title:'5. Timeline · APM Trace',    desc:'Each process is a lane; spans show event windows. Datadog-style, time-as-axis.' },
  { id:'v6-brutalist',  title:'6. Brutalist · Data-Dense',  desc:'Hard borders, full table, every field visible. No chrome — just the facts.' },
  { id:'v7-age-adaptive', title:'7. Age-Adaptive · 3 → 95',  desc:'Slider morphs the whole UI through 8 design bands — toddler primary colors → elementary playful → tween → teen neon → adult → mature serif → senior hi-contrast → elder essentials only.' }
];

function renderGrid(mode) {
  const q = mode === 'seed' ? '?seed=1' : '';
  document.getElementById('grid').innerHTML = variants.map((v, i) => `
    <div class="card" onclick="openFull(${i})" role="link" tabindex="0" aria-label="Open ${v.title}" data-variant-id="${v.id}">
      <div class="preview"><iframe src="${v.id}.html${q}#nocache=${Date.now()}" loading="lazy"></iframe></div>
      <div class="info">
        <h3>${v.title}</h3>
        <div class="desc">${v.desc}</div>
        <a href="${v.id}.html${q}" class="open" onclick="event.stopPropagation(); event.preventDefault(); openFull(${i})">Open full-screen →</a>
        <a href="${v.id}.html${q}" class="open" style="margin-left:8px" onclick="event.stopPropagation()" target="_blank" rel="noopener noreferrer">Open in new tab ↗</a>
      </div>
    </div>`).join('');
  // Enter/Space on a focused card opens it
  document.getElementById('grid').addEventListener('keydown', function(e) {
    if (e.key !== 'Enter' && e.key !== ' ') return;
    const card = e.target.closest('.card');
    if (!card) return;
    e.preventDefault();
    const idx = Array.prototype.indexOf.call(card.parentNode.children, card);
    openFull(idx);
  });
}

let _mode = 'live';
function setMode(m) {
  _mode = m;
  document.getElementById('mode-live').classList.toggle('active', m === 'live');
  document.getElementById('mode-seed').classList.toggle('active', m === 'seed');
  renderGrid(m);
  refreshMeta();
}
function refreshAll() {
  renderGrid(_mode);
  refreshMeta();
}
async function refreshMeta() {
  const q = _mode === 'seed' ? '?seed=1' : '';
  try {
    const r = await fetch('/data' + q);
    const d = await r.json();
    const open = (d.failures||[]).filter(f => f.status === 'triage' || f.status === 'investigating').length;
    const total = (d.failures||[]).length;
    const h = d.hermes ? d.hermes.last24h : null;
    const sev = h && h.bySev ? h.bySev : { high:0, medium:0, low:0 };
    const sourceBadge = d._live
      ? '<span style="background:#0a3d24; color:#4ade80; padding:2px 8px; border-radius:4px; font-size:11px;">● LIVE</span>'
      : '<span style="background:#3d2a0a; color:#fbbf24; padding:2px 8px; border-radius:4px; font-size:11px;">● SEED' + (d._fallback ? ' (CNCP unreachable)' : '') + '</span>';
    document.getElementById('meta').innerHTML = `${sourceBadge} · ${open} open / ${total} total · Hermes 24h H${sev.high} M${sev.medium} L${sev.low}`;
  } catch (e) {
    document.getElementById('meta').innerHTML = '<span style="color:#ff5555;">meta fetch failed</span>';
  }
}
renderGrid(_mode);
refreshMeta();
setInterval(refreshMeta, 30000);

let currentIdx = 0;
function openFull(i) {
  currentIdx = i;
  const v = variants[i];
  const q = _mode === 'seed' ? '?seed=1' : '';
  document.getElementById('full-frame').src = v.id + '.html' + q;
  document.getElementById('full-label').textContent = v.title + (_mode === 'seed' ? ' · SEED' : ' · LIVE');
  document.getElementById('full').style.display = 'block';
}
function closeFull() {
  document.getElementById('full').style.display = 'none';
  document.getElementById('full-frame').src = '';
}
function navVariant(delta) {
  currentIdx = (currentIdx + delta + variants.length) % variants.length;
  openFull(currentIdx);
}
window.addEventListener('keydown', e => {
  if (document.getElementById('full').style.display !== 'block') return;
  if (e.key === 'Escape') closeFull();
  if (e.key === 'ArrowLeft') navVariant(-1);
  if (e.key === 'ArrowRight') navVariant(1);
});
</script>
</body></html>