From 0dc2dd3f25878444c9ebdb58369f62d6a4ed5bea Mon Sep 17 00:00:00 2001 From: you Date: Fri, 20 Mar 2026 01:14:32 +0000 Subject: [PATCH 1/2] mobile packets: bottom sheet detail, collapsed filters, smaller table, fewer columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mobile (≤640px) default columns: time, hash, type, details (hide region, observer, path, rpt, size) - Detail panel hidden on mobile; tapping row opens slide-up bottom sheet (70vh max, close button, drag handle) - Filter bar collapses to single 'Filters' toggle button on mobile - Table font 11px, tighter padding, no min-width forcing horizontal scroll - Panel-right completely hidden on mobile (no split layout) --- public/index.html | 4 ++-- public/packets.js | 46 +++++++++++++++++++++++++++++++++------ public/style.css | 55 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/public/index.html b/public/index.html index 665cee1f..7222ba7d 100644 --- a/public/index.html +++ b/public/index.html @@ -20,7 +20,7 @@ - + - + diff --git a/public/packets.js b/public/packets.js index 46e48676..214127d5 100644 --- a/public/packets.js +++ b/public/packets.js @@ -278,6 +278,7 @@
+
@@ -318,6 +319,13 @@ typeSel.innerHTML += ``; } + // Filter toggle button for mobile + document.getElementById('filterToggleBtn').addEventListener('click', function() { + const bar = document.getElementById('pktFilters'); + bar.classList.toggle('filters-expanded'); + this.textContent = bar.classList.contains('filters-expanded') ? 'Filters ▴' : 'Filters ▾'; + }); + // Filter event listeners document.getElementById('fHash').value = filters.hash || ''; document.getElementById('fHash').addEventListener('input', debounce((e) => { filters.hash = e.target.value || undefined; loadPackets(); }, 300)); @@ -343,7 +351,8 @@ { key: 'rpt', label: 'Rpt' }, { key: 'details', label: 'Details' }, ]; - const defaultHidden = ['region']; + const isMobile = window.innerWidth <= 640; + const defaultHidden = isMobile ? ['region', 'observer', 'path', 'rpt', 'size'] : ['region']; let visibleCols; try { visibleCols = JSON.parse(localStorage.getItem('packets-visible-cols')); @@ -633,10 +642,33 @@ async function selectPacket(id) { selectedId = id; renderTableRows(); - const panel = document.getElementById('pktRight'); - panel.classList.remove('empty'); - panel.innerHTML = '
Loading…
'; - initPanelResize(); + const isMobileNow = window.innerWidth <= 640; + let panel; + if (isMobileNow) { + // Use mobile bottom sheet + let sheet = document.getElementById('mobileDetailSheet'); + if (!sheet) { + sheet = document.createElement('div'); + sheet.id = 'mobileDetailSheet'; + sheet.className = 'mobile-detail-sheet'; + sheet.innerHTML = '
'; + document.body.appendChild(sheet); + sheet.querySelector('#mobileSheetClose').addEventListener('click', () => { + sheet.classList.remove('open'); + }); + sheet.querySelector('.mobile-sheet-handle').addEventListener('click', () => { + sheet.classList.remove('open'); + }); + } + panel = sheet.querySelector('.mobile-sheet-content'); + panel.innerHTML = '
Loading…
'; + sheet.classList.add('open'); + } else { + panel = document.getElementById('pktRight'); + panel.classList.remove('empty'); + panel.innerHTML = '
Loading…
'; + initPanelResize(); + } try { const data = await api(`/packets/${id}`); @@ -647,11 +679,11 @@ const newHops = hops.filter(h => !(h in hopNameCache)); if (newHops.length) await resolveHops(newHops); } catch {} - panel.innerHTML = '
'; + panel.innerHTML = isMobileNow ? '' : '
'; const content = document.createElement('div'); panel.appendChild(content); renderDetail(content, data); - initPanelResize(); + if (!isMobileNow) initPanelResize(); } catch (e) { panel.innerHTML = `
Error: ${e.message}
`; } diff --git a/public/style.css b/public/style.css index 57618b14..5f59c4b2 100644 --- a/public/style.css +++ b/public/style.css @@ -811,8 +811,8 @@ button.ch-item.selected { background: var(--selected-bg); } /* Layouts: stack instead of side-by-side */ .split-layout { flex-direction: column; overflow-y: auto; } - .panel-left { padding: 10px; flex: none; min-height: 50vh; overflow-x: auto; } - .panel-right { width: 100%; min-width: 0; border-left: none; border-top: 1px solid var(--border); max-height: none; flex: none; } + .panel-left { padding: 6px; flex: 1; min-height: 0; overflow-x: auto; -webkit-overflow-scrolling: touch; } + .panel-right { display: none; } /* Channels: Discord-style full screen toggle */ .ch-layout { flex-direction: row; position: relative; } @@ -830,18 +830,21 @@ button.ch-item.selected { background: var(--selected-bg); } .ch-back-btn { display: flex; } .ch-main-header { display: flex; align-items: center; gap: 8px; } - /* Tables: smaller text, allow horizontal scroll */ - .data-table { font-size: 12px; } - .data-table td { padding: 6px 6px; max-width: 120px; } - .data-table th { padding: 6px 6px; font-size: 11px; } - .panel-left { overflow-x: auto; -webkit-overflow-scrolling: touch; } - .data-table { min-width: 500px; } + /* Tables: smaller text for mobile */ + .data-table { font-size: 11px; min-width: 0; } + .data-table td { padding: 5px 4px; max-width: 100px; } + .data-table th { padding: 5px 4px; font-size: 10px; } + .panel-left { overflow-x: auto; } - /* Filters: full width */ - .filter-bar { flex-direction: column; gap: 4px; } - .filter-bar input { width: 100%; } - .filter-bar select { width: 100%; } - .filter-bar .btn { min-height: 44px; } + /* Filters: collapse on mobile */ + .filter-bar { flex-direction: row; flex-wrap: wrap; gap: 4px; } + .filter-toggle-btn { display: inline-flex !important; } + .filter-bar > *:not(.filter-toggle-btn):not(.col-toggle-wrap) { display: none; } + .filter-bar.filters-expanded > * { display: inline-flex; } + .filter-bar.filters-expanded > .col-toggle-wrap { display: inline-block; } + .filter-bar.filters-expanded input { width: 100%; } + .filter-bar.filters-expanded select { width: 100%; } + .filter-bar .btn { min-height: 36px; } .node-filter-wrap { width: 100%; } /* Nodes */ @@ -1384,3 +1387,29 @@ tr[data-hops]:hover { background: rgba(59,130,246,0.1); } .claimed-row { background: color-mix(in srgb, var(--accent) 8%, transparent) !important; border-left: 3px solid var(--accent); } .claimed-row:hover { background: color-mix(in srgb, var(--accent) 14%, transparent) !important; } .claimed-badge { color: var(--accent); font-size: 13px; margin-right: 2px; } + +/* Filter toggle button — hidden on desktop */ +.filter-toggle-btn { display: none; } + +/* Mobile detail bottom sheet */ +.mobile-detail-sheet { + display: none; + position: fixed; bottom: 0; left: 0; right: 0; + max-height: 70vh; background: var(--detail-bg); + border-top-left-radius: 16px; border-top-right-radius: 16px; + box-shadow: 0 -4px 24px rgba(0,0,0,.3); + z-index: 200; overflow-y: auto; padding: 8px 16px 24px; + transform: translateY(100%); transition: transform .25s ease; +} +.mobile-detail-sheet.open { display: block; transform: translateY(0); } +.mobile-sheet-handle { + width: 40px; height: 4px; background: var(--border); + border-radius: 2px; margin: 4px auto 8px; cursor: pointer; +} +.mobile-sheet-close { + position: absolute; top: 8px; right: 12px; + background: none; border: none; font-size: 20px; + color: var(--text-muted); cursor: pointer; z-index: 1; +} +.mobile-sheet-close:hover { color: var(--text); } +.mobile-sheet-content { padding-top: 4px; } From 36598a3623c22ffa22c0f551e00c1dd7fdd7bc16 Mon Sep 17 00:00:00 2001 From: you Date: Fri, 20 Mar 2026 01:15:49 +0000 Subject: [PATCH 2/2] mobile: hide hash column by default --- public/index.html | 2 +- public/packets.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index 7222ba7d..d8a59fa8 100644 --- a/public/index.html +++ b/public/index.html @@ -78,7 +78,7 @@ - + diff --git a/public/packets.js b/public/packets.js index 214127d5..b5a68e91 100644 --- a/public/packets.js +++ b/public/packets.js @@ -352,7 +352,7 @@ { key: 'details', label: 'Details' }, ]; const isMobile = window.innerWidth <= 640; - const defaultHidden = isMobile ? ['region', 'observer', 'path', 'rpt', 'size'] : ['region']; + const defaultHidden = isMobile ? ['region', 'hash', 'observer', 'path', 'rpt', 'size'] : ['region']; let visibleCols; try { visibleCols = JSON.parse(localStorage.getItem('packets-visible-cols'));