<?php
/**
 * nsi.php — aptum.fun/nsi
 * AI-powered Niche Strength Index — intelligent account clusters
 */

define('EVAL_DIR', __DIR__ . '/evaluations/');

// Try to load AI-generated clusters
$clustersFile = EVAL_DIR . 'nsi_clusters.json';
$clusters = null;
if (file_exists($clustersFile)) {
    $raw = json_decode(file_get_contents($clustersFile), true);
    if (!empty($raw['clusters'])) $clusters = $raw;
}

// Fallback: build clusters from niche_category grouping
$evals = [];
if (is_dir(EVAL_DIR)) {
    foreach (glob(EVAL_DIR . '*.json') as $f) {
        $bn = basename($f);
        if ($bn === 'queue.json' || $bn === 'nsi_clusters.json') continue;
        $d = json_decode(file_get_contents($f), true);
        if (!$d || !isset($d['evaluation']['overall'])) continue;
        $evals[] = [
            'username'      => $d['username'] ?? basename($f, '.json'),
            'niche'         => $d['evaluation']['niche'] ?? '',
            'niche_category'=> $d['evaluation']['niche_category'] ?? $d['evaluation']['niche'] ?? '',
            'overall'       => (float)($d['evaluation']['overall'] ?? 0),
            'grade'         => $d['evaluation']['grade'] ?? '',
            'account_type'  => $d['evaluation']['report']['account_type'] ?? '',
            'vibe'          => $d['evaluation']['vibe']['label'] ?? '',
            'followers'     => $d['profile']['followers'] ?? '',
            'pfpUrl'        => $d['profile']['pfpUrl'] ?? '',
            'er_pct'        => $d['evaluation']['report']['weighted_er_pct'] ?? '',
            'scannedAt'     => $d['scannedAt'] ?? null,
        ];
    }
}

if (!$clusters && !empty($evals)) {
    $groups = [];
    foreach ($evals as $e) {
        $n = trim($e['niche_category'] ?: 'Other');
        $groups[$n][] = $e;
    }
    $fallbackClusters = [];
    foreach ($groups as $name => $members) {
        usort($members, fn($a,$b) => $b['overall'] <=> $a['overall']);
        $avg = array_sum(array_column($members, 'overall')) / count($members);
        $fallbackClusters[] = [
            'name'         => $name,
            'description'  => 'Accounts grouped by content category.',
            'theme'        => '',
            'members'      => array_column($members, 'username'),
            'members_data' => $members,
            'avg_score'    => round($avg, 1),
            'top_score'    => $members[0]['overall'],
        ];
    }
    usort($fallbackClusters, fn($a,$b) => $b['avg_score'] <=> $a['avg_score']);
    $clusters = [
        'clusters'      => $fallbackClusters,
        'generated_at'  => null,
        'account_count' => count($evals),
        'is_fallback'   => true,
    ];
}

// ── SVG icon set (Lucide-style, 24×24 stroke) ────────────────────────────────
function clusterIcon(int $i): string {
    $icons = [
        // 0 trending-up
        '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
        // 1 cpu / chip
        '<rect x="9" y="9" width="6" height="6"/><path d="M15 9V7a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v2M9 15v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2M15 12h2a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2h-2M9 12H7a2 2 0 0 0-2 2v0a2 2 0 0 0 2 2h2"/>',
        // 2 crosshair / target
        '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
        // 3 zap / bolt
        '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
        // 4 wrench / tool
        '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
        // 5 activity / pulse (wellness)
        '<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>',
        // 6 message-square (commentary)
        '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
        // 7 bar-chart-2 (analytics)
        '<line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/>',
        // 8 user-x (ghost / inactive)
        '<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><line x1="18" y1="8" x2="23" y2="13"/><line x1="23" y1="8" x2="18" y2="13"/>',
    ];
    $inner = $icons[$i % count($icons)];
    return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">' . $inner . '</svg>';
}

