<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}JA4 SOC Dashboard{% endblock %}</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        brand: { 50:'#eef2ff',100:'#e0e7ff',500:'#6366f1',600:'#4f46e5',700:'#4338ca',900:'#312e81' },
                        surface: { 800:'#1e293b', 900:'#0f172a', 950:'#020617' },
                    },
                    fontFamily: { sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'] },
                }
            }
        }
    </script>
    <style>
        body { font-family: 'Inter', system-ui, sans-serif; }
        /* ── Threat badges ── */
        .threat-critical { color: #ef4444; font-weight: 700; }
        .threat-high     { color: #f97316; font-weight: 600; }
        .threat-medium   { color: #eab308; }
        .threat-low      { color: #22c55e; }
        .threat-normal   { color: #6b7280; }
        /* ── KPI cards ── */
        .kpi-card { background: rgba(17,24,39,0.8); border-radius: 0.75rem; padding: 1rem; border: 1px solid #1f2937; backdrop-filter: blur(8px); }
        /* ── Data table ── */
        .data-table { width: 100%; font-size: 0.875rem; text-align: left; }
        .data-table th { padding: 0.625rem 0.75rem; background: #111827; color: #9ca3af; font-weight: 500; border-bottom: 1px solid #1f2937; position: sticky; top: 0; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
        .data-table td { padding: 0.5rem 0.75rem; border-bottom: 1px solid rgba(31,41,55,0.5); color: #d1d5db; }
        .data-table tbody tr:hover { background: rgba(31,41,55,0.4); }
        .data-table tbody tr { cursor: pointer; transition: background-color 0.15s, color 0.15s; }
        /* ── Badges ── */
        .badge { display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; }
        .badge-critical { background: rgba(239,68,68,0.2); color: #f87171; }
        .badge-high     { background: rgba(249,115,22,0.2); color: #fb923c; }
        .badge-medium   { background: rgba(234,179,8,0.2); color: #facc15; }
        .badge-low      { background: rgba(34,197,94,0.2); color: #4ade80; }
        .badge-normal   { background: rgba(107,114,128,0.2); color: #9ca3af; }
        .badge-known    { background: rgba(59,130,246,0.2); color: #60a5fa; }
        /* ── Filter buttons ── */
        .filter-btn { padding: 0.375rem 0.75rem; font-size: 0.75rem; border-radius: 0.5rem; border: 1px solid #374151; color: #9ca3af; transition: border-color 0.15s, color 0.15s; cursor: pointer; background: transparent; }
        .filter-btn:hover { border-color: #6366f1; color: #6366f1; }
        .filter-btn.active { border-color: #6366f1; background: rgba(99,102,241,0.2); color: #6366f1; }
        /* ── Section card ── */
        .section-card { background: rgba(17,24,39,0.8); border-radius: 0.75rem; border: 1px solid #1f2937; backdrop-filter: blur(8px); }
        .section-header { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 1.25rem; border-bottom: 1px solid #1f2937; }
        .section-title { font-size: 0.875rem; font-weight: 600; color: #e5e7eb; display: flex; align-items: center; gap: 0.5rem; }
        .section-body { padding: 1.25rem; }
        /* ── Sidebar ── */
        .nav-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0.75rem; border-radius: 0.5rem; color: #9ca3af; transition: color 0.15s, background 0.15s; font-size: 0.875rem; cursor: pointer; text-decoration: none; }
        .nav-item:hover { color: white; background: rgba(31,41,55,0.6); }
        .nav-item.active { background: rgba(79,70,229,0.2); color: #818cf8; border-left: 2px solid #6366f1; }
        .nav-group-title { font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: #4b5563; padding: 1rem 0.75rem 0.25rem; }
    </style>
    <style>
        /* ── Raw CSS (no @apply) for reliable rendering ── */
        .sidebar { width: 220px; transition: width 0.2s ease; }
        .sidebar.collapsed { width: 56px; }
        .sidebar.collapsed .nav-text { display: none; }
        .sidebar.collapsed .nav-group-title { display: none; }
        .sidebar.collapsed .sidebar-logo-text { display: none; }

        /* ── Infobulles (hover tooltips) ── */
        .doc-btn {
            display: inline-flex; align-items: center; justify-content: center;
            width: 16px; height: 16px; border-radius: 50%; font-size: 10px;
            color: #4b5563; cursor: help; transition: all 0.15s;
            vertical-align: middle; margin-left: 4px; flex-shrink: 0;
        }
        .doc-btn:hover { color: #d1d5db; background: #374151; }
        .doc-panel {
            display: none; position: absolute; z-index: 60;
            top: calc(100% + 10px); right: -8px; width: 300px;
            padding: 12px 14px; background: #111827; border: 1px solid #374151;
            border-radius: 10px; box-shadow: 0 16px 48px rgba(0,0,0,0.6);
            font-size: 11px; line-height: 1.6; color: #d1d5db;
            pointer-events: auto;
        }
        .doc-panel::before {
            content: ''; position: absolute; top: -5px; right: 14px;
            width: 10px; height: 10px; background: #111827;
            border-left: 1px solid #374151; border-top: 1px solid #374151;
            transform: rotate(45deg);
        }
        .doc-panel h4 { color: white; font-weight: 600; font-size: 12px; margin: 0 0 6px; }
        .doc-panel p { margin: 0 0 5px; }
        .doc-panel .doc-source { color: #6b7280; font-style: italic; margin-top: 6px; padding-top: 6px; border-top: 1px solid #1f2937; font-size: 10px; }
        /* Hover: show on parent hover */
        .relative:has(> .doc-btn):hover > .doc-panel,
        .doc-panel.show {
            display: block; animation: ttIn 0.12s ease-out;
        }
        /* Mobile: keep tap toggle via JS */
        @keyframes ttIn { from { opacity:0; transform:translateY(-3px); } to { opacity:1; transform:translateY(0); } }

        /* ── Animations ── */
        @keyframes fadeUp { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:translateY(0); } }
        .animate-in { animation: fadeUp 0.3s ease-out both; }
        .kpi-card { animation: fadeUp 0.3s ease-out both; }
        .kpi-card:nth-child(1) { animation-delay: 0ms; }
        .kpi-card:nth-child(2) { animation-delay: 40ms; }
        .kpi-card:nth-child(3) { animation-delay: 80ms; }
        .kpi-card:nth-child(4) { animation-delay: 120ms; }
        .kpi-card:nth-child(5) { animation-delay: 160ms; }
        .kpi-card:nth-child(6) { animation-delay: 200ms; }
        @keyframes pulse-dot { 0%,100%{opacity:1;} 50%{opacity:0.4;} }
        .live-dot { width:6px; height:6px; background:#22c55e; border-radius:50%; display:inline-block; animation: pulse-dot 1.5s ease-in-out infinite; }
        /* ── Scrollbar ── */
        ::-webkit-scrollbar { width: 6px; height: 6px; }
        ::-webkit-scrollbar-track { background: transparent; }
        ::-webkit-scrollbar-thumb { background: #374151; border-radius: 3px; }
        ::-webkit-scrollbar-thumb:hover { background: #4b5563; }
    </style>
    {% block head %}{% endblock %}
</head>
<body class="bg-gray-950 text-gray-200 min-h-screen flex">

    <!-- ═══ Sidebar Navigation ═══ -->
    <aside id="sidebar" class="sidebar fixed top-0 left-0 h-screen bg-gray-950 border-r border-gray-800 flex flex-col z-50 overflow-hidden">
        <!-- Logo -->
        <div class="flex items-center gap-2 px-3 h-14 border-b border-gray-800 shrink-0">
            <button onclick="toggleSidebar()" class="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center text-white font-bold text-sm hover:bg-brand-500 transition-colors shrink-0">J4</button>
            <span class="sidebar-logo-text text-white font-semibold text-sm whitespace-nowrap">JA4 SOC</span>
        </div>
        <!-- Nav groups -->
        <nav class="flex-1 overflow-y-auto py-2 px-2 space-y-0.5">
            <div class="nav-group-title">Surveillance</div>
            <a href="/" class="nav-item {% if active_page == 'overview' %}active{% endif %}" title="Vue d'ensemble">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
                <span class="nav-text">Overview</span>
            </a>
            <a href="/detections" class="nav-item {% if active_page == 'detections' %}active{% endif %}" title="Détections d'anomalies">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
                <span class="nav-text">Détections</span>
            </a>
            <a href="/scores" class="nav-item {% if active_page == 'scores' %}active{% endif %}" title="Scores ML">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
                <span class="nav-text">Scores</span>
            </a>
            <a href="/campaigns" class="nav-item {% if active_page == 'campaigns' %}active{% endif %}" title="Campagnes de bots (clusters)">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
                <span class="nav-text">Campagnes</span>
            </a>
            <a href="/tactics" class="nav-item {% if active_page == 'tactics' %}active{% endif %}" title="Tactiques d'attaque détectées">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
                <span class="nav-text">Tactiques</span>
            </a>
            <a href="/fleet" class="nav-item {% if active_page == 'fleet' %}active{% endif %}" title="Flottes JA4×ASN (§5.2)">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
                <span class="nav-text">Flottes</span>
            </a>
            <a href="/browsers" class="nav-item {% if active_page == 'browsers' %}active{% endif %}" title="Signatures navigateur H2 (§4)">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
                <span class="nav-text">Navigateurs</span>
            </a>
            <a href="/fingerprints" class="nav-item {% if active_page == 'fingerprints' %}active{% endif %}" title="Découverte de fingerprints (extraction DB)">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
                <span class="nav-text">Découverte</span>
            </a>

            <div class="nav-group-title">Investigation</div>
            <a href="/traffic" class="nav-item {% if active_page == 'traffic' %}active{% endif %}" title="Logs HTTP bruts">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2 1 3 3 3h10c2 0 3-1 3-3V7c0-2-1-3-3-3H7c-2 0-3 1-3 3z"/><path stroke-linecap="round" stroke-width="2" d="M9 12h6M9 8h6M9 16h3"/></svg>
                <span class="nav-text">Trafic</span>
            </a>
            <a href="/network" class="nav-item {% if active_page == 'network' %}active{% endif %}" title="Analyse réseau">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9"/></svg>
                <span class="nav-text">Réseau</span>
            </a>
            <a href="/features" class="nav-item {% if active_page == 'features' %}active{% endif %}" title="Features ML">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z"/></svg>
                <span class="nav-text">Features</span>
            </a>

            <div class="nav-group-title">Opérations</div>
            <a href="/models" class="nav-item {% if active_page == 'models' %}active{% endif %}" title="Modèles ML">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
                <span class="nav-text">Modèles</span>
            </a>
            <a href="/classify" class="nav-item {% if active_page == 'classify' %}active{% endif %}" title="Classification SOC">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/></svg>
                <span class="nav-text">Classifier</span>
            </a>
            <a href="/reflists" class="nav-item {% if active_page == 'reflists' %}active{% endif %}" title="Listes de référence CSV / Dictionnaires">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.58 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.58 4 8 4s8-1.79 8-4M4 7c0-2.21 3.58-4 8-4s8 1.79 8 4m0 5c0 2.21-3.58 4-8 4s-8-1.79-8-4"/></svg>
                <span class="nav-text">Listes réf.</span>
            </a>
            <a href="/health" class="nav-item {% if active_page == 'health' %}active{% endif %}" title="Santé du pipeline ML (§9)">
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
                <span class="nav-text">Santé ML</span>
            </a>
        </nav>
        <!-- Footer -->
        <div class="px-3 py-3 border-t border-gray-800 shrink-0">
            <span class="flex items-center gap-2 text-[10px] text-gray-500">
                <span class="live-dot"></span>
                <span id="clock" class="nav-text"></span>
            </span>
        </div>
    </aside>

    <!-- ═══ Main Content ═══ -->
    <div id="main-wrap" class="flex-1 min-h-screen" style="margin-left:220px; transition: margin-left 0.2s ease;">
        <!-- Page header -->
        <header class="sticky top-0 z-40 bg-gray-950/90 backdrop-blur-md border-b border-gray-800">
            <div class="flex items-center h-12 px-4 lg:px-6">
                <h1 class="text-sm lg:text-base font-semibold text-gray-100 truncate">{% block page_title %}{% endblock %}</h1>
                <div class="flex-1"></div>
                {% block header_actions %}{% endblock %}
            </div>
        </header>
        <main class="px-3 py-4 lg:px-5 lg:py-5 xl:px-6">
            {% block content %}{% endblock %}
        </main>
    </div>

    <script>
        // ── Sidebar toggle ──
        function toggleSidebar() {
            const sb = document.getElementById('sidebar');
            const mw = document.getElementById('main-wrap');
            sb.classList.toggle('collapsed');
            mw.style.marginLeft = sb.classList.contains('collapsed') ? '56px' : '220px';
        }
        // Auto-collapse on narrow screens
        if (window.innerWidth < 1024) toggleSidebar();

        // ── Keyboard shortcuts ──
        document.addEventListener('keydown', e => {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
            const routes = {'1':'/',  '2':'/detections', '3':'/scores', '4':'/campaigns',
                            '5':'/traffic', '6':'/network', '7':'/features', '8':'/models', '9':'/classify'};
            if (e.altKey && routes[e.key]) { e.preventDefault(); window.location = routes[e.key]; }
            if (e.key === 'b' && e.altKey) { e.preventDefault(); toggleSidebar(); }
        });

        // ── Clock ──
        function updateClock() {
            const el = document.getElementById('clock');
            if (el) el.textContent = new Date().toLocaleString('fr-FR');
        }
        updateClock(); setInterval(updateClock, 1000);

        // ── Doc tooltip (mobile tap fallback) ──
        function docToggle(btn) {
            const panel = btn.nextElementSibling;
            document.querySelectorAll('.doc-panel.show').forEach(p => { if (p !== panel) p.classList.remove('show'); });
            panel.classList.toggle('show');
            // Auto-dismiss after 8s on mobile
            if (panel.classList.contains('show')) {
                clearTimeout(panel._timer);
                panel._timer = setTimeout(() => panel.classList.remove('show'), 8000);
            }
        }
        document.addEventListener('click', e => {
            if (!e.target.closest('.doc-btn') && !e.target.closest('.doc-panel'))
                document.querySelectorAll('.doc-panel.show').forEach(p => p.classList.remove('show'));
        });

        // ── HTML escape ──
        function esc(s) {
            const d = document.createElement('div');
            d.appendChild(document.createTextNode(s));
            return d.innerHTML;
        }

        // ── Number formatting ──
        function fmtNum(n) {
            if (n == null) return '—';
            n = Number(n);
            if (n >= 1000000) return (n/1000000).toFixed(1) + 'M';
            if (n >= 1000) return (n/1000).toFixed(1) + 'K';
            return n.toLocaleString('fr-FR');
        }
        function fmtPct(n) { return n == null ? '—' : (Number(n)*100).toFixed(1)+'%'; }

        // ── Threat helpers ──
        function threatBadge(level) {
            const map = {
                'CRITICAL':'badge-critical','HIGH':'badge-high','MEDIUM':'badge-medium',
                'LOW':'badge-low','NORMAL':'badge-normal','KNOWN_BOT':'badge-known',
                'ANUBIS_DENY':'badge-critical','LEGITIMATE_BROWSER':'badge-low'
            };
            return `<span class="badge ${map[level]||'badge-normal'}">${escapeHtml(level||'')}</span>`;
        }
        function escapeHtml(s) {
            const d = document.createElement('div');
            d.textContent = String(s);
            return d.innerHTML;
        }

        // ── Link formatters (all sanitized) ──
        function fmtIP(ip) {
            if (!ip) return '';
            let s = String(ip).replace('::ffff:','');
            return `<a href="/ip/${encodeURIComponent(s)}" class="text-brand-500 hover:underline font-mono text-xs">${escapeHtml(s)}</a>`;
        }
        function fmtScore(v) {
            let n = parseFloat(v);
            if (isNaN(n)) return '—';
            let color = n > 0.7 ? 'text-red-400' : n > 0.4 ? 'text-orange-400' : n > 0.1 ? 'text-yellow-400' : 'text-green-400';
            return `<span class="${color} font-mono">${n.toFixed(4)}</span>`;
        }
        function fmtASN(org) {
            if (!org) return '';
            return `<a href="/network?asn_org=${encodeURIComponent(org)}" onclick="event.stopPropagation()" class="text-blue-400 hover:underline cursor-pointer">${escapeHtml(org)}</a>`;
        }
        function fmtCountry(cc) {
            if (!cc) return '';
            const flags = {'FR':'🇫🇷','DE':'🇩🇪','NL':'🇳🇱','GB':'🇬🇧','ES':'🇪🇸','US':'🇺🇸','RU':'🇷🇺','IT':'🇮🇹','JP':'🇯🇵','CN':'🇨🇳','KR':'🇰🇷','BR':'🇧🇷','AU':'🇦🇺','CA':'🇨🇦','IN':'🇮🇳','SG':'🇸🇬','SE':'🇸🇪','FI':'🇫🇮','IE':'🇮🇪','CH':'🇨🇭'};
            return `<a href="/detections?country_code=${encodeURIComponent(cc)}" onclick="event.stopPropagation()" class="hover:underline cursor-pointer">${flags[cc]||'🏳️'} ${escapeHtml(cc)}</a>`;
        }
        function fmtJA4(ja4) {
            if (!ja4) return '';
            return `<a href="/ja4/${encodeURIComponent(ja4)}" onclick="event.stopPropagation()" class="text-purple-400 hover:underline cursor-pointer font-mono text-[11px]" title="${escapeHtml(ja4)}">${escapeHtml(ja4.substring(0,22))}…</a>`;
        }
        function fmtJA4Full(ja4) {
            if (!ja4) return '';
            return `<a href="/ja4/${encodeURIComponent(ja4)}" onclick="event.stopPropagation()" class="text-purple-400 hover:underline cursor-pointer font-mono text-[11px]">${escapeHtml(ja4)}</a>`;
        }
        function fmtBotName(name) {
            if (!name) return '';
            return `<a href="/detections?bot_name=${encodeURIComponent(name)}" onclick="event.stopPropagation()" class="text-cyan-400 hover:underline cursor-pointer">${escapeHtml(name)}</a>`;
        }
        function fmtThreatLink(level) {
            if (!level) return '';
            return `<a href="/detections?threat_level=${encodeURIComponent(level)}" class="cursor-pointer">${threatBadge(level)}</a>`;
        }
        function fmtLabel(label) {
            if (!label) return '';
            const colors = {human:'text-green-400 bg-green-500/10',datacenter:'text-red-400 bg-red-500/10',hosting:'text-orange-400 bg-orange-500/10',cdn:'text-cyan-400 bg-cyan-500/10'};
            return `<span class="px-1.5 py-0.5 rounded text-xs ${colors[label]||'text-gray-400 bg-gray-500/10'}">${escapeHtml(label)}</span>`;
        }
        function fmtDuration(seconds) {
            if (!seconds || seconds < 0) return '—';
            if (seconds < 60) return Math.round(seconds) + 's';
            if (seconds < 3600) return Math.round(seconds/60) + 'min';
            return (seconds/3600).toFixed(1) + 'h';
        }
        function fmtAgo(dateStr) {
            if (!dateStr) return '—';
            const diff = (Date.now() - new Date(dateStr).getTime()) / 1000;
            if (diff < 60) return 'à l\'instant';
            if (diff < 3600) return Math.round(diff/60) + ' min';
            if (diff < 86400) return Math.round(diff/3600) + 'h';
            return Math.round(diff/86400) + 'j';
        }
        function fmtDate(d) {
            if (!d) return '—';
            return String(d).substring(0, 16).replace('T', ' ');
        }

        // ── ECharts helpers ──
        const EC_COLORS = ['#6366f1','#22c55e','#f97316','#ef4444','#3b82f6','#eab308','#ec4899','#14b8a6','#8b5cf6','#f43f5e'];
        const EC_TEXT   = '#9ca3af';
        const EC_GRID   = '#374151';

        function ecBase(overrides) {
            return Object.assign({
                backgroundColor: 'transparent',
                textStyle: { color: EC_TEXT, fontFamily: 'Inter, system-ui, sans-serif', fontSize: 11 },
                animation: true, animationDuration: 400,
            }, overrides);
        }
        function ecTooltip(extra) {
            return Object.assign({
                backgroundColor: '#1f2937', borderColor: '#374151',
                textStyle: { color: '#e5e7eb', fontSize: 11 },
                confine: true,
            }, extra);
        }
        function ecGrid(extra) {
            return Object.assign({ left: 50, right: 20, top: 30, bottom: 30 }, extra);
        }

        // ── Chart resize on window resize ──
        window.addEventListener('resize', () => {
            document.querySelectorAll('[_echarts_instance_]').forEach(el => {
                echarts.getInstanceByDom(el)?.resize();
            });
        });

        // ── Generic table builder ──
        function buildTable(tbody, rows, cols) {
            tbody.innerHTML = rows.map(r =>
                '<tr>' + cols.map(c => `<td>${c.fmt ? c.fmt(r[c.key], r) : escapeHtml(r[c.key]??'')}</td>`).join('') + '</tr>'
            ).join('');
        }

        // ── Doc helper: generates an ⓘ tooltip button + panel ──
        function docHTML(title, body, source) {
            return `<span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn" aria-label="Aide">ⓘ</button><div class="doc-panel"><h4>${escapeHtml(title)}</h4>${body}<p class="doc-source">Source : ${escapeHtml(source)}</p></div></span>`;
        }
    </script>
    {% block scripts %}{% endblock %}
</body>
</html>
