<!DOCTYPE html>
<!--
  Familiar - Self-hosted AI Agent
  Copyright (c) 2026 George Scott Foley
  
  Licensed under the MIT License
  For commercial licensing, contact: licensing@familiar.ai
-->
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Familiar Dashboard</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <style>
        :root {
            --bg-dark: #0f0f1a;
            --bg-card: #1a1a2e;
            --bg-input: #252540;
            --accent: #e94560;
            --accent-hover: #ff6b6b;
            --text: #eaeaea;
            --text-dim: #888;
            --success: #4ade80;
            --warning: #fbbf24;
            --error: #f87171;
            --border: #333;
        }
        
        * { margin: 0; padding: 0; box-sizing: border-box; }
        
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            background: var(--bg-dark);
            color: var(--text);
            min-height: 100vh;
        }
        
        /* Layout */
        .app {
            display: grid;
            grid-template-columns: 240px 1fr 320px;
            grid-template-rows: 64px 1fr;
            height: 100vh;
        }
        
        @media (max-width: 1200px) {
            .app { grid-template-columns: 240px 1fr; }
            .right-panel { display: none; }
        }
        
        @media (max-width: 768px) {
            .app { grid-template-columns: 1fr; }
            .sidebar { display: none; }
        }
        
        /* Header */
        header {
            grid-column: 1 / -1;
            background: var(--bg-card);
            border-bottom: 1px solid var(--border);
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0 24px;
        }
        
        .logo {
            display: flex;
            align-items: center;
            gap: 12px;
            font-size: 1.25rem;
            font-weight: 700;
        }
        
        .logo i { color: var(--accent); }
        
        .status-badge {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 16px;
            background: var(--bg-input);
            border-radius: 20px;
            font-size: 0.875rem;
        }
        
        .status-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--success);
            animation: pulse 2s infinite;
        }
        
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
        
        /* Sidebar */
        .sidebar {
            background: var(--bg-card);
            border-right: 1px solid var(--border);
            padding: 20px 12px;
            overflow-y: auto;
        }
        
        .nav-section {
            margin-bottom: 24px;
        }
        
        .nav-section h4 {
            font-size: 0.7rem;
            text-transform: uppercase;
            color: var(--text-dim);
            padding: 0 12px;
            margin-bottom: 8px;
        }
        
        .nav-item {
            display: flex;
            align-items: center;
            gap: 12px;
            padding: 12px;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            color: var(--text-dim);
        }
        
        .nav-item:hover {
            background: var(--bg-input);
            color: var(--text);
        }
        
        .nav-item.active {
            background: var(--accent);
            color: white;
        }
        
        .nav-item i { width: 20px; text-align: center; }
        
        /* Main content */
        main {
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }
        
        .panel {
            flex: 1;
            padding: 24px;
            overflow-y: auto;
            display: none;
        }
        
        .panel.active { display: block; }
        
        .panel-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 24px;
        }
        
        .panel-header h2 {
            font-size: 1.5rem;
        }
        
        /* Chat */
        #chat-panel {
            display: none;
            flex-direction: column;
        }
        
        #chat-panel.active { display: flex; }
        
        .messages {
            flex: 1;
            overflow-y: auto;
            padding: 24px;
            display: flex;
            flex-direction: column;
            gap: 16px;
        }
        
        .message {
            max-width: 75%;
            padding: 14px 18px;
            border-radius: 16px;
            line-height: 1.5;
        }
        
        .message.user {
            align-self: flex-end;
            background: var(--accent);
            border-bottom-right-radius: 4px;
        }
        
        .message.assistant {
            align-self: flex-start;
            background: var(--bg-card);
            border-bottom-left-radius: 4px;
        }
        
        .message pre {
            background: var(--bg-dark);
            padding: 12px;
            border-radius: 8px;
            margin-top: 8px;
            overflow-x: auto;
            font-size: 0.875rem;
        }
        
        .chat-input-wrap {
            display: flex;
            gap: 12px;
            padding: 20px 24px;
            background: var(--bg-card);
            border-top: 1px solid var(--border);
        }
        
        .chat-input {
            flex: 1;
            padding: 14px 18px;
            border: none;
            border-radius: 12px;
            background: var(--bg-input);
            color: var(--text);
            font-size: 1rem;
        }
        
        .chat-input:focus {
            outline: 2px solid var(--accent);
        }
        
        .send-btn {
            padding: 14px 24px;
            border: none;
            border-radius: 12px;
            background: var(--accent);
            color: white;
            cursor: pointer;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 8px;
            transition: background 0.2s;
        }
        
        .send-btn:hover { background: var(--accent-hover); }
        
        .send-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        /* Cards */
        .card {
            background: var(--bg-card);
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 16px;
        }
        
        .card h3 {
            font-size: 0.875rem;
            color: var(--text-dim);
            margin-bottom: 12px;
        }
        
        .stat-row {
            display: flex;
            justify-content: space-between;
            padding: 10px 0;
            border-bottom: 1px solid var(--border);
        }
        
        .stat-row:last-child { border-bottom: none; }
        
        .stat-value { color: var(--accent); font-weight: 600; }
        
        /* Lists */
        .list-item {
            display: flex;
            align-items: flex-start;
            gap: 12px;
            padding: 14px;
            background: var(--bg-card);
            border-radius: 10px;
            margin-bottom: 10px;
        }
        
        .list-item .icon {
            width: 40px;
            height: 40px;
            border-radius: 10px;
            background: var(--bg-input);
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }
        
        .list-item .content { flex: 1; }
        .list-item .title { font-weight: 500; }
        .list-item .meta { font-size: 0.8rem; color: var(--text-dim); margin-top: 4px; }
        
        /* Task specific */
        .task-checkbox {
            width: 22px;
            height: 22px;
            border: 2px solid var(--accent);
            border-radius: 6px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-shrink: 0;
        }
        
        .task-checkbox.done {
            background: var(--success);
            border-color: var(--success);
        }
        
        .task-checkbox.done i { color: white; }
        
        .priority-urgent { color: var(--error); }
        .priority-high { color: var(--warning); }
        .priority-normal { color: var(--success); }
        
        /* Toggle */
        .toggle {
            width: 48px;
            height: 26px;
            background: var(--bg-input);
            border-radius: 13px;
            cursor: pointer;
            position: relative;
            transition: background 0.3s;
        }
        
        .toggle.on { background: var(--success); }
        
        .toggle::after {
            content: '';
            position: absolute;
            width: 22px;
            height: 22px;
            background: white;
            border-radius: 50%;
            top: 2px;
            left: 2px;
            transition: left 0.3s;
        }
        
        .toggle.on::after { left: 24px; }
        
        /* Right panel */
        .right-panel {
            background: var(--bg-card);
            border-left: 1px solid var(--border);
            padding: 20px;
            overflow-y: auto;
        }
        
        /* Node item */
        .node-item {
            display: flex;
            align-items: center;
            gap: 10px;
            padding: 10px;
            background: var(--bg-input);
            border-radius: 8px;
            margin-bottom: 8px;
        }
        
        .node-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: var(--success);
        }
        
        .node-dot.offline { background: var(--error); }
        
        /* Buttons */
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-weight: 500;
            transition: all 0.2s;
        }
        
        .btn-primary {
            background: var(--accent);
            color: white;
        }
        
        .btn-primary:hover { background: var(--accent-hover); }
        
        .btn-secondary {
            background: var(--bg-input);
            color: var(--text);
        }
        
        .btn-secondary:hover { background: var(--border); }
        
        /* Inputs */
        input, select, textarea {
            padding: 12px 16px;
            border: none;
            border-radius: 8px;
            background: var(--bg-input);
            color: var(--text);
            font-size: 1rem;
            width: 100%;
        }
        
        input:focus, select:focus, textarea:focus {
            outline: 2px solid var(--accent);
        }
        
        /* Modal */
        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0,0,0,0.7);
            display: none;
            align-items: center;
            justify-content: center;
            z-index: 100;
        }
        
        .modal-overlay.show { display: flex; }
        
        .modal {
            background: var(--bg-card);
            border-radius: 16px;
            padding: 24px;
            width: 90%;
            max-width: 500px;
        }
        
        .modal h3 { margin-bottom: 20px; }
        
        .modal .form-group {
            margin-bottom: 16px;
        }
        
        .modal .form-group label {
            display: block;
            margin-bottom: 6px;
            font-size: 0.875rem;
            color: var(--text-dim);
        }
        
        .modal .actions {
            display: flex;
            gap: 12px;
            justify-content: flex-end;
            margin-top: 24px;
        }
        
        /* Loading */
        .loading {
            text-align: center;
            padding: 40px;
            color: var(--text-dim);
        }
        
        .spinner {
            display: inline-block;
            width: 30px;
            height: 30px;
            border: 3px solid var(--bg-input);
            border-top-color: var(--accent);
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        /* Wizard card */
        .wizard-card {
            background: var(--bg-card);
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 16px;
        }

        .wizard-card-header {
            font-size: 1.1rem;
            font-weight: 600;
            margin-bottom: 16px;
        }

        .wizard-tabs {
            display: flex;
            gap: 4px;
            margin-bottom: 16px;
            border-bottom: 1px solid var(--border);
            padding-bottom: 8px;
        }

        .wizard-tab {
            padding: 8px 16px;
            border: none;
            border-radius: 8px 8px 0 0;
            background: transparent;
            color: var(--text-dim);
            cursor: pointer;
            font-size: 0.9rem;
            transition: all 0.2s;
        }

        .wizard-tab:hover {
            background: var(--bg-input);
            color: var(--text);
        }

        .wizard-tab.active {
            background: var(--accent);
            color: white;
        }

        .wizard-tab-content {
            white-space: pre-line;
            line-height: 1.6;
            margin-bottom: 16px;
            font-size: 0.95rem;
        }

        .wizard-buttons {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }

        .wizard-btn {
            padding: 10px 18px;
            border: 1px solid var(--border);
            border-radius: 8px;
            background: var(--bg-input);
            color: var(--text);
            cursor: pointer;
            font-size: 0.9rem;
            transition: all 0.2s;
        }

        .wizard-btn:hover {
            background: var(--accent);
            color: white;
            border-color: var(--accent);
        }

        .wizard-text {
            background: var(--bg-card);
            border-radius: 12px;
            padding: 16px 20px;
            margin-bottom: 12px;
            line-height: 1.6;
        }

        .wizard-input-wrap {
            display: flex;
            gap: 8px;
            margin-top: 12px;
        }

        .wizard-input-wrap input {
            flex: 1;
        }

        /* Terminal Panel */
        #terminal-panel {
            display: none;
            flex-direction: column;
            padding: 0;
        }
        #terminal-panel.active { display: flex; }

        .terminal-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 10px 16px;
            background: #0d0d18;
            border-bottom: 1px solid #1e1e30;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
            font-size: 0.85rem;
        }
        .terminal-title { color: var(--accent); font-weight: 600; }
        .terminal-model { color: var(--text-dim); font-size: 0.75rem; }

        .terminal-output {
            flex: 1;
            overflow-y: auto;
            padding: 16px;
            background: #0a0a12;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
            font-size: 0.875rem;
            line-height: 1.6;
        }

        .terminal-line { margin-bottom: 2px; white-space: pre-wrap; word-break: break-word; }
        .terminal-line.user { color: var(--accent); }
        .terminal-line.user::before { content: '\203A '; opacity: 0.6; }
        .terminal-line.assistant { color: #d0d0d8; }
        .terminal-line.tool { color: var(--warning); font-style: italic; font-size: 0.8rem; }
        .terminal-line.error { color: var(--error); }
        .terminal-line pre {
            background: #12121f;
            padding: 10px 12px;
            border-radius: 6px;
            margin: 4px 0;
            overflow-x: auto;
            font-size: 0.825rem;
        }

        .terminal-cursor {
            display: inline-block;
            width: 8px;
            height: 1.1em;
            background: var(--text);
            vertical-align: text-bottom;
            animation: term-blink 0.8s step-end infinite;
        }
        @keyframes term-blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
        }

        .terminal-input-bar {
            display: flex;
            align-items: center;
            padding: 10px 16px;
            background: #0d0d18;
            border-top: 1px solid #1e1e30;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
        }
        .terminal-prompt {
            color: var(--accent);
            font-size: 1.1rem;
            margin-right: 10px;
            user-select: none;
        }
        .terminal-input {
            flex: 1;
            background: transparent;
            border: none;
            color: var(--text);
            font-family: inherit;
            font-size: 0.875rem;
            outline: none;
            padding: 4px 0;
        }
        /* Shell Panel */
        #shell-panel {
            display: none;
            flex-direction: column;
            padding: 0;
        }
        #shell-panel.active { display: flex; }

        .shell-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 10px 16px;
            background: #0d0d18;
            border-bottom: 1px solid #1e1e30;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
            font-size: 0.85rem;
        }
        .shell-title { color: var(--warning); font-weight: 600; }
        .shell-status { color: var(--text-dim); font-size: 0.75rem; }
        .shell-kill-btn {
            display: none;
            background: var(--error);
            color: #fff;
            border: none;
            border-radius: 4px;
            padding: 3px 10px;
            font-size: 0.75rem;
            cursor: pointer;
            font-family: inherit;
        }
        .shell-kill-btn:hover { opacity: 0.85; }

        .shell-output {
            flex: 1;
            overflow-y: auto;
            padding: 16px;
            background: #0a0a12;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
            font-size: 0.875rem;
            line-height: 1.6;
        }

        .shell-line { margin-bottom: 1px; white-space: pre-wrap; word-break: break-word; }
        .shell-line.command { color: var(--warning); }
        .shell-line.command::before { content: '$ '; opacity: 0.6; }
        .shell-line.output { color: #d0d0d8; }
        .shell-line.exit-ok { color: var(--success); font-size: 0.8rem; font-style: italic; }
        .shell-line.exit-fail { color: var(--error); font-size: 0.8rem; font-style: italic; }
        .shell-line.error { color: var(--error); }

        .shell-input-bar {
            display: flex;
            align-items: center;
            padding: 10px 16px;
            background: #0d0d18;
            border-top: 1px solid #1e1e30;
            font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
        }
        .shell-prompt {
            color: var(--warning);
            font-size: 1.1rem;
            margin-right: 10px;
            user-select: none;
        }
        .shell-input {
            flex: 1;
            background: transparent;
            border: none;
            color: var(--text);
            font-family: inherit;
            font-size: 0.875rem;
            outline: none;
            padding: 4px 0;
        }

        .shell-nav-badge {
            font-size: 0.55rem;
            background: var(--warning);
            color: #000;
            padding: 1px 5px;
            border-radius: 3px;
            margin-left: 6px;
            font-weight: 700;
            letter-spacing: 0.5px;
        }

        /* Model Routing Card */
        .routing-toggle-bar {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 12px;
            padding-bottom: 10px;
            border-bottom: 1px solid var(--border);
        }
        .routing-toggle-bar .label {
            font-size: 0.875rem;
            color: var(--text-secondary);
        }
        .routing-toggle-bar .mode-value {
            font-weight: 600;
            color: var(--text);
            margin-left: 6px;
        }
        .routing-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 0.8rem;
            table-layout: fixed;
            border: 1px solid var(--border);
            border-radius: 6px;
            overflow: hidden;
        }
        .routing-table th {
            text-align: left;
            font-weight: 600;
            font-size: 0.75rem;
            color: var(--text-secondary);
            padding: 8px 10px;
            border: 1px solid var(--border);
            background: var(--surface);
        }
        .routing-table td {
            padding: 8px 10px;
            border: 1px solid var(--border);
            vertical-align: middle;
        }
        .routing-table .col-cat { width: 25%; font-weight: 500; }
        .routing-table .col-desc { width: 40%; color: var(--text-secondary); font-size: 0.75rem; }
        .routing-table .col-model { width: 35%; }
        .routing-table select {
            background: var(--surface);
            border: 1px solid var(--border);
            border-radius: 4px;
            color: var(--text);
            padding: 3px 6px;
            font-size: 0.75rem;
            font-family: inherit;
            width: 100%;
            display: block;
        }
        .routing-table .auto-label {
            color: var(--accent);
            font-size: 0.75rem;
        }
        .routing-note {
            font-size: 0.8rem;
            color: var(--text-secondary);
            padding: 10px 0;
        }

        /* Voice controls */
        .voice-btn {
            padding: 14px;
            border: none;
            border-radius: 12px;
            background: var(--bg-input);
            color: var(--text-dim);
            cursor: pointer;
            font-size: 1rem;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            width: 48px;
        }
        .voice-btn:hover { background: var(--border); color: var(--text); }
        .voice-btn.recording {
            background: var(--error);
            color: white;
            animation: rec-pulse 1s ease-in-out infinite;
        }
        @keyframes rec-pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.6; }
        }
        .tts-toggle {
            padding: 14px;
            border: none;
            border-radius: 12px;
            background: var(--bg-input);
            color: var(--text-dim);
            cursor: pointer;
            font-size: 1rem;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
            width: 48px;
        }
        .tts-toggle:hover { background: var(--border); color: var(--text); }
        .tts-toggle.on { background: var(--accent); color: white; }

        /* Connect Browse */
        .browse-services { display: flex; flex-direction: column; gap: 16px; }
        .browse-service-card {
            background: var(--bg-card);
            border-radius: 12px;
            padding: 16px;
            border: 1px solid var(--border);
        }
        .browse-service-header {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 10px;
        }
        .browse-service-header .icon {
            width: 36px; height: 36px; border-radius: 8px;
            background: var(--bg-input);
            display: flex; align-items: center; justify-content: center;
            flex-shrink: 0; font-size: 1rem;
        }
        .browse-service-header .name { font-weight: 600; flex: 1; }
        .browse-service-header .summary { font-size: 0.8rem; color: var(--text-dim); }
        .browse-service-card .browse-item {
            padding: 8px 0;
            border-top: 1px solid var(--bg-input);
        }
        .browse-service-card .browse-item:first-child { border-top: none; }
        .browse-item .item-name { font-size: 0.9rem; }
        .browse-item .item-detail { font-size: 0.78rem; color: var(--text-dim); margin-top: 2px; }
        .browse-btn-configure {
            display: inline-block; margin-top: 12px; padding: 8px 16px;
            background: var(--bg-input); color: var(--text);
            border: 1px solid var(--border); border-radius: 8px;
            cursor: pointer; font-size: 0.85rem; transition: all 0.2s;
        }
        .browse-btn-configure:hover { background: var(--border); }
        .browse-empty {
            text-align: center; padding: 40px 20px; color: var(--text-dim);
        }
        .browse-error { font-size: 0.8rem; color: var(--error); margin-top: 4px; }
        .browse-url-bar {
            display: flex; gap: 8px; margin-top: 10px;
        }
        .browse-url-bar input {
            flex: 1; padding: 8px 12px; border-radius: 8px;
            border: 1px solid var(--border); background: var(--bg-input);
            color: var(--text); font-size: 0.85rem; outline: none;
        }
        .browse-url-bar input:focus { border-color: var(--accent); }
        .browse-url-bar button {
            padding: 8px 16px; border-radius: 8px; border: none;
            background: var(--accent); color: #fff; cursor: pointer;
            font-size: 0.85rem; font-weight: 600; transition: background 0.2s;
        }
        .browse-url-bar button:hover { background: var(--accent-hover); }
        .browse-page-preview {
            margin-top: 10px; padding: 12px; border-radius: 8px;
            background: var(--bg-input); font-size: 0.82rem;
            max-height: 200px; overflow-y: auto; white-space: pre-wrap;
            line-height: 1.4;
        }
        .browse-page-preview .preview-title {
            font-weight: 600; margin-bottom: 4px;
        }
        .browse-page-preview .preview-url {
            font-size: 0.75rem; color: var(--text-dim); margin-bottom: 8px;
        }
        /* Video action buttons */
        .video-actions {
            display: flex; gap: 8px; margin: 10px 0;
        }
        .video-play-btn, .video-download-btn {
            padding: 8px 16px; border-radius: 8px; border: none;
            font-size: 0.82rem; font-weight: 600; cursor: pointer;
            display: inline-flex; align-items: center; gap: 6px;
            transition: background 0.2s;
        }
        .video-play-btn {
            background: var(--accent); color: #fff;
        }
        .video-play-btn:hover { background: var(--accent-hover); }
        .video-download-btn {
            background: var(--bg-input); color: var(--text);
            border: 1px solid var(--border);
        }
        .video-download-btn:hover { background: var(--border); }
        .video-quality-select {
            padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border);
            background: var(--bg-input); color: var(--text); font-size: 0.8rem;
        }
        #video-player-area, .video-result-player { margin-top: 10px; position: relative; }
        #video-player-area iframe, #video-player-area video,
        .video-result-player iframe, .video-result-player video {
            border-radius: 8px; max-width: 100%;
        }
        .video-close-btn {
            position: absolute; top: 4px; right: 4px; z-index: 10;
            background: rgba(0,0,0,0.7); color: #fff; border: none;
            border-radius: 50%; width: 28px; height: 28px; cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            font-size: 0.85rem; transition: background 0.2s;
        }
        .video-close-btn:hover { background: rgba(0,0,0,0.9); }
        .video-download-status {
            margin-top: 6px; font-size: 0.8rem; color: var(--text-dim);
        }
        /* Online video search results */
        .video-online-search { margin-top: 10px; }
        .video-online-search .browse-url-bar { margin-top: 8px; }
        .video-search-result {
            display: flex; gap: 10px; padding: 8px; border-radius: 6px;
            background: var(--bg-input); margin-bottom: 6px; align-items: center;
        }
        .video-search-result:hover { background: var(--border); }
        .video-thumbnail {
            width: 80px; height: 45px; border-radius: 4px;
            object-fit: cover; flex-shrink: 0; background: #000;
        }
        .video-search-result .item-name {
            font-size: 0.82rem; font-weight: 500;
        }
        .video-search-result .item-detail {
            font-size: 0.72rem; color: var(--text-dim);
        }
        .video-search-result .video-actions {
            margin: 4px 0 0 0; gap: 6px;
        }
        .video-search-result .video-play-btn,
        .video-search-result .video-download-btn {
            padding: 4px 10px; font-size: 0.75rem;
        }
    </style>