function scoreColor(float $s): string {
    if ($s >= 7) return '#0B7A42';
    if ($s >= 5) return 'rgba(10,12,18,0.55)';
    return '#B91C1C';
}
function relTime(?string $iso): string {
    if (!$iso) return '';
    $d = time() - strtotime($iso);
    if ($d < 60)    return $d . 's ago';
    if ($d < 3600)  return floor($d/60) . 'm ago';
    if ($d < 86400) return floor($d/3600) . 'h ago';
    return floor($d/86400) . 'd ago';
}

$totalAccounts = $clusters['account_count'] ?? count($evals);
$totalClusters = count($clusters['clusters'] ?? []);
$generatedAt   = $clusters['generated_at'] ?? null;
$isFallback    = $clusters['is_fallback'] ?? false;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Niche Strength Index — aptum_</title>
<meta property="og:title" content="Niche Strength Index — aptum_">
<meta property="og:description" content="AI-powered clustering of X accounts by content niche, vibe, and audience type.">
<meta property="og:image" content="https://aptum.fun/eva.png">
<meta name="twitter:card" content="summary_large_image">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=IBM+Plex+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#F4F5F8;--bg1:#ECEEF3;--bg2:#FFFFFF;--bg3:#F8F9FB;
  --bdr:rgba(9,11,16,0.12);--bdr2:rgba(9,11,16,0.22);--bdr3:rgba(9,11,16,0.30);
  --ink:#0A0C12;--ink2:rgba(10,12,18,0.80);--ink3:rgba(10,12,18,0.62);
  --ink4:rgba(10,12,18,0.45);--ink5:rgba(10,12,18,0.20);
  --green:#0B7A42;--green-bg:rgba(11,122,66,0.08);--red:#B91C1C;
  --gold:#B8860B;--gold-bg:rgba(184,134,11,0.08);
  --mono:'IBM Plex Mono',monospace;--disp:'Syne',sans-serif;
}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}

