From e42477b810754daed91c671fea786c1ee7c4d464 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Sat, 4 Apr 2026 23:56:07 -0700 Subject: [PATCH] feat: collapsible panels + medium breakpoint on live map (#606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 --- public/live.css | 22 ++++++++++++++++++++-- public/live.js | 17 +++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/public/live.css b/public/live.css index 8d0d4dbd..d0d5d42a 100644 --- a/public/live.css +++ b/public/live.css @@ -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; diff --git a/public/live.js b/public/live.js index 1b8fa2ab..df6e027a 100644 --- a/public/live.js +++ b/public/live.js @@ -762,7 +762,7 @@
- +

PACKET TYPES

    @@ -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 */ } }); }