From 765e19933d3bbdf438eec1f7aac1c9115db4e587 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Wed, 18 Feb 2026 13:05:58 -0800 Subject: [PATCH] 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 --- src/gateway/ui/app.js | 62 +- src/gateway/ui/style.css | 1757 ++------------------------------------ 2 files changed, 117 insertions(+), 1702 deletions(-) diff --git a/src/gateway/ui/app.js b/src/gateway/ui/app.js index 564c57f..b27e2d9 100644 --- a/src/gateway/ui/app.js +++ b/src/gateway/ui/app.js @@ -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 = '

Page not found

'; + contentEl.innerHTML = '
Page not found
'; 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); } diff --git a/src/gateway/ui/style.css b/src/gateway/ui/style.css index 2efe31d..1f376a0 100644 --- a/src/gateway/ui/style.css +++ b/src/gateway/ui/style.css @@ -1,1739 +1,118 @@ /* ========================================================================== - Flynn Gateway — Shared Dark Theme - Terminal-aesthetic dark theme used by chat.html and index.html (dashboard). + Flynn Gateway — Minimal Base Styles + Tailwind handles layout/spacing/color. This file covers only what + Tailwind utility classes cannot express: scrollbars, keyframes, + stateful component classes, and code-block overrides. ========================================================================== */ -/* ---------- CSS Custom Properties (Design Tokens) ---------- */ -:root { - --bg-primary: #0d1117; - --bg-secondary: #161b22; - --bg-tertiary: #1c2128; - --bg-input: #0d1117; - - --text-primary: #c9d1d9; - --text-secondary: #8b949e; - --text-muted: #6e7681; - - --accent: #58a6ff; - --accent-muted: rgba(88, 166, 255, 0.15); - - --error: #f85149; - --error-muted: rgba(248, 81, 73, 0.15); - - --success: #3fb950; - --success-muted: rgba(63, 185, 80, 0.15); - - --warning: #d29922; - - --border: #30363d; - --border-light: #21262d; - - --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace; - --font-size-base: 14px; - --font-size-sm: 12px; - --font-size-lg: 18px; - --font-size-xl: 24px; - - --line-height: 1.55; - --radius: 6px; - --radius-lg: 10px; - --container-max: 900px; - - --transition: 150ms ease; -} - -/* ---------- Reset ---------- */ -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* ---------- Base ---------- */ -html, -body { - height: 100%; - width: 100%; - overflow: hidden; /* Pages handle their own scrolling */ -} - -body { - background-color: var(--bg-primary); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-base); - line-height: var(--line-height); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* ---------- Links ---------- */ -a { - color: var(--accent); - text-decoration: none; - transition: opacity var(--transition); -} - -a:hover { - opacity: 0.8; - text-decoration: underline; -} - -/* ---------- Container ---------- */ -.container { - max-width: var(--container-max); - margin: 0 auto; - padding: 0 16px; -} - -/* ---------- Header ---------- */ -header { - display: flex; - align-items: center; - gap: 16px; - padding: 12px 16px; - border-bottom: 1px solid var(--border); -} - -header h1 { - font-size: var(--font-size-lg); - font-weight: 700; - color: var(--text-primary); - margin-right: auto; -} - -header #status { - font-size: var(--font-size-sm); - color: var(--text-muted); -} - -header #status.connected { - color: var(--success); -} - -header #status.disconnected, -header #status.status-error { - color: var(--error); -} - -header #status.status-ok { - color: var(--success); -} - /* ---------- Scrollbar (Webkit) ---------- */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { - background: var(--bg-primary); + background: rgb(24 24 27); /* zinc-900 */ } ::-webkit-scrollbar-thumb { - background: var(--border); + background: rgb(63 63 70); /* zinc-700 */ border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { - background: var(--text-muted); -} - -/* ========================================================================== - Chat-Specific Styles - ========================================================================== */ - -/* Chat layout — full viewport flex column */ -.chat-container { - display: flex; - flex-direction: column; - height: 100vh; - max-width: var(--container-max); - margin: 0 auto; -} - -/* Scrollable message area */ -.messages { - flex: 1 1 0; - overflow-y: auto; - padding: 16px; - display: flex; - flex-direction: column; - gap: 12px; -} - -/* Message wrapper - contains message box and actions below it */ -.message-wrapper { - display: flex; - flex-direction: column; - gap: 6px; - max-width: 85%; -} - -/* Individual message bubble */ -.message { - padding: 10px 14px; - border-radius: var(--radius); - word-wrap: break-word; - white-space: pre-wrap; - font-size: var(--font-size-base); - line-height: var(--line-height); -} - -/* User messages — right-aligned with accent tint */ -.message.user { - align-self: flex-end; - background-color: var(--accent-muted); - color: var(--text-primary); - border: 1px solid rgba(88, 166, 255, 0.25); -} - -/* Assistant messages — left-aligned with subtle bg */ -.message.assistant { - align-self: flex-start; - background-color: var(--bg-secondary); - color: var(--text-primary); - border: 1px solid var(--border-light); -} - -/* Error messages */ -.message.error { - align-self: flex-start; - background-color: var(--error-muted); - color: var(--text-primary); - border: 1px solid rgba(248, 81, 73, 0.35); -} - -/* Input area — fixed at the bottom of chat */ -.input-area { - display: flex; - flex-direction: row; - gap: 8px; - padding: 12px 16px; - border-top: 1px solid var(--border); - background-color: var(--bg-secondary); -} - -.input-area input { - flex: 1 1 0; - padding: 10px 12px; - background-color: var(--bg-input); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-base); - border: 1px solid var(--border); - border-radius: var(--radius); - outline: none; - transition: border-color var(--transition); -} - -.input-area input::placeholder { - color: var(--text-muted); -} - -.input-area input:focus { - border-color: var(--accent); -} - -.input-area button { - padding: 10px 18px; - background-color: var(--accent); - color: var(--bg-primary); - font-family: var(--font-mono); - font-size: var(--font-size-base); - font-weight: 600; - border: none; - border-radius: var(--radius); - cursor: pointer; - transition: opacity var(--transition); -} - -.input-area button:hover { - opacity: 0.85; -} - -.input-area button:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* ========================================================================== - Dashboard-Specific Styles - ========================================================================== */ - -/* Dashboard layout — grid of cards */ -.dashboard { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - gap: 16px; - padding: 24px 0; -} - -/* Section headings in dashboard page */ -.container > section { - padding: 16px 0; -} - -.container > section > h2 { - font-size: var(--font-size-lg); - font-weight: 600; - color: var(--text-primary); - margin-bottom: 12px; - padding-bottom: 8px; - border-bottom: 1px solid var(--border); -} - -/* Dashboard header — spans full width */ -.dashboard-header { - grid-column: 1 / -1; - padding-bottom: 8px; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; -} - -.dashboard-header h1 { - font-size: var(--font-size-xl); - font-weight: 600; - color: var(--text-primary); -} - -/* Card component */ -.card { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 16px; - transition: border-color var(--transition); -} - -.card:hover { - border-color: var(--text-muted); -} - -.card h2 { - font-size: var(--font-size-base); - font-weight: 600; - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 12px; -} - -.card .value { - font-size: var(--font-size-xl); - font-weight: 700; - color: var(--text-primary); - line-height: 1.2; -} - -.card .label { - font-size: var(--font-size-sm); - color: var(--text-muted); - margin-top: 4px; -} - -/* Status indicators */ -.status-ok { - color: var(--success); -} - -.status-error { - color: var(--error); -} - -.status-warning { - color: var(--warning); -} - -.status-dot { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - margin-right: 6px; -} - -.status-dot.ok { - background-color: var(--success); -} - -.status-dot.error { - background-color: var(--error); -} - -.status-dot.warning { - background-color: var(--warning); -} - -/* Session list */ -.session-list { - list-style: none; -} - -.session-list li { - padding: 8px 0; - border-bottom: 1px solid var(--border-light); - font-size: var(--font-size-sm); - color: var(--text-secondary); -} - -.session-list li:last-child { - border-bottom: none; -} - -.session-list li a { - color: var(--accent); -} - -/* Tool list */ -.tool-list { - list-style: none; -} - -.tool-list li { - padding: 6px 0; - border-bottom: 1px solid var(--border-light); - font-size: var(--font-size-sm); - color: var(--text-secondary); -} - -.tool-list li:last-child { - border-bottom: none; -} - -/* ========================================================================== - Utility Classes - ========================================================================== */ - -/* Tool event display — monospace block for tool_start / tool_end */ -.tool-event { - font-family: var(--font-mono); - font-size: var(--font-size-sm); - background-color: var(--bg-tertiary); - color: var(--text-muted); - border: 1px solid var(--border-light); - border-radius: var(--radius); - padding: 8px 10px; - margin: 4px 0; - white-space: pre-wrap; - word-break: break-all; -} - -/* CSS spinner animation */ -.spinner { - display: inline-block; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin 0.8s linear infinite; + background: rgb(113 113 122); /* zinc-500 */ } +/* ---------- Keyframes ---------- */ @keyframes spin { to { transform: rotate(360deg); } } -/* Hidden utility */ -.hidden { - display: none !important; -} - -/* Text utilities */ -.text-muted { - color: var(--text-muted); -} - -.text-secondary { - color: var(--text-secondary); -} - -.text-accent { - color: var(--accent); -} - -.text-error { - color: var(--error); -} - -.text-success { - color: var(--success); -} - -.text-sm { - font-size: var(--font-size-sm); -} - -.text-lg { - font-size: var(--font-size-lg); -} - -/* Badges */ -.badge { - display: inline-block; - padding: 2px 8px; - font-size: var(--font-size-sm); - border-radius: 12px; - font-weight: 600; -} - -.badge.ok { - background-color: var(--success-muted); - color: var(--success); -} - -.badge.error { - background-color: var(--error-muted); - color: var(--error); -} - -/* ========================================================================== - Responsive — Small Screens (<600px) - ========================================================================== */ -@media (max-width: 600px) { - .container { - padding: 0 8px; - } - - .chat-container { - max-width: 100%; - } - - .messages { - padding: 10px 8px; - } - - .message { - max-width: 95%; - padding: 8px 10px; - } - - .input-area { - padding: 8px; - } - - .input-area input { - padding: 8px 10px; - } - - .input-area button { - padding: 8px 14px; - } - - .dashboard { - grid-template-columns: 1fr; - padding: 16px 8px; - gap: 12px; - } - - .card { - padding: 12px; - } - - .card .value { - font-size: var(--font-size-lg); - } - - .chat-layout { - height: calc(100vh - 32px); - } - - .chat-header { - padding-bottom: 8px; - margin-bottom: 8px; - } - - .btn-action { - font-size: 11px; - padding: 6px 8px; - min-height: 36px; - } - - .slash-popup-item { - padding: 10px 12px; - } - - .slash-popup-item .cmd-name { - min-width: 70px; - font-size: var(--font-size-sm); - } - - .message-actions { - opacity: 1; - } - - .msg-action-btn { - width: 28px; - height: 28px; - } -} - -/* ========================================================================== - SPA Shell Layout - ========================================================================== */ - -.app-shell { - display: flex; - height: 100vh; - overflow: hidden; -} - -/* Sidebar navigation */ -.sidebar { - width: 220px; - min-width: 220px; - background-color: var(--bg-secondary); - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - overflow-y: auto; -} - -.sidebar-header { - padding: 16px; - border-bottom: 1px solid var(--border); -} - -.logo { - font-size: var(--font-size-lg); - font-weight: 700; - color: var(--accent); - letter-spacing: 1px; -} - -.nav-links { - flex: 1; - padding: 8px 0; -} - -.nav-link { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 16px; - color: var(--text-secondary); - text-decoration: none; - font-size: var(--font-size-base); - transition: all var(--transition); - border-left: 3px solid transparent; -} - -.nav-link:hover { - color: var(--text-primary); - background-color: var(--bg-tertiary); - text-decoration: none; - opacity: 1; -} - -.nav-link.active { - color: var(--accent); - background-color: var(--accent-muted); - border-left-color: var(--accent); -} - -.nav-icon { - font-size: var(--font-size-base); - width: 20px; - text-align: center; -} - -.sidebar-footer { - padding: 12px 16px; - border-top: 1px solid var(--border); -} - -.conn-status { - font-size: var(--font-size-sm); - display: flex; - align-items: center; - gap: 6px; -} - -.conn-status::before { - content: ''; - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--text-muted); -} - -.conn-status.connected::before { - background-color: var(--success); -} - -.conn-status.connecting::before { - background-color: var(--warning); -} - -.conn-status.disconnected::before { - background-color: var(--error); -} - -/* Main content area */ -.content { - flex: 1; - overflow-y: auto; - padding: 24px; -} - -.page { - max-width: 1200px; - margin: 0 auto; -} - -/* Page titles */ -.page-title { - font-size: var(--font-size-xl); - font-weight: 600; - color: var(--text-primary); - margin-bottom: 24px; - padding-bottom: 12px; - border-bottom: 1px solid var(--border); -} - -/* ── Dashboard Cards ────────────────────────────────────────── */ - -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 16px; - margin-bottom: 32px; -} - -.stat-card { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: 16px; - transition: border-color var(--transition); -} - -.stat-card:hover { - border-color: var(--text-muted); -} - -.stat-label { - font-size: var(--font-size-sm); - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 8px; -} - -.stat-value { - font-size: var(--font-size-xl); - font-weight: 700; - color: var(--text-primary); -} - -.stat-value.ok { color: var(--success); } -.stat-value.error { color: var(--error); } -.stat-value.warning { color: var(--warning); } - -/* ── Channels Grid ──────────────────────────────────────────── */ - -.channels-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 12px; - margin-bottom: 32px; -} - -.channel-card { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 12px; - display: flex; - align-items: center; - gap: 10px; -} - -.channel-dot { - width: 10px; - height: 10px; - border-radius: 50%; - flex-shrink: 0; -} - -.channel-dot.connected { background-color: var(--success); } -.channel-dot.connecting { background-color: var(--warning); } -.channel-dot.disconnected { background-color: var(--text-muted); } -.channel-dot.error { background-color: var(--error); } - -.channel-name { - font-weight: 600; - color: var(--text-primary); - text-transform: capitalize; -} - -/* ── Services Grid ──────────────────────────────────────────── */ - -.services-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); - gap: 12px; - margin-bottom: 32px; -} - -.service-card { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; - border-left: 3px solid var(--border); -} - -.service-card.service-connected { - border-left-color: var(--success); -} - -.service-card.service-configured { - border-left-color: var(--accent); -} - -.service-card.service-error { - border-left-color: var(--error); -} - -.service-card.service-not-configured { - border-left-color: var(--text-muted); - opacity: 0.6; -} - -.service-type-icon { - font-size: var(--font-size-base); - flex-shrink: 0; -} - -.service-name { - font-weight: 600; - color: var(--text-primary); - text-transform: capitalize; -} - -.service-status { - font-size: var(--font-size-sm); - color: var(--text-muted); - text-transform: uppercase; -} - -.service-card.service-connected .service-status { - color: var(--success); -} - -.service-card.service-configured .service-status { - color: var(--accent); -} - -.service-card.service-error .service-status { - color: var(--error); -} - -.service-card.service-not-configured .service-status { - color: var(--text-muted); - font-style: italic; -} - -.service-description { - font-size: var(--font-size-sm); - color: var(--text-muted); -} - -/* ── Section Headers ────────────────────────────────────────── */ - -.section-title { - font-size: var(--font-size-lg); - font-weight: 600; - color: var(--text-primary); - margin-bottom: 16px; - margin-top: 24px; -} - -/* ── Usage Page Header ─────────────────────────────────────── */ - -.usage-header { - display: flex; - align-items: center; - justify-content: space-between; -} - -.usage-header .page-title { - margin-bottom: 0; - padding-bottom: 0; - border-bottom: none; - flex: 1; -} - -.usage-header .btn { - flex-shrink: 0; -} - -/* ── Data Tables ────────────────────────────────────────────── */ - -table { - width: 100%; - border-collapse: collapse; - font-size: var(--font-size-sm); -} - -th, td { - text-align: left; - padding: 10px 12px; - border-bottom: 1px solid var(--border-light); -} - -th { - color: var(--text-secondary); - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom-color: var(--border); -} - -td { - color: var(--text-primary); -} - -tr:hover td { - background-color: var(--bg-tertiary); -} - -/* ── Chat Page ──────────────────────────────────────────────── */ - -.chat-layout { - display: flex; - flex-direction: column; - height: calc(100vh - 48px); - max-width: var(--container-max); -} - -.chat-header { - display: flex; - align-items: center; - gap: 12px; - padding-bottom: 12px; - border-bottom: 1px solid var(--border); - margin-bottom: 12px; -} - -.chat-header select { - background-color: var(--bg-input); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-sm); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 6px 10px; - outline: none; -} - -.chat-header select:focus { - border-color: var(--accent); -} - -.chat-header button { - padding: 6px 12px; - background-color: var(--bg-tertiary); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-sm); - border: 1px solid var(--border); - border-radius: var(--radius); - cursor: pointer; - transition: all var(--transition); -} - -.chat-header button:hover { - background-color: var(--accent-muted); - border-color: var(--accent); -} - -.chat-messages { - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 12px; - padding: 12px 0; -} - -.chat-input { - display: flex; - gap: 8px; - padding-top: 12px; - border-top: 1px solid var(--border); -} - -.chat-input textarea { - flex: 1; - padding: 10px 12px; - background-color: var(--bg-input); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-base); - border: 1px solid var(--border); - border-radius: var(--radius); - outline: none; - resize: none; - min-height: 42px; - max-height: 150px; - line-height: var(--line-height); - transition: border-color var(--transition); -} - -.chat-input textarea::placeholder { - color: var(--text-muted); -} - -.chat-input textarea:focus { - border-color: var(--accent); -} - -.chat-input button { - padding: 10px 18px; - background-color: var(--accent); - color: var(--bg-primary); - font-family: var(--font-mono); - font-size: var(--font-size-base); - font-weight: 600; - border: none; - border-radius: var(--radius); - cursor: pointer; - transition: opacity var(--transition); - align-self: flex-end; -} - -.chat-input button:hover { - opacity: 0.85; -} - -.chat-input button:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* Chat actions bar (search button, etc.) */ -.chat-actions { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 0; - flex-wrap: wrap; -} - -.chat-attachments { - display: inline-flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.attachment-chip { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 4px 10px; - border-radius: 999px; - background: var(--bg-secondary); - border: 1px solid var(--border); - color: var(--text-secondary); - font-size: var(--font-size-sm); -} - -.attachment-name { - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.attachment-remove { - appearance: none; - border: 0; - background: transparent; - color: var(--text-muted); - cursor: pointer; - font-size: 16px; - line-height: 1; - padding: 0; -} - -.attachment-remove:hover { - color: var(--text-primary); -} - -.btn-action { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 12px; - border-radius: 16px; - background: var(--bg-tertiary); - color: var(--text-secondary); - border: 1px solid var(--border); - font-family: var(--font-mono); - font-size: var(--font-size-sm); - cursor: pointer; - white-space: nowrap; - user-select: none; - transition: all var(--transition); -} - -.btn-action:hover { - color: var(--text-primary); - border-color: var(--text-muted); - background: var(--bg-secondary); -} - -.btn-action.active { - background: var(--accent-muted); - color: var(--accent); - border-color: var(--accent); -} - -.btn-action svg { - width: 14px; - height: 14px; - flex-shrink: 0; - fill: currentColor; -} - -/* Slash command autocomplete popup */ -.chat-input-wrapper { - position: relative; -} - -.slash-popup { - position: absolute; - bottom: 100%; - left: 0; - right: 0; - margin-bottom: 4px; - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - max-height: 240px; - overflow-y: auto; - z-index: 100; - box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3); -} - -.slash-popup-item { - display: flex; - align-items: baseline; - gap: 12px; - padding: 8px 12px; - cursor: pointer; - transition: background var(--transition); -} - -.slash-popup-item:hover, -.slash-popup-item.selected { - background: var(--bg-tertiary); -} - -.slash-popup-item .cmd-name { - color: var(--accent); - font-weight: 600; - font-size: var(--font-size-base); - min-width: 80px; -} - -.slash-popup-item .cmd-desc { - color: var(--text-muted); - font-size: var(--font-size-sm); -} - -/* System messages */ -.message.system { - align-self: stretch; - max-width: 100%; - background: var(--bg-tertiary); - color: var(--text-secondary); - border: 1px solid var(--border-light); - font-size: var(--font-size-sm); -} - -.message.system strong { - color: var(--text-primary); -} - -.message.system code { - color: var(--accent); - background: var(--bg-secondary); - padding: 1px 4px; - border-radius: 3px; -} - -/* Message wrapper alignment */ -.message-wrapper.user { - align-self: flex-end; -} - -.message-wrapper.assistant { - align-self: flex-start; -} - -/* Message action buttons (copy, edit) */ -.message-actions { - display: flex; - gap: 4px; - opacity: 0; - transition: opacity var(--transition); - align-self: flex-end; -} - -.message-wrapper:hover .message-actions { - opacity: 1; -} - -/* User message actions align right */ -.message-wrapper.user .message-actions { - align-self: flex-end; -} - -/* Assistant message actions align left */ -.message-wrapper.assistant .message-actions { - align-self: flex-start; -} - -.msg-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - padding: 0; - border: none; - border-radius: 4px; - background: transparent; - color: var(--text-muted); - cursor: pointer; - transition: all var(--transition); -} - -.msg-action-btn:hover { - background: var(--bg-tertiary); - color: var(--text-primary); -} - -.msg-action-btn.copied { - color: var(--success); -} - -.msg-action-btn svg { - width: 14px; - height: 14px; - flex-shrink: 0; - fill: currentColor; -} - -/* Streaming text cursor */ -.streaming-cursor::after { - content: '|'; - animation: blink 1s step-end infinite; - color: var(--accent); -} - @keyframes blink { 50% { opacity: 0; } } -/* Tool event in chat (collapsible) */ -.tool-event-group { - border: 1px solid var(--border-light); - border-radius: var(--radius); - margin: 4px 0; - overflow: hidden; +/* ---------- Spinner ---------- */ +.spinner { + display: inline-block; + width: 18px; + height: 18px; + border: 2px solid rgb(63 63 70); /* zinc-700 */ + border-top-color: rgb(59 130 246); /* blue-500 */ + border-radius: 50%; + animation: spin 0.8s linear infinite; } -.tool-event-header { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - background-color: var(--bg-tertiary); - cursor: pointer; - font-size: var(--font-size-sm); - color: var(--text-muted); - user-select: none; -} - -.tool-event-header:hover { - color: var(--text-secondary); +/* ---------- Streaming cursor ---------- */ +.streaming-cursor::after { + content: '|'; + animation: blink 1s step-end infinite; + color: rgb(59 130 246); /* blue-500 */ } +/* ---------- Tool event collapse ---------- */ .tool-event-body { - padding: 8px 10px; - font-size: var(--font-size-sm); - color: var(--text-secondary); - background-color: var(--bg-secondary); display: none; - white-space: pre-wrap; - word-break: break-all; - max-height: 200px; - overflow-y: auto; } .tool-event-body.open { display: block; } -/* Code blocks in assistant messages */ -.message.assistant pre { - background-color: var(--bg-tertiary); - border: 1px solid var(--border-light); - border-radius: var(--radius); +/* ---------- Sidebar nav active state ---------- */ +.nav-link.active { + color: rgb(59 130 246); /* blue-500 */ + background: rgba(59 130 246, 0.1); + border-left-color: rgb(59 130 246); +} + +/* ---------- Mobile pill active state ---------- */ +.page-pill.active { + background: rgb(59 130 246); /* blue-500 */ + color: white; +} + +/* ---------- Hidden utility ---------- */ +.hidden { + display: none !important; +} + +/* ---------- Links ---------- */ +a { + color: rgb(59 130 246); /* blue-500 */ + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +/* ---------- Chat action button active state ---------- */ +.btn-action.active { + background: rgba(59 130 246 / 0.15); /* blue-500/15 */ + color: rgb(59 130 246); /* blue-500 */ + border-color: rgba(59 130 246 / 0.3); /* blue-500/30 */ +} + +/* ---------- Code blocks in assistant messages ---------- */ +[data-role="assistant"] pre { + background-color: rgb(24 24 27); /* zinc-900 */ + border: 1px solid rgb(39 39 42); /* zinc-800 */ + border-radius: 6px; padding: 12px; overflow-x: auto; margin: 8px 0; } -.message.assistant code { - font-family: var(--font-mono); - font-size: var(--font-size-sm); +[data-role="assistant"] code { + font-family: 'JetBrains Mono', ui-monospace, monospace; + font-size: 0.85em; } -.message.assistant p code { - background-color: var(--bg-tertiary); +[data-role="assistant"] p code { + background-color: rgb(24 24 27); /* zinc-900 */ padding: 2px 6px; border-radius: 3px; } - -/* ── Settings Page ──────────────────────────────────────────── */ - -.settings-section { - margin-bottom: 32px; -} - -.config-view { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 16px; - white-space: pre-wrap; - font-size: var(--font-size-sm); - color: var(--text-secondary); - max-height: 400px; - overflow-y: auto; -} - -.hook-editor { - display: flex; - flex-direction: column; - gap: 12px; -} - -.hook-group { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 12px; -} - -.hook-group label { - display: block; - font-size: var(--font-size-sm); - color: var(--text-secondary); - margin-bottom: 6px; - font-weight: 600; - text-transform: uppercase; -} - -.hook-group textarea { - width: 100%; - padding: 8px; - background-color: var(--bg-input); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--font-size-sm); - border: 1px solid var(--border); - border-radius: var(--radius); - outline: none; - resize: vertical; - min-height: 60px; -} - -.hook-group textarea:focus { - border-color: var(--accent); -} - -.assistant-mode-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 10px; - margin-bottom: 12px; -} - -.assistant-toggle, -.assistant-field { - display: flex; - align-items: center; - gap: 8px; - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 10px 12px; - color: var(--text-secondary); - font-size: var(--font-size-sm); -} - -.assistant-field { - flex-direction: column; - align-items: flex-start; -} - -.assistant-field input[type="number"], -.assistant-field input[type="text"] { - width: 100%; - padding: 8px; - background-color: var(--bg-input); - color: var(--text-primary); - border: 1px solid var(--border); - border-radius: var(--radius); - outline: none; -} - -.assistant-field input:focus { - border-color: var(--accent); -} - -.assistant-actions { - display: flex; - align-items: center; - gap: 12px; -} - -.btn { - padding: 8px 16px; - font-family: var(--font-mono); - font-size: var(--font-size-sm); - font-weight: 600; - border: 1px solid var(--border); - border-radius: var(--radius); - cursor: pointer; - transition: all var(--transition); -} - -.btn-primary { - background-color: var(--accent); - color: var(--bg-primary); - border-color: var(--accent); -} - -.btn-primary:hover { - opacity: 0.85; -} - -.btn-danger { - background-color: var(--error-muted); - color: var(--error); - border-color: rgba(248, 81, 73, 0.35); -} - -.btn-danger:hover { - background-color: var(--error); - color: white; -} - -.btn-secondary { - background-color: var(--bg-tertiary); - color: var(--text-primary); -} - -.btn-secondary:hover { - background-color: var(--accent-muted); - border-color: var(--accent); -} - -/* ── Sessions Page ──────────────────────────────────────────── */ - -.session-actions { - display: flex; - gap: 6px; -} - -.session-actions button { - padding: 4px 10px; - font-size: var(--font-size-sm); -} - -.session-detail { - margin-top: 24px; -} - -.session-detail-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 16px; -} - -.message-history { - display: flex; - flex-direction: column; - gap: 12px; - max-height: 60vh; - overflow-y: auto; - padding: 12px; - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); -} - -/* ── Empty States ───────────────────────────────────────────── */ - -.empty-state { - text-align: center; - padding: 48px 24px; - color: var(--text-muted); - font-size: var(--font-size-base); -} - -/* ── Event Stream ──────────────────────────────────────── */ -.event-stream { - max-height: 300px; - overflow-y: auto; - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 8px; - font-size: var(--font-size-sm); - font-family: var(--font-mono); -} - -.event-row { - padding: 4px 8px; - border-bottom: 1px solid var(--border-light); - white-space: pre-wrap; - word-break: break-word; -} - -.event-row:last-child { - border-bottom: none; -} - -.event-level-error { color: var(--error); } -.event-level-warn { color: var(--warning); } -.event-level-info { color: var(--text-secondary); } - -/* ── Model Metrics Summary ─────────────────────────────── */ -.metrics-summary { - display: flex; - gap: 24px; - margin-bottom: 12px; - font-size: var(--font-size-sm); - color: var(--text-secondary); -} - -.metrics-summary .metric { - display: flex; - gap: 6px; -} - -.metrics-summary .metric-value { - font-weight: 600; - color: var(--text-primary); -} - -.assistant-health-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); - gap: 8px; - margin-bottom: 12px; -} - -.assistant-chip { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 12px; - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - font-size: var(--font-size-sm); -} - -.assistant-chip-label { - color: var(--text-secondary); -} - -.assistant-chip-value { - font-weight: 700; - color: var(--text-primary); -} - -.assistant-preview { - margin-top: 12px; - padding: 12px; - border: 1px solid var(--border); - border-radius: var(--radius); - background-color: var(--bg-secondary); -} - -.assistant-playbooks { - margin-top: 12px; - padding: 12px; - border: 1px solid var(--border); - border-radius: var(--radius); - background-color: var(--bg-secondary); -} - -.assistant-playbook-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 8px; - margin: 10px 0; -} - -.assistant-setup { - margin-top: 12px; - padding: 12px; - border: 1px solid var(--border); - border-radius: var(--radius); - background-color: var(--bg-secondary); -} - -.assistant-checklist { - list-style: none; - margin: 10px 0; - padding: 0; - display: grid; - gap: 6px; -} - -.assistant-checklist li { - display: flex; - align-items: center; - gap: 8px; - color: var(--text-secondary); - font-size: var(--font-size-sm); -} - -.assistant-checklist li.done { - color: var(--text-primary); -} - -.assistant-check { - width: 18px; - text-align: center; - font-weight: 700; -} - -.assistant-setup-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 8px; -} - -.assistant-preview-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.assistant-preview-title { - font-size: var(--font-size-sm); - font-weight: 700; - color: var(--text-primary); -} - -.assistant-preview-meta { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 10px; -} - -.assistant-preview-body { - max-height: 180px; - overflow-y: auto; - border: 1px solid var(--border-light); - border-radius: var(--radius); - padding: 10px; - background-color: var(--bg-tertiary); - white-space: pre-wrap; - word-break: break-word; - font-size: var(--font-size-sm); -} - -/* ── Responsive: Mobile ─────────────────────────────────────── */ - -@media (max-width: 768px) { - .sidebar { - width: 60px; - min-width: 60px; - } - - .sidebar-header { - padding: 12px 8px; - text-align: center; - } - - .logo { - font-size: var(--font-size-base); - } - - .nav-link { - padding: 12px; - justify-content: center; - } - - .nav-link span:not(.nav-icon) { - display: none; - } - - .nav-icon { - margin: 0; - } - - .sidebar-footer { - padding: 8px; - text-align: center; - } - - .conn-status { - font-size: 0; - } - - .content { - padding: 16px; - } - - .stats-grid { - grid-template-columns: repeat(2, 1fr); - } - - .chat-header { - flex-wrap: wrap; - gap: 8px; - } - - .chat-header select { - min-width: 0; - flex: 1; - } - - .chat-actions { - padding: 6px 0; - } - - .btn-action { - padding: 8px 10px; - min-height: 36px; - } - - .slash-popup { - max-height: 200px; - } -}