html,body{height:100%;scrollbar-width:none;}
html::-webkit-scrollbar,body::-webkit-scrollbar{display:none}
body{background:var(--bg);color:var(--ink);font-family:var(--mono);-webkit-font-smoothing:antialiased;overflow-x:hidden;}
body::after{content:'';position:fixed;inset:0;z-index:0;pointer-events:none;
  background-image:linear-gradient(rgba(9,11,16,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(9,11,16,.05) 1px,transparent 1px);
  background-size:48px 48px;}

/* TOP BAR */
.topbar{position:fixed;top:0;left:0;right:0;z-index:300;height:32px;background:var(--ink);display:flex;align-items:center;}
.tb-item{display:flex;align-items:center;gap:6px;padding:0 16px;height:32px;border-right:1px solid rgba(255,255,255,.07);font-size:9.5px;letter-spacing:.1em;color:rgba(255,255,255,.35);}
.tb-val{font-weight:700;color:rgba(255,255,255,.80);}
.tb-live-dot{width:5px;height:5px;border-radius:50%;background:#34D399;animation:blink 2.5s infinite;flex-shrink:0;}
#tb-clock{font-size:9.5px;color:rgba(255,255,255,.22);letter-spacing:.1em;margin-left:auto;padding-right:16px;}

/* NAVBAR */
.navbar{position:fixed;top:32px;left:0;right:0;z-index:200;height:52px;background:rgba(255,255,255,.94);backdrop-filter:blur(14px);border-bottom:1.5px solid var(--bdr2);display:flex;align-items:stretch;}
.brand{display:flex;align-items:center;gap:10px;padding:0 24px;border-right:1px solid var(--bdr2);text-decoration:none;}
.brand-name{font-family:var(--disp);font-size:15px;font-weight:800;letter-spacing:.16em;color:var(--ink);}
.brand-pill{font-size:8.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;padding:2px 9px;background:var(--ink);color:#fff;}
.nav-end{margin-left:auto;display:flex;align-items:center;padding:0 24px;gap:22px;}
.nav-a{font-size:9.5px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--ink4);text-decoration:none;transition:color .12s;}
.nav-a:hover{color:var(--ink);}
.nav-a.active{color:var(--ink);}

/* PAGE */
.page{position:relative;z-index:10;max-width:960px;margin:0 auto;padding:104px 18px 80px;overflow-x:hidden;}

/* HERO */
.hero{margin-bottom:32px;animation:fadeUp .4s both;}
.hero-eyebrow{font-size:9px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:var(--ink4);margin-bottom:10px;display:flex;align-items:center;gap:8px;}
.hero-line{width:28px;height:1px;background:var(--bdr2);}
.hero-title{font-family:var(--disp);font-size:clamp(22px,3vw,30px);font-weight:800;letter-spacing:-.01em;color:var(--ink);margin-bottom:5px;}
.hero-sub{font-size:11px;color:var(--ink4);letter-spacing:.04em;font-weight:500;}
.hero-stats{display:flex;gap:18px;margin-top:14px;flex-wrap:wrap;}
.hero-stat{display:flex;align-items:center;gap:7px;padding:6px 14px 6px 12px;background:var(--bg2);border:1px solid var(--bdr2);}
.hero-stat-num{font-family:var(--disp);font-size:17px;font-weight:800;color:var(--ink);}
.hero-stat-label{font-size:8.5px;color:var(--ink4);letter-spacing:.1em;text-transform:uppercase;font-weight:600;}

/* PANEL */
.panel{background:var(--bg2);border:1px solid var(--bdr2);margin-bottom:12px;animation:fadeUp .4s both;}
.sec-hd{padding:9px 18px;border-bottom:1px solid var(--bdr2);display:flex;align-items:center;gap:10px;background:var(--bg1);}
.sec-num{font-size:8px;color:var(--ink4);letter-spacing:.1em;font-weight:700;border:1px solid var(--bdr);padding:2px 6px;background:var(--bg3);}
.sec-pipe{color:var(--bdr2);}
.sec-title{font-size:10.5px;color:var(--ink);letter-spacing:.12em;text-transform:uppercase;font-weight:700;}
.sec-sub{font-size:10px;color:var(--ink4);flex:1;font-weight:500;}
.sec-status{font-size:8.5px;color:var(--ink4);margin-left:auto;letter-spacing:.08em;font-weight:600;white-space:nowrap;}
.sec-status.ok{color:var(--green);}

/* AI BADGE */
.ai-badge{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;background:var(--ink);color:rgba(255,255,255,.85);font-size:8px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;margin-left:auto;}
.ai-badge svg{width:10px;height:10px;stroke:rgba(255,255,255,.7);}
.fallback-note{padding:10px 18px;font-size:10px;color:var(--ink4);border-bottom:1px solid var(--bdr);background:var(--gold-bg);}

/* OVERVIEW BAR */
.overview-bar{display:flex;flex-direction:column;gap:0;}
.ob-row{display:flex;align-items:center;gap:12px;padding:10px 18px;border-bottom:1px solid var(--bdr);cursor:pointer;transition:background .1s;}
.ob-row:last-child{border-bottom:none;}
.ob-row:hover{background:var(--bg3);}
.ob-icon{width:20px;height:20px;flex-shrink:0;color:var(--ink4);}
.ob-icon svg{width:100%;height:100%;}
.ob-row:hover .ob-icon{color:var(--ink2);}
.ob-name{font-size:11px;font-weight:700;color:var(--ink);letter-spacing:.02em;width:180px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ob-desc{font-size:9.5px;color:var(--ink4);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.ob-meta{display:flex;align-items:center;gap:12px;flex-shrink:0;}
.ob-avg{font-size:13px;font-weight:800;font-family:var(--disp);}
.ob-count{font-size:8.5px;color:var(--ink4);font-weight:600;letter-spacing:.06em;}
.ob-bar-wrap{width:80px;height:4px;background:var(--bdr);flex-shrink:0;}
.ob-bar{height:100%;}

/* CLUSTER CARDS */
.clusters-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1px;background:var(--bdr);}
.cluster-card{background:var(--bg2);padding:0;transition:background .12s;}
.cluster-card:hover{background:var(--bg3);}
.cc-head{padding:14px 16px 12px;border-bottom:1px solid var(--bdr);}
.cc-top{display:flex;align-items:flex-start;gap:11px;margin-bottom:8px;}
.cc-icon{width:28px;height:28px;flex-shrink:0;margin-top:1px;padding:5px;background:var(--bg1);border:1px solid var(--bdr);color:var(--ink3);}
.cc-icon svg{width:100%;height:100%;}
.cc-name{font-size:13px;font-weight:700;color:var(--ink);letter-spacing:.02em;line-height:1.3;}
.cc-meta{display:flex;align-items:center;gap:8px;margin-top:3px;}
.cc-avg{font-family:var(--disp);font-size:16px;font-weight:800;}
.cc-avg-label{font-size:8px;color:var(--ink4);letter-spacing:.08em;text-transform:uppercase;font-weight:700;}
.cc-count-pill{font-size:8px;font-weight:700;letter-spacing:.1em;padding:2px 7px;background:var(--bg1);border:1px solid var(--bdr);color:var(--ink4);}
.cc-desc{font-size:9.5px;color:var(--ink3);line-height:1.55;margin-top:8px;}
.cc-members{padding:12px 16px;}
.cc-member{display:flex;align-items:center;gap:10px;padding:7px 0;border-bottom:1px solid var(--bdr);text-decoration:none;transition:background .1s;}
.cc-member:last-child{border-bottom:none;}
.cc-member:hover .cc-m-handle{color:var(--ink);}
.cc-m-pfp{width:28px;height:28px;border-radius:50%;object-fit:cover;background:var(--bg3);flex-shrink:0;border:1px solid var(--bdr);}
.cc-m-info{flex:1;min-width:0;}
.cc-m-handle{font-size:11.5px;font-weight:700;color:var(--ink2);letter-spacing:.01em;transition:color .1s;}
.cc-m-niche{font-size:8.5px;color:var(--ink4);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cc-m-score{font-size:12px;font-weight:700;font-family:var(--disp);flex-shrink:0;}
.cc-m-bar{height:2px;background:var(--bdr);margin-top:3px;width:100%;}
.cc-m-bar-fill{height:100%;}
.cc-more{font-size:9px;color:var(--ink4);text-align:center;padding:8px 0 2px;letter-spacing:.06em;}

/* EMPTY */
.empty{padding:48px 24px;text-align:center;font-size:11px;color:var(--ink4);letter-spacing:.06em;}

/* GENERATED TIME */
.gen-time{font-size:9px;color:var(--ink5);letter-spacing:.06em;padding:10px 18px;text-align:right;border-top:1px solid var(--bdr);}

@media(max-width:640px){
  .tb-item:nth-child(n+3){display:none;}
  #tb-clock{display:none;}
  .nav-end{padding:0 10px;gap:10px;}
  .brand{padding:0 14px;}
  .brand-name{font-size:13px;}
  .page{padding:84px 10px 52px;}
  .hero{margin-bottom:20px;}
  .sec-sub{display:none;}
  .clusters-grid{grid-template-columns:1fr;}
  .ob-desc{display:none;}
  .ob-name{width:130px;}
  .ob-bar-wrap{width:56px;}
  .hero-stats{gap:10px;}
}
@media(max-width:420px){
  .brand-pill{display:none;}
  .nav-a{font-size:8.5px;letter-spacing:.08em;}
  .ob-name{width:100px;}
  .ob-avg{font-size:11px;}
  .hero-stat-num{font-size:14px;}
}
</style>
</head>
<body>

<!-- TOP BAR -->
<div class="topbar">
  <div class="tb-item"><span class="tb-live-dot"></span><span class="tb-val">LIVE</span></div>
  <div class="tb-item">NSI <span class="tb-val"><?= $totalClusters ?> CLUSTERS</span></div>
  <div class="tb-item"><span class="tb-val"><?= $totalAccounts ?></span> ACCOUNTS</div>
  <span id="tb-clock"></span>
</div>

<!-- NAVBAR -->
<nav class="navbar">
  <a href="/evaluate" class="brand">
    <span class="brand-name">APTUM_</span>
    <span class="brand-pill">AI</span>
  </a>
  <div class="nav-end">
    <a href="/evaluate" class="nav-a">Evaluate</a>
    <a href="/leaderboard" class="nav-a">Leaderboard</a>
    <a href="/nsi" class="nav-a active">NSI</a>
  </div>
</nav>

<div class="page">

  <!-- HERO -->
  <div class="hero">
    <div class="hero-eyebrow"><span class="hero-line"></span>AI-POWERED CLUSTERING<span class="hero-line"></span></div>
    <div class="hero-title">Niche Strength Index</div>
    <div class="hero-sub">Accounts grouped by what they actually do — not just keywords.</div>
    <div class="hero-stats">
      <div class="hero-stat">
        <span class="hero-stat-num"><?= $totalAccounts ?></span>
        <span class="hero-stat-label">Accounts</span>
      </div>
      <div class="hero-stat">
        <span class="hero-stat-num"><?= $totalClusters ?></span>
        <span class="hero-stat-label">Clusters</span>
      </div>
      <?php if ($generatedAt): ?>
      <div class="hero-stat">
        <span class="hero-stat-num" style="font-size:12px;"><?= relTime($generatedAt) ?></span>
        <span class="hero-stat-label">Last Updated</span>
      </div>
      <?php endif; ?>
    </div>
  </div>

  <?php if (empty($clusters['clusters'])): ?>
  <div class="panel">
    <div class="empty">No evaluations yet. <a href="/evaluate" style="color:var(--ink3)">Run some evaluations</a> to generate clusters.</div>
  </div>
  <?php else: ?>

  <!-- ── §01 CLUSTER OVERVIEW ── -->
  <?php $maxAvg = max(array_column($clusters['clusters'], 'avg_score')); ?>
  <div class="panel" style="animation-delay:.05s">
    <div class="sec-hd">
      <span class="sec-num">01</span><span class="sec-pipe">|</span>
      <span class="sec-title">Cluster Overview</span>
      <span class="sec-sub">Ranked by average aptum score</span>
      <?php if (!$isFallback): ?>
      <span class="ai-badge">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
        AI Clustered
      </span>
      <?php else: ?>
      <span class="sec-status">Category fallback</span>
      <?php endif; ?>
    </div>
    <?php if ($isFallback): ?>
    <div class="fallback-note">
      AI clustering is warming up — showing category groups for now. Clusters will update automatically after sizable data is gathered.
    </div>
    <?php endif; ?>
    <div class="overview-bar">
      <?php foreach ($clusters['clusters'] as $i => $cl):
        $pct    = $maxAvg > 0 ? round(($cl['avg_score'] / $maxAvg) * 100) : 0;
        $sc     = scoreColor($cl['avg_score']);
        $anchor = 'cluster-' . $i;
      ?>
      <div class="ob-row" onclick="document.getElementById('<?= $anchor ?>').scrollIntoView({behavior:'smooth',block:'start'})">
        <span class="ob-icon"><?= clusterIcon($i) ?></span>
        <span class="ob-name"><?= htmlspecialchars($cl['name']) ?></span>
        <span class="ob-desc"><?= htmlspecialchars($cl['description']) ?></span>
        <div class="ob-meta">
          <div class="ob-bar-wrap"><div class="ob-bar" style="width:<?= $pct ?>%;background:<?= $sc ?>;"></div></div>
          <span class="ob-avg" style="color:<?= $sc ?>"><?= $cl['avg_score'] ?></span>
          <span class="ob-count"><?= count($cl['members_data'] ?? $cl['members']) ?> accts</span>
        </div>
      </div>
      <?php endforeach; ?>
    </div>
  </div>

  <!-- ── §02 CLUSTER DETAIL CARDS ── -->
  <div class="panel" style="animation-delay:.1s">
    <div class="sec-hd">
      <span class="sec-num">02</span><span class="sec-pipe">|</span>
      <span class="sec-title">Cluster Breakdown</span>
      <span class="sec-sub">Members, scores, and niches per cluster</span>
      <span class="sec-status ok"><?= $totalAccounts ?> accounts · <?= $totalClusters ?> clusters</span>
    </div>
    <div class="clusters-grid">
      <?php foreach ($clusters['clusters'] as $i => $cl):
        $members    = $cl['members_data'] ?? [];
        $sc         = scoreColor($cl['avg_score']);
        $displayMax = 6;
        $shown      = array_slice($members, 0, $displayMax);
        $extra      = count($members) - $displayMax;
      ?>
      <div class="cluster-card" id="cluster-<?= $i ?>">
        <div class="cc-head">
          <div class="cc-top">
            <span class="cc-icon"><?= clusterIcon($i) ?></span>
            <div>
              <div class="cc-name"><?= htmlspecialchars($cl['name']) ?></div>
              <div class="cc-meta">
                <span class="cc-avg" style="color:<?= $sc ?>"><?= $cl['avg_score'] ?></span>
                <span class="cc-avg-label">avg</span>
                <span class="cc-count-pill"><?= count($members) ?> accts</span>
              </div>
            </div>
          </div>
          <div class="cc-desc"><?= htmlspecialchars($cl['description']) ?></div>
        </div>
        <div class="cc-members">
          <?php foreach ($shown as $m):
            $msc  = scoreColor($m['overall']);
            $mpct = (int)(($m['overall'] / 10) * 100);
            $pfp  = htmlspecialchars($m['pfpUrl'] ?: 'https://unavatar.io/x/' . urlencode($m['username']));
            $fbPfp = 'https://unavatar.io/x/' . urlencode($m['username']);
          ?>
          <a class="cc-member" href="/evaluate?u=<?= urlencode($m['username']) ?>">
            <img class="cc-m-pfp" src="<?= $pfp ?>"
                 onerror="this.src='<?= $fbPfp ?>';this.onerror=null" alt="">
            <div class="cc-m-info">
              <div class="cc-m-handle">@<?= htmlspecialchars($m['username']) ?></div>
              <div class="cc-m-niche"><?= htmlspecialchars($m['niche'] ?: $m['niche_category']) ?></div>
              <div class="cc-m-bar"><div class="cc-m-bar-fill" style="width:<?= $mpct ?>%;background:<?= $msc ?>;"></div></div>
            </div>
            <span class="cc-m-score" style="color:<?= $msc ?>"><?= number_format($m['overall'], 1) ?></span>
          </a>
          <?php endforeach; ?>
          <?php if ($extra > 0): ?>
          <div class="cc-more">+<?= $extra ?> more in this cluster</div>
          <?php endif; ?>
        </div>
      </div>
      <?php endforeach; ?>
    </div>
    <?php if ($generatedAt): ?>
    <div class="gen-time">AI clusters generated <?= relTime($generatedAt) ?> · <?= htmlspecialchars(substr($generatedAt, 0, 10)) ?></div>
    <?php endif; ?>
  </div>

  <?php endif; ?>

</div><!-- /page -->

<script>
function updateClock() {
    const n = new Date(), p = v => String(v).padStart(2,'0');
    document.getElementById('tb-clock').textContent =
        `${p(n.getUTCHours())}:${p(n.getUTCMinutes())}:${p(n.getUTCSeconds())} UTC`;
}
updateClock(); setInterval(updateClock, 1000);
</script>
</body>
</html>
