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