</head>
<body>
    <div class="app">
        <!-- Header -->
        <header>
            <div class="logo">
                <i class="fas fa-dragon"></i>
                <span>Pi Agent</span>
            </div>
            <div class="status-badge">
                <div class="status-dot" id="status-dot"></div>
                <span id="status-text">Connecting...</span>
            </div>
        </header>
        
        <!-- Sidebar -->
        <nav class="sidebar">
            <div class="nav-section">
                <h4>Main</h4>
                <div class="nav-item active" data-panel="chat">
                    <i class="fas fa-comments"></i>
                    <span>Chat</span>
                </div>
                <div class="nav-item" data-panel="terminal">
                    <i class="fas fa-terminal"></i>
                    <span>Terminal</span>
                </div>
                <div class="nav-item" data-panel="shell" id="shell-nav" style="display:none">
                    <i class="fas fa-terminal"></i>
                    <span>Shell<span class="shell-nav-badge">HOST</span></span>
                </div>
                <div class="nav-item" data-panel="briefing">
                    <i class="fas fa-sun"></i>
                    <span>Briefing</span>
                </div>
                <div class="nav-item" data-panel="email">
                    <i class="fas fa-envelope"></i>
                    <span>Email Triage</span>
                </div>
            </div>
            
            <div class="nav-section">
                <h4>Manage</h4>
                <div class="nav-item" data-panel="connect">
                    <i class="fas fa-plug"></i>
                    <span>Connect</span>
                </div>
                <div class="nav-item" data-panel="tasks">
                    <i class="fas fa-check-square"></i>
                    <span>Tasks</span>
                </div>
                <div class="nav-item" data-panel="memory">
                    <i class="fas fa-brain"></i>
                    <span>Memory</span>
                </div>
                <div class="nav-item" data-panel="skills">
                    <i class="fas fa-puzzle-piece"></i>
                    <span>Skills</span>
                </div>
            </div>
            
            <div class="nav-section">
                <h4>System</h4>
                <div class="nav-item" data-panel="nodes">
                    <i class="fas fa-network-wired"></i>
                    <span>Mesh Nodes</span>
                </div>
                <div class="nav-item" data-panel="settings">
                    <i class="fas fa-cog"></i>
                    <span>Settings</span>
                </div>
            </div>
        </nav>
        
        <!-- Main Content -->
        <main>
            <!-- Chat Panel -->
            <div id="chat-panel" class="panel active">
                <div class="messages" id="messages">
                    <div class="message assistant">
                        <p>👋 Hi! I'm your Pi Agent. How can I help you today?</p>
                    </div>
                </div>
                <div class="chat-input-wrap">
                    <button class="voice-btn" id="voice-btn" onclick="toggleVoice()" title="Voice input">
                        <i class="fas fa-microphone"></i>
                    </button>
                    <input type="text" class="chat-input" id="chat-input" placeholder="Type a message..." />
                    <button class="tts-toggle" id="tts-toggle" onclick="toggleTTS()" title="Read responses aloud">
                        <i class="fas fa-volume-up"></i>
                    </button>
                    <button class="send-btn" id="send-btn" onclick="sendMessage()">
                        <i class="fas fa-paper-plane"></i>
                        Send
                    </button>
                </div>
            </div>
            
            <!-- Terminal Panel -->
            <div id="terminal-panel" class="panel">
                <div class="terminal-header">
                    <span class="terminal-title">familiar $</span>
                    <span class="terminal-model" id="terminal-model"></span>
                </div>
                <div class="terminal-output" id="terminal-output"></div>
                <div class="terminal-input-bar">
                    <span class="terminal-prompt">&rsaquo;</span>
                    <input type="text" class="terminal-input" id="terminal-input"
                           placeholder="Type a command..." autocomplete="off" spellcheck="false" />
                </div>
            </div>

            <!-- Shell Panel -->
            <div id="shell-panel" class="panel">
                <div class="shell-header">
                    <span class="shell-title">host $</span>
                    <span class="shell-status" id="shell-status"></span>
                    <button class="shell-kill-btn" id="shell-kill-btn" onclick="shellKill()">Kill</button>
                </div>
                <div class="shell-output" id="shell-output"></div>
                <div class="shell-input-bar">
                    <span class="shell-prompt">$</span>
                    <input type="text" class="shell-input" id="shell-input"
                           placeholder="Enter a command..." autocomplete="off" spellcheck="false" />
                </div>
            </div>

            <!-- Briefing Panel -->
            <div id="briefing-panel" class="panel">
                <div class="panel-header">
                    <h2>Daily Briefing</h2>
                    <button class="btn btn-primary" onclick="loadBriefing()">
                        <i class="fas fa-sync"></i> Refresh
                    </button>
                </div>
                <div id="briefing-content" class="card">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Email Panel -->
            <div id="email-panel" class="panel">
                <div class="panel-header">
                    <h2>Email Triage</h2>
                    <button class="btn btn-primary" onclick="loadEmailTriage()">
                        <i class="fas fa-sync"></i> Refresh
                    </button>
                </div>
                <div id="email-content">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Tasks Panel -->
            <div id="tasks-panel" class="panel">
                <div class="panel-header">
                    <h2>Tasks</h2>
                    <button class="btn btn-primary" onclick="showAddTaskModal()">
                        <i class="fas fa-plus"></i> Add Task
                    </button>
                </div>
                <div id="tasks-list">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Memory Panel -->
            <div id="memory-panel" class="panel">
                <div class="panel-header">
                    <h2>Memory</h2>
                    <button class="btn btn-primary" onclick="showAddMemoryModal()">
                        <i class="fas fa-plus"></i> Add Memory
                    </button>
                </div>
                <div id="memory-list">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Skills Panel -->
            <div id="skills-panel" class="panel">
                <div class="panel-header">
                    <h2>Skills</h2>
                </div>
                <div id="skills-list">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Connect Panel -->
            <div id="connect-panel" class="panel">
                <div class="panel-header">
                    <h2>Connect Services</h2>
                </div>
                <div id="connect-content">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>

            <!-- Nodes Panel -->
            <div id="nodes-panel" class="panel">
                <div class="panel-header">
                    <h2>Mesh Network</h2>
                </div>
                <div id="nodes-content">
                    <div class="loading"><div class="spinner"></div></div>
                </div>
            </div>
            
            <!-- Settings Panel -->
            <div id="settings-panel" class="panel">
                <div class="panel-header">
                    <h2>Settings</h2>
                </div>
                
                <div class="card">
                    <h3>LLM Provider</h3>
                    <select id="provider-select" onchange="changeProvider(this.value)">
                        <option value="anthropic">Claude (Anthropic)</option>
                        <option value="openai">GPT (OpenAI)</option>
                        <option value="ollama">Ollama (Local)</option>
                    </select>
                </div>

                <div class="card" id="routing-card">
                    <h3>Model Routing</h3>
                    <div id="routing-content">
                        <div class="loading"><div class="spinner"></div></div>
                    </div>
                </div>

                <div class="card">
                    <h3>Agent</h3>
                    <div class="stat-row">
                        <span>Name</span>
                        <span id="setting-name" class="stat-value">Pi Agent</span>
                    </div>
                    <div class="stat-row">
                        <span>Memory</span>
                        <div class="toggle on" id="toggle-memory"></div>
                    </div>
                    <div class="stat-row">
                        <span>Skills</span>
                        <div class="toggle on" id="toggle-skills"></div>
                    </div>
                </div>

                <div class="card">
                    <h3>Host Shell</h3>
                    <div class="stat-row">
                        <span>Enable</span>
                        <div class="toggle" id="toggle-shell" onclick="toggleShell()"></div>
                    </div>
                    <div style="font-size: 0.75rem; color: var(--text-dim); margin-top: 8px;">
                        Allows running host system commands from the dashboard.
                    </div>
                </div>

                <div class="card">
                    <h3>Mesh Gateway</h3>
                    <div class="stat-row">
                        <span>Status</span>
                        <span id="gateway-status" class="stat-value">Running</span>
                    </div>
                    <div class="stat-row">
                        <span>Port</span>
                        <span class="stat-value">18789</span>
                    </div>
                    <div style="margin-top: 12px;">
                        <button class="btn btn-secondary" onclick="showMeshKey()">
                            <i class="fas fa-key"></i> Show Secret Key
                        </button>
                    </div>
                </div>
            </div>
        </main>
        
        <!-- Right Panel -->
        <aside class="right-panel">
            <div class="card">
                <h3>System Status</h3>
                <div class="stat-row">
                    <span>Provider</span>
                    <span class="stat-value" id="stat-provider">Loading...</span>
                </div>
                <div class="stat-row">
                    <span>Memory</span>
                    <span class="stat-value" id="stat-memory">0 entries</span>
                </div>
                <div class="stat-row">
                    <span>Skills</span>
                    <span class="stat-value" id="stat-skills">0</span>
                </div>
                <div class="stat-row">
                    <span>Tools</span>
                    <span class="stat-value" id="stat-tools">0</span>
                </div>
            </div>
            
            <div class="card">
                <h3>Connected Nodes</h3>
                <div id="nodes-sidebar">
                    <div class="node-item">
                        <div class="node-dot"></div>
                        <div>
                            <div style="font-weight: 500;">Gateway</div>
                            <div style="font-size: 0.75rem; color: var(--text-dim);">This device</div>
                        </div>
                    </div>
                </div>
            </div>
            
            <div class="card">
                <h3>Quick Actions</h3>
                <button class="btn btn-secondary" style="width: 100%; margin-bottom: 8px;" onclick="quickCommand('daily_briefing')">
                    <i class="fas fa-sun"></i> Morning Briefing
                </button>
                <button class="btn btn-secondary" style="width: 100%; margin-bottom: 8px;" onclick="quickCommand('triage_inbox')">
                    <i class="fas fa-envelope"></i> Sort Inbox
                </button>
                <button class="btn btn-secondary" style="width: 100%;" onclick="quickCommand('check_deadlines')">
                    <i class="fas fa-clock"></i> Check Deadlines
                </button>
            </div>
        </aside>
    </div>
    
    <!-- Add Task Modal -->
    <div class="modal-overlay" id="task-modal">
        <div class="modal">
            <h3>Add Task</h3>
            <div class="form-group">
                <label>Title</label>
                <input type="text" id="task-title" placeholder="What needs to be done?" />
            </div>
            <div class="form-group">
                <label>Priority</label>
                <select id="task-priority">
                    <option value="normal">Normal</option>
                    <option value="high">High</option>
                    <option value="urgent">Urgent</option>
                    <option value="low">Low</option>
                </select>
            </div>
            <div class="form-group">
                <label>Due Date</label>
                <input type="text" id="task-due" placeholder="tomorrow, next week, 2024-03-15" />
            </div>
            <div class="form-group">
                <label>Category</label>
                <select id="task-category">
                    <option value="general">General</option>
                    <option value="grants">Grants</option>
                    <option value="donors">Donors</option>
                    <option value="board">Board</option>
                    <option value="events">Events</option>
                    <option value="compliance">Compliance</option>
                </select>
            </div>
            <div class="actions">
                <button class="btn btn-secondary" onclick="hideModal('task-modal')">Cancel</button>
                <button class="btn btn-primary" onclick="addTask()">Add Task</button>
            </div>
        </div>
    </div>
    
    <!-- Add Memory Modal -->
    <div class="modal-overlay" id="memory-modal">
        <div class="modal">
            <h3>Add Memory</h3>
            <div class="form-group">
                <label>Key</label>
                <input type="text" id="memory-key" placeholder="e.g., user_name, favorite_color" />
            </div>
            <div class="form-group">
                <label>Value</label>
                <textarea id="memory-value" rows="3" placeholder="What should I remember?"></textarea>
            </div>
            <div class="form-group">
                <label>Category</label>
                <select id="memory-category">
                    <option value="fact">Fact</option>
                    <option value="preference">Preference</option>
                    <option value="user_info">User Info</option>
                    <option value="task">Task</option>
                </select>
            </div>
            <div class="actions">
                <button class="btn btn-secondary" onclick="hideModal('memory-modal')">Cancel</button>
                <button class="btn btn-primary" onclick="addMemory()">Save</button>
            </div>
        </div>
    </div>
    
    <script>
        // API base
        const API = '';
        
        // ── Auth layer ──
        // Read key from URL (?api_key=xxx), sessionStorage, or prompt user
        let _apiKey = new URLSearchParams(window.location.search).get('api_key')
                    || sessionStorage.getItem('familiar_api_key')
                    || '';
        
        // Clean the URL so the key isn't visible in the address bar
        if (window.location.search.includes('api_key')) {
            const url = new URL(window.location);
            url.searchParams.delete('api_key');
            window.history.replaceState({}, '', url);
        }
        
        if (_apiKey) sessionStorage.setItem('familiar_api_key', _apiKey);
        
        // Authenticated fetch wrapper
        function apiFetch(url, opts = {}) {
            opts.headers = opts.headers || {};
            if (_apiKey) opts.headers['X-API-Key'] = _apiKey;
            return fetch(url, opts).then(res => {
                if (res.status === 401) { showLoginOverlay(); throw new Error('Unauthorized'); }
                return res;
            });
        }
        
        function showLoginOverlay() {
            let overlay = document.getElementById('auth-overlay');
            if (overlay) { overlay.style.display = 'flex'; return; }
            overlay = document.createElement('div');
            overlay.id = 'auth-overlay';
            overlay.style.cssText = 'position:fixed;inset:0;background:rgba(15,15,26,0.95);display:flex;align-items:center;justify-content:center;z-index:9999;';
            overlay.innerHTML = `
                <div style="background:#1a1a2e;padding:40px;border-radius:12px;max-width:440px;width:90%;text-align:center;">
                    <h2 style="margin-bottom:8px;color:#eaeaea;">🦔 Familiar Dashboard</h2>
                    <p style="color:#888;margin-bottom:24px;font-size:14px;">Paste the API key from your terminal</p>
                    <input id="auth-key-input" type="text" placeholder="API key"
                        style="width:100%;padding:12px;background:#252540;border:1px solid #333;border-radius:6px;color:#eaeaea;font-family:monospace;font-size:14px;margin-bottom:16px;">
                    <button id="auth-submit-btn"
                        style="width:100%;padding:12px;background:#e94560;color:white;border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:600;">
                        Connect
                    </button>
                    <p id="auth-error" style="color:#f87171;margin-top:12px;font-size:13px;display:none;">Invalid key — check your terminal output</p>
                </div>`;
            document.body.appendChild(overlay);
            const inp = document.getElementById('auth-key-input');
            inp.focus();
            inp.addEventListener('keypress', e => { if (e.key === 'Enter') tryAuth(); });
            document.getElementById('auth-submit-btn').addEventListener('click', tryAuth);
        }
        
        async function tryAuth() {
            const inp = document.getElementById('auth-key-input');
            const key = inp.value.trim();
            if (!key) return;
            try {
                const res = await apiFetch(API + '/api/status', { headers: { 'X-API-Key': key } });
                if (res.ok) {
                    _apiKey = key;
                    sessionStorage.setItem('familiar_api_key', key);
                    document.getElementById('auth-overlay').style.display = 'none';
                    loadStatus();
                } else {
                    document.getElementById('auth-error').style.display = 'block';
                }
            } catch(e) {
                document.getElementById('auth-error').style.display = 'block';
            }
        }
        
        // Verify auth on load
        if (!_apiKey) { showLoginOverlay(); }
        else { fetch(API + '/api/status', { headers: { 'X-API-Key': _apiKey } })
            .then(r => { if (r.status === 401) showLoginOverlay(); })
            .catch(() => {}); }
        
        
        // Navigation
        document.querySelectorAll('.nav-item').forEach(item => {
            item.addEventListener('click', () => {
                const panel = item.dataset.panel;
                
                document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
                item.classList.add('active');
                
                document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
                document.getElementById(panel + '-panel').classList.add('active');
                
                // Load data for panel
                switch(panel) {
                    case 'briefing': loadBriefing(); break;
                    case 'email': loadEmailTriage(); break;
                    case 'tasks': loadTasks(); break;
                    case 'memory': loadMemory(); break;
                    case 'skills': loadSkills(); break;
                    case 'nodes': loadNodes(); break;
                    case 'connect': loadConnectBrowse(); break;
                    case 'terminal': loadTerminalModel(); break;
                    case 'shell': shellFocusInput(); break;
                    case 'settings': loadRoutingConfig(); loadShellToggle(); break;
                }
            });
        });
        
        // Chat
        const chatInput = document.getElementById('chat-input');
        const sendBtn = document.getElementById('send-btn');
        const messagesDiv = document.getElementById('messages');
        
        chatInput.addEventListener('keypress', e => {
            if (e.key === 'Enter') sendMessage();
        });
        
        async function sendMessage() {
            const text = chatInput.value.trim();
            if (!text) return;
            
            // Add user message
            addMessage(text, 'user');
            chatInput.value = '';
            sendBtn.disabled = true;
            
            try {
                const res = await apiFetch(API + '/api/chat', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({message: text})
                });
                const data = await res.json();
                if (data.wizard_messages) {
                    data.wizard_messages.forEach(msg => addWizardMessage(msg));
                } else {
                    addMessage(data.response, 'assistant');
                    if (ttsEnabled) playTTS(data.response);
                }
            } catch (e) {
                addMessage('Error: Could not reach agent', 'assistant');
            }
            
            sendBtn.disabled = false;
        }
        
        // ── Voice Input / TTS ────────────────────────────────
        let ttsEnabled = false;
        let mediaRecorder = null;
        let audioChunks = [];
        let isRecording = false;

        function toggleVoice() {
            if (isRecording) {
                stopVoice();
            } else {
                startVoice();
            }
        }

        async function startVoice() {
            const btn = document.getElementById('voice-btn');
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' });
                audioChunks = [];

                mediaRecorder.ondataavailable = e => {
                    if (e.data.size > 0) audioChunks.push(e.data);
                };

                mediaRecorder.onstop = () => {
                    stream.getTracks().forEach(t => t.stop());
                    const blob = new Blob(audioChunks, { type: 'audio/webm' });
                    sendVoice(blob);
                };

                mediaRecorder.start();
                isRecording = true;
                btn.classList.add('recording');
            } catch (e) {
                addMessage('Microphone access denied or unavailable.', 'assistant');
            }
        }

        function stopVoice() {
            const btn = document.getElementById('voice-btn');
            if (mediaRecorder && mediaRecorder.state !== 'inactive') {
                mediaRecorder.stop();
            }
            isRecording = false;
            btn.classList.remove('recording');
        }

        async function sendVoice(blob) {
            sendBtn.disabled = true;
            const form = new FormData();
            form.append('audio', blob, 'recording.webm');

            try {
                const res = await apiFetch(API + '/api/voice/stt', {
                    method: 'POST',
                    body: form
                });
                const data = await res.json();
                if (data.error) {
                    addMessage('Voice error: ' + data.error, 'assistant');
                } else {
                    addMessage(data.transcript, 'user');
                    addMessage(data.response, 'assistant');
                    if (ttsEnabled) playTTS(data.response);
                }
            } catch (e) {
                addMessage('Error: Could not process voice input', 'assistant');
            }
            sendBtn.disabled = false;
        }

        function toggleTTS() {
            ttsEnabled = !ttsEnabled;
            document.getElementById('tts-toggle').classList.toggle('on', ttsEnabled);
        }

        async function playTTS(text) {
            try {
                const res = await apiFetch(API + '/api/voice/tts', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ text: text })
                });
                if (!res.ok) return;
                const blob = await res.blob();
                const url = URL.createObjectURL(blob);
                const audio = new Audio(url);
                audio.onended = () => URL.revokeObjectURL(url);
                audio.play();
            } catch (e) {
                // TTS failure is non-critical; text response is already shown
            }
        }

        function escapeHtml(str) {
            const div = document.createElement('div');
            div.textContent = str;
            return div.innerHTML;
        }

        function addMessage(text, role) {
            const msg = document.createElement('div');
            msg.className = 'message ' + role;

            // Escape HTML entities first to prevent XSS
            text = escapeHtml(text);

            // Format text (safe — operates on escaped content)
            text = text.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
            text = text.replace(/`([^`]+)`/g, '<code style="background:var(--bg-input);padding:2px 6px;border-radius:4px;">$1</code>');
            text = text.replace(/\n/g, '<br>');

            msg.innerHTML = '<p>' + text + '</p>';
            messagesDiv.appendChild(msg);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }
        
        function quickCommand(cmd) {
            chatInput.value = cmd.replace(/_/g, ' ');
            sendMessage();
            
            // Switch to chat
            document.querySelector('[data-panel="chat"]').click();
        }
        
        // Load functions
        async function loadStatus() {
            try {
                const res = await apiFetch(API + '/api/status');
                const data = await res.json();
                
                document.getElementById('status-text').textContent = 'Online';
                document.getElementById('status-dot').style.background = 'var(--success)';
                
                document.getElementById('stat-provider').textContent = data.provider || 'Unknown';
                document.getElementById('stat-memory').textContent = (data.memory_entries || 0) + ' entries';
                document.getElementById('stat-skills').textContent = data.skills_loaded || 0;
                document.getElementById('stat-tools').textContent = data.tools_available || 0;
            } catch (e) {
                document.getElementById('status-text').textContent = 'Offline';
                document.getElementById('status-dot').style.background = 'var(--error)';
            }
        }
        
        async function loadBriefing() {
            const container = document.getElementById('briefing-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            
            try {
                const res = await apiFetch(API + '/api/briefing');
                const data = await res.json();
                
                let html = data.briefing || 'No briefing available';
                html = html.replace(/\n/g, '<br>');
                container.innerHTML = html;
            } catch (e) {
                container.innerHTML = '<p>Error loading briefing</p>';
            }
        }
        
        async function loadEmailTriage() {
            const container = document.getElementById('email-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            
            try {
                const res = await apiFetch(API + '/api/email/triage');
                const data = await res.json();
                
                let html = data.triage || 'No email data';
                html = html.replace(/\n/g, '<br>');
                container.innerHTML = '<div class="card">' + html + '</div>';
            } catch (e) {
                container.innerHTML = '<div class="card"><p>Error loading email triage. Make sure email is configured.</p></div>';
            }
        }
        
        async function loadTasks() {
            const container = document.getElementById('tasks-list');
            
            try {
                const res = await apiFetch(API + '/api/tasks');
                const data = await res.json();
                
                if (!data.tasks || data.tasks.length === 0) {
                    container.innerHTML = '<p style="color:var(--text-dim)">No tasks yet</p>';
                    return;
                }
                
                container.innerHTML = data.tasks.filter(t => !t.completed).map(t => `
                    <div class="list-item">
                        <div class="task-checkbox ${t.completed ? 'done' : ''}" onclick="completeTask('${t.id}')">
                            ${t.completed ? '<i class="fas fa-check"></i>' : ''}
                        </div>
                        <div class="content">
                            <div class="title">${t.title}</div>
                            <div class="meta">
                                <span class="priority-${t.priority}">${t.priority}</span>
                                ${t.due_date ? ' • Due: ' + t.due_date : ''}
                                ${t.category ? ' • ' + t.category : ''}
                            </div>
                        </div>
                    </div>
                `).join('');
            } catch (e) {
                container.innerHTML = '<p>Error loading tasks</p>';
            }
        }
        
        async function completeTask(id) {
            await apiFetch(API + '/api/tasks/' + id + '/complete', {method: 'POST'});
            loadTasks();
        }
        
        async function loadMemory() {
            const container = document.getElementById('memory-list');
            
            try {
                const res = await apiFetch(API + '/api/memory');
                const data = await res.json();
                
                if (!data.memories || data.memories.length === 0) {
                    container.innerHTML = '<p style="color:var(--text-dim)">No memories yet</p>';
                    return;
                }
                
                container.innerHTML = data.memories.map(m => `
                    <div class="list-item">
                        <div class="icon"><i class="fas fa-lightbulb"></i></div>
                        <div class="content">
                            <div class="title">${m.key}</div>
                            <div class="meta">${m.value}</div>
                            <div class="meta">${m.category} • ${m.updated_at?.slice(0,10) || ''}</div>
                        </div>
                    </div>
                `).join('');
            } catch (e) {
                container.innerHTML = '<p>Error loading memory</p>';
            }
        }
        
        async function loadSkills() {
            const container = document.getElementById('skills-list');
            
            try {
                const res = await apiFetch(API + '/api/skills');
                const data = await res.json();
                
                if (!data.skills || data.skills.length === 0) {
                    container.innerHTML = '<p style="color:var(--text-dim)">No skills loaded</p>';
                    return;
                }
                
                container.innerHTML = data.skills.map(s => `
                    <div class="list-item">
                        <div class="icon"><i class="fas fa-puzzle-piece"></i></div>
                        <div class="content">
                            <div class="title">${s.name}</div>
                            <div class="meta">${s.tools?.length || 0} tools</div>
                        </div>
                        <div class="toggle ${s.enabled ? 'on' : ''}" onclick="toggleSkill('${s.name}')"></div>
                    </div>
                `).join('');
            } catch (e) {
                container.innerHTML = '<p>Error loading skills</p>';
            }
        }
        
        async function toggleSkill(name) {
            await apiFetch(API + '/api/skills/' + name + '/toggle', {method: 'POST'});
            loadSkills();
        }
        
        async function loadNodes() {
            const container = document.getElementById('nodes-content');
            const sidebar = document.getElementById('nodes-sidebar');
            
            try {
                const res = await apiFetch(API + '/api/nodes');
                const data = await res.json();
                
                const gatewayHtml = `
                    <div class="node-item">
                        <div class="node-dot"></div>
                        <div>
                            <div style="font-weight: 500;">Gateway</div>
                            <div style="font-size: 0.75rem; color: var(--text-dim);">This device</div>
                        </div>
                    </div>
                `;
                
                if (!data.nodes || data.nodes.length === 0) {
                    container.innerHTML = `
                        <div class="card">
                            <p>No nodes connected</p>
                            <p style="margin-top: 10px; color: var(--text-dim);">
                                Connect a node with:<br>
                                <code style="display: block; margin-top: 8px; padding: 12px; background: var(--bg-input); border-radius: 6px; font-size: 0.85rem;">
                                    python -m pi_agent.node --gateway ws://YOUR_IP:18789 --key YOUR_KEY
                                </code>
                            </p>
                        </div>
                    `;
                    sidebar.innerHTML = gatewayHtml;
                    return;
                }
                
                const nodesHtml = data.nodes.map(n => `
                    <div class="list-item">
                        <div class="icon"><i class="fas fa-laptop"></i></div>
                        <div class="content">
                            <div class="title">${n.name}</div>
                            <div class="meta">${n.type} • ${n.tools?.length || 0} tools</div>
                        </div>
                        <div class="node-dot"></div>
                    </div>
                `).join('');
                
                container.innerHTML = nodesHtml;
                sidebar.innerHTML = gatewayHtml + data.nodes.map(n => `
                    <div class="node-item">
                        <div class="node-dot"></div>
                        <div>
                            <div style="font-weight: 500;">${n.name}</div>
                            <div style="font-size: 0.75rem; color: var(--text-dim);">${n.type}</div>
                        </div>
                    </div>
                `).join('');
            } catch (e) {
                container.innerHTML = '<p>Error loading nodes</p>';
            }
        }
        
        // Modals
        function showModal(id) {
            document.getElementById(id).classList.add('show');
        }
        
        function hideModal(id) {
            document.getElementById(id).classList.remove('show');
        }
        
        function showAddTaskModal() { showModal('task-modal'); }
        function showAddMemoryModal() { showModal('memory-modal'); }
        
        async function addTask() {
            const data = {
                title: document.getElementById('task-title').value,
                priority: document.getElementById('task-priority').value,
                due_date: document.getElementById('task-due').value,
                category: document.getElementById('task-category').value
            };
            
            if (!data.title) {
                alert('Please enter a task title');
                return;
            }
            
            await apiFetch(API + '/api/tasks', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            });
            
            hideModal('task-modal');
            document.getElementById('task-title').value = '';
            document.getElementById('task-due').value = '';
            loadTasks();
        }
        
        async function addMemory() {
            const data = {
                key: document.getElementById('memory-key').value,
                value: document.getElementById('memory-value').value,
                category: document.getElementById('memory-category').value
            };
            
            if (!data.key || !data.value) {
                alert('Please enter key and value');
                return;
            }
            
            await apiFetch(API + '/api/memory', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            });
            
            hideModal('memory-modal');
            document.getElementById('memory-key').value = '';
            document.getElementById('memory-value').value = '';
            loadMemory();
        }
        
        async function changeProvider(provider) {
            await apiFetch(API + '/api/config', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({provider: provider})
            });
            loadStatus();
        }
        
        function showMeshKey() {
            apiFetch(API + '/api/config')
                .then(r => r.json())
                .then(d => alert('Mesh key functionality coming soon'));
        }
        
        // ── Connect Browse ───────────────────────────────────
        async function loadConnectBrowse() {
            const container = document.getElementById('connect-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            try {
                const res = await apiFetch(API + '/api/connect/browse');
                const data = await res.json();
                const services = data.services || {};
                const keys = Object.keys(services);
                if (keys.length === 0) {
                    container.innerHTML = '<div class="browse-empty">'
                        + '<p>No services connected yet.</p>'
                        + '<button class="browse-btn-configure" onclick="loadConnectWizard()">'
                        + '<i class="fas fa-plug"></i> Configure Services</button></div>';
                    return;
                }
                let html = '<div class="browse-services">';
                keys.forEach(key => {
                    const svc = services[key];
                    html += '<div class="browse-service-card">';
                    html += '<div class="browse-service-header">';
                    html += '<div class="icon"><i class="' + escapeHtml(svc.icon || 'fas fa-plug') + '"></i></div>';
                    html += '<div class="name">' + escapeHtml(svc.display_name || key) + '</div>';
                    if (svc.summary) html += '<div class="summary">' + escapeHtml(svc.summary) + '</div>';
                    html += '</div>';
                    if (svc.error) {
                        html += '<div class="browse-error">' + escapeHtml(svc.error) + '</div>';
                    }
                    (svc.items || []).forEach(item => {
                        html += '<div class="browse-item">';
                        html += '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                        if (item.detail) html += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                        html += '</div>';
                    });
                    if (key === 'browser') {
                        html += '<div class="browse-url-bar">'
                            + '<input type="text" id="browse-url-input" placeholder="Enter URL..." />'
                            + '<button id="browse-url-go">Go</button>'
                            + '</div>'
                            + '<div id="browse-page-preview"></div>';
                    } else {
                        html += '<div class="browse-url-bar">'
                            + '<input type="text" class="browse-search-input" data-service="' + escapeHtml(key) + '" placeholder="Search ' + escapeHtml(svc.display_name || key) + '..." />'
                            + '<button class="browse-search-go" data-service="' + escapeHtml(key) + '">Search</button>'
                            + '</div>'
                            + '<div class="browse-search-results" data-service="' + escapeHtml(key) + '"></div>';
                        if (key === 'video') {
                            html += '<div class="video-online-search">'
                                + '<div style="font-size:0.78rem;color:var(--text-dim);margin-top:10px"><i class="fas fa-globe"></i> Search online</div>'
                                + '<div class="browse-url-bar">'
                                + '<input type="text" id="video-online-search-input" placeholder="Search videos online..." />'
                                + '<button id="video-online-search-go">Search</button>'
                                + '</div>'
                                + '<div id="video-online-results"></div>'
                                + '</div>';
                        }
                    }
                    html += '</div>';
                });
                html += '</div>';
                html += '<button class="browse-btn-configure" onclick="loadConnectWizard()">'
                    + '<i class="fas fa-cog"></i> Configure</button>';
                container.innerHTML = html;
                // Wire up browser URL bar
                const urlInput = document.getElementById('browse-url-input');
                const urlGo = document.getElementById('browse-url-go');
                if (urlInput && urlGo) {
                    const doNavigate = async () => {
                        const url = urlInput.value.trim();
                        if (!url) return;
                        const preview = document.getElementById('browse-page-preview');
                        preview.innerHTML = '<div class="spinner"></div>';
                        urlGo.disabled = true;
                        try {
                            const r = await apiFetch(API + '/api/connect/browse/browser/navigate', {
                                method: 'POST',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({url: url})
                            });
                            const d = await r.json();
                            if (d.success) {
                                if (d.is_video) {
                                    preview.innerHTML = '<div class="browse-page-preview">'
                                        + '<div class="preview-title">' + escapeHtml(d.title || '') + '</div>'
                                        + '<div class="preview-url">' + escapeHtml(d.url || '') + '</div>'
                                        + '<div class="video-actions">'
                                        + '<button class="video-play-btn" data-url="' + escapeHtml(d.url) + '">'
                                        + '<i class="fas fa-play"></i> Play (Ad-Free)</button>'
                                        + '<select class="video-quality-select"><option>360p</option>'
                                        + '<option selected>720p</option><option>1080p</option></select>'
                                        + '<button class="video-download-btn" data-url="' + escapeHtml(d.url) + '">'
                                        + '<i class="fas fa-download"></i> Download</button>'
                                        + '</div>'
                                        + '<div id="video-player-area"></div>'
                                        + '<div class="video-download-status" id="video-dl-status"></div>'
                                        + '</div>';
                                    // Wire play button
                                    preview.querySelector('.video-play-btn').addEventListener('click', async function() {
                                        const area = document.getElementById('video-player-area');
                                        area.innerHTML = '<div class="spinner"></div>';
                                        try {
                                            const er = await apiFetch(API + '/api/connect/browse/browser/video/embed', {
                                                method: 'POST',
                                                headers: {'Content-Type': 'application/json'},
                                                body: JSON.stringify({url: this.dataset.url})
                                            });
                                            const ed = await er.json();
                                            if (ed.success) {
                                                area.innerHTML = '<button class="video-close-btn" title="Close player"><i class="fas fa-times"></i></button>' + ed.html;
                                                area.querySelector('.video-close-btn').addEventListener('click', () => { area.innerHTML = ''; });
                                            } else {
                                                area.innerHTML = '<div class="browse-error">' + escapeHtml(ed.error || 'Embed failed') + '</div>';
                                            }
                                        } catch (e) {
                                            area.innerHTML = '<div class="browse-error">Failed to load player</div>';
                                        }
                                    });
                                    // Wire download button
                                    preview.querySelector('.video-download-btn').addEventListener('click', async function() {
                                        const status = document.getElementById('video-dl-status');
                                        const quality = preview.querySelector('.video-quality-select').value;
                                        this.disabled = true;
                                        status.innerHTML = '<div class="spinner" style="display:inline-block;width:14px;height:14px"></div> Downloading at ' + escapeHtml(quality) + '...';
                                        try {
                                            const dr = await apiFetch(API + '/api/connect/browse/browser/video/download', {
                                                method: 'POST',
                                                headers: {'Content-Type': 'application/json'},
                                                body: JSON.stringify({url: this.dataset.url, quality: quality})
                                            });
                                            const dd = await dr.json();
                                            if (dd.success) {
                                                status.textContent = 'Downloaded: ' + dd.title + ' (' + dd.size + ', ' + dd.duration + ')';
                                            } else {
                                                status.textContent = 'Download failed: ' + (dd.error || 'Unknown error');
                                            }
                                        } catch (e) {
                                            status.textContent = 'Download request failed';
                                        }
                                        this.disabled = false;
                                    });
                                } else {
                                    preview.innerHTML = '<div class="browse-page-preview">'
                                        + '<div class="preview-title">' + escapeHtml(d.title || '') + '</div>'
                                        + '<div class="preview-url">' + escapeHtml(d.url || '') + '</div>'
                                        + escapeHtml(d.content || '(empty page)')
                                        + '</div>';
                                }
                            } else {
                                preview.innerHTML = '<div class="browse-error">' + escapeHtml(d.error || 'Navigation failed') + '</div>';
                            }
                        } catch (err) {
                            preview.innerHTML = '<div class="browse-error">Request failed</div>';
                        }
                        urlGo.disabled = false;
                    };
                    urlGo.addEventListener('click', doNavigate);
                    urlInput.addEventListener('keydown', e => { if (e.key === 'Enter') doNavigate(); });
                }
                // Wire up service search bars
                document.querySelectorAll('.browse-search-go').forEach(btn => {
                    const svcKey = btn.dataset.service;
                    const input = document.querySelector('.browse-search-input[data-service="' + svcKey + '"]');
                    const resultsDiv = document.querySelector('.browse-search-results[data-service="' + svcKey + '"]');
                    if (!input || !resultsDiv) return;
                    const doSearch = async () => {
                        const q = input.value.trim();
                        if (!q) return;
                        btn.disabled = true;
                        resultsDiv.innerHTML = '<div class="spinner"></div>';
                        try {
                            const r = await apiFetch(API + '/api/connect/browse/search', {
                                method: 'POST',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({service: svcKey, query: q})
                            });
                            const d = await r.json();
                            if (d.error) {
                                resultsDiv.innerHTML = '<div class="browse-error">' + escapeHtml(d.error) + '</div>';
                            } else if ((d.items || []).length === 0) {
                                resultsDiv.innerHTML = '<div class="browse-error">No results found</div>';
                            } else {
                                let rhtml = '';
                                (d.items || []).forEach(item => {
                                    rhtml += '<div class="browse-item">';
                                    rhtml += '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                                    if (item.detail) rhtml += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                                    rhtml += '</div>';
                                });
                                if (d.summary) rhtml += '<div class="item-detail" style="margin-top:4px">' + escapeHtml(d.summary) + '</div>';
                                resultsDiv.innerHTML = rhtml;
                            }
                        } catch (err) {
                            resultsDiv.innerHTML = '<div class="browse-error">Search request failed</div>';
                        }
                        btn.disabled = false;
                    };
                    btn.addEventListener('click', doSearch);
                    input.addEventListener('keydown', e => { if (e.key === 'Enter') doSearch(); });
                });
                // Wire up online video search (Invidious)
                const vidSearchInput = document.getElementById('video-online-search-input');
                const vidSearchGo = document.getElementById('video-online-search-go');
                if (vidSearchInput && vidSearchGo) {
                    const doVidSearch = async () => {
                        const q = vidSearchInput.value.trim();
                        if (!q) return;
                        vidSearchGo.disabled = true;
                        const resultsDiv = document.getElementById('video-online-results');
                        resultsDiv.innerHTML = '<div class="spinner"></div>';
                        try {
                            const r = await apiFetch(API + '/api/connect/browse/video/search', {
                                method: 'POST',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({query: q})
                            });
                            const d = await r.json();
                            if (d.error) {
                                resultsDiv.innerHTML = '<div class="browse-error">' + escapeHtml(d.error) + '</div>';
                            } else if ((d.items || []).length === 0) {
                                resultsDiv.innerHTML = '<div class="browse-error">No results found</div>';
                            } else {
                                let rhtml = '';
                                (d.items || []).forEach(item => {
                                    rhtml += '<div class="video-search-result">';
                                    if (item.thumbnail) rhtml += '<img class="video-thumbnail" src="' + escapeHtml(item.thumbnail) + '" alt="" />';
                                    rhtml += '<div style="flex:1;min-width:0">'
                                        + '<div class="item-name">' + escapeHtml(item.name || '') + '</div>';
                                    if (item.detail) rhtml += '<div class="item-detail">' + escapeHtml(item.detail) + '</div>';
                                    rhtml += '<div class="video-actions">'
                                        + '<button class="video-play-btn" data-url="' + escapeHtml(item.url || '') + '"><i class="fas fa-play"></i> Play</button>'
                                        + '<button class="video-download-btn" data-url="' + escapeHtml(item.url || '') + '"><i class="fas fa-download"></i> Download</button>'
                                        + '</div></div></div>';
                                });
                                if (d.summary) rhtml += '<div class="item-detail" style="margin-top:4px">' + escapeHtml(d.summary) + '</div>';
                                resultsDiv.innerHTML = rhtml;
                                // Wire play/download buttons in search results
                                resultsDiv.querySelectorAll('.video-play-btn').forEach(pbtn => {
                                    pbtn.addEventListener('click', async function() {
                                        const parent = this.closest('.video-search-result');
                                        let area = parent.querySelector('.video-result-player');
                                        if (!area) { area = document.createElement('div'); area.className = 'video-result-player'; parent.appendChild(area); }
                                        area.innerHTML = '<div class="spinner"></div>';
                                        try {
                                            const er = await apiFetch(API + '/api/connect/browse/browser/video/embed', {
                                                method: 'POST',
                                                headers: {'Content-Type': 'application/json'},
                                                body: JSON.stringify({url: this.dataset.url})
                                            });
                                            const ed = await er.json();
                                            if (ed.success) {
                                                area.innerHTML = '<button class="video-close-btn" title="Close player"><i class="fas fa-times"></i></button>' + ed.html;
                                                area.querySelector('.video-close-btn').addEventListener('click', () => { area.innerHTML = ''; });
                                            } else {
                                                area.innerHTML = '<div class="browse-error">' + escapeHtml(ed.error || 'Embed failed') + '</div>';
                                            }
                                        } catch (e) { area.innerHTML = '<div class="browse-error">Failed to load player</div>'; }
                                    });
                                });
                                resultsDiv.querySelectorAll('.video-download-btn').forEach(dbtn => {
                                    dbtn.addEventListener('click', async function() {
                                        this.disabled = true;
                                        this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Downloading...';
                                        try {
                                            const dr = await apiFetch(API + '/api/connect/browse/browser/video/download', {
                                                method: 'POST',
                                                headers: {'Content-Type': 'application/json'},
                                                body: JSON.stringify({url: this.dataset.url, quality: '720p'})
                                            });
                                            const dd = await dr.json();
                                            this.innerHTML = dd.success
                                                ? '<i class="fas fa-check"></i> ' + escapeHtml(dd.size)
                                                : '<i class="fas fa-times"></i> Failed';
                                        } catch (e) { this.innerHTML = '<i class="fas fa-times"></i> Failed'; }
                                        this.disabled = false;
                                    });
                                });
                            }
                        } catch (err) {
                            resultsDiv.innerHTML = '<div class="browse-error">Search request failed</div>';
                        }
                        vidSearchGo.disabled = false;
                    };
                    vidSearchGo.addEventListener('click', doVidSearch);
                    vidSearchInput.addEventListener('keydown', e => { if (e.key === 'Enter') doVidSearch(); });
                }
            } catch (e) {
                container.innerHTML = '<p>Error loading services</p>';
            }
        }

        // ── Connect Wizard ───────────────────────────────────
        async function loadConnectWizard() {
            const container = document.getElementById('connect-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            try {
                const res = await apiFetch(API + '/api/connect/wizard/start', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({args: []})
                });
                const data = await res.json();
                container.innerHTML = '';
                const backBtn = document.createElement('button');
                backBtn.className = 'browse-btn-configure';
                backBtn.innerHTML = '<i class="fas fa-arrow-left"></i> Back to overview';
                backBtn.onclick = () => loadConnectBrowse();
                container.appendChild(backBtn);
                renderWizardMessages(container, data.wizard_messages || []);
            } catch (e) {
                container.innerHTML = '<p>Error loading connect wizard</p>';
            }
        }

        function renderWizardMessages(container, messages) {
            messages.forEach(msg => {
                if (msg.type === 'card_menu') {
                    container.appendChild(renderCardMenu(msg));
                } else if (msg.type === 'menu') {
                    container.appendChild(renderMenu(msg));
                } else if (msg.type === 'text') {
                    const div = document.createElement('div');
                    div.className = 'wizard-text';
                    div.innerHTML = msg.text;
                    container.appendChild(div);
                }
            });
        }

        function renderCardMenu(msg) {
            const card = document.createElement('div');
            card.className = 'wizard-card';

            // Tabs
            const tabBar = document.createElement('div');
            tabBar.className = 'wizard-tabs';
            (msg.tabs || []).forEach(tab => {
                const btn = document.createElement('button');
                btn.className = 'wizard-tab' + (tab.active ? ' active' : '');
                btn.textContent = tab.icon + ' ' + tab.label;
                btn.onclick = () => wizardSwitchTab(tab.id);
                tabBar.appendChild(btn);
            });
            card.appendChild(tabBar);

            // Tab content text
            if (msg.tab_content) {
                const content = document.createElement('div');
                content.className = 'wizard-tab-content';
                content.innerHTML = msg.tab_content;
                card.appendChild(content);
            }

            // Service buttons
            if (msg.tab_options && msg.tab_options.length > 0) {
                const btns = document.createElement('div');
                btns.className = 'wizard-buttons';
                msg.tab_options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.className = 'wizard-btn';
                    btn.textContent = opt.label;
                    btn.onclick = () => wizardSelect(opt.key);
                    btns.appendChild(btn);
                });
                card.appendChild(btns);
            }

            return card;
        }

        function renderMenu(msg) {
            const card = document.createElement('div');
            card.className = 'wizard-card';

            if (msg.text) {
                const text = document.createElement('div');
                text.className = 'wizard-tab-content';
                text.innerHTML = msg.text;
                card.appendChild(text);
            }

            if (msg.options && msg.options.length > 0) {
                const btns = document.createElement('div');
                btns.className = 'wizard-buttons';
                msg.options.forEach(opt => {
                    const btn = document.createElement('button');
                    btn.className = 'wizard-btn';
                    btn.textContent = opt.label;
                    btn.onclick = () => wizardSelect(opt.key);
                    btns.appendChild(btn);
                });
                card.appendChild(btns);
            }

            return card;
        }

        async function wizardSelect(key) {
            const container = document.getElementById('connect-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            try {
                const res = await apiFetch(API + '/api/connect/wizard/select', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({key: key})
                });
                const data = await res.json();
                container.innerHTML = '';
                renderWizardMessages(container, data.wizard_messages || []);
                maybeAddWizardInput(container);
            } catch (e) {
                container.innerHTML = '<p>Error communicating with wizard</p>';
            }
        }

        async function wizardSwitchTab(tabId) {
            const container = document.getElementById('connect-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            try {
                const res = await apiFetch(API + '/api/connect/wizard/tab', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({tab: tabId})
                });
                const data = await res.json();
                container.innerHTML = '';
                renderWizardMessages(container, data.wizard_messages || []);
            } catch (e) {
                container.innerHTML = '<p>Error switching tab</p>';
            }
        }

        async function wizardSendInput() {
            const inp = document.getElementById('wizard-text-input');
            if (!inp) return;
            const text = inp.value.trim();
            if (!text) return;
            inp.value = '';

            const container = document.getElementById('connect-content');
            container.innerHTML = '<div class="loading"><div class="spinner"></div></div>';
            try {
                const res = await apiFetch(API + '/api/connect/wizard/input', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({text: text})
                });
                const data = await res.json();
                container.innerHTML = '';
                renderWizardMessages(container, data.wizard_messages || []);
                maybeAddWizardInput(container);
            } catch (e) {
                container.innerHTML = '<p>Error sending input</p>';
            }
        }

        function maybeAddWizardInput(container) {
            // Add an input field if wizard is waiting for text (no buttons visible)
            const hasButtons = container.querySelector('.wizard-buttons');
            if (!hasButtons) {
                const wrap = document.createElement('div');
                wrap.className = 'wizard-input-wrap';
                wrap.innerHTML = '<input type="text" id="wizard-text-input" placeholder="Type your answer...">'
                    + '<button class="btn btn-primary" onclick="wizardSendInput()">Send</button>';
                container.appendChild(wrap);
                const inp = document.getElementById('wizard-text-input');
                if (inp) {
                    inp.focus();
                    inp.addEventListener('keypress', e => { if (e.key === 'Enter') wizardSendInput(); });
                }
            }
        }

        function addWizardMessage(msg) {
            const wrap = document.createElement('div');
            wrap.className = 'message assistant';
            if (msg.type === 'card_menu') {
                wrap.appendChild(renderCardMenu(msg));
            } else if (msg.type === 'menu') {
                wrap.appendChild(renderMenu(msg));
            } else if (msg.type === 'text') {
                wrap.innerHTML = '<p>' + msg.text + '</p>';
            }
            messagesDiv.appendChild(wrap);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        // ── Terminal ─────────────────────────────────────────
        const termInput = document.getElementById('terminal-input');
        const termOutput = document.getElementById('terminal-output');
        let termHistory = [];
        let termHistIdx = -1;
        let termStreaming = false;

        termInput.addEventListener('keydown', e => {
            if (e.key === 'Enter' && !termStreaming) {
                terminalSend();
            } else if (e.key === 'ArrowUp') {
                e.preventDefault();
                if (termHistory.length && termHistIdx < termHistory.length - 1) {
                    termHistIdx++;
                    termInput.value = termHistory[termHistory.length - 1 - termHistIdx];
                }
            } else if (e.key === 'ArrowDown') {
                e.preventDefault();
                if (termHistIdx > 0) {
                    termHistIdx--;
                    termInput.value = termHistory[termHistory.length - 1 - termHistIdx];
                } else {
                    termHistIdx = -1;
                    termInput.value = '';
                }
            }
        });

        async function loadTerminalModel() {
            try {
                const res = await apiFetch(API + '/api/status');
                const data = await res.json();
                document.getElementById('terminal-model').textContent = data.provider || '';
            } catch (_) {}
        }

        function termAddLine(text, cls) {
            const line = document.createElement('div');
            line.className = 'terminal-line ' + cls;
            line.textContent = text;
            termOutput.appendChild(line);
            termOutput.scrollTop = termOutput.scrollHeight;
            return line;
        }

        function termFormatCode(raw) {
            // Convert triple-backtick blocks to <pre><code>
            return raw.replace(/```(?:\w*)\n([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
        }

        async function terminalSend() {
            const text = termInput.value.trim();
            if (!text) return;

            termHistory.push(text);
            termHistIdx = -1;
            termInput.value = '';

            termAddLine(text, 'user');

            termStreaming = true;
            termInput.disabled = true;

            // Create response line with blinking cursor
            const respLine = document.createElement('div');
            respLine.className = 'terminal-line assistant';
            const cursor = document.createElement('span');
            cursor.className = 'terminal-cursor';
            respLine.appendChild(cursor);
            termOutput.appendChild(respLine);
            termOutput.scrollTop = termOutput.scrollHeight;

            let accumulated = '';

            try {
                const res = await apiFetch(API + '/api/terminal/stream', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({message: text})
                });

                const reader = res.body.getReader();
                const decoder = new TextDecoder();
                let buffer = '';

                while (true) {
                    const {done, value} = await reader.read();
                    if (done) break;

                    buffer += decoder.decode(value, {stream: true});
                    const lines = buffer.split('\n');
                    buffer = lines.pop(); // keep incomplete line

                    for (const line of lines) {
                        if (!line.startsWith('data: ')) continue;
                        let evt;
                        try { evt = JSON.parse(line.slice(6)); } catch(_) { continue; }

                        if (evt.type === 'text') {
                            accumulated += evt.content;
                            // Update response line (keep cursor at end)
                            respLine.innerHTML = '';
                            const span = document.createElement('span');
                            span.innerHTML = termFormatCode(escapeHtml(accumulated));
                            respLine.appendChild(span);
                            respLine.appendChild(cursor);
                            termOutput.scrollTop = termOutput.scrollHeight;
                        } else if (evt.type === 'tool_start') {
                            termAddLine('[Running ' + evt.tool_name + '...]', 'tool');
                        } else if (evt.type === 'tool_end') {
                            termAddLine('[Done: ' + evt.tool_name + ']', 'tool');
                        } else if (evt.type === 'error') {
                            termAddLine('Error: ' + (evt.error || 'Unknown'), 'error');
                        } else if (evt.type === 'done') {
                            break;
                        }
                    }
                }
            } catch (e) {
                termAddLine('Error: connection failed', 'error');
            }

            // Remove cursor, finalize response
            if (cursor.parentNode) cursor.remove();
            if (!accumulated) respLine.remove(); // no content produced

            termStreaming = false;
            termInput.disabled = false;
            termInput.focus();
        }

        // ── Host Shell Terminal ──────────────────────────────────
        const shellInput = document.getElementById('shell-input');
        const shellOutput = document.getElementById('shell-output');
        const shellKillBtn = document.getElementById('shell-kill-btn');
        let shellHistory = [];
        let shellHistIdx = -1;
        let shellRunning = false;

        // Check if shell is enabled and show nav item
        (async function checkShellEnabled() {
            try {
                const res = await apiFetch(API + '/api/shell/enabled');
                const data = await res.json();
                if (data.enabled) {
                    document.getElementById('shell-nav').style.display = '';
                }
            } catch (_) {}
        })();

        function shellFocusInput() {
            if (shellInput) shellInput.focus();
        }

        function stripAnsi(str) {
            return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
        }

        function shellAddLine(text, cls) {
            const line = document.createElement('div');
            line.className = 'shell-line ' + cls;
            line.textContent = text;
            shellOutput.appendChild(line);
            shellOutput.scrollTop = shellOutput.scrollHeight;
            return line;
        }

        if (shellInput) {
            shellInput.addEventListener('keydown', e => {
                if (e.key === 'Enter' && !shellRunning) {
                    shellSend();
                } else if (e.key === 'ArrowUp') {
                    e.preventDefault();
                    if (shellHistory.length && shellHistIdx < shellHistory.length - 1) {
                        shellHistIdx++;
                        shellInput.value = shellHistory[shellHistory.length - 1 - shellHistIdx];
                    }
                } else if (e.key === 'ArrowDown') {
                    e.preventDefault();
                    if (shellHistIdx > 0) {
                        shellHistIdx--;
                        shellInput.value = shellHistory[shellHistory.length - 1 - shellHistIdx];
                    } else {
                        shellHistIdx = -1;
                        shellInput.value = '';
                    }
                } else if (e.key === 'c' && e.ctrlKey) {
                    e.preventDefault();
                    if (shellRunning) shellKill();
                }
            });
        }

        async function shellSend() {
            const text = shellInput.value.trim();
            if (!text) return;

            shellHistory.push(text);
            shellHistIdx = -1;
            shellInput.value = '';

            shellAddLine(text, 'command');

            shellRunning = true;
            shellInput.disabled = true;
            shellKillBtn.style.display = 'inline-block';
            document.getElementById('shell-status').textContent = 'running...';

            try {
                const res = await apiFetch(API + '/api/shell/stream', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({command: text})
                });

                if (!res.ok) {
                    const err = await res.json().catch(() => ({}));
                    shellAddLine(err.error || 'Request failed', 'error');
                    shellRunning = false;
                    shellInput.disabled = false;
                    shellKillBtn.style.display = 'none';
                    document.getElementById('shell-status').textContent = '';
                    shellInput.focus();
                    return;
                }

                const reader = res.body.getReader();
                const decoder = new TextDecoder();
                let buffer = '';

                while (true) {
                    const {done, value} = await reader.read();
                    if (done) break;

                    buffer += decoder.decode(value, {stream: true});
                    const lines = buffer.split('\n');
                    buffer = lines.pop();

                    for (const line of lines) {
                        if (!line.startsWith('data: ')) continue;
                        let evt;
                        try { evt = JSON.parse(line.slice(6)); } catch(_) { continue; }

                        if (evt.type === 'output') {
                            const clean = stripAnsi(evt.content).replace(/\n$/, '');
                            if (clean) shellAddLine(clean, 'output');
                        } else if (evt.type === 'exit') {
                            const cls = evt.code === 0 ? 'exit-ok' : 'exit-fail';
                            shellAddLine('exit ' + evt.code, cls);
                        } else if (evt.type === 'error') {
                            shellAddLine('Error: ' + (evt.error || 'Unknown'), 'error');
                        } else if (evt.type === 'done') {
                            break;
                        }
                    }
                }
            } catch (e) {
                shellAddLine('Error: connection failed', 'error');
            }

            shellRunning = false;
            shellInput.disabled = false;
            shellKillBtn.style.display = 'none';
            document.getElementById('shell-status').textContent = '';
            shellInput.focus();
        }

        async function shellKill() {
            try {
                await apiFetch(API + '/api/shell/kill', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: '{}'
                });
            } catch (_) {}
        }

        // Model Routing
        let _routingConfig = null;
        const CATEGORY_ICONS = {
            simple: 'fas fa-bolt', moderate: 'fas fa-comment',
            complex: 'fas fa-brain', coding: 'fas fa-code',
            creative: 'fas fa-palette', private: 'fas fa-lock',
            long: 'fas fa-file-alt'
        };

        async function loadShellToggle() {
            try {
                const res = await apiFetch(API + '/api/shell/enabled');
                const data = await res.json();
                const tog = document.getElementById('toggle-shell');
                if (tog) tog.classList.toggle('on', data.enabled);
            } catch (_) {}
        }

        async function toggleShell() {
            const tog = document.getElementById('toggle-shell');
            const nowOn = !tog.classList.contains('on');
            try {
                const res = await apiFetch(API + '/api/shell/enabled', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({enabled: nowOn})
                });
                const data = await res.json();
                tog.classList.toggle('on', data.enabled);
                document.getElementById('shell-nav').style.display = data.enabled ? '' : 'none';
            } catch (_) {}
        }

        async function loadRoutingConfig() {
            try {
                const resp = await apiFetch('/api/config/routing');
                _routingConfig = await resp.json();
                renderRoutingCard(_routingConfig);
            } catch(e) {
                document.getElementById('routing-content').innerHTML =
                    '<div class="routing-note">Could not load routing config.</div>';
            }
        }

        function renderRoutingCard(cfg) {
            const el = document.getElementById('routing-content');
            if (!cfg.router_active) {
                el.innerHTML = '<div class="routing-note">' +
                    '<i class="fas fa-info-circle"></i> ' +
                    'Add a second LLM provider to enable model routing.</div>';
                return;
            }
            const isManual = cfg.mode === 'manual';
            let html = '<div class="routing-toggle-bar">' +
                '<div><span class="label">Mode:</span>' +
                '<span class="mode-value">' + (isManual ? 'Manual' : 'Auto') + '</span></div>' +
                '<div class="toggle ' + (isManual ? 'on' : '') + '" ' +
                'onclick="toggleRoutingMode()"></div></div>';

            html += '<table class="routing-table"><thead><tr>' +
                '<th class="col-cat">Category</th>' +
                '<th class="col-desc">Description</th>' +
                '<th class="col-model">Model</th></tr></thead><tbody>';

            const cats = cfg.categories || {};
            for (const [key, cat] of Object.entries(cats)) {
                html += '<tr><td class="col-cat">' +
                    key.charAt(0).toUpperCase() + key.slice(1) +
                    '</td><td class="col-desc">' + (cat.description || '') + '</td>' +
                    '<td class="col-model">';
                if (isManual) {
                    html += '<select onchange="setManualRoute(\'' + key + '\', this.value)">';
                    for (const m of cfg.available_models) {
                        const sel = m === cat.model ? ' selected' : '';
                        html += '<option value="' + m + '"' + sel + '>' + m + '</option>';
                    }
                    html += '</select>';
                } else {
                    html += '<span class="auto-label">' + cat.auto_model + '</span>';
                }
                html += '</td></tr>';
            }
            html += '</tbody></table>';
            el.innerHTML = html;
        }

        async function toggleRoutingMode() {
            if (!_routingConfig) return;
            const newMode = _routingConfig.mode === 'manual' ? 'auto' : 'manual';
            const body = { mode: newMode };
            if (newMode === 'manual' && _routingConfig.categories) {
                const routes = {};
                for (const [k, v] of Object.entries(_routingConfig.categories)) {
                    routes[k] = v.auto_model;
                }
                body.routes = routes;
            }
            try {
                const resp = await apiFetch('/api/config/routing', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(body)
                });
                _routingConfig = await resp.json();
                renderRoutingCard(_routingConfig);
            } catch(e) { console.error('Routing toggle failed', e); }
        }

        async function setManualRoute(category, model) {
            if (!_routingConfig) return;
            const routes = {};
            for (const [k, v] of Object.entries(_routingConfig.categories)) {
                routes[k] = v.model;
            }
            routes[category] = model;
            try {
                const resp = await apiFetch('/api/config/routing', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({ mode: 'manual', routes })
                });
                _routingConfig = await resp.json();
                renderRoutingCard(_routingConfig);
            } catch(e) { console.error('Route update failed', e); }
        }

        // Init
        loadStatus();
        setInterval(loadStatus, 30000);
        setInterval(loadNodes, 15000);
    </script>
</body>
</html>
