feat(gateway-ui): replace legacy CSS with Tailwind base and responsive router

Reduce style.css from 1740 lines to ~120 lines covering only what
Tailwind cannot express (scrollbars, keyframes, stateful toggles).
Rewrite app.js router for responsive nav with desktop sidebar active
states, mobile pill indicators, and dual connection status updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-18 13:05:58 -08:00
parent 9d01441acd
commit 765e19933d
2 changed files with 117 additions and 1702 deletions
+49 -13
View File
@@ -2,6 +2,7 @@
* Flynn SPA Router
*
* Hash-based routing with page lifecycle management.
* Drives both desktop sidebar and mobile pill navigation.
*/
import { getClient } from './lib/ws-client.js';
import { registerPwaServiceWorker } from './lib/pwa.js';
@@ -19,8 +20,7 @@ export function navigate(path) {
}
function getPath() {
const hash = window.location.hash.slice(1) || '/';
return hash;
return window.location.hash.slice(1) || '/';
}
async function render() {
@@ -28,7 +28,7 @@ async function render() {
const page = routes.get(path);
if (!page) {
contentEl.innerHTML = '<div class="page-error"><h2>Page not found</h2></div>';
contentEl.innerHTML = '<div class="p-8 text-center text-zinc-500">Page not found</div>';
return;
}
@@ -37,17 +37,22 @@ async function render() {
currentPage.teardown();
}
// Update nav
// Update desktop sidebar active state
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.toggle('active', link.getAttribute('href') === `#${path}`);
});
// Update mobile pill active state
document.querySelectorAll('.page-pill').forEach(pill => {
pill.classList.toggle('active', pill.dataset.hash === `#${path}`);
});
// Render new page
currentPage = page;
contentEl.innerHTML = '';
const pageEl = document.createElement('div');
pageEl.className = 'page';
pageEl.className = 'page max-w-7xl mx-auto px-4 md:px-0';
contentEl.appendChild(pageEl);
await page.render(pageEl, getClient());
@@ -56,23 +61,54 @@ async function render() {
export function initRouter() {
contentEl = document.getElementById('content');
window.addEventListener('hashchange', render);
// Wire mobile pill click handlers
document.querySelectorAll('.page-pill').forEach(pill => {
pill.addEventListener('click', () => {
window.location.hash = pill.dataset.hash;
});
});
void registerPwaServiceWorker().catch(() => undefined);
render();
}
// Connection status indicator
// Connection status indicator — updates both desktop and mobile dots
export function initStatusIndicator() {
const statusEl = document.getElementById('conn-status');
const connDot = document.getElementById('conn-dot');
const connText = document.getElementById('conn-text');
const connDotMobile = document.getElementById('conn-dot-mobile');
const client = getClient();
const textMap = {
connected: 'Connected',
connecting: 'Connecting...',
disconnected: 'Disconnected',
locked: 'Locked',
};
const colorMap = {
connected: 'bg-green-500',
connecting: 'bg-amber-500',
disconnected: 'bg-red-500',
locked: 'bg-red-500',
};
const baseDotClasses = 'w-2 h-2 rounded-full shrink-0';
function applyStatus(status) {
const text = textMap[status] || 'Disconnected';
const color = colorMap[status] || 'bg-zinc-600';
if (connText) connText.textContent = text;
if (connDot) connDot.className = `${baseDotClasses} ${color}`;
if (connDotMobile) connDotMobile.className = `${baseDotClasses} ${color}`;
}
client.onStatusChange((status) => {
statusEl.textContent = status === 'connected' ? 'Connected' :
status === 'connecting' ? 'Connecting...' : 'Disconnected';
statusEl.className = `conn-status ${status}`;
applyStatus(status);
});
// Set initial status
statusEl.textContent = client.status === 'connected' ? 'Connected' :
client.status === 'connecting' ? 'Connecting...' : 'Disconnected';
statusEl.className = `conn-status ${client.status}`;
applyStatus(client.status);
}
+68 -1689
View File
File diff suppressed because it is too large Load Diff