ScuttleBot
ui: add scroll indicators to mobile nav Show fade-gradient arrows at the edges of the nav tab bar on mobile to indicate scrollable content. Indicators appear/hide based on scroll position — left arrow when scrolled right, right arrow when more tabs are off-screen.
Commit
c0251636b0ee88b5bc37b08232275acb2fc8d7ff4fc9d1471376333705347449
Parent
3456dc42b864a49…
1 file changed
+25
-2
+25
-2
| --- internal/api/ui/index.html | ||
| +++ internal/api/ui/index.html | ||
| @@ -199,15 +199,21 @@ | ||
| 199 | 199 | @media (max-width: 600px) { |
| 200 | 200 | /* header: shorter, compact brand, scrollable nav */ |
| 201 | 201 | header { padding:0 8px; height:44px; } |
| 202 | 202 | .brand { padding-right:8px; margin-right:0; border-right:none; } |
| 203 | 203 | .brand span { display:none; } |
| 204 | - nav { overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; } | |
| 204 | + nav { overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; position:relative; } | |
| 205 | 205 | nav::-webkit-scrollbar { display:none; } |
| 206 | 206 | .nav-tab { padding:0 10px; font-size:12px; } |
| 207 | 207 | .header-right { display:none; } |
| 208 | 208 | |
| 209 | + /* scroll indicators */ | |
| 210 | + .nav-scroll-left, .nav-scroll-right { display:none; position:absolute; top:0; bottom:0; width:28px; pointer-events:none; z-index:2; align-items:center; justify-content:center; font-size:11px; color:#58a6ff; } | |
| 211 | + .nav-scroll-left { left:0; background:linear-gradient(90deg,#161b22 40%,transparent); } | |
| 212 | + .nav-scroll-right { right:0; background:linear-gradient(270deg,#161b22 40%,transparent); } | |
| 213 | + .nav-scroll-left.visible, .nav-scroll-right.visible { display:flex; } | |
| 214 | + | |
| 209 | 215 | /* content: reduce padding, stack grids */ |
| 210 | 216 | .pane-inner { padding:12px; gap:12px; } |
| 211 | 217 | .stat-grid { grid-template-columns:1fr !important; } |
| 212 | 218 | .card-body { padding:12px !important; } |
| 213 | 219 | |
| @@ -296,18 +302,20 @@ | ||
| 296 | 302 | <header> |
| 297 | 303 | <div class="brand"> |
| 298 | 304 | <h1>⬡ scuttlebot</h1> |
| 299 | 305 | <span>agent coordination backplane</span> |
| 300 | 306 | </div> |
| 301 | - <nav> | |
| 307 | + <nav id="main-nav"> | |
| 308 | + <span class="nav-scroll-left" id="nav-scroll-left">‹</span> | |
| 302 | 309 | <div class="nav-tab active" id="tab-status" onclick="switchTab('status')">◈ status</div> |
| 303 | 310 | <div class="nav-tab" id="tab-users" onclick="switchTab('users')">◉ users</div> |
| 304 | 311 | <div class="nav-tab" id="tab-agents" onclick="switchTab('agents')">◎ agents</div> |
| 305 | 312 | <div class="nav-tab" id="tab-channels" onclick="switchTab('channels')">◎ channels</div> |
| 306 | 313 | <div class="nav-tab" id="tab-chat" onclick="switchTab('chat')">◌ chat</div> |
| 307 | 314 | <div class="nav-tab" id="tab-ai" onclick="switchTab('ai')">✦ ai</div> |
| 308 | 315 | <div class="nav-tab" id="tab-settings" onclick="switchTab('settings')">⚙ settings</div> |
| 316 | + <span class="nav-scroll-right visible" id="nav-scroll-right">›</span> | |
| 309 | 317 | </nav> |
| 310 | 318 | <div class="header-right"> |
| 311 | 319 | <span id="header-user-display" style="font-size:12px;color:#8b949e"></span> |
| 312 | 320 | <button class="sm" onclick="logout()">sign out</button> |
| 313 | 321 | </div> |
| @@ -2946,12 +2954,27 @@ | ||
| 2946 | 2954 | email: document.getElementById('tls-email').value.trim() || undefined, |
| 2947 | 2955 | allow_insecure: document.getElementById('tls-allow-insecure').checked, |
| 2948 | 2956 | } |
| 2949 | 2957 | }, 'tls-config-save-result'); |
| 2950 | 2958 | } |
| 2959 | + | |
| 2960 | +// --- nav scroll indicators --- | |
| 2961 | +(function() { | |
| 2962 | + const nav = document.getElementById('main-nav'); | |
| 2963 | + const left = document.getElementById('nav-scroll-left'); | |
| 2964 | + const right = document.getElementById('nav-scroll-right'); | |
| 2965 | + if (!nav || !left || !right) return; | |
| 2966 | + function update() { | |
| 2967 | + left.classList.toggle('visible', nav.scrollLeft > 4); | |
| 2968 | + right.classList.toggle('visible', nav.scrollLeft + nav.clientWidth < nav.scrollWidth - 4); | |
| 2969 | + } | |
| 2970 | + nav.addEventListener('scroll', update, { passive: true }); | |
| 2971 | + window.addEventListener('resize', update); | |
| 2972 | + update(); | |
| 2973 | +})(); | |
| 2951 | 2974 | |
| 2952 | 2975 | // --- init --- |
| 2953 | 2976 | function loadAll() { loadStatus(); loadAgents(); loadSettings(); startMetricsPoll(); } |
| 2954 | 2977 | initAuth(); |
| 2955 | 2978 | </script> |
| 2956 | 2979 | </body> |
| 2957 | 2980 | </html> |
| 2958 | 2981 |
| --- internal/api/ui/index.html | |
| +++ internal/api/ui/index.html | |
| @@ -199,15 +199,21 @@ | |
| 199 | @media (max-width: 600px) { |
| 200 | /* header: shorter, compact brand, scrollable nav */ |
| 201 | header { padding:0 8px; height:44px; } |
| 202 | .brand { padding-right:8px; margin-right:0; border-right:none; } |
| 203 | .brand span { display:none; } |
| 204 | nav { overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; } |
| 205 | nav::-webkit-scrollbar { display:none; } |
| 206 | .nav-tab { padding:0 10px; font-size:12px; } |
| 207 | .header-right { display:none; } |
| 208 | |
| 209 | /* content: reduce padding, stack grids */ |
| 210 | .pane-inner { padding:12px; gap:12px; } |
| 211 | .stat-grid { grid-template-columns:1fr !important; } |
| 212 | .card-body { padding:12px !important; } |
| 213 | |
| @@ -296,18 +302,20 @@ | |
| 296 | <header> |
| 297 | <div class="brand"> |
| 298 | <h1>⬡ scuttlebot</h1> |
| 299 | <span>agent coordination backplane</span> |
| 300 | </div> |
| 301 | <nav> |
| 302 | <div class="nav-tab active" id="tab-status" onclick="switchTab('status')">◈ status</div> |
| 303 | <div class="nav-tab" id="tab-users" onclick="switchTab('users')">◉ users</div> |
| 304 | <div class="nav-tab" id="tab-agents" onclick="switchTab('agents')">◎ agents</div> |
| 305 | <div class="nav-tab" id="tab-channels" onclick="switchTab('channels')">◎ channels</div> |
| 306 | <div class="nav-tab" id="tab-chat" onclick="switchTab('chat')">◌ chat</div> |
| 307 | <div class="nav-tab" id="tab-ai" onclick="switchTab('ai')">✦ ai</div> |
| 308 | <div class="nav-tab" id="tab-settings" onclick="switchTab('settings')">⚙ settings</div> |
| 309 | </nav> |
| 310 | <div class="header-right"> |
| 311 | <span id="header-user-display" style="font-size:12px;color:#8b949e"></span> |
| 312 | <button class="sm" onclick="logout()">sign out</button> |
| 313 | </div> |
| @@ -2946,12 +2954,27 @@ | |
| 2946 | email: document.getElementById('tls-email').value.trim() || undefined, |
| 2947 | allow_insecure: document.getElementById('tls-allow-insecure').checked, |
| 2948 | } |
| 2949 | }, 'tls-config-save-result'); |
| 2950 | } |
| 2951 | |
| 2952 | // --- init --- |
| 2953 | function loadAll() { loadStatus(); loadAgents(); loadSettings(); startMetricsPoll(); } |
| 2954 | initAuth(); |
| 2955 | </script> |
| 2956 | </body> |
| 2957 | </html> |
| 2958 |
| --- internal/api/ui/index.html | |
| +++ internal/api/ui/index.html | |
| @@ -199,15 +199,21 @@ | |
| 199 | @media (max-width: 600px) { |
| 200 | /* header: shorter, compact brand, scrollable nav */ |
| 201 | header { padding:0 8px; height:44px; } |
| 202 | .brand { padding-right:8px; margin-right:0; border-right:none; } |
| 203 | .brand span { display:none; } |
| 204 | nav { overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; position:relative; } |
| 205 | nav::-webkit-scrollbar { display:none; } |
| 206 | .nav-tab { padding:0 10px; font-size:12px; } |
| 207 | .header-right { display:none; } |
| 208 | |
| 209 | /* scroll indicators */ |
| 210 | .nav-scroll-left, .nav-scroll-right { display:none; position:absolute; top:0; bottom:0; width:28px; pointer-events:none; z-index:2; align-items:center; justify-content:center; font-size:11px; color:#58a6ff; } |
| 211 | .nav-scroll-left { left:0; background:linear-gradient(90deg,#161b22 40%,transparent); } |
| 212 | .nav-scroll-right { right:0; background:linear-gradient(270deg,#161b22 40%,transparent); } |
| 213 | .nav-scroll-left.visible, .nav-scroll-right.visible { display:flex; } |
| 214 | |
| 215 | /* content: reduce padding, stack grids */ |
| 216 | .pane-inner { padding:12px; gap:12px; } |
| 217 | .stat-grid { grid-template-columns:1fr !important; } |
| 218 | .card-body { padding:12px !important; } |
| 219 | |
| @@ -296,18 +302,20 @@ | |
| 302 | <header> |
| 303 | <div class="brand"> |
| 304 | <h1>⬡ scuttlebot</h1> |
| 305 | <span>agent coordination backplane</span> |
| 306 | </div> |
| 307 | <nav id="main-nav"> |
| 308 | <span class="nav-scroll-left" id="nav-scroll-left">‹</span> |
| 309 | <div class="nav-tab active" id="tab-status" onclick="switchTab('status')">◈ status</div> |
| 310 | <div class="nav-tab" id="tab-users" onclick="switchTab('users')">◉ users</div> |
| 311 | <div class="nav-tab" id="tab-agents" onclick="switchTab('agents')">◎ agents</div> |
| 312 | <div class="nav-tab" id="tab-channels" onclick="switchTab('channels')">◎ channels</div> |
| 313 | <div class="nav-tab" id="tab-chat" onclick="switchTab('chat')">◌ chat</div> |
| 314 | <div class="nav-tab" id="tab-ai" onclick="switchTab('ai')">✦ ai</div> |
| 315 | <div class="nav-tab" id="tab-settings" onclick="switchTab('settings')">⚙ settings</div> |
| 316 | <span class="nav-scroll-right visible" id="nav-scroll-right">›</span> |
| 317 | </nav> |
| 318 | <div class="header-right"> |
| 319 | <span id="header-user-display" style="font-size:12px;color:#8b949e"></span> |
| 320 | <button class="sm" onclick="logout()">sign out</button> |
| 321 | </div> |
| @@ -2946,12 +2954,27 @@ | |
| 2954 | email: document.getElementById('tls-email').value.trim() || undefined, |
| 2955 | allow_insecure: document.getElementById('tls-allow-insecure').checked, |
| 2956 | } |
| 2957 | }, 'tls-config-save-result'); |
| 2958 | } |
| 2959 | |
| 2960 | // --- nav scroll indicators --- |
| 2961 | (function() { |
| 2962 | const nav = document.getElementById('main-nav'); |
| 2963 | const left = document.getElementById('nav-scroll-left'); |
| 2964 | const right = document.getElementById('nav-scroll-right'); |
| 2965 | if (!nav || !left || !right) return; |
| 2966 | function update() { |
| 2967 | left.classList.toggle('visible', nav.scrollLeft > 4); |
| 2968 | right.classList.toggle('visible', nav.scrollLeft + nav.clientWidth < nav.scrollWidth - 4); |
| 2969 | } |
| 2970 | nav.addEventListener('scroll', update, { passive: true }); |
| 2971 | window.addEventListener('resize', update); |
| 2972 | update(); |
| 2973 | })(); |
| 2974 | |
| 2975 | // --- init --- |
| 2976 | function loadAll() { loadStatus(); loadAgents(); loadSettings(); startMetricsPoll(); } |
| 2977 | initAuth(); |
| 2978 | </script> |
| 2979 | </body> |
| 2980 | </html> |
| 2981 |