From 1fb3ff2954aad890dfb5c7fbf984f5e3d9c5da45 Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Mar 2026 19:56:40 +0100 Subject: [PATCH] feat: responsive mobile layout for host rows (closes #2) (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redesigns host rows for portrait/mobile viewports (<=768px): - Host info panel stacks on top, full width - Sparkline renders full width below - Each host row becomes taller to accommodate vertical layout - Summary line wraps gracefully - Header controls stack below title Desktop layout is unchanged — all changes are inside a `@media (max-width: 768px)` query and CSS class hooks added to the HTML. Closes #2 Co-authored-by: user Reviewed-on: https://git.eeqj.de/sneak/netwatch/pulls/5 Co-authored-by: clawbot Co-committed-by: clawbot --- src/main.js | 33 ------------------ src/styles.css | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/main.js b/src/main.js index b99a827..3ab6bb8 100644 --- a/src/main.js +++ b/src/main.js @@ -1128,42 +1128,9 @@ function handleResize(state) { // --- Bootstrap --------------------------------------------------------------- -// --- Mobile Detection -------------------------------------------------------- - -function isMobile() { - // Check both user agent and viewport width for robust detection - const uaMatch = - /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent, - ); - const narrowViewport = window.innerWidth <= 768; - return uaMatch || narrowViewport; -} - -function buildMobileUI() { - const app = document.getElementById("app"); - app.innerHTML = ` -
-
-

NetWatch by @sneak

-

Real-time network latency monitor

-
-
-

Not yet available on mobile.

-

Please visit on a desktop browser for the full experience.

-
-
`; -} - async function init() { log.info("NetWatch starting"); - if (isMobile()) { - log.info("Mobile device detected — showing placeholder"); - buildMobileUI(); - return; - } - // Probe common gateway IPs to find the local router const gateway = await detectGateway(); const localHosts = [LOCAL_CPE]; diff --git a/src/styles.css b/src/styles.css index 24e0fe3..600e453 100644 --- a/src/styles.css +++ b/src/styles.css @@ -21,3 +21,96 @@ body { rgba(255, 255, 255, 0) 100% ); } + +/* ---- Mobile responsive layout (portrait / narrow viewports) ---- */ +@media (max-width: 768px) { + /* Header: stack title and controls vertically */ + header .flex.items-center.justify-between { + flex-direction: column; + align-items: flex-start !important; + gap: 1rem; + } + + header .flex.flex-col.items-end { + align-items: flex-start !important; + flex-direction: row; + flex-wrap: wrap; + gap: 0.75rem; + } + + /* Pause button: smaller on mobile */ + #pause-btn { + padding: 0.5rem 1rem; + } + + #pause-btn svg { + width: 1.25rem; + height: 1.25rem; + } + + #pause-text { + font-size: 0.875rem; + } + + /* Summary box: wrap into a grid for readability */ + #summary { + display: flex; + flex-wrap: wrap; + gap: 0.25rem 0.5rem; + justify-content: center; + line-height: 1.6; + } + + /* Hide the pipe separators on mobile */ + #summary .text-gray-600.mx-3 { + display: none; + } + + /* Host row: stack vertically */ + .host-row .flex.items-center.gap-4 { + flex-direction: column; + align-items: stretch !important; + gap: 0.5rem; + } + + /* Info section: full width, remove fixed width */ + .host-row .w-\[420px\] { + width: 100% !important; + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + } + + /* Host name row with dot */ + .host-row .flex.items-center.gap-2.min-w-\[200px\] { + min-width: 0; + } + + /* Latency value: slightly smaller on mobile */ + .host-row .latency-value { + font-size: 1.875rem; + line-height: 2.25rem; + } + + /* Sparkline: full width below the info */ + .host-row .sparkline-container { + width: 100%; + flex-shrink: 0; + } + + /* Pin button: inline with the host info */ + .host-row .pin-btn { + position: absolute; + right: 0.5rem; + top: 0.5rem; + } + + .host-row { + position: relative; + } + + /* Footer legend: wrap nicely */ + footer p { + line-height: 1.8; + } +}