fix(dashboard): hover infobulles, full-width layout, UX polish

- Fix doc tooltips: split CSS into <style type='text/tailwindcss'> for
  @apply directives + raw CSS for reliable doc panel rendering
- Convert doc panels from click-toggle to hover-based infobulles with
  arrow pointer, fade-in animation, and auto-dismiss on mobile
- Replace '?' icons with 'ⓘ' across all 11 templates (51 tooltips)
- Full-width layout: reduce padding on mobile (px-3), scale up on
  desktop (lg:px-5, xl:px-6) for maximum screen utilization
- Auto-collapse sidebar on narrow screens (<1024px)
- Keyboard shortcuts: Alt+1–9 for page navigation, Alt+B toggle sidebar
- Add LEGITIMATE_BROWSER filter button to detections page
- Sticky header with stronger blur (backdrop-blur-md)
- All 46 routes pass tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-09 13:30:16 +02:00
parent 63ba6d203c
commit 6babc55e3e
11 changed files with 115 additions and 68 deletions

View File

@ -23,7 +23,7 @@
}
}
</script>
<style>
<style type="text/tailwindcss">
body { font-family: 'Inter', system-ui, sans-serif; }
/* ── Threat badges ── */
.threat-critical { color: #ef4444; font-weight: 700; }
@ -56,21 +56,51 @@
.section-title { @apply text-sm font-semibold text-gray-200 flex items-center gap-2; }
.section-body { @apply p-5; }
/* ── Sidebar ── */
.nav-item { @apply flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800/60 transition-colors text-sm cursor-pointer; }
.nav-item.active { @apply bg-brand-600/20 text-brand-400 border-l-2 border-brand-500; }
.nav-group-title { @apply text-[10px] uppercase tracking-widest text-gray-600 px-3 pt-4 pb-1; }
</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; }
.nav-item { @apply flex items-center gap-3 px-3 py-2 rounded-lg text-gray-400 hover:text-white hover:bg-gray-800/60 transition-colors text-sm cursor-pointer; }
.nav-item.active { @apply bg-brand-600/20 text-brand-400 border-l-2 border-brand-500; }
.nav-group-title { @apply text-[10px] uppercase tracking-widest text-gray-600 px-3 pt-4 pb-1; }
/* ── Doc tooltips ── */
.doc-btn { @apply inline-flex items-center justify-center w-5 h-5 rounded-full text-gray-500 hover:text-gray-300 hover:bg-gray-700 transition-colors cursor-help text-xs; }
.doc-panel { @apply hidden absolute z-50 w-80 p-4 bg-gray-800 border border-gray-700 rounded-xl shadow-2xl text-xs text-gray-300 leading-relaxed; }
.doc-panel.show { @apply block; }
.doc-panel h4 { @apply text-white font-semibold text-sm mb-2; }
.doc-panel p { @apply mb-2; }
.doc-panel .doc-source { @apply text-gray-500 italic mt-2 pt-2 border-t border-gray-700; }
/* ── 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; }
@ -156,14 +186,14 @@
<!-- ═══ 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/80 backdrop-blur border-b border-gray-800">
<div class="flex items-center h-12 px-6">
<h1 class="text-base font-semibold text-gray-100">{% block page_title %}{% endblock %}</h1>
<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-6 py-5">
<main class="px-3 py-4 lg:px-5 lg:py-5 xl:px-6">
{% block content %}{% endblock %}
</main>
</div>
@ -176,6 +206,17 @@
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() {
@ -184,11 +225,16 @@
}
updateClock(); setInterval(updateClock, 1000);
// ── Doc tooltip system ──
// ── 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'))
@ -314,9 +360,9 @@
).join('');
}
// ── Doc helper: generates a (?) button + panel ──
// ── 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>`;
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 %}