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; - } -}