diff --git a/public/analytics.js b/public/analytics.js index c420dc6e..6b36b506 100644 --- a/public/analytics.js +++ b/public/analytics.js @@ -566,7 +566,7 @@ - ${ch.channels.map(c => ` + ${ch.channels.map(c => ` diff --git a/public/live.js b/public/live.js index 9f1ad3ae..5853f01a 100644 --- a/public/live.js +++ b/public/live.js @@ -330,6 +330,7 @@ } function updateTimelinePlayhead() { + if (VCR.dragging) return; // don't fight the user's drag const canvas = document.getElementById('vcrTimeline'); if (!canvas) return; const ctx = canvas.getContext('2d'); @@ -574,24 +575,22 @@ timelineEl.addEventListener('mouseleave', () => { timeTooltip.classList.add('hidden'); }); // Drag scrubbing on timeline - let dragging = false; + VCR.dragging = false; function scrubToX(clientX, isFinal) { const rect = timelineEl.getBoundingClientRect(); const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)); const now = Date.now(); const targetTs = now - VCR.timelineScope + pct * VCR.timelineScope; + // Always move playhead visually during drag + const playheadEl = document.getElementById('vcrPlayhead'); + if (playheadEl) { + playheadEl.style.left = (pct * rect.width) + 'px'; + } + // If buffer is empty or target is before buffer start, fetch from DB on release if (VCR.buffer.length === 0 || targetTs < VCR.buffer[0].ts - 5000) { - if (isFinal) { - vcrRewind(now - targetTs); - } else { - // Update playhead position visually during drag even without buffer - const playheadEl = document.getElementById('vcrPlayhead'); - if (playheadEl) { - playheadEl.style.left = (pct * rect.width) + 'px'; - } - } + if (isFinal) vcrRewind(now - targetTs); return; } @@ -605,38 +604,37 @@ stopReplay(); VCR.playhead = closest; vcrSetMode('REPLAY'); - updateTimelinePlayhead(); updateVCRUI(); } timelineEl.addEventListener('mousedown', (e) => { - dragging = true; + VCR.dragging = true; scrubToX(e.clientX, false); e.preventDefault(); }); document.addEventListener('mousemove', (e) => { - if (!dragging) return; + if (!VCR.dragging) return; scrubToX(e.clientX, false); }); document.addEventListener('mouseup', (e) => { - if (!dragging) return; - dragging = false; + if (!VCR.dragging) return; + VCR.dragging = false; scrubToX(e.clientX, true); if (VCR.mode === 'REPLAY') startReplay(); }); // Touch support timelineEl.addEventListener('touchstart', (e) => { - dragging = true; + VCR.dragging = true; scrubToX(e.touches[0].clientX, false); e.preventDefault(); }, { passive: false }); timelineEl.addEventListener('touchmove', (e) => { - if (!dragging) return; + if (!VCR.dragging) return; scrubToX(e.touches[0].clientX, false); }); timelineEl.addEventListener('touchend', (e) => { - if (!dragging) return; + if (!VCR.dragging) return; const lastTouch = e.changedTouches[0]; - dragging = false; + VCR.dragging = false; scrubToX(lastTouch.clientX, true); if (VCR.mode === 'REPLAY') startReplay(); }); diff --git a/public/packets.js b/public/packets.js index e3543c43..4a4402ee 100644 --- a/public/packets.js +++ b/public/packets.js @@ -474,8 +474,10 @@
Timestamp
${pkt.timestamp}
Path
${pathHops.length ? renderPath(pathHops) : '—'}
- ${pathHops.length ? `` : ''} - +
+ ${pathHops.length ? `` : ''} + +
${hasRawHex ? `
${buildHexLegend(ranges)}
${createColoredHexDump(pkt.raw_hex, ranges)}
` : ''} diff --git a/public/style.css b/public/style.css index 7fa5565b..6c73528a 100644 --- a/public/style.css +++ b/public/style.css @@ -1069,15 +1069,17 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); } [data-theme="dark"] .node-qr svg rect[fill="#000000"] { fill: var(--text); } /* Replay on Live Map button in packet detail */ -.replay-live-btn { - display: block; - width: 100%; +.detail-actions { + display: flex; + gap: 8px; margin-top: 12px; - padding: 8px 16px; +} +.replay-live-btn { + padding: 5px 12px; background: rgba(168, 85, 247, 0.15); border: 1px solid rgba(168, 85, 247, 0.3); color: #c084fc; - font-size: 0.85rem; + font-size: 0.78rem; font-weight: 600; border-radius: 6px; cursor: pointer; @@ -1120,16 +1122,15 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); } /* Detail map link */ .detail-map-link { - display: inline-block; - margin: 8px 0; - padding: 6px 12px; + padding: 5px 12px; background: rgba(245, 158, 11, 0.12); border: 1px solid rgba(245, 158, 11, 0.25); color: #fbbf24; border-radius: 6px; - font-size: 0.82rem; + font-size: 0.78rem; font-weight: 600; text-decoration: none; + cursor: pointer; transition: background 0.15s; } .detail-map-link:hover { background: rgba(245, 158, 11, 0.25); }
ChannelHashMessagesUnique SendersLast ActivityDecrypted
${esc(c.name || 'Unknown')} ${c.hash} ${c.messages}