feat: collapsible panels + medium breakpoint on live map (#606)

## Summary

Adds collapsible/minimizable UI panels on the live map page so overlay
panels don't block map content on medium-sized screens.

Fixes #279

## Changes

### Collapsible Legend Panel (all screen sizes)
- The legend toggle button (🎨/✕) is now visible at **all** screen sizes,
not just mobile
- Clicking it smoothly collapses/expands the legend with a CSS
transition
- Collapsed state persists in `localStorage` (`live-legend-hidden`)
- Feed panel already had hide/show with localStorage — no changes needed
there

### Medium Breakpoint (768px)
New `@media (max-width: 768px)` rules for tablet/small laptop screens:
- Feed panel: 360px → 280px wide, max-height 340px → 200px
- Node detail panel: 320px → 260px wide
- Legend: smaller font (10px) and tighter padding
- Header: reduced gap and padding
- Stats/toggles: smaller font sizes

### What's NOT changed
- Mobile (≤640px): existing behavior preserved (feed/legend hidden
entirely)
- Desktop (>768px): no changes — panels render at full size as before

## Testing
- `test-packet-filter.js`: 62 passed
- `test-aging.js`: 29 passed  
- `test-frontend-helpers.js`: 445 passed

---------

Co-authored-by: you <you@example.com>
This commit is contained in:
Kpa-clawbot
2026-04-04 23:56:07 -07:00
committed by GitHub
parent cbc3e3ce13
commit e42477b810
2 changed files with 33 additions and 6 deletions
+20 -2
View File
@@ -201,6 +201,15 @@
display: flex;
flex-direction: column;
gap: 3px;
transition: opacity 0.3s, transform 0.3s;
}
/* Collapsible legend (#279) */
.live-legend.hidden {
opacity: 0;
transform: translateX(100%);
pointer-events: none;
visibility: hidden;
}
.legend-title {
@@ -272,6 +281,16 @@
background: rgba(59, 130, 246, 0.2) !important;
}
/* ---- Medium breakpoint (#279) ---- */
@media (max-width: 768px) {
.live-feed { width: 280px; max-height: 200px; }
.live-node-detail { width: 260px; }
.live-legend { font-size: 10px; padding: 8px 10px; }
.live-header { gap: 8px; padding: 6px 12px; }
.live-stat-pill { font-size: 11px; padding: 2px 8px; }
.live-toggles { font-size: 10px; gap: 6px; }
}
/* ---- Responsive ---- */
@media (max-width: 640px) {
.live-feed { display: none !important; }
@@ -702,9 +721,8 @@
border: 0;
}
/* Legend toggle button for mobile (#60) */
/* Legend toggle button — visible at all sizes (#60, #279) */
.legend-toggle-btn {
display: none;
position: absolute;
bottom: 82px;
right: 12px;
+13 -4
View File
@@ -762,7 +762,7 @@
<button class="feed-hide-btn" id="nodeDetailClose" title="Close">✕</button>
<div id="nodeDetailContent"></div>
</div>
<button class="legend-toggle-btn hidden" id="legendToggleBtn" aria-label="Show legend" title="Show legend">🎨</button>
<button class="legend-toggle-btn" id="legendToggleBtn" aria-label="Show legend" title="Show legend">🎨</button>
<div class="live-overlay live-legend" id="liveLegend" role="region" aria-label="Map legend">
<h3 class="legend-title">PACKET TYPES</h3>
<ul class="legend-list">
@@ -1043,10 +1043,19 @@
const legendEl = document.getElementById('liveLegend');
const legendToggleBtn = document.getElementById('legendToggleBtn');
if (legendToggleBtn && legendEl) {
// Restore legend collapsed state from localStorage (#279)
try {
if (localStorage.getItem('live-legend-hidden') === 'true') {
legendEl.classList.add('hidden');
legendToggleBtn.setAttribute('aria-label', 'Show legend');
legendToggleBtn.textContent = '🎨';
}
} catch (_) { /* private browsing / storage disabled */ }
legendToggleBtn.addEventListener('click', () => {
const isVisible = legendEl.classList.toggle('legend-mobile-visible');
legendToggleBtn.setAttribute('aria-label', isVisible ? 'Hide legend' : 'Show legend');
legendToggleBtn.textContent = isVisible ? '' : '🎨';
const nowHidden = legendEl.classList.toggle('hidden');
legendToggleBtn.setAttribute('aria-label', nowHidden ? 'Show legend' : 'Hide legend');
legendToggleBtn.textContent = nowHidden ? '🎨' : '';
try { localStorage.setItem('live-legend-hidden', String(nowHidden)); } catch (_) { /* ignore */ }
});
}